Explorar el Código

Merge branch 'ripplebiz:dev' into dev

cod3doomy hace 1 año
padre
commit
77bfc0db1c

+ 1 - 0
examples/companion_radio/NodePrefs.h

@@ -19,6 +19,7 @@ struct NodePrefs {  // persisted to file
   uint8_t tx_power_dbm;
   uint8_t telemetry_mode_base;
   uint8_t telemetry_mode_loc;
+  uint8_t telemetry_mode_env;
   float rx_delay_base;
   uint32_t ble_pin;
 };

+ 27 - 0
examples/companion_radio/UITask.cpp

@@ -53,6 +53,29 @@ void UITask::begin(DisplayDriver* display, NodePrefs* node_prefs, const char* bu
 
   // v1.2.3 (1 Jan 2025)
   sprintf(_version_info, "%s (%s)", version, build_date);
+
+#ifdef PIN_BUZZER
+  buzzer.begin();
+#endif
+}
+
+void UITask::soundBuzzer(UIEventType bet) {
+#if defined(PIN_BUZZER)
+switch(bet){
+  case UIEventType::contactMessage:
+    // gemini's pick
+    buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
+    break;
+  case UIEventType::channelMessage:
+  case UIEventType::roomMessage:
+  case UIEventType::newContactMessage:
+  case UIEventType::none:
+  default:
+    break;
+}
+#endif
+//  Serial.print("DBG:  Buzzzzzz -> ");
+//  Serial.println((int) bet);
 }
 
 void UITask::msgRead(int msgcount) {
@@ -248,6 +271,10 @@ void UITask::loop() {
   buttonHandler();
   userLedHandler();
 
+#ifdef PIN_BUZZER
+  if (buzzer.isPlaying())  buzzer.loop();
+#endif
+
   if (_display != NULL && _display->isOn()) {
     static bool _firstBoot = true;
     if(_firstBoot && millis() >= BOOT_SCREEN_MILLIS) {

+ 18 - 0
examples/companion_radio/UITask.h

@@ -4,11 +4,27 @@
 #include <helpers/ui/DisplayDriver.h>
 #include <stddef.h>
 
+#ifdef PIN_BUZZER
+  #include <helpers/ui/buzzer.h>
+#endif
+
 #include "NodePrefs.h"
 
+ enum class UIEventType
+{
+    none,
+    contactMessage,
+    channelMessage,
+    roomMessage,
+    newContactMessage
+};
+
 class UITask {
   DisplayDriver* _display;
   mesh::MainBoard* _board;
+#ifdef PIN_BUZZER
+  genericBuzzer buzzer;
+#endif
   unsigned long _next_refresh, _auto_off;
   bool _connected;
   uint32_t _pin_code;
@@ -24,6 +40,7 @@ class UITask {
   void userLedHandler();
   void renderBatteryIndicator(uint16_t batteryMilliVolts);
 
+ 
 public:
 
   UITask(mesh::MainBoard* board) : _board(board), _display(NULL) {
@@ -37,5 +54,6 @@ public:
   void clearMsgPreview();
   void msgRead(int msgcount);
   void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount);
+  void soundBuzzer(UIEventType bet = UIEventType::none);
   void loop();
 };

+ 28 - 14
examples/companion_radio/main.cpp

@@ -271,8 +271,8 @@ class MyMesh : public BaseChatMesh {
 
   void saveContacts() {
 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+    _fs->remove("/contacts3");
     File file = _fs->open("/contacts3", FILE_O_WRITE);
-    if (file) { file.seek(0); file.truncate(); }
 #elif defined(RP2040_PLATFORM)
     File file = _fs->open("/contacts3", "w");
 #else
@@ -336,8 +336,8 @@ class MyMesh : public BaseChatMesh {
 
   void saveChannels() {
   #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+    _fs->remove("/channels2");
     File file = _fs->open("/channels2", FILE_O_WRITE);
-    if (file) { file.seek(0); file.truncate(); }
   #elif defined(RP2040_PLATFORM)
     File file = _fs->open("/channels2", "w");
   #else
@@ -393,8 +393,8 @@ class MyMesh : public BaseChatMesh {
     sprintf(path, "/bl/%s", fname);
 
   #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+    _fs->remove(path);
     File f = _fs->open(path, FILE_O_WRITE);
-    if (f) { f.seek(0); f.truncate(); }
   #elif defined(RP2040_PLATFORM)
     File f = _fs->open(path, "w");
   #else
@@ -483,10 +483,6 @@ class MyMesh : public BaseChatMesh {
     return 0;  // queue is empty
   }
 
-  void soundBuzzer() {
-    // TODO
-  }
-
 protected:
   float getAirtimeBudgetFactor() const override {
     return _prefs.airtime_factor;
@@ -523,7 +519,9 @@ protected:
         _serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE);
       }
     } else {
-      soundBuzzer();
+    #ifdef DISPLAY_CLASS
+      ui_task.soundBuzzer(UIEventType::newContactMessage);
+    #endif
     }
 
     saveContacts();
@@ -584,7 +582,9 @@ protected:
       frame[0] = PUSH_CODE_MSG_WAITING;  // send push 'tickle'
       _serial->writeFrame(frame, 1);
     } else {
-      soundBuzzer();
+    #ifdef DISPLAY_CLASS
+      ui_task.soundBuzzer(UIEventType::contactMessage);
+    #endif
     }
   #ifdef DISPLAY_CLASS
     ui_task.newMsg(path_len, from.name, text, offline_queue_len);
@@ -635,7 +635,9 @@ protected:
       frame[0] = PUSH_CODE_MSG_WAITING;  // send push 'tickle'
       _serial->writeFrame(frame, 1);
     } else {
-      soundBuzzer();
+    #ifdef DISPLAY_CLASS
+      ui_task.soundBuzzer(UIEventType::channelMessage);
+    #endif
     }
   #ifdef DISPLAY_CLASS
     ui_task.newMsg(path_len, "Public", text, offline_queue_len);
@@ -659,6 +661,12 @@ protected:
         permissions |= cp & TELEM_PERM_LOCATION;
       }
 
+      if (_prefs.telemetry_mode_env == TELEM_MODE_ALLOW_ALL) {
+        permissions |= TELEM_PERM_ENVIRONMENT;
+      } else if (_prefs.telemetry_mode_env == TELEM_MODE_ALLOW_FLAGS) {
+        permissions |= cp & TELEM_PERM_ENVIRONMENT;
+      }
+
       if (permissions & TELEM_PERM_BASE) {   // only respond if base permission bit is set
         telemetry.reset();
         telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
@@ -822,7 +830,7 @@ public:
       file.read((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm));  // 68
       file.read((uint8_t *) &_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base));  // 69
       file.read((uint8_t *) &_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc));  // 70
-      file.read(pad, 1);  // 71
+      file.read((uint8_t *) &_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env));  // 71
       file.read((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base));  // 72
       file.read(pad, 4);   // 76
       file.read((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin));  // 80
@@ -861,6 +869,11 @@ public:
     mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4);
     strcpy(_prefs.node_name, pub_key_hex);
 
+    // if name is provided as a build flag, use that as default node name instead
+    #ifdef ADVERT_NAME
+    strcpy(_prefs.node_name, ADVERT_NAME);
+    #endif
+
     // load persisted prefs
     if (_fs->exists("/new_prefs")) {
       loadPrefsInt("/new_prefs");   // new filename
@@ -913,8 +926,8 @@ public:
 
   void savePrefs() {
 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+    _fs->remove("/new_prefs");
     File file = _fs->open("/new_prefs", FILE_O_WRITE);
-    if (file) { file.seek(0); file.truncate(); }
 #elif defined(RP2040_PLATFORM)
     File file = _fs->open("/new_prefs", "w");
 #else
@@ -938,7 +951,7 @@ public:
       file.write((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm));  // 68
       file.write((uint8_t *) &_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base));  // 69
       file.write((uint8_t *) &_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc));  // 70
-      file.write(pad, 1);  // 71
+      file.write((uint8_t *) &_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env));  // 71
       file.write((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base));  // 72
       file.write(pad, 4);   // 76
       file.write((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin));  // 80
@@ -983,7 +996,7 @@ public:
       memcpy(&out_frame[i], &lon, 4); i += 4;
       out_frame[i++] = 0;  // reserved
       out_frame[i++] = 0;  // reserved
-      out_frame[i++] = (_prefs.telemetry_mode_loc << 2) | (_prefs.telemetry_mode_base);  // v5+
+      out_frame[i++] = (_prefs.telemetry_mode_env << 4) | (_prefs.telemetry_mode_loc << 2) | (_prefs.telemetry_mode_base);  // v5+
       out_frame[i++] = _prefs.manual_add_contacts;
 
       uint32_t freq = _prefs.freq * 1000;
@@ -1275,6 +1288,7 @@ public:
       if (len >= 3) {
         _prefs.telemetry_mode_base = cmd_frame[2] & 0x03;       // v5+
         _prefs.telemetry_mode_loc = (cmd_frame[2] >> 2) & 0x03;
+        _prefs.telemetry_mode_env = (cmd_frame[2] >> 4) & 0x03;
       }
       savePrefs();
       writeOKFrame();

+ 2 - 2
examples/simple_secure_chat/main.cpp

@@ -127,8 +127,8 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
 
   void saveContacts() {
 #if defined(NRF52_PLATFORM)
+    _fs->remove("/contacts");
     File file = _fs->open("/contacts", FILE_O_WRITE);
-    if (file) { file.seek(0); file.truncate(); }
 #elif defined(RP2040_PLATFORM)
     File file = _fs->open("/contacts", "w");
 #else
@@ -341,8 +341,8 @@ public:
 
   void savePrefs() {
 #if defined(NRF52_PLATFORM)
+    _fs->remove("/node_prefs");
     File file = _fs->open("/node_prefs", FILE_O_WRITE);
-    if (file) { file.seek(0); file.truncate(); }
 #elif defined(RP2040_PLATFORM)
     File file = _fs->open("/node_prefs", "w");
 #else

+ 1 - 1
src/helpers/CommonCLI.cpp

@@ -74,8 +74,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
 
 void CommonCLI::savePrefs(FILESYSTEM* fs) {
 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+  fs->remove("/com_prefs");
   File file = fs->open("/com_prefs", FILE_O_WRITE);
-  if (file) { file.seek(0); file.truncate(); }
 #elif defined(RP2040_PLATFORM)
   File file = fs->open("/com_prefs", "w");
 #else

+ 2 - 2
src/helpers/IdentityStore.cpp

@@ -47,8 +47,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) {
   sprintf(filename, "%s/%s.id", _dir, name);
 
 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+  _fs->remove(filename);
   File file = _fs->open(filename, FILE_O_WRITE);
-  if (file) { file.seek(0); file.truncate(); }
 #elif defined(RP2040_PLATFORM)
   File file = _fs->open(filename, "w");
 #else
@@ -69,8 +69,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const
   sprintf(filename, "%s/%s.id", _dir, name);
 
 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+  _fs->remove(filename);
   File file = _fs->open(filename, FILE_O_WRITE);
-  if (file) { file.seek(0); file.truncate(); }
 #elif defined(RP2040_PLATFORM)
   File file = _fs->open(filename, "w");
 #else

+ 90 - 0
src/helpers/sensors/EnvironmentSensorManager.cpp

@@ -0,0 +1,90 @@
+#include "EnvironmentSensorManager.h"
+
+static Adafruit_AHTX0 AHTX0;
+static Adafruit_BME280 BME280;
+static Adafruit_INA3221 INA3221;
+static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS);
+
+bool EnvironmentSensorManager::begin() {
+
+  if (INA3221.begin(TELEM_INA3221_ADDRESS, &Wire)) {
+      MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS);
+      MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID());
+
+      for(int i = 0; i < 3; i++) {
+        INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE);
+      }
+      INA3221_initialized = true;
+  } else {
+      INA3221_initialized = false;
+      MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS);
+  }
+
+  if (INA219.begin(&Wire)) {
+      MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS);
+      INA219_initialized = true;
+  } else {
+      INA219_initialized = false;
+      MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
+  }
+
+  if (AHTX0.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) {
+      MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS);
+      AHTX0_initialized = true;
+  } else {
+      AHTX0_initialized = false;
+      MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS);
+  }
+
+  if (BME280.begin(TELEM_BME280_ADDRESS, &Wire)) {
+      MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS);
+      MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID);
+      BME280_initialized = true;
+  } else {
+      BME280_initialized = false;
+      MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS);
+  }
+
+  return true;
+}
+
+bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
+  next_available_channel = TELEM_CHANNEL_SELF + 1;
+  if (requester_permissions & TELEM_PERM_ENVIRONMENT) {
+    if (INA3221_initialized) {
+      for(int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) {
+        // add only enabled INA3221 channels to telemetry
+        if (INA3221.isChannelEnabled(i)) {
+          float voltage = INA3221.getBusVoltage(i);
+          float current = INA3221.getCurrentAmps(i);
+          telemetry.addVoltage(next_available_channel, voltage);
+          telemetry.addCurrent(next_available_channel, current);
+          telemetry.addPower(next_available_channel, voltage * current);
+          next_available_channel++;
+        }
+      }
+    }
+    if (INA219_initialized) {
+        telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V());
+        telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000);
+        telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000);
+        next_available_channel++;
+    }
+    if (AHTX0_initialized) {
+      sensors_event_t humidity, temp;
+      AHTX0.getEvent(&humidity, &temp);
+
+      telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature);
+      telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity);      
+    }
+
+    if (BME280_initialized) {
+        telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature());
+        telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity());
+        telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure());
+        telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA));
+    }
+  }
+
+  return true;
+}

