mirror of
https://github.com/meshtastic/firmware.git
synced 2026-03-28 20:13:43 -04:00
* Don't ever define PIN_LED or BLE_LED_INVERTED (#9494)
* Don't ever define PIN_LED
* Deprecate BLE_LED_INVERTED
* Add StatusMessage module and config overrides (#9351)
* Add StatusMessage module and config overrides
* Trunk
* Don't reboot node simply for a StatusMessage config update
* Missed in reviews - fixing send bubble (#9505)
* Prefer EXT_PWR_DETECT pin over chargingVolt to detect power unplugged (#9511)
* Make sure we always return a value in NodeDB::restorePreferences() (#9516)
In case FScom is not defined there is no return statement. This
moves the return outside of the ifdef to make sure a defined
value is returned.
* Inkhud battery icon improvements. (#9513)
* Inkhud battery icon improvements.
Fixes the battery icon draining from the flat side towards the bump, which is backwards from general design language seen on most devices
By request of kr0n05_ on discord, adds the ability to mirror the battery icon which fixes that issue in another way, and is also a common design seen on other devices.
* Remove option for icon mirroring
* Add border + dither to battery to prevent font overlap
* Fix trunk format
* Code cleanup, courtesy of Xaositek.
* Add reply bot module with DM-only responses and rate limiting (#9456)
* Implement Meshtastic reply bot module with ping and status features
Adds a reply bot module that listens for /ping, /hello, and /test commands received via direct messages or broadcasts on the primary channel. The module always replies via direct message to the sender only, reporting hop count, RSSI, and SNR. Per-sender cooldowns are enforced to reduce network spam, and the module can be excluded at build time via a compile flag. Updates include the new module source files and required build configuration changes.
* Update ReplyBotModule.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update src/modules/ReplyBotModule.h
Match the existing MESHTASTIC_EXCLUDE_* guard pattern so the module is excluded by default.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update src/modules/ReplyBotModule.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Tidying up
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* HotFix for ReplyBot - Modules.cpp included and moved configuration.h (#9532)
* Undefine LED_BUILTIN (#9531)
Keep variant in sync with
https://github.com/meshtastic/firmware/commit/df40085
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* Add agc reset attempt (#8163)
* Add agc reset attempt
* Add radioLibInterface include
* Trunk
* AGC reset don't crash, don't naively call
* Update src/main.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Use Throttle function
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Remove unused hmx variable (#9529)
The variable is not used at all in the function, remove it to
silence the compiler warning.
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* Rename LED_PIN to LED_POWER, move handling out of main to dedicated module (#9512)
* Rename LED_PIN to LED_POWER, move handling out of main to dedicated module
* Misc
* Remove errant endif
* Fix hop_limit upgrade detection (#9550)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* meshtasticd: Fix install on Fedora 43 (#9556)
* RPM: Include meshtasticd-start.sh (#9561)
* Add Slash Key to VirtualKeyboard (#9563)
Addition of ? and / to the virtual Keyboard via short and long press
* Add support for CW2015 LiPo battery fuel gauge (#9564)
* Add support for CW2015 LiPo battery fuel gauge
* Address Copilot's concerns, minor fixups
* Make LED_POWER blip even in critical battery (#9545)
* Enable FORTIFY and SP for native builds (#9537)
* Enable FORITFY and NX for native builds
meshtasticd does have an executable stack and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and a non-executable stack.
This gives the following improvements on Debian Trixie:
$ checksec --file=./.pio/build/native/meshtasticd
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 13516 Symbols No 0 17 ./.pio/build/native/meshtasticd
$ checksec --file=./.pio/build/native/meshtasticd
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 13519 Symbols Yes 12 20 ./.pio/build/native/meshtasticd
Tested with --sim mode I do not get any crashes or similar.
* Enable FORTIFY and NX for native builds
meshtasticd does have an executable stack and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and a non-executable stack.
This gives the following improvements on Debian Trixie:
$ checksec --file=./.pio/build/native/meshtasticd
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 13516 Symbols No 0 17 ./.pio/build/native/meshtasticd
$ checksec --file=./.pio/build/native/meshtasticd
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 13519 Symbols Yes 12 20 ./.pio/build/native/meshtasticd
Tested with --sim mode I do not get any crashes or similar.
* Enable FORTIFY and SP for native builds
meshtasticd does have a stack canaries and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and stack canaries.
This gives the following improvements on Debian Trixie:
$ checksec --file=./.pio/build/native/meshtasticd
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 13516 Symbols No 0 17 ./.pio/build/native/meshtasticd
$ checksec --file=./.pio/build/native/meshtasticd
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 13519 Symbols Yes 12 20 ./.pio/build/native/meshtasticd
Tested with --sim mode I do not get any crashes or similar.
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* Update built-in documentation for current method of implementation (#9592)
* Refactor logging in ProtobufModule to ensure message details are logged after successful decoding (#9536)
* Automated version bumps (#9604)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
* Add missing openocd_target to custom nrf52 boards (#9603)
This stops platformio complaining about `Missing target configuration for me25ls01-4y10td` etc when trying to flash with nrfutil.
* Add sdl libs for native builds (#9595)
* Add sdl libs for native builds
* Alpine try again
* fix some random compiler warnings (#9596)
* Modify the dependency library of v4-tft (#9507)
* BaseUI: Favorite Screen Signal Quality improvement (#9566)
* Favorite Signal Quality improvement
* Show Voltage if node shares it.
* Trunk Fix
* Change Favorite tittle to encase name with Asterisks
* Add Pluggin In condition for Battery Line
* Adjust getUptimeStr Prefixes
* Create isAPIConnected for SharedCommon usage
* Correct leftSideSpacing to account for isAPIConnected
---------
Co-authored-by: Jason P <applewiz@mac.com>
* ExternalNotification and StatusLED now call AmbientLighting to update… (#9554)
* ExternalNotification and StatusLED now call AmbientLighting to update RGB LEDs. Add optional heartbeat
* Don't overwrite RGB state if heartbeat is disabled.
* Use the right define
* Remove another .h and make rgb static
* move rgb objects into AmbientLighting class
* Straighten out AmbientLighting Thread object
* Use %f for floats
* Fixes on SCD4X admin comands (#9607)
* Fixes on SCD4X admin comands
* Minor fix in logs for SEN5X
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* feat/add sfa30 (#9372)
* Move PMSA003I to separate class and update AQ telemetry
* AirQualityTelemetry module not depend on PM sensor presence
* Remove commented line
* Fixes on PMS class
* Add missing warmup period to wakeUp function
* Fixes on compilation for different variants
* Add functions to check for I2C bus speed and set it
* Initial implementation for SFA30Sensor
* Move PMSA003I to separate class and update AQ telemetry
* AirQualityTelemetry module not depend on PM sensor presence
* Remove commented line
* Fixes on PMS class
* Add missing warmup period to wakeUp function
* Fixes on compilation for different variants
* Add functions to check for I2C bus speed and set it
* Add ScreenFonts.h
Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>
* PMSA003I 1st round test
* Fix I2C scan speed
* Fix minor issues and bring back I2C SPEED def
* Remove PMSA003I library as its no longer needed
* Add functional SCD4X
* Fix screen frame for CO2
* Add admin commands to SCD4X class
* Add further admin commands and fixes.
* Remove unused I2C speed functions and cleanup
* Cleanup of SEN5X specific code added from switching branches
* Remove SCAN_I2C_CLOCK_SPEED block as its not needed
* Remove associated functions for setting I2C speed
* Unify build epoch to add flag in platformio-custom.py (#7917)
* Unify build_epoch replacement logic in platformio-custom
* Missed one
* Fix build error in rak_wismesh_tap_v2 (#7905)
In the logs was:
"No screen resolution defined in build_flags. Please define DISPLAY_SIZE."
set according to similar devices.
* Put guards in place around debug heap operations (#7955)
* Put guards in place around debug heap operations
* Add macros to clean up code
* Add pointer as well
* Cleanup
* Fix memory leak in NextHopRouter: always free packet copy when removing from pending
* Formatting
* Only queue 2 client notification
* Merge pull request #7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak
Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap
* Merge pull request #7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak
Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap
* Update protobufs (#7973)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
* T-Lora Pager: Support LR1121 and SX1280 models (#7956)
* T-Lora Pager: Support LR1121 and SX1280 models
* Remove ifdefs
* Trunk
* Trunk
* Static memory pool allocation (#7966)
* Static memory pool
* Initializer
* T-Lora Pager: Support LR1121 and SX1280 models (#7956)
* T-Lora Pager: Support LR1121 and SX1280 models
* Remove ifdefs
---------
Co-authored-by: WillyJL <me@willyjl.dev>
* Portduino dynamic alloc
* Missed
* Drop the limit
* Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (#7977)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* Fix json report crashes on esp32 (#7978)
* Tweak maximums
* Fix DRAM overflow on old esp32 targets
* Guard bad time warning logs using GPS_DEBUG (#7897)
In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy.
In combination, these result in a spamming of the logs when a bad time is found
When the GPS is active, we're calling the GPS thread every 0.2secs.
So this log could be printed 4,500 times in a no-lock scenario :)
Reserve this experience for developers using GPS_DEBUG.
Fixes https://github.com/meshtastic/firmware/issues/7896
* Scale probe buffer size based on current baud rate (#7975)
* Scale probe buffer size based on current baud rate
* Throttle bad time validation logging and fix time comparison logic
* Remove comment
* Missed the other instances
* Copy pasta
* Fix GPS gm_mktime memory leak (#7981)
* Fix overflow of time value (#7984)
* Fix overflow of time value
* Revert "Fix overflow of time value"
This reverts commit 0847969201.
* That got boogered up
* Remove PMSA003 include from modules
* Add flag to exclude air quality module
* Rework PMSA003I to align with new I2C scanner
* Reworks AQ telemetry to match new dynamic allocation method
* Adds VBLE_I2C_CLOCK_SPEED build flag for sensors with different I2C speed requirements
* Reworks PMSA003I
* Move add sensor template to separate file
* Split telemetry on screen options
* Add variable I2C clock compile flag
* Added to Seeed Xiao S3 as demo
* Fix drawFrame in AQ module
* Module settings override to i2cScan module function
* Move to CAN_RECLOCK_I2C per architecture
* Add reclock function in TelemetrySensor.cpp
* Add flag in ESP32 common
* Minor fix
* Move I2C reclock function to src/detect
* Fix uninitMemberVar errors and compile issue
* Make sleep, wakeUp functions generic
* Fix STM32 builds
* Add exclude AQ sensor to builds that have environmental sensor excludes
* Add includes to AddI2CSensorTemplate.h
* SEN5X first pass
* WIP Sen5X functions
* Further (non-working) progress in SEN5X
* WIP Sen5X functions
* Changes on SEN5X library - removing pm_env as well
* Small cleanup of SEN5X sensors
* Minor change for SEN5X detection
* Remove dup code
* Enable PM sensor before sending telemetry.
This enables the PM sensor for a predefined period to allow for warmup.
Once telemetry is sent, the sensor shuts down again.
* Small cleanups in SEN5X sensor
* Add dynamic measurement interval for SEN5X
* Only disable SEN5X if enough time after reading.
* Idle for SEN5X on communication error
* Cleanup of logs and remove unnecessary delays
* Small TODO
* Settle on uint16_t for SEN5X PM data
* Make AQTelemetry sensors non-exclusive
* Implementation of cleaning in FS prefs and cleanup
* Remove unnecessary LOGS
* Add cleaning date storage in FS
* Report non-cumulative PN
* Bring back detection code for SEN5X after branch rebase
* Add placeholder for admin message
* Add VOC measurements and persistence (WIP)
* Adds VOC measurements and state
* Still not working on VOC Index persistence
* Should it stay in continuous mode?
* Add one-shot mode config flag to SEN5X
* Add nan checks on sensor data from SEN5X
* Working implementation on VOCState
* Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h)
* Adds conditions for stability and sensor state
* Fixes on VOC state and mode swtiching
* Adds a new RHT/Gas only mode, with 3600s stabilization time
* Fixes the VOCState buffer mismatch
* Fixes SEN50/54/55 model mistake
* Adapt SEN5X to new sensor list structure. Improve reclock.
* Improve reClockI2C conditions for different variants
* Add sleep, wakeUp, pendingForReady, hasSleep functions to PM sensors to save battery
* Add SEN5X
* Fix merge errors
* Small reordering in PMS class for consistency
* If one sensor fails, AQ telemetry still reports data
* Small formatting fix
* Add SEN5X to AQI in ScanI2C
* SCD4X now part of AQ module with template list
* Fixes difference between idle and sleep
* In LowPower, sleep is disabled
* Requires testing for I2C clock comms for commands
* Remove unnecessary import
* Add co2 to serialized AQ metrics
* Add SFA30 with new sensor template in AQ module
* Update library dependencies in platformio.ini
* Fix unitialized variables in SEN5X constructor
* Fix missing import
* Fix uninitMemberVars
* Fix import error for SCD4X
* Fix I2CClock logic
* Fix not reclocking back to 700000Hz
* Fix multiple sensors being read simultaneously
* The logic in AQ module is different to the one in EnvironmentTelemetryModule. In Env module, if any sensor fails to getMetrics, no valid flag for the module. This prevents other sensors to report data.
* Fix pending clock change in PMSA003
* Cleanup of SEN5X class
* Exclude AQ sensor from wio-e5 due to flash limitations
* Fix I2C clock change logic
* Make sure clock is always set to needed value
* Fix returns
* Fix trunk
* Fix on condition in reclock
* Fix trunk
* Final SFA30 class implementation
* Add HCHO to screen and improve logs
* Add metrics to mesh packet serializer
* Minor fixes in logs
* OCD tidy up of logs
* Fix sleep function
* Remove old I2C_CLOCK_SPEED code
---------
Co-authored-by: nikl <nikl174@mailbox.org>
Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>
Co-authored-by: Nashui-Yan <yannashui10@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Mike Robbins <mrobbins@alum.mit.edu>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: WillyJL <me@willyjl.dev>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* Log rxBad PacketHeaders with more info (id, relay_node) like printPacket, so we can try to match RX errors to other packets in the logs. (#9614)
* Exclude status message module
* fix: zero entire public key array instead of only first byte (#9619)
* Update protobufs (#9621)
* Xiao NRF - define suitable i2c pins for the sub-variants (#8866)
Co-authored-by: Christian Walther <cwalther@gmx.ch>
* Update src/detect/ScanI2C.cpp
Co-authored-by: Wessel <wessel@weebl.me>
* Update src/modules/Telemetry/Sensor/SFA30Sensor.cpp
Co-authored-by: Wessel <wessel@weebl.me>
* Update src/modules/Telemetry/Sensor/SFA30Sensor.cpp
Co-authored-by: Wessel <wessel@weebl.me>
* Update src/modules/Telemetry/Sensor/SFA30Sensor.cpp
Co-authored-by: Wessel <wessel@weebl.me>
* Update src/mesh/NodeDB.cpp
Co-authored-by: Wessel <wessel@weebl.me>
* convert GPS global and some new in gps.cpp to unique_ptr (#9628)
Trying this to see if anything bad happen if I were to replace most raw pointers with unique_ptr.
I didn't used std::make_unique since it is only supported in C++14 and onwards but until we update esp32 to arduino 3.x the ESP32 xtensa chips use a C++11 std.
* replace delete in RedirectablePrint.cpp with std::unique_ptr (#9642)
Is part of the unique_ptr modernization effort.
* replace delete in EInkDynamicDisplay.{cpp,h} with std::unique_ptr (#9643)
Is part of the unique_ptr modernization effort.
* Undefine LED_BUILTIN for Heltec v2 variant (#9647)
* Undefine LED_BUILTIN for Heltec v2 variant
* Undefine LED_BUILTIN for Heltec v2.1 variant
---------
Co-authored-by: Jorropo <jorropo.pgm@gmail.com>
* replace delete in RadioInterface.cpp with std::unique_ptr (#9645)
Is part of the unique_ptr modernization effort.
* fix typo in PIN_GPS_SWITCH (#9648)
Wasn't caught by CI.
* replace delete in CryptoEngine.{cpp,h} with std::unique_ptr (#9649)
Is part of the unique_ptr modernization effort.
* workaround NCP5623 and LP5562 I2C builds (#9652)
Theses two appear to be buggy on r1-neo and nomadstar meteor pro, they rely on Wire.h being included previously to their import.
Idk why other platforms using the same smart LEDs are working while theses ones don't.
This should make CI green on the dev branch.
* replace delete in AudioThread.h with std::unique_ptr (#9651)
Is part of the unique_ptr modernization effort.
* Add USB_MODE=1 for Station G2 (#9660)
* InkHUD: Favorite Map Applet (#9654)
* fix a lot of low level cppcheck warnings (#9623)
* simplify the observer pattern, since all the called functions are const getters.
* use arduino macro over std: for numerical values and refactor local variables in drawScrollbar()
* oh, so Cppcheck actually complained about const pointers not being const.
* slowly getting out of ifdef hell
* fix inkHUD warnings as well
* last 2 check warnings
* git checks should fail on low defects from now on
* Feat/add scd30 (#9609)
* Merge develop into SCD30
* Add SCD30 class
* Fix logging and admin commands
* Minor cleanup and logging improvements
* Minor formatting issue
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Improvements on setTemperature
* Fix casting float-uint16_t
* Pass 100 for resetting temperature offset
* Fix issues pointed out by copilot
* Add quick reboot to set interval quicker on scd30
* Change saveState to only happen after boot and minor log changes
* Fix missing semicolon in one shot mode log
---------
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* fix: respect DontMqttMeBro flag regardless of channel PSK (#9626)
The previous PSK check was broken from its introduction in #4643 —
memcmp was used in boolean context without comparing to 0, inverting
the condition. Since no one noticed for over a year, the PSK-based
filtering provided no practical value. Simplifying to always respect
the sender's preference is both more correct and easier to reason about.
* our firmware action is too clever
Update pio_target and add pio_opts for checks.
* fix detection of SCD30 by checking if the size of the return from a 2 byte register read is correct (#9664)
* fix detection of SCD30 by checking if thee size of the return from a 2 byte register read is correct
fix signedness warning in PMSA003 sensor code.
* Add alternate path for LPS22HB
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* check EndTransmission for errors and compare returned length to expected value
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* #9623 resolved a local shadow of next_key by converting it to int. (#9665)
* zip a few gitrefs down (#9672)
* InkHUD: Allow non-system applets to subscribe to input events (#9514)
* Allow inkhud user applets to consume inputs with opt-in system
Adds a way for applets to subscribe to input events while keeping it off
by default to preserve compatibility and expected behaviours. Adds
example for use as well.
* Add check for nullptr on getActiveApplet uses
* Remove redundant includes
* Move subscribedInputs to protected
* More consistent naming scheme
* Fake IAQ values on Non-BSEC2 platforms like Platformio and the original ESP32 (#9663)
* BSEC2 Replacement
- add approximation for IAQ to non-BSEC2 platforms.
- Re-add this sensor to ESP32 targets, and refactor env_extra includes.
- Fix C++ 11 compatibility
* Check for gas resistance 0
* ULED_BUILTIN for 9m2ibr_aprs_lora_tracker (#9685)
---------
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Eric Sesterhenn <eric.sesterhenn@x41-dsec.de>
Co-authored-by: Vortetty <33466216+Vortetty@users.noreply.github.com>
Co-authored-by: Mattatat25 <108779801+mattatat25@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Max <rekin.m@gmail.com>
Co-authored-by: Colby Dillion <colby.dillion@pacshealth.com>
Co-authored-by: Austin <vidplace7@gmail.com>
Co-authored-by: Tom <116762865+NomDeTom@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Chloe Bethel <chloe@9net.org>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Quency-D <55523105+Quency-D@users.noreply.github.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Co-authored-by: oscgonfer <oscgonfer@users.noreply.github.com>
Co-authored-by: nikl <nikl174@mailbox.org>
Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>
Co-authored-by: Nashui-Yan <yannashui10@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Mike Robbins <mrobbins@alum.mit.edu>
Co-authored-by: WillyJL <me@willyjl.dev>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Wessel <wessel@weebl.me>
Co-authored-by: Christian Walther <cwalther@gmx.ch>
Co-authored-by: Jorropo <jorropo.pgm@gmail.com>
Co-authored-by: Eric Barch <ericb@ericbarch.com>
Co-authored-by: Clive Blackledge <clive@ansible.org>
1826 lines
57 KiB
C++
1826 lines
57 KiB
C++
/**
|
||
* @file Power.cpp
|
||
* @brief This file contains the implementation of the Power class, which is
|
||
* responsible for managing power-related functionality of the device. It
|
||
* includes battery level sensing, power management unit (PMU) control, and
|
||
* power state machine management. The Power class is used by the main device
|
||
* class to manage power-related functionality.
|
||
*
|
||
* The file also includes implementations of various battery level sensors, such
|
||
* as the AnalogBatteryLevel class, which assumes the battery voltage is
|
||
* attached via a voltage-divider to an analog input.
|
||
*
|
||
* This file is part of the Meshtastic project.
|
||
* For more information, see: https://meshtastic.org/
|
||
*/
|
||
#include "power.h"
|
||
#include "MessageStore.h"
|
||
#include "NodeDB.h"
|
||
#include "PowerFSM.h"
|
||
#include "Throttle.h"
|
||
#include "buzz/buzz.h"
|
||
#include "configuration.h"
|
||
#include "main.h"
|
||
#include "meshUtils.h"
|
||
#include "power/PowerHAL.h"
|
||
#include "sleep.h"
|
||
|
||
#if defined(ARCH_PORTDUINO)
|
||
#include "api/WiFiServerAPI.h"
|
||
#include "input/LinuxInputImpl.h"
|
||
#endif
|
||
|
||
// Working USB detection for powered/charging states on the RAK platform
|
||
#ifdef NRF_APM
|
||
#include "nrfx_power.h"
|
||
#endif
|
||
|
||
#if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT
|
||
#include "mqtt/MQTT.h"
|
||
#include "target_specific.h"
|
||
#if HAS_WIFI
|
||
#include <WiFi.h>
|
||
#endif
|
||
|
||
#if HAS_ETHERNET && defined(USE_WS5500)
|
||
#include <ETHClass2.h>
|
||
#define ETH ETH2
|
||
#endif // HAS_ETHERNET
|
||
|
||
#endif
|
||
|
||
#ifndef DELAY_FOREVER
|
||
#define DELAY_FOREVER portMAX_DELAY
|
||
#endif
|
||
|
||
#if defined(BATTERY_PIN) && defined(ARCH_ESP32)
|
||
|
||
#ifndef BAT_MEASURE_ADC_UNIT // ADC1 is default
|
||
static const adc1_channel_t adc_channel = ADC_CHANNEL;
|
||
static const adc_unit_t unit = ADC_UNIT_1;
|
||
#else // ADC2
|
||
static const adc2_channel_t adc_channel = ADC_CHANNEL;
|
||
static const adc_unit_t unit = ADC_UNIT_2;
|
||
RTC_NOINIT_ATTR uint64_t RTC_reg_b;
|
||
|
||
#endif // BAT_MEASURE_ADC_UNIT
|
||
|
||
esp_adc_cal_characteristics_t *adc_characs = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t));
|
||
#ifndef ADC_ATTENUATION
|
||
static const adc_atten_t atten = ADC_ATTEN_DB_12;
|
||
#else
|
||
static const adc_atten_t atten = ADC_ATTENUATION;
|
||
#endif
|
||
#endif // BATTERY_PIN && ARCH_ESP32
|
||
|
||
#ifdef EXT_CHRG_DETECT
|
||
#ifndef EXT_CHRG_DETECT_MODE
|
||
static const uint8_t ext_chrg_detect_mode = INPUT;
|
||
#else
|
||
static const uint8_t ext_chrg_detect_mode = EXT_CHRG_DETECT_MODE;
|
||
#endif
|
||
#ifndef EXT_CHRG_DETECT_VALUE
|
||
static const uint8_t ext_chrg_detect_value = HIGH;
|
||
#else
|
||
static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
|
||
#endif
|
||
#endif
|
||
|
||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||
#if __has_include(<Adafruit_INA219.h>)
|
||
INA219Sensor ina219Sensor;
|
||
#else
|
||
NullSensor ina219Sensor;
|
||
#endif
|
||
|
||
#if __has_include(<INA226.h>)
|
||
INA226Sensor ina226Sensor;
|
||
#else
|
||
NullSensor ina226Sensor;
|
||
#endif
|
||
|
||
#if __has_include(<Adafruit_INA260.h>)
|
||
INA260Sensor ina260Sensor;
|
||
#else
|
||
NullSensor ina260Sensor;
|
||
#endif
|
||
|
||
#if __has_include(<INA3221.h>)
|
||
INA3221Sensor ina3221Sensor;
|
||
#else
|
||
NullSensor ina3221Sensor;
|
||
#endif
|
||
|
||
#endif
|
||
|
||
#if !MESHTASTIC_EXCLUDE_I2C
|
||
#include "modules/Telemetry/Sensor/MAX17048Sensor.h"
|
||
#include <utility>
|
||
extern std::pair<uint8_t, TwoWire *> nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1];
|
||
#if HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY)
|
||
#if __has_include(<Adafruit_MAX1704X.h>)
|
||
MAX17048Sensor max17048Sensor;
|
||
#else
|
||
NullSensor max17048Sensor;
|
||
#endif
|
||
#endif
|
||
#endif
|
||
|
||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && HAS_RAKPROT
|
||
RAK9154Sensor rak9154Sensor;
|
||
#endif
|
||
|
||
#ifdef HAS_PPM
|
||
// note: XPOWERS_CHIP_XXX must be defined in variant.h
|
||
#include <XPowersLib.h>
|
||
XPowersPPM *PPM = NULL;
|
||
#endif
|
||
|
||
#ifdef HAS_BQ27220
|
||
#include "bq27220.h"
|
||
#endif
|
||
|
||
#ifdef HAS_PMU
|
||
XPowersLibInterface *PMU = NULL;
|
||
#else
|
||
|
||
// Copy of the base class defined in axp20x.h.
|
||
// I'd rather not include axp20x.h as it brings Wire dependency.
|
||
class HasBatteryLevel
|
||
{
|
||
public:
|
||
/**
|
||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||
*/
|
||
virtual int getBatteryPercent() { return -1; }
|
||
|
||
/**
|
||
* The raw voltage of the battery or NAN if unknown
|
||
*/
|
||
virtual uint16_t getBattVoltage() { return 0; }
|
||
|
||
/**
|
||
* return true if there is a battery installed in this unit
|
||
*/
|
||
virtual bool isBatteryConnect() { return false; }
|
||
|
||
virtual bool isVbusIn() { return false; }
|
||
virtual bool isCharging() { return false; }
|
||
};
|
||
#endif
|
||
|
||
bool pmu_irq = false;
|
||
|
||
Power *power;
|
||
|
||
using namespace meshtastic;
|
||
|
||
// NRF52 has AREF_VOLTAGE defined in architecture.h but
|
||
// make sure it's included. If something is wrong with NRF52
|
||
// definition - compilation will fail on missing definition
|
||
#if !defined(AREF_VOLTAGE) && !defined(ARCH_NRF52)
|
||
#define AREF_VOLTAGE 3.3
|
||
#endif
|
||
|
||
/**
|
||
* If this board has a battery level sensor, set this to a valid implementation
|
||
*/
|
||
static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level sensor
|
||
|
||
#ifdef BATTERY_PIN
|
||
|
||
void battery_adcEnable()
|
||
{
|
||
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
|
||
#ifdef ADC_USE_PULLUP
|
||
pinMode(ADC_CTRL, INPUT_PULLUP);
|
||
#else
|
||
#ifdef HELTEC_V3
|
||
pinMode(ADC_CTRL, INPUT);
|
||
uint8_t adc_ctl_enable_value = !(digitalRead(ADC_CTRL));
|
||
pinMode(ADC_CTRL, OUTPUT);
|
||
digitalWrite(ADC_CTRL, adc_ctl_enable_value);
|
||
#else
|
||
pinMode(ADC_CTRL, OUTPUT);
|
||
digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED);
|
||
#endif
|
||
#endif
|
||
delay(10);
|
||
#endif
|
||
}
|
||
|
||
static void battery_adcDisable()
|
||
{
|
||
#ifdef ADC_CTRL // disable adc voltage divider when we need to read
|
||
#ifdef ADC_USE_PULLUP
|
||
pinMode(ADC_CTRL, INPUT_PULLDOWN);
|
||
#else
|
||
#ifdef HELTEC_V3
|
||
pinMode(ADC_CTRL, ANALOG);
|
||
#else
|
||
digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED);
|
||
#endif
|
||
#endif
|
||
#endif
|
||
}
|
||
|
||
#endif
|
||
|
||
/**
|
||
* A simple battery level sensor that assumes the battery voltage is attached
|
||
* via a voltage-divider to an analog input
|
||
*/
|
||
class AnalogBatteryLevel : public HasBatteryLevel
|
||
{
|
||
public:
|
||
/**
|
||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||
*/
|
||
virtual int getBatteryPercent() override
|
||
{
|
||
#if defined(HAS_RAKPROT) && !defined(HAS_PMU)
|
||
if (hasRAK()) {
|
||
return rak9154Sensor.getBusBatteryPercent();
|
||
}
|
||
#endif
|
||
|
||
float v = getBattVoltage();
|
||
|
||
if (v < noBatVolt)
|
||
return -1; // If voltage is super low assume no battery installed
|
||
|
||
#ifdef NO_BATTERY_LEVEL_ON_CHARGE
|
||
// This does not work on a RAK4631 with battery connected
|
||
if (v > chargingVolt)
|
||
return 0; // While charging we can't report % full on the battery
|
||
#endif
|
||
/**
|
||
* @brief Battery voltage lookup table interpolation to obtain a more
|
||
* precise percentage rather than the old proportional one.
|
||
* @author Gabriele Russo
|
||
* @date 06/02/2024
|
||
*/
|
||
float battery_SOC = 0.0;
|
||
uint16_t voltage = v / NUM_CELLS; // single cell voltage (average)
|
||
for (int i = 0; i < NUM_OCV_POINTS; i++) {
|
||
if (OCV[i] <= voltage) {
|
||
if (i == 0) {
|
||
battery_SOC = 100.0; // 100% full
|
||
} else {
|
||
// interpolate between OCV[i] and OCV[i-1]
|
||
battery_SOC = (float)100.0 / (NUM_OCV_POINTS - 1.0) *
|
||
(NUM_OCV_POINTS - 1.0 - i + ((float)voltage - OCV[i]) / (OCV[i - 1] - OCV[i]));
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
#if defined(BATTERY_CHARGING_INV)
|
||
// bit of trickery to show 99% up until the charge finishes
|
||
if (!digitalRead(BATTERY_CHARGING_INV) && battery_SOC > 99)
|
||
battery_SOC = 99;
|
||
#endif
|
||
return clamp((int)(battery_SOC), 0, 100);
|
||
}
|
||
|
||
/**
|
||
* The raw voltage of the batteryin millivolts or NAN if unknown
|
||
*/
|
||
virtual uint16_t getBattVoltage() override
|
||
{
|
||
|
||
#if HAS_TELEMETRY && defined(HAS_RAKPROT) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||
if (hasRAK()) {
|
||
return getRAKVoltage();
|
||
}
|
||
#endif
|
||
|
||
#if HAS_TELEMETRY && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||
if (hasINA()) {
|
||
return getINAVoltage();
|
||
}
|
||
#endif
|
||
|
||
#ifndef ADC_MULTIPLIER
|
||
#define ADC_MULTIPLIER 2.0
|
||
#endif
|
||
|
||
#ifndef BATTERY_SENSE_SAMPLES
|
||
#define BATTERY_SENSE_SAMPLES \
|
||
15 // Set the number of samples, it has an effect of increasing sensitivity in
|
||
// complex electromagnetic environment.
|
||
#endif
|
||
|
||
#ifdef BATTERY_PIN
|
||
// Override variant or default ADC_MULTIPLIER if we have the override pref
|
||
float operativeAdcMultiplier =
|
||
config.power.adc_multiplier_override > 0 ? config.power.adc_multiplier_override : ADC_MULTIPLIER;
|
||
// Do not call analogRead() often.
|
||
const uint32_t min_read_interval = 5000;
|
||
if (!initial_read_done || !Throttle::isWithinTimespanMs(last_read_time_ms, min_read_interval)) {
|
||
last_read_time_ms = millis();
|
||
|
||
uint32_t raw = 0;
|
||
float scaled = 0;
|
||
|
||
battery_adcEnable();
|
||
#ifdef ARCH_ESP32 // ADC block for espressif platforms
|
||
raw = espAdcRead();
|
||
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
|
||
scaled *= operativeAdcMultiplier;
|
||
#else // block for all other platforms
|
||
for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
|
||
raw += analogRead(BATTERY_PIN);
|
||
}
|
||
raw = raw / BATTERY_SENSE_SAMPLES;
|
||
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
|
||
#endif
|
||
battery_adcDisable();
|
||
|
||
if (!initial_read_done) {
|
||
// Flush the smoothing filter with an ADC reading, if the reading is
|
||
// plausibly correct
|
||
if (scaled > last_read_value)
|
||
last_read_value = scaled;
|
||
initial_read_done = true;
|
||
} else {
|
||
// Already initialized - filter this reading
|
||
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
|
||
}
|
||
|
||
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u",
|
||
// BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) (last_read_value));
|
||
}
|
||
return last_read_value;
|
||
#endif // BATTERY_PIN
|
||
return 0;
|
||
}
|
||
|
||
#if defined(ARCH_ESP32) && !defined(HAS_PMU) && defined(BATTERY_PIN)
|
||
/**
|
||
* ESP32 specific function for getting calibrated ADC reads
|
||
*/
|
||
uint32_t espAdcRead()
|
||
{
|
||
|
||
uint32_t raw = 0;
|
||
uint8_t raw_c = 0; // raw reading counter
|
||
|
||
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
|
||
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
|
||
int val_ = adc1_get_raw(adc_channel);
|
||
if (val_ >= 0) { // save only valid readings
|
||
raw += val_;
|
||
raw_c++;
|
||
}
|
||
// delayMicroseconds(100);
|
||
}
|
||
#else // ADC2
|
||
#ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3
|
||
// ADC2 wifi bug workaround not required, breaks compile
|
||
// On ESP32S3, ADC2 can take turns with Wifi (?)
|
||
|
||
int32_t adc_buf;
|
||
esp_err_t read_result;
|
||
|
||
// Multiple samples
|
||
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
|
||
adc_buf = 0;
|
||
read_result = -1;
|
||
|
||
read_result = adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
|
||
if (read_result == ESP_OK) {
|
||
raw += adc_buf;
|
||
raw_c++; // Count valid samples
|
||
} else {
|
||
LOG_DEBUG("An attempt to sample ADC2 failed");
|
||
}
|
||
}
|
||
|
||
#else // Other ESP32
|
||
int32_t adc_buf = 0;
|
||
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
|
||
// ADC2 wifi bug workaround, see
|
||
// https://github.com/espressif/arduino-esp32/issues/102
|
||
WRITE_PERI_REG(SENS_SAR_READ_CTRL2_REG, RTC_reg_b);
|
||
SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV);
|
||
adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
|
||
raw += adc_buf;
|
||
raw_c++;
|
||
}
|
||
#endif // BAT_MEASURE_ADC_UNIT
|
||
|
||
#endif // End BAT_MEASURE_ADC_UNIT
|
||
return (raw / (raw_c < 1 ? 1 : raw_c));
|
||
}
|
||
#endif
|
||
|
||
/**
|
||
* return true if there is a battery installed in this unit
|
||
*/
|
||
// if we have a integrated device with a battery, we can assume that the
|
||
// battery is always connected
|
||
#ifdef BATTERY_IMMUTABLE
|
||
virtual bool isBatteryConnect() override { return true; }
|
||
#elif defined(ADC_V)
|
||
virtual bool isBatteryConnect() override
|
||
{
|
||
int lastReading = digitalRead(ADC_V);
|
||
// 判断值是否变化
|
||
for (int i = 2; i < 500; i++) {
|
||
int reading = digitalRead(ADC_V);
|
||
if (reading != lastReading) {
|
||
return false; // 有变化,USB供电, 没接电池
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
#else
|
||
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
|
||
#endif
|
||
|
||
/// If we see a battery voltage higher than physics allows - assume charger is
|
||
/// pumping in power On some boards we don't have the power management chip
|
||
/// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power
|
||
/// source
|
||
virtual bool isVbusIn() override
|
||
{
|
||
#ifdef EXT_PWR_DETECT
|
||
#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
|
||
// if external powered that pin will be pulled down
|
||
if (digitalRead(EXT_PWR_DETECT) == LOW) {
|
||
return true;
|
||
}
|
||
// if it's not LOW - check the battery
|
||
#else
|
||
// if external powered that pin will be pulled up
|
||
if (digitalRead(EXT_PWR_DETECT) == HIGH) {
|
||
return true;
|
||
}
|
||
// if it's not HIGH - check the battery
|
||
#endif
|
||
// If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it.
|
||
return false;
|
||
|
||
// technically speaking this should work for all(?) NRF52 boards
|
||
// but needs testing across multiple devices. NRF52 USB would not even work if
|
||
// VBUS was not properly connected and detected by the CPU
|
||
#elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO)
|
||
return powerHAL_isVBUSConnected();
|
||
#endif
|
||
return getBattVoltage() > chargingVolt;
|
||
}
|
||
|
||
/// Assume charging if we have a battery and external power is connected.
|
||
/// we can't be smart enough to say 'full'?
|
||
virtual bool isCharging() override
|
||
{
|
||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT) && !defined(HAS_PMU)
|
||
if (hasRAK()) {
|
||
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
|
||
}
|
||
#endif
|
||
#if defined(ELECROW_ThinkNode_M6)
|
||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value || isVbusIn();
|
||
#elif EXT_CHRG_DETECT
|
||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||
#elif defined(BATTERY_CHARGING_INV)
|
||
return !digitalRead(BATTERY_CHARGING_INV);
|
||
#else
|
||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
|
||
if (hasINA()) {
|
||
// get current flow from INA sensor - negative value means power flowing
|
||
// into the battery default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT
|
||
// RESISTOR <--> INA_VIN- <--> LOAD
|
||
LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address);
|
||
#if defined(INA_CHARGING_DETECTION_INVERT)
|
||
return getINACurrent() > 0;
|
||
#else
|
||
return getINACurrent() < 0;
|
||
#endif
|
||
}
|
||
return isBatteryConnect() && isVbusIn();
|
||
#endif
|
||
#endif
|
||
// by default, we check the battery voltage only
|
||
return isVbusIn();
|
||
}
|
||
|
||
private:
|
||
/// If we see a battery voltage higher than physics allows - assume charger is
|
||
/// pumping in power
|
||
|
||
/// For heltecs with no battery connected, the measured voltage is 2204, so
|
||
// need to be higher than that, in this case is 2500mV (3000-500)
|
||
const uint16_t OCV[NUM_OCV_POINTS] = {OCV_ARRAY};
|
||
const float chargingVolt = (OCV[0] + 10) * NUM_CELLS;
|
||
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
|
||
// Start value from minimum voltage for the filter to not start from 0
|
||
// that could trigger some events.
|
||
// This value is over-written by the first ADC reading, it the voltage seems
|
||
// reasonable.
|
||
bool initial_read_done = false;
|
||
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
|
||
uint32_t last_read_time_ms = 0;
|
||
|
||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT)
|
||
|
||
uint16_t getRAKVoltage() { return rak9154Sensor.getBusVoltageMv(); }
|
||
|
||
bool hasRAK()
|
||
{
|
||
if (!rak9154Sensor.isInitialized())
|
||
return rak9154Sensor.runOnce() > 0;
|
||
return rak9154Sensor.isRunning();
|
||
}
|
||
#endif
|
||
|
||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||
uint16_t getINAVoltage()
|
||
{
|
||
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
|
||
return ina219Sensor.getBusVoltageMv();
|
||
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA226].first ==
|
||
config.power.device_battery_ina_address) {
|
||
return ina226Sensor.getBusVoltageMv();
|
||
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
|
||
config.power.device_battery_ina_address) {
|
||
return ina260Sensor.getBusVoltageMv();
|
||
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA3221].first ==
|
||
config.power.device_battery_ina_address) {
|
||
return ina3221Sensor.getBusVoltageMv();
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int16_t getINACurrent()
|
||
{
|
||
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
|
||
return ina219Sensor.getCurrentMa();
|
||
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA226].first ==
|
||
config.power.device_battery_ina_address) {
|
||
return ina226Sensor.getCurrentMa();
|
||
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA3221].first ==
|
||
config.power.device_battery_ina_address) {
|
||
return ina3221Sensor.getCurrentMa();
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
bool hasINA()
|
||
{
|
||
if (!config.power.device_battery_ina_address) {
|
||
return false;
|
||
}
|
||
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
|
||
if (!ina219Sensor.isInitialized())
|
||
return ina219Sensor.runOnce() > 0;
|
||
return ina219Sensor.isRunning();
|
||
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA226].first ==
|
||
config.power.device_battery_ina_address) {
|
||
if (!ina226Sensor.isInitialized())
|
||
return ina226Sensor.runOnce() > 0;
|
||
return ina226Sensor.isRunning();
|
||
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
|
||
config.power.device_battery_ina_address) {
|
||
if (!ina260Sensor.isInitialized())
|
||
return ina260Sensor.runOnce() > 0;
|
||
return ina260Sensor.isRunning();
|
||
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA3221].first ==
|
||
config.power.device_battery_ina_address) {
|
||
if (!ina3221Sensor.isInitialized())
|
||
return ina3221Sensor.runOnce() > 0;
|
||
return ina3221Sensor.isRunning();
|
||
}
|
||
return false;
|
||
}
|
||
#endif
|
||
};
|
||
|
||
static AnalogBatteryLevel analogLevel;
|
||
|
||
Power::Power() : OSThread("Power")
|
||
{
|
||
statusHandler = {};
|
||
low_voltage_counter = 0;
|
||
#ifdef DEBUG_HEAP
|
||
lastheap = memGet.getFreeHeap();
|
||
#endif
|
||
}
|
||
|
||
bool Power::analogInit()
|
||
{
|
||
#ifdef EXT_PWR_DETECT
|
||
#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
|
||
pinMode(EXT_PWR_DETECT, INPUT_PULLUP);
|
||
#else
|
||
pinMode(EXT_PWR_DETECT, INPUT);
|
||
#endif
|
||
#endif
|
||
#ifdef EXT_CHRG_DETECT
|
||
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
|
||
#endif
|
||
|
||
#ifdef BATTERY_PIN
|
||
LOG_DEBUG("Use analog input %d for battery level", BATTERY_PIN);
|
||
|
||
// disable any internal pullups
|
||
pinMode(BATTERY_PIN, INPUT);
|
||
|
||
#ifndef BATTERY_SENSE_RESOLUTION_BITS
|
||
#define BATTERY_SENSE_RESOLUTION_BITS 10
|
||
#endif
|
||
|
||
#ifdef ARCH_ESP32 // ESP32 needs special analog stuff
|
||
|
||
#ifndef ADC_WIDTH // max resolution by default
|
||
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
|
||
#else
|
||
static const adc_bits_width_t width = ADC_WIDTH;
|
||
#endif
|
||
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
|
||
adc1_config_width(width);
|
||
adc1_config_channel_atten(adc_channel, atten);
|
||
#else // ADC2
|
||
adc2_config_channel_atten(adc_channel, atten);
|
||
#ifndef CONFIG_IDF_TARGET_ESP32S3
|
||
// ADC2 wifi bug workaround
|
||
// Not required with ESP32S3, breaks compile
|
||
RTC_reg_b = READ_PERI_REG(SENS_SAR_READ_CTRL2_REG);
|
||
#endif
|
||
#endif
|
||
// calibrate ADC
|
||
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_characs);
|
||
// show ADC characterization base
|
||
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
|
||
LOG_INFO("ADC config based on Two Point values stored in eFuse");
|
||
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
|
||
LOG_INFO("ADC config based on reference voltage stored in eFuse");
|
||
}
|
||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||
// ESP32S3
|
||
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
|
||
LOG_INFO("ADC config based on Two Point values and fitting curve "
|
||
"coefficients stored in eFuse");
|
||
}
|
||
#endif
|
||
else {
|
||
LOG_INFO("ADC config based on default reference voltage");
|
||
}
|
||
#endif // ARCH_ESP32
|
||
|
||
// NRF52 ADC init moved to powerHAL_init in nrf52 platform
|
||
|
||
#ifndef ARCH_ESP32
|
||
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
|
||
#endif
|
||
|
||
batteryLevel = &analogLevel;
|
||
return true;
|
||
#else
|
||
return false;
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* Initializes the Power class.
|
||
*
|
||
* @return true if the setup was successful, false otherwise.
|
||
*/
|
||
bool Power::setup()
|
||
{
|
||
bool found = false;
|
||
if (axpChipInit()) {
|
||
found = true;
|
||
} else if (cw2015Init()) {
|
||
found = true;
|
||
} else if (max17048Init()) {
|
||
found = true;
|
||
} else if (lipoChargerInit()) {
|
||
found = true;
|
||
} else if (serialBatteryInit()) {
|
||
found = true;
|
||
} else if (meshSolarInit()) {
|
||
found = true;
|
||
} else if (analogInit()) {
|
||
found = true;
|
||
} else {
|
||
#ifdef NRF_APM
|
||
found = true;
|
||
#endif
|
||
}
|
||
#ifdef EXT_PWR_DETECT
|
||
attachInterrupt(
|
||
EXT_PWR_DETECT,
|
||
[]() {
|
||
power->setIntervalFromNow(0);
|
||
runASAP = true;
|
||
},
|
||
CHANGE);
|
||
#endif
|
||
#ifdef BATTERY_CHARGING_INV
|
||
attachInterrupt(
|
||
BATTERY_CHARGING_INV,
|
||
[]() {
|
||
power->setIntervalFromNow(0);
|
||
runASAP = true;
|
||
},
|
||
CHANGE);
|
||
#endif
|
||
#ifdef EXT_CHRG_DETECT
|
||
attachInterrupt(
|
||
EXT_CHRG_DETECT,
|
||
[]() {
|
||
power->setIntervalFromNow(0);
|
||
runASAP = true;
|
||
BaseType_t higherWake = 0;
|
||
},
|
||
CHANGE);
|
||
#endif
|
||
enabled = found;
|
||
low_voltage_counter = 0;
|
||
|
||
return found;
|
||
}
|
||
|
||
void Power::powerCommandsCheck()
|
||
{
|
||
if (rebootAtMsec && millis() > rebootAtMsec) {
|
||
LOG_INFO("Rebooting");
|
||
reboot();
|
||
}
|
||
|
||
if (shutdownAtMsec && millis() > shutdownAtMsec) {
|
||
shutdownAtMsec = 0;
|
||
shutdown();
|
||
}
|
||
}
|
||
|
||
void Power::reboot()
|
||
{
|
||
notifyReboot.notifyObservers(NULL);
|
||
#if defined(ARCH_ESP32)
|
||
ESP.restart();
|
||
#elif defined(ARCH_NRF52)
|
||
NVIC_SystemReset();
|
||
#elif defined(ARCH_RP2040)
|
||
rp2040.reboot();
|
||
#elif defined(ARCH_PORTDUINO)
|
||
deInitApiServer();
|
||
if (aLinuxInputImpl)
|
||
aLinuxInputImpl->deInit();
|
||
SPI.end();
|
||
Wire.end();
|
||
Serial1.end();
|
||
if (screen) {
|
||
delete screen;
|
||
screen = nullptr;
|
||
}
|
||
LOG_DEBUG("final reboot!");
|
||
::reboot();
|
||
#elif defined(ARCH_STM32WL)
|
||
HAL_NVIC_SystemReset();
|
||
#else
|
||
rebootAtMsec = -1;
|
||
LOG_WARN("FIXME implement reboot for this platform. Note that some settings "
|
||
"require a restart to be applied");
|
||
#endif
|
||
}
|
||
|
||
void Power::shutdown()
|
||
{
|
||
|
||
#if HAS_SCREEN
|
||
if (screen) {
|
||
#ifdef T_DECK_PRO
|
||
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!",
|
||
0); // T-Deck Pro has no power button
|
||
#elif defined(USE_EINK)
|
||
screen->showSimpleBanner("Shutting Down...",
|
||
2250); // dismiss after 3 seconds to avoid the
|
||
// banner on the sleep screen
|
||
#else
|
||
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
||
#endif
|
||
}
|
||
#endif
|
||
#if !defined(ARCH_STM32WL)
|
||
playShutdownMelody();
|
||
#endif
|
||
nodeDB->saveToDisk();
|
||
#if HAS_SCREEN
|
||
messageStore.saveToFlash();
|
||
#endif
|
||
#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
|
||
#ifdef PIN_LED1
|
||
ledOff(PIN_LED1);
|
||
#endif
|
||
#ifdef PIN_LED2
|
||
ledOff(PIN_LED2);
|
||
#endif
|
||
#ifdef PIN_LED3
|
||
ledOff(PIN_LED3);
|
||
#endif
|
||
#ifdef LED_NOTIFICATION
|
||
ledOff(LED_NOTIFICATION);
|
||
#endif
|
||
doDeepSleep(DELAY_FOREVER, true, true);
|
||
#elif defined(ARCH_PORTDUINO)
|
||
exit(EXIT_SUCCESS);
|
||
#else
|
||
LOG_WARN("FIXME implement shutdown for this platform");
|
||
#endif
|
||
}
|
||
|
||
/// Reads power status to powerStatus singleton.
|
||
//
|
||
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
|
||
void Power::readPowerStatus()
|
||
{
|
||
int32_t batteryVoltageMv = -1; // Assume unknown
|
||
int8_t batteryChargePercent = -1;
|
||
OptionalBool usbPowered = OptUnknown;
|
||
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM
|
||
// code doesn't run every time
|
||
OptionalBool isChargingNow = OptUnknown;
|
||
|
||
if (batteryLevel) {
|
||
hasBattery = batteryLevel->isBatteryConnect() ? OptTrue : OptFalse;
|
||
#ifndef NRF_APM
|
||
usbPowered = batteryLevel->isVbusIn() ? OptTrue : OptFalse;
|
||
isChargingNow = batteryLevel->isCharging() ? OptTrue : OptFalse;
|
||
#endif
|
||
if (hasBattery) {
|
||
batteryVoltageMv = batteryLevel->getBattVoltage();
|
||
// If the AXP192 returns a valid battery percentage, use it
|
||
if (batteryLevel->getBatteryPercent() >= 0) {
|
||
batteryChargePercent = batteryLevel->getBatteryPercent();
|
||
} else {
|
||
// If the AXP192 returns a percentage less than 0, the feature is either
|
||
// not supported or there is an error In that case, we compute an
|
||
// estimate of the charge percent based on open circuit voltage table
|
||
// defined in power.h
|
||
batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) /
|
||
((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))),
|
||
0, 100);
|
||
}
|
||
}
|
||
}
|
||
|
||
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way
|
||
// better instead to make a Nrf52IsUsbPowered subclass (which shares a
|
||
// superclass with the BatteryLevel stuff) that just provides a few methods. But
|
||
// in the interest of fixing this bug I'm going to follow current practice.
|
||
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates
|
||
// the power states. Takes 20 seconds or so to detect changes.
|
||
|
||
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
|
||
// LOG_DEBUG("NRF Power %d", nrf_usb_state);
|
||
|
||
// If changed to DISCONNECTED
|
||
if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED)
|
||
isChargingNow = usbPowered = OptFalse;
|
||
// If changed to CONNECTED / READY
|
||
else
|
||
isChargingNow = usbPowered = OptTrue;
|
||
|
||
#endif
|
||
|
||
// Notify any status instances that are observing us
|
||
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
|
||
if (millis() > lastLogTime + 50 * 1000) {
|
||
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
|
||
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
|
||
lastLogTime = millis();
|
||
}
|
||
newStatus.notifyObservers(&powerStatus2);
|
||
#ifdef DEBUG_HEAP
|
||
if (lastheap != memGet.getFreeHeap()) {
|
||
// Use stack-allocated buffer to avoid heap allocations in monitoring code
|
||
char threadlist[256] = "Threads running:";
|
||
int threadlistLen = strlen(threadlist);
|
||
int running = 0;
|
||
for (int i = 0; i < MAX_THREADS; i++) {
|
||
auto thread = concurrency::mainController.get(i);
|
||
if ((thread != nullptr) && (thread->enabled)) {
|
||
// Use snprintf to safely append to stack buffer without heap allocation
|
||
int remaining = sizeof(threadlist) - threadlistLen - 1;
|
||
if (remaining > 0) {
|
||
int written = snprintf(threadlist + threadlistLen, remaining, " %s", thread->ThreadName.c_str());
|
||
if (written > 0 && written < remaining) {
|
||
threadlistLen += written;
|
||
}
|
||
}
|
||
running++;
|
||
}
|
||
}
|
||
LOG_HEAP(threadlist);
|
||
LOG_HEAP("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
|
||
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
|
||
lastheap = memGet.getFreeHeap();
|
||
}
|
||
#ifdef DEBUG_HEAP_MQTT
|
||
if (mqtt) {
|
||
// send MQTT-Packet with Heap-Size
|
||
uint8_t dmac[6];
|
||
getMacAddr(dmac); // Get our hardware ID
|
||
char mac[18];
|
||
sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]);
|
||
|
||
auto newHeap = memGet.getFreeHeap();
|
||
// Use stack-allocated buffers to avoid heap allocations in monitoring code
|
||
char heapTopic[128];
|
||
snprintf(heapTopic, sizeof(heapTopic), "%s/2/heap/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac);
|
||
char heapString[16];
|
||
snprintf(heapString, sizeof(heapString), "%u", newHeap);
|
||
mqtt->pubSub.publish(heapTopic, heapString, false);
|
||
|
||
auto wifiRSSI = WiFi.RSSI();
|
||
char wifiTopic[128];
|
||
snprintf(wifiTopic, sizeof(wifiTopic), "%s/2/wifi/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac);
|
||
char wifiString[16];
|
||
snprintf(wifiString, sizeof(wifiString), "%d", wifiRSSI);
|
||
mqtt->pubSub.publish(wifiTopic, wifiString, false);
|
||
}
|
||
#endif
|
||
|
||
#endif
|
||
|
||
// If we have a battery at all and it is less than 0%, force deep sleep if we
|
||
// have more than 10 low readings in a row. NOTE: min LiIon/LiPo voltage
|
||
// is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
|
||
//
|
||
|
||
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
|
||
if (batteryLevel->getBattVoltage() < OCV[NUM_OCV_POINTS - 1]) {
|
||
low_voltage_counter++;
|
||
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
|
||
if (low_voltage_counter > 10) {
|
||
LOG_INFO("Low voltage detected, trigger deep sleep");
|
||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||
}
|
||
} else {
|
||
low_voltage_counter = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
int32_t Power::runOnce()
|
||
{
|
||
readPowerStatus();
|
||
|
||
#ifdef HAS_PMU
|
||
// WE no longer use the IRQ line to wake the CPU (due to false wakes from
|
||
// sleep), but we do poll the IRQ status by reading the registers over I2C
|
||
if (PMU) {
|
||
|
||
PMU->getIrqStatus();
|
||
|
||
if (PMU->isVbusRemoveIrq()) {
|
||
LOG_INFO("USB unplugged");
|
||
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
|
||
}
|
||
|
||
if (PMU->isVbusInsertIrq()) {
|
||
LOG_INFO("USB plugged In");
|
||
powerFSM.trigger(EVENT_POWER_CONNECTED);
|
||
}
|
||
|
||
/*
|
||
Other things we could check if we cared...
|
||
|
||
if (PMU->isBatChagerStartIrq()) {
|
||
LOG_DEBUG("Battery start charging");
|
||
}
|
||
if (PMU->isBatChagerDoneIrq()) {
|
||
LOG_DEBUG("Battery fully charged");
|
||
}
|
||
if (PMU->isBatInsertIrq()) {
|
||
LOG_DEBUG("Battery inserted");
|
||
}
|
||
if (PMU->isBatRemoveIrq()) {
|
||
LOG_DEBUG("Battery removed");
|
||
}
|
||
*/
|
||
#ifndef T_WATCH_S3 // FIXME - why is this triggering on the T-Watch S3?
|
||
if (PMU->isPekeyLongPressIrq()) {
|
||
LOG_DEBUG("PEK long button press");
|
||
if (screen)
|
||
screen->setOn(false);
|
||
}
|
||
#endif
|
||
|
||
PMU->clearIrqStatus();
|
||
}
|
||
#endif
|
||
// Only read once every 20 seconds once the power status for the app has been
|
||
// initialized
|
||
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
||
}
|
||
|
||
/**
|
||
* Init the power manager chip
|
||
*
|
||
* axp192 power
|
||
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose
|
||
comms to the axp192 because the OLED and the axp192 share the same i2c bus,
|
||
instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max ->
|
||
ESP32 (keep this on!) LDO1 30mA -> charges GPS backup battery // charges the
|
||
tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
|
||
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
||
*
|
||
*/
|
||
bool Power::axpChipInit()
|
||
{
|
||
|
||
#ifdef HAS_PMU
|
||
|
||
TwoWire *w = NULL;
|
||
|
||
// Use macro to distinguish which wire is used by PMU
|
||
#ifdef PMU_USE_WIRE1
|
||
w = &Wire1;
|
||
#else
|
||
w = &Wire;
|
||
#endif
|
||
|
||
/**
|
||
* It is not necessary to specify the wire pin,
|
||
* just input the wire, because the wire has been initialized in main.cpp
|
||
*/
|
||
if (!PMU) {
|
||
PMU = new XPowersAXP2101(*w);
|
||
if (!PMU->init()) {
|
||
LOG_WARN("No AXP2101 power management");
|
||
delete PMU;
|
||
PMU = NULL;
|
||
} else {
|
||
LOG_INFO("AXP2101 PMU init succeeded");
|
||
}
|
||
}
|
||
|
||
if (!PMU) {
|
||
PMU = new XPowersAXP192(*w);
|
||
if (!PMU->init()) {
|
||
LOG_WARN("No AXP192 power management");
|
||
delete PMU;
|
||
PMU = NULL;
|
||
} else {
|
||
LOG_INFO("AXP192 PMU init succeeded");
|
||
}
|
||
}
|
||
|
||
if (!PMU) {
|
||
/*
|
||
* In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will
|
||
* be called at the same time. In order not to affect other devices, if the
|
||
* initialization of the PMU fails, Wire needs to be re-initialized once, if
|
||
* there are multiple devices sharing the bus.
|
||
* * */
|
||
#ifndef PMU_USE_WIRE1
|
||
w->begin(I2C_SDA, I2C_SCL);
|
||
#endif
|
||
return false;
|
||
}
|
||
|
||
batteryLevel = PMU;
|
||
|
||
if (PMU->getChipModel() == XPOWERS_AXP192) {
|
||
|
||
// lora radio power channel
|
||
PMU->setPowerChannelVoltage(XPOWERS_LDO2, 3300);
|
||
PMU->enablePowerOutput(XPOWERS_LDO2);
|
||
|
||
// oled module power channel,
|
||
// disable it will cause abnormal communication between boot and AXP power
|
||
// supply, do not turn it off
|
||
PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
|
||
// enable oled power
|
||
PMU->enablePowerOutput(XPOWERS_DCDC1);
|
||
|
||
// gnss module power channel - now turned on in setGpsPower
|
||
PMU->setPowerChannelVoltage(XPOWERS_LDO3, 3300);
|
||
// PMU->enablePowerOutput(XPOWERS_LDO3);
|
||
|
||
// protected oled power source
|
||
PMU->setProtectedChannel(XPOWERS_DCDC1);
|
||
// protected esp32 power source
|
||
PMU->setProtectedChannel(XPOWERS_DCDC3);
|
||
|
||
// disable not use channel
|
||
PMU->disablePowerOutput(XPOWERS_DCDC2);
|
||
|
||
// disable all axp chip interrupt
|
||
PMU->disableIRQ(XPOWERS_AXP192_ALL_IRQ);
|
||
|
||
// Set constant current charging current
|
||
PMU->setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_450MA);
|
||
|
||
// Set up the charging voltage
|
||
PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
|
||
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
|
||
|
||
/*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it
|
||
* uses an AXP2101 power chip*/
|
||
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
|
||
// Unuse power channel
|
||
PMU->disablePowerOutput(XPOWERS_DCDC2);
|
||
PMU->disablePowerOutput(XPOWERS_DCDC3);
|
||
PMU->disablePowerOutput(XPOWERS_DCDC4);
|
||
PMU->disablePowerOutput(XPOWERS_DCDC5);
|
||
PMU->disablePowerOutput(XPOWERS_ALDO1);
|
||
PMU->disablePowerOutput(XPOWERS_ALDO4);
|
||
PMU->disablePowerOutput(XPOWERS_BLDO1);
|
||
PMU->disablePowerOutput(XPOWERS_BLDO2);
|
||
PMU->disablePowerOutput(XPOWERS_DLDO1);
|
||
PMU->disablePowerOutput(XPOWERS_DLDO2);
|
||
|
||
// GNSS RTC PowerVDD 3300mV
|
||
PMU->setPowerChannelVoltage(XPOWERS_VBACKUP, 3300);
|
||
PMU->enablePowerOutput(XPOWERS_VBACKUP);
|
||
|
||
// ESP32 VDD 3300mV
|
||
// ! No need to set, automatically open , Don't close it
|
||
// PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
|
||
// PMU->setProtectedChannel(XPOWERS_DCDC1);
|
||
|
||
// LoRa VDD 3300mV
|
||
PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300);
|
||
PMU->enablePowerOutput(XPOWERS_ALDO2);
|
||
|
||
// GNSS VDD 3300mV
|
||
PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300);
|
||
PMU->enablePowerOutput(XPOWERS_ALDO3);
|
||
} else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE ||
|
||
HW_VENDOR == meshtastic_HardwareModel_T_WATCH_S3) {
|
||
// t-beam s3 core
|
||
/**
|
||
* gnss module power channel
|
||
* The default ALDO4 is off, you need to turn on the GNSS power first,
|
||
* otherwise it will be invalid during initialization
|
||
*/
|
||
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
|
||
PMU->enablePowerOutput(XPOWERS_ALDO4);
|
||
|
||
// lora radio power channel
|
||
PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300);
|
||
PMU->enablePowerOutput(XPOWERS_ALDO3);
|
||
|
||
// m.2 interface
|
||
PMU->setPowerChannelVoltage(XPOWERS_DCDC3, 3300);
|
||
PMU->enablePowerOutput(XPOWERS_DCDC3);
|
||
|
||
/**
|
||
* ALDO2 cannot be turned off.
|
||
* It is a necessary condition for sensor communication.
|
||
* It must be turned on to properly access the sensor and screen
|
||
* It is also responsible for the power supply of PCF8563
|
||
*/
|
||
PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300);
|
||
PMU->enablePowerOutput(XPOWERS_ALDO2);
|
||
|
||
// 6-axis , magnetometer ,bme280 , oled screen power channel
|
||
PMU->setPowerChannelVoltage(XPOWERS_ALDO1, 3300);
|
||
PMU->enablePowerOutput(XPOWERS_ALDO1);
|
||
|
||
// sdcard (T-Beam S3) / gnns (T-Watch S3 Plus) power channel
|
||
PMU->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
|
||
#ifndef T_WATCH_S3
|
||
PMU->enablePowerOutput(XPOWERS_BLDO1);
|
||
#else
|
||
// DRV2605 power channel
|
||
PMU->setPowerChannelVoltage(XPOWERS_BLDO2, 3300);
|
||
PMU->enablePowerOutput(XPOWERS_BLDO2);
|
||
#endif
|
||
|
||
// PMU->setPowerChannelVoltage(XPOWERS_DCDC4, 3300);
|
||
// PMU->enablePowerOutput(XPOWERS_DCDC4);
|
||
|
||
// not use channel
|
||
PMU->disablePowerOutput(XPOWERS_DCDC2); // not elicited
|
||
PMU->disablePowerOutput(XPOWERS_DCDC5); // not elicited
|
||
PMU->disablePowerOutput(XPOWERS_DLDO1); // Invalid power channel, it does not exist
|
||
PMU->disablePowerOutput(XPOWERS_DLDO2); // Invalid power channel, it does not exist
|
||
PMU->disablePowerOutput(XPOWERS_VBACKUP);
|
||
}
|
||
|
||
// disable all axp chip interrupt
|
||
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||
|
||
// Set the constant current charging current of AXP2101, temporarily use
|
||
// 500mA by default
|
||
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
|
||
|
||
// Set up the charging voltage
|
||
PMU->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
|
||
}
|
||
|
||
PMU->clearIrqStatus();
|
||
|
||
// TBeam1.1 /T-Beam S3-Core has no external TS detection,
|
||
// it needs to be disabled, otherwise it will cause abnormal charging
|
||
PMU->disableTSPinMeasure();
|
||
|
||
// PMU->enableSystemVoltageMeasure();
|
||
PMU->enableVbusVoltageMeasure();
|
||
PMU->enableBattVoltageMeasure();
|
||
|
||
if (PMU->isChannelAvailable(XPOWERS_DCDC1)) {
|
||
LOG_DEBUG("DC1 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_DCDC1) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_DCDC1));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_DCDC2)) {
|
||
LOG_DEBUG("DC2 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_DCDC2) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_DCDC2));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_DCDC3)) {
|
||
LOG_DEBUG("DC3 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_DCDC3) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_DCDC3));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_DCDC4)) {
|
||
LOG_DEBUG("DC4 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_DCDC4) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_DCDC4));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_LDO2)) {
|
||
LOG_DEBUG("LDO2 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_LDO2) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_LDO2));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_LDO3)) {
|
||
LOG_DEBUG("LDO3 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_LDO3) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_LDO3));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_ALDO1)) {
|
||
LOG_DEBUG("ALDO1: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_ALDO1) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_ALDO1));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_ALDO2)) {
|
||
LOG_DEBUG("ALDO2: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_ALDO2) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_ALDO2));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_ALDO3)) {
|
||
LOG_DEBUG("ALDO3: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_ALDO3) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_ALDO3));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_ALDO4)) {
|
||
LOG_DEBUG("ALDO4: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_ALDO4) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_ALDO4));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_BLDO1)) {
|
||
LOG_DEBUG("BLDO1: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_BLDO1) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_BLDO1));
|
||
}
|
||
if (PMU->isChannelAvailable(XPOWERS_BLDO2)) {
|
||
LOG_DEBUG("BLDO2: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_BLDO2) ? "+" : "-",
|
||
PMU->getPowerChannelVoltage(XPOWERS_BLDO2));
|
||
}
|
||
|
||
// We can safely ignore this approach for most (or all) boards because MCU
|
||
// turned off earlier than battery discharged to 2.6V.
|
||
//
|
||
// Unfortunately for now we can't use this killswitch for RAK4630-based boards
|
||
// because they have a bug with battery voltage measurement. Probably it
|
||
// sometimes drops to low values.
|
||
#ifndef RAK4630
|
||
// Set PMU shutdown voltage at 2.6V to maximize battery utilization
|
||
PMU->setSysPowerDownVoltage(2600);
|
||
#endif
|
||
|
||
#ifdef PMU_IRQ
|
||
uint64_t pmuIrqMask = 0;
|
||
|
||
if (PMU->getChipModel() == XPOWERS_AXP192) {
|
||
pmuIrqMask = XPOWERS_AXP192_VBUS_INSERT_IRQ | XPOWERS_AXP192_BAT_INSERT_IRQ | XPOWERS_AXP192_PKEY_SHORT_IRQ;
|
||
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
|
||
pmuIrqMask = XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_PKEY_SHORT_IRQ;
|
||
}
|
||
|
||
pinMode(PMU_IRQ, INPUT);
|
||
attachInterrupt(
|
||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||
|
||
// we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ
|
||
// because it occurs repeatedly while there is no battery also it could cause
|
||
// inadvertent waking from light sleep just because the battery filled we
|
||
// don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while
|
||
// no battery installed we don't look at AXPXXX_VBUS_REMOVED_IRQ because we
|
||
// don't have anything hooked to vbus
|
||
PMU->enableIRQ(pmuIrqMask);
|
||
|
||
PMU->clearIrqStatus();
|
||
#endif /*PMU_IRQ*/
|
||
|
||
readPowerStatus();
|
||
|
||
pmu_found = true;
|
||
|
||
return pmu_found;
|
||
|
||
#else
|
||
return false;
|
||
#endif
|
||
}
|
||
|
||
#if !MESHTASTIC_EXCLUDE_I2C && __has_include(<Adafruit_MAX1704X.h>)
|
||
|
||
/**
|
||
* Wrapper class for an I2C MAX17048 Lipo battery sensor.
|
||
*/
|
||
class MAX17048BatteryLevel : public HasBatteryLevel
|
||
{
|
||
private:
|
||
MAX17048Singleton *max17048 = nullptr;
|
||
|
||
public:
|
||
/**
|
||
* Init the I2C MAX17048 Lipo battery level sensor
|
||
*/
|
||
bool runOnce()
|
||
{
|
||
if (max17048 == nullptr) {
|
||
max17048 = MAX17048Singleton::GetInstance();
|
||
}
|
||
|
||
// try to start if the sensor has been detected
|
||
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX17048].first != 0) {
|
||
return max17048->runOnce(nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX17048].second);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||
*/
|
||
virtual int getBatteryPercent() override { return max17048->getBusBatteryPercent(); }
|
||
|
||
/**
|
||
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||
*/
|
||
virtual uint16_t getBattVoltage() override { return max17048->getBusVoltageMv(); }
|
||
|
||
/**
|
||
* return true if there is a battery installed in this unit
|
||
*/
|
||
virtual bool isBatteryConnect() override { return max17048->isBatteryConnected(); }
|
||
|
||
/**
|
||
* return true if there is an external power source detected
|
||
*/
|
||
virtual bool isVbusIn() override { return max17048->isExternallyPowered(); }
|
||
|
||
/**
|
||
* return true if the battery is currently charging
|
||
*/
|
||
virtual bool isCharging() override { return max17048->isBatteryCharging(); }
|
||
};
|
||
|
||
MAX17048BatteryLevel max17048Level;
|
||
|
||
/**
|
||
* Init the Lipo battery level sensor
|
||
*/
|
||
bool Power::max17048Init()
|
||
{
|
||
bool result = max17048Level.runOnce();
|
||
LOG_DEBUG("Power::max17048Init lipo sensor is %s", result ? "ready" : "not ready yet");
|
||
if (!result)
|
||
return false;
|
||
batteryLevel = &max17048Level;
|
||
return true;
|
||
}
|
||
|
||
#else
|
||
/**
|
||
* The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel
|
||
*/
|
||
bool Power::max17048Init()
|
||
{
|
||
return false;
|
||
}
|
||
#endif
|
||
|
||
#if !MESHTASTIC_EXCLUDE_I2C && HAS_CW2015
|
||
|
||
class CW2015BatteryLevel : public AnalogBatteryLevel
|
||
{
|
||
public:
|
||
/**
|
||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||
*/
|
||
virtual int getBatteryPercent() override
|
||
{
|
||
int data = -1;
|
||
Wire.beginTransmission(CW2015_ADDR);
|
||
Wire.write(0x04);
|
||
if (Wire.endTransmission() == 0) {
|
||
if (Wire.requestFrom(CW2015_ADDR, (uint8_t)1)) {
|
||
data = Wire.read();
|
||
}
|
||
}
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||
*/
|
||
virtual uint16_t getBattVoltage() override
|
||
{
|
||
uint16_t mv = 0;
|
||
Wire.beginTransmission(CW2015_ADDR);
|
||
Wire.write(0x02);
|
||
if (Wire.endTransmission() == 0) {
|
||
if (Wire.requestFrom(CW2015_ADDR, (uint8_t)2)) {
|
||
mv = Wire.read();
|
||
mv <<= 8;
|
||
mv |= Wire.read();
|
||
// Voltage is read in 305uV units, convert to mV
|
||
mv = mv * 305 / 1000;
|
||
}
|
||
}
|
||
return mv;
|
||
}
|
||
};
|
||
|
||
CW2015BatteryLevel cw2015Level;
|
||
|
||
/**
|
||
* Init the CW2015 battery level sensor
|
||
*/
|
||
bool Power::cw2015Init()
|
||
{
|
||
|
||
Wire.beginTransmission(CW2015_ADDR);
|
||
uint8_t getInfo[] = {0x0a, 0x00};
|
||
Wire.write(getInfo, 2);
|
||
Wire.endTransmission();
|
||
delay(10);
|
||
Wire.beginTransmission(CW2015_ADDR);
|
||
Wire.write(0x00);
|
||
bool result = false;
|
||
if (Wire.endTransmission() == 0) {
|
||
if (Wire.requestFrom(CW2015_ADDR, (uint8_t)1)) {
|
||
uint8_t data = Wire.read();
|
||
LOG_DEBUG("CW2015 init read data: 0x%x", data);
|
||
if (data == 0x73) {
|
||
result = true;
|
||
batteryLevel = &cw2015Level;
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
#else
|
||
/**
|
||
* The CW2015 battery level sensor is unavailable - default to AnalogBatteryLevel
|
||
*/
|
||
bool Power::cw2015Init()
|
||
{
|
||
return false;
|
||
}
|
||
#endif
|
||
|
||
#if defined(HAS_PPM) && HAS_PPM
|
||
|
||
/**
|
||
* Adapter class for BQ25896/BQ27220 Lipo battery charger.
|
||
*/
|
||
class LipoCharger : public HasBatteryLevel
|
||
{
|
||
private:
|
||
BQ27220 *bq = nullptr;
|
||
|
||
public:
|
||
/**
|
||
* Init the I2C BQ25896 Lipo battery charger
|
||
*/
|
||
bool runOnce()
|
||
{
|
||
if (PPM == nullptr) {
|
||
PPM = new XPowersPPM;
|
||
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
|
||
if (result) {
|
||
LOG_INFO("PPM BQ25896 init succeeded");
|
||
// Set the minimum operating voltage. Below this voltage, the PPM will
|
||
// protect PPM->setSysPowerDownVoltage(3100);
|
||
|
||
// Set input current limit, default is 500mA
|
||
// PPM->setInputCurrentLimit(800);
|
||
|
||
// Disable current limit pin
|
||
// PPM->disableCurrentLimitPin();
|
||
|
||
// Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV
|
||
PPM->setChargeTargetVoltage(4288);
|
||
|
||
// Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA
|
||
// PPM->setPrechargeCurr(64);
|
||
|
||
// The premise is that limit pin is disabled, or it will
|
||
// only follow the maximum charging current set by limit pin.
|
||
// Set the charging current , Range:0~5056mA ,step:64mA
|
||
PPM->setChargerConstantCurr(1024);
|
||
|
||
// To obtain voltage data, the ADC must be enabled first
|
||
PPM->enableMeasure();
|
||
|
||
// Turn on charging function
|
||
// If there is no battery connected, do not turn on the charging
|
||
// function
|
||
PPM->enableCharge();
|
||
} else {
|
||
LOG_WARN("PPM BQ25896 init failed");
|
||
delete PPM;
|
||
PPM = nullptr;
|
||
return false;
|
||
}
|
||
}
|
||
if (bq == nullptr) {
|
||
bq = new BQ27220;
|
||
bq->setDefaultCapacity(BQ27220_DESIGN_CAPACITY);
|
||
|
||
bool result = bq->init();
|
||
if (result) {
|
||
LOG_DEBUG("BQ27220 design capacity: %d", bq->getDesignCapacity());
|
||
LOG_DEBUG("BQ27220 fullCharge capacity: %d", bq->getFullChargeCapacity());
|
||
LOG_DEBUG("BQ27220 remaining capacity: %d", bq->getRemainingCapacity());
|
||
return true;
|
||
} else {
|
||
LOG_WARN("BQ27220 init failed");
|
||
delete bq;
|
||
bq = nullptr;
|
||
return false;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||
*/
|
||
virtual int getBatteryPercent() override
|
||
{
|
||
return -1;
|
||
// return bq->getChargePercent(); // don't use BQ27220 for battery percent,
|
||
// it is not calibrated
|
||
}
|
||
|
||
/**
|
||
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||
*/
|
||
virtual uint16_t getBattVoltage() override { return bq->getVoltage(); }
|
||
|
||
/**
|
||
* return true if there is a battery installed in this unit
|
||
*/
|
||
virtual bool isBatteryConnect() override { return PPM->getBattVoltage() > 0; }
|
||
|
||
/**
|
||
* return true if there is an external power source detected
|
||
*/
|
||
virtual bool isVbusIn() override { return PPM->isVbusIn(); }
|
||
|
||
/**
|
||
* return true if the battery is currently charging
|
||
*/
|
||
virtual bool isCharging() override
|
||
{
|
||
bool isCharging = PPM->isCharging();
|
||
if (isCharging) {
|
||
LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull());
|
||
} else {
|
||
if (!PPM->isVbusIn()) {
|
||
LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity());
|
||
}
|
||
}
|
||
return isCharging;
|
||
}
|
||
};
|
||
|
||
LipoCharger lipoCharger;
|
||
|
||
/**
|
||
* Init the Lipo battery charger
|
||
*/
|
||
bool Power::lipoChargerInit()
|
||
{
|
||
bool result = lipoCharger.runOnce();
|
||
LOG_DEBUG("Power::lipoChargerInit lipo sensor is %s", result ? "ready" : "not ready yet");
|
||
if (!result)
|
||
return false;
|
||
batteryLevel = &lipoCharger;
|
||
return true;
|
||
}
|
||
|
||
#else
|
||
/**
|
||
* The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel
|
||
*/
|
||
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
|
||
|
||
#ifdef HAS_SERIAL_BATTERY_LEVEL
|
||
#include <SoftwareSerial.h>
|
||
|
||
/**
|
||
* SerialBatteryLevel class for pulling battery information from a secondary MCU over serial.
|
||
*/
|
||
class SerialBatteryLevel : public HasBatteryLevel
|
||
{
|
||
|
||
public:
|
||
/**
|
||
* Init the I2C meshSolar battery level sensor
|
||
*/
|
||
bool runOnce()
|
||
{
|
||
BatterySerial.begin(4800);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||
*/
|
||
virtual int getBatteryPercent() override { return v_percent; }
|
||
|
||
/**
|
||
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||
*/
|
||
virtual uint16_t getBattVoltage() override { return voltage * 1000; }
|
||
|
||
/**
|
||
* return true if there is a battery installed in this unit
|
||
*/
|
||
virtual bool isBatteryConnect() override
|
||
{
|
||
// definitely need to gobble up more bytes at once
|
||
if (BatterySerial.available() > 5) {
|
||
// LOG_WARN("SerialBatteryLevel: %u bytes available", BatterySerial.available());
|
||
while (BatterySerial.available() > 11) {
|
||
BatterySerial.read(); // flush old data
|
||
}
|
||
// LOG_WARN("SerialBatteryLevel: %u bytes now available", BatterySerial.available());
|
||
int tries = 0;
|
||
while (BatterySerial.read() != 0xFE) {
|
||
tries++; // wait for start byte
|
||
if (tries > 10) {
|
||
LOG_WARN("SerialBatteryLevel: no start byte found");
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
Data[1] = BatterySerial.read();
|
||
Data[2] = BatterySerial.read();
|
||
Data[3] = BatterySerial.read();
|
||
Data[4] = BatterySerial.read();
|
||
Data[5] = BatterySerial.read();
|
||
if (Data[5] != 0xFD) {
|
||
LOG_WARN("SerialBatteryLevel: invalid end byte %02x", Data[5]);
|
||
return true;
|
||
}
|
||
v_percent = Data[1];
|
||
voltage = Data[2] + (((float)Data[3]) / 100) + (((float)Data[4]) / 10000);
|
||
voltage *= 2;
|
||
// LOG_WARN("SerialBatteryLevel: received data %u, %f, %02x", v_percent, voltage, Data[5]);
|
||
return true;
|
||
}
|
||
// This function runs first, so use it to grab the latest data from the secondary MCU
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* return true if there is an external power source detected
|
||
*/
|
||
virtual bool isVbusIn() override
|
||
{
|
||
#if defined(EXT_CHRG_DETECT)
|
||
|
||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||
|
||
#endif
|
||
return false;
|
||
}
|
||
|
||
virtual bool isCharging() override
|
||
{
|
||
#ifdef EXT_CHRG_DETECT
|
||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||
|
||
#endif
|
||
// by default, we check the battery voltage only
|
||
return isVbusIn();
|
||
}
|
||
|
||
private:
|
||
SoftwareSerial BatterySerial = SoftwareSerial(SERIAL_BATTERY_RX, SERIAL_BATTERY_TX);
|
||
uint8_t Data[6] = {0};
|
||
int v_percent = 0;
|
||
float voltage = 0.0;
|
||
};
|
||
|
||
SerialBatteryLevel serialBatteryLevel;
|
||
|
||
/**
|
||
* Init the serial battery level sensor
|
||
*/
|
||
bool Power::serialBatteryInit()
|
||
{
|
||
#ifdef EXT_PWR_DETECT
|
||
pinMode(EXT_PWR_DETECT, INPUT);
|
||
#endif
|
||
#ifdef EXT_CHRG_DETECT
|
||
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
|
||
#endif
|
||
|
||
bool result = serialBatteryLevel.runOnce();
|
||
LOG_DEBUG("Power::serialBatteryInit serial battery sensor is %s", result ? "ready" : "not ready yet");
|
||
if (!result)
|
||
return false;
|
||
batteryLevel = &serialBatteryLevel;
|
||
return true;
|
||
}
|
||
|
||
#else
|
||
/**
|
||
* If this device has no serial battery level sensor, don't try to use it.
|
||
*/
|
||
bool Power::serialBatteryInit()
|
||
{
|
||
return false;
|
||
}
|
||
#endif
|