diff --git a/README.md b/README.md index 0ee4b3e6..3e34d254 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,7 @@ This video explains all the above mentioned steps: https://youtu.be/_mH2AjnAjDk ## Dependencies 📖 -This code uses the following libraries, already located in the lib folder for an easy start: -- ESP32-Arduino-CAN (https://github.com/miwagner/ESP32-Arduino-CAN/) slightly modified for this usecase -- eModbus library (https://github.com/eModbus/eModbus) -- Adafruit Neopixel (https://github.com/adafruit/Adafruit_NeoPixel) -- mackelec SerialDataLink (https://github.com/mackelec/SerialDataLink) -- pierremolinaro acan2515 (https://github.com/pierremolinaro/acan2515) +This code uses two libraries, ESP32-Arduino-CAN (https://github.com/miwagner/ESP32-Arduino-CAN/) slightly modified for this usecase, and the eModbus library (https://github.com/eModbus/eModbus). Both these are already located in the Software folder for an easy start. It is also based on the info found in the following excellent repositories/websites: - https://gitlab.com/pelle8/gen24 diff --git a/Software/Software.ino b/Software/Software.ino index d01a10c9..e02a1979 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -11,15 +11,12 @@ #include "src/lib/eModbus-eModbus/Logging.h" #include "src/lib/eModbus-eModbus/ModbusServerRTU.h" #include "src/lib/eModbus-eModbus/scripts/mbServerFCs.h" -#include "src/lib/mackelec-SerialDataLink/SerialDataLink.h" #include "src/lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" // Interval settings int intervalUpdateValues = 4800; // Interval at which to update inverter values / Modbus registers -const int interval1 = 1; // Interval for 1ms tasks const int interval10 = 10; // Interval for 10ms tasks -unsigned long previousMillis1ms = 0; unsigned long previousMillis10ms = 50; unsigned long previousMillisUpdateVal = 0; @@ -131,9 +128,6 @@ void loop() { #ifdef DUAL_CAN receive_can2(); #endif -#ifdef SERIAL_LINK_TRANSMITTER_INVERTER - receive_serial(); -#endif // Process if (millis() - previousMillis10ms >= interval10) // Every 10ms @@ -156,9 +150,6 @@ void loop() { #ifdef DUAL_CAN send_can2(); #endif -#ifdef SERIAL_LINK_RECEIVER_FROM_BATTERY - send_serial(); -#endif } // Initialization functions @@ -226,13 +217,6 @@ void init_modbus() { pinMode(PIN_5V_EN, OUTPUT); digitalWrite(PIN_5V_EN, HIGH); -#if defined(SERIAL_LINK_RECEIVER_FROM_BATTERY) || defined(SERIAL_LINK_TRANSMITTER_INVERTER) - Serial2.begin(9600); // If the Modbus RTU port will be used for serial link -#if defined(BYD_MODBUS) || defined(LUNA2000_MODBUS) -#error Modbus pins cannot be used for Serial and Modbus at the same time! -#endif -#endif - #ifdef BYD_MODBUS // Init Static data to the RTU Modbus handle_static_data_modbus_byd(); @@ -402,26 +386,6 @@ void send_can() { #endif } -#ifdef SERIAL_LINK_RECEIVER_FROM_BATTERY -void send_serial() { - static unsigned long currentMillis = millis(); - if (currentMillis - previousMillis1ms >= interval1) { - previousMillis1ms = currentMillis; - manageSerialLinkReceiver(); - } -} -#endif - -#ifdef SERIAL_LINK_TRANSMITTER_INVERTER -void receive_serial() { - static unsigned long currentMillis = millis(); - if (currentMillis - previousMillis1ms >= interval1) { - previousMillis1ms = currentMillis; - manageSerialLinkTransmitter(); - } -} -#endif - #ifdef DUAL_CAN void receive_can2() { // This function is similar to receive_can, but just takes care of inverters in the 2nd bus. // Depending on which inverter is selected, we forward this to their respective CAN routines diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 24d2491e..c05068c6 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -42,7 +42,5 @@ //#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence //#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM logic for contactors, which lower power consumption and heat generation //#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for FoxESS inverters) -//#define SERIAL_LINK_RECEIVER_FROM_BATTERY //Enable this line to send battery data over Modbus pins to another Lilygo (This LilyGo interfaces with battery) -//#define SERIAL_LINK_TRANSMITTER_INVERTER //Enable this line to receive battery data over Modbus pins from another Lilygo (This LilyGo interfaces with inverter) #endif diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index 1880d8e8..d68a892f 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -32,9 +32,4 @@ #ifdef TEST_FAKE_BATTERY #include "TEST-FAKE-BATTERY.h" //See this file for more Fake battery settings #endif - -#ifdef SERIAL_LINK_RECEIVER_FROM_BATTERY -#include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h" -#endif - #endif diff --git a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp deleted file mode 100644 index 9d286324..00000000 --- a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp - -#include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h" - -//#define INVERTER_SEND_NUM_VARIABLES 3 //--- comment out if nothing to send -#define INVERTER_RECV_NUM_VARIABLES 16 - -#ifdef INVERTER_SEND_NUM_VARIABLES -const uint8_t sendingNumVariables = INVERTER_SEND_NUM_VARIABLES; -#else -const uint8_t sendingNumVariables = 0; -#endif - -// txid,rxid, num_send,num_recv -SerialDataLink dataLinkReceive(Serial2, 0, 0x01, sendingNumVariables, - INVERTER_RECV_NUM_VARIABLES); // ... - -void __getData() { - SOC = (uint16_t)dataLinkReceive.getReceivedData(0); - StateOfHealth = (uint16_t)dataLinkReceive.getReceivedData(1); - battery_voltage = (uint16_t)dataLinkReceive.getReceivedData(2); - battery_current = (uint16_t)dataLinkReceive.getReceivedData(3); - capacity_Wh = (uint16_t)dataLinkReceive.getReceivedData(4); - remaining_capacity_Wh = (uint16_t)dataLinkReceive.getReceivedData(5); - max_target_discharge_power = (uint16_t)dataLinkReceive.getReceivedData(6); - max_target_charge_power = (uint16_t)dataLinkReceive.getReceivedData(7); - bms_status = (uint16_t)dataLinkReceive.getReceivedData(8); - bms_char_dis_status = (uint16_t)dataLinkReceive.getReceivedData(9); - stat_batt_power = (uint16_t)dataLinkReceive.getReceivedData(10); - temperature_min = (uint16_t)dataLinkReceive.getReceivedData(11); - temperature_max = (uint16_t)dataLinkReceive.getReceivedData(12); - cell_max_voltage = (uint16_t)dataLinkReceive.getReceivedData(13); - cell_min_voltage = (uint16_t)dataLinkReceive.getReceivedData(14); - batteryAllowsContactorClosing = (uint16_t)dataLinkReceive.getReceivedData(15); -} - -void updateData() { - // --- update with fresh data - /* - dataLinkReceive.updateData(0,var1); - dataLinkReceive.updateData(1,var2); - dataLinkReceive.updateData(2,var3); - */ -} - -/* -* @ 9600bps, assume void manageSerialLinkReceiver() -* is called every 1mS -*/ - -void manageSerialLinkReceiver() { - static bool lasterror = false; - static unsigned long lastGood; - static uint16_t lastGoodMaxCharge; - static uint16_t lastGoodMaxDischarge; - static bool initLink = false; - - unsigned long currentTime = millis(); - - if (!initLink) { - initLink = true; - // sends variables every 5000mS even if no change - dataLinkReceive.setUpdateInterval(5000); -#ifdef SERIALDATALINK_MUTEACK - dataLinkReceive.muteACK(true); -#endif - } - dataLinkReceive.run(); - bool readError = dataLinkReceive.checkReadError(true); // check for error & clear error flag - LEDcolor = GREEN; - if (readError) { - LEDcolor = RED; - bms_status = 4; //FAULT - Serial.print(currentTime); - Serial.println(" - ERROR: Serial Data Link - Read Error"); - lasterror = true; - } else { - if (lasterror) { - lasterror = false; - Serial.print(currentTime); - Serial.println(" - RECOVERY: Serial Data Link - Read GOOD"); - } - lastGood = currentTime; - } - if (dataLinkReceive.checkNewData(true)) // true = clear Flag - { - __getData(); - lastGoodMaxCharge = max_target_charge_power; - lastGoodMaxDischarge = max_target_discharge_power; - } - - unsigned long minutesLost = (currentTime - lastGood) / 60000UL; - ; - if (minutesLost > 0 && lastGood > 0) { - // lose 25% each minute of data loss - if (minutesLost < 4) { - max_target_charge_power = (lastGoodMaxCharge * (4 - minutesLost)) / 4; - max_target_discharge_power = (lastGoodMaxDischarge * (4 - minutesLost)) / 4; - } else { - max_target_charge_power = 0; - max_target_discharge_power = 0; - } - } - - static unsigned long updateTime = 0; - -#ifdef INVERTER_SEND_NUM_VARIABLES - if (currentTime - updateTime > 100) { - updateTime = currentTime; - dataLinkReceive.run(); - bool sendError = dataLinkReceive.checkTransmissionError(true); // check for error & clear error flag - updateData(); - } -#endif -} diff --git a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h deleted file mode 100644 index 65dc04fd..00000000 --- a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h +++ /dev/null @@ -1,38 +0,0 @@ -// SERIAL-LINK-RECEIVER-FROM-BATTERY.h - -#ifndef SERIAL_LINK_RECEIVER_FROM_BATTERY_H -#define SERIAL_LINK_RECEIVER_FROM_BATTERY_H - -#include -#include "../../USER_SETTINGS.h" -#include "../devboard/config.h" // Needed for LED defines -#include "../lib/mackelec-SerialDataLink/SerialDataLink.h" - -// https://github.com/mackelec/SerialDataLink - -#define ABSOLUTE_MAX_VOLTAGE \ - 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge) -#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled - -// These parameters need to be mapped for the inverter -extern uint16_t SOC; //SOC%, 0-100.00 (0-10000) -extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000) -extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000) -extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485) -extern uint16_t capacity_Wh; //Wh, 0-60000 -extern uint16_t remaining_capacity_Wh; //Wh, 0-60000 -extern uint16_t max_target_discharge_power; //W, 0-60000 -extern uint16_t max_target_charge_power; //W, 0-60000 -extern uint16_t bms_status; //Enum, 0-5 -extern uint16_t bms_char_dis_status; //Enum, 0-2 -extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530) -extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) -extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) -extern uint16_t cell_max_voltage; //mV, 0-4350 -extern uint16_t cell_min_voltage; //mV, 0-4350 -extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false -extern uint8_t LEDcolor; //Enum, 0-10 - -void manageSerialLinkReceiver(); - -#endif diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index 9d8825f7..97527c2b 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -29,8 +29,4 @@ #include "SOLAX-CAN.h" #endif -#ifdef SERIAL_LINK_TRANSMITTER_INVERTER -#include "SERIAL-LINK-TRANSMITTER-INVERTER.h" -#endif - #endif diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp deleted file mode 100644 index 229d0bd5..00000000 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp +++ /dev/null @@ -1,85 +0,0 @@ -//SERIAL-LINK-TRANSMITTER-INVERTER.cpp - -#include "SERIAL-LINK-TRANSMITTER-INVERTER.h" - -/* -* SerialDataLink -* txid=1, rxid=0 gives this the startup transmit priority_queue -* Will transmit max 16 int variable - receive none -*/ - -#define BATTERY_SEND_NUM_VARIABLES 16 -//#define BATTERY_RECV_NUM_VARIABLES 3 //--- comment out if nothing to receive - -#ifdef BATTERY_RECV_NUM_VARIABLES -const uint8_t receivingNumVariables = BATTERY_RECV_NUM_VARIABLES; -#else -const uint8_t receivingNumVariables = 0; -#endif - -// txid,rxid,num_tx,num_rx -SerialDataLink dataLinkTransmit(Serial2, 0x01, 0, BATTERY_SEND_NUM_VARIABLES, receivingNumVariables); - -void _getData() { - /* - var1 = dataLinkTransmit.getReceivedData(0); - var2 = dataLinkTransmit.getReceivedData(1); - var3 = dataLinkTransmit.getReceivedData(2); - */ -} - -void manageSerialLinkTransmitter() { - static bool initLink = false; - static unsigned long updateTime = 0; - static bool lasterror = false; - - dataLinkTransmit.run(); - -#ifdef BATTERY_RECV_NUM_VARIABLES - bool readError = dataLinkTransmit.checkReadError(true); // check for error & clear error flag - if (dataLinkTransmit.checkNewData(true)) // true = clear Flag - { - _getData(); - } -#endif - - if (millis() - updateTime > 100) { - updateTime = millis(); - if (!initLink) { - initLink = true; - // sends variables every 5000mS even if no change - dataLinkTransmit.setUpdateInterval(5000); - } - bool sendError = dataLinkTransmit.checkTransmissionError(true); - LEDcolor = GREEN; - if (sendError) { - LEDcolor = RED; - Serial.print(millis()); - Serial.println(" - ERROR: Serial Data Link - SEND Error"); - lasterror = true; - } else { - if (lasterror) { - lasterror = false; - Serial.print(millis()); - Serial.println(" - RECOVERY: Serial Data Link - Send GOOD"); - } - } - - dataLinkTransmit.updateData(0, SOC); - dataLinkTransmit.updateData(1, StateOfHealth); - dataLinkTransmit.updateData(2, battery_voltage); - dataLinkTransmit.updateData(3, battery_current); - dataLinkTransmit.updateData(4, capacity_Wh); - dataLinkTransmit.updateData(5, remaining_capacity_Wh); - dataLinkTransmit.updateData(6, max_target_discharge_power); - dataLinkTransmit.updateData(7, max_target_charge_power); - dataLinkTransmit.updateData(8, bms_status); - dataLinkTransmit.updateData(9, bms_char_dis_status); - dataLinkTransmit.updateData(10, stat_batt_power); - dataLinkTransmit.updateData(11, temperature_min); - dataLinkTransmit.updateData(12, temperature_max); - dataLinkTransmit.updateData(13, cell_max_voltage); - dataLinkTransmit.updateData(14, cell_min_voltage); - dataLinkTransmit.updateData(15, batteryAllowsContactorClosing); - } -} diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h deleted file mode 100644 index 68625b89..00000000 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h +++ /dev/null @@ -1,32 +0,0 @@ -//SERIAL-LINK-TRANSMITTER-INVERTER.h - -#ifndef SERIAL_LINK_TRANSMITTER_INVERTER_H -#define SERIAL_LINK_TRANSMITTER_INVERTER_H - -#include -#include "../../USER_SETTINGS.h" -#include "../devboard/config.h" // Needed for LED defines -#include "../lib/mackelec-SerialDataLink/SerialDataLink.h" - -// These parameters need to be mapped for the inverter -extern uint16_t SOC; //SOC%, 0-100.00 (0-10000) -extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000) -extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000) -extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485) -extern uint16_t capacity_Wh; //Wh, 0-60000 -extern uint16_t remaining_capacity_Wh; //Wh, 0-60000 -extern uint16_t max_target_discharge_power; //W, 0-60000 -extern uint16_t max_target_charge_power; //W, 0-60000 -extern uint16_t bms_status; //Enum, 0-5 -extern uint16_t bms_char_dis_status; //Enum, 0-2 -extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530) -extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) -extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) -extern uint16_t cell_max_voltage; //mV, 0-4350 -extern uint16_t cell_min_voltage; //mV, 0-4350 -extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false -extern uint8_t LEDcolor; //Enum, 0-10 - -void manageSerialLinkTransmitter(); - -#endif diff --git a/Software/src/lib/mackelec-SerialDataLink/SerialDataLink.cpp b/Software/src/lib/mackelec-SerialDataLink/SerialDataLink.cpp deleted file mode 100644 index 31a6902c..00000000 --- a/Software/src/lib/mackelec-SerialDataLink/SerialDataLink.cpp +++ /dev/null @@ -1,568 +0,0 @@ -// SerialDataLink.cpp - -#include "SerialDataLink.h" - - -const uint16_t crcTable[256] = { - 0, 32773, 32783, 10, 32795, 30, 20, 32785, - 32819, 54, 60, 32825, 40, 32813, 32807, 34, - 32867, 102, 108, 32873, 120, 32893, 32887, 114, - 80, 32853, 32863, 90, 32843, 78, 68, 32833, - 32963, 198, 204, 32969, 216, 32989, 32983, 210, - 240, 33013, 33023, 250, 33003, 238, 228, 32993, - 160, 32933, 32943, 170, 32955, 190, 180, 32945, - 32915, 150, 156, 32921, 136, 32909, 32903, 130, - 33155, 390, 396, 33161, 408, 33181, 33175, 402, - 432, 33205, 33215, 442, 33195, 430, 420, 33185, - 480, 33253, 33263, 490, 33275, 510, 500, 33265, - 33235, 470, 476, 33241, 456, 33229, 33223, 450, - 320, 33093, 33103, 330, 33115, 350, 340, 33105, - 33139, 374, 380, 33145, 360, 33133, 33127, 354, - 33059, 294, 300, 33065, 312, 33085, 33079, 306, - 272, 33045, 33055, 282, 33035, 270, 260, 33025, - 33539, 774, 780, 33545, 792, 33565, 33559, 786, - 816, 33589, 33599, 826, 33579, 814, 804, 33569, - 864, 33637, 33647, 874, 33659, 894, 884, 33649, - 33619, 854, 860, 33625, 840, 33613, 33607, 834, - 960, 33733, 33743, 970, 33755, 990, 980, 33745, - 33779, 1014, 1020, 33785, 1000, 33773, 33767, 994, - 33699, 934, 940, 33705, 952, 33725, 33719, 946, - 912, 33685, 33695, 922, 33675, 910, 900, 33665, - 640, 33413, 33423, 650, 33435, 670, 660, 33425, - 33459, 694, 700, 33465, 680, 33453, 33447, 674, - 33507, 742, 748, 33513, 760, 33533, 33527, 754, - 720, 33493, 33503, 730, 33483, 718, 708, 33473, - 33347, 582, 588, 33353, 600, 33373, 33367, 594, - 624, 33397, 33407, 634, 33387, 622, 612, 33377, - 544, 33317, 33327, 554, 33339, 574, 564, 33329, - 33299, 534, 540, 33305, 520, 33293, 33287, 514 -}; - -union Convert -{ - uint16_t u16; - int16_t i16; - struct - { - byte low; - byte high; - }; -}convert; - - - - - -// Constructor -SerialDataLink::SerialDataLink(Stream &serial, uint8_t transmitID, uint8_t receiveID, uint8_t maxIndexTX, uint8_t maxIndexRX, bool enableRetransmit) - : serial(serial), transmitID(transmitID), receiveID(receiveID), maxIndexTX(maxIndexTX), maxIndexRX(maxIndexRX), retransmitEnabled(enableRetransmit) { - // Initialize buffers and state variables - txBufferIndex = 0; - isTransmitting = false; - isReceiving = false; - transmissionError = false; - readError = false; - newData = false; - - // Initialize data arrays and update flags - - memset(dataArrayTX, 0, sizeof(dataArrayTX)); - memset(dataArrayRX, 0, sizeof(dataArrayRX)); - memset(dataUpdated, 0, sizeof(dataUpdated)); - memset(lastSent , 0, sizeof(lastSent )); - - // Additional initialization as required -} - -void SerialDataLink::updateData(uint8_t index, int16_t value) -{ - if (index < maxIndexTX) - { - if (dataArrayTX[index] != value) - { - dataArrayTX[index] = value; - dataUpdated[index] = true; - lastSent[index] = millis(); - } - } -} - -int16_t SerialDataLink::getReceivedData(uint8_t index) -{ - if (index < dataArraySizeRX) { - return dataArrayRX[index]; - } else { - // Handle the case where the index is out of bounds - return -1; - } -} - -bool SerialDataLink::checkTransmissionError(bool resetFlag) -{ - bool currentStatus = transmissionError; - if (resetFlag && transmissionError) { - transmissionError = false; - } - return currentStatus; -} - -bool SerialDataLink::checkReadError(bool reset) -{ - bool error = readError; - if (reset) { - readError = false; - } - return error; -} - - -bool SerialDataLink::checkNewData(bool resetFlag) { - bool currentStatus = newData; - if (resetFlag && newData) { - newData = false; - } - return currentStatus; -} - -void SerialDataLink::muteACK(bool mute) -{ - muteAcknowledgement = mute; -} - -void SerialDataLink::run() -{ - unsigned long currentTime = millis(); - static DataLinkState oldstate; - - - // Check if state has not changed for a prolonged period - if (oldstate != currentState) - { - lastStateChangeTime = currentTime; - oldstate = currentState; - } - if ((currentTime - lastStateChangeTime) > stateChangeTimeout) { - // Reset the state to Idle and perform necessary cleanup - currentState = DataLinkState::Idle; - // Perform any additional cleanup or reinitialization here - // ... - - lastStateChangeTime = currentTime; // Reset the last state change time - } - switch (currentState) - { - case DataLinkState::Idle: - // Decide if the device should start transmitting - currentState = DataLinkState::Receiving; - if (shouldTransmit()) - { - currentState = DataLinkState::Transmitting; - } - break; - - case DataLinkState::Transmitting: - if (isTransmitting) - { - sendNextByte(); // Continue sending the current data - } - else - { - - constructPacket(); // Construct a new packet if not currently transmitting - - if (muteAcknowledgement) - { - needToACK = false; - needToNACK = false; - } - uint8_t ack; - // now it is known which acknoledge need sending since last Reception - if (needToACK) - { - needToACK = false; - ack = (txBufferIndex > 5) ? ACK_RTT_CODE : ACK_CODE; - serial.write(ack); - } - if (needToNACK) - { - needToNACK = false; - ack = (txBufferIndex > 5) ? NACK_RTT_CODE : NACK_CODE; - serial.write(ack); - } - } - - if (maxIndexTX < 1) - { - currentState = DataLinkState::Receiving; - } - // Check if the transmission is complete - if (transmissionComplete) - { - transmissionComplete = false; - isTransmitting = false; - currentState = DataLinkState::WaitingForAck; // Move to WaitingForAck state - } - break; - - - case DataLinkState::WaitingForAck: - if (ackTimeout()) - { - // Handle ACK timeout scenario - transmissionError = true; - isTransmitting = false; - //handleAckTimeout(); - //--- if no ACK's etc received may as well move to Transmitting - currentState = DataLinkState::Transmitting; - } - if (ackReceived()) - { - // No data to send from the other device - currentState = DataLinkState::Transmitting; - } - if (requestToSend) - { - // The other device has data to send (indicated by ACK+RTT) - currentState = DataLinkState::Receiving; - } - break; - - - case DataLinkState::Receiving: - read(); - if (readComplete) - { - readComplete = false; - // transition to transmit mode - currentState = DataLinkState::Transmitting; - } - break; - - default: - currentState = DataLinkState::Idle; - } -} - -void SerialDataLink::updateState(DataLinkState newState) -{ - if (currentState != newState) - { - currentState = newState; - lastStateChangeTime = millis(); - } -} - -bool SerialDataLink::shouldTransmit() -{ - // Priority condition: Device with transmitID = 1 and receiveID = 0 has the highest priority - if (transmitID == 1 && receiveID == 0) - { - return true; - } - return false; -} - -void SerialDataLink::constructPacket() -{ - if (maxIndexTX <1) return; - if (!isTransmitting) - { - lastTransmissionTime = millis(); - txBufferIndex = 0; // Reset the TX buffer index - - addToTxBuffer(headerChar); - addToTxBuffer(transmitID); - addToTxBuffer(0); // EOT position - place holder - unsigned long currentTime = millis(); - int count = txBufferIndex; - - for (uint8_t i = 0; i < maxIndexTX; i++) - { - if (dataUpdated[i] || (currentTime - lastSent[i] >= updateInterval)) - { - addToTxBuffer(i); - convert.i16 = dataArrayTX[i]; - addToTxBuffer(convert.high); - addToTxBuffer(convert.low); - - dataUpdated[i] = false; - lastSent[i] = currentTime; // Update the last sent time for this index - } - } - - if (count == txBufferIndex) - { - // No data was added to the buffer, so no need to send a packet - return; - } - - addToTxBuffer(eotChar); - //----- assign EOT position - txBuffer[2] = txBufferIndex - 1; - uint16_t crc = calculateCRC16(txBuffer, txBufferIndex); - convert.u16 = crc; - addToTxBuffer(convert.high); - addToTxBuffer(convert.low); - isTransmitting = true; - } -} - - -void SerialDataLink::addToTxBuffer(uint8_t byte) -{ - if (txBufferIndex < txBufferSize) - { - txBuffer[txBufferIndex] = byte; - txBufferIndex++; - } -} - -bool SerialDataLink::sendNextByte() -{ - if (!isTransmitting) return false; - - if (txBufferIndex >= txBufferSize) - { - txBufferIndex = 0; // Reset the TX buffer index - isTransmitting = false; - return false; // Buffer was fully sent, end transmission - } - serial.write(txBuffer[sendBufferIndex]); - sendBufferIndex++; - - if (sendBufferIndex >= txBufferIndex) - { - isTransmitting = false; - txBufferIndex = 0; // Reset the TX buffer index for the next packet - sendBufferIndex = 0; - transmissionComplete = true; - return true; // Packet was fully sent - } - return false; // More bytes remain to be sent -} - -bool SerialDataLink::ackReceived() -{ - // Check if there is data available to read - if (serial.available() > 0) - { - // Peek at the next byte without removing it from the buffer - uint8_t nextByte = serial.peek(); - - if (nextByte == headerChar) - { - requestToSend = true; - return false; - } - - uint8_t receivedByte = serial.read(); - - switch (receivedByte) - { - case ACK_CODE: - // Handle standard ACK - return true; - - case ACK_RTT_CODE: - // Handle ACK with request to transmit - requestToSend = true; - return true; - - case NACK_RTT_CODE: - requestToSend = true; - case NACK_CODE: - transmissionError = true; - return false; - - default: - break; - } - - } - - return false; // No ACK, NACK, or new packet received -} - -bool SerialDataLink::ackTimeout() -{ - // Check if the current time has exceeded the last transmission time by the ACK timeout period - if (millis() - lastTransmissionTime > ACK_TIMEOUT) { - return true; // Timeout occurred - } - return false; // No timeout -} - - - -void SerialDataLink::read() -{ - if (maxIndexRX < 1) return; - if (serial.available()) - { - //Serial.print("."); - if (millis() - lastHeaderTime > PACKET_TIMEOUT && rxBufferIndex > 0) - { - // Timeout occurred, reset buffer and pointer - rxBufferIndex = 0; - eotPosition = 0; - readError = true; - } - uint8_t incomingByte = serial.read(); - switch (rxBufferIndex) { - case 0: // Looking for the header - if (incomingByte == headerChar) - { - lastHeaderTime = millis(); - rxBuffer[rxBufferIndex] = incomingByte; - rxBufferIndex++; - } - break; - - case 1: // Looking for the address - if (incomingByte == receiveID) { - rxBuffer[rxBufferIndex] = incomingByte; - rxBufferIndex++; - } else { - // Address mismatch, reset to look for a new packet - rxBufferIndex = 0; - } - break; - - case 2: // EOT position - eotPosition = incomingByte; - rxBuffer[rxBufferIndex] = incomingByte; - rxBufferIndex++; - break; - - default: - // Normal data handling - rxBuffer[rxBufferIndex] = incomingByte; - rxBufferIndex++; - - if (isCompletePacket()) - { - processPacket(); - rxBufferIndex = 0; // Reset for the next packet - readComplete = true; // Indicate that read operation is complete - } - - // Check for buffer overflow - if (rxBufferIndex >= rxBufferSize) - { - rxBufferIndex = 0; - } - break; - } - } -} - -bool SerialDataLink::isCompletePacket() { - if (rxBufferIndex - 3 < eotPosition) return false; - // Ensure there are enough bytes for EOT + 2-byte CRC - - // Check if the third-last byte is the EOT character - if (rxBuffer[eotPosition] == eotChar) - { - return true; - } - return false; -} - -bool SerialDataLink::checkCRC() -{ - uint16_t receivedCrc; - if (rxBufferIndex < 3) - { - // Not enough data for CRC check - return false; - } - - - convert.high = rxBuffer[rxBufferIndex - 2]; - convert.low = rxBuffer[rxBufferIndex - 1]; - receivedCrc = convert.u16; - - // Calculate CRC for the received data (excluding the CRC bytes themselves) - uint16_t calculatedCrc = calculateCRC16(rxBuffer, rxBufferIndex - 2); - return receivedCrc == calculatedCrc; -} - - -void SerialDataLink::processPacket() -{ - - if (!checkCRC()) { - // CRC check failed, handle the error - readError = true; - return; - } - - // Start from index 3 to skip the SOT and ADDRESS and EOT Position characters - uint8_t i = 3; - while (i < eotPosition) - { - uint8_t arrayID = rxBuffer[i++]; - - // Make sure there's enough data for a complete int16 (2 bytes) - if (i + 1 >= rxBufferIndex) { - readError = true; - needToNACK = true; - return; // Incomplete packet or buffer overflow - } - - // Combine the next two bytes into an int16 value - int16_t value = (int16_t(rxBuffer[i]) << 8) | int16_t(rxBuffer[i + 1]); - i += 2; - - // Handle the array ID and value here - if (arrayID < dataArraySizeRX) { - dataArrayRX[arrayID] = value; - } - else - { - // Handle invalid array ID - readError = true; - needToNACK = true; - return; - } - newData = true; - needToACK = true; - } -} - - - -void SerialDataLink::setUpdateInterval(unsigned long interval) { - updateInterval = interval; -} - -void SerialDataLink::setAckTimeout(unsigned long timeout) { - ACK_TIMEOUT = timeout; -} - -void SerialDataLink::setPacketTimeout(unsigned long timeout) { - PACKET_TIMEOUT = timeout; -} - -void SerialDataLink::setHeaderChar(char header) -{ - headerChar = header; -} - -void SerialDataLink::setEOTChar(char eot) -{ - eotChar = eot; -} - - - -uint16_t SerialDataLink::calculateCRC16(const uint8_t* data, size_t length) -{ - uint16_t crc = 0xFFFF; // Start value for CRC - for (size_t i = 0; i < length; i++) - { - uint8_t index = (crc >> 8) ^ data[i]; - crc = (crc << 8) ^ crcTable[index]; - } - return crc; -} diff --git a/Software/src/lib/mackelec-SerialDataLink/SerialDataLink.h b/Software/src/lib/mackelec-SerialDataLink/SerialDataLink.h deleted file mode 100644 index 258939c4..00000000 --- a/Software/src/lib/mackelec-SerialDataLink/SerialDataLink.h +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @file SerialDataLink.h - * @brief Half-Duplex Serial Data Link for Arduino - * - * This file contains the definition of the SerialDataLink class, designed to facilitate - * half-duplex communication between Arduino controllers. The class employs a non-blocking, - * poll-based approach to transmit and receive data, making it suitable for applications - * where continuous monitoring and variable transfer between controllers are required. - * - * The half-duplex nature of this implementation allows for data transfer in both directions, - * but not simultaneously, ensuring a controlled communication flow and reducing the likelihood - * of data collision. - * - * - * @author MackElec - * @web https://github.com/mackelec/SerialDataLink - * @license MIT - */ - -// ... Class definition ... - -/** - * @class SerialDataLink - * @brief Class for managing half-duplex serial communication. - * - * Provides functions to send and receive data in a half-duplex manner over a serial link. - * It supports non-blocking operation with a polling approach to check for new data and - * transmission errors. - * - * Public Methods: - * - SerialDataLink(): Constructor to initialize the communication parameters. - * - run(): Main method to be called frequently to handle data transmission and reception. - * - updateData(): Method to update data to be transmitted. - * - getReceivedData(): Retrieves data received from the serial link. - * - checkNewData(): Checks if new data has been received. - * - checkTransmissionError(): Checks for transmission errors. - * - checkReadError(): Checks for read errors. - * - setUpdateInterval(): Sets the interval for data updates. - * - setAckTimeout(): Sets the timeout for acknowledgments. - * - setPacketTimeout(): Sets the timeout for packet reception. - * - setHeaderChar(): Sets the character used to denote the start of a packet. - * - setEOTChar(): Sets the character used to denote the end of a packet. - */ - - - - -#ifndef SERIALDATALINK_H -#define SERIALDATALINK_H - -#include - -class SerialDataLink { -public: - // Constructor - SerialDataLink(Stream &serial, uint8_t transmitID, uint8_t receiveID, uint8_t maxIndexTX, uint8_t maxIndexRX, bool enableRetransmit = false); - - // Method to handle data transmission and reception - void run(); - - void updateData(uint8_t index, int16_t value); - - // Check if new data has been received - bool checkNewData(bool resetFlag); - int16_t getReceivedData(uint8_t index); - - // Check for errors - bool checkTransmissionError(bool resetFlag); - bool checkReadError(bool resetFlag); - - // Setter methods for various parameters and special characters - - void setUpdateInterval(unsigned long interval); - void setAckTimeout(unsigned long timeout); - void setPacketTimeout(unsigned long timeout); - - void setHeaderChar(char header); - void setEOTChar(char eot); - void muteACK(bool mute); - -private: - enum class DataLinkState - { - Idle, - Transmitting, - WaitingForAck, - Receiving, - Error - }; - - DataLinkState currentState; - Stream &serial; - uint8_t transmitID; - uint8_t receiveID; - - // Separate max indices for TX and RX - const uint8_t maxIndexTX; - const uint8_t maxIndexRX; - - - // Buffer and state management - static const uint8_t txBufferSize = 128; // Adjust size as needed - static const uint8_t rxBufferSize = 128; // Adjust size as needed - - uint8_t txBuffer[txBufferSize]; - uint8_t rxBuffer[rxBufferSize]; - - uint8_t txBufferIndex; - uint8_t rxBufferIndex; - uint8_t sendBufferIndex = 0; - - bool isTransmitting; - bool transmissionComplete = false; - bool isReceiving; - bool readComplete = false; - bool retransmitEnabled; - bool transmissionError = false; - bool readError = false; - bool muteAcknowledgement = false; - - // Data arrays and update management - - static const uint8_t dataArraySizeTX = 20; // Adjust size as needed for TX - static const uint8_t dataArraySizeRX = 20; // Adjust size as needed for RX - - int16_t dataArrayTX[dataArraySizeTX]; - int16_t dataArrayRX[dataArraySizeRX]; - bool dataUpdated[dataArraySizeTX]; - unsigned long lastSent[dataArraySizeTX]; - - unsigned long updateInterval = 500; - unsigned long ACK_TIMEOUT = 100; - unsigned long PACKET_TIMEOUT = 100; // Timeout in milliseconds - - unsigned long lastStateChangeTime = 0; - unsigned long stateChangeTimeout = 200; - - // Special characters for packet framing - char headerChar = '<'; - char eotChar = '>'; - - static const uint8_t ACK_CODE = 0x06; // Standard acknowledgment - static const uint8_t ACK_RTT_CODE = 0x07; // Acknowledgment with request to transmit - static const uint8_t NACK_CODE = 0x08; // Negative acknowledgment - static const uint8_t NACK_RTT_CODE = 0x09; // Negative acknowledgment with request to transmit - - - - // Internal methods for packet construction, transmission, and reception - bool shouldTransmit(); - void constructPacket(); - void addToTxBuffer(uint8_t byte); - bool sendNextByte(); - bool ackReceived(); - bool ackTimeout(); - void updateState(DataLinkState newState); - - // Internal methods for reception - void read(); - void handleResendRequest(); - bool isCompletePacket(); - void processPacket(); - void sendACK(); - bool checkCRC(); - uint16_t calculateCRC16(const uint8_t* data, size_t length); - - unsigned long lastTransmissionTime; - bool requestToSend = false; - unsigned long lastHeaderTime = 0; - bool newData = false; - bool needToACK = false; - bool needToNACK = false; - uint8_t eotPosition = 0; -}; - -#endif // SERIALDATALINK_H