+ 32 - 0
src/helpers/sensors/EnvironmentSensorManager.h

@@ -0,0 +1,32 @@
+#pragma once
+
+#include <helpers/SensorManager.h>
+#include <Mesh.h>
+#include <Adafruit_INA3221.h>
+#include <Adafruit_INA219.h>
+#include <Adafruit_AHTX0.h>
+#include <Adafruit_BME280.h>
+
+#define TELEM_AHTX_ADDRESS      0x38      // AHT10, AHT20 temperature and humidity sensor I2C address
+#define TELEM_BME280_ADDRESS    0x76      // BME280 environmental sensor I2C address
+#define TELEM_INA3221_ADDRESS   0x42      // INA3221 3 channel current sensor I2C address
+#define TELEM_INA219_ADDRESS    0x40      // INA219 single channel current sensor I2C address
+
+#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts
+#define TELEM_INA3221_NUM_CHANNELS 3
+
+#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25)    // Athmospheric pressure at sea level
+
+class EnvironmentSensorManager : public SensorManager {
+protected:
+  int next_available_channel = TELEM_CHANNEL_SELF + 1;
+
+  bool INA3221_initialized = false;
+  bool INA219_initialized = false;
+  bool BME280_initialized = false;
+  bool AHTX0_initialized = false;
+public:
+  EnvironmentSensorManager(){};
+  bool begin() override;
+  bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
+};

