mirror of
https://github.com/OpenEPaperLink/OpenEPaperLink.git
synced 2026-03-21 11:06:49 +01:00
Compare commits
187 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb0064363f | ||
|
|
c1324c5089 | ||
|
|
df3c615eef | ||
|
|
d5e19d20fa | ||
|
|
e62a1b07bf | ||
|
|
4d06ef546d | ||
|
|
7096f7e756 | ||
|
|
1abedff388 | ||
|
|
8d2546a2aa | ||
|
|
c5a8058d62 | ||
|
|
ef59b87ae0 | ||
|
|
d526c43fd8 | ||
|
|
86abc112dd | ||
|
|
af50f96671 | ||
|
|
46c8b73fa0 | ||
|
|
c65c8b749e | ||
|
|
f61c2e3d04 | ||
|
|
0dc406b865 | ||
|
|
25b185da28 | ||
|
|
496d4d382f | ||
|
|
d09e89c9dd | ||
|
|
ce319bb499 | ||
|
|
4dbcc753fd | ||
|
|
6951cd79b7 | ||
|
|
eeb18f204d | ||
|
|
bfff2ef0b9 | ||
|
|
ef5ddac368 | ||
|
|
0e8e7b5b75 | ||
|
|
e8a92c4fcb | ||
|
|
299b8f300e | ||
|
|
0b0802ad02 | ||
|
|
f8ce3a51d2 | ||
|
|
0e63e064fc | ||
|
|
8d6c763aba | ||
|
|
764747fb45 | ||
|
|
6bb2e0362b | ||
|
|
54dd05e698 | ||
|
|
328f00a559 | ||
|
|
14fda5c32a | ||
|
|
624bf49ee3 | ||
|
|
ab48cbe747 | ||
|
|
2a5094993c | ||
|
|
49d981f006 | ||
|
|
fb2abf933d | ||
|
|
b78e4a6099 | ||
|
|
e0df4490b3 | ||
|
|
7226793e41 | ||
|
|
934d33f950 | ||
|
|
a143b49492 | ||
|
|
f048cfb296 | ||
|
|
89fec64e91 | ||
|
|
bdb1e8af82 | ||
|
|
f40238a49d | ||
|
|
957b94469f | ||
|
|
c2255a3de7 | ||
|
|
caf5e49595 | ||
|
|
7782a37c97 | ||
|
|
f86f2ce587 | ||
|
|
7d1b81690b | ||
|
|
ac6a46262a | ||
|
|
bde464e8c1 | ||
|
|
b2e73c9360 | ||
|
|
863e18a4d7 | ||
|
|
d90f4e181a | ||
|
|
33c7053121 | ||
|
|
5f06f5b0a9 | ||
|
|
18baa45433 | ||
|
|
43c9a69f88 | ||
|
|
b313d07669 | ||
|
|
eb00a1d9c4 | ||
|
|
f1c9ac0a75 | ||
|
|
0b2a8b38ac | ||
|
|
561dd82236 | ||
|
|
66e0b5d9f6 | ||
|
|
08329b89c5 | ||
|
|
e8eb87e7c1 | ||
|
|
c621050f18 | ||
|
|
2fc5c54b8e | ||
|
|
e2fb26c0ad | ||
|
|
d53cc834c4 | ||
|
|
5755e4aad9 | ||
|
|
87ce6d949d | ||
|
|
e102f8e4e9 | ||
|
|
0fb0c6f74d | ||
|
|
5dfd0e4582 | ||
|
|
f311239c9c | ||
|
|
c3e729744a | ||
|
|
447611ba4a | ||
|
|
6637405358 | ||
|
|
32c74ba5b4 | ||
|
|
177f93844c | ||
|
|
d76d110f39 | ||
|
|
1584f35624 | ||
|
|
ac0c3ccfc9 | ||
|
|
3810fbf68c | ||
|
|
20b4f728e4 | ||
|
|
047230de25 | ||
|
|
107764c6be | ||
|
|
0819b19db2 | ||
|
|
4aedce7839 | ||
|
|
2d486d7c66 | ||
|
|
ba8a5c6990 | ||
|
|
1ae015c65f | ||
|
|
92681aa4c5 | ||
|
|
004438cee9 | ||
|
|
b7546cf6d4 | ||
|
|
cce5f56d67 | ||
|
|
eb58b7fc02 | ||
|
|
8f0362455a | ||
|
|
79fe05581c | ||
|
|
658b3b8635 | ||
|
|
77da5964bf | ||
|
|
0232725711 | ||
|
|
ff7f397705 | ||
|
|
d8fa96b20e | ||
|
|
15a9728f45 | ||
|
|
6c9439822b | ||
|
|
4b667d0039 | ||
|
|
19bbba5202 | ||
|
|
ad76d122e5 | ||
|
|
5ec69153b5 | ||
|
|
45427148f6 | ||
|
|
c5fb16836f | ||
|
|
63b6f911b6 | ||
|
|
dec9b17655 | ||
|
|
b8c4d4420e | ||
|
|
0b064a9cee | ||
|
|
4d186c81ff | ||
|
|
5cc7869c0f | ||
|
|
6a8450cbcb | ||
|
|
ca8781f956 | ||
|
|
311ae1a570 | ||
|
|
06b2718d7d | ||
|
|
0f574bc3e8 | ||
|
|
7e49c2a09e | ||
|
|
23cbadb9f6 | ||
|
|
fce2e43ef7 | ||
|
|
4f7a381eed | ||
|
|
6a0f1310e1 | ||
|
|
bb36185066 | ||
|
|
be8eac2fc5 | ||
|
|
e4ecf08e29 | ||
|
|
99c048a29d | ||
|
|
f49731a240 | ||
|
|
375662c69e | ||
|
|
e246ac578d | ||
|
|
d8dcd498a3 | ||
|
|
91b01c5fca | ||
|
|
30812dff49 | ||
|
|
31a90d1498 | ||
|
|
2d02da1574 | ||
|
|
33f77b2192 | ||
|
|
e079c30c54 | ||
|
|
eba9f54454 | ||
|
|
a0a39e98cd | ||
|
|
2144bd58f9 | ||
|
|
fdd87779d7 | ||
|
|
44514e24d1 | ||
|
|
8857ecb669 | ||
|
|
1871f53b5a | ||
|
|
1de47b1133 | ||
|
|
2e38e9f218 | ||
|
|
a48511145c | ||
|
|
ae87ac1960 | ||
|
|
d84a5f6e75 | ||
|
|
f4025fb18f | ||
|
|
9d4e31b01b | ||
|
|
5950c5df4a | ||
|
|
691b64d192 | ||
|
|
573ba8c424 | ||
|
|
511fa3e5dd | ||
|
|
cc3128d22a | ||
|
|
78e097738a | ||
|
|
534c52cebf | ||
|
|
8cf6d01098 | ||
|
|
aadbe7652e | ||
|
|
7735612a16 | ||
|
|
c22de350f6 | ||
|
|
95d5aac01a | ||
|
|
7a31db91ba | ||
|
|
344ded01ac | ||
|
|
4747669df8 | ||
|
|
a532c7c190 | ||
|
|
a6772c6fae | ||
|
|
6ec580d267 | ||
|
|
7805ab2b46 | ||
|
|
e6401f6840 |
14
.github/workflows/conditional-build-test.yml
vendored
14
.github/workflows/conditional-build-test.yml
vendored
@@ -5,7 +5,7 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
determine-builds:
|
||||
name: Evaluate Required Builds
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
# Map a step output to job output
|
||||
outputs:
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
tag-build:
|
||||
name: Build Tag FW
|
||||
needs: [determine-builds]
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code (with submodules)
|
||||
uses: actions/checkout@v4
|
||||
@@ -70,16 +70,22 @@ jobs:
|
||||
#- OpenEPaperLink_AP_and_Flasher
|
||||
- ESP32_S3_16_8_YELLOW_AP
|
||||
- OpenEPaperLink_Mini_AP_v4
|
||||
runs-on: ubuntu-22.04
|
||||
- OpenEPaperLink_ESP32-PoE-ISO_AP
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-pio
|
||||
|
||||
- name: Build ${{ matrix.environment }}
|
||||
- name: Build ${{ matrix.environment }} binary
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
pio run --environment ${{ matrix.environment }}
|
||||
- name: Build ${{ matrix.environment }} filesystem
|
||||
if: ${{ matrix.environment != 'OpenEPaperLink_ESP32-PoE-ISO_AP' }}
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
pio run --target buildfs --environment ${{ matrix.environment }}
|
||||
|
||||
|
||||
|
||||
|
||||
155
.github/workflows/release.yml
vendored
155
.github/workflows/release.yml
vendored
@@ -5,6 +5,12 @@ on:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
INCLUDE_C6_H2: true
|
||||
INCLUDE_MINI_AP: false
|
||||
INCLUDE_Nano_AP: false
|
||||
INCLUDE_S2_Tag_Flasher: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -20,14 +26,14 @@ jobs:
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
# - name: Commit zipped files
|
||||
|
||||
# - name: Commit zipped files
|
||||
# run: |
|
||||
# git config --global user.name 'Bot'
|
||||
# git config --global user.email "bot@openepaperlink.de"
|
||||
# git commit -am "Zipped web files"
|
||||
# git push origin HEAD:master
|
||||
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: pip install --upgrade platformio
|
||||
|
||||
@@ -41,37 +47,45 @@ jobs:
|
||||
run: |
|
||||
mkdir espbinaries
|
||||
|
||||
#- name: esp-idf build
|
||||
# uses: espressif/esp-idf-ci-action@v1
|
||||
# with:
|
||||
# esp_idf_version: latest
|
||||
# target: esp32c6
|
||||
# path: 'ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/'
|
||||
- name: build ESP32-C6 firmware
|
||||
if: ${{ env.INCLUDE_C6_H2 == 'true' }}
|
||||
uses: espressif/esp-idf-ci-action@v1
|
||||
with:
|
||||
esp_idf_version: latest
|
||||
target: esp32c6
|
||||
path: 'ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/'
|
||||
|
||||
# - name: esp-idf build
|
||||
# uses: espressif/esp-idf-ci-action@v1
|
||||
# with:
|
||||
# esp_idf_version: latest
|
||||
# target: esp32h2
|
||||
# path: 'ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/'
|
||||
- name: Add C6 files to release
|
||||
if: ${{ env.INCLUDE_C6_H2 == 'true' }}
|
||||
run: |
|
||||
cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/build/OpenEPaperLink_esp32_C6.bin espbinaries/OpenEPaperLink_esp32_C6.bin
|
||||
cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/build/bootloader/bootloader.bin espbinaries/bootloader_C6.bin
|
||||
cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/build/partition_table/partition-table.bin espbinaries/partition-table_C6.bin
|
||||
cp /home/runner/work/OpenEPaperLink/OpenEPaperLink//binaries/ESP32-C6/firmware_C6.json espbinaries
|
||||
|
||||
#- name: Add C6 files to release
|
||||
# run: |
|
||||
# cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/build/OpenEPaperLink_esp32_C6.bin espbinaries/OpenEPaperLink_esp32_C6.bin
|
||||
- name: build ESP32-H2 firmware
|
||||
if: ${{ env.INCLUDE_C6_H2 == 'true' }}
|
||||
uses: espressif/esp-idf-ci-action@v1
|
||||
with:
|
||||
esp_idf_version: latest
|
||||
target: esp32h2
|
||||
path: 'ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/'
|
||||
|
||||
#- name: Add H2 files to release
|
||||
# run: |
|
||||
# cd ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/
|
||||
# dir build
|
||||
# esptool.py --chip esp32h2 merge_bin -o merged-firmware.bin --flash_mode dio --flash_size 4MB --flash_freq 48m 0x0 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/OpenEPaperLink_esp32_C6.bin
|
||||
# cp merged-firmware.bin ../../espbinaries/OpenEPaperLink_esp32_H2.bin
|
||||
- name: Add H2 files to release
|
||||
if: ${{ env.INCLUDE_C6_H2 == 'true' }}
|
||||
run: |
|
||||
cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/build/OpenEPaperLink_esp32_H2.bin espbinaries/OpenEPaperLink_esp32_H2.bin
|
||||
cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/build/bootloader/bootloader.bin espbinaries/bootloader_H2.bin
|
||||
cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/build/partition_table/partition-table.bin espbinaries/partition-table_H2.bin
|
||||
cp /home/runner/work/OpenEPaperLink/OpenEPaperLink//binaries/ESP32-H2/firmware_H2.json espbinaries
|
||||
|
||||
# - name: Zip web files
|
||||
# run: |
|
||||
# run: |
|
||||
# cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_AP-Flasher
|
||||
# python gzip_wwwfiles.py
|
||||
|
||||
|
||||
- name: Build firmware for OpenEPaperLink_Mini_AP
|
||||
if: ${{ env.INCLUDE_MINI_AP == 'true' }}
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
|
||||
@@ -88,8 +102,9 @@ jobs:
|
||||
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
|
||||
if: ${{ env.INCLUDE_Nano_AP == 'true' }}
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
|
||||
@@ -106,11 +121,10 @@ jobs:
|
||||
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: move files for big APs
|
||||
# run: |
|
||||
# cp -a binaries/ESP32-C6/. ESP32_AP-Flasher/data/
|
||||
|
||||
|
||||
- name: Build firmware for OpenEPaperLink_AP_and_Flasher
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
@@ -128,7 +142,6 @@ jobs:
|
||||
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: Build firmware for ESP32_S3_16_8_YELLOW_AP
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
@@ -183,6 +196,24 @@ jobs:
|
||||
cp ESP32_S3_16_8_LILYGO_AP/firmware.bin espbinaries/ESP32_S3_16_8_LILYGO_AP.bin
|
||||
cp ESP32_S3_16_8_LILYGO_AP/merged-firmware.bin espbinaries/ESP32_S3_16_8_LILYGO_AP_full.bin
|
||||
|
||||
- name: Build firmware for ESP32_S3_16_8_LILYGO_T3
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
|
||||
pio run --environment ESP32_S3_16_8_LILYGO_T3
|
||||
pio run --target buildfs --environment ESP32_S3_16_8_LILYGO_T3
|
||||
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_LILYGO_T3
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_LILYGO_T3/boot_app0.bin
|
||||
cp .pio/build/ESP32_S3_16_8_LILYGO_T3/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_LILYGO_T3/firmware.bin
|
||||
cp .pio/build/ESP32_S3_16_8_LILYGO_T3/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_LILYGO_T3/bootloader.bin
|
||||
cp .pio/build/ESP32_S3_16_8_LILYGO_T3/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_LILYGO_T3/partitions.bin
|
||||
cp .pio/build/ESP32_S3_16_8_LILYGO_T3/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_LILYGO_T3/littlefs.bin
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_LILYGO_T3
|
||||
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 0x00910000 littlefs.bin
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
|
||||
cp ESP32_S3_16_8_LILYGO_T3/firmware.bin espbinaries/ESP32_S3_16_8_LILYGO_T3.bin
|
||||
cp ESP32_S3_16_8_LILYGO_T3/merged-firmware.bin espbinaries/ESP32_S3_16_8_LILYGO_T3_full.bin
|
||||
|
||||
- name: Build firmware for OpenEPaperLink_Nano_TLSR
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
@@ -200,7 +231,7 @@ jobs:
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
|
||||
cp OpenEPaperLink_Nano_TLSR/firmware.bin espbinaries/OpenEPaperLink_Nano_TLSR.bin
|
||||
cp OpenEPaperLink_Nano_TLSR/merged-firmware.bin espbinaries/OpenEPaperLink_Nano_TLSR_full.bin
|
||||
|
||||
|
||||
- name: Build firmware for OpenEPaperLink_PoE_AP
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
@@ -254,7 +285,65 @@ jobs:
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
|
||||
cp BLE_ONLY_AP/firmware.bin espbinaries/BLE_ONLY_AP.bin
|
||||
cp BLE_ONLY_AP/merged-firmware.bin espbinaries/BLE_ONLY_AP_full.bin
|
||||
|
||||
- name: Build firmware for OpenEPaperLink_Nano_TLSR_C6
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
|
||||
pio run --environment OpenEPaperLink_Nano_TLSR_C6
|
||||
pio run --target buildfs --environment OpenEPaperLink_Nano_TLSR_C6
|
||||
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_TLSR_C6
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_TLSR_C6/boot_app0.bin
|
||||
cp .pio/build/OpenEPaperLink_Nano_TLSR_C6/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_TLSR_C6/firmware.bin
|
||||
cp .pio/build/OpenEPaperLink_Nano_TLSR_C6/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_TLSR_C6/bootloader.bin
|
||||
cp .pio/build/OpenEPaperLink_Nano_TLSR_C6/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_TLSR_C6/partitions.bin
|
||||
cp .pio/build/OpenEPaperLink_Nano_TLSR_C6/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_TLSR_C6/littlefs.bin
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Nano_TLSR_C6
|
||||
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 0x00910000 littlefs.bin
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
|
||||
cp OpenEPaperLink_Nano_TLSR_C6/firmware.bin espbinaries/OpenEPaperLink_Nano_TLSR_C6.bin
|
||||
cp OpenEPaperLink_Nano_TLSR_C6/merged-firmware.bin espbinaries/OpenEPaperLink_Nano_TLSR_C6_full.bin
|
||||
|
||||
- name: Build firmware for ESP32_S3_16_8_4inch_AP
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
|
||||
pio run --environment ESP32_S3_16_8_4inch_AP
|
||||
pio run --target buildfs --environment ESP32_S3_16_8_4inch_AP
|
||||
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_4inch_AP
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_4inch_AP/boot_app0.bin
|
||||
cp .pio/build/ESP32_S3_16_8_4inch_AP/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_4inch_AP/firmware.bin
|
||||
cp .pio/build/ESP32_S3_16_8_4inch_AP/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_4inch_AP/bootloader.bin
|
||||
cp .pio/build/ESP32_S3_16_8_4inch_AP/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_4inch_AP/partitions.bin
|
||||
cp .pio/build/ESP32_S3_16_8_4inch_AP/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_4inch_AP/littlefs.bin
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_S3_16_8_4inch_AP
|
||||
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 0x00910000 littlefs.bin
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
|
||||
cp ESP32_S3_16_8_4inch_AP/firmware.bin espbinaries/ESP32_S3_16_8_4inch_AP.bin
|
||||
cp ESP32_S3_16_8_4inch_AP/merged-firmware.bin espbinaries/ESP32_S3_16_8_4inch_AP_full.bin
|
||||
|
||||
- name: Build firmware for OpenEPaperLink_ESP32-PoE-ISO_AP
|
||||
run: |
|
||||
cd ESP32_AP-Flasher
|
||||
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
|
||||
pio run --environment OpenEPaperLink_ESP32-PoE-ISO_AP
|
||||
mv data data.bak
|
||||
mkdir data
|
||||
pio run --target buildfs --environment OpenEPaperLink_ESP32-PoE-ISO_AP
|
||||
rmdir data
|
||||
mv data.bak data
|
||||
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_ESP32-PoE-ISO_AP
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_ESP32-PoE-ISO_AP/boot_app0.bin
|
||||
cp .pio/build/OpenEPaperLink_ESP32-PoE-ISO_AP/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_ESP32-PoE-ISO_AP/firmware.bin
|
||||
cp .pio/build/OpenEPaperLink_ESP32-PoE-ISO_AP/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_ESP32-PoE-ISO_AP/bootloader.bin
|
||||
cp .pio/build/OpenEPaperLink_ESP32-PoE-ISO_AP/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_ESP32-PoE-ISO_AP/partitions.bin
|
||||
cp .pio/build/OpenEPaperLink_ESP32-PoE-ISO_AP/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_ESP32-PoE-ISO_AP/littlefs.bin
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_ESP32-PoE-ISO_AP
|
||||
esptool.py --chip esp32 merge_bin -o merged-firmware.bin --flash_mode qio --flash_freq 80m --flash_size 4MB 0x0000 bootloader.bin 0x8000 partitions.bin 0xD000 boot_app0.bin 0x10000 firmware.bin 0x3D0000 littlefs.bin
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
|
||||
cp OpenEPaperLink_ESP32-PoE-ISO_AP/firmware.bin espbinaries/OpenEPaperLink_ESP32-PoE-ISO_AP.bin
|
||||
cp OpenEPaperLink_ESP32-PoE-ISO_AP/merged-firmware.bin espbinaries/OpenEPaperLink_ESP32-PoE-ISO_AP_full.bin
|
||||
|
||||
- name: generate release json file
|
||||
run: |
|
||||
mkdir jsonfiles
|
||||
@@ -268,10 +357,11 @@ jobs:
|
||||
tag: ${{ github.ref }}
|
||||
file_glob: true
|
||||
overwrite: true
|
||||
|
||||
|
||||
# this is down here intentionally to be able to modify the binary folder before adding it to the Tag_Flasher later (ota binaries can be removed)
|
||||
|
||||
- name: Build firmware for Tag_Flasher
|
||||
if: ${{ env.INCLUDE_S2_Tag_Flasher == 'true' }}
|
||||
run: |
|
||||
cd Tag_Flasher/ESP32_Flasher
|
||||
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
|
||||
@@ -288,7 +378,6 @@ jobs:
|
||||
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
|
||||
cp S2_Tag_Flasher/firmware.bin espbinaries/S2_Tag_Flasher.bin
|
||||
cp S2_Tag_Flasher/merged-firmware.bin espbinaries/S2_Tag_Flasher_full.bin
|
||||
|
||||
- name: Add esp bins to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
menu "OEPL Hardware config"
|
||||
menu "OEPL config"
|
||||
|
||||
choice OEPL_HARDWARE_PROFILE
|
||||
prompt "Hardware profile"
|
||||
@@ -13,6 +13,15 @@ menu "OEPL Hardware config"
|
||||
config OEPL_HARDWARE_PROFILE_CUSTOM
|
||||
bool "Custom"
|
||||
|
||||
config OEPL_HARDWARE_PROFILE_LILYGO
|
||||
bool "LILYGO-AP"
|
||||
|
||||
config OEPL_HARDWARE_PROFILE_4inch
|
||||
bool "4inchAP"
|
||||
|
||||
config OEPL_HARDWARE_PROFILE_ELECROW_C6
|
||||
bool "ELECROW-C6-AP"
|
||||
|
||||
endchoice
|
||||
|
||||
config OEPL_HARDWARE_UART_TX
|
||||
@@ -25,6 +34,16 @@ menu "OEPL Hardware config"
|
||||
int "GPIO - UART RX"
|
||||
default 2
|
||||
|
||||
config OEPL_HARDWARE_UART_TX
|
||||
depends on OEPL_HARDWARE_PROFILE_4inch
|
||||
int "GPIO - UART TX"
|
||||
default 16
|
||||
|
||||
config OEPL_HARDWARE_UART_RX
|
||||
depends on OEPL_HARDWARE_PROFILE_4inch
|
||||
int "GPIO - UART RX"
|
||||
default 17
|
||||
|
||||
config OEPL_SUBGIG_SUPPORT
|
||||
bool "Enable SubGhz Support"
|
||||
default "n"
|
||||
@@ -40,6 +59,7 @@ menu "OEPL Hardware config"
|
||||
default 18 if IDF_TARGET_ESP32C2
|
||||
default 19 if IDF_TARGET_ESP32C3
|
||||
default 30 if IDF_TARGET_ESP32C6
|
||||
default 30 if IDF_TARGET_ESP32H2
|
||||
|
||||
config MISO_GPIO
|
||||
int "CC1101 MISO GPIO"
|
||||
@@ -98,6 +118,16 @@ menu "OEPL Hardware config"
|
||||
USE SPI3_HOST. This is also called VSPI_HOST
|
||||
endchoice
|
||||
endmenu
|
||||
|
||||
config OEPL_DEBUG_PRINT
|
||||
bool "Enable OEPL Debug logging"
|
||||
default "n"
|
||||
|
||||
config OEPL_VERBOSE_DEBUG
|
||||
depends on OEPL_DEBUG_PRINT
|
||||
bool "Enable OEPL Verbose Debug logging"
|
||||
default "n"
|
||||
|
||||
endmenu
|
||||
|
||||
|
||||
|
||||
@@ -13,24 +13,16 @@
|
||||
|
||||
#include "radio.h"
|
||||
#include "proto.h"
|
||||
#include "utils.h"
|
||||
#include "second_uart.h"
|
||||
#include "cc1101_radio.h"
|
||||
#include "logging.h"
|
||||
#include "SubGigRadio.h"
|
||||
|
||||
void DumpHex(void *AdrIn,int Len);
|
||||
bool CC1101_QuickCheck(void);
|
||||
|
||||
#define LOGE(format, ... ) \
|
||||
printf("%s#%d: " format,__FUNCTION__,__LINE__,## __VA_ARGS__)
|
||||
|
||||
#if 0
|
||||
#define LOG(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
|
||||
#define LOG_RAW(format, ... ) printf(format,## __VA_ARGS__)
|
||||
#define LOG_HEX(x,y) DumpHex(x,y)
|
||||
#else
|
||||
#define LOG(format, ... )
|
||||
#define LOG_RAW(format, ... )
|
||||
#define LOG_HEX(x,y)
|
||||
#endif
|
||||
|
||||
#define wait_Miso(level) CC1101_WaitMISO(__FUNCTION__,__LINE__,level)
|
||||
|
||||
// SPI Stuff
|
||||
#if CONFIG_SPI2_HOST
|
||||
@@ -39,6 +31,9 @@ void DumpHex(void *AdrIn,int Len);
|
||||
#define HOST_ID SPI3_HOST
|
||||
#endif
|
||||
|
||||
// Wait for up to 2 milliseconds for MISO to go low
|
||||
#define MISO_WAIT_TIMEOUT 2
|
||||
|
||||
// Address Config = No address check
|
||||
// Base Frequency = xxx.xxx
|
||||
// CRC Enable = false
|
||||
@@ -247,10 +242,10 @@ SubGigErr SubGig_radio_init(uint8_t ch)
|
||||
SubGigErr Ret = SUBGIG_ERR_NONE;
|
||||
|
||||
do {
|
||||
gpio_reset_pin(CONFIG_CSN_GPIO);
|
||||
gpio_set_direction(CONFIG_CSN_GPIO, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(CONFIG_CSN_GPIO, 1);
|
||||
|
||||
if(!CC1101_QuickCheck()) {
|
||||
Ret = SUBGIG_CC1101_NOT_FOUND;
|
||||
break;
|
||||
}
|
||||
spi_bus_config_t buscfg = {
|
||||
.sclk_io_num = CONFIG_SCK_GPIO,
|
||||
.mosi_io_num = CONFIG_MOSI_GPIO,
|
||||
@@ -297,6 +292,7 @@ SubGigErr SubGig_radio_init(uint8_t ch)
|
||||
}
|
||||
// Check Chip ID
|
||||
if(!CC1101_Present()) {
|
||||
LOGE("CC1101 not detected\n");
|
||||
Ret = SUBGIG_CC1101_NOT_FOUND;
|
||||
break;
|
||||
}
|
||||
@@ -314,7 +310,7 @@ SubGigErr SubGig_radio_init(uint8_t ch)
|
||||
} while(false);
|
||||
|
||||
if(ErrLine != 0) {
|
||||
LOG("%s#%d: failed %d\n",__FUNCTION__,ErrLine,Err);
|
||||
LOGA("%s#%d: failed %d\n",__FUNCTION__,ErrLine,Err);
|
||||
if(Err == 0) {
|
||||
Ret = ESP_FAIL;
|
||||
}
|
||||
@@ -453,8 +449,6 @@ int8_t SubGig_commsRxUnencrypted(uint8_t *data)
|
||||
if(RxBytes >= 2) {
|
||||
// NB: RxBytes includes the CRC, deduct it
|
||||
Ret = (uint8_t) RxBytes - 2;
|
||||
LOG("Received %d byte subgig frame:\n",Ret);
|
||||
LOG_HEX(data,Ret);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
@@ -477,7 +471,7 @@ int CheckSubGigState()
|
||||
}
|
||||
|
||||
if(Err != SUBGIG_ERR_NONE) {
|
||||
LOG("CheckSubGigState: returing %d\n",Err);
|
||||
LOGE("Returning %d\n",Err);
|
||||
}
|
||||
|
||||
return Err;
|
||||
@@ -558,5 +552,96 @@ void DumpHex(void *AdrIn,int Len)
|
||||
LOG_RAW("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Quick and hopefully safe check if a CC1101 is present.
|
||||
// Only the CSN and MISO GPIOs are configured for this test.
|
||||
// If they are and there's a CC1101 then MISO should go low when
|
||||
// CSN is low
|
||||
bool CC1101_QuickCheck()
|
||||
{
|
||||
// Init CSn and MISO
|
||||
esp_err_t Err = ESP_OK;
|
||||
bool Ret = false;
|
||||
int Line = 0;
|
||||
int MisoLevel;
|
||||
|
||||
do {
|
||||
Err = gpio_reset_pin(CONFIG_MISO_GPIO);
|
||||
if(Err != ESP_OK) {
|
||||
Line = __LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
Err = gpio_set_direction(CONFIG_MISO_GPIO,GPIO_MODE_INPUT);
|
||||
if(Err != ESP_OK) {
|
||||
Line = __LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
Err = gpio_set_pull_mode(CONFIG_MISO_GPIO,GPIO_PULLUP_ONLY);
|
||||
if(Err != ESP_OK) {
|
||||
Line = __LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
Err = gpio_reset_pin(CONFIG_CSN_GPIO);
|
||||
if(Err != ESP_OK) {
|
||||
Line = __LINE__;
|
||||
break;
|
||||
}
|
||||
Err = gpio_set_direction(CONFIG_CSN_GPIO,GPIO_MODE_OUTPUT);
|
||||
if(Err != ESP_OK) {
|
||||
Line = __LINE__;
|
||||
break;
|
||||
}
|
||||
Err = gpio_set_level(CONFIG_CSN_GPIO,1);
|
||||
if(Err != ESP_OK) {
|
||||
Line = __LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
// The CC1101 is not selected and MISO has a pullup so it should be high
|
||||
if(wait_Miso(1) != 1) {
|
||||
LOGA("Error: SubGhz MISO stuck low\n");
|
||||
break;
|
||||
}
|
||||
// Select the CC1101
|
||||
Err = gpio_set_level(CONFIG_CSN_GPIO,0);
|
||||
if(Err != ESP_OK) {
|
||||
Line = __LINE__;
|
||||
break;
|
||||
}
|
||||
MisoLevel = wait_Miso(0);
|
||||
|
||||
// Deselect the CC1101
|
||||
Err = gpio_set_level(CONFIG_CSN_GPIO,1);
|
||||
if(Err != ESP_OK) {
|
||||
Line = __LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
if(MisoLevel == 0) {
|
||||
Ret = true;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
|
||||
if(Line != 0) {
|
||||
LOGA("%s#%d: gpio call failed (0x%x)\n",__FUNCTION__,__LINE__,Err);
|
||||
}
|
||||
|
||||
if(Ret) {
|
||||
// Disable pullup, it's no longer needed
|
||||
gpio_set_pull_mode(CONFIG_MISO_GPIO,GPIO_FLOATING);
|
||||
}
|
||||
else {
|
||||
// CC1101 not present, deinit MISO and CSn GPIOs
|
||||
LOGE("CC1101 not detected\n");
|
||||
gpio_reset_pin(CONFIG_MISO_GPIO);
|
||||
gpio_reset_pin(CONFIG_CSN_GPIO);
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
#endif // CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
|
||||
|
||||
@@ -34,34 +34,12 @@
|
||||
#include <stdbool.h>
|
||||
#include <driver/spi_master.h>
|
||||
#include "proto.h"
|
||||
#include "utils.h"
|
||||
#include "second_uart.h"
|
||||
#include "cc1101_radio.h"
|
||||
#include "logging.h"
|
||||
#include "radio.h"
|
||||
|
||||
#define ENABLE_LOGGING 0
|
||||
|
||||
// LOGA - generic logging, always enabled
|
||||
#define LOGA(format, ... ) printf(format,## __VA_ARGS__)
|
||||
// LOGE - error logging, always enabled
|
||||
#define LOGE(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
#define LOG(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
|
||||
#define LOG_RAW(format, ... ) printf(format,## __VA_ARGS__)
|
||||
#else
|
||||
#define LOG(format, ... )
|
||||
#define LOG_RAW(format, ... )
|
||||
#endif
|
||||
|
||||
#define ENABLE_VERBOSE_LOGGING 0
|
||||
|
||||
#if ENABLE_VERBOSE_LOGGING
|
||||
#define LOGV(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
|
||||
#define LOGV_RAW(format, ... ) printf(format,## __VA_ARGS__)
|
||||
#else
|
||||
#define LOGV(format, ... )
|
||||
#define LOGB_RAW(format, ... )
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
@@ -197,7 +175,7 @@ uint8_t CC1101_readReg(uint8_t regAddr, uint8_t regType);
|
||||
void CC1101_writeReg(uint8_t regAddr, uint8_t value);
|
||||
void CC1101_setTxState(void);
|
||||
|
||||
void setIdleState(void);
|
||||
static void setIdleState(void);
|
||||
|
||||
spi_device_handle_t gSpiHndl;
|
||||
|
||||
@@ -288,7 +266,7 @@ static uint8_t gRfState;
|
||||
|
||||
#define cc1101_Select() gpio_set_level(CONFIG_CSN_GPIO, LOW)
|
||||
#define cc1101_Deselect() gpio_set_level(CONFIG_CSN_GPIO, HIGH)
|
||||
#define wait_Miso() while(gpio_get_level(CONFIG_MISO_GPIO)>0)
|
||||
#define wait_Miso() CC1101_WaitMISO(__FUNCTION__,__LINE__,0)
|
||||
#define getGDO0state() gpio_get_level(CONFIG_GDO0_GPIO)
|
||||
#define wait_GDO0_high() while(!getGDO0state())
|
||||
#define wait_GDO0_low() while(getGDO0state())
|
||||
@@ -633,14 +611,13 @@ int CC1101_Rx(uint8_t *RxBuf,size_t RxBufLen,uint8_t *pRssi,uint8_t *pLqi)
|
||||
// Any data waiting to be read and no overflow?
|
||||
do {
|
||||
if(rxBytes & CC1101_RXFIFO_OVERFLOW_MASK) {
|
||||
LOGE("RxFifo overflow\n");
|
||||
// This occurs occasionally due to random noise, so do don't log
|
||||
Ret = -2;
|
||||
break;
|
||||
}
|
||||
|
||||
if(rxBytes < 2) {
|
||||
// should have at least 2 bytes, packet len and one byte of data
|
||||
LOGE("Internal error, rxBytes = %d\n",rxBytes);
|
||||
Ret = -2;
|
||||
break;
|
||||
}
|
||||
@@ -786,5 +763,24 @@ void CC1101_logState()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Wait for up to 2 milliseconds for MISO to go low
|
||||
#define MISO_WAIT_TIMEOUT 2
|
||||
int CC1101_WaitMISO(const char *Func,int Line,int level)
|
||||
{
|
||||
uint32_t Start = getMillis();
|
||||
int MisoLevel;
|
||||
|
||||
while((MisoLevel = gpio_get_level(CONFIG_MISO_GPIO)) != level) {
|
||||
if((getMillis() - Start) >= MISO_WAIT_TIMEOUT) {
|
||||
LOGA("%s#%d: timeout waiting for MISO to go %s\n",
|
||||
Func,Line,level ? "high" : "low");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return MisoLevel;
|
||||
}
|
||||
|
||||
#endif // CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
|
||||
|
||||
@@ -29,6 +29,14 @@
|
||||
#ifndef __CC1101_RADIO_H_
|
||||
#define __CC1101_RADIO_H_
|
||||
|
||||
// Log to all
|
||||
#define LOGA(format, ... ) \
|
||||
uart_printf(format "\r",## __VA_ARGS__)
|
||||
|
||||
// Error log to all
|
||||
#define LOGE(format, ... ) \
|
||||
uart_printf("%s#%d: " format "\r",__FUNCTION__,__LINE__,## __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* CC1101 configuration registers
|
||||
*/
|
||||
@@ -113,6 +121,7 @@ void CC1101_DumpRegs(void);
|
||||
void CC1101_reset(void);
|
||||
void CC1101_logState(void);
|
||||
void CC1101_setRxState(void);
|
||||
int CC1101_WaitMISO(const char *Func,int Line,int level);
|
||||
|
||||
#endif // __CC1101_RADIO_H_
|
||||
|
||||
|
||||
23
ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/main/logging.h
Executable file
23
ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/main/logging.h
Executable file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#if CONFIG_OEPL_DEBUG_PRINT
|
||||
#define LOG(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
|
||||
#define LOG_RAW(format, ... ) printf(format,## __VA_ARGS__)
|
||||
void DumpHex(void *AdrIn,int Len);
|
||||
#define LOG_HEX(x,y) DumpHex(x,y)
|
||||
#else
|
||||
#define LOG(format, ... )
|
||||
#define LOG_RAW(format, ... )
|
||||
#define LOG_HEX(x,y)
|
||||
#endif
|
||||
|
||||
#if CONFIG_OEPL_VERBOSE_DEBUG
|
||||
#define LOGV(format, ... ) LOG(format,## __VA_ARGS__)
|
||||
#define LOGV_RAW(format, ... ) LOG_RAW(format,## __VA_ARGS__)
|
||||
#define LOGV_HEX(x,y) LOG_HEX(x,y)
|
||||
#else
|
||||
#define LOGV(format, ... )
|
||||
#define LOGV_RAW(format, ... )
|
||||
#define LOGV_HEX(x,y)
|
||||
#endif
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
#include "radio.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "second_uart.h"
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C6
|
||||
#include "soc/lp_uart_reg.h"
|
||||
#endif
|
||||
#include "soc/uart_struct.h"
|
||||
#include "utils.h"
|
||||
#include <esp_mac.h>
|
||||
@@ -26,6 +28,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "logging.h"
|
||||
#include "SubGigRadio.h"
|
||||
|
||||
|
||||
@@ -33,6 +36,30 @@ static const char *TAG = "MAIN";
|
||||
|
||||
const uint8_t channelList[6] = {11, 15, 20, 25, 26, 27};
|
||||
|
||||
#if CONFIG_OEPL_VERBOSE_DEBUG
|
||||
const struct {
|
||||
uint8_t Type;
|
||||
const char *Name;
|
||||
} gPktTypeLookupTbl[] = {
|
||||
{PKT_TAG_RETURN_DATA, "TAG_RETURN_DATA"},
|
||||
{PKT_TAG_RETURN_DATA_ACK, "TAG_RETURN_DATA_ACK"},
|
||||
{PKT_AVAIL_DATA_SHORTREQ, "AVAIL_DATA_SHORTREQ"},
|
||||
{PKT_AVAIL_DATA_REQ, "AVAIL_DATA_REQ"},
|
||||
{PKT_AVAIL_DATA_INFO, "AVAIL_DATA_INFO"},
|
||||
{PKT_BLOCK_PARTIAL_REQUEST, "BLOCK_PARTIAL_REQUEST"},
|
||||
{PKT_BLOCK_REQUEST_ACK, "BLOCK_REQUEST_ACK"},
|
||||
{PKT_BLOCK_REQUEST, "BLOCK_REQUEST"},
|
||||
{PKT_BLOCK_PART, "BLOCK_PART"},
|
||||
{PKT_XFER_COMPLETE, "XFER_COMPLETE"},
|
||||
{PKT_XFER_COMPLETE_ACK, "XFER_COMPLETE_ACK"},
|
||||
{PKT_CANCEL_XFER, "CANCEL_XFER"},
|
||||
{PKT_PING, "PING"},
|
||||
{PKT_PONG, "PONG"},
|
||||
{0,NULL} // End of table
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
#define DATATYPE_NOUPDATE 0
|
||||
#define HW_TYPE 0xC6
|
||||
|
||||
@@ -42,7 +69,7 @@ const uint8_t channelList[6] = {11, 15, 20, 25, 26, 27};
|
||||
struct pendingData pendingDataArr[MAX_PENDING_MACS];
|
||||
|
||||
// VERSION GOES HERE!
|
||||
uint16_t version = 0x001d;
|
||||
uint16_t version = 0x001f;
|
||||
|
||||
#define RAW_PKT_PADDING 2
|
||||
|
||||
@@ -122,7 +149,7 @@ uint8_t getBlockDataLength() {
|
||||
}
|
||||
|
||||
// pendingdata slot stuff
|
||||
int8_t findSlotForMac(const uint8_t *mac) {
|
||||
int32_t findSlotForMac(const uint8_t *mac) {
|
||||
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
|
||||
if (memcmp(mac, ((uint8_t *) &(pendingDataArr[c].targetMac)), 8) == 0) {
|
||||
if (pendingDataArr[c].attemptsLeft != 0) {
|
||||
@@ -132,7 +159,7 @@ int8_t findSlotForMac(const uint8_t *mac) {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int8_t findFreeSlot() {
|
||||
int32_t findFreeSlot() {
|
||||
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
|
||||
if (pendingDataArr[c].attemptsLeft == 0) {
|
||||
return c;
|
||||
@@ -140,7 +167,7 @@ int8_t findFreeSlot() {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int8_t findSlotForVer(const uint8_t *ver) {
|
||||
int32_t findSlotForVer(const uint8_t *ver) {
|
||||
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
|
||||
if (memcmp(ver, ((uint8_t *) &(pendingDataArr[c].availdatainfo.dataVer)), 8) == 0) {
|
||||
if (pendingDataArr[c].attemptsLeft != 0) return c;
|
||||
@@ -149,14 +176,14 @@ int8_t findSlotForVer(const uint8_t *ver) {
|
||||
return -1;
|
||||
}
|
||||
void deleteAllPendingDataForVer(const uint8_t *ver) {
|
||||
int8_t slot = -1;
|
||||
int32_t slot = -1;
|
||||
do {
|
||||
slot = findSlotForVer(ver);
|
||||
if (slot != -1) pendingDataArr[slot].attemptsLeft = 0;
|
||||
} while (slot != -1);
|
||||
}
|
||||
void deleteAllPendingDataForMac(const uint8_t *mac) {
|
||||
int8_t slot = -1;
|
||||
int32_t slot = -1;
|
||||
do {
|
||||
slot = findSlotForMac(mac);
|
||||
if (slot != -1) pendingDataArr[slot].attemptsLeft = 0;
|
||||
@@ -287,7 +314,7 @@ void processSerial(uint8_t lastchar) {
|
||||
if (bytesRemain == 0) {
|
||||
if (checkCRC(serialbuffer, sizeof(struct pendingData))) {
|
||||
struct pendingData *pd = (struct pendingData *) serialbuffer;
|
||||
int8_t slot = findSlotForMac(pd->targetMac);
|
||||
int32_t slot = findSlotForMac(pd->targetMac);
|
||||
if (slot == -1) slot = findFreeSlot();
|
||||
if (slot != -1) {
|
||||
memcpy(&(pendingDataArr[slot]), serialbuffer, sizeof(struct pendingData));
|
||||
@@ -472,7 +499,7 @@ void processBlockRequest(const uint8_t *buffer, uint8_t forceBlockDownload) {
|
||||
lastBlockRequest = getMillis();
|
||||
} else {
|
||||
// we're talking to another mac, let this mac know we can't accomodate another request right now
|
||||
pr("BUSY!\n");
|
||||
pr("BUSY!\n\r");
|
||||
sendCancelXfer(rxHeader->src);
|
||||
return;
|
||||
}
|
||||
@@ -494,9 +521,9 @@ void processBlockRequest(const uint8_t *buffer, uint8_t forceBlockDownload) {
|
||||
if (forceBlockDownload) {
|
||||
if ((getMillis() - nextBlockAttempt) > 380) {
|
||||
requestDataDownload = true;
|
||||
pr("FORCED\n");
|
||||
pr("FORCED\n\r");
|
||||
} else {
|
||||
pr("IGNORED\n");
|
||||
pr("IGNORED\n\r");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -597,7 +624,7 @@ void processXferComplete(uint8_t *buffer) {
|
||||
if (memcmp(lastAckMac, rxHeader->src, 8) != 0) {
|
||||
memcpy((void *) lastAckMac, (void *) rxHeader->src, 8);
|
||||
espNotifyXferComplete(rxHeader->src);
|
||||
int8_t slot = findSlotForMac(rxHeader->src);
|
||||
int32_t slot = findSlotForMac(rxHeader->src);
|
||||
if (slot != -1) pendingDataArr[slot].attemptsLeft = 0;
|
||||
}
|
||||
}
|
||||
@@ -645,7 +672,7 @@ void sendPart(uint8_t partNo) {
|
||||
}
|
||||
void sendBlockData() {
|
||||
if (getBlockDataLength() == 0) {
|
||||
pr("Invalid block request received, 0 parts..\n");
|
||||
pr("Invalid block request received, 0 parts..\n\r");
|
||||
requestedData.requestedParts[0] |= 0x01;
|
||||
}
|
||||
|
||||
@@ -658,7 +685,7 @@ void sendBlockData() {
|
||||
pr(".");
|
||||
}
|
||||
}
|
||||
pr("\n");
|
||||
pr("\n\r");
|
||||
|
||||
uint8_t partNo = 0;
|
||||
while (partNo < BLOCK_MAX_PARTS) {
|
||||
@@ -752,16 +779,36 @@ void app_main(void) {
|
||||
|
||||
pr("RES>");
|
||||
pr("RDY>");
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C6
|
||||
ESP_LOGI(TAG, "C6 ready!");
|
||||
#else
|
||||
ESP_LOGI(TAG, "H2 ready!");
|
||||
#endif
|
||||
|
||||
housekeepingTimer = getMillis();
|
||||
while (1) {
|
||||
while ((getMillis() - housekeepingTimer) < ((1000 * HOUSEKEEPING_INTERVAL) - 100)) {
|
||||
int8_t ret = commsRxUnencrypted(radiorxbuffer);
|
||||
int32_t ret = commsRxUnencrypted(radiorxbuffer);
|
||||
if (ret > 1) {
|
||||
led_flash(0);
|
||||
|
||||
uint8_t PktType = getPacketType(radiorxbuffer);
|
||||
#if CONFIG_OEPL_VERBOSE_DEBUG
|
||||
LOGV_RAW("Received %d byte ",ret);
|
||||
for(uint8_t i = 0; gPktTypeLookupTbl[i].Name != NULL; i++) {
|
||||
if(gPktTypeLookupTbl[i].Type == PktType) {
|
||||
LOGV_RAW("%s",gPktTypeLookupTbl[i].Name);
|
||||
break;
|
||||
}
|
||||
if(gPktTypeLookupTbl[i].Name == NULL) {
|
||||
LOGV_RAW("undefined (0x%02x)",PktType);
|
||||
}
|
||||
}
|
||||
LOGV_RAW(" packet:\n");
|
||||
LOGV_HEX(radiorxbuffer,ret);
|
||||
#endif
|
||||
// received a packet, lets see what it is
|
||||
switch (getPacketType(radiorxbuffer)) {
|
||||
switch (PktType) {
|
||||
case PKT_AVAIL_DATA_REQ:
|
||||
if (ret == 28) {
|
||||
// old version of the AvailDataReq struct, set all the new fields to zero, so it will pass the CRC
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#define LED1 22
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C6
|
||||
#define LED2 23
|
||||
#elif CONFIG_IDF_TARGET_ESP32H2
|
||||
#define LED2 25
|
||||
#endif
|
||||
|
||||
#define PROTO_PAN_ID (0x4447) // PAN ID compression shall be used
|
||||
#define PROTO_PAN_ID_SUBGHZ (0x1337) // PAN ID compression shall be used
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
#include "sdkconfig.h"
|
||||
// if you get an error about soc/lp_uart_reg.h not being found,
|
||||
// you didn't choose the right build target. :-)
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C6
|
||||
#include "soc/lp_uart_reg.h"
|
||||
#endif
|
||||
#include "soc/uart_struct.h"
|
||||
#include "utils.h"
|
||||
#include <esp_mac.h>
|
||||
@@ -39,7 +41,9 @@ void esp_ieee802154_receive_done(uint8_t *frame, esp_ieee802154_frame_info_t *fr
|
||||
memcpy(inner_rxPKT, &frame[0], frame[0] + 1);
|
||||
xQueueSendFromISR(packet_buffer, (void *)&inner_rxPKT, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR_ARG(xHigherPriorityTaskWoken);
|
||||
esp_ieee802154_receive_sfd_done();
|
||||
if(esp_ieee802154_receive_handle_done(frame)) {
|
||||
ESP_EARLY_LOGI(TAG, "esp_ieee802154_receive_handle_done() failed");
|
||||
}
|
||||
}
|
||||
|
||||
void esp_ieee802154_transmit_failed(const uint8_t *frame, esp_ieee802154_tx_error_t error) {
|
||||
@@ -50,7 +54,11 @@ void esp_ieee802154_transmit_failed(const uint8_t *frame, esp_ieee802154_tx_erro
|
||||
void esp_ieee802154_transmit_done(const uint8_t *frame, const uint8_t *ack, esp_ieee802154_frame_info_t *ack_frame_info) {
|
||||
isInTransmit = 0;
|
||||
ESP_EARLY_LOGI(TAG, "TX %d", frame[0]);
|
||||
esp_ieee802154_receive_sfd_done();
|
||||
if(ack != NULL) {
|
||||
if(esp_ieee802154_receive_handle_done(ack)) {
|
||||
ESP_EARLY_LOGI(TAG, "esp_ieee802154_receive_handle_done() failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
static bool zigbee_is_enabled = false;
|
||||
void radio_init(uint8_t ch) {
|
||||
|
||||
@@ -19,10 +19,12 @@
|
||||
#include "proto.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/uart_struct.h"
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C6
|
||||
#include "soc/lp_uart_reg.h"
|
||||
#endif
|
||||
static const char *TAG = "SECOND_UART";
|
||||
#include "second_uart.h"
|
||||
|
||||
static const char *TAG = "SECOND_UART";
|
||||
|
||||
#define BUF_SIZE (1024)
|
||||
#define RD_BUF_SIZE (BUF_SIZE)
|
||||
@@ -43,6 +45,8 @@ void init_second_uart() {
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
ESP_LOGI(TAG, "HARDWARE_UART_TX %d, CONFIG_OEPL_HARDWARE_UART_RX %d",
|
||||
CONFIG_OEPL_HARDWARE_UART_TX,CONFIG_OEPL_HARDWARE_UART_RX);
|
||||
ESP_ERROR_CHECK(uart_driver_install(1, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0));
|
||||
ESP_ERROR_CHECK(uart_param_config(1, &uart_config));
|
||||
ESP_ERROR_CHECK(uart_set_pin(1, CONFIG_OEPL_HARDWARE_UART_TX, CONFIG_OEPL_HARDWARE_UART_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
|
||||
@@ -13,11 +13,17 @@ void uart_printf(const char *format, ...);
|
||||
#define pr uart_printf
|
||||
|
||||
#if defined(CONFIG_OEPL_HARDWARE_PROFILE_DEFAULT)
|
||||
#define CONFIG_OEPL_HARDWARE_UART_TX 3
|
||||
#define CONFIG_OEPL_HARDWARE_UART_RX 2
|
||||
#define CONFIG_OEPL_HARDWARE_UART_TX 3
|
||||
#define CONFIG_OEPL_HARDWARE_UART_RX 2
|
||||
#elif defined(CONFIG_OEPL_HARDWARE_PROFILE_LILYGO)
|
||||
#define CONFIG_OEPL_HARDWARE_UART_TX 24
|
||||
#define CONFIG_OEPL_HARDWARE_UART_RX 23
|
||||
#elif defined(CONFIG_OEPL_HARDWARE_PROFILE_POE_AP)
|
||||
#define CONFIG_OEPL_HARDWARE_UART_TX 5
|
||||
#define CONFIG_OEPL_HARDWARE_UART_RX 18
|
||||
#elif defined(CONFIG_OEPL_HARDWARE_PROFILE_ELECROW_C6)
|
||||
#define CONFIG_OEPL_HARDWARE_UART_TX 0
|
||||
#define CONFIG_OEPL_HARDWARE_UART_RX 1
|
||||
#elif defined(CONFIG_OEPL_HARDWARE_PROFILE_CUSTOM)
|
||||
#if !defined(CONFIG_OEPL_HARDWARE_UART_TX) || !defined(CONFIG_OEPL_HARDWARE_UART_RX)
|
||||
#error "No UART TX / RX pins defined. Please check menuconfig"
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
#include "proto.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/uart_struct.h"
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C6
|
||||
#include "soc/lp_uart_reg.h"
|
||||
#endif
|
||||
#include "nvs_flash.h"
|
||||
|
||||
void delay(int ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }
|
||||
|
||||
@@ -7,4 +7,4 @@ CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_OEPL_SUBGIG_SUPPORT=y
|
||||
|
||||
CONFIG_IEEE802154_RX_BUFFER_SIZE=100
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(OpenEPaperLink_esp32_C6)
|
||||
project(OpenEPaperLink_esp32_H2)
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
idf_component_register( SRCS
|
||||
SRCS "utils.c"
|
||||
SRCS "second_uart.c"
|
||||
SRCS "radio.c"
|
||||
SRCS "SubGigRadio.c"
|
||||
SRCS "cc1101_radio.c"
|
||||
SRCS "led.c"
|
||||
SRCS "main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
SRC_DIRS "../../OpenEPaperLink_esp32_C6_AP/main"
|
||||
INCLUDE_DIRS "../../OpenEPaperLink_esp32_C6_AP/main")
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
menu "OEPL Hardware config"
|
||||
menu "OEPL config"
|
||||
|
||||
choice OEPL_HARDWARE_PROFILE
|
||||
prompt "Hardware profile"
|
||||
default OEPL_HARDWARE_PROFILE_DEFAULT
|
||||
|
||||
config OEPL_HARDWARE_PROFILE_DEFAULT
|
||||
bool "Default"
|
||||
|
||||
config OEPL_HARDWARE_PROFILE_POE_AP
|
||||
bool "PoE-AP"
|
||||
default OEPL_HARDWARE_PROFILE_LILYGO
|
||||
|
||||
config OEPL_HARDWARE_PROFILE_CUSTOM
|
||||
bool "Custom"
|
||||
|
||||
config OEPL_HARDWARE_PROFILE_LILYGO
|
||||
bool "LILYGO-AP"
|
||||
|
||||
endchoice
|
||||
|
||||
config OEPL_HARDWARE_UART_TX
|
||||
@@ -99,6 +96,16 @@ menu "OEPL Hardware config"
|
||||
USE SPI3_HOST. This is also called VSPI_HOST
|
||||
endchoice
|
||||
endmenu
|
||||
|
||||
config OEPL_DEBUG_PRINT
|
||||
bool "Enable OEPL Debug logging"
|
||||
default "n"
|
||||
|
||||
config OEPL_VERBOSE_DEBUG
|
||||
depends on OEPL_DEBUG_PRINT
|
||||
bool "Enable OEPL Verbose Debug logging"
|
||||
default "n"
|
||||
|
||||
endmenu
|
||||
|
||||
|
||||
|
||||
@@ -1,562 +0,0 @@
|
||||
#include "sdkconfig.h"
|
||||
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <driver/spi_master.h>
|
||||
#include <driver/gpio.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "radio.h"
|
||||
#include "proto.h"
|
||||
#include "cc1101_radio.h"
|
||||
#include "SubGigRadio.h"
|
||||
|
||||
void DumpHex(void *AdrIn,int Len);
|
||||
|
||||
#define LOGE(format, ... ) \
|
||||
printf("%s#%d: " format,__FUNCTION__,__LINE__,## __VA_ARGS__)
|
||||
|
||||
#if 0
|
||||
#define LOG(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
|
||||
#define LOG_RAW(format, ... ) printf(format,## __VA_ARGS__)
|
||||
#define LOG_HEX(x,y) DumpHex(x,y)
|
||||
#else
|
||||
#define LOG(format, ... )
|
||||
#define LOG_RAW(format, ... )
|
||||
#define LOG_HEX(x,y)
|
||||
#endif
|
||||
|
||||
|
||||
// SPI Stuff
|
||||
#if CONFIG_SPI2_HOST
|
||||
#define HOST_ID SPI2_HOST
|
||||
#elif CONFIG_SPI3_HOST
|
||||
#define HOST_ID SPI3_HOST
|
||||
#endif
|
||||
|
||||
// Address Config = No address check
|
||||
// Base Frequency = xxx.xxx
|
||||
// CRC Enable = false
|
||||
// Carrier Frequency = 915.000000
|
||||
// Channel Number = 0
|
||||
// Channel Spacing = 199.951172
|
||||
// Data Rate = 1.19948
|
||||
// Deviation = 5.157471
|
||||
// Device Address = 0
|
||||
// Manchester Enable = false
|
||||
// Modulated = false
|
||||
// Modulation Format = ASK/OOK
|
||||
// PA Ramping = false
|
||||
// Packet Length = 255
|
||||
// Packet Length Mode = Reserved
|
||||
// Preamble Count = 4
|
||||
// RX Filter BW = 58.035714
|
||||
// Sync Word Qualifier Mode = No preamble/sync
|
||||
// TX Power = 10
|
||||
// Whitening = false
|
||||
// Rf settings for CC1110
|
||||
const RfSetting gCW[] = {
|
||||
{CC1101_PKTCTRL0,0x22}, // PKTCTRL0: Packet Automation Control
|
||||
{CC1101_FSCTRL1,0x06}, // FSCTRL1: Frequency Synthesizer Control
|
||||
{CC1101_MDMCFG4,0xF5}, // MDMCFG4: Modem configuration
|
||||
{CC1101_MDMCFG3,0x83}, // MDMCFG3: Modem Configuration
|
||||
{CC1101_MDMCFG2,0xb0}, // MDMCFG2: Modem Configuration
|
||||
{CC1101_DEVIATN,0x15}, // DEVIATN: Modem Deviation Setting
|
||||
{CC1101_MCSM0,0x18}, // MCSM0: Main Radio Control State Machine Configuration
|
||||
{CC1101_FOCCFG,0x17}, // FOCCFG: Frequency Offset Compensation Configuration
|
||||
{CC1101_FSCAL3,0xE9}, // FSCAL3: Frequency Synthesizer Calibration
|
||||
{CC1101_FSCAL2,0x2A}, // FSCAL2: Frequency Synthesizer Calibration
|
||||
{CC1101_FSCAL1,0x00}, // FSCAL1: Frequency Synthesizer Calibration
|
||||
{CC1101_FSCAL0,0x1F}, // FSCAL0: Frequency Synthesizer Calibration
|
||||
{CC1101_TEST1,0x31}, // TEST1: Various Test Settings
|
||||
{CC1101_TEST0,0x09}, // TEST0: Various Test Settings
|
||||
{0xff,0} // end of table
|
||||
};
|
||||
|
||||
|
||||
// Set Base Frequency to 865.999634
|
||||
const RfSetting g866Mhz[] = {
|
||||
{CC1101_FREQ2,0x21}, // FREQ2: Frequency Control Word, High Byte
|
||||
{CC1101_FREQ1,0x4e}, // FREQ1: Frequency Control Word, Middle Byte
|
||||
{CC1101_FREQ0,0xc4}, // FREQ0: Frequency Control Word, Low Byte
|
||||
{0xff,0} // end of table
|
||||
};
|
||||
|
||||
// Set Base Frequency to 863.999756
|
||||
const RfSetting g864Mhz[] = {
|
||||
{CC1101_FREQ2,0x21}, // FREQ2: Frequency Control Word, High Byte
|
||||
{CC1101_FREQ1,0x3b}, // FREQ1: Frequency Control Word, Middle Byte
|
||||
{CC1101_FREQ0,0x13}, // FREQ0: Frequency Control Word, Low Byte
|
||||
{0xff,0} // end of table
|
||||
};
|
||||
|
||||
// Set Base Frequency to 902.999756
|
||||
const RfSetting g903Mhz[] = {
|
||||
{CC1101_FREQ2,0x22}, // FREQ2: Frequency Control Word, High Byte
|
||||
{CC1101_FREQ1,0xbb}, // FREQ1: Frequency Control Word, Middle Byte
|
||||
{CC1101_FREQ0,0x13}, // FREQ0: Frequency Control Word, Low Byte
|
||||
{0xff,0} // end of table
|
||||
};
|
||||
|
||||
|
||||
// Seet Base Frequency to 915.000000
|
||||
const RfSetting g915Mhz[] = {
|
||||
{CC1101_FREQ2,0x23}, // FREQ2: Frequency Control Word, High Byte
|
||||
{CC1101_FREQ1,0x31}, // FREQ1: Frequency Control Word, Middle Byte
|
||||
{CC1101_FREQ0,0x3B}, // FREQ0: Frequency Control Word, Low Byte
|
||||
{0xff,0} // end of table
|
||||
};
|
||||
|
||||
// Address Config = No address check
|
||||
// Base Frequency = 901.934937 (adjusted to compensate for the crappy crystal on the CC1101 board)
|
||||
// CRC Enable = true
|
||||
// Carrier Frequency = 901.934937
|
||||
// Channel Number = 0
|
||||
// Channel Spacing = 199.951172
|
||||
// Data Rate = 38.3835
|
||||
// Deviation = 20.629883
|
||||
// Device Address = ff
|
||||
// Manchester Enable = false
|
||||
// Modulated = true
|
||||
// Modulation Format = GFSK
|
||||
// PA Ramping = false
|
||||
// Packet Length = 61
|
||||
// Packet Length Mode = Variable packet length mode. Packet length configured by the first byte after sync word
|
||||
// Preamble Count = 4
|
||||
// RX Filter BW = 101.562500
|
||||
// Sync Word Qualifier Mode = 30/32 sync word bits detected
|
||||
// TX Power = 10
|
||||
// Whitening = false
|
||||
// The following was generated by setting the spec for Register to "{CC1101_@RN@,0x@VH@},"
|
||||
const RfSetting gIDF_Basic[] = {
|
||||
{CC1101_SYNC1,0xC7},
|
||||
{CC1101_SYNC0,0x0A},
|
||||
{CC1101_PKTLEN,0x3D},
|
||||
{CC1101_PKTCTRL0,0x05},
|
||||
{CC1101_ADDR,0xFF},
|
||||
{CC1101_FSCTRL1,0x08},
|
||||
{CC1101_FREQ2,0x22},
|
||||
{CC1101_FREQ1,0xB1},
|
||||
{CC1101_FREQ0,0x3B},
|
||||
{CC1101_MDMCFG4,0xCA},
|
||||
{CC1101_MDMCFG3,0x83},
|
||||
{CC1101_MDMCFG2,0x93},
|
||||
{CC1101_DEVIATN,0x35},
|
||||
// {CC1101_MCSM0,0x18}, FS_AUTOCAL = 1, PO_TIMEOUT = 2
|
||||
{CC1101_MCSM0,0x18},
|
||||
{CC1101_FOCCFG,0x16},
|
||||
{CC1101_AGCCTRL2,0x43},
|
||||
{CC1101_FSCAL3,0xEF},
|
||||
{CC1101_FSCAL2,0x2D},
|
||||
{CC1101_FSCAL1,0x25},
|
||||
{CC1101_FSCAL0,0x1F},
|
||||
{CC1101_TEST2,0x81},
|
||||
{CC1101_TEST1,0x35},
|
||||
{CC1101_TEST0,0x09},
|
||||
{0xff,0} // end of table
|
||||
};
|
||||
|
||||
// RF configuration from Dimitry's orginal code
|
||||
// Address Config = No address check
|
||||
// Base Frequency = 902.999756
|
||||
// CRC Autoflush = false
|
||||
// CRC Enable = true
|
||||
// Carrier Frequency = 902.999756
|
||||
// Channel Number = 0
|
||||
// Channel Spacing = 335.632324
|
||||
// Data Format = Normal mode
|
||||
// Data Rate = 249.939
|
||||
// Deviation = 165.039063
|
||||
// Device Address = 22
|
||||
// Manchester Enable = false
|
||||
// Modulated = true
|
||||
// Modulation Format = GFSK
|
||||
// PA Ramping = false
|
||||
// Packet Length = 255
|
||||
// Packet Length Mode = Variable packet length mode. Packet length configured by the first byte after sync word
|
||||
// Preamble Count = 24
|
||||
// RX Filter BW = 650.000000
|
||||
// Sync Word Qualifier Mode = 30/32 sync word bits detected
|
||||
// TX Power = 0
|
||||
// Whitening = true
|
||||
// Rf settings for CC1101
|
||||
// The following was generated by setting the spec for Register to "{CC1101_@RN@,0x@VH@},"
|
||||
const RfSetting gDmitry915[] = {
|
||||
{CC1101_FREQ2,0x22},
|
||||
{CC1101_FREQ1,0xBB},
|
||||
{CC1101_FREQ0,0x13},
|
||||
{CC1101_MDMCFG4,0x1D},
|
||||
{CC1101_MDMCFG3,0x3B},
|
||||
{CC1101_MDMCFG2,0x13},
|
||||
{CC1101_MDMCFG1,0x73},
|
||||
{CC1101_MDMCFG0,0xA7},
|
||||
{CC1101_DEVIATN,0x65},
|
||||
{CC1101_MCSM0,0x18},
|
||||
{CC1101_FOCCFG,0x1E},
|
||||
{CC1101_BSCFG,0x1C},
|
||||
{CC1101_AGCCTRL2,0xC7},
|
||||
{CC1101_AGCCTRL1,0x00},
|
||||
{CC1101_AGCCTRL0,0xB0},
|
||||
{CC1101_FREND1,0xB6},
|
||||
{CC1101_FSCAL3,0xEA},
|
||||
{CC1101_FSCAL2,0x2A},
|
||||
{CC1101_FSCAL1,0x00},
|
||||
{CC1101_FSCAL0,0x1F},
|
||||
{CC1101_TEST0,0x09},
|
||||
{0xff,0} // end of table
|
||||
};
|
||||
|
||||
SubGigData gSubGigData;
|
||||
|
||||
int CheckSubGigState(void);
|
||||
void SubGig_CC1101_reset(void);
|
||||
void SubGig_CC1101_SetConfig(const RfSetting *pConfig);
|
||||
|
||||
static void IRAM_ATTR gpio_isr_handler(void *arg)
|
||||
{
|
||||
gSubGigData.RxAvailable = true;
|
||||
}
|
||||
|
||||
// return SUBGIG_ERR_NONE aka ESP_OK aka 0 if CC1101 is detected and all is good
|
||||
SubGigErr SubGig_radio_init(uint8_t ch)
|
||||
{
|
||||
esp_err_t Err;
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.clock_speed_hz = 5000000, // SPI clock is 5 MHz!
|
||||
.queue_size = 7,
|
||||
.mode = 0, // SPI mode 0
|
||||
.spics_io_num = -1, // we will use manual CS control
|
||||
.flags = SPI_DEVICE_NO_DUMMY
|
||||
};
|
||||
gpio_config_t io_conf = {
|
||||
.intr_type = GPIO_INTR_NEGEDGE, // GPIO interrupt type : falling edge
|
||||
//bit mask of the pins
|
||||
.pin_bit_mask = 1ULL<<CONFIG_GDO0_GPIO,
|
||||
//set as input mode
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
//enable pull-up mode
|
||||
.pull_up_en = 1,
|
||||
.pull_down_en = 0
|
||||
};
|
||||
int ErrLine = 0;
|
||||
SubGigErr Ret = SUBGIG_ERR_NONE;
|
||||
|
||||
do {
|
||||
gpio_reset_pin(CONFIG_CSN_GPIO);
|
||||
gpio_set_direction(CONFIG_CSN_GPIO, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(CONFIG_CSN_GPIO, 1);
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.sclk_io_num = CONFIG_SCK_GPIO,
|
||||
.mosi_io_num = CONFIG_MOSI_GPIO,
|
||||
.miso_io_num = CONFIG_MISO_GPIO,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1
|
||||
};
|
||||
|
||||
if((Err = spi_bus_initialize(HOST_ID,&buscfg,SPI_DMA_CH_AUTO)) != 0) {
|
||||
ErrLine = __LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
if((Err = spi_bus_add_device(HOST_ID,&devcfg,&gSpiHndl)) != 0) {
|
||||
ErrLine = __LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
// Configure GDO0, interrupt on falling edge
|
||||
if((Err = gpio_config(&io_conf)) != 0) {
|
||||
ErrLine = __LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
// Configure GDO2, interrupts disabled
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.pin_bit_mask = 1ULL<<CONFIG_GDO2_GPIO;
|
||||
if((Err = gpio_config(&io_conf)) != 0) {
|
||||
ErrLine = __LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
// install gpio isr service
|
||||
if((Err = gpio_install_isr_service(0)) != 0) {
|
||||
ErrLine = __LINE__;
|
||||
break;
|
||||
}
|
||||
//hook isr handler for specific gpio pin
|
||||
Err = gpio_isr_handler_add(CONFIG_GDO0_GPIO,gpio_isr_handler,
|
||||
(void*) CONFIG_GDO0_GPIO);
|
||||
if(Err != 0) {
|
||||
ErrLine = __LINE__;
|
||||
break;
|
||||
}
|
||||
// Check Chip ID
|
||||
if(!CC1101_Present()) {
|
||||
Ret = SUBGIG_CC1101_NOT_FOUND;
|
||||
break;
|
||||
}
|
||||
gSubGigData.Present = true;
|
||||
SubGig_CC1101_reset();
|
||||
CC1101_SetConfig(NULL);
|
||||
SubGig_CC1101_SetConfig(gDmitry915);
|
||||
#if 0
|
||||
CC1101_DumpRegs();
|
||||
#endif
|
||||
if(ch != 0) {
|
||||
SubGig_radioSetChannel(ch);
|
||||
}
|
||||
// good to go!
|
||||
} while(false);
|
||||
|
||||
if(ErrLine != 0) {
|
||||
LOG("%s#%d: failed %d\n",__FUNCTION__,ErrLine,Err);
|
||||
if(Err == 0) {
|
||||
Ret = ESP_FAIL;
|
||||
}
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
|
||||
SubGigErr SubGig_radioSetChannel(uint8_t ch)
|
||||
{
|
||||
SubGigErr Ret = SUBGIG_ERR_NONE;
|
||||
|
||||
RfSetting SetChannr[2] = {
|
||||
{CC1101_CHANNR,0},
|
||||
{0xff,0} // end of table
|
||||
};
|
||||
|
||||
do {
|
||||
if(!gSubGigData.Enabled && gSubGigData.Present && ch != 0) {
|
||||
gSubGigData.Enabled = true;
|
||||
LOG("SubGhz enabled\n");
|
||||
}
|
||||
if((Ret = CheckSubGigState()) != SUBGIG_ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
if(ch == 0) {
|
||||
// Disable SubGhz
|
||||
LOG("SubGhz disabled\n");
|
||||
gSubGigData.Enabled = false;
|
||||
break;
|
||||
}
|
||||
LOG("Set channel %d\n",ch);
|
||||
|
||||
if(ch >= FIRST_866_CHAN && ch < FIRST_866_CHAN + NUM_866_CHANNELS) {
|
||||
// Base Frequency = 863.999756
|
||||
// total channels 6 (0 -> 5) (CHANNR 0 -> 15)
|
||||
// Channel 100 / CHANNR 0: 863.999756
|
||||
// Channel 101 / CHANNR 3: 865.006 Mhz
|
||||
// Channel 102 / CHANNR 6: 866.014 Mhz
|
||||
// Channel 103 / CHANNR 9: 867.020 Mhz
|
||||
// Channel 104 / CHANNR 12: 868.027 Mhz
|
||||
// Channel 105 / CHANNR 15: 869.034 Mhz
|
||||
SubGig_CC1101_SetConfig(g864Mhz);
|
||||
SetChannr[0].Value = (ch - FIRST_866_CHAN) * 3;
|
||||
}
|
||||
else {
|
||||
// Base Frequency = 902.999756
|
||||
// Dmitry's orginal code used 25 channels in 915 Mhz
|
||||
// We don't want to have to scan that many so for OEPL we'll just use 6
|
||||
// to match 866.
|
||||
// Channel 200 / CHANNR 0: 903.000 Mhz
|
||||
// Channel 201 / CHANNR 12: 907.027 Mhz
|
||||
// Channel 202 / CHANNR 24: 911.054 Mhz
|
||||
// Channel 203 / CHANNR 24: 915.083 Mhz
|
||||
// Channel 204 / CHANNR 48: 919.110 Mhz
|
||||
// Channel 205 / CHANNR 60: 923.138 Mhz
|
||||
SubGig_CC1101_SetConfig(g903Mhz);
|
||||
|
||||
if(ch >= FIRST_915_CHAN && ch < FIRST_915_CHAN + NUM_915_CHANNELS) {
|
||||
SetChannr[0].Value = (ch - FIRST_915_CHAN) * 12;
|
||||
}
|
||||
else {
|
||||
Ret = SUBGIG_INVALID_CHANNEL;
|
||||
SetChannr[0].Value = 0; // default to the first channel on 915
|
||||
}
|
||||
}
|
||||
SubGig_CC1101_SetConfig(SetChannr);
|
||||
CC1101_setRxState();
|
||||
} while(false);
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
SubGigErr SubGig_radioTx(uint8_t *packet)
|
||||
{
|
||||
SubGigErr Ret = SUBGIG_ERR_NONE;
|
||||
|
||||
do {
|
||||
if(gSubGigData.FreqTest) {
|
||||
break;
|
||||
}
|
||||
if((Ret = CheckSubGigState()) != SUBGIG_ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(packet[0] < 3 || packet[0] > RADIO_MAX_PACKET_LEN + RAW_PKT_PADDING) {
|
||||
Ret = SUBGIG_TX_BAD_LEN;
|
||||
break;
|
||||
}
|
||||
|
||||
// All packets seem to be padded by RAW_PKT_PADDING (2 bytes)
|
||||
// Remove the padding before sending so the length is correct when received
|
||||
packet[0] -= RAW_PKT_PADDING;
|
||||
LOG("Sending %d byte subgig frame:\n",packet[0]);
|
||||
LOG_HEX(&packet[1],packet[0]);
|
||||
if(CC1101_Tx(packet)) {
|
||||
Ret = SUBGIG_TX_FAILED;
|
||||
}
|
||||
// Clear RxAvailable, in TX GDO0 deasserts on TX FIFO underflows
|
||||
gSubGigData.RxAvailable = false;
|
||||
// restore original len just in case anyone cares
|
||||
packet[0] += RAW_PKT_PADDING;
|
||||
} while(false);
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
// returns packet size in bytes data in data
|
||||
int8_t SubGig_commsRxUnencrypted(uint8_t *data)
|
||||
{
|
||||
int RxBytes;
|
||||
int8_t Ret = 0;
|
||||
|
||||
do {
|
||||
if(CheckSubGigState() != SUBGIG_ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(gSubGigData.FreqTest) {
|
||||
break;
|
||||
}
|
||||
CC1101_logState();
|
||||
|
||||
if(!gSubGigData.RxAvailable && gpio_get_level(CONFIG_GDO0_GPIO) == 1) {
|
||||
// Did we miss an interrupt?
|
||||
if(gpio_get_level(CONFIG_GDO0_GPIO) == 1) {
|
||||
// Yup!
|
||||
LOGE("SubGhz lost interrupt\n");
|
||||
gSubGigData.RxAvailable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(gSubGigData.RxAvailable){
|
||||
gSubGigData.RxAvailable = false;
|
||||
RxBytes = CC1101_Rx(data,128,NULL,NULL);
|
||||
|
||||
if(RxBytes >= 2) {
|
||||
// NB: RxBytes includes the CRC, deduct it
|
||||
Ret = (uint8_t) RxBytes - 2;
|
||||
LOG("Received %d byte subgig frame:\n",Ret);
|
||||
LOG_HEX(data,Ret);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
int CheckSubGigState()
|
||||
{
|
||||
int Err = SUBGIG_ERR_NONE;
|
||||
|
||||
if(!gSubGigData.Present) {
|
||||
Err = SUBGIG_CC1101_NOT_FOUND;
|
||||
}
|
||||
else if(!gSubGigData.Initialized) {
|
||||
Err = SUBGIG_NOT_INITIALIZED;
|
||||
}
|
||||
else if(!gSubGigData.Enabled) {
|
||||
Err = SUBGIG_NOT_ENABLED;
|
||||
}
|
||||
|
||||
if(Err != SUBGIG_ERR_NONE) {
|
||||
LOG("CheckSubGigState: returing %d\n",Err);
|
||||
}
|
||||
|
||||
return Err;
|
||||
}
|
||||
|
||||
SubGigErr SubGig_FreqTest(bool b866Mhz,bool bStart)
|
||||
{
|
||||
SubGigErr Err = SUBGIG_ERR_NONE;
|
||||
#if 0
|
||||
uint8_t TxData = 0; // len = 0
|
||||
|
||||
do {
|
||||
if((Err = CheckSubGigState()) != SUBGIG_ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
if(bStart) {
|
||||
LOG_RAW("Starting %sMhz Freq test\n",b866Mhz ? "866" : "915");
|
||||
SubGig_CC1101_reset();
|
||||
SubGig_CC1101_SetConfig(gCW);
|
||||
SubGig_CC1101_SetConfig(b866Mhz ? g866Mhz : g915Mhz);
|
||||
CC1101_cmdStrobe(CC1101_SIDLE);
|
||||
CC1101_cmdStrobe(CC1101_SFTX); // flush Tx Fifo
|
||||
CC1101_cmdStrobe(CC1101_STX);
|
||||
gRfState = RFSTATE_TX;
|
||||
gSubGigData.FreqTest = true;
|
||||
}
|
||||
else {
|
||||
LOG_RAW("Ending Freq test\n");
|
||||
gSubGigData.FreqTest = false;
|
||||
SubGig_CC1101_reset();
|
||||
SubGig_CC1101_SetConfig(gSubGigData.pConfig);
|
||||
}
|
||||
} while(false);
|
||||
#endif
|
||||
return Err;
|
||||
}
|
||||
|
||||
void SubGig_CC1101_reset()
|
||||
{
|
||||
gSubGigData.Initialized = false;
|
||||
gSubGigData.FixedRegsSet = false;
|
||||
CC1101_reset();
|
||||
}
|
||||
|
||||
void SubGig_CC1101_SetConfig(const RfSetting *pConfig)
|
||||
{
|
||||
CC1101_SetConfig(pConfig);
|
||||
gSubGigData.Initialized = true;
|
||||
}
|
||||
|
||||
void DumpHex(void *AdrIn,int Len)
|
||||
{
|
||||
unsigned char *Adr = (unsigned char *) AdrIn;
|
||||
int i = 0;
|
||||
int j;
|
||||
|
||||
while(i < Len) {
|
||||
for(j = 0; j < 16; j++) {
|
||||
if((i + j) == Len) {
|
||||
break;
|
||||
}
|
||||
LOG_RAW("%02x ",Adr[i+j]);
|
||||
}
|
||||
|
||||
LOG_RAW(" ");
|
||||
for(j = 0; j < 16; j++) {
|
||||
if((i + j) == Len) {
|
||||
break;
|
||||
}
|
||||
if(isprint(Adr[i+j])) {
|
||||
LOG_RAW("%c",Adr[i+j]);
|
||||
}
|
||||
else {
|
||||
LOG_RAW(".");
|
||||
}
|
||||
}
|
||||
i += 16;
|
||||
LOG_RAW("\n");
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef _SUBGIG_RADIO_H_
|
||||
#define _SUBGIG_RADIO_H_
|
||||
|
||||
//sub-GHz 866 Mhz channels start at 100
|
||||
#define FIRST_866_CHAN (100)
|
||||
#define NUM_866_CHANNELS (6)
|
||||
|
||||
//sub-GHz 915 Mhz channels start at 200
|
||||
#define FIRST_915_CHAN (200)
|
||||
#define NUM_915_CHANNELS (6)
|
||||
|
||||
typedef enum {
|
||||
SUBGIG_ERR_NONE,
|
||||
SUBGIG_CC1101_NOT_FOUND,
|
||||
SUBGIG_NOT_INITIALIZED,
|
||||
SUBGIG_NOT_ENABLED,
|
||||
SUBGIG_TX_FAILED,
|
||||
SUBGIG_TX_BAD_LEN,
|
||||
SUBGIG_INVALID_CHANNEL,
|
||||
} SubGigErr;
|
||||
|
||||
typedef struct {
|
||||
uint8_t Present:1;
|
||||
uint8_t Enabled:1;
|
||||
uint8_t FreqTest:1;
|
||||
uint8_t RxAvailable:1;
|
||||
uint8_t Initialized:1;
|
||||
uint8_t FixedRegsSet:1;
|
||||
} SubGigData;
|
||||
|
||||
extern SubGigData gSubGigData;
|
||||
|
||||
SubGigErr SubGig_radio_init(uint8_t ch);
|
||||
SubGigErr SubGig_radioTx(uint8_t *packet);
|
||||
SubGigErr SubGig_radioSetChannel(uint8_t ch);
|
||||
int8_t SubGig_commsRxUnencrypted(uint8_t *data);
|
||||
SubGigErr SubGig_FreqTest(bool b866Mhz,bool bStart);
|
||||
|
||||
#endif // _SUBGIG_RADIO_H_
|
||||
|
||||
@@ -1,790 +0,0 @@
|
||||
// Large portions of this code was copied from:
|
||||
// https://github.com/nopnop2002/esp-idf-cc1101 with the following copyright
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 panStamp <contact@panstamp.com>
|
||||
* Copyright (c) 2016 Tyler Sommer <contact@tylersommer.pro>
|
||||
*
|
||||
* This file is part of the CC1101 project.
|
||||
*
|
||||
* CC1101 is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* CC1101 is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with CC1101; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*
|
||||
* Author: Daniel Berenguer
|
||||
* Creation date: 03/03/2011
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <driver/spi_master.h>
|
||||
#include "proto.h"
|
||||
#include "cc1101_radio.h"
|
||||
#include "radio.h"
|
||||
|
||||
#define ENABLE_LOGGING 0
|
||||
|
||||
// LOGA - generic logging, always enabled
|
||||
#define LOGA(format, ... ) printf(format,## __VA_ARGS__)
|
||||
// LOGE - error logging, always enabled
|
||||
#define LOGE(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
|
||||
|
||||
#if ENABLE_LOGGING
|
||||
#define LOG(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
|
||||
#define LOG_RAW(format, ... ) printf(format,## __VA_ARGS__)
|
||||
#else
|
||||
#define LOG(format, ... )
|
||||
#define LOG_RAW(format, ... )
|
||||
#endif
|
||||
|
||||
#define ENABLE_VERBOSE_LOGGING 0
|
||||
|
||||
#if ENABLE_VERBOSE_LOGGING
|
||||
#define LOGV(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
|
||||
#define LOGV_RAW(format, ... ) printf(format,## __VA_ARGS__)
|
||||
#else
|
||||
#define LOGV(format, ... )
|
||||
#define LOGB_RAW(format, ... )
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <driver/spi_master.h>
|
||||
#include <driver/gpio.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
/**
|
||||
* RF STATES
|
||||
*/
|
||||
enum RFSTATE {
|
||||
RFSTATE_IDLE = 0,
|
||||
RFSTATE_RX,
|
||||
RFSTATE_TX
|
||||
};
|
||||
|
||||
/**
|
||||
* Type of transfers
|
||||
*/
|
||||
#define WRITE_BURST 0x40
|
||||
#define READ_SINGLE 0x80
|
||||
#define READ_BURST 0xC0
|
||||
|
||||
/**
|
||||
* Type of register
|
||||
*/
|
||||
#define CC1101_CONFIG_REGISTER READ_SINGLE
|
||||
#define CC1101_STATUS_REGISTER READ_BURST
|
||||
|
||||
/**
|
||||
* PATABLE & FIFO's
|
||||
*/
|
||||
#define CC1101_PATABLE 0x3E // PATABLE address
|
||||
#define CC1101_TXFIFO 0x3F // TX FIFO address
|
||||
#define CC1101_RXFIFO 0x3F // RX FIFO address
|
||||
|
||||
/**
|
||||
* Command strobes
|
||||
*/
|
||||
#define CC1101_SRES 0x30 // Reset CC1101 chip
|
||||
#define CC1101_SFSTXON 0x31 // Enable and calibrate frequency synthesizer (if MCSM0.FS_AUTOCAL=1). If in RX (with CCA):
|
||||
// Go to a wait state where only the synthesizer is running (for quick RX / TX turnaround).
|
||||
#define CC1101_SXOFF 0x32 // Turn off crystal oscillator
|
||||
#define CC1101_SCAL 0x33 // Calibrate frequency synthesizer and turn it off. SCAL can be strobed from IDLE mode without
|
||||
// setting manual calibration mode (MCSM0.FS_AUTOCAL=0)
|
||||
#define CC1101_SRX 0x34 // Enable RX. Perform calibration first if coming from IDLE and MCSM0.FS_AUTOCAL=1
|
||||
#define CC1101_STX 0x35 // In IDLE state: Enable TX. Perform calibration first if MCSM0.FS_AUTOCAL=1.
|
||||
// If in RX state and CCA is enabled: Only go to TX if channel is clear
|
||||
#define CC1101_SIDLE 0x36 // Exit RX / TX, turn off frequency synthesizer and exit Wake-On-Radio mode if applicable
|
||||
#define CC1101_SWOR 0x38 // Start automatic RX polling sequence (Wake-on-Radio) as described in Section 19.5 if
|
||||
// WORCTRL.RC_PD=0
|
||||
#define CC1101_SPWD 0x39 // Enter power down mode when CSn goes high
|
||||
#define CC1101_SFRX 0x3A // Flush the RX FIFO buffer. Only issue SFRX in IDLE or RXFIFO_OVERFLOW states
|
||||
#define CC1101_SFTX 0x3B // Flush the TX FIFO buffer. Only issue SFTX in IDLE or TXFIFO_UNDERFLOW states
|
||||
#define CC1101_SWORRST 0x3C // Reset real time clock to Event1 value
|
||||
#define CC1101_SNOP 0x3D // No operation. May be used to get access to the chip status byte
|
||||
|
||||
#define CC1101_STATE_SLEEP 0x00
|
||||
#define CC1101_STATE_IDLE 0x01
|
||||
#define CC1101_STATE_XOFF 0x02
|
||||
#define CC1101_STATE_VCOON_MC 0x03
|
||||
#define CC1101_STATE_REGON_MC 0x04
|
||||
#define CC1101_STATE_MANCAL 0x05
|
||||
#define CC1101_STATE_VCOON 0x06
|
||||
#define CC1101_STATE_REGON 0x07
|
||||
#define CC1101_STATE_STARTCAL 0x08
|
||||
#define CC1101_STATE_BWBOOST 0x09
|
||||
#define CC1101_STATE_FS_LOCK 0x0A
|
||||
#define CC1101_STATE_IFADCON 0x0B
|
||||
#define CC1101_STATE_ENDCAL 0x0C
|
||||
#define CC1101_STATE_RX 0x0D
|
||||
#define CC1101_STATE_RX_END 0x0E
|
||||
#define CC1101_STATE_RX_RST 0x0F
|
||||
#define CC1101_STATE_TXRX_SWITCH 0x10
|
||||
#define CC1101_STATE_RXFIFO_OVERFLOW 0x11
|
||||
#define CC1101_STATE_FSTXON 0x12
|
||||
#define CC1101_STATE_TX 0x13
|
||||
#define CC1101_STATE_TX_END 0x14
|
||||
#define CC1101_STATE_RXTX_SWITCH 0x15
|
||||
#define CC1101_STATE_TXFIFO_UNDERFLOW 0x16
|
||||
|
||||
// Masks for first byte read from RXFIFO
|
||||
#define CC1101_NUM_RXBYTES_MASK 0x7f
|
||||
#define CC1101_RXFIFO_OVERFLOW_MASK 0x80
|
||||
|
||||
// Masks for last byte read from RXFIFO
|
||||
#define CC1101_LQI_MASK 0x7f
|
||||
#define CC1101_CRC_OK_MASK 0x80
|
||||
|
||||
// IOCFG2 GDO2: high when TX FIFO at or above the TX FIFO threshold
|
||||
#define CC1101_DEFVAL_IOCFG2 0x02
|
||||
|
||||
// IOCFG1 GDO1: High impedance (3-state)
|
||||
#define CC1101_DEFVAL_IOCFG1 0x2E
|
||||
|
||||
// GDO0 goes high when sync word has been sent / received, and
|
||||
// goes low at the end of the packet.
|
||||
// In TX mode the pin will go low if the TX FIFO underflows.
|
||||
#define CC1101_DEFVAL_IOCFG0 0x06
|
||||
|
||||
// Threshold = 32 bytes (1/2 of FIFO len)
|
||||
#define CC1101_DEFVAL_FIFOTHR 0x07
|
||||
#define CC1101_DEFVAL_RCCTRL1 0x41
|
||||
#define CC1101_DEFVAL_RCCTRL0 0x00
|
||||
#define CC1101_DEFVAL_AGCTEST 0x3F
|
||||
#define CC1101_DEFVAL_MCSM1 0x20
|
||||
#define CC1101_DEFVAL_WORCTRL 0xFB
|
||||
#define CC1101_DEFVAL_FSCTRL0 0
|
||||
|
||||
#define CC1101_DEFVAL_PATABLE 0xc0 // full power
|
||||
|
||||
RfSetting gFixedConfig[] = {
|
||||
{CC1101_IOCFG2,CC1101_DEFVAL_IOCFG2},
|
||||
{CC1101_IOCFG1,CC1101_DEFVAL_IOCFG1},
|
||||
{CC1101_IOCFG0,CC1101_DEFVAL_IOCFG0},
|
||||
{CC1101_FIFOTHR,CC1101_DEFVAL_FIFOTHR},
|
||||
{CC1101_FSCTRL0,CC1101_DEFVAL_FSCTRL0},
|
||||
{CC1101_RCCTRL1,CC1101_DEFVAL_RCCTRL1},
|
||||
{CC1101_RCCTRL0,CC1101_DEFVAL_RCCTRL0},
|
||||
{CC1101_MCSM1,CC1101_DEFVAL_MCSM1},
|
||||
{CC1101_WORCTRL,CC1101_DEFVAL_WORCTRL},
|
||||
{0xff,0},
|
||||
};
|
||||
|
||||
void CC1101_readBurstReg(uint8_t *buffer,uint8_t regAddr,uint8_t len);
|
||||
void CC1101_cmdStrobe(uint8_t cmd);
|
||||
void CC1101_wakeUp(void);
|
||||
uint8_t CC1101_readReg(uint8_t regAddr, uint8_t regType);
|
||||
void CC1101_writeReg(uint8_t regAddr, uint8_t value);
|
||||
void CC1101_setTxState(void);
|
||||
|
||||
void setIdleState(void);
|
||||
|
||||
spi_device_handle_t gSpiHndl;
|
||||
|
||||
#define readConfigReg(regAddr) CC1101_readReg(regAddr, CC1101_CONFIG_REGISTER)
|
||||
#define readStatusReg(regAddr) CC1101_readReg(regAddr, CC1101_STATUS_REGISTER)
|
||||
#define flushRxFifo() CC1101_cmdStrobe(CC1101_SFRX)
|
||||
#define flushTxFifo() CC1101_cmdStrobe(CC1101_SFTX)
|
||||
|
||||
const char *RegNamesCC1101[] = {
|
||||
"IOCFG2", // 0x00 GDO2 output pin configuration
|
||||
"IOCFG1", // 0x01 GDO1 output pin configuration
|
||||
"IOCFG0", // 0x02 GDO0 output pin configuration
|
||||
"FIFOTHR", // 0x03 RX FIFO and TX FIFO thresholds
|
||||
"SYNC1", // 0x04 Sync word, high INT8U
|
||||
"SYNC0", // 0x05 Sync word, low INT8U
|
||||
"PKTLEN", // 0x06 Packet length
|
||||
"PKTCTRL1", // 0x07 Packet automation control
|
||||
"PKTCTRL0", // 0x08 Packet automation control
|
||||
"ADDR", // 0x09 Device address
|
||||
"CHANNR", // 0x0A Channel number
|
||||
"FSCTRL1", // 0x0B Frequency synthesizer control
|
||||
"FSCTRL0", // 0x0C Frequency synthesizer control
|
||||
"FREQ2", // 0x0D Frequency control word, high INT8U
|
||||
"FREQ1", // 0x0E Frequency control word, middle INT8U
|
||||
"FREQ0", // 0x0F Frequency control word, low INT8U
|
||||
"MDMCFG4", // 0x10 Modem configuration
|
||||
"MDMCFG3", // 0x11 Modem configuration
|
||||
"MDMCFG2", // 0x12 Modem configuration
|
||||
"MDMCFG1", // 0x13 Modem configuration
|
||||
"MDMCFG0", // 0x14 Modem configuration
|
||||
"DEVIATN", // 0x15 Modem deviation setting
|
||||
"MCSM2", // 0x16 Main Radio Control State Machine configuration
|
||||
"MCSM1", // 0x17 Main Radio Control State Machine configuration
|
||||
"MCSM0", // 0x18 Main Radio Control State Machine configuration
|
||||
"FOCCFG", // 0x19 Frequency Offset Compensation configuration
|
||||
"BSCFG", // 0x1A Bit Synchronization configuration
|
||||
"AGCCTRL2", // 0x1B AGC control
|
||||
"AGCCTRL1", // 0x1C AGC control
|
||||
"AGCCTRL0", // 0x1D AGC control
|
||||
"WOREVT1", // 0x1E High INT8U Event 0 timeout
|
||||
"WOREVT0", // 0x1F Low INT8U Event 0 timeout
|
||||
"WORCTRL", // 0x20 Wake On Radio control
|
||||
"FREND1", // 0x21 Front end RX configuration
|
||||
"FREND0", // 0x22 Front end TX configuration
|
||||
"FSCAL3", // 0x23 Frequency synthesizer calibration
|
||||
"FSCAL2", // 0x24 Frequency synthesizer calibration
|
||||
"FSCAL1", // 0x25 Frequency synthesizer calibration
|
||||
"FSCAL0", // 0x26 Frequency synthesizer calibration
|
||||
"RCCTRL1", // 0x27 RC oscillator configuration
|
||||
"RCCTRL0", // 0x28 RC oscillator configuration
|
||||
"FSTEST", // 0x29 Frequency synthesizer calibration control
|
||||
"PTEST", // 0x2A Production test
|
||||
"AGCTEST", // 0x2B AGC test
|
||||
"TEST2", // 0x2C Various test settings
|
||||
"TEST1", // 0x2D Various test settings
|
||||
"TEST0", // 0x2E Various test settings
|
||||
"0x2f", // 0x2f
|
||||
//CC1101 Strobe commands
|
||||
"SRES", // 0x30 Reset chip.
|
||||
"SFSTXON", // 0x31 Enable and calibrate frequency synthesizer (if MCSM0.FS_AUTOCAL=1).
|
||||
"SXOFF", // 0x32 Turn off crystal oscillator.
|
||||
"SCAL", // 0x33 Calibrate frequency synthesizer and turn it off
|
||||
"SRX", // 0x34 Enable RX. Perform calibration first if coming from IDLE and
|
||||
"STX", // 0x35 In IDLE state: Enable TX. Perform calibration first if
|
||||
"SIDLE", // 0x36 Exit RX / TX, turn off frequency synthesizer and exit
|
||||
"SAFC", // 0x37 Perform AFC adjustment of the frequency synthesizer
|
||||
"SWOR", // 0x38 Start automatic RX polling sequence (Wake-on-Radio)
|
||||
"SPWD", // 0x39 Enter power down mode when CSn goes high.
|
||||
"SFRX", // 0x3A Flush the RX FIFO buffer.
|
||||
"SFTX", // 0x3B Flush the TX FIFO buffer.
|
||||
"SWORRST", // 0x3C Reset real time clock.
|
||||
"SNOP", // 0x3D No operation. May be used to pad strobe commands to two
|
||||
"PATABLE" // 0x3E
|
||||
};
|
||||
|
||||
|
||||
// SPI Stuff
|
||||
#if CONFIG_SPI2_HOST
|
||||
#define HOST_ID SPI2_HOST
|
||||
#elif CONFIG_SPI3_HOST
|
||||
#define HOST_ID SPI3_HOST
|
||||
#endif
|
||||
|
||||
/*
|
||||
* RF state
|
||||
*/
|
||||
static uint8_t gRfState;
|
||||
|
||||
#define cc1101_Select() gpio_set_level(CONFIG_CSN_GPIO, LOW)
|
||||
#define cc1101_Deselect() gpio_set_level(CONFIG_CSN_GPIO, HIGH)
|
||||
#define wait_Miso() while(gpio_get_level(CONFIG_MISO_GPIO)>0)
|
||||
#define getGDO0state() gpio_get_level(CONFIG_GDO0_GPIO)
|
||||
#define wait_GDO0_high() while(!getGDO0state())
|
||||
#define wait_GDO0_low() while(getGDO0state())
|
||||
#define getGDO2state() gpio_get_level(CONFIG_GDO2_GPIO)
|
||||
#define wait_GDO2_low() while(getGDO2state())
|
||||
|
||||
/**
|
||||
* Arduino Macros
|
||||
*/
|
||||
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
|
||||
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
|
||||
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
|
||||
#define bitWrite(value, bit, bitvalue) ((bitvalue) ? bitSet(value, bit) : bitClear(value, bit))
|
||||
#define delayMicroseconds(us) esp_rom_delay_us(us)
|
||||
#define LOW 0
|
||||
#define HIGH 1
|
||||
|
||||
int32_t gFreqErrSum;
|
||||
uint8_t gFreqErrSumCount;
|
||||
int8_t gFreqCorrection;
|
||||
|
||||
bool spi_write_byte(uint8_t* Dataout,size_t DataLength)
|
||||
{
|
||||
spi_transaction_t SPITransaction;
|
||||
|
||||
if(DataLength > 0) {
|
||||
memset(&SPITransaction,0,sizeof(spi_transaction_t));
|
||||
SPITransaction.length = DataLength * 8;
|
||||
SPITransaction.tx_buffer = Dataout;
|
||||
SPITransaction.rx_buffer = NULL;
|
||||
spi_device_transmit(gSpiHndl,&SPITransaction);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool spi_read_byte(uint8_t* Datain,uint8_t* Dataout,size_t DataLength)
|
||||
{
|
||||
spi_transaction_t SPITransaction;
|
||||
|
||||
if(DataLength > 0) {
|
||||
memset(&SPITransaction,0,sizeof(spi_transaction_t));
|
||||
SPITransaction.length = DataLength * 8;
|
||||
SPITransaction.tx_buffer = Dataout;
|
||||
SPITransaction.rx_buffer = Datain;
|
||||
spi_device_transmit(gSpiHndl,&SPITransaction);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t spi_transfer(uint8_t address)
|
||||
{
|
||||
uint8_t datain[1];
|
||||
uint8_t dataout[1];
|
||||
dataout[0] = address;
|
||||
//spi_write_byte(dev, dataout, 1 );
|
||||
//spi_read_byte(datain, dataout, 1 );
|
||||
|
||||
spi_transaction_t SPITransaction;
|
||||
memset(&SPITransaction,0,sizeof(spi_transaction_t));
|
||||
SPITransaction.length = 8;
|
||||
SPITransaction.tx_buffer = dataout;
|
||||
SPITransaction.rx_buffer = datain;
|
||||
spi_device_transmit(gSpiHndl,&SPITransaction);
|
||||
|
||||
return datain[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* CC1101_wakeUp
|
||||
*
|
||||
* Wake up CC1101 from Power Down state
|
||||
*/
|
||||
void CC1101_wakeUp(void)
|
||||
{
|
||||
cc1101_Select();
|
||||
wait_Miso();
|
||||
cc1101_Deselect();
|
||||
}
|
||||
|
||||
/**
|
||||
* CC1101_writeReg
|
||||
*
|
||||
* Write single register into the CC1101 IC via SPI
|
||||
*
|
||||
* @param regAddr Register address
|
||||
* @param value Value to be writen
|
||||
*/
|
||||
void CC1101_writeReg(uint8_t regAddr, uint8_t value)
|
||||
{
|
||||
if(regAddr < 0x3f) {
|
||||
LOGV("0x%x -> %s(0x%x)\n",value,RegNamesCC1101[regAddr],regAddr);
|
||||
}
|
||||
else {
|
||||
LOGV("0x%x -> 0x%x\n",value,regAddr);
|
||||
}
|
||||
cc1101_Select(); // Select CC1101
|
||||
wait_Miso(); // Wait until MISO goes low
|
||||
spi_transfer(regAddr); // Send register address
|
||||
spi_transfer(value); // Send value
|
||||
cc1101_Deselect(); // Deselect CC1101
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CC1101_cmdStrobe
|
||||
*
|
||||
* Send command strobe to the CC1101 IC via SPI
|
||||
*
|
||||
* @param cmd Command strobe
|
||||
*/
|
||||
void CC1101_cmdStrobe(uint8_t cmd)
|
||||
{
|
||||
cc1101_Select();
|
||||
wait_Miso();
|
||||
spi_transfer(cmd);
|
||||
cc1101_Deselect();
|
||||
}
|
||||
|
||||
/**
|
||||
* CC1101_readReg
|
||||
*
|
||||
* Read CC1101 register via SPI
|
||||
*
|
||||
* @param regAddr Register address
|
||||
* @param regType Type of register: CONFIG_REGISTER or STATUS_REGISTER
|
||||
*
|
||||
* Return:
|
||||
* Data uint8_t returned by the CC1101 IC
|
||||
*/
|
||||
uint8_t CC1101_readReg(uint8_t regAddr,uint8_t regType)
|
||||
{
|
||||
uint8_t addr, val;
|
||||
|
||||
addr = regAddr | regType;
|
||||
cc1101_Select();
|
||||
wait_Miso();
|
||||
spi_transfer(addr);
|
||||
val = spi_transfer(0x00); // Read result
|
||||
cc1101_Deselect();
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* CC1101_readBurstReg
|
||||
*
|
||||
* Read burst data from CC1101 via SPI
|
||||
*
|
||||
* @param buffer Buffer where to copy the result to
|
||||
* @param regAddr Register address
|
||||
* @param len Data length
|
||||
*/
|
||||
void CC1101_readBurstReg(uint8_t *buffer,uint8_t regAddr,uint8_t len)
|
||||
{
|
||||
uint8_t addr, i;
|
||||
|
||||
addr = regAddr | READ_BURST;
|
||||
cc1101_Select();
|
||||
wait_Miso();
|
||||
spi_transfer(addr); // Send register address
|
||||
for(i = 0; i < len; i++) {
|
||||
buffer[i] = spi_transfer(0x00); // Read result uint8_t by uint8_t
|
||||
}
|
||||
cc1101_Deselect();
|
||||
}
|
||||
|
||||
/**
|
||||
* reset
|
||||
*
|
||||
* Reset CC1101
|
||||
*/
|
||||
void CC1101_reset(void)
|
||||
{
|
||||
// See sectin 19.1.2 of the CC1101 spec sheet for reasons for the following
|
||||
cc1101_Deselect();
|
||||
delayMicroseconds(5);
|
||||
cc1101_Select();
|
||||
delayMicroseconds(10);
|
||||
cc1101_Deselect();
|
||||
delayMicroseconds(41);
|
||||
cc1101_Select();
|
||||
|
||||
// Wait until MISO goes low indicating XOSC stable
|
||||
wait_Miso();
|
||||
spi_transfer(CC1101_SRES); // Send reset command strobe
|
||||
wait_Miso();
|
||||
cc1101_Deselect();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CC1101_setRxState
|
||||
*
|
||||
* Enter Rx state
|
||||
*/
|
||||
void CC1101_setRxState(void)
|
||||
{
|
||||
CC1101_cmdStrobe(CC1101_SRX);
|
||||
gRfState = RFSTATE_RX;
|
||||
}
|
||||
|
||||
/**
|
||||
* CC1101_setTxState
|
||||
*
|
||||
* Enter Tx state
|
||||
*/
|
||||
void CC1101_setTxState(void)
|
||||
{
|
||||
CC1101_cmdStrobe(CC1101_STX);
|
||||
gRfState = RFSTATE_TX;
|
||||
}
|
||||
|
||||
|
||||
void CC1101_DumpRegs()
|
||||
{
|
||||
#if ENABLE_LOGGING
|
||||
uint8_t regAddr;
|
||||
uint8_t value;
|
||||
|
||||
LOG("\n");
|
||||
for(regAddr = 0; regAddr < 0x2f; regAddr++) {
|
||||
value = CC1101_readReg(regAddr,READ_SINGLE);
|
||||
LOG("%02x %s: 0x%02X\n",regAddr,RegNamesCC1101[regAddr],value);
|
||||
}
|
||||
|
||||
#if 0
|
||||
for(regAddr = 0; regAddr < 0x2f; regAddr++) {
|
||||
value = CC1101_readReg(regAddr,READ_SINGLE);
|
||||
LOG("<Register><Name>%s</Name><Value>0x%02X</Value></Register>\n",RegNamesCC1101[regAddr],value);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool CC1101_Tx(uint8_t *TxData)
|
||||
{
|
||||
bool Ret = false;
|
||||
int ErrLine = 0;
|
||||
spi_transaction_t SPITransaction;
|
||||
uint8_t BytesSent = 0;
|
||||
uint8_t Bytes2Send;
|
||||
uint8_t len;
|
||||
uint8_t CanSend;
|
||||
esp_err_t Err;
|
||||
|
||||
do {
|
||||
// The first byte in the buffer is the number of data bytes to send,
|
||||
// we also need to send the first byte
|
||||
len = 1 + *TxData;
|
||||
memset(&SPITransaction,0,sizeof(spi_transaction_t));
|
||||
SPITransaction.tx_buffer = TxData;
|
||||
|
||||
setIdleState();
|
||||
flushTxFifo();
|
||||
|
||||
while(BytesSent < len) {
|
||||
Bytes2Send = len - BytesSent;
|
||||
if(BytesSent == 0) {
|
||||
// First chunk, the FIFO is empty and can take 64 bytes
|
||||
if(Bytes2Send > 64) {
|
||||
Bytes2Send = 64;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Not the first chunk, we can only send FIFO_THRESHOLD bytes
|
||||
// and only when GDO2 says we can
|
||||
if(getGDO2state()) {
|
||||
wait_GDO2_low();
|
||||
}
|
||||
CanSend = readStatusReg(CC1101_TXBYTES);
|
||||
if(CanSend & 0x80) {
|
||||
LOGE("TX FIFO underflow, BytesSent %d\n",BytesSent);
|
||||
ErrLine = __LINE__;
|
||||
break;
|
||||
}
|
||||
CanSend = 64 - CanSend;
|
||||
if(CanSend == 0) {
|
||||
LOGE("CanSend == 0, GDO2 problem\n");
|
||||
ErrLine = __LINE__;
|
||||
break;
|
||||
}
|
||||
|
||||
if(Bytes2Send > CanSend) {
|
||||
Bytes2Send = CanSend;
|
||||
}
|
||||
}
|
||||
SPITransaction.length = Bytes2Send * 8;
|
||||
SPITransaction.rxlength = 0;
|
||||
cc1101_Select();
|
||||
wait_Miso();
|
||||
spi_transfer(CC1101_TXFIFO | WRITE_BURST);
|
||||
if((Err = spi_device_transmit(gSpiHndl,&SPITransaction)) != ESP_OK) {
|
||||
ErrLine = __LINE__;
|
||||
LOGE("spi_device_transmit failed %d\n",Err);
|
||||
break;
|
||||
}
|
||||
cc1101_Deselect();
|
||||
// LOG("Sending %d bytes\n",Bytes2Send);
|
||||
if(BytesSent == 0) {
|
||||
// some or all of the tx data has been written to the FIFO,
|
||||
// start transmitting
|
||||
// LOG("Start tx\n");
|
||||
CC1101_setTxState();
|
||||
// Wait for the sync word to be transmitted
|
||||
wait_GDO0_high();
|
||||
}
|
||||
SPITransaction.tx_buffer += Bytes2Send;
|
||||
BytesSent += Bytes2Send;
|
||||
}
|
||||
|
||||
// Wait until the end of the TxData transmission
|
||||
wait_GDO0_low();
|
||||
Ret = true;
|
||||
} while(false);
|
||||
|
||||
setIdleState();
|
||||
CC1101_setRxState();
|
||||
|
||||
if(ErrLine != 0) {
|
||||
LOGE("%s#%d: failure\n",__FUNCTION__,ErrLine);
|
||||
}
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
// Called when GDO0 goes low, i.e. end of packet.
|
||||
// Everything has been received.
|
||||
// NB: this means the entire packet must fit in the FIFO so maximum
|
||||
// message length is 64 bytes.
|
||||
int CC1101_Rx(uint8_t *RxBuf,size_t RxBufLen,uint8_t *pRssi,uint8_t *pLqi)
|
||||
{
|
||||
uint8_t rxBytes = readStatusReg(CC1101_RXBYTES);
|
||||
uint8_t Rssi;
|
||||
uint8_t Lqi;
|
||||
int Ret;
|
||||
int8_t FreqErr;
|
||||
int8_t FreqCorrection;
|
||||
|
||||
// Any data waiting to be read and no overflow?
|
||||
do {
|
||||
if(rxBytes & CC1101_RXFIFO_OVERFLOW_MASK) {
|
||||
LOGE("RxFifo overflow\n");
|
||||
Ret = -2;
|
||||
break;
|
||||
}
|
||||
|
||||
if(rxBytes < 2) {
|
||||
// should have at least 2 bytes, packet len and one byte of data
|
||||
LOGE("Internal error, rxBytes = %d\n",rxBytes);
|
||||
Ret = -2;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get packet length
|
||||
Ret = readConfigReg(CC1101_RXFIFO);
|
||||
if(Ret > RxBufLen) {
|
||||
// Toss the data
|
||||
LOGE("RxBuf too small %d < %d\n",RxBufLen,Ret);
|
||||
Ret = -1;
|
||||
break;
|
||||
}
|
||||
// Read the data
|
||||
CC1101_readBurstReg(RxBuf,CC1101_RXFIFO,Ret);
|
||||
// Read RSSI
|
||||
Rssi = readConfigReg(CC1101_RXFIFO);
|
||||
// Read LQI and CRC_OK
|
||||
Lqi = readConfigReg(CC1101_RXFIFO);
|
||||
if(!(Lqi & CC1101_CRC_OK_MASK)) {
|
||||
// Crc error, ignore the packet
|
||||
LOG("Ignoring %d byte packet, CRC error\n",Ret);
|
||||
Ret = 0;
|
||||
break;
|
||||
}
|
||||
// CRC is valid
|
||||
if(pRssi != NULL) {
|
||||
*pRssi = Rssi;
|
||||
}
|
||||
if(pLqi != NULL) {
|
||||
*pLqi = Lqi & CC1101_LQI_MASK;
|
||||
}
|
||||
FreqErr = (int8_t) CC1101_readReg(CC1101_FREQEST,CC1101_STATUS_REGISTER);
|
||||
if(FreqErr != 0 && gFreqErrSumCount < 255) {
|
||||
gFreqErrSum += FreqErr + gFreqCorrection;
|
||||
gFreqErrSumCount++;
|
||||
FreqCorrection = (int8_t) (gFreqErrSum / gFreqErrSumCount);
|
||||
if(gFreqCorrection != FreqCorrection) {
|
||||
LOGA("FreqCorrection %d -> %d\n",gFreqCorrection,FreqCorrection);
|
||||
gFreqCorrection = FreqCorrection;
|
||||
CC1101_writeReg(CC1101_FSCTRL0,gFreqCorrection);
|
||||
}
|
||||
if(gFreqErrSumCount == 255) {
|
||||
LOGA("Final FreqCorrection %d\n",gFreqCorrection);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
setIdleState();
|
||||
flushRxFifo();
|
||||
CC1101_setRxState();
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
bool CC1101_Present()
|
||||
{
|
||||
bool Ret = false;
|
||||
uint8_t PartNum = CC1101_readReg(CC1101_PARTNUM, CC1101_STATUS_REGISTER);
|
||||
uint8_t ChipVersion = CC1101_readReg(CC1101_VERSION, CC1101_STATUS_REGISTER);
|
||||
|
||||
if(PartNum == 0 && (ChipVersion == 20 || ChipVersion == 4)) {
|
||||
LOGA("CC1101 detected\n");
|
||||
Ret = true;
|
||||
}
|
||||
else {
|
||||
if(PartNum != 0) {
|
||||
LOGA("Invalid PartNum 0x%x\n",PartNum);
|
||||
}
|
||||
else {
|
||||
LOGA("Invalid or unsupported ChipVersion 0x%x\n",ChipVersion);
|
||||
}
|
||||
}
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
void CC1101_SetConfig(const RfSetting *pConfig)
|
||||
{
|
||||
int i;
|
||||
uint8_t RegWasSet[CC1101_TEST0 + 1];
|
||||
uint8_t Reg;
|
||||
|
||||
memset(RegWasSet,0,sizeof(RegWasSet));
|
||||
setIdleState();
|
||||
|
||||
if(pConfig == NULL) {
|
||||
// Just set the fixed registers
|
||||
LOG("Setting fixed registers\n");
|
||||
for(i = 0; (Reg = gFixedConfig[i].Reg) != 0xff; i++) {
|
||||
CC1101_writeReg(Reg,gFixedConfig[i].Value);
|
||||
}
|
||||
// Set TX power
|
||||
CC1101_writeReg(CC1101_PATABLE,CC1101_DEFVAL_PATABLE);
|
||||
}
|
||||
else {
|
||||
for(i = 0; (Reg = gFixedConfig[i].Reg) != 0xff; i++) {
|
||||
RegWasSet[Reg] = 1;
|
||||
}
|
||||
|
||||
while((Reg = pConfig->Reg) != 0xff) {
|
||||
if(RegWasSet[Reg] == 1) {
|
||||
LOG("%s value ignored\n",RegNamesCC1101[Reg]);
|
||||
}
|
||||
else {
|
||||
if(RegWasSet[Reg] == 2) {
|
||||
LOG("%s value set before\n",RegNamesCC1101[Reg]);
|
||||
}
|
||||
CC1101_writeReg(pConfig->Reg,pConfig->Value);
|
||||
RegWasSet[Reg] = 2;
|
||||
}
|
||||
pConfig++;
|
||||
}
|
||||
#if 0
|
||||
for(Reg = 0; Reg <= CC1101_TEST0; Reg++) {
|
||||
if(RegWasSet[Reg] == 0) {
|
||||
LOG("%s value not set\n",RegNamesCC1101[Reg]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void setIdleState()
|
||||
{
|
||||
uint8_t MarcState;
|
||||
CC1101_cmdStrobe(CC1101_SIDLE);
|
||||
// Wait for it
|
||||
do {
|
||||
MarcState = readStatusReg(CC1101_MARCSTATE);
|
||||
} while(MarcState != CC1101_STATE_IDLE);
|
||||
}
|
||||
|
||||
|
||||
void CC1101_logState()
|
||||
{
|
||||
static uint8_t LastMarcState = 0xff;
|
||||
uint8_t MarcState;
|
||||
|
||||
MarcState = readStatusReg(CC1101_MARCSTATE);
|
||||
if(LastMarcState != MarcState) {
|
||||
LOG("MarcState 0x%x -> 0x%x\n",LastMarcState,MarcState);
|
||||
LastMarcState = MarcState;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
// Large portions of this code was copied from:
|
||||
// https://github.com/nopnop2002/esp-idf-cc1101 with the following copyright
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 panStamp <contact@panstamp.com>
|
||||
* Copyright (c) 2016 Tyler Sommer <contact@tylersommer.pro>
|
||||
*
|
||||
* This file is part of the CC1101 project.
|
||||
*
|
||||
* CC1101 is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* CC1101 is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with CC1101; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*
|
||||
* Author: Daniel Berenguer
|
||||
* Creation date: 03/03/2011
|
||||
*/
|
||||
|
||||
#ifndef __CC1101_RADIO_H_
|
||||
#define __CC1101_RADIO_H_
|
||||
|
||||
/**
|
||||
* CC1101 configuration registers
|
||||
*/
|
||||
#define CC1101_IOCFG2 0x00 // GDO2 Output Pin Configuration
|
||||
#define CC1101_IOCFG1 0x01 // GDO1 Output Pin Configuration
|
||||
#define CC1101_IOCFG0 0x02 // GDO0 Output Pin Configuration
|
||||
#define CC1101_FIFOTHR 0x03 // RX FIFO and TX FIFO Thresholds
|
||||
#define CC1101_SYNC1 0x04 // Sync Word, High Byte
|
||||
#define CC1101_SYNC0 0x05 // Sync Word, Low Byte
|
||||
#define CC1101_PKTLEN 0x06 // Packet Length
|
||||
#define CC1101_PKTCTRL1 0x07 // Packet Automation Control
|
||||
#define CC1101_PKTCTRL0 0x08 // Packet Automation Control
|
||||
#define CC1101_ADDR 0x09 // Device Address
|
||||
#define CC1101_CHANNR 0x0A // Channel Number
|
||||
#define CC1101_FSCTRL1 0x0B // Frequency Synthesizer Control
|
||||
#define CC1101_FSCTRL0 0x0C // Frequency Synthesizer Control
|
||||
#define CC1101_FREQ2 0x0D // Frequency Control Word, High Byte
|
||||
#define CC1101_FREQ1 0x0E // Frequency Control Word, Middle Byte
|
||||
#define CC1101_FREQ0 0x0F // Frequency Control Word, Low Byte
|
||||
#define CC1101_MDMCFG4 0x10 // Modem Configuration
|
||||
#define CC1101_MDMCFG3 0x11 // Modem Configuration
|
||||
#define CC1101_MDMCFG2 0x12 // Modem Configuration
|
||||
#define CC1101_MDMCFG1 0x13 // Modem Configuration
|
||||
#define CC1101_MDMCFG0 0x14 // Modem Configuration
|
||||
#define CC1101_DEVIATN 0x15 // Modem Deviation Setting
|
||||
#define CC1101_MCSM2 0x16 // Main Radio Control State Machine Configuration
|
||||
#define CC1101_MCSM1 0x17 // Main Radio Control State Machine Configuration
|
||||
#define CC1101_MCSM0 0x18 // Main Radio Control State Machine Configuration
|
||||
#define CC1101_FOCCFG 0x19 // Frequency Offset Compensation Configuration
|
||||
#define CC1101_BSCFG 0x1A // Bit Synchronization Configuration
|
||||
#define CC1101_AGCCTRL2 0x1B // AGC Control
|
||||
#define CC1101_AGCCTRL1 0x1C // AGC Control
|
||||
#define CC1101_AGCCTRL0 0x1D // AGC Control
|
||||
#define CC1101_WOREVT1 0x1E // High Byte Event0 Timeout
|
||||
#define CC1101_WOREVT0 0x1F // Low Byte Event0 Timeout
|
||||
#define CC1101_WORCTRL 0x20 // Wake On Radio Control
|
||||
#define CC1101_FREND1 0x21 // Front End RX Configuration
|
||||
#define CC1101_FREND0 0x22 // Front End TX Configuration
|
||||
#define CC1101_FSCAL3 0x23 // Frequency Synthesizer Calibration
|
||||
#define CC1101_FSCAL2 0x24 // Frequency Synthesizer Calibration
|
||||
#define CC1101_FSCAL1 0x25 // Frequency Synthesizer Calibration
|
||||
#define CC1101_FSCAL0 0x26 // Frequency Synthesizer Calibration
|
||||
#define CC1101_RCCTRL1 0x27 // RC Oscillator Configuration
|
||||
#define CC1101_RCCTRL0 0x28 // RC Oscillator Configuration
|
||||
#define CC1101_FSTEST 0x29 // Frequency Synthesizer Calibration Control
|
||||
#define CC1101_PTEST 0x2A // Production Test
|
||||
#define CC1101_AGCTEST 0x2B // AGC Test
|
||||
#define CC1101_TEST2 0x2C // Various Test Settings
|
||||
#define CC1101_TEST1 0x2D // Various Test Settings
|
||||
#define CC1101_TEST0 0x2E // Various Test Settings
|
||||
|
||||
/**
|
||||
* Status registers
|
||||
*/
|
||||
#define CC1101_PARTNUM 0x30 // Chip ID
|
||||
#define CC1101_VERSION 0x31 // Chip ID
|
||||
#define CC1101_FREQEST 0x32 // Frequency Offset Estimate from Demodulator
|
||||
#define CC1101_LQI 0x33 // Demodulator Estimate for Link Quality
|
||||
#define CC1101_RSSI 0x34 // Received Signal Strength Indication
|
||||
#define CC1101_MARCSTATE 0x35 // Main Radio Control State Machine State
|
||||
#define CC1101_WORTIME1 0x36 // High Byte of WOR Time
|
||||
#define CC1101_WORTIME0 0x37 // Low Byte of WOR Time
|
||||
#define CC1101_PKTSTATUS 0x38 // Current GDOx Status and Packet Status
|
||||
#define CC1101_VCO_VC_DAC 0x39 // Current Setting from PLL Calibration Module
|
||||
#define CC1101_TXBYTES 0x3A // Underflow and Number of Bytes
|
||||
#define CC1101_RXBYTES 0x3B // Overflow and Number of Bytes
|
||||
#define CC1101_RCCTRL1_STATUS 0x3C // Last RC Oscillator Calibration Result
|
||||
#define CC1101_RCCTRL0_STATUS 0x3D // Last RC Oscillator Calibration Result
|
||||
|
||||
typedef struct {
|
||||
uint16_t Reg;
|
||||
uint8_t Value;
|
||||
} RfSetting;
|
||||
|
||||
extern spi_device_handle_t gSpiHndl;
|
||||
|
||||
void CC1101_SetConfig(const RfSetting *pConfig);
|
||||
int CC1101_Rx(uint8_t *RxBuf,size_t RxBufLen,uint8_t *pRssi,uint8_t *pLqi);
|
||||
bool CC1101_Tx(uint8_t *TxData);
|
||||
bool CC1101_Present(void);
|
||||
void CC1101_DumpRegs(void);
|
||||
void CC1101_reset(void);
|
||||
void CC1101_logState(void);
|
||||
void CC1101_setRxState(void);
|
||||
|
||||
#endif // __CC1101_RADIO_H_
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#include "led.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "proto.h"
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NUM_LEDS 2
|
||||
|
||||
const gpio_num_t led_pins[NUM_LEDS] = {LED1, LED2};
|
||||
TimerHandle_t led_timers[NUM_LEDS] = {0};
|
||||
|
||||
void led_timer_callback(TimerHandle_t xTimer) {
|
||||
int led_index = (int)pvTimerGetTimerID(xTimer);
|
||||
if (led_index >= 0 && led_index < NUM_LEDS) {
|
||||
gpio_set_level(led_pins[led_index], 0);
|
||||
}
|
||||
}
|
||||
|
||||
void init_led() {
|
||||
gpio_config_t led1 = {};
|
||||
led1.intr_type = GPIO_INTR_DISABLE;
|
||||
led1.mode = GPIO_MODE_OUTPUT;
|
||||
led1.pin_bit_mask = ((1ULL << LED1) | (1ULL << LED2));
|
||||
led1.pull_down_en = 0;
|
||||
led1.pull_up_en = 0;
|
||||
gpio_config(&led1);
|
||||
|
||||
for (int i = 0; i < NUM_LEDS; i++) {
|
||||
led_timers[i] = xTimerCreate("led_timer", pdMS_TO_TICKS(50), pdFALSE, (void *)i, led_timer_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void led_flash(int nr) {
|
||||
gpio_set_level(led_pins[nr], 1);
|
||||
if (nr >= 0 && nr < NUM_LEDS) {
|
||||
xTimerStart(led_timers[nr], 0);
|
||||
}
|
||||
}
|
||||
|
||||
void led_set(int nr, bool state) {
|
||||
gpio_set_level(nr, state);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
|
||||
void init_led();
|
||||
void led_set(int nr, bool state);
|
||||
void led_flash(int nr);
|
||||
@@ -1,833 +0,0 @@
|
||||
// Ported to ESP32-H2 By ATC1441(ATCnetz.de) for OpenEPaperLink at ~08.2023
|
||||
|
||||
#include "main.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_ieee802154.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_phy_init.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "led.h"
|
||||
#include "proto.h"
|
||||
#include "radio.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "second_uart.h"
|
||||
//#include "soc/lp_uart_reg.h"
|
||||
#include "soc/uart_struct.h"
|
||||
#include "utils.h"
|
||||
#include <esp_mac.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "SubGigRadio.h"
|
||||
|
||||
|
||||
static const char *TAG = "MAIN";
|
||||
|
||||
const uint8_t channelList[6] = {11, 15, 20, 25, 26, 27};
|
||||
|
||||
#define DATATYPE_NOUPDATE 0
|
||||
#define HW_TYPE 0xC6
|
||||
|
||||
#define MAX_PENDING_MACS 250
|
||||
#define HOUSEKEEPING_INTERVAL 60UL
|
||||
|
||||
struct pendingData pendingDataArr[MAX_PENDING_MACS];
|
||||
|
||||
// VERSION GOES HERE!
|
||||
uint16_t version = 0x001d;
|
||||
|
||||
#define RAW_PKT_PADDING 2
|
||||
|
||||
uint8_t radiotxbuffer[128];
|
||||
uint8_t radiorxbuffer[128];
|
||||
|
||||
static uint32_t housekeepingTimer;
|
||||
|
||||
struct blockRequest requestedData = {0}; // holds which data was requested by the tag
|
||||
|
||||
uint8_t dstMac[8]; // target for the block transfer
|
||||
uint16_t dstPan; //
|
||||
|
||||
static uint32_t blockStartTimer = 0; // reference that holds when the AP sends the next block
|
||||
uint32_t nextBlockAttempt = 0; // reference time for when the AP can request a new block from the ESP32
|
||||
uint8_t seq = 0; // holds current sequence number for transmission
|
||||
uint8_t blockbuffer[BLOCK_XFER_BUFFER_SIZE + 5]; // block transfer buffer
|
||||
uint8_t lastAckMac[8] = {0};
|
||||
|
||||
// these variables hold the current mac were talking to
|
||||
#define CONCURRENT_REQUEST_DELAY 1200UL
|
||||
uint32_t lastBlockRequest = 0;
|
||||
uint8_t lastBlockMac[8];
|
||||
uint8_t lastTagReturn[8];
|
||||
|
||||
#define NO_SUBGHZ_CHANNEL 255
|
||||
uint8_t curSubGhzChannel;
|
||||
uint8_t curChannel = 25;
|
||||
uint8_t curPower = 10;
|
||||
|
||||
uint8_t curPendingData = 0;
|
||||
uint8_t curNoUpdate = 0;
|
||||
|
||||
bool highspeedSerial = false;
|
||||
|
||||
void sendXferCompleteAck(uint8_t *dst);
|
||||
void sendCancelXfer(uint8_t *dst);
|
||||
void espNotifyAPInfo();
|
||||
|
||||
// tools
|
||||
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;
|
||||
}
|
||||
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 getPacketType(void *buffer) {
|
||||
struct MacFcs *fcs = buffer;
|
||||
if ((fcs->frameType == 1) && (fcs->destAddrType == 2) && (fcs->srcAddrType == 3) && (fcs->panIdCompressed == 0)) {
|
||||
// broadcast frame
|
||||
uint8_t type = ((uint8_t *) buffer)[sizeof(struct MacFrameBcast)];
|
||||
return type;
|
||||
} else if ((fcs->frameType == 1) && (fcs->destAddrType == 3) && (fcs->srcAddrType == 3) && (fcs->panIdCompressed == 1)) {
|
||||
// normal frame
|
||||
uint8_t type = ((uint8_t *) buffer)[sizeof(struct MacFrameNormal)];
|
||||
return type;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
uint8_t getBlockDataLength() {
|
||||
uint8_t partNo = 0;
|
||||
for (uint8_t c = 0; c < BLOCK_MAX_PARTS; c++) {
|
||||
if (requestedData.requestedParts[c / 8] & (1 << (c % 8))) {
|
||||
partNo++;
|
||||
}
|
||||
}
|
||||
return partNo;
|
||||
}
|
||||
|
||||
// pendingdata slot stuff
|
||||
int8_t findSlotForMac(const uint8_t *mac) {
|
||||
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
|
||||
if (memcmp(mac, ((uint8_t *) &(pendingDataArr[c].targetMac)), 8) == 0) {
|
||||
if (pendingDataArr[c].attemptsLeft != 0) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int8_t findFreeSlot() {
|
||||
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
|
||||
if (pendingDataArr[c].attemptsLeft == 0) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int8_t findSlotForVer(const uint8_t *ver) {
|
||||
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
|
||||
if (memcmp(ver, ((uint8_t *) &(pendingDataArr[c].availdatainfo.dataVer)), 8) == 0) {
|
||||
if (pendingDataArr[c].attemptsLeft != 0) return c;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
void deleteAllPendingDataForVer(const uint8_t *ver) {
|
||||
int8_t slot = -1;
|
||||
do {
|
||||
slot = findSlotForVer(ver);
|
||||
if (slot != -1) pendingDataArr[slot].attemptsLeft = 0;
|
||||
} while (slot != -1);
|
||||
}
|
||||
void deleteAllPendingDataForMac(const uint8_t *mac) {
|
||||
int8_t slot = -1;
|
||||
do {
|
||||
slot = findSlotForMac(mac);
|
||||
if (slot != -1) pendingDataArr[slot].attemptsLeft = 0;
|
||||
} while (slot != -1);
|
||||
}
|
||||
|
||||
void countSlots() {
|
||||
curPendingData = 0;
|
||||
curNoUpdate = 0;
|
||||
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
|
||||
if (pendingDataArr[c].attemptsLeft != 0) {
|
||||
if (pendingDataArr[c].availdatainfo.dataType != 0) {
|
||||
curPendingData++;
|
||||
} else {
|
||||
curNoUpdate++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processing serial data
|
||||
#define ZBS_RX_WAIT_HEADER 0
|
||||
#define ZBS_RX_WAIT_SDA 1 // send data avail
|
||||
#define ZBS_RX_WAIT_CANCEL 2 // cancel traffic for mac
|
||||
#define ZBS_RX_WAIT_SCP 3 // set channel power
|
||||
#define ZBS_RX_WAIT_BLOCKDATA 4
|
||||
|
||||
bool isSame(uint8_t *in1, char *in2, int len) {
|
||||
bool flag = 1;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (in1[i] != in2[i]) flag = 0;
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
int blockPosition = 0;
|
||||
void processSerial(uint8_t lastchar) {
|
||||
static uint8_t cmdbuffer[4];
|
||||
static uint8_t RXState = 0;
|
||||
static uint8_t serialbuffer[48];
|
||||
static uint8_t *serialbufferp;
|
||||
static uint8_t bytesRemain = 0;
|
||||
static uint32_t lastSerial = 0;
|
||||
static uint32_t blockStartTime = 0;
|
||||
if ((RXState != ZBS_RX_WAIT_HEADER) && ((getMillis() - lastSerial) > 1000)) {
|
||||
RXState = ZBS_RX_WAIT_HEADER;
|
||||
ESP_LOGI(TAG, "UART Timeout");
|
||||
}
|
||||
lastSerial = getMillis();
|
||||
switch (RXState) {
|
||||
case ZBS_RX_WAIT_HEADER:
|
||||
// shift characters in
|
||||
for (uint8_t c = 0; c < 3; c++) {
|
||||
cmdbuffer[c] = cmdbuffer[c + 1];
|
||||
}
|
||||
cmdbuffer[3] = lastchar;
|
||||
|
||||
if (isSame(cmdbuffer + 1, ">D>", 3)) {
|
||||
pr("ACK>");
|
||||
blockStartTime = getMillis();
|
||||
ESP_LOGI(TAG, "Starting BlkData, %lu ms after request", blockStartTime - nextBlockAttempt );
|
||||
blockPosition = 0;
|
||||
RXState = ZBS_RX_WAIT_BLOCKDATA;
|
||||
}
|
||||
|
||||
if (isSame(cmdbuffer, "SDA>", 4)) {
|
||||
ESP_LOGI(TAG, "SDA In");
|
||||
RXState = ZBS_RX_WAIT_SDA;
|
||||
bytesRemain = sizeof(struct pendingData);
|
||||
serialbufferp = serialbuffer;
|
||||
break;
|
||||
}
|
||||
if (isSame(cmdbuffer, "CXD>", 4)) {
|
||||
ESP_LOGI(TAG, "CXD In");
|
||||
RXState = ZBS_RX_WAIT_CANCEL;
|
||||
bytesRemain = sizeof(struct pendingData);
|
||||
serialbufferp = serialbuffer;
|
||||
break;
|
||||
}
|
||||
if (isSame(cmdbuffer, "SCP>", 4)) {
|
||||
ESP_LOGI(TAG, "SCP In");
|
||||
RXState = ZBS_RX_WAIT_SCP;
|
||||
bytesRemain = sizeof(struct espSetChannelPower);
|
||||
serialbufferp = serialbuffer;
|
||||
break;
|
||||
}
|
||||
if (isSame(cmdbuffer, "NFO?", 4)) {
|
||||
pr("ACK>");
|
||||
ESP_LOGI(TAG, "NFO? In");
|
||||
espNotifyAPInfo();
|
||||
RXState = ZBS_RX_WAIT_HEADER;
|
||||
}
|
||||
if (isSame(cmdbuffer, "RDY?", 4)) {
|
||||
pr("ACK>");
|
||||
ESP_LOGI(TAG, "RDY? In");
|
||||
RXState = ZBS_RX_WAIT_HEADER;
|
||||
}
|
||||
if (isSame(cmdbuffer, "RSET", 4)) {
|
||||
pr("ACK>");
|
||||
ESP_LOGI(TAG, "RSET In");
|
||||
delay(100);
|
||||
// TODO RESET US HERE
|
||||
RXState = ZBS_RX_WAIT_HEADER;
|
||||
}
|
||||
if (isSame(cmdbuffer, "HSPD", 4)) {
|
||||
pr("ACK>");
|
||||
ESP_LOGI(TAG, "HSPD In, switching to 2000000");
|
||||
delay(100);
|
||||
uart_switch_speed(2000000);
|
||||
delay(100);
|
||||
highspeedSerial = true;
|
||||
pr("ACK>");
|
||||
RXState = ZBS_RX_WAIT_HEADER;
|
||||
}
|
||||
break;
|
||||
case ZBS_RX_WAIT_BLOCKDATA:
|
||||
blockbuffer[blockPosition++] = 0xAA ^ lastchar;
|
||||
if (blockPosition >= 4100) {
|
||||
ESP_LOGI(TAG, "Blockdata fully received in %lu ms, %lu ms after the request", getMillis() - blockStartTime, getMillis() - nextBlockAttempt);
|
||||
RXState = ZBS_RX_WAIT_HEADER;
|
||||
}
|
||||
break;
|
||||
|
||||
case ZBS_RX_WAIT_SDA:
|
||||
*serialbufferp = lastchar;
|
||||
serialbufferp++;
|
||||
bytesRemain--;
|
||||
if (bytesRemain == 0) {
|
||||
if (checkCRC(serialbuffer, sizeof(struct pendingData))) {
|
||||
struct pendingData *pd = (struct pendingData *) serialbuffer;
|
||||
int8_t slot = findSlotForMac(pd->targetMac);
|
||||
if (slot == -1) slot = findFreeSlot();
|
||||
if (slot != -1) {
|
||||
memcpy(&(pendingDataArr[slot]), serialbuffer, sizeof(struct pendingData));
|
||||
pr("ACK>");
|
||||
} else {
|
||||
pr("NOQ>");
|
||||
}
|
||||
} else {
|
||||
pr("NOK>");
|
||||
}
|
||||
|
||||
RXState = ZBS_RX_WAIT_HEADER;
|
||||
}
|
||||
break;
|
||||
case ZBS_RX_WAIT_CANCEL:
|
||||
*serialbufferp = lastchar;
|
||||
serialbufferp++;
|
||||
bytesRemain--;
|
||||
if (bytesRemain == 0) {
|
||||
if (checkCRC(serialbuffer, sizeof(struct pendingData))) {
|
||||
struct pendingData *pd = (struct pendingData *) serialbuffer;
|
||||
deleteAllPendingDataForMac((uint8_t *) &pd->targetMac);
|
||||
pr("ACK>");
|
||||
} else {
|
||||
pr("NOK>");
|
||||
}
|
||||
|
||||
RXState = ZBS_RX_WAIT_HEADER;
|
||||
}
|
||||
break;
|
||||
case ZBS_RX_WAIT_SCP:
|
||||
*serialbufferp = lastchar;
|
||||
serialbufferp++;
|
||||
bytesRemain--;
|
||||
if (bytesRemain == 0) {
|
||||
if (checkCRC(serialbuffer, sizeof(struct espSetChannelPower))) {
|
||||
struct espSetChannelPower *scp = (struct espSetChannelPower *) serialbuffer;
|
||||
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
if(curSubGhzChannel != scp->subghzchannel
|
||||
&& curSubGhzChannel != NO_SUBGHZ_CHANNEL)
|
||||
{
|
||||
curSubGhzChannel = scp->subghzchannel;
|
||||
ESP_LOGI(TAG,"Set SubGhz channel: %d",curSubGhzChannel);
|
||||
SubGig_radioSetChannel(scp->subghzchannel);
|
||||
if(scp->channel == 0) {
|
||||
// Not setting 802.15.4 channel
|
||||
goto SCPchannelFound;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
for (uint8_t c = 0; c < sizeof(channelList); c++) {
|
||||
if (channelList[c] == scp->channel) goto SCPchannelFound;
|
||||
}
|
||||
goto SCPfailed;
|
||||
SCPchannelFound:
|
||||
pr("ACK>");
|
||||
if (curChannel != scp->channel) {
|
||||
radioSetChannel(scp->channel);
|
||||
curChannel = scp->channel;
|
||||
}
|
||||
curPower = scp->power;
|
||||
radioSetTxPower(scp->power);
|
||||
ESP_LOGI(TAG, "Set channel: %d power: %d", curChannel, curPower);
|
||||
} else {
|
||||
SCPfailed:
|
||||
pr("NOK>");
|
||||
}
|
||||
RXState = ZBS_RX_WAIT_HEADER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// sending data to the ESP
|
||||
void espBlockRequest(const struct blockRequest *br, uint8_t *src) {
|
||||
struct espBlockRequest *ebr = (struct espBlockRequest *) blockbuffer;
|
||||
uartTx('R');
|
||||
uartTx('Q');
|
||||
uartTx('B');
|
||||
uartTx('>');
|
||||
memcpy(&(ebr->ver), &(br->ver), 8);
|
||||
memcpy(&(ebr->src), src, 8);
|
||||
ebr->blockId = br->blockId;
|
||||
addCRC(ebr, sizeof(struct espBlockRequest));
|
||||
for (uint8_t c = 0; c < sizeof(struct espBlockRequest); c++) {
|
||||
uartTx(((uint8_t *) ebr)[c]);
|
||||
}
|
||||
}
|
||||
void espNotifyAvailDataReq(const struct AvailDataReq *adr, const uint8_t *src) {
|
||||
uartTx('A');
|
||||
uartTx('D');
|
||||
uartTx('R');
|
||||
uartTx('>');
|
||||
|
||||
struct espAvailDataReq eadr = {0};
|
||||
memcpy((void *) eadr.src, (void *) src, 8);
|
||||
memcpy((void *) &eadr.adr, (void *) adr, sizeof(struct AvailDataReq));
|
||||
addCRC(&eadr, sizeof(struct espAvailDataReq));
|
||||
for (uint8_t c = 0; c < sizeof(struct espAvailDataReq); c++) {
|
||||
uartTx(((uint8_t *) &eadr)[c]);
|
||||
}
|
||||
}
|
||||
void espNotifyXferComplete(const uint8_t *src) {
|
||||
struct espXferComplete exfc;
|
||||
memcpy(&exfc.src, src, 8);
|
||||
uartTx('X');
|
||||
uartTx('F');
|
||||
uartTx('C');
|
||||
uartTx('>');
|
||||
addCRC(&exfc, sizeof(exfc));
|
||||
for (uint8_t c = 0; c < sizeof(exfc); c++) {
|
||||
uartTx(((uint8_t *) &exfc)[c]);
|
||||
}
|
||||
}
|
||||
void espNotifyTimeOut(const uint8_t *src) {
|
||||
struct espXferComplete exfc;
|
||||
memcpy(&exfc.src, src, 8);
|
||||
uartTx('X');
|
||||
uartTx('T');
|
||||
uartTx('O');
|
||||
uartTx('>');
|
||||
addCRC(&exfc, sizeof(exfc));
|
||||
for (uint8_t c = 0; c < sizeof(exfc); c++) {
|
||||
uartTx(((uint8_t *) &exfc)[c]);
|
||||
}
|
||||
}
|
||||
void espNotifyAPInfo() {
|
||||
pr("TYP>%02X", HW_TYPE);
|
||||
pr("VER>%04X", version);
|
||||
pr("MAC>%02X%02X", mSelfMac[0], mSelfMac[1]);
|
||||
pr("%02X%02X", mSelfMac[2], mSelfMac[3]);
|
||||
pr("%02X%02X", mSelfMac[4], mSelfMac[5]);
|
||||
pr("%02X%02X", mSelfMac[6], mSelfMac[7]);
|
||||
pr("ZCH>%02X", curChannel);
|
||||
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
pr("SCH>%03d",curSubGhzChannel);
|
||||
#endif
|
||||
pr("ZPW>%02X", curPower);
|
||||
countSlots();
|
||||
pr("PEN>%02X", curPendingData);
|
||||
pr("NOP>%02X", curNoUpdate);
|
||||
}
|
||||
|
||||
void espNotifyTagReturnData(uint8_t *src, uint8_t len) {
|
||||
struct tagReturnData *trd = (struct tagReturnData *)(radiorxbuffer + sizeof(struct MacFrameBcast) + 1); // oh how I'd love to pass this as an argument, but sdcc won't let me
|
||||
struct espTagReturnData *etrd = (struct espTagReturnData *)radiotxbuffer;
|
||||
|
||||
if (memcmp((void *) & trd->dataVer, lastTagReturn, 8) == 0) {
|
||||
return;
|
||||
} else {
|
||||
memcpy(lastTagReturn, &trd->dataVer, 8);
|
||||
}
|
||||
|
||||
memcpy(etrd->src, src, 8);
|
||||
etrd->len = len;
|
||||
memcpy(&etrd->returnData, trd, len);
|
||||
addCRC(etrd, len + 10);
|
||||
|
||||
uartTx('T');
|
||||
uartTx('R');
|
||||
uartTx('D');
|
||||
uartTx('>');
|
||||
for (uint8_t c = 0; c < len + 10; c++) {
|
||||
uartTx(((uint8_t *)etrd)[c]);
|
||||
}
|
||||
}
|
||||
|
||||
// process data from tag
|
||||
void processBlockRequest(const uint8_t *buffer, uint8_t forceBlockDownload) {
|
||||
struct MacFrameNormal *rxHeader = (struct MacFrameNormal *) buffer;
|
||||
struct blockRequest *blockReq = (struct blockRequest *) (buffer + sizeof(struct MacFrameNormal) + 1);
|
||||
if (!checkCRC(blockReq, sizeof(struct blockRequest))) return;
|
||||
|
||||
// check if we're already talking to this mac
|
||||
if (memcmp(rxHeader->src, lastBlockMac, 8) == 0) {
|
||||
lastBlockRequest = getMillis();
|
||||
} else {
|
||||
// we weren't talking to this mac, see if there was a transfer in progress from another mac, recently
|
||||
if ((getMillis() - lastBlockRequest) > CONCURRENT_REQUEST_DELAY) {
|
||||
// mark this mac as the new current mac we're talking to
|
||||
memcpy((void *) lastBlockMac, (void *) rxHeader->src, 8);
|
||||
lastBlockRequest = getMillis();
|
||||
} else {
|
||||
// we're talking to another mac, let this mac know we can't accomodate another request right now
|
||||
pr("BUSY!\n");
|
||||
sendCancelXfer(rxHeader->src);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check if we have data for this mac
|
||||
if (findSlotForMac(rxHeader->src) == -1) {
|
||||
// no data for this mac, politely tell it to fuck off
|
||||
sendCancelXfer(rxHeader->src);
|
||||
return;
|
||||
}
|
||||
|
||||
bool requestDataDownload = false;
|
||||
if ((blockReq->blockId != requestedData.blockId) || (blockReq->ver != requestedData.ver)) {
|
||||
// requested block isn't already in the buffer
|
||||
requestDataDownload = true;
|
||||
} else {
|
||||
// requested block is already in the buffer
|
||||
if (forceBlockDownload) {
|
||||
if ((getMillis() - nextBlockAttempt) > 380) {
|
||||
requestDataDownload = true;
|
||||
pr("FORCED\n");
|
||||
} else {
|
||||
pr("IGNORED\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy blockrequest into requested data
|
||||
memcpy(&requestedData, blockReq, sizeof(struct blockRequest));
|
||||
|
||||
struct MacFrameNormal *txHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
|
||||
struct blockRequestAck *blockRequestAck = (struct blockRequestAck *) (radiotxbuffer + sizeof(struct MacFrameNormal) + 2);
|
||||
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + sizeof(struct blockRequestAck) + RAW_PKT_PADDING;
|
||||
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_BLOCK_REQUEST_ACK;
|
||||
|
||||
if (blockStartTimer == 0) {
|
||||
if (requestDataDownload) {
|
||||
if (highspeedSerial == true) {
|
||||
blockRequestAck->pleaseWaitMs = 140;
|
||||
} else {
|
||||
blockRequestAck->pleaseWaitMs = 550;
|
||||
}
|
||||
} else {
|
||||
// block is already in buffer
|
||||
blockRequestAck->pleaseWaitMs = 30;
|
||||
}
|
||||
} else {
|
||||
blockRequestAck->pleaseWaitMs = 30;
|
||||
}
|
||||
blockStartTimer = getMillis() + blockRequestAck->pleaseWaitMs;
|
||||
|
||||
memcpy(txHeader->src, mSelfMac, 8);
|
||||
memcpy(txHeader->dst, rxHeader->src, 8);
|
||||
|
||||
txHeader->pan = rxHeader->pan;
|
||||
txHeader->fcs.frameType = 1;
|
||||
txHeader->fcs.panIdCompressed = 1;
|
||||
txHeader->fcs.destAddrType = 3;
|
||||
txHeader->fcs.srcAddrType = 3;
|
||||
txHeader->seq = seq++;
|
||||
|
||||
addCRC((void *) blockRequestAck, sizeof(struct blockRequestAck));
|
||||
|
||||
radioTx(radiotxbuffer);
|
||||
|
||||
// save the target for the blockdata
|
||||
memcpy(dstMac, rxHeader->src, 8);
|
||||
dstPan = rxHeader->pan;
|
||||
|
||||
if (requestDataDownload) {
|
||||
blockPosition = 0;
|
||||
espBlockRequest(&requestedData, rxHeader->src);
|
||||
nextBlockAttempt = getMillis();
|
||||
}
|
||||
}
|
||||
|
||||
void processAvailDataReq(uint8_t *buffer) {
|
||||
struct MacFrameBcast *rxHeader = (struct MacFrameBcast *) buffer;
|
||||
struct AvailDataReq *availDataReq = (struct AvailDataReq *) (buffer + sizeof(struct MacFrameBcast) + 1);
|
||||
|
||||
if (!checkCRC(availDataReq, sizeof(struct AvailDataReq))) return;
|
||||
|
||||
// prepare tx buffer to send a response
|
||||
memset(radiotxbuffer, 0, sizeof(struct MacFrameNormal) + sizeof(struct AvailDataInfo) + 2); // 120);
|
||||
struct MacFrameNormal *txHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
|
||||
struct AvailDataInfo *availDataInfo = (struct AvailDataInfo *) (radiotxbuffer + sizeof(struct MacFrameNormal) + 2);
|
||||
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + sizeof(struct AvailDataInfo) + RAW_PKT_PADDING;
|
||||
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_AVAIL_DATA_INFO;
|
||||
|
||||
// check to see if we have data available for this mac
|
||||
bool haveData = false;
|
||||
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
|
||||
if (pendingDataArr[c].attemptsLeft) {
|
||||
if (memcmp(pendingDataArr[c].targetMac, rxHeader->src, 8) == 0) {
|
||||
haveData = true;
|
||||
memcpy((void *) availDataInfo, &(pendingDataArr[c].availdatainfo), sizeof(struct AvailDataInfo));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// couldn't find data for this mac
|
||||
if (!haveData) availDataInfo->dataType = DATATYPE_NOUPDATE;
|
||||
|
||||
memcpy(txHeader->src, mSelfMac, 8);
|
||||
memcpy(txHeader->dst, rxHeader->src, 8);
|
||||
txHeader->pan = rxHeader->dstPan;
|
||||
txHeader->fcs.frameType = 1;
|
||||
txHeader->fcs.panIdCompressed = 1;
|
||||
txHeader->fcs.destAddrType = 3;
|
||||
txHeader->fcs.srcAddrType = 3;
|
||||
txHeader->seq = seq++;
|
||||
addCRC(availDataInfo, sizeof(struct AvailDataInfo));
|
||||
radioTx(radiotxbuffer);
|
||||
memset(lastAckMac, 0, 8); // reset lastAckMac, so we can record if we've received exactly one ack packet
|
||||
espNotifyAvailDataReq(availDataReq, rxHeader->src);
|
||||
}
|
||||
void processXferComplete(uint8_t *buffer) {
|
||||
struct MacFrameNormal *rxHeader = (struct MacFrameNormal *) buffer;
|
||||
sendXferCompleteAck(rxHeader->src);
|
||||
if (memcmp(lastAckMac, rxHeader->src, 8) != 0) {
|
||||
memcpy((void *) lastAckMac, (void *) rxHeader->src, 8);
|
||||
espNotifyXferComplete(rxHeader->src);
|
||||
int8_t slot = findSlotForMac(rxHeader->src);
|
||||
if (slot != -1) pendingDataArr[slot].attemptsLeft = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void processTagReturnData(uint8_t *buffer, uint8_t len) {
|
||||
struct MacFrameBcast *rxframe = (struct MacFrameBcast *)buffer;
|
||||
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *)(radiotxbuffer + 1);
|
||||
|
||||
if (!checkCRC((buffer + sizeof(struct MacFrameBcast) + 1), len - (sizeof(struct MacFrameBcast) + 1))) {
|
||||
return;
|
||||
}
|
||||
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_TAG_RETURN_DATA_ACK;
|
||||
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING;
|
||||
memcpy(frameHeader->src, mSelfMac, 8);
|
||||
memcpy(frameHeader->dst, rxframe->src, 8);
|
||||
radiotxbuffer[1] = 0x41; // fast way to set the appropriate bits
|
||||
radiotxbuffer[2] = 0xCC; // normal frame
|
||||
frameHeader->seq = seq++;
|
||||
frameHeader->pan = rxframe->srcPan;
|
||||
radioTx(radiotxbuffer);
|
||||
|
||||
espNotifyTagReturnData(rxframe->src, len - (sizeof(struct MacFrameBcast) + 1));
|
||||
}
|
||||
|
||||
// send block data to the tag
|
||||
void sendPart(uint8_t partNo) {
|
||||
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
|
||||
struct blockPart *blockPart = (struct blockPart *) (radiotxbuffer + sizeof(struct MacFrameNormal) + 2);
|
||||
memset(radiotxbuffer + 1, 0, sizeof(struct blockPart) + sizeof(struct MacFrameNormal));
|
||||
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_BLOCK_PART;
|
||||
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + sizeof(struct blockPart) + BLOCK_PART_DATA_SIZE + 1 + RAW_PKT_PADDING;
|
||||
memcpy(frameHeader->src, mSelfMac, 8);
|
||||
memcpy(frameHeader->dst, dstMac, 8);
|
||||
blockPart->blockId = requestedData.blockId;
|
||||
blockPart->blockPart = partNo;
|
||||
memcpy(&(blockPart->data), blockbuffer + (partNo * BLOCK_PART_DATA_SIZE), BLOCK_PART_DATA_SIZE);
|
||||
addCRC(blockPart, sizeof(struct blockPart) + BLOCK_PART_DATA_SIZE);
|
||||
frameHeader->fcs.frameType = 1;
|
||||
frameHeader->fcs.panIdCompressed = 1;
|
||||
frameHeader->fcs.destAddrType = 3;
|
||||
frameHeader->fcs.srcAddrType = 3;
|
||||
frameHeader->seq = seq++;
|
||||
frameHeader->pan = dstPan;
|
||||
radioTx(radiotxbuffer);
|
||||
}
|
||||
void sendBlockData() {
|
||||
if (getBlockDataLength() == 0) {
|
||||
pr("Invalid block request received, 0 parts..\n");
|
||||
requestedData.requestedParts[0] |= 0x01;
|
||||
}
|
||||
|
||||
pr("Sending parts:");
|
||||
for (uint8_t c = 0; (c < BLOCK_MAX_PARTS); c++) {
|
||||
if (c % 10 == 0) pr(" ");
|
||||
if (requestedData.requestedParts[c / 8] & (1 << (c % 8))) {
|
||||
pr("X");
|
||||
} else {
|
||||
pr(".");
|
||||
}
|
||||
}
|
||||
pr("\n");
|
||||
|
||||
uint8_t partNo = 0;
|
||||
while (partNo < BLOCK_MAX_PARTS) {
|
||||
for (uint8_t c = 0; (c < BLOCK_MAX_PARTS) && (partNo < BLOCK_MAX_PARTS); c++) {
|
||||
if (requestedData.requestedParts[c / 8] & (1 << (c % 8))) {
|
||||
sendPart(c);
|
||||
partNo++;
|
||||
}
|
||||
}
|
||||
if(dstPan == PROTO_PAN_ID_SUBGHZ) {
|
||||
// Don't send BLOCK_MAX_PARTS for subgig, it requests what it
|
||||
// can handle with its limited RAM
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void sendXferCompleteAck(uint8_t *dst) {
|
||||
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
|
||||
memset(radiotxbuffer + 1, 0, sizeof(struct blockPart) + sizeof(struct MacFrameNormal));
|
||||
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_XFER_COMPLETE_ACK;
|
||||
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING;
|
||||
memcpy(frameHeader->src, mSelfMac, 8);
|
||||
memcpy(frameHeader->dst, dst, 8);
|
||||
frameHeader->fcs.frameType = 1;
|
||||
frameHeader->fcs.panIdCompressed = 1;
|
||||
frameHeader->fcs.destAddrType = 3;
|
||||
frameHeader->fcs.srcAddrType = 3;
|
||||
frameHeader->seq = seq++;
|
||||
frameHeader->pan = dstPan;
|
||||
radioTx(radiotxbuffer);
|
||||
}
|
||||
void sendCancelXfer(uint8_t *dst) {
|
||||
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
|
||||
memset(radiotxbuffer + 1, 0, sizeof(struct blockPart) + sizeof(struct MacFrameNormal));
|
||||
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_CANCEL_XFER;
|
||||
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING;
|
||||
memcpy(frameHeader->src, mSelfMac, 8);
|
||||
memcpy(frameHeader->dst, dst, 8);
|
||||
frameHeader->fcs.frameType = 1;
|
||||
frameHeader->fcs.panIdCompressed = 1;
|
||||
frameHeader->fcs.destAddrType = 3;
|
||||
frameHeader->fcs.srcAddrType = 3;
|
||||
frameHeader->seq = seq++;
|
||||
frameHeader->pan = dstPan;
|
||||
radioTx(radiotxbuffer);
|
||||
}
|
||||
void sendPong(void *buf) {
|
||||
struct MacFrameBcast *rxframe = (struct MacFrameBcast *) buf;
|
||||
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
|
||||
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_PONG;
|
||||
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
if(rxframe->srcPan == PROTO_PAN_ID_SUBGHZ) {
|
||||
radiotxbuffer[sizeof(struct MacFrameNormal) + 2] = curSubGhzChannel;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
radiotxbuffer[sizeof(struct MacFrameNormal) + 2] = curChannel;
|
||||
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + 1 + RAW_PKT_PADDING;
|
||||
memcpy(frameHeader->src, mSelfMac, 8);
|
||||
memcpy(frameHeader->dst, rxframe->src, 8);
|
||||
radiotxbuffer[1] = 0x41; // fast way to set the appropriate bits
|
||||
radiotxbuffer[2] = 0xCC; // normal frame
|
||||
frameHeader->seq = seq++;
|
||||
frameHeader->pan = rxframe->srcPan;
|
||||
radioTx(radiotxbuffer);
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
esp_event_loop_create_default();
|
||||
|
||||
init_nvs();
|
||||
init_led();
|
||||
init_second_uart();
|
||||
|
||||
requestedData.blockId = 0xFF;
|
||||
// clear the array with pending information
|
||||
memset(pendingDataArr, 0, sizeof(pendingDataArr));
|
||||
|
||||
radio_init(curChannel);
|
||||
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
if(SubGig_radio_init(curSubGhzChannel)) {
|
||||
// Ether we don't have a cc1101 or it's not working
|
||||
curSubGhzChannel = NO_SUBGHZ_CHANNEL;
|
||||
ESP_LOGI(TAG,"CC1101 NOT detected.");
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(TAG,"CC1101 detected.");
|
||||
}
|
||||
#endif
|
||||
radioSetTxPower(10);
|
||||
|
||||
pr("RES>");
|
||||
pr("RDY>");
|
||||
ESP_LOGI(TAG, "H2 ready!");
|
||||
|
||||
housekeepingTimer = getMillis();
|
||||
while (1) {
|
||||
while ((getMillis() - housekeepingTimer) < ((1000 * HOUSEKEEPING_INTERVAL) - 100)) {
|
||||
int8_t ret = commsRxUnencrypted(radiorxbuffer);
|
||||
if (ret > 1) {
|
||||
led_flash(0);
|
||||
// received a packet, lets see what it is
|
||||
switch (getPacketType(radiorxbuffer)) {
|
||||
case PKT_AVAIL_DATA_REQ:
|
||||
if (ret == 28) {
|
||||
// old version of the AvailDataReq struct, set all the new fields to zero, so it will pass the CRC
|
||||
memset(radiorxbuffer + 1 + sizeof(struct MacFrameBcast) + sizeof(struct oldAvailDataReq), 0,
|
||||
sizeof(struct AvailDataReq) - sizeof(struct oldAvailDataReq) + 2);
|
||||
processAvailDataReq(radiorxbuffer);
|
||||
} else if (ret == 40) {
|
||||
// new version of the AvailDataReq struct
|
||||
processAvailDataReq(radiorxbuffer);
|
||||
}
|
||||
break;
|
||||
case PKT_BLOCK_REQUEST:
|
||||
processBlockRequest(radiorxbuffer, 1);
|
||||
break;
|
||||
case PKT_BLOCK_PARTIAL_REQUEST:
|
||||
processBlockRequest(radiorxbuffer, 0);
|
||||
break;
|
||||
case PKT_XFER_COMPLETE:
|
||||
processXferComplete(radiorxbuffer);
|
||||
break;
|
||||
case PKT_PING:
|
||||
sendPong(radiorxbuffer);
|
||||
break;
|
||||
case PKT_AVAIL_DATA_SHORTREQ:
|
||||
// a short AvailDataReq is basically a very short (1 byte payload) packet that requires little preparation on the tx side, for optimal
|
||||
// battery use bytes of the struct are set 0, so it passes the checksum test, and the ESP32 can detect that no interesting payload is
|
||||
// sent
|
||||
if (ret == 18) {
|
||||
memset(radiorxbuffer + 1 + sizeof(struct MacFrameBcast), 0, sizeof(struct AvailDataReq) + 2);
|
||||
processAvailDataReq(radiorxbuffer);
|
||||
}
|
||||
break;
|
||||
case PKT_TAG_RETURN_DATA:
|
||||
processTagReturnData(radiorxbuffer, ret);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "t=%02X" , getPacketType(radiorxbuffer));
|
||||
break;
|
||||
}
|
||||
} else if (blockStartTimer == 0) {
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
uint8_t curr_char;
|
||||
while (getRxCharSecond(&curr_char)) processSerial(curr_char);
|
||||
|
||||
if (blockStartTimer) {
|
||||
if (getMillis() > blockStartTimer) {
|
||||
sendBlockData();
|
||||
blockStartTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memset(&lastTagReturn, 0, 8);
|
||||
for (uint8_t cCount = 0; cCount < MAX_PENDING_MACS; cCount++) {
|
||||
if (pendingDataArr[cCount].attemptsLeft == 1) {
|
||||
if (pendingDataArr[cCount].availdatainfo.dataType != DATATYPE_NOUPDATE) {
|
||||
espNotifyTimeOut(pendingDataArr[cCount].targetMac);
|
||||
}
|
||||
pendingDataArr[cCount].attemptsLeft = 0;
|
||||
} else if (pendingDataArr[cCount].attemptsLeft > 1) {
|
||||
pendingDataArr[cCount].attemptsLeft--;
|
||||
if (pendingDataArr[cCount].availdatainfo.nextCheckIn) pendingDataArr[cCount].availdatainfo.nextCheckIn--;
|
||||
}
|
||||
}
|
||||
housekeepingTimer = getMillis();
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
#pragma once
|
||||
@@ -1,194 +0,0 @@
|
||||
#ifndef _PROTO_H_
|
||||
#define _PROTO_H_
|
||||
#include <stdint.h>
|
||||
|
||||
#define LED1 22
|
||||
#define LED2 25
|
||||
|
||||
#define PROTO_PAN_ID (0x4447) // PAN ID compression shall be used
|
||||
#define PROTO_PAN_ID_SUBGHZ (0x1337) // PAN ID compression shall be used
|
||||
|
||||
#define RADIO_MAX_PACKET_LEN (125) // useful payload, not including the crc
|
||||
|
||||
#define ADDR_MODE_NONE (0)
|
||||
#define ADDR_MODE_SHORT (2)
|
||||
#define ADDR_MODE_LONG (3)
|
||||
|
||||
#define FRAME_TYPE_BEACON (0)
|
||||
#define FRAME_TYPE_DATA (1)
|
||||
#define FRAME_TYPE_ACK (2)
|
||||
#define FRAME_TYPE_MAC_CMD (3)
|
||||
|
||||
#define SHORT_MAC_UNUSED (0x10000000UL) // for radioRxFilterCfg's myShortMac
|
||||
|
||||
struct MacFcs {
|
||||
uint8_t frameType : 3;
|
||||
uint8_t secure : 1;
|
||||
uint8_t framePending : 1;
|
||||
uint8_t ackReqd : 1;
|
||||
uint8_t panIdCompressed : 1;
|
||||
uint8_t rfu1 : 1;
|
||||
uint8_t rfu2 : 2;
|
||||
uint8_t destAddrType : 2;
|
||||
uint8_t frameVer : 2;
|
||||
uint8_t srcAddrType : 2;
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct MacFrameFromMaster {
|
||||
struct MacFcs fcs;
|
||||
uint8_t seq;
|
||||
uint16_t pan;
|
||||
uint8_t dst[8];
|
||||
uint16_t from;
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct MacFrameNormal {
|
||||
struct MacFcs fcs;
|
||||
uint8_t seq;
|
||||
uint16_t pan;
|
||||
uint8_t dst[8];
|
||||
uint8_t src[8];
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct MacFrameBcast {
|
||||
struct MacFcs fcs;
|
||||
uint8_t seq;
|
||||
uint16_t dstPan;
|
||||
uint16_t dstAddr;
|
||||
uint16_t srcPan;
|
||||
uint8_t src[8];
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
#define PKT_TAG_RETURN_DATA 0xE1
|
||||
#define PKT_TAG_RETURN_DATA_ACK 0xE2
|
||||
#define PKT_AVAIL_DATA_SHORTREQ 0xE3
|
||||
#define PKT_AVAIL_DATA_REQ 0xE5
|
||||
#define PKT_AVAIL_DATA_INFO 0xE6
|
||||
#define PKT_BLOCK_PARTIAL_REQUEST 0xE7
|
||||
#define PKT_BLOCK_REQUEST_ACK 0xE9
|
||||
#define PKT_BLOCK_REQUEST 0xE4
|
||||
#define PKT_BLOCK_PART 0xE8
|
||||
#define PKT_XFER_COMPLETE 0xEA
|
||||
#define PKT_XFER_COMPLETE_ACK 0xEB
|
||||
#define PKT_CANCEL_XFER 0xEC
|
||||
#define PKT_PING 0xED
|
||||
#define PKT_PONG 0xEE
|
||||
|
||||
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];
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct oldAvailDataReq {
|
||||
uint8_t checksum;
|
||||
uint8_t lastPacketLQI;
|
||||
int8_t lastPacketRSSI;
|
||||
int8_t temperature;
|
||||
uint16_t batteryMv;
|
||||
uint8_t hwType;
|
||||
uint8_t wakeupReason;
|
||||
uint8_t capabilities;
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct AvailDataInfo {
|
||||
uint8_t checksum;
|
||||
uint64_t dataVer; // MD5 of potential traffic
|
||||
uint32_t dataSize;
|
||||
uint8_t dataType;
|
||||
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
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct pendingData {
|
||||
struct AvailDataInfo availdatainfo;
|
||||
uint16_t attemptsLeft;
|
||||
uint8_t targetMac[8];
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct blockPart {
|
||||
uint8_t checksum;
|
||||
uint8_t blockId;
|
||||
uint8_t blockPart;
|
||||
uint8_t data[];
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct blockData {
|
||||
uint16_t size;
|
||||
uint16_t checksum;
|
||||
uint8_t data[];
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
#define TAG_RETURN_DATA_SIZE 90
|
||||
|
||||
struct tagReturnData {
|
||||
uint8_t checksum;
|
||||
uint8_t partId;
|
||||
uint64_t dataVer;
|
||||
uint8_t dataType;
|
||||
uint8_t data[TAG_RETURN_DATA_SIZE];
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
#define BLOCK_PART_DATA_SIZE 99
|
||||
#define BLOCK_MAX_PARTS 42
|
||||
#define BLOCK_DATA_SIZE 4096UL
|
||||
#define BLOCK_XFER_BUFFER_SIZE BLOCK_DATA_SIZE + sizeof(struct blockData)
|
||||
#define BLOCK_REQ_PARTS_BYTES 6
|
||||
|
||||
struct blockRequest {
|
||||
uint8_t checksum;
|
||||
uint64_t ver;
|
||||
uint8_t blockId;
|
||||
uint8_t type;
|
||||
uint8_t requestedParts[BLOCK_REQ_PARTS_BYTES];
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct blockRequestAck {
|
||||
uint8_t checksum;
|
||||
uint16_t pleaseWaitMs;
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct espBlockRequest {
|
||||
uint8_t checksum;
|
||||
uint64_t ver;
|
||||
uint8_t blockId;
|
||||
uint8_t src[8];
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct espXferComplete {
|
||||
uint8_t checksum;
|
||||
uint8_t src[8];
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct espAvailDataReq {
|
||||
uint8_t checksum;
|
||||
uint8_t src[8];
|
||||
struct AvailDataReq adr;
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct espSetChannelPower {
|
||||
uint8_t checksum;
|
||||
uint8_t channel;
|
||||
uint8_t power;
|
||||
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
uint8_t subghzchannel;
|
||||
#endif
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
struct espTagReturnData {
|
||||
uint8_t checksum;
|
||||
uint8_t src[8];
|
||||
uint8_t len;
|
||||
struct tagReturnData returnData;
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
#endif
|
||||
@@ -1,150 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include "radio.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_ieee802154.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_phy_init.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "led.h"
|
||||
#include "main.h"
|
||||
#include "proto.h"
|
||||
#include "sdkconfig.h"
|
||||
// if you get an error about soc/lp_uart_reg.h not being found,
|
||||
// you didn't choose the right build target. :-)
|
||||
//#include "soc/lp_uart_reg.h"
|
||||
#include "soc/uart_struct.h"
|
||||
#include "utils.h"
|
||||
#include <esp_mac.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include "SubGigRadio.h"
|
||||
|
||||
|
||||
static const char *TAG = "RADIO";
|
||||
|
||||
uint8_t mSelfMac[8];
|
||||
volatile uint8_t isInTransmit = 0;
|
||||
QueueHandle_t packet_buffer = NULL;
|
||||
void esp_ieee802154_receive_done(uint8_t *frame, esp_ieee802154_frame_info_t *frame_info) {
|
||||
ESP_EARLY_LOGI(TAG, "RX %d", frame[0]);
|
||||
BaseType_t xHigherPriorityTaskWoken;
|
||||
static uint8_t inner_rxPKT[130];
|
||||
memcpy(inner_rxPKT, &frame[0], frame[0] + 1);
|
||||
xQueueSendFromISR(packet_buffer, (void *)&inner_rxPKT, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR_ARG(xHigherPriorityTaskWoken);
|
||||
esp_ieee802154_receive_sfd_done();
|
||||
}
|
||||
|
||||
void esp_ieee802154_transmit_failed(const uint8_t *frame, esp_ieee802154_tx_error_t error) {
|
||||
isInTransmit = 0;
|
||||
ESP_EARLY_LOGE(TAG, "TX Err: %d", error);
|
||||
}
|
||||
|
||||
void esp_ieee802154_transmit_done(const uint8_t *frame, const uint8_t *ack, esp_ieee802154_frame_info_t *ack_frame_info) {
|
||||
isInTransmit = 0;
|
||||
ESP_EARLY_LOGI(TAG, "TX %d", frame[0]);
|
||||
esp_ieee802154_receive_sfd_done();
|
||||
}
|
||||
static bool zigbee_is_enabled = false;
|
||||
void radio_init(uint8_t ch) {
|
||||
if (packet_buffer == NULL) packet_buffer = xQueueCreate(32, 130);
|
||||
|
||||
// this will trigger a "IEEE802154 MAC sleep init failed" when called a second time, but it works
|
||||
if(zigbee_is_enabled)
|
||||
{
|
||||
zigbee_is_enabled = false;
|
||||
esp_ieee802154_disable();
|
||||
}
|
||||
zigbee_is_enabled = true;
|
||||
esp_ieee802154_enable();
|
||||
esp_ieee802154_set_channel(ch);
|
||||
// esp_ieee802154_set_txpower(int8_t power);
|
||||
esp_ieee802154_set_panid(PROTO_PAN_ID);
|
||||
esp_ieee802154_set_promiscuous(false);
|
||||
esp_ieee802154_set_coordinator(false);
|
||||
esp_ieee802154_set_pending_mode(ESP_IEEE802154_AUTO_PENDING_ZIGBEE);
|
||||
|
||||
// esp_ieee802154_set_extended_address needs the MAC in reversed byte order
|
||||
esp_read_mac(mSelfMac, ESP_MAC_IEEE802154);
|
||||
uint8_t eui64_rev[8] = {0};
|
||||
for (int i = 0; i < 8; i++) {
|
||||
eui64_rev[7 - i] = mSelfMac[i];
|
||||
}
|
||||
esp_ieee802154_set_extended_address(eui64_rev);
|
||||
esp_ieee802154_get_extended_address(mSelfMac);
|
||||
|
||||
esp_ieee802154_set_short_address(0xFFFE);
|
||||
esp_ieee802154_set_rx_when_idle(true);
|
||||
esp_ieee802154_receive();
|
||||
|
||||
led_flash(1);
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
led_flash(0);
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
led_flash(1);
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
led_flash(0);
|
||||
|
||||
ESP_LOGI(TAG, "Receiver ready, panId=0x%04x, channel=%d, long=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, short=%04x",
|
||||
esp_ieee802154_get_panid(), esp_ieee802154_get_channel(),
|
||||
mSelfMac[0], mSelfMac[1], mSelfMac[2], mSelfMac[3],
|
||||
mSelfMac[4], mSelfMac[5], mSelfMac[6], mSelfMac[7],
|
||||
esp_ieee802154_get_short_address());
|
||||
}
|
||||
|
||||
// uint32_t lastZbTx = 0;
|
||||
bool radioTx(uint8_t *packet) {
|
||||
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
// The subghz driver uses DMA
|
||||
static DMA_ATTR uint8_t txPKT[130];
|
||||
#else
|
||||
static uint8_t txPKT[130];
|
||||
#endif
|
||||
led_flash(1);
|
||||
// while (getMillis() - lastZbTx < 6) {
|
||||
// }
|
||||
// lastZbTx = getMillis();
|
||||
memcpy(txPKT, packet, packet[0]);
|
||||
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
struct MacFrameNormal *txHeader = (struct MacFrameNormal *) (packet + 1);
|
||||
|
||||
if(txHeader->pan == PROTO_PAN_ID_SUBGHZ) {
|
||||
return SubGig_radioTx(packet);
|
||||
}
|
||||
#endif
|
||||
while (isInTransmit) {
|
||||
}
|
||||
isInTransmit = 1;
|
||||
esp_ieee802154_transmit(txPKT, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
void radioSetChannel(uint8_t ch) {
|
||||
radio_init(ch);
|
||||
}
|
||||
|
||||
void radioSetTxPower(uint8_t power) {}
|
||||
|
||||
int8_t commsRxUnencrypted(uint8_t *data) {
|
||||
static uint8_t inner_rxPKT_out[130];
|
||||
if (xQueueReceive(packet_buffer, (void *)&inner_rxPKT_out, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
memcpy(data, &inner_rxPKT_out[1], inner_rxPKT_out[0] + 1);
|
||||
return inner_rxPKT_out[0] - 2;
|
||||
}
|
||||
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
|
||||
if(gSubGigData.Enabled) {
|
||||
int8_t Ret = SubGig_commsRxUnencrypted(data);
|
||||
if(Ret > 0) {
|
||||
return Ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define RAW_PKT_PADDING 2
|
||||
extern uint8_t mSelfMac[8];
|
||||
|
||||
void radio_init(uint8_t ch);
|
||||
bool radioTx(uint8_t *packet);
|
||||
void radioSetChannel(uint8_t ch);
|
||||
void radioSetTxPower(uint8_t power);
|
||||
int8_t commsRxUnencrypted(uint8_t *data);
|
||||
|
||||
#ifdef SUBGIG_SUPPORT
|
||||
void SubGig_radio_init(uint8_t ch);
|
||||
bool SubGig_radioTx(uint8_t *packet);
|
||||
void SubGig_radioSetChannel(uint8_t ch);
|
||||
void SubGig_radioSetTxPower(uint8_t power);
|
||||
int8_t SubGig_commsRxUnencrypted(uint8_t *data);
|
||||
#endif
|
||||
@@ -1,116 +0,0 @@
|
||||
#include <esp_mac.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_ieee802154.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_phy_init.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "main.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "proto.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/uart_struct.h"
|
||||
//#include "soc/lp_uart_reg.h"
|
||||
#include "second_uart.h"
|
||||
|
||||
//static const char *TAG = "SECOND_UART";
|
||||
|
||||
#define BUF_SIZE (1024)
|
||||
#define RD_BUF_SIZE (BUF_SIZE)
|
||||
static QueueHandle_t uart0_queue;
|
||||
|
||||
#define MAX_BUFF_POS 8000
|
||||
volatile int curr_buff_pos = 0;
|
||||
volatile int worked_buff_pos = 0;
|
||||
volatile uint8_t buff_pos[MAX_BUFF_POS + 5];
|
||||
|
||||
static void uart_event_task(void *pvParameters);
|
||||
void init_second_uart() {
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = 115200,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
ESP_ERROR_CHECK(uart_driver_install(1, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0));
|
||||
ESP_ERROR_CHECK(uart_param_config(1, &uart_config));
|
||||
ESP_ERROR_CHECK(uart_set_pin(1, CONFIG_OEPL_HARDWARE_UART_TX, CONFIG_OEPL_HARDWARE_UART_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
|
||||
xTaskCreate(uart_event_task, "uart_event_task", 16384, NULL, 12, NULL);
|
||||
}
|
||||
|
||||
void uart_switch_speed(int baudrate) {
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = baudrate,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
ESP_ERROR_CHECK(uart_param_config(1, &uart_config));
|
||||
}
|
||||
|
||||
void uartTx(uint8_t data) { uart_write_bytes(1, (const char *) &data, 1); }
|
||||
|
||||
|
||||
bool getRxCharSecond(uint8_t *newChar) {
|
||||
if (curr_buff_pos != worked_buff_pos) {
|
||||
*newChar = buff_pos[worked_buff_pos];
|
||||
worked_buff_pos++;
|
||||
worked_buff_pos %= MAX_BUFF_POS;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void uart_event_task(void *pvParameters) {
|
||||
uart_event_t event;
|
||||
uint8_t *dtmp = (uint8_t *) malloc(RD_BUF_SIZE);
|
||||
for (;;) {
|
||||
if (xQueueReceive(uart0_queue, (void *) &event, (TickType_t) portMAX_DELAY)) {
|
||||
bzero(dtmp, RD_BUF_SIZE);
|
||||
switch (event.type) {
|
||||
case UART_DATA:
|
||||
uart_read_bytes(1, dtmp, event.size, portMAX_DELAY);
|
||||
for (int i = 0; i < event.size; i++) {
|
||||
buff_pos[curr_buff_pos] = dtmp[i];
|
||||
curr_buff_pos++;
|
||||
curr_buff_pos %= MAX_BUFF_POS;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// ESP_LOGI(TAG, "uart event type: %d", event.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(dtmp);
|
||||
dtmp = NULL;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void uart_printf(const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
char buffer[128];
|
||||
int len = vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
if (len > 0) {
|
||||
uart_write_bytes(1, buffer, len);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
void init_second_uart();
|
||||
void uart_switch_speed(int baudrate);
|
||||
|
||||
void uartTx(uint8_t data);
|
||||
bool getRxCharSecond(uint8_t *newChar);
|
||||
|
||||
void uart_printf(const char *format, ...);
|
||||
|
||||
#define pr uart_printf
|
||||
|
||||
|
||||
|
||||
#define CONFIG_OEPL_HARDWARE_UART_TX 24
|
||||
#define CONFIG_OEPL_HARDWARE_UART_RX 23
|
||||
@@ -1,36 +0,0 @@
|
||||
#include <esp_mac.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_ieee802154.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_phy_init.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "main.h"
|
||||
#include "proto.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/uart_struct.h"
|
||||
//#include "soc/lp_uart_reg.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
void delay(int ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }
|
||||
|
||||
uint32_t getMillis() { return (uint32_t) (esp_timer_get_time() / 1000); }
|
||||
|
||||
void init_nvs()
|
||||
{
|
||||
// Initialize NVS
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
void delay(int ms);
|
||||
uint32_t getMillis();
|
||||
void init_nvs();
|
||||
@@ -1,8 +1,16 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
|
||||
#
|
||||
CONFIG_IDF_TARGET="esp32h2"
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_OEPL_HARDWARE_PROFILE_LILYGO=y
|
||||
CONFIG_OEPL_SUBGIG_SUPPORT=y
|
||||
CONFIG_MISO_GPIO=25
|
||||
CONFIG_SCK_GPIO=3
|
||||
CONFIG_MOSI_GPIO=11
|
||||
CONFIG_CSN_GPIO=8
|
||||
CONFIG_GDO0_GPIO=2
|
||||
CONFIG_GDO2_GPIO=22
|
||||
|
||||
BIN
ESP32_AP-Flasher/data/fonts/tahoma11.vlw
Normal file
BIN
ESP32_AP-Flasher/data/fonts/tahoma11.vlw
Normal file
Binary file not shown.
Binary file not shown.
@@ -10,54 +10,7 @@
|
||||
"/www/painter.js",
|
||||
"/www/setup.html",
|
||||
"/www/setup.js",
|
||||
"/www/upload-demo.html",
|
||||
"/fonts/weathericons30.vlw",
|
||||
"/fonts/weathericons70.vlw",
|
||||
"/fonts/weathericons78.vlw",
|
||||
"/fonts/calibrib120.vlw",
|
||||
"/fonts/calibrib150.vlw",
|
||||
"/fonts/calibrib50.vlw",
|
||||
"/fonts/calibrib60.vlw",
|
||||
"/fonts/BellCent10.vlw",
|
||||
"/tagtypes/00.json",
|
||||
"/tagtypes/01.json",
|
||||
"/tagtypes/02.json",
|
||||
"/tagtypes/05.json",
|
||||
"/tagtypes/11.json",
|
||||
"/tagtypes/21.json",
|
||||
"/tagtypes/22.json",
|
||||
"/tagtypes/26.json",
|
||||
"/tagtypes/27.json",
|
||||
"/tagtypes/2E.json",
|
||||
"/tagtypes/2F.json",
|
||||
"/tagtypes/30.json",
|
||||
"/tagtypes/31.json",
|
||||
"/tagtypes/32.json",
|
||||
"/tagtypes/33.json",
|
||||
"/tagtypes/34.json",
|
||||
"/tagtypes/35.json",
|
||||
"/tagtypes/36.json",
|
||||
"/tagtypes/40.json",
|
||||
"/tagtypes/41.json",
|
||||
"/tagtypes/42.json",
|
||||
"/tagtypes/43.json",
|
||||
"/tagtypes/55.json",
|
||||
"/tagtypes/60.json",
|
||||
"/tagtypes/61.json",
|
||||
"/tagtypes/62.json",
|
||||
"/tagtypes/80.json",
|
||||
"/tagtypes/81.json",
|
||||
"/tagtypes/82.json",
|
||||
"/tagtypes/83.json",
|
||||
"/tagtypes/B0.json",
|
||||
"/tagtypes/B1.json",
|
||||
"/tagtypes/B2.json",
|
||||
"/tagtypes/B3.json",
|
||||
"/tagtypes/B5.json",
|
||||
"/tagtypes/BD.json",
|
||||
"/tagtypes/BE.json",
|
||||
"/tagtypes/E0.json",
|
||||
"/tagtypes/E1.json",
|
||||
"/tagtypes/F0.json"
|
||||
"/www/flash.js",
|
||||
"/www/upload-demo.html"
|
||||
]
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
ESP32_AP-Flasher/data/www/g5decoder.js.gz
Normal file
BIN
ESP32_AP-Flasher/data/www/g5decoder.js.gz
Normal file
Binary file not shown.
Binary file not shown.
BIN
ESP32_AP-Flasher/data/www/jsontemplate-demo-v2.html.gz
Normal file
BIN
ESP32_AP-Flasher/data/www/jsontemplate-demo-v2.html.gz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
7
ESP32_AP-Flasher/esp32_sdcard_only.csv
Normal file
7
ESP32_AP-Flasher/esp32_sdcard_only.csv
Normal 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, 0x1E0000,
|
||||
app1, app, ota_1, 0x1F0000,0x1E0000,
|
||||
spiffs, data, spiffs, 0x3D0000,0x20000,
|
||||
coredump, data, coredump,0x3F0000,0x10000,
|
||||
|
@@ -4,9 +4,9 @@
|
||||
|
||||
class SPIFFSEditor: public AsyncWebHandler {
|
||||
private:
|
||||
fs::FS _fs;
|
||||
mutable fs::FS _fs;
|
||||
String _username;
|
||||
String _password;
|
||||
String _password;
|
||||
bool _authenticated;
|
||||
uint32_t _startTime;
|
||||
public:
|
||||
@@ -15,10 +15,10 @@ class SPIFFSEditor: public AsyncWebHandler {
|
||||
#else
|
||||
SPIFFSEditor(const String& username=String(), const String& password=String(), const fs::FS& fs=SPIFFS);
|
||||
#endif
|
||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
||||
virtual bool canHandle(AsyncWebServerRequest* request) const 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;}
|
||||
virtual bool isRequestHandlerTrivial() const override final {return false;}
|
||||
virtual String listFilesRecursively(String path, bool recursive = false);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,5 +6,6 @@ uint8_t gicToOEPLtype(uint8_t gicType);
|
||||
bool BLE_filter_add_device(BLEAdvertisedDevice advertisedDevice);
|
||||
bool BLE_is_image_pending(uint8_t address[8]);
|
||||
uint32_t compress_image(uint8_t address[8], uint8_t* buffer, uint32_t max_len);
|
||||
uint32_t get_ATC_BLE_OEPL_image(uint8_t address[8], uint8_t* buffer, uint32_t max_len, uint8_t* dataType, uint8_t* dataTypeArgument, uint16_t* nextCheckIn);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <Arduino.h>
|
||||
#include <LittleFS.h>
|
||||
#include <TFT_eSPI.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "makeimage.h"
|
||||
#include "tag_db.h"
|
||||
@@ -19,9 +20,9 @@ void checkVars();
|
||||
void drawNew(const uint8_t mac[8], tagRecord *&taginfo);
|
||||
bool updateTagImage(String &filename, const uint8_t *dst, uint16_t nextCheckin, tagRecord *&taginfo, imgParam &imageParams);
|
||||
void drawString(TFT_eSprite &spr, String content, int16_t posx, int16_t posy, String font, byte align = 0, uint16_t color = TFT_BLACK, uint16_t size = 30, uint16_t bgcolor = TFT_WHITE);
|
||||
void drawTextBox(TFT_eSprite &spr, String &content, int16_t &posx, int16_t &posy, int16_t boxwidth, int16_t boxheight, String font, uint16_t color = TFT_BLACK, uint16_t bgcolor = TFT_WHITE, float lineheight = 1);
|
||||
void drawTextBox(TFT_eSprite &spr, String &content, int16_t &posx, int16_t &posy, int16_t boxwidth, int16_t boxheight, String font, uint16_t color = TFT_BLACK, uint16_t bgcolor = TFT_WHITE, float lineheight = 1, byte align = TL_DATUM);
|
||||
void initSprite(TFT_eSprite &spr, int w, int h, imgParam &imageParams);
|
||||
void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams);
|
||||
void drawDate(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams);
|
||||
void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord *&taginfo, imgParam &imageParams);
|
||||
void drawWeather(String &filename, JsonObject &cfgobj, const tagRecord *taginfo, imgParam &imageParams);
|
||||
void drawForecast(String &filename, JsonObject &cfgobj, const tagRecord *taginfo, imgParam &imageParams);
|
||||
@@ -48,4 +49,5 @@ void getLocation(JsonObject &cfgobj);
|
||||
void prepareNFCReq(const uint8_t *dst, const char *url);
|
||||
void prepareLUTreq(const uint8_t *dst, const String &input);
|
||||
void prepareConfigFile(const uint8_t *dst, const JsonObject &config);
|
||||
void prepareTIME_RAW(const uint8_t *dst, time_t now);
|
||||
void getTemplate(JsonDocument &json, const uint8_t id, const uint8_t hwtype);
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
#include <Arduino.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
bool doC6flash(uint8_t doDownload);
|
||||
#if defined HAS_H2
|
||||
#define SHORT_CHIP_NAME "H2"
|
||||
#define OTA_BIN_DIR "ESP32-H2"
|
||||
#define ESP_CHIP_TYPE ESP32H2_CHIP
|
||||
#elif defined HAS_TSLR
|
||||
#define SHORT_CHIP_NAME "TSLR"
|
||||
#elif defined HAS_ELECROW_C6
|
||||
#define SHORT_CHIP_NAME "ELECROW_C6"
|
||||
#define OTA_BIN_DIR "ESP32-C6"
|
||||
#define ESP_CHIP_TYPE ESP32C6_CHIP
|
||||
#elif defined C6_OTA_FLASHING
|
||||
#define SHORT_CHIP_NAME "C6"
|
||||
#define OTA_BIN_DIR "ESP32-C6"
|
||||
#define ESP_CHIP_TYPE ESP32C6_CHIP
|
||||
#endif
|
||||
|
||||
bool FlashC6_H2(const char *Url);
|
||||
|
||||
@@ -93,6 +93,46 @@ extern Arduino_RGB_Display *gfx;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAS_4inch_TPANEL
|
||||
|
||||
#define LV_ATTRIBUTE_TICK_INC IRAM_ATTR
|
||||
#define TOUCH_MODULES_CST_MUTUAL
|
||||
|
||||
// esp32-4848S040
|
||||
#define LCD_WIDTH 480
|
||||
#define LCD_HEIGHT 480
|
||||
#define LCD_VSYNC 17
|
||||
#define LCD_HSYNC 16
|
||||
#define LCD_PCLK 21
|
||||
#define LCD_R0 0
|
||||
#define LCD_R1 11
|
||||
#define LCD_R2 12
|
||||
#define LCD_R3 13
|
||||
#define LCD_R4 14
|
||||
#define LCD_G0 8
|
||||
#define LCD_G1 20
|
||||
#define LCD_G2 3
|
||||
#define LCD_G3 46
|
||||
#define LCD_G4 9
|
||||
#define LCD_G5 10
|
||||
#define LCD_B0 15
|
||||
#define LCD_B1 4
|
||||
#define LCD_B2 5
|
||||
#define LCD_B3 6
|
||||
#define LCD_B4 7
|
||||
#define LCD_BL 38
|
||||
#define LCD_DE 18
|
||||
|
||||
#define SPI_LCD_CS 39
|
||||
#define SPI_LCD_SCLK 48
|
||||
#define SPI_LCD_MOSI 47
|
||||
|
||||
#include "Arduino_GFX_Library.h"
|
||||
|
||||
extern Arduino_RGB_Display *gfx;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAS_TFT
|
||||
|
||||
extern TFT_eSPI tft2;
|
||||
|
||||
@@ -26,6 +26,7 @@ const uint8_t PROGMEM gamma8[] = {
|
||||
void ledTask(void* parameter);
|
||||
void setBrightness(int brightness);
|
||||
void updateBrightnessFromConfig();
|
||||
void ledcSet(uint8_t channel, uint8_t brightness);
|
||||
|
||||
#ifdef HAS_RGB_LED
|
||||
extern CRGB rgbIdleColor;
|
||||
@@ -34,7 +35,6 @@ void shortBlink(CRGB cname);
|
||||
void showColorPattern(CRGB colorone, CRGB colortwo, CRGB colorthree);
|
||||
void rgbIdle();
|
||||
void addFadeColor(CRGB cname);
|
||||
|
||||
#endif
|
||||
|
||||
void quickBlink(uint8_t repeat);
|
||||
|
||||
@@ -17,7 +17,6 @@ struct imgParam {
|
||||
bool hasRed;
|
||||
uint8_t dataType;
|
||||
uint8_t dither;
|
||||
// bool grayLut = false;
|
||||
uint8_t bufferbpp = 8;
|
||||
uint8_t rotate = 0;
|
||||
uint16_t highlightColor = 2;
|
||||
@@ -39,6 +38,8 @@ struct imgParam {
|
||||
uint8_t preloadlut;
|
||||
|
||||
uint8_t zlib;
|
||||
uint8_t g5;
|
||||
uint8_t ts_option;
|
||||
};
|
||||
|
||||
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams);
|
||||
|
||||
@@ -26,6 +26,7 @@ extern void processDataReq(struct espAvailDataReq* adr, bool local, IPAddress re
|
||||
extern void processTagReturnData(struct espTagReturnData* trd, uint8_t len, bool local);
|
||||
|
||||
extern bool sendTagCommand(const uint8_t* dst, uint8_t cmd, bool local, const uint8_t* payload = nullptr);
|
||||
bool sendTagMac(const uint8_t* dst, const uint64_t newmac, bool local);
|
||||
extern bool sendAPSegmentedData(const uint8_t* dst, String data, uint16_t icons, bool inverted, bool local);
|
||||
extern bool showAPSegmentedInfo(const uint8_t* dst, bool local);
|
||||
extern void updateTaginfoitem(struct TagInfo* taginfoitem, IPAddress remoteIP);
|
||||
|
||||
@@ -12,8 +12,8 @@ extern struct espSetChannelPower curChannel;
|
||||
#define AP_STATE_NORADIO 7
|
||||
|
||||
struct APInfoS {
|
||||
bool isOnline = false;
|
||||
uint8_t state = AP_STATE_OFFLINE;
|
||||
volatile bool isOnline = false;
|
||||
volatile uint8_t state = AP_STATE_OFFLINE;
|
||||
uint8_t type;
|
||||
uint16_t version = 0;
|
||||
uint8_t channel;
|
||||
@@ -29,6 +29,17 @@ struct APInfoS {
|
||||
|
||||
extern struct APInfoS apInfo;
|
||||
|
||||
enum ApSerialState {
|
||||
SERIAL_STATE_NONE,
|
||||
SERIAL_STATE_INITIALIZED,
|
||||
SERIAL_STATE_STARTING,
|
||||
SERIAL_STATE_RUNNING,
|
||||
SERIAL_STATE_STOP,
|
||||
SERIAL_STATE_STOPPED
|
||||
};
|
||||
|
||||
extern volatile ApSerialState gSerialTaskState;
|
||||
|
||||
void APTask(void* parameter);
|
||||
|
||||
bool sendCancelPending(struct pendingData* pending);
|
||||
@@ -38,5 +49,5 @@ void APEnterEarlyReset();
|
||||
bool sendChannelPower(struct espSetChannelPower* scp);
|
||||
void rxSerialTask2(void* parameter);
|
||||
void APTagReset();
|
||||
bool bringAPOnline();
|
||||
bool bringAPOnline(uint8_t newState = AP_STATE_ONLINE);
|
||||
void setAPstate(bool isOnline, uint8_t state);
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "FS.h"
|
||||
|
||||
#ifdef HAS_SDCARD
|
||||
#ifndef SD_CARD_SDMMC
|
||||
|
||||
#ifndef SD_CARD_SS
|
||||
#error SD_CARD_SS UNDEFINED
|
||||
#endif
|
||||
@@ -18,6 +20,8 @@
|
||||
|
||||
#ifndef SD_CARD_MOSI
|
||||
#define SD_CARD_MOSI 23
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -36,7 +40,9 @@ class DynStorage {
|
||||
extern SemaphoreHandle_t fsMutex;
|
||||
extern DynStorage Storage;
|
||||
extern fs::FS *contentFS;
|
||||
#ifndef SD_CARD_ONLY
|
||||
extern void copyFile(File in, File out);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ class nrfswd : protected swd {
|
||||
|
||||
uint8_t nrf_read_bank(uint32_t address, uint32_t buffer[], int size);
|
||||
uint8_t nrf_write_bank(uint32_t address, uint32_t buffer[], int size);
|
||||
uint8_t nrf_erase_all();
|
||||
uint8_t erase_all_flash();
|
||||
uint8_t erase_uicr();
|
||||
uint8_t erase_page(uint32_t page);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define WAKEUP_REASON_NFC 3
|
||||
#define WAKEUP_REASON_BUTTON1 4
|
||||
#define WAKEUP_REASON_BUTTON2 5
|
||||
#define WAKEUP_REASON_BUTTON3 6
|
||||
#define WAKEUP_REASON_FAILED_OTA_FW 0xE0
|
||||
#define WAKEUP_REASON_FIRSTBOOT 0xFC
|
||||
#define WAKEUP_REASON_NETWORK_SCAN 0xFD
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#define NO_SUBGHZ_CHANNEL 255
|
||||
class tagRecord {
|
||||
public:
|
||||
tagRecord() : mac{0}, version(0), alias(""), lastseen(0), nextupdate(0), contentMode(0), pendingCount(0), md5{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0), isExternal(false), apIp(IPAddress(0, 0, 0, 0)), pendingIdle(0), hasCustomLUT(false), rotate(0), lut(0), tagSoftwareVersion(0), currentChannel(0), dataType(0), filename(""), data(nullptr), len(0), invert(0), updateCount(0), updateLast(0) {}
|
||||
tagRecord() : mac{0}, version(0), alias(""), lastseen(0), nextupdate(0), contentMode(0), pendingCount(0), md5{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0), isExternal(false), apIp(IPAddress(0, 0, 0, 0)), pendingIdle(0), rotate(0), lut(0), tagSoftwareVersion(0), currentChannel(0), dataType(0), filename(""), data(nullptr), len(0), invert(0), updateCount(0), updateLast(0) {}
|
||||
|
||||
uint8_t mac[8];
|
||||
uint8_t version;
|
||||
@@ -38,7 +38,6 @@ class tagRecord {
|
||||
bool isExternal;
|
||||
IPAddress apIp;
|
||||
uint16_t pendingIdle;
|
||||
bool hasCustomLUT;
|
||||
uint8_t rotate;
|
||||
uint8_t lut;
|
||||
uint16_t tagSoftwareVersion;
|
||||
@@ -64,7 +63,7 @@ struct Config {
|
||||
uint8_t language;
|
||||
uint8_t maxsleep;
|
||||
uint8_t stopsleep;
|
||||
uint8_t runStatus;
|
||||
volatile uint8_t runStatus;
|
||||
uint8_t preview;
|
||||
uint8_t nightlyreboot;
|
||||
uint8_t lock;
|
||||
@@ -76,6 +75,7 @@ struct Config {
|
||||
uint8_t discovery;
|
||||
String repo;
|
||||
String env;
|
||||
uint8_t showtimestamp;
|
||||
};
|
||||
|
||||
struct Color {
|
||||
@@ -93,6 +93,7 @@ struct HwType {
|
||||
uint8_t bpp;
|
||||
uint8_t shortlut;
|
||||
uint8_t zlib;
|
||||
uint8_t g5;
|
||||
uint16_t highlightColor;
|
||||
std::vector<Color> colortable;
|
||||
};
|
||||
|
||||
@@ -16,7 +16,8 @@ enum WifiStatus {
|
||||
WAIT_CONNECTING,
|
||||
CONNECTED,
|
||||
WAIT_RECONNECT,
|
||||
AP
|
||||
AP,
|
||||
ETHERNET
|
||||
};
|
||||
|
||||
class WifiManager {
|
||||
@@ -41,6 +42,7 @@ class WifiManager {
|
||||
bool waitForConnection();
|
||||
void pollSerial();
|
||||
static void terminalLog(String text);
|
||||
static String buildHostname(esp_mac_type_t mac_type);
|
||||
|
||||
public:
|
||||
WifiManager();
|
||||
@@ -53,6 +55,8 @@ class WifiManager {
|
||||
void startManagementServer();
|
||||
void poll();
|
||||
static void WiFiEvent(WiFiEvent_t event);
|
||||
void initEth();
|
||||
IPAddress localIP();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
114
ESP32_AP-Flasher/lib2/gt911-touch/src/Touch_GT911.cpp
Normal file
114
ESP32_AP-Flasher/lib2/gt911-touch/src/Touch_GT911.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "Arduino.h"
|
||||
#include <Touch_GT911.h>
|
||||
#include <Wire.h>
|
||||
|
||||
Touch_GT911::Touch_GT911(uint8_t _sda, uint8_t _scl, uint16_t _width, uint16_t _height) :
|
||||
pinSda(_sda), pinScl(_scl), width(_width), height(_height) {
|
||||
}
|
||||
TPoint::TPoint(void) {
|
||||
id = x = y = size = 0;
|
||||
}
|
||||
TPoint::TPoint(uint8_t _id, uint16_t _x, uint16_t _y, uint16_t _size) {
|
||||
id = _id;
|
||||
x = _x;
|
||||
y = _y;
|
||||
size = _size;
|
||||
}
|
||||
bool TPoint::operator==(TPoint point) {
|
||||
return ((point.x == x) && (point.y == y) && (point.size == size));
|
||||
}
|
||||
bool TPoint::operator!=(TPoint point) {
|
||||
return ((point.x != x) || (point.y != y) || (point.size != size));
|
||||
}
|
||||
void Touch_GT911::begin(uint8_t _addr) {
|
||||
addr = _addr;
|
||||
Wire.begin(pinSda, pinScl);
|
||||
}
|
||||
void Touch_GT911::calculateChecksum() {
|
||||
uint8_t checksum;
|
||||
for (uint8_t i=0; i<GT911_CONFIG_SIZE; i++) {
|
||||
checksum += configBuf[i];
|
||||
}
|
||||
checksum = (~checksum) + 1;
|
||||
configBuf[GT911_CONFIG_CHKSUM - GT911_CONFIG_START] = checksum;
|
||||
}
|
||||
void Touch_GT911::reConfig() {
|
||||
calculateChecksum();
|
||||
writeByteData(GT911_CONFIG_CHKSUM, configBuf[GT911_CONFIG_CHKSUM-GT911_CONFIG_START]);
|
||||
writeByteData(GT911_CONFIG_FRESH, 1);
|
||||
}
|
||||
void Touch_GT911::setResolution(uint16_t _width, uint16_t _height) {
|
||||
configBuf[GT911_X_OUTPUT_MAX_LOW - GT911_CONFIG_START] = lowByte(_width);
|
||||
configBuf[GT911_X_OUTPUT_MAX_HIGH - GT911_CONFIG_START] = highByte(_width);
|
||||
configBuf[GT911_Y_OUTPUT_MAX_LOW - GT911_CONFIG_START] = lowByte(_height);
|
||||
configBuf[GT911_Y_OUTPUT_MAX_HIGH - GT911_CONFIG_START] = highByte(_height);
|
||||
reConfig();
|
||||
}
|
||||
void Touch_GT911::read(void) {
|
||||
uint8_t data[7];
|
||||
uint8_t id;
|
||||
uint16_t x, y, size;
|
||||
|
||||
uint8_t pointInfo = readByteData(GT911_POINT_INFO);
|
||||
uint8_t bufferStatus = pointInfo >> 7 & 1;
|
||||
uint8_t proximityValid = pointInfo >> 5 & 1;
|
||||
uint8_t haveKey = pointInfo >> 4 & 1;
|
||||
isLargeDetect = pointInfo >> 6 & 1;
|
||||
touches = pointInfo & 0xF;
|
||||
isTouched = touches > 0;
|
||||
if (bufferStatus == 1 && isTouched) {
|
||||
for (uint8_t i=0; i<touches; i++) {
|
||||
readBlockData(data, GT911_POINT_1 + i * 8, 7);
|
||||
points[i] = readPoint(data);
|
||||
}
|
||||
}
|
||||
writeByteData(GT911_POINT_INFO, 0);
|
||||
}
|
||||
TPoint Touch_GT911::readPoint(uint8_t *data) {
|
||||
uint16_t temp;
|
||||
uint8_t id = data[0];
|
||||
uint16_t x = data[1] + (data[2] << 8);
|
||||
uint16_t y = data[3] + (data[4] << 8);
|
||||
uint16_t size = data[5] + (data[6] << 8);
|
||||
x = width - x;
|
||||
y = height - y;
|
||||
return TPoint(id, x, y, size);
|
||||
}
|
||||
void Touch_GT911::writeByteData(uint16_t reg, uint8_t val) {
|
||||
Wire.beginTransmission(addr);
|
||||
Wire.write(highByte(reg));
|
||||
Wire.write(lowByte(reg));
|
||||
Wire.write(val);
|
||||
Wire.endTransmission();
|
||||
}
|
||||
uint8_t Touch_GT911::readByteData(uint16_t reg) {
|
||||
uint8_t x;
|
||||
Wire.beginTransmission(addr);
|
||||
Wire.write(highByte(reg));
|
||||
Wire.write(lowByte(reg));
|
||||
Wire.endTransmission();
|
||||
Wire.requestFrom(addr, (uint8_t)1);
|
||||
x = Wire.read();
|
||||
return x;
|
||||
}
|
||||
void Touch_GT911::writeBlockData(uint16_t reg, uint8_t *val, uint8_t size) {
|
||||
Wire.beginTransmission(addr);
|
||||
Wire.write(highByte(reg));
|
||||
Wire.write(lowByte(reg));
|
||||
// Wire.write(val, size);
|
||||
for (uint8_t i=0; i<size; i++) {
|
||||
Wire.write(val[i]);
|
||||
}
|
||||
Wire.endTransmission();
|
||||
}
|
||||
void Touch_GT911::readBlockData(uint8_t *buf, uint16_t reg, uint8_t size) {
|
||||
Wire.beginTransmission(addr);
|
||||
Wire.write(highByte(reg));
|
||||
Wire.write(lowByte(reg));
|
||||
Wire.endTransmission();
|
||||
Wire.requestFrom(addr, size);
|
||||
for (uint8_t i=0; i<size; i++) {
|
||||
buf[i] = Wire.read();
|
||||
}
|
||||
}
|
||||
|
||||
116
ESP32_AP-Flasher/lib2/gt911-touch/src/Touch_GT911.h
Normal file
116
ESP32_AP-Flasher/lib2/gt911-touch/src/Touch_GT911.h
Normal file
@@ -0,0 +1,116 @@
|
||||
|
||||
#ifndef Touch_GT911_H
|
||||
#define Touch_GT911_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Wire.h>
|
||||
|
||||
#define GT911_ADDR1 (uint8_t)0x5D
|
||||
#define GT911_ADDR2 (uint8_t)0x14
|
||||
|
||||
// Real-time command (Write only)
|
||||
#define GT911_COMMAND (uint16_t)0x8040
|
||||
#define GT911_ESD_CHECK (uint16_t)0x8041
|
||||
#define GT911_COMMAND_CHECK (uint16_t)0x8046
|
||||
|
||||
#define GT911_STRETCH_R0 (uint16_t)0x805E
|
||||
#define GT911_STRETCH_R1 (uint16_t)0x805F
|
||||
#define GT911_STRETCH_R2 (uint16_t)0x8060
|
||||
#define GT911_STRETCH_RM (uint16_t)0x8061
|
||||
#define GT911_DRV_GROUPA_NUM (uint16_t)0x8062
|
||||
#define GT911_CONFIG_START (uint16_t)0x8047
|
||||
#define GT911_CONFIG_VERSION (uint16_t)0x8047
|
||||
#define GT911_X_OUTPUT_MAX_LOW (uint16_t)0x8048
|
||||
#define GT911_X_OUTPUT_MAX_HIGH (uint16_t)0x8049
|
||||
#define GT911_Y_OUTPUT_MAX_LOW (uint16_t)0x804A
|
||||
#define GT911_Y_OUTPUT_MAX_HIGH (uint16_t)0x804B
|
||||
#define GT911_TOUCH_NUMBER (uint16_t)0x804C
|
||||
#define GT911_MODULE_SWITCH_1 (uint16_t)0x804D
|
||||
#define GT911_MODULE_SWITCH_2 (uint16_t)0x804E
|
||||
#define GT911_SHAKE_COUNT (uint16_t)0x804F
|
||||
#define GT911_FILTER (uint16_t)0x8050
|
||||
#define GT911_LARGE_TOUCH (uint16_t)0x8051
|
||||
#define GT911_NOISE_REDUCTION (uint16_t)0x8052
|
||||
#define GT911_SCREEN_TOUCH_LEVEL (uint16_t)0x8053
|
||||
#define GT911_SCREEN_RELEASE_LEVEL (uint16_t)0x8054
|
||||
#define GT911_LOW_POWER_CONTROL (uint16_t)0x8055
|
||||
#define GT911_REFRESH_RATE (uint16_t)0x8056
|
||||
#define GT911_X_THRESHOLD (uint16_t)0x8057
|
||||
#define GT911_Y_THRESHOLD (uint16_t)0x8058
|
||||
#define GT911_SPACE_TOP_BOTTOM (uint16_t)0x805B
|
||||
#define GT911_PANEL_TX_GAIN (uint16_t)0x806B
|
||||
#define GT911_PANEL_RX_GAIN (uint16_t)0x806C
|
||||
#define GT911_PANEL_DUMP_SHIFT (uint16_t)0x806D
|
||||
#define GT911_DRV_FRAME_CONTROL (uint16_t)0x806E
|
||||
#define GT911_CHARGING_LEVEL_UP (uint16_t)0x806F
|
||||
#define GT911_MODULE_SWITCH3 (uint16_t)0x8070
|
||||
#define GT911_GESTURE_DIS (uint16_t)0X8071
|
||||
#define GT911_GESTURE_LONG_PRESS_TIME (uint16_t)0x8072
|
||||
#define GT911_X_Y_SLOPE_ADJUST (uint16_t)0X8073
|
||||
#define GT911_GESTURE_CONTROL (uint16_t)0X8074
|
||||
#define GT911_GESTURE_SWITCH1 (uint16_t)0X8075
|
||||
#define GT911_GESTURE_SWITCH2 (uint16_t)0X8076
|
||||
#define GT911_GESTURE_REFRESH_RATE (uint16_t)0x8077
|
||||
#define GT911_GESTURE_TOUCH_LEVEL (uint16_t)0x8078
|
||||
#define GT911_NEWGREENWAKEUPLEVEL (uint16_t)0x8079
|
||||
#define GT911_FREQ_HOPPING_START (uint16_t)0x807A
|
||||
#define GT911_CONFIG_CHKSUM (uint16_t)0X80FF
|
||||
#define GT911_CONFIG_FRESH (uint16_t)0X8100
|
||||
#define GT911_CONFIG_SIZE (uint16_t)0xFF-0x46
|
||||
// Coordinate information
|
||||
#define GT911_PRODUCT_ID (uint16_t)0X8140
|
||||
#define GT911_FIRMWARE_VERSION (uint16_t)0X8140
|
||||
#define GT911_RESOLUTION (uint16_t)0X8140
|
||||
#define GT911_VENDOR_ID (uint16_t)0X8140
|
||||
#define GT911_IMFORMATION (uint16_t)0X8140
|
||||
#define GT911_POINT_INFO (uint16_t)0X814E
|
||||
#define GT911_POINT_1 (uint16_t)0X814F
|
||||
#define GT911_POINT_2 (uint16_t)0X8157
|
||||
#define GT911_POINT_3 (uint16_t)0X815F
|
||||
#define GT911_POINT_4 (uint16_t)0X8167
|
||||
#define GT911_POINT_5 (uint16_t)0X816F
|
||||
#define GT911_POINTS_REG {GT911_POINT_1, GT911_POINT_2, GT911_POINT_3, GT911_POINT_4, GT911_POINT_5}
|
||||
|
||||
class TPoint {
|
||||
public:
|
||||
TPoint(void);
|
||||
TPoint(uint8_t id, uint16_t x, uint16_t y, uint16_t size);
|
||||
|
||||
bool operator==(TPoint);
|
||||
bool operator!=(TPoint);
|
||||
|
||||
uint8_t id;
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint8_t size;
|
||||
};
|
||||
|
||||
class Touch_GT911 {
|
||||
public:
|
||||
Touch_GT911(uint8_t _sda, uint8_t _scl,uint16_t _width, uint16_t _height);
|
||||
void begin(uint8_t _addr=GT911_ADDR1);
|
||||
void setRotation(uint8_t rot);
|
||||
void setResolution(uint16_t _width, uint16_t _height);
|
||||
uint8_t getGesture(void);
|
||||
void read(void);
|
||||
uint8_t isLargeDetect;
|
||||
uint8_t touches = 0;
|
||||
bool isTouched = false;
|
||||
TPoint points[5];
|
||||
private:
|
||||
void calculateChecksum();
|
||||
void reConfig();
|
||||
TPoint readPoint(uint8_t *data);
|
||||
void writeByteData(uint16_t reg, uint8_t val);
|
||||
uint8_t readByteData(uint16_t reg);
|
||||
void writeBlockData(uint16_t reg, uint8_t *val, uint8_t size);
|
||||
void readBlockData(uint8_t *buf, uint16_t reg, uint8_t size);
|
||||
uint8_t addr;
|
||||
uint8_t pinSda;
|
||||
uint8_t pinScl;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t configBuf[GT911_CONFIG_SIZE];
|
||||
};
|
||||
|
||||
#endif // Touch_GT911_H
|
||||
File diff suppressed because it is too large
Load Diff
23
ESP32_AP-Flasher/prepare_sdcard.sh
Executable file
23
ESP32_AP-Flasher/prepare_sdcard.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
if [[ -d $1 ]]; then
|
||||
rm -rf $1/*
|
||||
cp -r data/* $1
|
||||
rm $1/www/*
|
||||
cp -r wwwroot/* $1/www/
|
||||
cp ../binaries/ESP32-C6/firmware.json $1
|
||||
for f in bootloader partition-table OpenEPaperLink_esp32_C6
|
||||
do
|
||||
if [[ -e ../ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/build/bootloader/${f}.bin ]]; then
|
||||
cp ../ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/build/bootloader/${f}.bin $1
|
||||
else
|
||||
cp ../binaries/ESP32-C6/${f}.bin $1
|
||||
fi
|
||||
done
|
||||
mkdir $1/current
|
||||
echo "[[]]" > $1/current/tagDB.json
|
||||
echo "OK"
|
||||
else
|
||||
echo "$1 is not a directory"
|
||||
exit 1
|
||||
fi
|
||||
@@ -8,7 +8,7 @@ SPIFFSEditor::SPIFFSEditor(const fs::FS &fs, const String &username, const Strin
|
||||
: _fs(fs), _username(username), _password(password), _authenticated(false), _startTime(0) {
|
||||
}
|
||||
|
||||
bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request) {
|
||||
bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request) const {
|
||||
if (request->url().equalsIgnoreCase("/edit")) {
|
||||
if (request->method() == HTTP_GET) {
|
||||
if (request->hasParam("list")) {
|
||||
@@ -34,7 +34,6 @@ bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
request->addInterestingHeader("If-Modified-Since");
|
||||
return true;
|
||||
} else if (request->method() == HTTP_POST || request->method() == HTTP_DELETE || request->method() == HTTP_PUT) {
|
||||
return true;
|
||||
@@ -91,7 +90,7 @@ void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request) {
|
||||
if (request->header("If-Modified-Since").equals(buildTime)) {
|
||||
request->send(304);
|
||||
} else {
|
||||
AsyncWebServerResponse *response = request->beginResponse(_fs, "/www/edit.html");
|
||||
AsyncWebServerResponse *response = request->beginResponse(_fs, "/www/edit.html", "text/html");
|
||||
response->addHeader("Last-Modified", buildTime);
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,26 @@ uint8_t gicToOEPLtype(uint8_t gicType) {
|
||||
}
|
||||
}
|
||||
|
||||
struct BleAdvDataStructV1 {
|
||||
uint16_t manu_id; // 0x1337 for us
|
||||
uint8_t version;
|
||||
uint16_t hw_type;
|
||||
uint16_t fw_version;
|
||||
uint16_t capabilities;
|
||||
uint16_t battery_mv;
|
||||
uint8_t counter;
|
||||
} __packed;
|
||||
struct BleAdvDataStructV2 {
|
||||
uint16_t manu_id; // 0x1337 for us
|
||||
uint8_t version;
|
||||
uint16_t hw_type;
|
||||
uint16_t fw_version;
|
||||
uint16_t capabilities;
|
||||
uint16_t battery_mv;
|
||||
int8_t temperature;
|
||||
uint8_t counter;
|
||||
} __packed;
|
||||
|
||||
bool BLE_filter_add_device(BLEAdvertisedDevice advertisedDevice) {
|
||||
Serial.print("BLE Advertised Device found: ");
|
||||
Serial.println(advertisedDevice.toString().c_str());
|
||||
@@ -81,11 +101,16 @@ bool BLE_filter_add_device(BLEAdvertisedDevice advertisedDevice) {
|
||||
uint8_t manuData[100];
|
||||
if (manuDatalen > sizeof(manuData))
|
||||
return false; // Manu data too big, could never happen but better make sure here
|
||||
#if ESP_ARDUINO_VERSION_MAJOR == 2
|
||||
memcpy(&manuData, (uint8_t*)advertisedDevice.getManufacturerData().data(), manuDatalen);
|
||||
#else
|
||||
// [Nic] suggested fix for arduino 3.x by copilot, but I cannot test it
|
||||
memcpy(&manuData, (uint8_t*)advertisedDevice.getManufacturerData().c_str(), manuDatalen);
|
||||
#endif
|
||||
Serial.printf(" Address type: %02X Manu data: ", advertisedDevice.getAddressType());
|
||||
for (int i = 0; i < advertisedDevice.getManufacturerData().length(); i++)
|
||||
Serial.printf("%02X", manuData[i]);
|
||||
Serial.printf("\r\n");
|
||||
memcpy(&manuData, (uint8_t*)advertisedDevice.getManufacturerData().data(), manuDatalen);
|
||||
if (manuDatalen == 7 && manuData[0] == 0x53 && manuData[1] == 0x50) { // Lets check for a Gicisky E-Paper display
|
||||
|
||||
struct espAvailDataReq theAdvData;
|
||||
@@ -103,10 +128,81 @@ bool BLE_filter_add_device(BLEAdvertisedDevice advertisedDevice) {
|
||||
theAdvData.src[7] = manuData[6];
|
||||
theAdvData.adr.batteryMv = manuData[3] * 100;
|
||||
theAdvData.adr.lastPacketRSSI = advertisedDevice.getRSSI();
|
||||
theAdvData.adr.lastPacketLQI = advertisedDevice.getRSSI();
|
||||
theAdvData.adr.hwType = gicToOEPLtype(manuData[2]);
|
||||
theAdvData.adr.tagSoftwareVersion = manuData[4] << 8 | manuData[5];
|
||||
theAdvData.adr.capabilities = 0x00;
|
||||
|
||||
processDataReq(&theAdvData, true);
|
||||
return true;
|
||||
} else if (manuDatalen >= 3 && manuData[0] == 0x37 && manuData[1] == 0x13) { // Lets check for a Gicisky E-Paper display
|
||||
Serial.printf("ATC BLE OEPL Detected\r\n");
|
||||
struct espAvailDataReq theAdvData;
|
||||
memset((uint8_t*)&theAdvData, 0x00, sizeof(espAvailDataReq));
|
||||
uint8_t versionAdvData = manuData[2];
|
||||
|
||||
switch (versionAdvData) {
|
||||
case 1: {
|
||||
if (manuDatalen >= sizeof(BleAdvDataStructV1)) {
|
||||
struct BleAdvDataStructV1 inAdvData;
|
||||
memcpy(&inAdvData, manuData, sizeof(BleAdvDataStructV1));
|
||||
printf("Version 1 ATC_BLE_OEPL Received\r\n");
|
||||
/*Serial.printf("manu_id %04X\r\n", inAdvData.manu_id);
|
||||
Serial.printf("version %02X\r\n", inAdvData.version);
|
||||
Serial.printf("hw_type %04X\r\n", inAdvData.hw_type);
|
||||
Serial.printf("fw_version %04X\r\n", inAdvData.fw_version);
|
||||
Serial.printf("capabilities %04X\r\n", inAdvData.capabilities);
|
||||
Serial.printf("battery_mv %u\r\n", inAdvData.battery_mv);
|
||||
Serial.printf("counter %u\r\n", inAdvData.counter);*/
|
||||
theAdvData.adr.batteryMv = inAdvData.battery_mv;
|
||||
theAdvData.adr.lastPacketRSSI = advertisedDevice.getRSSI();
|
||||
theAdvData.adr.hwType = inAdvData.hw_type & 0xff;
|
||||
theAdvData.adr.tagSoftwareVersion = inAdvData.fw_version;
|
||||
theAdvData.adr.capabilities = inAdvData.capabilities & 0xff;
|
||||
} else {
|
||||
printf("Version 1 data length incorrect!\r\n");
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
case 2: {
|
||||
if (manuDatalen >= sizeof(BleAdvDataStructV2)) {
|
||||
struct BleAdvDataStructV2 inAdvData;
|
||||
memcpy(&inAdvData, manuData, sizeof(BleAdvDataStructV2));
|
||||
printf("Version 2 ATC_BLE_OEPL Received\r\n");
|
||||
/*Serial.printf("manu_id %04X\r\n", inAdvData.manu_id);
|
||||
Serial.printf("version %02X\r\n", inAdvData.version);
|
||||
Serial.printf("hw_type %04X\r\n", inAdvData.hw_type);
|
||||
Serial.printf("fw_version %04X\r\n", inAdvData.fw_version);
|
||||
Serial.printf("capabilities %04X\r\n", inAdvData.capabilities);
|
||||
Serial.printf("battery_mv %u\r\n", inAdvData.battery_mv);
|
||||
Serial.printf("temperature %i\r\n", inAdvData.temperature);
|
||||
Serial.printf("counter %u\r\n", inAdvData.counter);*/
|
||||
theAdvData.adr.batteryMv = inAdvData.battery_mv;
|
||||
theAdvData.adr.temperature = inAdvData.temperature;
|
||||
theAdvData.adr.lastPacketRSSI = advertisedDevice.getRSSI();
|
||||
theAdvData.adr.hwType = inAdvData.hw_type & 0xff;
|
||||
theAdvData.adr.tagSoftwareVersion = inAdvData.fw_version;
|
||||
theAdvData.adr.capabilities = inAdvData.capabilities & 0xff;
|
||||
} else {
|
||||
printf("Version 2 data length incorrect!\r\n");
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
printf("Version %02X currently not supported!\r\n", versionAdvData);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
uint8_t macReversed[6];
|
||||
memcpy(&macReversed, (uint8_t*)advertisedDevice.getAddress().getNative(), 6);
|
||||
theAdvData.src[0] = macReversed[5];
|
||||
theAdvData.src[1] = macReversed[4];
|
||||
theAdvData.src[2] = macReversed[3];
|
||||
theAdvData.src[3] = macReversed[2];
|
||||
theAdvData.src[4] = macReversed[1];
|
||||
theAdvData.src[5] = macReversed[0];
|
||||
theAdvData.src[6] = manuData[0]; // We use this do find out what type of display we got for compression^^
|
||||
theAdvData.src[7] = manuData[1];
|
||||
processDataReq(&theAdvData, true);
|
||||
return true;
|
||||
}
|
||||
@@ -133,7 +229,6 @@ bool BLE_filter_add_device(BLEAdvertisedDevice advertisedDevice) {
|
||||
theAdvData.adr.hwType = ATC_MI_THERMOMETER;
|
||||
theAdvData.adr.tagSoftwareVersion = 0x00;
|
||||
theAdvData.adr.capabilities = 0x00;
|
||||
|
||||
processDataReq(&theAdvData, true);
|
||||
Serial.printf("We got an ATC_MiThermometer via BLE\r\n");
|
||||
return true;
|
||||
@@ -150,6 +245,14 @@ bool BLE_is_image_pending(uint8_t address[8]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (int16_t c = 0; c < tagDB.size(); c++) {
|
||||
tagRecord* taginfo = tagDB.at(c);
|
||||
if (taginfo->pendingCount > 0 && taginfo->version == 0 && (taginfo->mac[7] == 0x13) && (taginfo->mac[6] == 0x37)) {
|
||||
memcpy(address, taginfo->mac, 8);
|
||||
Serial.printf("ATC BLE OEPL data Waiting\r\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -374,4 +477,37 @@ uint32_t compress_image(uint8_t address[8], uint8_t* buffer, uint32_t max_len) {
|
||||
return len_compressed;
|
||||
}
|
||||
|
||||
uint32_t get_ATC_BLE_OEPL_image(uint8_t address[8], uint8_t* buffer, uint32_t max_len, uint8_t* dataType, uint8_t* dataTypeArgument, uint16_t* nextCheckIn) {
|
||||
uint32_t t = millis();
|
||||
PendingItem* queueItem = getQueueItem(address, 0);
|
||||
if (queueItem == nullptr) {
|
||||
prepareCancelPending(address);
|
||||
Serial.printf("blockrequest: couldn't find taginfo %02X%02X%02X%02X%02X%02X%02X%02X\r\n", address[7], address[6], address[5], address[4], address[3], address[2], address[1], address[0]);
|
||||
return 0;
|
||||
}
|
||||
if (queueItem->data == nullptr) {
|
||||
fs::File file = contentFS->open(queueItem->filename);
|
||||
if (!file) {
|
||||
Serial.print("No current file. " + String(queueItem->filename) + " Canceling request\r\n");
|
||||
prepareCancelPending(address);
|
||||
return 0;
|
||||
}
|
||||
queueItem->data = getDataForFile(file);
|
||||
Serial.println("Reading file " + String(queueItem->filename) + " in " + String(millis() - t) + "ms");
|
||||
file.close();
|
||||
}
|
||||
if (queueItem->len > max_len) {
|
||||
Serial.print("The upload is too big better cencel it\r\n");
|
||||
prepareCancelPending(address);
|
||||
return 0;
|
||||
}
|
||||
*dataType = queueItem->pendingdata.availdatainfo.dataType;
|
||||
*dataTypeArgument = queueItem->pendingdata.availdatainfo.dataTypeArgument;
|
||||
*nextCheckIn = queueItem->pendingdata.availdatainfo.nextCheckIn;
|
||||
uint32_t len_compressed = queueItem->len;
|
||||
memcpy(buffer, queueItem->data, queueItem->len);
|
||||
Serial.print("Data is prepared Len: " + String(queueItem->len) + "\r\n");
|
||||
return queueItem->len;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#ifdef HAS_BLE_WRITER
|
||||
#include <Arduino.h>
|
||||
#include <MD5Builder.h>
|
||||
|
||||
#include "BLEDevice.h"
|
||||
#include "ble_filter.h"
|
||||
@@ -7,12 +8,13 @@
|
||||
|
||||
#define INTERVAL_BLE_SCANNING_SECONDS 60
|
||||
#define INTERVAL_HANDLE_PENDING_SECONDS 10
|
||||
#define BUFFER_MAX_SIZE_COMPRESSING 100000
|
||||
#define BUFFER_MAX_SIZE_COMPRESSING 135000
|
||||
|
||||
#define BLE_MAIN_STATE_IDLE 0
|
||||
#define BLE_MAIN_STATE_PREPARE 1
|
||||
#define BLE_MAIN_STATE_CONNECT 2
|
||||
#define BLE_MAIN_STATE_UPLOAD 3
|
||||
#define BLE_MAIN_STATE_ATC_BLE_OEPL_UPLOAD 4
|
||||
|
||||
int ble_main_state = BLE_MAIN_STATE_IDLE;
|
||||
uint32_t last_ble_scan = 0;
|
||||
@@ -23,9 +25,25 @@ uint32_t last_ble_scan = 0;
|
||||
#define BLE_UPLOAD_STATE_UPLOAD 5
|
||||
int BLE_upload_state = BLE_UPLOAD_STATE_INIT;
|
||||
|
||||
#define BLE_CMD_ACK_CMD 99
|
||||
#define BLE_CMD_AVAILDATA 100
|
||||
#define BLE_CMD_BLK_DATA 101
|
||||
#define BLE_CMD_ERR_BLKPRT 196
|
||||
#define BLE_CMD_ACK_BLKPRT 197
|
||||
#define BLE_CMD_REQ 198
|
||||
#define BLE_CMD_ACK 199
|
||||
#define BLE_CMD_ACK_IS_SHOWN 200
|
||||
#define BLE_CMD_ACK_FW_UPDATED 201
|
||||
|
||||
struct AvailDataInfo BLEavaildatainfo = {0};
|
||||
struct blockRequest BLEblkRequst = {0};
|
||||
|
||||
bool BLE_connected = false;
|
||||
bool BLE_new_notify = false;
|
||||
|
||||
static BLEUUID ATC_BLE_OEPL_ServiceUUID((uint16_t)0x1337);
|
||||
static BLEUUID ATC_BLE_OEPL_CtrlUUID((uint16_t)0x1337);
|
||||
|
||||
static BLEUUID gicServiceUUID((uint16_t)0xfef0);
|
||||
static BLEUUID gicCtrlUUID((uint16_t)0xfef1);
|
||||
static BLEUUID gicImgUUID((uint16_t)0xfef2);
|
||||
@@ -33,20 +51,21 @@ static BLEUUID gicImgUUID((uint16_t)0xfef2);
|
||||
BLERemoteCharacteristic* ctrlChar;
|
||||
BLERemoteCharacteristic* imgChar;
|
||||
BLEAdvertisedDevice* myDevice;
|
||||
|
||||
BLEClient* pClient;
|
||||
|
||||
uint8_t BLE_notify_buffer[255] = {0};
|
||||
uint8_t BLE_notify_buffer[256] = {0};
|
||||
|
||||
uint32_t curr_part = 0;
|
||||
uint8_t BLE_buff[255];
|
||||
uint32_t BLE_err_counter = 0;
|
||||
uint32_t BLE_curr_part = 0;
|
||||
uint32_t BLE_max_block_parts = 0;
|
||||
uint8_t BLE_mini_buff[256];
|
||||
|
||||
uint32_t BLE_last_notify = 0;
|
||||
uint32_t BLE_last_pending_check = 0;
|
||||
uint8_t BLE_curr_address[8] = {0};
|
||||
|
||||
uint32_t compressed_len = 0;
|
||||
uint8_t* buffer;
|
||||
uint32_t BLE_compressed_len = 0;
|
||||
uint8_t* BLE_image_buffer;
|
||||
|
||||
static void notifyCallback(
|
||||
BLERemoteCharacteristic* pBLERemoteCharacteristic,
|
||||
@@ -81,7 +100,13 @@ class MyClientCallback : public BLEClientCallbacks {
|
||||
}
|
||||
};
|
||||
|
||||
bool BLE_connect(uint8_t addr[8]) {
|
||||
enum BLE_CONNECTION_TYPE {
|
||||
BLE_TYPE_GICISKY = 0,
|
||||
BLE_TYPE_ATC_BLE_OEPL
|
||||
};
|
||||
|
||||
bool BLE_connect(uint8_t addr[8], BLE_CONNECTION_TYPE conn_type) {
|
||||
BLE_err_counter = 0;
|
||||
uint8_t temp_Address[] = {addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]};
|
||||
Serial.printf("BLE Connecting to: %02X:%02X:%02X:%02X:%02X:%02X\r\n", addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
|
||||
pClient = BLEDevice::createClient();
|
||||
@@ -92,25 +117,27 @@ bool BLE_connect(uint8_t addr[8]) {
|
||||
return false;
|
||||
}
|
||||
uint32_t timeStart = millis();
|
||||
while (millis() - timeStart <= 5000) {// We wait for a few seconds as otherwise the connection might not be ready!
|
||||
while (millis() - timeStart <= 5000) { // We wait for a few seconds as otherwise the connection might not be ready!
|
||||
delay(100);
|
||||
}
|
||||
if (!BLE_connected)
|
||||
return false;
|
||||
Serial.printf("BLE starting to get service\r\n");
|
||||
BLERemoteService* pRemoteService = pClient->getService(gicServiceUUID);
|
||||
BLERemoteService* pRemoteService = pClient->getService((conn_type == BLE_TYPE_GICISKY) ? gicServiceUUID : ATC_BLE_OEPL_ServiceUUID);
|
||||
if (pRemoteService == nullptr) {
|
||||
Serial.printf("BLE Service failed\r\n");
|
||||
pClient->disconnect();
|
||||
return false;
|
||||
}
|
||||
imgChar = pRemoteService->getCharacteristic(gicImgUUID);
|
||||
if (imgChar == nullptr) {
|
||||
Serial.printf("BLE IMG Char failed\r\n");
|
||||
pClient->disconnect();
|
||||
return false;
|
||||
if (conn_type == BLE_TYPE_GICISKY) {
|
||||
imgChar = pRemoteService->getCharacteristic(gicImgUUID);
|
||||
if (imgChar == nullptr) {
|
||||
Serial.printf("BLE IMG Char failed\r\n");
|
||||
pClient->disconnect();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ctrlChar = pRemoteService->getCharacteristic(gicCtrlUUID);
|
||||
ctrlChar = pRemoteService->getCharacteristic((conn_type == BLE_TYPE_GICISKY) ? gicCtrlUUID : ATC_BLE_OEPL_CtrlUUID);
|
||||
if (ctrlChar == nullptr) {
|
||||
Serial.printf("BLE ctrl Char failed\r\n");
|
||||
pClient->disconnect();
|
||||
@@ -147,6 +174,49 @@ void BLE_startScan(uint32_t timeout) {
|
||||
pBLEScan->start(timeout, false);
|
||||
}
|
||||
|
||||
#define BLOCK_DATA_SIZE_BLE 4096
|
||||
#define BLOCK_PART_DATA_SIZE_BLE 230
|
||||
uint8_t tempBlockBuffer[BLOCK_DATA_SIZE_BLE + 4];
|
||||
uint8_t tempPacketBuffer[2 + 3 + BLOCK_PART_DATA_SIZE_BLE];
|
||||
void ATC_BLE_OEPL_PrepareBlk(uint8_t indexBlockId) {
|
||||
if (BLE_image_buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
uint32_t bufferPosition = (BLOCK_DATA_SIZE_BLE * indexBlockId);
|
||||
uint32_t lenNow = BLOCK_DATA_SIZE_BLE;
|
||||
uint16_t crcCalc = 0;
|
||||
if ((BLE_compressed_len - bufferPosition) < BLOCK_DATA_SIZE_BLE)
|
||||
lenNow = (BLE_compressed_len - bufferPosition);
|
||||
tempBlockBuffer[0] = lenNow & 0xff;
|
||||
tempBlockBuffer[1] = (lenNow >> 8) & 0xff;
|
||||
for (uint16_t c = 0; c < lenNow; c++) {
|
||||
tempBlockBuffer[4 + c] = BLE_image_buffer[c + bufferPosition];
|
||||
crcCalc += tempBlockBuffer[4 + c];
|
||||
}
|
||||
tempBlockBuffer[2] = crcCalc & 0xff;
|
||||
tempBlockBuffer[3] = (crcCalc >> 8) & 0xff;
|
||||
BLE_max_block_parts = (4 + lenNow) / BLOCK_PART_DATA_SIZE_BLE;
|
||||
if ((4 + lenNow) % BLOCK_PART_DATA_SIZE_BLE)
|
||||
BLE_max_block_parts++;
|
||||
Serial.println("Preparing block: " + String(indexBlockId) + " BuffPos: " + String(bufferPosition) + " LenNow: " + String(lenNow) + " MaxBLEparts: " + String(BLE_max_block_parts));
|
||||
BLE_curr_part = 0;
|
||||
}
|
||||
|
||||
void ATC_BLE_OEPL_SendPart(uint8_t indexBlockId, uint8_t indexPkt) {
|
||||
uint8_t crcCalc = indexBlockId + indexPkt;
|
||||
for (uint16_t c = 0; c < BLOCK_PART_DATA_SIZE_BLE; c++) {
|
||||
tempPacketBuffer[5 + c] = tempBlockBuffer[c + (BLOCK_PART_DATA_SIZE_BLE * indexPkt)];
|
||||
crcCalc += tempPacketBuffer[5 + c];
|
||||
}
|
||||
tempPacketBuffer[0] = 0x00;
|
||||
tempPacketBuffer[1] = 0x65;
|
||||
tempPacketBuffer[2] = crcCalc;
|
||||
tempPacketBuffer[3] = indexBlockId;
|
||||
tempPacketBuffer[4] = indexPkt;
|
||||
Serial.println("BLE Sending packet Len " + String(sizeof(tempPacketBuffer)));
|
||||
ctrlChar->writeValue(tempPacketBuffer, sizeof(tempPacketBuffer), true);
|
||||
}
|
||||
|
||||
void BLETask(void* parameter) {
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
Serial.println("BLE task started");
|
||||
@@ -162,25 +232,79 @@ void BLETask(void* parameter) {
|
||||
}
|
||||
if (millis() - BLE_last_pending_check >= (INTERVAL_HANDLE_PENDING_SECONDS * 1000)) {
|
||||
if (BLE_is_image_pending(BLE_curr_address)) {
|
||||
delay(4000); // We better wait here, since the pending image needs to be created first
|
||||
Serial.println("BLE Image is pending");
|
||||
// Here we create the compressed buffer
|
||||
buffer = (uint8_t*)malloc(BUFFER_MAX_SIZE_COMPRESSING);
|
||||
if (buffer == nullptr) {
|
||||
Serial.println("BLE Could not create buffer!");
|
||||
compressed_len = 0;
|
||||
} else {
|
||||
compressed_len = compress_image(BLE_curr_address, buffer, BUFFER_MAX_SIZE_COMPRESSING);
|
||||
Serial.printf("BLE Compressed Length: %i\r\n", compressed_len);
|
||||
// then we connect to BLE to send the compressed data
|
||||
if (compressed_len && BLE_connect(BLE_curr_address)) {
|
||||
curr_part = 0;
|
||||
memset(BLE_notify_buffer, 0x00, sizeof(BLE_notify_buffer));
|
||||
BLE_upload_state = BLE_UPLOAD_STATE_INIT;
|
||||
ble_main_state = BLE_MAIN_STATE_UPLOAD;
|
||||
BLE_new_notify = true; // trigger the upload here
|
||||
Serial.println("BLE Image is pending but we wait a bit");
|
||||
delay(5000); // We better wait here, since the pending image needs to be created first
|
||||
if (BLE_curr_address[7] == 0x13 && BLE_curr_address[6] == 0x37) { // This is an ATC BLE OEPL display
|
||||
// Here we create the compressed buffer
|
||||
BLE_image_buffer = (uint8_t*)malloc(BUFFER_MAX_SIZE_COMPRESSING);
|
||||
if (BLE_image_buffer == nullptr) {
|
||||
Serial.println("BLE Could not create buffer!");
|
||||
BLE_compressed_len = 0;
|
||||
} else {
|
||||
free(buffer);
|
||||
uint8_t dataType = 0x00;
|
||||
uint8_t dataTypeArgument = 0x00;
|
||||
uint16_t nextCheckin = 0x00;
|
||||
BLE_compressed_len = get_ATC_BLE_OEPL_image(BLE_curr_address, BLE_image_buffer, BUFFER_MAX_SIZE_COMPRESSING, &dataType, &dataTypeArgument, &nextCheckin);
|
||||
Serial.printf("BLE data Length: %i\r\n", BLE_compressed_len);
|
||||
// then we connect to BLE to send the compressed data
|
||||
if (BLE_compressed_len && BLE_connect(BLE_curr_address, BLE_TYPE_ATC_BLE_OEPL)) {
|
||||
BLE_err_counter = 0;
|
||||
BLE_curr_part = 0;
|
||||
memset(BLE_notify_buffer, 0x00, sizeof(BLE_notify_buffer));
|
||||
|
||||
uint8_t md5bytes[16];
|
||||
MD5Builder md5;
|
||||
md5.begin();
|
||||
md5.add(BLE_image_buffer, BLE_compressed_len);
|
||||
md5.calculate();
|
||||
md5.getBytes(md5bytes);
|
||||
|
||||
BLEavaildatainfo.dataType = dataType;
|
||||
BLEavaildatainfo.dataVer = *((uint64_t*)md5bytes);
|
||||
BLEavaildatainfo.dataSize = BLE_compressed_len;
|
||||
BLEavaildatainfo.dataTypeArgument = dataTypeArgument;
|
||||
BLEavaildatainfo.nextCheckIn = nextCheckin;
|
||||
BLEavaildatainfo.checksum = 0;
|
||||
for (uint16_t c = 1; c < sizeof(struct AvailDataInfo); c++) {
|
||||
BLEavaildatainfo.checksum += (uint8_t)((uint8_t*)&BLEavaildatainfo)[c];
|
||||
}
|
||||
BLE_upload_state = BLE_UPLOAD_STATE_INIT;
|
||||
ble_main_state = BLE_MAIN_STATE_ATC_BLE_OEPL_UPLOAD;
|
||||
BLE_new_notify = true; // trigger the upload here
|
||||
} else {
|
||||
free(BLE_image_buffer);
|
||||
if (BLE_err_counter++ >= 5) { // 5 Retries for a BLE Connection
|
||||
struct espXferComplete reportStruct;
|
||||
memcpy((uint8_t*)&reportStruct.src, BLE_curr_address, 8);
|
||||
processXferComplete(&reportStruct, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // This is a Gicisky display
|
||||
// Here we create the compressed buffer
|
||||
BLE_image_buffer = (uint8_t*)malloc(BUFFER_MAX_SIZE_COMPRESSING);
|
||||
if (BLE_image_buffer == nullptr) {
|
||||
Serial.println("BLE Could not create buffer!");
|
||||
BLE_compressed_len = 0;
|
||||
} else {
|
||||
BLE_compressed_len = compress_image(BLE_curr_address, BLE_image_buffer, BUFFER_MAX_SIZE_COMPRESSING);
|
||||
Serial.printf("BLE Compressed Length: %i\r\n", BLE_compressed_len);
|
||||
// then we connect to BLE to send the compressed data
|
||||
if (BLE_compressed_len && BLE_connect(BLE_curr_address, BLE_TYPE_GICISKY)) {
|
||||
BLE_err_counter = 0;
|
||||
BLE_curr_part = 0;
|
||||
memset(BLE_notify_buffer, 0x00, sizeof(BLE_notify_buffer));
|
||||
BLE_upload_state = BLE_UPLOAD_STATE_INIT;
|
||||
ble_main_state = BLE_MAIN_STATE_UPLOAD;
|
||||
BLE_new_notify = true; // trigger the upload here
|
||||
} else {
|
||||
free(BLE_image_buffer);
|
||||
if (BLE_err_counter++ >= 5) { // 5 Retries for a BLE Connection
|
||||
struct espXferComplete reportStruct;
|
||||
memcpy((uint8_t*)&reportStruct.src, BLE_curr_address, 8);
|
||||
processXferComplete(&reportStruct, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BLE_last_pending_check = millis();
|
||||
@@ -196,25 +320,25 @@ void BLETask(void* parameter) {
|
||||
switch (BLE_upload_state) {
|
||||
default:
|
||||
case BLE_UPLOAD_STATE_INIT:
|
||||
BLE_buff[0] = 1;
|
||||
ctrlChar->writeValue(BLE_buff, 1);
|
||||
BLE_mini_buff[0] = 1;
|
||||
ctrlChar->writeValue(BLE_mini_buff, 1);
|
||||
break;
|
||||
case BLE_UPLOAD_STATE_SIZE:
|
||||
BLE_buff[0] = 0x02;
|
||||
BLE_buff[1] = compressed_len & 0xff;
|
||||
BLE_buff[2] = (compressed_len >> 8) & 0xff;
|
||||
BLE_buff[3] = (compressed_len >> 16) & 0xff;
|
||||
BLE_buff[4] = (compressed_len >> 24) & 0xff;
|
||||
BLE_buff[5] = 0x00;
|
||||
ctrlChar->writeValue(BLE_buff, 6);
|
||||
BLE_mini_buff[0] = 0x02;
|
||||
BLE_mini_buff[1] = BLE_compressed_len & 0xff;
|
||||
BLE_mini_buff[2] = (BLE_compressed_len >> 8) & 0xff;
|
||||
BLE_mini_buff[3] = (BLE_compressed_len >> 16) & 0xff;
|
||||
BLE_mini_buff[4] = (BLE_compressed_len >> 24) & 0xff;
|
||||
BLE_mini_buff[5] = 0x00;
|
||||
ctrlChar->writeValue(BLE_mini_buff, 6);
|
||||
break;
|
||||
case BLE_UPLOAD_STATE_START:
|
||||
BLE_buff[0] = 0x03;
|
||||
ctrlChar->writeValue(BLE_buff, 1);
|
||||
BLE_mini_buff[0] = 0x03;
|
||||
ctrlChar->writeValue(BLE_mini_buff, 1);
|
||||
break;
|
||||
case BLE_UPLOAD_STATE_UPLOAD:
|
||||
if (BLE_notify_buffer[2] == 0x08) {
|
||||
free(buffer);
|
||||
free(BLE_image_buffer);
|
||||
pClient->disconnect();
|
||||
ble_main_state = BLE_MAIN_STATE_IDLE;
|
||||
BLE_last_pending_check = millis();
|
||||
@@ -222,35 +346,103 @@ void BLETask(void* parameter) {
|
||||
struct espXferComplete reportStruct;
|
||||
memcpy((uint8_t*)&reportStruct.src, BLE_curr_address, 8);
|
||||
processXferComplete(&reportStruct, true);
|
||||
curr_part = 0;
|
||||
BLE_err_counter = 0;
|
||||
BLE_curr_part = 0;
|
||||
} else {
|
||||
uint32_t req_curr_part = (BLE_notify_buffer[6] << 24) | (BLE_notify_buffer[5] << 24) | (BLE_notify_buffer[4] << 24) | BLE_notify_buffer[3];
|
||||
if (req_curr_part != curr_part) {
|
||||
Serial.printf("Something went wrong, expected req part: %i but got: %i we better abort here.\r\n", req_curr_part, curr_part);
|
||||
free(buffer);
|
||||
if (req_curr_part != BLE_curr_part) {
|
||||
Serial.printf("Something went wrong, expected req part: %i but got: %i we better abort here.\r\n", req_curr_part, BLE_curr_part);
|
||||
free(BLE_image_buffer);
|
||||
pClient->disconnect();
|
||||
ble_main_state = BLE_MAIN_STATE_IDLE;
|
||||
BLE_last_pending_check = millis();
|
||||
}
|
||||
uint32_t curr_len = 240;
|
||||
if (compressed_len - (curr_part * 240) < 240)
|
||||
curr_len = compressed_len - (curr_part * 240);
|
||||
BLE_buff[0] = curr_part & 0xff;
|
||||
BLE_buff[1] = (curr_part >> 8) & 0xff;
|
||||
BLE_buff[2] = (curr_part >> 16) & 0xff;
|
||||
BLE_buff[3] = (curr_part >> 24) & 0xff;
|
||||
memcpy((uint8_t*)&BLE_buff[4], (uint8_t*)&buffer[curr_part * 240], curr_len);
|
||||
imgChar->writeValue(BLE_buff, curr_len + 4);
|
||||
Serial.printf("BLE sending part: %i\r\n", curr_part);
|
||||
curr_part++;
|
||||
if (BLE_compressed_len - (BLE_curr_part * 240) < 240)
|
||||
curr_len = BLE_compressed_len - (BLE_curr_part * 240);
|
||||
BLE_mini_buff[0] = BLE_curr_part & 0xff;
|
||||
BLE_mini_buff[1] = (BLE_curr_part >> 8) & 0xff;
|
||||
BLE_mini_buff[2] = (BLE_curr_part >> 16) & 0xff;
|
||||
BLE_mini_buff[3] = (BLE_curr_part >> 24) & 0xff;
|
||||
memcpy((uint8_t*)&BLE_mini_buff[4], (uint8_t*)&BLE_image_buffer[BLE_curr_part * 240], curr_len);
|
||||
imgChar->writeValue(BLE_mini_buff, curr_len + 4);
|
||||
Serial.printf("BLE sending part: %i\r\n", BLE_curr_part);
|
||||
BLE_curr_part++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (millis() - BLE_last_notify > 30000) { // Something odd, better reset connection!
|
||||
Serial.println("BLE err going back to IDLE");
|
||||
free(buffer);
|
||||
free(BLE_image_buffer);
|
||||
pClient->disconnect();
|
||||
BLE_err_counter = 0;
|
||||
ble_main_state = BLE_MAIN_STATE_IDLE;
|
||||
BLE_last_pending_check = millis();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BLE_MAIN_STATE_ATC_BLE_OEPL_UPLOAD: {
|
||||
if (BLE_connected && BLE_new_notify) {
|
||||
BLE_new_notify = false;
|
||||
BLE_last_notify = millis();
|
||||
switch (BLE_upload_state) {
|
||||
default:
|
||||
case BLE_UPLOAD_STATE_INIT:
|
||||
BLE_mini_buff[0] = 0x00;
|
||||
BLE_mini_buff[1] = 0x64;
|
||||
memcpy((uint8_t*)&BLE_mini_buff[2], &BLEavaildatainfo, sizeof(struct AvailDataInfo));
|
||||
ctrlChar->writeValue(BLE_mini_buff, sizeof(struct AvailDataInfo) + 2);
|
||||
BLE_upload_state = BLE_UPLOAD_STATE_UPLOAD;
|
||||
break;
|
||||
case BLE_UPLOAD_STATE_UPLOAD: {
|
||||
uint8_t notifyLen = BLE_notify_buffer[0];
|
||||
uint16_t notifyCMD = (BLE_notify_buffer[1] << 8) | BLE_notify_buffer[2];
|
||||
Serial.println("BLE CMD " + String(notifyCMD));
|
||||
switch (notifyCMD) {
|
||||
case BLE_CMD_REQ:
|
||||
if (notifyLen == (sizeof(struct blockRequest) + 2)) {
|
||||
Serial.println("We got a request for a BLK");
|
||||
memcpy(&BLEblkRequst, &BLE_notify_buffer[3], sizeof(struct blockRequest));
|
||||
BLE_curr_part = 0;
|
||||
ATC_BLE_OEPL_PrepareBlk(BLEblkRequst.blockId);
|
||||
ATC_BLE_OEPL_SendPart(BLEblkRequst.blockId, BLE_curr_part);
|
||||
}
|
||||
break;
|
||||
case BLE_CMD_ACK_BLKPRT:
|
||||
BLE_curr_part++;
|
||||
BLE_err_counter = 0;
|
||||
case BLE_CMD_ERR_BLKPRT:
|
||||
if (BLE_curr_part <= BLE_max_block_parts && BLE_err_counter++ < 15) {
|
||||
ATC_BLE_OEPL_SendPart(BLEblkRequst.blockId, BLE_curr_part);
|
||||
break;
|
||||
} // FALLTROUGH!!! We cancel the upload if we land here since we dont have so many parts of a block!
|
||||
case BLE_CMD_ACK:
|
||||
case BLE_CMD_ACK_IS_SHOWN:
|
||||
case BLE_CMD_ACK_FW_UPDATED:
|
||||
Serial.println("BLE Upload done");
|
||||
free(BLE_image_buffer);
|
||||
pClient->disconnect();
|
||||
ble_main_state = BLE_MAIN_STATE_IDLE;
|
||||
BLE_last_pending_check = millis();
|
||||
// Done and the image is refreshing now
|
||||
struct espXferComplete reportStruct;
|
||||
memcpy((uint8_t*)&reportStruct.src, BLE_curr_address, 8);
|
||||
processXferComplete(&reportStruct, true);
|
||||
BLE_err_counter = 0;
|
||||
BLE_max_block_parts = 0;
|
||||
BLE_curr_part = 0;
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
} else {
|
||||
if (millis() - BLE_last_notify > 30000) { // Something odd, better reset connection!
|
||||
Serial.println("BLE err going back to IDLE");
|
||||
free(BLE_image_buffer);
|
||||
pClient->disconnect();
|
||||
BLE_err_counter = 0;
|
||||
ble_main_state = BLE_MAIN_STATE_IDLE;
|
||||
BLE_last_pending_check = millis();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,14 @@
|
||||
#include "storage.h"
|
||||
#include "tag_db.h"
|
||||
#include "web.h"
|
||||
#include "espflasher.h"
|
||||
#include "util.h"
|
||||
|
||||
#define LOG(format, ... ) Serial.printf(format,## __VA_ARGS__)
|
||||
|
||||
#ifndef FLASHER_DEBUG_PORT
|
||||
#define FLASHER_DEBUG_PORT 2
|
||||
#endif
|
||||
|
||||
esp_loader_error_t connect_to_target(uint32_t higher_transmission_rate) {
|
||||
esp_loader_connect_args_t connect_config = ESP_LOADER_CONNECT_DEFAULT();
|
||||
@@ -120,150 +128,197 @@ esp_loader_error_t flash_binary(String &file_path, size_t address) {
|
||||
|
||||
bool downloadAndWriteBinary(String &filename, const char *url) {
|
||||
HTTPClient binaryHttp;
|
||||
Serial.println(url);
|
||||
bool Ret = false;
|
||||
bool bHaveFsMutex = false;
|
||||
|
||||
LOG("downloadAndWriteBinary: url %s\n",url);
|
||||
binaryHttp.begin(url);
|
||||
binaryHttp.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
|
||||
int binaryResponseCode = binaryHttp.GET();
|
||||
Serial.println(binaryResponseCode);
|
||||
if (binaryResponseCode == HTTP_CODE_OK) {
|
||||
do {
|
||||
int binaryResponseCode = binaryHttp.GET();
|
||||
if(binaryResponseCode != HTTP_CODE_OK) {
|
||||
wsSerial("http error " + String(binaryResponseCode) + " fetching " + String(url));
|
||||
break;
|
||||
}
|
||||
int contentLength = binaryHttp.getSize();
|
||||
Serial.println(contentLength);
|
||||
LOG("contentLength %d\r\n",contentLength);
|
||||
if(contentLength < 0) {
|
||||
wsSerial("Couldn't get contentLength");
|
||||
break;
|
||||
}
|
||||
xSemaphoreTake(fsMutex, portMAX_DELAY);
|
||||
bHaveFsMutex = true;
|
||||
File file = contentFS->open(filename, "wb");
|
||||
if (file) {
|
||||
wsSerial("downloading " + String(filename));
|
||||
WiFiClient *stream = binaryHttp.getStreamPtr();
|
||||
uint8_t buffer[1024];
|
||||
size_t totalBytesRead = 0;
|
||||
time_t timeOut = millis() + 5000;
|
||||
// while (stream->available()) {
|
||||
while (millis() < timeOut) {
|
||||
size_t bytesRead = stream->readBytes(buffer, sizeof(buffer));
|
||||
if(!file) {
|
||||
wsSerial("file open error " + String(filename));
|
||||
break;
|
||||
}
|
||||
wsSerial("downloading " + String(filename));
|
||||
WiFiClient *stream = binaryHttp.getStreamPtr();
|
||||
uint8_t buffer[1024];
|
||||
size_t totalBytesRead = 0;
|
||||
// timeout if we don't average at least 1k bytes/second
|
||||
unsigned long timeOut = millis() + contentLength;
|
||||
while(stream->connected() && totalBytesRead < contentLength) {
|
||||
size_t bytesRead;
|
||||
size_t bytesToRead;
|
||||
if(stream->available()) {
|
||||
bytesToRead = min(sizeof(buffer), (size_t) stream->available());
|
||||
bytesRead = stream->readBytes(buffer, bytesToRead);
|
||||
if(bytesRead == 0 || millis() > timeOut) {
|
||||
wsSerial("Download time out");
|
||||
break;
|
||||
}
|
||||
file.write(buffer, bytesRead);
|
||||
totalBytesRead += bytesRead;
|
||||
if (totalBytesRead == contentLength) break;
|
||||
vTaskDelay(1 / portTICK_PERIOD_MS);
|
||||
} else {
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
file.close();
|
||||
xSemaphoreGive(fsMutex);
|
||||
binaryHttp.end();
|
||||
|
||||
file = contentFS->open(filename, "r");
|
||||
if (file) {
|
||||
if (totalBytesRead == contentLength || (contentLength == 0 && file.size() > 0)) {
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
wsSerial("Download failed, " + String(file.size()) + " bytes");
|
||||
file.close();
|
||||
}
|
||||
} else {
|
||||
xSemaphoreGive(fsMutex);
|
||||
wsSerial("file open error " + String(filename));
|
||||
}
|
||||
} else {
|
||||
wsSerial("http error " + String(binaryResponseCode) + " fetching " + String(url));
|
||||
}
|
||||
file.close();
|
||||
|
||||
if(!stream->connected()) {
|
||||
wsSerial("Connection dropped during transfer");
|
||||
break;
|
||||
}
|
||||
file = contentFS->open(filename, "r");
|
||||
if(!file) {
|
||||
wsSerial("file open error " + String(filename));
|
||||
break;
|
||||
}
|
||||
if(file.size() == contentLength) {
|
||||
Ret = true;
|
||||
} else {
|
||||
wsSerial("Download failed, " + String(file.size()) + " bytes");
|
||||
}
|
||||
file.close();
|
||||
} while(false);
|
||||
binaryHttp.setReuse(false);
|
||||
binaryHttp.end();
|
||||
return false;
|
||||
if(bHaveFsMutex) {
|
||||
xSemaphoreGive(fsMutex);
|
||||
}
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
bool doC6flash(uint8_t doDownload) {
|
||||
String filenameFirmwareLocal = "/firmware.json";
|
||||
DynamicJsonDocument jsonDoc(1024);
|
||||
if (doDownload) {
|
||||
const String githubUrl = "https://raw.githubusercontent.com/" + config.repo + "/master/binaries/ESP32-C6/firmware.json";
|
||||
if (downloadAndWriteBinary(filenameFirmwareLocal, githubUrl.c_str())) {
|
||||
File readfile = contentFS->open(filenameFirmwareLocal, "r");
|
||||
if (!readfile) {
|
||||
Serial.println("load firmware.json: Failed to open file");
|
||||
return false;
|
||||
}
|
||||
DeserializationError jsonError = deserializeJson(jsonDoc, readfile);
|
||||
bool FlashC6_H2(const char *RepoUrl) {
|
||||
String JsonFilename = "/firmware_" SHORT_CHIP_NAME ".json" ;
|
||||
bool Ret = false;
|
||||
bool bLoaderInit = false;
|
||||
bool bDownload = strlen(RepoUrl) > 0;
|
||||
int retry;
|
||||
JsonDocument jsonDoc;
|
||||
|
||||
if (!jsonError) {
|
||||
JsonArray jsonArray = jsonDoc.as<JsonArray>();
|
||||
for (JsonObject obj : jsonArray) {
|
||||
String filename = "/" + obj["filename"].as<String>();
|
||||
// String binaryUrl = "https://raw.githubusercontent.com/" + config.repo + "/master/binaries/ESP32-C6" + String(filename);
|
||||
String binaryUrl = "http://www.openepaperlink.eu/binaries/ESP32-C6" + String(filename);
|
||||
for (int retry = 0; retry < 10; retry++) {
|
||||
if (downloadAndWriteBinary(filename, binaryUrl.c_str())) {
|
||||
break;
|
||||
}
|
||||
wsSerial("Retry " + String(retry));
|
||||
if (retry < 9) {
|
||||
delay(1000);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
wsSerial("json error fetching " + String(githubUrl));
|
||||
return false;
|
||||
LOG("%s#%d: ",__FUNCTION__,__LINE__); util::printHeap();
|
||||
|
||||
do {
|
||||
if(bDownload) {
|
||||
String FileUrl = RepoUrl + JsonFilename;
|
||||
if(!downloadAndWriteBinary(JsonFilename, FileUrl.c_str())) {
|
||||
LOG("%s#%d: ",__FUNCTION__,__LINE__); util::printHeap();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
File readfile = contentFS->open(filenameFirmwareLocal, "r");
|
||||
if (!readfile) {
|
||||
Serial.println("load local firmware.json: Failed to open file");
|
||||
return false;
|
||||
|
||||
File readfile = contentFS->open(JsonFilename, "r");
|
||||
if(!readfile) {
|
||||
wsSerial("load " + JsonFilename + ": Failed to open file");
|
||||
return true;
|
||||
}
|
||||
DeserializationError jsonError = deserializeJson(jsonDoc, readfile);
|
||||
}
|
||||
|
||||
const loader_esp32_config_t config = {
|
||||
.baud_rate = 115200,
|
||||
.uart_port = 2,
|
||||
.uart_rx_pin = FLASHER_DEBUG_TXD,
|
||||
.uart_tx_pin = FLASHER_DEBUG_RXD,
|
||||
.reset_trigger_pin = FLASHER_AP_RESET,
|
||||
.gpio0_trigger_pin = FLASHER_DEBUG_PROG,
|
||||
};
|
||||
if(jsonError) {
|
||||
wsSerial(String("json error parsing") + JsonFilename);
|
||||
break;
|
||||
}
|
||||
|
||||
if (loader_port_esp32_init(&config) != ESP_LOADER_SUCCESS) {
|
||||
wsSerial("Serial initialization failed");
|
||||
loader_port_esp32_deinit();
|
||||
return false;
|
||||
}
|
||||
if(!bDownload) {
|
||||
Ret = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (connect_to_target(115200) == ESP_LOADER_SUCCESS) {
|
||||
if (esp_loader_get_target() == ESP32C6_CHIP) {
|
||||
wsSerial("Connected to ESP32-C6");
|
||||
int maxRetries = 5;
|
||||
esp_loader_error_t err;
|
||||
JsonArray jsonArray = jsonDoc.as<JsonArray>();
|
||||
for(JsonObject obj : jsonArray) {
|
||||
String filename = "/" + obj["filename"].as<String>();
|
||||
String binaryUrl = RepoUrl + filename;
|
||||
|
||||
JsonArray jsonArray = jsonDoc.as<JsonArray>();
|
||||
for (JsonObject obj : jsonArray) {
|
||||
String filename = "/" + obj["filename"].as<String>();
|
||||
const char *addressStr = obj["address"];
|
||||
uint32_t address = strtoul(addressStr, NULL, 16);
|
||||
|
||||
for (int retry = 0; retry < maxRetries; retry++) {
|
||||
err = flash_binary(filename, address);
|
||||
if (err == ESP_LOADER_SUCCESS) break;
|
||||
Serial.printf("Flash failed with error %d. Retrying...\n", err);
|
||||
for(retry = 0; retry < 10; retry++) {
|
||||
if(downloadAndWriteBinary(filename, binaryUrl.c_str())) {
|
||||
break;
|
||||
}
|
||||
wsSerial("Retry " + String(retry));
|
||||
if(retry < 9) {
|
||||
delay(1000);
|
||||
}
|
||||
if (err != ESP_LOADER_SUCCESS) {
|
||||
loader_port_esp32_deinit();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println("Done!");
|
||||
} else {
|
||||
wsSerial("Connected to wrong ESP32 type");
|
||||
loader_port_esp32_deinit();
|
||||
return false;
|
||||
if(retry == 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
wsSerial("Connection to the C6 failed");
|
||||
if(retry < 10) {
|
||||
Ret = true;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
if(Ret == true) do {
|
||||
Ret = false;
|
||||
const loader_esp32_config_t config = {
|
||||
.baud_rate = 115200,
|
||||
.uart_port = FLASHER_DEBUG_PORT,
|
||||
.uart_rx_pin = FLASHER_DEBUG_TXD,
|
||||
.uart_tx_pin = FLASHER_DEBUG_RXD,
|
||||
.reset_trigger_pin = FLASHER_AP_RESET,
|
||||
.gpio0_trigger_pin = FLASHER_DEBUG_PROG,
|
||||
};
|
||||
|
||||
bLoaderInit = true;
|
||||
if(loader_port_esp32_init(&config) != ESP_LOADER_SUCCESS) {
|
||||
wsSerial("Serial initialization failed");
|
||||
break;
|
||||
}
|
||||
|
||||
if(connect_to_target(115200) != ESP_LOADER_SUCCESS) {
|
||||
wsSerial("Connection to the " SHORT_CHIP_NAME " failed");
|
||||
break;
|
||||
}
|
||||
|
||||
if(esp_loader_get_target() != ESP_CHIP_TYPE) {
|
||||
wsSerial("Connected to wrong ESP32 type");
|
||||
break;
|
||||
}
|
||||
wsSerial("Connected to ESP32-" SHORT_CHIP_NAME);
|
||||
int maxRetries = 5;
|
||||
esp_loader_error_t err;
|
||||
|
||||
JsonArray jsonArray = jsonDoc.as<JsonArray>();
|
||||
for(JsonObject obj : jsonArray) {
|
||||
String filename = "/" + obj["filename"].as<String>();
|
||||
const char *addressStr = obj["address"];
|
||||
uint32_t address = strtoul(addressStr, NULL, 16);
|
||||
|
||||
for(int retry = 0; retry < maxRetries; retry++) {
|
||||
err = flash_binary(filename, address);
|
||||
if(err == ESP_LOADER_SUCCESS) {
|
||||
Ret = true;
|
||||
break;
|
||||
}
|
||||
Serial.printf("Flash failed with error %d. Retrying...\n", err);
|
||||
delay(1000);
|
||||
}
|
||||
if(err != ESP_LOADER_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Serial.println("Done!");
|
||||
} while(false);
|
||||
|
||||
if(bLoaderInit) {
|
||||
loader_port_esp32_deinit();
|
||||
return false;
|
||||
}
|
||||
loader_port_esp32_deinit();
|
||||
return true;
|
||||
|
||||
LOG("%s#%d: ",__FUNCTION__,__LINE__); util::printHeap();
|
||||
return Ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "storage.h"
|
||||
#include "time.h"
|
||||
#include "zbs_interface.h"
|
||||
#include <WiFi.h>
|
||||
|
||||
#ifdef HAS_EXT_FLASHER
|
||||
#include "webflasher.h"
|
||||
@@ -178,7 +179,7 @@ bool flasher::getInfoBlockType() {
|
||||
}
|
||||
|
||||
bool flasher::findTagByMD5() {
|
||||
DynamicJsonDocument doc(3000);
|
||||
JsonDocument doc;
|
||||
fs::File readfile = contentFS->open("/tag_md5_db.json", "r");
|
||||
DeserializationError err = deserializeJson(doc, readfile);
|
||||
if (!err) {
|
||||
@@ -207,7 +208,7 @@ bool flasher::findTagByMD5() {
|
||||
}
|
||||
|
||||
bool flasher::findTagByType(uint8_t type) {
|
||||
DynamicJsonDocument doc(3000);
|
||||
JsonDocument doc;
|
||||
fs::File readfile = contentFS->open("/tag_md5_db.json", "r");
|
||||
DeserializationError err = deserializeJson(doc, readfile);
|
||||
if (!err) {
|
||||
@@ -265,7 +266,7 @@ bool flasher::getFirmwareMac() {
|
||||
void flasher::getMacFromWiFi() {
|
||||
mac[0] = 0x00;
|
||||
mac[1] = 0x00;
|
||||
esp_read_mac(mac + 2, ESP_MAC_WIFI_SOFTAP);
|
||||
WiFi.softAPmacAddress(mac + 2);
|
||||
}
|
||||
|
||||
bool flasher::backupFlash() {
|
||||
@@ -447,7 +448,7 @@ bool flasher::writeFlashFromPackOffset(fs::File *file, uint16_t length) {
|
||||
}
|
||||
|
||||
bool flasher::writeFlashFromPack(String filename, uint8_t type) {
|
||||
DynamicJsonDocument doc(1024);
|
||||
JsonDocument doc;
|
||||
fs::File readfile = contentFS->open(filename, "r");
|
||||
DeserializationError err = deserializeJson(doc, readfile);
|
||||
if (!err) {
|
||||
@@ -507,7 +508,7 @@ bool flasher::writeBlock(uint16_t offset, uint8_t *data, uint16_t len, bool info
|
||||
|
||||
#ifndef C6_OTA_FLASHING
|
||||
uint16_t getAPUpdateVersion(uint8_t type) {
|
||||
StaticJsonDocument<512> doc;
|
||||
JsonDocument doc;
|
||||
fs::File readfile = contentFS->open("/AP_FW_Pack.bin", "r");
|
||||
DeserializationError err = deserializeJson(doc, readfile);
|
||||
if (!err) {
|
||||
|
||||
203
ESP32_AP-Flasher/src/g5/Group5.h
Normal file
203
ESP32_AP-Flasher/src/g5/Group5.h
Normal file
@@ -0,0 +1,203 @@
|
||||
#ifndef __GROUP5__
|
||||
#define __GROUP5__
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#ifdef __AVR__
|
||||
#include <avr/pgmspace.h>
|
||||
#endif
|
||||
//
|
||||
// Group5 1-bit image compression library
|
||||
// Written by Larry Bank
|
||||
// Copyright (c) 2024 BitBank Software, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file ./LICENSE.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// ./APL.txt.
|
||||
//
|
||||
// The name "Group5" is derived from the CCITT Group4 standard
|
||||
// This code is based on a lot of the good ideas from CCITT T.6
|
||||
// for FAX image compression, but modified to work in a very
|
||||
// constrained environment. The Huffman tables for horizontal
|
||||
// mode have been replaced with a simple 2-bit flag followed by
|
||||
// short or long counts of a fixed length. The short codes are
|
||||
// always 3 bits (run lengths 0-7) and the long codes are the
|
||||
// number of bits needed to encode the width of the image.
|
||||
// For example, if a 320 pixel wide image is being compressed,
|
||||
// the longest horizontal run needed is 320, which requires 9
|
||||
// bits to encode. The 2 prefix bits have the following meaning:
|
||||
// 00 = short, short (3+3 bits)
|
||||
// 01 = short, long (3+N bits)
|
||||
// 10 = long, short (N+3 bits)
|
||||
// 11 = long, long (N+N bits)
|
||||
// The rest of the code works identically to Group4 2D FAX
|
||||
//
|
||||
// Caution - this is the maximum number of color changes per line
|
||||
// The default value is set low to work embedded systems with little RAM
|
||||
// for font compression, this is plenty since each line of a character should have
|
||||
// a maximum of 7 color changes
|
||||
// You can define this in your compiler macros to override the default vlaue
|
||||
//
|
||||
|
||||
|
||||
#define MAX_IMAGE_FLIPS 640
|
||||
|
||||
#ifndef MAX_IMAGE_FLIPS
|
||||
#ifdef __AVR__
|
||||
#define MAX_IMAGE_FLIPS 32
|
||||
#else
|
||||
#define MAX_IMAGE_FLIPS 512
|
||||
#endif // __AVR__
|
||||
#endif
|
||||
// Horizontal prefix bits
|
||||
enum {
|
||||
HORIZ_SHORT_SHORT=0,
|
||||
HORIZ_SHORT_LONG,
|
||||
HORIZ_LONG_SHORT,
|
||||
HORIZ_LONG_LONG
|
||||
};
|
||||
|
||||
// Return code for encoder and decoder
|
||||
enum {
|
||||
G5_SUCCESS = 0,
|
||||
G5_INVALID_PARAMETER,
|
||||
G5_DECODE_ERROR,
|
||||
G5_UNSUPPORTED_FEATURE,
|
||||
G5_ENCODE_COMPLETE,
|
||||
G5_DECODE_COMPLETE,
|
||||
G5_NOT_INITIALIZED,
|
||||
G5_DATA_OVERFLOW,
|
||||
G5_MAX_FLIPS_EXCEEDED
|
||||
};
|
||||
//
|
||||
// Decoder state
|
||||
//
|
||||
typedef struct g5_dec_image_tag
|
||||
{
|
||||
int iWidth, iHeight; // image size
|
||||
int iError;
|
||||
int y; // last y value drawn
|
||||
int iVLCSize;
|
||||
int iHLen; // length of 'long' horizontal codes for this image
|
||||
int iPitch; // width in bytes of output buffer
|
||||
uint32_t u32Accum; // fractional scaling accumulator
|
||||
uint32_t ulBitOff, ulBits; // vlc decode variables
|
||||
uint8_t *pSrc, *pBuf; // starting & current buffer pointer
|
||||
int16_t *pCur, *pRef; // current state of current vs reference flips
|
||||
int16_t CurFlips[MAX_IMAGE_FLIPS];
|
||||
int16_t RefFlips[MAX_IMAGE_FLIPS];
|
||||
} G5DECIMAGE;
|
||||
|
||||
// Due to unaligned memory causing an exception, we have to do these macros the slow way
|
||||
#ifdef __AVR__
|
||||
// assume PROGMEM as the source of data
|
||||
inline uint32_t TIFFMOTOLONG(uint8_t *p)
|
||||
{
|
||||
uint32_t u32 = pgm_read_dword(p);
|
||||
return __builtin_bswap32(u32);
|
||||
}
|
||||
#else
|
||||
#define TIFFMOTOLONG(p) (((uint32_t)(*p)<<24UL) + ((uint32_t)(*(p+1))<<16UL) + ((uint32_t)(*(p+2))<<8UL) + (uint32_t)(*(p+3)))
|
||||
#endif // __AVR__
|
||||
|
||||
#define TOP_BIT 0x80000000
|
||||
#define MAX_VALUE 0xffffffff
|
||||
// Must be a 32-bit target processor
|
||||
#define REGISTER_WIDTH 32
|
||||
#define BIGUINT uint32_t
|
||||
|
||||
//
|
||||
// G5 Encoder
|
||||
//
|
||||
|
||||
typedef struct pil_buffered_bits
|
||||
{
|
||||
unsigned char *pBuf; // buffer pointer
|
||||
uint32_t ulBits; // buffered bits
|
||||
uint32_t ulBitOff; // current bit offset
|
||||
uint32_t ulDataSize; // available data
|
||||
} BUFFERED_BITS;
|
||||
|
||||
//
|
||||
// Encoder state
|
||||
//
|
||||
typedef struct g5_enc_image_tag
|
||||
{
|
||||
int iWidth, iHeight; // image size
|
||||
int iError;
|
||||
int y; // last y encoded
|
||||
int iOutSize;
|
||||
int iDataSize; // generated output size
|
||||
uint8_t *pOutBuf;
|
||||
int16_t *pCur, *pRef; // pointers to swap current and reference lines
|
||||
BUFFERED_BITS bb;
|
||||
int16_t CurFlips[MAX_IMAGE_FLIPS];
|
||||
int16_t RefFlips[MAX_IMAGE_FLIPS];
|
||||
} G5ENCIMAGE;
|
||||
|
||||
// 16-bit marker at the start of a BB_FONT file
|
||||
// (BitBank FontFile)
|
||||
#define BB_FONT_MARKER 0xBBFF
|
||||
// 16-bit marker at the start of a BB_BITMAP file
|
||||
// (BitBank BitmapFile)
|
||||
#define BB_BITMAP_MARKER 0xBBBF
|
||||
|
||||
// Font info per character (glyph)
|
||||
typedef struct {
|
||||
uint16_t bitmapOffset; // Offset to compressed bitmap data for this glyph (starting from the end of the BB_GLYPH[] array)
|
||||
uint8_t width; // bitmap width in pixels
|
||||
uint8_t xAdvance; // total width in pixels (bitmap + padding)
|
||||
uint16_t height; // bitmap height in pixels
|
||||
int16_t xOffset; // left padding to upper left corner
|
||||
int16_t yOffset; // padding from baseline to upper left corner (usually negative)
|
||||
} BB_GLYPH;
|
||||
|
||||
// This structure is stored at the beginning of a BB_FONT file
|
||||
typedef struct {
|
||||
uint16_t u16Marker; // 16-bit Marker defining a BB_FONT file
|
||||
uint16_t first; // first char (ASCII value)
|
||||
uint16_t last; // last char (ASCII value)
|
||||
uint16_t height; // total height of font
|
||||
uint32_t rotation; // store this as 32-bits to not have a struct packing problem
|
||||
BB_GLYPH glyphs[]; // Array of glyphs (one for each char)
|
||||
} BB_FONT;
|
||||
|
||||
// This structure defines the start of a compressed bitmap file
|
||||
typedef struct {
|
||||
uint16_t u16Marker; // 16-bit marker defining a BB_BITMAP file
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint16_t size; // compressed data size (not including this 8-byte header)
|
||||
} BB_BITMAP;
|
||||
|
||||
#ifdef __cplusplus
|
||||
//
|
||||
// The G5 classes wrap portable C code which does the actual work
|
||||
//
|
||||
class G5ENCODER
|
||||
{
|
||||
public:
|
||||
int init(int iWidth, int iHeight, uint8_t *pOut, int iOutSize);
|
||||
int encodeLine(uint8_t *pPixels);
|
||||
int size();
|
||||
|
||||
private:
|
||||
G5ENCIMAGE _g5enc;
|
||||
};
|
||||
class G5DECODER
|
||||
{
|
||||
public:
|
||||
int init(int iWidth, int iHeight, uint8_t *pData, int iDataSize);
|
||||
int decodeLine(uint8_t *pOut);
|
||||
|
||||
private:
|
||||
G5DECIMAGE _g5dec;
|
||||
};
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // __GROUP5__
|
||||
338
ESP32_AP-Flasher/src/g5/g5dec.inl
Normal file
338
ESP32_AP-Flasher/src/g5/g5dec.inl
Normal file
@@ -0,0 +1,338 @@
|
||||
//
|
||||
// Group5
|
||||
// A 1-bpp image decoder
|
||||
//
|
||||
// Written by Larry Bank
|
||||
// Copyright (c) 2024 BitBank Software, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file ./LICENSE.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// ./APL.txt.
|
||||
#include "Group5.h"
|
||||
|
||||
/*
|
||||
The code tree that follows has: bit_length, decode routine
|
||||
These codes are for Group 4 (MMR) decoding
|
||||
|
||||
01 = vertneg1, 11h = vert1, 20h = horiz, 30h = pass, 12h = vert2
|
||||
02 = vertneg2, 13h = vert3, 03 = vertneg3, 90h = trash
|
||||
*/
|
||||
|
||||
static const uint8_t code_table[128] =
|
||||
{0x90, 0, 0x40, 0, /* trash, uncompr mode - codes 0 and 1 */
|
||||
3, 7, /* V(-3) pos = 2 */
|
||||
0x13, 7, /* V(3) pos = 3 */
|
||||
2, 6, 2, 6, /* V(-2) pos = 4,5 */
|
||||
0x12, 6, 0x12, 6, /* V(2) pos = 6,7 */
|
||||
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4, /* pass pos = 8->F */
|
||||
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4,
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, /* horiz pos = 10->1F */
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
|
||||
/* V(-1) pos = 20->2F */
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3, /* V(1) pos = 30->3F */
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3};
|
||||
|
||||
static int g5_decode_init(G5DECIMAGE *pImage, int iWidth, int iHeight, uint8_t *pData, int iDataSize)
|
||||
{
|
||||
if (pImage == NULL || iWidth < 1 || iHeight < 1 || pData == NULL || iDataSize < 1)
|
||||
return G5_INVALID_PARAMETER;
|
||||
|
||||
pImage->iVLCSize = iDataSize;
|
||||
pImage->pSrc = pData;
|
||||
pImage->ulBitOff = 0;
|
||||
pImage->y = 0;
|
||||
pImage->ulBits = TIFFMOTOLONG(pData); // preload the first 32 bits of data
|
||||
pImage->iWidth = iWidth;
|
||||
pImage->iHeight = iHeight;
|
||||
return G5_SUCCESS;
|
||||
|
||||
} /* g5_decode_init() */
|
||||
|
||||
static void G5DrawLine(G5DECIMAGE *pPage, int16_t *pCurFlips, uint8_t *pOut)
|
||||
{
|
||||
int x, len, run;
|
||||
uint8_t lBit, rBit, *p;
|
||||
int iStart = 0, xright = pPage->iWidth;
|
||||
uint8_t *pDest;
|
||||
|
||||
iStart = 0;
|
||||
|
||||
pDest = pOut;
|
||||
len = (xright+7)>>3; // number of bytes to generate
|
||||
for (x=0; x<len; x++) {
|
||||
pOut[x] = 0xff; // start with white and only draw the black runs
|
||||
}
|
||||
x = 0;
|
||||
while (x < xright) { // while the scaled x is within the window bounds
|
||||
x = *pCurFlips++; // black starting point
|
||||
run = *pCurFlips++ - x; // get the black run
|
||||
x -= iStart;
|
||||
if (x >= xright || run == 0)
|
||||
break;
|
||||
if ((x + run) > 0) { /* If the run is visible, draw it */
|
||||
if (x < 0) {
|
||||
run += x; /* draw only visible part of run */
|
||||
x = 0;
|
||||
}
|
||||
if ((x + run) > xright) { /* Don't let it go off right edge */
|
||||
run = xright - x;
|
||||
}
|
||||
/* Draw this run */
|
||||
lBit = 0xff << (8 - (x & 7));
|
||||
rBit = 0xff >> ((x + run) & 7);
|
||||
len = ((x+run)>>3) - (x >> 3);
|
||||
p = &pDest[x >> 3];
|
||||
if (len == 0) {
|
||||
lBit |= rBit;
|
||||
*p &= lBit;
|
||||
} else {
|
||||
*p++ &= lBit;
|
||||
while (len > 1) {
|
||||
*p++ = 0;
|
||||
len--;
|
||||
}
|
||||
*p = rBit;
|
||||
}
|
||||
} // visible run
|
||||
} /* while drawing line */
|
||||
} /* G5DrawLine() */
|
||||
//
|
||||
// Initialize internal structures to decode the image
|
||||
//
|
||||
static void Decode_Begin(G5DECIMAGE *pPage)
|
||||
{
|
||||
int i, xsize;
|
||||
int16_t *CurFlips, *RefFlips;
|
||||
|
||||
xsize = pPage->iWidth;
|
||||
|
||||
RefFlips = pPage->RefFlips;
|
||||
CurFlips = pPage->CurFlips;
|
||||
|
||||
/* Seed the current and reference line with XSIZE for V(0) codes */
|
||||
for (i=0; i<MAX_IMAGE_FLIPS-2; i++) {
|
||||
RefFlips[i] = xsize;
|
||||
CurFlips[i] = xsize;
|
||||
}
|
||||
/* Prefill both current and reference lines with 7fff to prevent it from
|
||||
walking off the end if the data gets bunged and the current X is > XSIZE
|
||||
3-16-94 */
|
||||
CurFlips[i] = RefFlips[i] = 0x7fff;
|
||||
CurFlips[i+1] = RefFlips[i+1] = 0x7fff;
|
||||
|
||||
pPage->pCur = CurFlips;
|
||||
pPage->pRef = RefFlips;
|
||||
pPage->pBuf = pPage->pSrc;
|
||||
pPage->ulBits = TIFFMOTOLONG(pPage->pSrc); // load 32 bits to start
|
||||
pPage->ulBitOff = 0;
|
||||
// Calculate the number of bits needed for a long horizontal code
|
||||
#ifdef __AVR__
|
||||
pPage->iHLen = 16 - __builtin_clz(pPage->iWidth);
|
||||
#else
|
||||
pPage->iHLen = 32 - __builtin_clz(pPage->iWidth);
|
||||
#endif
|
||||
} /* Decode_Begin() */
|
||||
//
|
||||
// Decode a single line of G5 data (private function)
|
||||
//
|
||||
static int DecodeLine(G5DECIMAGE *pPage)
|
||||
{
|
||||
signed int a0, a0_p, b1;
|
||||
int16_t *pCur, *pRef, *RefFlips, *CurFlips;
|
||||
int xsize, tot_run=0, tot_run1 = 0;
|
||||
int32_t sCode;
|
||||
uint32_t lBits;
|
||||
uint32_t ulBits, ulBitOff;
|
||||
uint8_t *pBuf/*, *pBufEnd*/;
|
||||
uint32_t u32HMask, u32HLen; // horizontal code mask and length
|
||||
|
||||
pCur = CurFlips = pPage->pCur;
|
||||
pRef = RefFlips = pPage->pRef;
|
||||
ulBits = pPage->ulBits;
|
||||
ulBitOff = pPage->ulBitOff;
|
||||
pBuf = pPage->pBuf;
|
||||
// pBufEnd = &pPage->pSrc[pPage->iVLCSize];
|
||||
u32HLen = pPage->iHLen;
|
||||
u32HMask = (1 << u32HLen) - 1;
|
||||
a0 = -1;
|
||||
xsize = pPage->iWidth;
|
||||
|
||||
while (a0 < xsize) { /* Decode this line */
|
||||
if (ulBitOff > (REGISTER_WIDTH-8)) { // need at least 7 unused bits
|
||||
pBuf += (ulBitOff >> 3);
|
||||
ulBitOff &= 7;
|
||||
ulBits = TIFFMOTOLONG(pBuf);
|
||||
}
|
||||
if ((int32_t)(ulBits << ulBitOff) < 0) { /* V(0) code is the most frequent case (1 bit) */
|
||||
a0 = *pRef++;
|
||||
ulBitOff++; // length = 1 bit
|
||||
*pCur++ = a0;
|
||||
} else { /* Slower method for the less frequence codes */
|
||||
lBits = (ulBits >> ((REGISTER_WIDTH - 8) - ulBitOff)) & 0xfe; /* Only the first 7 bits are useful */
|
||||
sCode = code_table[lBits]; /* Get the code type as an 8-bit value */
|
||||
ulBitOff += code_table[lBits+1]; /* Get the code length */
|
||||
switch (sCode) {
|
||||
case 1: /* V(-1) */
|
||||
case 2: /* V(-2) */
|
||||
case 3: /* V(-3) */
|
||||
a0 = *pRef - sCode; /* A0 = B1 - x */
|
||||
*pCur++ = a0;
|
||||
if (pRef == RefFlips) {
|
||||
pRef += 2;
|
||||
}
|
||||
pRef--;
|
||||
while (a0 >= *pRef) {
|
||||
pRef += 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x11: /* V(1) */
|
||||
case 0x12: /* V(2) */
|
||||
case 0x13: /* V(3) */
|
||||
a0 = *pRef++; /* A0 = B1 */
|
||||
b1 = a0;
|
||||
a0 += sCode & 7; /* A0 = B1 + x */
|
||||
if (b1 != xsize && a0 < xsize) {
|
||||
while (a0 >= *pRef) {
|
||||
pRef += 2;
|
||||
}
|
||||
}
|
||||
if (a0 > xsize) {
|
||||
a0 = xsize;
|
||||
}
|
||||
*pCur++ = a0;
|
||||
break;
|
||||
|
||||
case 0x20: /* Horizontal codes */
|
||||
if (ulBitOff > (REGISTER_WIDTH-16)) { // need at least 16 unused bits
|
||||
pBuf += (ulBitOff >> 3);
|
||||
ulBitOff &= 7;
|
||||
ulBits = TIFFMOTOLONG(pBuf);
|
||||
}
|
||||
a0_p = a0;
|
||||
if (a0 < 0) {
|
||||
a0_p = 0;
|
||||
}
|
||||
lBits = (ulBits >> ((REGISTER_WIDTH - 2) - ulBitOff)) & 0x3; // get 2-bit prefix for code type
|
||||
// There are 4 possible horizontal cases: short/short, short/long, long/short, long/long
|
||||
// These are encoded in a 2-bit prefix code, followed by 3 bits for short or N bits for long code
|
||||
// N is the log base 2 of the image width (e.g. 320 pixels requires 9 bits)
|
||||
ulBitOff += 2;
|
||||
switch (lBits) {
|
||||
case HORIZ_SHORT_SHORT:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
|
||||
ulBitOff += 3;
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
|
||||
ulBitOff += 3;
|
||||
break;
|
||||
case HORIZ_SHORT_LONG:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
|
||||
ulBitOff += 3;
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
|
||||
ulBitOff += u32HLen;
|
||||
break;
|
||||
case HORIZ_LONG_SHORT:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
|
||||
ulBitOff += u32HLen;
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
|
||||
ulBitOff += 3;
|
||||
break;
|
||||
case HORIZ_LONG_LONG:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
|
||||
ulBitOff += u32HLen;
|
||||
if (ulBitOff > (REGISTER_WIDTH-16)) { // need at least 16 unused bits
|
||||
pBuf += (ulBitOff >> 3);
|
||||
ulBitOff &= 7;
|
||||
ulBits = TIFFMOTOLONG(pBuf);
|
||||
}
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
|
||||
ulBitOff += u32HLen;
|
||||
break;
|
||||
} // switch on lBits
|
||||
a0 = a0_p + tot_run;
|
||||
*pCur++ = a0;
|
||||
a0 += tot_run1;
|
||||
if (a0 < xsize) {
|
||||
while (a0 >= *pRef) {
|
||||
pRef += 2;
|
||||
}
|
||||
}
|
||||
*pCur++ = a0;
|
||||
break;
|
||||
|
||||
case 0x30: /* Pass code */
|
||||
pRef++; /* A0 = B2, iRef+=2 */
|
||||
a0 = *pRef++;
|
||||
break;
|
||||
|
||||
default: /* ERROR */
|
||||
pPage->iError = G5_DECODE_ERROR;
|
||||
goto pilreadg5z;
|
||||
} /* switch */
|
||||
} /* Slow climb */
|
||||
}
|
||||
/*--- Convert flips data into run lengths ---*/
|
||||
*pCur++ = xsize; /* Terminate the line properly */
|
||||
*pCur++ = xsize;
|
||||
pilreadg5z:
|
||||
// Save the current VLC decoder state
|
||||
pPage->ulBits = ulBits;
|
||||
pPage->ulBitOff = ulBitOff;
|
||||
pPage->pBuf = pBuf;
|
||||
return pPage->iError;
|
||||
} /* DecodeLine() */
|
||||
//
|
||||
// Decompress the VLC data
|
||||
//
|
||||
static int g5_decode_line(G5DECIMAGE *pPage, uint8_t *pOut)
|
||||
{
|
||||
int rc;
|
||||
uint8_t *pBufEnd;
|
||||
int16_t *t1;
|
||||
|
||||
if (pPage == NULL || pOut == NULL)
|
||||
return G5_INVALID_PARAMETER;
|
||||
if (pPage->y >= pPage->iHeight)
|
||||
return G5_DECODE_COMPLETE;
|
||||
|
||||
pPage->iError = G5_SUCCESS;
|
||||
|
||||
if (pPage->y == 0) { // first time through
|
||||
Decode_Begin(pPage);
|
||||
}
|
||||
pBufEnd = &pPage->pSrc[pPage->iVLCSize];
|
||||
|
||||
if (pPage->pBuf >= pBufEnd) { // read past the end, error
|
||||
pPage->iError = G5_DECODE_ERROR;
|
||||
return G5_DECODE_ERROR;
|
||||
}
|
||||
rc = DecodeLine(pPage);
|
||||
if (rc == G5_SUCCESS) {
|
||||
// Draw the current line
|
||||
G5DrawLine(pPage, pPage->pCur, pOut);
|
||||
/*--- Swap current and reference lines ---*/
|
||||
t1 = pPage->pRef;
|
||||
pPage->pRef = pPage->pCur;
|
||||
pPage->pCur = t1;
|
||||
pPage->y++;
|
||||
if (pPage->y >= pPage->iHeight) {
|
||||
pPage->iError = G5_DECODE_COMPLETE;
|
||||
}
|
||||
} else {
|
||||
pPage->iError = rc;
|
||||
}
|
||||
return pPage->iError;
|
||||
} /* Decode() */
|
||||
|
||||
305
ESP32_AP-Flasher/src/g5/g5enc.inl
Normal file
305
ESP32_AP-Flasher/src/g5/g5enc.inl
Normal file
@@ -0,0 +1,305 @@
|
||||
//
|
||||
// G5 Encoder
|
||||
// A 1-bpp image encoding library
|
||||
//
|
||||
// Written by Larry Bank
|
||||
// Copyright (c) 2024 BitBank Software, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file ./LICENSE.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// ./APL.txt.
|
||||
#include "Group5.h"
|
||||
|
||||
/* Number of consecutive 1 bits in a byte from MSB to LSB */
|
||||
static uint8_t bitcount[256] =
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0-15 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 16-31 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 32-47 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 48-63 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 64-79 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 80-95 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 96-111 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 112-127 */
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 128-143 */
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 144-159 */
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 160-175 */
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 176-191 */
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 192-207 */
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 208-223 */
|
||||
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, /* 224-239 */
|
||||
4,4,4,4,4,4,4,4,5,5,5,5,6,6,7,8}; /* 240-255 */
|
||||
|
||||
/* Table of vertical codes for G5 encoding */
|
||||
/* code followed by length, starting with v(-3) */
|
||||
static const uint8_t vtable[14] =
|
||||
{3,7, /* V(-3) = 0000011 */
|
||||
3,6, /* V(-2) = 000011 */
|
||||
3,3, /* V(-1) = 011 */
|
||||
1,1, /* V(0) = 1 */
|
||||
2,3, /* V(1) = 010 */
|
||||
2,6, /* V(2) = 000010 */
|
||||
2,7}; /* V(3) = 0000010 */
|
||||
|
||||
|
||||
static void G5ENCInsertCode(BUFFERED_BITS *bb, BIGUINT ulCode, int iLen)
|
||||
{
|
||||
if ((bb->ulBitOff + iLen) > REGISTER_WIDTH) { // need to write data
|
||||
bb->ulBits |= (ulCode >> (bb->ulBitOff + iLen - REGISTER_WIDTH)); // partial bits on first word
|
||||
*(BIGUINT *)bb->pBuf = __builtin_bswap32(bb->ulBits);
|
||||
bb->pBuf += sizeof(BIGUINT);
|
||||
bb->ulBits = ulCode << ((REGISTER_WIDTH*2) - (bb->ulBitOff + iLen));
|
||||
bb->ulBitOff += iLen - REGISTER_WIDTH;
|
||||
} else {
|
||||
bb->ulBits |= (ulCode << (REGISTER_WIDTH - bb->ulBitOff - iLen));
|
||||
bb->ulBitOff += iLen;
|
||||
}
|
||||
} /* G5ENCInsertCode() */
|
||||
//
|
||||
// Flush any buffered bits to the output
|
||||
//
|
||||
static void G5ENCFlushBits(BUFFERED_BITS *bb)
|
||||
{
|
||||
while (bb->ulBitOff >= 8)
|
||||
{
|
||||
*bb->pBuf++ = (unsigned char) (bb->ulBits >> (REGISTER_WIDTH - 8));
|
||||
bb->ulBits <<= 8;
|
||||
bb->ulBitOff -= 8;
|
||||
}
|
||||
*bb->pBuf++ = (unsigned char) (bb->ulBits >> (REGISTER_WIDTH - 8));
|
||||
bb->ulBitOff = 0;
|
||||
bb->ulBits = 0;
|
||||
} /* G5ENCFlushBits() */
|
||||
//
|
||||
// Initialize the compressor
|
||||
// This must be called before adding data to the output
|
||||
//
|
||||
int g5_encode_init(G5ENCIMAGE *pImage, int iWidth, int iHeight, uint8_t *pOut, int iOutSize)
|
||||
{
|
||||
int iError = G5_SUCCESS;
|
||||
|
||||
if (pImage == NULL || iHeight <= 0)
|
||||
return G5_INVALID_PARAMETER;
|
||||
pImage->iWidth = iWidth; // image size
|
||||
pImage->iHeight = iHeight;
|
||||
pImage->pCur = pImage->CurFlips;
|
||||
pImage->pRef = pImage->RefFlips;
|
||||
pImage->pOutBuf = pOut; // optional output buffer
|
||||
pImage->iOutSize = iOutSize; // output buffer pre-allocated size
|
||||
pImage->iDataSize = 0; // no data yet
|
||||
pImage->y = 0;
|
||||
for (int i=0; i<MAX_IMAGE_FLIPS; i++) {
|
||||
pImage->RefFlips[i] = iWidth;
|
||||
pImage->CurFlips[i] = iWidth;
|
||||
}
|
||||
pImage->bb.pBuf = pImage->pOutBuf;
|
||||
pImage->bb.ulBits = 0;
|
||||
pImage->bb.ulBitOff = 0;
|
||||
pImage->iError = iError;
|
||||
return iError;
|
||||
} /* g5_encode_init() */
|
||||
//
|
||||
// Internal function to convert uncompressed 1-bit per pixel data
|
||||
// into the run-end data needed to feed the G5 encoder
|
||||
//
|
||||
static int G5ENCEncodeLine(unsigned char *buf, int xsize, int16_t *pDest)
|
||||
{
|
||||
int iCount, xborder;
|
||||
uint8_t i, c;
|
||||
int8_t cBits;
|
||||
int iLen;
|
||||
int16_t x;
|
||||
int16_t *pLimit = pDest + (MAX_IMAGE_FLIPS-4);
|
||||
|
||||
xborder = xsize;
|
||||
iCount = (xsize + 7) >> 3; /* Number of bytes per line */
|
||||
cBits = 8;
|
||||
iLen = 0; /* Current run length */
|
||||
x = 0;
|
||||
|
||||
c = *buf++; /* Get the first byte to start */
|
||||
iCount--;
|
||||
while (iCount >=0) {
|
||||
if (pDest >= pLimit) return G5_MAX_FLIPS_EXCEEDED;
|
||||
i = bitcount[c]; /* Get the number of consecutive bits */
|
||||
iLen += i; /* Add this length to total run length */
|
||||
c <<= i;
|
||||
cBits -= i; /* Minus the number in a byte */
|
||||
if (cBits <= 0)
|
||||
{
|
||||
iLen += cBits; /* Adjust length */
|
||||
cBits = 8;
|
||||
c = *buf++; /* Get another data byte */
|
||||
iCount--;
|
||||
continue; /* Keep doing white until color change */
|
||||
}
|
||||
c = ~c; /* flip color to count black pixels */
|
||||
/* Store the white run length */
|
||||
xborder -= iLen;
|
||||
if (xborder < 0)
|
||||
{
|
||||
iLen += xborder; /* Make sure run length is not past end */
|
||||
break;
|
||||
}
|
||||
x += iLen;
|
||||
*pDest++ = x;
|
||||
iLen = 0;
|
||||
doblack:
|
||||
i = bitcount[c]; /* Get consecutive bits */
|
||||
iLen += i; /* Add to total run length */
|
||||
c <<= i;
|
||||
cBits -= i;
|
||||
if (cBits <= 0)
|
||||
{
|
||||
iLen += cBits; /* Adjust length */
|
||||
cBits = 8;
|
||||
c = *buf++; /* Get another data byte */
|
||||
c = ~c; /* Flip color to find black */
|
||||
iCount--;
|
||||
if (iCount < 0)
|
||||
break;
|
||||
goto doblack;
|
||||
}
|
||||
/* Store the black run length */
|
||||
c = ~c; /* Flip color again to find white pixels */
|
||||
xborder -= iLen;
|
||||
if (xborder < 0)
|
||||
{
|
||||
iLen += xborder; /* Make sure run length is not past end */
|
||||
break;
|
||||
}
|
||||
x += iLen;
|
||||
*pDest++ = x;
|
||||
iLen = 0;
|
||||
} /* while */
|
||||
|
||||
x += iLen;
|
||||
if (pDest >= pLimit) return G5_MAX_FLIPS_EXCEEDED;
|
||||
*pDest++ = x;
|
||||
*pDest++ = x; // Store a few more XSIZE to end the line
|
||||
*pDest++ = x; // so that the compressor doesn't go past
|
||||
*pDest++ = x; // the end of the line
|
||||
return G5_SUCCESS;
|
||||
} /* G5ENCEncodeLine() */
|
||||
//
|
||||
// Compress a line of pixels and add it to the output
|
||||
// the input format is expected to be MSB (most significant bit) first
|
||||
// for example, pixel 0 is in byte 0 at bit 7 (0x80)
|
||||
// Returns G5ENC_SUCCESS for each line if all is well and G5ENC_IMAGE_COMPLETE
|
||||
// for the last line
|
||||
//
|
||||
int g5_encode_encodeLine(G5ENCIMAGE *pImage, uint8_t *pPixels)
|
||||
{
|
||||
int16_t a0, a0_c, b2, a1;
|
||||
int dx, run1, run2;
|
||||
int xsize, iErr, iHighWater;
|
||||
int iCur, iRef, iLen;
|
||||
int iHLen; // number of bits for long horizontal codes
|
||||
int16_t *CurFlips, *RefFlips;
|
||||
BUFFERED_BITS bb;
|
||||
|
||||
if (pImage == NULL || pPixels == NULL)
|
||||
return G5_INVALID_PARAMETER;
|
||||
iHighWater = pImage->iOutSize - 32;
|
||||
iHLen = 32 - __builtin_clz(pImage->iWidth);
|
||||
memcpy(&bb, &pImage->bb, sizeof(BUFFERED_BITS)); // keep local copy
|
||||
CurFlips = pImage->pCur;
|
||||
RefFlips = pImage->pRef;
|
||||
xsize = pImage->iWidth; /* For performance reasons */
|
||||
|
||||
// Convert the incoming line of pixels into run-end data
|
||||
iErr = G5ENCEncodeLine(pPixels, pImage->iWidth, CurFlips);
|
||||
if (iErr != G5_SUCCESS) return iErr; // exceeded the maximum number of color changes
|
||||
/* Encode this line as G5 */
|
||||
a0 = a0_c = 0;
|
||||
iCur = iRef = 0;
|
||||
while (a0 < xsize) {
|
||||
b2 = RefFlips[iRef+1];
|
||||
a1 = CurFlips[iCur];
|
||||
if (b2 < a1) { /* Is b2 to the left of a1? */
|
||||
/* yes, do pass mode */
|
||||
a0 = b2;
|
||||
iRef += 2;
|
||||
G5ENCInsertCode(&bb, 1, 4); /* Pass code = 0001 */
|
||||
} else { /* Try vertical and horizontal mode */
|
||||
dx = RefFlips[iRef] - a1; /* b1 - a1 */
|
||||
if (dx > 3 || dx < -3) { /* Horizontal mode */
|
||||
G5ENCInsertCode(&bb, 1, 3); /* Horizontal code = 001 */
|
||||
run1 = CurFlips[iCur] - a0;
|
||||
run2 = CurFlips[iCur+1] - CurFlips[iCur];
|
||||
if (run1 < 8) {
|
||||
if (run2 < 8) { // short, short
|
||||
G5ENCInsertCode(&bb, HORIZ_SHORT_SHORT, 2); /* short, short = 00 */
|
||||
G5ENCInsertCode(&bb, run1, 3);
|
||||
G5ENCInsertCode(&bb, run2, 3);
|
||||
} else { // short, long
|
||||
G5ENCInsertCode(&bb, HORIZ_SHORT_LONG, 2); /* short, long = 01 */
|
||||
G5ENCInsertCode(&bb, run1, 3);
|
||||
G5ENCInsertCode(&bb, run2, iHLen);
|
||||
}
|
||||
} else { // first run is long
|
||||
if (run2 < 8) { // long, short
|
||||
G5ENCInsertCode(&bb, HORIZ_LONG_SHORT, 2); /* long, short = 10 */
|
||||
G5ENCInsertCode(&bb, run1, iHLen);
|
||||
G5ENCInsertCode(&bb, run2, 3);
|
||||
} else { // long, long
|
||||
G5ENCInsertCode(&bb, HORIZ_LONG_LONG, 2); /* long, long = 11 */
|
||||
G5ENCInsertCode(&bb, run1, iHLen);
|
||||
G5ENCInsertCode(&bb, run2, iHLen);
|
||||
}
|
||||
}
|
||||
a0 = CurFlips[iCur+1]; /* a0 = a2 */
|
||||
if (a0 != xsize) {
|
||||
iCur += 2; /* Skip two color flips */
|
||||
while (RefFlips[iRef] != xsize && RefFlips[iRef] <= a0) {
|
||||
iRef += 2;
|
||||
}
|
||||
}
|
||||
} else { /* Vertical mode */
|
||||
dx = (dx + 3) * 2; /* Convert to index table */
|
||||
G5ENCInsertCode(&bb, vtable[dx], vtable[dx+1]);
|
||||
a0 = a1;
|
||||
a0_c = 1-a0_c;
|
||||
if (a0 != xsize) {
|
||||
if (iRef != 0) {
|
||||
iRef -= 2;
|
||||
}
|
||||
iRef++; /* Skip a color change in cur and ref */
|
||||
iCur++;
|
||||
while (RefFlips[iRef] <= a0 && RefFlips[iRef] != xsize) {
|
||||
iRef += 2;
|
||||
}
|
||||
}
|
||||
} /* vertical mode */
|
||||
} /* horiz/vert mode */
|
||||
} /* while x < xsize */
|
||||
iLen = (int)(bb.pBuf-pImage->pOutBuf);
|
||||
if (iLen >= iHighWater) { // not enough space
|
||||
pImage->iError = iErr = G5_DATA_OVERFLOW; // we don't have a better error
|
||||
return iErr;
|
||||
}
|
||||
if (pImage->y == pImage->iHeight-1) { // last line of image
|
||||
G5ENCFlushBits(&bb); // output the final buffered bits
|
||||
// wrap up final output
|
||||
pImage->iDataSize = (int)(bb.pBuf-pImage->pOutBuf);
|
||||
iErr = G5_ENCODE_COMPLETE;
|
||||
}
|
||||
pImage->pCur = RefFlips; // swap current and reference lines
|
||||
pImage->pRef = CurFlips;
|
||||
pImage->y++;
|
||||
memcpy(&pImage->bb, &bb, sizeof(bb));
|
||||
return iErr;
|
||||
} /* g5_encode_encodeLine() */
|
||||
//
|
||||
// Returns the number of bytes of G5 created by the encoder
|
||||
//
|
||||
int g5_encode_getOutSize(G5ENCIMAGE *pImage)
|
||||
{
|
||||
int iSize = 0;
|
||||
if (pImage != NULL)
|
||||
iSize = pImage->iDataSize;
|
||||
return iSize;
|
||||
} /* g5_encode_getOutSize() */
|
||||
@@ -12,16 +12,21 @@
|
||||
#include "ips_display.h"
|
||||
|
||||
#define YELLOW_SENSE 8 // sense AP hardware
|
||||
|
||||
#ifdef HAS_ELECROW_ADV_2_8
|
||||
#define TFT_BACKLIGHT 38
|
||||
#else
|
||||
#define TFT_BACKLIGHT 14
|
||||
#endif
|
||||
|
||||
TFT_eSPI tft2 = TFT_eSPI();
|
||||
uint8_t YellowSense = 0;
|
||||
bool tftLogscreen = true;
|
||||
bool tftOverride = false;
|
||||
|
||||
#ifdef HAS_LILYGO_TPANEL
|
||||
|
||||
#if defined HAS_LILYGO_TPANEL || defined HAS_4inch_TPANEL
|
||||
|
||||
#if defined HAS_LILYGO_TPANEL
|
||||
static const uint8_t st7701_type9_init_operations_lilygo[] = {
|
||||
|
||||
BEGIN_WRITE,
|
||||
@@ -41,14 +46,14 @@ static const uint8_t st7701_type9_init_operations_lilygo[] = {
|
||||
|
||||
WRITE_C8_D8, 0xCC, 0x10,
|
||||
|
||||
WRITE_COMMAND_8, 0xB0, // Positive Voltage Gamma Control
|
||||
WRITE_COMMAND_8, 0xB0, // Positive Voltage Gamma Control
|
||||
WRITE_BYTES, 16,
|
||||
0x00, 0x0F, 0x16, 0x0E,
|
||||
0x11, 0x07, 0x09, 0x09,
|
||||
0x08, 0x23, 0x05, 0x11,
|
||||
0x0F, 0x28, 0x2D, 0x18,
|
||||
|
||||
WRITE_COMMAND_8, 0xB1, // Negative Voltage Gamma Control
|
||||
WRITE_COMMAND_8, 0xB1, // Negative Voltage Gamma Control
|
||||
WRITE_BYTES, 16,
|
||||
0x00, 0x0F, 0x16, 0x0E,
|
||||
0x11, 0x07, 0x09, 0x08,
|
||||
@@ -142,7 +147,7 @@ static const uint8_t st7701_type9_init_operations_lilygo[] = {
|
||||
// WRITE_C8_D8, 0xD1, 0x81,//Test
|
||||
// WRITE_C8_D8, 0xD2, 0x08,//Test
|
||||
|
||||
WRITE_COMMAND_8, 0x29, // Display On
|
||||
WRITE_COMMAND_8, 0x29, // Display On
|
||||
|
||||
// WRITE_C8_D8, 0x35, 0x00,//Test
|
||||
// WRITE_C8_D8, 0xCE, 0x04,//Test
|
||||
@@ -152,41 +157,201 @@ static const uint8_t st7701_type9_init_operations_lilygo[] = {
|
||||
// 0xF0, 0xA3, 0xA3, 0x71,
|
||||
|
||||
END_WRITE};
|
||||
|
||||
Arduino_DataBus *bus = new Arduino_XL9535SWSPI(IIC_SDA /* SDA */, IIC_SCL /* SCL */, -1 /* XL PWD */,
|
||||
Arduino_DataBus* bus = new Arduino_XL9535SWSPI(IIC_SDA /* SDA */, IIC_SCL /* SCL */, -1 /* XL PWD */,
|
||||
XL95X5_CS /* XL CS */, XL95X5_SCLK /* XL SCK */, XL95X5_MOSI /* XL MOSI */);
|
||||
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
|
||||
-1 /* DE */, LCD_VSYNC /* VSYNC */, LCD_HSYNC /* HSYNC */, LCD_PCLK /* PCLK */,
|
||||
LCD_B0 /* B0 */, LCD_B1 /* B1 */, LCD_B2 /* B2 */, LCD_B3 /* B3 */, LCD_B4 /* B4 */,
|
||||
LCD_G0 /* G0 */, LCD_G1 /* G1 */, LCD_G2 /* G2 */, LCD_G3 /* G3 */, LCD_G4 /* G4 */, LCD_G5 /* G5 */,
|
||||
LCD_R0 /* R0 */, LCD_R1 /* R1 */, LCD_R2 /* R2 */, LCD_R3 /* R3 */, LCD_R4 /* R4 */,
|
||||
1 /* hsync_polarity */, 20 /* hsync_front_porch */, 2 /* hsync_pulse_width */, 0 /* hsync_back_porch */,
|
||||
1 /* vsync_polarity */, 30 /* vsync_front_porch */, 8 /* vsync_pulse_width */, 1 /* vsync_back_porch */,
|
||||
10 /* pclk_active_neg */, 6000000L /* prefer_speed */, true /* useBigEndian */,
|
||||
0 /* de_idle_high*/, 0 /* pclk_idle_high */);
|
||||
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
|
||||
LCD_WIDTH /* width */, LCD_HEIGHT /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
|
||||
bus, -1 /* RST */, st7701_type9_init_operations_lilygo, sizeof(st7701_type9_init_operations_lilygo));
|
||||
Arduino_ESP32RGBPanel* rgbpanel = new Arduino_ESP32RGBPanel(
|
||||
-1 /* DE */, LCD_VSYNC /* VSYNC */, LCD_HSYNC /* HSYNC */, LCD_PCLK /* PCLK */,
|
||||
LCD_B0 /* B0 */, LCD_B1 /* B1 */, LCD_B2 /* B2 */, LCD_B3 /* B3 */, LCD_B4 /* B4 */,
|
||||
LCD_G0 /* G0 */, LCD_G1 /* G1 */, LCD_G2 /* G2 */, LCD_G3 /* G3 */, LCD_G4 /* G4 */, LCD_G5 /* G5 */,
|
||||
LCD_R0 /* R0 */, LCD_R1 /* R1 */, LCD_R2 /* R2 */, LCD_R3 /* R3 */, LCD_R4 /* R4 */,
|
||||
1 /* hsync_polarity */, 20 /* hsync_front_porch */, 2 /* hsync_pulse_width */, 0 /* hsync_back_porch */,
|
||||
1 /* vsync_polarity */, 30 /* vsync_front_porch */, 8 /* vsync_pulse_width */, 1 /* vsync_back_porch */,
|
||||
10 /* pclk_active_neg */, 6000000L /* prefer_speed */, true /* useBigEndian */,
|
||||
0 /* de_idle_high*/, 0 /* pclk_idle_high */);
|
||||
Arduino_RGB_Display* gfx = new Arduino_RGB_Display(
|
||||
LCD_WIDTH /* width */, LCD_HEIGHT /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
|
||||
bus, -1 /* RST */, st7701_type9_init_operations_lilygo, sizeof(st7701_type9_init_operations_lilygo));
|
||||
|
||||
#else
|
||||
static const uint8_t st7701_4848S040_init[] = {
|
||||
BEGIN_WRITE,
|
||||
WRITE_COMMAND_8, 0xFF,
|
||||
WRITE_BYTES, 5, 0x77, 0x01, 0x00, 0x00, 0x10,
|
||||
|
||||
WRITE_C8_D16, 0xC0, 0x3B, 0x00,
|
||||
WRITE_C8_D16, 0xC1, 0x0D, 0x02,
|
||||
WRITE_C8_D16, 0xC2, 0x31, 0x05,
|
||||
WRITE_C8_D8, 0xCD, 0x00, // 0xCD, 0x08 !!
|
||||
|
||||
WRITE_COMMAND_8, 0xB0, // Positive Voltage Gamma Control
|
||||
WRITE_BYTES, 16,
|
||||
0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08,
|
||||
0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18,
|
||||
|
||||
WRITE_COMMAND_8, 0xB1, // Negative Voltage Gamma Control
|
||||
WRITE_BYTES, 16,
|
||||
0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08,
|
||||
0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18,
|
||||
|
||||
// PAGE1
|
||||
WRITE_COMMAND_8, 0xFF,
|
||||
WRITE_BYTES, 5, 0x77, 0x01, 0x00, 0x00, 0x11,
|
||||
|
||||
WRITE_C8_D8, 0xB0, 0x60, // Vop=4.7375v
|
||||
WRITE_C8_D8, 0xB1, 0x32, // VCOM=32
|
||||
WRITE_C8_D8, 0xB2, 0x07, // VGH=15v
|
||||
WRITE_C8_D8, 0xB3, 0x80,
|
||||
WRITE_C8_D8, 0xB5, 0x49, // VGL=-10.17v
|
||||
WRITE_C8_D8, 0xB7, 0x85,
|
||||
WRITE_C8_D8, 0xB8, 0x21, // AVDD=6.6 & AVCL=-4.6
|
||||
WRITE_C8_D8, 0xC1, 0x78,
|
||||
WRITE_C8_D8, 0xC2, 0x78,
|
||||
|
||||
WRITE_COMMAND_8, 0xE0,
|
||||
WRITE_BYTES, 3, 0x00, 0x1B, 0x02,
|
||||
|
||||
WRITE_COMMAND_8, 0xE1,
|
||||
WRITE_BYTES, 11,
|
||||
0x08, 0xA0, 0x00, 0x00,
|
||||
0x07, 0xA0, 0x00, 0x00,
|
||||
0x00, 0x44, 0x44,
|
||||
|
||||
WRITE_COMMAND_8, 0xE2,
|
||||
WRITE_BYTES, 12,
|
||||
0x11, 0x11, 0x44, 0x44,
|
||||
0xED, 0xA0, 0x00, 0x00,
|
||||
0xEC, 0xA0, 0x00, 0x00,
|
||||
|
||||
WRITE_COMMAND_8, 0xE3,
|
||||
WRITE_BYTES, 4, 0x00, 0x00, 0x11, 0x11,
|
||||
|
||||
WRITE_C8_D16, 0xE4, 0x44, 0x44,
|
||||
|
||||
WRITE_COMMAND_8, 0xE5,
|
||||
WRITE_BYTES, 16,
|
||||
0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0,
|
||||
0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0,
|
||||
|
||||
WRITE_COMMAND_8, 0xE6,
|
||||
WRITE_BYTES, 4, 0x00, 0x00, 0x11, 0x11,
|
||||
|
||||
WRITE_C8_D16, 0xE7, 0x44, 0x44,
|
||||
|
||||
WRITE_COMMAND_8, 0xE8,
|
||||
WRITE_BYTES, 16,
|
||||
0x09, 0xE8, 0xD8, 0xA0,
|
||||
0x0B, 0xEA, 0xD8, 0xA0,
|
||||
0x0D, 0xEC, 0xD8, 0xA0,
|
||||
0x0F, 0xEE, 0xD8, 0xA0,
|
||||
|
||||
WRITE_COMMAND_8, 0xEB,
|
||||
WRITE_BYTES, 7, 0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40,
|
||||
|
||||
WRITE_C8_D16, 0xEC, 0x3C, 0x00,
|
||||
|
||||
WRITE_COMMAND_8, 0xED,
|
||||
WRITE_BYTES, 16,
|
||||
0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA,
|
||||
|
||||
//-----------VAP & VAN---------------
|
||||
WRITE_COMMAND_8, 0xFF,
|
||||
WRITE_BYTES, 5, 0x77, 0x01, 0x00, 0x00, 0x13,
|
||||
|
||||
WRITE_C8_D8, 0xE5, 0xE4,
|
||||
|
||||
WRITE_COMMAND_8, 0xFF,
|
||||
WRITE_BYTES, 5, 0x77, 0x01, 0x00, 0x00, 0x00,
|
||||
|
||||
// WRITE_COMMAND_8, 0x21, // 0x20 normal, 0x21 IPS !!
|
||||
WRITE_C8_D8, 0x3A, 0x50, // 0x70 RGB888, 0x60 RGB666, 0x50 RGB565
|
||||
WRITE_COMMAND_8, 0x11, // Sleep Out
|
||||
END_WRITE,
|
||||
|
||||
DELAY, 120,
|
||||
|
||||
BEGIN_WRITE,
|
||||
WRITE_COMMAND_8, 0x29, // Display On
|
||||
END_WRITE};
|
||||
Arduino_DataBus* bus = new Arduino_SWSPI(
|
||||
GFX_NOT_DEFINED /* DC */, SPI_LCD_CS /* CS */, SPI_LCD_SCLK /* SCK */, SPI_LCD_MOSI /* MOSI */, GFX_NOT_DEFINED /* MISO */);
|
||||
Arduino_ESP32RGBPanel* rgbpanel = new Arduino_ESP32RGBPanel(
|
||||
LCD_DE /* DE */, LCD_VSYNC /* VSYNC */, LCD_HSYNC /* HSYNC */, LCD_PCLK /* PCLK */,
|
||||
LCD_R0 /* B0 */, LCD_R1 /* B1 */, LCD_R2 /* B2 */, LCD_R3 /* B3 */, LCD_R4 /* B4 */,
|
||||
LCD_G0 /* G0 */, LCD_G1 /* G1 */, LCD_G2 /* G2 */, LCD_G3 /* G3 */, LCD_G4 /* G4 */, LCD_G5 /* G5 */,
|
||||
LCD_B0 /* R0 */, LCD_B1 /* R1 */, LCD_B2 /* R2 */, LCD_B3 /* R3 */, LCD_B4 /* R4 */,
|
||||
1 /* hsync_polarity */, 10 /* hsync_front_porch */, 8 /* hsync_pulse_width */, 50 /* hsync_back_porch */,
|
||||
1 /* vsync_polarity */, 10 /* vsync_front_porch */, 8 /* vsync_pulse_width */, 20 /* vsync_back_porch */,
|
||||
10 /* pclk_active_neg */, 6000000L /* prefer_speed */, true);
|
||||
Arduino_RGB_Display* gfx = new Arduino_RGB_Display(
|
||||
LCD_WIDTH /* width */, LCD_HEIGHT /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
|
||||
bus, -1 /* RST */, st7701_4848S040_init, sizeof(st7701_4848S040_init));
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined HAS_GT911_TOUCH
|
||||
#include <Wire.h>
|
||||
#include <Touch_GT911.h>
|
||||
#define TOUCH_GT911_SCL 45
|
||||
#define TOUCH_GT911_SDA 19
|
||||
int touch_last_x = 0, touch_last_y = 0;
|
||||
uint32_t last_touch_read = 0;
|
||||
uint8_t is_new_touch_checked = false;
|
||||
Touch_GT911 ts = Touch_GT911(TOUCH_GT911_SDA, TOUCH_GT911_SCL, max(480, 0), max(480, 0));
|
||||
|
||||
void touch_init()
|
||||
{
|
||||
ts.begin();
|
||||
}
|
||||
void touch_loop()
|
||||
{
|
||||
if (millis() - last_touch_read >= 50) {
|
||||
last_touch_read = millis();
|
||||
ts.read();
|
||||
if (ts.isTouched)
|
||||
{
|
||||
touch_last_x = map(ts.points[0].x, 480, 0, 0, 480 - 1);
|
||||
touch_last_y = map(ts.points[0].y, 480, 0, 0, 480 - 1);
|
||||
Serial.printf("Touch position X: %i Y: %i\r\n", touch_last_x, touch_last_y);
|
||||
if(is_new_touch_checked == false)
|
||||
{
|
||||
is_new_touch_checked = true;
|
||||
if(touch_last_x <= 240)
|
||||
sendAvail(WAKEUP_REASON_BUTTON1);
|
||||
else
|
||||
sendAvail(WAKEUP_REASON_BUTTON2);
|
||||
}
|
||||
}else{
|
||||
is_new_touch_checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
void touch_init()
|
||||
{
|
||||
}
|
||||
void touch_loop()
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
void TFTLog(String text) {
|
||||
|
||||
#ifdef HAS_LILYGO_TPANEL
|
||||
#if defined HAS_LILYGO_TPANEL || defined HAS_4inch_TPANEL
|
||||
|
||||
gfx->setTextSize(2);
|
||||
|
||||
gfx->setTextSize(2);
|
||||
|
||||
if (tftLogscreen == false) {
|
||||
gfx->fillScreen(BLACK);
|
||||
gfx->setCursor(0, 0);
|
||||
tftLogscreen = true;
|
||||
}
|
||||
}
|
||||
if (text.isEmpty()) return;
|
||||
gfx->setTextColor(WHITE);
|
||||
gfx->println(text);
|
||||
|
||||
#else
|
||||
|
||||
|
||||
#else
|
||||
|
||||
if (tftLogscreen == false) {
|
||||
tft2.fillScreen(TFT_BLACK);
|
||||
tft2.setCursor(0, 0, (tft2.width() == 160 ? 1 : 2));
|
||||
@@ -220,7 +385,7 @@ void TFTLog(String text) {
|
||||
tft2.setTextColor(TFT_GREEN);
|
||||
}
|
||||
tft2.println(text);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
int32_t findId(uint8_t mac[8]) {
|
||||
@@ -240,11 +405,13 @@ void sendAvail(uint8_t wakeupReason) {
|
||||
memcpy(&eadr.src, mac, 6);
|
||||
eadr.adr.lastPacketRSSI = WiFi.RSSI();
|
||||
eadr.adr.currentChannel = config.channel;
|
||||
#ifdef HAS_LILYGO_TPANEL
|
||||
#ifdef TFT_HW_TYPE
|
||||
eadr.adr.hwType = TFT_HW_TYPE;
|
||||
#elif defined HAS_LILYGO_TPANEL || defined HAS_4inch_TPANEL
|
||||
eadr.adr.hwType = 0xE2;
|
||||
#else
|
||||
#else
|
||||
eadr.adr.hwType = (tft2.width() == 160 ? 0xE1 : 0xE0);
|
||||
#endif
|
||||
#endif
|
||||
eadr.adr.wakeupReason = wakeupReason;
|
||||
eadr.adr.capabilities = 0;
|
||||
eadr.adr.tagSoftwareVersion = 0;
|
||||
@@ -253,50 +420,65 @@ void sendAvail(uint8_t wakeupReason) {
|
||||
}
|
||||
|
||||
void yellow_ap_display_init(void) {
|
||||
|
||||
#ifdef HAS_LILYGO_TPANEL
|
||||
|
||||
tftLogscreen = true;
|
||||
#if defined HAS_LILYGO_TPANEL || defined HAS_4inch_TPANEL
|
||||
|
||||
tftLogscreen = true;
|
||||
|
||||
pinMode(LCD_BL, OUTPUT);
|
||||
digitalWrite(LCD_BL, HIGH);
|
||||
|
||||
#if ESP_ARDUINO_VERSION_MAJOR == 2
|
||||
ledcAttachPin(LCD_BL, 1);
|
||||
ledcSetup(1, 1000, 8);
|
||||
ledcWrite(1, config.tft); // brightness
|
||||
#else
|
||||
ledcAttachChannel(LCD_BL, 1000, 8, 1);
|
||||
ledcWriteChannel(1, config.tft);
|
||||
#endif
|
||||
|
||||
ledcWrite(1, config.tft); // brightness
|
||||
|
||||
#if defined HAS_LILYGO_TPANEL
|
||||
Wire.begin(IIC_SDA, IIC_SCL);
|
||||
|
||||
#endif
|
||||
gfx->begin();
|
||||
gfx->fillScreen(BLACK);
|
||||
|
||||
#else
|
||||
|
||||
#else
|
||||
#ifdef HAS_ELECROW_C6
|
||||
YellowSense = 0;
|
||||
#else
|
||||
pinMode(YELLOW_SENSE, INPUT_PULLDOWN);
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
if (digitalRead(YELLOW_SENSE) == HIGH) YellowSense = 1;
|
||||
#endif
|
||||
pinMode(TFT_BACKLIGHT, OUTPUT);
|
||||
digitalWrite(TFT_BACKLIGHT, LOW);
|
||||
|
||||
tft2.init();
|
||||
#ifdef ST7735_NANO_TLSR
|
||||
#ifdef ST7735_NANO_TLSR
|
||||
YellowSense = 0;
|
||||
tft2.setRotation(1);
|
||||
#else
|
||||
#else
|
||||
tft2.setRotation(YellowSense == 1 ? 1 : 3);
|
||||
#endif
|
||||
#endif
|
||||
tft2.fillScreen(TFT_BLACK);
|
||||
tft2.setCursor(12, 0, (tft2.width() == 160 ? 1 : 2));
|
||||
tft2.setTextColor(TFT_WHITE);
|
||||
tftLogscreen = true;
|
||||
|
||||
ledcSetup(6, 5000, 8);
|
||||
ledcAttachPin(TFT_BACKLIGHT, 6);
|
||||
#if ESP_ARDUINO_VERSION_MAJOR == 2
|
||||
ledcSetup(1, 5000, 8);
|
||||
ledcAttachPin(TFT_BACKLIGHT, 1);
|
||||
ledcWrite(1, config.tft);
|
||||
if (tft2.width() == 160) {
|
||||
GPIO.func_out_sel_cfg[TFT_BACKLIGHT].inv_sel = 1;
|
||||
}
|
||||
ledcWrite(6, config.tft);
|
||||
#endif
|
||||
#else
|
||||
ledcAttachChannel(TFT_BACKLIGHT, 5000, 8, 1);
|
||||
ledcWriteChannel(1, config.tft);
|
||||
if (tft2.width() == 160) ledcOutputInvert(TFT_BACKLIGHT, true);
|
||||
#endif
|
||||
#endif
|
||||
touch_init();
|
||||
}
|
||||
|
||||
void yellow_ap_display_loop(void) {
|
||||
@@ -338,19 +520,19 @@ void yellow_ap_display_loop(void) {
|
||||
void* spriteData = spr.getPointer();
|
||||
size_t bytesRead = file.readBytes((char*)spriteData, spr.width() * spr.height() * 2);
|
||||
file.close();
|
||||
|
||||
#ifdef HAS_LILYGO_TPANEL
|
||||
long dy = spr.height();
|
||||
long dx = spr.width();
|
||||
|
||||
|
||||
#if defined HAS_LILYGO_TPANEL || defined HAS_4inch_TPANEL
|
||||
long dy = spr.height();
|
||||
long dx = spr.width();
|
||||
|
||||
uint16_t* data = static_cast<uint16_t*>(const_cast<void*>(spriteData));
|
||||
|
||||
gfx->draw16bitRGBBitmap(0, 0, (uint16_t *)spriteData, dx, dy);
|
||||
spr.deleteSprite();
|
||||
#else
|
||||
gfx->draw16bitRGBBitmap(0, 0, (uint16_t*)spriteData, dx, dy);
|
||||
spr.deleteSprite();
|
||||
#else
|
||||
spr.pushSprite(0, 0);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
tftLogscreen = false;
|
||||
|
||||
struct espXferComplete xfc = {0};
|
||||
@@ -359,6 +541,7 @@ void yellow_ap_display_loop(void) {
|
||||
}
|
||||
last_update = millis();
|
||||
}
|
||||
touch_loop();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -29,8 +29,8 @@ void updateLanguageFromConfig() {
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(1024);
|
||||
StaticJsonDocument<80> filter;
|
||||
JsonDocument doc;
|
||||
JsonDocument filter;
|
||||
filter[String(currentLanguage)] = true;
|
||||
const DeserializationError error = deserializeJson(doc, file, DeserializationOption::Filter(filter));
|
||||
file.close();
|
||||
|
||||
@@ -36,6 +36,14 @@ struct ledInstruction {
|
||||
bool reQueue = false;
|
||||
};
|
||||
|
||||
void ledcSet(uint8_t channel, uint8_t brightness) {
|
||||
#if ESP_ARDUINO_VERSION_MAJOR == 2
|
||||
ledcWrite(channel, brightness);
|
||||
#else
|
||||
ledcWriteChannel(channel, brightness);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef HAS_RGB_LED
|
||||
|
||||
void addToRGBQueue(struct ledInstructionRGB* rgb, bool requeue) {
|
||||
@@ -132,13 +140,9 @@ void rgbIdleStep() {
|
||||
|
||||
void setBrightness(int brightness) {
|
||||
maxledbrightness = brightness;
|
||||
|
||||
#ifdef HAS_LILYGO_TPANEL
|
||||
ledcWrite(1, config.tft);
|
||||
#else
|
||||
#ifdef HAS_TFT
|
||||
ledcWrite(6, config.tft);
|
||||
#endif
|
||||
|
||||
#if defined HAS_LILYGO_TPANEL || defined HAS_4inch_TPANEL || HAS_TFT
|
||||
ledcSet(1, config.tft);
|
||||
#endif
|
||||
#ifdef HAS_RGB_LED
|
||||
FastLED.setBrightness(maxledbrightness);
|
||||
@@ -150,12 +154,8 @@ void updateBrightnessFromConfig() {
|
||||
if (newbrightness != maxledbrightness) {
|
||||
setBrightness(newbrightness);
|
||||
}
|
||||
#ifdef HAS_LILYGO_TPANEL
|
||||
ledcWrite(1, config.tft);
|
||||
#else
|
||||
#ifdef HAS_TFT
|
||||
ledcWrite(6, config.tft);
|
||||
#endif
|
||||
#if defined HAS_LILYGO_TPANEL || defined HAS_4inch_TPANEL || HAS_TFT
|
||||
ledcSet(1, config.tft);
|
||||
#endif
|
||||
if (apInfo.state == AP_STATE_NORADIO) addFadeMono(config.led);
|
||||
}
|
||||
@@ -176,11 +176,9 @@ void addFadeMono(uint8_t value) {
|
||||
}
|
||||
|
||||
void showMono(uint8_t brightness) {
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||
ledcWrite(7, 255 - gamma8[brightness]);
|
||||
#else
|
||||
ledcWrite(7, gamma8[brightness]);
|
||||
#endif
|
||||
if (FLASHER_LED != -1) {
|
||||
ledcSet(7, gamma8[brightness]);
|
||||
}
|
||||
}
|
||||
|
||||
void quickBlink(uint8_t repeat) {
|
||||
@@ -222,11 +220,13 @@ void ledTask(void* parameter) {
|
||||
|
||||
ledQueue = xQueueCreate(30, sizeof(struct ledInstruction*));
|
||||
|
||||
ledcSetup(7, 5000, 8);
|
||||
if (FLASHER_LED != -1) {
|
||||
digitalWrite(FLASHER_LED, HIGH);
|
||||
pinMode(FLASHER_LED, OUTPUT);
|
||||
#if ESP_ARDUINO_VERSION_MAJOR == 2
|
||||
ledcSetup(7, 5000, 8);
|
||||
ledcAttachPin(FLASHER_LED, 7);
|
||||
#else
|
||||
ledcAttachChannel(FLASHER_LED, 1000, 8, 7);
|
||||
#endif
|
||||
}
|
||||
|
||||
struct ledInstruction* monoled = nullptr;
|
||||
@@ -297,7 +297,7 @@ void ledTask(void* parameter) {
|
||||
if (monoled->fadeTime <= 1) {
|
||||
showMono(monoled->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (monoled->fadeTime) {
|
||||
monoled->fadeTime--;
|
||||
@@ -313,4 +313,4 @@ void ledTask(void* parameter) {
|
||||
|
||||
vTaskDelay(1 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <time.h>
|
||||
#ifdef ETHERNET_CLK_MODE
|
||||
#include <ETH.h>
|
||||
#endif
|
||||
|
||||
#include "contentmanager.h"
|
||||
#include "flasher.h"
|
||||
@@ -48,7 +51,12 @@ void delayedStart(void* parameter) {
|
||||
}
|
||||
|
||||
void setup() {
|
||||
#ifdef UART_LOGGING_TX_ONLY_PIN
|
||||
Serial.begin(115200, SERIAL_8N1, -1, UART_LOGGING_TX_ONLY_PIN);
|
||||
gpio_set_drive_capability((gpio_num_t)FLASHER_AP_RXD, GPIO_DRIVE_CAP_0);
|
||||
#else
|
||||
Serial.begin(115200);
|
||||
#endif
|
||||
#if ARDUINO_USB_CDC_ON_BOOT == 1
|
||||
Serial.setTxTimeoutMs(0); // workaround bug in USB CDC that slows down serial output when no usb connected
|
||||
#endif
|
||||
@@ -114,6 +122,7 @@ void setup() {
|
||||
}
|
||||
*/
|
||||
|
||||
wm.initEth();
|
||||
initAPconfig();
|
||||
|
||||
updateLanguageFromConfig();
|
||||
@@ -150,7 +159,12 @@ void setup() {
|
||||
// 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, 5, NULL);
|
||||
#else
|
||||
|
||||
#ifdef ETHERNET_CLK_MODE
|
||||
if (!(ETHERNET_CLK_MODE == ETH_CLOCK_GPIO0_IN || ETHERNET_CLK_MODE == ETH_CLOCK_GPIO0_OUT))
|
||||
#endif
|
||||
pinMode(0, INPUT_PULLUP);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAS_EXT_FLASHER
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
#include "ips_display.h"
|
||||
#endif
|
||||
|
||||
#include "commstructs.h"
|
||||
#ifndef SAVE_SPACE
|
||||
#include "g5/Group5.h"
|
||||
#include "g5/g5enc.inl"
|
||||
#endif
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
TFT_eSprite spr = TFT_eSprite(&tft);
|
||||
|
||||
@@ -69,23 +75,51 @@ struct Error {
|
||||
int32_t b;
|
||||
};
|
||||
|
||||
uint32_t colorDistance(Color &c1, Color &c2, Error &e1) {
|
||||
e1.r = constrain(e1.r, -255, 255);
|
||||
e1.g = constrain(e1.g, -255, 255);
|
||||
e1.b = constrain(e1.b, -255, 255);
|
||||
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 + b_diff * b_diff;
|
||||
if (abs(c1.r - c1.g) < 20 && abs(c1.b - c1.g) < 20) {
|
||||
if (abs(c2.r - c2.g) > 20 || abs(c2.b - c2.g) > 20) return 4294967295; // don't select color pixels on black and white
|
||||
}
|
||||
return 3 * r_diff * r_diff + 5.47 * g_diff * g_diff + 1.53 * b_diff * b_diff;
|
||||
}
|
||||
|
||||
std::tuple<int, int, float, float> findClosestColors(const Color &pixel, const std::vector<Color> &palette) {
|
||||
int closestIndex = -1, secondClosestIndex = -1;
|
||||
float closestDist = std::numeric_limits<float>::max();
|
||||
float secondClosestDist = std::numeric_limits<float>::max();
|
||||
for (size_t i = 0; i < palette.size(); ++i) {
|
||||
float dist = colorDistance(pixel, palette[i], (Error){0, 0, 0});
|
||||
if (dist < closestDist) {
|
||||
secondClosestIndex = closestIndex;
|
||||
secondClosestDist = closestDist;
|
||||
closestIndex = i;
|
||||
closestDist = dist;
|
||||
} else if (dist < secondClosestDist) {
|
||||
secondClosestIndex = i;
|
||||
secondClosestDist = dist;
|
||||
}
|
||||
}
|
||||
if (closestIndex != -1 && secondClosestIndex != -1) {
|
||||
auto rgbValue = [](const Color &color) {
|
||||
return (color.r << 16) | (color.g << 8) | color.b;
|
||||
};
|
||||
|
||||
if (rgbValue(palette[secondClosestIndex]) > rgbValue(palette[closestIndex])) {
|
||||
std::swap(closestIndex, secondClosestIndex);
|
||||
std::swap(closestDist, secondClosestDist);
|
||||
}
|
||||
}
|
||||
return { closestIndex, secondClosestIndex, closestDist, secondClosestDist};
|
||||
}
|
||||
|
||||
void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t buffer_size, bool is_red) {
|
||||
|
||||
uint8_t rotate = imageParams.rotate;
|
||||
long bufw = spr.width(), bufh = spr.height();
|
||||
|
||||
if (imageParams.rotatebuffer % 2) {
|
||||
//turn the image 90 or 270
|
||||
// turn the image 90 or 270
|
||||
rotate = (rotate + 3) % 4;
|
||||
rotate = (rotate + (imageParams.rotatebuffer - 1)) % 4;
|
||||
bufw = spr.height();
|
||||
@@ -107,11 +141,7 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t
|
||||
Error *error_bufferold = new Error[bufw + 4];
|
||||
Error *error_buffernew = new Error[bufw + 4];
|
||||
|
||||
const uint8_t ditherMatrix[4][4] = {
|
||||
{0, 9, 2, 10},
|
||||
{12, 5, 14, 6},
|
||||
{3, 11, 1, 8},
|
||||
{15, 7, 13, 4}};
|
||||
size_t bitOffset = 0;
|
||||
|
||||
memset(error_bufferold, 0, bufw * sizeof(Error));
|
||||
for (uint16_t y = 0; y < bufh; y++) {
|
||||
@@ -132,43 +162,72 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t
|
||||
break;
|
||||
}
|
||||
|
||||
if (imageParams.dither == 2) {
|
||||
// Ordered dithering
|
||||
uint8_t ditherValue = ditherMatrix[y % 4][x % 4];
|
||||
error_bufferold[x].r = (ditherValue << 4) - 120; // * 256 / 16 - 128 + 8
|
||||
error_bufferold[x].g = (ditherValue << 4) - 120;
|
||||
error_bufferold[x].b = (ditherValue << 4) - 120;
|
||||
}
|
||||
|
||||
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++) {
|
||||
if (best_color_distance == 0) break;
|
||||
uint32_t distance = colorDistance(color, palette[i], error_bufferold[x]);
|
||||
if (distance < best_color_distance) {
|
||||
best_color_distance = distance;
|
||||
best_color_index = i;
|
||||
if (imageParams.dither == 2) {
|
||||
// special ordered dithering
|
||||
auto [c1Index, c2Index, distC1, distC2] = findClosestColors(color, palette);
|
||||
Color c1 = palette[c1Index];
|
||||
Color c2 = palette[c2Index];
|
||||
float weight = distC1 / (distC1 + distC2);
|
||||
if (weight <= 0.03) {
|
||||
best_color_index = c1Index;
|
||||
} else if (weight < 0.30) {
|
||||
best_color_index = ((y % 2 && ((y / 2 + x) % 2)) ? c2Index : c1Index);
|
||||
} else if (weight < 0.70) {
|
||||
best_color_index = ((x + y) % 2 ? c2Index : c1Index);
|
||||
} else if (weight < 0.97) {
|
||||
best_color_index = ((y % 2 && ((y / 2 + x) % 2)) % 2 ? c1Index : c2Index);
|
||||
} else {
|
||||
best_color_index = c2Index;
|
||||
}
|
||||
}
|
||||
uint8_t bitIndex = 7 - (x % 8);
|
||||
uint32_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)
|
||||
if (imageParams.dither == 1 || imageParams.dither == 0) {
|
||||
uint32_t best_color_distance = colorDistance(color, palette[0], error_bufferold[x]);
|
||||
|
||||
for (int i = 1; i < num_colors; i++) {
|
||||
if (best_color_distance == 0) break;
|
||||
uint32_t distance = colorDistance(color, palette[i], error_bufferold[x]);
|
||||
if (distance < best_color_distance) {
|
||||
best_color_distance = distance;
|
||||
best_color_index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (imageParams.bpp == 3 || imageParams.bpp == 4) {
|
||||
size_t byteIndex = bitOffset / 8;
|
||||
uint8_t bitIndex = bitOffset % 8;
|
||||
|
||||
if (bitIndex + imageParams.bpp <= 8) {
|
||||
buffer[byteIndex] |= best_color_index << (8 - bitIndex - imageParams.bpp);
|
||||
} else {
|
||||
uint8_t highPart = best_color_index >> (bitIndex + imageParams.bpp - 8);
|
||||
uint8_t lowPart = best_color_index & ((1 << (bitIndex + imageParams.bpp - 8)) - 1);
|
||||
buffer[byteIndex] |= highPart;
|
||||
buffer[byteIndex + 1] |= lowPart << (8 - (bitIndex + imageParams.bpp - 8));
|
||||
}
|
||||
bitOffset += imageParams.bpp;
|
||||
} else {
|
||||
uint8_t bitIndex = 7 - (x % 8);
|
||||
uint32_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:
|
||||
imageParams.hasRed = true;
|
||||
buffer[byteIndex] |= (1 << bitIndex);
|
||||
break;
|
||||
case 2:
|
||||
imageParams.hasRed = true;
|
||||
if (is_red)
|
||||
buffer[byteIndex] |= (1 << bitIndex);
|
||||
break;
|
||||
case 3:
|
||||
imageParams.hasRed = true;
|
||||
buffer[byteIndex] |= (1 << bitIndex);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (imageParams.dither == 1) {
|
||||
@@ -179,34 +238,44 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t
|
||||
color.g + error_bufferold[x].g - palette[best_color_index].g,
|
||||
color.b + error_bufferold[x].b - palette[best_color_index].b};
|
||||
|
||||
error_buffernew[x].r += error.r >> 2;
|
||||
error_buffernew[x].g += error.g >> 2;
|
||||
error_buffernew[x].b += error.b >> 2;
|
||||
float scaling_factor = 255.0f / std::max(std::abs(error.r), std::max(std::abs(error.g), std::abs(error.b)));
|
||||
if (scaling_factor < 1.0f) {
|
||||
error.r *= scaling_factor;
|
||||
error.g *= scaling_factor;
|
||||
error.b *= scaling_factor;
|
||||
}
|
||||
|
||||
error_buffernew[x].r += error.r / 4;
|
||||
error_buffernew[x].g += error.g / 4;
|
||||
error_buffernew[x].b += error.b / 4;
|
||||
|
||||
if (x > 0) {
|
||||
error_buffernew[x - 1].r += error.r >> 3;
|
||||
error_buffernew[x - 1].g += error.g >> 3;
|
||||
error_buffernew[x - 1].b += error.b >> 3;
|
||||
error_buffernew[x - 1].r += error.r / 8;
|
||||
error_buffernew[x - 1].g += error.g / 8;
|
||||
error_buffernew[x - 1].b += error.b / 8;
|
||||
}
|
||||
|
||||
if (x > 1) {
|
||||
error_buffernew[x - 2].r += error.r >> 4;
|
||||
error_buffernew[x - 2].g += error.g >> 4;
|
||||
error_buffernew[x - 2].b += error.b >> 4;
|
||||
error_buffernew[x - 2].r += error.r / 16;
|
||||
error_buffernew[x - 2].g += error.g / 16;
|
||||
error_buffernew[x - 2].b += error.b / 16;
|
||||
}
|
||||
error_buffernew[x + 1].r += error.r >> 3;
|
||||
error_buffernew[x + 1].g += error.g >> 3;
|
||||
error_buffernew[x + 1].b += error.b >> 3;
|
||||
|
||||
error_bufferold[x + 1].r += error.r >> 2;
|
||||
error_bufferold[x + 1].g += error.g >> 2;
|
||||
error_bufferold[x + 1].b += error.b >> 2;
|
||||
error_buffernew[x + 1].r += error.r / 8;
|
||||
error_buffernew[x + 1].g += error.g / 8;
|
||||
error_buffernew[x + 1].b += error.b / 8;
|
||||
|
||||
error_buffernew[x + 2].r += error.r >> 4;
|
||||
error_buffernew[x + 2].g += error.g >> 4;
|
||||
error_buffernew[x + 2].b += error.b >> 4;
|
||||
error_bufferold[x + 1].r += error.r / 4;
|
||||
error_bufferold[x + 1].g += error.g / 4;
|
||||
error_bufferold[x + 1].b += error.b / 4;
|
||||
|
||||
error_bufferold[x + 2].r += error.r >> 3;
|
||||
error_bufferold[x + 2].g += error.g >> 3;
|
||||
error_bufferold[x + 2].b += error.b >> 3;
|
||||
error_buffernew[x + 2].r += error.r / 16;
|
||||
error_buffernew[x + 2].g += error.g / 16;
|
||||
error_buffernew[x + 2].b += error.b / 16;
|
||||
|
||||
error_bufferold[x + 2].r += error.r / 8;
|
||||
error_bufferold[x + 2].g += error.g / 8;
|
||||
error_bufferold[x + 2].b += error.b / 8;
|
||||
}
|
||||
}
|
||||
memcpy(error_bufferold, error_buffernew, bufw * sizeof(Error));
|
||||
@@ -226,7 +295,10 @@ size_t prepareHeader(uint8_t headerbuf[], uint16_t bufw, uint16_t bufh, imgParam
|
||||
memcpy(headerbuf + (imageParams.rotatebuffer % 2 == 1 ? 3 : 1), &bufw, sizeof(uint16_t));
|
||||
memcpy(headerbuf + (imageParams.rotatebuffer % 2 == 1 ? 1 : 3), &bufh, sizeof(uint16_t));
|
||||
|
||||
if (imageParams.hasRed && imageParams.bpp > 1) {
|
||||
if (imageParams.bpp == 3 || imageParams.bpp == 4) {
|
||||
totalbytes = buffer_size * imageParams.bpp + headersize;
|
||||
headerbuf[5] = imageParams.bpp;
|
||||
} else if (imageParams.hasRed && imageParams.bpp > 1) {
|
||||
totalbytes = buffer_size * 2 + headersize;
|
||||
headerbuf[5] = 2;
|
||||
} else {
|
||||
@@ -266,19 +338,96 @@ void rewriteHeader(File &f_out) {
|
||||
f_out.write(flg);
|
||||
}
|
||||
|
||||
#ifndef SAVE_SPACE
|
||||
uint8_t *g5Compress(uint16_t width, uint16_t height, uint8_t *buffer, uint16_t buffersize, uint16_t &outBufferSize) {
|
||||
G5ENCIMAGE g5enc;
|
||||
int rc;
|
||||
uint8_t *outbuffer = (uint8_t *)ps_malloc(buffersize+16384);
|
||||
if (outbuffer == NULL) {
|
||||
Serial.println("Failed to allocate the output buffer for the G5 encoder");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rc = g5_encode_init(&g5enc, width, height, outbuffer, buffersize);
|
||||
for (int y = 0; y < height && rc == G5_SUCCESS; y++) {
|
||||
rc = g5_encode_encodeLine(&g5enc, buffer);
|
||||
buffer += (width / 8);
|
||||
if (rc != G5_SUCCESS) break;
|
||||
}
|
||||
if (rc == G5_ENCODE_COMPLETE) {
|
||||
outBufferSize = g5_encode_getOutSize(&g5enc);
|
||||
} else {
|
||||
printf("Encode failed! rc=%d\n", rc);
|
||||
free(outbuffer);
|
||||
return nullptr;
|
||||
}
|
||||
return outbuffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
// The "ts_option" is a bitmapped variable with a default value of 1
|
||||
// which is black on white, long format @ bottom right.
|
||||
//
|
||||
// b2, b1, b0:
|
||||
// 0 - no timestamp
|
||||
// 1 - bottom right
|
||||
// 2 - top right
|
||||
// 3 - bottom left
|
||||
// 4 - top left
|
||||
// 5 -> 7 reserved
|
||||
// b3:
|
||||
// 0 - long format (year-month-day hr:min)
|
||||
// 1 - short format (month-day hr:min)
|
||||
// b4:
|
||||
// 0 - black on white
|
||||
// 1 - white on black
|
||||
// b5 -> b7: reserved
|
||||
//
|
||||
void doTimestamp(TFT_eSprite *spr, uint8_t ts_option) {
|
||||
time_t now = time(nullptr);
|
||||
struct tm *timeinfo = localtime(&now);
|
||||
char buffer[20];
|
||||
strftime(buffer, sizeof(buffer),
|
||||
(ts_option & 0x8) ? "%m-%d %H:%M" : "%Y-%m-%d %H:%M",timeinfo);
|
||||
int ts_chars = strlen(buffer);
|
||||
|
||||
uint16_t char_color;
|
||||
uint16_t bg_color;
|
||||
|
||||
if(ts_option & 0x10) {
|
||||
char_color = TFT_WHITE;
|
||||
bg_color= TFT_BLACK;
|
||||
}
|
||||
else {
|
||||
char_color = TFT_BLACK;
|
||||
bg_color = TFT_WHITE;
|
||||
}
|
||||
|
||||
ts_option = (ts_option & 0x3) - 1;
|
||||
|
||||
int32_t ts_x = (ts_option & 2) ? 1 : spr->width() - ts_chars * 6 - 2;
|
||||
int32_t ts_y = (ts_option & 1) ? 1 : spr->height() - 10;
|
||||
|
||||
spr->drawRect(ts_x - 1, ts_y - 1, ts_chars * 6 + 1, 9, bg_color);
|
||||
spr->setTextColor(char_color, bg_color);
|
||||
spr->setCursor(ts_x,ts_y);
|
||||
spr->print(buffer);
|
||||
}
|
||||
|
||||
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
|
||||
long t = millis();
|
||||
|
||||
if (imageParams.ts_option) doTimestamp(&spr,imageParams.ts_option);
|
||||
#ifdef HAS_TFT
|
||||
extern uint8_t YellowSense;
|
||||
if (fileout == "direct") {
|
||||
if (tftOverride == false) {
|
||||
TFT_eSprite spr2 = TFT_eSprite(&tft2);
|
||||
#ifdef ST7735_NANO_TLSR
|
||||
#ifdef ST7735_NANO_TLSR
|
||||
tft2.setRotation(1);
|
||||
#else
|
||||
#else
|
||||
tft2.setRotation(YellowSense == 1 ? 1 : 3);
|
||||
#endif
|
||||
#endif
|
||||
spr2.createSprite(spr.width(), spr.height());
|
||||
spr2.setColorDepth(spr.getColorDepth());
|
||||
|
||||
@@ -287,20 +436,19 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
|
||||
size_t dataSize = spr.width() * spr.height() * (spr.getColorDepth() / 8);
|
||||
memcpy(spriteData2, spriteData, dataSize);
|
||||
|
||||
#ifdef HAS_LILYGO_TPANEL
|
||||
if (spr.getColorDepth() == 16)
|
||||
{
|
||||
long dy = spr.height();
|
||||
long dx = spr.width();
|
||||
|
||||
uint16_t* data = static_cast<uint16_t*>(const_cast<void*>(spriteData2));
|
||||
#if defined HAS_LILYGO_TPANEL || defined HAS_4inch_TPANEL
|
||||
if (spr.getColorDepth() == 16) {
|
||||
long dy = spr.height();
|
||||
long dx = spr.width();
|
||||
|
||||
gfx->draw16bitRGBBitmap(0, 0, (uint16_t *)spriteData2, dx, dy);
|
||||
spr2.deleteSprite();
|
||||
}
|
||||
#else
|
||||
uint16_t *data = static_cast<uint16_t *>(const_cast<void *>(spriteData2));
|
||||
|
||||
gfx->draw16bitRGBBitmap(0, 0, (uint16_t *)spriteData2, dx, dy);
|
||||
spr2.deleteSprite();
|
||||
}
|
||||
#else
|
||||
spr2.pushSprite(0, 0);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -313,12 +461,13 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
|
||||
case 1:
|
||||
case 2: {
|
||||
long bufw = spr.width(), bufh = spr.height();
|
||||
size_t buffer_size = (bufw * bufh) / 8;
|
||||
size_t buffer_size = ((bufw * bufh) + 7) / 8; // round up: not all dimensions are multiples of 8
|
||||
#ifdef BOARD_HAS_PSRAM
|
||||
uint8_t *buffer = (uint8_t *)ps_malloc(buffer_size);
|
||||
#else
|
||||
uint8_t *buffer = (uint8_t *)malloc(buffer_size);
|
||||
imageParams.zlib = 0;
|
||||
imageParams.g5 = 0;
|
||||
#endif
|
||||
if (!buffer) {
|
||||
Serial.println("Failed to allocate buffer");
|
||||
@@ -359,6 +508,69 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
|
||||
free(comp);
|
||||
|
||||
rewriteHeader(f_out);
|
||||
#ifndef SAVE_SPACE
|
||||
} else if (imageParams.g5) {
|
||||
// handling for G5-compressed image data
|
||||
|
||||
uint8_t headerbuf[6];
|
||||
prepareHeader(headerbuf, bufw, bufh, imageParams, buffer_size);
|
||||
f_out.write(headerbuf, sizeof(headerbuf));
|
||||
|
||||
uint16_t height = imageParams.height; // spr.height();
|
||||
uint16_t width = imageParams.width;
|
||||
spr.width();
|
||||
if (imageParams.hasRed && imageParams.bpp > 1) {
|
||||
uint8_t *newbuffer = (uint8_t *)ps_realloc(buffer, 2 * buffer_size);
|
||||
if (newbuffer == NULL) {
|
||||
Serial.println("Failed to allocate larger buffer for 2bpp G5");
|
||||
free(buffer);
|
||||
f_out.close();
|
||||
xSemaphoreGive(fsMutex);
|
||||
return;
|
||||
}
|
||||
buffer = newbuffer;
|
||||
spr2color(spr, imageParams, buffer + buffer_size, buffer_size, true);
|
||||
buffer_size *= 2;
|
||||
// double the height, to do two layers sequentially
|
||||
if (imageParams.rotatebuffer % 2) {
|
||||
width *= 2;
|
||||
} else {
|
||||
height *= 2;
|
||||
}
|
||||
}
|
||||
uint16_t outbufferSize = 0;
|
||||
uint8_t *outBuffer;
|
||||
bool compressionSuccessful = true;
|
||||
if (imageParams.rotatebuffer % 2) {
|
||||
outBuffer = g5Compress(height, width, buffer, buffer_size, outbufferSize);
|
||||
} else {
|
||||
outBuffer = g5Compress(width, height, buffer, buffer_size, outbufferSize);
|
||||
}
|
||||
if (outBuffer == NULL) {
|
||||
Serial.println("Failed to compress G5");
|
||||
compressionSuccessful = false;
|
||||
} else {
|
||||
printf("Compressed %d to %d bytes\n", buffer_size, outbufferSize);
|
||||
if (outbufferSize > buffer_size) {
|
||||
printf("That wasn't very useful, falling back to raw\n");
|
||||
compressionSuccessful = false;
|
||||
} else {
|
||||
f_out.write(outBuffer, outbufferSize);
|
||||
}
|
||||
free(outBuffer);
|
||||
}
|
||||
if (!compressionSuccessful) {
|
||||
// if we failed to compress the image, or the resulting image was larger than a raw file, fallback
|
||||
imageParams.g5 = false;
|
||||
if (imageParams.hasRed && imageParams.bpp > 1) {
|
||||
imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
|
||||
} else {
|
||||
imageParams.dataType = DATATYPE_IMG_RAW_1BPP;
|
||||
}
|
||||
f_out.seek(0);
|
||||
f_out.write(buffer, buffer_size);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
f_out.write(buffer, buffer_size);
|
||||
if (imageParams.hasRed && imageParams.bpp > 1) {
|
||||
@@ -370,6 +582,24 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
|
||||
free(buffer);
|
||||
} break;
|
||||
|
||||
case 3:
|
||||
case 4: {
|
||||
long bufw = spr.width(), bufh = spr.height();
|
||||
size_t buffer_size = ((bufw * bufh) + 7) / 8 * imageParams.bpp;
|
||||
uint8_t *buffer = (uint8_t *)ps_malloc(buffer_size);
|
||||
if (!buffer) {
|
||||
Serial.println("Failed to allocate buffer");
|
||||
util::printLargestFreeBlock();
|
||||
f_out.close();
|
||||
xSemaphoreGive(fsMutex);
|
||||
return;
|
||||
}
|
||||
spr2color(spr, imageParams, buffer, buffer_size, false);
|
||||
f_out.write(buffer, buffer_size);
|
||||
|
||||
free(buffer);
|
||||
} break;
|
||||
|
||||
case 16: {
|
||||
size_t spriteDataSize = (spr.getColorDepth() == 1) ? (spr.width() * spr.height() / 8) : ((spr.getColorDepth() == 8) ? (spr.width() * spr.height()) : ((spr.getColorDepth() == 16) ? (spr.width() * spr.height() * 2) : 0));
|
||||
f_out.write((const uint8_t *)spr.getPointer(), spriteDataSize);
|
||||
|
||||
@@ -273,7 +273,10 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
|
||||
case DATATYPE_IMG_DIFF:
|
||||
case DATATYPE_IMG_ZLIB:
|
||||
case DATATYPE_IMG_RAW_1BPP:
|
||||
case DATATYPE_IMG_RAW_2BPP: {
|
||||
case DATATYPE_IMG_RAW_2BPP:
|
||||
case DATATYPE_IMG_G5:
|
||||
case DATATYPE_IMG_RAW_3BPP:
|
||||
case DATATYPE_IMG_RAW_4BPP: {
|
||||
char hexmac[17];
|
||||
mac2hex(pending->targetMac, hexmac);
|
||||
String filename = "/current/" + String(hexmac) + "_" + String(millis() % 1000000) + ".pending";
|
||||
@@ -336,8 +339,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
|
||||
break;
|
||||
}
|
||||
case DATATYPE_NFC_RAW_CONTENT:
|
||||
case DATATYPE_NFC_URL_DIRECT:
|
||||
case DATATYPE_CUSTOM_LUT_OTA: {
|
||||
case DATATYPE_NFC_URL_DIRECT: {
|
||||
char hexmac[17];
|
||||
mac2hex(pending->targetMac, hexmac);
|
||||
char dataUrl[80];
|
||||
@@ -346,7 +348,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
|
||||
snprintf(dataUrl, sizeof(dataUrl), "http://%s/getdata?mac=%s&md5=%s", remoteIP.toString().c_str(), hexmac, md5);
|
||||
wsLog("GET " + String(dataUrl));
|
||||
HTTPClient http;
|
||||
logLine("http DATATYPE_CUSTOM_LUT_OTA " + String(dataUrl));
|
||||
logLine("http DATATYPE_NFC_* " + String(dataUrl));
|
||||
http.begin(dataUrl);
|
||||
int httpCode = http.GET();
|
||||
if (httpCode == 200) {
|
||||
@@ -445,9 +447,11 @@ void processXferComplete(struct espXferComplete* xfc, bool local) {
|
||||
contentFS->remove(dst_path);
|
||||
}
|
||||
if (contentFS->exists(queueItem->filename)) {
|
||||
if (config.preview && (queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_2BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_1BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_ZLIB)) {
|
||||
uint8_t dataType = queueItem->pendingdata.availdatainfo.dataType;
|
||||
if (config.preview && dataType != DATATYPE_FW_UPDATE && dataType != DATATYPE_NOUPDATE) {
|
||||
contentFS->rename(queueItem->filename, String(dst_path));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (queueItem->pendingdata.availdatainfo.dataType != DATATYPE_FW_UPDATE) contentFS->remove(queueItem->filename);
|
||||
}
|
||||
}
|
||||
@@ -503,9 +507,9 @@ void processXferTimeout(struct espXferComplete* xfc, bool local) {
|
||||
if (taginfo != nullptr) {
|
||||
taginfo->pendingIdle = 60;
|
||||
clearPending(taginfo);
|
||||
while (dequeueItem(xfc->src)) {
|
||||
};
|
||||
}
|
||||
while (dequeueItem(xfc->src)) {
|
||||
};
|
||||
|
||||
checkQueue(xfc->src);
|
||||
|
||||
@@ -556,15 +560,14 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP
|
||||
taginfo->apIp = IPAddress(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
if (taginfo->pendingIdle == 0) {
|
||||
taginfo->expectedNextCheckin = now + 60;
|
||||
if (taginfo->pendingIdle == 0 || countQueueItem(eadr->src) > 0) {
|
||||
if (taginfo->expectedNextCheckin < now + 60) taginfo->expectedNextCheckin = now + 60;
|
||||
} else if (taginfo->pendingIdle == 9999) {
|
||||
taginfo->expectedNextCheckin = 3216153600;
|
||||
taginfo->pendingIdle = 0;
|
||||
} else {
|
||||
taginfo->expectedNextCheckin = now + taginfo->pendingIdle;
|
||||
taginfo->pendingIdle = 0;
|
||||
}
|
||||
taginfo->pendingIdle = 0;
|
||||
taginfo->lastseen = now;
|
||||
|
||||
if (eadr->adr.lastPacketRSSI != 0) {
|
||||
@@ -603,6 +606,7 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP
|
||||
if (local) {
|
||||
sprintf(buffer, "<ADR %02X%02X%02X%02X%02X%02X%02X%02X\r\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);
|
||||
checkQueue(eadr->src); // experiemental 3/26/25: redundant check
|
||||
}
|
||||
|
||||
if (local) {
|
||||
@@ -748,6 +752,35 @@ bool sendTagCommand(const uint8_t* dst, uint8_t cmd, bool local, const uint8_t*
|
||||
}
|
||||
}
|
||||
|
||||
bool sendTagMac(const uint8_t* dst, const uint64_t newmac, bool local) {
|
||||
struct pendingData pending = {0};
|
||||
memcpy(pending.targetMac, dst, 8);
|
||||
pending.availdatainfo.dataType = DATATYPE_COMMAND_DATA;
|
||||
pending.availdatainfo.dataTypeArgument = 0x23;
|
||||
pending.availdatainfo.nextCheckIn = 0;
|
||||
|
||||
|
||||
pending.availdatainfo.dataVer = newmac;
|
||||
pending.availdatainfo.dataSize = 0;
|
||||
|
||||
pending.attemptsLeft = MAX_XFER_ATTEMPTS;
|
||||
Serial.printf(">Tag %02X%02X%02X%02X%02X%02X%02X%02X Mac set\r\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
|
||||
|
||||
tagRecord* taginfo = tagRecord::findByMAC(dst);
|
||||
if (taginfo != nullptr) {
|
||||
taginfo->pendingCount++;
|
||||
wsSendTaginfo(taginfo->mac, SYNC_TAGSTATUS);
|
||||
}
|
||||
|
||||
if (local) {
|
||||
return queueDataAvail(&pending, true);
|
||||
} else {
|
||||
queueDataAvail(&pending, false);
|
||||
udpsync.netSendDataAvail(&pending);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void updateTaginfoitem(struct TagInfo* taginfoitem, IPAddress remoteIP) {
|
||||
tagRecord* taginfo = tagRecord::findByMAC(taginfoitem->mac);
|
||||
|
||||
@@ -800,7 +833,7 @@ bool checkMirror(struct tagRecord* taginfo, struct pendingData* pending) {
|
||||
for (int16_t c = 0; c < tagDB.size(); c++) {
|
||||
tagRecord* taginfo2 = tagDB.at(c);
|
||||
if (taginfo2->contentMode == 20 && taginfo2->version == 0) {
|
||||
DynamicJsonDocument doc(500);
|
||||
JsonDocument doc;
|
||||
deserializeJson(doc, taginfo2->modeConfigJson);
|
||||
JsonObject cfgobj = doc.as<JsonObject>();
|
||||
uint8_t mac[8] = {0};
|
||||
@@ -953,20 +986,23 @@ bool queueDataAvail(struct pendingData* pending, bool local) {
|
||||
taginfo->data = nullptr;
|
||||
} else {
|
||||
newPending.data = nullptr;
|
||||
|
||||
// optional: read data early, don't wait for block request.
|
||||
fs::File file = contentFS->open(newPending.filename);
|
||||
if (file) {
|
||||
newPending.data = getDataForFile(file);
|
||||
Serial.println("Reading file " + String(newPending.filename));
|
||||
file.close();
|
||||
} else {
|
||||
Serial.println("Warning: not found: " + String(newPending.filename));
|
||||
|
||||
if (pendingQueue.size() < 5) { // maximized to 5 to save some memory
|
||||
// optional: read data early, don't wait for block request.
|
||||
fs::File file = contentFS->open(newPending.filename);
|
||||
if (file) {
|
||||
newPending.data = getDataForFile(file);
|
||||
Serial.println("Reading file " + String(newPending.filename));
|
||||
file.close();
|
||||
} else {
|
||||
Serial.println("Warning: not found: " + String(newPending.filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
newPending.len = taginfo->len;
|
||||
|
||||
if ((pending->availdatainfo.dataType == DATATYPE_IMG_RAW_1BPP || pending->availdatainfo.dataType == DATATYPE_IMG_RAW_2BPP || pending->availdatainfo.dataType == DATATYPE_IMG_ZLIB) && (pending->availdatainfo.dataTypeArgument & 0xF8) == 0x00) {
|
||||
uint8_t dataType = pending->availdatainfo.dataType;
|
||||
if (dataType != DATATYPE_FW_UPDATE && dataType != DATATYPE_NOUPDATE && pending->availdatainfo.dataTypeArgument & 0xF8 == 0x00) {
|
||||
// in case of an image (no preload), remove already queued images
|
||||
pendingQueue.erase(std::remove_if(pendingQueue.begin(), pendingQueue.end(),
|
||||
[pending](const PendingItem& item) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <MD5Builder.h>
|
||||
#include <Update.h>
|
||||
|
||||
#include "flasher.h"
|
||||
#include "espflasher.h"
|
||||
#include "leds.h"
|
||||
#include "serialap.h"
|
||||
@@ -15,6 +16,7 @@
|
||||
#include "util.h"
|
||||
#include "web.h"
|
||||
|
||||
|
||||
#ifndef BUILD_ENV_NAME
|
||||
#define BUILD_ENV_NAME unknown
|
||||
#endif
|
||||
@@ -30,9 +32,11 @@
|
||||
|
||||
#define STR_IMPL(x) #x
|
||||
#define STR(x) STR_IMPL(x)
|
||||
#define LOG(format, ... ) Serial.printf(format,## __VA_ARGS__)
|
||||
|
||||
|
||||
void handleSysinfoRequest(AsyncWebServerRequest* request) {
|
||||
StaticJsonDocument<250> doc;
|
||||
JsonDocument doc;
|
||||
doc["alias"] = config.alias;
|
||||
doc["env"] = STR(BUILD_ENV_NAME);
|
||||
doc["buildtime"] = STR(BUILD_TIME);
|
||||
@@ -41,11 +45,20 @@ void handleSysinfoRequest(AsyncWebServerRequest* request) {
|
||||
doc["psramsize"] = ESP.getPsramSize();
|
||||
doc["flashsize"] = ESP.getFlashChipSize();
|
||||
doc["rollback"] = Update.canRollBack();
|
||||
#if defined C6_OTA_FLASHING
|
||||
doc["hasC6"] = 1;
|
||||
#else
|
||||
doc["ap_version"] = apInfo.version;
|
||||
|
||||
doc["hasC6"] = 0;
|
||||
doc["hasH2"] = 0;
|
||||
doc["hasTslr"] = 0;
|
||||
|
||||
#if defined HAS_H2
|
||||
doc["hasH2"] = 1;
|
||||
#elif defined HAS_TSLR
|
||||
doc["hasTslr"] = 1;
|
||||
#elif defined C6_OTA_FLASHING
|
||||
doc["hasC6"] = 1;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_EXT_FLASHER
|
||||
doc["hasFlasher"] = 1;
|
||||
#else
|
||||
@@ -66,7 +79,7 @@ void handleCheckFile(AsyncWebServerRequest* request) {
|
||||
const String filePath = request->getParam("path")->value();
|
||||
File file = contentFS->open(filePath, "r");
|
||||
if (!file) {
|
||||
StaticJsonDocument<64> doc;
|
||||
JsonDocument doc;
|
||||
doc["filesize"] = 0;
|
||||
doc["md5"] = "";
|
||||
String jsonResponse;
|
||||
@@ -85,7 +98,7 @@ void handleCheckFile(AsyncWebServerRequest* request) {
|
||||
|
||||
file.close();
|
||||
|
||||
StaticJsonDocument<128> doc;
|
||||
JsonDocument doc;
|
||||
doc["filesize"] = fileSize;
|
||||
doc["md5"] = md5Hash;
|
||||
String jsonResponse;
|
||||
@@ -135,12 +148,13 @@ void handleLittleFSUpload(AsyncWebServerRequest* request, String filename, size_
|
||||
file.write(uploadInfo->buffer, uploadInfo->bufferSize);
|
||||
file.close();
|
||||
uploadInfo->bufferSize = 0;
|
||||
xSemaphoreGive(fsMutex);
|
||||
} else {
|
||||
xSemaphoreGive(fsMutex);
|
||||
logLine("Failed to open file for appending: " + uploadfilename);
|
||||
final = true;
|
||||
error = true;
|
||||
}
|
||||
xSemaphoreGive(fsMutex);
|
||||
|
||||
memcpy(uploadInfo->buffer, data, len);
|
||||
uploadInfo->bufferSize = len;
|
||||
@@ -153,11 +167,12 @@ void handleLittleFSUpload(AsyncWebServerRequest* request, String filename, size_
|
||||
if (file) {
|
||||
file.write(uploadInfo->buffer, uploadInfo->bufferSize);
|
||||
file.close();
|
||||
xSemaphoreGive(fsMutex);
|
||||
} else {
|
||||
xSemaphoreGive(fsMutex);
|
||||
logLine("Failed to open file for appending: " + uploadfilename);
|
||||
error = true;
|
||||
}
|
||||
xSemaphoreGive(fsMutex);
|
||||
request->_tempObject = nullptr;
|
||||
delete uploadInfo;
|
||||
}
|
||||
@@ -289,22 +304,29 @@ void handleRollback(AsyncWebServerRequest* request) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef C6_OTA_FLASHING
|
||||
void C6firmwareUpdateTask(void* parameter) {
|
||||
uint8_t doDownload = *((uint8_t*)parameter);
|
||||
char* urlPtr = reinterpret_cast<char*>(parameter);
|
||||
|
||||
LOG("C6firmwareUpdateTask: url '%s'\n", urlPtr);
|
||||
wsSerial("Stopping AP service");
|
||||
|
||||
setAPstate(false, AP_STATE_FLASHING);
|
||||
gSerialTaskState = SERIAL_STATE_STOP;
|
||||
config.runStatus = RUNSTATUS_STOP;
|
||||
setAPstate(false, AP_STATE_FLASHING);
|
||||
#ifndef FLASHER_DEBUG_SHARED
|
||||
extern bool rxSerialStopTask2;
|
||||
rxSerialStopTask2 = true;
|
||||
#endif
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
Serial1.end();
|
||||
setAPstate(false, AP_STATE_FLASHING);
|
||||
|
||||
wsSerial("C6 flash starting");
|
||||
wsSerial(SHORT_CHIP_NAME " flash starting");
|
||||
|
||||
bool result = doC6flash(doDownload);
|
||||
bool result = FlashC6_H2(urlPtr);
|
||||
|
||||
wsSerial("C6 flash end");
|
||||
wsSerial(SHORT_CHIP_NAME " flash end");
|
||||
|
||||
if (result) {
|
||||
setAPstate(false, AP_STATE_OFFLINE);
|
||||
@@ -314,36 +336,65 @@ void C6firmwareUpdateTask(void* parameter) {
|
||||
|
||||
wsSerial("starting monitor");
|
||||
Serial1.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD);
|
||||
#ifndef FLASHER_DEBUG_SHARED
|
||||
rxSerialStopTask2 = false;
|
||||
#ifdef FLASHER_DEBUG_RXD
|
||||
xTaskCreate(rxSerialTask2, "rxSerialTask2", 1750, NULL, 2, NULL);
|
||||
xTaskCreate(rxSerialTask2, "rxSerialTask2", 1850, NULL, 2, NULL);
|
||||
#endif
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
wsSerial("resetting AP");
|
||||
APTagReset();
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
apInfo.version = 0;
|
||||
wsSerial("bringing AP online");
|
||||
if (bringAPOnline()) config.runStatus = RUNSTATUS_RUN;
|
||||
// if (bringAPOnline(AP_STATE_REQUIRED_POWER_CYCLE)) config.runStatus = RUNSTATUS_STOP;
|
||||
if (bringAPOnline(AP_STATE_ONLINE)) {
|
||||
config.runStatus = RUNSTATUS_RUN;
|
||||
setAPstate(true, AP_STATE_ONLINE);
|
||||
}
|
||||
|
||||
wsSerial("Finished!");
|
||||
} else {
|
||||
wsSerial("Flashing failed. :-(");
|
||||
// Wait for version info to arrive
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
if(apInfo.version == 0) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
wsSerial("Finished!");
|
||||
char buffer[50];
|
||||
snprintf(buffer,sizeof(buffer),
|
||||
"ESP32-" SHORT_CHIP_NAME " version is now %04x", apInfo.version);
|
||||
wsSerial(String(buffer));
|
||||
}
|
||||
else if(apInfo.version == 0) {
|
||||
wsSerial("AP failed to come online. :-(");
|
||||
}
|
||||
else {
|
||||
wsSerial("Flashing failed. :-(");
|
||||
}
|
||||
// wsSerial("Reboot system now");
|
||||
// wsSerial("[reboot]");
|
||||
free(urlPtr);
|
||||
vTaskDelay(30000 / portTICK_PERIOD_MS);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void handleUpdateC6(AsyncWebServerRequest* request) {
|
||||
#if defined C6_OTA_FLASHING
|
||||
uint8_t doDownload = 1;
|
||||
if (request->hasParam("download", true)) {
|
||||
doDownload = atoi(request->getParam("download", true)->value().c_str());
|
||||
if (request->hasParam("url",true)) {
|
||||
const char* urlStr = request->getParam("url", true)->value().c_str();
|
||||
char* urlCopy = strdup(urlStr);
|
||||
xTaskCreate(C6firmwareUpdateTask, "OTAUpdateTask", 6400, urlCopy, 10, NULL);
|
||||
request->send(200, "Ok");
|
||||
}
|
||||
xTaskCreate(C6firmwareUpdateTask, "OTAUpdateTask", 6144, &doDownload, 10, NULL);
|
||||
request->send(200, "Ok");
|
||||
else {
|
||||
LOG("Sending bad request");
|
||||
request->send(400, "Bad request");
|
||||
}
|
||||
#elif defined(SHORT_CHIP_NAME)
|
||||
request->send(400, SHORT_CHIP_NAME " flashing not implemented");
|
||||
#else
|
||||
request->send(400, "C6 flashing not implemented");
|
||||
request->send(400, "C6/H2 flashing not implemented");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -355,7 +406,7 @@ void handleUpdateActions(AsyncWebServerRequest* request) {
|
||||
request->send(200, "No update actions needed");
|
||||
return;
|
||||
}
|
||||
DynamicJsonDocument doc(1000);
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
const JsonArray deleteFiles = doc["deletefile"].as<JsonArray>();
|
||||
for (const auto& filePath : deleteFiles) {
|
||||
@@ -367,4 +418,4 @@ void handleUpdateActions(AsyncWebServerRequest* request) {
|
||||
wsSerial("Cleanup finished");
|
||||
request->send(200, "Clean up finished");
|
||||
contentFS->remove("/update_actions.json");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "settings.h"
|
||||
#include "leds.h"
|
||||
|
||||
#ifdef HAS_EXT_FLASHER
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
@@ -34,16 +35,21 @@ void rampTagPower(uint8_t* pin, bool up) {
|
||||
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
|
||||
#endif
|
||||
if (up) {
|
||||
#if ESP_ARDUINO_VERSION_MAJOR == 2
|
||||
ledcSetup(0, 50000, 8);
|
||||
ledcWrite(0, 254);
|
||||
vTaskDelay(1 / portTICK_PERIOD_MS);
|
||||
pinMode(pin[0], OUTPUT);
|
||||
ledcAttachPin(pin[0], 0);
|
||||
#else
|
||||
ledcWriteChannel(0, 254);
|
||||
ledcAttachChannel(pin[0], 50000, 8, 0);
|
||||
#endif
|
||||
pinMode(FLASHER_EXT_RESET, OUTPUT);
|
||||
digitalWrite(FLASHER_EXT_RESET, LOW);
|
||||
ledcAttachPin(pin[0], 0);
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
for (uint8_t c = 254; c != 0xFF; c--) {
|
||||
ledcWrite(0, c);
|
||||
ledcSet(0, c);
|
||||
delayMicroseconds(700);
|
||||
}
|
||||
digitalWrite(pin[0], LOW);
|
||||
@@ -52,14 +58,14 @@ void rampTagPower(uint8_t* pin, bool up) {
|
||||
digitalWrite(FLASHER_EXT_RESET, INPUT_PULLUP);
|
||||
} else {
|
||||
ledcSetup(0, 50000, 8);
|
||||
ledcWrite(0, 0);
|
||||
ledcSet(0, 0);
|
||||
vTaskDelay(1 / portTICK_PERIOD_MS);
|
||||
pinMode(pin[0], OUTPUT);
|
||||
pinMode(FLASHER_EXT_RESET, INPUT_PULLDOWN);
|
||||
ledcAttachPin(pin[0], 0);
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
for (uint8_t c = 0; c < 0xFF; c++) {
|
||||
ledcWrite(0, c);
|
||||
ledcSet(0, c);
|
||||
if (c > 250) {
|
||||
vTaskDelay(2 / portTICK_PERIOD_MS);
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <system.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "commstructs.h"
|
||||
#include "contentmanager.h"
|
||||
@@ -13,6 +15,9 @@
|
||||
#include "storage.h"
|
||||
#include "web.h"
|
||||
#include "zbs_interface.h"
|
||||
#include "wifimanager.h"
|
||||
|
||||
#define LOG(format, ...) printf(format, ##__VA_ARGS__)
|
||||
|
||||
QueueHandle_t rxCmdQueue;
|
||||
SemaphoreHandle_t txActive;
|
||||
@@ -25,7 +30,9 @@ SemaphoreHandle_t txActive;
|
||||
volatile uint8_t cmdReplyValue = CMD_REPLY_WAIT;
|
||||
|
||||
#define AP_SERIAL_PORT Serial1
|
||||
#ifndef FLASHER_DEBUG_SHARED
|
||||
volatile bool rxSerialStopTask2 = false;
|
||||
#endif
|
||||
|
||||
uint8_t channelList[6];
|
||||
struct espSetChannelPower curChannel = {0, 11, 10};
|
||||
@@ -42,6 +49,8 @@ struct espSetChannelPower curChannel = {0, 11, 10};
|
||||
volatile uint32_t lastAPActivity = 0;
|
||||
struct APInfoS apInfo;
|
||||
|
||||
volatile ApSerialState gSerialTaskState;
|
||||
|
||||
struct rxCmd {
|
||||
uint8_t* data;
|
||||
uint8_t len;
|
||||
@@ -150,12 +159,28 @@ void setAPstate(bool isOnline, uint8_t state) {
|
||||
CRGB::Red,
|
||||
CRGB::YellowGreen};
|
||||
rgbIdleColor = colorMap[state];
|
||||
#ifdef BLE_ONLY
|
||||
rgbIdleColor = CRGB::Green;
|
||||
#endif
|
||||
#ifdef BLE_ONLY
|
||||
rgbIdleColor = CRGB::Green;
|
||||
#endif
|
||||
rgbIdlePeriod = (isOnline ? 767 : 255);
|
||||
if (isOnline) rgbIdle();
|
||||
#endif
|
||||
#ifdef FLASHER_DEBUG_SHARED
|
||||
// Flasher shares port with AP comms
|
||||
if (state == AP_STATE_FLASHING) {
|
||||
LOG("Shared COM port, gSerialTaskState %d\n", gSerialTaskState);
|
||||
gSerialTaskState = SERIAL_STATE_STOP;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
vTaskDelay(1 / portTICK_RATE_MS);
|
||||
if (gSerialTaskState == SERIAL_STATE_STOPPED) {
|
||||
gSerialTaskState = SERIAL_STATE_NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG("gSerialTaskState %d\n", gSerialTaskState);
|
||||
}
|
||||
#endif
|
||||
wsSendSysteminfo();
|
||||
}
|
||||
|
||||
// Reset the tag
|
||||
@@ -384,46 +409,49 @@ void rxCmdProcessor(void* parameter) {
|
||||
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);
|
||||
if (apInfo.isOnline) {
|
||||
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);
|
||||
// shortBlink(CRGB::Blue);
|
||||
#endif
|
||||
quickBlink(3);
|
||||
break;
|
||||
case RX_CMD_ADR:
|
||||
processDataReq((struct espAvailDataReq*)rxcmd->data, true);
|
||||
quickBlink(3);
|
||||
break;
|
||||
case RX_CMD_ADR:
|
||||
processDataReq((struct espAvailDataReq*)rxcmd->data, true);
|
||||
#ifdef HAS_RGB_LED
|
||||
// shortBlink(CRGB::Aqua);
|
||||
// shortBlink(CRGB::Aqua);
|
||||
#endif
|
||||
quickBlink(1);
|
||||
break;
|
||||
case RX_CMD_XFC:
|
||||
processXferComplete((struct espXferComplete*)rxcmd->data, true);
|
||||
quickBlink(1);
|
||||
break;
|
||||
case RX_CMD_XFC:
|
||||
processXferComplete((struct espXferComplete*)rxcmd->data, true);
|
||||
#ifdef HAS_RGB_LED
|
||||
// shortBlink(CRGB::Purple);
|
||||
// shortBlink(CRGB::Purple);
|
||||
#endif
|
||||
break;
|
||||
case RX_CMD_XTO:
|
||||
processXferTimeout((struct espXferComplete*)rxcmd->data, true);
|
||||
break;
|
||||
case RX_CMD_RSET:
|
||||
Serial.println("AP did reset, resending pending\r\n");
|
||||
refreshAllPending();
|
||||
sendChannelPower(&curChannel);
|
||||
break;
|
||||
case RX_CMD_TRD:
|
||||
// received tag return data
|
||||
processTagReturnData((struct espTagReturnData*)rxcmd->data, rxcmd->len, true);
|
||||
break;
|
||||
break;
|
||||
case RX_CMD_XTO:
|
||||
processXferTimeout((struct espXferComplete*)rxcmd->data, true);
|
||||
break;
|
||||
case RX_CMD_RSET:
|
||||
Serial.println("AP did reset, resending pending\r\n");
|
||||
refreshAllPending();
|
||||
sendChannelPower(&curChannel);
|
||||
break;
|
||||
case RX_CMD_TRD:
|
||||
// received tag return data
|
||||
processTagReturnData((struct espTagReturnData*)rxcmd->data, rxcmd->len, true);
|
||||
break;
|
||||
}
|
||||
if (rxcmd->data) free(rxcmd->data);
|
||||
if (rxcmd) free(rxcmd);
|
||||
}
|
||||
if (rxcmd->data) free(rxcmd->data);
|
||||
if (rxcmd) free(rxcmd);
|
||||
}
|
||||
vTaskDelay(1 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
void rxSerialTask(void* parameter) {
|
||||
@@ -435,7 +463,9 @@ void rxSerialTask(void* parameter) {
|
||||
static char lastchar = 0;
|
||||
static uint8_t charindex = 0;
|
||||
|
||||
while (1) {
|
||||
gSerialTaskState = SERIAL_STATE_RUNNING;
|
||||
LOG("rxSerialTask starting\n");
|
||||
while (gSerialTaskState == SERIAL_STATE_RUNNING) {
|
||||
while (AP_SERIAL_PORT.available()) {
|
||||
lastchar = AP_SERIAL_PORT.read();
|
||||
switch (RXState) {
|
||||
@@ -506,8 +536,8 @@ void rxSerialTask(void* parameter) {
|
||||
packetp = (uint8_t*)calloc(sizeof(struct espBlockRequest) + 8, 1);
|
||||
memset(cmdbuffer, 0x00, 4);
|
||||
lastAPActivity = millis();
|
||||
if (apInfo.isOnline == false)
|
||||
setAPstate(true, AP_STATE_ONLINE);
|
||||
// don't set APstate heree, as it interferes with the flashing process
|
||||
// if (apInfo.isOnline == false && config.runStatus == RUNSTATUS_RUN) setAPstate(true, AP_STATE_ONLINE);
|
||||
}
|
||||
if (strncmp(cmdbuffer, "ADR>", 4) == 0) {
|
||||
RXState = ZBS_RX_WAIT_DATA_REQ;
|
||||
@@ -516,8 +546,8 @@ void rxSerialTask(void* parameter) {
|
||||
packetp = (uint8_t*)calloc(sizeof(struct espAvailDataReq) + 8, 1);
|
||||
memset(cmdbuffer, 0x00, 4);
|
||||
lastAPActivity = millis();
|
||||
if (apInfo.isOnline == false)
|
||||
setAPstate(true, AP_STATE_ONLINE);
|
||||
// don't set APstate heree, as it interferes with the flashing process
|
||||
// if (apInfo.isOnline == false && config.runStatus == RUNSTATUS_RUN) setAPstate(true, AP_STATE_ONLINE);
|
||||
}
|
||||
if (strncmp(cmdbuffer, "XFC>", 4) == 0) {
|
||||
RXState = ZBS_RX_WAIT_XFERCOMPLETE;
|
||||
@@ -540,8 +570,6 @@ void rxSerialTask(void* parameter) {
|
||||
packetp = (uint8_t*)calloc(sizeof(struct espTagReturnData) + 8, 1);
|
||||
memset(cmdbuffer, 0x00, 4);
|
||||
lastAPActivity = millis();
|
||||
if (apInfo.isOnline == false)
|
||||
setAPstate(true, AP_STATE_ONLINE);
|
||||
}
|
||||
break;
|
||||
case ZBS_RX_BLOCK_REQUEST:
|
||||
@@ -666,10 +694,26 @@ void rxSerialTask(void* parameter) {
|
||||
}
|
||||
vTaskDelay(1 / portTICK_PERIOD_MS);
|
||||
} // end of while(1)
|
||||
|
||||
AP_SERIAL_PORT.end();
|
||||
gSerialTaskState = SERIAL_STATE_STOPPED;
|
||||
LOG("rxSerialTask stopped\n");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
#if defined(FLASHER_DEBUG_RXD) && !defined(FLASHER_DEBUG_SHARED)
|
||||
uint32_t millisDiff(uint32_t m) {
|
||||
uint32_t ms = millis();
|
||||
if (ms >= m)
|
||||
return ms - m;
|
||||
else
|
||||
return UINT32_MAX - m + ms + 1;
|
||||
}
|
||||
|
||||
#ifdef FLASHER_DEBUG_RXD
|
||||
void rxSerialTask2(void* parameter) {
|
||||
char rxStr[100] = {0};
|
||||
int rxStrCount = 0;
|
||||
uint32_t modemResetHoldoff = millis();
|
||||
char lastchar = 0;
|
||||
time_t startTime = millis();
|
||||
int charCount = 0;
|
||||
@@ -681,6 +725,33 @@ void rxSerialTask2(void* parameter) {
|
||||
|
||||
// debug info
|
||||
Serial.write(lastchar);
|
||||
|
||||
rxStr[rxStrCount] = lastchar;
|
||||
if (lastchar == '\n' || lastchar == '\r') {
|
||||
if (strncmp(rxStr, "receive buffer full, drop the current frame", 43) == 0 && millisDiff(modemResetHoldoff) > 20000) {
|
||||
modemResetHoldoff = millis();
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
config.runStatus = RUNSTATUS_STOP;
|
||||
Serial.println("IEEE802.15.4 modem stuck case detected, resetting...");
|
||||
APTagReset();
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
Serial.println("bringing AP online again");
|
||||
if (bringAPOnline()) {
|
||||
config.runStatus = RUNSTATUS_RUN;
|
||||
Serial.println("Finished!");
|
||||
} else {
|
||||
Serial.println("Failed!");
|
||||
}
|
||||
logLine("IEEE802.15.4 modem reset " + (config.runStatus == RUNSTATUS_RUN) ? ("ok") : ("failed"));
|
||||
}
|
||||
rxStrCount = 0;
|
||||
memset(rxStr, 0, sizeof(rxStr));
|
||||
} else if (rxStrCount < sizeof(rxStr) - 2) {
|
||||
rxStrCount++;
|
||||
} else {
|
||||
rxStrCount = 0;
|
||||
memset(rxStr, 0, sizeof(rxStr));
|
||||
}
|
||||
}
|
||||
vTaskDelay(1 / portTICK_PERIOD_MS);
|
||||
|
||||
@@ -688,7 +759,7 @@ void rxSerialTask2(void* parameter) {
|
||||
if (currentTime - startTime >= 1000) {
|
||||
if (charCount > 6000) {
|
||||
rxSerialStopTask2 = true;
|
||||
Serial.println("Serial monitor stopped because of flooding (" + String(charCount) + " characters per second");
|
||||
Serial.println("Serial monitor stopped because of flooding (" + String(charCount) + " characters per second)");
|
||||
}
|
||||
startTime = currentTime;
|
||||
charCount = 0;
|
||||
@@ -733,7 +804,7 @@ void checkWaitPowerCycle() {
|
||||
#endif
|
||||
}
|
||||
void segmentedShowIp() {
|
||||
IPAddress IP = WiFi.localIP();
|
||||
IPAddress IP = wm.localIP();
|
||||
char temp[12];
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
sendAPSegmentedData(apInfo.mac, (String) "IP Addr", 0x0200, true, true);
|
||||
@@ -746,12 +817,36 @@ void segmentedShowIp() {
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
bool bringAPOnline() {
|
||||
#ifdef BLE_ONLY
|
||||
bool bringAPOnline(uint8_t newState) {
|
||||
#ifdef BLE_ONLY
|
||||
apInfo.state = AP_STATE_NORADIO;
|
||||
#endif
|
||||
#endif
|
||||
if (apInfo.state == AP_STATE_NORADIO) return true;
|
||||
if (apInfo.state == AP_STATE_FLASHING) return false;
|
||||
|
||||
if (gSerialTaskState != SERIAL_STATE_INITIALIZED) {
|
||||
#ifdef HAS_ELECROW_ADV_2_8
|
||||
// Set GPIO45 low to connect the wireless interface to the multiplexed pins
|
||||
pinMode(45, OUTPUT);
|
||||
digitalWrite(45, LOW);
|
||||
#endif
|
||||
|
||||
#if (AP_PROCESS_PORT == FLASHER_AP_PORT)
|
||||
AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD);
|
||||
#elif defined(HAS_EXT_FLASHER)
|
||||
#if (AP_PROCESS_PORT == FLASHER_EXT_PORT)
|
||||
AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_EXT_RXD, FLASHER_EXT_TXD);
|
||||
#elif (AP_PROCESS_PORT == FLASHER_ALTRADIO_PORT)
|
||||
AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD);
|
||||
#endif
|
||||
#endif
|
||||
gSerialTaskState = SERIAL_STATE_INITIALIZED;
|
||||
}
|
||||
if (gSerialTaskState != SERIAL_STATE_RUNNING) {
|
||||
gSerialTaskState = SERIAL_STATE_STARTING;
|
||||
xTaskCreate(rxSerialTask, "rxSerialTask", 1750, NULL, 11, NULL);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
}
|
||||
setAPstate(false, AP_STATE_OFFLINE);
|
||||
// try without rebooting
|
||||
AP_SERIAL_PORT.updateBaudRate(115200);
|
||||
@@ -788,18 +883,18 @@ bool bringAPOnline() {
|
||||
}
|
||||
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
setAPstate(true, AP_STATE_ONLINE);
|
||||
setAPstate(newState == AP_STATE_ONLINE ? true : false, newState);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool checkRadio() {
|
||||
#ifdef BLE_ONLY
|
||||
#ifdef BLE_ONLY
|
||||
return false;
|
||||
#endif
|
||||
#ifndef C6_OTA_FLASHING
|
||||
#endif
|
||||
#ifndef C6_OTA_FLASHING
|
||||
return true;
|
||||
#endif
|
||||
#endif
|
||||
// make a short between FLASHER_AP_TXD and FLASHER_AP_RXD to indicate that no radio is present
|
||||
// e.g. for flasher only, or just to use the S3 to generate images for smaller AP's
|
||||
pinMode(FLASHER_AP_TXD, OUTPUT);
|
||||
@@ -823,25 +918,11 @@ void APTask(void* parameter) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if (AP_PROCESS_PORT == FLASHER_AP_PORT)
|
||||
AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD);
|
||||
#endif
|
||||
#ifdef HAS_EXT_FLASHER
|
||||
#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
|
||||
|
||||
xTaskCreate(rxCmdProcessor, "rxCmdProcessor", 6000, NULL, 15, NULL);
|
||||
xTaskCreate(rxSerialTask, "rxSerialTask", 1750, NULL, 11, NULL);
|
||||
#ifdef FLASHER_DEBUG_RXD
|
||||
xTaskCreate(rxSerialTask2, "rxSerialTask2", 1750, NULL, 2, NULL);
|
||||
#endif
|
||||
|
||||
#if defined(FLASHER_DEBUG_RXD) && !defined(FLASHER_DEBUG_SHARED)
|
||||
xTaskCreate(rxSerialTask2, "rxSerialTask2", 1850, NULL, 2, NULL);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
#endif
|
||||
bringAPOnline();
|
||||
|
||||
#ifndef C6_OTA_FLASHING
|
||||
@@ -876,7 +957,7 @@ void APTask(void* parameter) {
|
||||
if (FLASHER_AP_MOSI != -1) {
|
||||
fsversion = getAPUpdateVersion(apInfo.type);
|
||||
if ((fsversion) && (apInfo.version != fsversion)) {
|
||||
Serial.printf("Firmware version on LittleFS: %04X\r\n", fsversion);
|
||||
Serial.printf("Firmware version on FS: %04X\r\n", fsversion);
|
||||
|
||||
Serial.printf("We're going to try to update the AP's FW in\r\n");
|
||||
flashCountDown(30);
|
||||
|
||||
@@ -2,22 +2,63 @@
|
||||
|
||||
#ifdef HAS_SDCARD
|
||||
#include "FS.h"
|
||||
#ifdef SD_CARD_SDMMC
|
||||
#include "SD_MMC.h"
|
||||
#define SDCARD SD_MMC
|
||||
#else
|
||||
#include "SD.h"
|
||||
#include "SPI.h"
|
||||
#define SDCARD SD
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef SD_CARD_ONLY
|
||||
#include "LittleFS.h"
|
||||
#endif
|
||||
|
||||
DynStorage::DynStorage() : isInited(0) {}
|
||||
|
||||
SemaphoreHandle_t fsMutex;
|
||||
SemaphoreHandle_t fsMutex = NULL;
|
||||
|
||||
#ifndef SD_CARD_ONLY
|
||||
static void initLittleFS() {
|
||||
LittleFS.begin();
|
||||
contentFS = &LittleFS;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAS_SDCARD
|
||||
static bool sd_init_done = false;
|
||||
#ifdef SD_CARD_SDMMC
|
||||
static void initSDCard() {
|
||||
if(!SD_MMC.begin("/sdcard", true, true, BOARD_MAX_SDMMC_FREQ, 5)){
|
||||
Serial.println("Card Mount Failed");
|
||||
return;
|
||||
}
|
||||
uint8_t cardType = SD_MMC.cardType();
|
||||
|
||||
if(cardType == CARD_NONE){
|
||||
Serial.println("No SD_MMC card attached");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("SD_MMC Card Type: ");
|
||||
if(cardType == CARD_MMC){
|
||||
Serial.println("MMC");
|
||||
} else if(cardType == CARD_SD){
|
||||
Serial.println("SDSC");
|
||||
} else if(cardType == CARD_SDHC){
|
||||
Serial.println("SDHC");
|
||||
} else {
|
||||
Serial.println("UNKNOWN");
|
||||
}
|
||||
|
||||
uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
|
||||
Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);
|
||||
|
||||
contentFS = &SD_MMC;
|
||||
}
|
||||
#else
|
||||
static SPIClass* spi;
|
||||
|
||||
static void initSDCard() {
|
||||
@@ -45,15 +86,19 @@ static void initSDCard() {
|
||||
contentFS = &SD;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uint64_t DynStorage::freeSpace(){
|
||||
this->begin();
|
||||
#ifdef HAS_SDCARD
|
||||
return SD.totalBytes() - SD.usedBytes();
|
||||
return SDCARD.totalBytes() - SDCARD.usedBytes();
|
||||
#endif
|
||||
#ifndef SD_CARD_ONLY
|
||||
return LittleFS.totalBytes() - LittleFS.usedBytes();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef SD_CARD_ONLY
|
||||
void copyFile(File in, File out) {
|
||||
Serial.print("Copying ");
|
||||
Serial.print(in.path());
|
||||
@@ -127,14 +172,25 @@ void copyIfNeeded(const char* path) {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void DynStorage::begin() {
|
||||
fsMutex = xSemaphoreCreateMutex();
|
||||
if(fsMutex == NULL) {
|
||||
fsMutex = xSemaphoreCreateMutex();
|
||||
}
|
||||
|
||||
#ifndef SD_CARD_ONLY
|
||||
initLittleFS();
|
||||
#endif
|
||||
|
||||
#ifdef HAS_SDCARD
|
||||
initSDCard();
|
||||
|
||||
if(!sd_init_done) {
|
||||
xSemaphoreTake(fsMutex, portMAX_DELAY);
|
||||
initSDCard();
|
||||
xSemaphoreGive(fsMutex);
|
||||
sd_init_done = true;
|
||||
}
|
||||
#ifndef SD_CARD_ONLY
|
||||
copyIfNeeded("/index.html");
|
||||
copyIfNeeded("/fonts");
|
||||
copyIfNeeded("/www");
|
||||
@@ -143,6 +199,7 @@ void DynStorage::begin() {
|
||||
copyIfNeeded("/tag_md5_db.json");
|
||||
copyIfNeeded("/update_actions.json");
|
||||
copyIfNeeded("/content_template.json");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (!contentFS->exists("/current")) {
|
||||
@@ -155,7 +212,17 @@ void DynStorage::begin() {
|
||||
|
||||
void DynStorage::end() {
|
||||
#ifdef HAS_SDCARD
|
||||
#ifndef SD_CARD_ONLY
|
||||
initLittleFS();
|
||||
#endif
|
||||
#ifdef SD_CARD_SDMMC
|
||||
#ifndef SD_CARD_ONLY
|
||||
contentFS = &LittleFS;
|
||||
#endif
|
||||
SD_MMC.end();
|
||||
sd_init_done = false;
|
||||
#else
|
||||
#ifndef SD_CARD_ONLY
|
||||
if (SD_CARD_CLK == FLASHER_AP_CLK ||
|
||||
SD_CARD_MISO == FLASHER_AP_MISO ||
|
||||
SD_CARD_MOSI == FLASHER_AP_MOSI) {
|
||||
@@ -171,7 +238,8 @@ void DynStorage::end() {
|
||||
|
||||
contentFS = &LittleFS;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -264,7 +264,19 @@ void nrfswd::write_register(uint32_t address, uint32_t value) {
|
||||
bool state3 = DP_Read(DP_RDBUFF, temp);
|
||||
// if (showDebug) Serial.printf("%i%i%i Write Register: 0x%08x : 0x%08x\r\n", state1, state2, state3, address, value);
|
||||
}
|
||||
|
||||
uint8_t nrfswd::nrf_erase_all() {
|
||||
nrf_port_selection(1);
|
||||
nrf_write_port(1, AP_NRF_ERASEALL, 1);
|
||||
long timeout = millis();
|
||||
while (nrf_read_port(1, AP_NRF_ERASEALLSTATUS)) {
|
||||
if (millis() - timeout > 1000) return 1;
|
||||
}
|
||||
nrf_write_port(1, AP_NRF_ERASEALL, 0);
|
||||
nrf_port_selection(0);
|
||||
nrf_soft_reset();
|
||||
init();
|
||||
return 0;
|
||||
}
|
||||
uint8_t nrfswd::erase_all_flash() {
|
||||
write_register(0x4001e504, 2);
|
||||
long timeout = millis();
|
||||
|
||||
@@ -14,7 +14,7 @@ void timeSyncCallback(struct timeval* tv) {
|
||||
}
|
||||
|
||||
void initTime(void* parameter) {
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
if (!(WiFi.status() == WL_CONNECTED || wm.wifiStatus == ETHERNET)) {
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
}
|
||||
sntp_set_time_sync_notification_cb(timeSyncCallback);
|
||||
|
||||
@@ -67,25 +67,24 @@ bool hex2mac(const String& hexString, uint8_t* mac) {
|
||||
}
|
||||
|
||||
String tagDBtoJson(const uint8_t mac[8], uint8_t startPos) {
|
||||
DynamicJsonDocument doc(5000);
|
||||
JsonArray tags = doc.createNestedArray("tags");
|
||||
JsonDocument doc;
|
||||
JsonArray tags = doc["tags"].to<JsonArray>();
|
||||
|
||||
for (uint32_t c = startPos; c < tagDB.size(); ++c) {
|
||||
const tagRecord* taginfo = tagDB.at(c);
|
||||
|
||||
const bool select = !mac || memcmp(taginfo->mac, mac, 8) == 0;
|
||||
if (select && taginfo->version == 0) {
|
||||
JsonObject tag = tags.createNestedObject();
|
||||
JsonObject tag = tags.add<JsonObject>();
|
||||
fillNode(tag, taginfo);
|
||||
if (measureJson(doc) > 5000) {
|
||||
doc["continu"] = c + 1;
|
||||
break;
|
||||
}
|
||||
if (mac) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.capacity() - doc.memoryUsage() < doc.memoryUsage() / (c + 1) + 500) {
|
||||
doc["continu"] = c + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return doc.as<String>();
|
||||
@@ -126,7 +125,7 @@ void fillNode(JsonObject& tag, const tagRecord* taginfo) {
|
||||
}
|
||||
|
||||
void saveDB(const String& filename) {
|
||||
DynamicJsonDocument doc(2500);
|
||||
JsonDocument doc;
|
||||
|
||||
const long t = millis();
|
||||
|
||||
@@ -138,8 +137,10 @@ void saveDB(const String& filename) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
String backupFilename = filename + ".bak";
|
||||
if (!contentFS->rename(filename.c_str(), backupFilename.c_str())) {
|
||||
xSemaphoreGive(fsMutex);
|
||||
logLine("error renaming tagDB to .bak");
|
||||
wsErr("error renaming tagDB to .bak");
|
||||
xSemaphoreTake(fsMutex, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +157,7 @@ void saveDB(const String& filename) {
|
||||
doc.clear();
|
||||
|
||||
if (taginfo->version == 0) {
|
||||
JsonObject tag = doc.createNestedObject();
|
||||
JsonObject tag = doc.add<JsonObject>();
|
||||
fillNode(tag, taginfo);
|
||||
if (c > 0) {
|
||||
file.write(',');
|
||||
@@ -186,7 +187,7 @@ bool loadDB(const String& filename) {
|
||||
bool parsing = true;
|
||||
|
||||
if (readfile.find("[")) {
|
||||
DynamicJsonDocument doc(1000);
|
||||
JsonDocument doc;
|
||||
while (parsing) {
|
||||
DeserializationError err = deserializeJson(doc, readfile);
|
||||
if (!err) {
|
||||
@@ -210,7 +211,7 @@ bool loadDB(const String& filename) {
|
||||
taginfo->nextupdate = (uint32_t)tag["nextupdate"];
|
||||
taginfo->expectedNextCheckin = (uint32_t)tag["nextcheckin"];
|
||||
if (taginfo->expectedNextCheckin < now) {
|
||||
taginfo->expectedNextCheckin = now + 1800;
|
||||
taginfo->expectedNextCheckin = now + 60;
|
||||
}
|
||||
taginfo->pendingCount = 0;
|
||||
taginfo->alias = tag["alias"].as<String>();
|
||||
@@ -279,11 +280,11 @@ uint32_t getTagCount(uint32_t& timeoutcount, uint32_t& lowbattcount) {
|
||||
if (!taginfo->isExternal) tagcount++;
|
||||
const int32_t timeout = now - taginfo->lastseen;
|
||||
if (taginfo->expectedNextCheckin < 3600) {
|
||||
// not initialised, timeout if not seen last 10 minutes
|
||||
if (timeout > 600) timeoutcount++;
|
||||
} else if (now - taginfo->expectedNextCheckin > 600) {
|
||||
// expected checkin is behind, timeout if not seen last 10 minutes
|
||||
if (timeout > 600) timeoutcount++;
|
||||
// not initialised, timeout if not seen last 5 minutes
|
||||
if (timeout > config.maxsleep * 60 + 300) timeoutcount++;
|
||||
} else if (now - static_cast<time_t>(taginfo->expectedNextCheckin) > 600) {
|
||||
// expected checkin is behind, timeout if not seen last 5 minutes
|
||||
if (timeout > config.maxsleep * 60 + 300) timeoutcount++;
|
||||
}
|
||||
if (taginfo->batteryMv < 2400 && taginfo->batteryMv != 0 && taginfo->batteryMv != 1337) lowbattcount++;
|
||||
}
|
||||
@@ -308,7 +309,7 @@ void clearPending(tagRecord* taginfo) {
|
||||
}
|
||||
|
||||
void initAPconfig() {
|
||||
DynamicJsonDocument APconfig(768);
|
||||
JsonDocument APconfig;
|
||||
File configFile = contentFS->open("/current/apconfig.json", "r");
|
||||
if (configFile) {
|
||||
DeserializationError error = deserializeJson(APconfig, configFile);
|
||||
@@ -319,29 +320,30 @@ void initAPconfig() {
|
||||
}
|
||||
configFile.close();
|
||||
}
|
||||
config.channel = APconfig.containsKey("channel") ? APconfig["channel"] : 0;
|
||||
config.subghzchannel = APconfig.containsKey("subghzchannel") ? APconfig["subghzchannel"] : 0;
|
||||
config.channel = APconfig["channel"].is<uint8_t>() ? APconfig["channel"] : 0;
|
||||
config.subghzchannel = APconfig["subghzchannel"].is<uint8_t>() ? APconfig["subghzchannel"] : 0;
|
||||
if (APconfig["alias"]) strlcpy(config.alias, APconfig["alias"], sizeof(config.alias));
|
||||
config.led = APconfig.containsKey("led") ? APconfig["led"] : 255;
|
||||
config.tft = APconfig.containsKey("tft") ? APconfig["tft"] : 255;
|
||||
config.language = APconfig.containsKey("language") ? APconfig["language"] : 0;
|
||||
config.maxsleep = APconfig.containsKey("maxsleep") ? APconfig["maxsleep"] : 10;
|
||||
config.stopsleep = APconfig.containsKey("stopsleep") ? APconfig["stopsleep"] : 1;
|
||||
config.preview = APconfig.containsKey("preview") ? APconfig["preview"] : 1;
|
||||
config.nightlyreboot = APconfig.containsKey("nightlyreboot") ? APconfig["nightlyreboot"] : 1;
|
||||
config.lock = APconfig.containsKey("lock") ? APconfig["lock"] : 0;
|
||||
config.sleepTime1 = APconfig.containsKey("sleeptime1") ? APconfig["sleeptime1"] : 0;
|
||||
config.sleepTime2 = APconfig.containsKey("sleeptime2") ? APconfig["sleeptime2"] : 0;
|
||||
config.ble = APconfig.containsKey("ble") ? APconfig["ble"] : 0;
|
||||
config.discovery = APconfig.containsKey("discovery") ? APconfig["discovery"] : 0;
|
||||
#ifdef BLE_ONLY
|
||||
config.ble = true;
|
||||
#endif
|
||||
config.led = APconfig["led"].is<uint8_t>() ? APconfig["led"] : 255;
|
||||
config.tft = APconfig["tft"].is<uint8_t>() ? APconfig["tft"] : 255;
|
||||
config.language = APconfig["language"].is<uint8_t>() ? APconfig["language"] : 0;
|
||||
config.maxsleep = APconfig["maxsleep"].is<uint8_t>() ? APconfig["maxsleep"] : 10;
|
||||
config.stopsleep = APconfig["stopsleep"].is<uint8_t>() ? APconfig["stopsleep"] : 1;
|
||||
config.preview = APconfig["preview"].is<uint8_t>() ? APconfig["preview"] : 1;
|
||||
config.nightlyreboot = APconfig["nightlyreboot"].is<uint8_t>() ? APconfig["nightlyreboot"] : 1;
|
||||
config.lock = APconfig["lock"].is<uint8_t>() ? APconfig["lock"] : 0;
|
||||
config.sleepTime1 = APconfig["sleeptime1"].is<uint8_t>() ? APconfig["sleeptime1"] : 0;
|
||||
config.sleepTime2 = APconfig["sleeptime2"].is<uint8_t>() ? APconfig["sleeptime2"] : 0;
|
||||
config.ble = APconfig["ble"].is<uint8_t>() ? APconfig["ble"] : 0;
|
||||
config.discovery = APconfig["discovery"].is<uint8_t>() ? APconfig["discovery"] : 0;
|
||||
config.showtimestamp = APconfig["showtimestamp"].is<uint8_t>() ? APconfig["showtimestamp"] : 0;
|
||||
#ifdef BLE_ONLY
|
||||
config.ble = true;
|
||||
#endif
|
||||
// default wifi power 8.5 dbM
|
||||
// see https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiGeneric.h#L111
|
||||
config.wifiPower = APconfig.containsKey("wifipower") ? APconfig["wifipower"] : 34;
|
||||
config.repo = APconfig.containsKey("repo") ? APconfig["repo"].as<String>() : String("OpenEPaperLink/OpenEPaperLink");
|
||||
config.env = APconfig.containsKey("env") ? APconfig["env"].as<String>() : String(STR(BUILD_ENV_NAME));
|
||||
config.wifiPower = APconfig["wifipower"].is<uint8_t>() ? APconfig["wifipower"] : 34;
|
||||
config.repo = APconfig["repo"].is<String>() ? APconfig["repo"].as<String>() : String("OpenEPaperLink/OpenEPaperLink");
|
||||
config.env = APconfig["env"].is<String>() ? APconfig["env"].as<String>() : String(STR(BUILD_ENV_NAME));
|
||||
if (APconfig["timezone"]) {
|
||||
strlcpy(config.timeZone, APconfig["timezone"], sizeof(config.timeZone));
|
||||
} else {
|
||||
@@ -352,7 +354,7 @@ void initAPconfig() {
|
||||
void saveAPconfig() {
|
||||
xSemaphoreTake(fsMutex, portMAX_DELAY);
|
||||
fs::File configFile = contentFS->open("/current/apconfig.json", "w");
|
||||
DynamicJsonDocument APconfig(500);
|
||||
JsonDocument APconfig;
|
||||
APconfig["channel"] = config.channel;
|
||||
APconfig["subghzchannel"] = config.subghzchannel;
|
||||
APconfig["alias"] = config.alias;
|
||||
@@ -372,6 +374,7 @@ void saveAPconfig() {
|
||||
APconfig["repo"] = config.repo;
|
||||
APconfig["env"] = config.env;
|
||||
APconfig["discovery"] = config.discovery;
|
||||
APconfig["showtimestamp"] = config.showtimestamp;
|
||||
serializeJsonPretty(APconfig, configFile);
|
||||
configFile.close();
|
||||
xSemaphoreGive(fsMutex);
|
||||
@@ -388,16 +391,17 @@ HwType getHwType(const uint8_t id) {
|
||||
File jsonFile = contentFS->open(filename, "r");
|
||||
|
||||
if (jsonFile) {
|
||||
StaticJsonDocument<150> filter;
|
||||
JsonDocument filter;
|
||||
filter["width"] = true;
|
||||
filter["height"] = true;
|
||||
filter["rotatebuffer"] = true;
|
||||
filter["bpp"] = true;
|
||||
filter["shortlut"] = true;
|
||||
filter["zlib_compression"] = true;
|
||||
filter["g5_compression"] = true;
|
||||
filter["highlight_color"] = true;
|
||||
filter["colortable"] = true;
|
||||
StaticJsonDocument<1000> doc;
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, jsonFile, DeserializationOption::Filter(filter));
|
||||
jsonFile.close();
|
||||
if (error) {
|
||||
@@ -411,12 +415,17 @@ HwType getHwType(const uint8_t id) {
|
||||
hwType.rotatebuffer = doc["rotatebuffer"];
|
||||
hwType.bpp = doc["bpp"];
|
||||
hwType.shortlut = doc["shortlut"];
|
||||
if (doc.containsKey("zlib_compression")) {
|
||||
if (doc["zlib_compression"].is<const char*>()) {
|
||||
hwType.zlib = strtol(doc["zlib_compression"], nullptr, 16);
|
||||
} else {
|
||||
hwType.zlib = 0;
|
||||
}
|
||||
hwType.highlightColor = doc.containsKey("highlight_color") ? doc["highlight_color"].as<uint16_t>() : 2;
|
||||
if (doc["g5_compression"].is<const char*>()) {
|
||||
hwType.g5 = strtol(doc["g5_compression"], nullptr, 16);
|
||||
} else {
|
||||
hwType.g5 = 0;
|
||||
}
|
||||
hwType.highlightColor = doc["highlight_color"].is<uint16_t>() ? doc["highlight_color"].as<uint16_t>() : 2;
|
||||
JsonObject colorTable = doc["colortable"];
|
||||
for (auto kv : colorTable) {
|
||||
JsonArray color = kv.value();
|
||||
|
||||
@@ -10,6 +10,9 @@ std::unordered_map<size_t, TagData::Parser> TagData::parsers = {};
|
||||
void TagData::loadParsers(const String& filename) {
|
||||
const long start = millis();
|
||||
|
||||
if (!contentFS->exists(filename)) {
|
||||
return;
|
||||
}
|
||||
fs::File file = contentFS->open(filename, "r");
|
||||
if (!file) {
|
||||
return;
|
||||
@@ -17,7 +20,7 @@ void TagData::loadParsers(const String& filename) {
|
||||
Serial.println("Reading parsers from file");
|
||||
|
||||
if (file.find("[")) {
|
||||
DynamicJsonDocument doc(1000);
|
||||
JsonDocument doc;
|
||||
bool parsing = true;
|
||||
while (parsing) {
|
||||
DeserializationError err = deserializeJson(doc, file);
|
||||
@@ -145,4 +148,4 @@ void TagData::parse(const uint8_t src[8], const size_t id, const uint8_t* data,
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "serialap.h"
|
||||
#include "tag_db.h"
|
||||
#include "web.h"
|
||||
#include "wifimanager.h"
|
||||
|
||||
#define UDPIP IPAddress(239, 10, 0, 1)
|
||||
#define UDPPORT 16033
|
||||
@@ -34,7 +35,7 @@ void UDPcomm::init() {
|
||||
if (config.discovery == 0) {
|
||||
if (udp.listenMulticast(UDPIP, UDPPORT)) {
|
||||
udp.onPacket([this](AsyncUDPPacket packet) {
|
||||
if (packet.remoteIP() != WiFi.localIP()) {
|
||||
if (packet.remoteIP() != wm.localIP()) {
|
||||
this->processPacket(packet);
|
||||
}
|
||||
});
|
||||
@@ -42,7 +43,7 @@ void UDPcomm::init() {
|
||||
} else {
|
||||
if (udp.listen(UDPPORT)) {
|
||||
udp.onPacket([this](AsyncUDPPacket packet) {
|
||||
if (packet.isBroadcast() && packet.remoteIP() != WiFi.localIP()) {
|
||||
if (packet.isBroadcast() && packet.remoteIP() != wm.localIP()) {
|
||||
this->processPacket(packet);
|
||||
}
|
||||
});
|
||||
@@ -88,7 +89,7 @@ void UDPcomm::processPacket(AsyncUDPPacket packet) {
|
||||
}
|
||||
case PKT_APLIST_REQ: {
|
||||
APlist APitem;
|
||||
APitem.src = WiFi.localIP();
|
||||
APitem.src = wm.localIP();
|
||||
strcpy(APitem.alias, config.alias);
|
||||
APitem.channelId = curChannel.channel;
|
||||
APitem.tagCount = getTagCount();
|
||||
@@ -154,7 +155,7 @@ void autoselect(void* pvParameters) {
|
||||
|
||||
void UDPcomm::getAPList() {
|
||||
APlist APitem;
|
||||
APitem.src = WiFi.localIP();
|
||||
APitem.src = wm.localIP();
|
||||
strcpy(APitem.alias, config.alias);
|
||||
APitem.channelId = curChannel.channel;
|
||||
APitem.tagCount = getTagCount();
|
||||
|
||||
@@ -306,6 +306,7 @@ typedef enum {
|
||||
|
||||
CMD_ERASE_FLASH = 26,
|
||||
CMD_ERASE_INFOPAGE = 27,
|
||||
CMD_ERASE_ALL = 28,
|
||||
CMD_SAVE_MAC_FROM_FW = 40,
|
||||
CMD_PASS_THROUGH = 50,
|
||||
|
||||
@@ -420,6 +421,16 @@ void processFlasherCommand(struct flasherCommand* cmd, uint8_t transportType) {
|
||||
}
|
||||
sendFlasherAnswer(CMD_ERASE_INFOPAGE, NULL, 0, transportType);
|
||||
break;
|
||||
case CMD_ERASE_ALL:
|
||||
if (selectedController == CONTROLLER_NRF82511) {
|
||||
if (nrfflasherp == nullptr) return;
|
||||
nrfflasherp->nrf_erase_all();
|
||||
} else if (selectedController == CONTROLLER_CC) {
|
||||
if (ccflasherp == nullptr) return;
|
||||
ccflasherp->erase_chip();
|
||||
}
|
||||
sendFlasherAnswer(CMD_ERASE_ALL, NULL, 0, transportType);
|
||||
break;
|
||||
case CMD_SELECT_PORT:
|
||||
wsSerial("> select port");
|
||||
selectedFlasherPort = cmd->data[0];
|
||||
@@ -453,7 +464,7 @@ void processFlasherCommand(struct flasherCommand* cmd, uint8_t transportType) {
|
||||
break;
|
||||
}
|
||||
nrfflasherp->init();
|
||||
temp_buff[0] = (nrfflasherp->isConnected && !nrfflasherp->isLocked);
|
||||
temp_buff[0] = nrfflasherp->isConnected ? (nrfflasherp->isLocked ? 2 : 1) : 0;
|
||||
sendFlasherAnswer(CMD_SELECT_NRF82511, temp_buff, 1, transportType);
|
||||
currentFlasherOffset = 0;
|
||||
selectedController = CONTROLLER_NRF82511;
|
||||
|
||||
@@ -26,13 +26,12 @@
|
||||
#include "tag_db.h"
|
||||
#include "udp.h"
|
||||
#include "wifimanager.h"
|
||||
#include <sys/time.h>
|
||||
|
||||
#ifdef HAS_EXT_FLASHER
|
||||
#include "webflasher.h"
|
||||
#endif
|
||||
|
||||
extern uint8_t data_to_send[];
|
||||
|
||||
AsyncWebServer server(80);
|
||||
AsyncWebSocket ws("/ws");
|
||||
WifiManager wm;
|
||||
@@ -41,7 +40,7 @@ SemaphoreHandle_t wsMutex;
|
||||
uint32_t lastssidscan = 0;
|
||||
|
||||
void wsLog(const String &text) {
|
||||
StaticJsonDocument<250> doc;
|
||||
JsonDocument doc;
|
||||
doc["logMsg"] = text;
|
||||
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
|
||||
ws.textAll(doc.as<String>());
|
||||
@@ -49,7 +48,7 @@ void wsLog(const String &text) {
|
||||
}
|
||||
|
||||
void wsErr(const String &text) {
|
||||
StaticJsonDocument<250> doc;
|
||||
JsonDocument doc;
|
||||
doc["errMsg"] = text;
|
||||
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
|
||||
ws.textAll(doc.as<String>());
|
||||
@@ -68,8 +67,8 @@ size_t dbSize() {
|
||||
}
|
||||
|
||||
void wsSendSysteminfo() {
|
||||
DynamicJsonDocument doc(300);
|
||||
JsonObject sys = doc.createNestedObject("sys");
|
||||
JsonDocument doc;
|
||||
JsonObject sys = doc["sys"].to<JsonObject>();
|
||||
time_t now;
|
||||
time(&now);
|
||||
static int freeSpaceLastRun = 0;
|
||||
@@ -109,7 +108,7 @@ void wsSendSysteminfo() {
|
||||
strftime(timeBuffer, sizeof(timeBuffer), languageDateFormat[0].c_str(), &timeinfo);
|
||||
setVarDB("ap_date", timeBuffer);
|
||||
}
|
||||
setVarDB("ap_ip", WiFi.localIP().toString());
|
||||
setVarDB("ap_ip", wm.localIP().toString());
|
||||
|
||||
#ifdef HAS_SUBGHZ
|
||||
String ApChanString = String(apInfo.channel);
|
||||
@@ -208,8 +207,8 @@ void wsSendTaginfo(const uint8_t *mac, uint8_t syncMode) {
|
||||
}
|
||||
|
||||
void wsSendAPitem(struct APlist *apitem) {
|
||||
DynamicJsonDocument doc(250);
|
||||
JsonObject ap = doc.createNestedObject("apitem");
|
||||
JsonDocument doc;
|
||||
JsonObject ap = doc["apitem"].to<JsonObject>();
|
||||
|
||||
char version_str[6];
|
||||
sprintf(version_str, "%04X", apitem->version);
|
||||
@@ -230,7 +229,7 @@ void wsSerial(const String &text) {
|
||||
}
|
||||
|
||||
void wsSerial(const String &text, const String &color) {
|
||||
StaticJsonDocument<250> doc;
|
||||
JsonDocument doc;
|
||||
doc["console"] = text;
|
||||
if (!color.isEmpty()) doc["color"] = color;
|
||||
Serial.println(text);
|
||||
@@ -324,7 +323,7 @@ void init_web() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
request->send_P(200, "application/octet-stream", queueItem->data, queueItem->len);
|
||||
request->send(200, "application/octet-stream", queueItem->data, queueItem->len);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -338,7 +337,7 @@ void init_web() {
|
||||
taginfo->data = getDataForFile(file);
|
||||
file.close();
|
||||
}
|
||||
request->send_P(200, "application/octet-stream", taginfo->data, taginfo->len);
|
||||
request->send(200, "application/octet-stream", taginfo->data, taginfo->len);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -516,13 +515,21 @@ void init_web() {
|
||||
UDPcomm udpsync;
|
||||
udpsync.getAPList();
|
||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||
String HasC6 = "0";
|
||||
String HasH2 = "0";
|
||||
String HasTSLR = "0";
|
||||
|
||||
response->print("{");
|
||||
#ifdef C6_OTA_FLASHING
|
||||
response->print("\"C6\": \"1\", ");
|
||||
#else
|
||||
response->print("\"C6\": \"0\", ");
|
||||
#ifdef HAS_H2
|
||||
HasH2 = "1";
|
||||
#elif defined(HAS_TSLR)
|
||||
HasTSLR = "1";
|
||||
#elif defined(C6_OTA_FLASHING)
|
||||
HasC6 = "1";
|
||||
#endif
|
||||
response->print("\"C6\": \"" + HasC6 + "\", ");
|
||||
response->print("\"H2\": \"" + HasH2 + "\", ");
|
||||
response->print("\"TLSR\": \"" + HasTSLR + "\", ");
|
||||
#ifdef SAVE_SPACE
|
||||
response->print("\"savespace\": \"1\", ");
|
||||
#else
|
||||
@@ -628,6 +635,9 @@ void init_web() {
|
||||
if (request->hasParam("discovery", true)) {
|
||||
config.discovery = static_cast<uint8_t>(request->getParam("discovery", true)->value().toInt());
|
||||
}
|
||||
if (request->hasParam("showtimestamp", true)) {
|
||||
config.showtimestamp = static_cast<uint8_t>(request->getParam("showtimestamp", true)->value().toInt());
|
||||
}
|
||||
if (request->hasParam("repo", true)) {
|
||||
config.repo = request->getParam("repo", true)->value();
|
||||
}
|
||||
@@ -639,6 +649,28 @@ void init_web() {
|
||||
request->send(200, "text/plain", "Ok, saved");
|
||||
});
|
||||
|
||||
// Allow external time sync (e.g., from Home Assistant) without Internet
|
||||
// Usage: POST /set_time with form field 'epoch' (UNIX time seconds)
|
||||
server.on("/set_time", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
if (request->hasParam("epoch", true)) {
|
||||
time_t epoch = static_cast<time_t>(request->getParam("epoch", true)->value().toInt());
|
||||
if (epoch > 1600000000) { // basic sanity check (~2020-09-13)
|
||||
struct timeval tv;
|
||||
tv.tv_sec = epoch;
|
||||
tv.tv_usec = 0;
|
||||
settimeofday(&tv, nullptr);
|
||||
logLine("Time set via /set_time");
|
||||
wsSendSysteminfo();
|
||||
request->send(200, "text/plain", "ok");
|
||||
return;
|
||||
} else {
|
||||
request->send(400, "text/plain", "invalid epoch");
|
||||
return;
|
||||
}
|
||||
}
|
||||
request->send(400, "text/plain", "missing 'epoch'");
|
||||
});
|
||||
|
||||
server.on("/set_var", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
if (request->hasParam("key", true) && request->hasParam("val", true)) {
|
||||
std::string key = request->getParam("key", true)->value().c_str();
|
||||
@@ -652,7 +684,7 @@ void init_web() {
|
||||
});
|
||||
server.on("/set_vars", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
if (request->hasParam("json", true)) {
|
||||
DynamicJsonDocument jsonDocument(2048);
|
||||
JsonDocument jsonDocument;
|
||||
DeserializationError error = deserializeJson(jsonDocument, request->getParam("json", true)->value());
|
||||
if (error) {
|
||||
request->send(400, "text/plain", "Failed to parse JSON");
|
||||
@@ -679,7 +711,7 @@ void init_web() {
|
||||
server.on("/get_wifi_config", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
Preferences preferences;
|
||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||
StaticJsonDocument<250> doc;
|
||||
JsonDocument doc;
|
||||
preferences.begin("wifi", false);
|
||||
const char *keys[] = {"ssid", "pw", "ip", "mask", "gw", "dns"};
|
||||
const size_t numKeys = sizeof(keys) / sizeof(keys[0]);
|
||||
@@ -693,13 +725,13 @@ void init_web() {
|
||||
|
||||
server.on("/get_ssid_list", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||
DynamicJsonDocument doc(5000);
|
||||
JsonDocument doc;
|
||||
|
||||
doc["scanstatus"] = WiFi.scanComplete();
|
||||
JsonArray networks = doc.createNestedArray("networks");
|
||||
JsonArray networks = doc["networks"].to<JsonArray>();
|
||||
for (int i = 0; i < (WiFi.scanComplete() > 50 ? 50 : WiFi.scanComplete()); ++i) {
|
||||
if (WiFi.SSID(i) != "") {
|
||||
JsonObject network = networks.createNestedObject();
|
||||
JsonObject network = networks.add<JsonObject>();
|
||||
network["ssid"] = WiFi.SSID(i);
|
||||
network["ch"] = WiFi.channel(i);
|
||||
network["rssi"] = WiFi.RSSI(i);
|
||||
@@ -725,7 +757,7 @@ void init_web() {
|
||||
const size_t numKeys = sizeof(keys) / sizeof(keys[0]);
|
||||
for (size_t i = 0; i < numKeys; i++) {
|
||||
String key = keys[i];
|
||||
if (jsonObj.containsKey(key)) {
|
||||
if (jsonObj[key].is<String>()) {
|
||||
preferences.putString(key.c_str(), jsonObj[key].as<String>());
|
||||
}
|
||||
}
|
||||
@@ -868,10 +900,11 @@ void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index
|
||||
file.write(uploadInfo->buffer, uploadInfo->bufferSize);
|
||||
file.close();
|
||||
uploadInfo->bufferSize = 0;
|
||||
xSemaphoreGive(fsMutex);
|
||||
} else {
|
||||
xSemaphoreGive(fsMutex);
|
||||
logLine("Failed to open file for appending: " + uploadfilename);
|
||||
}
|
||||
xSemaphoreGive(fsMutex);
|
||||
|
||||
memcpy(uploadInfo->buffer, data, len);
|
||||
uploadInfo->bufferSize = len;
|
||||
@@ -885,10 +918,11 @@ void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index
|
||||
if (file) {
|
||||
file.write(uploadInfo->buffer, uploadInfo->bufferSize);
|
||||
file.close();
|
||||
xSemaphoreGive(fsMutex);
|
||||
} else {
|
||||
xSemaphoreGive(fsMutex);
|
||||
logLine("Failed to open file for appending: " + uploadfilename);
|
||||
}
|
||||
xSemaphoreGive(fsMutex);
|
||||
request->_tempObject = nullptr;
|
||||
delete uploadInfo;
|
||||
}
|
||||
@@ -903,6 +937,18 @@ void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index
|
||||
if (request->hasParam("dither", true)) {
|
||||
dither = request->getParam("dither", true)->value().toInt();
|
||||
}
|
||||
if (request->hasParam("alias", true)) {
|
||||
taginfo->alias = request->getParam("alias", true)->value();
|
||||
}
|
||||
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());
|
||||
}
|
||||
if (request->hasParam("invert", true)) {
|
||||
taginfo->invert = atoi(request->getParam("invert", true)->value().c_str());
|
||||
}
|
||||
uint32_t ttl = 0;
|
||||
if (request->hasParam("ttl", true)) {
|
||||
ttl = request->getParam("ttl", true)->value().toInt();
|
||||
@@ -946,7 +992,7 @@ void doJsonUpload(AsyncWebServerRequest *request) {
|
||||
uint8_t mac[8];
|
||||
if (hex2mac(dst, mac)) {
|
||||
xSemaphoreTake(fsMutex, portMAX_DELAY);
|
||||
File file = LittleFS.open("/current/" + dst + ".json", "w");
|
||||
File file = contentFS->open("/current/" + dst + ".json", "w");
|
||||
if (!file) {
|
||||
request->send(400, "text/plain", "Failed to create file");
|
||||
xSemaphoreGive(fsMutex);
|
||||
|
||||
@@ -466,9 +466,9 @@ void webFlasherTask(void* parameter) {
|
||||
}
|
||||
|
||||
void handleWSdata(uint8_t* data, size_t len, AsyncWebSocketClient* client) {
|
||||
StaticJsonDocument<200> doc;
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, (const char*)data);
|
||||
StaticJsonDocument<250> response;
|
||||
JsonDocument response;
|
||||
response["flashstatus"] = 1;
|
||||
|
||||
if (error) {
|
||||
@@ -476,7 +476,7 @@ void handleWSdata(uint8_t* data, size_t len, AsyncWebSocketClient* client) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc.containsKey("flashcmd")) {
|
||||
if (doc["flashcmd"].is<int>()) {
|
||||
uint16_t flashcmd = doc["flashcmd"].as<int>();
|
||||
switch (flashcmd) {
|
||||
case WEBFLASH_ENABLE_AUTOFLASH:
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <WiFi.h>
|
||||
#include <esp_wifi.h>
|
||||
|
||||
#include <ETH.h>
|
||||
|
||||
#include "newproto.h"
|
||||
#include "system.h"
|
||||
#include "tag_db.h"
|
||||
@@ -16,6 +18,13 @@ uint8_t WifiManager::apClients = 0;
|
||||
uint8_t x_buffer[100];
|
||||
uint8_t x_position = 0;
|
||||
|
||||
#if defined(ETHERNET_PHY_POWER) && defined(ETHERNET_PHY_MDC) && defined(ETHERNET_PHY_MDIO) && defined(ETHERNET_PHY_TYPE) && defined(ETHERNET_CLK_MODE)
|
||||
static bool eth_init = false;
|
||||
static bool eth_connected = false;
|
||||
static bool eth_ip_ok = false;
|
||||
static long eth_timeout = 0;
|
||||
#endif
|
||||
|
||||
WifiManager::WifiManager() {
|
||||
_reconnectIntervalCheck = 5000;
|
||||
_retryIntervalCheck = 5 * 60000;
|
||||
@@ -43,6 +52,24 @@ void WifiManager::terminalLog(String text) {
|
||||
}
|
||||
|
||||
void WifiManager::poll() {
|
||||
|
||||
#if defined(ETHERNET_PHY_POWER) && defined(ETHERNET_PHY_MDC) && defined(ETHERNET_PHY_MDIO) && defined(ETHERNET_PHY_TYPE) && defined(ETHERNET_CLK_MODE)
|
||||
|
||||
if (eth_connected) {
|
||||
wifiStatus = ETHERNET;
|
||||
if(!eth_ip_ok && eth_timeout != 0 && millis() - eth_timeout > 2000) {
|
||||
eth_timeout = 0;
|
||||
eth_connected = false;
|
||||
}
|
||||
} else if(!eth_connected && wifiStatus == ETHERNET) {
|
||||
wifiStatus = NOINIT;
|
||||
_APstarted = false;
|
||||
WiFi.mode(WIFI_STA);
|
||||
connectToWifi();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (wifiStatus == AP && millis() > _nextReconnectCheck && _ssid != "") {
|
||||
if (apClients == 0) {
|
||||
terminalLog("Attempting to reconnect to WiFi.");
|
||||
@@ -68,6 +95,10 @@ void WifiManager::poll() {
|
||||
}
|
||||
|
||||
#ifndef HAS_USB
|
||||
|
||||
#ifdef ETHERNET_CLK_MODE
|
||||
if (!(ETHERNET_CLK_MODE == ETH_CLOCK_GPIO0_IN || ETHERNET_CLK_MODE == ETH_CLOCK_GPIO0_OUT)) {
|
||||
#endif
|
||||
// ap_and_flasher has gpio0 in use as FLASHER_AP_POWER
|
||||
if (digitalRead(0) == LOW) {
|
||||
Serial.println("GPIO0 LOW");
|
||||
@@ -99,12 +130,37 @@ void WifiManager::poll() {
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
#ifdef ETHERNET_CLK_MODE
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
pollSerial();
|
||||
}
|
||||
|
||||
void WifiManager::initEth() {
|
||||
#if defined(ETHERNET_PHY_POWER) && defined(ETHERNET_PHY_MDC) && defined(ETHERNET_PHY_MDIO) && defined(ETHERNET_PHY_TYPE) && defined(ETHERNET_CLK_MODE)
|
||||
if(!eth_init) {
|
||||
eth_init = true;
|
||||
ETH.begin(
|
||||
ETH_PHY_ADDR,
|
||||
ETHERNET_PHY_POWER,
|
||||
ETHERNET_PHY_MDC,
|
||||
ETHERNET_PHY_MDIO,
|
||||
ETHERNET_PHY_TYPE,
|
||||
ETHERNET_CLK_MODE,
|
||||
false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WifiManager::connectToWifi() {
|
||||
#if defined(ETHERNET_PHY_POWER) && defined(ETHERNET_PHY_MDC) && defined(ETHERNET_PHY_MDIO) && defined(ETHERNET_PHY_TYPE) && defined(ETHERNET_CLK_MODE)
|
||||
if (wifiStatus == ETHERNET || eth_connected)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
Preferences preferences;
|
||||
preferences.begin("wifi", false);
|
||||
_ssid = preferences.getString("ssid", WiFi_SSID());
|
||||
@@ -136,6 +192,11 @@ bool WifiManager::connectToWifi() {
|
||||
}
|
||||
|
||||
bool WifiManager::connectToWifi(String ssid, String pass, bool savewhensuccessfull) {
|
||||
#if defined(ETHERNET_PHY_POWER) && defined(ETHERNET_PHY_MDC) && defined(ETHERNET_PHY_MDIO) && defined(ETHERNET_PHY_TYPE) && defined(ETHERNET_CLK_MODE)
|
||||
if (wifiStatus == ETHERNET)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
_ssid = ssid;
|
||||
_pass = pass;
|
||||
_savewhensuccessfull = savewhensuccessfull;
|
||||
@@ -145,26 +206,7 @@ bool WifiManager::connectToWifi(String ssid, String pass, bool savewhensuccessfu
|
||||
delay(100);
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
delay(100);
|
||||
char hostname[32] = "OpenEpaperLink-";
|
||||
uint8_t mac[6];
|
||||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||
char lastTwoBytes[5];
|
||||
sprintf(lastTwoBytes, "%02X%02X", mac[4], mac[5]);
|
||||
strcat(hostname, lastTwoBytes);
|
||||
|
||||
if (config.alias[0] != '\0') {
|
||||
int len = strlen(config.alias);
|
||||
int j = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = config.alias[i];
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-') {
|
||||
hostname[j] = c;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
hostname[j] = '\0';
|
||||
}
|
||||
WiFi.setHostname(hostname);
|
||||
WiFi.setHostname(buildHostname(ESP_MAC_WIFI_STA).c_str());
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.setSleep(WIFI_PS_MIN_MODEM);
|
||||
|
||||
@@ -177,6 +219,11 @@ bool WifiManager::connectToWifi(String ssid, String pass, bool savewhensuccessfu
|
||||
}
|
||||
|
||||
bool WifiManager::waitForConnection() {
|
||||
#if defined(ETHERNET_PHY_POWER) && defined(ETHERNET_PHY_MDC) && defined(ETHERNET_PHY_MDIO) && defined(ETHERNET_PHY_TYPE) && defined(ETHERNET_CLK_MODE)
|
||||
if (wifiStatus == ETHERNET)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
unsigned long timeout = millis() + _connectionTimeout;
|
||||
wifiStatus = WAIT_CONNECTING;
|
||||
|
||||
@@ -209,7 +256,7 @@ bool WifiManager::waitForConnection() {
|
||||
}
|
||||
|
||||
void WifiManager::startManagementServer() {
|
||||
if (!_APstarted) {
|
||||
if (!_APstarted && wifiStatus != ETHERNET) {
|
||||
terminalLog("Starting config AP, ssid: OpenEPaperLink");
|
||||
logLine("Starting configuration AP, ssid OpenEPaperLink");
|
||||
WiFi.disconnect(true, true);
|
||||
@@ -225,6 +272,37 @@ void WifiManager::startManagementServer() {
|
||||
}
|
||||
}
|
||||
|
||||
String WifiManager::buildHostname(esp_mac_type_t mac_type) {
|
||||
char hostname[32] = "OpenEpaperLink-";
|
||||
uint8_t mac[6];
|
||||
esp_read_mac(mac, mac_type);
|
||||
char lastTwoBytes[5];
|
||||
sprintf(lastTwoBytes, "%02X%02X", mac[4], mac[5]);
|
||||
strcat(hostname, lastTwoBytes);
|
||||
|
||||
if (config.alias[0] != '\0') {
|
||||
int len = strlen(config.alias);
|
||||
int j = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = config.alias[i];
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-') {
|
||||
hostname[j] = c;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
hostname[j] = '\0';
|
||||
}
|
||||
return String(hostname);
|
||||
}
|
||||
|
||||
IPAddress WifiManager::localIP() {
|
||||
if (wifiStatus == ETHERNET) {
|
||||
return ETH.localIP();
|
||||
} else {
|
||||
return WiFi.localIP();
|
||||
}
|
||||
}
|
||||
|
||||
String WifiManager::WiFi_SSID() {
|
||||
wifi_config_t conf;
|
||||
esp_wifi_get_config(WIFI_IF_STA, &conf);
|
||||
@@ -296,6 +374,46 @@ void WifiManager::WiFiEvent(WiFiEvent_t event) {
|
||||
// eventname = "Assigned IP address to client";
|
||||
break;
|
||||
|
||||
#if defined(ETHERNET_PHY_POWER) && defined(ETHERNET_PHY_MDC) && defined(ETHERNET_PHY_MDIO) && defined(ETHERNET_PHY_TYPE) && defined(ETHERNET_CLK_MODE)
|
||||
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
eventname = "ETH Started";
|
||||
//set eth hostname here
|
||||
ETH.setHostname(buildHostname(ESP_MAC_ETH).c_str());
|
||||
eth_timeout = 0;
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
eventname = "ETH Connected";
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
WiFi.disconnect();
|
||||
eth_connected = true;
|
||||
eth_timeout = millis();
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
if (ETH.fullDuplex()) {
|
||||
eventname = "ETH MAC: " + ETH.macAddress() + ", IPv4: " + ETH.localIP().toString() + ", FULL_DUPLEX, " + ETH.linkSpeed() + "Mbps";
|
||||
} else {
|
||||
eventname = "ETH MAC: " + ETH.macAddress() + ", IPv4: " + ETH.localIP().toString() + ", " + ETH.linkSpeed() + "Mbps";
|
||||
}
|
||||
eth_ip_ok = true;
|
||||
init_udp();
|
||||
eth_timeout = 0;
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
eventname = "ETH Disconnected";
|
||||
eth_connected = false;
|
||||
eth_ip_ok = false;
|
||||
eth_timeout = 0;
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_STOP:
|
||||
eventname = "ETH Stopped";
|
||||
eth_connected = false;
|
||||
eth_ip_ok = false;
|
||||
eth_timeout = 0;
|
||||
break;
|
||||
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,27 @@
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Current date",
|
||||
"desc": "Shows the current date",
|
||||
"param": [ ]
|
||||
"desc": "Shows the current date. In some templates, the sunrise and sunset times are shown as well, when you fill in your location.",
|
||||
"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": "geoselect"
|
||||
},
|
||||
{
|
||||
"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": 2,
|
||||
@@ -140,7 +159,7 @@
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Weather forecast",
|
||||
"desc": "Weather forecast for the next five days. Weather data by Open-Meteo.com. Parameters Lat, Lon and Time Zone are filled automatically from the entered location. In case of an ambiguous location, you can alter those manually.",
|
||||
"desc": "Weather forecast for the next five days. Weather data by Open-Meteo.com. Parameters Lat, Lon and Time Zone are filled automatically from the entered location. In case of an ambiguous location, choose from the drop down list.",
|
||||
"param": [
|
||||
{
|
||||
"key": "location",
|
||||
@@ -389,6 +408,7 @@
|
||||
"NO4": "Norway NO4",
|
||||
"NO5": "Norway NO5",
|
||||
"PL": "Poland",
|
||||
"PT": "Portugal",
|
||||
"RO": "Romania",
|
||||
"SK": "Slovakia",
|
||||
"SI": "Slovenia",
|
||||
@@ -431,6 +451,29 @@
|
||||
"0": "No",
|
||||
"1": "-Yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "interval",
|
||||
"name": "Display interval",
|
||||
"desc": "Data averaging interval for better readability on smaller displays",
|
||||
"type": "select",
|
||||
"options": {
|
||||
"0": "-Native (15-min or hourly)",
|
||||
"30": "30 minutes",
|
||||
"60": "1 hour"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "cheapblock",
|
||||
"name": "Cheap block hours",
|
||||
"desc": "Number of hours for cheapest consecutive block (e.g., 3 for dishwasher, 2.5 for 2h30m). Set to 0 to disable. Shows up to 2 non-overlapping blocks.",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "updatefreq",
|
||||
"name": "Update frequency",
|
||||
"desc": "Tag refresh interval in minutes (e.g., 15, 30, 60). Default is 15 minutes to match 15-minute data intervals. Higher values save battery.",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -593,21 +636,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Send custom LUT",
|
||||
"desc": "EXPERIMENTAL. Don't use. YOU RISK DAMAGING YOUR SCREEN.",
|
||||
"capabilities": 4,
|
||||
"properties": [ "savespace" ],
|
||||
"param": [
|
||||
{
|
||||
"key": "bytes",
|
||||
"name": "bytes",
|
||||
"desc": "76 bytes, formatted as 0x00,0x00,...",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "Send Command",
|
||||
@@ -621,6 +649,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"name": "Set Tag Mac",
|
||||
"desc": "Sets the tags mac-address to a specific value. Must be 16 characters, hexdecimal (8 bytes)",
|
||||
"param": [
|
||||
{
|
||||
"key": "mac",
|
||||
"name": "MAC",
|
||||
"desc": "Set Mac address",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Set Tag Config",
|
||||
@@ -743,5 +784,12 @@
|
||||
"type": "binfile"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"name": "Current Time",
|
||||
"desc": "Displays the current time on the tag, only rarely supported even if shown here! It uses a fast LUT when possible otherwise a full refresh which is not very battery friendly and will anoy by flickering every minute",
|
||||
"param": [
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<!--This is the plain html source of the hex encoded Editor-Page embedded in SPIFFSEditor.cpp -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
@@ -425,7 +424,7 @@
|
||||
|
||||
const canvas = $('#previewimg');
|
||||
canvas.style.display = 'block';
|
||||
fetch(path)
|
||||
fetch(path + "?r=" + Math.random())
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(buffer => {
|
||||
|
||||
@@ -433,6 +432,16 @@
|
||||
if (tagTypes[hwtype].zlib > 0 && targetDiv.dataset.ver >= tagTypes[hwtype].zlib) {
|
||||
data = window.opener.processZlib(data);
|
||||
}
|
||||
if (data.length > 0 && tagTypes[hwtype].g5 > 0 && targetDiv.dataset.ver >= tagTypes[hwtype].g5) {
|
||||
const headerSize = data[0];
|
||||
let bufw = (data[2] << 8) | data[1];
|
||||
let bufh = (data[4] << 8) | data[3];
|
||||
if ((bufw == tagTypes[hwtype].width || bufw == tagTypes[hwtype].height) && (bufh == tagTypes[hwtype].width || bufh == tagTypes[hwtype].height) && (data[5] <= 3)) {
|
||||
// valid header for g5 compression
|
||||
if (data[5] == 2) bufh *= 2;
|
||||
data = window.opener.processG5(data.subarray(headerSize), bufw, bufh);
|
||||
}
|
||||
}
|
||||
|
||||
[canvas.width, canvas.height] = [tagTypes[hwtype].width, tagTypes[hwtype].height] || [0, 0];
|
||||
if (tagTypes[hwtype].rotatebuffer%2) [canvas.width, canvas.height] = [canvas.height, canvas.width];
|
||||
@@ -452,7 +461,24 @@
|
||||
imageData.data[i * 4 + 2] = is16Bit ? (rgb & 0x1F) << 3 : ((rgb & 0x03) << 6) * 1.3;
|
||||
imageData.data[i * 4 + 3] = 255;
|
||||
}
|
||||
} else if ([3, 4].includes(tagTypes[hwtype].bpp)) {
|
||||
const bpp = tagTypes[hwtype].bpp;
|
||||
const colorTable = tagTypes[hwtype].colortable;
|
||||
let pixelIndex = 0;
|
||||
let bitOffset = 0;
|
||||
|
||||
while (bitOffset < data.length * 8) {
|
||||
let byteIndex = bitOffset >> 3;
|
||||
let startBit = bitOffset & 7;
|
||||
let pixelValue = (data[byteIndex] << 8 | data[byteIndex + 1] || 0) >> (16 - bpp - startBit) & ((1 << bpp) - 1);
|
||||
let color = colorTable[pixelValue];
|
||||
imageData.data[pixelIndex * 4] = color[0];
|
||||
imageData.data[pixelIndex * 4 + 1] = color[1];
|
||||
imageData.data[pixelIndex * 4 + 2] = color[2];
|
||||
imageData.data[pixelIndex * 4 + 3] = 255;
|
||||
pixelIndex++;
|
||||
bitOffset += bpp;
|
||||
}
|
||||
} else {
|
||||
|
||||
const offsetRed = (data.length >= (canvas.width * canvas.height / 8) * 2) ? canvas.width * canvas.height / 8 : 0;
|
||||
@@ -599,9 +625,9 @@
|
||||
leaf.onclick = function (e) {
|
||||
treeRoot.removeChild(treeRoot.childNodes[0]);
|
||||
if (name == "..") {
|
||||
httpGet(treeRoot, "/");
|
||||
httpGet(treeRoot, path === "/" ? "/" : path.substring(0, path.lastIndexOf('/')) || "/");
|
||||
} else {
|
||||
httpGet(treeRoot, "/" + name);
|
||||
httpGet(treeRoot, path + (path != "/" ? "/" : "") + name);
|
||||
}
|
||||
};
|
||||
leaf.oncontextmenu = function (e) {
|
||||
@@ -627,9 +653,6 @@
|
||||
sortByKey(items, 'name');
|
||||
var list = ce("ul");
|
||||
parent.appendChild(list);
|
||||
if (path != "/") {
|
||||
list.appendChild(createDirLeaf("/", "..", 0));
|
||||
}
|
||||
var ll = items.length;
|
||||
for (var i = 0; i < ll; i++) {
|
||||
if (items[i].type === "file") {
|
||||
@@ -639,7 +662,9 @@
|
||||
list.insertBefore(createDirLeaf(path, items[i].name, items[i].size), list.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
if (path != "/") {
|
||||
list.insertBefore(createDirLeaf(path, "..", 0), list.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
function isTextFile(path) {
|
||||
@@ -752,8 +777,10 @@
|
||||
if (typeof lang === "undefined") {
|
||||
lang = getLangFromFilename(file);
|
||||
}
|
||||
|
||||
if (typeof theme === "undefined") theme = "textmate";
|
||||
if (typeof theme === "undefined") {
|
||||
const currentTheme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
theme = (currentTheme === 'dark') ? "tomorrow_night" : "textmate";
|
||||
}
|
||||
|
||||
if (typeof type === "undefined") {
|
||||
type = "text/" + lang;
|
||||
@@ -836,7 +863,7 @@
|
||||
editor.loadUrl(vars.file);
|
||||
};
|
||||
</script>
|
||||
<script id='ace' src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript"
|
||||
<script id='ace' src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.37.2/ace.js" type="text/javascript"
|
||||
charset="utf-8"></script>
|
||||
<script>
|
||||
if (typeof ace.edit == "undefined") {
|
||||
|
||||
377
ESP32_AP-Flasher/wwwroot/g5decoder.js
Normal file
377
ESP32_AP-Flasher/wwwroot/g5decoder.js
Normal file
@@ -0,0 +1,377 @@
|
||||
//
|
||||
// Group5
|
||||
// A 1-bpp image decoder
|
||||
//
|
||||
// Written by Larry Bank
|
||||
// Copyright (c) 2024 BitBank Software, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file ./LICENSE.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// ./APL.txt.
|
||||
|
||||
// Converted from C to Javascript by Nic Limper
|
||||
|
||||
// Define constants
|
||||
const MAX_IMAGE_FLIPS = 640;
|
||||
|
||||
// Horizontal prefix bits
|
||||
const HORIZ_SHORT_SHORT = 0;
|
||||
const HORIZ_SHORT_LONG = 1;
|
||||
const HORIZ_LONG_SHORT = 2;
|
||||
const HORIZ_LONG_LONG = 3;
|
||||
|
||||
// Return code for encoder and decoder
|
||||
const G5_SUCCESS = 0;
|
||||
const G5_INVALID_PARAMETER = 1;
|
||||
const G5_DECODE_ERROR = 2;
|
||||
const G5_UNSUPPORTED_FEATURE = 3;
|
||||
const G5_ENCODE_COMPLETE = 4;
|
||||
const G5_DECODE_COMPLETE = 5;
|
||||
const G5_NOT_INITIALIZED = 6;
|
||||
const G5_DATA_OVERFLOW = 7;
|
||||
const G5_MAX_FLIPS_EXCEEDED = 8;
|
||||
|
||||
// Utility function equivalent to the TIFFMOTOLONG macro
|
||||
function TIFFMOTOLONG(p, ix) {
|
||||
let value = 0;
|
||||
if (ix < p.length) value |= p[ix] << 24;
|
||||
if (ix + 1 < p.length) value |= p[ix + 1] << 16;
|
||||
if (ix + 2 < p.length) value |= p[ix + 2] << 8;
|
||||
if (ix + 3 < p.length) value |= p[ix + 3];
|
||||
return value;
|
||||
}
|
||||
|
||||
// Constants for bit manipulation
|
||||
const REGISTER_WIDTH = 32; // Must align with a 32-bit system in C++
|
||||
|
||||
/*
|
||||
The code tree that follows has: bit_length, decode routine
|
||||
These codes are for Group 4 (MMR) decoding
|
||||
|
||||
01 = vertneg1, 11h = vert1, 20h = horiz, 30h = pass, 12h = vert2
|
||||
02 = vertneg2, 13h = vert3, 03 = vertneg3, 90h = trash
|
||||
*/
|
||||
|
||||
const code_table = [
|
||||
0x90, 0, 0x40, 0, // trash, uncompressed mode - codes 0 and 1
|
||||
3, 7, // V(-3) pos = 2
|
||||
0x13, 7, // V(3) pos = 3
|
||||
2, 6, 2, 6, // V(-2) pos = 4,5
|
||||
0x12, 6, 0x12, 6, // V(2) pos = 6,7
|
||||
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4, // pass pos = 8->F
|
||||
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4,
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, // horiz pos = 10->1F
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, // V(-1) pos = 20->2F
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3, // V(1) pos = 30->3F
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3
|
||||
];
|
||||
|
||||
class G5DECIMAGE {
|
||||
constructor() {
|
||||
this.iWidth = 0;
|
||||
this.iHeight = 0;
|
||||
this.iError = 0;
|
||||
this.y = 0;
|
||||
this.iVLCSize = 0;
|
||||
this.iHLen = 0;
|
||||
this.iPitch = 0;
|
||||
this.u32Accum = 0;
|
||||
this.ulBitOff = 0;
|
||||
this.ulBits = 0;
|
||||
this.pSrc = null; // Input buffer
|
||||
this.pBuf = null; // Current buffer index
|
||||
this.pBufIndex = 0;
|
||||
this.pCur = new Int16Array(MAX_IMAGE_FLIPS); // Current state
|
||||
this.pRef = new Int16Array(MAX_IMAGE_FLIPS); // Reference state
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//static int g5_decode_init(G5DECIMAGE *pImage, int iWidth, int iHeight, uint8_t *pData, int iDataSize)
|
||||
function g5_decode_init(pImage, iWidth, iHeight, pData, iDataSize) {
|
||||
if (
|
||||
pImage == null ||
|
||||
iWidth < 1 ||
|
||||
iHeight < 1 ||
|
||||
pData == null ||
|
||||
iDataSize < 1
|
||||
) {
|
||||
return G5_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
pImage.iVLCSize = iDataSize;
|
||||
pImage.pSrc = pData;
|
||||
pImage.ulBitOff = 0;
|
||||
pImage.y = 0;
|
||||
pImage.ulBits = TIFFMOTOLONG(pData, 0); // Preload the first 32 bits of data
|
||||
pImage.iWidth = iWidth;
|
||||
pImage.iHeight = iHeight;
|
||||
|
||||
return G5_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
//static void G5DrawLine(G5DECIMAGE *pPage, int16_t *pCurFlips, uint8_t *pOut)
|
||||
function G5DrawLine(pPage, pCurFlips, pOut) {
|
||||
const xright = pPage.iWidth;
|
||||
let pCurIndex = 0;
|
||||
|
||||
// Initialize output to white (0xff)
|
||||
const len = (xright + 7) >> 3; // Number of bytes to generate
|
||||
pOut.fill(0xff, 0, len);
|
||||
|
||||
let x = 0;
|
||||
while (x < xright) { // While the scaled x is within the window bounds
|
||||
const startX = pCurFlips[pCurIndex++]; // Black starting point
|
||||
const run = pCurFlips[pCurIndex++] - startX; // Get the black run
|
||||
|
||||
if (startX >= xright || run <= 0) break;
|
||||
|
||||
// Calculate visible run
|
||||
let visibleX = Math.max(0, startX);
|
||||
let visibleRun = Math.min(xright, startX + run) - visibleX;
|
||||
|
||||
if (visibleRun > 0) {
|
||||
const startByte = visibleX >> 3;
|
||||
const endByte = (visibleX + visibleRun) >> 3;
|
||||
|
||||
const lBit = (0xff << (8 - (visibleX & 7))) & 0xff; // Left bitmask based on the starting x position
|
||||
const rBit = 0xff >> ((visibleX + visibleRun) & 7); // Right bitmask based on the ending x position
|
||||
|
||||
if (endByte == startByte) {
|
||||
// If the run fits in a single byte, combine left and right bit masks
|
||||
pOut[startByte] &= (lBit | rBit);
|
||||
} else {
|
||||
// Mask the left-most byte
|
||||
pOut[startByte] &= lBit;
|
||||
|
||||
// Set intermediate bytes to 0
|
||||
for (let i = startByte + 1; i < endByte; i++) {
|
||||
pOut[i] = 0x00;
|
||||
}
|
||||
|
||||
// Mask the right-most byte if it's not fully aligned
|
||||
pOut[endByte] &= rBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initialize internal structures to decode the image
|
||||
//
|
||||
function Decode_Begin(pPage) {
|
||||
const xsize = pPage.iWidth;
|
||||
|
||||
// Seed the current and reference lines with xsize for V(0) codes
|
||||
for (let i = 0; i < MAX_IMAGE_FLIPS - 2; i++) {
|
||||
pPage.pRef[i] = xsize;
|
||||
pPage.pCur[i] = xsize;
|
||||
}
|
||||
|
||||
// Prefill both current and reference lines with 0x7fff to prevent walking off the end
|
||||
// if the data gets bunged and the current X is > XSIZE
|
||||
pPage.pCur[MAX_IMAGE_FLIPS - 2] = pPage.pRef[MAX_IMAGE_FLIPS - 2] = 0x7fff;
|
||||
pPage.pCur[MAX_IMAGE_FLIPS - 1] = pPage.pRef[MAX_IMAGE_FLIPS - 1] = 0x7fff;
|
||||
|
||||
pPage.pBuf = pPage.pSrc; // Start buffer
|
||||
pPage.pBufIndex = 0;
|
||||
|
||||
// Load 32 bits to start (use a helper function to interpret bytes as a 32-bit integer)
|
||||
pPage.ulBits = TIFFMOTOLONG(pPage.pSrc, 0);
|
||||
pPage.ulBitOff = 0;
|
||||
|
||||
// Calculate the number of bits needed for a long horizontal code
|
||||
pPage.iHLen = 32 - Math.clz32(pPage.iWidth); // clz32 counts leading zeroes in JavaScript
|
||||
}
|
||||
|
||||
|
||||
// Decode a single line of G5 data
|
||||
//
|
||||
function DecodeLine(pPage) {
|
||||
let a0 = -1;
|
||||
let a0_p, b1;
|
||||
let pCurIndex = 0, pRefIndex = 0;
|
||||
const pCur = pPage.pCur;
|
||||
const pRef = pPage.pRef;
|
||||
let ulBits = pPage.ulBits;
|
||||
let ulBitOff = pPage.ulBitOff;
|
||||
let pBufIndex = pPage.pBufIndex;
|
||||
const pBuf = pPage.pBuf;
|
||||
const xsize = pPage.iWidth;
|
||||
const u32HLen = pPage.iHLen;
|
||||
const u32HMask = (1 << u32HLen) - 1;
|
||||
let tot_run, tot_run1;
|
||||
|
||||
while (a0 < xsize) {
|
||||
if (ulBitOff > (REGISTER_WIDTH - 8)) {
|
||||
pBufIndex += (ulBitOff >> 3);
|
||||
ulBitOff &= 7;
|
||||
ulBits = TIFFMOTOLONG(pBuf, pBufIndex);
|
||||
}
|
||||
|
||||
if (((ulBits << ulBitOff) & 0x80000000) !== 0) {
|
||||
a0 = pRef[pRefIndex++];
|
||||
pCur[pCurIndex++] = a0;
|
||||
ulBitOff++;
|
||||
} else {
|
||||
const lBits = (ulBits >> (REGISTER_WIDTH - 8 - ulBitOff)) & 0xfe;
|
||||
const sCode = code_table[lBits];
|
||||
ulBitOff += code_table[lBits + 1];
|
||||
switch (sCode) {
|
||||
case 1: case 2: case 3: // V(-1), V(-2), V(-3)
|
||||
a0 = pRef[pRefIndex] - sCode; // A0 = B1 - x
|
||||
pCur[pCurIndex++] = a0;
|
||||
if (pRefIndex == 0) {
|
||||
pRefIndex += 2;
|
||||
}
|
||||
pRefIndex--;
|
||||
while (a0 >= pRef[pRefIndex]) {
|
||||
pRefIndex += 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x11: case 0x12: case 0x13: // V(1), V(2), V(3)
|
||||
a0 = pRef[pRefIndex++];
|
||||
b1 = a0;
|
||||
a0 += sCode & 7;
|
||||
if (b1 !== xsize && a0 < xsize) {
|
||||
while (a0 >= pRef[pRefIndex]) {
|
||||
pRefIndex += 2;
|
||||
}
|
||||
}
|
||||
if (a0 > xsize) {
|
||||
a0 = xsize;
|
||||
}
|
||||
pCur[pCurIndex++] = a0;
|
||||
break;
|
||||
|
||||
case 0x20: // Horizontal codes
|
||||
if (ulBitOff > (REGISTER_WIDTH - 16)) {
|
||||
pBufIndex += (ulBitOff >> 3);
|
||||
ulBitOff &= 7;
|
||||
ulBits = TIFFMOTOLONG(pBuf, pBufIndex);
|
||||
}
|
||||
|
||||
a0_p = Math.max(0, a0);
|
||||
const lBits = (ulBits >> ((REGISTER_WIDTH - 2) - ulBitOff)) & 0x3;
|
||||
ulBitOff += 2;
|
||||
|
||||
switch (lBits) {
|
||||
case HORIZ_SHORT_SHORT:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
|
||||
ulBitOff += 3;
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
|
||||
ulBitOff += 3;
|
||||
break;
|
||||
|
||||
case HORIZ_SHORT_LONG:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
|
||||
ulBitOff += 3;
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
|
||||
ulBitOff += u32HLen;
|
||||
break;
|
||||
|
||||
case HORIZ_LONG_SHORT:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
|
||||
ulBitOff += u32HLen;
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
|
||||
ulBitOff += 3;
|
||||
break;
|
||||
|
||||
case HORIZ_LONG_LONG:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
|
||||
ulBitOff += u32HLen;
|
||||
if (ulBitOff > (REGISTER_WIDTH - 16)) {
|
||||
pBufIndex += (ulBitOff >> 3);
|
||||
ulBitOff &= 7;
|
||||
ulBits = TIFFMOTOLONG(pBuf, pBufIndex);
|
||||
}
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
|
||||
ulBitOff += u32HLen;
|
||||
break;
|
||||
}
|
||||
a0 = a0_p + tot_run;
|
||||
pCur[pCurIndex++] = a0;
|
||||
a0 += tot_run1;
|
||||
|
||||
if (a0 < xsize) {
|
||||
while (a0 >= pRef[pRefIndex]) {
|
||||
pRefIndex += 2;
|
||||
}
|
||||
}
|
||||
pCur[pCurIndex++] = a0;
|
||||
break;
|
||||
|
||||
case 0x30: // Pass code
|
||||
pRefIndex++;
|
||||
a0 = pRef[pRefIndex++];
|
||||
break;
|
||||
|
||||
default: // ERROR
|
||||
pPage.iError = G5_DECODE_ERROR;
|
||||
return pPage.iError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pCur[pCurIndex++] = xsize;
|
||||
pCur[pCurIndex++] = xsize;
|
||||
|
||||
pPage.ulBits = ulBits;
|
||||
pPage.ulBitOff = ulBitOff;
|
||||
pPage.pBufIndex = pBufIndex;
|
||||
|
||||
return pPage.iError;
|
||||
}
|
||||
|
||||
|
||||
function processG5(data, width, height) {
|
||||
try {
|
||||
let decoder = new G5DECIMAGE();
|
||||
let initResult = g5_decode_init(decoder, width, height, data, data.length);
|
||||
|
||||
if (initResult !== G5_SUCCESS) {
|
||||
throw new Error("Initialization failed with code: " + initResult);
|
||||
}
|
||||
|
||||
Decode_Begin(decoder);
|
||||
|
||||
let outputBuffer = new Uint8Array(height * ((width + 7) >> 3)); // Adjust for byte alignment
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
let lineBuffer = outputBuffer.subarray(y * ((width + 7) >> 3), (y + 1) * ((width + 7) >> 3));
|
||||
decoder.y = y;
|
||||
let decodeResult = DecodeLine(decoder);
|
||||
|
||||
if (decodeResult !== G5_SUCCESS) {
|
||||
console.log("Decoding error on line " + y + ": " + decoder.iError);
|
||||
}
|
||||
|
||||
G5DrawLine(decoder, decoder.pCur, lineBuffer);
|
||||
const temp = decoder.pRef;
|
||||
decoder.pRef = decoder.pCur;
|
||||
decoder.pCur = temp;
|
||||
}
|
||||
|
||||
return outputBuffer;
|
||||
} catch (error) {
|
||||
console.error("Error during G5 decoding:", error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||
|
||||
<title>Open EPaper Link Access Point</title>
|
||||
<script src="main.js" defer></script>
|
||||
<link rel="stylesheet" href="main.css" type="text/css" />
|
||||
<script src="main.js?2.74" defer></script>
|
||||
<script src="g5decoder.js?2.74"></script>
|
||||
<link rel="stylesheet" href="main.css?2.74" type="text/css" />
|
||||
<!--<link rel="icon" type="image/vnd.icon" href="favicon.ico">-->
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0" />
|
||||
@@ -24,11 +25,11 @@
|
||||
<div class="tablinks material-symbols-outlined" data-target="hometab" title="Dashboard">home</div>
|
||||
<div class="tablinks material-symbols-outlined" data-target="tagtab" title="Tags">sell</div>
|
||||
<div class="tablinks material-symbols-outlined" data-target="aptab" title="Access Points">cell_tower</div>
|
||||
<!--<div class="tablinks material-symbols-outlined" data-target="templatetab" title="Templates">browse
|
||||
</div>-->
|
||||
<!--<div class="tablinks material-symbols-outlined" data-target="templatetab" title="Templates">browse</div>-->
|
||||
<div class="tablinks material-symbols-outlined" data-target="flashtab" title="Tag flasher" style="display:none;">flash_on</div>
|
||||
<div class="tablinks material-symbols-outlined" data-target="configtab" title="Settings">settings</div>
|
||||
<div class="tablinks material-symbols-outlined" data-target="logtab" title="Logging">text_snippet</div>
|
||||
<button id="theme-toggle" class="theme-toggle-btn material-symbols-outlined" title="Toggle Theme">light_mode</button>
|
||||
</div>
|
||||
<!-- /tabs -->
|
||||
<div><span id="runstate"></div>
|
||||
@@ -181,6 +182,7 @@
|
||||
<div class="nextcheckin"></div>
|
||||
<div class="nextupdate"></div>
|
||||
<div class="corner">
|
||||
<div class="waitingicon" title="New content is scheduled to be generated (as soon as possible, or shortly before the next expected checkin time)">⏳</div>
|
||||
<div class="pendingicon" title="A new message is waiting for the tag to pick up">↻</div>
|
||||
<div class="warningicon" title="This tag has not been seen for a long time">⚠
|
||||
</div>
|
||||
@@ -289,24 +291,24 @@ options:
|
||||
<option value="27">27</option>
|
||||
</select>
|
||||
</p>
|
||||
<p title="Enable SubGhz support and select channel. This requires an AP that has an optional CC1101 SubGhz radio module attached.">
|
||||
<label for="apcfgsubgigchid">SubGhz channel</label>
|
||||
<select id="apcfgsubgigchid">
|
||||
<option value="0" selected>disabled</option>
|
||||
<option value="100">100 - 864.000 Mhz (Europe, etc)</option>
|
||||
<option value="101">101 - 865.006 Mhz (Europe, etc)</option>
|
||||
<option value="102">102 - 866.014 Mhz (Europe, etc)</option>
|
||||
<option value="103">103 - 867.020 Mhz (Europe, etc)</option>
|
||||
<option value="104">104 - 868.027 Mhz (Europe, etc)</option>
|
||||
<option value="105">105 - 869.034 Mhz (Europe, etc)</option>
|
||||
<option value="200">200 - 903.000 Mhz (US, etc)</option>
|
||||
<option value="201">201 - 907.027 Mhz (US, etc)</option>
|
||||
<option value="202">202 - 911.054 Mhz (US, etc)</option>
|
||||
<option value="203">203 - 915.083 Mhz (US, etc)</option>
|
||||
<option value="204">204 - 919.110 Mhz (US, etc)</option>
|
||||
<option value="205">205 - 923.138 Mhz (US, etc)</option>
|
||||
</select>
|
||||
</p>
|
||||
<p title="Enable SubGhz support and select channel. This requires an AP that has an optional CC1101 SubGhz radio module attached.">
|
||||
<label for="apcfgsubgigchid">SubGhz channel</label>
|
||||
<select id="apcfgsubgigchid">
|
||||
<option value="0" selected>disabled</option>
|
||||
<option value="100">100 - 864.000 Mhz (Europe, etc)</option>
|
||||
<option value="101">101 - 865.006 Mhz (Europe, etc)</option>
|
||||
<option value="102">102 - 866.014 Mhz (Europe, etc)</option>
|
||||
<option value="103">103 - 867.020 Mhz (Europe, etc)</option>
|
||||
<option value="104">104 - 868.027 Mhz (Europe, etc)</option>
|
||||
<option value="105">105 - 869.034 Mhz (Europe, etc)</option>
|
||||
<option value="200">200 - 903.000 Mhz (US, etc)</option>
|
||||
<option value="201">201 - 907.027 Mhz (US, etc)</option>
|
||||
<option value="202">202 - 911.054 Mhz (US, etc)</option>
|
||||
<option value="203">203 - 915.083 Mhz (US, etc)</option>
|
||||
<option value="204">204 - 919.110 Mhz (US, etc)</option>
|
||||
<option value="205">205 - 923.138 Mhz (US, etc)</option>
|
||||
</select>
|
||||
</p>
|
||||
<p title="Enable Bluetooth (BLE) support. Only enable this if you have BLE capable tags. Changing this value requires a reboot to take effect.">
|
||||
<label for="apcfgble">Bluetooth</label>
|
||||
<select id="apcfgble">
|
||||
@@ -478,6 +480,13 @@ options:
|
||||
<option value="1">Broadcast</option>
|
||||
</select>
|
||||
</p>
|
||||
<p title="Show a timestamp on the screen when the tag is updated">
|
||||
<label for="apcshowtimestamp">Enable timestamp</label>
|
||||
<select id="apcshowtimestamp">
|
||||
<option value="0" selected>no</option>
|
||||
<option value="1">yes</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<input type="button" value="Save" id="apcfgsave"><span id="apcfgmsg"></span>
|
||||
</p>
|
||||
@@ -521,7 +530,13 @@ options:
|
||||
<button id="confirmSelectRepo">Confirm</button><button id="cancelSelectRepo">Cancel</button>
|
||||
</div>
|
||||
<h4>Releases</h4>
|
||||
<div id="releasetable"></div>
|
||||
To update to the latest version, use the big 'update now' button if it is shown on top of this screen.<br>
|
||||
To up/downgrade to other versions: for the smoothest experience, first update the ESP32 part and<br>
|
||||
without rebooting, update the filesystem, then reboot the AP and reload the webpage.<br>
|
||||
<div id="releasetable" class="releasetable"></div>
|
||||
<h4 id="radio_release_title"></h4>
|
||||
<div id="radio_releasetable" class="releasetable"></div>
|
||||
<div id="radio_releasetable1" class="releasetable"></div>
|
||||
<h4>Other actions</h4>
|
||||
<div>
|
||||
<p id="rollbackOption" style="display:none">
|
||||
@@ -608,8 +623,7 @@ options:
|
||||
</p>
|
||||
</dialog>
|
||||
|
||||
<ul id="context-menu"
|
||||
style="display: none; position: absolute; background: white; border: 1px solid gray; padding: 0; list-style: none;">
|
||||
<ul id="context-menu" style="display: none; position: absolute;">
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
|
||||
415
ESP32_AP-Flasher/wwwroot/jsontemplate-demo-v2.html
Normal file
415
ESP32_AP-Flasher/wwwroot/jsontemplate-demo-v2.html
Normal file
@@ -0,0 +1,415 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>JSON Template Publisher - Flat Dark</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #1e1e1e;
|
||||
color: #dcdcdc;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.main-wrapper {
|
||||
display: flex;
|
||||
gap: 25px;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
padding: 20px;
|
||||
}
|
||||
.input-column, .preview-column {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.preview-column {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #2a2a2a;
|
||||
padding: 25px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #383838;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.input-column .container {
|
||||
|
||||
}
|
||||
|
||||
|
||||
h3 {
|
||||
color: #4dabf7;
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.6em;
|
||||
font-weight: 600;
|
||||
}
|
||||
p, label {
|
||||
line-height: 1.6;
|
||||
}
|
||||
a {
|
||||
color: #4dabf7;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
color: #74c0fc;
|
||||
}
|
||||
input[type="text"], textarea, select {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #4a4a4a;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
font-size: 0.95rem;
|
||||
background-color: #303030;
|
||||
color: #dcdcdc;
|
||||
}
|
||||
input[type="text"]:focus, textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: #4dabf7;
|
||||
box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.3);
|
||||
}
|
||||
textarea {
|
||||
min-height: 140px;
|
||||
resize: vertical;
|
||||
}
|
||||
input[type="submit"], button {
|
||||
background-color: #0078d4;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.15s ease-in-out;
|
||||
}
|
||||
input[type="submit"]:hover, button:hover {
|
||||
background-color: #005a9e;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: #b0b0b0;
|
||||
}
|
||||
#previewArea {
|
||||
margin-top: 0;
|
||||
padding: 20px;
|
||||
border: 1px solid #383838;
|
||||
border-radius: 8px;
|
||||
background-color: #2a2a2a;
|
||||
text-align: center;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#previewArea h4 {
|
||||
margin-top: 0;
|
||||
color: #4dabf7;
|
||||
}
|
||||
#previewCanvas {
|
||||
border: 1px solid #4f4f4f;
|
||||
max-width: 100%;
|
||||
max-height: 450px;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 12px auto;
|
||||
transform-origin: center center;
|
||||
background-color: #fff;
|
||||
}
|
||||
.info-text {
|
||||
font-size: 0.85em;
|
||||
color: #999;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.status-message {
|
||||
margin-top: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
word-break: break-word;
|
||||
border: 1px solid transparent;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.error-message { color: #ffcdd2; background-color: #c62828; border-color: #b71c1c; }
|
||||
.success-message { color: #c8e6c9; background-color: #2e7d32; border-color: #1b5e20; }
|
||||
.warning-message { color: #fff9c4; background-color: #f9a825; border-color: #f57f17; }
|
||||
.info-message { color: #bbdefb; background-color: #1565c0; border-color: #0d47a1; }
|
||||
|
||||
.flex-group { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
|
||||
.flex-group > div { flex: 1; min-width: 180px; }
|
||||
.flex-group > button { flex-shrink: 0; margin-bottom: 15px; padding: 10px 15px; }
|
||||
#clearMacHistoryBtn { background-color: #d32f2f; }
|
||||
#clearMacHistoryBtn:hover { background-color: #b71c1c; }
|
||||
</style>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"></script>
|
||||
<script>
|
||||
|
||||
const MAX_IMAGE_FLIPS=640,HORIZ_SHORT_SHORT=0,HORIZ_SHORT_LONG=1,HORIZ_LONG_SHORT=2,HORIZ_LONG_LONG=3,G5_SUCCESS=0,G5_INVALID_PARAMETER=1,G5_DECODE_ERROR=2,REGISTER_WIDTH=32;const code_table=[0x90,0,0x40,0,3,7,0x13,7,2,6,2,6,0x12,6,0x12,6,0x30,4,0x30,4,0x30,4,0x30,4,0x30,4,0x30,4,0x30,4,0x30,4,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,0x20,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3,0x11,3];
|
||||
function TIFFMOTOLONG(p,ix){let val=0;if(ix<p.length)val|=p[ix]<<24;if(ix+1<p.length)val|=p[ix+1]<<16;if(ix+2<p.length)val|=p[ix+2]<<8;if(ix+3<p.length)val|=p[ix+3];return val>>>0}
|
||||
class G5DECIMAGE{constructor(){this.iWidth=0;this.iHeight=0;this.iError=0;this.y=0;this.iVLCSize=0;this.iHLen=0;this.iPitch=0;this.u32Accum=0;this.ulBitOff=0;this.ulBits=0;this.pSrc=null;this.pBuf=null;this.pBufIndex=0;this.pCur=new Int16Array(MAX_IMAGE_FLIPS);this.pRef=new Int16Array(MAX_IMAGE_FLIPS)}}
|
||||
function g5_decode_init(pImage,iWidth,iHeight,pData,iDataSize){if(!pImage||iWidth<1||iHeight<1||!pData||iDataSize<1)return G5_INVALID_PARAMETER;pImage.iVLCSize=iDataSize;pImage.pSrc=pData;pImage.ulBitOff=0;pImage.y=0;pImage.ulBits=TIFFMOTOLONG(pData,0);pImage.iWidth=iWidth;pImage.iHeight=iHeight;return G5_SUCCESS}
|
||||
function G5DrawLine(pPage,pCurFlips,pOut){const xright=pPage.iWidth;let pCurIndex=0;const len=(xright+7)>>3;pOut.fill(255,0,len);let x=0;while(x<xright){const startX=pCurFlips[pCurIndex++],run=pCurFlips[pCurIndex++]-startX;if(startX>=xright||run<=0)break;let visibleX=Math.max(0,startX),visibleRun=Math.min(xright,startX+run)-visibleX;if(visibleRun>0){const startByte=visibleX>>3,endByte=(visibleX+visibleRun-1)>>3,lBit=255<<8-(visibleX&7)&255,rBit=255>>(visibleX+visibleRun&7);if(endByte===startByte)pOut[startByte]&=lBit|rBit;else{pOut[startByte]&=lBit;for(let i=startByte+1;i<endByte;i++)pOut[i]=0;if(endByte>startByte)pOut[endByte]&=rBit}}x=startX+run}}
|
||||
function Decode_Begin(pPage){const xsize=pPage.iWidth;for(let i=0;i<MAX_IMAGE_FLIPS-2;i++)pPage.pRef[i]=pPage.pCur[i]=xsize;pPage.pCur[MAX_IMAGE_FLIPS-2]=pPage.pRef[MAX_IMAGE_FLIPS-2]=32767;pPage.pCur[MAX_IMAGE_FLIPS-1]=pPage.pRef[MAX_IMAGE_FLIPS-1]=32767;pPage.pBuf=pPage.pSrc;pPage.pBufIndex=0;pPage.ulBits=TIFFMOTOLONG(pPage.pSrc,0);pPage.ulBitOff=0;pPage.iHLen=xsize>0?32-Math.clz32(xsize):0}
|
||||
function DecodeLine(pPage){let a0=-1,a0_p,b1,pCurIndex=0,pRefIndex=0;const pCur=pPage.pCur,pRef=pPage.pRef;let ulBits=pPage.ulBits,ulBitOff=pPage.ulBitOff,pBufIndex=pPage.pBufIndex;const pBuf=pPage.pBuf,xsize=pPage.iWidth,u32HLen=pPage.iHLen,u32HMask=(1<<u32HLen)-1;let tot_run,tot_run1;while(a0<xsize){if(pBufIndex+(ulBitOff>>3)>=pPage.iVLCSize&&ulBitOff>REGISTER_WIDTH-8)return pPage.iError=G5_DECODE_ERROR;if(ulBitOff>REGISTER_WIDTH-8){pBufIndex+=ulBitOff>>3;ulBitOff&=7;ulBits=TIFFMOTOLONG(pBuf,pBufIndex)}if((ulBits<<ulBitOff&2147483648)!==0){a0=pRef[pRefIndex++];pCur[pCurIndex++]=a0;ulBitOff++}else{const lBits=ulBits>>(REGISTER_WIDTH-8-ulBitOff)&254,sCode=code_table[lBits];ulBitOff+=code_table[lBits+1];switch(sCode){case 1:case 2:case 3:a0=pRef[pRefIndex]-sCode;pCur[pCurIndex++]=a0;if(pRefIndex==0)pRefIndex+=2;pRefIndex--;while(a0>=pRef[pRefIndex])pRefIndex+=2;break;case 17:case 18:case 19:a0=pRef[pRefIndex++];b1=a0;a0+=sCode&7;if(b1!==xsize&&a0<xsize)while(a0>=pRef[pRefIndex])pRefIndex+=2;if(a0>xsize)a0=xsize;pCur[pCurIndex++]=a0;break;case 32:if(ulBitOff>REGISTER_WIDTH-16){pBufIndex+=ulBitOff>>3;ulBitOff&=7;ulBits=TIFFMOTOLONG(pBuf,pBufIndex)}a0_p=Math.max(0,a0);const lBitsH=ulBits>>(REGISTER_WIDTH-2-ulBitOff)&3;ulBitOff+=2;switch(lBitsH){case HORIZ_SHORT_SHORT:tot_run=ulBits>>(REGISTER_WIDTH-3-ulBitOff)&7;ulBitOff+=3;tot_run1=ulBits>>(REGISTER_WIDTH-3-ulBitOff)&7;ulBitOff+=3;break;case HORIZ_SHORT_LONG:tot_run=ulBits>>(REGISTER_WIDTH-3-ulBitOff)&7;ulBitOff+=3;tot_run1=ulBits>>(REGISTER_WIDTH-u32HLen-ulBitOff)&u32HMask;ulBitOff+=u32HLen;break;case HORIZ_LONG_SHORT:tot_run=ulBits>>(REGISTER_WIDTH-u32HLen-ulBitOff)&u32HMask;ulBitOff+=u32HLen;tot_run1=ulBits>>(REGISTER_WIDTH-3-ulBitOff)&7;ulBitOff+=3;break;case HORIZ_LONG_LONG:tot_run=ulBits>>(REGISTER_WIDTH-u32HLen-ulBitOff)&u32HMask;ulBitOff+=u32HLen;if(ulBitOff>REGISTER_WIDTH-u32HLen&&u32HLen>0){pBufIndex+=ulBitOff>>3;ulBitOff&=7;ulBits=TIFFMOTOLONG(pBuf,pBufIndex)}tot_run1=ulBits>>(REGISTER_WIDTH-u32HLen-ulBitOff)&u32HMask;ulBitOff+=u32HLen;break}a0=a0_p+tot_run;pCur[pCurIndex++]=a0;a0+=tot_run1;if(a0<xsize)while(a0>=pRef[pRefIndex])pRefIndex+=2;pCur[pCurIndex++]=a0;break;case 48:pRefIndex++;a0=pRef[pRefIndex++];break;default:return pPage.iError=G5_DECODE_ERROR}}}pCur[pCurIndex++]=xsize;pCur[pCurIndex++]=xsize;pPage.ulBits=ulBits;pPage.ulBitOff=ulBitOff;pPage.pBufIndex=pBufIndex;return pPage.iError}
|
||||
function processG5(data,width,height){try{let decoder=new G5DECIMAGE,initResult=g5_decode_init(decoder,width,height,data,data.length);if(initResult!==G5_SUCCESS)throw new Error("G5 Init failed: "+initResult);Decode_Begin(decoder);let outputBuffer=new Uint8Array(height*((width+7)>>3));for(let y=0;y<height;y++){let lineBuffer=outputBuffer.subarray(y*((width+7)>>3),(y+1)*((width+7)>>3));decoder.y=y;let decodeResult=DecodeLine(decoder);if(decodeResult!==G5_SUCCESS)console.warn("G5 Decode error line "+y+": "+decoder.iError);G5DrawLine(decoder,decoder.pCur,lineBuffer);const temp=decoder.pRef;decoder.pRef=decoder.pCur;decoder.pCur=temp}return outputBuffer}catch(error){console.error("Error in processG5:",error.message);return new Uint8Array(0)}}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-wrapper">
|
||||
<div class="input-column">
|
||||
<div class="container">
|
||||
<h3>JSON Template Publisher</h3>
|
||||
<p class="info-text">Use this form to push JSON templates to an E-Paper Tag. Ensure your JSON is valid. Check syntax at <a href="https://jsonlint.com/" target="_blank">jsonlint.com</a>.<br>
|
||||
Documentation: <a href="https://github.com/OpenEPaperLink/OpenEPaperLink/wiki/Json-template" target="_blank">OpenEPaperLink JSON Template Wiki</a>
|
||||
</p>
|
||||
|
||||
<form id="jsonUploadForm" method="POST" action="/jsonupload">
|
||||
<div class="form-group flex-group">
|
||||
<div>
|
||||
<label for="macInput">MAC Address:</label>
|
||||
<input type="text" id="macInput" name="mac" pattern="[0-9a-fA-F]{12,16}" title="12 or 16 Hex characters" required>
|
||||
</div>
|
||||
<button type="button" id="refreshTagDbBtn" title="Reload Tag Database from AP">↻</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="macSelect">Available Tags (grouped by type):</label>
|
||||
<select id="macSelect" style="margin-bottom: 5px;"></select>
|
||||
<button type="button" id="clearMacHistoryBtn" title="Clear locally stored MAC history">Clear History</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="jsonInput">JSON String:</label>
|
||||
<textarea id="jsonInput" name="json">
|
||||
[
|
||||
{ "text": [5, 5, "Bahnschrift 20", "fonts/bahnschrift20", 1] },
|
||||
{ "box": [10, 30, 20, 20, 2] }
|
||||
]
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Upload JSON & Generate Preview">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-column">
|
||||
<div class="container">
|
||||
<div id="globalStatus" class="status-message" style="display:none;"></div>
|
||||
<div id="previewArea" style="display:none;">
|
||||
<h4>Image Preview</h4>
|
||||
<canvas id="previewCanvas"></canvas>
|
||||
<p id="previewStatus" class="status-message" style="display:none;"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const ge=id=>document.getElementById(id);
|
||||
const macInputEl=ge('macInput'),macSelectEl=ge('macSelect'),jsonInputEl=ge('jsonInput'),previewAreaEl=ge('previewArea'),previewCanvasEl=ge('previewCanvas'),previewStatusEl=ge('previewStatus'),jsonUploadFormEl=ge('jsonUploadForm'),clearMacHistoryBtnEl=ge('clearMacHistoryBtn'),globalStatusEl=ge('globalStatus'),refreshTagDbBtnEl=ge('refreshTagDbBtn');
|
||||
const MAX_HISTORY_ITEMS=10,BASE_SERVER_URL='';
|
||||
let localTagDB={},localTagTypes={},currentPreviewMac=null,currentPreviewHwType=null,currentPreviewVersion=null,currentPreviewTagTypeDef=null;
|
||||
let offscreenCanvas = document.createElement('canvas');
|
||||
let offscreenCtx = offscreenCanvas.getContext('2d');
|
||||
let socket;
|
||||
|
||||
if (typeof window.processZlib === 'undefined' && typeof pako !== 'undefined') {
|
||||
window.processZlib = function(rawDataFromDotRawFile) {
|
||||
try {
|
||||
const EXTERNAL_HEADER_SIZE = 4;
|
||||
if (rawDataFromDotRawFile.length <= EXTERNAL_HEADER_SIZE) {setStatus(previewStatusEl, `Zlib error: Raw data too short.`, 'error',true); return new Uint8ClampedArray(0);}
|
||||
const zlibStream = rawDataFromDotRawFile.subarray(EXTERNAL_HEADER_SIZE);
|
||||
const inflatedBuffer = pako.inflate(zlibStream);
|
||||
if (!inflatedBuffer || inflatedBuffer.length === 0) {setStatus(previewStatusEl, 'Zlib error: Inflation resulted in empty data.', 'error',true); return new Uint8ClampedArray(0);}
|
||||
const internalHeaderSize = inflatedBuffer[0];
|
||||
if (internalHeaderSize >= 0 && inflatedBuffer.length > internalHeaderSize) {return inflatedBuffer.subarray(internalHeaderSize);}
|
||||
else if (internalHeaderSize === 0) {return inflatedBuffer;}
|
||||
else {console.warn("Zlib: Unusual internal header. Length:", inflatedBuffer.length, "Header byte:", internalHeaderSize); return inflatedBuffer;}
|
||||
} catch (err) {
|
||||
let msg = (err && err.message) ? err.message : (typeof err === 'string' ? err : 'Unknown Zlib error');
|
||||
console.error('pako.inflate error:', err); setStatus(previewStatusEl, `Zlib error: ${msg}`, 'error',true); return new Uint8ClampedArray(0);
|
||||
}
|
||||
};
|
||||
} else if (typeof window.processZlib === 'undefined') {
|
||||
window.processZlib = function(data) {setStatus(previewStatusEl, "Zlib (pako) not loaded.",'warning',true); return data;};
|
||||
}
|
||||
|
||||
function setStatus(element, message, type = 'info', append = false) {
|
||||
element.classList.remove('error-message', 'success-message', 'warning-message', 'info-message');
|
||||
element.classList.add(`${type}-message`, 'info-message');
|
||||
|
||||
if (append) {
|
||||
let currentBaseHTML = element.innerHTML.split("<br>")[0];
|
||||
element.innerHTML = `${currentBaseHTML}<br>${message}`;
|
||||
} else {
|
||||
element.innerHTML = message;
|
||||
}
|
||||
element.style.display = message ? 'block' : 'none';
|
||||
}
|
||||
|
||||
|
||||
function connectWebSocket() {
|
||||
const protocol = location.protocol === "https:" ? "wss://" : "ws://";
|
||||
let basePath = location.pathname;
|
||||
if (basePath.includes('.')) { basePath = basePath.substring(0, basePath.lastIndexOf('/'));}
|
||||
if (!basePath.endsWith('/')) { basePath += '/';}
|
||||
if (basePath === '//') basePath = '/';
|
||||
const wsUrl = `${protocol}${location.host}${basePath}ws`;
|
||||
|
||||
socket = new WebSocket(wsUrl);
|
||||
socket.onopen = () => { console.log("WebSocket connected."); setStatus(globalStatusEl, "WebSocket connected.", 'success');};
|
||||
socket.onmessage = event => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.logMsg && typeof msg.logMsg === 'string') {
|
||||
const log = msg.logMsg;
|
||||
if (log.startsWith("Updating ") && log.length === ("Updating ".length + 16) ) {
|
||||
const macFromMsg = log.substring("Updating ".length).toUpperCase();
|
||||
if (macFromMsg === currentPreviewMac) {
|
||||
setStatus(previewStatusEl, `Server is updating tag ${macFromMsg}. Waiting for .pending file notification...`, 'info', false);
|
||||
}
|
||||
} else if (log.startsWith("new image: /current/") && log.endsWith(".pending")) {
|
||||
const pendingFileWithPath = log.substring("new image: ".length);
|
||||
const pendingFilename = pendingFileWithPath.substring(pendingFileWithPath.lastIndexOf('/') + 1);
|
||||
const macInFilename = pendingFilename.substring(0, pendingFilename.indexOf('_'));
|
||||
|
||||
if (macInFilename.toUpperCase() === currentPreviewMac && currentPreviewTagTypeDef) {
|
||||
console.log(`.pending file reported for ${currentPreviewMac}: ${pendingFilename}`);
|
||||
const pendingImagePath = `${BASE_SERVER_URL}/current/${pendingFilename}`;
|
||||
setStatus(previewStatusEl, `Loading pending image: ${pendingFilename}...`, 'info', false);
|
||||
loadAndRenderImage(pendingImagePath, currentPreviewMac, currentPreviewHwType, currentPreviewVersion, currentPreviewTagTypeDef);
|
||||
}
|
||||
} else if (log === "new image is the same as current image. not updating tag.") {
|
||||
if (currentPreviewMac && currentPreviewTagTypeDef) {
|
||||
console.log("Image is the same. Loading .raw for " + currentPreviewMac);
|
||||
setStatus(previewStatusEl, `Image is same as current. Loading existing .raw file for ${currentPreviewMac}...`, 'info', false);
|
||||
const tagData = localTagDB[currentPreviewMac];
|
||||
if (tagData) {
|
||||
const cacheTag = tagData.hash !== '00000000000000000000000000000000' ? tagData.hash : Date.now();
|
||||
let imagePath = '';
|
||||
if(tagData.isexternal && tagData.apip && tagData.apip !== '0.0.0.0'){imagePath = `http://${tagData.apip}/current/${currentPreviewMac}.raw?${cacheTag}`}
|
||||
else{imagePath = `${BASE_SERVER_URL}/current/${currentPreviewMac}.raw?${cacheTag}`}
|
||||
loadAndRenderImage(imagePath, currentPreviewMac, currentPreviewHwType, currentPreviewVersion, currentPreviewTagTypeDef);
|
||||
} else {
|
||||
setStatus(previewStatusEl, `Cannot load .raw for ${currentPreviewMac}: Tag data not found.`, 'error', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) { console.error("Error processing WebSocket message:", e, event.data); }
|
||||
};
|
||||
socket.onclose = event => { console.log("WebSocket disconnected. Code:", event.code); setStatus(globalStatusEl, "WebSocket disconnected. Reconnecting in 5s...", 'warning'); setTimeout(connectWebSocket, 5000);};
|
||||
socket.onerror = error => { console.error("WebSocket error:", error); setStatus(globalStatusEl, "WebSocket connection error.", 'error');};
|
||||
}
|
||||
|
||||
async function fetchTagDB(pos=0){if(pos===0)localTagDB={};setStatus(globalStatusEl,'Loading Tag Database...','info');try{const response=await fetch(`${BASE_SERVER_URL}/get_db?pos=${pos}`);if(!response.ok)throw new Error(`Failed to load Tag DB: ${response.status}`);const data=await response.json();(data.tags||[]).forEach(tag=>{localTagDB[tag.mac.toUpperCase()]={hwType:tag.hwType,ver:tag.ver,alias:tag.alias,hash:tag.hash,isexternal:tag.isexternal||false,apip:tag.apip||'0.0.0.0'}});if(data.continu&&data.continu>pos){await fetchTagDB(data.continu)}else{setStatus(globalStatusEl,`Tag DB loaded. ${Object.keys(localTagDB).length} tags. Refreshing types...`,'success');await refreshTagTypeDefinitionsForDropdown();populateMacDropdown()}}catch(error){console.error("Error fetchTagDB:",error);setStatus(globalStatusEl,`Error Tag DB: ${error.message}`,'error')}}
|
||||
async function getTagTypeDefinition(hwType){if(localTagTypes[hwType]&&!localTagTypes[hwType].busy)return localTagTypes[hwType];if(localTagTypes[hwType]?.busy){await new Promise(resolve=>{const interval=setInterval(()=>{if(!localTagTypes[hwType]?.busy){clearInterval(interval);resolve(localTagTypes[hwType])}},100)});return localTagTypes[hwType]}localTagTypes[hwType]={busy:true};try{let response;try{response=await fetch(`${BASE_SERVER_URL}/tagtypes/${Number(hwType).toString(16).padStart(2,'0').toUpperCase()}.json`)}catch(e){}if(!response||!response.ok){const repo='OpenEPaperLink/OpenEPaperLink',ghUrl=`https://raw.githubusercontent.com/${repo}/master/resources/tagtypes/${Number(hwType).toString(16).padStart(2,'0').toUpperCase()}.json`;response=await fetch(ghUrl)}if(!response.ok)throw new Error(`Def for ${hwType} not found (Status: ${response.status})`);const jsonData=await response.json();const definition={name:jsonData.name,width:parseInt(jsonData.width),height:parseInt(jsonData.height),bpp:parseInt(jsonData.bpp),rotatebuffer:jsonData.rotatebuffer||0,colortable:Object.values(jsonData.perceptual??jsonData.colortable??[]),zlib:parseInt(jsonData.zlib_compression||"0",16),g5:parseInt(jsonData.g5_compression||"0",16),busy:false};localTagTypes[hwType]=definition;localStorage.setItem("localTagTypesCache",JSON.stringify(localTagTypes));return definition}catch(error){console.error(`Error getTagTypeDef ${hwType}:`,error);localTagTypes[hwType]={busy:false,name:`Unknown (${hwType})`,width:0,height:0,bpp:0,colortable:[]};return localTagTypes[hwType]}}
|
||||
function loadCachedTagTypeDefinitions(){try{const cached=localStorage.getItem("localTagTypesCache");if(cached)localTagTypes=JSON.parse(cached)}catch(e){console.warn("Error loading Tag Type Defs from cache:",e)}}
|
||||
async function refreshTagTypeDefinitionsForDropdown(){const hwTypesInDb=new Set(Object.values(localTagDB).map(tag=>tag.hwType));for(const hwType of hwTypesInDb){if(!localTagTypes[hwType]||localTagTypes[hwType].name?.startsWith("Unknown")){await getTagTypeDefinition(hwType)}}}
|
||||
function populateMacDropdown(){macSelectEl.innerHTML='<option value="">Select a Tag...</option>';const groupedByHwType={};for(const mac in localTagDB){const tag=localTagDB[mac];if(!groupedByHwType[tag.hwType])groupedByHwType[tag.hwType]=[];groupedByHwType[tag.hwType].push({mac,alias:tag.alias})}const sortedHwTypes=Object.keys(groupedByHwType).sort((a,b)=>{const nameA=localTagTypes[a]?.name||`Type ${a}`;const nameB=localTagTypes[b]?.name||`Type ${b}`;return nameA.localeCompare(nameB)});for(const hwType of sortedHwTypes){const group=document.createElement('optgroup');const typeDef=localTagTypes[hwType];group.label=typeDef?.name?`${typeDef.name} (${typeDef.width}x${typeDef.height}, Type ${hwType})`:`Type ${hwType}`;groupedByHwType[hwType].sort((a,b)=>(a.alias||a.mac).localeCompare(b.alias||b.mac));groupedByHwType[hwType].forEach(tag=>{const option=document.createElement('option');option.value=tag.mac;option.textContent=tag.alias?`${tag.alias} (${tag.mac})`:tag.mac;group.appendChild(option)});macSelectEl.appendChild(group)}}
|
||||
function saveToMacHistory(mac){if(!mac||!/^[0-9a-fA-F]{12,16}$/.test(mac))return;let history=JSON.parse(localStorage.getItem('macStorageHistory'))||[];const upperMac=mac.toUpperCase();history=history.filter(item=>item.toUpperCase()!==upperMac);history.unshift(upperMac);if(history.length>MAX_HISTORY_ITEMS)history=history.slice(0,MAX_HISTORY_ITEMS);localStorage.setItem('macStorageHistory',JSON.stringify(history))}
|
||||
macSelectEl.addEventListener('change',()=>{if(macSelectEl.value){macInputEl.value=macSelectEl.value;const tagInfo=localTagDB[macSelectEl.value.toUpperCase()];if(tagInfo)setStatus(globalStatusEl,`Selected: ${tagInfo.alias||macSelectEl.value}, HW: ${tagInfo.hwType}, FW: ${tagInfo.ver}`,'info')}});
|
||||
macInputEl.addEventListener('change',()=>{const mac=macInputEl.value.toUpperCase();const tagInfo=localTagDB[mac];if(tagInfo)setStatus(globalStatusEl,`Tag: ${tagInfo.alias||mac}, HW: ${tagInfo.hwType}, FW: ${tagInfo.ver}`,'info');else if(mac.length===12||mac.length===16)setStatus(globalStatusEl,`Details for ${mac} not in local DB.`,'warning');else setStatus(globalStatusEl,'','info');macSelectEl.value=mac});
|
||||
clearMacHistoryBtnEl.addEventListener('click',()=>{if(confirm("Clear MAC history from browser storage?")){localStorage.removeItem('macStorageHistory');macInputEl.value='';setStatus(globalStatusEl,'Local MAC history cleared.','info')}});
|
||||
function loadLastMacUsed(){const lastMac=localStorage.getItem('lastMacUsed');if(lastMac)macInputEl.value=lastMac}
|
||||
function saveLastMacUsed(mac){if(mac&&/^[0-9a-fA-F]{12,16}$/.test(mac)){localStorage.setItem('lastMacUsed',mac.toUpperCase());saveToMacHistory(mac.toUpperCase())}}
|
||||
|
||||
jsonUploadFormEl.addEventListener('submit',async event=>{event.preventDefault();const mac=macInputEl.value.trim().toUpperCase();if(!mac||!/^[0-9a-fA-F]{12,16}$/.test(mac)){alert("Please enter a valid MAC address.");return}saveLastMacUsed(mac);let tagInfo=localTagDB[mac];if(!tagInfo){setStatus(globalStatusEl,`Info for MAC ${mac} not in local DB. Reloading...`,'warning');await fetchTagDB();tagInfo=localTagDB[mac];if(!tagInfo){setStatus(previewStatusEl,`Could not load hardware info for MAC ${mac}.`,'error',false);previewAreaEl.style.display='block';previewCanvasEl.style.display='none';return}}currentPreviewMac=mac;currentPreviewHwType=tagInfo.hwType;currentPreviewVersion=tagInfo.ver;currentPreviewTagTypeDef=await getTagTypeDefinition(currentPreviewHwType);if(!currentPreviewTagTypeDef||currentPreviewTagTypeDef.width===0){setStatus(previewStatusEl,`Could not load Tag Type Definition for hwType ${currentPreviewHwType}.`,'error',false);previewAreaEl.style.display='block';previewCanvasEl.style.display='none';return}previewAreaEl.style.display='block';setStatus(previewStatusEl,'Sending JSON... Waiting for image update status via WebSocket...','info',false);previewCanvasEl.style.display='none';if(offscreenCanvas)offscreenCtx.clearRect(0,0,offscreenCanvas.width,offscreenCanvas.height);else{const ctx=previewCanvasEl.getContext('2d');if(ctx)ctx.clearRect(0,0,previewCanvasEl.width,previewCanvasEl.height)}try{const formData=new FormData(jsonUploadFormEl);formData.set('mac',mac);const response=await fetch(jsonUploadFormEl.action,{method:'POST',body:formData});if(!response.ok){const errorText=await response.text();throw new Error(`JSON Upload Error: ${response.status} ${errorText}`)}setStatus(previewStatusEl,'JSON sent. Waiting for image update notification via WebSocket...','success', false);
|
||||
}catch(error){console.error("Error JSON submission:",error);setStatus(previewStatusEl,`Error: ${error.message}`,'error',false);}});
|
||||
|
||||
refreshTagDbBtnEl.addEventListener('click',()=>fetchTagDB());
|
||||
|
||||
async function loadAndRenderImage(path,mac,hwType,ver,tagTypeDef){
|
||||
setStatus(previewStatusEl,`Loading image: ${path.substring(path.lastIndexOf('/')+1)}...`,'info', false);
|
||||
|
||||
if(!tagTypeDef||tagTypeDef.width===0){setStatus(previewStatusEl,`Error: Tag Type Def for hwType '${hwType}' invalid.`,'error', false);return false}
|
||||
|
||||
let offscreenDrawWidth = tagTypeDef.width; let offscreenDrawHeight = tagTypeDef.height;
|
||||
const rotateBuffer = tagTypeDef.rotatebuffer || 0;
|
||||
if (rotateBuffer % 2 !== 0) {[offscreenDrawWidth, offscreenDrawHeight] = [tagTypeDef.height, tagTypeDef.width];}
|
||||
|
||||
if (offscreenCanvas.width !== offscreenDrawWidth || offscreenCanvas.height !== offscreenDrawHeight) {
|
||||
offscreenCanvas.width = offscreenDrawWidth; offscreenCanvas.height = offscreenDrawHeight;
|
||||
}
|
||||
offscreenCtx.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
|
||||
|
||||
try{const response=await fetch(path);
|
||||
if(!response.ok){throw new Error(`Image not found or server error: ${response.status} on ${path}`);}
|
||||
const buffer=await response.arrayBuffer();let data=new Uint8ClampedArray(buffer);
|
||||
if(data.length===0){setStatus(previewStatusEl,"Received image data empty.",'error', true);previewCanvasEl.style.display='none';return false}
|
||||
|
||||
let originalDataLength = data.length;
|
||||
if(tagTypeDef.zlib>0&&ver>=tagTypeDef.zlib){
|
||||
let decompressedZlib = window.processZlib(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
||||
if (decompressedZlib && decompressedZlib.length > 0 && decompressedZlib.length !== originalDataLength) {
|
||||
data = new Uint8ClampedArray(decompressedZlib.buffer, decompressedZlib.byteOffset, decompressedZlib.byteLength);
|
||||
} else if (decompressedZlib && decompressedZlib.length === originalDataLength && originalDataLength > 0) {
|
||||
} else { setStatus(previewStatusEl,"Zlib error: Empty result.",'error', true); return false; }
|
||||
}
|
||||
|
||||
originalDataLength = data.length;
|
||||
if(data.length>0&&tagTypeDef.g5>0&&ver>=tagTypeDef.g5){
|
||||
const headerSize=data[0];
|
||||
if (data.length > headerSize) {
|
||||
let bufw=(data[2]<<8)|data[1],bufh=(data[4]<<8)|data[3];
|
||||
if((bufw==tagTypeDef.width||bufw==tagTypeDef.height)&&(bufh==tagTypeDef.width||bufh==tagTypeDef.height)&&(data[5]<=3)){
|
||||
if(data[5]==2)bufh*=2; let g5Stream = data.subarray(headerSize);
|
||||
let decompressedG5 = window.processG5(g5Stream, bufw, bufh);
|
||||
if (decompressedG5 && decompressedG5.length > 0 && decompressedG5.length !== originalDataLength) {
|
||||
data = new Uint8ClampedArray(decompressedG5.buffer, decompressedG5.byteOffset, decompressedG5.byteLength);
|
||||
} else if (decompressedG5 && decompressedG5.length === originalDataLength && originalDataLength > 0) {
|
||||
} else { setStatus(previewStatusEl,"G5 error: Empty result.",'error', true); return false; }
|
||||
} else { }
|
||||
} else { }
|
||||
}
|
||||
|
||||
if(data.length===0){setStatus(previewStatusEl,"Image data empty post-decompression.",'error', true);previewCanvasEl.style.display='none';return false}
|
||||
|
||||
const offscreenImageData = offscreenCtx.createImageData(offscreenCanvas.width, offscreenCanvas.height);
|
||||
const bpp=tagTypeDef.bpp,colorTable=tagTypeDef.colortable;
|
||||
if(!colorTable||colorTable.length===0){setStatus(previewStatusEl,`Error: No color table for hwType ${hwType}.`,'error', true);return false}
|
||||
if(bpp==16){const is16Bit=data.length===offscreenCanvas.width*offscreenCanvas.height*2;for(let i=0;i<Math.min(offscreenCanvas.width*offscreenCanvas.height,data.length/(is16Bit?2:1));i++){const dIdx=is16Bit?i*2:i,rgb=is16Bit?(data[dIdx]<<8)|data[dIdx+1]:data[dIdx];offscreenImageData.data[i*4]=is16Bit?((rgb>>11)&31)<<3:(((rgb>>5)&7)<<5)*1.13;offscreenImageData.data[i*4+1]=is16Bit?((rgb>>5)&63)<<2:(((rgb>>2)&7)<<5)*1.13;offscreenImageData.data[i*4+2]=is16Bit?(rgb&31)<<3:((rgb&3)<<6)*1.3;offscreenImageData.data[i*4+3]=255}}
|
||||
else if([3,4].includes(bpp)){let pxIdx=0,bitOff=0,totalPx=offscreenCanvas.width*offscreenCanvas.height;while(bitOff<data.length*8&&pxIdx<totalPx){let byteIdx=bitOff>>3,startBit=bitOff&7,pxVal=0;if(startBit+bpp<=8)pxVal=(data[byteIdx]>>(8-startBit-bpp))&((1<<bpp)-1);else{let bitsFirst=8-startBit;pxVal=(data[byteIdx]&((1<<bitsFirst)-1))<<(bpp-bitsFirst);if(byteIdx+1<data.length)pxVal|=data[byteIdx+1]>>(8-(bpp-bitsFirst))}if(pxVal<colorTable.length){let c=colorTable[pxVal];offscreenImageData.data[pxIdx*4]=c[0];offscreenImageData.data[pxIdx*4+1]=c[1];offscreenImageData.data[pxIdx*4+2]=c[2];offscreenImageData.data[pxIdx*4+3]=255}else{offscreenImageData.data[pxIdx*4]=255;offscreenImageData.data[pxIdx*4+1]=0;offscreenImageData.data[pxIdx*4+2]=0;offscreenImageData.data[pxIdx*4+3]=255}pxIdx++;bitOff+=bpp}}
|
||||
else{const offsetR=(bpp>=2&&data.length>=(offscreenCanvas.width*offscreenCanvas.height/8)*2)?offscreenCanvas.width*offscreenCanvas.height/8:0;let pxVal=0,totalPx=offscreenCanvas.width*offscreenCanvas.height,currPxIdx=0;let bytesIter=offsetR?offsetR:Math.ceil(totalPx/8);for(let i=0;i<bytesIter&&i<data.length;i++){for(let j=0;j<8;j++){if(currPxIdx>=totalPx)break;if(offsetR&&(i+offsetR>=data.length))pxVal=(data[i]&(1<<(7-j)))?1:0;else if(offsetR)pxVal=((data[i]&(1<<(7-j)))?1:0)|(((data[i+offsetR]&(1<<(7-j)))?1:0)<<1);else pxVal=(data[i]&(1<<(7-j)))?1:0;if(pxVal<colorTable.length){offscreenImageData.data[currPxIdx*4]=colorTable[pxVal][0];offscreenImageData.data[currPxIdx*4+1]=colorTable[pxVal][1];offscreenImageData.data[currPxIdx*4+2]=colorTable[pxVal][2];offscreenImageData.data[currPxIdx*4+3]=255}else{offscreenImageData.data[currPxIdx*4]=0;offscreenImageData.data[currPxIdx*4+1]=255;offscreenImageData.data[currPxIdx*4+2]=0;offscreenImageData.data[currPxIdx*4+3]=255}currPxIdx++}if(currPxIdx>=totalPx)break}}
|
||||
offscreenCtx.putImageData(offscreenImageData,0,0);
|
||||
|
||||
previewCanvasEl.width = offscreenCanvas.width;
|
||||
previewCanvasEl.height = offscreenCanvas.height;
|
||||
previewCanvasEl.style.transform = (rotateBuffer >= 2) ? 'rotate(180deg)' : 'none';
|
||||
|
||||
const visibleCtx=previewCanvasEl.getContext('2d');
|
||||
visibleCtx.drawImage(offscreenCanvas,0,0);
|
||||
previewCanvasEl.style.display='block';
|
||||
|
||||
setStatus(previewStatusEl,`Image ${path.substring(path.lastIndexOf('/')+1)} rendered.`,'success', true);
|
||||
return true;
|
||||
}catch(error){
|
||||
console.error("Error loading/rendering image:",error);
|
||||
setStatus(previewStatusEl,`Error loading ${path.substring(path.lastIndexOf('/')+1)}: ${error.message}`,'error', true);
|
||||
previewCanvasEl.style.display='none';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded',async()=>{loadCachedTagTypeDefinitions();await fetchTagDB();loadLastMacUsed(); connectWebSocket();});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,112 @@
|
||||
/* CSS Variables for Theming */
|
||||
:root {
|
||||
--color-background: #e4e4e0;
|
||||
--color-text: #000000;
|
||||
--color-module-bg: #ffffff;
|
||||
--color-text-light: #444;
|
||||
--color-text-error: red;
|
||||
--color-header-bg: #646260;
|
||||
--color-header-text: #ffffff;
|
||||
--color-nav-bg: #ffffff;
|
||||
--color-tab-bg: #f2f2f2;
|
||||
--color-tab-hover-bg: #ddd;
|
||||
--color-tab-active-bg: #ccc;
|
||||
--color-tab-border: #ccc;
|
||||
--color-card-bg: #ffffff;
|
||||
--color-card-border: #cccccc;
|
||||
--color-card-hover-shadow: rgba(0, 0, 0, 0.63);
|
||||
--color-dialog-bg: #f0e6d3;
|
||||
--color-button-bg: #ccc;
|
||||
--color-button-text: black;
|
||||
--color-button-hover-bg: #aaaaaa;
|
||||
--color-input-bg: #ffffff;
|
||||
--color-input-text: #000000;
|
||||
--color-input-border: #cccccc;
|
||||
--color-console-bg: black;
|
||||
--color-console-text: white;
|
||||
--color-console-mono-bg: #666;
|
||||
--color-console-mono-text: #ccc;
|
||||
--color-console-quote-text: white;
|
||||
--color-tag-pending-anim-bg: #d4d4f5;
|
||||
--color-tag-group-bg: #6d6e6e;
|
||||
--color-tag-group-text: white;
|
||||
--color-tag-group-border: #c0c0c0;
|
||||
--color-context-menu-bg: white;
|
||||
--color-context-menu-border: gray;
|
||||
--color-context-menu-hover-bg: #e0e0e0;
|
||||
--color-link: #0000ee;
|
||||
--color-readonly-bg: #ccc;
|
||||
--color-tag-deepsleep-bg: #e4e4e0;
|
||||
--color-tag-boot-bg: #b0d0b0;
|
||||
--color-tag-wakeup-bg: #c8f1bb;
|
||||
--color-tag-scan-bg: #c0c0d0;
|
||||
--color-tag-reset-bg: #d0a0a0;
|
||||
--color-tag-failed-ota-bg: #f0a0a0;
|
||||
--color-tag-timeout-bg: #e0e0a0;
|
||||
--color-log-new-bg-start: rgba(255, 255, 204, 1);
|
||||
--color-log-new-bg-mid: rgba(255, 255, 204, 0.5);
|
||||
--color-log-new-bg-end: rgba(255, 255, 204, 0);
|
||||
}
|
||||
|
||||
body.dark-mode {
|
||||
--color-background: #1c1c1e;
|
||||
--color-text: #f0f0f0;
|
||||
--color-module-bg: #3a3a3c;
|
||||
--color-text-light: #cccccc;
|
||||
--color-text-error: #ff8a8a;
|
||||
--color-header-bg: #2a2a2d;
|
||||
--color-header-text: #f0f0f0;
|
||||
--color-nav-bg: #1f1f22;
|
||||
--color-tab-bg: #3a3a3c;
|
||||
--color-tab-hover-bg: #4a4a4c;
|
||||
--color-tab-active-bg: #5a5a5c;
|
||||
--color-tab-border: #5a5a5c;
|
||||
--color-card-bg: #2c2c2e;
|
||||
--color-card-border: #444444;
|
||||
--color-card-hover-shadow: rgba(255, 255, 255, 0.2);
|
||||
--color-dialog-bg: #3a3a3c;
|
||||
--color-button-bg: #5a5a5c;
|
||||
--color-button-text: #f0f0f0;
|
||||
--color-button-hover-bg: #6a6a6c;
|
||||
--color-input-bg: #3a3a3c;
|
||||
--color-input-text: #f0f0f0;
|
||||
--color-input-border: #555555;
|
||||
--color-console-bg: #3a3a3c;
|
||||
--color-console-text: #f0f0f0;
|
||||
--color-console-mono-bg: #444;
|
||||
--color-console-mono-text: #ddd;
|
||||
--color-console-quote-text: #ffffff;
|
||||
--color-tag-pending-anim-bg: #3c3c5c;
|
||||
--color-tag-group-bg: #4a4a4c;
|
||||
--color-tag-group-text: #f0f0f0;
|
||||
--color-tag-group-border: #555555;
|
||||
--color-context-menu-bg: #2c2c2e;
|
||||
--color-context-menu-border: #555;
|
||||
--color-context-menu-hover-bg: #4a4a4c;
|
||||
--color-link: #58a6ff;
|
||||
--color-readonly-bg: #4a4a4c;
|
||||
--color-tag-deepsleep-bg: #3a3a3c;
|
||||
--color-tag-boot-bg: #3a5c3a;
|
||||
--color-tag-wakeup-bg: #4a6c4a;
|
||||
--color-tag-scan-bg: #4c4c5c;
|
||||
--color-tag-reset-bg: #6c4c4c;
|
||||
--color-tag-failed-ota-bg: #7c4c4c;
|
||||
--color-tag-timeout-bg: #6c6c4c;
|
||||
--color-log-new-bg-start: rgba(88, 166, 255, 0.3);
|
||||
--color-log-new-bg-mid: rgba(88, 166, 255, 0.15);
|
||||
--color-log-new-bg-end: rgba(88, 166, 255, 0);
|
||||
}
|
||||
|
||||
/* Tag State Classes */
|
||||
.tagcard.state-deepsleep { background-color: var(--color-tag-deepsleep-bg); }
|
||||
.tagcard.state-boot { background-color: var(--color-tag-boot-bg); }
|
||||
.tagcard.state-wakeup { background-color: var(--color-tag-wakeup-bg); }
|
||||
.tagcard.state-scan { background-color: var(--color-tag-scan-bg); }
|
||||
.tagcard.state-reset { background-color: var(--color-tag-reset-bg); }
|
||||
.tagcard.state-failed-ota { background-color: var(--color-tag-failed-ota-bg); }
|
||||
.tagcard.state-timeout { background-color: var(--color-tag-timeout-bg); }
|
||||
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -16,11 +125,12 @@ body {
|
||||
font-size: 12px;
|
||||
font-family: Helvetica, Arial, Verdana, sans-serif;
|
||||
line-height: 1.5;
|
||||
background-color: #e4e4e0;
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #646260;
|
||||
background-color: var(--color-header-bg);
|
||||
z-index: 999;
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
@@ -35,7 +145,7 @@ nav>div {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
background-color: var(--color-nav-bg);
|
||||
}
|
||||
|
||||
nav label {
|
||||
@@ -50,11 +160,13 @@ footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
background-color: var(--color-nav-bg);
|
||||
border-top: 1px solid var(--color-card-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
#sysinfo {
|
||||
float: right;
|
||||
}
|
||||
footer #sysinfo {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.logo {
|
||||
@@ -63,12 +175,13 @@ footer {
|
||||
text-indent: 12px;
|
||||
overflow: hidden;
|
||||
font-size: 2.5em;
|
||||
color: white;
|
||||
color: var(--color-header-text);
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding-bottom: 10px;
|
||||
font-size: 1.5em;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* tabs */
|
||||
@@ -79,7 +192,8 @@ h3 {
|
||||
}
|
||||
|
||||
.tablinks {
|
||||
background-color: #f2f2f2;
|
||||
background-color: var(--color-tab-bg);
|
||||
color: var(--color-text);
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
@@ -87,12 +201,12 @@ h3 {
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: #ddd;
|
||||
background-color: var(--color-tab-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-container .active {
|
||||
background-color: #ccc;
|
||||
background-color: var(--color-tab-active-bg);
|
||||
margin-top: 10px;
|
||||
margin-bottom: -10px;
|
||||
padding-bottom: 0px;
|
||||
@@ -100,7 +214,7 @@ h3 {
|
||||
|
||||
.tabcontent {
|
||||
display: none;
|
||||
border-top: 1px solid #ccc;
|
||||
border-top: 1px solid var(--color-tab-border);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,11 +223,11 @@ label {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
padding: 4px 20px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.container {
|
||||
padding-bottom: 20px;
|
||||
;
|
||||
}
|
||||
|
||||
#hometab {
|
||||
@@ -121,9 +235,10 @@ label {
|
||||
|
||||
& table {
|
||||
margin: 20px;
|
||||
background: #fff;
|
||||
background: var(--color-card-bg);
|
||||
padding: 5px 20px;
|
||||
border-spacing: 0px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
& td {
|
||||
@@ -136,7 +251,7 @@ label {
|
||||
}
|
||||
|
||||
& tr:hover {
|
||||
background-color: #ccc;
|
||||
background-color: var(--color-tab-active-bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -167,14 +282,14 @@ label {
|
||||
}
|
||||
|
||||
.tabheader {
|
||||
background-color: white;
|
||||
background-color: var(--color-nav-bg);
|
||||
padding: 5px 10px;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
|
||||
#filterOptions {
|
||||
background-color: white;
|
||||
background-color: var(--color-nav-bg);
|
||||
max-height: 0;
|
||||
padding: 0px 10px;
|
||||
overflow: hidden;
|
||||
@@ -205,6 +320,9 @@ label {
|
||||
& p {
|
||||
padding: 3px;
|
||||
}
|
||||
& a:not(#downloadDBbutton):not(.wifibutton) {
|
||||
color: var(--color-link);
|
||||
}
|
||||
}
|
||||
|
||||
#updatetab, #flashtab {
|
||||
@@ -226,7 +344,7 @@ label {
|
||||
margin: 0px 5px;
|
||||
}
|
||||
& input:read-only {
|
||||
background-color: #ccc;
|
||||
background-color: var(--color-readonly-bg);
|
||||
}
|
||||
& .warning {
|
||||
padding: 5px;
|
||||
@@ -243,10 +361,15 @@ label {
|
||||
}
|
||||
|
||||
.apcard {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
background-color: var(--color-card-bg);
|
||||
border: 1px solid var(--color-card-border);
|
||||
padding: 10px;
|
||||
width: 300px;
|
||||
color: var(--color-text);
|
||||
|
||||
& .apip a {
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
& .apalias {
|
||||
font-size: 1.5em;
|
||||
@@ -278,15 +401,15 @@ label {
|
||||
padding: 5px 10px;
|
||||
margin-bottom: 0px;
|
||||
margin-top: -1px;
|
||||
background-color: #ccc;
|
||||
background-color: var(--color-button-bg);
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
color: var(--color-button-text);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background-color: #aaa;
|
||||
background-color: var(--color-button-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,18 +419,19 @@ label {
|
||||
#uploadButton,
|
||||
.wifibutton {
|
||||
padding: 4px 5px;
|
||||
background-color: #ccc;
|
||||
background-color: var(--color-button-bg);
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
color: var(--color-button-text);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
width: 120px;
|
||||
margin: 2px 5px 2px 20px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
transition: none;
|
||||
|
||||
&:hover {
|
||||
background-color: #aaa;
|
||||
background-color: var(--color-button-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,10 +451,13 @@ input[type="submit"],
|
||||
button {
|
||||
appearance: none;
|
||||
border-radius: 0;
|
||||
color: var(--color-input-text);
|
||||
}
|
||||
|
||||
input {
|
||||
border: solid 1px #cccccc;
|
||||
input, textarea, select {
|
||||
border: solid 1px var(--color-input-border);
|
||||
background-color: var(--color-input-bg);
|
||||
color: var(--color-input-text);
|
||||
padding: 4px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
@@ -340,18 +467,21 @@ button {
|
||||
border: 0px;
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
background-color: #ccc;
|
||||
background-color: var(--color-button-bg);
|
||||
color: var(--color-button-text);
|
||||
}
|
||||
|
||||
input[type=button]:hover,
|
||||
button:hover {
|
||||
background-color: #aaaaaa;
|
||||
background-color: var(--color-button-hover-bg);
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
padding: 0px;
|
||||
margin-left: 20px;
|
||||
width: 200px;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
@@ -361,7 +491,6 @@ input[type=checkbox] {
|
||||
select {
|
||||
padding: 3px 4px;
|
||||
border-radius: 0px;
|
||||
border: solid 1px #cccccc;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
@@ -375,7 +504,7 @@ select {
|
||||
padding: 15px;
|
||||
background-color: #f0e6d3;
|
||||
z-index: 999;
|
||||
box-shadow: 7px 10px 52px -19px rgba(0, 0, 0, 0.63);
|
||||
box-shadow: 7px 10px 52px -19px var(--color-card-hover-shadow);
|
||||
overflow: auto;
|
||||
max-height: calc(100vh - 75px);
|
||||
}
|
||||
@@ -384,11 +513,12 @@ select {
|
||||
margin: auto;
|
||||
min-width: 380px;
|
||||
padding: 15px;
|
||||
background-color: #f0e6d3;
|
||||
background-color: var(--color-dialog-bg);
|
||||
z-index: 999;
|
||||
box-shadow: 7px 10px 52px -19px rgba(0, 0, 0, 0.63);
|
||||
box-shadow: 7px 10px 52px -19px var(--color-card-hover-shadow);
|
||||
overflow: auto;
|
||||
max-height: calc(100vh - 75px);
|
||||
color: var(--color-text);
|
||||
|
||||
& label {
|
||||
width: 150px;
|
||||
@@ -414,7 +544,7 @@ select {
|
||||
|
||||
#configbox input,
|
||||
#apconfigbox input {
|
||||
border: solid 1px #cccccc;
|
||||
border: solid 1px var(--color-input-border);
|
||||
}
|
||||
|
||||
#configbox input[type=number] {
|
||||
@@ -475,7 +605,7 @@ select {
|
||||
|
||||
.closebtn,
|
||||
.closebtn2 {
|
||||
border: 1px solid black;
|
||||
border: 1px solid var(--color-text);
|
||||
float: right;
|
||||
width: 19px;
|
||||
height: 20px;
|
||||
@@ -483,6 +613,7 @@ select {
|
||||
text-align: center;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
#logtab {
|
||||
@@ -523,13 +654,14 @@ select {
|
||||
min-height: 170px;
|
||||
margin: 3px;
|
||||
padding: 4px 5px 5px 8px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #cccccc;
|
||||
transition: box-shadow 0.3s ease;
|
||||
background-color: var(--color-card-bg);
|
||||
border: 1px solid var(--color-card-border);
|
||||
transition: box-shadow 0.3s ease, background-color 0.3s ease;
|
||||
color: var(--color-text);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
box-shadow: 7px 10px 52px -19px rgba(0, 0, 0, 0.63);
|
||||
box-shadow: 7px 10px 52px -19px var(--color-card-hover-shadow);
|
||||
filter: brightness(1.02);
|
||||
}
|
||||
}
|
||||
@@ -549,10 +681,10 @@ select {
|
||||
}
|
||||
|
||||
.taggroup {
|
||||
border: 1px solid #c0c0c0;
|
||||
border: 1px solid var(--color-tag-group-border);
|
||||
width: 100%;
|
||||
background-color: #6d6e6e;
|
||||
color: white;
|
||||
background-color: var(--color-tag-group-bg);
|
||||
color: var(--color-tag-group-text);
|
||||
padding: 5px;
|
||||
margin: 0px 3px;
|
||||
}
|
||||
@@ -564,7 +696,7 @@ select {
|
||||
.currimg img,
|
||||
.currimg canvas {
|
||||
max-width: 50px;
|
||||
border: 1px solid #c0c0c0;
|
||||
border: 1px solid var(--color-tag-group-border);
|
||||
}
|
||||
|
||||
.mac {
|
||||
@@ -621,6 +753,15 @@ select {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.lastseen {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.lastseen.error {
|
||||
color: var(--color-text-error);
|
||||
}
|
||||
|
||||
|
||||
.corner {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
@@ -651,12 +792,23 @@ select {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.waitingicon {
|
||||
display: none;
|
||||
font-size: 1.2em;
|
||||
background-color: lightgreen;
|
||||
color: black;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
vertical-align: top;
|
||||
text-align: center;
|
||||
}
|
||||
ul.messages {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
ul.messages li {
|
||||
position: relative;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
ul.messages li.new {
|
||||
@@ -667,40 +819,41 @@ ul.messages li.new {
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
color: var(--color-text-error);
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
background-color: #666;
|
||||
color: #ccc;
|
||||
background-color: var(--color-console-mono-bg);
|
||||
color: var(--color-console-mono-text);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.quote {
|
||||
color: white;
|
||||
color: var(--color-console-quote-text);
|
||||
}
|
||||
|
||||
#georesults {
|
||||
position: absolute;
|
||||
background: white;
|
||||
background: var(--color-nav-bg);
|
||||
cursor: pointer;
|
||||
width: 240px;
|
||||
max-height: 115px;
|
||||
overflow-y: scroll;
|
||||
padding: 0px 2px 0px 2px;
|
||||
border: 1px solid var(--color-card-border);
|
||||
& div {
|
||||
display:flex;
|
||||
}
|
||||
& div:hover {
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--color-tab-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
#paintbutton {
|
||||
padding: 1px 3px;
|
||||
border: 1px solid black;
|
||||
border: 1px solid var(--color-text);
|
||||
font-size: 1.3em;
|
||||
vertical-align: top;
|
||||
margin-left: 12px;
|
||||
@@ -709,7 +862,7 @@ ul.messages li.new {
|
||||
}
|
||||
|
||||
#paintbutton:hover {
|
||||
background-color: #aaaaaa;
|
||||
background-color: var(--color-button-hover-bg);
|
||||
}
|
||||
|
||||
.drophighlight {
|
||||
@@ -720,8 +873,8 @@ ul.messages li.new {
|
||||
#context-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background: white;
|
||||
border: 1px solid gray;
|
||||
background: var(--color-context-menu-bg);
|
||||
border: 1px solid var(--color-context-menu-border);
|
||||
list-style: none;
|
||||
z-index: 999;
|
||||
padding: 2px 5px;
|
||||
@@ -730,10 +883,11 @@ ul.messages li.new {
|
||||
#context-menu li {
|
||||
cursor: pointer;
|
||||
padding: 1px 5px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
#context-menu li:hover {
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--color-context-menu-hover-bg);
|
||||
}
|
||||
|
||||
/* painter */
|
||||
@@ -743,7 +897,7 @@ ul.messages li.new {
|
||||
}
|
||||
|
||||
#canvasdiv canvas {
|
||||
border: 1px solid black;
|
||||
border: 1px solid var(--color-text);
|
||||
}
|
||||
|
||||
#buttonbar {
|
||||
@@ -755,8 +909,8 @@ ul.messages li.new {
|
||||
#buttonbar button,
|
||||
#layersdiv button {
|
||||
padding: 1px 2px;
|
||||
border: 1px solid #cccccc;
|
||||
background-color: #dddddd;
|
||||
border: 1px solid var(--color-input-border);
|
||||
background-color: var(--color-button-bg);
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
@@ -766,13 +920,13 @@ ul.messages li.new {
|
||||
}
|
||||
|
||||
#buttonbar .active {
|
||||
background-color: #ffffff;
|
||||
background-color: var(--color-input-bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#buttonbar button:hover,
|
||||
#layersdiv button:hover {
|
||||
background-color: #cccccc;
|
||||
background-color: var(--color-button-hover-bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -796,14 +950,14 @@ ul.messages li.new {
|
||||
}
|
||||
|
||||
#savebar button {
|
||||
border: solid 1px #666666;
|
||||
border: solid 1px var(--color-text-light);
|
||||
}
|
||||
|
||||
/* updatescreens */
|
||||
|
||||
#easyupdate {
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
background-color: var(--color-module-bg);
|
||||
width: 400px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@@ -825,38 +979,38 @@ h4 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#releasetable {
|
||||
.releasetable {
|
||||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
#releasetable table {
|
||||
.releasetable table {
|
||||
border-spacing: 1px;
|
||||
}
|
||||
|
||||
#releasetable th {
|
||||
.releasetable th {
|
||||
text-align: left;
|
||||
background-color: #ffffff;
|
||||
background-color: var(--color-module-bg);
|
||||
padding: 1px 5px;
|
||||
}
|
||||
|
||||
#releasetable td {
|
||||
background-color: #ffffff;
|
||||
.releasetable td {
|
||||
background-color: var(--color-module-bg);
|
||||
padding: 1px 5px;
|
||||
min-width: 70px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
#releasetable td:nth-child(2) {
|
||||
.releasetable td:nth-child(2) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#releasetable button {
|
||||
.releasetable button {
|
||||
padding: 3px 10px;
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--color-button-bg);
|
||||
}
|
||||
|
||||
#releasetable button:hover {
|
||||
background-color: #a0a0a0;
|
||||
.releasetable button:hover {
|
||||
background-color: var(--color-button-hover-bg);
|
||||
}
|
||||
|
||||
.updateCol1, .flashCol1 {
|
||||
@@ -865,9 +1019,9 @@ h4 {
|
||||
|
||||
.console {
|
||||
width: 450px;
|
||||
background-color: black;
|
||||
font-family: 'lucida console', 'ui-monospace';
|
||||
color: white;
|
||||
background-color: var(--color-console-bg);
|
||||
font-family: 'lucida console', 'ui-monospace', 'monospace';
|
||||
color: var(--color-console-text);
|
||||
padding: 5px 10px;
|
||||
padding-bottom: 25px;
|
||||
height: calc(100vh - 200px);
|
||||
@@ -906,15 +1060,15 @@ h4 {
|
||||
|
||||
@keyframes new {
|
||||
0% {
|
||||
background-color: rgba(255, 255, 204, 1);
|
||||
background-color: var(--color-log-new-bg-start);
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: rgba(255, 255, 204, .5);
|
||||
background-color: var(--color-log-new-bg-mid);
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: rgba(255, 255, 204, 0);
|
||||
background-color: var(--color-log-new-bg-end);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,14 +1090,13 @@ h4 {
|
||||
0% {}
|
||||
|
||||
50% {
|
||||
background-color: #d4d4f5;
|
||||
background-color: var(--color-tag-pending-anim-bg);
|
||||
}
|
||||
|
||||
100% {}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
/* styles for mobile devices in portrait mode */
|
||||
|
||||
body {
|
||||
font-size: 13px;
|
||||
@@ -998,4 +1151,22 @@ h4 {
|
||||
padding: 1px 1px;
|
||||
}
|
||||
|
||||
.theme-toggle-btn {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: var(--color-header-text);
|
||||
cursor: pointer;
|
||||
align-self: center;
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
margin-left: auto;
|
||||
border-left: 1px solid var(--color-tab-border);
|
||||
padding: 6px 1rem;
|
||||
border-radius: 4px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.theme-toggle-btn:hover {
|
||||
background-color: var(--color-tab-hover-bg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ const WAKEUP_REASON_GPIO = 2;
|
||||
const WAKEUP_REASON_NFC = 3;
|
||||
const WAKEUP_REASON_BUTTON1 = 4;
|
||||
const WAKEUP_REASON_BUTTON2 = 5;
|
||||
const WAKEUP_REASON_BUTTON3 = 6;
|
||||
const WAKEUP_REASON_FAILED_OTA_FW = 0xE0;
|
||||
const WAKEUP_REASON_FIRSTBOOT = 0xFC;
|
||||
const WAKEUP_REASON_NETWORK_SCAN = 0xFD;
|
||||
@@ -21,7 +22,7 @@ const apstate = [
|
||||
{ state: "online", color: "green", icon: "check_circle" },
|
||||
{ state: "flashing", color: "orange", icon: "flash_on" },
|
||||
{ state: "wait for reset", color: "blue", icon: "hourglass" },
|
||||
{ state: "AP requires power cycle", color: "purple", icon: "refresh" },
|
||||
{ state: "AP requires reboot", color: "purple", icon: "refresh" },
|
||||
{ state: "failed", color: "red", icon: "error" },
|
||||
{ state: "coming online...", color: "orange", icon: "hourglass" },
|
||||
{ state: "AP without radio", color: "green", icon: "wifi_off" }
|
||||
@@ -54,10 +55,9 @@ window.addEventListener("loadConfig", function () {
|
||||
$(".logo").innerHTML = data.alias;
|
||||
this.document.title = data.alias;
|
||||
}
|
||||
if (data.C6 == 1) {
|
||||
if (data.C6 == 1 || (data.H2 && data.H2 == 1)) {
|
||||
var optionToRemove = $("#apcfgchid").querySelector('option[value="27"]');
|
||||
if (optionToRemove) $("#apcfgchid").removeChild(optionToRemove);
|
||||
$('#updateC6Option').style.display = 'block';
|
||||
}
|
||||
if (data.hasFlasher == 1) {
|
||||
$('[data-target="flashtab"]').style.display = 'block';
|
||||
@@ -83,6 +83,34 @@ window.addEventListener("loadConfig", function () {
|
||||
});
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
const themeToggle = $('#theme-toggle');
|
||||
if (themeToggle) {
|
||||
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
const setTheme = (theme) => {
|
||||
if (theme === 'dark') {
|
||||
document.body.classList.add('dark-mode');
|
||||
themeToggle.innerHTML = 'dark_mode';
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
document.body.classList.remove('dark-mode');
|
||||
themeToggle.innerHTML = 'light_mode';
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
};
|
||||
|
||||
themeToggle.addEventListener('click', () => {
|
||||
const isDarkMode = document.body.classList.contains('dark-mode');
|
||||
setTheme(isDarkMode ? 'light' : 'dark');
|
||||
});
|
||||
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme) {
|
||||
setTheme(savedTheme);
|
||||
} else {
|
||||
setTheme(prefersDarkScheme.matches ? 'dark' : 'light');
|
||||
}
|
||||
}
|
||||
window.dispatchEvent(loadConfig);
|
||||
initTabs();
|
||||
fetch('content_cards.json')
|
||||
@@ -125,6 +153,18 @@ function initTabs() {
|
||||
tabLink.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
const targetId = this.getAttribute("data-target");
|
||||
const url = new URL(window.location);
|
||||
if (targetId === 'tagtab') {
|
||||
if (url.searchParams.get('tab') !== 'tagtab') {
|
||||
url.searchParams.set('tab', 'tagtab');
|
||||
history.replaceState(null, '', url);
|
||||
}
|
||||
} else {
|
||||
if (url.searchParams.has('tab')) {
|
||||
url.searchParams.delete('tab');
|
||||
history.replaceState(null, '', url);
|
||||
}
|
||||
}
|
||||
const loadTabEvent = new CustomEvent('loadTab', { detail: targetId });
|
||||
document.dispatchEvent(loadTabEvent);
|
||||
tabContents.forEach(tabContent => {
|
||||
@@ -133,11 +173,20 @@ function initTabs() {
|
||||
tabLinks.forEach(link => {
|
||||
link.classList.remove("active");
|
||||
});
|
||||
if (targetId == "logtab") document.getElementById(targetId).scrollTop = 0;
|
||||
document.getElementById(targetId).style.display = "block";
|
||||
this.classList.add("active");
|
||||
});
|
||||
});
|
||||
tabLinks[0].click();
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const tabToOpen = urlParams.get('tab');
|
||||
const targetTabLink = document.querySelector(`.tablinks[data-target="${tabToOpen}"]`);
|
||||
if (targetTabLink) {
|
||||
targetTabLink.click();
|
||||
} else {
|
||||
tabLinks[0].click();
|
||||
}
|
||||
};
|
||||
|
||||
function loadTags(pos) {
|
||||
@@ -266,6 +315,17 @@ function convertSize(bytes) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function clearTagStateClasses(tagElement) {
|
||||
tagElement.classList.remove(
|
||||
'state-deepsleep',
|
||||
'state-boot',
|
||||
'state-wakeup',
|
||||
'state-scan',
|
||||
'state-reset',
|
||||
'state-failed-ota'
|
||||
);
|
||||
}
|
||||
|
||||
function processTags(tagArray) {
|
||||
for (const element of tagArray) {
|
||||
const tagmac = element.mac;
|
||||
@@ -299,7 +359,7 @@ function processTags(tagArray) {
|
||||
if (alias.match(/^4467/)) {
|
||||
let macdigit = Number.parseInt(alias.substr(4, 2), 16) & 0x1f;
|
||||
let model = String.fromCharCode(macdigit + 65);
|
||||
if (model == 'J' || model == 'M') {
|
||||
if (model >= 'A' && model <= 'Z') {
|
||||
macdigit = Number.parseInt(alias.substr(6, 2), 16) & 0x1f;
|
||||
model += String.fromCharCode(macdigit + 65);
|
||||
alias = model + alias.substr(8, 8) + 'x'
|
||||
@@ -371,15 +431,20 @@ function processTags(tagArray) {
|
||||
} else {
|
||||
$('#tag' + tagmac + ' .nextupdate').innerHTML = "";
|
||||
}
|
||||
if (element.nextupdate < (Date.now() / 1000) - servertimediff) {
|
||||
$('#tag' + tagmac + ' .waitingicon').style.display = 'inline-block';
|
||||
} else {
|
||||
$('#tag' + tagmac + ' .waitingicon').style.display = 'none';
|
||||
}
|
||||
|
||||
if (element.nextcheckin > 1672531200) {
|
||||
div.dataset.nextcheckin = element.nextcheckin;
|
||||
} else {
|
||||
div.dataset.nextcheckin = element.lastseen + 1800;
|
||||
div.dataset.nextcheckin = element.lastseen + 60;
|
||||
}
|
||||
|
||||
div.style.opacity = '1';
|
||||
$('#tag' + tagmac + ' .lastseen').style.color = "black";
|
||||
$('#tag' + tagmac + ' .lastseen').style.color = "";
|
||||
div.classList.remove("tagpending");
|
||||
div.dataset.lastseen = element.lastseen;
|
||||
div.dataset.wakeupreason = element.wakeupReason;
|
||||
@@ -387,45 +452,56 @@ function processTags(tagArray) {
|
||||
div.dataset.channel = element.ch;
|
||||
div.dataset.isexternal = element.isexternal;
|
||||
$('#tag' + tagmac + ' .warningicon').style.display = 'none';
|
||||
$('#tag' + tagmac).style.background = "#ffffff";
|
||||
if (element.contentMode == 12 || element.nextcheckin == 3216153600) $('#tag' + tagmac).style.background = "#e4e4e0";
|
||||
|
||||
div.style.background = '';
|
||||
div.classList.remove('state-timeout');
|
||||
clearTagStateClasses(div);
|
||||
|
||||
let stateClassSet = false;
|
||||
switch (parseInt(element.wakeupReason)) {
|
||||
case WAKEUP_REASON_TIMED:
|
||||
break;
|
||||
case WAKEUP_REASON_BOOT:
|
||||
case WAKEUP_REASON_FIRSTBOOT:
|
||||
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "<font color=yellow>First boot</font>"
|
||||
$('#tag' + tagmac).style.background = "#b0d0b0";
|
||||
div.classList.add("state-boot");
|
||||
stateClassSet = true;
|
||||
break;
|
||||
case WAKEUP_REASON_GPIO:
|
||||
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "GPIO wakeup"
|
||||
$('#tag' + tagmac).style.background = "#c8f1bb";
|
||||
break;
|
||||
case WAKEUP_REASON_BUTTON1:
|
||||
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "Button 1 pressed"
|
||||
$('#tag' + tagmac).style.background = "#c8f1bb";
|
||||
break;
|
||||
case WAKEUP_REASON_BUTTON2:
|
||||
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "Button 2 pressed"
|
||||
$('#tag' + tagmac).style.background = "#c8f1bb";
|
||||
break;
|
||||
case WAKEUP_REASON_BUTTON3:
|
||||
case WAKEUP_REASON_NFC:
|
||||
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "NFC wakeup"
|
||||
$('#tag' + tagmac).style.background = "#c8f1bb";
|
||||
let reasonText = {
|
||||
[WAKEUP_REASON_GPIO]: "GPIO wakeup",
|
||||
[WAKEUP_REASON_BUTTON1]: "Button 1 pressed",
|
||||
[WAKEUP_REASON_BUTTON2]: "Button 2 pressed",
|
||||
[WAKEUP_REASON_BUTTON3]: "Button 3 pressed",
|
||||
[WAKEUP_REASON_NFC]: "NFC wakeup"
|
||||
}[parseInt(element.wakeupReason)];
|
||||
$('#tag' + tagmac + ' .nextcheckin').innerHTML = reasonText;
|
||||
div.classList.add("state-wakeup");
|
||||
stateClassSet = true;
|
||||
break;
|
||||
case WAKEUP_REASON_NETWORK_SCAN:
|
||||
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "<font color=yellow>Network scan</font>"
|
||||
$('#tag' + tagmac).style.background = "#c0c0d0";
|
||||
div.classList.add("state-scan");
|
||||
stateClassSet = true;
|
||||
break;
|
||||
case WAKEUP_REASON_WDT_RESET:
|
||||
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "Watchdog reset!"
|
||||
$('#tag' + tagmac).style.background = "#d0a0a0";
|
||||
div.classList.add("state-reset");
|
||||
stateClassSet = true;
|
||||
break;
|
||||
case WAKEUP_REASON_FAILED_OTA_FW:
|
||||
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "Firmware update rejected!"
|
||||
$('#tag' + tagmac).style.background = "#f0a0a0";
|
||||
div.classList.add("state-failed-ota");
|
||||
stateClassSet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!stateClassSet && (element.contentMode == 12 || element.nextcheckin == 3216153600)) {
|
||||
div.classList.add("state-deepsleep");
|
||||
}
|
||||
$('#tag' + tagmac + ' .pendingicon').style.display = (element.pending ? 'inline-block' : 'none');
|
||||
$('#tag' + tagmac + ' .pendingicon').innerHTML = element.pending;
|
||||
div.classList.add("tagflash");
|
||||
@@ -460,18 +536,24 @@ function updatecards() {
|
||||
if (tagDB[tagmac].batteryMv < 2400 && tagDB[tagmac].batteryMv != 0 && tagDB[tagmac].batteryMv != 1337) lowbattcount++;
|
||||
if (item.dataset.lastseen && item.dataset.lastseen > (Date.now() / 1000) - servertimediff - 30 * 24 * 3600 * 60) {
|
||||
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';
|
||||
$('#tag' + tagmac + ' .lastseen').innerHTML = "<span>last seen</span>" + displayTime(Math.floor(idletime)) + " ago";
|
||||
if ((Date.now() / 1000) - servertimediff - apConfig.maxsleep * 60 - 300 > item.dataset.nextcheckin) {
|
||||
item.querySelector('.warningicon').style.display = 'inline-block';
|
||||
item.classList.remove("tagpending");
|
||||
item.classList.add('state-timeout');
|
||||
timeoutcount++;
|
||||
} else {
|
||||
item.classList.remove('state-timeout');
|
||||
if (tagDB[tagmac].pending) pendingcount++;
|
||||
}
|
||||
|
||||
const lastseenEl = item.querySelector('.lastseen');
|
||||
if (idletime > 24 * 3600) {
|
||||
$('#tag' + tagmac).style.opacity = '.5';
|
||||
$('#tag' + tagmac + ' .lastseen').style.color = "red";
|
||||
item.style.opacity = '.5';
|
||||
lastseenEl.classList.add('error');
|
||||
} else {
|
||||
item.style.opacity = '1';
|
||||
lastseenEl.classList.remove('error');
|
||||
}
|
||||
} else {
|
||||
if ($('#tag' + tagmac + ' .lastseen')) {
|
||||
@@ -489,6 +571,12 @@ function updatecards() {
|
||||
} else {
|
||||
// $('#tag' + tagmac + ' .nextcheckin').innerHTML = "";
|
||||
}
|
||||
|
||||
if (item.dataset.nextupdate < (Date.now() / 1000) - servertimediff) {
|
||||
$('#tag' + tagmac + ' .waitingicon').style.display = 'inline-block';
|
||||
} else {
|
||||
$('#tag' + tagmac + ' .waitingicon').style.display = 'none';
|
||||
}
|
||||
})
|
||||
|
||||
$('#dashboardTagCount').innerHTML = tagcount;
|
||||
@@ -793,6 +881,7 @@ document.addEventListener("loadTab", function (event) {
|
||||
$("#apcnight1").value = data.sleeptime1;
|
||||
$("#apcnight2").value = data.sleeptime2;
|
||||
$("#apcdiscovery").value = data.discovery;
|
||||
$("#apcshowtimestamp").value = data.showtimestamp;
|
||||
}
|
||||
})
|
||||
$('#apcfgmsg').innerHTML = '';
|
||||
@@ -830,7 +919,8 @@ $('#apcfgsave').onclick = function () {
|
||||
formData.append('timezone', $('#apctimezone').value);
|
||||
formData.append('sleeptime1', $('#apcnight1').value);
|
||||
formData.append('sleeptime2', $('#apcnight2').value);
|
||||
formData.append('discovery', $('#apcdiscovery').value)
|
||||
formData.append('discovery', $('#apcdiscovery').value);
|
||||
formData.append('showtimestamp', $('#apcshowtimestamp').value);
|
||||
fetch("save_apcfg", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
@@ -994,7 +1084,7 @@ function contentselected() {
|
||||
fetch('edit?list=%2F&recursive=1')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
let files = data.filter(item => item.type === "file" && item.name.endsWith(".jpg"));
|
||||
let files = data.filter(item => item.type === "file" && (item.name.toLowerCase().endsWith(".jpg") || item.name.toLowerCase().endsWith(".jpeg")));
|
||||
if (element.type == 'binfile') files = data.filter(item => item.type === "file" && item.name.endsWith(".bin"));
|
||||
if (element.type == 'jsonfile') files = data.filter(item => item.type === "file" && item.name.endsWith(".json"));
|
||||
const optionElement = document.createElement("option");
|
||||
@@ -1207,6 +1297,16 @@ function drawCanvas(buffer, canvas, hwtype, tagmac, doRotate) {
|
||||
if (data.length > 0 && tagTypes[hwtype].zlib > 0 && $('#tag' + tagmac).dataset.ver >= tagTypes[hwtype].zlib) {
|
||||
data = processZlib(data);
|
||||
}
|
||||
if (data.length > 0 && tagTypes[hwtype].g5 > 0 && $('#tag' + tagmac).dataset.ver >= tagTypes[hwtype].g5) {
|
||||
const headerSize = data[0];
|
||||
let bufw = (data[2] << 8) | data[1];
|
||||
let bufh = (data[4] << 8) | data[3];
|
||||
if ((bufw == tagTypes[hwtype].width || bufw == tagTypes[hwtype].height) && (bufh == tagTypes[hwtype].width || bufh == tagTypes[hwtype].height) && (data[5] <= 3)) {
|
||||
// valid header for g5 compression
|
||||
if (data[5] == 2) bufh *= 2;
|
||||
data = processG5(data.subarray(headerSize), bufw, bufh);
|
||||
}
|
||||
}
|
||||
|
||||
[canvas.width, canvas.height] = [tagTypes[hwtype].width, tagTypes[hwtype].height] || [0, 0];
|
||||
if (tagTypes[hwtype].rotatebuffer % 2) [canvas.width, canvas.height] = [canvas.height, canvas.width];
|
||||
@@ -1243,6 +1343,24 @@ function drawCanvas(buffer, canvas, hwtype, tagmac, doRotate) {
|
||||
imageData.data[i * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
} else if ([3, 4].includes(tagTypes[hwtype].bpp)) {
|
||||
const bpp = tagTypes[hwtype].bpp;
|
||||
const colorTable = tagTypes[hwtype].colortable;
|
||||
let pixelIndex = 0;
|
||||
let bitOffset = 0;
|
||||
|
||||
while (bitOffset < data.length * 8) {
|
||||
let byteIndex = bitOffset >> 3;
|
||||
let startBit = bitOffset & 7;
|
||||
let pixelValue = (data[byteIndex] << 8 | data[byteIndex + 1] || 0) >> (16 - bpp - startBit) & ((1 << bpp) - 1);
|
||||
let color = colorTable[pixelValue];
|
||||
imageData.data[pixelIndex * 4] = color[0];
|
||||
imageData.data[pixelIndex * 4 + 1] = color[1];
|
||||
imageData.data[pixelIndex * 4 + 2] = color[2];
|
||||
imageData.data[pixelIndex * 4 + 3] = 255;
|
||||
pixelIndex++;
|
||||
bitOffset += bpp;
|
||||
}
|
||||
} else {
|
||||
|
||||
const offsetRed = (data.length >= (canvas.width * canvas.height / 8) * 2) ? canvas.width * canvas.height / 8 : 0;
|
||||
@@ -1495,10 +1613,11 @@ async function getTagtype(hwtype) {
|
||||
height: parseInt(jsonData.height),
|
||||
bpp: parseInt(jsonData.bpp),
|
||||
rotatebuffer: jsonData.rotatebuffer,
|
||||
colortable: Object.values(jsonData.colortable),
|
||||
colortable: Object.values(jsonData.perceptual ?? jsonData.colortable),
|
||||
contentids: Object.values(jsonData.contentids ?? []),
|
||||
options: Object.values(jsonData.options ?? []),
|
||||
zlib: parseInt(jsonData.zlib_compression || "0", 16),
|
||||
g5: parseInt(jsonData.g5_compression || "0", 16),
|
||||
shortlut: parseInt(jsonData.shortlut),
|
||||
busy: false,
|
||||
usetemplate: parseInt(jsonData.usetemplate || "0", 10)
|
||||
@@ -1538,6 +1657,7 @@ function dropUpload() {
|
||||
|
||||
dropZone.addEventListener('drop', (event) => {
|
||||
event.preventDefault();
|
||||
const shiftKey = event.shiftKey;
|
||||
const file = event.dataTransfer.files[0];
|
||||
const tagCard = event.target.closest('.tagcard');
|
||||
const mac = tagCard.dataset.mac;
|
||||
@@ -1571,6 +1691,7 @@ function dropUpload() {
|
||||
canvas.toBlob(async (blob) => {
|
||||
const formData = new FormData();
|
||||
formData.append('mac', mac);
|
||||
if (shiftKey) formData.append('dither', '2');
|
||||
formData.append('file', blob, 'image.jpg');
|
||||
|
||||
try {
|
||||
@@ -1746,7 +1867,7 @@ function populateAPCard(msg) {
|
||||
|
||||
function populateAPInfo(apip) {
|
||||
let apid = apip.replace(/\./g, "-");
|
||||
fetch('sysinfo')
|
||||
fetch('http://' + apip + '/sysinfo')
|
||||
.then(response => {
|
||||
if (response.status != 200) {
|
||||
$('#ap' + apid + ' .apswversion').innerHTML = "Error fetching sysinfo: " + response.status;
|
||||
@@ -1757,12 +1878,25 @@ function populateAPInfo(apip) {
|
||||
})
|
||||
.then(data => {
|
||||
if (data.env) {
|
||||
let gModuleType = "";
|
||||
if (data.hasC6 == 1) {
|
||||
gModuleType = "esp32-C6";
|
||||
}
|
||||
if (data.hasH2 == 1) {
|
||||
gModuleType = "esp32-H2";
|
||||
}
|
||||
if (data.hasTslr == 1) {
|
||||
gModuleType = "TSLR";
|
||||
}
|
||||
let version = '';
|
||||
version += `env: ${data.env}<br>`;
|
||||
version += `build date: ${formatEpoch(data.buildtime)}<br>`;
|
||||
version += `esp32 version: ${data.buildversion}<br>`;
|
||||
version += `psram size: ${data.psramsize}<br>`;
|
||||
version += `flash size: ${data.flashsize}<br>`;
|
||||
if (gModuleType) {
|
||||
version += `${gModuleType} version: 0x${parseInt(data.ap_version).toString(16).toUpperCase()}<br>`;
|
||||
}
|
||||
$('#ap' + apid + ' .apswversion').innerHTML = version;
|
||||
}
|
||||
})
|
||||
@@ -1873,7 +2007,7 @@ function openPreview(mac, w, h) {
|
||||
previewWindow.document.body.style.backgroundColor = "#dddddd";
|
||||
previewWindow.document.body.style.margin = "15px";
|
||||
previewWindow.document.body.style.overflow = "hidden";
|
||||
previewWindow.document.body.innerHTML = `<canvas id="preview" style="border:1px solid #888888;"></canvas>`;
|
||||
previewWindow.document.body.innerHTML = `<canvas id="preview" style="border:1px solid #888888;image-rendering: pixelated;"></canvas>`;
|
||||
|
||||
showPreview(previewWindow, element);
|
||||
|
||||
@@ -1895,11 +2029,12 @@ function showPreview(previewWindow, element) {
|
||||
console.log('refresh ' + element.mac);
|
||||
previewWindow.pending = element.pending;
|
||||
previewWindow.hash = "";
|
||||
let cachetag = Date.now();
|
||||
|
||||
if (element.isexternal && element.contentMode == 12) {
|
||||
imageSrc = 'http://' + tagDB[element.mac].apip + '/getdata?mac=' + element.mac + '&md5=0000000000000000';
|
||||
imageSrc = 'http://' + tagDB[element.mac].apip + '/getdata?mac=' + element.mac + '&md5=0000000000000000&c=' + cachetag;
|
||||
} else {
|
||||
imageSrc = '/getdata?mac=' + element.mac + '&md5=0000000000000000';
|
||||
imageSrc = '/getdata?mac=' + element.mac + '&md5=0000000000000000&c=' + cachetag;
|
||||
}
|
||||
|
||||
} else if (element.hash != previewWindow.hash) {
|
||||
@@ -1924,4 +2059,4 @@ function showPreview(previewWindow, element) {
|
||||
console.error('fetch preview image error:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,27 @@ let running = false;
|
||||
let errors = 0;
|
||||
let env = '', currentVer = '', currentBuildtime = 0;
|
||||
let buttonState = false;
|
||||
let gIsC6 = false;
|
||||
let gIsH2 = false;
|
||||
let gModuleType = '';
|
||||
let gShortName = '';
|
||||
let gCurrentRfVer = 0;
|
||||
|
||||
export async function initUpdate() {
|
||||
if (apConfig.C6 == 1) {
|
||||
gIsC6 = true;
|
||||
gModuleType = "ESP32-C6";
|
||||
gShortName = "C6";
|
||||
}
|
||||
else if (apConfig?.H2 && apConfig.H2 == 1) {
|
||||
gIsH2 = true;
|
||||
gModuleType = "ESP32-H2";
|
||||
gShortName = "H2";
|
||||
}
|
||||
else {
|
||||
gModuleType = "Unknown"
|
||||
}
|
||||
$('#radio_release_title').innerHTML = gModuleType + " Firmware";
|
||||
|
||||
const response = await fetch("version.txt");
|
||||
let filesystemversion = await response.text();
|
||||
@@ -30,7 +49,7 @@ export async function initUpdate() {
|
||||
$('#selectRepo').style.display = 'inline-block';
|
||||
$('#repoWarning').style.display = 'none';
|
||||
|
||||
const sysinfoPromise = fetch("sysinfo")
|
||||
const sdata = await fetch("sysinfo")
|
||||
.then(response => {
|
||||
if (response.status != 200) {
|
||||
print("Error fetching sysinfo: " + response.status, "red");
|
||||
@@ -48,94 +67,173 @@ export async function initUpdate() {
|
||||
print('Error fetching sysinfo: ' + error, "red");
|
||||
});
|
||||
|
||||
const repoPromise = fetch(repoUrl)
|
||||
.then(response => response.json())
|
||||
if (sdata.env) {
|
||||
print(`current env: ${sdata.env}`);
|
||||
print(`build date: ${formatEpoch(sdata.buildtime)}`);
|
||||
print(`esp32 version: ${sdata.buildversion}`);
|
||||
print(`filesystem version: ${filesystemversion}`);
|
||||
print(`psram size: ${sdata.psramsize}`);
|
||||
print(`flash size: ${sdata.flashsize}`);
|
||||
if (gModuleType !== '') {
|
||||
let hex_ver = sdata.ap_version && !isNaN(sdata.ap_version)
|
||||
? ('0000' + sdata.ap_version.toString(16)).slice(-4)
|
||||
: 'unknown';
|
||||
print(`${gModuleType} version: ${hex_ver}`);
|
||||
}
|
||||
print("--------------------------", "gray");
|
||||
env = apConfig.env || sdata.env;
|
||||
if (sdata.env != env) {
|
||||
print(`Warning: you selected a build environment ${env} which is\ndifferent than the currently used ${sdata.env}.\nOnly update the firmware with a mismatched build environment if\nyou know what you're doing.`, "yellow");
|
||||
}
|
||||
currentVer = sdata.buildversion;
|
||||
currentBuildtime = sdata.buildtime;
|
||||
gCurrentRfVer = sdata.ap_version;
|
||||
if (sdata.rollback) $("#rollbackOption").style.display = 'block';
|
||||
$('#environment').value = env;
|
||||
}
|
||||
|
||||
|
||||
Promise.all([sysinfoPromise, repoPromise])
|
||||
.then(([sdata, rdata]) => {
|
||||
|
||||
if (sdata.env) {
|
||||
let matchtest = '';
|
||||
if (sdata.buildversion != filesystemversion && filesystemversion != "custom" && sdata.buildversion != "custom") matchtest = " <- not matching!"
|
||||
print(`env: ${sdata.env}`);
|
||||
print(`build date: ${formatEpoch(sdata.buildtime)}`);
|
||||
print(`esp32 version: ${sdata.buildversion}`);
|
||||
print(`filesystem version: ${filesystemversion}` + matchtest);
|
||||
print(`psram size: ${sdata.psramsize}`);
|
||||
print(`flash size: ${sdata.flashsize}`);
|
||||
print("--------------------------", "gray");
|
||||
env = sdata.env;
|
||||
currentVer = sdata.buildversion;
|
||||
currentBuildtime = sdata.buildtime;
|
||||
if (sdata.rollback) $("#rollbackOption").style.display = 'block';
|
||||
$('#environment').value = env;
|
||||
const rdata = await fetch(repoUrl).then(response => response.json())
|
||||
const JsonName = 'firmware_' + gShortName + '.json';
|
||||
const releaseDetails = rdata.map(release => {
|
||||
const assets = release.assets;
|
||||
const filesJsonAsset = assets.find(asset => asset.name === 'filesystem.json');
|
||||
const binariesJsonAsset = assets.find(asset => asset.name === 'binaries.json');
|
||||
const containsEnv = assets.find(asset => asset.name === env + '.bin');
|
||||
const firmwareAsset = assets.find(asset => asset.name === JsonName);
|
||||
if (filesJsonAsset && binariesJsonAsset && containsEnv) {
|
||||
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: filesJsonAsset.browser_download_url,
|
||||
bin_url: binariesJsonAsset.browser_download_url,
|
||||
firmware_url: firmwareAsset?.browser_download_url,
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
const releaseDetails = rdata.map(release => {
|
||||
const assets = release.assets;
|
||||
const filesJsonAsset = assets.find(asset => asset.name === 'filesystem.json');
|
||||
const binariesJsonAsset = assets.find(asset => asset.name === 'binaries.json');
|
||||
const containsEnv = assets.find(asset => asset.name === env + '.bin');
|
||||
if (filesJsonAsset && binariesJsonAsset && containsEnv) {
|
||||
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: filesJsonAsset.browser_download_url,
|
||||
bin_url: binariesJsonAsset.browser_download_url
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const easyupdate = $('#easyupdate');
|
||||
if (releaseDetails.length === 0) {
|
||||
easyupdate.innerHTML = ("No releases found.");
|
||||
if (releaseDetails.length === 0) {
|
||||
easyupdate.innerHTML = ("No releases found.");
|
||||
} else {
|
||||
const release = releaseDetails[0];
|
||||
if (release?.tag_name) {
|
||||
if (release.tag_name == currentVer) {
|
||||
easyupdate.innerHTML = `Version ${currentVer}. You are up to date`;
|
||||
} else if (release.date < formatEpoch(currentBuildtime - 30 * 60)) {
|
||||
easyupdate.innerHTML = `Your version is newer than the latest release date.<br>Are you the developer? :-)`;
|
||||
} else {
|
||||
const release = releaseDetails[0];
|
||||
if (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.bin_url}','${release.file_url}','${release.tag_name}')">Update now!</button>`;
|
||||
}
|
||||
}
|
||||
easyupdate.innerHTML = `An update from version ${currentVer} to version ${release.tag_name} is available.<button onclick="otamodule.updateAll('${release.bin_url}','${release.file_url}','${release.tag_name}')">Update now!</button>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
const table = document.createElement('table');
|
||||
const tableHeader = document.createElement('tr');
|
||||
tableHeader.innerHTML = '<th>Release</th><th>Date</th><th>Name</th><th colspan="2"><center>Update</center></th><th>Remark</th>';
|
||||
table.appendChild(tableHeader);
|
||||
|
||||
let rowCounter = 0;
|
||||
releaseDetails.forEach(release => {
|
||||
if (rowCounter < 4 && 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 type="button" onclick="otamodule.updateWebpage('${release.file_url}','${release.tag_name}', true)">Filesystem</button></td><td><button type="button" onclick="otamodule.updateESP('${release.bin_url}', true)">ESP32</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>";
|
||||
let rowCounter = 0;
|
||||
let radioFwCounter = 0;
|
||||
releaseDetails.forEach(release => {
|
||||
if (rowCounter < 4 && 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 type="button" onclick="otamodule.updateESP('${release.bin_url}', true)">ESP32</button></td><td><button type="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);
|
||||
rowCounter++;
|
||||
}
|
||||
if (release?.firmware_url) {
|
||||
radioFwCounter++;
|
||||
}
|
||||
});
|
||||
$('#releasetable').innerHTML = "";
|
||||
$('#releasetable').appendChild(table);
|
||||
|
||||
if (radioFwCounter > 0) {
|
||||
const table1 = document.createElement('table');
|
||||
const tableHeader1 = document.createElement('tr');
|
||||
|
||||
tableHeader1.innerHTML = '<th>Release</th><th>Date</th><th>Name</th><th><center>Update</center></th><th>Version</th><th>Remark</th>';
|
||||
table1.appendChild(tableHeader1);
|
||||
|
||||
rowCounter = 0;
|
||||
for (const release of releaseDetails) {
|
||||
if (rowCounter < 4 && release?.firmware_url) {
|
||||
const tableRow = document.createElement('tr');
|
||||
var tablerow;
|
||||
var firmwareVer = "unknown";
|
||||
var release_url = release.firmware_url;
|
||||
|
||||
tablerow = `<td><a href="${release.html_url}" target="_new">${release.tag_name}</a></td><td>${release.date}</td><td>${release.name}</td>`;
|
||||
tablerow += `<td><button type="button" onclick="otamodule.updateC6H2('${release_url}')">${gModuleType}</button></td>`;
|
||||
const firmwareUrl = 'http://proxy.openepaperlink.org/proxy.php?url=' + release.firmware_url;
|
||||
firmwareVer = await fetch(firmwareUrl, { method: 'GET' })
|
||||
.then(function (response) { return response.json(); })
|
||||
.then(function (response) {
|
||||
return response[2]['version'];
|
||||
})
|
||||
.catch(error => {
|
||||
print('Error fetching releases:' + error, "red");
|
||||
});
|
||||
tablerow += '<td>' + firmwareVer + '</td><td>';
|
||||
if (firmwareVer != 'unknown') {
|
||||
let Ver = Number('0x' + firmwareVer);
|
||||
if (Ver > gCurrentRfVer) {
|
||||
tablerow += 'newer';
|
||||
}
|
||||
else if (Ver < gCurrentRfVer) {
|
||||
tablerow += 'older';
|
||||
}
|
||||
else if (!Number.isNaN(Ver)) {
|
||||
tablerow += 'current version';
|
||||
}
|
||||
tableRow.innerHTML = tablerow;
|
||||
table.appendChild(tableRow);
|
||||
rowCounter++;
|
||||
}
|
||||
});
|
||||
tablerow += '</td>';
|
||||
tableRow.innerHTML = tablerow;
|
||||
table1.appendChild(tableRow);
|
||||
rowCounter++;
|
||||
}
|
||||
};
|
||||
|
||||
$('#releasetable').innerHTML = "";
|
||||
$('#releasetable').appendChild(table);
|
||||
disableButtons(buttonState);
|
||||
})
|
||||
.catch(error => {
|
||||
print('Error fetching releases:' + error, "red");
|
||||
});
|
||||
$('#radio_releasetable').innerHTML = "";
|
||||
$('#radio_releasetable').appendChild(table1);
|
||||
}
|
||||
|
||||
const table2 = document.createElement('table');
|
||||
{
|
||||
const tableHeader2 = document.createElement('tr');
|
||||
tableHeader2.innerHTML = '<th>Firmware</th><th><center>Update</center></th>';
|
||||
table2.appendChild(tableHeader2);
|
||||
const tableRow = document.createElement('tr');
|
||||
tablerow = '<td title="manual upload, make sure all four files are present">Binaries from <a href="/edit" target="littlefs">file system</a></td>';
|
||||
tablerow += `<td><button type="button" onclick="otamodule.updateC6H2('')">${gModuleType}</button></td>`;
|
||||
tableRow.innerHTML = tablerow;
|
||||
table2.appendChild(tableRow);
|
||||
}
|
||||
{
|
||||
const tableRow = document.createElement('tr');
|
||||
const Url = "https://raw.githubusercontent.com/" + repo +
|
||||
"/master/binaries/ESP32-" + gShortName +
|
||||
"/firmware_" + gShortName + ".json";
|
||||
|
||||
tablerow = `<td><a href="https://github.com/${repo}/tree/master/binaries/ESP32-${gShortName}/" target="_new">Latest version from repo</a></td>`;
|
||||
tablerow += `<td><button type="button" onclick="otamodule.updateC6H2('${Url}')">${gModuleType}</button></td>`;
|
||||
tableRow.innerHTML = tablerow;
|
||||
table2.appendChild(tableRow);
|
||||
}
|
||||
$('#radio_releasetable1').innerHTML = "";
|
||||
$('#radio_releasetable1').appendChild(table2);
|
||||
|
||||
disableButtons(buttonState);
|
||||
}
|
||||
|
||||
export function updateAll(binUrl, fileUrl, tagname) {
|
||||
@@ -354,19 +452,18 @@ $('#rollbackBtn').onclick = function () {
|
||||
disableButtons(false);
|
||||
}
|
||||
|
||||
$('#updateC6Btn').onclick = function () {
|
||||
export async function updateC6H2(Url) {
|
||||
if (running) return;
|
||||
disableButtons(true);
|
||||
running = true;
|
||||
errors = 0;
|
||||
const ReleaseUrl = Url.substring(0, Url.lastIndexOf('/'));
|
||||
const consoleDiv = document.getElementById('updateconsole');
|
||||
consoleDiv.scrollTop = consoleDiv.scrollHeight;
|
||||
|
||||
print("Flashing ESP32-C6...");
|
||||
|
||||
const isChecked = $('#c6download').checked;
|
||||
const formData = new FormData();
|
||||
formData.append('download', isChecked ? '1' : '0');
|
||||
|
||||
print("Flashing " + gModuleType + " ...");
|
||||
formData.append('url', ReleaseUrl);
|
||||
|
||||
fetch("update_c6", {
|
||||
method: "POST",
|
||||
@@ -401,7 +498,7 @@ $('#selectRepo').onclick = function (event) {
|
||||
return fetch(updateUrl);
|
||||
} else {
|
||||
throw new Error("Json file binaries.json and/or filesystem.json not found in the release assets");
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
.then(updateResponse => {
|
||||
@@ -414,7 +511,7 @@ $('#selectRepo').onclick = function (event) {
|
||||
if (!responseBody.trim().startsWith("[")) {
|
||||
throw new Error("Failed to fetch the release info file");
|
||||
}
|
||||
const updateData = JSON.parse(responseBody).filter(item => !item.name.endsWith('_full.bin'));
|
||||
const updateData = JSON.parse(responseBody).filter(item => !item.name.endsWith('_full.bin') && !item.name.includes('_H2.') && !item.name.includes('_C6.'));
|
||||
|
||||
const inputParent = $('#environment').parentNode;
|
||||
const selectElement = document.createElement('select');
|
||||
@@ -435,7 +532,7 @@ $('#selectRepo').onclick = function (event) {
|
||||
})
|
||||
.catch(error => {
|
||||
print('Error fetching releases:' + error, "red");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#cancelSelectRepo').onclick = function (event) {
|
||||
@@ -614,7 +711,7 @@ async function fetchAndCheckTagtypes(cleanup) {
|
||||
check = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (check) {
|
||||
let githubUrl = "https://raw.githubusercontent.com/" + repo + "/master/resources/tagtypes/" + filename;
|
||||
|
||||
@@ -637,3 +734,7 @@ async function fetchAndCheckTagtypes(cleanup) {
|
||||
print("Error: " + error, "red");
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeVersion(version) {
|
||||
return version.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user