If you want to be able to use the TTGO T-Watch as a real connected watch, you have to integrate the code necessary for automatic (or manual) standby in the project. In addition to the ESP32’s standby functions (Deep Sleep and Light Sleep for example), the BMA423 accelerometer can be used to detect movement to turn on the screen. You can also use the main button which is connected to the T-Watch AXP202 power controller to turn the screen off or on.
As the T-Watch is above all an ESP32 development board, we can use all the methods already detailed in this article to put it to sleep (deep or light).
The source code used for this article was developed by Lewis He, the lead developer of the LilyGoWatch library at LilyGo. It is available under the name SimpleWatch in the examples folder of the LVGL library.
Reminder on the ESP32 standby
The ESP32 has 5 operating modes which directly affect the battery life. Obviously, we must not forget the on-board peripherals (accelerometer, touch screen …) which also consume.
Active Mode |
||||||
GPIO | Wireless | Bluetooth | CPU | ULP | RTC | Consumption* |
95 ~ 240mA |
Modem Sleep | ||||||
20 ~ 68 mA |
Light Sleep | ||||||
0.8 mA | ||||||
Core paused |
Deep Sleep | ||||||
10 ~ 150 μA | ||||||
RTC only |
Hibernation |
||||||
1 μA | ||||||
RTC only | x1 |
Functions available depending on the type of sleep activated
Before choosing the standby mode for your watch or connected object project, here is a summary table that allows you to verify that a method allows you to wake up the processor according to the type of standby activated.
Method
Modem-Sleep
Light-Sleep
Deep-Sleep
Hibernation
Timer
esp_sleep_enable_timer_wakeup()
✓
✓
✓
✓
Touch
esp_sleep_enable_touchpad_wakeup()
✓
✓
✓
Ext0 and Ext1
esp_sleep_enable_ext0_wakeup()
esp_sleep_enable_ext1_wakeup()✓
✓
✓
GPIO
esp_sleep_enable_gpio_wakeup()
✓
✓
UART
esp_sleep_enable_uart_wakeup()
✓
✓
ULP
esp_sleep_enable_ulp_wakeup()
✓
✓
✓
Automatically put the screen to sleep after a few seconds of inactivity
The lv_disp_get_inactive_time method of the LVGL library allows you to know at any time the duration for how long the screen has not been touched. It suffices to regularly compare the inactivity time with a setpoint to trigger the execution of a function which puts the watch accessories on standby.
if (lv_disp_get_inactive_time(NULL) < DEFAULT_SCREEN_TIMEOUT) {
lv_task_handler();
} else {
low_energy();
}
The isOn() method of the bl class (Backlight or backlight) lets you know if the watch is on. Here is an example of a function that puts watch devices to sleep
watch->closeBL(); // switch off backlight
watch->displaySleep(); // switch off screen
watch->stopLvglTick(); // Pause LVGL handler
You can then turn off the WiFi connectivity
WiFi.mode(WIFI_OFF);
Light standby of the ESP32
The light standby allows the watch to be reactivated instantly. To gain even more autonomy, it is possible to reduce the frequency of the CPU. Here, for example, we lower the CPU frequency of the ESP32 to 20MHz (instead of 240MHz at most).
setCpuFrequencyMhz(20);
Now we can send the ESP32 to a nap
esp_light_sleep_start();
Wake up watch devices
We will obviously want to wake up the watch. Several solutions are possible on T-Watch:
- Use the main button which is connected to the AXP202 power controller
- Use the user button available on some models
- Detect movement or tap on the screen using the BMA423 accelerometer.
All of these methods generate a digital signal that can be retrieved from the GPIO of the ESP32.
Here is how for example to wake up the watch with the main button
gpio_wakeup_enable ((gpio_num_t)AXP202_INT, GPIO_INTR_LOW_LEVEL);
esp_sleep_enable_gpio_wakeup();
esp_light_sleep_start();
As soon as the watch is reactivated, it will suffice to restart the following minimum services
watch->displayWakeup(); // switch on light
watch->openBL(); // Backlight
watch->startLvglTick(); // LVGL handler
lv_disp_trig_activity(NULL);
Wake up the T-Watch with the BMA423 accelerometer or the main button
The Core PCB (motherboard) has a BMA423 accelerometer that sends a signal when movement is detected. This signal is retrieved by the LilyGoWatch library which provides a very complete API to manage actions.
We will thus be able to turn on the screen when we raise our arm or when we tap the screen. Handy for a connected watch!
The source code of the library adapted for the T-Watch is here. The original version of Bosch SensorTech here.
The accelerometer API is accessible via the sensor class.
BMA *sensor;
The interrupt signal from the BMA423 is connected to pin 39 of the ESP32 regardless of the T-Watch model.
It is not necessary to know the pin using the constant BMA423_INT1 . The first thing to do is to recover the interrupt signal generated by the BMA 423
Lewis Le advises us to create a real-time task of FreeRTOS to wake up the watch more quickly. Here is how it works.
The first thing to do is therefore to declare the interruptions which will allow the ESP32 to wake up from its sleep. Here, we create two interrupts. The first will allow you to wake up the ESP32 using the main button connected to the AXP202. The second using the interrupt signal sent by the accelerometer as soon as movement is detected.
setCpuFrequencyMhz(20);
gpio_wakeup_enable ((gpio_num_t)AXP202_INT, GPIO_INTR_LOW_LEVEL);
gpio_wakeup_enable ((gpio_num_t)BMA423_INT1, GPIO_INTR_LOW_LEVEL);
esp_sleep_enable_gpio_wakeup();
esp_light_sleep_start();
A function must then be defined for each interrupt which will be executed when a wake-up signal is received. Here a message is sent to the FreeRTOS real-time task manager as soon as a tap on the screen is detected.
pinMode(BMA423_INT1, INPUT);
attachInterrupt(BMA423_INT1, [] {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
EventBits_t bits = xEventGroupGetBitsFromISR(isr_group);
if (bits & WATCH_FLAG_SLEEP_MODE)
{
//! For quick wake up, use the group flag
xEventGroupSetBitsFromISR(isr_group, WATCH_FLAG_SLEEP_EXIT | WATCH_FLAG_BMA_IRQ, &xHigherPriorityTaskWoken);
} else
{
uint8_t data = Q_EVENT_BMA_INT;
xQueueSendFromISR(g_event_queue_handle, &data, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken)
{
portYIELD_FROM_ISR ();
}
}, RISING);
Then all that remains is to configure and start the accelerometer. Here is at least what to do
sensor = watch->bma;
sensor->enableAccel(); // Activate accelerometer
sensor->enableFeature(BMA423_WAKEUP, true); // Activate wake-up function
sensor->enableWakeupInterrupt(); // wake-up interrupt signal
We can then finely manage the settings but this is not necessary to turn on the T-Watch screen.
The xEventGroup letterbox is placed in the loop() . We get the new message using the xEventGroupGetBits() method . We increase the CPU frequency of the ESP32 to 160MHz (up to 240MHz but beware of the impact on battery life).
Remember to delete the xEventGroupClearBits message , otherwise it will not be possible to wake up the watch next time.
bool rlst;
uint8_t data;
// Traite l'interruption du BMA 423
EventBits_t bits = xEventGroupGetBits(isr_group);
if (bits & WATCH_FLAG_SLEEP_EXIT) {
Serial.println("EXIT SLEEP MODE");
if (lenergy) {
lenergy = false;
setCpuFrequencyMhz(160); // Augmente la fréquence du CPU de l'ESP32
}
low_energy(); // Réveille l'écran et les accessoires, actualise l'écran
// Détruit le message WATCH_FLAG_BMA_IRQ (réveil avec le BMA 423)
// sinon il ne sera pas possible de réveiller la prochaine fois
if (bits & WATCH_FLAG_BMA_IRQ) {
Serial.printf("WATCH_FLAG_BMA_IRQ bits=%u\n", bits);
do {
rlst = watch->bma->readInterrupt();
} while (!rlst);
xEventGroupClearBits(isr_group, WATCH_FLAG_BMA_IRQ);
}
}
Deep sleep. Alarm clock by timer or main button
The T-Watch Touch is a fantastic ESP32 development platform that can be supplemented with numerous expansion cards.
- Basic Expansion Board (supplied with each T-Watch) , 2 x 8-pin expansion connectors providing access to pins 33, 34, 21 (SDA), 22 (SCL) of ESP32 and IO0, IO1, IO2 and IO3 of the AXP202 (power manager). This is the card delivered as standard
- T-Fork connector for breadboard
- GPS and M8N blox
- Game , joystick and 4 buttons to transform the T-Watch into a Gameboy!
- Motor & Speaker (Pack H329), vibrator + speaker
- MPR121, external touch interface
- SIM800L, modem GPRS
- MP3 , MP3 player
- NFC , contactless reader (note, this is not an RFID reader)
- T-Car , can drive up to 3 servo motors via the 1-Wire bus
- T-Quick , can control up to 2 motors in I2C
- MAX98357 (Pack H328), I2S audio output
- S76G Lora + GPS (Pack H327)
- S78G Lora (433 to 470 MHz) + GPS (Pack H397). LoRa modem
See more expansion boards for the T-Watch Touch or No-Touch
We can very easily develop a temperature or pollutant recorder, a GPS tracker, monitor access and send an SMS in the event of an intrusion … All these applications that can run on the internal LiPo battery do not require the ESP32. is permanently active.
Ask the AXP202 power manager to turn off devices
All watch peripherals are powered by the AXP202 microcontroller (including the ESP32).
Before putting the watch on standby, we will therefore already have to stop the peripherals otherwise they will continue to function and drain the battery, which is not really what we are looking for here!
There are two ways to do this depending on the model of T-Watch being used.
- For the T-Watch 2020, just call the powerOff()
function. This method only deactivates the LDO2 output - For other models, you must manually stop each output by calling the setPowerOutPut () function of the power class . Here is the list of outputs used.
Here is the list of the AXP202 outputs used by the T-Watch Core PCBs.
On T-Watch 2020 run powerOff () For other T- Watches run displaySleep ()
AXP202 output
T-Watch (other versions)
T-Watch 2020 V1
T-Watch 2020 V2
DC2
Not used
DC3
ESP32 power supply. Do not deactivate !!
LDO1
Not available
LDO2
Backlight, TFT screen backlight
LDO3
Backplane power supply (expansion card)
TFT / TOUCHT screen
LDO4
S76 / 78G expansion card power supply (Lora + GPS) only
Not used
GPS module
EXTEN
Not used
Not used
TOUCH RESET EN
Here is an example that allows you to manually turn off devices
power->setPowerOutPut(AXP202_LDO3, false);
power->setPowerOutPut(AXP202_LDO4, false);
power->setPowerOutPut(AXP202_LDO2, false);
// Optionnel (non utilisé)
power->setPowerOutPut(AXP202_EXTEN, false);
power->setPowerOutPut(AXP202_DCDC2, false);
Program the alarm clock with the main button or a Timer
To put the ESP32 into Deep Sleep, all you have to do is provide exit conditions.
Here, for example, the alarm clock is programmed when the main button is pressed on input ext0.
esp_sleep_enable_ext0_wakeup((gpio_num_t)AXP202_INT, LOW);
Or by a Timer which will regularly wake up the ESP32. Useful for regularly recording a GPS position, temperature, CO2 content …
esp_sleep_enable_timer_wakeup(uS_TO_S_FACTOR * TIME_TO_SLEEP);
All that remains is to put the ESP32 to sleep
To go further with the Deep Sleep mode of the ESP32, you can continue by reading this article
Full source code of the example
Here is the full Arduino source code for the example which you can also grab directly from GitHub . The project can be compiled using the Arduino IDE or PlatformIO.
The source code can be used as a basis for the development of your own connected watch project.
/* Created by Lewis he on October 10, 2019. */
#include <Arduino.h>
#include <LilyGoWatch.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/queue.h"
#include <soc/rtc.h>
#include "esp_wifi.h"
#include "esp_sleep.h"
#include <WiFi.h>
#include "gui.h"
#define G_EVENT_VBUS_PLUGIN _BV(0)
#define G_EVENT_VBUS_REMOVE _BV(1)
#define G_EVENT_CHARGE_DONE _BV(2)
#define G_EVENT_WIFI_SCAN_START _BV(3)
#define G_EVENT_WIFI_SCAN_DONE _BV(4)
#define G_EVENT_WIFI_CONNECTED _BV(5)
#define G_EVENT_WIFI_BEGIN _BV(6)
#define G_EVENT_WIFI_OFF _BV(7)
enum {
Q_EVENT_WIFI_SCAN_DONE,
Q_EVENT_WIFI_CONNECT,
Q_EVENT_BMA_INT,
Q_EVENT_AXP_INT,
} ;
#define DEFAULT_SCREEN_TIMEOUT 5*1000
#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 20 /* Time ESP32 will go to sleep (in seconds) */
#define WATCH_FLAG_SLEEP_MODE _BV(1)
#define WATCH_FLAG_SLEEP_EXIT _BV(2)
#define WATCH_FLAG_BMA_IRQ _BV(3)
#define WATCH_FLAG_AXP_IRQ _BV(4)
/*******************************************/
/* Pointers */
/*******************************************/
TTGOClass *watch;
BMA *sensor;
AXP20X_Class *power;
TFT_eSPI *tft = nullptr;
QueueHandle_t g_event_queue_handle = NULL;
EventGroupHandle_t g_event_group = NULL;
EventGroupHandle_t isr_group = NULL;
bool lenergy = false;
/*******************************************/
/* PROTOTYPES */
/*******************************************/
void print_wakeup_reason();
void buttonClicked();
void setupNetwork()
{
WiFi.mode(WIFI_STA);
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) {
xEventGroupClearBits(g_event_group, G_EVENT_WIFI_CONNECTED);
}, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) {
uint8_t data = Q_EVENT_WIFI_SCAN_DONE;
xQueueSend(g_event_queue_handle, &data, portMAX_DELAY);
}, WiFiEvent_t::SYSTEM_EVENT_SCAN_DONE);
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) {
xEventGroupSetBits(g_event_group, G_EVENT_WIFI_CONNECTED);
}, WiFiEvent_t::SYSTEM_EVENT_STA_CONNECTED);
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) {
//wifi_connect_status(true);
}, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
}
/*******************************************/
/* Switch to low consumption mode */
/*******************************************/
void low_energy()
{
// Backlight is ON
if (watch->bl->isOn()) {
xEventGroupSetBits(isr_group, WATCH_FLAG_SLEEP_MODE);
watch->closeBL();
watch->bma->enableStepCountInterrupt(false);
watch->displaySleep();
watch->stopLvglTick();
if (!WiFi.isConnected()) {
lenergy = true;
WiFi.mode(WIFI_OFF);
// Decrease CPU frequency to 20MHz to reduce consumption
setCpuFrequencyMhz(20);
Serial.println("ENTER IN LIGHT SLEEEP MODE");
delay(50);
// Alarm clock with the home button
gpio_wakeup_enable ((gpio_num_t)AXP202_INT, GPIO_INTR_LOW_LEVEL);
gpio_wakeup_enable ((gpio_num_t)BMA423_INT1, GPIO_INTR_LOW_LEVEL);
esp_sleep_enable_gpio_wakeup();
esp_light_sleep_start();
}
// The backlight is off
} else {
watch->displayWakeup();
watch->openBL();
watch->startLvglTick();
lv_disp_trig_activity(NULL);
updateStepCounter(watch->bma->getCounter());
updateBatteryLevel();
updateBatteryIcon(LV_ICON_CALCULATION);
watch->rtc->syncToSystem();
watch->bma->enableStepCountInterrupt();
}
}
void setup()
{
Serial.begin(115200);
print_wakeup_reason();
//Create a program that allows the required message objects and group flags
g_event_queue_handle = xQueueCreate(20, sizeof(uint8_t));
g_event_group = xEventGroupCreate();
isr_group = xEventGroupCreate();
watch = TTGOClass::getWatch();
tft = watch->tft;
watch->begin();
// Records pointers
power = watch->power;
// User button handler
watch->button->setClickHandler(buttonClicked);
// Turn on the IRQ used
watch->power->adc1Enable(AXP202_BATT_VOL_ADC1 | AXP202_BATT_CUR_ADC1 | AXP202_VBUS_VOL_ADC1 | AXP202_VBUS_CUR_ADC1, AXP202_ON);
watch->power->enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ, AXP202_ON);
watch->power->clearIRQ();
// Turn off unused power
watch->power->setPowerOutPut(AXP202_EXTEN, AXP202_OFF);
watch->power->setPowerOutPut(AXP202_DCDC2, AXP202_OFF);
watch->power->setPowerOutPut(AXP202_LDO3, AXP202_OFF);
watch->power->setPowerOutPut(AXP202_LDO4, AXP202_OFF);
//Connection interrupted to the specified pin
pinMode(BMA423_INT1, INPUT);
attachInterrupt(BMA423_INT1, [] {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
EventBits_t bits = xEventGroupGetBitsFromISR(isr_group);
if (bits & WATCH_FLAG_SLEEP_MODE)
{
//! For quick wake up, use the group flag
xEventGroupSetBitsFromISR(isr_group, WATCH_FLAG_SLEEP_EXIT | WATCH_FLAG_BMA_IRQ, &xHigherPriorityTaskWoken);
} else
{
uint8_t data = Q_EVENT_BMA_INT;
xQueueSendFromISR(g_event_queue_handle, &data, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken)
{
portYIELD_FROM_ISR ();
}
}, RISING);
sensor = watch->bma;
// Optional
// // Accel parameter structure
// Acfg cfg;
// /*!
// Output data rate in Hz, Optional parameters:
// - BMA4_OUTPUT_DATA_RATE_0_78HZ
// - BMA4_OUTPUT_DATA_RATE_1_56HZ
// - BMA4_OUTPUT_DATA_RATE_3_12HZ
// - BMA4_OUTPUT_DATA_RATE_6_25HZ
// - BMA4_OUTPUT_DATA_RATE_12_5HZ
// - BMA4_OUTPUT_DATA_RATE_25HZ
// - BMA4_OUTPUT_DATA_RATE_50HZ
// - BMA4_OUTPUT_DATA_RATE_100HZ
// - BMA4_OUTPUT_DATA_RATE_200HZ
// - BMA4_OUTPUT_DATA_RATE_400HZ
// - BMA4_OUTPUT_DATA_RATE_800HZ
// - BMA4_OUTPUT_DATA_RATE_1600HZ
// */
// cfg.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
// /*!
// G-range, Optional parameters:
// - BMA4_ACCEL_RANGE_2G
// - BMA4_ACCEL_RANGE_4G
// - BMA4_ACCEL_RANGE_8G
// - BMA4_ACCEL_RANGE_16G
// */
// cfg.range = BMA4_ACCEL_RANGE_4G;
// /*!
// Bandwidth parameter, determines filter configuration, Optional parameters:
// - BMA4_ACCEL_OSR4_AVG1
// - BMA4_ACCEL_OSR2_AVG2
// - BMA4_ACCEL_NORMAL_AVG4
// - BMA4_ACCEL_CIC_AVG8
// - BMA4_ACCEL_RES_AVG16
// - BMA4_ACCEL_RES_AVG32
// - BMA4_ACCEL_RES_AVG64
// - BMA4_ACCEL_RES_AVG128
// */
// cfg.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
//
// /*! Filter performance mode , Optional parameters:
// - BMA4_CIC_AVG_MODE
// - BMA4_CONTINUOUS_MODE
// */
// cfg.perf_mode = BMA4_CONTINUOUS_MODE;
//
// // Configure the BMA423 accelerometer
// sensor->accelConfig(cfg);
//
// Enable BMA423 accelerometer
sensor->enableAccel();
// Enable BMA423 isDoubleClick feature
sensor->enableFeature(BMA423_WAKEUP, true);
sensor->enableWakeupInterrupt();
pinMode(AXP202_INT, INPUT);
attachInterrupt(AXP202_INT, [] {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
EventBits_t bits = xEventGroupGetBitsFromISR(isr_group);
if (bits & WATCH_FLAG_SLEEP_MODE)
{
//! For quick wake up, use the group flag
xEventGroupSetBitsFromISR(isr_group, WATCH_FLAG_SLEEP_EXIT | WATCH_FLAG_AXP_IRQ, &xHigherPriorityTaskWoken);
} else
{
uint8_t data = Q_EVENT_AXP_INT;
xQueueSendFromISR(g_event_queue_handle, &data, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken)
{
portYIELD_FROM_ISR ();
}
}, FALLING);
//Check if the RTC clock matches, if not, use compile time
watch->rtc->check();
//Synchronize time to system time
watch->rtc->syncToSystem();
#ifdef LILYGO_WATCH_HAS_BUTTON
//Set the user button long press to restart
watch->button->setLongClickHandler([]() {
Serial.println("Pressed Restart Button,Restart now ...");
delay(1000);
esp_restart();
});
#endif
//Setting up the network
setupNetwork();
//Initialize lvgl
watch->lvgl_begin();
//Execute your own GUI interface
setupGui();
//Clear lvgl counter
lv_disp_trig_activity(NULL);
#ifdef LILYGO_WATCH_HAS_BUTTON
//In lvgl we call the button processing regularly
lv_task_create([](lv_task_t *args) {
watch->button->loop();
}, 30, 1, nullptr);
#endif
//When the initialization is complete, turn on the backlight
watch->openBL();
}
void loop()
{
bool rlst;
uint8_t data;
//! Fast response wake-up interrupt
EventBits_t bits = xEventGroupGetBits(isr_group);
if (bits & WATCH_FLAG_SLEEP_EXIT) {
Serial.println("EXIT SLEEP MODE");
if (lenergy) {
lenergy = false;
setCpuFrequencyMhz(160);
}
low_energy();
// Destroys the WATCH_FLAG_BMA_IRQ message (wake up with the BMA 423)
if (bits & WATCH_FLAG_BMA_IRQ) {
Serial.printf("WATCH_FLAG_BMA_IRQ bits=%u\n", bits);
do {
rlst = watch->bma->readInterrupt();
} while (!rlst);
xEventGroupClearBits(isr_group, WATCH_FLAG_BMA_IRQ);
}
if (bits & WATCH_FLAG_AXP_IRQ) {
Serial.printf("WATCH_FLAG_AXP_IRQ bits=%u\n", bits);
watch->power->readIRQ();
watch->power->clearIRQ();
//TODO: Only accept axp power pek key short press
xEventGroupClearBits(isr_group, WATCH_FLAG_AXP_IRQ);
}
xEventGroupClearBits(isr_group, WATCH_FLAG_SLEEP_EXIT);
xEventGroupClearBits(isr_group, WATCH_FLAG_SLEEP_MODE);
}
if ((bits & WATCH_FLAG_SLEEP_MODE)) {
//! No event processing after entering the information screen
return;
}
//! Normal polling
if (xQueueReceive(g_event_queue_handle, &data, 5 / portTICK_RATE_MS) == pdPASS) {
switch (data) {
case Q_EVENT_BMA_INT:
do {
rlst = watch->bma->readInterrupt();
} while (!rlst);
break;
case Q_EVENT_AXP_INT:
watch->power->readIRQ();
if (watch->power->isPEKShortPressIRQ()) {
// Switch to low consumption mode when the main button is pressed
watch->power->clearIRQ();
low_energy();
return;
}
watch->power->clearIRQ();
break;
case Q_EVENT_WIFI_SCAN_DONE: {
int16_t len = WiFi.scanComplete();
for (int i = 0; i < len; ++i) {
wifi_list_add(WiFi.SSID(i).c_str());
}
break;
}
default:
break;
}
}
if (lv_disp_get_inactive_time(NULL) < DEFAULT_SCREEN_TIMEOUT) {
lv_task_handler();
} else {
low_energy();
}
}
void buttonClicked(){
Serial.println("User button clicked, enter in deep sleep mode");
// Set screen and touch to sleep mode
watch->displaySleep();
/*
When using T - Watch2020V1, you can directly call power->powerOff(),
if you use the 2019 version of TWatch, choose to turn off
according to the power you need to turn off
*/
#ifdef LILYGO_WATCH_2020_V1
watch->powerOff();
// LDO2 is used to power the display, and LDO2 can be turned off if needed
// power->setPowerOutPut(AXP202_LDO2, false);
#else
power->setPowerOutPut(AXP202_LDO3, false);
power->setPowerOutPut(AXP202_LDO4, false);
power->setPowerOutPut(AXP202_LDO2, false);
// The following power channels are not used
power->setPowerOutPut(AXP202_EXTEN, false);
power->setPowerOutPut(AXP202_DCDC2, false);
#endif
esp_sleep_enable_ext0_wakeup((gpio_num_t)AXP202_INT, LOW);
// Activate Timer Wakeup. Usefull for a GPS trcker for example
esp_sleep_enable_timer_wakeup(uS_TO_S_FACTOR * TIME_TO_SLEEP);
esp_deep_sleep_start();
}
//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch(wakeup_reason)
{
case 1 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
case 2 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
case 3 : Serial.println("Wakeup caused by touchpad"); break;
case 4 : Serial.println("Wakeup caused by timer"); break;
case 5 : Serial.println("Wakeup caused by ULP program"); break;
default : Serial.println("Wakeup was not caused by deep sleep"); break;
}
}
/*
Copyright (c) 2019 lewis he
This is just a demonstration. Most of the functions are not implemented.
The main implementation is low-power standby.
The off-screen standby (not deep sleep) current is about 4mA.
Select standard motherboard and standard backplane for testing.
Created by Lewis he on October 10, 2019.
*/
#ifndef __GUI_H
#define __GUI_H
typedef enum {
LV_ICON_BAT_EMPTY,
LV_ICON_BAT_1,
LV_ICON_BAT_2,
LV_ICON_BAT_3,
LV_ICON_BAT_FULL,
LV_ICON_CHARGE,
LV_ICON_CALCULATION
} lv_icon_battery_t;
typedef enum {
LV_STATUS_BAR_BATTERY_LEVEL = 0,
LV_STATUS_BAR_BATTERY_ICON = 1,
LV_STATUS_BAR_WIFI = 2,
LV_STATUS_BAR_BLUETOOTH = 3,
} lv_icon_status_bar_t;
void setupGui();
void updateStepCounter(uint32_t counter);
void updateBatteryIcon(lv_icon_battery_t index);
void wifi_list_add(const char *ssid);
void wifi_connect_status(bool result);
void updateBatteryLevel();
#endif /*__GUI_H */
/*
Copyright (c) 2019 lewis he
This is just a demonstration. Most of the functions are not implemented.
The main implementation is low-power standby.
The off-screen standby (not deep sleep) current is about 4mA.
Select standard motherboard and standard backplane for testing.
Created by Lewis he on October 10, 2019.
*/
// Please select the model you want to use in config.h
//#include "config.h"
#include
#include
#include "gui.h"
#include
#include "string.h"
#include
#include "FS.h"
#include "SD.h"
#include
#define RTC_TIME_ZONE "CST-8"
LV_FONT_DECLARE(Geometr);
LV_FONT_DECLARE(Ubuntu);
LV_IMG_DECLARE(bg);
LV_IMG_DECLARE(bg1);
LV_IMG_DECLARE(bg2);
LV_IMG_DECLARE(bg3);
LV_IMG_DECLARE(WALLPAPER_1_IMG);
LV_IMG_DECLARE(WALLPAPER_2_IMG);
LV_IMG_DECLARE(WALLPAPER_3_IMG);
LV_IMG_DECLARE(step);
LV_IMG_DECLARE(menu);
LV_IMG_DECLARE(wifi);
LV_IMG_DECLARE(light);
LV_IMG_DECLARE(bluetooth);
LV_IMG_DECLARE(sd);
LV_IMG_DECLARE(setting);
LV_IMG_DECLARE(on);
LV_IMG_DECLARE(off);
LV_IMG_DECLARE(level1);
LV_IMG_DECLARE(level2);
LV_IMG_DECLARE(level3);
LV_IMG_DECLARE(iexit);
LV_IMG_DECLARE(modules);
LV_IMG_DECLARE(CAMERA_PNG);
extern EventGroupHandle_t g_event_group;
extern QueueHandle_t g_event_queue_handle;
static lv_style_t settingStyle;
static lv_obj_t *mainBar = nullptr;
static lv_obj_t *timeLabel = nullptr;
static lv_obj_t *menuBtn = nullptr;
static uint8_t globalIndex = 0;
static void lv_update_task(struct _lv_task_t *);
static void lv_battery_task(struct _lv_task_t *);
static void updateTime();
static void view_event_handler(lv_obj_t *obj, lv_event_t event);
static void wifi_event_cb();
static void sd_event_cb();
static void setting_event_cb();
static void light_event_cb();
static void modules_event_cb();
static void camera_event_cb();
static void wifi_destory();
class StatusBar
{
typedef struct {
bool vaild;
lv_obj_t *icon;
} lv_status_bar_t;
public:
StatusBar()
{
memset(_array, 0, sizeof(_array));
}
void createIcons(lv_obj_t *par)
{
_par = par;
static lv_style_t barStyle;
lv_style_init(&barStyle);
lv_style_set_radius(&barStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_bg_color(&barStyle, LV_OBJ_PART_MAIN, LV_COLOR_GRAY);
lv_style_set_bg_opa(&barStyle, LV_OBJ_PART_MAIN, LV_OPA_20);
lv_style_set_border_width(&barStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_text_color(&barStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
lv_style_set_image_recolor(&barStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
_bar = lv_cont_create(_par, NULL);
lv_obj_set_size(_bar, LV_HOR_RES, _barHeight);
lv_obj_add_style(_bar, LV_OBJ_PART_MAIN, &barStyle);
_array[0].icon = lv_label_create(_bar, NULL);
lv_label_set_text(_array[0].icon, "100%");
_array[1].icon = lv_img_create(_bar, NULL);
lv_img_set_src(_array[1].icon, LV_SYMBOL_BATTERY_FULL);
_array[2].icon = lv_img_create(_bar, NULL);
lv_img_set_src(_array[2].icon, LV_SYMBOL_WIFI);
lv_obj_set_hidden(_array[2].icon, true);
_array[3].icon = lv_img_create(_bar, NULL);
lv_img_set_src(_array[3].icon, LV_SYMBOL_BLUETOOTH);
lv_obj_set_hidden(_array[3].icon, true);
//step counter
_array[4].icon = lv_img_create(_bar, NULL);
lv_img_set_src(_array[4].icon, &step);
lv_obj_align(_array[4].icon, _bar, LV_ALIGN_IN_LEFT_MID, 10, 0);
_array[5].icon = lv_label_create(_bar, NULL);
lv_label_set_text(_array[5].icon, "0");
lv_obj_align(_array[5].icon, _array[4].icon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
refresh();
}
void setStepCounter(uint32_t counter)
{
lv_label_set_text(_array[5].icon, String(counter).c_str());
lv_obj_align(_array[5].icon, _array[4].icon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
}
void updateLevel(int level)
{
lv_label_set_text(_array[0].icon, (String(level) + "%").c_str());
refresh();
}
void updateBatteryIcon(lv_icon_battery_t icon)
{
const char *icons[6] = {LV_SYMBOL_BATTERY_EMPTY, LV_SYMBOL_BATTERY_1, LV_SYMBOL_BATTERY_2, LV_SYMBOL_BATTERY_3, LV_SYMBOL_BATTERY_FULL, LV_SYMBOL_CHARGE};
lv_img_set_src(_array[1].icon, icons[icon]);
refresh();
}
void show(lv_icon_status_bar_t icon)
{
lv_obj_set_hidden(_array[icon].icon, false);
refresh();
}
void hidden(lv_icon_status_bar_t icon)
{
lv_obj_set_hidden(_array[icon].icon, true);
refresh();
}
uint8_t height()
{
return _barHeight;
}
lv_obj_t *self()
{
return _bar;
}
private:
void refresh()
{
int prev;
for (int i = 0; i < 4; i++) {
if (!lv_obj_get_hidden(_array[i].icon)) {
if (i == LV_STATUS_BAR_BATTERY_LEVEL) {
lv_obj_align(_array[i].icon, NULL, LV_ALIGN_IN_RIGHT_MID, 0, 0);
} else {
lv_obj_align(_array[i].icon, _array[prev].icon, LV_ALIGN_OUT_LEFT_MID, iconOffset, 0);
}
prev = i;
}
}
};
lv_obj_t *_bar = nullptr;
lv_obj_t *_par = nullptr;
uint8_t _barHeight = 30;
lv_status_bar_t _array[6];
const int8_t iconOffset = -5;
};
class MenuBar
{
public:
typedef struct {
const char *name;
void *img;
void (*event_cb)();
} lv_menu_config_t;
MenuBar()
{
_cont = nullptr;
_view = nullptr;
_exit = nullptr;
_obj = nullptr;
_vp = nullptr;
};
~MenuBar() {};
void createMenu(lv_menu_config_t *config, int count, lv_event_cb_t event_cb, int direction = 1)
{
static lv_style_t menuStyle;
lv_style_init(&menuStyle);
lv_style_set_radius(&menuStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_bg_color(&menuStyle, LV_OBJ_PART_MAIN, LV_COLOR_GRAY);
lv_style_set_bg_opa(&menuStyle, LV_OBJ_PART_MAIN, LV_OPA_0);
lv_style_set_border_width(&menuStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_text_color(&menuStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
lv_style_set_image_recolor(&menuStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
_count = count;
_vp = new lv_point_t [count];
_obj = new lv_obj_t *[count];
for (int i = 0; i < count; i++) {
if (direction) {
_vp[i].x = 0;
_vp[i].y = i;
} else {
_vp[i].x = i;
_vp[i].y = 0;
}
}
_cont = lv_cont_create(lv_scr_act(), NULL);
lv_obj_set_size(_cont, LV_HOR_RES, LV_VER_RES - 30);
lv_obj_align(_cont, NULL, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
lv_obj_add_style(_cont, LV_OBJ_PART_MAIN, &menuStyle);
_view = lv_tileview_create(_cont, NULL);
lv_tileview_set_valid_positions(_view, _vp, count );
lv_tileview_set_edge_flash(_view, false);
lv_obj_align(_view, NULL, LV_ALIGN_CENTER, 0, 0);
lv_page_set_scrlbar_mode(_view, LV_SCRLBAR_MODE_OFF);
lv_obj_add_style(_view, LV_OBJ_PART_MAIN, &menuStyle);
lv_coord_t _w = lv_obj_get_width(_view) ;
lv_coord_t _h = lv_obj_get_height(_view);
for (int i = 0; i < count; i++) {
_obj[i] = lv_cont_create(_view, _view);
lv_obj_set_size(_obj[i], _w, _h);
lv_obj_t *img = lv_img_create(_obj[i], NULL);
lv_img_set_src(img, config[i].img);
lv_obj_align(img, _obj[i], LV_ALIGN_CENTER, 0, 0);
lv_obj_t *label = lv_label_create(_obj[i], NULL);
lv_label_set_text(label, config[i].name);
lv_obj_align(label, img, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
i == 0 ? lv_obj_align(_obj[i], NULL, LV_ALIGN_CENTER, 0, 0) : lv_obj_align(_obj[i], _obj[i - 1], direction ? LV_ALIGN_OUT_BOTTOM_MID : LV_ALIGN_OUT_RIGHT_MID, 0, 0);
lv_tileview_add_element(_view, _obj[i]);
lv_obj_set_click(_obj[i], true);
lv_obj_set_event_cb(_obj[i], event_cb);
}
_exit = lv_imgbtn_create(lv_scr_act(), NULL);
lv_imgbtn_set_src(_exit, LV_BTN_STATE_RELEASED, &menu);
lv_imgbtn_set_src(_exit, LV_BTN_STATE_PRESSED, &menu);
lv_imgbtn_set_src(_exit, LV_BTN_STATE_CHECKED_PRESSED, &menu);
lv_imgbtn_set_src(_exit, LV_BTN_STATE_CHECKED_RELEASED, &menu);
lv_obj_align(_exit, NULL, LV_ALIGN_IN_BOTTOM_RIGHT, -20, -20);
lv_obj_set_event_cb(_exit, event_cb);
lv_obj_set_top(_exit, true);
}
lv_obj_t *exitBtn() const
{
return _exit;
}
lv_obj_t *self() const
{
return _cont;
}
void hidden(bool en = true)
{
lv_obj_set_hidden(_cont, en);
lv_obj_set_hidden(_exit, en);
}
lv_obj_t *obj(int index) const
{
if (index > _count)return nullptr;
return _obj[index];
}
private:
lv_obj_t *_cont, *_view, *_exit, * *_obj;
lv_point_t *_vp ;
int _count = 0;
};
MenuBar::lv_menu_config_t _cfg[7] = {
{.name = "WiFi", .img = (void *) &wifi, .event_cb = wifi_event_cb},
{.name = "Bluetooth", .img = (void *) &bluetooth, /*.event_cb = bluetooth_event_cb*/},
{.name = "SD Card", .img = (void *) &sd, /*.event_cb =sd_event_cb*/},
{.name = "Light", .img = (void *) &light, /*.event_cb = light_event_cb*/},
{.name = "Setting", .img = (void *) &setting, /*.event_cb = setting_event_cb */},
{.name = "Modules", .img = (void *) &modules, /*.event_cb = modules_event_cb */},
{.name = "Camera", .img = (void *) &CAMERA_PNG, /*.event_cb = camera_event_cb*/ }
};
MenuBar menuBars;
StatusBar bar;
static void event_handler(lv_obj_t *obj, lv_event_t event)
{
if (event == LV_EVENT_SHORT_CLICKED) { //! Event callback Is in here
if (obj == menuBtn) {
lv_obj_set_hidden(mainBar, true);
if (menuBars.self() == nullptr) {
menuBars.createMenu(_cfg, sizeof(_cfg) / sizeof(_cfg[0]), view_event_handler);
lv_obj_align(menuBars.self(), bar.self(), LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
} else {
menuBars.hidden(false);
}
}
}
}
void setupGui()
{
lv_style_init(&settingStyle);
lv_style_set_radius(&settingStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_bg_color(&settingStyle, LV_OBJ_PART_MAIN, LV_COLOR_GRAY);
lv_style_set_bg_opa(&settingStyle, LV_OBJ_PART_MAIN, LV_OPA_0);
lv_style_set_border_width(&settingStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_text_color(&settingStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
lv_style_set_image_recolor(&settingStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
//Create wallpaper
void *images[] = {(void *) &bg, (void *) &bg1, (void *) &bg2, (void *) &bg3 };
lv_obj_t *scr = lv_scr_act();
lv_obj_t *img_bin = lv_img_create(scr, NULL); /*Create an image object*/
srand((int)time(0));
int r = rand() % 4;
lv_img_set_src(img_bin, images[r]);
lv_obj_align(img_bin, NULL, LV_ALIGN_CENTER, 0, 0);
//! bar
bar.createIcons(scr);
updateBatteryLevel();
lv_icon_battery_t icon = LV_ICON_CALCULATION;
TTGOClass *ttgo = TTGOClass::getWatch();
if (ttgo->power->isChargeing()) {
icon = LV_ICON_CHARGE;
}
updateBatteryIcon(icon);
//! main
static lv_style_t mainStyle;
lv_style_init(&mainStyle);
lv_style_set_radius(&mainStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_bg_color(&mainStyle, LV_OBJ_PART_MAIN, LV_COLOR_GRAY);
lv_style_set_bg_opa(&mainStyle, LV_OBJ_PART_MAIN, LV_OPA_0);
lv_style_set_border_width(&mainStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_text_color(&mainStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
lv_style_set_image_recolor(&mainStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
mainBar = lv_cont_create(scr, NULL);
lv_obj_set_size(mainBar, LV_HOR_RES, LV_VER_RES - bar.height());
lv_obj_add_style(mainBar, LV_OBJ_PART_MAIN, &mainStyle);
lv_obj_align(mainBar, bar.self(), LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
//! Time
static lv_style_t timeStyle;
lv_style_copy(&timeStyle, &mainStyle);
lv_style_set_text_font(&timeStyle, LV_STATE_DEFAULT, &Ubuntu);
timeLabel = lv_label_create(mainBar, NULL);
lv_obj_add_style(timeLabel, LV_OBJ_PART_MAIN, &timeStyle);
updateTime();
//! menu
static lv_style_t style_pr;
lv_style_init(&style_pr);
lv_style_set_image_recolor(&style_pr, LV_OBJ_PART_MAIN, LV_COLOR_BLACK);
lv_style_set_text_color(&style_pr, LV_OBJ_PART_MAIN, lv_color_hex3(0xaaa));
menuBtn = lv_imgbtn_create(mainBar, NULL);
lv_imgbtn_set_src(menuBtn, LV_BTN_STATE_RELEASED, &menu);
lv_imgbtn_set_src(menuBtn, LV_BTN_STATE_PRESSED, &menu);
lv_imgbtn_set_src(menuBtn, LV_BTN_STATE_CHECKED_RELEASED, &menu);
lv_imgbtn_set_src(menuBtn, LV_BTN_STATE_CHECKED_PRESSED, &menu);
lv_obj_add_style(menuBtn, LV_OBJ_PART_MAIN, &style_pr);
lv_obj_align(menuBtn, mainBar, LV_ALIGN_OUT_BOTTOM_MID, 0, -70);
lv_obj_set_event_cb(menuBtn, event_handler);
lv_task_create(lv_update_task, 1000, LV_TASK_PRIO_LOWEST, NULL);
lv_task_create(lv_battery_task, 30000, LV_TASK_PRIO_LOWEST, NULL);
}
void updateStepCounter(uint32_t counter)
{
bar.setStepCounter(counter);
}
static void updateTime()
{
time_t now;
struct tm info;
char buf[64];
time(&now);
localtime_r(&now, &info);
strftime(buf, sizeof(buf), "%H:%M", &info);
lv_label_set_text(timeLabel, buf);
lv_obj_align(timeLabel, NULL, LV_ALIGN_IN_TOP_MID, 0, 20);
TTGOClass *ttgo = TTGOClass::getWatch();
ttgo->rtc->syncToRtc();
}
void updateBatteryLevel()
{
TTGOClass *ttgo = TTGOClass::getWatch();
int p = ttgo->power->getBattPercentage();
bar.updateLevel(p);
}
void updateBatteryIcon(lv_icon_battery_t icon)
{
if (icon >= LV_ICON_CALCULATION) {
TTGOClass *ttgo = TTGOClass::getWatch();
int level = ttgo->power->getBattPercentage();
if (level > 95)icon = LV_ICON_BAT_FULL;
else if (level > 80)icon = LV_ICON_BAT_3;
else if (level > 45)icon = LV_ICON_BAT_2;
else if (level > 20)icon = LV_ICON_BAT_1;
else icon = LV_ICON_BAT_EMPTY;
}
bar.updateBatteryIcon(icon);
}
static void lv_update_task(struct _lv_task_t *data)
{
updateTime();
}
static void lv_battery_task(struct _lv_task_t *data)
{
updateBatteryLevel();
}
static void view_event_handler(lv_obj_t *obj, lv_event_t event)
{
int size = sizeof(_cfg) / sizeof(_cfg[0]);
if (event == LV_EVENT_SHORT_CLICKED) {
if (obj == menuBars.exitBtn()) {
menuBars.hidden();
lv_obj_set_hidden(mainBar, false);
return;
}
for (int i = 0; i < size; i++) {
if (obj == menuBars.obj(i)) {
if (_cfg[i].event_cb != nullptr) {
menuBars.hidden();
_cfg[i].event_cb();
}
return;
}
}
}
}
/*****************************************************************
*
* ! Keyboard Class
*
*/
class Keyboard
{
public:
typedef enum {
KB_EVENT_OK,
KB_EVENT_EXIT,
} kb_event_t;
typedef void (*kb_event_cb)(kb_event_t event);
Keyboard()
{
_kbCont = nullptr;
};
~Keyboard()
{
if (_kbCont)
lv_obj_del(_kbCont);
_kbCont = nullptr;
};
void create(lv_obj_t *parent = nullptr)
{
static lv_style_t kbStyle;
lv_style_init(&kbStyle);
lv_style_set_radius(&kbStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_bg_color(&kbStyle, LV_OBJ_PART_MAIN, LV_COLOR_GRAY);
lv_style_set_bg_opa(&kbStyle, LV_OBJ_PART_MAIN, LV_OPA_0);
lv_style_set_border_width(&kbStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_text_color(&kbStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
lv_style_set_image_recolor(&kbStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
if (parent == nullptr) {
parent = lv_scr_act();
}
_kbCont = lv_cont_create(parent, NULL);
lv_obj_set_size(_kbCont, LV_HOR_RES, LV_VER_RES - 30);
lv_obj_align(_kbCont, NULL, LV_ALIGN_CENTER, 0, 0);
lv_obj_add_style(_kbCont, LV_OBJ_PART_MAIN, &kbStyle);
lv_obj_t *ta = lv_textarea_create(_kbCont, NULL);
lv_obj_set_height(ta, 40);
lv_textarea_set_one_line(ta, true);
lv_textarea_set_pwd_mode(ta, false);
lv_textarea_set_text(ta, "");
lv_obj_align(ta, _kbCont, LV_ALIGN_IN_TOP_MID, 10, 10);
lv_obj_t *kb = lv_keyboard_create(_kbCont, NULL);
lv_keyboard_set_map(kb, LV_KEYBOARD_MODE_TEXT_LOWER, btnm_mapplus[0]);
lv_obj_set_height(kb, LV_VER_RES / 3 * 2);
lv_obj_set_width(kb, 240);
lv_obj_align(kb, _kbCont, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
lv_keyboard_set_textarea(kb, ta);
lv_obj_add_style(kb, LV_OBJ_PART_MAIN, &kbStyle);
lv_obj_add_style(ta, LV_OBJ_PART_MAIN, &kbStyle);
lv_obj_set_event_cb(kb, __kb_event_cb);
_kb = this;
}
void align(const lv_obj_t *base, lv_align_t align, lv_coord_t x = 0, lv_coord_t y = 0)
{
lv_obj_align(_kbCont, base, align, x, y);
}
static void __kb_event_cb(lv_obj_t *kb, lv_event_t event)
{
if (event != LV_EVENT_VALUE_CHANGED && event != LV_EVENT_LONG_PRESSED_REPEAT) return;
lv_keyboard_ext_t *ext = (lv_keyboard_ext_t *)lv_obj_get_ext_attr(kb);
const char *txt = lv_btnmatrix_get_active_btn_text(kb);
if (txt == NULL) return;
static int index = 0;
if (strcmp(txt, LV_SYMBOL_OK) == 0) {
strcpy(__buf, lv_textarea_get_text(ext->ta));
if (_kb->_cb != nullptr) {
_kb->_cb(KB_EVENT_OK);
}
return;
} else if (strcmp(txt, "Exit") == 0) {
if (_kb->_cb != nullptr) {
_kb->_cb(KB_EVENT_EXIT);
}
return;
} else if (strcmp(txt, LV_SYMBOL_RIGHT) == 0) {
index = index + 1 >= sizeof(btnm_mapplus) / sizeof(btnm_mapplus[0]) ? 0 : index + 1;
lv_keyboard_set_map(kb, LV_KEYBOARD_MODE_TEXT_LOWER, btnm_mapplus[index]);
return;
} else if (strcmp(txt, "Del") == 0) {
lv_textarea_del_char(ext->ta);
} else {
lv_textarea_add_text(ext->ta, txt);
}
}
void setKeyboardEvent(kb_event_cb cb)
{
_cb = cb;
}
const char *getText()
{
return (const char *)__buf;
}
void hidden(bool en = true)
{
lv_obj_set_hidden(_kbCont, en);
}
private:
lv_obj_t *_kbCont = nullptr;
kb_event_cb _cb = nullptr;
static const char *btnm_mapplus[10][23];
static Keyboard *_kb;
static char __buf[128];
};
char Keyboard::__buf[128];
Keyboard *Keyboard::_kb = nullptr;
const char *Keyboard::btnm_mapplus[10][23] = {
{
"a", "b", "c", "\n",
"d", "e", "f", "\n",
"g", "h", "i", "\n",
LV_SYMBOL_OK, "Del", "Exit", LV_SYMBOL_RIGHT, ""
},
{
"j", "k", "l", "\n",
"n", "m", "o", "\n",
"p", "q", "r", "\n",
LV_SYMBOL_OK, "Del", "Exit", LV_SYMBOL_RIGHT, ""
},
{
"s", "t", "u", "\n",
"v", "w", "x", "\n",
"y", "z", " ", "\n",
LV_SYMBOL_OK, "Del", "Exit", LV_SYMBOL_RIGHT, ""
},
{
"A", "B", "C", "\n",
"D", "E", "F", "\n",
"G", "H", "I", "\n",
LV_SYMBOL_OK, "Del", "Exit", LV_SYMBOL_RIGHT, ""
},
{
"J", "K", "L", "\n",
"N", "M", "O", "\n",
"P", "Q", "R", "\n",
LV_SYMBOL_OK, "Del", "Exit", LV_SYMBOL_RIGHT, ""
},
{
"S", "T", "U", "\n",
"V", "W", "X", "\n",
"Y", "Z", " ", "\n",
LV_SYMBOL_OK, "Del", "Exit", LV_SYMBOL_RIGHT, ""
},
{
"1", "2", "3", "\n",
"4", "5", "6", "\n",
"7", "8", "9", "\n",
LV_SYMBOL_OK, "Del", "Exit", LV_SYMBOL_RIGHT, ""
},
{
"0", "+", "-", "\n",
"/", "*", "=", "\n",
"!", "?", "#", "\n",
LV_SYMBOL_OK, "Del", "Exit", LV_SYMBOL_RIGHT, ""
},
{
"<", ">", "@", "\n",
"%", "$", "(", "\n",
")", "{", "}", "\n",
LV_SYMBOL_OK, "Del", "Exit", LV_SYMBOL_RIGHT, ""
},
{
"[", "]", ";", "\n",
"\"", "'", ".", "\n",
",", ":", " ", "\n",
LV_SYMBOL_OK, "Del", "Exit", LV_SYMBOL_RIGHT, ""
}
};
/*****************************************************************
*
* ! Switch Class
*
*/
class Switch
{
public:
typedef struct {
const char *name;
void (*cb)(uint8_t, bool);
} switch_cfg_t;
typedef void (*exit_cb)();
Switch()
{
_swCont = nullptr;
}
~Switch()
{
if (_swCont)
lv_obj_del(_swCont);
_swCont = nullptr;
}
void create(switch_cfg_t *cfg, uint8_t count, exit_cb cb, lv_obj_t *parent = nullptr)
{
static lv_style_t swlStyle;
lv_style_init(&swlStyle);
lv_style_set_radius(&swlStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_bg_color(&swlStyle, LV_OBJ_PART_MAIN, LV_COLOR_GRAY);
lv_style_set_bg_opa(&swlStyle, LV_OBJ_PART_MAIN, LV_OPA_0);
lv_style_set_border_width(&swlStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_border_opa(&swlStyle, LV_OBJ_PART_MAIN, LV_OPA_50);
lv_style_set_text_color(&swlStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
lv_style_set_image_recolor(&swlStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
if (parent == nullptr) {
parent = lv_scr_act();
}
_exit_cb = cb;
_swCont = lv_cont_create(parent, NULL);
lv_obj_set_size(_swCont, LV_HOR_RES, LV_VER_RES - 30);
lv_obj_align(_swCont, NULL, LV_ALIGN_CENTER, 0, 0);
lv_obj_add_style(_swCont, LV_OBJ_PART_MAIN, &swlStyle);
_count = count;
_sw = new lv_obj_t *[count];
_cfg = new switch_cfg_t [count];
memcpy(_cfg, cfg, sizeof(switch_cfg_t) * count);
lv_obj_t *prev = nullptr;
for (int i = 0; i < count; i++) {
lv_obj_t *la1 = lv_label_create(_swCont, NULL);
lv_label_set_text(la1, cfg[i].name);
i == 0 ? lv_obj_align(la1, NULL, LV_ALIGN_IN_TOP_LEFT, 30, 20) : lv_obj_align(la1, prev, LV_ALIGN_OUT_BOTTOM_MID, 0, 20);
_sw[i] = lv_imgbtn_create(_swCont, NULL);
lv_imgbtn_set_src(_sw[i], LV_BTN_STATE_RELEASED, &off);
lv_imgbtn_set_src(_sw[i], LV_BTN_STATE_PRESSED, &off);
lv_imgbtn_set_src(_sw[i], LV_BTN_STATE_CHECKED_RELEASED, &off);
lv_imgbtn_set_src(_sw[i], LV_BTN_STATE_CHECKED_PRESSED, &off);
lv_obj_set_click(_sw[i], true);
lv_obj_align(_sw[i], la1, LV_ALIGN_OUT_RIGHT_MID, 80, 0);
lv_obj_set_event_cb(_sw[i], __switch_event_cb);
prev = la1;
}
_exitBtn = lv_imgbtn_create(_swCont, NULL);
lv_imgbtn_set_src(_exitBtn, LV_BTN_STATE_RELEASED, &iexit);
lv_imgbtn_set_src(_exitBtn, LV_BTN_STATE_PRESSED, &iexit);
lv_imgbtn_set_src(_exitBtn, LV_BTN_STATE_CHECKED_RELEASED, &iexit);
lv_imgbtn_set_src(_exitBtn, LV_BTN_STATE_CHECKED_PRESSED, &iexit);
lv_obj_set_click(_exitBtn, true);
lv_obj_align(_exitBtn, _swCont, LV_ALIGN_IN_BOTTOM_MID, 0, -5);
lv_obj_set_event_cb(_exitBtn, __switch_event_cb);
_switch = this;
}
void align(const lv_obj_t *base, lv_align_t align, lv_coord_t x = 0, lv_coord_t y = 0)
{
lv_obj_align(_swCont, base, align, x, y);
}
void hidden(bool en = true)
{
lv_obj_set_hidden(_swCont, en);
}
static void __switch_event_cb(lv_obj_t *obj, lv_event_t event)
{
if (event == LV_EVENT_SHORT_CLICKED) {
Serial.println("LV_EVENT_SHORT_CLICKED");
if (obj == _switch->_exitBtn) {
if ( _switch->_exit_cb != nullptr) {
_switch->_exit_cb();
return;
}
}
}
if (event == LV_EVENT_SHORT_CLICKED) {
Serial.println("LV_EVENT_VALUE_CHANGED");
for (int i = 0; i < _switch->_count ; i++) {
lv_obj_t *sw = _switch->_sw[i];
if (obj == sw) {
const void *src = lv_imgbtn_get_src(sw, LV_BTN_STATE_RELEASED);
const void *dst = src == &off ? &on : &off;
bool en = src == &off;
lv_imgbtn_set_src(sw, LV_BTN_STATE_RELEASED, dst);
lv_imgbtn_set_src(sw, LV_BTN_STATE_PRESSED, dst);
lv_imgbtn_set_src(sw, LV_BTN_STATE_CHECKED_RELEASED, dst);
lv_imgbtn_set_src(sw, LV_BTN_STATE_CHECKED_PRESSED, dst);
if (_switch->_cfg[i].cb != nullptr) {
_switch->_cfg[i].cb(i, en);
}
return;
}
}
}
}
void setStatus(uint8_t index, bool en)
{
if (index > _count)return;
lv_obj_t *sw = _sw[index];
const void *dst = en ? &on : &off;
lv_imgbtn_set_src(sw, LV_BTN_STATE_RELEASED, dst);
lv_imgbtn_set_src(sw, LV_BTN_STATE_PRESSED, dst);
lv_imgbtn_set_src(sw, LV_BTN_STATE_CHECKED_RELEASED, dst);
lv_imgbtn_set_src(sw, LV_BTN_STATE_CHECKED_PRESSED, dst);
}
private:
static Switch *_switch;
lv_obj_t *_swCont = nullptr;
uint8_t _count;
lv_obj_t **_sw = nullptr;
switch_cfg_t *_cfg = nullptr;
lv_obj_t *_exitBtn = nullptr;
exit_cb _exit_cb = nullptr;
};
Switch *Switch::_switch = nullptr;
/*****************************************************************
*
* ! Preload Class
*
*/
class Preload
{
public:
Preload()
{
_preloadCont = nullptr;
}
~Preload()
{
if (_preloadCont == nullptr) return;
lv_obj_del(_preloadCont);
_preloadCont = nullptr;
}
void create(lv_obj_t *parent = nullptr)
{
if (parent == nullptr) {
parent = lv_scr_act();
}
if (_preloadCont == nullptr) {
static lv_style_t plStyle;
lv_style_init(&plStyle);
lv_style_set_radius(&plStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_bg_color(&plStyle, LV_OBJ_PART_MAIN, LV_COLOR_GRAY);
lv_style_set_bg_opa(&plStyle, LV_OBJ_PART_MAIN, LV_OPA_0);
lv_style_set_border_width(&plStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_text_color(&plStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
lv_style_set_image_recolor(&plStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
static lv_style_t style;
lv_style_init(&style);
lv_style_set_radius(&style, LV_OBJ_PART_MAIN, 0);
lv_style_set_bg_color(&style, LV_OBJ_PART_MAIN, LV_COLOR_GRAY);
lv_style_set_bg_opa(&style, LV_OBJ_PART_MAIN, LV_OPA_0);
lv_style_set_border_width(&style, LV_OBJ_PART_MAIN, 0);
lv_style_set_text_color(&style, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
lv_style_set_image_recolor(&style, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
_preloadCont = lv_cont_create(parent, NULL);
lv_obj_set_size(_preloadCont, LV_HOR_RES, LV_VER_RES - 30);
lv_obj_align(_preloadCont, NULL, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
lv_obj_add_style(_preloadCont, LV_OBJ_PART_MAIN, &plStyle);
lv_obj_t *preload = lv_spinner_create(_preloadCont, NULL);
lv_obj_set_size(preload, lv_obj_get_width(_preloadCont) / 2, lv_obj_get_height(_preloadCont) / 2);
lv_obj_add_style(preload, LV_OBJ_PART_MAIN, &style);
lv_obj_align(preload, _preloadCont, LV_ALIGN_CENTER, 0, 0);
}
}
void align(const lv_obj_t *base, lv_align_t align, lv_coord_t x = 0, lv_coord_t y = 0)
{
lv_obj_align(_preloadCont, base, align, x, y);
}
void hidden(bool en = true)
{
lv_obj_set_hidden(_preloadCont, en);
}
private:
lv_obj_t *_preloadCont = nullptr;
};
/*****************************************************************
*
* ! List Class
*
*/
class List
{
public:
typedef void(*list_event_cb)(const char *);
List()
{
}
~List()
{
if (_listCont == nullptr) return;
lv_obj_del(_listCont);
_listCont = nullptr;
}
void create(lv_obj_t *parent = nullptr)
{
if (parent == nullptr) {
parent = lv_scr_act();
}
if (_listCont == nullptr) {
static lv_style_t listStyle;
lv_style_init(&listStyle);
lv_style_set_radius(&listStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_bg_color(&listStyle, LV_OBJ_PART_MAIN, LV_COLOR_GRAY);
lv_style_set_bg_opa(&listStyle, LV_OBJ_PART_MAIN, LV_OPA_0);
lv_style_set_border_width(&listStyle, LV_OBJ_PART_MAIN, 0);
lv_style_set_text_color(&listStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
lv_style_set_image_recolor(&listStyle, LV_OBJ_PART_MAIN, LV_COLOR_WHITE);
_listCont = lv_list_create(lv_scr_act(), NULL);
lv_list_set_scrollbar_mode(_listCont, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_size(_listCont, LV_HOR_RES, LV_VER_RES - 30);
lv_obj_add_style(_listCont, LV_OBJ_PART_MAIN, &listStyle);
lv_obj_align(_listCont, NULL, LV_ALIGN_CENTER, 0, 0);
}
_list = this;
}
void add(const char *txt, void *imgsrc = (void *)LV_SYMBOL_WIFI)
{
lv_obj_t *btn = lv_list_add_btn(_listCont, imgsrc, txt);
lv_obj_set_event_cb(btn, __list_event_cb);
}
void align(const lv_obj_t *base, lv_align_t align, lv_coord_t x = 0, lv_coord_t y = 0)
{
lv_obj_align(_listCont, base, align, x, y);
}
void hidden(bool en = true)
{
lv_obj_set_hidden(_listCont, en);
}
static void __list_event_cb(lv_obj_t *obj, lv_event_t event)
{
if (event == LV_EVENT_SHORT_CLICKED) {
const char *txt = lv_list_get_btn_text(obj);
if (_list->_cb != nullptr) {
_list->_cb(txt);
}
}
}
void setListCb(list_event_cb cb)
{
_cb = cb;
}
private:
lv_obj_t *_listCont = nullptr;
static List *_list ;
list_event_cb _cb = nullptr;
};
List *List::_list = nullptr;
/*****************************************************************
*
* ! Task Class
*
*/
class Task
{
public:
Task()
{
_handler = nullptr;
_cb = nullptr;
}
~Task()
{
if ( _handler == nullptr)return;
Serial.println("Free Task Func");
lv_task_del(_handler);
_handler = nullptr;
_cb = nullptr;
}
void create(lv_task_cb_t cb, uint32_t period = 1000, lv_task_prio_t prio = LV_TASK_PRIO_LOW)
{
_handler = lv_task_create(cb, period, prio, NULL);
};
private:
lv_task_t *_handler = nullptr;
lv_task_cb_t _cb = nullptr;
};
/*****************************************************************
*
* ! MesBox Class
*
*/
class MBox
{
public:
MBox()
{
_mbox = nullptr;
}
~MBox()
{
if (_mbox == nullptr)return;
lv_obj_del(_mbox);
_mbox = nullptr;
}
void create(const char *text, lv_event_cb_t event_cb, const char **btns = nullptr, lv_obj_t *par = nullptr)
{
if (_mbox != nullptr)return;
lv_obj_t *p = par == nullptr ? lv_scr_act() : par;
_mbox = lv_msgbox_create(p, NULL);
lv_msgbox_set_text(_mbox, text);
if (btns == nullptr) {
static const char *defBtns[] = {"Ok", ""};
lv_msgbox_add_btns(_mbox, defBtns);
} else {
lv_msgbox_add_btns(_mbox, btns);
}
lv_obj_set_width(_mbox, LV_HOR_RES - 40);
lv_obj_set_event_cb(_mbox, event_cb);
lv_obj_align(_mbox, NULL, LV_ALIGN_CENTER, 0, 0);
}
void setData(void *data)
{
lv_obj_set_user_data(_mbox, data);
}
void *getData()
{
return lv_obj_get_user_data(_mbox);
}
void setBtn(const char **btns)
{
lv_msgbox_add_btns(_mbox, btns);
}
private:
lv_obj_t *_mbox = nullptr;
};
/*****************************************************************
*
* ! GLOBAL VALUE
*
*/
static Keyboard *kb = nullptr;
static Switch *sw = nullptr;
static Preload *pl = nullptr;
static List *list = nullptr;
static Task *task = nullptr;
static Ticker *gTicker = nullptr;
static MBox *mbox = nullptr;
static char ssid[64], password[64];
/*****************************************************************
*
* !WIFI EVENT
*
*/
void wifi_connect_status(bool result)
{
if (gTicker != nullptr) {
delete gTicker;
gTicker = nullptr;
}
if (kb != nullptr) {
delete kb;
kb = nullptr;
}
if (sw != nullptr) {
delete sw;
sw = nullptr;
}
if (pl != nullptr) {
delete pl;
pl = nullptr;
}
if (result) {
bar.show(LV_STATUS_BAR_WIFI);
} else {
bar.hidden(LV_STATUS_BAR_WIFI);
}
menuBars.hidden(false);
}
void wifi_kb_event_cb(Keyboard::kb_event_t event)
{
if (event == 0) {
kb->hidden();
Serial.println(kb->getText());
strlcpy(password, kb->getText(), sizeof(password));
pl->hidden(false);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
WiFi.begin(ssid, password);
gTicker = new Ticker;
gTicker->once_ms(5 * 1000, []() {
wifi_connect_status(false);
});
} else if (event == 1) {
delete kb;
delete sw;
delete pl;
pl = nullptr;
kb = nullptr;
sw = nullptr;
menuBars.hidden(false);
}
}
void wifi_sw_event_cb(uint8_t index, bool en)
{
switch (index) {
case 0:
if (en) {
WiFi.begin();
} else {
WiFi.disconnect();
bar.hidden(LV_STATUS_BAR_WIFI);
}
break;
case 1:
sw->hidden();
pl = new Preload;
pl->create();
pl->align(bar.self(), LV_ALIGN_OUT_BOTTOM_MID);
WiFi.disconnect();
WiFi.scanNetworks(true);
break;
case 2:
if (!WiFi.isConnected()) {
//TODO pop-up window
Serial.println("WiFi is no connect");
return;
} else {
configTzTime(RTC_TIME_ZONE, "pool.ntp.org");
sw->hidden(false);
}
break;
default:
break;
}
}
void wifi_list_cb(const char *txt)
{
strlcpy(ssid, txt, sizeof(ssid));
delete list;
list = nullptr;
kb = new Keyboard;
kb->create();
kb->align(bar.self(), LV_ALIGN_OUT_BOTTOM_MID);
kb->setKeyboardEvent(wifi_kb_event_cb);
}
void wifi_list_add(const char *ssid)
{
if (list == nullptr) {
pl->hidden();
list = new List;
list->create();
list->align(bar.self(), LV_ALIGN_OUT_BOTTOM_MID);
list->setListCb(wifi_list_cb);
}
list->add(ssid);
}
static void wifi_event_cb()
{
Switch::switch_cfg_t cfg[3] = {{"Switch", wifi_sw_event_cb}, {"Scan", wifi_sw_event_cb}, {"NTP Sync", wifi_sw_event_cb}};
sw = new Switch;
sw->create(cfg, 3, []() {
delete sw;
sw = nullptr;
menuBars.hidden(false);
});
sw->align(bar.self(), LV_ALIGN_OUT_BOTTOM_MID);
sw->setStatus(0, WiFi.isConnected());
}
static void wifi_destory()
{
Serial.printf("globalIndex:%d\n", globalIndex);
switch (globalIndex) {
//! wifi management main
case 0:
menuBars.hidden(false);
delete sw;
sw = nullptr;
break;
//! wifi ap list
case 1:
if (list != nullptr) {
delete list;
list = nullptr;
}
if (gTicker != nullptr) {
delete gTicker;
gTicker = nullptr;
}
if (kb != nullptr) {
delete kb;
kb = nullptr;
}
if (pl != nullptr) {
delete pl;
pl = nullptr;
}
sw->hidden(false);
break;
//! wifi keyboard
case 2:
if (gTicker != nullptr) {
delete gTicker;
gTicker = nullptr;
}
if (kb != nullptr) {
delete kb;
kb = nullptr;
}
if (pl != nullptr) {
delete pl;
pl = nullptr;
}
sw->hidden(false);
break;
case 3:
break;
default:
break;
}
globalIndex--;
}
/*****************************************************************
*
* !SETTING EVENT
*
*/
static void setting_event_cb()
{
}
/*****************************************************************
*
* ! LIGHT EVENT
*
*/
static void light_sw_event_cb(uint8_t index, bool en)
{
//Add lights that need to be controlled
}
static void light_event_cb()
{
const uint8_t cfg_count = 4;
Switch::switch_cfg_t cfg[cfg_count] = {
{"light1", light_sw_event_cb},
{"light2", light_sw_event_cb},
{"light3", light_sw_event_cb},
{"light4", light_sw_event_cb},
};
sw = new Switch;
sw->create(cfg, cfg_count, []() {
delete sw;
sw = nullptr;
menuBars.hidden(false);
});
sw->align(bar.self(), LV_ALIGN_OUT_BOTTOM_MID);
//Initialize switch status
for (int i = 0; i < cfg_count; i++) {
sw->setStatus(i, 0);
}
}
/*****************************************************************
*
* ! MBOX EVENT
*
*/
static lv_obj_t *mbox1 = nullptr;
static void create_mbox(const char *txt, lv_event_cb_t event_cb)
{
if (mbox1 != nullptr)return;
static const char *btns[] = {"Ok", ""};
mbox1 = lv_msgbox_create(lv_scr_act(), NULL);
lv_msgbox_set_text(mbox1, txt);
lv_msgbox_add_btns(mbox1, btns);
lv_obj_set_width(mbox1, LV_HOR_RES - 40);
lv_obj_set_event_cb(mbox1, event_cb);
lv_obj_align(mbox1, NULL, LV_ALIGN_CENTER, 0, 0);
}
static void destory_mbox()
{
if (pl != nullptr) {
delete pl;
pl = nullptr;
}
if (list != nullptr) {
delete list;
list = nullptr;
}
if (mbox1 != nullptr) {
lv_obj_del(mbox1);
mbox1 = nullptr;
}
}
/*****************************************************************
*
* ! SD CARD EVENT
*
*/
static void sd_event_cb()
{
}
/*****************************************************************
*
* ! Modules EVENT
*
*/
static void modules_event_cb()
{
}
/*****************************************************************
*
* ! Camera EVENT
*
*/
static void camera_event_cb()
{
}
[env:ttgo-t-watch]
platform = espressif32
board = ttgo-t-watch
framework = arduino
monitor_speed = 115200
build_flags =
-D LILYGO_WATCH_2019_WITH_TOUCH=1
;-D LILYGO_WATCH_2019_NO_TOUCH=1
;-D LILYGO_WATCH_BLOCK=1
;-D LILYGO_WATCH_2020_V1=1
-D LILYGO_WATCH_LVGL=1
lib_deps =
TTGO TWatch Library
The source code has been tested on the T-Watch Touch 2019
As well as on the T-Watch 2020
Updates
2020/11/26 Publication of the article