+ 54 - 0
src/helpers/ui/buzzer.cpp

@@ -0,0 +1,54 @@
+#ifdef PIN_BUZZER
+#include "buzzer.h"
+
+void genericBuzzer::begin() {
+//    Serial.print("DBG: Setting up buzzer on pin ");
+//    Serial.println(PIN_BUZZER);
+    #ifdef PIN_BUZZER_EN
+      pinMode(PIN_BUZZER_EN, OUTPUT);
+      digitalWrite(PIN_BUZZER_EN, HIGH);
+    #endif
+
+    quiet(false);
+    pinMode(PIN_BUZZER, OUTPUT);
+    startup();
+}
+
+void genericBuzzer::play(const char *melody) {
+    if (isPlaying())   // interrupt existing
+    {
+        rtttl::stop();
+    }
+
+    if (_is_quiet) return;
+
+    rtttl::begin(PIN_BUZZER,melody);
+//    Serial.print("DBG: Playing melody - isQuiet: ");
+//    Serial.println(isQuiet());
+}
+
+bool genericBuzzer::isPlaying() {
+    return rtttl::isPlaying();
+}
+
+void genericBuzzer::loop() {
+    if (!rtttl::done()) rtttl::play();
+}
+
+void genericBuzzer::startup() {
+    play(startup_song);
+}
+
+void genericBuzzer::shutdown() {
+    play(shutdown_song);
+}
+
+void genericBuzzer::quiet(bool buzzer_state) {
+    _is_quiet = buzzer_state;
+}
+
+bool genericBuzzer::isQuiet() {
+    return _is_quiet;
+}
+
+#endif  // ifdef PIN_BUZZER

+ 37 - 0
src/helpers/ui/buzzer.h

@@ -0,0 +1,37 @@
+#pragma once
+
+#include <Arduino.h>
+#include <NonBlockingRtttl.h>
+
+/* class abstracts underlying RTTTL library 
+
+    Just a simple imlementation to start.  At the moment use same
+    melody for message and discovery
+    Suggest enum type for different sounds
+    - on message
+    - on discovery
+
+    TODO
+    - make message ring tone configurable
+
+*/
+
+class genericBuzzer
+{
+    public:
+        void begin();  // set up buzzer port
+        void play(const char *melody); // Generic play function
+        void loop();  // loop driven-nonblocking
+        void startup();  // play startup sound
+        void shutdown();  // play shutdown sound
+        bool isPlaying();  // returns true if a sound is still playing else false
+        void quiet(bool buzzer_state);  // enables or disables the buzzer
+        bool isQuiet();  // get buzzer state on/off
+
+    private:
+        // gemini's picks:
+        const char *startup_song = "Startup:d=4,o=5,b=160:16c6,16e6,8g6";
+        const char *shutdown_song = "Shutdown:d=4,o=5,b=100:8g5,16e5,16c5";
+
+        bool _is_quiet = true;
+};

+ 1 - 2
variants/lilygo_tbeam/platformio.ini

@@ -7,6 +7,7 @@ build_flags =
   -D LILYGO_TBEAM
   -D RADIO_CLASS=CustomSX1276
   -D WRAPPER_CLASS=CustomSX1276Wrapper
+  -D SX127X_CURRENT_LIMIT=120
   -D LORA_TX_POWER=20
   -D P_LORA_TX_LED=4
   -D PIN_BOARD_SDA=21
@@ -31,8 +32,6 @@ build_flags =
   -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D OFFLINE_QUEUE_SIZE=256
-;  -D MESH_PACKET_LOGGING=1
-;  -D MESH_DEBUG=1
 ;  -D RADIOLIB_DEBUG_BASIC=1
 ;  -D ENABLE_PRIVATE_KEY_IMPORT=1
 ;  -D ENABLE_PRIVATE_KEY_EXPORT=1

+ 1 - 0
variants/lilygo_tbeam_supreme_SX1262/platformio.ini

@@ -14,6 +14,7 @@ build_flags =
   -D DISPLAY_CLASS=SH1106Display
   -D SX126X_CURRENT_LIMIT=140
   -D SX126X_RX_BOOSTED_GAIN=1
