Compare commits
No commits in common. "d0b0de550b8c5ab687fe5f3de3f6879339942c5f" and "d91b9038176f559d9777c16d2a12875c8b378bf4" have entirely different histories.
d0b0de550b
...
d91b903817
@ -383,7 +383,7 @@ async fn main(spawner: Spawner) {
|
|||||||
.add_horizontal(IconButton::new(size32px::actions::AddCircle).smartstate(sm.next()))
|
.add_horizontal(IconButton::new(size32px::actions::AddCircle).smartstate(sm.next()))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
if !(appdata.timer_running() || appdata.timer_paused()) {
|
if !appdata.timer_running() {
|
||||||
appdata.add_secs(10);
|
appdata.add_secs(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -392,7 +392,7 @@ async fn main(spawner: Spawner) {
|
|||||||
.add(IconButton::new(size32px::actions::MinusCircle).smartstate(sm.next()))
|
.add(IconButton::new(size32px::actions::MinusCircle).smartstate(sm.next()))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
if !(appdata.timer_running() || appdata.timer_paused()) {
|
if !appdata.timer_running() {
|
||||||
appdata.sub_secs(10);
|
appdata.sub_secs(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ rustflags = [
|
|||||||
# Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
|
# Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
|
||||||
"--cfg",
|
"--cfg",
|
||||||
"espidf_time64",
|
"espidf_time64",
|
||||||
|
|
||||||
# Added the following 2 entries so lvgl will build without getting string.h file not found
|
# Added the following 2 entries so lvgl will build without getting string.h file not found
|
||||||
"--sysroot",
|
"--sysroot",
|
||||||
"/home/ed/.rustup/toolchains/esp/xtensa-esp32s3-elf/esp-13.2.0_20230928/xtensa-esp-elf/xtensa-esp-elf/include",
|
"/home/ed/.rustup/toolchains/esp/xtensa-esp32s3-elf/esp-13.2.0_20230928/xtensa-esp-elf/xtensa-esp-elf/include",
|
||||||
@ -29,7 +30,7 @@ DEP_LV_CONFIG_PATH = { relative = true, value = "lvgl-configs" }
|
|||||||
CROSS_COMPILE = "xtensa-esp32-elf"
|
CROSS_COMPILE = "xtensa-esp32-elf"
|
||||||
|
|
||||||
# Directory for custom fonts (written in C) that Lvgl can use
|
# Directory for custom fonts (written in C) that Lvgl can use
|
||||||
LVGL_FONTS_DIR = { relative = true, value = "custom-fonts" }
|
LVGL_FONTS_DIR = {relative = true, value = "custom-fonts"}
|
||||||
|
|
||||||
PATH = "/home/yannik/.rustup/toolchains/esp/xtensa-esp-elf/esp-14.2.0_20240906/xtensa-esp-elf/bin:/home/yannik/.local/share/pnpm:/home/yannik/.local/share/zinit/polaris/bin:/home/yannik/.rustup/toolchains/esp/xtensa-esp-elf/esp-14.2.0_20240906/xtensa-esp-elf/bin:/home/yannik/.surrealdb:/home/yannik/.pyenv/shims:/home/yannik/.pyenv/bin:/home/yannik/.cargo/bin:/home/yannik/.local/bin:/home/yannik/bin:/usr/local/bin:/usr/lib64/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/var/lib/snapd/snap/bin:/home/yannik/development/flutter/bin"
|
PATH="/usr/lib64/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/var/lib/snapd/snap/bin:/home/yannik/.rustup/toolchains/esp/xtensa-esp32s2-elf/esp-2021r2-patch5-8_4_0/xtensa-esp32s2-elf/bin:/home/yannik/.rustup/toolchains/esp/xtensa-esp32s3-elf/esp-2021r2-patch5-8_4_0/xtensa-esp32s3-elf/bin:/home/yannik/.rustup/toolchains/esp/xtensa-esp32-elf/esp-2021r2-patch5-8_4_0/xtensa-esp32-elf/bin:/home/yannik/.rustup/toolchains/esp/riscv32-esp-elf/esp-2021r2-patch5-8_4_0/riscv32-esp-elf/bin:"
|
||||||
LIBCLANG_PATH = "/home/yannik/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-15.0.0-20221201/esp-clang/lib"
|
LIBCLANG_PATH="/home/yannik/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-15.0.0-20221201/esp-clang/lib"
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "rust-m5stack-lvgl-demo"
|
name = "rust-m5stack-lvgl-demo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["enelson1001 <ednelson5080@gmail>"]
|
authors = ["enelson1001 <ednelson5080@gmail>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
rust-version = "1.71"
|
rust-version = "1.71"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
debug = true # Symbols are nice and they don't increase the size on Flash
|
debug = true # Symbols are nice and they don't increase the size on Flash
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
@ -21,12 +21,6 @@ name = "starter"
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "timer"
|
name = "timer"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "light-control"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "microwave-ui"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["embassy", "esp-idf-svc/native", "std"]
|
default = ["embassy", "esp-idf-svc/native", "std"]
|
||||||
|
|
||||||
@ -42,13 +36,13 @@ embassy = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = { version = "0.4", default-features = false }
|
log = { version = "0.4", default-features = false }
|
||||||
esp-idf-svc = { version = "0.49.1", default-features = false }
|
esp-idf-svc = { version = "0.49.1", default-features = false }
|
||||||
|
|
||||||
esp-idf-hal = { version = "0.44.1" }
|
esp-idf-hal = { version = "0.44.1" }
|
||||||
esp-idf-sys = { version = "0.35.0" }
|
esp-idf-sys = { version = "0.35.0" }
|
||||||
|
|
||||||
cstr_core = "0.2.1"
|
cstr_core = "0.2.1"
|
||||||
embedded-graphics-core = "0.4.0"
|
embedded-graphics-core = "0.4.0"
|
||||||
|
|
||||||
lvgl = { version = "0.6.2", default-features = false, features = [
|
lvgl = { version = "0.6.2", default-features = false, features = [
|
||||||
@ -59,19 +53,19 @@ lvgl = { version = "0.6.2", default-features = false, features = [
|
|||||||
lvgl-sys = { version = "0.6.2" }
|
lvgl-sys = { version = "0.6.2" }
|
||||||
|
|
||||||
display-interface-spi = "0.5.0"
|
display-interface-spi = "0.5.0"
|
||||||
mipidsi = "0.8.0"
|
mipidsi = "0.8.0"
|
||||||
static_cell = "2.1.0"
|
static_cell = "2.1.0"
|
||||||
|
|
||||||
xpt2046 = { git = "https://github.com/Yandrik/xpt2046.git", version = "0.3.1" }
|
xpt2046 = { git = "https://github.com/Yandrik/xpt2046.git", version = "0.3.1" }
|
||||||
|
|
||||||
embedded-graphics-profiler-display = { version = "0.1.0", path = "../embedded-graphics-profiler-display", features = ["std"] }
|
embedded-graphics-profiler-display = { version = "0.1.0", path = "../embedded-graphics-profiler-display", features = ["std"] }
|
||||||
|
|
||||||
heapless = "0.8.0"
|
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
embuild = "0.32.0"
|
embuild = "0.32.0"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
lvgl = { git = "https://github.com/enelson1001/lv_binding_rust" }
|
lvgl = { git = "https://github.com/enelson1001/lv_binding_rust" }
|
||||||
lvgl-sys = { git = "https://github.com/enelson1001/lv_binding_rust" }
|
lvgl-sys = { git = "https://github.com/enelson1001/lv_binding_rust" }
|
||||||
|
@ -328,17 +328,17 @@
|
|||||||
*https://fonts.google.com/specimen/Montserrat*/
|
*https://fonts.google.com/specimen/Montserrat*/
|
||||||
#define LV_FONT_MONTSERRAT_8 0
|
#define LV_FONT_MONTSERRAT_8 0
|
||||||
#define LV_FONT_MONTSERRAT_10 0
|
#define LV_FONT_MONTSERRAT_10 0
|
||||||
#define LV_FONT_MONTSERRAT_12 1
|
#define LV_FONT_MONTSERRAT_12 0
|
||||||
#define LV_FONT_MONTSERRAT_14 1
|
#define LV_FONT_MONTSERRAT_14 1
|
||||||
#define LV_FONT_MONTSERRAT_16 1
|
#define LV_FONT_MONTSERRAT_16 0
|
||||||
#define LV_FONT_MONTSERRAT_18 0
|
#define LV_FONT_MONTSERRAT_18 0
|
||||||
#define LV_FONT_MONTSERRAT_20 0
|
#define LV_FONT_MONTSERRAT_20 0
|
||||||
#define LV_FONT_MONTSERRAT_22 0
|
#define LV_FONT_MONTSERRAT_22 0
|
||||||
#define LV_FONT_MONTSERRAT_24 1
|
#define LV_FONT_MONTSERRAT_24 0
|
||||||
#define LV_FONT_MONTSERRAT_26 0
|
#define LV_FONT_MONTSERRAT_26 0
|
||||||
#define LV_FONT_MONTSERRAT_28 0
|
#define LV_FONT_MONTSERRAT_28 0
|
||||||
#define LV_FONT_MONTSERRAT_30 0
|
#define LV_FONT_MONTSERRAT_30 0
|
||||||
#define LV_FONT_MONTSERRAT_32 1
|
#define LV_FONT_MONTSERRAT_32 0
|
||||||
#define LV_FONT_MONTSERRAT_34 0
|
#define LV_FONT_MONTSERRAT_34 0
|
||||||
#define LV_FONT_MONTSERRAT_36 0
|
#define LV_FONT_MONTSERRAT_36 0
|
||||||
#define LV_FONT_MONTSERRAT_38 0
|
#define LV_FONT_MONTSERRAT_38 0
|
||||||
|
@ -1,543 +0,0 @@
|
|||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
cmp::min,
|
|
||||||
str::FromStr,
|
|
||||||
sync::mpsc::channel,
|
|
||||||
thread,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use cstr_core::CString;
|
|
||||||
use display_interface_spi::SPIInterface;
|
|
||||||
use embedded_graphics_core::{draw_target::DrawTarget, prelude::Point};
|
|
||||||
use embedded_graphics_profiler_display::ProfilerDisplay;
|
|
||||||
use esp_idf_hal::spi::SpiSingleDeviceDriver;
|
|
||||||
use esp_idf_hal::{
|
|
||||||
delay::{self, Delay},
|
|
||||||
gpio::*,
|
|
||||||
peripherals::Peripherals,
|
|
||||||
spi::{config::DriverConfig, Dma, SpiConfig, SpiDeviceDriver},
|
|
||||||
units::FromValueType, // for converting 26MHz to value
|
|
||||||
};
|
|
||||||
use lvgl::{
|
|
||||||
font::Font,
|
|
||||||
input_device::{
|
|
||||||
pointer::{Pointer, PointerInputData},
|
|
||||||
InputDriver,
|
|
||||||
},
|
|
||||||
style::{FlexAlign, FlexFlow, Layout, Style},
|
|
||||||
widgets::{Btn, Label, Slider, Switch},
|
|
||||||
Align,
|
|
||||||
AnimationState,
|
|
||||||
Color,
|
|
||||||
Display,
|
|
||||||
DrawBuffer,
|
|
||||||
Event,
|
|
||||||
LvError,
|
|
||||||
NativeObject,
|
|
||||||
Obj,
|
|
||||||
Part,
|
|
||||||
Screen,
|
|
||||||
TextAlign,
|
|
||||||
Widget,
|
|
||||||
};
|
|
||||||
use mipidsi::{
|
|
||||||
models::ILI9486Rgb565,
|
|
||||||
options::{ColorInversion, ColorOrder, Orientation, Rotation},
|
|
||||||
Builder,
|
|
||||||
};
|
|
||||||
use xpt2046::Xpt2046;
|
|
||||||
|
|
||||||
fn lerp_fixed(start: u8, end: u8, t: u8, max_t: u8) -> u8 {
|
|
||||||
let (start, end, t, max_t) = (start as u16, end as u16, t as u16, max_t as u16);
|
|
||||||
let t = t.min(max_t);
|
|
||||||
let result = start + ((end - start.min(end)) * t + (max_t / 2)) / max_t;
|
|
||||||
result as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct Lamp {
|
|
||||||
pub name: heapless::String<64>,
|
|
||||||
pub on: bool,
|
|
||||||
pub brightness: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Lamp {
|
|
||||||
pub fn new(name: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
name: heapless::String::from(heapless::String::from_str(name).unwrap()),
|
|
||||||
on: false,
|
|
||||||
brightness: 255,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Page<'a> {
|
|
||||||
Home,
|
|
||||||
LampCtrl(&'a Lamp),
|
|
||||||
}
|
|
||||||
struct AppData {
|
|
||||||
lamps: heapless::Vec<Lamp, 8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppData {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
lamps: heapless::Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_lamp(&mut self, name: &str) {
|
|
||||||
self.lamps.push(Lamp::new(name)).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), LvError> {
|
|
||||||
const HOR_RES: u32 = 320;
|
|
||||||
const VER_RES: u32 = 240;
|
|
||||||
const LINES: u32 = 20;
|
|
||||||
|
|
||||||
// It is necessary to call this function once. Otherwise some patches to the
|
|
||||||
// runtime implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
|
|
||||||
esp_idf_svc::sys::link_patches();
|
|
||||||
|
|
||||||
// Initialize lvgl
|
|
||||||
lvgl::init();
|
|
||||||
|
|
||||||
// Bind the log crate to the ESP Logging facilities
|
|
||||||
esp_idf_svc::log::EspLogger::initialize_default();
|
|
||||||
|
|
||||||
log::info!("================= Starting App =======================");
|
|
||||||
|
|
||||||
let peripherals = Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
// #[allow(unused)]
|
|
||||||
let pins = peripherals.pins;
|
|
||||||
|
|
||||||
let sclk = pins.gpio14;
|
|
||||||
let miso = pins.gpio12;
|
|
||||||
let mosi = pins.gpio13;
|
|
||||||
let cs = pins.gpio15;
|
|
||||||
let dc = pins.gpio2;
|
|
||||||
|
|
||||||
let spi = SpiDeviceDriver::new_single(
|
|
||||||
peripherals.spi2,
|
|
||||||
sclk, // sclk
|
|
||||||
mosi, // sdo
|
|
||||||
Some(miso), // sdi
|
|
||||||
Some(cs), // cs
|
|
||||||
&DriverConfig::new().dma(Dma::Channel1(4096)),
|
|
||||||
&SpiConfig::new().write_only(true).baudrate(10.MHz().into()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// let rst = PinDriver::output(pins.gpio33).unwrap();
|
|
||||||
let dc = PinDriver::output(dc).unwrap();
|
|
||||||
let di = SPIInterface::new(spi, dc);
|
|
||||||
|
|
||||||
// Turn backlight on
|
|
||||||
let mut bklt = PinDriver::output(pins.gpio21).unwrap();
|
|
||||||
bklt.set_high().unwrap();
|
|
||||||
|
|
||||||
// Configuration for M5Stack Core Development Kit V1.0
|
|
||||||
// Puts display in landscape mode with the three buttons at the bottom of screen
|
|
||||||
// let mut m5stack_display = Builder::ili9342c_rgb565(di)
|
|
||||||
// .with_display_size(320, 240)
|
|
||||||
// .with_color_order(ColorOrder::Bgr)
|
|
||||||
// .with_orientation(Orientation::Portrait(false))
|
|
||||||
// .with_invert_colors(mipidsi::ColorInversion::Inverted)
|
|
||||||
// .init(&mut delay::Ets, Some(rst))
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
// 0.6.0
|
|
||||||
// let mut raw_display = Builder::ili9342c_rgb565(di)
|
|
||||||
// .with_orientation(Orientation::Portrait(false))
|
|
||||||
// .with_color_order(ColorOrder::Bgr)
|
|
||||||
// .with_invert_colors(true)
|
|
||||||
// .init(&mut Delay::new_default(), None::<PinDriver<AnyOutputPin, Output>>)
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
let raw_display = Builder::new(ILI9486Rgb565, di)
|
|
||||||
.orientation(Orientation {
|
|
||||||
rotation: Rotation::Deg90,
|
|
||||||
mirrored: true,
|
|
||||||
})
|
|
||||||
.color_order(ColorOrder::Bgr)
|
|
||||||
.invert_colors(ColorInversion::Inverted)
|
|
||||||
.init(&mut Delay::new_default())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut raw_display = ProfilerDisplay::new(raw_display);
|
|
||||||
|
|
||||||
// Stack size value - 20,000 for 10 lines, 40,000 for 20 lines
|
|
||||||
// let (touch_send, touch_recv) = channel();
|
|
||||||
let touch_irq = pins.gpio36;
|
|
||||||
let touch_mosi = pins.gpio32;
|
|
||||||
let touch_miso = pins.gpio39;
|
|
||||||
let touch_clk = pins.gpio25;
|
|
||||||
let touch_cs = pins.gpio33;
|
|
||||||
|
|
||||||
let mut touch_driver = Xpt2046::new(
|
|
||||||
SpiDeviceDriver::new_single(
|
|
||||||
peripherals.spi3,
|
|
||||||
touch_clk,
|
|
||||||
touch_mosi,
|
|
||||||
Some(touch_miso),
|
|
||||||
Some(touch_cs),
|
|
||||||
&DriverConfig::new(),
|
|
||||||
&SpiConfig::new().write_only(true).baudrate(2.MHz().into()),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
PinDriver::input(touch_irq).unwrap(),
|
|
||||||
xpt2046::Orientation::LandscapeFlipped,
|
|
||||||
);
|
|
||||||
touch_driver.set_num_samples(1);
|
|
||||||
touch_driver.init(&mut Delay::new_default()).unwrap();
|
|
||||||
|
|
||||||
let touch_driver = RefCell::new(touch_driver);
|
|
||||||
|
|
||||||
let _lvgl_thread = thread::Builder::new()
|
|
||||||
.stack_size(40_000)
|
|
||||||
.spawn(move || {
|
|
||||||
println!("thread started");
|
|
||||||
|
|
||||||
let mut appdata = AppData::new();
|
|
||||||
|
|
||||||
let buffer = DrawBuffer::<{ (HOR_RES * LINES) as usize }>::default();
|
|
||||||
let display = Display::register(buffer, HOR_RES, VER_RES, |refresh| {
|
|
||||||
raw_display.draw_iter(refresh.as_pixels()).unwrap();
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Register a new input device that's capable of reading the current state of
|
|
||||||
// the input
|
|
||||||
// let mut last_touch = RefCell::new(None);
|
|
||||||
let _touch_screen = Pointer::register(
|
|
||||||
|| {
|
|
||||||
let mut td_ref = touch_driver.borrow_mut();
|
|
||||||
td_ref.run().expect("Running Touch driver failed");
|
|
||||||
if td_ref.is_touched() {
|
|
||||||
let point = td_ref.get_touch_point();
|
|
||||||
// println!("touched {:?}", point);
|
|
||||||
PointerInputData::Touch(Point::new(point.x + 20, 240 - point.y)).pressed().once()
|
|
||||||
} else {
|
|
||||||
// println!("untouched");
|
|
||||||
PointerInputData::Touch(Point::new(0, 0)).released().once()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
&display,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create screen and widgets
|
|
||||||
let mut screen = display.get_scr_act().unwrap();
|
|
||||||
let mut screen_style = Style::default();
|
|
||||||
screen_style.set_bg_color(Color::from_rgb((0, 0, 0)));
|
|
||||||
screen_style.set_radius(0);
|
|
||||||
screen_style.set_layout(Layout::flex());
|
|
||||||
screen_style.set_flex_flow(FlexFlow::ROW_WRAP);
|
|
||||||
screen_style.set_flex_main_place(FlexAlign::CENTER);
|
|
||||||
screen_style.set_flex_cross_place(FlexAlign::CENTER);
|
|
||||||
screen.add_style(Part::Main, &mut screen_style);
|
|
||||||
|
|
||||||
|
|
||||||
appdata.add_lamp("Front Door");
|
|
||||||
appdata.add_lamp("Living Room");
|
|
||||||
appdata.add_lamp("Bedroom");
|
|
||||||
appdata.add_lamp("Bathroom");
|
|
||||||
appdata.add_lamp("Porch");
|
|
||||||
|
|
||||||
let mut light_page = Screen::blank().unwrap();
|
|
||||||
light_page.set_size(320, 240);
|
|
||||||
let mut light_page_style = Style::default();
|
|
||||||
light_page_style.set_bg_color(Color::from_rgb((0, 0, 0)));
|
|
||||||
light_page_style.set_radius(0);
|
|
||||||
light_page.add_style(Part::Main, &mut light_page_style);
|
|
||||||
|
|
||||||
let mut light_page_title_label = Label::create(&mut light_page).unwrap();
|
|
||||||
let mut light_page_title_style = Style::default();
|
|
||||||
light_page_title_style.set_text_color(Color::from_rgb((255, 255, 255)));
|
|
||||||
unsafe { light_page_title_style.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_24)) };
|
|
||||||
light_page_title_label.add_style(Part::Main, &mut light_page_title_style);
|
|
||||||
light_page_title_label.set_align(Align::TopMid, 0, 6);
|
|
||||||
|
|
||||||
|
|
||||||
let mut brightness_slider = Slider::create(&mut light_page).unwrap();
|
|
||||||
brightness_slider.set_size(250, 10);
|
|
||||||
// brightness_slider.range(0..255);
|
|
||||||
brightness_slider.set_value(0, AnimationState::OFF);
|
|
||||||
brightness_slider.set_align(Align::Center, 0, -20);
|
|
||||||
|
|
||||||
let mut brightness_slider_label = Label::create(&mut light_page).unwrap();
|
|
||||||
// no text for now
|
|
||||||
let mut white_text_style = Style::default();
|
|
||||||
white_text_style.set_text_color(Color::from_rgb((255, 255, 255)));
|
|
||||||
unsafe { white_text_style.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_14)) };
|
|
||||||
brightness_slider_label.add_style(Part::Main, &mut white_text_style);
|
|
||||||
brightness_slider_label.set_text(CString::new("Brightness").unwrap().as_c_str());
|
|
||||||
brightness_slider_label.set_align(Align::Center, 0, -50);
|
|
||||||
|
|
||||||
|
|
||||||
// Add switch
|
|
||||||
let mut light_switch = Switch::create(&mut light_page).unwrap();
|
|
||||||
light_switch.set_align(Align::Center, 0, 40);
|
|
||||||
|
|
||||||
let mut light_switch_label = Label::create(&mut light_page).unwrap();
|
|
||||||
// no text for now
|
|
||||||
let mut lslabel_style = Style::default();
|
|
||||||
lslabel_style.set_text_color(Color::from_rgb((255, 255, 255)));
|
|
||||||
unsafe { lslabel_style.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_14)) };
|
|
||||||
light_switch_label.add_style(Part::Main, &mut lslabel_style);
|
|
||||||
light_switch_label.set_text(CString::new("On/Off").unwrap().as_c_str());
|
|
||||||
light_switch_label.set_align(Align::Center, 0, 10);
|
|
||||||
|
|
||||||
// Add back button
|
|
||||||
let mut back_btn = Btn::create(&mut light_page).unwrap();
|
|
||||||
back_btn.set_size(40, 40);
|
|
||||||
back_btn.set_align(Align::TopLeft, 10, 10);
|
|
||||||
back_btn.on_event(|_btn, event| {
|
|
||||||
if let Event::Pressed = event {
|
|
||||||
display.set_scr_act(&mut screen);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut back_label = Label::create(&mut back_btn).unwrap();
|
|
||||||
back_label.set_text(CString::new(b"\xef\x81\x93").unwrap().as_c_str()); // Font Awesome left arrow
|
|
||||||
let mut back_style = Style::default();
|
|
||||||
unsafe { back_style.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_24)) };
|
|
||||||
back_label.add_style(Part::Main, &mut back_style);
|
|
||||||
|
|
||||||
let mut light_ctrls = heapless::Vec::<(Btn, Label, Label), 8>::new();
|
|
||||||
|
|
||||||
let mut page = Page::Home;
|
|
||||||
|
|
||||||
for lamp in appdata.lamps.iter_mut() {
|
|
||||||
let mut cont = Btn::new().unwrap();
|
|
||||||
cont.set_size(100, 110);
|
|
||||||
// cont.set_align(Align::TopLeft, 0, 0);
|
|
||||||
|
|
||||||
cont.on_event(|_btn, event| {
|
|
||||||
if let Event::Pressed = event {
|
|
||||||
println!("lamp {:?}", lamp.name);
|
|
||||||
// page = Page::LampCtrl(lamp);
|
|
||||||
|
|
||||||
brightness_slider.set_value(lerp_fixed(0, 100, lamp.brightness, 255).into(), AnimationState::OFF);
|
|
||||||
unsafe {
|
|
||||||
if lamp.on {
|
|
||||||
lvgl_sys::lv_obj_add_state(light_switch.raw().as_ptr(), lvgl_sys::LV_STATE_CHECKED as u16);
|
|
||||||
} else {
|
|
||||||
lvgl_sys::lv_obj_clear_state(light_switch.raw().as_ptr(), lvgl_sys::LV_STATE_CHECKED as u16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
light_switch.on_event(|_ls, event| {
|
|
||||||
if let Event::ValueChanged | Event::Released = event {
|
|
||||||
let on = unsafe { lvgl_sys::lv_obj_has_state(_ls.raw().as_ptr(), lvgl_sys::LV_STATE_CHECKED as u16) };
|
|
||||||
lamp.on = on;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
brightness_slider.on_event(|_sldr, event| {
|
|
||||||
// println!("event: {:?}", event);
|
|
||||||
if let Event::ValueChanged | Event::Released = event {
|
|
||||||
let brightness = _sldr.get_value();
|
|
||||||
println!("brightness: {}", brightness);
|
|
||||||
lamp.brightness = lerp_fixed(0, 255, brightness as u8, 100);
|
|
||||||
println!("brightness: {}", lamp.brightness);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
light_page_title_label.set_text(CString::new(lamp.name.as_str()).unwrap().as_c_str());
|
|
||||||
|
|
||||||
display.set_scr_act(&mut light_page);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut style_name = Style::default();
|
|
||||||
style_name.set_text_align(TextAlign::Center);
|
|
||||||
style_name.set_text_color(Color::from_rgb((255, 255, 255)));
|
|
||||||
unsafe { style_name.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_14)) };
|
|
||||||
|
|
||||||
let mut style_icon = Style::default();
|
|
||||||
style_icon.set_text_color(Color::from_rgb((255, 255, 255)));
|
|
||||||
unsafe { style_icon.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_32)) };
|
|
||||||
style_icon.set_text_align(TextAlign::Center);
|
|
||||||
|
|
||||||
|
|
||||||
let mut icon = Label::create(&mut cont).unwrap();
|
|
||||||
icon.set_text(CString::new(b"\xef\x84\xa4").unwrap().as_c_str());
|
|
||||||
icon.add_style(Part::Main, Box::leak(Box::new(style_icon)));
|
|
||||||
icon.set_align(Align::Center, 0, 0);
|
|
||||||
|
|
||||||
let mut name = Label::create(&mut cont).unwrap();
|
|
||||||
name.set_text(CString::new(lamp.name.as_str()).unwrap().as_c_str());
|
|
||||||
// style_name.set_text_color(Color::from_rgb((255, 255, 255))); // white
|
|
||||||
name.add_style(Part::Main, Box::leak(Box::new(style_name)));
|
|
||||||
name.set_align(Align::BottomMid, 0, -4);
|
|
||||||
|
|
||||||
light_ctrls.push((cont, icon, name))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// let mut time = Label::new().unwrap();
|
|
||||||
// time.set_text(CString::new("00:10:000").unwrap().as_c_str());
|
|
||||||
// let mut style_time = Style::default();
|
|
||||||
// style_time.set_text_color(Color::from_rgb((255, 255, 255))); // white
|
|
||||||
// style_time.set_text_align(TextAlign::Center);
|
|
||||||
// unsafe { style_time.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_24)) };
|
|
||||||
// time.add_style(Part::Main, &mut style_time);
|
|
||||||
// time.set_align(Align::Center, 0, 0);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
// let mut cont = Obj::new().unwrap();
|
|
||||||
// cont.set_size(320, 50);
|
|
||||||
// let mut style = Style::default();
|
|
||||||
// style.set_flex_flow(FlexFlow::ROW);
|
|
||||||
// style.set_flex_main_place(FlexAlign::SPACE_EVENLY);
|
|
||||||
// style.set_flex_cross_place(FlexAlign::CENTER);
|
|
||||||
// // style.set_bg_opa(0);
|
|
||||||
// style.set_border_width(0);
|
|
||||||
// cont.add_style(Part::Any, &mut style);
|
|
||||||
// cont.set_align(Align::Center, 0, 40);
|
|
||||||
|
|
||||||
let mut button_add = Btn::create(&mut screen).unwrap();
|
|
||||||
button_add.set_align(Align::Center, -50, 32);
|
|
||||||
// button_add.set_pos(130, 150);
|
|
||||||
button_add.set_size(30, 30);
|
|
||||||
// button_add.set_align(Align::Center, 0, 0);
|
|
||||||
let mut btn_lbl1 = Label::create(&mut button_add).unwrap();
|
|
||||||
btn_lbl1.set_text(CString::new(b"+").unwrap().as_c_str());
|
|
||||||
|
|
||||||
button_add.on_event(|_btn, event| {
|
|
||||||
if let Event::Pressed = event {
|
|
||||||
println!("pressed");
|
|
||||||
if appdata.timer_stopped() {
|
|
||||||
appdata.add_secs(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// println!("Button received event: {:?}", event);
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut button_sub = Btn::create(&mut screen).unwrap();
|
|
||||||
// button_sub.set_pos(170, 150);
|
|
||||||
button_sub.set_size(30, 30);
|
|
||||||
button_sub.set_align(Align::Center, 50, 35);
|
|
||||||
// button_sub.set_align(Align::Center, 0, 0);
|
|
||||||
let mut btn_lbl2 = Label::create(&mut button_sub).unwrap();
|
|
||||||
btn_lbl2.set_text(CString::new(b"-").unwrap().as_c_str());
|
|
||||||
|
|
||||||
button_sub.on_event(|_btn, event| {
|
|
||||||
if let Event::Pressed = event {
|
|
||||||
if appdata.timer_stopped() {
|
|
||||||
appdata.sub_secs(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut center_label = Label::new().unwrap();
|
|
||||||
center_label.set_text(CString::new(b"+/- 10s").unwrap().as_c_str());
|
|
||||||
center_label.set_align(Align::Center, 0, 35);
|
|
||||||
let mut cl_style = Style::default();
|
|
||||||
cl_style.set_text_color(Color::from_rgb((255, 255, 255)));
|
|
||||||
center_label.add_style(Part::Main, &mut cl_style);
|
|
||||||
|
|
||||||
let mut button_reset = Btn::create(&mut screen).unwrap();
|
|
||||||
//button_reset.set_pos(123, 190);
|
|
||||||
button_reset.set_align(Align::Center, -22, 70);
|
|
||||||
button_reset.set_size(35, 35);
|
|
||||||
let mut btn_lbl3 = Label::create(&mut button_reset).unwrap();
|
|
||||||
btn_lbl3.set_text(CString::new(b"\xEF\x80\xA1").unwrap().as_c_str());
|
|
||||||
|
|
||||||
|
|
||||||
const PLAY: &'static [u8; 3] = b"\xEF\x81\x8B";
|
|
||||||
const PAUSE: &'static [u8; 3] = b"\xEF\x81\x8C";
|
|
||||||
const STOP: &'static [u8; 3] = b"\xEF\x81\x8D";
|
|
||||||
|
|
||||||
let mut button_start_stop = Btn::create(&mut screen).unwrap();
|
|
||||||
// button_start_stop.set_pos(173, 190);
|
|
||||||
button_start_stop.set_align(Align::Center, 22, 70);
|
|
||||||
button_start_stop.set_size(35, 35);
|
|
||||||
let mut btn_lbl4 = Label::create(&mut button_start_stop).unwrap();
|
|
||||||
btn_lbl4.set_text(CString::new(b"\xEF\x81\x8B").unwrap().as_c_str());
|
|
||||||
|
|
||||||
button_reset.on_event(|_btn, event| {
|
|
||||||
if let Event::Pressed = event {
|
|
||||||
appdata.reset_timer();
|
|
||||||
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
button_start_stop.on_event(|_btn, event| {
|
|
||||||
if let Event::Pressed = event {
|
|
||||||
if appdata.timer_finished() {
|
|
||||||
// println!("Resetting finished timer");
|
|
||||||
appdata.reset_timer();
|
|
||||||
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
|
|
||||||
} else if appdata.timer_running() {
|
|
||||||
// println!("Pausing timer");
|
|
||||||
appdata.pause_timer();
|
|
||||||
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
|
|
||||||
} else {
|
|
||||||
// println!("Starting timer");
|
|
||||||
appdata.start_timer();
|
|
||||||
if appdata.timer_finished() {
|
|
||||||
// println!("Timer already finished");
|
|
||||||
btn_lbl4.set_text(CString::new(STOP).unwrap().as_c_str());
|
|
||||||
} else {
|
|
||||||
// println!("Timer running");
|
|
||||||
btn_lbl4.set_text(CString::new(PAUSE).unwrap().as_c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut was_finished = false;
|
|
||||||
let mut last_rem_time: Duration = appdata.remaining() + Duration::from_millis(10);
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
let mut last_time = Instant::now();
|
|
||||||
loop {
|
|
||||||
let start_time = Instant::now();
|
|
||||||
|
|
||||||
let start_draw_time = Instant::now();
|
|
||||||
lvgl::task_handler();
|
|
||||||
|
|
||||||
// Simulate clock - so sleep for one second so time text is incremented in
|
|
||||||
// seconds
|
|
||||||
// delay::FreeRtos::delay_ms(1);
|
|
||||||
|
|
||||||
let now_time = Instant::now();
|
|
||||||
lvgl::tick_inc(now_time.duration_since(last_time));
|
|
||||||
last_time = now_time;
|
|
||||||
|
|
||||||
|
|
||||||
let end_time = Instant::now();
|
|
||||||
let draw_time = raw_display.get_time();
|
|
||||||
let prep_time = start_draw_time - start_time;
|
|
||||||
let proc_time = end_time - start_draw_time;
|
|
||||||
let proc_time = proc_time - min(draw_time, proc_time);
|
|
||||||
|
|
||||||
if draw_time.as_micros() > 0 {
|
|
||||||
println!(
|
|
||||||
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
|
|
||||||
draw_time.as_millis(),
|
|
||||||
draw_time.as_micros() % 100,
|
|
||||||
prep_time.as_millis(),
|
|
||||||
prep_time.as_micros() % 100,
|
|
||||||
proc_time.as_millis(),
|
|
||||||
proc_time.as_micros() % 100,
|
|
||||||
(draw_time + prep_time + proc_time).as_millis(),
|
|
||||||
(draw_time + prep_time + proc_time).as_micros() % 100, );
|
|
||||||
}
|
|
||||||
raw_display.reset_time();
|
|
||||||
|
|
||||||
delay::FreeRtos::delay_ms(2);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// Don't exit application
|
|
||||||
delay::FreeRtos::delay_ms(1_000_000);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,522 +0,0 @@
|
|||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
cmp::min,
|
|
||||||
str::FromStr,
|
|
||||||
sync::mpsc::channel,
|
|
||||||
thread,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use cstr_core::CString;
|
|
||||||
use display_interface_spi::SPIInterface;
|
|
||||||
use embedded_graphics_core::{draw_target::DrawTarget, prelude::Point};
|
|
||||||
use embedded_graphics_profiler_display::ProfilerDisplay;
|
|
||||||
use esp_idf_hal::spi::SpiSingleDeviceDriver;
|
|
||||||
use esp_idf_hal::{
|
|
||||||
delay::{self, Delay},
|
|
||||||
gpio::*,
|
|
||||||
peripherals::Peripherals,
|
|
||||||
spi::{config::DriverConfig, Dma, SpiConfig, SpiDeviceDriver},
|
|
||||||
units::FromValueType, // for converting 26MHz to value
|
|
||||||
};
|
|
||||||
use lvgl::{
|
|
||||||
font::Font,
|
|
||||||
input_device::{
|
|
||||||
pointer::{Pointer, PointerInputData},
|
|
||||||
InputDriver,
|
|
||||||
},
|
|
||||||
style::{FlexAlign, FlexFlow, Layout, Style},
|
|
||||||
widgets::{Arc, Btn, Label, Slider, Switch},
|
|
||||||
Align,
|
|
||||||
AnimationState,
|
|
||||||
Color,
|
|
||||||
Display,
|
|
||||||
DrawBuffer,
|
|
||||||
Event,
|
|
||||||
LvError,
|
|
||||||
NativeObject,
|
|
||||||
Obj,
|
|
||||||
Part,
|
|
||||||
Screen,
|
|
||||||
TextAlign,
|
|
||||||
Widget,
|
|
||||||
};
|
|
||||||
use mipidsi::{
|
|
||||||
models::ILI9486Rgb565,
|
|
||||||
options::{ColorInversion, ColorOrder, Orientation, Rotation},
|
|
||||||
Builder,
|
|
||||||
};
|
|
||||||
use xpt2046::Xpt2046;
|
|
||||||
|
|
||||||
fn lerp_fixed(start: u8, end: u8, t: u8, max_t: u8) -> u8 {
|
|
||||||
let (start, end, t, max_t) = (start as u16, end as u16, t as u16, max_t as u16);
|
|
||||||
let t = t.min(max_t);
|
|
||||||
let result = start + ((end - start.min(end)) * t + (max_t / 2)) / max_t;
|
|
||||||
result as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct Lamp {
|
|
||||||
pub name: heapless::String<64>,
|
|
||||||
pub on: bool,
|
|
||||||
pub brightness: u8,
|
|
||||||
}
|
|
||||||
struct AppData {
|
|
||||||
timer_start: Instant,
|
|
||||||
timer_set_duration: Duration,
|
|
||||||
timer_remaining_duration: Duration,
|
|
||||||
timer_running: bool,
|
|
||||||
timer_paused: bool,
|
|
||||||
wattage_level: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppData {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
timer_start: Instant::now(),
|
|
||||||
timer_set_duration: Duration::from_secs(10),
|
|
||||||
timer_remaining_duration: Duration::from_secs(10),
|
|
||||||
timer_running: false,
|
|
||||||
timer_paused: false,
|
|
||||||
wattage_level: 5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_timer_duration(&mut self, duration: Duration) {
|
|
||||||
self.timer_set_duration = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_secs(&mut self, secs: u64) {
|
|
||||||
self.set_timer_duration(
|
|
||||||
self.timer_set_duration
|
|
||||||
.checked_add(Duration::from_secs(secs))
|
|
||||||
.unwrap_or(Duration::from_secs(5999))
|
|
||||||
.min(Duration::from_secs(5999)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sub_secs(&mut self, secs: u64) {
|
|
||||||
self.set_timer_duration(
|
|
||||||
self.timer_set_duration
|
|
||||||
.checked_sub(Duration::from_secs(secs))
|
|
||||||
.unwrap_or(Duration::from_secs(10))
|
|
||||||
.max(Duration::from_secs(10)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_timer(&mut self) {
|
|
||||||
if !self.timer_paused {
|
|
||||||
self.timer_remaining_duration = self.timer_set_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.timer_start = Instant::now();
|
|
||||||
self.timer_running = true;
|
|
||||||
self.timer_paused = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pause_timer(&mut self) {
|
|
||||||
self.timer_paused = true;
|
|
||||||
self.timer_remaining_duration = self
|
|
||||||
.timer_remaining_duration
|
|
||||||
.checked_sub(self.timer_start.elapsed())
|
|
||||||
.unwrap_or(Duration::from_secs(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_timer(&mut self) {
|
|
||||||
self.timer_start = Instant::now();
|
|
||||||
self.timer_paused = false;
|
|
||||||
self.timer_running = false;
|
|
||||||
self.timer_remaining_duration = self.timer_set_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remaining(&self) -> Duration {
|
|
||||||
if self.timer_stopped() {
|
|
||||||
self.timer_set_duration
|
|
||||||
} else if self.timer_paused() {
|
|
||||||
self.timer_remaining_duration
|
|
||||||
} else {
|
|
||||||
self.timer_remaining_duration
|
|
||||||
.checked_sub(self.timer_start.elapsed())
|
|
||||||
.unwrap_or(Duration::from_secs(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_stopped(&self) -> bool {
|
|
||||||
!self.timer_running
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_paused(&self) -> bool {
|
|
||||||
self.timer_running && self.timer_paused
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_running(&self) -> bool {
|
|
||||||
self.timer_running && !self.timer_paused
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_finished(&self) -> bool {
|
|
||||||
self.timer_running && self.remaining() == Duration::from_secs(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_wattage_level(&mut self, level: u8) {
|
|
||||||
assert!(level < 6, "wattage level cannot be over 6");
|
|
||||||
self.wattage_level = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_wattage_level_str(&self) -> &'static str {
|
|
||||||
match self.wattage_level {
|
|
||||||
0 => "180W",
|
|
||||||
1 => "220W",
|
|
||||||
2 => "360W",
|
|
||||||
3 => "480W",
|
|
||||||
4 => "620W",
|
|
||||||
5 => "800W",
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), LvError> {
|
|
||||||
const HOR_RES: u32 = 320;
|
|
||||||
const VER_RES: u32 = 240;
|
|
||||||
const LINES: u32 = 20;
|
|
||||||
|
|
||||||
// It is necessary to call this function once. Otherwise some patches to the
|
|
||||||
// runtime implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
|
|
||||||
esp_idf_svc::sys::link_patches();
|
|
||||||
|
|
||||||
// Initialize lvgl
|
|
||||||
lvgl::init();
|
|
||||||
|
|
||||||
// Bind the log crate to the ESP Logging facilities
|
|
||||||
esp_idf_svc::log::EspLogger::initialize_default();
|
|
||||||
|
|
||||||
log::info!("================= Starting App =======================");
|
|
||||||
|
|
||||||
let peripherals = Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
// #[allow(unused)]
|
|
||||||
let pins = peripherals.pins;
|
|
||||||
|
|
||||||
let sclk = pins.gpio14;
|
|
||||||
let miso = pins.gpio12;
|
|
||||||
let mosi = pins.gpio13;
|
|
||||||
let cs = pins.gpio15;
|
|
||||||
let dc = pins.gpio2;
|
|
||||||
|
|
||||||
let spi = SpiDeviceDriver::new_single(
|
|
||||||
peripherals.spi2,
|
|
||||||
sclk, // sclk
|
|
||||||
mosi, // sdo
|
|
||||||
Some(miso), // sdi
|
|
||||||
Some(cs), // cs
|
|
||||||
&DriverConfig::new().dma(Dma::Channel1(4096)),
|
|
||||||
&SpiConfig::new().write_only(true).baudrate(10.MHz().into()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// let rst = PinDriver::output(pins.gpio33).unwrap();
|
|
||||||
let dc = PinDriver::output(dc).unwrap();
|
|
||||||
let di = SPIInterface::new(spi, dc);
|
|
||||||
|
|
||||||
// Turn backlight on
|
|
||||||
let mut bklt = PinDriver::output(pins.gpio21).unwrap();
|
|
||||||
bklt.set_high().unwrap();
|
|
||||||
|
|
||||||
// Configuration for M5Stack Core Development Kit V1.0
|
|
||||||
// Puts display in landscape mode with the three buttons at the bottom of screen
|
|
||||||
// let mut m5stack_display = Builder::ili9342c_rgb565(di)
|
|
||||||
// .with_display_size(320, 240)
|
|
||||||
// .with_color_order(ColorOrder::Bgr)
|
|
||||||
// .with_orientation(Orientation::Portrait(false))
|
|
||||||
// .with_invert_colors(mipidsi::ColorInversion::Inverted)
|
|
||||||
// .init(&mut delay::Ets, Some(rst))
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
// 0.6.0
|
|
||||||
// let mut raw_display = Builder::ili9342c_rgb565(di)
|
|
||||||
// .with_orientation(Orientation::Portrait(false))
|
|
||||||
// .with_color_order(ColorOrder::Bgr)
|
|
||||||
// .with_invert_colors(true)
|
|
||||||
// .init(&mut Delay::new_default(), None::<PinDriver<AnyOutputPin, Output>>)
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
let raw_display = Builder::new(ILI9486Rgb565, di)
|
|
||||||
.orientation(Orientation {
|
|
||||||
rotation: Rotation::Deg90,
|
|
||||||
mirrored: true,
|
|
||||||
})
|
|
||||||
.color_order(ColorOrder::Bgr)
|
|
||||||
.invert_colors(ColorInversion::Inverted)
|
|
||||||
.init(&mut Delay::new_default())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut raw_display = ProfilerDisplay::new(raw_display);
|
|
||||||
|
|
||||||
// Stack size value - 20,000 for 10 lines, 40,000 for 20 lines
|
|
||||||
// let (touch_send, touch_recv) = channel();
|
|
||||||
let touch_irq = pins.gpio36;
|
|
||||||
let touch_mosi = pins.gpio32;
|
|
||||||
let touch_miso = pins.gpio39;
|
|
||||||
let touch_clk = pins.gpio25;
|
|
||||||
let touch_cs = pins.gpio33;
|
|
||||||
|
|
||||||
let mut touch_driver = Xpt2046::new(
|
|
||||||
SpiDeviceDriver::new_single(
|
|
||||||
peripherals.spi3,
|
|
||||||
touch_clk,
|
|
||||||
touch_mosi,
|
|
||||||
Some(touch_miso),
|
|
||||||
Some(touch_cs),
|
|
||||||
&DriverConfig::new(),
|
|
||||||
&SpiConfig::new().write_only(true).baudrate(2.MHz().into()),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
PinDriver::input(touch_irq).unwrap(),
|
|
||||||
xpt2046::Orientation::LandscapeFlipped,
|
|
||||||
);
|
|
||||||
touch_driver.set_num_samples(1);
|
|
||||||
touch_driver.init(&mut Delay::new_default()).unwrap();
|
|
||||||
|
|
||||||
let touch_driver = RefCell::new(touch_driver);
|
|
||||||
|
|
||||||
let _lvgl_thread = thread::Builder::new()
|
|
||||||
.stack_size(40_000)
|
|
||||||
.spawn(move || {
|
|
||||||
println!("thread started");
|
|
||||||
|
|
||||||
let mut appdata = AppData::new();
|
|
||||||
|
|
||||||
let buffer = DrawBuffer::<{ (HOR_RES * LINES) as usize }>::default();
|
|
||||||
let display = Display::register(buffer, HOR_RES, VER_RES, |refresh| {
|
|
||||||
raw_display.draw_iter(refresh.as_pixels()).unwrap();
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Register a new input device that's capable of reading the current state of
|
|
||||||
// the input
|
|
||||||
// let mut last_touch = RefCell::new(None);
|
|
||||||
let _touch_screen = Pointer::register(
|
|
||||||
|| {
|
|
||||||
let mut td_ref = touch_driver.borrow_mut();
|
|
||||||
td_ref.run().expect("Running Touch driver failed");
|
|
||||||
if td_ref.is_touched() {
|
|
||||||
let point = td_ref.get_touch_point();
|
|
||||||
// println!("touched {:?}", point);
|
|
||||||
PointerInputData::Touch(Point::new(point.x + 20, 240 - point.y)).pressed().once()
|
|
||||||
} else {
|
|
||||||
// println!("untouched");
|
|
||||||
PointerInputData::Touch(Point::new(0, 0)).released().once()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
&display,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create screen and widgets
|
|
||||||
let mut screen = display.get_scr_act().unwrap();
|
|
||||||
let mut screen_style = Style::default();
|
|
||||||
screen_style.set_bg_color(Color::from_rgb((0, 0, 0)));
|
|
||||||
screen_style.set_radius(0);
|
|
||||||
// screen_style.set_layout(Layout::flex());
|
|
||||||
// screen_style.set_flex_flow(FlexFlow::ROW_WRAP);
|
|
||||||
// screen_style.set_flex_main_place(FlexAlign::CENTER);
|
|
||||||
// screen_style.set_flex_cross_place(FlexAlign::CENTER);
|
|
||||||
screen.add_style(Part::Main, &mut screen_style);
|
|
||||||
|
|
||||||
|
|
||||||
let mut wattage_label = Label::create(&mut screen).unwrap();
|
|
||||||
wattage_label.set_align(Align::Center, -70, 0);
|
|
||||||
let mut wattage_style = Style::default();
|
|
||||||
wattage_style.set_text_color(Color::from_rgb((255, 255, 255)));
|
|
||||||
unsafe { wattage_style.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_32)) };
|
|
||||||
wattage_label.add_style(Part::Main, &mut wattage_style);
|
|
||||||
wattage_label.set_text(CString::new(appdata.get_wattage_level_str()).unwrap().as_c_str());
|
|
||||||
|
|
||||||
|
|
||||||
let mut power_arc = Arc::create(&mut screen).unwrap();
|
|
||||||
power_arc.set_size(150, 150);
|
|
||||||
power_arc.set_align(Align::Center, -70, 0);
|
|
||||||
power_arc.set_angles(135, 45); // Creates a 270-degree arc
|
|
||||||
power_arc.set_rotation(0);
|
|
||||||
power_arc.set_bg_angles(135, 45);
|
|
||||||
unsafe {
|
|
||||||
lvgl_sys::lv_arc_set_range(power_arc.raw().as_mut(), 0, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
power_arc.on_event(|arc, event| {
|
|
||||||
if let Event::ValueChanged = event {
|
|
||||||
let mut value = unsafe {
|
|
||||||
lvgl_sys::lv_arc_get_value(arc.raw().as_mut())
|
|
||||||
};
|
|
||||||
appdata.set_wattage_level((value as u8).min(6));
|
|
||||||
wattage_label.set_text(CString::new(appdata.get_wattage_level_str()).unwrap().as_c_str());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add timer display on the right
|
|
||||||
let mut time_label = Label::new().unwrap();
|
|
||||||
time_label.set_text(CString::new("00:10:000").unwrap().as_c_str());
|
|
||||||
let mut style_time = Style::default();
|
|
||||||
style_time.set_text_color(Color::from_rgb((255, 255, 255)));
|
|
||||||
style_time.set_text_align(TextAlign::Center);
|
|
||||||
unsafe { style_time.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_24)) };
|
|
||||||
time_label.add_style(Part::Main, &mut style_time);
|
|
||||||
time_label.set_align(Align::Center, 80, 0);
|
|
||||||
|
|
||||||
// Add + and - buttons at bottom
|
|
||||||
let mut button_add = Btn::create(&mut screen).unwrap();
|
|
||||||
button_add.set_align(Align::Center, 10, 70);
|
|
||||||
button_add.set_size(30, 30);
|
|
||||||
let mut btn_lbl1 = Label::create(&mut button_add).unwrap();
|
|
||||||
btn_lbl1.set_text(CString::new(b"+").unwrap().as_c_str());
|
|
||||||
|
|
||||||
let mut button_sub = Btn::create(&mut screen).unwrap();
|
|
||||||
button_sub.set_size(30, 30);
|
|
||||||
button_sub.set_align(Align::Center, 130, 70);
|
|
||||||
let mut btn_lbl2 = Label::create(&mut button_sub).unwrap();
|
|
||||||
btn_lbl2.set_text(CString::new(b"-").unwrap().as_c_str());
|
|
||||||
|
|
||||||
// Add timer control logic from timer.rs
|
|
||||||
button_add.on_event(|_btn, event| {
|
|
||||||
if let Event::Pressed = event {
|
|
||||||
if appdata.timer_stopped() {
|
|
||||||
appdata.add_secs(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
button_sub.on_event(|_btn, event| {
|
|
||||||
if let Event::Pressed = event {
|
|
||||||
if appdata.timer_stopped() {
|
|
||||||
appdata.sub_secs(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add reset button
|
|
||||||
let mut button_reset = Btn::create(&mut screen).unwrap();
|
|
||||||
button_reset.set_align(Align::Center, 50, 70);
|
|
||||||
button_reset.set_size(35, 35);
|
|
||||||
let mut btn_lbl3 = Label::create(&mut button_reset).unwrap();
|
|
||||||
btn_lbl3.set_text(CString::new(b"\xEF\x80\xA1").unwrap().as_c_str());
|
|
||||||
|
|
||||||
// Add start/stop/pause button
|
|
||||||
const PLAY: &'static [u8; 3] = b"\xEF\x81\x8B";
|
|
||||||
const PAUSE: &'static [u8; 3] = b"\xEF\x81\x8C";
|
|
||||||
const STOP: &'static [u8; 3] = b"\xEF\x81\x8D";
|
|
||||||
|
|
||||||
let mut button_start_stop = Btn::create(&mut screen).unwrap();
|
|
||||||
button_start_stop.set_align(Align::Center, 90, 70);
|
|
||||||
button_start_stop.set_size(35, 35);
|
|
||||||
let mut btn_lbl4 = Label::create(&mut button_start_stop).unwrap();
|
|
||||||
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
|
|
||||||
|
|
||||||
// Add event handlers
|
|
||||||
button_reset.on_event(|_btn, event| {
|
|
||||||
if let Event::Pressed = event {
|
|
||||||
appdata.reset_timer();
|
|
||||||
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
|
|
||||||
unsafe {
|
|
||||||
lvgl_sys::lv_obj_clear_state(button_add.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
lvgl_sys::lv_obj_clear_state(button_sub.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
lvgl_sys::lv_obj_clear_state(power_arc.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
button_start_stop.on_event(|_btn, event| {
|
|
||||||
if let Event::Pressed = event {
|
|
||||||
if appdata.timer_finished() {
|
|
||||||
appdata.reset_timer();
|
|
||||||
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
|
|
||||||
// Enable all buttons
|
|
||||||
unsafe {
|
|
||||||
lvgl_sys::lv_obj_clear_state(button_add.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
lvgl_sys::lv_obj_clear_state(button_sub.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
lvgl_sys::lv_obj_clear_state(power_arc.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
}
|
|
||||||
} else if appdata.timer_running() {
|
|
||||||
appdata.pause_timer();
|
|
||||||
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
|
|
||||||
// Enable all buttons
|
|
||||||
unsafe {
|
|
||||||
lvgl_sys::lv_obj_clear_state(button_add.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
lvgl_sys::lv_obj_clear_state(button_sub.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
lvgl_sys::lv_obj_clear_state(power_arc.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
appdata.start_timer();
|
|
||||||
if appdata.timer_finished() {
|
|
||||||
btn_lbl4.set_text(CString::new(STOP).unwrap().as_c_str());
|
|
||||||
} else {
|
|
||||||
btn_lbl4.set_text(CString::new(PAUSE).unwrap().as_c_str());
|
|
||||||
// Disable buttons while running
|
|
||||||
unsafe {
|
|
||||||
lvgl_sys::lv_obj_add_state(button_add.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
lvgl_sys::lv_obj_add_state(button_sub.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
lvgl_sys::lv_obj_add_state(power_arc.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut was_finished = false;
|
|
||||||
let mut last_rem_time: Duration = appdata.remaining() + Duration::from_millis(10);
|
|
||||||
|
|
||||||
let mut last_time = Instant::now();
|
|
||||||
loop {
|
|
||||||
let start_time = Instant::now();
|
|
||||||
|
|
||||||
let dur = appdata.remaining();
|
|
||||||
if last_rem_time != dur {
|
|
||||||
let val = CString::new(format!("{:02}:{:02}:{:03}",
|
|
||||||
dur.as_secs() / 60,
|
|
||||||
dur.as_secs() % 60,
|
|
||||||
dur.as_millis() % 1000)).unwrap();
|
|
||||||
|
|
||||||
time_label.set_text(&val).unwrap();
|
|
||||||
last_rem_time = dur;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !was_finished && appdata.timer_finished() {
|
|
||||||
btn_lbl4.set_text(CString::new(STOP).unwrap().as_c_str());
|
|
||||||
}
|
|
||||||
was_finished = appdata.timer_finished();
|
|
||||||
|
|
||||||
let start_draw_time = Instant::now();
|
|
||||||
lvgl::task_handler();
|
|
||||||
|
|
||||||
let now_time = Instant::now();
|
|
||||||
lvgl::tick_inc(now_time.duration_since(last_time));
|
|
||||||
last_time = now_time;
|
|
||||||
|
|
||||||
let end_time = Instant::now();
|
|
||||||
let draw_time = raw_display.get_time();
|
|
||||||
let prep_time = start_draw_time - start_time;
|
|
||||||
let proc_time = end_time - start_draw_time;
|
|
||||||
let proc_time = proc_time - min(draw_time, proc_time);
|
|
||||||
|
|
||||||
if draw_time.as_micros() > 0 {
|
|
||||||
println!(
|
|
||||||
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
|
|
||||||
draw_time.as_millis(),
|
|
||||||
draw_time.as_micros() % 100,
|
|
||||||
prep_time.as_millis(),
|
|
||||||
prep_time.as_micros() % 100,
|
|
||||||
proc_time.as_millis(),
|
|
||||||
proc_time.as_micros() % 100,
|
|
||||||
(draw_time + prep_time + proc_time).as_millis(),
|
|
||||||
(draw_time + prep_time + proc_time).as_micros() % 100, );
|
|
||||||
}
|
|
||||||
raw_display.reset_time();
|
|
||||||
|
|
||||||
delay::FreeRtos::delay_ms(2);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// Don't exit application
|
|
||||||
delay::FreeRtos::delay_ms(1_000_000);
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,7 +24,7 @@ use lvgl::{
|
|||||||
pointer::{Pointer, PointerInputData},
|
pointer::{Pointer, PointerInputData},
|
||||||
InputDriver,
|
InputDriver,
|
||||||
},
|
},
|
||||||
style::{FlexAlign, FlexFlow, Style},
|
style::Style,
|
||||||
widgets::{Btn, Label},
|
widgets::{Btn, Label},
|
||||||
Align,
|
Align,
|
||||||
Color,
|
Color,
|
||||||
@ -32,7 +32,6 @@ use lvgl::{
|
|||||||
DrawBuffer,
|
DrawBuffer,
|
||||||
Event,
|
Event,
|
||||||
LvError,
|
LvError,
|
||||||
Obj,
|
|
||||||
Part,
|
Part,
|
||||||
TextAlign,
|
TextAlign,
|
||||||
Widget,
|
Widget,
|
||||||
@ -283,11 +282,9 @@ fn main() -> Result<(), LvError> {
|
|||||||
screen.add_style(Part::Main, &mut screen_style);
|
screen.add_style(Part::Main, &mut screen_style);
|
||||||
|
|
||||||
let mut time = Label::new().unwrap();
|
let mut time = Label::new().unwrap();
|
||||||
time.set_text(CString::new("00:10:000").unwrap().as_c_str());
|
|
||||||
let mut style_time = Style::default();
|
let mut style_time = Style::default();
|
||||||
style_time.set_text_color(Color::from_rgb((255, 255, 255))); // white
|
style_time.set_text_color(Color::from_rgb((255, 255, 255))); // white
|
||||||
style_time.set_text_align(TextAlign::Center);
|
style_time.set_text_align(TextAlign::Center);
|
||||||
unsafe { style_time.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_24)) };
|
|
||||||
|
|
||||||
// Custom font requires lvgl-sys in Cargo.toml and 'use lvgl_sys' in this file
|
// Custom font requires lvgl-sys in Cargo.toml and 'use lvgl_sys' in this file
|
||||||
|
|
||||||
@ -296,22 +293,10 @@ fn main() -> Result<(), LvError> {
|
|||||||
// Time text will be centered in screen
|
// Time text will be centered in screen
|
||||||
time.set_align(Align::Center, 0, 0);
|
time.set_align(Align::Center, 0, 0);
|
||||||
|
|
||||||
// let mut cont = Obj::new().unwrap();
|
|
||||||
// cont.set_size(320, 50);
|
|
||||||
// let mut style = Style::default();
|
|
||||||
// style.set_flex_flow(FlexFlow::ROW);
|
|
||||||
// style.set_flex_main_place(FlexAlign::SPACE_EVENLY);
|
|
||||||
// style.set_flex_cross_place(FlexAlign::CENTER);
|
|
||||||
// // style.set_bg_opa(0);
|
|
||||||
// style.set_border_width(0);
|
|
||||||
// cont.add_style(Part::Any, &mut style);
|
|
||||||
// cont.set_align(Align::Center, 0, 40);
|
|
||||||
|
|
||||||
let mut button_add = Btn::create(&mut screen).unwrap();
|
let mut button_add = Btn::create(&mut screen).unwrap();
|
||||||
button_add.set_align(Align::Center, -50, 32);
|
// button_add.set_align(Align::Center, -50, -100);
|
||||||
// button_add.set_pos(130, 150);
|
button_add.set_pos(130, 150);
|
||||||
button_add.set_size(30, 30);
|
button_add.set_size(30, 30);
|
||||||
// button_add.set_align(Align::Center, 0, 0);
|
|
||||||
let mut btn_lbl1 = Label::create(&mut button_add).unwrap();
|
let mut btn_lbl1 = Label::create(&mut button_add).unwrap();
|
||||||
btn_lbl1.set_text(CString::new(b"+").unwrap().as_c_str());
|
btn_lbl1.set_text(CString::new(b"+").unwrap().as_c_str());
|
||||||
|
|
||||||
@ -326,10 +311,8 @@ fn main() -> Result<(), LvError> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let mut button_sub = Btn::create(&mut screen).unwrap();
|
let mut button_sub = Btn::create(&mut screen).unwrap();
|
||||||
// button_sub.set_pos(170, 150);
|
button_sub.set_pos(170, 150);
|
||||||
button_sub.set_size(30, 30);
|
button_sub.set_size(30, 30);
|
||||||
button_sub.set_align(Align::Center, 50, 35);
|
|
||||||
// button_sub.set_align(Align::Center, 0, 0);
|
|
||||||
let mut btn_lbl2 = Label::create(&mut button_sub).unwrap();
|
let mut btn_lbl2 = Label::create(&mut button_sub).unwrap();
|
||||||
btn_lbl2.set_text(CString::new(b"-").unwrap().as_c_str());
|
btn_lbl2.set_text(CString::new(b"-").unwrap().as_c_str());
|
||||||
|
|
||||||
@ -341,16 +324,8 @@ fn main() -> Result<(), LvError> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut center_label = Label::new().unwrap();
|
|
||||||
center_label.set_text(CString::new(b"+/- 10s").unwrap().as_c_str());
|
|
||||||
center_label.set_align(Align::Center, 0, 35);
|
|
||||||
let mut cl_style = Style::default();
|
|
||||||
cl_style.set_text_color(Color::from_rgb((255, 255, 255)));
|
|
||||||
center_label.add_style(Part::Main, &mut cl_style);
|
|
||||||
|
|
||||||
let mut button_reset = Btn::create(&mut screen).unwrap();
|
let mut button_reset = Btn::create(&mut screen).unwrap();
|
||||||
//button_reset.set_pos(123, 190);
|
button_reset.set_pos(123, 190);
|
||||||
button_reset.set_align(Align::Center, -22, 70);
|
|
||||||
button_reset.set_size(35, 35);
|
button_reset.set_size(35, 35);
|
||||||
let mut btn_lbl3 = Label::create(&mut button_reset).unwrap();
|
let mut btn_lbl3 = Label::create(&mut button_reset).unwrap();
|
||||||
btn_lbl3.set_text(CString::new(b"\xEF\x80\xA1").unwrap().as_c_str());
|
btn_lbl3.set_text(CString::new(b"\xEF\x80\xA1").unwrap().as_c_str());
|
||||||
@ -361,8 +336,7 @@ fn main() -> Result<(), LvError> {
|
|||||||
const STOP: &'static [u8; 3] = b"\xEF\x81\x8D";
|
const STOP: &'static [u8; 3] = b"\xEF\x81\x8D";
|
||||||
|
|
||||||
let mut button_start_stop = Btn::create(&mut screen).unwrap();
|
let mut button_start_stop = Btn::create(&mut screen).unwrap();
|
||||||
// button_start_stop.set_pos(173, 190);
|
button_start_stop.set_pos(173, 190);
|
||||||
button_start_stop.set_align(Align::Center, 22, 70);
|
|
||||||
button_start_stop.set_size(35, 35);
|
button_start_stop.set_size(35, 35);
|
||||||
let mut btn_lbl4 = Label::create(&mut button_start_stop).unwrap();
|
let mut btn_lbl4 = Label::create(&mut button_start_stop).unwrap();
|
||||||
btn_lbl4.set_text(CString::new(b"\xEF\x81\x8B").unwrap().as_c_str());
|
btn_lbl4.set_text(CString::new(b"\xEF\x81\x8B").unwrap().as_c_str());
|
||||||
@ -399,19 +373,15 @@ fn main() -> Result<(), LvError> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let mut was_finished = false;
|
let mut was_finished = false;
|
||||||
let mut last_rem_time: Duration = appdata.remaining() + Duration::from_millis(10);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let rem_time = appdata.remaining();
|
let rem_time = appdata.remaining();
|
||||||
let val = CString::new(format!("{:02}:{:02}:{:03}",
|
let val = CString::new(format!("{:02}:{:02}:{:03}",
|
||||||
rem_time.as_secs() / 60,
|
rem_time.as_secs() / 60,
|
||||||
rem_time.as_secs() % 60,
|
rem_time.as_secs() % 60,
|
||||||
rem_time.as_millis() % 1000)).unwrap();
|
rem_time.as_millis() % 1000)).unwrap();
|
||||||
|
|
||||||
time.set_text(&val).unwrap();
|
time.set_text(&val).unwrap();
|
||||||
last_rem_time = rem_time;
|
|
||||||
|
|
||||||
|
|
||||||
if !was_finished && appdata.timer_finished() {
|
if !was_finished && appdata.timer_finished() {
|
||||||
was_finished = true;
|
was_finished = true;
|
||||||
@ -449,7 +419,7 @@ fn main() -> Result<(), LvError> {
|
|||||||
}
|
}
|
||||||
raw_display.reset_time();
|
raw_display.reset_time();
|
||||||
|
|
||||||
delay::FreeRtos::delay_ms(2);
|
delay::FreeRtos::delay_ms(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
# This file is MIT or Apache-2.0 as per the repository README.md file
|
# This file is MIT or Apache-2.0 as per the repository README.md file
|
||||||
#
|
#
|
||||||
|
|
||||||
|
[target.xtensa-esp32-none]
|
||||||
# runner = "espflash --monitor" # Select this runner for espflash v1.x.x
|
# runner = "espflash --monitor" # Select this runner for espflash v1.x.x
|
||||||
runner = "espflash flash --monitor --baud 921600" # Select this runner for espflash v2.x.x
|
runner = "espflash flash --monitor --baud 921600" # Select this runner for espflash v2.x.x
|
||||||
|
|
||||||
|
@ -15,15 +15,6 @@ build = "build.rs"
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "starter"
|
name = "starter"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "timer"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "light-control"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "microwave-ui"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
slint = { version = "1.8", default-features = false, features = ["compat-1-2", "renderer-software", "libm", "unsafe-single-threaded"] }
|
slint = { version = "1.8", default-features = false, features = ["compat-1-2", "renderer-software", "libm", "unsafe-single-threaded"] }
|
||||||
|
|
||||||
@ -51,7 +42,7 @@ embedded-graphics-profiler-display = { version = "0.1.0", path = "../embedded-gr
|
|||||||
xpt2046 = { git = "https://github.com/Yandrik/xpt2046.git", version = "0.3.1" }
|
xpt2046 = { git = "https://github.com/Yandrik/xpt2046.git", version = "0.3.1" }
|
||||||
|
|
||||||
|
|
||||||
esp-backtrace = { version = "0.14.2", features = ["esp32", "println", "panic-handler", "exception-handler"] }
|
esp-backtrace = { version = "0.14.2", features = ["esp32", "println"] }
|
||||||
|
|
||||||
static_cell = { version = "2.1.0", features = ["nightly"] }
|
static_cell = { version = "2.1.0", features = ["nightly"] }
|
||||||
embedded-hal-bus = "0.2.0"
|
embedded-hal-bus = "0.2.0"
|
||||||
|
@ -5,22 +5,4 @@ fn main() {
|
|||||||
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer),
|
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
slint_build::compile_with_config(
|
|
||||||
"ui/timer-app.slint",
|
|
||||||
slint_build::CompilerConfiguration::new()
|
|
||||||
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
slint_build::compile_with_config(
|
|
||||||
"ui/lights-app.slint",
|
|
||||||
slint_build::CompilerConfiguration::new()
|
|
||||||
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
slint_build::compile_with_config(
|
|
||||||
"ui/microwave-ui.slint",
|
|
||||||
slint_build::CompilerConfiguration::new()
|
|
||||||
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 373 B |
Binary file not shown.
Before Width: | Height: | Size: 401 B |
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 542 B |
@ -1,347 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
use alloc::rc::Rc;
|
|
||||||
use core::mem::MaybeUninit;
|
|
||||||
use core::{cell::RefCell, cmp::min, fmt};
|
|
||||||
use display_interface_spi::SPIInterface;
|
|
||||||
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_sync::{
|
|
||||||
blocking_mutex::{raw::NoopRawMutex, NoopMutex},
|
|
||||||
signal::Signal,
|
|
||||||
};
|
|
||||||
use embassy_time::Delay;
|
|
||||||
use embassy_time::{Duration, Instant, Timer};
|
|
||||||
use embedded_graphics_core::pixelcolor::Rgb565;
|
|
||||||
use embedded_graphics_core::prelude::*;
|
|
||||||
use embedded_graphics_core::primitives::Rectangle;
|
|
||||||
use embedded_graphics_profiler_display::ProfilerDisplay;
|
|
||||||
use embedded_hal::digital::OutputPin;
|
|
||||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
|
||||||
use esp_backtrace as _;
|
|
||||||
use esp_hal::interrupt::Priority;
|
|
||||||
use esp_hal::{self, prelude::*};
|
|
||||||
use esp_hal::{
|
|
||||||
clock::ClockControl,
|
|
||||||
gpio::{GpioPin, Input, Io, Level, Output, Pull, NO_PIN},
|
|
||||||
peripherals::{Peripherals, SPI2, SPI3},
|
|
||||||
prelude::*,
|
|
||||||
rtc_cntl::Rtc,
|
|
||||||
spi::{master::Spi, FullDuplexMode, SpiMode},
|
|
||||||
system::SystemControl,
|
|
||||||
timer::timg::TimerGroup,
|
|
||||||
};
|
|
||||||
use esp_hal_embassy::InterruptExecutor;
|
|
||||||
use esp_println::println;
|
|
||||||
use mipidsi::{
|
|
||||||
models::ILI9486Rgb565,
|
|
||||||
options::{ColorInversion, ColorOrder, Orientation, Rotation},
|
|
||||||
Builder, Display,
|
|
||||||
};
|
|
||||||
use slint::platform::software_renderer::MinimalSoftwareWindow;
|
|
||||||
use slint::platform::{Platform, PointerEventButton, WindowEvent};
|
|
||||||
use slint::private_unstable_api::re_exports::LogicalPoint;
|
|
||||||
use slint::LogicalPosition;
|
|
||||||
use static_cell::StaticCell;
|
|
||||||
use xpt2046::Xpt2046;
|
|
||||||
|
|
||||||
use esp_alloc as _;
|
|
||||||
|
|
||||||
// slint::slint!{ export MyUI := Window {} }
|
|
||||||
/*
|
|
||||||
slint::include_modules!();
|
|
||||||
# */
|
|
||||||
|
|
||||||
slint::include_modules!();
|
|
||||||
|
|
||||||
fn init_heap() {
|
|
||||||
const HEAP_SIZE: usize = 64 * 1024;
|
|
||||||
static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
esp_alloc::HEAP.add_region(esp_alloc::HeapRegion::new(
|
|
||||||
HEAP.as_mut_ptr() as *mut u8,
|
|
||||||
HEAP_SIZE,
|
|
||||||
esp_alloc::MemoryCapability::Internal.into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::task]
|
|
||||||
async fn touch_task(
|
|
||||||
touch_irq: GpioPin<36>,
|
|
||||||
spi: ExclusiveDevice<
|
|
||||||
Spi<'static, SPI3, FullDuplexMode>,
|
|
||||||
Output<'static, GpioPin<33>>,
|
|
||||||
&'static mut Delay,
|
|
||||||
>,
|
|
||||||
touch_signal: &'static Signal<NoopRawMutex, Option<Point>>,
|
|
||||||
) -> ! {
|
|
||||||
let mut touch_driver =
|
|
||||||
Xpt2046::new(spi, Input::new(touch_irq, Pull::Up), xpt2046::Orientation::LandscapeFlipped);
|
|
||||||
touch_driver.set_num_samples(16);
|
|
||||||
touch_driver.init(&mut embassy_time::Delay).unwrap();
|
|
||||||
|
|
||||||
esp_println::println!("touch task");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
touch_driver.run().expect("Running Touch driver failed");
|
|
||||||
if touch_driver.is_touched() {
|
|
||||||
let point = touch_driver.get_touch_point();
|
|
||||||
touch_signal.signal(Some(Point::new(point.x + 25, 240 - point.y)));
|
|
||||||
} else {
|
|
||||||
touch_signal.signal(None);
|
|
||||||
}
|
|
||||||
Timer::after(Duration::from_millis(1)).await; // 100 a second
|
|
||||||
|
|
||||||
// Your touch handling logic here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn point_to_logical_pos(point: Point) -> LogicalPosition {
|
|
||||||
LogicalPosition::new(point.x as f32, point.y as f32)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CYDPlatform {
|
|
||||||
window: Rc<slint::platform::software_renderer::MinimalSoftwareWindow>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Platform for CYDPlatform {
|
|
||||||
fn create_window_adapter(
|
|
||||||
&self,
|
|
||||||
) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
|
|
||||||
Ok(self.window.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn duration_since_start(&self) -> core::time::Duration {
|
|
||||||
embassy_time::Instant::from_millis(0).elapsed().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection DuplicatedCode
|
|
||||||
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[main]
|
|
||||||
async fn main(spawner: Spawner) {
|
|
||||||
init_heap();
|
|
||||||
let peripherals = Peripherals::take();
|
|
||||||
let system = SystemControl::new(peripherals.SYSTEM);
|
|
||||||
let mut clocks = ClockControl::boot_defaults(system.clock_control).freeze();
|
|
||||||
|
|
||||||
let mut rtc = Rtc::new(peripherals.LPWR);
|
|
||||||
rtc.rwdt.disable();
|
|
||||||
let mut timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
|
|
||||||
timer_group0.wdt.disable();
|
|
||||||
let mut timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
|
|
||||||
timer_group1.wdt.disable();
|
|
||||||
|
|
||||||
esp_hal_embassy::init(&clocks, timer_group0.timer0);
|
|
||||||
|
|
||||||
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
|
|
||||||
|
|
||||||
let touch_irq = io.pins.gpio36;
|
|
||||||
let touch_mosi = io.pins.gpio32;
|
|
||||||
let touch_miso = io.pins.gpio39;
|
|
||||||
let touch_clk = io.pins.gpio25;
|
|
||||||
let touch_cs = io.pins.gpio33;
|
|
||||||
|
|
||||||
// 2MHz is the MAX! DO NOT DECREASE! This is really important.
|
|
||||||
let mut touch_spi = Spi::new(peripherals.SPI3, 2.MHz(), SpiMode::Mode0, &mut clocks).with_pins(
|
|
||||||
Some(touch_clk),
|
|
||||||
Some(touch_mosi),
|
|
||||||
Some(touch_miso),
|
|
||||||
NO_PIN,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TOUCH_DELAY_STATICCELL: StaticCell<Delay> = StaticCell::new();
|
|
||||||
let mut delay = TOUCH_DELAY_STATICCELL.init(Delay);
|
|
||||||
|
|
||||||
let touch_spi =
|
|
||||||
ExclusiveDevice::new(touch_spi, Output::new(touch_cs, Level::Low), delay).unwrap();
|
|
||||||
|
|
||||||
let touch_signal = Signal::new();
|
|
||||||
static TOUCH_SIGNAL: StaticCell<Signal<NoopRawMutex, Option<Point>>> = StaticCell::new();
|
|
||||||
let touch_signal = &*TOUCH_SIGNAL.init(touch_signal);
|
|
||||||
|
|
||||||
// let sw_int = system.software_interrupt_control.software_interrupt2;
|
|
||||||
|
|
||||||
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
|
|
||||||
// let executor = InterruptExecutor::<2>::new(sw_int);
|
|
||||||
// let executor = EXECUTOR.init(executor);
|
|
||||||
|
|
||||||
spawner.spawn(touch_task(touch_irq, touch_spi, touch_signal)).unwrap();
|
|
||||||
|
|
||||||
// executor.start(Priority::Priority1);
|
|
||||||
|
|
||||||
// Display setup
|
|
||||||
let sclk = io.pins.gpio14;
|
|
||||||
let miso = io.pins.gpio12;
|
|
||||||
let mosi = io.pins.gpio13;
|
|
||||||
let cs = io.pins.gpio15;
|
|
||||||
let dc = io.pins.gpio2;
|
|
||||||
let mut backlight = Output::new(io.pins.gpio21, Level::Low);
|
|
||||||
|
|
||||||
let mut spi = Spi::new(peripherals.SPI2, 10u32.MHz(), SpiMode::Mode0, &clocks).with_pins(
|
|
||||||
Some(sclk),
|
|
||||||
Some(mosi),
|
|
||||||
Some(miso),
|
|
||||||
esp_hal::gpio::NO_PIN,
|
|
||||||
);
|
|
||||||
|
|
||||||
// static DISP_SPI_BUS: StaticCell<NoopMutex<RefCell<Spi<SPI2, FullDuplexMode>>>> = StaticCell::new();
|
|
||||||
// let spi_bus = NoopMutex::new(RefCell::new(spi));
|
|
||||||
// let spi_bus = DISP_SPI_BUS.init(spi_bus);
|
|
||||||
|
|
||||||
static spi_delay_staticcell: StaticCell<Delay> = StaticCell::new();
|
|
||||||
let mut delay = spi_delay_staticcell.init(Delay);
|
|
||||||
let spi = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), delay).unwrap();
|
|
||||||
|
|
||||||
let di = SPIInterface::new(spi, Output::new(dc, Level::Low));
|
|
||||||
|
|
||||||
let display = Builder::new(ILI9486Rgb565, di)
|
|
||||||
.orientation(Orientation { rotation: Rotation::Deg90, mirrored: true })
|
|
||||||
.color_order(ColorOrder::Bgr)
|
|
||||||
.invert_colors(ColorInversion::Inverted)
|
|
||||||
.init(&mut embassy_time::Delay)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut display = ProfilerDisplay::new(display);
|
|
||||||
|
|
||||||
backlight.set_high();
|
|
||||||
|
|
||||||
let size = display.bounding_box().size;
|
|
||||||
let size = slint::PhysicalSize::new(size.width as u32, size.height as u32);
|
|
||||||
|
|
||||||
let window = MinimalSoftwareWindow::new(Default::default());
|
|
||||||
slint::platform::set_platform(Box::new(CYDPlatform { window: window.clone() })).unwrap();
|
|
||||||
|
|
||||||
let ui = create_slint_app();
|
|
||||||
|
|
||||||
window.set_size(slint::PhysicalSize::new(320, 240));
|
|
||||||
|
|
||||||
let mut buffer_provider = DrawBuffer {
|
|
||||||
display,
|
|
||||||
buffer: &mut [slint::platform::software_renderer::Rgb565Pixel(0); 320],
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut last_touch = None;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let start_time = Instant::now();
|
|
||||||
if let Some(touch) = touch_signal.try_take() {
|
|
||||||
// println!("touch: {:?}, last_touch: {:?}", touch, last_touch);
|
|
||||||
let button = PointerEventButton::Left;
|
|
||||||
let interact = match (touch, last_touch) {
|
|
||||||
(Some(point), Some(_)) => {
|
|
||||||
Some(WindowEvent::PointerMoved { position: point_to_logical_pos(point) })
|
|
||||||
}
|
|
||||||
(Some(point), None) => Some(WindowEvent::PointerPressed {
|
|
||||||
position: point_to_logical_pos(point),
|
|
||||||
button,
|
|
||||||
}),
|
|
||||||
(None, Some(point)) => Some(WindowEvent::PointerReleased {
|
|
||||||
position: point_to_logical_pos(point),
|
|
||||||
button,
|
|
||||||
}),
|
|
||||||
(None, None) => None,
|
|
||||||
};
|
|
||||||
if let Some(event) = interact {
|
|
||||||
// println!("event: {:?}", event);
|
|
||||||
window.dispatch_event(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
last_touch = touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
slint::platform::update_timers_and_animations();
|
|
||||||
|
|
||||||
// let window = window.clone();
|
|
||||||
let start_draw_time = Instant::now();
|
|
||||||
window.draw_if_needed(|renderer| {
|
|
||||||
renderer.render_by_line(&mut buffer_provider);
|
|
||||||
});
|
|
||||||
|
|
||||||
let button = PointerEventButton::Left;
|
|
||||||
|
|
||||||
if window.has_active_animations() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let display = &mut buffer_provider.display;
|
|
||||||
|
|
||||||
let end_time = embassy_time::Instant::now();
|
|
||||||
let draw_time = display.get_time();
|
|
||||||
let prep_time = start_draw_time - start_time;
|
|
||||||
let proc_time = end_time - start_draw_time;
|
|
||||||
let proc_time = proc_time - min(draw_time, proc_time);
|
|
||||||
rtc.rwdt.feed();
|
|
||||||
|
|
||||||
if draw_time.as_micros() > 0 {
|
|
||||||
println!(
|
|
||||||
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
|
|
||||||
draw_time.as_millis(),
|
|
||||||
draw_time.as_micros() % 100,
|
|
||||||
prep_time.as_millis(),
|
|
||||||
prep_time.as_micros() % 100,
|
|
||||||
proc_time.as_millis(),
|
|
||||||
proc_time.as_micros() % 100,
|
|
||||||
(draw_time + prep_time + proc_time).as_millis(),
|
|
||||||
(draw_time + prep_time + proc_time).as_micros() % 100, );
|
|
||||||
}
|
|
||||||
display.reset_time();
|
|
||||||
Timer::after(Duration::from_millis(1)).await; // 60 a second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_slint_app() -> LightsApp {
|
|
||||||
let ui = LightsApp::new().unwrap();
|
|
||||||
ui
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DrawBuffer<'a, DT> {
|
|
||||||
display: DT,
|
|
||||||
buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
// DI: display_interface_spi::WriteOnlyDataCommand,
|
|
||||||
E: core::fmt::Debug,
|
|
||||||
DT: DrawTarget<Color = Rgb565, Error = E>,
|
|
||||||
// RST: OutputPin<Error = core::convert::Infallible>,
|
|
||||||
> slint::platform::software_renderer::LineBufferProvider for &mut DrawBuffer<'_, DT>
|
|
||||||
{
|
|
||||||
type TargetPixel = slint::platform::software_renderer::Rgb565Pixel;
|
|
||||||
|
|
||||||
fn process_line(
|
|
||||||
&mut self,
|
|
||||||
line: usize,
|
|
||||||
range: core::ops::Range<usize>,
|
|
||||||
render_fn: impl FnOnce(&mut [slint::platform::software_renderer::Rgb565Pixel]),
|
|
||||||
) {
|
|
||||||
let buffer = &mut self.buffer[range.clone()];
|
|
||||||
|
|
||||||
render_fn(buffer);
|
|
||||||
|
|
||||||
// We send empty data just to get the device in the right window
|
|
||||||
self.display
|
|
||||||
.fill_contiguous(
|
|
||||||
&Rectangle::new(
|
|
||||||
Point::new(range.start as i32, line as i32),
|
|
||||||
Size::new((range.end - range.start) as u32, 1),
|
|
||||||
),
|
|
||||||
// range.start as u16,
|
|
||||||
// line as _,
|
|
||||||
// range.end as u16,
|
|
||||||
// line as u16,
|
|
||||||
buffer
|
|
||||||
.iter()
|
|
||||||
.map(|x| embedded_graphics_core::pixelcolor::raw::RawU16::new(x.0).into()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,490 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
use alloc::rc::Rc;
|
|
||||||
use alloc::sync::Arc;
|
|
||||||
use core::mem::MaybeUninit;
|
|
||||||
use core::{cell::RefCell, cmp::min, fmt};
|
|
||||||
use display_interface_spi::SPIInterface;
|
|
||||||
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
|
||||||
use embassy_sync::{
|
|
||||||
blocking_mutex::{raw::NoopRawMutex, NoopMutex},
|
|
||||||
signal::Signal,
|
|
||||||
};
|
|
||||||
use embassy_time::Delay;
|
|
||||||
use embassy_time::{Duration, Instant, Timer};
|
|
||||||
use embedded_graphics_core::pixelcolor::Rgb565;
|
|
||||||
use embedded_graphics_core::prelude::*;
|
|
||||||
use embedded_graphics_core::primitives::Rectangle;
|
|
||||||
use embedded_graphics_profiler_display::ProfilerDisplay;
|
|
||||||
use embedded_hal::digital::OutputPin;
|
|
||||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
|
||||||
use esp_backtrace as _;
|
|
||||||
use esp_hal::interrupt::Priority;
|
|
||||||
use esp_hal::{self, prelude::*};
|
|
||||||
use esp_hal::{
|
|
||||||
clock::ClockControl,
|
|
||||||
gpio::{GpioPin, Input, Io, Level, Output, Pull, NO_PIN},
|
|
||||||
peripherals::{Peripherals, SPI2, SPI3},
|
|
||||||
prelude::*,
|
|
||||||
rtc_cntl::Rtc,
|
|
||||||
spi::{master::Spi, FullDuplexMode, SpiMode},
|
|
||||||
system::SystemControl,
|
|
||||||
timer::timg::TimerGroup,
|
|
||||||
};
|
|
||||||
use esp_hal_embassy::InterruptExecutor;
|
|
||||||
use esp_println::println;
|
|
||||||
use mipidsi::{
|
|
||||||
models::ILI9486Rgb565,
|
|
||||||
options::{ColorInversion, ColorOrder, Orientation, Rotation},
|
|
||||||
Builder, Display,
|
|
||||||
};
|
|
||||||
use slint::platform::software_renderer::MinimalSoftwareWindow;
|
|
||||||
use slint::platform::{Platform, PointerEventButton, WindowEvent};
|
|
||||||
use slint::private_unstable_api::re_exports::LogicalPoint;
|
|
||||||
use slint::{format, LogicalPosition};
|
|
||||||
use static_cell::StaticCell;
|
|
||||||
use xpt2046::Xpt2046;
|
|
||||||
|
|
||||||
use esp_alloc as _;
|
|
||||||
|
|
||||||
// slint::slint!{ export MyUI := Window {} }
|
|
||||||
/*
|
|
||||||
slint::include_modules!();
|
|
||||||
# */
|
|
||||||
|
|
||||||
slint::include_modules!();
|
|
||||||
|
|
||||||
fn init_heap() {
|
|
||||||
const HEAP_SIZE: usize = 64 * 1024;
|
|
||||||
static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
esp_alloc::HEAP.add_region(esp_alloc::HeapRegion::new(
|
|
||||||
HEAP.as_mut_ptr() as *mut u8,
|
|
||||||
HEAP_SIZE,
|
|
||||||
esp_alloc::MemoryCapability::Internal.into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::task]
|
|
||||||
async fn touch_task(
|
|
||||||
touch_irq: GpioPin<36>,
|
|
||||||
spi: ExclusiveDevice<
|
|
||||||
Spi<'static, SPI3, FullDuplexMode>,
|
|
||||||
Output<'static, GpioPin<33>>,
|
|
||||||
&'static mut Delay,
|
|
||||||
>,
|
|
||||||
touch_signal: &'static Signal<CriticalSectionRawMutex, Option<Point>>,
|
|
||||||
) -> ! {
|
|
||||||
let mut touch_driver =
|
|
||||||
Xpt2046::new(spi, Input::new(touch_irq, Pull::Up), xpt2046::Orientation::LandscapeFlipped);
|
|
||||||
touch_driver.set_num_samples(1);
|
|
||||||
touch_driver.init(&mut embassy_time::Delay).unwrap();
|
|
||||||
|
|
||||||
esp_println::println!("touch task");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
touch_driver.run().expect("Running Touch driver failed");
|
|
||||||
if touch_driver.is_touched() {
|
|
||||||
let point = touch_driver.get_touch_point();
|
|
||||||
touch_signal.signal(Some(Point::new(point.x + 25, 240 - point.y)));
|
|
||||||
} else {
|
|
||||||
touch_signal.signal(None);
|
|
||||||
}
|
|
||||||
Timer::after(Duration::from_millis(1)).await; // 100 a second
|
|
||||||
|
|
||||||
// Your touch handling logic here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn point_to_logical_pos(point: Point) -> LogicalPosition {
|
|
||||||
LogicalPosition::new(point.x as f32, point.y as f32)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CYDPlatform {
|
|
||||||
window: Rc<slint::platform::software_renderer::MinimalSoftwareWindow>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Platform for CYDPlatform {
|
|
||||||
fn create_window_adapter(
|
|
||||||
&self,
|
|
||||||
) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
|
|
||||||
Ok(self.window.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn duration_since_start(&self) -> core::time::Duration {
|
|
||||||
embassy_time::Instant::from_millis(0).elapsed().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection DuplicatedCode
|
|
||||||
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AppData {
|
|
||||||
timer_start: Instant,
|
|
||||||
timer_set_duration: Duration,
|
|
||||||
timer_remaining_duration: Duration,
|
|
||||||
timer_running: bool,
|
|
||||||
timer_paused: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppData {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
timer_start: Instant::now(),
|
|
||||||
timer_set_duration: Duration::from_secs(10),
|
|
||||||
timer_remaining_duration: Duration::from_secs(10),
|
|
||||||
timer_running: false,
|
|
||||||
timer_paused: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_timer_duration(&mut self, duration: Duration) {
|
|
||||||
self.timer_set_duration = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_secs(&mut self, secs: u64) {
|
|
||||||
self.set_timer_duration(
|
|
||||||
self.timer_set_duration
|
|
||||||
.checked_add(Duration::from_secs(secs))
|
|
||||||
.unwrap_or(Duration::from_secs(5999))
|
|
||||||
.min(Duration::from_secs(5999)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sub_secs(&mut self, secs: u64) {
|
|
||||||
self.set_timer_duration(
|
|
||||||
self.timer_set_duration
|
|
||||||
.checked_sub(Duration::from_secs(secs))
|
|
||||||
.unwrap_or(Duration::from_secs(10))
|
|
||||||
.max(Duration::from_secs(10)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_timer(&mut self) {
|
|
||||||
if !self.timer_paused {
|
|
||||||
self.timer_remaining_duration = self.timer_set_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.timer_start = Instant::now();
|
|
||||||
self.timer_running = true;
|
|
||||||
self.timer_paused = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pause_timer(&mut self) {
|
|
||||||
self.timer_paused = true;
|
|
||||||
self.timer_remaining_duration = self
|
|
||||||
.timer_remaining_duration
|
|
||||||
.checked_sub(self.timer_start.elapsed())
|
|
||||||
.unwrap_or(Duration::from_secs(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_timer(&mut self) {
|
|
||||||
self.timer_start = Instant::now();
|
|
||||||
self.timer_paused = false;
|
|
||||||
self.timer_running = false;
|
|
||||||
self.timer_remaining_duration = self.timer_set_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remaining(&self) -> Duration {
|
|
||||||
if self.timer_stopped() {
|
|
||||||
self.timer_set_duration
|
|
||||||
} else if self.timer_paused() {
|
|
||||||
self.timer_remaining_duration
|
|
||||||
} else {
|
|
||||||
self.timer_remaining_duration
|
|
||||||
.checked_sub(self.timer_start.elapsed())
|
|
||||||
.unwrap_or(Duration::from_secs(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_stopped(&self) -> bool {
|
|
||||||
!self.timer_running
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_paused(&self) -> bool {
|
|
||||||
self.timer_running && self.timer_paused
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_running(&self) -> bool {
|
|
||||||
self.timer_running && !self.timer_paused
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_finished(&self) -> bool {
|
|
||||||
self.timer_running && self.remaining() == Duration::from_secs(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[main]
|
|
||||||
async fn main(spawner: Spawner) {
|
|
||||||
init_heap();
|
|
||||||
let peripherals = Peripherals::take();
|
|
||||||
let system = SystemControl::new(peripherals.SYSTEM);
|
|
||||||
let mut clocks = ClockControl::boot_defaults(system.clock_control).freeze();
|
|
||||||
|
|
||||||
let mut rtc = Rtc::new(peripherals.LPWR);
|
|
||||||
rtc.rwdt.disable();
|
|
||||||
let mut timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
|
|
||||||
timer_group0.wdt.disable();
|
|
||||||
let mut timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
|
|
||||||
timer_group1.wdt.disable();
|
|
||||||
|
|
||||||
esp_hal_embassy::init(&clocks, timer_group0.timer0);
|
|
||||||
|
|
||||||
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
|
|
||||||
|
|
||||||
let touch_irq = io.pins.gpio36;
|
|
||||||
let touch_mosi = io.pins.gpio32;
|
|
||||||
let touch_miso = io.pins.gpio39;
|
|
||||||
let touch_clk = io.pins.gpio25;
|
|
||||||
let touch_cs = io.pins.gpio33;
|
|
||||||
|
|
||||||
// 2MHz is the MAX! DO NOT DECREASE! This is really important.
|
|
||||||
let mut touch_spi = Spi::new(peripherals.SPI3, 2.MHz(), SpiMode::Mode0, &mut clocks).with_pins(
|
|
||||||
Some(touch_clk),
|
|
||||||
Some(touch_mosi),
|
|
||||||
Some(touch_miso),
|
|
||||||
NO_PIN,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TOUCH_DELAY_STATICCELL: StaticCell<Delay> = StaticCell::new();
|
|
||||||
let mut delay = TOUCH_DELAY_STATICCELL.init(Delay);
|
|
||||||
|
|
||||||
let touch_spi =
|
|
||||||
ExclusiveDevice::new(touch_spi, Output::new(touch_cs, Level::Low), delay).unwrap();
|
|
||||||
|
|
||||||
let touch_signal = Signal::new();
|
|
||||||
static TOUCH_SIGNAL: StaticCell<Signal<CriticalSectionRawMutex, Option<Point>>> =
|
|
||||||
StaticCell::new();
|
|
||||||
let touch_signal = &*TOUCH_SIGNAL.init(touch_signal);
|
|
||||||
|
|
||||||
// let sw_int = system.software_interrupt_control.software_interrupt2;
|
|
||||||
|
|
||||||
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
|
|
||||||
// let executor = InterruptExecutor::<2>::new(sw_int);
|
|
||||||
// let executor = EXECUTOR.init(executor);
|
|
||||||
|
|
||||||
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
|
|
||||||
// let executor =
|
|
||||||
// InterruptExecutor::<2>::new(system.software_interrupt_control.software_interrupt2);
|
|
||||||
// let mut executor = EXECUTOR.init(executor);
|
|
||||||
|
|
||||||
// executor
|
|
||||||
// .start(Priority::Priority2)
|
|
||||||
spawner.spawn(touch_task(touch_irq, touch_spi, touch_signal)).unwrap();
|
|
||||||
|
|
||||||
// Display setup
|
|
||||||
let sclk = io.pins.gpio14;
|
|
||||||
let miso = io.pins.gpio12;
|
|
||||||
let mosi = io.pins.gpio13;
|
|
||||||
let cs = io.pins.gpio15;
|
|
||||||
let dc = io.pins.gpio2;
|
|
||||||
let mut backlight = Output::new(io.pins.gpio21, Level::Low);
|
|
||||||
|
|
||||||
let mut spi = Spi::new(peripherals.SPI2, 10u32.MHz(), SpiMode::Mode0, &clocks).with_pins(
|
|
||||||
Some(sclk),
|
|
||||||
Some(mosi),
|
|
||||||
Some(miso),
|
|
||||||
esp_hal::gpio::NO_PIN,
|
|
||||||
);
|
|
||||||
|
|
||||||
// static DISP_SPI_BUS: StaticCell<NoopMutex<RefCell<Spi<SPI2, FullDuplexMode>>>> = StaticCell::new();
|
|
||||||
// let spi_bus = NoopMutex::new(RefCell::new(spi));
|
|
||||||
// let spi_bus = DISP_SPI_BUS.init(spi_bus);
|
|
||||||
|
|
||||||
static SPI_DELAY_STATICCELL: StaticCell<Delay> = StaticCell::new();
|
|
||||||
let mut delay = SPI_DELAY_STATICCELL.init(Delay);
|
|
||||||
let spi = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), delay).unwrap();
|
|
||||||
|
|
||||||
let di = SPIInterface::new(spi, Output::new(dc, Level::Low));
|
|
||||||
|
|
||||||
let display = Builder::new(ILI9486Rgb565, di)
|
|
||||||
.orientation(Orientation { rotation: Rotation::Deg90, mirrored: true })
|
|
||||||
.color_order(ColorOrder::Bgr)
|
|
||||||
.invert_colors(ColorInversion::Inverted)
|
|
||||||
.init(&mut embassy_time::Delay)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut display = ProfilerDisplay::new(display);
|
|
||||||
|
|
||||||
backlight.set_high();
|
|
||||||
|
|
||||||
let size = display.bounding_box().size;
|
|
||||||
let size = slint::PhysicalSize::new(size.width as u32, size.height as u32);
|
|
||||||
|
|
||||||
let window = MinimalSoftwareWindow::new(Default::default());
|
|
||||||
slint::platform::set_platform(Box::new(CYDPlatform { window: window.clone() })).unwrap();
|
|
||||||
|
|
||||||
let ui = create_slint_app();
|
|
||||||
|
|
||||||
window.set_size(slint::PhysicalSize::new(320, 240));
|
|
||||||
|
|
||||||
let mut buffer_provider = DrawBuffer {
|
|
||||||
display,
|
|
||||||
buffer: &mut [slint::platform::software_renderer::Rgb565Pixel(0); 320],
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut last_touch = None;
|
|
||||||
|
|
||||||
let mut appdata = Rc::new(RefCell::new(AppData::new()));
|
|
||||||
|
|
||||||
let mut cl_appdata = appdata.clone();
|
|
||||||
ui.on_add_10s(move || cl_appdata.borrow_mut().add_secs(10));
|
|
||||||
let mut cl_appdata = appdata.clone();
|
|
||||||
ui.on_sub_10s(move || cl_appdata.borrow_mut().sub_secs(10));
|
|
||||||
let mut cl_appdata = appdata.clone();
|
|
||||||
ui.on_start_timer(move || cl_appdata.borrow_mut().start_timer());
|
|
||||||
let mut cl_appdata = appdata.clone();
|
|
||||||
ui.on_stop_timer(move || cl_appdata.borrow_mut().pause_timer());
|
|
||||||
let mut cl_appdata = appdata.clone();
|
|
||||||
ui.on_reset_timer(move || cl_appdata.borrow_mut().reset_timer());
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let start_time = Instant::now();
|
|
||||||
if let Some(touch) = touch_signal.try_take() {
|
|
||||||
// println!("touch: {:?}, last_touch: {:?}", touch, last_touch);
|
|
||||||
let button = PointerEventButton::Left;
|
|
||||||
let interact = match (touch, last_touch) {
|
|
||||||
(Some(point), Some(_)) => {
|
|
||||||
Some(WindowEvent::PointerMoved { position: point_to_logical_pos(point) })
|
|
||||||
}
|
|
||||||
(Some(point), None) => Some(WindowEvent::PointerPressed {
|
|
||||||
position: point_to_logical_pos(point),
|
|
||||||
button,
|
|
||||||
}),
|
|
||||||
(None, Some(point)) => Some(WindowEvent::PointerReleased {
|
|
||||||
position: point_to_logical_pos(point),
|
|
||||||
button,
|
|
||||||
}),
|
|
||||||
(None, None) => None,
|
|
||||||
};
|
|
||||||
if let Some(event) = interact {
|
|
||||||
// println!("event: {:?}", event);
|
|
||||||
window.dispatch_event(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
last_touch = touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let appdata = appdata.borrow();
|
|
||||||
let remaining = appdata.remaining();
|
|
||||||
ui.set_show_reset_timer(appdata.timer_finished());
|
|
||||||
ui.set_show_start_timer(
|
|
||||||
!appdata.timer_running() || appdata.timer_paused() && !appdata.timer_finished(),
|
|
||||||
);
|
|
||||||
ui.set_show_stop_timer(appdata.timer_running() && !appdata.timer_finished());
|
|
||||||
|
|
||||||
ui.set_timer_text(format!(
|
|
||||||
"{:02}:{:02}:{:03}",
|
|
||||||
remaining.as_secs() / 60,
|
|
||||||
remaining.as_secs() % 60,
|
|
||||||
remaining.as_millis() % 1000
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
slint::platform::update_timers_and_animations();
|
|
||||||
|
|
||||||
// let window = window.clone();
|
|
||||||
let start_draw_time = Instant::now();
|
|
||||||
window.draw_if_needed(|renderer| {
|
|
||||||
// println!("dirty reg: {:?}", renderer.render_by_line(&mut buffer_provider));
|
|
||||||
renderer.render_by_line(&mut buffer_provider);
|
|
||||||
});
|
|
||||||
|
|
||||||
let button = PointerEventButton::Left;
|
|
||||||
|
|
||||||
if window.has_active_animations() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let display = &mut buffer_provider.display;
|
|
||||||
|
|
||||||
let end_time = embassy_time::Instant::now();
|
|
||||||
let draw_time = display.get_time();
|
|
||||||
let prep_time = start_draw_time - start_time;
|
|
||||||
let proc_time = end_time - start_draw_time;
|
|
||||||
let proc_time = proc_time - min(draw_time, proc_time);
|
|
||||||
rtc.rwdt.feed();
|
|
||||||
|
|
||||||
if draw_time.as_micros() > 0 {
|
|
||||||
println!(
|
|
||||||
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
|
|
||||||
draw_time.as_millis(),
|
|
||||||
draw_time.as_micros() % 100,
|
|
||||||
prep_time.as_millis(),
|
|
||||||
prep_time.as_micros() % 100,
|
|
||||||
proc_time.as_millis(),
|
|
||||||
proc_time.as_micros() % 100,
|
|
||||||
(draw_time + prep_time + proc_time).as_millis(),
|
|
||||||
(draw_time + prep_time + proc_time).as_micros() % 100, );
|
|
||||||
}
|
|
||||||
display.reset_time();
|
|
||||||
Timer::after(Duration::from_millis(1)).await; // 60 a second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_slint_app() -> MicrowaveUI {
|
|
||||||
let ui = MicrowaveUI::new().unwrap();
|
|
||||||
|
|
||||||
/*
|
|
||||||
let ui_handle = ui.as_weak();
|
|
||||||
ui.on_request_increase_value(move || {
|
|
||||||
let ui = ui_handle.unwrap();
|
|
||||||
ui.set_counter(ui.get_counter() + 1);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
ui
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DrawBuffer<'a, DT> {
|
|
||||||
display: DT,
|
|
||||||
buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
// DI: display_interface_spi::WriteOnlyDataCommand,
|
|
||||||
E: core::fmt::Debug,
|
|
||||||
DT: DrawTarget<Color = Rgb565, Error = E>,
|
|
||||||
// RST: OutputPin<Error = core::convert::Infallible>,
|
|
||||||
> slint::platform::software_renderer::LineBufferProvider for &mut DrawBuffer<'_, DT>
|
|
||||||
{
|
|
||||||
type TargetPixel = slint::platform::software_renderer::Rgb565Pixel;
|
|
||||||
|
|
||||||
fn process_line(
|
|
||||||
&mut self,
|
|
||||||
line: usize,
|
|
||||||
range: core::ops::Range<usize>,
|
|
||||||
render_fn: impl FnOnce(&mut [slint::platform::software_renderer::Rgb565Pixel]),
|
|
||||||
) {
|
|
||||||
let buffer = &mut self.buffer[range.clone()];
|
|
||||||
|
|
||||||
render_fn(buffer);
|
|
||||||
|
|
||||||
// We send empty data just to get the device in the right window
|
|
||||||
self.display
|
|
||||||
.fill_contiguous(
|
|
||||||
&Rectangle::new(
|
|
||||||
Point::new(range.start as i32, line as i32),
|
|
||||||
Size::new((range.end - range.start) as u32, 1),
|
|
||||||
),
|
|
||||||
// range.start as u16,
|
|
||||||
// line as _,
|
|
||||||
// range.end as u16,
|
|
||||||
// line as u16,
|
|
||||||
buffer
|
|
||||||
.iter()
|
|
||||||
.map(|x| embedded_graphics_core::pixelcolor::raw::RawU16::new(x.0).into()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,490 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
use alloc::rc::Rc;
|
|
||||||
use alloc::sync::Arc;
|
|
||||||
use core::mem::MaybeUninit;
|
|
||||||
use core::{cell::RefCell, cmp::min, fmt};
|
|
||||||
use display_interface_spi::SPIInterface;
|
|
||||||
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
|
||||||
use embassy_sync::{
|
|
||||||
blocking_mutex::{raw::NoopRawMutex, NoopMutex},
|
|
||||||
signal::Signal,
|
|
||||||
};
|
|
||||||
use embassy_time::Delay;
|
|
||||||
use embassy_time::{Duration, Instant, Timer};
|
|
||||||
use embedded_graphics_core::pixelcolor::Rgb565;
|
|
||||||
use embedded_graphics_core::prelude::*;
|
|
||||||
use embedded_graphics_core::primitives::Rectangle;
|
|
||||||
use embedded_graphics_profiler_display::ProfilerDisplay;
|
|
||||||
use embedded_hal::digital::OutputPin;
|
|
||||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
|
||||||
use esp_backtrace as _;
|
|
||||||
use esp_hal::interrupt::Priority;
|
|
||||||
use esp_hal::{self, prelude::*};
|
|
||||||
use esp_hal::{
|
|
||||||
clock::ClockControl,
|
|
||||||
gpio::{GpioPin, Input, Io, Level, Output, Pull, NO_PIN},
|
|
||||||
peripherals::{Peripherals, SPI2, SPI3},
|
|
||||||
prelude::*,
|
|
||||||
rtc_cntl::Rtc,
|
|
||||||
spi::{master::Spi, FullDuplexMode, SpiMode},
|
|
||||||
system::SystemControl,
|
|
||||||
timer::timg::TimerGroup,
|
|
||||||
};
|
|
||||||
use esp_hal_embassy::InterruptExecutor;
|
|
||||||
use esp_println::println;
|
|
||||||
use mipidsi::{
|
|
||||||
models::ILI9486Rgb565,
|
|
||||||
options::{ColorInversion, ColorOrder, Orientation, Rotation},
|
|
||||||
Builder, Display,
|
|
||||||
};
|
|
||||||
use slint::platform::software_renderer::MinimalSoftwareWindow;
|
|
||||||
use slint::platform::{Platform, PointerEventButton, WindowEvent};
|
|
||||||
use slint::private_unstable_api::re_exports::LogicalPoint;
|
|
||||||
use slint::{format, LogicalPosition};
|
|
||||||
use static_cell::StaticCell;
|
|
||||||
use xpt2046::Xpt2046;
|
|
||||||
|
|
||||||
use esp_alloc as _;
|
|
||||||
|
|
||||||
// slint::slint!{ export MyUI := Window {} }
|
|
||||||
/*
|
|
||||||
slint::include_modules!();
|
|
||||||
# */
|
|
||||||
|
|
||||||
slint::include_modules!();
|
|
||||||
|
|
||||||
fn init_heap() {
|
|
||||||
const HEAP_SIZE: usize = 32 * 1024;
|
|
||||||
static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
esp_alloc::HEAP.add_region(esp_alloc::HeapRegion::new(
|
|
||||||
HEAP.as_mut_ptr() as *mut u8,
|
|
||||||
HEAP_SIZE,
|
|
||||||
esp_alloc::MemoryCapability::Internal.into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::task]
|
|
||||||
async fn touch_task(
|
|
||||||
touch_irq: GpioPin<36>,
|
|
||||||
spi: ExclusiveDevice<
|
|
||||||
Spi<'static, SPI3, FullDuplexMode>,
|
|
||||||
Output<'static, GpioPin<33>>,
|
|
||||||
&'static mut Delay,
|
|
||||||
>,
|
|
||||||
touch_signal: &'static Signal<CriticalSectionRawMutex, Option<Point>>,
|
|
||||||
) -> ! {
|
|
||||||
let mut touch_driver =
|
|
||||||
Xpt2046::new(spi, Input::new(touch_irq, Pull::Up), xpt2046::Orientation::LandscapeFlipped);
|
|
||||||
touch_driver.set_num_samples(1);
|
|
||||||
touch_driver.init(&mut embassy_time::Delay).unwrap();
|
|
||||||
|
|
||||||
esp_println::println!("touch task");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
touch_driver.run().expect("Running Touch driver failed");
|
|
||||||
if touch_driver.is_touched() {
|
|
||||||
let point = touch_driver.get_touch_point();
|
|
||||||
touch_signal.signal(Some(Point::new(point.x + 25, 240 - point.y)));
|
|
||||||
} else {
|
|
||||||
touch_signal.signal(None);
|
|
||||||
}
|
|
||||||
Timer::after(Duration::from_millis(1)).await; // 100 a second
|
|
||||||
|
|
||||||
// Your touch handling logic here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn point_to_logical_pos(point: Point) -> LogicalPosition {
|
|
||||||
LogicalPosition::new(point.x as f32, point.y as f32)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CYDPlatform {
|
|
||||||
window: Rc<slint::platform::software_renderer::MinimalSoftwareWindow>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Platform for CYDPlatform {
|
|
||||||
fn create_window_adapter(
|
|
||||||
&self,
|
|
||||||
) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
|
|
||||||
Ok(self.window.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn duration_since_start(&self) -> core::time::Duration {
|
|
||||||
embassy_time::Instant::from_millis(0).elapsed().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection DuplicatedCode
|
|
||||||
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AppData {
|
|
||||||
timer_start: Instant,
|
|
||||||
timer_set_duration: Duration,
|
|
||||||
timer_remaining_duration: Duration,
|
|
||||||
timer_running: bool,
|
|
||||||
timer_paused: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppData {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
timer_start: Instant::now(),
|
|
||||||
timer_set_duration: Duration::from_secs(10),
|
|
||||||
timer_remaining_duration: Duration::from_secs(10),
|
|
||||||
timer_running: false,
|
|
||||||
timer_paused: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_timer_duration(&mut self, duration: Duration) {
|
|
||||||
self.timer_set_duration = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_secs(&mut self, secs: u64) {
|
|
||||||
self.set_timer_duration(
|
|
||||||
self.timer_set_duration
|
|
||||||
.checked_add(Duration::from_secs(secs))
|
|
||||||
.unwrap_or(Duration::from_secs(5999))
|
|
||||||
.min(Duration::from_secs(5999)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sub_secs(&mut self, secs: u64) {
|
|
||||||
self.set_timer_duration(
|
|
||||||
self.timer_set_duration
|
|
||||||
.checked_sub(Duration::from_secs(secs))
|
|
||||||
.unwrap_or(Duration::from_secs(10))
|
|
||||||
.max(Duration::from_secs(10)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_timer(&mut self) {
|
|
||||||
if !self.timer_paused {
|
|
||||||
self.timer_remaining_duration = self.timer_set_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.timer_start = Instant::now();
|
|
||||||
self.timer_running = true;
|
|
||||||
self.timer_paused = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pause_timer(&mut self) {
|
|
||||||
self.timer_paused = true;
|
|
||||||
self.timer_remaining_duration = self
|
|
||||||
.timer_remaining_duration
|
|
||||||
.checked_sub(self.timer_start.elapsed())
|
|
||||||
.unwrap_or(Duration::from_secs(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_timer(&mut self) {
|
|
||||||
self.timer_start = Instant::now();
|
|
||||||
self.timer_paused = false;
|
|
||||||
self.timer_running = false;
|
|
||||||
self.timer_remaining_duration = self.timer_set_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remaining(&self) -> Duration {
|
|
||||||
if self.timer_stopped() {
|
|
||||||
self.timer_set_duration
|
|
||||||
} else if self.timer_paused() {
|
|
||||||
self.timer_remaining_duration
|
|
||||||
} else {
|
|
||||||
self.timer_remaining_duration
|
|
||||||
.checked_sub(self.timer_start.elapsed())
|
|
||||||
.unwrap_or(Duration::from_secs(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_stopped(&self) -> bool {
|
|
||||||
!self.timer_running
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_paused(&self) -> bool {
|
|
||||||
self.timer_running && self.timer_paused
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_running(&self) -> bool {
|
|
||||||
self.timer_running && !self.timer_paused
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timer_finished(&self) -> bool {
|
|
||||||
self.timer_running && self.remaining() == Duration::from_secs(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[main]
|
|
||||||
async fn main(spawner: Spawner) {
|
|
||||||
init_heap();
|
|
||||||
let peripherals = Peripherals::take();
|
|
||||||
let system = SystemControl::new(peripherals.SYSTEM);
|
|
||||||
let mut clocks = ClockControl::boot_defaults(system.clock_control).freeze();
|
|
||||||
|
|
||||||
let mut rtc = Rtc::new(peripherals.LPWR);
|
|
||||||
rtc.rwdt.disable();
|
|
||||||
let mut timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
|
|
||||||
timer_group0.wdt.disable();
|
|
||||||
let mut timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
|
|
||||||
timer_group1.wdt.disable();
|
|
||||||
|
|
||||||
esp_hal_embassy::init(&clocks, timer_group0.timer0);
|
|
||||||
|
|
||||||
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
|
|
||||||
|
|
||||||
let touch_irq = io.pins.gpio36;
|
|
||||||
let touch_mosi = io.pins.gpio32;
|
|
||||||
let touch_miso = io.pins.gpio39;
|
|
||||||
let touch_clk = io.pins.gpio25;
|
|
||||||
let touch_cs = io.pins.gpio33;
|
|
||||||
|
|
||||||
// 2MHz is the MAX! DO NOT DECREASE! This is really important.
|
|
||||||
let mut touch_spi = Spi::new(peripherals.SPI3, 2.MHz(), SpiMode::Mode0, &mut clocks).with_pins(
|
|
||||||
Some(touch_clk),
|
|
||||||
Some(touch_mosi),
|
|
||||||
Some(touch_miso),
|
|
||||||
NO_PIN,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TOUCH_DELAY_STATICCELL: StaticCell<Delay> = StaticCell::new();
|
|
||||||
let mut delay = TOUCH_DELAY_STATICCELL.init(Delay);
|
|
||||||
|
|
||||||
let touch_spi =
|
|
||||||
ExclusiveDevice::new(touch_spi, Output::new(touch_cs, Level::Low), delay).unwrap();
|
|
||||||
|
|
||||||
let touch_signal = Signal::new();
|
|
||||||
static TOUCH_SIGNAL: StaticCell<Signal<CriticalSectionRawMutex, Option<Point>>> =
|
|
||||||
StaticCell::new();
|
|
||||||
let touch_signal = &*TOUCH_SIGNAL.init(touch_signal);
|
|
||||||
|
|
||||||
// let sw_int = system.software_interrupt_control.software_interrupt2;
|
|
||||||
|
|
||||||
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
|
|
||||||
// let executor = InterruptExecutor::<2>::new(sw_int);
|
|
||||||
// let executor = EXECUTOR.init(executor);
|
|
||||||
|
|
||||||
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
|
|
||||||
// let executor =
|
|
||||||
// InterruptExecutor::<2>::new(system.software_interrupt_control.software_interrupt2);
|
|
||||||
// let mut executor = EXECUTOR.init(executor);
|
|
||||||
|
|
||||||
// executor
|
|
||||||
// .start(Priority::Priority2)
|
|
||||||
spawner.spawn(touch_task(touch_irq, touch_spi, touch_signal)).unwrap();
|
|
||||||
|
|
||||||
// Display setup
|
|
||||||
let sclk = io.pins.gpio14;
|
|
||||||
let miso = io.pins.gpio12;
|
|
||||||
let mosi = io.pins.gpio13;
|
|
||||||
let cs = io.pins.gpio15;
|
|
||||||
let dc = io.pins.gpio2;
|
|
||||||
let mut backlight = Output::new(io.pins.gpio21, Level::Low);
|
|
||||||
|
|
||||||
let mut spi = Spi::new(peripherals.SPI2, 10u32.MHz(), SpiMode::Mode0, &clocks).with_pins(
|
|
||||||
Some(sclk),
|
|
||||||
Some(mosi),
|
|
||||||
Some(miso),
|
|
||||||
esp_hal::gpio::NO_PIN,
|
|
||||||
);
|
|
||||||
|
|
||||||
// static DISP_SPI_BUS: StaticCell<NoopMutex<RefCell<Spi<SPI2, FullDuplexMode>>>> = StaticCell::new();
|
|
||||||
// let spi_bus = NoopMutex::new(RefCell::new(spi));
|
|
||||||
// let spi_bus = DISP_SPI_BUS.init(spi_bus);
|
|
||||||
|
|
||||||
static SPI_DELAY_STATICCELL: StaticCell<Delay> = StaticCell::new();
|
|
||||||
let mut delay = SPI_DELAY_STATICCELL.init(Delay);
|
|
||||||
let spi = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), delay).unwrap();
|
|
||||||
|
|
||||||
let di = SPIInterface::new(spi, Output::new(dc, Level::Low));
|
|
||||||
|
|
||||||
let display = Builder::new(ILI9486Rgb565, di)
|
|
||||||
.orientation(Orientation { rotation: Rotation::Deg90, mirrored: true })
|
|
||||||
.color_order(ColorOrder::Bgr)
|
|
||||||
.invert_colors(ColorInversion::Inverted)
|
|
||||||
.init(&mut embassy_time::Delay)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut display = ProfilerDisplay::new(display);
|
|
||||||
|
|
||||||
backlight.set_high();
|
|
||||||
|
|
||||||
let size = display.bounding_box().size;
|
|
||||||
let size = slint::PhysicalSize::new(size.width as u32, size.height as u32);
|
|
||||||
|
|
||||||
let window = MinimalSoftwareWindow::new(Default::default());
|
|
||||||
slint::platform::set_platform(Box::new(CYDPlatform { window: window.clone() })).unwrap();
|
|
||||||
|
|
||||||
let ui = create_slint_app();
|
|
||||||
|
|
||||||
window.set_size(slint::PhysicalSize::new(320, 240));
|
|
||||||
|
|
||||||
let mut buffer_provider = DrawBuffer {
|
|
||||||
display,
|
|
||||||
buffer: &mut [slint::platform::software_renderer::Rgb565Pixel(0); 320],
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut last_touch = None;
|
|
||||||
|
|
||||||
let mut appdata = Rc::new(RefCell::new(AppData::new()));
|
|
||||||
|
|
||||||
let mut cl_appdata = appdata.clone();
|
|
||||||
ui.on_add_10s(move || cl_appdata.borrow_mut().add_secs(10));
|
|
||||||
let mut cl_appdata = appdata.clone();
|
|
||||||
ui.on_sub_10s(move || cl_appdata.borrow_mut().sub_secs(10));
|
|
||||||
let mut cl_appdata = appdata.clone();
|
|
||||||
ui.on_start_timer(move || cl_appdata.borrow_mut().start_timer());
|
|
||||||
let mut cl_appdata = appdata.clone();
|
|
||||||
ui.on_stop_timer(move || cl_appdata.borrow_mut().pause_timer());
|
|
||||||
let mut cl_appdata = appdata.clone();
|
|
||||||
ui.on_reset_timer(move || cl_appdata.borrow_mut().reset_timer());
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let start_time = Instant::now();
|
|
||||||
if let Some(touch) = touch_signal.try_take() {
|
|
||||||
// println!("touch: {:?}, last_touch: {:?}", touch, last_touch);
|
|
||||||
let button = PointerEventButton::Left;
|
|
||||||
let interact = match (touch, last_touch) {
|
|
||||||
(Some(point), Some(_)) => {
|
|
||||||
Some(WindowEvent::PointerMoved { position: point_to_logical_pos(point) })
|
|
||||||
}
|
|
||||||
(Some(point), None) => Some(WindowEvent::PointerPressed {
|
|
||||||
position: point_to_logical_pos(point),
|
|
||||||
button,
|
|
||||||
}),
|
|
||||||
(None, Some(point)) => Some(WindowEvent::PointerReleased {
|
|
||||||
position: point_to_logical_pos(point),
|
|
||||||
button,
|
|
||||||
}),
|
|
||||||
(None, None) => None,
|
|
||||||
};
|
|
||||||
if let Some(event) = interact {
|
|
||||||
// println!("event: {:?}", event);
|
|
||||||
window.dispatch_event(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
last_touch = touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let appdata = appdata.borrow();
|
|
||||||
let remaining = appdata.remaining();
|
|
||||||
ui.set_show_reset_timer(appdata.timer_finished());
|
|
||||||
ui.set_show_start_timer(
|
|
||||||
!appdata.timer_running() || appdata.timer_paused() && !appdata.timer_finished(),
|
|
||||||
);
|
|
||||||
ui.set_show_stop_timer(appdata.timer_running() && !appdata.timer_finished());
|
|
||||||
|
|
||||||
ui.set_timer_text(format!(
|
|
||||||
"{:02}:{:02}:{:03}",
|
|
||||||
remaining.as_secs() / 60,
|
|
||||||
remaining.as_secs() % 60,
|
|
||||||
remaining.as_millis() % 1000
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
slint::platform::update_timers_and_animations();
|
|
||||||
|
|
||||||
// let window = window.clone();
|
|
||||||
let start_draw_time = Instant::now();
|
|
||||||
window.draw_if_needed(|renderer| {
|
|
||||||
// println!("dirty reg: {:?}", renderer.render_by_line(&mut buffer_provider));
|
|
||||||
renderer.render_by_line(&mut buffer_provider);
|
|
||||||
});
|
|
||||||
|
|
||||||
let button = PointerEventButton::Left;
|
|
||||||
|
|
||||||
if window.has_active_animations() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let display = &mut buffer_provider.display;
|
|
||||||
|
|
||||||
let end_time = embassy_time::Instant::now();
|
|
||||||
let draw_time = display.get_time();
|
|
||||||
let prep_time = start_draw_time - start_time;
|
|
||||||
let proc_time = end_time - start_draw_time;
|
|
||||||
let proc_time = proc_time - min(draw_time, proc_time);
|
|
||||||
rtc.rwdt.feed();
|
|
||||||
|
|
||||||
if draw_time.as_micros() > 0 {
|
|
||||||
println!(
|
|
||||||
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
|
|
||||||
draw_time.as_millis(),
|
|
||||||
draw_time.as_micros() % 100,
|
|
||||||
prep_time.as_millis(),
|
|
||||||
prep_time.as_micros() % 100,
|
|
||||||
proc_time.as_millis(),
|
|
||||||
proc_time.as_micros() % 100,
|
|
||||||
(draw_time + prep_time + proc_time).as_millis(),
|
|
||||||
(draw_time + prep_time + proc_time).as_micros() % 100, );
|
|
||||||
}
|
|
||||||
display.reset_time();
|
|
||||||
Timer::after(Duration::from_millis(1)).await; // 60 a second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_slint_app() -> TimerApp {
|
|
||||||
let ui = TimerApp::new().unwrap();
|
|
||||||
|
|
||||||
/*
|
|
||||||
let ui_handle = ui.as_weak();
|
|
||||||
ui.on_request_increase_value(move || {
|
|
||||||
let ui = ui_handle.unwrap();
|
|
||||||
ui.set_counter(ui.get_counter() + 1);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
ui
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DrawBuffer<'a, DT> {
|
|
||||||
display: DT,
|
|
||||||
buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
// DI: display_interface_spi::WriteOnlyDataCommand,
|
|
||||||
E: core::fmt::Debug,
|
|
||||||
DT: DrawTarget<Color = Rgb565, Error = E>,
|
|
||||||
// RST: OutputPin<Error = core::convert::Infallible>,
|
|
||||||
> slint::platform::software_renderer::LineBufferProvider for &mut DrawBuffer<'_, DT>
|
|
||||||
{
|
|
||||||
type TargetPixel = slint::platform::software_renderer::Rgb565Pixel;
|
|
||||||
|
|
||||||
fn process_line(
|
|
||||||
&mut self,
|
|
||||||
line: usize,
|
|
||||||
range: core::ops::Range<usize>,
|
|
||||||
render_fn: impl FnOnce(&mut [slint::platform::software_renderer::Rgb565Pixel]),
|
|
||||||
) {
|
|
||||||
let buffer = &mut self.buffer[range.clone()];
|
|
||||||
|
|
||||||
render_fn(buffer);
|
|
||||||
|
|
||||||
// We send empty data just to get the device in the right window
|
|
||||||
self.display
|
|
||||||
.fill_contiguous(
|
|
||||||
&Rectangle::new(
|
|
||||||
Point::new(range.start as i32, line as i32),
|
|
||||||
Size::new((range.end - range.start) as u32, 1),
|
|
||||||
),
|
|
||||||
// range.start as u16,
|
|
||||||
// line as _,
|
|
||||||
// range.end as u16,
|
|
||||||
// line as u16,
|
|
||||||
buffer
|
|
||||||
.iter()
|
|
||||||
.map(|x| embedded_graphics_core::pixelcolor::raw::RawU16::new(x.0).into()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,6 +5,20 @@ export component AppWindow inherits Window {
|
|||||||
height: 240px;
|
height: 240px;
|
||||||
in-out property <int> counter: 42;
|
in-out property <int> counter: 42;
|
||||||
callback request-increase-value();
|
callback request-increase-value();
|
||||||
|
|
||||||
|
VerticalBox {
|
||||||
|
alignment: start;
|
||||||
|
Text {
|
||||||
|
text: "Hello World!";
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Counter App (Slint)";
|
||||||
|
}
|
||||||
|
|
||||||
VerticalBox {
|
VerticalBox {
|
||||||
Text {
|
Text {
|
||||||
text: "Counter: \{root.counter}";
|
text: "Counter: \{root.counter}";
|
||||||
@ -17,6 +31,6 @@ export component AppWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AboutSlint { }
|
// AboutSlint { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,164 +0,0 @@
|
|||||||
import { Button, VerticalBox , AboutSlint, Slider, Switch } from "std-widgets.slint";
|
|
||||||
|
|
||||||
export struct Light {
|
|
||||||
name: string,
|
|
||||||
brightness: int,
|
|
||||||
on: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
export component LightsApp inherits Window {
|
|
||||||
width: 320px;
|
|
||||||
height: 240px;
|
|
||||||
|
|
||||||
in property <bool> show-start-timer: true;
|
|
||||||
in property <bool> show-stop-timer: false;
|
|
||||||
in property <bool> show-reset-timer: false;
|
|
||||||
|
|
||||||
property <[Light]> lights: [
|
|
||||||
{
|
|
||||||
name: "Bathroom",
|
|
||||||
brightness: 100,
|
|
||||||
on: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Bedroom",
|
|
||||||
brightness: 200,
|
|
||||||
on: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Living Room",
|
|
||||||
brightness: 50,
|
|
||||||
on: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Front Door",
|
|
||||||
brightness: 250,
|
|
||||||
on: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Porch",
|
|
||||||
brightness: 1,
|
|
||||||
on: true,
|
|
||||||
},
|
|
||||||
// {name: "Bathroom", brightness: 4, on: false, },
|
|
||||||
];
|
|
||||||
|
|
||||||
property <int> selected-light: 0;
|
|
||||||
property <bool> show-light-page: false;
|
|
||||||
|
|
||||||
in property <string> timer-text: "00:00:000";
|
|
||||||
|
|
||||||
property <image> start-timer-icon: @image-url("../img/play.png");
|
|
||||||
property <image> stop-timer-icon: @image-url("../img/pause.png");
|
|
||||||
property <image> reset-timer-icon: @image-url("../img/xmark-square.png");
|
|
||||||
|
|
||||||
property <image> light-icon: @image-url("../img/play.png");
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
alignment: start;
|
|
||||||
Text {
|
|
||||||
text: show-light-page ? lights[selected-light].name : "Light Control App (Slint)";
|
|
||||||
font-size: 14px;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !show-light-page: VerticalLayout {
|
|
||||||
alignment: center;
|
|
||||||
spacing: 5px;
|
|
||||||
padding-top: 25px;
|
|
||||||
// height: 200px;
|
|
||||||
HorizontalLayout {
|
|
||||||
alignment: LayoutAlignment.center;
|
|
||||||
spacing: 6px;
|
|
||||||
for i in 3: HorizontalLayout {
|
|
||||||
alignment: center;
|
|
||||||
height: 100px;
|
|
||||||
if i < lights.length:
|
|
||||||
Button {
|
|
||||||
width: 96px;
|
|
||||||
height: 96px;
|
|
||||||
// icon: light-icon;
|
|
||||||
text: lights[i].name;
|
|
||||||
clicked => {
|
|
||||||
selected-light = i;
|
|
||||||
show-light-page = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalLayout {
|
|
||||||
alignment: LayoutAlignment.center;
|
|
||||||
spacing: 6px;
|
|
||||||
for i in 3: HorizontalLayout {
|
|
||||||
alignment: center;
|
|
||||||
height: 100px;
|
|
||||||
if i + 3 < lights.length:
|
|
||||||
Button {
|
|
||||||
width: 96px;
|
|
||||||
height: 96px;
|
|
||||||
// icon: light-icon;
|
|
||||||
text: lights[i].name;
|
|
||||||
clicked => {
|
|
||||||
selected-light = i + 3;
|
|
||||||
show-light-page = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if show-light-page: Rectangle {
|
|
||||||
VerticalLayout {
|
|
||||||
alignment: LayoutAlignment.center;
|
|
||||||
width: 320px;
|
|
||||||
spacing: 4px;
|
|
||||||
|
|
||||||
HorizontalLayout {
|
|
||||||
alignment: LayoutAlignment.center;
|
|
||||||
Slider {
|
|
||||||
width: 250px;
|
|
||||||
maximum: 255;
|
|
||||||
value: lights[selected-light].brightness;
|
|
||||||
changed(f) => {
|
|
||||||
lights[selected-light].brightness = f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Brightness";
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalLayout {
|
|
||||||
alignment: LayoutAlignment.center;
|
|
||||||
Switch {
|
|
||||||
checked: lights[selected-light].on;
|
|
||||||
toggled => {
|
|
||||||
lights[selected-light].on = !lights[selected-light].on
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "On / Off";
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
x: 10px;
|
|
||||||
y: 10px;
|
|
||||||
text: "<";
|
|
||||||
clicked => {
|
|
||||||
show-light-page = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
import { Button, VerticalBox, HorizontalBox, AboutSlint } from "std-widgets.slint";
|
|
||||||
|
|
||||||
export component MicrowaveUI inherits Window {
|
|
||||||
width: 320px;
|
|
||||||
height: 240px;
|
|
||||||
|
|
||||||
property <[string]> wattages: ["180W", "220W", "360W", "480W", "620W", "800W"];
|
|
||||||
property <int> selected-wattage: 0;
|
|
||||||
|
|
||||||
callback add-10s();
|
|
||||||
callback sub-10s();
|
|
||||||
callback start-timer();
|
|
||||||
callback stop-timer();
|
|
||||||
callback reset-timer();
|
|
||||||
|
|
||||||
in property <bool> show-start-timer: true;
|
|
||||||
in property <bool> show-stop-timer: false;
|
|
||||||
in property <bool> show-reset-timer: false;
|
|
||||||
|
|
||||||
in property <string> timer-text: "00:00:000";
|
|
||||||
|
|
||||||
property <image> start-timer-icon: @image-url("../img/play.png");
|
|
||||||
property <image> stop-timer-icon: @image-url("../img/pause.png");
|
|
||||||
property <image> reset-timer-icon: @image-url("../img/xmark-square.png");
|
|
||||||
|
|
||||||
function multifunction-button-pressed() {
|
|
||||||
if (show-start-timer) {
|
|
||||||
start-timer();
|
|
||||||
}
|
|
||||||
if (show-stop-timer) {
|
|
||||||
stop-timer();
|
|
||||||
}
|
|
||||||
if (show-reset-timer) {
|
|
||||||
reset-timer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function multifunction-button-icon() -> image {
|
|
||||||
if (show-start-timer) {
|
|
||||||
return start-timer-icon;
|
|
||||||
}
|
|
||||||
if (show-stop-timer) {
|
|
||||||
return stop-timer-icon;
|
|
||||||
}
|
|
||||||
if (show-reset-timer) {
|
|
||||||
return reset-timer-icon;
|
|
||||||
}
|
|
||||||
return start-timer-icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
alignment: start;
|
|
||||||
Text {
|
|
||||||
text: " Microwave UI (Slint)";
|
|
||||||
font-size: 14px;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalBox {
|
|
||||||
VerticalBox {
|
|
||||||
padding-top: 15px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
Button {
|
|
||||||
enabled: (selected-wattage < wattages.length - 1) && show-start-timer;
|
|
||||||
text: "+";
|
|
||||||
clicked => {
|
|
||||||
selected-wattage += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
font-size: 24px;
|
|
||||||
text: wattages[selected-wattage];
|
|
||||||
horizontal-alignment: center;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
enabled: !(selected-wattage <= 0) && show-start-timer;
|
|
||||||
text: "-";
|
|
||||||
clicked => {
|
|
||||||
selected-wattage -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
alignment: center;
|
|
||||||
Text {
|
|
||||||
text: timer-text;
|
|
||||||
font-size: 24px;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
height: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalLayout {
|
|
||||||
alignment: center;
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "+10s";
|
|
||||||
enabled: show-start-timer;
|
|
||||||
clicked => {
|
|
||||||
add-10s();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Set Timer";
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "-10s";
|
|
||||||
enabled: show-start-timer;
|
|
||||||
clicked => {
|
|
||||||
sub-10s();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalLayout {
|
|
||||||
alignment: center;
|
|
||||||
spacing: 8px;
|
|
||||||
Button {
|
|
||||||
icon: @image-url("../img/restart.png");
|
|
||||||
clicked => {
|
|
||||||
reset-timer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
icon: multifunction-button-icon();
|
|
||||||
// visible: root.show_start_timer;
|
|
||||||
clicked => {
|
|
||||||
multifunction-button-pressed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
import { Button, VerticalBox , AboutSlint } from "std-widgets.slint";
|
|
||||||
|
|
||||||
export component TimerApp inherits Window {
|
|
||||||
width: 320px;
|
|
||||||
height: 240px;
|
|
||||||
|
|
||||||
callback add-10s();
|
|
||||||
callback sub-10s();
|
|
||||||
callback start-timer();
|
|
||||||
callback stop-timer();
|
|
||||||
callback reset-timer();
|
|
||||||
|
|
||||||
in property <bool> show-start-timer: true;
|
|
||||||
in property <bool> show-stop-timer: false;
|
|
||||||
in property <bool> show-reset-timer: false;
|
|
||||||
|
|
||||||
in property <string> timer-text: "00:00:000";
|
|
||||||
|
|
||||||
property <image> start-timer-icon: @image-url("../img/play.png");
|
|
||||||
property <image> stop-timer-icon: @image-url("../img/pause.png");
|
|
||||||
property <image> reset-timer-icon: @image-url("../img/xmark-square.png");
|
|
||||||
|
|
||||||
function multifunction-button-pressed() {
|
|
||||||
if (show-start-timer) {
|
|
||||||
start-timer();
|
|
||||||
}
|
|
||||||
if (show-stop-timer) {
|
|
||||||
stop-timer();
|
|
||||||
}
|
|
||||||
if (show-reset-timer) {
|
|
||||||
reset-timer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function multifunction-button-icon() -> image {
|
|
||||||
if (show-start-timer) {
|
|
||||||
return start-timer-icon;
|
|
||||||
}
|
|
||||||
if (show-stop-timer) {
|
|
||||||
return stop-timer-icon;
|
|
||||||
}
|
|
||||||
if (show-reset-timer) {
|
|
||||||
return reset-timer-icon;
|
|
||||||
}
|
|
||||||
return start-timer-icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
alignment: start;
|
|
||||||
Text {
|
|
||||||
text: "Timer App (Slint)";
|
|
||||||
font-size: 14px;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
alignment: center;
|
|
||||||
Text {
|
|
||||||
text: timer-text;
|
|
||||||
font-size: 24px;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
height: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalLayout {
|
|
||||||
alignment: center;
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "+10s";
|
|
||||||
enabled: show-start-timer;
|
|
||||||
clicked => {
|
|
||||||
add-10s();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Set Timer";
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "-10s";
|
|
||||||
enabled: show-start-timer;
|
|
||||||
clicked => {
|
|
||||||
sub-10s();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalLayout {
|
|
||||||
alignment: center;
|
|
||||||
spacing: 8px;
|
|
||||||
Button {
|
|
||||||
icon: @image-url("../img/restart.png");
|
|
||||||
clicked => {
|
|
||||||
reset-timer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
icon: multifunction-button-icon();
|
|
||||||
// visible: root.show_start_timer;
|
|
||||||
clicked => {
|
|
||||||
multifunction-button-pressed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user