diff --git a/boards/heltec_mesh_solar.json b/boards/heltec_mesh_solar.json new file mode 100644 index 000000000..44fafe7ef --- /dev/null +++ b/boards/heltec_mesh_solar.json @@ -0,0 +1,53 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x4405"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"] + ], + "usb_product": "HT-n5262", + "mcu": "nrf52840", + "variant": "heltec_mesh_solar", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": ["arduino"], + "name": "Heltec nrf (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://heltec.org/project/meshsolar/", + "vendor": "Heltec" +} diff --git a/src/Power.cpp b/src/Power.cpp index b489bc33c..1a79e1586 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -681,6 +681,8 @@ bool Power::setup() found = true; } else if (lipoChargerInit()) { found = true; + } else if (meshSolarInit()) { + found = true; } else if (analogInit()) { found = true; } @@ -1448,3 +1450,75 @@ bool Power::lipoChargerInit() return false; } #endif + + + +#ifdef HELTEC_MESH_SOLAR +#include "meshSolarApp.h" + +/** + * meshSolar class for an SMBUS battery sensor. + */ +class meshSolarBatteryLevel : public HasBatteryLevel +{ + + public: + /** + * Init the I2C meshSolar battery level sensor + */ + bool runOnce() + { + meshSolarStart(); + return true; + } + + /** + * Battery state of charge, from 0 to 100 or -1 for unknown + */ + virtual int getBatteryPercent() override { return meshSolarGetBatteryPercent(); } + + /** + * The raw voltage of the battery in millivolts, or NAN if unknown + */ + virtual uint16_t getBattVoltage() override { return meshSolarGetBattVoltage(); } + + /** + * return true if there is a battery installed in this unit + */ + virtual bool isBatteryConnect() override { return meshSolarIsBatteryConnect(); } + + /** + * return true if there is an external power source detected + */ + virtual bool isVbusIn() override { return meshSolarIsVbusIn();} + + /** + * return true if the battery is currently charging + */ + virtual bool isCharging() override { return meshSolarIsCharging(); } +}; + +meshSolarBatteryLevel meshSolarLevel; + +/** + * Init the meshSolar battery level sensor + */ +bool Power::meshSolarInit() +{ + bool result = meshSolarLevel.runOnce(); + LOG_DEBUG("Power::meshSolarInit mesh solar sensor is %s", result ? "ready" : "not ready yet"); + if (!result) + return false; + batteryLevel = &meshSolarLevel; + return true; +} + +#else +/** + * The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel + */ +bool Power::meshSolarInit() +{ + return false; +} +#endif \ No newline at end of file diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 68c41980d..093a24678 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -64,6 +64,14 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con int32_t SerialConsole::runOnce() { +#ifdef HELTEC_MESH_SOLAR + //After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module. + if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port + && moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) + { + return 250; + } +#endif return runOncePart(); } diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 4a42e5197..e897fc9a2 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -15,9 +15,65 @@ int32_t StreamAPI::runOncePart() checkConnectionTimeout(); return result; } +int32_t StreamAPI::runOncePart(char *buf,uint16_t bufLen) +{ + auto result = readStream(buf,bufLen); + writeStream(); + checkConnectionTimeout(); + return result; +} + +int32_t StreamAPI::handleRecStream(char *buf,uint16_t bufLen) +{ + uint16_t index = 0; + while (bufLen > index) { // Currently we never want to block + int cInt = buf[index++]; + if (cInt < 0) + break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit + // arduino + + uint8_t c = (uint8_t)cInt; + + // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload + size_t ptr = rxPtr; + + rxPtr++; // assume we will probably advance the rxPtr + rxBuf[ptr] = c; // store all bytes (including framing) + + // console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c); + + if (ptr == 0) { // looking for START1 + if (c != START1) + rxPtr = 0; // failed to find framing + } else if (ptr == 1) { // looking for START2 + if (c != START2) + rxPtr = 0; // failed to find framing + } else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing + uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + + // console->printf("len %d\n", len); + + if (ptr == HEADER_LEN - 1) { + // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid + // protobuf also) + if (len > MAX_TO_FROM_RADIO_SIZE) + rxPtr = 0; // length is bogus, restart search for framing + } + + if (rxPtr != 0) // Is packet still considered 'good'? + if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload? + rxPtr = 0; // start over again on the next packet + + // If we didn't just fail the packet and we now have the right # of bytes, parse it + handleToRadio(rxBuf + HEADER_LEN, len); + } + } + } + return 0; +} /** - * Read any rx chars from the link and call handleToRadio + * Read any rx chars from the link and call handleRecStream */ int32_t StreamAPI::readStream() { @@ -26,50 +82,30 @@ int32_t StreamAPI::readStream() bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); return recentRx ? 5 : 250; } else { + char buf[1]; while (stream->available()) { // Currently we never want to block - int cInt = stream->read(); - if (cInt < 0) - break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit - // arduino - - uint8_t c = (uint8_t)cInt; - - // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload - size_t ptr = rxPtr; - - rxPtr++; // assume we will probably advance the rxPtr - rxBuf[ptr] = c; // store all bytes (including framing) - - // console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c); - - if (ptr == 0) { // looking for START1 - if (c != START1) - rxPtr = 0; // failed to find framing - } else if (ptr == 1) { // looking for START2 - if (c != START2) - rxPtr = 0; // failed to find framing - } else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing - uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing - - // console->printf("len %d\n", len); - - if (ptr == HEADER_LEN - 1) { - // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid - // protobuf also) - if (len > MAX_TO_FROM_RADIO_SIZE) - rxPtr = 0; // length is bogus, restart search for framing - } - - if (rxPtr != 0) // Is packet still considered 'good'? - if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload? - rxPtr = 0; // start over again on the next packet - - // If we didn't just fail the packet and we now have the right # of bytes, parse it - handleToRadio(rxBuf + HEADER_LEN, len); - } - } + buf[0] = stream->read(); + handleRecStream(buf,1); } + // we had bytes available this time, so assume we might have them next time also + lastRxMsec = millis(); + return 0; + } +} + +/** + * Read any rx chars from the link and call handleRecStream + */ +int32_t StreamAPI::readStream(char *buf,uint16_t bufLen) +{ + uint16_t index = 0; + if (bufLen<1) { + // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time + bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); + return recentRx ? 5 : 250; + } else { + handleRecStream(buf,bufLen); // we had bytes available this time, so assume we might have them next time also lastRxMsec = millis(); return 0; diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 6e0364bc1..547dd0175 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -50,12 +50,15 @@ class StreamAPI : public PhoneAPI * phone. */ virtual int32_t runOncePart(); + virtual int32_t runOncePart(char *buf,uint16_t bufLen); private: /** * Read any rx chars from the link and call handleToRadio */ int32_t readStream(); + int32_t readStream(char *buf,uint16_t bufLen); + int32_t handleRecStream(char *buf,uint16_t bufLen); /** * call getFromRadio() and deliver encapsulated packets to the Stream diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index f3091e5bf..46edccc2a 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -45,6 +45,9 @@ */ +#ifdef HELTEC_MESH_SOLAR +#include "meshSolarApp.h" +#endif #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ !defined(CONFIG_IDF_TARGET_ESP32C3) @@ -60,7 +63,7 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; -#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) +#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || defined(HELTEC_MESH_SOLAR) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial; #elif defined(CONFIG_IDF_TARGET_ESP32C6) @@ -77,7 +80,8 @@ size_t serialPayloadSize; bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config) { if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA, - meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO)) { + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO, + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { const char *warning = "Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes."; LOG_ERROR(warning); @@ -238,7 +242,17 @@ int32_t SerialModule::runOnce() else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { processWXSerial(); - } else { + } +#if defined(HELTEC_MESH_SOLAR) + else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { + serialPayloadSize = Serial.readBytes(serialBytes, sizeof(serialBytes)-1); + //If the parsing fails, the following parsing will be performed. + if((serialPayloadSize > 0) && (meshSolarCmdHandle(serialBytes)!=0)) { + return runOncePart(serialBytes,serialPayloadSize); + } + } +#endif + else { #if defined(CONFIG_IDF_TARGET_ESP32C6) while (Serial1.available()) { serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 1bbdd77e0..9b001468a 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -95,6 +95,8 @@ #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK #elif defined(SEEED_WIO_TRACKER_L1) #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 +#elif defined(HELTEC_MESH_SOLAR) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR #else #define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN #endif diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 1bf9a39fd..f0a585baf 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -319,7 +319,7 @@ void cpuDeepSleep(uint32_t msecToWake) #endif #endif -#ifdef HELTEC_MESH_NODE_T114 +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_MESH_SOLAR) nrf_gpio_cfg_default(PIN_GPS_PPS); detachInterrupt(PIN_GPS_PPS); detachInterrupt(PIN_BUTTON1); diff --git a/src/power.h b/src/power.h index 1c078c06d..e96f5b022 100644 --- a/src/power.h +++ b/src/power.h @@ -128,6 +128,8 @@ class Power : private concurrency::OSThread bool lipoInit(); /// Setup a Lipo charger bool lipoChargerInit(); + /// Setup a meshSolar battery sensor + bool meshSolarInit(); private: void shutdown(); diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini new file mode 100644 index 000000000..65d26dc40 --- /dev/null +++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini @@ -0,0 +1,19 @@ +; First prototype nrf52840/sx1262 device +[env:heltec-mesh-solar] +extends = nrf52840_base +board = heltec_mesh_solar +board_level = pr +debug_tool = jlink + +# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/heltec_mesh_solar + -DGPS_POWER_TOGGLE + -DHELTEC_MESH_SOLAR + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_solar> +lib_deps = + ${nrf52840_base.lib_deps} + https://github.com/NMIoT/meshsolar/archive/dfc5330dad443982e6cdd37a61d33fc7252f468b.zip + lewisxhe/PCF8563_Library@^1.0.1 + ArduinoJson@6.21.4 diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp new file mode 100644 index 000000000..8236d7cf4 --- /dev/null +++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp @@ -0,0 +1,36 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT); +} diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h new file mode 100644 index 000000000..defc23d46 --- /dev/null +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -0,0 +1,157 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_HELTEC_NRF_ +#define _VARIANT_HELTEC_NRF_ +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + + +#define PIN_LED1 (0 + 12) // green (confirmed on 1.0 board) +#define LED_BLUE PIN_LED1 // fake for bluefruit library +#define LED_GREEN PIN_LED1 +#define LED_BUILTIN LED_GREEN +#define LED_STATE_ON 0 // State when LED is lit + +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA (32+15) // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use + +/* + * Buttons + */ +#define PIN_BUTTON1 (32 + 10) +// #define PIN_BUTTON2 (0 + 18) // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular +// GPIO + +/* +No longer populated on PCB +*/ +#define PIN_SERIAL2_RX (0 + 9) +#define PIN_SERIAL2_TX (0 + 10) +// #define PIN_SERIAL2_EN (0 + 17) + +/* + * I2C + */ + +#define WIRE_INTERFACES_COUNT 2 + +// I2C bus 0 +// Routed to footprint for PCF8563TS RTC +// Not populated on T114 V1, maybe in future? +#define PIN_WIRE_SDA (0 + 6) // P0.26 +#define PIN_WIRE_SCL (0 + 26) // P0.27 + +// I2C bus 1 +// Available on header pins, for general use +#define PIN_WIRE1_SDA (0 + 30) // P0.16 +#define PIN_WIRE1_SCL (0 + 5) // P0.13 + +/* + * Lora radio + */ + +#define USE_SX1262 +// #define USE_SX1268 +#define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead +#define LORA_CS (0 + 24) +#define SX126X_DIO1 (0 + 20) +// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching +// #define SX1262_DIO3 (0 + 21) +// This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the +// main +// CPU? +#define SX126X_BUSY (0 + 17) +#define SX126X_RESET (0 + 25) +// Not really an E22 but TTGO seems to be trying to clone that +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +/* + * GPS pins + */ + +#define GPS_L76K + +// #define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K +// #define GPS_RESET_MODE LOW +// #define PIN_GPS_EN (21) +#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing +#define VEXT_ON_VALUE HIGH +// #define GPS_EN_ACTIVE HIGH +#define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake +#define PIN_GPS_PPS (32 + 4) +// Seems to be missing on this new board +// #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS + +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +// For LORA, spi 0 +#define PIN_SPI_MISO (0 + 23) +#define PIN_SPI_MOSI (0 + 22) +#define PIN_SPI_SCK (0 + 19) + +// #define PIN_PWR_EN (0 + 6) + +// To debug via the segger JLINK console rather than the CDC-ACM serial device +// #define USE_SEGGER + +#define BQ4050_SDA_PIN (32+1) // I2C data line pin +#define BQ4050_SCL_PIN (32+0) // I2C clock line pin +#define BQ4050_EMERGENCY_SHUTDOWN_PIN (32+3) // Emergency shutdown pin + +#define HAS_RTC 0 +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif