nodemcu-firmware/app/modules/dcc.c

418 lines
11 KiB
C
Raw Normal View History

// NodeMCU Lua port by @voborsky, @pjsg
2019-12-28 14:10:11 +01:00
// Module for handling NMRA DCC protocol
// #define NODE_DEBUG
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "driver/NmraDcc.h"
#include "hw_timer.h"
2019-12-28 14:10:11 +01:00
#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"
#define TIMER_OWNER (('D' << 8) + 'C')
2019-12-28 14:10:11 +01:00
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;
static int CV_ref = LUA_NOREF;
static int8_t ackPin;
static char ackInProgress;
static platform_task_handle_t tasknumber;
typedef struct {
uint16_t cv;
uint8_t value;
} CVData;
2019-12-28 14:10:11 +01:00
// 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");
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
void notifyDccIdle(void) {
lua_State* L = lua_getstate();
cbInit(L, DCC_IDLE);
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
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");
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
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");
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
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");
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
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");
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
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");
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
void notifyDccAccBoardAddrSet( uint16_t BoardAddr) {
lua_State* L = lua_getstate();
cbInit(L, DCC_ACCESSORY);
cbAddFieldInteger(L, BoardAddr, "BoardAddr");
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
void notifyDccAccOutputAddrSet( uint16_t Addr) {
lua_State* L = lua_getstate();
cbInit(L, DCC_ACCESSORY);
cbAddFieldInteger(L, Addr, "Addr");
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
void notifyDccSigOutputState( uint16_t Addr, uint8_t State) {
lua_State* L = lua_getstate();
cbInit(L, DCC_ACCESSORY);
cbAddFieldInteger(L, State, "State");
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
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);
}
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
void notifyServiceMode(bool InServiceMode){
lua_State* L = lua_getstate();
cbInit(L, DCC_SERVICEMODE);
cbAddFieldInteger(L, InServiceMode, "InServiceMode");
luaL_pcallx(L, 2, 0);
2019-12-28 14:10:11 +01:00
}
// CV handling
uint16_t notifyCVValid( uint16_t CV, uint8_t Writable ) {
2019-12-28 14:10:11 +01:00
lua_State* L = lua_getstate();
if (CV_ref != LUA_NOREF) {
return 1;
}
2019-12-28 14:10:11 +01:00
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");
if (luaL_pcallx(L, 2, 1) != LUA_OK)
return 0;
uint8 result = lua_tointeger(L, -1) || lua_toboolean(L, -1);
2019-12-28 14:10:11 +01:00
lua_pop(L, 1);
return result;
}
static int doDirectCVRead(lua_State *L) {
CVData *data = (CVData*) lua_touserdata(L, -1);
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_ref);
lua_pushinteger(L, data->cv);
lua_gettable(L, -2);
data->value = (uint8_t) luaL_checkinteger(L, -1);
return 0;
}
static int doDirectCVWrite(lua_State *L) {
CVData *data = (CVData*) lua_touserdata(L, -1);
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_ref);
lua_pushinteger(L, data->cv);
lua_pushinteger(L, data->value);
lua_settable(L, -3);
return 0;
}
uint16_t notifyCVRead( uint16_t CV) {
2019-12-28 14:10:11 +01:00
lua_State* L = lua_getstate();
if (CV_ref != LUA_NOREF) {
CVData data;
data.cv = CV;
lua_pushcfunction(L, doDirectCVRead);
lua_pushlightuserdata(L, &data);
if (lua_pcall(L, 1, 0, 0)) {
// An error.
lua_pop(L, 1);
return 256;
}
return data.value;
}
2019-12-28 14:10:11 +01:00
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");
if (luaL_pcallx(L, 2, 1) != LUA_OK)
return 0;;
2019-12-28 14:10:11 +01:00
uint8 result = lua_tointeger(L, -1);
lua_pop(L, 1);
return result;
}
uint16_t notifyCVWrite( uint16_t CV, uint8_t Value) {
2019-12-28 14:10:11 +01:00
lua_State* L = lua_getstate();
if (CV_ref != LUA_NOREF) {
CVData data;
data.cv = CV;
data.value = Value;
lua_pushcfunction(L, doDirectCVWrite);
lua_pushlightuserdata(L, &data);
if (lua_pcall(L, 1, 0, 0)) {
// An error.
lua_pop(L, 1);
return 256;
}
return data.value;
}
2019-12-28 14:10:11 +01:00
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");
luaL_pcallx(L, 2, 1);
// Return is an optional value (if integer). If nil, then it is old style
if (!lua_isnil(L, -1)) {
Value = lua_tointeger(L, -1);
}
lua_pop(L, 1);
2019-12-28 14:10:11 +01:00
return Value;
}
static void notifyCVNoArgs(int callback_type) {
2019-12-28 14:10:11 +01:00
lua_State* L = lua_getstate();
if (notify_cb == LUA_NOREF) {
2019-12-28 14:10:11 +01:00
return;
}
2019-12-28 14:10:11 +01:00
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb);
lua_pushinteger(L, callback_type);
luaL_pcallx(L, 1, 0);
2019-12-28 14:10:11 +01:00
}
void notifyCVResetFactoryDefault(void) {
notifyCVNoArgs(CV_RESET);
}
void notifyCVAck(void) {
// Invoked when we should generate an ack pulse (if possible)
if (ackPin >= 0 && !ackInProgress) {
// Duration is 6ms +/- 1ms
platform_hw_timer_arm_us(TIMER_OWNER, 6 * 1000);
platform_gpio_write(ackPin, 1);
ackInProgress = TRUE;
}
}
static void ICACHE_RAM_ATTR cvAckComplete(os_param_t param) {
// Invoked when we should end the ack pulse
ackInProgress = FALSE;
platform_gpio_write(ackPin, 0);
if (CV_ref == LUA_NOREF) {
platform_post_high(tasknumber, CV_ACK_COMPLETE);
}
}
2019-12-28 14:10:11 +01:00
static int dcc_lua_setup(lua_State* L) {
NODE_DBG("[dcc_lua_setup]\n");
int narg = 1;
uint8_t pin = luaL_checkinteger(L, narg);
luaL_argcheck(L, platform_gpio_exists(pin) && pin>0, narg, "Invalid interrupt pin");
narg++;
int8_t ackpin = -1;
if (lua_type(L, narg) == LUA_TNUMBER) {
ackpin = luaL_checkinteger(L, narg);
luaL_argcheck(L, platform_gpio_exists(ackpin), narg, "Invalid ack pin");
narg++;
}
2019-12-28 14:10:11 +01:00
if (lua_isfunction(L, narg)) {
lua_pushvalue(L, narg++);
2019-12-28 14:10:11 +01:00
register_lua_cb(L, &notify_cb);
} else {
2019-12-28 14:10:11 +01:00
unregister_lua_cb(L, &notify_cb);
}
uint8_t ManufacturerId = luaL_checkinteger(L, narg++);
uint8_t VersionId = luaL_checkinteger(L, narg++);
uint8_t Flags = luaL_checkinteger(L, narg++);
uint8_t OpsModeAddressBaseCV = luaL_checkinteger(L, narg++);
if (lua_istable(L, narg)) {
// This is the raw CV table
lua_pushvalue(L, narg++);
register_lua_cb(L, &CV_ref);
} else {
unregister_lua_cb(L, &CV_ref);
2019-12-28 14:10:11 +01:00
}
if (lua_isfunction(L, narg)) {
lua_pushvalue(L, narg++);
register_lua_cb(L, &CV_cb);
} else {
2019-12-28 14:10:11 +01:00
unregister_lua_cb(L, &CV_cb);
}
if (ackpin >= 0) {
// Now start things up
if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, FALSE)) {
// Failed to init the timer
luaL_error(L, "Unable to initialize timer");
}
platform_hw_timer_set_func(TIMER_OWNER, cvAckComplete, 0);
platform_gpio_write(ackpin, 0);
platform_gpio_mode(ackpin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT);
}
2019-12-28 14:10:11 +01:00
NODE_DBG("[dcc_lua_setup] Enabling interrupt on PIN %d\n", pin);
ackPin = ackpin;
ackInProgress = FALSE;
2019-12-28 14:10:11 +01:00
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);
unregister_lua_cb(L, &CV_cb);
unregister_lua_cb(L, &CV_ref);
2019-12-28 14:10:11 +01:00
return 0;
}
static void dcc_task(os_param_t param, uint8_t prio)
{
(void) prio;
notifyCVNoArgs(param);
}
int dcc_lua_init( lua_State *L ) {
NODE_DBG("[dcc_lua_init]\n");
2019-12-28 14:10:11 +01:00
dcc_init();
tasknumber = platform_task_get_id(dcc_task);
2019-12-28 14:10:11 +01:00
return 0;
}
// Module function map
LROT_BEGIN(dcc, NULL, 0)
2019-12-28 14:10:11 +01:00
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( CV_ACK_COMPLETE, CV_ACK_COMPLETE )
2019-12-28 14:10:11 +01:00
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)
2019-12-28 14:10:11 +01:00
NODEMCU_MODULE(DCC, "dcc", dcc, dcc_lua_init);