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

Merge branch '59-soil-sensor-for-satellite' into 'develop'

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

See merge request !30
parents 6eeedc33 ab590732
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 #103886 passed
Showing
with 746 additions and 817 deletions
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$">
<contentRoot DIR="$PROJECT_DIR$/.." />
</component>
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>
\ No newline at end of file
# !!! WARNING !!! AUTO-GENERATED FILE, PLEASE DO NOT MODIFY IT AND USE
# https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags
#
# If you need to override existing CMake configuration or add extra,
# please create `CMakeListsUser.txt` in the root of project.
# The `CMakeListsUser.txt` will not be overwritten by PlatformIO.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER_WORKS 1)
set(CMAKE_CXX_COMPILER_WORKS 1)
project("ESPcam" C CXX)
include(CMakeListsPrivate.txt)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/CMakeListsUser.txt)
include(CMakeListsUser.txt)
endif()
add_custom_target(
Production ALL
COMMAND platformio -c clion run "$<$<NOT:$<CONFIG:All>>:-e${CMAKE_BUILD_TYPE}>"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_target(
Debug ALL
COMMAND platformio -c clion debug "$<$<NOT:$<CONFIG:All>>:-e${CMAKE_BUILD_TYPE}>"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_executable(Z_DUMMY_TARGET ${SRC_LIST})
This diff is collapsed.
......@@ -14,6 +14,7 @@ board = esp32cam
framework = arduino
monitor_speed = 115200
lib_ldf_mode = deep
monitor_port = /dev/ttyACM1
lib_extra_dirs =
../libs
../../shared-libs
......
#include "Arduino.h"
#include "ESPNow.hpp"
#include "NoDataAvailableException.hpp"
#include "SentecRainGaugeSensor.h"
#include "rs485.hpp"
#include <list>
#include <ina219.hpp>
static const char *TAG = "MAIN";
#define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 5 * 1 /* Time ESP32 will go to sleep (in seconds) */
#define TIME_TO_SLEEP 0 * 1 /* Time ESP32 will go to sleep (in seconds) */
Forte_RS485 rs485;
ForteRS485 rs485;
ForteINA219 ina219;
SEM404 rainGaugeSensor = SEM404(2, 19);
float getBatteryVoltage() {
//************ Measuring Battery Voltage ***********
// reference voltage of microcontroller 3.3v for esp32
const float reference_vcc = 3.3;
// value of R2 resistor [kOhm]
const float bat_res_gnd = 20;
// value of R1 resistor [kOhm]
const float bat_res_vcc = 68;
// max ADC value
const int adc = 4095;
float sample = 0;
// Get 100 analog read to prevent unusefully read
for (int i = 0; i < 100; i++) {
sample =
sample + analogRead(35); // read the voltage from the divider circuit
delay(2);
}
sample = sample / 100;
float battery_voltage =
(sample / 4095 * reference_vcc * (bat_res_vcc + bat_res_gnd)
/ bat_res_gnd);
ESP_LOGI(TAG, "Battery Voltage: %4.2f V", battery_voltage);
ESP_LOGD(TAG, "ADC mean Value: %4.2f", sample);
return battery_voltage;
}
// FIXME: put this in ESPNow.hpp and let stupid Markus Rampp know how you did it. After years of Python he moved past
// the idea of types and declarations
void send_msgs(const std::__cxx11::list<Message> msgs) {
for (const Message &msg: msgs) {
if (msg.send() != ESP_OK) {
RtcMemory::store_data(msg.getMessageAsMinifiedJsonString());
//************ Measuring Battery Voltage ***********
// reference voltage of microcontroller 3.3v for esp32
const float reference_vcc = 3.3;
// value of R2 resistor [kOhm]
const float bat_res_gnd = 20;
// value of R1 resistor [kOhm]
const float bat_res_vcc = 68;
// max ADC value
const int adc = 4095;
float sample = 0;
// Get 100 analog read to prevent unusefully read
for (int i = 0; i < 100; i++) {
sample = sample + analogRead(35); // read the voltage from the divider circuit
delay(2);
}
unsigned long ts = millis();
// it takes ~110ms for receiving an acknowledgement by the host in perfect conditions
uint16_t message_timeout = 2000;
while (!was_msg_received()) {
if ((millis() - ts) > message_timeout) {
RtcMemory::store_data(msg.getMessageAsMinifiedJsonString());
ESP_LOGE(TAG, "Timeout: Host not available\n");
break;
}
}
ESP_LOGD(TAG, "Time until acknowledgement: %ld", millis() - ts);
}
sample = sample / 100;
float battery_voltage = (sample / 4095 * reference_vcc * (bat_res_vcc + bat_res_gnd) / bat_res_gnd);
ESP_LOGI(TAG, "Battery Voltage: %4.2f V", battery_voltage);
ESP_LOGD(TAG, "ADC mean Value: %4.2f", sample);
return battery_voltage;
}
void setup() {
// whole loop should be around ~3000 ms
Serial.begin(115200);
rs485.setup();
getBatteryVoltage();
ina219.setup();
ina219.setPinSetup({21, 22});
// ~2100ms
try {
auto messages = rs485.buildMessages();
// split into arrays of 6 messages TODO: Make this cleaner with default constructor
std::array<Message, 6> messages_1 =
{Message::nullMessage(), Message::nullMessage(), Message::nullMessage(),
Message::nullMessage(), Message::nullMessage(),
Message::nullMessage()};
std::array<Message, 6> messages_2 =
{Message::nullMessage(), Message::nullMessage(), Message::nullMessage(),
Message::nullMessage(), Message::nullMessage(),
Message::nullMessage()};
auto ina219_messages = ina219.buildMessages();
std::array<Message, 6> ina_array =
{Message::nullMessage(), Message::nullMessage(), Message::nullMessage(),
Message::nullMessage(), Message::nullMessage(),
Message::nullMessage()};
// log ina messages
for (const auto &msg: ina219_messages) {
ESP_LOGI(TAG,
"INA219 Message: %s",
msg.getMessageAsMinifiedJsonString().c_str());
}
int i = 0;
for (const auto &msg: messages) {
if (i < 6) {
messages_1[i] = msg;
} else {
messages_2[i - 6] = msg;
}
i++;
// whole loop should be around ~3000 ms
Serial.begin(115200);
rs485.setup();
getBatteryVoltage();
ina219.setPinSetup({21, 22});
ina219.setup();
try {
ForteRS485::powerOnRS485Sensors();
auto messages = rainGaugeSensor.buildMessages();
ForteRS485::powerOffRS485Sensors();
// split into arrays of 6 messages TODO: Make this cleaner with default constructor
std::array<Message, 6> messages_1 = {Message::nullMessage(), Message::nullMessage(), Message::nullMessage(),
Message::nullMessage(), Message::nullMessage(), Message::nullMessage()};
auto ina219_messages = ina219.buildMessages();
std::array<Message, 6> ina_array = {Message::nullMessage(), Message::nullMessage(), Message::nullMessage(),
Message::nullMessage(), Message::nullMessage(), Message::nullMessage()};
// log ina messages
for (const auto &msg : ina219_messages) {
ESP_LOGI(TAG, "INA219 Message: %s", msg.getMessageAsMinifiedJsonString().c_str());
}
int i = 0;
for (const auto &msg : messages) {
if (i < 6) {
messages_1[i] = msg;
}
i++;
}
espnow_setup();
// demo of how to reset the rain gauge
#ifdef RESET_RAIN_GAUGE
if (messages_1.front().getClientDataPackage().getMeasurementData().getValue() > 20) {
ForteRS485::powerOnRS485Sensors();
if (rainGaugeSensor.resetPrecipitation() == ErrorType::SEM404_COULD_NOT_RESET_PRECIPITATION) {
ESP_LOGE(TAG, "Could not reset precipitation");
auto precipitationErrorMessage = Message(
Measurement(ERROR_VALUE, rainGaugeSensor.address, NO_I2C_ADDRESS, MeasurementType::PRECIPITATION,
ErrorType::SEM404_COULD_NOT_RESET_PRECIPITATION),
rainGaugeSensor.getSensorInformation(), Time::getInstance().getEpochSeconds());
std::array<Message, 6> messages_error = {precipitationErrorMessage, Message::nullMessage(),
Message::nullMessage(), Message::nullMessage(),
Message::nullMessage(), Message::nullMessage()};
Message::sendMessages(messages_error);
}
ForteRS485::powerOffRS485Sensors();
}
#endif
i = 0;
for (const auto &msg : ina219_messages) {
ina_array[i] = msg;
i++;
}
Message::sendMessages(messages_1);
// Message::sendMessages(ina_array);
} catch (const NoDataAvailableException &e) {
std::cerr << e.what() << '\n';
}
i = 0;
for (const auto &msg: ina219_messages) {
ina_array[i] = msg;
i++;
}
espnow_setup();
Message::sendMessages(messages_1);
Message::sendMessages(messages_2);
Message::sendMessages(ina_array);
} catch (const NoDataAvailableException &e) {
std::cerr << e.what() << '\n';
}
rs485.teardown();
ESP_LOGD(TAG, "Going to sleep for %d seconds", TIME_TO_SLEEP);
ESP_LOGD(TAG, "Going to sleep for %d seconds", TIME_TO_SLEEP);
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
}
void loop() {}
......@@ -4,6 +4,8 @@ board = esp32dev
framework = arduino
monitor_speed = 115200
lib_ldf_mode = deep
upload_port = /dev/ttyUSB0
monitor_port = /dev/ttyUSB0
lib_extra_dirs =
../libs
../../shared-libs
......
......@@ -4,14 +4,16 @@
#include "dr26.hpp"
#include "f_deep_sleep.hpp"
#include <Arduino.h>
#include <SentecRainGaugeSensor.h>
#include <list>
#include <rs485.hpp>
// Execution time:
// FIXME: Boot: needs to be optimised. should be around 300 ms, can be <100ms
// https://github.com/makermoekoe/Picoclick-C3#speed-up-boot-process
// Part [ms]
// Boot: 300
// First setup: 150
// First setup: 150
// Data aquisition: 500 (4 Dendrometers)
// ESPNow setup: 220
// Sending 580
......@@ -26,105 +28,118 @@ static const char *TAG = "MAIN";
LC709203F battery_monitor;
#define RS485Serial Serial2
#define RE_DE_PIN 19 // Line to pull high or low to receive or send data from RS485
#define RXPin 14 // Serial Receive pin
#define TXPin 15 // Serial Transmit pin
#define POWER_SWITCH_PIN_12V 12
#define POWER_SWITCH_PIN_5V 13
ForteRS485 rs485;
SEM404 rainGaugeSensor{2, RE_DE_PIN};
ForteDR26 dr26_channel0;
ForteDR26 dr26_channel3;
ForteDR26 dr26_channel1;
ForteDR26 dr26_channel2;
void send_msgs(const std::__cxx11::list<Message> msgs)
{
for (const Message &msg : msgs) {
if (msg.send() != ESP_OK) {
RtcMemory::store_data(msg.getMessageAsMinifiedJsonString());
}
unsigned long ts = millis();
// it takes ~110ms for receiving an acknowledgement by the host in perfect conditions
uint16_t message_timeout = 2000;
while (!was_msg_received()) {
if ((millis() - ts) > message_timeout) {
RtcMemory::store_data(msg.getMessageAsMinifiedJsonString());
ESP_LOGE(TAG, "Timeout: Host not available\n");
break;
}
}
ESP_LOGD(TAG, "Time until acknowledgement: %ld", millis() - ts);
}
}
// one loop takes ~2200 ms
void setup()
{
unsigned long ts = millis();
Serial.begin(115200);
// Set the GPIO which conrtols the step up to OUTPUT
gpio_set_direction(GPIO_NUM_32, GPIO_MODE_OUTPUT);
// blinking led for debug
// gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT);
// gpio_set_level(GPIO_NUM_17, 1);
battery_monitor.begin();
DeepSleep::print_wakeup_reason();
DeepSleep::bootCount++;
ESP_LOGD(TAG, "Boot number: %d", DeepSleep::bootCount);
// battery protection: go to deep sleep for unlimited time when voltage less than 3.2V
if (battery_monitor.cellVoltage_mV() < 3200) {
battery_monitor.setPowerMode(LC709203F_POWER_SLEEP);
esp_deep_sleep_start();
}
gpio_set_level(GPIO_NUM_32, 1);
// delay(100);
dr26_channel1.setup();
dr26_channel0.setChannel(0);
dr26_channel1.setChannel(1);
dr26_channel2.setChannel(2);
dr26_channel3.setChannel(3);
ESP_LOGD(TAG, "Setup took %ld ms", millis() - ts);
try {
// FIXME: put me into seperate trys? No data will be sent when 1 exception occurs
ts = millis();
auto messages0 = dr26_channel0.buildMessages();
auto messages1 = dr26_channel1.buildMessages();
auto messages2 = dr26_channel2.buildMessages();
auto messages3 = dr26_channel3.buildMessages();
auto messages4 = battery_monitor.buildMessages();
// 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);
gpio_set_level(GPIO_NUM_32, 0);
// FIXME: put this outside the try loop?
ts = millis();
espnow_setup();
ESP_LOGD(TAG, "EPSNow setup took %ld ms", millis() - ts);
ts = millis();
send_msgs(messages0);
send_msgs(messages1);
send_msgs(messages2);
send_msgs(messages3);
send_msgs(messages4);
// roughly takes 3s in ideal conditions
ESP_LOGD(TAG, "Sending messages took %ld ms", millis() - ts);
} catch (const NoDataAvailableException &e) {
std::cerr << e.what() << '\n';
}
// just to be safe in case exception happens above
gpio_set_level(GPIO_NUM_32, 0);
// keep it in deep sleep
gpio_hold_en((gpio_num_t)GPIO_NUM_32);
battery_monitor.setPowerMode(LC709203F_POWER_SLEEP);
// battery protection: go to deep sleep for unlimited time when voltage less than 3.2V
if (battery_monitor.cellVoltage_mV() > 3200) {
DeepSleep::deep_sleep(10 * 60);
} else {
esp_deep_sleep_start();
}
void setup() {
unsigned long ts = millis();
Serial.begin(115200);
rs485.setup();
// Set the GPIO which conrtols the step up to OUTPUT
gpio_set_direction(GPIO_NUM_32, GPIO_MODE_OUTPUT);
// blinking led for debug
// gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT);
// gpio_set_level(GPIO_NUM_17, 1);
battery_monitor.begin();
DeepSleep::print_wakeup_reason();
DeepSleep::bootCount++;
ESP_LOGD(TAG, "Boot number: %d", DeepSleep::bootCount);
// battery protection: go to deep sleep for unlimited time when voltage less than 3.2V
if (battery_monitor.cellVoltage_mV() < 3200) {
battery_monitor.setPowerMode(LC709203F_POWER_SLEEP);
esp_deep_sleep_start();
}
gpio_set_level(GPIO_NUM_32, 1);
// delay(100);
dr26_channel1.setup();
dr26_channel0.setChannel(0);
dr26_channel1.setChannel(1);
dr26_channel2.setChannel(2);
dr26_channel3.setChannel(3);
ESP_LOGD(TAG, "Setup took %ld ms", millis() - ts);
try {
// FIXME: put me into seperate trys? No data will be sent when 1 exception occurs
ts = millis();
// auto messages0 = dr26_channel0.buildMessages();
// auto messages1 = dr26_channel1.buildMessages();
// auto messages2 = dr26_channel2.buildMessages();
// auto messages3 = dr26_channel3.buildMessages();
// auto messages4 = battery_monitor.buildMessages();
ForteRS485::powerOnRS485Sensors();
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);
gpio_set_level(GPIO_NUM_32, 0);
// std::array<Message, 6> messages =
// {messages0.front(), messages1.front(), messages2.front(),
// messages3.front(), messages4.front(), rainMessage.front()};
std::array<Message, 6> messages = {Message::nullMessage(), Message::nullMessage(), Message::nullMessage(),
Message::nullMessage(), Message::nullMessage(), rainMessage.front()};
ts = millis();
espnow_setup();
ESP_LOGD(TAG, "EPSNow setup took %ld ms", millis() - ts);
if (messages.back().getClientDataPackage().getMeasurementData().getValue() > 20) {
ForteRS485::powerOnRS485Sensors();
if (rainGaugeSensor.resetPrecipitation() == ErrorType::SEM404_COULD_NOT_RESET_PRECIPITATION) {
ESP_LOGE(TAG, "Could not reset precipitation");
auto precipitationErrorMessage = Message(
Measurement(ERROR_VALUE, rainGaugeSensor.address, NO_I2C_ADDRESS, MeasurementType::PRECIPITATION,
ErrorType::SEM404_COULD_NOT_RESET_PRECIPITATION),
rainGaugeSensor.getSensorInformation(), Time::getInstance().getEpochSeconds());
std::array<Message, 6> messages_error = {precipitationErrorMessage, Message::nullMessage(),
Message::nullMessage(), Message::nullMessage(),
Message::nullMessage(), Message::nullMessage()};
Message::sendMessages(messages_error);
}
ForteRS485::powerOffRS485Sensors();
}
ts = millis();
Message::sendMessages(messages);
// roughly takes 3s in ideal conditions
ESP_LOGD(TAG, "Sending messages took %ld ms", millis() - ts);
} catch (const NoDataAvailableException &e) {
std::cerr << e.what() << '\n';
}
// just to be safe in case exception happens above
gpio_set_level(GPIO_NUM_32, 0);
// keep it in deep sleep
gpio_hold_en((gpio_num_t)GPIO_NUM_32);
battery_monitor.setPowerMode(LC709203F_POWER_SLEEP);
// battery protection: go to deep sleep for unlimited time when voltage less than 3.2V
if (battery_monitor.cellVoltage_mV() > 3200) {
DeepSleep::deep_sleep(2);
} else {
esp_deep_sleep_start();
}
}
void loop() {}
\ No newline at end of file
//
// Created by zoe on 2/20/23.
//
#include "SentecRainGaugeSensor.h"
static const char *TAG = "SEM404";
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() {}
//
// Created by zoe on 2/20/23.
//
#ifndef CLIENT_CENTRAL_MAST_SENTECRAINGAUGESENSOR_H
#define CLIENT_CENTRAL_MAST_SENTECRAINGAUGESENSOR_H
#include "SentecSensorsRS485.h"
#include <ForteSensor.hpp>
#include <Message.hpp>
struct out_data_rain_gauge {
float precipitation;
ErrorType precipitationError;
};
/**
* @brief Class to read data from the Sentec rain gauge sensor (SEM404)
* @implements ForteSensor<out_data_rain_gauge>
* @extends SentecSensorRS485
*/
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!!!)
/**
* @brief Reset the precipitation value
* @return ErrorType. If the reset was unsuccessful, this will return ErrorType::SEM404_RESET_PRECIPITATION_ERROR.
* Otherwise it returns the error given from the device
*/
ErrorType resetPrecipitation();
void resetSensor();
/**
* @brief Get the instantaneous precipitation value
* @return out_data_rain_gauge
*/
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 <SentecSensors.h>
#include "SentecRainGaugeSensor.h"
#include <SentecSensorsRS485.h>
/***************************************
* RS485 SENSOR READOUT
****************************************/
static const char *TAG = "SENTEC";
SentecSensorRS485::SentecSensorRS485(HardwareSerial *ser, byte add) {
SentecSensorRS485::SentecSensorRS485(byte add) {
address = add;
RS485 = ser;
}
SentecSensorRS485::SentecSensorRS485(HardwareSerial *ser, byte add, uint8_t serialControlPin) {
SentecSensorRS485::SentecSensorRS485(byte add, uint8_t serialControlPin) {
address = add;
RS485 = ser;
serialCommunicationControlPin = serialControlPin;
}
......@@ -30,19 +29,11 @@ void SentecSensorRS485::write(byte queryFrame[], int length) {
}
String SentecSensorRS485::getValueStr(float value) {
if (valid) {
return String(value, 1);
} else {
return String("null");
}
return String(value, 1);
}
String SentecSensorRS485::getValueStr(int value) {
if (valid) {
return String(value);
} else {
return String("null");
}
return String(value);
}
ErrorType SentecSensorRS485::queryAddress() {
......@@ -81,7 +72,7 @@ ErrorType SentecSensorRS485::readRegister(int registerStartAddress, int register
return getResponse();
}
ErrorType SentecSensorRS485::writeRegister(int registerAddress, int value) {
WriteRegisterReturnType SentecSensorRS485::writeRegister(int registerAddress, int value) {
// function code 0x06: change sensor settings
// e.g. a new address, reset rainfal data...
......@@ -99,96 +90,123 @@ ErrorType SentecSensorRS485::writeRegister(int registerAddress, int value) {
ESP_LOGD(TAG, "Query (settings): ");
printBytes(query, 8);
write(query, sizeof(query));
return getResponse();
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
ErrorType tmp = writeRegister(word(0x07, 0xD0), add);
if (tmp == ErrorType::DATA_OK)
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;
if (!isAnswerFrameEqualToQueryFrame(answerFrame, tmp.writtenQuery)) {
return ErrorType::COULD_NOT_SET_ADDRESS;
}
return tmp.errorType;
}
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++) {
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;
resetAnswerFrame();
returnCode = ErrorType::WRONG_CRC;
}
// TODO: Test this @future_zoe
returnCode = checkForErrors(idx, responseLength);
// breaking after first successfull try
if (returnCode == ErrorType::DATA_OK) {
break;
}
}
// ESP_LOGE(TAG, "Returncode: %d", returnCode);
return returnCode;
}
ErrorType SentecSensorRS485::checkForErrors(int idx, int responseLength) {
// TODO: Not optimal, has precedence
auto connectionEndedPrematurelyReturnCode = checkConnectionEndedPrematurely(idx, responseLength);
auto sensorConnectedReturnCode = checkSensorConnected();
auto crcReturnCode = checkCRC(responseLength);
if (sensorConnectedReturnCode != ErrorType::DATA_OK) {
return sensorConnectedReturnCode;
} else if (connectionEndedPrematurelyReturnCode != ErrorType::DATA_OK) {
return connectionEndedPrematurelyReturnCode;
} else if (crcReturnCode != ErrorType::DATA_OK) {
return crcReturnCode;
}
return ErrorType::DATA_OK;
}
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
// Yes, this is necessary. No, I don't know exactly what it does.
......@@ -249,76 +267,18 @@ std::string SentecSensorRS485::formatBytes(byte *data, int length) {
}
std::string SentecSensorRS485::formatBytes(word data) {
byte arr[2] = {data >> 8, data & 0xFF};
byte arr[2] = {static_cast<byte>(data >> 8), static_cast<byte>(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;
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;
}
}
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;
}
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));
}
return true;
}
\ No newline at end of file
......@@ -3,19 +3,26 @@
#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:
public:
byte address;
uint8_t serialCommunicationControlPin = 19;
HardwareSerial *RS485;
byte answerFrame[10];
// TODO use valid flag to log None
bool valid = false;
std::shared_ptr<HardwareSerial> RS485 = RS485HardwareSerial::getInstance().getRS485Serial();
byte answerFrame[10] = {0};
SentecSensorRS485(HardwareSerial *ser, byte add);
SentecSensorRS485(byte add);
SentecSensorRS485(HardwareSerial *ser, byte add, uint8_t serialControlPin);
SentecSensorRS485(byte add, uint8_t serialControlPin);
void write(byte queryFrame[], int length);
......@@ -29,8 +36,14 @@ public:
ErrorType readRegister(int registerStartAddress, int registerLength);
ErrorType writeRegister(int registerAddress, int value);
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();
......@@ -47,51 +60,27 @@ public:
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();
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);
ErrorType checkForErrors(int idx, int responseLength);
};
#endif
//
// Created by zoe on 3/06/23.
//
#include "SentecSolarRadiationSensor.h"
String SEM228A::getSolarRadiationStr() {
return getValueStr((int)glob);
}
void SEM228A::setup() {
// TODO: check if rs485 serial is active
}
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> 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 SEM228A::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 "SentecSensorsRS485.h"
#include <ForteSensor.hpp>
struct out_data_solar_radiation {
float solarRadiation;
ErrorType solarError;
};
/**
* @brief Class to read data from the Sentec solar radiation sensor (SEM228A)
* @implements ForteSensor<out_data_solar_radiation>
* @extends SentecSensorRS485
*/
class SEM228A : public SentecSensorRS485, public ForteSensor<out_data_solar_radiation> {
public:
using SentecSensorRS485::SentecSensorRS485;
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();
};
#endif // CLIENT_CENTRAL_MAST_SENTECSOLARRADIATIONSENSOR_H
......@@ -3,8 +3,11 @@
static const char *TAG = "DR26";
void ForteDR26::setup() {
if (sensorConnected) {
ESP_LOGD(TAG, "ADS already initialized");
return;
}
Wire.begin();
ads.setGain(GAIN_ONE);
ads.setDataRate(RATE_ADS1115_8SPS);
if (ads.begin()) {
......@@ -14,8 +17,6 @@ void ForteDR26::setup() {
ESP_LOGE(TAG, "failed to initialize ADS");
sensorConnected = false;
}
delay(100);
channel = 0;
}
......@@ -23,25 +24,22 @@ Measurement ForteDR26::readData() {
float volts = 0;
int16_t adc = 0;
if (!sensorConnected) {
return {ERROR_VALUE, channel, NO_I2C_ADDRESS, MeasurementType::CIRCUMFERENCE_INCREMENT,
ErrorType::SENSOR_INIT_FAILED};
}
try {
// TODO: This function might never return if conversionComplete is never true. Check if this is the case
// FIXME: How to notice from the dr if a value is incorrect?
adc = ads.readADC_SingleEnded(channel);
volts = ads.computeVolts(adc);
}
catch (NoDataAvailableException &e) {
} catch (NoDataAvailableException &e) {
// FIXME: This catch block will never be called since we aint throwing any exceptions
return {ERROR_VALUE, channel, NO_I2C_ADDRESS, MeasurementType::CIRCUMFERENCE_INCREMENT,
ErrorType::NO_DATA};
}
if (!sensorConnected) {
return {ERROR_VALUE, channel, NO_I2C_ADDRESS, MeasurementType::CIRCUMFERENCE_INCREMENT,
ErrorType::SENSOR_INIT_FAILED};
return {ERROR_VALUE, channel, NO_I2C_ADDRESS, MeasurementType::CIRCUMFERENCE_INCREMENT, ErrorType::NO_DATA};
}
return {volts, channel, NO_I2C_ADDRESS, MeasurementType::CIRCUMFERENCE_INCREMENT,
ErrorType::DATA_OK};
return {volts, channel, NO_I2C_ADDRESS, MeasurementType::CIRCUMFERENCE_INCREMENT, ErrorType::DATA_OK};
}
// The following functions change the ADC input range: be careful
......@@ -70,9 +68,7 @@ void ForteDR26::setChannel(int c) {
std::list<Message> ForteDR26::buildMessages() {
std::list<Message> messages;
auto data = readData();
messages.emplace_back(data,
sensorInformation,
Time::getInstance().getEpochSeconds());
messages.emplace_back(data, sensorInformation, Time::getInstance().getEpochSeconds());
return messages;
}
......@@ -80,7 +76,5 @@ SensorInformation ForteDR26::getSensorInformation() const {
return sensorInformation;
}
// TODO: What is this?
Adafruit_ADS1115 ForteDR26::ads = ads;
......@@ -23,7 +23,7 @@ class ForteDR26: public ForteSensor<Measurement> {
const SensorInformation
sensorInformation{HardwareName::DRS26, SensorProtocol::Analog};
int channel;
bool sensorConnected = true;
bool sensorConnected = false;
};
#endif
\ No newline at end of file
......@@ -46,11 +46,11 @@ std::list<Message> ForteINA219::buildMessages() {
out_data_ina219 measurements = readData();
messages.emplace_back(measurements.shuntVoltage_mV, sensorInformation, 0);
messages.emplace_back(measurements.busVoltage_V, sensorInformation, 0);
messages.emplace_back(measurements.current_mA, sensorInformation, 0);
messages.emplace_back(measurements.power_mW, sensorInformation, 0);
messages.emplace_back(measurements.loadVoltage_V, sensorInformation, 0);
messages.emplace_back(measurements.shuntVoltage_mV, sensorInformation, Time::getInstance().getEpochSeconds());
messages.emplace_back(measurements.busVoltage_V, sensorInformation, Time::getInstance().getEpochSeconds());
messages.emplace_back(measurements.current_mA, sensorInformation, Time::getInstance().getEpochSeconds());
messages.emplace_back(measurements.power_mW, sensorInformation, Time::getInstance().getEpochSeconds());
messages.emplace_back(measurements.loadVoltage_V, sensorInformation, Time::getInstance().getEpochSeconds());
// messages.emplace_back(ina219OverflowData, sensorInformation, 0); TODO: Do we need this as an extra message?
return messages;
......
//
// Created by zoe on 3/8/23.
//
#ifndef CLIENT_CENTRAL_MAST_RS485HARDWARESERIAL_H
#define CLIENT_CENTRAL_MAST_RS485HARDWARESERIAL_H
#include <HardwareSerial.h>
#include <memory>
/**
* @brief Singleton class to get the RS485 serial port
* @example std::shared_ptr<HardwareSerial> RS485 = RS485HardwareSerial::getInstance().getRS485Serial();
*/
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
#include "rs485.hpp"
#include "SentecRainGaugeSensor.h"
#include "SentecSolarRadiationSensor.h"
// RS485 control
#define RS485Serial Serial2
#define RXPin 14 // Serial Receive pin
#define TXPin 15 // Serial Transmit pin
//#define RS485Serial Serial2
#define RX_PIN 14 // Serial Receive pin
#define TX_PIN 15 // Serial Transmit pin
#define RE_DE_PIN 19 // Line to pull high or low to receive or send data from RS485
......@@ -12,89 +15,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
SoilMoistureSensor
soilSensor3 = SoilMoistureSensor(&RS485Serial, 3, RE_DE_PIN); //.....
SoilMoistureSensor soilSensor4 = SoilMoistureSensor(&RS485Serial, 4, RE_DE_PIN);
SoilMoistureSensor soilSensor5 = SoilMoistureSensor(&RS485Serial, 5, 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);
SEM228A solarSensor(1, RE_DE_PIN);
SEM404 rainGauge = SEM404(2, RE_DE_PIN);
void ForteRS485::setup() {
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, TX_PIN, RX_PIN);
}
void ForteRS485::teardown() {
RS485Serial->end();
}
out_data_rs485 ForteRS485::readData() {
powerOnRS485Sensors();
out_data_rs485 output{};
output.solar = solarSensor.readData();
output.precipitation = rainGauge.readData();
powerOffRS485Sensors();
return output;
}
void ForteRS485::powerOffRS485Sensors() {
digitalWrite(POWER_SWITCH_PIN_12V, LOW);
digitalWrite(POWER_SWITCH_PIN_5V, LOW);
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.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();
digitalWrite(POWER_SWITCH_PIN_12V, LOW);
digitalWrite(POWER_SWITCH_PIN_5V, LOW);
gpio_hold_en((gpio_num_t) POWER_SWITCH_PIN_12V);
gpio_hold_en((gpio_num_t) POWER_SWITCH_PIN_5V);
return output;
gpio_hold_en((gpio_num_t)POWER_SWITCH_PIN_12V);
gpio_hold_en((gpio_num_t)POWER_SWITCH_PIN_5V);
}
void ForteRS485::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(100);
}
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};
messages.emplace_back(solarRadiation, sensorInformation,
Time::getInstance().getEpochSeconds());
messages.emplace_back(soilTemp3, sensorInformation,
Time::getInstance().getEpochSeconds());
messages.emplace_back(soilTemp4, sensorInformation,
Time::getInstance().getEpochSeconds());
messages.emplace_back(soilTemp5, sensorInformation,
Time::getInstance().getEpochSeconds());
messages.emplace_back(soilMoisture3, sensorInformation,
Time::getInstance().getEpochSeconds());
messages.emplace_back(soilMoisture4, sensorInformation,
Time::getInstance().getEpochSeconds());
messages.emplace_back(soilMoisture5, sensorInformation,
Time::getInstance().getEpochSeconds());
messages.emplace_back(precipitation,
sensorInformation,
Time::getInstance().getEpochSeconds());
return messages;
std::list<Message> ForteRS485::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());
return messages;
}
SensorInformation Forte_RS485::getSensorInformation() const {
return sensorInformation;
SensorInformation ForteRS485::getSensorInformation() const {
return sensorInformation;
}
\ No newline at end of file
#ifndef _RS485
#define _RS485
#include <MeasurementTypes.h>
#include "Message.hpp"
#include "ForteSensor.hpp"
#include "SentecSensors.h"
#include "Message.hpp"
#include "SentecSensorsRS485.h"
#include <MeasurementTypes.h>
#include <RS485HardwareSerial.h>
#include <SentecRainGaugeSensor.h>
#include <SentecSolarRadiationSensor.h>
/**
* @brief Struct to hold all the data from the RS485 sensors
*/
struct out_data_rs485 {
float solarRadiation;
float soilMoisture3;
float soilTemperature3;
float soilMoisture4;
float soilTemperature4;
float soilMoisture5;
float soilTemperature5;
float precipitation;
out_data_solar_radiation solar;
out_data_rain_gauge precipitation;
};
class Forte_RS485: public ForteSensor<out_data_rs485> {
public:
void setup() override;
out_data_rs485 readData() override;
std::list<Message> buildMessages() override;
[[nodiscard]] SensorInformation getSensorInformation() const override;
/**
* @brief Class to read data from both the Sentec solar radiation sensor and the Sentec rain gauge sensor at once
* This class is required to be instantiated and setup in order to use RS485 communication.
*/
class ForteRS485 : public ForteSensor<out_data_rs485> {
public:
void setup() override;
out_data_rs485 readData() override;
std::list<Message> buildMessages() override;
[[nodiscard]] SensorInformation getSensorInformation() const override;
void teardown();
/**
* @brief Power on the RS485 sensors
*/
static void powerOnRS485Sensors();
/**
* @brief Power off the RS485 sensors
*/
static void powerOffRS485Sensors();
private:
const SensorInformation
sensorInformation{HardwareName::RS485, SensorProtocol::RS485};
private:
std::shared_ptr<HardwareSerial> RS485Serial;
const SensorInformation sensorInformation{HardwareName::RS485, SensorProtocol::RS485};
};
#endif
\ No newline at end of file
#endif
\ No newline at end of file
......@@ -13,12 +13,14 @@ namespace SDCardLogger {
printToSerial = printToSerialAsWell;
}
int vprintf_into_sd(const char *szFormat, va_list args) {
String logstring = "[" + rtc.getDateTime() + "] ";
logstring += szFormat;
auto timeSinceBootMs = esp_timer_get_time() / 1000;
String logstring = String(timeSinceBootMs) + ", ";
// print current core
logstring += "Core: ";
logstring += xPortGetCoreID();
logstring += ". ";
logstring += ", ";
logstring += "[" + rtc.getDateTime() + "] ";
logstring += szFormat;
if (!SDUtilities::isSDAvailable()) {
if (printToSerial) {
logstring += " (SD card not available)\n";
......
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