Compare commits

..

9 Commits

19 changed files with 649 additions and 492 deletions

View File

@ -1,234 +1,234 @@
name: Build_firmware_master
on:
workflow_dispatch: # Start a workflow
push:
branches: master
paths-ignore:
- '.github/**' # Ignore changes towards the .github directory
- '**.md' # Do no build if *.md files changes
# Ensures that only one deploy task per branch/environment will run at a time.
concurrency:
group: environment-${{ github.ref }}
cancel-in-progress: true
jobs:
safeboot-images:
runs-on: ubuntu-latest
if: github.repository == 'arendst/Tasmota'
continue-on-error: true
strategy:
matrix:
variant:
- tasmota32-safeboot
- tasmota32solo1-safeboot
- tasmota32c2-safeboot
- tasmota32c3-safeboot
- tasmota32c3ser-safeboot
- tasmota32s2-safeboot
- tasmota32s2cdc-safeboot
- tasmota32s3-safeboot
- tasmota32s3ser-safeboot
- tasmota32c6-safeboot
- tasmota32c6ser-safeboot
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
pip install wheel
pip install -U platformio
cp ./platformio_override_sample.ini ./platformio_override.ini
- name: Add "release" to footer
run: |
sed -i -e "s/TASMOTA_SHA_SHORT/TASMOTA_SHA_SHORT release-/g" ./tasmota/include/tasmota_version.h
- name: Run PlatformIO
run: platformio run -e ${{ matrix.variant }}
- name: Upload safeboot firmware artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.variant }}
path: ./build_output
base-images:
runs-on: ubuntu-latest
if: github.repository == 'arendst/Tasmota'
continue-on-error: true
strategy:
matrix:
variant:
- tasmota
- tasmota-4M
- tasmota-minimal
- tasmota-display
- tasmota-ir
- tasmota-knx
- tasmota-lite
- tasmota-sensors
- tasmota-zbbridge
- tasmota-zigbee
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
pip install wheel
pip install -U platformio
cp ./platformio_override_sample.ini ./platformio_override.ini
- name: Add "release" to footer
run: |
sed -i -e "s/TASMOTA_SHA_SHORT/TASMOTA_SHA_SHORT release-/g" ./tasmota/include/tasmota_version.h
- name: Run PlatformIO
run: platformio run -e ${{ matrix.variant }}
- name: Upload firmware artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.variant }}
path: ./build_output
base32-images:
needs: safeboot-images
runs-on: ubuntu-latest
if: github.repository == 'arendst/Tasmota'
continue-on-error: true
strategy:
matrix:
variant:
- tasmota32
- tasmota32-zbbrdgpro
- tasmota32-webcam
- tasmota32-bluetooth
- tasmota32-nspanel
- tasmota32-display
- tasmota32-ir
- tasmota32-lvgl
- tasmota32c2
- tasmota32c3
- tasmota32c6
- tasmota32s2
- tasmota32s2cdc
- tasmota32s3
- tasmota32solo1
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
pip install wheel
pip install -U platformio
cp ./platformio_override_sample.ini ./platformio_override.ini
- name: Download safeboot firmwares
uses: actions/download-artifact@v4
with:
pattern: tasmota32*
path: ./temp
- name: Move safeboot files
run: |
mkdir -p ./firmware/firmware
find ./temp -type f -exec cp -t ./firmware/firmware {} +
- name: Add "release" to footer
run: |
sed -i -e "s/TASMOTA_SHA_SHORT/TASMOTA_SHA_SHORT release-/g" ./tasmota/include/tasmota_version.h
- name: Run PlatformIO
run: platformio run -e ${{ matrix.variant }}
- name: Upload firmware artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.variant }}
path: ./build_output
language-images:
needs: safeboot-images
runs-on: ubuntu-latest
if: github.repository == 'arendst/Tasmota'
continue-on-error: true
strategy:
matrix:
variant: [ tasmota, tasmota32 ]
language: [ AD, AF, BG, BR, CN, CZ, DE, ES, FR, FY, GR, HE, HU, IT, KO, LT, NL, PL, PT, RO, RU, SE, SK, TR, TW, UK, VN ]
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
pip install wheel
pip install -U platformio
cp ./platformio_override_sample.ini ./platformio_override.ini
- name: Download safeboot firmwares
uses: actions/download-artifact@v4
with:
pattern: tasmota32*
path: ./temp
- name: Move safeboot files
run: |
mkdir -p ./firmware/firmware
find ./temp -type f -exec cp -t ./firmware/firmware {} +
- name: Add "release" to footer
run: |
sed -i -e "s/TASMOTA_SHA_SHORT/TASMOTA_SHA_SHORT release-/g" ./tasmota/include/tasmota_version.h
- name: Run PlatformIO
run: platformio run -e ${{ matrix.variant }}-${{ matrix.language }}
- name: Upload language firmware artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.variant }}-${{ matrix.language }}
path: ./build_output
Release:
needs: [base-images, base32-images, language-images]
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Download all Tasmota artifacts
uses: actions/download-artifact@v4
with:
pattern: tasmota*
path: ./temp
- name: Move files
run: |
mkdir -p ./release
find ./temp -type f -exec cp -t ./release {} +
- name: Display structure of downloaded files
run: ls -R ./release/
- name: Release
uses: jason2866/action-gh-release@v1.2
#if: startsWith(github.ref, 'refs/tags/')
with:
tag_name: ${{ github.run_number }}
files: |
./release/tasmota*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Start_final_copy:
needs: Release
runs-on: ubuntu-latest
steps:
- name: Dispatch workflow in arendst/Tasmota-firmware
run: |
curl -X POST https://api.github.com/repos/arendst/Tasmota-firmware/actions/workflows/fetch_deploy.yml/dispatches \
-H 'Accept: application/vnd.github.everest-preview+json' \
-u ${{ secrets.API_TOKEN_GITHUB }} \
--data '{"ref": "gh_actions"}'
name: Build_firmware_master
on:
workflow_dispatch: # Start a workflow
push:
branches: master
paths-ignore:
- '.github/**' # Ignore changes towards the .github directory
- '**.md' # Do no build if *.md files changes
# Ensures that only one deploy task per branch/environment will run at a time.
concurrency:
group: environment-${{ github.ref }}
cancel-in-progress: true
jobs:
safeboot-images:
runs-on: ubuntu-latest
if: github.repository == 'arendst/Tasmota'
continue-on-error: true
strategy:
matrix:
variant:
- tasmota32-safeboot
- tasmota32solo1-safeboot
- tasmota32c2-safeboot
- tasmota32c3-safeboot
- tasmota32c3ser-safeboot
- tasmota32s2-safeboot
- tasmota32s2cdc-safeboot
- tasmota32s3-safeboot
- tasmota32s3ser-safeboot
- tasmota32c6-safeboot
- tasmota32c6ser-safeboot
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
pip install wheel
pip install -U platformio
cp ./platformio_override_sample.ini ./platformio_override.ini
- name: Add "release" to footer
run: |
sed -i -e "s/TASMOTA_SHA_SHORT/TASMOTA_SHA_SHORT release-/g" ./tasmota/include/tasmota_version.h
- name: Run PlatformIO
run: platformio run -e ${{ matrix.variant }}
- name: Upload safeboot firmware artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.variant }}
path: ./build_output
base-images:
runs-on: ubuntu-latest
if: github.repository == 'arendst/Tasmota'
continue-on-error: true
strategy:
matrix:
variant:
- tasmota
- tasmota-4M
- tasmota-minimal
- tasmota-display
- tasmota-ir
- tasmota-knx
- tasmota-lite
- tasmota-sensors
- tasmota-zbbridge
- tasmota-zigbee
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
pip install wheel
pip install -U platformio
cp ./platformio_override_sample.ini ./platformio_override.ini
- name: Add "release" to footer
run: |
sed -i -e "s/TASMOTA_SHA_SHORT/TASMOTA_SHA_SHORT release-/g" ./tasmota/include/tasmota_version.h
- name: Run PlatformIO
run: platformio run -e ${{ matrix.variant }}
- name: Upload firmware artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.variant }}
path: ./build_output
base32-images:
needs: safeboot-images
runs-on: ubuntu-latest
if: github.repository == 'arendst/Tasmota'
continue-on-error: true
strategy:
matrix:
variant:
- tasmota32
- tasmota32-zbbrdgpro
- tasmota32-webcam
- tasmota32-bluetooth
- tasmota32-nspanel
- tasmota32-display
- tasmota32-ir
- tasmota32-lvgl
- tasmota32c2
- tasmota32c3
- tasmota32c6
- tasmota32s2
- tasmota32s2cdc
- tasmota32s3
- tasmota32solo1
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
pip install wheel
pip install -U platformio
cp ./platformio_override_sample.ini ./platformio_override.ini
- name: Download safeboot firmwares
uses: actions/download-artifact@v4
with:
pattern: tasmota32*
path: ./temp
- name: Move safeboot files
run: |
mkdir -p ./firmware/firmware
find ./temp -type f -exec cp -t ./firmware/firmware {} +
- name: Add "release" to footer
run: |
sed -i -e "s/TASMOTA_SHA_SHORT/TASMOTA_SHA_SHORT release-/g" ./tasmota/include/tasmota_version.h
- name: Run PlatformIO
run: platformio run -e ${{ matrix.variant }}
- name: Upload firmware artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.variant }}
path: ./build_output
language-images:
needs: safeboot-images
runs-on: ubuntu-latest
if: github.repository == 'arendst/Tasmota'
continue-on-error: true
strategy:
matrix:
variant: [ tasmota, tasmota32 ]
language: [ AD, AF, BG, BR, CN, CZ, DE, ES, FR, FY, GR, HE, HU, IT, KO, LT, NL, PL, PT, RO, RU, SE, SK, TR, TW, UK, VN ]
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
pip install wheel
pip install -U platformio
cp ./platformio_override_sample.ini ./platformio_override.ini
- name: Download safeboot firmwares
uses: actions/download-artifact@v4
with:
pattern: tasmota32*
path: ./temp
- name: Move safeboot files
run: |
mkdir -p ./firmware/firmware
find ./temp -type f -exec cp -t ./firmware/firmware {} +
- name: Add "release" to footer
run: |
sed -i -e "s/TASMOTA_SHA_SHORT/TASMOTA_SHA_SHORT release-/g" ./tasmota/include/tasmota_version.h
- name: Run PlatformIO
run: platformio run -e ${{ matrix.variant }}-${{ matrix.language }}
- name: Upload language firmware artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.variant }}-${{ matrix.language }}
path: ./build_output
Release:
needs: [base-images, base32-images, language-images]
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Download all Tasmota artifacts
uses: actions/download-artifact@v4
with:
pattern: tasmota*
path: ./temp
- name: Move files
run: |
mkdir -p ./release
find ./temp -type f -exec cp -t ./release {} +
- name: Display structure of downloaded files
run: ls -R ./release/
- name: Release
uses: jason2866/action-gh-release@v1.2
#if: startsWith(github.ref, 'refs/tags/')
with:
tag_name: ${{ github.run_number }}
files: |
./release/tasmota*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Start_final_copy:
needs: Release
runs-on: ubuntu-latest
steps:
- name: Dispatch workflow in arendst/Tasmota-firmware
run: |
curl -X POST https://api.github.com/repos/arendst/Tasmota-firmware/actions/workflows/fetch_deploy.yml/dispatches \
-H 'Accept: application/vnd.github.everest-preview+json' \
-u ${{ secrets.API_TOKEN_GITHUB }} \
--data '{"ref": "gh_actions"}'

View File

@ -1,6 +1,25 @@
# Changelog
All notable changes to this project will be documented in this file.
## [Unreleased] - Development
## [14.6.0.1]
### Added
- Command `JsonPP 0..7` to enable (>0) JSON Pretty Print on user interfaces and set number of indents
- Command `JsonPP <command>|backlog <command>;...` to enable JSON PP only once
### Breaking Changed
### Changed
### Fixed
- Berry `bytes().asstring()` now truncates a string if buffer contains NULL (#23311)
- Berry string literals containing NULL are truncated (#23312)
### Removed
## [Released]
## [14.6.0] 20250416

View File

@ -114,58 +114,17 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
[Complete list](BUILDS.md) of available feature and sensors.
## Changelog v14.6.0 Ryan
## Changelog v14.6.0.1
### Added
- Filesystem command ``UfsList[2]``
- Extend command `GPIO` with different display options and allowing updating of module GPIO's in one go
- Support Vango Technologies V924x ultralow power, single-phase, power measurement [#23127](https://github.com/arendst/Tasmota/issues/23127)
- Support for HLK-LD2402 24GHz smart wave motion sensor [#23133](https://github.com/arendst/Tasmota/issues/23133)
- Support for Telnet server using command `Telnet <0|1|port>[,<IP filter>]` if enabled with `#define USE_TELNET`
- Support for XMODEM over serial and telnet if enabled with `#define USE_XYZMODEM`
- PZEM_AC device address in JSON and GUI [#23268](https://github.com/arendst/Tasmota/issues/23268)
- Allow acl in mqtt when client certificate is in use with `#define USE_MQTT_CLIENT_CERT` [#22998](https://github.com/arendst/Tasmota/issues/22998)
- AlpineJS 2.8.2 - optional for now [#23259](https://github.com/arendst/Tasmota/issues/23259)
- ESP32 show network interface priority in `Status 5` debug logging [#23302](https://github.com/arendst/Tasmota/issues/23302)
- Berry experimental driver for AXP2101 for M5Core2v1.1 [#23039](https://github.com/arendst/Tasmota/issues/23039)
- Berry `tasmota.when_network_up()` and simplified Matter using it [#23057](https://github.com/arendst/Tasmota/issues/23057)
- Berry `introspect.solidified()` to know if a Berry object is solidified or in RAM [#23063](https://github.com/arendst/Tasmota/issues/23063)
- Berry `global.undef()` to undefine a global variable [#23073](https://github.com/arendst/Tasmota/issues/23073)
- Berry load `.tapp` files in `/.extensions/` then in `/` [#23113](https://github.com/arendst/Tasmota/issues/23113)
- Berry `re.dump()` [#23162](https://github.com/arendst/Tasmota/issues/23162)
- Berry `bytes.add()` now accepts 3-bytes values [#23200](https://github.com/arendst/Tasmota/issues/23200)
- Berry expose `esp_http_server` for websockets [#23206](https://github.com/arendst/Tasmota/issues/23206)
- Matter prepare for ICD cluster [#23158](https://github.com/arendst/Tasmota/issues/23158)
- LVGL experimental mirroring of display on Web UI [#23041](https://github.com/arendst/Tasmota/issues/23041)
- HASPmota autostart when `pages.jsonl` exists [#23181](https://github.com/arendst/Tasmota/issues/23181)
- Command `JsonPP 0..7` to enable (>0) JSON Pretty Print on user interfaces and set number of indents
- Command `JsonPP <command>|backlog <command>;...` to enable JSON PP only once
### Breaking Changed
- Berry remove `Leds.create_matrix` from the standard library waiting for reimplementation [#23114](https://github.com/arendst/Tasmota/issues/23114)
- HASPmota added `y2_min` and `y2_max` to control the second series of `chart` [#23287](https://github.com/arendst/Tasmota/issues/23287)
- HASPmota default theme is now Tasmota-style [#23288](https://github.com/arendst/Tasmota/issues/23288)
### Changed
- ESP32 Platform from 2025.02.30 to 2025.04.30, Framework (Arduino Core) from v3.1.1.250203 to v3.1.3.250411 and IDF from v5.3.2.250120 to 5.3.2.250403 [#23280](https://github.com/arendst/Tasmota/issues/23280)
- Output of commands `GPIO` and `GPIOs` swapped
- Smoothen light gamma curve when using `Fade` [#23230](https://github.com/arendst/Tasmota/issues/23230)
- RCSwitch `RCSWITCH_SEPARATION_LIMIT` from 4100 to 3600
- GPIOViewer from v1.6.1 to v1.6.2 (No functional change)
- HLK-LD2402 updates for firmware 3.3.5+ [#23281](https://github.com/arendst/Tasmota/issues/23281)
- ESP8266 enable FTP for >= 4MB variants [#23120](https://github.com/arendst/Tasmota/issues/23120)
- ESP32 enable webcam version 2 [#18732](https://github.com/arendst/Tasmota/issues/18732)
- Berry update flasher for Sonoff ZBBridge Pro [#23136](https://github.com/arendst/Tasmota/issues/23136)
- Berry `re` now accepts `bytes()` as precompiled patterns, added `re.compilebytes()` [#23149](https://github.com/arendst/Tasmota/issues/23149)
- LVGL, prepare for HASPmota theme, change: no-grow when clicked, DPI set to 160 [#23040](https://github.com/arendst/Tasmota/issues/23040)
- LVGL Mirroring add checkbox to enable/disable the feature (in the iterim for a better solution) [#23047](https://github.com/arendst/Tasmota/issues/23047)
- Leds Panel add checkbox to enable/disable the feature (in the iterim for a better solution) [#23048](https://github.com/arendst/Tasmota/issues/23048)
### Fixed
- Too many zeros in RCSwitch received data regression from v14.4.1.4 [#23050](https://github.com/arendst/Tasmota/issues/23050)
- INA226 driver fixes [#23197](https://github.com/arendst/Tasmota/issues/23197)
- TLS increase timeout and fix crash [#23249](https://github.com/arendst/Tasmota/issues/23249)
- ESP32 receive incomplete serial data over 128 bytes [#23156](https://github.com/arendst/Tasmota/issues/23156)
- ESP32 intermittent exception on WiFi AP cannot be reached [#23115](https://github.com/arendst/Tasmota/issues/23115)
- ESP32-C3 WiFi sleep [#23096](https://github.com/arendst/Tasmota/issues/23096)
- Berry prevent `import` from hiding a solidified class [#23112](https://github.com/arendst/Tasmota/issues/23112)
- Berry `readline` when a line is exactly 98 characters [#23276](https://github.com/arendst/Tasmota/issues/23276)
- Berry `bytes().asstring()` now truncates a string if buffer contains NULL [#23311](https://github.com/arendst/Tasmota/issues/23311)
- Berry string literals containing NULL are truncated [#23312](https://github.com/arendst/Tasmota/issues/23312)
### Removed

View File

@ -0,0 +1,128 @@
/**
* Base64 encoding and decoding of strings. Uses '+' for 62, '/' for 63, '=' for padding
*/
#include "base64.hpp"
unsigned char binary_to_base64(unsigned char v) {
// Capital letters - 'A' is ascii 65 and base64 0
if(v < 26) return v + 'A';
// Lowercase letters - 'a' is ascii 97 and base64 26
if(v < 52) return v + 71;
// Digits - '0' is ascii 48 and base64 52
if(v < 62) return v - 4;
// '+' is ascii 43 and base64 62
if(v == 62) return '+';
// '/' is ascii 47 and base64 63
if(v == 63) return '/';
return 64;
}
unsigned char base64_to_binary(unsigned char c) {
// Capital letters - 'A' is ascii 65 and base64 0
if('A' <= c && c <= 'Z') return c - 'A';
// Lowercase letters - 'a' is ascii 97 and base64 26
if('a' <= c && c <= 'z') return c - 71;
// Digits - '0' is ascii 48 and base64 52
if('0' <= c && c <= '9') return c + 4;
// '+' is ascii 43 and base64 62
if(c == '+') return 62;
// '/' is ascii 47 and base64 63
if(c == '/') return 63;
return 255;
}
unsigned int encode_base64_length(unsigned int input_length) {
return (input_length + 2)/3*4;
}
unsigned int decode_base64_length(unsigned char input[]) {
unsigned char *start = input;
while(base64_to_binary(input[0]) < 64) {
++input;
}
unsigned int input_length = input - start;
unsigned int output_length = input_length/4*3;
switch(input_length % 4) {
default: return output_length;
case 2: return output_length + 1;
case 3: return output_length + 2;
}
}
unsigned int encode_base64(unsigned char input[], unsigned int input_length, unsigned char output[]) {
unsigned int full_sets = input_length/3;
// While there are still full sets of 24 bits...
for(unsigned int i = 0; i < full_sets; ++i) {
output[0] = binary_to_base64( input[0] >> 2);
output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4);
output[2] = binary_to_base64((input[1] & 0x0F) << 2 | input[2] >> 6);
output[3] = binary_to_base64( input[2] & 0x3F);
input += 3;
output += 4;
}
switch(input_length % 3) {
case 0:
output[0] = '\0';
break;
case 1:
output[0] = binary_to_base64( input[0] >> 2);
output[1] = binary_to_base64((input[0] & 0x03) << 4);
output[2] = '=';
output[3] = '=';
output[4] = '\0';
break;
case 2:
output[0] = binary_to_base64( input[0] >> 2);
output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4);
output[2] = binary_to_base64((input[1] & 0x0F) << 2);
output[3] = '=';
output[4] = '\0';
break;
}
return encode_base64_length(input_length);
}
unsigned int decode_base64(unsigned char input[], unsigned char output[]) {
unsigned int output_length = decode_base64_length(input);
// While there are still full sets of 24 bits...
for(unsigned int i = 2; i < output_length; i += 3) {
output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2;
output[2] = base64_to_binary(input[2]) << 6 | base64_to_binary(input[3]);
input += 4;
output += 3;
}
switch(output_length % 3) {
case 1:
output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
break;
case 2:
output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2;
break;
}
return output_length;
}

View File

@ -69,127 +69,4 @@ unsigned int encode_base64(unsigned char input[], unsigned int input_length, uns
*/
unsigned int decode_base64(unsigned char input[], unsigned char output[]);
unsigned char binary_to_base64(unsigned char v) {
// Capital letters - 'A' is ascii 65 and base64 0
if(v < 26) return v + 'A';
// Lowercase letters - 'a' is ascii 97 and base64 26
if(v < 52) return v + 71;
// Digits - '0' is ascii 48 and base64 52
if(v < 62) return v - 4;
// '+' is ascii 43 and base64 62
if(v == 62) return '+';
// '/' is ascii 47 and base64 63
if(v == 63) return '/';
return 64;
}
unsigned char base64_to_binary(unsigned char c) {
// Capital letters - 'A' is ascii 65 and base64 0
if('A' <= c && c <= 'Z') return c - 'A';
// Lowercase letters - 'a' is ascii 97 and base64 26
if('a' <= c && c <= 'z') return c - 71;
// Digits - '0' is ascii 48 and base64 52
if('0' <= c && c <= '9') return c + 4;
// '+' is ascii 43 and base64 62
if(c == '+') return 62;
// '/' is ascii 47 and base64 63
if(c == '/') return 63;
return 255;
}
unsigned int encode_base64_length(unsigned int input_length) {
return (input_length + 2)/3*4;
}
unsigned int decode_base64_length(unsigned char input[]) {
unsigned char *start = input;
while(base64_to_binary(input[0]) < 64) {
++input;
}
unsigned int input_length = input - start;
unsigned int output_length = input_length/4*3;
switch(input_length % 4) {
default: return output_length;
case 2: return output_length + 1;
case 3: return output_length + 2;
}
}
unsigned int encode_base64(unsigned char input[], unsigned int input_length, unsigned char output[]) {
unsigned int full_sets = input_length/3;
// While there are still full sets of 24 bits...
for(unsigned int i = 0; i < full_sets; ++i) {
output[0] = binary_to_base64( input[0] >> 2);
output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4);
output[2] = binary_to_base64((input[1] & 0x0F) << 2 | input[2] >> 6);
output[3] = binary_to_base64( input[2] & 0x3F);
input += 3;
output += 4;
}
switch(input_length % 3) {
case 0:
output[0] = '\0';
break;
case 1:
output[0] = binary_to_base64( input[0] >> 2);
output[1] = binary_to_base64((input[0] & 0x03) << 4);
output[2] = '=';
output[3] = '=';
output[4] = '\0';
break;
case 2:
output[0] = binary_to_base64( input[0] >> 2);
output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4);
output[2] = binary_to_base64((input[1] & 0x0F) << 2);
output[3] = '=';
output[4] = '\0';
break;
}
return encode_base64_length(input_length);
}
unsigned int decode_base64(unsigned char input[], unsigned char output[]) {
unsigned int output_length = decode_base64_length(input);
// While there are still full sets of 24 bits...
for(unsigned int i = 2; i < output_length; i += 3) {
output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2;
output[2] = base64_to_binary(input[2]) << 6 | base64_to_binary(input[3]);
input += 4;
output += 3;
}
switch(output_length % 3) {
case 1:
output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
break;
case 2:
output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2;
break;
}
return output_length;
}
#endif // ifndef

View File

@ -806,7 +806,8 @@ static int m_asstring(bvm *vm)
{
buf_impl attr = bytes_check_data(vm, 0);
check_ptr(vm, &attr);
be_pushnstring(vm, (const char*) attr.bufptr, attr.len);
size_t safe_len = strnlen((const char*) attr.bufptr, attr.len);
be_pushnstring(vm, (const char*) attr.bufptr, safe_len);
be_return(vm);
}

View File

@ -284,7 +284,8 @@ static void tr_string(blexer *lexer)
break;
}
}
lexer->buf.len = dst - lexbuf(lexer);
size_t len = dst - lexbuf(lexer);
lexer->buf.len = strnlen(lexbuf(lexer), len);
}
static int skip_newline(blexer *lexer)

View File

@ -354,3 +354,9 @@ assert(b.appendb64(c, 2) == bytes("AABBCC49673D3D"))
b = bytes("AABBCC")
assert(bytes().fromstring(bytes("11").tob64()) == bytes('45513D3D'))
assert(b.appendb64(c, 1, 1) == bytes("AABBCC45513D3D"))
#- asstring truncates if NULL is present -#
s=bytes("414243").asstring()
assert(size(s) == 3)
s=bytes("410000").asstring()
assert(size(s) == 1)

View File

@ -84,3 +84,6 @@ var malformed_numbers = [
for i : malformed_numbers
test_source(i, 'malformed number')
end
#- ensure that string literal with NULL character is truncated -#
assert(size('aa\000bb\000cc') == 2)

View File

@ -272,6 +272,7 @@
// Commands tasmota.ino
#define D_CMND_BACKLOG "Backlog"
#define D_CMND_JSON "Json"
#define D_CMND_JSON_PP "JsonPP"
#define D_CMND_DELAY "Delay"
#define D_CMND_NODELAY "NoDelay"
#define D_CMND_STATUS "Status"

View File

@ -16,9 +16,10 @@ class TASCONSOLE {
virtual void begin(uint32_t) = 0;
virtual void flush() = 0;
virtual size_t println() = 0;
virtual size_t print(char *) = 0;
virtual size_t printf(const char*, char *, const char*&, const char*&, const char*&) = 0;
virtual size_t printf(char *) = 0;
virtual size_t println(const char*) = 0;
virtual size_t print(char*) = 0;
virtual size_t printf(const char*, char*, const char*&, const char*&, const char*&) = 0;
virtual size_t printf(char*) = 0;
virtual size_t read() = 0;
virtual size_t write(uint8_t) = 0;
virtual size_t write(const uint8_t *buf, size_t size) = 0;
@ -49,7 +50,11 @@ public:
return object->println();
}
size_t print(char * string) {
size_t println(const char *string) {
return object->println(string);
}
size_t print(char *string) {
return object->print(string);
}
@ -96,10 +101,15 @@ public:
object.flush();
}
size_t println() override {
size_t println() override {
return object.println();
}
size_t print(char * string) override {
size_t println(const char *string) override {
return object.println(string);
}
size_t print(char *string) override {
return object.print(string);
}

View File

@ -252,9 +252,7 @@ typedef union {
uint32_t spare13 : 1; // bit 13
uint32_t spare14 : 1; // bit 14
uint32_t spare15 : 1; // bit 15
uint32_t spare16 : 1; // bit 16
uint32_t spare17 : 1; // bit 17
uint32_t spare18 : 1; // bit 18
uint32_t json_pretty_print : 3; // bit 16.18 (v14.6.0.1) - JSON pretty print log data no or indent
uint32_t dali_group_sliders : 5; // bit 19.23 (v14.3.0.3) - (DALI) Number of group sliders 0 to 16
uint32_t FTP_Mode : 2; // bit 24/25
uint32_t tariff_forced : 2; // bit 26/27 (v12.4.0.2) - Energy forced tariff : 0=tariff change on time, 1|2=tariff forced

View File

@ -22,6 +22,6 @@
#define TASMOTA_SHA_SHORT // Filled by Github sed
const uint32_t TASMOTA_VERSION = 0x0E060000; // 14.6.0.0
const uint32_t TASMOTA_VERSION = 0x0E060001; // 14.6.0.1
#endif // _TASMOTA_VERSION_H_

View File

@ -28,7 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v9.4.0.1 - Last update 12.03.2025
* Updated until v9.4.0.1 - Last update 17.04.2025
\*********************************************************************/
#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -52,7 +52,7 @@
// Common
#define D_ABSOLUTE_HUMIDITY "Umidità ass"
#define D_ADDRESS "Address"
#define D_ADDRESS "Indirizzo"
#define D_ADMIN "Admin"
#define D_AIR_QUALITY "Qualità dell'aria"
#define D_AP "AP" // Access Point

View File

@ -2611,6 +2611,95 @@ bool GetLog(uint32_t req_loglevel, uint32_t* index_p, char** entry_pp, size_t* l
return false;
}
bool LogDataJsonPrettyPrint(const char *log_line, uint32_t log_data_len, std::function<void(const char*, uint32_t)> println) {
// log_line:
// 14:49:36.123 MQTT: stat/wemos5/RESULT = {"POWER":"OFF"}
// 14:30:16.749-172/38 MQT: tele/atomlite3/INFO3 = {"Info3":{"RestartReason":"Vbat power on reset","BootCount":74}}
if (!Settings->mbflag2.json_pretty_print) { return false; } // [JsonPP] Number of indents
char *bch = (char*)memchr(log_line, '{', log_data_len);
if (!bch) { return false; } // No JSON data
uint32_t pos_brace = bch - log_line;
uint32_t cnt_brace = 0; // {}
uint32_t len_mxtime = strchr(log_line, ' ') - log_line +2;
uint32_t pos_value_pair = pos_brace;
uint32_t cnt_bracket = 0; // []
uint32_t cnt_Indent = 0; // indent
bool quotes = false; // ""
bool bracket_comma = false;
bool pls_print = false;
for (uint32_t i = pos_brace; i < log_data_len; i++) {
char curchar = log_line[i];
char nxtchar = log_line[i +1];
cnt_Indent = cnt_brace + cnt_bracket;
if (curchar == '{') {
cnt_brace++;
pls_print = true;
}
else if (cnt_brace) {
if (nxtchar == '}') {
pls_print = true;
}
if (curchar == '}') {
cnt_brace--;
if (cnt_brace) {
if (nxtchar != ',') {
pls_print = true;
cnt_Indent = cnt_brace + cnt_bracket;
}
} else {
pls_print = true;
cnt_Indent = 0;
}
}
else if (curchar == '[') {
cnt_bracket++;
if (nxtchar == '[') {
pls_print = true;
}
}
else if (curchar == ']') {
cnt_bracket--;
if (nxtchar == ',') {
bracket_comma = true;
}
else {
pls_print = true;
if ((nxtchar == ']') || (nxtchar == '}')) {
cnt_Indent = cnt_brace + cnt_bracket;
}
}
}
else if (curchar == '"') {
quotes ^= 1;
}
else if (curchar == ',') {
if (!quotes && (!cnt_bracket || bracket_comma)) {
bracket_comma = false;
pls_print = true;
}
}
}
if (pls_print) {
pls_print = false;
uint32_t len_id = (pos_brace == i) ? pos_brace +1 : len_mxtime;
uint32_t len_indent = cnt_Indent * Settings->mbflag2.json_pretty_print;
uint32_t len_value_pair = (i - pos_value_pair) +1;
uint32_t len_full = len_id + len_indent + len_value_pair +1;
char line[len_full]; // Known max value pair size is 152
strlcpy(line, log_line, len_id); // Repeat mxtime
sprintf(line, "%s%*s", line, len_indent, ""); // Add space indent
strncat(line, log_line + pos_value_pair, len_value_pair);
println(line, strlen(line)); // Callback for output
pos_value_pair = i +1;
}
}
return true;
}
uint32_t HighestLogLevel(void) {
uint32_t highest_loglevel = TasmotaGlobal.seriallog_level;
if (Settings->seriallog_level > highest_loglevel) { highest_loglevel = Settings->seriallog_level; }
@ -2660,7 +2749,9 @@ void AddLogData(uint32_t loglevel, const char* log_data, const char* log_data_pa
if ((loglevel <= TasmotaGlobal.seriallog_level) &&
(TasmotaGlobal.masterlog_level <= TasmotaGlobal.seriallog_level)) {
TasConsole.printf("%s%s%s%s\r\n", mxtime, log_data, log_data_payload, log_data_retained);
if (!Settings->mbflag2.json_pretty_print || !strchr(log_data_payload, '{')) {
TasConsole.printf("%s%s%s%s\r\n", mxtime, log_data, log_data_payload, log_data_retained);
}
}
if (!TasmotaGlobal.log_buffer) { return; } // Leave now if there is no buffer available
@ -2707,6 +2798,14 @@ void AddLogData(uint32_t loglevel, const char* log_data, const char* log_data_pa
// These calls fail to show initial logging
log_line += 2; // Skip log_buffer_pointer and loglevel
// 14:49:36.123 MQTT: stat/wemos5/RESULT = {"POWER":"OFF"}
// 14:30:16.749-172/38 MQT: tele/atomlite3/INFO3 = {"Info3":{"RestartReason":"Vbat power on reset","BootCount":74}}
if ((loglevel <= TasmotaGlobal.seriallog_level) &&
(TasmotaGlobal.masterlog_level <= TasmotaGlobal.seriallog_level)) {
LogDataJsonPrettyPrint(log_line, log_data_len, TasConsoleLDJsonPPCb);
}
#ifdef USE_SERIAL_BRIDGE
if (loglevel <= TasmotaGlobal.seriallog_level) {
SerialBridgeWrite(log_line, log_data_len);
@ -2723,6 +2822,10 @@ void AddLogData(uint32_t loglevel, const char* log_data, const char* log_data_pa
}
}
void TasConsoleLDJsonPPCb(const char* line, uint32_t len) {
TasConsole.println(line);
}
void AddLog(uint32_t loglevel, PGM_P formatP, ...) {
#ifdef ESP32
if (xPortInIsrContext()) {

View File

@ -29,18 +29,18 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix
D_CMND_VOLTAGE_RESOLUTION "|" D_CMND_FREQUENCY_RESOLUTION "|" D_CMND_CURRENT_RESOLUTION "|" D_CMND_ENERGY_RESOLUTION "|" D_CMND_WEIGHT_RESOLUTION "|"
D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOREAD "|" D_CMND_GPIOS "|" D_CMND_TEMPLATE "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|"
D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|"
#ifdef USE_UFILESYS
D_CMND_FILELOG "|"
#endif // USE_UFILESYS
D_CMND_SERIALBUFFER "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|" D_CMND_SERIALCONFIG "|" D_CMND_SERIALDELIMITER "|"
D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|" D_CMND_WIFI "|" D_CMND_DNSTIMEOUT "|"
D_CMND_DEVICENAME "|" D_CMND_FN "|" D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|"
D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_LEDMASK "|" D_CMND_LEDPWM_ON "|" D_CMND_LEDPWM_OFF "|" D_CMND_LEDPWM_MODE "|"
D_CMND_WIFIPOWER "|" D_CMND_TEMPOFFSET "|" D_CMND_HUMOFFSET "|" D_CMND_SPEEDUNIT "|" D_CMND_GLOBAL_TEMP "|" D_CMND_GLOBAL_HUM"|" D_CMND_GLOBAL_PRESS "|" D_CMND_SWITCHTEXT "|" D_CMND_WIFISCAN "|" D_CMND_WIFITEST "|"
D_CMND_ZIGBEE_BATTPERCENT "|"
D_CMND_DEVICENAME "|" D_CMND_FN "|" D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|"
D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|" D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|"
D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_LEDMASK "|" D_CMND_LEDPWM_ON "|" D_CMND_LEDPWM_OFF "|" D_CMND_LEDPWM_MODE "|"
D_CMND_WIFIPOWER "|" D_CMND_TEMPOFFSET "|" D_CMND_HUMOFFSET "|" D_CMND_SPEEDUNIT "|" D_CMND_GLOBAL_TEMP "|" D_CMND_GLOBAL_HUM"|" D_CMND_GLOBAL_PRESS "|" D_CMND_SWITCHTEXT "|"
D_CMND_WIFISCAN "|" D_CMND_WIFITEST "|" D_CMND_ZIGBEE_BATTPERCENT "|"
#ifdef USE_I2C
D_CMND_I2CSCAN "|" D_CMND_I2CDRIVER "|"
#endif
#ifdef USE_DEVICE_GROUPS
D_CMND_DEVGROUP_NAME "|"
#ifdef USE_DEVICE_GROUPS_SEND
@ -48,14 +48,20 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix
#endif // USE_DEVICE_GROUPS_SEND
D_CMND_DEVGROUP_SHARE "|" D_CMND_DEVGROUPSTATUS "|" D_CMND_DEVGROUP_TIE "|"
#endif // USE_DEVICE_GROUPS
D_CMND_SETSENSOR "|" D_CMND_SENSOR "|" D_CMND_DRIVER "|" D_CMND_JSON
#ifdef USE_UFILESYS
D_CMND_FILELOG "|"
#endif // USE_UFILESYS
#ifdef ESP32
"|Info|"
"|Info|"
#if defined(SOC_TOUCH_VERSION_1) || defined(SOC_TOUCH_VERSION_2)
D_CMND_TOUCH_CAL "|" D_CMND_TOUCH_THRES "|"
#endif // ESP32 SOC_TOUCH_VERSION_1 or SOC_TOUCH_VERSION_2
D_CMND_CPU_FREQUENCY
#endif // ESP32
D_CMND_SETSENSOR "|" D_CMND_SENSOR "|" D_CMND_DRIVER "|" D_CMND_JSON "|" D_CMND_JSON_PP
#endif //FIRMWARE_MINIMAL
;
@ -72,18 +78,18 @@ void (* const TasmotaCommand[])(void) PROGMEM = {
&CmndVoltageResolution, &CmndFrequencyResolution, &CmndCurrentResolution, &CmndEnergyResolution, &CmndWeightResolution,
&CmndModule, &CmndModules, &CmndGpio, &CmndGpioRead, &CmndGpios, &CmndTemplate, &CmndPwm, &CmndPwmfrequency, &CmndPwmrange,
&CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport,
#ifdef USE_UFILESYS
&CmndFilelog,
#endif // USE_UFILESYS
&CmndSerialBuffer, &CmndSerialSend, &CmndBaudrate, &CmndSerialConfig, &CmndSerialDelimiter,
&CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig, &CmndWifi, &CmndDnsTimeout,
&CmndDevicename, &CmndFriendlyname, &CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd,
&CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndLedPwmOn, &CmndLedPwmOff, &CmndLedPwmMode,
&CmndWifiPower, &CmndTempOffset, &CmndHumOffset, &CmndSpeedUnit, &CmndGlobalTemp, &CmndGlobalHum, &CmndGlobalPress, &CmndSwitchText, &CmndWifiScan, &CmndWifiTest,
&CmndBatteryPercent,
&CmndDevicename, &CmndFriendlyname, &CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset,
&CmndTime, &CmndTimezone, &CmndTimeStd, &CmndTimeDst, &CmndAltitude,
&CmndLedPower, &CmndLedState, &CmndLedMask, &CmndLedPwmOn, &CmndLedPwmOff, &CmndLedPwmMode,
&CmndWifiPower, &CmndTempOffset, &CmndHumOffset, &CmndSpeedUnit, &CmndGlobalTemp, &CmndGlobalHum, &CmndGlobalPress, &CmndSwitchText,
&CmndWifiScan, &CmndWifiTest, &CmndBatteryPercent,
#ifdef USE_I2C
&CmndI2cScan, &CmndI2cDriver,
#endif
#ifdef USE_DEVICE_GROUPS
&CmndDevGroupName,
#ifdef USE_DEVICE_GROUPS_SEND
@ -91,14 +97,20 @@ void (* const TasmotaCommand[])(void) PROGMEM = {
#endif // USE_DEVICE_GROUPS_SEND
&CmndDevGroupShare, &CmndDevGroupStatus, &CmndDevGroupTie,
#endif // USE_DEVICE_GROUPS
&CmndSetSensor, &CmndSensor, &CmndDriver, &CmndJson
#ifdef USE_UFILESYS
&CmndFilelog,
#endif // USE_UFILESYS
#ifdef ESP32
, &CmndInfo,
&CmndInfo,
#if defined(SOC_TOUCH_VERSION_1) || defined(SOC_TOUCH_VERSION_2)
&CmndTouchCal, &CmndTouchThres,
#endif // ESP32 SOC_TOUCH_VERSION_1 or SOC_TOUCH_VERSION_2
&CmndCpuFrequency
&CmndCpuFrequency,
#endif // ESP32
&CmndSetSensor, &CmndSensor, &CmndDriver, &CmndJson, &CmndJsonPP
#endif //FIRMWARE_MINIMAL
};
@ -652,6 +664,35 @@ void CmndDelay(void) {
ResponseCmndNumber(bl_delay);
}
void CmndJsonPP(void) {
// JsonPP 0 - Disable JSON Pretty Print
// JsonPP 1..7 - Enable JSON Pretty Print with 1..7 indent spaces
// JsonPP <command> - If not enabled, enable JSON PP with 1 indent, execute command and restore JsonPP
// JsonPP Backlog <command>;... - If not enabled, enable JSON PP with 1 indent, execute command and restore JsonPP
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 7)) {
Settings->mbflag2.json_pretty_print = XdrvMailbox.payload;
}
else if (XdrvMailbox.data_len) {
uint32_t last_json_pretty_print = Settings->mbflag2.json_pretty_print;
if (0 == Settings->mbflag2.json_pretty_print) {
Settings->mbflag2.json_pretty_print = 1; // Default 1 indent if not set
}
bool backlog = (strchr(XdrvMailbox.data, ';') != nullptr);
String cmnds = XdrvMailbox.data;
if (!last_json_pretty_print && backlog) {
cmnds += ";_JsonPP ";
cmnds += last_json_pretty_print; // Restore JsonPP after execution of backlog commands
}
ExecuteCommand((char*)cmnds.c_str(), SRC_IGNORE);
if (!last_json_pretty_print && !backlog) { // Restore JsonPP after execution of single command
Settings->mbflag2.json_pretty_print = last_json_pretty_print;
}
ResponseClear();
return;
}
ResponseCmndNumber(Settings->mbflag2.json_pretty_print);
}
void CmndPower(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.devices_present)) {

View File

@ -500,6 +500,7 @@ struct WEB {
bool upload_services_stopped = false;
bool reset_web_log_flag = false; // Reset web console log
bool initial_config = false;
bool cflg;
} Web;
/*********************************************************************************************/
@ -3737,18 +3738,24 @@ void HandleConsoleRefresh(void) {
index = 0;
Web.reset_web_log_flag = true;
}
bool cflg = (index);
Web.cflg = (index);
char* line;
size_t len;
while (GetLog(Settings->weblog_level, &index, &line, &len)) {
if (cflg) { WSContentSend_P(PSTR("\n")); }
WSContentSend(line, len -1);
cflg = true;
if (!LogDataJsonPrettyPrint(line, len -1, WSContentSendLDJsonPPCb)) {
WSContentSendLDJsonPPCb(line, len -1);
}
}
WSContentSend_P(PSTR("}1"));
WSContentEnd();
}
void WSContentSendLDJsonPPCb(const char* line, uint32_t len) {
if (Web.cflg) { WSContentSend_P(PSTR("\n")); }
WSContentSend(line, len);
Web.cflg = true;
}
/********************************************************************************************/
void HandleNotFound(void) {

View File

@ -65,8 +65,9 @@ enum XTrim1aModes { XYZT_NONE, XYZT_TRIM, XYZT_AUTO };
enum XReceiveModes { XYZD_NONE, XYZD_SOH, XYZD_BLK1, XYZD_BLK2, XYZD_DATA };
enum XYZFileSteps { XYZM_IDLE,
XYZM_SEND, XYZM_SEOT, XYZM_ACKT, XYZM_COMPLETE, XYZM_ERROR, XYZM_DONE,
XYZM_RECEIVE, XYZM_RCV_START, XYZM_RCV_EOT };
XYZM_SEND, XYZM_SND_ACK,
XYZM_RECEIVE, XYZM_RCV_START, XYZM_RCV_EOT,
XYZM_ERROR, XYZM_DONE };
enum XReceiveStates { XYZS_OK, XYZS_TIMEOUT, XYZS_EOT, XYZS_CAN, XYZS_OTHER, XYZS_CHECKSUM, XYZS_PACKET, XYZS_FILE };
@ -712,8 +713,7 @@ void XModemSendStart(void) {
XYZFile.step = XYZM_SEND;
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Send started"));
} else {
XYZFile.step = XYZM_SEOT;
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Send aborted"));
XYZModemInit();
}
}
@ -738,8 +738,8 @@ bool XYZModemLoop(void) {
// *** Send
case XYZM_SEND: { // *** Handle file send using XModem - upload
if (XYZModemFileAvailable()) {
if (XYZFile.byte_counter && !(XYZFile.byte_counter % 10240)) { // Show progress every 10kB
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Progress %d kB"), XYZFile.byte_counter / 1024);
if (XYZFile.byte_counter && !(XYZFile.byte_counter % 10240)) { // Show progress every 10KB
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Progress %d KB"), XYZFile.byte_counter / 1024);
}
if (!XYZModemSend(XYZModem.packet_no)) {
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Packet %d send failed"), XYZModem.packet_no);
@ -748,21 +748,16 @@ bool XYZModemLoop(void) {
}
XYZModem.packet_no++;
} else {
XYZFile.step = XYZM_SEOT;
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Send %d bytes"), XYZFile.size);
// Once the last block is ACKed by the target, the transfer should be finalized by an
// EOT (ASCII 0x04) packet from the source. This packet is confirmed via XModem ACK
// from the target.
XYZModemWrite(XYZM_EOT); // *** Send EOT
XYZModem.timeout = millis() + (30 * 1000); // Allow 30 seconds to receive EOT ACK
XYZFile.step = XYZM_SND_ACK;
}
break;
}
case XYZM_SEOT: { // *** Send EOT and wait for ACK
// Once the last block is ACKed by the target, the transfer should be finalized by an
// EOT (ASCII 0x04) packet from the source. This packet is confirmed via XModem ACK
// from the target.
XYZModemWrite(XYZM_EOT);
XYZModem.timeout = millis() + (30 * 1000); // Allow 30 seconds to receive EOT ACK
XYZFile.step = XYZM_ACKT;
break;
}
case XYZM_ACKT: { // *** Send EOT and wait for ACK
case XYZM_SND_ACK: { // *** Wait for ACK
// The ACK for the last XModem data packet may take much longer (1-3 seconds) than prior
// data packets to be received.
if (millis() > XYZModem.timeout) {
@ -778,9 +773,8 @@ bool XYZModemLoop(void) {
return true;
}
else if (XYZM_ACK == xmodem_ack) {
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Successful"));
XYZModem.timeout = millis() + (30 * 1000); // Allow 30 seconds
XYZFile.byte_counter = 0;
// !!! Send an AddLog here as it previously would interfere with XModem protocol !!!
AddLog(LOG_LEVEL_INFO, PSTR("XMD: Send %d bytes succesful"), XYZFile.size);
XYZFile.step = XYZM_DONE;
}
}
@ -807,16 +801,15 @@ bool XYZModemLoop(void) {
break;
}
case XYZM_RCV_START: {
if (XYZFile.byte_counter && !(XYZFile.byte_counter % 10240)) { // Show progress every 10kB
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Progress %d kB"), XYZFile.byte_counter / 1024);
if (XYZFile.byte_counter && !(XYZFile.byte_counter % 10240)) { // Show progress every 10KB
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Progress %d KB"), XYZFile.byte_counter / 1024);
}
int result = XYZModemReceive(XYZModem.packet_no);
if (result) {
switch (result) {
case XYZS_EOT: {
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Received %d bytes"), XYZFile.byte_counter);
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Successful"));
XYZModemFileWriteEot(1);
AddLog(LOG_LEVEL_INFO, PSTR("XMD: Received %d bytes succesful"), XYZFile.byte_counter);
XYZFile.step = XYZM_DONE;
break;
}
@ -875,17 +868,6 @@ bool XYZModemLoop(void) {
break;
}
// *** Finish
case XYZM_COMPLETE: { // *** Wait for send complete
if (millis() > XYZModem.timeout) {
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Timeout"));
XYZFile.step = XYZM_ERROR;
return true;
} else {
XYZFile.state = XYZM_COMPLETE;
XYZFile.step = XYZM_DONE;
}
break;
}
case XYZM_ERROR:
XYZFile.state = XYZM_ERROR;
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Failed"));

View File

@ -97,6 +97,25 @@ void TelnetWriteColor(uint32_t color) {
}
}
void TelnetLDJsonPPCb(const char* line, uint32_t len) {
uint32_t textcolor = Telnet.color[Telnet.prompt];
uint32_t diffcolor = textcolor;
if ((textcolor >= 30) && (textcolor <= 37)) {
diffcolor += 60; // Highlight color
}
else if ((textcolor >= 90) && (textcolor <= 97)) {
diffcolor -= 60; // Lowlight color
}
char* time_end = (char*)memchr(line, ' ', len); // Find first word (usually 14:49:36.123-017)
uint32_t time_len = time_end - line;
TelnetWriteColor(diffcolor);
Telnet.client.write(line, time_len);
TelnetWriteColor(textcolor);
Telnet.client.write(time_end, len - time_len);
TelnetWriteColor(0);
Telnet.client.println();
}
void TelnetWrite(char *line, uint32_t len) {
if (Telnet.client) {
if (3 == Telnet.prompt) { // Print linefeed for non-requested data
@ -104,22 +123,10 @@ void TelnetWrite(char *line, uint32_t len) {
Telnet.client.println();
}
// line = 14:49:36.123-017 MQTT: stat/wemos5/RESULT = {"POWER":"OFF"}
uint32_t textcolor = Telnet.color[Telnet.prompt];
uint32_t diffcolor = textcolor;
if ((textcolor >= 30) && (textcolor <= 37)) {
diffcolor += 60; // Highlight color
if (!LogDataJsonPrettyPrint(line, len, TelnetLDJsonPPCb)) {
TelnetLDJsonPPCb(line, len);
}
else if ((textcolor >= 90) && (textcolor <= 97)) {
diffcolor -= 60; // Lowlight color
}
char* time_end = (char*)memchr(line, ' ', len); // Find first word (usually 14:49:36.123-017)
uint32_t time_len = time_end - line;
TelnetWriteColor(diffcolor);
Telnet.client.write(line, time_len);
TelnetWriteColor(textcolor);
Telnet.client.write(time_end, len - time_len);
TelnetWriteColor(0);
Telnet.client.println();
}
}
@ -182,10 +189,24 @@ void TelnetLoop(void) {
}
// Input keyboard data
bool telnet_iac = false;
while (Telnet.client.available()) {
yield();
uint8_t in_byte = Telnet.client.read();
if (telnet_iac) {
telnet_iac = false;
if (in_byte != 0xFF) {
// Process telnet Interpret as Command (IAC) codes
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_TELNET "IAC %d"), in_byte);
continue;
}
}
else if (0xFF == in_byte) { // Telnet Interpret as Command (IAC)
telnet_iac = true;
continue;
}
#ifdef USE_XYZMODEM
if (XYZModemWifiClientStart(&Telnet.client, in_byte)) { return; }
#endif // USE_XYZMODEM