mirror of
https://github.com/meshtastic/firmware.git
synced 2025-08-13 00:35:16 +00:00
Merge branch 'master' into apollo
This commit is contained in:
commit
652441fcc0
@ -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}" ]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit ab576a4a122c1a1d0a3c2235b0a0cf3bd4a83c65
|
||||
Subproject commit dc066c89f73fce882e5a47648cba18a1967a7f56
|
@ -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:
|
||||
|
@ -136,6 +136,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define OPT3001_ADDR_ALT 0x44
|
||||
#define MLX90632_ADDR 0x3A
|
||||
#define DFROBOT_LARK_ADDR 0x42
|
||||
#define NAU7802_ADDR 0x2A
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ACCELEROMETER
|
||||
|
@ -50,7 +50,8 @@ class ScanI2C
|
||||
MLX90632,
|
||||
AHT10,
|
||||
BMX160,
|
||||
DFROBOT_LARK
|
||||
DFROBOT_LARK,
|
||||
NAU7802
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -117,8 +117,16 @@ class LGFX : public lgfx::LGFX_Device
|
||||
static LGFX *tft = nullptr;
|
||||
|
||||
#elif defined(RAK14014)
|
||||
#include <RAK14014_FT6336U.h>
|
||||
#include <TFT_eSPI.h>
|
||||
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 <LovyanGFX.hpp> // 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)
|
||||
|
@ -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) \
|
||||
|
@ -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<char>(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<char>(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) ||
|
||||
(event->inputEvent == static_cast<char>(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT))) {
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
#if defined(T_WATCH_S3) || defined(RAK14014)
|
||||
if (event->inputEvent == static_cast<char>(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) {
|
||||
this->payload = 0xb4;
|
||||
} else if (event->inputEvent == static_cast<char>(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
|
||||
|
||||
|
@ -98,7 +98,7 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
||||
int getNextIndex();
|
||||
int getPrevIndex();
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
#if defined(T_WATCH_S3) || defined(RAK14014)
|
||||
void drawKeyboard(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
String keyForCoordinates(uint x, uint y);
|
||||
bool shift = false;
|
||||
@ -150,7 +150,7 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
||||
unsigned long lastTouchMillis = 0;
|
||||
String temporaryMessage;
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
#if defined(T_WATCH_S3) || defined(RAK14014)
|
||||
Letter keyboard[2][4][10] = {{{{"Q", 20, 0, 0, 0, 0},
|
||||
{"W", 22, 0, 0, 0, 0},
|
||||
{"E", 17, 0, 0, 0, 0},
|
||||
|
95
src/modules/DropzoneModule.cpp
Normal file
95
src/modules/DropzoneModule.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
#if !MESHTASTIC_EXCLUDE_DROPZONE
|
||||
|
||||
#include "DropzoneModule.h"
|
||||
#include "MeshService.h"
|
||||
#include "configuration.h"
|
||||
#include "gps/GeoCoord.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "main.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "modules/Telemetry/Sensor/DFRobotLarkSensor.h"
|
||||
#include "modules/Telemetry/UnitConversions.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
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<const char *>(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
|
37
src/modules/DropzoneModule.h
Normal file
37
src/modules/DropzoneModule.h
Normal file
@ -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
|
@ -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.
|
||||
|
||||
|
@ -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
|
@ -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;
|
||||
|
@ -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
|
143
src/modules/Telemetry/Sensor/NAU7802Sensor.cpp
Normal file
143
src/modules/Telemetry/Sensor/NAU7802Sensor.cpp
Normal 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
|
31
src/modules/Telemetry/Sensor/NAU7802Sensor.h
Normal file
31
src/modules/Telemetry/Sensor/NAU7802Sensor.h
Normal 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
|
@ -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;
|
||||
|
21
src/modules/Telemetry/UnitConversions.cpp
Normal file
21
src/modules/Telemetry/UnitConversions.cpp
Normal file
@ -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;
|
||||
}
|
10
src/modules/Telemetry/UnitConversions.h
Normal file
10
src/modules/Telemetry/UnitConversions.h
Normal file
@ -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);
|
||||
};
|
@ -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
|
@ -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
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user