412 lines
13 KiB
C++
412 lines
13 KiB
C++
|
|
/**
|
|
* @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;
|
|
}
|