+  -D SX126X_CURRENT_LIMIT=140
 build_src_filter = ${esp32_base.build_src_filter}
   +<../variants/lilygo_tbeam_supreme_SX1262>
   +<helpers/ui/SH1106Display.cpp>

+ 2 - 3
variants/nano_g2_ultra/nano-g2.cpp

@@ -27,11 +27,10 @@ void NanoG2Ultra::begin()
   // for future use, sub-classes SHOULD call this from their begin()
   startup_reason = BD_STARTUP_NORMAL;
 
+  // set user button
   pinMode(PIN_BUTTON1, INPUT);
+
   // the external notification circuit is shared for both buzzer and led
-  // need to find out the switch state or somehow write a function that can
-  // sound the buzzer or signal the led. the led will stay on once brought HIGH
-  // and can be then brought LOW. It turns off with a hardware btn.
   pinMode(EXT_NOTIFY_OUT, OUTPUT);
   digitalWrite(EXT_NOTIFY_OUT, LOW);
 

+ 4 - 1
variants/nano_g2_ultra/platformio.ini

@@ -40,6 +40,7 @@ build_flags =
   -D BLE_DEBUG_LOGGING=1
   -D OFFLINE_QUEUE_SIZE=256
   -D DISPLAY_CLASS=SH1106Display
+  -D PIN_BUZZER=4
 ;  -D ENABLE_PRIVATE_KEY_IMPORT=1
 ;  -D ENABLE_PRIVATE_KEY_EXPORT=1
 ;  -D MESH_PACKET_LOGGING=1
@@ -47,10 +48,12 @@ build_flags =
 build_src_filter = ${Nano_G2_Ultra.build_src_filter}
   +<helpers/nrf52/SerialBLEInterface.cpp>
   +<helpers/ui/SH1106Display.cpp>
+  +<helpers/ui/buzzer.cpp>
   +<../examples/companion_radio>
 lib_deps =
   ${Nano_G2_Ultra.lib_deps}
   densaugeo/base64 @ ~1.4.0
   adafruit/Adafruit SH110X @ ~2.1.13
   adafruit/Adafruit GFX Library @ ^1.12.1
-
+  stevemarple/MicroNMEA @ ^2.0.6
+  end2endzone/NonBlockingRTTTL@^1.3.0

+ 105 - 2
variants/nano_g2_ultra/target.cpp

@@ -1,6 +1,7 @@
 #include <Arduino.h>
 #include "target.h"
 #include <helpers/ArduinoHelpers.h>
+#include <helpers/sensors/MicroNMEALocationProvider.h>
 
 NanoG2Ultra board;
 
@@ -10,10 +11,11 @@ WRAPPER_CLASS radio_driver(radio, board);
 
 VolatileRTCClock fallback_clock;
 AutoDiscoverRTCClock rtc_clock(fallback_clock);
-SensorManager sensors;
+MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
+NanoG2UltraSensorManager sensors = NanoG2UltraSensorManager(nmea);
 
 #ifdef DISPLAY_CLASS
-  DISPLAY_CLASS display;
+DISPLAY_CLASS display;
 #endif
 
 #ifndef LORA_CR
@@ -73,6 +75,107 @@ void radio_set_tx_power(uint8_t dbm)
   radio.setOutputPower(dbm);
 }
 
