116 Commits

Author SHA1 Message Date
dfad7cfb76 version bump
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 4m42s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 4m30s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 4m24s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 4m30s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-27 00:14:44 +01:00
5d78572481 send screen names to ESP Insights
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-27 00:13:53 +01:00
9e9fb15f86 fix I2C timeout crash
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-27 00:10:51 +01:00
e7af663bc3 send button event to ESP Insights
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-27 00:10:37 +01:00
e81fc62645 multiple wifi connection for login check
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Successful in 6m28s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Successful in 6m15s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 3m13s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 3m27s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-26 00:13:10 +01:00
aa10eb55f4 fix imports
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Successful in 3m36s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Successful in 3m42s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 3m9s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 2m59s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-25 00:24:09 +01:00
c259a3f2c8 disable BLE scan at startup
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 3m18s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32c6, release-v5.5) (pull_request) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.4) (pull_request) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.5) (pull_request) Has been cancelled
ESP-IDF Build / build (esp32c6, release-v5.4) (pull_request) Has been cancelled
we will refactor it to Thread later

Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-24 23:49:21 +01:00
fa05783fb9 fix crash
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-24 23:48:53 +01:00
8ba2a5be1d show NTP time while day/night - and "simulated" while simulation
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-24 23:46:05 +01:00
016734a4db MK 2.1 of the System Control (ESP32-S3)
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 3m44s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 3m31s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 3m1s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 3m3s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-23 21:50:19 +01:00
e16cfbd03c code cleanup
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Has been cancelled
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Has been cancelled
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 20:41:55 +01:00
387b9d4c65 remove ESP-IDF v6
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 3m21s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 3m35s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Successful in 3m33s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Successful in 3m49s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 15:58:11 +01:00
c27759cd75 disable external devices
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32c6, release-v6.0) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v6.0) (push) Has been cancelled
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Has been cancelled
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 15:54:47 +01:00
d68667d377 add external package
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 3m23s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 3m29s
ESP-IDF Build / build (esp32c6, release-v6.0) (push) Failing after 4m8s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Successful in 3m46s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Successful in 4m5s
ESP-IDF Build / build (esp32s3, release-v6.0) (push) Failing after 1m24s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 15:04:45 +01:00
926e3d1aef add missing include
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 3m48s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32c6, release-v6.0) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v6.0) (push) Has been cancelled
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 14:59:36 +01:00
690e22817e use defined release branches
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 3m21s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 3m46s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v6.0) (push) Has been cancelled
ESP-IDF Build / build (esp32c6, release-v6.0) (push) Has been cancelled
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 14:50:59 +01:00
4d1b3cc766 external MQTT component
Some checks failed
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 1m14s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, latest) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Has been cancelled
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 14:47:07 +01:00
e9b8cdde8b manual git checkout
Some checks failed
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 1m23s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, latest) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Has been cancelled
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 14:42:02 +01:00
96a7be473c remove container in container
Some checks failed
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 4s
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 4s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 4s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 4s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 4s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 4s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 14:40:28 +01:00
6f9a0337bb more optimisation
Some checks failed
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 12s
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 12s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 12s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 12s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 12s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 12s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 14:36:13 +01:00
aac244705d esp-idf build
Some checks failed
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 12s
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 12s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 12s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 13s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 14:34:03 +01:00
a284cbe35e better path
Some checks failed
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 14s
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 13s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Has been cancelled
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 14:32:51 +01:00
15fb917aaf debugging action
Some checks failed
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 14s
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 14s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 14s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 14:31:13 +01:00
73ed926059 fix analytics key file name
Some checks failed
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 14s
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 14s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 13s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 13s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-12-20 14:27:31 +01:00
c586de76a2 move actions to gitea only
Some checks failed
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 45s
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 41s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 41s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 42s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 41s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 50s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-10-01 17:04:38 +02:00
4a2d73c686 extract API key
Some checks failed
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 57s
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 49s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 42s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 43s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Has been cancelled
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Has been cancelled
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-10-01 17:01:09 +02:00
f9010d5491 fix github action
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-10-01 16:52:53 +02:00
0f7686d5a5 add simulation to UI
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-30 21:53:40 +02:00
9ae568c2f4 new light schema file
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-30 20:47:08 +02:00
99aa30c8e5 optimise simulation handling
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-30 20:16:10 +02:00
08b0e04584 implement new light mode (off/day/night/simulation)
missing:
- fully connect it to the ui
- setup duration in light settings

Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-29 23:15:06 +02:00
dc66484f5e switch from global net labels to local
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-26 23:02:26 +02:00
f0e8ea5aaa autogeneration of wiki files (schematics and PCB screenshots)
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-26 23:01:57 +02:00
845fdd306e day/night cycle on LED 1 from CSV file
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-26 23:01:26 +02:00
f2e2fe4078 add sponsoring
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-20 20:24:19 +02:00
273f9491f8 some optimizations regarding LED color
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-20 20:21:42 +02:00
2f03713a4e latest hardware lib
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-20 15:23:09 +02:00
9df2b28eb3 optimize some log entries
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-14 00:43:02 +02:00
e17e2504d7 new color schema and intervals for status led
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-14 00:33:19 +02:00
5d553c0fbb fix compile error
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-13 20:59:08 +02:00
59f2d3f83a new partition table
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-13 20:42:10 +02:00
6efbe91747 optimize init sequenze
show SplashScreen while connecting

Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-13 20:42:01 +02:00
a312625085 add other esp insights log types
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-12 23:30:10 +02:00
6b7ef5b573 optimize ESP Insights handling
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-12 11:21:41 +02:00
4ac3f93f34 code cleanup
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-11 22:49:17 +02:00
57047044d6 connect via wifi and enable esp insights
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-11 22:39:00 +02:00
1dd1a30ea8 more kconfig options
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-11 11:44:57 +02:00
37c75f3785 remove desktop build (for now)
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-11 10:51:57 +02:00
9612e53ca4 update submodule link
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-11 10:41:38 +02:00
63b8f2ac16 partition change (for future FOTAs)
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-11 10:39:42 +02:00
b05ffb544c add i2c check for display
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-09-11 10:39:12 +02:00
597bfeee28 scan ble advertise packages
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-08-30 01:13:45 +02:00
5427570c14 folder cleanup
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-08-23 16:35:09 +02:00
6e84d57b77 starting status WLEDs behavior
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-08-23 16:02:21 +02:00
d8b1718069 fix link for kicad library
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-08-23 14:03:44 +02:00
7100f59c7f fix action
Some checks failed
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 24s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 20s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 20s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-08-21 23:25:23 +02:00
f7e8c86bbd update submodule link
Some checks failed
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 18s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 14s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 14s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-08-21 23:18:50 +02:00
1ead66520b add new ClockScreenSaver
Some checks failed
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 22s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 18s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 21s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-08-21 23:16:03 +02:00
f199f0d781 added README.md
Some checks failed
ESP-IDF Build / build (esp32c3, latest) (push) Failing after 22s
ESP-IDF Build / build (esp32c3, release-v5.4) (push) Failing after 18s
ESP-IDF Build / build (esp32c3, release-v5.5) (push) Failing after 19s
ESP-IDF Build / build (esp32h2, latest) (push) Failing after 18s
ESP-IDF Build / build (esp32h2, release-v5.4) (push) Failing after 19s
ESP-IDF Build / build (esp32h2, release-v5.5) (push) Failing after 18s
ESP-IDF Build / build (esp32p4, latest) (push) Failing after 19s
ESP-IDF Build / build (esp32p4, release-v5.4) (push) Failing after 19s
ESP-IDF Build / build (esp32p4, release-v5.5) (push) Failing after 19s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 18s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 18s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 19s
Signed-off-by: mars3142 <developer@mars3142.org>
2025-08-20 10:58:40 +02:00
e4476309fd combine firmware and hardware repositories
Some checks failed
ESP-IDF Build / build (esp32c3, latest) (push) Failing after 23s
ESP-IDF Build / build (esp32c3, release-v5.4) (push) Failing after 19s
ESP-IDF Build / build (esp32c3, release-v5.5) (push) Failing after 19s
ESP-IDF Build / build (esp32h2, latest) (push) Failing after 19s
ESP-IDF Build / build (esp32h2, release-v5.4) (push) Failing after 18s
ESP-IDF Build / build (esp32h2, release-v5.5) (push) Failing after 19s
ESP-IDF Build / build (esp32p4, latest) (push) Failing after 19s
ESP-IDF Build / build (esp32p4, release-v5.4) (push) Failing after 18s
ESP-IDF Build / build (esp32p4, release-v5.5) (push) Failing after 18s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 19s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 19s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 18s
2025-08-20 10:45:11 +02:00
Peter Siegmund
b792ebfacd move into hardware subfolder 2025-08-20 10:42:36 +02:00
5a08c2e09d move into firmware subfolder
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-08-20 10:27:03 +02:00
d316bb9f2c updated esp-idf version in github action
Some checks failed
ESP-IDF Build / build (esp32c3, latest) (push) Failing after 41s
ESP-IDF Build / build (esp32c3, release-v5.4) (push) Failing after 18s
ESP-IDF Build / build (esp32c3, release-v5.5) (push) Failing after 18s
ESP-IDF Build / build (esp32h2, latest) (push) Failing after 16s
ESP-IDF Build / build (esp32h2, release-v5.4) (push) Failing after 16s
ESP-IDF Build / build (esp32h2, release-v5.5) (push) Failing after 16s
ESP-IDF Build / build (esp32p4, latest) (push) Failing after 16s
ESP-IDF Build / build (esp32p4, release-v5.4) (push) Failing after 16s
ESP-IDF Build / build (esp32p4, release-v5.5) (push) Failing after 16s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 16s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 15s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 16s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-07-26 14:24:09 +02:00
2898009516 update sheet comments
Signed-off-by: Peter Siegmund <mars3142@users.noreply.github.com>
2025-07-22 23:00:53 +02:00
c9ea1096a7 FIX: WLED connection
Signed-off-by: Peter Siegmund <mars3142@users.noreply.github.com>
2025-07-18 22:34:32 +02:00
ca996d1c13 use button component from espressif
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-28 00:12:57 +02:00
f97f67422a better spawn delay for ESP32
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-26 21:12:08 +02:00
ab14765750 auto load and dependency fix
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-25 07:47:23 +02:00
00cfecf13a increase timeout time for screensaver
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-25 07:19:23 +02:00
6260c7e62c new Screensaver
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-24 21:24:55 +02:00
0c8c831eea new persistence manager component
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-23 23:10:42 +02:00
c6f0c4572d new native matrix implementation
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-23 00:10:32 +02:00
0b65ac198f starting with led-manager as esp32 and native component
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-21 23:55:58 +02:00
5805d9ea14 move PersistenceManager into platform project
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-20 23:45:01 +02:00
72fd0bdf1a checking persistence on desktop and esp32
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-20 23:29:00 +02:00
26723db8d8 code cleanup
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-20 21:35:09 +02:00
54080bfd9d more comments
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-20 21:03:29 +02:00
a0fe4ba538 code optimization
- lifecycle functions for widgets
- persistence functions

Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-20 20:53:13 +02:00
1a912d31c4 implement ScreenSaver in simulator
set ruth to stub only, because of cross compile error

Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-20 10:49:34 +02:00
d3dd96c93a add screensaver and optimize performance
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-19 23:21:43 +02:00
3ac9565007 reduce startup time on ESP32
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-18 00:12:59 +02:00
df550540b8 dummy pins for compile checks
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-16 13:00:20 +02:00
6645263ba2 showing the menu on the MCU display
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-16 00:29:23 +02:00
d97f6c48c1 latest u8g2 package
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-15 23:03:40 +02:00
6cf56ba468 latest ImGui
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-15 22:28:39 +02:00
11c459bad8 added check for ESP32H2
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-15 17:33:59 +02:00
1c7942384b starting MCU development
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-15 17:29:22 +02:00
9806d4c9ae more callback functions
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-15 17:17:26 +02:00
d9a0dfb8bd scrollbar optimization
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-15 14:42:35 +02:00
ea0208083f more interaction
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-15 09:19:14 +02:00
5464bacc52 starting convert for ESP32
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-15 01:43:56 +02:00
2191174681 implement left/right with callback
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-15 00:49:30 +02:00
52a49363eb optimized and commented code
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-14 19:02:51 +02:00
4aa3e2cbeb pin u8g2 to specific version for ESP-IDF
see https://github.com/olikraus/u8g2/issues/2658

Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-12 21:00:31 +02:00
280ad59ff8 only latest stable ESP-IDF and "latest"
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-12 20:32:39 +02:00
e487b8357a restructure folder
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-12 20:21:53 +02:00
5e2456f4b8 some more widgets
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-12 20:15:56 +02:00
f875f7832f implement onTap and remove memory leaks
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-09 22:36:56 +02:00
d7fbbcc869 delete codeQL
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-09 00:27:43 +02:00
266114d046 handle mouse events
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-06-09 00:17:54 +02:00
60fccfeccc Create codeql.yml 2025-04-18 15:45:02 +02:00
5a3a433d17 add missing include
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-18 15:22:11 +02:00
ecd26bf2ee add action permissions
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-18 15:21:54 +02:00
a56435c49d fixed u8g2 checkout for desktop (hopefully)
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-18 15:05:47 +02:00
e5e602d1fc custom bootloader code and starting app as task
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-12 01:28:08 +02:00
fa5b4da0f5 fixing format error
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-12 00:27:02 +02:00
75169956ea more gitignore
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-12 00:20:46 +02:00
6e22bee95c more modules for the MCU code
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-11 23:54:25 +02:00
5b82cd8189 desktop code refactoring
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-10 23:31:16 +02:00
b3bf03999b combine with desktop project
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-10 23:20:19 +02:00
b6fb4eb65c update README
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-10 22:57:57 +02:00
eae8b78c60 project file splitting
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-10 22:31:37 +02:00
858c45fdcc reduce default log level
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-10 22:31:28 +02:00
e94aeb7942 add cmake to .gitignore
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-10 21:48:52 +02:00
48b296b900 removed lvgl references
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-10 21:48:39 +02:00
7a10803af5 initial commit
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2025-04-10 21:38:59 +02:00
115 changed files with 14864 additions and 4937 deletions

View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,52 @@
name: ESP-IDF Build
on:
push:
paths:
- "firmware/**"
- ".gitea/workflows/esp32_build.yml"
pull_request:
merge_group:
workflow_dispatch:
schedule:
- cron: "0 5 * * 3"
permissions:
contents: read
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
strategy:
matrix:
idf_ver: [release-v5.4, release-v5.5]
idf_target: [esp32c6, esp32s3]
runs-on: ubuntu-latest
container: espressif/idf:${{ matrix.idf_ver }}
timeout-minutes: 30
steps:
- name: Checkout repo
run: |
git config --global --add safe.directory "*"
git clone --recurse-submodules ${{ github.server_url }}/${{ github.repository }}.git .
git checkout ${{ github.sha }}
- name: Extract API key
env:
INSIGHTS_API_KEY: ${{ secrets.INSIGHTS_API_KEY }}
run: |
echo $INSIGHTS_API_KEY > firmware/components/analytics/insights_auth_key.txt
- name: ESP-IDF build
env:
IDF_TARGET: ${{ matrix.idf_target }}
run: |
cd firmware
. $IDF_PATH/export.sh
idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release" build

34
.gitignore vendored
View File

@@ -1,8 +1,26 @@
*-backups/
*_old
jlcpcb/
.DS_Store
**/*.bak
fp-info-cache*
*.lck
datasheets/
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
### VisualStudioCode ###
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode
**/*_back.png
**/*_front.png
**/*_schematic*.png
**/wiki/*

4
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "library"]
path = library
url = git@github.com:mars3142/kicad_library.git
path = hardware/library
url = https://git.mars3142.dev/mars3142/kicad_library.git

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
warnemuende.lighthouses@mars3142.org.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

661
LICENSE Normal file
View File

@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.

View File

@@ -1,671 +0,0 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.762,
"height": 1.524,
"width": 1.524
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_near_hole": "error",
"hole_to_hole": "error",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 1,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.005,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0,
0.25,
0.5
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"conflicting_netclasses": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "error",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [
"wemos"
],
"pinned_symbol_libs": [
"wemos",
"easyeda2kicad"
]
},
"meta": {
"filename": "Maerklin System Control.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.25,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.55,
"diff_pair_width": 0.5,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "+5V",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 0,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.5,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "DOUT",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 1,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.3,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": [
{
"netclass": "+5V",
"pattern": "*5V"
},
{
"netclass": "DOUT",
"pattern": "DOUT"
}
]
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "../../../../../../../../Applications/KiCad/",
"plot": "Production/",
"pos_files": "Production/",
"specctra_dsn": "",
"step": "Maerklin System Control.step",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": false,
"name": "Grouped By Value",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"3f36a0ad-b1c0-412e-a225-00e05b89b09f",
"Root"
]
],
"text_variables": {}
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# System Control
See the [wiki](https://wiki.mars3142.dev/project/maerklin/system_control/start) for more information.

2
firmware/.clang-format Normal file
View File

@@ -0,0 +1,2 @@
---
BasedOnStyle: Microsoft

14
firmware/.clang-tidy Normal file
View File

@@ -0,0 +1,14 @@
Checks: >
-*,
bugprone-*,
misc-include-cleaner,
modernize-use-nullptr,
readability-identifier-naming
WarningsAsErrors: ''
HeaderFilterRegex: '.*'
CheckOptions:
- key: misc-include-cleaner.IgnoreHeaders
value: ''

View File

@@ -0,0 +1,13 @@
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]

View File

@@ -0,0 +1,21 @@
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.espIdfPath": "/opt/esp/idf",
"idf.toolsPath": "/opt/esp",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}

42
firmware/.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
# esp-idf
build/
build-release/
managed_components/
sdkconfig
sdkconfig.old
dependencies.lock
config.env
kconfigs_projbuild.in
kconfigs.in
config/
# cmake
cmake-build-debug
CMakeCache.txt
cmake_install.cmake
install_manifest.txt
CMakeFiles/
CTestTestfile.cmake
CMakeScripts
compile_commands.json
# They ignored Idea (Webstorm) completely
.idea/
.cache/
# However, they kept the following 4 files version controlled
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
__pycache__
_deps/
.cmake/
.ninja_*
bin/
components/**/*.a
*.ninja
Testing/*
**/insights_auth_key.txt

15
firmware/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
},
{
"type": "espidf",
"name": "Launch",
"request": "launch"
}
]
}

4
firmware/CMakeLists.txt Executable file
View File

@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(system_control)

661
firmware/LICENSE Normal file
View File

@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.

2
firmware/Makefile Normal file
View File

@@ -0,0 +1,2 @@
release:
idf.py -B build-release -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release" fullclean build

15
firmware/README.md Executable file
View File

@@ -0,0 +1,15 @@
## Systen Control
### ESP32-S3 (folder: main)
This is an implementation of my custom system control project (custom pcb with Lolin ESP32-S3 Mini) and LED strip.
The build process is straight forward with ESP-IDF. We used version 5.4 while development and the github actions tried to compile for multiple ESP-IDF versions, so we are safe.
### Desktop (folder: src)
It's included also a desktop application (with SDL3), so you can test the project without any MCU.
### Global Information
The projects can be generated from the root, because here is the starting CMakeLists.txt file.

View File

@@ -0,0 +1,10 @@
idf_component_register(SRCS
src/analytics.c
INCLUDE_DIRS "include"
REQUIRES
rmaker_common
esp_insights
rmaker_common
)
target_add_binary_data(${COMPONENT_TARGET} "insights_auth_key.txt" TEXT)

View File

@@ -0,0 +1,7 @@
#pragma once
#include <sys/cdefs.h>
__BEGIN_DECLS
void analytics_init(void);
__END_DECLS

View File

@@ -0,0 +1,20 @@
#include "analytics.h"
#include "esp_insights.h"
#include "esp_rmaker_utils.h"
extern const char insights_auth_key_start[] asm("_binary_insights_auth_key_txt_start");
extern const char insights_auth_key_end[] asm("_binary_insights_auth_key_txt_end");
void analytics_init(void)
{
esp_insights_config_t config = {
.log_type = ESP_DIAG_LOG_TYPE_ERROR | ESP_DIAG_LOG_TYPE_EVENT | ESP_DIAG_LOG_TYPE_WARNING,
.node_id = NULL,
.auth_key = insights_auth_key_start,
.alloc_ext_ram = true,
};
esp_insights_init(&config);
esp_rmaker_time_sync_init(NULL);
}

View File

@@ -0,0 +1,13 @@
idf_component_register(SRCS
src/ble/ble_connection.c
src/ble/ble_scanner.c
src/ble_manager.c
src/wifi_manager.c
INCLUDE_DIRS "include"
REQUIRES
bt
driver
nvs_flash
esp_insights
led-manager
)

View File

@@ -0,0 +1,7 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=4.1.0'
espressif/ble_conn_mgr: '^0.1.3'

View File

@@ -0,0 +1,15 @@
#pragma once
#include "ble_device.h"
#ifdef __cplusplus
extern "C"
{
#endif
void ble_connect(device_info_t *device);
void ble_clear_bonds(void);
void ble_clear_bond(const ble_addr_t *addr);
void read_characteristic(uint16_t char_val_handle);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,24 @@
#pragma once
#include <host/ble_hs.h>
#include <host/util/util.h>
#include <nimble/nimble_port.h>
#include <nimble/nimble_port_freertos.h>
#include <stdint.h>
// Structure to cache device data
typedef struct
{
ble_addr_t addr;
uint16_t manufacturer_id;
uint8_t manufacturer_data[31]; // Max. length of manufacturer data
uint8_t manufacturer_data_len;
char name[32];
uint16_t service_uuids_16[10]; // Up to 10 16-bit Service UUIDs
uint8_t service_uuids_16_count;
ble_uuid128_t service_uuids_128[5]; // Up to 5 128-bit Service UUIDs
uint8_t service_uuids_128_count;
bool has_manufacturer;
bool has_name;
int8_t rssi;
} device_info_t;

View File

@@ -0,0 +1,14 @@
#pragma once
#include "ble_device.h"
#ifdef __cplusplus
extern "C"
{
#endif
void start_scan(void);
int get_device_count(void);
device_info_t *get_device(int index);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
void ble_manager_task(void *pvParameter);
void ble_connect_to_device(int index);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,7 @@
#pragma once
#include <sys/cdefs.h>
__BEGIN_DECLS
void wifi_manager_init(void);
__END_DECLS

View File

@@ -0,0 +1,364 @@
#include "ble/ble_connection.h"
#include <esp_log.h>
#include <host/ble_gap.h>
#include <host/ble_hs.h>
#include <host/ble_sm.h>
#include <host/ble_store.h>
#include <string.h>
static const char *TAG = "ble_connection";
static uint16_t g_conn_handle;
static uint16_t g_char_val_handle; // Handle der Characteristic, die du lesen willst
static bool g_bonding_in_progress = false;
const char *ble_error_to_string(int status)
{
switch (status)
{
case 0:
return "Success";
case BLE_HS_EDONE:
return "Operation complete";
case BLE_HS_EALREADY:
return "Operation already in progress";
case BLE_HS_EINVAL:
return "Invalid argument";
case BLE_HS_EMSGSIZE:
return "Message too large";
case BLE_HS_ENOENT:
return "No entry found";
case BLE_HS_ENOMEM:
return "Out of memory";
case BLE_HS_ENOTCONN:
return "Not connected";
case BLE_HS_ENOTSUP:
return "Not supported";
case BLE_HS_EAPP:
return "Application error";
case BLE_HS_EBADDATA:
return "Bad data";
case BLE_HS_EOS:
return "OS error";
case BLE_HS_ECONTROLLER:
return "Controller error";
case BLE_HS_ETIMEOUT:
return "Timeout";
case BLE_HS_EBUSY:
return "Busy";
case BLE_HS_EREJECT:
return "Rejected";
case BLE_HS_EUNKNOWN:
return "Unknown error";
case BLE_HS_EROLE:
return "Role error";
case BLE_HS_ETIMEOUT_HCI:
return "HCI timeout";
case BLE_HS_ENOMEM_EVT:
return "No memory for event";
case BLE_HS_ENOADDR:
return "No address";
case BLE_HS_ENOTSYNCED:
return "Not synchronized";
case BLE_HS_EAUTHEN:
return "Authentication failed";
case BLE_HS_EAUTHOR:
return "Authorization failed";
case BLE_HS_EENCRYPT:
return "Encryption failed";
case BLE_HS_EENCRYPT_KEY_SZ:
return "Encryption key size";
case BLE_HS_ESTORE_CAP:
return "Storage capacity exceeded";
case BLE_HS_ESTORE_FAIL:
return "Storage failure";
default:
// ATT-Fehler prüfen
if ((status & 0x100) == 0x100)
{
return "ATT error";
}
return "Unknown error";
}
}
static void ble_sm_event_cb(struct ble_gap_event *event, void *arg)
{
switch (event->type)
{
case BLE_GAP_EVENT_PASSKEY_ACTION:
ESP_LOGI(TAG, "Passkey action required");
// Hier können Sie Passkey-Aktionen implementieren
// z.B. Display passkey, Input passkey, etc.
break;
case BLE_GAP_EVENT_ENC_CHANGE:
ESP_LOGI(TAG, "Encryption change: status=%d", event->enc_change.status);
if (event->enc_change.status == 0)
{
ESP_LOGI(TAG, "Encryption established successfully");
g_bonding_in_progress = false;
}
break;
case BLE_GAP_EVENT_REPEAT_PAIRING:
ESP_LOGI(TAG, "Repeat pairing");
break;
default:
break;
}
}
static bool is_device_bonded(const ble_addr_t *addr)
{
struct ble_store_value_sec sec_value;
struct ble_store_key_sec sec_key = {0};
sec_key.peer_addr = *addr;
sec_key.idx = 0;
int rc = ble_store_read_peer_sec(&sec_key, &sec_value);
return (rc == 0);
}
static void initiate_bonding(uint16_t conn_handle)
{
if (!g_bonding_in_progress)
{
g_bonding_in_progress = true;
ESP_LOGI(TAG, "Initiating bonding for connection %d", conn_handle);
// Starte Security/Bonding Prozess
int rc = ble_gap_security_initiate(conn_handle);
if (rc != 0)
{
ESP_LOGE(TAG, "Failed to initiate security: %s", ble_error_to_string(rc));
g_bonding_in_progress = false;
}
}
}
static int gattc_svcs_callback(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_svc *service, void *arg)
{
if (error->status != 0)
{
ESP_LOGE(TAG, "Error discovering service: %s", ble_error_to_string(error->status));
return 0;
}
char uuid_str[37]; // Maximale Länge für 128-bit UUID
ble_uuid_to_str(&service->uuid.u, uuid_str);
ESP_LOGI(TAG, "Discovered service: %s", uuid_str);
return 0;
}
static int gattc_char_callback(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *chr,
void *arg)
{
if (error->status != 0)
{
ESP_LOGE(TAG, "Error discovering characteristic: %d", error->status);
return 0;
}
g_char_val_handle = chr->val_handle;
read_characteristic(chr->val_handle);
return 0;
}
// Callback für GATT-Events
static int gattc_event_callback(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_svc *service, void *arg)
{
if (error->status != 0)
{
ESP_LOGE(TAG, "Error discovering service: %d", error->status);
return 0;
}
ble_gattc_disc_all_svcs(conn_handle, gattc_svcs_callback, NULL);
// ble_gattc_disc_all_chrs(conn_handle, service->start_handle, service->end_handle, gattc_char_callback, NULL);
return 0;
}
static int gattc_read_callback(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr,
void *arg)
{
if (error->status == 0)
{
ESP_LOGI(TAG, "Wert gelesen %d, Länge: %d", attr->handle, attr->om->om_len);
ESP_LOG_BUFFER_HEX("READ_DATA", attr->om->om_data, attr->om->om_len);
}
else
{
ESP_LOGE(TAG, "Lesefehler, Status: %d", error->status);
}
return 0;
}
static int ble_gap_event_handler(struct ble_gap_event *event, void *arg)
{
device_info_t *device = (device_info_t *)arg;
switch (event->type)
{
case BLE_GAP_EVENT_CONNECT:
if (event->connect.status == 0)
{
g_conn_handle = event->connect.conn_handle;
ESP_LOGI(TAG, "Connected; conn_handle=%d", g_conn_handle);
// Prüfe ob Device bereits gebondet ist
if (is_device_bonded(&device->addr))
{
ESP_LOGI(TAG, "Device already bonded, using existing bond");
// Bei gebondetem Device kann direkt mit Service Discovery begonnen werden
ble_gattc_disc_all_svcs(g_conn_handle, gattc_event_callback, NULL);
}
else
{
ESP_LOGI(TAG, "Device not bonded, initiating bonding");
// Starte Bonding-Prozess
initiate_bonding(g_conn_handle);
// Service Discovery wird nach erfolgreichem Bonding gestartet
}
}
else
{
ESP_LOGE(TAG, "Connection failed; status=%d", event->connect.status);
}
break;
case BLE_GAP_EVENT_DISCONNECT:
g_conn_handle = 0;
g_bonding_in_progress = false;
ESP_LOGI(TAG, "Disconnected; reason=%d", event->disconnect.reason);
break;
case BLE_GAP_EVENT_CONN_UPDATE:
ESP_LOGI(TAG, "Connection updated; status=%d", event->conn_update.status);
break;
case BLE_GAP_EVENT_ENC_CHANGE:
ESP_LOGI(TAG, "Encryption change: status=%d", event->enc_change.status);
if (event->enc_change.status == 0)
{
ESP_LOGI(TAG, "Encryption established, bonding complete");
g_bonding_in_progress = false;
// Nach erfolgreichem Bonding: Service Discovery starten
ble_gattc_disc_all_svcs(g_conn_handle, gattc_event_callback, NULL);
}
else
{
ESP_LOGE(TAG, "Encryption failed: %s", ble_error_to_string(event->enc_change.status));
g_bonding_in_progress = false;
}
break;
case BLE_GAP_EVENT_PASSKEY_ACTION:
ESP_LOGI(TAG, "Passkey action event");
// Implementieren Sie hier die Passkey-Behandlung
// z.B. einen festen Passkey eingeben:
struct ble_sm_io pkey = {0};
pkey.action = BLE_SM_IOACT_INPUT;
pkey.passkey = 100779;
ble_sm_inject_io(event->passkey.conn_handle, &pkey);
break;
case BLE_GAP_EVENT_REPEAT_PAIRING:
ESP_LOGI(TAG, "Device requests repeat pairing");
// Hole die Peer-Adresse aus der Verbindung
struct ble_gap_conn_desc conn_desc;
int rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &conn_desc);
if (rc == 0)
{
// Lösche alte Bonding-Info
ble_clear_bond(&conn_desc.peer_ota_addr);
}
// Erlaube erneutes Pairing
return BLE_GAP_REPEAT_PAIRING_RETRY;
default:
break;
}
return 0;
}
void ble_connect(device_info_t *device)
{
struct ble_gap_conn_params conn_params = {
.scan_itvl = 0x0010,
.scan_window = 0x0010,
.itvl_min = BLE_GAP_INITIAL_CONN_ITVL_MIN,
.itvl_max = BLE_GAP_INITIAL_CONN_ITVL_MAX,
.latency = BLE_GAP_INITIAL_CONN_LATENCY,
.supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT,
.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN,
.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN,
};
// Prüfe ob Device bereits gebondet ist
if (is_device_bonded(&device->addr))
{
ESP_LOGI(TAG, "Connecting to bonded device");
}
else
{
ESP_LOGI(TAG, "Connecting to new device (will bond after connection)");
}
int rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &device->addr, 30000, &conn_params, ble_gap_event_handler, device);
if (rc != 0)
{
ESP_LOGE(TAG, "Error initiating connection: %s", ble_error_to_string(rc));
}
}
void ble_clear_bonds(void)
{
ESP_LOGI(TAG, "Clearing all bonds");
int rc = ble_store_clear();
if (rc != 0)
{
ESP_LOGE(TAG, "Failed to clear bond storage: %s", ble_error_to_string(rc));
}
else
{
ESP_LOGI(TAG, "All bonds cleared successfully");
}
}
// Funktion zum Löschen der Bonding-Info eines bestimmten Devices
void ble_clear_bond(const ble_addr_t *addr)
{
ESP_LOGI(TAG, "Clearing bond for specific device");
int rc = ble_store_util_delete_peer(addr);
if (rc != 0)
{
ESP_LOGE(TAG, "Failed to delete peer: %s", ble_error_to_string(rc));
}
else
{
ESP_LOGI(TAG, "Peer deleted successfully");
}
}
void read_characteristic(uint16_t char_val_handle)
{
if (char_val_handle != 0 && g_conn_handle != 0)
{
int rc = ble_gattc_read(g_conn_handle, char_val_handle, gattc_read_callback, NULL);
if (rc != 0)
{
ESP_LOGE(TAG, "Error reading characteristic: %d", rc);
}
}
}

View File

@@ -0,0 +1,324 @@
#include "ble/ble_scanner.h"
#include "ble/ble_device.h"
#include "led_status.h"
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <host/ble_hs.h>
#include <host/util/util.h>
#include <nimble/nimble_port.h>
#include <nimble/nimble_port_freertos.h>
#include <services/gap/ble_svc_gap.h>
#include <stdint.h>
static const char *TAG = "ble_scanner";
// List of allowed manufacturer IDs
static const uint16_t ALLOWED_MANUFACTURERS[] = {
0xC0DE, // mars3142
};
static const size_t NUM_MANUFACTURERS = sizeof(ALLOWED_MANUFACTURERS) / sizeof(uint16_t);
static bool scanning = false;
static bool is_manufacturer_allowed(uint16_t company_id)
{
for (size_t i = 0; i < NUM_MANUFACTURERS; i++)
{
if (ALLOWED_MANUFACTURERS[i] == company_id)
{
return true;
}
}
return false;
}
static int ble_central_gap_event(struct ble_gap_event *event, void *arg);
/**
* Starts the BLE scan process.
*/
void start_scan(void)
{
led_behavior_t led_behavior = {
.on_time_ms = 200,
.off_time_ms = 200,
.color = {.red = 0, .green = 0, .blue = 50},
.index = 1,
.mode = LED_MODE_BLINK,
};
led_status_set_behavior(led_behavior);
struct ble_gap_disc_params disc_params = {
.filter_policy = 0,
.limited = 0,
.passive = 0,
.filter_duplicates = 1,
};
int32_t duration_ms = 10000; // 10 seconds
int rc = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, duration_ms, &disc_params, ble_central_gap_event, NULL);
if (rc != 0)
{
ESP_LOGE(TAG, "Error starting scan; rc=%d", rc);
}
scanning = true;
}
#define MAX_DEVICES 40
static device_info_t devices[MAX_DEVICES];
static int device_count = 0;
// Helper function to find or create a device entry
static device_info_t *find_or_create_device(const ble_addr_t *addr)
{
// Search for existing device
for (int i = 0; i < device_count; i++)
{
if (memcmp(&devices[i].addr, addr, sizeof(ble_addr_t)) == 0)
{
return &devices[i];
}
}
// Add new device
if (device_count < MAX_DEVICES)
{
memset(&devices[device_count], 0, sizeof(device_info_t));
memcpy(&devices[device_count].addr, addr, sizeof(ble_addr_t));
devices[device_count].has_manufacturer = false;
devices[device_count].has_name = false;
strcpy(devices[device_count].name, "Unknown");
return &devices[device_count++];
}
return NULL;
}
static int ble_central_gap_event(struct ble_gap_event *event, void *arg)
{
struct ble_gap_disc_desc *disc;
struct ble_hs_adv_fields fields;
switch (event->type)
{
case BLE_GAP_EVENT_DISC: {
disc = &event->disc;
// Find or create device
device_info_t *device = find_or_create_device(&disc->addr);
if (device == NULL)
{
return 0;
}
// Update RSSI
device->rssi = disc->rssi;
// Parse advertising data
memset(&fields, 0, sizeof(fields));
ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data);
// Process manufacturer data
if (fields.mfg_data != NULL && fields.mfg_data_len >= 2)
{
uint16_t company_id = fields.mfg_data[0] | (fields.mfg_data[1] << 8);
device->manufacturer_id = company_id;
device->has_manufacturer = true;
// Store complete manufacturer data (incl. Company ID)
device->manufacturer_data_len = fields.mfg_data_len;
memcpy(device->manufacturer_data, fields.mfg_data, fields.mfg_data_len);
}
// Process name
if (fields.name != NULL && fields.name_len > 0)
{
size_t copy_len = fields.name_len < sizeof(device->name) - 1 ? fields.name_len : sizeof(device->name) - 1;
memcpy(device->name, fields.name, copy_len);
device->name[copy_len] = '\0';
device->has_name = true;
}
// Process 16-bit Service UUIDs
if (fields.uuids16 != NULL && fields.num_uuids16 > 0)
{
for (int i = 0; i < fields.num_uuids16 && device->service_uuids_16_count < 10; i++)
{
// Check if UUID already exists
bool exists = false;
for (int j = 0; j < device->service_uuids_16_count; j++)
{
if (device->service_uuids_16[j] == fields.uuids16[i].value)
{
exists = true;
break;
}
}
if (!exists)
{
device->service_uuids_16[device->service_uuids_16_count++] = fields.uuids16[i].value;
}
}
}
// Process 128-bit Service UUIDs
if (fields.uuids128 != NULL && fields.num_uuids128 > 0)
{
for (int i = 0; i < fields.num_uuids128 && device->service_uuids_128_count < 5; i++)
{
// Check if UUID already exists
bool exists = false;
for (int j = 0; j < device->service_uuids_128_count; j++)
{
if (memcmp(&device->service_uuids_128[j], &fields.uuids128[i], sizeof(ble_uuid128_t)) == 0)
{
exists = true;
break;
}
}
if (!exists)
{
memcpy(&device->service_uuids_128[device->service_uuids_128_count++], &fields.uuids128[i],
sizeof(ble_uuid128_t));
}
}
}
// Check if we have all data and the device is allowed
if (device->has_name && device->has_manufacturer && is_manufacturer_allowed(device->manufacturer_id))
{
ESP_LOGI(TAG, "*** Allowed device found ***");
ESP_LOGI(TAG, " Name: %s", device->name);
ESP_LOGI(TAG, " Address: %02X:%02X:%02X:%02X:%02X:%02X", device->addr.val[5], device->addr.val[4],
device->addr.val[3], device->addr.val[2], device->addr.val[1], device->addr.val[0]);
ESP_LOGI(TAG, " Manufacturer ID: 0x%04X", device->manufacturer_id);
ESP_LOGI(TAG, " RSSI: %d dBm", device->rssi);
// Print Service UUIDs
if (device->service_uuids_16_count > 0)
{
ESP_LOGI(TAG, " 16-bit Service UUIDs (%d):", device->service_uuids_16_count);
for (int i = 0; i < device->service_uuids_16_count; i++)
{
const char *name = "";
// Known Service UUIDs
switch (device->service_uuids_16[i])
{
case 0x180A:
name = " (Device Information)";
break;
case 0x180F:
name = " (Battery Service)";
break;
case 0x1801:
name = " (Generic Attribute)";
break;
case 0x1800:
name = " (Generic Access)";
break;
case 0x181A:
name = " (Environmental Sensing)";
break;
default:
if (device->service_uuids_16[i] >= 0xA000)
{
name = " (Custom)";
}
break;
}
ESP_LOGI(TAG, " - 0x%04X%s", device->service_uuids_16[i], name);
}
}
if (device->service_uuids_128_count > 0)
{
ESP_LOGI(TAG, " 128-bit Service UUIDs (%d):", device->service_uuids_128_count);
for (int i = 0; i < device->service_uuids_128_count; i++)
{
char uuid_str[37]; // UUID string format
snprintf(uuid_str, sizeof(uuid_str),
"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
device->service_uuids_128[i].value[15], device->service_uuids_128[i].value[14],
device->service_uuids_128[i].value[13], device->service_uuids_128[i].value[12],
device->service_uuids_128[i].value[11], device->service_uuids_128[i].value[10],
device->service_uuids_128[i].value[9], device->service_uuids_128[i].value[8],
device->service_uuids_128[i].value[7], device->service_uuids_128[i].value[6],
device->service_uuids_128[i].value[5], device->service_uuids_128[i].value[4],
device->service_uuids_128[i].value[3], device->service_uuids_128[i].value[2],
device->service_uuids_128[i].value[1], device->service_uuids_128[i].value[0]);
ESP_LOGI(TAG, " - %s", uuid_str);
}
}
// Print manufacturer data (without Company ID, i.e., from byte 2)
if (device->manufacturer_data_len > 2)
{
int payload_len = device->manufacturer_data_len - 2;
ESP_LOGI(TAG, " Manufacturer Data (%d bytes):", payload_len);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, &device->manufacturer_data[2], payload_len, ESP_LOG_INFO);
// Print data byte by byte for better readability
ESP_LOGI(TAG, " Data interpretation:");
for (int i = 0; i < payload_len; i++)
{
ESP_LOGI(TAG, " - Byte %d: 0x%02X (%d)", i, device->manufacturer_data[i + 2],
device->manufacturer_data[i + 2]);
}
// Optional: Interpret data as 16-bit values (if the number of bytes is even)
if (payload_len >= 2 && (payload_len % 2 == 0))
{
ESP_LOGI(TAG, " As 16-bit values:");
for (int i = 0; i < payload_len; i += 2)
{
uint16_t value = device->manufacturer_data[i + 2] | (device->manufacturer_data[i + 3] << 8);
ESP_LOGI(TAG, " - Word %d: 0x%04X (%d)", i / 2, value, value);
}
}
}
else
{
ESP_LOGI(TAG, " No manufacturer payload data");
}
}
return 0;
}
case BLE_GAP_EVENT_DISC_COMPLETE: {
led_behavior_t led_behavior = {
.index = 1,
.mode = LED_MODE_OFF,
};
led_status_set_behavior(led_behavior);
ESP_LOGI(TAG, "Discovery complete");
scanning = false;
return 0;
}
default:
break;
}
return 0;
}
device_info_t *get_device(int index)
{
if (index < 0 || index >= device_count || !is_manufacturer_allowed(devices[index].manufacturer_id))
{
return NULL;
}
return &devices[index];
}
int get_device_count(void)
{
if (!scanning)
{
return device_count;
}
return 0;
}

View File

@@ -0,0 +1,60 @@
#include "ble_manager.h"
#include "ble/ble_connection.h"
#include "ble/ble_scanner.h"
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <host/ble_hs.h>
#include <host/util/util.h>
#include <nimble/nimble_port.h>
#include <nimble/nimble_port_freertos.h>
#include <services/gap/ble_svc_gap.h>
static const char *TAG = "ble_manager";
/**
* Callback that is called when the NimBLE stack is synchronized and ready.
*/
static void on_sync(void)
{
start_scan();
}
static void ble_host_task(void *param)
{
ESP_LOGI(TAG, "BLE Host Task Started");
nimble_port_run(); // This blocks until the stack is stopped
nimble_port_freertos_deinit();
}
void ble_manager_task(void *pvParameter)
{
// Initialize and start the NimBLE stack
nimble_port_init();
// Host configuration with our sync callback
ble_hs_cfg.sync_cb = on_sync;
// Configure GAP service for central mode
ble_svc_gap_init();
// Start the NimBLE host task
nimble_port_freertos_init(ble_host_task); // Not a separate task, can run in the app task
vTaskDelete(NULL);
}
bool ble_found_devices(void)
{
return get_device_count() > 0;
}
void ble_connect_to_device(int index)
{
device_info_t *device = get_device(index);
if (device != NULL)
{
ble_connect(device);
}
}

View File

@@ -0,0 +1,229 @@
#include "wifi_manager.h"
#include <esp_event.h>
#include <esp_insights.h>
#include <esp_log.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/task.h>
#include <led_status.h>
#include <lwip/err.h>
#include <lwip/sys.h>
#include <nvs_flash.h>
#include <sdkconfig.h>
#include <string.h>
// Event group to signal when we are connected
static EventGroupHandle_t s_wifi_event_group;
// The bits for the event group
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "wifi_manager";
static int s_retry_num = 0;
static int s_current_network_index = 0;
// WiFi network configuration structure
typedef struct
{
const char *ssid;
const char *password;
} wifi_network_config_t;
// Array of configured WiFi networks
static const wifi_network_config_t s_wifi_networks[] = {
#if CONFIG_WIFI_ENABLED
{CONFIG_WIFI_SSID_1, CONFIG_WIFI_PASSWORD_1},
#if CONFIG_WIFI_NETWORK_COUNT >= 2
{CONFIG_WIFI_SSID_2, CONFIG_WIFI_PASSWORD_2},
#endif
#if CONFIG_WIFI_NETWORK_COUNT >= 3
{CONFIG_WIFI_SSID_3, CONFIG_WIFI_PASSWORD_3},
#endif
#if CONFIG_WIFI_NETWORK_COUNT >= 4
{CONFIG_WIFI_SSID_4, CONFIG_WIFI_PASSWORD_4},
#endif
#if CONFIG_WIFI_NETWORK_COUNT >= 5
{CONFIG_WIFI_SSID_5, CONFIG_WIFI_PASSWORD_5},
#endif
#endif
};
static const int s_wifi_network_count = sizeof(s_wifi_networks) / sizeof(s_wifi_networks[0]);
static void try_next_network(void);
static void connect_to_network(int index)
{
#if CONFIG_WIFI_ENABLED
if (index >= s_wifi_network_count)
{
ESP_LOGE(TAG, "No more networks to try");
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
return;
}
const wifi_network_config_t *network = &s_wifi_networks[index];
// Skip empty SSIDs
if (network->ssid == NULL || strlen(network->ssid) == 0)
{
ESP_LOGW(TAG, "Skipping empty SSID at index %d", index);
s_current_network_index++;
s_retry_num = 0;
try_next_network();
return;
}
ESP_DIAG_EVENT(TAG, "Trying to connect to network %d: %s", index + 1, network->ssid);
wifi_config_t wifi_config = {
.sta =
{
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
strncpy((char *)wifi_config.sta.ssid, network->ssid, sizeof(wifi_config.sta.ssid) - 1);
strncpy((char *)wifi_config.sta.password, network->password, sizeof(wifi_config.sta.password) - 1);
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
esp_wifi_connect();
#endif
}
static void try_next_network(void)
{
#if CONFIG_WIFI_ENABLED
s_current_network_index++;
s_retry_num = 0;
if (s_current_network_index < s_wifi_network_count)
{
connect_to_network(s_current_network_index);
}
else
{
ESP_LOGE(TAG, "Failed to connect to any configured network");
led_behavior_t led0_behavior = {
.index = 0,
.mode = LED_MODE_BLINK,
.color = {.red = 50, .green = 0, .blue = 0},
.on_time_ms = 1000,
.off_time_ms = 500,
};
led_status_set_behavior(led0_behavior);
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
#endif
}
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
#if CONFIG_WIFI_ENABLED
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
led_behavior_t led0_behavior = {
.index = 0,
.mode = LED_MODE_BLINK,
.color = {.red = 50, .green = 50, .blue = 0},
.on_time_ms = 200,
.off_time_ms = 200,
};
led_status_set_behavior(led0_behavior);
connect_to_network(s_current_network_index);
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
if (s_retry_num < CONFIG_WIFI_CONNECT_RETRIES)
{
led_behavior_t led0_behavior = {
.index = 0,
.mode = LED_MODE_BLINK,
.color = {.red = 50, .green = 50, .blue = 0},
.on_time_ms = 200,
.off_time_ms = 200,
};
led_status_set_behavior(led0_behavior);
s_retry_num++;
ESP_DIAG_EVENT(TAG, "Retrying network %d (%d/%d)", s_current_network_index + 1, s_retry_num,
CONFIG_WIFI_CONNECT_RETRIES);
esp_wifi_connect();
return;
}
// Retries exhausted for current network, try next one
ESP_LOGW(TAG, "Failed to connect to network %d after %d retries, trying next...", s_current_network_index + 1,
CONFIG_WIFI_CONNECT_RETRIES);
try_next_network();
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
led_behavior_t led0_behavior = {
.index = 0,
.mode = LED_MODE_SOLID,
.color = {.red = 0, .green = 50, .blue = 0},
};
led_status_set_behavior(led0_behavior);
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_DIAG_EVENT(TAG, "Got IP address:" IPSTR " (network %d: %s)", IP2STR(&event->ip_info.ip),
s_current_network_index + 1, s_wifi_networks[s_current_network_index].ssid);
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
#endif
}
void wifi_manager_init()
{
#if CONFIG_WIFI_ENABLED
s_wifi_event_group = xEventGroupCreate();
s_current_network_index = 0;
s_retry_num = 0;
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_DIAG_EVENT(TAG, "WiFi manager initialized with %d network(s), waiting for connection...", s_wifi_network_count);
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or
connection failed for all networks (WIFI_FAIL_BIT). The bits are set by event_handler() */
EventBits_t bits =
xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT)
{
ESP_DIAG_EVENT(TAG, "Connected to AP SSID:%s", s_wifi_networks[s_current_network_index].ssid);
}
else if (bits & WIFI_FAIL_BIT)
{
ESP_LOGE(TAG, "Failed to connect to any configured WiFi network");
}
else
{
ESP_LOGE(TAG, "Unexpected event");
}
#endif
}

View File

@@ -0,0 +1,21 @@
idf_component_register(SRCS
src/common/InactivityTracker.cpp
src/common/Menu.cpp
src/common/ScrollBar.cpp
src/common/Widget.cpp
src/data/MenuItem.cpp
src/ui/ExternalDevices.cpp
src/ui/LightMenu.cpp
src/ui/MainMenu.cpp
src/ui/ClockScreenSaver.cpp
src/ui/ScreenSaver.cpp
src/ui/SettingsMenu.cpp
src/ui/SplashScreen.cpp
INCLUDE_DIRS "include"
PRIV_REQUIRES
u8g2
connectivity-manager
led-manager
persistence-manager
simulator
)

View File

@@ -0,0 +1,74 @@
/**
* @file MenuOptions.h
* @brief Menu configuration structure and callback definitions
* @details This header defines the menu_options_t structure which contains all
* necessary configuration options and callback functions for menu widgets.
* It provides the interface between menu widgets and the application's
* screen management system, display context, and input handling.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
// Standard libraries for function objects and smart pointers
#include <functional>
#include <memory>
// Project-specific headers
#include "common/Widget.h"
#include "IPersistenceManager.h"
#include "u8g2.h"
class MenuItem;
/**
* @struct menu_options_t
* @brief Configuration structure for menu widgets containing display context and callbacks
* @details This structure serves as a configuration container that provides menu widgets
* with access to the display system, screen management functions, input
* handling callbacks, and persistent storage.
*
* @see Widget
* @see ButtonType
* @see IPersistenceManager
*/
typedef struct
{
/**
* @brief Pointer to u8g2 display context for graphics output operations
*/
u8g2_t *u8g2;
/**
* @brief Callback function to set the current active screen
*/
std::function<void(std::shared_ptr<Widget>)> setScreen;
/**
* @brief Callback function to add a new screen to the navigation stack
*/
std::function<void(std::shared_ptr<Widget>)> pushScreen;
/**
* @brief Callback function to remove the top screen from the navigation stack
*/
std::function<void()> popScreen;
/**
* @brief Callback function to handle button press events
*/
std::function<void(ButtonType button)> onButtonClicked;
/**
* @brief Shared pointer to platform-independent persistence manager
* @details This provides access to persistent key-value storage across different
* platforms. The actual implementation (SDL3 or ESP32/NVS) is determined
* at compile time based on the target platform.
*
* @note The persistence manager is shared across all menu widgets and maintains
* its state throughout the application lifecycle.
*/
std::shared_ptr<IPersistenceManager> persistenceManager;
} menu_options_t;

View File

@@ -0,0 +1,95 @@
/**
* @file Common.h
* @brief Common definitions and types for the INSA component
* @details This header file contains shared enumerations, type definitions, and
* callback function types used throughout the INSA component system.
* It provides the foundation for button handling and event management.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include <functional>
/**
* @enum ButtonType
* @brief Enumeration defining the different types of buttons available in the system
* @details This enumeration represents all possible button types that can be handled
* by the system's input management. NONE represents no button pressed or
* an invalid button state, while the other values correspond to specific
* directional and action buttons.
*/
enum class ButtonType
{
NONE, ///< No button pressed or invalid button state
UP, ///< Up directional button for navigation
DOWN, ///< Down directional button for navigation
LEFT, ///< Left directional button for navigation
RIGHT, ///< Right directional button for navigation
SELECT, ///< Select/confirm button for accepting choices
BACK ///< Back/cancel button for returning to previous state
};
// Forward declaration of MenuItem to avoid circular dependency
class MenuItem;
/**
* @typedef ButtonCallback
* @brief Type alias for button event callback function
* @details This function type is used to define callback functions that handle
* button press events. The callback receives information about which
* button was pressed and any additional context data.
*
* @param MenuItem menu item for the specific action
* @param ButtonType The type of button that was pressed
*
* @note The first parameter can be used to distinguish between multiple instances
* of the same button type or to pass additional event-specific data.
*
* @example
* @code
* ButtonCallback myCallback = [](const MenuItem& item, ButtonType type) {
* if (type == ButtonType::SELECT) {
* // Handle select button press
* processSelection(item);
* }
* };
* @endcode
*/
typedef std::function<void(MenuItem, ButtonType)> ButtonCallback;
/**
* @def IMPLEMENT_GET_NAME
* @brief Macro to implement getName() method using __FILE__
* @details Extracts the class name from the source filename automatically.
* Use this macro in .cpp files to implement Widget::getName().
* @param ClassName The class name for the method scope (e.g., MainMenu)
*/
#define IMPLEMENT_GET_NAME(ClassName) \
const char *ClassName::getName() const \
{ \
static const char *cachedName = nullptr; \
if (!cachedName) \
{ \
const char *file = __FILE__; \
const char *lastSlash = file; \
for (const char *p = file; *p; ++p) \
{ \
if (*p == '/' || *p == '\\') \
lastSlash = p + 1; \
} \
static char buffer[64]; \
size_t i = 0; \
for (; lastSlash[i] && lastSlash[i] != '.' && i < sizeof(buffer) - 1; ++i) \
{ \
buffer[i] = lastSlash[i]; \
} \
buffer[i] = '\0'; \
cachedName = buffer; \
} \
return cachedName; \
}
// Include MenuItem.h after the typedef to avoid circular dependency
#include "data/MenuItem.h"

View File

@@ -0,0 +1,194 @@
/**
* @file InactivityTracker.h
* @brief Inactivity tracking system for monitoring user interaction timeouts
* @details This header defines the InactivityTracker class which monitors user
* activity and triggers timeout callbacks when the system remains inactive
* for a specified duration. It provides essential functionality for power
* management, screen savers, and automatic system state transitions.
* @author System Control Team
* @date 2025-06-20
*/
#pragma once
#include <functional>
#include <stdint.h>
/**
* @class InactivityTracker
* @brief Activity monitoring class for detecting user inactivity periods
* @details This class provides a robust mechanism for tracking user activity and
* triggering automatic actions when the system remains inactive for a
* configured timeout period. It is commonly used for implementing power
* saving features, automatic screen savers, session timeouts, and other
* time-based system behaviors.
*
* The InactivityTracker operates by:
* - Continuously tracking elapsed time since the last user activity
* - Comparing elapsed time against a configurable timeout threshold
* - Executing a callback function when the timeout is reached
* - Providing methods to reset the timer when activity is detected
* - Supporting enable/disable functionality for dynamic control
*
* Key features include:
* - Configurable timeout duration in milliseconds
* - Custom callback function execution on timeout
* - Activity reset capability for responsive user interaction
* - Enable/disable control for conditional monitoring
* - High-resolution timing support using 64-bit millisecond precision
*
* Common use cases:
* - Screen saver activation after idle periods
* - Automatic screen dimming or shutdown
* - Session timeout management
* - Power management and battery conservation
* - User interface state transitions
* - Security lockout after inactivity
*
* The class is designed to be lightweight and efficient, suitable for
* real-time applications where precise timing and minimal overhead are important.
*
* @note This class requires regular update calls to function properly.
* @note The timeout callback is executed once per timeout period and will
* not repeat until the tracker is reset and times out again.
*
* @see Widget for integration with UI components
* @see Menu for menu timeout implementations
*/
class InactivityTracker
{
public:
/**
* @brief Constructs an InactivityTracker with specified timeout and callback
* @param timeoutMs Timeout duration in milliseconds before triggering callback
* @param onTimeout Callback function to execute when timeout is reached
*
* @pre timeoutMs must be greater than 0 for meaningful timeout behavior
* @pre onTimeout must be a valid callable function object
* @post InactivityTracker is initialized, enabled, and ready for activity monitoring
*
* @details The constructor initializes the inactivity tracker with the specified
* timeout duration and callback function. The tracker starts in an enabled
* state with zero elapsed time, ready to begin monitoring user activity.
*
* The timeout callback function can perform any necessary actions when inactivity
* is detected, such as:
* - Activating screen savers or power saving modes
* - Transitioning to different application states
* - Logging inactivity events
* - Triggering security lockouts
* - Initiating automatic save operations
*
* @note The tracker begins monitoring immediately upon construction.
* @note The callback function should be lightweight to avoid blocking
* the main application thread during timeout processing.
*
* Example usage:
* @code
* auto tracker = InactivityTracker(30000, []() {
* // Activate screen saver after 30 seconds of inactivity
* activateScreenSaver();
* });
* @endcode
*/
InactivityTracker(uint64_t timeoutMs, const std::function<void()> &onTimeout);
/**
* @brief Updates the inactivity timer and checks for timeout conditions
* @param dt Delta time in milliseconds since the last update call
*
* @details This method must be called regularly (typically every frame) to
* maintain accurate timing and timeout detection. It increments the
* elapsed time counter and triggers the timeout callback when the
* configured timeout duration is reached.
*
* The update process:
* - Adds the delta time to the elapsed time counter (if enabled)
* - Compares elapsed time against the configured timeout threshold
* - Executes the timeout callback if the threshold is exceeded
* - Continues monitoring until reset or disabled
*
* @note This method should be called consistently from the main application
* loop to ensure accurate timing behavior.
* @note The timeout callback is executed only once per timeout period.
* @note If the tracker is disabled, elapsed time is not updated.
*
* @see reset() to restart the inactivity timer
* @see setEnabled() to control monitoring state
*/
void update(uint64_t dt);
/**
* @brief Resets the inactivity timer to indicate recent user activity
*
* @details This method should be called whenever user activity is detected
* to restart the inactivity timeout period. It resets the elapsed
* time counter to zero, effectively extending the timeout deadline
* and preventing timeout callback execution until the full timeout
* duration elapses again without further resets.
*
* Common scenarios for calling reset():
* - Button presses or key events
* - Mouse movement or touch input
* - Menu navigation or selection actions
* - Any user interface interaction
* - System activity that should extend the timeout
*
* @post Elapsed time is reset to zero, restarting the timeout period
*
* @note This method can be called at any time, even when the tracker
* is disabled, to prepare for future monitoring.
* @note Frequent reset calls from active user interaction will prevent
* timeout callback execution, which is the intended behavior.
*
* Example usage:
* @code
* void onButtonPress() {
* tracker.reset(); // User activity detected, restart timeout
* // Handle button press...
* }
* @endcode
*/
void reset();
/**
* @brief Enables or disables inactivity monitoring
* @param enabled True to enable monitoring, false to disable
*
* @details This method controls whether the inactivity tracker actively
* monitors for timeouts. When disabled, the elapsed time counter
* is not updated during update() calls, effectively pausing the
* timeout detection without losing the current elapsed time state.
*
* Use cases for disabling:
* - Temporary suspension during system operations
* - Context-sensitive monitoring (disable in certain application states)
* - Power management control (disable during low-power modes)
* - User preference settings (allow users to disable timeouts)
* - Development and debugging (disable for testing)
*
* When re-enabled, monitoring resumes from the current elapsed time state,
* allowing for seamless pause/resume functionality.
*
* @post Monitoring state is updated according to the enabled parameter
*
* @note Disabling the tracker does not reset the elapsed time counter.
* @note The timeout callback will not be executed while disabled, even
* if the timeout threshold would otherwise be exceeded.
* @note Enabling/disabling can be done at any time during operation.
*
* Example usage:
* @code
* tracker.setEnabled(false); // Pause monitoring during critical operation
* performCriticalOperation();
* tracker.setEnabled(true); // Resume monitoring after completion
* @endcode
*/
void setEnabled(bool enabled);
private:
uint64_t m_timeoutMs; ///< Timeout duration in milliseconds before callback execution
uint64_t m_elapsedTime; ///< Current elapsed time since last reset in milliseconds
bool m_enabled; ///< Flag indicating whether monitoring is currently active
std::function<void()> m_onTimeout; ///< Callback function executed when timeout threshold is reached
};

View File

@@ -0,0 +1,223 @@
/**
* @file Menu.h
* @brief Menu widget class for creating interactive menu systems
* @details This header defines the Menu class which extends the Widget base class
* to provide a comprehensive, customizable menu system supporting various
* types of interactive menu items including text buttons, selections,
* number inputs, and toggles. The menu supports navigation with directional
* input and provides visual feedback through selection highlighting and scrollbars.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "Common.h"
#include "MenuOptions.h"
#include "Widget.h"
#include "data/MenuItem.h"
/**
* @class Menu
* @brief A comprehensive menu widget class for interactive user interfaces
* @details This class extends the Widget base class to provide a customizable menu system
* with support for various types of interactive menu items. It handles user input
* through directional navigation and action buttons, provides visual feedback
* through selection highlighting, and supports scrolling for long menu lists.
*
* The menu system supports four types of menu items:
* - Text buttons: Simple selectable text items
* - Selection items: Dropdown/list selection with multiple options
* - Number inputs: Numeric value adjustment controls
* - Toggle items: Boolean on/off switches
*
* @note Menu items are identified by unique IDs and can be dynamically added
* after menu creation.
*
* @see Widget
* @see MenuItem
* @see menu_options_t
*/
class Menu : public Widget
{
public:
/**
* @brief Constructs a new Menu instance with the specified configuration
* @param options Pointer to menu configuration options structure
*
* @pre options must not be nullptr and must remain valid for the menu's lifetime
* @post Menu is initialized with the provided configuration and ready for item addition
*
* @note The menu does not take ownership of the options structure and assumes
* it remains valid throughout the menu's lifetime.
*/
explicit Menu(menu_options_t *options);
/**
* @brief Destructor - Cleans up resources when menu is destroyed
* @details Properly releases any allocated resources and ensures clean shutdown
* of the menu system.
*/
~Menu() override;
/**
* @brief Adds a text-based menu item (button) to the menu
* @param id Unique identifier for this menu item (must be unique within the menu)
* @param text Display text shown on the menu item
*
* @pre id must be unique within this menu instance
* @post A new text menu item is added to the menu's item collection
*
* @note Text items act as buttons and generate selection events when activated
*/
void addText(uint8_t id, const std::string &text);
void addTextCounter(uint8_t id, const std::string &text, const uint8_t value);
/**
* @brief Adds a selection menu item (dropdown/list selection) to the menu
* @param id Unique identifier for this menu item (must be unique within the menu)
* @param text Display text/label for the selection item
* @param values Vector of all available options to choose from
* @param index Reference to current selected value (will be modified by user interaction)
*
* @pre id must be unique within this menu instance
* @pre values vector must not be empty
* @pre value must be one of the strings in the values vector
* @post A new selection menu item is added with the specified options
*
* @note The value parameter is modified directly when the user changes the selection
*/
void addSelection(uint8_t id, const std::string &text, const std::vector<std::string> &values, int index);
/**
* @brief Adds a toggle/checkbox menu item to the menu
* @param id Unique identifier for this menu item (must be unique within the menu)
* @param text Display text/label for the toggle item
* @param selected Current state of the toggle (true = on/enabled, false = off/disabled)
*
* @pre id must be unique within this menu instance
* @post A new toggle menu item is added with the specified initial state
*
* @note Toggle state can be changed through user interaction with select button
*/
void addToggle(uint8_t id, const std::string &text, bool selected);
protected:
/**
* @brief Virtual callback method for handling button press events on specific menu items
* @param item The menu item that received the button press
* @param button The type of button that was pressed
*
* @details This method can be overridden by derived classes to implement custom
* button handling logic for specific menu items. The base implementation
* is empty, allowing derived classes to selectively handle events.
*
* @note Override this method in derived classes to implement custom menu item
* interaction behavior beyond the standard navigation and value modification.
*/
virtual void onButtonPressed(const MenuItem &item, const ButtonType button)
{
// Base implementation intentionally empty - override in derived classes as needed
}
/**
* @brief Retrieves a menu item by its index position
* @param index Zero-based index of the menu item to retrieve
* @return MenuItem object at the specified index
*
* @pre index must be within valid range [0, getItemCount()-1]
* @post Returns a copy of the menu item at the specified position
*
* @throws std::out_of_range if index is invalid
*
* @note This method returns a copy of the menu item, not a reference
*/
MenuItem getItem(int index);
/**
* @brief Gets the total number of menu items in the menu
* @return Size of the menu items collection
*
* @post Returns current count of menu items (>= 0)
*
* @note This count includes all types of menu items (text, selection, toggle)
*/
[[nodiscard]] size_t getItemCount() const;
/**
* @brief Dynamically adjusts the number of menu items to the specified size
* @param size Target number of menu items the menu should contain
*
* @details If the target size is larger than current item count, new selection
* items are added using the first item's values as template. If the
* target size is smaller, excess items are removed from the end.
*
* @pre size must be > 0 and at least one menu item must exist as template
* @post Menu contains exactly 'size' number of items
*
* @note New items are created as selection items with auto-generated names
* in the format "Section X" where X is the item number
*/
void setItemSize(size_t size, int8_t startIndex = 0);
/**
* @brief Toggles the boolean state of a toggle menu item
* @param menuItem The toggle menu item whose state should be flipped
*
* @pre menuItem must be of type TOGGLE
* @post The menu item's value is switched between "true" and "false"
*
* @details Changes "true" to "false" and "false" to "true" for toggle items.
* The modified item replaces the original in the menu's item collection.
*
* @note This method directly modifies the menu's internal state
*/
void toggle(const MenuItem &menuItem);
/**
* @brief Changes the selected value of a selection menu item based on button input
* @param menuItem The selection menu item to modify
* @param button The directional button pressed (LEFT or RIGHT)
*
* @pre menuItem must be of type SELECTION with valid values array
* @post The menu item's selected index is adjusted based on button direction
*
* @details LEFT button moves to previous option (wraps to end if at beginning),
* RIGHT button moves to next option (wraps to beginning if at end).
* Other button types are ignored.
*
* @note The modified item replaces the original in the menu's item collection
*/
MenuItem switchValue(const MenuItem &menuItem, ButtonType button);
private:
MenuItem replaceItem(int index, const MenuItem &item);
void Render() override;
void OnButtonClicked(ButtonType button) override;
void onPressedDown();
void onPressedUp();
void onPressedLeft() const;
void onPressedRight() const;
void onPressedSelect() const;
void onPressedBack() const;
void drawScrollBar() const;
void drawSelectionBox() const;
void renderWidget(const MenuItem *item, const uint8_t *font, int x, int y) const;
// Member variables
size_t m_selected_item = 0;
std::vector<MenuItem> m_items;
menu_options_t *m_options;
};

View File

@@ -0,0 +1,106 @@
/**
* @file ScrollBar.h
* @brief Vertical scrollbar widget for indicating scroll position in long content
* @details This header defines the ScrollBar class which provides a visual scrollbar
* widget for indicating the current position within scrollable content.
* The scrollbar displays a thumb that moves proportionally to represent
* the current scroll position and visible area relative to the total content.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "MenuOptions.h"
#include "Widget.h"
/**
* @class ScrollBar
* @brief A vertical scrollbar widget that represents scroll position and range
* @details This final class inherits from Widget and provides visual feedback for
* scrollable content. It displays a vertical track with a movable thumb
* that indicates the current position within a scrollable range. The thumb
* size is proportional to the visible area relative to the total content,
* and its position reflects the current scroll offset.
*
* The scrollbar automatically calculates thumb dimensions and position based on
* the provided scroll values (current, minimum, maximum). It is designed to be
* used alongside scrollable content like menus or lists to provide visual
* feedback about scroll state.
*
* @note This class is marked as final and cannot be inherited from.
*
* @see Widget
* @see menu_options_t
*/
class ScrollBar final : public Widget
{
public:
/**
* @brief Constructs a ScrollBar with specified position and dimensions
* @param options Pointer to menu options configuration structure
* @param x X coordinate position of the scrollbar on screen
* @param y Y coordinate position of the scrollbar on screen
* @param width Width of the scrollbar in pixels
* @param height Height of the scrollbar in pixels
*
* @pre options must not be nullptr and must remain valid for the scrollbar's lifetime
* @pre width and height must be greater than 0
* @pre x and y must be valid screen coordinates
* @post ScrollBar is initialized with the specified geometry and ready for use
*
* @note The scrollbar does not take ownership of the options structure and
* assumes it remains valid throughout the scrollbar's lifetime.
*/
ScrollBar(const menu_options_t *options, size_t x, size_t y, size_t width, size_t height);
/**
* @brief Renders the scrollbar to the screen
* @details Overrides the base Widget render method to draw the scrollbar track
* and thumb. The appearance is determined by the current scroll state
* and the menu options configuration.
*
* @pre u8g2 display context must be initialized and ready for drawing
* @post Scrollbar's visual representation is drawn to the display buffer
*
* @note This method is called during each frame's render cycle. The scrollbar
* track and thumb are drawn based on the current scroll values set by refresh().
*/
void Render() override;
/**
* @brief Updates the scrollbar state with new scroll values
* @param value Current scroll position value (must be between min and max)
* @param max Maximum scroll value (total content size)
* @param min Minimum scroll value (default: 0, typically the start of content)
*
* @pre value must be between min and max (inclusive)
* @pre max must be greater than or equal to min
* @post Scrollbar thumb position and size are recalculated based on new values
*
* @details This method recalculates the thumb's height and vertical position
* based on the provided scroll range and current position. The thumb
* height represents the proportion of visible content to total content,
* while its position represents the current scroll offset within the range.
*
* @note Call this method whenever the scroll state changes to keep the
* scrollbar visualization synchronized with the actual content position.
*/
void refresh(size_t value, size_t max, size_t min = 0);
private:
// Position and dimensions
size_t m_x; ///< X coordinate of the scrollbar's left edge
size_t m_y; ///< Y coordinate of the scrollbar's top edge
size_t m_width; ///< Width of the scrollbar track in pixels
size_t m_height; ///< Height of the scrollbar track in pixels
// Scroll state values
size_t m_value; ///< Current scroll position within the range [m_min, m_max]
size_t m_max; ///< Maximum scroll value representing the end of content
size_t m_min; ///< Minimum scroll value representing the start of content
// Calculated thumb properties (updated by refresh())
size_t m_thumbHeight; ///< Calculated height of the scroll thumb in pixels
size_t m_thumbY; ///< Calculated Y position of the scroll thumb relative to track
};

View File

@@ -0,0 +1,181 @@
/**
* @file Widget.h
* @brief Base widget class for UI components in the INSA system
* @details This header defines the Widget base class that serves as the foundation
* for all UI components in the system. It provides a standardized interface
* for rendering, updating, and handling user input using the u8g2 graphics library.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "u8g2.h"
#include "common/Common.h"
/**
* @class Widget
* @brief Base class for UI widgets that can be rendered and interact with user input
* @details This abstract base class provides a common interface for all widgets in the system.
* It manages the u8g2 display context and defines the core methods that all widgets
* must implement or can override. The class follows the template method pattern,
* allowing derived classes to customize behavior while maintaining a consistent
* interface for the UI system.
*
* @note All widgets should inherit from this class to ensure compatibility with
* the UI management system.
*
* @see u8g2_t
* @see ButtonType
*/
class Widget
{
public:
/**
* @brief Constructs a widget with the given u8g2 display context
* @param u8g2 Pointer to the u8g2 display context used for rendering operations
*
* @pre u8g2 must not be nullptr
* @post Widget is initialized with the provided display context
*
* @note The widget does not take ownership of the u8g2 context and assumes
* it remains valid for the lifetime of the widget.
*/
explicit Widget(u8g2_t *u8g2);
/**
* @brief Virtual destructor to ensure proper cleanup of derived classes
* @details Ensures that derived class destructors are called correctly when
* a widget is destroyed through a base class pointer.
*/
virtual ~Widget() = default;
/**
* @brief Called when the widget becomes active or enters the foreground
* @details This method is invoked when the widget transitions from inactive
* to active state, such as when it becomes the current screen or
* gains focus. Derived classes can override this method to perform
* initialization tasks, reset state, or prepare for user interaction.
*
* @note The base implementation is empty, allowing derived classes to override
* only if entry behavior is needed.
* @note This method is typically called by the UI management system during
* screen transitions or focus changes.
*/
virtual void onEnter();
/**
* @brief Called when the widget is temporarily paused or loses focus
* @details This method is invoked when the widget needs to suspend its
* operations temporarily, such as when another widget takes focus
* or the system enters a paused state. Derived classes can override
* this method to pause animations, save state, or reduce resource usage.
*
* @note The base implementation is empty, allowing derived classes to override
* only if pause behavior is needed.
* @note The widget should be prepared to resume from this state when resume() is called.
*/
virtual void onPause();
/**
* @brief Called when the widget resumes from a paused state
* @details This method is invoked when the widget transitions from paused
* to active state, typically after a previous pause() call. Derived
* classes can override this method to restore animations, reload
* resources, or continue interrupted operations.
*
* @note The base implementation is empty, allowing derived classes to override
* only if resume behavior is needed.
* @note This method should restore the widget to the state it was in before pause() was called.
*/
virtual void onResume();
/**
* @brief Called when the widget is being destroyed or exits the system
* @details This method is invoked when the widget is about to be removed
* from the system or transitions to an inactive state permanently.
* Derived classes can override this method to perform cleanup tasks,
* save final state, or release resources that are not automatically freed.
*
* @note The base implementation is empty, allowing derived classes to override
* only if onExit behavior is needed.
* @note This method is called before the widget's destructor and provides
* an opportunity for controlled shutdown of widget-specific resources.
*/
virtual void onExit();
/**
* @brief Updates the widget's internal state based on elapsed time
* @param dt Delta time in milliseconds since the last update call
*
* @details This method is called once per frame by the UI system to handle
* animations, timers, state transitions, or other time-dependent behavior.
* The base implementation is empty, allowing derived classes to override
* only if time-based updates are needed.
*
* @note Override this method in derived classes to implement time-based behavior
* such as animations, blinking effects, or timeout handling.
*/
virtual void Update(uint64_t dt);
/**
* @brief Renders the widget to the display
* @details This method should be overridden by derived classes to implement
* their specific rendering logic using the u8g2 display context.
* The base implementation is empty, requiring derived classes to
* provide their own rendering code.
*
* @pre u8g2 context must be initialized and ready for drawing operations
* @post Widget's visual representation is drawn to the display buffer
*
* @note This method is called during the rendering phase of each frame.
* Derived classes should use the u8g2 member variable to perform
* drawing operations.
*/
virtual void Render();
/**
* @brief Handles button press events
* @param button The type of button that was pressed
*
* @details This method is called when a button press event occurs and allows
* the widget to respond to user input. The base implementation is empty,
* allowing derived classes to override only if input handling is needed.
*
* @note Override this method in derived classes to implement button-specific
* behavior such as navigation, selection, or state changes.
*
* @see ButtonType for available button types
*/
virtual void OnButtonClicked(ButtonType button);
/**
* @brief Returns the name of this widget for diagnostic purposes
* @return A string identifying the widget type
*
* @details This method returns a human-readable name for the widget which
* is used for logging and diagnostic events. Derived classes should
* override this method to return their specific screen/widget name.
*
* @note The base implementation returns "Widget". Override in derived classes
* to provide meaningful screen names for diagnostics.
*/
virtual const char *getName() const;
protected:
/**
* @brief Pointer to the u8g2 display context used for rendering operations
* @details This member provides access to the u8g2 graphics library functions
* for drawing text, shapes, bitmaps, and other UI elements. It is
* initialized during construction and remains valid for the widget's lifetime.
*
* @note This member is protected to allow derived classes direct access while
* preventing external modification. Derived classes should use this
* context for all rendering operations.
*
* @warning Do not modify or delete this pointer. The widget does not own
* the u8g2 context and assumes it is managed externally.
*/
u8g2_t *u8g2;
};

View File

@@ -0,0 +1,310 @@
/**
* @file MenuItem.h
* @brief Menu item data structure for user interface menu systems
* @details This header defines the MenuItem class which represents individual menu
* items that can be displayed and interacted with in user interface menus.
* It supports various types of menu items including simple buttons, toggles,
* value selectors, and multi-option selections with flexible callback handling.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include <functional>
#include <string>
#include <vector>
#include "common/Common.h"
/**
* @class MenuItem
* @brief Flexible menu item class supporting various interaction types and behaviors
* @details This class represents individual menu items that can be displayed in user
* interface menus. It provides a flexible foundation for different types of
* menu interactions including simple navigation buttons, toggle switches,
* value adjustments, and multi-option selections.
*
* The MenuItem class supports multiple interaction patterns:
* - **Simple Actions**: Basic menu items that execute a function when activated
* - **Value Display**: Items that show a current value (read-only or editable)
* - **Selection Lists**: Items that cycle through multiple predefined values
* - **Toggle States**: Boolean items that can be switched on/off
* - **Custom Behaviors**: Flexible callback system for specialized interactions
*
* Each menu item is identified by a unique ID and has a type that defines its
* visual appearance and interaction behavior. The callback system allows for
* flexible event handling while maintaining type safety through std::function.
*
* Key features include:
* - Multiple constructor overloads for different menu item types
* - Type-safe callback system with ButtonCallback function objects
* - Support for both single values and value lists
* - Flexible text and value management
* - Efficient state management and validation
*
* @note This class is designed to be lightweight and efficient for embedded
* systems while providing rich functionality for complex user interfaces.
*
* @see ButtonCallback for callback function signature
* @see ButtonType for available button types
* @see Menu for menu container functionality
*/
class MenuItem
{
public:
/**
* @brief Constructs a simple action menu item with text and callback
* @param id Unique identifier for this menu item within its parent menu
* @param type Type identifier defining the item's behavior and visual appearance
* @param text Display text shown to the user for this menu item
* @param callback Function to call when the item is activated
*
* @pre id must be unique within the parent menu context
* @pre text should not be empty for proper user interface display
* @pre callback should be a valid callable object
* @post MenuItem is initialized as a simple action item ready for display
*
* @details Creates a basic menu item that displays text and executes a callback
* when activated. This is the most common type of menu item used for
* navigation, simple actions, and command execution.
*
* Typical use cases include:
* - Navigation items (e.g., "Settings", "Back", "Exit")
* - Action items (e.g., "Save", "Reset", "Start")
* - Sub-menu entries (e.g., "Light Control", "System Info")
*
* @note The callback is stored as a std::function and can be a lambda,
* function pointer, or any callable object matching the ButtonCallback signature.
*/
MenuItem(uint8_t id, uint8_t type, std::string text, ButtonCallback callback);
/**
* @brief Constructs a value-displaying menu item with text, value, and callback
* @param id Unique identifier for this menu item within its parent menu
* @param type Type identifier defining the item's behavior and visual appearance
* @param text Display text shown to the user for this menu item
* @param value Current value associated with this item (displayed to user)
* @param callback Function to call when the item is activated
*
* @pre id must be unique within the parent menu context
* @pre text should not be empty for proper user interface display
* @pre callback should be a valid callable object
* @post MenuItem is initialized with text and value display capabilities
*
* @details Creates a menu item that displays both text and a current value.
* This type is commonly used for settings display, status information,
* or items where the current state needs to be visible to the user.
*
* Typical use cases include:
* - Setting displays (e.g., "Brightness: 75%")
* - Status information (e.g., "Connection: WiFi")
* - Editable values (e.g., "Timeout: 30s")
* - Current selections (e.g., "Mode: Auto")
*
* @note The value can be updated later using setValue() to reflect changes
* in the underlying system state.
*/
MenuItem(uint8_t id, uint8_t type, std::string text, std::string value, ButtonCallback callback);
/**
* @brief Constructs a multi-selection menu item with selectable values
* @param id Unique identifier for this menu item within its parent menu
* @param type Type identifier defining the item's behavior and visual appearance
* @param text Display text shown to the user for this menu item
* @param values List of all available values that can be selected
* @param index Currently selected value from the available options
* @param callback Function to call when the item is activated
*
* @pre id must be unique within the parent menu context
* @pre text should not be empty for proper user interface display
* @pre value should be present in the values vector
* @pre values should not be empty and should contain valid options
* @pre callback should be a valid callable object
* @post MenuItem is initialized with multiple selectable values
*
* @details Creates a menu item that allows selection from multiple predefined
* values. This type enables cycling through options or displaying
* selection dialogs, making it ideal for configuration settings
* with discrete choices.
*
* Typical use cases include:
* - Mode selection (e.g., "Display Mode: [Day, Night, Auto]")
* - Configuration options (e.g., "Language: [English, Deutsch, Français]")
* - Preset selection (e.g., "Profile: [Home, Office, Travel]")
* - Format selection (e.g., "Time Format: [12H, 24H]")
*
* @note The callback can implement cycling logic to move through the values
* or open a selection dialog for user choice.
* @note The values vector is stored by copy, so modifications to the original
* vector after construction do not affect the menu item.
*/
MenuItem(uint8_t id, uint8_t type, std::string text, std::vector<std::string> values, int index,
ButtonCallback callback);
/**
* @brief Gets the unique identifier of this menu item
* @return The menu item's unique ID as assigned during construction
*
* @details Returns the unique identifier that distinguishes this menu item
* from others within the same menu context. This ID is used by
* menu systems for item identification, event routing, and state management.
*
* @note The ID is immutable after construction and guaranteed to be unique
* within the menu context where this item is used.
*/
[[nodiscard]] uint8_t getId() const;
/**
* @brief Gets the type identifier of this menu item
* @return The menu item's type identifier defining its behavior
*
* @details Returns the type identifier that defines how this menu item
* behaves and appears in the user interface. The type determines
* rendering style, interaction patterns, and event handling behavior.
*
* @note The type is immutable after construction and should correspond
* to predefined menu item type constants defined in the system.
*/
[[nodiscard]] uint8_t getType() const;
/**
* @brief Gets the display text of this menu item
* @return Const reference to the menu item's display text
*
* @details Returns the text that is displayed to the user for this menu item.
* This text serves as the primary label and should be descriptive
* enough for users to understand the item's purpose.
*
* @note Returns a const reference for efficiency while preventing
* accidental modification of the text content.
*/
[[nodiscard]] const std::string &getText() const;
/**
* @brief Gets the current value of this menu item
* @return Const reference to the menu item's current value
*
* @details Returns the current value associated with this menu item.
* For simple action items, this may be empty. For value-based
* items, this represents the current state, selection, or setting.
*
* @note Returns a const reference for efficiency while preventing
* accidental modification through the getter.
* @note For boolean items, the value is typically "true"/"false" or similar.
*/
[[nodiscard]] const std::string &getValue() const;
/**
* @brief Sets a new value for this menu item
* @param value The new value to assign to this menu item
*
* @details Updates the current value of this menu item. This is commonly
* used to reflect changes in system state, user selections, or
* configuration updates. The new value should be appropriate
* for the menu item's type and purpose.
*
* @note For multi-selection items, the value should be one of the
* predefined values in the values vector.
* @note For boolean items, typical values are "true"/"false", "ON"/"OFF",
* or other boolean representations.
* @note The value update does not automatically trigger the callback;
* this is purely for state management.
*/
void setValue(const std::string &value);
/**
* @brief Handles button press events for this menu item
* @param button The type of button that was pressed
*
* @details Processes button press events by invoking the associated callback
* function if one exists. This method serves as the event handler
* that connects user interactions to the menu item's functionality.
*
* The method performs the following actions:
* - Validates that the ID matches this menu item
* - Checks if a callback function is available
* - Invokes the callback with the provided button type
* - Handles any callback-related error conditions gracefully
*
* @note This method is typically called by the parent menu system when
* user interaction occurs on this menu item.
* @note If no callback is set, the method returns without error.
* @note The callback is responsible for implementing the specific behavior
* for the button press (navigation, value changes, actions, etc.).
*/
void onButtonPressed(ButtonType button) const;
/**
* @brief Checks if this menu item has an associated callback function
* @return true if a callback function is set, false otherwise
*
* @details Determines whether this menu item has a valid callback function
* that can be invoked when the item is activated. This is useful
* for menu systems that need to distinguish between interactive
* and non-interactive items.
*
* @note Menu items without callbacks are typically used for display-only
* purposes such as headers, separators, or status information.
* @note Interactive menu items should always have callbacks to provide
* meaningful user interaction.
*/
[[nodiscard]] bool hasCallback() const;
[[nodiscard]] int getIndex() const;
[[nodiscard]] std::vector<std::string> getValues() const;
[[nodiscard]] size_t getItemCount() const;
[[nodiscard]] MenuItem copyWith(const std::string &value) const;
[[nodiscard]] MenuItem copyWith(size_t index) const;
private:
/**
* @brief Unique identifier for this menu item
* @details Stores the unique ID that distinguishes this menu item from others
* within the same menu context. Used for event routing and item identification.
*/
uint8_t m_id;
/**
* @brief Type identifier defining the item's behavior and appearance
* @details Stores the type that determines how this menu item behaves,
* how it's rendered, and what interaction patterns it supports.
*/
uint8_t m_type;
/**
* @brief Display text shown to the user
* @details Stores the primary text label that is displayed to users,
* serving as the main identifier and description of the menu item's purpose.
*/
std::string m_text;
/**
* @brief Current value associated with this menu item
* @details Stores the current value for value-based menu items, representing
* the current state, selection, or setting that should be displayed to the user.
*/
std::string m_value;
/**
* @brief Available values for selection-based menu items
* @details Stores the list of all possible values that can be selected for
* multi-option menu items. Used for cycling through options or
* displaying selection dialogs.
*/
std::vector<std::string> m_values;
int m_index = -1;
/**
* @brief Callback function invoked when the menu item is activated
* @details Stores the function object that implements the menu item's behavior
* when activated by user interaction. Uses std::function for flexibility
* and type safety.
*/
ButtonCallback m_callback;
};

View File

@@ -0,0 +1,23 @@
#define road_horizontal_width 16
#define road_horizontal_height 16
static unsigned char road_horizontal_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x8c, 0xc7, 0x8c, 0xc7, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#define road_vertical_width 16
#define road_vertical_height 16
static unsigned char road_vertical_bits[] = {0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00};
#define road_t_up_width 16
#define road_t_up_height 16
static unsigned char road_t_up_bits[] = {0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
0x03, 0xcc, 0xcf, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#define road_t_crossing_width 16
#define road_t_crossing_height 16
static unsigned char road_t_crossing_bits[] = {0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80,
0x01, 0xe6, 0x67, 0xe6, 0x67, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00,
0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00};

View File

@@ -0,0 +1,55 @@
#pragma once
#define car_width 16
#define car_height 16
const unsigned char car_left_bits [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x0f, 0xee, 0x77, 0xee, 0x77, 0xee, 0x77,
0x2e, 0x74, 0xde, 0x78, 0x02, 0x40, 0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00
};
const unsigned char car_right_bits [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x0f, 0xee, 0x77, 0xee, 0x77, 0xee, 0x77,
0x2e, 0x74, 0x1e, 0x7b, 0x02, 0x40, 0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00
};
#define convertable_width 16
#define convertable_height 16
static unsigned char convertable_left_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x30, 0x0c, 0xee,
0x77, 0x2e, 0x74, 0xae, 0x75, 0xae, 0x75, 0xfe, 0x7f, 0x82, 0x41,
0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00};
static unsigned char convertable_right_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x30, 0x0c, 0xee,
0x77, 0x2e, 0x74, 0xae, 0x75, 0xae, 0x75, 0xfe, 0x7f, 0x82, 0x41,
0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00};
#define lorry_width 32
#define lorry_height 16
const unsigned char lorry_left_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0xe0,
0x0b, 0x00, 0x00, 0xf0, 0x0b, 0x00, 0x00, 0xee, 0x0b, 0x00, 0x00, 0xee, 0x0f,
0xbc, 0x07, 0x2e, 0xfa, 0xff, 0x7f, 0xae, 0xfa, 0xff, 0x7f, 0xbe, 0x0a, 0x00,
0x60, 0x02, 0x0a, 0x00, 0x60, 0x3c, 0x0a, 0xbc, 0x67, 0x42, 0xfe, 0x43, 0x78,
0xda, 0xff, 0x5b, 0x7b, 0x18, 0x00, 0x18, 0x03, 0x00, 0x00, 0x00, 0x00};
const unsigned char lorry_right_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
0x00, 0xd0, 0x07, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xd0, 0x77, 0xe0, 0x3d,
0xf0, 0x77, 0xfe, 0xff, 0x5f, 0x74, 0xfe, 0xff, 0x5f, 0x75, 0x06, 0x00, 0x50,
0x7d, 0x06, 0x00, 0x50, 0x40, 0xe6, 0x3d, 0x50, 0x3c, 0x1e, 0xc2, 0x7f, 0x42,
0xde, 0xda, 0xff, 0x5b, 0xc0, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00};
#define suv_width 16
#define suv_height 16
const unsigned char suv_left_bits[] = {0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x0f, 0xee, 0x77, 0xee,
0x77, 0xee, 0x77, 0x2e, 0x74, 0xde, 0x78, 0x02, 0x40, 0x02, 0x40,
0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00};
const unsigned char suv_right_bits[] = {0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x0f, 0xee, 0x77, 0xee,
0x77, 0xee, 0x77, 0x2e, 0x74, 0x1e, 0x7b, 0x02, 0x40, 0x02, 0x40,
0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00};
#define truck_width 32
#define truck_height 16
const unsigned char truck_left_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3f, 0x00, 0xff, 0xff, 0x7f, 0xe0,
0xff, 0xff, 0x7f, 0xf0, 0xff, 0xff, 0x7f, 0xee, 0xff, 0xff, 0x7f, 0xee, 0x03,
0x00, 0x60, 0x2e, 0x02, 0x00, 0x40, 0xae, 0x02, 0x00, 0x40, 0xbe, 0x02, 0x00,
0x40, 0x02, 0x02, 0x00, 0x40, 0x3c, 0x02, 0xbc, 0x47, 0x42, 0x02, 0x42, 0x48,
0xda, 0xff, 0x5b, 0x7b, 0x18, 0x38, 0x18, 0x03, 0x00, 0x00, 0x00, 0x00};
const unsigned char truck_right_bits[] = {0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0xfe, 0xff, 0xff, 0x00, 0xfe,
0xff, 0xff, 0x07, 0xfe, 0xff, 0xff, 0x0f, 0xfe, 0xff, 0xff, 0x77, 0x06, 0x00,
0xc0, 0x77, 0x02, 0x00, 0x40, 0x74, 0x02, 0x00, 0x40, 0x75, 0x02, 0x00, 0x40,
0x7d, 0x02, 0x00, 0x40, 0x40, 0xe2, 0x3d, 0x40, 0x3c, 0x12, 0x42, 0x40, 0x42,
0xde, 0xda, 0xff, 0x5b, 0xc0, 0x18, 0x1c, 0x18, 0x00, 0x00, 0x00, 0x00};

