2020-07-03 18:35:02 +02:00
/*
* © 2020 , Chris Harlow . All rights reserved .
*
* This file is part of Asbelos DCC API
*
* This is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* It is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with CommandStation . If not , see < https : //www.gnu.org/licenses/>.
*/
2020-06-03 15:26:49 +02:00
/**********************************************************************
DCC + + BASE STATION supports optional OUTPUT control of any unused Arduino Pins for custom purposes .
Pins can be activited or de - activated . The default is to set ACTIVE pins HIGH and INACTIVE pins LOW .
However , this default behavior can be inverted for any pin in which case ACTIVE = LOW and INACTIVE = HIGH .
Definitions and state ( ACTIVE / INACTIVE ) for pins are retained in EEPROM and restored on power - up .
The default is to set each defined pin to active or inactive according to its restored state .
However , the default behavior can be modified so that any pin can be forced to be either active or inactive
upon power - up regardless of its previous state before power - down .
To have this sketch utilize one or more Arduino pins as custom outputs , first define / edit / delete
output definitions using the following variation of the " Z " command :
< Z ID PIN IFLAG > : creates a new output ID , with specified PIN and IFLAG values .
if output ID already exists , it is updated with specificed PIN and IFLAG .
note : output state will be immediately set to ACTIVE / INACTIVE and pin will be set to HIGH / LOW
according to IFLAG value specifcied ( see below ) .
returns : < O > if successful and < X > if unsuccessful ( e . g . out of memory )
< Z ID > : deletes definition of output ID
returns : < O > if successful and < X > if unsuccessful ( e . g . ID does not exist )
< Z > : lists all defined output pins
returns : < Y ID PIN IFLAG STATE > for each defined output pin or < X > if no output pins defined
where
ID : the numeric ID ( 0 - 32767 ) of the output
PIN : the arduino pin number to use for the output
STATE : the state of the output ( 0 = INACTIVE / 1 = ACTIVE )
IFLAG : defines the operational behavior of the output based on bits 0 , 1 , and 2 as follows :
IFLAG , bit 0 : 0 = forward operation ( ACTIVE = HIGH / INACTIVE = LOW )
1 = inverted operation ( ACTIVE = LOW / INACTIVE = HIGH )
IFLAG , bit 1 : 0 = state of pin restored on power - up to either ACTIVE or INACTIVE depending
on state before power - down ; state of pin set to INACTIVE when first created
1 = state of pin set on power - up , or when first created , to either ACTIVE of INACTIVE
depending on IFLAG , bit 2
IFLAG , bit 2 : 0 = state of pin set to INACTIVE uponm power - up or when first created
1 = state of pin set to ACTIVE uponm power - up or when first created
Once all outputs have been properly defined , use the < E > command to store their definitions to EEPROM .
If you later make edits / additions / deletions to the output definitions , you must invoke the < E > command if you want those
new definitions updated in the EEPROM . You can also clear everything stored in the EEPROM by invoking the < e > command .
To change the state of outputs that have been defined use :
< Z ID STATE > : sets output ID to either ACTIVE or INACTIVE state
returns : < Y ID STATE > , or < X > if turnout ID does not exist
where
ID : the numeric ID ( 0 - 32767 ) of the turnout to control
STATE : the state of the output ( 0 = INACTIVE / 1 = ACTIVE )
When controlled as such , the Arduino updates and stores the direction of each output in EEPROM so
that it is retained even without power . A list of the current states of each output in the form < Y ID STATE > is generated
by this sketch whenever the < s > status command is invoked . This provides an efficient way of initializing
the state of any outputs being monitored or controlled by a separate interface or GUI program .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "Outputs.h"
# include "EEStore.h"
2020-12-27 16:20:11 +01:00
# include "StringFormatter.h"
2021-10-15 22:10:50 +02:00
# include "DCCWaveform.h"
2020-09-06 13:44:19 +02:00
2020-12-27 16:20:11 +01:00
// print all output states to stream
void Output : : printAll ( Print * stream ) {
for ( Output * tt = Output : : firstOutput ; tt ! = NULL ; tt = tt - > nextOutput )
2021-03-30 23:01:37 +02:00
StringFormatter : : send ( stream , F ( " <Y %d %d> \n " ) , tt - > data . id , tt - > data . oStatus ) ;
2020-12-27 16:20:11 +01:00
} // Output::printAll
2020-08-15 12:32:32 +02:00
2020-06-03 15:26:49 +02:00
void Output : : activate ( int s ) {
data . oStatus = ( s > 0 ) ; // if s>0, set status to active, else inactive
2020-08-15 12:32:32 +02:00
digitalWrite ( data . pin , data . oStatus ^ bitRead ( data . iFlag , 0 ) ) ; // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW)
2020-06-03 15:26:49 +02:00
if ( num > 0 )
EEPROM . put ( num , data . oStatus ) ;
}
///////////////////////////////////////////////////////////////////////////////
2021-07-24 21:11:18 +02:00
Output * Output : : get ( uint16_t n ) {
2020-06-03 15:26:49 +02:00
Output * tt ;
for ( tt = firstOutput ; tt ! = NULL & & tt - > data . id ! = n ; tt = tt - > nextOutput ) ;
return ( tt ) ;
}
///////////////////////////////////////////////////////////////////////////////
2021-07-24 21:11:18 +02:00
bool Output : : remove ( uint16_t n ) {
2020-06-03 15:26:49 +02:00
Output * tt , * pp = NULL ;
for ( tt = firstOutput ; tt ! = NULL & & tt - > data . id ! = n ; pp = tt , tt = tt - > nextOutput ) ;
if ( tt = = NULL ) return false ;
if ( tt = = firstOutput )
firstOutput = tt - > nextOutput ;
else
pp - > nextOutput = tt - > nextOutput ;
free ( tt ) ;
return true ;
}
///////////////////////////////////////////////////////////////////////////////
void Output : : load ( ) {
2021-07-25 22:53:20 +02:00
struct BrokenOutputData bdata ;
2020-06-03 15:26:49 +02:00
Output * tt ;
2021-07-25 22:53:20 +02:00
bool isBroken = 1 ;
// This is a scary kluge. As we have two formats in EEPROM due to an
// earlier bug, we don't know which we encounter now. So we guess
// that if in all entries this byte has value of 7 or lower this is
// an iFlag and thus the broken format. Otherwise it would be a pin
// id. If someone uses only pins 0 to 7 of their arduino, they
// loose. This is (if you look at an arduino) however unlikely.
2020-06-03 15:26:49 +02:00
2021-07-25 23:12:12 +02:00
for ( uint16_t i = 0 ; i < EEStore : : eeStore - > data . nOutputs ; i + + ) {
2021-07-25 22:53:20 +02:00
EEPROM . get ( EEStore : : pointer ( ) + i * sizeof ( struct BrokenOutputData ) , bdata ) ;
if ( bdata . iFlag > 7 ) { // it's a pin and not an iFlag!
isBroken = 0 ;
break ;
}
}
if ( isBroken ) {
2021-07-25 23:12:12 +02:00
for ( uint16_t i = 0 ; i < EEStore : : eeStore - > data . nOutputs ; i + + ) {
2021-07-25 22:53:20 +02:00
EEPROM . get ( EEStore : : pointer ( ) , bdata ) ;
tt = create ( bdata . id , bdata . pin , bdata . iFlag ) ;
tt - > data . oStatus = bitRead ( tt - > data . iFlag , 1 ) ? bitRead ( tt - > data . iFlag , 2 ) : bdata . oStatus ; // restore status to EEPROM value is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag
digitalWrite ( tt - > data . pin , tt - > data . oStatus ^ bitRead ( tt - > data . iFlag , 0 ) ) ;
pinMode ( tt - > data . pin , OUTPUT ) ;
tt - > num = EEStore : : pointer ( ) ;
2021-07-25 23:07:20 +02:00
EEStore : : advance ( sizeof ( struct BrokenOutputData ) ) ;
2021-07-25 22:53:20 +02:00
}
} else {
struct OutputData data ;
2021-07-25 23:12:12 +02:00
for ( uint16_t i = 0 ; i < EEStore : : eeStore - > data . nOutputs ; i + + ) {
2021-07-25 22:53:20 +02:00
EEPROM . get ( EEStore : : pointer ( ) , data ) ;
tt = create ( data . id , data . pin , data . iFlag ) ;
tt - > data . oStatus = bitRead ( tt - > data . iFlag , 1 ) ? bitRead ( tt - > data . iFlag , 2 ) : data . oStatus ; // restore status to EEPROM value is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag
digitalWrite ( tt - > data . pin , tt - > data . oStatus ^ bitRead ( tt - > data . iFlag , 0 ) ) ;
pinMode ( tt - > data . pin , OUTPUT ) ;
tt - > num = EEStore : : pointer ( ) ;
EEStore : : advance ( sizeof ( struct OutputData ) ) ;
}
2020-06-03 15:26:49 +02:00
}
}
///////////////////////////////////////////////////////////////////////////////
void Output : : store ( ) {
Output * tt ;
tt = firstOutput ;
EEStore : : eeStore - > data . nOutputs = 0 ;
while ( tt ! = NULL ) {
tt - > num = EEStore : : pointer ( ) ;
EEPROM . put ( EEStore : : pointer ( ) , tt - > data ) ;
EEStore : : advance ( sizeof ( tt - > data ) ) ;
tt = tt - > nextOutput ;
EEStore : : eeStore - > data . nOutputs + + ;
}
}
///////////////////////////////////////////////////////////////////////////////
2021-07-24 21:11:18 +02:00
Output * Output : : create ( uint16_t id , uint8_t pin , uint8_t iFlag , uint8_t v ) {
2020-06-03 15:26:49 +02:00
Output * tt ;
2021-10-15 22:10:50 +02:00
if ( DCCWaveform : : mainTrack . pinUsed ( pin ) | |
DCCWaveform : : progTrack . pinUsed ( pin ) ) {
return NULL ;
}
2020-06-03 15:26:49 +02:00
if ( firstOutput = = NULL ) {
firstOutput = ( Output * ) calloc ( 1 , sizeof ( Output ) ) ;
tt = firstOutput ;
} else if ( ( tt = get ( id ) ) = = NULL ) {
tt = firstOutput ;
while ( tt - > nextOutput ! = NULL )
tt = tt - > nextOutput ;
tt - > nextOutput = ( Output * ) calloc ( 1 , sizeof ( Output ) ) ;
tt = tt - > nextOutput ;
}
if ( tt = = NULL ) return tt ;
tt - > data . id = id ;
tt - > data . pin = pin ;
tt - > data . iFlag = iFlag ;
tt - > data . oStatus = 0 ;
if ( v = = 1 ) {
tt - > data . oStatus = bitRead ( tt - > data . iFlag , 1 ) ? bitRead ( tt - > data . iFlag , 2 ) : 0 ; // sets status to 0 (INACTIVE) is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag
2020-08-15 12:32:32 +02:00
digitalWrite ( tt - > data . pin , tt - > data . oStatus ^ bitRead ( tt - > data . iFlag , 0 ) ) ;
2020-06-03 15:26:49 +02:00
pinMode ( tt - > data . pin , OUTPUT ) ;
}
return ( tt ) ;
}
///////////////////////////////////////////////////////////////////////////////
Output * Output : : firstOutput = NULL ;