+void NanoG2UltraSensorManager::start_gps()
+{
+  if (!gps_active)
+  {
+    MESH_DEBUG_PRINTLN("starting GPS");
+    digitalWrite(PIN_GPS_STANDBY, HIGH);
+    gps_active = true;
+  }
+}
+
+void NanoG2UltraSensorManager::stop_gps()
+{
+  if (gps_active)
+  {
+    MESH_DEBUG_PRINTLN("stopping GPS");
+    digitalWrite(PIN_GPS_STANDBY, LOW);
+    gps_active = false;
+  }
+}
+
+bool NanoG2UltraSensorManager::begin()
+{
+  Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); // be sure to tx into rx and rx into tx
+  Serial1.begin(115200);
+
+  pinMode(PIN_GPS_STANDBY, OUTPUT);
+  digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby
+  delay(500);
+
+  // We'll consider GPS detected if we see any data on Serial1
+  if (Serial1.available() > 0)
+  {
+    MESH_DEBUG_PRINTLN("GPS detected");
+  }
+  else
+  {
+    MESH_DEBUG_PRINTLN("No GPS detected");
+  }
+  digitalWrite(GPS_EN, LOW); // Put GPS back into standby mode
+  return true;
+}
+
+bool NanoG2UltraSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP &telemetry)
+{
+  if (requester_permissions & TELEM_PERM_LOCATION)
+  { // does requester have permission?
+    telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude);
+  }
+  return true;
+}
+
+void NanoG2UltraSensorManager::loop()
+{
+  static long next_gps_update = 0;
+  _location->loop();
+  if (millis() > next_gps_update && gps_active) // don't bother if gps position is not enabled
+  {
+    if (_location->isValid())
+    {
+      node_lat = ((double)_location->getLatitude()) / 1000000.;
+      node_lon = ((double)_location->getLongitude()) / 1000000.;
+      node_altitude = ((double)_location->getAltitude()) / 1000.0;
+      MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
+    }
+    next_gps_update = millis() + (1000 * 60); // after initial update, only check every minute TODO: should be configurable
+  }
+}
+
+int NanoG2UltraSensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch)
+
+const char *NanoG2UltraSensorManager::getSettingName(int i) const
+{
+  return i == 0 ? "gps" : NULL;
+}
+
+const char *NanoG2UltraSensorManager::getSettingValue(int i) const
+{
+  if (i == 0)
+  {
+    return gps_active ? "1" : "0";
+  }
+  return NULL;
+}
+
+bool NanoG2UltraSensorManager::setSettingValue(const char *name, const char *value)
+{
+  if (strcmp(name, "gps") == 0)
+  {
+    if (strcmp(value, "0") == 0)
+    {
+      stop_gps();
+    }
+    else
+    {
+      start_gps();
+    }
+    return true;
+  }
+  return false; // not supported
+}
+
 mesh::LocalIdentity radio_new_identity()
 {
   RadioNoiseListener rng(radio);

+ 23 - 3
variants/nano_g2_ultra/target.h

@@ -8,16 +8,36 @@
 #include <helpers/AutoDiscoverRTCClock.h>
 #include <helpers/SensorManager.h>
 #ifdef DISPLAY_CLASS
-  #include <helpers/ui/SH1106Display.h>
+#include <helpers/ui/SH1106Display.h>
 #endif
+#include <helpers/sensors/LocationProvider.h>
+
+class NanoG2UltraSensorManager : public SensorManager
+{
+  bool gps_active = false;
+  LocationProvider *_location;
+
+  void start_gps();
+  void stop_gps();
+
+public:
+  NanoG2UltraSensorManager(LocationProvider &location) : _location(&location) {}
+  bool begin() override;
+  bool querySensors(uint8_t requester_permissions, CayenneLPP &telemetry) override;
+  void loop() override;
+  int getNumSettings() const override;
+  const char *getSettingName(int i) const override;
+  const char *getSettingValue(int i) const override;
+  bool setSettingValue(const char *name, const char *value) override;
+};
 
 extern NanoG2Ultra board;
 extern WRAPPER_CLASS radio_driver;
 extern AutoDiscoverRTCClock rtc_clock;
-extern SensorManager sensors;
+extern NanoG2UltraSensorManager sensors;
 
 #ifdef DISPLAY_CLASS
-  extern DISPLAY_CLASS display;
+extern DISPLAY_CLASS display;
 #endif
 
 bool radio_init();

+ 4 - 0
variants/nano_g2_ultra/variant.h

@@ -130,11 +130,15 @@ External serial flash W25Q16JV_IQ
      * GPS pins
      */
 
+#define HAS_GPS 1
 #define GPS_L76K
 
 #define PIN_GPS_STANDBY (0 + 13) // An output to wake GPS, low means allow sleep, high means force wake STANDBY
 #define PIN_GPS_TX (0 + 9)       // This is for bits going TOWARDS the CPU
 #define PIN_GPS_RX (0 + 10)      // This is for bits going TOWARDS the GPS
+#define GPS_RX_PIN PIN_GPS_RX
+#define GPS_TX_PIN PIN_GPS_TX
+
 
     // #define GPS_THREAD_INTERVAL 50
 

+ 10 - 6
variants/promicro/platformio.ini

@@ -15,12 +15,14 @@ build_flags = ${nrf52840_base.build_flags}
   -D PIN_USER_BTN=6
 build_src_filter = ${nrf52840_base.build_src_filter}
   +<helpers/nrf52/PromicroBoard.cpp>
+  +<helpers/sensors>  
   +<../variants/promicro>
 lib_deps= ${nrf52840_base.lib_deps}
 	adafruit/Adafruit SSD1306 @ ^2.5.13
-  robtillaart/INA3221 @ ^0.4.1
-  robtillaart/INA219 @ ^0.4.1
-  adafruit/Adafruit AHTX0@^2.0.5
+  adafruit/Adafruit INA3221 Library @ ^1.0.1
+  adafruit/Adafruit INA219 @ ^1.2.3
+  adafruit/Adafruit AHTX0 @ ^2.0.5
+  adafruit/Adafruit BME280 Library @ ^2.3.0  
 
 [env:Faketec_Repeater]
 extends = Faketec
@@ -120,11 +122,13 @@ build_flags = ${nrf52840_base.build_flags}
 build_src_filter =
   ${nrf52840_base.build_src_filter}
   +<helpers/nrf52/PromicroBoard.cpp>
+  +<helpers/sensors>  
   +<../variants/promicro>
 lib_deps= ${nrf52840_base.lib_deps}
-  robtillaart/INA3221 @ ^0.4.1
-  robtillaart/INA219 @ ^0.4.1
-  adafruit/Adafruit AHTX0@^2.0.5
+  adafruit/Adafruit INA3221 Library @ ^1.0.1
+  adafruit/Adafruit INA219 @ ^1.2.3
+  adafruit/Adafruit AHTX0 @ ^2.0.5
+  adafruit/Adafruit BME280 Library @ ^2.3.0  
 
 [env:ProMicroLLCC68_Repeater]
 extends = ProMicroLLCC68

+ 1 - 118
variants/promicro/target.cpp

@@ -10,7 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board);
 
 VolatileRTCClock fallback_clock;
 AutoDiscoverRTCClock rtc_clock(fallback_clock);
