Reorganisation fichiers

This commit is contained in:
Serge NOEL
2026-02-13 08:49:53 +01:00
parent ec9957d5b1
commit 758f73bc0e
33 changed files with 977 additions and 499 deletions

View File

@@ -0,0 +1,197 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
Simple XPT2046 SPI/Bitbang interface for PacoMouseCYD
*/
#include "config.h"
#include "XPT2046.h"
#define Z_THRESHOLD 300
#define MSEC_THRESHOLD 4
XPT2046_TS::XPT2046_TS(uint8_t mosiPin, uint8_t misoPin, uint8_t clkPin, uint8_t csPin) :
_mosiPin(mosiPin), _misoPin(misoPin), _clkPin(clkPin), _csPin(csPin) {
cal = TouchCalibration{0, 4095, 0, 4095, 0}; // other initializations, if required
_msraw = millis();
#ifdef USE_XPT2046_SPI
hspi = new SPIClass(HSPI); // XPT2046 connected to HSPI in CYD 2.4"
hspi->begin();
#endif
}
void XPT2046_TS::begin(uint16_t width, uint16_t height) {
pinMode(_csPin, OUTPUT);
digitalWrite(_csPin, HIGH);
#ifdef USE_XPT2046_BITBANG
pinMode(_clkPin, OUTPUT); // init all pins in bitbang mode only (CYD 2.8")
digitalWrite(_clkPin, LOW);
pinMode(_mosiPin, OUTPUT);
pinMode(_misoPin, INPUT);
#endif
_width = width;
_height = height;
}
void XPT2046_TS::setCalibration(uint16_t xMin, uint16_t xMax, uint16_t yMin, uint16_t yMax) {
cal.xMin = xMin;
cal.xMax = xMax;
cal.yMin = yMin;
cal.yMax = yMax;
}
TouchCalibration XPT2046_TS::getCalibration() {
return cal;
}
void XPT2046_TS::setRotation(uint8_t n) {
cal.rotation = n % 4;
}
bool XPT2046_TS::touched() {
update();
return (_zraw > Z_THRESHOLD);
}
TSPoint XPT2046_TS::getTouch() {
update();
uint16_t x = map(_xraw, cal.xMin, cal.xMax, 0, _width);
uint16_t y = map(_yraw, cal.yMin, cal.yMax, 0, _height);
if ((x >= _width) || (x <= 0) || (y >= _height) || (y <= 0))
_zraw = 0;
return TSPoint{x, y, _zraw};
}
void XPT2046_TS::readData(uint16_t *x, uint16_t *y, uint16_t *z) {
update();
*x = _xraw; // read raw data
*y = _yraw;
*z = _zraw;
}
#ifdef USE_XPT2046_BITBANG
uint16_t XPT2046_TS::readSPI(byte command) {
uint16_t result = 0;
for (int i = 7; i >= 0; i--) {
digitalWrite(_mosiPin, command & (1 << i)); // send command
digitalWrite(_clkPin, HIGH);
delayMicroseconds(7);
digitalWrite(_clkPin, LOW);
delayMicroseconds(7);
}
for (int i = 11; i >= 0; i--) { // read data
digitalWrite(_clkPin, HIGH);
delayMicroseconds(7);
digitalWrite(_clkPin, LOW);
delayMicroseconds(7);
result |= (digitalRead(_misoPin) << i);
}
return result;
}
void XPT2046_TS::update() {
int t;
uint32_t now = millis();
if (now - _msraw < MSEC_THRESHOLD)
return;
digitalWrite(_csPin, LOW);
readSPI(0xB0);
readSPI(0xB0);
readSPI(0xB0);
int z1 = readSPI(0xB0);
_zraw = z1 + 4095;
readSPI(0xC0);
readSPI(0xC0);
readSPI(0xC0);
int z2 = readSPI(0xC0);
_zraw -= z2;
readSPI(0x90);
readSPI(0x90);
readSPI(0x90);
_xraw = readSPI(0x90);
readSPI(0xD0);
readSPI(0xD0);
readSPI(0xD0);
_yraw = readSPI(0xD0);
digitalWrite(_csPin, HIGH);
_msraw = now;
switch (cal.rotation) {
case 0:
t = 4095 - _yraw;
_yraw = _xraw;
_xraw = t;
break;
case 1:
break;
case 2:
t = _xraw;
_xraw = _yraw;
_yraw = 4095 - t;
break;
default:
_xraw = 4095 - _xraw;
_yraw = 4095 - _yraw;
break;
}
}
#endif
#ifdef USE_XPT2046_SPI
void XPT2046_TS::update() {
int t;
uint32_t now = millis();
if (now - _msraw < MSEC_THRESHOLD)
return;
hspi->beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
digitalWrite(_csPin, LOW);
hspi->transfer(0xB0);
hspi->transfer16(0xB0);
hspi->transfer16(0xB0);
hspi->transfer16(0xB0);
int z1 = hspi->transfer16(0xC0) >> 3;
_zraw = z1 + 4095;
hspi->transfer16(0xC0);
hspi->transfer16(0xC0);
hspi->transfer16(0xC0);
int z2 = hspi->transfer16(0x90) >> 3;
_zraw -= z2;
hspi->transfer16(0x90);
hspi->transfer16(0x90);
hspi->transfer16(0x90);
_xraw = hspi->transfer16(0xD0) >> 3;
hspi->transfer16(0xD0);
hspi->transfer16(0xD0);
hspi->transfer16(0xD0);
_yraw = hspi->transfer16(0x0) >> 3;
digitalWrite(_csPin, HIGH);
hspi->endTransaction();
_msraw = now;
switch (cal.rotation) {
case 0:
_xraw = 4095 - _xraw;
break;
case 1:
t = _yraw;
_yraw = _xraw;
_xraw = t;
break;
case 2:
_yraw = 4095 - _yraw;
break;
default:
t = _yraw;
_yraw = 4095 - _xraw;
_xraw = 4095 - t;
break;
}
}
#endif

View File