View File

@@ -0,0 +1,70 @@
/**
* @file ClockScreenSaver.h
* @brief Animated clock screensaver implementation with bouncing time display
* @details This header defines the ClockScreenSaver class which provides an animated
* clock screensaver that shows the system time bouncing around the screen.
* The screensaver prevents screen burn-in while displaying useful information.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "MenuOptions.h"
#include "common/Widget.h"
/**
* @class ClockScreenSaver
* @brief Animated clock screensaver widget for system idle periods
*/
class ClockScreenSaver final : public Widget
{
public:
explicit ClockScreenSaver(menu_options_t *options);
void Update(uint64_t dt) override;
void Render() override;
void OnButtonClicked(ButtonType button) override;
const char *getName() const override;
private:
static constexpr int MOVE_INTERVAL = 50; // milliseconds between movements
static constexpr int X_VELOCITY = 1; // horizontal movement speed
static constexpr int Y_VELOCITY = 1; // vertical movement speed
static constexpr int TEXT_PADDING = 0; // padding around text for bounds checking
static constexpr const uint8_t *FONT = u8g2_font_profont15_tf; // font for time display
menu_options_t *m_options;
uint64_t m_moveTimer;
// Position and movement
int m_posX;
int m_posY;
int m_velocityX;
int m_velocityY;
// Text dimensions (calculated once)
int m_textWidth;
int m_textHeight;
/**
* @brief Initialize position and movement
*/
void initPosition();
/**
* @brief Update the text dimensions based on current time
*/
void updateTextDimensions();
/**
* @brief Get the current time as a formatted string
* @param buffer Buffer to store the formatted time
* @param bufferSize Size of the buffer
*/
void getCurrentTimeString(char *buffer, size_t bufferSize) const;
/**
* @brief Check and handle collision with screen boundaries
*/
void checkBoundaryCollision();
};

View File

@@ -0,0 +1,15 @@
#pragma once
#include "common/Menu.h"
class ExternalDevices final : public Menu
{
public:
explicit ExternalDevices(menu_options_t *options);
const char *getName() const override;
private:
void onButtonPressed(const MenuItem &menuItem, ButtonType button) override;
menu_options_t *m_options;
};

View File

@@ -0,0 +1,143 @@
/**
* @file LightMenu.h
* @brief Light control menu implementation for lighting system management
* @details This header defines the LightMenu class which provides a specialized
* user interface for controlling lighting systems and illumination features.
* It extends the Menu base class to offer comprehensive light control
* functionality including brightness adjustment, color selection, and
* lighting mode configuration.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "common/Menu.h"
/**
* @class LightMenu
* @brief Light control menu interface class for illumination system management
* @details This final class inherits from Menu and provides a comprehensive interface
* for controlling various aspects of the lighting system. It allows users to
* adjust brightness levels, select colors, configure lighting modes, and
* manage automated lighting behaviors.
*
* The LightMenu class extends the base Menu functionality by:
* - Providing light-specific control options (brightness, color, modes)
* - Implementing real-time lighting preview and feedback
* - Managing lighting presets and custom configurations
* - Handling immediate lighting adjustments and scheduled operations
*
* Typical lighting control features include:
* - Brightness adjustment (0-100% or similar range)
* - Color selection (RGB, HSV, or predefined colors)
* - Lighting modes (solid, fade, strobe, custom patterns)
* - Timer-based automation (on/off schedules)
* - Preset management (save/load favorite configurations)
* - Zone-based control (if multiple light zones are supported)
*
* The menu provides immediate visual feedback by applying changes to the
* connected lighting hardware in real-time as users navigate through options.
*
* @note This class is marked as final and cannot be inherited from.
* @note Lighting changes are typically applied immediately for instant feedback,
* with the option to save configurations as presets.
*
* @see Menu for base menu functionality
* @see menu_options_t for configuration structure
* @see MainMenu for navigation back to main interface
*/
class LightMenu final : public Menu
{
public:
/**
* @brief Constructs the light control menu with the specified configuration
* @param options Pointer to menu options configuration structure
*
* @pre options must not be nullptr and must remain valid for the menu's lifetime
* @pre options->u8g2 must be initialized and ready for graphics operations
* @pre All callback functions in options must be properly configured
* @pre Lighting hardware must be initialized and responsive
* @post LightMenu is initialized with current lighting state and ready for user interaction
*
* @details The constructor initializes the light control menu by:
* - Reading current lighting system state and parameters
* - Creating appropriate menu items for available lighting features
* - Setting up real-time preview capabilities
* - Loading saved lighting presets and configurations
* - Configuring value ranges and validation for lighting parameters
*
* The menu automatically detects available lighting capabilities and presents
* only the controls that are supported by the connected hardware. This ensures
* a consistent user experience across different lighting system configurations.
*
* @note The menu does not take ownership of the options structure and assumes
* it remains valid throughout the menu's lifetime.
* @note Current lighting state is preserved and can be restored if the user
* exits without saving changes.
*
* @see Menu::Menu for base class construction details
*/
explicit LightMenu(menu_options_t *options);
const char *getName() const override;
private:
/**
* @brief Handles button press events specific to light control menu items
* @param menuItem
* @param button Type of button that was pressed
*
* @details Overrides the base Menu class method to provide light control-specific
* button handling logic. This method processes user interactions with
* lighting control items and performs appropriate actions such as:
* - Adjusting brightness levels (increment/decrement)
* - Changing color values or selecting predefined colors
* - Switching between lighting modes and patterns
* - Saving/loading lighting presets
* - Toggling lighting zones on/off
* - Applying lighting changes immediately to hardware
*
* The method provides real-time feedback by immediately applying lighting
* changes to the connected hardware, allowing users to see the effects of
* their adjustments instantly. It also handles validation to ensure that
* lighting parameters remain within safe and supported ranges.
*
* Special handling includes:
* - Smooth transitions for brightness adjustments
* - Color wheel navigation for color selection
* - Mode cycling for lighting patterns
* - Confirmation prompts for preset operations
*
* @note This method is called by the base Menu class when a button press
* occurs on a menu item, after the base class has handled standard
* navigation operations.
* @note Changes are applied immediately to provide instant visual feedback,
* but can be reverted if the user cancels or exits without saving.
*
* @see Menu::onButtonPressed for the base implementation
* @see ButtonType for available button types
*/
void onButtonPressed(const MenuItem &menuItem, ButtonType button) override;
/**
* @brief Pointer to menu options configuration structure
* @details Stores a reference to the menu configuration passed during construction.
* This pointer provides access to the display context and callback functions
* needed for menu operations, screen transitions, and lighting control
* communication with the hardware subsystem.
*
* The configuration enables:
* - Display context for rendering lighting control interface
* - Screen management callbacks for navigation to other menus
* - Hardware communication for real-time lighting control
* - System callbacks for lighting state persistence
*
* @note This pointer is not owned by the LightMenu and must remain valid
* throughout the menu's lifetime. It is managed by the application framework.
*
* @warning Must not be modified after construction as it may be shared
* with other components and contains critical system callbacks.
*/
menu_options_t *m_options;
};

View File

@@ -0,0 +1,106 @@
/**
* @file MainMenu.h
* @brief Main menu implementation for the application's primary interface
* @details This header defines the MainMenu class which serves as the primary
* user interface entry point for the application. It extends the Menu
* base class to provide a customized main menu experience with
* application-specific menu items and navigation behavior.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "common/Menu.h"
/**
* @class MainMenu
* @brief Main menu interface class providing the primary application navigation
* @details This final class inherits from Menu and represents the main menu interface
* of the application. It serves as the primary entry point for user interaction
* and provides navigation to all major application features and sub-menus.
*
* The MainMenu class customizes the base Menu functionality by:
* - Defining application-specific menu items during construction
* - Implementing custom button handling for main menu navigation
* - Managing transitions to sub-menus and application features
*
* This class is typically the first screen presented to users after the splash
* screen and serves as the central hub for all application functionality.
*
* @note This class is marked as final and cannot be inherited from.
*
* @see Menu for base menu functionality
* @see menu_options_t for configuration structure
*/
class MainMenu final : public Menu
{
public:
/**
* @brief Constructs the main menu with the specified configuration
* @param options Pointer to menu options configuration structure
*
* @pre options must not be nullptr and must remain valid for the menu's lifetime
* @pre options->u8g2 must be initialized and ready for graphics operations
* @pre All callback functions in options must be properly configured
* @post MainMenu is initialized with application-specific menu items and ready for use
*
* @details The constructor initializes the main menu by setting up all the
* primary application menu items such as:
* - Settings access
* - Feature-specific menus
* - System controls
* - Application onExit options
*
* @note The menu does not take ownership of the options structure and assumes
* it remains valid throughout the menu's lifetime.
*/
explicit MainMenu(menu_options_t *options);
const char *getName() const override;
private:
/**
* @brief Handles button press events specific to main menu items
* @param menuItem
* @param button Type of button that was pressed
*
* @details Overrides the base Menu class method to provide main menu-specific
* button handling logic. This method processes user interactions with
* main menu items and initiates appropriate actions such as:
* - Navigation to sub-menus (Settings, Light Control, etc.)
* - Direct feature activation
* - System operations
*
* The method uses the menu item ID to determine which action to take and
* utilizes the callback functions provided in m_options to perform screen
* transitions or other application-level operations.
*
* @note This method is called by the base Menu class when a button press
* occurs on a menu item, after the base class has handled standard
* navigation operations.
*
* @see Menu::onButtonPressed for the base implementation
* @see ButtonType for available button types
*/
void onButtonPressed(const MenuItem &menuItem, ButtonType button) override;
/**
* @brief Pointer to menu options configuration structure
* @details Stores a reference to the menu configuration passed during construction.
* This pointer provides access to the display context and callback functions
* needed for menu operations, screen transitions, and user interaction handling.
*
* The configuration includes:
* - Display context for rendering operations
* - Screen management callbacks for navigation
* - Input handling callbacks for button events
*
* @note This pointer is not owned by the MainMenu and must remain valid
* throughout the menu's lifetime. It is managed by the application framework.
*
* @warning Must not be modified after construction as it may be shared
* with other components.
*/
menu_options_t *m_options;
};

View File

@@ -0,0 +1,143 @@
/**
* @file ScreenSaver.h
* @brief Animated screensaver implementation with vehicle effect
* @details This header defines the ScreenSaver class which provides an animated
* vehicle screensaver that activates during periods of user inactivity.
* The screensaver displays moving vehicles to prevent screen burn-in while
* providing visual feedback that the system is still active and responsive.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "MenuOptions.h"
#include "common/Widget.h"
#include <vector>
/**
* @class ScreenSaver
* @brief Animated vehicle screensaver widget for system idle periods
*/
class ScreenSaver final : public Widget
{
public:
explicit ScreenSaver(menu_options_t *options);
void Update(uint64_t dt) override;
void Render() override;
void OnButtonClicked(ButtonType button) override;
const char *getName() const override;
private:
/**
* @enum VehicleType
* @brief Types of available vehicles
*/
enum class VehicleType
{
CAR,
CONVERTABLE,
SUV,
LORRY,
TRUCK
};
/**
* @enum Direction
* @brief Movement direction for vehicles
*/
enum class Direction
{
LEFT,
RIGHT
};
/**
* @struct Vehicle
* @brief Individual vehicle object for animation
*/
struct Vehicle
{
int x; // X position on screen
int y; // Y position on screen
float speed; // Movement speed
VehicleType type; // Type of vehicle
Direction direction; // Movement direction
bool active; // Whether a vehicle is currently active
};
static constexpr int MAX_LEFT_VEHICLES = 2;
static constexpr int MAX_RIGHT_VEHICLES = 2;
static constexpr int MAX_VEHICLES = MAX_LEFT_VEHICLES + MAX_RIGHT_VEHICLES;
static constexpr int VEHICLE_SPAWN_DELAY = 2500; // milliseconds
static constexpr float MIN_SPEED = 1.0f;
static constexpr float MAX_SPEED = 2.0f;
static constexpr int MIN_SAME_DIRECTION_DISTANCE = 48; // 32 + 16 pixels
static constexpr int MAX_SAME_DIRECTION_DISTANCE = 64; // 32 + 32 pixels
menu_options_t *m_options;
uint64_t m_animationCounter;
uint64_t m_lastSpawnTime;
std::vector<Vehicle> m_vehicles;
int m_leftVehicleCount;
int m_rightVehicleCount;
int m_sceneOffsetX = 0;
int m_sceneOffsetY = 0;
uint64_t m_sceneShiftTimer = 0;
/**
* @brief Initialize vehicle system
*/
void initVehicles();
/**
* @brief Spawn a new vehicle if conditions are met
*/
void trySpawnVehicle();
/**
* @brief Get a random vehicle type
* @return Random VehicleType
*/
static VehicleType getRandomVehicleType();
/**
* @brief Get a random direction with constraint checking
* @return Direction for new vehicle
*/
static Direction getRandomDirection();
/**
* @brief Draw a vehicle at a specified position
* @param vehicle Vehicle to draw
*/
void drawVehicle(const Vehicle &vehicle) const;
/**
* @brief Draw a bitmap with transparency (black pixels are transparent)
* @param x X position
* @param y Y position
* @param width Bitmap width
* @param height Bitmap height
* @param bitmap Bitmap data
*/
void drawTransparentBitmap(int x, int y, int width, int height, const unsigned char *bitmap) const;
/**
* @brief Get vehicle bitmap data
* @param type Vehicle type
* @param direction Movement direction
* @param width Output parameter for bitmap width
* @param height Output parameter for bitmap height
* @return Pointer to bitmap data
*/
static const unsigned char *getVehicleBitmap(VehicleType type, Direction direction, int &width, int &height);
/**
* @brief Check if there's enough distance to spawn a vehicle in a specific direction
* @param direction Direction to check
* @return true if spawning is allowed
*/
bool canSpawnInDirection(Direction direction) const;
};

View File

@@ -0,0 +1,79 @@
/**
* @file SettingsMenu.h
* @brief Settings menu implementation for application configuration
* @details This header defines the SettingsMenu class which provides a user interface
* for configuring application settings and preferences. It extends the Menu
* base class to offer a specialized settings management interface with
* various configuration options and system parameters.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "common/Menu.h"
/**
* @class SettingsMenu
* @brief Settings menu interface class for application configuration management
* @details This final class inherits from Menu and provides a comprehensive settings
* interface for the application. It allows users to configure various aspects
* of the system including display preferences, system behavior, network
* settings, and other configurable parameters.
*
* The SettingsMenu class extends the base Menu functionality by:
* - Providing settings-specific menu items (toggles, selections, number inputs)
* - Managing configuration persistence and validation
* - Organizing settings into logical categories and sections
* - Implementing real-time preview of setting changes where applicable
*
* Typical settings categories include:
* - Display settings (brightness, contrast, theme)
* - System preferences (timeouts, auto-save, etc.)
* - Network configuration (if applicable)
* - User interface preferences
* - Hardware-specific parameters
*
* @note This class is marked as final and cannot be inherited from.
* @note Settings changes are typically applied immediately or after confirmation,
* depending on the specific setting type and system requirements.
*
* @see Menu for base menu functionality
* @see menu_options_t for configuration structure
* @see MainMenu for navigation back to main interface
*/
class SettingsMenu final : public Menu
{
public:
/**
* @brief Constructs the settings menu with the specified configuration
* @param options Pointer to menu options configuration structure
*
* @pre options must not be nullptr and must remain valid for the menu's lifetime
* @pre options->u8g2 must be initialized and ready for graphics operations
* @pre All callback functions in options must be properly configured
* @post SettingsMenu is initialized with all available configuration options and ready for use
*
* @details The constructor initializes the settings menu by creating all the
* configuration menu items based on the current system state and
* available options. This includes:
* - Loading current setting values from persistent storage
* - Creating appropriate menu items (toggles, selections, number inputs)
* - Setting up validation ranges and allowed values
* - Organizing items in a logical, user-friendly order
*
* The menu automatically detects which settings are available based on
* hardware capabilities and system configuration, ensuring only relevant
* options are presented to the user.
*
* @note The menu does not take ownership of the options structure and assumes
* it remains valid throughout the menu's lifetime.
* @note Setting values are loaded from persistent storage during construction
* and changes are typically saved automatically or on confirmation.
*
* @see Menu::Menu for base class construction details
*/
explicit SettingsMenu(menu_options_t *options);
const char *getName() const override;
};

View File

@@ -0,0 +1,179 @@
/**
* @file SplashScreen.h
* @brief Application splash screen implementation for startup presentation
* @details This header defines the SplashScreen class which provides the initial
* visual presentation when the application starts. It serves as a loading
* screen that displays branding information, initialization progress, and
* provides visual feedback during the application startup sequence.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "MenuOptions.h"
#include "common/Widget.h"
/**
* @class SplashScreen
* @brief Application startup screen widget with branding and initialization feedback
* @details This final class inherits from Widget and represents the initial screen
* displayed when the application starts. It serves multiple purposes including
* brand presentation, system initialization feedback, and smooth transition
* preparation to the main application interface.
*
* The SplashScreen class provides:
* - Brand identity display (logos, company information, product name)
* - System initialization progress indication
* - Smooth animations and visual effects for professional appearance
* - Automatic transition timing to main menu after initialization
* - Error indication if initialization fails
*
* Key features include:
* - Time-based automatic progression to main menu
* - Animated elements (fade-in effects, progress indicators)
* - Version information display
* - Loading status messages
* - Graceful handling of initialization delays
*
* The splash screen typically displays for a minimum duration to ensure users
* can read branding information, even if system initialization completes quickly.
* It automatically transitions to the main menu once both the minimum display
* time and system initialization are complete.
*
* @note This class is marked as final and cannot be inherited from.
* @note The splash screen does not handle user input - it operates on timing
* and system state rather than user interaction.
*
* @see Widget for base widget functionality
* @see menu_options_t for configuration structure
* @see MainMenu for the target transition screen
*/
class SplashScreen final : public Widget
{
public:
/**
* @brief Constructs the splash screen with specified configuration
* @param options Pointer to menu options configuration structure
*
* @pre options must not be nullptr and must remain valid for the splash screen's lifetime
* @pre options->u8g2 must be initialized and ready for graphics operations
* @pre Screen transition callbacks in options must be properly configured
* @post SplashScreen is initialized and ready to display startup sequence
*
* @details The constructor initializes the splash screen by:
* - Setting up the initial display state and animations
* - Preparing branding elements (logos, text, version info)
* - Initializing timing controls for minimum display duration
* - Configuring transition parameters for smooth progression
* - Loading any required graphics resources or assets
*
* The splash screen prepares all visual elements during construction to
* ensure smooth rendering performance during the critical startup phase.
* It also establishes the timing framework for controlling display duration
* and transition timing.
*
* @note The splash screen does not take ownership of the options structure
* and assumes it remains valid throughout the screen's lifetime.
* @note Graphics resources are loaded during construction, so any required
* assets must be available at initialization time.
*
* @see Widget::Widget for base class construction details
*/
explicit SplashScreen(menu_options_t *options);
/**
* @brief Updates the splash screen state, animations, and timing logic
* @param dt Delta time in milliseconds since the last update call
*
* @details Overrides the base Widget update method to handle splash screen-specific
* logic including:
* - Animation progression (fade effects, transitions, progress indicators)
* - Timing control for minimum display duration
* - System initialization status monitoring
* - Automatic transition preparation to main menu
* - Loading progress updates and status message changes
*
* The update method manages the splash screen's lifecycle by tracking
* elapsed time and system readiness state. It ensures the splash screen
* remains visible for a minimum duration while also monitoring system
* initialization completion. Once both conditions are met, it initiates
* the transition to the main application interface.
*
* Animation updates include:
* - Fade-in effects for branding elements
* - Progress bar or spinner animations
* - Text transitions for status messages
* - Smooth preparation for screen transition
*
* @note This method is called every frame during the splash screen display
* and must be efficient to maintain smooth visual presentation.
* @note The method automatically handles transition to the main menu when
* appropriate, using the callback functions provided in m_options.
*
* @see Widget::update for the base update interface
*/
void Update(uint64_t dt) override;
/**
* @brief Renders the splash screen visual elements to the display
*
* @details Overrides the base Widget render method to draw all splash screen
* elements including branding, status information, and visual effects.
* The rendering includes:
* - Company/product logos and branding elements
* - Application name and version information
* - Loading progress indicators (progress bars, spinners, etc.)
* - Status messages indicating initialization progress
* - Background graphics and visual effects
*
* The render method creates a professional, polished appearance that
* reinforces brand identity while providing useful feedback about the
* application startup process. All elements are positioned and styled
* to create a cohesive, attractive presentation.
*
* Rendering features include:
* - Centered layout with balanced visual hierarchy
* - Smooth animations and transitions
* - Consistent typography and color scheme
* - Progress feedback elements
* - Error indication if initialization problems occur
*
* @pre u8g2 display context must be initialized and ready for drawing
* @post All splash screen visual elements are drawn to the display buffer
*
* @note This method is called every frame and must be efficient to maintain
* smooth visual presentation during the startup sequence.
* @note The visual design should be consistent with the overall application
* theme and branding guidelines.
*
* @see Widget::render for the base render interface
*/
void Render() override;
const char *getName() const override;
private:
/**
* @brief Pointer to menu options configuration structure
* @details Stores a reference to the menu configuration passed during construction.
* This provides access to the display context for rendering operations
* and screen transition callbacks for automatic progression to the main menu.
*
* The configuration enables:
* - Display context (u8g2) for graphics rendering operations
* - Screen transition callbacks for automatic progression to main menu
* - System integration for initialization status monitoring
*
* The splash screen uses the setScreen callback to automatically transition
* to the main menu once the startup sequence is complete. This ensures a
* seamless user experience from application launch to main interface.
*
* @note This pointer is not owned by the SplashScreen and must remain valid
* throughout the screen's lifetime. It is managed by the application framework.
*
* @warning Must not be modified after construction as it contains critical
* system callbacks required for proper application flow.
*/
menu_options_t *m_options;
};

View File

@@ -0,0 +1,35 @@
#include "common/InactivityTracker.h"
InactivityTracker::InactivityTracker(const uint64_t timeoutMs, const std::function<void()> &onTimeout)
: m_timeoutMs(timeoutMs), m_elapsedTime(0), m_enabled(true), m_onTimeout(onTimeout)
{
}
void InactivityTracker::update(uint64_t dt)
{
if (!m_enabled)
return;
m_elapsedTime += dt;
if (m_elapsedTime >= m_timeoutMs && m_onTimeout)
{
m_onTimeout();
m_enabled = false;
}
}
void InactivityTracker::reset()
{
m_elapsedTime = 0;
m_enabled = true;
}
void InactivityTracker::setEnabled(bool enabled)
{
m_enabled = enabled;
if (enabled)
{
reset();
}
}

View File

@@ -0,0 +1,386 @@
#include "common/Menu.h"
#include "common/ScrollBar.h"
#include "u8g2.h"
// Menu item type constants for better readability
namespace MenuItemTypes
{
constexpr uint8_t TEXT = 0;
constexpr uint8_t SELECTION = 1;
constexpr uint8_t TOGGLE = 2;
constexpr uint8_t TEXT_COUNTER = 3;
} // namespace MenuItemTypes
// UI layout constants
namespace UIConstants
{
constexpr int LEFT_MARGIN = 8;
constexpr int RIGHT_PADDING = 8;
constexpr int SCROLLBAR_WIDTH = 3;
constexpr int FRAME_BOX_SIZE = 14;
constexpr int FRAME_OFFSET = 11;
constexpr int SELECTION_MARGIN = 10;
constexpr int CORNER_RADIUS = 3;
constexpr int LINE_SPACING = 14;
constexpr int BOTTOM_OFFSET = 10;
} // namespace UIConstants
Menu::Menu(menu_options_t *options) : Widget(options->u8g2), m_options(options)
{
// Set up button callback using lambda to forward to member function
m_options->onButtonClicked = [this](const ButtonType button) { OnButtonClicked(button); };
}
Menu::~Menu()
{
// Clean up callback to prevent dangling pointer
if (m_options)
{
m_options->onButtonClicked = nullptr;
}
}
MenuItem Menu::getItem(const int index)
{
return m_items.at(index);
}
size_t Menu::getItemCount() const
{
return m_items.size();
}
void Menu::setItemSize(const size_t size, int8_t startIndex)
{
if ((m_items.size() - 1) < size)
{
for (size_t i = m_items.size() - 1; i < size; i++)
{
auto caption = std::string("Bereich ") + std::to_string(i + 1 - startIndex);
auto index = 0;
if (m_options && m_options->persistenceManager)
{
constexpr int key_length = 20;
char key[key_length] = "";
snprintf(key, key_length, "section_%zu", i + 1 - startIndex);
index = m_options->persistenceManager->GetValue(key, index);
}
addSelection(i + 1, caption, m_items.at(0).getValues(), index);
}
}
else
{
m_items.erase(m_items.begin() + static_cast<int>(size + 1), m_items.end());
}
}
void Menu::toggle(const MenuItem &menuItem)
{
const auto item =
menuItem.copyWith(menuItem.getValue() == std::to_string(true) ? std::to_string(false) : std::to_string(true));
replaceItem(menuItem.getId(), item);
}
MenuItem Menu::switchValue(const MenuItem &menuItem, ButtonType button)
{
MenuItem result = menuItem;
switch (button)
{
case ButtonType::LEFT:
if (menuItem.getIndex() > 0)
{
const auto item = menuItem.copyWith(menuItem.getIndex() - 1);
result = replaceItem(menuItem.getId(), item);
}
else
{
const auto item = menuItem.copyWith(menuItem.getItemCount() - 1);
result = replaceItem(menuItem.getId(), item);
}
break;
case ButtonType::RIGHT:
if (menuItem.getIndex() < menuItem.getItemCount() - 1)
{
const auto item = menuItem.copyWith(menuItem.getIndex() + 1);
result = replaceItem(menuItem.getId(), item);
}
else
{
const auto item = menuItem.copyWith(0);
result = replaceItem(menuItem.getId(), item);
}
break;
default:
break;
}
return result;
}
MenuItem Menu::replaceItem(const int index, const MenuItem &item)
{
m_items.at(index) = item;
return item;
}
void Menu::Render()
{
// Initialize selection if not set
if (m_selected_item >= m_items.size() && !m_items.empty())
{
m_selected_item = 0;
}
// Early return if no items to render
if (m_items.empty())
{
return;
}
// Clear screen with black background
u8g2_SetDrawColor(u8g2, 0);
u8g2_DrawBox(u8g2, 0, 0, u8g2->width, u8g2->height);
// Set white foreground color for drawing
u8g2_SetDrawColor(u8g2, 1);
// Draw UI components
drawScrollBar();
drawSelectionBox();
// Calculate center position for main item
const int centerY = u8g2->height / 2 + 3;
// Render the currently selected item (main/center item)
const auto &selectedItem = m_items[m_selected_item];
renderWidget(&selectedItem, u8g2_font_helvB08_tr, UIConstants::LEFT_MARGIN, centerY);
// Render previous item (above) if available
if (m_selected_item > 0)
{
const auto &prevItem = m_items[m_selected_item - 1];
renderWidget(&prevItem, u8g2_font_haxrcorp4089_tr, UIConstants::LEFT_MARGIN, UIConstants::LINE_SPACING);
}
// Render next item (below) if available
if (m_selected_item < m_items.size() - 1)
{
const auto &nextItem = m_items[m_selected_item + 1];
renderWidget(&nextItem, u8g2_font_haxrcorp4089_tr, UIConstants::LEFT_MARGIN,
u8g2->height - UIConstants::BOTTOM_OFFSET);
}
}
void Menu::renderWidget(const MenuItem *item, const uint8_t *font, const int x, const int y) const
{
// Set font and draw main text
u8g2_SetFont(u8g2, font);
u8g2_DrawStr(u8g2, x, y, item->getText().c_str());
// Render type-specific elements
switch (item->getType())
{
case MenuItemTypes::TEXT: {
const std::string formattedValue = ">";
const u8g2_uint_t textWidth = u8g2_GetStrWidth(u8g2, formattedValue.c_str());
u8g2_DrawStr(u8g2, u8g2->width - textWidth - UIConstants::SELECTION_MARGIN, y, formattedValue.c_str());
break;
}
case MenuItemTypes::TEXT_COUNTER: {
const std::string formattedValue = "(" + item->getValue() + ") >";
const u8g2_uint_t textWidth = u8g2_GetStrWidth(u8g2, formattedValue.c_str());
u8g2_DrawStr(u8g2, u8g2->width - textWidth - UIConstants::SELECTION_MARGIN, y, formattedValue.c_str());
break;
}
case MenuItemTypes::SELECTION: {
// Format selection value with angle brackets
const std::string formattedValue = "< " + item->getValue() + " >";
const u8g2_uint_t textWidth = u8g2_GetStrWidth(u8g2, formattedValue.c_str());
u8g2_DrawStr(u8g2, u8g2->width - textWidth - UIConstants::SELECTION_MARGIN, y, formattedValue.c_str());
break;
}
case MenuItemTypes::TOGGLE: {
// Draw checkbox frame
const int frameX = u8g2->width - UIConstants::FRAME_BOX_SIZE - UIConstants::SELECTION_MARGIN;
const int frameY = y - UIConstants::FRAME_OFFSET;
u8g2_DrawFrame(u8g2, frameX, frameY, UIConstants::FRAME_BOX_SIZE, UIConstants::FRAME_BOX_SIZE);
// Draw checkmark (X) if toggle is true
if (item->getValue() == std::to_string(true))
{
const int checkX1 = frameX + 2;
const int checkY1 = frameY + 2;
const int checkX2 = frameX + UIConstants::FRAME_BOX_SIZE - 3;
const int checkY2 = frameY + UIConstants::FRAME_BOX_SIZE - 3;
// Draw X pattern for checked state
u8g2_DrawLine(u8g2, checkX1, checkY1, checkX2, checkY2);
u8g2_DrawLine(u8g2, checkX1, checkY2, checkX2, checkY1);
}
break;
}
default:
// No additional rendering needed for text and number types
break;
}
}
void Menu::OnButtonClicked(const ButtonType button)
{
// Map button input to navigation functions
switch (button)
{
case ButtonType::UP:
onPressedUp();
break;
case ButtonType::DOWN:
onPressedDown();
break;
case ButtonType::LEFT:
onPressedLeft();
break;
case ButtonType::RIGHT:
onPressedRight();
break;
case ButtonType::SELECT:
onPressedSelect();
break;
case ButtonType::BACK:
onPressedBack();
break;
default:
// Ignore unknown button inputs
break;
}
}
void Menu::onPressedDown()
{
if (m_items.empty())
return;
// Wrap around to first item when at the end
m_selected_item = (m_selected_item + 1) % m_items.size();
}
void Menu::onPressedUp()
{
if (m_items.empty())
return;
// Wrap around to last item when at the beginning
m_selected_item = (m_selected_item == 0) ? m_items.size() - 1 : m_selected_item - 1;
}
void Menu::onPressedLeft() const
{
if (m_selected_item < m_items.size())
{
const auto &item = m_items.at(m_selected_item);
item.onButtonPressed(ButtonType::LEFT);
}
}
void Menu::onPressedRight() const
{
if (m_selected_item < m_items.size())
{
const auto &item = m_items.at(m_selected_item);
item.onButtonPressed(ButtonType::RIGHT);
}
}
void Menu::onPressedSelect() const
{
if (m_selected_item < m_items.size())
{
const auto &item = m_items.at(m_selected_item);
item.onButtonPressed(ButtonType::SELECT);
}
}
void Menu::onPressedBack() const
{
// Navigate back to previous screen if callback is available
if (m_options && m_options->popScreen)
{
m_options->popScreen();
}
}
void Menu::addText(uint8_t id, const std::string &text)
{
addTextCounter(id, text, 0);
}
void Menu::addTextCounter(uint8_t id, const std::string &text, const uint8_t value)
{
auto callback = [this](const MenuItem &menuItem, const ButtonType button) -> void {
onButtonPressed(menuItem, button);
};
if (value > 0)
{
m_items.emplace_back(id, MenuItemTypes::TEXT_COUNTER, text, std::to_string(value), callback);
}
else
{
m_items.emplace_back(id, MenuItemTypes::TEXT, text, callback);
}
}
void Menu::addSelection(uint8_t id, const std::string &text, const std::vector<std::string> &values, const int index)
{
auto callback = [this](const MenuItem &menuItem, const ButtonType button) -> void {
onButtonPressed(menuItem, button);
};
m_items.emplace_back(id, MenuItemTypes::SELECTION, text, values, index, callback);
}
void Menu::addToggle(uint8_t id, const std::string &text, const bool selected)
{
auto callback = [this](const MenuItem &menuItem, const ButtonType button) -> void {
onButtonPressed(menuItem, button);
};
m_items.emplace_back(id, MenuItemTypes::TOGGLE, text, std::to_string(selected), callback);
}
void Menu::drawScrollBar() const
{
// Create scrollbar instance
ScrollBar scrollBar(m_options, u8g2->width - UIConstants::SCROLLBAR_WIDTH, 3, 1, u8g2->height - 6);
scrollBar.refresh(m_selected_item, m_items.size());
scrollBar.Render();
}
void Menu::drawSelectionBox() const
{
// Calculate dimensions for the selection box
const auto displayHeight = u8g2->height;
const auto displayWidth = u8g2->width;
const auto boxHeight = displayHeight / 3;
const auto y = boxHeight * 2 - 2;
const auto x = displayWidth - UIConstants::RIGHT_PADDING;
// Draw the rounded frame for the selection box
u8g2_DrawRFrame(u8g2, 2, boxHeight, displayWidth - UIConstants::RIGHT_PADDING, boxHeight,
UIConstants::CORNER_RADIUS);
// Draw horizontal line separator
u8g2_DrawLine(u8g2, 4, y, displayWidth - UIConstants::RIGHT_PADDING, y);
// Draw vertical line on the right side
u8g2_DrawLine(u8g2, x, y - boxHeight + 3, x, y - 1);
}

View File

@@ -0,0 +1,41 @@
#include "common/ScrollBar.h"
ScrollBar::ScrollBar(const menu_options_t *options, const size_t x, const size_t y, const size_t width,
const size_t height)
: Widget(options->u8g2), m_x(x), m_y(y), m_width(width), m_height(height), m_value(0), m_max(0), m_min(0),
m_thumbHeight(0), m_thumbY(0)
{
}
void ScrollBar::Render()
{
if (m_max <= 1)
{
return;
}
for (size_t y = m_y; y < m_y + m_height; y += 2)
{
u8g2_DrawPixel(u8g2, m_x, y);
}
u8g2_DrawBox(u8g2, u8g2->width - 4, m_thumbY, 3, m_thumbHeight);
}
void ScrollBar::refresh(const size_t value, const size_t max, const size_t min)
{
m_value = value;
m_max = max;
m_min = min;
if (m_max <= 1)
{
return;
}
m_thumbHeight = std::max(m_height / 4, static_cast<size_t>(3));
const size_t trackLength = m_height - m_thumbHeight;
m_thumbY = m_y + (m_value * trackLength) / (m_max - 1);
}

View File

@@ -0,0 +1,38 @@
#include "common/Widget.h"
Widget::Widget(u8g2_t *u8g2) : u8g2(u8g2)
{
}
void Widget::onEnter()
{
}
void Widget::onPause()
{
}
void Widget::onResume()
{
}
void Widget::onExit()
{
}
void Widget::Update(uint64_t dt)
{
}
void Widget::Render()
{
}
void Widget::OnButtonClicked(ButtonType button)
{
}
const char *Widget::getName() const
{
return "Widget";
}

View File

@@ -0,0 +1,99 @@
#include "data/MenuItem.h"
// Constructor for basic menu items (text buttons)
MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, ButtonCallback callback)
: m_id(id), m_type(type), m_text(std::move(text)), m_callback(std::move(callback))
{
}
// Constructor for menu items with a single value (toggles)
MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, std::string value, ButtonCallback callback)
: m_id(id), m_type(type), m_text(std::move(text)), m_value(std::move(value)), m_callback(std::move(callback))
{
}
// Constructor for menu items with multiple values (selections)
MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, std::vector<std::string> values, int index,
ButtonCallback callback)
: m_id(id), m_type(type), m_text(std::move(text)), m_values(std::move(values)), m_index(index),
m_callback(std::move(callback))
{
}
uint8_t MenuItem::getId() const
{
return m_id;
}
uint8_t MenuItem::getType() const
{
return m_type;
}
const std::string &MenuItem::getText() const
{
return m_text;
}
const std::string &MenuItem::getValue() const
{
// Return the selected value from values array if available and index is valid
if (!m_values.empty() && m_index >= 0 && m_index < m_values.size())
{
return m_values.at(m_index);
}
// Otherwise return the direct value
return m_value;
}
void MenuItem::setValue(const std::string &value)
{
m_value = value;
}
void MenuItem::onButtonPressed(const ButtonType button) const
{
// Execute the callback function if one is registered
if (m_callback)
{
m_callback(*this, button);
}
}
bool MenuItem::hasCallback() const
{
return (m_callback != nullptr);
}
int MenuItem::getIndex() const
{
return m_index;
}
std::vector<std::string> MenuItem::getValues() const
{
return m_values;
}
size_t MenuItem::getItemCount() const
{
return m_values.size();
}
MenuItem MenuItem::copyWith(const std::string &value) const
{
// Create a copy of this menu item with a new value
MenuItem copy = *this;
copy.m_value = value;
return copy;
}
MenuItem MenuItem::copyWith(const size_t index) const
{
// Create a copy of this menu item with a new selected index
MenuItem copy = *this;
// Check for potential overflow when converting size_t to int
copy.m_index = static_cast<int>(index);
return copy;
}

View File

@@ -0,0 +1,136 @@
#include "ui/ClockScreenSaver.h"
#include "hal_esp32/PersistenceManager.h"
#include "simulator.h"
#include <cstring>
#include <ctime>
ClockScreenSaver::ClockScreenSaver(menu_options_t *options)
: Widget(options->u8g2), m_options(options), m_moveTimer(0), m_posX(0), m_posY(0), m_velocityX(X_VELOCITY),
m_velocityY(Y_VELOCITY), m_textWidth(0), m_textHeight(0)
{
initPosition();
}
void ClockScreenSaver::initPosition()
{
// Start in the center of the screen
updateTextDimensions();
m_posX = (u8g2->width - m_textWidth) / 2;
m_posY = (u8g2->height - m_textHeight) / 2;
// Set initial velocity
m_velocityX = X_VELOCITY;
m_velocityY = Y_VELOCITY;
}
void ClockScreenSaver::updateTextDimensions()
{
char timeBuffer[32];
getCurrentTimeString(timeBuffer, sizeof(timeBuffer));
// Set font (use a large font for better visibility)
u8g2_SetFont(u8g2, FONT);
// Calculate text dimensions
m_textWidth = u8g2_GetStrWidth(u8g2, timeBuffer);
m_textHeight = u8g2_GetAscent(u8g2) - u8g2_GetDescent(u8g2);
}
void ClockScreenSaver::getCurrentTimeString(char *buffer, size_t bufferSize) const
{
if (m_options && m_options->persistenceManager->GetValue("light_active", false) &&
m_options->persistenceManager->GetValue("light_mode", 0) == 0)
{
char *simulated_time = get_time();
if (simulated_time != nullptr)
{
strncpy(buffer, simulated_time, bufferSize);
return;
}
}
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
// Format time as HH:MM:SS
strftime(buffer, bufferSize, "%H:%M:%S", timeinfo);
}
void ClockScreenSaver::Update(const uint64_t dt)
{
m_moveTimer += dt;
// Move the clock position at regular intervals
if (m_moveTimer > MOVE_INTERVAL)
{
m_moveTimer = 0;
// Update text dimensions (in case time format changes)
updateTextDimensions();
// Update position
m_posX += m_velocityX;
m_posY += m_velocityY;
// Check for collisions with screen boundaries
checkBoundaryCollision();
}
}
void ClockScreenSaver::checkBoundaryCollision()
{
// Check horizontal boundaries
if (m_posX <= TEXT_PADDING)
{
m_posX = TEXT_PADDING;
m_velocityX = X_VELOCITY; // Bounce right
}
else if (m_posX + m_textWidth >= u8g2->width - TEXT_PADDING)
{
m_posX = u8g2->width - m_textWidth - TEXT_PADDING;
m_velocityX = -X_VELOCITY; // Bounce left
}
// Check vertical boundaries
if (m_posY <= TEXT_PADDING + m_textHeight)
{
m_posY = TEXT_PADDING + m_textHeight;
m_velocityY = Y_VELOCITY; // Bounce down
}
else if (m_posY >= u8g2->height - TEXT_PADDING)
{
m_posY = u8g2->height - TEXT_PADDING;
m_velocityY = -Y_VELOCITY; // Bounce up
}
}
void ClockScreenSaver::Render()
{
// Clear screen with a black background
u8g2_SetDrawColor(u8g2, 0);
u8g2_DrawBox(u8g2, 0, 0, u8g2->width, u8g2->height);
u8g2_SetDrawColor(u8g2, 1);
// Get current time
char timeBuffer[32];
getCurrentTimeString(timeBuffer, sizeof(timeBuffer));
// Set font
u8g2_SetFont(u8g2, FONT);
// Draw the time at current position
u8g2_DrawStr(u8g2, m_posX, m_posY, timeBuffer);
}
void ClockScreenSaver::OnButtonClicked(ButtonType button)
{
// Exit screensaver on any button press
if (m_options && m_options->popScreen)
{
m_options->popScreen();
}
}
IMPLEMENT_GET_NAME(ClockScreenSaver)

View File

@@ -0,0 +1,27 @@
#include "ui/ExternalDevices.h"
#include "ble/ble_device.h"
#include "ble/ble_scanner.h"
#include "ble_manager.h"
ExternalDevices::ExternalDevices(menu_options_t *options) : Menu(options), m_options(options)
{
for (int i = 0; i < get_device_count(); i++)
{
device_info_t *device = get_device(i);
if (device != nullptr)
{
addText(i, device->name);
}
}
}
void ExternalDevices::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
if (button == ButtonType::SELECT)
{
ble_connect_to_device(menuItem.getId());
}
}
IMPLEMENT_GET_NAME(ExternalDevices)

View File

@@ -0,0 +1,127 @@
#include "ui/LightMenu.h"
#include "led_strip_ws2812.h"
#include "simulator.h"
/**
* @namespace LightMenuItem
* @brief Constants for light menu item identifiers
*/
namespace LightMenuItem
{
constexpr auto ACTIVATE = 0; ///< ID for the light activation toggle
constexpr auto MODE = 1; ///< ID for the light mode selection
constexpr auto VARIANT = 2; ///< ID for the light variant selection
} // namespace LightMenuItem
namespace LightMenuOptions
{
constexpr auto LIGHT_ACTIVE = "light_active";
constexpr auto LIGHT_MODE = "light_mode";
constexpr auto LIGHT_VARIANT = "light_variant";
} // namespace LightMenuOptions
LightMenu::LightMenu(menu_options_t *options) : Menu(options), m_options(options)
{
// Add toggle for enabling/disabling the light system
bool active = false;
if (m_options && m_options->persistenceManager)
{
active = m_options->persistenceManager->GetValue(LightMenuOptions::LIGHT_ACTIVE, active);
}
addToggle(LightMenuItem::ACTIVATE, "Einschalten", active);
// Create mode selection options (Simulation/Day/Night modes)
std::vector<std::string> items;
items.emplace_back("Simulation"); // Simulation mode
items.emplace_back("Tag"); // Day mode
items.emplace_back("Nacht"); // Night mode
int mode_value = 0;
if (m_options && m_options->persistenceManager)
{
mode_value = m_options->persistenceManager->GetValue(LightMenuOptions::LIGHT_MODE, mode_value);
}
addSelection(LightMenuItem::MODE, "Modus", items, mode_value);
std::vector<std::string> variants;
variants.emplace_back("1");
variants.emplace_back("2");
variants.emplace_back("3");
int variant_value = 3;
if (m_options && m_options->persistenceManager)
{
variant_value = m_options->persistenceManager->GetValue(LightMenuOptions::LIGHT_VARIANT, variant_value) - 1;
}
addSelection(LightMenuItem::VARIANT, "Variante", variants, variant_value);
}
void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
std::shared_ptr<Widget> widget;
// Handle different menu items based on their ID
switch (menuItem.getId())
{
case LightMenuItem::ACTIVATE: {
// Toggle the light activation state when SELECT is pressed
if (button == ButtonType::SELECT)
{
toggle(menuItem);
const auto value = getItem(menuItem.getId()).getValue() == "1";
if (m_options && m_options->persistenceManager)
{
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_ACTIVE, value);
}
start_simulation();
}
break;
}
case LightMenuItem::MODE: {
// Switch between day/night modes using left/right buttons
const auto item = switchValue(menuItem, button);
if (button == ButtonType::LEFT || button == ButtonType::RIGHT)
{
const auto value = getItem(item.getId()).getIndex();
if (m_options && m_options->persistenceManager)
{
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_MODE, value);
m_options->persistenceManager->Save();
}
start_simulation();
}
break;
}
case LightMenuItem::VARIANT: {
// Change light variant using left/right buttons
const auto item = switchValue(menuItem, button);
if (button == ButtonType::LEFT || button == ButtonType::RIGHT)
{
const auto value = getItem(item.getId()).getIndex() + 1;
if (m_options && m_options->persistenceManager)
{
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_VARIANT, value);
m_options->persistenceManager->Save();
}
start_simulation();
}
break;
}
default:
// Handle unknown menu items (no action required)
break;
}
// Push the new widget to the screen stack if one was created
if (m_options && m_options->pushScreen)
{
m_options->pushScreen(widget);
}
}
IMPLEMENT_GET_NAME(LightMenu)

View File

@@ -0,0 +1,52 @@
#include "ui/MainMenu.h"
#include "common/Widget.h"
#include "ui/ExternalDevices.h"
#include "ui/LightMenu.h"
#include "ui/SettingsMenu.h"
namespace MainMenuItem
{
constexpr uint8_t LIGHT = 0;
constexpr uint8_t EXTERNAL_DEVICES = 1;
constexpr uint8_t SETTINGS = 2;
} // namespace MainMenuItem
MainMenu::MainMenu(menu_options_t *options) : Menu(options), m_options(options)
{
addText(MainMenuItem::LIGHT, "Lichtsteuerung");
// addTextCounter(MainMenuItem::EXTERNAL_DEVICES, "ext. Geraete", 0);
addText(MainMenuItem::SETTINGS, "Einstellungen");
}
void MainMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
if (button == ButtonType::SELECT)
{
std::shared_ptr<Widget> widget;
switch (menuItem.getId())
{
case MainMenuItem::LIGHT:
widget = std::make_shared<LightMenu>(m_options);
break;
case MainMenuItem::SETTINGS:
widget = std::make_shared<SettingsMenu>(m_options);
break;
case MainMenuItem::EXTERNAL_DEVICES:
widget = std::make_shared<ExternalDevices>(m_options);
break;
default:
break;
}
if (m_options && m_options->pushScreen)
{
m_options->pushScreen(widget);
}
}
}
IMPLEMENT_GET_NAME(MainMenu)

View File

@@ -0,0 +1,331 @@
#include "ui/ScreenSaver.h"
#include "data/roads.h"
#include "data/vehicles.h"
#include <cstdlib>
ScreenSaver::ScreenSaver(menu_options_t *options)
: Widget(options->u8g2), m_options(options), m_animationCounter(0), m_lastSpawnTime(0), m_leftVehicleCount(0),
m_rightVehicleCount(0)
{
initVehicles();
}
void ScreenSaver::initVehicles()
{
m_vehicles.resize(MAX_VEHICLES);
for (auto &vehicle : m_vehicles)
{
vehicle.active = false;
}
}
void ScreenSaver::Update(const uint64_t dt)
{
m_animationCounter += dt;
m_lastSpawnTime += dt;
m_sceneShiftTimer += dt;
// Shift entire scene every 30 seconds
if (m_sceneShiftTimer > 30000)
{
m_sceneOffsetX = (random() % 7) - 3; // -3 to +3 pixels
m_sceneOffsetY = (random() % 7) - 3; // -3 to +3 pixels
m_sceneShiftTimer = 0;
}
// Try to spawn a new vehicle every few seconds
if (m_lastSpawnTime > VEHICLE_SPAWN_DELAY)
{
trySpawnVehicle();
m_lastSpawnTime = 0;
}
// Update vehicle positions
if (m_animationCounter > 16) // ~60 FPS
{
m_animationCounter = 0;
for (auto &vehicle : m_vehicles)
{
if (!vehicle.active)
continue;
// Move vehicle
if (vehicle.direction == Direction::LEFT)
{
vehicle.x -= static_cast<int>(vehicle.speed);
// Remove the vehicle if it goes off-screen
if (vehicle.x <= -32) // Allow for largest vehicle width
{
vehicle.active = false;
m_leftVehicleCount--;
}
}
else // Direction::RIGHT
{
vehicle.x += static_cast<int>(vehicle.speed);
// Remove the vehicle if it goes off-screen
if (vehicle.x >= (u8g2->width + 32)) // Allow for largest vehicle width
{
vehicle.active = false;
m_rightVehicleCount--;
}
}
}
}
}
bool ScreenSaver::canSpawnInDirection(Direction direction) const
{
// Minimalen Abstand zwischen 48 und 64 Pixel zufällig wählen
int requiredDistance =
MIN_SAME_DIRECTION_DISTANCE + (random() % (MAX_SAME_DIRECTION_DISTANCE - MIN_SAME_DIRECTION_DISTANCE + 1));
for (const auto &vehicle : m_vehicles)
{
if (!vehicle.active || vehicle.direction != direction)
continue;
// Abstand zum nächsten Fahrzeug in gleicher Richtung prüfen
if (direction == Direction::LEFT)
{
// Fahrzeuge fahren von rechts nach links
// Neues Fahrzeug würde bei u8g2->width + 16 starten
int newVehicleX = u8g2->width + 16;
// Prüfen ob genug Abstand zum existierenden Fahrzeug
if (newVehicleX - vehicle.x < requiredDistance)
return false;
}
else // Direction::RIGHT
{
// Fahrzeuge fahren von links nach rechts
// Neues Fahrzeug würde bei -32 starten
int newVehicleX = -32;
// Prüfen ob genug Abstand zum existierenden Fahrzeug
if (vehicle.x - newVehicleX < requiredDistance)
return false;
}
}
return true;
}
void ScreenSaver::trySpawnVehicle()
{
// Check if we can spawn a new vehicle
int activeVehicles = 0;
int availableSlot = -1;
for (int i = 0; i < MAX_VEHICLES; i++)
{
if (m_vehicles[i].active)
{
activeVehicles++;
}
else if (availableSlot == -1)
{
availableSlot = i;
}
}
// Don't spawn if we're at max capacity or no slot available
if (activeVehicles >= MAX_VEHICLES || availableSlot == -1)
{
return;
}
Direction direction = getRandomDirection();
// Check direction constraints
if ((direction == Direction::LEFT && m_leftVehicleCount >= MAX_LEFT_VEHICLES) ||
(direction == Direction::RIGHT && m_rightVehicleCount >= MAX_RIGHT_VEHICLES))
{
return;
}
if (!canSpawnInDirection(direction))
{
return;
}
// Create new vehicle
Vehicle &newVehicle = m_vehicles[availableSlot];
newVehicle.type = getRandomVehicleType();
newVehicle.direction = direction;
newVehicle.speed = MIN_SPEED + (static_cast<float>(random()) / RAND_MAX) * (MAX_SPEED - MIN_SPEED);
// Set Y position based on a direction (simulate opposing traffic lanes)
const int halfHeight = u8g2->height / 2;
if (direction == Direction::RIGHT)
{
// Vehicles going LEFT use bottom half of screen
newVehicle.y = halfHeight + 8 + (random() % (halfHeight - 24));
m_rightVehicleCount++;
}
else // Direction::RIGHT
{
// Vehicles going RIGHT use top half of screen
newVehicle.y = 8 + (random() % (halfHeight - 24));
m_leftVehicleCount++;
}
// Set the starting X position based on the direction
if (direction == Direction::LEFT)
{
// Vehicles going LEFT (from right to left) start from RIGHT side of screen
newVehicle.x = u8g2->width + 16;
}
else // Direction::RIGHT
{
// Vehicles going RIGHT (from left to right) start from LEFT side of screen
newVehicle.x = -32; // Account for the largest vehicle width
}
newVehicle.active = true;
}
ScreenSaver::VehicleType ScreenSaver::getRandomVehicleType()
{
switch (random() % 5)
{
case 0:
return VehicleType::CAR;
case 1:
return VehicleType::CONVERTABLE;
case 2:
return VehicleType::SUV;
case 3:
return VehicleType::LORRY;
case 4:
return VehicleType::TRUCK;
default:
return VehicleType::CAR;
}
}
ScreenSaver::Direction ScreenSaver::getRandomDirection()
{
// Simple 50/50 chance for each direction
return (random() % 2 == 0) ? Direction::LEFT : Direction::RIGHT;
}
void ScreenSaver::Render()
{
// Clear screen with a black background
u8g2_SetDrawColor(u8g2, 0);
u8g2_DrawBox(u8g2, 0, 0, u8g2->width, u8g2->height);
u8g2_SetDrawColor(u8g2, 1);
// Calculate offsets
const int roadOffset = (m_animationCounter / 100) % road_horizontal_width;
// Draw all active vehicles with a scene offset
for (const auto &vehicle : m_vehicles)
{
if (vehicle.active)
{
Vehicle offsetVehicle = vehicle;
offsetVehicle.x += m_sceneOffsetX;
offsetVehicle.y += m_sceneOffsetY;
drawVehicle(offsetVehicle);
}
}
// Draw road with offsets
const int y = u8g2->height / 2 - road_horizontal_height / 2 + m_sceneOffsetY;
for (int x = -road_horizontal_width + roadOffset + m_sceneOffsetX; x <= u8g2->width; x += road_horizontal_width)
{
drawTransparentBitmap(x, y, road_horizontal_width, road_horizontal_height, road_horizontal_bits);
}
}
void ScreenSaver::drawVehicle(const Vehicle &vehicle) const
{
int width, height;
if (const unsigned char *bitmap = getVehicleBitmap(vehicle.type, vehicle.direction, width, height))
{
drawTransparentBitmap(vehicle.x, vehicle.y, width, height, bitmap);
// u8g2_DrawXBM(u8g2, vehicle.x, vehicle.y, width, height, bitmap);
}
}
void ScreenSaver::drawTransparentBitmap(const int x, const int y, const int width, const int height,
const unsigned char *bitmap) const
{
for (int py = 0; py < height; py++)
{
for (int px = 0; px < width; px++)
{
// Calculate byte and a bit of position in bitmap
const int byteIndex = (py * ((width + 7) / 8)) + (px / 8);
// Check if the pixel is set (white)
if (const int bitIndex = px % 8; bitmap[byteIndex] & (1 << bitIndex))
{
// Only draw white pixels, skip black (transparent) pixels
const int screenX = x + px;
// Bounds checking
if (const int screenY = y + py;
screenX >= 0 && screenX < u8g2->width && screenY >= 0 && screenY < u8g2->height)
{
u8g2_DrawPixel(u8g2, screenX, screenY);
}
}
// Black pixels are simply not drawn (transparent)
}
}
}
const unsigned char *ScreenSaver::getVehicleBitmap(const VehicleType type, const Direction direction, int &width,
int &height)
{
switch (type)
{
case VehicleType::CAR:
width = car_width;
height = car_height;
return (direction == Direction::LEFT) ? car_left_bits : car_right_bits;
case VehicleType::CONVERTABLE:
width = convertable_width;
height = convertable_height;
return (direction == Direction::LEFT) ? convertable_left_bits : convertable_right_bits;
case VehicleType::SUV:
width = suv_width;
height = suv_height;
return (direction == Direction::LEFT) ? suv_left_bits : suv_right_bits;
case VehicleType::LORRY:
width = lorry_width;
height = lorry_height;
return (direction == Direction::LEFT) ? lorry_left_bits : lorry_right_bits;
case VehicleType::TRUCK:
width = truck_width;
height = truck_height;
return (direction == Direction::LEFT) ? truck_left_bits : truck_right_bits;
default:
width = car_width;
height = car_height;
return car_left_bits;
}
}
void ScreenSaver::OnButtonClicked(ButtonType button)
{
if (m_options && m_options->popScreen)
{
m_options->popScreen();
}
}
IMPLEMENT_GET_NAME(ScreenSaver)

View File

@@ -0,0 +1,13 @@
#include "ui/SettingsMenu.h"
namespace SettingsMenuItem
{
constexpr uint8_t OTA_UPLOAD = 0;
}
SettingsMenu::SettingsMenu(menu_options_t *options) : Menu(options)
{
addText(SettingsMenuItem::OTA_UPLOAD, "OTA Einspielen");
}
IMPLEMENT_GET_NAME(SettingsMenu)

View File

@@ -0,0 +1,32 @@
#include "ui/SplashScreen.h"
#include "ui/MainMenu.h"
uint64_t splashTime = 0;
SplashScreen::SplashScreen(menu_options_t *options) : Widget(options->u8g2), m_options(options)
{
}
void SplashScreen::Update(const uint64_t dt)
{
splashTime += dt;
if (splashTime > 100)
{
if (m_options && m_options->setScreen)
{
m_options->setScreen(std::make_shared<MainMenu>(m_options));
}
}
}
void SplashScreen::Render()
{
u8g2_SetFont(u8g2, u8g2_font_DigitalDisco_tr);
u8g2_DrawStr(u8g2, 28, u8g2->height / 2 - 10, "HO Anlage");
u8g2_DrawStr(u8g2, 30, u8g2->height / 2 + 5, "Axel Janz");
u8g2_SetFont(u8g2, u8g2_font_haxrcorp4089_tr);
u8g2_DrawStr(u8g2, 35, 50, "Initialisierung...");
}
IMPLEMENT_GET_NAME(SplashScreen)

View File

@@ -0,0 +1,13 @@
idf_component_register(SRCS
src/color.c
src/led_status.c
src/led_strip_ws2812.c
INCLUDE_DIRS "include"
PRIV_REQUIRES
insa
u8g2
esp_event
esp_timer
persistence-manager
simulator
)

View File

@@ -0,0 +1,19 @@
menu "Led Manager Configuration"
config WLED_DIN_PIN
int "WLED Data In Pin"
default 14
help
The number of the WLED data in pin.
config STATUS_WLED_PIN
int "Status WLED Pin"
default 2
help
The number of the status WLED pin.
config LED_STRIP_MAX_LEDS
int "Maximum number of LEDs"
default 800
help
The maximum number of LEDs that can be controlled.
endmenu

View File

@@ -0,0 +1,2 @@
dependencies:
espressif/led_strip: '~3.0.1'

View File

@@ -0,0 +1,29 @@
#pragma once
#include "SDL3/SDL_render.h"
#include <cstdint>
class Matrix
{
public:
explicit Matrix(SDL_WindowID windowId, SDL_Renderer *renderer, uint8_t cols, uint8_t rows);
[[nodiscard]] SDL_Renderer *renderer() const;
void Render() const;
[[nodiscard]] SDL_WindowID windowId() const;
private:
void DrawColoredGrid() const;
SDL_WindowID m_windowId;
SDL_Renderer *m_renderer;
uint8_t m_cols;
uint8_t m_rows;
static constexpr float cellSize = 50.0f;
static constexpr float spacing = 1.0f;
};

View File

@@ -0,0 +1,25 @@
#pragma once
#include <stdint.h>
#include <sys/cdefs.h>
typedef struct
{
uint8_t red;
uint8_t green;
uint8_t blue;
} rgb_t;
typedef struct
{
uint8_t h;
uint8_t s;
uint8_t v;
} hsv_t;
__BEGIN_DECLS
rgb_t interpolate_color_rgb(rgb_t start, rgb_t end, float factor);
rgb_t interpolate_color_hsv(rgb_t start, rgb_t end, float factor);
hsv_t rgb_to_hsv(rgb_t rgb);
rgb_t hsv_to_rgb(hsv_t hsv);
__END_DECLS

View File

@@ -0,0 +1,47 @@
#pragma once
#include "color.h"
#include <esp_check.h>
#include <stdint.h>
// Number of LEDs to be controlled
#define STATUS_LED_COUNT 3
// Possible lighting modes
typedef enum
{
LED_MODE_OFF,
LED_MODE_SOLID,
LED_MODE_BLINK
} led_mode_t;
// This is the structure you pass from the outside to define a behavior
typedef struct
{
uint32_t on_time_ms; // Only relevant for BLINK
uint32_t off_time_ms; // Only relevant for BLINK
rgb_t color;
uint8_t index;
led_mode_t mode;
} led_behavior_t;
__BEGIN_DECLS
/**
* @brief Initializes the status manager and the LED strip.
*
* @param gpio_num GPIO where the data line of the LEDs is connected.
* @return esp_err_t ESP_OK on success.
*/
esp_err_t led_status_init(int gpio_num);
/**
* @brief Sets the lighting behavior for a single LED.
*
* This function is thread-safe.
*
* @param index Index of the LED (0 to STATUS_LED_COUNT - 1).
* @param behavior The structure with the desired behavior.
* @return esp_err_t ESP_OK on success, ESP_ERR_INVALID_ARG on invalid index.
*/
esp_err_t led_status_set_behavior(led_behavior_t behavior);
__END_DECLS

View File

@@ -0,0 +1,18 @@
#pragma once
#include "color.h"
#include <esp_check.h>
#include <sys/cdefs.h>
typedef enum
{
LED_STATE_OFF,
LED_STATE_DAY,
LED_STATE_NIGHT,
LED_STATE_SIMULATION,
} led_state_t;
__BEGIN_DECLS
esp_err_t led_strip_init(void);
esp_err_t led_strip_update(led_state_t state, rgb_t color);
__END_DECLS

View File

@@ -0,0 +1,183 @@
#include "color.h"
#include <math.h>
rgb_t interpolate_color_rgb(rgb_t start, rgb_t end, float factor)
{
// Clamp factor to [0, 1]
if (factor > 1.0f)
factor = 1.0f;
if (factor < 0.0f)
factor = 0.0f;
rgb_t result;
result.red = (uint8_t)(start.red + (end.red - start.red) * factor);
result.green = (uint8_t)(start.green + (end.green - start.green) * factor);
result.blue = (uint8_t)(start.blue + (end.blue - start.blue) * factor);
return result;
}
rgb_t interpolate_color_hsv(rgb_t start, rgb_t end, float factor)
{
// Clamp factor to [0, 1]
if (factor > 1.0f)
factor = 1.0f;
if (factor < 0.0f)
factor = 0.0f;
// Convert RGB to HSV
hsv_t start_hsv = rgb_to_hsv(start);
hsv_t end_hsv = rgb_to_hsv(end);
// Handle hue interpolation carefully (circular)
double h1 = start_hsv.h;
double h2 = end_hsv.h;
double diff = h2 - h1;
if (diff > 180.0)
{
h1 += 360.0;
}
else if (diff < -180.0)
{
h2 += 360.0;
}
// Interpolate HSV values
hsv_t interpolated_hsv;
interpolated_hsv.h = fmod(h1 + (h2 - h1) * factor, 360.0);
if (interpolated_hsv.h < 0)
{
interpolated_hsv.h += 360.0;
}
interpolated_hsv.s = start_hsv.s + (end_hsv.s - start_hsv.s) * factor;
interpolated_hsv.v = start_hsv.v + (end_hsv.v - start_hsv.v) * factor;
// Convert back to RGB
return hsv_to_rgb(interpolated_hsv);
}
hsv_t rgb_to_hsv(rgb_t rgb)
{
hsv_t hsv;
uint8_t max = rgb.red;
uint8_t min = rgb.red;
if (rgb.green > max)
max = rgb.green;
if (rgb.blue > max)
max = rgb.blue;
if (rgb.green < min)
min = rgb.green;
if (rgb.blue < min)
min = rgb.blue;
uint8_t delta = max - min;
// Value berechnen
hsv.v = max;
// Saturation berechnen
if (max != 0)
{
hsv.s = (delta * 255) / max;
}
else
{
// Schwarz (r = g = b = 0)
hsv.s = 0;
hsv.h = 0;
return hsv;
}
// Hue berechnen
if (delta != 0)
{
int16_t hue;
if (rgb.red == max)
{
// Zwischen Gelb und Magenta
hue = ((int16_t)(rgb.green - rgb.blue) * 30) / delta;
if (hue < 0)
hue += 180;
}
else if (rgb.green == max)
{
// Zwischen Cyan und Gelb
hue = 60 + ((int16_t)(rgb.blue - rgb.red) * 30) / delta;
}
else
{
// Zwischen Magenta und Cyan
hue = 120 + ((int16_t)(rgb.red - rgb.green) * 30) / delta;
}
hsv.h = (uint8_t)hue;
}
else
{
// Graustufe
hsv.h = 0;
}
return hsv;
}
rgb_t hsv_to_rgb(hsv_t hsv)
{
rgb_t rgb;
if (hsv.s == 0)
{
// Graustufe
rgb.red = hsv.v;
rgb.green = hsv.v;
rgb.blue = hsv.v;
}
else
{
uint16_t region = hsv.h / 30;
uint16_t remainder = (hsv.h - (region * 30)) * 6;
uint8_t p = (hsv.v * (255 - hsv.s)) / 255;
uint8_t q = (hsv.v * (255 - ((hsv.s * remainder) / 180))) / 255;
uint8_t t = (hsv.v * (255 - ((hsv.s * (180 - remainder)) / 180))) / 255;
switch (region)
{
case 0:
rgb.red = hsv.v;
rgb.green = t;
rgb.blue = p;
break;
case 1:
rgb.red = q;
rgb.green = hsv.v;
rgb.blue = p;
break;
case 2:
rgb.red = p;
rgb.green = hsv.v;
rgb.blue = t;
break;
case 3:
rgb.red = p;
rgb.green = q;
rgb.blue = hsv.v;
break;
case 4:
rgb.red = t;
rgb.green = p;
rgb.blue = hsv.v;
break;
default: // case 5:
rgb.red = hsv.v;
rgb.green = p;
rgb.blue = q;
break;
}
}
return rgb;
}

View File

@@ -0,0 +1,155 @@
#include "led_status.h"
#include <esp_log.h>
#include <esp_timer.h> // For high-resolution timestamps
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <led_strip.h>
static const char *TAG = "led_status";
// Internal control structure for each LED
typedef struct
{
led_behavior_t behavior; // The desired behavior (target state)
uint64_t last_toggle_time_us; // Timestamp of the last toggle (in microseconds)
bool is_on_in_blink; // Current state in blink mode (actual state)
} led_control_t;
// --- Module variables ---
static led_strip_handle_t led_strip;
static led_control_t led_controls[STATUS_LED_COUNT];
static SemaphoreHandle_t mutex; // To protect the led_controls array
// The core: The task that controls the LEDs
static void led_status_task(void *pvParameters)
{
ESP_LOGI(TAG, "Led Status Task started.");
while (true)
{
uint64_t now_us = esp_timer_get_time();
// Take the mutex to safely access the control data
if (xSemaphoreTake(mutex, pdMS_TO_TICKS(100)) == pdTRUE)
{
for (int i = 0; i < STATUS_LED_COUNT; i++)
{
led_control_t *control = &led_controls[i];
switch (control->behavior.mode)
{
case LED_MODE_OFF:
led_strip_set_pixel(led_strip, i, 0, 0, 0);
break;
case LED_MODE_SOLID:
led_strip_set_pixel(led_strip, i, control->behavior.color.red, control->behavior.color.green,
control->behavior.color.blue);
break;
case LED_MODE_BLINK: {
uint32_t duration_ms =
control->is_on_in_blink ? control->behavior.on_time_ms : control->behavior.off_time_ms;
if ((now_us - control->last_toggle_time_us) / 1000 >= duration_ms)
{
control->is_on_in_blink = !control->is_on_in_blink; // Toggle state
control->last_toggle_time_us = now_us; // Update timestamp
}
if (control->is_on_in_blink)
{
led_strip_set_pixel(led_strip, i, control->behavior.color.red, control->behavior.color.green,
control->behavior.color.blue);
}
else
{
led_strip_set_pixel(led_strip, i, 0, 0, 0);
}
}
break;
}
}
// Release the mutex
xSemaphoreGive(mutex);
}
else
{
ESP_LOGW(TAG, "Failed to acquire mutex within timeout");
}
// Update the physical LED strip with the new values
led_strip_refresh(led_strip);
// Delay task for 20ms before the next iteration
vTaskDelay(pdMS_TO_TICKS(20));
}
}
// Initialization function
esp_err_t led_status_init(int gpio_num)
{
// LED strip configuration (e.g., for WS2812)
led_strip_config_t strip_config = {
.strip_gpio_num = gpio_num,
.max_leds = STATUS_LED_COUNT,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRBW,
.flags =
{
.invert_out = false,
},
};
led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 10 * 1000 * 1000, // 10MHz
.mem_block_symbols = 0,
.flags =
{
.with_dma = false,
},
};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
ESP_LOGI(TAG, "LED strip initialized.");
// Create mutex
mutex = xSemaphoreCreateMutex();
if (mutex == NULL)
{
ESP_LOGE(TAG, "Could not create mutex.");
return ESP_FAIL;
}
// Start task
xTaskCreatePinnedToCore(led_status_task, "led_status_task", 2048, NULL, tskIDLE_PRIORITY + 2, NULL,
CONFIG_FREERTOS_NUMBER_OF_CORES - 1);
return ESP_OK;
}
// Function to set the behavior
esp_err_t led_status_set_behavior(led_behavior_t behavior)
{
if (behavior.index >= STATUS_LED_COUNT)
{
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(mutex, pdMS_TO_TICKS(100)) == pdTRUE)
{
led_controls[behavior.index].behavior = behavior;
// Reset internal state variables to start the new pattern cleanly
led_controls[behavior.index].is_on_in_blink = false;
led_controls[behavior.index].last_toggle_time_us = esp_timer_get_time();
xSemaphoreGive(mutex);
}
else
{
ESP_LOGE(TAG, "Failed to acquire mutex for set_behavior");
return ESP_FAIL;
}
return ESP_OK;
}

View File

@@ -0,0 +1,117 @@
#include "led_strip_ws2812.h"
#include "color.h"
#include "led_status.h"
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <led_strip.h>
#include <sdkconfig.h>
static const char *TAG = "led_strip";
static led_strip_handle_t led_strip;
static QueueHandle_t led_command_queue;
static const uint32_t MAX_LEDS = CONFIG_LED_STRIP_MAX_LEDS;
typedef struct
{
led_state_t state;
rgb_t color;
} led_command_t;
static void set_all_pixels(const rgb_t color)
{
for (uint32_t i = 0; i < MAX_LEDS; i++)
{
led_strip_set_pixel(led_strip, i, color.red, color.green, color.blue);
}
led_strip_refresh(led_strip);
led_behavior_t led_behavior = {
.index = 2,
.mode = LED_MODE_SOLID,
.color = {.red = color.red, .green = color.green, .blue = color.blue},
};
led_status_set_behavior(led_behavior);
}
void led_strip_task(void *pvParameters)
{
led_state_t current_state = LED_STATE_OFF;
led_command_t cmd;
for (;;)
{
TickType_t wait_ticks = (current_state == LED_STATE_SIMULATION) ? pdMS_TO_TICKS(50) : portMAX_DELAY;
if (xQueueReceive(led_command_queue, &cmd, wait_ticks) == pdPASS)
{
current_state = cmd.state;
}
rgb_t color;
switch (current_state)
{
case LED_STATE_OFF:
color = (rgb_t){.red = 0, .green = 0, .blue = 0};
break;
default:
color = cmd.color;
break;
}
set_all_pixels(color);
}
};
esp_err_t led_strip_init(void)
{
led_strip_config_t strip_config = {
.strip_gpio_num = CONFIG_WLED_DIN_PIN,
.max_leds = MAX_LEDS,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
.flags = {.invert_out = false},
};
led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 0,
.mem_block_symbols = 0,
.flags = {.with_dma = true},
};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
led_command_queue = xQueueCreate(5, sizeof(led_command_t));
if (led_command_queue == NULL)
{
ESP_LOGE(TAG, "Failed to create command queue");
return ESP_FAIL;
}
set_all_pixels((rgb_t){.red = 0, .green = 0, .blue = 0});
xTaskCreatePinnedToCore(led_strip_task, "led_strip_task", 4096, NULL, tskIDLE_PRIORITY + 1, NULL,
CONFIG_FREERTOS_NUMBER_OF_CORES - 1);
ESP_LOGI(TAG, "LED strip initialized");
return ESP_OK;
}
esp_err_t led_strip_update(led_state_t state, rgb_t color)
{
led_command_t cmd = {
.state = state,
.color = color,
};
if (xQueueSend(led_command_queue, &cmd, pdMS_TO_TICKS(100)) != pdPASS)
{
ESP_LOGE(TAG, "Failed to send command to LED manager queue");
return ESP_FAIL;
}
return ESP_OK;
}

View File

@@ -0,0 +1,6 @@
idf_component_register(SRCS
src/PersistenceManager.cpp
INCLUDE_DIRS "include"
REQUIRES
nvs_flash
)

View File

@@ -0,0 +1,108 @@
#pragma once
#include <string>
#include <type_traits>
/**
* @interface IPersistenceManager
* @brief Abstract interface for platform-independent persistence management
* @details This interface defines the contract for key-value storage and retrieval
* systems across different platforms (Desktop/SDL3 and ESP32).
*/
class IPersistenceManager
{
public:
virtual ~IPersistenceManager() = default;
/**
* @brief Template methods for type-safe setting and retrieving of values
* @tparam T The type of value to set (must be one of: bool, int, float, double, std::string)
* @param key The key to associate with the value
* @param value The value to store
*/
template<typename T>
void SetValue(const std::string& key, const T& value) {
static_assert(std::is_same_v<T, bool> ||
std::is_same_v<T, int> ||
std::is_same_v<T, float> ||
std::is_same_v<T, double> ||
std::is_same_v<T, std::string>,
"Unsupported type for IPersistenceManager");
SetValueImpl(key, value);
}
/**
* @brief Template method for type-safe retrieval of values
* @tparam T The type of value to retrieve
* @param key The key to look up
* @param defaultValue The default value to return if the key is not found
* @return The stored value or default value if the key doesn't exist
*/
template<typename T>
[[nodiscard]] T GetValue(const std::string& key, const T& defaultValue = T{}) const {
return GetValueImpl<T>(key, defaultValue);
}
/**
* @brief Utility methods for key management
*/
[[nodiscard]] virtual bool HasKey(const std::string& key) const = 0; ///< Check if a key exists
virtual void RemoveKey(const std::string& key) = 0; ///< Remove a key-value pair
virtual void Clear() = 0; ///< Clear all stored data
[[nodiscard]] virtual size_t GetKeyCount() const = 0; ///< Get the number of stored keys
/**
* @brief Persistence operations
*/
virtual bool Save() = 0; ///< Save data to persistent storage
virtual bool Load() = 0; ///< Load data from persistent storage
protected:
/**
* @brief Template-specific implementations that must be overridden by derived classes
* @details These methods handle the actual storage and retrieval of different data types
*/
virtual void SetValueImpl(const std::string& key, bool value) = 0;
virtual void SetValueImpl(const std::string& key, int value) = 0;
virtual void SetValueImpl(const std::string& key, float value) = 0;
virtual void SetValueImpl(const std::string& key, double value) = 0;
virtual void SetValueImpl(const std::string& key, const std::string& value) = 0;
[[nodiscard]] virtual bool GetValueImpl(const std::string& key, bool defaultValue) const = 0;
[[nodiscard]] virtual int GetValueImpl(const std::string& key, int defaultValue) const = 0;
[[nodiscard]] virtual float GetValueImpl(const std::string& key, float defaultValue) const = 0;
[[nodiscard]] virtual double GetValueImpl(const std::string& key, double defaultValue) const = 0;
[[nodiscard]] virtual std::string GetValueImpl(const std::string& key, const std::string& defaultValue) const = 0;
private:
/**
* @brief Template dispatch methods for type-safe value retrieval
* @tparam T The type to retrieve
* @param key The key to look up
* @param defaultValue The default value to return
* @return The retrieved value or default if not found
*/
template<typename T>
[[nodiscard]] T GetValueImpl(const std::string& key, const T& defaultValue) const
{
if constexpr (std::is_same_v<T, bool>) {
return GetValueImpl(key, static_cast<bool>(defaultValue));
} else if constexpr (std::is_same_v<T, int>) {
return GetValueImpl(key, static_cast<int>(defaultValue));
} else if constexpr (std::is_same_v<T, float>) {
return GetValueImpl(key, static_cast<float>(defaultValue));
} else if constexpr (std::is_same_v<T, double>) {
return GetValueImpl(key, static_cast<double>(defaultValue));
} else if constexpr (std::is_same_v<T, std::string>) {
return GetValueImpl(key, static_cast<const std::string&>(defaultValue));
} else {
static_assert(std::is_same_v<T, bool> ||
std::is_same_v<T, int> ||
std::is_same_v<T, float> ||
std::is_same_v<T, double> ||
std::is_same_v<T, std::string>,
"Unsupported type for IPersistenceManager");
return defaultValue; // This line will never be reached, but satisfies the compiler
}
}
};

View File

@@ -0,0 +1,58 @@
#pragma once
#include "IPersistenceManager.h"
#include <string>
#include <unordered_map>
#include <nvs.h>
#include <nvs_flash.h>
/**
* @class PersistenceManager
* @brief ESP32-specific implementation using NVS (Non-Volatile Storage)
* @details This implementation uses ESP32's NVS API for persistent storage
* in flash memory, providing a platform-optimized solution for
* embedded systems.
*/
class PersistenceManager final : public IPersistenceManager
{
private:
nvs_handle_t nvs_handle_;
std::string namespace_;
bool initialized_;
public:
explicit PersistenceManager(const std::string &nvs_namespace = "config");
~PersistenceManager() override;
bool HasKey(const std::string &key) const override;
void RemoveKey(const std::string &key) override;
void Clear() override;
size_t GetKeyCount() const override;
bool Save() override;
bool Load() override;
bool Initialize();
void Deinitialize();
bool IsInitialized() const
{
return initialized_;
}
protected:
void SetValueImpl(const std::string &key, bool value) override;
void SetValueImpl(const std::string &key, int value) override;
void SetValueImpl(const std::string &key, float value) override;
void SetValueImpl(const std::string &key, double value) override;
void SetValueImpl(const std::string &key, const std::string &value) override;
bool GetValueImpl(const std::string &key, bool defaultValue) const override;
int GetValueImpl(const std::string &key, int defaultValue) const override;
float GetValueImpl(const std::string &key, float defaultValue) const override;
double GetValueImpl(const std::string &key, double defaultValue) const override;
std::string GetValueImpl(const std::string &key, const std::string &defaultValue) const override;
private:
bool EnsureInitialized() const;
};

View File

@@ -0,0 +1,279 @@
#include "hal_esp32/PersistenceManager.h"
#include <cstring>
#include <esp_log.h>
static const char *TAG = "PersistenceManager";
PersistenceManager::PersistenceManager(const std::string &nvs_namespace)
: namespace_(nvs_namespace), initialized_(false)
{
Initialize();
Load();
}
PersistenceManager::~PersistenceManager()
{
Deinitialize();
}
bool PersistenceManager::Initialize()
{
if (initialized_)
{
return true;
}
// Open NVS handle
esp_err_t err = nvs_open(namespace_.c_str(), NVS_READWRITE, &nvs_handle_);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(err));
return false;
}
initialized_ = true;
ESP_LOGI(TAG, "PersistenceManager initialized with namespace: %s", namespace_.c_str());
return true;
}
void PersistenceManager::Deinitialize()
{
if (initialized_)
{
nvs_close(nvs_handle_);
initialized_ = false;
}
}
bool PersistenceManager::EnsureInitialized() const
{
if (!initialized_)
{
ESP_LOGE(TAG, "PersistenceManager not initialized");
return false;
}
return true;
}
bool PersistenceManager::HasKey(const std::string &key) const
{
if (!EnsureInitialized())
return false;
size_t required_size = 0;
esp_err_t err = nvs_get_blob(nvs_handle_, key.c_str(), nullptr, &required_size);
return err == ESP_OK;
}
void PersistenceManager::RemoveKey(const std::string &key)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_erase_key(nvs_handle_, key.c_str());
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
{
ESP_LOGE(TAG, "Failed to remove key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::Clear()
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_erase_all(nvs_handle_);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to clear all keys: %s", esp_err_to_name(err));
}
}
size_t PersistenceManager::GetKeyCount() const
{
if (!EnsureInitialized())
return 0;
nvs_iterator_t it = nullptr;
esp_err_t err = nvs_entry_find(NVS_DEFAULT_PART_NAME, namespace_.c_str(), NVS_TYPE_ANY, &it);
if (err != ESP_OK || it == nullptr)
{
return 0;
}
size_t count = 0;
while (it != nullptr)
{
count++;
err = nvs_entry_next(&it);
if (err != ESP_OK)
{
break;
}
}
nvs_release_iterator(it);
return count;
}
bool PersistenceManager::Save()
{
if (!EnsureInitialized())
return false;
esp_err_t err = nvs_commit(nvs_handle_);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to commit NVS: %s", esp_err_to_name(err));
return false;
}
return true;
}
bool PersistenceManager::Load()
{
return EnsureInitialized();
}
void PersistenceManager::SetValueImpl(const std::string &key, bool value)
{
if (!EnsureInitialized())
return;
uint8_t val = value ? 1 : 0;
esp_err_t err = nvs_set_u8(nvs_handle_, key.c_str(), val);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set bool key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, int value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_i32(nvs_handle_, key.c_str(), value);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set int key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, float value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_blob(nvs_handle_, key.c_str(), &value, sizeof(float));
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set float key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, double value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_blob(nvs_handle_, key.c_str(), &value, sizeof(double));
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set double key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, const std::string &value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_str(nvs_handle_, key.c_str(), value.c_str());
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set string key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
bool PersistenceManager::GetValueImpl(const std::string &key, bool defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
uint8_t value;
esp_err_t err = nvs_get_u8(nvs_handle_, key.c_str(), &value);
if (err != ESP_OK)
{
return defaultValue;
}
return value != 0;
}
int PersistenceManager::GetValueImpl(const std::string &key, int defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
int32_t value;
esp_err_t err = nvs_get_i32(nvs_handle_, key.c_str(), &value);
if (err != ESP_OK)
{
return defaultValue;
}
return static_cast<int>(value);
}
float PersistenceManager::GetValueImpl(const std::string &key, float defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
float value;
size_t required_size = sizeof(float);
esp_err_t err = nvs_get_blob(nvs_handle_, key.c_str(), &value, &required_size);
if (err != ESP_OK || required_size != sizeof(float))
{
return defaultValue;
}
return value;
}
double PersistenceManager::GetValueImpl(const std::string &key, double defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
double value;
size_t required_size = sizeof(double);
esp_err_t err = nvs_get_blob(nvs_handle_, key.c_str(), &value, &required_size);
if (err != ESP_OK || required_size != sizeof(double))
{
return defaultValue;
}
return value;
}
std::string PersistenceManager::GetValueImpl(const std::string &key, const std::string &defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
size_t required_size = 0;
esp_err_t err = nvs_get_str(nvs_handle_, key.c_str(), nullptr, &required_size);
if (err != ESP_OK)
{
return defaultValue;
}
std::string value(required_size - 1, '\0'); // -1 for null terminator
err = nvs_get_str(nvs_handle_, key.c_str(), value.data(), &required_size);
if (err != ESP_OK)
{
return defaultValue;
}
return value;
}

View File

@@ -0,0 +1,9 @@
idf_component_register(SRCS
"src/simulator.cpp"
"src/storage.cpp"
INCLUDE_DIRS "include"
PRIV_REQUIRES
led-manager
persistence-manager
spiffs
)

View File

@@ -0,0 +1,20 @@
#pragma once
#include "esp_check.h"
#include <stdint.h>
// Configuration structure for the simulation
typedef struct
{
int cycle_duration_minutes;
} simulation_config_t;
char *get_time(void);
esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue, uint8_t white,
uint8_t brightness, uint8_t saturation);
void cleanup_light_items(void);
void start_simulate_day(void);
void start_simulate_night(void);
void start_simulation_task(void);
void stop_simulation_task(void);
void start_simulation(void);

View File

@@ -0,0 +1,4 @@
#pragma once
void initialize_storage();
void load_file(const char *filename);

View File

@@ -0,0 +1,429 @@
#include "simulator.h"
#include "color.h"
#include "hal_esp32/PersistenceManager.h"
#include "led_strip_ws2812.h"
#include "storage.h"
#include <esp_heap_caps.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
static const char *TAG = "simulator";
static char *time;
static char *time_to_string(int hhmm)
{
static char buffer[20];
snprintf(buffer, sizeof(buffer), "%02d:%02d Uhr", hhmm / 100, hhmm % 100);
return buffer;
}
static TaskHandle_t simulation_task_handle = NULL;
static SemaphoreHandle_t simulation_mutex = NULL;
static void ensure_mutex_initialized(void)
{
if (simulation_mutex == NULL)
{
simulation_mutex = xSemaphoreCreateMutex();
}
}
// The struct is extended with a 'next' pointer to form a linked list.
typedef struct light_item_node_t
{
char time[5];
uint8_t red;
uint8_t green;
uint8_t blue;
struct light_item_node_t *next;
} light_item_node_t;
// Global pointers for the head and tail of the list.
static light_item_node_t *head = NULL;
static light_item_node_t *tail = NULL;
// Interpolation mode selection
typedef enum
{
INTERPOLATION_RGB,
INTERPOLATION_HSV
} interpolation_mode_t;
// You can change this to test different interpolation methods
static const interpolation_mode_t interpolation_mode = INTERPOLATION_RGB;
char *get_time(void)
{
return time;
}
// Main interpolation function that selects the appropriate method
static rgb_t interpolate_color(rgb_t start, rgb_t end, float factor)
{
switch (interpolation_mode)
{
case INTERPOLATION_RGB:
return interpolate_color_rgb(start, end, factor);
case INTERPOLATION_HSV:
default:
return interpolate_color_hsv(start, end, factor);
}
}
esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue, uint8_t white,
uint8_t brightness, uint8_t saturation)
{
// Allocate memory for a new node in PSRAM.
light_item_node_t *new_node = (light_item_node_t *)heap_caps_malloc(sizeof(light_item_node_t), MALLOC_CAP_SPIRAM);
if (new_node == NULL)
{
ESP_LOGE(TAG, "Failed to allocate memory in PSRAM for new light_item_node_t.");
return ESP_FAIL;
}
rgb_t color = {.red = red, .green = green, .blue = blue};
if (saturation < 255)
{
hsv_t hsv = rgb_to_hsv(color);
hsv.s = hsv.s * (saturation / 255.0f);
// color = hsv_to_rgb(hsv);
}
float brightness_factor = brightness / 255.0f;
memcpy(new_node->time, time, sizeof(new_node->time));
new_node->red = (uint8_t)(color.red * brightness_factor);
new_node->green = (uint8_t)(color.green * brightness_factor);
new_node->blue = (uint8_t)(color.blue * brightness_factor);
new_node->next = NULL;
// Append the new node to the end of the list.
if (head == NULL)
{
// If the list is empty, the new node becomes both head and tail.
head = new_node;
tail = new_node;
}
else
{
// Otherwise, append the new node to the end and update tail.
tail->next = new_node;
tail = new_node;
}
return ESP_OK;
}
void cleanup_light_items(void)
{
light_item_node_t *current = head;
light_item_node_t *next_node;
while (current != NULL)
{
next_node = current->next;
heap_caps_free(current);
current = next_node;
}
head = NULL;
tail = NULL;
ESP_LOGI(TAG, "Cleaned up all light items.");
}
static void initialize_light_items(void)
{
cleanup_light_items();
initialize_storage();
static char filename[30];
auto persistence = PersistenceManager();
int variant = persistence.GetValue("light_variant", 1);
snprintf(filename, sizeof(filename), "/spiffs/schema_%02d.csv", variant);
load_file(filename);
if (head == NULL)
{
ESP_LOGW(TAG, "Light schedule is empty. Simulation will not run.");
vTaskDelete(NULL);
return;
}
}
static light_item_node_t *find_best_light_item_for_time(int hhmm)
{
light_item_node_t *best_item = NULL;
light_item_node_t *current = head;
int best_time = -1;
while (current != NULL)
{
int current_time = atoi(current->time);
if (current_time <= hhmm && current_time > best_time)
{
best_time = current_time;
best_item = current;
}
current = current->next;
}
if (best_item == NULL)
{
// If no item is found for the given time (e.g., before the first item of the day),
// find the last item of the previous day.
best_time = -1;
current = head;
while (current != NULL)
{
int current_time = atoi(current->time);
if (current_time > best_time)
{
best_time = current_time;
best_item = current;
}
current = current->next;
}
}
return best_item;
}
static light_item_node_t *find_next_light_item_for_time(int hhmm)
{
light_item_node_t *current = head;
light_item_node_t *next_item = NULL;
int next_time = 9999; // Initialize with a value larger than any possible time
// First pass: find the soonest time after hhmm
while (current != NULL)
{
int current_time = atoi(current->time);
if (current_time > hhmm && current_time < next_time)
{
next_time = current_time;
next_item = current;
}
current = current->next;
}
// If no item is found for the rest of the day, wrap around to the beginning of the next day
if (next_item == NULL)
{
current = head;
next_time = 9999;
while (current != NULL)
{
int current_time = atoi(current->time);
if (current_time < next_time)
{
next_time = current_time;
next_item = current;
}
current = current->next;
}
}
return next_item;
}
void start_simulate_day(void)
{
initialize_light_items();
light_item_node_t *current_item = find_best_light_item_for_time(1200);
if (current_item != NULL)
{
led_strip_update(LED_STATE_DAY,
(rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue});
}
}
void start_simulate_night(void)
{
initialize_light_items();
light_item_node_t *current_item = find_best_light_item_for_time(0);
if (current_item != NULL)
{
led_strip_update(LED_STATE_NIGHT,
(rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue});
}
}
void simulate_cycle(void *args)
{
simulation_config_t *config = (simulation_config_t *)args;
int cycle_duration_minutes = config->cycle_duration_minutes;
heap_caps_free(config);
if (cycle_duration_minutes <= 0)
{
ESP_LOGE(TAG, "Invalid cycle duration: %d minutes. Must be positive.", cycle_duration_minutes);
if (simulation_mutex != NULL && xSemaphoreTake(simulation_mutex, portMAX_DELAY) == pdTRUE)
{
simulation_task_handle = NULL;
xSemaphoreGive(simulation_mutex);
}
vTaskDelete(NULL);
return;
}
initialize_light_items();
const int total_minutes_in_day = 24 * 60;
long delay_ms = (long)cycle_duration_minutes * 60 * 1000 / total_minutes_in_day;
ESP_LOGI(TAG, "Starting simulation of a 24h cycle over %d minutes. Each simulated minute will take %ld ms.",
cycle_duration_minutes, delay_ms);
int current_minute_of_day = 0;
while (1)
{
int hours = current_minute_of_day / 60;
int minutes = current_minute_of_day % 60;
int hhmm = hours * 100 + minutes;
time = time_to_string(hhmm);
light_item_node_t *current_item = find_best_light_item_for_time(hhmm);
light_item_node_t *next_item = find_next_light_item_for_time(hhmm);
if (current_item != NULL && next_item != NULL)
{
int current_item_time_min = (atoi(current_item->time) / 100) * 60 + (atoi(current_item->time) % 100);
int next_item_time_min = (atoi(next_item->time) / 100) * 60 + (atoi(next_item->time) % 100);
if (next_item_time_min < current_item_time_min)
{
next_item_time_min += total_minutes_in_day;
}
int minutes_since_current_item_start = current_minute_of_day - current_item_time_min;
if (minutes_since_current_item_start < 0)
{
minutes_since_current_item_start += total_minutes_in_day;
}
int interval_duration = next_item_time_min - current_item_time_min;
if (interval_duration == 0)
{
interval_duration = 1;
}
float interpolation_factor = (float)minutes_since_current_item_start / (float)interval_duration;
// Prepare colors for interpolation
rgb_t start_rgb = {.red = current_item->red, .green = current_item->green, .blue = current_item->blue};
rgb_t end_rgb = {.red = next_item->red, .green = next_item->green, .blue = next_item->blue};
// Use the interpolation function
rgb_t final_rgb = interpolate_color(start_rgb, end_rgb, interpolation_factor);
led_strip_update(LED_STATE_SIMULATION, final_rgb);
}
else if (current_item != NULL)
{
// No next item, just use current
led_strip_update(
LED_STATE_SIMULATION,
(rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue});
}
vTaskDelay(pdMS_TO_TICKS(delay_ms));
current_minute_of_day++;
if (current_minute_of_day >= total_minutes_in_day)
{
current_minute_of_day = 0;
ESP_LOGI(TAG, "Simulation cycle restarting.");
}
}
}
void start_simulation_task(void)
{
stop_simulation_task();
simulation_config_t *config =
(simulation_config_t *)heap_caps_malloc(sizeof(simulation_config_t), MALLOC_CAP_SPIRAM);
if (config == NULL)
{
ESP_LOGE(TAG, "Failed to allocate memory for simulation config.");
return;
}
config->cycle_duration_minutes = 15;
if (xTaskCreatePinnedToCore(simulate_cycle, "simulate_cycle", 4096, (void *)config, tskIDLE_PRIORITY + 1,
&simulation_task_handle, CONFIG_FREERTOS_NUMBER_OF_CORES - 1) != pdPASS)
{
ESP_LOGE(TAG, "Failed to create simulation task.");
heap_caps_free(config);
}
}
void stop_simulation_task(void)
{
ensure_mutex_initialized();
if (xSemaphoreTake(simulation_mutex, portMAX_DELAY) == pdTRUE)
{
if (simulation_task_handle != NULL)
{
TaskHandle_t handle_to_delete = simulation_task_handle;
simulation_task_handle = NULL;
xSemaphoreGive(simulation_mutex);
// Prüfe ob der Task noch existiert bevor er gelöscht wird
eTaskState state = eTaskGetState(handle_to_delete);
if (state != eDeleted && state != eInvalid)
{
vTaskDelete(handle_to_delete);
}
}
else
{
xSemaphoreGive(simulation_mutex);
}
}
}
void start_simulation(void)
{
stop_simulation_task();
auto persistence = PersistenceManager();
if (persistence.GetValue("light_active", false))
{
int mode = persistence.GetValue("light_mode", 0);
switch (mode)
{
case 0: // Simulation mode
start_simulation_task();
break;
case 1: // Day mode
start_simulate_day();
break;
case 2: // Night mode
start_simulate_night();
break;
default:
ESP_LOGW(TAG, "Unknown light mode: %d", mode);
break;
}
}
else
{
led_strip_update(LED_STATE_OFF, rgb_t{});
}
}

View File

@@ -0,0 +1,99 @@
#include "storage.h"
#include "esp_check.h"
#include "esp_log.h"
#include "esp_spiffs.h"
#include "simulator.h"
#include <cstring>
#include <errno.h>
#include <stdio.h>
static const char *TAG = "storage";
void initialize_storage()
{
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = NULL,
.max_files = 5,
.format_if_mount_failed = false,
};
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK)
{
if (ret == ESP_FAIL)
{
ESP_LOGE(TAG, "Failed to mount or format filesystem");
}
else if (ret == ESP_ERR_NOT_FOUND)
{
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
}
else
{
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
}
return;
}
}
void load_file(const char *filename)
{
ESP_LOGI(TAG, "Loading file: %s", filename);
FILE *f = fopen(filename, "r");
if (f == NULL)
{
ESP_LOGE(TAG, "Failed to open file for reading");
return;
}
char line[128];
uint8_t line_number = 0;
while (fgets(line, sizeof(line), f))
{
char *pos = strchr(line, '\n');
if (pos)
{
*pos = '\0';
}
if (strlen(line) == 0)
{
continue;
}
char *trimmed = line;
while (*trimmed == ' ' || *trimmed == '\t')
{
trimmed++;
}
if (*trimmed == '#' || *trimmed == '\0')
{
continue;
}
char time[10] = {0};
int red, green, blue, white, brightness, saturation;
int items_scanned = sscanf(line, "%d,%d,%d,%d,%d,%d", &red, &green, &blue, &white, &brightness, &saturation);
if (items_scanned == 6)
{
int total_minutes = line_number * 30;
int hours = total_minutes / 60;
int minutes = total_minutes % 60;
snprintf(time, sizeof(time), "%02d%02d", hours, minutes);
add_light_item(time, red, green, blue, white, brightness, saturation);
line_number++;
}
else
{
ESP_LOGW(TAG, "Could not parse line: %s", line);
}
}
fclose(f);
ESP_LOGI(TAG, "Finished loading file. Loaded %d entries.", line_number);
}

26
firmware/main/CMakeLists.txt Executable file
View File

@@ -0,0 +1,26 @@
idf_component_register(SRCS
main.cpp
app_task.cpp
button_handling.c
i2c_checker.c
hal/u8g2_esp32_hal.c
INCLUDE_DIRS "."
PRIV_REQUIRES
analytics
insa
connectivity-manager
led-manager
persistence-manager
simulator
u8g2
hal
nvs_flash
esp_timer
esp_event
esp_wifi
app_update
rmaker_common
driver
)
spiffs_create_partition_image(storage ../storage FLASH_IN_PROJECT)

View File

@@ -0,0 +1,108 @@
menu "System Control"
menu "WiFi Configuration"
config WIFI_ENABLED
bool "Enable WiFi"
default y
help
Enable or disable WiFi connectivity.
config WIFI_NETWORK_COUNT
depends on WIFI_ENABLED
int "Number of WiFi Networks"
default 1
range 1 5
help
Number of WiFi networks to configure (1-5).
config WIFI_SSID_1
depends on WIFI_ENABLED
string "WiFi SSID 1"
default "YourSSID1"
help
The SSID of the first WiFi network.
config WIFI_PASSWORD_1
depends on WIFI_ENABLED
string "WiFi Password 1"
default "YourPassword1"
help
The password of the first WiFi network.
config WIFI_SSID_2
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 2
string "WiFi SSID 2"
default ""
help
The SSID of the second WiFi network.
config WIFI_PASSWORD_2
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 2
string "WiFi Password 2"
default ""
help
The password of the second WiFi network.
config WIFI_SSID_3
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 3
string "WiFi SSID 3"
default ""
help
The SSID of the third WiFi network.
config WIFI_PASSWORD_3
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 3
string "WiFi Password 3"
default ""
help
The password of the third WiFi network.
config WIFI_SSID_4
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 4
string "WiFi SSID 4"
default ""
help
The SSID of the fourth WiFi network.
config WIFI_PASSWORD_4
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 4
string "WiFi Password 4"
default ""
help
The password of the fourth WiFi network.
config WIFI_SSID_5
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 5
string "WiFi SSID 5"
default ""
help
The SSID of the fifth WiFi network.
config WIFI_PASSWORD_5
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 5
string "WiFi Password 5"
default ""
help
The password of the fifth WiFi network.
config WIFI_CONNECT_RETRIES
depends on WIFI_ENABLED
int "WiFi Connection Retry Attempts per Network"
default 3
help
Number of times to retry connecting to each WiFi network before trying the next one.
endmenu
menu "Display Settings"
config DISPLAY_SDA_PIN
int "I2C SDA Pin"
default 35
help
GPIO pin number for the SDA line of the display.
config DISPLAY_SCL_PIN
int "I2C SCL Pin"
default 36
help
GPIO pin number for the SCL line of the display.
endmenu
endmenu

227
firmware/main/app_task.cpp Normal file
View File