-PromicroSensorManager sensors;
+EnvironmentSensorManager sensors;
 
 #ifdef DISPLAY_CLASS
   DISPLAY_CLASS display;
@@ -79,120 +79,3 @@ mesh::LocalIdentity radio_new_identity() {
   return mesh::LocalIdentity(&rng);  // create new random identity
 }
 
-static INA3221 INA_3221(TELEM_INA3221_ADDRESS, &Wire);
-static INA219 INA_219(TELEM_INA219_ADDRESS, &Wire);
-static Adafruit_AHTX0 AHTX;
-
-bool PromicroSensorManager::begin() {
-  initINA3221();
-  initINA219();
-  initAHTX();
-
-  return true;
-}
-
-bool PromicroSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
-  int nextAvalableChannel = TELEM_CHANNEL_SELF + 1;
-  if (requester_permissions & TELEM_PERM_ENVIRONMENT) {
-    if (INA3221initialized) {
-      for(int i = 0; i < 3; i++) {
-        // add only enabled INA3221 channels to telemetry
-        if (INA3221_CHANNEL_ENABLED[i]) {
-          telemetry.addVoltage(nextAvalableChannel, INA_3221.getBusVoltage(i));
-          telemetry.addCurrent(nextAvalableChannel, INA_3221.getCurrent(i));
-          telemetry.addPower(nextAvalableChannel, INA_3221.getPower(i));
-          nextAvalableChannel++;
-        }
-      }
-    }
-    if (INA219initialized) {
-      telemetry.addVoltage(nextAvalableChannel, INA_219.getBusVoltage());
-      telemetry.addCurrent(nextAvalableChannel, INA_219.getCurrent());
-      telemetry.addPower(nextAvalableChannel, INA_219.getPower());
-      nextAvalableChannel++;
-    }
-    if (AHTXinitialized) {
-      sensors_event_t humidity, temp;
-      AHTX.getEvent(&humidity, &temp);
-      telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature);
-      telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity);
-    }
-  }
-
-  return true;
-}
-
-int PromicroSensorManager::getNumSettings() const {
-  return NUM_SENSOR_SETTINGS;
-}
-
-const char* PromicroSensorManager::getSettingName(int i) const {
-  if (i >= 0 && i < NUM_SENSOR_SETTINGS) {
-    return INA3221_CHANNEL_NAMES[i];
-  }
-  return NULL;
-}
-
-const char* PromicroSensorManager::getSettingValue(int i) const {
-  if (i >= 0 && i < NUM_SENSOR_SETTINGS) {
-    return INA3221_CHANNEL_ENABLED[i] ? "1" : "0";
-  }
-  return NULL;
-}
-
-bool PromicroSensorManager::setSettingValue(const char* name, const char* value) {
-  for (int i = 0; i < NUM_SENSOR_SETTINGS; i++) {
-    if (strcmp(name, INA3221_CHANNEL_NAMES[i]) == 0) {
-      int channelEnabled = INA_3221.getEnableChannel(i);
-      if (strcmp(value, "1") == 0) {
-        INA3221_CHANNEL_ENABLED[i] = true;
-        if (!channelEnabled) {
-          INA_3221.enableChannel(i);
-        }
-      } else {
-        INA3221_CHANNEL_ENABLED[i] = false;
-        if (channelEnabled) {
-          INA_3221.disableChannel(i);
-        }
-      }
-      return true;
-    }
-  }
-  return false;
-}
-
-void PromicroSensorManager::initINA3221() {
-  if (INA_3221.begin()) {
-    MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", INA_3221.getAddress());
-    MESH_DEBUG_PRINTLN("%04X %04X %04X", INA_3221.getDieID(), INA_3221.getManufacturerID(), INA_3221.getConfiguration());
-
-    for(int i = 0; i < 3; i++) {
-      INA_3221.setShuntR(i, TELEM_INA3221_SHUNT_VALUE);
-    }
-    INA3221initialized = true;
-  } else {
-    INA3221initialized = false;
-    MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS);
-  }
-}
-
-void PromicroSensorManager::initINA219() { 
-  if (INA_219.begin()) {
-    MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", INA_219.getAddress());
-    INA_219.setMaxCurrentShunt(TELEM_INA219_MAX_CURRENT, TELEM_INA219_SHUNT_VALUE);
-    INA219initialized = true;
-  } else {
-    INA219initialized = false;
-    MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
-  }
-}
-
-void PromicroSensorManager::initAHTX() { 
-  if (AHTX.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) {
-    MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS);
-    AHTXinitialized = true;
-  } else {
-    AHTXinitialized = false;
-    MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS);
-  }
-}

+ 2 - 41
variants/promicro/target.h

@@ -7,19 +7,16 @@
 #include <helpers/CustomSX1262Wrapper.h>
 #include <helpers/CustomLLCC68Wrapper.h>
 #include <helpers/AutoDiscoverRTCClock.h>
-#include <helpers/SensorManager.h>
-#include <INA3221.h>
-#include <INA219.h>
-#include <Adafruit_AHTX0.h>
 #ifdef DISPLAY_CLASS
   #include <helpers/ui/SSD1306Display.h>
 #endif
 
-#define NUM_SENSOR_SETTINGS 3
+#include <helpers/sensors/EnvironmentSensorManager.h>
 
 extern PromicroBoard board;
 extern WRAPPER_CLASS radio_driver;
 extern AutoDiscoverRTCClock rtc_clock;
+extern EnvironmentSensorManager sensors;
 
 #ifdef DISPLAY_CLASS
   extern DISPLAY_CLASS display;
@@ -31,39 +28,3 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
 void radio_set_tx_power(uint8_t dbm);
 mesh::LocalIdentity radio_new_identity();
 
-#define TELEM_INA3221_ADDRESS 0x42      // INA3221 3 channel current sensor I2C address
-#define TELEM_INA219_ADDRESS  0x40      // INA219 single channel current sensor I2C address
-#define TELEM_AHTX_ADDRESS    0x38      // AHT10, AHT20 temperature and humidity sensor I2C address
-
-#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts
-#define TELEM_INA3221_SETTING_CH1 "INA3221-1"
-#define TELEM_INA3221_SETTING_CH2 "INA3221-2"
-#define TELEM_INA3221_SETTING_CH3 "INA3221-3"
-
-#define TELEM_INA219_SHUNT_VALUE 0.100  // shunt value in ohms (may differ between manufacturers)
-#define TELEM_INA219_MAX_CURRENT 5
-
-class PromicroSensorManager: public SensorManager {
-  bool INA3221initialized = false;
-  bool INA219initialized = false;
-  bool AHTXinitialized = false;
-
-  // INA3221 channels in telemetry
-  const char * INA3221_CHANNEL_NAMES[NUM_SENSOR_SETTINGS] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3};
-  bool INA3221_CHANNEL_ENABLED[NUM_SENSOR_SETTINGS] = {true, true, true};
-  
-  void initINA3221();
-  void initINA219();
-  void initAHTX();
-public:
-  PromicroSensorManager(){};
-  bool begin() override;
-  bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
-  int getNumSettings() const override;
-  const char* getSettingName(int i) const override;
-  const char* getSettingValue(int i) const override;
-  bool setSettingValue(const char* name, const char* value) override;
-};
-
-
-extern PromicroSensorManager sensors;

+ 4 - 0
variants/t1000-e/platformio.ini

@@ -47,9 +47,13 @@ build_flags = ${t1000-e.build_flags}
   -D RX_BOOSTED_GAIN=true
   -D RF_SWITCH_TABLE
   -D DISPLAY_CLASS=NullDisplayDriver
+  -D PIN_BUZZER=25
+  -D PIN_BUZZER_EN=37      ; P1/5 - required for T1000-E
 build_src_filter = ${t1000-e.build_src_filter}
   +<helpers/nrf52/SerialBLEInterface.cpp>
+  +<helpers/ui/buzzer.cpp>
   +<../examples/companion_radio/*.cpp>
 lib_deps = ${t1000-e.lib_deps}
   densaugeo/base64 @ ~1.4.0
   stevemarple/MicroNMEA @ ^2.0.6
+  end2endzone/NonBlockingRTTTL@^1.3.0

+ 1 - 1
variants/t114/target.cpp

@@ -97,7 +97,7 @@ bool T114SensorManager::begin() {
   digitalWrite(GPS_EN, HIGH);  // Power on GPS
 
   // Give GPS a moment to power up and send data
-  delay(500);
+  delay(1500);
 
   // We'll consider GPS detected if we see any data on Serial1
   gps_detected = (Serial1.available() > 0);

+ 1 - 1
variants/t114/variant.h

@@ -109,7 +109,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // Buzzer
 
-#define PIN_BUZZER              (46)
+// #define PIN_BUZZER              (46)
 
 
 ////////////////////////////////////////////////////////////////////////////////