2020-02-01 16:30:53 +00:00
/*
Main module
# Modified by Kyle T. Gabriel to fix issue with incorrect GPS data for TTNMapper
Copyright ( C ) 2018 by Xose Pérez < xose dot perez at gmail dot com >
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program 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 General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "configuration.h"
# include "rom/rtc.h"
2020-02-02 03:45:12 +00:00
# include <driver/rtc_io.h>
2020-02-01 16:30:53 +00:00
# include <TinyGPS++.h>
# include <Wire.h>
# include "BluetoothUtil.h"
2020-02-02 00:14:34 +00:00
# include "MeshBluetoothService.h"
2020-02-02 20:45:32 +00:00
# include "MeshService.h"
2020-02-01 16:30:53 +00:00
# ifdef T_BEAM_V10
# include "axp20x.h"
AXP20X_Class axp ;
bool pmu_irq = false ;
String baChStatus = " No charging " ;
# endif
bool ssd1306_found = false ;
bool axp192_found = false ;
bool packetSent , packetQueued ;
// deep sleep support
RTC_DATA_ATTR int bootCount = 0 ;
esp_sleep_source_t wakeCause ; // the reason we booted this time
// -----------------------------------------------------------------------------
// Application
// -----------------------------------------------------------------------------
void doDeepSleep ( uint64_t msecToWake )
{
2020-02-02 00:14:34 +00:00
Serial . printf ( " Entering deep sleep for %llu seconds \n " , msecToWake / 1000 ) ;
// not using wifi yet, but once we are this is needed to shutoff the radio hw
// esp_wifi_stop();
2020-02-02 03:45:12 +00:00
BLEDevice : : deinit ( false ) ; // We are required to shutdown bluetooth before deep or light sleep
2020-02-02 00:14:34 +00:00
screen_off ( ) ; // datasheet says this will draw only 10ua
2020-02-02 03:09:17 +00:00
// Put radio in sleep mode (will still draw power but only 0.2uA)
2020-02-02 20:45:32 +00:00
service . radio . sleep ( ) ;
2020-02-02 03:09:17 +00:00
2020-02-02 03:45:12 +00:00
# ifdef RESET_OLED
digitalWrite ( RESET_OLED , 1 ) ; // put the display in reset before killing its power
# endif
# ifdef VEXT_ENABLE
digitalWrite ( VEXT_ENABLE , 1 ) ; // turn off the display power
# endif
# ifdef LED_PIN
digitalWrite ( LED_PIN , 0 ) ; // turn off the led
# endif
2020-02-02 00:14:34 +00:00
# ifdef T_BEAM_V10
if ( axp192_found )
{
// turn on after initial testing with real hardware
2020-02-02 16:17:45 +00:00
// No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would
// leave floating input for the IRQ line
// If we want to leave the radio receving in would be 11.5mA current draw, but most of the time it is just waiting
// in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets
// all the time.
// axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio
2020-02-02 00:14:34 +00:00
axp . setPowerOutPut ( AXP192_LDO3 , AXP202_OFF ) ; // GPS main power
}
# endif
2020-02-01 16:30:53 +00:00
2020-02-02 03:45:12 +00:00
/*
Some ESP32 IOs have internal pullups or pulldowns , which are enabled by default .
If an external circuit drives this pin in deep sleep mode , current consumption may
increase due to current flowing through these pullups and pulldowns .
To isolate a pin , preventing extra current draw , call rtc_gpio_isolate ( ) function .
For example , on ESP32 - WROVER module , GPIO12 is pulled up externally .
GPIO12 also has an internal pulldown in the ESP32 chip . This means that in deep sleep ,
some current will flow through these external and internal resistors , increasing deep
sleep current above the minimal possible value .
2020-02-02 16:17:45 +00:00
Note : we don ' t isolate pins that are used for the LORA , LED , i2c , spi or the wake button
2020-02-02 03:45:12 +00:00
*/
static const uint8_t rtcGpios [ ] = { /* 0, */ 2 ,
/* 4, */ 12 , 13 , /* 14, */ /* 15, */
/* 25, */ 26 , /* 27, */
32 , 33 , 34 , 35 , 36 , 37 , /* 38, */ 39 } ;
for ( int i = 0 ; i < sizeof ( rtcGpios ) ; i + + )
rtc_gpio_isolate ( ( gpio_num_t ) rtcGpios [ i ] ) ;
2020-02-02 02:45:27 +00:00
2020-02-02 16:17:45 +00:00
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
// to detect wake and in normal operation the external part drives them hard.
2020-02-02 00:14:34 +00:00
// FIXME - use an external 10k pulldown so we can leave the RTC peripherals powered off
// until then we need the following lines
esp_sleep_pd_config ( ESP_PD_DOMAIN_RTC_PERIPH , ESP_PD_OPTION_ON ) ;
2020-02-01 16:30:53 +00:00
2020-02-01 22:23:21 +00:00
# ifdef BUTTON_PIN
2020-02-02 00:14:34 +00:00
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
uint64_t gpioMask = ( 1ULL < < BUTTON_PIN ) ;
2020-02-01 16:30:53 +00:00
2020-02-02 16:17:45 +00:00
// Not needed because both of the current boards have external pullups
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of just the first)
// gpio_pullup_en((gpio_num_t)BUTTON_PIN);
2020-02-01 16:30:53 +00:00
2020-02-02 00:14:34 +00:00
esp_sleep_enable_ext1_wakeup ( gpioMask , ESP_EXT1_WAKEUP_ALL_LOW ) ;
2020-02-01 22:23:21 +00:00
# endif
2020-02-01 16:30:53 +00:00
2020-02-02 00:14:34 +00:00
esp_sleep_enable_timer_wakeup ( msecToWake * 1000ULL ) ; // call expects usecs
esp_deep_sleep_start ( ) ; // TBD mA sleep current (battery)
2020-02-01 16:30:53 +00:00
}
2020-02-02 00:14:34 +00:00
void sleep ( )
{
2020-02-02 02:45:27 +00:00
# ifdef SLEEP_MSECS
2020-02-01 16:30:53 +00:00
// If the user has a screen, tell them we are about to sleep
2020-02-02 00:14:34 +00:00
if ( ssd1306_found )
{
2020-02-01 16:30:53 +00:00
// Show the going to sleep message on the screen
char buffer [ 20 ] ;
snprintf ( buffer , sizeof ( buffer ) , " Sleeping in %3.1fs \n " , ( MESSAGE_TO_SLEEP_DELAY / 1000.0 ) ) ;
screen_print ( buffer ) ;
// Wait for MESSAGE_TO_SLEEP_DELAY millis to sleep
delay ( MESSAGE_TO_SLEEP_DELAY ) ;
2020-02-02 00:14:34 +00:00
}
2020-02-01 16:30:53 +00:00
// We sleep for the interval between messages minus the current millis
// this way we distribute the messages evenly every SEND_INTERVAL millis
2020-02-02 02:45:27 +00:00
doDeepSleep ( SLEEP_MSECS ) ;
2020-02-01 16:30:53 +00:00
# endif
}
void scanI2Cdevice ( void )
{
2020-02-02 00:14:34 +00:00
byte err , addr ;
int nDevices = 0 ;
for ( addr = 1 ; addr < 127 ; addr + + )
{
Wire . beginTransmission ( addr ) ;
err = Wire . endTransmission ( ) ;
if ( err = = 0 )
{
Serial . print ( " I2C device found at address 0x " ) ;
if ( addr < 16 )
Serial . print ( " 0 " ) ;
Serial . print ( addr , HEX ) ;
Serial . println ( " ! " ) ;
nDevices + + ;
if ( addr = = SSD1306_ADDRESS )
{
ssd1306_found = true ;
Serial . println ( " ssd1306 display found " ) ;
}
# ifdef T_BEAM_V10
if ( addr = = AXP192_SLAVE_ADDRESS )
{
axp192_found = true ;
Serial . println ( " axp192 PMU found " ) ;
}
# endif
2020-02-01 16:30:53 +00:00
}
2020-02-02 00:14:34 +00:00
else if ( err = = 4 )
{
Serial . print ( " Unknow error at address 0x " ) ;
if ( addr < 16 )
Serial . print ( " 0 " ) ;
Serial . println ( addr , HEX ) ;
}
}
if ( nDevices = = 0 )
Serial . println ( " No I2C devices found \n " ) ;
else
Serial . println ( " done \n " ) ;
2020-02-01 16:30:53 +00:00
}
/**
* Init the power manager chip
*
* axp192 power
DCDC1 0.7 - 3.5 V @ 1200 mA 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.5 V @ 700 mA max - > ESP32 ( keep this on ! )
LDO1 30 mA - > 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 200 mA - > LORA
LDO3 200 mA - > GPS
*/
2020-02-02 00:14:34 +00:00
void axp192Init ( )
{
# ifdef T_BEAM_V10
if ( axp192_found )
{
if ( ! axp . begin ( Wire , AXP192_SLAVE_ADDRESS ) )
{
Serial . println ( " AXP192 Begin PASS " ) ;
}
else
{
Serial . println ( " AXP192 Begin FAIL " ) ;
}
// axp.setChgLEDMode(LED_BLINK_4HZ);
Serial . printf ( " DCDC1: %s \n " , axp . isDCDC1Enable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . printf ( " DCDC2: %s \n " , axp . isDCDC2Enable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . printf ( " LDO2: %s \n " , axp . isLDO2Enable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . printf ( " LDO3: %s \n " , axp . isLDO3Enable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . printf ( " DCDC3: %s \n " , axp . isDCDC3Enable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . printf ( " Exten: %s \n " , axp . isExtenEnable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . println ( " ---------------------------------------- " ) ;
axp . setPowerOutPut ( AXP192_LDO2 , AXP202_ON ) ; // LORA radio
axp . setPowerOutPut ( AXP192_LDO3 , AXP202_ON ) ; // GPS main power
axp . setPowerOutPut ( AXP192_DCDC2 , AXP202_ON ) ;
axp . setPowerOutPut ( AXP192_EXTEN , AXP202_ON ) ;
axp . setPowerOutPut ( AXP192_DCDC1 , AXP202_ON ) ;
axp . setDCDC1Voltage ( 3300 ) ; // for the OLED power
Serial . printf ( " DCDC1: %s \n " , axp . isDCDC1Enable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . printf ( " DCDC2: %s \n " , axp . isDCDC2Enable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . printf ( " LDO2: %s \n " , axp . isLDO2Enable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . printf ( " LDO3: %s \n " , axp . isLDO3Enable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . printf ( " DCDC3: %s \n " , axp . isDCDC3Enable ( ) ? " ENABLE " : " DISABLE " ) ;
Serial . printf ( " Exten: %s \n " , axp . isExtenEnable ( ) ? " ENABLE " : " DISABLE " ) ;
pinMode ( PMU_IRQ , INPUT_PULLUP ) ;
attachInterrupt ( PMU_IRQ , [ ] {
pmu_irq = true ;
} ,
FALLING ) ;
axp . adc1Enable ( AXP202_BATT_CUR_ADC1 , 1 ) ;
axp . enableIRQ ( AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ , 1 ) ;
axp . clearIRQ ( ) ;
if ( axp . isChargeing ( ) )
{
baChStatus = " Charging " ;
2020-02-01 16:30:53 +00:00
}
2020-02-02 00:14:34 +00:00
}
else
{
Serial . println ( " AXP192 not found " ) ;
}
# endif
2020-02-01 16:30:53 +00:00
}
// Perform power on init that we do on each wake from deep sleep
2020-02-02 00:14:34 +00:00
void initDeepSleep ( )
{
bootCount + + ;
wakeCause = esp_sleep_get_wakeup_cause ( ) ;
/*
2020-02-01 16:30:53 +00:00
Not using yet because we are using wake on all buttons being low
wakeButtons = esp_sleep_get_ext1_wakeup_status ( ) ; // If one of these buttons is set it was the reason we woke
if ( wakeCause = = ESP_SLEEP_WAKEUP_EXT1 & & ! wakeButtons ) // we must have been using the 'all buttons rule for waking' to support busted boards, assume button one was pressed
wakeButtons = ( ( uint64_t ) 1 ) < < buttons . gpios [ 0 ] ;
*/
2020-02-02 00:14:34 +00:00
Serial . printf ( " booted, wake cause %d (boot count %d) \n " , wakeCause , bootCount ) ;
2020-02-01 16:30:53 +00:00
}
2020-02-02 00:14:34 +00:00
void setup ( )
{
// Debug
# ifdef DEBUG_PORT
2020-02-01 16:30:53 +00:00
DEBUG_PORT . begin ( SERIAL_BAUD ) ;
2020-02-02 00:14:34 +00:00
# endif
2020-02-01 16:30:53 +00:00
initDeepSleep ( ) ;
// delay(1000); FIXME - remove
2020-02-01 22:23:21 +00:00
# ifdef VEXT_ENABLE
pinMode ( VEXT_ENABLE , OUTPUT ) ;
digitalWrite ( VEXT_ENABLE , 0 ) ; // turn on the display power
2020-02-02 00:14:34 +00:00
# endif
2020-02-01 22:23:21 +00:00
# ifdef RESET_OLED
pinMode ( RESET_OLED , OUTPUT ) ;
digitalWrite ( RESET_OLED , 1 ) ;
2020-02-02 00:14:34 +00:00
# endif
2020-02-01 22:23:21 +00:00
2020-02-01 16:30:53 +00:00
Wire . begin ( I2C_SDA , I2C_SCL ) ;
scanI2Cdevice ( ) ;
axp192Init ( ) ;
// Buttons & LED
2020-02-01 22:23:21 +00:00
# ifdef BUTTON_PIN
2020-02-01 16:30:53 +00:00
pinMode ( BUTTON_PIN , INPUT_PULLUP ) ;
2020-02-01 22:23:21 +00:00
digitalWrite ( BUTTON_PIN , 1 ) ;
# endif
2020-02-01 16:30:53 +00:00
# ifdef LED_PIN
pinMode ( LED_PIN , OUTPUT ) ;
2020-02-02 00:05:12 +00:00
digitalWrite ( LED_PIN , 1 ) ; // turn on for now
2020-02-01 16:30:53 +00:00
# endif
// Hello
DEBUG_MSG ( APP_NAME " " APP_VERSION " \n " ) ;
// Don't init display if we don't have one or we are waking headless due to a timer event
2020-02-02 00:14:34 +00:00
if ( wakeCause = = ESP_SLEEP_WAKEUP_TIMER )
2020-02-01 16:30:53 +00:00
ssd1306_found = false ; // forget we even have the hardware
2020-02-02 00:14:34 +00:00
if ( ssd1306_found )
2020-02-01 16:30:53 +00:00
screen_setup ( ) ;
// Init GPS
gps_setup ( ) ;
// Show logo on first boot after removing battery
2020-02-02 00:05:12 +00:00
//if (bootCount == 0) {
2020-02-02 00:14:34 +00:00
screen_print ( APP_NAME " " APP_VERSION , 0 , 0 ) ;
screen_show_logo ( ) ;
screen_update ( ) ;
delay ( LOGO_DELAY ) ;
2020-02-02 00:05:12 +00:00
//}
2020-02-01 16:30:53 +00:00
2020-02-02 20:45:32 +00:00
service . init ( ) ;
2020-02-02 00:14:34 +00:00
BLEServer * serve = initBLE ( " KHBT Test " ) ; // FIXME, use a real name based on the macaddr
BLEService * bts = createMeshBluetoothService ( serve ) ;
bts - > start ( ) ;
serve - > getAdvertising ( ) - > addServiceUUID ( bts - > getUUID ( ) ) ;
2020-02-01 16:30:53 +00:00
}
2020-02-02 00:14:34 +00:00
void loop ( )
{
2020-02-01 16:30:53 +00:00
gps_loop ( ) ;
screen_loop ( ) ;
2020-02-02 20:45:32 +00:00
service . loop ( ) ;
2020-02-01 16:30:53 +00:00
loopBLE ( ) ;
2020-02-02 02:45:27 +00:00
# ifdef LED_PIN
// toggle the led so we can get some rough sense of how often loop is pausing
digitalWrite ( LED_PIN , digitalRead ( LED_PIN ) ? 0 : 1 ) ;
2020-02-02 03:09:17 +00:00
# endif
2020-02-01 16:30:53 +00:00
2020-02-01 22:23:21 +00:00
# ifdef BUTTON_PIN
2020-02-01 16:30:53 +00:00
// if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of this boilerplate)
static bool wasPressed = false ;
static uint32_t minPressMs ; // what tick should we call this press long enough
2020-02-02 00:14:34 +00:00
if ( ! digitalRead ( BUTTON_PIN ) )
{
if ( ! wasPressed )
{ // just started a new press
2020-02-01 16:30:53 +00:00
Serial . println ( " pressing " ) ;
wasPressed = true ;
minPressMs = millis ( ) + 3000 ;
2020-02-02 00:14:34 +00:00
}
}
else if ( wasPressed )
{
2020-02-01 16:30:53 +00:00
// we just did a release
wasPressed = false ;
2020-02-02 00:14:34 +00:00
if ( millis ( ) > minPressMs )
{
2020-02-01 16:30:53 +00:00
// held long enough
screen_print ( " Erasing prefs " ) ;
delay ( 5000 ) ; // Give some time to read the screen
// ESP.restart();
}
}
2020-02-01 22:23:21 +00:00
# endif
2020-02-01 16:30:53 +00:00
2020-02-02 02:45:27 +00:00
# ifdef MINWAKE_MSECS
2020-02-02 03:09:17 +00:00
if ( millis ( ) > MINWAKE_MSECS )
{
sleep ( ) ;
}
2020-02-01 16:30:53 +00:00
# endif
2020-02-02 03:09:17 +00:00
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
// i.e. don't just keep spinning in loop as fast as we can.
delay ( 100 ) ;
2020-02-01 16:30:53 +00:00
}