diff --git a/CircularBuffer.cpp b/CircularBuffer.cpp new file mode 100644 index 0000000..2f8016f --- /dev/null +++ b/CircularBuffer.cpp @@ -0,0 +1,387 @@ +/* + * © 2023 Thierry Paris / Locoduino + * All rights reserved. + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +#include "CircularBuffer.hpp" + +CircularBuffer::CircularBuffer(int inSize) +{ + this->head = 0; + this->tail = 0; + this->full = false; + this->peakCount = 0; + this->buffer = NULL; + this->size = inSize; +#ifdef ARDUINO_ARCH_ESP32 + this->xSemaphore = NULL; +#endif +} + +void CircularBuffer::begin(bool inMultiThread) +{ +#ifdef ARDUINO_ARCH_ESP32 + if (inMultiThread) + { + this->xSemaphore = xSemaphoreCreateMutex(); + } +#endif + this->buffer = new byte[this->size]; + this->clear(); +} + +bool CircularBuffer::CheckIfBeginDone() +{ + if (this->buffer == NULL) + { + DIAG(F("Error : CircularBuffer missing begin !")); + return false; + } + return true; +} + +void CircularBuffer::end() +{ + START_SEMAPHORE() + if (!this->CheckIfBeginDone()) return; + + delete[] this->buffer; + this->buffer = NULL; // to be sure to crash at any attemps to use it ! + END_SEMAPHORE() +} + +void CircularBuffer::clear() +{ + if (!this->CheckIfBeginDone()) return; + + START_SEMAPHORE() + memset(this->buffer, 0, this->size); + + this->full = false; + this->head = this->tail = 0; + this->peakCount = 0; + END_SEMAPHORE() +} + +bool CircularBuffer::PushByte(byte inpData) +{ + if (!this->CheckIfBeginDone()) return false; + + bool ok = true; + START_SEMAPHORE() + + if (!this->full) + { + this->buffer[this->head] = inpData; + this->head++; + this->full = this->head == this->tail; + + if (this->full) + { + ok = false; + this->peakCount = this->size + 1; // maximum size ! + } + } + + if (!ok) + { + DIAG(F("Error : the byte has been lost ! Buffer is full !")); + } + + END_SEMAPHORE() + + this->GetCount(); // update peakCount... + return ok; +} + +bool CircularBuffer::PushBytes(byte* inpData, int inDataLength) +{ + if (!this->CheckIfBeginDone()) return false; + + bool ok = true; + START_SEMAPHORE() + if (!this->full) + { + for (int i = 0; i < inDataLength; i++) + { + this->buffer[this->head] = inpData[i]; + this->head = (this->head + 1) % this->size; + this->full = this->head == this->tail; + + if (this->full) + { + ok = false; + this->peakCount = this->size + (inDataLength - i); // maximum size ! + break; + } + } + } + + if (!ok) + { + DIAG(F("Error : bytes has been lost ! Buffer is full !")); + } + + END_SEMAPHORE() + this->GetCount(); // update peakCount... + return ok; +} + +byte CircularBuffer::GetByte() +{ + if (!this->CheckIfBeginDone()) return 0; + + byte value = 0; + if (this->isEmpty()) + return 0; + + START_SEMAPHORE() + value = this->buffer[this->tail]; + this->tail = (this->tail + 1) % this->size; + this->full = false; + END_SEMAPHORE() + + return value; +} + +int16_t CircularBuffer::GetInt16() +{ + if (!this->CheckIfBeginDone()) return 0; + + if (this->isEmpty() || this->GetCount() < 2) + return 0; + + byte value1 = this->GetByte(); + byte value2 = this->GetByte(); + + return int16_t((unsigned char)(value2) << 8 | (unsigned char)(value1)); +} + +int32_t CircularBuffer::GetInt32() +{ + if (!this->CheckIfBeginDone()) return 0; + + if (this->isEmpty() || this->GetCount() < 4) + return 0; + + byte value1 = this->GetByte(); + byte value2 = this->GetByte(); + byte value3 = this->GetByte(); + byte value4 = this->GetByte(); + + return int32_t(value4 << 24 | value3 << 16 | value2 << 8 | value1); +} + +bool CircularBuffer::GetBytes(byte* inpData, int inDataLength) +{ + if (!this->CheckIfBeginDone()) return false; + + if (this->GetCount() < inDataLength) + return false; + + START_SEMAPHORE() + for (int i = 0; i < inDataLength; i++) + { + inpData[i] = this->buffer[this->tail]; + this->tail = (this->tail + 1) % this->size; + } + this->full = false; + END_SEMAPHORE() + + return true; +} + +int16_t CircularBuffer::GetInt16(byte* pBuffer, int inPos) +{ + byte value1 = pBuffer[inPos]; + byte value2 = pBuffer[inPos+1]; + + return int16_t((unsigned char)(value2) << 8 | (unsigned char)(value1)); +} + +int32_t CircularBuffer::GetInt32(byte* pBuffer, int inPos) +{ + byte value1 = pBuffer[inPos]; + byte value2 = pBuffer[inPos + 1]; + byte value3 = pBuffer[inPos + 2]; + byte value4 = pBuffer[inPos + 3]; + + return int32_t(value4 << 24 | value3 << 16 | value2 << 8 | value1); +} + +void CircularBuffer::GetBytes(byte* pBuffer, int inPos, byte* inpData, int inDataLength) +{ + memcpy(pBuffer + inPos, inpData, inDataLength); +} + +int CircularBuffer::GetCount() +{ + if (!this->CheckIfBeginDone()) return 0; + + int usedSize = 0; + + usedSize = this->size; + + if (!this->full) + { + if (this->head >= this->tail) + { + usedSize = this->head - this->tail; + } + else + { + usedSize = this->size + this->head - this->tail; + } + } + + if (usedSize > this->peakCount) + this->peakCount = usedSize; + + return usedSize; +} + +#ifdef DCCPP_DEBUG_MODE +#ifdef VISUALSTUDIO +const char textNum[] = "123456789"; +const char textChars[] = "ABCDEF"; +const char textSymb[] = "/*-+&$!:;,"; +void CircularBuffer::Test() +{ + CircularBuffer test(20); + + test.begin(false); + + Serial.println("After all initialized"); + test.printCircularBuffer(); + + test.PushBytes((byte*)textNum, 9); + Serial.println("After nums pushed"); + test.printCircularBuffer(); + + byte ret = test.GetByte(); + Serial.print("ret '1' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '2': "); + Serial.println((int)ret); + test.printCircularBuffer(); + + test.PushBytes((byte*)textChars, 6); + Serial.println("After chars pushed"); + test.printCircularBuffer(); + + ret = test.GetByte(); + Serial.print("ret '3' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '4': "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '5': "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '6': "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '7': "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '8': "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '9': "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret 'A': "); + Serial.println((int)ret); + test.printCircularBuffer(); + + test.PushBytes((byte*)textSymb, 10); + Serial.println("After chars pushed"); + test.printCircularBuffer(); + + ret = test.GetByte(); + Serial.print("ret 'B' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret 'C' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret 'D' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret 'E' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret 'F' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '/' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '*' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '-' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '+' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '&' : "); + Serial.println((int)ret); + ret = test.GetByte(); + Serial.print("ret '$' : "); + Serial.println((int)ret); + test.printCircularBuffer(); + + Serial.print("Final size : "); + Serial.println((int)test.GetCount()); + Serial.print("Max used : "); + Serial.println((int)test.GetPeakCount()); + + // add too much data in the buffer... + test.PushBytes((byte*)textSymb, 10); + Serial.println("After chars pushed"); + test.PushBytes((byte*)textNum, 9); + Serial.println("After chars pushed"); + test.printCircularBuffer(); + + Serial.print("Final size : "); + Serial.println((int)test.GetCount()); + Serial.print("Max used : "); + Serial.println((int)test.GetPeakCount()); +} +#endif + +void CircularBuffer::printCircularBuffer() +{ + if (!this->CheckIfBeginDone()) return; + + if (this->full) + Serial.println("FULL !"); + for (int i = 0; i < this->size; i++) + { + if (i == this->tail) + Serial.print("Tail "); + if (i == this->head) + Serial.print("Head "); + if (i != this->tail && i != this->head) + Serial.print(" "); + Serial.println((int)this->buffer[i]); + } +} +#endif diff --git a/CircularBuffer.hpp b/CircularBuffer.hpp new file mode 100644 index 0000000..76e0e29 --- /dev/null +++ b/CircularBuffer.hpp @@ -0,0 +1,185 @@ +/* + * © 2023 Thierry Paris / Locoduino + * All rights reserved. + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +//------------------------------------------------------------------- +#ifndef __CircularBuffer_Hpp__ +#define __CircularBuffer_Hpp__ +//------------------------------------------------------------------- + +#include +#include "DIAG.h" + +/** This is a thread-safe buffer of bytes, binary or not. Bytes are pushed on the top (its head), and got from the bottom of the +* buffer (its tail...). +*/ +class CircularBuffer +{ +private: + byte *buffer; // buffer itself + int size; // total size of this buffer + int head; // index of the first free byte in the buffer + int tail; // index of the next byte to get. + bool full; // if true, no more space ! + int peakCount; // biggest occupancy size since the creation of the buffer. +#ifdef ARDUINO_ARCH_ESP32 + SemaphoreHandle_t xSemaphore; // semaphore d'exclusion mutuelle +#endif + +public: + /** Constructor. + @param inSize + */ + CircularBuffer(int inSize); + + /** Initialize the list. + @param inMultiThread If the buffer is used in multi-thread environnement, initialize the semaphore before calling this begin(). + */ + void begin(bool inMultiThread = false); + + /** Close the usage of this buffer and free the allocated memory. + */ + void end(); + + /** Remove the full content of the buffer, ready for the next push. + */ + void clear(); + + /** Add one byte in the buffer. + @param inpData byte to add. + @return true if the byte have been pushed. false if the byte are lost due to max size reached... + */ + bool PushByte(byte inpData); + + /** Add some bytes in the buffer. + @param inpData pointer to bytes to add. + @param inDataLength number of bytes to add. + @return true if all bytes have been pushed. false if one or more bytes are lost due to max size reached... + */ + bool PushBytes(byte* inpData, int inDataLength); + + /** Get the next byte from the buffer. + @return first available byte, or 0. + */ + byte GetByte(); + + /** Get the next two bytes from the buffer, to form an integer. + @return integer created from two available bytes, or 0. + */ + int16_t GetInt16(); + + /** Get the next four bytes from the buffer, to form an integer. + @return integer created from two available bytes, or 0. + */ + int32_t GetInt32(); + + /** Get some bytes. + @param inpData buffer to fill. + @param inDataLength number of bytes to get. + @return true if all bytes have been get. false if there were not enough bytes in th buffer. + */ + bool GetBytes(byte* inpData, int inDataLength); + + /** Get the next two bytes from the given buffer starting from the given position, to form an integer. + @param pBuffer buffer to scan. + @param inPos Position of the first useful byte. + @return integer created from two available bytes, or 0. + */ + static int16_t GetInt16(byte* pBuffer, int inPos); + + /** Get the next four bytes from the given buffer starting from the given position, to form a 32 bits integer. + @param pBuffer buffer to scan. + @param inPos Position of the first useful byte. + @return integer created from four available bytes, or 0. + */ + static int32_t GetInt32(byte* pBuffer, int inPos); + + /** Get some bytes from the given buffer starting from the given position. + @param pBuffer buffer to scan. + @param inPos Position of the first useful byte. + @param inpData buffer to fill. + @param inDataLength number of bytes to get. + @return true if all bytes have been get. false if there were not enough bytes in th buffer. + */ + static void GetBytes(byte *pBuffer, int inPos, byte* inpData, int inDataLength); + + /** Count the number of bytes in the buffer. + @return number of bytes in the buffer. + */ + int GetCount(); + + /** Get the maximum size used by the buffer since the beggining of its usage. + @return maximum number of bytes in the buffer. + */ + int GetPeakCount() { return this->peakCount; } + + /** Check if the buffer is empty or not. + */ + bool isEmpty() const + { + //if head and tail are equal, we are empty + return (!this->full && (this->head == this->tail)); + } + + /** Check if the buffer is full or not. + */ + bool isFull() const + { + //If tail is ahead the head by 1, we are full + return this->full; + } + + /** Check if the begin has been called. If not, the buffer is not allocated and any usage will crash the system ! + */ + bool CheckIfBeginDone(); + +#ifdef DCCPP_DEBUG_MODE +#ifdef VISUALSTUDIO + /** Unit test function + */ + static void Test(); +#endif + + /** Print the list of messages in the stack. + @remark Only available if DCCPP_DEBUG_MODE is defined. + */ + void printCircularBuffer(); +#endif +}; + +#ifdef ARDUINO_ARCH_ESP32 +#define START_SEMAPHORE() \ + { \ + byte semaphoreTaken = this->xSemaphore == NULL?1:0; \ + if (this->xSemaphore != NULL) \ + if (xSemaphoreTake(this->xSemaphore, (TickType_t)100) == pdTRUE) \ + semaphoreTaken = 1; \ + if (semaphoreTaken == 1) + +#define END_SEMAPHORE() \ + xSemaphoreGive(this->xSemaphore); \ + } + +#define ABORT_SEMAPHORE() \ + xSemaphoreGive(this->xSemaphore); +#else +#define START_SEMAPHORE() +#define END_SEMAPHORE() +#define ABORT_SEMAPHORE() +#endif + +#endif \ No newline at end of file diff --git a/StringFormatter.cpp b/StringFormatter.cpp index f7d9c50..470972e 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -24,6 +24,9 @@ bool Diag::ACK=false; bool Diag::CMD=false; bool Diag::WIFI=false; bool Diag::WITHROTTLE=false; +bool Diag::Z21THROTTLE=true; +bool Diag::Z21THROTTLEVERBOSE=true; +bool Diag::Z21THROTTLEDATA=true; bool Diag::ETHERNET=false; bool Diag::LCN=false; diff --git a/StringFormatter.h b/StringFormatter.h index 6923c10..ab8118f 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -28,6 +28,9 @@ class Diag { static bool CMD; static bool WIFI; static bool WITHROTTLE; + static bool Z21THROTTLE; + static bool Z21THROTTLEVERBOSE; + static bool Z21THROTTLEDATA; static bool ETHERNET; static bool LCN; diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 7754e11..cd10ffc 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -27,6 +27,7 @@ #include "RingStream.h" #include "CommandDistributor.h" #include "WiThrottle.h" +#include "Z21Throttle.h" /* #include "soc/rtc_wdt.h" #include "esp_task_wdt.h" @@ -113,6 +114,7 @@ bool WifiESP::setup(const char *SSid, bool havePassword = true; bool haveSSID = true; bool wifiUp = false; + IPAddress localIP; uint8_t tries = 40; //#ifdef SERIAL_BT_COMMANDS @@ -152,7 +154,7 @@ bool WifiESP::setup(const char *SSid, delay(500); } if (WiFi.status() == WL_CONNECTED) { - DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str()); + DIAG(F("Wifi STA IP %s"),(localIP=WiFi.localIP()).toString().c_str()); wifiUp = true; } else { DIAG(F("Could not connect to Wifi SSID %s"),SSid); @@ -166,7 +168,7 @@ bool WifiESP::setup(const char *SSid, delay(500); } if (WiFi.status() == WL_CONNECTED) { - DIAG(F("Wifi STA IP 2nd try %s"),WiFi.localIP().toString().c_str()); + DIAG(F("Wifi STA IP 2nd try %s"),(localIP=WiFi.localIP()).toString().c_str()); wifiUp = true; } else { DIAG(F("Wifi STA mode FAIL. Will revert to AP mode")); @@ -195,7 +197,7 @@ bool WifiESP::setup(const char *SSid, havePassword ? password : strPass.c_str(), channel, false, 8)) { DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str()); - DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str()); + DIAG(F("Wifi AP IP %s"),(localIP=WiFi.softAPIP()).toString().c_str()); wifiUp = true; APmode = true; } else { @@ -212,6 +214,7 @@ bool WifiESP::setup(const char *SSid, server = new WiFiServer(port); // start listening on tcp port server->begin(); // server started here + Z21Throttle::setup(localIP, Z21_UDPPORT); #ifdef WIFI_TASK_ON_CORE0 //start loop task @@ -297,6 +300,7 @@ void WifiESP::loop() { } // all clients WiThrottle::loop(outboundRing); + Z21Throttle::loop(); // something to write out? clientId=outboundRing->read(); diff --git a/Z21Throttle.cpp b/Z21Throttle.cpp new file mode 100644 index 0000000..4ccbec5 --- /dev/null +++ b/Z21Throttle.cpp @@ -0,0 +1,846 @@ +/* + * © 2023 Thierry Paris / Locoduino + * All rights reserved. + * + * This file is part of CommandStation-EX-Labox + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ +#include +#include "defines.h" +#include +#include "Z21Throttle.h" +#include "DCC.h" +#include "WifiESP32.h" +#include "DCCWaveform.h" +#include "StringFormatter.h" +#include "Turnouts.h" +#include "DIAG.h" +#include "GITHUB_SHA.h" +#include "version.h" +#include "EXRAIL2.h" +#include "CommandDistributor.h" +#include "TrackManager.h" +#include "DCCTimer.h" +#ifdef USE_HMI + #include "hmi.h" +#endif + +static std::vector clientsUDP; // a list to hold all UDP clients + +Z21Throttle *Z21Throttle::firstThrottle=NULL; +byte Z21Throttle::commBuffer[100]; +byte Z21Throttle::replyBuffer[20]; + +Z21Throttle* Z21Throttle::readWriteThrottle = NULL; +int Z21Throttle::cvAddress = -1; +int Z21Throttle::cvValue = -1; + +void printClientsUDP(); + +WiFiUDP NetworkClientUDP::client; + +#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco 0) { + for (int clientId = 0; clientId < clientsUDP.size(); clientId++) { + if (clientsUDP[clientId].inUse) + if (clientsUDP[clientId].remoteIP == NetworkClientUDP::client.remoteIP() && clientsUDP[clientId].remotePort == NetworkClientUDP::client.remotePort()) { + clientsUDP[clientId].pudpBuffer->PushBytes(udp, len); + return clientId; + } + } + } + + return -1; +} + +void Z21Throttle::loop() { + int clientId = 0; + + // loop over all clients and remove inactive + for (clientId = 0; clientId < clientsUDP.size(); clientId++) { + // check if client is there and alive + if (clientsUDP[clientId].inUse && !clientsUDP[clientId].connected) { + if (Diag::Z21THROTTLE) DIAG(F("Remove UDP client %d"), clientId); + clientsUDP[clientId].inUse = false; + printClientsUDP(); + } + } + + int len = NetworkClientUDP::client.parsePacket(); + if (len > 0) { + int clientId = 0; + for (; clientId < clientsUDP.size(); clientId++) { + if (clientsUDP[clientId].inUse) { + if (clientsUDP[clientId].remoteIP == NetworkClientUDP::client.remoteIP() && clientsUDP[clientId].remotePort == NetworkClientUDP::client.remotePort()) { + //if (Diag::Z21THROTTLEVERBOSE) DIAG(F("UDP client %d : %s Already connected"), clientId, clientsUDP[clientId].remoteIP.toString().c_str()); + break; + } + } + } + + if (clientId >= clientsUDP.size()) { + NetworkClientUDP nc; + nc.remoteIP = NetworkClientUDP::client.remoteIP(); + nc.remotePort = NetworkClientUDP::client.remotePort(); + nc.connected = true; + nc.inUse = true; + + clientsUDP.push_back(nc); + if (Diag::Z21THROTTLE) DIAG(F("New UDP client %d, %s"), clientId, nc.remoteIP.toString().c_str()); + printClientsUDP(); + #ifdef USE_HMI + if (hmi::CurrentInterface != NULL) hmi::CurrentInterface->NewClient(clientId, nc.remoteIP, 0); + #endif + // Fleischmann/Roco Android app starts with Power on ! + TrackManager::setMainPower(POWERMODE::ON); + } + + clientId = readUdpPacket(); + + if (clientId >= 0) { + if (clientsUDP[clientId].ok()) { + Z21Throttle* pThrottle = getOrAddThrottle(clientId); + + if (pThrottle != NULL) + pThrottle->parse(); + } + } + } +} + +/** Print the list of assigned locomotives. */ +void Z21Throttle::printLocomotives(bool addTab) { + if (!Diag::Z21THROTTLE) + return; + + DIAG(F(" Locomotives ------------------")); + for (int loco = 0; loco < MAX_MY_LOCO; loco++) + if (myLocos[loco].throttle != '\0') + DIAG(F("%s %d : cab %d on throttle %c"), addTab ? " ":"", loco, myLocos[loco].cab, myLocos[loco].throttle); +} + +/** Print the list of assigned locomotives. */ +void printClientsUDP() { + if (!Diag::Z21THROTTLE) return; + + DIAG(F(" UDP Clients ------------------")); + for (int clientId = 0; clientId < clientsUDP.size(); clientId++) + if (clientsUDP[clientId].ok()) + DIAG(F(" %d %s: %s:%d"), clientId, clientsUDP[clientId].connected?"Connected":"Not connected", clientsUDP[clientId].remoteIP.toString().c_str(), clientsUDP[clientId].remotePort); + else + DIAG(F(" %d unused"), clientId); +} + +/** Print the list of assigned locomotives. */ +void Z21Throttle::printThrottles(bool inPrintLocomotives) { + if (!Diag::Z21THROTTLE) return; + + DIAG(F(" Z21 Throttles ---------------")); + for (Z21Throttle* wt = firstThrottle; wt != NULL; wt = wt->nextThrottle) { + if (wt->clientid == -1) + DIAG(F(" unused")); + else { + DIAG(F(" %d : %d.%d.%d.%d:%d"), wt->clientid, + clientsUDP[wt->clientid].remoteIP[0], + clientsUDP[wt->clientid].remoteIP[1], + clientsUDP[wt->clientid].remoteIP[2], + clientsUDP[wt->clientid].remoteIP[3], + clientsUDP[wt->clientid].remotePort); + + if (inPrintLocomotives) + wt->printLocomotives(true); + } + } +} + +Z21Throttle* Z21Throttle::getOrAddThrottle(int clientId) { + for (Z21Throttle* wt = firstThrottle; wt != NULL ; wt = wt->nextThrottle) { + if (wt->clientid == clientId) + return wt; + } + + Z21Throttle *p = new Z21Throttle(clientId); + printThrottles(false); + return p; +} + +void Z21Throttle::forget( byte clientId) { + for (Z21Throttle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) + if (wt->clientid==clientId) { + delete wt; + break; + } +} + +bool Z21Throttle::isThrottleInUse(int cab) { + for (Z21Throttle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) + if (wt->areYouUsingThrottle(cab)) return true; + + return false; +} + +bool Z21Throttle::areYouUsingThrottle(int cab) { + LOOPLOCOS('*', cab) { // see if I have this cab in use + return true; + } + + return false; +} + +// One instance of Z21Throttle per connected client, so we know what the locos are + +Z21Throttle::Z21Throttle(int inClientId) { + if (Diag::Z21THROTTLE) DIAG(F("New Z21Throttle for client UDP %d"), clientid); + nextThrottle=firstThrottle; + firstThrottle= this; + clientid = inClientId; + initSent=false; // prevent sending heartbeats before connection completed + turnoutListHash = -1; // make sure turnout list is sent once + exRailSent=false; + mostRecentCab=0; + for (int loco=0;lococlientid); + if (firstThrottle== this) { + firstThrottle=this->nextThrottle; + return; + } + + for (Z21Throttle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) { + if (wt->nextThrottle==this) { + wt->nextThrottle=this->nextThrottle; + return; + } + } +} + +void Z21Throttle::write(byte* inpData, int inLengthData) { + size_t size = 0; + +// if (this->dontReply) +// return; + + NetworkClientUDP::client.beginPacket(clientsUDP[this->clientid].remoteIP, clientsUDP[this->clientid].remotePort); + size = NetworkClientUDP::client.write(inpData, inLengthData); + NetworkClientUDP::client.endPacket(); + + if (Diag::Z21THROTTLEDATA) DIAG(F("Z21 Throttle %d : %s SENT 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x"), clientid, + size == 0 ? "BINARY NOT" :"", + (inLengthData > 0)?inpData[0]:0, + (inLengthData > 1)?inpData[1]:0, + (inLengthData > 2)?inpData[2]:0, + (inLengthData > 3)?inpData[3]:0, + (inLengthData > 4)?inpData[4]:0, + (inLengthData > 5)?inpData[5]:0, + (inLengthData > 6)?inpData[6]:0, + (inLengthData > 7)?inpData[7]:0, + (inLengthData > 8)?inpData[8]:0, + (inLengthData > 9)?inpData[9]:0); +} + +// sizes : [ 2 ][ 2 ][inLengthData] +// bytes : [length1, length2][Header1, Header2][Data........] +bool Z21Throttle::notify(unsigned int inHeader, byte* inpData, unsigned int inLengthData, bool inXorInData) { + int realLength = (inLengthData + 4 + (inXorInData == false ? 1 : 0)); + + Z21Throttle::commBuffer[0] = realLength % 256; + Z21Throttle::commBuffer[1] = realLength / 256; + Z21Throttle::commBuffer[2] = inHeader % 256; + Z21Throttle::commBuffer[3] = inHeader / 256; + memcpy(Z21Throttle::commBuffer + 4, inpData, inLengthData); + + if (!inXorInData) { // if xor byte not included in data, compute and write it ! + byte xxor = 0; + + for (unsigned int i = 0; i < inLengthData; i++) + xxor ^= inpData[i]; + + Z21Throttle::commBuffer[inLengthData+4] = xxor; + } + + write(Z21Throttle::commBuffer, realLength); + + return true; +} + +// sizes : [ 2 ][ 2 ][ 1 ][inLengthData] +// bytes : [length1, length2][Header1, Header2][XHeader][Data........] +bool Z21Throttle::notify(unsigned int inHeader, unsigned int inXHeader, byte* inpData, unsigned int inLengthData, bool inXorInData) { + int realLength = (inLengthData + 5 + (inXorInData == false ? 1 : 0)); + + Z21Throttle::commBuffer[0] = realLength % 256; + Z21Throttle::commBuffer[1] = realLength / 256; + Z21Throttle::commBuffer[2] = inHeader % 256; + Z21Throttle::commBuffer[3] = inHeader / 256; + Z21Throttle::commBuffer[4] = inXHeader; + memcpy(Z21Throttle::commBuffer + 5, inpData, inLengthData); + + if (!inXorInData) { // if xor byte not included in data, compute and write it ! + byte xxor = inXHeader; + + for (unsigned int i = 0; i < inLengthData; i++) + xxor ^= inpData[i]; + + Z21Throttle::commBuffer[inLengthData + 5] = xxor; + } + + write(Z21Throttle::commBuffer, realLength); + + return true; +} + +// sizes : [ 2 ][ 2 ][ 1 ][ 1 ][inLengthData] +// bytes : [length1, length2][Header1, Header2][XHeader][DB0][Data........] +bool Z21Throttle::notify(unsigned int inHeader, unsigned int inXHeader, byte inDB0, byte* inpData, unsigned int inLengthData, bool inXorInData) { + int realLength = (inLengthData + 6 + (inXorInData == false ? 1 : 0)); + + Z21Throttle::commBuffer[0] = realLength % 256; + Z21Throttle::commBuffer[1] = realLength / 256; + Z21Throttle::commBuffer[2] = inHeader % 256; + Z21Throttle::commBuffer[3] = inHeader / 256; + Z21Throttle::commBuffer[4] = inXHeader; + Z21Throttle::commBuffer[5] = inDB0; + memcpy(Z21Throttle::commBuffer + 6, inpData, inLengthData); + + if (!inXorInData) { // if xor byte not included in data, compute and write it ! + byte xxor = inXHeader^inDB0; + + for (unsigned int i = 0; i < inLengthData; i++) + xxor ^= inpData[i]; + + Z21Throttle::commBuffer[inLengthData + 6] = xxor; + } + + write(Z21Throttle::commBuffer, realLength); + + return true; +} + +void Z21Throttle::notifyStatus() { + Z21Throttle::replyBuffer[0] = 0; // main current 1 + Z21Throttle::replyBuffer[1] = 0; // main current 2 + Z21Throttle::replyBuffer[2] = 0; // prog current 1 + Z21Throttle::replyBuffer[3] = 0; // prog current 2 + Z21Throttle::replyBuffer[4] = 0; // filtered main current 1 + Z21Throttle::replyBuffer[5] = 0; // filtered main current 2 + Z21Throttle::replyBuffer[6] = 0; // Temperature 1 + Z21Throttle::replyBuffer[7] = 0; // Temperature 2 + Z21Throttle::replyBuffer[8] = 5; // Supply voltage 1 + Z21Throttle::replyBuffer[9] = 0; // supply voltage 2 + Z21Throttle::replyBuffer[10] = 16; // VCC voltage 1 + Z21Throttle::replyBuffer[11] = 0; // VCC voltage 2 + Z21Throttle::replyBuffer[12] = 0b00000000; // CentralState + Z21Throttle::replyBuffer[13] = 0b00000000; // CentralStateEx + Z21Throttle::replyBuffer[14] = 0; + Z21Throttle::replyBuffer[15] = 0; + notify(HEADER_LAN_SYSTEMSTATE, Z21Throttle::replyBuffer, 16, true); +} + +int Z21Throttle::getOrAddLoco(int cab) { + int loco = 0; + for (; loco < MAX_MY_LOCO; loco++) { + if (myLocos[loco].throttle != '\0' && myLocos[loco].cab == cab) + return loco; + } + + if (loco >= MAX_MY_LOCO) { + //use first empty "slot" on this client's list, will be added to DCC registration list + for (int locoToAdd = 0; locoToAdd < MAX_MY_LOCO; locoToAdd++) { + if (myLocos[locoToAdd].throttle == '\0') { + myLocos[locoToAdd].throttle = '0' + this->clientid; + myLocos[locoToAdd].cab = cab; + myLocos[locoToAdd].functionMap = DCC::getFunctionMap(cab); + myLocos[locoToAdd].broadcastPending = true; // means speed/dir will be sent later + mostRecentCab = cab; + myLocos[locoToAdd].functionToggles = 1<<2; // F2 (HORN) is a non-toggle + return locoToAdd; + } + } + } + + return -1; // no loco found, and no place to add one ! +} + +void Z21Throttle::notifyLocoInfo(byte inMSB, byte inLSB) { + int locoAddress = ((inMSB & 0x3F) << 8) + inLSB; + int loco = getOrAddLoco(locoAddress); + + if (loco == -1) + return; //'Too many locos !' + + Z21Throttle::replyBuffer[0] = inMSB; // loco address msb + Z21Throttle::replyBuffer[1] = inLSB; // loco address lsb + Z21Throttle::replyBuffer[2] = B00000100; // 0000CKKK C = already controlled KKK = speed steps 000:14, 010:28, 100:128 + Z21Throttle::replyBuffer[3] = DCC::getThrottleSpeed(locoAddress); // RVVVVVVV R = forward VVVVVVV = speed + if (DCC::getThrottleDirection(locoAddress)) bitSet(Z21Throttle::replyBuffer[3], 7); + + Z21Throttle::replyBuffer[4] = B00000000; // 0DSLFGHJ D = double traction S = Smartsearch L = F0 F = F4 G = F3 H = F2 J = F1 + if (DCC::getFn(locoAddress, 0)) bitSet(Z21Throttle::replyBuffer[4], 4); + if (DCC::getFn(locoAddress, 1)) bitSet(Z21Throttle::replyBuffer[4], 0); + if (DCC::getFn(locoAddress, 2)) bitSet(Z21Throttle::replyBuffer[4], 1); + if (DCC::getFn(locoAddress, 3)) bitSet(Z21Throttle::replyBuffer[4], 2); + if (DCC::getFn(locoAddress, 4)) bitSet(Z21Throttle::replyBuffer[4], 3); + + Z21Throttle::replyBuffer[5] = B00000000; // function F5 to F12 F5 is bit0 + if (DCC::getFn(locoAddress, 5)) bitSet(Z21Throttle::replyBuffer[5], 0); + if (DCC::getFn(locoAddress, 6)) bitSet(Z21Throttle::replyBuffer[5], 1); + if (DCC::getFn(locoAddress, 7)) bitSet(Z21Throttle::replyBuffer[5], 2); + if (DCC::getFn(locoAddress, 8)) bitSet(Z21Throttle::replyBuffer[5], 3); + if (DCC::getFn(locoAddress, 9)) bitSet(Z21Throttle::replyBuffer[5], 4); + if (DCC::getFn(locoAddress, 10)) bitSet(Z21Throttle::replyBuffer[5],5); + if (DCC::getFn(locoAddress, 11)) bitSet(Z21Throttle::replyBuffer[5],6); + if (DCC::getFn(locoAddress, 12)) bitSet(Z21Throttle::replyBuffer[5],7); + + Z21Throttle::replyBuffer[6] = B00000000; // function F13 to F20 F13 is bit0 + if (DCC::getFn(locoAddress, 13)) bitSet(Z21Throttle::replyBuffer[6], 0); + if (DCC::getFn(locoAddress, 14)) bitSet(Z21Throttle::replyBuffer[6], 1); + if (DCC::getFn(locoAddress, 15)) bitSet(Z21Throttle::replyBuffer[6], 2); + if (DCC::getFn(locoAddress, 16)) bitSet(Z21Throttle::replyBuffer[6], 3); + if (DCC::getFn(locoAddress, 17)) bitSet(Z21Throttle::replyBuffer[6], 4); + if (DCC::getFn(locoAddress, 18)) bitSet(Z21Throttle::replyBuffer[6], 5); + if (DCC::getFn(locoAddress, 19)) bitSet(Z21Throttle::replyBuffer[6], 6); + if (DCC::getFn(locoAddress, 20)) bitSet(Z21Throttle::replyBuffer[6], 7); + + Z21Throttle::replyBuffer[7] = B00000000; // function F21 to F28 F21 is bit0 + if (DCC::getFn(locoAddress, 21)) bitSet(Z21Throttle::replyBuffer[7], 0); + if (DCC::getFn(locoAddress, 22)) bitSet(Z21Throttle::replyBuffer[7], 1); + if (DCC::getFn(locoAddress, 23)) bitSet(Z21Throttle::replyBuffer[7], 2); + if (DCC::getFn(locoAddress, 24)) bitSet(Z21Throttle::replyBuffer[7], 3); + if (DCC::getFn(locoAddress, 25)) bitSet(Z21Throttle::replyBuffer[7], 4); + if (DCC::getFn(locoAddress, 26)) bitSet(Z21Throttle::replyBuffer[7], 5); + if (DCC::getFn(locoAddress, 27)) bitSet(Z21Throttle::replyBuffer[7], 6); + if (DCC::getFn(locoAddress, 28)) bitSet(Z21Throttle::replyBuffer[7], 7); + + notify(HEADER_LAN_XPRESS_NET, LAN_X_HEADER_LOCO_INFO, Z21Throttle::replyBuffer, 8, false); +} + +void Z21Throttle::notifyTurnoutInfo(byte inMSB, byte inLSB) { + Z21Throttle::replyBuffer[0] = inMSB; // turnout address msb + Z21Throttle::replyBuffer[1] = inLSB; // turnout address lsb + Z21Throttle::replyBuffer[2] = B00000000; // 000000ZZ ZZ : 00 not switched 01 pos1 10 pos2 11 invalid + notify(HEADER_LAN_XPRESS_NET, LAN_X_HEADER_TURNOUT_INFO, Z21Throttle::replyBuffer, 3, false); +} + +void Z21Throttle::notifyLocoMode(byte inMSB, byte inLSB) { + Z21Throttle::replyBuffer[0] = inMSB; // loco address msb + Z21Throttle::replyBuffer[1] = inLSB; // loco address lsb + Z21Throttle::replyBuffer[2] = B00000000; // 00000000 DCC 00000001 MM + notify(HEADER_LAN_GET_LOCOMODE, Z21Throttle::replyBuffer, 3, true); +} + +void Z21Throttle::notifyFirmwareVersion() { + Z21Throttle::replyBuffer[0] = 0x01; // Version major in BCD + Z21Throttle::replyBuffer[1] = 0x23; // Version minor in BCD + notify(HEADER_LAN_XPRESS_NET, LAN_X_HEADER_FIRMWARE_VERSION, 0x0A, Z21Throttle::replyBuffer, 2, false); +} + +void Z21Throttle::notifyHWInfo() { + Z21Throttle::replyBuffer[0] = 0x00; // Hardware type in BCD on int32 + Z21Throttle::replyBuffer[1] = 0x02; // Hardware type in BCD on int32 + Z21Throttle::replyBuffer[2] = 0x00; // Hardware type in BCD on int32 + Z21Throttle::replyBuffer[3] = 0x00; // Hardware type in BCD on int32 + Z21Throttle::replyBuffer[4] = 0x23; // Firmware version in BCD on int32 + Z21Throttle::replyBuffer[5] = 0x01; // Firmware version in BCD on int32 + Z21Throttle::replyBuffer[6] = 0x00; // Firmware version in BCD on int32 + Z21Throttle::replyBuffer[7] = 0x00; // Firmware version in BCD on int32 + notify(HEADER_LAN_GET_HWINFO, Z21Throttle::replyBuffer, 8, true); +} + +void Z21Throttle::notifyCvNACK(int inCvAddress) { + Z21Throttle::replyBuffer[0] = highByte(inCvAddress); // cv address msb + Z21Throttle::replyBuffer[1] = lowByte(inCvAddress); // cv address lsb + notify(HEADER_LAN_XPRESS_NET, LAN_X_HEADER_CV_NACK, LAN_X_DB0_CV_NACK, Z21Throttle::replyBuffer, 0, false); +} + +void Z21Throttle::notifyCvRead(int inCvAddress, int inValue) { + Z21Throttle::replyBuffer[0] = highByte(inCvAddress); // cv address msb + Z21Throttle::replyBuffer[1] = lowByte(inCvAddress); // cv address lsb + Z21Throttle::replyBuffer[2] = inValue; // cv value + notify(HEADER_LAN_XPRESS_NET, LAN_X_HEADER_CV_RESULT, 0x14, Z21Throttle::replyBuffer, 3, false); +} + +void Z21Throttle::setSpeed(byte inNbSteps, byte inDB1, byte inDB2, byte inDB3) { + bool isForward = bitRead(inDB3, 7); + byte speed = inDB3; + bitClear(speed, 7); + + if (Diag::Z21THROTTLE) DIAG(F("Z21 Throttle %d : speed %d"), clientid, speed * (isForward ? 1:-1)); + + int locoAddress = ((inDB1 & 0x3F) << 8) + inDB2; + + if (getOrAddLoco(locoAddress) == -1) + return; + + DCC::setThrottle(locoAddress, speed, isForward); + + if ((this->broadcastFlags & BROADCAST_BASE) != 0) + notifyLocoInfo(inDB1, inDB2); +} + +// +// TODO Pass through a text message to avoid multi thread locks... +// + +void Z21Throttle::setFunction(byte inDB1, byte inDB2, byte inDB3) { + // inDB3 : TTNN NNNN TT:00 off, TT:01 on; TT:10 toggle NNNNNN function number + + byte action = bitRead(inDB3, 6) + 2 * bitRead(inDB3, 7); + byte function = inDB3; + bitClear(function, 6); + bitClear(function, 7); + bool activeFlag = action == 0b01; + + if (Diag::Z21THROTTLE) DIAG(F("Z21 Throttle %d : function %d %s"), clientid, function, activeFlag?"ON":"OFF"); + + int locoAddress = ((inDB1 & 0x3F) << 8) + inDB2; + if (getOrAddLoco(locoAddress) == -1) + return; + + if (action == 0b10) { // toggle + bool isActivated = DCC::getFn(locoAddress, function); + activeFlag = !isActivated; + } + + DCC::setFn(locoAddress, function, activeFlag); + if ((this->broadcastFlags & BROADCAST_BASE) != 0) + notifyLocoInfo(inDB1, inDB2); +} + +// +// TODO Pass through a text message to avoid multi thread locks... +// + +void Z21CvValueCallback(int16_t inValue) +{ + Z21Throttle::cvValue = inValue; + + if (inValue == -1) + Z21Throttle::readWriteThrottle->notifyCvNACK(Z21Throttle::cvAddress); + else + Z21Throttle::readWriteThrottle->notifyCvRead(Z21Throttle::cvAddress, inValue); + + Z21Throttle::readWriteThrottle = NULL; +} + +void Z21Throttle::cvReadProg(byte inDB1, byte inDB2) { + if (Z21Throttle::readWriteThrottle != NULL) + return; + + int cvAddress = ((inDB1 & 0x3F) << 8) + inDB2 + 1; + + if (Diag::Z21THROTTLE) DIAG(F("Z21 Throttle %d : cvRead Prog %d"), clientid, cvAddress); + + Z21Throttle::readWriteThrottle = this; + Z21Throttle::cvAddress = cvAddress - 1; + + void (*ptr)(int16_t) = &Z21CvValueCallback; + DCC::readCV(cvAddress, ptr); +} + +// Working as cvReadProg for the moment... +void Z21Throttle::cvReadMain(byte inDB1, byte inDB2) { + if (Z21Throttle::readWriteThrottle != NULL) + return; + + int cvAddress = ((inDB1 & 0x3F) << 8) + inDB2 + 1; + + if (Diag::Z21THROTTLE) DIAG(F("Z21 Throttle %d : cvRead Main cv %d"), clientid, cvAddress); + + Z21Throttle::readWriteThrottle = this; + Z21Throttle::cvAddress = cvAddress - 1; + + void (*ptr)(int16_t) = &Z21CvValueCallback; + DCC::readCV(cvAddress, ptr); +} + +// +// TODO Pass through a text message to avoid multi thread locks... +// + +void Z21Throttle::cvWriteProg(byte inDB1, byte inDB2, byte inDB3) { + if (Z21Throttle::readWriteThrottle != NULL) + return; + + int cvAddress = ((inDB1 & 0x3F) << 8) + inDB2 + 1; + + if (Diag::Z21THROTTLE) DIAG(F("Z21 Throttle %d : cvWrite Prog cv %d value %d"), clientid, cvAddress, inDB3); + + Z21Throttle::readWriteThrottle = this; + Z21Throttle::cvAddress = cvAddress - 1; + + void (*ptr)(int16_t) = &Z21CvValueCallback; + DCC::writeCVByte(cvAddress, inDB3, ptr); +} + +// Working as cvReadProg for the moment... +void Z21Throttle::cvReadPom(byte inDB1, byte inDB2, byte inDB3, byte inDB4) { + if (Z21Throttle::readWriteThrottle != NULL) + return; + + int locoAddress = ((inDB1 & 0x3F) << 8) + inDB2; + int cvAddress = ((inDB3 & B00000011) << 8) + inDB4 + 1; + + if (Diag::Z21THROTTLE) DIAG(F("Z21 Throttle %d : cvRead Pom Loco %d cv %d"), clientid, locoAddress, cvAddress); + + Z21Throttle::readWriteThrottle = this; + Z21Throttle::cvAddress = cvAddress - 1; + + void (*ptr)(int16_t) = &Z21CvValueCallback; + DCC::readCV(cvAddress, ptr); +} + +bool Z21Throttle::parse() { + bool done = false; + byte DB[100]; + CircularBuffer* pBuffer = clientsUDP[this->clientid].pudpBuffer; + + if (pBuffer == NULL) + return false; + if (pBuffer->isEmpty()) + return false; + + int lengthData = pBuffer->GetInt16() - 4; // length of the data = total length - length of length (!) - length of header + int header = pBuffer->GetInt16(); + byte Xheader = 0; + byte DB0 = 0; + int nbLocos = CountLocos(); + + if (lengthData > 0) { + pBuffer->GetBytes(DB, lengthData); + if (Diag::Z21THROTTLEDATA) DIAG(F("%d <- len:%d header:0x%02x : 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x"), + this->clientid, lengthData, header, + (lengthData > 0)?DB[0]:0, + (lengthData > 1)?DB[1]:0, + (lengthData > 2)?DB[2]:0, + (lengthData > 3)?DB[3]:0, + (lengthData > 4)?DB[4]:0, + (lengthData > 5)?DB[5]:0, + (lengthData > 6)?DB[6]:0, + (lengthData > 7)?DB[7]:0, + (lengthData > 8)?DB[8]:0, + (lengthData > 9)?DB[9]:0); + } + + switch (header) { + case HEADER_LAN_XPRESS_NET: + Xheader = DB[0]; + switch (Xheader) { + case LAN_X_HEADER_GENERAL: + DB0 = DB[1]; + switch (DB0) { + case LAN_X_DB0_GET_VERSION: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d GET_VERSION"), this->clientid); + break; + case LAN_X_DB0_GET_STATUS: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d GET_STATUS "), this->clientid); + notifyStatus(); + done = true; + break; + case LAN_X_DB0_SET_TRACK_POWER_OFF: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d POWER_OFF"), this->clientid); + // + // TODO Pass through a text message to avoid multi thread locks... + // + TrackManager::setMainPower(POWERMODE::OFF); + done = true; + break; + case LAN_X_DB0_SET_TRACK_POWER_ON: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d POWER_ON"), this->clientid); + // + // TODO Pass through a text message to avoid multi thread locks... + // + TrackManager::setMainPower(POWERMODE::ON); + done = true; + break; + } + break; + case LAN_X_HEADER_SET_STOP: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d EMERGENCY_STOP"), this->clientid); + // + // TODO Pass through a text message to avoid multi thread locks... + // + //Emergency Stop (speed code 1) + // setThrottle will cause a broadcast so notification will be sent + LOOPLOCOS('*', 0) { DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); } + done = true; + break; + case LAN_X_HEADER_SET_LOCO: + DB0 = DB[1]; + switch (DB0) { + case LAN_X_DB0_LOCO_DCC14: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d LOCO DCC 14 SPEED"), this->clientid); + setSpeed(14, DB[2], DB[3], DB[4]); + done = true; + break; + case LAN_X_DB0_LOCO_DCC28: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d LOCO DCC 28 SPEED"), this->clientid); + setSpeed(28, DB[2], DB[3], DB[4]); + done = true; + break; + case LAN_X_DB0_LOCO_DCC128: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d LOCO DCC 128 SPEED"), this->clientid); + setSpeed(128, DB[2], DB[3], DB[4]); + done = true; + break; + case LAN_X_DB0_SET_LOCO_FUNCTION: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d LOCO DCC FUNCTION"), this->clientid); + setFunction(DB[2], DB[3], DB[4]); + if (Diag::Z21THROTTLE) { + // Debug capacity to print data... + byte function = DB[4]; + bitClear(function, 6); + bitClear(function, 7); + if (function == 12) { // why not ? + printClientsUDP(); + printThrottles(true); + } + } + done = true; + break; + } + break; + case LAN_X_HEADER_GET_LOCO_INFO: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d LOCO INFO: "), this->clientid); + notifyLocoInfo(DB[2], DB[3]); + done = true; + + break; + case LAN_X_HEADER_GET_TURNOUT_INFO: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d TURNOUT INFO "), this->clientid); + notifyTurnoutInfo(DB[1], DB[2]); + done = true; + + break; + case LAN_X_HEADER_GET_FIRMWARE_VERSION: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d FIRMWARE VERSION "), this->clientid); + notifyFirmwareVersion(); + done = true; + break; + case LAN_X_HEADER_CV_READ: + if (TrackManager::getProgDriver() != NULL) { + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d CV READ PROG "), this->clientid); + // DB0 should be 0x11 + cvReadProg(DB[2], DB[3]); + } + else { + // + // TODO Dont work today... + // + + // If no prog track, read on the main track ! + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d CV READ MAIN "), this->clientid); + // DB0 should be 0x11 + cvReadMain(DB[2], DB[3]); + } + done = true; + break; + case LAN_X_HEADER_CV_POM: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d CV READ POM"), this->clientid); + // DB0 should be 0x11 + cvReadPom(DB[2], DB[3], DB[4], DB[5]); + done = true; + break; + case LAN_X_HEADER_CV_WRITE: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d CV WRITE "), this->clientid); + notifyFirmwareVersion(); + done = true; + break; + case LAN_X_HEADER_SET_TURNOUT: + case 0x22: + break; + } + break; + + case HEADER_LAN_SET_BROADCASTFLAGS: + this->broadcastFlags = CircularBuffer::GetInt32(DB, 0); + if (Diag::Z21THROTTLEDATA) DIAG(F("BROADCAST FLAGS %d : %s %s %s %s %s %s %s %s %s %s %s"), this->clientid, + (this->broadcastFlags & BROADCAST_BASE) ? "BASE " : "" , + (this->broadcastFlags & BROADCAST_RBUS) ? "RBUS " : "" , + (this->broadcastFlags & BROADCAST_RAILCOM) ? "RAILCOM " : "" , + (this->broadcastFlags & BROADCAST_SYSTEM) ? "SYSTEM " : "" , + (this->broadcastFlags & BROADCAST_BASE_LOCOINFO) ? "LOCOINFO " : "" , + (this->broadcastFlags & BROADCAST_LOCONET) ? "LOCONET " : "" , + (this->broadcastFlags & BROADCAST_LOCONET_LOCO) ? "LOCONET_LOCO " : "" , + (this->broadcastFlags & BROADCAST_LOCONET_SWITCH) ? "LOCONET_SWITCH " : "" , + (this->broadcastFlags & BROADCAST_LOCONET_DETECTOR) ? "LOCONET_DETECTOR " : "" , + (this->broadcastFlags & BROADCAST_RAILCOM_AUTO) ? "RAILCOM_AUTO " : "" , + (this->broadcastFlags & BROADCAST_CAN) ? "CAN" : "" ); + done = true; + break; + case HEADER_LAN_GET_LOCOMODE: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d GET LOCOMODE"), this->clientid); + notifyLocoMode(DB[0], DB[1]); // big endian here, but resend the same as received, so no problem. + done = true; + break; + + case HEADER_LAN_SET_LOCOMODE: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d SET LOCOMODE"), this->clientid); + done = true; + break; + case HEADER_LAN_GET_HWINFO: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d GET HWINFO"), this->clientid); + notifyHWInfo(); // big endian here, but resend the same as received, so no problem. + done = true; + break; + case HEADER_LAN_LOGOFF: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d LOGOFF"), this->clientid); + this->clientid = -1; + done = true; + break; + case HEADER_LAN_SYSTEMSTATE_GETDATA: + if (Diag::Z21THROTTLEVERBOSE) DIAG(F("%d SYSTEMSTATE GETDATA"), this->clientid); + notifyStatus(); // big endian here, but resend the same as received, so no problem. + done = true; + break; + case HEADER_LAN_GET_SERIAL_NUMBER: + case HEADER_LAN_GET_BROADCASTFLAGS: + case HEADER_LAN_GET_TURNOUTMODE: + case HEADER_LAN_SET_TURNOUTMODE: + case HEADER_LAN_RMBUS_DATACHANGED: + case HEADER_LAN_RMBUS_GETDATA: + case HEADER_LAN_RMBUS_PROGRAMMODULE: + case HEADER_LAN_RAILCOM_DATACHANGED: + case HEADER_LAN_RAILCOM_GETDATA: + case HEADER_LAN_LOCONET_DISPATCH_ADDR: + case HEADER_LAN_LOCONET_DETECTOR: + break; + } + + if (!done) { + if (Diag::Z21THROTTLE) DIAG(F("Z21 Throttle %d : not treated : header:%x Xheader:%x DB0:%x"), this->clientid, header, Xheader, DB0); + } + else { + int newNbLocos = CountLocos(); + if (nbLocos != newNbLocos) + printLocomotives(); + } + return true; +} diff --git a/Z21Throttle.h b/Z21Throttle.h new file mode 100644 index 0000000..3d061f6 --- /dev/null +++ b/Z21Throttle.h @@ -0,0 +1,223 @@ +/* + * © 2023 Thierry Paris / Locoduino + * All rights reserved. + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ +#ifndef Z21Throttle_h +#define Z21Throttle_h + +#include "CircularBuffer.hpp" +#include "WiFiClient.h" + +#define UDPBYTE_SIZE 64 +#define UDP_BUFFERSIZE 256 + +struct MYLOCOZ21 { + char throttle; //indicates which throttle letter on client, '0' + clientid + int cab; //address of this loco + bool broadcastPending; + uint32_t functionMap; + uint32_t functionToggles; +}; + +class NetworkClientUDP { + public: + NetworkClientUDP() { + this->pudpBuffer = new CircularBuffer(UDP_BUFFERSIZE); + this->pudpBuffer->begin(true); + }; + bool ok() { + return (inUse); + }; + + bool inUse = true; + bool connected = false; + CircularBuffer *pudpBuffer = NULL; + IPAddress remoteIP; + int remotePort; + + static WiFiUDP client; +}; + +class Z21Throttle { + public: + static void loop(); + static Z21Throttle* getOrAddThrottle(int clientId); + static void markForBroadcast(int cab); + static void forget(byte clientId); + static void findUniqThrottle(int id, char *u); + static void setup(IPAddress ip, int port); + + void notifyCvNACK(int inCvAddress); + void notifyCvRead(int inCvAddress, int inValue); + + bool parse(); + + static Z21Throttle *readWriteThrottle; // NULL if no throttle is reading or writing a CV... + static int cvAddress; + static int cvValue; + + private: + Z21Throttle(int clientId); + ~Z21Throttle(); + + static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client + static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport + static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs + static Z21Throttle* firstThrottle; + static byte commBuffer[100]; + static byte replyBuffer[20]; + static char LorS(int cab); + static bool isThrottleInUse(int cab); + static void setSendTurnoutList(); + bool areYouUsingThrottle(int cab); + Z21Throttle* nextThrottle; + + int clientid; + char uniq[17] = ""; + + MYLOCOZ21 myLocos[MAX_MY_LOCO]; + + int CountLocos() { + int count = 0; + for (int loco=0;loco