diff --git a/Dockerfile b/Dockerfile
index fee6c62d4..08cb3925d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -48,6 +48,7 @@ USER mesh
WORKDIR /home/mesh
COPY --from=builder /tmp/firmware/release/meshtasticd /home/mesh/
+RUN mkdir data
VOLUME /home/mesh/data
CMD [ "sh", "-cx", "./meshtasticd -d /home/mesh/data --hwid=${HWID:-$RANDOM}" ]
diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index 0408c8a83..63671d482 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -1,6 +1,6 @@
[nrf52_base]
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
-platform = platformio/nordicnrf52@^10.4.0
+platform = platformio/nordicnrf52@^10.5.0
extends = arduino_base
build_type = debug
diff --git a/platformio.ini b/platformio.ini
index 34471fc54..d8d398775 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -73,6 +73,7 @@ build_flags = -Wno-missing-field-initializers
-DRADIOLIB_EXCLUDE_FSK4
-DRADIOLIB_EXCLUDE_APRS
-DRADIOLIB_EXCLUDE_LORAWAN
+ -DMESHTASTIC_EXCLUDE_DROPZONE=1
monitor_speed = 115200
@@ -121,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
@@ -134,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/protobufs b/protobufs
index ab576a4a1..dc066c89f 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit ab576a4a122c1a1d0a3c2235b0a0cf3bd4a83c65
+Subproject commit dc066c89f73fce882e5a47648cba18a1967a7f56
diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h
index f03752cad..f45511cca 100644
--- a/src/AccelerometerThread.h
+++ b/src/AccelerometerThread.h
@@ -138,6 +138,7 @@ class AccelerometerThread : public concurrency::OSThread
float heading = FusionCompassCalculateHeading(FusionConventionNed, ga, ma);
switch (config.display.compass_orientation) {
+ case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0_INVERTED:
case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0:
break;
case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90:
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 a6bc2c151..33da9e25a 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/gps/GPS.cpp b/src/gps/GPS.cpp
index 91246b9bd..f8279da01 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -28,6 +28,12 @@
#define GPS_STANDBY_THRESHOLD_MINUTES 15
#endif
+// How many seconds of sleep make it worthwhile for the GPS to use powered-on standby
+// Shorter than this, and we'll just wait instead
+#ifndef GPS_IDLE_THRESHOLD_SECONDS
+#define GPS_IDLE_THRESHOLD_SECONDS 10
+#endif
+
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
HardwareSerial *GPS::_serial_gps = &Serial1;
#else
@@ -776,14 +782,22 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
{
// Record the current powerState
if (on)
- powerState = GPS_AWAKE;
- else if (!on && standbyOnly)
+ powerState = GPS_ACTIVE;
+ else if (!enabled) // User has disabled with triple press
+ powerState = GPS_OFF;
+ else if (sleepTime <= GPS_IDLE_THRESHOLD_SECONDS * 1000UL)
+ powerState = GPS_IDLE;
+ else if (standbyOnly)
powerState = GPS_STANDBY;
else
powerState = GPS_OFF;
LOG_DEBUG("GPS::powerState=%d\n", powerState);
+ // If the next update is due *really soon*, don't actually power off or enter standby. Just wait it out.
+ if (!on && powerState == GPS_IDLE)
+ return;
+
if (on) {
clearBuffer(); // drop any old data waiting in the buffer before re-enabling
if (en_gpio)
@@ -880,54 +894,69 @@ void GPS::setConnected()
void GPS::setAwake(bool wantAwake)
{
- // If user has disabled GPS, make sure it is off, not just in standby
+ // If user has disabled GPS, make sure it is off, not just in standby or idle
if (!wantAwake && !enabled && powerState != GPS_OFF) {
setGPSPower(false, false, 0);
return;
}
// If GPS power state needs to change
- if ((wantAwake && powerState != GPS_AWAKE) || (!wantAwake && powerState == GPS_AWAKE)) {
+ if ((wantAwake && powerState != GPS_ACTIVE) || (!wantAwake && powerState == GPS_ACTIVE)) {
LOG_DEBUG("WANT GPS=%d\n", wantAwake);
// Calculate how long it takes to get a GPS lock
if (wantAwake) {
+ // Record the time we start looking for a lock
lastWakeStartMsec = millis();
} else {
+ // Record by how much we missed our ideal target postion.gps_update_interval (for logging only)
+ // Need to calculate this before we update lastSleepStartMsec, to make the new prediction
+ int32_t lateByMsec = (int32_t)(millis() - lastSleepStartMsec) - (int32_t)getSleepTime();
+
+ // Record the time we finish looking for a lock
lastSleepStartMsec = millis();
- if (GPSCycles == 1) { // Skipping initial lock time, as it will likely be much longer than average
- averageLockTime = lastSleepStartMsec - lastWakeStartMsec;
- } else if (GPSCycles > 1) {
- averageLockTime += ((int32_t)(lastSleepStartMsec - lastWakeStartMsec) - averageLockTime) / (int32_t)GPSCycles;
+
+ // How long did it take to get GPS lock this time?
+ uint32_t lockTime = lastSleepStartMsec - lastWakeStartMsec;
+
+ // Update the lock-time prediction
+ // Used pre-emptively, attempting to hit target of gps.position_update_interval
+ switch (GPSCycles) {
+ case 0:
+ LOG_DEBUG("Initial GPS lock took %ds\n", lockTime / 1000);
+ break;
+ case 1:
+ predictedLockTime = lockTime; // Avoid slow ramp-up - start with a real value
+ LOG_DEBUG("GPS Lock took %ds\n", lockTime / 1000);
+ break;
+ default:
+ // Predict lock-time using exponential smoothing: respond slowly to changes
+ predictedLockTime = (lockTime * 0.2) + (predictedLockTime * 0.8); // Latest lock time has 20% weight on prediction
+ LOG_INFO("GPS Lock took %ds. %s by %ds. Next lock predicted to take %ds.\n", lockTime / 1000,
+ (lateByMsec > 0) ? "Late" : "Early", abs(lateByMsec) / 1000, predictedLockTime / 1000);
}
GPSCycles++;
- LOG_DEBUG("GPS Lock took %d, average %d\n", (lastSleepStartMsec - lastWakeStartMsec) / 1000, averageLockTime / 1000);
}
+ // How long to wait before attempting next GPS update
+ // Aims to hit position.gps_update_interval by using the lock-time prediction
+ uint32_t compensatedSleepTime = (getSleepTime() > predictedLockTime) ? (getSleepTime() - predictedLockTime) : 0;
+
// If long interval between updates: power off between updates
- if ((int32_t)getSleepTime() - averageLockTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) {
- setGPSPower(wantAwake, false, getSleepTime() - averageLockTime);
- return;
+ if (compensatedSleepTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) {
+ setGPSPower(wantAwake, false, getSleepTime() - predictedLockTime);
}
- // If waking frequently: standby only. Would use more power trying to reacquire lock each time
- else if ((int32_t)getSleepTime() - averageLockTime > 10000) { // 10 seconds is enough for standby
+ // If waking relatively frequently: don't power off. Would use more energy trying to reacquire lock each time
+ // We'll either use a "powered-on" standby, or just wait it out, depending on how soon the next update is due
+ // Will decide which inside setGPSPower method
+ else {
#ifdef GPS_UC6580
- setGPSPower(wantAwake, false, getSleepTime() - averageLockTime);
+ setGPSPower(wantAwake, false, compensatedSleepTime);
#else
- setGPSPower(wantAwake, true, getSleepTime() - averageLockTime);
+ setGPSPower(wantAwake, true, compensatedSleepTime);
#endif
- return;
}
-
- // Gradually recover from an abnormally long "time to get lock"
- if (averageLockTime > 20000) {
- averageLockTime -= 1000; // eventually want to sleep again.
- }
-
- // Make sure we don't have a fallthrough where GPS is stuck off
- if (wantAwake)
- setGPSPower(true, true, 0);
}
}
@@ -1033,14 +1062,14 @@ int32_t GPS::runOnce()
uint32_t timeAsleep = now - lastSleepStartMsec;
auto sleepTime = getSleepTime();
- if (powerState != GPS_AWAKE && (sleepTime != UINT32_MAX) &&
- ((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - averageLockTime)))) {
+ if (powerState != GPS_ACTIVE && (sleepTime != UINT32_MAX) &&
+ ((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - predictedLockTime)))) {
// We now want to be awake - so wake up the GPS
setAwake(true);
}
// While we are awake
- if (powerState == GPS_AWAKE) {
+ if (powerState == GPS_ACTIVE) {
// LOG_DEBUG("looking for location\n");
// If we've already set time from the GPS, no need to ask the GPS
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
@@ -1086,7 +1115,7 @@ int32_t GPS::runOnce()
// 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms
// if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake.
- return (powerState == GPS_AWAKE) ? GPS_THREAD_INTERVAL : 5000;
+ return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
}
// clear the GPS rx buffer as quickly as possible
@@ -1617,9 +1646,9 @@ bool GPS::whileIdle()
{
unsigned int charsInBuf = 0;
bool isValid = false;
- if (powerState != GPS_AWAKE) {
+ if (powerState != GPS_ACTIVE) {
clearBuffer();
- return (powerState == GPS_AWAKE);
+ return (powerState == GPS_ACTIVE);
}
#ifdef SERIAL_BUFFER_SIZE
if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) {
@@ -1650,6 +1679,10 @@ bool GPS::whileIdle()
}
void GPS::enable()
{
+ // Clear the old lock-time prediction
+ GPSCycles = 0;
+ predictedLockTime = 0;
+
enabled = true;
setInterval(GPS_THREAD_INTERVAL);
setAwake(true);
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index e9ec111a7..55bd42d0f 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -39,9 +39,10 @@ typedef enum {
} GPS_RESPONSE;
enum GPSPowerState : uint8_t {
- GPS_OFF = 0,
- GPS_AWAKE = 1,
- GPS_STANDBY = 2,
+ GPS_OFF = 0, // Physically powered off
+ GPS_ACTIVE = 1, // Awake and want a position
+ GPS_STANDBY = 2, // Physically powered on, but soft-sleeping
+ GPS_IDLE = 3, // Awake, but not wanting another position yet
};
// Generate a string representation of DOP
@@ -72,7 +73,7 @@ class GPS : private concurrency::OSThread
uint32_t rx_gpio = 0;
uint32_t tx_gpio = 0;
uint32_t en_gpio = 0;
- int32_t averageLockTime = 0;
+ uint32_t predictedLockTime = 0;
uint32_t GPSCycles = 0;
int speedSelect = 0;
@@ -93,7 +94,7 @@ class GPS : private concurrency::OSThread
bool GPSInitFinished = false; // Init thread finished?
bool GPSInitStarted = false; // Init thread finished?
- GPSPowerState powerState = GPS_OFF; // GPS_AWAKE if we want a location right now
+ GPSPowerState powerState = GPS_OFF; // GPS_ACTIVE if we want a location right now
uint8_t numSatellites = 0;
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 5a892bbfb..60168cffc 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -277,6 +277,30 @@ static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
}
}
+/// Check if the display can render a string (detect special chars; emoji)
+static bool haveGlyphs(const char *str)
+{
+#if defined(OLED_UA) || defined(OLED_RU)
+ // Don't want to make any assumptions about custom language support
+ return true;
+#endif
+
+ // Check each character with the lookup function for the OLED library
+ // We're not really meant to use this directly..
+ bool have = true;
+ for (uint16_t i = 0; i < strlen(str); i++) {
+ uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]);
+ // If font doesn't support a character, it is substituted for ¿
+ if (result == 191 && (uint8_t)str[i] != 191) {
+ have = false;
+ break;
+ }
+ }
+
+ LOG_DEBUG("haveGlyphs=%d\n", have);
+ return have;
+}
+
#ifdef USE_EINK
/// Used on eink displays while in deep sleep
static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@@ -301,14 +325,15 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *pauseText = "Screen Paused";
const char *idText = owner.short_name;
+ const bool useId = haveGlyphs(idText); // This bool is used to hide the idText box if we can't render the short name
constexpr uint16_t padding = 5;
constexpr uint8_t dividerGap = 1;
constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in.
// Dimensions
- const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText));
+ const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText), true); // "true": handle utf8 chars
const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText));
- const uint16_t boxWidth = padding + idTextWidth + padding + padding + pauseTextWidth + padding;
+ const uint16_t boxWidth = padding + (useId ? idTextWidth + padding + padding : 0) + pauseTextWidth + padding;
const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding;
// Position
@@ -318,7 +343,7 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta
const int16_t boxBottom = boxTop + boxHeight - 1;
const int16_t idTextLeft = boxLeft + padding;
const int16_t idTextTop = boxTop + padding;
- const int16_t pauseTextLeft = boxLeft + padding + idTextWidth + padding + padding;
+ const int16_t pauseTextLeft = boxLeft + (useId ? padding + idTextWidth + padding : 0) + padding;
const int16_t pauseTextTop = boxTop + padding;
const int16_t dividerX = boxLeft + padding + idTextWidth + padding;
const int16_t dividerTop = boxTop + 1 + dividerGap;
@@ -331,12 +356,14 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
// Draw: Text
- display->drawString(idTextLeft, idTextTop, idText);
+ if (useId)
+ display->drawString(idTextLeft, idTextTop, idText);
display->drawString(pauseTextLeft, pauseTextTop, pauseText);
display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold
// Draw: divider
- display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
+ if (useId)
+ display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
}
#endif
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index b19e402b8..8ea90c523 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -117,8 +117,16 @@ class LGFX : public lgfx::LGFX_Device
static LGFX *tft = nullptr;
#elif defined(RAK14014)
+#include
#include
TFT_eSPI *tft = nullptr;
+FT6336U ft6336u;
+
+static uint8_t _rak14014_touch_int = false; // TP interrupt generation flag.
+static void rak14014_tpIntHandle(void)
+{
+ _rak14014_touch_int = true;
+}
#elif defined(ST7789_CS)
#include // Graphics and font library for ST7735 driver chip
@@ -642,8 +650,12 @@ void TFTDisplay::sendCommand(uint8_t com)
void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
{
+#ifdef RAK14014
+ // todo
+#else
tft->setBrightness(_brightness);
LOG_DEBUG("Brightness is set to value: %i \n", _brightness);
+#endif
}
void TFTDisplay::flipScreenVertically()
@@ -657,6 +669,7 @@ void TFTDisplay::flipScreenVertically()
bool TFTDisplay::hasTouch(void)
{
#ifdef RAK14014
+ return true;
#elif !defined(M5STACK)
return tft->touch() != nullptr;
#else
@@ -667,6 +680,15 @@ bool TFTDisplay::hasTouch(void)
bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
{
#ifdef RAK14014
+ if (_rak14014_touch_int) {
+ _rak14014_touch_int = false;
+ /* The X and Y axes have to be switched */
+ *y = ft6336u.read_touch1_x();
+ *x = TFT_HEIGHT - ft6336u.read_touch1_y();
+ return true;
+ } else {
+ return false;
+ }
#elif !defined(M5STACK)
return tft->getTouch(x, y);
#else
@@ -716,7 +738,10 @@ bool TFTDisplay::connect()
#elif defined(RAK14014)
tft->setRotation(1);
tft->setSwapBytes(true);
-// tft->fillScreen(TFT_BLACK);
+ // tft->fillScreen(TFT_BLACK);
+ ft6336u.begin();
+ pinMode(SCREEN_TOUCH_INT, INPUT_PULLUP);
+ attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING);
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2)
tft->setRotation(1); // T-Deck has the TFT in landscape
#elif defined(T_WATCH_S3)
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index 2a209ad0a..d0e643dff 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -135,6 +135,8 @@ typedef struct _meshtastic_AdminMessage {
bool enter_dfu_mode_request;
/* Delete the file by the specified path from the device */
char delete_file_request[201];
+ /* Set zero and offset for scale chips */
+ uint32_t set_scale;
/* Set the owner for this node */
meshtastic_User set_owner;
/* Set channels (using the new API).
@@ -238,6 +240,7 @@ extern "C" {
#define meshtastic_AdminMessage_get_node_remote_hardware_pins_response_tag 20
#define meshtastic_AdminMessage_enter_dfu_mode_request_tag 21
#define meshtastic_AdminMessage_delete_file_request_tag 22
+#define meshtastic_AdminMessage_set_scale_tag 23
#define meshtastic_AdminMessage_set_owner_tag 32
#define meshtastic_AdminMessage_set_channel_tag 33
#define meshtastic_AdminMessage_set_config_tag 34
@@ -281,6 +284,7 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,get_node_remote_hardware_pin
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,get_node_remote_hardware_pins_response,get_node_remote_hardware_pins_response), 20) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,enter_dfu_mode_request,enter_dfu_mode_request), 21) \
X(a, STATIC, ONEOF, STRING, (payload_variant,delete_file_request,delete_file_request), 22) \
+X(a, STATIC, ONEOF, UINT32, (payload_variant,set_scale,set_scale), 23) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_owner,set_owner), 32) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_channel,set_channel), 33) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_config,set_config), 34) \
diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index 9b993ae5a..f513e045f 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -49,7 +49,7 @@ CannedMessageModule::CannedMessageModule()
LOG_INFO("CannedMessageModule is enabled\n");
// T-Watch interface currently has no way to select destination type, so default to 'node'
-#ifdef T_WATCH_S3
+#if defined(T_WATCH_S3) || defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE;
#endif
@@ -75,7 +75,7 @@ int CannedMessageModule::splitConfiguredMessages()
String messages = cannedMessageModuleConfig.messages;
-#ifdef T_WATCH_S3
+#if defined(T_WATCH_S3) || defined(RAK14014)
String separator = messages.length() ? "|" : "";
messages = "[---- Free Text ----]" + separator + messages;
@@ -144,7 +144,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
}
if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT)) {
-#ifdef T_WATCH_S3
+#if defined(T_WATCH_S3) || defined(RAK14014)
if (this->currentMessageIndex == 0) {
this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT;
@@ -170,7 +170,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
e.frameChanged = true;
this->currentMessageIndex = -1;
-#ifndef T_WATCH_S3
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->freetext = ""; // clear freetext
this->cursor = 0;
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
@@ -183,7 +183,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
(event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) ||
(event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT))) {
-#ifdef T_WATCH_S3
+#if defined(T_WATCH_S3) || defined(RAK14014)
if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) {
this->payload = 0xb4;
} else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) {
@@ -283,7 +283,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
}
}
-#ifdef T_WATCH_S3
+#if defined(T_WATCH_S3) || defined(RAK14014)
if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
String keyTapped = keyForCoordinates(event->touchX, event->touchY);
@@ -404,7 +404,7 @@ int32_t CannedMessageModule::runOnce()
this->freetext = ""; // clear freetext
this->cursor = 0;
-#ifndef T_WATCH_S3
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
#endif
@@ -417,7 +417,7 @@ int32_t CannedMessageModule::runOnce()
this->freetext = ""; // clear freetext
this->cursor = 0;
-#ifndef T_WATCH_S3
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
#endif
@@ -437,7 +437,7 @@ int32_t CannedMessageModule::runOnce()
powerFSM.trigger(EVENT_PRESS);
return INT32_MAX;
} else {
-#ifdef T_WATCH_S3
+#if defined(T_WATCH_S3) || defined(RAK14014)
sendText(this->dest, indexChannels[this->channel], this->messages[this->currentMessageIndex], true);
#else
sendText(NODENUM_BROADCAST, channels.getPrimaryIndex(), this->messages[this->currentMessageIndex], true);
@@ -454,7 +454,7 @@ int32_t CannedMessageModule::runOnce()
this->freetext = ""; // clear freetext
this->cursor = 0;
-#ifndef T_WATCH_S3
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
#endif
@@ -471,7 +471,7 @@ int32_t CannedMessageModule::runOnce()
this->freetext = ""; // clear freetext
this->cursor = 0;
-#ifndef T_WATCH_S3
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
#endif
@@ -484,7 +484,7 @@ int32_t CannedMessageModule::runOnce()
this->freetext = ""; // clear freetext
this->cursor = 0;
-#ifndef T_WATCH_S3
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
#endif
@@ -714,7 +714,7 @@ void CannedMessageModule::showTemporaryMessage(const String &message)
setIntervalFromNow(2000);
}
-#ifdef T_WATCH_S3
+#if defined(T_WATCH_S3) || defined(RAK14014)
String CannedMessageModule::keyForCoordinates(uint x, uint y)
{
@@ -949,7 +949,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
display->drawString(10 + x, 0 + y + FONT_HEIGHT_SMALL, "Canned Message\nModule disabled.");
} else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
-#ifdef T_WATCH_S3
+#if defined(T_WATCH_S3) || defined(RAK14014)
drawKeyboard(display, state, 0, 0);
#else
diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h
index 43897e782..00e8c2bf9 100644
--- a/src/modules/CannedMessageModule.h
+++ b/src/modules/CannedMessageModule.h
@@ -98,7 +98,7 @@ class CannedMessageModule : public SinglePortModule, public Observable
+
+#include "modules/Telemetry/Sensor/DFRobotLarkSensor.h"
+#include "modules/Telemetry/UnitConversions.h"
+
+#include
+
+DropzoneModule *dropzoneModule;
+
+int32_t DropzoneModule::runOnce()
+{
+ // Send on a 5 second delay from receiving the matching request
+ if (startSendConditions != 0 && (startSendConditions + 5000U) < millis()) {
+ service.sendToMesh(sendConditions(), RX_SRC_LOCAL);
+ startSendConditions = 0;
+ }
+ // Run every second to check if we need to send conditions
+ return 1000;
+}
+
+ProcessMessage DropzoneModule::handleReceived(const meshtastic_MeshPacket &mp)
+{
+ auto &p = mp.decoded;
+ char matchCompare[54];
+ auto incomingMessage = reinterpret_cast(p.payload.bytes);
+ sprintf(matchCompare, "%s conditions", owner.short_name);
+ if (strncasecmp(incomingMessage, matchCompare, strlen(matchCompare)) == 0) {
+ LOG_DEBUG("Received dropzone conditions request\n");
+ startSendConditions = millis();
+ }
+
+ sprintf(matchCompare, "%s conditions", owner.long_name);
+ if (strncasecmp(incomingMessage, matchCompare, strlen(matchCompare)) == 0) {
+ LOG_DEBUG("Received dropzone conditions request\n");
+ startSendConditions = millis();
+ }
+ return ProcessMessage::CONTINUE;
+}
+
+meshtastic_MeshPacket *DropzoneModule::sendConditions()
+{
+ char replyStr[200];
+ /*
+ CLOSED @ {HH:MM:SS}z
+ Wind 2 kts @ 125°
+ 29.25 inHg 72°C
+ */
+ uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
+ int hour = 0, min = 0, sec = 0;
+ if (rtc_sec > 0) {
+ long hms = rtc_sec % SEC_PER_DAY;
+ hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
+
+ hour = hms / SEC_PER_HOUR;
+ min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
+ sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN;
+ }
+
+ // Check if the dropzone is open or closed by reading the analog pin
+ // If pin is connected to GND (below 100 should be lower than floating voltage),
+ // the dropzone is open
+ auto dropzoneStatus = analogRead(A1) < 100 ? "OPEN" : "CLOSED";
+ auto reply = allocDataPacket();
+
+ auto node = nodeDB->getMeshNode(nodeDB->getNodeNum());
+ if (sensor.hasSensor()) {
+ meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
+ sensor.getMetrics(&telemetry);
+ auto windSpeed = UnitConversions::MetersPerSecondToKnots(telemetry.variant.environment_metrics.wind_speed);
+ auto windDirection = telemetry.variant.environment_metrics.wind_direction;
+ auto temp = telemetry.variant.environment_metrics.temperature;
+ auto baro = UnitConversions::HectoPascalToInchesOfMercury(telemetry.variant.environment_metrics.barometric_pressure);
+ sprintf(replyStr, "%s @ %02d:%02d:%02dz\nWind %.2f kts @ %d°\nBaro %.2f inHg %.2f°C", dropzoneStatus, hour, min, sec,
+ windSpeed, windDirection, baro, temp);
+ } else {
+ LOG_ERROR("No sensor found\n");
+ sprintf(replyStr, "%s @ %02d:%02d:%02d\nNo sensor found", dropzoneStatus, hour, min, sec);
+ }
+ LOG_DEBUG("Conditions reply: %s\n", replyStr);
+ reply->decoded.payload.size = strlen(replyStr); // You must specify how many bytes are in the reply
+ memcpy(reply->decoded.payload.bytes, replyStr, reply->decoded.payload.size);
+
+ return reply;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/DropzoneModule.h b/src/modules/DropzoneModule.h
new file mode 100644
index 000000000..28f54ee0f
--- /dev/null
+++ b/src/modules/DropzoneModule.h
@@ -0,0 +1,37 @@
+#pragma once
+#if !MESHTASTIC_EXCLUDE_DROPZONE
+#include "SinglePortModule.h"
+#include "modules/Telemetry/Sensor/DFRobotLarkSensor.h"
+
+/**
+ * An example module that replies to a message with the current conditions
+ * and status at the dropzone when it receives a text message mentioning it's name followed by "conditions"
+ */
+class DropzoneModule : public SinglePortModule, private concurrency::OSThread
+{
+ DFRobotLarkSensor sensor;
+
+ public:
+ /** Constructor
+ * name is for debugging output
+ */
+ DropzoneModule() : SinglePortModule("dropzone", meshtastic_PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("DropzoneModule")
+ {
+ // Set up the analog pin for reading the dropzone status
+ pinMode(PIN_A1, INPUT);
+ }
+
+ virtual int32_t runOnce() override;
+
+ protected:
+ /** Called to handle a particular incoming message
+ */
+ virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
+
+ private:
+ meshtastic_MeshPacket *sendConditions();
+ uint32_t startSendConditions = 0;
+};
+
+extern DropzoneModule *dropzoneModule;
+#endif
\ No newline at end of file
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index e6c44fae6..1b4bbc3b4 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -70,6 +70,11 @@
#include "modules/SerialModule.h"
#endif
#endif
+
+#if !MESHTASTIC_EXCLUDE_DROPZONE
+#include "modules/DropzoneModule.h"
+#endif
+
/**
* Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else)
*/
@@ -100,6 +105,10 @@ void setupModules()
#if !MESHTASTIC_EXCLUDE_ATAK
atakPluginModule = new AtakPluginModule();
#endif
+
+#if !MESHTASTIC_EXCLUDE_DROPZONE
+ dropzoneModule = new DropzoneModule();
+#endif
// Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
// to a global variable.
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/DFRobotLarkSensor.h b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
index b26d690b1..7a988e84a 100644
--- a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
+++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
@@ -1,3 +1,7 @@
+#pragma once
+
+#ifndef _MT_DFROBOTLARKSENSOR_H
+#define _MT_DFROBOTLARKSENSOR_H
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
@@ -21,4 +25,5 @@ class DFRobotLarkSensor : public TelemetrySensor
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
};
+#endif
#endif
\ No newline at end of file
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;
diff --git a/src/modules/Telemetry/UnitConversions.cpp b/src/modules/Telemetry/UnitConversions.cpp
new file mode 100644
index 000000000..9f40de40f
--- /dev/null
+++ b/src/modules/Telemetry/UnitConversions.cpp
@@ -0,0 +1,21 @@
+#include "UnitConversions.h"
+
+float UnitConversions::CelsiusToFahrenheit(float celcius)
+{
+ return (celcius * 9) / 5 + 32;
+}
+
+float UnitConversions::MetersPerSecondToKnots(float metersPerSecond)
+{
+ return metersPerSecond * 1.94384;
+}
+
+float UnitConversions::MetersPerSecondToMilesPerHour(float metersPerSecond)
+{
+ return metersPerSecond * 2.23694;
+}
+
+float UnitConversions::HectoPascalToInchesOfMercury(float hectoPascal)
+{
+ return hectoPascal * 0.029529983071445;
+}
diff --git a/src/modules/Telemetry/UnitConversions.h b/src/modules/Telemetry/UnitConversions.h
new file mode 100644
index 000000000..60f9b664a
--- /dev/null
+++ b/src/modules/Telemetry/UnitConversions.h
@@ -0,0 +1,10 @@
+#pragma once
+
+class UnitConversions
+{
+ public:
+ static float CelsiusToFahrenheit(float celcius);
+ static float MetersPerSecondToKnots(float metersPerSecond);
+ static float MetersPerSecondToMilesPerHour(float metersPerSecond);
+ static float HectoPascalToInchesOfMercury(float hectoPascal);
+};
diff --git a/variants/rak10701/platformio.ini b/variants/rak10701/platformio.ini
index 75e29bca0..4c9bf3b20 100644
--- a/variants/rak10701/platformio.ini
+++ b/variants/rak10701/platformio.ini
@@ -17,6 +17,8 @@ lib_deps =
https://github.com/RAKWireless/RAK13800-W5100S.git#1.0.2
rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2
bodmer/TFT_eSPI
+ beegee-tokyo/RAKwireless RAK12034@^1.0.0
+ beegee-tokyo/RAK14014-FT6336U @ 1.0.1
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
;upload_protocol = jlink
\ No newline at end of file
diff --git a/variants/rak10701/variant.h b/variants/rak10701/variant.h
index 076504c16..c263796ee 100644
--- a/variants/rak10701/variant.h
+++ b/variants/rak10701/variant.h
@@ -307,10 +307,10 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG
#define SCREEN_ROTATE
#define SCREEN_TRANSITION_FRAMERATE 5
-#define HAS_TOUCHSCREEN 0
-#define SCREEN_TOUCH_INT 10 // From tp.h on the tracker open source code.
-#define TOUCH_I2C_PORT 0
-#define TOUCH_SLAVE_ADDRESS 0x5D // GT911
+#define HAS_TOUCHSCREEN 1
+#define SCREEN_TOUCH_INT WB_IO6
+
+#define CANNED_MESSAGE_MODULE_ENABLE 1
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini
index 24f209b01..4870d4b68 100644
--- a/variants/rak4631/platformio.ini
+++ b/variants/rak4631/platformio.ini
@@ -16,7 +16,7 @@ lib_deps =
melopero/Melopero RV3028@^1.1.0
https://github.com/RAKWireless/RAK13800-W5100S.git#1.0.2
rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2
- beegee-tokyo/RAKwireless RAK12034@^1.0.0
+ https://github.com/meshtastic/RAK12034-BMX160.git#4821355fb10390ba8557dc43ca29a023bcfbb9d9
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
;upload_protocol = jlink
\ No newline at end of file