first commit
This commit is contained in:
commit
5893b00dd2
1669 changed files with 1982740 additions and 0 deletions
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
For ArduinoIDE
|
||||
*/
|
||||
#include "main/MultipleUnits.cpp"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# M5UnitUnified example
|
||||
## MultipleUnits
|
||||
### Overview
|
||||
This is a demo program that was shown at MFT2024 and M5JPTour2024.
|
||||
It displays information on 4 units (Vmeter, TVOC, ENVIII, HEART) via UnitPaHub2.
|
||||
The address of UnitPaHub2 must be 0x71 (because it conflicts with ENVIII).
|
||||
See also https://docs.m5stack.com/en/unit/pahub2
|
||||
|
||||
NOTICE: Use Core devices capable of displaying 320x240 pixels
|
||||
|
||||
### ArduinoIDE
|
||||
Install each unit's library with the library manager.
|
||||
|
||||
### PlatformIO
|
||||
For convenience of unit testing, the libraries for each unit are registered in lib\_deps.
|
||||
Use the env of the applicable Core device.
|
||||
|
||||
---
|
||||
### 概要
|
||||
MFT2024, M5JPTour2024 にて公開されていたデモプログラムです。
|
||||
UnitPaHub2 を介して 4つのユニット (Vmeter, TVOC, ENVIII, HEART) の情報を表示します。
|
||||
UnitPaHub2 のアドレスを 0x71 にする必要があります(ENVIII と衝突するため)
|
||||
こちらを参照 https://docs.m5stack.com/en/unit/pahub2
|
||||
|
||||
注意: 320x240 ピクセルの表示ができる Core デバイスを使用してください。
|
||||
|
||||
### ArduinoIDE
|
||||
各ユニットのライブラリをライブラリマネージャでインストールしてください。
|
||||
|
||||
### PlatformIO
|
||||
ユニットテストの都合上、各ユニット毎のライブラリは lib\_deps に登録されています
|
||||
該当する Core デバイスの env を使用しください。
|
||||
|
||||
|
|
@ -0,0 +1,432 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
Demonstration of using M5UnitUnified with multiple units
|
||||
|
||||
Required Devices:
|
||||
- Any Core with LCD
|
||||
- UnitPaHub2
|
||||
- UnitVmeter : 0
|
||||
- UnitTVOC : 1
|
||||
- UnitENV3 : 2
|
||||
- UnitHEART : 3
|
||||
*/
|
||||
#include <M5Unified.h>
|
||||
#include <M5UnitUnified.h>
|
||||
#include <M5UnitUnifiedHUB.h>
|
||||
#include <M5UnitUnifiedENV.h> // ENVIII,TVOC
|
||||
#include <M5UnitUnifiedMETER.h> // Vmeter
|
||||
#include <M5UnitUnifiedHEART.h> // HEART
|
||||
#include <Wire.h>
|
||||
#if __has_include(<esp_idf_version.h>)
|
||||
#include <esp_idf_version.h>
|
||||
#else // esp_idf_version.h has been introduced in Arduino 1.0.5 (ESP-IDF3.3)
|
||||
#define ESP_IDF_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
|
||||
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(3, 2, 0)
|
||||
#endif
|
||||
|
||||
#include "../src/ui/ui_UnitVmeter.hpp"
|
||||
#include "../src/ui/ui_UnitTVOC.hpp"
|
||||
#include "../src/ui/ui_UnitENV3.hpp"
|
||||
#include "../src/ui/ui_UnitHEART.hpp"
|
||||
|
||||
using namespace m5::unit;
|
||||
|
||||
namespace {
|
||||
LGFX_Sprite strips[2];
|
||||
constexpr uint32_t SPLIT_NUM{4};
|
||||
int32_t strip_height{};
|
||||
auto& lcd = M5.Display;
|
||||
|
||||
UnitUnified Units;
|
||||
UnitPaHub2 unitPaHub{0x71}; // NEED changed register to 0x71. see also https://docs.m5stack.com/en/unit/pahub2
|
||||
UnitVmeter unitVmeter; // channel 0
|
||||
UnitTVOC unitTVOC; // channel 1
|
||||
UnitENV3 unitENV3; // channel 2
|
||||
UnitHEART unitHeart; // channel 3
|
||||
|
||||
auto& unitSHT30 = unitENV3.sht30; // alias
|
||||
auto& unitQMP6988 = unitENV3.qmp6988; // alias
|
||||
|
||||
UnitVmeterSmallUI vmeterSmallUI(&lcd);
|
||||
UnitTVOCSmallUI tvocSmallUI(&lcd);
|
||||
UnitHEARTSmallUI heartSmallUI(&lcd);
|
||||
UnitENV3SmallUI env3SmallUI(&lcd);
|
||||
|
||||
volatile SemaphoreHandle_t _updateLock{};
|
||||
constexpr TickType_t ui_take_wait{0};
|
||||
|
||||
void prepare()
|
||||
{
|
||||
// Each unit settings
|
||||
{
|
||||
auto ccfg = unitVmeter.component_config();
|
||||
ccfg.self_update = true; // Don't update in UnitUnified::update, update explicitly myself.
|
||||
ccfg.stored_size = 64; // Number of elements in the circular buffer that the instance has.
|
||||
unitVmeter.component_config(ccfg);
|
||||
|
||||
// Setup fro begin
|
||||
auto cfg = unitVmeter.config();
|
||||
// 12 ms is used by TVOC, so frequency is reduced
|
||||
cfg.rate = m5::unit::ads111x::Sampling::Rate64; // 64mps
|
||||
unitVmeter.config(cfg);
|
||||
}
|
||||
|
||||
{
|
||||
// Setup fro begin
|
||||
auto cfg = unitTVOC.config();
|
||||
cfg.interval = 1000 / 10; // 10 mps
|
||||
unitTVOC.config(cfg);
|
||||
|
||||
auto ccfg = unitTVOC.component_config();
|
||||
ccfg.self_update = true; // Don't update in UnitUnified::update, update explicitly myself.
|
||||
ccfg.stored_size = 1000 / cfg.interval; // Number of elements in the circular buffer that the instance has
|
||||
unitTVOC.component_config(ccfg);
|
||||
}
|
||||
|
||||
{
|
||||
auto ccfg = unitSHT30.component_config();
|
||||
ccfg.self_update = true;
|
||||
ccfg.stored_size = 10; // Number of elements in the circular buffer that the instance has
|
||||
unitSHT30.component_config(ccfg);
|
||||
|
||||
// Setup fro begin
|
||||
auto cfg = unitSHT30.config();
|
||||
cfg.mps = m5::unit::sht30::MPS::Ten; // 10 mps
|
||||
unitSHT30.config(cfg);
|
||||
}
|
||||
{
|
||||
auto ccfg = unitQMP6988.component_config();
|
||||
ccfg.self_update = true;
|
||||
ccfg.stored_size = 16; // Number of elements in the circular buffer that the instance has
|
||||
unitQMP6988.component_config(ccfg);
|
||||
|
||||
// Setup fro begin
|
||||
auto cfg = unitQMP6988.config();
|
||||
cfg.standby =
|
||||
m5::unit::qmp6988::Standby::Time50ms; // about 16 mps (Calculated from other parameters and this value
|
||||
unitQMP6988.config(cfg);
|
||||
}
|
||||
|
||||
{
|
||||
auto ccfg = unitHeart.component_config();
|
||||
ccfg.self_update = true; // Don't update in UnitUnified::update, update explicitly myself.
|
||||
ccfg.stored_size = 160; // Number of elements in the circular buffer that the instance has
|
||||
unitHeart.component_config(ccfg);
|
||||
}
|
||||
|
||||
// UI
|
||||
heartSmallUI.construct();
|
||||
tvocSmallUI.construct();
|
||||
vmeterSmallUI.construct();
|
||||
env3SmallUI.construct();
|
||||
heartSmallUI.monitor().setSamplingRate(m5::unit::max30100::getSamplingRate(unitHeart.config().sampling_rate));
|
||||
}
|
||||
|
||||
// task for Vmeter
|
||||
void update_vmeter(void*)
|
||||
{
|
||||
static uint32_t fcnt{}, mps{}, mcnt{};
|
||||
static unsigned long start_at{};
|
||||
|
||||
for (;;) {
|
||||
// Exclusive control of TwoWire access and unit updates
|
||||
xSemaphoreTake(_updateLock, portMAX_DELAY);
|
||||
unitVmeter.update();
|
||||
xSemaphoreGive(_updateLock);
|
||||
|
||||
// If measurement data is available, acquire it and pass it to the UI.
|
||||
// If the UI is locked, skip and continue.
|
||||
if (!unitVmeter.empty()) {
|
||||
if (vmeterSmallUI.lock(ui_take_wait)) {
|
||||
mcnt += unitVmeter.available();
|
||||
while (unitVmeter.available()) {
|
||||
vmeterSmallUI.push_back(unitVmeter.voltage()); // Gets the oldest data
|
||||
unitVmeter.discard(); // Discard oldest one
|
||||
}
|
||||
vmeterSmallUI.unlock();
|
||||
}
|
||||
}
|
||||
// std::this_thread::yield();
|
||||
m5::utility::delay(1);
|
||||
|
||||
++fcnt;
|
||||
auto now = m5::utility::millis();
|
||||
if (now >= start_at + 1000) {
|
||||
mps = fcnt;
|
||||
M5_LOGD("Vmeter:%u (%u)", mps, mcnt);
|
||||
fcnt = mcnt = 0;
|
||||
start_at = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Task for TVOC
|
||||
void update_tvoc(void*)
|
||||
{
|
||||
static uint32_t fcnt{}, mps{}, mcnt{};
|
||||
static unsigned long start_at{};
|
||||
|
||||
// Waiting for SGP30 to start periodic measurement (15sec)
|
||||
for (;;) {
|
||||
if (unitTVOC.canMeasurePeriodic()) {
|
||||
break;
|
||||
}
|
||||
// Exclusive control of TwoWire access and unit updates
|
||||
xSemaphoreTake(_updateLock, portMAX_DELAY);
|
||||
unitTVOC.update();
|
||||
xSemaphoreGive(_updateLock);
|
||||
m5::utility::delay(1000);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
// Exclusive control of TwoWire access and unit updates
|
||||
xSemaphoreTake(_updateLock, portMAX_DELAY);
|
||||
// TVOC measuement needs 12ms for read...
|
||||
unitTVOC.update();
|
||||
xSemaphoreGive(_updateLock);
|
||||
|
||||
// If measurement data is available, acquire it and pass it to the UI.
|
||||
// If the UI is locked, skip and continue.
|
||||
if (!unitTVOC.empty()) {
|
||||
if (tvocSmallUI.lock(ui_take_wait)) {
|
||||
mcnt += unitTVOC.available();
|
||||
while (unitTVOC.available()) {
|
||||
tvocSmallUI.push_back(unitTVOC.co2eq(), unitTVOC.tvoc()); // Gets the oldest data
|
||||
unitTVOC.discard(); // Discard oldest one
|
||||
}
|
||||
tvocSmallUI.unlock();
|
||||
}
|
||||
}
|
||||
// std::this_thread::yield();
|
||||
m5::utility::delay(1);
|
||||
|
||||
++fcnt;
|
||||
auto now = m5::utility::millis();
|
||||
if (now >= start_at + 1000) {
|
||||
mps = fcnt;
|
||||
M5_LOGD("TVOC:%u (%u)", mps, mcnt);
|
||||
fcnt = mcnt = 0;
|
||||
start_at = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Task for SHT30(ENV3)
|
||||
void update_sht30(void*)
|
||||
{
|
||||
static uint32_t fcnt{}, mps{}, mcnt{};
|
||||
static unsigned long start_at{};
|
||||
|
||||
for (;;) {
|
||||
// Exclusive control of TwoWire access and unit updates
|
||||
xSemaphoreTake(_updateLock, portMAX_DELAY);
|
||||
unitSHT30.update();
|
||||
xSemaphoreGive(_updateLock);
|
||||
|
||||
// If measurement data is available, acquire it and pass it to the UI.
|
||||
// If the UI is locked, skip and continue.
|
||||
if (!unitSHT30.empty()) {
|
||||
if (env3SmallUI.lock(ui_take_wait)) {
|
||||
mcnt += unitSHT30.available();
|
||||
auto latest = unitSHT30.latest();
|
||||
env3SmallUI.sht30_push_back(latest.temperature(), latest.humidity()); // Gets the latest data
|
||||
unitSHT30.flush(); // Discard all data
|
||||
env3SmallUI.unlock();
|
||||
}
|
||||
}
|
||||
// std::this_thread::yield();
|
||||
m5::utility::delay(1);
|
||||
|
||||
++fcnt;
|
||||
auto now = m5::utility::millis();
|
||||
if (now >= start_at + 1000) {
|
||||
mps = fcnt;
|
||||
M5_LOGD("SHT30:%u (%u)", mps, mcnt);
|
||||
fcnt = mcnt = 0;
|
||||
start_at = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Task for QMP6988(ENV3)
|
||||
void update_qmp6988(void*)
|
||||
{
|
||||
static uint32_t fcnt{}, mps{}, mcnt{};
|
||||
static unsigned long start_at{};
|
||||
|
||||
for (;;) {
|
||||
// Exclusive control of TwoWire access and unit updates
|
||||
xSemaphoreTake(_updateLock, portMAX_DELAY);
|
||||
unitQMP6988.update();
|
||||
xSemaphoreGive(_updateLock);
|
||||
|
||||
// If measurement data is available, acquire it and pass it to the UI.
|
||||
// If the UI is locked, skip and continue.
|
||||
if (!unitQMP6988.empty()) {
|
||||
if (env3SmallUI.lock(ui_take_wait)) {
|
||||
mcnt += unitQMP6988.available();
|
||||
auto latest = unitQMP6988.latest();
|
||||
env3SmallUI.qmp6988_push_back(latest.temperature(), latest.pressure()); // Gets the latest data
|
||||
unitQMP6988.flush(); // Discard all data
|
||||
env3SmallUI.unlock();
|
||||
}
|
||||
}
|
||||
// std::this_thread::yield();
|
||||
m5::utility::delay(1);
|
||||
|
||||
++fcnt;
|
||||
auto now = m5::utility::millis();
|
||||
if (now >= start_at + 1000) {
|
||||
mps = fcnt;
|
||||
M5_LOGD("QMP6988:%u (%u)", mps, mcnt);
|
||||
fcnt = mcnt = 0;
|
||||
start_at = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Task for HEART
|
||||
void update_heart(void*)
|
||||
{
|
||||
static uint32_t fcnt{}, mps{}, mcnt{};
|
||||
static unsigned long start_at{};
|
||||
|
||||
for (;;) {
|
||||
// Exclusive control of TwoWire access and unit updates
|
||||
xSemaphoreTake(_updateLock, portMAX_DELAY);
|
||||
unitHeart.update();
|
||||
xSemaphoreGive(_updateLock);
|
||||
|
||||
// If measurement data is available, acquire it and pass it to the UI.
|
||||
// If the UI is locked, skip and continue.
|
||||
if (!unitHeart.empty()) {
|
||||
if (heartSmallUI.lock(ui_take_wait)) {
|
||||
mcnt += unitHeart.available();
|
||||
while (unitHeart.available()) {
|
||||
heartSmallUI.push_back(unitHeart.ir(), unitHeart.red());
|
||||
unitHeart.discard();
|
||||
}
|
||||
heartSmallUI.unlock();
|
||||
}
|
||||
}
|
||||
m5::utility::delay(1);
|
||||
// std::this_thread::yield();
|
||||
|
||||
++fcnt;
|
||||
auto now = m5::utility::millis();
|
||||
if (now >= start_at + 1000) {
|
||||
mps = fcnt;
|
||||
M5_LOGD("Heart:%u (%u)", mps, mcnt);
|
||||
fcnt = mcnt = 0;
|
||||
start_at = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawUI(LovyanGFX& dst, const uint32_t x, const uint32_t yoffset)
|
||||
{
|
||||
vmeterSmallUI.push(&dst, 0, 0 + yoffset);
|
||||
tvocSmallUI.push(&dst, lcd.width() >> 1, 0 + yoffset);
|
||||
env3SmallUI.push(&dst, 0, (lcd.height() >> 1) + yoffset);
|
||||
heartSmallUI.push(&dst, lcd.width() >> 1, (lcd.height() >> 1) + yoffset);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void setup()
|
||||
{
|
||||
M5.begin();
|
||||
lcd.startWrite();
|
||||
lcd.clear(TFT_DARKGRAY);
|
||||
//
|
||||
strip_height = lcd.height() / SPLIT_NUM;
|
||||
uint32_t cnt{};
|
||||
for (auto&& spr : strips) {
|
||||
spr.setPsram(false);
|
||||
spr.setColorDepth(lcd.getColorDepth());
|
||||
cnt += spr.createSprite(lcd.width(), strip_height) ? 1 : 0;
|
||||
}
|
||||
assert(cnt == 2 && "Failed to create sprite");
|
||||
|
||||
prepare();
|
||||
|
||||
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
|
||||
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
|
||||
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
|
||||
Wire.begin(pin_num_sda, pin_num_scl, 400000U);
|
||||
|
||||
if (!unitPaHub.add(unitVmeter, 0) /* Connect Vmeter to PaHub2 ch:0 */
|
||||
|| !unitPaHub.add(unitTVOC, 1) /* Connect TVOC to PaHub2 ch:1 */
|
||||
|| !unitPaHub.add(unitENV3, 2) /* Connect ENV3 to PaHub2 ch:2 */
|
||||
|| !unitPaHub.add(unitHeart, 3) /* Connect HEART to PaHub2 ch:3 */
|
||||
|| !Units.add(unitPaHub, Wire) /* Connect PaHub2 to Core */
|
||||
|| !Units.begin() /* Begin UnitUnified */
|
||||
) {
|
||||
M5_LOGE("Failed to begin");
|
||||
lcd.clear(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
|
||||
lcd.clear(TFT_DARKGREEN);
|
||||
|
||||
M5_LOGI("M5UnitUnified has been begun");
|
||||
M5_LOGI("%s", Units.debugInfo().c_str());
|
||||
M5_LOGI("CPP %ld", __cplusplus);
|
||||
M5_LOGI("ESP-IDF Version %d.%d.%d", (ESP_IDF_VERSION >> 16) & 0xFF, (ESP_IDF_VERSION >> 8) & 0xFF,
|
||||
ESP_IDF_VERSION & 0xFF);
|
||||
M5_LOGI("BOARD:%X", M5.getBoard());
|
||||
M5_LOGI("Heap: %u", esp_get_free_heap_size());
|
||||
|
||||
//
|
||||
_updateLock = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(_updateLock);
|
||||
xTaskCreateUniversal(update_vmeter, "vmeter", 8192, nullptr, 2, nullptr, PRO_CPU_NUM);
|
||||
xTaskCreateUniversal(update_tvoc, "tvoc", 8192, nullptr, 1, nullptr, PRO_CPU_NUM);
|
||||
xTaskCreateUniversal(update_sht30, "sht30", 8192, nullptr, 1, nullptr, PRO_CPU_NUM);
|
||||
xTaskCreateUniversal(update_qmp6988, "qmp6988", 8192, nullptr, 1, nullptr, PRO_CPU_NUM);
|
||||
xTaskCreateUniversal(update_heart, "heart", 8192, nullptr, 1, nullptr, PRO_CPU_NUM);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
static uint32_t fpsCnt{}, fps{};
|
||||
static unsigned long start_at{};
|
||||
|
||||
++fpsCnt;
|
||||
auto now = m5::utility::millis();
|
||||
if (now >= start_at + 1000) {
|
||||
fps = fpsCnt;
|
||||
M5_LOGD("FPS:%u", fps);
|
||||
fpsCnt = 0;
|
||||
start_at = now;
|
||||
}
|
||||
|
||||
M5.update();
|
||||
|
||||
// All units do their own updates, so there is no need to call for a unit-wide update here.
|
||||
// xSemaphoreTake(_updateLock, portMAX_DELAY);
|
||||
// unitTVOC.update();
|
||||
// xSemaphoreGive(_updateLock);
|
||||
|
||||
tvocSmallUI.update();
|
||||
vmeterSmallUI.update();
|
||||
heartSmallUI.update();
|
||||
env3SmallUI.update();
|
||||
|
||||
static uint32_t current{};
|
||||
int32_t offset{};
|
||||
uint32_t cnt{SPLIT_NUM};
|
||||
while (cnt--) {
|
||||
auto& spr = strips[current];
|
||||
spr.clear();
|
||||
drawUI(spr, 0, offset);
|
||||
spr.pushSprite(&lcd, 0, -offset);
|
||||
current ^= 1;
|
||||
offset -= strip_height;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@brief Configrate time
|
||||
@file config_time.cpp
|
||||
*/
|
||||
#include "config_time.hpp"
|
||||
#include <M5Unified.h>
|
||||
#include <M5Utility.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_sntp.h>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
// NTP server URI
|
||||
constexpr char ntp0[] = "ntp.nict.jp";
|
||||
constexpr char ntp1[] = "ntp.jst.mfeed.ad.jp";
|
||||
constexpr char ntp2[] = "time.cloudflare.com";
|
||||
const char* ntpURLTable[] = {ntp0, ntp1, ntp2};
|
||||
|
||||
constexpr char defaultPosixTZ[] = "JST-9"; // Asia/Tokyo
|
||||
|
||||
auto rng = std::default_random_engine{};
|
||||
} // namespace
|
||||
|
||||
bool isEnabledRTC()
|
||||
{
|
||||
// Check RTC if exists
|
||||
if (M5.Rtc.isEnabled()) {
|
||||
auto dt = M5.Rtc.getDateTime(); // GMT
|
||||
if (dt.date.year > 2016) {
|
||||
M5_LOGV("RTC time already set. (GMT) %04d/%02d/%2d %02d:%02d:%02d", dt.date.year, dt.date.month,
|
||||
dt.date.date, dt.time.hours, dt.time.minutes, dt.time.seconds);
|
||||
return true;
|
||||
}
|
||||
M5_LOGW("RTC is not set to the correct time");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setTimezone(const char* posix_tz)
|
||||
{
|
||||
setenv("TZ", posix_tz ? posix_tz : defaultPosixTZ, 1);
|
||||
tzset();
|
||||
}
|
||||
|
||||
bool configTime(const char* posix_tz, const char* ssid, const char* password)
|
||||
{
|
||||
M5_LOGI("Configrate time");
|
||||
|
||||
// WiFi connect
|
||||
if (ssid && password) {
|
||||
WiFi.begin(ssid, password);
|
||||
} else {
|
||||
// Connect to credential in Hardware. (ESP32 saves the last WiFi connection)
|
||||
WiFi.begin();
|
||||
}
|
||||
int32_t retry{10};
|
||||
while (WiFi.status() != WL_CONNECTED && --retry >= 0) {
|
||||
M5_LOGI(".");
|
||||
m5::utility::delay(1000);
|
||||
}
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
M5_LOGE("Failed to connect WiFi");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shuffle(std::begin(ntpURLTable), std::end(ntpURLTable), rng);
|
||||
configTzTime(posix_tz ? posix_tz : defaultPosixTZ, ntpURLTable[0], ntpURLTable[1], ntpURLTable[2]);
|
||||
|
||||
// Waiting for time synchronization
|
||||
retry = 10;
|
||||
sntp_sync_status_t st{};
|
||||
while (((st = sntp_get_sync_status()) == SNTP_SYNC_STATUS_RESET) && --retry >= 0) {
|
||||
M5_LOGI("Time synchronization in progress");
|
||||
m5::utility::delay(1000);
|
||||
}
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
|
||||
std::tm discard{};
|
||||
if ((st != SNTP_SYNC_STATUS_COMPLETED) || !getLocalTime(&discard, 10 * 1000 /* timeout */)) {
|
||||
M5_LOGE("Failed to sync time");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set RTC if exists
|
||||
if (M5.Rtc.isEnabled()) {
|
||||
time_t t = time(nullptr) + 1;
|
||||
while (t > time(nullptr)) {
|
||||
/* Nop */
|
||||
}
|
||||
M5.Rtc.setDateTime(std::gmtime(&t));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@brief Configrate time by NTP
|
||||
@file config_time.cpp
|
||||
*/
|
||||
#ifndef CONFIG_TIME_HPP
|
||||
#define CONFIG_TIME_HPP
|
||||
|
||||
bool isEnabledRTC();
|
||||
void setTimezone(const char* posix_tz = nullptr);
|
||||
bool configTime(const char* ssid = nullptr, const char* password = nullptr);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_bar_meter.cpp
|
||||
@brief Bar meter
|
||||
*/
|
||||
#include "ui_bar_meter.hpp"
|
||||
#include <M5Utility.h>
|
||||
|
||||
namespace {
|
||||
constexpr float table0[] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
|
||||
constexpr float table1[] = {0.125f, 0.125f * 3, 0.125f * 5, 0.125f * 7};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
void BarMeterH::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
|
||||
{
|
||||
dst->setClipRect(x, y, width(), height());
|
||||
const auto t = height() >> 3;
|
||||
const auto gy = y + t * 6;
|
||||
const auto gw = width() - 1;
|
||||
const auto gh0 = t;
|
||||
const auto gh1 = gh0 >> 1;
|
||||
|
||||
dst->fillRect(x, y, width(), height(), TFT_BLUE);
|
||||
|
||||
// gauge
|
||||
dst->drawFastHLine(x, gy, width(), gaugeColor());
|
||||
|
||||
for (auto&& e : table0) {
|
||||
dst->drawFastVLine(x + gw * e, gy - gh0, gh0, gaugeColor());
|
||||
}
|
||||
for (auto&& e : table1) {
|
||||
dst->drawFastVLine(x + gw * e, gy - gh1, gh1, gaugeColor());
|
||||
}
|
||||
// needle
|
||||
dst->drawFastVLine(x + gw * ratio(val), y, height(), needleColor());
|
||||
|
||||
dst->clearClipRect();
|
||||
}
|
||||
|
||||
void BarMeterV::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
|
||||
{
|
||||
dst->setClipRect(x, y, width(), height());
|
||||
const auto t = width() >> 3;
|
||||
const auto gx = x + t * 6;
|
||||
const auto gh = height() - 1;
|
||||
const auto gw0 = t;
|
||||
const auto gw1 = gw0 >> 1;
|
||||
|
||||
// gauge
|
||||
dst->drawFastVLine(gx, y, height(), gaugeColor());
|
||||
|
||||
for (auto&& e : table0) {
|
||||
dst->drawFastHLine(gx - gw0, y + gh * e, gw0, gaugeColor());
|
||||
}
|
||||
for (auto&& e : table1) {
|
||||
dst->drawFastHLine(gx - gw1, y + gh * e, gw1, gaugeColor());
|
||||
}
|
||||
// needle
|
||||
dst->drawFastHLine(x, y + gh * (1.0f - ratio(val)), width(), needleColor());
|
||||
|
||||
dst->clearClipRect();
|
||||
}
|
||||
|
||||
void ColorBarMeterH::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
|
||||
{
|
||||
const auto w = width();
|
||||
const auto t = height() >> 3;
|
||||
const auto h = t << 2;
|
||||
auto left = x;
|
||||
auto top = y + height() / 2 - height() / 4;
|
||||
auto gw = width() - 1;
|
||||
|
||||
dst->setClipRect(x, y, width(), height());
|
||||
|
||||
// gauge
|
||||
if (!_crange.empty()) {
|
||||
dst->fillRect(left, top, w, h, _crange.back().clr);
|
||||
for (auto it = _crange.crbegin() + 1; it != _crange.crend(); ++it) {
|
||||
int32_t ww = w * ratio(it->lesseq);
|
||||
dst->fillRect(left, top, ww, h, it->clr);
|
||||
}
|
||||
} else {
|
||||
dst->fillRect(left, top, w, h, backgroundColor());
|
||||
}
|
||||
dst->drawRect(left, top, w, h, gaugeColor());
|
||||
// needle
|
||||
dst->drawFastVLine(left + gw * ratio(val), y, height(), needleColor());
|
||||
|
||||
dst->clearClipRect();
|
||||
}
|
||||
|
||||
void ColorBarMeterV::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
|
||||
{
|
||||
const auto h = height();
|
||||
const auto w = width() >> 1;
|
||||
auto top = y;
|
||||
auto left = x + width() / 2 - width() / 4;
|
||||
auto gh = height() - 1;
|
||||
|
||||
dst->setClipRect(x, y, width(), height());
|
||||
|
||||
// gauge
|
||||
if (!_crange.empty()) {
|
||||
dst->fillRect(left, top, w, h, _crange.back().clr);
|
||||
for (auto it = _crange.crbegin() + 1; it != _crange.crend(); ++it) {
|
||||
int32_t hh = h * ratio(it->lesseq);
|
||||
dst->fillRect(left, top + height() - hh, w, hh, it->clr);
|
||||
}
|
||||
} else {
|
||||
dst->fillRect(left, top, w, h, backgroundColor());
|
||||
}
|
||||
dst->drawRect(left, top, w, h, gaugeColor());
|
||||
// needle
|
||||
dst->drawFastHLine(x, y + gh * (1.0f - ratio(val)), width(), needleColor());
|
||||
|
||||
dst->clearClipRect();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_scale_meter.hpp
|
||||
@brief Bar meter
|
||||
*/
|
||||
#ifndef UI_PARTS_BAR_METER_HPP
|
||||
#define UI_PARTS_BAR_METER_HPP
|
||||
|
||||
#include "ui_base.hpp"
|
||||
#include <initializer_list>
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
/*!
|
||||
@class BarMeterH
|
||||
@brief Horizontal bar meter
|
||||
*/
|
||||
class BarMeterH : public Base {
|
||||
public:
|
||||
BarMeterH(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const int32_t wid, const int32_t hgt)
|
||||
: Base(parent, minimum, maximum, wid, hgt)
|
||||
{
|
||||
}
|
||||
virtual ~BarMeterH()
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
|
||||
};
|
||||
|
||||
/*!
|
||||
@class BarMeterH
|
||||
@brief Vertical bar meter
|
||||
*/
|
||||
class BarMeterV : public Base {
|
||||
public:
|
||||
BarMeterV(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const int32_t wid, const int32_t hgt)
|
||||
: Base(parent, minimum, maximum, wid, hgt)
|
||||
{
|
||||
}
|
||||
virtual ~BarMeterV()
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
|
||||
};
|
||||
|
||||
struct ColorRange {
|
||||
int32_t lesseq;
|
||||
m5gfx::rgb565_t clr;
|
||||
};
|
||||
|
||||
class ColorBarMeterH : public Base {
|
||||
public:
|
||||
ColorBarMeterH(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const int32_t wid,
|
||||
const int32_t hgt, std::initializer_list<ColorRange> init = {})
|
||||
: Base(parent, minimum, maximum, wid, hgt), _crange(init.begin(), init.end())
|
||||
{
|
||||
}
|
||||
virtual ~ColorBarMeterH()
|
||||
{
|
||||
}
|
||||
|
||||
void setColorRange(std::initializer_list<ColorRange> init)
|
||||
{
|
||||
_crange = std::vector<ColorRange>(init);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
|
||||
|
||||
private:
|
||||
std::vector<ColorRange> _crange{};
|
||||
};
|
||||
|
||||
class ColorBarMeterV : public Base {
|
||||
public:
|
||||
ColorBarMeterV(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const int32_t wid,
|
||||
const int32_t hgt, std::initializer_list<ColorRange> init = {})
|
||||
: Base(parent, minimum, maximum, wid, hgt), _crange(init.begin(), init.end())
|
||||
{
|
||||
}
|
||||
virtual ~ColorBarMeterV()
|
||||
{
|
||||
}
|
||||
|
||||
void setColorRange(std::initializer_list<ColorRange> init)
|
||||
{
|
||||
_crange = std::vector<ColorRange>(init);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
|
||||
|
||||
private:
|
||||
std::vector<ColorRange> _crange{};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_base.cpp
|
||||
@brief Base class for UI
|
||||
*/
|
||||
#include "ui_base.hpp"
|
||||
#include <M5Utility.h>
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
void Base::animate(const int32_t val, const elapsed_time_t dur)
|
||||
{
|
||||
if (_to != val && _min != _max) {
|
||||
_from = _value;
|
||||
_to = std::min(std::max(val, _min), _max);
|
||||
_start_at = m5::utility::millis();
|
||||
_duration = dur;
|
||||
}
|
||||
}
|
||||
|
||||
bool Base::update()
|
||||
{
|
||||
if (_start_at) {
|
||||
auto now = m5::utility::millis();
|
||||
if (now >= _start_at + _duration) {
|
||||
_start_at = 0;
|
||||
_value = _to;
|
||||
} else {
|
||||
float t = (now - _start_at) / (float)_duration;
|
||||
_value = _from + (_to - _from) * t;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_base.hpp
|
||||
@brief Base class for UI
|
||||
*/
|
||||
#ifndef UI_BASE_HPP
|
||||
#define UI_BASE_HPP
|
||||
|
||||
#include <M5GFX.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
class Base {
|
||||
public:
|
||||
using elapsed_time_t = unsigned long;
|
||||
|
||||
Base(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const int32_t wid, const int32_t hgt)
|
||||
: _parent(parent),
|
||||
_min{minimum},
|
||||
_max{maximum},
|
||||
_value{minimum},
|
||||
_from{minimum},
|
||||
_to{minimum},
|
||||
_wid{wid},
|
||||
_hgt{hgt}
|
||||
{
|
||||
}
|
||||
virtual ~Base()
|
||||
{
|
||||
}
|
||||
|
||||
inline int32_t value() const
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
inline int32_t valueTo() const
|
||||
{
|
||||
return _to;
|
||||
}
|
||||
inline int32_t width() const
|
||||
{
|
||||
return _wid;
|
||||
}
|
||||
inline int32_t height() const
|
||||
{
|
||||
return _hgt;
|
||||
}
|
||||
inline int32_t range() const
|
||||
{
|
||||
return _max - _min;
|
||||
}
|
||||
|
||||
inline m5gfx::rgb565_t needleColor() const
|
||||
{
|
||||
return _needleClr;
|
||||
}
|
||||
inline m5gfx::rgb565_t gaugeColor() const
|
||||
{
|
||||
return _gaugeClr;
|
||||
}
|
||||
inline m5gfx::rgb565_t backgroundColor() const
|
||||
{
|
||||
return _bgClr;
|
||||
}
|
||||
template <typename T>
|
||||
void setNeedleColor(const T& clr)
|
||||
{
|
||||
_needleClr = clr;
|
||||
}
|
||||
template <typename T>
|
||||
void setGaugeColor(const T& clr)
|
||||
{
|
||||
_gaugeClr = clr;
|
||||
}
|
||||
template <typename T>
|
||||
void setBackgroundColor(const T& clr)
|
||||
{
|
||||
_bgClr = clr;
|
||||
}
|
||||
|
||||
virtual bool update();
|
||||
|
||||
///@name Control
|
||||
///@{
|
||||
virtual void animate(const int32_t val, const elapsed_time_t dur);
|
||||
inline void set(const int32_t val)
|
||||
{
|
||||
animate(val, 0U);
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Push
|
||||
///@{
|
||||
inline void push(const int32_t x, const int32_t y)
|
||||
{
|
||||
push(_parent, x, y);
|
||||
}
|
||||
virtual void push(LovyanGFX* dst, const int32_t x, const int32_t y)
|
||||
{
|
||||
render(dst, x, y, _value);
|
||||
}
|
||||
///@}
|
||||
|
||||
protected:
|
||||
inline float ratio(const int32_t val)
|
||||
{
|
||||
return range() > 0 ? (std::min(std::max(val, _min), _max) - _min) / (float)range() : 0.0f;
|
||||
}
|
||||
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
LovyanGFX* _parent{};
|
||||
int32_t _min{}, _max{}, _value{}, _from{}, _to{}, _wid{}, _hgt{};
|
||||
elapsed_time_t _start_at{}, _duration{};
|
||||
m5gfx::rgb565_t _needleClr{TFT_WHITE}, _gaugeClr{TFT_DARKGRAY}, _bgClr{TFT_BLACK};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
#endif
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_gauge_meter.cpp
|
||||
@brief Gauge meter
|
||||
*/
|
||||
#include "ui_gauge_meter.hpp"
|
||||
#include <M5Utility.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
GaugeMeter::GaugeMeter(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const float minDeg,
|
||||
const float maxDeg, const int32_t wid, const int32_t hgt, const int32_t thickness)
|
||||
: GaugeMeter(parent, minimum, maximum, minDeg, maxDeg, wid, hgt, (wid >> 1) - 1, (hgt >> 1) - 1,
|
||||
std::min(wid >> 1, hgt >> 1) - 1, thickness)
|
||||
{
|
||||
}
|
||||
|
||||
GaugeMeter::GaugeMeter(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const float minDeg,
|
||||
const float maxDeg, const int32_t wid, const int32_t hgt, const int32_t cx, const int32_t cy,
|
||||
const int32_t radius, const int32_t thickness)
|
||||
: Base(parent, minimum, maximum, wid, hgt),
|
||||
_cx(cx),
|
||||
_cy(cy),
|
||||
_radius(radius),
|
||||
_thickness{thickness},
|
||||
_minDeg{minDeg},
|
||||
_maxDeg{maxDeg}
|
||||
{
|
||||
}
|
||||
|
||||
void GaugeMeter::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
|
||||
{
|
||||
dst->setClipRect(x, y, width(), height());
|
||||
|
||||
int32_t r0{_radius}, r1{_radius - _thickness};
|
||||
float sdeg{std::fmin(_minDeg, _maxDeg)};
|
||||
float edeg{std::fmax(_minDeg, _maxDeg)};
|
||||
// float sdeg{_minDeg};
|
||||
// float edeg{_maxDeg};
|
||||
|
||||
dst->fillArc(x + _cx, y + _cy, r0, r1, sdeg, edeg, backgroundColor());
|
||||
float deg = _minDeg + (_maxDeg - _minDeg) * ratio(val);
|
||||
dst->fillArc(x + _cx, y + _cy, r0, r1, sdeg, deg, needleColor());
|
||||
|
||||
dst->drawArc(x + _cx, y + _cy, r0, r1, sdeg, edeg, gaugeColor());
|
||||
|
||||
dst->clearClipRect();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_gauge_meter.hpp
|
||||
@brief Gauge meter
|
||||
*/
|
||||
#ifndef UI_GAUGE_METER_HPP
|
||||
#define UI_GAUGE_METER_HPP
|
||||
|
||||
#include "ui_base.hpp"
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
class GaugeMeter : public Base {
|
||||
public:
|
||||
GaugeMeter(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const float minDeg, const float maxDeg,
|
||||
const int32_t wid, const int32_t hgt, const int32_t thickness);
|
||||
GaugeMeter(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const float minDeg, const float maxDeg,
|
||||
const int32_t wid, const int32_t hgt, const int32_t cx, const int32_t cy, const int32_t radius,
|
||||
const int32_t thickness);
|
||||
|
||||
virtual ~GaugeMeter()
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
|
||||
|
||||
private:
|
||||
int32_t _cx{}, _cy{}, _radius{}, _thickness{};
|
||||
float _minDeg{}, _maxDeg{};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_lgfx_extesion.hpp
|
||||
@brief M5GFX lgfx extensions
|
||||
*/
|
||||
#ifndef UI_LGFX_EXTENSION_HPP
|
||||
#define UI_LGFX_EXTENSION_HPP
|
||||
|
||||
#include <M5GFX.h>
|
||||
|
||||
namespace m5 {
|
||||
namespace lgfx {
|
||||
/*!
|
||||
@brief Push sprite partialty
|
||||
@param dst Push target
|
||||
@param dx Destination X coordinate for push
|
||||
@param dy Destination Y coordinate for push
|
||||
@param width Width of partial rectangle
|
||||
@param height Height of partial rectangle
|
||||
@param src Source sprite
|
||||
@param sx Source X coordinate for push
|
||||
@param sy Source Y coordinate for push
|
||||
@warning If you have already set clip rect to dst, save and set it again on your own.
|
||||
@warning After this function call, the clip rectangle in dst is cleared.
|
||||
*/
|
||||
inline void pushPartial(LovyanGFX* dst, const int32_t dx, const int32_t dy, const int32_t width, const int32_t height,
|
||||
LGFX_Sprite* src, const int32_t sx, const int32_t sy)
|
||||
{
|
||||
dst->setClipRect(dx, dy, width, height);
|
||||
src->pushSprite(dst, dx - sx, dy - sy);
|
||||
dst->clearClipRect();
|
||||
}
|
||||
|
||||
/*!
|
||||
@copybrief pushPartial(LovyanGFX* dst, const int32_t dx, const int32_t dy, const int32_t width, const int32_t height,
|
||||
LGFX_Sprite* src, const int32_t sx, const int32_t sy)
|
||||
@copydoc pushPartial(LovyanGFX* dst, const int32_t dx, const int32_t dy, const int32_t width, const int32_t height,
|
||||
LGFX_Sprite* src, const int32_t sx, const int32_t sy)
|
||||
@param transp Color/Palette for transparent
|
||||
@tparam T Color/Palettetype
|
||||
*/
|
||||
template <typename T>
|
||||
void pushPartial(LovyanGFX* dst, const int32_t dx, const int32_t dy, const int32_t width, const int32_t height,
|
||||
LGFX_Sprite* src, const int32_t sx, const int32_t sy, const T& transp)
|
||||
{
|
||||
dst->setClipRect(dx, dy, width, height);
|
||||
src->pushSprite(dst, dx - sx, dy - sy, transp);
|
||||
dst->clearClipRect();
|
||||
}
|
||||
|
||||
} // namespace lgfx
|
||||
} // namespace m5
|
||||
#endif
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_plotter.cpp
|
||||
@brief Plotter
|
||||
*/
|
||||
|
||||
#include "ui_plotter.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
Plotter::Plotter(LovyanGFX* parent, const size_t maxPlot, const int32_t wid, const int32_t hgt,
|
||||
const int32_t coefficient)
|
||||
: _parent(parent), _wid{wid}, _hgt{hgt}, _coefficient(coefficient), _data(maxPlot), _autoScale{true}
|
||||
{
|
||||
}
|
||||
|
||||
Plotter::Plotter(LovyanGFX* parent, const size_t maxPlot, const int32_t minimum, const int32_t maximum,
|
||||
const int32_t wid, const int32_t hgt, const int32_t coefficient)
|
||||
: _parent(parent),
|
||||
_min{minimum},
|
||||
_max{maximum},
|
||||
_wid{wid},
|
||||
_hgt{hgt},
|
||||
_coefficient(coefficient),
|
||||
_data(maxPlot),
|
||||
_autoScale{false}
|
||||
{
|
||||
}
|
||||
|
||||
void Plotter::update()
|
||||
{
|
||||
if (_cb && _autoScale && _cb->size() >= 2) {
|
||||
auto it = std::minmax_element(_cb->cbegin(), _cb->cend());
|
||||
_min = *(it.first);
|
||||
_max = *(it.second);
|
||||
if (_min == _max) {
|
||||
++_max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Plotter::push_back(const float val)
|
||||
{
|
||||
push_back((int32_t)(val * _coefficient));
|
||||
}
|
||||
|
||||
void Plotter::push_back(const int32_t val)
|
||||
{
|
||||
auto v = _autoScale ? val : std::min(std::max(val, _min), _max);
|
||||
_data.push_back(v);
|
||||
|
||||
if (_autoScale && _data.size() >= 2) {
|
||||
#if 0
|
||||
if (_min == _max) {
|
||||
auto it = std::minmax_element(_data.cbegin(), _data.cend());
|
||||
_min = *(it.first);
|
||||
_max = *(it.second);
|
||||
} else {
|
||||
if (v < _min) {
|
||||
_min = v;
|
||||
}
|
||||
if (v > _max) {
|
||||
_max = v;
|
||||
}
|
||||
}
|
||||
#else
|
||||
auto it = std::minmax_element(_data.cbegin(), _data.cend());
|
||||
_min = *(it.first);
|
||||
_max = *(it.second);
|
||||
if (_min == _max) {
|
||||
++_max;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Plotter::assign(m5::container::CircularBuffer<int32_t>& cb)
|
||||
{
|
||||
_cb = &cb;
|
||||
if (_autoScale && _cb->size() >= 2) {
|
||||
auto it = std::minmax_element(_cb->cbegin(), _cb->cend());
|
||||
_min = *(it.first);
|
||||
_max = *(it.second);
|
||||
}
|
||||
}
|
||||
|
||||
void Plotter::push(LovyanGFX* dst, const int32_t x, const int32_t y)
|
||||
{
|
||||
dst->setClipRect(x, y, width(), height());
|
||||
|
||||
// gauge
|
||||
dst->drawFastHLine(x, y, _wid, _gaugeClr);
|
||||
dst->drawFastHLine(x, y + (_hgt >> 1), _wid, _gaugeClr);
|
||||
dst->drawFastHLine(x, y + (_hgt >> 2), _wid, _gaugeClr);
|
||||
dst->drawFastHLine(x, y + (_hgt >> 2) * 3, _wid, _gaugeClr);
|
||||
dst->drawFastHLine(x, y + _hgt - 1, _wid, _gaugeClr);
|
||||
|
||||
if (_data.size() >= 2) {
|
||||
auto it = _cb ? _cb->cbegin() : _data.cbegin();
|
||||
auto itend = _cb ? --_cb->cend() : --_data.cend();
|
||||
auto sz = _cb ? _cb->size() : _data.size();
|
||||
const float range{(float)_max - _min};
|
||||
const int32_t hh{_hgt - 1};
|
||||
int32_t left{x};
|
||||
|
||||
// plot latest
|
||||
if (sz > _wid) {
|
||||
auto cnt{sz - _wid};
|
||||
while (cnt--) {
|
||||
++it; // Bidirectional iterator, so only ++/-- is available.
|
||||
}
|
||||
}
|
||||
if (sz < _wid) {
|
||||
left += _wid - sz;
|
||||
}
|
||||
|
||||
while (it != itend) {
|
||||
int32_t s{*it}, e{*(++it)};
|
||||
dst->drawLine(left, y + hh - hh * (s - _min) / range, left + 1, y + hh - hh * (e - _min) / range,
|
||||
_needleClr);
|
||||
++left;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
auto f = dst->getFont();
|
||||
auto td = dst->getTextDatum();
|
||||
dst->setFont(&fonts::Font0);
|
||||
dst->setTextColor(TFT_WHITE);
|
||||
dst->setTextDatum(_tdatum);
|
||||
|
||||
int32_t tx{x}; // left
|
||||
switch (_tdatum & 0x03) {
|
||||
case 1: // center
|
||||
tx = x + (_wid >> 1);
|
||||
break;
|
||||
case 2: // right:
|
||||
tx = x + _wid;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
auto s = m5::utility::formatString("%d%s", _min / _coefficient, _ustr ? _ustr : "");
|
||||
dst->drawString(s.c_str(), tx, y + _hgt - 8);
|
||||
if (_min != _max) {
|
||||
auto s = m5::utility::formatString("%d%s", _max / _coefficient, _ustr ? _ustr : "");
|
||||
dst->drawString(s.c_str(), tx, y);
|
||||
if (_max - _min > 1) {
|
||||
s = m5::utility::formatString("%d%s", (_min + ((_max - _min) >> 1)) / _coefficient, _ustr ? _ustr : "");
|
||||
dst->drawString(s.c_str(), tx, y + _hgt / 2 - 4);
|
||||
}
|
||||
}
|
||||
|
||||
dst->setTextDatum(td);
|
||||
dst->setFont(f);
|
||||
dst->clearClipRect();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_plotter.hpp
|
||||
@brief Plotter
|
||||
*/
|
||||
#ifndef UI_PLOTTER_HPP
|
||||
#define UI_PLOTTER_HPP
|
||||
|
||||
#include <M5GFX.h>
|
||||
#include <M5Utility.h>
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
class Plotter {
|
||||
public:
|
||||
Plotter(LovyanGFX* parent, const size_t maxPlot, const int32_t wid, const int32_t hgt,
|
||||
const int32_t coefficient = 1);
|
||||
Plotter(LovyanGFX* parent, const size_t maxPlot, const int32_t minimum, const int32_t maximum, const int32_t wid,
|
||||
const int32_t hgt, const int32_t coefficient = 1);
|
||||
|
||||
void update();
|
||||
|
||||
inline int32_t width() const
|
||||
{
|
||||
return _wid;
|
||||
}
|
||||
inline int32_t height() const
|
||||
{
|
||||
return _hgt;
|
||||
}
|
||||
inline int32_t minimum() const
|
||||
{
|
||||
return _min;
|
||||
}
|
||||
inline int32_t maximum() const
|
||||
{
|
||||
return _max;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void setNeedleColor(const T& clr)
|
||||
{
|
||||
_needleClr = clr;
|
||||
}
|
||||
template <typename T>
|
||||
void setGaugeColor(const T& clr)
|
||||
{
|
||||
_gaugeClr = clr;
|
||||
}
|
||||
template <typename T>
|
||||
void setBackgroundColor(const T& clr)
|
||||
{
|
||||
_bgClr = clr;
|
||||
}
|
||||
|
||||
inline void setUnitString(const char* s)
|
||||
{
|
||||
_ustr = s;
|
||||
}
|
||||
inline void setGaugeTextDatum(const textdatum_t datum)
|
||||
{
|
||||
_tdatum = datum;
|
||||
}
|
||||
|
||||
void push_back(const float val);
|
||||
void push_back(const int32_t val);
|
||||
void assign(m5::container::CircularBuffer<int32_t>& cb);
|
||||
|
||||
inline void push(const int32_t x, const int32_t y)
|
||||
{
|
||||
push(_parent, x, y);
|
||||
}
|
||||
virtual void push(LovyanGFX* dst, const int32_t x, const int32_t y);
|
||||
|
||||
protected:
|
||||
m5gfx::rgb565_t needleColor() const
|
||||
{
|
||||
return _needleClr;
|
||||
}
|
||||
m5gfx::rgb565_t gaugeColor() const
|
||||
{
|
||||
return _gaugeClr;
|
||||
}
|
||||
m5gfx::rgb565_t backgroundColor() const
|
||||
{
|
||||
return _bgClr;
|
||||
}
|
||||
|
||||
protected:
|
||||
private:
|
||||
LovyanGFX* _parent{};
|
||||
int32_t _min{}, _max{}, _wid{}, _hgt{}, _coefficient{};
|
||||
m5::container::CircularBuffer<int32_t> _data;
|
||||
m5::container::CircularBuffer<int32_t>* _cb{};
|
||||
|
||||
m5gfx::rgb565_t _needleClr{TFT_WHITE}, _gaugeClr{TFT_DARKGRAY}, _bgClr{TFT_BLACK};
|
||||
textdatum_t _tdatum{textdatum_t::top_left};
|
||||
const char* _ustr{};
|
||||
bool _autoScale{};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
#endif
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_rotary_couter.cpp
|
||||
@brief Rotary counter
|
||||
*/
|
||||
#include "ui_rotary_counter.hpp"
|
||||
#include <M5Utility.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
RotaryCounter::Number::Number(LGFX_Sprite* src, const uint8_t base) : _src{src}, _base{base}
|
||||
{
|
||||
assert(_src && "Source must be NOT nullptr");
|
||||
_height = _src->height() / (_base + 1);
|
||||
}
|
||||
|
||||
void RotaryCounter::Number::animate(const uint8_t num, const uint32_t dur)
|
||||
{
|
||||
_duration = dur;
|
||||
|
||||
auto n = num % _base;
|
||||
const int32_t shgt{_height * _base};
|
||||
|
||||
if (_to != n) {
|
||||
// _y = _fy = _ty % (_height * _base);
|
||||
_fy = _y % shgt;
|
||||
_ty = n * _height;
|
||||
if (_ty < _fy) {
|
||||
_ty += shgt;
|
||||
}
|
||||
_start_at = m5::utility::millis();
|
||||
_to = n;
|
||||
// printf("==> %d >Y ;%d -> %d\n", _to, _fy, _ty);
|
||||
}
|
||||
}
|
||||
|
||||
bool RotaryCounter::Number::update(const unsigned long now)
|
||||
{
|
||||
const int32_t shgt{_height * _base};
|
||||
if (_start_at) {
|
||||
if (now >= _start_at + _duration) {
|
||||
_start_at = 0;
|
||||
_ty %= shgt;
|
||||
_y = _fy = _ty;
|
||||
} else {
|
||||
float t = (now - _start_at) / (float)_duration;
|
||||
_y = (int16_t)(_fy + (_ty - _fy) * t) % shgt;
|
||||
// printf(">>> [%d] %d: (%d - %d) %f\n", _to, _y, _fy, _ty, t);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RotaryCounter::RotaryCounter(LovyanGFX* parent, const size_t maxDigits, LGFX_Sprite* src, const uint8_t base)
|
||||
: _parent(parent), _base(base)
|
||||
{
|
||||
_numbers.resize(maxDigits);
|
||||
if (src) {
|
||||
construct(src);
|
||||
}
|
||||
}
|
||||
|
||||
void RotaryCounter::construct(LGFX_Sprite* src)
|
||||
{
|
||||
assert(src != nullptr && "src must be NOT nullptr");
|
||||
for (auto& n : _numbers) {
|
||||
n = Number(src, _base);
|
||||
}
|
||||
}
|
||||
|
||||
bool RotaryCounter::update()
|
||||
{
|
||||
bool updated{};
|
||||
if (!_pause) {
|
||||
auto now = m5::utility::millis();
|
||||
for (auto&& n : _numbers) {
|
||||
updated |= n.update(now);
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
void RotaryCounter::animate(const uint32_t val, const unsigned long dur)
|
||||
{
|
||||
uint32_t v{val};
|
||||
for (auto it = _numbers.rbegin(); it != _numbers.rend(); ++it) {
|
||||
it->animate(v % 10, dur);
|
||||
v /= 10;
|
||||
}
|
||||
}
|
||||
|
||||
void RotaryCounter::animate(const size_t digit, const uint8_t val, const unsigned long dur)
|
||||
{
|
||||
if (digit >= _numbers.size()) {
|
||||
M5_LIB_LOGE("Illegal digit %zu/%zu", digit, _numbers.size());
|
||||
return;
|
||||
}
|
||||
_numbers[digit].animate(val, dur);
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_rotary_couter.hpp
|
||||
@brief Rotary counter
|
||||
*/
|
||||
#ifndef UI_ROTARY_COUNTER_HPP
|
||||
#define UI_ROTARY_COUNTER_HPP
|
||||
|
||||
#include <M5GFX.h>
|
||||
#include <vector>
|
||||
#include "ui_lgfx_extension.hpp"
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
/*!
|
||||
@class RotaryCounter
|
||||
@brief Rotary counter with any digits
|
||||
*/
|
||||
class RotaryCounter {
|
||||
public:
|
||||
using elapsed_time_t = unsigned long;
|
||||
|
||||
// For each number
|
||||
class Number {
|
||||
public:
|
||||
Number()
|
||||
{
|
||||
}
|
||||
Number(LGFX_Sprite* src, const uint8_t base = 10);
|
||||
inline LGFX_Sprite* sprite()
|
||||
{
|
||||
return _src;
|
||||
}
|
||||
inline int32_t sourceY() const
|
||||
{
|
||||
return _y;
|
||||
}
|
||||
inline uint32_t width() const
|
||||
{
|
||||
return _src ? _src->width() : 0U;
|
||||
}
|
||||
inline uint32_t height() const
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
inline void set(const uint8_t num)
|
||||
{
|
||||
animate(num, 0);
|
||||
}
|
||||
void animate(const uint8_t num, const uint32_t dur);
|
||||
bool update(const elapsed_time_t now);
|
||||
|
||||
private:
|
||||
LGFX_Sprite* _src{};
|
||||
int32_t _height{}, _fy{}, _ty{}, _y{};
|
||||
elapsed_time_t _start_at{}, _duration{};
|
||||
uint8_t _base{};
|
||||
uint8_t _to{};
|
||||
};
|
||||
|
||||
using vector_type_t = std::vector<Number>;
|
||||
|
||||
/*!
|
||||
@brief Constructor
|
||||
@param parent Push target
|
||||
@param src Source sprite
|
||||
@param digits Number of digits
|
||||
@param base How many decimal digits?
|
||||
@note The source sprite should consist of the following
|
||||
[0...9] [0...5] [0...2]
|
||||
+---+ +---+ +---+
|
||||
| 0 | | 0 | | 0 |
|
||||
| 1 | | 1 | | 1 |
|
||||
| 2 | | 2 | | 2 |
|
||||
| 3 | | 3 | | 0 |
|
||||
| 4 | | 4 | +---+
|
||||
| 5 | | 5 |
|
||||
| 6 | | 0 |
|
||||
| 7 | +---*
|
||||
| 8 |
|
||||
| 9 |
|
||||
| 0 |
|
||||
+---+
|
||||
*/
|
||||
RotaryCounter(LovyanGFX* parent, const size_t digits, LGFX_Sprite* src = nullptr, const uint8_t base = 10);
|
||||
virtual ~RotaryCounter()
|
||||
{
|
||||
}
|
||||
|
||||
//! @brief Construct with source sprite
|
||||
void construct(LGFX_Sprite* src);
|
||||
|
||||
//!@brief Update all numbers
|
||||
virtual bool update();
|
||||
|
||||
///@Properties
|
||||
///@{
|
||||
const vector_type_t& numbers() const
|
||||
{
|
||||
return _numbers;
|
||||
}
|
||||
vector_type_t& numbers()
|
||||
{
|
||||
return _numbers;
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Control
|
||||
///@{
|
||||
/*!@brief Pause/Resume */
|
||||
inline void pause(const bool paused)
|
||||
{
|
||||
_pause = paused;
|
||||
}
|
||||
//! @brief Pause
|
||||
inline void pause()
|
||||
{
|
||||
pause(true);
|
||||
}
|
||||
//! @brief Resume
|
||||
inline void resume()
|
||||
{
|
||||
pause(false);
|
||||
}
|
||||
//! @brief Animate and change values (all)
|
||||
void animate(const uint32_t val, const elapsed_time_t dur);
|
||||
//! @brief Set value (all)
|
||||
inline void set(const uint32_t val)
|
||||
{
|
||||
animate(val, 0U);
|
||||
}
|
||||
//! Animate and change values (partial)
|
||||
void animate(const size_t digit, const uint8_t val, const elapsed_time_t dur);
|
||||
//! @brief Set value (partial)
|
||||
inline void set(const size_t digit, const uint8_t val)
|
||||
{
|
||||
animate(digit, val, 0U);
|
||||
}
|
||||
///@}
|
||||
|
||||
///@warning If you have already set clip rect to dst, save and set it again on your own.
|
||||
///@warning After this function call, the clip rectangle in dst is cleared.
|
||||
///@name Push
|
||||
///@{
|
||||
inline void push(const int32_t x, const int32_t y)
|
||||
{
|
||||
push(_parent, x, y);
|
||||
}
|
||||
void push(LovyanGFX* dst, const int32_t x, const int32_t y)
|
||||
{
|
||||
int32_t left{x};
|
||||
for (auto&& n : _numbers) {
|
||||
m5::lgfx::pushPartial(dst, left, y, n.width(), n.height(), n.sprite(), 0, n.sourceY());
|
||||
left += n.width();
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
inline void push(const int32_t x, const int32_t y, const T& transp)
|
||||
{
|
||||
push(_parent, x, y, transp);
|
||||
}
|
||||
template <typename T>
|
||||
void push(LovyanGFX* dst, const int32_t x, const int32_t y, const T& transp)
|
||||
{
|
||||
int32_t left{x};
|
||||
if (!_fit) {
|
||||
fit();
|
||||
_fit = true;
|
||||
}
|
||||
for (auto&& n : _numbers) {
|
||||
m5::lgfx::pushPartial(dst, left, y, n.width(), n.height(), n.sprite(), 0, n.sourceY(), transp);
|
||||
left += n.width();
|
||||
}
|
||||
}
|
||||
///@}
|
||||
|
||||
protected:
|
||||
void fit();
|
||||
|
||||
LovyanGFX* _parent{};
|
||||
int32_t _height{};
|
||||
vector_type_t _numbers{};
|
||||
uint8_t _base{};
|
||||
bool _fit{}, _pause{};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
#endif
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_scale_meter.cpp
|
||||
@brief Scale meter
|
||||
*/
|
||||
#include "ui_scale_meter.hpp"
|
||||
#include <M5Utility.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
constexpr float table0[] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
|
||||
constexpr float table1[] = {0.125f, 0.125f * 3, 0.125f * 5, 0.125f * 7};
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
void ScaleMeter::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
|
||||
{
|
||||
dst->setClipRect(x, y, width(), height());
|
||||
|
||||
auto rad = _radius - 1;
|
||||
|
||||
// gauge
|
||||
int32_t r0{rad}, r1{rad - 1};
|
||||
float sdeg{std::fmin(_minDeg, _maxDeg)};
|
||||
float edeg{std::fmax(_minDeg, _maxDeg)};
|
||||
dst->fillArc(x + _cx, y + _cy, r0, r1, sdeg, edeg, gaugeColor());
|
||||
|
||||
const auto w = _maxDeg - _minDeg;
|
||||
constexpr float deg_to_rad = 0.017453292519943295769236907684886f;
|
||||
for (auto&& e : table0) {
|
||||
const float f = _minDeg + w * e;
|
||||
const float cf = std::cos(f * deg_to_rad);
|
||||
const float sf = std::sin(f * deg_to_rad);
|
||||
int32_t sx = rad * cf;
|
||||
int32_t sy = rad * sf;
|
||||
int32_t ex = (rad - 4) * cf;
|
||||
int32_t ey = (rad - 4) * sf;
|
||||
dst->drawLine(x + _cx + sx, y + _cy + sy, x + _cx + ex, y + _cy + ey, gaugeColor());
|
||||
}
|
||||
for (auto&& e : table1) {
|
||||
const float f = _minDeg + w * e;
|
||||
const float cf = std::cos(f * deg_to_rad);
|
||||
const float sf = std::sin(f * deg_to_rad);
|
||||
int32_t sx = rad * cf;
|
||||
int32_t sy = rad * sf;
|
||||
int32_t ex = (rad - 2) * cf;
|
||||
int32_t ey = (rad - 2) * sf;
|
||||
dst->drawLine(x + _cx + sx, y + _cy + sy, x + _cx + ex, y + _cy + ey, gaugeColor());
|
||||
}
|
||||
|
||||
// needle
|
||||
float deg = _minDeg + (_maxDeg - _minDeg) * ratio(val);
|
||||
int32_t tx = rad * std::cos(deg * deg_to_rad);
|
||||
int32_t ty = rad * std::sin(deg * deg_to_rad);
|
||||
dst->drawLine(x + _cx, y + _cy, x + _cx + tx, y + _cy + ty, needleColor());
|
||||
|
||||
dst->clearClipRect();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_scale_meter.hpp
|
||||
@brief Scale meter
|
||||
*/
|
||||
#ifndef UI_SCALE_METER_HPP
|
||||
#define UI_SCALE_METER_HPP
|
||||
|
||||
#include "ui_base.hpp"
|
||||
|
||||
namespace m5 {
|
||||
namespace ui {
|
||||
|
||||
class ScaleMeter : public Base {
|
||||
public:
|
||||
ScaleMeter(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const float minDeg, const float maxDeg,
|
||||
const int32_t wid, const int32_t hgt, const int32_t cx, const int32_t cy, const uint32_t radius)
|
||||
: Base(parent, minimum, maximum, wid, hgt), _cx(cx), _cy(cy), _radius(radius), _minDeg{minDeg}, _maxDeg{maxDeg}
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~ScaleMeter()
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
|
||||
|
||||
private:
|
||||
int32_t _cx{}, _cy{}, _radius{};
|
||||
float _minDeg{}, _maxDeg{};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace m5
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file sprite.cpp
|
||||
@brief Shared sprite
|
||||
*/
|
||||
|
||||
#include "sprite.hpp"
|
||||
|
||||
struct NumberSprite {
|
||||
const lgfx::IFont* font;
|
||||
LGFX_Sprite* sprite;
|
||||
const int32_t width, height;
|
||||
const uint32_t base;
|
||||
};
|
||||
|
||||
// For rotary counter
|
||||
LGFX_Sprite number10_6x8;
|
||||
LGFX_Sprite number10_8x16;
|
||||
LGFX_Sprite number6_6x8;
|
||||
LGFX_Sprite number6_8x16;
|
||||
|
||||
void make_shared_sprites()
|
||||
{
|
||||
NumberSprite table[] = {
|
||||
{&fonts::Font0, &number10_6x8, 6, 8, 10},
|
||||
{&fonts::Font2, &number10_8x16, 8, 16, 10},
|
||||
{&fonts::Font0, &number6_6x8, 6, 8, 6},
|
||||
{&fonts::Font2, &number6_8x16, 8, 16, 6},
|
||||
};
|
||||
|
||||
for (auto&& e : table) {
|
||||
e.sprite->setPsram(false);
|
||||
e.sprite->setColorDepth(1);
|
||||
e.sprite->createSprite(e.width + 2, e.height * (e.base + 1));
|
||||
e.sprite->setPaletteColor(0, TFT_BLACK);
|
||||
e.sprite->setPaletteColor(1, TFT_WHITE);
|
||||
e.sprite->setTextColor(1, 0);
|
||||
e.sprite->setFont(e.font);
|
||||
for (int i = 0; i <= e.base; ++i) {
|
||||
e.sprite->setCursor(1, i * e.height + 1);
|
||||
e.sprite->printf("%d", i % e.base);
|
||||
}
|
||||
// e.sprite->drawFastVLine(0, 0, e.sprite->height(), 1);
|
||||
// e.sprite->drawFastVLine(e.sprite->width() - 1, 0, e.sprite->height(), 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file sprite.hpp
|
||||
@brief Shared sprite
|
||||
*/
|
||||
#ifndef SPRITE_HPP
|
||||
#define SPRITE_HPP
|
||||
|
||||
#include <M5GFX.h>
|
||||
|
||||
void make_shared_sprites();
|
||||
|
||||
// For rotary counter
|
||||
extern LGFX_Sprite number10_6x8;
|
||||
extern LGFX_Sprite number10_8x16;
|
||||
extern LGFX_Sprite number6_6x8;
|
||||
extern LGFX_Sprite number6_8x16;
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitBase.cpp
|
||||
@brief UI for UnitBase
|
||||
*/
|
||||
|
||||
#include "ui_UnitBase.hpp"
|
||||
|
||||
UnitUIBase::UnitUIBase(LovyanGFX* parent) : _parent(parent)
|
||||
{
|
||||
_sem = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(_sem);
|
||||
}
|
||||
|
||||
UnitUIBase::~UnitUIBase()
|
||||
{
|
||||
xSemaphoreTake(_sem, portMAX_DELAY);
|
||||
vSemaphoreDelete(_sem);
|
||||
}
|
||||
|
||||
bool UnitUIBase::lock(portTickType bt)
|
||||
{
|
||||
return xSemaphoreTake(_sem, bt) == pdTRUE;
|
||||
}
|
||||
|
||||
void UnitUIBase::unlock()
|
||||
{
|
||||
xSemaphoreGive(_sem);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitBase.hpp
|
||||
@brief UI for UnitBase
|
||||
*/
|
||||
#ifndef UI_UNIT_BASE_HPP
|
||||
#define UI_UNIT_BASE_HPP
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <M5GFX.h>
|
||||
#include <vector>
|
||||
|
||||
class UnitUIBase {
|
||||
public:
|
||||
explicit UnitUIBase(LovyanGFX* parent);
|
||||
virtual ~UnitUIBase();
|
||||
|
||||
bool lock(portTickType bt = portMAX_DELAY);
|
||||
// TickType_t
|
||||
void unlock();
|
||||
|
||||
virtual void construct() = 0;
|
||||
virtual void update() = 0;
|
||||
|
||||
virtual void push(LovyanGFX* dst, const int32_t x, const int32_t y) = 0;
|
||||
inline void push(const int32_t x, const int32_t y)
|
||||
{
|
||||
push(_parent, x, y);
|
||||
}
|
||||
|
||||
protected:
|
||||
LovyanGFX* _parent{};
|
||||
int32_t _wid{}, _hgt{};
|
||||
|
||||
private:
|
||||
volatile SemaphoreHandle_t _sem{};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitCO2.cpp
|
||||
@brief UI for UnitCO2
|
||||
*/
|
||||
#include "ui_UnitCO2.hpp"
|
||||
#include <M5Unified.h>
|
||||
#include <M5Utility.h>
|
||||
#include <iterator>
|
||||
|
||||
namespace {
|
||||
constexpr int32_t GAP{2};
|
||||
constexpr float COEFF{100.0f};
|
||||
constexpr float COEFF_RECIPROCAL{1.0f / COEFF};
|
||||
|
||||
constexpr int32_t min_co2{0};
|
||||
constexpr int32_t max_co2{6000};
|
||||
constexpr int32_t min_temp{-10};
|
||||
constexpr int32_t max_temp{40};
|
||||
|
||||
m5gfx::rgb565_t temp_chooseColor(const int32_t val)
|
||||
{
|
||||
return val > 0 ? m5gfx::rgb565_t(0xfe, 0xcb, 0xf2) : m5gfx::rgb565_t(0xb8, 0xc2, 0xf2);
|
||||
}
|
||||
|
||||
constexpr std::initializer_list<m5::ui::ColorRange> co2_color_table = {
|
||||
{1000, m5gfx::rgb565_t(TFT_GREEN)},
|
||||
{1500, m5gfx::rgb565_t(TFT_GOLD)},
|
||||
{2500, m5gfx::rgb565_t(TFT_ORANGE)},
|
||||
{6000, m5gfx::rgb565_t(TFT_RED)},
|
||||
};
|
||||
|
||||
m5gfx::rgb565_t co2_chooseColor(const int32_t val)
|
||||
{
|
||||
for (auto&& e : co2_color_table) {
|
||||
if (val <= e.lesseq) {
|
||||
return e.clr;
|
||||
}
|
||||
}
|
||||
return (std::end(co2_color_table) - 1)->clr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void UnitCO2SmallUI::construct()
|
||||
{
|
||||
auto& lcd = M5.Display;
|
||||
_wid = lcd.width() >> 1;
|
||||
_hgt = lcd.height() >> 1;
|
||||
int32_t wh = std::max(_wid / 2, _hgt / 2) - GAP * 2;
|
||||
|
||||
_tempMeter.reset(new m5::ui::GaugeMeter(_parent, min_temp * COEFF, max_temp * COEFF, 90.0f + 45.0f,
|
||||
90.0f - 45.0f + 360.f, wh, wh, 10));
|
||||
_tempMeter->set(0);
|
||||
|
||||
_co2Meter.reset(
|
||||
new m5::ui::ColorBarMeterH(_parent, min_co2, max_co2, _wid - GAP * 2, _hgt - wh - GAP * 2, co2_color_table));
|
||||
}
|
||||
|
||||
void UnitCO2SmallUI::push_back(const int32_t co2, const float temp)
|
||||
{
|
||||
_tempMeter->animate(temp * COEFF, 1000);
|
||||
_co2Meter->animate(co2, 1000);
|
||||
}
|
||||
|
||||
void UnitCO2SmallUI::update()
|
||||
{
|
||||
_tempMeter->update();
|
||||
_tempMeter->setNeedleColor(temp_chooseColor(_tempMeter->value()));
|
||||
_co2Meter->update();
|
||||
}
|
||||
|
||||
void UnitCO2SmallUI::push(LovyanGFX* dst, const int32_t x, const int32_t y)
|
||||
{
|
||||
auto left = x;
|
||||
auto right = x + _wid - 1;
|
||||
auto top = y;
|
||||
auto bottom = y + _hgt - 1;
|
||||
auto w = right - left + 1;
|
||||
auto h = bottom - top + 1;
|
||||
|
||||
// BG
|
||||
dst->fillRoundRect(x, y, _wid, _hgt, GAP * 2, TFT_MAGENTA);
|
||||
dst->fillRoundRect(x + GAP, y + GAP, _wid - GAP * 2, _hgt - GAP * 2, GAP, TFT_BLACK);
|
||||
|
||||
auto tcx = left + GAP;
|
||||
auto tcy = top + GAP;
|
||||
_tempMeter->push(dst, x + GAP, y + GAP);
|
||||
_co2Meter->push(dst, x + GAP, tcy + _tempMeter->height() + GAP * 2);
|
||||
|
||||
//
|
||||
auto f = dst->getFont();
|
||||
dst->setFont(&fonts::Font0);
|
||||
dst->setTextColor(TFT_WHITE);
|
||||
auto td = dst->getTextDatum();
|
||||
|
||||
dst->setTextDatum(textdatum_t::middle_center);
|
||||
auto s = m5::utility::formatString("%3.2fC", _tempMeter->value() * COEFF_RECIPROCAL);
|
||||
dst->drawString(s.c_str(), tcx + _tempMeter->width() / 2, tcy + _tempMeter->height() / 2);
|
||||
dst->drawString("TEMP", tcx + _tempMeter->width() / 2, tcy + _tempMeter->height() / 2 + 10);
|
||||
|
||||
dst->setTextDatum(textdatum_t::top_right);
|
||||
dst->drawString("CO2", right - GAP, top + _tempMeter->height() + GAP - 10);
|
||||
auto sw = dst->drawString("ppm", right - GAP, top + _tempMeter->height() + GAP);
|
||||
dst->setTextColor((uint16_t)co2_chooseColor(_co2Meter->value()));
|
||||
s = m5::utility::formatString("%d", _co2Meter->value());
|
||||
dst->drawString(s.c_str(), right - GAP - sw, top + _tempMeter->height() + GAP);
|
||||
|
||||
dst->setFont(&fonts::Font2);
|
||||
dst->setTextColor(TFT_WHITE);
|
||||
dst->setTextDatum(textdatum_t::middle_center);
|
||||
dst->drawString("UnitCO2", left + w / 4 * 3, top + h / 4);
|
||||
|
||||
dst->setTextDatum(td);
|
||||
dst->setFont(f);
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitCO2.hpp
|
||||
@brief UI for UnitCO2
|
||||
*/
|
||||
#ifndef UI_UNIT_CO2_HPP
|
||||
#define UI_UNIT_CO2_HPP
|
||||
|
||||
#include "parts/ui_rotary_counter.hpp"
|
||||
#include "parts/ui_scale_meter.hpp"
|
||||
#include "parts/ui_gauge_meter.hpp"
|
||||
#include "parts/ui_bar_meter.hpp"
|
||||
#include "ui_UnitBase.hpp"
|
||||
#include <memory>
|
||||
|
||||
class UnitCO2SmallUI : public UnitUIBase {
|
||||
public:
|
||||
explicit UnitCO2SmallUI(LovyanGFX* parent = nullptr) : UnitUIBase(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void push_back(const int32_t co2, const float temp);
|
||||
|
||||
virtual void construct() override;
|
||||
virtual void update() override;
|
||||
virtual void push(LovyanGFX* dst, const int32_t x, const int32_t y) override;
|
||||
|
||||
private:
|
||||
LovyanGFX* _parent{};
|
||||
int32_t _wid{}, _hgt{};
|
||||
std::unique_ptr<m5::ui::GaugeMeter> _tempMeter{};
|
||||
std::unique_ptr<m5::ui::ColorBarMeterH> _co2Meter{};
|
||||
};
|
||||
#endif
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitENV3.cpp
|
||||
@brief UI for UnitENV3
|
||||
*/
|
||||
#include "ui_UnitENV3.hpp"
|
||||
#include <M5Unified.h>
|
||||
#include <M5Utility.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
constexpr int32_t GAP{2};
|
||||
constexpr float COEFF{100.0f};
|
||||
constexpr float COEFF_RECIPROCAL{1.0f / COEFF};
|
||||
|
||||
constexpr m5gfx::rgb565_t hum_needle_color{32, 147, 223};
|
||||
constexpr m5gfx::rgb565_t pres_needle_color{161, 54, 64};
|
||||
|
||||
constexpr int32_t min_temp{-10};
|
||||
constexpr int32_t max_temp{40};
|
||||
constexpr int32_t min_hum{0};
|
||||
constexpr int32_t max_hum{100};
|
||||
constexpr int32_t min_pres{0};
|
||||
constexpr int32_t max_pres{1500};
|
||||
|
||||
m5gfx::rgb565_t temp_chooseColor(const int32_t val)
|
||||
{
|
||||
return val > 0 ? m5gfx::rgb565_t(0xfe, 0xcb, 0xf2) : m5gfx::rgb565_t(0xb8, 0xc2, 0xf2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void UnitENV3SmallUI::construct()
|
||||
{
|
||||
auto& lcd = M5.Display;
|
||||
_wid = lcd.width() >> 1;
|
||||
_hgt = lcd.height() >> 1;
|
||||
|
||||
// auto left = 0 + GAP;
|
||||
// auto right = _wid - GAP - 1;
|
||||
// auto top = 0;
|
||||
// auto bottom = _hgt - 1;
|
||||
auto w = _wid / 5;
|
||||
auto h = _hgt / 2 - GAP * 2;
|
||||
auto rad = std::min(w, h / 2);
|
||||
auto wh = (std::min(_wid, _hgt) >> 1) - GAP * 2;
|
||||
|
||||
_tempMeterSHT.reset(
|
||||
new m5::ui::GaugeMeter(_parent, min_temp * COEFF, max_temp * COEFF, 25.0f, -25.0f + 360.f, wh, wh, 10));
|
||||
_tempMeterQMP.reset(
|
||||
new m5::ui::GaugeMeter(_parent, min_temp * COEFF, max_temp * COEFF, 25.0f, -25.0f + 360.f, wh, wh, 10));
|
||||
_humMeter.reset(
|
||||
new m5::ui::ScaleMeter(_parent, min_hum * COEFF, max_hum * COEFF, 360.0f + 90.0f, 270.0f, w, h, 0, h / 2, rad));
|
||||
_presMeter.reset(new m5::ui::ScaleMeter(_parent, min_pres * COEFF, max_pres * COEFF, 360.0f + 90.0f, 270.0f, w, h,
|
||||
0, h / 2, rad));
|
||||
|
||||
_humMeter->setNeedleColor(hum_needle_color);
|
||||
_presMeter->setNeedleColor(pres_needle_color);
|
||||
}
|
||||
|
||||
void UnitENV3SmallUI::sht30_push_back(const float tmp, const float hum)
|
||||
{
|
||||
_tempMeterSHT->animate(tmp * COEFF, 10);
|
||||
_humMeter->animate(hum * COEFF, 10);
|
||||
}
|
||||
|
||||
void UnitENV3SmallUI::qmp6988_push_back(const float tmp, const float pa)
|
||||
{
|
||||
_tempMeterQMP->animate(tmp * COEFF, 10);
|
||||
_presMeter->animate(pa * COEFF * 0.01f, 10); // pa to hPa
|
||||
}
|
||||
|
||||
void UnitENV3SmallUI::update()
|
||||
{
|
||||
lock();
|
||||
_tempMeterSHT->update();
|
||||
_tempMeterQMP->update();
|
||||
_tempMeterSHT->setNeedleColor(temp_chooseColor(_tempMeterSHT->value()));
|
||||
_tempMeterQMP->setNeedleColor(temp_chooseColor(_tempMeterQMP->value()));
|
||||
_humMeter->update();
|
||||
_presMeter->update();
|
||||
unlock();
|
||||
}
|
||||
|
||||
void UnitENV3SmallUI::push(LovyanGFX* dst, const int32_t x, const int32_t y)
|
||||
{
|
||||
auto left = x;
|
||||
auto right = x + _wid - 1;
|
||||
auto top = y;
|
||||
auto bottom = y + _hgt - 1;
|
||||
auto w = right - left + 1;
|
||||
auto h = bottom - top + 1;
|
||||
|
||||
auto sx = left + GAP;
|
||||
auto sy = top + GAP;
|
||||
auto qx = left + GAP;
|
||||
auto qy = top + h / 2 + GAP;
|
||||
auto hx = right - (_humMeter->width() + GAP);
|
||||
auto hy = top + GAP;
|
||||
auto px = right - (_presMeter->width() + GAP);
|
||||
auto py = top + h / 2 + GAP;
|
||||
|
||||
auto f = dst->getFont();
|
||||
dst->setFont(&fonts::Font0);
|
||||
dst->setTextColor(TFT_WHITE);
|
||||
auto td = dst->getTextDatum();
|
||||
|
||||
// BG
|
||||
dst->fillRoundRect(x, y, _wid, _hgt, GAP, TFT_GREEN);
|
||||
dst->fillRoundRect(x + GAP, y + GAP, _wid - GAP * 2, _hgt - GAP * 2, GAP, TFT_BLACK);
|
||||
|
||||
// meters
|
||||
_tempMeterSHT->push(dst, sx, sy);
|
||||
_humMeter->push(dst, hx, hy);
|
||||
_tempMeterQMP->push(dst, qx, qy);
|
||||
_presMeter->push(dst, px, py);
|
||||
|
||||
// text
|
||||
dst->setTextDatum(textdatum_t::middle_left);
|
||||
auto s = m5::utility::formatString("T:%3.2fC", _tempMeterSHT->value() * COEFF_RECIPROCAL);
|
||||
dst->drawString(s.c_str(), sx + 16, sy + _tempMeterSHT->height() / 2);
|
||||
s = m5::utility::formatString("T:%3.2fC", _tempMeterQMP->value() * COEFF_RECIPROCAL);
|
||||
dst->drawString(s.c_str(), qx + 16, qy + _tempMeterQMP->height() / 2);
|
||||
dst->setTextDatum(textdatum_t::middle_right);
|
||||
s = m5::utility::formatString("H:%3.2f%%", _humMeter->value() * COEFF_RECIPROCAL);
|
||||
dst->drawString(s.c_str(), hx, hy + _humMeter->height() / 2);
|
||||
s = m5::utility::formatString("P:%4.0f", _presMeter->value() * COEFF_RECIPROCAL);
|
||||
dst->drawString(s.c_str(), px, py + _presMeter->height() / 2);
|
||||
|
||||
#if 0
|
||||
auto s = m5::utility::formatString("%dC", max_temp);
|
||||
dst->drawString(s.c_str(), sx, sy);
|
||||
dst->drawString(s.c_str(), qx, qy);
|
||||
|
||||
s = m5::utility::formatString("%dC", (max_temp - min_temp) / 2);
|
||||
dst->setTextDatum(textdatum_t::middle_left);
|
||||
dst->drawString(s.c_str(), sx, sy + _tempMeterSHT->height() / 2);
|
||||
dst->drawString(s.c_str(), qx, qy + _tempMeterSHT->height() / 2);
|
||||
|
||||
dst->setTextDatum(textdatum_t::bottom_left);
|
||||
s = m5::utility::formatString("%dC", min_temp);
|
||||
dst->drawString(s.c_str(), sx, sy + _tempMeterSHT->height());
|
||||
dst->drawString(s.c_str(), qx, qy + _tempMeterSHT->height());
|
||||
#endif
|
||||
|
||||
dst->setTextDatum(textdatum_t::top_right);
|
||||
s = m5::utility::formatString("%d%%", max_hum);
|
||||
dst->drawString(s.c_str(), right - GAP, hy);
|
||||
dst->setTextDatum(textdatum_t::bottom_right);
|
||||
s = m5::utility::formatString("%d%%", min_hum);
|
||||
dst->drawString(s.c_str(), right - GAP, hy + _humMeter->height());
|
||||
|
||||
dst->setTextDatum(textdatum_t::top_right);
|
||||
s = m5::utility::formatString("%dhPa", max_pres);
|
||||
dst->drawString(s.c_str(), right - GAP, py);
|
||||
dst->setTextDatum(textdatum_t::bottom_right);
|
||||
s = m5::utility::formatString("%dhPa", min_pres);
|
||||
dst->drawString(s.c_str(), right - GAP, py + _presMeter->height());
|
||||
|
||||
//
|
||||
#if 0
|
||||
dst->setTextDatum(textdatum_t::top_left);
|
||||
dst->drawString("SHT30", sx + _tempMeterSHT->width() + GAP, sy + 10);
|
||||
s = m5::utility::formatString(" T: %3.2f C", _tempMeterSHT->valueTo() * COEFF_RECIPROCAL);
|
||||
dst->drawString(s.c_str(), sx + _tempMeterSHT->width() + GAP, sy + 10 * 2);
|
||||
s = m5::utility::formatString(" H: %3.2f RH", _humMeter->valueTo() * COEFF_RECIPROCAL);
|
||||
dst->drawString(s.c_str(), sx + _tempMeterSHT->width() + GAP, sy + 10 * 3);
|
||||
dst->setTextDatum(textdatum_t::bottom_right);
|
||||
dst->drawString("QMP6988", right - (_tempMeterSHT->width() + GAP), bottom - GAP - 10 + 1);
|
||||
s = m5::utility::formatString(" T: %4.2f C", _tempMeterQMP->valueTo() * COEFF_RECIPROCAL);
|
||||
dst->drawString(s.c_str(), right - (_tempMeterSHT->width() + GAP), bottom - GAP - 10 * 2);
|
||||
s = m5::utility::formatString(" P: %4.2f hPa", _presMeter->valueTo() * COEFF_RECIPROCAL);
|
||||
dst->drawString(s.c_str(), right - (_tempMeterSHT->width() + GAP), bottom - GAP - 10 * 3);
|
||||
#endif
|
||||
|
||||
dst->setTextDatum(textdatum_t::top_center);
|
||||
dst->drawString("SHT30", x + w / 2, top + GAP);
|
||||
dst->setTextDatum(textdatum_t::bottom_center);
|
||||
dst->drawString("QMP6988", x + w / 2, bottom - GAP);
|
||||
|
||||
dst->setFont(&fonts::Font2);
|
||||
dst->setTextColor(TFT_GREEN);
|
||||
dst->setTextDatum(textdatum_t::middle_center);
|
||||
dst->drawString("UnitENVIII", left + w / 2, top + h / 2);
|
||||
|
||||
dst->setTextDatum(td);
|
||||
dst->setFont(f);
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitENV3.hpp
|
||||
@brief UI for UnitENV3
|
||||
*/
|
||||
#ifndef UI_UNIT_ENV3_HPP
|
||||
#define UI_UNIT_ENV3_HPP
|
||||
|
||||
#include "parts/ui_scale_meter.hpp"
|
||||
#include "parts/ui_gauge_meter.hpp"
|
||||
#include "ui_UnitBase.hpp"
|
||||
#include <memory>
|
||||
|
||||
class UnitENV3SmallUI : public UnitUIBase {
|
||||
public:
|
||||
explicit UnitENV3SmallUI(LovyanGFX* parent) : UnitUIBase(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void construct() override;
|
||||
|
||||
void sht30_push_back(const float tmp, const float hum); // SHT30
|
||||
void qmp6988_push_back(const float tmp, const float pres); // QMP6988
|
||||
|
||||
void update() override;
|
||||
void push(LovyanGFX* dst, const int32_t x, const int32_t y) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<m5::ui::GaugeMeter> _tempMeterSHT{};
|
||||
std::unique_ptr<m5::ui::GaugeMeter> _tempMeterQMP{};
|
||||
std::unique_ptr<m5::ui::ScaleMeter> _humMeter;
|
||||
std::unique_ptr<m5::ui::ScaleMeter> _presMeter;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitHEART.cpp
|
||||
@brief UI for UnitHEART
|
||||
*/
|
||||
#include "ui_UnitHEART.hpp"
|
||||
#include <M5Unified.h>
|
||||
#include <M5Utility.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
constexpr int32_t GAP{2};
|
||||
constexpr float COEFF{100.0f};
|
||||
constexpr float COEFF_RECIPROCAL{1.0f / COEFF};
|
||||
|
||||
constexpr m5gfx::rgb565_t ir_gauge_color{161, 54, 54};
|
||||
constexpr m5gfx::rgb565_t spo2_gauge_color{38, 41, 64};
|
||||
|
||||
constexpr int32_t min_spo2{90};
|
||||
constexpr int32_t max_spo2{100};
|
||||
|
||||
constexpr char spO2ustr[] = "%";
|
||||
} // namespace
|
||||
|
||||
void UnitHEARTSmallUI::construct()
|
||||
{
|
||||
auto& lcd = M5.Display;
|
||||
_wid = lcd.width() >> 1;
|
||||
_hgt = lcd.height() >> 1;
|
||||
|
||||
auto gw = _wid - GAP * 2;
|
||||
auto gh = (_hgt >> 1) - (GAP * 2 + 16);
|
||||
|
||||
_irPlotter.reset(new m5::ui::Plotter(_parent, gw, gw, gh));
|
||||
_irPlotter->setGaugeColor(ir_gauge_color);
|
||||
_spO2Plotter.reset(new m5::ui::Plotter(_parent, gw, min_spo2 * COEFF, max_spo2 * COEFF, gw, gh, COEFF));
|
||||
_spO2Plotter->setGaugeColor(spo2_gauge_color);
|
||||
_spO2Plotter->setUnitString(spO2ustr);
|
||||
_spO2Plotter->setGaugeTextDatum(textdatum_t::top_right);
|
||||
}
|
||||
|
||||
void UnitHEARTSmallUI::push_back(const int32_t ir, const int32_t red)
|
||||
{
|
||||
_intermediateBuffer.emplace_back(Data{ir, red});
|
||||
}
|
||||
|
||||
void UnitHEARTSmallUI::update()
|
||||
{
|
||||
if (_beatCounter > 0) {
|
||||
--_beatCounter;
|
||||
}
|
||||
|
||||
lock();
|
||||
for (auto&& e : _intermediateBuffer) {
|
||||
_monitor.push_back(e.ir, e.red);
|
||||
_monitor.update();
|
||||
|
||||
beat(_monitor.isBeat());
|
||||
_bpm = _monitor.bpm();
|
||||
|
||||
// _irPlotter->push_back(e.ir);
|
||||
_irPlotter->push_back(_monitor.latestIR());
|
||||
_spO2Plotter->push_back(_monitor.SpO2());
|
||||
}
|
||||
_intermediateBuffer.clear();
|
||||
unlock();
|
||||
|
||||
_irPlotter->update();
|
||||
_spO2Plotter->update();
|
||||
}
|
||||
|
||||
void UnitHEARTSmallUI::push(LovyanGFX* dst, const int32_t x, const int32_t y)
|
||||
{
|
||||
auto f = dst->getFont();
|
||||
dst->setFont(&fonts::Font0);
|
||||
dst->setTextColor(TFT_WHITE);
|
||||
auto td = dst->getTextDatum();
|
||||
|
||||
auto left = x;
|
||||
auto right = x + _wid - 1;
|
||||
auto top = y;
|
||||
auto bottom = y + _hgt - 1;
|
||||
auto w = right - left + 1;
|
||||
auto h = bottom - top + 1;
|
||||
|
||||
// BG
|
||||
dst->fillRoundRect(x, y, _wid, _hgt, GAP, TFT_YELLOW);
|
||||
dst->fillRoundRect(x + GAP, y + GAP, _wid - GAP * 2, _hgt - GAP * 2, GAP, TFT_BLACK);
|
||||
|
||||
_irPlotter->push(dst, x + GAP, y + GAP);
|
||||
_spO2Plotter->push(dst, x + GAP, y + _hgt - GAP - _spO2Plotter->height());
|
||||
|
||||
auto s = m5::utility::formatString("HR:%3dbpm", _bpm);
|
||||
dst->drawString(s.c_str(), left + GAP * 2, top + GAP * 2 + _irPlotter->height());
|
||||
|
||||
dst->setTextDatum(textdatum_t::bottom_right);
|
||||
s = m5::utility::formatString("SpO2:%3.2f%%", _monitor.SpO2());
|
||||
dst->drawString(s.c_str(), right - GAP, bottom - _spO2Plotter->height() - GAP);
|
||||
|
||||
constexpr int32_t radius{4};
|
||||
dst->fillCircle(right - radius * 2 - GAP, top + GAP * 2 + _irPlotter->height() + radius, radius,
|
||||
_beatCounter > 0 ? TFT_RED : TFT_DARKGRAY);
|
||||
|
||||
dst->setFont(&fonts::Font2);
|
||||
dst->setTextDatum(textdatum_t::middle_center);
|
||||
dst->setTextColor(TFT_YELLOW);
|
||||
dst->drawString("UnitHEART", left + w / 2, top + h / 2);
|
||||
|
||||
dst->setTextDatum(td);
|
||||
dst->setFont(f);
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitHEART.hpp
|
||||
@brief UI for UnitHEART
|
||||
*/
|
||||
#ifndef UI_UNIT_HEART_HPP
|
||||
#define UI_UNIT_HEART_HPP
|
||||
|
||||
#include "parts/ui_plotter.hpp"
|
||||
#include "ui_unitBase.hpp"
|
||||
#include <M5UnitUnifiedHEART.h>
|
||||
#include <memory>
|
||||
|
||||
class UnitHEARTSmallUI : public UnitUIBase {
|
||||
public:
|
||||
explicit UnitHEARTSmallUI(LovyanGFX* parent = nullptr) : UnitUIBase(parent)
|
||||
{
|
||||
}
|
||||
|
||||
inline m5::heart::PulseMonitor& monitor()
|
||||
{
|
||||
return _monitor;
|
||||
}
|
||||
|
||||
inline void beat(bool beated)
|
||||
{
|
||||
if (beated) {
|
||||
_beatCounter = 4;
|
||||
}
|
||||
}
|
||||
void push_back(const int32_t ir, const int32_t red);
|
||||
|
||||
void construct() override;
|
||||
|
||||
void update() override;
|
||||
void push(LovyanGFX* dst, const int32_t x, const int32_t y) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<m5::ui::Plotter> _irPlotter{};
|
||||
std::unique_ptr<m5::ui::Plotter> _spO2Plotter{};
|
||||
m5::heart::PulseMonitor _monitor{100.0f};
|
||||
int32_t _beatCounter{}, _bpm{};
|
||||
|
||||
struct Data {
|
||||
int32_t ir, red;
|
||||
};
|
||||
std::vector<Data> _intermediateBuffer{};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitTVOC.cpp
|
||||
@brief UI for UnitTVOC
|
||||
*/
|
||||
#include "ui_UnitTVOC.hpp"
|
||||
#include <M5Unified.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
const int32_t GAP{2};
|
||||
|
||||
constexpr m5gfx::rgb565_t co2_gauge_color{38, 41, 64};
|
||||
constexpr m5gfx::rgb565_t tvoc_gauge_color{64, 48, 26};
|
||||
constexpr char co2ustr[] = "ppm";
|
||||
constexpr char tvocustr[] = "ppb";
|
||||
|
||||
constexpr std::initializer_list<m5::ui::ColorRange> tvocGauge = {
|
||||
{220, m5gfx::rgb565_t(TFT_GREEN)}, {660, m5gfx::rgb565_t(TFT_GOLD)}, {1430, m5gfx::rgb565_t(TFT_ORANGE)},
|
||||
{2000, m5gfx::rgb565_t(TFT_RED)}, {3300, m5gfx::rgb565_t(TFT_VIOLET)}, {5500, m5gfx::rgb565_t(TFT_PURPLE)},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void UnitTVOCSmallUI::construct()
|
||||
{
|
||||
auto& lcd = M5.Display;
|
||||
_wid = lcd.width() >> 1;
|
||||
_hgt = lcd.height() >> 1;
|
||||
|
||||
auto bw = _wid / 6 - GAP * 2;
|
||||
auto bh = (_hgt >> 1) - GAP * 2 - 8;
|
||||
|
||||
auto pw = _wid / 6 * 5 - GAP * 2;
|
||||
auto ph = (_hgt >> 1) - GAP * 2 - 8;
|
||||
|
||||
_co2Bar.reset(new m5::ui::BarMeterV(_parent, 400, 6000, bw, bh));
|
||||
_tvocBar.reset(new m5::ui::ColorBarMeterV(_parent, 0, 5500, bw, bh, tvocGauge));
|
||||
|
||||
_co2Plotter.reset(new m5::ui::Plotter(_parent, pw, pw, ph));
|
||||
_co2Plotter->setGaugeColor(co2_gauge_color);
|
||||
_co2Plotter->setUnitString(co2ustr);
|
||||
_co2Plotter->setGaugeTextDatum(textdatum_t::top_right);
|
||||
|
||||
_tvocPlotter.reset(new m5::ui::Plotter(_parent, pw, pw, ph));
|
||||
_tvocPlotter->setGaugeColor(tvoc_gauge_color);
|
||||
_tvocPlotter->setUnitString(tvocustr);
|
||||
|
||||
_intermediateBuffer.reserve(pw);
|
||||
_intermediateBuffer.clear();
|
||||
}
|
||||
|
||||
void UnitTVOCSmallUI::push_back(const int32_t co2, const int32_t tvoc)
|
||||
{
|
||||
_co2Bar->animate(co2, 10);
|
||||
_tvocBar->animate(tvoc, 10);
|
||||
_intermediateBuffer.emplace_back(Data{co2, tvoc});
|
||||
}
|
||||
|
||||
void UnitTVOCSmallUI::update()
|
||||
{
|
||||
lock();
|
||||
for (auto&& e : _intermediateBuffer) {
|
||||
_co2Plotter->push_back(e.co2);
|
||||
_tvocPlotter->push_back(e.tvoc);
|
||||
}
|
||||
_intermediateBuffer.clear();
|
||||
unlock();
|
||||
|
||||
_co2Bar->update();
|
||||
_tvocBar->update();
|
||||
_co2Plotter->update();
|
||||
_tvocPlotter->update();
|
||||
}
|
||||
|
||||
void UnitTVOCSmallUI::push(LovyanGFX* dst, const int32_t x, const int32_t y)
|
||||
{
|
||||
auto left = x;
|
||||
auto right = x + _wid - 1;
|
||||
auto top = y;
|
||||
auto bottom = y + _hgt - 1;
|
||||
auto w = right - left + 1;
|
||||
auto h = bottom - top + 1;
|
||||
|
||||
auto f = dst->getFont();
|
||||
dst->setFont(&fonts::Font0);
|
||||
dst->setTextColor(TFT_WHITE);
|
||||
auto td = dst->getTextDatum();
|
||||
|
||||
// BG
|
||||
dst->fillRoundRect(x, y, _wid, _hgt, GAP, TFT_BLUE);
|
||||
dst->fillRoundRect(x + GAP, y + GAP, _wid - GAP * 2, _hgt - GAP * 2, GAP, TFT_BLACK);
|
||||
|
||||
_co2Bar->push(dst, left + GAP, y + GAP);
|
||||
_co2Plotter->push(dst, right - _co2Plotter->width() - GAP, y + GAP);
|
||||
|
||||
_tvocPlotter->push(dst, left + GAP, bottom - _tvocPlotter->height() - GAP);
|
||||
_tvocBar->push(dst, right - _tvocBar->width() - GAP, bottom - _tvocPlotter->height() - GAP);
|
||||
|
||||
dst->drawString("CO2eq", left + GAP * 3 + _co2Bar->width(), y + GAP);
|
||||
dst->setTextDatum(textdatum_t::bottom_right);
|
||||
dst->drawString("TVOC", right - (GAP * 2 + _tvocBar->width()), bottom);
|
||||
|
||||
dst->setFont(&fonts::Font2);
|
||||
dst->setTextColor(TFT_BLUE);
|
||||
dst->setTextDatum(textdatum_t::middle_center);
|
||||
dst->drawString("UnitTVOC", x + w / 2, y + h / 2);
|
||||
|
||||
dst->setTextDatum(td);
|
||||
dst->setFont(f);
|
||||
}
|
||||
// current valiue
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitTVOC.hpp
|
||||
@brief UI for UnitTVOC
|
||||
*/
|
||||
#ifndef UI_UNIT_TVOC_HPP
|
||||
#define UI_UNIT_TVOC_HPP
|
||||
|
||||
#include "parts/ui_bar_meter.hpp"
|
||||
#include "parts/ui_plotter.hpp"
|
||||
#include "ui_UnitBase.hpp"
|
||||
#include <memory>
|
||||
|
||||
class UnitTVOCSmallUI : public UnitUIBase {
|
||||
public:
|
||||
explicit UnitTVOCSmallUI(LovyanGFX* parent) : UnitUIBase(parent)
|
||||
{
|
||||
}
|
||||
void push_back(const int32_t co2, const int32_t tvoc);
|
||||
|
||||
void construct() override;
|
||||
|
||||
void update() override;
|
||||
void push(LovyanGFX* dst, const int32_t x, const int32_t y) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<m5::ui::Plotter> _co2Plotter{};
|
||||
std::unique_ptr<m5::ui::BarMeterV> _co2Bar{};
|
||||
std::unique_ptr<m5::ui::Plotter> _tvocPlotter{};
|
||||
std::unique_ptr<m5::ui::ColorBarMeterV> _tvocBar{};
|
||||
struct Data {
|
||||
int32_t co2, tvoc;
|
||||
};
|
||||
std::vector<Data> _intermediateBuffer{};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitVmeter.cpp
|
||||
@brief UI for UnitVmeter
|
||||
*/
|
||||
#include "ui_UnitVmeter.hpp"
|
||||
#include <M5Unified.h>
|
||||
#include <M5Utility.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
constexpr int32_t GAP{2};
|
||||
|
||||
constexpr m5gfx::rgb565_t voltage_gauge_color{129, 134, 80};
|
||||
|
||||
constexpr char vustr[] = "mV";
|
||||
} // namespace
|
||||
|
||||
void UnitVmeterSmallUI::construct()
|
||||
{
|
||||
auto& lcd = M5.Display;
|
||||
_wid = lcd.width() >> 1;
|
||||
_hgt = lcd.height() >> 1;
|
||||
|
||||
auto pw = _wid - GAP * 2;
|
||||
// auto ph = (_hgt >> 2) * 3 - GAP * 2;
|
||||
auto ph = (_hgt >> 1) - GAP * 2 - 4;
|
||||
|
||||
_voltagePlotter.reset(new m5::ui::Plotter(_parent, pw, pw, ph));
|
||||
_voltagePlotter->setUnitString(vustr);
|
||||
_voltagePlotter->setGaugeColor(voltage_gauge_color);
|
||||
|
||||
_intermediateBuffer.reserve(pw);
|
||||
_intermediateBuffer.clear();
|
||||
}
|
||||
|
||||
void UnitVmeterSmallUI::push_back(const float mv)
|
||||
{
|
||||
_intermediateBuffer.emplace_back(mv);
|
||||
}
|
||||
|
||||
void UnitVmeterSmallUI::update()
|
||||
{
|
||||
lock();
|
||||
for (auto&& e : _intermediateBuffer) {
|
||||
_voltagePlotter->push_back(e);
|
||||
}
|
||||
_intermediateBuffer.clear();
|
||||
unlock();
|
||||
_voltagePlotter->update();
|
||||
}
|
||||
void UnitVmeterSmallUI::push(LovyanGFX* dst, const int32_t x, const int32_t y)
|
||||
{
|
||||
auto left = x;
|
||||
auto right = x + _wid - 1;
|
||||
auto top = y;
|
||||
auto bottom = y + _hgt - 1;
|
||||
auto w = right - left + 1;
|
||||
auto h = bottom - top + 1;
|
||||
|
||||
auto f = dst->getFont();
|
||||
dst->setFont(&fonts::Font0);
|
||||
dst->setTextColor(TFT_WHITE);
|
||||
auto td = dst->getTextDatum();
|
||||
|
||||
// BG
|
||||
dst->fillRoundRect(x, y, _wid, _hgt, GAP, TFT_RED);
|
||||
dst->fillRoundRect(x + GAP, y + GAP, _wid - GAP * 2, _hgt - GAP * 2, GAP, TFT_BLACK);
|
||||
|
||||
_voltagePlotter->push(dst, x + GAP, y + GAP);
|
||||
|
||||
auto s = m5::utility::formatString("MIN:%5dmV", _voltagePlotter->minimum());
|
||||
dst->drawString(s.c_str(), x + GAP * 2, y + GAP * 2 + _voltagePlotter->height() + 16);
|
||||
s = m5::utility::formatString("MAX:%5dmV", _voltagePlotter->maximum());
|
||||
dst->drawString(s.c_str(), x + GAP * 2, y + GAP * 2 + _voltagePlotter->height() + 16 + 10 * 1);
|
||||
|
||||
dst->setFont(&fonts::Font2);
|
||||
dst->setTextColor(TFT_RED);
|
||||
dst->setTextDatum(textdatum_t::middle_center);
|
||||
dst->drawString("UnitVmeter", left + w / 2, top + h / 2);
|
||||
|
||||
dst->setTextDatum(td);
|
||||
dst->setFont(f);
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ui_UnitVmeter.hpp
|
||||
@brief UI for UnitVmeter
|
||||
*/
|
||||
#ifndef UI_UNIT_VMETER_HPP
|
||||
#define UI_UNIT_VMETER_HPP
|
||||
|
||||
#include "parts/ui_plotter.hpp"
|
||||
#include "ui_UnitBase.hpp"
|
||||
#include <memory>
|
||||
|
||||
class UnitVmeterSmallUI : public UnitUIBase {
|
||||
public:
|
||||
explicit UnitVmeterSmallUI(LovyanGFX* parent) : UnitUIBase(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void push_back(const float mv);
|
||||
|
||||
void construct() override;
|
||||
void update() override;
|
||||
void push(LovyanGFX* dst, const int32_t x, const int32_t y) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<m5::ui::Plotter> _voltagePlotter{};
|
||||
std::vector<float> _intermediateBuffer{};
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue