Compare commits
117 Commits
jlcpcb_202
...
52f6c2acab
| Author | SHA1 | Date | |
|---|---|---|---|
|
52f6c2acab
|
|||
|
dfad7cfb76
|
|||
|
5d78572481
|
|||
|
9e9fb15f86
|
|||
|
e7af663bc3
|
|||
|
e81fc62645
|
|||
|
aa10eb55f4
|
|||
|
c259a3f2c8
|
|||
|
fa05783fb9
|
|||
|
8ba2a5be1d
|
|||
|
016734a4db
|
|||
|
e16cfbd03c
|
|||
|
387b9d4c65
|
|||
|
c27759cd75
|
|||
|
d68667d377
|
|||
|
926e3d1aef
|
|||
|
690e22817e
|
|||
|
4d1b3cc766
|
|||
|
e9b8cdde8b
|
|||
|
96a7be473c
|
|||
|
6f9a0337bb
|
|||
|
aac244705d
|
|||
|
a284cbe35e
|
|||
|
15fb917aaf
|
|||
|
73ed926059
|
|||
|
c586de76a2
|
|||
|
4a2d73c686
|
|||
|
f9010d5491
|
|||
|
0f7686d5a5
|
|||
|
9ae568c2f4
|
|||
|
99aa30c8e5
|
|||
|
08b0e04584
|
|||
|
dc66484f5e
|
|||
|
f0e8ea5aaa
|
|||
|
845fdd306e
|
|||
|
f2e2fe4078
|
|||
|
273f9491f8
|
|||
|
2f03713a4e
|
|||
|
9df2b28eb3
|
|||
|
e17e2504d7
|
|||
|
5d553c0fbb
|
|||
|
59f2d3f83a
|
|||
|
6efbe91747
|
|||
|
a312625085
|
|||
|
6b7ef5b573
|
|||
|
4ac3f93f34
|
|||
|
57047044d6
|
|||
|
1dd1a30ea8
|
|||
|
37c75f3785
|
|||
|
9612e53ca4
|
|||
|
63b8f2ac16
|
|||
|
b05ffb544c
|
|||
|
597bfeee28
|
|||
|
5427570c14
|
|||
|
6e84d57b77
|
|||
|
d8b1718069
|
|||
|
7100f59c7f
|
|||
|
f7e8c86bbd
|
|||
|
1ead66520b
|
|||
| f199f0d781 | |||
| e4476309fd | |||
|
|
b792ebfacd | ||
|
5a08c2e09d
|
|||
|
d316bb9f2c
|
|||
|
2898009516
|
|||
|
c9ea1096a7
|
|||
|
ca996d1c13
|
|||
|
f97f67422a
|
|||
|
ab14765750
|
|||
|
00cfecf13a
|
|||
|
6260c7e62c
|
|||
|
0c8c831eea
|
|||
|
c6f0c4572d
|
|||
|
0b65ac198f
|
|||
|
5805d9ea14
|
|||
|
72fd0bdf1a
|
|||
|
26723db8d8
|
|||
|
54080bfd9d
|
|||
|
a0fe4ba538
|
|||
|
1a912d31c4
|
|||
|
d3dd96c93a
|
|||
|
3ac9565007
|
|||
|
df550540b8
|
|||
|
6645263ba2
|
|||
|
d97f6c48c1
|
|||
|
6cf56ba468
|
|||
|
11c459bad8
|
|||
|
1c7942384b
|
|||
|
9806d4c9ae
|
|||
|
d9a0dfb8bd
|
|||
|
ea0208083f
|
|||
|
5464bacc52
|
|||
|
2191174681
|
|||
|
52a49363eb
|
|||
|
4aa3e2cbeb
|
|||
|
280ad59ff8
|
|||
|
e487b8357a
|
|||
|
5e2456f4b8
|
|||
|
f875f7832f
|
|||
|
d7fbbcc869
|
|||
|
266114d046
|
|||
| 60fccfeccc | |||
|
5a3a433d17
|
|||
|
ecd26bf2ee
|
|||
|
a56435c49d
|
|||
|
e5e602d1fc
|
|||
|
fa5b4da0f5
|
|||
|
75169956ea
|
|||
|
6e22bee95c
|
|||
|
5b82cd8189
|
|||
|
b3bf03999b
|
|||
|
b6fb4eb65c
|
|||
|
eae8b78c60
|
|||
|
858c45fdcc
|
|||
|
e94aeb7942
|
|||
|
48b296b900
|
|||
|
7a10803af5
|
38
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
38
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal 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.
|
||||
20
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
20
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal 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.
|
||||
52
.gitea/workflows/esp32_build.yml
Normal file
52
.gitea/workflows/esp32_build.yml
Normal 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
34
.gitignore
vendored
@@ -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
4
.gitmodules
vendored
@@ -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
128
CODE_OF_CONDUCT.md
Normal 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
661
LICENSE
Normal 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/>.
|
||||
@@ -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
3
README.md
Normal 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
2
firmware/.clang-format
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
BasedOnStyle: Microsoft
|
||||
14
firmware/.clang-tidy
Normal file
14
firmware/.clang-tidy
Normal 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: ''
|
||||
13
firmware/.devcontainer/Dockerfile
Normal file
13
firmware/.devcontainer/Dockerfile
Normal 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"]
|
||||
21
firmware/.devcontainer/devcontainer.json
Normal file
21
firmware/.devcontainer/devcontainer.json
Normal 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
42
firmware/.gitignore
vendored
Normal 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
15
firmware/.vscode/launch.json
vendored
Normal 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
4
firmware/CMakeLists.txt
Executable 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
661
firmware/LICENSE
Normal 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
2
firmware/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
release:
|
||||
idf.py -B build-release -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release" fullclean build
|
||||
722
firmware/README-API.md
Normal file
722
firmware/README-API.md
Normal file
@@ -0,0 +1,722 @@
|
||||
# System Control - API Documentation
|
||||
|
||||
This document describes all REST API endpoints and WebSocket messages required for the ESP32 firmware implementation.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [REST API Endpoints](#rest-api-endpoints)
|
||||
- [WiFi](#wifi)
|
||||
- [Light Control](#light-control)
|
||||
- [Schema](#schema)
|
||||
- [Devices](#devices)
|
||||
- [Scenes](#scenes)
|
||||
- [WebSocket](#websocket)
|
||||
- [Connection](#connection)
|
||||
- [Client to Server Messages](#client-to-server-messages)
|
||||
- [Server to Client Messages](#server-to-client-messages)
|
||||
|
||||
---
|
||||
|
||||
## REST API Endpoints
|
||||
|
||||
### WiFi
|
||||
|
||||
#### Scan Networks
|
||||
|
||||
Scans for available WiFi networks.
|
||||
|
||||
- **URL:** `/api/wifi/scan`
|
||||
- **Method:** `GET`
|
||||
- **Response:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"ssid": "NetworkName",
|
||||
"rssi": -45
|
||||
},
|
||||
{
|
||||
"ssid": "AnotherNetwork",
|
||||
"rssi": -72
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|--------|------------------------------------|
|
||||
| ssid | string | Network name (SSID) |
|
||||
| rssi | number | Signal strength in dBm |
|
||||
|
||||
---
|
||||
|
||||
#### Save WiFi Configuration
|
||||
|
||||
Saves WiFi credentials and initiates connection.
|
||||
|
||||
- **URL:** `/api/wifi/config`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ssid": "NetworkName",
|
||||
"password": "SecretPassword123"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|----------|--------|----------|-------------------|
|
||||
| ssid | string | Yes | Network name |
|
||||
| password | string | No | Network password |
|
||||
|
||||
- **Response:** `200 OK` on success, `400 Bad Request` on error
|
||||
|
||||
---
|
||||
|
||||
#### Get WiFi Status
|
||||
|
||||
Returns current WiFi connection status.
|
||||
|
||||
- **URL:** `/api/wifi/status`
|
||||
- **Method:** `GET`
|
||||
- **Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"connected": true,
|
||||
"ssid": "NetworkName",
|
||||
"ip": "192.168.1.100",
|
||||
"rssi": -45
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-----------|---------|------------------------------------------|
|
||||
| connected | boolean | Whether connected to a network |
|
||||
| ssid | string | Connected network name (if connected) |
|
||||
| ip | string | Assigned IP address (if connected) |
|
||||
| rssi | number | Signal strength in dBm (if connected) |
|
||||
|
||||
---
|
||||
|
||||
### Light Control
|
||||
|
||||
#### Set Light Power
|
||||
|
||||
Turns the main light on or off.
|
||||
|
||||
- **URL:** `/api/light/power`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"on": true
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|---------|----------|--------------------------|
|
||||
| on | boolean | Yes | `true` = on, `false` = off |
|
||||
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
#### Set Light Mode
|
||||
|
||||
Sets the lighting mode.
|
||||
|
||||
- **URL:** `/api/light/mode`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "simulation"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|--------|----------|------------------------------------------------|
|
||||
| mode | string | Yes | One of: `day`, `night`, `simulation` |
|
||||
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
#### Set Active Schema
|
||||
|
||||
Sets the active schema for simulation mode.
|
||||
|
||||
- **URL:** `/api/light/schema`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "schema_01.csv"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|--------|----------|-------------------------------------------------------|
|
||||
| schema | string | Yes | Schema filename: `schema_01.csv`, `schema_02.csv`, etc. |
|
||||
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
#### Get Light Status
|
||||
|
||||
Returns current light status (alternative to WebSocket).
|
||||
|
||||
- **URL:** `/api/light/status`
|
||||
- **Method:** `GET`
|
||||
- **Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"on": true,
|
||||
"mode": "simulation",
|
||||
"schema": "schema_01.csv",
|
||||
"color": {
|
||||
"r": 255,
|
||||
"g": 240,
|
||||
"b": 220
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|--------|---------|--------------------------------------|
|
||||
| on | boolean | Current power state |
|
||||
| mode | string | Current mode (day/night/simulation) |
|
||||
| schema | string | Active schema filename |
|
||||
| color | object | Current RGB color being displayed |
|
||||
|
||||
---
|
||||
|
||||
### Schema
|
||||
|
||||
#### Load Schema
|
||||
|
||||
Loads a schema file.
|
||||
|
||||
- **URL:** `/api/schema/{filename}`
|
||||
- **Method:** `GET`
|
||||
- **URL Parameters:**
|
||||
- `filename`: Schema file name (e.g., `schema_01.csv`)
|
||||
- **Response:** CSV text data
|
||||
|
||||
```
|
||||
255,240,220,0,100,250
|
||||
255,230,200,0,120,250
|
||||
...
|
||||
```
|
||||
|
||||
The CSV format has 48 rows (one per 30-minute interval) with 6 values per row:
|
||||
|
||||
| Column | Description | Range |
|
||||
|--------|--------------------------------|---------|
|
||||
| 1 | Red (R) | 0-255 |
|
||||
| 2 | Green (G) | 0-255 |
|
||||
| 3 | Blue (B) | 0-255 |
|
||||
| 4 | Value 1 (V1) - custom value | 0-255 |
|
||||
| 5 | Value 2 (V2) - custom value | 0-255 |
|
||||
| 6 | Value 3 (V3) - custom value | 0-255 |
|
||||
|
||||
---
|
||||
|
||||
#### Save Schema
|
||||
|
||||
Saves a schema file.
|
||||
|
||||
- **URL:** `/api/schema/{filename}`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `text/csv`
|
||||
- **URL Parameters:**
|
||||
- `filename`: Schema file name (e.g., `schema_01.csv`)
|
||||
- **Request Body:** CSV text data (same format as above)
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
### Devices
|
||||
|
||||
#### Scan for Devices
|
||||
|
||||
Scans for available Matter devices to pair.
|
||||
|
||||
- **URL:** `/api/devices/scan`
|
||||
- **Method:** `GET`
|
||||
- **Response:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "matter-001",
|
||||
"type": "light",
|
||||
"name": "Matter Lamp"
|
||||
},
|
||||
{
|
||||
"id": "matter-002",
|
||||
"type": "sensor",
|
||||
"name": "Temperature Sensor"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|--------|-----------------------------------------------|
|
||||
| id | string | Unique device identifier |
|
||||
| type | string | Device type: `light`, `sensor`, `unknown` |
|
||||
| name | string | Device name (can be empty) |
|
||||
|
||||
---
|
||||
|
||||
#### Pair Device
|
||||
|
||||
Pairs a discovered device.
|
||||
|
||||
- **URL:** `/api/devices/pair`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "matter-001",
|
||||
"name": "Living Room Lamp"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|--------|----------|------------------------------|
|
||||
| id | string | Yes | Device ID from scan |
|
||||
| name | string | Yes | User-defined device name |
|
||||
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
#### Get Paired Devices
|
||||
|
||||
Returns list of all paired devices.
|
||||
|
||||
- **URL:** `/api/devices/paired`
|
||||
- **Method:** `GET`
|
||||
- **Response:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "matter-001",
|
||||
"type": "light",
|
||||
"name": "Living Room Lamp"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|--------|-------------------------------------------|
|
||||
| id | string | Unique device identifier |
|
||||
| type | string | Device type: `light`, `sensor`, `unknown` |
|
||||
| name | string | User-defined device name |
|
||||
|
||||
---
|
||||
|
||||
#### Update Device Name
|
||||
|
||||
Updates the name of a paired device.
|
||||
|
||||
- **URL:** `/api/devices/update`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "matter-001",
|
||||
"name": "New Device Name"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|--------|----------|------------------------|
|
||||
| id | string | Yes | Device ID |
|
||||
| name | string | Yes | New device name |
|
||||
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
#### Unpair Device
|
||||
|
||||
Removes a paired device.
|
||||
|
||||
- **URL:** `/api/devices/unpair`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "matter-001"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|--------|----------|---------------|
|
||||
| id | string | Yes | Device ID |
|
||||
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
#### Toggle Device
|
||||
|
||||
Toggles a device (e.g., light on/off).
|
||||
|
||||
- **URL:** `/api/devices/toggle`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "matter-001"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|--------|----------|---------------|
|
||||
| id | string | Yes | Device ID |
|
||||
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
### Scenes
|
||||
|
||||
#### Get All Scenes
|
||||
|
||||
Returns all configured scenes.
|
||||
|
||||
- **URL:** `/api/scenes`
|
||||
- **Method:** `GET`
|
||||
- **Response:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "scene-1",
|
||||
"name": "Evening Mood",
|
||||
"icon": "🌅",
|
||||
"actions": {
|
||||
"light": "on",
|
||||
"mode": "simulation",
|
||||
"schema": "schema_02.csv",
|
||||
"devices": [
|
||||
{
|
||||
"id": "matter-001",
|
||||
"state": "on"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "scene-2",
|
||||
"name": "Night Mode",
|
||||
"icon": "🌙",
|
||||
"actions": {
|
||||
"light": "on",
|
||||
"mode": "night"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Scene Object:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---------|--------|------------------------------------|
|
||||
| id | string | Unique scene identifier |
|
||||
| name | string | Scene name |
|
||||
| icon | string | Emoji icon for the scene |
|
||||
| actions | object | Actions to execute (see below) |
|
||||
|
||||
**Actions Object:**
|
||||
|
||||
| Field | Type | Optional | Description |
|
||||
|---------|--------|----------|------------------------------------------|
|
||||
| light | string | Yes | `"on"` or `"off"` |
|
||||
| mode | string | Yes | `"day"`, `"night"`, or `"simulation"` |
|
||||
| schema | string | Yes | Schema filename (e.g., `schema_01.csv`) |
|
||||
| devices | array | Yes | Array of device actions (see below) |
|
||||
|
||||
**Device Action Object:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|--------|----------------------------------|
|
||||
| id | string | Device ID |
|
||||
| state | string | `"on"` or `"off"` |
|
||||
|
||||
---
|
||||
|
||||
#### Create/Update Scene
|
||||
|
||||
Creates a new scene or updates an existing one.
|
||||
|
||||
- **URL:** `/api/scenes`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "scene-1",
|
||||
"name": "Evening Mood",
|
||||
"icon": "🌅",
|
||||
"actions": {
|
||||
"light": "on",
|
||||
"mode": "simulation",
|
||||
"schema": "schema_02.csv",
|
||||
"devices": [
|
||||
{
|
||||
"id": "matter-001",
|
||||
"state": "on"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
#### Delete Scene
|
||||
|
||||
Deletes a scene.
|
||||
|
||||
- **URL:** `/api/scenes`
|
||||
- **Method:** `DELETE`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "scene-1"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|--------|----------|---------------|
|
||||
| id | string | Yes | Scene ID |
|
||||
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
#### Activate Scene
|
||||
|
||||
Executes all actions of a scene.
|
||||
|
||||
- **URL:** `/api/scenes/activate`
|
||||
- **Method:** `POST`
|
||||
- **Content-Type:** `application/json`
|
||||
- **Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "scene-1"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|--------|----------|---------------|
|
||||
| id | string | Yes | Scene ID |
|
||||
|
||||
- **Response:** `200 OK` on success
|
||||
|
||||
---
|
||||
|
||||
## WebSocket
|
||||
|
||||
### Connection
|
||||
|
||||
- **URL:** `ws://{host}/ws` or `wss://{host}/ws`
|
||||
- **Protocol:** JSON messages
|
||||
|
||||
The WebSocket connection is used for real-time status updates. The client should reconnect automatically if the connection is lost (recommended: 3 second delay).
|
||||
|
||||
---
|
||||
|
||||
### Client to Server Messages
|
||||
|
||||
#### Request Status
|
||||
|
||||
Requests the current system status.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "getStatus"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Server to Client Messages
|
||||
|
||||
#### Status Update
|
||||
|
||||
Sent in response to `getStatus` or when status changes.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "status",
|
||||
"on": true,
|
||||
"mode": "simulation",
|
||||
"schema": "schema_01.csv",
|
||||
"color": {
|
||||
"r": 255,
|
||||
"g": 240,
|
||||
"b": 220
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|--------|---------|--------------------------------------|
|
||||
| type | string | Always `"status"` |
|
||||
| on | boolean | Light power state |
|
||||
| mode | string | Current mode (day/night/simulation) |
|
||||
| schema | string | Active schema filename |
|
||||
| color | object | Current RGB color (optional) |
|
||||
|
||||
---
|
||||
|
||||
#### Color Update
|
||||
|
||||
Sent when the current color changes (during simulation).
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "color",
|
||||
"r": 255,
|
||||
"g": 200,
|
||||
"b": 150
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|--------|--------------------------|
|
||||
| type | string | Always `"color"` |
|
||||
| r | number | Red value (0-255) |
|
||||
| g | number | Green value (0-255) |
|
||||
| b | number | Blue value (0-255) |
|
||||
|
||||
---
|
||||
|
||||
#### WiFi Status Update
|
||||
|
||||
Sent when WiFi connection status changes.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "wifi",
|
||||
"connected": true,
|
||||
"ip": "192.168.1.100",
|
||||
"rssi": -45
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-----------|---------|---------------------------------------|
|
||||
| type | string | Always `"wifi"` |
|
||||
| connected | boolean | Whether connected to a network |
|
||||
| ip | string | Assigned IP address (if connected) |
|
||||
| rssi | number | Signal strength in dBm (if connected) |
|
||||
|
||||
---
|
||||
|
||||
## Static Files
|
||||
|
||||
The web interface files should be served from the `/storage/www/` directory:
|
||||
|
||||
| Path | Description |
|
||||
|----------------------|------------------------------------------------|
|
||||
| `/` | Serves `index.html` (or `captive.html` in AP mode) |
|
||||
| `/index.html` | Main HTML file (full interface) |
|
||||
| `/captive.html` | Captive portal (WiFi setup only) |
|
||||
| `/css/shared.css` | Shared styles for all pages |
|
||||
| `/css/index.css` | Styles for main interface |
|
||||
| `/css/captive.css` | Styles for captive portal |
|
||||
| `/js/wifi-shared.js` | Shared WiFi configuration logic |
|
||||
| `/js/*.js` | JavaScript modules |
|
||||
|
||||
---
|
||||
|
||||
## Captive Portal
|
||||
|
||||
When the ESP32 is in Access Point (AP) mode (no WiFi configured or connection failed), it should serve the captive portal:
|
||||
|
||||
### Behavior
|
||||
|
||||
1. **AP Mode Activation:**
|
||||
- ESP32 creates an access point (e.g., "marklin-setup")
|
||||
- DNS server redirects all requests to the ESP32's IP (captive portal detection)
|
||||
|
||||
2. **Captive Portal Detection:**
|
||||
- Respond to common captive portal detection URLs:
|
||||
- `/generate_204` (Android)
|
||||
- `/hotspot-detect.html` (Apple)
|
||||
- `/connecttest.txt` (Windows)
|
||||
- Return redirect or serve `captive.html`
|
||||
|
||||
3. **Serving Files in AP Mode:**
|
||||
- `/` → `captive.html`
|
||||
- `/captive.html` → Captive portal page
|
||||
- `/js/wifi-shared.js` → WiFi functions
|
||||
- API endpoints remain the same (`/api/wifi/*`)
|
||||
|
||||
4. **After Successful Configuration:**
|
||||
- ESP32 attempts to connect to the configured network
|
||||
- If successful, switch to Station mode and serve `index.html`
|
||||
- If failed, remain in AP mode
|
||||
|
||||
### Recommended AP Settings
|
||||
|
||||
| Setting | Value |
|
||||
|---------------|--------------------------|
|
||||
| SSID | `marklin-setup` |
|
||||
| Password | None (open) or `marklin` |
|
||||
| IP Address | `192.168.4.1` |
|
||||
| Gateway | `192.168.4.1` |
|
||||
| Subnet | `255.255.255.0` |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
All endpoints should return appropriate HTTP status codes:
|
||||
|
||||
| Code | Description |
|
||||
|------|---------------------------------------|
|
||||
| 200 | Success |
|
||||
| 400 | Bad Request (invalid input) |
|
||||
| 404 | Not Found (resource doesn't exist) |
|
||||
| 500 | Internal Server Error |
|
||||
|
||||
Error responses should include a JSON body with an error message:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Description of what went wrong"
|
||||
}
|
||||
```
|
||||
209
firmware/README-captive.md
Normal file
209
firmware/README-captive.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Captive Portal Implementation Guide
|
||||
|
||||
This document describes how to implement the captive portal functionality on the ESP32 side to work with `captive.html`.
|
||||
|
||||
## Overview
|
||||
|
||||
When the ESP32 has no WiFi credentials stored (or connection fails), it should start in Access Point (AP) mode and serve a captive portal that allows users to configure WiFi settings.
|
||||
|
||||
## How Captive Portal Detection Works
|
||||
|
||||
Operating systems automatically send HTTP requests to known URLs to check for internet connectivity:
|
||||
|
||||
| OS | Detection URL | Expected Response |
|
||||
|---|---|---|
|
||||
| **iOS/macOS** | `http://captive.apple.com/hotspot-detect.html` | `<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>` |
|
||||
| **Android** | `http://connectivitycheck.gstatic.com/generate_204` | HTTP 204 No Content |
|
||||
| **Windows** | `http://www.msftconnecttest.com/connecttest.txt` | `Microsoft Connect Test` |
|
||||
|
||||
If the response doesn't match, the OS assumes there's a captive portal and opens a browser.
|
||||
|
||||
## ESP32 Implementation Steps
|
||||
|
||||
### 1. Start Access Point Mode
|
||||
|
||||
```c
|
||||
wifi_config_t ap_config = {
|
||||
.ap = {
|
||||
.ssid = "SystemControl-Setup",
|
||||
.ssid_len = 0,
|
||||
.password = "", // Open network for easy access
|
||||
.max_connection = 4,
|
||||
.authmode = WIFI_AUTH_OPEN
|
||||
}
|
||||
};
|
||||
esp_wifi_set_mode(WIFI_MODE_AP);
|
||||
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
|
||||
esp_wifi_start();
|
||||
```
|
||||
|
||||
### 2. Start DNS Server (DNS Hijacking)
|
||||
|
||||
Redirect ALL DNS queries to the ESP32's IP address:
|
||||
|
||||
```c
|
||||
// Simplified example - use a proper DNS server component
|
||||
void dns_server_task(void *pvParameters) {
|
||||
// Listen on UDP port 53
|
||||
// For any DNS query, respond with ESP32's AP IP (e.g., 192.168.4.1)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Configure HTTP Server with Redirects
|
||||
|
||||
```c
|
||||
// Handler for captive portal detection URLs
|
||||
esp_err_t captive_redirect_handler(httpd_req_t *req) {
|
||||
httpd_resp_set_status(req, "302 Found");
|
||||
httpd_resp_set_hdr(req, "Location", "http://192.168.4.1/captive.html");
|
||||
httpd_resp_send(req, NULL, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Register handlers for detection URLs
|
||||
httpd_uri_t apple_detect = {
|
||||
.uri = "/hotspot-detect.html",
|
||||
.method = HTTP_GET,
|
||||
.handler = captive_redirect_handler
|
||||
};
|
||||
|
||||
httpd_uri_t android_detect = {
|
||||
.uri = "/generate_204",
|
||||
.method = HTTP_GET,
|
||||
.handler = captive_redirect_handler
|
||||
};
|
||||
|
||||
// Catch-all for any unknown paths
|
||||
httpd_uri_t catch_all = {
|
||||
.uri = "/*",
|
||||
.method = HTTP_GET,
|
||||
.handler = captive_redirect_handler
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Serve Static Files
|
||||
|
||||
Serve the captive portal files from SPIFFS/LittleFS:
|
||||
|
||||
- `/captive.html` - Main captive portal page
|
||||
- `/favicon.svg` - Favicon
|
||||
- `/css/shared.css` - Shared styles
|
||||
- `/css/captive.css` - Captive-specific styles
|
||||
- `/js/i18n.js` - Internationalization
|
||||
- `/js/wifi-shared.js` - WiFi configuration logic
|
||||
|
||||
### 5. Implement WiFi Configuration API
|
||||
|
||||
```c
|
||||
// POST /api/wifi/config
|
||||
// Body: { "ssid": "NetworkName", "password": "SecretPassword" }
|
||||
esp_err_t wifi_config_handler(httpd_req_t *req) {
|
||||
// 1. Parse JSON body
|
||||
// 2. Store credentials in NVS
|
||||
// 3. Send success response
|
||||
// 4. Schedule restart/reconnect
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// GET /api/wifi/scan
|
||||
// Returns: [{ "ssid": "Network1", "rssi": -45 }, ...]
|
||||
esp_err_t wifi_scan_handler(httpd_req_t *req) {
|
||||
// 1. Perform WiFi scan
|
||||
// 2. Return JSON array of networks
|
||||
return ESP_OK;
|
||||
}
|
||||
```
|
||||
|
||||
## Flow After User Submits WiFi Credentials
|
||||
|
||||
```
|
||||
1. User enters SSID + Password, clicks "Connect"
|
||||
↓
|
||||
2. Frontend sends POST /api/wifi/config
|
||||
↓
|
||||
3. ESP32 stores credentials in NVS (Non-Volatile Storage)
|
||||
↓
|
||||
4. ESP32 sends HTTP 200 OK response
|
||||
↓
|
||||
5. Frontend shows countdown (10 seconds)
|
||||
↓
|
||||
6. ESP32 stops AP mode
|
||||
↓
|
||||
7. ESP32 connects to configured WiFi
|
||||
↓
|
||||
8. ESP32 gets new IP from router (e.g., 192.168.1.42)
|
||||
↓
|
||||
9. User connects phone/PC to normal WiFi
|
||||
↓
|
||||
10. User accesses ESP32 via new IP or mDNS (e.g., http://system-control.local)
|
||||
```
|
||||
|
||||
## Recommended: mDNS Support
|
||||
|
||||
Register an mDNS hostname so users can access the device without knowing the IP:
|
||||
|
||||
```c
|
||||
mdns_init();
|
||||
mdns_hostname_set("system-control");
|
||||
mdns_instance_name_set("System Control");
|
||||
```
|
||||
|
||||
Then the device is accessible at: `http://system-control.local`
|
||||
|
||||
## Error Handling / Fallback
|
||||
|
||||
If WiFi connection fails after credentials are saved:
|
||||
|
||||
1. Wait for connection timeout (e.g., 30 seconds)
|
||||
2. If connection fails, restart in AP mode
|
||||
3. Show error message on captive portal
|
||||
4. Allow user to re-enter credentials
|
||||
|
||||
```c
|
||||
// Pseudo-code
|
||||
if (wifi_connect_timeout()) {
|
||||
nvs_erase_key("wifi_ssid");
|
||||
nvs_erase_key("wifi_password");
|
||||
esp_restart(); // Will boot into AP mode again
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoints Summary
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/wifi/scan` | Scan for available networks |
|
||||
| POST | `/api/wifi/config` | Save WiFi credentials |
|
||||
| GET | `/api/wifi/status` | Get current connection status |
|
||||
|
||||
### Request/Response Examples
|
||||
|
||||
**GET /api/wifi/scan**
|
||||
```json
|
||||
[
|
||||
{ "ssid": "HomeNetwork", "rssi": -45, "secure": true },
|
||||
{ "ssid": "GuestWiFi", "rssi": -67, "secure": false }
|
||||
]
|
||||
```
|
||||
|
||||
**POST /api/wifi/config**
|
||||
```json
|
||||
{ "ssid": "HomeNetwork", "password": "MySecretPassword" }
|
||||
```
|
||||
|
||||
**GET /api/wifi/status**
|
||||
```json
|
||||
{
|
||||
"connected": true,
|
||||
"ssid": "HomeNetwork",
|
||||
"ip": "192.168.1.42",
|
||||
"rssi": -52
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Open AP**: The setup AP is intentionally open for easy access. Keep setup time short.
|
||||
2. **HTTPS**: Consider using HTTPS for the main interface (after WiFi setup).
|
||||
3. **Timeout**: Auto-disable AP mode after successful connection.
|
||||
4. **Button Reset**: Implement a physical button to reset WiFi credentials and re-enter AP mode.
|
||||
15
firmware/README.md
Executable file
15
firmware/README.md
Executable 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.
|
||||
10
firmware/components/analytics/CMakeLists.txt
Normal file
10
firmware/components/analytics/CMakeLists.txt
Normal 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)
|
||||
7
firmware/components/analytics/include/analytics.h
Normal file
7
firmware/components/analytics/include/analytics.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
void analytics_init(void);
|
||||
__END_DECLS
|
||||
20
firmware/components/analytics/src/analytics.c
Normal file
20
firmware/components/analytics/src/analytics.c
Normal 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);
|
||||
}
|
||||
13
firmware/components/connectivity-manager/CMakeLists.txt
Normal file
13
firmware/components/connectivity-manager/CMakeLists.txt
Normal 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
|
||||
)
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
void wifi_manager_init(void);
|
||||
__END_DECLS
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
324
firmware/components/connectivity-manager/src/ble/ble_scanner.c
Normal file
324
firmware/components/connectivity-manager/src/ble/ble_scanner.c
Normal 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;
|
||||
}
|
||||
60
firmware/components/connectivity-manager/src/ble_manager.c
Normal file
60
firmware/components/connectivity-manager/src/ble_manager.c
Normal 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);
|
||||
}
|
||||
}
|
||||
229
firmware/components/connectivity-manager/src/wifi_manager.c
Normal file
229
firmware/components/connectivity-manager/src/wifi_manager.c
Normal 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
|
||||
}
|
||||
21
firmware/components/insa/CMakeLists.txt
Normal file
21
firmware/components/insa/CMakeLists.txt
Normal 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
|
||||
)
|
||||
74
firmware/components/insa/include/MenuOptions.h
Normal file
74
firmware/components/insa/include/MenuOptions.h
Normal 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;
|
||||
95
firmware/components/insa/include/common/Common.h
Normal file
95
firmware/components/insa/include/common/Common.h
Normal 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"
|
||||
194
firmware/components/insa/include/common/InactivityTracker.h
Normal file
194
firmware/components/insa/include/common/InactivityTracker.h
Normal 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
|
||||
};
|
||||
223
firmware/components/insa/include/common/Menu.h
Normal file
223
firmware/components/insa/include/common/Menu.h
Normal 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;
|
||||
};
|
||||
106
firmware/components/insa/include/common/ScrollBar.h
Normal file
106
firmware/components/insa/include/common/ScrollBar.h
Normal 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
|
||||
};
|
||||
181
firmware/components/insa/include/common/Widget.h
Normal file
181
firmware/components/insa/include/common/Widget.h
Normal 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;
|
||||
};
|
||||
310
firmware/components/insa/include/data/MenuItem.h
Normal file
310
firmware/components/insa/include/data/MenuItem.h
Normal 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;
|
||||
};
|
||||
23
firmware/components/insa/include/data/roads.h
Normal file
23
firmware/components/insa/include/data/roads.h
Normal 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};
|
||||
55
firmware/components/insa/include/data/vehicles.h
Normal file
55
firmware/components/insa/include/data/vehicles.h
Normal 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};
|
||||
70
firmware/components/insa/include/ui/ClockScreenSaver.h
Normal file
70
firmware/components/insa/include/ui/ClockScreenSaver.h
Normal 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();
|
||||
};
|
||||
15
firmware/components/insa/include/ui/ExternalDevices.h
Normal file
15
firmware/components/insa/include/ui/ExternalDevices.h
Normal 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;
|
||||
};
|
||||
143
firmware/components/insa/include/ui/LightMenu.h
Normal file
143
firmware/components/insa/include/ui/LightMenu.h
Normal 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;
|
||||
};
|
||||
106
firmware/components/insa/include/ui/MainMenu.h
Normal file
106
firmware/components/insa/include/ui/MainMenu.h
Normal 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;
|
||||
};
|
||||
143
firmware/components/insa/include/ui/ScreenSaver.h
Normal file
143
firmware/components/insa/include/ui/ScreenSaver.h
Normal 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;
|
||||
};
|
||||
79
firmware/components/insa/include/ui/SettingsMenu.h
Normal file
79
firmware/components/insa/include/ui/SettingsMenu.h
Normal 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;
|
||||
};
|
||||
179
firmware/components/insa/include/ui/SplashScreen.h
Normal file
179
firmware/components/insa/include/ui/SplashScreen.h
Normal 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;
|
||||
};
|
||||
35
firmware/components/insa/src/common/InactivityTracker.cpp
Normal file
35
firmware/components/insa/src/common/InactivityTracker.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
386
firmware/components/insa/src/common/Menu.cpp
Normal file
386
firmware/components/insa/src/common/Menu.cpp
Normal 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);
|
||||
}
|
||||
41
firmware/components/insa/src/common/ScrollBar.cpp
Normal file
41
firmware/components/insa/src/common/ScrollBar.cpp
Normal 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);
|
||||
}
|
||||
38
firmware/components/insa/src/common/Widget.cpp
Normal file
38
firmware/components/insa/src/common/Widget.cpp
Normal 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";
|
||||
}
|
||||
99
firmware/components/insa/src/data/MenuItem.cpp
Normal file
99
firmware/components/insa/src/data/MenuItem.cpp
Normal 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;
|
||||
}
|
||||
136
firmware/components/insa/src/ui/ClockScreenSaver.cpp
Normal file
136
firmware/components/insa/src/ui/ClockScreenSaver.cpp
Normal 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)
|
||||
27
firmware/components/insa/src/ui/ExternalDevices.cpp
Normal file
27
firmware/components/insa/src/ui/ExternalDevices.cpp
Normal 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)
|
||||
127
firmware/components/insa/src/ui/LightMenu.cpp
Normal file
127
firmware/components/insa/src/ui/LightMenu.cpp
Normal 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)
|
||||
52
firmware/components/insa/src/ui/MainMenu.cpp
Normal file
52
firmware/components/insa/src/ui/MainMenu.cpp
Normal 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)
|
||||
331
firmware/components/insa/src/ui/ScreenSaver.cpp
Normal file
331
firmware/components/insa/src/ui/ScreenSaver.cpp
Normal 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)
|
||||
13
firmware/components/insa/src/ui/SettingsMenu.cpp
Normal file
13
firmware/components/insa/src/ui/SettingsMenu.cpp
Normal 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)
|
||||
32
firmware/components/insa/src/ui/SplashScreen.cpp
Normal file
32
firmware/components/insa/src/ui/SplashScreen.cpp
Normal 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)
|
||||
13
firmware/components/led-manager/CMakeLists.txt
Normal file
13
firmware/components/led-manager/CMakeLists.txt
Normal 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
|
||||
)
|
||||
19
firmware/components/led-manager/Kconfig
Normal file
19
firmware/components/led-manager/Kconfig
Normal 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
|
||||
2
firmware/components/led-manager/idf_component.yml
Executable file
2
firmware/components/led-manager/idf_component.yml
Executable file
@@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
espressif/led_strip: '~3.0.1'
|
||||
29
firmware/components/led-manager/include/Matrix.h
Normal file
29
firmware/components/led-manager/include/Matrix.h
Normal 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;
|
||||
};
|
||||
25
firmware/components/led-manager/include/color.h
Normal file
25
firmware/components/led-manager/include/color.h
Normal 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
|
||||
47
firmware/components/led-manager/include/led_status.h
Normal file
47
firmware/components/led-manager/include/led_status.h
Normal 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
|
||||
18
firmware/components/led-manager/include/led_strip_ws2812.h
Normal file
18
firmware/components/led-manager/include/led_strip_ws2812.h
Normal 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
|
||||
183
firmware/components/led-manager/src/color.c
Normal file
183
firmware/components/led-manager/src/color.c
Normal 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;
|
||||
}
|
||||
155
firmware/components/led-manager/src/led_status.c
Normal file
155
firmware/components/led-manager/src/led_status.c
Normal 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;
|
||||
}
|
||||
117
firmware/components/led-manager/src/led_strip_ws2812.c
Normal file
117
firmware/components/led-manager/src/led_strip_ws2812.c
Normal 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;
|
||||
}
|
||||
6
firmware/components/persistence-manager/CMakeLists.txt
Normal file
6
firmware/components/persistence-manager/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
idf_component_register(SRCS
|
||||
src/PersistenceManager.cpp
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES
|
||||
nvs_flash
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
9
firmware/components/simulator/CMakeLists.txt
Normal file
9
firmware/components/simulator/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
firmware/components/simulator/include/simulator.h
Normal file
20
firmware/components/simulator/include/simulator.h
Normal 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);
|
||||
4
firmware/components/simulator/include/storage.h
Normal file
4
firmware/components/simulator/include/storage.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void initialize_storage();
|
||||
void load_file(const char *filename);
|
||||
429
firmware/components/simulator/src/simulator.cpp
Normal file
429
firmware/components/simulator/src/simulator.cpp
Normal 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{});
|
||||
}
|
||||
}
|
||||
99
firmware/components/simulator/src/storage.cpp
Normal file
99
firmware/components/simulator/src/storage.cpp
Normal 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
26
firmware/main/CMakeLists.txt
Executable 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)
|
||||
108
firmware/main/Kconfig.projbuild
Normal file
108
firmware/main/Kconfig.projbuild
Normal 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
227
firmware/main/app_task.cpp
Normal 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
3
firmware/main/app_task.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void app_task(void *args);
|
||||
93
firmware/main/button_handling.c
Normal file
93
firmware/main/button_handling.c
Normal 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);
|
||||
}
|
||||
}
|
||||
8
firmware/main/button_handling.h
Normal file
8
firmware/main/button_handling.h
Normal 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
8
firmware/main/common.h
Normal 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
|
||||
292
firmware/main/hal/u8g2_esp32_hal.c
Normal file
292
firmware/main/hal/u8g2_esp32_hal.c
Normal 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
|
||||
85
firmware/main/hal/u8g2_esp32_hal.h
Normal file
85
firmware/main/hal/u8g2_esp32_hal.h
Normal 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
|
||||
74
firmware/main/i2c_checker.c
Normal file
74
firmware/main/i2c_checker.c
Normal 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;
|
||||
}
|
||||
13
firmware/main/i2c_checker.h
Normal file
13
firmware/main/i2c_checker.h
Normal 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
|
||||
8
firmware/main/idf_component.yml
Executable file
8
firmware/main/idf_component.yml
Executable 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
39
firmware/main/main.cpp
Normal 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
9
firmware/partitions.csv
Normal 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 ,
|
||||
|
44
firmware/sdkconfig.defaults
Executable file
44
firmware/sdkconfig.defaults
Executable 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
|
||||
2
firmware/sdkconfig.defaults.esp32c6
Normal file
2
firmware/sdkconfig.defaults.esp32c6
Normal file
@@ -0,0 +1,2 @@
|
||||
# default ESP target
|
||||
CONFIG_IDF_TARGET="esp32c6"
|
||||
2
firmware/sdkconfig.defaults.esp32s3
Normal file
2
firmware/sdkconfig.defaults.esp32s3
Normal file
@@ -0,0 +1,2 @@
|
||||
# default ESP target
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user