Skip to content
Snippets Groups Projects
Verified Commit bb49b9f8 authored by Zoe Michaela Dietmar Pfister's avatar Zoe Michaela Dietmar Pfister :gay_pride_flag:
Browse files

Renamed Sentec Sensors, made RS485 serial connection a singleton shared...

Renamed Sentec Sensors, made RS485 serial connection a singleton shared pointer, allow resetting of precipitation, added error type for non-ability to reset precipitation
parent 95b035ce
No related branches found
No related tags found
2 merge requests!39Merge Develop into Main,!30Renamed Sentec Sensors, made RS485 serial connection a singleton shared...
Pipeline #102050 passed
Showing
with 573 additions and 508 deletions
......@@ -4,51 +4,70 @@
#include "SentecRainGaugeSensor.h"
void RainGaugeSensor::resetPrecipitation() {
// clears rainfall rata from the rain gauge
// delay resetting after the last register reading
delay(100);
Serial.println("Resetting precipitation sum");
writeRegister(0x00, 0x5A);
// TODO check response: matches the sent message exactly
}
void RainGaugeSensor::resetSensor() {
// no clue what the difference between this one and the previous one is...
// and the manual is not helpful, of course
delay(100);
Serial.println("Resetting precipitation sum");
writeRegister(0x37, 0x03);
// TODO check response: matches the sent message exactly
}
out_data_rain_gauge RainGaugeSensor::getInstantaneousPrecipitation() {
// gets tips of scale since the last reset, i.e. total precipitation (I THINK)
// manual says this is current precipitation - is it?
auto error = readRegister(0, 0x01);
precipitation = word(answerFrame[3], answerFrame[4]);
ESP_LOGI("RainGauge", "Precipitation: %.1f mm", precipitation / 10.0);
// resetPrecipitation();
return {static_cast<float>(precipitation), error};
}
String RainGaugeSensor::getPrecipitationStr() {
return getValueStr((float) (precipitation / 10.0));
}
SensorInformation RainGaugeSensor::getSensorInformation() const {
return SensorInformation(HardwareName::SEM404, SensorProtocol::RS485);
}
std::list<Message> RainGaugeSensor::buildMessages() {
auto messages = std::list<Message>();
auto data = getInstantaneousPrecipitation();
Measurement precipitationMeasurement{data.precipitation, address, NO_I2C_ADDRESS,
MeasurementType::PRECIPITATION, data.precipitationError};
messages.emplace_back(Message{precipitationMeasurement, getSensorInformation(),
Time::getInstance().getEpochSeconds()});
return messages;
}
out_data_rain_gauge RainGaugeSensor::readData() {
return getInstantaneousPrecipitation();
}
void RainGaugeSensor::setup() {
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
delay(100);
Serial.println("Resetting precipitation sum");
auto writeRegisterReturn = writeRegister(0x00, 0x5A);
// other errors like not connected will be shared when reading out the sensor
if (!isAnswerFrameEqualToQueryFrame(answerFrame, writeRegisterReturn.writtenQuery)) {
ESP_LOGE(TAG, "Could not reset precipitation");
return ErrorType::SEM404_COULD_NOT_RESET_PRECIPITATION;
}
return writeRegisterReturn.errorType;
}
void SEM404::resetSensor() {
// no clue what the difference between this one and the previous one is...
// and the manual is not helpful, of course
delay(100);
Serial.println("Resetting precipitation sum");
writeRegister(0x37, 0x03);
// TODO check response: matches the sent message exactly
}
out_data_rain_gauge SEM404::getInstantaneousPrecipitation() {
// gets tips of scale since the last reset, i.e. total precipitation (I THINK)
// manual says this is current precipitation - is it?
auto error = readRegister(0, 0x01);
precipitation = word(answerFrame[3], answerFrame[4]);
ESP_LOGI("RainGauge", "Precipitation: %s mm", getPrecipitationStr().c_str());
// resetPrecipitation();
return {static_cast<float>(precipitation), error};
}
String SEM404::getPrecipitationStr() {
return getValueStr((float)(precipitation / 10.0));
}
SensorInformation SEM404::getSensorInformation() const {
return {HardwareName::SEM404, SensorProtocol::RS485};
}
std::list<Message> SEM404::buildMessages() {
auto messages = std::list<Message>();
auto data = getInstantaneousPrecipitation();
Measurement precipitationMeasurement{data.precipitation, address, NO_I2C_ADDRESS, MeasurementType::PRECIPITATION,
data.precipitationError};
messages.emplace_back(precipitationMeasurement, getSensorInformation(), Time::getInstance().getEpochSeconds());
return messages;
}
out_data_rain_gauge SEM404::readData() {
return getInstantaneousPrecipitation();
}
void SEM404::setup() {}
......@@ -5,34 +5,35 @@
#ifndef CLIENT_CENTRAL_MAST_SENTECRAINGAUGESENSOR_H
#define CLIENT_CENTRAL_MAST_SENTECRAINGAUGESENSOR_H
#include "SentecSensors.h"
#include "SentecSensorsRS485.h"
#include <ForteSensor.hpp>
#include <Message.hpp>
struct out_data_rain_gauge {
float precipitation;
ErrorType precipitationError;
float precipitation;
ErrorType precipitationError;
};
class RainGaugeSensor
: public SentecSensorRS485, ForteSensor<out_data_rain_gauge> {
public:
using SentecSensorRS485::SentecSensorRS485;
// precipitation values [mm]
unsigned int precipitation = 0; // prcp since reset? (I THINK!!!)
class SEM404 : public SentecSensorRS485, ForteSensor<out_data_rain_gauge> {
public:
using SentecSensorRS485::SentecSensorRS485;
SEM404() : SentecSensorRS485(0) {}
// precipitation values [mm]
unsigned int precipitation = 0; // prcp since reset? (I THINK!!!)
void resetPrecipitation();
ErrorType resetPrecipitation();
void resetSensor();
void resetSensor();
out_data_rain_gauge getInstantaneousPrecipitation();
out_data_rain_gauge getInstantaneousPrecipitation();
String getPrecipitationStr();
String getPrecipitationStr();
void setup() override;
out_data_rain_gauge readData() override;
std::list<Message> buildMessages() override;
[[nodiscard]] SensorInformation getSensorInformation() const override;
void setup() override;
out_data_rain_gauge readData() override;
std::list<Message> buildMessages() override;
[[nodiscard]] SensorInformation getSensorInformation() const override;
bool isAnswerFrameEqualToQueryFrame(byte *answerFrame, byte *queryFrame);
};
#endif //CLIENT_CENTRAL_MAST_SENTECRAINGAUGESENSOR_H
#endif // CLIENT_CENTRAL_MAST_SENTECRAINGAUGESENSOR_H
#include "NoDataAvailableException.hpp"
#include "SentecRainGaugeSensor.h"
#include "SentecSolarRadiationSensor.h"
#include <SentecSensors.h>
/***************************************
* RS485 SENSOR READOUT
****************************************/
static const char *TAG = "SENTEC";
SentecSensorRS485::SentecSensorRS485(HardwareSerial *ser, byte add) {
address = add;
RS485 = ser;
}
SentecSensorRS485::SentecSensorRS485(HardwareSerial *ser,
byte add,
uint8_t serialControlPin) {
address = add;
RS485 = ser;
serialCommunicationControlPin = serialControlPin;
}
void SentecSensorRS485::write(byte queryFrame[], int length) {
// sends a message (bytes) to the sensor
// Initialize the transmitter
digitalWrite(serialCommunicationControlPin, HIGH);
// Send message: request a reading from the sensor
RS485->write(queryFrame, length);
RS485->flush();
// Initialize the receiver
digitalWrite(serialCommunicationControlPin, LOW);
}
String SentecSensorRS485::getValueStr(float value) {
if (valid) {
return String(value, 1);
} else {
return String("null");
}
}
String SentecSensorRS485::getValueStr(int value) {
if (valid) {
return String(value);
} else {
return String("null");
}
}
ErrorType SentecSensorRS485::queryAddress() {
// request the address of the sensor with ONLY ONE SENSOR ON THE BUS
byte tmp_addr = address; // store the address in a temporary byte
address = 0xFF; // change the address to FF (0) for address check
ErrorType tmp = readRegister(word(0x07, 0xD0), 2);
address = tmp_addr; // set the original address back
return tmp;
}
ErrorType SentecSensorRS485::readRegister(int registerStartAddress) {
return readRegister(registerStartAddress, 1);
}
ErrorType
SentecSensorRS485::readRegister(int registerStartAddress, int registerLength) {
// function code 0x03: get data measured by the sensor
byte query[8];
query[0] = address;
// function code
query[1] = 0x03;
// register start address
query[2] = registerStartAddress >> 8;
query[3] = registerStartAddress & 0xFF;
// register length
query[4] = registerLength >> 8;
query[5] = registerLength & 0xFF;
// calculate last two bytes (CRC check)
calculateCRC(query, sizeof(query) - 2);
// # Serial.print("Query (get data): 0x"); #Print bytes
// # printBytes(query, 8);
// write the data request to the modbus line
write(query, sizeof(query));
// get response from sensor
return getResponse();
}
ErrorType SentecSensorRS485::writeRegister(int registerAddress, int value) {
// function code 0x06: change sensor settings
// e.g. a new address, reset rainfal data...
byte query[8];
query[0] = address;
// function code
query[1] = 0x06;
// register start address
query[2] = registerAddress >> 8;
query[3] = registerAddress & 0xFF;
// register length
query[4] = value >> 8;
query[5] = value & 0xFF;
calculateCRC(query, sizeof(query) - 2);
ESP_LOGD(TAG, "Query (settings): ");
printBytes(query, 8);
write(query, sizeof(query));
return getResponse();
}
ErrorType SentecSensorRS485::setAddress(byte add) {
// change the address of a sensor
ErrorType tmp = writeRegister(word(0x07, 0xD0), add);
if (tmp == ErrorType::DATA_OK)
address = add;
// TODO check response: matches the sent message exactly
return tmp;
}
void SentecSensorRS485::resetAnswerFrame() {
for (unsigned char &i: answerFrame) {
i = 0;
}
}
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 = 200;
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++) {
// 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,
"-----------------------------------------------------------------------------------------");
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;
}
// breaking after first successfull try
if (returnCode == ErrorType::DATA_OK) {
break;
}
}
// ESP_LOGE(TAG, "Returncode: %d", returnCode);
return returnCode;
}
unsigned int SentecSensorRS485::calculateCRC(byte query[], int length) {
// Change the last two bytes of the queryFrame to conform to a CRC check
// Yes, this is necessary. No, I don't know exactly what it does.
unsigned int tmp1, tmp2, flag;
tmp1 = 0xFFFF;
for (unsigned char i = 0; i < length; i++) {
tmp1 = tmp1 ^ query[i];
for (unsigned char j = 1; j <= 8; j++) {
flag = tmp1 & 0x0001;
tmp1 >>= 1;
if (flag)
tmp1 ^= 0xA001;
}
}
// Reverse byte order.
tmp2 = tmp1 >> 8;
tmp1 = (tmp1 << 8) | tmp2;
tmp1 &= 0xFFFF;
// change last two query bytes
query[length + 1] = tmp1;
query[length] = tmp1 >> 8;
return tmp1; // the returned value is already swapped - CRC_L byte is first & CRC_H byte is last
}
void SentecSensorRS485::printBytes(byte *data, int length) {
// prints 8-bit data in hex with leading zeroes
char tmp[16];
for (int i = 0; i < length; i++) {
sprintf(tmp, "%.2X", data[i]);
// sprintf(tmp, "0x%.2X",data[i]);
Serial.print(tmp);
Serial.print(" ");
}
Serial.println();
}
void SentecSensorRS485::printBytes(word data) {
char tmp[10];
sprintf(tmp, "0x%.2X", data >> 8);
Serial.print(tmp);
sprintf(tmp, "%.2X", data & 0xFF);
// sprintf(tmp, "0x%.2X",data[i]);
Serial.print(tmp);
}
std::string SentecSensorRS485::formatBytes(byte *data, int length) {
// pls don't hate me for building strings like that.
// I also wish that I would be good at programming
std::string tmp;
// prints 8-bit data in hex with leading zeroes
char buff[4];
for (int i = 0; i < length; i++) {
snprintf(buff, sizeof(buff), "%02X ", data[i]);
tmp += buff;
}
return tmp;
}
std::string SentecSensorRS485::formatBytes(word data) {
byte arr[2] = {static_cast<byte>(data >> 8), static_cast<byte>(data & 0xFF)};
return formatBytes(arr, 2);
}
#ifndef SENTECSENSORS_H
#define SENTECSENSORS_H
#include <Arduino.h>
#include <ErrorTypes.h>
#include <HardwareNames.h>
#include <SensorInformation.hpp>
class SentecSensorRS485 {
public:
byte address;
uint8_t serialCommunicationControlPin = 19;
HardwareSerial *RS485;
byte answerFrame[10];
// TODO use valid flag to log None
bool valid = false;
SentecSensorRS485(HardwareSerial *ser, byte add);
SentecSensorRS485(HardwareSerial *ser, byte add, uint8_t serialControlPin);
void write(byte queryFrame[], int length);
String getValueStr(float value);
String getValueStr(int value);
ErrorType queryAddress();
ErrorType readRegister(int registerStartAddress);
ErrorType readRegister(int registerStartAddress, int registerLength);
ErrorType writeRegister(int registerAddress, int value);
ErrorType setAddress(byte add);
void resetAnswerFrame();
ErrorType getResponse();
unsigned int calculateCRC(byte query[], int length);
void printBytes(byte *data, int length);
void printBytes(word data);
std::string formatBytes(byte *data, int length);
std::string formatBytes(word data);
};
#endif
#include "NoDataAvailableException.hpp"
#include <SentecSensorsRS485.h>
/***************************************
* RS485 SENSOR READOUT
****************************************/
static const char *TAG = "SENTEC";
SentecSensorRS485::SentecSensorRS485(byte add) {
address = add;
}
SentecSensorRS485::SentecSensorRS485(byte add, uint8_t serialControlPin) {
address = add;
serialCommunicationControlPin = serialControlPin;
}
void SentecSensorRS485::write(byte queryFrame[], int length) {
// sends a message (bytes) to the sensor
// Initialize the transmitter
digitalWrite(serialCommunicationControlPin, HIGH);
// Send message: request a reading from the sensor
RS485->write(queryFrame, length);
RS485->flush();
// Initialize the receiver
digitalWrite(serialCommunicationControlPin, LOW);
}
String SentecSensorRS485::getValueStr(float value) {
if (valid) {
return String(value, 1);
} else {
return {"null"};
}
}
String SentecSensorRS485::getValueStr(int value) {
if (valid) {
return String(value);
} else {
return {"null"};
}
}
ErrorType SentecSensorRS485::queryAddress() {
// request the address of the sensor with ONLY ONE SENSOR ON THE BUS
byte tmp_addr = address; // store the address in a temporary byte
address = 0xFF; // change the address to FF (0) for address check
ErrorType tmp = readRegister(word(0x07, 0xD0), 2);
address = tmp_addr; // set the original address back
return tmp;
}
ErrorType SentecSensorRS485::readRegister(int registerStartAddress) {
return readRegister(registerStartAddress, 1);
}
ErrorType SentecSensorRS485::readRegister(int registerStartAddress, int registerLength) {
// function code 0x03: get data measured by the sensor
byte query[8];
query[0] = address;
// function code
query[1] = 0x03;
// register start address
query[2] = registerStartAddress >> 8;
query[3] = registerStartAddress & 0xFF;
// register length
query[4] = registerLength >> 8;
query[5] = registerLength & 0xFF;
// calculate last two bytes (CRC check)
calculateCRC(query, sizeof(query) - 2);
// # Serial.print("Query (get data): 0x"); #Print bytes
// # printBytes(query, 8);
// write the data request to the modbus line
write(query, sizeof(query));
// get response from sensor
return getResponse();
}
WriteRegisterReturnType SentecSensorRS485::writeRegister(int registerAddress, int value) {
// function code 0x06: change sensor settings
// e.g. a new address, reset rainfal data...
byte query[8];
query[0] = address;
// function code
query[1] = 0x06;
// register start address
query[2] = registerAddress >> 8;
query[3] = registerAddress & 0xFF;
// register length
query[4] = value >> 8;
query[5] = value & 0xFF;
calculateCRC(query, sizeof(query) - 2);
ESP_LOGD(TAG, "Query (settings): ");
printBytes(query, 8);
write(query, sizeof(query));
return {getResponse(), {query[0], query[1], query[2], query[3], query[4], query[5], query[6], query[7]}};
}
ErrorType SentecSensorRS485::setAddress(byte add) {
// change the address of a sensor
WriteRegisterReturnType tmp = writeRegister(word(0x07, 0xD0), add);
if (tmp.errorType == ErrorType::DATA_OK)
address = add;
// TODO check response: matches the sent message exactly
return tmp.errorType;
}
void SentecSensorRS485::resetAnswerFrame() {
for (unsigned char &i : answerFrame) {
i = 0;
}
}
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++) {
// 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, "-----------------------------------------------------------------------------------------");
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;
}
// breaking after first successfull try
if (returnCode == ErrorType::DATA_OK) {
break;
}
}
// ESP_LOGE(TAG, "Returncode: %d", returnCode);
return returnCode;
}
unsigned int SentecSensorRS485::calculateCRC(byte query[], int length) {
// Change the last two bytes of the queryFrame to conform to a CRC check
// Yes, this is necessary. No, I don't know exactly what it does.
unsigned int tmp1, tmp2, flag;
tmp1 = 0xFFFF;
for (unsigned char i = 0; i < length; i++) {
tmp1 = tmp1 ^ query[i];
for (unsigned char j = 1; j <= 8; j++) {
flag = tmp1 & 0x0001;
tmp1 >>= 1;
if (flag)
tmp1 ^= 0xA001;
}
}
// Reverse byte order.
tmp2 = tmp1 >> 8;
tmp1 = (tmp1 << 8) | tmp2;
tmp1 &= 0xFFFF;
// change last two query bytes
query[length + 1] = tmp1;
query[length] = tmp1 >> 8;
return tmp1; // the returned value is already swapped - CRC_L byte is first & CRC_H byte is last
}
void SentecSensorRS485::printBytes(byte *data, int length) {
// prints 8-bit data in hex with leading zeroes
char tmp[16];
for (int i = 0; i < length; i++) {
sprintf(tmp, "%.2X", data[i]);
// sprintf(tmp, "0x%.2X",data[i]);
Serial.print(tmp);
Serial.print(" ");
}
Serial.println();
}
void SentecSensorRS485::printBytes(word data) {
char tmp[10];
sprintf(tmp, "0x%.2X", data >> 8);
Serial.print(tmp);
sprintf(tmp, "%.2X", data & 0xFF);
// sprintf(tmp, "0x%.2X",data[i]);
Serial.print(tmp);
}
std::string SentecSensorRS485::formatBytes(byte *data, int length) {
// pls don't hate me for building strings like that.
// I also wish that I would be good at programming
std::string tmp;
// prints 8-bit data in hex with leading zeroes
char buff[4];
for (int i = 0; i < length; i++) {
snprintf(buff, sizeof(buff), "%02X ", data[i]);
tmp += buff;
}
return tmp;
}
std::string SentecSensorRS485::formatBytes(word data) {
byte arr[2] = {static_cast<byte>(data >> 8), static_cast<byte>(data & 0xFF)};
return formatBytes(arr, 2);
}
#ifndef SENTECSENSORS_H
#define SENTECSENSORS_H
#include <Arduino.h>
#include <ErrorTypes.h>
#include <HardwareNames.h>
#include <RS485HardwareSerial.h>
#include <SensorInformation.hpp>
#include <memory>
struct WriteRegisterReturnType {
ErrorType errorType;
byte writtenQuery[8];
};
class SentecSensorRS485 {
public:
byte address;
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);
SentecSensorRS485(byte add, uint8_t serialControlPin);
void write(byte queryFrame[], int length);
String getValueStr(float value);
String getValueStr(int value);
ErrorType queryAddress();
ErrorType readRegister(int registerStartAddress);
ErrorType readRegister(int registerStartAddress, int registerLength);
WriteRegisterReturnType writeRegister(int registerAddress, int value);
ErrorType setAddress(byte add);
void resetAnswerFrame();
ErrorType getResponse();
unsigned int calculateCRC(byte query[], int length);
void printBytes(byte *data, int length);
void printBytes(word data);
std::string formatBytes(byte *data, int length);
std::string formatBytes(word data);
};
#endif
......@@ -4,31 +4,29 @@
#include "SentecSolarRadiationSensor.h"
String SolarRadiationSensor::getSolarRadiationStr() {
return getValueStr((int) glob);
String SEM228A::getSolarRadiationStr() {
return getValueStr((int)glob);
}
void SolarRadiationSensor::setup() {
// TODO: check if rs485 serial is active
void SEM228A::setup() {
// TODO: check if rs485 serial is active
}
out_data_solar_radiation SolarRadiationSensor::readData() {
auto error = readRegister(0, 1);
glob = word(answerFrame[3], answerFrame[4]);
ESP_LOGI("SolarRadiation", "Global solar radiation: %d W/m^2", glob);
return {static_cast<float>(glob), error};
out_data_solar_radiation SEM228A::readData() {
auto error = readRegister(0, 1);
glob = word(answerFrame[3], answerFrame[4]);
ESP_LOGI("SolarRadiation", "Global solar radiation: %d W/m^2", glob);
return {static_cast<float>(glob), error};
}
std::list<Message> SolarRadiationSensor::buildMessages() {
auto messages = std::list<Message>();
auto data = readData();
Measurement solarRadiation{data.solarRadiation, address, NO_I2C_ADDRESS,
MeasurementType::SOLAR_RADIATION, data.solarError};
messages.emplace_back(Message{solarRadiation, getSensorInformation(),
Time::getInstance().getEpochSeconds()});
return messages;
std::list<Message> SEM228A::buildMessages() {
auto messages = std::list<Message>();
auto data = readData();
Measurement solarRadiation{data.solarRadiation, address, NO_I2C_ADDRESS, MeasurementType::SOLAR_RADIATION,
data.solarError};
messages.emplace_back(Message{solarRadiation, getSensorInformation(), Time::getInstance().getEpochSeconds()});
return messages;
}
SensorInformation SolarRadiationSensor::getSensorInformation() const {
return SensorInformation(HardwareName::SEM228A, SensorProtocol::RS485);
SensorInformation SEM228A::getSensorInformation() const {
return SensorInformation(HardwareName::SEM228A, SensorProtocol::RS485);
}
......@@ -5,28 +5,28 @@
#ifndef CLIENT_CENTRAL_MAST_SENTECSOLARRADIATIONSENSOR_H
#define CLIENT_CENTRAL_MAST_SENTECSOLARRADIATIONSENSOR_H
#include "SentecSensorsRS485.h"
#include <ForteSensor.hpp>
#include "SentecSensors.h"
struct out_data_solar_radiation {
float solarRadiation;
ErrorType solarError;
float solarRadiation;
ErrorType solarError;
};
class SolarRadiationSensor: public SentecSensorRS485, public ForteSensor<out_data_solar_radiation> {
public:
using SentecSensorRS485::SentecSensorRS485;
class SEM228A : public SentecSensorRS485, public ForteSensor<out_data_solar_radiation> {
public:
using SentecSensorRS485::SentecSensorRS485;
// global radiation [W/m^2]
word glob = 0;
SEM228A() : SentecSensorRS485(0) {}
// global radiation [W/m^2]
word glob = 0;
void setup() override;
out_data_solar_radiation readData() override;
std::list<Message> buildMessages() override;
[[nodiscard]] SensorInformation getSensorInformation() const override;
String getSolarRadiationStr();
void setup() override;
out_data_solar_radiation readData() override;
std::list<Message> buildMessages() override;
[[nodiscard]] SensorInformation getSensorInformation() const override;
String getSolarRadiationStr();
};
#endif //CLIENT_CENTRAL_MAST_SENTECSOLARRADIATIONSENSOR_H
#endif // CLIENT_CENTRAL_MAST_SENTECSOLARRADIATIONSENSOR_H
//
// Created by zoe on 3/8/23.
//
#ifndef CLIENT_CENTRAL_MAST_RS485HARDWARESERIAL_H
#define CLIENT_CENTRAL_MAST_RS485HARDWARESERIAL_H
#include <HardwareSerial.h>
#include <memory>
class RS485HardwareSerial {
public:
static RS485HardwareSerial &getInstance()
{
static RS485HardwareSerial instance; // Guaranteed to be destroyed.
// Instantiated on first use.
return instance;
}
std::shared_ptr<HardwareSerial> getRS485Serial() {
return RS485Serial;
}
private:
RS485HardwareSerial() {} // Constructor? (the {} brackets) are needed here.
const std::shared_ptr<HardwareSerial> RS485Serial = std::make_shared<HardwareSerial>(Serial2);
// C++ 11
// =======
// We can use the better technique of deleting the methods
// we don't want.
public:
RS485HardwareSerial(RS485HardwareSerial const &) = delete;
void operator=(RS485HardwareSerial const &) = delete;
// Note: Scott Meyers mentions in his Effective Modern
// C++ book, that deleted functions should generally
// be public as it results in better error messages
// due to the compilers behavior to check accessibility
// before deleted status
};
#endif //CLIENT_CENTRAL_MAST_RS485HARDWARESERIAL_H
......@@ -2,7 +2,7 @@
#include "SentecRainGaugeSensor.h"
#include "SentecSolarRadiationSensor.h"
// RS485 control
#define RS485Serial Serial2
//#define RS485Serial Serial2
#define RXPin 14 // Serial Receive pin
#define TXPin 15 // Serial Transmit pin
......@@ -14,59 +14,61 @@
static const char *TAG = "RS485";
// Configure sensors
SolarRadiationSensor solarSensor(&RS485Serial, 1, RE_DE_PIN);
RainGaugeSensor rainGauge = RainGaugeSensor(&RS485Serial,
2,
RE_DE_PIN); // Give 2 Sensor Adress 2
SEM228A solarSensor(1, RE_DE_PIN);
SEM404 rainGauge = SEM404(2, RE_DE_PIN);
void Forte_RS485::setup() {
// configure the pins to be output only
pinMode(RE_DE_PIN, OUTPUT);
pinMode(POWER_SWITCH_PIN_12V, OUTPUT);
pinMode(POWER_SWITCH_PIN_5V, OUTPUT);
RS485Serial.begin(4800, SERIAL_8N1, TXPin, RXPin);
RS485Serial = RS485HardwareSerial::getInstance().getRS485Serial();
// configure the pins to be output only
pinMode(RE_DE_PIN, OUTPUT);
pinMode(POWER_SWITCH_PIN_12V, OUTPUT);
pinMode(POWER_SWITCH_PIN_5V, OUTPUT);
RS485Serial->begin(4800, SERIAL_8N1, TXPin, RXPin);
}
void Forte_RS485::teardown() {
RS485Serial->end();
}
out_data_rs485 Forte_RS485::readData() {
// Power on sensor
digitalWrite(POWER_SWITCH_PIN_12V, HIGH);
digitalWrite(POWER_SWITCH_PIN_5V, HIGH);
// Wait for sensors to power up
// TODO minimize delay
delay(300);
out_data_rs485 output;
unsigned long ts = millis();
output.solar = solarSensor.readData();
output.precipitation = rainGauge.readData();
digitalWrite(POWER_SWITCH_PIN_12V, LOW);
digitalWrite(POWER_SWITCH_PIN_5V, LOW);
powerOnRS485Sensors();
gpio_hold_en((gpio_num_t) POWER_SWITCH_PIN_12V);
gpio_hold_en((gpio_num_t) POWER_SWITCH_PIN_5V);
return output;
out_data_rs485 output{};
output.solar = solarSensor.readData();
output.precipitation = rainGauge.readData();
powerOffRS485Sensors();
return output;
}
void Forte_RS485::powerOffRS485Sensors() {
digitalWrite(POWER_SWITCH_PIN_12V, LOW);
digitalWrite(POWER_SWITCH_PIN_5V, LOW);
std::list<Message> Forte_RS485::buildMessages() {
std::list<Message> messages;
out_data_rs485 output = readData();
gpio_hold_en((gpio_num_t)POWER_SWITCH_PIN_12V);
gpio_hold_en((gpio_num_t)POWER_SWITCH_PIN_5V);
}
void Forte_RS485::powerOnRS485Sensors() { // Power on sensor
digitalWrite(POWER_SWITCH_PIN_12V, HIGH);
digitalWrite(POWER_SWITCH_PIN_5V, HIGH); // Wait for sensors to power up
// TODO minimize delay
delay(2000);
}
Measurement solarRadiation{output.solar.solarRadiation, 1, NO_I2C_ADDRESS,
MeasurementType::SOLAR_RADIATION,
output.solar.solarError};
Measurement precipitation
{output.precipitation.precipitation, 2, NO_I2C_ADDRESS,
MeasurementType::PRECIPITATION, output.precipitation.precipitationError};
std::list<Message> Forte_RS485::buildMessages() {
std::list<Message> messages;
out_data_rs485 output = readData();
Measurement solarRadiation{output.solar.solarRadiation, 1, NO_I2C_ADDRESS, MeasurementType::SOLAR_RADIATION,
output.solar.solarError};
Measurement precipitation{output.precipitation.precipitation, 2, NO_I2C_ADDRESS, MeasurementType::PRECIPITATION,
output.precipitation.precipitationError};
messages.emplace_back(solarRadiation, sensorInformation,
Time::getInstance().getEpochSeconds());
messages.emplace_back(precipitation,
sensorInformation,
Time::getInstance().getEpochSeconds());
messages.emplace_back(solarRadiation, sensorInformation, Time::getInstance().getEpochSeconds());
messages.emplace_back(precipitation, sensorInformation, Time::getInstance().getEpochSeconds());
return messages;
return messages;
}
SensorInformation Forte_RS485::getSensorInformation() const {
return sensorInformation;
return sensorInformation;
}
\ No newline at end of file
......@@ -3,10 +3,11 @@
#include <MeasurementTypes.h>
#include <SentecSolarRadiationSensor.h>
#include <RS485HardwareSerial.h>
#include <SentecRainGaugeSensor.h>
#include "Message.hpp"
#include "ForteSensor.hpp"
#include "SentecSensors.h"
#include "SentecSensorsRS485.h"
struct out_data_rs485 {
out_data_solar_radiation solar;
......@@ -19,8 +20,14 @@ class Forte_RS485: public ForteSensor<out_data_rs485> {
out_data_rs485 readData() override;
std::list<Message> buildMessages() override;
[[nodiscard]] SensorInformation getSensorInformation() const override;
void teardown();
static void powerOnRS485Sensors();
static void powerOffRS485Sensors();
private:
std::shared_ptr<HardwareSerial> RS485Serial;
const SensorInformation
sensorInformation{HardwareName::RS485, SensorProtocol::RS485};
......
......@@ -5,37 +5,39 @@
#include "ErrorTypes.h"
namespace ErrorTypes {
// INFO: If you add a new error type, add it here and to the documentation at https://git.uibk.ac.at/informatik/qe/forte/sensor-system/-/wikis/Error-Types as well
std::string errorTypeToString(ErrorType errorType) {
switch (errorType) {
case ErrorType::SENSOR_NOT_FOUND:
return "SENSOR_NOT_FOUND";
case ErrorType::SENSOR_NOT_CONNECTED:
return "SENSOR_NOT_CONNECTED";
case ErrorType::NO_DATA:
return "NO_DATA";
case ErrorType::DATA_OK:
return "DATA_OK";
case ErrorType::NULL_MESSAGE:
return "NULL_MESSAGE";
case ErrorType::SENSOR_DOES_NOT_RETURN_DATA:
return "SENSOR_DOES_NOT_RETURN_DATA";
case ErrorType::BATTERY_VOLTAGE_TOO_LOW:
return "BATTERY_VOLTAGE_TOO_LOW";
case ErrorType::INVALID_VALUE:
return "INVALID_VALUE";
case ErrorType::SENSOR_INIT_FAILED:
return "SENSOR_INIT_FAILED";
case ErrorType::INA219_OVERFLOW:
return "INA219_OVERFLOW";
case ErrorType::CONNECTION_ENDED_PREMATURELY:
return "CONNECTION_ENDED_PREMATURELY";
case ErrorType::WRONG_CRC:
return "WRONG_CRC";
case ErrorType::UNKNOWN:
default:
return "UNKNOWN_ERROR_TYPE";
}
}
// INFO: If you add a new error type, add it here and to the documentation at https://git.uibk.ac.at/informatik/qe/forte/sensor-system/-/wikis/Error-Types as well
std::string errorTypeToString(ErrorType errorType) {
switch (errorType) {
case ErrorType::SENSOR_NOT_FOUND:
return "SENSOR_NOT_FOUND";
case ErrorType::SENSOR_NOT_CONNECTED:
return "SENSOR_NOT_CONNECTED";
case ErrorType::NO_DATA:
return "NO_DATA";
case ErrorType::DATA_OK:
return "DATA_OK";
case ErrorType::NULL_MESSAGE:
return "NULL_MESSAGE";
case ErrorType::SENSOR_DOES_NOT_RETURN_DATA:
return "SENSOR_DOES_NOT_RETURN_DATA";
case ErrorType::BATTERY_VOLTAGE_TOO_LOW:
return "BATTERY_VOLTAGE_TOO_LOW";
case ErrorType::INVALID_VALUE:
return "INVALID_VALUE";
case ErrorType::SENSOR_INIT_FAILED:
return "SENSOR_INIT_FAILED";
case ErrorType::INA219_OVERFLOW:
return "INA219_OVERFLOW";
case ErrorType::CONNECTION_ENDED_PREMATURELY:
return "CONNECTION_ENDED_PREMATURELY";
case ErrorType::WRONG_CRC:
return "WRONG_CRC";
case ErrorType::SEM404_COULD_NOT_RESET_PRECIPITATION:
return "SEM404_COULD_NOT_RESET_PRECIPITATION";
case ErrorType::UNKNOWN:
default:
return "UNKNOWN_ERROR_TYPE";
}
}
}
\ No newline at end of file
......@@ -7,24 +7,25 @@
#include <string>
enum class ErrorType : char {
SENSOR_NOT_FOUND,
SENSOR_INIT_FAILED,
SENSOR_NOT_CONNECTED,
NO_DATA,
DATA_OK,
NULL_MESSAGE, // message that is sent as padding, should be thrown away
SENSOR_DOES_NOT_RETURN_DATA,
BATTERY_VOLTAGE_TOO_LOW,
INVALID_VALUE,
INA219_OVERFLOW,
CONNECTION_ENDED_PREMATURELY, // connection ended prematurely
WRONG_CRC, // corrupted data package
UNKNOWN,
enum class ErrorType: char {
SENSOR_NOT_FOUND,
SENSOR_INIT_FAILED,
SENSOR_NOT_CONNECTED,
SEM404_COULD_NOT_RESET_PRECIPITATION,
NO_DATA,
DATA_OK,
NULL_MESSAGE, // message that is sent as padding, should be thrown away
SENSOR_DOES_NOT_RETURN_DATA,
BATTERY_VOLTAGE_TOO_LOW,
INVALID_VALUE,
INA219_OVERFLOW,
CONNECTION_ENDED_PREMATURELY, // connection ended prematurely
WRONG_CRC, // corrupted data package
UNKNOWN,
};
namespace ErrorTypes {
std::string errorTypeToString(ErrorType errorType);
std::string errorTypeToString(ErrorType errorType);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment