Compare commits
115 Commits
jlcpcb_202
...
dfad7cfb76
| Author | SHA1 | Date | |
|---|---|---|---|
|
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
|
|||
|
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/
|
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
|
||||||
*_old
|
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
|
||||||
jlcpcb/
|
|
||||||
.DS_Store
|
### VisualStudioCode ###
|
||||||
**/*.bak
|
.vscode/*
|
||||||
fp-info-cache*
|
!.vscode/tasks.json
|
||||||
*.lck
|
!.vscode/launch.json
|
||||||
datasheets/
|
!.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"]
|
[submodule "library"]
|
||||||
path = library
|
path = hardware/library
|
||||||
url = git@github.com:mars3142/kicad_library.git
|
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
|
||||||
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"
|
||||||
6
firmware/sdkconfig.release
Normal file
6
firmware/sdkconfig.release
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Build type
|
||||||
|
CONFIG_APP_REPRODUCIBLE_BUILD=y
|
||||||
|
|
||||||
|
# Compiler options
|
||||||
|
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||||
|
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y
|
||||||
48
firmware/storage/schema_01.csv
Normal file
48
firmware/storage/schema_01.csv
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
140,25,112,0,100,250
|
||||||
|
255,130,112,0,130,250
|
||||||
|
255,155,112,0,150,250
|
||||||
|
255,177,115,0,170,250
|
||||||
|
255,200,135,0,190,250
|
||||||
|
255,219,170,0,210,250
|
||||||
|
255,234,205,0,217,250
|
||||||
|
255,249,240,0,223,250
|
||||||
|
255,249,250,0,227,250
|
||||||
|
239,245,255,0,234,250
|
||||||
|
224,240,255,0,239,250
|
||||||
|
215,235,255,0,244,250
|
||||||
|
212,234,255,0,248,250
|
||||||
|
210,233,255,0,250,250
|
||||||
|
208,232,255,0,252,250
|
||||||
|
207,231,255,0,255,250
|
||||||
|
205,230,255,0,252,250
|
||||||
|
204,229,255,0,250,250
|
||||||
|
204,229,255,0,247,250
|
||||||
|
206,230,255,0,245,250
|
||||||
|
208,231,255,0,243,250
|
||||||
|
213,232,255,0,240,250
|
||||||
|
219,234,255,0,237,250
|
||||||
|
229,239,255,0,235,250
|
||||||
|
236,246,255,0,233,250
|
||||||
|
255,252,251,0,230,250
|
||||||
|
255,243,236,0,225,250
|
||||||
|
255,225,202,0,220,250
|
||||||
|
255,203,174,0,205,250
|
||||||
|
255,178,129,0,190,250
|
||||||
|
255,146,85,0,165,250
|
||||||
|
255,93,38,0,140,250
|
||||||
|
140,55,70,0,120,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
25,25,112,0,100,250
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user