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

WIP: Sentec Sensor Refactor

parent 6eeedc33
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 #101706 passed
Showing
with 591 additions and 395 deletions
//
// Created by zoe on 2/20/23.
//
#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() {
}
//
// Created by zoe on 2/20/23.
//
#ifndef CLIENT_CENTRAL_MAST_SENTECRAINGAUGESENSOR_H
#define CLIENT_CENTRAL_MAST_SENTECRAINGAUGESENSOR_H
#include "SentecSensors.h"
#include <ForteSensor.hpp>
#include <Message.hpp>
struct out_data_rain_gauge {
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!!!)
void resetPrecipitation();
void resetSensor();
out_data_rain_gauge getInstantaneousPrecipitation();
String getPrecipitationStr();
void setup() override;
out_data_rain_gauge readData() override;
std::list<Message> buildMessages() override;
[[nodiscard]] SensorInformation getSensorInformation() const override;
};
#endif //CLIENT_CENTRAL_MAST_SENTECRAINGAUGESENSOR_H
#include "NoDataAvailableException.hpp"
#include "SentecSoilMoistureSensor.h"
#include "SentecRainGaugeSensor.h"
#include "SentecSolarRadiationSensor.h"
#include <SentecSensors.h>
/***************************************
......@@ -7,318 +10,258 @@
static const char *TAG = "SENTEC";
SentecSensorRS485::SentecSensorRS485(HardwareSerial *ser, byte add) {
address = add;
RS485 = ser;
address = add;
RS485 = ser;
}
SentecSensorRS485::SentecSensorRS485(HardwareSerial *ser, byte add, uint8_t serialControlPin) {
address = add;
RS485 = ser;
serialCommunicationControlPin = serialControlPin;
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);
// 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");
}
if (valid) {
return String(value, 1);
} else {
return String("null");
}
}
String SentecSensorRS485::getValueStr(int value) {
if (valid) {
return String(value);
} else {
return String("null");
}
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
// 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;
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);
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::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();
// 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;
// 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 (int i = 0; i < 10; i++) {
answerFrame[i] = 0;
}
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;
resetAnswerFrame();
returnCode = ErrorType::WRONG_CRC;
}
// breaking after first successfull try
if (returnCode == ErrorType::DATA_OK) {
break;
// 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;
// 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;
}
// 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
}
// 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();
// 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);
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;
// 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] = {data >> 8, data & 0xFF};
return formatBytes(arr, 2);
}
word SolarRadiationSensor::getSolarRadiation() {
readRegister(0, 1);
glob = word(answerFrame[3], answerFrame[4]);
ESP_LOGI(TAG, "Global solar radiation: %d W/m^2", glob);
return glob;
}
String SolarRadiationSensor::getSolarRadiationStr() {
return getValueStr((int) glob);
}
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
}
word RainGaugeSensor::getInstantaneousPrecipitation() {
// gets tips of scale since the last reset, i.e. total precipitation (I THINK)
// manual says this is current precipitation - is it?
readRegister(0, 0x01);
precipitation = word(answerFrame[3], answerFrame[4]);
ESP_LOGI(TAG, "Precipitation: %.1f mm", precipitation / 10.0);
// resetPrecipitation();
return precipitation;
}
String RainGaugeSensor::getPrecipitationStr() {
return getValueStr((float) (precipitation / 10.0));
}
float SoilMoistureSensor::getMoistureTemp() {
readRegister(0, 2); // start register at 0, read 2 variables (vwc, soil temp)
moistureRaw = (answerFrame[3] << 8) + answerFrame[4];
// TODO: neg. temp check
if (answerFrame[5] < 0x80) {
temperatureRaw = (answerFrame[5] << 8) + answerFrame[6];
} else {
temperatureRaw = (answerFrame[5] << 8) + answerFrame[6] - 65536;
}
ESP_LOGI(TAG, "Soil moisture: %.1f %", (moistureRaw - moistureOffset) / 10.0);
ESP_LOGI(TAG, "Soil temperature: %.1f °C", (temperatureRaw - temperatureOffset) / 10.0);
return (temperatureRaw - temperatureOffset) / 10.0;
byte arr[2] = {static_cast<byte>(data >> 8), static_cast<byte>(data & 0xFF)};
return formatBytes(arr, 2);
}
float SoilMoistureSensor::getMoisture() {
return (moistureRaw - moistureOffset) / 10.0;
}
String SoilMoistureSensor::getMoistureStr() {
return getValueStr((float) ((moistureRaw - moistureOffset) / 10.0));
}
String SoilMoistureSensor::getTemperatureStr() {
return getValueStr((float) ((temperatureRaw - temperatureOffset) / 10.0));
}
......@@ -3,95 +3,52 @@
#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;
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);
SentecSensorRS485(HardwareSerial *ser, byte add, uint8_t serialControlPin);
SentecSensorRS485(HardwareSerial *ser, byte add, uint8_t serialControlPin);
void write(byte queryFrame[], int length);
void write(byte queryFrame[], int length);
String getValueStr(float value);
String getValueStr(float value);
String getValueStr(int value);
String getValueStr(int value);
ErrorType queryAddress();
ErrorType queryAddress();
ErrorType readRegister(int registerStartAddress);
ErrorType readRegister(int registerStartAddress);
ErrorType readRegister(int registerStartAddress, int registerLength);
ErrorType readRegister(int registerStartAddress, int registerLength);
ErrorType writeRegister(int registerAddress, int value);
ErrorType writeRegister(int registerAddress, int value);
ErrorType setAddress(byte add);
ErrorType setAddress(byte add);
void resetAnswerFrame();
void resetAnswerFrame();
ErrorType getResponse();
ErrorType getResponse();
unsigned int calculateCRC(byte query[], int length);
unsigned int calculateCRC(byte query[], int length);
void printBytes(byte *data, int length);
void printBytes(byte *data, int length);
void printBytes(word data);
void printBytes(word data);
std::string formatBytes(byte *data, int length);
std::string formatBytes(byte *data, int length);
std::string formatBytes(word data);
std::string formatBytes(word data);
};
class SolarRadiationSensor : public SentecSensorRS485 {
public:
using SentecSensorRS485::SentecSensorRS485;
// global radiation [W/m^2]
word glob = 0;
word getSolarRadiation();
String getSolarRadiationStr();
};
class RainGaugeSensor : public SentecSensorRS485 {
public:
using SentecSensorRS485::SentecSensorRS485;
// precipitation values [mm]
word precipitation = 0; // prcp since reset? (I THINK!!!)
void resetPrecipitation();
void resetSensor();
word getInstantaneousPrecipitation();
String getPrecipitationStr();
};
class SoilMoistureSensor : public SentecSensorRS485 {
public:
using SentecSensorRS485::SentecSensorRS485;
// vwc: volumetric water content [%]
uint16_t moistureRaw = 0;
int moistureOffset = 0;
// soil temperature [deg C]
int temperatureRaw = 0;
int temperatureOffset = 0;
float getMoistureTemp();
float getMoisture();
String getMoistureStr();
String getTemperatureStr();
};
#endif
//
// Created by zoe on 2/20/23.
//
#include "SentecSoilMoistureSensor.h"
out_data_soil_moisture SoilMoistureSensor::getMoistureTemp() {
auto error = readRegister(0,
2); // start register at 0, read 2 variables (vwc, soil temp)
moistureRaw = (answerFrame[3] << 8) + answerFrame[4];
// TODO: neg. temp check
if (answerFrame[5] < 0x80) {
temperatureRaw = (answerFrame[5] << 8) + answerFrame[6];
} else {
temperatureRaw = (answerFrame[5] << 8) + answerFrame[6] - 65536;
}
auto soilTemperature = getTemperature();
auto soilMoisture = getMoisture();
ESP_LOGI("SOILMOISTURE",
"Soil moisture: %.1f %",
soilMoisture);
ESP_LOGI("SOILMOISTURE",
"Soil temperature: %.1f °C",
soilTemperature);
return {soilMoisture, soilTemperature, error};
}
float SoilMoistureSensor::getMoisture() {
return (moistureRaw - moistureOffset) / 10.0;
}
float SoilMoistureSensor::getTemperature() {
return (temperatureRaw - temperatureOffset) / 10.0;
}
String SoilMoistureSensor::getMoistureStr() {
return getValueStr((float) ((moistureRaw - moistureOffset) / 10.0));
}
String SoilMoistureSensor::getTemperatureStr() {
return getValueStr((float) ((temperatureRaw - temperatureOffset) / 10.0));
}
SensorInformation SoilMoistureSensor::getSensorInformation() const {
return SensorInformation(HardwareName::SEM225, SensorProtocol::RS485);
}
out_data_soil_moisture SoilMoistureSensor::readData() {
return getMoistureTemp();
}
std::list<Message> SoilMoistureSensor::buildMessages() {
auto data = readData();
Measurement soilMoisture{data.moisture, address, NO_I2C_ADDRESS,
MeasurementType::SOIL_MOISTURE, data.soilError};
Measurement soilTemperature{data.temperature, address, NO_I2C_ADDRESS,
MeasurementType::SOIL_TEMPERATURE,
data.soilError};
return {Message{soilMoisture, getSensorInformation(),
Time::getInstance().getEpochSeconds()},
Message{soilTemperature, getSensorInformation(),
Time::getInstance().getEpochSeconds()}};
}
void SoilMoistureSensor::setup() {
// TODO: check if rs485 serial is active
}
//
// Created by zoe on 2/20/23.
//
#ifndef CLIENT_CENTRAL_MAST_SENTECSOILMOISTURESENSOR_H
#define CLIENT_CENTRAL_MAST_SENTECSOILMOISTURESENSOR_H
#include "SentecSensors.h"
#include <ForteSensor.hpp>
#include <Message.hpp>
struct out_data_soil_moisture {
float moisture;
float temperature;
ErrorType soilError;
};
class SoilMoistureSensor
: public SentecSensorRS485, ForteSensor<out_data_soil_moisture> {
public:
using SentecSensorRS485::SentecSensorRS485;
// vwc: volumetric water content [%]
uint16_t moistureRaw = 0;
int moistureOffset = 0;
// soil temperature [deg C]
int temperatureRaw = 0;
int temperatureOffset = 0;
void setup() override;
out_data_soil_moisture readData() override;
std::list<Message> buildMessages() override;
[[nodiscard]] SensorInformation getSensorInformation() const override;
private:
out_data_soil_moisture getMoistureTemp();
float getMoisture();
float getTemperature();
String getMoistureStr();
String getTemperatureStr();
};
#endif //CLIENT_CENTRAL_MAST_SENTECSOILMOISTURESENSOR_H
//
// Created by zoe on 3/06/23.
//
#include "SentecSolarRadiationSensor.h"
String SolarRadiationSensor::getSolarRadiationStr() {
return getValueStr((int) glob);
}
void SolarRadiationSensor::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};
}
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;
}
SensorInformation SolarRadiationSensor::getSensorInformation() const {
return SensorInformation(HardwareName::SEM228A, SensorProtocol::RS485);
}
//
// Created by zoe on 2/20/23.
//
#ifndef CLIENT_CENTRAL_MAST_SENTECSOLARRADIATIONSENSOR_H
#define CLIENT_CENTRAL_MAST_SENTECSOLARRADIATIONSENSOR_H
#include <ForteSensor.hpp>
#include "SentecSensors.h"
struct out_data_solar_radiation {
float solarRadiation;
ErrorType solarError;
};
class SolarRadiationSensor: public SentecSensorRS485, public ForteSensor<out_data_solar_radiation> {
public:
using SentecSensorRS485::SentecSensorRS485;
// 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();
};
#endif //CLIENT_CENTRAL_MAST_SENTECSOLARRADIATIONSENSOR_H
#include "rs485.hpp"
#include "SentecSoilMoistureSensor.h"
#include "SentecRainGaugeSensor.h"
#include "SentecSolarRadiationSensor.h"
// RS485 control
#define RS485Serial Serial2
#define RXPin 14 // Serial Receive pin
......@@ -38,14 +41,11 @@ out_data_rs485 Forte_RS485::readData() {
delay(300);
out_data_rs485 output;
unsigned long ts = millis();
output.solarRadiation = solarSensor.getSolarRadiation();
output.soilTemperature3 = soilSensor3.getMoistureTemp();
output.soilTemperature4 = soilSensor4.getMoistureTemp();
output.soilTemperature5 = soilSensor5.getMoistureTemp();
output.soilMoisture3 = soilSensor3.getMoisture();
output.soilMoisture4 = soilSensor4.getMoisture();
output.soilMoisture5 = soilSensor5.getMoisture();
output.precipitation = rainGauge.getInstantaneousPrecipitation();
output.solar = solarSensor.readData();
output.soil3 = soilSensor3.readData();
output.soil4 = soilSensor4.readData();
output.soil5 = soilSensor5.readData();
output.precipitation = rainGauge.readData();
digitalWrite(POWER_SWITCH_PIN_12V, LOW);
digitalWrite(POWER_SWITCH_PIN_5V, LOW);
......@@ -57,22 +57,32 @@ out_data_rs485 Forte_RS485::readData() {
std::list<Message> Forte_RS485::buildMessages() {
std::list<Message> messages;
out_data_rs485 output = readData();
Measurement
solarRadiation{output.solarRadiation, MeasurementType::SOLAR_RADIATION};
Measurement
soilTemp3{output.soilTemperature3, MeasurementType::SOIL_TEMPERATURE_3};
Measurement
soilTemp4{output.soilTemperature4, MeasurementType::SOIL_TEMPERATURE_4};
Measurement
soilTemp5{output.soilTemperature5, MeasurementType::SOIL_TEMPERATURE_5};
Measurement
soilMoisture3{output.soilMoisture3, MeasurementType::SOIL_MOISTURE_3};
Measurement
soilMoisture4{output.soilMoisture4, MeasurementType::SOIL_MOISTURE_4};
Measurement
soilMoisture5{output.soilMoisture5, MeasurementType::SOIL_MOISTURE_5};
Measurement
precipitation{output.precipitation, MeasurementType::PRECIPITATION};
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};
Measurement soilTemp3{output.soil3.temperature, 3, NO_I2C_ADDRESS,
MeasurementType::SOIL_TEMPERATURE,
output.soil3.soilError};
Measurement soilTemp4{output.soil4.temperature, 4, NO_I2C_ADDRESS,
MeasurementType::SOIL_TEMPERATURE,
output.soil4.soilError};
Measurement soilTemp5{output.soil5.temperature, 5, NO_I2C_ADDRESS,
MeasurementType::SOIL_TEMPERATURE,
output.soil5.soilError};
Measurement soilMoisture3
{output.soil3.moisture, 3, NO_I2C_ADDRESS, MeasurementType::SOIL_MOISTURE,
output.soil3.soilError};
Measurement soilMoisture4
{output.soil4.moisture, 4, NO_I2C_ADDRESS, MeasurementType::SOIL_MOISTURE,
output.soil4.soilError};
Measurement soilMoisture5
{output.soil5.moisture, 5, NO_I2C_ADDRESS, MeasurementType::SOIL_MOISTURE,
output.soil5.soilError};
messages.emplace_back(solarRadiation, sensorInformation,
Time::getInstance().getEpochSeconds());
......
......@@ -2,19 +2,19 @@
#define _RS485
#include <MeasurementTypes.h>
#include <SentecSoilMoistureSensor.h>
#include <SentecSolarRadiationSensor.h>
#include <SentecRainGaugeSensor.h>
#include "Message.hpp"
#include "ForteSensor.hpp"
#include "SentecSensors.h"
struct out_data_rs485 {
float solarRadiation;
float soilMoisture3;
float soilTemperature3;
float soilMoisture4;
float soilTemperature4;
float soilMoisture5;
float soilTemperature5;
float precipitation;
out_data_soil_moisture soil3;
out_data_soil_moisture soil4;
out_data_soil_moisture soil5;
out_data_solar_radiation solar;
out_data_rain_gauge precipitation;
};
class Forte_RS485: public ForteSensor<out_data_rs485> {
......
......@@ -4,36 +4,42 @@
#include "HardwareNames.h"
namespace HardwareNames {
std::string hardwareNameToString(HardwareName hardwareName) {
// switch
switch (hardwareName) {
case HardwareName::RS485:
return "RS485";
case HardwareName::INA219:
return "INA219";
case HardwareName::SCD30:
return "SCD30";
case HardwareName::RAIN_GAUGE:
return "RAIN_GAUGE";
case HardwareName::SOIL_MOISTURE_SENSOR:
return "SOIL_MOISTURE_SENSOR";
case HardwareName::SOIL_TEMPERATURE_SENSOR:
return "SOIL_TEMPERATURE_SENSOR";
case HardwareName::SOLAR_RADIATION_SENSOR:
return "SOLAR_RADIATION_SENSOR";
case HardwareName::SHT85:
return "SHT85";
case HardwareName::DRS26:
return "DRS26";
case HardwareName::DR26:
return "DR26";
case HardwareName::MOCK:
return "MOCK";
case HardwareName::NONE:
return "NONE";
case HardwareName::LC709203:
break;
}
return "UNKNOWN_HARDWARE_NAME";
}
std::string hardwareNameToString(HardwareName hardwareName) {
// switch
switch (hardwareName) {
case HardwareName::RS485:
return "RS485";
case HardwareName::INA219:
return "INA219";
case HardwareName::SCD30:
return "SCD30";
case HardwareName::RAIN_GAUGE:
return "RAIN_GAUGE";
case HardwareName::SOIL_MOISTURE_SENSOR:
return "SOIL_MOISTURE_SENSOR";
case HardwareName::SOIL_TEMPERATURE_SENSOR:
return "SOIL_TEMPERATURE_SENSOR";
case HardwareName::SOLAR_RADIATION_SENSOR:
return "SOLAR_RADIATION_SENSOR";
case HardwareName::SHT85:
return "SHT85";
case HardwareName::DRS26:
return "DRS26";
case HardwareName::DR26:
return "DR26";
case HardwareName::MOCK:
return "MOCK";
case HardwareName::NONE:
return "NONE";
case HardwareName::LC709203:
return "LC709203";
case HardwareName::SEM228A:
return "SEM228A";
case HardwareName::SEM225:
return "SEM225";
case HardwareName::SEM404:
return "SEM404";
}
return "UNKNOWN_HARDWARE_NAME";
}
} // namespace HardwareNames
\ No newline at end of file
......@@ -16,6 +16,9 @@ enum class HardwareName : short {
SOIL_MOISTURE_SENSOR,
SOIL_TEMPERATURE_SENSOR,
SOLAR_RADIATION_SENSOR,
SEM404,
SEM228A,
SEM225,
SHT85,
DRS26,
DR26,
......
......@@ -47,6 +47,10 @@ std::string measurementTypeToString(MeasurementType measurementType) {
return "CO2";
case MeasurementType::NULL_MEASUREMENT:
return "NULL_MEASUREMENT";
case MeasurementType::SOIL_MOISTURE:
return "SOIL_MOISTURE";
case MeasurementType::SOIL_TEMPERATURE:
return "SOIL_TEMPERATURE";
}
return "UNKNOWN";
}
......
......@@ -20,6 +20,8 @@ enum class MeasurementType {
BATTERY_VOLTAGE,
MOCK,
SOLAR_RADIATION,
SOIL_MOISTURE,
SOIL_TEMPERATURE,
SOIL_MOISTURE_3,
SOIL_TEMPERATURE_3,
SOIL_MOISTURE_4,
......
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