Commit 36df8d00 authored by Lukáš Voborský's avatar Lukáš Voborský Committed by Marcel Stör
Browse files

Add DCC decoder module (#2905)

parent db1e4454
//------------------------------------------------------------------------
//
// Model Railroading with Arduino - NmraDcc.cpp
//
// Copyright (c) 2008 - 2017 Alex Shepherd
//
// This source file is subject of the GNU general public license 2,
// that is available at the world-wide-web at
// http://www.gnu.org/licenses/gpl.txt
//
//------------------------------------------------------------------------
//
// file: NmraDcc.cpp
// author: Alex Shepherd
// webpage: http://mrrwa.org/
// history: 2008-03-20 Initial Version
// 2011-06-26 Migrated into Arduino library from OpenDCC codebase
// 2014 Added getAddr to NmraDcc Geoff Bunza
// 2015-11-06 Martin Pischky (martin@pischky.de):
// Experimental Version to support 14 speed steps
// and new signature of notifyDccSpeed and notifyDccFunc
// 2015-12-16 Version without use of Timer0 by Franz-Peter Müller
// 2016-07-16 handle glitches on DCC line
// 2016-08-20 added ESP8266 support by Sven (littleyoda)
// 2017-01-19 added STM32F1 support by Franz-Peter
// 2017-11-29 Ken West (kgw4449@gmail.com):
// Minor fixes to pass NMRA Baseline Conformance Tests.
// 2018-12-17 added ESP32 support by Trusty (thierry@lapajaparis.net)
// 2019-02-17 added ESP32 specific changes by Hans Tanner
//
//------------------------------------------------------------------------
//
// purpose: Provide a simplified interface to decode NMRA DCC packets
// and build DCC Mobile and Stationary Decoders
//
//------------------------------------------------------------------------
// NodeMCU Lua port by @voborsky
// #define NODE_DEBUG
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include "platform.h"
#include "user_interface.h"
#include "task/task.h"
#include "driver/NmraDcc.h"
#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte) \
(byte & 0x80 ? '1' : '0'), \
(byte & 0x40 ? '1' : '0'), \
(byte & 0x20 ? '1' : '0'), \
(byte & 0x10 ? '1' : '0'), \
(byte & 0x08 ? '1' : '0'), \
(byte & 0x04 ? '1' : '0'), \
(byte & 0x02 ? '1' : '0'), \
(byte & 0x01 ? '1' : '0')
//------------------------------------------------------------------------
// DCC Receive Routine
//
// Howto: uses two interrupts: a rising edge in DCC polarity triggers INTx
// in INTx handler, Timer0 CompareB with a delay of 80us is started.
// On Timer0 CompareB Match the level of DCC is evaluated and
// parsed.
//
// |<-----116us----->|
//
// DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________
// ^-INTx
// |----87us--->|
// ^Timer-INT: reads zero
//
// DCC 0: _________XXXXXXXXXXXXXXXXXX__________________
// ^-INTx
// |----------->|
// ^Timer-INT: reads one
//
// new DCC Receive Routine without Timer0 ........................................................
//
// Howto: uses only one interrupt at the rising or falling edge of the DCC signal
// The time between two edges is measured to determine the bit value
// Synchronising to the edge of the first part of a bit is done after recognizing the start bit
// During synchronizing each part of a bit is detected ( Interruptmode 'change' )
//
// |<-----116us----->|
// DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________
// |<--------146us------>|
// ^-INTx ^-INTx
// less than 138us: its a one-Bit
//
//
// |<-----------------232us----------->|
// DCC 0: _________XXXXXXXXXXXXXXXXXX__________________XXXXXXXX__________
// |<--------146us------->|
// ^-INTx ^-INTx
// greater than 138us: its a zero bit
//
//
//
//
//------------------------------------------------------------------------
#define abs(a) ((a) > 0 ? (a) : (0-a))
#define MAX_ONEBITFULL 146
#define MAX_PRAEAMBEL 146
#define MAX_ONEBITHALF 82
#define MIN_ONEBITFULL 82
#define MIN_ONEBITHALF 35
#define MAX_BITDIFF 18
#ifdef NODE_DEBUG
#define PULLUP PLATFORM_GPIO_PULLUP
#define OUTPUT PLATFORM_GPIO_OUTPUT
#define HIGH PLATFORM_GPIO_HIGH
#define LOW PLATFORM_GPIO_LOW
#define MODE_TP1 platform_gpio_mode( 5, OUTPUT, PULLUP ); // GPIO 14
#define SET_TP1 platform_gpio_write(5, HIGH);
#define CLR_TP1 platform_gpio_write(5, LOW);
#define MODE_TP2 platform_gpio_mode( 6, OUTPUT, PULLUP ); // GPIO 12
#define SET_TP2 platform_gpio_write(6, HIGH);
#define CLR_TP2 platform_gpio_write(6, LOW);
#define MODE_TP3 platform_gpio_mode( 7, OUTPUT, PULLUP ); // GPIO 13
#define SET_TP3 platform_gpio_write(7, HIGH);
#define CLR_TP3 platform_gpio_write(7, LOW);
#define MODE_TP4 platform_gpio_mode( 8, OUTPUT, PULLUP ); // GPIO 15
#define SET_TP4 platform_gpio_write(8, HIGH);
#define CLR_TP4 platform_gpio_write(8, LOW);
#else
#define MODE_TP1
#define SET_TP1
#define CLR_TP1
#define MODE_TP2
#define SET_TP2
#define CLR_TP2
#define MODE_TP3
#define SET_TP3
#define CLR_TP3
#define MODE_TP4
#define SET_TP4
#define CLR_TP4
#endif
static uint8_t ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING
static int16_t bitMax, bitMin;
DCC_MSG Msg ;
typedef enum
{
WAIT_PREAMBLE = 0,
WAIT_START_BIT,
WAIT_DATA,
WAIT_END_BIT
}
DccRxWaitState ;
typedef enum
{
OPS_INS_RESERVED = 0,
OPS_INS_VERIFY_BYTE,
OPS_INS_BIT_MANIPULATION,
OPS_INS_WRITE_BYTE
}
OpsInstructionType;
struct DccRx_t
{
DccRxWaitState State ;
uint8_t BitCount ;
uint8_t TempByte ;
DCC_MSG PacketBuf;
DCC_MSG PacketCopy;
}
DccRx ;
typedef struct
{
uint8_t Flags ;
uint8_t OpsModeAddressBaseCV ;
uint8_t inServiceMode ;
long LastServiceModeMillis ;
uint8_t PageRegister ; // Used for Paged Operations in Service Mode Programming
uint8_t DuplicateCount ;
DCC_MSG LastMsg ;
uint8_t IntPin;
uint8_t IntBitmask;
int16_t myDccAddress; // Cached value of DCC Address from CVs
uint8_t inAccDecDCCAddrNextReceivedMode;
#ifdef DCC_DEBUG
uint8_t IntCount;
uint8_t TickCount;
#endif
}
DCC_PROCESSOR_STATE ;
DCC_PROCESSOR_STATE DccProcState ;
task_handle_t DataReady_taskid;
static uint32_t ICACHE_RAM_ATTR InterruptHandler (uint32_t ret_gpio_status)
{
// This function really is running at interrupt level with everything
// else masked off. It should take as little time as necessary.
uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
if ((gpio_status & DccProcState.IntBitmask) == 0) {
return ret_gpio_status;
}
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & DccProcState.IntBitmask);
uint32_t actMicros = system_get_time();
ret_gpio_status &= ~(DccProcState.IntBitmask);
// Bit evaluation without Timer 0 ------------------------------
uint8_t DccBitVal;
static int8_t bit1, bit2 ;
static unsigned long lastMicros = 0;
static uint8_t halfBit;
unsigned long bitMicros;
SET_TP3;
bitMicros = actMicros-lastMicros;
if ( bitMicros < bitMin ) {
// too short - my be false interrupt due to glitch or false protocol -> ignore
CLR_TP3;
return ret_gpio_status; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
}
DccBitVal = ( bitMicros < bitMax );
lastMicros = actMicros;
#ifdef NODE_DEBUG
if(DccBitVal) {SET_TP2;} else {CLR_TP2;};
#endif
#ifdef DCC_DEBUG
DccProcState.TickCount++;
#endif
switch( DccRx.State )
{
case WAIT_PREAMBLE:
if( DccBitVal )
{
SET_TP1;
DccRx.BitCount++;
if( DccRx.BitCount > 10 ) {
DccRx.State = WAIT_START_BIT ;
// While waiting for the start bit, detect halfbit lengths. We will detect the correct
// sync and detect whether we see a false (e.g. motorola) protocol
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), GPIO_PIN_INTR_ANYEDGE);
halfBit = 0;
bitMax = MAX_ONEBITHALF;
bitMin = MIN_ONEBITHALF;
CLR_TP1;
}
} else {
SET_TP1;
DccRx.BitCount = 0 ;
CLR_TP1;
}
break;
case WAIT_START_BIT:
// we are looking for first half "0" bit after preamble
switch ( halfBit ) {
case 0: //SET_TP1;
// check first part
if ( DccBitVal ) {
// is still 1-bit (Preamble)
halfBit=1;
bit1=bitMicros;
} else {
// was "0" half bit, maybe the startbit
SET_TP1;
halfBit = 4;
CLR_TP1;
}
break;
case 1: //SET_TP1; // previous halfbit was '1'
if ( DccBitVal ) {
// its a '1' halfBit -> we are still in the preamble
halfBit = 0;
bit2=bitMicros;
DccRx.BitCount++;
if( abs(bit2-bit1) > MAX_BITDIFF ) {
// the length of the 2 halfbits differ too much -> wrong protokoll
CLR_TP2;
CLR_TP3;
DccRx.State = WAIT_PREAMBLE;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
DccRx.BitCount = 0;
SET_TP4;
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), ISREdge);
SET_TP3;
CLR_TP4;
}
} else {
// first '0' half detected in second halfBit
// wrong sync or not a DCC protokoll
CLR_TP3;
halfBit = 3;
SET_TP3;
}
break;
case 3: //SET_TP1; // previous halfbit was '0' in second halfbit
if ( DccBitVal ) {
// its a '1' halfbit -> we got only a half '0' bit -> cannot be DCC
DccRx.State = WAIT_PREAMBLE;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
DccRx.BitCount = 0;
} else {
// we got two '0' halfbits -> it's the startbit
// but sync is NOT ok, change IRQ edge.
if ( ISREdge == GPIO_PIN_INTR_POSEDGE ) ISREdge = GPIO_PIN_INTR_NEGEDGE; else ISREdge = GPIO_PIN_INTR_POSEDGE;
DccRx.State = WAIT_DATA ;
bitMax = MAX_ONEBITFULL;
bitMin = MIN_ONEBITFULL;
DccRx.PacketBuf.Size = 0;
DccRx.PacketBuf.PreambleBits = 0;
for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
DccRx.PacketBuf.Data[i] = 0;
DccRx.PacketBuf.PreambleBits = DccRx.BitCount;
DccRx.BitCount = 0 ;
DccRx.TempByte = 0 ;
}
SET_TP4;
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), ISREdge);
CLR_TP1;
CLR_TP4;
break;
case 4: SET_TP1; // previous (first) halfbit was 0
// if this halfbit is 0 too, we got the startbit
if ( DccBitVal ) {
// second halfbit is 1 -> unknown protokoll
DccRx.State = WAIT_PREAMBLE;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
DccRx.BitCount = 0;
} else {
// we got the startbit
DccRx.State = WAIT_DATA ;
bitMax = MAX_ONEBITFULL;
bitMin = MIN_ONEBITFULL;
DccRx.PacketBuf.Size = 0;
DccRx.PacketBuf.PreambleBits = 0;
for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
DccRx.PacketBuf.Data[i] = 0;
DccRx.PacketBuf.PreambleBits = DccRx.BitCount;
DccRx.BitCount = 0 ;
DccRx.TempByte = 0 ;
}
CLR_TP1;
SET_TP4;
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), ISREdge);
CLR_TP4;
break;
}
break;
case WAIT_DATA:
DccRx.BitCount++;
DccRx.TempByte = ( DccRx.TempByte << 1 ) ;
if( DccBitVal )
DccRx.TempByte |= 1 ;
if( DccRx.BitCount == 8 )
{
if( DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN ) // Packet is too long - abort
{
DccRx.State = WAIT_PREAMBLE ;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
DccRx.BitCount = 0 ;
}
else
{
DccRx.State = WAIT_END_BIT ;
DccRx.PacketBuf.Data[ DccRx.PacketBuf.Size++ ] = DccRx.TempByte ;
}
}
break;
case WAIT_END_BIT:
DccRx.BitCount++;
if( DccBitVal ) // End of packet?
{
CLR_TP3;
DccRx.State = WAIT_PREAMBLE ;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
DccRx.PacketCopy = DccRx.PacketBuf ;
uint8_t param;
task_post_high(DataReady_taskid, (os_param_t) &param);
SET_TP3;
}
else // Get next Byte
// KGW - Abort immediately if packet is too long.
if( DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN ) // Packet is too long - abort
{
DccRx.State = WAIT_PREAMBLE ;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
DccRx.BitCount = 0 ;
}
else
{
DccRx.State = WAIT_DATA ;
DccRx.BitCount = 0 ;
DccRx.TempByte = 0 ;
}
}
CLR_TP1;
CLR_TP3;
return ret_gpio_status;
}
uint8_t validCV( uint16_t CV, uint8_t Writable )
{
if( notifyCVResetFactoryDefault && (CV == CV_MANUFACTURER_ID ) && Writable )
notifyCVResetFactoryDefault();
if( notifyCVValid )
return notifyCVValid( CV, Writable ) ;
return 0;
}
uint8_t readCV( unsigned int CV )
{
if( notifyCVRead )
return notifyCVRead( CV ) ;
return 0;
}
uint8_t writeCV( unsigned int CV, uint8_t Value)
{
switch( CV )
{
case CV_29_CONFIG:
// copy addressmode Bit to Flags
DccProcState.Flags = ( DccProcState.Flags & ~FLAGS_CV29_BITS) | (Value & FLAGS_CV29_BITS);
// no break, because myDccAdress must also be reset
case CV_ACCESSORY_DECODER_ADDRESS_LSB: // Also same CV for CV_MULTIFUNCTION_PRIMARY_ADDRESS
case CV_ACCESSORY_DECODER_ADDRESS_MSB:
case CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB:
case CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB:
DccProcState.myDccAddress = -1; // Assume any CV Write Operation might change the Address
}
if( notifyCVWrite )
return notifyCVWrite( CV, Value ) ;
return 0;
}
uint16_t getMyAddr(void)
{
uint8_t CV29Value ;
if( DccProcState.myDccAddress != -1 ) // See if we can return the cached value
return( DccProcState.myDccAddress );
CV29Value = readCV( CV_29_CONFIG ) ;
if( CV29Value & CV29_ACCESSORY_DECODER ) // Accessory Decoder?
{
if( CV29Value & CV29_OUTPUT_ADDRESS_MODE )
DccProcState.myDccAddress = ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) << 8 ) | readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB );
else
DccProcState.myDccAddress = ( ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) & 0b00000111) << 6 ) | ( readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB ) & 0b00111111) ;
}
else // Multi-Function Decoder?
{
if( CV29Value & CV29_EXT_ADDRESSING ) // Two Byte Address?
DccProcState.myDccAddress = ( ( readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB ) - 192 ) << 8 ) | readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB ) ;
else
DccProcState.myDccAddress = readCV( 1 ) ;
}
return DccProcState.myDccAddress ;
}
void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value )
{
// is it a Byte Operation
if( Cmd & 0x04 )
{
// Perform the Write Operation
if( Cmd & 0x08 )
{
if( validCV( CVAddr, 1 ) )
{
writeCV( CVAddr, Value );
}
}
}
// Perform the Bit-Wise Operation
else
{
uint8_t BitMask = (1 << (Value & 0x07) ) ;
uint8_t BitValue = Value & 0x08 ;
uint8_t BitWrite = Value & 0x10 ;
uint8_t tempValue = readCV( CVAddr ) ; // Read the Current CV Value
// Perform the Bit Write Operation
if( BitWrite )
{
if( validCV( CVAddr, 1 ) )
{
if( BitValue )
tempValue |= BitMask ; // Turn the Bit On
else
tempValue &= ~BitMask ; // Turn the Bit Off
writeCV( CVAddr, tempValue );
}
}
}
}
#ifdef NMRA_DCC_PROCESS_MULTIFUNCTION
void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Cmd, uint8_t Data1, uint8_t Data2 )
{
uint8_t speed ;
uint16_t CVAddr ;
DCC_DIRECTION dir ;
DCC_SPEED_STEPS speedSteps ;
uint8_t CmdMasked = Cmd & 0b11100000 ;
// NODE_DBG("[dcc_processMultiFunctionMessage] Addr: %d, Type: %d, Cmd: %d ("BYTE_TO_BINARY_PATTERN"), Data: %d, %d, CmdMasked="BYTE_TO_BINARY_PATTERN"\n", Addr, AddrType, Cmd, BYTE_TO_BINARY(Cmd), Data1, Data2, BYTE_TO_BINARY(CmdMasked));
// If we are an Accessory Decoder
if( DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER )
{
// NODE_DBG("[dcc_processMultiFunctionMessage] DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER\n");
// and this isn't an Ops Mode Write or we are NOT faking the Multifunction Ops mode address in CV 33+34 or
// it's not our fake address, then return
if( ( CmdMasked != 0b11100000 ) || ( DccProcState.OpsModeAddressBaseCV == 0 ) )
return ;
uint16_t FakeOpsAddr = readCV( DccProcState.OpsModeAddressBaseCV ) | ( readCV( DccProcState.OpsModeAddressBaseCV + 1 ) << 8 ) ;
uint16_t OpsAddr = Addr & 0x3FFF ;
if( OpsAddr != FakeOpsAddr )
return ;
}
// We are looking for FLAGS_MY_ADDRESS_ONLY but it does not match and it is not a Broadcast Address then return
else if( ( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY ) && ( Addr != getMyAddr() ) && ( Addr != 0 ) )
return ;
NODE_DBG("[dcc_processMultiFunctionMessage] CmdMasked: %x\n", CmdMasked);
switch( CmdMasked )
{
case 0b00000000: // Decoder Control
switch( Cmd & 0b00001110 )
{
case 0b00000000:
if( notifyDccReset && ( Cmd & 0b00000001 ) ) // Hard Reset
if( notifyDccReset)
notifyDccReset( 1 ) ;
break ;
case 0b00000010: // Factory Test
break ;
case 0b00000110: // Set Decoder Flags
break ;
case 0b00001010: // Set Advanced Addressing
break ;
case 0b00001110: // Decoder Acknowledgment
break ;
default: // Reserved
;
}
break ;
case 0b00100000: // Advanced Operations
switch( Cmd & 0b00011111 )
{
case 0b00011111:
if( notifyDccSpeed )
{
switch( Data1 & 0b01111111 )
{
case 0b00000000: // 0=STOP
speed = 1 ; // => 1
break ;
case 0b00000001: // 1=EMERGENCY_STOP
speed = 0 ; // => 0
break ;
default: // 2..127
speed = (Data1 & 0b01111111) ;
}
dir = (DCC_DIRECTION) ((Data1 & 0b10000000) >> 7) ;
notifyDccSpeed( Addr, AddrType, speed, dir, SPEED_STEP_128 ) ;
}
}
break;
case 0b01000000:
case 0b01100000:
//TODO should we cache this info in DCC_PROCESSOR_STATE.Flags ?
#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
speedSteps = (readCV( CV_29_CONFIG ) & CV29_F0_LOCATION) ? SPEED_STEP_28 : SPEED_STEP_14 ;
#else
speedSteps = SPEED_STEP_28 ;
#endif
if( notifyDccSpeed )
{
switch( Cmd & 0b00011111 )
{
case 0b00000000: // 0 0000 = STOP
case 0b00010000: // 1 0000 = STOP
speed = 1 ; // => 1
break ;
case 0b00000001: // 0 0001 = EMERGENCY STOP
case 0b00010001: // 1 0001 = EMERGENCY STOP
speed = 0 ; // => 0
break ;
default:
#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
if( speedSteps == SPEED_STEP_14 )
{
speed = (Cmd & 0b00001111) ; // => 2..15
}
else
{
#endif
speed = (((Cmd & 0b00001111) << 1 ) | ((Cmd & 0b00010000) >> 4)) - 2 ; // => 2..29
#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
}
#endif
}
dir = (DCC_DIRECTION) ((Cmd & 0b00100000) >> 5) ;
notifyDccSpeed( Addr, AddrType, speed, dir, speedSteps ) ;
}
if( notifyDccSpeedRaw )
notifyDccSpeedRaw(Addr, AddrType, Cmd );
#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
if( notifyDccFunc && (speedSteps == SPEED_STEP_14) )
{
// function light is controlled by this package
uint8_t fn0 = (Cmd & 0b00010000) ;
notifyDccFunc( Addr, AddrType, FN_0, fn0 ) ;
}
#endif
break;
case 0b10000000: // Function Group 0..4
if( notifyDccFunc )
{
// function light is controlled by this package (28 or 128 speed steps)
notifyDccFunc( Addr, AddrType, FN_0_4, Cmd & 0b00011111 ) ;
}
break;
case 0b10100000: // Function Group 5..8
if( notifyDccFunc)
{
if (Cmd & 0b00010000 )
notifyDccFunc( Addr, AddrType, FN_5_8, Cmd & 0b00001111 ) ;
else
notifyDccFunc( Addr, AddrType, FN_9_12, Cmd & 0b00001111 ) ;
}
break;
case 0b11000000: // Feature Expansion Instruction
switch(Cmd & 0b00011111)
{
case 0b00011110:
if( notifyDccFunc )
notifyDccFunc( Addr, AddrType, FN_13_20, Data1 ) ;
break;
case 0b00011111:
if( notifyDccFunc )
notifyDccFunc( Addr, AddrType, FN_21_28, Data1 ) ;
break;
}
break;
case 0b11100000: // CV Access
CVAddr = ( ( ( Cmd & 0x03 ) << 8 ) | Data1 ) + 1 ;
processDirectOpsOperation( Cmd, CVAddr, Data2 ) ;
break;
}
}
#endif
/////////////////////////////////////////////////////////////////////////
#ifdef NMRA_DCC_PROCESS_SERVICEMODE
void processServiceModeOperation( DCC_MSG * pDccMsg )
{
uint16_t CVAddr ;
uint8_t Value ;
if( pDccMsg->Size == 3) // 3 Byte Packets are for Address Only, Register and Paged Mode
{
uint8_t RegisterAddr ;
NODE_DBG("[dcc_processServiceModeOperation] 3-BytePkt\n");
RegisterAddr = pDccMsg->Data[0] & 0x07 ;
Value = pDccMsg->Data[1] ;
if( RegisterAddr == 5 )
{
DccProcState.PageRegister = Value ;
}
else
{
if( RegisterAddr == 4 )
CVAddr = CV_29_CONFIG ;
else if( ( RegisterAddr <= 3 ) && ( DccProcState.PageRegister > 0 ) )
CVAddr = ( ( DccProcState.PageRegister - 1 ) * 4 ) + RegisterAddr + 1 ;
else
CVAddr = RegisterAddr + 1 ;
if( pDccMsg->Data[0] & 0x08 ) // Perform the Write Operation
{
if( validCV( CVAddr, 1 ) )
{
writeCV( CVAddr, Value );
}
}
}
}
else if( pDccMsg->Size == 4) // 4 Byte Packets are for Direct Byte & Bit Mode
{
NODE_DBG("[dcc_processServiceModeOperation] BB-Mode\n");
CVAddr = ( ( ( pDccMsg->Data[0] & 0x03 ) << 8 ) | pDccMsg->Data[1] ) + 1 ;
Value = pDccMsg->Data[2] ;
processDirectOpsOperation( pDccMsg->Data[0] & 0b00001100, CVAddr, Value ) ;
}
}
#endif
void resetServiceModeTimer(uint8_t inServiceMode)
{
if (notifyServiceMode && inServiceMode != DccProcState.inServiceMode)
{
notifyServiceMode(inServiceMode);
}
// Set the Service Mode
DccProcState.inServiceMode = inServiceMode ;
DccProcState.LastServiceModeMillis = inServiceMode ? system_get_time() : 0 ;
if (notifyServiceMode && inServiceMode != DccProcState.inServiceMode)
{
notifyServiceMode(inServiceMode);
}
}
void clearDccProcState(uint8_t inServiceMode)
{
resetServiceModeTimer( inServiceMode ) ;
// Set the Page Register to it's default of 1 only on the first Reset
DccProcState.PageRegister = 1 ;
// Clear the LastMsg buffer and DuplicateCount in preparation for possible CV programming
DccProcState.DuplicateCount = 0 ;
memset( &DccProcState.LastMsg, 0, sizeof( DCC_MSG ) ) ;
}
void execDccProcessor( DCC_MSG * pDccMsg )
{
NODE_DBG("[dcc_execDccProcessor]\n");
if( ( pDccMsg->Data[0] == 0 ) && ( pDccMsg->Data[1] == 0 ) )
{
if( notifyDccReset )
notifyDccReset( 0 ) ;
#ifdef NMRA_DCC_PROCESS_SERVICEMODE
// If this is the first Reset then perform some one-shot actions as we maybe about to enter service mode
if( DccProcState.inServiceMode )
resetServiceModeTimer( 1 ) ;
else
clearDccProcState( 1 );
#endif
}
else
{
#ifdef NMRA_DCC_PROCESS_SERVICEMODE
if( DccProcState.inServiceMode && ( pDccMsg->Data[0] >= 112 ) && ( pDccMsg->Data[0] < 128 ) )
{
resetServiceModeTimer( 1 ) ;
if( memcmp( pDccMsg, &DccProcState.LastMsg, sizeof( DCC_MSG ) ) )
{
DccProcState.DuplicateCount = 0 ;
memcpy( &DccProcState.LastMsg, pDccMsg, sizeof( DCC_MSG ) ) ;
}
// Wait until you see 2 identicle packets before acting on a Service Mode Packet
else
{
DccProcState.DuplicateCount++ ;
processServiceModeOperation( pDccMsg ) ;
}
}
else
{
if( DccProcState.inServiceMode )
clearDccProcState( 0 );
#endif
// Idle Packet
if( ( pDccMsg->Data[0] == 0b11111111 ) && ( pDccMsg->Data[1] == 0 ) )
{
if( notifyDccIdle )
notifyDccIdle() ;
}
#ifdef NMRA_DCC_PROCESS_MULTIFUNCTION
// Multi Function Decoders (7-bit address)
else if( pDccMsg->Data[0] < 128 )
processMultiFunctionMessage( pDccMsg->Data[0], DCC_ADDR_SHORT, pDccMsg->Data[1], pDccMsg->Data[2], pDccMsg->Data[3] ) ;
// Basic Accessory Decoders (9-bit) & Extended Accessory Decoders (11-bit)
else if( pDccMsg->Data[0] < 192 )
#else
else if( ( pDccMsg->Data[0] >= 128 ) && ( pDccMsg->Data[0] < 192 ) )
#endif
{
if( DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER )
{
int16_t BoardAddress ;
int16_t OutputAddress ;
uint8_t TurnoutPairIndex ;
#ifdef NODE_DEBUG
// SerialPrintPacketHex(F( "eDP: AccCmd: "), pDccMsg);
#endif
BoardAddress = ( ( (~pDccMsg->Data[1]) & 0b01110000 ) << 2 ) | ( pDccMsg->Data[0] & 0b00111111 ) ;
TurnoutPairIndex = (pDccMsg->Data[1] & 0b00000110) >> 1;
NODE_DBG("[dcc_execDccProcessor] eDP: BAddr:%d, Index:%d\n", BoardAddress, TurnoutPairIndex);
// First check for Legacy Accessory Decoder Configuration Variable Access Instruction
// as it's got a different format to the others
if((pDccMsg->Size == 5) && ((pDccMsg->Data[1] & 0b10001100) == 0b00001100))
{
NODE_DBG( "eDP: Legacy Accessory Decoder CV Access Command");
// Check if this command is for our address or the broadcast address
if((BoardAddress != getMyAddr()) && ( BoardAddress < 511 ))
{
NODE_DBG("[dcc_execDccProcessor] eDP: Board Address Not Matched\n");
return;
}
uint16_t cvAddress = ((pDccMsg->Data[1] & 0b00000011) << 8) + pDccMsg->Data[2] + 1;
uint8_t cvValue = pDccMsg->Data[3];
NODE_DBG("[dcc_execDccProcessor] eDP: CV:%d Value:%d\n", cvAddress, cvValue );
if(validCV( cvAddress, 1 ))
writeCV(cvAddress, cvValue);
return;
}
OutputAddress = (((BoardAddress - 1) << 2 ) | TurnoutPairIndex) + 1 ; //decoder output addresses start with 1, packet address range starts with 0
// ( according to NMRA 9.2.2 )
NODE_DBG("[dcc_execDccProcessor] eDP: OAddr:%d\n", OutputAddress);
if( DccProcState.inAccDecDCCAddrNextReceivedMode)
{
if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE )
{
NODE_DBG("[dcc_execDccProcessor] eDP: Set OAddr:%d\n", OutputAddress);
//uint16_t storedOutputAddress = OutputAddress + 1; // The value stored in CV1 & 9 for Output Addressing Mode is + 1
writeCV(CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t)(OutputAddress % 256));
writeCV(CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t)(OutputAddress / 256));
if( notifyDccAccOutputAddrSet )
notifyDccAccOutputAddrSet(OutputAddress);
}
else
{
NODE_DBG("[dcc_execDccProcessor] eDP: Set BAddr:%d\n", BoardAddress);
writeCV(CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t)(BoardAddress % 64));
writeCV(CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t)(BoardAddress / 64));
if( notifyDccAccBoardAddrSet )
notifyDccAccBoardAddrSet(BoardAddress);
}
DccProcState.inAccDecDCCAddrNextReceivedMode = 0; // Reset the mode now that we have set the address
}
// If we're filtering addresses, does the address match our address or is it a broadcast address? If NOT then return
if( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY )
{
if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) {
NODE_DBG("[dcc_execDccProcessor] AddrChk: OAddr:%d, BAddr:%d, myAddr:%d Chk=%d\n", OutputAddress, BoardAddress, getMyAddr(), OutputAddress != getMyAddr() );
if ( OutputAddress != getMyAddr() && OutputAddress < 2045 ) {
NODE_DBG("[dcc_execDccProcessor] eDP: OAddr:%d, myAddr:%d - no match\n", OutputAddress, getMyAddr() );
return;
}
} else {
if( ( BoardAddress != getMyAddr() ) && ( BoardAddress < 511 ) ) {
NODE_DBG("[dcc_execDccProcessor] eDP: BAddr:%d, myAddr:%d - no match\n", BoardAddress, getMyAddr() );
return;
}
}
NODE_DBG("[dcc_execDccProcessor] eDP: Address Matched\n");
}
if((pDccMsg->Size == 4) && ((pDccMsg->Data[1] & 0b10001001) == 1)) // Extended Accessory Decoder Control Packet Format
{
// According to the NMRA Dcc Spec the Signal State should only use the lower 5 Bits,
// however some manufacturers seem to allow/use all 8 bits, so we'll relax that constraint for now
uint8_t state = pDccMsg->Data[2] ;
NODE_DBG("[dcc_execDccProcessor] eDP: OAddr:%d Extended State:%0X\n", OutputAddress, state);
if( notifyDccSigOutputState )
notifyDccSigOutputState(OutputAddress, state);
}
else if(pDccMsg->Size == 3) // Basic Accessory Decoder Packet Format
{
uint8_t direction = pDccMsg->Data[1] & 0b00000001;
uint8_t outputPower = (pDccMsg->Data[1] & 0b00001000) >> 3;
if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE )
{
NODE_DBG("[dcc_execDccProcessor] eDP: OAddr:%d Turnout Dir:%d Output Power:%d\n", OutputAddress, direction, outputPower);
if( notifyDccAccTurnoutOutput )
notifyDccAccTurnoutOutput( OutputAddress, direction, outputPower );
}
else
{
NODE_DBG("[dcc_execDccProcessor] eDP: Turnout Pair Index:%d Dir:%d Output Power: %d\n", TurnoutPairIndex, direction, outputPower);
if( notifyDccAccTurnoutBoard )
notifyDccAccTurnoutBoard( BoardAddress, TurnoutPairIndex, direction, outputPower );
}
}
else if(pDccMsg->Size == 6) // Accessory Decoder OPS Mode Programming
{
NODE_DBG("[dcc_execDccProcessor] eDP: OPS Mode CV Programming Command\n");
// Check for unsupported OPS Mode Addressing mode
if(((pDccMsg->Data[1] & 0b10001001) != 1) && ((pDccMsg->Data[1] & 0b10001111) != 0x80))
{
NODE_DBG("[dcc_execDccProcessor] eDP: Unsupported OPS Mode CV Addressing Mode\n");
return;
}
// Check if this command is for our address or the broadcast address
if(DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE)
{
NODE_DBG("[dcc_execDccProcessor] eDP: Check Output Address:%d\n", OutputAddress);
if((OutputAddress != getMyAddr()) && ( OutputAddress < 2045 ))
{
NODE_DBG("[dcc_execDccProcessor] eDP: Output Address Not Matched\n");
return;
}
}
else
{
NODE_DBG("[dcc_execDccProcessor] eDP: Check Board Address:%d\n", BoardAddress);
if((BoardAddress != getMyAddr()) && ( BoardAddress < 511 ))
{
NODE_DBG("[dcc_execDccProcessor] eDP: Board Address Not Matched\n");
return;
}
}
uint16_t cvAddress = ((pDccMsg->Data[2] & 0b00000011) << 8) + pDccMsg->Data[3] + 1;
uint8_t cvValue = pDccMsg->Data[4];
OpsInstructionType insType = (OpsInstructionType)((pDccMsg->Data[2] & 0b00001100) >> 2) ;
NODE_DBG("[dcc_execDccProcessor] eDP: OPS Mode Instruction:%d\n", insType);
switch(insType)
{
case OPS_INS_RESERVED:
case OPS_INS_VERIFY_BYTE:
NODE_DBG("[dcc_execDccProcessor] eDP: Unsupported OPS Mode Instruction:%d\n", insType);
break; // We only support Write Byte or Bit Manipulation
case OPS_INS_WRITE_BYTE:
NODE_DBG("[dcc_execDccProcessor] eDP: CV:%d Value:%d\n", cvAddress, cvValue);
if(validCV( cvAddress, 1 ))
writeCV(cvAddress, cvValue);
break;
// 111CDBBB
// Where BBB represents the bit position within the CV,
// D contains the value of the bit to be verified or written,
// and C describes whether the operation is a verify bit or a write bit operation.
// C = "1" WRITE BIT
// C = "0" VERIFY BIT
case OPS_INS_BIT_MANIPULATION:
// Make sure its a Write Bit Manipulation
if((cvValue & 0b00010000) && validCV(cvAddress, 1 ))
{
uint8_t currentValue = readCV(cvAddress);
uint8_t newValueMask = 1 << (cvValue & 0b00000111);
if(cvValue & 0b00001000)
writeCV(cvAddress, currentValue | newValueMask);
else
writeCV(cvAddress, currentValue & ~newValueMask);
}
break;
}
}
}
}
#ifdef NMRA_DCC_PROCESS_MULTIFUNCTION
// Multi Function Decoders (14-bit address)
else if( pDccMsg->Data[0] < 232 )
{
uint16_t Address ;
Address = ( ( pDccMsg->Data[0] - 192 ) << 8 ) | pDccMsg->Data[1];
//TODO should we convert Address to 1 .. 10239 ?
processMultiFunctionMessage( Address, DCC_ADDR_LONG, pDccMsg->Data[2], pDccMsg->Data[3], pDccMsg->Data[4] ) ;
}
#endif
#ifdef NMRA_DCC_PROCESS_SERVICEMODE
}
#endif
}
}
static void process (os_param_t param, uint8_t prio)
{
// !!!!!! - this will not happen as we call process task only when data is ready
// if( DccProcState.inServiceMode )
// {
// if( (system_get_time() - DccProcState.LastServiceModeMillis ) > 20L )
// {
// clearDccProcState( 0 ) ;
// }
// }
// !!!!!!
// We need to do this check with interrupts disabled
//SET_TP4;
Msg = DccRx.PacketCopy ;
#ifdef DCC_DBGVAR
countOf.Tel++;
#endif
uint8_t xorValue = 0 ;
for(uint8_t i = 0; i < DccRx.PacketCopy.Size; i++)
xorValue ^= DccRx.PacketCopy.Data[i];
if(xorValue) {
#ifdef DCC_DBGVAR
NODE_DBG("[dcc_process] Cerr\n");
NODE_DBG("[dcc_process] Data dump:");
for(uint8_t i = 0; i < DccRx.PacketCopy.Size; i++)
NODE_DBG(" %x", DccRx.PacketCopy.Data[i]);
NODE_DBG("\n");
countOf.Err++;
#endif
return;// 0 ;
} else {
NODE_DBG("[dcc_process] Size: %d\tPreambleBits: %d\t%d, %d, %d, %d, %d, %d\n",
Msg.Size, Msg.PreambleBits, Msg.Data[0], Msg.Data[1], Msg.Data[2], Msg.Data[3], Msg.Data[4], Msg.Data[5]);
execDccProcessor( &Msg );
}
return;// 1 ;
}
void dcc_setup(uint8_t pin, uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV)
{
NODE_DBG("[dcc_setup]\n");
// Clear all the static member variables
memset( &DccRx, 0, sizeof( DccRx) );
MODE_TP1; // only for debugging and timing measurement
MODE_TP2;
MODE_TP3;
MODE_TP4;
CLR_TP1;
CLR_TP2;
CLR_TP3;
CLR_TP4;
bitMax = MAX_ONEBITFULL;
bitMin = MIN_ONEBITFULL;
DccProcState.Flags = Flags ;
DccProcState.OpsModeAddressBaseCV = OpsModeAddressBaseCV ;
DccProcState.myDccAddress = -1;
DccProcState.inAccDecDCCAddrNextReceivedMode = 0;
ISREdge = GPIO_PIN_INTR_POSEDGE;
DccProcState.IntPin = pin;
DccProcState.IntBitmask = 1 << pin_num[pin];
platform_gpio_mode(pin, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP);
NODE_DBG("[dcc_setup] platform_gpio_register_intr_hook - pin: %d, mask: %d\n", DccProcState.IntPin, DccProcState.IntBitmask);
platform_gpio_register_intr_hook(DccProcState.IntBitmask, InterruptHandler);
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[pin]), GPIO_PIN_INTR_POSEDGE);
// Set the Bits that control Multifunction or Accessory behaviour
// and if the Accessory decoder optionally handles Output Addressing
// we need to peal off the top two bits
writeCV( CV_29_CONFIG, ( readCV( CV_29_CONFIG ) & ~FLAGS_CV29_BITS ) | (Flags & FLAGS_CV29_BITS) ) ; //!!!!!
uint8_t doAutoFactoryDefault = 0;
if((Flags & FLAGS_AUTO_FACTORY_DEFAULT) && (readCV(CV_VERSION_ID) == 255) && (readCV(CV_MANUFACTURER_ID) == 255))
doAutoFactoryDefault = 1;
writeCV( CV_VERSION_ID, VersionId ) ;
writeCV( CV_MANUFACTURER_ID, ManufacturerId ) ;
clearDccProcState( 0 );
if(notifyCVResetFactoryDefault && doAutoFactoryDefault)
notifyCVResetFactoryDefault();
}
void dcc_close()
{
NODE_DBG("[dcc_close]\n");
platform_gpio_mode(DccProcState.IntPin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_PULLUP);
}
void dcc_init()
{
NODE_DBG("[dcc_init]\n");
DataReady_taskid = task_get_id((task_callback_t) process);
}
\ No newline at end of file
//------------------------------------------------------------------------
//
// Model Railroading with Arduino - NmraDcc.h
//
// Copyright (c) 2008 - 2018 Alex Shepherd
//
// This source file is subject of the GNU general public license 2,
// that is available at the world-wide-web at
// http://www.gnu.org/licenses/gpl.txt
//
//------------------------------------------------------------------------
//
// file: NmraDcc.h
// author: Alex Shepherd
// webpage: http://mrrwa.org/
// history: 2008-03-20 Initial Version
// 2011-06-26 Migrated into Arduino library from OpenDCC codebase
// 2014 Added getAddr to NmraDcc Geoff Bunza
// 2015-11-06 Martin Pischky (martin@pischky.de):
// Experimental Version to support 14 speed steps
// and new signature of notifyDccSpeed and notifyDccFunc
// 2017-11-29 Ken West (kgw4449@gmail.com):
// Added method and callback headers.
//
//------------------------------------------------------------------------
//
// purpose: Provide a simplified interface to decode NMRA DCC packets
// and build DCC MutliFunction and Stationary Decoders
//
//------------------------------------------------------------------------
// NodeMCU Lua port by @voborsky
// #define NODE_DEBUG
// #define DCC_DEBUG
// #define DCC_DBGVAR
// Uncomment the following Line to Enable Service Mode CV Programming
#define NMRA_DCC_PROCESS_SERVICEMODE
// Uncomment the following line to Enable MultiFunction Decoder Operations
#define NMRA_DCC_PROCESS_MULTIFUNCTION
// #ifndef NMRADCC_IS_IN
// #define NMRADCC_IS_IN
#define NMRADCC_VERSION 201 // Version 2.0.1
#define MAX_DCC_MESSAGE_LEN 6 // including XOR-Byte
typedef struct
{
uint8_t Size ;
uint8_t PreambleBits ;
uint8_t Data[MAX_DCC_MESSAGE_LEN] ;
} DCC_MSG ;
//--------------------------------------------------------------------------
// This section contains the NMRA Assigned DCC Manufacturer Id Codes that
// are used in projects
//
// This value is to be used for CV8
//--------------------------------------------------------------------------
#define MAN_ID_JMRI 0x12
#define MAN_ID_DIY 0x0D
#define MAN_ID_SILICON_RAILWAY 0x21
//--------------------------------------------------------------------------
// This section contains the Product/Version Id Codes for projects
//
// This value is to be used for CV7
//
// NOTE: Each Product/Version Id Code needs to be UNIQUE for that particular
// the DCC Manufacturer Id Code
//--------------------------------------------------------------------------
// Product/Version Id Codes allocated under: MAN_ID_JMRI
// Product/Version Id Codes allocated under: MAN_ID_DIY
// Standard CV Addresses
#define CV_ACCESSORY_DECODER_ADDRESS_LSB 1
#define CV_ACCESSORY_DECODER_ADDRESS_MSB 9
#define CV_MULTIFUNCTION_PRIMARY_ADDRESS 1
#define CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB 17
#define CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB 18
#define CV_VERSION_ID 7
#define CV_MANUFACTURER_ID 8
#define CV_29_CONFIG 29
typedef enum {
CV29_LOCO_DIR = 0b00000001, /** bit 0: Locomotive Direction: "0" = normal, "1" = reversed */
CV29_F0_LOCATION = 0b00000010, /** bit 1: F0 location: "0" = bit 4 in Speed and Direction instructions, "1" = bit 4 in function group one instruction */
CV29_APS = 0b00000100, /** bit 2: Alternate Power Source (APS) "0" = NMRA Digital only, "1" = Alternate power source set by CV12 */
CV29_ADV_ACK = 0b00001000, /** bit 3: ACK, Advanced Acknowledge mode enabled if 1, disabled if 0 */
CV29_SPEED_TABLE_ENABLE = 0b00010000, /** bit 4: STE, Speed Table Enable, "0" = values in CVs 2, 4 and 6, "1" = Custom table selected by CV 25 */
CV29_EXT_ADDRESSING = 0b00100000, /** bit 5: "0" = one byte addressing, "1" = two byte addressing */
CV29_OUTPUT_ADDRESS_MODE = 0b01000000, /** bit 6: "0" = Decoder Address Mode "1" = Output Address Mode */
CV29_ACCESSORY_DECODER = 0b10000000, /** bit 7: "0" = Multi-Function Decoder Mode "1" = Accessory Decoder Mode */
} CV_29_BITS;
typedef enum {
#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
SPEED_STEP_14 = 15, /**< ESTOP=0, 1 to 15 */
#endif
SPEED_STEP_28 = 29, /**< ESTOP=0, 1 to 29 */
SPEED_STEP_128 = 127 /**< ESTOP=0, 1 to 127 */
} DCC_SPEED_STEPS;
typedef enum {
DCC_DIR_REV = 0, /** The locomotive to go in the reverse direction */
DCC_DIR_FWD = 1, /** The locomotive should move in the forward direction */
} DCC_DIRECTION;
typedef enum {
DCC_ADDR_SHORT, /** Short address is used. The range is 0 to 127. */
DCC_ADDR_LONG, /** Long Address is used. The range is 1 to 10239 */
} DCC_ADDR_TYPE;
typedef enum
{
FN_0_4 = 1,
FN_5_8,
FN_9_12,
FN_13_20,
FN_21_28,
#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
FN_0 /** function light is controlled by base line package (14 speed steps) */
#endif
} FN_GROUP;
#define FN_BIT_00 0x10
#define FN_BIT_01 0x01
#define FN_BIT_02 0x02
#define FN_BIT_03 0x04
#define FN_BIT_04 0x08
#define FN_BIT_05 0x01
#define FN_BIT_06 0x02
#define FN_BIT_07 0x04
#define FN_BIT_08 0x08
#define FN_BIT_09 0x01
#define FN_BIT_10 0x02
#define FN_BIT_11 0x04
#define FN_BIT_12 0x08
#define FN_BIT_13 0x01
#define FN_BIT_14 0x02
#define FN_BIT_15 0x04
#define FN_BIT_16 0x08
#define FN_BIT_17 0x10
#define FN_BIT_18 0x20
#define FN_BIT_19 0x40
#define FN_BIT_20 0x80
#define FN_BIT_21 0x01
#define FN_BIT_22 0x02
#define FN_BIT_23 0x04
#define FN_BIT_24 0x08
#define FN_BIT_25 0x10
#define FN_BIT_26 0x20
#define FN_BIT_27 0x40
#define FN_BIT_28 0x80
#ifdef DCC_DBGVAR
typedef struct countOf_t {
unsigned long Tel;
unsigned long Err;
}countOf_t ;
countOf_t countOf;
#endif
// Flag values to be logically ORed together and passed into the init() method
#define FLAGS_MY_ADDRESS_ONLY 0x01 // Only process DCC Packets with My Address
#define FLAGS_AUTO_FACTORY_DEFAULT 0x02 // Call notifyCVResetFactoryDefault() if CV 7 & 8 == 255
#define FLAGS_SETCV_CALLED 0x10 // only used internally !!
#define FLAGS_OUTPUT_ADDRESS_MODE 0x40 // CV 29/541 bit 6
#define FLAGS_DCC_ACCESSORY_DECODER 0x80 // CV 29/541 bit 7
// Flag Bits that are cloned from CV29 relating the DCC Accessory Decoder
#define FLAGS_CV29_BITS (FLAGS_OUTPUT_ADDRESS_MODE | FLAGS_DCC_ACCESSORY_DECODER)
#define DCC_RESET 1
#define DCC_IDLE 2
#define DCC_SPEED 3
#define DCC_SPEED_RAW 4
#define DCC_FUNC 5
#define DCC_TURNOUT 6
#define DCC_ACCESSORY 7
#define DCC_RAW 8
#define DCC_SERVICEMODE 9
#define CV_VALID 10
#define CV_READ 11
#define CV_WRITE 12
#define CV_RESET 13
void dcc_setup(uint8_t pin, uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV );
void dcc_close();
void dcc_init();
/************************************************************************************
Call-back functions
************************************************************************************/
/*+
* notifyDccReset(uint8_t hardReset) Callback for a DCC reset command.
*
* Inputs:
* hardReset - 0 normal reset command.
* 1 hard reset command.
*
* Returns:
* None
*/
extern void notifyDccReset(uint8_t hardReset ) __attribute__ ((weak));
/*+
* notifyDccIdle() Callback for a DCC idle command.
*
* Inputs:
* None
*
* Returns:
* None
*/
extern void notifyDccIdle(void) __attribute__ ((weak));
/*+
* notifyDccSpeed() Callback for a multifunction decoder speed command.
* The received speed and direction are unpacked to separate values.
*
* Inputs:
* Addr - Active decoder address.
* AddrType - DCC_ADDR_SHORT or DCC_ADDR_LONG.
* Speed - Decoder speed. 0 = Emergency stop
* 1 = Regular stop
* 2 to SpeedSteps = Speed step 1 to max.
* Dir - DCC_DIR_REV or DCC_DIR_FWD
* SpeedSteps - Highest speed, SPEED_STEP_14 = 15
* SPEED_STEP_28 = 29
* SPEED_STEP_128 = 127
*
* Returns:
* None
*/
extern void notifyDccSpeed( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Speed, DCC_DIRECTION Dir, DCC_SPEED_STEPS SpeedSteps ) __attribute__ ((weak));
/*+
* notifyDccSpeedRaw() Callback for a multifunction decoder speed command.
* The value in Raw is the unpacked speed command.
*
* Inputs:
* Addr - Active decoder address.
* AddrType - DCC_ADDR_SHORT or DCC_ADDR_LONG.
* Raw - Raw decoder speed command.
*
* Returns:
* None
*/
extern void notifyDccSpeedRaw( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Raw) __attribute__ ((weak));
/*+
* notifyDccFunc() Callback for a multifunction decoder function command.
*
* Inputs:
* Addr - Active decoder address.
* AddrType - DCC_ADDR_SHORT or DCC_ADDR_LONG.
* FuncGrp - Function group. FN_0 - 14 speed step headlight function.
* Mask FN_BIT_00.
* FN_0_4 - Functions 0 to 4. Mask FN_BIT_00 - FN_BIT_04
* FN_5_8 - Functions 5 to 8. Mask FN_BIT_05 - FN_BIT_08
* FN_9_12 - Functions 9 to 12. Mask FN_BIT_09 - FN_BIT_12
* FN_13_20 - Functions 13 to 20. Mask FN_BIT_13 - FN_BIT_20
* FN_21_28 - Functions 21 to 28. Mask FN_BIT_21 - FN_BIT_28
* FuncState - Function state. Bitmask where active functions have a 1 at that bit.
* You must & FuncState with the appropriate
* FN_BIT_nn value to isolate a given bit.
*
* Returns:
* None
*/
extern void notifyDccFunc( uint16_t Addr, DCC_ADDR_TYPE AddrType, FN_GROUP FuncGrp, uint8_t FuncState) __attribute__ ((weak));
/*+
* notifyDccAccTurnoutBoard() Board oriented callback for a turnout accessory decoder.
* Most useful when CV29_OUTPUT_ADDRESS_MODE is not set.
* Decoders of this type have 4 paired turnout outputs per board.
* OutputPower is 1 if the power is on, and 0 otherwise.
*
* Inputs:
* BoardAddr - Per board address. Equivalent to CV 1 LSB & CV 9 MSB.
* OutputPair - Output pair number. It has a range of 0 to 3.
* Equivalent to upper 2 bits of the 3 DDD bits in the accessory packet.
* Direction - Turnout direction. It has a value of 0 or 1.
* It is equivalent to bit 0 of the 3 DDD bits in the accessory packet.
* OutputPower - Output On/Off. Equivalent to packet C bit. It has these values:
* 0 - Output pair is off.
* 1 - Output pair is on.
*
* Returns:
* None
*/
extern void notifyDccAccTurnoutBoard( uint16_t BoardAddr, uint8_t OutputPair, uint8_t Direction, uint8_t OutputPower ) __attribute__ ((weak));
/*+
* notifyDccAccTurnoutOutput() Output oriented callback for a turnout accessory decoder.
* Most useful when CV29_OUTPUT_ADDRESS_MODE is not set.
* Decoders of this type have 4 paired turnout outputs per board.
* OutputPower is 1 if the power is on, and 0 otherwise.
*
* Inputs:
* Addr - Per output address. There will be 4 Addr addresses
* per board for a standard accessory decoder with 4 output pairs.
* Direction - Turnout direction. It has a value of 0 or 1.
* Equivalent to bit 0 of the 3 DDD bits in the accessory packet.
* OutputPower - Output On/Off. Equivalent to packet C bit. It has these values:
* 0 - Output is off.
* 1 - Output is on.
*
* Returns:
* None
*/
extern void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower ) __attribute__ ((weak));
/*+
* notifyDccAccBoardAddrSet() Board oriented callback for a turnout accessory decoder.
* This notification is when a new Board Address is set to the
* address of the next DCC Turnout Packet that is received
*
* This is enabled via the setAccDecDCCAddrNextReceived() method above
*
* Inputs:
* BoardAddr - Per board address. Equivalent to CV 1 LSB & CV 9 MSB.
* per board for a standard accessory decoder with 4 output pairs.
*
* Returns:
* None
*/
extern void notifyDccAccBoardAddrSet( uint16_t BoardAddr) __attribute__ ((weak));
/*+
* notifyDccAccOutputAddrSet() Output oriented callback for a turnout accessory decoder.
* This notification is when a new Output Address is set to the
* address of the next DCC Turnout Packet that is received
*
* This is enabled via the setAccDecDCCAddrNextReceived() method above
*
* Inputs:
* Addr - Per output address. There will be 4 Addr addresses
* per board for a standard accessory decoder with 4 output pairs.
*
* Returns:
* None
*/
extern void notifyDccAccOutputAddrSet( uint16_t Addr) __attribute__ ((weak));
/*+
* notifyDccSigOutputState() Callback for a signal aspect accessory decoder.
* Defined in S-9.2.1 as the Extended Accessory Decoder Control Packet.
*
* Inputs:
* Addr - Decoder address.
* State - 6 bit command equivalent to S-9.2.1 00XXXXXX.
*
* Returns:
* None
*/
extern void notifyDccSigOutputState( uint16_t Addr, uint8_t State) __attribute__ ((weak));
/*+
* notifyDccMsg() Raw DCC packet callback.
* Called with raw DCC packet bytes.
*
* Inputs:
* Msg - Pointer to DCC_MSG structure. The values are:
* Msg->Size - Number of Data bytes in the packet.
* Msg->PreambleBits - Number of preamble bits in the packet.
* Msg->Data[] - Array of data bytes in the packet.
*
* Returns:
* None
*/
extern void notifyDccMsg( DCC_MSG * Msg ) __attribute__ ((weak));
/*+
* notifyCVValid() Callback to determine if a given CV is valid.
* This is called when the library needs to determine
* if a CV is valid. Note: If defined, this callback
* MUST determine if a CV is valid and return the
* appropriate value. If this callback is not defined,
* the library will determine validity.
*
* Inputs:
* CV - CV number.
* Writable - 1 for CV writes. 0 for CV reads.
*
* Returns:
* 1 - CV is valid.
* 0 - CV is not valid.
*/
extern uint8_t notifyCVValid( uint16_t CV, uint8_t Writable ) __attribute__ ((weak));
/*+
* notifyCVRead() Callback to read a CV.
* This is called when the library needs to read
* a CV. Note: If defined, this callback
* MUST return the value of the CV.
* If this callback is not defined,
* the library will read the CV from EEPROM.
*
* Inputs:
* CV - CV number.
*
* Returns:
* Value - Value of the CV.
*/
extern uint8_t notifyCVRead( uint16_t CV) __attribute__ ((weak));
/*+
* notifyCVWrite() Callback to write a value to a CV.
* This is called when the library needs to write
* a CV. Note: If defined, this callback
* MUST write the Value to the CV and return the value of the CV.
* If this callback is not defined,
* the library will read the CV from EEPROM.
*
* Inputs:
* CV - CV number.
* Value - Value of the CV.
*
* Returns:
* Value - Value of the CV.
*/
extern uint8_t notifyCVWrite( uint16_t CV, uint8_t Value) __attribute__ ((weak));
/*+
* notifyCVResetFactoryDefault() Called when CVs must be reset.
* This is called when CVs must be reset
* to their factory defaults. This callback
* should write the factory default value of
* relevent CVs using the setCV() method.
* setCV() must not block whens this is called.
* Test with isSetCVReady() prior to calling setCV()
*
* Inputs:
* None
* *
* Returns:
* None
*/
extern void notifyCVResetFactoryDefault(void) __attribute__ ((weak));
/*+
* notifyServiceMode(bool) Called when state of 'inServiceMode' changes
*
* Inputs:
* bool state of inServiceMode
* *
* Returns:
* None
*/
extern void notifyServiceMode(bool) __attribute__ ((weak));
// Deprecated, only for backward compatibility with version 1.4.2.
// Don't use in new designs. These functions may be dropped in future versions
// extern void notifyDccAccState( uint16_t Addr, uint16_t BoardAddr, uint8_t OutputAddr, uint8_t State ) __attribute__ ((weak));
// extern void notifyDccSigState( uint16_t Addr, uint8_t OutputIndex, uint8_t State) __attribute__ ((weak));
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
//#define LUA_USE_MODULES_COLOR_UTILS //#define LUA_USE_MODULES_COLOR_UTILS
//#define LUA_USE_MODULES_CRON //#define LUA_USE_MODULES_CRON
//#define LUA_USE_MODULES_CRYPTO //#define LUA_USE_MODULES_CRYPTO
//#define LUA_USE_MODULES_DCC
#define LUA_USE_MODULES_DHT #define LUA_USE_MODULES_DHT
//#define LUA_USE_MODULES_ENCODER //#define LUA_USE_MODULES_ENCODER
//#define LUA_USE_MODULES_ENDUSER_SETUP // USE_DNS in dhcpserver.h needs to be enabled for this module to work. //#define LUA_USE_MODULES_ENDUSER_SETUP // USE_DNS in dhcpserver.h needs to be enabled for this module to work.
......
// NodeMCU Lua port by @voborsky
// Module for handling NMRA DCC protocol
// #define NODE_DEBUG
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "driver/NmraDcc.h"
#ifdef LUA_USE_MODULES_DCC
#if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE)
#error Must have GPIO_INTERRUPT and GPIO_INTERRUPT_HOOK if using DCC module
#endif
#endif
#define TYPE "Type"
#define OPERATION "Operation"
static inline void register_lua_cb(lua_State* L,int* cb_ref){
int ref=luaL_ref(L, LUA_REGISTRYINDEX);
if( *cb_ref != LUA_NOREF){
luaL_unref(L, LUA_REGISTRYINDEX, *cb_ref);
}
*cb_ref = ref;
}
static inline void unregister_lua_cb(lua_State* L, int* cb_ref){
if(*cb_ref != LUA_NOREF){
luaL_unref(L, LUA_REGISTRYINDEX, *cb_ref);
*cb_ref = LUA_NOREF;
}
}
static int notify_cb = LUA_NOREF;
static int CV_cb = LUA_NOREF;
// DCC commands
void cbInit(lua_State* L, uint16_t command) {
if(notify_cb == LUA_NOREF)
return;
lua_rawgeti(L, LUA_REGISTRYINDEX, notify_cb);
lua_pushinteger(L, command);
lua_newtable(L);
}
void cbAddFieldInteger(lua_State* L, uint16_t Value, char *Field) {
lua_pushinteger(L, Value);
lua_setfield(L, -2, Field);
}
void notifyDccReset(uint8_t hardReset ) {
lua_State* L = lua_getstate();
cbInit(L, DCC_RESET);
cbAddFieldInteger(L, hardReset, "hardReset");
lua_call(L, 2, 0);
}
void notifyDccIdle(void) {
lua_State* L = lua_getstate();
cbInit(L, DCC_IDLE);
lua_call(L, 2, 0);
}
void notifyDccSpeed( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Speed, DCC_DIRECTION Dir, DCC_SPEED_STEPS SpeedSteps ) {
lua_State* L = lua_getstate();
cbInit(L, DCC_SPEED);
cbAddFieldInteger(L, Addr, "Addr");
cbAddFieldInteger(L, AddrType, "AddrType");
cbAddFieldInteger(L, Speed, "Speed");
cbAddFieldInteger(L, Dir, "Dir");
cbAddFieldInteger(L, SpeedSteps, "SpeedSteps");
lua_call(L, 2, 0);
}
void notifyDccSpeedRaw( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Raw) {
lua_State* L = lua_getstate();
cbInit(L, DCC_SPEED_RAW);
cbAddFieldInteger(L, Addr, "Addr");
cbAddFieldInteger(L, AddrType, "AddrType");
cbAddFieldInteger(L, Raw, "Raw");
lua_call(L, 2, 0);
}
void notifyDccFunc( uint16_t Addr, DCC_ADDR_TYPE AddrType, FN_GROUP FuncGrp, uint8_t FuncState) {
lua_State* L = lua_getstate();
cbInit(L, DCC_FUNC);
cbAddFieldInteger(L, Addr, "Addr");
cbAddFieldInteger(L, AddrType, "AddrType");
cbAddFieldInteger(L, FuncGrp, "FuncGrp");
cbAddFieldInteger(L, FuncState, "FuncState");
lua_call(L, 2, 0);
}
void notifyDccAccTurnoutBoard( uint16_t BoardAddr, uint8_t OutputPair, uint8_t Direction, uint8_t OutputPower ) {
lua_State* L = lua_getstate();
cbInit(L, DCC_TURNOUT);
cbAddFieldInteger(L, BoardAddr, "BoardAddr");
cbAddFieldInteger(L, OutputPair, "OutputPair");
cbAddFieldInteger(L, Direction, "Direction");
cbAddFieldInteger(L, OutputPower, "OutputPower");
lua_call(L, 2, 0);
}
void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower ) {
lua_State* L = lua_getstate();
cbInit(L, DCC_TURNOUT);
cbAddFieldInteger(L, Addr, "Addr");
cbAddFieldInteger(L, Direction, "Direction");
cbAddFieldInteger(L, OutputPower, "OutputPower");
lua_call(L, 2, 0);
}
void notifyDccAccBoardAddrSet( uint16_t BoardAddr) {
lua_State* L = lua_getstate();
cbInit(L, DCC_ACCESSORY);
cbAddFieldInteger(L, BoardAddr, "BoardAddr");
lua_call(L, 2, 0);
}
void notifyDccAccOutputAddrSet( uint16_t Addr) {
lua_State* L = lua_getstate();
cbInit(L, DCC_ACCESSORY);
cbAddFieldInteger(L, Addr, "Addr");
lua_call(L, 2, 0);
}
void notifyDccSigOutputState( uint16_t Addr, uint8_t State) {
lua_State* L = lua_getstate();
cbInit(L, DCC_ACCESSORY);
cbAddFieldInteger(L, State, "State");
lua_call(L, 2, 0);
}
void notifyDccMsg( DCC_MSG * Msg ) {
lua_State* L = lua_getstate();
cbInit(L, DCC_RAW);
cbAddFieldInteger(L, Msg->Size, "Size");
cbAddFieldInteger(L, Msg->PreambleBits, "PreambleBits");
char field[8];
for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ ) {
ets_sprintf(field, "Data%d", i);
cbAddFieldInteger(L, Msg->Data[i], field);
}
lua_call(L, 2, 0);
}
void notifyServiceMode(bool InServiceMode){
lua_State* L = lua_getstate();
cbInit(L, DCC_SERVICEMODE);
cbAddFieldInteger(L, InServiceMode, "InServiceMode");
lua_call(L, 2, 0);
}
// CV handling
uint8_t notifyCVValid( uint16_t CV, uint8_t Writable ) {
lua_State* L = lua_getstate();
if(notify_cb == LUA_NOREF)
return 0;
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb);
lua_pushinteger(L, CV_VALID);
lua_newtable(L);
cbAddFieldInteger(L, CV, "CV");
cbAddFieldInteger(L, Writable, "Writable");
lua_call(L, 2, 1);
uint8 result = lua_tointeger(L, -1);
lua_pop(L, 1);
return result;
}
uint8_t notifyCVRead( uint16_t CV) {
lua_State* L = lua_getstate();
if(notify_cb == LUA_NOREF)
return 0;
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb);
lua_pushinteger(L, CV_READ);
lua_newtable(L);
cbAddFieldInteger(L, CV, "CV");
lua_call(L, 2, 1);
uint8 result = lua_tointeger(L, -1);
lua_pop(L, 1);
return result;
}
uint8_t notifyCVWrite( uint16_t CV, uint8_t Value) {
lua_State* L = lua_getstate();
if(notify_cb == LUA_NOREF)
return 0;
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb);
lua_pushinteger(L, CV_WRITE);
lua_newtable(L);
cbAddFieldInteger(L, CV, "CV");
cbAddFieldInteger(L, Value, "Value");
lua_call(L, 2, 0);
return Value;
}
void notifyCVResetFactoryDefault(void) {
lua_State* L = lua_getstate();
if(notify_cb == LUA_NOREF)
return;
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb);
lua_pushinteger(L, CV_RESET);
lua_call(L, 1, 0);
}
static int dcc_lua_setup(lua_State* L) {
NODE_DBG("[dcc_lua_setup]\n");
if (!lua_isnumber(L, 6) && !lua_isnumber(L, 7)) {
return luaL_error(L, "wrong arg range");
}
uint8_t pin = luaL_checkinteger(L, 1);
luaL_argcheck(L, platform_gpio_exists(pin) && pin>0, 1, "Invalid interrupt pin");
if (lua_type(L, 2) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION)
{
lua_pushvalue(L, 2);
register_lua_cb(L, &notify_cb);
}
else
{
unregister_lua_cb(L, &notify_cb);
}
uint8_t ManufacturerId = luaL_checkinteger(L, 3);
uint8_t VersionId = luaL_checkinteger(L, 4);
uint8_t Flags = luaL_checkinteger(L, 5);
uint8_t OpsModeAddressBaseCV = luaL_checkinteger(L, 6);
if (lua_type(L, 7) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION)
{
lua_pushvalue(L, 7);
register_lua_cb(L, &CV_cb);
}
else
{
unregister_lua_cb(L, &CV_cb);
}
NODE_DBG("[dcc_lua_setup] Enabling interrupt on PIN %d\n", pin);
dcc_setup(pin, ManufacturerId, VersionId, Flags, OpsModeAddressBaseCV );
return 0;
}
static int dcc_lua_close(lua_State* L) {
dcc_close();
unregister_lua_cb(L, &notify_cb);
return 0;
}
int luaopen_dcc( lua_State *L ) {
NODE_DBG("[dcc_luaopen]\n");
dcc_init();
//DccRx.lua_cb_ref = LUA_NOREF;
return 0;
}
// Module function map
LROT_BEGIN( dcc )
LROT_FUNCENTRY( setup, dcc_lua_setup )
LROT_FUNCENTRY( close, dcc_lua_close )
LROT_NUMENTRY( DCC_RESET, DCC_RESET )
LROT_NUMENTRY( DCC_IDLE, DCC_IDLE )
LROT_NUMENTRY( DCC_SPEED, DCC_SPEED )
LROT_NUMENTRY( DCC_SPEED_RAW, DCC_SPEED_RAW )
LROT_NUMENTRY( DCC_FUNC, DCC_FUNC )
LROT_NUMENTRY( DCC_TURNOUT, DCC_TURNOUT )
LROT_NUMENTRY( DCC_ACCESSORY, DCC_ACCESSORY )
LROT_NUMENTRY( DCC_RAW, DCC_RAW )
LROT_NUMENTRY( DCC_SERVICEMODE, DCC_SERVICEMODE )
LROT_NUMENTRY( CV_VALID, CV_VALID )
LROT_NUMENTRY( CV_READ, CV_READ )
LROT_NUMENTRY( CV_WRITE, CV_WRITE )
LROT_NUMENTRY( CV_RESET, CV_RESET )
LROT_NUMENTRY( MAN_ID_JMRI, MAN_ID_JMRI)
LROT_NUMENTRY( MAN_ID_DIY, MAN_ID_DIY)
LROT_NUMENTRY( MAN_ID_SILICON_RAILWAY, MAN_ID_SILICON_RAILWAY)
LROT_NUMENTRY( FLAGS_MY_ADDRESS_ONLY, FLAGS_MY_ADDRESS_ONLY )
LROT_NUMENTRY( FLAGS_AUTO_FACTORY_DEFAULT, FLAGS_AUTO_FACTORY_DEFAULT )
LROT_NUMENTRY( FLAGS_OUTPUT_ADDRESS_MODE, FLAGS_OUTPUT_ADDRESS_MODE )
LROT_NUMENTRY( FLAGS_DCC_ACCESSORY_DECODER, FLAGS_DCC_ACCESSORY_DECODER )
LROT_END( dcc, NULL, 0 )
NODEMCU_MODULE(DCC, "dcc", dcc, luaopen_dcc);
# DCC module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2019-12-28 | [vsky279](https://github.com/vsky279) | [vsky279](https://github.com/vsky279) | [dcc.c](../../app/modules/dcc.c)|
The dcc module implements decoder of the [National Model Railroad Association](https://www.nmra.org/) (NMRA) Digital Command Control (DCC) decoder - see [DCC wiki](https://dccwiki.com/Introduction_to_DCC) for details.
The hardware needed to decode the DCC signal can be built based on different DCC decoders implementation for Arduino, for inspiration see [https://mrrwa.org/dcc-decoder-interface/](https://mrrwa.org/dcc-decoder-interface/). Basically the signal from the DCC bus is connected via an optocoupler to any GPIO pin. The DCC bus can be also used to power the ESP.
The module is based on the project NmraDcc [https://github.com/mrrwa/NmraDcc](https://github.com/mrrwa/NmraDcc) by Alex Shepherd. The module is based on the version from May 2005, commit [6d12e6cd3f5f520020d49946652a94c1e3473f6b](https://github.com/mrrwa/NmraDcc/tree/6d12e6cd3f5f520020d49946652a94c1e3473f6b).
## dcc.setup()
Initializes the dcc module and links callback functions.
#### Syntax
`dcc.setup(DCC_command, ManufacturerId, VersionId, Flags, OpsModeAddressBaseCV, CV_callback)`
#### Parameters
- `DCC_command(cmd, params)` calllback function that is called when a DCC command is decoded. `cmd` parameters is one of the following values. `params` contains a collection of parameters specific to given command.
- `dcc.DCC_RESET` no additional parameters, `params` is `nil`.
- `dcc.DCC_IDLE` no additional parameters, `params` is `nil`.
- `dcc.DCC_SPEED` parameters collection members are `Addr`, `AddrType`, `Speed`,`Dir`, `SpeedSteps`.
- `dcc.DCC_SPEED_RAW` parameters collection members are `Addr`, `AddrType`, `Raw`.
- `dcc.DCC_FUNC` parameters collection members are `Addr`, `AddrType`, `FuncGrp`,`FuncState`.
- `dcc.DCC_TURNOUT` parameters collection members are `BoardAddr`, `OutputPair`, `Direction`,`OutputPower` or `Addr`, `Direction`,`OutputPower`.
- `dcc.DCC_ACCESSORY` parameters collection has one member `BoardAddr` or `Addr` or `State`.
- `dcc.DCC_RAW` parameters collection member are `Size`, `PreambleBits`, `Data1` to `Data6`.
- `dcc.DCC_SERVICEMODE` parameters collection has one member `InServiceMode`.
- `ManufacturerId` Manufacturer ID returned in CV 8. Commonly `dcc.MAN_ID_DIY`.
- `VersionId` Version ID returned in CV 7.
- `Flags` one of or combination (OR operator) of
- `dcc.FLAGS_MY_ADDRESS_ONLY`Only process packets with My Address.
- `dcc.FLAGS_DCC_ACCESSORY_DECODER` Decoder is an accessory decode.
- `dcc.FLAGS_OUTPUT_ADDRESS_MODE` This flag applies to accessory decoders only. Accessory decoders normally have 4 paired outputs and a single address refers to all 4 outputs. Setting this flag causes each address to refer to a single output.
- `dcc.FLAGS_AUTO_FACTORY_DEFAULT` Call DCC command callback with `dcc.CV_RESET` command if CV 7 & 8 == 255.
- `OpsModeAddressBaseCV` Ops Mode base address. Set it to 0?
- `CV_callback(operation, param)` callback function that is called when any manipulation with CV ([Configuarion Variable](https://dccwiki.com/Configuration_Variable)) is requested.
- `dcc.CV_VALID`to determine if a given CV is valid. This callback must determine if a CV is valid and return the appropriate value. `param` collection has members `CV` and `Value`.
- `dcc.CV_READ` to read a CV. This callback must return the value of the CV. `param` collection has one member `CV` determing the CV number to be read.
- `dcc.CV_WRITE` to write a value to a CV. This callback must write the Value to the CV and return the value of the CV. `param` collection has members `CV` and `Value`.
- `dcc.CV_RESET` Called when CVs must be reset to their factory defaults.
#### Returns
`nil`
#### Example
`bit` module is used in the example though it is not needed for the dcc module functionality.
```lua
local PIN = 2 -- GPIO4
local addr = 0x12a
CV = {[29]=0,
[1]=bit.band(addr, 0x3f), --CV_ACCESSORY_DECODER_ADDRESS_LSB (6 bits)
[9]=bit.band(bit.rshift(addr,6), 0x7) --CV_ACCESSORY_DECODER_ADDRESS_MSB (3 bits)
}
local function DCC_command(cmd, params)
if cmd == dcc.DCC_IDLE then
return
elseif cmd == dcc.DCC_TURNOUT then
print("Turnout command")
elseif cmd == dcc.DCC_SPEED then
print("Speed command")
elseif cmd == dcc.DCC_FUNC then
print("Function command")
else
print("Other command", cmd)
end
for i,j in pairs(params) do
print(i, j)
end
print(("="):rep(80))
end
local function CV_callback(operation, param)
local oper = ""
local result
if operation == dcc.CV_WRITE then
oper = "Write"
CV[param.CV]=param.Value
elseif operation == dcc.CV_READ then
oper = "Read"
result = CV[param.CV]
elseif operation == dcc.CV_VALID then
oper = "Valid"
result = 1
elseif operation == CV_RESET then
oper = "Reset"
CV = {}
end
print(("[CV_callback] %s CV %d%s"):format(oper, param.CV or `nil`, param.Value and "\tValue: "..param.Value or "\tValue: nil"))
return result
end
dcc.setup(PIN,
DCC_command,
dcc.MAN_ID_DIY, 1,
--bit.bor(dcc.FLAGS_AUTO_FACTORY_DEFAULT, dcc.FLAGS_DCC_ACCESSORY_DECODER, dcc.FLAGS_MY_ADDRESS_ONLY),
bit.bor(dcc.FLAGS_AUTO_FACTORY_DEFAULT),
0, -- ???
CV_callback)
```
## dcc.close()
Stops the dcc module.
#### Syntax
`dcc.close()`
#### Parameters
`nil`
#### Returns
`nil`
-- Simple example for responding to NMRA DCC commands
-- author @voborsky
local PIN = 2 -- GPIO4
local addr = 0x12a
CV = {[29]=0,
[1]=bit.band(addr, 0x3f), --CV_ACCESSORY_DECODER_ADDRESS_LSB (6 bits)
[9]=bit.band(bit.rshift(addr,6), 0x7) --CV_ACCESSORY_DECODER_ADDRESS_MSB (3 bits)
}
local function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
local cmd_last
local params_last
local function is_new(cmd, params)
if cmd ~= cmd_last then return true end
for i,j in pairs(params) do
if params_last[i] ~= j then return true end
end
return false
end
local function DCC_command(cmd, params)
if not is_new(cmd, params) then return end
if cmd == dcc.DCC_IDLE then
return
elseif cmd == dcc.DCC_TURNOUT then
print("Turnout command")
elseif cmd == dcc.DCC_SPEED then
print("Speed command")
elseif cmd == dcc.DCC_FUNC then
print("Function command")
else
print("Other command", cmd)
end
for i,j in pairs(params) do
print(i, j)
end
print(("="):rep(80))
cmd_last = cmd
params_last = deepcopy(params)
end
local function CV_callback(operation, param)
local oper = ""
local result
if operation == dcc.CV_WRITE then
oper = "Write"
CV[param.CV]=param.Value
elseif operation == dcc.CV_READ then
oper = "Read"
result = CV[param.CV]
elseif operation == dcc.CV_VALID then
oper = "Valid"
result = 1
elseif operation == CV_RESET then
oper = "Reset"
CV = {}
end
print(("[CV_callback] %s CV %d%s"):format(oper, param.CV, param.Value and "\tValue: "..param.Value or "\tValue: nil"))
return result
end
dcc.setup(PIN,
DCC_command,
dcc.MAN_ID_DIY, 1,
--bit.bor(dcc.FLAGS_AUTO_FACTORY_DEFAULT, dcc.FLAGS_DCC_ACCESSORY_DECODER, dcc.FLAGS_MY_ADDRESS_ONLY), -- Accessories (turnouts) decoder
bit.bor(dcc.FLAGS_AUTO_FACTORY_DEFAULT), -- Cab (train) decoder
0, -- ???
CV_callback)
...@@ -67,6 +67,7 @@ pages: ...@@ -67,6 +67,7 @@ pages:
- 'color-utils': 'modules/color-utils.md' - 'color-utils': 'modules/color-utils.md'
- 'cron': 'modules/cron.md' - 'cron': 'modules/cron.md'
- 'crypto': 'modules/crypto.md' - 'crypto': 'modules/crypto.md'
- 'dcc': 'modules/dcc.md'
- 'dht': 'modules/dht.md' - 'dht': 'modules/dht.md'
- 'encoder': 'modules/encoder.md' - 'encoder': 'modules/encoder.md'
- 'enduser setup / captive portal / WiFi manager': 'modules/enduser-setup.md' - 'enduser setup / captive portal / WiFi manager': 'modules/enduser-setup.md'
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment