WIP: add NAU7802 based scale controller. (#4092)

* WIP: add NAU7802 based scale controller. Needs proto commit

* WIP: add NAU7802 based scale controller. Needs proto commit

* telemetry uses kg, scale internally g

* add sensor calibration setters
This commit is contained in:
Thomas Göttgens
2024-06-16 02:59:22 +02:00
committed by GitHub
parent 96be051bff
commit a38a18da0d
9 changed files with 318 additions and 11 deletions

View File

@@ -27,6 +27,7 @@
#include "Sensor/LPS22HBSensor.h"
#include "Sensor/MCP9808Sensor.h"
#include "Sensor/MLX90632Sensor.h"
#include "Sensor/NAU7802Sensor.h"
#include "Sensor/OPT3001Sensor.h"
#include "Sensor/RCWL9620Sensor.h"
#include "Sensor/SHT31Sensor.h"
@@ -51,6 +52,7 @@ RCWL9620Sensor rcwl9620Sensor;
AHT10Sensor aht10Sensor;
MLX90632Sensor mlx90632Sensor;
DFRobotLarkSensor dfRobotLarkSensor;
NAU7802Sensor nau7802Sensor;
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
@@ -125,6 +127,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
result = aht10Sensor.runOnce();
if (mlx90632Sensor.hasSensor())
result = mlx90632Sensor.runOnce();
if (nau7802Sensor.hasSensor())
result = nau7802Sensor.runOnce();
}
return result;
} else {
@@ -223,12 +227,18 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
"Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " +
String(lastMeasurement.variant.environment_metrics.current, 0) + "mA");
}
if (lastMeasurement.variant.environment_metrics.iaq != 0) {
display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq));
}
if (lastMeasurement.variant.environment_metrics.distance != 0)
display->drawString(x, y += fontHeight(FONT_SMALL),
"Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm");
if (lastMeasurement.variant.environment_metrics.weight != 0)
display->drawString(x, y += fontHeight(FONT_SMALL),
"Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg");
}
bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
@@ -245,8 +255,9 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac
LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", sender, t->variant.environment_metrics.voltage,
t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance, t->variant.environment_metrics.lux);
LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees\n", sender,
t->variant.environment_metrics.wind_speed, t->variant.environment_metrics.wind_direction);
LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees, weight=%fkg\n", sender,
t->variant.environment_metrics.wind_speed, t->variant.environment_metrics.wind_direction,
t->variant.environment_metrics.weight);
#endif
// release previous packet before occupying a new spot
@@ -331,6 +342,10 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
valid = valid && rcwl9620Sensor.getMetrics(&m);
hasSensor = true;
}
if (nau7802Sensor.hasSensor()) {
valid = valid && nau7802Sensor.getMetrics(&m);
hasSensor = true;
}
if (aht10Sensor.hasSensor()) {
if (!bmp280Sensor.hasSensor()) {
valid = valid && aht10Sensor.getMetrics(&m);
@@ -354,8 +369,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", m.variant.environment_metrics.voltage,
m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance, m.variant.environment_metrics.lux);
LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees\n", m.variant.environment_metrics.wind_speed,
m.variant.environment_metrics.wind_direction);
LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees, weight=%fkg\n", m.variant.environment_metrics.wind_speed,
m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight);
sensor_read_error_count = 0;
@@ -388,4 +403,102 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
return valid;
}
AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response)
{
AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED;
if (dfRobotLarkSensor.hasSensor()) {
result = dfRobotLarkSensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (sht31Sensor.hasSensor()) {
result = sht31Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (lps22hbSensor.hasSensor()) {
result = lps22hbSensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (shtc3Sensor.hasSensor()) {
result = shtc3Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (bmp085Sensor.hasSensor()) {
result = bmp085Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (bmp280Sensor.hasSensor()) {
result = bmp280Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (bme280Sensor.hasSensor()) {
result = bme280Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (bme680Sensor.hasSensor()) {
result = bme680Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (mcp9808Sensor.hasSensor()) {
result = mcp9808Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (ina219Sensor.hasSensor()) {
result = ina219Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (ina260Sensor.hasSensor()) {
result = ina260Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (veml7700Sensor.hasSensor()) {
result = veml7700Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (tsl2591Sensor.hasSensor()) {
result = tsl2591Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (opt3001Sensor.hasSensor()) {
result = opt3001Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (mlx90632Sensor.hasSensor()) {
result = mlx90632Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (rcwl9620Sensor.hasSensor()) {
result = rcwl9620Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (nau7802Sensor.hasSensor()) {
result = nau7802Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
if (aht10Sensor.hasSensor()) {
result = aht10Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
return result;
}
#endif

View File

@@ -37,6 +37,10 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu
*/
bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response) override;
private:
float CelsiusToFahrenheit(float c);
bool firstTime = 1;

View File

@@ -0,0 +1,143 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "FSCommon.h"
#include "NAU7802Sensor.h"
#include "TelemetrySensor.h"
#include <pb_decode.h>
#include <pb_encode.h>
meshtastic_Nau7802Config nau7802config = meshtastic_Nau7802Config_init_zero;
NAU7802Sensor::NAU7802Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_NAU7802, "NAU7802") {}
int32_t NAU7802Sensor::runOnce()
{
LOG_INFO("Init sensor: %s\n", sensorName);
if (!hasSensor()) {
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
}
status = nau7802.begin(*nodeTelemetrySensorsMap[sensorType].second);
nau7802.setSampleRate(NAU7802_SPS_320);
if (!loadCalibrationData()) {
LOG_ERROR("Failed to load calibration data\n");
}
nau7802.calibrateAFE();
LOG_INFO("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor());
return initI2CSensor();
}
void NAU7802Sensor::setup() {}
bool NAU7802Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
LOG_DEBUG("NAU7802Sensor::getMetrics\n");
nau7802.powerUp();
// Wait for the sensor to become ready for one second max
uint32_t start = millis();
while (!nau7802.available()) {
delay(100);
if (millis() - start > 1000) {
nau7802.powerDown();
return false;
}
}
// Check if we have correct calibration values after powerup
LOG_DEBUG("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor());
measurement->variant.environment_metrics.weight = nau7802.getWeight() / 1000; // sample is in kg
nau7802.powerDown();
return true;
}
void NAU7802Sensor::calibrate(float weight)
{
nau7802.calculateCalibrationFactor(weight * 1000, 64); // internal sample is in grams
if (!saveCalibrationData()) {
LOG_WARN("Failed to save calibration data\n");
}
LOG_INFO("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor());
}
AdminMessageHandleResult NAU7802Sensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response)
{
AdminMessageHandleResult result;
switch (request->which_payload_variant) {
case meshtastic_AdminMessage_set_scale_tag:
if (request->set_scale == 0) {
this->tare();
LOG_DEBUG("Client requested to tare scale\n");
} else {
this->calibrate(request->set_scale);
LOG_DEBUG("Client requested to calibrate to %d kg\n", request->set_scale);
}
result = AdminMessageHandleResult::HANDLED;
break;
default:
result = AdminMessageHandleResult::NOT_HANDLED;
}
return result;
}
void NAU7802Sensor::tare()
{
nau7802.calculateZeroOffset(64);
if (!saveCalibrationData()) {
LOG_WARN("Failed to save calibration data\n");
}
LOG_INFO("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor());
}
bool NAU7802Sensor::saveCalibrationData()
{
if (FSCom.exists(nau7802ConfigFileName) && !FSCom.remove(nau7802ConfigFileName)) {
LOG_WARN("Can't remove old state file\n");
}
auto file = FSCom.open(nau7802ConfigFileName, FILE_O_WRITE);
nau7802config.zeroOffset = nau7802.getZeroOffset();
nau7802config.calibrationFactor = nau7802.getCalibrationFactor();
bool okay = false;
if (file) {
LOG_INFO("%s state write to %s.\n", sensorName, nau7802ConfigFileName);
pb_ostream_t stream = {&writecb, &file, meshtastic_Nau7802Config_size};
if (!pb_encode(&stream, &meshtastic_Nau7802Config_msg, &nau7802config)) {
LOG_ERROR("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream));
} else {
okay = true;
}
file.flush();
file.close();
} else {
LOG_INFO("Can't write %s state (File: %s).\n", sensorName, nau7802ConfigFileName);
}
return okay;
}
bool NAU7802Sensor::loadCalibrationData()
{
auto file = FSCom.open(nau7802ConfigFileName, FILE_O_READ);
bool okay = false;
if (file) {
LOG_INFO("%s state read from %s.\n", sensorName, nau7802ConfigFileName);
pb_istream_t stream = {&readcb, &file, meshtastic_Nau7802Config_size};
if (!pb_decode(&stream, &meshtastic_Nau7802Config_msg, &nau7802config)) {
LOG_ERROR("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
} else {
nau7802.setZeroOffset(nau7802config.zeroOffset);
nau7802.setCalibrationFactor(nau7802config.calibrationFactor);
okay = true;
}
file.close();
} else {
LOG_INFO("No %s state found (File: %s).\n", sensorName, nau7802ConfigFileName);
}
return okay;
}
#endif

View File

@@ -0,0 +1,31 @@
#include "MeshModule.h"
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include <SparkFun_Qwiic_Scale_NAU7802_Arduino_Library.h>
class NAU7802Sensor : public TelemetrySensor
{
private:
NAU7802 nau7802;
protected:
virtual void setup() override;
const char *nau7802ConfigFileName = "/prefs/nau7802.dat";
bool saveCalibrationData();
bool loadCalibrationData();
public:
NAU7802Sensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
void tare();
void calibrate(float weight);
AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response) override;
};
#endif

View File

@@ -4,6 +4,7 @@
#pragma once
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "MeshModule.h"
#include "NodeDB.h"
#include <utility>
@@ -42,6 +43,12 @@ class TelemetrySensor
virtual void setup();
public:
virtual AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response)
{
return AdminMessageHandleResult::NOT_HANDLED;
}
bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; }
virtual int32_t runOnce() = 0;