diff --git a/examples/wifi-echo/server/esp32/README.screen.md b/examples/wifi-echo/server/esp32/README.screen.md new file mode 100644 index 00000000000000..e4792fdc59ee5c --- /dev/null +++ b/examples/wifi-echo/server/esp32/README.screen.md @@ -0,0 +1,91 @@ +# Simple Screen UI Framework + +## Overview + +This is a framework for creating simple user interfaces for devices with tiny +screens and just a few buttons. + +For example, the [M5Stack](http://m5stack.com/) ESP32 Basic Core IoT device has +a 320x240 TFT display and three push buttons. + +This framework enables a UI such as the following, where focus is indicated by +color, the left and middle buttons function as a single axis navpad, the right +button function as an action button, and the back button is provided by the +framework onscreen: + + +--------------------------------------+ + | < SCREEN TITLE | + | ------------ | + | | + | ITEM 1 | + | ITEM 2 | + | ITEM 3 | + | | + | | + | | + | | + | PREVIOUS NEXT ACTION | + +--------------------------------------+ + +## Features + +- stack of screens with system back button +- labeled buttons for navigation and action +- focus handling +- custom screen content +- virtual LEDs (VLEDs) + +## Screen Manager + +The screen manager maintains a stack of screens that are pushed and popped for +interaction. Each is drawn with a title, system back button, three button +labels, and custom screen content. The screen manager dispatches events to the +topmost screen, such as: + +- enter/exit events (including whether the screen was pushed or popped) +- focus events (for navigation) +- action events +- display events (lazily) + +If configured, virtual LEDs (VLEDs) are omnipresent and can be toggled at will. + +## Screen + +Screens provide a title and three button labels to the screen manager. They +handle events from the screen manager, and cooperate to ensure focus behaves +nicely. + +## Focus Handling + +Screens are created without focus, and indicate to the screen manager whether +they can receive focus. If so, the screen manager focuses them when they are +pushed, and subsequently dispatches focus next/previous events which the screen +can use for navigation before any action is performed. + +The screen can request the screen manager to focus the system back button, which +is always available except when there are no covered screens on the stack. In +this way, a list screen can wrap its focus of list items through the system back +button if it is available, while skipping it otherwise. + +Blur and unblur focus events allow a screen's focus state to be preserved when +it is covered by a pushed screen and restored when it is subsequently uncovered. + +In summary, screens collaborate with the screen manager to handle focus via the +following focus event types: + +- NONE: remove focus from screen completely +- BLUR: unfocus screen (but retain focus state) +- UNBLUR: restore focus state (that was retained when blurred) +- NEXT: navigate focus forward in screen (possibly requesting focus back + button) +- PREVIOUS: navigate focus forward (possibly requesting back button focus) + +## Typical Usage + +A list screen can provide multiple options to the user. Each list item can push +another screen on the stack with more items. In this way a hierarchy of screens +can be formed. + +The back button dismisses a screen much as "escape" or "cancel" would. It's +possible to push an informational screen that is not focusable and has no +interaction, such that the only action available is back. diff --git a/examples/wifi-echo/server/esp32/main/Display.cpp b/examples/wifi-echo/server/esp32/main/Display.cpp index 9edfc5494b50a7..d29c3483b38a95 100644 --- a/examples/wifi-echo/server/esp32/main/Display.cpp +++ b/examples/wifi-echo/server/esp32/main/Display.cpp @@ -38,7 +38,7 @@ #if CONFIG_HAVE_DISPLAY // Brightness picked such that it's easy for cameras to focus on -#define DEFFAULT_BRIGHTNESS_PERCENT 10 +#define DEFAULT_BRIGHTNESS_PERCENT 10 // 8MHz is the recommended SPI speed to init the driver with // It later gets set to the preconfigured defaults within the driver @@ -59,6 +59,8 @@ extern const char * TAG; uint16_t DisplayHeight = 0; uint16_t DisplayWidth = 0; +bool awake = false; + #if CONFIG_DISPLAY_AUTO_OFF // FreeRTOS timer used to turn the display off after a short while TimerHandle_t displayTimer = NULL; @@ -154,13 +156,16 @@ void SetBrightness(uint16_t brightness_percent) } } -void WakeDisplay() +bool WakeDisplay() { - SetBrightness(DEFFAULT_BRIGHTNESS_PERCENT); + bool woken = !awake; + awake = true; + SetBrightness(DEFAULT_BRIGHTNESS_PERCENT); #if CONFIG_DISPLAY_AUTO_OFF xTimerStart(displayTimer, 0); ESP_LOGI(TAG, "Display awake but will switch off automatically in %d seconds", DISPLAY_TIMEOUT_MS / 1000); #endif + return woken; } void ClearDisplay() @@ -197,6 +202,7 @@ void TimerCallback(TimerHandle_t xTimer) { ESP_LOGI(TAG, "Display going to sleep..."); SetBrightness(0); + awake = false; } void SetupBrightnessControl() diff --git a/examples/wifi-echo/server/esp32/main/LEDWidget.cpp b/examples/wifi-echo/server/esp32/main/LEDWidget.cpp index dffa392917c893..502a4b0a4f1899 100644 --- a/examples/wifi-echo/server/esp32/main/LEDWidget.cpp +++ b/examples/wifi-echo/server/esp32/main/LEDWidget.cpp @@ -24,7 +24,8 @@ */ #include "LEDWidget.h" -#include "Display.h" + +#include "ScreenManager.h" #include "driver/gpio.h" #include "esp_log.h" @@ -54,6 +55,8 @@ void LEDWidget::Init(gpio_num_t gpioNum) mBlinkOnTimeMS = 0; mBlinkOffTimeMS = 0; mGPIONum = gpioNum; + mVLED1 = -1; + mVLED2 = -1; mState = false; mError = false; errorTimer = NULL; @@ -87,9 +90,10 @@ void ClearErrorState(TimerHandle_t handle) #if CONFIG_HAVE_DISPLAY LEDWidget * pWidget = (LEDWidget *) pvTimerGetTimerID(handle); pWidget->mError = false; - pWidget->Display(); - // If a status change occured, wake the display - WakeDisplay(); + if (pWidget->mVLED2 != -1) + { + ScreenManager::SetVLED(pWidget->mVLED2, false); + } #endif } @@ -103,9 +107,10 @@ void LEDWidget::BlinkOnError() } errorTimer = xTimerCreate("ErrorTimer", pdMS_TO_TICKS(2000), false, this, ClearErrorState); xTimerStart(errorTimer, 0); - Display(); - // If a status change occured, wake the display - WakeDisplay(); + if (mVLED2 != -1) + { + ScreenManager::SetVLED(mVLED2, true); + } #endif } @@ -136,49 +141,26 @@ void LEDWidget::DoSet(bool state) if (stateChange) { #if CONFIG_HAVE_DISPLAY - - Display(); - // If a status change occured, wake the display - WakeDisplay(); + if (mVLED1 != -1) + { + ScreenManager::SetVLED(mVLED1, mState); + } #endif } } #if CONFIG_HAVE_DISPLAY -void LEDWidget::Display() +void LEDWidget::SetVLED(int id1, int id2) { - uint16_t msgX = 0; - uint16_t msgY = (DisplayHeight * LED_STATUS_POSITION) / 100; - uint16_t circleX = (LED_INDICATOR_X * DisplayWidth) / 100; - uint16_t circleY = (LED_INDICATOR_Y * DisplayHeight) / 100; - - // Wipe the Light Status Area - ClearRect(0, LED_STATUS_POSITION); - // Wipe the status circle - TFT_fillCircle(circleX, circleY, LED_INDICATOR_R_PX, TFT_BLACK); - // Display the Light Status on screen - TFT_setFont(DEJAVU24_FONT, NULL); - // Draw the default "Off" indicator - TFT_drawCircle(circleX, circleY, LED_INDICATOR_R_PX, TFT_DARKGREY); - - if (mError) + mVLED1 = id1; + if (mVLED1 != -1) { - TFT_print((char *) "Recv Error", msgX, msgY); - // Draw the "Error" indicator - TFT_fillCircle(circleX, circleY, LED_INDICATOR_R_PX, TFT_RED); + ScreenManager::SetVLED(mVLED1, mState); } - else + mVLED2 = id2; + if (mVLED2 != -1) { - if (mState) - { - TFT_print((char *) onMsg, msgX, msgY); - // Draw the "ON" indicator - TFT_fillCircle(circleX, circleY, LED_INDICATOR_R_PX, TFT_GREEN); - } - else - { - TFT_print((char *) offMsg, msgX, msgY); - } + ScreenManager::SetVLED(mVLED2, mError); } } #endif diff --git a/examples/wifi-echo/server/esp32/main/ListScreen.cpp b/examples/wifi-echo/server/esp32/main/ListScreen.cpp new file mode 100644 index 00000000000000..82876b76c59998 --- /dev/null +++ b/examples/wifi-echo/server/esp32/main/ListScreen.cpp @@ -0,0 +1,104 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file ListScreen.cpp + * + * Simple list screen. + * + */ + +#include "ListScreen.h" + +#if CONFIG_HAVE_DISPLAY + +#include + +namespace { + +const char * buttonText[] = { "Up", "Down", "Action" }; + +}; + +std::string ListScreen::GetButtonText(int id) +{ + return buttonText[id - 1]; +} + +void ListScreen::Display() +{ + int i = 0; + int items = (DisplayHeight - ScreenTitleSafeTop - ScreenTitleSafeBottom) / ScreenFontHeight; + if (items < model->GetItemCount()) + { + i = std::max(0, focusIndex - items + (focusIndex == model->GetItemCount() - 1 ? 1 : 2)); + } + + for (int count = 0, y = ScreenTitleSafeTop; i < model->GetItemCount() && count < items; ++i, ++count, y += ScreenFontHeight) + { + tft_fg = focusIndex == i ? ScreenFocusColor : ScreenNormalColor; + TFT_print((char *) model->GetItemText(i).c_str(), ScreenTitleSafeTop, y); + } +} + +void ListScreen::Focus(FocusType focus) +{ + switch (focus) + { + case FocusType::NONE: + hasFocus = false; + focusIndex = -1; + break; + case FocusType::BLUR: + hasFocus = false; + // leave focus index alone + break; + case FocusType::UNBLUR: + hasFocus = true; + // leave focus index alone + break; + case FocusType::NEXT: + hasFocus = true; + if (focusIndex == -1) + { + focusIndex = 0; + break; + } + focusIndex = (focusIndex + 1) % model->GetItemCount(); // wraparound + if (focusIndex == 0) + { + ScreenManager::FocusBack(); // try focus back if it did wrap + } + break; + case FocusType::PREVIOUS: + hasFocus = true; + if (focusIndex == -1) + { + focusIndex = model->GetItemCount() - 1; + break; + } + focusIndex = (focusIndex + model->GetItemCount() - 1) % model->GetItemCount(); // wraparound + if (focusIndex == model->GetItemCount() - 1) + { + ScreenManager::FocusBack(); // try focus back if it did wrap + } + break; + } +} + +#endif // CONFIG_HAVE_DISPLAY diff --git a/examples/wifi-echo/server/esp32/main/QRCodeScreen.cpp b/examples/wifi-echo/server/esp32/main/QRCodeScreen.cpp new file mode 100644 index 00000000000000..f43e798aa47df9 --- /dev/null +++ b/examples/wifi-echo/server/esp32/main/QRCodeScreen.cpp @@ -0,0 +1,103 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file QRCodeScreen.cpp + * + * Screen which displays a QR code. + * + */ + +#include "QRCodeScreen.h" + +#if CONFIG_HAVE_DISPLAY + +// TODO organize includes below + +#include "esp_log.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "qrcodegen.h" + +#include +#include +#include + +// TODO need sensible library tag when put in library +extern const char * TAG; + +namespace { + +constexpr int kVersion = 4; +constexpr int kModuleSize = 4; +constexpr int kBorderSize = 1; + +color_t qrCodeColor = TFT_LIGHTGREY; + +}; // namespace + +QRCodeScreen::QRCodeScreen(std::string text, std::string title) : title(title) +{ + constexpr int qrCodeSize = qrcodegen_BUFFER_LEN_FOR_VERSION(kVersion); + + // TODO check text length against max size permitted, or maybe adjust version used accordingly + + std::vector temp(qrCodeSize); + qrCode.resize(qrCodeSize); + + if (!qrcodegen_encodeText(text.c_str(), temp.data(), qrCode.data(), qrcodegen_Ecc_LOW, kVersion, kVersion, qrcodegen_Mask_AUTO, + true)) + { + ESP_LOGE(TAG, "qrcodegen_encodeText() failed"); + qrCode.clear(); + } +} + +void QRCodeScreen::Display() +{ + if (qrCode.empty()) + { + return; + } + + const uint8_t * data = qrCode.data(); + const int size = qrcodegen_getSize(data); + const int displaySize = (2 * kBorderSize + size) * kModuleSize; + const int displayX = (DisplayWidth - displaySize) / 2; + const int displayY = ScreenTitleSafeTop + ((DisplayHeight - ScreenTitleSafeTop - ScreenTitleSafeBottom) - displaySize) / 2; + + TFT_fillRect(displayX, displayY, displaySize, displaySize, qrCodeColor); + + for (int y = 0; y < size; ++y) + { + for (int x = 0; x < size; ++x) + { + if (qrcodegen_getModule(data, x, y)) + { + TFT_fillRect(displayX + (kBorderSize + x) * kModuleSize, displayY + (kBorderSize + y) * kModuleSize, kModuleSize, + kModuleSize, TFT_BLACK); + } + } + } +} + +#endif // CONFIG_HAVE_DISPLAY diff --git a/examples/wifi-echo/server/esp32/main/QRCodeWidget.cpp b/examples/wifi-echo/server/esp32/main/QRCodeWidget.cpp deleted file mode 100644 index 302a543d5986d1..00000000000000 --- a/examples/wifi-echo/server/esp32/main/QRCodeWidget.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/* - * - * Copyright (c) 2020 Project CHIP Authors - * Copyright (c) 2018 Nest Labs, Inc. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @file QRCodeWidget.cpp - * - * This file implements the QRCodeWidget that displays a QRCode centered on the screen - * It generates a CHIP SetupPayload for onboarding. - * - */ - -#include "esp_log.h" -#include "esp_system.h" -#include "esp_wifi.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - -#include "qrcodegen.h" - -#include -#include -#include - -#include "Display.h" -#include "QRCodeWidget.h" - -// A temporary value assigned for this example's QRCode -// Spells CHIP on a dialer -#define EXAMPLE_VENDOR_ID 2447 -// Spells ESP32 on a dialer -#define EXAMPLE_PRODUCT_ID 37732 -// Used to have an initial shared secret -#define EXAMPLE_SETUP_CODE 123456789 -// Used to discriminate the device -#define EXAMPLE_DISCRIMINATOR 0X0F00 -// Used to indicate that an IP address has been added to the QRCode -#define EXAMPLE_VENDOR_TAG_IP 1 - -#if CONFIG_HAVE_DISPLAY - -extern const char * TAG; - -using namespace ::chip; -using namespace ::chip::DeviceLayer; - -// QRCode config -enum -{ - kQRCodeVersion = 4, - kQRCodeModuleSizePix = 4, - kQRCodePadding = 2 -}; - -void GetGatewayIP(char * ip_buf, size_t ip_len) -{ - tcpip_adapter_ip_info_t ip; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); - IPAddress::FromIPv4(ip.ip).ToString(ip_buf, ip_len); - ESP_LOGE(TAG, "Got gateway ip %s", ip_buf); -} - -string createSetupPayload() -{ - SetupPayload payload; - payload.version = 1; - payload.discriminator = EXAMPLE_DISCRIMINATOR; - payload.setUpPINCode = EXAMPLE_SETUP_CODE; - payload.rendezvousInformation = static_cast(CONFIG_RENDEZVOUS_MODE); - payload.vendorID = EXAMPLE_VENDOR_ID; - payload.productID = EXAMPLE_PRODUCT_ID; - - char gw_ip[INET6_ADDRSTRLEN]; - GetGatewayIP(gw_ip, sizeof(gw_ip)); - payload.addOptionalVendorData(EXAMPLE_VENDOR_TAG_IP, gw_ip); - - QRCodeSetupPayloadGenerator generator(payload); - string result; - size_t tlvDataLen = sizeof(gw_ip); - uint8_t tlvDataStart[tlvDataLen]; - CHIP_ERROR err = generator.payloadBase41Representation(result, tlvDataStart, tlvDataLen); - if (err != CHIP_NO_ERROR) - { - ESP_LOGE(TAG, "Couldn't get payload string %d", generator.payloadBase41Representation(result)); - } - return result; -}; - -QRCodeWidget::QRCodeWidget() -{ - QRCodeColor = TFT_DARKGREY; - VMargin = 10; -} - -void QRCodeWidget::Display() -{ - CHIP_ERROR err = CHIP_NO_ERROR; - enum - { - kMaxQRCodeStrLength = 120 - }; - - string generatedQRCodeData; - char * qrCodeStr = NULL; - uint8_t * qrCodeTempBuf = NULL; - uint8_t * qrCode = NULL; - uint16_t qrCodeDisplaySize, qrCodeX, qrCodeY, qrCodeXOffset, qrCodeYOffset; - - generatedQRCodeData = createSetupPayload(); - qrCodeStr = (char *) generatedQRCodeData.c_str(); - - // Generate the QR code. - uint16_t qrCodeSize = qrcodegen_BUFFER_LEN_FOR_VERSION(kQRCodeVersion); - qrCodeTempBuf = (uint8_t *) malloc(qrCodeSize); - qrCode = (uint8_t *) malloc(qrCodeSize); - VerifyOrExit(qrCodeTempBuf != NULL, err = CHIP_ERROR_NO_MEMORY); - VerifyOrExit(qrCode != NULL, err = CHIP_ERROR_NO_MEMORY); - if (!qrcodegen_encodeText(qrCodeStr, qrCodeTempBuf, qrCode, qrcodegen_Ecc_LOW, kQRCodeVersion, kQRCodeVersion, - qrcodegen_Mask_AUTO, true)) - { - ESP_LOGE(TAG, "qrcodegen_encodeText() failed"); - ExitNow(err = CHIP_ERROR_INCORRECT_STATE); - } - - // Draw the QR code image on the screen. - qrCodeDisplaySize = (qrcodegen_getSize(qrCode) + kQRCodePadding) * kQRCodeModuleSizePix; - qrCodeX = (DisplayWidth - qrCodeDisplaySize) / 2; - qrCodeY = (DisplayHeight * VMargin) / 100; - qrCodeXOffset = qrCodeX + kQRCodeModuleSizePix; - qrCodeYOffset = qrCodeY + kQRCodeModuleSizePix; - TFT_fillRect(qrCodeX, qrCodeY, (int) qrCodeDisplaySize, (int) qrCodeDisplaySize, QRCodeColor); - for (uint8_t y = 0; y < qrcodegen_getSize(qrCode); y++) - { - for (uint8_t x = 0; x < qrcodegen_getSize(qrCode); x++) - { - if (qrcodegen_getModule(qrCode, x, y)) - { - TFT_fillRect(x * kQRCodeModuleSizePix + qrCodeXOffset, y * kQRCodeModuleSizePix + qrCodeYOffset, - (int) kQRCodeModuleSizePix, (int) kQRCodeModuleSizePix, TFT_BLACK); - } - } - } - -exit: - if (qrCode != NULL) - { - free(qrCode); - } - if (qrCodeTempBuf != NULL) - { - free(qrCodeTempBuf); - } - if (err != CHIP_NO_ERROR) - { - ESP_LOGE(TAG, "QRCodeWidget::Display() failed: %s", ErrorStr(err)); - } -} - -#endif // CONFIG_HAVE_DISPLAY diff --git a/examples/wifi-echo/server/esp32/main/include/QRCodeWidget.h b/examples/wifi-echo/server/esp32/main/Screen.cpp similarity index 62% rename from examples/wifi-echo/server/esp32/main/include/QRCodeWidget.h rename to examples/wifi-echo/server/esp32/main/Screen.cpp index 6986775ce0cf16..1f53115cb761b9 100644 --- a/examples/wifi-echo/server/esp32/main/include/QRCodeWidget.h +++ b/examples/wifi-echo/server/esp32/main/Screen.cpp @@ -17,33 +17,25 @@ */ /** - * @file QRCodeWidget.cpp + * @file Screen.cpp * - * This file describes the QRCodeWidget that displays a QRCode centered on the screen + * Simple screen. * */ -#ifndef QRCODE_WIDGET_H -#define QRCODE_WIDGET_H - -#include "Display.h" +#include "Screen.h" #if CONFIG_HAVE_DISPLAY -class QRCodeWidget -{ -public: - color_t QRCodeColor; - uint16_t VMargin; - /** - * @brief - * Initializes the QRCode by generating a CHIP SetupPayload - * and draws it onto the display - */ - void Display(); - QRCodeWidget(); +namespace { + +const char * buttonText[] = { "Previous", "Next", "Action" }; + }; -#endif // CONFIG_HAVE_DISPLAY +std::string Screen::GetButtonText(int id) +{ + return buttonText[id - 1]; +} -#endif // QRCODE_WIDGET_H +#endif // CONFIG_HAVE_DISPLAY diff --git a/examples/wifi-echo/server/esp32/main/ScreenManager.cpp b/examples/wifi-echo/server/esp32/main/ScreenManager.cpp new file mode 100644 index 00000000000000..649551e0ccf5cd --- /dev/null +++ b/examples/wifi-echo/server/esp32/main/ScreenManager.cpp @@ -0,0 +1,330 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file ScreenManager.cpp + * + * Simple screen manager. + * + */ + +#include "ScreenManager.h" + +#if CONFIG_HAVE_DISPLAY + +#include +#include + +uint16_t ScreenFontHeight; +uint16_t ScreenTitleSafeTop; +uint16_t ScreenTitleSafeBottom; + +color_t ScreenNormalColor = { 255, 255, 255 }; +color_t ScreenFocusColor = { 128, 128, 255 }; +color_t ScreenButtonColor = { 64, 64, 64 }; + +namespace { + +constexpr int kMainFont = DEJAVU24_FONT; +constexpr int kButtonFont = DEJAVU18_FONT; + +constexpr int kVLEDWidth = 8; +constexpr int kVLEDHeight = 16; + +SemaphoreHandle_t mutex; + +struct Lock +{ + Lock() { xSemaphoreTakeRecursive(mutex, portMAX_DELAY); } + ~Lock() { xSemaphoreGive(mutex); } +}; + +struct VLED +{ + color_t color; + color_t color_off; + bool on; + VLED(color_t color) : color(color), on(false) + { + color_off = color; + color_off.r &= 0x1F; + color_off.g &= 0x1F; + color_off.b &= 0x1F; + } +}; + +std::vector vleds; + +std::vector screens; + +bool focusBack = false; + +int lazyDisplay = 0; +bool dirtyDisplay = false; + +struct LazyDisplay +{ + LazyDisplay() { ++lazyDisplay; } + ~LazyDisplay() + { + if (--lazyDisplay == 0) + { + if (dirtyDisplay) + { + ScreenManager::Display(); + dirtyDisplay = false; + } + } + } +}; + +// Print text centered horizontally at x. +void PrintCentered(const char * s, int x, int y) +{ + TFT_print((char *) s, x - (TFT_getStringWidth((char *) s) / 2), y); +} + +// Print button text in appropriate location (1 to 3). +void DisplayButtonText(int id, const char * s) +{ + tft_fg = ScreenButtonColor; + int x = (DisplayWidth / 2) + (id - 2) * (DisplayWidth * 3 / 10); + PrintCentered(s, x, DisplayHeight - (ScreenTitleSafeBottom / 2)); // within ScreenTitleSafeBottom +} + +void DisplayVLED(int id) +{ + TFT_fillRect(0, ScreenFontHeight * 3 / 2 + id * (kVLEDHeight + 2), kVLEDWidth, kVLEDHeight, + vleds[id].on ? vleds[id].color : vleds[id].color_off); +} + +}; // namespace + +void ScreenManager::Init() +{ + mutex = xSemaphoreCreateRecursiveMutex(); + + // https://github.com/loboris/ESP32_TFT_library/issues/48 + TFT_setFont(kButtonFont, nullptr); + ScreenTitleSafeBottom = TFT_getfontheight() * 2; + TFT_setFont(kMainFont, nullptr); + ScreenFontHeight = TFT_getfontheight(); + ScreenTitleSafeTop = ScreenFontHeight * 5 / 2; +} + +void ScreenManager::Display() +{ + Lock lock; + + if (lazyDisplay) + { + dirtyDisplay = true; + return; + } + + TFT_fillScreen(TFT_BLACK); + TFT_setFont(kMainFont, nullptr); + + if (screens.empty()) + { + tft_fg = TFT_RED; + PrintCentered("No Screen", DisplayWidth / 2, DisplayHeight / 2); + return; + } + + if (screens.size() > 1) + { + tft_fg = focusBack ? ScreenFocusColor : ScreenNormalColor; + TFT_print("<", ScreenFontHeight, ScreenFontHeight / 2); + } + + std::string title = screens.back()->GetTitle(); + tft_fg = ScreenNormalColor; + TFT_print((char *) title.c_str(), ScreenTitleSafeTop, ScreenFontHeight / 2); // within ScreenTitleSafeTop + TFT_drawRect(ScreenTitleSafeTop, ScreenFontHeight * 3 / 2, TFT_getStringWidth((char *) title.c_str()), 2, ScreenNormalColor); + + TFT_setFont(kButtonFont, nullptr); + if (screens.back()->IsFocusable()) + { + DisplayButtonText(1, screens.back()->GetButtonText(1).c_str()); + DisplayButtonText(2, screens.back()->GetButtonText(2).c_str()); + } + if (focusBack) + { + DisplayButtonText(3, "Back"); + } + else if (screens.back()->IsFocusable()) + { + DisplayButtonText(3, screens.back()->GetButtonText(3).c_str()); + } + TFT_setFont(kMainFont, nullptr); + + for (int i = 0; i < vleds.size(); ++i) + { + DisplayVLED(i); + } + + screens.back()->Display(); +} + +void ScreenManager::ButtonPressed(int id) +{ + Lock lock; + LazyDisplay lazy; + + if (screens.empty()) + { + return; + } + + if (focusBack && id == 3) + { + PopScreen(); + } + else if (screens.back()->IsFocusable()) + { + switch (id) + { + case 1: + focusBack = false; + screens.back()->Focus(Screen::FocusType::PREVIOUS); + break; + case 2: + focusBack = false; + screens.back()->Focus(Screen::FocusType::NEXT); + break; + case 3: + screens.back()->Action(); + break; + } + Display(); + } +} + +void ScreenManager::PushScreen(Screen * screen) +{ + Lock lock; + LazyDisplay lazy; + + if (!screens.empty()) + { + if (screens.back()->IsFocusable()) + { + screens.back()->Focus(Screen::FocusType::BLUR); + } + screens.back()->Exit(false); + } + + screen->Enter(true); // screen is not top when enter/pushed + screens.push_back(screen); // screen is pushed immediately after first enter + + focusBack = false; + + if (screens.back()->IsFocusable()) + { + screens.back()->Focus(Screen::FocusType::NEXT); + } + else + { + focusBack = true; + } + + Display(); +} + +void ScreenManager::PopScreen() +{ + Lock lock; + LazyDisplay lazy; + + if (screens.empty()) + { + return; + } + + Screen * screen = screens.back(); + screens.pop_back(); // screen is popped immediately before last exit + screen->Exit(true); // screen is not top when exit/popped + delete screen; + + focusBack = false; + + if (!screens.empty()) + { + screens.back()->Enter(false); + if (screens.back()->IsFocusable()) + { + screens.back()->Focus(Screen::FocusType::UNBLUR); + } + else + { + focusBack = true; + } + } + + Display(); +} + +void ScreenManager::FocusBack() +{ + Lock lock; + if (screens.size() > 1) + { + focusBack = true; + if (screens.back()->IsFocusable()) + { + screens.back()->Focus(Screen::FocusType::NONE); + } + } + else + { + focusBack = false; + } +} + +int ScreenManager::AddVLED(color_t color) +{ + Lock lock; + int id = vleds.size(); + vleds.emplace_back(color); + DisplayVLED(id); + return id; +} + +void ScreenManager::SetVLED(int id, bool on) +{ + Lock lock; + if (vleds[id].on == on) + { + return; + } + + vleds[id].on = on; + DisplayVLED(id); + WakeDisplay(); +} + +void ScreenManager::ToggleVLED(int id) +{ + Lock lock; + vleds[id].on = !vleds[id].on; + DisplayVLED(id); + WakeDisplay(); +} + +#endif // CONFIG_HAVE_DISPLAY diff --git a/examples/wifi-echo/server/esp32/main/include/Display.h b/examples/wifi-echo/server/esp32/main/include/Display.h index 3c6bd39b1f33a0..73df406ae64b94 100644 --- a/examples/wifi-echo/server/esp32/main/include/Display.h +++ b/examples/wifi-echo/server/esp32/main/include/Display.h @@ -92,8 +92,10 @@ extern void DisplayStatusMessage(char * msg, uint16_t vpos); /** * @brief * Reset the display timeout and set the brightness back up to default values + * + * @return true If the display was woken */ -extern void WakeDisplay(); +extern bool WakeDisplay(); #endif // #if CONFIG_HAVE_DISPLAY diff --git a/examples/wifi-echo/server/esp32/main/include/LEDWidget.h b/examples/wifi-echo/server/esp32/main/include/LEDWidget.h index dc625486e64d3c..9941845adfd61c 100644 --- a/examples/wifi-echo/server/esp32/main/include/LEDWidget.h +++ b/examples/wifi-echo/server/esp32/main/include/LEDWidget.h @@ -38,19 +38,23 @@ class LEDWidget void BlinkOnError(); void Animate(); #if CONFIG_HAVE_DISPLAY - void Display(); + void SetVLED(int id1, int id2); #endif - bool mError; - TimerHandle_t errorTimer; private: int64_t mLastChangeTimeUS; uint32_t mBlinkOnTimeMS; uint32_t mBlinkOffTimeMS; gpio_num_t mGPIONum; + int mVLED1; + int mVLED2; bool mState; + bool mError; + TimerHandle_t errorTimer; void DoSet(bool state); + + friend void ClearErrorState(TimerHandle_t); }; #endif // TITLE_WIDGET_H diff --git a/examples/wifi-echo/server/esp32/main/include/ListScreen.h b/examples/wifi-echo/server/esp32/main/include/ListScreen.h new file mode 100644 index 00000000000000..c945382f8484aa --- /dev/null +++ b/examples/wifi-echo/server/esp32/main/include/ListScreen.h @@ -0,0 +1,128 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file ListScreen.h + * + * Simple list screen. + * + */ + +#ifndef LIST_SCREEN_H +#define LIST_SCREEN_H + +#include "Screen.h" +#include "ScreenManager.h" + +#if CONFIG_HAVE_DISPLAY + +#include +#include +#include +#include + +class ListScreen : public Screen +{ +public: + class Model + { + public: + virtual ~Model() = default; + virtual std::string GetTitle() { return std::string(); } + virtual int GetItemCount() { return 0; } + virtual std::string GetItemText(int i) { return std::string(); } + virtual void ItemAction(int i) {} + }; + +private: + Model * model = nullptr; + bool hasFocus = false; + int focusIndex = -1; + +public: + ListScreen(Model * model) : model(model) {} + + virtual ~ListScreen() { delete model; } + + virtual std::string GetTitle() { return model->GetTitle(); } + + virtual std::string GetButtonText(int id); + + virtual void Display(); + + virtual bool IsFocusable() { return model->GetItemCount() > 0; } + + virtual void Focus(FocusType focus); + + virtual void Action() { model->ItemAction(focusIndex); } +}; + +class SimpleListModel : public ListScreen::Model +{ + std::string title; + std::function action; + std::vector>> items; + +public: + virtual std::string GetTitle() { return title; } + virtual int GetItemCount() { return items.size(); } + virtual std::string GetItemText(int i) { return std::get<0>(items[i]); } + + virtual void ItemAction(int i) + { + auto & action = std::get<1>(items[i]); + if (action) + { + action(); + } + else if (this->action) + { + this->action(i); + } + } + + // Builder interface. + + SimpleListModel * Title(std::string title) + { + this->title = std::move(title); + return this; + } + + SimpleListModel * Action(std::function action) + { + this->action = std::move(action); + return this; + } + + SimpleListModel * Item(std::string text) + { + items.emplace_back(std::move(text), std::move(std::function())); + return this; + } + + SimpleListModel * Item(std::string text, std::function action) + { + items.emplace_back(std::move(text), std::move(action)); + return this; + } +}; + +#endif // CONFIG_HAVE_DISPLAY + +#endif // LIST_SCREEN_H diff --git a/examples/wifi-echo/server/esp32/main/include/QRCodeScreen.h b/examples/wifi-echo/server/esp32/main/include/QRCodeScreen.h new file mode 100644 index 00000000000000..446118bd8ab2be --- /dev/null +++ b/examples/wifi-echo/server/esp32/main/include/QRCodeScreen.h @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file QRCodeScreen.h + * + * Screen which displays a QR code. + * + */ + +#ifndef QR_CODE_SCREEN_H +#define QR_CODE_SCREEN_H + +#include "Screen.h" +#include "ScreenManager.h" + +#if CONFIG_HAVE_DISPLAY + +#include +#include + +class QRCodeScreen : public Screen +{ + std::vector qrCode; + std::string title; + +public: + QRCodeScreen(std::string text, std::string title = "QR Code"); + + virtual std::string GetTitle() { return title; } + + virtual void Display(); +}; + +#endif // CONFIG_HAVE_DISPLAY + +#endif // QR_CODE_SCREEN_H diff --git a/examples/wifi-echo/server/esp32/main/include/Screen.h b/examples/wifi-echo/server/esp32/main/include/Screen.h new file mode 100644 index 00000000000000..ae51884eb58424 --- /dev/null +++ b/examples/wifi-echo/server/esp32/main/include/Screen.h @@ -0,0 +1,70 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file Screen.h + * + * Simple screen. + * + */ + +#ifndef SCREEN_H +#define SCREEN_H + +#include "Display.h" + +#if CONFIG_HAVE_DISPLAY + +#include "ScreenManager.h" + +#include + +class Screen +{ +public: + enum FocusType + { + NONE, + BLUR, + UNBLUR, + NEXT, + PREVIOUS + }; + + virtual ~Screen() = default; + + virtual std::string GetTitle() { return "Untitled"; } + + virtual std::string GetButtonText(int id); + + virtual void Display() {} + + virtual void Enter(bool pushed) {} + + virtual void Exit(bool popped) {} + + virtual bool IsFocusable() { return false; } + + virtual void Focus(FocusType focus) {} + + virtual void Action() {} +}; + +#endif // CONFIG_HAVE_DISPLAY + +#endif // SCREEN_H diff --git a/examples/wifi-echo/server/esp32/main/include/ScreenManager.h b/examples/wifi-echo/server/esp32/main/include/ScreenManager.h new file mode 100644 index 00000000000000..301d19d92e76ee --- /dev/null +++ b/examples/wifi-echo/server/esp32/main/include/ScreenManager.h @@ -0,0 +1,67 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file ScreenManager.h + * + * Simple screen manager. + * + */ + +#ifndef SCREEN_MANAGER_H +#define SCREEN_MANAGER_H + +#include "Display.h" +#include "Screen.h" + +#if CONFIG_HAVE_DISPLAY + +extern uint16_t ScreenFontHeight; +extern uint16_t ScreenTitleSafeTop; +extern uint16_t ScreenTitleSafeBottom; + +extern color_t ScreenNormalColor; +extern color_t ScreenFocusColor; + +class Screen; + +class ScreenManager +{ +public: + static void Init(); + + static void Display(); + + static void ButtonPressed(int id); + + static void PushScreen(Screen * screen); + + static void PopScreen(); + + static void FocusBack(); + + static int AddVLED(color_t color); + + static void SetVLED(int id, bool on); + + static void ToggleVLED(int id); +}; + +#endif // CONFIG_HAVE_DISPLAY + +#endif // SCREEN_MANAGER_H diff --git a/examples/wifi-echo/server/esp32/main/wifi-echo.cpp b/examples/wifi-echo/server/esp32/main/wifi-echo.cpp index 64a7b9af6b73ea..8f6a9c07a7feb3 100644 --- a/examples/wifi-echo/server/esp32/main/wifi-echo.cpp +++ b/examples/wifi-echo/server/esp32/main/wifi-echo.cpp @@ -21,7 +21,9 @@ #include "Display.h" #include "EchoDeviceCallbacks.h" #include "LEDWidget.h" -#include "QRCodeWidget.h" +#include "ListScreen.h" +#include "QRCodeScreen.h" +#include "ScreenManager.h" #include "esp_event_loop.h" #include "esp_heap_caps_init.h" #include "esp_log.h" @@ -33,10 +35,15 @@ #include "nvs_flash.h" #include "tcpip_adapter.h" -#include + +#include +#include +#include +#include #include #include +#include #include #include @@ -50,13 +57,19 @@ extern void startClient(void); #if CONFIG_DEVICE_TYPE_M5STACK -#define ATTENTION_BUTTON_GPIO_NUM GPIO_NUM_37 // Use the right button (button "C") as the attention button on M5Stack -#define STATUS_LED_GPIO_NUM GPIO_NUM_MAX // No status LED on M5Stack +#define BUTTON_1_GPIO_NUM GPIO_NUM_39 // Left button on M5Stack +#define BUTTON_2_GPIO_NUM GPIO_NUM_38 // Middle button on M5Stack +#define BUTTON_3_GPIO_NUM GPIO_NUM_37 // Right button on M5Stack +#define STATUS_LED_GPIO_NUM GPIO_NUM_MAX // No status LED on M5Stack +#define LIGHT_CONTROLLER_OUTPUT_GPIO_NUM GPIO_NUM_2 // Use GPIO2 as the light controller output on M5Stack #elif CONFIG_DEVICE_TYPE_ESP32_DEVKITC -#define ATTENTION_BUTTON_GPIO_NUM GPIO_NUM_0 // Use the IO0 button as the attention button on ESP32-DevKitC and compatibles -#define STATUS_LED_GPIO_NUM GPIO_NUM_2 // Use LED1 (blue LED) as status LED on DevKitC +#define BUTTON_1_GPIO_NUM GPIO_NUM_34 // Button 1 on DevKitC +#define BUTTON_2_GPIO_NUM GPIO_NUM_35 // Button 2 on DevKitC +#define BUTTON_3_GPIO_NUM GPIO_NUM_0 // Button 3 on DevKitC +#define STATUS_LED_GPIO_NUM GPIO_NUM_2 // Use LED1 (blue LED) as status LED on DevKitC +#define LIGHT_CONTROLLER_OUTPUT_GPIO_NUM GPIO_NUM_33 // Use GPIO33 as the light controller output on DevKitC #else // !CONFIG_DEVICE_TYPE_ESP32_DEVKITC @@ -64,18 +77,28 @@ extern void startClient(void); #endif // !CONFIG_DEVICE_TYPE_ESP32_DEVKITC -#if CONFIG_HAVE_DISPLAY +// A temporary value assigned for this example's QRCode +// Spells CHIP on a dialer +#define EXAMPLE_VENDOR_ID 2447 +// Spells ESP32 on a dialer +#define EXAMPLE_PRODUCT_ID 37732 +// Used to have an initial shared secret +#define EXAMPLE_SETUP_CODE 123456789 +// Used to discriminate the device +#define EXAMPLE_DISCRIMINATOR 0X0F00 +// Used to indicate that an IP address has been added to the QRCode +#define EXAMPLE_VENDOR_TAG_IP 1 -static QRCodeWidget sQRCodeWidget; +#if CONFIG_HAVE_DISPLAY // Where to draw the connection status message #define CONNECTION_MESSAGE 75 // Where to draw the IPv6 information #define IPV6_INFO 85 + #endif // CONFIG_HAVE_DISPLAY LEDWidget statusLED; -static Button attentionButton; const char * TAG = "wifi-echo-demo"; @@ -87,6 +110,249 @@ namespace { SecureSessionMgr sTransportIPv4; SecureSessionMgr sTransportIPv6; +std::vector