Si l’on veut pouvoir utiliser la TTGO T-Watch comme une véritable montre connectée, il faut mettre en intégrer dans le projet le code nécessaire à la mise en veille automatique (ou manuelle). En plus des fonctions de mise en veille de l’ESP32 (Deep Sleep et Light Sleep par exemple), on peut utiliser l’accéléromètre BMA423 pour détecter un mouvement pour allumer l’écran. On pourra également utiliser le bouton principal qui est connecté au contrôleur d’alimentation AXP202 de la T-Watch pour éteindre ou allumer l’écran.
Comme la T-Watch est avant tout une carte de développement ESP32, on peut utiliser toutes les méthodes déjà détaillée dans cet article pour le mettre en veille (profonde ou légère).
Le code source utilisé pour cet article a été développé par Lewis He, le principal développeur de la librairie LilyGoWatch de LilyGo. Il est disponible sous le nom de SimpleWatch dans le dossier des exemples de la librairie LVGL.
Rappel sur la mise en veille de l’ESP32
L’ESP32 dispose de 5 modes de fonctionnement qui affecte directement la durée de vie de la batterie. Bien évidement, il ne faut pas oublier les périphériques (accéléromètre, écran tactile…) embarqués qui consomment eux aussi.
Active Mode |
||||||
GPIO | WiFi | Bluetooth | CPU | ULP | RTC | Consommation* |
95 ~ 240 mA |
Modem Sleep | ||||||
20 ~ 68 mA |
Light Sleep | ||||||
0,8 mA | ||||||
Core en pause |
Deep Sleep | ||||||
10 ~ 150 μA | ||||||
RTC uniquement |
Hibernation |
||||||
1 μA | ||||||
RTC uniquement | x1 |
Fonctions disponibles en fonction du type de sommeil activé
Avant de choisir le mode de mise en veille pour votre projet de montre ou d’objet connecté, voici un tableau de synthèse qui permet de vérifier qu’une méthode permet de réveiller le processeur en fonction du type de veille activée.
Méthode | Modem-Sleep | Light-Sleep | Deep-Sleep | Hibernation | |
Timer | esp_sleep_enable_timer_wakeup() | ✓ | ✓ | ✓ | ✓ |
Tactile | esp_sleep_enable_touchpad_wakeup() | ✓ | ✓ | ✓ | |
Ext0 et 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() | ✓ | ✓ | ✓ |
Mettre en veille automatiquement l’écran après quelques secondes d’inactivité
La méthode lv_disp_get_inactive_time de la librairie LVGL permet de connaître à tout moment la durée depuis combien de temps l’écran n’a pas été touché. Il suffit de comparer régulièrement le temps d’inactivité à une consigne pour déclencher l’exécution d’une fonction qui met en veille les accessoires de la montre
if (lv_disp_get_inactive_time(NULL) < DEFAULT_SCREEN_TIMEOUT) {
lv_task_handler();
} else {
low_energy();
}
La méthode isOn() de la classe bl (Backlight ou rétro-éclairage) permet de savoir si la montre est allumée. Voici un exemple de fonction qui met en veille les périphériques de la montre
watch->closeBL(); // Eteint le rétro-éclairage
watch->displaySleep(); // Eteint l'écran
watch->stopLvglTick(); // Met en pose LVGL
On peut ensuite éteindre la connectivité WiFi
WiFi.mode(WIFI_OFF);
Mise en veille légère de l’ESP32
La mise en veille légère permet de réactiver la montre instantanément. Pour gagner encore en autonomie, il est possible de réduire la fréquence du CPU. Ici par exemple, on baisse la fréquence du CPU de l’ESP32 à 20MHz (au lieu de 240MHz au maximum).
setCpuFrequencyMhz(20);
Maintenant on peut envoyer l’ESP32 à la sieste
esp_light_sleep_start();
Réveiller les périphériques de la montre
On va évidemment vouloir réveiller la montre. Plusieurs solutions sont possibles sur les T-Watch :
- Utiliser le bouton principal qui est connecté au contrôleur d’alimentation AXP202
- Utiliser le bouton utilisateur disponible sur certains modèles
- Détecter un mouvement ou un tap sur l’écran à l’aide de l’accéléromètre BMA423.
Toutes ces méthodes génèrent un signal numérique que l’on peut récupérer sur le GPIO de l’ESP32.
Voici comment faire par exemple pour réveiller la montre avec le bouton principal
gpio_wakeup_enable ((gpio_num_t)AXP202_INT, GPIO_INTR_LOW_LEVEL);
esp_sleep_enable_gpio_wakeup();
esp_light_sleep_start();
Dès que la montre est ré-activée, il suffira de relancer les services minimum suivants
watch->displayWakeup(); // allume l'écran
watch->openBL(); // le rétro-éclairgae
watch->startLvglTick(); // Le superviseur LVGL
lv_disp_trig_activity(NULL);
Réveiller la T-Watch avec l’accéléromètre BMA423 ou le bouton principal
Le Core PCB (carte mère) embarque un accéléromètre BMA423 qui envoie un signal lorsqu’un mouvement est détecté. Ce signal est récupéré par la librairie LilyGoWatch qui met à disposition une API très complète pour gérer des actions.
On va ainsi pouvoir allumer l’écran lorsqu’on lève le bras ou lorsqu’on tape l’écran. Pratique pour une montre connectée !
Le code source de la librairie adapté pour la T-Watch est ici. La version originale de Bosch SensorTech ici.
L’API de l’accéléromètre est accessible via la classe sensor.
BMA *sensor;
Le signal d’interruption du BMA423 est connecté à la broche 39 de l’ESP32 quelque soit le modèle de T-Watch.
Il n’est pas nécessaire de connaître la broche en utilisant la constante BMA423_INT1. La première chose à faire est de récupérer le signal d’interruption généré par le BMA 423
Lewis Le nous conseille de créer une tâche temps réel de FreeRTOS pour réveiller plus rapidement la montre. Voici comment ça fonctionne.
La première chose à faire est donc de déclarer les interruptions qui vont permettre de réveiller le ESP32 de son sommeil. Ici, on créé deux interruptions. La première va permettre de réveiller l’ESP32 à l’aide du bouton principal connecté à l’AXP202. La seconde à l’aide du signal d’interruption envoyé par l’accéléromètre dès qui mouvement est détecté.
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();
On doit ensuite définir pour chaque interruption une fonction qui sera exécutée d’un qu’un signal de réveil est reçu. Ici un message est envoyé au gestionnaire de tâches temps réel de FreeRTOS dès qu’un tap sur l’écran est détecté.
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);
Ensuite, il ne reste plus qu’à configurer et démarrer l’accéléromètre. Voici au minimum ce qu’il faut faire
sensor = watch->bma;
sensor->enableAccel(); // Activer l'accéléromètre
sensor->enableFeature(BMA423_WAKEUP, true); // Activer la fonction de réveille
sensor->enableWakeupInterrupt(); // Activer l'envoie du signal d'interruption en cas de réveil
On pourra ensuite gérer finement les réglages mais ce n’est pas nécessaire pour allumer l’écran de la T-Watch.
La boite au lettre xEventGroup est placée dans la loop(). On récupère le nouveau message à l’aide de la méthode xEventGroupGetBits(). On augmente la fréquence du CPU de l’ESP32 à 160MHz (jusqu’à 240MHz mais attention à l’impact sur l’autonomie de la batterie).
Il ne faut pas oublier de supprimer le message xEventGroupClearBits, sinon il ne sera pas possible de réveiller la montre la prochaine fois.
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);
}
}
Mise en veille profonde (deep sleep). Réveil par timer ou bouton principal
La T-Watch Touch est une fantastique plateforme de développement ESP32 que l’on peut compléter par de nombreuses cartes d’extension.
- Basic Expansion Board (livré avec chaque T-Watch), 2 connecteurs d’extension de 8 broches permettant d’accéder aux broches 33, 34, 21 (SDA), 22 (SCL) de l’ESP32 et IO0, IO1, IO2 et IO3 de l’AXP202 (gestionnaire d’alimentation). C’est la carte livrée en standard
- T-Fork connecteur pour breadboard
- GPS u-blox M8N
- Game, joystick et 4 boutons pour transformer la T-Watch en Gameboy !
- Motor & Speaker (Pack H329), vibreur + haut parleur
- MPR121, interface tactile externe
- SIM800L, modem GPRS
- MP3, lecteur MP3
- NFC, lecteur sans contact (attention, ce n’est pas un lecteur RFID)
- T-Car, permet de piloter jusqu’à 3 servo-moteur via le bus 1-Wire
- T-Quick, permet de piloter jusqu’à 2 moteurs en I2C
- MAX98357 (Pack H328), sortie audio I2S
- S76G Lora + GPS (Pack H327)
- S78G Lora (433 à 470 MHz) + GPS (Pack H397). Modem LoRa
Voir plus de cartes d’extension pour la T-Watch Touch ou No-Touch
On pourra très facilement développer un enregistreur de température ou de polluant, un tracker GPS, surveiller un accès et envoyer un SMS en cas d’intrusion… Toutes ces applications qui pourront fonctionner sur la batterie LiPo interne ne nécessite pas que l’ESP32 soit actif en permanence.
Demander au gestionnaire d’alimentation AXP202 d’éteindre les périphériques
Tous les périphériques de la montre sont alimentés par le micro-contrôleur AXP202 (y compris l’ESP32).
Avant de mettre la montre en veille, on va donc déjà devoir arrêter les périphériques sinon ils vont continuer à fonctionner et drainer la batterie, ce n’est pas vraiment ce qu’on cherche ici !
Il y a deux façons de faire en fonction du modèle de T-Watch utilisé.
- Pour la T-Watch 2020, il suffit d’appeler la fonction powerOff(). Cette méthode désactive uniquement la sortie LDO2
- Pour les autres modèles, il faut arrêter manuellement chaque sortie en appelant la fonction setPowerOutPut() de la classe power. Voici la liste des sorties utilisées.
Voici la liste des sorties de l’AXP202 utilisées par le Core PCB des T-Watch.
Sortie AXP202 | T-Watch (autres versions) | T-Watch 2020 V1 | T-Watch 2020 V2 |
---|---|---|---|
DC2 | Non utilisée | ||
DC3 | Alimentation ESP32. Ne pas désactiver !! | ||
LDO1 | Non disponible | ||
LDO2 | Backlight, rétro-éclairage de l’écran TFT
Sur T-Watch 2020 exécuter powerOff() Pour les autres T-Watch exécuter displaySleep() |
||
LDO3 | Alimentation fond de panier (carte d’extension) | Ecran TFT/TOUCHT | |
LDO4 | Alimentation carte d’extension S76/78G (Lora + GPS) uniquement | Non utilisée | Module GPS |
EXTEN | Non utilisée | Non utilisée | TOUCH RESET EN |
Voici un exemple qui permet d’éteindre manuellement les périphériques
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);
Programmer le réveil de la montre avec le bouton principal ou un Timer
Pour mettre l’ESP32 en sommeil profond (Deep Sleep en anglais), il suffit de prévoir des conditions de sortie.
Ici par exemple on programme le réveil lorsqu’on appuie sur le bouton principal sur l’entrée ext0.
esp_sleep_enable_ext0_wakeup((gpio_num_t)AXP202_INT, LOW);
Ou par un Timer qui va réveiller régulièrement l’ESP32. Pratique pour enregistrer régulièrement une position GPS, une température, une teneur en CO2…
esp_sleep_enable_timer_wakeup(uS_TO_S_FACTOR * TIME_TO_SLEEP);
Il ne reste plus qu’à mettre en veille l’ESP32
Pour aller plus loin avec le mode Deep Sleep de l’ESP32, vous pouvez continuer par la lecture de cet article
Code source complet de l’exemple
Voici le code source Arduino complet de l’exemple que vous pouvez également récupérer directement sur GitHub. Le projet peut être compilé à l’aide de l’IDE Arduino ou de PlatformIO.
Le code source peut servir de base pour le développement de votre propre projet de montre connectée.
- main.cpp
- gui.cpp
- gui.h
- platformio.ini
/*
Adaptation du code source original de Lewis Le : deep sleep, réveil par bouton
ou accéléromètre BMA423
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.
*/
#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);
}
/*******************************************/
/* Bascule en mode basse consommation */
/*******************************************/
void low_energy()
{
// Le rétro-éclairage est actif
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);
// Diminue la fréquence du CPU à 20MHz pour réduire la consommation
setCpuFrequencyMhz(20);
Serial.println("ENTER IN LIGHT SLEEEP MODE");
delay(50);
// Réveil avec le bouton principal
// Réveil avec le bouton principal
gpio_wakeup_enable ((gpio_num_t)AXP202_INT, GPIO_INTR_LOW_LEVEL);
esp_sleep_enable_gpio_wakeup();
esp_light_sleep_start();
}
// Le rétro-éclairage est éteint
} 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();
// Enregistre les pointeurs
// Enregistre les pointeurs
power = watch->power;
// User button handler | Superviseur bouton utilisateur
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;
// Optionnel
// // 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
// Active le BMA 423
sensor->enableAccel();
// Enable BMA423 isDoubleClick feature
sensor->enableFeature(BMA423_WAKEUP, true);
// Envoi un signal sur la broche 39 de l'ESP32 dès qu'un mouvement est détecté
sensor->enableWakeupInterrupt();
// Récupère les interruptions du contrôleur d'alimentation AXP202
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;
// rtc_clk_cpu_freq_set(RTC_CPU_FREQ_160M);
setCpuFrequencyMhz(160);
}
low_energy();
// Détruit le message WATCH_FLAG_BMA_IRQ (réveil avec le 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()) {
// Bascule en mode basse consommation lorsqu'on appuie sur le bouton principal
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);
// Active le réveil automatique. Utile pour un tracker GPS
esp_sleep_enable_timer_wakeup(uS_TO_S_FACTOR * TIME_TO_SLEEP);
esp_deep_sleep_start();
}
// Affiche la cause du réveil de l'ESP32
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.
*/
// 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()
{
}
/*
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 */
[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
Le code source a été testé sur la T-Watch Touch 2019
Ainsi que sur la T-Watch 2020
Mises à jour
26/11/2020 Publication de l’article
- T-Watch. Code simplifié pour extinction et réveil avec BMA423 ou AXP202 de l’ESP32
- T-Watch. Mise en veille et réveil de l’ESP32 avec accéléromètre BMA423 ou AXP202
- T-Watch. Menu, pages, navigation entre écrans avec TFT_eSPI
- T-Watch. Dessiner des fractales de Mandelbrot ou Julia avec un ESP32
- T-Watch. Afficher des images XBM (TFT_eSPI) et C++ (LVGL). Compatible ESP32, Arduino