@@ -0,0 +1,411 @@
/**
* @file accessories.ino
* @brief Accessory FIFO and control functions for PacoMouseCYD throttle.
* @author F. Cañada
* @date 2025-2026
* @copyright https://usuaris.tinet.cat/fmco/
*
* This file contains functions to manage accessory commands using a FIFO buffer,
* and to send accessory commands to the model train system.
*/
////////////////////////////////////////////////////////////
// API Documentation
////////////////////////////////////////////////////////////
/**
* @brief Initializes the accessory FIFO buffer.
*
* Resets FIFO indices and state to empty.
*/
void initFIFO();
/**
* @brief Reads the next accessory command from the FIFO.
* @return The next accessory command in the FIFO.
*/
unsigned int readFIFO();
/**
* @brief Writes an accessory command to the FIFO.
* @param FAdr The accessory address.
* @param pos The position or state to set.
*/
void writeFIFO(uint16_t FAdr, uint8_t pos);
/**
* @brief Processes and sends accessory commands from the FIFO.
*
* Handles timing and state transitions for accessory activation and deactivation.
*/
void sendAccessoryFIFO();
/**
* @brief Adds an accessory command to the FIFO for later execution.
* @param FAdr The accessory address.
* @param pos The position or state to set.
*/
void moveAccessory(uint16_t FAdr, uint8_t pos);
////////////////////////////////////////////////////////////
// End API Documentation
////////////////////////////////////////////////////////////
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** ACCESSORY FIFO *****
////////////////////////////////////////////////////////////
void initFIFO() {
lastInFIFO = 0;
firstOutFIFO = 0;
cntFIFO = 0;
stateFIFO = FIFO_EMPTY;
}
unsigned int readFIFO () {
firstOutFIFO = (firstOutFIFO + 1 ) & 0x1F; // next one (hardcoded)
cntFIFO--;
return (accessoryFIFO[firstOutFIFO]);
}
void writeFIFO (uint16_t FAdr, uint8_t pos) {
lastInFIFO = (lastInFIFO + 1 ) & 0x1F; // next one (hardcoded)
cntFIFO++;
if (pos > 0)
FAdr |= 0x8000;
accessoryFIFO[lastInFIFO] = FAdr; // save in FIFO
}
void sendAccessoryFIFO () {
switch (stateFIFO) {
case FIFO_ACC_CDU: // wait for CDU recharge
case FIFO_EMPTY:
if (cntFIFO > 0) { // activate accessory from FIFO
lastAccessory = readFIFO();
sendAccessory(lastAccessory & 0x7FFF, bitRead(lastAccessory, 15), true);
setTimer(TMR_ACCESSORY, TIME_ACC_ON, TMR_ONESHOT);
stateFIFO = FIFO_ACC_ON;
DEBUG_MSG("Moving acc. %d-%d", lastAccessory & 0x7FFF, lastAccessory >> 15);
}
else
stateFIFO = FIFO_EMPTY;
break;
case FIFO_ACC_ON: // deactivate accessory
sendAccessory(lastAccessory & 0x7FFF, bitRead(lastAccessory, 15), false);
setTimer(TMR_ACCESSORY, TIME_ACC_CDU, TMR_ONESHOT);
stateFIFO = FIFO_ACC_CDU;
break;
}
}
////////////////////////////////////////////////////////////
// ***** ACCESSORY *****
////////////////////////////////////////////////////////////
void moveAccessory(uint16_t FAdr, uint8_t pos) {
writeFIFO(FAdr, pos); // put accessory in FIFO
if (stateFIFO == FIFO_EMPTY) // if not pending accessories, force sending now
sendAccessoryFIFO();
}
void setAccAspect(uint16_t FAdr, uint16_t FAdr2, uint8_t aspect, uint16_t outs) {
uint8_t pos;
pos = aspect * 4;
if (FAdr > 0) {
if (bitRead(outs, pos)) {
moveAccessory(FAdr, 0);
}
else {
if (bitRead(outs, pos + 1))
moveAccessory(FAdr, 1);
}
}
if (FAdr2 > 0) {
if (bitRead(outs, pos + 2)) {
moveAccessory(FAdr2, 0);
}
else {
if (bitRead(outs, pos + 3))
moveAccessory(FAdr2, 1);
}
}
}
////////////////////////////////////////////////////////////
// ***** PANEL *****
////////////////////////////////////////////////////////////
void deleteAccPanelElement(uint8_t pos) {
accPanel[pos].type = ACC_UNDEF;
accPanel[pos].currAspect = 0;
accPanel[pos].addr = 0;
accPanel[pos].addr2 = 0;
accPanel[pos].activeOutput = 0;
accPanel[pos].accName[0] = '\0';
}
void loadDefaultAccPanel() {
uint8_t n;
for (n = 0; n < 16; n++) { // Default panel is all blank with only a keypad button
deleteAccPanelElement(n);
}
accPanel[15].type = ACC_KEYPAD;
}
void updateAccPanel() {
uint8_t n, type, aspect;
snprintf(panelNameBuf, PANEL_LNG + 1, panelNamesBuf[currPanel]);
for (n = 0; n < 16; n++) {
type = accPanel[n].type;
aspect = accPanel[n].currAspect;
snprintf(accNamesBuf[n], ACC_LNG + 1, accPanel[n].accName);
fncData[FNC_ACC0 + n].num = accDef[type].num;
fncData[FNC_ACC0 + n].idIcon = accDef[type].icon[aspect].fncIcon;
fncData[FNC_ACC0 + n].color = accDef[type].icon[aspect].color;
fncData[FNC_ACC0 + n].colorOn = accDef[type].icon[aspect].colorOn;
fncData[FNC_ACC0 + n].backgnd = (type == ACC_UNDEF) ? COLOR_WHITE : COLOR_LIGHTGREY;
buttonData[BUT_ACC_0 + n].backgnd = (type == ACC_UNDEF) ? COLOR_WHITE : COLOR_LIGHTGREY;
}
}
void saveCurrentAspects() {
uint8_t n;
for (n = 0; n < 16; n++)
savedAspect[currPanel][n] = accPanel[n].currAspect;
}
void getLastAspects() {
uint8_t n;
for (n = 0; n < 16; n++)
accPanel[n].currAspect = savedAspect[currPanel][n];
}
void deleteLastAspects() {
uint8_t n;
for (n = 0; n < 16; n++)
accPanel[n].currAspect = 0;
}
void initLastAspects() {
uint8_t i, j;
for (i = 0; i < 16; i++)
for (j = 0; j < 16; j++)
savedAspect[i][j] = 0;;
}
void populateAccPanel() {
loadDefaultAccPanel(); // Load panel data
if (sdDetected) {
if (loadAccPanel(SD))
getLastAspects();
else
deleteLastAspects();
}
else {
if (loadAccPanel(LittleFS))
getLastAspects();
else
deleteLastAspects();
}
updateAccPanel();
}
void accPanelClick(uint8_t pos) {
uint16_t addr, outs;
uint8_t type, aspect;
currPanelAcc = pos;
if (editAccessory) {
paramChild = pos;
type = accPanel[pos].type;
fncData[FNC_ACC_TYPE].num = accDef[type].num;
fncData[FNC_ACC_TYPE].idIcon = accDef[type].icon[0].fncIcon;
fncData[FNC_ACC_TYPE].color = accDef[type].icon[0].color;
fncData[FNC_ACC_TYPE].colorOn = accDef[type].icon[0].colorOn;
encoderValue = type;
encoderMax = ACC_MAX - 1;
openWindow(WIN_ACC_TYPE);
}
else {
type = accPanel[pos].type;
switch (type) {
case ACC_UNDEF:
break;
case ACC_KEYPAD:
openWindow(WIN_ACC_CTRL);
break;
default:
addr = accPanel[pos].addr;
outs = accPanel[pos].activeOutput;
switch (accDef[type].aspects) {
case 2:
aspect = (accPanel[pos].currAspect > 0) ? 0 : 1;
accPanel[pos].currAspect = aspect;
fncData[FNC_ACC0 + pos].idIcon = accDef[type].icon[aspect].fncIcon;
fncData[FNC_ACC0 + pos].color = accDef[type].icon[aspect].color;
fncData[FNC_ACC0 + pos].colorOn = accDef[type].icon[aspect].colorOn;
setAccAspect(addr, 0, aspect, outs);
newEvent(OBJ_BUTTON, BUT_ACC_0 + pos, EVNT_DRAW);
break;
case 3:
currAccAspects = 3;
winData[WIN_ACC_ASPECT].x = 30;
winData[WIN_ACC_ASPECT].w = 180;
buttonData[BUT_ACC_ASPECT0].x = 50;
buttonData[BUT_ACC_ASPECT1].x = 100;
buttonData[BUT_ACC_ASPECT2].x = 150;
fncData[FNC_ASPECT0].x = 54;
fncData[FNC_ASPECT0].idIcon = accDef[type].icon[0].fncIcon;
fncData[FNC_ASPECT0].color = accDef[type].icon[0].color;
fncData[FNC_ASPECT0].colorOn = accDef[type].icon[0].colorOn;
fncData[FNC_ASPECT1].x = 104;
fncData[FNC_ASPECT1].idIcon = accDef[type].icon[1].fncIcon;
fncData[FNC_ASPECT1].color = accDef[type].icon[1].color;
fncData[FNC_ASPECT1].colorOn = accDef[type].icon[1].colorOn;
fncData[FNC_ASPECT2].x = 154;
fncData[FNC_ASPECT2].idIcon = accDef[type].icon[2].fncIcon;
fncData[FNC_ASPECT2].color = accDef[type].icon[2].color;
fncData[FNC_ASPECT2].colorOn = accDef[type].icon[2].colorOn;
openWindow(WIN_ACC_ASPECT);
break;
case 4:
currAccAspects = 4;
winData[WIN_ACC_ASPECT].x = 5;
winData[WIN_ACC_ASPECT].w = 230;
buttonData[BUT_ACC_ASPECT0].x = 25;
buttonData[BUT_ACC_ASPECT1].x = 75;
buttonData[BUT_ACC_ASPECT2].x = 125;
buttonData[BUT_ACC_ASPECT3].x = 175;
fncData[FNC_ASPECT0].x = 29;
fncData[FNC_ASPECT0].idIcon = accDef[type].icon[0].fncIcon;
fncData[FNC_ASPECT0].color = accDef[type].icon[0].color;
fncData[FNC_ASPECT0].colorOn = accDef[type].icon[0].colorOn;
fncData[FNC_ASPECT1].x = 79;
fncData[FNC_ASPECT1].idIcon = accDef[type].icon[1].fncIcon;
fncData[FNC_ASPECT1].color = accDef[type].icon[1].color;
fncData[FNC_ASPECT1].colorOn = accDef[type].icon[1].colorOn;
fncData[FNC_ASPECT2].x = 129;
fncData[FNC_ASPECT2].idIcon = accDef[type].icon[2].fncIcon;
fncData[FNC_ASPECT2].color = accDef[type].icon[2].color;
fncData[FNC_ASPECT2].colorOn = accDef[type].icon[2].colorOn;
fncData[FNC_ASPECT3].x = 179;
fncData[FNC_ASPECT3].idIcon = accDef[type].icon[3].fncIcon;
fncData[FNC_ASPECT3].color = accDef[type].icon[3].color;
fncData[FNC_ASPECT3].colorOn = accDef[type].icon[3].colorOn;
openWindow(WIN_ACC_ASPECT);
break;
default:
buttonData[BUT_ACC_0 + pos].backgnd = COLOR_BLACK;
drawObject(OBJ_BUTTON, BUT_ACC_0 + pos);
buttonData[BUT_ACC_0 + pos].backgnd = COLOR_LIGHTGREY;
setAccAspect(addr, 0, 0, outs);
newEvent(OBJ_BUTTON, BUT_ACC_0 + pos, EVNT_DRAW);
delay(80);
break;
}
break;
}
}
}
void accAspectClick(uint8_t aspect) {
uint16_t addr, addr2, outs;
uint8_t type;
type = accPanel[currPanelAcc].type;
accPanel[currPanelAcc].currAspect = aspect;
fncData[FNC_ACC0 + currPanelAcc].idIcon = accDef[type].icon[aspect].fncIcon;
fncData[FNC_ACC0 + currPanelAcc].color = accDef[type].icon[aspect].color;
fncData[FNC_ACC0 + currPanelAcc].colorOn = accDef[type].icon[aspect].colorOn;
addr = accPanel[currPanelAcc].addr;
addr2 = accPanel[currPanelAcc].addr2;
outs = accPanel[currPanelAcc].activeOutput;
setAccAspect(addr, addr2, aspect, outs);
}
void accTypeClick() {
uint8_t index, n;
index = encoderValue;
switch (index) {
case ACC_UNDEF:
alertWindow(ERR_ASK_SURE);
break;
case ACC_KEYPAD:
editAccessory = false;
winData[WIN_ACCESSORY].backgnd = COLOR_WHITE;
deleteAccPanelElement(paramChild);
accPanel[paramChild].type = ACC_KEYPAD;
updateAccPanel();
updateSpeedHID(); // set encoder
closeWindow(WIN_ACC_TYPE);
break;
default:
if (index != accPanel[paramChild].type) {
currAccEdit.type = (accType)index;
currAccEdit.addr = 0;
currAccEdit.addr2 = 0;
currAccEdit.currAspect = 0;
currAccEdit.activeOutput = accOutDefault[index];
currAccEdit.accName[0] = '\0';
}
else {
currAccEdit = accPanel[paramChild];
}
snprintf(accKeybName, ACC_LNG + 1, currAccEdit.accName);
snprintf(accKeybAddr1, ADDR_LNG + 1, "%d", currAccEdit.addr);
snprintf(accKeybAddr2, ADDR_LNG + 1, "%d", currAccEdit.addr2);
for (n = 0; n < 4; n++) {
fncData[FNC_EDIT_ASPECT0 + n].idIcon = accDef[index].icon[n].fncIcon;
fncData[FNC_EDIT_ASPECT0 + n].color = accDef[index].icon[n].color;
fncData[FNC_EDIT_ASPECT0 + n].colorOn = accDef[index].icon[n].colorOn;
}
accOutUpdate();
closeWindow(WIN_ACC_TYPE);
openWindow(WIN_ACC_EDIT);
break;
}
}
void accOutUpdate() {
uint8_t n;
for (n = 0; n < 16; n += 2) {
buttonData[BUT_ACC_OUT0 + n].backgnd = bitRead(currAccEdit.activeOutput, n) ? COLOR_RED : COLOR_LIGHTBLACK;
buttonData[BUT_ACC_OUT0 + n + 1].backgnd = bitRead(currAccEdit.activeOutput, n + 1) ? COLOR_GREEN : COLOR_LIGHTBLACK;
}
}
void accOutClick(uint8_t out) {
uint8_t outR, outG;
outR = out & 0xFE;
outG = out | 0x01;
currAccEdit.activeOutput ^= bit(out);
if (bitRead(out, 0)) { // green
if (bitRead(currAccEdit.activeOutput, outG))
bitClear(currAccEdit.activeOutput, outR);
}
else { // red
if (bitRead(currAccEdit.activeOutput, outR))
bitClear(currAccEdit.activeOutput, outG);
}
accOutUpdate();
newEvent(OBJ_BUTTON, BUT_ACC_OUT0 + outR, EVNT_DRAW);
newEvent(OBJ_BUTTON, BUT_ACC_OUT0 + outG, EVNT_DRAW);
}
void updateAccChange() {
accPanel[paramChild] = currAccEdit;
updateAccPanel();
accPanelChanged = true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,238 @@
/**
* @file encoder.ino
* @brief Rotary encoder and button handling for PacoMouseCYD throttle.
* @author F. Cañada
* @date 2025-2026
* @copyright https://usuaris.tinet.cat/fmco/
*
* This file contains interrupt service routines and functions for reading and processing
* rotary encoder and button input, including debouncing and value management.
*/
////////////////////////////////////////////////////////////
// API Documentation
////////////////////////////////////////////////////////////
/**
* @brief Interrupt Service Routine for the encoder.
*
* Sets a flag indicating the encoder needs service.
*/
void IRAM_ATTR encoderISR();
/**
* @brief Handles encoder state changes and updates encoder value.
*/
void encoderService();
/**
* @brief Reads the encoder button and updates its state.
*/
void readButtons();
/**
* @brief Processes encoder movement and updates UI or state accordingly.
*/
void controlEncoder();
////////////////////////////////////////////////////////////
// End API Documentation
////////////////////////////////////////////////////////////
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** ENCODER *****
////////////////////////////////////////////////////////////
IRAM_ATTR void encoderISR () { // Encoder interrupt
encoderNeedService = true;
}
void encoderService () { // Encoder interrupt service
encoderNeedService = false;
lastTimeEncoder = millis();
outA = digitalRead (ENCODER_A);
outB = digitalRead (ENCODER_B);
if (outA != copyOutA) { // evitamos rebotes
copyOutA = outA;
if (copyOutB == 0x80) {
copyOutB = outB;
}
else {
if ( outB != copyOutB) {
copyOutB = 0x80;
if (outA == outB) // comprueba sentido de giro
encoderValue = (encoderValue < encoderMax) ? ++encoderValue : encoderMax ; // CW, hasta maximo
else
encoderValue = (encoderValue > 0) ? --encoderValue : 0; // CCW, hasta 0
encoderChange = true;
}
}
}
}
void readButtons () {
byte inputButton;
timeButtons = millis(); // lee cada cierto tiempo
inputButton = digitalRead (ENCODER_SW); // comprueba cambio en boton del encoder
if (statusSwitch != inputButton) {
statusSwitch = inputButton;
if (statusSwitch == LOW)
switchOn = true;
}
}
void controlEncoder() { // encoder movement
encoderChange = false;
aliveAndKicking();
DEBUG_MSG("Encoder: %d", encoderValue);
switch (objStack[lastWinStack].objID) {
case WIN_SSID:
scrSSID = encoderValue;
scanWiFiFill();
drawObject(OBJ_TXT, TXT_SSID1);
drawObject(OBJ_TXT, TXT_SSID2);
drawObject(OBJ_TXT, TXT_SSID3);
drawObject(OBJ_TXT, TXT_SSID4);
drawObject(OBJ_TXT, TXT_SSID5);
drawObject(OBJ_TXT, TXT_SSID6);
break;
case WIN_THROTTLE:
case WIN_SPEEDO:
case WIN_STA_PLAY:
updateMySpeed();
break;
case WIN_CHG_FUNC:
fncData[FNC_CHG].idIcon = encoderValue * 2;
drawObject(OBJ_FNC, FNC_CHG);
break;
case WIN_SEL_LOCO:
populateLocoList();
drawObject(OBJ_TXT, TXT_SEL_ADDR1);
drawObject(OBJ_TXT, TXT_SEL_NAME1);
drawObject(OBJ_TXT, TXT_SEL_ADDR2);
drawObject(OBJ_TXT, TXT_SEL_NAME2);
drawObject(OBJ_TXT, TXT_SEL_ADDR3);
drawObject(OBJ_TXT, TXT_SEL_NAME3);
drawObject(OBJ_TXT, TXT_SEL_ADDR4);
drawObject(OBJ_TXT, TXT_SEL_NAME4);
drawObject(OBJ_TXT, TXT_SEL_ADDR5);
drawObject(OBJ_TXT, TXT_SEL_NAME5);
drawObject(OBJ_TXT, TXT_SEL_ADDR6);
drawObject(OBJ_TXT, TXT_SEL_NAME6);
break;
case WIN_STEAM:
showSpeedSteam((encoderValue << 1) + 240);
break;
case WIN_ACC_TYPE:
fncData[FNC_ACC_TYPE].num = accDef[encoderValue].num;
fncData[FNC_ACC_TYPE].idIcon = accDef[encoderValue].icon[0].fncIcon;
fncData[FNC_ACC_TYPE].color = accDef[encoderValue].icon[0].color;
fncData[FNC_ACC_TYPE].colorOn = accDef[encoderValue].icon[0].colorOn;
drawObject(OBJ_FNC, FNC_ACC_TYPE);
break;
}
}
void controlSwitch() { // encoder switch
uint16_t value, value2, txtID;
uint32_t delta, dist;
char msg[NAME_LNG + 1];
switchOn = false;
aliveAndKicking();
DEBUG_MSG("Encoder Switch");
switch (objStack[lastWinStack].objID) {
case WIN_SSID:
snprintf (wifiSetting.ssid, 32, WiFi.SSID(scrSSID).c_str()); //saveSSID(scrSSID);
DEBUG_MSG("New SSID: %s", wifiSetting.ssid);
eepromChanged = true;
closeWindow(WIN_SSID);
openWindow(WIN_WIFI);
break;
case WIN_THROTTLE:
case WIN_STA_PLAY:
if (encoderValue > 0) {
encoderValue = 0;
if (stopMode > 0)
locoData[myLocoData].mySpeed = 1;
else
locoData[myLocoData].mySpeed = 0;
locoOperationSpeed();
}
else {
locoData[myLocoData].myDir ^= 0x80;
changeDirection();
}
updateSpeedDir();
break;
case WIN_CHG_FUNC:
fncData[FNC_F0 + paramChild].idIcon = fncData[FNC_CHG].idIcon;
closeWindow(WIN_CHG_FUNC);
break;
case WIN_SEL_LOCO:
releaseLoco();
txtID = (encoderValue > 5) ? 5 : encoderValue;
if (useID) {
value2 = (encoderValue > 5) ? encoderValue - 5 : 0;
value = sortedLocoStack[value2 + txtID];
}
else {
value = atoi(txtData[TXT_SEL_ADDR1 + txtID].buf);
}
//value = atoi(txtData[TXT_SEL_ADDR1 + txtID].buf);
DEBUG_MSG("Selected Loco %d", value);
closeWindow(WIN_SEL_LOCO);
getNewLoco(value);
break;
case WIN_SPEEDO:
switch (speedoPhase) { //enum speedo {SPD_WAIT, SPD_BEGIN, SPD_COUNT, SPD_ARRIVE, SPD_END};
case SPD_WAIT:
if (getCurrentStep() > 0) {
speedoStartTime = millis();
setSpeedoPhase(SPD_BEGIN);
getLabelTxt(LBL_MEASURE, msg);
snprintf(spdSpeedBuf, NAME_LNG + 1, "%s", msg);
drawObject(OBJ_TXT, TXT_SPEEDO_SPD);
setTimer(TMR_SPEEDO, 5, TMR_ONESHOT);
}
else {
locoData[myLocoData].myDir ^= 0x80;
changeDirection();
updateSpeedDir();
}
break;
case SPD_BEGIN:
case SPD_COUNT:
speedoEndTime = millis();
setSpeedoPhase(SPD_ARRIVE);
setTimer(TMR_SPEEDO, 5, TMR_ONESHOT);
dist = speedoLength * 36 * speedoScale;
delta = (speedoEndTime - speedoStartTime) * 10;
speedoSpeed = dist / delta;
snprintf(spdSpeedBuf, NAME_LNG + 1, "%d km/h", speedoSpeed);
drawObject(OBJ_TXT, TXT_SPEEDO_SPD);
break;
case SPD_ARRIVE:
break;
case SPD_END:
break;
}
break;
case WIN_STEAM:
steamThrottleStop();
currentSteamSpeed = 0;
locoData[myLocoData].mySpeed = 0;
locoOperationSpeed();
break;
case WIN_ACC_TYPE:
accTypeClick();
break;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,417 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
bool checkName(char *fileName) {
bool result;
uint16_t lng;
result = false;
lng = strlen(fileName);
if (lng > 4) {
if ((fileName[lng - 4] == '.') && (fileName[lng - 3] == 'c') && (fileName[lng - 2] == 's') && (fileName[lng - 1] == 'v'))
return true;
}
return result;
}
bool saveLocoData(fs::FS &fs, uint16_t pos) { // save loco data in .csv file
char field[30];
uint16_t cnt;
bool dataOK, isDir;
File myFile;
dataOK = false;
myFile = fs.open("/loco");
if (myFile) {
isDir = myFile.isDirectory();
myFile.close();
if (!isDir)
return dataOK;
DEBUG_MSG("/loco is a directory");
}
else {
DEBUG_MSG("Directory /loco not found. Creating...")
fs.mkdir("/loco");
}
sprintf (field, "/loco/%d.csv", locoData[pos].myAddr.address);
myFile = fs.open(field, FILE_WRITE);
if (myFile) {
DEBUG_MSG("File %s opened for writting", field);
getLabelTxt(LBL_NAME, field); // Header
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_IMAGE, field);
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_VMAX, field);
myFile.print(field);
for (cnt = 0; cnt < 29; cnt++) {
myFile.print(CSV_FILE_DELIMITER);
myFile.print('F');
myFile.print(cnt);
}
myFile.print("\r\n");
myFile.print(locoData[pos].myName); // Loco data
myFile.print(CSV_FILE_DELIMITER);
myFile.print(locoData[pos].myLocoID);
myFile.print(CSV_FILE_DELIMITER);
myFile.print(locoData[pos].myVmax);
for (cnt = 0; cnt < 29; cnt++) {
myFile.print(CSV_FILE_DELIMITER);
myFile.print(locoData[pos].myFuncIcon[cnt] >> 1);
}
myFile.print("\r\n");
myFile.close();
dataOK = true;
}
return dataOK;
}
void deleteLocoData (fs::FS &fs, uint16_t num)
{
char line[200];
sprintf (line, "/loco/%d.csv", num);
if (fs.remove(line)) {
DEBUG_MSG("File %s deleted", line);
}
else {
DEBUG_MSG("File %s delete failed", line);
}
}
bool readLocoData(fs::FS &fs, uint16_t num, uint16_t pos) { // read loco data from .csv file
char line[200];
bool dataOK;
uint16_t n;
File myFile;
dataOK = false;
sprintf (line, "/loco/%d.csv", num);
myFile = fs.open(line);
if (myFile) {
if (readCSV(myFile, line, sizeof(line), false)) { // skip header line
if (readCSV(myFile, locoData[pos].myName, sizeof(locoData[pos].myName), true)) { // read data field: Name
if (readCSV(myFile, line, sizeof(line), true)) { // read data field: Picture
locoData[pos].myLocoID = atoi(line);
if (readCSV(myFile, line, sizeof(line), true)) {
locoData[pos].myVmax = atoi(line);
DEBUG_MSG("%d %s %d %d", num, locoData[pos].myName, locoData[pos].myLocoID, locoData[pos].myVmax)
for (n = 0; n < 29; n++) {
if (!readCSV(myFile, line, sizeof(line), true)) { // read data field: Functions
myFile.close();
return false;
}
locoData[pos].myFuncIcon[n] = (uint8_t)(atoi(line) << 1);
//DEBUG_MSG("Func: %d - %d", n, locoData[pos].myFuncIcon[n])
}
locoData[pos].myAddr.address = num;
dataOK = true;
}
}
}
}
myFile.close();
}
return dataOK;
}
bool readCSV(File & f, char* line, uint16_t maxLen, bool getField) {
uint16_t n; // read field or line from CSV file
char chr;
n = 0;
yield();
while (n < maxLen) {
chr = f.read();
switch (chr) {
case 0:
line[n] = '\0';
return false; // EOF
break;
case '\r':
line[n] = '\0';
break;
case '\n':
line[n] = '\0';
return true;
break;
case ',':
case ';':
if (getField) {
line[n] = '\0';
return true;
}
break;
default:
line[n++] = chr;
break;
}
}
return false; // too long
}
void loadLocoFiles(fs::FS &fs, const char * dirname) {
uint16_t pos, adr;
char nameFile[50];
File myFile;
if (wifiSetting.protocol == CLIENT_ECOS)
return;
File root = fs.open(dirname);
if (!root) {
//DEBUG_MSG("Failed to open directory %s", dirname);
return;
}
if (!root.isDirectory()) {
//DEBUG_MSG("%s Not a directory", dirname);
return;
}
pos = 0;
File file = root.openNextFile();
while (file) {
if (! file.isDirectory()) {
if (pos < LOCOS_IN_STACK) {
sprintf(nameFile, "%s", file.name());
adr = atoi(nameFile);
//DEBUG_MSG("%d %s", adr, nameFile);
if (readLocoData(fs, adr, pos++))
pushLoco(adr);
}
}
file = root.openNextFile();
}
}
void initImageList() {
uint16_t n, maxImg;
uint16_t pos, id;
char nameFile[50];
File myFile;
for (n = 0; n < MAX_LOCO_IMAGE; n++) // add to list system images
locoImages[n] = (n < (MAX_SYS_LPIC - 1)) ? n + 1 : 0;
if (wifiSetting.protocol != CLIENT_ECOS) {
if (sdDetected) { // add to list user images from SD
File root = SD.open("/image");
if (root) {
if (root.isDirectory()) {
pos = MAX_SYS_LPIC - 1;
File file = root.openNextFile();
while (file) {
if (! file.isDirectory()) {
if (pos < MAX_LOCO_IMAGE) {
sprintf(nameFile, "%s", file.name());
id = atoi(nameFile);
if (id >= 1000) {
locoImages[pos++] = id;
}
}
}
file = root.openNextFile();
}
}
}
}
}
locoImageIndex = 0;
}
void populateImageList() {
uint16_t n;
for (n = 0; n < 6; n++)
lpicData[LPIC_SEL_IMG1 + n].id = locoImages[locoImageIndex + n];
}
bool saveCurrAccPanel(fs::FS &fs) {
char field[30];
uint16_t cnt;
bool dataOK, isDir;
File myFile;
dataOK = false;
myFile = fs.open("/acc");
if (myFile) {
isDir = myFile.isDirectory();
myFile.close();
if (!isDir)
return dataOK;
DEBUG_MSG("/acc is a directory");
}
else {
DEBUG_MSG("Directory /acc not found. Creating...")
fs.mkdir("/acc");
}
sprintf (field, "/acc/%d.csv", currPanel);
myFile = fs.open(field, FILE_WRITE);
if (myFile) {
DEBUG_MSG("File %s opened for writting", field);
getLabelTxt(LBL_ACC_TYPE, field); // Header
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_ACC_ADDR, field);
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_ACC_ADDR, field);
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_ACC_NAME, field);
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_BITS, field);
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
myFile.print("\r\n");
for (cnt = 0; cnt < 16; cnt++) {
myFile.print(accPanel[cnt].type); // Acc data
myFile.print(CSV_FILE_DELIMITER);
myFile.print(accPanel[cnt].addr);
myFile.print(CSV_FILE_DELIMITER);
myFile.print(accPanel[cnt].addr2);
myFile.print(CSV_FILE_DELIMITER);
myFile.print(accPanel[cnt].accName);
myFile.print(CSV_FILE_DELIMITER);
myFile.print(accPanel[cnt].activeOutput);
myFile.print(CSV_FILE_DELIMITER);
myFile.print("\r\n");
}
myFile.close();
dataOK = true;
}
return dataOK;
}
bool loadAccPanel(fs::FS & fs) {
char line[200];
bool dataOK;
uint16_t cnt = 0;
File myFile;
dataOK = false;
sprintf (line, "/acc/%d.csv", currPanel);
myFile = fs.open(line);
if (myFile) {
if (readCSV(myFile, line, sizeof(line), false)) { // skip header line
for (cnt = 0; cnt < 16; cnt++) {
readCSV(myFile, line, sizeof(line), true);
accPanel[cnt].type = (accType)atoi(line);
readCSV(myFile, line, sizeof(line), true);
accPanel[cnt].addr = atoi(line);
readCSV(myFile, line, sizeof(line), true);
accPanel[cnt].addr2 = atoi(line);
readCSV(myFile, line, sizeof(line), true);
snprintf(accPanel[cnt].accName, ACC_LNG + 1, line);
readCSV(myFile, line, sizeof(line), false);
accPanel[cnt].activeOutput = atoi(line);
DEBUG_MSG("Line %d: %d, %d, %d, \"%s\", %d", cnt, accPanel[cnt].type, accPanel[cnt].addr, accPanel[cnt].addr2, accPanel[cnt].accName, accPanel[cnt].activeOutput)
}
}
myFile.close();
dataOK = true;
}
return dataOK;
}
bool saveAccPanelNames(fs::FS & fs) {
char field[30];
uint16_t cnt;
bool dataOK, isDir;
File myFile;
dataOK = false;
myFile = fs.open("/acc");
if (myFile) {
isDir = myFile.isDirectory();
myFile.close();
if (!isDir)
return dataOK;
DEBUG_MSG("/acc is a directory");
}
else {
DEBUG_MSG("Directory /acc not found. Creating...")
fs.mkdir("/acc");
}
sprintf (field, "/acc/panel.csv");
myFile = fs.open(field, FILE_WRITE);
if (myFile) {
DEBUG_MSG("File %s opened for writting", field);
getLabelTxt(LBL_NAME, field); // Header
myFile.print(field);
myFile.print("\r\n");
for (cnt = 0; cnt < 16; cnt++) {
myFile.print(panelNamesBuf[cnt]); // Panel names
myFile.print("\r\n");
}
myFile.close();
dataOK = true;
}
return dataOK;
}
bool loadAccPanelNames(fs::FS & fs) {
char line[200];
bool dataOK;
uint16_t n;
File myFile;
dataOK = false;
sprintf (line, "/acc/panel.csv");
myFile = fs.open(line);
if (myFile) {
if (readCSV(myFile, line, sizeof(line), false)) { // skip header line
for (n = 0; n < 16; n++) {
if (readCSV(myFile, line, sizeof(line), true)) { // read data field
snprintf(panelNamesBuf[n], PANEL_LNG + 1, line);
DEBUG_MSG("%s", panelNamesBuf[n])
}
}
}
myFile.close();
}
else {
for (n = 0; n < 16; n++) // default names
snprintf(panelNamesBuf[n], PANEL_LNG + 1, "Panel %d", n);
}
return dataOK;
}
/*
void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.path(), levels - 1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** STATION RUN - MODEL TRAIN GAME FOR KIDS *****
////////////////////////////////////////////////////////////
void updateStationTime (uint16_t seconds) {
snprintf(staTimeBuf, ACC_LNG + 1, "%d:%02d", seconds / 60, seconds % 60);
}
void updateStationTarget() {
staStations = staLevel + 4;
if (staStartTime < 10)
staStartTime = 10;
staTime = staStartTime + ((staLevel - 1) * 10);
}
void newStationCounters (bool ini) {
if (ini) {
staStars = 0;
staLevel = 1;
randomSeed(millis());
}
staCurrStation = 0;
updateStationTarget();
}
uint8_t newStation(byte last) {
uint8_t station; // genera numero estacion sin repetir la ultima
do {
station = random (0, staMaxStations);
} while (station == last);
return (station);
}
void updateTargetStations() {
snprintf(staStationsBuf, ACC_LNG + 1, "%d", staStations);
}
void updateCountStations() {
snprintf(staStationsBuf, ACC_LNG + 1, "%d/%d", staCurrStation, staStations);
}
void updateStationLevel() {
snprintf(staLevelBuf, ADDR_LNG + 1, "%d", staLevel);
}
void updateStationStars() {
snprintf(staStarsBuf, ADDR_LNG + 1, "%d", staStars);
}
void setNewTarget() {
uint16_t pos;
staLastStation = newStation(staLastStation);
iconData[ICON_STA_TARGET].color = staColors[staLastStation];
pos = iconData[ICON_STA_TRAIN].x;
iconData[ICON_STA_TRAIN].x = iconData[ICON_STA_TARGET].x;
iconData[ICON_STA_TARGET].x = pos;
iconData[ICON_STA_PIN].x = pos + 8;
}
void clickTargetStation() {
encoderValue = 0;
locoData[myLocoData].mySpeed = 0;
locoOperationSpeed();
updateSpeedDir();
staCurrStation++;
if (staCurrStation == staStations) {
stopTimer(TMR_STA_RUN);
staLevel++;
updateStationLevel();
newStationCounters(false);
updateTargetStations();
updateStationTime(staTime);
closeWindow(WIN_STA_PLAY);
openWindow(WIN_STA_STARS); // well done!
}
else {
updateCountStations();
setNewTarget();
newEvent(OBJ_WIN, WIN_STA_PLAY, EVNT_DRAW);
}
}
uint16_t staGetTurnoutAdr(uint16_t eeAdr, uint16_t defAdr) {
uint16_t adr;
adr = (EEPROM.read(eeAdr) << 8) + EEPROM.read(eeAdr + 1);
if (adr > 2048)
adr = defAdr;
return adr;
}
void updateTurnoutButtons() {
uint16_t n, icon;
for (n = 0; n < 4; n++) {
if (staTurnoutPos[n])
fncData[FNC_STA_ACC0 + n].idIcon = bitRead(staTurnoutDef, n) ? FNC_TURNRD_OFF : FNC_TURNLD_OFF;
else
fncData[FNC_STA_ACC0 + n].idIcon = bitRead(staTurnoutDef, n) ? FNC_TURNRS_OFF : FNC_TURNLS_OFF;
fncData[FNC_STA_ACC0 + n].colorOn = staTurnoutPos[n] ? COLOR_RED : COLOR_GREEN;
}
}

View File

@@ -0,0 +1,399 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** STEAM THROTTLE *****
////////////////////////////////////////////////////////////
void initSteamThrottle () {
uint16_t n;
if (oldSteamLoco != locoData[myLocoData].myAddr.address) {
oldSteamLoco = locoData[myLocoData].myAddr.address;
steamPressure = 80;
waterLevelBoiler = 80;
waterLevelTender = 350;
oldPressure = 0;
oldLevelBoiler = 0;
oldLevelTender = 0;
barData[BAR_JOHNSON].value = STEAM_JOHNSON_NEUTRAL; // neutral position
barData[BAR_BRAKE].value = 0;
steamDir = locoData[myLocoData].myDir;
for (n = 0; n < MAX_LIMIT; n++)
steamSpeedLimit[n] = LIMIT_NONE;
}
currentSteamTime = millis();
steamTimeSpeed = currentSteamTime;
steamTimeSteam = currentSteamTime;
steamTimeWater = currentSteamTime;
steamTimeLoad = currentSteamTime;
steamTimeSmoke = currentSteamTime;
shovelCoal = false;
setFirebox();
endWaterInjection();
endTenderFill();
setTimer(TMR_STEAM, 5, TMR_ONESHOT);
changeSmoke = STEAM_SMOKE_FAST;
oldPressure = 0;
oldSpeedSteam = 305;
encoderMax = 31; // set throttle to current speed
updateSteamThrottle();
if (steamDir != locoData[myLocoData].myDir) { // check Johnson bar position
steamDir = locoData[myLocoData].myDir;
barData[BAR_JOHNSON].value = 6 - barData[BAR_JOHNSON].value;
}
}
void updateSteamThrottle() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
case CLIENT_XNET:
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 0..127
currentSteamSpeed = locoData[myLocoData].mySpeed;
encoderValue = currentSteamSpeed >> 2;
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 0..31
encoderValue = (locoData[myLocoData].mySpeed & 0x0F) << 1;
if (bitRead(locoData[myLocoData].mySpeed, 4))
bitSet(encoderValue, 0);
currentSteamSpeed = (encoderValue > 3) ? (encoderValue << 2) + (encoderValue >> 3) : 0;
}
else { // 0..15
encoderValue = locoData[myLocoData].mySpeed & 0x0F;
encoderValue = (encoderValue > 1) ? encoderValue << 1 : 0;
currentSteamSpeed = (encoderValue << 2) + (encoderValue >> 3);
}
}
break;
case CLIENT_LNET:
case CLIENT_ECOS:
currentSteamSpeed = locoData[myLocoData].mySpeed;
encoderValue = currentSteamSpeed >> 2;
break;
}
}
void steamProcess() {
uint16_t value, newSpeed, mappedThrottle, jFactor;
steamSpeedLimit[LIMIT_THROTTLE] = (encoderValue << 2) + (encoderValue >> 3); // Read Throtle Speed (0..31 -> 0..127)
steamSpeedLimit[LIMIT_JOHNSON] = LIMIT_NONE;
switch (barData[BAR_JOHNSON].value) { // Read Johnson Bar
case 0: // full reverse
jFactor = 3;
changeSpeed = 80;
steamDir = 0x00;
break;
case 1:
jFactor = 2;
changeSpeed = 200;
steamDir = 0x00;
break;
case 2:
jFactor = 1;
changeSpeed = 500;
steamDir = 0x00;
break;
case STEAM_JOHNSON_NEUTRAL: // Neutral position of Johnson Bar
jFactor = 1;
changeSpeed = 1000;
steamSpeedLimit[LIMIT_JOHNSON] = 0;
break;
case 4:
jFactor = 1;
changeSpeed = 500;
steamDir = 0x80;
break;
case 5:
jFactor = 2;
changeSpeed = 200;
steamDir = 0x80;
break;
case 6: // full forward
jFactor = 3;
changeSpeed = 80;
steamDir = 0x80;
break;
}
if (steamDir != locoData[myLocoData].myDir) { // Check direction
locoData[myLocoData].myDir = steamDir;
changeDirection();
DEBUG_MSG("STEAM: Change direction")
}
value = steamSpeedLimit[LIMIT_THROTTLE];
changeSteam = 10000 - ((value * 9) * jFactor); // Steam timeout: 6571 to 10000
changeWater = 14000 - ((value * 27) * jFactor); // Water timeout: 3713 to 14000
if (barData[BAR_BRAKE].value > 0) { // Brake bar: 300, 150, 100
changeSpeed = 300 / barData[BAR_BRAKE].value;
}
currentSteamTime = millis();
if (currentSteamTime > (steamTimeWater + changeWater)) { // Water consumption
steamTimeWater = currentSteamTime;
if (waterLevelBoiler > 0) {
waterLevelBoiler--;
DEBUG_MSG("Boiler Level: %d", waterLevelBoiler)
}
}
if (waterLevelBoiler < 10) { // Stop loco if not enough water
steamSpeedLimit[LIMIT_WATER] = 0;
steamThrottleStop();
}
else {
steamSpeedLimit[LIMIT_WATER] = LIMIT_NONE;
}
if (currentSteamTime > (steamTimeSteam + changeSteam)) { // Steam consumption
steamTimeSteam = currentSteamTime;
if (steamPressure > 0)
steamPressure--;
}
if (steamPressure < 50) { // Limit speed based on steam level
value = (steamPressure < 20) ? 0 : map(steamPressure, 20, 50, 20, 120);
steamSpeedLimit[LIMIT_PRESSURE] = value;
}
else {
steamSpeedLimit[LIMIT_PRESSURE] = LIMIT_NONE;
}
if (currentSteamTime > (steamTimeLoad + STEAM_LOAD_TIME)) { // Load coal and water
steamTimeLoad = currentSteamTime;
if (shovelCoal) { // Fire open for shoveling coal
if (steamPressure > 96) {
shovelCoal = false;
setFirebox();
newEvent(OBJ_FNC, FNC_ST_FIRE, EVNT_DRAW);
}
else {
if (steamPressure < 20) // slowly pressure up at beginning
steamPressure += 1;
else
steamPressure += 2;
}
}
if (waterInjection) { // Water injector open
if (waterLevelTender > 0) { // Inject water with water from tender
if (waterLevelBoiler > 95) {
endWaterInjection();
}
else {
waterLevelBoiler += 2;
waterLevelTender--;
}
steamSpeedLimit[LIMIT_TENDER] = LIMIT_NONE;
}
else {
endWaterInjection(); // Stop locomotive if tender empty
steamSpeedLimit[LIMIT_TENDER] = 0;
steamThrottleStop();
}
}
if (fillTender) {
if ((waterLevelTender > 495) || (currentSteamSpeed != 0)) { // Only fill tender when stopped
endTenderFill();
}
else {
waterLevelTender++;
if (waterLevelTender > 6) // Minimum level to run again
steamSpeedLimit[LIMIT_TENDER] = LIMIT_NONE;
}
}
}
if (currentSteamTime > (steamTimeSmoke + changeSmoke)) { // Chimney smoke
steamTimeSmoke = currentSteamTime;
if (currentSteamSpeed > 0) {
fncData[FNC_ST_SMOKE].state = !fncData[FNC_ST_SMOKE].state;
changeSmoke = map(currentSteamSpeed, 0, 127, STEAM_SMOKE_SLOW, STEAM_SMOKE_FAST);
}
else {
fncData[FNC_ST_SMOKE].state = false;
changeSmoke = STEAM_SMOKE_SLOW + STEAM_SMOKE_SLOW;
}
newEvent(OBJ_FNC, FNC_ST_SMOKE, EVNT_DRAW);
}
if (barData[BAR_BRAKE].value > 0) { // Braking
value = barData[BAR_BRAKE].value * 8;
value = (currentSteamSpeed > value) ? (currentSteamSpeed - value) : 0;
steamSpeedLimit[LIMIT_BRAKE] = value;
}
else {
steamSpeedLimit[LIMIT_BRAKE] = LIMIT_NONE;
}
targetSpeedSteam = LIMIT_NONE; // Find lower limit
for (value = 0; value < MAX_LIMIT; value++) {
if (steamSpeedLimit[value] < targetSpeedSteam)
targetSpeedSteam = steamSpeedLimit[value];
}
newSpeed = currentSteamSpeed;
if (currentSteamTime > (steamTimeSpeed + changeSpeed)) { // Speed acceleration
steamTimeSpeed = currentSteamTime;
//DEBUG_MSG("Target: %d Current: %d New: %d", targetSpeedSteam, currentSteamSpeed, newSpeed)
if (targetSpeedSteam > currentSteamSpeed) {
newSpeed = currentSteamSpeed + 1;
DEBUG_MSG("Inc New: %d", newSpeed)
}
if (targetSpeedSteam < currentSteamSpeed) {
newSpeed = currentSteamSpeed - 1;
DEBUG_MSG("Dec New: %d", newSpeed)
}
//DEBUG_MSG("New acc: %d", newSpeed)
}
if (currentSteamSpeed != newSpeed) { // changes in speed
DEBUG_MSG("Step: %d - New: %d LIMITS ", currentSteamSpeed, newSpeed)
#ifdef DEBUG
for (value = 0; value < MAX_LIMIT; value++) {
Serial.print(steamSpeedLimit[value]);
Serial.print(" ");
}
Serial.println();
#endif
currentSteamSpeed = newSpeed;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
case CLIENT_XNET:
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 128 steps
mappedThrottle = (currentSteamSpeed > 1) ? currentSteamSpeed : 0;
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 28 steps
if (currentSteamSpeed > 15) {
mappedThrottle = currentSteamSpeed >> 3;
bitWrite(mappedThrottle, 4, bitRead(currentSteamSpeed, 2));
}
else {
mappedThrottle = 0;
}
}
else { // 14 steps
mappedThrottle = (currentSteamSpeed > 15) ? currentSteamSpeed >> 3 : 0;
}
}
break;
case CLIENT_LNET:
case CLIENT_ECOS:
mappedThrottle = (currentSteamSpeed > 1) ? currentSteamSpeed : 0;
break;
}
locoData[myLocoData].mySpeed = mappedThrottle;
locoOperationSpeed();
DEBUG_MSG("Steam step: %d", currentSteamSpeed)
if ((currentSteamSpeed > 0) && (changeSmoke > STEAM_SMOKE_SLOW)) { // initial chuff
changeSmoke = 0;
fncData[FNC_ST_SMOKE].state = false;
}
}
if ((oldLevelTender / 10) != (waterLevelTender / 10)) {
oldLevelTender = waterLevelTender;
barData[BAR_TENDER].value = waterLevelTender;
newEvent(OBJ_BAR, BAR_TENDER, EVNT_DRAW);
}
value = waterLevelBoiler / 2;
if (oldLevelBoiler != value ) {
oldLevelBoiler = value;
barData[BAR_WATER].value = value;
newEvent(OBJ_BAR, BAR_WATER, EVNT_DRAW);
}
value = steamPressure * 270 / 100;
if (oldPressure != value) {
showPressure(value);
DEBUG_MSG("Pressure: %d", steamPressure)
}
}
void startWaterInjection () {
waterInjection = true;
fncData[FNC_ST_WATER].colorOn = COLOR_DARKGREEN;
drawObject(OBJ_FNC, FNC_ST_WATER);
}
void endWaterInjection () {
waterInjection = false;
fncData[FNC_ST_WATER].colorOn = COLOR_RED;
drawObject(OBJ_FNC, FNC_ST_WATER);
}
void startTenderFill() {
fillTender = true;
//fncData[FNC_ST_TENDER].color = COLOR_RED;
fncData[FNC_ST_TENDER].colorOn = COLOR_DARKGREEN;
drawObject(OBJ_FNC, FNC_ST_TENDER);
}
void endTenderFill() {
fillTender = false;
//fncData[FNC_ST_TENDER].color = COLOR_WHITE;
fncData[FNC_ST_TENDER].colorOn = COLOR_RED;
drawObject(OBJ_FNC, FNC_ST_TENDER);
}
void setFirebox() {
if (shovelCoal) {
fncData[FNC_ST_FIRE].idIcon = FNC_FIRE_OP_OFF;
fncData[FNC_ST_FIRE].color = COLOR_ORANGE;
fncData[FNC_ST_FIRE].colorOn = COLOR_YELLOW;
}
else {
fncData[FNC_ST_FIRE].idIcon = FNC_FIRE_CL_OFF;
fncData[FNC_ST_FIRE].color = COLOR_SILVER;
fncData[FNC_ST_FIRE].colorOn = COLOR_RED;
}
}
void steamThrottleStop() { // set controls for stop
if (encoderValue > 0) {
encoderValue = 0;
showSpeedSteam(240);
}
if (barData[BAR_JOHNSON].value != STEAM_JOHNSON_NEUTRAL) {
barData[BAR_JOHNSON].value = STEAM_JOHNSON_NEUTRAL; // Neutral
drawObject(OBJ_BAR, BAR_JOHNSON);
}
}
void showPressure(uint16_t angle) {
tft.setPivot(140, 105); // Set pivot to center of manometer in TFT screen
sprite.setColorDepth(8); // Create an 8bpp Sprite
sprite.createSprite(19, 19); // 8bpp requires 19 * 19 = 361 bytes
sprite.setPivot(9, 9); // Set pivot relative to top left corner of Sprite
sprite.fillSprite(COLOR_WHITE); // Fill the Sprite with background
sprite.pushRotated(oldPressure);
oldPressure = angle;
sprite.drawBitmap(0, 0, needle_bar, 19, 19, COLOR_BLUE);
sprite.pushRotated(angle);
sprite.deleteSprite();
}
void showSpeedSteam(uint16_t angle) {
tft.setPivot(120, 170); // Set pivot to center of bar in TFT screen
sprite.setColorDepth(8); // Create an 8bpp Sprite
sprite.createSprite(83, 15); // 8bpp requires 83 * 15 = 1245 bytes
sprite.setPivot(76, 7); // Set pivot relative to top left corner of Sprite
sprite.fillSprite(COLOR_BLACK); // Fill the Sprite with background
sprite.pushRotated(oldSpeedSteam);
oldSpeedSteam = angle;
sprite.drawBitmap(0, 0, speed_steam, 83, 15, COLOR_RED);
sprite.pushRotated(angle);
tft.drawArc(120, 170, 27, 22, 315, 45, COLOR_RED, COLOR_BLACK, false);
sprite.deleteSprite();
}

View File

@@ -0,0 +1,842 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** SYSTEM SUPPORT *****
////////////////////////////////////////////////////////////
void initPins() {
// Set all chip selects high to avoid bus contention during initialisation of each peripheral
digitalWrite(TFT_CS, HIGH); // TFT screen chip select
digitalWrite(SD_CS, HIGH); // SD card chips select
digitalWrite(XPT2046_CS, HIGH); // Touch screen chips select
pinMode (SW_BOOT, INPUT); // Button BOOT
pinMode (ENCODER_A, INPUT); // Encoder
pinMode (ENCODER_B, INPUT);
pinMode (ENCODER_SW, INPUT);
#if (USE_RGB_LED == PRESENT)
pinMode(RGB_LED_R, OUTPUT); // RGB LED
pinMode(RGB_LED_G, OUTPUT);
pinMode(RGB_LED_B, OUTPUT);
setColorRGB(0); // turn off RGB LED
#endif
}
void setBacklight (uint8_t value) { // set PWM backlight
#if (ESP_ARDUINO_VERSION_MAJOR > 2)
// Code for version 3.x
ledcWrite(TFT_BL, value);
#else
// Code for version 2.x
ledcWrite(LEDC_CHANNEL_0, value);
#endif
currBacklight = value;
}
void setRotationDisplay(uint8_t pos) { // Rotate display and touchscreen
tft.setRotation(pos);
touchscreen.setRotation((pos + XPT_ROTATION) & 0x03);
}
void aliveAndKicking() {
setTimer (TMR_BLIGHT, INACT_TIME, TMR_ONESHOT); // reset timeout and restore backlight
if (currBacklight != backlight)
setBacklight(backlight);
}
#if (USE_RGB_LED == PRESENT)
void setColorRGB (uint16_t color) { // set color of RGB LED
int state;
state = (color & 0x04) ? LOW : HIGH;
digitalWrite(RGB_LED_G, state);
state = (color & 0x02) ? LOW : HIGH;
digitalWrite(RGB_LED_R, state);
state = (color & 0x01) ? LOW : HIGH;
digitalWrite(RGB_LED_B, state);
DEBUG_MSG("Color: %d", color & 0x07)
}
#endif
initResult initSequence() { // Performs init sequence
char label[MAX_LABEL_LNG];
char chr;
int n;
initResult result;
result = INIT_OK;
delay(500);
drawObject(OBJ_ICON, ICON_SDCARD); // detecting SD card
if (sdDetected) {
sprintf (FileName, "/image/logo.bmp");
if (tft.width() == 240)
drawBmp (FileName, 0, 180);
else
drawBmp (FileName, 40, 260);
loadLocoFiles(SD, "/loco"); // load loco data & panel names from SD file
loadAccPanelNames(SD);
}
else {
if (LittleFS.begin(false)) {
loadLocoFiles(LittleFS, "/loco"); // load loco data & panel names from FS file
loadAccPanelNames(LittleFS);
}
else {
DEBUG_MSG("LittleFS Mount Failed. Formating....");
LittleFS.format();
}
drawObject(OBJ_ICON, ICON_NO_SD);
result = INIT_NO_SD;
DEBUG_MSG("Total: %ul Used: %ul", LittleFS.totalBytes(), LittleFS.usedBytes())
}
populateAccPanel(); // load first accessory panel
barData[BAR_INIT].value = 10;
drawObject(OBJ_BAR, BAR_INIT);
drawObject(OBJ_ICON, ICON_WIFI); // connecting to WiFi network
drawObject(OBJ_DRAWSTR, DSTR_INIT_STAT);
drawObject(OBJ_LABEL, LBL_CONNECT);
WiFi.mode(WIFI_STA);
WiFi.begin(wifiSetting.ssid, wifiSetting.password);
n = 0;
while ((WiFi.status() != WL_CONNECTED) && n < 80) { // tries to connect to router in 20 seconds
n += 2;
barData[BAR_INIT].value = 10 + n;
drawObject(OBJ_BAR, BAR_INIT);
delay(500);
DEBUG_MSG(".");
}
barData[BAR_INIT].value = 90;
drawObject(OBJ_BAR, BAR_INIT);
if (WiFi.status() == WL_CONNECTED) { // Connect to server with current protocol
drawObject(OBJ_DRAWSTR, DSTR_INIT_STAT); // show Protocol
getLabelTxt(LBL_SEL_Z21 + wifiSetting.protocol, label);
tft.drawString(label, 20, 120, GFXFF);
DEBUG_MSG("Channel: %d", WiFi.channel());
DEBUG_MSG("IP address: %u.%u.%u.%u", WiFi.localIP().operator[](0), WiFi.localIP().operator[](1), WiFi.localIP().operator[](2), WiFi.localIP().operator[](3));
DEBUG_MSG("%s", WiFi.macAddress().c_str())
useID = false;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
WiFi.setSleep(false);
Udp.begin(z21Port);
//wifiSetting.port = z21Port;
DEBUG_MSG("Now listening UDP port %d", z21Port);
getStatusZ21(); // every x seconds
getSerialNumber();
delay(500);
setBroadcastFlags (0x00000013); // Broadcasts and info messages concerning driving and switching, report changes on feedback bus & fast clock
getStatusZ21();
//askZ21begin (LAN_GET_BROADCASTFLAGS);
//sendUDP (0x04);
break;
case CLIENT_LNET:
if (!Client.connect(wifiSetting.CS_IP, wifiSetting.port)) {
DEBUG_MSG("Connection to Loconet over TCP/IP failed");
result = INIT_NO_CONNECT;
}
else {
Client.setNoDelay(true);
rcvStrPhase = WAIT_TOKEN;
getTypeCS();
}
break;
case CLIENT_XNET:
if (!Client.connect(wifiSetting.CS_IP, XnetPort)) {
DEBUG_MSG("Connection to Xpressnet failed");
result = INIT_NO_CONNECT;
}
else {
wifiSetting.port = XnetPort;
Client.setNoDelay(true);
rxIndice = FRAME1;
getVersionXnet(); // pide la version del Xpressnet
getStatusXnet(); // pide estado de la central
}
break;
case CLIENT_ECOS:
useID = true;
if (!Client.connect(wifiSetting.CS_IP, ECoSPort)) {
DEBUG_MSG("Connection to ECoS failed");
result = INIT_NO_CONNECT;
}
else {
wifiSetting.port = ECoSPort;
Client.setNoDelay(true);
requestViews();
requestLocoList();
waitWifiData(500);
}
break;
}
}
else {
drawObject(OBJ_ICON, ICON_NO_WIFI);
result = INIT_NO_WIFI;
}
barData[BAR_INIT].value = 95;
drawObject(OBJ_BAR, BAR_INIT);
drawObject(OBJ_ICON, ICON_INIT_LOCO); // fill image list
initImageList();
barData[BAR_INIT].value = 100;
drawObject(OBJ_BAR, BAR_INIT);
setTimer (TMR_END_LOGO, 7, TMR_ONESHOT); // Wait for answer
return result;
}
bool notLocked () { // check if not locked
if (lockOptions & ((1 << LOCK_SEL_LOCO) | (1 << LOCK_TURNOUT) | (1 << LOCK_PROG)))
return false;
else
return true;
}
bool notLockedOption (byte opt) { // check if option not locked
if (lockOptions & (1 << opt))
return false;
else
return true;
}
////////////////////////////////////////////////////////////
// ***** TOUCHSCREEN *****
////////////////////////////////////////////////////////////
void calibrateTouchscreen(uint16_t colorIn, uint16_t colorOut, uint16_t bg) {
uint16_t TS_TOP, TS_BOT, TS_LEFT, TS_RT;
uint16_t x, y, z;
TSPoint p;
TS_TOP = 4095;
TS_BOT = 0;
TS_LEFT = 4095;
TS_RT = 0;
tft.fillScreen(bg);
for (int i = 0; i < 4; i++) {
tft.fillCircle(0, 0, 15, bg); // delete touch corners points
tft.fillCircle(tft.width(), 0, 15, bg);
tft.fillCircle(0, tft.height(), 15, bg);
tft.fillCircle(tft.width(), tft.height(), 15, bg);
DEBUG_MSG("Calibrate step: %d", i)
switch (i) { // show current touch corner point
case 0:
tft.fillCircle(0, 0, 15, colorOut);
tft.fillCircle(0, 0, 7, colorIn);
break;
case 1:
tft.fillCircle(tft.width(), 0, 15, colorOut);
tft.fillCircle(tft.width(), 0, 7, colorIn);
break;
case 2:
tft.fillCircle(0, tft.height(), 15, colorOut);
tft.fillCircle(0, tft.height(), 7, colorIn);
break;
case 3:
tft.fillCircle(tft.width(), tft.height(), 15, colorOut);
tft.fillCircle(tft.width(), tft.height(), 7, colorIn);
break;
}
while (touchscreen.touched()) // wait to release
delay(0);
DEBUG_MSG("Pen released")
while (!touchscreen.touched()) // wait to touch
delay(0);
DEBUG_MSG("Pen touched")
touchscreen.readData(&x, &y, &z);
if (x < TS_LEFT) {
TS_LEFT = x;
}
if (y < TS_TOP) {
TS_TOP = y;
}
if (x > TS_RT) {
TS_RT = x;
}
if (y > TS_BOT) {
TS_BOT = y;
}
}
tft.fillCircle(tft.width(), tft.height(), 15, bg); // delete last touch corner point
touchscreen.setCalibration(TS_LEFT, TS_RT, TS_TOP, TS_BOT);
DEBUG_MSG("xMin: %d, xMax: %d, yMin: %d, yMax: %d", TS_LEFT, TS_RT, TS_TOP, TS_BOT);
}
void showClockData(uint16_t txtFocus) {
uint16_t n;
for (n = 0; n < 3; n++)
txtData[TXT_HOUR + n].backgnd = COLOR_BACKGROUND;
txtData[txtFocus].backgnd = COLOR_YELLOW; // select focus on selected field
keybData[KEYB_CLOCK].idTextbox = txtFocus;
}
////////////////////////////////////////////////////////////
// ***** WIFI *****
////////////////////////////////////////////////////////////
void scanWiFi() {
networks = 0;
while (networks == 0) {
WiFi.disconnect(true); //DISCONNECT WITH TRUE (SHUOLD TURN OFF THE RADIO)
delay(1000);
WiFi.mode(WIFI_STA); //CALLING THE WIFI MODE AS STATION
WiFi.scanDelete();
networks = WiFi.scanNetworks();
DEBUG_MSG("Networks: %d", networks);
if ((networks > 0) && (networks < 32768)) {
encoderMax = networks - 1;
encoderValue = 0;
scrSSID = 0;
}
else
networks = 0;
}
}
void scanWiFiFill() {
uint16_t n, line;
n = (scrSSID > 5) ? scrSSID - 5 : 0;
snprintf (ssidName1, SSID_LNG, WiFi.SSID(n).c_str());
snprintf (ssidName2, SSID_LNG, WiFi.SSID(n + 1).c_str());
snprintf (ssidName3, SSID_LNG, WiFi.SSID(n + 2).c_str());
snprintf (ssidName4, SSID_LNG, WiFi.SSID(n + 3).c_str());
snprintf (ssidName5, SSID_LNG, WiFi.SSID(n + 4).c_str());
snprintf (ssidName6, SSID_LNG, WiFi.SSID(n + 5).c_str());
line = (scrSSID > 5) ? 5 : scrSSID;
for (n = 0; n < 6; n++) {
txtData[TXT_SSID1 + n].backgnd = (n == line) ? COLOR_BLUE : COLOR_BLACK;
}
}
void wifiAnalyzer() {
int16_t n, i;
char txt[10];
for (n = 0; n < 14; n++) {
ap_count[n] = 0;
max_rssi[n] = RSSI_FLOOR;
}
n = WiFi.scanNetworks();
drawObject(OBJ_DRAWSTR, DSTR_WIFI_SCAN);
drawObject(OBJ_LABEL, LBL_SSID_SCAN);
drawObject(OBJ_FNC, FNC_SCAN_RESET);
tft.setFreeFont(FSSB6);
if ((n > 0) && (n < 32768)) {
for (i = 0; i < n; i++) {
int32_t channel = WiFi.channel(i);
int32_t rssi = WiFi.RSSI(i);
uint16_t color = channel_color[channel - 1];
int height = constrain(map(rssi, RSSI_FLOOR, RSSI_CEILING, 1, GRAPH_HEIGHT), 1, GRAPH_HEIGHT);
// channel stat
ap_count[channel - 1]++;
if (rssi > max_rssi[channel - 1]) {
max_rssi[channel - 1] = rssi;
}
tft.drawLine((channel * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE - height, ((channel - 1) * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE + 1, color);
tft.drawLine((channel * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE - height, ((channel + 1) * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE + 1, color);
// Print SSID, signal strengh and if not encrypted
tft.setTextColor(color);
tft.setTextDatum(MC_DATUM);
tft.drawString(WiFi.SSID(i), (channel * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE - 10 - height, GFXFF);
// rest for WiFi routine?
delay(10);
}
}
else {
tft.setFreeFont(FSSB9);
tft.setTextColor(COLOR_WHITE);
tft.drawString("SSID = 0", 120 + GRAPH_OFFSET, 120, GFXFF);
}
tft.setFreeFont(FSSB6);
tft.setTextDatum(TC_DATUM);
for (i = 1; i < 15; i++) {
tft.setTextColor(channel_color[i - 1]);
snprintf(txt, 10, "%d", i);
tft.drawString(txt, (i * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE + 12, GFXFF);
if (ap_count[i - 1] > 0) {
snprintf(txt, 10, "(%d)", ap_count[i - 1]);
tft.drawString(txt, (i * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE + 24, GFXFF);
}
}
setTimer(TMR_SCAN, 50, TMR_ONESHOT);
}
void wifiProcess() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
processZ21();
break;
case CLIENT_XNET:
processXnet();
break;
case CLIENT_LNET:
processLnet();
break;
case CLIENT_ECOS:
ECoSProcess();
break;
}
}
void waitWifiData(uint32_t ms) {
uint32_t now;
now = millis();
while ((millis() - now) < ms)
wifiProcess();
}
void setProtocolData() {
uint16_t n;
useID = false;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
snprintf(keybProtoBuf, PWD_LNG, "Z21");
snprintf(keybPortBuf, 6, "%d", z21Port);
break;
case CLIENT_LNET:
if (wifiSetting.serverType)
snprintf(keybProtoBuf, PWD_LNG, "LBServer");
else
snprintf(keybProtoBuf, PWD_LNG, "Loconet Binary");
snprintf(keybPortBuf, 6, "%d", wifiSetting.port);
break;
case CLIENT_XNET:
snprintf(keybProtoBuf, PWD_LNG, "Xpressnet LAN");
snprintf(keybPortBuf, 6, "%d", XnetPort);
break;
case CLIENT_ECOS:
useID = true;
snprintf(keybProtoBuf, PWD_LNG, "ECoS");
snprintf(keybPortBuf, 6, "%d", ECoSPort);
break;
}
for (n = 0; n < 5; n++)
txtData[TXT_IP1 + n].backgnd = COLOR_BACKGROUND;
txtData[TXT_IP1].backgnd = COLOR_YELLOW; // select focus on first IP byte
keybData[KEYB_IP].idTextbox = TXT_IP1;
}
void setOptionsData() {
switchData[SW_OPT_ADR].state = false; // show disable all as default
switchData[SW_OPT_ADR].colorKnob = COLOR_BACKGROUND;
radioData[RAD_CSTATION].value = radioData[RAD_CSTATION].num;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
switchData[SW_OPT_ADR].colorKnob = COLOR_WHITE;
switchData[SW_OPT_ADR].state = (shortAddress == 99) ? true : false;
break;
case CLIENT_XNET:
break;
case CLIENT_LNET:
radioData[RAD_CSTATION].value = typeCmdStation;
break;
case CLIENT_ECOS:
break;
}
}
////////////////////////////////////////////////////////////
// ***** PROTOCOL COMMON *****
////////////////////////////////////////////////////////////
void infoLocomotora (unsigned int loco) {
DEBUG_MSG("Info Loco % d", loco)
switch (wifiSetting.protocol) {
case CLIENT_Z21:
infoLocomotoraZ21 (loco);
break;
case CLIENT_XNET:
infoLocomotoraXnet (loco);
break;
case CLIENT_LNET:
infoLocomotoraLnet (loco);
break;
case CLIENT_ECOS:
infoLocomotoraECoS (loco); // ID
break;
}
}
void locoOperationSpeed() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
locoOperationSpeedZ21();
break;
case CLIENT_XNET:
locoOperationSpeedXnet();
break;
case CLIENT_LNET:
locoOperationSpeedLnet();
break;
case CLIENT_ECOS:
locoOperationSpeedECoS();
break;
}
}
void changeDirection() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
locoOperationSpeedZ21();
break;
case CLIENT_XNET:
locoOperationSpeedXnet();
break;
case CLIENT_LNET:
changeDirectionF0F4Lnet();
break;
case CLIENT_ECOS:
changeDirectionECoS();
break;
}
}
void funcOperations (uint8_t fnc) {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
funcOperationsZ21 (fnc);
break;
case CLIENT_XNET:
funcOperationsXnet (fnc);
break;
case CLIENT_LNET:
funcOperationsLnet (fnc);
break;
case CLIENT_ECOS:
funcOperationsECoS(fnc);
break;
}
}
byte getCurrentStep() {
byte value;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
value = getCurrentStepZ21();
break;
case CLIENT_XNET:
value = getCurrentStepXnet();
break;
case CLIENT_LNET:
value = getCurrentStepLnet();
break;
case CLIENT_ECOS:
value = getCurrentStepECoS();
break;
}
return value;
}
void releaseLoco() {
switch (wifiSetting.protocol) {
case CLIENT_LNET:
doDispatchGet = false;
doDispatchPut = false;
liberaSlot(); // pasa slot actual a COMMON
break;
case CLIENT_ECOS:
releaseLocoECoS();
break;
}
}
void sendAccessory(unsigned int FAdr, int pair, bool activate) {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
setAccessoryZ21(FAdr, pair, activate);
break;
case CLIENT_XNET:
setAccessoryXnet(FAdr, activate, pair);
break;
case CLIENT_LNET:
lnetRequestSwitch (FAdr, activate, pair);
break;
case CLIENT_ECOS:
setAccessoryECoS(FAdr, pair, activate);
break;
}
}
void resumeOperations() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
resumeOperationsZ21();
break;
case CLIENT_XNET:
resumeOperationsXnet();
break;
case CLIENT_LNET:
resumeOperationsLnet();
break;
case CLIENT_ECOS:
resumeOperationsECoS();
break;
}
}
void emergencyOff() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
emergencyOffZ21();
break;
case CLIENT_XNET:
emergencyOffXnet();
break;
case CLIENT_LNET:
emergencyOffLnet();
break;
case CLIENT_ECOS:
emergencyOffECoS();
break;
}
}
void togglePower() {
if (isTrackOff())
resumeOperations(); // Track Power On
else
emergencyOff(); // Track Power Off
}
bool isTrackOff() {
bool state;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
state = (csStatus & csTrackVoltageOff) ? true : false;
break;
case CLIENT_XNET:
state = (csStatus & csEmergencyOff) ? true : false;
break;
case CLIENT_LNET:
state = (bitRead(mySlot.trk, 0)) ? false : true;
break;
case CLIENT_ECOS:
state = (csStatus > 0) ? false : true;
break;
}
return state;
}
void getStatusCS() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
getStatusZ21();
break;
case CLIENT_XNET:
getStatusXnet();
break;
case CLIENT_LNET:
getTypeCS(); // workaround, not defined for Lnet
break;
case CLIENT_ECOS:
getStatusECoS();
break;
}
}
void setTime(byte hh, byte mm, byte rate) {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
setTimeZ21(hh, mm, rate);
break;
case CLIENT_XNET:
setTimeXnet(hh, mm, rate);
break;
case CLIENT_LNET:
setTimeLnet(hh, mm, rate);
break;
// ECoS not supported
}
}
void readCV (unsigned int adr, byte stepPrg) {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
readCVZ21(adr, stepPrg);
break;
case CLIENT_XNET:
readCVXnet(adr, stepPrg);
break;
case CLIENT_LNET:
readCVLnet(adr, stepPrg);
break;
case CLIENT_ECOS:
readCVECoS(adr, stepPrg);
break;
}
}
void writeCV (unsigned int adr, unsigned int data, byte stepPrg) {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
writeCVZ21(adr, data, stepPrg);
break;
case CLIENT_XNET:
writeCVXnet(adr, data, stepPrg);
break;
case CLIENT_LNET:
writeCVLnet(adr, data, stepPrg);
break;
case CLIENT_ECOS:
writeCVECoS(adr, data, stepPrg);
break;
}
}
void exitProgramming() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
if (csStatus & csProgrammingModeActive)
resumeOperationsZ21();
break;
case CLIENT_XNET:
if (csStatus & csServiceMode)
resumeOperationsXnet();
break;
case CLIENT_ECOS:
exitProgrammingECoS();
break;
}
}
////////////////////////////////////////////////////////////
// ***** CV PROGRAMMING *****
////////////////////////////////////////////////////////////
void endProg() { // Fin de programcion/lectura CV
DEBUG_MSG("END PROG: CVData - % d Step: % d", CVdata, progStepCV);
if (CVdata > 255) {
if (progStepCV == PRG_RD_CV29) // Si buscaba direccion, muestra CV1 en lugar de CV29
CVaddress = 1;
showDataCV();
}
else {
switch (progStepCV) {
case PRG_CV:
showDataCV();
break;
case PRG_RD_CV29:
cv29 = (byte) CVdata;
if (bitRead(cv29, 5)) {
CVaddress = 17; // Long address
readCV(CVaddress, PRG_RD_CV17);
}
else {
CVaddress = 1; // Short address
readCV(CVaddress, PRG_RD_CV1);
}
break;
case PRG_RD_CV1:
decoAddress = CVdata;
showDirCV();
break;
case PRG_RD_CV17:
cv17 = (byte) CVdata;
CVaddress = 18;
readCV(CVaddress, PRG_RD_CV18);
break;
case PRG_RD_CV18:
cv18 = (byte) CVdata;
decoAddress = ((cv17 & 0x3F) << 8) | cv18;
showDirCV();
break;
case PRG_WR_CV17: // Long address
CVaddress = 18;
writeCV(CVaddress, lowByte(decoAddress), PRG_WR_CV18);
break;
case PRG_WR_CV18:
bitSet(cv29, 5);
CVaddress = 29;
writeCV(CVaddress, cv29, PRG_WR_CV29);
break;
case PRG_WR_CV1: // short address
bitClear(cv29, 5);
CVaddress = 29;
writeCV(CVaddress, cv29, PRG_WR_CV29);
break;
case PRG_WR_CV29:
showDirCV();
break;
}
}
}
void showDataCV() { // muestra valor de la CV
progStepCV = PRG_IDLE;
enterCVdata = (CVdata > 255) ? false : true;
setStatusCV(); // show error / manufacturer / CV / pom
setFieldsCV();
setBitsCV();
if (isWindow(WIN_ALERT))
closeWindow(WIN_ALERT);
if (isWindow(WIN_PROG_CV)) {
showFieldsCV();
newEvent(OBJ_TXT, TXT_CV_STATUS, EVNT_DRAW);
}
if (wifiSetting.protocol == CLIENT_LNET)
progUhli(UHLI_PRG_END);
}
void showDirCV() { // muestra direccion de la locomotora segun sus CV
progStepCV = PRG_IDLE;
setStatusCV(); // show error / manufacturer / CV / pom
setFieldsCV();
setBitsCV();
if (isWindow(WIN_ALERT))
closeWindow(WIN_ALERT);
sprintf(locoEditAddr, " % d", decoAddress);
openWindow(WIN_PROG_ADDR);
if (wifiSetting.protocol == CLIENT_LNET)
progUhli(UHLI_PRG_END);
if (wifiSetting.protocol != CLIENT_ECOS)
pushLoco(decoAddress); // mete esta loco en el stack
}
void readBasicCV (uint16_t num) {
closeWindow(WIN_READ_CV);
if (num == 1) {
readCV(29, PRG_RD_CV29);
}
else {
CVaddress = num;
readCV(num, PRG_CV);
}
alertWindow(ERR_CV);
}
////////////////////////////////////////////////////////////
// ***** EEPROM *****
////////////////////////////////////////////////////////////
void saveCalibrationValues() {
TouchCalibration cal;
cal = touchscreen.getCalibration();
EEPROM.write (EE_XMIN_H, highByte(cal.xMin));
EEPROM.write (EE_XMIN_L, lowByte(cal.xMin));
EEPROM.write (EE_XMAX_H, highByte(cal.xMax));
EEPROM.write (EE_XMAX_L, lowByte(cal.xMax));
EEPROM.write (EE_YMIN_H, highByte(cal.yMin));
EEPROM.write (EE_YMIN_L, lowByte(cal.yMin));
EEPROM.write (EE_YMAX_H, highByte(cal.yMax));
EEPROM.write (EE_YMAX_L, lowByte(cal.yMax));
EEPROM.commit();
}

View File

@@ -0,0 +1,885 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** WINDOW OBJECTS *****
////////////////////////////////////////////////////////////
void openWindow(uint16_t id) {
uint16_t n;
char buf[MAX_LABEL_LNG];
switch (id) {
case WIN_LOGO:
createObject(OBJ_WIN, WIN_LOGO);
createObject(OBJ_DRAWSTR, DSTR_INIT);
createObject(OBJ_LABEL, LBL_PACO_TXT);
posObjStack1 = createObject(OBJ_LABEL, LBL_INIT);
createObject(OBJ_ICON, ICON_PACO);
createObject(OBJ_BAR, BAR_INIT);
drawWindow(WIN_LOGO);
break;
case WIN_CALIBRATE:
createObject(OBJ_WIN, WIN_CALIBRATE);
posObjStack2 = createObject(OBJ_LABEL, LBL_CAL);
posObjStack1 = createObject(OBJ_LABEL, LBL_PRESS);
newEvent(OBJ_WIN, WIN_CALIBRATE, EVNT_DRAW);
break;
case WIN_SSID:
createObject(OBJ_WIN, WIN_SSID);
createObject(OBJ_ICON, ICON_WIFI_SSID);
createObject(OBJ_BUTTON, BUT_SSID_CLOSE);
posObjStack1 = createObject(OBJ_LABEL, LBL_SCAN);
drawWindow(WIN_SSID);
scanWiFi();
objStack[posObjStack1].objID = LBL_SSID_SCAN;
createObject(OBJ_TXT, TXT_SSID1);
createObject(OBJ_TXT, TXT_SSID2);
createObject(OBJ_TXT, TXT_SSID3);
createObject(OBJ_TXT, TXT_SSID4);
createObject(OBJ_TXT, TXT_SSID5);
createObject(OBJ_TXT, TXT_SSID6);
scanWiFiFill();
newEvent(OBJ_WIN, WIN_SSID, EVNT_DRAW);
break;
case WIN_WIFI:
snprintf(ssidName, SSID_LNG, "%s", wifiSetting.ssid);
snprintf(keybPwdHideBuf, 9, "********");
snprintf(keybIP1Buf, 4, "%d", wifiSetting.CS_IP[0]);
snprintf(keybIP2Buf, 4, "%d", wifiSetting.CS_IP[1]);
snprintf(keybIP3Buf, 4, "%d", wifiSetting.CS_IP[2]);
snprintf(keybIP4Buf, 4, "%d", wifiSetting.CS_IP[3]);
setProtocolData();
createObject(OBJ_WIN, WIN_WIFI);
createObject(OBJ_ICON, ICON_WIFI_CFG);
createObject(OBJ_LABEL, LBL_SSID);
createObject(OBJ_TXT, TXT_SSID);
createObject(OBJ_LABEL, LBL_PWD_HIDE);
createObject(OBJ_TXT, TXT_PWD_HIDE);
createObject(OBJ_LABEL, LBL_IP);
createObject(OBJ_TXT, TXT_IP1);
createObject(OBJ_TXT, TXT_IP2);
createObject(OBJ_TXT, TXT_IP3);
createObject(OBJ_TXT, TXT_IP4);
createObject(OBJ_LABEL, LBL_PORT);
createObject(OBJ_TXT, TXT_PORT);
createObject(OBJ_KEYBOARD, KEYB_IP);
createObject(OBJ_LABEL, LBL_PROTOCOL);
createObject(OBJ_TXT, TXT_PROTOCOL);
createObject(OBJ_BUTTON, BUT_WIFI_OK);
newEvent(OBJ_WIN, WIN_WIFI, EVNT_DRAW);
break;
case WIN_WIFI_PWD:
snprintf(keybPwdBuf, PWD_LNG, wifiSetting.password);
createObject(OBJ_WIN, WIN_WIFI_PWD);
createObject(OBJ_TXT, TXT_PWD);
createObject(OBJ_KEYBOARD, KEYB_PWD);
createObject(OBJ_BUTTON, BUT_PWD_OK);
createObject(OBJ_BUTTON, BUT_PWD_CNCL);
newEvent(OBJ_WIN, WIN_WIFI_PWD, EVNT_DRAW);
break;
case WIN_PROTOCOL:
radioData[RAD_PROTOCOL].value = wifiSetting.protocol - CLIENT_Z21;
if (wifiSetting.protocol == CLIENT_LNET)
radioData[RAD_PROTOCOL_LN].value = (wifiSetting.serverType) ? 0 : 1;
else
radioData[RAD_PROTOCOL_LN].value = radioData[RAD_PROTOCOL_LN].num;
createObject(OBJ_WIN, WIN_PROTOCOL);
createObject(OBJ_RADIO, RAD_PROTOCOL);
createObject(OBJ_RADIO, RAD_PROTOCOL_LN);
createObject(OBJ_LABEL, LBL_SEL_PROT);
createObject(OBJ_LABEL, LBL_SEL_Z21);
createObject(OBJ_LABEL, LBL_SEL_XNET);
createObject(OBJ_LABEL, LBL_SEL_ECOS);
createObject(OBJ_LABEL, LBL_SEL_LNET);
createObject(OBJ_LABEL, LBL_SEL_LBSERVER);
createObject(OBJ_LABEL, LBL_SEL_BINARY);
createObject(OBJ_BUTTON, BUT_PROT_OK);
createObject(OBJ_BUTTON, BUT_OPTIONS);
newEvent(OBJ_WIN, WIN_PROTOCOL, EVNT_DRAW);
break;
case WIN_THROTTLE:
iconData[ICON_LOK_EDIT].bitmap = (wifiSetting.protocol == CLIENT_ECOS) ? info24 : wrench;
createObject(OBJ_WIN, WIN_THROTTLE);
createObject(OBJ_ICON, ICON_MENU);
createObject(OBJ_ICON, ICON_POWER);
createObject(OBJ_ICON, ICON_FNEXT);
createObject(OBJ_ICON, ICON_LOK_EDIT);
createObject(OBJ_FNC, FNC_ACC_PANEL);
//createObject(OBJ_ICON, ICON_FWD);
//createObject(OBJ_ICON, ICON_REV);
createObject(OBJ_TXT, TXT_CLOCK);
createObject(OBJ_TXT, TXT_LOCO_NAME);
createObject(OBJ_TXT, TXT_LOCO_ADDR);
createObject(OBJ_LPIC, LPIC_MAIN);
createObject(OBJ_GAUGE, GAUGE_SPEED);
createObject(OBJ_LABEL, LBL_KMH);
createObject(OBJ_FNC, FNC_FX0);
createObject(OBJ_FNC, FNC_FX1);
createObject(OBJ_FNC, FNC_FX2);
createObject(OBJ_FNC, FNC_FX3);
createObject(OBJ_FNC, FNC_FX4);
createObject(OBJ_FNC, FNC_FX5);
createObject(OBJ_FNC, FNC_FX6);
createObject(OBJ_FNC, FNC_FX7);
createObject(OBJ_FNC, FNC_FX8);
createObject(OBJ_FNC, FNC_FX9);
newEvent(OBJ_WIN, WIN_THROTTLE, EVNT_DRAW);
break;
case WIN_CONFIG:
buttonData[BUT_CFG_I_LANG].objID = DSTR_ENGLISH + currLanguage;
lastLanguage = currLanguage;
createObject(OBJ_WIN, WIN_CONFIG);
createObject(OBJ_DRAWSTR, DSTR_CFG_MENU);
createObject(OBJ_BUTTON, BUT_CFG_I_LANG);
createObject(OBJ_BUTTON, BUT_CFG_T_LANG);
createObject(OBJ_BUTTON, BUT_CFG_I_SCR);
createObject(OBJ_BUTTON, BUT_CFG_T_SCR);
createObject(OBJ_BUTTON, BUT_CFG_I_SPD);
createObject(OBJ_BUTTON, BUT_CFG_T_SPD);
createObject(OBJ_BUTTON, BUT_CFG_I_WIFI);
createObject(OBJ_BUTTON, BUT_CFG_T_WIFI);
createObject(OBJ_BUTTON, BUT_CFG_I_FCLK);
createObject(OBJ_BUTTON, BUT_CFG_T_FCLK);
createObject(OBJ_BUTTON, BUT_CFG_I_LOCK);
createObject(OBJ_BUTTON, BUT_CFG_T_LOCK);
createObject(OBJ_BUTTON, BUT_CFG_I_ABOUT);
createObject(OBJ_BUTTON, BUT_CFG_T_ABOUT);
createObject(OBJ_ICON, ICON_CFG_EXIT);
newEvent(OBJ_WIN, WIN_CONFIG, EVNT_DRAW);
break;
case WIN_SCREEN:
barData[BAR_BLIGHT].value = backlight;
switchData[SW_ROTATE].state = (locationUSB == USB_UP);
createObject(OBJ_WIN, WIN_SCREEN);
createObject(OBJ_ICON, ICON_BLIGHT);
createObject(OBJ_BAR, BAR_BLIGHT);
createObject(OBJ_SWITCH, SW_ROTATE);
createObject(OBJ_LABEL, LBL_SCR_ROTATE);
createObject(OBJ_BUTTON, BUT_CFG_TOUCH);
createObject(OBJ_BUTTON, BUT_SCR_OK);
createObject(OBJ_BUTTON, BUT_SCR_CNCL);
newEvent(OBJ_WIN, WIN_SCREEN, EVNT_DRAW);
break;
case WIN_SPEED:
switchData[SW_SHUNTING].state = shuntingMode;
radioData[RAD_STOP_MODE].value = (stopMode > 0) ? 1 : 0;
createObject(OBJ_WIN, WIN_SPEED);
createObject(OBJ_LABEL, LBL_SHUNTING);
createObject(OBJ_SWITCH, SW_SHUNTING);
createObject(OBJ_RADIO, RAD_STOP_MODE);
createObject(OBJ_ICON, ICON_STOP);
createObject(OBJ_LABEL, LBL_STOP_0);
createObject(OBJ_LABEL, LBL_STOP_E);
createObject(OBJ_BUTTON, BUT_SPD_OK);
newEvent(OBJ_WIN, WIN_SPEED, EVNT_DRAW);
break;
case WIN_SET_CLOCK:
snprintf(keybHourBuf, 3, "%d", clockHour);
snprintf(keybMinBuf, 3, "%d", clockMin);
snprintf(keybRateBuf, 4, "%d", clockRate);
showClockData(TXT_HOUR);
createObject(OBJ_WIN, WIN_SET_CLOCK);
createObject(OBJ_DRAWSTR, DSTR_CLOCK);
createObject(OBJ_CHAR, CHAR_CLK_COLON);
createObject(OBJ_ICON, ICON_SET_CLOCK);
createObject(OBJ_TXT, TXT_HOUR);
createObject(OBJ_TXT, TXT_MIN);
createObject(OBJ_TXT, TXT_RATE);
createObject(OBJ_LABEL, LBL_RATE);
createObject(OBJ_KEYBOARD, KEYB_CLOCK);
createObject(OBJ_BUTTON, BUT_CLOCK_OK);
createObject(OBJ_BUTTON, BUT_CLOCK_CNCL);
newEvent(OBJ_WIN, WIN_SET_CLOCK, EVNT_DRAW);
break;
case WIN_LOCK:
switchData[SW_LOCK_LOK].state = (bitRead(lockOptions, LOCK_SEL_LOCO)) ? true : false;
switchData[SW_LOCK_ACC].state = (bitRead(lockOptions, LOCK_TURNOUT)) ? true : false;
switchData[SW_LOCK_PRG].state = (bitRead(lockOptions, LOCK_PROG)) ? true : false;
createObject(OBJ_WIN, WIN_LOCK);
createObject(OBJ_SWITCH, SW_LOCK_LOK);
createObject(OBJ_SWITCH, SW_LOCK_ACC);
createObject(OBJ_SWITCH, SW_LOCK_PRG);
createObject(OBJ_LABEL, LBL_LOCK_LOK);
createObject(OBJ_LABEL, LBL_LOCK_ACC);
createObject(OBJ_LABEL, LBL_LOCK_PRG);
createObject(OBJ_BUTTON, BUT_LOCK);
newEvent(OBJ_WIN, WIN_LOCK, EVNT_DRAW);
break;
case WIN_ABOUT:
snprintf (aboutPacoMouseCYD, PWD_LNG + 1, "v%s.%s%s", VER_H, VER_L, VER_R);
snprintf (aboutIP, PWD_LNG + 1, "IP: %u.%u.%u.%u", WiFi.localIP().operator[](0), WiFi.localIP().operator[](1), WiFi.localIP().operator[](2), WiFi.localIP().operator[](3));
snprintf (aboutMAC, PWD_LNG + 1, "MAC: %s", WiFi.macAddress().c_str());
createObject(OBJ_WIN, WIN_ABOUT);
createObject(OBJ_DRAWSTR, DSTR_ABOUT);
createObject(OBJ_LABEL, LBL_PACO_TXT);
createObject(OBJ_ICON, ICON_ABOUT_PACO);
createObject(OBJ_TXT, TXT_ABOUT);
createObject(OBJ_TXT, TXT_ABOUT_IP);
createObject(OBJ_TXT, TXT_ABOUT_MAC);
createObject(OBJ_LABEL, LBL_PACO_WEB);
newEvent(OBJ_WIN, WIN_ABOUT, EVNT_DRAW);
break;
case WIN_LOK_EDIT:
snprintf (locoEditName, NAME_LNG + 1, "%s", locoData[myLocoData].myName );
sprintf (locoEditAddr, "%d", locoData[myLocoData].myAddr.address);
sprintf (locoEditID, "%d", locoData[myLocoData].myLocoID);
sprintf (locoEditVmax, "%d", locoData[myLocoData].myVmax);
lpicData[LPIC_LOK_EDIT].id = locoData[myLocoData].myLocoID;
for (n = 0; n < 29; n++)
fncData[FNC_F0 + n].idIcon = locoData[myLocoData].myFuncIcon[n];
createObject(OBJ_WIN, WIN_LOK_EDIT);
createObject(OBJ_LABEL, LBL_ADDR);
createObject(OBJ_LABEL, LBL_IMAGE);
createObject(OBJ_LABEL, LBL_NAME);
createObject(OBJ_LABEL, LBL_VMAX);
createObject(OBJ_LPIC, LPIC_LOK_EDIT);
createObject(OBJ_TXT, TXT_EDIT_ADDR);
createObject(OBJ_TXT, TXT_EDIT_NAME);
createObject(OBJ_TXT, TXT_EDIT_IMAGE);
createObject(OBJ_TXT, TXT_EDIT_VMAX);
createObject(OBJ_BUTTON, BUT_EDIT_FUNC);
createObject(OBJ_BUTTON, BUT_EDIT_CNCL);
if (wifiSetting.protocol != CLIENT_ECOS) {
createObject(OBJ_BUTTON, BUT_EDIT_OK);
if ((locoData[myLocoData].mySpeed < 2) && (countLocoInStack() > 1)) // stopped and remaining locos in stack
createObject(OBJ_BUTTON, BUT_EDIT_DEL);
}
newEvent(OBJ_WIN, WIN_LOK_EDIT, EVNT_DRAW);
break;
case WIN_EDIT_NAME:
snprintf(keybNameBuf, NAME_LNG + 1, locoData[myLocoData].myName);
txtData[TXT_NAME].maxLength = NAME_LNG;
createObject(OBJ_WIN, WIN_EDIT_NAME);
createObject(OBJ_TXT, TXT_NAME);
createObject(OBJ_KEYBOARD, KEYB_NAME);
createObject(OBJ_BUTTON, BUT_NAME_OK);
createObject(OBJ_BUTTON, BUT_NAME_CNCL);
newEvent(OBJ_WIN, WIN_EDIT_NAME, EVNT_DRAW);
break;
case WIN_FUNC:
createObject(OBJ_WIN, WIN_FUNC);
for (n = 0; n < 29; n++)
createObject(OBJ_FNC, FNC_F0 + n);
createObject(OBJ_LABEL, LBL_ADDR);
createObject(OBJ_TXT, TXT_EDIT_ADDR);
createObject(OBJ_LABEL, LBL_EDIT_FUNC);
if (wifiSetting.protocol != CLIENT_ECOS)
createObject(OBJ_BUTTON, BUT_FNC_OK);
createObject(OBJ_BUTTON, BUT_FNC_CNCL);
newEvent(OBJ_WIN, WIN_FUNC, EVNT_DRAW);
break;
case WIN_CHG_FUNC:
createObject(OBJ_WIN, WIN_CHG_FUNC);
createObject(OBJ_FNC, FNC_CHG);
createObject(OBJ_TXT, TXT_EDIT_FNC);
newEvent(OBJ_WIN, WIN_CHG_FUNC, EVNT_DRAW);
break;
case WIN_VMAX:
createObject(OBJ_WIN, WIN_VMAX);
createObject(OBJ_TXT, TXT_KEYB_VMAX);
createObject(OBJ_KEYBOARD, KEYB_VMAX);
newEvent(OBJ_WIN, WIN_VMAX, EVNT_DRAW);
break;
case WIN_SEL_LOCO:
createObject(OBJ_WIN, WIN_SEL_LOCO);
createObject(OBJ_DRAWSTR, DSTR_SELLOK);
posObjStack1 = createObject(OBJ_ICON, ICON_LAST_UP);
prepareLocoList();
createObject(OBJ_ICON, ICON_SEL_LOK);
if (wifiSetting.protocol != CLIENT_ECOS)
createObject(OBJ_FNC, FNC_SEL_KEYPAD);
createObject(OBJ_TXT, TXT_SEL_ADDR1);
createObject(OBJ_TXT, TXT_SEL_NAME1);
createObject(OBJ_TXT, TXT_SEL_ADDR2);
createObject(OBJ_TXT, TXT_SEL_NAME2);
createObject(OBJ_TXT, TXT_SEL_ADDR3);
createObject(OBJ_TXT, TXT_SEL_NAME3);
createObject(OBJ_TXT, TXT_SEL_ADDR4);
createObject(OBJ_TXT, TXT_SEL_NAME4);
createObject(OBJ_TXT, TXT_SEL_ADDR5);
createObject(OBJ_TXT, TXT_SEL_NAME5);
createObject(OBJ_TXT, TXT_SEL_ADDR6);
createObject(OBJ_TXT, TXT_SEL_NAME6);
newEvent(OBJ_WIN, WIN_SEL_LOCO, EVNT_DRAW);
break;
case WIN_ENTER_ADDR:
locoKeybAddr[0] = '\0';
createObject(OBJ_WIN, WIN_ENTER_ADDR);
createObject(OBJ_TXT, TXT_KEYB_ADDR);
createObject(OBJ_KEYBOARD, KEYB_ADDR);
newEvent(OBJ_WIN, WIN_ENTER_ADDR, EVNT_DRAW);
break;
case WIN_SEL_IMAGE:
createObject(OBJ_WIN, WIN_SEL_IMAGE);
createObject(OBJ_LABEL, LBL_SEL_IMAGE);
createObject(OBJ_BUTTON, BUT_IMAGE_CNCL);
drawWindow(WIN_SEL_IMAGE);
populateImageList();
createObject(OBJ_LPIC, LPIC_SEL_IMG1);
createObject(OBJ_LPIC, LPIC_SEL_IMG2);
createObject(OBJ_LPIC, LPIC_SEL_IMG3);
createObject(OBJ_LPIC, LPIC_SEL_IMG4);
createObject(OBJ_LPIC, LPIC_SEL_IMG5);
createObject(OBJ_LPIC, LPIC_SEL_IMG6);
createObject(OBJ_ICON, ICON_PREV_IMAGE);
createObject(OBJ_ICON, ICON_NEXT_IMAGE);
newEvent(OBJ_WIN, WIN_SEL_IMAGE, EVNT_DRAW);
break;
case WIN_MENU:
createObject(OBJ_WIN, WIN_MENU);
createObject(OBJ_BUTTON, BUT_MENU_I_DRIVE);
createObject(OBJ_BUTTON, BUT_MENU_T_DRIVE);
createObject(OBJ_BUTTON, BUT_MENU_I_ACC);
createObject(OBJ_BUTTON, BUT_MENU_T_ACC);
createObject(OBJ_BUTTON, BUT_MENU_I_CV);
createObject(OBJ_BUTTON, BUT_MENU_T_CV);
createObject(OBJ_BUTTON, BUT_MENU_I_CFG);
createObject(OBJ_BUTTON, BUT_MENU_T_CFG);
createObject(OBJ_BUTTON, BUT_MENU_I_UTILS);
createObject(OBJ_BUTTON, BUT_MENU_T_UTILS);
createObject(OBJ_DRAWSTR, DSTR_MENU);
newEvent(OBJ_WIN, WIN_MENU, EVNT_DRAW);
break;
case WIN_OPTIONS:
setOptionsData();
createObject(OBJ_WIN, WIN_OPTIONS);
switch (wifiSetting.protocol) {
case CLIENT_Z21:
//createObject(OBJ_SWITCH, SW_OPT_TT_OFFSET);
//createObject(OBJ_LABEL, LBL_OPT_TT_OFFSET);
createObject(OBJ_SWITCH, SW_OPT_ADR);
createObject(OBJ_LABEL, LBL_OPT_ADR);
break;
case CLIENT_XNET:
//createObject(OBJ_SWITCH, SW_OPT_TT_OFFSET);
//createObject(OBJ_LABEL, LBL_OPT_TT_OFFSET);
break;
case CLIENT_LNET:
switchData[SW_OPT_DISCOVER].state = (autoIdentifyCS > 0) ? true : false;
createObject(OBJ_SWITCH, SW_OPT_DISCOVER);
createObject(OBJ_LABEL, LBL_OPT_DISCOVER);
createObject(OBJ_RADIO, RAD_CSTATION);
createObject(OBJ_LABEL, LBL_OPT_IB2);
createObject(OBJ_LABEL, LBL_OPT_UHLI);
createObject(OBJ_LABEL, LBL_OPT_DIG);
break;
case CLIENT_ECOS:
break;
}
createObject(OBJ_BUTTON, BUT_OPT_OK);
newEvent(OBJ_WIN, WIN_OPTIONS, EVNT_DRAW);
break;
case WIN_SPEEDO:
speedoSpeed = 0;
speedoPhase = SPD_WAIT;
setSpeedoPhase(SPD_WAIT);
setTextSpeedo();
snprintf(spdLengthBuf, NAME_LNG + 1, "%d", speedoLength);
snprintf(spdSpeedBuf, NAME_LNG + 1, "%d km/h", speedoSpeed);
iconData[ICON_SPEEDO_LOK].x = 40 + (speedoPhase * 32);
drawStrData[DSTR_SPEEDO_BLANK].x = 40 + (speedoPhase * 32);
lpicData[LPIC_SPEEDO].id = locoData[myLocoData].myLocoID;
createObject(OBJ_WIN, WIN_SPEEDO);
createObject(OBJ_LPIC, LPIC_SPEEDO);
createObject(OBJ_LABEL, LBL_SCALE);
createObject(OBJ_LABEL, LBL_MM);
createObject(OBJ_GAUGE, GAUGE_SPEEDO);
createObject(OBJ_FNC, FNC_SPEEDO_DIR);
createObject(OBJ_DRAWSTR, DSTR_SPEEDO_BLANK);
createObject(OBJ_DRAWSTR, DSTR_SPEEDO_TRK);
createObject(OBJ_ICON, ICON_SPEEDO_LOK);
createObject(OBJ_ICON, ICON_SPEEDO_RADAR);
createObject(OBJ_BUTTON, BUT_SPEEDO_CNCL);
createObject(OBJ_BUTTON, BUT_SPEEDO_CV);
createObject(OBJ_TXT, TXT_SPEEDO_SCALE);
createObject(OBJ_TXT, TXT_SPEEDO_LNG);
createObject(OBJ_TXT, TXT_SPEEDO_SPD);
newEvent(OBJ_WIN, WIN_SPEEDO, EVNT_DRAW);
break;
case WIN_SPEEDO_LNG:
snprintf(speedoKeybLng, PORT_LNG + 1, "%d", speedoLength);
createObject(OBJ_WIN, WIN_SPEEDO_LNG);
createObject(OBJ_TXT, TXT_EDIT_LNG);
createObject(OBJ_KEYBOARD, KEYB_LNG);
newEvent(OBJ_WIN, WIN_SPEEDO_LNG, EVNT_DRAW);
break;
case WIN_SPEEDO_SCALE:
setTextSpeedo();
createObject(OBJ_WIN, WIN_SPEEDO_SCALE);
createObject(OBJ_TXT, TXT_EDIT_SCALE);
createObject(OBJ_TXT, TXT_NUM_SCALE);
createObject(OBJ_KEYBOARD, KEYB_SCALE);
createObject(OBJ_BUTTON, BUT_SPEEDO_H0);
createObject(OBJ_BUTTON, BUT_SPEEDO_N);
createObject(OBJ_BUTTON, BUT_SPEEDO_TT);
createObject(OBJ_BUTTON, BUT_SPEEDO_Z);
createObject(OBJ_BUTTON, BUT_SPEEDO_0);
newEvent(OBJ_WIN, WIN_SPEEDO_SCALE, EVNT_DRAW);
break;
case WIN_READ_CV:
createObject(OBJ_WIN, WIN_READ_CV);
createObject(OBJ_DRAWSTR, DSTR_CFG_MENU);
createObject(OBJ_BUTTON, BUT_CV_ADDR);
createObject(OBJ_BUTTON, BUT_CV_SPD_L);
createObject(OBJ_BUTTON, BUT_CV_SPD_M);
createObject(OBJ_BUTTON, BUT_CV_SPD_H);
createObject(OBJ_BUTTON, BUT_CV_ACC);
createObject(OBJ_BUTTON, BUT_CV_DEC);
createObject(OBJ_BUTTON, BUT_CV_CFG);
createObject(OBJ_BUTTON, BUT_CV_MAN);
newEvent(OBJ_WIN, WIN_READ_CV, EVNT_DRAW);
break;
case WIN_PROG_CV:
//buttonData[BUT_CV_LNCV].backgnd = (wifiSetting.protocol == CLIENT_LNET) ? COLOR_CREAM : COLOR_LIGHTBLACK;
setFieldsCV();
setBitsCV();
setStatusCV();
switchData[SW_POM].state = modeProg;
createObject(OBJ_WIN, WIN_PROG_CV);
createObject(OBJ_LABEL, LBL_CV);
createObject(OBJ_LABEL, LBL_POM);
createObject(OBJ_LABEL, LBL_BITS);
createObject(OBJ_SWITCH, SW_POM);
createObject(OBJ_BUTTON, BUT_CV_READ);
createObject(OBJ_BUTTON, BUT_CV_CNCL);
if (wifiSetting.protocol == CLIENT_LNET)
createObject(OBJ_BUTTON, BUT_CV_LNCV);
createObject(OBJ_KEYBOARD, KEYB_CV);
createObject(OBJ_CHAR, CHAR_CV_EQUAL);
createObject(OBJ_BUTTON, BUT_CV_0);
createObject(OBJ_BUTTON, BUT_CV_1);
createObject(OBJ_BUTTON, BUT_CV_2);
createObject(OBJ_BUTTON, BUT_CV_3);
createObject(OBJ_BUTTON, BUT_CV_4);
createObject(OBJ_BUTTON, BUT_CV_5);
createObject(OBJ_BUTTON, BUT_CV_6);
createObject(OBJ_BUTTON, BUT_CV_7);
createObject(OBJ_TXT, TXT_CV);
createObject(OBJ_TXT, TXT_CV_VAL);
createObject(OBJ_TXT, TXT_CV_STATUS);
newEvent(OBJ_WIN, WIN_PROG_CV, EVNT_DRAW);
break;
case WIN_PROG_ADDR:
getLabelTxt(LBL_CV_ADDR, buf);
snprintf(cvStatusBuf, PWD_LNG + 1, "%s", buf);
createObject(OBJ_WIN, WIN_PROG_ADDR);
createObject(OBJ_TXT, TXT_CV_STATUS);
createObject(OBJ_ICON, ICON_ADDR);
createObject(OBJ_TXT, TXT_CV_ADDR);
createObject(OBJ_KEYBOARD, KEYB_CV_ADDR);
createObject(OBJ_BUTTON, BUT_ADDR_CNCL);
newEvent(OBJ_WIN, WIN_PROG_ADDR, EVNT_DRAW);
break;
case WIN_PROG_LNCV:
setFieldsLNCV();
createObject(OBJ_WIN, WIN_PROG_LNCV);
createObject(OBJ_KEYBOARD, KEYB_LNCV);
createObject(OBJ_LABEL, LBL_LNCV_ART);
createObject(OBJ_LABEL, LBL_LNCV_MOD);
createObject(OBJ_LABEL, LBL_LNCV_NUM);
createObject(OBJ_BUTTON, BUT_LNCV_FIND);
createObject(OBJ_BUTTON, BUT_LNCV_CNCL);
createObject(OBJ_TXT, TXT_LNCV_ART);
createObject(OBJ_TXT, TXT_LNCV_MOD);
createObject(OBJ_TXT, TXT_LNCV_ADR);
createObject(OBJ_TXT, TXT_LNCV_VAL);
createObject(OBJ_CHAR, CHAR_LNCV_EQUAL);
newEvent(OBJ_WIN, WIN_PROG_LNCV, EVNT_DRAW);
break;
case WIN_STEAM:
fncData[FNC_ST_SMOKE].state = false;
createObject(OBJ_WIN, WIN_STEAM);
createObject(OBJ_DRAWSTR, DSTR_STEAM);
createObject(OBJ_ICON, ICON_POWER);
createObject(OBJ_ICON, ICON_MANOMETER);
createObject(OBJ_ICON, ICON_STEAM_EDIT);
createObject(OBJ_BUTTON, BUT_STEAM_CNCL);
createObject(OBJ_FNC, FNC_ST_WATER);
createObject(OBJ_FNC, FNC_ST_TENDER);
createObject(OBJ_FNC, FNC_ST_WHISTLE);
createObject(OBJ_FNC, FNC_ST_FIRE);
createObject(OBJ_FNC, FNC_ST_SMOKE);
createObject(OBJ_BAR, BAR_JOHNSON);
createObject(OBJ_BAR, BAR_WATER);
createObject(OBJ_BAR, BAR_TENDER);
createObject(OBJ_BAR, BAR_BRAKE);
newEvent(OBJ_WIN, WIN_STEAM, EVNT_DRAW);
break;
case WIN_UTIL:
createObject(OBJ_WIN, WIN_UTIL);
createObject(OBJ_BUTTON, BUT_UTL_I_SPEEDO);
createObject(OBJ_BUTTON, BUT_UTL_T_SPEEDO);
createObject(OBJ_BUTTON, BUT_UTL_I_STEAM);
createObject(OBJ_BUTTON, BUT_UTL_T_STEAM);
createObject(OBJ_BUTTON, BUT_UTL_I_SCAN);
createObject(OBJ_BUTTON, BUT_UTL_T_SCAN);
createObject(OBJ_BUTTON, BUT_UTL_I_STA);
createObject(OBJ_BUTTON, BUT_UTL_T_STA);
createObject(OBJ_ICON, ICON_UTL_EXIT);
createObject(OBJ_DRAWSTR, DSTR_UTL_MENU);
newEvent(OBJ_WIN, WIN_UTIL, EVNT_DRAW);
break;
case WIN_ACCESSORY:
editAccessory = false;
winData[WIN_ACCESSORY].backgnd = COLOR_WHITE;
updateAccPanel();
createObject(OBJ_WIN, WIN_ACCESSORY);
createObject(OBJ_BUTTON, BUT_ACC_0);
createObject(OBJ_BUTTON, BUT_ACC_1);
createObject(OBJ_BUTTON, BUT_ACC_2);
createObject(OBJ_BUTTON, BUT_ACC_3);
createObject(OBJ_BUTTON, BUT_ACC_4);
createObject(OBJ_BUTTON, BUT_ACC_5);
createObject(OBJ_BUTTON, BUT_ACC_6);
createObject(OBJ_BUTTON, BUT_ACC_7);
createObject(OBJ_BUTTON, BUT_ACC_8);
createObject(OBJ_BUTTON, BUT_ACC_9);
createObject(OBJ_BUTTON, BUT_ACC_10);
createObject(OBJ_BUTTON, BUT_ACC_11);
createObject(OBJ_BUTTON, BUT_ACC_12);
createObject(OBJ_BUTTON, BUT_ACC_13);
createObject(OBJ_BUTTON, BUT_ACC_14);
createObject(OBJ_BUTTON, BUT_ACC_15);
createObject(OBJ_BUTTON, BUT_ACC_CNCL);
createObject(OBJ_BUTTON, BUT_ACC_EDIT);
createObject(OBJ_TXT, TXT_ACC_0);
createObject(OBJ_TXT, TXT_ACC_1);
createObject(OBJ_TXT, TXT_ACC_2);
createObject(OBJ_TXT, TXT_ACC_3);
createObject(OBJ_TXT, TXT_ACC_4);
createObject(OBJ_TXT, TXT_ACC_5);
createObject(OBJ_TXT, TXT_ACC_6);
createObject(OBJ_TXT, TXT_ACC_7);
createObject(OBJ_TXT, TXT_ACC_8);
createObject(OBJ_TXT, TXT_ACC_9);
createObject(OBJ_TXT, TXT_ACC_10);
createObject(OBJ_TXT, TXT_ACC_11);
createObject(OBJ_TXT, TXT_ACC_12);
createObject(OBJ_TXT, TXT_ACC_13);
createObject(OBJ_TXT, TXT_ACC_14);
createObject(OBJ_TXT, TXT_ACC_15);
createObject(OBJ_TXT, TXT_PANEL);
newEvent(OBJ_WIN, WIN_ACCESSORY, EVNT_DRAW);
break;
case WIN_PANELS:
createObject(OBJ_WIN, WIN_PANELS);
createObject(OBJ_TXT, TXT_PANEL0);
createObject(OBJ_TXT, TXT_PANEL1);
createObject(OBJ_TXT, TXT_PANEL2);
createObject(OBJ_TXT, TXT_PANEL3);
createObject(OBJ_TXT, TXT_PANEL4);
createObject(OBJ_TXT, TXT_PANEL5);
createObject(OBJ_TXT, TXT_PANEL6);
createObject(OBJ_TXT, TXT_PANEL7);
createObject(OBJ_TXT, TXT_PANEL8);
createObject(OBJ_TXT, TXT_PANEL9);
createObject(OBJ_TXT, TXT_PANEL10);
createObject(OBJ_TXT, TXT_PANEL11);
createObject(OBJ_TXT, TXT_PANEL12);
createObject(OBJ_TXT, TXT_PANEL13);
createObject(OBJ_TXT, TXT_PANEL14);
createObject(OBJ_TXT, TXT_PANEL15);
newEvent(OBJ_WIN, WIN_PANELS, EVNT_DRAW);
break;
case WIN_PANEL_NAME:
snprintf(keybNameBuf, PANEL_LNG + 1, panelNameBuf);
txtData[TXT_NAME].maxLength = PANEL_LNG;
createObject(OBJ_WIN, WIN_PANEL_NAME);
createObject(OBJ_TXT, TXT_NAME);
createObject(OBJ_KEYBOARD, KEYB_NAME);
createObject(OBJ_BUTTON, BUT_NAME_OK);
createObject(OBJ_BUTTON, BUT_NAME_CNCL);
newEvent(OBJ_WIN, WIN_PANEL_NAME, EVNT_DRAW);
break;
case WIN_ACC_CTRL:
snprintf(accKeybAddr, ADDR_LNG + 1, "%d", myTurnout);
createObject(OBJ_WIN, WIN_ACC_CTRL);
createObject(OBJ_TXT, TXT_ACC_ADDR);
createObject(OBJ_KEYBOARD, KEYB_ACC);
createObject(OBJ_ICON, ICON_KEYB_ACC);
createObject(OBJ_BUTTON, BUT_ACC_RED);
createObject(OBJ_BUTTON, BUT_ACC_GREEN);
newEvent(OBJ_WIN, WIN_ACC_CTRL, EVNT_DRAW);
break;
case WIN_ACC_ASPECT:
createObject(OBJ_WIN, WIN_ACC_ASPECT);
createObject(OBJ_BUTTON, BUT_ACC_ASPECT0);
createObject(OBJ_BUTTON, BUT_ACC_ASPECT1);
createObject(OBJ_BUTTON, BUT_ACC_ASPECT2);
if (currAccAspects == 4)
createObject(OBJ_BUTTON, BUT_ACC_ASPECT3);
newEvent(OBJ_WIN, WIN_ACC_ASPECT, EVNT_DRAW);
break;
case WIN_ACC_TYPE:
createObject(OBJ_WIN, WIN_ACC_TYPE);
createObject(OBJ_LABEL, LBL_ACC_TYPE);
createObject(OBJ_FNC, FNC_ACC_TYPE);
newEvent(OBJ_WIN, WIN_ACC_TYPE, EVNT_DRAW);
break;
case WIN_ACC_EDIT:
n = accDef[currAccEdit.type].aspects;
winData[WIN_ACC_EDIT].h = 130 + (n * 40);
buttonData[BUT_TYPE_OK].y = 93 + (n * 40);
buttonData[BUT_TYPE_CNCL].y = 93 + (n * 40);
iconData[ICON_TYPE_OK].y = 97 + (n * 40);
iconData[ICON_TYPE_CNCL].y = 97 + (n * 40);
createObject(OBJ_WIN, WIN_ACC_EDIT);
createObject(OBJ_LABEL, LBL_ACC_NAME);
createObject(OBJ_LABEL, LBL_ACC_ADDR);
createObject(OBJ_TXT, TXT_ACC_NAME);
createObject(OBJ_TXT, TXT_ACC_ADDR1);
createObject(OBJ_FNC, FNC_EDIT_ASPECT0);
createObject(OBJ_BUTTON, BUT_ACC_OUT0);
createObject(OBJ_BUTTON, BUT_ACC_OUT1);
if (n > 1) {
createObject(OBJ_FNC, FNC_EDIT_ASPECT1);
createObject(OBJ_BUTTON, BUT_ACC_OUT4);
createObject(OBJ_BUTTON, BUT_ACC_OUT5);
}
if (n > 2) {
createObject(OBJ_TXT, TXT_ACC_ADDR2);
createObject(OBJ_ICON, ICON_PLUS_ONE);
createObject(OBJ_FNC, FNC_EDIT_ASPECT2);
createObject(OBJ_BUTTON, BUT_ACC_OUT2);
createObject(OBJ_BUTTON, BUT_ACC_OUT3);
createObject(OBJ_BUTTON, BUT_ACC_OUT6);
createObject(OBJ_BUTTON, BUT_ACC_OUT7);
createObject(OBJ_BUTTON, BUT_ACC_OUT8);
createObject(OBJ_BUTTON, BUT_ACC_OUT9);
createObject(OBJ_BUTTON, BUT_ACC_OUT10);
createObject(OBJ_BUTTON, BUT_ACC_OUT11);
}
if (n > 3) {
createObject(OBJ_FNC, FNC_EDIT_ASPECT3);
createObject(OBJ_BUTTON, BUT_ACC_OUT12);
createObject(OBJ_BUTTON, BUT_ACC_OUT13);
createObject(OBJ_BUTTON, BUT_ACC_OUT14);
createObject(OBJ_BUTTON, BUT_ACC_OUT15);
}
createObject(OBJ_BUTTON, BUT_TYPE_OK);
createObject(OBJ_BUTTON, BUT_TYPE_CNCL);
newEvent(OBJ_WIN, WIN_ACC_EDIT, EVNT_DRAW);
break;
case WIN_ACC_NAME:
snprintf(keybNameBuf, ACC_LNG + 1, accKeybName);
txtData[TXT_NAME].maxLength = ACC_LNG;
createObject(OBJ_WIN, WIN_ACC_NAME);
createObject(OBJ_TXT, TXT_NAME);
createObject(OBJ_KEYBOARD, KEYB_NAME);
createObject(OBJ_BUTTON, BUT_NAME_OK);
createObject(OBJ_BUTTON, BUT_NAME_CNCL);
newEvent(OBJ_WIN, WIN_ACC_NAME, EVNT_DRAW);
break;
case WIN_ACC_ADDR1:
snprintf(accKeybAdrEdit, ADDR_LNG + 1, "%d", currAccEdit.addr);
createObject(OBJ_WIN, WIN_ACC_ADDR1);
createObject(OBJ_TXT, TXT_ACC_EDIT);
createObject(OBJ_KEYBOARD, KEYB_ACC_ADDR);
newEvent(OBJ_WIN, WIN_ACC_ADDR1, EVNT_DRAW);
break;
case WIN_ACC_ADDR2:
snprintf(accKeybAdrEdit, ADDR_LNG + 1, "%d", currAccEdit.addr2);
createObject(OBJ_WIN, WIN_ACC_ADDR2);
createObject(OBJ_TXT, TXT_ACC_EDIT);
createObject(OBJ_KEYBOARD, KEYB_ACC_ADDR);
newEvent(OBJ_WIN, WIN_ACC_ADDR2, EVNT_DRAW);
break;
case WIN_WIFI_SCAN:
setTimer(TMR_SCAN, 5, TMR_ONESHOT);
createObject(OBJ_WIN, WIN_WIFI_SCAN);
createObject(OBJ_DRAWSTR, DSTR_WIFI_SCAN);
createObject(OBJ_LABEL, LBL_SSID_SCAN);
createObject(OBJ_FNC, FNC_SCAN_RESET);
newEvent(OBJ_WIN, WIN_WIFI_SCAN, EVNT_DRAW);
break;
case WIN_STA_RUN:
updateStationTime(staTime);
updateStationLevel();
updateStationStars();
updateTargetStations();
createObject(OBJ_WIN, WIN_STA_RUN);
createObject(OBJ_LABEL, LBL_STA_RUN);
createObject(OBJ_LABEL, LBL_STA_LEVEL);
createObject(OBJ_LABEL, LBL_STA_INSTR);
createObject(OBJ_FNC, FNC_STA_STARS);
createObject(OBJ_ICON, ICON_STA_CLOCK);
createObject(OBJ_ICON, ICON_STA_STATION);
createObject(OBJ_ICON, ICON_STA_EDIT);
createObject(OBJ_BUTTON, BUT_STA_START);
createObject(OBJ_BUTTON, BUT_STA_CNCL);
createObject(OBJ_TXT, TXT_STA_LEVEL);
createObject(OBJ_TXT, TXT_STA_STARS);
createObject(OBJ_TXT, TXT_STA_STATION);
createObject(OBJ_TXT, TXT_STA_CLOCK);
newEvent(OBJ_WIN, WIN_STA_RUN, EVNT_DRAW);
break;
case WIN_STA_PLAY:
updateTurnoutButtons();
fncData[FNC_STA_RAYO].state = isTrackOff();
createObject(OBJ_WIN, WIN_STA_PLAY);
createObject(OBJ_DRAWSTR, DSTR_STATION_PLAY);
createObject(OBJ_ICON, ICON_STA_TARGET);
createObject(OBJ_ICON, ICON_STA_TRAIN);
createObject(OBJ_ICON, ICON_STA_PIN);
createObject(OBJ_ICON, ICON_STA_TIME);
createObject(OBJ_ICON, ICON_STA_COUNT);
createObject(OBJ_TXT, TXT_STA_TIME);
createObject(OBJ_TXT, TXT_STA_COUNT);
createObject(OBJ_TXT, TXT_STA_STARC);
createObject(OBJ_GAUGE, GAUGE_STATION);
createObject(OBJ_FNC, FNC_STA_DIR);
createObject(OBJ_FNC, FNC_STA_STARC);
createObject(OBJ_FNC, FNC_STA_RAYO);
createObject(OBJ_BUTTON, BUT_STA_STOP);
switch (staMaxTurnout) {
case 1:
fncData[FNC_STA_ACC0].x = 104;
buttonData[BUT_STA_ACC0].x = 100;
createObject(OBJ_BUTTON, BUT_STA_ACC0);
break;
case 2:
fncData[FNC_STA_ACC0].x = 54;
fncData[FNC_STA_ACC1].x = 154;
buttonData[BUT_STA_ACC0].x = 50;
buttonData[BUT_STA_ACC1].x = 150;
createObject(OBJ_BUTTON, BUT_STA_ACC0);
createObject(OBJ_BUTTON, BUT_STA_ACC1);
break;
case 3:
fncData[FNC_STA_ACC0].x = 40;
fncData[FNC_STA_ACC1].x = 104;
fncData[FNC_STA_ACC2].x = 168;
buttonData[BUT_STA_ACC0].x = 36;
buttonData[BUT_STA_ACC1].x = 100;
buttonData[BUT_STA_ACC2].x = 164;
createObject(OBJ_BUTTON, BUT_STA_ACC0);
createObject(OBJ_BUTTON, BUT_STA_ACC1);
createObject(OBJ_BUTTON, BUT_STA_ACC2);
break;
default:
fncData[FNC_STA_ACC0].x = 20;
fncData[FNC_STA_ACC1].x = 76;
fncData[FNC_STA_ACC2].x = 132;
fncData[FNC_STA_ACC3].x = 188;
buttonData[BUT_STA_ACC0].x = 16;
buttonData[BUT_STA_ACC1].x = 72;
buttonData[BUT_STA_ACC2].x = 128;
buttonData[BUT_STA_ACC3].x = 184;
createObject(OBJ_BUTTON, BUT_STA_ACC0);
createObject(OBJ_BUTTON, BUT_STA_ACC1);
createObject(OBJ_BUTTON, BUT_STA_ACC2);
createObject(OBJ_BUTTON, BUT_STA_ACC3);
break;
}
newEvent(OBJ_WIN, WIN_STA_PLAY, EVNT_DRAW);
break;
case WIN_STA_STARS:
createObject(OBJ_WIN, WIN_STA_STARS);
if (staCurrTime > 0) {
staStars++;
createObject(OBJ_FNC, FNC_STA_STAR1);
if (staCurrTime > 10) { // time remaining
staStars++;
createObject(OBJ_FNC, FNC_STA_STAR2);
createObject(OBJ_LABEL, LBL_STA_EXCEL);
}
else {
createObject(OBJ_LABEL, LBL_STA_GREAT);
}
updateStationStars();
}
else {
createObject(OBJ_ICON, ICON_STA_TIMEOUT);
createObject(OBJ_LABEL, LBL_STA_TIMEOUT);
}
newEvent(OBJ_WIN, WIN_STA_STARS, EVNT_DRAW);
break;
case WIN_STA_EDIT:
snprintf(staStartTimeBuf, IP_LNG + 1, "%d", staStartTime);
snprintf(staStatNumBuf, IP_LNG + 1, "%d", staMaxStations);
snprintf(staTurnNumBuf, IP_LNG + 1, "%d", staMaxTurnout);
snprintf(staTurnout1Buf, ADDR_LNG + 1, "%d", staTurnoutAdr1);
snprintf(staTurnout2Buf, ADDR_LNG + 1, "%d", staTurnoutAdr2);
snprintf(staTurnout3Buf, ADDR_LNG + 1, "%d", staTurnoutAdr3);
snprintf(staTurnout4Buf, ADDR_LNG + 1, "%d", staTurnoutAdr4);
for (n = 0; n < 8; n++)
switchData[SW_STA_OR1 + n].state = bitRead(staTurnoutDef, n);
createObject(OBJ_WIN, WIN_STA_EDIT);
createObject(OBJ_LABEL, LBL_STA_STATIONS);
createObject(OBJ_LABEL, LBL_STA_TURNOUTS);
createObject(OBJ_LABEL, LBL_STA_TIME);
createObject(OBJ_LABEL, LBL_STA_DESC);
createObject(OBJ_TXT, TXT_STA_STARTTIME);
createObject(OBJ_TXT, TXT_STA_STATNUM);
createObject(OBJ_TXT, TXT_STA_TURNNUM);
createObject(OBJ_TXT, TXT_STA_TURNOUT1);
createObject(OBJ_TXT, TXT_STA_TURNOUT2);
createObject(OBJ_TXT, TXT_STA_TURNOUT3);
createObject(OBJ_TXT, TXT_STA_TURNOUT4);
createObject(OBJ_BUTTON, BUT_STA_EDIT);
createObject(OBJ_SWITCH, SW_STA_OR1);
createObject(OBJ_SWITCH, SW_STA_OR2);
createObject(OBJ_SWITCH, SW_STA_OR3);
createObject(OBJ_SWITCH, SW_STA_OR4);
createObject(OBJ_SWITCH, SW_STA_INV1);
createObject(OBJ_SWITCH, SW_STA_INV2);
createObject(OBJ_SWITCH, SW_STA_INV3);
createObject(OBJ_SWITCH, SW_STA_INV4);
createObject(OBJ_BUTTON, BUT_STA_STAM);
createObject(OBJ_BUTTON, BUT_STA_STAP);
createObject(OBJ_BUTTON, BUT_STA_TURNM);
createObject(OBJ_BUTTON, BUT_STA_TURNP);
newEvent(OBJ_WIN, WIN_STA_EDIT, EVNT_DRAW);
break;
case WIN_STA_KEYB:
createObject(OBJ_WIN, WIN_STA_KEYB);
createObject(OBJ_KEYBOARD, KEYB_STA);
newEvent(OBJ_WIN, WIN_STA_KEYB, EVNT_DRAW);
break;
}
}
void alertWindow(byte err) {
errType = err;
createObject(OBJ_WIN, WIN_ALERT);
switch (err) {
case ERR_SERV:
createObject(OBJ_ICON, ICON_WARNING);
createObject(OBJ_ICON, ICON_WARNING_ON);
createObject(OBJ_LABEL, LBL_SERVICE);
break;
case ERR_CHG_WIFI:
createObject(OBJ_ICON, ICON_INFO);
createObject(OBJ_LABEL, LBL_CHG_WIFI);
break;
case ERR_FULL:
createObject(OBJ_ICON, ICON_WARNING);
createObject(OBJ_ICON, ICON_WARNING_ON);
createObject(OBJ_LABEL, LBL_STACK_FULL);
break;
case ERR_STOP:
createObject(OBJ_ICON, ICON_ESTOP);
createObject(OBJ_LABEL, LBL_ESTOP);
break;
case ERR_WAIT:
case ERR_CV:
barData[BAR_WAIT].value = 0;
setTimer(TMR_WAIT, 5, TMR_ONESHOT);
if (err == ERR_WAIT)
createObject(OBJ_ICON, ICON_WAIT);
else
createObject(OBJ_ICON, ICON_WAIT_CV);
createObject(OBJ_BAR, BAR_WAIT);
break;
case ERR_ASK_SURE:
createObject(OBJ_ICON, ICON_WARNING);
createObject(OBJ_ICON, ICON_WARNING_ON);
createObject(OBJ_LABEL, LBL_ASK_SURE);
createObject(OBJ_BUTTON, BUT_SURE_OK);
createObject(OBJ_BUTTON, BUT_SURE_CNCL);
break;
}
newEvent(OBJ_WIN, WIN_ALERT, EVNT_DRAW);
}

View File

@@ -0,0 +1,648 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
This software and associated files are a DIY project that is not intended for commercial use.
This software uses libraries with different licenses, follow all their different terms included.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
Sources are only provided for building and uploading to the device.
You are not allowed to modify the source code or fork/publish this project.
Commercial use is forbidden.
*/
////////////////////////////////////////////////////////////
// ***** XPRESSNET LAN SOPORTE *****
////////////////////////////////////////////////////////////
void showErrorXnet() { // muestra pantalla de error
if (csStatus & csEmergencyOff) {
iconData[ICON_POWER].color = COLOR_RED;
setTimer (TMR_POWER, 5, TMR_PERIODIC); // Flash power icon
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
if (isWindow(WIN_STA_PLAY)) {
fncData[FNC_STA_RAYO].state = true;
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
}
}
if (csStatus & csServiceMode) {
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
alertWindow(ERR_SERV);
}
if (csStatus & csEmergencyStop) {
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
alertWindow(ERR_STOP);
}
}
void showNormalOpsXnet() {
stopTimer (TMR_POWER);
iconData[ICON_POWER].color = COLOR_GREEN;
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
if (isWindow(WIN_STA_PLAY)) {
fncData[FNC_STA_RAYO].state = false;
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
}
if (isWindow(WIN_ALERT)) {
switch (errType) {
case ERR_SERV:
case ERR_STOP:
case ERR_CV:
closeWindow(WIN_ALERT);
break;
}
}
}
uint16_t addrXnet(uint16_t adr) {
if (adr > 99) // Comprueba si es direccion larga
adr |= 0xC000;
return adr;
}
bool isRecentMM () { // Comprueba central Multimaus reciente
if ((xnetCS == 0x10) && (highVerMM > 0) && (lowVerMM > 0x02))
return true;
else
return false;
}
////////////////////////////////////////////////////////////
// ***** XPRESSNET LAN MESSAGES *****
////////////////////////////////////////////////////////////
void getStatusXnet () {
headerXN (0x21); // Command station status request (0x21,0x24,0x05)
dataXN (0x24);
sendXN();
}
void getVersionXnet () {
headerXN (0x21); // Command station software version (0x21,0x21,0x00)
dataXN (0x21);
sendXN();
}
void versionMultimaus() {
headerXN (0xF1); // Multimaus software version (0xF1,0x0A,XOR)
dataXN (0x0A);
sendXN();
}
void getResultsXnet() {
headerXN (0x21); // Request for Service Mode results (0x21,0x10,0x31)
dataXN (0x10);
sendXN();
//getResultsSM = false;
}
void resumeOperationsXnet () {
headerXN (0x21); // Resume operations request (0x21,0x81,0xA0)
dataXN (0x81);
sendXN();
}
void emergencyOffXnet() {
headerXN (0x21); // Stop operations request (emergency off)(0x21,0x80,0xA1)
dataXN (0x80);
sendXN();
}
void infoLocomotoraXnet (unsigned int loco) { // Locomotive information request (0xE3,0x00,ADRH,ADRL,XOR)
uint16_t adr;
adr = addrXnet(loco);
headerXN (0xE3);
dataXN (0x00);
dataXN (highByte(adr));
dataXN (lowByte (adr));
sendXN();
if ((xnetVersion > 0x35) || (xnetCS == 0x10)) {
headerXN (0xE3);
if (xnetCS == 0x10)
dataXN (0xF0); // Locomotive function F13..F20 info MM (0xE3,0xF0,ADRH,ADRL,XOR)
else
dataXN (0x09); // Locomotive function F13..F28 info v3.6 (0xE3,0x09,ADRH,ADRL,XOR)
dataXN (highByte(adr));
dataXN (lowByte (adr));
sendXN();
}
getInfoLoco = false;
}
void locoOperationSpeedXnet() { // Locomotive speed and direction operations (0xE4,ID,ADRH,ADRL,SPD,XOR)
uint16_t adr;
adr = addrXnet(locoData[myLocoData].myAddr.address);
headerXN (0xE4);
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 128 steps
dataXN (0x13);
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 28 steps
dataXN (0x12);
}
else {
dataXN (0x10); // 14 steps
}
}
dataXN (highByte(adr));
dataXN (lowByte(adr));
dataXN (locoData[myLocoData].mySpeed | locoData[myLocoData].myDir);
sendXN();
bitClear(locoData[myLocoData].mySteps, 3); // currently operated by me
updateSpeedDir();
}
void funcOperationsXnet (byte fnc) { // Function operation instructions (0xE4,ID,ADRH,ADRL,GRP,XOR)
byte grp, grpID;
uint16_t adr;
adr = addrXnet(locoData[myLocoData].myAddr.address);
if (fnc > 20) {
grpID = 0x28; // F21..F28
grp = ((locoData[myLocoData].myFunc.xFunc[2] >> 5) & 0x07);
grp |= (locoData[myLocoData].myFunc.xFunc[3] << 3);
}
else {
if (fnc > 12) {
if (xnetCS == 0x10)
grpID = 0xF3; // F13..F20 MM (0xE4,0xF3,ADH,ADL,F13F20,XOR)
else
grpID = 0x23; // F13..F20
grp = ((locoData[myLocoData].myFunc.xFunc[1] >> 5) & 0x07);
grp |= (locoData[myLocoData].myFunc.xFunc[2] << 3);
}
else {
if (fnc > 8) {
grpID = 0x22; // F9..F12
grp = ((locoData[myLocoData].myFunc.xFunc[1] >> 1) & 0x0F);
}
else {
if (fnc > 4) {
grpID = 0x21; // F5..F8
grp = ((locoData[myLocoData].myFunc.xFunc[0] >> 5) & 0x07);
if (bitRead(locoData[myLocoData].myFunc.xFunc[1], 0))
grp |= 0x08;
}
else {
grpID = 0x20; // F0..F4
grp = ((locoData[myLocoData].myFunc.xFunc[0] >> 1) & 0x0F);
if (bitRead(locoData[myLocoData].myFunc.xFunc[0], 0))
grp |= 0x10;
}
}
}
}
headerXN (0xE4);
dataXN (grpID);
dataXN (highByte(adr));
dataXN (lowByte(adr));
dataXN (grp);
sendXN();
bitClear(locoData[myLocoData].mySteps, 3); // currently operated by me
}
byte getCurrentStepXnet() {
byte currStep;
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 128 steps -> 0..126
if (locoData[myLocoData].mySpeed > 1)
return (locoData[myLocoData].mySpeed - 1);
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 28 steps -> 0..28 '---04321' -> '---43210'
currStep = (locoData[myLocoData].mySpeed << 1) & 0x1F;
bitWrite(currStep, 0, bitRead(locoData[myLocoData].mySpeed, 4));
if (currStep > 3)
return (currStep - 3);
}
else { // 14 steps -> 0..14
if (locoData[myLocoData].mySpeed > 1)
return (locoData[myLocoData].mySpeed - 1);
}
}
return (0);
}
void setAccessoryXnet (unsigned int direccion, bool activa, byte posicion) { // 1..1024
byte adr, dato;
direccion--; // 000000AAAAAAAABB
adr = (direccion >> 2) & 0x00FF; // AAAAAAAA
dato = ((direccion & 0x0003) << 1) | 0x80; // 1000xBBx
if (posicion > 0)
dato |= 0x01;
if (activa) { // 1000dBBD
dato |= 0x08;
}
headerXN (0x52); // Accessory Decoder operation request (0x52,AAAAAAAA,1000dBBD,XOR)
dataXN (adr);
dataXN (dato);
sendXN();
}
void setTimeXnet(byte hh, byte mm, byte rate) {
clockHour = hh;
clockMin = mm;
clockRate = rate;
if (rate > 0) {
headerXN (0x24); // set clock
dataXN (0x2B);
dataXN (hh);
dataXN (mm);
dataXN (rate);
sendXN ();
/*
headerXN (0x21); // start clock
dataXN (0x2C);
sendXN (0x07);
*/
}
else {
headerXN (0x21); // recommended for rate=0. stop clock
dataXN (0x2D);
sendXN ();
}
}
void readCVXnet (unsigned int adr, byte stepPrg) {
if (!modeProg) { // Read only in Direct mode
if (isRecentMM()) {
headerXN (0x23); // Multimaus v1.03
dataXN (0x15);
adr--;
dataXN (highByte(adr) & 0x03);
dataXN (lowByte(adr));
sendXN();
lastCV = lowByte(adr) + 1;
}
else {
headerXN (0x22);
if (xnetVersion > 0x35)
dataXN (0x18 | (highByte(adr) & 0x03)); // v3.6 & up CV1..CV1024
else
dataXN (0x15); // v3.0 CV1..CV256
dataXN (lowByte(adr));
sendXN();
lastCV = lowByte(adr);
}
getResultsSM = true;
infoTimer = millis();
progStepCV = stepPrg;
//DEBUG_MSG("Read CV %d", adr);
}
}
void writeCVXnet (unsigned int adr, unsigned int data, byte stepPrg) {
uint16_t adrLoco;
if (modeProg) {
headerXN (0xE6); // Operations Mode Programming byte mode write request (0xE6,0x30,ADRH,ADRL,0xEC+C,CV,DATA,XOR)
dataXN (0x30);
adrLoco = addrXnet(locoData[myLocoData].myAddr.address);
dataXN (highByte(adrLoco));
dataXN (lowByte(adrLoco));
adr--;
dataXN (0xEC | (highByte(adr) & 0x03));
dataXN (lowByte(adr));
dataXN(data);
sendXN();
}
else {
if (isRecentMM()) {
headerXN (0x24); // Multimaus v1.03
dataXN (0x16);
adr--;
dataXN (highByte(adr) & 0x03);
dataXN (lowByte(adr));
dataXN(data);
sendXN();
lastCV = lowByte(adr) + 1;
}
else {
headerXN (0x23);
if (xnetVersion > 0x35)
dataXN (0x1C | (highByte(adr) & 0x03)); // v3.6 & up CV1..CV1024
else
dataXN (0x16); // v3.0 CV1..CV256
dataXN (lowByte(adr));
dataXN(data);
sendXN();
lastCV = lowByte(adr);
}
getResultsSM = true;
infoTimer = millis();
}
progStepCV = stepPrg;
//DEBUG_MSG("Write CV%d = %d", adr, data);
}
////////////////////////////////////////////////////////////
// ***** XPRESSNET LAN DECODE *****
////////////////////////////////////////////////////////////
void headerXN (byte header) {
txBytes = HEADER; // coloca header en el buffer
txXOR = header;
txBuffer[txBytes++] = header;
txBuffer[FRAME1] = 0xFF;
txBuffer[FRAME2] = 0xFE;
}
void dataXN (byte dato) {
txBuffer[txBytes++] = dato; // coloca dato en el buffer
txXOR ^= dato;
}
void sendXN () {
bool recvAnswer;
txBuffer[txBytes++] = txXOR; // coloca XOR byte en el buffer
#ifdef DEBUG
Serial.print(F("TX: "));
for (uint8_t x = 0; x < txBytes; x++) {
uint8_t val = txBuffer[x];
if (val < 16)
Serial.print('0');
Serial.print(val, HEX);
Serial.print(' ');
}
Serial.println();
#endif
Client.write((byte *)&txBuffer[FRAME1], txBytes); // envia paquete xpressnet
timeoutXnet = millis();
recvAnswer = false;
while ((millis() - timeoutXnet < 500) && (!recvAnswer)) // wait answer for 500ms
recvAnswer = xnetReceive();
}
bool xnetReceive() {
bool getAnswer;
getAnswer = false;
while (Client.available()) {
rxData = Client.read();
//DEBUG_MSG("%d-%02X", rxIndice, rxData);
switch (rxIndice) {
case FRAME1:
rxBufferXN[FRAME1] = rxData;
if (rxData == 0xFF) // 0xFF... Posible inicio de paquete
rxIndice = FRAME2;
break;
case FRAME2:
rxBufferXN[FRAME2] = rxData;
switch (rxData) {
case 0xFF: // 0xFF 0xFF... FRAME2 puede ser FRAME1 (inicio de otro paquete)
break;
case 0xFE: // 0xFF 0xFE... Inicio paquete correcto
case 0xFD: // 0xFF 0xFD... Inicio paquete de broadcast correcto
rxIndice = HEADER;
rxXOR = 0;
break;
default: // 0xFF 0xXX... No es inicio de paquete
rxIndice = FRAME1;
break;
}
break;
default:
rxBufferXN[rxIndice++] = rxData;
rxXOR ^= rxData;
if (((rxBufferXN[HEADER] & 0x0F) + 4) == rxIndice) { // si se han recibido todos los datos indicados en el paquete
if (rxXOR == 0) { // si el paquete es correcto
rxBytes = rxIndice;
#ifdef DEBUG
Serial.print(F("RX: "));
for (uint8_t x = 0; x < rxBytes; x++) {
uint8_t val = rxBufferXN[x];
if (val < 16)
Serial.print('0');
Serial.print(val, HEX);
Serial.print(' ');
}
Serial.println();
#endif
procesaXN(); // nuevo paquete recibido, procesarlo
getAnswer = true;
}
rxIndice = FRAME1; // proximo paquete
}
break;
}
}
return getAnswer;
}
void processXnet () { // procesa Xpressnet
xnetReceive();
if (getInfoLoco && (csStatus == csNormalOps))
infoLocomotoraXnet(addrXnet(locoData[myLocoData].myAddr.address));
if (millis() - infoTimer > 1000UL) { // Cada segundo
infoTimer = millis();
if (getResultsSM) // Resultados de CV pendientes
getResultsXnet(); // pide resultados
else {
if (bitRead(locoData[myLocoData].mySteps, 3)) // Loco controlada por otro mando
getInfoLoco = true; // pide info locomotora
if (askMultimaus) { // pide info Multimaus
askMultimaus = false;
versionMultimaus();
}
}
}
if (progFinished) { // fin de lectura/programacion CV
progFinished = false;
endProg();
}
if (millis() - pingTimer > XNET_PING_INTERVAL) { // Refresca para mantener la conexion
pingTimer = millis();
getStatusXnet(); // pide estado de la central
}
}
void procesaXN () {
byte n, longitud, modulo, dato;
uint16_t adr;
switch (rxBufferXN[HEADER]) { // segun el header byte
case 0x61:
switch (rxBufferXN[DATA1]) {
case 0x01: // Normal operation resumed (0x61,0x01,0x60)
csStatus = csNormalOps;
showNormalOpsXnet();
break;
case 0x08: // Z21 LAN_X_BC_TRACK_SHORT_CIRCUIT (0x61,0x08,XOR)
case 0x00: // Track power off (0x61,0x00,0x61)
csStatus |= csEmergencyOff;
showErrorXnet();
break;
case 0x02: // Service mode entry (0x61,0x02,0x63)
csStatus |= csServiceMode;
if (!getResultsSM) // show 'Service Mode' if we aren't programming CV
showErrorXnet();
break;
case 0x12: // Programming info. "shortcircuit" (0x61,0x12,XOR)
case 0x13: // Programming info. "Data byte not found" (0x61,0x13,XOR)
CVdata = 0x0600;
getResultsSM = false;
progFinished = true;
break;
case 0x81: // Command station busy response (0x61,0x81,XOR)
break;
case 0x1F: // Programming info. "Command station busy" (0x61,0x1F,XOR)
getResultsSM = true;
infoTimer = millis();
break;
case 0x82: // Instruction not supported by command station (0x61,0x82,XOR)
getResultsSM = false;
if (csStatus & csServiceMode) {
CVdata = 0x0600;
progFinished = true;
}
break;
}
break;
case 0x81:
if (rxBufferXN[DATA1] == 0) { // Emergency Stop (0x81,0x00,0x81)
csStatus |= csEmergencyStop;
showErrorXnet();
}
break;
case 0x62:
if (rxBufferXN[DATA1] == 0x22) { // Command station status indication response (0x62,0x22,DATA,XOR)
csStatus = rxBufferXN[DATA2] & (csEmergencyStop | csEmergencyOff | csServiceMode) ;
if ((xnetCS >= 0x10) && (rxBufferXN[DATA2] & csProgrammingModeActive)) // Multimaus/Z21 Service Mode
csStatus |= csServiceMode;
if (csStatus == csNormalOps)
showNormalOpsXnet();
else
showErrorXnet();
}
break;
case 0x63:
switch (rxBufferXN[DATA1]) {
case 0x03: // Broadcast "Modellzeit" (0x63,0x03,dddhhhhh,s0mmmmmm,XOR) (v4.0)
clockHour = rxBufferXN[DATA2] & 0x1F;
clockMin = rxBufferXN[DATA3] & 0x3F;
clockRate = !bitRead(rxBufferXN[DATA3], 7);
updateFastClock();
break;
case 0x14: // Service Mode response for Direct CV mode (0x63,0x1x,CV,DATA,XOR)
case 0x15:
case 0x16:
case 0x17:
if (rxBufferXN[DATA2] == lastCV) { // comprobar CV (DR5000)
lastCV ^= 0x55;
getResultsSM = false;
CVdata = rxBufferXN[DATA3];
progFinished = true;
}
break;
case 0x21: // Command station software version (0x63,0x21,VER,ID,XOR)
xnetVersion = rxBufferXN[DATA2];
xnetCS = rxBufferXN[DATA3];
if (xnetCS == 0x10)
askMultimaus = true;
break;
}
break;
case 0xE3:
if (rxBufferXN[DATA1] == 0x40) { // Locomotive is being operated by another device response (0xE3,0x40,ADRH,ADRL,XOR)
adr = addrXnet(locoData[myLocoData].myAddr.address);
if ((rxBufferXN[DATA3] == lowByte(adr)) && (rxBufferXN[DATA2] == highByte(adr))) { // DR5000 workaround
bitSet(locoData[myLocoData].mySteps, 3);
}
}
if (rxBufferXN[DATA1] == 0x52) { // Locomotive function info F13..F28 (0xE3,0x52,FNC,FNC,XOR)
locoData[myLocoData].myFunc.Bits &= 0xE0001FFF;
locoData[myLocoData].myFunc.Bits |= ((unsigned long)rxBufferXN[DATA2] << 13);
locoData[myLocoData].myFunc.Bits |= ((unsigned long)rxBufferXN[DATA3] << 21);
updateFuncState(isWindow(WIN_THROTTLE));
}
break;
case 0xE4:
if ((rxBufferXN[DATA1] & 0xF0) == 0x00) { // Locomotive information normal locomotive (0xE4,ID,SPD,FKTA,FKTB,XOR)
locoData[myLocoData].mySteps = rxBufferXN[DATA1]; // '0000BFFF'
locoData[myLocoData].myDir = rxBufferXN[DATA2] & 0x80; // 'RVVVVVVV'
locoData[myLocoData].mySpeed = rxBufferXN[DATA2] & 0x7F;
locoData[myLocoData].myFunc.Bits &= 0xFFFFE000; // '000FFFFF','FFFFFFFF'
locoData[myLocoData].myFunc.Bits |= ((unsigned long)rxBufferXN[DATA4] << 5);
locoData[myLocoData].myFunc.xFunc[0] |= ((rxBufferXN[DATA3] & 0x0F) << 1);
bitWrite(locoData[myLocoData].myFunc.xFunc[0], 0, bitRead(rxBufferXN[DATA3], 4));
updateFuncState(isWindow(WIN_THROTTLE));
if (isWindow(WIN_THROTTLE) || isWindow(WIN_SPEEDO))
updateSpeedHID();
}
break;
case 0xE7:
if ((rxBufferXN[DATA1] & 0xF0) == 0x00) { // Locomotive function info F13..F20 MM (0xE7,STP,SPD,FNC,FNC,FNC,0x00,0x00,XOR)
locoData[myLocoData].mySteps = rxBufferXN[DATA1]; // '0000BFFF'
locoData[myLocoData].myDir = rxBufferXN[DATA2] & 0x80; // 'RVVVVVVV'
locoData[myLocoData].mySpeed = rxBufferXN[DATA2] & 0x7F;
locoData[myLocoData].myFunc.Bits &= 0xFE00000;
locoData[myLocoData].myFunc.Bits |= ((unsigned long)rxBufferXN[DATA5] << 13);
locoData[myLocoData].myFunc.Bits |= ((unsigned long)rxBufferXN[DATA4] << 5);
locoData[myLocoData].myFunc.xFunc[0] |= ((rxBufferXN[DATA3] & 0x0F) << 1);
bitWrite(locoData[myLocoData].myFunc.xFunc[0], 0, bitRead(rxBufferXN[DATA3], 4));
updateFuncState(isWindow(WIN_THROTTLE));
if (isWindow(WIN_THROTTLE) || isWindow(WIN_SPEEDO))
updateSpeedHID();
}
break;
case 0xF3:
if (rxBufferXN[DATA1] == 0x0A) { // Multimaus firmware version (0xF3,0x0A,VERH,VERL,XOR)
highVerMM = rxBufferXN[DATA2];
lowVerMM = rxBufferXN[DATA3];
}
break;
default:
if ((rxBufferXN[HEADER] & 0xF0) == 0x40) { // Feedback broadcast / Accessory decoder information response (0x4X,MOD,DATA,...,XOR)
/*
for (n = HEADER; n < (rxBytes - 2); n += 2) {
modulo = rxBufferXN[n + 1];
dato = rxBufferXN[n + 2];
if (modulo == miModulo) { // Si es mi desvio guarda su posicion
if (bitRead(dato, 4) == bitRead(miAccPos, 1)) {
if (bitRead(miAccPos, 0))
myPosTurnout = (dato >> 2) & 0x03;
else
myPosTurnout = dato & 0x03;
if (scrOLED == SCR_TURNOUT)
updateOLED = true;
}
}
#ifdef USE_AUTOMATION
for (byte n = 0; n < MAX_AUTO_SEQ; n++) {
if ((automation[n].opcode & OPC_AUTO_MASK) == OPC_AUTO_FBK) {
if (modulo == automation[n].param) {
unsigned int nibble = (dato & 0x10) ? 0x0F : 0xF0;
automation[n].value &= nibble;
nibble = (dato & 0x10) ? (dato << 4) : (dato & 0x0F);
automation[n].value |= nibble;
}
}
}
#endif
modulo++;
if (modulo == Shuttle.moduleA) // shuttle contacts
updateShuttleStatus(&Shuttle.statusA, dato);
if (modulo == Shuttle.moduleB)
updateShuttleStatus(&Shuttle.statusB, dato);
}
*/
}
break;
}
}

