diff --git a/client/client_satellite/src/main.cpp b/client/client_satellite/src/main.cpp index d041c8197d72678d00cd7fae2831aa3d5360ef66..770e9204dfdea6fcf01e18dc592d5722b0902315 100644 --- a/client/client_satellite/src/main.cpp +++ b/client/client_satellite/src/main.cpp @@ -88,7 +88,7 @@ void setup() { // auto messages3 = dr26_channel3.buildMessages(); // auto messages4 = battery_monitor.buildMessages(); ForteRS485::powerOnRS485Sensors(); - auto soilMessage = rainGaugeSensor.buildMessages(); + auto rainMessage = rainGaugeSensor.buildMessages(); ForteRS485::powerOffRS485Sensors(); // roughly takes 500ms, ~120ms for each adc channel, barely anything for battery monitor ESP_LOGD(TAG, "Reading data and building messages took %ld ms", millis() - ts); @@ -96,9 +96,9 @@ void setup() { // std::array<Message, 6> messages = // {messages0.front(), messages1.front(), messages2.front(), - // messages3.front(), messages4.front(), soilMessage.front()}; + // messages3.front(), messages4.front(), rainMessage.front()}; std::array<Message, 6> messages = {Message::nullMessage(), Message::nullMessage(), Message::nullMessage(), - Message::nullMessage(), Message::nullMessage(), soilMessage.front()}; + Message::nullMessage(), Message::nullMessage(), rainMessage.front()}; ts = millis(); espnow_setup(); diff --git a/client/libs/SentecSensors/SentecRainGaugeSensor.cpp b/client/libs/SentecSensors/SentecRainGaugeSensor.cpp index befb26d7d700b1fdbba3ada325efbba0b8eccf92..716c129743f2c68002b277c90b5af82ee505751c 100644 --- a/client/libs/SentecSensors/SentecRainGaugeSensor.cpp +++ b/client/libs/SentecSensors/SentecRainGaugeSensor.cpp @@ -6,18 +6,6 @@ static const char *TAG = "SEM404"; -bool SEM404::isAnswerFrameEqualToQueryFrame(byte answerFrame[8], byte queryFrame[8]) { - for (int i = 0; i < 8; i++) { - if (answerFrame[i] != queryFrame[i]) { - ESP_LOGD(TAG, "Response: 0x%s", formatBytes(answerFrame, 8).c_str()); - ESP_LOGD(TAG, "Response: 0x%s", formatBytes(queryFrame, 8).c_str()); - ESP_LOGE("TEST", "Error: answerFrame[%d] != ret.writtenQuery[%d]", i, i); - return false; - } - } - return true; -} - ErrorType SEM404::resetPrecipitation() { // clears rainfall rata from the rain gauge // delay resetting after the last register reading diff --git a/client/libs/SentecSensors/SentecRainGaugeSensor.h b/client/libs/SentecSensors/SentecRainGaugeSensor.h index 669801afd3124563d03b7718a569ee2c1caae939..521459d1219209f30caa2c8bd0266a917ba0b328 100644 --- a/client/libs/SentecSensors/SentecRainGaugeSensor.h +++ b/client/libs/SentecSensors/SentecRainGaugeSensor.h @@ -47,14 +47,5 @@ class SEM404 : public SentecSensorRS485, ForteSensor<out_data_rain_gauge> { out_data_rain_gauge readData() override; std::list<Message> buildMessages() override; [[nodiscard]] SensorInformation getSensorInformation() const override; - - private: - /** - * @brief Check if the answer frame is equal to the query frame - * @param answerFrame The frame returned from the device - * @param queryFrame The frame sent to the device - * @return true if the answer frame is equal to the query frame - */ - bool isAnswerFrameEqualToQueryFrame(byte *answerFrame, byte *queryFrame); }; #endif // CLIENT_CENTRAL_MAST_SENTECRAINGAUGESENSOR_H diff --git a/client/libs/SentecSensors/SentecSensorsRS485.cpp b/client/libs/SentecSensors/SentecSensorsRS485.cpp index 583c799c80363339af6f60649376e52acd64e33e..d483631d20776ac901a38354e9e2049d606b72bc 100644 --- a/client/libs/SentecSensors/SentecSensorsRS485.cpp +++ b/client/libs/SentecSensors/SentecSensorsRS485.cpp @@ -1,4 +1,5 @@ #include "NoDataAvailableException.hpp" +#include "SentecRainGaugeSensor.h" #include <SentecSensorsRS485.h> /*************************************** @@ -28,19 +29,11 @@ void SentecSensorRS485::write(byte queryFrame[], int length) { } String SentecSensorRS485::getValueStr(float value) { - if (valid) { - return String(value, 1); - } else { - return {"null"}; - } + return String(value, 1); } String SentecSensorRS485::getValueStr(int value) { - if (valid) { - return String(value); - } else { - return {"null"}; - } + return String(value); } ErrorType SentecSensorRS485::queryAddress() { @@ -105,7 +98,11 @@ ErrorType SentecSensorRS485::setAddress(byte add) { WriteRegisterReturnType tmp = writeRegister(word(0x07, 0xD0), add); if (tmp.errorType == ErrorType::DATA_OK) address = add; - // TODO check response: matches the sent message exactly + + if (!isAnswerFrameEqualToQueryFrame(answerFrame, tmp.writtenQuery)) { + return ErrorType::COULD_NOT_SET_ADDRESS; + } + return tmp.errorType; } @@ -118,61 +115,34 @@ void SentecSensorRS485::resetAnswerFrame() { ErrorType SentecSensorRS485::getResponse() { // reads the response of a sensor ErrorType returnCode = ErrorType::DATA_OK; - valid = true; int idx = 0; - int byteReceived; // usual response length: changed in the while loop to match the response, // changed only when reading data (then it's 7 or 9 bytes, sensor dpendent) int responseLength = 8; // reading an answer takes up to 39 milliseconds for 2 byte readRegister - const int timeout = 2000; const int retries = 1; // #editet to q - size_t tries = 1; // it doesn't seem to help to request multiple times if first time goes wrong - for (tries; tries <= retries; tries++) { + for (size_t tries = 1; tries <= retries; tries++) { // if we lose connection with the sensor, we get an array of zeros back resetAnswerFrame(); - unsigned long time = millis(); - while (idx < responseLength && (millis() - time) < timeout) { - if (RS485->available()) { - byteReceived = RS485->read(); - // Serial.println(byteReceived, HEX); - // check for first byte. It has to be the device address unless for broadcasts with address = 0xFF - if (idx == 0 && address != 0xFF && byteReceived != address) { - ESP_LOGE(TAG, "Invalid byte. First byte needs to be address 0x%02X but got 0x%02Xinstead"); - } else { - answerFrame[idx] = byteReceived; - // for reading register: third received byte is data length, read number of bytes accordingly - if (idx == 2 && answerFrame[1] == 0x03) { - // 5 bytes for address, function code, data length, CRC_H, CRC_L - responseLength = 5 + byteReceived; - } - idx++; - } - } - } - ESP_LOGD(TAG, "-----------------------------------------------------------------------------------------"); + readRS485(idx, responseLength); + ESP_LOGD(TAG, "Response: 0x%s", formatBytes(answerFrame, responseLength).c_str()); - // ESP_LOGD(TAG, "Tries: %d", tries); - // ESP_LOGD(TAG, "Bytes received: %d", idx); - if (answerFrame[0] == 0) { - ESP_LOGE(TAG, "Check sensor connection. First byte is 0x00."); - valid = false; - returnCode = ErrorType::SENSOR_NOT_CONNECTED; - } else if (idx < responseLength) { - ESP_LOGE(TAG, "Response too short: %d bytes < %d bytes. Unfinished transmission.", idx, responseLength); - valid = false; - returnCode = ErrorType::CONNECTION_ENDED_PREMATURELY; - } - word crc_received = word(answerFrame[responseLength - 2], answerFrame[responseLength - 1]); - word crc = calculateCRC(answerFrame, responseLength - 2); - if (valid && crc_received != word(crc)) { - ESP_LOGE(TAG, "CRC wrong: Expected 0x%s got 0x%s", formatBytes(crc).c_str(), - formatBytes(crc_received).c_str()); - valid = false; - returnCode = ErrorType::WRONG_CRC; + + // TODO: Not optimal, has precedence + auto connectionEndedPrematurelyReturnCode = checkConnectionEndedPrematurely(idx, responseLength); + auto sensorConnectedReturnCode = checkSensorConnected(); + auto crcReturnCode = checkCRC(responseLength); + + if (sensorConnectedReturnCode != ErrorType::DATA_OK) { + returnCode = sensorConnectedReturnCode; + } else if (connectionEndedPrematurelyReturnCode != ErrorType::DATA_OK) { + returnCode = connectionEndedPrematurelyReturnCode; + } else if (crcReturnCode != ErrorType::DATA_OK) { + returnCode = crcReturnCode; } + // breaking after first successfull try if (returnCode == ErrorType::DATA_OK) { break; @@ -182,6 +152,54 @@ ErrorType SentecSensorRS485::getResponse() { // ESP_LOGE(TAG, "Returncode: %d", returnCode); return returnCode; } +ErrorType SentecSensorRS485::checkCRC(int responseLength) { + word crc_received = word(answerFrame[responseLength - 2], answerFrame[responseLength - 1]); + word crc = calculateCRC(answerFrame, responseLength - 2); + if (crc_received != word(crc)) { + ESP_LOGE(TAG, "CRC wrong: Expected 0x%s got 0x%s", formatBytes(crc).c_str(), formatBytes(crc_received).c_str()); + return ErrorType::WRONG_CRC; + } + return ErrorType::DATA_OK; +} + +ErrorType SentecSensorRS485::checkConnectionEndedPrematurely(int idx, int responseLength) const { + if (idx < responseLength) { + ESP_LOGE(TAG, "Response too short: %d bytes < %d bytes. Unfinished transmission.", idx, responseLength); + return ErrorType::CONNECTION_ENDED_PREMATURELY; + } + return ErrorType::DATA_OK; +} + +ErrorType SentecSensorRS485::checkSensorConnected() const { + if (answerFrame[0] == 0) { + ESP_LOGE(TAG, "Check sensor connection. First byte is 0x00."); + return ErrorType::SENSOR_NOT_CONNECTED; + } + return ErrorType::DATA_OK; +} + +void SentecSensorRS485::readRS485(int &idx, int &responseLength, const int timeout) { + int byteReceived = 0; + unsigned long time = millis(); + while (idx < responseLength && (millis() - time) < timeout) { + if (RS485->available()) { + byteReceived = RS485->read(); + // Serial.println(byteReceived, HEX); + // check for first byte. It has to be the device address unless for broadcasts with address = 0xFF + if (idx == 0 && address != 0xFF && byteReceived != address) { + ESP_LOGE(TAG, "Invalid byte. First byte needs to be address 0x%02X but got 0x%02Xinstead"); + } else { + answerFrame[idx] = byteReceived; + // for reading register: third received byte is data length, read number of bytes accordingly + if (idx == 2 && answerFrame[1] == 0x03) { + // 5 bytes for address, function code, data length, CRC_H, CRC_L + responseLength = 5 + byteReceived; + } + idx++; + } + } + } +} unsigned int SentecSensorRS485::calculateCRC(byte query[], int length) { // Change the last two bytes of the queryFrame to conform to a CRC check @@ -246,3 +264,15 @@ std::string SentecSensorRS485::formatBytes(word data) { byte arr[2] = {static_cast<byte>(data >> 8), static_cast<byte>(data & 0xFF)}; return formatBytes(arr, 2); } + +bool SentecSensorRS485::isAnswerFrameEqualToQueryFrame(byte returnedFrame[8], byte queryFrame[8]) { + for (int i = 0; i < 8; i++) { + if (returnedFrame[i] != queryFrame[i]) { + ESP_LOGD(TAG, "Response: 0x%s", formatBytes(returnedFrame, 8).c_str()); + ESP_LOGD(TAG, "Response: 0x%s", formatBytes(queryFrame, 8).c_str()); + ESP_LOGE("TEST", "Error: returnedFrame[%d] != ret.writtenQuery[%d]", i, i); + return false; + } + } + return true; +} \ No newline at end of file diff --git a/client/libs/SentecSensors/SentecSensorsRS485.h b/client/libs/SentecSensors/SentecSensorsRS485.h index bedb9b95f0338343a33dfa310a3f754e85f404c5..af867df3a963430ca6815843b920174151eefe6e 100644 --- a/client/libs/SentecSensors/SentecSensorsRS485.h +++ b/client/libs/SentecSensors/SentecSensorsRS485.h @@ -19,8 +19,6 @@ class SentecSensorRS485 { uint8_t serialCommunicationControlPin = 19; std::shared_ptr<HardwareSerial> RS485 = RS485HardwareSerial::getInstance().getRS485Serial(); byte answerFrame[10] = {0}; - // TODO use valid flag to log None - bool valid = false; SentecSensorRS485(byte add); @@ -40,6 +38,12 @@ class SentecSensorRS485 { WriteRegisterReturnType writeRegister(int registerAddress, int value); + /** + * @brief Set the address of the sensor + * @param add The new address of the sensor + * @return ErrorType. If the address was not set correctly, the error type is set to + * ErrorType::COULD_NOT_SET_ADDRESS + */ ErrorType setAddress(byte add); void resetAnswerFrame(); @@ -55,6 +59,27 @@ class SentecSensorRS485 { std::string formatBytes(byte *data, int length); std::string formatBytes(word data); + + protected: + /** + * @brief Check if the answer frame is equal to the query frame. ONLY CHECKS THE FIRST 8 BYTES + * @param returnedFrame The frame returned from the device + * @param queryFrame The frame sent to the device + * @return true if the answer frame is equal to the query frame + */ + bool isAnswerFrameEqualToQueryFrame(byte *returnedFrame, byte *queryFrame); + + private: + /** + * @brief Read the response from the RS485 bus + * @param[in, out] idx The index of the response + * @param[in, out] responseLength The length of the response + * @param timeout The timeout in ms. Default is 2000ms + */ + void readRS485(int &idx, int &responseLength, const int timeout = 2000); + ErrorType checkSensorConnected() const; + ErrorType checkConnectionEndedPrematurely(int idx, int responseLength) const; + ErrorType checkCRC(int responseLength); }; #endif