Add DCC decoder module (#2905)

This commit is contained in:
Lukáš Voborský 2019-12-28 14:10:11 +01:00 committed by Marcel Stör
parent db1e4454de
commit 36df8d009f
7 changed files with 2136 additions and 0 deletions

1161
app/driver/NmraDcc.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,479 @@
//------------------------------------------------------------------------
//
// 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));

View File

@ -21,6 +21,7 @@
//#define LUA_USE_MODULES_COLOR_UTILS
//#define LUA_USE_MODULES_CRON
//#define LUA_USE_MODULES_CRYPTO
//#define LUA_USE_MODULES_DCC
#define LUA_USE_MODULES_DHT
//#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.

290
app/modules/dcc.c Normal file
View File

@ -0,0 +1,290 @@
// 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);

118
docs/modules/dcc.md Normal file
View File

@ -0,0 +1,118 @@
# 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`

86
lua_examples/dcc/dcc.lua Normal file
View File

@ -0,0 +1,86 @@
-- 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)

View File

@ -67,6 +67,7 @@ pages:
- 'color-utils': 'modules/color-utils.md'
- 'cron': 'modules/cron.md'
- 'crypto': 'modules/crypto.md'
- 'dcc': 'modules/dcc.md'
- 'dht': 'modules/dht.md'
- 'encoder': 'modules/encoder.md'
- 'enduser setup / captive portal / WiFi manager': 'modules/enduser-setup.md'