From a38a18da0d1b8b69b092b04e2de5daf205d89f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 16 Jun 2024 02:59:22 +0200 Subject: [PATCH] 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 --- platformio.ini | 18 ++- src/configuration.h | 1 + src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 121 ++++++++++++++- src/modules/Telemetry/EnvironmentTelemetry.h | 4 + .../Telemetry/Sensor/NAU7802Sensor.cpp | 143 ++++++++++++++++++ src/modules/Telemetry/Sensor/NAU7802Sensor.h | 31 ++++ .../Telemetry/Sensor/TelemetrySensor.h | 7 + 9 files changed, 318 insertions(+), 11 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/NAU7802Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/NAU7802Sensor.h diff --git a/platformio.ini b/platformio.ini index 0de3e25c9..d8d398775 100644 --- a/platformio.ini +++ b/platformio.ini @@ -122,10 +122,7 @@ lib_deps = adafruit/Adafruit BMP280 Library@^2.6.8 adafruit/Adafruit BMP085 Library@^1.2.4 adafruit/Adafruit BME280 Library@^2.2.2 - https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 - boschsensortec/BME68x Sensor Library@^1.1.40407 adafruit/Adafruit MCP9808 Library@^2.0.0 - https://github.com/KodinLanewave/INA3221@^1.0.0 adafruit/Adafruit INA260 Library@^1.5.0 adafruit/Adafruit INA219@^1.2.0 adafruit/Adafruit SHTC3 Library@^1.0.0 @@ -135,13 +132,22 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 adafruit/Adafruit AHTX0@^2.0.5 - lewisxhe/SensorLib@^0.2.0 adafruit/Adafruit LSM6DS@^4.7.2 - mprograms/QMC5883LCompass@^1.2.0 adafruit/Adafruit VEML7700 Library@^2.1.6 adafruit/Adafruit SHT4x Library@^1.0.4 adafruit/Adafruit TSL2591 Library@^1.4.5 + sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@^1.0.5 ClosedCube OPT3001@^1.1.2 emotibit/EmotiBit MLX90632@^1.0.8 dfrobot/DFRobot_RTU@^1.0.3 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file + + + https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 + boschsensortec/BME68x Sensor Library@^1.1.40407 + https://github.com/KodinLanewave/INA3221@^1.0.0 + lewisxhe/SensorLib@^0.2.0 + mprograms/QMC5883LCompass@^1.2.0 + + + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee + diff --git a/src/configuration.h b/src/configuration.h index 62c48a205..1149f344c 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -136,6 +136,7 @@ along with this program. If not, see . #define OPT3001_ADDR_ALT 0x44 #define MLX90632_ADDR 0x3A #define DFROBOT_LARK_ADDR 0x42 +#define NAU7802_ADDR 0x2A // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 20994ede1..dcc1f40ae 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -50,7 +50,8 @@ class ScanI2C MLX90632, AHT10, BMX160, - DFROBOT_LARK + DFROBOT_LARK, + NAU7802 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index f800a9963..6766db014 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -350,6 +350,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591 light sensor found\n"); SCAN_SIMPLE_CASE(OPT3001_ADDR, OPT3001, "OPT3001 light sensor found\n"); SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n"); + SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n"); default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 46b8a1ad8..ff3202067 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -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 \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index cdd9491d4..ca150347e 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -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; diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp new file mode 100644 index 000000000..39ac4b08b --- /dev/null +++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp @@ -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 +#include + +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 \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.h b/src/modules/Telemetry/Sensor/NAU7802Sensor.h new file mode 100644 index 000000000..c53a3b31a --- /dev/null +++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.h @@ -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 + +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 \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h index 35cb7965d..da376ad31 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.h +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h @@ -4,6 +4,7 @@ #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "MeshModule.h" #include "NodeDB.h" #include @@ -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;