278 Commits

Author SHA1 Message Date
Mimoja
aff20e272c Add updateRemote.sh script to update the contentFs on multiple APs at once
As flashing a new LittleFS in case of e.g. a changed main.js is taking time and
the provided web interface is not the fastest to navigate we are introducing a
small script to upload the whole data folder to the esp.
It can also be sourced into a shell allowing for manual file uploads to the esp.

In addition we are also adding a "push_ota.sh" script designed to show its use as
well as uploading the recenty build firmware to the esp where future commits will
allow of SDCard based updates.

Signed-off-by: Mimoja <git@mimoja.de>
2023-07-08 01:21:29 +02:00
Mimoja
8078b4aeed Refactor build flags
So far the platform-io build-flags config option was a copy&paste
situation from one board to the next.
With the recently introduced new supported board these build-flags
are redundant and difficult to oversee.
Longterm they should ideally be partially generated to ensure a good
customizability but for now we are moving the common ones into the
base env where they can be imported by all the boards.

Signed-off-by: Mimoja <git@mimoja.de>
2023-07-08 01:21:29 +02:00
Mimoja
fd22bc0378 Fix: Properly calculate db size for SystemInfo
So far the calculated size for the tagDB in the frontend was
purely based on the size of each of its static entries.
However the data pointer as well as the json based config string
can occupy additional memory which is not accounted for.

We are therefore introducing a helper function to properly
calculate the DBs size.

Signed-off-by: Mimoja <git@mimoja.de>
2023-07-08 01:21:29 +02:00
atc1441
63463435cd Updated NanoAp 3D Printed case to a new version 2023-07-05 22:56:58 +02:00
Jelmer
3dd4f23880 Update README.md
added license
2023-07-04 22:22:18 +02:00
Jelmer
9891d23db1 Create LICENSE
Added CC-BY-NC-SA
2023-07-04 22:04:03 +02:00
Jelmer
4ed9d2e2e9 Merge pull request #77 from Mimoja/powerPins
ESP: Fix Powermanagement for non-configured POWER

Sensible changes, thank you!
2023-07-04 20:42:33 +02:00
Mimoja
a60bc91205 ESP: Fix Powermanagement for non-configured POWER
We currently have two ways of undefining the PowerPin in software for the ESP32
For once we can set POWER_NO_SOFT_POWER which will lead to some helpful screen
messages _but_ will still require the definition of the FLASHER_AP_POWER pin.

The second way (which was replaced with POWER_NO_SOFT_POWER) allowed to set the
powerPin array to {-1} to indicate that the pin is undefined. This would then be
handled in pretty far down the call stack right before toggeling the pin in powerControl:

```
 void powerControl(bool powerState, uint8_t* pin, uint8_t pincount) {
    if (pin[0] == -1) return;
```

This however is leading to an error: pin is n pointer (here an array with additional length)
to an uint8_t which can never be -1. Therefore the check wont ever be reached and an invalid
gpio number is passed to the gpio hal.
This caused the error:
```
  E (XXXX) gpio: gpio_set_level(226): GPIO output gpio_num error
```
to show up in the logs.
We are now proposing three seperate changes to mitigate this behaviour:
1) We are addng the POWER_NO_SOFT_POWER to the Wemos and the M5Stack boards default configs
for new users to find.
2) We are replacing the pin[0] check in powerControl with a check on the pincount and a
nullptr check as the zbs_interface->begin() allows for no powerPin to be passed in which
case we are dereferencing a nullptr.
3) ZBS_interface->begin() is losing its powerPin related default configurations and
sanity checks are put in place before every calling.

We have opted to do 3) in this way and not adding the checked into the ZBS_interface->begin()
function which is also passed an uint8_t pointer to keep the API of the class stable
for reuse in other projects. Changing the interface however would be ideal here

Signed-off-by: Mimoja <git@mimoja.de>
2023-07-03 21:39:41 +02:00
Jonas Niesner
9f86c9ef2c Update main.js
fix
2023-07-02 08:40:37 +02:00
Jonas Niesner
f7078363a4 Update main.js
added ap temp
2023-07-02 08:39:22 +02:00
Jonas Niesner
e0fd1e0f86 Update index.html
added ap temp
2023-07-02 08:38:07 +02:00
Jonas Niesner
057cf14298 Added additional system info to websocket 2023-07-01 11:51:20 +02:00
Nic Limper
51da22b0d4 small fix 2023-06-28 13:52:30 +02:00
Nic Limper
42a3651075 channel id, tag fw version and tag commands integrated in webinterface 2023-06-28 13:11:33 +02:00
Nic Limper
56dfd0c2f0 fix Weemos typo in automatic builds 2023-06-28 11:34:09 +02:00
Nic Limper
716d9823db various small fixes
- cleanup font in flash. Don't forget to update content_template.json in the file system after updating fw.
- fix layout issue in update window
- better error handling + auto retry during fw update
- fixed spelling 'weemos' -> 'wemos_d1_mini32' in build name
2023-06-28 11:01:08 +02:00
Jelmer
da6d783e3b added new larger availdatareq struct for esp32 2023-06-27 23:17:01 +02:00
jjwbruijn
306e5aaf00 bigger availdatareq struct with version 2023-06-27 23:00:11 +02:00
Nic Limper
5376f2540e layout bugfix
fixes issue #74
2023-06-27 14:14:52 +02:00
Jonas Niesner
a251ed10e7 Cleaned up release.yml and added 2 new platforms 2023-06-26 09:51:21 +02:00
Jonas Niesner
65410e3e8d Improve push/pr test pipeline 2023-06-26 09:46:57 +02:00
Jonas Niesner
b2174c2980 cleaned up platformio.ini 2023-06-26 09:44:30 +02:00
Jelmer
7db482a6ba Delete AP_force_flash.bin 2023-06-26 09:02:53 +02:00
Jelmer
bac7cf7ab5 Merge pull request #71 from Mimoja/misc
Misc changes to for low-effort setups
2023-06-25 15:17:52 +02:00
Jelmer
a3e739b8da Merge pull request #72 from slimline33/master
Thank you for your contribution!
2023-06-25 15:16:47 +02:00
Mimoja
cc1dcd9d29 ESP: split esp2buffer into two calls
Sprite rendering is the most heap hungry operation in during
content generation. This can lead to ESP panics as the exception
for the failing "new" is not handled.
To further half the required the memory we are doing it in two passes
for black and red independant. While this add a few ms to the rendering
the main time of the rendering is writing to the FS anyways so the overhead
neglectable after all.

Signed-off-by: Mimoja <git@mimoja.de>
2023-06-25 13:12:56 +02:00
Mimoja
10468313e1 Print free disk space in human readable numbers
Signed-off-by: Mimoja <git@mimoja.de>
2023-06-25 13:12:56 +02:00
Mimoja
2e55c49a92 main: Print free heap in parallel to web interface
Signed-off-by: Mimoja <git@mimoja.de>
2023-06-25 13:12:56 +02:00
Mimoja
e45845e00e Add SPIFFSEditor header to match imported source file
The SPIFFSEditor.h header is being imported from the .platformio folder
while the corresponding .cpp is actually stored with the project.
To ensure we can not accidentally edit the wrong file in the future import
the header as well

Signed-off-by: Mimoja <git@mimoja.de>
2023-06-25 13:12:56 +02:00
Mimoja
940795d05d Dont attack the ledc to the LED pin if no LED is available
Signed-off-by: Mimoja <git@mimoja.de>
2023-06-25 13:12:56 +02:00
Mimoja
a3fc1da6d2 Add Flash Timeout parameter
Signed-off-by: Mimoja <git@mimoja.de>
2023-06-25 13:12:56 +02:00
Bimmi
d778aab553 Add favicon.ico
upload favicon.ico
2023-06-25 08:45:00 +02:00
Bimmi
f460046c51 Update index.html
added code for favicon.ico
2023-06-25 08:44:07 +02:00
Jelmer
e5ddba02ff Merge pull request #70 from Mimoja/SDCard
Add Support for an SDCard and SoftSPI
2023-06-24 23:29:24 +02:00
Jonas Niesner
7ba80f57c9 Merge branch 'master' into SDCard 2023-06-21 21:41:08 +02:00
Jonas Niesner
d6feb5a8ca Update platformio.ini
Added M5 core build config
2023-06-20 21:24:46 +02:00
Jonas Niesner
1e034c83fa Merge pull request #68 from Mimoja/weemos
Add Weemos D1 mini 32 as target
2023-06-20 21:20:43 +02:00
Mimoja
1e93a24b53 Allow the usage of SoftSPI for AP flashing
The AP-Tags are not very fast at flashing. SoftSPI is fast enough for the
rare flashing.
This gives us the ability to move the AP connection to arbitrary pins.

Signed-off-by: Mimoja <git@mimoja.de>
2023-06-20 17:55:41 +02:00
Mimoja
dbf76e2176 Expose flasher speed to config via define
Signed-off-by: Mimoja <git@mimoja.de>
2023-06-20 17:51:10 +02:00
Mimoja
93639ff8ac Disconnect SDCard if it shares the pins with the flasher
Signed-off-by: Mimoja <git@mimoja.de>
2023-06-20 17:51:10 +02:00
Mimoja
48b2925b9f Reset flasher if it shared the pins with the SDCard
If the flasher and the SDCard share pins the ESP will need to be reset
to bring VSPI and HSPI back into sane states. Trying to reinit them
without a reset will lead to heap corruptions.
Better safe than sorry.

Signed-off-by: Mimoja <git@mimoja.de>
2023-06-20 17:51:10 +02:00
Mimoja
9c83d8b5a5 Use SDCard usage where available
When an SD Card is available it is the more interesing
metric in terms of space usage. We are therefore using
it here.

Signed-off-by: Mimoja <git@mimoja.de>
2023-06-20 17:51:10 +02:00
Mimoja
f487092f7f Add SDCard support
To be able to store more images and never worry about running
out of storage again we are adding support of SDCards.

We are faced with two options:
1) Remove the LittleFS code
2) Allow for both FSs but increase the app partition as we run
out of space.

We have opted for 2 for two reasons:
1) Unformated SDCards can be initialized from the littleFS
2) If the SDCard and the flasher share the same SPI pins
the SDCard needs to be completely disconnected so there needs
to be a FS in that rare case.

Performance wise the SDCard is not slowing the system down.
Writing the AP db with 70 APs takes 2.5 seconds, on the SD
only 800ms. This however is SD and setup depending.

By default the SDCard is expected on pins 18,19,23 however this
is configurable via preprocessor defines.

Globally we replace LittleFS with a new pointer to the currently
used FS allowing for hotswapping where the FS pointer is not saved.
One case which is therefore not working is the SPIFSEditor which
copies the FS interally.

Signed-off-by: Mimoja <git@mimoja.de>
2023-06-20 17:51:10 +02:00
Mimoja
eae7c3a159 ESP: Delete files after transfer where requested
Beyond around 20 APs the littleFs will no longer provide enough
space to add .pending files. The original .raw files are premarily
held to be able to send them to the web interface.
In those rare cases where this is not strictly needed we can
allow specific setups to auto-delete them.

Signed-off-by: Mimoja <git@mimoja.de>
2023-06-20 17:51:10 +02:00
Mimoja
4d78df09cc Add M5stack (original core) config
The M5stack board offers three interesting features for use with
OpenEPaperLink:
- LCD
- SDCard slot
- Modules including a batters

Which means it can easily be used to walk around with an AP for testing
purposes.
The LCD is currely not used as the M5Stack library is so old that it collides
with newer versions of eTFT which we are importing already.

Signed-off-by: Mimoja <git@mimoja.de>
2023-06-20 17:51:09 +02:00
Mimoja
e8740b2022 Add Weemos D1 mini 32 as target
The original weemos D1 mini was one of the most popular
ESP8266 boards. To this day many designs are designed around its
formfactor.
The successor is the here added D1 mini 32. It brings the same
"core" header with 8 GPIO but also additional io.

We are adding a default config which is workable with the old
pinconfig as those are usually the only ones exposed on the
breakout boards.
2023-06-20 17:24:53 +02:00
atc1441
6c4e254db0 Update NanoAP_Case.jpg 2023-06-19 22:43:48 +02:00
atc1441
5ccbcb4719 Nano AP Infos and Files added 2023-06-19 22:42:24 +02:00
atc1441
617b49eb86 Yep, Pinout of NanoAP fixed... 2023-06-19 11:47:46 +02:00
Jelmer
5868abf189 Merge pull request #64 from jjwbruijn/customLUTTest
Tag-side config options with binaries
2023-06-15 14:35:14 +02:00
Jelmer
94be1ea3aa Added possibility to xfer tag-side config options 2023-06-15 13:18:12 +02:00
jjwbruijn
5190327d5a optimized i2c, disabled for 4.2 2023-06-15 11:42:15 +02:00
jjwbruijn
6ecf6410ee moved some defaults, fixed typo 2023-06-14 22:11:30 +02:00
jjwbruijn
e72e89d85e Added settings/config for tags 2023-06-14 21:45:36 +02:00
Jonas Niesner
054146677f Fixed release description overwrite 2023-06-13 18:22:46 +02:00
Jonas Niesner
58d9fe2217 Split json file list 2023-06-13 18:21:33 +02:00
Nic Limper
205dfa0ce2 logging reboots 2023-06-11 13:29:49 +02:00
Jelmer
5c7b53b740 update in power management (multiple pins) 2023-06-10 14:24:50 +02:00
jjwbruijn
5196c1b212 76 bytes custom OTA Lut and changes location of the init 2023-06-09 14:59:00 +02:00
Nic Limper
e7fbaffbab added advanced tag options
- added advanced tag options: image rotation / always use default lut / force refresh / cancel pending
- fixed rx/tx swap in documentation
2023-06-09 12:56:58 +02:00
Nic Limper
c68b582be7 overhaul of contentmanager, 4.2" layouts, bugfixes
- added Swedish å Å to Bahnschrift20 and 30
- option to turn dither on/off in 'static image'
- major overhaul of contentmanager.cpp, content is now generated from json template.
- fallback to 1bpp for 4.2" when no psram
- added 4.2" content layouts
- fixed small apconfig bug
- pause content generation for 60 seconds after crash, to prevent uncontrollable boot loop
2023-06-08 15:41:15 +02:00
Nic Limper
9d07df27f0 fix font characters <32 2023-06-07 12:32:11 +02:00
atc1441
064bd4258c Nano AP fully power Control support
The idea to switch to an array based power Control was discarded for now
2023-06-07 01:00:40 +02:00
Jonas Niesner
8e31a1b8eb Update README.md
Added new badges
2023-06-06 20:24:12 +02:00
Jonas Niesner
9598bea315 Merge pull request #57 from seeers/master
Update links to wiki pages README.md
2023-06-06 20:13:06 +02:00
seeers
6d2ed6915b Update links to wiki pages README.md 2023-06-06 20:10:06 +02:00
Jonas Niesner
6ffe967577 Update release.yml
Add builds for Nano_AP
2023-06-06 20:03:47 +02:00
Nic Limper
e169dbab6e added äöüßÄÖÜ to fonts + small updates
- fonts re-rendered: added äöüßÄÖÜ.
- shuffled a bit with fonts to optimize disk usage
- ability to delete files during OTA
- prettify tagDB.json
- error handling in get_DB
- small cosmetic changes

* Due to new ability to delete files: After firmware update via OTA, please perform an OTA update of the filesystem again to perform the deletion of unneeded fonts.
* If updating via platform.io: don't forget to update the filesystem image.
2023-06-06 16:04:18 +02:00
atc1441
b9d3289852 Added NanoAP 2023-06-03 17:21:35 +02:00
atc1441
b0971bd111 Pogo pin size typo fix in ATC1441 Jig 2023-06-03 08:39:12 +02:00
Jelmer
11193a1fbf low power during ap/associate, for S2 boards 2023-06-02 13:24:03 +02:00
Nic Limper
db88de1f75 minor bug fixes
-
2023-06-01 21:42:09 +02:00
Jelmer
e9a554a2bf improved timings for reset/bringing AP online 2023-05-31 20:34:36 +02:00
Nic Limper
dfde0c0efe ota bugfix 2023-05-30 18:25:03 +02:00
Nic Limper
2065072747 Merge pull request #52 from nlimper/ota
OTA updates + buienradar
2023-05-30 00:29:03 +02:00
Nic Limper
a2abe466f5 ota ready 2023-05-30 00:25:09 +02:00
Nic Limper
396536522a small ota bugfix 2023-05-29 23:52:30 +02:00
Nic Limper
c228104c29 update ota update 2023-05-29 23:39:07 +02:00
Nic Limper
8b0c516598 update ota update 2023-05-29 20:42:20 +02:00
Nic Limper
d02f51882b ota improvements 2023-05-29 16:39:16 +02:00
Jelmer
1414814b4d These pages were moved to the Wiki 2023-05-28 21:09:15 +02:00
Nic Limper
aaf4f94df7 buienradar (dutch rain predictions) 2023-05-28 01:37:28 +02:00
jjwbruijn
c6012a3cb5 Added jigs for two uncommon tags 2023-05-28 01:34:37 +02:00
Nic Limper
da095a2382 Merge branch 'master' into ota 2023-05-27 23:03:23 +02:00
Jonas Niesner
726a8055b3 Merge pull request #51 from jonasniesner/master
Fixed release scripts
2023-05-27 22:55:02 +02:00
Jonas Niesner
b010787d7d fixed typo 2023-05-27 22:46:35 +02:00
Jonas Niesner
e91891d283 Fixed path generation for non numeric release tags 2023-05-27 22:46:01 +02:00
Nic Limper
3e4175a790 Demo image generator for python
Generates crisp images
2023-05-27 21:46:51 +02:00
jjwbruijn
6380cd15cb added some more dumps for reference 2023-05-27 21:03:55 +02:00
Nic Limper
20d46df302 bugfix 2023-05-27 20:39:04 +02:00
Nic Limper
3788608e63 first version of OTA firmware updates 2023-05-27 19:25:28 +02:00
Nic Limper
cd288e79f4 fixes crash on apconfig 2023-05-26 01:58:43 +02:00
Jonas Niesner
2bdb82d146 Merge pull request #50 from jonasniesner/master
Update release pipeline
2023-05-25 22:31:21 +02:00
Jonas Niesner
26731ffe7b Create genfilelist.py 2023-05-25 22:21:40 +02:00
Jonas Niesner
b0a0e4b85b Update release.yml
Added files.json
2023-05-25 22:21:07 +02:00
Nic Limper
49d86620bd preparations for custom LUT 2023-05-25 14:28:54 +02:00
Nic Limper
f73ce60468 apconfig improvements 2023-05-25 09:22:48 +02:00
Nic Limper
92c8d6178d some smaller cosmetic changes 2023-05-24 20:46:13 +02:00
Nic Limper
5f3918e882 multi-AP sync improvements
- add: syncing data segment of non-image datatypes between AP's
- add: syncing metadata and deletions between AP's
- sorted tags on the webpage by alias
2023-05-24 17:00:41 +02:00
Jonas Niesner
cab72aa29e Merge pull request #49 from jonasniesner/master
Update release.yml
2023-05-24 08:44:06 +02:00
Jonas Niesner
8b024bb347 Update release.yml
Add full system img and release version to binaries
2023-05-24 08:41:48 +02:00
Jelmer
b0eb1a3997 Update README.md 2023-05-24 01:51:52 +02:00
jjwbruijn
7b081431de fixed block transfer for OTA LUT and NFC 2023-05-24 00:08:50 +02:00
jjwbruijn
a888991ecc Tag: Added OTA LUT capability 2023-05-23 21:12:02 +02:00
Nic Limper
0641547d73 8 byte mac addr instead of 6 byte 2023-05-23 21:06:50 +02:00
Nic Limper
f1df44fbb1 Merge branch 'master' of https://github.com/jjwbruijn/OpenEPaperLink 2023-05-23 11:20:28 +02:00
Nic Limper
6afc2a020e improvements for non-rgb leds 2023-05-23 11:20:03 +02:00
Jelmer
4545a7a504 fixed no-display AP flashing 2023-05-23 11:14:48 +02:00
Jelmer
ada68b1a33 auto tag flashing - fixed nodisplay update 2023-05-23 11:13:29 +02:00
Nic Limper
533f0cec78 set NFC url / new tagtypes / minor fixes 2023-05-23 00:46:32 +02:00
jjwbruijn
92368f4499 Border control waveforms for clearer borders 2023-05-23 00:32:22 +02:00
jjwbruijn
691febcf6a Fixed NFC payload offset 2023-05-22 23:52:41 +02:00
jjwbruijn
d7d7261167 Type 0x11 for UC8151, silent timeout for no_update messages 2023-05-22 22:57:11 +02:00
Nic Limper
feb8e7e69c segmented tags content 2023-05-22 13:00:39 +02:00
Nic Limper
0cc7ba4fc6 fixed wind icons even more 2023-05-21 21:20:51 +02:00
Nic Limper
0380ea2cb0 improved content config
don't forget to update the files in /www
2023-05-21 20:59:58 +02:00
Jonas Niesner
962e4995e2 Merge pull request #37 from jonasniesner/master
Add issue templates
2023-05-21 19:20:56 +02:00
Jonas Niesner
7dc14c9229 Update bug_report.md 2023-05-21 19:19:50 +02:00
Jonas Niesner
03968dda72 Update feature_request.md 2023-05-21 19:17:24 +02:00
Jonas Niesner
c66565df71 Update bug_report.md 2023-05-21 19:11:26 +02:00
Jonas Niesner
ad91a24c98 Update issue templates 2023-05-21 19:07:05 +02:00
Jonas Niesner
0c672433cb Merge pull request #36 from jonasniesner/master
Add builder scripts
2023-05-21 14:46:53 +02:00
Jonas Niesner
f0035f5e87 Create release.yml 2023-05-21 14:29:44 +02:00
Jonas Niesner
c8458fcf51 Create build-esp32.yml 2023-05-21 14:29:08 +02:00
jjwbruijn
a2aea16de8 Removed button option in settings.h, firmware now checks for its presence 2023-05-21 01:42:27 +02:00
Jelmer
999c57deb1 Renamed setup SSID 2023-05-19 21:32:20 +02:00
Jelmer
ecbbff75f8 Merge branch 'master' of https://github.com/jjwbruijn/OpenEPaperLink 2023-05-19 00:40:42 +02:00
Jelmer
ed0189ceb1 Added flash backup for unknown tag types 2023-05-19 00:40:39 +02:00
Jelmer
1330c18b86 Merge pull request #35 from jonasniesner/master
Updated platformio.ini to support OTA for the ESP32
2023-05-18 19:04:36 +02:00
Jonas Niesner
321d0111b5 Update main.cpp 2023-05-18 12:38:46 +02:00
Jonas Niesner
c52cb89be4 Update README.md 2023-05-18 09:55:00 +02:00
Jonas Niesner
5aa7174600 Update README.md 2023-05-18 09:28:46 +02:00
Jonas Niesner
755d676f07 Update README.md 2023-05-18 09:01:12 +02:00
Jonas Niesner
484bb00341 Update platformio.ini 2023-05-17 23:12:32 +02:00
Jonas Niesner
1a9bf422ea Update README.md 2023-05-17 22:01:43 +02:00
Jonas Niesner
0646e85a70 Update main.cpp 2023-05-17 21:36:27 +02:00
Jonas Niesner
59f44e4a82 Update main.cpp 2023-05-17 21:22:03 +02:00
Jonas Niesner
bf6021d6cd Update README.md 2023-05-17 21:04:04 +02:00
Jonas Niesner
03647ada44 Update README.md 2023-05-17 20:42:21 +02:00
Jonas Niesner
c7e7b49d49 Update README.md 2023-05-17 20:31:04 +02:00
atc1441
f1ed22d131 Undo port settings 2023-05-17 10:29:33 +02:00
atc1441
437c4fdcc0 Date info added on Google Calendar content 2023-05-17 10:25:51 +02:00
Jelmer
15bcba19f6 Create README.md 2023-05-17 01:59:27 +02:00
Jelmer
2b3f066568 Rename README.md to Building.md 2023-05-17 01:45:22 +02:00
Jelmer
61ee713539 Create Getting Started.md 2023-05-17 01:44:37 +02:00
Nic Limper
ae4311e346 small typos 2023-05-16 22:53:19 +02:00
Nic Limper
a5165eb946 delete temporary files 2023-05-16 22:16:47 +02:00
Jelmer
0ec2713305 Merge pull request #34 from jonasniesner/master
Update readme and add build Information
2023-05-16 21:41:39 +02:00
Jonas Niesner
2746e9d114 Update readme and added build Information 2023-05-16 19:41:24 +02:00
Nic Limper
19a08de207 re-rendered fonts for uppercase characters in dates 2023-05-16 16:51:23 +02:00
Nic Limper
8f2ab2823a Merge pull request #33 from nlimper/main
Painter
2023-05-16 15:39:51 +02:00
Nic Limper
28f8667baa Painter
- new paint option for freehand drawing and quick texts
- added gamma correction on rendering
- option to turn dither on/off on jpg upload
- python version for packagebinaries (same output as the php version)
2023-05-16 15:34:51 +02:00
atc1441
1123e5cb9e Fixed default LED brightness typo 2023-05-15 23:42:51 +02:00
atc1441
0bae760eaa Added content language option, Now supported: English, Dutch and German
New languages can be added by adding it into the language.h file everything else should adapt to it
2023-05-15 23:30:17 +02:00
Jelmer
4b6efb7626 fixed builds for non-rgb-led-equipped boards 2023-05-15 21:07:43 +02:00
Jelmer
355f1bc108 Merge pull request #30 from foorschtbar/master
MAC in http Request and RGB LED brightness
2023-05-15 20:49:03 +02:00
Jelmer
480b4624d3 Merge branch 'master' into master 2023-05-15 20:42:40 +02:00
Jelmer
be9dba5a30 improved PSRAM support for MiniAP 2023-05-15 20:14:48 +02:00
Nic Limper
beb0e639e2 small fixes
- disable useless compiler warnings in fastled and tft_espi
- web preview thumbnail fix
- faster tag updates while webpage is displayed
- fixed tag fw ota update
2023-05-15 19:46:12 +02:00
Nic Limper
16ebd9194e small fixes
- disable useless compiler warnings in fastled and tft_espi
- web preview thumbnail fix
- faster tag updates while webpage is displayed
- fixed tag fw ota update
2023-05-15 19:35:15 +02:00
Nic Limper
23bddd296f Merge pull request #31 from jonasniesner/master
Fix for building on linux
2023-05-15 18:27:42 +02:00
Jonas Niesner
6b9f790c63 Fix for building on linux 2023-05-15 18:24:58 +02:00
Fab!an
b2bd6a7fe4 Merge branch 'jjwbruijn:master' into master 2023-05-15 17:08:16 +02:00
foorschtbar
7b662dd65e Added option to control RGB LED brightness 2023-05-15 17:03:13 +02:00
foorschtbar
504d150e7b Added ESL MAC in http image request 2023-05-15 11:33:39 +02:00
jjwbruijn
4080273df1 AP 0015 - Filtering on packet length 2023-05-13 21:41:53 +02:00
Jelmer
f7de700634 Added Mini AP guide 2023-05-09 22:09:49 +02:00
Jelmer
40074c6bf9 prevent Mini AP from booting before its time 2023-05-09 16:57:57 +02:00
Jelmer
fd77e0a77c Improved state handling, error messages&led status 2023-05-09 16:34:33 +02:00
atc1441
1c0a45fcd7 No space in name is allowed 2023-05-09 16:13:04 +02:00
jjwbruijn
1b363860d1 AP 0014 - Support for messages on segmented AP 2023-05-09 11:27:13 +02:00
Jelmer
e3bfaaa62f fixes compiler errors for other boards 2023-05-08 01:16:41 +02:00
Jelmer
cfee590f01 Added the option to run an AP from another port 2023-05-07 15:42:49 +02:00
Jelmer
9ccf3ab029 Merge branch 'master' of https://github.com/jjwbruijn/OpenEPaperLink 2023-05-07 13:22:45 +02:00
Jelmer
22e9c23924 Fixes #29 - Wind direction icon is 180 degrees off 2023-05-07 13:21:58 +02:00
jjwbruijn
2060edb54c Added link to AP FW Pack 2023-05-07 00:38:51 +02:00
jjwbruijn
bccd617881 Added 2.9" version AP 2023-05-07 00:24:06 +02:00
Jelmer
117120c0df Better auto-flashing, support for firmware-packs 2023-05-06 18:41:22 +02:00
jjwbruijn
25cfe44784 Added firmware packs 2023-05-06 18:33:40 +02:00
Jelmer
5a1488c986 Changes in AP-Serial, hopefully more readable 2023-05-03 13:49:06 +02:00
jjwbruijn
3b55fb01a5 some mods in AP-ESP32 protocol 2023-05-03 13:42:36 +02:00
jjwbruijn
1cd7b67d95 Fixed 'ghosted' image issue, higher channels now supported 2023-05-01 18:20:40 +02:00
Jelmer
2ab6a7a798 Multi-AP support!
Multi-AP support!
2023-05-01 11:36:12 +02:00
Nic Limper
d9eb97af26 better use of prepareIdleReq 2023-04-28 22:11:10 +02:00
Nic Limper
36d30ee0cb auto channel select, ap config screen 2023-04-28 14:51:51 +02:00
Nic Limper
9158549cb3 cleanup pendingdata vector 2023-04-27 22:37:33 +02:00
Nic Limper
a46678386e Merge remote-tracking branch 'upstream/master' 2023-04-27 10:11:10 +02:00
Jelmer
37a189c998 debug version pushed, fixed 2023-04-27 02:09:55 +02:00
Jelmer
e3ea9121b4 added support for changing channels 2023-04-27 02:04:18 +02:00
jjwbruijn
aec5cc07e7 Better AP detection - AP Channel set - Updated virtual tag mac - mac used for cancelled request 2023-04-27 02:00:12 +02:00
Nic Limper
b7a339c034 caching lat/lon in weather 2023-04-26 19:32:08 +02:00
Nic Limper
0edb93be1a zeroconfig multiAP over UDP 2023-04-26 17:17:02 +02:00
Nic Limper
cb9b9f164a cleaning up 2023-04-26 09:03:33 +02:00
Nic Limper
981fe9f312 Merge remote-tracking branch 'upstream/master' 2023-04-24 18:54:49 +02:00
jjwbruijn
013e6263a9 Merge branch 'master' of github.com:jjwbruijn/OpenEPaperLink 2023-04-23 22:24:35 +02:00
Jelmer
b7583e0028 Merge branch 'master' of https://github.com/jjwbruijn/OpenEPaperLink 2023-04-23 22:18:35 +02:00
Jelmer
ad7b75a582 ESP: Added Block-request source 2023-04-23 22:18:32 +02:00
jjwbruijn
9d29ffff68 AP: Added Block-request source in esp message struct 2023-04-23 22:17:17 +02:00
jjwbruijn
9a824b87ef Moved stuff, added Mini AP designs 2023-04-23 18:59:44 +02:00
Jelmer
6629b77d3d Added support for the Mini-AP (ESP32-S2) 2023-04-23 16:44:29 +02:00
Jelmer
45e3d7427e Update troubleshooting.md 2023-04-17 22:47:49 +02:00
Jelmer
db1fc60b07 Update troubleshooting.md 2023-04-17 22:43:59 +02:00
Jelmer
20364563be Create troubleshooting.md 2023-04-17 22:29:27 +02:00
jjwbruijn
df2f35db8e Battery-checker FW for 1.54", for checking the 2540 coin cells 2023-04-15 20:58:02 +02:00
Nic Limper
7d15f41b56 Merge remote-tracking branch 'upstream/master' 2023-04-15 14:02:06 +02:00
Jelmer
562ee3e095 Merge branch 'master' of https://github.com/jjwbruijn/OpenEPaperLink 2023-04-14 14:33:18 +02:00
Jelmer
f2e7b5b332 changed (hopefully improved) power ramping on AP 2023-04-14 14:33:16 +02:00
jjwbruijn
0f166b3a4f Simplified (lower power) channel scanning, only 11/15/20/25/26/27 2023-04-09 22:37:27 +02:00
jjwbruijn
b5117fdcb0 Added 1.54" Tag-AP support as virtual tag 2023-04-09 13:32:03 +02:00
Jelmer
eed25ab60b added ebay sources for components 2023-04-06 00:28:39 +02:00
Jelmer
00ebbda1c7 added info about the (very optional) heatsink 2023-04-06 00:19:59 +02:00
Jelmer
ffe90a5b9b added segmented case 2023-04-04 20:52:07 +02:00
jjwbruijn
c202a1ecec fixed display glitch 2023-04-04 20:45:45 +02:00
jjwbruijn
0f97c290a1 added segmented case files 2023-04-04 20:45:45 +02:00
Jelmer
26446ca087 Merge pull request #27 from jonasniesner/master
Add alternative 2.9" flasher PCB
2023-04-04 20:42:36 +02:00
Jonas Niesner
c474c364e6 Update settings.h 2023-04-04 19:40:23 +02:00
Jonas Niesner
09d6dd2fbb Update platformio.ini 2023-04-04 19:34:19 +02:00
Jonas Niesner
e080499e6a Update README.md 2023-04-04 19:25:08 +02:00
Jonas Niesner
6f509ebf0f Update README.md 2023-04-04 19:24:22 +02:00
Jonas Niesner
75bb534de8 Update README.md 2023-04-04 19:23:30 +02:00
Jonas Niesner
70fb112961 Create README.md 2023-04-04 19:22:56 +02:00
Jonas Niesner
27ff235593 Add PCB files for 2.9 2023-04-04 19:14:17 +02:00
Jelmer
316e32cd9b Update README.md 2023-04-04 13:38:50 +02:00
Jelmer
8335bf4270 no extra bytes, added integrity on data-available 2023-04-03 22:57:06 +02:00
jjwbruijn
372b96ef6b improved AP state timeout / Display on segmented 2023-04-03 22:36:03 +02:00
Jelmer
df805965c2 Merge pull request #26 from jjwbruijn/alt-serial2
Some changes to the AP, lower baudrate and (hopefully) better connections
2023-04-03 12:27:50 +02:00
Nic Limper
3ff40e526d wip dither 2023-04-02 23:23:41 +02:00
jjwbruijn
431fdc1abe added binaries 2023-04-02 21:32:55 +02:00
Jelmer
a78002543b esp32-serial changes 0xAA 2023-04-01 18:19:49 +02:00
jjwbruijn
55b254fdec basic improvements, XOR'D 0x55 before xmit 2023-04-01 17:06:26 +02:00
jjwbruijn
984f46c4f1 reverted back to old AP strategy 2023-03-28 22:15:38 +02:00
Nic Limper
bf66a49038 small fixes 2023-03-28 16:39:28 +02:00
Nic Limper
faf2d4cc19 udp wip 2023-03-28 11:26:36 +02:00
jjwbruijn
7bd0c8c815 even moar symlinks 2023-03-28 00:09:01 +02:00
jjwbruijn
92870a8886 moar symlinks 2023-03-27 23:55:11 +02:00
jjwbruijn
21bb64d20d symlink attempt 24352 2023-03-27 23:02:45 +02:00
Jelmer
8c7cacddfc added return validation 2023-03-27 22:56:00 +02:00
Nic Limper
1a6d3d6408 content for 4.2" tags 2023-03-27 20:29:30 +02:00
Jelmer
13451e3fde updated AP binaries 2023-03-27 17:26:45 +02:00
Jelmer
2cdb2b8a6f added dma-ish handling 2023-03-27 17:24:48 +02:00
Jelmer
d92681ef02 fixed flasher, DMA handling 2023-03-27 17:13:12 +02:00
Jelmer
5057bb7627 moved files to symlinked library folders 2023-03-27 15:32:37 +02:00
Jelmer
ea36618295 fixed flashing the internal AP-tag 2023-03-27 14:54:31 +02:00
Jelmer
d5399ba309 Update README.md 2023-03-26 20:39:37 +02:00
Jelmer
3ad8d0e9fe Update README.md 2023-03-26 20:33:17 +02:00
Jelmer
6b33a41e85 Update README.md 2023-03-26 20:29:10 +02:00
Jelmer
38ccabe825 specs placeholder 2023-03-26 20:05:26 +02:00
Jelmer
283fba0ebe Update README.md 2023-03-26 20:00:06 +02:00
Jelmer
c8c6a8c6fb Update README.md 2023-03-25 11:37:14 +01:00
Jelmer
7ba0fd1e82 Update README.md 2023-03-25 11:05:33 +01:00
Nic Limper
f3c7a949a6 Merge branch 'master' of https://github.com/jjwbruijn/OpenEPaperLink 2023-03-23 23:40:16 +01:00
Nic Limper
e19c9dc612 added content: google calendar
via google apps script, see /data/calendar.txt
2023-03-23 23:40:06 +01:00
Jelmer
97897dd234 more cleanup 2023-03-23 16:33:48 +01:00
Jelmer
2b35c0f249 -none- 2023-03-23 16:33:05 +01:00
Jelmer
be9d5fc082 altradio-header support for zbs_flasher 2023-03-23 16:28:31 +01:00
Nic Limper
059ab15722 Merge branch 'master' of https://github.com/jjwbruijn/OpenEPaperLink 2023-03-23 13:56:04 +01:00
Nic Limper
9212aad8d8 bugfix color palette 2023-03-23 13:55:55 +01:00
Jelmer
e7e8af39e2 Update README.md 2023-03-23 13:54:41 +01:00
Jelmer
215ebf7c44 Create README.md 2023-03-23 13:11:56 +01:00
Jelmer
27ecf54fb6 fixed incompatibility, broke something else 2023-03-23 11:44:58 +01:00
Jelmer
70690da3a8 Merge remote-tracking branch 'origin/new_flasher_pcb' 2023-03-23 10:26:26 +01:00
Jelmer
e4853e105a board-based pins 2023-03-23 10:17:31 +01:00
Jelmer
2bb7a8c7b5 Renamed folders 2023-03-23 09:51:50 +01:00
Jelmer
e944607228 added jigs and case 2023-03-23 01:06:00 +01:00
Nic Limper
1b2a6c448d qr code library 2023-03-22 23:29:13 +01:00
Nic Limper
8352e5811d QR content card 2023-03-22 23:23:36 +01:00
Nic Limper
5205338635 external image upload, 1bpp buffer, cleanup
- if image only has BW, then sending red buffer is skipped
- jpg image upload from external source: see /upload-test.html
2023-03-22 19:30:25 +01:00
Jelmer
5bc988c90e added led support 2023-03-22 14:31:26 +01:00
Jelmer
add3d568a4 added zbs_flasher.py version w/ internalap support 2023-03-18 01:53:10 +01:00
Jelmer
9b4fba818f working dual port flasher, direct usb only 2023-03-18 01:50:39 +01:00
Jelmer
c8b0812759 Merge pull request #22 from nlimper/raw-buffer
Raw buffer
2023-03-17 11:39:34 +01:00
Nic Limper
ef09cf4999 dithering/raw previews/css
- fixed preview images on webpage (raw buffer is rendered client side)
- added responsive css, webpage works on mobile now
- content is now rendered on 4 bit sprite using indexed color
- added floyd steinberg dithering
2023-03-16 22:16:42 +01:00
Nic Limper
3a80c3f5e9 Merge branch 'master' into raw-buffer 2023-03-13 13:38:19 +01:00
Jelmer
29c0deb541 Merge pull request #21 from jjwbruijn/NFC-support
Nfc support
2023-03-11 11:30:28 +01:00
Jelmer
09bc3056bf added support for NFC wake 2023-03-08 00:37:03 +01:00
Jelmer
85a3e28f2e added 1.54 binary 2023-03-07 21:55:36 +01:00
Jelmer
3abf3de984 nfc support working on tag, no esp32 yet 2023-03-07 21:52:00 +01:00
Nic Limper
ea7a76be89 raw buffer
still some work to do:
- 1.54" img url breaks communication with the tag_fw... why???
- generate canvas from raw on webpage
- dithering in spr2buffer
2023-02-25 20:26:31 +01:00
480 changed files with 35895 additions and 67961 deletions

27
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,27 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
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.
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: 'Feature request: '
labels: 'enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is.
**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.

57
.github/workflows/esp32-build-test.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: ESP32 firmware builf test
on: [push,pull_request]
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
~/.cache/pip
~/.platformio/.cache
key: ${{ runner.os }}-pio
- uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install PlatformIO Core
run: pip install --upgrade platformio
- name: Build Simple_AP
run: |
cd ESP32_AP-Flasher
pio run --environment Simple_AP
pio run --target buildfs --environment Simple_AP
- name: Build OpenEPaperLink_Mini_AP
run: |
cd ESP32_AP-Flasher
pio run --environment OpenEPaperLink_Mini_AP
pio run --target buildfs --environment OpenEPaperLink_Mini_AP
- name: Build OpenEPaperLink_Nano_AP
run: |
cd ESP32_AP-Flasher
pio run --environment OpenEPaperLink_Nano_AP
pio run --target buildfs --environment OpenEPaperLink_Nano_AP
- name: Build OpenEPaperLink_AP_and_Flasher
run: |
cd ESP32_AP-Flasher
pio run --environment OpenEPaperLink_AP_and_Flasher
pio run --target buildfs --environment OpenEPaperLink_AP_and_Flasher
- name: Build Wemos_d1_mini32_AP
run: |
cd ESP32_AP-Flasher
pio run --environment Wemos_d1_mini32_AP
pio run --target buildfs --environment Wemos_d1_mini32_AP
- name: Build M5Stack_Core_ONE_AP
run: |
cd ESP32_AP-Flasher
pio run --environment M5Stack_Core_ONE_AP
pio run --target buildfs --environment M5Stack_Core_ONE_AP

196
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,196 @@
name: Release binaries
on:
push:
tags:
- '*'
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
~/.cache/pip
~/.platformio/.cache
key: ${{ runner.os }}-pio
- uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install PlatformIO Core
run: pip install --upgrade platformio
- name: Install esptool
run: pip install esptool
- name: create folders
run: |
mkdir espbinaries
- name: Build firmware for Simple_AP
run: |
cd ESP32_AP-Flasher
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
pio run --environment Simple_AP
pio run --target buildfs --environment Simple_AP
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP/boot_app0.bin
cp .pio/build/Simple_AP/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP/firmware.bin
cp .pio/build/Simple_AP/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP/bootloader.bin
cp .pio/build/Simple_AP/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP/partitions.bin
cp .pio/build/Simple_AP/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP/littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP
esptool.py --chip esp32 merge_bin -o merged-firmware.bin --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 bootloader.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 firmware.bin 0x290000 littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
cp Simple_AP/firmware.bin espbinaries/Simple_AP.bin
cp Simple_AP/merged-firmware.bin espbinaries/Simple_AP_full.bin
- name: Build firmware for Wemos_d1_mini32_AP
run: |
cd ESP32_AP-Flasher
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
pio run --environment Wemos_d1_mini32_AP
pio run --target buildfs --environment Wemos_d1_mini32_AP
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/Wemos_d1_mini32_AP
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Wemos_d1_mini32_AP/boot_app0.bin
cp .pio/build/Wemos_d1_mini32_AP/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Wemos_d1_mini32_AP/firmware.bin
cp .pio/build/Wemos_d1_mini32_AP/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Wemos_d1_mini32_AP/bootloader.bin
cp .pio/build/Wemos_d1_mini32_AP/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Wemos_d1_mini32_AP/partitions.bin
cp .pio/build/Wemos_d1_mini32_AP/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Wemos_d1_mini32_AP/littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/Wemos_d1_mini32_AP
esptool.py --chip esp32 merge_bin -o merged-firmware.bin --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 bootloader.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 firmware.bin 0x290000 littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
cp Wemos_d1_mini32_AP/firmware.bin espbinaries/Wemos_d1_mini32_AP.bin
cp Wemos_d1_mini32_AP/merged-firmware.bin espbinaries/Wemos_d1_mini32_AP_full.bin
- name: Build firmware for M5Stack_Core_ONE_AP
run: |
cd ESP32_AP-Flasher
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
pio run --environment M5Stack_Core_ONE_AP
pio run --target buildfs --environment M5Stack_Core_ONE_AP
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/M5Stack_Core_ONE_AP
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/M5Stack_Core_ONE_AP/boot_app0.bin
cp .pio/build/M5Stack_Core_ONE_AP/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/M5Stack_Core_ONE_AP/firmware.bin
cp .pio/build/M5Stack_Core_ONE_AP/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/M5Stack_Core_ONE_AP/bootloader.bin
cp .pio/build/M5Stack_Core_ONE_AP/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/M5Stack_Core_ONE_AP/partitions.bin
cp .pio/build/M5Stack_Core_ONE_AP/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/M5Stack_Core_ONE_AP/littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/M5Stack_Core_ONE_AP
esptool.py --chip esp32 merge_bin -o merged-firmware.bin --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 bootloader.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 firmware.bin 0x2B0000 littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
cp M5Stack_Core_ONE_AP/firmware.bin espbinaries/M5Stack_Core_ONE_AP.bin
cp M5Stack_Core_ONE_AP/merged-firmware.bin espbinaries/M5Stack_Core_ONE_AP_full.bin
- name: Build firmware for OpenEPaperLink_Mini_AP
run: |
cd ESP32_AP-Flasher
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
pio run --environment OpenEPaperLink_Mini_AP
pio run --target buildfs --environment OpenEPaperLink_Mini_AP
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP/boot_app0.bin
cp .pio/build/OpenEPaperLink_Mini_AP/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP/firmware.bin
cp .pio/build/OpenEPaperLink_Mini_AP/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP/bootloader.bin
cp .pio/build/OpenEPaperLink_Mini_AP/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP/partitions.bin
cp .pio/build/OpenEPaperLink_Mini_AP/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP/littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP
esptool.py --chip esp32-s2 merge_bin -o merged-firmware.bin --flash_mode dio --flash_freq 80m --flash_size 4MB 0x1000 bootloader.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 firmware.bin 0x290000 littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
cp OpenEPaperLink_Mini_AP/firmware.bin espbinaries/OpenEPaperLink_Mini_AP.bin
cp OpenEPaperLink_Mini_AP/merged-firmware.bin espbinaries/OpenEPaperLink_Mini_AP_full.bin
- name: Build firmware for OpenEPaperLink_Nano_AP
run: |
cd ESP32_AP-Flasher
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
pio run --environment OpenEPaperLink_Nano_AP
pio run --target buildfs --environment OpenEPaperLink_Nano_AP
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_AP
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_AP/boot_app0.bin
cp .pio/build/OpenEPaperLink_Nano_AP/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_AP/firmware.bin
cp .pio/build/OpenEPaperLink_Nano_AP/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_AP/bootloader.bin
cp .pio/build/OpenEPaperLink_Nano_AP/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_AP/partitions.bin
cp .pio/build/OpenEPaperLink_Nano_AP/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_AP/littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_AP
esptool.py --chip esp32-s2 merge_bin -o merged-firmware.bin --flash_mode dio --flash_freq 80m --flash_size 4MB 0x1000 bootloader.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 firmware.bin 0x290000 littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
cp OpenEPaperLink_Nano_AP/firmware.bin espbinaries/OpenEPaperLink_Nano_AP.bin
cp OpenEPaperLink_Nano_AP/merged-firmware.bin espbinaries/OpenEPaperLink_Nano_AP_full.bin
- name: Build firmware for OpenEPaperLink_AP_and_Flasher
run: |
cd ESP32_AP-Flasher
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
pio run --environment OpenEPaperLink_AP_and_Flasher
pio run --target buildfs --environment OpenEPaperLink_AP_and_Flasher
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_AP_and_Flasher
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_AP_and_Flasher/boot_app0.bin
cp .pio/build/OpenEPaperLink_AP_and_Flasher/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_AP_and_Flasher/firmware.bin
cp .pio/build/OpenEPaperLink_AP_and_Flasher/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_AP_and_Flasher/bootloader.bin
cp .pio/build/OpenEPaperLink_AP_and_Flasher/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_AP_and_Flasher/partitions.bin
cp .pio/build/OpenEPaperLink_AP_and_Flasher/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_AP_and_Flasher/littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_AP_and_Flasher
esptool.py --chip esp32-s3 merge_bin -o merged-firmware.bin --flash_mode dio --flash_freq 80m --flash_size 16MB 0x0000 bootloader.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 firmware.bin 0x00c90000 littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
cp OpenEPaperLink_AP_and_Flasher/firmware.bin espbinaries/OpenEPaperLink_AP_and_Flasher.bin
cp OpenEPaperLink_AP_and_Flasher/merged-firmware.bin espbinaries/OpenEPaperLink_AP_and_Flasher_full.bin
- name: generate release json file
run: |
mkdir jsonfiles
python genfilelist.py ${{ github.ref_name }} $GITHUB_REPOSITORY $GITHUB_SHA
- name: Add file lists to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: jsonfiles/*
tag: ${{ github.ref }}
file_glob: true
overwrite: true
- name: Add esp bins to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: espbinaries/*
tag: ${{ github.ref }}
file_glob: true
overwrite: true
- name: Add tag bins to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: binaries/*
tag: ${{ github.ref }}
file_glob: true
overwrite: true
# - name: Add www folder to release
# uses: svenstaro/upload-release-action@v2
# with:
# repo_token: ${{ secrets.GITHUB_TOKEN }}
# file: ESP32_AP-Flasher/data/www/*
# tag: ${{ github.ref }}
# file_glob: true
# - name: Add fonts folder to release
# uses: svenstaro/upload-release-action@v2
# with:
# repo_token: ${{ secrets.GITHUB_TOKEN }}
# file: ESP32_AP-Flasher/data/fonts/*
# tag: ${{ github.ref }}
# file_glob: true
# - name: Add data folder to release
# uses: svenstaro/upload-release-action@v2
# with:
# repo_token: ${{ secrets.GITHUB_TOKEN }}
# file: ESP32_AP-Flasher/data/*
# tag: ${{ github.ref }}
# file_glob: true

View File

@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.16.0)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp32_fw)

View File

@@ -0,0 +1 @@
../../binaries/AP_FW_Pack.bin

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,129 @@
{
"1": {
"0": {
"weekday": [ 76, 10, "fonts/calibrib30" ],
"month": [ 76, 120, "fonts/calibrib30" ],
"day": [ 76, 42, "fonts/calibrib100" ]
},
"1": {
"weekday": [ 148, 10, "fonts/calibrib60" ],
"date": [ 148, 73, "fonts/calibrib50" ]
},
"2": {
"weekday": [ 200, 25, "fonts/calibrib60" ],
"month": [ 200, 225, "fonts/calibrib60" ],
"day": [ 200, 95, "fonts/calibrib150" ]
}
},
"2": {
"0": {
"fonts": [ "fonts/calibrib120", "fonts/calibrib80", "fonts/calibrib50", "fonts/calibrib50" ],
"xy": [ 76, 83 ]
},
"1": {
"fonts": [ "fonts/calibrib150", "fonts/calibrib150", "fonts/calibrib120", "fonts/calibrib100" ],
"xy": [ 148, 74 ]
},
"2": {
"fonts": [ "fonts/calibrib150", "fonts/calibrib150", "fonts/calibrib150", "fonts/calibrib120" ],
"xy": [ 200, 148 ]
}
},
"4": {
"0": {
"location": [ 10, 145, "t0_14b_tf" ],
"wind": [ 140, 10, "fonts/bahnschrift30" ],
"temp": [ 10, 10, "fonts/bahnschrift30" ],
"icon": [ 33, 33, "fonts/weathericons78" ],
"dir": [ 100, -2, "fonts/weathericons30" ],
"umbrella": [ 115, 110 ]
},
"1": {
"location": [ 5, 5, "fonts/bahnschrift30" ],
"wind": [ 280, 5, "fonts/bahnschrift30" ],
"temp": [ 5, 65, "fonts/bahnschrift70" ],
"icon": [ 185, 32, "fonts/weathericons70" ],
"dir": [ 240, -3, "fonts/weathericons30" ],
"umbrella": [ 190, 0 ]
},
"2": {
"location": [ 20, 20, "fonts/calibrib30" ],
"wind": [ 290, 83, "fonts/calibrib60" ],
"temp": [ 20, 170, "fonts/calibrib150" ],
"icon": [ 100, 50, "fonts/weathericons78" ],
"dir": [ 220, 50, "fonts/weathericons78" ],
"umbrella": [ 330, 10 ]
}
},
"8": {
"1": {
"location": [ 5, 12, "t0_14b_tf" ],
"column": [ 5, 59 ],
"day": [ 30, 18, "fonts/twcondensed20", 41, 108 ],
"icon": [ 12, 58, "fonts/weathericons30" ],
"wind": [ 17, 25 ],
"line": [ 20, 128 ]
},
"2": {
"location": [ 10, 10, "fonts/calibrib30" ],
"column": [ 6, 66 ],
"day": [ 33, 60, "fonts/bahnschrift20", 104, 230 ],
"rain": [ 34, 260 ],
"icon": [ 15, 145, "fonts/weathericons30" ],
"wind": [ 17, 90 ],
"line": [ 50, 300 ]
}
},
"9": {
"1": {
"title": [ 5, 3, "fonts/bahnschrift20" ],
"items": 8,
"line": [ 5, 34, 13 ],
"font": "glasstown_nbp_tf"
},
"2": {
"title": [ 10, 10, "fonts/calibrib30" ],
"items": 12,
"line": [ 10, 60, 20 ],
"font": "7x14_tf"
}
},
"10": {
"0": {
"title": [ 10, 15, "t0_14b_tf" ],
"pos": [ 76, 20 ]
},
"1": {
"title": [ 10, 5, "fonts/bahnschrift20" ],
"pos": [ 149, 25 ]
},
"2": {
"title": [ 10, 10, "fonts/bahnschrift20" ],
"pos": [ 200, 30 ]
}
},
"11": {
"1": {
"title": [ 5, 2, "fonts/bahnschrift20" ],
"date": [ 290, 2 ],
"items": 7,
"red": [ 0, 21, 296, 14 ],
"line": [ 5, 32, 15, "t0_14b_tf", 50 ]
},
"2": {
"title": [ 10, 10, "fonts/bahnschrift30" ],
"date": [ 390, 10 ],
"items": 12,
"red": [ 0, 48, 400, 17 ],
"line": [ 10, 61, 18, "7x14_tf", 60 ]
}
},
"16": {
"1": {
"location": [ 5, 5, "fonts/bahnschrift30" ],
"title": [ 247, 11, "glasstown_nbp_tf" ],
"cols": [ 1, 125, 12, "glasstown_nbp_tf" ],
"bars": [ 5, 111, 10 ]
}
}
}

View File

@@ -0,0 +1,64 @@
import requests
from PIL import Image, ImageDraw, ImageFont
mac = "00000197E5CB3B38" # destination mac address
dither = 0 # set dither to 1 is you're sending photos etc
apip = "192.168.178.192" # ip address of your access point
# Create a new paletted image with indexed colors
image = Image.new('P', (296, 128))
# Define the color palette (white, black, red)
palette = [
255, 255, 255, # white
0, 0, 0, # black
255, 0, 0 # red
]
# Assign the color palette to the image
image.putpalette(palette)
# Initialize the drawing context
draw = ImageDraw.Draw(image)
# Define the text lines
line1 = 'OpenEPaperLink'
line2 = 'Demo image'
# Define the fonts and sizes
font_line1 = ImageFont.truetype('arial.ttf', size=36) # Change the font file and size as per your preference
font_line2 = ImageFont.truetype('arial.ttf', size=16) # Change the font file and size as per your preference
# Calculate the text bounding boxes to get the text widths and heights
text_bbox_line1 = draw.textbbox((0, 0), line1, font=font_line1)
text_bbox_line2 = draw.textbbox((0, 0), line2, font=font_line2)
# Calculate the text positions to center the lines horizontally
text_position_line1 = ((image.width - (text_bbox_line1[2] - text_bbox_line1[0])) // 2, 20)
text_position_line2 = ((image.width - (text_bbox_line2[2] - text_bbox_line2[0])) // 2, 80)
# Write the text on the image
draw.text(text_position_line1, line1, fill=2, font=font_line1) # Use palette index 1 for black color
draw.text(text_position_line2, line2, fill=1, font=font_line2) # Use palette index 2 for red color
# Convert the image to 24-bit RGB
rgb_image = image.convert('RGB')
# Save the image as JPEG with maximum quality
image_path = 'output.jpg'
rgb_image.save(image_path, 'JPEG', quality="maximum")
# Prepare the HTTP POST request
url = "http://" + apip + "/imgupload"
payload = {"dither": dither, "mac": mac} # Additional POST parameter
files = {"file": open(image_path, "rb")} # File to be uploaded
# Send the HTTP POST request
response = requests.post(url, data=payload, files=files)
# Check the response status
if response.status_code == 200:
print("Image uploaded successfully!")
else:
print("Failed to upload the image.")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,92 @@
[
{
"name":"1.54 White",
"mac_offset":64518,
"mac_format":1,
"mac_suffix":"3410",
"MD5":"EC4CF26432B8E250BD3C56EFA0B6E852",
"type":0,
"note":"White 1.54 033"
},
{
"name":"1.54 Black NFC",
"mac_offset":64518,
"mac_format":1,
"mac_suffix":"3430",
"MD5":"F77F2BDA76EC584CFF143DFD74654354",
"type":0,
"note":"Black 1.54 032 w/NFC"
},
{
"name":"Segmented UK",
"mac_offset":30722,
"mac_format":2,
"mac_suffix":"0131",
"MD5":"A27D978C48888FA045E8D3F90DC27029",
"type":240,
"note":"Segmented, UK"
},
{
"name":"NoDisplay ZBS243 AP",
"mac_offset":0,
"mac_format":0,
"mac_suffix":"0000",
"MD5":"ABBAABBAD3ADD0D0CAFED3F90DC27029",
"type":255,
"note":"NoDisplay ZBS243 AP"
},
{
"name":"4.2 Black + NFC",
"mac_offset":64518,
"mac_format":1,
"mac_suffix":"4830",
"MD5":"A7E30D170AC8F2569F4D7E815594FB9F",
"type":2,
"note":"4.2 v033 FW"
},
{
"name":"2.9 White",
"mac_offset":64518,
"mac_format":1,
"mac_suffix":"3B10",
"MD5":"A41315C16017BA64CF5BE12DFCA0F781",
"type":1,
"note":"2.9 v033 FW"
},
{
"name":"2.9 White - 2nd",
"mac_offset":64518,
"mac_format":1,
"mac_suffix":"3B10",
"MD5":"4FFCE4402E1540824BDDA62BC93FC3D9",
"type":1,
"note":"2.9 v033 FW - 2nd version"
},
{
"name":"2.9 White + NFC UC8151",
"mac_offset":64518,
"mac_format":1,
"mac_suffix":"3B30",
"MD5":"F707C29A240D7DD237C3C1265FAF4B68",
"type":17,
"note":"2.9 White + NFC UC8151 V032 FW"
},
{
"name":"2.9 White + NFC UC8151",
"mac_offset":64518,
"mac_format":1,
"mac_suffix":"3B30",
"MD5":"D7E45FEECA144BB4ED750BEADE2CAD0A",
"type":17,
"note":"2.9 White + NFC UC8151 V033 FW"
},
{
"name":"2.9 White + NFC UC8151",
"mac_offset":64518,
"mac_format":1,
"mac_suffix":"3B30",
"MD5":"4643F4F74321FC9B4F9F0ECBD93B74AB",
"type":17,
"note":"2.9 White + NFC UC8151 V025 FW"
}
]

View File

@@ -0,0 +1,15 @@
{
"deletefile": [
"/fonts/calibrib62.vlw",
"/fonts/GillSC16.vlw",
"/fonts/GillSC20.vlw",
"/fonts/numbers1-1.vlw",
"/fonts/numbers1-2.vlw",
"/fonts/numbers2-1.vlw",
"/fonts/numbers2-2.vlw",
"/fonts/numbers3-1.vlw",
"/fonts/numbers3-2.vlw",
"/fonts/tw20.vlw",
"/fonts/twbold20.vlw"
]
}

View File

@@ -0,0 +1,537 @@
[
{
"id": 0,
"name": "Static image",
"desc": "Shows a static image, from file system, painter or external source",
"hwtype": [
0,
1,
2,
17
],
"param": [
{
"key": "filename",
"name": "Filename",
"desc": "Local filename on the littlefs drive",
"type": "text"
},
{
"key": "timetolive",
"name": "TimeToLive",
"desc": "Amount (minutes) that this image will stay valid. The tag might not respond meanwhile",
"type": "int"
},
{
"key": "dither",
"name": "Dithering",
"desc": "Turn halftone dithering on or off. Turn it on when displaying photos. For texts, you better leave if off",
"type": "select",
"options": {
"0": "off",
"1": "on"
}
}
]
},
{
"id": 1,
"name": "Current date",
"desc": "Shows the current date",
"hwtype": [
0,
1,
2,
17,
240
],
"param": []
},
{
"id": 2,
"name": "Count days",
"desc": "Counts days, starting with the value below. If the count value gets higher than the threshold, the number is displayed in red, otherwise it's black",
"hwtype": [
0,
1,
2,
17,
240
],
"param": [
{
"key": "counter",
"name": "Counter value",
"desc": "Current value",
"type": "int"
},
{
"key": "thresholdred",
"name": "Threshold",
"desc": "Value is displayed in red if higher than the threshold",
"type": "int",
"hwtype": [
0,
1,
2,
17
]
}
]
},
{
"id": 3,
"name": "Count hours",
"desc": "Counts hours, starting with the value below. If the count value gets higher than the threshold, the number is displayed in red, otherwise it's black",
"hwtype": [
0,
1,
2,
17,
240
],
"param": [
{
"key": "counter",
"name": "Counter",
"desc": "Current value",
"type": "int"
},
{
"key": "thresholdred",
"name": "Threshold",
"desc": "Value is displayed in red if higher than the threshold",
"type": "int",
"hwtype": [
0,
1,
2,
17
]
}
]
},
{
"id": 4,
"name": "Current weather",
"desc": "Current weather. Weather data by Open-Meteo.com",
"hwtype": [
0,
1,
2,
17,
240
],
"param": [
{
"key": "location",
"name": "Location",
"desc": "Name of the city. This is used to lookup the lat/long data, and to display as the title",
"type": "text"
},
{
"key": "#lat",
"name": "Lat",
"desc": "Latitude (set automatic when generating image)",
"type": "ro"
},
{
"key": "#lon",
"name": "Lon",
"desc": "Longitude (set automatic when generating image)",
"type": "ro"
}
]
},
{
"id": 8,
"name": "Weather forecast",
"desc": "Weather forecast for the next five days. Weather data by Open-Meteo.com",
"hwtype": [
1,
2,
17
],
"param": [
{
"key": "location",
"name": "Location",
"desc": "Name of the city. This is used to lookup the lat/long data, and to display as the title",
"type": "text"
},
{
"key": "#lat",
"name": "Lat",
"desc": "Latitude (set automatic when generating image)",
"type": "ro"
},
{
"key": "#lon",
"name": "Lon",
"desc": "Longitude (set automatic when generating image)",
"type": "ro"
}
]
},
{
"id": 16,
"name": "Buienradar",
"desc": "Dutch rain predictions for the next two hours. Only works for locations in the Netherlands and Belgium.",
"hwtype": [
1,
17
],
"param": [
{
"key": "location",
"name": "Location",
"desc": "Name of the city. This is used to lookup the lat/long data, and to display as the title",
"type": "text"
},
{
"key": "ttl",
"name": "Time To Live",
"desc": "How often (in minutes) should this be refreshed. Minimum is 5 minutes, but will shorten battery lifetime",
"type": "int"
},
{
"key": "#lat",
"name": "Lat",
"desc": "Latitude (set automatic when generating image)",
"type": "ro"
},
{
"key": "#lon",
"name": "Lon",
"desc": "Longitude (set automatic when generating image)",
"type": "ro"
}
]
},
{
"id": 9,
"name": "RSS feed",
"desc": "Gets an RSS feed, and display the first few lines of it",
"hwtype": [
1,
2,
17
],
"param": [
{
"key": "title",
"name": "Title",
"desc": "Displayed title",
"type": "text"
},
{
"key": "url",
"name": "URL",
"desc": "Full URL of the RSS feed",
"type": "text"
},
{
"key": "interval",
"name": "Interval",
"desc": "How often (in minutes) the feed is being refreshed",
"type": "int"
}
]
},
{
"id": 7,
"name": "Image URL",
"desc": "Gets an external image and displays it",
"hwtype": [
0,
1,
2,
17
],
"param": [
{
"key": "url",
"name": "URL",
"desc": "Full URL of the image. Image should be in jpeg format (non-progressive), and with exactly the right resolution for the screen (eg 128x296 or 152x152). Will be auto-rotated. Colors will be dithered",
"type": "text"
},
{
"key": "interval",
"name": "Interval",
"desc": "How often (in minutes) the image is being fetched",
"type": "int"
}
]
},
{
"id": 10,
"name": "QR code",
"desc": "Displayes a full screen QR code",
"hwtype": [
0,
1,
2,
17
],
"param": [
{
"key": "title",
"name": "Title",
"desc": "Displayed title",
"type": "text"
},
{
"key": "qr-content",
"name": "QR content",
"desc": "Any content that can be coded into a QR code",
"type": "text"
}
]
},
{
"id": 11,
"name": "Google calendar",
"desc": "Displays the current and upcoming appointments (next 24 hours) from a Google calendar. To let this work, you need a small Google Apps Script to interface with your calendar. See documentation on github how to do that",
"hwtype": [
1,
2,
17
],
"param": [
{
"key": "title",
"name": "Title",
"desc": "Displayed title",
"type": "text"
},
{
"key": "apps_script_url",
"name": "Apps Script URL",
"desc": "URL given by Google Apps Script",
"type": "text"
},
{
"key": "interval",
"name": "Interval",
"desc": "How often (in minutes) the calendar is being refreshed",
"type": "int"
}
]
},
{
"id": 5,
"name": "Firmware update",
"desc": "To update tag firmware",
"hwtype": [
0,
1,
2,
17,
240
],
"param": [
{
"key": "filename",
"name": "Filename",
"desc": "Local file on littlefs partition",
"type": "text"
}
]
},
{
"id": 12,
"name": "Remote content",
"desc": "Content is generated by a different Access Point",
"hwtype": []
},
{
"id": 13,
"name": "Set segments",
"desc": "Used for debugging. Work in progress",
"hwtype": [
240
],
"param": [
{
"key": "line1",
"name": "line 1",
"desc": "8888",
"type": "text"
},
{
"key": "line2",
"name": "line 2",
"desc": "88",
"type": "text"
},
{
"key": "line3",
"name": "line 3",
"desc": "8888",
"type": "text"
}
]
},
{
"id": 14,
"name": "Set NFC URL",
"desc": "Send the URL to the NFC chip. The URL is transmitted to a NFC reader (like your phone) if you hold it next to the tag",
"hwtype": [
0,
17
],
"capabilities": 64,
"param": [
{
"key": "url",
"name": "URL",
"desc": "Full URL",
"type": "text"
}
]
},
{
"id": 15,
"name": "Send custom LUT",
"desc": "EXPERIMENTAL. Don't use. YOU RISK DAMAGING YOUR SCREEN.",
"hwtype": [
1
],
"capabilities": 4,
"param": [
{
"key": "bytes",
"name": "bytes",
"desc": "76 bytes, formatted as 0x00,0x00,...",
"type": "text"
}
]
},
{
"id": 17,
"name": "Send Command",
"desc": "Send a command to a tag to execute",
"hwtype": [
0,
1,
2,
17,
240
],
"param": [
{
"key": "cmd",
"name": "CMD",
"desc": "Action",
"type": "select",
"options": {
"0": "Reboot",
"1": "Scan Channels",
"2": "Clear settings"
}
}
]
},
{
"id": 18,
"name": "Set Tag Config",
"desc": "Sets tag options. The options you see below are the default options. This may or may not match current tag settings",
"hwtype": [
0,
1,
2,
17,
240
],
"param": [
{
"key": "fastboot",
"name": "Boot method",
"desc": "How the tag should boot, fast or normal",
"type": "select",
"options": {
"0": "-Normal boot",
"1": "Fast boot"
}
},
{
"key": "rfwake",
"name": "RF Wake",
"desc": "If the tag should support RF wake or not. This adds a 0.9µA current draw",
"type": "select",
"options": {
"0": "-Disabled",
"1": "Enabled"
}
},
{
"key": "tagroaming",
"name": "Tag Roaming",
"desc": "If enabled, the tag will periodically scan for AP's and will switch to a different channel if a stronger signal is found",
"type": "select",
"options": {
"0": "-Disabled",
"1": "Enabled"
}
},
{
"key": "tagscanontimeout",
"name": "Scan for AP on timeout",
"desc": "If a tag hasn't found an AP for an hour, should it rescan the channels for another AP?",
"type": "select",
"options": {
"1": "-Enabled",
"0": "Disabled"
}
},
{
"key": "showlowbat",
"name": "Low Battery symbol",
"desc": "Should the tag display the 'low battery' symbol if the battery a voltage threshold has been reached?",
"type": "select",
"options": {
"1": "-Enabled",
"0": "Disabled"
}
},
{
"key": "shownorf",
"name": "No AP symbol",
"desc": "Should the tag display the 'No-signal/AP' symbol if it hasn't been able to contact an AP?",
"type": "select",
"options": {
"1": "-Enabled",
"0": "Disabled"
}
},
{
"key": "lowvoltage",
"name": "Low voltage threshold",
"desc": "Below what voltage should the tag display the 'low bat' symbol?",
"type": "select",
"options": {
"2600": "-2.6v",
"2500": "2.5v",
"2400": "2.4v",
"2300": "2.3v",
"2200": "2.2v"
}
},
{
"key": "fixedchannel",
"name": "Fixed Channel",
"desc": "What channel should the tag initially join?",
"type": "select",
"options": {
"0": "-Auto",
"11": "11",
"15": "15",
"20": "20",
"25": "25",
"26": "26",
"27": "27"
}
}
]
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -0,0 +1,210 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>Open EPaper Link Access Point</title>
<link rel="stylesheet" href="main.css" type="text/css" />
<link rel="icon" type="image/vnd.icon" href="favicon.ico">
</head>
<body>
<header>
<div class="logo">Open EPaper Link Access Point</div>
</header>
<div id="configbox">
<div class="closebtn">&#10006;</div>
<h3 id="cfgmac">00000000</h3>
<p>
<label for="cfgalias">Alias</label>
<input id="cfgalias" type="text">
</p>
<p>
<label for="cfgcontent">Content</label>
<select id="cfgcontent" onchange="contentselected()">
</select>
<button id="paintbutton"><i>A</i>&#128396;</button>
</p>
<div id="customoptions"></div>
<div id="advancedoptions" style="height: 0px;">
<p>Advanced options</p>
<p>
<label for="cfgrotate">Rotate image</label>
<select id="cfgrotate">
<option value="0">0 degrees</option>
</select>
</p>
<p>
<label for="cfglut">LUT</label>
<select id="cfglut">
<option value="0">auto</option>
</select>
</p>
<p class="tagbuttons">
<button id="cfgrefresh">force refresh</button>
<button id="cfgclrpending">clear pending</button>
<button id="cfgtagreboot">reboot</button>
<button id="cfgscan">scan</button>
<button id="cfgreset">reset settings</button>
<button id="cfgdelete" title="remove"><img src="data:image/gif;base64,R0lGODlhEAAQAPMAANXV1e3t7d/f39HR0dvb2/Hx8dTU1OLi4urq6mZmZpmZmf///wAAAAAAAAAAAAAAACH5BAEAAAwALAAAAAAQABAAAARBkMlJq71Yrp3ZXkr4WWCYnOZSgQVyEMYwJCq1nHhe20qgCAoA7QLyAYU7njE4JPV+zOSkCEUSFbmTVPPpbjvgTAQAOw== "></button>
</p>
</div>
<p id="savebar">
<span><input type="button" value="Save" id="cfgsave"></span>
<span id="cfgmore" title="advanced options">&#x1f783;</span>
</p>
</div>
<div id="apconfigbox">
<div class="closebtn">&#10006;</div>
<h3>Access Point config</h3>
<p>
<label for="apcfgalias">Alias</label>
<input id="apcfgalias" type="text">
</p>
<p>
<label for="apcfgchid">Channel</label>
<select id="apcfgchid">
<option value="0">auto</option>
<option value="11">11</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
</select>
</p>
<p>
<label for="apcfgledbrightness">LED brightness</label>
<select id="apcfgledbrightness">
<option value="-1">off</option>
<option value="64">25%</option>
<option value="128">50%</option>
<option value="192">75%</option>
<option value="255">100%</option>
</select>
</p>
<p>
<label for="apcfglanguage">Content language</label>
<select id="apcfglanguage">
<option value="0">EN English</option>
<option value="1">NL Nederlands</option>
<option value="2">DE Deutsch</option>
</select>
</p>
<p title="Depending on the content, a tag can sleep for
longer periods when no updates are expected
(like a date display). This setting specifies
the maximum sleep time.">
<label for="apclatency">Maximum sleep</label>
<select id="apclatency">
<option value="0">shortest (40 sec)</option>
<option value="5">5 minutes</option>
<option value="10">10 minute</option>
<option value="30">30 minutes</option>
<option value="60">1 hour</option>
</select>
</p>
<p title="If connected to the website, don't sleep extra.
Latency will be around 40 seconds.">
<label for="apcpreventsleep">Shorten latency during config</label>
<select id="apcpreventsleep">
<option value="0">no</option>
<option value="1">yes</option>
</select>
</p>
<p>
<input type="button" value="Save" id="apcfgsave">
</p>
<p>
Active access points:<br>
<table id="aptable">
<tr>
<th>ip</th>
<th>alias</th>
<th>tags</th>
<th>ch</th>
<th>AP ver</th>
</tr>
</table>
</p>
<p>
<span id="rebootbutton">reboot AP</span>
<a href="/backup_db" id="downloadDBbutton">download tagDB</a>
<span id="updatebutton">update</span>
</p>
<p>
<a href="https://github.com/jjwbruijn/OpenEPaperLink" target="_new">Github OpenEPaperLink</a>
</p>
</div>
<div id="apupdatebox">
<div class="closebtn">&#10006;</div>
<h3>Update dashboard</h3>
<div id="easyupdate"></div>
<div id="advanceddiv">
<div id="releasetable"></div>
<div id="rollbackOption" style="display:none"><button id="rollbackBtn">Rollback to previous firmware</button></div>
</div>
</div>
<form>
<div class="container">
<div class="window">
<div class="actionbox">
<div>
<div>Currently active tags:</div>
<div><span id="temp"></div>
<div><span id="runstate"></div>
<div><span id="apstatecolor">&#11044;</span> <span id="apstate">loading</span></div>
<div><span id="apconfigbutton">AP config</span></div>
<div><a href="/edit" target="littlefs" class="filebutton">edit contentFS</a></div>
</div>
</div>
<div id="taglist" class="taglist">
<div class="tagcard" id="tagtemplate">
<div class="currimg"><canvas class="tagimg"></div>
<div class="mac"></div>
<div class="alias"></div>
<div class="model"></div>
<div class="received"></div>
<div class="contentmode"></div>
<div class="lastseen"></div>
<div class="nextcheckin"></div>
<div class="nextupdate"></div>
<div class="corner">
<div class="pendingicon" title="A new message is waiting for the tag to pick up">&circlearrowright;</div>
<div class="warningicon" title="This tag has not been seen for a long time">&#9888;</div>
</div>
</div>
</div>
<div class="logbox">
<p>
<span>logging</span>
<span><img id="clearlog" src="data:image/gif;base64,R0lGODlhEAAQAPMAANXV1e3t7d/f39HR0dvb2/Hx8dTU1OLi4urq6mZmZpmZmf///wAAAAAAAAAAAAAAACH5BAEAAAwALAAAAAAQABAAAARBkMlJq71Yrp3ZXkr4WWCYnOZSgQVyEMYwJCq1nHhe20qgCAoA7QLyAYU7njE4JPV+zOSkCEUSFbmTVPPpbjvgTAQAOw==
"></span>
<span id="sysinfo"></span>
</p>
<ul id="messages" class="messages">
</ul>
</div>
</div>
</div>
</form>
<script src="main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,612 @@
* {
margin:0;
padding:0;
border:0;
list-style-type: none;
outline: none;
font-weight: 400;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
font-smooth: auto;
-webkit-font-smoothing: antialiased;
}
html, body {
height: 100%;
}
body {
font-size: 12px;
font-family: Helvetica, Arial, Verdana, sans-serif;
line-height: 1.5;
background-color: #e4e4e0;
}
header {
height: 50px;
background-color: #646260;
}
label {
width: 120px;
display: inline-block;
vertical-align: top;
padding: 3px 0px;
}
.logo {
margin: 0 auto;
height: 50px;
text-indent: 50px;
overflow:hidden;
font-size: 2.5em;
color: white;
}
.window {
margin: 0 auto;
max-width: 94%;
}
.actionbox>div:first-child {
padding: 10px;
background-color: white;
margin: 5px;
}
.actionbox>div:first-child>div:first-child {
flex-grow: 2;
}
.actionbox>div {
display:flex;
gap: 20px;
}
#rebootbutton, #updatebutton, #downloadDBbutton, #apconfigbutton, .filebutton {
padding: 2px 5px;
background-color: #cccccc;
text-decoration: none;
color: black;
cursor: pointer;
white-space: nowrap;
}
.columns div {
flex: 1;
}
textarea,
input.text,
input[type="text"],
input[type="button"],
input[type="submit"],
button {
appearance: none;
border-radius: 0;
}
input {
border: solid 1px #cccccc;
padding: 4px;
border-radius: 0px;
}
input[type=button], button {
border: 0px;
padding: 4px 10px;
cursor:pointer;
}
input[type=button]:hover,
button:hover {
background-color:#aaaaaa;
}
select {
padding: 3px 4px;
border-radius: 0px;
border: solid 1px #cccccc;
}
#configbox, #apconfigbox, #apupdatebox {
display: none;
position: fixed;
top: 65px;
left: 15px;
width: 380px;
padding: 15px;
background-color: #f0e6d3;
z-index: 999;
box-shadow: 7px 10px 52px -19px rgba(0, 0, 0, 0.63);
overflow: auto;
max-height: calc(100vh - 75px);
}
#configbox p, #apconfigbox p, #apupdatebox p {
padding: 5px;
display: flex;
gap: 5px;
align-items: flex-start;
}
#configbox h3, #apconfigbox h3, #apupdatebox h3 {
font-size: 1.5em;
font-weight: bold;
}
#configbox input, #apconfigbox input {
border: solid 1px #cccccc;
}
#configbox input[type=number] {
width: 80px;
}
#advancedoptions {
overflow: hidden;
transition: height 0.3s ease;
}
#advancedoptions p:first-child {
font-weight: 700;
font-size: 1.2em;
}
.tagbuttons {
flex-flow: wrap;
}
.tagbuttons button {
font-size: 0.95em;
padding: 2px 4px;
}
#savebar {
display: flex;
align-items: flex-end;
justify-content: space-between;
}
#savebar:first-child {
flex-grow: 2;
}
#cfgmore {
padding: 2px 5px;
font-weight: 700;
font-size: 1.2em;
cursor: pointer;
}
#apconfigbox {
background-color: #e6f0d3;
}
#aptable {
width: 100%;
border-spacing: 0;
}
#aptable th {
text-align: left;
background-color: #00000020;
padding: 0px 3px;
}
#aptable th, #aptable td {
border-right: 1px solid #000010;
padding: 0px 3px;
}
#aptable td:nth-child(1), #aptable th:nth-child(1) {
border-left: 1px solid #000010;
}
#aptable td:nth-child(3),
#aptable td:nth-child(4) {
text-align: right;
}
#apupdatebox {
background-color: #f0d0c8;
width: 700px;
padding-bottom: 20px;
border: 1px solid #d0b0a8;
}
#cfgdelete {
cursor: pointer;
padding: 2px 10px;
}
.closebtn {
border: 1px solid black;
float: right;
width: 19px;
height: 20px;
font-size: 1.1em;
text-align: center;
margin: 5px;
cursor: pointer;
}
.logbox {
margin: 5px;
}
.logbox p {
background-color: #ffffff;
padding: 5px 10px;
}
.logbox img {
vertical-align: bottom;
cursor:pointer;
}
.logbox #sysinfo {
float: right;
}
.taglist {
display: flex;
flex-wrap: wrap;
}
#tagtemplate {
display:none;
}
.tagcard {
width: 225px;
position: relative;
min-height: 170px;
margin: 5px;
padding: 5px;
background-color: #ffffff;
border: 1px solid #cccccc;
transition: box-shadow 0.3s ease;
}
.tagcard:hover {
cursor:pointer;
box-shadow: 7px 10px 52px -19px rgba(0, 0, 0, 0.63);
filter: brightness(1.02);
}
.tagflash {
animation: tagflash 1s;
}
.tagpending {
animation: pending 1.5s ease infinite;
}
.currimg {
float: right;
}
.currimg img, .currimg canvas {
max-width: 50px;
border: 1px solid #c0c0c0;
}
.mac {
font-size: 0.9em;
cursor:pointer;
}
.alias {
font-size: 1.4em;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.received {
display: flex;
font-size: .85em;
padding-bottom: 5px;
}
.received div {
display: inline-block;
}
.contentmode {
font-weight: bold;
font-size: 1.4em;
padding-bottom: 5px;
}
.lastseen, .nextcheckin, .nextupdate {
font-size: 0.9em;
}
.lastseen span,
.nextcheckin span,
.nextupdate span {
width:105px;
display:inline-block;
}
.corner {
position: absolute;
right: 0px;
bottom: 0px;
padding: 5px;
}
.pendingicon {
width: 20px;
height: 20px;
background-color: rgb(7, 174, 230);
font-size: 1.2em;
text-align: center;
font-weight: bold;
display: none;
vertical-align: top;
}
.warningicon {
display:none;
font-size: 1.3em;
background-color: yellow;
color: black;
height: 20px;
width: 20px;
vertical-align: top;
text-align: center;
}
ul.messages {
padding: 5px;
}
ul.messages li {
position: relative;
}
ul.messages li.new {
animation-name: new;
animation-duration: 1400ms;
animation-iteration-count: 1;
animation-timing-function: ease-in-out;
}
.error {
color: red;
}
#paintbutton {
padding: 1px 3px;
border: 1px solid black;
font-size: 1.3em;
vertical-align: top;
margin-left:12px;
cursor: pointer;
}
#paintbutton:hover {
background-color: #aaaaaa;
}
/* painter */
#canvasdiv {
padding: 5px;
}
#canvasdiv canvas {
border: 1px solid black;
}
#buttonbar {
padding: 5px;
display: flex;
gap: 5px;
}
#buttonbar button,
#layersdiv button {
padding: 1px 2px;
border: 1px solid #cccccc;
background-color: #dddddd;
width: 40px;
}
#buttonbar button {
font-size: 1.2em;
font-weight: bold;
}
#buttonbar .active {
background-color: #ffffff;
cursor: pointer;
}
#buttonbar button:hover,
#layersdiv button:hover {
background-color: #cccccc;
cursor: pointer;
}
#layersdiv {
padding: 0px 5px;
}
#layersdiv>div {
display: flex;
gap: 5px;
margin-bottom: 5px;
}
#layersdiv input,
#layersdiv select {
padding: 2px;
}
#font-select {
width: 150px;
}
#savebar button {
border: solid 1px #666666;
}
/* updatescreens */
#easyupdate{
margin-top: 10px;
}
#easyupdate button {
display: block;
margin: 15px 40px;
padding: 10px 20px;
}
#easyupdate a {
cursor: pointer;
text-decoration: underline;
}
#advanceddiv {
display: none;
}
#releasetable {
margin: 10px 0px;
}
#releasetable table {
border-spacing: 1px;
}
#releasetable th {
text-align: left;
background-color: #ffffff;
padding: 1px 5px;
}
#releasetable td {
background-color: #ffffff;
padding: 1px 5px;
min-width: 70px;
vertical-align: baseline;
}
#releasetable td:nth-child(2) {
word-wrap: nowrap;
}
#releasetable button {
padding: 3px 10px;
background-color: #e0e0e0;
}
#releasetable button:hover {
background-color: #a0a0a0;
}
.console {
width: 100%;
background-color: black;
font-family: 'lucida console','ui-monospace';
color: white;
padding: 5px 10px;
margin: 20px 0px;
padding-bottom: 25px;
height: 400px;
overflow-y: scroll;
white-space: break-spaces;
}
.console div {
word-break: break-all;
}
/* media */
@media(max-width: 460px) {
.messages li div, ul.messages li div.date, ul.messages li div.message {
display:block;
position:relative;
padding: 0;
left: auto;
}
.messages li div.message, li.pending {
margin-bottom: 8px;
}
ul.messages {
padding-bottom: 4px;
}
}
@keyframes new {
0% { background-color: rgba(255, 255, 204, 1); }
50% { background-color: rgba(255, 255, 204, .5); }
100% { background-color: rgba(255, 255, 204, 0); }
}
@keyframes tagflash {
0% { opacity: 1; }
50% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes pending {
0% { }
50% { background-color: #d4d4f5;}
100% { }
}
@media screen and (max-width: 480px) {
/* styles for mobile devices in portrait mode */
body {
font-size: 13px;
}
.tagcard {
width: 100%;
min-height: 200px;
}
.logbox #sysinfo {
float: none;
display: block;
}
#configbox {
top: 0px;
left: 0px;
width: 100%;
}
.currimg {
float: none;
position: absolute;
right: 5px;
}
.currimg img,
.currimg canvas {
max-width: 60px;
}
.logo {
padding-top: 10px;
text-indent: 0px;
font-size: 1.8em;
text-align: center;
}
.actionbox>div {
gap: 5px;
flex-flow: wrap;
}
.actionbox>div:first-child>div:first-child {
flex-basis: 100%;
}
}

View File

@@ -0,0 +1,726 @@
const $ = document.querySelector.bind(document);
const WAKEUP_REASON_TIMED = 0;
const WAKEUP_REASON_BOOT = 1;
const WAKEUP_REASON_GPIO = 2;
const WAKEUP_REASON_NFC = 3;
const WAKEUP_REASON_FIRSTBOOT = 0xFC;
const WAKEUP_REASON_NETWORK_SCAN = 0xFD;
const WAKEUP_REASON_WDT_RESET = 0xFE;
const models = ["1.54\" 152x152px", "2.9\" 296x128px", "4.2\" 400x300px"];
models[240] = "Segmented tag"
models[17] = "2.9\" 296x128px (UC8151)"
const displaySizeLookup = { 0: [152, 152, 4], 1: [128, 296, 2], 2: [400, 300, 2] }; // w, h, rotate
displaySizeLookup[17] = [128, 296, 2];
displaySizeLookup[240] = [0, 0, 0];
const colorTable = { 0: [255, 255, 255], 1: [0, 0, 0], 2: [255, 0, 0], 3: [150, 150, 150] };
const apstate = [
{ state: "offline", color: "red" },
{ state: "online", color: "green" },
{ state: "flashing", color: "orange" },
{ state: "wait for reset", color: "blue" },
{ state: "requires power cycle", color: "purple" },
{ state: "failed", color: "red" },
{ state: "coming online", color: "yellow" }
];
const runstate = [
{ state: "⏹︎ stopped" },
{ state: "⏸pause" },
{ state: "" }, // hide running
{ state: "⏳︎ init" }
];
const imageQueue = [];
let isProcessing = false;
let servertimediff = 0;
let paintLoaded = false, paintShow = false;
var cardconfig;
let otamodule;
window.addEventListener("load", function () {
fetch("/get_ap_config")
.then(response => response.json())
.then(data => {
if (data.alias) {
$(".logo").innerHTML = data.alias;
this.document.title = data.alias;
}
});
fetch('/content_cards.json')
.then(response => response.json())
.then(data => {
cardconfig = data;
loadTags(0);
connect();
setInterval(updatecards, 1000);
})
.catch(error => {
console.error('Error:', error);
alert("I can\'t load /www/content_cards.json.\r\nHave you upload it to the data partition?");
});
});
let socket;
function loadTags(pos) {
fetch("/get_db?pos=" + pos)
.then(response => response.json())
.then(data => {
processTags(data.tags);
if (data.continu && data.continu > pos) loadTags(data.continu);
})
//.catch(error => showMessage('loadTags error: ' + error));
}
function connect() {
socket = new WebSocket("ws://" + location.host + "/ws");
socket.addEventListener("open", (event) => {
showMessage("websocket connected");
});
socket.addEventListener("message", (event) => {
console.log(event.data)
const msg = JSON.parse(event.data);
if (msg.logMsg) {
showMessage(msg.logMsg, false);
}
if (msg.errMsg) {
showMessage(msg.errMsg, true);
}
if (msg.tags) {
processTags(msg.tags);
}
if (msg.sys) {
$('#sysinfo').innerHTML = 'free heap: ' + msg.sys.heap + ' bytes &#x2507; db size: ' + convertSize(msg.sys.dbsize) + " ("+ msg.sys.dbsize + ' bytes) &#x2507; db record count: ' + msg.sys.recordcount + ' &#x2507; filesystem free: ' + convertSize(msg.sys.littlefsfree);
if (msg.sys.apstate) {
$("#apstatecolor").style.color = apstate[msg.sys.apstate].color;
$("#apstate").innerHTML = apstate[msg.sys.apstate].state;
$("#runstate").innerHTML = runstate[msg.sys.runstate].state;
$("#temp").innerHTML = msg.sys.temp.toFixed(1) + '°C';
}
servertimediff = (Date.now() / 1000) - msg.sys.currtime;
}
if (msg.apitem) {
var row = $("#aptable").insertRow();
row.insertCell(0).innerHTML = "<a href=\"http://" + msg.apitem.ip + "\" target=\"_new\">" + msg.apitem.ip + "</a>";
row.insertCell(1).innerHTML = msg.apitem.alias;
row.insertCell(2).innerHTML = msg.apitem.count;
row.insertCell(3).innerHTML = msg.apitem.channel;
row.insertCell(4).innerHTML = msg.apitem.version;
}
if (msg.console) {
if (otamodule && typeof(otamodule.print) === "function") {
let color = "#c0c0c0";
if (msg.console.startsWith("Fail") || msg.console.startsWith("Err")) {
color = "red";
}
otamodule.print(msg.console, color);
}
}
});
socket.addEventListener("close", (event) => {
showMessage(`websocket closed ${event.code}`);
setTimeout(connect, 5000);
});
}
function convertSize(bytes) {
if (bytes >= 1073741824) { bytes = (bytes / 1073741824).toFixed(2) + " GB"; }
else if (bytes >= 1048576) { bytes = (bytes / 1048576).toFixed(2) + " MB"; }
else if (bytes >= 1024) { bytes = (bytes / 1024).toFixed(2) + " kB"; }
else if (bytes > 1) { bytes = bytes + " bytes"; }
else if (bytes == 1) { bytes = bytes + " byte"; }
else { bytes = "0 bytes"; }
return bytes;
}
function processTags(tagArray) {
for (const element of tagArray) {
tagmac = element.mac;
var div = $('#tag' + tagmac);
if (div == null) {
div = $('#tagtemplate').cloneNode(true);
div.setAttribute('id', 'tag' + tagmac);
div.dataset.mac = tagmac;
div.dataset.hwtype = -1;
$('#taglist').appendChild(div);
}
div.style.display = 'block';
if (element.contentMode == 255) {
div.remove();
showMessage(tagmac + " removed by remote AP");
continue;
}
if (element.isexternal) {
$('#tag' + tagmac + ' .mac').innerHTML = tagmac + " via ext AP";
} else {
$('#tag' + tagmac + ' .mac').innerHTML = tagmac;
}
let alias = element.alias;
if (!alias) alias = tagmac.replace(/^0{1,4}/, '');
if ($('#tag' + tagmac + ' .alias').innerHTML != alias) {
$('#tag' + tagmac + ' .alias').innerHTML = alias;
sortGrid();
}
let contentDefObj = getContentDefById(element.contentMode);
if (contentDefObj) $('#tag' + tagmac + ' .contentmode').innerHTML = contentDefObj.name;
if (element.RSSI) {
div.dataset.hwtype = element.hwType;
$('#tag' + tagmac + ' .model').innerHTML = models[element.hwType];
let statusline = "";
if (element.RSSI != 100) {
if (element.ch > 0) statusline += `CH ${element.ch}, `;
statusline += `RSSI ${element.RSSI}, LQI ${element.LQI}`;
} else {
statusline = "AP";
}
if (element.batteryMv != 0 && element.batteryMv != 1337) {
statusline += ", " + (element.batteryMv >= 2600 ? "&#x2265;" : "") + (element.batteryMv / 1000) + "V";
}
if (element.ver != 0 && element.ver != 1) {
$('#tag' + tagmac + ' .received').title = `fw: ${element.ver}`;
} else {
$('#tag' + tagmac + ' .received').title = "";
}
$('#tag' + tagmac + ' .received').innerHTML = statusline;
$('#tag' + tagmac + ' .received').style.opacity = "1";
} else {
$('#tag' + tagmac + ' .model').innerHTML = "waiting for hardware type";
$('#tag' + tagmac + ' .received').style.opacity = "0";
}
if (div.dataset.hash != element.hash && div.dataset.hwtype > -1 && (element.isexternal == false || element.contentMode != 12)) {
loadImage(tagmac, '/current/' + tagmac + '.raw?' + (new Date()).getTime());
div.dataset.hash = element.hash;
}
if (element.isexternal == true && element.contentMode == 12) $('#tag' + tagmac + ' .tagimg').style.display = 'none';
if (element.nextupdate > 1672531200 && element.nextupdate != 3216153600) {
var date = new Date(element.nextupdate * 1000);
var options = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
$('#tag' + tagmac + ' .nextupdate').innerHTML = "<span>next update</span>" + date.toLocaleString('nl-NL', options);
} else {
$('#tag' + tagmac + ' .nextupdate').innerHTML = "";
}
if (element.nextcheckin > 1672531200) {
div.dataset.nextcheckin = element.nextcheckin;
} else {
div.dataset.nextcheckin = element.lastseen + 1800;
}
div.style.opacity = '1';
$('#tag' + tagmac + ' .lastseen').style.color = "black";
div.classList.remove("tagpending");
div.dataset.lastseen = element.lastseen;
div.dataset.wakeupreason = element.wakeupReason;
$('#tag' + tagmac + ' .warningicon').style.display = 'none';
$('#tag' + tagmac).style.background = "#ffffff";
if (element.contentMode == 12) $('#tag' + tagmac).style.background = "#e4e4e0";
switch (parseInt(element.wakeupReason)) {
case WAKEUP_REASON_TIMED:
break;
case WAKEUP_REASON_BOOT:
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "<font color=yellow>First boot</font>"
$('#tag' + tagmac).style.background = "#b0d0b0";
break;
case WAKEUP_REASON_GPIO:
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "GPIO wakeup"
$('#tag' + tagmac).style.background = "#c8f1bb";
break;
case WAKEUP_REASON_NFC:
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "NFC wakeup"
break;
case WAKEUP_REASON_FIRSTBOOT:
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "<font color=yellow>First boot</font>"
$('#tag' + tagmac).style.background = "#b0d0b0";
break;
case WAKEUP_REASON_NETWORK_SCAN:
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "<font color=yellow>Network scan</font>"
$('#tag' + tagmac).style.background = "#c0c0d0";
break;
case WAKEUP_REASON_WDT_RESET:
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "Watchdog reset!"
$('#tag' + tagmac).style.background = "#d0a0a0";
break;
}
$('#tag' + tagmac + ' .pendingicon').style.display = (element.pending ? 'inline-block' : 'none');
div.classList.add("tagflash");
(function (tagmac) {
setTimeout(function () { $('#tag' + tagmac).classList.remove("tagflash"); }, 1400);
})(tagmac);
if (element.pending) div.classList.add("tagpending");
}
}
function updatecards() {
$('#taglist').querySelectorAll('[data-mac]').forEach(item => {
let tagmac = item.dataset.mac;
if (item.dataset.lastseen && item.dataset.lastseen > 1672531200) {
let idletime = (Date.now() / 1000) - servertimediff - item.dataset.lastseen;
$('#tag' + tagmac + ' .lastseen').innerHTML = "<span>last seen</span>" + displayTime(Math.floor(idletime)) + " ago";
if ((Date.now() / 1000) - servertimediff - 600 > item.dataset.nextcheckin) {
$('#tag' + tagmac + ' .warningicon').style.display = 'inline-block';
$('#tag' + tagmac).classList.remove("tagpending")
$('#tag' + tagmac).style.background = '#e0e0a0';
}
if (idletime > 24 * 3600) {
$('#tag' + tagmac).style.opacity = '.5';
$('#tag' + tagmac + ' .lastseen').style.color = "red";
}
} else {
if ($('#tag' + tagmac + ' .lastseen')) {
$('#tag' + tagmac + ' .lastseen').innerHTML = ""
} else {
console.log(tagmac + " not found")
}
}
if (item.dataset.nextcheckin > 1672531200 && parseInt(item.dataset.wakeupreason) == 0) {
let nextcheckin = item.dataset.nextcheckin - ((Date.now() / 1000) - servertimediff);
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "<span>expected checkin</span>" + displayTime(Math.floor(nextcheckin));
}
})
}
$('#clearlog').onclick = function () {
$('#messages').innerHTML = '';
}
document.querySelectorAll('.closebtn').forEach(button => {
button.addEventListener('click', (event) => {
event.target.parentNode.style.display = 'none';
$('#advancedoptions').style.height = '0px';
});
});
//clicking on a tag: load config dialog for tag
$('#taglist').addEventListener("click", (event) => {
let currentElement = event.target;
while (currentElement !== $('#taglist')) {
if (currentElement.classList.contains("tagcard")) {
break;
}
currentElement = currentElement.parentNode;
}
if (!currentElement.classList.contains("tagcard")) {
return;
}
const mac = currentElement.dataset.mac;
$('#cfgmac').innerHTML = mac;
$('#cfgmac').dataset.mac = mac;
fetch("/get_db?mac=" + mac)
.then(response => response.json())
.then(data => {
var tagdata = data.tags[0];
$('#cfgalias').value = tagdata.alias;
$('#cfgmore').style.display = "none";
if (populateSelectTag(tagdata.hwType, tagdata.capabilities)) {
$('#cfgcontent').parentNode.style.display = "flex";
$('#cfgcontent').value = tagdata.contentMode;
$('#cfgcontent').dataset.json = tagdata.modecfgjson;
contentselected();
if (tagdata.contentMode != 12) $('#cfgmore').style.display = 'block';
} else {
$('#customoptions').innerHTML = "";
$('#cfgcontent').parentNode.style.display = "none";
}
$('#cfgrotate').value = tagdata.rotate;
$('#cfglut').value = tagdata.lut;
$('#cfgmore').innerHTML = '&#x1f783;';
$('#configbox').style.display = 'block';
})
})
$('#cfgmore').onclick = function () {
$('#cfgmore').innerHTML = $('#advancedoptions').style.height == '0px' ? '&#x1f781;' : '&#x1f783;';
$('#advancedoptions').style.height = $('#advancedoptions').style.height == '0px' ? $('#advancedoptions').scrollHeight + 'px' : '0px';
};
$('#cfgsave').onclick = function () {
let contentMode = $('#cfgcontent').value;
let contentDef = getContentDefById(contentMode);
let extraoptions = contentDef?.param ?? null;
let obj = {};
let formData = new FormData();
formData.append("mac", $('#cfgmac').dataset.mac);
formData.append("alias", $('#cfgalias').value);
if (contentMode) {
extraoptions.forEach(element => {
if ($('#opt' + element.key)) {
obj[element.key] = $('#opt' + element.key).value;
}
});
formData.append("contentmode", contentMode);
formData.append("modecfgjson", JSON.stringify(obj));
} else {
formData.append("contentmode", "0");
formData.append("modecfgjson", String());
}
formData.append("rotate", $('#cfgrotate').value);
formData.append("lut", $('#cfglut').value);
fetch("/save_cfg", {
method: "POST",
body: formData
})
.then(response => response.text())
.then(data => showMessage(data))
.catch(error => showMessage('Error: ' + error));
$('#advancedoptions').style.height = '0px';
$('#configbox').style.display = 'none';
}
function sendCmd(mac, cmd) {
let formData = new FormData();
formData.append("mac", mac);
formData.append("cmd", cmd);
fetch("/tag_cmd", {
method: "POST",
body: formData
})
.then(response => response.text())
.then(data => {
var div = $('#tag' + $('#cfgmac').dataset.mac);
if (cmd == "del") div.remove();
showMessage(data);
})
.catch(error => showMessage('Error: ' + error));
$('#advancedoptions').style.height = '0px';
$('#configbox').style.display = 'none';
}
$('#cfgdelete').onclick = function () {
sendCmd($('#cfgmac').dataset.mac, "del");
}
$('#cfgclrpending').onclick = function () {
sendCmd($('#cfgmac').dataset.mac, "clear");
}
$('#cfgrefresh').onclick = function () {
sendCmd($('#cfgmac').dataset.mac, "refresh");
}
$('#cfgtagreboot').onclick = function () {
sendCmd($('#cfgmac').dataset.mac, "reboot");
}
$('#cfgscan').onclick = function () {
sendCmd($('#cfgmac').dataset.mac, "scan");
}
$('#cfgreset').onclick = function () {
sendCmd($('#cfgmac').dataset.mac, "reset");
}
$('#rebootbutton').onclick = function () {
showMessage("rebooting AP....", true);
fetch("/reboot", {
method: "POST"
});
socket.close();
}
$('#apconfigbutton').onclick = function () {
var table = document.getElementById("aptable");
var rowCount = table.rows.length;
for (var i = rowCount - 1; i > 0; i--) {
table.deleteRow(i);
}
fetch("/get_ap_config")
.then(response => response.json())
.then(data => {
$('#apcfgalias').value = data.alias;
$('#apcfgchid').value = data.channel;
$("#apcfgledbrightness").value = data.led;
$("#apcfglanguage").value = data.language;
$("#apclatency").value = data.maxsleep;
$("#apcpreventsleep").value = data.stopsleep;
})
$('#apconfigbox').style.display = 'block'
}
$('#apcfgsave').onclick = function () {
let formData = new FormData();
formData.append("alias", $('#apcfgalias').value);
formData.append("channel", $('#apcfgchid').value);
formData.append('led', $('#apcfgledbrightness').value);
formData.append('language', $('#apcfglanguage').value);
formData.append('maxsleep', $('#apclatency').value);
formData.append('stopsleep', $('#apcpreventsleep').value);
fetch("/save_apcfg", {
method: "POST",
body: formData
})
.then(response => response.text())
.then(data => showMessage(data))
.catch(error => showMessage('Error: ' + error));
$(".logo").innerHTML = $('#apcfgalias').value;
$('#apconfigbox').style.display = 'none';
}
$('#updatebutton').onclick = function () {
$('#apconfigbox').style.display = 'none';
$('#apupdatebox').style.display = 'block';
loadOTA();
}
async function loadOTA() {
otamodule = await import('./ota.js?v=' + Date.now());
otamodule.initUpdate();
}
$('#paintbutton').onclick = function () {
if (paintShow) {
paintShow = false;
$('#cfgsave').parentNode.style.display = 'block';
contentselected();
} else {
paintShow = true;
$('#cfgsave').parentNode.style.display = 'none';
$('#customoptions').innerHTML = "<div id=\"buttonbar\"></div><div id=\"canvasdiv\"></div><div id=\"layersdiv\"></div><p id=\"savebar\"></p>";
const mac = $('#cfgmac').dataset.mac
const hwtype = $('#tag' + mac).dataset.hwtype;
var [width, height] = displaySizeLookup[hwtype] || [0, 0];
if (height > width) [width, height] = [height, width];
if (paintLoaded) {
startPainter(mac, width, height);
} else {
loadScript('painter.js', function () {
startPainter(mac, width, height);
});
}
}
}
function loadScript(url, callback) {
var script = document.createElement('script');
script.src = url;
script.onload = function () {
if (callback) {
callback();
}
};
document.head.appendChild(script);
}
function contentselected() {
let contentMode = $('#cfgcontent').value;
$('#customoptions').innerHTML = "";
var obj = {};
if ($('#cfgcontent').dataset.json && ($('#cfgcontent').dataset.json != "null")) {
obj = JSON.parse($('#cfgcontent').dataset.json);
}
$('#paintbutton').style.display = 'none';
if (contentMode) {
let contentDef = getContentDefById(contentMode);
if (contentDef) {
$('#customoptions').innerHTML = "<p>" + contentDef?.desc + "</p>"
}
$('#paintbutton').style.display = (contentMode == 0 ? 'inline-block' : 'none');
let extraoptions = contentDef?.param ?? null;
extraoptions.forEach(element => {
var label = document.createElement("label");
label.innerHTML = element.name;
label.setAttribute("for", 'opt' + element.key);
if (element.desc) {
label.style.cursor = 'help';
label.title = element.desc;
}
var input = document.createElement("input");
switch (element.type) {
case 'text':
input.type = "text";
break;
case 'int':
input.type = "number";
break;
case 'ro':
input.type = "text";
input.disabled = true;
break;
case 'select':
input = document.createElement("select");
for (const key in element.options) {
const optionElement = document.createElement("option");
optionElement.value = key;
optionElement.text = element.options[key];
if (element.options[key].substring(0,1)=="-") {
optionElement.text = element.options[key].substring(1);
optionElement.selected = true;
} else {
optionElement.selected = false;
}
input.appendChild(optionElement);
}
break;
}
input.id = 'opt' + element.key;
input.title = element.desc;
if (obj[element.key]) input.value = obj[element.key];
var p = document.createElement("p");
p.appendChild(label);
p.appendChild(input);
$('#customoptions').appendChild(p);
});
}
paintShow = false;
$('#cfgsave').parentNode.style.display = 'block';
}
function populateSelectTag(hwtype, capabilities) {
var selectTag = $("#cfgcontent");
selectTag.innerHTML = "";
var optionsAdded = false;
var option;
cardconfig.forEach(item => {
var capcheck = item.capabilities ?? 0;
var hwtypeArray = item.hwtype;
if (hwtypeArray.includes(hwtype) && (capabilities & capcheck || capcheck == 0)) {
option = document.createElement("option");
option.value = item.id;
option.text = item.name;
selectTag.appendChild(option);
optionsAdded = true;
}
});
var rotateTag = $("#cfgrotate");
rotateTag.innerHTML = "";
for (let i = 0; i < 4; i++) {
if (i == 0 || displaySizeLookup[hwtype][2] == 4 || (i == 2 && displaySizeLookup[hwtype][2] == 2)) {
option = document.createElement("option");
option.value = i;
option.text = (i * 90) + " degrees";
rotateTag.appendChild(option);
}
}
var lutTag = $("#cfglut");
lutTag.innerHTML = "";
option = document.createElement("option");
option.value = "0";
option.text = "auto";
lutTag.appendChild(option);
if (hwtype != 240) {
option = document.createElement("option");
option.value = "1";
option.text = "Always full refresh";
lutTag.appendChild(option);
}
return optionsAdded;
}
function getContentDefById(id) {
if (id == null) return null;
var obj = cardconfig.find(item => item.id == id);
return obj ? obj : null;
}
function showMessage(message, iserr) {
const messages = $('#messages');
var date = new Date(),
time = date.toLocaleTimeString('nl-NL', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
if (iserr) {
messages.insertAdjacentHTML("afterbegin", '<li class="new error">' + htmlEncode(time + ' ' + message) + '</li>');
} else {
messages.insertAdjacentHTML("afterbegin", '<li class="new">' + htmlEncode(time + ' ' + message) + '</li>');
}
}
function htmlEncode(input) {
const textArea = document.createElement("textarea");
textArea.innerText = input;
return textArea.innerHTML.split("<br>").join("\n");
}
function loadImage(id, imageSrc) {
imageQueue.push({ id, imageSrc });
if (!isProcessing) {
processQueue();
}
}
function processQueue() {
if (imageQueue.length === 0) {
isProcessing = false;
return;
}
isProcessing = true;
const { id, imageSrc } = imageQueue.shift();
const canvas = $('#tag' + id + ' .tagimg');
canvas.style.display = 'block';
const hwtype = $('#tag' + id).dataset.hwtype;
fetch(imageSrc)
.then(response => response.arrayBuffer())
.then(buffer => {
[canvas.width, canvas.height] = displaySizeLookup[hwtype] || [0, 0];
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(canvas.width, canvas.height);
const data = new Uint8ClampedArray(buffer);
const offsetRed = (data.length >= (canvas.width * canvas.height / 8) * 2) ? canvas.width * canvas.height / 8 : 0;
var pixelValue = 0;
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < 8; j++) {
const pixelIndex = i * 8 + j;
if (offsetRed) {
pixelValue = ((data[i] & (1 << (7 - j))) ? 1 : 0) | (((data[i + offsetRed] & (1 << (7 - j))) ? 1 : 0) << 1);
} else {
pixelValue = ((data[i] & (1 << (7 - j))) ? 1 : 0);
}
imageData.data[pixelIndex * 4] = colorTable[pixelValue][0];
imageData.data[pixelIndex * 4 + 1] = colorTable[pixelValue][1];
imageData.data[pixelIndex * 4 + 2] = colorTable[pixelValue][2];
imageData.data[pixelIndex * 4 + 3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
processQueue();
})
.catch(error => {
processQueue();
});
}
function displayTime(seconds) {
let hours = Math.floor(Math.abs(seconds) / 3600);
let minutes = Math.floor((Math.abs(seconds) % 3600) / 60);
let remainingSeconds = Math.abs(seconds) % 60;
return (seconds < 0 ? '-' : '') + (hours > 0 ? `${hours}:${String(minutes).padStart(2, '0')}` : `${minutes}`) + `:${String(remainingSeconds).padStart(2, '0')}`;
}
function sortGrid() {
const sortableGrid = $('#taglist');
const gridItems = Array.from(sortableGrid.getElementsByClassName('tagcard'));
gridItems.sort((a, b) => {
const macA = a.querySelector('.alias').innerHTML;
const macB = b.querySelector('.alias').innerHTML;
if (macA < macB) return -1;
if (macA > macB) return 1;
return 0;
});
gridItems.forEach((item) => sortableGrid.appendChild(item));
}

View File

@@ -0,0 +1,441 @@
const repoUrl = 'https://api.github.com/repos/jjwbruijn/OpenEPaperLink/releases';
const $ = document.querySelector.bind(document);
let running = false;
let errors = 0;
let env = '', currentVer = '', currentBuildtime = 0;
let buttonState = false;
export async function initUpdate() {
if (!$("#updateconsole")) {
const consoleDiv = document.createElement('div');
consoleDiv.classList.add('console');
consoleDiv.id = "updateconsole";
$('#apupdatebox').appendChild(consoleDiv);
}
$("#updateconsole").innerHTML = "";
const response = await fetch("/version.txt");
let filesystemversion = await response.text();
if (!filesystemversion) filesystemversion = "unknown";
fetch("/sysinfo")
.then(response => {
if (response.status != 200) {
print("Error fetching sysinfo: " + response.status, "red");
if (response.status == 404) {
print("Your current firmware version is not yet capable of updating OTA.");
print("Update it manually one last time.");
disableButtons(true);
}
return "{}";
} else {
return response.json();
}
})
.then(data => {
if (data.env) {
let matchtest='';
if (data.buildversion != filesystemversion && filesystemversion != "custom" && data.buildversion != "custom") matchtest = " <- not matching!"
print(`env: ${data.env}`);
print(`build date: ${formatEpoch(data.buildtime)}`);
print(`esp32 version: ${data.buildversion}`);
print(`filesystem version: ${filesystemversion}` + matchtest);
print(`sha: ${data.sha}`);
print(`psram size: ${data.psramsize}`);
print(`flash size: ${data.flashsize}`);
print("--------------------------","gray");
env = data.env;
currentVer = data.buildversion;
currentBuildtime = data.buildtime;
if (data.rollback) $("#rollbackOption").style.display = 'block';
}
})
.catch(error => {
print('Error fetching sysinfo: ' + error, "red");
});
fetch(repoUrl)
.then(response => response.json())
.then(data => {
const releaseDetails = data.map(release => {
const assets = release.assets;
let fileUrl = null;
const filesJsonAsset = assets.find(asset => asset.name === 'files.json');
if (filesJsonAsset) {
fileUrl = filesJsonAsset.browser_download_url;
return {
html_url: release.html_url,
tag_name: release.tag_name,
name: release.name,
date: formatDateTime(release.published_at),
author: release.author.login,
file_url: fileUrl
}
};
});
const easyupdate = $('#easyupdate');
if (releaseDetails.length === 0) {
easyupdate.innerHTML = ("No releases found.");
} else {
const release = releaseDetails[0];
if (release && release.tag_name) {
if (release.tag_name == currentVer) {
easyupdate.innerHTML = `Version ${currentVer}. You are up to date`;
} else if (release.date < formatEpoch(currentBuildtime)) {
easyupdate.innerHTML = `Your version is newer than the latest release date.<br>Are you the developer? :-)`;
} else {
easyupdate.innerHTML = `An update from version ${currentVer} to version ${release.tag_name} is available.<button onclick="otamodule.updateAll('${release.file_url}','${release.tag_name}')">Update now!</button>`;
}
}
}
easyupdate.innerHTML += "<br><a onclick=\"$('#advanceddiv').style.display='block'\">advanced options</a>"
const table = document.createElement('table');
const tableHeader = document.createElement('tr');
tableHeader.innerHTML = '<th>Release</th><th>Date</th><th>Name</th><th colspan="2">Update:</th><th>Remark</th>';
table.appendChild(tableHeader);
releaseDetails.forEach(release => {
if (release && release.html_url) {
const tableRow = document.createElement('tr');
let tablerow = `<td><a href="${release.html_url}" target="_new">${release.tag_name}</a></td><td>${release.date}</td><td>${release.name}</td><td><button onclick="otamodule.updateESP('${release.file_url}', true)">ESP32</button></td><td><button onclick="otamodule.updateWebpage('${release.file_url}','${release.tag_name}', true)">Filesystem</button></td>`;
if (release.tag_name == currentVer) {
tablerow += "<td>current version</td>";
} else if (release.date < formatEpoch(currentBuildtime)) {
tablerow += "<td>older</td>";
} else {
tablerow += "<td>newer</td>";
}
tableRow.innerHTML = tablerow;
table.appendChild(tableRow);
}
});
$('#releasetable').innerHTML = "";
$('#releasetable').appendChild(table);
disableButtons(buttonState);
})
.catch(error => {
print('Error fetching releases:' + error, "red");
});
}
export function updateAll(fileUrl, tagname) {
updateWebpage(fileUrl, tagname, false)
.then(() => {
updateESP(fileUrl, false);
})
.catch(error => {
console.error(error);
});
}
export async function updateWebpage(fileUrl, tagname, showReload) {
return new Promise((resolve, reject) => {
(async function () {
try {
if (running) return;
if (showReload) {
if (!confirm("Confirm updating the filesystem")) return;
} else {
if (!confirm("Confirm updating the esp32 and filesystem")) return;
}
disableButtons(true);
running = true;
errors = 0;
const consoleDiv = document.getElementById('updateconsole');
consoleDiv.scrollTop = consoleDiv.scrollHeight;
print("Updating littleFS partition...");
try {
const response = await fetch("/update_actions", {
method: "POST",
body: ''
});
if (response.ok) {
const data = await response.text();
} else {
print(`error performing update actions: ${response.status}`, "red");
errors++;
}
} catch (error) {
console.error(`error calling update actions:` + error, "red");
errors++;
}
fetch("/getexturl?url=" + fileUrl)
.then(response => response.json())
.then(data => {
checkfiles(data.files);
})
.catch(error => {
print('Error fetching data:' + error, "red");
});
const checkfiles = async (files) => {
for (const file of files) {
try {
const url = "/check_file?path=" + encodeURIComponent(file.path);
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
if (data.filesize == file.size && data.md5 == file.md5) {
print(`file ${file.path} is up to date`, "green");
} else if (data.filesize == 0) {
await fetchAndPost(file.url, file.name, file.path);
} else {
await fetchAndPost(file.url, file.name, file.path);
}
} else {
print(`error checking file ${file.path}: ${response.status}`, "red");
errors++;
}
} catch (error) {
console.error(`error checking file ${file.path}:` + error, "red");
errors++;
}
}
writeVersion(tagname, "version.txt", "/www/version.txt")
running = false;
if (errors) {
print("------", "gray");
print(`Finished updating with ${errors} errors.`, "red");
reject(error);
} else {
print("------", "gray");
print("Update succesful.");
resolve();
}
disableButtons(false);
if (showReload) {
const newLine = document.createElement('div');
newLine.innerHTML = "<button onclick=\"location.reload()\">Reload this page</button>";
consoleDiv.appendChild(newLine);
consoleDiv.scrollTop = consoleDiv.scrollHeight;
}
};
} catch (error) {
print('Error: ' + error, "red");
errors++;
reject(error);
}
})();
});
}
export async function updateESP(fileUrl, showConfirm) {
if (running) return;
if (showConfirm) {
if (!confirm("Confirm updating the esp32")) return;
}
disableButtons(true);
running = true;
errors = 0;
const consoleDiv = document.getElementById('updateconsole');
consoleDiv.scrollTop = consoleDiv.scrollHeight;
print("Updating firmware...");
let binurl, binmd5, binsize;
let retryCount = 0;
const maxRetries = 5;
while (retryCount < maxRetries) {
try {
const response = await fetch("/getexturl?url=" + fileUrl);
if (!response.ok) {
throw new Error("Network response was not OK");
}
const responseBody = await response.text();
if (responseBody.trim()[0] !== "{") {
throw new Error("Failed to fetch the release info file");
}
const data = JSON.parse(responseBody);
const file = data.binaries?.find((entry) => entry.name == env + '.bin');
if (file) {
binurl = file.url;
binmd5 = file.md5;
binsize = file.size;
console.log(`URL for "${file.name}": ${binurl}`);
try {
const response = await fetch('/update_ota', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
url: binurl,
md5: binmd5,
size: binsize
})
});
if (response.ok) {
const result = await response.text();
print('OTA update initiated.');
} else {
print('Failed to initiate OTA update: ' + response.status, "red");
}
} catch (error) {
print('Error during OTA update: ' + error, "red");
}
break;
} else {
print(`No info about "${env}" found in the release.`, "red");
}
} catch (error) {
print('Error: ' + error.message, "yellow");
retryCount++;
print(`Retrying... attempt ${retryCount}`);
await new Promise((resolve) => setTimeout(resolve, 3000));
}
}
if (retryCount === maxRetries) {
print("Reached maximum retry count. Failed to execute the update.", "red");
}
running = false;
disableButtons(false);
}
$('#rollbackBtn').onclick = function () {
if (running) return;
disableButtons(true);
running = true;
errors = 0;
const consoleDiv = document.getElementById('updateconsole');
consoleDiv.scrollTop = consoleDiv.scrollHeight;
print("Rolling back...");
fetch("/rollback", {
method: "POST",
body: ''
})
running = false;
disableButtons(false);
}
export function print(line, color = "white") {
const consoleDiv = document.getElementById('updateconsole');
if (consoleDiv) {
const isScrolledToBottom = consoleDiv.scrollHeight - consoleDiv.clientHeight <= consoleDiv.scrollTop;
const newLine = document.createElement('div');
newLine.style.color = color;
if (line == "[reboot]") {
newLine.innerHTML = "<button onclick=\"otamodule.reboot()\">Reboot</button>";
} else {
newLine.textContent = line;
}
consoleDiv.appendChild(newLine);
if (isScrolledToBottom) {
consoleDiv.scrollTop = consoleDiv.scrollHeight;
}
}
}
export function reboot() {
print("Rebooting now... Reloading webpage in 5 seconds...", "yellow");
fetch("/reboot",{method: "POST"});
setTimeout(() => {
location.reload();
}, 5000);
}
function formatEpoch(epochTime) {
const date = new Date(epochTime * 1000); // Convert seconds to milliseconds
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-based
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
function formatDateTime(utcDateString) {
const localTimeZoneOffset = new Date().getTimezoneOffset();
const date = new Date(utcDateString);
date.setMinutes(date.getMinutes() - localTimeZoneOffset);
const year = date.getUTCFullYear();
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
const day = String(date.getUTCDate()).padStart(2, '0');
const hours = String(date.getUTCHours()).padStart(2, '0');
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}`;
return formattedDate;
}
const fetchAndPost = async (url, name, path) => {
try {
print("updating " + path);
const response = await fetch(url);
const fileContent = await response.blob();
const formData = new FormData();
formData.append('path', path);
formData.append('file', fileContent, name);
const uploadResponse = await fetch('/littlefs_put', {
method: 'POST',
body: formData
});
if (!uploadResponse.ok) {
print(`${response.status} ${response.body}`, "red");
errors++;
}
} catch (error) {
print('error: ' + error, "red");
errors++;
}
};
const writeVersion = async (content, name, path) => {
try {
print("uploading " + path);
const formData = new FormData();
formData.append('path', path);
const blob = new Blob([content]);
formData.append('file', blob, name);
const uploadResponse = await fetch('/littlefs_put', {
method: 'POST',
body: formData
});
if (!uploadResponse.ok) {
print(`${response.status} ${response.body}`, "red");
errors++;
}
} catch (error) {
print('error: ' + error, "red");
errors++;
}
};
function disableButtons(active) {
$("#apupdatebox").querySelectorAll('button').forEach(button => {
button.disabled = active;
});
buttonState = active;
}

View File

@@ -0,0 +1,360 @@
function startPainter(mac, width, height) {
let isDrawing = false;
let lastX = 0;
let lastY = 0;
let color = 'black';
let linewidth = 3;
let cursor = 'auto';
let isAddingText = false;
let layerDiv, intervalId, showCursor, input, textX, textY, font, sizeSelect, isDragging;
var fonts = ['Roboto', 'Open Sans', 'Lato', 'Montserrat', 'PT Sans', 'Barlow Condensed', 'Headland One', 'Sofia Sans Extra Condensed', 'Mynerve', 'Lilita One', 'Passion One', 'Big Shoulders Display'];
loadGoogleFonts(fonts);
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.imageSmoothingEnabled = false;
$("#canvasdiv").appendChild(canvas);
canvas.style.imageRendering = 'pixelated';
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('touchstart', startDrawing, { passive: true });
canvas.addEventListener('touchend', stopDrawing);
canvas.addEventListener('touchmove', draw, { passive: true });
const bgCanvas = document.createElement('canvas');
bgCanvas.width = canvas.width;
bgCanvas.height = canvas.height;
const bgCtx = bgCanvas.getContext('2d');
bgCtx.fillStyle = '#ffffff';
bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
const txtButton = document.createElement('button');
txtButton.innerHTML = 'tT';
txtButton.style.fontStyle = 'italic';
txtButton.addEventListener('click', addText);
const blackButton = document.createElement('button');
blackButton.innerHTML = '&#65103;&#128396';
blackButton.style.color = 'black';
blackButton.addEventListener('click', () => {
color = 'black';
linewidth = 3;
cursor = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><circle cx=\'2\' cy=\'2\' r=\'2\' opacity=\'0.5\'/></svg>") 2 2, auto';
blackButton.classList.add('active');
redButton.classList.remove('active');
whiteButton.classList.remove('active');
});
blackButton.classList.add('active');
const redButton = document.createElement('button');
redButton.innerHTML = '&#65103;&#128396';
redButton.style.color = 'red';
redButton.addEventListener('click', () => {
color = 'red';
linewidth = 3;
cursor = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><circle cx=\'2\' cy=\'2\' r=\'2\' opacity=\'0.5\'/></svg>") 2 2, auto';
blackButton.classList.remove('active');
redButton.classList.add('active');
whiteButton.classList.remove('active');
});
const whiteButton = document.createElement('button');
whiteButton.innerHTML = '&#11044;';
whiteButton.style.color = 'white';
whiteButton.addEventListener('click', () => {
color = 'white';
linewidth = 20;
cursor = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><circle cx=\'10\' cy=\'10\' r=\'10\' opacity=\'0.5\'/></svg>") 10 10, auto';
blackButton.classList.remove('active');
redButton.classList.remove('active');
whiteButton.classList.add('active');
});
const clearButton = document.createElement('button');
clearButton.innerHTML = '&#128437;';
clearButton.addEventListener('click', () => {
if (isAddingText) handleFinish(false);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
});
const uploadButton = document.createElement('button');
uploadButton.innerHTML = 'Upload';
uploadButton.addEventListener('click', () => {
if (isAddingText) handleFinish(true);
const dataURL = canvas.toDataURL('image/jpeg');
const binaryImage = dataURLToBlob(dataURL);
const formData = new FormData();
formData.append('mac', mac);
formData.append('dither', '0');
formData.append('file', binaryImage, 'image.jpg');
const xhr = new XMLHttpRequest();
xhr.open('POST', '/imgupload');
xhr.send(formData);
$('#configbox').style.display = 'none';
});
$("#buttonbar").appendChild(blackButton);
$("#buttonbar").appendChild(redButton);
$("#buttonbar").appendChild(whiteButton);
$("#buttonbar").appendChild(txtButton);
$("#buttonbar").appendChild(clearButton);
$("#savebar").appendChild(uploadButton);
canvas.addEventListener('mouseenter', function () {
if (!isAddingText) {
canvas.style.cursor = cursor;
} else {
canvas.style.cursor = 'move';
}
});
canvas.addEventListener('mouseleave', function () {
canvas.style.cursor = 'auto';
});
function startDrawing(e) {
if (isAddingText) return;
isDrawing = true;
var rect = canvas.getBoundingClientRect();
lastX = e.pageX - rect.left - window.pageXOffset;
lastY = e.pageY - rect.top - window.pageYOffset;
}
function stopDrawing() {
if (isAddingText) return;
isDrawing = false;
}
function draw(e) {
if (isAddingText) return;
if (!isDrawing) return;
var rect = canvas.getBoundingClientRect();
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(e.pageX - rect.left - window.pageXOffset, e.pageY - rect.top - window.pageYOffset);
ctx.strokeStyle = color;
ctx.lineWidth = linewidth;
ctx.lineCap = "round";
ctx.stroke();
lastX = e.pageX - rect.left - window.pageXOffset;
lastY = e.pageY - rect.top - window.pageYOffset;
}
function addText() {
if (isAddingText) {
handleFinish(true);
return;
}
txtButton.classList.add('active');
bgCtx.drawImage(canvas, 0, 0);
const defaultX = 5;
const defaultY = 40;
isDragging = false;
let startX, startY;
showCursor = true;
textX = defaultX;
textY = defaultY;
font = '24px ' + fonts[0];
input = document.createElement('textarea');
input.type = 'text';
input.placeholder = 'Type text here';
input.style.opacity = '0';
input.style.position = 'absolute';
input.style.left = '-200px'
input.addEventListener('input', () => {
drawText(input.value, textX, textY);
});
input.addEventListener('keyup', () => {
input.selectionStart = input.selectionEnd = input.value.length;
});
input.addEventListener('blur', function () {
input.focus();
});
intervalId = setInterval(function () {
showCursor = !showCursor;
drawText(input.value, textX, textY);
}, 300);
canvas.addEventListener('mouseup', handleMouseUp);
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('touchstart', handleTouchStart, { passive: true });
canvas.addEventListener('touchend', handleTouchEnd);
canvas.addEventListener('touchmove', handleTouchMove, { passive: true });
var sizes = [10,11,12,13,14,16,18,20,24,28,32,36,40,48,56,64,72,84];
const fontSelect = document.createElement('select');
fontSelect.id = 'font-select';
for (var i = 0; i < fonts.length; i++) {
const option = document.createElement('option');
option.value = fonts[i];
option.text = fonts[i];
option.style.fontFamily = fonts[i];
fontSelect.appendChild(option);
}
sizeSelect = document.createElement('select');
sizeSelect.id = 'size-select';
for (var i = 0; i < sizes.length; i++) {
const option = document.createElement('option');
option.value = sizes[i];
option.text = sizes[i] + ' px';
sizeSelect.appendChild(option);
}
function updateFont() {
var selectedFont = fontSelect.value;
var selectedSize = sizeSelect.value;
fontSelect.style.fontFamily = selectedFont;
font = selectedSize + 'px ' + selectedFont;
drawText(input.value, textX, textY);
}
fontSelect.value = fonts[0];
sizeSelect.value = '24';
fontSelect.addEventListener('change', updateFont);
sizeSelect.addEventListener('change', updateFont);
const finishButton = document.createElement('button');
finishButton.innerHTML = '&#10004;';
finishButton.addEventListener('click', clickHandleFinish);
layerDiv = document.createElement('div');
layerDiv.appendChild(input);
layerDiv.appendChild(fontSelect);
layerDiv.appendChild(sizeSelect);
layerDiv.appendChild(finishButton);
$("#layersdiv").appendChild(layerDiv);
input.focus();
isAddingText = true;
//cursor = 'move';
blackButton.innerHTML = 'aA'
redButton.innerHTML = 'aA'
if (color=='white') {
whiteButton.classList.remove('active');
blackButton.classList.add('active');
color='black';
}
}
function handleFinish(apply) {
canvas.removeEventListener('mousedown', handleMouseDown);
canvas.removeEventListener('mouseup', handleMouseUp);
canvas.removeEventListener('mousemove', handleMouseMove);
canvas.removeEventListener('touchstart', handleTouchStart);
canvas.removeEventListener('touchend', handleTouchEnd);
canvas.removeEventListener('touchmove', handleTouchMove);
isAddingText = false;
cursor = 'auto';
layerDiv.remove();
clearInterval(intervalId);
showCursor = false;
if (apply) drawText(input.value, textX, textY);
txtButton.classList.remove('active');
blackButton.innerHTML = '&#65103;&#128396'
redButton.innerHTML = '&#65103;&#128396'
}
function drawText(text, x, y) {
ctx.drawImage(bgCanvas, 0, 0);
ctx.save();
ctx.translate(x, y);
ctx.font = font;
ctx.fillStyle = color;
const lines = text.split('\n');
lines.forEach((line, index) => {
ctx.fillText(line + (showCursor && index === lines.length - 1 ? '|' : ''), 0, index * (sizeSelect.value * 1.1));
});
ctx.restore();
}
function handleMouseDown(e) {
isDragging = true;
startX = textX;
startY = textY;
({ clientX: lastMouseX, clientY: lastMouseY } = e);
}
function handleMouseMove(e) {
if (isDragging) {
const { clientX, clientY } = e;
textX = startX + clientX - lastMouseX;
textY = startY + clientY - lastMouseY;
drawText(input.value, textX, textY);
}
}
function handleTouchStart(e) {
isDragging = true;
startX = textX;
startY = textY;
({ clientX: lastTouchX, clientY: lastTouchY } = e.touches[0]);
}
function handleTouchMove(e) {
if (isDragging) {
const { clientX, clientY } = e.touches[0];
textX = startX + clientX - lastTouchX;
textY = startY + clientY - lastTouchY;
drawText(input.value, textX, textY);
}
}
function handleMouseUp(e) {
isDragging = false;
}
function handleTouchEnd(e) {
isDragging = false;
}
function clickHandleFinish() {
handleFinish(true);
}
}
function loadGoogleFonts(fonts) {
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://fonts.googleapis.com/css?family=' + fonts.join('|');
document.head.appendChild(link);
}
function dataURLToBlob(dataURL) {
const byteString = atob(dataURL.split(',')[1]);
const mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0];
const arrayBuffer = new ArrayBuffer(byteString.length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
uint8Array[i] = byteString.charCodeAt(i);
}
return new Blob([arrayBuffer], { type: mimeString });
}
paintLoaded = true;

View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Image Upload Form</title>
</head>
<body>
<h3>demo upload form</h3>
<p>You can use this as an example how to push images to a tag by an external server/script.</p>
<p>
<form method="POST" enctype="multipart/form-data" action="/imgupload">
<p>
<label for="mac">Enter a 6 or 8 byte MAC address:</label><br>
<input type="text" id="mac" name="mac">
</p>
<p>
<label for="dither">Floyd Steinberg dithering (0=off, 1=on)</label><br>
<input type="text" id="dither" name="dither">
</p>
<p>
<label for="image">Select an image to upload. Must have the correct resolution for the tag (rotating is allowed):</label><br>
<input type="file" id="image" name="file">
</p>
<p>
<input type="submit" value="Upload">
</p>
</form>
</p>
</body>
</html>

View File

@@ -0,0 +1,9 @@
dependencies:
idf:
component_hash: null
source:
type: idf
version: 4.4.4
manifest_hash: dcf4d39b94252de130019eadceb989d72b0dbc26b552cfdcbb50f6da531d2b92
target: esp32s3
version: 1.0.0

View File

@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x150000,
app1, app, ota_1, 0x160000,0x150000,
spiffs, data, spiffs, 0x2B0000,0x140000,
coredump, data, coredump,0x3F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x150000
5 app1 app ota_1 0x160000 0x150000
6 spiffs data spiffs 0x2B0000 0x140000
7 coredump data coredump 0x3F0000 0x10000

View File

@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x150000,
app1, app, ota_1, 0x160000,0x150000,
spiffs, data, spiffs, 0x2B0000,0x140000,
coredump, data, coredump,0x3F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x150000
5 app1 app ota_1 0x160000 0x150000
6 spiffs data spiffs 0x2B0000 0x140000
7 coredump data coredump 0x3F0000 0x10000

View File

@@ -0,0 +1,24 @@
#ifndef SPIFFSEditor_H_
#define SPIFFSEditor_H_
#include <ESPAsyncWebServer.h>
class SPIFFSEditor: public AsyncWebHandler {
private:
fs::FS _fs;
String _username;
String _password;
bool _authenticated;
uint32_t _startTime;
public:
#ifdef ESP32
SPIFFSEditor(const fs::FS& fs, const String& username=String(), const String& password=String());
#else
SPIFFSEditor(const String& username=String(), const String& password=String(), const fs::FS& fs=SPIFFS);
#endif
virtual bool canHandle(AsyncWebServerRequest *request) override final;
virtual void handleRequest(AsyncWebServerRequest *request) override final;
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final;
virtual bool isRequestHandlerTrivial() override final {return false;}
};
#endif

View File

@@ -0,0 +1,129 @@
#include <Arduino.h>
#pragma pack(push, 1)
#include "../../tag_types.h"
struct espBlockRequest {
uint8_t checksum;
uint64_t ver;
uint8_t blockId;
uint8_t src[8];
} __packed;
struct espXferComplete {
uint8_t checksum;
uint8_t src[8];
} __packed;
struct espSetChannelPower {
uint8_t checksum;
uint8_t channel;
uint8_t power;
} __packed;
struct blockData {
uint16_t size;
uint16_t checksum;
uint8_t data[];
} __packed;
struct AvailDataReq {
uint8_t checksum;
uint8_t lastPacketLQI;
int8_t lastPacketRSSI;
int8_t temperature;
uint16_t batteryMv;
uint8_t hwType;
uint8_t wakeupReason;
uint8_t capabilities;
uint16_t tagSoftwareVersion;
uint8_t currentChannel;
uint8_t customMode;
uint8_t reserved[8];
} __packed;
struct espAvailDataReq {
uint8_t checksum;
uint8_t src[8];
struct AvailDataReq adr;
} __packed;
#define EPD_LUT_DEFAULT 0
#define EPD_LUT_NO_REPEATS 1
#define EPD_LUT_FAST_NO_REDS 2
#define EPD_LUT_FAST 3
#define EPD_LUT_OTA 0x10
struct AvailDataInfo {
uint8_t checksum;
uint64_t dataVer; // MD5 of potential traffic
uint32_t dataSize;
uint8_t dataType; // allows for 16 different datatypes
uint8_t dataTypeArgument; // extra specification or instruction for the tag (LUT to be used for drawing image)
uint16_t nextCheckIn; // when should the tag check-in again? Measured in minutes
} __packed;
struct pendingData {
struct AvailDataInfo availdatainfo;
uint16_t attemptsLeft;
uint8_t targetMac[8];
} __packed;
#define BLOCK_DATA_SIZE 4096
#define BLOCK_XFER_BUFFER_SIZE BLOCK_DATA_SIZE + sizeof(struct blockData)
#define PKT_AVAIL_DATA_REQ 0xE5
#define PKT_AVAIL_DATA_INFO 0xE6
#define PKT_XFER_COMPLETE 0xEA
#define PKT_XFER_TIMEOUT 0xED
#define PKT_CANCEL_XFER 0xEC
#define PKT_APLIST_REQ 0x80
#define PKT_APLIST_REPLY 0x81
#define PKT_TAGINFO 0x82
struct APlist {
uint32_t src;
char alias[32];
uint8_t channelId;
uint8_t tagCount;
uint16_t version;
} __packed;
#define SYNC_NOSYNC 0
#define SYNC_USERCFG 1
#define SYNC_TAGSTATUS 2
#define SYNC_DELETE 3
#define SYNC_VERSION 0xAA00
struct TagInfo {
uint16_t structVersion = SYNC_VERSION;
uint8_t mac[8];
uint8_t syncMode;
char alias[32];
uint32_t lastseen;
uint32_t nextupdate;
bool pending;
uint32_t expectedNextCheckin;
uint8_t hwType;
uint8_t wakeupReason;
uint8_t capabilities;
uint16_t pendingIdle;
uint8_t contentMode;
} __packed;
struct tagsettings {
uint8_t settingsVer; // the version of the struct as written to the infopage
uint8_t enableFastBoot; // default 0; if set, it will skip splashscreen
uint8_t enableRFWake; // default 0; if set, it will enable RF wake. This will add about ~0.9µA idle power consumption
uint8_t enableTagRoaming; // default 0; if set, the tag will scan for an accesspoint every few check-ins. This will increase power consumption quite a bit
uint8_t enableScanForAPAfterTimeout; // default 1; if a the tag failed to check in, after a few attempts it will try to find a an AP on other channels
uint8_t enableLowBatSymbol; // default 1; tag will show 'low battery' icon on screen if the battery is depleted
uint8_t enableNoRFSymbol; // default 1; tag will show 'no signal' icon on screen if it failed to check in for a longer period of time
uint8_t fastBootCapabilities; // holds the byte with 'capabilities' as detected during a normal tag boot; allows the tag to skip detecting buttons and NFC chip
uint8_t customMode; // default 0; if anything else, tag will bootup in a different 'mode'
uint16_t batLowVoltage; // Low battery threshold voltage (2450 for 2.45v). defaults to BATTERY_VOLTAGE_MINIMUM from powermgt.h
uint16_t minimumCheckInTime; // defaults to BASE_INTERVAL from powermgt.h
uint8_t fixedChannel; // default 0; if set to a valid channel number, the tag will stick to that channel
} __packed;
#pragma pack(pop)

View File

@@ -0,0 +1,42 @@
#include <Arduino.h>
#include <LittleFS.h>
#include <TFT_eSPI.h>
#include "U8g2_for_TFT_eSPI.h"
#include "makeimage.h"
#include "tag_db.h"
struct contentTypes {
uint16_t id;
String name;
uint16_t tagTypes;
void (*functionname)();
String description;
String optionList;
};
void contentRunner();
void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo);
bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin, tagRecord *&taginfo, imgParam &imageParams);
void drawString(TFT_eSprite &spr, String content, uint16_t posx, uint16_t posy, String font, byte align = 0, uint16_t color = TFT_BLACK);
void initSprite(TFT_eSprite &spr, int w, int h, imgParam &imageParams);
void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams);
void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord *&taginfo, imgParam &imageParams);
void drawWeather(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams);
void drawForecast(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams);
void drawIdentify(String &filename, tagRecord *&taginfo, imgParam &imageParams);
int getImgURL(String &filename, String URL, time_t fetched, imgParam &imageParams, String MAC);
bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams);
bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams);
void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginfo, imgParam &imageParams);
void drawBuienradar(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams);
char *formatHttpDate(time_t t);
String urlEncode(const char *msg);
int windSpeedToBeaufort(float windSpeed);
String windDirectionIcon(int degrees);
void getLocation(JsonObject &cfgobj);
void prepareNFCReq(uint8_t* dst, const char* url);
void prepareLUTreq(uint8_t *dst, String input);
void prepareConfigFile(uint8_t *dst, JsonObject config);
void getTemplate(JsonDocument &json, const char *filePath, uint8_t id, uint8_t hwtype);
void setU8G2Font(const String &title, U8g2_for_TFT_eSPI &u8f);

View File

@@ -0,0 +1,13 @@
#include <Arduino.h>
uint16_t getAPUpdateVersion(uint8_t type);
bool checkForcedAPFlash();
bool doForcedAPFlash();
bool doAPFlash();
bool doAPUpdate(uint8_t type);
void flashCountDown(uint8_t c);
#ifdef OPENEPAPERLINK_PCB
bool extTagConnected();
bool doTagFlash();
#endif

View File

@@ -0,0 +1,33 @@
#pragma once
#include <Arduino.h>
static int defaultLanguage = 0;
static String languageList[] = {"EN - English", "NL - Nederlands", "DE - Deutsch"};
/*EN English language section*/
static String languageEnDaysShort[] = {"SU", "MO", "TU", "WE", "TH", "FR", "SA"};
static String languageEnDays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
static String languageEnMonth[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
/*END English language section END*/
/*NL Dutch language section*/
static String languageNlDaysShort[] = {"ZO", "MA", "DI", "WO", "DO", "VR", "ZA"};
static String languageNlDays[] = {"zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"};
static String languageNlMonth[] = {"januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"};
/*END Dutch language section END*/
/*DE German language section*/
static String languageDeDaysShort[] = {"SO", "MO", "DI", "MI", "DO", "FR", "SA"};
static String languageDeDays[] = {"Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"};
static String languageDeMonth[] = {"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"};
/*END German language section END*/
static String* languageDaysShort[] = {languageEnDaysShort, languageNlDaysShort, languageDeDaysShort};
static String* languageDays[] = {languageEnDays, languageNlDays, languageDeDays};
static String* languageMonth[] = {languageEnMonth, languageNlMonth, languageDeMonth};
void updateLanguageFromConfig();
int getDefaultLanguage();
int getCurrentLanguage();

View File

@@ -0,0 +1,19 @@
#include <Arduino.h>
#ifdef HAS_RGB_LED
#define FASTLED_INTERNAL
#include <FastLED.h>
#endif
void ledTask(void* parameter);
void setBrightness(int brightness);
void updateBrightnessFromConfig();
#ifdef HAS_RGB_LED
void shortBlink(CRGB cname);
void showColorPattern(CRGB colorone, CRGB colortwo, CRGB colorthree);
void rgbIdle();
void addFadeColor(CRGB cname);
#endif
void quickBlink(uint8_t repeat);

View File

@@ -0,0 +1,20 @@
#include <Arduino.h>
#include <TFT_eSPI.h>
#pragma once
struct imgParam {
bool hasRed;
uint8_t dataType;
bool dither;
bool grayLut = false;
uint8_t bpp = 8;
uint8_t rotate = 0;
char segments[12];
uint16_t symbols;
bool invert;
};
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams);
void jpg2buffer(String filein, String fileout, imgParam &imageParams);

View File

@@ -0,0 +1,22 @@
#include <Arduino.h>
extern void addCRC(void* p, uint8_t len);
extern bool checkCRC(void* p, uint8_t len);
extern void processBlockRequest(struct espBlockRequest* br);
extern void prepareIdleReq(uint8_t* dst, uint16_t nextCheckin);
extern void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, uint8_t* dst);
extern bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t nextCheckin);
extern void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP);
extern void processXferComplete(struct espXferComplete* xfc, bool local);
extern void processXferTimeout(struct espXferComplete* xfc, bool local);
extern void processDataReq(struct espAvailDataReq* adr, bool local);
extern bool sendTagCommand(uint8_t* dst, uint8_t cmd, bool local);
extern bool sendAPSegmentedData(uint8_t* dst, String data, uint16_t icons, bool inverted, bool local);
extern bool showAPSegmentedInfo(uint8_t* dst, bool local);
extern void updateTaginfoitem(struct TagInfo* taginfoitem);
void refreshAllPending();
void updateContent(uint8_t* dst);
void setAPchannel();

View File

@@ -0,0 +1,13 @@
#include <Arduino.h>
#include "web.h"
void handleSysinfoRequest(AsyncWebServerRequest* request);
void handleCheckFile(AsyncWebServerRequest* request);
void handleGetExtUrl(AsyncWebServerRequest* request);
void handleLittleFSUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final);
void handleUpdateOTA(AsyncWebServerRequest* request);
void firmwareUpdateTask(void* parameter);
void updateFirmware(const char* url, const char* expectedMd5, size_t size);
void handleRollback(AsyncWebServerRequest* request);
void handleUpdateActions(AsyncWebServerRequest* request);

View File

@@ -0,0 +1,5 @@
#include <Arduino.h>
//void doLeds();
void powerControl(bool powerState, uint8_t* pin, uint8_t pincount);

View File

@@ -0,0 +1,35 @@
#include <Arduino.h>
extern struct espSetChannelPower curChannel;
#define AP_STATE_OFFLINE 0
#define AP_STATE_ONLINE 1
#define AP_STATE_FLASHING 2
#define AP_STATE_WAIT_RESET 3
#define AP_STATE_REQUIRED_POWER_CYCLE 4
#define AP_STATE_FAILED 5
#define AP_STATE_COMING_ONLINE 6
struct APInfoS {
bool isOnline = false;
uint8_t state = AP_STATE_OFFLINE;
uint8_t type;
uint16_t version = 0;
uint8_t channel;
uint8_t mac[8];
uint8_t power;
uint8_t pending;
uint8_t nop;
};
extern struct APInfoS apInfo;
void APTask(void* parameter);
bool sendCancelPending(struct pendingData* pending);
bool sendDataAvail(struct pendingData* pending);
bool sendPing();
void APEnterEarlyReset();
bool sendChannelPower(struct espSetChannelPower* scp);

View File

@@ -0,0 +1,8 @@
#include <Arduino.h>
extern QueueHandle_t consoleCmdQueue;
extern TaskHandle_t consoleTaskHandle;
void consoleStopTask();
void consoleTask(void* parameter);
void consoleUartHandler(uint8_t* data, uint8_t len);

View File

@@ -0,0 +1,16 @@
#include <Arduino.h>
#define FLASHER_AP_PORT 0
#ifdef OPENEPAPERLINK_PCB
#define FLASHER_EXT_PORT 1
#define FLASHER_ALTRADIO_PORT 2
#endif
// Which port we should use for the AP process
// (useful for testing, can only be FLASHER_AP_PORT for any other board than the OpenEPaperLinkPCB board)
#define AP_PROCESS_PORT FLASHER_AP_PORT
// flasher options
#define CUSTOM_MAC_HDR 0x0000
#define MAX_WRITE_ATTEMPTS 5

View File

@@ -0,0 +1,39 @@
#ifndef _DYN_STORAGE_H_
#define _DYN_STORAGE_H_
#include "FS.h"
#ifdef HAS_SDCARD
#ifndef SD_CARD_SS
#error SD_CARD_SS UNDEFINED
#endif
#ifndef SD_CARD_CLK
#define SD_CARD_CLK 18
#endif
#ifndef SD_CARD_MISO
#define SD_CARD_MISO 19
#endif
#ifndef SD_CARD_MOSI
#define SD_CARD_MOSI 23
#endif
#endif
class DynStorage {
public:
DynStorage();
void begin();
void end();
void listFiles();
size_t freeSpace();
private:
bool isInited;
};
extern DynStorage Storage;
extern fs::FS *contentFS;
#endif

View File

@@ -0,0 +1,14 @@
#include <Arduino.h>
#define WAKEUP_REASON_TIMED 0
#define WAKEUP_REASON_BOOT 1
#define WAKEUP_REASON_GPIO 2
#define WAKEUP_REASON_NFC 3
#define WAKEUP_REASON_FIRSTBOOT 0xFC
#define WAKEUP_REASON_NETWORK_SCAN 0xFD
#define WAKEUP_REASON_WDT_RESET 0xFE
void init_time();
void logLine(char* buffer);
void logLine(String text);
void logStartUp();

View File

@@ -0,0 +1,85 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#include <vector>
#pragma pack(push, 1)
#pragma once
#define WAKEUP_REASON_TIMED 0
#define WAKEUP_REASON_BOOTUP 1
#define WAKEUP_REASON_GPIO 2
#define WAKEUP_REASON_NFC 3
#define RUNSTATUS_STOP 0
#define RUNSTATUS_PAUSE 1
#define RUNSTATUS_RUN 2
#define RUNSTATUS_INIT 3
class tagRecord {
public:
tagRecord() : mac{0}, alias(""), lastseen(0), nextupdate(0), contentMode(0), pending(false), md5{0}, md5pending{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0), isExternal(false), pendingIdle(0), hasCustomLUT(false), rotate(0), lut(0), tagSoftwareVersion(0), currentChannel(0),
dataType(0), filename(""), data(nullptr), len(0) {}
uint8_t mac[8];
String alias;
uint32_t lastseen;
uint32_t nextupdate;
uint8_t contentMode;
bool pending;
uint8_t md5[16];
uint8_t md5pending[16];
uint32_t expectedNextCheckin;
String modeConfigJson;
uint8_t LQI;
int8_t RSSI;
int8_t temperature;
uint16_t batteryMv;
uint8_t hwType;
uint8_t wakeupReason;
uint8_t capabilities;
uint32_t lastfullupdate;
bool isExternal;
uint16_t pendingIdle;
bool hasCustomLUT;
uint8_t rotate;
uint8_t lut;
uint16_t tagSoftwareVersion;
uint8_t currentChannel;
uint8_t dataType;
String filename;
uint8_t* data;
uint32_t len;
static tagRecord* findByMAC(uint8_t mac[8]);
};
struct Config {
uint8_t channel;
char alias[32];
int16_t led;
uint8_t language;
uint8_t maxsleep;
uint8_t stopsleep;
uint8_t runStatus;
};
// extern SemaphoreHandle_t tagDBOwner;
extern Config config;
extern std::vector<tagRecord*> tagDB;
extern DynamicJsonDocument APconfig;
String tagDBtoJson(uint8_t mac[8] = nullptr, uint8_t startPos = 0);
bool deleteRecord(uint8_t mac[8]);
void fillNode(JsonObject &tag, tagRecord* &taginfo);
void saveDB(String filename);
void loadDB(String filename);
void destroyDB();
uint8_t getTagCount();
void mac2hex(uint8_t* mac, char* hexBuffer);
bool hex2mac(const String& hexString, uint8_t* mac);
void clearPending(tagRecord* taginfo);
void initAPconfig();
void saveAPconfig();
#pragma pack(pop)

View File

@@ -0,0 +1,26 @@
#include <Arduino.h>
#include "AsyncUDP.h"
#ifndef defudpcomm
#define defudpcomm
class UDPcomm {
public:
UDPcomm();
~UDPcomm();
void init();
void getAPList();
void netProcessDataReq(struct espAvailDataReq* eadr);
void netProcessXferComplete(struct espXferComplete* xfc);
void netProcessXferTimeout(struct espXferComplete* xfc);
void netSendDataAvail(struct pendingData* pending);
void netTaginfo(struct TagInfo* taginfoitem);
private:
AsyncUDP udp;
void processPacket(AsyncUDPPacket packet);
};
#endif
void init_udp();

View File

@@ -0,0 +1,4 @@
#include <Arduino.h>
void usbFlasherTask(void* parameter);

View File

@@ -1,5 +1,5 @@
#include<Arduino.h>
#include <Arduino.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
@@ -9,11 +9,12 @@ void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index
extern void webSocketSendProcess(void *parameter);
void wsLog(String text);
void wsErr(String text);
void wsSendTaginfo(uint8_t mac[6]);
void wsSendTaginfo(uint8_t *mac, uint8_t syncMode);
void wsSendSysteminfo();
void wsSendAPitem(struct APlist* apitem);
void wsSerial(String text);
uint8_t wsClientCount();
extern uint64_t swap64(uint64_t x);
extern AsyncWebSocket ws; //("/ws");
extern AsyncWebSocket ws;
extern SemaphoreHandle_t wsMutex;
extern TaskHandle_t websocketUpdater;

View File

@@ -1,15 +1,16 @@
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <stdint.h>
#include <SPI.h>
/* Autor: Aaron Christophel ATCnetz.de */
#include <Arduino.h>
void simplePowerOn();
class ZBS_interface {
public:
uint8_t begin();
class ZBS_interface
{
public:
uint8_t begin(uint8_t SS, uint8_t CLK, uint8_t MOSI, uint8_t MISO, uint8_t RESET, uint8_t* POWER, uint8_t powerPins, uint32_t spi_speed = 8000000);
void setSpeed(uint32_t speed);
void set_power(uint8_t state);
void enable_debug();
void reset();
@@ -27,17 +28,24 @@ class ZBS_interface {
uint8_t select_flash(uint8_t page);
void erase_flash();
void erase_infoblock();
~ZBS_interface();
private:
private:
SPIClass *spi = NULL;
SPISettings spiSettings;
uint8_t _SS_PIN = -1;
uint8_t _CLK_PIN = -1;
uint8_t _MOSI_PIN = -1;
uint8_t _MISO_PIN = -1;
uint8_t _RESET_PIN = -1;
uint8_t _POWER_PIN = -1;
uint8_t* _POWER_PIN = nullptr;
uint8_t _POWER_PINS = 1;
int ZBS_spi_delay = 1;
uint8_t spi_ready = 0;
uint32_t after_byte_delay = 10;
typedef enum {
typedef enum
{
ZBS_CMD_W_RAM = 0x02,
ZBS_CMD_R_RAM = 0x03,
ZBS_CMD_W_FLASH = 0x08,
@@ -48,10 +56,9 @@ class ZBS_interface {
ZBS_CMD_ERASE_INFOBLOCK = 0x48,
} ZBS_CMD_LIST;
typedef enum {
typedef enum
{
ZBS_ON = 1,
ZBS_OFF = 0,
} ZBS_POWER_STATE;
};
extern ZBS_interface zbs;

View File

@@ -0,0 +1,254 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env]
platform = espressif32
framework = arduino
lib_deps =
https://github.com/me-no-dev/ESPAsyncWebServer
https://github.com/tzapu/WiFiManager.git#feature_asyncwebserver
bblanchon/ArduinoJson
bodmer/TFT_eSPI
https://github.com/Bodmer/TJpg_Decoder.git
https://github.com/garretlab/shoddyxml2
https://github.com/Bodmer/U8g2_for_TFT_eSPI
https://github.com/ricmoo/qrcode
fastled/FastLED
https://github.com/MajenkoLibraries/SoftSPI
platform_packages =
board_build.filesystem = littlefs
monitor_filters = esp32_exception_decoder
monitor_speed = 115200
board_build.f_cpu = 240000000L
build_flags =
-D BUILD_ENV_NAME=$PIOENV
-D BUILD_TIME=$UNIX_TIME
-D USER_SETUP_LOADED
-D DISABLE_ALL_LIBRARY_WARNINGS
-D ILI9341_DRIVER
-D SMOOTH_FONT
; ----------------------------------------------------------------------------------------
; !!! this configuration expects the Mini_AP
;
; ----------------------------------------------------------------------------------------
[env:OpenEPaperLink_Mini_AP]
platform = https://github.com/platformio/platform-espressif32.git
board=lolin_s2_mini
board_build.partitions = default.csv
build_unflags =
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags =
${env.build_flags}
-D OPENEPAPERLINK_MINI_AP_PCB
-D ARDUINO_USB_MODE=0
-D CONFIG_SPIRAM_USE_MALLOC=1
-D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
-D HAS_RGB_LED
-D BOARD_HAS_PSRAM
-D POWER_NO_SOFT_POWER
-D FLASHER_AP_SS=11
-D FLASHER_AP_CLK=9
-D FLASHER_AP_MOSI=10
-D FLASHER_AP_MISO=8
-D FLASHER_AP_RESET=13
-D FLASHER_AP_POWER={-1} ;this board has no soft power control
-D FLASHER_AP_TXD=7
-D FLASHER_AP_RXD=6
-D FLASHER_AP_TEST=12
-D FLASHER_LED=15
-D FLASHER_RGB_LED=33
build_src_filter =
+<*>-<usbflasher.cpp>-<serialconsole.cpp>
board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680
board_upload.flash_size = 4MB
; ----------------------------------------------------------------------------------------
; !!! this configuration expects the Nano_AP
;
; ----------------------------------------------------------------------------------------
[env:OpenEPaperLink_Nano_AP]
platform = https://github.com/platformio/platform-espressif32.git
board=lolin_s2_mini
board_build.partitions = default.csv
build_unflags =
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags =
${env.build_flags}
-D OPENEPAPERLINK_NANO_AP_PCB
-D ARDUINO_USB_MODE=0
-D CONFIG_SPIRAM_USE_MALLOC=1
-D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
-D BOARD_HAS_PSRAM
-D FLASHER_AP_SS=38
-D FLASHER_AP_CLK=40
-D FLASHER_AP_MOSI=39
-D FLASHER_AP_MISO=33
-D FLASHER_AP_RESET=37
-D FLASHER_AP_POWER={16,17,18,21}
-D FLASHER_AP_TXD=35
-D FLASHER_AP_RXD=34
-D FLASHER_AP_TEST=36
-D FLASHER_LED=15
-D FLASHER_RGB_LED=-1
build_src_filter =
+<*>-<usbflasher.cpp>-<serialconsole.cpp>
board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680
board_upload.flash_size = 4MB
; ----------------------------------------------------------------------------------------
; !!! this configuration expects the 16MB Flash / 8MB Ram version of the ESP32-S3-DevkitC1
;
; ----------------------------------------------------------------------------------------
[env:OpenEPaperLink_AP_and_Flasher]
platform = https://github.com/platformio/platform-espressif32.git
board = esp32-s3-devkitc-1
board_build.partitions = default_16MB.csv
build_unflags =
-D ARDUINO_USB_MODE=1
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags =
${env.build_flags}
-D OPENEPAPERLINK_PCB
-D ARDUINO_USB_MODE=0
-D CONFIG_ESP32S3_SPIRAM_SUPPORT=1
-D CONFIG_SPIRAM_USE_MALLOC=1
-D HAS_USB
-D HAS_RGB_LED
-D BOARD_HAS_PSRAM
-D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
-D POWER_RAMPING
-D POWER_HIGH_SIDE_DRIVER
-D FLASHER_AP_SS=4
-D FLASHER_AP_CLK=5
-D FLASHER_AP_MOSI=7
-D FLASHER_AP_MISO=6
-D FLASHER_AP_RESET=15
-D FLASHER_AP_POWER={0}
-D FLASHER_AP_TXD=16
-D FLASHER_AP_RXD=18
-D FLASHER_AP_TEST=17
-D FLASHER_EXT_SS=40
-D FLASHER_EXT_CLK=41
-D FLASHER_EXT_MOSI=2
-D FLASHER_EXT_MISO=42
-D FLASHER_EXT_RESET=1
-D FLASHER_EXT_POWER={8}
-D FLASHER_EXT_TXD=38
-D FLASHER_EXT_RXD=39
-D FLASHER_EXT_TEST=47
-D FLASHER_ALT_SS=3
-D FLASHER_ALT_CLK=46
-D FLASHER_ALT_MOSI=10
-D FLASHER_ALT_MISO=9
-D FLASHER_ALT_RESET=11
-D FLASHER_ALT_POWER={-1}
-D FLASHER_ALT_TXD=12
-D FLASHER_ALT_RXD=14
-D FLASHER_ALT_TEST=13
-D FLASHER_LED=21
-D FLASHER_RGB_LED=48
board_build.flash_mode=qio
board_build.arduino.memory_type = qio_opi
board_build.psram_type=qspi_opi
board_upload.maximum_size = 16777216
board_upload.maximum_ram_size = 327680
board_upload.flash_size = 16MB
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an esp32
;
; ----------------------------------------------------------------------------------------
[env:Simple_AP]
board = esp32dev
board_build.partitions = default.csv
build_flags =
${env.build_flags}
-D CORE_DEBUG_LEVEL=0
-D SIMPLE_AP
-D FLASHER_AP_SS=5
-D FLASHER_AP_CLK=18
-D FLASHER_AP_MOSI=23
-D FLASHER_AP_MISO=19
-D FLASHER_AP_RESET=2
-D FLASHER_AP_POWER={13,15}
-D FLASHER_AP_TEST=-1
-D FLASHER_AP_TXD=17
-D FLASHER_AP_RXD=16
-D FLASHER_LED=22
build_src_filter =
+<*>-<usbflasher.cpp>-<serialconsole.cpp>
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an wemos_d1_mini32
;
; ----------------------------------------------------------------------------------------
[env:Wemos_d1_mini32_AP]
board = wemos_d1_mini32
board_build.partitions = default.csv
build_flags =
${env.build_flags}
-D CORE_DEBUG_LEVEL=0
-D POWER_NO_SOFT_POWER
-D FLASHER_AP_SS=5
-D FLASHER_AP_CLK=18
-D FLASHER_AP_MOSI=23
-D FLASHER_AP_MISO=19
-D FLASHER_AP_RESET=14
-D FLASHER_AP_POWER={-1}
-D FLASHER_AP_TEST=-1
-D FLASHER_AP_TXD=16
-D FLASHER_AP_RXD=17
-D FLASHER_LED=22
build_src_filter =
+<*>-<usbflasher.cpp>-<serialconsole.cpp>
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an m5stack esp32
;
; ----------------------------------------------------------------------------------------
[env:M5Stack_Core_ONE_AP]
platform = espressif32
board = m5stack-core-esp32
board_build.partitions = esp32_sdcard.csv
build_flags =
${env.build_flags}
-D CORE_DEBUG_LEVEL=0
-D POWER_NO_SOFT_POWER
-D HAS_SDCARD
-D USE_SOFTSPI
-D SD_CARD_SS=4
-D SD_CARD_CLK=18
-D SD_CARD_MISO=19
-D SD_CARD_MOSI=23
-D FLASHER_AP_SS=5
-D FLASHER_AP_CLK=36
-D FLASHER_AP_MOSI=26
-D FLASHER_AP_MISO=35
-D FLASHER_AP_RESET=2
-D FLASHER_AP_POWER={-1}
-D FLASHER_AP_TEST=-1
-D FLASHER_AP_TXD=16
-D FLASHER_AP_RXD=17
-D FLASHER_LED=-1
-D FLASH_TIMEOUT=10
-D USER_SETUP_LOADED
-D DISABLE_ALL_LIBRARY_WARNINGS
-D ILI9341_DRIVER
-D SMOOTH_FONT
build_src_filter =
+<*>-<usbflasher.cpp>-<serialconsole.cpp>

19
ESP32_AP-Flasher/push_ota.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
source updateRemote.sh > /dev/null
if [ -z "$IP" ]
then
echo "ERROR: Empty IP variable"
exit 1
fi
if [ -z "$PIOENV" ]
then
echo "ERROR: Empty PIOENV variable"
exit 1
fi
md5sum .pio/build/$PIOENV/firmware.bin | tee .pio/build/$PIOENV/firmware.md5
upload_file .pio/build/$PIOENV/firmware.md5:ota_md5.txt
upload_file .pio/build/$PIOENV/firmware.bin:ota.bin

View File

@@ -0,0 +1,6 @@
# This file was automatically generated for projects
# without default 'CMakeLists.txt' file.
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*)
idf_component_register(SRCS ${app_sources})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,736 @@
#include "flasher.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include "storage.h"
#include "LittleFS.h"
#include <MD5Builder.h>
// #include <FS.h>
#include "leds.h"
#include "settings.h"
#include "time.h"
#include "zbs_interface.h"
#define FINGERPRINT_FLASH_SIZE 10240
#ifdef OPENEPAPERLINK_PCB
bool extTagConnected() {
// checks if the TEST (P1.0) pin on the ZBS243 will come up high. If it doesn't, there's probably a tag connected.
pinMode(FLASHER_EXT_TEST, INPUT_PULLDOWN);
vTaskDelay(5 / portTICK_PERIOD_MS);
pinMode(FLASHER_EXT_TEST, INPUT_PULLUP);
vTaskDelay(5 / portTICK_PERIOD_MS);
return !digitalRead(FLASHER_EXT_TEST);
}
#endif
void dump(uint8_t *a, uint16_t l) {
if (a == nullptr) {
Serial.print("Tried to dump the contents of a nullptr, this is probably not what you want.\n");
}
Serial.printf(" ");
#define ROWS 16
for (uint8_t c = 0; c < ROWS; c++) {
Serial.printf(" %02X", c);
}
Serial.printf("\n--------");
for (uint8_t c = 0; c < ROWS; c++) {
Serial.printf("---");
}
for (uint16_t c = 0; c < l; c++) {
if ((c % ROWS) == 0) {
Serial.printf("\n0x%04X | ", c);
}
Serial.printf("%02X ", a[c]);
}
Serial.printf("\n--------");
for (uint8_t c = 0; c < ROWS; c++) {
Serial.printf("---");
}
Serial.printf("\n");
}
int8_t powerPinsAP[] = FLASHER_AP_POWER;
int8_t pinsAP[] = {FLASHER_AP_CLK, FLASHER_AP_MISO, FLASHER_AP_MOSI, FLASHER_AP_RESET, FLASHER_AP_RXD, FLASHER_AP_SS, FLASHER_AP_TEST, FLASHER_AP_TXD};
#ifdef OPENEPAPERLINK_PCB
int8_t powerPinsExt[] = FLASHER_EXT_POWER;
int8_t powerPinsAlt[] = FLASHER_ALT_POWER;
uint8_t pinsExt[] = {FLASHER_EXT_CLK, FLASHER_EXT_MISO, FLASHER_EXT_MOSI, FLASHER_EXT_RESET, FLASHER_EXT_RXD, FLASHER_EXT_SS, FLASHER_EXT_TEST, FLASHER_EXT_TXD};
#endif
class flasher {
public:
class ZBS_interface *zbs = nullptr;
uint8_t md5[16] = {0};
char md5char[34];
uint8_t tagtype;
uint8_t *infoblock = nullptr;
// Infoblock structure:
// 0x00-0x0F - Calibration data
// 0x10-0x17 - MAC
// 0x19 - OpenEPaperLink Type
// 0x30 - Original firmware MD5
uint8_t mac[8] = {0};
uint8_t mac_format = 0;
uint16_t mac_suffix = 0;
uint16_t mac_offset = 0;
flasher();
~flasher();
bool connectTag(uint8_t port);
void getFirmwareMD5();
bool getFirmwareMac();
bool findTagByMD5();
bool findTagByType(uint8_t type);
bool getInfoBlockMD5();
bool getInfoBlockMac();
bool getInfoBlockType();
void getMacFromWiFi();
bool prepareInfoBlock();
bool backupFlash();
bool writeFlash(uint8_t *flashbuffer, uint16_t size);
bool writeFlashFromPack(String filename, uint8_t type);
bool writeFlashFromPackOffset(fs::File *file, uint16_t length);
bool readInfoBlock();
bool writeInfoBlock();
protected:
bool writeBlock256(uint16_t offset, uint8_t *flashbuffer);
void get_mac_format1();
void get_mac_format2();
};
flasher::flasher() {
zbs = new ZBS_interface;
Storage.end();
}
flasher::~flasher() {
delete zbs;
Storage.begin();
}
static uint8_t validatePowerPinCount(int8_t *powerPin, uint8_t pinCount) {
if (pinCount > 0) {
pinCount = powerPinsAP[0] != -1 ? pinCount : 0;
}
return pinCount;
}
#ifndef FLASHER_AP_SPEED
#define FLASHER_AP_SPEED 4000000
#endif
bool flasher::connectTag(uint8_t port) {
bool result;
uint8_t power_pins = 0;
switch (port) {
case 0:
power_pins = validatePowerPinCount(powerPinsAP, sizeof(powerPinsAP));
result = zbs->begin(FLASHER_AP_SS, FLASHER_AP_CLK, FLASHER_AP_MOSI, FLASHER_AP_MISO, FLASHER_AP_RESET, (uint8_t *)powerPinsAP, power_pins, FLASHER_AP_SPEED);
break;
#ifdef OPENEPAPERLINK_PCB
case 1:
power_pins = validatePowerPinCount(powerPinsExt, sizeof(powerPinsExt));
result = zbs->begin(FLASHER_EXT_SS, FLASHER_EXT_CLK, FLASHER_EXT_MOSI, FLASHER_EXT_MISO, FLASHER_EXT_RESET, (uint8_t *)powerPinsExt, power_pins, FLASHER_AP_SPEED);
break;
case 2:
power_pins = validatePowerPinCount(powerPinsAlt, sizeof(powerPinsAlt));
result = zbs->begin(FLASHER_ALT_SS, FLASHER_ALT_CLK, FLASHER_ALT_MOSI, FLASHER_ALT_MISO, FLASHER_ALT_RESET, (uint8_t *)powerPinsAlt, power_pins, FLASHER_AP_SPEED);
break;
#endif
default:
Serial.printf("Tried to connect to port %d, but this port isn't available. Some dev borked it up, probably Jelmer.\n", port);
return false;
}
if (!result) Serial.printf("I tried connecting to port %d, but I couldn't establish a link to the tag. That's all I know.\n", port);
return result;
}
void flasher::getFirmwareMD5() {
uint8_t *buffer = (uint8_t *)malloc(FINGERPRINT_FLASH_SIZE);
if (buffer == nullptr) {
Serial.print("couldn't malloc bytes for firmware MD5\n");
return;
}
zbs->select_flash(0);
for (uint16_t c = 0; c < FINGERPRINT_FLASH_SIZE; c++) {
buffer[c] = zbs->read_flash(c);
}
{
MD5Builder md5calc;
md5calc.begin();
md5calc.add(buffer, FINGERPRINT_FLASH_SIZE);
md5calc.calculate();
md5calc.getBytes(md5);
}
for (uint8_t c = 0; c < 16; c++) {
sprintf(md5char + (2 * c), "%02X", md5[c]);
}
Serial.printf("MD5=%s\n", md5char);
free(buffer);
}
bool flasher::getInfoBlockMac() {
if (!zbs->select_flash(1)) return false;
for (uint16_t c = 7; c < 8; c--) {
mac[7 - c] = zbs->read_flash(c + 0x10);
}
Serial.printf("Infopage mac=");
uint16_t macsum = 0;
for (uint8_t c = 0; c < 8; c++) {
macsum += mac[c];
Serial.printf("%02X", mac[c]);
}
Serial.printf("\n");
if (macsum == 0) return false;
if (macsum > 0x5F9) return false;
return true;
}
bool flasher::getInfoBlockMD5() {
if (!zbs->select_flash(1)) return false;
for (uint16_t c = 0; c < 16; c++) {
md5[c] = zbs->read_flash(c + 0x30);
}
uint16_t macsum = 0;
for (uint8_t c = 0; c < 16; c++) {
macsum += md5[c];
sprintf(md5char + (2 * c), "%02X", md5[c]);
}
Serial.printf("Infoblock MD5=%s\n", md5char);
if (macsum == 0) return false; // invalid mac
if (macsum > 0xF00) return false; // *probably* an invalid mac
return true;
}
bool flasher::getInfoBlockType() {
if (!zbs->select_flash(1)) return false;
tagtype = zbs->read_flash(0x19);
return true;
}
bool flasher::findTagByMD5() {
StaticJsonDocument<3000> doc;
DynamicJsonDocument APconfig(600);
fs::File readfile = contentFS->open("/tag_md5_db.json", "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {
for (JsonObject elem : doc.as<JsonArray>()) {
const char *jsonmd5 = elem["MD5"];
if (jsonmd5 != nullptr) {
if (strncmp(md5char, jsonmd5, 32) == 0) {
Serial.print("MD5 Matches > ");
const char *name = elem["name"];
Serial.println(name);
mac_suffix = strtoul(elem["mac_suffix"], 0, 16);
mac_format = elem["mac_format"];
mac_offset = elem["mac_offset"];
tagtype = elem["type"];
readfile.close();
return true;
}
}
}
Serial.print("Failed to find this tag's current firmware MD5 in the json database. If this tag is already OpenEpaperLink, this is to be expected.\n");
} else {
Serial.print("Failed to read json file\n");
}
readfile.close();
return false;
}
bool flasher::findTagByType(uint8_t type) {
StaticJsonDocument<3000> doc;
DynamicJsonDocument APconfig(600);
fs::File readfile = contentFS->open("/tag_md5_db.json", "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {
for (JsonObject elem : doc.as<JsonArray>()) {
if (elem["type"] != nullptr) {
uint8_t jtype = elem["type"];
if (jtype == type) {
Serial.print("Type Matches > ");
const char *name = elem["name"];
Serial.println(name);
const char *jsonmd5 = elem["MD5"];
for (uint8_t c = 0; c < 16; c++) {
uint32_t n = 0;
sscanf(jsonmd5 + (2 * c), "%02X", &n);
md5[c] = (uint8_t)n;
}
for (uint8_t c = 0; c < 16; c++) {
sprintf(md5char + (2 * c), "%02X", md5[c]);
}
mac_suffix = strtoul(elem["mac_suffix"], 0, 16);
mac_format = elem["mac_format"];
mac_offset = elem["mac_offset"];
tagtype = elem["type"];
readfile.close();
return true;
}
}
}
Serial.print("Failed to find this tag's type in the json database.\n");
} else {
Serial.print("Failed to read json file\n");
}
readfile.close();
return false;
}
bool flasher::getFirmwareMac() {
if (!mac_offset) return false;
switch (mac_format) {
case 1:
get_mac_format1();
break;
case 2:
get_mac_format2();
break;
default:
return false;
}
return true;
}
void flasher::getMacFromWiFi() {
mac[0] = 0x00;
mac[1] = 0x00;
esp_read_mac(mac + 2, ESP_MAC_WIFI_SOFTAP);
}
bool flasher::backupFlash() {
getFirmwareMD5();
if (!zbs->select_flash(0)) return false;
md5char[16] = 0x00;
fs::File backup = contentFS->open("/" + (String)md5char + "_backup.bin", "w", true);
for (uint32_t c = 0; c < 65535; c++) {
backup.write(zbs->read_flash(c));
}
backup.close();
return true;
}
// extract original mac from firmware (1.54" and 2.9") and make it 2 bytes longer based on info in settings.h
void flasher::get_mac_format1() {
zbs->select_flash(0);
for (uint8_t c = 0; c < 6; c++) {
mac[c + 2] = zbs->read_flash(mac_offset + c); // 0xFC06
}
mac[0] = (uint8_t)(CUSTOM_MAC_HDR >> 8);
mac[1] = (uint8_t)CUSTOM_MAC_HDR;
mac[6] = (uint8_t)(mac_suffix >> 8);
mac[7] = (uint8_t)(mac_suffix & 0xFF);
uint8_t xorchk = 0;
for (uint8_t c = 2; c < 8; c++) {
xorchk ^= (mac[c] & 0x0F);
xorchk ^= (mac[c] >> 4);
}
mac[7] |= xorchk;
}
// extract original mac from segmented tag
void flasher::get_mac_format2() {
zbs->select_flash(0);
for (uint8_t c = 0; c < 3; c++) {
mac[c] = zbs->read_flash(mac_offset + c); // 0x7802
}
for (uint8_t c = 3; c < 6; c++) {
mac[c] = zbs->read_flash(mac_offset + 2 + c);
}
uint16_t type = mac_suffix;
mac[6] = (uint8_t)(type >> 8);
mac[7] = (uint8_t)(type & 0xFF);
}
// erase flash and program from flash buffer
bool flasher::writeFlash(uint8_t *flashbuffer, uint16_t size) {
if (!zbs->select_flash(0)) return false;
zbs->erase_flash();
if (!zbs->select_flash(0)) return false;
Serial.printf("Starting flash, size=%d\n", size);
for (uint16_t c = 0; c < size; c++) {
if (flashbuffer[c] == 0xFF) goto flashWriteSuccess;
for (uint8_t i = 0; i < MAX_WRITE_ATTEMPTS; i++) {
zbs->write_flash(c, flashbuffer[c]);
if (zbs->read_flash(c) == flashbuffer[c]) {
goto flashWriteSuccess;
}
}
return false;
flashWriteSuccess:
if (c % 256 == 0) {
#ifdef HAS_RGB_LED
shortBlink(CRGB::Yellow);
#else
quickBlink(2);
#endif
Serial.printf("\rNow flashing, %d/%d ", c, size);
vTaskDelay(1 / portTICK_PERIOD_MS);
}
}
return true;
}
bool flasher::writeBlock256(uint16_t offset, uint8_t *flashbuffer) {
for (uint16_t c = 0; c < 256; c++) {
if (flashbuffer[c] == 0xFF) goto flashWriteSuccess;
for (uint8_t i = 0; i < MAX_WRITE_ATTEMPTS; i++) {
zbs->write_flash(offset + c, flashbuffer[c]);
if (zbs->read_flash(offset + c) == flashbuffer[c]) {
goto flashWriteSuccess;
}
}
return false;
flashWriteSuccess:
continue;
}
return true;
}
// get info from infoblock (eeprom flash, kinda)
bool flasher::readInfoBlock() {
if (!zbs->select_flash(1)) return false;
if (infoblock == nullptr) {
infoblock = (uint8_t *)malloc(1024);
if (infoblock == nullptr) return false;
}
for (uint16_t c = 0; c < 1024; c++) {
infoblock[c] = zbs->read_flash(c);
}
return true;
}
// write info to infoblock
bool flasher::writeInfoBlock() {
if (infoblock == nullptr) return false;
if (!zbs->select_flash(1)) return false;
zbs->erase_infoblock();
if (!zbs->select_flash(1)) return false;
// select info page
for (uint16_t c = 0; c < 1024; c++) {
if (infoblock[c] == 0xFF) goto ifBlockWriteSuccess;
for (uint8_t i = 0; i < MAX_WRITE_ATTEMPTS; i++) {
zbs->write_flash(c, infoblock[c]);
if (zbs->read_flash(c) == infoblock[c]) {
goto ifBlockWriteSuccess;
}
}
return false;
ifBlockWriteSuccess:
continue;
}
return true;
}
bool flasher::prepareInfoBlock() {
if (infoblock == nullptr) return false;
for (uint8_t c = 7; c < 8; c--) {
infoblock[0x10 + (7 - c)] = mac[c];
}
infoblock[0x19] = tagtype;
for (uint8_t c = 0; c < 16; c++) {
infoblock[0x30 + c] = md5[c];
}
return true;
}
bool flasher::writeFlashFromPackOffset(fs::File *file, uint16_t length) {
if (!zbs->select_flash(0)) return false;
zbs->erase_flash();
if (!zbs->select_flash(0)) return false;
Serial.printf("Starting flash, size=%d\n", length);
uint8_t *buf = (uint8_t *)malloc(256);
uint16_t offset = 0;
while (length) {
if (length > 256) {
file->read(buf, 256);
length -= 256;
} else {
file->read(buf, length);
length = 0;
}
#ifdef HAS_RGB_LED
shortBlink(CRGB::Yellow);
#else
quickBlink(2);
#endif
Serial.printf("\rFlashing, %d bytes left ", length);
bool res = writeBlock256(offset, buf);
offset += 256;
if (!res) {
Serial.printf("Failed writing block to tag, probably a hardware failure\n");
return false;
}
vTaskDelay(1 / portTICK_PERIOD_MS);
}
Serial.printf("\nFlashing done\n");
return true;
}
bool flasher::writeFlashFromPack(String filename, uint8_t type) {
StaticJsonDocument<512> doc;
DynamicJsonDocument APconfig(512);
fs::File readfile = contentFS->open(filename, "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {
for (JsonObject elem : doc.as<JsonArray>()) {
if (elem["type"] != nullptr) {
uint8_t jtype = elem["type"];
if (jtype == type) {
const char *name = elem["name"];
Serial.print("Flashing from FW pack: ");
Serial.println(name);
uint32_t offset = elem["offset"];
uint16_t length = elem["length"];
readfile.seek(offset);
bool result = writeFlashFromPackOffset(&readfile, length);
readfile.close();
return result;
}
}
}
Serial.print("Failed to find this tag's type in the FW pack database.\n");
} else {
Serial.print("Failed to read json header from FW pack\n");
}
readfile.close();
return false;
}
uint16_t getAPUpdateVersion(uint8_t type) {
StaticJsonDocument<512> doc;
DynamicJsonDocument APconfig(512);
fs::File readfile = contentFS->open("/AP_FW_Pack.bin", "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {
for (JsonObject elem : doc.as<JsonArray>()) {
if (elem["type"] != nullptr) {
uint8_t jtype = elem["type"];
if (jtype == type) {
const char *name = elem["name"];
uint32_t version = elem["version"];
Serial.printf("AP FW version %04X - %s found in FW pack\n", version, name);
readfile.close();
return version;
}
}
}
Serial.print("Failed to find this tag's type in the AP FW pack database.\n");
} else {
Serial.print("Failed to read json header from FW pack\n");
}
readfile.close();
return 0;
}
bool checkForcedAPFlash() {
return contentFS->exists("/AP_force_flash.bin");
}
bool doForcedAPFlash() {
class flasher *f = new flasher();
if (!f->connectTag(AP_PROCESS_PORT)) {
delete f;
return false;
}
// we're going to overwrite the contents of the tag, so if we haven't set the mac already, we can forget about it. We'll set the mac to the wifi mac
if (!f->getInfoBlockMac()) {
f->readInfoBlock();
f->getMacFromWiFi();
f->prepareInfoBlock();
f->writeInfoBlock();
}
fs::File readfile = contentFS->open("/AP_force_flash.bin", "r");
bool res = f->writeFlashFromPackOffset(&readfile, readfile.size());
#ifdef HAS_RGB_LED
if (res) addFadeColor(CRGB::Green);
if (!res) addFadeColor(CRGB::Red);
#endif
readfile.close();
if (res) contentFS->remove("/AP_force_flash.bin");
f->zbs->reset();
delete f;
return res;
}
bool doAPFlash() {
// This function expects a tag in stock configuration, to be used as an AP. It can also work with 'dead' AP's.
class flasher *f = new flasher();
if (!f->connectTag(AP_PROCESS_PORT)) {
Serial.printf("Sorry, failed to connect to this tag...\n");
delete f;
return false;
}
f->getFirmwareMD5();
if (f->findTagByMD5()) {
// fresh tag for AP
Serial.printf("Found an original fw tag, flashing it for use with OpenEPaperLink\n");
f->readInfoBlock();
f->getFirmwareMac();
f->prepareInfoBlock();
f->writeInfoBlock();
} else if (f->getInfoBlockMD5() && f->findTagByMD5()) {
// used tag, but recognized
} else {
// unknown tag, bailing out.
f->backupFlash();
Serial.printf("Found a tag, but don't know what to do with it. Consider flashing using a file called \"AP_force_flash.bin\"\n");
delete f;
return false;
}
bool res = f->writeFlashFromPack("/AP_FW_Pack.bin", f->tagtype);
f->zbs->reset();
delete f;
return res;
}
bool doAPUpdate(uint8_t type) {
// this function expects the tag to be already flashed with some version of the OpenEpaperLink Firmware, and that it correctly reported its type
class flasher *f = new flasher();
if (!f->connectTag(AP_PROCESS_PORT)) {
Serial.printf("Sorry, failed to connect to this tag...\n");
delete f;
return false;
}
f->readInfoBlock();
if (f->getInfoBlockMD5() && f->findTagByMD5()) {
// header (MD5) was correctly set. We'll use the type set there, instead of the provided 'type' in the argument
type = f->tagtype;
} else {
f->readInfoBlock();
// Couldn't recognize the firmware, maybe it was already used for OpenEPaperLink?
if (!f->getInfoBlockMac()) {
// infoblock mac was incorrectly configured, we skipped it in an earlier version. We'll consider it lost, and overwrite it with the wifi mac.
f->getMacFromWiFi();
}
// we'll try to update the MD5, searching for it by type.
f->findTagByType(type);
f->writeInfoBlock();
}
bool res = f->writeFlashFromPack("/AP_FW_Pack.bin", f->tagtype);
#ifdef HAS_RGB_LED
if (res) addFadeColor(CRGB::Green);
if (!res) addFadeColor(CRGB::Red);
#endif
if (res) f->zbs->reset();
delete f;
return res;
}
void flashCountDown(uint8_t c) {
Serial.printf("\r%d ", c);
for (c -= 1; c < 254; c--) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.printf("\r%d ", c);
}
}
void pinTest() {
uint8_t *pintest;
pintest = (uint8_t *)pinsAP;
for (uint8_t c = 0; c < 8; c++) {
if (pintest[c] != -1) {
pinMode(pintest[c], INPUT_PULLDOWN);
vTaskDelay(10 / portTICK_PERIOD_MS);
if (digitalRead(pintest[c])) {
Serial.printf("Pin %d failed to become low\n", c);
} else {
pinMode(pintest[c], INPUT_PULLUP);
bool pinChange = false;
uint16_t pinTime = 0;
for (uint16_t t = 0; t < 65535; t++) {
if (digitalRead(pintest[c])) {
pinChange = true;
pinTime = t;
break;
}
ets_delay_us(1);
}
if (pinChange) {
Serial.printf("Pin %d went high in %d µS\n", pintest[c], pinTime);
} else {
Serial.printf("Pin %d timeout becoming high\n", pintest[c]);
}
}
}
}
for (uint8_t c = 0; c < 8; c++) {
if (pintest[c] != -1) {
pinMode(pintest[c], INPUT_PULLDOWN);
}
}
}
#ifdef OPENEPAPERLINK_PCB
// perform device flash, save mac, everything
bool doTagFlash() {
class flasher *f = new flasher();
if (!f->connectTag(FLASHER_EXT_PORT)) {
Serial.printf("Sorry, failed to connect to this tag...\n");
return false;
}
f->getFirmwareMD5();
if (f->findTagByMD5()) {
// this tag currently contains original firmware, found its fingerprint
Serial.printf("Found original firmware tag, recognized its fingerprint (%s)\n", f->md5char);
f->readInfoBlock();
f->getFirmwareMac();
f->prepareInfoBlock();
f->writeInfoBlock();
f->writeFlashFromPack("/Tag_FW_Pack.bin", f->tagtype);
f->zbs->reset();
} else if (f->getInfoBlockMD5()) {
// did find an infoblock MD5 that looks valid
if (f->findTagByMD5()) {
// did find the md5 in the database
Serial.printf("Found an already-flashed tag, recognized its fingerprint (%s)\n", f->md5char);
f->getInfoBlockMac();
f->getInfoBlockType();
f->readInfoBlock();
f->writeFlashFromPack("/Tag_FW_Pack.bin", f->tagtype);
f->zbs->reset();
} else {
// couldn't find the md5 from the infoblock
Serial.printf("Found an already-flashed tag, but we couldn't find its fingerprint (%s) in the database\n", f->md5char);
return false;
}
} else {
// We couldn't recognize the tag from it's fingerprint...
Serial.printf("Found a tag but didn't recognize its fingerprint\n", f->md5char);
f->backupFlash();
Serial.printf("Saved this MD5 binary to filesystem\n");
}
delete f;
return false;
}
#endif

View File

@@ -0,0 +1,25 @@
#include "language.h"
#include <Arduino.h>
#include "settings.h"
#include "tag_db.h"
int currentLanguage = defaultLanguage;
void updateLanguageFromConfig() {
int tempLang = config.language;
if (tempLang < 0 || tempLang >= sizeof(languageList)) {
Serial.println("Language not supported");
return;
}
currentLanguage = tempLang;
}
int getDefaultLanguage() {
return defaultLanguage;
}
int getCurrentLanguage() {
return currentLanguage;
}

View File

@@ -0,0 +1,355 @@
#include <Arduino.h>
#ifdef HAS_RGB_LED
#define FASTLED_INTERNAL
#include <FastLED.h>
#endif
#include "settings.h"
#include "tag_db.h"
QueueHandle_t ledQueue;
int maxledbrightness = 255;
#ifdef HAS_RGB_LED
QueueHandle_t rgbLedQueue;
struct ledInstructionRGB {
CRGB ledColor;
uint16_t fadeTime;
uint16_t length;
bool reQueue = false;
};
CRGB leds[1];
volatile bool rgbQueueFlush = false;
#endif
struct ledInstruction {
uint16_t value;
uint16_t fadeTime;
uint16_t length;
bool reQueue = false;
};
const uint8_t PROGMEM gamma8[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 112, 114,
115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142,
144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175,
177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213,
215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255};
#ifdef HAS_RGB_LED
void addToRGBQueue(struct ledInstructionRGB* rgb, bool requeue) {
rgb->reQueue = requeue;
if (!rgbLedQueue) {
delete rgb;
return;
}
BaseType_t queuestatus = xQueueSend(rgbLedQueue, &rgb, 0);
if (queuestatus == pdFALSE) {
delete rgb;
}
}
void addFadeColor(CRGB cname) {
struct ledInstructionRGB* rgb = new struct ledInstructionRGB;
rgb->ledColor = cname;
rgb->fadeTime = 750;
rgb->length = 0;
addToRGBQueue(rgb, false);
}
void shortBlink(CRGB cname) {
struct ledInstructionRGB* rgb = new struct ledInstructionRGB;
rgb->ledColor = CRGB::Black;
rgb->fadeTime = 0;
rgb->length = 3;
addToRGBQueue(rgb, false);
rgb = new struct ledInstructionRGB;
rgb->ledColor = cname;
rgb->ledColor.maximizeBrightness(0x80);
rgb->fadeTime = 0;
rgb->length = 10;
addToRGBQueue(rgb, false);
rgb = new struct ledInstructionRGB;
rgb->ledColor = CRGB::Black;
rgb->fadeTime = 0;
rgb->length = 3;
addToRGBQueue(rgb, false);
}
void flushRGBQueue() {
rgbQueueFlush = true;
}
void rgbIdle() {
flushRGBQueue();
}
void showColorPattern(CRGB colorone, CRGB colortwo, CRGB colorthree) {
struct ledInstructionRGB* rgb = new struct ledInstructionRGB;
rgb->ledColor = CRGB::Black;
rgb->fadeTime = 0;
rgb->length = 600;
addToRGBQueue(rgb, true);
rgb = new struct ledInstructionRGB;
rgb->ledColor = colorone;
rgb->fadeTime = 0;
rgb->length = 120;
addToRGBQueue(rgb, true);
rgb = new struct ledInstructionRGB;
rgb->ledColor = CRGB::Black;
rgb->fadeTime = 0;
rgb->length = 200;
addToRGBQueue(rgb, true);
rgb = new struct ledInstructionRGB;
rgb->ledColor = colortwo;
rgb->fadeTime = 0;
rgb->length = 120;
addToRGBQueue(rgb, true);
rgb = new struct ledInstructionRGB;
rgb->ledColor = CRGB::Black;
rgb->fadeTime = 0;
rgb->length = 200;
addToRGBQueue(rgb, true);
rgb = new struct ledInstructionRGB;
rgb->ledColor = colorthree;
rgb->fadeTime = 0;
rgb->length = 120;
addToRGBQueue(rgb, true);
}
void showRGB() {
FastLED.show();
}
volatile CRGB rgbIdleColor = CRGB::Green;
volatile uint16_t rgbIdlePeriod = 800;
void rgbIdleStep() {
static bool dirUp = true;
static uint16_t step = 0;
if (dirUp) {
// up
step++;
if (step == rgbIdlePeriod) {
dirUp = false;
}
} else {
// down
step--;
if (step == 0) {
dirUp = true;
}
}
CRGB newvalue = blend(CRGB::Black, (const CRGB&)rgbIdleColor, gamma8[map(step, 0, rgbIdlePeriod, 0, 255)]);
if (newvalue != leds[0]) {
leds[0] = newvalue;
showRGB();
}
}
#endif
void setBrightness(int brightness) {
maxledbrightness = brightness;
#ifdef HAS_RGB_LED
FastLED.setBrightness(maxledbrightness);
#endif
}
void updateBrightnessFromConfig() {
if (config.led != 0) {
int newbrightness = config.led;
if (newbrightness < 0) newbrightness = 0;
if (newbrightness != maxledbrightness) {
setBrightness(newbrightness);
}
}
}
void addToMonoQueue(struct ledInstruction* mono) {
BaseType_t queuestatus = xQueueSend(ledQueue, &mono, 0);
if (queuestatus == pdFALSE) {
delete mono;
}
}
void addFadeMono(uint8_t value) {
struct ledInstruction* mono = new struct ledInstruction;
mono->value = value;
mono->fadeTime = 750;
mono->length = 0;
addToMonoQueue(mono);
}
void showMono(uint8_t brightness) {
ledcWrite(7, 255 - gamma8[brightness]);
}
void quickBlink(uint8_t repeat) {
for (int i = 0; i < repeat; i++) {
struct ledInstruction* mono = new struct ledInstruction;
mono->value = maxledbrightness;
mono->fadeTime = 120 / repeat;
mono->length = 0;
addToMonoQueue(mono);
mono = new struct ledInstruction;
mono->value = 0;
mono->fadeTime = 120 / repeat;
mono->length = 0;
addToMonoQueue(mono);
}
}
volatile uint16_t monoIdlePeriod = 900;
uint8_t monoValue = 0;
void monoIdleStep() {
static bool dirUp = true;
static uint16_t step = 0;
if (dirUp) {
// up
step++;
if (step == monoIdlePeriod) {
dirUp = false;
}
} else {
// down
step--;
if (step == 0) {
dirUp = true;
}
}
uint8_t newvalue = map(step, 0, monoIdlePeriod, 0, maxledbrightness);
if (newvalue != monoValue) {
monoValue = newvalue;
showMono(newvalue);
}
}
void ledTask(void* parameter) {
#ifdef HAS_RGB_LED
FastLED.addLeds<WS2812B, FLASHER_RGB_LED, GRB>(leds, 1); // GRB ordering is typical
leds[0] = CRGB::Blue;
showRGB();
rgbLedQueue = xQueueCreate(30, sizeof(struct ledInstructionRGB*));
struct ledInstructionRGB* rgb = nullptr;
// open with a nice RGB crossfade
addFadeColor(CRGB::Red);
addFadeColor(CRGB::Green);
addFadeColor(CRGB::Blue);
addFadeColor(CRGB::Red);
addFadeColor(CRGB::Green);
addFadeColor(CRGB::Blue);
CRGB oldColor = CRGB::Black;
uint16_t rgbInstructionFadeTime = 0;
#endif
ledQueue = xQueueCreate(30, sizeof(struct ledInstruction*));
ledcSetup(7, 5000, 8);
if (FLASHER_LED != -1) {
digitalWrite(FLASHER_LED, HIGH);
pinMode(FLASHER_LED, OUTPUT);
ledcAttachPin(FLASHER_LED, 7);
}
struct ledInstruction* monoled = nullptr;
addFadeMono(0);
addFadeMono(maxledbrightness);
addFadeMono(0);
uint8_t oldBrightness = 0;
uint16_t monoInstructionFadeTime = 0;
while (1) {
#ifdef HAS_RGB_LED
// handle RGB led instructions
if (rgb == nullptr) {
// fetch a led instruction
BaseType_t q = xQueueReceive(rgbLedQueue, &rgb, 1);
if (q == pdTRUE) {
if (rgb->reQueue && !rgbQueueFlush) {
// requeue this instruction at the end of the queue, caveman style.
struct ledInstructionRGB* requeue = new ledInstructionRGB;
requeue->fadeTime = rgb->fadeTime;
requeue->ledColor = rgb->ledColor;
requeue->length = rgb->length;
addToRGBQueue(requeue, true);
}
if (rgbQueueFlush) {
delete rgb;
rgb = nullptr;
} else {
rgbInstructionFadeTime = rgb->fadeTime;
if (rgb->fadeTime <= 1) {
leds[0] = rgb->ledColor;
showRGB();
}
}
} else {
rgbQueueFlush = false;
// no commands, run idle led task
rgbIdleStep();
}
} else {
// process instruction
if (rgb->fadeTime) {
rgb->fadeTime--;
leds[0] = blend(rgb->ledColor, oldColor, map(rgb->fadeTime, 0, rgbInstructionFadeTime, 0, 255));
showRGB();
} else if (rgb->length) {
rgb->length--;
} else {
oldColor = rgb->ledColor;
delete rgb;
rgb = nullptr;
}
}
#endif
// handle flasher LED (single color)
if (monoled == nullptr) {
BaseType_t q = xQueueReceive(ledQueue, &monoled, 1);
if (q == pdTRUE) {
monoInstructionFadeTime = monoled->fadeTime;
if (monoled->fadeTime <= 1) {
showMono(monoled->value);
}
} else {
//monoIdleStep();
}
} else {
if (monoled->fadeTime) {
monoled->fadeTime--;
showMono(map(monoled->fadeTime, 0, monoInstructionFadeTime, monoled->value, oldBrightness));
} else if (monoled->length) {
monoled->length--;
} else {
oldBrightness = monoled->value;
delete monoled;
monoled = nullptr;
}
}
vTaskDelay(1 / portTICK_PERIOD_MS);
}
}

View File

@@ -0,0 +1,186 @@
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiManager.h>
#include <time.h>
#include "storage.h"
#include "contentmanager.h"
#include "flasher.h"
#include "makeimage.h"
#include "serialap.h"
#include "settings.h"
#include "system.h"
#include "tag_db.h"
#ifdef HAS_USB
#include "usbflasher.h"
#endif
#include "language.h"
#include "leds.h"
#include "udp.h"
#include "web.h"
void pinTest();
void delayedStart(void* parameter) {
vTaskDelay(30000 / portTICK_PERIOD_MS);
Serial.println("Resuming content generation");
wsLog("resuming content generation");
config.runStatus = RUNSTATUS_RUN;
vTaskDelay(10 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
void timeTask(void* parameter) {
wsSendSysteminfo();
Serial.printf("Free heap: %.2f KB\n", ESP.getFreeHeap() / 1024.0f);
while (1) {
time_t now;
time(&now);
if (now % 5 == 0 || apInfo.state != AP_STATE_ONLINE || config.runStatus != RUNSTATUS_RUN) wsSendSysteminfo();
if (now % 5 == 0) Serial.printf("Free heap: %.2f KB\n", ESP.getFreeHeap() / 1024.0f);
if (now % 300 == 6 && config.runStatus != RUNSTATUS_STOP) saveDB("/current/tagDB.json");
if (apInfo.state == AP_STATE_ONLINE) contentRunner();
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void setup() {
// starts the led task/state machine
xTaskCreate(ledTask, "ledhandler", 2000, NULL, 2, NULL);
vTaskDelay(10 / portTICK_PERIOD_MS);
// show a nice pattern to indicate the AP is booting / waiting for WiFi setup
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Aqua, CRGB::Green, CRGB::Blue);
#endif
#if defined(OPENEPAPERLINK_MINI_AP_PCB) || defined(OPENEPAPERLINK_NANO_AP_PCB)
APEnterEarlyReset();
// this allows us to view the booting process. After the device showing up, you have 3 seconds to open a terminal on the COM port
vTaskDelay(3000 / portTICK_PERIOD_MS);
#ifdef DEBUG_VERSION
// Specifically for the Mini-version (using an ESP32-S2), use another serial port for debug output. Makes it possible to see core dumps
Serial0.begin(115200, SERIAL_8N1, 38, 37);
Serial0.printf("Started debug output...\n");
Serial0.setDebugOutput(true);
#endif
#endif
Serial.begin(115200);
Serial.print(">\n");
pinTest();
#ifdef BOARD_HAS_PSRAM
if (!psramInit()) {
Serial.printf("This build of the AP expects PSRAM, but we couldn't find/init any. Something is terribly wrong here! System halted.");
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Yellow, CRGB::Red, CRGB::Red);
#endif
while (1) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
};
heap_caps_malloc_extmem_enable(64);
#endif
Storage.begin();
/*
Serial.println("\n\n##################################");
Serial.printf("Internal Total heap %d, internal Free Heap %d\n", ESP.getHeapSize(), ESP.getFreeHeap());
Serial.printf("SPIRam Total heap %d, SPIRam Free Heap %d\n", ESP.getPsramSize(), ESP.getFreePsram());
Serial.printf("ChipRevision %d, Cpu Freq %d, SDK Version %s\n", ESP.getChipRevision(), ESP.getCpuFreqMHz(), ESP.getSdkVersion());
Serial.printf("Flash Size %d, Flash Speed %d\n", ESP.getFlashChipSize(), ESP.getFlashChipSpeed());
Serial.println("##################################\n\n");
Serial.printf("Total heap: %d\n", ESP.getHeapSize());
Serial.printf("Free heap: %d\n", ESP.getFreeHeap());
Serial.printf("Total PSRAM: %d\n", ESP.getPsramSize());
Serial.printf("Free PSRAM: %d\n\n", ESP.getFreePsram());
Serial.printf("ESP32 Partition table:\n");
Serial.printf("| Type | Sub | Offset | Size | Label |\n");
Serial.printf("| ---- | --- | -------- | -------- | ---------------- |\n");
esp_partition_iterator_t pi = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
if (pi != NULL) {
do {
const esp_partition_t* p = esp_partition_get(pi);
Serial.printf("| %02x | %02x | 0x%06X | 0x%06X | %-16s |\r\n",
p->type, p->subtype, p->address, p->size, p->label);
} while (pi = (esp_partition_next(pi)));
}
*/
#ifdef HAS_USB
// We'll need to start the 'usbflasher' task for boards with a second (USB) port. This can be used as a 'flasher' interface, using a python script on the host
xTaskCreate(usbFlasherTask, "usbflasher", 10000, NULL, configMAX_PRIORITIES - 10, NULL);
#endif
configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "0.nl.pool.ntp.org", "europe.pool.ntp.org", "time.nist.gov");
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
initAPconfig();
updateLanguageFromConfig();
updateBrightnessFromConfig();
init_web();
init_udp();
#ifdef HAS_RGB_LED
rgbIdle();
#endif
loadDB("/current/tagDB.json");
// tagDBOwner = xSemaphoreCreateMutex();
xTaskCreate(APTask, "AP Process", 6000, NULL, 2, NULL);
xTaskCreate(webSocketSendProcess, "ws", 2000, NULL, configMAX_PRIORITIES - 10, NULL);
vTaskDelay(10 / portTICK_PERIOD_MS);
config.runStatus = RUNSTATUS_INIT;
xTaskCreate(timeTask, "timed tasks", 12000, NULL, 2, NULL);
init_time();
logStartUp();
esp_reset_reason_t resetReason = esp_reset_reason();
if (resetReason == ESP_RST_PANIC) {
Serial.println("Panic! Pausing content generation for 30 seconds");
config.runStatus = RUNSTATUS_PAUSE;
xTaskCreate(delayedStart, "delaystart", 2000, NULL, 2, NULL);
} else {
config.runStatus = RUNSTATUS_RUN;
}
}
void loop() {
vTaskDelay(10000 / portTICK_PERIOD_MS);
// performDeviceFlash();
while (1) {
// pinTest();
while (1) {
vTaskDelay(10000 / portTICK_PERIOD_MS);
// pinTest();
}
#ifdef OPENEPAPERLINK_PCB
if (extTagConnected()) {
flashCountDown(3);
pinMode(FLASHER_EXT_TEST, OUTPUT);
digitalWrite(FLASHER_EXT_TEST, LOW);
doTagFlash();
vTaskDelay(10000 / portTICK_PERIOD_MS);
pinMode(FLASHER_EXT_TEST, INPUT);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
#endif
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}

View File

@@ -0,0 +1,230 @@
#include <Arduino.h>
#include <FS.h>
#include "storage.h"
#include <TFT_eSPI.h>
#include <TJpg_Decoder.h>
#include <makeimage.h>
#include <web.h>
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
bool spr_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) {
spr.pushImage(x, y, w, h, bitmap);
return 1;
}
void jpg2buffer(String filein, String fileout, imgParam &imageParams) {
Storage.begin();
TJpgDec.setSwapBytes(true);
TJpgDec.setJpgScale(1);
TJpgDec.setCallback(spr_output);
uint16_t w = 0, h = 0;
if (filein.c_str()[0] != '/') {
filein = "/" + filein;
}
TJpgDec.getFsJpgSize(&w, &h, filein, *contentFS);
if (w==0 && h==0) {
wsErr("invalid jpg");
return;
}
Serial.println("jpeg conversion " + String(w) + "x" + String(h));
#ifdef BOARD_HAS_PSRAM
spr.setColorDepth(16);
#else
spr.setColorDepth(8);
#endif
spr.createSprite(w, h);
if (spr.getPointer() == nullptr) {
wsErr("low on memory. Fallback to 1bpp");
spr.setColorDepth(1);
spr.setBitmapColor(TFT_WHITE, TFT_BLACK);
imageParams.bpp = 1;
spr.createSprite(w, h);
}
if (spr.getPointer() == nullptr) {
wsErr("Failed to create sprite in jpg2buffer");
} else {
spr.fillSprite(TFT_WHITE);
TJpgDec.drawFsJpg(0, 0, filein, *contentFS);
spr2buffer(spr, fileout, imageParams);
spr.deleteSprite();
}
}
struct Color {
uint8_t r, g, b;
Color() : r(0), g(0), b(0) {}
Color(uint16_t value_) : r((value_ >> 8) & 0xF8 | (value_ >> 13) & 0x07), g((value_ >> 3) & 0xFC | (value_ >> 9) & 0x03), b((value_ << 3) & 0xF8 | (value_ >> 2) & 0x07) {}
Color(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
};
struct Error {
float r;
float g;
float b;
};
uint32_t colorDistance(const Color &c1, const Color &c2, const Error &e1) {
int32_t r_diff = c1.r + e1.r - c2.r;
int32_t g_diff = c1.g + e1.g - c2.g;
int32_t b_diff = c1.b + e1.b - c2.b;
return 3 * r_diff * r_diff + 6 * g_diff * g_diff + 1 * b_diff * b_diff;
}
uint8_t *spr2color(TFT_eSprite &spr, imgParam &imageParams, size_t *buffer_size, bool is_red) {
bool dither = true;
uint8_t rotate = imageParams.rotate;
long bufw = spr.width(), bufh = spr.height();
if (bufw > bufh && bufw!=400 && bufh!=300) {
rotate = (rotate + 3) % 4;
bufw = spr.height();
bufh = spr.width();
}
*buffer_size = (bufw * bufh) / 8;
uint8_t *buffer = (uint8_t*) malloc(*buffer_size);
if (!buffer) {
Serial.println("Fallied to allocated buffer");
return nullptr;
}
memset(buffer, 0, *buffer_size);
std::vector<Color> palette = {
{255, 255, 255}, // White
{0, 0, 0}, // Black
{255, 0, 0} // Red
};
if (imageParams.grayLut) {
Color newColor = {160, 160, 160};
palette.push_back(newColor);
Serial.println("rendering with gray");
}
int num_colors = palette.size();
if (imageParams.bpp == 1) num_colors = 2;
Color color;
Error *error_bufferold = new Error[bufw + 4];
Error *error_buffernew = new Error[bufw + 4];
memset(error_bufferold, 0, bufw * sizeof(Error));
for (uint16_t y = 0; y < bufh; y++) {
memset(error_buffernew, 0, bufw * sizeof(Error));
for (uint16_t x = 0; x < bufw; x++) {
switch (rotate) {
case 0:
color = Color(spr.readPixel(x, y));
break;
case 1:
color = Color(spr.readPixel(y, bufw - 1 - x));
break;
case 2:
color = Color(spr.readPixel(bufw - 1 - x, bufh - 1 - y));
break;
case 3:
color = Color(spr.readPixel(bufh - 1 - y, x));
break;
}
int best_color_index = 0;
uint32_t best_color_distance = colorDistance(color, palette[0], error_bufferold[x]);
for (int i = 1; i < num_colors; i++) {
uint32_t distance = colorDistance(color, palette[i], error_bufferold[x]);
if (distance < best_color_distance) {
best_color_distance = distance;
best_color_index = i;
}
}
uint8_t bitIndex = 7 - (x % 8);
uint16_t byteIndex = (y * bufw + x) / 8;
// this looks a bit ugly, but it's performing better than shorter notations
switch (best_color_index) {
case 1:
if(!is_red)
buffer[byteIndex] |= (1 << bitIndex);
break;
case 2:
imageParams.hasRed = true;
if(is_red)
buffer[byteIndex] |= (1 << bitIndex);
break;
case 3:
buffer[byteIndex] |= (1 << bitIndex);
imageParams.hasRed = true;
break;
}
if (imageParams.dither) {
Error error = {
static_cast<float>(color.r) + error_bufferold[x].r - static_cast<float>(palette[best_color_index].r),
static_cast<float>(color.g) + error_bufferold[x].g - static_cast<float>(palette[best_color_index].g),
static_cast<float>(color.b) + error_bufferold[x].b - static_cast<float>(palette[best_color_index].b) };
// Burkes Dithering
error_buffernew[x].r += error.r / 4.0f;
error_buffernew[x].g += error.g / 4.0f;
error_buffernew[x].b += error.b / 4.0f;
if (x > 0) {
error_buffernew[x - 1].r += error.r / 8.0f;
error_buffernew[x - 1].g += error.g / 8.0f;
error_buffernew[x - 1].b += error.b / 8.0f;
}
if (x > 1) {
error_buffernew[x - 2].r += error.r / 16.0f;
error_buffernew[x - 2].g += error.g / 16.0f;
error_buffernew[x - 2].b += error.b / 16.0f;
}
error_buffernew[x + 1].r += error.r / 8.0f;
error_buffernew[x + 1].g += error.g / 8.0f;
error_buffernew[x + 1].b += error.b / 8.0f;
error_bufferold[x + 1].r += error.r / 4.0f;
error_bufferold[x + 1].g += error.g / 4.0f;
error_bufferold[x + 1].b += error.b / 4.0f;
error_buffernew[x + 2].r += error.r / 16.0f;
error_buffernew[x + 2].g += error.g / 16.0f;
error_buffernew[x + 2].b += error.b / 16.0f;
error_bufferold[x + 2].r += error.r / 8.0f;
error_bufferold[x + 2].g += error.g / 8.0f;
error_bufferold[x + 2].b += error.b / 8.0f;
}
}
memcpy(error_bufferold, error_buffernew, bufw * sizeof(Error));
}
delete[] error_buffernew;
delete[] error_bufferold;
return buffer;
}
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
long t = millis();
Storage.begin();
fs::File f_out = contentFS->open(fileout, "w");
size_t bufferSize;
uint8_t *blackBuffer = (uint8_t*) spr2color(spr, imageParams, &bufferSize, false);
if(!blackBuffer)
return;
f_out.write(blackBuffer, bufferSize);
free(blackBuffer);
if (imageParams.hasRed) {
uint8_t *redBuffer = (uint8_t*) spr2color(spr, imageParams, &bufferSize, true);
if(!redBuffer)
return;
f_out.write(redBuffer, bufferSize);
free(redBuffer);
}
f_out.close();
Serial.println("finished writing buffer " + String(millis() - t) + "ms");
}

View File

@@ -0,0 +1,662 @@
#include "newproto.h"
#include <Arduino.h>
#include <FS.h>
#include <HTTPClient.h>
#include "storage.h"
#include <MD5Builder.h>
#include <makeimage.h>
#include <time.h>
#include "storage.h"
#include "commstructs.h"
#include "serialap.h"
#include "settings.h"
#include "system.h"
#include "tag_db.h"
#include "udp.h"
#include "web.h"
extern uint16_t sendBlock(const void* data, const uint16_t len);
extern UDPcomm udpsync;
void addCRC(void* p, uint8_t len) {
uint8_t total = 0;
for (uint8_t c = 1; c < len; c++) {
total += ((uint8_t*)p)[c];
}
((uint8_t*)p)[0] = total;
// pr("%d",total);
}
bool checkCRC(void* p, uint8_t len) {
uint8_t total = 0;
for (uint8_t c = 1; c < len; c++) {
total += ((uint8_t*)p)[c];
}
return ((uint8_t*)p)[0] == total;
}
uint8_t* getDataForFile(fs::File* file) {
uint8_t* ret = nullptr;
ret = (uint8_t*)malloc(file->size());
if (ret) {
file->seek(0);
file->readBytes((char*)ret, file->size());
} else {
Serial.print("malloc failed for file...\n");
}
return ret;
}
void prepareCancelPending(uint8_t dst[8]) {
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
sendCancelPending(&pending);
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(dst);
if (taginfo == nullptr) {
wsErr("Tag not found, this shouldn't happen.");
return;
}
clearPending(taginfo);
wsSendTaginfo(dst, SYNC_TAGSTATUS);
}
void prepareIdleReq(uint8_t* dst, uint16_t nextCheckin) {
if (nextCheckin > config.maxsleep) nextCheckin = config.maxsleep;
if (nextCheckin > 0) {
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
pending.availdatainfo.dataType = DATATYPE_NOUPDATE;
pending.availdatainfo.nextCheckIn = nextCheckin;
pending.attemptsLeft = 10 + config.maxsleep;
char buffer[64];
sprintf(buffer, ">SDA %02X%02X%02X%02X%02X%02X%02X%02X NOP\n", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
Serial.print(buffer);
sendDataAvail(&pending);
}
}
void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, uint8_t* dst) {
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(dst);
if (taginfo == nullptr) {
wsErr("Tag not found, this shouldn't happen.");
return;
}
clearPending(taginfo);
taginfo->data = (uint8_t*)malloc(len);
if (taginfo->data == nullptr) {
wsErr("no memory allocation for data");
return;
}
memcpy(taginfo->data, data, len);
taginfo->pending = true;
taginfo->len = len;
taginfo->expectedNextCheckin = 0;
taginfo->filename = String();
taginfo->dataType = dataType;
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
pending.availdatainfo.dataSize = len;
pending.availdatainfo.dataType = dataType;
pending.availdatainfo.nextCheckIn = 0;
pending.availdatainfo.dataVer = millis();
pending.attemptsLeft = 10;
if (taginfo->isExternal) {
udpsync.netSendDataAvail(&pending);
} else {
sendDataAvail(&pending);
}
wsSendTaginfo(dst, SYNC_TAGSTATUS);
}
bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t nextCheckin) {
if (nextCheckin > config.maxsleep) nextCheckin = config.maxsleep;
if (wsClientCount() && config.stopsleep == 1) nextCheckin=0;
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(dst);
if (taginfo == nullptr) {
wsErr("Tag not found, this shouldn't happen.");
return true;
}
*filename = "/" + *filename;
Storage.begin();
if (!contentFS->exists(*filename)) {
wsErr("File not found. " + *filename);
return false;
}
fs::File file = contentFS->open(*filename);
uint32_t filesize = file.size();
if (filesize == 0) {
file.close();
wsErr("File has size 0. " + *filename);
return false;
}
uint8_t md5bytes[16];
{
MD5Builder md5;
md5.begin();
md5.addStream(file, filesize);
md5.calculate();
md5.getBytes(md5bytes);
}
file.close();
uint16_t attempts = 60 * 24;
uint8_t lut = EPD_LUT_NO_REPEATS;
if (memcmp(md5bytes, taginfo->md5pending, 16) == 0) {
wsLog("new image is the same as current or already pending image. not updating tag.");
wsSendTaginfo(dst, SYNC_TAGSTATUS);
if (contentFS->exists(*filename)) {
contentFS->remove(*filename);
}
return true;
}
time_t now;
time(&now);
time_t last_midnight = now - now % (24 * 60 * 60) + 3 * 3600; // somewhere in the middle of the night
if (taginfo->lastfullupdate < last_midnight || taginfo->hwType == SOLUM_29_UC8151 || taginfo->lut == 1) {
lut = EPD_LUT_DEFAULT; // full update once a day
taginfo->lastfullupdate = now;
}
if (taginfo->hasCustomLUT && taginfo->capabilities & CAPABILITY_SUPPORTS_CUSTOM_LUTS && taginfo->lut != 1) {
Serial.println("using custom LUT");
lut = EPD_LUT_OTA;
}
if (dataType != DATATYPE_FW_UPDATE) {
char dst_path[64];
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
if (contentFS->exists(dst_path)) {
contentFS->remove(dst_path);
}
contentFS->rename(*filename, dst_path);
*filename = String(dst_path);
wsLog("new image: " + String(dst_path));
time_t now;
time(&now);
taginfo->expectedNextCheckin = now + nextCheckin * 60 + 60;
clearPending(taginfo);
taginfo->filename = *filename;
taginfo->len = filesize;
taginfo->dataType = dataType;
taginfo->pending = true;
memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes));
} else {
wsLog("firmware upload pending");
clearPending(taginfo);
taginfo->filename = *filename;
taginfo->len = filesize;
taginfo->dataType = dataType;
taginfo->pending = true;
}
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
pending.availdatainfo.dataType = dataType;
pending.availdatainfo.dataVer = *((uint64_t*)md5bytes);
pending.availdatainfo.dataSize = filesize;
pending.availdatainfo.dataTypeArgument = lut;
pending.availdatainfo.nextCheckIn = nextCheckin;
pending.attemptsLeft = attempts;
if (taginfo->isExternal == false) {
char buffer[64];
sprintf(buffer, ">SDA %02X%02X%02X%02X%02X%02X%02X%02X TYPE 0x%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0], pending.availdatainfo.dataType);
Serial.print(buffer);
sendDataAvail(&pending);
} else {
udpsync.netSendDataAvail(&pending);
}
wsSendTaginfo(dst, SYNC_TAGSTATUS);
return true;
}
void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(pending->targetMac);
if (taginfo == nullptr) {
return;
}
if (taginfo->isExternal == false) {
switch (pending->availdatainfo.dataType) {
case DATATYPE_IMG_DIFF:
case DATATYPE_IMG_RAW_1BPP:
case DATATYPE_IMG_RAW_2BPP:
case DATATYPE_IMG_RAW_1BPP_DIRECT: {
Storage.begin();
char hexmac[17];
mac2hex(pending->targetMac, hexmac);
String filename = "/current/" + String(hexmac) + ".pending";
String imageUrl = "http://" + remoteIP.toString() + filename;
wsLog("GET " + imageUrl);
HTTPClient http;
http.begin(imageUrl);
int httpCode = http.GET();
if (httpCode == 200) {
File file = contentFS->open(filename, "w");
http.writeToStream(&file);
file.close();
}
http.end();
fs::File file = contentFS->open(filename);
uint32_t filesize = file.size();
if (filesize == 0) {
file.close();
wsErr("Remote file not found. " + filename);
return;
}
uint8_t md5bytes[16];
{
MD5Builder md5;
md5.begin();
md5.addStream(file, filesize);
md5.calculate();
md5.getBytes(md5bytes);
}
file.close();
clearPending(taginfo);
taginfo->filename = filename;
taginfo->len = filesize;
taginfo->dataType = pending->availdatainfo.dataType;
taginfo->pending = true;
memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes));
break;
}
case DATATYPE_NFC_RAW_CONTENT:
case DATATYPE_NFC_URL_DIRECT:
case DATATYPE_CUSTOM_LUT_OTA: {
char hexmac[17];
mac2hex(pending->targetMac, hexmac);
String dataUrl = "http://" + remoteIP.toString() + "/getdata?mac=" + String(hexmac);
wsLog("GET " + dataUrl);
HTTPClient http;
http.begin(dataUrl);
int httpCode = http.GET();
if (httpCode == 200) {
size_t len = http.getSize();
if (len > 0) {
clearPending(taginfo);
taginfo->data = new uint8_t[len];
WiFiClient* stream = http.getStreamPtr();
stream->readBytes(taginfo->data, len);
taginfo->dataType = pending->availdatainfo.dataType;
taginfo->pending = true;
taginfo->len = len;
}
}
http.end();
break;
}
case DATATYPE_FW_UPDATE: {
return;
}
}
taginfo->contentMode = 12;
taginfo->nextupdate = 3216153600;
sendDataAvail(pending);
wsSendTaginfo(pending->targetMac, SYNC_NOSYNC);
}
}
void processBlockRequest(struct espBlockRequest* br) {
if (config.runStatus == RUNSTATUS_STOP) return;
if (!checkCRC(br, sizeof(struct espBlockRequest))) {
Serial.print("Failed CRC on a blockrequest received by the AP");
return;
}
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(br->src);
if (taginfo == nullptr) {
prepareCancelPending(br->src);
Serial.printf("blockrequest: couldn't find taginfo %02X%02X%02X%02X%02X%02X%02X%02X\n", br->src[7], br->src[6], br->src[5], br->src[4], br->src[3], br->src[2], br->src[1], br->src[0]);
return;
}
if (taginfo->data == nullptr) {
// not cached. open file, cache the data
fs::File file = contentFS->open(taginfo->filename);
if (!file) {
Serial.print("No current file. Canceling request\n");
prepareCancelPending(br->src);
return;
}
taginfo->data = getDataForFile(&file);
file.close();
}
// check if we're not exceeding max blocks (to prevent sendBlock from exceeding its boundary)
uint8_t totalblocks = (taginfo->len / BLOCK_DATA_SIZE);
if (taginfo->len % BLOCK_DATA_SIZE) totalblocks++;
if (br->blockId >= totalblocks) {
br->blockId = totalblocks - 1;
}
uint32_t len = taginfo->len - (BLOCK_DATA_SIZE * br->blockId);
if (len > BLOCK_DATA_SIZE) len = BLOCK_DATA_SIZE;
uint16_t checksum = sendBlock(taginfo->data + (br->blockId * BLOCK_DATA_SIZE), len);
char buffer[150];
sprintf(buffer, "< Block Request received for file %s block %d, len %d checksum %u\0", taginfo->filename.c_str(), br->blockId, len, checksum);
wsLog((String)buffer);
Serial.printf("<RQB file %s block %d, len %d checksum %u\n\0", taginfo->filename.c_str(), br->blockId, len, checksum);
}
void processXferComplete(struct espXferComplete* xfc, bool local) {
if (config.runStatus == RUNSTATUS_STOP) return;
char buffer[64];
sprintf(buffer, "< %02X%02X%02X%02X%02X%02X%02X%02X reports xfer complete\n\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
wsLog((String)buffer);
if (local) {
Serial.printf("<XFC %02X%02X%02X%02X%02X%02X%02X%02X\n", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
} else {
Serial.printf("<REMOTE XFC %02X%02X%02X%02X%02X%02X%02X%02X\n", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
}
char src_path[64];
char dst_path[64];
sprintf(src_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.raw\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
if (contentFS->exists(dst_path) && contentFS->exists(src_path)) {
contentFS->remove(dst_path);
}
if (contentFS->exists(src_path)) {
#ifndef REMOVE_RAW
contentFS->rename(src_path, dst_path);
#else
contentFS->remove(src_path);
#endif
}
time_t now;
time(&now);
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(xfc->src);
if (taginfo != nullptr) {
memcpy(taginfo->md5, taginfo->md5pending, sizeof(taginfo->md5pending));
clearPending(taginfo);
taginfo->wakeupReason = 0;
if (taginfo->contentMode == 12 && local == false) {
if (contentFS->exists(dst_path)) {
contentFS->remove(dst_path);
}
}
}
wsSendTaginfo(xfc->src, SYNC_TAGSTATUS);
if (local) udpsync.netProcessXferComplete(xfc);
}
void processXferTimeout(struct espXferComplete* xfc, bool local) {
if (config.runStatus == RUNSTATUS_STOP) return;
char buffer[64];
sprintf(buffer, "< %02X%02X%02X%02X%02X%02X%02X%02X xfer timeout\n\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
wsErr((String)buffer);
if (local) {
Serial.printf("<XTO %02X%02X%02X%02X%02X%02X%02X%02X\n", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
} else {
Serial.printf("<REMOTE XTO %02X%02X%02X%02X%02X%02X%02X%02X\n", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
}
time_t now;
time(&now);
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(xfc->src);
if (taginfo != nullptr) {
taginfo->expectedNextCheckin = now + 60;
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
clearPending(taginfo);
}
wsSendTaginfo(xfc->src, SYNC_TAGSTATUS);
if (local) udpsync.netProcessXferTimeout(xfc);
}
void processDataReq(struct espAvailDataReq* eadr, bool local) {
if (config.runStatus == RUNSTATUS_STOP) return;
char buffer[64];
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(eadr->src);
if (taginfo == nullptr) {
taginfo = new tagRecord;
memcpy(taginfo->mac, eadr->src, sizeof(taginfo->mac));
taginfo->pending = false;
tagDB.push_back(taginfo);
}
time_t now;
time(&now);
char hexmac[17];
mac2hex(eadr->src, hexmac);
if (!local) {
if (taginfo->isExternal == false) {
wsLog("moved AP from local to external " + String(hexmac));
}
taginfo->isExternal = true;
} else {
if (taginfo->isExternal == true) {
wsLog("moved AP from external to local " + String(hexmac));
}
taginfo->isExternal = false;
}
if (taginfo->pendingIdle == 0) {
taginfo->expectedNextCheckin = now + 60;
} else {
taginfo->expectedNextCheckin = now + 60 * taginfo->pendingIdle;
taginfo->pendingIdle = 0;
}
taginfo->lastseen = now;
if (eadr->adr.lastPacketRSSI != 0) {
if (eadr->adr.wakeupReason >= 0xF0) {
if (!taginfo->pending) taginfo->nextupdate = 0;
memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
const char* reason = "";
if (eadr->adr.wakeupReason == WAKEUP_REASON_FIRSTBOOT) reason = "Booting";
else if (eadr->adr.wakeupReason == WAKEUP_REASON_NETWORK_SCAN) reason = "Network scan";
else if (eadr->adr.wakeupReason == WAKEUP_REASON_WDT_RESET) reason = "Watchdog reset";
sprintf(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X %s", eadr->src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0], reason);
logLine(buffer);
}
if (taginfo->batteryMv != eadr->adr.batteryMv) {
sprintf(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X battery went from %.2fV to %.2fV", eadr->src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0], static_cast<float>(taginfo->batteryMv) / 1000.0, static_cast<float>(eadr->adr.batteryMv) / 1000.0);
logLine(buffer);
}
taginfo->LQI = eadr->adr.lastPacketLQI;
taginfo->hwType = eadr->adr.hwType;
taginfo->RSSI = eadr->adr.lastPacketRSSI;
taginfo->temperature = eadr->adr.temperature;
taginfo->batteryMv = eadr->adr.batteryMv;
taginfo->hwType = eadr->adr.hwType;
taginfo->wakeupReason = eadr->adr.wakeupReason;
taginfo->capabilities = eadr->adr.capabilities;
taginfo->currentChannel = eadr->adr.currentChannel;
taginfo->tagSoftwareVersion = eadr->adr.tagSoftwareVersion;
}
if (local) {
sprintf(buffer, "<ADR %02X%02X%02X%02X%02X%02X%02X%02X\n\0", eadr->src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]);
} else {
sprintf(buffer, "<REMOTE ADR %02X%02X%02X%02X%02X%02X%02X%02X\n\0", eadr->src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]);
}
Serial.print(buffer);
wsSendTaginfo(eadr->src, SYNC_TAGSTATUS);
if (local) {
udpsync.netProcessDataReq(eadr);
}
}
void refreshAllPending() {
for (int16_t c = 0; c < tagDB.size(); c++) {
tagRecord* taginfo = nullptr;
taginfo = tagDB.at(c);
if (taginfo->pending) {
clearPending(taginfo);
taginfo->nextupdate = 0;
memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
wsSendTaginfo(taginfo->mac, SYNC_TAGSTATUS);
}
}
};
void updateContent(uint8_t* dst) {
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(dst);
if (taginfo != nullptr) {
clearPending(taginfo);
taginfo->nextupdate = 0;
memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
wsSendTaginfo(taginfo->mac, SYNC_TAGSTATUS);
}
}
void setAPchannel() {
if (config.channel == 0) {
// trigger channel autoselect
UDPcomm udpsync;
udpsync.getAPList();
} else {
if (curChannel.channel != config.channel) {
curChannel.channel = config.channel;
sendChannelPower(&curChannel);
}
}
}
bool sendAPSegmentedData(uint8_t* dst, String data, uint16_t icons, bool inverted, bool local) {
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
pending.availdatainfo.dataType = DATATYPE_UK_SEGMENTED;
pending.availdatainfo.dataSize = icons << 16;
memcpy((void*)&(pending.availdatainfo.dataVer), data.c_str(), 10);
pending.availdatainfo.dataTypeArgument = inverted;
pending.availdatainfo.nextCheckIn = 0;
pending.attemptsLeft = 120;
char buffer[64];
sprintf(buffer, ">AP Segmented Data %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
Serial.print(buffer);
if (local) {
return sendDataAvail(&pending);
} else {
udpsync.netSendDataAvail(&pending);
return true;
}
}
bool showAPSegmentedInfo(uint8_t* dst, bool local) {
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
pending.availdatainfo.dataType = DATATYPE_UK_SEGMENTED;
pending.availdatainfo.dataSize = 0x00;
pending.availdatainfo.dataVer = 0x00;
pending.availdatainfo.dataTypeArgument = 0;
pending.availdatainfo.nextCheckIn = 0;
pending.attemptsLeft = 120;
char buffer[64];
sprintf(buffer, ">SDA %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
Serial.print(buffer);
if (local) {
return sendDataAvail(&pending);
} else {
udpsync.netSendDataAvail(&pending);
return true;
}
}
bool sendTagCommand(uint8_t* dst, uint8_t cmd, bool local) {
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
pending.availdatainfo.dataType = DATATYPE_COMMAND_DATA;
pending.availdatainfo.dataTypeArgument = cmd;
pending.availdatainfo.nextCheckIn = 0;
pending.attemptsLeft = 120;
char buffer[64];
sprintf(buffer, ">Tag CMD %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
Serial.print(buffer);
if (local) {
return sendDataAvail(&pending);
} else {
udpsync.netSendDataAvail(&pending);
return true;
}
}
void updateTaginfoitem(struct TagInfo* taginfoitem) {
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(taginfoitem->mac);
if (taginfo == nullptr) {
taginfo = new tagRecord;
memcpy(taginfo->mac, taginfoitem->mac, sizeof(taginfo->mac));
taginfo->pending = false;
tagDB.push_back(taginfo);
}
tagRecord initialTagInfo = *taginfo;
switch (taginfoitem->syncMode) {
case SYNC_USERCFG:
taginfo->alias = String(taginfoitem->alias);
taginfo->nextupdate = taginfoitem->nextupdate;
break;
case SYNC_TAGSTATUS:
taginfo->lastseen = taginfoitem->lastseen;
taginfo->nextupdate = taginfoitem->nextupdate;
taginfo->pending = taginfoitem->pending;
taginfo->expectedNextCheckin = taginfoitem->expectedNextCheckin;
taginfo->hwType = taginfoitem->hwType;
taginfo->wakeupReason = taginfoitem->wakeupReason;
taginfo->capabilities = taginfoitem->capabilities;
taginfo->pendingIdle = taginfoitem->pendingIdle;
break;
}
char hexmac[17];
mac2hex(taginfo->mac, hexmac);
if (taginfo->contentMode != 12 && taginfoitem->contentMode != 12) {
wsLog("Remote AP takes control over tag " + String(hexmac));
taginfo->contentMode = 12;
}
if (taginfoitem->syncMode == SYNC_DELETE) {
taginfo->contentMode = 255;
wsSendTaginfo(taginfo->mac, SYNC_NOSYNC);
deleteRecord(taginfoitem->mac);
} else {
bool hasChanges = (memcmp(&initialTagInfo, taginfo, sizeof(tagRecord)) != 0);
if (hasChanges) {
wsSendTaginfo(taginfo->mac, SYNC_NOSYNC);
}
}
}

View File

@@ -0,0 +1,289 @@
#include "ota.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <FS.h>
#include <HTTPClient.h>
#include "storage.h"
#include <MD5Builder.h>
#include <Update.h>
#include "tag_db.h"
#include "web.h"
#ifndef BUILD_ENV_NAME
#define BUILD_ENV_NAME unknown
#endif
#ifndef BUILD_TIME
#define BUILD_TIME 0
#endif
#ifndef BUILD_VERSION
#define BUILD_VERSION custom
#endif
#ifndef SHA
#define SHA 0
#endif
#define STR_IMPL(x) #x
#define STR(x) STR_IMPL(x)
void handleSysinfoRequest(AsyncWebServerRequest* request) {
StaticJsonDocument<250> doc;
doc["alias"] = config.alias;
doc["env"] = STR(BUILD_ENV_NAME);
doc["buildtime"] = STR(BUILD_TIME);
doc["buildversion"] = STR(BUILD_VERSION);
doc["sha"] = STR(SHA);
doc["psramsize"] = ESP.getPsramSize();
doc["flashsize"] = ESP.getFlashChipSize();
doc["rollback"] = Update.canRollBack();
size_t bufferSize = measureJson(doc) + 1;
AsyncResponseStream* response = request->beginResponseStream("application/json", bufferSize);
serializeJson(doc, *response);
request->send(response);
};
void handleCheckFile(AsyncWebServerRequest* request) {
if (!request->hasParam("path")) {
request->send(400);
return;
}
String filePath = request->getParam("path")->value();
File file = contentFS->open(filePath, "r");
if (!file) {
StaticJsonDocument<64> doc;
doc["filesize"] = 0;
doc["md5"] = "";
String jsonResponse;
serializeJson(doc, jsonResponse);
request->send(200, "application/json", jsonResponse);
return;
}
size_t fileSize = file.size();
MD5Builder md5;
md5.begin();
md5.addStream(file, fileSize);
md5.calculate();
String md5Hash = md5.toString();
file.close();
StaticJsonDocument<128> doc;
doc["filesize"] = fileSize;
doc["md5"] = md5Hash;
String jsonResponse;
serializeJson(doc, jsonResponse);
request->send(200, "application/json", jsonResponse);
}
void handleGetExtUrl(AsyncWebServerRequest* request) {
if (request->hasParam("url")) {
String url = request->getParam("url")->value();
HTTPClient http;
http.begin(url);
http.setConnectTimeout(5000);
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
Serial.println(httpResponseCode);
String contentType = http.header("Content-Type");
size_t contentLength = http.getSize();
if (contentLength > 0) {
String content = http.getString();
AsyncWebServerResponse* response = request->beginResponse(200, contentType, content);
request->send(response);
} else {
request->send(500, "text/plain", "no size header");
}
} else {
request->send(httpResponseCode, "text/plain", "Failed to fetch URL");
wsSerial(http.errorToString(httpResponseCode));
}
http.end();
} else {
request->send(400, "text/plain", "Missing 'url' parameter");
}
}
void handleLittleFSUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) {
bool error = false;
if (!index) {
String path;
if (!request->hasParam("path", true)) {
path = "/temp/null.bin";
final = true;
error = true;
} else {
path = request->getParam("path", true)->value();
Serial.println("update " + path);
request->_tempFile = contentFS->open(path, "w", true);
}
}
if (len) {
if (!request->_tempFile.write(data, len)) {
error = true;
final = true;
}
}
if (final) {
request->_tempFile.close();
if (error) {
request->send(507, "text/plain", "Error. Disk full?");
} else {
request->send(200, "text/plain", "Ok, file written");
}
}
}
struct FirmwareUpdateParams {
String url;
String md5;
size_t size;
};
void handleUpdateOTA(AsyncWebServerRequest* request) {
if (request->hasParam("url", true) && request->hasParam("md5", true) && request->hasParam("size", true)) {
FirmwareUpdateParams* params = new FirmwareUpdateParams;
params->url = request->getParam("url", true)->value();
params->md5 = request->getParam("md5", true)->value();
params->size = request->getParam("size", true)->value().toInt();
xTaskCreate(firmwareUpdateTask, "OTAUpdateTask", 6144, params, 10, NULL);
request->send(200, "text/plain", "In progress");
} else {
request->send(400, "Bad request");
}
}
void firmwareUpdateTask(void* parameter) {
FirmwareUpdateParams* params = reinterpret_cast<FirmwareUpdateParams*>(parameter);
if (ESP.getMaxAllocHeap() < 22000) {
wsSerial("Error: Not enough memory left. Restart the esp32 and try updating again.");
wsSerial("[reboot]");
} else {
const char* url = params->url.c_str();
const char* md5 = params->md5.c_str();
size_t size = params->size;
updateFirmware(url, md5, size);
}
delete params;
vTaskDelete(NULL);
}
void updateFirmware(const char* url, const char* expectedMd5, size_t size) {
uint32_t freeStack = uxTaskGetStackHighWaterMark(NULL);
Serial.printf("Free heap: %d allocatable: %d stack: %d\n", ESP.getFreeHeap(), ESP.getMaxAllocHeap(), freeStack);
config.runStatus = RUNSTATUS_STOP;
vTaskDelay(3000 / portTICK_PERIOD_MS);
// xSemaphoreTake(tagDBOwner, portMAX_DELAY);
saveDB("/current/tagDB.json");
// destroyDB();
HTTPClient httpClient;
wsSerial("start downloading");
wsSerial(url);
httpClient.begin(url);
httpClient.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
freeStack = uxTaskGetStackHighWaterMark(NULL);
Serial.printf("Free heap: %d allocatable: %d stack: %d\n", ESP.getFreeHeap(), ESP.getMaxAllocHeap(), freeStack);
int httpCode = httpClient.GET();
freeStack = uxTaskGetStackHighWaterMark(NULL);
Serial.printf("Free heap: %d allocatable: %d stack: %d\n", ESP.getFreeHeap(), ESP.getMaxAllocHeap(), freeStack);
if (httpCode == HTTP_CODE_OK) {
if (Update.begin(size)) {
Update.setMD5(expectedMd5);
unsigned long progressTimer = millis();
Update.onProgress([&progressTimer](size_t progress, size_t total) {
if (millis() - progressTimer > 500 || progress == total) {
char buffer[50];
sprintf(buffer, "Progress: %u%% %d %d", progress * 100 / total, progress, total);
wsSerial(String(buffer));
progressTimer = millis();
vTaskDelay(1 / portTICK_PERIOD_MS);
}
});
size_t written = Update.writeStream(httpClient.getStream());
if (written == httpClient.getSize()) {
if (Update.end(true)) {
wsSerial("Firmware update successful");
wsSerial("Reboot system now");
wsSerial("[reboot]");
vTaskDelay(1000 / portTICK_PERIOD_MS);
// ESP.restart();
} else {
wsSerial("Error updating firmware:");
wsSerial(Update.errorString());
}
} else {
wsSerial("Error writing firmware data:");
wsSerial(Update.errorString());
}
} else {
wsSerial("Failed to begin firmware update");
wsSerial(Update.errorString());
}
} else {
wsSerial("Failed to download firmware file (HTTP code " + String(httpCode) + ")");
wsSerial(httpClient.errorToString(httpCode));
}
httpClient.end();
// loadDB("/current/tagDB.json");
config.runStatus = RUNSTATUS_RUN;
// xSemaphoreGive(tagDBOwner);
}
void handleRollback(AsyncWebServerRequest* request) {
if (Update.canRollBack()) {
bool rollbackSuccess = Update.rollBack();
if (rollbackSuccess) {
request->send(200, "Rollback successful");
wsSerial("Rollback successful");
wsSerial("Reboot system now");
wsSerial("[reboot]");
vTaskDelay(1000 / portTICK_PERIOD_MS);
// ESP.restart();
} else {
wsSerial("Rollback failed");
request->send(400, "Rollback failed");
}
} else {
wsSerial("Rollback not allowed");
request->send(400, "Rollback not allowed");
}
}
void handleUpdateActions(AsyncWebServerRequest* request) {
wsSerial("Performing cleanup");
File file = contentFS->open("/update_actions.json", "r");
if (!file) {
wsSerial("No update_actions.json present");
request->send(200, "No update actions needed");
return;
}
StaticJsonDocument<1000> doc;
DeserializationError error = deserializeJson(doc, file);
JsonArray deleteFiles = doc["deletefile"].as<JsonArray>();
for (const auto& filePath : deleteFiles) {
if (contentFS->remove(filePath.as<const char*>())) {
wsSerial("deleted file: " + filePath.as<String>());
}
}
file.close();
wsSerial("Cleanup finished");
request->send(200, "Clean up finished");
contentFS->remove("/update_actions.json");
}

View File

@@ -0,0 +1,101 @@
#include "powermgt.h"
#include <Arduino.h>
#include "settings.h"
#ifdef OPENEPAPERLINK_PCB
#include "soc/rtc_cntl_reg.h"
#include "soc/soc.h"
#endif
void simpleAPPower(uint8_t* pin, uint8_t pincount, bool state) {
for (uint8_t c = 0; c < pincount; c++) {
pinMode(pin[c], INPUT);
}
for (uint8_t c = 0; c < pincount; c++) {
#ifdef POWER_HIGH_SIDE_DRIVER
digitalWrite(pin[c], !state);
#else
digitalWrite(pin[c], state);
#endif
}
for (uint8_t c = 0; c < pincount; c++) {
pinMode(pin[c], OUTPUT);
}
}
// On the OpenEPaperLink board, there is no in-rush current limiting. The tags that can be connected to the board can have significant capacity, which,
// when drained if the board applies power, will cause the 3v3 rail to sag enough to reset the ESP32. This is obviously not great. To prevent this from happening,
// we ramp up/down the voltage with PWM. Ramping down really is unnecessary, as the board has a resistor to dump the charge into.
void rampTagPower(uint8_t* pin, bool up) {
#ifdef OPENEPAPERLINK_PCB
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
#endif
if (up) {
ledcSetup(0, 152000, 8); // 141251 okay // 101251 okay
ledcWrite(0, 254);
vTaskDelay(1 / portTICK_PERIOD_MS);
ledcAttachPin(pin[0], 0);
pinMode(pin[0], OUTPUT);
vTaskDelay(10 / portTICK_PERIOD_MS);
for (uint8_t c = 254; c != 0xFF; c--) {
ledcWrite(0, c);
if (c > 250) {
vTaskDelay(2 / portTICK_PERIOD_MS);
} else {
delayMicroseconds(100);
}
}
digitalWrite(pin[0], LOW);
ledcDetachPin(pin[0]);
digitalWrite(pin[0], LOW);
} else {
ledcSetup(0, 152000, 8); // 141251 okay // 101251 okay
ledcWrite(0, 0);
vTaskDelay(1 / portTICK_PERIOD_MS);
ledcAttachPin(pin[0], 0);
pinMode(pin[0], OUTPUT);
vTaskDelay(10 / portTICK_PERIOD_MS);
for (uint8_t c = 0; c < 0xFF; c++) {
ledcWrite(0, c);
if (c > 250) {
vTaskDelay(2 / portTICK_PERIOD_MS);
} else {
delayMicroseconds(100);
}
}
digitalWrite(pin[0], HIGH);
ledcDetachPin(pin[0]);
digitalWrite(pin[0], HIGH);
}
#ifdef OPENEPAPERLINK_PCB
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1);
#endif
}
void powerControl(bool powerState, uint8_t* pin, uint8_t pincount) {
if (pincount == 0) return;
if (pin == nullptr) return;
#ifdef POWER_RAMPING
if (powerState == true) {
#ifdef POWER_HIGH_SIDE_DRIVER
rampTagPower(pin, true);
#else
rampTagPower(pin, false);
#endif
} else {
pinMode(pin[0], OUTPUT);
#ifdef POWER_HIGH_SIDE_DRIVER
digitalWrite(pin[0], HIGH);
#else
digitalWrite(pin[0], LOW);
#endif
}
#else
simpleAPPower(pin, pincount, false);
delay(500);
simpleAPPower(pin, pincount, true);
#endif
}

View File

@@ -0,0 +1,797 @@
#include "serialap.h"
#include <Arduino.h>
#include <HardwareSerial.h>
#include "commstructs.h"
#include "flasher.h"
#include "leds.h"
#include "newproto.h"
#include "powermgt.h"
#include "settings.h"
#include "storage.h"
#include "web.h"
#include "zbs_interface.h"
QueueHandle_t rxCmdQueue;
SemaphoreHandle_t txActive;
// If a command is sent, it will wait for a reply here
#define CMD_REPLY_WAIT 0x00
#define CMD_REPLY_ACK 0x01
#define CMD_REPLY_NOK 0x02
#define CMD_REPLY_NOQ 0x03
volatile uint8_t cmdReplyValue = CMD_REPLY_WAIT;
#define AP_SERIAL_PORT Serial1
uint8_t channelList[6];
struct espSetChannelPower curChannel = {0, 11, 10};
#define RX_CMD_RQB 0x01
#define RX_CMD_ADR 0x02
#define RX_CMD_XFC 0x03
#define RX_CMD_XTO 0x04
#define RX_CMD_RDY 0x05
#define RX_CMD_RSET 0x06
#define AP_ACTIVITY_MAX_INTERVAL 30 * 1000
volatile uint32_t lastAPActivity = 0;
struct APInfoS apInfo;
extern uint8_t* getDataForFile(File* file);
struct rxCmd {
uint8_t* data;
uint8_t len;
uint8_t type;
};
#define ZBS_RX_WAIT_HEADER 0
#define ZBS_RX_WAIT_PKT_LEN 1
#define ZBS_RX_WAIT_PKT_RX 2
#define ZBS_RX_WAIT_SEP1 3
#define ZBS_RX_WAIT_SEP2 4
#define ZBS_RX_WAIT_VER 6
#define ZBS_RX_BLOCK_REQUEST 7
#define ZBS_RX_WAIT_XFERCOMPLETE 8
#define ZBS_RX_WAIT_DATA_REQ 9
#define ZBS_RX_WAIT_JOINNETWORK 10
#define ZBS_RX_WAIT_XFERTIMEOUT 11
#define ZBS_RX_WAIT_MAC 12
#define ZBS_RX_WAIT_CHANNEL 13
#define ZBS_RX_WAIT_POWER 14
#define ZBS_RX_WAIT_PENDING 15
#define ZBS_RX_WAIT_NOP 16
#define ZBS_RX_WAIT_TYPE 17
bool txStart() {
while (1) {
if (xPortInIsrContext()) {
if (xSemaphoreTakeFromISR(txActive, NULL) == pdTRUE) return true;
} else {
if (xSemaphoreTake(txActive, portTICK_PERIOD_MS)) return true;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
Serial.println("wait... tx busy");
}
// this never happens. Should we make a timeout?
return false;
}
void txEnd() {
if (xPortInIsrContext()) {
xSemaphoreGiveFromISR(txActive, NULL);
} else {
xSemaphoreGive(txActive);
}
}
bool waitCmdReply() {
uint32_t val = millis();
while (millis() < val + 100) {
switch (cmdReplyValue) {
case CMD_REPLY_WAIT:
break;
case CMD_REPLY_ACK:
lastAPActivity = millis();
return true;
break;
case CMD_REPLY_NOK:
lastAPActivity = millis();
return false;
break;
case CMD_REPLY_NOQ:
lastAPActivity = millis();
return false;
break;
}
vTaskDelay(1 / portTICK_RATE_MS);
}
return false;
}
#if (AP_PROCESS_PORT == FLASHER_AP_PORT)
int8_t APpowerPins[] = FLASHER_AP_POWER;
#define AP_RESET_PIN FLASHER_AP_RESET
#define AP_POWER_PIN FLASHER_AP_POWER
#endif
#ifdef OPENEPAPERLINK_PCB
#if (AP_PROCESS_PORT == FLASHER_EXT_PORT)
int8_t APpowerPins[] = FLASHER_EXT_POWER;
#define AP_RESET_PIN FLASHER_EXT_RESET
#define AP_POWER_PIN FLASHER_EXT_POWER
#endif
#if (AP_PROCESS_PORT == FLASHER_ALTRADIO_PORT)
int8_t APpowerPins[] = FLASHER_ALT_POWER;
#define AP_RESET_PIN FLASHER_ALT_RESET
#define AP_POWER_PIN FLASHER_ALT_POWER
#endif
#endif
void APEnterEarlyReset() {
pinMode(AP_RESET_PIN, OUTPUT);
digitalWrite(AP_RESET_PIN, LOW);
}
// Reset the tag
void APTagReset() {
uint8_t powerPins = sizeof(APpowerPins);
if (powerPins > 0 && APpowerPins[0] == -1)
powerPins = 0;
pinMode(AP_RESET_PIN, OUTPUT);
digitalWrite(AP_RESET_PIN, LOW);
vTaskDelay(50 / portTICK_PERIOD_MS);
powerControl(false, (uint8_t*)APpowerPins, powerPins);
vTaskDelay(300 / portTICK_PERIOD_MS);
powerControl(true, (uint8_t*)APpowerPins, powerPins);
vTaskDelay(100 / portTICK_PERIOD_MS);
digitalWrite(AP_RESET_PIN, HIGH);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
// Send data to the AP
uint16_t sendBlock(const void* data, const uint16_t len) {
if (!apInfo.isOnline) return false;
if (!txStart()) return 0;
for (uint8_t attempt = 0; attempt < 5; attempt++) {
cmdReplyValue = CMD_REPLY_WAIT;
AP_SERIAL_PORT.print(">D>");
if (waitCmdReply()) goto blksend;
Serial.printf("block send failed in try %d\n", attempt);
}
Serial.print("Failed sending block...\n");
txEnd();
return 0;
blksend:
uint8_t blockbuffer[sizeof(struct blockData)];
struct blockData* bd = (struct blockData*)blockbuffer;
bd->size = len;
bd->checksum = 0;
// calculate checksum
for (uint16_t c = 0; c < len; c++) {
bd->checksum += ((uint8_t*)data)[c];
}
// send blockData header
for (uint8_t c = 0; c < sizeof(struct blockData); c++) {
AP_SERIAL_PORT.write(0xAA ^ blockbuffer[c]);
}
// send an entire block of data
uint16_t c;
for (c = 0; c < len; c++) {
AP_SERIAL_PORT.write(0xAA ^ ((uint8_t*)data)[c]);
}
// fill the rest of the block-length filled with something else (will end up as 0xFF in the buffer)
for (; c < BLOCK_DATA_SIZE; c++) {
AP_SERIAL_PORT.write(0x55);
}
// dummy bytes in case some bytes were missed, makes sure the AP gets kicked out of data-loading mode
for (c = 0; c < 32; c++) {
AP_SERIAL_PORT.write(0xF5);
}
delay(10);
txEnd();
return bd->checksum;
}
bool sendDataAvail(struct pendingData* pending) {
if (!apInfo.isOnline) return false;
if (!txStart()) return false;
addCRC(pending, sizeof(struct pendingData));
for (uint8_t attempt = 0; attempt < 5; attempt++) {
cmdReplyValue = CMD_REPLY_WAIT;
AP_SERIAL_PORT.print("SDA>");
for (uint8_t c = 0; c < sizeof(struct pendingData); c++) {
AP_SERIAL_PORT.write(((uint8_t*)pending)[c]);
}
if (waitCmdReply()) goto sdasend;
Serial.printf("SDA send failed in try %d\n", attempt);
delay(200);
}
Serial.print("SDA failed to send...\n");
txEnd();
return false;
sdasend:
txEnd();
return true;
}
bool sendCancelPending(struct pendingData* pending) {
if (!apInfo.isOnline) return false;
if (!txStart()) return false;
addCRC(pending, sizeof(struct pendingData));
for (uint8_t attempt = 0; attempt < 5; attempt++) {
cmdReplyValue = CMD_REPLY_WAIT;
AP_SERIAL_PORT.print("CXD>");
for (uint8_t c = 0; c < sizeof(struct pendingData); c++) {
AP_SERIAL_PORT.write(((uint8_t*)pending)[c]);
}
if (waitCmdReply()) goto cxdsent;
Serial.printf("CXD send failed in try %d\n", attempt);
}
Serial.print("CXD failed to send...\n");
txEnd();
return false;
cxdsent:
txEnd();
return true;
}
bool sendChannelPower(struct espSetChannelPower* scp) {
if ((apInfo.state != AP_STATE_ONLINE) && (apInfo.state != AP_STATE_COMING_ONLINE)) return false;
if (!txStart()) return false;
addCRC(scp, sizeof(struct espSetChannelPower));
for (uint8_t attempt = 0; attempt < 5; attempt++) {
cmdReplyValue = CMD_REPLY_WAIT;
AP_SERIAL_PORT.print("SCP>");
for (uint8_t c = 0; c < sizeof(struct espSetChannelPower); c++) {
AP_SERIAL_PORT.write(((uint8_t*)scp)[c]);
}
if (waitCmdReply()) goto scpSent;
Serial.printf("SCP send failed in try %d\n", attempt);
}
Serial.print("SCP failed to send...\n");
txEnd();
return false;
scpSent:
txEnd();
return true;
}
bool sendPing() {
if (!txStart()) return false;
for (uint8_t attempt = 0; attempt < 5; attempt++) {
cmdReplyValue = CMD_REPLY_WAIT;
AP_SERIAL_PORT.print("RDY?");
if (waitCmdReply()) goto pingSent;
}
txEnd();
return false;
pingSent:
txEnd();
return true;
}
bool sendGetInfo() {
if (!txStart()) return false;
for (uint8_t attempt = 0; attempt < 5; attempt++) {
cmdReplyValue = CMD_REPLY_WAIT;
AP_SERIAL_PORT.print("NFO?");
if (waitCmdReply()) goto nfoRequested;
}
txEnd();
return false;
nfoRequested:
txEnd();
return true;
}
// add RX'd request from the AP to the processor queue
void addRXQueue(uint8_t* data, uint8_t len, uint8_t type) {
struct rxCmd* rxcmd = new struct rxCmd;
rxcmd->data = data;
rxcmd->len = len;
rxcmd->type = type;
BaseType_t queuestatus = xQueueSend(rxCmdQueue, &rxcmd, 0);
if (queuestatus == pdFALSE) {
if (data) free(data);
free(rxcmd);
}
}
// Asynchronous command processor
void rxCmdProcessor(void* parameter) {
rxCmdQueue = xQueueCreate(30, sizeof(struct rxCmd*));
txActive = xSemaphoreCreateBinary();
xSemaphoreGive(txActive);
while (1) {
struct rxCmd* rxcmd = nullptr;
BaseType_t q = xQueueReceive(rxCmdQueue, &rxcmd, 10);
if (q == pdTRUE) {
switch (rxcmd->type) {
case RX_CMD_RQB:
processBlockRequest((struct espBlockRequest*)rxcmd->data);
#ifdef HAS_RGB_LED
shortBlink(CRGB::Blue);
#else
quickBlink(3);
#endif
break;
case RX_CMD_ADR:
processDataReq((struct espAvailDataReq*)rxcmd->data, true);
#ifdef HAS_RGB_LED
shortBlink(CRGB::Aqua);
#else
quickBlink(1);
#endif
break;
case RX_CMD_XFC:
processXferComplete((struct espXferComplete*)rxcmd->data, true);
#ifdef HAS_RGB_LED
shortBlink(CRGB::Purple);
#endif
break;
case RX_CMD_XTO:
processXferTimeout((struct espXferComplete*)rxcmd->data, true);
break;
}
if (rxcmd->data) free(rxcmd->data);
if (rxcmd) free(rxcmd);
}
}
}
void rxSerialTask(void* parameter) {
static char cmdbuffer[4] = {0};
static uint8_t* packetp = nullptr;
// static uint8_t pktlen = 0;
static uint8_t pktindex = 0; // length of the command
static uint8_t RXState = ZBS_RX_WAIT_HEADER;
static char lastchar = 0;
static uint8_t charindex = 0;
while (1) {
while (AP_SERIAL_PORT.available()) {
lastchar = AP_SERIAL_PORT.read();
switch (RXState) {
case ZBS_RX_WAIT_HEADER:
// Serial.write(lastchar);
// shift characters in
for (uint8_t c = 0; c < 3; c++) {
cmdbuffer[c] = cmdbuffer[c + 1];
}
cmdbuffer[3] = lastchar;
if ((strncmp(cmdbuffer, "ACK>", 4) == 0)) cmdReplyValue = CMD_REPLY_ACK;
if ((strncmp(cmdbuffer, "NOK>", 4) == 0)) cmdReplyValue = CMD_REPLY_NOK;
if ((strncmp(cmdbuffer, "NOQ>", 4) == 0)) cmdReplyValue = CMD_REPLY_NOQ;
if ((strncmp(cmdbuffer, "VER>", 4) == 0)) {
pktindex = 0;
RXState = ZBS_RX_WAIT_VER;
charindex = 0;
memset(cmdbuffer, 0x00, 4);
}
if ((strncmp(cmdbuffer, "MAC>", 4) == 0)) {
RXState = ZBS_RX_WAIT_MAC;
charindex = 0;
memset(cmdbuffer, 0x00, 4);
}
if ((strncmp(cmdbuffer, "ZCH>", 4) == 0)) {
RXState = ZBS_RX_WAIT_CHANNEL;
charindex = 0;
memset(cmdbuffer, 0x00, 4);
}
if ((strncmp(cmdbuffer, "ZPW>", 4) == 0)) {
RXState = ZBS_RX_WAIT_POWER;
charindex = 0;
memset(cmdbuffer, 0x00, 4);
}
if ((strncmp(cmdbuffer, "PEN>", 4) == 0)) {
RXState = ZBS_RX_WAIT_PENDING;
charindex = 0;
memset(cmdbuffer, 0x00, 4);
}
if ((strncmp(cmdbuffer, "NOP>", 4) == 0)) {
RXState = ZBS_RX_WAIT_NOP;
charindex = 0;
memset(cmdbuffer, 0x00, 4);
}
if ((strncmp(cmdbuffer, "TYP>", 4) == 0)) {
RXState = ZBS_RX_WAIT_TYPE;
charindex = 0;
memset(cmdbuffer, 0x00, 4);
}
if (strncmp(cmdbuffer, "RES>", 4) == 0) {
addRXQueue(NULL, 0, RX_CMD_RSET);
}
if (strncmp(cmdbuffer, "RQB>", 4) == 0) {
RXState = ZBS_RX_BLOCK_REQUEST;
charindex = 0;
pktindex = 0;
packetp = (uint8_t*)calloc(sizeof(struct espBlockRequest) + 8, 1);
memset(cmdbuffer, 0x00, 4);
}
if (strncmp(cmdbuffer, "ADR>", 4) == 0) {
RXState = ZBS_RX_WAIT_DATA_REQ;
charindex = 0;
pktindex = 0;
packetp = (uint8_t*)calloc(sizeof(struct espAvailDataReq) + 8, 1);
memset(cmdbuffer, 0x00, 4);
}
if (strncmp(cmdbuffer, "XFC>", 4) == 0) {
RXState = ZBS_RX_WAIT_XFERCOMPLETE;
pktindex = 0;
packetp = (uint8_t*)calloc(sizeof(struct espXferComplete) + 8, 1);
memset(cmdbuffer, 0x00, 4);
}
if (strncmp(cmdbuffer, "XTO>", 4) == 0) {
RXState = ZBS_RX_WAIT_XFERTIMEOUT;
pktindex = 0;
packetp = (uint8_t*)calloc(sizeof(struct espXferComplete) + 8, 1);
memset(cmdbuffer, 0x00, 4);
}
if (strncmp(cmdbuffer, "RDY>", 4) == 0) {
addRXQueue(NULL, 0, RX_CMD_RDY);
}
break;
case ZBS_RX_BLOCK_REQUEST:
packetp[pktindex] = lastchar;
pktindex++;
if (pktindex == sizeof(struct espBlockRequest)) {
addRXQueue(packetp, pktindex, RX_CMD_RQB);
RXState = ZBS_RX_WAIT_HEADER;
}
break;
case ZBS_RX_WAIT_XFERCOMPLETE:
packetp[pktindex] = lastchar;
pktindex++;
if (pktindex == sizeof(struct espXferComplete)) {
addRXQueue(packetp, pktindex, RX_CMD_XFC);
RXState = ZBS_RX_WAIT_HEADER;
}
break;
case ZBS_RX_WAIT_XFERTIMEOUT:
packetp[pktindex] = lastchar;
pktindex++;
if (pktindex == sizeof(struct espXferComplete)) {
addRXQueue(packetp, pktindex, RX_CMD_XTO);
RXState = ZBS_RX_WAIT_HEADER;
}
break;
case ZBS_RX_WAIT_DATA_REQ:
packetp[pktindex] = lastchar;
pktindex++;
if (pktindex == sizeof(struct espAvailDataReq)) {
addRXQueue(packetp, pktindex, RX_CMD_ADR);
RXState = ZBS_RX_WAIT_HEADER;
}
break;
case ZBS_RX_WAIT_VER:
cmdbuffer[charindex] = lastchar;
charindex++;
if (charindex == 4) {
charindex = 0;
apInfo.version = (uint16_t)strtoul(cmdbuffer, NULL, 16);
RXState = ZBS_RX_WAIT_HEADER;
}
break;
case ZBS_RX_WAIT_MAC:
cmdbuffer[charindex] = lastchar;
charindex++;
if (charindex == 2) {
charindex = 0;
apInfo.mac[pktindex] = (uint8_t)strtoul(cmdbuffer, NULL, 16);
pktindex++;
}
if (pktindex == 8) {
RXState = ZBS_RX_WAIT_HEADER;
}
break;
case ZBS_RX_WAIT_CHANNEL:
cmdbuffer[charindex] = lastchar;
charindex++;
if (charindex == 2) {
RXState = ZBS_RX_WAIT_HEADER;
apInfo.channel = (uint8_t)strtoul(cmdbuffer, NULL, 16);
}
break;
case ZBS_RX_WAIT_POWER:
cmdbuffer[charindex] = lastchar;
charindex++;
if (charindex == 2) {
RXState = ZBS_RX_WAIT_HEADER;
apInfo.power = (uint8_t)strtoul(cmdbuffer, NULL, 16);
}
break;
case ZBS_RX_WAIT_PENDING:
cmdbuffer[charindex] = lastchar;
charindex++;
if (charindex == 2) {
RXState = ZBS_RX_WAIT_HEADER;
apInfo.pending = (uint8_t)strtoul(cmdbuffer, NULL, 16);
}
break;
case ZBS_RX_WAIT_NOP:
cmdbuffer[charindex] = lastchar;
charindex++;
if (charindex == 2) {
RXState = ZBS_RX_WAIT_HEADER;
apInfo.nop = (uint8_t)strtoul(cmdbuffer, NULL, 16);
}
break;
case ZBS_RX_WAIT_TYPE:
cmdbuffer[charindex] = lastchar;
charindex++;
if (charindex == 2) {
RXState = ZBS_RX_WAIT_HEADER;
apInfo.type = (uint8_t)strtoul(cmdbuffer, NULL, 16);
}
break;
}
}
vTaskDelay(1 / portTICK_PERIOD_MS);
} // end of while(1)
}
void ShowAPInfo() {
Serial.printf("+----------------------------+\n");
Serial.printf("| AP Information - type %02X |\n", apInfo.type);
Serial.printf("+----------------------------+\n");
Serial.printf("| Channel | 0x%02X |\n", apInfo.channel);
Serial.printf("| Power | %02X |\n", apInfo.power);
Serial.printf("| MAC | %02X%02X%02X%02X%02X%02X%02X%02X |\n", apInfo.mac[7], apInfo.mac[6], apInfo.mac[5], apInfo.mac[4], apInfo.mac[3], apInfo.mac[2], apInfo.mac[1], apInfo.mac[0]);
Serial.printf("| Version | 0x%04X |\n", apInfo.version);
Serial.printf("+----------------------------+\n");
}
void notifySegmentedFlash() {
sendAPSegmentedData(apInfo.mac, (String) "Fl ash", 0x0800, false, true);
vTaskDelay(2000 / portTICK_PERIOD_MS);
#ifdef POWER_NO_SOFT_POWER
sendAPSegmentedData(apInfo.mac, (String) "If done", 0x0800, false, true);
vTaskDelay(2000 / portTICK_PERIOD_MS);
sendAPSegmentedData(apInfo.mac, (String) "RE boot", 0x0800, false, true);
vTaskDelay(1000 / portTICK_PERIOD_MS);
#endif
}
void checkWaitPowerCycle() {
// check if we should wait for a power cycle. If we do, try to inform the user the best we can, and hang.
#ifdef POWER_NO_SOFT_POWER
apInfo.isOnline = false;
apInfo.state = AP_STATE_REQUIRED_POWER_CYCLE;
// If we have no soft power control, we'll now wait until the device is power-cycled
Serial.printf("Please power-cycle your AP/device\n");
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Aqua, CRGB::Aqua, CRGB::Red);
#endif
while (1) {
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
#endif
}
void segmentedShowIp() {
IPAddress IP = WiFi.localIP();
char temp[12];
vTaskDelay(2000 / portTICK_PERIOD_MS);
sendAPSegmentedData(apInfo.mac, (String) "IP Addr", 0x0200, true, true);
vTaskDelay(2000 / portTICK_PERIOD_MS);
sprintf(temp, "%03d IP %03d", IP[0], IP[1]);
sendAPSegmentedData(apInfo.mac, (String)temp, 0x0200, true, true);
vTaskDelay(2000 / portTICK_PERIOD_MS);
sprintf(temp, "%03d IP %03d", IP[2], IP[3]);
sendAPSegmentedData(apInfo.mac, (String)temp, 0x0200, true, true);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
bool bringAPOnline() {
apInfo.isOnline = false;
apInfo.state = AP_STATE_OFFLINE;
APTagReset();
vTaskDelay(500 / portTICK_PERIOD_MS);
uint32_t bootTimeout = millis();
bool APrdy = false;
while ((!APrdy) && (millis() - bootTimeout < 10 * 1000)) {
APrdy = sendPing();
vTaskDelay(300 / portTICK_PERIOD_MS);
}
if (!APrdy) {
return false;
} else {
apInfo.state = AP_STATE_COMING_ONLINE;
sendChannelPower(&curChannel);
vTaskDelay(200 / portTICK_PERIOD_MS);
if (!sendGetInfo()) {
apInfo.state = AP_STATE_OFFLINE;
return false;
}
vTaskDelay(200 / portTICK_PERIOD_MS);
apInfo.isOnline = true;
apInfo.state = AP_STATE_ONLINE;
return true;
}
}
void APTask(void* parameter) {
xTaskCreate(rxCmdProcessor, "rxCmdProcessor", 4000, NULL, configMAX_PRIORITIES - 10, NULL);
xTaskCreate(rxSerialTask, "rxSerialTask", 1750, NULL, configMAX_PRIORITIES - 4, NULL);
#if (AP_PROCESS_PORT == FLASHER_AP_PORT)
AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD);
#endif
#ifdef OPENEPAPERLINK_PCB
#if (AP_PROCESS_PORT == FLASHER_EXT_PORT)
AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_EXT_RXD, FLASHER_EXT_TXD);
#endif
#if (AP_PROCESS_PORT == FLASHER_ALTRADIO_PORT)
AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD);
#endif
#endif
bringAPOnline();
if (checkForcedAPFlash()) {
if (apInfo.type == SOLUM_SEG_UK && apInfo.isOnline) {
Serial.printf("Showing some stuff on the segmented display about our intentions to flash...\n");
notifySegmentedFlash();
}
Serial.printf("We're going to try to perform an 'AP forced flash' in\n");
flashCountDown(10);
Serial.printf("\nPerforming force flash of the AP\n");
apInfo.isOnline = false;
apInfo.state = AP_STATE_FLASHING;
doForcedAPFlash();
checkWaitPowerCycle();
bringAPOnline();
}
if (apInfo.isOnline) {
// AP works!
ShowAPInfo();
if (apInfo.type == SOLUM_SEG_UK) {
apInfo.state = AP_STATE_COMING_ONLINE;
segmentedShowIp();
showAPSegmentedInfo(apInfo.mac, true);
apInfo.state = AP_STATE_ONLINE;
updateContent(apInfo.mac);
}
uint16_t fsversion;
fsversion = getAPUpdateVersion(apInfo.type);
if ((fsversion) && (apInfo.version != fsversion)) {
Serial.printf("Firmware version on LittleFS: %04X\n", fsversion);
Serial.printf("We're going to try to update the AP's FW in\n");
flashCountDown(30);
Serial.printf("\n");
notifySegmentedFlash();
apInfo.isOnline = false;
apInfo.state = AP_STATE_FLASHING;
if (doAPUpdate(apInfo.type)) {
checkWaitPowerCycle();
Serial.printf("Flash completed, let's try to boot the AP!\n");
if (bringAPOnline()) {
// AP works
ShowAPInfo();
setAPchannel();
} else {
Serial.printf("Failed to bring up the AP after flashing seemed successful... That's not supposed to happen!\n");
Serial.printf("This can be caused by a bad AP firmware, failed or failing hardware, or the inability to fully power-cycle the AP\n");
apInfo.state = AP_STATE_FAILED;
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Red, CRGB::Yellow, CRGB::Red);
#endif
}
} else {
apInfo.state = AP_STATE_FAILED;
checkWaitPowerCycle();
Serial.println("Failed to update version on the AP :(\n");
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Red, CRGB::Red, CRGB::Red);
#endif
}
}
refreshAllPending();
} else {
#ifndef FLASH_TIMEOUT
#define FLASH_TIMEOUT 30
#endif
// AP unavailable, maybe time to flash?
apInfo.isOnline = false;
apInfo.state = AP_STATE_OFFLINE;
Serial.println("I wasn't able to connect to a ZBS (AP) tag.\n");
Serial.printf("This could be the first time this AP is booted and the AP-tag may be unflashed. We'll try to flash it!\n");
Serial.printf("If this tag was previously flashed succesfully but this message still shows up, there's probably something wrong with the serial connections.\n");
Serial.printf("The build of this firmware expects an AP tag with TXD/RXD on ESP32 pins %d and %d, does this match with your wiring?\n", FLASHER_AP_RXD, FLASHER_AP_TXD);
Serial.printf("Performing firmware flash in about %d seconds!\n", FLASH_TIMEOUT);
flashCountDown(FLASH_TIMEOUT);
if (doAPFlash()) {
checkWaitPowerCycle();
if (bringAPOnline()) {
// AP works
ShowAPInfo();
if (apInfo.type == SOLUM_SEG_UK) {
segmentedShowIp();
showAPSegmentedInfo(apInfo.mac, true);
}
refreshAllPending();
} else {
Serial.printf("Failed to bring up the AP after successful flashing... That's not supposed to happen!\n");
Serial.printf("This generally means that the flasher connections (MISO/MOSI/CLK/RESET/CS) are okay,\n");
Serial.printf("but we can't (yet) talk to the AP over serial lines. Verify the pins mentioned above.\n\n");
#ifndef POWER_NO_SOFT_POWER
Serial.printf("The firmware you're using expects soft power control over the AP tag; if it can't\n");
Serial.printf("power-cycle the AP-tag using GPIO pin %d, this can cause this very same issue.\n", APpowerPins[0]);
#endif
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Red, CRGB::Yellow, CRGB::Red);
#endif
apInfo.isOnline = false;
apInfo.state = AP_STATE_FAILED;
}
} else {
// failed to flash
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Red, CRGB::Red, CRGB::Red);
#endif
apInfo.isOnline = false;
apInfo.state = AP_STATE_FAILED;
Serial.println("Failed to flash the AP :(");
Serial.println("Seems like you're running into some issues with the wiring, or (very small chance) the tag itself");
Serial.println("This ESP32-build expects the following pins connected to the ZBS243:");
Serial.println("--- ZBS243 based tag ESP32 ---");
Serial.printf(" TXD ---------------- %02d\n", FLASHER_AP_RXD);
Serial.printf(" RXD ---------------- %02d\n", FLASHER_AP_TXD);
Serial.printf(" CS/SS ---------------- %02d\n", FLASHER_AP_SS);
Serial.printf(" MOSI ---------------- %02d\n", FLASHER_AP_MOSI);
Serial.printf(" MISO ---------------- %02d\n", FLASHER_AP_MISO);
Serial.printf(" CLK ---------------- %02d\n", FLASHER_AP_CLK);
Serial.printf(" RSET ---------------- %02d\n", FLASHER_AP_RESET);
#ifdef POWER_NO_SOFT_POWER
Serial.printf("Your firmware is configured without soft power control. This means you'll have to manually power-cycle the tag after flashing.\n");
#else
Serial.printf(" POWER ---------------- %02d\n", APpowerPins[0]);
#endif
Serial.println("Please verify your wiring and try again!");
}
#ifdef HAS_SDCARD
if (SD_CARD_CLK == FLASHER_AP_CLK ||
SD_CARD_MISO == FLASHER_AP_MISO ||
SD_CARD_MOSI == FLASHER_AP_MOSI) {
Serial.println("Reseting in 30 seconds to restore SPI state!\n");
flashCountDown(30);
ESP.restart();
}
#endif
}
uint8_t attempts = 0;
while (1) {
if ((apInfo.isOnline) && (millis() - lastAPActivity > AP_ACTIVITY_MAX_INTERVAL)) {
bool reply = sendPing();
if (!reply) {
attempts++;
} else {
attempts = 0;
}
if (attempts > 5) {
apInfo.state = AP_STATE_WAIT_RESET;
apInfo.isOnline = false;
if (!bringAPOnline()) {
// tried to reset the AP, but we failed... Maybe the AP-Tag died?
apInfo.state = AP_STATE_FAILED;
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Yellow, CRGB::Yellow, CRGB::Red);
#endif
} else {
apInfo.state = AP_STATE_ONLINE;
apInfo.isOnline = true;
attempts = 0;
refreshAllPending();
}
}
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}

View File

@@ -0,0 +1,118 @@
#include "serialconsole.h"
#include <Arduino.h>
#include "USB.h"
QueueHandle_t consoleCmdQueue;
TaskHandle_t consoleTaskHandle;
extern USBCDC USBSerial;
struct consoleCommand {
uint8_t command = 0;
uint8_t len = 0;
uint8_t* data = nullptr;
};
void consoleStopTask() {
if (consoleTaskHandle) vTaskDelete(consoleTaskHandle);
consoleTaskHandle = NULL;
}
/*
.in>1B 5B 3C 30 3B 33 39 3B 39 4D
,in>1B 5B 3C 30 3B 33 39 3B 39 6D
,in>1B 5B 3C 32 3B 33 39 3B 39 4D
,in>1B 5B 3C 32 3B 33 39 3B 39 6D
in>1B 5B 3C 30 3B 32 38 3B 31 32 4D
in>1B 5B 3C 30 3B 32 38 3B 31 32 6D
in>1B 5B 3C 32 3B 32 38 3B 31 32 4D
in>1B 5B 3C 32 3B 32 38 3B 31 32 6D
in>1B 5B 3C 36 34 3B 32 39 3B 31 32 4D
in>1B 5B 3C 36 35 3B 32 39 3B 31 32 4D
*/
bool escapeCommandComplete(struct consoleCommand* cmd) {
return true;
}
void consoleUartHandler(uint8_t* data, uint8_t len) {
static struct consoleCommand* cmd = nullptr;
static bool commandStarted = false;
while (len--) {
uint8_t usbbyte = *(data++);
if (cmd == nullptr) {
cmd = new struct consoleCommand;
cmd->data = (uint8_t*)calloc(65, 1);
cmd->len = 0;
}
// check if we've started a command in this byte
if ((!commandStarted) && (usbbyte == 0x1B)) {
commandStarted = true;
if (cmd->len != 0) {
BaseType_t queuestatus = xQueueSend(consoleCmdQueue, &cmd, 0);
if (queuestatus == pdFALSE) {
if (cmd->data != nullptr) free(cmd->data);
delete cmd;
}
cmd = nullptr;
}
}
if (cmd == nullptr) {
cmd = new struct consoleCommand;
cmd->data = (uint8_t*)calloc(65, 1);
cmd->len = 0;
}
cmd->data[cmd->len++] = usbbyte;
cmd->len %= 64;
if (commandStarted) {
if (escapeCommandComplete(cmd) || cmd->len == 0x00) {
BaseType_t queuestatus = xQueueSend(consoleCmdQueue, &cmd, 0);
if (queuestatus == pdFALSE) {
if (cmd->data != nullptr) free(cmd->data);
delete cmd;
}
commandStarted = false;
cmd = nullptr;
}
}
}
if (!commandStarted && cmd != nullptr) {
BaseType_t queuestatus = xQueueSend(consoleCmdQueue, &cmd, 0);
if (queuestatus == pdFALSE) {
if (cmd->data != nullptr) free(cmd->data);
delete cmd;
}
cmd = nullptr;
}
}
void consoleTask(void* parameter) {
struct consoleCommand* cmd;
USBSerial.print("\e[?1000;1006;1015h"); // works
while (true) {
BaseType_t queuereceive = xQueueReceive(consoleCmdQueue, &cmd, 1500 / portTICK_PERIOD_MS);
if (queuereceive == pdTRUE) {
uint8_t c = 0;
Serial.printf("queue>");
while (cmd->len--) {
Serial.printf(" %02X", cmd->data[c]);
c++;
}
if (cmd->data != nullptr) free(cmd->data);
delete cmd;
Serial.printf("\n");
}
}
}

View File

@@ -0,0 +1,215 @@
#include "storage.h"
#ifdef HAS_SDCARD
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#endif
#include "LittleFS.h"
DynStorage::DynStorage() : isInited(0) {}
static void initLittleFS() {
LittleFS.begin();
contentFS = &LittleFS;
}
#ifdef HAS_SDCARD
static SPIClass* spi;
static void initSDCard() {
uint8_t spi_bus = VSPI;
// SD.begin and spi.begin are allocating memory so we dont want to do that
if(!spi) {
spi = new SPIClass(spi_bus);
spi->begin(SD_CARD_CLK, SD_CARD_MISO, SD_CARD_MOSI, SD_CARD_SS);
bool res = SD.begin(SD_CARD_SS, *spi, 40000000);
if (!res) {
Serial.println("Card Mount Failed");
return;
}
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
contentFS = &SD;
}
#endif
size_t DynStorage::freeSpace(){
this->begin();
#ifdef HAS_SDCARD
return SD.totalBytes() - SD.usedBytes();
#endif
return LittleFS.totalBytes() - LittleFS.usedBytes();
}
void copyFile(File in, File out) {
Serial.print("Copying ");
Serial.print(in.path());
Serial.print(" to ");
Serial.println(out.path());
size_t n;
uint8_t buf[64];
while ((n = in.read(buf, sizeof(buf))) > 0) {
out.write(buf, n);
}
}
void copyBetweenFS(FS& sourceFS, const char* source_path, FS& targetFS) {
File root = sourceFS.open(source_path);
char next_path[128];
if (root.isDirectory()) {
if (!contentFS->exists(root.path())) {
if (!contentFS->mkdir(root.path())) {
Serial.print("Failed to create directory ");
Serial.println(root.path());
return;
}
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
sprintf(next_path, "%s/%s\0", root.path(), file.path());
copyBetweenFS(sourceFS, file.path(), targetFS);
} else {
File target = contentFS->open(file.path(), "w");
if (target) {
copyFile(file, target);
target.close();
file.close();
} else {
Serial.print("Couldn't create high target file");
Serial.println(file.path());
return;
}
}
file = root.openNextFile();
}
} else {
File target = contentFS->open(root.path(), "w");
if (target) {
copyFile(root, target);
} else {
Serial.print("Couldn't create target file ");
Serial.println(root.path());
return;
}
}
}
#ifdef HAS_SDCARD
void copyIfNeeded(const char* path) {
if (!contentFS->exists(path) && LittleFS.exists(path)) {
Serial.printf("SDCard does not contain %s, littleFS does, copying\n", path);
copyBetweenFS(LittleFS, path, *contentFS);
}
}
#endif
void DynStorage::begin() {
initLittleFS();
#ifdef HAS_SDCARD
initSDCard();
copyIfNeeded("/index.html");
copyIfNeeded("/fonts");
copyIfNeeded("/www");
copyIfNeeded("/AP_FW_Pack.bin");
copyIfNeeded("/tag_md5_db.json");
copyIfNeeded("/update_actions.json");
copyIfNeeded("/content_template.json");
#endif
if (!contentFS->exists("/current")) {
contentFS->mkdir("/current");
}
if (!contentFS->exists("/temp")) {
contentFS->mkdir("/temp");
}
}
void DynStorage::end() {
#ifdef HAS_SDCARD
initLittleFS();
if (SD_CARD_CLK == FLASHER_AP_CLK ||
SD_CARD_MISO == FLASHER_AP_MISO ||
SD_CARD_MOSI == FLASHER_AP_MOSI) {
Serial.println("Tearing down SD card connection");
copyBetweenFS(*contentFS, "/tag_md5_db.json", LittleFS);
copyBetweenFS(*contentFS, "/AP_FW_Pack.bin", LittleFS);
if (contentFS->exists("/AP_force_flash.bin")) {
copyBetweenFS(*contentFS, "/AP_force_flash.bin", LittleFS);
contentFS->remove("/AP_force_flash.bin");
}
Serial.println("Swapping to LittleFS");
contentFS = &LittleFS;
}
#endif
}
void listDir(fs::FS& fs, const char* dirname, uint8_t levels) {
Storage.begin();
// Print blank line on screen
Serial.printf(" \n ");
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (!strcmp("System Volume Information", file.name())) {
file = root.openNextFile();
continue;
}
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.path(), levels - 1);
}
Serial.println();
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void DynStorage::listFiles() {
listDir(LittleFS, "/", 1);
#ifdef HAS_SDCARD
listDir(*contentFS, "/", 1);
#endif
}
fs::FS* contentFS;
DynStorage Storage;

View File

@@ -0,0 +1,80 @@
#include "system.h"
#include <Arduino.h>
#include <FS.h>
#include "storage.h"
void init_time() {
struct tm timeinfo;
while (true) {
if (!getLocalTime(&timeinfo)) {
Serial.println("Waiting for valid time from NTP-server");
vTaskDelay(1000 / portTICK_PERIOD_MS);
} else {
break;
}
}
}
void logLine(char* buffer) {
logLine(String(buffer));
}
void logLine(String text) {
time_t now;
time(&now);
char timeStr[24];
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S ", localtime(&now));
File logFile = contentFS->open("/log.txt", "a");
if (logFile) {
logFile.print(timeStr);
logFile.println(text);
logFile.close();
}
}
void logStartUp() {
esp_reset_reason_t resetReason = esp_reset_reason();
String logEntry = "Reboot. Reason: ";
switch (resetReason) {
case ESP_RST_POWERON:
logEntry += "Power-on";
break;
case ESP_RST_EXT:
logEntry += "External";
break;
case ESP_RST_SW:
logEntry += "Software";
break;
case ESP_RST_PANIC:
logEntry += "Panic";
break;
case ESP_RST_INT_WDT:
logEntry += "Watchdog";
break;
case ESP_RST_TASK_WDT:
logEntry += "Task Watchdog";
break;
case ESP_RST_WDT:
logEntry += "Other Watchdog";
break;
case ESP_RST_DEEPSLEEP:
logEntry += "Deep Sleep";
break;
case ESP_RST_BROWNOUT:
logEntry += "Brownout";
break;
case ESP_RST_SDIO:
logEntry += "SDIO";
break;
default:
logEntry += "Unknown";
break;
}
logLine(logEntry);
}

View File

@@ -2,29 +2,37 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#include <FS.h>
#include <vector>
#include "LittleFS.h"
#include "storage.h"
#include "language.h"
std::vector<tagRecord*> tagDB;
tagRecord* tagRecord::findByMAC(uint8_t mac[6]) {
Config config;
// SemaphoreHandle_t tagDBOwner;
tagRecord* tagRecord::findByMAC(uint8_t mac[8]) {
for (int16_t c = 0; c < tagDB.size(); c++) {
tagRecord* tag = nullptr;
tag = tagDB.at(c);
if (memcmp(tag->mac, mac, 6) == 0) {
if (memcmp(tag->mac, mac, 8) == 0) {
return tag;
}
}
return nullptr;
}
bool deleteRecord(uint8_t mac[6]) {
bool deleteRecord(uint8_t mac[8]) {
for (int16_t c = 0; c < tagDB.size(); c++) {
tagRecord* tag = nullptr;
tag = tagDB.at(c);
if (memcmp(tag->mac, mac, 6) == 0) {
if (memcmp(tag->mac, mac, 8) == 0) {
if (tag->data != nullptr) {
free(tag->data);
}
tag->data = nullptr;
delete tagDB[c];
tagDB.erase(tagDB.begin() + c);
return true;
@@ -33,8 +41,29 @@ bool deleteRecord(uint8_t mac[6]) {
return false;
}
String tagDBtoJson(uint8_t mac[6], uint8_t startPos) {
DynamicJsonDocument doc(2500);
void mac2hex(uint8_t* mac, char* hexBuffer) {
sprintf(hexBuffer, "%02X%02X%02X%02X%02X%02X%02X%02X",
mac[7], mac[6], mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]);
}
bool hex2mac(const String& hexString, uint8_t* mac) {
size_t hexLength = hexString.length();
if (hexLength != 12 && hexLength != 16) {
return false;
}
if (hexLength / 2 == 6) {
mac[6] = 0;
mac[7] = 0;
return (sscanf(hexString.c_str(), "%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
&mac[5], &mac[4], &mac[3], &mac[2], &mac[1], &mac[0]) == 6);
} else {
return (sscanf(hexString.c_str(), "%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
&mac[7], &mac[6], &mac[5], &mac[4], &mac[3], &mac[2], &mac[1], &mac[0]) == 8);
}
}
String tagDBtoJson(uint8_t mac[8], uint8_t startPos) {
DynamicJsonDocument doc(5000);
JsonArray tags = doc.createNestedArray("tags");
for (int16_t c = startPos; c < tagDB.size(); c++) {
@@ -43,7 +72,7 @@ String tagDBtoJson(uint8_t mac[6], uint8_t startPos) {
bool select = false;
if (mac) {
if (memcmp(taginfo->mac, mac, 6) == 0) {
if (memcmp(taginfo->mac, mac, 8) == 0) {
select = true;
}
} else {
@@ -56,7 +85,7 @@ String tagDBtoJson(uint8_t mac[6], uint8_t startPos) {
break;
}
}
if (doc.capacity()-doc.memoryUsage() < doc.memoryUsage()/(c+1) + 100) {
if (doc.capacity() - doc.memoryUsage() < doc.memoryUsage()/(c+1) + 150) {
doc["continu"] = c+1;
break;
}
@@ -65,10 +94,9 @@ String tagDBtoJson(uint8_t mac[6], uint8_t startPos) {
}
void fillNode(JsonObject &tag, tagRecord* &taginfo) {
char buffer[16];
sprintf(buffer, "%02X%02X%02X%02X%02X%02X\0", taginfo->mac[0], taginfo->mac[1], taginfo->mac[2], taginfo->mac[3], taginfo->mac[4], taginfo->mac[5]);
tag["mac"] = (String)buffer;
char hexmac[17];
mac2hex(taginfo->mac, hexmac);
tag["mac"] = String(hexmac);
char hex[33];
for (uint8_t i = 0; i < 16; i++) {
sprintf(hex + (i * 2), "%02x", taginfo->md5[i]);
@@ -88,6 +116,11 @@ void fillNode(JsonObject &tag, tagRecord* &taginfo) {
tag["wakeupReason"] = taginfo->wakeupReason;
tag["capabilities"] = taginfo->capabilities;
tag["modecfgjson"] = taginfo->modeConfigJson;
tag["isexternal"] = taginfo->isExternal;
tag["rotate"] = taginfo->rotate;
tag["lut"] = taginfo->lut;
tag["ch"] = taginfo->currentChannel;
tag["ver"] = taginfo->tagSoftwareVersion;
}
void saveDB(String filename) {
@@ -95,8 +128,8 @@ void saveDB(String filename) {
long t = millis();
LittleFS.begin();
fs::File file = LittleFS.open(filename, "w");
Storage.begin();
fs::File file = contentFS->open(filename, "w");
if (!file) {
Serial.println("saveDB: Failed to open file");
return;
@@ -114,7 +147,7 @@ void saveDB(String filename) {
if (c > 0) {
file.write(',');
}
serializeJson(doc, file);
serializeJsonPretty(doc, file);
}
file.write(']');
@@ -127,11 +160,11 @@ void saveDB(String filename) {
void loadDB(String filename) {
StaticJsonDocument<1000> doc;
Serial.println("start reading DB from file");
Serial.println("reading DB from file");
long t = millis();
LittleFS.begin();
fs::File readfile = LittleFS.open(filename, "r");
Storage.begin();
fs::File readfile = contentFS->open(filename, "r");
if (!readfile) {
Serial.println("loadDB: Failed to open file");
return;
@@ -147,8 +180,8 @@ void loadDB(String filename) {
if (!err) {
JsonObject tag = doc[0];
String dst = tag["mac"].as<String>();
uint8_t mac[12];
if (sscanf(dst.c_str(), "%02X%02X%02X%02X%02X%02X", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6) {
uint8_t mac[8];
if (hex2mac(dst, mac)) {
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(mac);
if (taginfo == nullptr) {
@@ -180,6 +213,11 @@ void loadDB(String filename) {
taginfo->wakeupReason = tag["wakeupReason"];
taginfo->capabilities = tag["capabilities"];
taginfo->modeConfigJson = tag["modecfgjson"].as<String>();
taginfo->isExternal = tag["isexternal"].as<bool>();
taginfo->rotate = tag["rotate"] | 0;
taginfo->lut = tag["lut"] | 0;
taginfo->currentChannel = tag["ch"] | 0;
taginfo->tagSoftwareVersion = tag["ver"] | 0;
}
} else {
Serial.print(F("deserializeJson() failed: "));
@@ -191,8 +229,74 @@ void loadDB(String filename) {
}
readfile.close();
Serial.println(millis() - t);
Serial.println("finished reading file");
return;
}
void destroyDB() {
Serial.println("destoying DB");
Serial.printf("before, free heap: %d\n", ESP.getFreeHeap());
for (int16_t c = 0; c < tagDB.size(); c++) {
tagRecord* tag = nullptr;
tag = tagDB.at(c);
if (tag->data != nullptr) {
free(tag->data);
}
tag->data = nullptr;
delete tagDB[c];
tagDB.erase(tagDB.begin() + c);
}
Serial.printf("after, free heap: %d\n", ESP.getFreeHeap());
}
uint8_t getTagCount() {
uint8_t tagcount = 0;
for (int16_t c = 0; c < tagDB.size(); c++) {
tagRecord* taginfo = nullptr;
taginfo = tagDB.at(c);
if (taginfo->isExternal == false) tagcount++;
}
return tagcount;
}
void clearPending(tagRecord* taginfo) {
taginfo->filename = String();
if (taginfo->data != nullptr) {
free(taginfo->data);
taginfo->data = nullptr;
}
taginfo->pending = false;
}
void initAPconfig() {
Storage.begin();
DynamicJsonDocument APconfig(500);
File configFile = contentFS->open("/current/apconfig.json", "r");
if (configFile) {
DeserializationError error = deserializeJson(APconfig, configFile);
if (error) {
configFile.close();
Serial.println("failed to read apconfig.json. Using default config");
Serial.println(error.c_str());
}
configFile.close();
}
config.channel = APconfig["channel"] | 0;
if (APconfig["alias"]) strlcpy(config.alias, APconfig["alias"], sizeof(config.alias));
config.led = APconfig["led"] | 255;
config.language = APconfig["language"] | getDefaultLanguage();
config.maxsleep = APconfig["maxsleep"] | 10;
config.stopsleep = APconfig["stopsleep"] | 1;
}
void saveAPconfig() {
fs::File configFile = contentFS->open("/current/apconfig.json", "w");
DynamicJsonDocument APconfig(500);
APconfig["channel"] = config.channel;
APconfig["alias"] = config.alias;
APconfig["led"] = config.led;
APconfig["language"] = config.language;
APconfig["maxsleep"] = config.maxsleep;
APconfig["stopsleep"] = config.stopsleep;
serializeJsonPretty(APconfig, configFile);
configFile.close();
}

View File

@@ -0,0 +1,196 @@
#include <Arduino.h>
#include <WiFi.h>
#include "AsyncUDP.h"
#include "commstructs.h"
#include "newproto.h"
#include "tag_db.h"
#include "web.h"
#include "serialap.h"
#include "udp.h"
#define UDPIP IPAddress(239, 10, 0, 1)
#define UDPPORT 16033
UDPcomm udpsync;
extern uint8_t channelList[6];
extern espSetChannelPower curChannel;
void init_udp() {
udpsync.init();
}
UDPcomm::UDPcomm() {
// Constructor
}
UDPcomm::~UDPcomm() {
// Destructor
}
void UDPcomm::init() {
if (udp.listenMulticast(UDPIP, UDPPORT)) {
udp.onPacket([this](AsyncUDPPacket packet) {
if (packet.remoteIP() != WiFi.localIP()) {
this->processPacket(packet);
}
});
}
setAPchannel();
}
void UDPcomm::processPacket(AsyncUDPPacket packet) {
if (config.runStatus == RUNSTATUS_STOP) return;
switch (packet.data()[0]) {
case PKT_AVAIL_DATA_INFO: {
espAvailDataReq adr;
memset(&adr, 0, sizeof(espAvailDataReq));
memcpy(&adr, &packet.data()[1], std::min(packet.length() - 1, sizeof(espAvailDataReq)));
processDataReq(&adr, false);
break;
}
case PKT_XFER_COMPLETE: {
espXferComplete xfc;
memset(&xfc, 0, sizeof(espXferComplete));
memcpy(&xfc, &packet.data()[1], std::min(packet.length() - 1, sizeof(espXferComplete)));
processXferComplete(&xfc, false);
break;
}
case PKT_XFER_TIMEOUT: {
espXferComplete xfc;
memset(&xfc, 0, sizeof(espXferComplete));
memcpy(&xfc, &packet.data()[1], std::min(packet.length() - 1, sizeof(espXferComplete)));
processXferTimeout(&xfc, false);
break;
}
case PKT_AVAIL_DATA_REQ: {
pendingData pending;
memset(&pending, 0, sizeof(pendingData));
memcpy(&pending, &packet.data()[1], std::min(packet.length() - 1, sizeof(pendingData)));
prepareExternalDataAvail(&pending, packet.remoteIP());
break;
}
case PKT_APLIST_REQ: {
IPAddress senderIP = packet.remoteIP();
APlist APitem;
APitem.src = WiFi.localIP();
strcpy(APitem.alias, config.alias);
APitem.channelId = curChannel.channel;
APitem.tagCount = getTagCount();
APitem.version = apInfo.version;
uint8_t buffer[sizeof(struct APlist) + 1];
buffer[0] = PKT_APLIST_REPLY;
memcpy(buffer + 1, &APitem, sizeof(struct APlist));
udp.writeTo(buffer, sizeof(buffer), senderIP, UDPPORT);
break;
}
case PKT_APLIST_REPLY: {
APlist APreply;
memset(&APreply, 0, sizeof(APlist));
memcpy(&APreply, &packet.data()[1], std::min(packet.length() - 1, sizeof(APlist)));
// remove active channel from list
for (int i = 0; i < 6; i++) {
if (channelList[i] == APreply.channelId) channelList[i] = 0;
}
wsSendAPitem(&APreply);
break;
}
case PKT_TAGINFO: {
uint16_t syncversion = (packet.data()[2] << 8) | packet.data()[1];
if (syncversion != SYNC_VERSION) {
wsErr("Got a packet from " + packet.remoteIP().toString() + " with mismatched udp sync version. Update firmware!");
} else {
TagInfo* taginfoitem = (TagInfo*)&packet.data()[1];
updateTaginfoitem(taginfoitem);
}
}
}
}
void autoselect(void* pvParameters) {
// reset channel list
uint8_t values[] = {11, 15, 20, 25, 26, 27};
memcpy(channelList, values, sizeof(values));
// wait 5s for channelList to collect all AP's
vTaskDelay(5000 / portTICK_PERIOD_MS);
curChannel.channel = 0;
for (int i = 0; i < 6; i++) {
if (channelList[i] > 0) {
curChannel.channel = channelList[i];
break;
}
}
if (curChannel.channel == 0) {
curChannel.channel = 11;
}
config.channel = curChannel.channel;
do {
vTaskDelay(1000 / portTICK_PERIOD_MS);
} while (!apInfo.isOnline);
sendChannelPower(&curChannel);
saveAPconfig();
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
void UDPcomm::getAPList() {
APlist APitem;
APitem.src = WiFi.localIP();
strcpy(APitem.alias, config.alias);
APitem.channelId = curChannel.channel;
APitem.tagCount = getTagCount();
APitem.version = apInfo.version;
wsSendAPitem(&APitem);
if (config.alias == 0) {
xTaskCreate(autoselect, "autoselect", 5000, NULL, configMAX_PRIORITIES - 10, NULL);
}
uint8_t buffer[sizeof(struct APlist) + 1];
buffer[0] = PKT_APLIST_REQ;
memcpy(buffer + 1, &APitem, sizeof(struct APlist));
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
}
void UDPcomm::netProcessDataReq(struct espAvailDataReq* eadr) {
uint8_t buffer[sizeof(struct espAvailDataReq) + 1];
buffer[0] = PKT_AVAIL_DATA_INFO;
memcpy(buffer + 1, eadr, sizeof(struct espAvailDataReq));
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
}
void UDPcomm::netProcessXferComplete(struct espXferComplete* xfc) {
uint8_t buffer[sizeof(struct espXferComplete) + 1];
buffer[0] = PKT_XFER_COMPLETE;
memcpy(buffer + 1, xfc, sizeof(struct espXferComplete));
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
}
void UDPcomm::netProcessXferTimeout(struct espXferComplete* xfc) {
uint8_t buffer[sizeof(struct espXferComplete) + 1];
buffer[0] = PKT_XFER_TIMEOUT;
memcpy(buffer + 1, xfc, sizeof(struct espXferComplete));
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
}
void UDPcomm::netSendDataAvail(struct pendingData* pending) {
uint8_t buffer[sizeof(struct pendingData) + 1];
buffer[0] = PKT_AVAIL_DATA_REQ;
memcpy(buffer + 1, pending, sizeof(struct pendingData));
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
}
void UDPcomm::netTaginfo(struct TagInfo* taginfoitem) {
uint8_t buffer[sizeof(struct TagInfo) + 1];
buffer[0] = PKT_TAGINFO;
memcpy(buffer + 1, taginfoitem, sizeof(struct TagInfo));
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
}

View File

@@ -0,0 +1,399 @@
#include <Arduino.h>
#include "USB.h"
#include "powermgt.h"
#include "serialconsole.h"
#include "settings.h"
#include "zbs_interface.h"
USBCDC USBSerial;
QueueHandle_t flasherCmdQueue;
uint32_t usbConnectedStartTime = 0;
#define FLASHER_WAIT_A 0
#define FLASHER_WAIT_T 1
#define FLASHER_WAIT_CMD 2
#define FLASHER_WAIT_LEN 3
#define FLASHER_WAIT_DATA 4
#define FLASHER_WAIT_CRCH 5
#define FLASHER_WAIT_CRCL 6
struct flasherCommand {
uint8_t command = 0;
uint8_t len = 0;
uint8_t* data = nullptr;
};
#define FLASHER_MODE_UNKNOWN 0
#define FLASHER_MODE_FLASHER 1
#define FLASHER_MODE_CONSOLE 2
volatile uint8_t usbFlasherMode = FLASHER_MODE_UNKNOWN;
void enterConsoleMode() {
usbFlasherMode = FLASHER_MODE_CONSOLE;
xTaskCreate(consoleTask, "consoleTask", 10000, NULL, 2, &consoleTaskHandle);
}
int8_t powerPins[] = FLASHER_AP_POWER;
#ifdef OPENEPAPERLINK_PCB
int8_t powerPins2[] = FLASHER_EXT_POWER;
int8_t powerPins3[] = FLASHER_ALT_POWER;
#endif
void sendFlasherAnswer(uint8_t answer_cmd, uint8_t* ans_buff, uint8_t len) {
uint8_t* answer_buffer = (uint8_t*)calloc(2 + 2 + len + 2, 1);
if (answer_buffer == nullptr) return;
uint32_t CRC_value = 0xAB34;
answer_buffer[0] = 'A';
answer_buffer[1] = 'T';
answer_buffer[2] = answer_cmd;
CRC_value += answer_cmd;
answer_buffer[3] = len;
CRC_value += len;
for (int i = 0; i < len; i++) {
answer_buffer[4 + i] = ans_buff[i];
CRC_value += ans_buff[i];
}
answer_buffer[2 + 2 + len] = CRC_value >> 8;
answer_buffer[2 + 2 + len + 1] = CRC_value;
USBSerial.write(answer_buffer, 2 + 2 + len + 2);
free(answer_buffer);
}
void flasherUartHandler(uint8_t* data, uint8_t len) {
static struct flasherCommand* cmd;
static uint8_t flasherSerialState = FLASHER_WAIT_A;
static uint8_t flasherCmdDataIndex = 0;
static uint16_t flasherCRC = 0xAB34;
static uint32_t flasherLastCmd = 0;
if ((flasherSerialState != FLASHER_WAIT_A) && (millis() - flasherLastCmd >= 225)) {
flasherSerialState = FLASHER_WAIT_A;
}
while (len--) {
uint8_t usbbyte = *(data++);
switch (flasherSerialState) {
case FLASHER_WAIT_A:
if (usbbyte == 'A') {
flasherSerialState = FLASHER_WAIT_T;
flasherLastCmd = millis();
} else {
enterConsoleMode();
}
break;
case FLASHER_WAIT_T:
if (usbbyte == 'T') {
flasherSerialState = FLASHER_WAIT_CMD;
cmd = new flasherCommand;
flasherCRC = 0xAB34;
flasherCmdDataIndex = 0;
} else {
flasherSerialState = FLASHER_WAIT_A;
}
break;
case FLASHER_WAIT_CMD:
cmd->command = usbbyte;
flasherCRC += usbbyte;
flasherSerialState = FLASHER_WAIT_LEN;
break;
case FLASHER_WAIT_LEN:
flasherCRC += usbbyte;
if (usbbyte) {
cmd->len = usbbyte;
cmd->data = (uint8_t*)calloc(usbbyte, 1);
flasherSerialState = FLASHER_WAIT_DATA;
} else {
flasherSerialState = FLASHER_WAIT_CRCH;
}
break;
case FLASHER_WAIT_DATA:
flasherCRC += usbbyte;
cmd->data[flasherCmdDataIndex++] = usbbyte;
if (flasherCmdDataIndex == cmd->len) {
flasherSerialState = FLASHER_WAIT_CRCH;
}
break;
case FLASHER_WAIT_CRCH:
flasherCRC -= ((uint16_t)usbbyte << 8);
flasherSerialState = FLASHER_WAIT_CRCL;
break;
case FLASHER_WAIT_CRCL:
flasherCRC -= ((uint16_t)usbbyte);
if (flasherCRC) {
Serial.printf("CRC failed for flasher command :( %04X\n", flasherCRC);
cmd = nullptr;
} else {
if (usbFlasherMode == FLASHER_MODE_UNKNOWN) usbFlasherMode = FLASHER_MODE_FLASHER;
BaseType_t queuestatus = xQueueSend(flasherCmdQueue, &cmd, 0);
if (queuestatus == pdFALSE) {
if (cmd->data != nullptr) free(cmd->data);
delete cmd;
}
cmd = nullptr;
}
flasherSerialState = FLASHER_WAIT_A;
break;
}
}
}
void resetFlasherState() {
if (usbFlasherMode != FLASHER_MODE_UNKNOWN) {
if (usbFlasherMode == FLASHER_MODE_CONSOLE) consoleStopTask();
Serial.print("Resetting flasher state");
usbFlasherMode = FLASHER_MODE_UNKNOWN;
usbConnectedStartTime = millis();
}
}
static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
if (event_base == ARDUINO_USB_EVENTS) {
arduino_usb_event_data_t* data = (arduino_usb_event_data_t*)event_data;
switch (event_id) {
case ARDUINO_USB_STARTED_EVENT:
Serial.println("USB PLUGGED");
resetFlasherState();
break;
case ARDUINO_USB_STOPPED_EVENT:
Serial.println("USB UNPLUGGED");
resetFlasherState();
break;
case ARDUINO_USB_SUSPEND_EVENT:
Serial.printf("USB SUSPENDED: remote_wakeup_en: %u\n", data->suspend.remote_wakeup_en);
break;
case ARDUINO_USB_RESUME_EVENT:
Serial.println("USB RESUMED");
break;
default:
ets_printf("other USB event %d\n", event_id);
break;
}
} else if (event_base == ARDUINO_USB_CDC_EVENTS) {
arduino_usb_cdc_event_data_t* data = (arduino_usb_cdc_event_data_t*)event_data;
switch (event_id) {
case ARDUINO_USB_CDC_CONNECTED_EVENT:
ets_printf("CDC CONNECTED\n");
resetFlasherState();
usbConnectedStartTime = millis();
break;
case ARDUINO_USB_CDC_DISCONNECTED_EVENT:
ets_printf("CDC DISCONNECTED\n");
resetFlasherState();
break;
case ARDUINO_USB_CDC_LINE_STATE_EVENT:
ets_printf("CDC LINE STATE: dtr: %u, rts: %u\n", data->line_state.dtr, data->line_state.rts);
if (data->line_state.dtr == 0) resetFlasherState();
break;
case ARDUINO_USB_CDC_LINE_CODING_EVENT:
ets_printf("CDC LINE CODING: bit_rate: %u, data_bits: %u, stop_bits: %u, parity: %u\n", data->line_coding.bit_rate, data->line_coding.data_bits, data->line_coding.stop_bits, data->line_coding.parity);
resetFlasherState();
break;
case ARDUINO_USB_CDC_RX_EVENT:
// Serial.printf("CDC RX [%u]:", data->rx.len);
{
uint8_t buf[data->rx.len];
size_t len = USBSerial.read(buf, data->rx.len);
if (usbFlasherMode != FLASHER_MODE_CONSOLE) {
flasherUartHandler(buf, len);
} else {
consoleUartHandler(buf, len);
}
}
break;
case ARDUINO_USB_CDC_RX_OVERFLOW_EVENT:
Serial.printf("CDC RX Overflow of %d bytes", data->rx_overflow.dropped_bytes);
break;
default:
break;
}
}
}
typedef enum {
CMD_GET_VERSION = 1,
CMD_RESET_ESP = 2,
CMD_ZBS_BEGIN = 10,
CMD_RESET_ZBS = 11,
CMD_SELECT_PAGE = 12,
CMD_SET_POWER = 13,
CMD_READ_RAM = 20,
CMD_WRITE_RAM = 21,
CMD_READ_FLASH = 22,
CMD_WRITE_FLASH = 23,
CMD_READ_SFR = 24,
CMD_WRITE_SFR = 25,
CMD_ERASE_FLASH = 26,
CMD_ERASE_INFOBLOCK = 27,
CMD_SAVE_MAC_FROM_FW = 40,
CMD_PASS_THROUGH = 50,
} ZBS_UART_PROTO;
uint32_t FLASHER_VERSION = 0x0000002F;
static class ZBS_interface* zbs = nullptr;
void processFlasherCommand(struct flasherCommand* cmd) {
uint8_t* tempbuffer;
uint8_t temp_buff[16];
uint32_t spi_speed = 0;
uint8_t powerPinCount = 1;
static uint32_t curspeed = 0;
switch (cmd->command) {
case CMD_GET_VERSION:
temp_buff[0] = FLASHER_VERSION >> 24;
temp_buff[1] = FLASHER_VERSION >> 16;
temp_buff[2] = FLASHER_VERSION >> 8;
temp_buff[3] = FLASHER_VERSION;
sendFlasherAnswer(cmd->command, temp_buff, 4);
break;
case CMD_RESET_ESP:
sendFlasherAnswer(cmd->command, NULL, 0);
delay(100);
ESP.restart();
break;
case CMD_ZBS_BEGIN:
if (zbs != nullptr) {
delete zbs;
}
zbs = new ZBS_interface;
if (cmd->data[0] & 1) {
spi_speed = 1000000;
} else {
spi_speed = 8000000;
}
curspeed = spi_speed;
if (cmd->data[0] & 2) {
powerPinCount = powerPins[0] != -1 ? sizeof(powerPins) : 0;
temp_buff[0] = zbs->begin(FLASHER_AP_SS, FLASHER_AP_CLK, FLASHER_AP_MOSI, FLASHER_AP_MISO, FLASHER_AP_RESET, (uint8_t*)powerPins, powerPinCount, spi_speed);
} else if (cmd->data[0] & 4) {
#ifdef OPENEPAPERLINK_PCB
powerPinCount = powerPins3[0] != -1 ? sizeof(powerPins3) : 0;
temp_buff[0] = zbs->begin(FLASHER_ALT_SS, FLASHER_ALT_CLK, FLASHER_ALT_MOSI, FLASHER_ALT_MISO, FLASHER_ALT_RESET, (uint8_t*)powerPins3, powerPinCount, spi_speed);
#endif
} else {
#ifdef OPENEPAPERLINK_PCB
powerPinCount = powerPins2[0] != -1 ? sizeof(powerPins2) : 0;
temp_buff[0] = zbs->begin(FLASHER_EXT_SS, FLASHER_EXT_CLK, FLASHER_EXT_MOSI, FLASHER_EXT_MISO, FLASHER_EXT_RESET, (uint8_t*)powerPins2, powerPinCount, spi_speed);
#endif
}
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_RESET_ZBS:
zbs->reset();
temp_buff[0] = 1;
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_SELECT_PAGE:
temp_buff[0] = zbs->select_flash(cmd->data[0] ? 1 : 0);
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_SET_POWER:
zbs->set_power(cmd->data[0] ? 1 : 0);
temp_buff[0] = 1;
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_READ_RAM:
temp_buff[0] = zbs->read_ram(cmd->data[0]);
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_WRITE_RAM:
zbs->write_ram(cmd->data[0], cmd->data[1]);
temp_buff[0] = 1;
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_READ_FLASH:
tempbuffer = (uint8_t*)calloc(cmd->data[0], 1);
// cmd_buff[0] = len
// cmd_buff[1] << 8 | cmd_buff[2] = position
// Serial.printf("Loading %d bytes from %04X \n", cmd->data[0], (cmd->data[1] << 8 | cmd->data[2]));
for (int i = 0; i < cmd->data[0]; i++) {
tempbuffer[i] = zbs->read_flash((cmd->data[1] << 8 | cmd->data[2]) + i);
}
sendFlasherAnswer(cmd->command, tempbuffer, cmd->data[0]);
free(tempbuffer);
break;
case CMD_WRITE_FLASH:
// cmd_buff[0] = len
// cmd_buff[1] << 8 | cmd_buff[2] = position
// cmd_buff[3+i] = data
if (cmd->data[0] >= (0xff - 3)) { // Len too high, only 0xFF - header len possible
temp_buff[0] = 0xEE;
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
}
// Serial.printf("Writing %d bytes to %04X \n", cmd->data[0], (cmd->data[1] << 8 | cmd->data[2]));
for (int i = 0; i < cmd->data[0]; i++) {
if (cmd->data[3 + i] != 0xff) {
for (uint8_t attempts = 0; attempts < 10; attempts++) {
zbs->write_flash((cmd->data[1] << 8 | cmd->data[2]) + i, cmd->data[3 + i]);
if (zbs->read_flash((cmd->data[1] << 8 | cmd->data[2]) + i) == cmd->data[3 + i]) {
goto flash_pass;
}
curspeed -= 100000;
zbs->setSpeed(curspeed);
}
flash_fail:
temp_buff[0] = 0;
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
flash_pass:
continue;
}
}
temp_buff[0] = 1;
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_READ_SFR:
temp_buff[0] = zbs->read_sfr(cmd->data[0]);
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_WRITE_SFR:
zbs->write_sfr(cmd->data[0], cmd->data[1]);
temp_buff[0] = 1;
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_ERASE_FLASH:
zbs->erase_flash();
temp_buff[0] = 1;
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_ERASE_INFOBLOCK:
zbs->erase_infoblock();
temp_buff[0] = 1;
sendFlasherAnswer(cmd->command, temp_buff, 1);
break;
case CMD_SAVE_MAC_FROM_FW:
case CMD_PASS_THROUGH:
break;
}
}
void usbFlasherTask(void* parameter) {
flasherCmdQueue = xQueueCreate(10, sizeof(struct flasherCommand*));
consoleCmdQueue = xQueueCreate(10, sizeof(struct consoleCommand*));
#if ARDUINO_USB_MODE
#warning Wrong USB mode is in use, check settings in platformio.ini
#endif
USB.onEvent(usbEventCallback);
USBSerial.onEvent(usbEventCallback);
USBSerial.setTimeout(1000);
USB.begin();
USBSerial.begin();
struct flasherCommand* cmd;
while (true) {
BaseType_t queuereceive = xQueueReceive(flasherCmdQueue, &cmd, portMAX_DELAY);
if (queuereceive == pdTRUE) {
processFlasherCommand(cmd);
if (cmd->data != nullptr) {
free(cmd->data);
}
delete cmd;
}
}
}

View File

@@ -0,0 +1,500 @@
#include "web.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ESPmDNS.h>
#include <FS.h>
#include "storage.h"
#include "LittleFS.h"
#include "SPIFFSEditor.h"
#include <WiFi.h>
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager/tree/feature_asyncwebserver
#include "commstructs.h"
#include "language.h"
#include "leds.h"
#include "newproto.h"
#include "ota.h"
#include "serialap.h"
#include "settings.h"
#include "tag_db.h"
#include "udp.h"
extern uint8_t data_to_send[];
// const char *http_username = "admin";
// const char *http_password = "admin";
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
SemaphoreHandle_t wsMutex;
void webSocketSendProcess(void *parameter) {
wsMutex = xSemaphoreCreateMutex();
while (true) {
ws.cleanupClients();
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
#ifdef HAS_RGB_LED
shortBlink(CRGB::BlueViolet);
#endif
switch (type) {
case WS_EVT_CONNECT:
ets_printf("ws[%s][%u] connect\n", server->url(), client->id());
// client->ping();
break;
case WS_EVT_DISCONNECT:
ets_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
break;
case WS_EVT_ERROR:
ets_printf("WS Error received :(\n\n");
// ets_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t *)arg), (char *)data);
break;
case WS_EVT_PONG:
ets_printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len) ? (char *)data : "");
break;
case WS_EVT_DATA:
/*
AwsFrameInfo *info = (AwsFrameInfo *)arg;
if (info->final && info->index == 0 && info->len == len) {
// the whole message is in a single frame and we got all of it's data
ets_printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT) ? "text" : "binary", info->len);
if (info->opcode == WS_TEXT) {
data[len] = 0;
ets_printf("%s\n", (char *)data);
} else {
for (size_t i = 0; i < info->len; i++) {
ets_printf("%02x ", data[i]);
}
ets_printf("\n");
}
if (info->opcode == WS_TEXT)
client->text("{\"status\":\"received\"}");
else
client->binary("{\"status\":\"received\"}");
} else {
// message is comprised of multiple frames or the frame is split into multiple packets
if (info->index == 0) {
if (info->num == 0)
ets_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT) ? "text" : "binary");
ets_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
}
ets_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT) ? "text" : "binary", info->index, info->index + len);
if (info->message_opcode == WS_TEXT) {
data[len] = 0;
ets_printf("%s\n", (char *)data);
} else {
for (size_t i = 0; i < len; i++) {
ets_printf("%02x ", data[i]);
}
ets_printf("\n");
}
if ((info->index + len) == info->len) {
ets_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
if (info->final) {
ets_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT) ? "text" : "binary");
if (info->message_opcode == WS_TEXT)
client->text("{\"status\":\"received\"}");
else
client->binary("{\"status\":\"received\"}");
}
}
} */
break;
}
}
void wsLog(String text) {
StaticJsonDocument<250> doc;
doc["logMsg"] = text;
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
ws.textAll(doc.as<String>());
if (wsMutex) xSemaphoreGive(wsMutex);
}
void wsErr(String text) {
StaticJsonDocument<250> doc;
doc["errMsg"] = text;
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
ws.textAll(doc.as<String>());
if (wsMutex) xSemaphoreGive(wsMutex);
}
size_t dbSize(){
size_t size = tagDB.size() * sizeof(tagRecord);
for(auto &tag : tagDB) {
if (tag->data)
size += tag->len;
size += tag->modeConfigJson.length();
}
return size;
}
void wsSendSysteminfo() {
DynamicJsonDocument doc(250);
JsonObject sys = doc.createNestedObject("sys");
time_t now;
time(&now);
sys["currtime"] = now;
sys["heap"] = ESP.getFreeHeap();
sys["recordcount"] = tagDB.size();
sys["dbsize"] = dbSize();
sys["littlefsfree"] = Storage.freeSpace();
sys["apstate"] = apInfo.state;
sys["runstate"] = config.runStatus;
sys["temp"] = temperatureRead();
sys["rssi"] = WiFi.RSSI();
sys["wifistatus"] = WiFi.status();
sys["wifissid"] = WiFi.SSID();
xSemaphoreTake(wsMutex, portMAX_DELAY);
ws.textAll(doc.as<String>());
xSemaphoreGive(wsMutex);
}
void wsSendTaginfo(uint8_t *mac, uint8_t syncMode) {
if (syncMode != SYNC_DELETE) {
String json = "";
json = tagDBtoJson(mac);
xSemaphoreTake(wsMutex, portMAX_DELAY);
ws.textAll(json);
xSemaphoreGive(wsMutex);
}
if (syncMode > SYNC_NOSYNC) {
tagRecord *taginfo = nullptr;
taginfo = tagRecord::findByMAC(mac);
if (taginfo != nullptr) {
if (taginfo->contentMode != 12 || syncMode == SYNC_DELETE) {
UDPcomm udpsync;
struct TagInfo taginfoitem;
memcpy(taginfoitem.mac, taginfo->mac, sizeof(taginfoitem.mac));
taginfoitem.syncMode = syncMode;
taginfoitem.contentMode = taginfo->contentMode;
if (syncMode == SYNC_USERCFG) {
strncpy(taginfoitem.alias, taginfo->alias.c_str(), sizeof(taginfoitem.alias) - 1);
taginfoitem.alias[sizeof(taginfoitem.alias) - 1] = '\0';
taginfoitem.nextupdate = taginfo->nextupdate;
}
if (syncMode == SYNC_TAGSTATUS) {
taginfoitem.lastseen = taginfo->lastseen;
taginfoitem.nextupdate = taginfo->nextupdate;
taginfoitem.pending = taginfo->pending;
taginfoitem.expectedNextCheckin = taginfo->expectedNextCheckin;
taginfoitem.hwType = taginfo->hwType;
taginfoitem.wakeupReason = taginfo->wakeupReason;
taginfoitem.capabilities = taginfo->capabilities;
taginfoitem.pendingIdle = taginfo->pendingIdle;
}
udpsync.netTaginfo(&taginfoitem);
}
}
}
}
void wsSendAPitem(struct APlist *apitem) {
DynamicJsonDocument doc(250);
JsonObject ap = doc.createNestedObject("apitem");
char version_str[6];
sprintf(version_str, "%04X", apitem->version);
ap["ip"] = ((IPAddress)apitem->src).toString();
ap["alias"] = apitem->alias;
ap["count"] = apitem->tagCount;
ap["channel"] = apitem->channelId;
ap["version"] = version_str;
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
ws.textAll(doc.as<String>());
if (wsMutex) xSemaphoreGive(wsMutex);
}
void wsSerial(String text) {
StaticJsonDocument<250> doc;
doc["console"] = text;
Serial.println(text);
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
ws.textAll(doc.as<String>());
if (wsMutex) xSemaphoreGive(wsMutex);
}
uint8_t wsClientCount() {
return ws.count();
}
void init_web() {
Storage.begin();
WiFi.mode(WIFI_STA);
WiFiManager wm;
bool res;
#if defined(OPENEPAPERLINK_MINI_AP_PCB) || defined(OPENEPAPERLINK_NANO_AP_PCB)
WiFi.setTxPower(WIFI_POWER_15dBm);
#endif
wm.setWiFiAutoReconnect(true);
res = wm.autoConnect("OpenEPaperLink Setup");
if (!res) {
Serial.println("Failed to connect");
ESP.restart();
}
#if defined(OPENEPAPERLINK_MINI_AP_PCB) || defined(OPENEPAPERLINK_NANO_AP_PCB)
WiFi.setTxPower(WIFI_POWER_19_5dBm);
#endif
Serial.print("Connected! IP address: ");
Serial.println(WiFi.localIP());
// server.addHandler(new SPIFFSEditor(*contentFS, http_username, http_password));
server.addHandler(new SPIFFSEditor(*contentFS));
ws.onEvent(onEvent);
server.addHandler(&ws);
server.on("/reboot", HTTP_POST, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK Reboot");
wsErr("REBOOTING");
ws.enable(false);
refreshAllPending();
saveDB("/current/tagDB.json");
ws.closeAll();
delay(100);
ESP.restart();
});
server.serveStatic("/current", *contentFS, "/current/");
server.serveStatic("/", *contentFS, "/www/").setDefaultFile("index.html");
server.on(
"/imgupload", HTTP_POST, [](AsyncWebServerRequest *request) {
request->send(200);
},
doImageUpload);
server.on("/get_db", HTTP_GET, [](AsyncWebServerRequest *request) {
String json = "";
if (request->hasParam("mac")) {
String dst = request->getParam("mac")->value();
uint8_t mac[8];
if (hex2mac(dst, mac)) {
json = tagDBtoJson(mac);
} else {
json = "{\"error\": \"malformatted parameter\"}";
}
} else {
uint8_t startPos = 0;
if (request->hasParam("pos")) {
startPos = atoi(request->getParam("pos")->value().c_str());
}
json = tagDBtoJson(nullptr, startPos);
}
request->send(200, "application/json", json);
});
server.on("/getdata", HTTP_GET, [](AsyncWebServerRequest *request) {
if (request->hasParam("mac")) {
String dst = request->getParam("mac")->value();
uint8_t mac[8];
if (hex2mac(dst, mac)) {
tagRecord *taginfo = nullptr;
taginfo = tagRecord::findByMAC(mac);
if (taginfo != nullptr) {
if (taginfo->pending == true) {
request->send_P(200, "application/octet-stream", taginfo->data, taginfo->len);
return;
}
}
}
}
request->send(400, "text/plain", "No data available");
});
server.on("/save_cfg", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("mac", true)) {
String dst = request->getParam("mac", true)->value();
uint8_t mac[8];
if (hex2mac(dst, mac)) {
tagRecord *taginfo = nullptr;
taginfo = tagRecord::findByMAC(mac);
if (taginfo != nullptr) {
taginfo->alias = request->getParam("alias", true)->value();
taginfo->modeConfigJson = request->getParam("modecfgjson", true)->value();
taginfo->contentMode = atoi(request->getParam("contentmode", true)->value().c_str());
taginfo->nextupdate = 0;
if (request->hasParam("rotate", true)) {
taginfo->rotate = atoi(request->getParam("rotate", true)->value().c_str());
}
if (request->hasParam("lut", true)) {
taginfo->lut = atoi(request->getParam("lut", true)->value().c_str());
}
// memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
// memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
wsSendTaginfo(mac, SYNC_USERCFG);
saveDB("/current/tagDB.json");
request->send(200, "text/plain", "Ok, saved");
} else {
request->send(200, "text/plain", "Error while saving: mac not found");
}
}
}
request->send(200, "text/plain", "Ok, saved");
});
server.on("/tag_cmd", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("mac", true) && request->hasParam("cmd", true)) {
uint8_t mac[8];
if (hex2mac(request->getParam("mac", true)->value(), mac)) {
tagRecord *taginfo = nullptr;
taginfo = tagRecord::findByMAC(mac);
if (taginfo != nullptr) {
const char *cmdValue = request->getParam("cmd", true)->value().c_str();
if (strcmp(cmdValue, "del") == 0) {
wsSendTaginfo(mac, SYNC_DELETE);
deleteRecord(mac);
}
if (strcmp(cmdValue, "clear") == 0) {
clearPending(taginfo);
memcpy(taginfo->md5pending, taginfo->md5, sizeof(taginfo->md5pending));
wsSendTaginfo(mac, SYNC_TAGSTATUS);
}
if (strcmp(cmdValue, "refresh") == 0) {
updateContent(mac);
}
if (strcmp(cmdValue, "reboot") == 0) {
sendTagCommand(mac, CMD_DO_REBOOT, !taginfo->isExternal);
}
if (strcmp(cmdValue, "scan") == 0) {
sendTagCommand(mac, CMD_DO_SCAN, !taginfo->isExternal);
}
if (strcmp(cmdValue, "reset") == 0) {
sendTagCommand(mac, CMD_DO_RESET_SETTINGS, !taginfo->isExternal);
}
request->send(200, "text/plain", "Ok, done");
} else {
request->send(200, "text/plain", "Error: mac not found");
}
}
} else {
request->send(500, "text/plain", "param error");
}
});
server.on("/get_ap_config", HTTP_GET, [](AsyncWebServerRequest *request) {
UDPcomm udpsync;
udpsync.getAPList();
File configFile = contentFS->open("/current/apconfig.json", "r");
if (!configFile) {
request->send(500, "text/plain", "Error opening apconfig.json file");
return;
}
request->send(configFile, "application/json");
configFile.close();
});
server.on("/save_apcfg", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("alias", true) && request->hasParam("channel", true)) {
String aliasValue = request->getParam("alias", true)->value();
size_t aliasLength = aliasValue.length();
if (aliasLength > 31) aliasLength = 31;
aliasValue.toCharArray(config.alias, aliasLength + 1);
config.alias[aliasLength] = '\0';
config.channel = static_cast<uint8_t>(request->getParam("channel", true)->value().toInt());
if (request->hasParam("led", true)) {
config.led = static_cast<int16_t>(request->getParam("led", true)->value().toInt());
updateBrightnessFromConfig();
}
if (request->hasParam("language", true)) {
config.language = static_cast<uint8_t>(request->getParam("language", true)->value().toInt());
updateLanguageFromConfig();
}
if (request->hasParam("maxsleep", true)) {
config.maxsleep = static_cast<uint8_t>(request->getParam("maxsleep", true)->value().toInt());
}
if (request->hasParam("stopsleep", true)) {
config.stopsleep = static_cast<uint8_t>(request->getParam("stopsleep", true)->value().toInt());
}
saveAPconfig();
setAPchannel();
}
request->send(200, "text/plain", "Ok, saved");
});
server.on("/backup_db", HTTP_GET, [](AsyncWebServerRequest *request) {
saveDB("/current/tagDB.json");
File file = contentFS->open("/current/tagDB.json", "r");
AsyncWebServerResponse *response = request->beginResponse(file, "tagDB.json", String(), true);
request->send(response);
file.close();
});
server.on("/sysinfo", HTTP_GET, handleSysinfoRequest);
server.on("/check_file", HTTP_GET, handleCheckFile);
server.on("/getexturl", HTTP_GET, handleGetExtUrl);
server.on("/rollback", HTTP_POST, handleRollback);
server.on("/update_actions", HTTP_POST, handleUpdateActions);
server.on("/update_ota", HTTP_POST, [](AsyncWebServerRequest *request) {
handleUpdateOTA(request);
});
server.on(
"/littlefs_put", HTTP_POST, [](AsyncWebServerRequest *request) {
request->send(200);
},
handleLittleFSUpload);
server.onNotFound([](AsyncWebServerRequest *request) {
if (request->url() == "/" || request->url() == "index.htm") {
request->send(200, "text/html", "index.html not found. Did you forget to upload the littlefs partition?");
return;
}
request->send(404);
});
server.begin();
}
void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
if (request->hasParam("mac", true)) {
filename = request->getParam("mac", true)->value() + ".jpg";
} else {
filename = "unknown.jpg";
}
request->_tempFile = contentFS->open("/" + filename, "w");
}
if (len) {
// stream the incoming chunk to the opened file
request->_tempFile.write(data, len);
}
if (final) {
request->_tempFile.close();
if (request->hasParam("mac", true)) {
String dst = request->getParam("mac", true)->value();
bool dither = true;
if (request->hasParam("dither", true)) {
if (request->getParam("dither", true)->value() == "0") dither = false;
}
uint8_t mac[8];
if (hex2mac(dst, mac)) {
tagRecord *taginfo = nullptr;
taginfo = tagRecord::findByMAC(mac);
if (taginfo != nullptr) {
taginfo->modeConfigJson = "{\"filename\":\"" + dst + ".jpg\",\"timetolive\":\"0\",\"dither\":\"" + String(dither) + "\",\"delete\":\"1\"}";
taginfo->contentMode = 0;
taginfo->nextupdate = 0;
wsSendTaginfo(mac, SYNC_USERCFG);
request->send(200, "text/plain", "Ok, saved");
} else {
request->send(200, "text/plain", "Error while saving: mac not found");
}
}
} else {
request->send(500, "text/plain", "parameters incomplete");
}
}
}

View File

@@ -1,55 +1,67 @@
/* Autor: Aaron Christophel ATCnetz.de */
#include "zbs_interface.h"
#include <Arduino.h>
#include <SPI.h>
#include <stdint.h>
#include <stdio.h>
#include "settings.h"
#ifdef USE_SOFTSPI
#include <SoftSPI.h>
#endif
void simplePowerOn() {
pinMode(ZBS_SS, INPUT);
pinMode(ZBS_CLK, INPUT);
pinMode(ZBS_MoSi, INPUT);
pinMode(ZBS_MiSo, INPUT);
pinMode(ZBS_Reset, OUTPUT);
digitalWrite(ZBS_Reset, HIGH);
zbs.set_power(0);
delay(500);
zbs.set_power(1);
}
uint8_t ZBS_interface::begin() {
_SS_PIN = ZBS_SS;
_CLK_PIN = ZBS_CLK;
_MOSI_PIN = ZBS_MoSi;
_MISO_PIN = ZBS_MiSo;
_RESET_PIN = ZBS_Reset;
#include "powermgt.h"
uint8_t ZBS_interface::begin(uint8_t SS, uint8_t CLK, uint8_t MOSI, uint8_t MISO, uint8_t RESET, uint8_t* POWER, uint8_t powerPins, uint32_t spi_speed) {
_SS_PIN = SS;
_CLK_PIN = CLK;
_MOSI_PIN = MOSI;
_MISO_PIN = MISO;
_RESET_PIN = RESET;
if (powerPins > 0)
_POWER_PIN = POWER;
else
_POWER_PIN = nullptr;
pinMode(_SS_PIN, OUTPUT);
pinMode(_RESET_PIN, OUTPUT);
digitalWrite(_SS_PIN, HIGH);
digitalWrite(_RESET_PIN, HIGH);
set_power(ZBS_ON);
pinMode(_CLK_PIN, OUTPUT);
pinMode(_MOSI_PIN, OUTPUT);
pinMode(_MISO_PIN, INPUT);
pinMode(_RESET_PIN, OUTPUT);
digitalWrite(_SS_PIN, HIGH);
digitalWrite(_CLK_PIN, LOW);
digitalWrite(_MOSI_PIN, HIGH);
digitalWrite(_RESET_PIN, HIGH);
set_power(ZBS_ON);
#ifdef USE_SOFTSPI
if (!spi) spi = new SoftSPI(_MOSI_PIN, _MISO_PIN, _CLK_PIN);
#else
if (!spi) spi = new SPIClass(HSPI);
#endif
spiSettings = SPISettings(spi_speed, MSBFIRST, SPI_MODE0);
spi_ready = 0;
if (spi_speed != 8000000) {
after_byte_delay = 10;
} else {
after_byte_delay = 10;
}
enable_debug();
return check_connection();
}
void ZBS_interface::setSpeed(uint32_t speed) {
spiSettings = SPISettings(speed, MSBFIRST, SPI_MODE0);
}
ZBS_interface::~ZBS_interface() {
if(spi)delete spi;
}
void ZBS_interface::set_power(uint8_t state) {
pinMode(ZBS_POWER1, INPUT);
pinMode(ZBS_POWER2, INPUT);
digitalWrite(ZBS_POWER1, state);
digitalWrite(ZBS_POWER2, state);
pinMode(ZBS_POWER1, OUTPUT);
pinMode(ZBS_POWER2, OUTPUT);
powerControl(state, _POWER_PIN, _POWER_PINS);
}
void ZBS_interface::enable_debug() {
@@ -81,6 +93,7 @@ void ZBS_interface::enable_debug() {
}
void ZBS_interface::reset() {
spi->end();
pinMode(_SS_PIN, INPUT);
pinMode(_CLK_PIN, INPUT);
pinMode(_MOSI_PIN, INPUT);
@@ -96,18 +109,14 @@ void ZBS_interface::reset() {
void ZBS_interface::send_byte(uint8_t data) {
digitalWrite(_SS_PIN, LOW);
delayMicroseconds(5);
for (int i = 0; i < 8; i++) {
if (data & 0x80) {
digitalWrite(_MOSI_PIN, HIGH);
} else {
digitalWrite(_MOSI_PIN, LOW);
}
delayMicroseconds(ZBS_spi_delay);
digitalWrite(_CLK_PIN, HIGH);
delayMicroseconds(ZBS_spi_delay);
digitalWrite(_CLK_PIN, LOW);
data <<= 1;
if (!spi_ready) {
spi_ready = 1;
spi->begin(_CLK_PIN, _MISO_PIN, _MOSI_PIN);
}
spi->beginTransaction(spiSettings);
spi->transfer(data);
spi->endTransaction();
delayMicroseconds(2);
digitalWrite(_SS_PIN, HIGH);
}
@@ -116,16 +125,13 @@ uint8_t ZBS_interface::read_byte() {
uint8_t data = 0x00;
digitalWrite(_SS_PIN, LOW);
delayMicroseconds(5);
for (int i = 0; i < 8; i++) {
data <<= 1;
if (digitalRead(_MISO_PIN)) {
data |= 1;
}
delayMicroseconds(ZBS_spi_delay);
digitalWrite(_CLK_PIN, HIGH);
delayMicroseconds(ZBS_spi_delay);
digitalWrite(_CLK_PIN, LOW);
if (!spi_ready) {
spi_ready = 1;
spi->begin(_CLK_PIN, _MISO_PIN, _MOSI_PIN);
}
spi->beginTransaction(spiSettings);
data = spi->transfer(0xff);
spi->endTransaction();
delayMicroseconds(2);
digitalWrite(_SS_PIN, HIGH);
return data;
@@ -135,7 +141,7 @@ void ZBS_interface::write_byte(uint8_t cmd, uint8_t addr, uint8_t data) {
send_byte(cmd);
send_byte(addr);
send_byte(data);
delay(1);
delayMicroseconds(after_byte_delay);
}
uint8_t ZBS_interface::read_byte(uint8_t cmd, uint8_t addr) {
@@ -143,7 +149,7 @@ uint8_t ZBS_interface::read_byte(uint8_t cmd, uint8_t addr) {
send_byte(cmd);
send_byte(addr);
data = read_byte();
delay(1);
delayMicroseconds(after_byte_delay);
return data;
}
@@ -152,7 +158,7 @@ void ZBS_interface::write_flash(uint16_t addr, uint8_t data) {
send_byte(addr >> 8);
send_byte(addr);
send_byte(data);
delay(1);
delayMicroseconds(after_byte_delay);
}
uint8_t ZBS_interface::read_flash(uint16_t addr) {
@@ -161,7 +167,7 @@ uint8_t ZBS_interface::read_flash(uint16_t addr) {
send_byte(addr >> 8);
send_byte(addr);
data = read_byte();
delay(1);
delayMicroseconds(after_byte_delay);
return data;
}
@@ -210,5 +216,3 @@ void ZBS_interface::erase_infoblock() {
send_byte(0x00);
delay(100);
}
ZBS_interface zbs;

View File

@@ -0,0 +1,50 @@
#!/bin/bash
(return 0 2>/dev/null) && sourced=1 || sourced=0
if [ $sourced -eq 0 ]; then
if [ $# -eq 0 ]
then
echo "No IP address provided"
exit 1
fi
IP=$1
if [ -z "$IP" ]
then
echo "ERROR: Empty IP"
exit 1
fi
fi
upload_file () {
for file in "$@"
do
split=( $(echo $file | tr ":" " ") )
echo $split
filename=${split[0]}
if [ -z ${split[1]} ]; then
filepath=$(echo ${filename} | cut -d'/' -f2-)
else
filepath=${split[1]}
fi
echo $filename "-->" $filepath
curl "http://${IP}/edit" -X POST \
-H "Origin: http://${IP}" \
-H 'Connection: keep-alive' \
-H "Referer: http://${IP}/edit" \
-F "data=@${filename};filename=\"${filepath}\""
echo ""
done
}
export -f upload_file
if [ $sourced -eq 0 ]; then
export IP
find data -type f -exec bash -c "upload_file {} $IP" \;
else
echo "You can now call "
echo "IP=1.2.3.4 upload_file data/file1.txt data/file2.txt:target.txt"
fi

View File

@@ -0,0 +1,2 @@
(kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor)
)

View File

@@ -0,0 +1,64 @@
(kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor)
(symbol "ESL-connector" (in_bom yes) (on_board yes)
(property "Reference" "U" (id 0) (at 0 11.43 0)
(effects (font (size 1.27 1.27)))
)
(property "Value" "ESL-connector" (id 1) (at 0 -11.43 0)
(effects (font (size 1.27 1.27)))
)
(property "Footprint" "" (id 2) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "ESL-connector_0_1"
(rectangle (start -6.35 10.16) (end 6.35 -10.16)
(stroke (width 0.1524) (type default) (color 0 0 0 0))
(fill (type none))
)
)
(symbol "ESL-connector_1_1"
(pin power_in line (at -8.89 7.62 0) (length 2.54)
(name "VCC" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin bidirectional line (at 8.89 -7.62 180) (length 2.54)
(name "P1.0" (effects (font (size 1.27 1.27))))
(number "10" (effects (font (size 1.27 1.27))))
)
(pin input line (at -8.89 3.81 0) (length 2.54)
(name "MoSi" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
(pin input line (at -8.89 0 0) (length 2.54)
(name "SS" (effects (font (size 1.27 1.27))))
(number "3" (effects (font (size 1.27 1.27))))
)
(pin output line (at -8.89 -3.81 0) (length 2.54)
(name "TXD" (effects (font (size 1.27 1.27))))
(number "4" (effects (font (size 1.27 1.27))))
)
(pin input line (at -8.89 -7.62 0) (length 2.54)
(name "CLK" (effects (font (size 1.27 1.27))))
(number "5" (effects (font (size 1.27 1.27))))
)
(pin output line (at 8.89 7.62 180) (length 2.54)
(name "MiSo" (effects (font (size 1.27 1.27))))
(number "6" (effects (font (size 1.27 1.27))))
)
(pin power_in line (at 8.89 3.81 180) (length 2.54)
(name "GND" (effects (font (size 1.27 1.27))))
(number "7" (effects (font (size 1.27 1.27))))
)
(pin input line (at 8.89 0 180) (length 2.54)
(name "RST" (effects (font (size 1.27 1.27))))
(number "8" (effects (font (size 1.27 1.27))))
)
(pin input line (at 8.89 -3.81 180) (length 2.54)
(name "RXD" (effects (font (size 1.27 1.27))))
(number "9" (effects (font (size 1.27 1.27))))
)
)
)
)

View File

@@ -0,0 +1,26 @@
# Alternative PCB for 2.9" flashing #
This is an alternative PCB for flashing 2.9" ESLs
<img width="600" alt="pcb" src="pcb.png">
<img width="600" alt="bot" src="jig_bot.jpg">
<img width="600" alt="top" src="jig_top.jpg">
### Parts (per board) ###
* 10x 1mm pogo pin
* 3x Switches
* 3x 3mm LEDs
* 3x 0805 SMD resistor (47 ohm or so, depending on LED color, personal taste, whatever)
* ESP32-DevKit-Lipo form Olimex
* please check the PCB for the rest
### Errata ###
The ESP32 board needs to be solder upside down
## Getting PCB's ##
You can order the boards from your favorite boardhouse, using the zip file in this repository.
## Disclaimer ##
There is no guarantee or warranty whatsoever, nor is there any promise or insinuation that this board fill fullfill any particular purpose. This board may very well not work for you, set your hair and/or, but not limited to, pants on fire, incite violance or persuade other countries to invade your country. You're on your own, chief!

View File

@@ -0,0 +1,40 @@
(footprint "SOLUM_ZBS_DEBUG" (version 20211014) (generator pcbnew)
(layer "F.Cu")
(tedit 0)
(descr "<h3>Solum Debug header pinout for 1mm pogo pins</h3>")
(fp_text reference "J19" (at 0 0) (layer "F.SilkS") hide
(effects (font (size 1.27 1.27) (thickness 0.15)))
(tstamp 82ec8fc7-4947-43e8-a28a-f87f8d8a424a)
)
(fp_text value "" (at 0 0) (layer "F.Fab") hide
(effects (font (size 1.27 1.27) (thickness 0.15)))
(tstamp 9be777ad-9f8e-434c-9925-b4f3cbcf667d)
)
(fp_line (start -3.745 -2.506) (end -3.745 2.494) (layer "F.SilkS") (width 0.127) (tstamp 230f73ed-7388-4311-bd67-896af89b3fcb))
(fp_line (start -3.345 0.044) (end -2.745 -0.356) (layer "F.SilkS") (width 0.127) (tstamp 25b0ffe5-9f43-42b5-ad90-4a164f170e55))
(fp_line (start 3.755 -2.506) (end 3.755 2.494) (layer "F.SilkS") (width 0.127) (tstamp 53faed3a-63cc-4487-8883-b5c4197265c2))
(fp_line (start -3.345 0.044) (end -2.745 0.444) (layer "F.SilkS") (width 0.127) (tstamp 5a223cb8-3c61-4d1f-a412-d9c7d77f66b9))
(fp_line (start -3.745 -2.506) (end 3.755 -2.506) (layer "F.SilkS") (width 0.127) (tstamp 85c102fc-8b65-4d83-a6fe-e043723d82a4))
(fp_line (start -3.745 2.494) (end 3.755 2.494) (layer "F.SilkS") (width 0.127) (tstamp a1645c22-11d6-485a-92a9-3621b643513c))
(fp_line (start -3.345 0.044) (end -1.495 0.044) (layer "F.SilkS") (width 0.127) (tstamp dab4d2de-3395-4723-9799-d0bb3a191c1b))
(pad "1" thru_hole circle (at -2.995 1.744) (size 1.27 1.27) (drill 1) (layers *.Cu *.Mask)
(solder_mask_margin 0.0508) (tstamp 71343d1a-b887-4c75-8f6e-3783c4cee9df))
(pad "2" thru_hole circle (at -0.995 1.744) (size 1.27 1.27) (drill 1) (layers *.Cu *.Mask)
(solder_mask_margin 0.0508) (tstamp a5bba08a-0aac-4518-8a18-8e86b2e9784c))
(pad "3" thru_hole circle (at 1.005 1.744) (size 1.27 1.27) (drill 1) (layers *.Cu *.Mask)
(solder_mask_margin 0.0508) (tstamp 64903969-f443-47c1-a03d-9412f3950a7f))
(pad "4" thru_hole circle (at 3.005 1.794) (size 1.27 1.27) (drill 1) (layers *.Cu *.Mask)
(solder_mask_margin 0.0508) (tstamp 53ec6283-c57d-491a-9c98-1df814704014))
(pad "5" thru_hole circle (at 0.005 -0.006) (size 1.27 1.27) (drill 1) (layers *.Cu *.Mask)
(solder_mask_margin 0.0508) (tstamp 6b11af81-f064-49d0-8c15-50a5af3bdce8))
(pad "6" thru_hole circle (at 2.005 -0.006) (size 1.27 1.27) (drill 1) (layers *.Cu *.Mask)
(solder_mask_margin 0.0508) (tstamp 048099e4-1d18-4e68-a98e-c376a592cc1d))
(pad "7" thru_hole circle (at -2.995 -1.756) (size 1.27 1.27) (drill 1) (layers *.Cu *.Mask)
(solder_mask_margin 0.0508) (tstamp 323c1c33-7532-46f9-b99c-f77f2344710a))
(pad "8" thru_hole circle (at -0.995 -1.756) (size 1.27 1.27) (drill 1) (layers *.Cu *.Mask)
(solder_mask_margin 0.0508) (tstamp e067c309-8b30-4efa-b5e6-088c3f57692f))
(pad "9" thru_hole circle (at 1.005 -1.756) (size 1.27 1.27) (drill 1) (layers *.Cu *.Mask)
(solder_mask_margin 0.0508) (tstamp f4cd57db-f82c-479a-9526-cfebc536273b))
(pad "10" thru_hole circle (at 3.005 -1.756) (size 1.27 1.27) (drill 1) (layers *.Cu *.Mask)
(solder_mask_margin 0.0508) (tstamp 2700ab94-06e0-45e2-9edc-3815ba09c9d6))
)

View File

@@ -0,0 +1,3 @@
(fp_lib_table
(lib (name "esl")(type "KiCad")(uri "${KIPRJMOD}/esl.pretty")(options "")(descr ""))
)

Binary file not shown.

View File

@@ -0,0 +1,252 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,(6.0.11)*
G04 #@! TF.CreationDate,2023-03-04T17:24:56+01:00*
G04 #@! TF.ProjectId,jig2,6a696732-2e6b-4696-9361-645f70636258,rev?*
G04 #@! TF.SameCoordinates,Original*
G04 #@! TF.FileFunction,Copper,L2,Bot*
G04 #@! TF.FilePolarity,Positive*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW (6.0.11)) date 2023-03-04 17:24:56*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 #@! TA.AperFunction,ComponentPad*
%ADD10C,2.000000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD11R,1.700000X1.700000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD12O,1.700000X1.700000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD13R,1.800000X1.800000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD14C,1.800000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD15C,1.270000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ViaPad*
%ADD16C,0.600000*%
G04 #@! TD*
G04 #@! TA.AperFunction,Conductor*
%ADD17C,1.000000*%
G04 #@! TD*
G04 #@! TA.AperFunction,Conductor*
%ADD18C,0.500000*%
G04 #@! TD*
G04 APERTURE END LIST*
D10*
X9250000Y-4765564D03*
X2750000Y-4765564D03*
X2750000Y-9265564D03*
X9250000Y-9265564D03*
D11*
X7620000Y-35560000D03*
D12*
X7620000Y-38100000D03*
X7620000Y-40640000D03*
X7620000Y-43180000D03*
X7620000Y-45720000D03*
X7620000Y-48260000D03*
X7620000Y-50800000D03*
X7620000Y-53340000D03*
X7620000Y-55880000D03*
X7620000Y-58420000D03*
X7620000Y-60960000D03*
X7620000Y-63500000D03*
X7620000Y-66040000D03*
X7620000Y-68580000D03*
X7620000Y-71120000D03*
X7620000Y-73660000D03*
X7620000Y-76200000D03*
X7620000Y-78740000D03*
X7620000Y-81280000D03*
D13*
X23290000Y-5080000D03*
D14*
X25830000Y-5080000D03*
D11*
X32975000Y-35570000D03*
D12*
X32975000Y-38110000D03*
X32975000Y-40650000D03*
X32975000Y-43190000D03*
X32975000Y-45730000D03*
X32975000Y-48270000D03*
X32975000Y-50810000D03*
X32975000Y-53350000D03*
X32975000Y-55890000D03*
X32975000Y-58430000D03*
X32975000Y-60970000D03*
X32975000Y-63510000D03*
X32975000Y-66050000D03*
X32975000Y-68590000D03*
X32975000Y-71130000D03*
X32975000Y-73670000D03*
X32975000Y-76210000D03*
X32975000Y-78750000D03*
X32975000Y-81290000D03*
D15*
X28256000Y-17005000D03*
X28256000Y-19005000D03*
X28256000Y-21005000D03*
X28206000Y-23005000D03*
X30006000Y-20005000D03*
X30006000Y-22005000D03*
X31756000Y-17005000D03*
X31756000Y-19005000D03*
X31756000Y-21005000D03*
X31756000Y-23005000D03*
D13*
X28830000Y-5080000D03*
D14*
X31370000Y-5080000D03*
D10*
X19250000Y-4765564D03*
X12750000Y-4765564D03*
X19250000Y-9265564D03*
X12750000Y-9265564D03*
D13*
X34370000Y-5080000D03*
D14*
X36910000Y-5080000D03*
D10*
X14750000Y-20820000D03*
X21250000Y-20820000D03*
X14750000Y-25320000D03*
X21250000Y-25320000D03*
D16*
X23000000Y-35500000D03*
X24000000Y-39700000D03*
X11000000Y-29750000D03*
X11500000Y-15000000D03*
X14440000Y-79310000D03*
X7620000Y-21620000D03*
X20500000Y-17000000D03*
X20500000Y-15250000D03*
X21000000Y-13750000D03*
X26750000Y-38250000D03*
D17*
X23000000Y-37093502D02*
X23000000Y-35500000D01*
X24828249Y-38921751D02*
X24050000Y-39700000D01*
X25606498Y-39700000D02*
X24828249Y-38921751D01*
X24828249Y-38921751D02*
X23000000Y-37093502D01*
X32975000Y-35570000D02*
X28845000Y-39700000D01*
X24050000Y-39700000D02*
X24000000Y-39700000D01*
D18*
X11000000Y-15500000D02*
X11500000Y-15000000D01*
D17*
X28845000Y-39700000D02*
X25606498Y-39700000D01*
D18*
X11000000Y-29750000D02*
X11000000Y-15500000D01*
X32975000Y-40650000D02*
X23150000Y-40650000D01*
X19250000Y-36750000D02*
X19250000Y-9265564D01*
X23150000Y-40650000D02*
X19250000Y-36750000D01*
X21940000Y-43190000D02*
X16750000Y-38000000D01*
X32975000Y-43190000D02*
X21940000Y-43190000D01*
X16750000Y-38000000D02*
X16750000Y-16765564D01*
X16750000Y-16765564D02*
X9250000Y-9265564D01*
X14750000Y-40250000D02*
X14750000Y-25320000D01*
X20230000Y-45730000D02*
X14750000Y-40250000D01*
X32975000Y-45730000D02*
X20230000Y-45730000D01*
D17*
X9200000Y-50800000D02*
X10750000Y-49250000D01*
X10750000Y-49250000D02*
X10750000Y-37250000D01*
D18*
X25410000Y-68590000D02*
X25160000Y-68590000D01*
X6820000Y-20820000D02*
X7620000Y-21620000D01*
D17*
X33505000Y-17005000D02*
X31756000Y-17005000D01*
X9060000Y-35560000D02*
X7620000Y-35560000D01*
X7620000Y-50800000D02*
X9200000Y-50800000D01*
D18*
X4200000Y-6215564D02*
X2750000Y-4765564D01*
D17*
X34525000Y-18025000D02*
X33505000Y-17005000D01*
X10750000Y-37250000D02*
X9060000Y-35560000D01*
X32975000Y-68590000D02*
X25410000Y-68590000D01*
D18*
X25160000Y-68590000D02*
X14440000Y-79310000D01*
X6820000Y-20820000D02*
X4200000Y-18200000D01*
D17*
X34525000Y-67040000D02*
X34525000Y-18025000D01*
X7620000Y-50800000D02*
X25410000Y-68590000D01*
D18*
X7620000Y-21620000D02*
X7620000Y-35560000D01*
D17*
X32975000Y-68590000D02*
X34525000Y-67040000D01*
D18*
X4200000Y-18200000D02*
X4200000Y-6215564D01*
X29239950Y-7750000D02*
X20500000Y-16489950D01*
X31700000Y-7750000D02*
X29239950Y-7750000D01*
X34370000Y-5080000D02*
X31700000Y-7750000D01*
X20500000Y-16489950D02*
X20500000Y-16750000D01*
X20500000Y-16750000D02*
X20500000Y-17000000D01*
X28830000Y-7170000D02*
X20750000Y-15250000D01*
X20750000Y-15250000D02*
X20500000Y-15250000D01*
X28830000Y-5080000D02*
X28830000Y-7170000D01*
X23290000Y-5080000D02*
X23290000Y-11460000D01*
X23290000Y-11460000D02*
X21000000Y-13750000D01*
D17*
X25250000Y-18250000D02*
X26495000Y-17005000D01*
X26495000Y-17005000D02*
X28256000Y-17005000D01*
X26750000Y-26250000D02*
X25250000Y-24750000D01*
X26750000Y-38250000D02*
X26750000Y-26250000D01*
X25250000Y-24750000D02*
X25250000Y-18250000D01*
M02*

View File

@@ -0,0 +1,101 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,(6.0.11)*
G04 #@! TF.CreationDate,2023-03-04T17:24:57+01:00*
G04 #@! TF.ProjectId,jig2,6a696732-2e6b-4696-9361-645f70636258,rev?*
G04 #@! TF.SameCoordinates,Original*
G04 #@! TF.FileFunction,Soldermask,Bot*
G04 #@! TF.FilePolarity,Negative*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW (6.0.11)) date 2023-03-04 17:24:57*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%ADD10C,2.000000*%
%ADD11R,1.700000X1.700000*%
%ADD12O,1.700000X1.700000*%
%ADD13R,1.800000X1.800000*%
%ADD14C,1.800000*%
%ADD15C,1.371600*%
G04 APERTURE END LIST*
D10*
X9250000Y-4765564D03*
X2750000Y-4765564D03*
X2750000Y-9265564D03*
X9250000Y-9265564D03*
D11*
X7620000Y-35560000D03*
D12*
X7620000Y-38100000D03*
X7620000Y-40640000D03*
X7620000Y-43180000D03*
X7620000Y-45720000D03*
X7620000Y-48260000D03*
X7620000Y-50800000D03*
X7620000Y-53340000D03*
X7620000Y-55880000D03*
X7620000Y-58420000D03*
X7620000Y-60960000D03*
X7620000Y-63500000D03*
X7620000Y-66040000D03*
X7620000Y-68580000D03*
X7620000Y-71120000D03*
X7620000Y-73660000D03*
X7620000Y-76200000D03*
X7620000Y-78740000D03*
X7620000Y-81280000D03*
D13*
X23290000Y-5080000D03*
D14*
X25830000Y-5080000D03*
D11*
X32975000Y-35570000D03*
D12*
X32975000Y-38110000D03*
X32975000Y-40650000D03*
X32975000Y-43190000D03*
X32975000Y-45730000D03*
X32975000Y-48270000D03*
X32975000Y-50810000D03*
X32975000Y-53350000D03*
X32975000Y-55890000D03*
X32975000Y-58430000D03*
X32975000Y-60970000D03*
X32975000Y-63510000D03*
X32975000Y-66050000D03*
X32975000Y-68590000D03*
X32975000Y-71130000D03*
X32975000Y-73670000D03*
X32975000Y-76210000D03*
X32975000Y-78750000D03*
X32975000Y-81290000D03*
D15*
X28256000Y-17005000D03*
X28256000Y-19005000D03*
X28256000Y-21005000D03*
X28206000Y-23005000D03*
X30006000Y-20005000D03*
X30006000Y-22005000D03*
X31756000Y-17005000D03*
X31756000Y-19005000D03*
X31756000Y-21005000D03*
X31756000Y-23005000D03*
D13*
X28830000Y-5080000D03*
D14*
X31370000Y-5080000D03*
D10*
X19250000Y-4765564D03*
X12750000Y-4765564D03*
X19250000Y-9265564D03*
X12750000Y-9265564D03*
D13*
X34370000Y-5080000D03*
D14*
X36910000Y-5080000D03*
D10*
X14750000Y-20820000D03*
X21250000Y-20820000D03*
X14750000Y-25320000D03*
X21250000Y-25320000D03*
M02*

View File

@@ -0,0 +1,15 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,(6.0.11)*
G04 #@! TF.CreationDate,2023-03-04T17:24:57+01:00*
G04 #@! TF.ProjectId,jig2,6a696732-2e6b-4696-9361-645f70636258,rev?*
G04 #@! TF.SameCoordinates,Original*
G04 #@! TF.FileFunction,Paste,Bot*
G04 #@! TF.FilePolarity,Positive*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW (6.0.11)) date 2023-03-04 17:24:57*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 APERTURE END LIST*
M02*

View File

@@ -0,0 +1,153 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,(6.0.11)*
G04 #@! TF.CreationDate,2023-03-04T17:24:57+01:00*
G04 #@! TF.ProjectId,jig2,6a696732-2e6b-4696-9361-645f70636258,rev?*
G04 #@! TF.SameCoordinates,Original*
G04 #@! TF.FileFunction,Legend,Bot*
G04 #@! TF.FilePolarity,Positive*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW (6.0.11)) date 2023-03-04 17:24:57*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%ADD10C,0.150000*%
%ADD11C,0.120000*%
%ADD12C,2.000000*%
%ADD13R,1.700000X1.700000*%
%ADD14O,1.700000X1.700000*%
%ADD15R,1.800000X1.800000*%
%ADD16C,1.800000*%
%ADD17C,1.371600*%
G04 APERTURE END LIST*
D10*
X19333333Y-19224761D02*
X19190476Y-19272380D01*
X18952380Y-19272380D01*
X18857142Y-19224761D01*
X18809523Y-19177142D01*
X18761904Y-19081904D01*
X18761904Y-18986666D01*
X18809523Y-18891428D01*
X18857142Y-18843809D01*
X18952380Y-18796190D01*
X19142857Y-18748571D01*
X19238095Y-18700952D01*
X19285714Y-18653333D01*
X19333333Y-18558095D01*
X19333333Y-18462857D01*
X19285714Y-18367619D01*
X19238095Y-18320000D01*
X19142857Y-18272380D01*
X18904761Y-18272380D01*
X18761904Y-18320000D01*
X18428571Y-18272380D02*
X18190476Y-19272380D01*
X18000000Y-18558095D01*
X17809523Y-19272380D01*
X17571428Y-18272380D01*
X17285714Y-18272380D02*
X16666666Y-18272380D01*
X17000000Y-18653333D01*
X16857142Y-18653333D01*
X16761904Y-18700952D01*
X16714285Y-18748571D01*
X16666666Y-18843809D01*
X16666666Y-19081904D01*
X16714285Y-19177142D01*
X16761904Y-19224761D01*
X16857142Y-19272380D01*
X17142857Y-19272380D01*
X17238095Y-19224761D01*
X17285714Y-19177142D01*
D11*
X14500000Y-23820000D02*
X14500000Y-22320000D01*
X20250000Y-26320000D02*
X15750000Y-26320000D01*
X21500000Y-22320000D02*
X21500000Y-23820000D01*
X15750000Y-19820000D02*
X20250000Y-19820000D01*
%LPC*%
D12*
X9250000Y-4765564D03*
X2750000Y-4765564D03*
X2750000Y-9265564D03*
X9250000Y-9265564D03*
D13*
X7620000Y-35560000D03*
D14*
X7620000Y-38100000D03*
X7620000Y-40640000D03*
X7620000Y-43180000D03*
X7620000Y-45720000D03*
X7620000Y-48260000D03*
X7620000Y-50800000D03*
X7620000Y-53340000D03*
X7620000Y-55880000D03*
X7620000Y-58420000D03*
X7620000Y-60960000D03*
X7620000Y-63500000D03*
X7620000Y-66040000D03*
X7620000Y-68580000D03*
X7620000Y-71120000D03*
X7620000Y-73660000D03*
X7620000Y-76200000D03*
X7620000Y-78740000D03*
X7620000Y-81280000D03*
D15*
X23290000Y-5080000D03*
D16*
X25830000Y-5080000D03*
D13*
X32975000Y-35570000D03*
D14*
X32975000Y-38110000D03*
X32975000Y-40650000D03*
X32975000Y-43190000D03*
X32975000Y-45730000D03*
X32975000Y-48270000D03*
X32975000Y-50810000D03*
X32975000Y-53350000D03*
X32975000Y-55890000D03*
X32975000Y-58430000D03*
X32975000Y-60970000D03*
X32975000Y-63510000D03*
X32975000Y-66050000D03*
X32975000Y-68590000D03*
X32975000Y-71130000D03*
X32975000Y-73670000D03*
X32975000Y-76210000D03*
X32975000Y-78750000D03*
X32975000Y-81290000D03*
D17*
X28256000Y-17005000D03*
X28256000Y-19005000D03*
X28256000Y-21005000D03*
X28206000Y-23005000D03*
X30006000Y-20005000D03*
X30006000Y-22005000D03*
X31756000Y-17005000D03*
X31756000Y-19005000D03*
X31756000Y-21005000D03*
X31756000Y-23005000D03*
D15*
X28830000Y-5080000D03*
D16*
X31370000Y-5080000D03*
D12*
X19250000Y-4765564D03*
X12750000Y-4765564D03*
X19250000Y-9265564D03*
X12750000Y-9265564D03*
D15*
X34370000Y-5080000D03*
D16*
X36910000Y-5080000D03*
D12*
X14750000Y-20820000D03*
X21250000Y-20820000D03*
X14750000Y-25320000D03*
X21250000Y-25320000D03*
M02*

View File

@@ -0,0 +1,26 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,(6.0.11)*
G04 #@! TF.CreationDate,2023-03-04T17:24:57+01:00*
G04 #@! TF.ProjectId,jig2,6a696732-2e6b-4696-9361-645f70636258,rev?*
G04 #@! TF.SameCoordinates,Original*
G04 #@! TF.FileFunction,Profile,NP*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW (6.0.11)) date 2023-03-04 17:24:57*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 #@! TA.AperFunction,Profile*
%ADD10C,0.100000*%
G04 #@! TD*
G04 APERTURE END LIST*
D10*
X0Y-85000000D02*
X0Y0D01*
X40000000Y-85000000D02*
X0Y-85000000D01*
X40000000Y0D02*
X40000000Y-85000000D01*
X0Y0D02*
X40000000Y0D01*
M02*

View File

@@ -0,0 +1,528 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,(6.0.11)*
G04 #@! TF.CreationDate,2023-03-04T17:24:56+01:00*
G04 #@! TF.ProjectId,jig2,6a696732-2e6b-4696-9361-645f70636258,rev?*
G04 #@! TF.SameCoordinates,Original*
G04 #@! TF.FileFunction,Copper,L1,Top*
G04 #@! TF.FilePolarity,Positive*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW (6.0.11)) date 2023-03-04 17:24:56*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 Aperture macros list*
%AMRoundRect*
0 Rectangle with rounded corners*
0 $1 Rounding radius*
0 $2 $3 $4 $5 $6 $7 $8 $9 X,Y pos of 4 corners*
0 Add a 4 corners polygon primitive as box body*
4,1,4,$2,$3,$4,$5,$6,$7,$8,$9,$2,$3,0*
0 Add four circle primitives for the rounded corners*
1,1,$1+$1,$2,$3*
1,1,$1+$1,$4,$5*
1,1,$1+$1,$6,$7*
1,1,$1+$1,$8,$9*
0 Add four rect primitives between the rounded corners*
20,1,$1+$1,$2,$3,$4,$5,0*
20,1,$1+$1,$4,$5,$6,$7,0*
20,1,$1+$1,$6,$7,$8,$9,0*
20,1,$1+$1,$8,$9,$2,$3,0*%
%AMFreePoly0*
4,1,22,0.500000,-0.750000,0.000000,-0.750000,0.000000,-0.745033,-0.079941,-0.743568,-0.215256,-0.701293,-0.333266,-0.622738,-0.424486,-0.514219,-0.481581,-0.384460,-0.499164,-0.250000,-0.500000,-0.250000,-0.500000,0.250000,-0.499164,0.250000,-0.499963,0.256109,-0.478152,0.396186,-0.417904,0.524511,-0.324060,0.630769,-0.204165,0.706417,-0.067858,0.745374,0.000000,0.744959,0.000000,0.750000,
0.500000,0.750000,0.500000,-0.750000,0.500000,-0.750000,$1*%
%AMFreePoly1*
4,1,20,0.000000,0.744959,0.073905,0.744508,0.209726,0.703889,0.328688,0.626782,0.421226,0.519385,0.479903,0.390333,0.500000,0.250000,0.500000,-0.250000,0.499851,-0.262216,0.476331,-0.402017,0.414519,-0.529596,0.319384,-0.634700,0.198574,-0.708877,0.061801,-0.746166,0.000000,-0.745033,0.000000,-0.750000,-0.500000,-0.750000,-0.500000,0.750000,0.000000,0.750000,0.000000,0.744959,
0.000000,0.744959,$1*%
G04 Aperture macros list end*
G04 #@! TA.AperFunction,SMDPad,CuDef*
%ADD10FreePoly0,180.000000*%
G04 #@! TD*
G04 #@! TA.AperFunction,SMDPad,CuDef*
%ADD11FreePoly1,180.000000*%
G04 #@! TD*
G04 #@! TA.AperFunction,SMDPad,CuDef*
%ADD12RoundRect,0.250000X0.350000X0.450000X-0.350000X0.450000X-0.350000X-0.450000X0.350000X-0.450000X0*%
G04 #@! TD*
G04 #@! TA.AperFunction,SMDPad,CuDef*
%ADD13RoundRect,0.250000X-0.450000X0.350000X-0.450000X-0.350000X0.450000X-0.350000X0.450000X0.350000X0*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD14C,2.000000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD15R,1.700000X1.700000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD16O,1.700000X1.700000*%
G04 #@! TD*
G04 #@! TA.AperFunction,SMDPad,CuDef*
%ADD17R,0.800000X1.900000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD18R,1.800000X1.800000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD19C,1.800000*%
G04 #@! TD*
G04 #@! TA.AperFunction,SMDPad,CuDef*
%ADD20RoundRect,0.250000X-0.350000X-0.450000X0.350000X-0.450000X0.350000X0.450000X-0.350000X0.450000X0*%
G04 #@! TD*
G04 #@! TA.AperFunction,SMDPad,CuDef*
%ADD21R,1.900000X0.800000*%
G04 #@! TD*
G04 #@! TA.AperFunction,SMDPad,CuDef*
%ADD22FreePoly0,90.000000*%
G04 #@! TD*
G04 #@! TA.AperFunction,SMDPad,CuDef*
%ADD23FreePoly1,90.000000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ComponentPad*
%ADD24C,1.270000*%
G04 #@! TD*
G04 #@! TA.AperFunction,SMDPad,CuDef*
%ADD25C,2.500000*%
G04 #@! TD*
G04 #@! TA.AperFunction,ViaPad*
%ADD26C,0.600000*%
G04 #@! TD*
G04 #@! TA.AperFunction,Conductor*
%ADD27C,0.500000*%
G04 #@! TD*
G04 #@! TA.AperFunction,Conductor*
%ADD28C,1.000000*%
G04 #@! TD*
G04 APERTURE END LIST*
G36*
X25250000Y-81350000D02*
G01*
X24750000Y-81350000D01*
X24750000Y-80950000D01*
X25250000Y-80950000D01*
X25250000Y-81350000D01*
G37*
G36*
X25250000Y-80550000D02*
G01*
X24750000Y-80550000D01*
X24750000Y-80150000D01*
X25250000Y-80150000D01*
X25250000Y-80550000D01*
G37*
D10*
X25650000Y-80750000D03*
D11*
X24350000Y-80750000D03*
D12*
X18250000Y-81120000D03*
X16250000Y-81120000D03*
D13*
X6000000Y-13000000D03*
X6000000Y-15000000D03*
D14*
X9250000Y-4765564D03*
X2750000Y-4765564D03*
X2750000Y-9265564D03*
X9250000Y-9265564D03*
D15*
X7620000Y-35560000D03*
D16*
X7620000Y-38100000D03*
X7620000Y-40640000D03*
X7620000Y-43180000D03*
X7620000Y-45720000D03*
X7620000Y-48260000D03*
X7620000Y-50800000D03*
X7620000Y-53340000D03*
X7620000Y-55880000D03*
X7620000Y-58420000D03*
X7620000Y-60960000D03*
X7620000Y-63500000D03*
X7620000Y-66040000D03*
X7620000Y-68580000D03*
X7620000Y-71120000D03*
X7620000Y-73660000D03*
X7620000Y-76200000D03*
X7620000Y-78740000D03*
X7620000Y-81280000D03*
D17*
X23380000Y-75120000D03*
X21480000Y-75120000D03*
X22430000Y-78120000D03*
D13*
X35500000Y-10000000D03*
X35500000Y-12000000D03*
X30500000Y-10000000D03*
X30500000Y-12000000D03*
X16000000Y-13000000D03*
X16000000Y-15000000D03*
D18*
X23290000Y-5080000D03*
D19*
X25830000Y-5080000D03*
D20*
X21250000Y-71120000D03*
X23250000Y-71120000D03*
D15*
X32975000Y-35570000D03*
D16*
X32975000Y-38110000D03*
X32975000Y-40650000D03*
X32975000Y-43190000D03*
X32975000Y-45730000D03*
X32975000Y-48270000D03*
X32975000Y-50810000D03*
X32975000Y-53350000D03*
X32975000Y-55890000D03*
X32975000Y-58430000D03*
X32975000Y-60970000D03*
X32975000Y-63510000D03*
X32975000Y-66050000D03*
X32975000Y-68590000D03*
X32975000Y-71130000D03*
X32975000Y-73670000D03*
X32975000Y-76210000D03*
X32975000Y-78750000D03*
X32975000Y-81290000D03*
D21*
X13400000Y-74470000D03*
X13400000Y-76370000D03*
X16400000Y-75420000D03*
D13*
X24500000Y-10000000D03*
X24500000Y-12000000D03*
D22*
X26250000Y-72650000D03*
D23*
X26250000Y-71350000D03*
D24*
X28256000Y-17005000D03*
X28256000Y-19005000D03*
X28256000Y-21005000D03*
X28206000Y-23005000D03*
X30006000Y-20005000D03*
X30006000Y-22005000D03*
X31756000Y-17005000D03*
X31756000Y-19005000D03*
X31756000Y-21005000D03*
X31756000Y-23005000D03*
D25*
X28500000Y-77500000D03*
D18*
X28830000Y-5080000D03*
D19*
X31370000Y-5080000D03*
D13*
X10750000Y-25500000D03*
X10750000Y-27500000D03*
D14*
X19250000Y-4765564D03*
X12750000Y-4765564D03*
X19250000Y-9265564D03*
X12750000Y-9265564D03*
D18*
X34370000Y-5080000D03*
D19*
X36910000Y-5080000D03*
D12*
X18750000Y-71120000D03*
X16750000Y-71120000D03*
D14*
X14750000Y-20820000D03*
X21250000Y-20820000D03*
X14750000Y-25320000D03*
X21250000Y-25320000D03*
D26*
X23000000Y-35500000D03*
X24000000Y-39700000D03*
X11000000Y-29750000D03*
X11500000Y-15000000D03*
X14440000Y-79310000D03*
X7620000Y-21620000D03*
X20500000Y-17000000D03*
X20500000Y-15250000D03*
X21000000Y-13750000D03*
X26750000Y-38250000D03*
D27*
X34975000Y-20225000D02*
X33755000Y-19005000D01*
X33755000Y-19005000D02*
X31756000Y-19005000D01*
X34975000Y-58970000D02*
X34975000Y-20225000D01*
X32975000Y-60970000D02*
X34975000Y-58970000D01*
X25830000Y-8670000D02*
X24500000Y-10000000D01*
X25830000Y-5080000D02*
X25830000Y-8670000D01*
X30671000Y-24090000D02*
X31756000Y-23005000D01*
X29050000Y-49200000D02*
X29050000Y-25700000D01*
X30660000Y-24090000D02*
X30671000Y-24090000D01*
X29000000Y-49250000D02*
X29050000Y-49200000D01*
X29050000Y-25700000D02*
X30660000Y-24090000D01*
X32975000Y-50810000D02*
X30560000Y-50810000D01*
X30560000Y-50810000D02*
X29000000Y-49250000D01*
X31370000Y-9130000D02*
X30500000Y-10000000D01*
X31370000Y-5080000D02*
X31370000Y-9130000D01*
X28300000Y-24450000D02*
X29300000Y-24450000D01*
X32975000Y-53350000D02*
X29350000Y-53350000D01*
X30006000Y-23244000D02*
X30006000Y-22005000D01*
X29300000Y-24450000D02*
X30000000Y-23750000D01*
X30000000Y-23250000D02*
X30006000Y-23244000D01*
X29350000Y-53350000D02*
X28300000Y-52300000D01*
X28300000Y-52300000D02*
X28300000Y-24450000D01*
X30000000Y-23750000D02*
X30000000Y-23250000D01*
X36910000Y-8590000D02*
X35500000Y-10000000D01*
X36910000Y-5080000D02*
X36910000Y-8590000D01*
X17500000Y-15000000D02*
X16000000Y-15000000D01*
D28*
X21480000Y-75120000D02*
X21480000Y-71350000D01*
X26250000Y-69750000D02*
X26250000Y-71350000D01*
X21250000Y-68500000D02*
X25000000Y-68500000D01*
D27*
X24500000Y-12000000D02*
X20500000Y-12000000D01*
X10750000Y-27500000D02*
X10750000Y-29500000D01*
X20500000Y-12000000D02*
X17500000Y-15000000D01*
X11000000Y-29750000D02*
X17250000Y-29750000D01*
D28*
X21480000Y-71350000D02*
X21250000Y-71120000D01*
D27*
X17250000Y-29750000D02*
X23000000Y-35500000D01*
X35500000Y-12000000D02*
X30500000Y-12000000D01*
D28*
X25000000Y-68500000D02*
X26250000Y-69750000D01*
X21250000Y-42450000D02*
X21250000Y-68500000D01*
X21250000Y-71120000D02*
X18750000Y-71120000D01*
D27*
X6000000Y-15000000D02*
X11500000Y-15000000D01*
D28*
X24000000Y-39700000D02*
X21250000Y-42450000D01*
D27*
X10750000Y-29500000D02*
X11000000Y-29750000D01*
D28*
X21250000Y-68500000D02*
X21250000Y-71120000D01*
D27*
X13000000Y-15000000D02*
X16000000Y-15000000D01*
X30500000Y-12000000D02*
X24500000Y-12000000D01*
X11500000Y-15000000D02*
X13000000Y-15000000D01*
X16000000Y-12515564D02*
X16000000Y-13000000D01*
X12750000Y-9265564D02*
X16000000Y-12515564D01*
X19250000Y-9265564D02*
X16000000Y-12515564D01*
X6000000Y-12515564D02*
X6000000Y-13000000D01*
X2750000Y-9265564D02*
X6000000Y-12515564D01*
X9250000Y-9265564D02*
X6000000Y-12515564D01*
X14750000Y-25320000D02*
X11320000Y-25320000D01*
X21250000Y-25320000D02*
X14750000Y-25320000D01*
X28206000Y-23005000D02*
X28206000Y-23544000D01*
X27600000Y-55100000D02*
X28390000Y-55890000D01*
X28390000Y-55890000D02*
X32975000Y-55890000D01*
X27600000Y-24150000D02*
X27600000Y-55100000D01*
X28206000Y-23544000D02*
X27600000Y-24150000D01*
X34275000Y-57130000D02*
X34275000Y-23524000D01*
X32975000Y-58430000D02*
X34275000Y-57130000D01*
X34275000Y-23524000D02*
X31756000Y-21005000D01*
X13400000Y-78270000D02*
X13400000Y-76370000D01*
X8420000Y-20820000D02*
X7620000Y-21620000D01*
X9250000Y-4765564D02*
X2750000Y-4765564D01*
X16250000Y-81120000D02*
X14440000Y-79310000D01*
X14750000Y-20820000D02*
X8420000Y-20820000D01*
X12750000Y-4765564D02*
X9250000Y-4765564D01*
X14440000Y-79310000D02*
X13400000Y-78270000D01*
X14750000Y-20820000D02*
X21250000Y-20820000D01*
X19250000Y-4765564D02*
X12750000Y-4765564D01*
X30006000Y-20005000D02*
X30006000Y-15744000D01*
X35675000Y-16425000D02*
X35675000Y-68430000D01*
X34250000Y-15000000D02*
X35675000Y-16425000D01*
X30006000Y-15744000D02*
X30750000Y-15000000D01*
X35675000Y-68430000D02*
X32975000Y-71130000D01*
X30750000Y-15000000D02*
X34250000Y-15000000D01*
X26200000Y-34800000D02*
X26200000Y-19550000D01*
X26745000Y-19005000D02*
X28256000Y-19005000D01*
X26200000Y-19550000D02*
X26745000Y-19005000D01*
X22900000Y-38100000D02*
X26200000Y-34800000D01*
X7620000Y-38100000D02*
X22900000Y-38100000D01*
X9460000Y-38800000D02*
X7620000Y-40640000D01*
X26900000Y-35100000D02*
X23200000Y-38800000D01*
X27245000Y-21005000D02*
X26900000Y-21350000D01*
X28256000Y-21005000D02*
X27245000Y-21005000D01*
X26900000Y-21350000D02*
X26900000Y-35100000D01*
X23200000Y-38800000D02*
X9460000Y-38800000D01*
X3400000Y-19089950D02*
X3400000Y-46400000D01*
X4169975Y-18319975D02*
X3400000Y-19089950D01*
X3400000Y-46400000D02*
X5260000Y-48260000D01*
X20500000Y-17000000D02*
X19180025Y-18319975D01*
X5260000Y-48260000D02*
X7620000Y-48260000D01*
X19180025Y-18319975D02*
X4169975Y-18319975D01*
X2700000Y-51700000D02*
X4340000Y-53340000D01*
X2700000Y-18800000D02*
X2700000Y-51700000D01*
X18130025Y-17619975D02*
X18010050Y-17500000D01*
X20500000Y-15250000D02*
X18130025Y-17619975D01*
X4000000Y-17500000D02*
X2700000Y-18800000D01*
X18010050Y-17500000D02*
X4000000Y-17500000D01*
X4340000Y-53340000D02*
X7620000Y-53340000D01*
X3380000Y-55880000D02*
X7620000Y-55880000D01*
X17519390Y-16000000D02*
X16769390Y-16750000D01*
X20939339Y-13750000D02*
X18689339Y-16000000D01*
X16769390Y-16750000D02*
X3250000Y-16750000D01*
X3250000Y-16750000D02*
X2000000Y-18000000D01*
X21000000Y-13750000D02*
X20939339Y-13750000D01*
X2000000Y-18000000D02*
X2000000Y-54500000D01*
X2000000Y-54500000D02*
X3380000Y-55880000D01*
X18689339Y-16000000D02*
X17519390Y-16000000D01*
X15060000Y-70310000D02*
X10790000Y-66040000D01*
X10790000Y-66040000D02*
X7620000Y-66040000D01*
X16750000Y-71120000D02*
X15060000Y-72810000D01*
X15060000Y-72810000D02*
X13400000Y-74470000D01*
X15060000Y-72810000D02*
X15060000Y-70310000D01*
X23380000Y-75120000D02*
X23380000Y-71250000D01*
X23380000Y-75120000D02*
X23380000Y-75470000D01*
X23380000Y-71250000D02*
X23250000Y-71120000D01*
X22330000Y-76520000D02*
X17500000Y-76520000D01*
X23380000Y-75470000D02*
X22330000Y-76520000D01*
X17500000Y-76520000D02*
X16400000Y-75420000D01*
D28*
X26000000Y-55250000D02*
X26000000Y-38750000D01*
X28500000Y-77500000D02*
X28500000Y-57750000D01*
X25650000Y-80350000D02*
X28500000Y-77500000D01*
X28500000Y-57750000D02*
X26000000Y-55250000D01*
X26000000Y-38750000D02*
X26500000Y-38250000D01*
X25650000Y-80750000D02*
X25650000Y-80350000D01*
X26500000Y-38250000D02*
X26650000Y-38250000D01*
X21250000Y-78120000D02*
X18250000Y-81120000D01*
X23380000Y-78120000D02*
X22430000Y-78120000D01*
X26250000Y-72650000D02*
X26250000Y-75250000D01*
X22430000Y-78830000D02*
X24350000Y-80750000D01*
X22430000Y-78120000D02*
X21250000Y-78120000D01*
X22430000Y-78120000D02*
X22430000Y-78830000D01*
X26250000Y-75250000D02*
X23380000Y-78120000D01*
M02*

View File

@@ -0,0 +1,178 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,(6.0.11)*
G04 #@! TF.CreationDate,2023-03-04T17:24:57+01:00*
G04 #@! TF.ProjectId,jig2,6a696732-2e6b-4696-9361-645f70636258,rev?*
G04 #@! TF.SameCoordinates,Original*
G04 #@! TF.FileFunction,Soldermask,Top*
G04 #@! TF.FilePolarity,Negative*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW (6.0.11)) date 2023-03-04 17:24:57*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 Aperture macros list*
%AMRoundRect*
0 Rectangle with rounded corners*
0 $1 Rounding radius*
0 $2 $3 $4 $5 $6 $7 $8 $9 X,Y pos of 4 corners*
0 Add a 4 corners polygon primitive as box body*
4,1,4,$2,$3,$4,$5,$6,$7,$8,$9,$2,$3,0*
0 Add four circle primitives for the rounded corners*
1,1,$1+$1,$2,$3*
1,1,$1+$1,$4,$5*
1,1,$1+$1,$6,$7*
1,1,$1+$1,$8,$9*
0 Add four rect primitives between the rounded corners*
20,1,$1+$1,$2,$3,$4,$5,0*
20,1,$1+$1,$4,$5,$6,$7,0*
20,1,$1+$1,$6,$7,$8,$9,0*
20,1,$1+$1,$8,$9,$2,$3,0*%
%AMFreePoly0*
4,1,22,0.500000,-0.750000,0.000000,-0.750000,0.000000,-0.745033,-0.079941,-0.743568,-0.215256,-0.701293,-0.333266,-0.622738,-0.424486,-0.514219,-0.481581,-0.384460,-0.499164,-0.250000,-0.500000,-0.250000,-0.500000,0.250000,-0.499164,0.250000,-0.499963,0.256109,-0.478152,0.396186,-0.417904,0.524511,-0.324060,0.630769,-0.204165,0.706417,-0.067858,0.745374,0.000000,0.744959,0.000000,0.750000,
0.500000,0.750000,0.500000,-0.750000,0.500000,-0.750000,$1*%
%AMFreePoly1*
4,1,20,0.000000,0.744959,0.073905,0.744508,0.209726,0.703889,0.328688,0.626782,0.421226,0.519385,0.479903,0.390333,0.500000,0.250000,0.500000,-0.250000,0.499851,-0.262216,0.476331,-0.402017,0.414519,-0.529596,0.319384,-0.634700,0.198574,-0.708877,0.061801,-0.746166,0.000000,-0.745033,0.000000,-0.750000,-0.500000,-0.750000,-0.500000,0.750000,0.000000,0.750000,0.000000,0.744959,
0.000000,0.744959,$1*%
G04 Aperture macros list end*
%ADD10FreePoly0,180.000000*%
%ADD11FreePoly1,180.000000*%
%ADD12RoundRect,0.250000X0.350000X0.450000X-0.350000X0.450000X-0.350000X-0.450000X0.350000X-0.450000X0*%
%ADD13RoundRect,0.250000X-0.450000X0.350000X-0.450000X-0.350000X0.450000X-0.350000X0.450000X0.350000X0*%
%ADD14C,2.000000*%
%ADD15R,1.700000X1.700000*%
%ADD16O,1.700000X1.700000*%
%ADD17R,0.800000X1.900000*%
%ADD18R,1.800000X1.800000*%
%ADD19C,1.800000*%
%ADD20RoundRect,0.250000X-0.350000X-0.450000X0.350000X-0.450000X0.350000X0.450000X-0.350000X0.450000X0*%
%ADD21R,1.900000X0.800000*%
%ADD22FreePoly0,90.000000*%
%ADD23FreePoly1,90.000000*%
%ADD24C,1.371600*%
%ADD25C,2.500000*%
G04 APERTURE END LIST*
D10*
X25650000Y-80750000D03*
D11*
X24350000Y-80750000D03*
D12*
X18250000Y-81120000D03*
X16250000Y-81120000D03*
D13*
X6000000Y-13000000D03*
X6000000Y-15000000D03*
D14*
X9250000Y-4765564D03*
X2750000Y-4765564D03*
X2750000Y-9265564D03*
X9250000Y-9265564D03*
D15*
X7620000Y-35560000D03*
D16*
X7620000Y-38100000D03*
X7620000Y-40640000D03*
X7620000Y-43180000D03*
X7620000Y-45720000D03*
X7620000Y-48260000D03*
X7620000Y-50800000D03*
X7620000Y-53340000D03*
X7620000Y-55880000D03*
X7620000Y-58420000D03*
X7620000Y-60960000D03*
X7620000Y-63500000D03*
X7620000Y-66040000D03*
X7620000Y-68580000D03*
X7620000Y-71120000D03*
X7620000Y-73660000D03*
X7620000Y-76200000D03*
X7620000Y-78740000D03*
X7620000Y-81280000D03*
D17*
X23380000Y-75120000D03*
X21480000Y-75120000D03*
X22430000Y-78120000D03*
D13*
X35500000Y-10000000D03*
X35500000Y-12000000D03*
X30500000Y-10000000D03*
X30500000Y-12000000D03*
X16000000Y-13000000D03*
X16000000Y-15000000D03*
D18*
X23290000Y-5080000D03*
D19*
X25830000Y-5080000D03*
D20*
X21250000Y-71120000D03*
X23250000Y-71120000D03*
D15*
X32975000Y-35570000D03*
D16*
X32975000Y-38110000D03*
X32975000Y-40650000D03*
X32975000Y-43190000D03*
X32975000Y-45730000D03*
X32975000Y-48270000D03*
X32975000Y-50810000D03*
X32975000Y-53350000D03*
X32975000Y-55890000D03*
X32975000Y-58430000D03*
X32975000Y-60970000D03*
X32975000Y-63510000D03*
X32975000Y-66050000D03*
X32975000Y-68590000D03*
X32975000Y-71130000D03*
X32975000Y-73670000D03*
X32975000Y-76210000D03*
X32975000Y-78750000D03*
X32975000Y-81290000D03*
D21*
X13400000Y-74470000D03*
X13400000Y-76370000D03*
X16400000Y-75420000D03*
D13*
X24500000Y-10000000D03*
X24500000Y-12000000D03*
D22*
X26250000Y-72650000D03*
D23*
X26250000Y-71350000D03*
D24*
X28256000Y-17005000D03*
X28256000Y-19005000D03*
X28256000Y-21005000D03*
X28206000Y-23005000D03*
X30006000Y-20005000D03*
X30006000Y-22005000D03*
X31756000Y-17005000D03*
X31756000Y-19005000D03*
X31756000Y-21005000D03*
X31756000Y-23005000D03*
D25*
X28500000Y-77500000D03*
D18*
X28830000Y-5080000D03*
D19*
X31370000Y-5080000D03*
D13*
X10750000Y-25500000D03*
X10750000Y-27500000D03*
D14*
X19250000Y-4765564D03*
X12750000Y-4765564D03*
X19250000Y-9265564D03*
X12750000Y-9265564D03*
D18*
X34370000Y-5080000D03*
D19*
X36910000Y-5080000D03*
D12*
X18750000Y-71120000D03*
X16750000Y-71120000D03*
D14*
X14750000Y-20820000D03*
X21250000Y-20820000D03*
X14750000Y-25320000D03*
X21250000Y-25320000D03*
M02*

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