@@ -0,0 +1,227 @@
#include "app_task.h"
#include "analytics.h"
#include "button_handling.h"
#include "common/InactivityTracker.h"
#include "hal/u8g2_esp32_hal.h"
#include "hal_esp32/PersistenceManager.h"
#include "i2c_checker.h"
#include "led_status.h"
#include "simulator.h"
#include "ui/ClockScreenSaver.h"
#include "ui/ScreenSaver.h"
#include "ui/SplashScreen.h"
#include "wifi_manager.h"
#include <driver/i2c.h>
#include <esp_diagnostics.h>
#include <esp_insights.h>
#include <esp_log.h>
#include <esp_task_wdt.h>
#include <esp_timer.h>
#include <sdkconfig.h>
#include <u8g2.h>
#define PIN_RST GPIO_NUM_NC
static const char *TAG = "app_task";
u8g2_t u8g2;
uint8_t last_value = 0;
menu_options_t options;
uint8_t received_signal;
std::shared_ptr<Widget> m_widget;
std::vector<std::shared_ptr<Widget>> m_history;
std::unique_ptr<InactivityTracker> m_inactivityTracker;
std::shared_ptr<PersistenceManager> m_persistenceManager;
extern QueueHandle_t buttonQueue;
static void setup_screen(void)
{
u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT;
u8g2_esp32_hal.bus.i2c.sda = I2C_MASTER_SDA_PIN;
u8g2_esp32_hal.bus.i2c.scl = I2C_MASTER_SCL_PIN;
u8g2_esp32_hal.reset = PIN_RST;
u8g2_esp32_hal_init(u8g2_esp32_hal);
u8g2_Setup_sh1106_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8g2_esp32_i2c_byte_cb, u8g2_esp32_gpio_and_delay_cb);
u8x8_SetI2CAddress(&u8g2.u8x8, DISPLAY_I2C_ADDRESS << 1);
ESP_DIAG_EVENT(TAG, "u8g2_InitDisplay");
u8g2_InitDisplay(&u8g2);
vTaskDelay(pdMS_TO_TICKS(10));
ESP_DIAG_EVENT(TAG, "u8g2_SetPowerSave");
u8g2_SetPowerSave(&u8g2, 0);
vTaskDelay(pdMS_TO_TICKS(10));
u8g2_ClearDisplay(&u8g2);
}
void setScreen(const std::shared_ptr<Widget> &screen)
{
if (screen != nullptr)
{
ESP_DIAG_EVENT(TAG, "Screen set: %s", screen->getName());
m_widget = screen;
m_history.clear();
m_history.emplace_back(m_widget);
m_widget->onEnter();
}
}
void pushScreen(const std::shared_ptr<Widget> &screen)
{
if (screen != nullptr)
{
if (m_widget)
{
m_widget->onPause();
}
ESP_DIAG_EVENT(TAG, "Screen pushed: %s", screen->getName());
m_widget = screen;
m_widget->onEnter();
m_history.emplace_back(m_widget);
}
}
void popScreen()
{
if (m_history.size() >= 2)
{
m_history.pop_back();
if (m_widget)
{
if (m_persistenceManager != nullptr)
{
m_persistenceManager->Save();
}
m_widget->onExit();
}
m_widget = m_history.back();
ESP_DIAG_EVENT(TAG, "Screen popped, now: %s", m_widget->getName());
m_widget->onResume();
}
}
static void init_ui(void)
{
m_persistenceManager = std::make_shared<PersistenceManager>();
options = {
.u8g2 = &u8g2,
.setScreen = [](const std::shared_ptr<Widget> &screen) { setScreen(screen); },
.pushScreen = [](const std::shared_ptr<Widget> &screen) { pushScreen(screen); },
.popScreen = []() { popScreen(); },
.onButtonClicked = nullptr,
.persistenceManager = m_persistenceManager,
};
m_widget = std::make_shared<SplashScreen>(&options);
m_inactivityTracker = std::make_unique<InactivityTracker>(60000, []() {
auto screensaver = std::make_shared<ClockScreenSaver>(&options);
options.pushScreen(screensaver);
});
u8g2_ClearBuffer(&u8g2);
m_widget->Render();
u8g2_SendBuffer(&u8g2);
}
static void handle_button(uint8_t button)
{
m_inactivityTracker->reset();
if (m_widget)
{
switch (button)
{
case 1:
m_widget->OnButtonClicked(ButtonType::UP);
break;
case 3:
m_widget->OnButtonClicked(ButtonType::LEFT);
break;
case 5:
m_widget->OnButtonClicked(ButtonType::RIGHT);
break;
case 6:
m_widget->OnButtonClicked(ButtonType::DOWN);
break;
case 16:
m_widget->OnButtonClicked(ButtonType::BACK);
break;
case 18:
m_widget->OnButtonClicked(ButtonType::SELECT);
break;
default:
ESP_LOGE(TAG, "Unhandled button: %u", button);
break;
}
}
}
void app_task(void *args)
{
if (i2c_bus_scan_and_check() != ESP_OK)
{
led_behavior_t led_behavior = {
.on_time_ms = 1000,
.off_time_ms = 500,
.color = {.red = 50, .green = 0, .blue = 0},
.index = 0,
.mode = LED_MODE_BLINK,
};
led_status_set_behavior(led_behavior);
ESP_LOGE(TAG, "Display not found on I2C bus");
vTaskDelete(nullptr);
return;
}
setup_screen();
setup_buttons();
init_ui();
#if CONFIG_WIFI_ENABLED
wifi_manager_init();
analytics_init();
#endif
start_simulation();
auto oldTime = esp_timer_get_time();
while (true)
{
u8g2_ClearBuffer(&u8g2);
if (m_widget != nullptr)
{
auto currentTime = esp_timer_get_time();
auto delta = currentTime - oldTime;
oldTime = currentTime;
uint64_t deltaMs = delta / 1000;
m_widget->Update(deltaMs);
m_widget->Render();
m_inactivityTracker->update(deltaMs);
}
u8g2_SendBuffer(&u8g2);
if (xQueueReceive(buttonQueue, &received_signal, pdMS_TO_TICKS(10)) == pdTRUE)
{
handle_button(received_signal);
}
}
cleanup_buttons();
}

3
firmware/main/app_task.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
void app_task(void *args);

View File

@@ -0,0 +1,93 @@
#include "button_handling.h"
#include "button_gpio.h"
#include "common.h"
#include <driver/gpio.h>
#include <esp_err.h>
#include <esp_insights.h>
#include <esp_log.h>
#include <esp_mac.h>
#include <freertos/FreeRTOS.h>
#include <iot_button.h>
#include <sdkconfig.h>
#include <stdio.h>
#include <string.h>
static const char *TAG = "button_handling";
const uint8_t gpios[] = {BUTTON_DOWN, BUTTON_UP, BUTTON_LEFT, BUTTON_RIGHT, BUTTON_SELECT, BUTTON_BACK};
static const char *button_names[] = {"DOWN", "UP", "LEFT", "RIGHT", "SELECT", "BACK"};
typedef struct
{
uint8_t gpio;
uint8_t index;
} button_user_data_t;
static button_user_data_t button_data[6];
QueueHandle_t buttonQueue = NULL;
static void button_event_cb(void *arg, void *usr_data)
{
if (buttonQueue == NULL)
{
ESP_LOGE(TAG, "Button queue not initialized!");
return;
}
button_user_data_t *data = (button_user_data_t *)usr_data;
uint8_t gpio_num = data->gpio;
const char *button_name = button_names[data->index];
ESP_DIAG_EVENT(TAG, "Button %s pressed (GPIO %d)", button_name, gpio_num);
if (xQueueSend(buttonQueue, &gpio_num, 0) != pdTRUE)
{
ESP_LOGW(TAG, "Failed to send button press to queue");
}
}
static void init_button(uint8_t gpio, int index)
{
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = gpio,
.active_level = 0,
.enable_power_save = true,
};
button_handle_t gpio_btn = NULL;
const esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &gpio_btn);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Button create failed");
}
button_data[index].gpio = gpio;
button_data[index].index = index;
iot_button_register_cb(gpio_btn, BUTTON_SINGLE_CLICK, NULL, button_event_cb, &button_data[index]);
}
void setup_buttons(void)
{
buttonQueue = xQueueCreate(10, sizeof(uint8_t));
if (buttonQueue == NULL)
{
ESP_LOGE(TAG, "Failed to create button queue");
return;
}
ESP_DIAG_EVENT(TAG, "Button queue created successfully");
for (int i = 0; i < sizeof(gpios) / sizeof(gpios[0]); i++)
{
init_button(gpios[i], i);
}
}
// Cleanup function (optional)
void cleanup_buttons(void)
{
if (buttonQueue != NULL)
{
vQueueDelete(buttonQueue);
}
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include <sys/cdefs.h>
__BEGIN_DECLS
void setup_buttons(void);
void cleanup_buttons(void);
__END_DECLS

8
firmware/main/common.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#define BUTTON_UP GPIO_NUM_1
#define BUTTON_DOWN GPIO_NUM_6
#define BUTTON_LEFT GPIO_NUM_3
#define BUTTON_RIGHT GPIO_NUM_5
#define BUTTON_SELECT GPIO_NUM_18
#define BUTTON_BACK GPIO_NUM_16

View File

@@ -0,0 +1,292 @@
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "u8g2_esp32_hal.h"
static const char *TAG = "u8g2_hal";
static const unsigned int I2C_TIMEOUT_MS = 1000;
static spi_device_handle_t handle_spi; // SPI handle.
static i2c_cmd_handle_t handle_i2c; // I2C handle.
static u8g2_esp32_hal_t u8g2_esp32_hal; // HAL state data.
static bool i2c_transfer_failed = false; // Flag to track I2C transfer errors
#define HOST SPI2_HOST
#undef ESP_ERROR_CHECK
#define ESP_ERROR_CHECK(x) \
do \
{ \
esp_err_t rc = (x); \
if (rc != ESP_OK) \
{ \
ESP_LOGE("err", "esp_err_t = %d", rc); \
assert(0 && #x); \
} \
} while (0);
// Softer error handling for I2C operations that may fail temporarily
#define I2C_ERROR_CHECK(x) \
do \
{ \
esp_err_t rc = (x); \
if (rc != ESP_OK) \
{ \
ESP_LOGW(TAG, "I2C error: %s = %d", #x, rc); \
i2c_transfer_failed = true; \
} \
} while (0);
/*
* Initialze the ESP32 HAL.
*/
void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param)
{
u8g2_esp32_hal = u8g2_esp32_hal_param;
} // u8g2_esp32_hal_init
/*
* HAL callback function as prescribed by the U8G2 library. This callback is
* invoked to handle SPI communications.
*/
uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
ESP_LOGD(TAG, "spi_byte_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr);
switch (msg)
{
case U8X8_MSG_BYTE_SET_DC:
if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED)
{
gpio_set_level(u8g2_esp32_hal.dc, arg_int);
}
break;
case U8X8_MSG_BYTE_INIT: {
if (u8g2_esp32_hal.bus.spi.clk == U8G2_ESP32_HAL_UNDEFINED ||
u8g2_esp32_hal.bus.spi.mosi == U8G2_ESP32_HAL_UNDEFINED ||
u8g2_esp32_hal.bus.spi.cs == U8G2_ESP32_HAL_UNDEFINED)
{
break;
}
spi_bus_config_t bus_config;
memset(&bus_config, 0, sizeof(spi_bus_config_t));
bus_config.sclk_io_num = u8g2_esp32_hal.bus.spi.clk; // CLK
bus_config.mosi_io_num = u8g2_esp32_hal.bus.spi.mosi; // MOSI
bus_config.miso_io_num = GPIO_NUM_NC; // MISO
bus_config.quadwp_io_num = GPIO_NUM_NC; // Not used
bus_config.quadhd_io_num = GPIO_NUM_NC; // Not used
// ESP_LOGI(TAG, "... Initializing bus.");
ESP_ERROR_CHECK(spi_bus_initialize(HOST, &bus_config, 1));
spi_device_interface_config_t dev_config;
dev_config.address_bits = 0;
dev_config.command_bits = 0;
dev_config.dummy_bits = 0;
dev_config.mode = 0;
dev_config.duty_cycle_pos = 0;
dev_config.cs_ena_posttrans = 0;
dev_config.cs_ena_pretrans = 0;
dev_config.clock_speed_hz = 10000;
dev_config.spics_io_num = u8g2_esp32_hal.bus.spi.cs;
dev_config.flags = 0;
dev_config.queue_size = 200;
dev_config.pre_cb = NULL;
dev_config.post_cb = NULL;
// ESP_LOGI(TAG, "... Adding device bus.");
ESP_ERROR_CHECK(spi_bus_add_device(HOST, &dev_config, &handle_spi));
break;
}
case U8X8_MSG_BYTE_SEND: {
spi_transaction_t trans_desc;
trans_desc.addr = 0;
trans_desc.cmd = 0;
trans_desc.flags = 0;
trans_desc.length = 8 * arg_int; // Number of bits NOT number of bytes.
trans_desc.rxlength = 0;
trans_desc.tx_buffer = arg_ptr;
trans_desc.rx_buffer = NULL;
// ESP_LOGI(TAG, "... Transmitting %d bytes.", arg_int);
ESP_ERROR_CHECK(spi_device_transmit(handle_spi, &trans_desc));
break;
}
}
return 0;
} // u8g2_esp32_spi_byte_cb
/*
* HAL callback function as prescribed by the U8G2 library. This callback is
* invoked to handle I2C communications.
*/
uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
ESP_LOGD(TAG, "i2c_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr);
switch (msg)
{
case U8X8_MSG_BYTE_SET_DC: {
if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED)
{
gpio_set_level(u8g2_esp32_hal.dc, arg_int);
}
break;
}
case U8X8_MSG_BYTE_INIT: {
if (u8g2_esp32_hal.bus.i2c.sda == U8G2_ESP32_HAL_UNDEFINED ||
u8g2_esp32_hal.bus.i2c.scl == U8G2_ESP32_HAL_UNDEFINED)
{
break;
}
i2c_config_t conf = {0};
conf.mode = I2C_MODE_MASTER;
ESP_LOGI(TAG, "sda_io_num %d", u8g2_esp32_hal.bus.i2c.sda);
conf.sda_io_num = u8g2_esp32_hal.bus.i2c.sda;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
ESP_LOGI(TAG, "scl_io_num %d", u8g2_esp32_hal.bus.i2c.scl);
conf.scl_io_num = u8g2_esp32_hal.bus.i2c.scl;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
ESP_LOGI(TAG, "clk_speed %d", I2C_MASTER_FREQ_HZ);
conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
ESP_LOGI(TAG, "i2c_param_config %d", conf.mode);
ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf));
ESP_LOGI(TAG, "i2c_driver_install %d", I2C_MASTER_NUM);
ESP_ERROR_CHECK(
i2c_driver_install(I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0));
break;
}
case U8X8_MSG_BYTE_SEND: {
if (i2c_transfer_failed)
{
break; // Skip sending if transfer already failed
}
uint8_t *data_ptr = (uint8_t *)arg_ptr;
ESP_LOG_BUFFER_HEXDUMP(TAG, data_ptr, arg_int, ESP_LOG_VERBOSE);
while (arg_int > 0)
{
I2C_ERROR_CHECK(i2c_master_write_byte(handle_i2c, *data_ptr, ACK_CHECK_EN));
if (i2c_transfer_failed)
{
break;
}
data_ptr++;
arg_int--;
}
break;
}
case U8X8_MSG_BYTE_START_TRANSFER: {
uint8_t i2c_address = u8x8_GetI2CAddress(u8x8);
handle_i2c = i2c_cmd_link_create();
i2c_transfer_failed = false; // Reset error flag at start of transfer
ESP_LOGD(TAG, "Start I2C transfer to %02X.", i2c_address >> 1);
I2C_ERROR_CHECK(i2c_master_start(handle_i2c));
I2C_ERROR_CHECK(i2c_master_write_byte(handle_i2c, i2c_address | I2C_MASTER_WRITE, ACK_CHECK_EN));
break;
}
case U8X8_MSG_BYTE_END_TRANSFER: {
ESP_LOGD(TAG, "End I2C transfer.");
if (!i2c_transfer_failed)
{
I2C_ERROR_CHECK(i2c_master_stop(handle_i2c));
I2C_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, handle_i2c, pdMS_TO_TICKS(I2C_TIMEOUT_MS)));
}
i2c_cmd_link_delete(handle_i2c);
break;
}
}
return 0;
} // u8g2_esp32_i2c_byte_cb
/*
* HAL callback function as prescribed by the U8G2 library. This callback is
* invoked to handle callbacks for GPIO and delay functions.
*/
uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
ESP_LOGD(TAG, "gpio_and_delay_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr);
switch (msg)
{
// Initialize the GPIO and DELAY HAL functions. If the pins for DC and
// RESET have been specified then we define those pins as GPIO outputs.
case U8X8_MSG_GPIO_AND_DELAY_INIT: {
uint64_t bitmask = 0;
if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED)
{
bitmask = bitmask | (1ull << u8g2_esp32_hal.dc);
}
if (u8g2_esp32_hal.reset != U8G2_ESP32_HAL_UNDEFINED)
{
bitmask = bitmask | (1ull << u8g2_esp32_hal.reset);
}
if (u8g2_esp32_hal.bus.spi.cs != U8G2_ESP32_HAL_UNDEFINED)
{
bitmask = bitmask | (1ull << u8g2_esp32_hal.bus.spi.cs);
}
if (bitmask == 0)
{
break;
}
gpio_config_t gpioConfig;
gpioConfig.pin_bit_mask = bitmask;
gpioConfig.mode = GPIO_MODE_OUTPUT;
gpioConfig.pull_up_en = GPIO_PULLUP_DISABLE;
gpioConfig.pull_down_en = GPIO_PULLDOWN_ENABLE;
gpioConfig.intr_type = GPIO_INTR_DISABLE;
gpio_config(&gpioConfig);
break;
}
// Set the GPIO reset pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_RESET:
if (u8g2_esp32_hal.reset != U8G2_ESP32_HAL_UNDEFINED)
{
gpio_set_level(u8g2_esp32_hal.reset, arg_int);
}
break;
// Set the GPIO client select pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_CS:
if (u8g2_esp32_hal.bus.spi.cs != U8G2_ESP32_HAL_UNDEFINED)
{
gpio_set_level(u8g2_esp32_hal.bus.spi.cs, arg_int);
}
break;
// Set the Software I²C pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_I2C_CLOCK:
if (u8g2_esp32_hal.bus.i2c.scl != U8G2_ESP32_HAL_UNDEFINED)
{
gpio_set_level(u8g2_esp32_hal.bus.i2c.scl, arg_int);
// printf("%c",(arg_int==1?'C':'c'));
}
break;
// Set the Software I²C pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_I2C_DATA:
if (u8g2_esp32_hal.bus.i2c.sda != U8G2_ESP32_HAL_UNDEFINED)
{
gpio_set_level(u8g2_esp32_hal.bus.i2c.sda, arg_int);
// printf("%c",(arg_int==1?'D':'d'));
}
break;
// Delay for the number of milliseconds passed in through arg_int.
case U8X8_MSG_DELAY_MILLI:
vTaskDelay(arg_int / portTICK_PERIOD_MS);
break;
}
return 0;
} // u8g2_esp32_gpio_and_delay_cb

View File

@@ -0,0 +1,85 @@
#ifndef U8G2_ESP32_HAL_H
#define U8G2_ESP32_HAL_H
/*
* u8g2_esp32_hal.h
*
* Created on: Feb 12, 2017
* Author: kolban
*/
#ifndef U8G2_ESP32_HAL_H_
#define U8G2_ESP32_HAL_H_
#include "u8g2.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "driver/spi_master.h"
#include "hal/i2c_types.h"
#define U8G2_ESP32_HAL_UNDEFINED GPIO_NUM_NC
#define I2C_MASTER_NUM I2C_NUM_0 // I2C port number for master dev
#define I2C_MASTER_TX_BUF_DISABLE 0 // I2C master do not need buffer
#define I2C_MASTER_RX_BUF_DISABLE 0 // I2C master do not need buffer
#define I2C_MASTER_FREQ_HZ 400000 // I2C master clock frequency
#define ACK_CHECK_EN 0x1 // I2C master will check ack from slave
#define ACK_CHECK_DIS 0x0 // I2C master will not check ack from slave
/** @public
* HAL configuration structure.
*/
typedef struct
{
union {
/* SPI settings. */
struct
{
/* GPIO num for clock. */
gpio_num_t clk;
/* GPIO num for SPI mosi. */
gpio_num_t mosi;
/* GPIO num for SPI slave/chip select. */
gpio_num_t cs;
} spi;
/* I2C settings. */
struct
{
/* GPIO num for I2C data. */
gpio_num_t sda;
/* GPIO num for I2C clock. */
gpio_num_t scl;
} i2c;
} bus;
/* GPIO num for reset. */
gpio_num_t reset;
/* GPIO num for DC. */
gpio_num_t dc;
} u8g2_esp32_hal_t;
/**
* Construct a default HAL configuration with all fields undefined.
*/
#define U8G2_ESP32_HAL_DEFAULT \
{.bus = {.spi = {.clk = U8G2_ESP32_HAL_UNDEFINED, \
.mosi = U8G2_ESP32_HAL_UNDEFINED, \
.cs = U8G2_ESP32_HAL_UNDEFINED}}, \
.reset = U8G2_ESP32_HAL_UNDEFINED, \
.dc = U8G2_ESP32_HAL_UNDEFINED}
__BEGIN_DECLS
/**
* Initialize the HAL with the given configuration.
*
* @see u8g2_esp32_hal_t
* @see U8G2_ESP32_HAL_DEFAULT
*/
void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param);
uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
__END_DECLS
#endif /* U8G2_ESP32_HAL_H_ */
#endif

View File

@@ -0,0 +1,74 @@
#include "i2c_checker.h"
#include "driver/i2c.h"
#include "esp_insights.h"
#include "esp_log.h"
#include "hal/u8g2_esp32_hal.h"
static const char *TAG = "i2c_checker";
esp_err_t i2c_device_check(i2c_port_t i2c_port, uint8_t device_address)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
// Send the device address with the write bit (LSB = 0)
i2c_master_write_byte(cmd, (device_address << 1) | I2C_MASTER_WRITE, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);
return ret;
}
esp_err_t i2c_bus_scan_and_check(void)
{
// 1. Configure and install I2C bus
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_PIN,
.scl_io_num = I2C_MASTER_SCL_PIN,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
esp_err_t err = i2c_param_config(I2C_MASTER_NUM, &conf);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "I2C parameter configuration failed: %s", esp_err_to_name(err));
return err;
}
err = i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "I2C driver installation failed: %s", esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG, "I2C driver initialized. Searching for device...");
// 2. Check if the device is present
err = i2c_device_check(I2C_MASTER_NUM, DISPLAY_I2C_ADDRESS);
if (err == ESP_OK)
{
ESP_LOGI(TAG, "Device found at address 0x%02X!", DISPLAY_I2C_ADDRESS);
// Here you could now call e.g. setup_screen()
}
else if (err == ESP_ERR_TIMEOUT)
{
ESP_LOGE(TAG, "Timeout! Device at address 0x%02X is not responding.", DISPLAY_I2C_ADDRESS);
}
else
{
ESP_LOGE(TAG, "Error communicating with address 0x%02X: %s", DISPLAY_I2C_ADDRESS, esp_err_to_name(err));
}
// 3. Uninstall I2C driver if it is no longer needed
i2c_driver_delete(I2C_MASTER_NUM);
ESP_DIAG_EVENT(TAG, "I2C driver uninstalled.");
return err;
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "driver/gpio.h"
#include "esp_err.h"
#define DISPLAY_I2C_ADDRESS 0x3C
#define I2C_MASTER_SDA_PIN ((gpio_num_t)CONFIG_DISPLAY_SDA_PIN)
#define I2C_MASTER_SCL_PIN ((gpio_num_t)CONFIG_DISPLAY_SCL_PIN)
__BEGIN_DECLS
esp_err_t i2c_bus_scan_and_check(void);
__END_DECLS

View File

@@ -0,0 +1,8 @@
dependencies:
u8g2:
git: https://github.com/olikraus/u8g2.git
# u8g2_hal:
# git: https://github.com/mkfrey/u8g2-hal-esp-idf.git
espressif/button: ^4.1.3
espressif/esp_insights: ^1.2.7
espressif/mqtt: ^1.0.0

39
firmware/main/main.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include "app_task.h"
#include "color.h"
#include "hal_esp32/PersistenceManager.h"
#include "led_status.h"
#include "led_strip_ws2812.h"
#include "wifi_manager.h"
#include <ble_manager.h>
#include <esp_event.h>
#include <esp_insights.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <nvs_flash.h>
#include <sdkconfig.h>
__BEGIN_DECLS
void app_main(void)
{
// Initialize NVS
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
auto persistence = PersistenceManager();
persistence.Load();
led_status_init(CONFIG_STATUS_WLED_PIN);
led_strip_init();
xTaskCreatePinnedToCore(app_task, "app_task", 8192, NULL, tskIDLE_PRIORITY + 5, NULL,
CONFIG_FREERTOS_NUMBER_OF_CORES - 1);
// xTaskCreatePinnedToCore(ble_manager_task, "ble_manager", 4096, NULL, tskIDLE_PRIORITY + 1, NULL,
// CONFIG_FREERTOS_NUMBER_OF_CORES - 1);
}
__END_DECLS

9
firmware/partitions.csv Normal file
View File

@@ -0,0 +1,9 @@
# Name , Type , SubType , Offset , Size , Flags
nvs , data , nvs , 0x9000 , 16k ,
otadata , data , ota , , 8k ,
phy_init , data , phy , , 4k ,
ota_0 , app , ota_0 , 0x10000 , 1536K ,
ota_1 , app , ota_1 , , 1536K ,
storage , data , spiffs , , 256K ,
coredump , data , coredump , , 576k ,
fctry , data , nvs , , 24k ,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 16k
3 otadata data ota 8k
4 phy_init data phy 4k
5 ota_0 app ota_0 0x10000 1536K
6 ota_1 app ota_1 1536K
7 storage data spiffs 256K
8 coredump data coredump 576k
9 fctry data nvs 24k

44
firmware/sdkconfig.defaults Executable file
View File

@@ -0,0 +1,44 @@
# Bluetooth
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
# Logging
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_MAXIMUM_LEVEL=3
# Flash Size
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
# Partitions
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
CONFIG_ESP_INSIGHTS_ENABLED=y
CONFIG_ESP_INSIGHTS_META_VERSION_10=n
CONFIG_DIAG_ENABLE_METRICS=y
CONFIG_DIAG_ENABLE_HEAP_METRICS=y
CONFIG_DIAG_ENABLE_WIFI_METRICS=y
CONFIG_DIAG_ENABLE_VARIABLES=y
CONFIG_DIAG_ENABLE_NETWORK_VARIABLES=y
# Core dump
CONFIG_ESP32_ENABLE_COREDUMP=y
CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH=y
CONFIG_ESP32_COREDUMP_DATA_FORMAT_ELF=y
CONFIG_ESP32_COREDUMP_CHECKSUM_CRC32=y
CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM=64
CONFIG_ESP32_CORE_DUMP_STACK_SIZE=1024
CONFIG_ESP_RMAKER_DEF_TIMEZONE="Europe/Berlin"
# ESP PSRAM
CONFIG_SPIRAM=y
# SPI RAM config
CONFIG_SPIRAM_SPEED=80
CONFIG_SPIRAM_USE_CAPS_ALLOC=y

View File

@@ -0,0 +1,2 @@
# default ESP target
CONFIG_IDF_TARGET="esp32c6"

View File

@@ -0,0 +1,2 @@
# default ESP target
CONFIG_IDF_TARGET="esp32s3"

View File

@@ -0,0 +1,6 @@
# Build type
CONFIG_APP_REPRODUCIBLE_BUILD=y
# Compiler options
CONFIG_COMPILER_OPTIMIZATION_PERF=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y

View File

@@ -0,0 +1,48 @@
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
140,25,112,0,100,250
255,130,112,0,130,250
255,155,112,0,150,250
255,177,115,0,170,250
255,200,135,0,190,250
255,219,170,0,210,250
255,234,205,0,217,250
255,249,240,0,223,250
255,249,250,0,227,250
239,245,255,0,234,250
224,240,255,0,239,250
215,235,255,0,244,250
212,234,255,0,248,250
210,233,255,0,250,250
208,232,255,0,252,250
207,231,255,0,255,250
205,230,255,0,252,250
204,229,255,0,250,250
204,229,255,0,247,250
206,230,255,0,245,250
208,231,255,0,243,250
213,232,255,0,240,250
219,234,255,0,237,250
229,239,255,0,235,250
236,246,255,0,233,250
255,252,251,0,230,250
255,243,236,0,225,250
255,225,202,0,220,250
255,203,174,0,205,250
255,178,129,0,190,250
255,146,85,0,165,250
255,93,38,0,140,250
140,55,70,0,120,250
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
25,25,112,0,100,250
1 25 25 112 0 100 250
2 25 25 112 0 100 250
3 25 25 112 0 100 250
4 25 25 112 0 100 250
5 25 25 112 0 100 250
6 25 25 112 0 100 250
7 25 25 112 0 100 250
8 25 25 112 0 100 250
9 25 25 112 0 100 250
10 140 25 112 0 100 250
11 255 130 112 0 130 250
12 255 155 112 0 150 250
13 255 177 115 0 170 250
14 255 200 135 0 190 250
15 255 219 170 0 210 250
16 255 234 205 0 217 250
17 255 249 240 0 223 250
18 255 249 250 0 227 250
19 239 245 255 0 234 250
20 224 240 255 0 239 250
21 215 235 255 0 244 250
22 212 234 255 0 248 250
23 210 233 255 0 250 250
24 208 232 255 0 252 250
25 207 231 255 0 255 250
26 205 230 255 0 252 250
27 204 229 255 0 250 250
28 204 229 255 0 247 250
29 206 230 255 0 245 250
30 208 231 255 0 243 250
31 213 232 255 0 240 250
32 219 234 255 0 237 250
33 229 239 255 0 235 250
34 236 246 255 0 233 250
35 255 252 251 0 230 250
36 255 243 236 0 225 250
37 255 225 202 0 220 250
38 255 203 174 0 205 250
39 255 178 129 0 190 250
40 255 146 85 0 165 250
41 255 93 38 0 140 250
42 140 55 70 0 120 250
43 25 25 112 0 100 250
44 25 25 112 0 100 250
45 25 25 112 0 100 250
46 25 25 112 0 100 250
47 25 25 112 0 100 250
48 25 25 112 0 100 250

Some files were not shown because too many files have changed in this diff Show More