1302 lines
51 KiB
C++
1302 lines
51 KiB
C++
|
|
/**
|
|
* @file lnet.ino
|
|
* @brief Loconet over TCP support for PacoMouseCYD throttle.
|
|
* @author F. Cañada
|
|
* @date 2025-2026
|
|
* @copyright https://usuaris.tinet.cat/fmco/
|
|
*
|
|
* This file contains functions for sending and receiving Loconet messages over TCP,
|
|
* handling Loconet protocol details, and managing communication with Loconet command stations.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// API Documentation
|
|
////////////////////////////////////////////////////////////
|
|
/**
|
|
* @brief Sends a Loconet message over TCP.
|
|
* @param Msg Pointer to the Loconet message structure.
|
|
*/
|
|
void lnetSend(lnMsg *Msg);
|
|
|
|
/**
|
|
* @brief Receives and processes Loconet messages from TCP.
|
|
*/
|
|
void lnetReceive();
|
|
|
|
// Add further function documentation here as needed for each public function.
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// End API Documentation
|
|
////////////////////////////////////////////////////////////
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** LOCONET OVER TCP SOPORTE *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
void lnetSend (lnMsg * Msg) {
|
|
byte n, pos, chk, nibble;
|
|
byte msgLng;
|
|
char msgStr[120];
|
|
|
|
chk = 0xFF;
|
|
msgLng = getLnMsgSize(Msg);
|
|
//msgLng = ((Msg->sz.command & 0x60) == 0x60) ? Msg->sz.mesg_size : ((Msg->sz.command & 0x60) >> 4) + 2;
|
|
if (wifiSetting.serverType) {
|
|
strcpy(msgStr, "SEND "); // Loconet over TCP/IP LBServer
|
|
pos = 5;
|
|
for (n = 0; n < msgLng - 1; n++) {
|
|
chk ^= Msg->data[n];
|
|
nibble = Msg->data[n] >> 4;
|
|
msgStr[pos++] = (nibble > 9) ? nibble + 0x37 : nibble + 0x30;
|
|
nibble = Msg->data[n] & 0x0F;
|
|
msgStr[pos++] = (nibble > 9) ? nibble + 0x37 : nibble + 0x30;
|
|
msgStr[pos++] = ' ';
|
|
}
|
|
nibble = chk >> 4;
|
|
msgStr[pos++] = (nibble > 9) ? nibble + 0x37 : nibble + 0x30;
|
|
nibble = chk & 0x0F;
|
|
msgStr[pos++] = (nibble > 9) ? nibble + 0x37 : nibble + 0x30;
|
|
//msgStr[pos++] = '\r';
|
|
msgStr[pos++] = '\n';
|
|
msgStr[pos++] = '\0';
|
|
Client.write(msgStr);
|
|
DEBUG_MSG(msgStr);
|
|
sentOK = false;
|
|
timeoutSend = millis();
|
|
while ((millis() - timeoutSend < 200) && (!sentOK)) // wait confirmation
|
|
lnetReceive();
|
|
}
|
|
else {
|
|
for (n = 0; n < msgLng - 1; n++) // Loconet over TCP/IP Binary
|
|
chk ^= Msg->data[n];
|
|
Msg->data[n] = chk;
|
|
Client.write((byte *)&Msg->data[0], msgLng);
|
|
}
|
|
}
|
|
|
|
|
|
void lnetReceive() {
|
|
char rxByte;
|
|
byte lng;
|
|
while (Client.available()) {
|
|
rxByte = Client.read();
|
|
if (wifiSetting.serverType) { // Loconet over TCP/IP LBServer
|
|
#ifdef DEBUG
|
|
Serial.print(rxByte);
|
|
#endif
|
|
switch (rcvStrPhase) {
|
|
case WAIT_TOKEN: // wait for RECEIVE token
|
|
switch (rxByte) {
|
|
case 'R': // wait for RECEIVE token
|
|
rcvStrPos = 0; // Possible match: RECEIVE. veRsion, bREak, eRRoR Checksum, eRRoR line, eRRoR message / No match: send, sent,timestamp
|
|
rcvStr[rcvStrPos++] = rxByte;
|
|
rcvStrPhase = RECV_TOKEN;
|
|
break;
|
|
case 'S': // wait for SENT token
|
|
rcvStrPos = 0; // Possible match: Send, Sent, timeStamp, verSion, error checkSum, error meSSage / No match: receive, break, error line
|
|
rcvStr[rcvStrPos++] = rxByte;
|
|
rcvStrPhase = SENT_TOKEN;
|
|
break;
|
|
}
|
|
break;
|
|
case SENT_TOKEN:
|
|
switch (rxByte) {
|
|
case 'E': // SENT valid characters
|
|
case 'N':
|
|
case 'T':
|
|
case ' ':
|
|
rcvStr[rcvStrPos++] = rxByte;
|
|
if (rcvStrPos == 5) {
|
|
if (! strncmp(rcvStr, "SENT ", 5)) {
|
|
rcvStrPhase = SENT_PARAM;
|
|
rcvStrPos = 0;
|
|
RecvPacket.data[rcvStrPos] = 0;
|
|
}
|
|
else
|
|
rcvStrPhase = WAIT_TOKEN;
|
|
}
|
|
break;
|
|
default: // SENT invalid characters
|
|
rcvStrPhase = WAIT_TOKEN;
|
|
break;
|
|
}
|
|
break;
|
|
case SENT_PARAM:
|
|
if ((rxByte == '\n') || (rxByte == '\r')) {
|
|
//DEBUG_MSG("SENT token detected!")
|
|
sentOK = true;
|
|
rcvStrPhase = WAIT_TOKEN;
|
|
}
|
|
break;
|
|
case RECV_TOKEN:
|
|
switch (rxByte) {
|
|
case 'E': // RECEIVE valid characters
|
|
case 'C':
|
|
case 'I':
|
|
case 'V':
|
|
case ' ':
|
|
rcvStr[rcvStrPos++] = rxByte;
|
|
if (rcvStrPos == 8) {
|
|
if (! strncmp(rcvStr, "RECEIVE ", 8)) {
|
|
rcvStrPhase = RECV_PARAM;
|
|
rcvStrPos = 0;
|
|
RecvPacket.data[rcvStrPos] = 0;
|
|
//DEBUG_MSG(" - RECEIVE token detected!")
|
|
}
|
|
else
|
|
rcvStrPhase = WAIT_TOKEN;
|
|
}
|
|
break;
|
|
default: // RECEIVE invalid characters
|
|
rcvStrPhase = WAIT_TOKEN;
|
|
break;
|
|
}
|
|
break;
|
|
case RECV_PARAM:
|
|
switch (rxByte) {
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
RecvPacket.data[rcvStrPos] = (RecvPacket.data[rcvStrPos] << 4) + (rxByte & 0x0F);
|
|
break;
|
|
case 'A':
|
|
case 'B':
|
|
case 'C':
|
|
case 'D':
|
|
case 'E':
|
|
case 'F':
|
|
case 'a': // workaround for LocoNetEtherBuffer example of Loconet.h library.
|
|
case 'b':
|
|
case 'c':
|
|
case 'd':
|
|
case 'e':
|
|
case 'f':
|
|
RecvPacket.data[rcvStrPos] = (RecvPacket.data[rcvStrPos] << 4) + (rxByte & 0x0F) + 9;
|
|
break;
|
|
case '\n':
|
|
rcvStrPhase = WAIT_TOKEN;
|
|
//DEBUG_MSG("Message received! CHK: %02X", chk)
|
|
lnetDecode(&RecvPacket);
|
|
/*
|
|
#ifdef DEBUG
|
|
rcvStrPos = ((RecvPacket.sz.command & 0x60) == 0x60) ? RecvPacket.sz.mesg_size : ((RecvPacket.sz.command & 0x60) >> 4) + 2; // imprime paquete
|
|
DEBUG_MSG("LN Lng: % d", rcvStrPos)
|
|
Serial.print(F("RX: "));
|
|
for (uint8_t x = 0; x < rcvStrPos; x++) {
|
|
uint8_t val = RecvPacket.data[x];
|
|
if (val < 16)
|
|
Serial.print('0');
|
|
Serial.print(val, HEX);
|
|
Serial.print(' ');
|
|
}
|
|
Serial.println();
|
|
#endif
|
|
*/
|
|
break;
|
|
case ' ':
|
|
rcvStrPos++;
|
|
if (rcvStrPos > 31)
|
|
rcvStrPhase = WAIT_TOKEN; // message too long, discard
|
|
else
|
|
RecvPacket.data[rcvStrPos] = 0;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else { // Loconet over TCP/IP Binary
|
|
if (rxByte & 0x80) {
|
|
rcvStrPos = 1;
|
|
RecvPacket.sz.command = rxByte;
|
|
}
|
|
else {
|
|
RecvPacket.data[rcvStrPos++] = rxByte;
|
|
lng = ((RecvPacket.sz.command & 0x60) == 0x60) ? RecvPacket.sz.mesg_size : ((RecvPacket.sz.command & 0x60) >> 4) + 2;
|
|
if (lng > sizeof(RecvPacket)) // discard message too long
|
|
rcvStrPos = 2;
|
|
if (rcvStrPos == lng) {
|
|
//DEBUG_MSG("Message received!")
|
|
lnetDecode(&RecvPacket);
|
|
#ifdef DEBUG
|
|
//DEBUG_MSG("LN Lng: % d Pkt: % d", rcvStrPos, lng)
|
|
Serial.print(F("RX: "));
|
|
for (uint8_t x = 0; x < lng; x++) {
|
|
uint8_t val = RecvPacket.data[x];
|
|
if (val < 16)
|
|
Serial.print('0');
|
|
Serial.print(val, HEX);
|
|
Serial.print(' ');
|
|
}
|
|
Serial.println();
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** LOCONET SOPORTE *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
uint8_t getLnMsgSize(volatile lnMsg* Msg) {
|
|
return ((Msg->sz.command & 0x60) == 0x60) ? Msg->sz.mesg_size : ((Msg->sz.command & 0x60) >> 4) + 2;
|
|
}
|
|
|
|
|
|
void send4byteMsg (byte opcode, byte slot, byte val) { // Envia un mensaje de 4 bytes
|
|
SendPacket.data[0] = opcode;
|
|
SendPacket.data[1] = slot;
|
|
SendPacket.data[2] = val;
|
|
lnetSend(&SendPacket);
|
|
}
|
|
|
|
|
|
void nullMoveSlot (byte slot) {
|
|
send4byteMsg(OPC_MOVE_SLOTS, slot, slot); // NULL MOVE, expect <E7>SLOT READ or LACK
|
|
pingTimer = millis();
|
|
}
|
|
|
|
|
|
void resumeOperationsLnet() {
|
|
SendPacket.data[0] = OPC_GPON; // Track Power On
|
|
lnetSend(&SendPacket);
|
|
}
|
|
|
|
|
|
void emergencyOffLnet() {
|
|
SendPacket.data[0] = OPC_GPOFF; // Track Power Off
|
|
lnetSend(&SendPacket);
|
|
}
|
|
|
|
|
|
void infoLocomotoraLnet (unsigned int address) {
|
|
byte adrH, adrL;
|
|
adrH = (address >> 7) & 0x7F;
|
|
adrL = address & 0x7F;
|
|
send4byteMsg(OPC_LOCO_ADR, adrH, adrL); // REQ loco ADR, expect <E7>SLOT READ
|
|
if (typeCmdStation == CMD_DR)
|
|
send4byteMsg(OPC_LOCO_ADR_UHLI, adrH, adrL); // REQ loco ADR, expect <E6>SLOT READ UHLI
|
|
}
|
|
|
|
|
|
byte getMaxStepLnet() {
|
|
byte stp;
|
|
stp = stepsLN[mySlot.state & 0x07];
|
|
return stp;
|
|
}
|
|
|
|
|
|
byte getCurrentStepLnet() {
|
|
byte maxStep, calcStep;
|
|
maxStep = getMaxStepLnet();
|
|
if (locoData[myLocoData].mySpeed > 1) {
|
|
if (maxStep == 128) { // 128 steps -> 0..126
|
|
return (locoData[myLocoData].mySpeed - 1);
|
|
}
|
|
else {
|
|
if (maxStep == 28) { // 28 steps -> 0..28
|
|
calcStep = ((locoData[myLocoData].mySpeed - 2) << 1) / 9;
|
|
return (calcStep + 1);
|
|
}
|
|
else { // 14 steps -> 0..14
|
|
calcStep = (locoData[myLocoData].mySpeed - 2) / 9;
|
|
return (calcStep + 1);
|
|
}
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
void liberaSlot() {
|
|
if (mySlot.num > 0) {
|
|
send4byteMsg(OPC_SLOT_STAT1, mySlot.num, mySlot.state & ~(STAT1_SL_BUSY)); //
|
|
DEBUG_MSG("Liberando slot %d", mySlot.num);
|
|
}
|
|
mySlot.num = 0;
|
|
locoData[myLocoData].myFunc.Bits = 0;
|
|
}
|
|
|
|
void locoOperationSpeedLnet() { // Envia velocidad
|
|
if (mySlot.num > 0) {
|
|
send4byteMsg(OPC_LOCO_SPD, mySlot.num, locoData[myLocoData].mySpeed);
|
|
//DEBUG_MSG("Operation Speed: %d", mySpeed);
|
|
}
|
|
}
|
|
|
|
|
|
void changeDirectionF0F4Lnet() { // Envia sentido y F0..F4
|
|
byte fnc;
|
|
if (mySlot.num > 0) {
|
|
fnc = ((locoData[myLocoData].myFunc.xFunc[0] >> 1) & 0x0F);
|
|
if (bitRead(locoData[myLocoData].myFunc.xFunc[0], 0))
|
|
fnc |= 0x10;
|
|
if (!(locoData[myLocoData].myDir & 0x80))
|
|
fnc |= 0x20;
|
|
send4byteMsg(OPC_LOCO_DIRF, mySlot.num, fnc);
|
|
//DEBUG_MSG("Dir: %d FncF0F4 %d", myDir >> 7, fnc & 0x1F);
|
|
}
|
|
}
|
|
|
|
|
|
void funcOperationsLnet (uint8_t fnc) {
|
|
uint8_t data;
|
|
if (mySlot.num > 0) {
|
|
switch (fnc) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
changeDirectionF0F4Lnet(); // LNPE
|
|
break;
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
data = ((locoData[myLocoData].myFunc.xFunc[0] >> 5) & 0x07); // LNPE
|
|
if (bitRead(locoData[myLocoData].myFunc.xFunc[1], 0))
|
|
data |= 0x08;
|
|
send4byteMsg(OPC_LOCO_SND, mySlot.num, data);
|
|
//DEBUG_MSG("FncF5F8 %d", data);
|
|
break;
|
|
default:
|
|
switch (typeCmdStation) {
|
|
case CMD_DR: // DR5000 / IB II
|
|
if (fnc > 12) {
|
|
changeFuncULHI(fnc);
|
|
}
|
|
else {
|
|
data = ((locoData[myLocoData].myFunc.xFunc[1] >> 1) & 0x0F);
|
|
send4byteMsg(OPC_LOCO_F9F12, mySlot.num, data);
|
|
//DEBUG_MSG("DR5000-FncF9F12 %d", data);
|
|
}
|
|
break;
|
|
case CMD_ULI: // Uhlenbrock
|
|
changeFuncULHI(fnc);
|
|
break;
|
|
case CMD_DIG: // Digitrax
|
|
changeFuncIMM(fnc);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void changeFuncULHI(uint8_t fnc) {
|
|
uint8_t arg4;
|
|
SendPacket.data[0] = OPC_UHLI_FUN; // Uhlenbrock
|
|
SendPacket.data[1] = 0x20;
|
|
SendPacket.data[2] = mySlot.num;
|
|
switch (fnc) { // ---87654 32109876 54321098 76543210
|
|
case 12:
|
|
case 20:
|
|
case 28:
|
|
SendPacket.data[3] = 0x05;
|
|
arg4 = bitRead(locoData[myLocoData].myFunc.Bits, 12) << 4;
|
|
arg4 |= bitRead(locoData[myLocoData].myFunc.Bits, 20) << 5;
|
|
arg4 |= bitRead(locoData[myLocoData].myFunc.Bits, 28) << 6;
|
|
SendPacket.data[4] = arg4 & 0x70; // F12,F20,F28
|
|
break;
|
|
case 13:
|
|
case 14:
|
|
case 15:
|
|
case 16:
|
|
case 17:
|
|
case 18:
|
|
case 19:
|
|
SendPacket.data[3] = 0x08;
|
|
arg4 = ((locoData[myLocoData].myFunc.xFunc[1] >> 5) & 0x07);
|
|
arg4 |= (locoData[myLocoData].myFunc.xFunc[2] << 3);
|
|
SendPacket.data[4] = arg4 & 0x7F; // F13..F19
|
|
break;
|
|
case 21:
|
|
case 22:
|
|
case 23:
|
|
case 24:
|
|
case 25:
|
|
case 26:
|
|
case 27:
|
|
SendPacket.data[3] = 0x09;
|
|
arg4 = ((locoData[myLocoData].myFunc.xFunc[2] >> 5) & 0x07);
|
|
arg4 |= (locoData[myLocoData].myFunc.xFunc[3] << 3);
|
|
SendPacket.data[4] = arg4 & 0x7F; // F21..F27
|
|
break;
|
|
default:
|
|
SendPacket.data[3] = 0x07;
|
|
arg4 = (uint8_t)(locoData[myLocoData].myFunc.Bits >> 5);
|
|
SendPacket.data[4] = arg4 & 0x7F; // F5..F11
|
|
break;
|
|
}
|
|
lnetSend(&SendPacket);
|
|
}
|
|
|
|
|
|
void changeFuncIMM(uint8_t fnc) {
|
|
uint8_t data, adrH, adrL;
|
|
adrH = (locoData[myLocoData].myAddr.address >> 7) & 0x7F; // Digitrax
|
|
adrL = locoData[myLocoData].myAddr.adr[0] & 0x7F;
|
|
SendPacket.data[0] = OPC_IMM_PACKET;
|
|
SendPacket.data[1] = 0x0B;
|
|
SendPacket.data[2] = 0x7F;
|
|
if (fnc > 12) {
|
|
if (fnc > 20)
|
|
data = (uint8_t)(locoData[myLocoData].myFunc.Bits >> 21) & 0xFF;
|
|
else
|
|
data = (uint8_t)(locoData[myLocoData].myFunc.Bits >> 13) & 0xFF;
|
|
if (adrH) {
|
|
SendPacket.data[3] = 0x44; // REPS: D4,5,6=#IM bytes,D3=0(reserved); D2,1,0=repeat CNT
|
|
SendPacket.data[4] = (bitRead(locoData[myLocoData].myAddr.adr[0], 7)) ? 0x07 : 0x05; // DHI
|
|
if (bitRead(data, 7))
|
|
SendPacket.data[4] |= 0x08;
|
|
SendPacket.data[5] = locoData[myLocoData].myAddr.adr[1] | 0x40; // IM1
|
|
SendPacket.data[6] = adrL; // IM2
|
|
if (fnc > 20)
|
|
SendPacket.data[7] = 0x5F; // IM3
|
|
else
|
|
SendPacket.data[7] = 0x5E; // IM3
|
|
SendPacket.data[8] = data & 0x7F; // IM4
|
|
}
|
|
else {
|
|
SendPacket.data[3] = 0x34; // REPS: D4,5,6=#IM bytes,D3=0(reserved); D2,1,0=repeat CNT
|
|
SendPacket.data[4] = (bitRead(data, 7)) ? 0x06 : 0x02; // DHI
|
|
SendPacket.data[5] = adrL; // IM1
|
|
if (fnc > 20)
|
|
SendPacket.data[6] = 0x5F; // IM2 [110-11111]
|
|
else
|
|
SendPacket.data[6] = 0x5E; // IM2 [110-11110]
|
|
SendPacket.data[7] = data & 0x7F; // IM3
|
|
SendPacket.data[8] = 0x00; // IM4
|
|
}
|
|
SendPacket.data[9] = 0x00; // IM5
|
|
lnetSend(&SendPacket);
|
|
}
|
|
else {
|
|
data = (uint8_t)(locoData[myLocoData].myFunc.Bits >> 9) & 0x0F;
|
|
if (adrH) {
|
|
SendPacket.data[3] = 0x34; // REPS: D4,5,6=#IM bytes,D3=0(reserved); D2,1,0=repeat CNT
|
|
SendPacket.data[4] = (bitRead(locoData[myLocoData].myAddr.adr[0], 7)) ? 0x07 : 0x05; // DHI
|
|
SendPacket.data[5] = locoData[myLocoData].myAddr.adr[1] | 0x40; // IM1
|
|
SendPacket.data[6] = adrL; // IM2
|
|
SendPacket.data[7] = 0x20 | data; // IM3 [101SDDDD]
|
|
}
|
|
else {
|
|
SendPacket.data[3] = 0x24;
|
|
SendPacket.data[4] = 0x02;
|
|
SendPacket.data[5] = adrL;
|
|
SendPacket.data[6] = 0x20 | data;
|
|
SendPacket.data[7] = 0x00;
|
|
}
|
|
SendPacket.data[8] = 0x00; // IM4
|
|
SendPacket.data[9] = 0x00; // IM5
|
|
lnetSend(&SendPacket);
|
|
//DEBUG_MSG("Digitrax-FncF9F12 %d", fnc);
|
|
}
|
|
}
|
|
|
|
|
|
void lnetRequestSwitch (unsigned int addr, byte output, byte dir) {
|
|
byte adrH, adrL;
|
|
adrH = ((--addr >> 7) & 0x0F);
|
|
adrL = addr & 0x7F;
|
|
if (output)
|
|
adrH |= OPC_SW_REQ_OUT; // output on
|
|
if (dir)
|
|
adrH |= OPC_SW_REQ_DIR; // direction closed/thrown
|
|
send4byteMsg(OPC_SW_REQ, adrL, adrH);
|
|
}
|
|
|
|
void getTypeCS() {
|
|
send4byteMsg(OPC_RQ_SL_DATA, SLOT_0, 0); // Read slot 0 to get TRK and identify command station
|
|
}
|
|
|
|
|
|
void setTimeLnet(byte hh, byte mm, byte rate) {
|
|
clockHour = hh;
|
|
clockMin = mm;
|
|
clockRate = rate;
|
|
SendPacket.data[0] = OPC_WR_SL_DATA; // Fast clock
|
|
SendPacket.data[1] = 0x0E;
|
|
SendPacket.data[2] = SLOT_FC;
|
|
SendPacket.data[3] = rate; // clock rate
|
|
SendPacket.data[4] = 0x7F;
|
|
SendPacket.data[5] = 0x79;
|
|
SendPacket.data[6] = mm + 0x43;
|
|
SendPacket.data[7] = mySlot.trk; // trk
|
|
SendPacket.data[8] = hh + 0x68;
|
|
SendPacket.data[9] = 0x00; // day
|
|
SendPacket.data[10] = 0x40; // control
|
|
SendPacket.data[11] = 0x00; // ID
|
|
SendPacket.data[12] = 0x00;
|
|
lnetSend(&SendPacket);
|
|
}
|
|
|
|
|
|
void showTrkLnet(uint8_t trk) {
|
|
if (lastTrk != trk) {
|
|
lastTrk = trk;
|
|
if (trk & GTRK_POWER) { // Normal power on
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
iconData[ICON_POWER].color = COLOR_RED; // Power off
|
|
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 (trk & GTRK_PROG_BUSY) { // Programming track busy
|
|
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
|
|
alertWindow(ERR_SERV);
|
|
}
|
|
if (!(trk & GTRK_IDLE)) { // broadcasting an emergency stop?
|
|
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
|
|
alertWindow(ERR_STOP);
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** LOCONET DECODE *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
void lnetDecode (lnMsg * LnPacket) {
|
|
unsigned int adr;
|
|
uint8_t slotStatus, i, n, msgLen;
|
|
|
|
#ifdef DEBUG
|
|
msgLen = getLnMsgSize(LnPacket); // imprime paquete
|
|
Serial.print(F("Decoding: "));
|
|
for (uint8_t x = 0; x < msgLen; x++) {
|
|
uint8_t val = LnPacket->data[x];
|
|
if (val < 16)
|
|
Serial.print('0');
|
|
Serial.print(val, HEX);
|
|
Serial.print(' ');
|
|
}
|
|
Serial.println();
|
|
#endif
|
|
|
|
msgLen = getLnMsgSize(LnPacket); // calcula checksum
|
|
i = 0;
|
|
for (n = 0; n < msgLen; n++)
|
|
i ^= LnPacket->data[n];
|
|
DEBUG_MSG("CHK: %02X", i)
|
|
if (i != 0xFF) // comprueba checksum
|
|
return;
|
|
|
|
switch (LnPacket->sz.command) {
|
|
case OPC_LOCO_SPD:
|
|
if (LnPacket->lsp.slot == mySlot.num) { // cambio en la velocidad
|
|
locoData[myLocoData].mySpeed = LnPacket->lsp.spd;
|
|
pingTimer = millis();
|
|
if (isWindow(WIN_THROTTLE) || isWindow(WIN_SPEEDO))
|
|
updateSpeedHID();
|
|
}
|
|
break;
|
|
case OPC_LOCO_DIRF:
|
|
if (LnPacket->ldf.slot == mySlot.num) { // cambio en el sentido o F0..F4
|
|
locoData[myLocoData].myDir = (((LnPacket->ldf.dirf << 2) ^ 0x80) & 0x80);
|
|
locoData[myLocoData].myFunc.xFunc[0] &= 0xE0;
|
|
locoData[myLocoData].myFunc.xFunc[0] |= ((LnPacket->ldf.dirf & 0x0F) << 1);
|
|
if (LnPacket->ldf.dirf & 0x10)
|
|
locoData[myLocoData].myFunc.xFunc[0] |= 0x01;
|
|
updateFuncState(isWindow(WIN_THROTTLE));
|
|
if (isWindow(WIN_THROTTLE) || isWindow(WIN_SPEEDO))
|
|
updateSpeedDir();
|
|
}
|
|
break;
|
|
case OPC_LOCO_SND:
|
|
if (LnPacket->ls.slot == mySlot.num) { // cambio en F5..F8
|
|
locoData[myLocoData].myFunc.xFunc[0] &= 0x1F;
|
|
locoData[myLocoData].myFunc.xFunc[1] &= 0xFE;
|
|
locoData[myLocoData].myFunc.xFunc[0] |= (LnPacket->ls.snd << 5);
|
|
if (LnPacket->ls.snd & 0x08)
|
|
locoData[myLocoData].myFunc.xFunc[1] |= 0x01;
|
|
updateFuncState(isWindow(WIN_THROTTLE));
|
|
}
|
|
break;
|
|
case OPC_LOCO_F9F12: // DR5000 - Intellibox-II
|
|
if (LnPacket->ls.slot == mySlot.num) { // cambio en F9..F12
|
|
locoData[myLocoData].myFunc.xFunc[1] &= 0xE1;
|
|
locoData[myLocoData].myFunc.xFunc[1] |= (LnPacket->ls.snd << 1);
|
|
updateFuncState(isWindow(WIN_THROTTLE));
|
|
}
|
|
break;
|
|
case OPC_UHLI_FUN:
|
|
if ((LnPacket->data[1] == 0x20) && (LnPacket->data[2] == mySlot.num)) { // Used by Intellibox-I for F0-F28 and Intellibox-II for F13-F28
|
|
switch (LnPacket->data[3]) {
|
|
case 0x05:
|
|
updateUhliF12F20F28(LnPacket->data[4]);
|
|
break;
|
|
case 0x06:
|
|
updateUhliF0F4(LnPacket->data[4]);
|
|
break;
|
|
case 0x07:
|
|
updateUhliF5F11(LnPacket->data[4]);
|
|
break;
|
|
|
|
case 0x08:
|
|
updateUhliF13F19(LnPacket->data[4]);
|
|
break;
|
|
case 0x09:
|
|
updateUhliF21F27(LnPacket->data[4]);
|
|
break;
|
|
}
|
|
/*
|
|
if (LnPacket->data[3] == 0x07) { // F5..F11 // Used only by Intellibox-I ("one") version 2.x
|
|
locoData[myLocoData].myFunc.Bits &= 0xFFFFF01F;
|
|
locoData[myLocoData].myFunc.Bits |= ((unsigned long)(LnPacket->data[4] & 0x7F) << 5);
|
|
}
|
|
if (LnPacket->data[3] == 0x05) { // F12,F20,F28 // Common to Intellibox-I and -II
|
|
bitWrite(locoData[myLocoData].myFunc.Bits, 12, bitRead(LnPacket->data[4], 4));
|
|
bitWrite(locoData[myLocoData].myFunc.Bits, 20, bitRead(LnPacket->data[4], 5));
|
|
bitWrite(locoData[myLocoData].myFunc.Bits, 28, bitRead(LnPacket->data[4], 6));
|
|
}
|
|
if (LnPacket->data[3] == 0x08) { // F13..F19 // Common to Intellibox-I and -II
|
|
locoData[myLocoData].myFunc.Bits &= 0xFFF01FFF;
|
|
locoData[myLocoData].myFunc.Bits |= ((unsigned long)(LnPacket->data[4] & 0x7F) << 13); // ---87654 32109876 54321098 76543210
|
|
}
|
|
if (LnPacket->data[3] == 0x09) { // F21..F27 // Common to Intellibox-I and -II
|
|
locoData[myLocoData].myFunc.Bits &= 0xF01FFFFF;
|
|
locoData[myLocoData].myFunc.Bits |= ((unsigned long)(LnPacket->data[4] & 0x7F) << 21);
|
|
}
|
|
if (LnPacket->data[3] == 0x06) { // F0..F4 // Used only by Intellibox-I ("one") version 2.x
|
|
locoData[myLocoData].myFunc.Bits &= 0xFFFFFFE0;
|
|
locoData[myLocoData].myFunc.Bits |= ((unsigned long)(LnPacket->data[4] & 0x0F) << 1);
|
|
bitWrite(locoData[myLocoData].myFunc.Bits, 0, bitRead(LnPacket->data[4], 4));
|
|
*/
|
|
updateFuncState(isWindow(WIN_THROTTLE));
|
|
}
|
|
break;
|
|
|
|
|
|
|
|
case OPC_IMM_PACKET:
|
|
if ((LnPacket->sp.mesg_size == 0x0B) && (LnPacket->sp.val7f == 0x7F)) {
|
|
if (bitRead(LnPacket->sp.dhi, 0)) {
|
|
if (bitRead(LnPacket->sp.im1, 6)) { // im1:1 1LLLLLL im2: L LLLLLLL
|
|
adr = (bitRead(LnPacket->sp.dhi, 1)) ? LnPacket->sp.im2 | 0x80 : LnPacket->sp.im2;
|
|
adr |= ((LnPacket->sp.im1 & 0x3F) << 8);
|
|
if (adr == locoData[myLocoData].myAddr.address) {
|
|
if (bitRead(LnPacket->sp.dhi, 2)) { // im3: 1 XXXFFFF
|
|
i = (bitRead(LnPacket->sp.dhi, 3)) ? LnPacket->sp.im4 | 0x80 : LnPacket->sp.im4;
|
|
decodeFuncIMM (LnPacket->sp.im3, i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (LnPacket->sp.im1 == locoData[myLocoData].myAddr.address) { // im1: 0 LLLLLLL
|
|
if (bitRead(LnPacket->sp.dhi, 1)) { // im2: 1 XXXFFFF
|
|
i = (bitRead(LnPacket->sp.dhi, 2)) ? LnPacket->sp.im3 | 0x80 : LnPacket->sp.im3;
|
|
decodeFuncIMM (LnPacket->sp.im2, i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case OPC_GPON:
|
|
if (!lnetProg) { // workaround for Daisy II WLAN
|
|
bitSet(mySlot.trk, 0);
|
|
showTrkLnet(mySlot.trk);
|
|
}
|
|
break;
|
|
case OPC_GPOFF:
|
|
if (!lnetProg) { // workaround for Daisy II WLAN
|
|
bitClear(mySlot.trk, 0);
|
|
showTrkLnet(mySlot.trk);
|
|
}
|
|
break;
|
|
case OPC_SL_RD_DATA: // informacion de un slot
|
|
adr = (LnPacket->sd.adr2 << 7) + LnPacket->sd.adr;
|
|
/*
|
|
if (doDispatchGet && (LnPacket->sd.slot < 0x79)) { // valid slot 1..120
|
|
locoData[myLocoData].myAddr.address = adr;
|
|
}
|
|
*/
|
|
if ((adr == locoData[myLocoData].myAddr.address) && (LnPacket->sd.slot < 0x79) && (!doDispatchPut)) { // valid slot 1..120
|
|
DEBUG_MSG("Slot read for ADDR:%d", adr);
|
|
mySlot.num = LnPacket->sd.slot; // es mi locomotora, guarda slot
|
|
mySlot.state = LnPacket->sd.stat;
|
|
mySlot.trk = LnPacket->sd.trk;
|
|
slotStatus = (LnPacket->sd.stat >> 4) & 0x03;
|
|
//DEBUG_MSG("Slot % d STATUS: % d", mySlot.num, slotStatus);
|
|
locoData[myLocoData].mySpeed = LnPacket->sd.spd; // actualiza velocidad
|
|
locoData[myLocoData].myDir = (((LnPacket->sd.dirf << 2) ^ 0x80) & 0x80); // actualiza sentido (LnPacket->sd.dirf << 2) & 0x80;
|
|
updateUhliF0F4(LnPacket->sd.dirf);
|
|
/*
|
|
locoData[myLocoData].myFunc.xFunc[0] |= ((LnPacket->sd.dirf & 0x0F) << 1);
|
|
locoData[myLocoData].myFunc.xFunc[0] |= ((LnPacket->sd.dirf >> 4) & 0x01);
|
|
*/
|
|
locoData[myLocoData].myFunc.Bits &= 0xFFFFFE1F; // F5..F8
|
|
locoData[myLocoData].myFunc.xFunc[0] |= ((LnPacket->sd.snd & 0x07) << 5);
|
|
bitWrite(locoData[myLocoData].myFunc.Bits, 8, bitRead(LnPacket->sd.snd, 3));
|
|
if (slotStatus != STAT_IN_USE) {
|
|
nullMoveSlot (mySlot.num); // si el slot no se usa, tomo el control para que refresque
|
|
}
|
|
if (doDispatchGet) {
|
|
/*
|
|
doDispatchGet = false;
|
|
checkLocoAddress();
|
|
pushLoco(locoData[myLocoData].myAddr.address); // guarda en stack
|
|
updateEEPROM (EE_ADRH, locoData[myLocoData].myAddr.adr[1]); // guarda nueva direccion en EEPROM
|
|
updateEEPROM (EE_ADRL, locoData[myLocoData].myAddr.adr[0]);
|
|
optOLED = OPT_SPEED;
|
|
enterMenuOption();
|
|
*/
|
|
}
|
|
else {
|
|
updateFuncState(isWindow(WIN_THROTTLE));
|
|
if (isWindow(WIN_THROTTLE) || isWindow(WIN_SPEEDO))
|
|
updateSpeedHID(); // set encoder
|
|
}
|
|
}
|
|
|
|
if (LnPacket->sd.slot == SLOT_FC) { // FAST Clock
|
|
if (LnPacket->fc.clk_cntrl & 0x40) { // bit 6 = 1; data is valid clock info
|
|
setFastClock(LnPacket);
|
|
}
|
|
}
|
|
|
|
if (LnPacket->sd.slot == SLOT_PRG) { // Programmer Task Final reply
|
|
if (progStepCV != PRG_IDLE) {
|
|
mySlot.trk = LnPacket->pt.trk;
|
|
CVdata = LnPacket->pt.data7 | (bitRead(LnPacket->pt.cvh, 1) << 7);
|
|
CVdata |= ((LnPacket->pt.pstat & 0x0F) << 8);
|
|
endProg();
|
|
}
|
|
}
|
|
|
|
if (LnPacket->sd.slot == SLOT_0) { // Slot 0
|
|
mySlot.trk = LnPacket->sd.trk; // get command station status
|
|
showTrkLnet(mySlot.trk);
|
|
identifyCS(LnPacket); // identify command station
|
|
}
|
|
break;
|
|
case OPC_WR_SL_DATA:
|
|
if ((mySlot.num > 0) && (LnPacket->sd.slot == mySlot.num)) { // Cambios en mi slot
|
|
infoLocomotoraLnet(locoData[myLocoData].myAddr.address); // do it with read slot
|
|
}
|
|
if (LnPacket->sd.slot == SLOT_PRG) { // Programmer Task Start
|
|
//CVdata = LnPacket->pt.cvl | bitRead((LnPacket->pt.cvh, 1) << 6);
|
|
}
|
|
if (LnPacket->sd.slot == SLOT_FC) { // FAST Clock
|
|
//if (LnPacket->fc.clk_cntrl & 0x40) // bit 6 = 1; data is valid clock info. JMRI sends only EF 0E 7B ... (OPC_WR_SL_DATA) with clk_cntrl == 0
|
|
setFastClock(LnPacket);
|
|
}
|
|
break;
|
|
case OPC_RQ_SL_DATA:
|
|
/*
|
|
if (LnPacket->sr.slot == SLOT_FC) { // FAST Clock SYNC
|
|
clockTimer = millis(); // reset local sub-minute phase counter
|
|
}
|
|
*/
|
|
break;
|
|
|
|
case OPC_SL_RD_UHLI: // Uhlenbrock loco data
|
|
case OPC_WR_SL_UHLI:
|
|
if (LnPacket->usd.mesg_size == 0x15) {
|
|
adr = (LnPacket->usd.adr2 << 7) + LnPacket->usd.adr;
|
|
if (adr == locoData[myLocoData].myAddr.address) {
|
|
mySlot.state = LnPacket->usd.stat;
|
|
mySlot.trk = LnPacket->usd.trk;
|
|
slotStatus = (LnPacket->usd.stat >> 4) & 0x03;
|
|
locoData[myLocoData].mySpeed = LnPacket->usd.spd; // actualiza velocidad
|
|
locoData[myLocoData].myDir = (((LnPacket->usd.dirf << 2) ^ 0x80) & 0x80); // actualiza sentido
|
|
updateUhliF0F4(LnPacket->usd.dirf);
|
|
updateUhliF5F11(LnPacket->usd.snd2);
|
|
updateUhliF13F19(LnPacket->usd.snd3);
|
|
updateUhliF21F27(LnPacket->usd.snd4);
|
|
updateUhliF12F20F28(LnPacket->usd.fhi);
|
|
updateFuncState(isWindow(WIN_THROTTLE));
|
|
if (isWindow(WIN_THROTTLE) || isWindow(WIN_SPEEDO))
|
|
updateSpeedHID(); // set encoder
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OPC_LONG_ACK:
|
|
//DEBUG_MSG("LACK Opcode: %x resp: %x", LnPacket->lack.opcode | 0x80, LnPacket->lack.ack1);
|
|
if (progStepCV != PRG_IDLE) {
|
|
if (LnPacket->lack.opcode == (OPC_WR_SL_DATA & 0x7F)) {
|
|
switch (LnPacket->lack.ack1) {
|
|
case 0x7F: // Function NOT implemented, no reply.
|
|
case 0x00: // Programmer BUSY , task aborted, no reply.
|
|
CVdata = 0x0600; // show ERR
|
|
case 0x40: // Task accepted blind NO <E7> reply at completion
|
|
endProg();
|
|
break;
|
|
case 0x01: // Task accepted , <E7> reply at completion.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (isWindow(WIN_PROG_LNCV)) {
|
|
if (LnPacket->lack.opcode == (OPC_IMM_PACKET & 0x7F) && (LnPacket->lack.ack1 != 0x7F)) { // error writing LNCV
|
|
txtData[TXT_LNCV_VAL].backgnd = COLOR_PINK;
|
|
newEvent(OBJ_TXT, TXT_LNCV_VAL, EVNT_DRAW);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case OPC_MOVE_SLOTS:
|
|
if ((LnPacket->sm.src == mySlot.num) && (LnPacket->sm.dest == mySlot.num)) {
|
|
// me quieren robar el slot. He sido yo?
|
|
}
|
|
break;
|
|
case OPC_SLOT_STAT1:
|
|
if ((mySlot.num > 0) && (LnPacket->ss.slot == mySlot.num)) {
|
|
// Cambios en mi slot
|
|
}
|
|
break;
|
|
|
|
case OPC_PEER_XFER:
|
|
// [E5 0F 05 49 4B 1F 01 2F 13 00 00 01 00 00 31] (LNCV) READ_CV_REPLY from module (Article #5039):
|
|
if ((LnPacket->ub.DSTL == 'I') && (LnPacket->ub.DSTH == 'K')) {
|
|
if ((LnPacket->ub.SRC == 0x05) && (LnPacket->ub.ReqId == LNCV_REQID_CFGREAD)) {
|
|
//DEBUG_MSG("PXCT: %0X", LnPacket->ub.PXCT1)
|
|
if (bitRead(LnPacket->ub.PXCT1, 0)) // expand bits in PXCT1
|
|
bitSet(LnPacket->ub.D0, 7);
|
|
if (bitRead(LnPacket->ub.PXCT1, 1))
|
|
bitSet(LnPacket->ub.D1, 7);
|
|
if (bitRead(LnPacket->ub.PXCT1, 2))
|
|
bitSet(LnPacket->ub.D2, 7);
|
|
if (bitRead(LnPacket->ub.PXCT1, 3))
|
|
bitSet(LnPacket->ub.D3, 7);
|
|
if (bitRead(LnPacket->ub.PXCT1, 4))
|
|
bitSet(LnPacket->ub.D4, 7);
|
|
if (bitRead(LnPacket->ub.PXCT1, 5))
|
|
bitSet(LnPacket->ub.D5, 7);
|
|
if (bitRead(LnPacket->ub.PXCT1, 6))
|
|
bitSet(LnPacket->ub.D6, 7);
|
|
artNum = LnPacket->ub.D0 | (LnPacket->ub.D1 << 8);
|
|
numLNCV = LnPacket->ub.D2 | (LnPacket->ub.D3 << 8);
|
|
valLNCV = LnPacket->ub.D4 | (LnPacket->ub.D5 << 8);
|
|
DEBUG_MSG("Art: %d LNCV: %d-%d", artNum, numLNCV, valLNCV);
|
|
if (numLNCV == 0)
|
|
modNum = valLNCV;
|
|
setFieldsLNCV();
|
|
if (isWindow(WIN_PROG_LNCV))
|
|
showFieldsLNCV();
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void lnetTimers() {
|
|
if (millis() - pingTimer > LNET_PING_INTERVAL) { // Refresca velocidad para mantener slot en IN_USE
|
|
if (mySlot.num > 0) {
|
|
pingTimer = millis();
|
|
send4byteMsg(OPC_LOCO_SPD, mySlot.num, locoData[myLocoData].mySpeed);
|
|
//DEBUG_MSG("Refresing speed %d", mySpeed);
|
|
}
|
|
}
|
|
if (clockRate > 0) { // Actualiza fast clock interno
|
|
if (millis() - clockTimer > clockInterval) {
|
|
clockTimer = millis();
|
|
clockMin++;
|
|
if (clockMin > 59) {
|
|
clockMin = 0;
|
|
clockHour++;
|
|
}
|
|
if (clockHour > 23)
|
|
clockHour = 0;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
void processLnet() {
|
|
lnetReceive();
|
|
lnetTimers();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** LOCONET DECODE SUPPORT *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
void setFastClock(lnMsg * LnPacket) {
|
|
if (LnPacket->fc.clk_rate != clockRate) { // 0 = Freeze clock, 1 = normal, 10 = 10:1 etc. Max is 0x7f
|
|
clockRate = LnPacket->fc.clk_rate; // calcula nuevo intervalo interno
|
|
if (clockRate > 0)
|
|
clockInterval = 60000UL / (unsigned long)clockRate; // calcula intervalo para un minuto
|
|
} // [EF 0E 7B 01 7F 79 43 07 68 1B 40 00 00 15] JMRI: 00:00 DAY 27
|
|
clockMin = LnPacket->fc.mins_60 - 0x43; // 256 - minutes ???
|
|
clockHour = LnPacket->fc.hours_24 - 0x68; // 256 - hours ???
|
|
clockTimer = millis();
|
|
DEBUG_MSG("CLOCK %d:%d R:%d", clockHour, clockMin, clockRate);
|
|
updateFastClock();
|
|
}
|
|
|
|
|
|
void decodeFuncIMM (uint8_t cmd, byte data) {
|
|
/*
|
|
if ((cmd & 0x60) == 0x00) { // 100D-DDDD F0-F4
|
|
locoData[myLocoData].myFunc.xFunc[0] &= 0xE0;
|
|
locoData[myLocoData].myFunc.xFunc[0] |= ((cmd & 0x0F) << 1);
|
|
if (cmd & 0x10)
|
|
locoData[myLocoData].myFunc.xFunc[0] |= 0x01;
|
|
cmd = 0x80;
|
|
}
|
|
if ((cmd & 0x70) == 0x30) { // 1011-FFFF F5-F8
|
|
locoData[myLocoData].myFunc.xFunc[0] &= 0x1F;
|
|
locoData[myLocoData].myFunc.xFunc[0] |= (cmd << 5);
|
|
if (cmd & 0x10)
|
|
locoData[myLocoData].myFunc.xFunc[1] |= 0x01;
|
|
else
|
|
locoData[myLocoData].myFunc.xFunc[1] &= 0xFE;
|
|
cmd = 0x80;
|
|
}
|
|
*/
|
|
if ((cmd & 0x70) == 0x20) { // 1010-FFFF F9-F12
|
|
locoData[myLocoData].myFunc.xFunc[1] &= 0xE1;
|
|
locoData[myLocoData].myFunc.xFunc[1] |= ((cmd & 0x0F) << 1);
|
|
cmd = 0x80;
|
|
}
|
|
if (cmd == 0x5E) { // 1101-1110 DDDDDDDD F13-F20
|
|
locoData[myLocoData].myFunc.Bits &= 0xFFE01FFF;
|
|
locoData[myLocoData].myFunc.Bits |= ((uint32_t)(data) << 13);
|
|
cmd = 0x80;
|
|
}
|
|
if (cmd == 0x5F) { // 1101-1111 DDDDDDDD F21-F28
|
|
locoData[myLocoData].myFunc.Bits &= 0xE01FFFFF;
|
|
locoData[myLocoData].myFunc.Bits |= ((uint32_t)(data) << 21);
|
|
cmd = 0x80;
|
|
}
|
|
if (cmd == 0x80)
|
|
updateFuncState(isWindow(WIN_THROTTLE));
|
|
}
|
|
|
|
void updateUhliF5F11(uint8_t fnc) {
|
|
locoData[myLocoData].myFunc.Bits &= 0xFFFFF01F;
|
|
locoData[myLocoData].myFunc.Bits |= ((unsigned long)(fnc & 0x7F) << 5);
|
|
}
|
|
|
|
void updateUhliF12F20F28(uint8_t fnc) {
|
|
bitWrite(locoData[myLocoData].myFunc.Bits, 12, bitRead(fnc, 4));
|
|
bitWrite(locoData[myLocoData].myFunc.Bits, 20, bitRead(fnc, 5));
|
|
bitWrite(locoData[myLocoData].myFunc.Bits, 28, bitRead(fnc, 6));
|
|
}
|
|
|
|
void updateUhliF13F19(uint8_t fnc) {
|
|
locoData[myLocoData].myFunc.Bits &= 0xFFF01FFF;
|
|
locoData[myLocoData].myFunc.Bits |= ((unsigned long)(fnc & 0x7F) << 13);
|
|
}
|
|
|
|
void updateUhliF21F27(uint8_t fnc) {
|
|
locoData[myLocoData].myFunc.Bits &= 0xF01FFFFF;
|
|
locoData[myLocoData].myFunc.Bits |= ((unsigned long)(fnc & 0x7F) << 21);
|
|
}
|
|
|
|
void updateUhliF0F4(uint8_t fnc) {
|
|
locoData[myLocoData].myFunc.Bits &= 0xFFFFFFE0;
|
|
locoData[myLocoData].myFunc.Bits |= ((unsigned long)(fnc & 0x0F) << 1);
|
|
bitWrite(locoData[myLocoData].myFunc.Bits, 0, bitRead(fnc, 4));
|
|
}
|
|
|
|
void clearPacketUlhi() { // Borra paquete largo para Intellibox II
|
|
for (byte i = 0; i < 31; i++)
|
|
SendPacket.data[i] = 0x00;
|
|
}
|
|
|
|
void progUhli (byte mode) {
|
|
ulhiProg = mode;
|
|
if (typeCmdStation == CMD_DR) { // Intellibox II program task start or end
|
|
SendPacket.data[0] = OPC_PEER_XFER;
|
|
SendPacket.data[1] = 0x07;
|
|
SendPacket.data[2] = 0x01;
|
|
SendPacket.data[3] = 'I';
|
|
SendPacket.data[4] = 'B';
|
|
SendPacket.data[5] = mode;
|
|
lnetSend(&SendPacket);
|
|
}
|
|
}
|
|
|
|
|
|
void readCVLnet (unsigned int adr, byte stepPrg) {
|
|
byte cvh;
|
|
if (!modeProg) { // Read only in Direct mode
|
|
clearPacketUlhi();
|
|
if (typeCmdStation == CMD_DR) {
|
|
//if (!modeProg)
|
|
if (ulhiProg == UHLI_PRG_END)
|
|
progUhli(UHLI_PRG_START); // Intellibox II format
|
|
SendPacket.data[0] = OPC_IMM_PACKET;
|
|
SendPacket.data[1] = 0x1F;
|
|
SendPacket.data[2] = 0x01;
|
|
SendPacket.data[3] = 'I';
|
|
SendPacket.data[4] = 'B';
|
|
SendPacket.data[5] = 0x71 | (bitRead(adr, 7) << 1);
|
|
SendPacket.data[6] = 0x72;
|
|
SendPacket.data[7] = adr & 0x7F;
|
|
SendPacket.data[8] = (adr >> 8) & 0x03;
|
|
SendPacket.data[10] = 0x70;
|
|
SendPacket.data[15] = 0x10;
|
|
}
|
|
else {
|
|
SendPacket.data[0] = OPC_WR_SL_DATA;
|
|
SendPacket.data[1] = 0x0E;
|
|
SendPacket.data[2] = SLOT_PRG; // Slot 0x7C
|
|
SendPacket.data[3] = 0x28; // PCMD Direct Read
|
|
// SendPacket.data[4] = 0x00;
|
|
// SendPacket.data[5] = 0x00; // HOPSA Loco address
|
|
// SendPacket.data[6] = 0x00; // LOPSA
|
|
SendPacket.data[7] = mySlot.trk; // TRK
|
|
adr--;
|
|
cvh = bitRead(adr, 7) | (bitRead(adr, 8) << 4) | (bitRead(adr, 9) << 5);
|
|
SendPacket.data[8] = cvh; // CVH <0,0,CV9,CV8 - 0,0, D7,CV7>
|
|
SendPacket.data[9] = adr & 0x7F; // CVL
|
|
// SendPacket.data[10] = 0x00; // DATA7
|
|
// SendPacket.data[11] = 0x00;
|
|
// SendPacket.data[12] = 0x00;
|
|
}
|
|
lnetSend(&SendPacket);
|
|
//DEBUG_MSG("Read CV %d", adr);
|
|
progStepCV = stepPrg;
|
|
}
|
|
}
|
|
|
|
|
|
void writeCVLnet (unsigned int adr, unsigned int data, byte stepPrg) {
|
|
byte cvh;
|
|
clearPacketUlhi();
|
|
if (typeCmdStation == CMD_DR) {
|
|
if (!modeProg)
|
|
if (ulhiProg == UHLI_PRG_END)
|
|
progUhli(UHLI_PRG_START); // Intellibox II format
|
|
SendPacket.data[0] = OPC_IMM_PACKET;
|
|
SendPacket.data[1] = 0x1F;
|
|
SendPacket.data[2] = 0x01;
|
|
SendPacket.data[3] = 'I';
|
|
SendPacket.data[4] = 'B';
|
|
if (modeProg) {
|
|
SendPacket.data[5] = 0x71 | (bitRead(locoData[myLocoData].myAddr.address, 7) << 1) | (bitRead(adr, 7) << 3);
|
|
SendPacket.data[6] = 0x5E;
|
|
SendPacket.data[7] = locoData[myLocoData].myAddr.address & 0x7F;
|
|
SendPacket.data[8] = (locoData[myLocoData].myAddr.address >> 8) & 0x3F;
|
|
SendPacket.data[9] = adr & 0x7F;
|
|
SendPacket.data[10] = 0x70 | (bitRead(data, 7) << 1);
|
|
SendPacket.data[11] = (adr >> 8) & 0x03;
|
|
SendPacket.data[12] = data & 0x7F;
|
|
}
|
|
else {
|
|
SendPacket.data[5] = 0x71 | (bitRead(adr, 7) << 1) | (bitRead(data, 7) << 3);
|
|
SendPacket.data[6] = 0x71;
|
|
SendPacket.data[7] = adr & 0x7F;
|
|
SendPacket.data[8] = (adr >> 8) & 0x03;
|
|
SendPacket.data[9] = data & 0x7F;
|
|
SendPacket.data[10] = 0x70;
|
|
}
|
|
SendPacket.data[15] = 0x10;
|
|
}
|
|
else {
|
|
SendPacket.data[0] = OPC_WR_SL_DATA; // Write in Direct mode or POM
|
|
SendPacket.data[1] = 0x0E;
|
|
SendPacket.data[2] = SLOT_PRG; // Slot 0x7C
|
|
// SendPacket.data[4] = 0x00;
|
|
if (modeProg) {
|
|
SendPacket.data[3] = 0x64; // PCMD PoM Write
|
|
SendPacket.data[5] = (locoData[myLocoData].myAddr.address >> 7) & 0x7F; // HOPSA Loco address
|
|
SendPacket.data[6] = locoData[myLocoData].myAddr.address & 0x7F; // LOPSA
|
|
}
|
|
else {
|
|
SendPacket.data[3] = 0x68; // PCMD Direct Write
|
|
// SendPacket.data[5] = 0x00; // HOPSA Loco address
|
|
// SendPacket.data[6] = 0x00; // LOPSA
|
|
}
|
|
SendPacket.data[7] = mySlot.trk; // TRK
|
|
adr--;
|
|
cvh = bitRead(adr, 7) | (bitRead(adr, 8) << 4) | (bitRead(adr, 9) << 5) | (bitRead(data, 7) << 1);
|
|
SendPacket.data[8] = cvh; // CVH <0,0,CV9,CV8 - 0,0, D7,CV7>
|
|
SendPacket.data[9] = adr & 0x7F; // CVL
|
|
SendPacket.data[10] = data & 0x7F; // DATA7
|
|
// SendPacket.data[11] = 0x00;
|
|
// SendPacket.data[12] = 0x00;
|
|
}
|
|
lnetSend(&SendPacket);
|
|
progStepCV = stepPrg;
|
|
//DEBUG_MSG("Write CV%d = %d", adr, data);
|
|
}
|
|
|
|
|
|
void sendLNCV (byte id, byte flags) {
|
|
byte i;
|
|
SendPacket.data[0] = (flags == LNCV_FLAG_PROFF) ? OPC_PEER_XFER : OPC_IMM_PACKET;
|
|
SendPacket.data[1] = 0x0F;
|
|
SendPacket.data[2] = 0x01;
|
|
SendPacket.data[3] = 0x05;
|
|
SendPacket.data[4] = 0x00;
|
|
SendPacket.data[5] = id;
|
|
SendPacket.data[6] = 0x00; // PXCT1
|
|
SendPacket.data[7] = lowByte(artNum);
|
|
SendPacket.data[8] = highByte(artNum);
|
|
SendPacket.data[9] = lowByte(numLNCV);
|
|
SendPacket.data[10] = highByte(numLNCV);
|
|
SendPacket.data[11] = lowByte(valLNCV);
|
|
SendPacket.data[12] = highByte(valLNCV);
|
|
SendPacket.data[13] = flags;
|
|
for (i = 0; i < 7; i++) { // set bits in PXCT1
|
|
if (SendPacket.data[7 + i] & 0x80) {
|
|
bitSet(SendPacket.data[6], i);
|
|
bitClear(SendPacket.data[7 + i], 7);
|
|
}
|
|
}
|
|
lnetSend(&SendPacket);
|
|
}
|
|
|
|
|
|
/*
|
|
Detect Hardware type (Read Slot 0)
|
|
0xE7, 0x0E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x49, 0x42, 0x18 "Intellibox / TwinCenter" ADR: 0 ID: 'IB' SPD: 2
|
|
0xE7, 0x0E, 0x00, 0x03, 0x00, 0x03, 0x00, 0x06, 0x08, 0x00, 0x00, 0x49, 0x42, 0x13 "DR5000" ADR: 0 ID: 'IB' SPD: 3
|
|
0xE7, 0x0E, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x08, 0x00, 0x00, 0x49, 0x42, 0x12 "YD7001" ADR: 0 ID: 'IB' SPD: 3
|
|
0xE7, 0x0E, 0x00, 0x02, 0x42, 0x03, 0x00, 0x07, 0x00, 0x00, 0x15, 0x49, 0x42, 0x4C "Intellibox II / IB-Basic / IB-Com" ADR: 'B' ID: 'IB' SPD: 3
|
|
0xE7, 0x0E, 0x00, 0x02, 0x42, 0x03, 0x00, 0x06, 0x00, 0x00, 0x15, 0x49, 0x42, 0x4D "System Control 7" ADR: 'B' ID: 'IB' SPD: 3
|
|
0xE7, 0x0E, 0x00, 0x02, 0x42, 0x03, 0x00, 0x07, 0x00, 0x00, 0x15, 0x49, 0x42, 0x4C "Daisy II Tillig" ADR: 'B' ID: 'IB' SPD: 3
|
|
0xE7, 0x0E, 0x00, 0x02, 0x42, 0x03, 0x00, 0x07, 0x00, 0x00, 0x15, 0x49, 0x42, 0x4C "Daisy II WLAN" ADR: 'B' ID: 'IB' SPD: 3
|
|
0xE7, 0x0E, 0x00, 0x00, 0x44, 0x02, 0x00, 0x07, 0x00, 0x59, 0x01, 0x49, 0x42, 0x04 "Daisy" ADR: 'DY' ID: 'IB' SPD: 2
|
|
0xE7, 0x0E, 0x00, 0x00, 0x4C, 0x01, 0x00, 0x07, 0x00, 0x49, 0x02, 0x49, 0x42, 0x1C "Adapter 63820" ADR: 'LI' ID: 'IB' SPD: 1
|
|
0xE7, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x5A, 0x21, 0x6A "Z21 Black" ADR: 0 ID: 'Z'21 SPD: 0
|
|
0xE7, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11 "Digitrax Chief" ADR: 0 ID: 0 SPD: 0
|
|
0xe7, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11 DCS100
|
|
0xe7, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12 DCS200
|
|
0xe7, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12 DCS50
|
|
0xe7, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52 DCS52
|
|
0xe7, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x25, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30 DT200
|
|
0xe7, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51 DCS210
|
|
0xe7, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x11, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 DCS240
|
|
0xe7, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x20, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71 DCS240+
|
|
0xe7, 0x0e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16 DB150 ADR: 0 ID: 0 SPD: 4
|
|
0xe7, 0x0e, 0x00, 0x33, 0x0e, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2d DCS51 ADR: 0 ID: 1 SPD: 0
|
|
0xE7, 0x0E, 0x00, 0x00, 0x4E, 0x10, 0x01, 0x07, 0x00, 0x4C, 0x00, 0x4A, 0x46, 0x09 NanoL ADR: 'NL' ID: 'JF' SPD: 16
|
|
0xB4, 0x3B, 0x00, 0x70 RB1110 not supported
|
|
|
|
SLOT STAT1 ADRL SPD DIRF TRK STAT2 ADH SND ID1 ID2 CHK
|
|
*/
|
|
|
|
void identifyCS(lnMsg * LnPacket) {
|
|
uint8_t typeCS;
|
|
typeCS = CMD_UNKNOW;
|
|
if (autoIdentifyCS) {
|
|
switch (LnPacket->sd.id1) {
|
|
case 'I':
|
|
if (LnPacket->sd.id2 == 'B') {
|
|
switch (LnPacket->sd.adr) {
|
|
case 0:
|
|
switch (LnPacket->sd.spd) {
|
|
case 2:
|
|
DEBUG_MSG("Intellibox / TwinCenter");
|
|
typeCS = CMD_ULI;
|
|
break;
|
|
case 3:
|
|
DEBUG_MSG("DR5000");
|
|
typeCS = CMD_DR;
|
|
break;
|
|
}
|
|
break;
|
|
case 'B':
|
|
DEBUG_MSG("Intellibox II / IB-Basic / IB-Com");
|
|
typeCS = CMD_DR;
|
|
break;
|
|
case 'D':
|
|
if (LnPacket->sd.adr2 == 'Y') {
|
|
DEBUG_MSG("Daisy");
|
|
typeCS = CMD_ULI;
|
|
}
|
|
break;
|
|
case 'L':
|
|
if (LnPacket->sd.adr2 == 'I') {
|
|
DEBUG_MSG("Adapter 63820"); // Only interface, commad station unknow
|
|
typeCS = CMD_DIG;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'Z':
|
|
if (LnPacket->sd.id2 == 0x21) {
|
|
DEBUG_MSG("Z21 Black");
|
|
typeCS = CMD_DR;
|
|
}
|
|
break;
|
|
case 0x00:
|
|
if (LnPacket->sd.id2 == 0x00) {
|
|
DEBUG_MSG("Digitrax Chief");
|
|
typeCS = CMD_DIG;
|
|
}
|
|
break;
|
|
}
|
|
if (typeCS == CMD_UNKNOW) {
|
|
DEBUG_MSG("CS Unknow");
|
|
autoIdentifyCS = 0x00;
|
|
}
|
|
else {
|
|
typeCmdStation = typeCS;
|
|
radioData[RAD_CSTATION].value = typeCS;
|
|
}
|
|
}
|
|
}
|