View File

@@ -0,0 +1,585 @@
/**
* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*
* This software and associated files are a DIY project that is not intended for commercial use.
* This software uses libraries with different licenses, follow all their different terms included.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
*
* Sources are only provided for building and uploading to the device.
* You are not allowed to modify the source code or fork/publish this project.
* Commercial use is forbidden.
*
**/
////////////////////////////////////////////////////////////
// ***** Z21 SOPORTE *****
////////////////////////////////////////////////////////////
void readCVZ21 (unsigned int adr, byte stepPrg) {
if (!modeProg) {
askZ21begin (LAN_X_Header);
askZ21data (0x23);
askZ21data (0x11);
adr--;
askZ21data ((adr >> 8) & 0xFF);
askZ21data (adr & 0xFF);
askZ21xor ();
sendUDP (0x09);
waitResultCV = true;
lastCV = lowByte(adr);
progStepCV = stepPrg;
DEBUG_MSG("Read CV %d", adr + 1);
}
}
void writeCVZ21 (unsigned int adr, unsigned int data, byte stepPrg) {
byte Adr_MSB;
if (modeProg) {
askZ21begin (LAN_X_Header);
askZ21data (0xE6);
askZ21data (0x30);
Adr_MSB = locoData[myLocoData].myAddr.adr[1] & 0x3F;
if (locoData[myLocoData].myAddr.address & 0x3F80)
Adr_MSB |= 0xC0;
askZ21data (Adr_MSB);
askZ21data (locoData[myLocoData].myAddr.adr[0]);
adr--;
askZ21data (0xEC | ((adr >> 8) & 0x03));
askZ21data (adr & 0xFF);
askZ21data (data);
askZ21xor ();
sendUDP (0x0C);
}
else {
askZ21begin (LAN_X_Header);
askZ21data (0x24);
askZ21data (0x12);
adr--;
askZ21data ((adr >> 8) & 0xFF);
askZ21data (adr & 0xFF);
askZ21data (data);
askZ21xor ();
sendUDP (0x0A);
waitResultCV = true;
lastCV = lowByte(adr);
}
progStepCV = stepPrg;
DEBUG_MSG("Write CV%d = %d", adr + 1, data);
}
void showErrorZ21() { // muestra pantalla de error
if (csStatus & csTrackVoltageOff) {
iconData[ICON_POWER].color = COLOR_RED;
setTimer (TMR_POWER, 5, TMR_PERIODIC); // Flash power icon
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
if (isWindow(WIN_STA_PLAY)) {
fncData[FNC_STA_RAYO].state = true;
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
}
}
if (csStatus & csProgrammingModeActive) {
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
alertWindow(ERR_SERV);
}
if (csStatus & csEmergencyStop) {
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
alertWindow(ERR_STOP);
}
}
void showNormalOpsZ21() {
stopTimer (TMR_POWER);
iconData[ICON_POWER].color = COLOR_GREEN;
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
if (isWindow(WIN_STA_PLAY)) {
fncData[FNC_STA_RAYO].state = false;
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
}
if (isWindow(WIN_ALERT)) {
switch (errType) {
case ERR_SERV:
case ERR_STOP:
case ERR_CV:
closeWindow(WIN_ALERT);
break;
}
}
}
void setTimeZ21(byte hh, byte mm, byte rate) {
clockHour = hh;
clockMin = mm;
clockRate = rate;
askZ21begin (LAN_FAST_CLOCK_CONTROL); // set clock
if (rate > 0) {
askZ21data (0x24);
askZ21data (0x2B);
askZ21data (hh);
askZ21data (mm);
askZ21data (rate);
askZ21xor ();
sendUDP (0x0A);
askZ21begin (LAN_FAST_CLOCK_CONTROL);
askZ21data (0x21); // start clock
askZ21data (0x2C);
askZ21xor ();
sendUDP (0x07);
}
else {
askZ21data (0x21); // recommended for rate=0. stop clock
askZ21data (0x2D);
askZ21xor ();
sendUDP (0x07);
}
}
////////////////////////////////////////////////////////////
// ***** Z21 DECODE *****
////////////////////////////////////////////////////////////
void processZ21() {
int len, packetSize;
packetSize = Udp.parsePacket(); // z21 UDP packet
if (packetSize) {
len = Udp.read(packetBuffer, packetSize); // read the packet into packetBufffer
ReceiveZ21 (len, packetBuffer); // decode received packet
}
delay(0);
if (millis() - infoTimer > 1000UL) { // Cada segundo
infoTimer = millis();
pingTimer++;
if (pingTimer >= Z21_PING_INTERVAL) {
pingTimer = 0;
if (!(csStatus & csProgrammingModeActive))
getStatusZ21();
}
/*
battery = ESP.getVcc (); // Read VCC voltage
if (battery < LowBattADC)
lowBATT = true;
*/
}
if (progFinished) { // fin de lectura/programacion CV
progFinished = false;
endProg();
}
delay(0);
}
// -------------------------------------------------------------------------------------
void setBroadcastFlags (unsigned long bFlags) {
askZ21begin (LAN_SET_BROADCASTFLAGS);
askZ21data (bFlags & 0xFF);
askZ21data ((bFlags >> 8) & 0xFF);
askZ21data ((bFlags >> 16) & 0xFF);
askZ21data ((bFlags >> 24) & 0xFF);
sendUDP (0x08);
}
void resumeOperationsZ21 () { // LAN_X_SET_TRACK_POWER_ON
askZ21begin (LAN_X_Header);
askZ21data (0x21);
askZ21data (0x81);
askZ21xor ();
sendUDP (0x07);
}
void emergencyOffZ21() { // LAN_X_SET_TRACK_POWER_OFF
askZ21begin (LAN_X_Header);
askZ21data (0x21);
askZ21data (0x80);
askZ21xor ();
sendUDP (0x07);
}
void getStatusZ21 () {
askZ21begin (LAN_X_Header);
askZ21data (0x21);
askZ21data (0x24);
askZ21xor ();
sendUDP (0x07);
}
void getSerialNumber () {
askZ21begin (LAN_GET_SERIAL_NUMBER);
sendUDP (0x04);
}
void infoLocomotoraZ21 (unsigned int Adr) {
byte Adr_MSB;
askZ21begin (LAN_X_Header);
askZ21data (0xE3);
askZ21data (0xF0);
Adr_MSB = (Adr >> 8) & 0x3F;
if (Adr & 0x3F80)
Adr_MSB |= 0xC0;
askZ21data (Adr_MSB);
askZ21data (Adr & 0xFF);
askZ21xor ();
sendUDP (0x09);
}
void locoOperationSpeedZ21() {
byte Adr_MSB;
DEBUG_MSG("Loco Operations")
askZ21begin (LAN_X_Header);
askZ21data (0xE4);
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 128 steps
askZ21data (0x13);
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 28 steps
askZ21data (0x12);
}
else {
askZ21data (0x10); // 14 steps
}
}
Adr_MSB = locoData[myLocoData].myAddr.adr[1] & 0x3F;
if (locoData[myLocoData].myAddr.address & 0x3F80)
Adr_MSB |= 0xC0;
askZ21data (Adr_MSB);
askZ21data (locoData[myLocoData].myAddr.adr[0]);
askZ21data (locoData[myLocoData].mySpeed | locoData[myLocoData].myDir);
askZ21xor ();
sendUDP (0x0A);
bitClear(locoData[myLocoData].mySteps, 3); // currently operated by me
updateSpeedDir();
}
byte getCurrentStepZ21() {
byte currStep;
DEBUG_MSG("Get Steps: %02X - Speed: %02X", locoData[myLocoData].mySteps, locoData[myLocoData].mySpeed);
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 128 steps -> 0..126
if (locoData[myLocoData].mySpeed > 1)
return (locoData[myLocoData].mySpeed - 1);
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 28 steps -> 0..28 '---04321' -> '---43210'
currStep = (locoData[myLocoData].mySpeed << 1) & 0x1F;
bitWrite(currStep, 0, bitRead(locoData[myLocoData].mySpeed, 4));
if (currStep > 3)
return (currStep - 3);
}
else { // 14 steps -> 0..14
if (locoData[myLocoData].mySpeed > 1)
return (locoData[myLocoData].mySpeed - 1);
}
}
return (0);
}
void funcOperationsZ21 (byte fnc) {
byte Adr_MSB;
askZ21begin (LAN_X_Header);
askZ21data (0xE4);
askZ21data (0xF8);
Adr_MSB = locoData[myLocoData].myAddr.adr[1] & 0x3F;
if (locoData[myLocoData].myAddr.address & 0x3F80)
Adr_MSB |= 0xC0;
askZ21data (Adr_MSB);
askZ21data (locoData[myLocoData].myAddr.adr[0]);
if (bitRead(locoData[myLocoData].myFunc.Bits, fnc))
askZ21data (fnc | 0x40);
else
askZ21data (fnc);
askZ21xor ();
sendUDP (0x0A);
bitClear(locoData[myLocoData].mySteps, 3); // currently operated by me
}
void infoDesvio (unsigned int FAdr) {
FAdr--;
askZ21begin (LAN_X_Header);
askZ21data (0x43);
askZ21data ((FAdr >> 8) & 0xFF);
askZ21data (FAdr & 0xFF);
askZ21xor ();
sendUDP (0x08);
}
void setAccessoryZ21 (unsigned int FAdr, int pair, bool active) {
byte db2;
FAdr--;
askZ21begin (LAN_X_Header);
askZ21data (0x53);
askZ21data ((FAdr >> 8) & 0xFF);
askZ21data (FAdr & 0xFF);
db2 = active ? 0x88 : 0x80;
if (pair > 0)
db2 |= 0x01;
askZ21data (db2); // '10Q0A00P'
askZ21xor ();
sendUDP (0x09);
}
void getFeedbackInfo (byte group) {
askZ21begin (LAN_RMBUS_GETDATA);
askZ21data (group);
sendUDP (0x05);
}
void ReceiveZ21 (int len, byte * packet) { // get UDP packet, maybe more than one!!
int DataLen, isPacket;
#ifdef DEBUG____X
Serial.print("\nRX Length: ");
Serial.println (len);
for (int i = 0; i < len; i++) {
Serial.print(packet[i], HEX);
Serial.print(" ");
}
Serial.println();
#endif
isPacket = 1;
while (isPacket) {
DataLen = (packet[DATA_LENH] << 8) + packet[DATA_LENL];
DecodeZ21 (DataLen, packet);
if (DataLen >= len) {
isPacket = 0;
}
else {
packet = packet + DataLen;
len = len - DataLen;
}
delay(0);
}
}
void DecodeZ21 (int len, byte * packet) { // decode z21 UDP packets
int Header, DataLen;
unsigned int FAdr;
byte group;
Header = (packet[DATA_HEADERH] << 8) + packet[DATA_HEADERL];
switch (Header) {
case LAN_GET_SERIAL_NUMBER:
break;
case LAN_GET_CODE: // FW 1.28
break;
case LAN_GET_HWINFO:
break;
case LAN_GET_BROADCASTFLAGS:
break;
case LAN_GET_LOCOMODE:
break;
case LAN_GET_TURNOUTMODE:
break;
case LAN_RMBUS_DATACHANGED:
/*
if (Shuttle.moduleA > 0) { // only check shuttle contacts
if ((packet[4] == 0x01) && (Shuttle.moduleA > 10))
Shuttle.statusA = packet[Shuttle.moduleA - 6];
if ((packet[4] == 0x00) && (Shuttle.moduleA < 11))
Shuttle.statusA = packet[Shuttle.moduleA + 4];
}
if (Shuttle.moduleB > 0) {
if ((packet[4] == 0x01) && (Shuttle.moduleB > 10))
Shuttle.statusB = packet[Shuttle.moduleB - 6];
if ((packet[4] == 0x00) && (Shuttle.moduleB < 11))
Shuttle.statusB = packet[Shuttle.moduleB + 4];
}
#ifdef USE_AUTOMATION
for (byte n = 0; n < MAX_AUTO_SEQ; n++) {
if ((automation[n].opcode & OPC_AUTO_MASK) == OPC_AUTO_FBK) {
if ((packet[4] == 0x01) && (automation[n].param > 9))
automation[n].value = packet[automation[n].param - 5];
if ((packet[4] == 0x00) && (automation[n].param < 10))
automation[n].value = packet[automation[n].param + 5];
DEBUG_MSG("RBUS %d", automation[n].value)
}
}
#endif
*/
break;
case LAN_SYSTEMSTATE_DATACHANGED:
csStatus = packet[16] & (csEmergencyStop | csTrackVoltageOff | csProgrammingModeActive); // CentralState
if (packet[16] & csShortCircuit)
csStatus |= csTrackVoltageOff;
break;
case LAN_RAILCOM_DATACHANGED:
break;
case LAN_LOCONET_Z21_TX: // a message has been written to the LocoNet bus by the Z21.
case LAN_LOCONET_Z21_RX: // a message has been received by the Z21 from the LocoNet bus.
case LAN_LOCONET_FROM_LAN: // another LAN client has written a message to the LocoNet bus via the Z21.
switch (packet[4]) {
case 0x83:
csStatus = csNormalOps; // OPC_GPON
showNormalOpsZ21();
break;
case 0x82:
csStatus |= csTrackVoltageOff; // OPC_GPOFF
showErrorZ21();
break;
}
break;
case LAN_LOCONET_DETECTOR:
break;
case LAN_FAST_CLOCK_DATA: // fast clock data FW 1.43
if (packet[8] & 0x80) { // Stop flag
clockRate = 0;
}
else {
clockHour = packet[6] & 0x1F;
clockMin = packet[7] & 0x3F;
clockRate = packet[9] & 0x3F;
updateFastClock();
}
DEBUG_MSG("Clock: %d:%d %d", clockHour, clockMin, clockRate);
break;
case LAN_X_Header:
switch (packet[XHEADER]) {
case 0x43: // LAN_X_TURNOUT_INFO
FAdr = (packet[DB0] << 8) + packet[DB1] + 1;
/*
if (FAdr == myTurnout) {
myPosTurnout = packet[DB2] & 0x03;
if (scrOLED == SCR_TURNOUT)
updateOLED = true;
}
*/
break;
case 0x61:
switch (packet[DB0]) {
case 0x01: // LAN_X_BC_TRACK_POWER_ON
csStatus = csNormalOps;
showNormalOpsZ21();
break;
case 0x08: // LAN_X_BC_TRACK_SHORT_CIRCUIT
csStatus |= csShortCircuit;
case 0x00: // LAN_X_BC_TRACK_POWER_OFF
csStatus |= csTrackVoltageOff;
showErrorZ21();
break;
case 0x02: // LAN_X_BC_PROGRAMMING_MODE
csStatus |= csProgrammingModeActive;
if (!waitResultCV)
showErrorZ21();
break;
case 0x12: // LAN_X_CV_NACK_SC
case 0x13: // LAN_X_CV_NACK
CVdata = 0x0600;
waitResultCV = false;
progFinished = true;
break;
case 0x82: // LAN_X_UNKNOWN_COMMAND
break;
}
break;
case 0x62:
switch (packet[DB0]) {
case 0x22: // LAN_X_STATUS_CHANGED
csStatus = packet[DB1] & (csEmergencyStop | csTrackVoltageOff | csProgrammingModeActive);
if (packet[DB1] & csShortCircuit)
csStatus |= csTrackVoltageOff;
if (csStatus == csNormalOps)
showNormalOpsZ21();
else
showErrorZ21();
break;
}
break;
case 0x64:
if (packet[DB0] == 0x14) { // LAN_X_CV_RESULT
if (packet[DB2] == lastCV) {
lastCV ^= 0x55;
CVdata = packet[DB3];
waitResultCV = false;
progFinished = true;
}
}
break;
case 0x81:
if (packet[DB0] == 0) { // LAN_X_BC_STOPPED
csStatus |= csEmergencyStop;
showErrorZ21();
}
break;
case 0xEF: // LAN_X_LOCO_INFO
DEBUG_MSG("RX: Loco data")
FAdr = ((packet[DB0] << 8) + packet[DB1]) & 0x3FFF;
if (FAdr == locoData[myLocoData].myAddr.address) {
locoData[myLocoData].mySteps = packet[DB2]; // '0000BFFF'
locoData[myLocoData].myDir = packet[DB3] & 0x80; // 'RVVVVVVV'
locoData[myLocoData].mySpeed = packet[DB3] & 0x7F;
locoData[myLocoData].myFunc.Bits &= 0xE0000000; // '000FFFFF','FFFFFFFF'
locoData[myLocoData].myFunc.xFunc[0] |= ((packet[DB4] & 0x0F) << 1);
bitWrite(locoData[myLocoData].myFunc.xFunc[0], 0, bitRead(packet[DB4], 4));
locoData[myLocoData].myFunc.Bits |= (unsigned long)(packet[DB5] << 5);
locoData[myLocoData].myFunc.Bits |= (unsigned long)(packet[DB6] << 13);
locoData[myLocoData].myFunc.Bits |= (unsigned long)(packet[DB7] << 21);
updateFuncState(isWindow(WIN_THROTTLE));
if (isWindow(WIN_THROTTLE) || isWindow(WIN_SPEEDO))
updateSpeedHID(); // set encoder
}
break;
}
break;
default: // Header other
break;
}
}
void askZ21begin (unsigned int header) {
OutData[DATA_HEADERL] = header & 0xFF;
OutData[DATA_HEADERH] = header >> 8;
OutPos = XHEADER;
OutXOR = 0;
}
void askZ21data (byte data) {
OutData[OutPos++] = data;
OutXOR ^= data;
}
void askZ21xor () {
OutData[OutPos] = OutXOR;
}
void sendUDP (int len) {
OutData[DATA_LENL] = len & 0xFF;
OutData[DATA_LENH] = len >> 8;
Udp.beginPacket(wifiSetting.CS_IP, z21Port);
Udp.write(OutData, len);
Udp.endPacket();
delay(0);
#ifdef DEBUG___X
Serial.print("TX: ");
for (int i = 0; i < len; i++) {
Serial.print(OutData[i], HEX);
Serial.print(" ");
}
Serial.println();
#endif
}