mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-28 13:16:32 +00:00
Merge branch 'development' into prerelease-12.5
This commit is contained in:
commit
3f43db93f0
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -7,7 +7,7 @@
|
||||
- [ ] Only relevant files were touched
|
||||
- [ ] Only one feature/fix was added per PR and the code change compiles without warnings
|
||||
- [ ] The code change is tested and works with Tasmota core ESP8266 V.2.7.4.9
|
||||
- [ ] The code change is tested and works with Tasmota core ESP32 V.2.0.6
|
||||
- [ ] The code change is tested and works with Tasmota core ESP32 V.2.0.7
|
||||
- [ ] I accept the [CLA](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md#contributor-license-agreement-cla).
|
||||
|
||||
_NOTE: The code change must pass CI tests. **Your PR cannot be merged unless tests pass**_
|
||||
|
147
.vscode/settings.json
vendored
147
.vscode/settings.json
vendored
@ -1,75 +1,74 @@
|
||||
{
|
||||
"platformio-ide.toolbar": [
|
||||
{
|
||||
"text": "$(home)",
|
||||
"tooltip": "PlatformIO: Home",
|
||||
"commands": [
|
||||
{
|
||||
"id": "platformio-ide.runPIOCoreCommand",
|
||||
"args": "pio home"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "$(trash)",
|
||||
"tooltip": "PlatformIO: Clean All",
|
||||
"commands": [
|
||||
{
|
||||
"id": "workbench.action.tasks.runTask",
|
||||
"args": "PlatformIO: Clean All"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "$(check)",
|
||||
"tooltip": "PlatformIO: Build",
|
||||
"commands": [
|
||||
{
|
||||
"id": "workbench.action.tasks.runTask",
|
||||
"args": "PlatformIO: Build"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "$(zap)",
|
||||
"tooltip": "PlatformIO: Build and Upload",
|
||||
"commands": [
|
||||
{
|
||||
"id": "workbench.action.tasks.runTask",
|
||||
"args": "PlatformIO: Upload"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "$(flame)",
|
||||
"tooltip": "PlatformIO: Build, Erase and Upload",
|
||||
"commands": [
|
||||
{
|
||||
"id": "platformio-ide.runPIOCoreCommand",
|
||||
"args": "pio run -t erase_upload"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "$(device-desktop)",
|
||||
"tooltip": "PlatformIO: Serial Monitor",
|
||||
"commands": [
|
||||
{
|
||||
"id": "workbench.action.tasks.runTask",
|
||||
"args": "PlatformIO: Monitor"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "$(refresh)",
|
||||
"tooltip": "PlatformIO: Rebuild IntelliSense Index",
|
||||
"commands": [
|
||||
{
|
||||
"id": "workbench.action.tasks.runTask",
|
||||
"args": "PlatformIO: Rebuild IntelliSense Index"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
"platformio-ide.toolbar": [
|
||||
{
|
||||
"text": "$(home)",
|
||||
"tooltip": "PlatformIO: Home",
|
||||
"commands": "platformio-ide.showHome"
|
||||
},
|
||||
{
|
||||
"text": "$(trash)",
|
||||
"tooltip": "PlatformIO: Clean",
|
||||
"commands": "platformio-ide.clean"
|
||||
},
|
||||
{
|
||||
"text": "$(check)",
|
||||
"tooltip": "PlatformIO: Build",
|
||||
"commands": "platformio-ide.build"
|
||||
},
|
||||
{
|
||||
"text": "Upload",
|
||||
"tooltip": "PlatformIO: Flash firmware (NO build run)",
|
||||
"commands": [
|
||||
{
|
||||
"id": "platformio-ide.runPIOCoreCommand",
|
||||
"args": "pio run -t nobuild -t factory_flash -e ${command:platformio-ide.activeEnvironment}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "$(zap)",
|
||||
"tooltip": "PlatformIO: Build and Upload",
|
||||
"commands": "platformio-ide.upload"
|
||||
},
|
||||
{
|
||||
"text": "$(flame)",
|
||||
"tooltip": "PlatformIO: Build, Erase and Upload",
|
||||
"commands": [
|
||||
{
|
||||
"id": "platformio-ide.runPIOCoreCommand",
|
||||
"args": "pio run -t erase_upload -e ${command:platformio-ide.activeEnvironment}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "$(error)",
|
||||
"tooltip": "PlatformIO: Erase Flash",
|
||||
"commands": [
|
||||
{
|
||||
"id": "platformio-ide.runPIOCoreCommand",
|
||||
"args": "pio run -t nobuild -t erase -e ${command:platformio-ide.activeEnvironment}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "$(arrow-right)",
|
||||
"tooltip": "PlatformIO: Upload and Monitor",
|
||||
"commands": "platformio-ide.uploadAndMonitor"
|
||||
},
|
||||
{
|
||||
"text": "$(device-desktop)",
|
||||
"tooltip": "PlatformIO: Serial Monitor",
|
||||
"commands": "platformio-ide.serialMonitor"
|
||||
},
|
||||
{
|
||||
"text": "$(terminal)",
|
||||
"tooltip": "PlatformIO: New Terminal",
|
||||
"commands": "platformio-ide.newTerminal"
|
||||
},
|
||||
{
|
||||
"text": "$(refresh)",
|
||||
"tooltip": "PlatformIO: Rebuild IntelliSense Index",
|
||||
"commands": "platformio-ide.rebuildProjectIndex"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up
|
||||
| USE_LM75AD | - | - / x | - | x | - | - |
|
||||
| USE_APDS9960 | - | - / - | - | - | - | - |
|
||||
| USE_MCP230xx | - | - / - | - | - | - | - |
|
||||
| USE_MCP23XXX_DRV | - | - / - | - | - | - | - |
|
||||
| USE_PCA9632 | - | - / - | - | - | - | - |
|
||||
| USE_PCA9685 | - | - / - | - | - | - | - |
|
||||
| USE_MPR121 | - | - / - | - | - | - | - |
|
||||
@ -143,6 +144,9 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up
|
||||
| USE_CHIRP | - | - / - | - | - | - | - |
|
||||
| USE_PAJ7620 | - | - / - | - | - | - | - |
|
||||
| USE_PCF8574 | - | - / - | - | - | - | - |
|
||||
| USE_PMSA003I | - | - / - | - | - | - | - |
|
||||
| USE_LOX_O2 | - | - / x | - | x | - | - |
|
||||
| USE_GDK101 | - | - / - | - | - | - | - |
|
||||
| | | | | | | |
|
||||
| Feature or Sensor | l | t | k | s | i | d | Remarks
|
||||
| USE_HIH6 | - | - / x | - | x | - | - |
|
||||
|
88
CHANGELOG.md
88
CHANGELOG.md
@ -3,6 +3,94 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Released]
|
||||
|
||||
## [12.5.0] 20230417
|
||||
- Release Peyton
|
||||
|
||||
## [12.4.0.5] 20230417
|
||||
### Added
|
||||
- Matter support for Light and Relays on ESP32 by Stephan Hadinger (#18320)
|
||||
- ESP32 WIP support for 16 shutters using `#define USE_SHUTTER_ESP32` in addition to `USE_SHUTTER` by Stefan Bode (#18295)
|
||||
- Berry `webserver.html_escape()` reusing the internal HTML escaping function
|
||||
- Support for GDK101 gamma radiation sensor by Petr Novacek (#18390)
|
||||
|
||||
### Changed
|
||||
- ESP32 LVGL library from v8.3.5 to v8.3.6 (no functional change)
|
||||
|
||||
### Fixed
|
||||
- ESP32 ``Upload``, ``Upgrade``, ``WebGetConfig``, ``WebQuery`` and ``WebSend`` random HTTP(S) connection timeout set to 5 sec (commit 542eca3)
|
||||
- ESP32 energy monitoring set StartTotalTime regression from v12.3.1.5 (#18385)
|
||||
|
||||
## [12.4.0.4] 20230403
|
||||
### Added
|
||||
- Matter support simple Relay on Apple Homekit by Stephan Hadinger (#18239)
|
||||
- VSC Pio menu bar extensions by @Jason2866 (#18233)
|
||||
- Command ``SwitchMode0`` to show or set all SwitchModes
|
||||
|
||||
### Changed
|
||||
- Support for multiple PCF8574 as switch/button/relay if enabled with `#define USE_PCF8574` and `#define USE_PCF8574_MODE2`
|
||||
|
||||
## [12.4.0.3] 20230322
|
||||
### Added
|
||||
- Support for PMSA003I Air Quality Sensor by Jean-Pierre Deschamps (#18214)
|
||||
- Support for DingTian virtual switch/button/relay (#18223)
|
||||
- Berry add `mdns.remove_service()`
|
||||
|
||||
### Fixed
|
||||
- Refactor energy monitoring reducing stack usage and solve inherent exceptions and watchdogs (#18164)
|
||||
- Berry fix `tasmota.get_power(index)`
|
||||
|
||||
## [12.4.0.2] 20230317
|
||||
### Added
|
||||
- Support for multiple MCP23008 as switch/button/relay if enabled with `#define USE_MCP23XXX_DRV`
|
||||
- Support for multiple PCF8574 as switch/button/relay
|
||||
- Extended Tariff command for forced tariff (#18080)
|
||||
- Berry support for Tensorflow Lite (TFL) by Christiaan Baars (#18119)
|
||||
- Zigbee send Tuya 'magic spell' to unlock devices when pairing (#18144)
|
||||
- Berry `webclient` `set_follow_redirects(bool)`
|
||||
- Berry `webclient` `collect_headers()` and `set_headers`
|
||||
- Display TM1650 commands like TM1637 (#18109)
|
||||
- Berry add `web_get_arg` event to drivers when `FUNC_WEB_GET_ARG` event is processed
|
||||
- Support for reset settings on filesystem
|
||||
|
||||
### Breaking Changed
|
||||
- Shelly Pro 4PM using standard MCP23xxx driver and needs one time Auto-Configuration
|
||||
|
||||
### Changed
|
||||
- Refactored Berry rule engine and support for arrays
|
||||
- ESP32 LVGL library from v8.3.3 to v8.3.5 (no functional change)
|
||||
- Removed absolute url from filesystem (#18148)
|
||||
- ``UrlFetch`` now follows redirects
|
||||
|
||||
### Fixed
|
||||
- TuyaMcu v1 sequence fix (#17625)
|
||||
- TuyaMcu v1 timer integer overflow (#18048)
|
||||
- PZEM energy monitor stabilize period on larger configs (#18103)
|
||||
- Rule topic comparison (#18144)
|
||||
- ESP32 energy period shows kWh value instead of Wh regression from v12.3.1.5 (#15856)
|
||||
|
||||
## [12.4.0.1] 20230301
|
||||
### Added
|
||||
- Matter read/write and commands (#18000)
|
||||
- Matter subscriptions (#18017, #18018)
|
||||
- Matter multi-fabric (#18019)
|
||||
- Support for multiple MCP23017/MCP23S17 as switch/button/relay
|
||||
- NTP time request from gateway (#17984)
|
||||
|
||||
### Changed
|
||||
- ADC Range oversample from 2 to 32 (#17975)
|
||||
- ESP32 Framework (Core) from v2.0.6 to v2.0.7
|
||||
- Move #define OTA_URL from user_config.h to board files for better inital support (#18008)
|
||||
- Increase number of (virtual)relays and (virtual)buttons to 32
|
||||
- LibTeleinfo from v1.1.3 to v1.1.5 (#18050)
|
||||
|
||||
### Fixed
|
||||
- SEN5X floats and units (#17961)
|
||||
- Energytotals cannot be set to negative values (#17965)
|
||||
- Undocumented support for non-sequential buttons and switches (#17967)
|
||||
- SR04 driver single pin ultrasonic sensor detection (#17966)
|
||||
- IR panasonic protocol regression from v12.0.2.4 (#18013)
|
||||
- EnergyTotal divided twice during minimal upgrade step regression from v12.3.1.3 (#18024)
|
||||
|
||||
## [12.4.0] 20230216
|
||||
- Release Peter
|
||||
|
||||
|
@ -76,6 +76,9 @@ In addition to @arendst the following code is mainly owned by:
|
||||
| xdrv_62_improv | @arendst
|
||||
| xdrv_63_modbus_bridge | @jeroenst
|
||||
| xdrv_64_pca9632 | Pascal Heinrich
|
||||
| xdrv_65_tuyamcubr | David Gwynne
|
||||
| xdrv_66_tm1638 | @arendst
|
||||
| xdrv_67_mcp23xxx | @arendst
|
||||
| |
|
||||
| xdrv_79_esp32_ble | @staars, @btsimonh
|
||||
| xdrv_81_esp32_webcam | @gemu, @philrich
|
||||
@ -120,7 +123,7 @@ In addition to @arendst the following code is mainly owned by:
|
||||
| xsns_25 |
|
||||
| xsns_26_lm75ad | Andre Thomas
|
||||
| xsns_27_apds9960 | Shawn Hymel
|
||||
| xsns_28_tm1638 | @arendst
|
||||
| xsns_28 |
|
||||
| xsns_29_mcp230xx | Andre Thomas
|
||||
| xsns_30_mpr121 | Rene 'Renne' Bartsch
|
||||
| xsns_31_ccs811 | Gerhard Mutz
|
||||
@ -199,6 +202,9 @@ In addition to @arendst the following code is mainly owned by:
|
||||
| xsns_101_hmc5883l | Andreas Achtzehn
|
||||
| xsns_102_ld2410 | @arendst
|
||||
| xsns_103_sen5x | @tyeth
|
||||
| xsns_104_pmsa003i | Jean-Pierre Deschamps
|
||||
| xsns_105_lox_o2 | @ACE1046
|
||||
| xsns_106_gdk101 | @Szewcson
|
||||
| |
|
||||
| Libraries |
|
||||
| |
|
||||
@ -206,6 +212,7 @@ In addition to @arendst the following code is mainly owned by:
|
||||
| ext-printf | @s-hadinger
|
||||
| jsmn | @s-hadinger
|
||||
| unishox | @s-hadinger
|
||||
| matter | @s-hadinger
|
||||
| |
|
||||
| PlatformIO |
|
||||
| |
|
||||
|
195
I2CDEVICES.md
195
I2CDEVICES.md
@ -6,109 +6,112 @@ Using command ``I2cDriver`` individual drivers can be enabled or disabled at run
|
||||
## Supported I2C devices
|
||||
The following table lists the supported I2C devices
|
||||
|
||||
Index | Define | Driver | Device | Address(es) | Description
|
||||
------|---------------------|----------|----------|-------------|-----------------------------------------------
|
||||
1 | USE_PCA9685 | xdrv_15 | PCA9685 | 0x40 - 0x47 | 16-channel 12-bit pwm driver
|
||||
Index | Define | Driver | Device | Address(es) | Description
|
||||
------|---------------------|---------|----------|-------------|-----------------------------------------------
|
||||
1 | USE_PCA9685 | xdrv_15 | PCA9685 | 0x40 - 0x47 | 16-channel 12-bit pwm driver
|
||||
2 | USE_PCF8574 | xdrv_28 | PCF8574 | 0x20 - 0x26 | 8-bit I/O expander (address range overridable)
|
||||
2 | USE_PCF8574 | xdrv_28 | PCF8574A | 0x39 - 0x3F | 8-bit I/O expander (address range overridable)
|
||||
3 | USE_DISPLAY_LCD | xdsp_01 | | 0x27, 0x3F | LCD display
|
||||
4 | USE_DISPLAY_SSD1306 | xdsp_02 | SSD1306 | 0x3C - 0x3D | Oled display
|
||||
5 | USE_DISPLAY_MATRIX | xdsp_03 | HT16K33 | 0x70 - 0x77 | 8x8 led matrix
|
||||
6 | USE_DISPLAY_SH1106 | xdsp_07 | SH1106 | 0x3C - 0x3D | Oled display
|
||||
7 | USE_ADE7953 | xnrg_07 | ADE7953 | 0x38 | Energy monitor
|
||||
8 | USE_SHT | xsns_07 | SHT1X | Any | Temperature and Humidity sensor
|
||||
9 | USE_HTU | xsns_08 | HTU21 | 0x40 | Temperature and Humidity sensor
|
||||
9 | USE_HTU | xsns_08 | SI7013 | 0x40 | Temperature and Humidity sensor
|
||||
9 | USE_HTU | xsns_08 | SI7020 | 0x40 | Temperature and Humidity sensor
|
||||
9 | USE_HTU | xsns_08 | SI7021 | 0x40 | Temperature and Humidity sensor
|
||||
10 | USE_BMP | xsns_09 | BMP085 | 0x76 - 0x77 | Pressure and temperature sensor
|
||||
10 | USE_BMP | xsns_09 | BMP180 | 0x76 - 0x77 | Pressure and temperature sensor
|
||||
10 | USE_BMP | xsns_09 | BMP280 | 0x76 - 0x77 | Pressure and temperature sensor
|
||||
10 | USE_BMP | xsns_09 | BME280 | 0x76 - 0x77 | Pressure, temperature and humidity sensor
|
||||
10 | USE_BMP | xsns_09 | BME680 | 0x76 - 0x77 | Pressure, temperature, humidity and gas sensor
|
||||
11 | USE_BH1750 | xsns_10 | BH1750 | 0x23, 0x5C | Ambient light intensity sensor
|
||||
12 | USE_VEML6070 | xsns_11 | VEML6070 | 0x38 - 0x39 | Ultra violet light intensity sensor
|
||||
13 | USE_ADS1115 | xsns_12 | ADS1115 | 0x48 - 0x4B | 4-channel 16-bit A/D converter
|
||||
14 | USE_INA219 | xsns_13 | INA219 | 0x40 - 0x41, 0x44 - 0x45 | Low voltage current sensor
|
||||
15 | USE_SHT3X | xsns_14 | SHT3X | 0x44 - 0x45 | Temperature and Humidity sensor
|
||||
15 | USE_SHT3X | xsns_14 | SHT4X | 0x44 - 0x45 | Temperature and Humidity sensor
|
||||
15 | USE_SHT3X | xsns_14 | SHTCX | 0x70 | Temperature and Humidity sensor
|
||||
16 | USE_TSL2561 | xsns_16 | TSL2561 | 0x29, 0x39, 0x49 | Light intensity sensor
|
||||
17 | USE_MGS | xsns_19 | Grove | 0x04 | Multichannel gas sensor
|
||||
18 | USE_SGP30 | xsns_21 | SGP30 | 0x58 | Gas (TVOC) and air quality sensor
|
||||
19 | USE_SI1145 | xsns_24 | SI1145 | 0x60 | Ultra violet index and light sensor
|
||||
19 | USE_SI1145 | xsns_24 | SI1146 | 0x60 | Ultra violet index and light sensor
|
||||
19 | USE_SI1145 | xsns_24 | SI1147 | 0x60 | Ultra violet index and light sensor
|
||||
20 | USE_LM75AD | xsns_26 | LM75AD | 0x48 - 0x4F | Temperature sensor
|
||||
21 | USE_APDS9960 | xsns_27 | APDS9960 | 0x39 | Proximity ambient light RGB and gesture sensor
|
||||
22 | USE_MCP230xx | xsns_29 | MCP23008 | 0x20 - 0x26 | 8-bit I/O expander
|
||||
22 | USE_MCP230xx | xsns_29 | MCP23017 | 0x20 - 0x26 | 16-bit I/O expander
|
||||
23 | USE_MPR121 | xsns_30 | MPR121 | 0x5A - 0x5D | Proximity capacitive touch sensor
|
||||
24 | USE_CCS811 | xsns_31 | CCS811 | 0x5A | Gas (TVOC) and air quality sensor
|
||||
3 | USE_DISPLAY_LCD | xdsp_01 | | 0x27, 0x3F | LCD display
|
||||
4 | USE_DISPLAY_SSD1306 | xdsp_02 | SSD1306 | 0x3C - 0x3D | Oled display
|
||||
5 | USE_DISPLAY_MATRIX | xdsp_03 | HT16K33 | 0x70 - 0x77 | 8x8 led matrix
|
||||
6 | USE_DISPLAY_SH1106 | xdsp_07 | SH1106 | 0x3C - 0x3D | Oled display
|
||||
7 | USE_ADE7953 | xnrg_07 | ADE7953 | 0x38 | Energy monitor
|
||||
8 | USE_SHT | xsns_07 | SHT1X | Any | Temperature and Humidity sensor
|
||||
9 | USE_HTU | xsns_08 | HTU21 | 0x40 | Temperature and Humidity sensor
|
||||
9 | USE_HTU | xsns_08 | SI7013 | 0x40 | Temperature and Humidity sensor
|
||||
9 | USE_HTU | xsns_08 | SI7020 | 0x40 | Temperature and Humidity sensor
|
||||
9 | USE_HTU | xsns_08 | SI7021 | 0x40 | Temperature and Humidity sensor
|
||||
10 | USE_BMP | xsns_09 | BMP085 | 0x76 - 0x77 | Pressure and temperature sensor
|
||||
10 | USE_BMP | xsns_09 | BMP180 | 0x76 - 0x77 | Pressure and temperature sensor
|
||||
10 | USE_BMP | xsns_09 | BMP280 | 0x76 - 0x77 | Pressure and temperature sensor
|
||||
10 | USE_BMP | xsns_09 | BME280 | 0x76 - 0x77 | Pressure, temperature and humidity sensor
|
||||
10 | USE_BMP | xsns_09 | BME680 | 0x76 - 0x77 | Pressure, temperature, humidity and gas sensor
|
||||
11 | USE_BH1750 | xsns_10 | BH1750 | 0x23, 0x5C | Ambient light intensity sensor
|
||||
12 | USE_VEML6070 | xsns_11 | VEML6070 | 0x38 - 0x39 | Ultra violet light intensity sensor
|
||||
13 | USE_ADS1115 | xsns_12 | ADS1115 | 0x48 - 0x4B | 4-channel 16-bit A/D converter
|
||||
14 | USE_INA219 | xsns_13 | INA219 | 0x40 - 0x41, 0x44 - 0x45 | Low voltage current sensor
|
||||
15 | USE_SHT3X | xsns_14 | SHT3X | 0x44 - 0x45 | Temperature and Humidity sensor
|
||||
15 | USE_SHT3X | xsns_14 | SHT4X | 0x44 - 0x45 | Temperature and Humidity sensor
|
||||
15 | USE_SHT3X | xsns_14 | SHTCX | 0x70 | Temperature and Humidity sensor
|
||||
16 | USE_TSL2561 | xsns_16 | TSL2561 | 0x29, 0x39, 0x49 | Light intensity sensor
|
||||
17 | USE_MGS | xsns_19 | Grove | 0x04 | Multichannel gas sensor
|
||||
18 | USE_SGP30 | xsns_21 | SGP30 | 0x58 | Gas (TVOC) and air quality sensor
|
||||
19 | USE_SI1145 | xsns_24 | SI1145 | 0x60 | Ultra violet index and light sensor
|
||||
19 | USE_SI1145 | xsns_24 | SI1146 | 0x60 | Ultra violet index and light sensor
|
||||
19 | USE_SI1145 | xsns_24 | SI1147 | 0x60 | Ultra violet index and light sensor
|
||||
20 | USE_LM75AD | xsns_26 | LM75AD | 0x48 - 0x4F | Temperature sensor
|
||||
21 | USE_APDS9960 | xsns_27 | APDS9960 | 0x39 | Proximity ambient light RGB and gesture sensor
|
||||
22 | USE_MCP230xx | xsns_29 | MCP23008 | 0x20 - 0x26 | 8-bit I/O expander
|
||||
22 | USE_MCP230xx | xsns_29 | MCP23017 | 0x20 - 0x26 | 16-bit I/O expander
|
||||
23 | USE_MPR121 | xsns_30 | MPR121 | 0x5A - 0x5D | Proximity capacitive touch sensor
|
||||
24 | USE_CCS811 | xsns_31 | CCS811 | 0x5A | Gas (TVOC) and air quality sensor
|
||||
24' | USE_CCS811_V2 | xsns_31 | CCS811 | 0x5A - 0x5B | Gas (TVOC) and air quality sensor
|
||||
25 | USE_MPU6050 | xsns_32 | MPU6050 | 0x68 - 0x69 | 3-axis gyroscope and temperature sensor
|
||||
26 | USE_DS3231 | xsns_33 | DS3231 | 0x68 | Real time clock
|
||||
27 | USE_MGC3130 | xsns_36 | MGC3130 | 0x42 | Electric field sensor
|
||||
28 | USE_MAX44009 | xsns_41 | MAX44009 | 0x4A - 0x4B | Ambient light intensity sensor
|
||||
29 | USE_SCD30 | xsns_42 | SCD30 | 0x61 | CO2 sensor
|
||||
30 | USE_SPS30 | xsns_44 | SPS30 | 0x69 | Particle sensor
|
||||
31 | USE_VL53L0X | xsns_45 | VL53L0X | 0x29 | Time-of-flight (ToF) distance sensor
|
||||
32 | USE_MLX90614 | xsns_46 | MLX90614 | 0x5A | Infra red temperature sensor
|
||||
33 | USE_CHIRP | xsns_48 | CHIRP | 0x20 | Soil moisture sensor
|
||||
34 | USE_PAJ7620 | xsns_50 | PAJ7620 | 0x73 | Gesture sensor
|
||||
35 | USE_INA226 | xsns_54 | INA226 | 0x40 - 0x41, 0x44 - 0x45 | Low voltage current sensor
|
||||
36 | USE_HIH6 | xsns_55 | HIH6130 | 0x27 | Temperature and Humidity sensor
|
||||
37 | USE_24C256 | xdrv_10 | 24C256 | 0x50 | Scripter EEPROM storage
|
||||
38 | USE_DISPLAY_ILI9488 | xdsp_08 | FT6236 | 0x38 | Touch panel controller
|
||||
39 | USE_DISPLAY_RA8876 | xdsp_10 | FT5316 | 0x38 | Touch panel controller
|
||||
25 | USE_MPU6050 | xsns_32 | MPU6050 | 0x68 - 0x69 | 3-axis gyroscope and temperature sensor
|
||||
26 | USE_DS3231 | xsns_33 | DS3231 | 0x68 | Real time clock
|
||||
27 | USE_MGC3130 | xsns_36 | MGC3130 | 0x42 | Electric field sensor
|
||||
28 | USE_MAX44009 | xsns_41 | MAX44009 | 0x4A - 0x4B | Ambient light intensity sensor
|
||||
29 | USE_SCD30 | xsns_42 | SCD30 | 0x61 | CO2 sensor
|
||||
30 | USE_SPS30 | xsns_44 | SPS30 | 0x69 | Particle sensor
|
||||
31 | USE_VL53L0X | xsns_45 | VL53L0X | 0x29 | Time-of-flight (ToF) distance sensor
|
||||
32 | USE_MLX90614 | xsns_46 | MLX90614 | 0x5A | Infra red temperature sensor
|
||||
33 | USE_CHIRP | xsns_48 | CHIRP | 0x20 | Soil moisture sensor
|
||||
34 | USE_PAJ7620 | xsns_50 | PAJ7620 | 0x73 | Gesture sensor
|
||||
35 | USE_INA226 | xsns_54 | INA226 | 0x40 - 0x41, 0x44 - 0x45 | Low voltage current sensor
|
||||
36 | USE_HIH6 | xsns_55 | HIH6130 | 0x27 | Temperature and Humidity sensor
|
||||
37 | USE_24C256 | xdrv_10 | 24C256 | 0x50 | Scripter EEPROM storage
|
||||
38 | USE_DISPLAY_ILI9488 | xdsp_08 | FT6236 | 0x38 | Touch panel controller
|
||||
39 | USE_DISPLAY_RA8876 | xdsp_10 | FT5316 | 0x38 | Touch panel controller
|
||||
40 | USE_TSL2591 | xsns_57 | TSL2591 | 0x29 | Light intensity sensor
|
||||
41 | USE_DHT12 | xsns_58 | DHT12 | 0x5C | Temperature and humidity sensor
|
||||
42 | USE_DS1624 | xsns_59 | DS1621 | 0x48 - 0x4F | Temperature sensor
|
||||
42 | USE_DS1624 | xsns_59 | DS1624 | 0x48 - 0x4F | Temperature sensor
|
||||
43 | USE_AHT1x | xsns_63 | AHT10/15 | 0x38 - 0x39 | Temperature and humidity sensor
|
||||
43 | USE_AHT2x | xsns_63 | AHT20 | 0x38 | Temperature and humidity sensor
|
||||
43 | USE_AHT2x | xsns_63 | AM2301B | 0x38 | Temperature and humidity sensor
|
||||
44 | USE_WEMOS_MOTOR_V1 | xdrv_34 | | 0x2D - 0x30 | WEMOS motor shield v1.0.0 (6612FNG)
|
||||
45 | USE_HDC1080 | xsns_65 | HDC1080 | 0x40 | Temperature and Humidity sensor
|
||||
46 | USE_IAQ | xsns_66 | IAQ | 0x5a | Air quality sensor
|
||||
47 | USE_DISPLAY_SEVENSEG| xdsp_11 | HT16K33 | 0x70 - 0x77 | Seven segment LED
|
||||
48 | USE_AS3935 | xsns_67 | AS3935 | 0x03 | Franklin Lightning Sensor
|
||||
49 | USE_VEML6075 | xsns_70 | VEML6075 | 0x10 | UVA/UVB/UVINDEX Sensor
|
||||
50 | USE_VEML7700 | xsns_71 | VEML7700 | 0x10 | Ambient light intensity sensor
|
||||
51 | USE_MCP9808 | xsns_72 | MCP9808 | 0x18 - 0x1F | Temperature sensor
|
||||
52 | USE_HP303B | xsns_73 | HP303B | 0x76 - 0x77 | Pressure and temperature sensor
|
||||
53 | USE_MLX90640 | xdrv_43 | MLX90640 | 0x33 | IR array temperature sensor
|
||||
54 | USE_VL53L1X | xsns_77 | VL53L1X | 0x29 | Time-of-flight (ToF) distance sensor
|
||||
55 | USE_EZOPH | xsns_78 | EZOPH | 0x61 - 0x70 | pH sensor
|
||||
55 | USE_EZOORP | xsns_78 | EZOORP | 0x61 - 0x70 | ORP sensor
|
||||
55 | USE_EZORTD | xsns_78 | EZORTD | 0x61 - 0x70 | Temperature sensor
|
||||
55 | USE_EZOHUM | xsns_78 | EZOHUM | 0x61 - 0x70 | Humidity sensor
|
||||
55 | USE_EZOEC | xsns_78 | EZOEC | 0x61 - 0x70 | Electric conductivity sensor
|
||||
55 | USE_EZOCO2 | xsns_78 | EZOCO2 | 0x61 - 0x70 | CO2 sensor
|
||||
55 | USE_EZOO2 | xsns_78 | EZOO2 | 0x61 - 0x70 | O2 sensor
|
||||
55 | USE_EZOPRS | xsns_78 | EZOPRS | 0x61 - 0x70 | Pressure sensor
|
||||
55 | USE_EZOFLO | xsns_78 | EZOFLO | 0x61 - 0x70 | Flow meter sensor
|
||||
55 | USE_EZODO | xsns_78 | EZODO | 0x61 - 0x70 | Disolved Oxygen sensor
|
||||
55 | USE_EZORGB | xsns_78 | EZORGB | 0x61 - 0x70 | Color sensor
|
||||
55 | USE_EZOPMP | xsns_78 | EZOPMP | 0x61 - 0x70 | Peristaltic Pump
|
||||
56 | USE_SEESAW_SOIL | xsns_81 | SEESOIL | 0x36 - 0x39 | Adafruit seesaw soil moisture sensor
|
||||
57 | USE_TOF10120 | xsns_84 | TOF10120 | 0x52 | Time-of-flight (ToF) distance sensor
|
||||
58 | USE_MPU_ACCEL | xsns_85 | MPU_ACCEL| 0x68 | MPU6886/MPU9250 6-axis MotionTracking sensor from M5Stac k
|
||||
59 | USE_BM8563 | xdrv_56 | BM8563 | 0x51 | BM8563 RTC from M5Stack
|
||||
60 | USE_AM2320 | xsns_88 | AM2320 | 0x5C | Temperature and Humidity sensor
|
||||
61 | USE_T67XX | xsns_89 | T67XX | 0x15 | CO2 sensor
|
||||
62 | USE_SCD40 | xsns_92 | SCD40 | 0x62 | CO2 sensor Sensirion SCD40/SCD41
|
||||
63 | USE_HM330X | xsns_93 | HM330X | 0x40 | Particule sensor
|
||||
64 | USE_HDC2010 | xsns_94 | HDC2010 | 0x40 | Temperature and Humidity sensor
|
||||
65 | USE_ADE7880 | xnrg_23 | ADE7880 | 0x38 | Energy monitor
|
||||
66 | USE_PCF85363 | xsns_99 | PCF85363 | 0x51 | Real time clock
|
||||
67 | USE_DS3502 | xdrv_61 | DS3502 | 0x28 - 0x2B | Digital potentiometer
|
||||
68 | USE_HYT | xsns_97 | HYTxxx | 0x28 | Temperature and Humidity sensor
|
||||
69 | USE_SGP40 | xsns_98 | SGP40 | 0x59 | Gas (TVOC) and air quality
|
||||
70 | USE_LUXV30B | xsns_99 | LUXV30B | 0x4A | DFRobot SEN0390 V30B lux sensor
|
||||
41 | USE_DHT12 | xsns_58 | DHT12 | 0x5C | Temperature and humidity sensor
|
||||
42 | USE_DS1624 | xsns_59 | DS1621 | 0x48 - 0x4F | Temperature sensor
|
||||
42 | USE_DS1624 | xsns_59 | DS1624 | 0x48 - 0x4F | Temperature sensor
|
||||
43 | USE_AHT1x | xsns_63 | AHT10/15 | 0x38 - 0x39 | Temperature and humidity sensor
|
||||
43 | USE_AHT2x | xsns_63 | AHT20 | 0x38 | Temperature and humidity sensor
|
||||
43 | USE_AHT2x | xsns_63 | AM2301B | 0x38 | Temperature and humidity sensor
|
||||
44 | USE_WEMOS_MOTOR_V1 | xdrv_34 | | 0x2D - 0x30 | WEMOS motor shield v1.0.0 (6612FNG)
|
||||
45 | USE_HDC1080 | xsns_65 | HDC1080 | 0x40 | Temperature and Humidity sensor
|
||||
46 | USE_IAQ | xsns_66 | IAQ | 0x5a | Air quality sensor
|
||||
47 | USE_DISPLAY_SEVENSEG| xdsp_11 | HT16K33 | 0x70 - 0x77 | Seven segment LED
|
||||
48 | USE_AS3935 | xsns_67 | AS3935 | 0x03 | Franklin Lightning Sensor
|
||||
49 | USE_VEML6075 | xsns_70 | VEML6075 | 0x10 | UVA/UVB/UVINDEX Sensor
|
||||
50 | USE_VEML7700 | xsns_71 | VEML7700 | 0x10 | Ambient light intensity sensor
|
||||
51 | USE_MCP9808 | xsns_72 | MCP9808 | 0x18 - 0x1F | Temperature sensor
|
||||
52 | USE_HP303B | xsns_73 | HP303B | 0x76 - 0x77 | Pressure and temperature sensor
|
||||
53 | USE_MLX90640 | xdrv_43 | MLX90640 | 0x33 | IR array temperature sensor
|
||||
54 | USE_VL53L1X | xsns_77 | VL53L1X | 0x29 | Time-of-flight (ToF) distance sensor
|
||||
55 | USE_EZOPH | xsns_78 | EZOPH | 0x61 - 0x70 | pH sensor
|
||||
55 | USE_EZOORP | xsns_78 | EZOORP | 0x61 - 0x70 | ORP sensor
|
||||
55 | USE_EZORTD | xsns_78 | EZORTD | 0x61 - 0x70 | Temperature sensor
|
||||
55 | USE_EZOHUM | xsns_78 | EZOHUM | 0x61 - 0x70 | Humidity sensor
|
||||
55 | USE_EZOEC | xsns_78 | EZOEC | 0x61 - 0x70 | Electric conductivity sensor
|
||||
55 | USE_EZOCO2 | xsns_78 | EZOCO2 | 0x61 - 0x70 | CO2 sensor
|
||||
55 | USE_EZOO2 | xsns_78 | EZOO2 | 0x61 - 0x70 | O2 sensor
|
||||
55 | USE_EZOPRS | xsns_78 | EZOPRS | 0x61 - 0x70 | Pressure sensor
|
||||
55 | USE_EZOFLO | xsns_78 | EZOFLO | 0x61 - 0x70 | Flow meter sensor
|
||||
55 | USE_EZODO | xsns_78 | EZODO | 0x61 - 0x70 | Disolved Oxygen sensor
|
||||
55 | USE_EZORGB | xsns_78 | EZORGB | 0x61 - 0x70 | Color sensor
|
||||
55 | USE_EZOPMP | xsns_78 | EZOPMP | 0x61 - 0x70 | Peristaltic Pump
|
||||
56 | USE_SEESAW_SOIL | xsns_81 | SEESOIL | 0x36 - 0x39 | Adafruit seesaw soil moisture sensor
|
||||
57 | USE_TOF10120 | xsns_84 | TOF10120 | 0x52 | Time-of-flight (ToF) distance sensor
|
||||
58 | USE_MPU_ACCEL | xsns_85 | MPU_ACCEL| 0x68 | MPU6886/MPU9250 6-axis MotionTracking sensor from M5Stack
|
||||
59 | USE_BM8563 | xdrv_56 | BM8563 | 0x51 | BM8563 RTC from M5Stack
|
||||
60 | USE_AM2320 | xsns_88 | AM2320 | 0x5C | Temperature and Humidity sensor
|
||||
61 | USE_T67XX | xsns_89 | T67XX | 0x15 | CO2 sensor
|
||||
62 | USE_SCD40 | xsns_92 | SCD40 | 0x62 | CO2 sensor Sensirion SCD40/SCD41
|
||||
63 | USE_HM330X | xsns_93 | HM330X | 0x40 | Particule sensor
|
||||
64 | USE_HDC2010 | xsns_94 | HDC2010 | 0x40 | Temperature and Humidity sensor
|
||||
65 | USE_ADE7880 | xnrg_23 | ADE7880 | 0x38 | Energy monitor
|
||||
66 | USE_PCF85363 | xsns_99 | PCF85363 | 0x51 | Real time clock
|
||||
67 | USE_DS3502 | xdrv_61 | DS3502 | 0x28 - 0x2B | Digital potentiometer
|
||||
68 | USE_HYT | xsns_97 | HYTxxx | 0x28 | Temperature and Humidity sensor
|
||||
69 | USE_SGP40 | xsns_98 | SGP40 | 0x59 | Gas (TVOC) and air quality
|
||||
70 | USE_LUXV30B | xsns_99 | LUXV30B | 0x4A | DFRobot SEN0390 V30B lux sensor
|
||||
71 | USE_QMC5883L | xsns_33 | QMC5883L | 0x0D | Magnetic Field Sensor
|
||||
72 | USE_INA3221 | xsns_100 | INA3221 | 0x40-0x43 | 3-channels Voltage and Current sensor
|
||||
73 | USE_HMC5883L | xsns_101 | HMC5883L | 0x1E | 3-channels Magnetic Field Sensor
|
||||
74 | USE_DISPLAY_TM1650 | xdsp_20 | TM1650 | 0x24 - 0x27, 0x34 - 0x37 | Four-digit seven-segment LED controller
|
||||
75 | USE_PCA9632 | xdrv_64 | PCA9632 | 0x60 | 4-channel 4-bit pwm driver
|
||||
76 | USE_SEN5X | xsns_103 | SEN5X | 0x69 | Gas (VOC/NOx index) and air quality (PPM <1,<2.5,<4,<10)
|
||||
77 | USE_MCP23XXX_DRV | xdrv_67 | MCP23x17 | 0x20 - 0x26 | 16-bit I/O expander as virtual button/switch/relay
|
||||
78 | USE_PMSA003I | xsns_104 | PMSA003I | 0x12 | PM2.5 Air Quality Sensor with I2C Interface
|
||||
79 | USE_GDK101 | xsns_106 | GDK101 | 0x18 - 0x1B | Gamma Radiation Sensor
|
@ -41,7 +41,7 @@ See [CHANGELOG.md](CHANGELOG.md) for detailed change information.
|
||||
|
||||
Unless your Tasmota powered device exhibits a problem or lacks a feature that you need, leave your device alone - it works so don’t make unnecessary changes! If the release version (i.e., the master branch) exhibits unexpected behaviour for your device and configuration, you should upgrade to the latest development version instead to see if your problem is resolved as some bugs in previous releases or development builds may already have been resolved.
|
||||
|
||||
Every commit made to the development branch, which is compiling successfuly, will post new binary files at http://ota.tasmota.com/tasmota/ (this web address can be used for OTA updates too). It is important to note that these binaries are based on the current development codebase. These commits are tested as much as is possible and are typically quite stable. However, it is infeasible to test on the hundreds of different types of devices with all the available configuration options permitted.
|
||||
Every commit made to the development branch, which is compiling successfully, will post new binary files at http://ota.tasmota.com/tasmota/ (this web address can be used for OTA updates too). It is important to note that these binaries are based on the current development codebase. These commits are tested as much as is possible and are typically quite stable. However, it is infeasible to test on the hundreds of different types of devices with all the available configuration options permitted.
|
||||
|
||||
Note that there is a chance, as with any upgrade, that the device may not function as expected. You must always account for the possibility that you may need to flash the device via the serial programming interface if the OTA upgrade fails. Even with the master release, you should always attempt to test the device or a similar prototype before upgrading a device which is in production or is hard to reach. And, as always, make a backup of the device configuration before beginning any firmware update.
|
||||
|
||||
@ -172,4 +172,4 @@ People helping to keep the show on the road:
|
||||
|
||||
## License
|
||||
|
||||
This program is licensed under GPL-3.0
|
||||
This program is licensed under GPL-3.0-only
|
||||
|
@ -36,9 +36,9 @@ While fallback or downgrading is common practice it was never supported due to S
|
||||
|
||||
This release will be supported from ESP8266/Arduino library Core version **2.7.4.9** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
|
||||
|
||||
This release will be supported from ESP32/Arduino library Core version **2.0.6**.
|
||||
This release will be supported from ESP32/Arduino library Core version **2.0.7**.
|
||||
|
||||
Support of ESP8266 Core versions before 2.7.4.9 and ESP32 Core versions before 2.0.6 have been removed.
|
||||
Support of ESP8266 Core versions before 2.7.4.9 and ESP32 Core versions before 2.0.7 have been removed.
|
||||
|
||||
## Support of TLS
|
||||
|
||||
@ -75,12 +75,12 @@ Latest released binaries can be downloaded from
|
||||
- http://ota.tasmota.com/tasmota/release
|
||||
|
||||
Historical binaries can be downloaded from
|
||||
- http://ota.tasmota.com/tasmota/release-12.4.0
|
||||
- http://ota.tasmota.com/tasmota/release-12.5.0
|
||||
|
||||
The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmota.com/tasmota/release/tasmota.bin.gz``
|
||||
|
||||
### ESP32, ESP32-C3, ESP32-S2 and ESP32-S3 based
|
||||
The following binary downloads have been compiled with ESP32/Arduino library core version **2.0.6**.
|
||||
The following binary downloads have been compiled with ESP32/Arduino library core version **2.0.7**.
|
||||
|
||||
- **tasmota32.bin** = The Tasmota version with most drivers including additional sensors and KNX for 4M+ flash. **RECOMMENDED RELEASE BINARY**
|
||||
- **tasmota32xy.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-C3/S2/S3 and 4M+ flash.
|
||||
@ -100,7 +100,7 @@ Latest released binaries can be downloaded from
|
||||
- https://ota.tasmota.com/tasmota32/release
|
||||
|
||||
Historical binaries can be downloaded from
|
||||
- https://ota.tasmota.com/tasmota32/release-12.4.0
|
||||
- https://ota.tasmota.com/tasmota32/release-12.5.0
|
||||
|
||||
The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasmota.com/tasmota32/release/tasmota32.bin``
|
||||
|
||||
@ -110,52 +110,47 @@ 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 v12.4.0 Peter
|
||||
## Changelog v12.5.0 Peyton
|
||||
### Added
|
||||
- Command ``DhtDelay<sensor> <high_delay>,<low_delay>`` to allow user control over high and low delay in microseconds [#17944](https://github.com/arendst/Tasmota/issues/17944)
|
||||
- Support for up to 3 (ESP8266) or 8 (ESP32) phase modbus energy monitoring device using generic Energy Modbus driver
|
||||
- Support for RGB displays [#17414](https://github.com/arendst/Tasmota/issues/17414)
|
||||
- Support for IPv6 DNS records (AAAA) and IPv6 ``Ping`` for ESP32 and ESP8266 [#17417](https://github.com/arendst/Tasmota/issues/17417)
|
||||
- Support for IPv6 only networks on Ethernet (not yet Wifi)
|
||||
- Support for TM1650 display as used in some clocks by Stefan Oskamp [#17594](https://github.com/arendst/Tasmota/issues/17594)
|
||||
- Support for PCA9632 4-channel 8-bit PWM driver as light driver by Pascal Heinrich [#17557](https://github.com/arendst/Tasmota/issues/17557)
|
||||
- support for SEN5X gas and air quality sensor by Tyeth Gundry [#17736](https://github.com/arendst/Tasmota/issues/17736)
|
||||
- Basic support for Shelly Pro 4PM
|
||||
- Berry support for ``crypto.SHA256`` [#17430](https://github.com/arendst/Tasmota/issues/17430)
|
||||
- Berry crypto add ``EC_P256`` and ``PBKDF2_HMAC_SHA256`` algorithms required by Matter protocol [#17473](https://github.com/arendst/Tasmota/issues/17473)
|
||||
- Berry crypto add ``random`` to generate series of random bytes
|
||||
- Berry crypto add ``HKDF_HMAC_SHA256``
|
||||
- Berry crypto add ``SPAKE2P_Matter`` for Matter support
|
||||
- Berry add ``mdns`` advanced features and query
|
||||
- Berry `int64.fromstring()` to convert a string to an int64 [#17953](https://github.com/arendst/Tasmota/issues/17953)
|
||||
- ESP32 command ``EnergyCols 1..8`` to change number of GUI columns
|
||||
- ESP32 command ``EnergyDisplay 1..3`` to change GUI column presentation
|
||||
- ESP32 support for eigth energy phases/channels
|
||||
- ESP32 support for BMPxxx sensors on two I2C busses [#17643](https://github.com/arendst/Tasmota/issues/17643)
|
||||
- ESP32 support for Biomine BioPDU 625x12 [#17857](https://github.com/arendst/Tasmota/issues/17857)
|
||||
- ESP32 preliminary support for Matter protocol, milestone 1 (commissioning) by Stephan Hadinger
|
||||
- Command ``SwitchMode0`` to show or set all SwitchModes
|
||||
- Support for multiple MCP23008/MCP23017/MCP23S17 as switch/button/relay if enabled with `#define USE_MCP23XXX_DRV`
|
||||
- Support for multiple PCF8574 as switch/button/relay if enabled with `#define USE_PCF8574` and `#define USE_PCF8574_MODE2`
|
||||
- Support for PMSA003I Air Quality Sensor by Jean-Pierre Deschamps [#18214](https://github.com/arendst/Tasmota/issues/18214)
|
||||
- Support for DingTian virtual switch/button/relay [#18223](https://github.com/arendst/Tasmota/issues/18223)
|
||||
- Support for GDK101 gamma radiation sensor by Petr Novacek [#18390](https://github.com/arendst/Tasmota/issues/18390)
|
||||
- NTP time request from gateway [#17984](https://github.com/arendst/Tasmota/issues/17984)
|
||||
- Extended Tariff command for forced tariff [#18080](https://github.com/arendst/Tasmota/issues/18080)
|
||||
- Display TM1650 commands like TM1637 [#18109](https://github.com/arendst/Tasmota/issues/18109)
|
||||
- VSC Pio menu bar extensions by @Jason2866 [#18233](https://github.com/arendst/Tasmota/issues/18233)
|
||||
- Zigbee send Tuya 'magic spell' to unlock devices when pairing [#18144](https://github.com/arendst/Tasmota/issues/18144)
|
||||
- ESP32 WIP support for 16 shutters using `#define USE_SHUTTER_ESP32` in addition to `USE_SHUTTER` by Stefan Bode [#18295](https://github.com/arendst/Tasmota/issues/18295)
|
||||
- Berry support for Tensorflow Lite (TFL) by Christiaan Baars [#18119](https://github.com/arendst/Tasmota/issues/18119)
|
||||
- Berry `webclient` features
|
||||
- Matter support for Light and Relays by Stephan Hadinger [#18320](https://github.com/arendst/Tasmota/issues/18320)
|
||||
|
||||
### Breaking Changed
|
||||
- TM1638 button and led support are handled as virtual switches and relays [#11031](https://github.com/arendst/Tasmota/issues/11031)
|
||||
- Shelly Pro 4PM using standard MCP23xxx driver and needs one time Auto-Configuration
|
||||
|
||||
### Changed
|
||||
- Dht driver from v6 to v7
|
||||
- ESP32 Framework (Core) from v2.0.5.3 to v2.0.6 (IPv6 support)
|
||||
- Energy totals max supported value from +/-21474.83647 to +/-2147483.647 kWh
|
||||
- Removed delays in TasmotaSerial and TasmotaModbus Tx enable switching
|
||||
- Keep webserver enabled on command ``upload``
|
||||
- Better support for virtual buttons and switches up to a total of 28
|
||||
- TuyaMcu support of virtual switches
|
||||
- Increase rule event buffer from 100 to 256 characters [#16943](https://github.com/arendst/Tasmota/issues/16943)
|
||||
- Tasmota OTA scripts now support both unzipped and gzipped file uploads [#17378](https://github.com/arendst/Tasmota/issues/17378)
|
||||
- LVGL allow access to `lv.LAYOUT_GRID` and `lv.LAYOUT_FLEX` [#17948](https://github.com/arendst/Tasmota/issues/17948)
|
||||
- ESP32 Framework (Core) from v2.0.6 to v2.0.7
|
||||
- ESP32 LVGL library from v8.3.3 to v8.3.6 (no functional change)
|
||||
- LibTeleinfo from v1.1.3 to v1.1.5 [#18050](https://github.com/arendst/Tasmota/issues/18050)
|
||||
- Increase number of (virtual)relays and (virtual)buttons to 32
|
||||
- ADC Range oversample from 2 to 32 [#17975](https://github.com/arendst/Tasmota/issues/17975)
|
||||
- Move #define OTA_URL from user_config.h to board files for better inital support [#18008](https://github.com/arendst/Tasmota/issues/18008)
|
||||
- Removed absolute url from filesystem [#18148](https://github.com/arendst/Tasmota/issues/18148)
|
||||
|
||||
### Fixed
|
||||
- Modbus transmit enable GPIO enabled once during write buffer
|
||||
- Energy dummy switched voltage and power regression from v12.2.0.2
|
||||
- Shutter default motorstop set to 0 [#17403](https://github.com/arendst/Tasmota/issues/17403)
|
||||
- Shutter default tilt configuration [#17484](https://github.com/arendst/Tasmota/issues/17484)
|
||||
- Orno WE517 modbus serial config 8E1 setting [#17545](https://github.com/arendst/Tasmota/issues/17545)
|
||||
- Rename ``tasmota4M.bin`` to ``tasmota-4M.bin`` to solve use of ``tasmota-minimal.bin`` [#17674](https://github.com/arendst/Tasmota/issues/17674)
|
||||
- ESP8266 set GPIO's to input on power on fixing relay spikes [#17531](https://github.com/arendst/Tasmota/issues/17531)
|
||||
- ESP8266 TLS SNI which would prevent AWS IoT connection [#17936](https://github.com/arendst/Tasmota/issues/17936)
|
||||
- TuyaMcu v1 sequence fix [#17625](https://github.com/arendst/Tasmota/issues/17625)
|
||||
- SEN5X floats and units [#17961](https://github.com/arendst/Tasmota/issues/17961)
|
||||
- Energytotals cannot be set to negative values [#17965](https://github.com/arendst/Tasmota/issues/17965)
|
||||
- SR04 driver single pin ultrasonic sensor detection [#17966](https://github.com/arendst/Tasmota/issues/17966)
|
||||
- IR panasonic protocol regression from v12.0.2.4 [#18013](https://github.com/arendst/Tasmota/issues/18013)
|
||||
- EnergyTotal divided twice during minimal upgrade step regression from v12.3.1.3 [#18024](https://github.com/arendst/Tasmota/issues/18024)
|
||||
- TuyaMcu v1 timer integer overflow [#18048](https://github.com/arendst/Tasmota/issues/18048)
|
||||
- PZEM energy monitor stabilize period on larger configs [#18103](https://github.com/arendst/Tasmota/issues/18103)
|
||||
- Rule topic comparison [#18144](https://github.com/arendst/Tasmota/issues/18144)
|
||||
- Refactor energy monitoring reducing stack usage and solve inherent exceptions and watchdogs [#18164](https://github.com/arendst/Tasmota/issues/18164)
|
||||
- ESP32 ``Upload``, ``Upgrade``, ``WebGetConfig``, ``WebQuery`` and ``WebSend`` random HTTP(S) connection timeout set to 5 sec (commit 542eca3)
|
||||
- ESP32 energy period shows kWh value instead of Wh regression from v12.3.1.5 [#15856](https://github.com/arendst/Tasmota/issues/15856)
|
||||
- ESP32 energy monitoring set StartTotalTime regression from v12.3.1.5 [#18385](https://github.com/arendst/Tasmota/issues/18385)
|
||||
|
73
TEMPLATES.md
73
TEMPLATES.md
@ -5,7 +5,7 @@
|
||||
|
||||
# Templates
|
||||
|
||||
Find below the available templates as of February 2023. More template information can be found in the [Tasmota Device Templates Repository](http://blakadder.github.io/templates)
|
||||
Find below the available templates as of April 2023. More template information can be found in the [Tasmota Device Templates Repository](http://blakadder.github.io/templates)
|
||||
|
||||
## Adapter Board
|
||||
```
|
||||
@ -220,6 +220,7 @@ LoraTap In-Wall {"NAME":"SC500W","GPIO":[0,0,0,576,160,161,0,0,224,
|
||||
LoraTap In-Wall {"NAME":"SC511WSC","GPIO":[0,1,0,320,32,34,0,0,224,33,226,225,0,0],"FLAG":0,"BASE":18}
|
||||
MS-108 In-Wall {"NAME":"MS-108","GPIO":[0,0,0,0,161,160,0,0,224,0,225,0,0,0],"FLAG":0,"BASE":18}
|
||||
MS-108WR RF Curtain Module {"NAME":"MS-108WR","GPIO":[1,1,1,544,32,33,1,1,225,32,224,1,1,1],"FLAG":0,"BASE":18}
|
||||
Nous {"NAME":" Smart WiFi Curtain Module L12T","GPIO":[1,160,1,161,225,224,1,1,544,1,32,1,1,1],"FLAG":0,"BASE":18}
|
||||
QS-WIFI-C01-RF {"NAME":"Shutter-QS-WIFI-C01","GPIO":[0,0,1,0,288,0,0,0,32,33,224,225,0,0],"FLAG":0,"BASE":18}
|
||||
```
|
||||
|
||||
@ -251,6 +252,7 @@ Zemismart Backlit {"NAME":"WF-CS01","GPIO":[544,227,289,34,226,161,0,
|
||||
|
||||
## DIN Relay
|
||||
```
|
||||
CurrySmarter Power Monitoring 30A {"NAME":"30A Breaker","GPIO":[0,0,0,0,7584,224,0,0,2720,32,2656,2624,320,0],"FLAG":0,"BASE":18}
|
||||
EARU DIN Circuit Breaker 1P 32A/50A {"NAME":"RDCBC-1P","GPIO":[320,0,0,0,0,0,0,0,32,224,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Hoch Circuit Breaker 1P {"NAME":"HOCH ZJSB9","GPIO":[32,0,0,0,0,0,0,0,224,320,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Ketotek Single Phase Energy Monitor {"NAME":"Ketotek KTEM06","GPIO":[0,2272,0,2304,0,0,0,0,0,0,320,0,32,0],"FLAG":0,"BASE":54}
|
||||
@ -288,10 +290,12 @@ Adafruit QT Py ESP32 Pico {"NAME":"QTPy ESP32 Pico","GPIO":[32,3200,0,3232,1,
|
||||
AZ-Envy Environmental Sensor {"NAME":"AZ Envy","GPIO":[32,0,320,0,640,608,0,0,0,0,0,0,0,4704],"FLAG":0,"BASE":18}
|
||||
Coiaca Tasmota {"NAME":"AWR01t","GPIO":[576,1,1,128,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Coiaca Tasmota Development Board AWR12 {"NAME":"AWR12t","GPIO":[320,1,1,1,1,1,0,0,1,1,1,1,1,1],"FLAG":0,"BASE":18}
|
||||
Dasduino CONNECT {"NAME":"Dasduino CONNECTPLUS","GPIO":[32,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,640,608,1,0,1,1,1,0,0,0,0,1376,1,1,1,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
Dasduino CONNECTPLUS {"NAME":"Dasduino CONNECT","GPIO":[1,1,1376,1,640,608,1,1,1,1,1,1,1,1],"FLAG":0,"BASE":18}
|
||||
Espoir Rev 1.0.0 PoE+ {"NAME":"Espoir","GPIO":[0,0,1,0,1,1,1,1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,5568,5600,1,7968,1,1,1,1],"FLAG":0,"BASE":1}
|
||||
LC Technology MicroPython Maker {"NAME":"LC-ESP-Python","GPIO":[1,1,544,1,1,1,1,1,1,1,1,1,1,1],"FLAG":0,"BASE":18}
|
||||
LilyGO RGB LED Ring Encoder {"NAME":"T-Encoder","GPIO":[0,0,1,0,1,0,0,0,1,1,1,1,0,0,0,480,6212,0,0,0,0,449,450,448,0,0,0,0,0,0,0,0,3296,3264,32,0],"FLAG":0,"BASE":1,"CMND":"BuzzerPwm 1"}
|
||||
LilyGO T7 v1.5 {"NAME":"LilyGO T7 V1.5","GPIO":[1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,544,0,0,0,1,0,1,1,1,0,0,0,0,0,1,1,4704,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
LilyGO T7 Mini32 V1.5 {"NAME":"LilyGO T7 V1.5","GPIO":[1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,544,0,0,0,1,0,1,1,1,0,0,0,0,0,1,1,4704,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
LilyGO TTGO ESP32 Ethernet {"NAME":"T-Internet-POE v1.2","GPIO":[0,1,1,1,1,1,1,1,1,1,1,1,1,1,5600,1,0,1,1,5568,0,1,1,1,0,0,0,0,1,1,1,1,1,0,0,1],"FLAG":0,"BASE":1,"CMND":"EthType 0|EthClockMode 1|EthAddress 0"}
|
||||
M5Stack Atom Lite ESP32 {"NAME":"M5Stack Atom Lite","GPIO":[1,1,1,1,1,1,1,1,1056,1,1,1,1,1,1,1,0,1,1,1,0,1,640,1376,0,0,0,0,608,1,1,1,1,0,0,32],"FLAG":0,"BASE":1}
|
||||
M5Stack AtomU USB-A ESP32 "Not available"
|
||||
@ -467,7 +471,7 @@ Wireless Tag 3.5" Touch {"NAME":"WT32-SC01","GPIO":[6210,1,1,1,1,1,0,0,1,70
|
||||
|
||||
## Display Switch
|
||||
```
|
||||
Lanbon L8 5 in 1 LCD Touch {"NAME":"Lanbon L8","GPIO":[0,0,0,0,0,992,0,0,224,0,225,0,0,0,1024,896,0,6624,6592,864,0,832,416,226,0,0,0,0,417,418,0,352,0,0,0,4736],"FLAG":0,"BASE":1}
|
||||
Lanbon L8 5 in 1 LCD Touch {"NAME":"Lanbon L8","GPIO":[608,0,0,0,640,992,0,0,224,0,225,0,0,0,1024,736,0,800,768,704,6210,672,416,226,0,0,0,0,417,418,0,2688,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
Sonoff NSPanel Touch {"NAME":"NSPanel","GPIO":[0,0,0,0,3872,0,0,0,0,0,32,0,0,0,0,225,0,480,224,1,0,0,0,33,0,0,0,0,0,0,0,0,0,0,4736,0],"FLAG":0,"BASE":1,"CMND":"ADCParam1 2,11200,10000,3950 | Sleep 0 | BuzzerPWM 1"}
|
||||
```
|
||||
|
||||
@ -590,6 +594,7 @@ Proscenic {"NAME":"Generic","GPIO":[1,1,1,1,1,1,0,0,1,1,1,1,1
|
||||
3DStar ESP IR Blaster xLR {"NAME":"3DS_IR Blaster_xLR","GPIO":[0,0,0,0,0,0,0,0,0,1088,0,1056,0,0],"FLAG":0,"BASE":18}
|
||||
A1 Universal Remote Control {"NAME":"A1 IR Controller","GPIO":[1,1,1,1,320,1088,0,0,0,32,1056,0,0,0],"FLAG":0,"BASE":62}
|
||||
AI Universal Remote {"NAME":"YTF IR Controller","GPIO":[1,1,1,1,320,1088,0,0,0,32,1056,0,0,0],"FLAG":0,"BASE":62}
|
||||
AI Universal Remote Control {"NAME":"LQ-08","GPIO":[0,0,0,0,0,1088,0,0,0,32,1056,0,0,0],"FLAG":0,"BASE":62}
|
||||
Alfawise KS1 {"NAME":"KS1","GPIO":[1,1792,32,1824,32,1088,0,0,320,0,1056,0,0,4704],"FLAG":0,"BASE":62}
|
||||
Antsig Universal Remote Controller {"NAME":"Antsig Smart Wi-Fi IR","GPIO":[1,1,1,1,320,1088,0,0,0,32,1056,0,0,0],"FLAG":0,"BASE":62}
|
||||
Automate Things IR Bridge {"NAME":"AT-IRBR-1.0","GPIO":[0,0,0,0,1056,1088,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18,"CMND":"Module 0"}
|
||||
@ -689,6 +694,7 @@ RGB+CCT 12-24V {"NAME":"WS05","GPIO":[0,0,0,0,0,420,0,0,418,417,41
|
||||
RGBW 12-24V {"NAME":"*WS04","GPIO":[0,0,0,0,0,0,0,0,417,418,416,419,0,0],"FLAG":0,"BASE":18}
|
||||
Shelly RGBW2 {"NAME":"Shelly RGBW2","GPIO":[0,0,288,0,419,1,0,0,416,32,418,417,0,0],"FLAG":0,"BASE":18}
|
||||
Spectrum Smart RGBCCT {"NAME":"Spectrum Smart RGB CCT Controller","GPIO":[32,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
Tuya RGBCCT {"NAME":"AP-5CH-1","GPIO":[0,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
Xunata Led Controller High Voltage 110/220V {"NAME":"KL-LED-WIFI-AC","GPIO":[0,0,0,0,0,0,0,0,0,416,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
ZJ-WF-ESP-A v1.1 {"NAME":"RGB2","GPIO":[0,0,0,0,0,0,0,0,417,416,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
```
|
||||
@ -801,6 +807,7 @@ Novostella UT88836 20W Flood {"NAME":"Novo 20W Flood","GPIO":[0,0,0,0,416,419,0
|
||||
Nue Vision Care Desk Lamp {"NAME":"Nue Vision Desk Lamp VC18","GPIO":[1,1,1,1,1,1,0,0,1,1,1,1,1,0],"FLAG":0,"BASE":54,"CMND":"TuyaMCU 11,1 | TuyaMCU 21,3 | TuyaMCU 23,4"}
|
||||
Philips Wiz 24W LED White Batten {"NAME":"PHILIPS-wiz-24w","GPIO":[0,0,0,0,417,0,0,0,0,416,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Polycab Hohm Avenir 20W Batten {"NAME":"PolycabBatten","GPIO":[0,0,0,0,0,416,0,0,0,449,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
RGB Floor Lamp {"NAME":"Floor Lamp","GPIO":[0,2272,0,2304,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":54}
|
||||
Sonoff {"NAME":"Sonoff BN-SZ","GPIO":[0,0,0,0,0,0,0,0,416,320,0,0,0,0],"FLAG":0,"BASE":22}
|
||||
Spotlight 9cm RGB+W 7W {"NAME":"Spotlight RGBW","GPIO":[0,0,0,0,0,0,0,0,0,3008,0,3040,0,0],"FLAG":0,"BASE":27}
|
||||
TCP WPAN Square 600X600mm 36W CCT Panel {"NAME":"TCPsmart LED Panel","GPIO":[0,0,0,0,0,416,0,0,0,449,0,0,0,0],"FLAG":0,"BASE":18,"CMND":"SO92 1|DimmerRange 30,100"}
|
||||
@ -838,6 +845,9 @@ Proscenic T21 Air Fryer {"NAME":"Proscenic T21","GPIO":[0,2272,0,2304,0,0,0
|
||||
RainPoint Indoor Water Pump {"NAME":"RainPoint","GPIO":[0,0,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":54,"CMND":"TuyaMCU 81,107|TuyaMCU 12,109|TuyaMCU 11,1|TuyaMCU 82,104"}
|
||||
Sinilink PCIe Computer Remote {"NAME":"XY-WPCE","GPIO":[1,1,320,1,32,224,0,0,160,0,0,0,0,0],"FLAG":0,"BASE":18,"CMND":"SO114 1 | SwitchMode1 2"}
|
||||
Sinilink USB Computer Remote {"NAME":"XY-WPCL","GPIO":[0,0,320,0,0,224,0,32,160,0,0,0,0,0],"FLAG":0,"BASE":18,"CMND":"SO114 1 | Pulsetime 10 | SwitchMode1 2"}
|
||||
Sinilink XY-Clock Clock Alarm Module {"NAME":"XY-Clock","GPIO":[288,0,289,0,0,416,32,33,608,640,0,0,34,0],"FLAG":0,"BASE":18}
|
||||
Sunbeam LoftTec Electric Blanket {"NAME":"Sunbeam Heated Blanket","GPIO":[0,2272,0,2304,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":54}
|
||||
Ulanzi Smart Pixel Clock {"NAME":"Ulanzi TC001","GPIO":[0,0,0,0,0,0,0,0,0,0,34,480,0,0,0,0,0,640,608,0,0,0,32,33,0,0,0,0,1376,0,4704,4768,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
Xystec USB3.0 4 Port Hub {"NAME":"Xystec USB Hub","GPIO":[0,0,0,0,224,0,0,0,226,227,225,0,0,0],"FLAG":0,"BASE":18}
|
||||
```
|
||||
|
||||
@ -922,7 +932,9 @@ Kogan Energy Meter IP44 {"NAME":"Kogan Smart Sw IP44","GPIO":[32,0,0,0,2688
|
||||
Konyks Pluviose 16A IP55 {"NAME":"Konyks Pluviose","GPIO":[32,0,0,0,0,0,0,0,224,288,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Koolertron {"NAME":"C168 Outdoor","GPIO":[0,32,0,320,2720,2656,0,0,224,2624,225,226,0,0],"FLAG":0,"BASE":18}
|
||||
Ledvance Smart+ 16A {"NAME":"LEDVANCE Smart Wifi Outdoor Plug","GPIO":[0,0,0,320,2688,2656,0,0,224,32,2624,0,0,0],"FLAG":0,"BASE":18}
|
||||
Ledvance Smart+ Compact {"NAME":"LEDVANCE SMART+ Compact Outdoor Plug ","GPIO":[0,0,0,0,320,0,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
LSC Dual Socket {"NAME":"LSC NFL-022","GPIO":[0,0,0,0,320,32,0,0,0,224,225,0,0,0],"FLAG":0,"BASE":18}
|
||||
LSC Dual Socket {"NAME":"LSC Outdoor Dual Socket","GPIO":[320,0,0,32,8673,8672,0,0,0,0,8674,0,8675,0],"FLAG":0,"BASE":18}
|
||||
Luminea 2 Outlet {"NAME":"Luminea","GPIO":[0,0,0,0,225,320,0,0,224,321,32,0,0,1],"FLAG":0,"BASE":18}
|
||||
Luminea NX-4458 {"NAME":"Luminea NX4458","GPIO":[32,0,0,0,2688,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":65}
|
||||
Master {"NAME":"Master_IOT-EXTPLUG","GPIO":[32,1,0,1,1,0,0,0,224,288,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
@ -957,6 +969,7 @@ WiOn Yard Stake {"NAME":"WiOn 50053","GPIO":[0,0,320,0,0,0,0,0,0,32
|
||||
WOOX R4051 {"NAME":"WOOX R4051","GPIO":[32,0,0,0,0,0,0,0,224,320,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
WOOX R4052 {"NAME":"WOOX R4052","GPIO":[32,0,0,0,0,0,0,0,224,320,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Wyze {"NAME":"Wyze Plug Outdoor","GPIO":[0,0,0,0,0,576,0,0,0,0,0,224,321,7713,7712,320,0,0,0,0,0,2624,2656,2720,0,0,0,0,225,0,4704,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
XtendLan IP66 Double {"NAME":"XtendLan_ZAS4","GPIO":[32,0,0,0,0,225,33,0,224,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
```
|
||||
|
||||
## Plug
|
||||
@ -1046,6 +1059,7 @@ Avatto OT08 {"NAME":"Avatto OT08","GPIO":[416,0,418,0,417,2720,
|
||||
Awow X5P {"NAME":"Awow","GPIO":[0,0,320,0,0,0,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
AWP02L-N {"NAME":"AWP02L-N","GPIO":[0,0,320,0,0,0,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
AzpenHome Smart {"NAME":"Socket2Me","GPIO":[288,1,1,1,225,1,0,0,224,1,32,1,1,0],"FLAG":0,"BASE":18}
|
||||
Baco Smart Power Socket {"NAME":"Balco HE200021","GPIO":[0,0,0,32,2720,2656,0,0,2624,576,224,0,0,0],"FLAG":0,"BASE":52}
|
||||
Bagotte SK-EU-A01 {"NAME":"Bagotte SK-EU-A01","GPIO":[96,0,0,0,0,0,0,0,224,320,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Bakibo Mini {"NAME":"SM300","GPIO":[320,0,576,0,0,2720,0,0,2624,32,2656,224,0,0],"FLAG":0,"BASE":59}
|
||||
Bakibo TP22Y {"NAME":"Bakibo TP22Y","GPIO":[0,0,0,32,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":52}
|
||||
@ -1180,6 +1194,7 @@ GDTech {"NAME":"GDTech Model: MPV2RO-US","GPIO":[320,0,0,0
|
||||
GDTech W-US001 {"NAME":"GDTech W-US001","GPIO":[1,32,1,1,1,1,0,0,1,320,224,1,1,4704],"FLAG":0,"BASE":18}
|
||||
GDTech W-US003 {"NAME":"W-US003","GPIO":[0,32,1,1,1,0,0,0,0,320,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Geekbes YM-WS-1 {"NAME":"Office Test Pl","GPIO":[1,1,1,1,1,1,1,1,576,32,163,224,1,1],"FLAG":0,"BASE":18}
|
||||
Geekome Enchufe Inteligente Chile {"NAME":"Geekhome PG01-CL10A_T","GPIO":[0,0,0,32,2720,2656,0,0,2624,544,224,0,0,0],"FLAG":0,"BASE":1}
|
||||
Geeni OUTDOOR {"NAME":"Geeni Outdoor","GPIO":[32,0,0,0,0,0,0,0,0,320,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Geeni Spot {"NAME":"Geeni Spot","GPIO":[576,0,0,0,320,0,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Geeni Spot Glo {"NAME":"Geeni Glo","GPIO":[0,0,0,0,320,0,0,0,224,32,225,0,0,0],"FLAG":0,"BASE":18}
|
||||
@ -1233,7 +1248,8 @@ HIPER IoT P05 {"NAME":"HIPER IoT P05","GPIO":[0,320,0,32,0,0,0,0,
|
||||
hiwild W-US002 {"NAME":"W-US002","GPIO":[0,32,0,0,0,0,0,0,0,288,224,0,576,0],"FLAG":0,"BASE":18}
|
||||
HLT-309 {"NAME":"HLT-309","GPIO":[0,0,0,0,0,0,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Hoin 10A {"NAME":"NIOH XS-SSC01","GPIO":[0,32,0,0,0,0,0,0,0,320,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Hombli Socket Duo {"NAME":"HombliSocketDuo","GPIO":[33,0,0,0,0,0,0,0,0,544,224,225,32,0],"FLAG":0,"BASE":18}
|
||||
Hombli Smart Socket EU {"NAME":"Hombli HBSS-0109","GPIO":[0,0,0,32,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Hombli Socket Duo {"NAME":"Hombli HBSD-0109","GPIO":[33,0,0,0,0,0,0,0,0,544,224,225,32,0],"FLAG":0,"BASE":18}
|
||||
Homecube {"NAME":"Homecube SP1","GPIO":[0,321,0,32,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":55}
|
||||
HomeMate 16A Heavy Duty {"NAME":"HMLPG16","GPIO":[0,288,0,32,2720,2656,0,0,2624,544,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Houzetek {"NAME":"AWP07L","GPIO":[320,0,0,0,0,2720,0,0,2624,32,2656,224,0,0],"FLAG":0,"BASE":18}
|
||||
@ -1247,11 +1263,13 @@ Hyleton 314 {"NAME":"hyleton-314","GPIO":[321,0,320,0,0,0,0,0,0
|
||||
Hyleton 315 {"NAME":"hyleton-315","GPIO":[0,0,0,0,321,320,0,0,224,64,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Hyleton 317 {"NAME":"hyleton-317","GPIO":[320,0,321,0,322,0,0,0,0,64,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Hyleton HLT-311 {"NAME":"HLT-311","GPIO":[544,0,320,0,0,0,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Hyrican SmartPlug 16A {"NAME":"Hyrican TM-MP-EU02","GPIO":[0,0,0,32,2656,2624,0,0,224,2720,320,0,0,0],"FLAG":0,"BASE":18}
|
||||
iClever IC-BS08 {"NAME":"iClever BS08","GPIO":[0,0,0,0,544,320,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
iDIGITAL {"NAME":"Brilliant","GPIO":[0,0,0,0,288,0,0,0,224,64,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
iGET Security {"NAME":"iGET Security DP16","GPIO":[320,1,576,1,2656,2720,0,0,2624,32,0,224,0,0],"FLAG":0,"BASE":45}
|
||||
Ihommate 16A {"NAME":"ZCH-02","GPIO":[0,0,0,32,2688,2656,0,0,2624,320,224,0,0,4704],"FLAG":0,"BASE":18}
|
||||
Infray 16A {"NAME":"AWP08L","GPIO":[32,0,288,0,0,0,0,0,0,0,0,224,0,4704],"FLAG":0,"BASE":18}
|
||||
Insmart {"NAME":"INSMART SP1","GPIO":[320,1,321,1,0,2720,0,0,2624,32,2656,224,0,0],"FLAG":0,"BASE":45}
|
||||
Insmart WP5 {"NAME":"INSMART","GPIO":[0,0,448,0,0,0,0,0,0,160,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Intempo Home Euro 2-Pin {"NAME":"Intempo EE5010WHTSTKEU","GPIO":[0,0,0,32,0,2720,0,0,0,576,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
iQtech SmartLife {"NAME":"iQ-Tech WS020","GPIO":[0,0,320,0,0,2720,0,0,2624,32,2656,224,0,0],"FLAG":0,"BASE":18}
|
||||
@ -1357,6 +1375,7 @@ Moes W-DE004S {"NAME":"Moes DE004S ","GPIO":[321,0,0,0,0,2720,0,0
|
||||
Moes WS-UEU {"NAME":"MoesHouse","GPIO":[0,0,0,224,32,0,0,0,321,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
MoKo 2 USB {"NAME":"MoKo Plug","GPIO":[0,32,0,0,0,0,0,0,0,321,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
MoKo YX-WS01A {"NAME":"MoKo Plug","GPIO":[0,32,0,0,0,0,0,0,0,321,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Muvit iO MIOSMP008 {"NAME":"MIOSMP008","GPIO":[0,0,0,32,2656,2624,0,0,224,2720,320,0,0,0],"FLAG":0,"BASE":1}
|
||||
MXQ LED Nightlight {"NAME":"MXQ SP06","GPIO":[0,0,0,0,288,192,0,0,225,321,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Nanxin NX-SM400 {"NAME":"NX-SM400","GPIO":[0,0,0,32,2720,2656,0,0,2592,288,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Naxa NSH-1000 {"NAME":"Naxa NSH-1000","GPIO":[0,0,0,0,32,0,0,0,321,320,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
@ -1389,6 +1408,7 @@ OFFONG 16A {"NAME":"OFFONG P1","GPIO":[0,32,0,0,2720,2656,0,0,
|
||||
Oittm Smart {"NAME":"Oittm","GPIO":[0,0,0,0,224,320,0,0,32,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
Olliwon {"NAME":"Olliwon","GPIO":[0,0,320,0,0,0,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Onearz Connect Smart {"NAME":"Onearz Power Plug Wifi","GPIO":[0,0,0,0,288,224,0,0,0,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Oneplug {"NAME":"ONEPLUG UP111","GPIO":[321,0,0,0,2656,2720,0,0,2624,0,224,0,32,0],"FLAG":0,"BASE":18}
|
||||
Onestyle SD-WL-02 {"NAME":"JH-G01B1","GPIO":[0,3072,0,3104,0,0,0,0,32,320,224,0,0,0],"FLAG":0,"BASE":41}
|
||||
Orbecco W-US009 {"NAME":"Orbecco Plug","GPIO":[0,32,0,0,0,0,0,0,0,288,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Orvibo B25 {"NAME":"Orvibo B25","GPIO":[0,0,0,0,289,224,0,0,288,0,32,0,0,0],"FLAG":0,"BASE":18}
|
||||
@ -1405,8 +1425,8 @@ Panamalar NX-SM200 {"NAME":"NX-SM200","GPIO":[0,0,0,0,320,2720,0,0,262
|
||||
Positivo PPW1000 {"NAME":"PPW1000","GPIO":[0,0,320,0,0,2720,0,0,2624,32,2656,224,0,0],"FLAG":0,"BASE":45}
|
||||
Positivo Max {"NAME":"PPW1600","GPIO":[0,0,0,32,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":55}
|
||||
PowerAdd BIE0091 {"NAME":"BIE0091","GPIO":[32,0,0,0,0,0,0,0,416,0,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Powertech {"NAME":"Jaycar MS6104","GPIO":[0,0,0,32,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":52}
|
||||
Powertech {"NAME":"Jaycar","GPIO":[320,0,0,0,0,0,0,0,0,160,0,224,0,0],"FLAG":0,"BASE":6}
|
||||
Powertech MS6104 {"NAME":"Jaycar MS6104","GPIO":[0,0,0,32,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":52}
|
||||
Powrui 3-Outlet with 4 USB {"NAME":"POWRUI AHR-077","GPIO":[0,0,0,35,34,33,0,0,225,226,32,224,544,0],"FLAG":0,"BASE":18}
|
||||
Powrui AW-08 {"NAME":"POWRUI AW-08","GPIO":[0,0,0,0,32,224,0,0,0,288,321,0,0,4704],"FLAG":0,"BASE":18}
|
||||
Premier PWIFPLG {"NAME":"Premier Plug","GPIO":[0,0,0,32,0,0,0,0,0,320,224,0,0,4704],"FLAG":0,"BASE":18}
|
||||
@ -1432,10 +1452,13 @@ SA-P202A {"NAME":"SA-P202A","GPIO":[0,0,0,0,0,320,0,0,224,32
|
||||
SA-P202C 16A {"NAME":"Elivco 202C-G","GPIO":[0,0,0,32,2688,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
SA-P302A {"NAME":"KinCam SA-P302A","GPIO":[0,0,0,0,0,320,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Sansui {"NAME":"Sansui YSP-1","GPIO":[288,0,289,0,0,0,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Sansui Rewireable {"NAME":"YSP-2","GPIO":[288,0,289,0,0,0,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
See Switches {"NAME":"SEESWITCHES SSPG01WH","GPIO":[321,1,320,1,0,2720,0,0,2624,32,2656,224,0,0],"FLAG":0,"BASE":45}
|
||||
Setti+ {"NAME":"Setti+ SP301","GPIO":[320,0,576,0,2656,2720,0,0,2624,32,0,224,0,0],"FLAG":0,"BASE":45}
|
||||
Shelly Plug {"NAME":"Shelly Plug EU","GPIO":[0,0,0,0,224,2688,0,0,96,288,289,0,290,0],"FLAG":0,"BASE":18}
|
||||
Shelly Plug S {"NAME":"Shelly Plug S","GPIO":[320,1,576,1,1,2720,0,0,2624,32,2656,224,1,4736],"FLAG":0,"BASE":45}
|
||||
Shelly Plug US {"NAME":"Shelly Plug US","GPIO":[288,0,321,0,224,2720,0,0,2624,32,2656,544,0,0],"FLAG":0,"BASE":45}
|
||||
Shelly Plus Plug S {"NAME":"Shelly Plus Plug S","GPIO":[0,0,0,0,224,0,32,2720,0,0,0,0,0,0,0,2624,0,0,2656,0,0,288,289,0,0,0,0,0,0,4736,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
Sieges {"NAME":"Sieges FLHS-ZN01","GPIO":[0,0,320,0,0,0,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
SilentNight {"NAME":"SilentNightPlug","GPIO":[0,0,0,0,288,0,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Silvergear Slimme Stekker {"NAME":"Silvergear SmartHomePlug","GPIO":[0,0,0,96,0,0,0,0,0,320,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
@ -1475,6 +1498,8 @@ STITCH {"NAME":"Stitch 27937","GPIO":[32,0,320,0,2688,2656
|
||||
STITCH {"NAME":"Stitch 35511","GPIO":[320,0,321,0,0,2688,0,0,0,32,2656,224,2624,0],"FLAG":0,"BASE":18}
|
||||
STITCH 15A In-Line {"NAME":"Stitch 39047","GPIO":[0,288,0,32,2688,2656,0,0,2624,0,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
STITCH Mini 10A {"NAME":"STITCH 41730","GPIO":[0,0,320,0,0,0,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Strong Helo Plight {"NAME":"Strong HELO-PLIGHT-EU","GPIO":[416,0,418,0,417,2720,0,0,2624,32,2656,224,0,0],"FLAG":0,"BASE":18}
|
||||
Strong Helo PLUSB 2x USB {"NAME":"Helo-PLUSB-EU","GPIO":[32,0,0,0,2720,2656,0,0,2624,320,224,225,8096,0],"FLAG":0,"BASE":18}
|
||||
SuperNight Dual {"NAME":"SUPERNIGHT","GPIO":[0,32,0,224,2656,2688,0,0,225,2624,576,0,0,4833],"FLAG":0,"BASE":18}
|
||||
SWA1 {"NAME":"SWA1","GPIO":[0,0,0,0,288,224,0,0,0,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
SWA1 FR {"NAME":"SWA1","GPIO":[0,0,0,0,288,224,0,0,0,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
@ -1763,6 +1788,7 @@ AiYaTo 12W {"NAME":"AiYaTo RGBCW","GPIO":[0,0,0,0,419,418,0,0,
|
||||
Alfawise LE12 9W 900LM {"NAME":"Alfawise LE12 ","GPIO":[0,0,0,0,420,417,0,0,418,0,419,416,0,0],"FLAG":0,"BASE":18}
|
||||
Aoycocr JL81 5W 400lm {"NAME":"AoycocrJLB1","GPIO":[0,0,0,0,418,0,0,0,417,420,416,419,0,0],"FLAG":0,"BASE":18}
|
||||
Aoycocr Q10CWM BR30 9W 720lm {"NAME":"AoycocrBR30","GPIO":[0,0,0,0,0,418,0,0,417,0,416,419,0,0],"FLAG":0,"BASE":18}
|
||||
Arlec 5.5W 470lm {"NAME":"Arlec GLD360HA","GPIO":[0,0,0,0,0,0,0,0,4067,0,4032,0,0,0],"FLAG":0,"BASE":18,"CMND":"SO37 24"}
|
||||
Arlec Smart 10W 830lm {"NAME":"Arlec GLD320HA","GPIO":[0,0,0,0,4067,0,0,0,0,4032,0,0,0,0],"FLAG":0,"BASE":18,"CMND":"SO37 6"}
|
||||
Arlec Smart 9.5W 806lm {"NAME":"Arlec RGBWW","GPIO":[0,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
Arlec Smart 9.5W 806lm {"NAME":"Arlec RGBWW","GPIO":[0,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
@ -1951,13 +1977,14 @@ cod.m WLAN Pixel Controller v0.8 {"NAME":"cod.m Pixel Controller V0.8","GPIO":[
|
||||
ESP01 NeoPixel Ring {"NAME":"ESP-01S-RGB-LED-v1.0","GPIO":[1,1,1376,1,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
H803WF 2048 Pixel 5V-24V {"NAME":"H803WF","GPIO":[0,0,0,0,3840,3840,0,0,3872,1376,0,3872,0,0],"FLAG":0,"BASE":18}
|
||||
IOTMCU {"NAME":"IOTMCU_ESP-12S-RGB-LED-v1","GPIO":[1,1,1,1,0,1376,0,0,1,1088,32,0,0,0],"FLAG":0,"BASE":18}
|
||||
LifeSmart Cololight MIX {"NAME":"ESP32-DevKit","GPIO":[0,0,0,0,1376,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,4704,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
LifeSmart Cololight PRO Hexagonal {"NAME":"Cololight PRO","GPIO":[0,0,0,0,32,0,0,0,0,33,0,0,1376,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4704,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
SP501E WS2812B {"NAME":"SP501E","GPIO":[0,32,0,1376,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
```
|
||||
|
||||
## RGBW
|
||||
```
|
||||
3Stone EBE-QPW36 1050lm {"NAME":"3STONE","GPIO":[0,0,0,0,2944,2912,0,0,416,2976,0,0,0,1],"FLAG":0,"BASE":18}
|
||||
3Stone EBE-QPW36 1050lm {"NAME":"3Stone","GPIO":[0,0,0,0,2912,417,0,0,0,416,2944,0,0,0],"FLAG":0,"BASE":18}
|
||||
Accewit 7W 650lm {"NAME":"Accewit Bulb","GPIO":[0,0,0,0,0,417,0,0,418,0,419,416,0,0],"FLAG":0,"BASE":18}
|
||||
Aisirer 7W 580lm {"NAME":"Aisirer RGBW","GPIO":[0,0,0,0,416,419,0,0,417,0,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
Aisirer 7W 580lm {"NAME":"Aisirer RGBW","GPIO":[0,0,0,0,416,419,0,0,417,0,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
@ -2093,6 +2120,7 @@ REPSN G45 5W 500lm {"NAME":"REPSN RGBW E14","GPIO":[0,0,0,0,0,0,0,0,40
|
||||
Riversong Juno 10W {"NAME":"Juno10","GPIO":[0,0,0,0,2912,416,0,0,0,2976,2944,0,0,0],"FLAG":0,"BASE":18}
|
||||
Rogoei EBE-QPZ04 6.5W 450lm {"NAME":"EBE-QPZ04","GPIO":[0,0,0,0,4032,0,0,0,0,0,4064,0,0,0],"FLAG":0,"BASE":18}
|
||||
Saudio 7W 700lm {"NAME":"X002BU0DOL","GPIO":[0,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
Sengled {"NAME":"Sengled RGBW","GPIO":[0,0,0,0,0,0,0,0,417,416,419,418,0,0],"FLAG":0,"BASE":18}
|
||||
Shelly Duo RGBW 5W 400lm {"NAME":"Shelly Duo RGBW","GPIO":[0,0,0,0,0,419,0,0,417,416,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
Shelly Duo RGBW 9W 800lm {"NAME":"Shelly Duo RGBW","GPIO":[0,0,0,0,0,419,0,0,417,416,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
Smart 810lm {"NAME":"OOOLED 60W RGB","GPIO":[0,0,0,0,418,419,0,0,416,0,417,0,0,4704],"FLAG":0,"BASE":18}
|
||||
@ -2129,6 +2157,7 @@ WOOX 4W 350lm {"NAME":"WOOX R4553","GPIO":[0,0,0,0,416,419,0,0,41
|
||||
WOOX R4553 650lm {"NAME":"WOOX R4553","GPIO":[0,0,0,0,416,419,0,0,417,0,418,0,0,0],"FLAG":0,"BASE":18}
|
||||
WOOX R5077 {"NAME":"WOOX R5077","GPIO":[0,0,0,0,2912,416,0,0,417,2976,2944,0,0,0],"FLAG":0,"BASE":18}
|
||||
Wyze Bulb Color {"NAME":"Wyze Bulb Color","GPIO":[0,0,0,0,0,0,0,0,0,418,416,419,0,0,0,0,0,0,0,224,0,0,417,0,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
Xiaomi Mi LED Smart Bulb Essential {"NAME":"Mi LED Smart Bulb Essential","GPIO":[0,0,0,0,418,419,0,0,416,417,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Zemismart 5W {"NAME":"Zemismart_GU10","GPIO":[0,0,0,0,0,0,0,0,0,3008,0,3040,0,0],"FLAG":0,"BASE":27}
|
||||
Zemismart 5W 480lm {"NAME":"Zemismart-E14-RGBW","GPIO":[0,0,0,0,0,0,0,0,0,3008,0,3040,0,0],"FLAG":0,"BASE":27}
|
||||
Zemismart A19 10W {"NAME":"Zemism_E27_A19","GPIO":[0,0,0,0,0,0,0,0,0,3008,0,3040,0,0],"FLAG":0,"BASE":27}
|
||||
@ -2145,7 +2174,10 @@ Athom 1Ch Inching/Self-locking {"NAME":"Athom R01","GPIO":[1,1,1,1,1,224,1,1,1,
|
||||
Athom 8Ch Inching/Self-locking 10A {"NAME":"Athom R08","GPIO":[229,1,1,1,230,231,1,1,226,227,225,228,224,0],"FLAG":0,"BASE":18}
|
||||
Claudy 5V {"NAME":"CLAUDY","GPIO":[0,0,225,0,0,0,0,0,0,0,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Devantech 8x16A {"NAME":"ESP32LR88","GPIO":[0,0,231,0,32,35,0,0,229,230,228,0,33,34,36,37,0,38,39,544,0,225,226,227,0,0,0,0,0,224,3232,3200,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
DoHome HomeKit DIY Switch {"NAME":"DoHome DIY","GPIO":[1,1,0,1,1,544,0,0,224,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
Dingtian 16 Channel {"NAME":"Dingtian DT-R008","GPIO":[1,9408,1,9440,1,1,1,1,1,9760,9729,9856,9792,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,9824,9952,1,1,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
Dingtian 32 Channel {"NAME":"Dingtian DT-R008","GPIO":[1,9408,1,9440,1,1,1,1,1,9760,9731,9856,9792,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,9824,9952,1,1,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
Dingtian 8 Channel {"NAME":"Dingtian DT-R008","GPIO":[1,9408,1,9440,1,1,1,1,1,9760,9728,9856,9792,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,9824,9952,1,1,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
DoHome HomeKit DIY Switch {"NAME":"DoHome DIY","GPIO":[1,1,0,1,96,544,0,0,224,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
Eachen ST-DC2 {"NAME":"Garage Control","GPIO":[162,0,0,0,226,225,33,0,224,288,163,227,0,4704],"FLAG":0,"BASE":18}
|
||||
Eachen ST-DC4 {"NAME":"Eachen_ST-DC4","GPIO":[160,1,1,1,226,225,1,1,224,544,1,227,1,0],"FLAG":0,"BASE":54}
|
||||
Eachen ST-UDC1 {"NAME":"ST-UDC1","GPIO":[160,0,0,0,0,0,0,0,224,320,0,0,0,4704],"FLAG":0,"BASE":18}
|
||||
@ -2153,14 +2185,18 @@ Electrodragon Board SPDT {"NAME":"ED Relay Board","GPIO":[1,1,1,1,1,1,0,0,22
|
||||
Electrodragon ESP8266 {"NAME":"ElectroDragon","GPIO":[33,1,32,1,1,1,0,0,225,224,1,1,288,4704],"FLAG":0,"BASE":15}
|
||||
Electrodragon Inductive Load {"NAME":"ED RelayBoard IL","GPIO":[0,0,1,0,1,1,0,0,224,225,1,1,288,0],"FLAG":0,"BASE":18}
|
||||
ESP-01 Relay V4.0 {"NAME":"ESP01v4","GPIO":[256,320,0,32,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
ESP-01 Relay V5.0 {"NAME":"ESP01v5","GPIO":[256,320,0,32,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
ESP-01S 5V Relay Module V1.0 {"NAME":"ESP-01S Relay","GPIO":[256,288,1,1,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
ESP-12F 5V/7-28V 1 Channel 30A {"NAME":"Aideepen","GPIO":[0,0,0,0,0,288,0,0,0,0,0,0,224,0],"FLAG":0,"BASE":18}
|
||||
ESP-12F 5V/7-28V 4 Channel 30A {"NAME":"ESP12F_Relay_30A_X4","GPIO":[1,1,1,1,32,1,1,1,226,227,225,1,224,1],"FLAG":0,"BASE":18}
|
||||
ESP-12F 5V/7-28V 8 Channel {"NAME":"ESP12F_Relay_X8","GPIO":[229,1,1,1,230,231,0,0,226,227,225,228,224,1],"FLAG":0,"BASE":18}
|
||||
ESP-12F 5V/7-28V 8 Channel {"NAME":"ESP12F_Relay_X8_v1.1","GPIO":[230,1,231,229,1,1,1,1,226,225,227,224,228,1],"FLAG":0,"BASE":18}
|
||||
ESP-12F 5V/7-30V/220V 4 Channel {"NAME":"ESP12F_Relay_X4","GPIO":[1,1,320,1,1,321,1,1,226,227,225,1,224,1],"FLAG":0,"BASE":18}
|
||||
ESP-12F 5V/8-80V 2 Channel {"NAME":"LC-Relay-ESP12-2R-D8","GPIO":[1,1,1,1,224,225,1,1,1,1,1,1,1,1],"FLAG":0,"BASE":18}
|
||||
ESP-12F DC 5V/12V/24V 16 Channel {"NAME":"ESP12F_Relay_X16","GPIO":[1,1,1,1,1,7712,1,1,7680,7648,7744,1,1,1],"FLAG":0,"BASE":18}
|
||||
ESP32 4 Channel {"NAME":"RobotDyn ESP32R4","GPIO":[0,0,1,0,1,1,0,0,1,1,1,1,1,1,1,1,0,0,0,1,0,224,225,0,0,0,0,0,227,226,32,33,34,0,0,35],"FLAG":0,"BASE":2}
|
||||
ESP32SR88 - WIFI 8 x 1A {"NAME":"ESP32SR88","GPIO":[0,3200,0,3232,226,544,0,0,0,0,0,0,225,224,32,544,33,34,35,36,0,37,38,39,0,229,228,227,0,0,0,0,231,230,0,0],"FLAG":0,"BASE":1}
|
||||
ESP8266 4 Channel 5V/12V {"NAME":"4CH Relay","GPIO":[0,0,0,0,0,0,0,0,226,227,225,0,224,0],"FLAG":0,"BASE":18}
|
||||
eWeLink PSF-B04 5V 7-32V 4 Channel {"NAME":"eWeLink 4CH","GPIO":[160,0,0,0,226,225,161,162,224,288,163,227,0,0],"FLAG":0,"BASE":18}
|
||||
Ewelink RF No Neutral 3 Channel {"NAME":"Ewelink 3 Gang Module","GPIO":[32,0,0,0,225,226,33,34,224,544,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Geekcreit 5V DIY 4 Channel Jog Inching Self-Locking {"NAME":"Geekcreit-4ch","GPIO":[160,0,0,0,226,225,161,162,224,288,163,227,0,0],"FLAG":0,"BASE":18}
|
||||
@ -2182,7 +2218,7 @@ LC Technology DC5-60V 1 Channel {"NAME":"ESP32_Relay_X1","GPIO":[1,1,1,1,1,1,1,
|
||||
LC Technology DC5-60V 2 Channel {"NAME":"ESP32_Relay_X2","GPIO":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,544,0,225,224,0,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
LC Technology DC5-60V 4 Channel {"NAME":"ESP32_Relay_X4","GPIO":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,544,0,226,227,1,0,0,0,0,224,225,1,1,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
LC Technology ESP8266 5V {"NAME":"ESP8266-01S","GPIO":[224,3200,0,3232,0,0,0,0,0,0,0,0,0,4704],"FLAG":0,"BASE":18}
|
||||
LilyGo T-Relay 8 {"NAME":"LilyGo ESP32 Relay 8","GPIO":[1,1,1,1,1,231,1,1,227,226,1,1,1,1,230,229,0,228,1,1,0,544,1,1,0,0,0,0,225,224,1,1,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
LilyGo T-Relay 5V 8 Channel {"NAME":"LilyGo ESP32 Relay 8","GPIO":[1,1,1,1,1,231,1,1,227,226,1,1,1,1,230,229,0,228,1,1,0,544,1,1,0,0,0,0,225,224,1,1,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
LilyGO TTGO 4 Channel ESP32 {"NAME":"T-Relay ESP32","GPIO":[0,0,1,0,1,227,0,0,1,1,1,1,0,0,226,225,0,224,1,1,0,544,1,1,0,0,0,0,1,1,1,1,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
LinkNode R4 {"NAME":"LinkNode R4","GPIO":[0,0,0,0,0,0,0,0,224,225,226,0,227,0],"FLAG":0,"BASE":18}
|
||||
LinkNode R8 {"NAME":"LinkNode R8","GPIO":[0,0,0,0,228,229,0,231,226,227,225,230,224,0],"FLAG":0,"BASE":18}
|
||||
@ -2202,15 +2238,13 @@ Sonoff SV {"NAME":"Sonoff SV","GPIO":[32,1,0,1,1,1,0,0,224,32
|
||||
Yunshan 7-30V 10A {"NAME":"Yunshan 10A","GPIO":[32,1,288,1,224,161,0,0,225,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
```
|
||||
|
||||
## Relay Module
|
||||
```
|
||||
```
|
||||
|
||||
## Sensor
|
||||
```
|
||||
Bresser 7-Kanal Thermo-/Hygrometer with Outdoor {"NAME":"Bresser","GPIO":[1,1,1,1,1,1,0,0,1,1,1,1,1,0],"FLAG":0,"BASE":54,"CMND":"SO97 1 | TuyaMCU 73,2 | TuyaMCU 71,102"}
|
||||
Genesense IoT Controller {"NAME":"GNS24","GPIO":[32,1,1312,1,256,320,1,1,256,1216,160,3840,1,4704],"FLAG":0,"BASE":18}
|
||||
Kaiweets Air Quality {"NAME":"Kaiweets EH-8","GPIO":[0,2272,0,2304,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":54}
|
||||
Shelly 3EM Power Monitoring Module {"NAME":"Shelly 3EM","GPIO":[1,1,288,1,32,8065,0,0,640,8064,608,224,8096,0],"FLAG":0,"BASE":18}
|
||||
Tuya Air Detector 6 in 1 {"NAME":"DCR-KQG","GPIO":[1,2272,544,2304,1,1,1,1,1,1,1,1,1,1],"FLAG":0,"BASE":54,"CMND":"TuyaMCU 80,2 | TuyaMCU 71,18 | TuyaMCU 73,19 | TuyaMCU 99,20 | TuyaMCU 76,21 | TuyaMCU 77,22 | HumRes 1 | TempRes 1 "}
|
||||
```
|
||||
|
||||
## Siren
|
||||
@ -2232,6 +2266,7 @@ Blitzwolf E27 {"NAME":"BlitzWolf LT-30","GPIO":[0,0,0,0,320,224,0
|
||||
Elegant Choice E27/E26 {"NAME":"name","GPIO":[0,0,0,0,0,0,0,0,0,0,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Slampher {"NAME":"Slampher","GPIO":[32,1,0,1,0,0,0,0,224,320,0,0,0,0],"FLAG":0,"BASE":9}
|
||||
SmartBase E0260 {"NAME":"SmartBaseE0260","GPIO":[0,0,0,0,320,0,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Timeguard Lamp Holder {"NAME":"Timeguard WFLH","GPIO":[0,0,0,0,576,320,0,0,224,0,32,0,0,0],"FLAG":0,"BASE":18}
|
||||
```
|
||||
|
||||
## Soil Sensor
|
||||
@ -2239,6 +2274,11 @@ SmartBase E0260 {"NAME":"SmartBaseE0260","GPIO":[0,0,0,0,320,0,0,0,
|
||||
DIY MORE ESP32 DHT11 {"NAME":"DIYMORESOILDHT11","GPIO":[1,1,1,1,1,1,1,1,1,1,1,1,544,1,1,1,0,1,1184,1,0,1,1,1,0,0,0,0,4864,288,4865,1,1,0,0,1],"FLAG":0,"BASE":1}
|
||||
```
|
||||
|
||||
## Soldering Iron
|
||||
```
|
||||
T13 100W PD3.0 Portable {"NAME":"PTS200","GPIO":[0,0,33,0,34,0,0,0,6210,0,32,512,0,0,0,0,0,640,608,0,0,224,4704,0,0,0,0,0,0,4737,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
```
|
||||
|
||||
## Switch
|
||||
```
|
||||
3 Way Smart Light {"NAME":"KS-602F","GPIO":[1,1,1,1,1,1,0,0,1,1,1,1,1,0],"FLAG":0,"BASE":54}
|
||||
@ -2273,6 +2313,7 @@ Bardi Smart Wallswitch 1 Gang {"NAME":"Bardi 1 Gang","GPIO":[321,320,544,0,0,32
|
||||
Bardi Smart Wallswitch 2 Gang {"NAME":"BARDI 2 Gang","GPIO":[320,0,544,33,225,0,0,0,288,224,321,0,32,0],"FLAG":0,"BASE":18}
|
||||
Bardi Smart Wallswitch 3 Gang {"NAME":"BARDI 3 Gang","GPIO":[320,321,544,34,226,33,0,0,288,224,322,225,32,0],"FLAG":0,"BASE":18}
|
||||
BAZZ SWTCHWFW1 {"NAME":"BAZZ KS-602S","GPIO":[32,0,0,0,0,0,224,288,256,320,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Bingoelec 1 Gang Touch {"NAME":"Bingoelec W601","GPIO":[0,544,0,0,0,32,0,0,224,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
BlitzWolf BW-SS3 1 Gang {"NAME":"BW-SS3-1G-EU","GPIO":[288,0,0,32,0,0,0,0,0,224,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
BlitzWolf BW-SS3 2 Gang {"NAME":"BW-SS3-2G-EU","GPIO":[544,1,1,1,225,33,1,1,32,224,1,1,1,1],"FLAG":0,"BASE":18}
|
||||
BlitzWolf BW-SS3 3 Gang {"NAME":"BlitzWolf SS3","GPIO":[576,0,0,161,225,162,0,0,160,224,0,226,0,0],"FLAG":0,"BASE":18}
|
||||
@ -2469,6 +2510,7 @@ Smatrul 2 Gang RF No Neutral {"NAME":"SMATRUL 2 GANG","GPIO":[0,544,0,32,33,0,0
|
||||
Smatrul 5A RF433MHz 1 Gang Touch {"NAME":"TMC01-EU","GPIO":[0,320,0,0,0,160,0,0,224,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Smatrul 5A RF433MHz 4 Gang Touch {"NAME":"TMW4-01(EU)","GPIO":[0,0,0,33,35,32,0,0,34,224,225,226,227,0],"FLAG":0,"BASE":18}
|
||||
Smatrul 5A RF433MHz 4 Gang Touch {"NAME":"Smatrul RF433MHz 3 Gang Touch Switch (TMW4-01(EU))","GPIO":[0,0,0,160,162,161,0,0,225,224,226,0,0,0],"FLAG":0,"BASE":18}
|
||||
Smatrul Infrared Sensor {"NAME":"WHS-2","GPIO":[0,0,0,160,288,0,0,0,0,224,0,0,0,0],"FLAG":0,"BASE":18,"CMND":"SwitchMode1 4 | SO13 1"}
|
||||
Sonoff IW101 {"NAME":"Sonoff IW101","GPIO":[32,3072,0,3104,0,0,0,0,224,544,0,0,0,0],"FLAG":0,"BASE":41}
|
||||
Sonoff SwitchMan M5-1C 1 Gang {"NAME":"Sonoff SwitchMan M5-1C-86","GPIO":[32,0,0,0,288,576,0,0,0,0,0,0,0,0,416,0,0,0,0,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
Sonoff SwitchMan M5-2C 2 Gang {"NAME":"Sonoff SwitchMan 2C","GPIO":[0,0,0,0,32,576,0,0,0,0,0,33,0,0,416,225,0,0,0,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
@ -2523,6 +2565,7 @@ TreatLife {"NAME":"TL SS01S Swtch","GPIO":[0,0,0,0,288,576,0,
|
||||
Treatlife 3-Way {"NAME":"Treatlife SS01 3-Way","GPIO":[0,0,0,0,224,576,0,0,225,33,160,0,0,0],"FLAG":0,"BASE":18}
|
||||
TreatLife 3-Way {"NAME":"Treatlife 3-Way","GPIO":[0,0,0,0,224,576,0,0,225,33,160,0,0,0],"FLAG":0,"BASE":18}
|
||||
TreatLife Single Pole ON/OFF {"NAME":"Treatlife SS02","GPIO":[0,0,0,0,288,576,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Tuya 20A {"NAME":"DS-161","GPIO":[544,0,0,32,0,0,0,0,0,224,288,0,0,0],"FLAG":0,"BASE":1}
|
||||
Tuya 3 Gang {"NAME":"KING-Tuya-key","GPIO":[0,0,0,0,226,225,0,0,224,32,34,0,33,0],"FLAG":0,"BASE":18}
|
||||
TY-US-L1-W {"NAME":"TY-US-L1-W","GPIO":[0,0,0,0,0,32,0,0,0,224,0,0,576,0],"FLAG":0,"BASE":18}
|
||||
TY-US-L3-W {"NAME":"TY-US-L3-W","GPIO":[0,0,0,0,224,33,0,0,34,226,32,225,576,1],"FLAG":0,"BASE":18}
|
||||
@ -2605,6 +2648,7 @@ Moes {"NAME":"Moes MS-104B","GPIO":[0,0,32,0,480,0,0,0,1
|
||||
Moes 10A {"NAME":"Moes MS-101","GPIO":[0,0,0,0,0,320,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Moes Mini 3 Gang 1/2 Way {"NAME":"Moes MS-104C","GPIO":[0,0,0,34,32,33,0,0,224,225,226,0,0,0],"FLAG":0,"BASE":18}
|
||||
Nedis 10A {"NAME":"Nedis WIFIPS10WT","GPIO":[0,0,0,0,224,0,0,0,32,321,0,288,0,0],"FLAG":0,"BASE":18}
|
||||
Nous 1/2 Channel {"NAME":"NOUS L13T Smart Switch Module","GPIO":[1,161,1,160,225,224,1,1,544,1,32,1,1,1],"FLAG":0,"BASE":18}
|
||||
Nova Digital Basic 1 MS101 {"NAME":"NovaDigBasic1","GPIO":[0,1,0,1,320,0,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
PPA Contatto Wi-Fi {"NAME":"PPA Contatto","GPIO":[0,0,32,0,224,162,0,0,288,225,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
PS-1604 16A {"NAME":"PS-1604 16A","GPIO":[32,1,1,1,1,0,0,0,224,320,1,0,0,0],"FLAG":0,"BASE":1}
|
||||
@ -2625,6 +2669,7 @@ Shelly Plus i4 {"NAME":"Shelly Plus i4","GPIO":[0,0,0,0,0,0,0,0,19
|
||||
Sinilink USB {"NAME":"XY-WFUSB","GPIO":[1,1,0,1,32,224,0,0,0,0,320,0,544,0],"FLAG":0,"BASE":18}
|
||||
Smarsecur Smart Switch {"NAME":"ESP-02S","GPIO":[0,0,0,32,0,0,0,0,0,0,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Smart Home SS-8839-01 {"NAME":"SS-8839-01","GPIO":[0,1,0,1,224,0,0,0,32,321,0,320,0,0],"FLAG":0,"BASE":18}
|
||||
Smart Switch {"NAME":"FL-S124-V1.0","GPIO":[1,1,1,1,32,224,1,1,1,320,1,1,1,1],"FLAG":0,"BASE":18}
|
||||
Sonoff 4CH (R2) {"NAME":"Sonoff 4CH","GPIO":[32,1,1,1,226,225,33,34,224,320,35,227,0,0],"FLAG":0,"BASE":7}
|
||||
Sonoff 4CH Pro (R2) {"NAME":"Sonoff 4CH Pro","GPIO":[32,1,1,1,226,225,33,34,224,320,35,227,0,0],"FLAG":0,"BASE":23}
|
||||
Sonoff 4CHPROR3 {"NAME":"Sonoff 4CHPROR3","GPIO":[32,1,1,1,226,225,33,34,224,320,35,227,0,0],"FLAG":0,"BASE":23}
|
||||
@ -2713,6 +2758,8 @@ Deta Double Power Point {"NAME":"DETA 2G GPO","GPIO":[0,0,0,0,544,0,0,0,65,
|
||||
DETA Outdoor Double Powerpoint {"NAME":"DETA 6294HA","GPIO":[0,0,0,3104,32,288,0,0,33,224,225,0,0,0],"FLAG":0,"BASE":18}
|
||||
Deta Single Power Point {"NAME":"DETA 1G GPO","GPIO":[0,0,0,3104,64,576,0,0,0,224,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Ener-J 13A Twin Wall Sockets with USB {"NAME":"Ener-J 2-Gang ","GPIO":[32,0,0,0,0,224,33,0,225,320,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
GHome USB Charger {"NAME":"GHome Smart WO2-1","GPIO":[320,0,0,0,0,257,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Globe Double Receptacle 15A {"NAME":"Globe 50024","GPIO":[0,0,0,0,320,32,0,0,224,0,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Gosund {"NAME":"Gosund WO1","GPIO":[320,0,576,0,2656,2720,0,0,2624,321,225,224,0,4704],"FLAG":0,"BASE":18}
|
||||
Gosund USB Charger {"NAME":"Gosund WO2","GPIO":[320,0,576,0,0,257,0,0,0,32,0,224,0,0],"FLAG":0,"BASE":18}
|
||||
Hevolta Glasense {"NAME":"Hevolta Socket","GPIO":[0,0,0,0,288,289,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
@ -2743,6 +2790,7 @@ TopGreener Dual USB {"NAME":"TGWF215U2A","GPIO":[0,320,0,32,2720,2656,0
|
||||
Vigica VGSPK00815 {"NAME":"VIGICA outlet","GPIO":[32,1,1,1,1,225,33,1,224,1,1,1,1,4704],"FLAG":0,"BASE":18}
|
||||
Virage Labs ViragePlug {"NAME":"ViragePlug","GPIO":[544,0,0,32,320,33,0,0,225,224,320,226,0,0],"FLAG":0,"BASE":18}
|
||||
Woox Dual {"NAME":"Woox R4053","GPIO":[33,0,0,0,0,224,32,0,225,320,0,0,0,0],"FLAG":0,"BASE":18}
|
||||
Xenon {"NAME":"Xenon SM-PM801-K1","GPIO":[0,320,0,32,2720,2656,0,0,2624,288,224,0,0,0],"FLAG":0,"BASE":18}
|
||||
Xenon 2AC 1USB {"NAME":"Xenon SM-PW801-U1","GPIO":[0,0,0,0,288,32,0,0,224,0,225,0,226,0],"FLAG":0,"BASE":18}
|
||||
```
|
||||
|
||||
@ -2761,4 +2809,5 @@ Sonoff ZBBridge {"NAME":"Sonoff ZbBridge","GPIO":[320,3552,0,3584,5
|
||||
Sonoff ZBBridge Pro {"NAME":"Sonoff Zigbee Pro","GPIO":[0,0,576,0,480,0,0,0,0,1,1,5792,0,0,0,3552,0,320,5793,3584,0,640,608,32,0,0,0,0,0,1,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
Tube's CC2652P2 Ethernet {"NAME":"Tube ZB CC2652","GPIO":[0,0,0,3840,0,3584,0,0,0,0,0,0,5536,3552,5600,0,0,0,0,5568,0,0,0,0,0,0,0,0,3840,5792,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
Tube's EFR32 Ethernet {"NAME":"Tube ZB EFR32","GPIO":[0,0,0,3840,0,3552,1,0,0,0,0,0,5536,3584,5600,0,0,0,0,5568,0,0,0,0,0,0,0,0,5793,5792,0,0,0,0,0,0],"FLAG":0,"BASE":1}
|
||||
TubesZB CC2652P2 Zigbee to PoE Coordinator 2022 {"NAME":"TubesZB CC2652 PoE Coordinator 2022","GPIO":[1,1,8864,1,5793,3584,0,0,5536,5792,8832,8800,3552,0,5600,1,1,1,1,5568,1,1,1,1,0,0,0,0,1,1,32,1,1,1,1,1],"FLAG":0,"BASE":1}
|
||||
```
|
||||
|
@ -4,7 +4,7 @@
|
||||
"ldscript": "esp32_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DHAS_PSRAM_FIX -mfix-esp32-psram-cache-issue -mfix-esp32-psram-cache-strategy=memw -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M",
|
||||
"extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DHAS_PSRAM_FIX -mfix-esp32-psram-cache-issue -mfix-esp32-psram-cache-strategy=memw -DESP32_4M",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"ldscript": "esp32_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M",
|
||||
"extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DESP32_4M",
|
||||
"f_cpu": "160000000L",
|
||||
"f_flash": "40000000L",
|
||||
"flash_mode": "dio",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"ldscript": "esp32_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DCORE32SOLO1",
|
||||
"extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DESP32_4M -DCORE32SOLO1",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
"flash_mode": "dio",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"ldscript": "esp32c3_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32C3",
|
||||
"extra_flags": "-DESP32_4M -DESP32C3",
|
||||
"f_cpu": "160000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"ldscript": "esp32c3_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32C3 -DUSE_USB_CDC_CONSOLE",
|
||||
"extra_flags": "-DARDUINO_USB_MODE=1 -DESP32_4M -DESP32C3 -DUSE_USB_CDC_CONSOLE",
|
||||
"f_cpu": "160000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"ldscript": "esp32s2_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=0 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DESP32_4M -DESP32S2",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DESP32_4M -DESP32S2",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"ldscript": "esp32s2_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=0 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32S2",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32S2",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=0 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DESP32_4M -DESP32S3",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DESP32_4M -DESP32S3",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"memory_type": "qio_qspi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=0 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DESP32_4M -DESP32S3",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DESP32_4M -DESP32S3",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32S3",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32S3",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"memory_type": "qio_qspi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32S3",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32S3",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
|
@ -228,7 +228,7 @@ bool TasmotaSerial::begin(uint32_t speed, uint32_t config) {
|
||||
#ifdef ESP32
|
||||
if (TSerial == nullptr) { // Allow for dynamic change in baudrate or config
|
||||
if (freeUart()) { // We prefer UART1 and UART2 and keep UART0 for debugging
|
||||
#ifdef ARDUINO_USB_CDC_ON_BOOT
|
||||
#if ARDUINO_USB_MODE
|
||||
TSerial = new HardwareSerial(m_uart);
|
||||
#else
|
||||
if (0 == m_uart) {
|
||||
@ -239,7 +239,7 @@ bool TasmotaSerial::begin(uint32_t speed, uint32_t config) {
|
||||
} else {
|
||||
TSerial = new HardwareSerial(m_uart);
|
||||
}
|
||||
#endif // ARDUINO_USB_CDC_ON_BOOT
|
||||
#endif // ARDUINO_USB_MODE
|
||||
if (serial_buffer_size > 256) { // RX Buffer can't be resized when Serial is already running (HardwareSerial.cpp)
|
||||
TSerial->setRxBufferSize(serial_buffer_size);
|
||||
}
|
||||
@ -460,6 +460,7 @@ size_t TasmotaSerial::write(uint8_t b) {
|
||||
size = 1;
|
||||
}
|
||||
if (m_tx_enable_pin > -1) {
|
||||
flush(); // Must wait for all data sent
|
||||
digitalWrite(m_tx_enable_pin, LOW);
|
||||
}
|
||||
return size;
|
||||
|
@ -411,6 +411,10 @@ void IRrecv::pause(void) {
|
||||
params.rcvstate = kStopState;
|
||||
params.rawlen = 0;
|
||||
params.overflow = false;
|
||||
#if defined(ESP8266)
|
||||
os_timer_disarm(&timer);
|
||||
detachInterrupt(params.recvpin);
|
||||
#endif
|
||||
#if defined(ESP32)
|
||||
gpio_intr_disable((gpio_num_t)params.recvpin);
|
||||
#endif // ESP32
|
||||
@ -424,6 +428,10 @@ void IRrecv::resume(void) {
|
||||
params.rcvstate = kIdleState;
|
||||
params.rawlen = 0;
|
||||
params.overflow = false;
|
||||
#if defined(ESP8266)
|
||||
os_timer_setfn(&timer, reinterpret_cast<os_timer_func_t *>(read_timeout),NULL);
|
||||
attachInterrupt(params.recvpin, gpio_intr, CHANGE);
|
||||
#endif
|
||||
#if defined(ESP32)
|
||||
timerAlarmDisable(timer);
|
||||
gpio_intr_enable((gpio_num_t)params.recvpin);
|
||||
|
@ -171,6 +171,7 @@ uint8_t TasmotaModbus::Send(uint8_t device_address, uint8_t function_code, uint1
|
||||
write(frame, framepointer);
|
||||
#ifdef TASMOTA_MODBUS_TX_ENABLE
|
||||
if (mb_tx_enable_pin > -1) {
|
||||
flush(); // Must wait for all data sent
|
||||
digitalWrite(mb_tx_enable_pin, LOW);
|
||||
}
|
||||
#endif // TASMOTA_MODBUS_TX_ENABLE
|
||||
@ -203,7 +204,7 @@ uint8_t TasmotaModbus::ReceiveBuffer(uint8_t *buffer, uint8_t register_count, ui
|
||||
}
|
||||
}
|
||||
|
||||
timeout = millis() + 10;
|
||||
timeout = millis() + 20;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -26,13 +26,15 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "epd2in9.h"
|
||||
#include "tasmota_options.h"
|
||||
|
||||
|
||||
#ifndef EPD_29_V1
|
||||
#define EPD_29_V2
|
||||
#endif
|
||||
|
||||
|
||||
//#define BUSY_PIN 16
|
||||
|
||||
|
||||
Epd::Epd(int16_t width, int16_t height) :
|
||||
Paint(width,height) {
|
||||
}
|
||||
@ -84,7 +86,6 @@ void Epd::DisplayInit(int8_t p,int8_t size,int8_t rot,int8_t font) {
|
||||
setTextColor(WHITE,BLACK);
|
||||
setCursor(0,0);
|
||||
fillScreen(BLACK);
|
||||
|
||||
disp_bpp = 1;
|
||||
}
|
||||
|
||||
@ -99,7 +100,6 @@ void Epd::Begin(int16_t cs,int16_t mosi,int16_t sclk, int16_t rst, int16_t busy)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void Epd::Init(int8_t p) {
|
||||
if (p == DISPLAY_INIT_PARTIAL) {
|
||||
Init(lut_partial_update);
|
||||
@ -116,20 +116,14 @@ void Epd::Init(int8_t p) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int Epd::Init(const unsigned char* lut) {
|
||||
/* this calls the peripheral hardware interface, see epdif */
|
||||
/*if (IfInit() != 0) {
|
||||
return -1;
|
||||
}*/
|
||||
/*
|
||||
cs_pin=pin[GPIO_SSPI_CS];
|
||||
mosi_pin=pin[GPIO_SSPI_MOSI];
|
||||
sclk_pin=pin[GPIO_SSPI_SCLK];
|
||||
*/
|
||||
|
||||
if (framebuffer) {
|
||||
// free(framebuffer);
|
||||
if (iniz) {
|
||||
#ifndef EPD_29_V2
|
||||
this->lut = lut;
|
||||
SetLut(this->lut);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
framebuffer = (uint8_t*)malloc(EPD_WIDTH * EPD_HEIGHT / 8);
|
||||
if (!framebuffer) return -1;
|
||||
@ -204,6 +198,7 @@ int Epd::Init(const unsigned char* lut) {
|
||||
SetLut(this->lut);
|
||||
#endif
|
||||
/* EPD hardware init end */
|
||||
iniz = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -250,7 +245,9 @@ void Epd::Reset(void) {
|
||||
digitalWrite(rst_pin, HIGH);
|
||||
delay(200);
|
||||
} else {
|
||||
#ifdef EPD_29_V2
|
||||
SendCommand(0x12);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,6 +109,7 @@ private:
|
||||
unsigned int mosi_pin;
|
||||
unsigned int sclk_pin;
|
||||
unsigned char mode;
|
||||
uint8_t iniz = 0;
|
||||
void delay_busy(uint32_t wait);
|
||||
void SetLut(const unsigned char* lut);
|
||||
void SetMemoryArea(int x_start, int y_start, int x_end, int y_end);
|
||||
|
@ -1,14 +1,12 @@
|
||||
Teleinfo (Aka TIC) Universal Library
|
||||
====================================
|
||||
# Teleinfo (Aka TIC) Universal Library
|
||||
|
||||
This is a generic Teleinfo French Meter Measure Library, it can be used on Arduino like device and also such as Spark Core, Particle, ESP8266, Raspberry PI or anywhere you can do Cpp code ...
|
||||
|
||||
You can see Teleinformation official french datasheet [there][1]
|
||||
You can see Teleinformation official french datasheet [there][1] and this one is for [Linky][3].
|
||||
|
||||
Since this is really dedicated to French energy measuring system, I will continue in French
|
||||
Since this is really dedicated to French energy measuring system, I will continue in French.
|
||||
|
||||
Installation
|
||||
============
|
||||
# Installation
|
||||
|
||||
Copier le contenu de ce dossier (download zip) dans le dossier libraries de votre environnement Arduino Vous devriez avoir maintenant quelque chose comme `your_sketchbook_folder/libraries/LibTeleinfo` et ce dossier doit contentir les fichiers .cpp et .h ainsi que le sous dossier `examples`.
|
||||
<br/>
|
||||
@ -17,14 +15,12 @@ Pour trouver votre dossier de sketchbook, dans l'environnement IDE, allez dans F
|
||||
allez voir ce [tutorial][2] sur les librairies Arduino si beoin.
|
||||
<br/>
|
||||
|
||||
Documentation
|
||||
=============
|
||||
# Documentation
|
||||
|
||||
J'ai écrit un article [dédié][10] sur cette librairie, vous pouvez aussi voir les [catégories][6] associées à la téléinfo sur mon [blog][7].
|
||||
Pour les commentaires et le support vous pouvez allez sur le [forum][8] dédié ou dans la [communauté][9]
|
||||
|
||||
Sketch d'exemples
|
||||
=================
|
||||
# Sketch d'exemples
|
||||
|
||||
- [Arduino_Softserial_Etiquette][3] Affiche des informations de téléinformation reçue étiquette par étiquette
|
||||
- [Arduino_Softserial_Blink][11] Affiche des informations de téléinformation reçue trame par trame avec clignotement LED court/long si les données ont été modifiés
|
||||
@ -32,23 +28,32 @@ Sketch d'exemples
|
||||
- [Raspberry_JSON][12] Retourne les informations de téléinformation au format JSON sur stdout.
|
||||
- [Wifinfo][5] ESP8266, ESP32 Wifi Teleinformation, Web + Rest + bonus, version en cours de développement, à venir mais un article [dédié][13] est déjà présent sur mon blog
|
||||
- [ESP32][14] ESP32 Basic test pour WifInfo32 nouveau nom Denky :-)
|
||||
- [ESP32_Passthru][14] ESP32 PassThru Basic test pour Denky D4, affiche les données et les stats de la téléinfo dans la console série
|
||||
- [ESP8266_DataChanged][15] ESP8266 Surveille et affiche les données changées entre 2 trames, clignote la LED RGB en fonction
|
||||
- [Teleinfo_DenkyD4][16] ESP32 Denky D4 Basic test et stats pour le nouveau Denky D4 basé sur l'ESP32-Pico-V3-02
|
||||
- [Teleinfo_Stats][18] ESP32 Programme de test et statistiques pour la qualité de réception
|
||||
|
||||
# Pourquoi
|
||||
|
||||
Pourquoi
|
||||
========
|
||||
- J'utilise la téléinfo dans plusieurs de mes programmes et j'en avais marre de devoir faire des copier/coller de code constament, j'ai donc décidé de faire une librairie commune que j'utilise sans me poser de question
|
||||
|
||||
License
|
||||
=======
|
||||
# License
|
||||
|
||||
Cette oeuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.
|
||||
|
||||
Si vous êtes une entreprise et que vous souhaitez participer car vous utilisez cette librairie dans du hardware (box, automate, ...), vous pouvez toujours m'envoyer un exemplaire de votre fabrication, c'est toujours sympa de voir ce qui est fait avec ce code ;-)
|
||||
|
||||
Divers
|
||||
======
|
||||
# Addon
|
||||
|
||||
Ajout des compteurs d'erreurs, et du traitement du caracteres EOT d'interruption de trames
|
||||
|
||||
# Divers
|
||||
|
||||
Vous pouvez aller voir les nouveautés et autres projets sur [blog][7]
|
||||
|
||||
[1]: http://www.erdf.fr/sites/default/files/ERDF-NOI-CPT_02E.pdf
|
||||
[1]: https://www.enedis.fr/sites/default/files/Enedis-NOI-CPT_02E.pdf
|
||||
[2]: http://learn.adafruit.com/arduino-tips-tricks-and-techniques/arduino-libraries
|
||||
[3]: https://www.enedis.fr/sites/default/files/Enedis-NOI-CPT_54E.pdf
|
||||
[6]: https://hallard.me/category/tinfo/
|
||||
[7]: https://hallard.me
|
||||
[8]: https://community.hallard.me/category/7
|
||||
@ -59,7 +64,12 @@ Vous pouvez aller voir les nouveautés et autres projets sur [blog][7]
|
||||
[4]: https://github.com/hallard/LibTeleinfo/blob/master/examples/Arduino_Softserial_JSON/Arduino_Softserial_JSON.ino
|
||||
[5]: https://github.com/hallard/LibTeleinfo/tree/master/examples/Wifinfo/Wifinfo.ino
|
||||
[11]: https://github.com/hallard/LibTeleinfo/blob/master/examples/Arduino_Softserial/Arduino_Softserial_Blink.ino
|
||||
[12]: https://github.com/hallard/LibTeleinfo/blob/master/examples/Raspberry_JSON/Raspberry_JSON.ino
|
||||
[12]: https://github.com/hallard/LibTeleinfo/blob/master/examples/Raspberry_JSON/raspjson.cpp
|
||||
[13]: https://hallard.me/wifiinfo/
|
||||
[14]: https://github.com/hallard/LibTeleinfo/blob/master/examples/ESP32/ESP32.ino
|
||||
[15]: https://github.com/hallard/LibTeleinfo/blob/master/examples/ESP8266_DataChanged/ESP8266_DataChanged.ino
|
||||
[16]: https://github.com/hallard/LibTeleinfo/blob/master/examples/Teleinfo_DenkyD4/Teleinfo_DenkyD4.ino
|
||||
[17]: https://github.com/hallard/LibTeleinfo/blob/master/examples/ESP32_Passthru/ESP32_Passthru.ino
|
||||
[18]: https://github.com/hallard/LibTeleinfo/blob/master/examples/Teleinfo_Stats/Teleinfo_Stats.ino
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "LibTeleinfo",
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.5",
|
||||
"keywords": "teleinfo, french, meter, power, erdf, linky, tic",
|
||||
"description": "Decoder for Teleinfo (aka TIC) from French smart power meters",
|
||||
"repository":
|
||||
|
@ -1,5 +1,5 @@
|
||||
name=LibTeleinfo
|
||||
version=1.1.3
|
||||
version=1.1.5
|
||||
author=Charles-Henri Hallard <hallard.me>
|
||||
maintainer=Charles-Henri Hallard <community.hallard.me>
|
||||
sentence=Decoder for Teleinfo (aka TIC) from French smart power meters
|
||||
|
@ -50,6 +50,8 @@ TInfo::TInfo()
|
||||
_fn_data = NULL;
|
||||
_fn_new_frame = NULL;
|
||||
_fn_updated_frame = NULL;
|
||||
|
||||
clearStats();
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
@ -78,6 +80,22 @@ void TInfo::init(_Mode_e mode)
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
Function: clearStats
|
||||
Purpose : clear stats counters
|
||||
Input : -
|
||||
Output : -
|
||||
Comments: -
|
||||
====================================================================== */
|
||||
void TInfo::clearStats()
|
||||
{
|
||||
// reset Frame counters stats
|
||||
_checksumerror =0;
|
||||
_framesizeerror=0;
|
||||
_frameformaterror=0;
|
||||
_frameinterrupted=0;
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
Function: attachADPS
|
||||
Purpose : attach a callback when we detected a ADPS on any phase
|
||||
@ -226,8 +244,11 @@ ValueList * TInfo::valueAdd(char * name, char * value, uint8_t checksum, uint8_t
|
||||
ValueList *parNode = NULL ;
|
||||
uint32_t ts = 0;
|
||||
|
||||
// Time stamped field?
|
||||
if (horodate && *horodate) {
|
||||
ts = horodate2Timestamp(horodate);
|
||||
// We don't check horodate (not used) on storage so re calculate checksum without this one
|
||||
checksum = calcChecksum(name,value) ;
|
||||
}
|
||||
|
||||
// Loop thru the node
|
||||
@ -257,7 +278,6 @@ ValueList * TInfo::valueAdd(char * name, char * value, uint8_t checksum, uint8_t
|
||||
// Copy it
|
||||
strlcpy(me->value, value , lgvalue + 1 );
|
||||
me->checksum = checksum ;
|
||||
|
||||
// That's all
|
||||
return (me);
|
||||
} else {
|
||||
@ -279,19 +299,12 @@ ValueList * TInfo::valueAdd(char * name, char * value, uint8_t checksum, uint8_t
|
||||
// Our linked list structure sizeof(ValueList)
|
||||
// + Name + '\0'
|
||||
// + Value + '\0'
|
||||
size_t size ;
|
||||
#if defined (ESP8266) || defined (ESP32)
|
||||
lgname = ESP_allocAlign(lgname+1); // Align name buffer
|
||||
lgvalue = ESP_allocAlign(lgvalue+1); // Align value buffer
|
||||
// Align the whole structure
|
||||
size = ESP_allocAlign( sizeof(ValueList) + lgname + lgvalue ) ;
|
||||
#else
|
||||
size = sizeof(ValueList) + lgname + 1 + lgvalue + 1 ;
|
||||
#endif
|
||||
size_t size = sizeof(ValueList) + lgname + 1 + lgvalue + 1 ;
|
||||
|
||||
// Create new node with size to store strings
|
||||
if ((newNode = (ValueList *) malloc(size) ) == NULL)
|
||||
if ((newNode = (ValueList *) malloc(size) ) == NULL) {
|
||||
return ( (ValueList *) NULL );
|
||||
}
|
||||
|
||||
// get our buffer Safe
|
||||
memset(newNode, 0, size);
|
||||
@ -456,10 +469,13 @@ char * TInfo::valueGet(char * name, char * value)
|
||||
if (lgname==strlen(me->name) && strcmp(me->name, name)==0) {
|
||||
// this one has a value ?
|
||||
if (me->value) {
|
||||
// copy to dest buffer
|
||||
uint8_t lgvalue = strlen(me->value);
|
||||
strlcpy(value, me->value , lgvalue + 1 );
|
||||
return ( value );
|
||||
// Check back checksum
|
||||
if (me->checksum == calcChecksum(me->name, me->value)) {
|
||||
// copy to dest buffer
|
||||
uint8_t lgvalue = strlen(me->value);
|
||||
strlcpy(value, me->value , lgvalue + 1 );
|
||||
return ( value );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -494,10 +510,13 @@ char * TInfo::valueGet_P(const char * name, char * value)
|
||||
if (lgname==strlen(me->name) && strcmp_P(me->name, name)==0) {
|
||||
// this one has a value ?
|
||||
if (me->value) {
|
||||
// copy to dest buffer
|
||||
uint8_t lgvalue = strlen(me->value);
|
||||
strlcpy(value, me->value , lgvalue + 1 );
|
||||
return ( value );
|
||||
// Check back checksum
|
||||
if (me->checksum == calcChecksum(me->name, me->value)) {
|
||||
// copy to dest buffer
|
||||
uint8_t lgvalue = strlen(me->value);
|
||||
strlcpy(value, me->value , lgvalue + 1 );
|
||||
return ( value );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -529,6 +548,7 @@ uint8_t TInfo::valuesDump(void)
|
||||
// Get our linked list
|
||||
ValueList * me = &_valueslist;
|
||||
uint8_t index = 0;
|
||||
uint8_t checksum=0;
|
||||
|
||||
// Got one ?
|
||||
if (me) {
|
||||
@ -541,7 +561,7 @@ uint8_t TInfo::valuesDump(void)
|
||||
TI_Debug(index) ;
|
||||
TI_Debug(F(") ")) ;
|
||||
|
||||
if (me->name) {
|
||||
if (me->name ) {
|
||||
TI_Debug(me->name) ;
|
||||
} else {
|
||||
TI_Debug(F("NULL")) ;
|
||||
@ -555,9 +575,17 @@ uint8_t TInfo::valuesDump(void)
|
||||
TI_Debug(F("NULL")) ;
|
||||
}
|
||||
|
||||
if (me->name && me->value && *me->name && *me->value) {
|
||||
checksum = calcChecksum(me->name, me->value);
|
||||
}
|
||||
|
||||
TI_Debug(F(" '")) ;
|
||||
TI_Debug(me->checksum) ;
|
||||
TI_Debug(F("' "));
|
||||
if (me->checksum != checksum ) {
|
||||
TI_Debug(F("'!Err "));
|
||||
} else {
|
||||
TI_Debug(F("' "));
|
||||
}
|
||||
|
||||
// Flags management
|
||||
if ( me->flags) {
|
||||
@ -663,22 +691,51 @@ LF etiquette HT donnee HT Chk CR
|
||||
====================================================================== */
|
||||
unsigned char TInfo::calcChecksum(char *etiquette, char *valeur, char * horodate)
|
||||
{
|
||||
char c;
|
||||
uint8_t sum = (_mode == TINFO_MODE_HISTORIQUE) ? _separator : (2 * _separator); // Somme des codes ASCII du message + 2 separateurs
|
||||
|
||||
// avoid dead loop, always check all is fine
|
||||
if (etiquette && valeur) {
|
||||
// this will not hurt and may save our life ;-)
|
||||
if (strlen(etiquette) && strlen(valeur)) {
|
||||
while (*etiquette)
|
||||
sum += *etiquette++ ;
|
||||
while (*etiquette) {
|
||||
c =*etiquette++;
|
||||
// Add another validity check since checksum may not be sufficient
|
||||
if ( (c>='A' && c<='Z') || (c>='0' && c<='9') || c=='-' || c=='+') {
|
||||
sum += c ;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
while(*valeur)
|
||||
sum += *valeur++ ;
|
||||
while(*valeur) {
|
||||
c = *valeur++ ;
|
||||
// Add another validity check since checksum may not be sufficient (space authorized in Standard mode)
|
||||
if ( (c>='A' && c<='Z') || (c>='0' && c<='9') || c==' ' || c=='.' || c=='-' || c=='+' || c=='/') {
|
||||
sum += c ;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (horodate) {
|
||||
sum += _separator;
|
||||
while (*horodate)
|
||||
sum += *horodate++ ;
|
||||
c = *horodate++;
|
||||
// Add another validity check starting season [E]té (Summer) or [H]iver (Winter)
|
||||
if ( c=='E' || c=='H' || c=='e' || c=='h') {
|
||||
sum += c ;
|
||||
while (*horodate) {
|
||||
c = *horodate++ ;
|
||||
// Add another validity check for horodate digits
|
||||
if ( c>='0' && c<='9') {
|
||||
sum += c ;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ( (sum & 0x3f) + ' ' ) ;
|
||||
@ -798,6 +855,7 @@ ValueList * TInfo::checkLine(char * pline)
|
||||
// 2 Label + Space + 1 etiquette + space + checksum + \r
|
||||
if ( len < 7 || len >= TINFO_BUFSIZE) {
|
||||
//AddLog(3, PSTR("LibTeleinfo: Error len < 7 || len >= TINFO_BUFSIZE"));
|
||||
_framesizeerror++;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -876,7 +934,8 @@ ValueList * TInfo::checkLine(char * pline)
|
||||
// Always check to avoid bad behavior
|
||||
if(strlen(ptok) && strlen(pvalue)) {
|
||||
// Is checksum is OK
|
||||
char calc_checksum = calcChecksum(ptok,pvalue,pts);
|
||||
char calc_checksum = calcChecksum(ptok,pvalue,pts);
|
||||
|
||||
if ( calc_checksum == checksum) {
|
||||
// In case we need to do things on specific labels
|
||||
customLabel(ptok, pvalue, &flags);
|
||||
@ -904,9 +963,18 @@ ValueList * TInfo::checkLine(char * pline)
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLog(1, PSTR("LibTeleinfo::checkLine Err checksum 0x%02X != 0x%02X"), calc_checksum, checksum);
|
||||
_checksumerror++;
|
||||
AddLog(1, PSTR("LibTeleinfo::checkLine Err checksum 0x%02X != 0x%02X (total errors=%d)"), calc_checksum, checksum, _checksumerror);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specific field not formated has others, don't set as an error
|
||||
if ( strcmp(ptok, "DATE") ) {
|
||||
_frameformaterror++;
|
||||
AddLog(1, PSTR("LibTeleinfo::checkLine frame format error total=%d"), _frameformaterror);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Next char
|
||||
@ -948,7 +1016,17 @@ _State_e TInfo::process(char c)
|
||||
_state = TINFO_WAIT_ETX;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
// frame interruption
|
||||
case TINFO_EOT:
|
||||
//AddLog(3, PSTR("LibTeleinfo: case TINFO_EOT >>>>>>>>>>>>>>>>>>"));
|
||||
// discard incomplete frame
|
||||
// Clear buffer, begin to store in it
|
||||
clearBuffer();
|
||||
_frameinterrupted++;
|
||||
_state = TINFO_WAIT_STX;
|
||||
break;
|
||||
|
||||
// End of transmission ?
|
||||
case TINFO_ETX:
|
||||
//AddLog(3, PSTR("LibTeleinfo: case TINFO_ETX >>>>>>>>>>>>>>>>>>"));
|
||||
@ -1002,8 +1080,12 @@ _State_e TInfo::process(char c)
|
||||
// Are we ready to process ?
|
||||
if (_state == TINFO_READY) {
|
||||
// Store data recceived (we'll need it)
|
||||
if ( _recv_idx < TINFO_BUFSIZE)
|
||||
if ( _recv_idx < TINFO_BUFSIZE) {
|
||||
_recv_buff[_recv_idx++]=c;
|
||||
} else {
|
||||
// group is too big (some ETX missing)
|
||||
_framesizeerror++;
|
||||
}
|
||||
|
||||
// clear the end of buffer (paranoia inside)
|
||||
memset(&_recv_buff[_recv_idx], 0, TINFO_BUFSIZE-_recv_idx);
|
||||
|
@ -74,14 +74,6 @@ void AddLog(uint32_t loglevel, PGM_P formatP, ...);
|
||||
#define TI_Debugflush {}
|
||||
#endif
|
||||
|
||||
// For 4 bytes Aligment boundaries
|
||||
#if defined (ESP8266) || defined (ESP32)
|
||||
#define ESP_allocAlign(size) ((size + 3) & ~((size_t) 3))
|
||||
#endif
|
||||
|
||||
#pragma pack(push) // push current alignment to stack
|
||||
#pragma pack(1) // set alignment to 1 byte boundary
|
||||
|
||||
// Linked list structure containing all values received
|
||||
typedef struct _ValueList ValueList;
|
||||
struct _ValueList
|
||||
@ -94,8 +86,6 @@ struct _ValueList
|
||||
char * value; // value
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
// Library state machine
|
||||
enum _Mode_e {
|
||||
TINFO_MODE_HISTORIQUE, // Legacy mode (1200)
|
||||
@ -125,6 +115,7 @@ enum _State_e {
|
||||
// Teleinfo start and end of frame characters
|
||||
#define TINFO_STX 0x02
|
||||
#define TINFO_ETX 0x03
|
||||
#define TINFO_EOT 0x04 // frame interrupt (End Of Transmission)
|
||||
#define TINFO_HT 0x09
|
||||
#define TINFO_SGR '\n' // start of group
|
||||
#define TINFO_EGR '\r' // End of group
|
||||
@ -151,7 +142,12 @@ class TInfo
|
||||
char * valueGet_P(const char * name, char * value);
|
||||
int labelCount();
|
||||
boolean listDelete();
|
||||
void clearStats();
|
||||
unsigned char calcChecksum(char *etiquette, char *valeur, char *horodate=NULL) ;
|
||||
uint32_t getChecksumErrorCount() { return _checksumerror; };
|
||||
uint32_t getFrameSizeErrorCount() { return _framesizeerror; };
|
||||
uint32_t getFrameFormatErrorCount() { return _frameformaterror; };
|
||||
uint32_t getFrameInterruptedCount() { return _frameinterrupted; };
|
||||
|
||||
private:
|
||||
void clearBuffer();
|
||||
@ -169,6 +165,13 @@ class TInfo
|
||||
char _separator;
|
||||
uint8_t _recv_idx; // index in receive buffer
|
||||
boolean _frame_updated; // Data on the frame has been updated
|
||||
|
||||
// Frame counters stats
|
||||
uint32_t _checksumerror;
|
||||
uint32_t _framesizeerror;
|
||||
uint32_t _frameformaterror;
|
||||
uint32_t _frameinterrupted;
|
||||
|
||||
void (*_fn_ADPS)(uint8_t phase, char * label);
|
||||
void (*_fn_data)(ValueList * valueslist, uint8_t state);
|
||||
void (*_fn_new_frame)(ValueList * valueslist);
|
||||
|
@ -51,6 +51,10 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
ptr += 3;
|
||||
headersize += 3;
|
||||
} else if(((*ptr) & 0xFF) == 0x4f) {
|
||||
// ???????? single frame did only decode with this compare
|
||||
ptr++;
|
||||
headersize++;
|
||||
} else if(((*ptr) & 0xFF) == 0x5e) {
|
||||
// ???????? single frame did only decode with this compare
|
||||
ptr++;
|
||||
headersize++;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Library to use Arduino MFRC522 module.
|
||||
*
|
||||
*
|
||||
* @authors Dr.Leong, Miguel Balboa, Søren Thing Andersen, Tom Clement, many more! See GitLog.
|
||||
*
|
||||
*
|
||||
* For more information read the README.
|
||||
*
|
||||
*
|
||||
* Please read this file for an overview and then MFRC522.cpp for comments on the specific functions.
|
||||
*/
|
||||
#ifndef MFRC522_h
|
||||
@ -92,7 +92,7 @@ public:
|
||||
DivIEnReg = 0x03 << 1, // enable and disable interrupt request control bits
|
||||
ComIrqReg = 0x04 << 1, // interrupt request bits
|
||||
DivIrqReg = 0x05 << 1, // interrupt request bits
|
||||
ErrorReg = 0x06 << 1, // error bits showing the error status of the last command executed
|
||||
ErrorReg = 0x06 << 1, // error bits showing the error status of the last command executed
|
||||
Status1Reg = 0x07 << 1, // communication status bits
|
||||
Status2Reg = 0x08 << 1, // receiver and transmitter status bits
|
||||
FIFODataReg = 0x09 << 1, // input and output of 64 byte FIFO buffer
|
||||
@ -102,10 +102,10 @@ public:
|
||||
BitFramingReg = 0x0D << 1, // adjustments for bit-oriented frames
|
||||
CollReg = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface
|
||||
// 0x0F // reserved for future use
|
||||
|
||||
|
||||
// Page 1: Command
|
||||
// 0x10 // reserved for future use
|
||||
ModeReg = 0x11 << 1, // defines general modes for transmitting and receiving
|
||||
ModeReg = 0x11 << 1, // defines general modes for transmitting and receiving
|
||||
TxModeReg = 0x12 << 1, // defines transmission data rate and framing
|
||||
RxModeReg = 0x13 << 1, // defines reception data rate and framing
|
||||
TxControlReg = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2
|
||||
@ -120,7 +120,7 @@ public:
|
||||
MfRxReg = 0x1D << 1, // controls some MIFARE communication receive parameters
|
||||
// 0x1E // reserved for future use
|
||||
SerialSpeedReg = 0x1F << 1, // selects the speed of the serial UART interface
|
||||
|
||||
|
||||
// Page 2: Configuration
|
||||
// 0x20 // reserved for future use
|
||||
CRCResultRegH = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation
|
||||
@ -129,7 +129,7 @@ public:
|
||||
ModWidthReg = 0x24 << 1, // controls the ModWidth setting?
|
||||
// 0x25 // reserved for future use
|
||||
RFCfgReg = 0x26 << 1, // configures the receiver gain
|
||||
GsNReg = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation
|
||||
GsNReg = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation
|
||||
CWGsPReg = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation
|
||||
ModGsPReg = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation
|
||||
TModeReg = 0x2A << 1, // defines settings for the internal timer
|
||||
@ -138,7 +138,7 @@ public:
|
||||
TReloadRegL = 0x2D << 1,
|
||||
TCounterValueRegH = 0x2E << 1, // shows the 16-bit timer value
|
||||
TCounterValueRegL = 0x2F << 1,
|
||||
|
||||
|
||||
// Page 3: Test Registers
|
||||
// 0x30 // reserved for future use
|
||||
TestSel1Reg = 0x31 << 1, // general test signal configuration
|
||||
@ -157,7 +157,7 @@ public:
|
||||
// 0x3E // reserved for production tests
|
||||
// 0x3F // reserved for production tests
|
||||
};
|
||||
|
||||
|
||||
// MFRC522 commands. Described in chapter 10 of the datasheet.
|
||||
enum PCD_Command : byte {
|
||||
PCD_Idle = 0x00, // no action, cancels current command execution
|
||||
@ -171,7 +171,7 @@ public:
|
||||
PCD_MFAuthent = 0x0E, // performs the MIFARE standard authentication as a reader
|
||||
PCD_SoftReset = 0x0F // resets the MFRC522
|
||||
};
|
||||
|
||||
|
||||
// MFRC522 RxGain[2:0] masks, defines the receiver's signal voltage gain factor (on the PCD).
|
||||
// Described in 9.3.3.6 / table 98 of the datasheet at http://www.nxp.com/documents/data_sheet/MFRC522.pdf
|
||||
enum PCD_RxGain : byte {
|
||||
@ -187,7 +187,7 @@ public:
|
||||
RxGain_avg = 0x04 << 4, // 100b - 33 dB, average, convenience for RxGain_33dB
|
||||
RxGain_max = 0x07 << 4 // 111b - 48 dB, maximum, convenience for RxGain_48dB
|
||||
};
|
||||
|
||||
|
||||
// Commands sent to the PICC.
|
||||
enum PICC_Command : byte {
|
||||
// The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4)
|
||||
@ -214,18 +214,18 @@ public:
|
||||
// The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight.
|
||||
PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 byte page to the PICC.
|
||||
};
|
||||
|
||||
|
||||
// MIFARE constants that does not fit anywhere else
|
||||
enum MIFARE_Misc {
|
||||
MF_ACK = 0xA, // The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK.
|
||||
MF_KEY_SIZE = 6 // A Mifare Crypto1 key is 6 bytes.
|
||||
};
|
||||
|
||||
|
||||
// PICC types we can detect. Remember to update PICC_GetTypeName() if you add more.
|
||||
// last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered
|
||||
enum PICC_Type : byte {
|
||||
PICC_TYPE_UNKNOWN ,
|
||||
PICC_TYPE_ISO_14443_4 , // PICC compliant with ISO/IEC 14443-4
|
||||
PICC_TYPE_ISO_14443_4 , // PICC compliant with ISO/IEC 14443-4
|
||||
PICC_TYPE_ISO_18092 , // PICC compliant with ISO/IEC 18092 (NFC)
|
||||
PICC_TYPE_MIFARE_MINI , // MIFARE Classic protocol, 320 bytes
|
||||
PICC_TYPE_MIFARE_1K , // MIFARE Classic protocol, 1KB
|
||||
@ -236,7 +236,7 @@ public:
|
||||
PICC_TYPE_TNP3XXX , // Only mentioned in NXP AN 10833 MIFARE Type Identification Procedure
|
||||
PICC_TYPE_NOT_COMPLETE = 0xff // SAK indicates UID is not complete.
|
||||
};
|
||||
|
||||
|
||||
// Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more.
|
||||
// last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered
|
||||
enum StatusCode : byte {
|
||||
@ -250,7 +250,7 @@ public:
|
||||
STATUS_CRC_WRONG , // The CRC_A does not match
|
||||
STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK.
|
||||
};
|
||||
|
||||
|
||||
// A struct used for passing the UID of a PICC.
|
||||
typedef struct {
|
||||
byte size; // Number of bytes in the UID. 4, 7 or 10.
|
||||
@ -262,17 +262,17 @@ public:
|
||||
typedef struct {
|
||||
byte keyByte[MF_KEY_SIZE];
|
||||
} MIFARE_Key;
|
||||
|
||||
|
||||
// Member variables
|
||||
Uid uid; // Used by PICC_ReadCardSerial().
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// Functions for setting up the Arduino
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
MFRC522();
|
||||
MFRC522(byte resetPowerDownPin);
|
||||
MFRC522(byte chipSelectPin, byte resetPowerDownPin);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// Basic interface functions for communicating with the MFRC522
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -283,7 +283,7 @@ public:
|
||||
void PCD_SetRegisterBitMask(PCD_Register reg, byte mask);
|
||||
void PCD_ClearRegisterBitMask(PCD_Register reg, byte mask);
|
||||
StatusCode PCD_CalculateCRC(byte *data, byte length, byte *result);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// Functions for manipulating the MFRC522
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -296,13 +296,13 @@ public:
|
||||
byte PCD_GetAntennaGain();
|
||||
void PCD_SetAntennaGain(byte mask);
|
||||
bool PCD_PerformSelfTest();
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// Power control functions
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
void PCD_SoftPowerDown();
|
||||
void PCD_SoftPowerUp();
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// Functions for communicating with PICCs
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -329,19 +329,21 @@ public:
|
||||
StatusCode MIFARE_GetValue(byte blockAddr, int32_t *value);
|
||||
StatusCode MIFARE_SetValue(byte blockAddr, int32_t value);
|
||||
StatusCode PCD_NTAG216_AUTH(byte *passWord, byte pACK[]);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// Support functions
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
StatusCode PCD_MIFARE_Transceive(byte *sendData, byte sendLen, bool acceptTimeout = false);
|
||||
// old function used too much memory, now name moved to flash; if you need char, copy from flash to memory
|
||||
//const char *GetStatusCodeName(byte code);
|
||||
static const __FlashStringHelper *GetStatusCodeName(StatusCode code);
|
||||
// static const __FlashStringHelper *GetStatusCodeName(StatusCode code);
|
||||
String GetStatusCodeName(StatusCode code);
|
||||
static PICC_Type PICC_GetType(byte sak);
|
||||
// old function used too much memory, now name moved to flash; if you need char, copy from flash to memory
|
||||
//const char *PICC_GetTypeName(byte type);
|
||||
static const __FlashStringHelper *PICC_GetTypeName(PICC_Type type);
|
||||
|
||||
// static const __FlashStringHelper *PICC_GetTypeName(PICC_Type type);
|
||||
String PICC_GetTypeName(PICC_Type type);
|
||||
|
||||
// Support functions for debuging
|
||||
void PCD_DumpVersionToSerial();
|
||||
void PICC_DumpToSerial(Uid *uid);
|
||||
@ -349,19 +351,19 @@ public:
|
||||
void PICC_DumpMifareClassicToSerial(Uid *uid, PICC_Type piccType, MIFARE_Key *key);
|
||||
void PICC_DumpMifareClassicSectorToSerial(Uid *uid, MIFARE_Key *key, byte sector);
|
||||
void PICC_DumpMifareUltralightToSerial();
|
||||
|
||||
|
||||
// Advanced functions for MIFARE
|
||||
void MIFARE_SetAccessBits(byte *accessBitBuffer, byte g0, byte g1, byte g2, byte g3);
|
||||
bool MIFARE_OpenUidBackdoor(bool logErrors);
|
||||
bool MIFARE_SetUid(byte *newUid, byte uidSize, bool logErrors);
|
||||
bool MIFARE_UnbrickUidSector(bool logErrors);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// Convenience functions - does not add extra functionality
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
virtual bool PICC_IsNewCardPresent();
|
||||
virtual bool PICC_ReadCardSerial();
|
||||
|
||||
|
||||
protected:
|
||||
byte _chipSelectPin; // Arduino pin connected to MFRC522's SPI slave select input (Pin 24, NSS, active low)
|
||||
byte _resetPowerDownPin; // Arduino pin connected to MFRC522's reset and power down input (Pin 6, NRSTPD, active low)
|
||||
|
11
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/.gitignore
vendored
Normal file
11
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
*~
|
||||
Doxyfile*
|
||||
doxygen_sqlite3.db
|
||||
html# osx
|
||||
.DS_Store
|
||||
|
||||
# doxygen
|
||||
Doxyfile*
|
||||
doxygen_sqlite3.db
|
||||
html
|
||||
*.tmp
|
133
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/Adafruit_PM25AQI.cpp
Normal file
133
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/Adafruit_PM25AQI.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
/*!
|
||||
* @file Adafruit_PM25AQI.cpp
|
||||
*
|
||||
* @mainpage Adafruit PM2.5 air quality sensor driver
|
||||
*
|
||||
* @section intro_sec Introduction
|
||||
*
|
||||
* This is the documentation for Adafruit's PM2.5 AQI driver for the
|
||||
* Arduino platform. It is designed specifically to work with the
|
||||
* Adafruit PM2.5 Air quality sensors: http://www.adafruit.com/products/4632
|
||||
*
|
||||
* These sensors use I2C or UART to communicate.
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing
|
||||
* products from Adafruit!
|
||||
*
|
||||
*
|
||||
* @section author Author
|
||||
* Written by Ladyada for Adafruit Industries.
|
||||
*
|
||||
* @section license License
|
||||
* BSD license, all text here must be included in any redistribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Adafruit_PM25AQI.h"
|
||||
|
||||
/*!
|
||||
* @brief Instantiates a new PM25AQI class
|
||||
*/
|
||||
Adafruit_PM25AQI::Adafruit_PM25AQI() {}
|
||||
|
||||
/*!
|
||||
* @brief Setups the hardware and detects a valid PMSA003I. Initializes I2C.
|
||||
* @param theWire
|
||||
* Optional pointer to I2C interface, otherwise use Wire
|
||||
* @return True if PMSA003I found on I2C, False if something went wrong!
|
||||
*/
|
||||
bool Adafruit_PM25AQI::begin_I2C(TwoWire *theWire) {
|
||||
if (!i2c_dev) {
|
||||
i2c_dev = new Adafruit_I2CDevice(PMSA003I_I2CADDR_DEFAULT, theWire);
|
||||
}
|
||||
|
||||
if (!i2c_dev->begin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Setups the hardware and detects a valid UART PM2.5
|
||||
* @param theSerial
|
||||
* Pointer to Stream (HardwareSerial/SoftwareSerial) interface
|
||||
* @return True
|
||||
*/
|
||||
bool Adafruit_PM25AQI::begin_UART(Stream *theSerial) {
|
||||
serial_dev = theSerial;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Setups the hardware and detects a valid UART PM2.5
|
||||
* @param data
|
||||
* Pointer to PM25_AQI_Data that will be filled by read()ing
|
||||
* @return True on successful read, false if timed out or bad data
|
||||
*/
|
||||
bool Adafruit_PM25AQI::read(PM25_AQI_Data *data) {
|
||||
uint8_t buffer[32];
|
||||
uint16_t sum = 0;
|
||||
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i2c_dev) { // ok using i2c?
|
||||
if (!i2c_dev->read(buffer, 32)) {
|
||||
return false;
|
||||
}
|
||||
} else if (serial_dev) { // ok using uart
|
||||
if (!serial_dev->available()) {
|
||||
return false;
|
||||
}
|
||||
int skipped = 0;
|
||||
while ((skipped < 32) && (serial_dev->peek() != 0x42)) {
|
||||
serial_dev->read();
|
||||
skipped++;
|
||||
if (!serial_dev->available()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (serial_dev->peek() != 0x42) {
|
||||
serial_dev->read();
|
||||
return false;
|
||||
}
|
||||
// Now read all 32 bytes
|
||||
if (serial_dev->available() < 32) {
|
||||
return false;
|
||||
}
|
||||
serial_dev->readBytes(buffer, 32);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that start byte is correct!
|
||||
if (buffer[0] != 0x42) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get checksum ready
|
||||
for (uint8_t i = 0; i < 30; i++) {
|
||||
sum += buffer[i];
|
||||
}
|
||||
|
||||
// The data comes in endian'd, this solves it so it works on all platforms
|
||||
uint16_t buffer_u16[15];
|
||||
for (uint8_t i = 0; i < 15; i++) {
|
||||
buffer_u16[i] = buffer[2 + i * 2 + 1];
|
||||
buffer_u16[i] += (buffer[2 + i * 2] << 8);
|
||||
}
|
||||
|
||||
// put it into a nice struct :)
|
||||
memcpy((void *)data, (void *)buffer_u16, 30);
|
||||
|
||||
if (sum != data->checksum) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// success!
|
||||
return true;
|
||||
}
|
65
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/Adafruit_PM25AQI.h
Normal file
65
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/Adafruit_PM25AQI.h
Normal file
@ -0,0 +1,65 @@
|
||||
/*!
|
||||
* @file Adafruit_PM25AQI.h
|
||||
*
|
||||
* This is the documentation for Adafruit's PM25 AQI driver for the
|
||||
* Arduino platform. It is designed specifically to work with the
|
||||
* Adafruit PM25 air quality sensors: http://www.adafruit.com/products/4632
|
||||
*
|
||||
* These sensors use I2C or UART to communicate.
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing
|
||||
* products from Adafruit!
|
||||
*
|
||||
* Written by Ladyada for Adafruit Industries.
|
||||
*
|
||||
* BSD license, all text here must be included in any redistribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ADAFRUIT_PM25AQI_H
|
||||
#define ADAFRUIT_PM25AQI_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Adafruit_I2CDevice.h>
|
||||
|
||||
// the i2c address
|
||||
#define PMSA003I_I2CADDR_DEFAULT 0x12 ///< PMSA003I has only one I2C address
|
||||
|
||||
/**! Structure holding Plantower's standard packet **/
|
||||
typedef struct PMSAQIdata {
|
||||
uint16_t framelen; ///< How long this data chunk is
|
||||
uint16_t pm10_standard, ///< Standard PM1.0
|
||||
pm25_standard, ///< Standard PM2.5
|
||||
pm100_standard; ///< Standard PM10.0
|
||||
uint16_t pm10_env, ///< Environmental PM1.0
|
||||
pm25_env, ///< Environmental PM2.5
|
||||
pm100_env; ///< Environmental PM10.0
|
||||
uint16_t particles_03um, ///< 0.3um Particle Count
|
||||
particles_05um, ///< 0.5um Particle Count
|
||||
particles_10um, ///< 1.0um Particle Count
|
||||
particles_25um, ///< 2.5um Particle Count
|
||||
particles_50um, ///< 5.0um Particle Count
|
||||
particles_100um; ///< 10.0um Particle Count
|
||||
uint16_t unused; ///< Unused
|
||||
uint16_t checksum; ///< Packet checksum
|
||||
} PM25_AQI_Data;
|
||||
|
||||
/*!
|
||||
* @brief Class that stores state and functions for interacting with
|
||||
* PM2.5 Air Quality Sensor
|
||||
*/
|
||||
class Adafruit_PM25AQI {
|
||||
public:
|
||||
Adafruit_PM25AQI();
|
||||
bool begin_I2C(TwoWire *theWire = &Wire);
|
||||
bool begin_UART(Stream *theStream);
|
||||
bool read(PM25_AQI_Data *data);
|
||||
|
||||
private:
|
||||
Adafruit_I2CDevice *i2c_dev = NULL;
|
||||
Stream *serial_dev = NULL;
|
||||
uint8_t _readbuffer[32];
|
||||
};
|
||||
|
||||
#endif
|
49
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/README.md
Normal file
49
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Adafruit PM2.5 Air Quality sensor [](https://github.com/adafruit/Adafruit_PM25AQI/actions)[](http://adafruit.github.io/Adafruit_PM25AQI/html/index.html)
|
||||
|
||||
This is the Adafruit PM25AQI Arduino Library for Arduino
|
||||
Tested and works great with the Adafruit PM2.5 Air Quality Sensor and Breadboard Adapter Kit
|
||||
|
||||
[<img src="https://cdn-shop.adafruit.com/1200x900/3686-10.jpg" width="500px">](https://www.adafruit.com/products/3686)
|
||||
|
||||
Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit!
|
||||
|
||||
# Installation
|
||||
To install, use the Arduino Library Manager and search for "Adafruit PM25 AQI" and install the library.
|
||||
|
||||
## Dependencies
|
||||
* [Adafruit BusIO](https://github.com/adafruit/Adafruit_BusIO)
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are welcome! Please read our [Code of Conduct](https://github.com/adafruit/Adafruit_PM25AQI/blob/master/CODE_OF_CONDUCT.md>)
|
||||
before contributing to help this project stay welcoming.
|
||||
|
||||
## Documentation and doxygen
|
||||
Documentation is produced by doxygen. Contributions should include documentation for any new code added.
|
||||
|
||||
Some examples of how to use doxygen can be found in these guide pages:
|
||||
|
||||
https://learn.adafruit.com/the-well-automated-arduino-library/doxygen
|
||||
|
||||
https://learn.adafruit.com/the-well-automated-arduino-library/doxygen-tips
|
||||
|
||||
## Formatting and clang-format
|
||||
This library uses [`clang-format`](https://releases.llvm.org/download.html) to standardize the formatting of `.cpp` and `.h` files.
|
||||
Contributions should be formatted using `clang-format`:
|
||||
|
||||
The `-i` flag will make the changes to the file.
|
||||
```bash
|
||||
clang-format -i *.cpp *.h
|
||||
```
|
||||
If you prefer to make the changes yourself, running `clang-format` without the `-i` flag will print out a formatted version of the file. You can save this to a file and diff it against the original to see the changes.
|
||||
|
||||
Note that the formatting output by `clang-format` is what the automated formatting checker will expect. Any diffs from this formatting will result in a failed build until they are addressed. Using the `-i` flag is highly recommended.
|
||||
|
||||
### clang-format resources
|
||||
* [Binary builds and source available on the LLVM downloads page](https://releases.llvm.org/download.html)
|
||||
* [Documentation and IDE integration](https://clang.llvm.org/docs/ClangFormat.html)
|
||||
|
||||
## About this Driver
|
||||
Written by Ladyada for Adafruit Industries.
|
||||
BSD license, check license.txt for more information
|
||||
All text above must be included in any redistribution
|
127
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/code-of-conduct.md
Normal file
127
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/code-of-conduct.md
Normal file
@ -0,0 +1,127 @@
|
||||
# Adafruit Community Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and leaders pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level or type of
|
||||
experience, education, socio-economic status, nationality, personal appearance,
|
||||
race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
We are committed to providing a friendly, safe and welcoming environment for
|
||||
all.
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Be kind and courteous to others
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Collaborating with other community members
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and sexual attention or advances
|
||||
* The use of inappropriate images, including in a community member's avatar
|
||||
* The use of inappropriate language, including in a community member's nickname
|
||||
* Any spamming, flaming, baiting or other attention-stealing behavior
|
||||
* Excessive or unwelcome helping; answering outside the scope of the question
|
||||
asked
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate
|
||||
|
||||
The goal of the standards and moderation guidelines outlined here is to build
|
||||
and maintain a respectful community. We ask that you don’t just aim to be
|
||||
"technically unimpeachable", but rather try to be your best self.
|
||||
|
||||
We value many things beyond technical expertise, including collaboration and
|
||||
supporting others within our community. Providing a positive experience for
|
||||
other community members can have a much more significant impact than simply
|
||||
providing the correct answer.
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project leaders are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project leaders have the right and responsibility to remove, edit, or
|
||||
reject messages, comments, commits, code, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any community member for other behaviors that they deem
|
||||
inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Moderation
|
||||
|
||||
Instances of behaviors that violate the Adafruit Community Code of Conduct
|
||||
may be reported by any member of the community. Community members are
|
||||
encouraged to report these situations, including situations they witness
|
||||
involving other community members.
|
||||
|
||||
You may report in the following ways:
|
||||
|
||||
In any situation, you may send an email to <support@adafruit.com>.
|
||||
|
||||
On the Adafruit Discord, you may send an open message from any channel
|
||||
to all Community Helpers by tagging @community helpers. You may also send an
|
||||
open message from any channel, or a direct message to @kattni#1507,
|
||||
@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or
|
||||
@Andon#8175.
|
||||
|
||||
Email and direct message reports will be kept confidential.
|
||||
|
||||
In situations on Discord where the issue is particularly egregious, possibly
|
||||
illegal, requires immediate action, or violates the Discord terms of service,
|
||||
you should also report the message directly to Discord.
|
||||
|
||||
These are the steps for upholding our community’s standards of conduct.
|
||||
|
||||
1. Any member of the community may report any situation that violates the
|
||||
Adafruit Community Code of Conduct. All reports will be reviewed and
|
||||
investigated.
|
||||
2. If the behavior is an egregious violation, the community member who
|
||||
committed the violation may be banned immediately, without warning.
|
||||
3. Otherwise, moderators will first respond to such behavior with a warning.
|
||||
4. Moderators follow a soft "three strikes" policy - the community member may
|
||||
be given another chance, if they are receptive to the warning and change their
|
||||
behavior.
|
||||
5. If the community member is unreceptive or unreasonable when warned by a
|
||||
moderator, or the warning goes unheeded, they may be banned for a first or
|
||||
second offense. Repeated offenses will result in the community member being
|
||||
banned.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct and the enforcement policies listed above apply to all
|
||||
Adafruit Community venues. This includes but is not limited to any community
|
||||
spaces (both public and private), the entire Adafruit Discord server, and
|
||||
Adafruit GitHub repositories. Examples of Adafruit Community spaces include
|
||||
but are not limited to meet-ups, audio chats on the Adafruit Discord, or
|
||||
interaction at a conference.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. As a community
|
||||
member, you are representing our community, and are expected to behave
|
||||
accordingly.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 1.4, available at
|
||||
<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>,
|
||||
and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html).
|
||||
|
||||
For other projects adopting the Adafruit Community Code of
|
||||
Conduct, please contact the maintainers of those projects for enforcement.
|
||||
If you wish to use this code of conduct for your own project, consider
|
||||
explicitly mentioning your moderation policy or making a copy with your
|
||||
own moderation policy so as to avoid confusion.
|
@ -0,0 +1,73 @@
|
||||
/* Test sketch for Adafruit PM2.5 sensor with UART or I2C */
|
||||
|
||||
#include "Adafruit_PM25AQI.h"
|
||||
|
||||
// If your PM2.5 is UART only, for UNO and others (without hardware serial)
|
||||
// we must use software serial...
|
||||
// pin #2 is IN from sensor (TX pin on sensor), leave pin #3 disconnected
|
||||
// comment these two lines if using hardware serial
|
||||
//#include <SoftwareSerial.h>
|
||||
//SoftwareSerial pmSerial(2, 3);
|
||||
|
||||
Adafruit_PM25AQI aqi = Adafruit_PM25AQI();
|
||||
|
||||
void setup() {
|
||||
// Wait for serial monitor to open
|
||||
Serial.begin(115200);
|
||||
while (!Serial) delay(10);
|
||||
|
||||
Serial.println("Adafruit PMSA003I Air Quality Sensor");
|
||||
|
||||
// Wait one second for sensor to boot up!
|
||||
delay(1000);
|
||||
|
||||
// If using serial, initialize it and set baudrate before starting!
|
||||
// Uncomment one of the following
|
||||
//Serial1.begin(9600);
|
||||
//pmSerial.begin(9600);
|
||||
|
||||
// There are 3 options for connectivity!
|
||||
if (! aqi.begin_I2C()) { // connect to the sensor over I2C
|
||||
//if (! aqi.begin_UART(&Serial1)) { // connect to the sensor over hardware serial
|
||||
//if (! aqi.begin_UART(&pmSerial)) { // connect to the sensor over software serial
|
||||
Serial.println("Could not find PM 2.5 sensor!");
|
||||
while (1) delay(10);
|
||||
}
|
||||
|
||||
Serial.println("PM25 found!");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
PM25_AQI_Data data;
|
||||
|
||||
if (! aqi.read(&data)) {
|
||||
Serial.println("Could not read from AQI");
|
||||
delay(500); // try again in a bit!
|
||||
return;
|
||||
}
|
||||
Serial.println("AQI reading success");
|
||||
|
||||
Serial.println();
|
||||
Serial.println(F("---------------------------------------"));
|
||||
Serial.println(F("Concentration Units (standard)"));
|
||||
Serial.println(F("---------------------------------------"));
|
||||
Serial.print(F("PM 1.0: ")); Serial.print(data.pm10_standard);
|
||||
Serial.print(F("\t\tPM 2.5: ")); Serial.print(data.pm25_standard);
|
||||
Serial.print(F("\t\tPM 10: ")); Serial.println(data.pm100_standard);
|
||||
Serial.println(F("Concentration Units (environmental)"));
|
||||
Serial.println(F("---------------------------------------"));
|
||||
Serial.print(F("PM 1.0: ")); Serial.print(data.pm10_env);
|
||||
Serial.print(F("\t\tPM 2.5: ")); Serial.print(data.pm25_env);
|
||||
Serial.print(F("\t\tPM 10: ")); Serial.println(data.pm100_env);
|
||||
Serial.println(F("---------------------------------------"));
|
||||
Serial.print(F("Particles > 0.3um / 0.1L air:")); Serial.println(data.particles_03um);
|
||||
Serial.print(F("Particles > 0.5um / 0.1L air:")); Serial.println(data.particles_05um);
|
||||
Serial.print(F("Particles > 1.0um / 0.1L air:")); Serial.println(data.particles_10um);
|
||||
Serial.print(F("Particles > 2.5um / 0.1L air:")); Serial.println(data.particles_25um);
|
||||
Serial.print(F("Particles > 5.0um / 0.1L air:")); Serial.println(data.particles_50um);
|
||||
Serial.print(F("Particles > 10 um / 0.1L air:")); Serial.println(data.particles_100um);
|
||||
Serial.println(F("---------------------------------------"));
|
||||
|
||||
|
||||
delay(1000);
|
||||
}
|
10
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/library.properties
Normal file
10
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/library.properties
Normal file
@ -0,0 +1,10 @@
|
||||
name=Adafruit PM25 AQI Sensor
|
||||
version=1.0.6
|
||||
author=Adafruit
|
||||
maintainer=Adafruit <info@adafruit.com>
|
||||
sentence=This is an Arduino library for the Adafruit PM2.5 Air Quality Sensor
|
||||
paragraph=This is an Arduino library for the Adafruit PM2.5 Air Quality Sensor
|
||||
category=Sensors
|
||||
url=https://github.com/adafruit/Adafruit_PM25AQI
|
||||
architectures=*
|
||||
depends=Adafruit BusIO
|
26
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/license.txt
Normal file
26
lib/lib_i2c/Adafruit_PM25AQI-1.0.6/license.txt
Normal file
@ -0,0 +1,26 @@
|
||||
Software License Agreement (BSD License)
|
||||
|
||||
Copyright (c) 2012, Adafruit Industries
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holders nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -229,17 +229,17 @@ HTTPUpdateResult HTTPUpdateLight::handleUpdate(HTTPClientLight& http, const Stri
|
||||
|
||||
uint32_t http_connect_time = millis();
|
||||
|
||||
int code = http.GET();
|
||||
int len = http.getSize();
|
||||
int code = http.GET(); // 0 if ok or < 0 if error
|
||||
int len = http.getSize(); // -1 if no info or > 0 when Content-Length is set by server
|
||||
|
||||
// Add specific logging for Tasmota
|
||||
if (len < 0) {
|
||||
if (len <= -1000) {
|
||||
AddLog(LOG_LEVEL_INFO, "OTA: TLS connection error %d after %d ms", -len - 1000, millis() - http_connect_time);
|
||||
} else if (len == -1) {
|
||||
AddLog(LOG_LEVEL_INFO, "OTA: Connection timeout after %d ms", len, millis() - http_connect_time);
|
||||
if (len < 0) { // -1 if no info or > 0 when Content-Length is set by server
|
||||
if (code <= -1000) { // BearSSL error 46 transformed to -1046
|
||||
AddLog(LOG_LEVEL_INFO, "OTA: TLS connection error %d after %d ms", -code - 1000, millis() - http_connect_time);
|
||||
} else if (code == -1) { // HTTPC_ERROR_CONNECTION_REFUSED
|
||||
AddLog(LOG_LEVEL_INFO, "OTA: Connection timeout after %d ms", millis() - http_connect_time);
|
||||
} else {
|
||||
AddLog(LOG_LEVEL_INFO, "OTA: Connection error %d after %d ms", len, millis() - http_connect_time);
|
||||
AddLog(LOG_LEVEL_INFO, "OTA: Connection error %d after %d ms", code, millis() - http_connect_time);
|
||||
}
|
||||
} else {
|
||||
AddLog(LOG_LEVEL_DEBUG, PSTR("OTA: Connected in %d ms, stack low mark %d"),
|
||||
|
@ -247,7 +247,7 @@ protected:
|
||||
/// request handling
|
||||
String _host;
|
||||
uint16_t _port = 0;
|
||||
int32_t _connectTimeout = -1;
|
||||
int32_t _connectTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT; // Do not set to -1 as it fails WiFiClient connect()
|
||||
bool _reuse = true;
|
||||
uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT;
|
||||
bool _useHTTP10 = false;
|
||||
|
@ -137,10 +137,10 @@ int WiFiClass32::getPhyMode() {
|
||||
int phy_mode = 0; // " BGNL"
|
||||
uint8_t protocol_bitmap;
|
||||
if (esp_wifi_get_protocol(WIFI_IF_STA, &protocol_bitmap) == ESP_OK) {
|
||||
if (protocol_bitmap & 1) { phy_mode = WIFI_PHY_MODE_11B; } // 1 = 11b
|
||||
if (protocol_bitmap & 2) { phy_mode = WIFI_PHY_MODE_11G; } // 2 = 11bg
|
||||
if (protocol_bitmap & 4) { phy_mode = WIFI_PHY_MODE_11N; } // 3 = 11bgn
|
||||
if (protocol_bitmap & 8) { phy_mode = 4; } // Low rate
|
||||
if (protocol_bitmap & 1) { phy_mode = TAS_WIFI_PHY_MODE_11B; } // 1 = 11b (WIFI_PHY_MODE_11B)
|
||||
if (protocol_bitmap & 2) { phy_mode = TAS_WIFI_PHY_MODE_11G; } // 2 = 11bg (WIFI_PHY_MODE_11G)
|
||||
if (protocol_bitmap & 4) { phy_mode = TAS_WIFI_PHY_MODE_11N; } // 3 = 11bgn (WIFI_PHY_MODE_11N)
|
||||
if (protocol_bitmap & 8) { phy_mode = 4; } // Low rate (WIFI_PHY_MODE_LR)
|
||||
}
|
||||
return phy_mode;
|
||||
}
|
||||
@ -240,7 +240,7 @@ int WiFiClass32::hostByName(const char* aHostname, IPAddress& aResult, int32_t t
|
||||
ip_addr_t addr;
|
||||
aResult = (uint32_t) 0; // by default set to IPv4 0.0.0.0
|
||||
dns_ipaddr = *IP4_ADDR_ANY; // by default set to IPv4 0.0.0.0
|
||||
|
||||
|
||||
scrubDNS(); // internal calls to reconnect can zero the DNS servers, save DNS for future use
|
||||
ip_addr_counter++; // increase counter, from now ignore previous responses
|
||||
clearStatusBits(WIFI_DNS_IDLE_BIT | WIFI_DNS_DONE_BIT);
|
||||
@ -260,7 +260,7 @@ int WiFiClass32::hostByName(const char* aHostname, IPAddress& aResult, int32_t t
|
||||
waitStatusBits(WIFI_DNS_DONE_BIT, timer_ms); //real internal timeout in lwip library is 14[s]
|
||||
clearStatusBits(WIFI_DNS_DONE_BIT);
|
||||
}
|
||||
|
||||
|
||||
if (!ip_addr_isany_val(dns_ipaddr)) {
|
||||
#ifdef USE_IPV6
|
||||
aResult = dns_ipaddr;
|
||||
|
@ -29,11 +29,25 @@
|
||||
#define WIFI_LIGHT_SLEEP 1
|
||||
#define WIFI_MODEM_SLEEP 2
|
||||
|
||||
// ESP8266
|
||||
typedef enum WiFiPhyMode
|
||||
{
|
||||
WIFI_PHY_MODE_11B = 1, WIFI_PHY_MODE_11G = 2, WIFI_PHY_MODE_11N = 3
|
||||
TAS_WIFI_PHY_MODE_LR = 0, TAS_WIFI_PHY_MODE_11B = 1, TAS_WIFI_PHY_MODE_11G = 2, TAS_WIFI_PHY_MODE_11N = 3
|
||||
} WiFiPhyMode_t;
|
||||
|
||||
/*
|
||||
// ESP32 was never defined until IDF 4.4
|
||||
typedef enum
|
||||
{
|
||||
WIFI_PHY_MODE_LR, // PHY mode for Low Rate
|
||||
WIFI_PHY_MODE_11B, // PHY mode for 11b
|
||||
WIFI_PHY_MODE_11G, // PHY mode for 11g
|
||||
WIFI_PHY_MODE_HT20, // PHY mode for 11n Bandwidth HT20
|
||||
WIFI_PHY_MODE_HT40, // PHY mode for 11n Bandwidth HT40
|
||||
WIFI_PHY_MODE_HE20, // PHY mode for 11n Bandwidth HE20
|
||||
} wifi_phy_mode_t;
|
||||
*/
|
||||
|
||||
class WiFiClass32 : public WiFiClass
|
||||
{
|
||||
public:
|
||||
|
@ -207,7 +207,7 @@ void analogWriteFreqRange(int32_t freq, int32_t range, int32_t pin) {
|
||||
_analogInit(); // make sure the mapping array is initialized
|
||||
uint32_t timer0_freq = timer_freq_hz[0]; // global values
|
||||
uint8_t timer0_res = timer_duty_resolution[0];
|
||||
|
||||
|
||||
int32_t timer = 0;
|
||||
int32_t res = timer0_res;
|
||||
if (pin < 0) {
|
||||
@ -233,7 +233,7 @@ void analogWriteFreqRange(int32_t freq, int32_t range, int32_t pin) {
|
||||
if (timer != 0) {
|
||||
ledcSetTimer(chan, 0);
|
||||
timer = 0;
|
||||
}
|
||||
}
|
||||
// else nothing to change
|
||||
} else {
|
||||
// specific (non-global) values, require a specific timer
|
||||
@ -293,7 +293,7 @@ int32_t analogAttach(uint32_t pin, bool output_invert) { // returns ledc chan
|
||||
AddLog(LOG_LEVEL_INFO, "PWM: no more PWM (ledc) channel for GPIO %i", pin);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// new channel attached to pin
|
||||
pin_to_channel[pin] = chan + 1;
|
||||
|
||||
|
@ -25,14 +25,14 @@
|
||||
|
||||
/*******************************************************************************************\
|
||||
* ESP32/S2/S3/C3... PWM analog support
|
||||
*
|
||||
*
|
||||
* The following supersedes Arduino framework and provides more granular control:
|
||||
* - fine grained phase control (in addition to duty cycle)
|
||||
* - fine control of PWM frequency and resolution per GPIO
|
||||
*
|
||||
*
|
||||
* By default, all PWM are using the same timer called Timer 0.
|
||||
* Changes in frequency of resolution apply to all PWM using Timer 0.
|
||||
*
|
||||
*
|
||||
* You can specify a different a different resolution/frequency for
|
||||
* specific GPIOs, this will internally assign a new timer to the GPIO.
|
||||
* The limit is 3 specific values in addition to the global value.
|
||||
@ -129,7 +129,7 @@ uint32_t analogGetTimerFrequency(uint8_t timer);
|
||||
#define os_delay_us ets_delay_us
|
||||
// Serial minimal type to hold the config
|
||||
typedef int SerConfu8;
|
||||
typedef int SerialConfig;
|
||||
//typedef int SerialConfig; // Will be replaced enum in esp32_hal-uart.h (#7926)
|
||||
|
||||
//
|
||||
// UDP
|
||||
|
@ -50,6 +50,7 @@ be_extern_native_module(partition_core);
|
||||
be_extern_native_module(crc);
|
||||
be_extern_native_module(crypto);
|
||||
be_extern_native_module(ULP);
|
||||
be_extern_native_module(TFL);
|
||||
be_extern_native_module(mdns);
|
||||
#ifdef USE_ZIGBEE
|
||||
be_extern_native_module(zigbee);
|
||||
@ -171,6 +172,9 @@ BERRY_LOCAL const bntvmodule* const be_module_table[] = {
|
||||
#if defined(USE_BERRY_ULP) && ((CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3))
|
||||
&be_native_module(ULP),
|
||||
#endif // USE_BERRY_ULP
|
||||
#if defined(USE_BERRY_TF_LITE)
|
||||
&be_native_module(TFL),
|
||||
#endif //USE_BERRY_TF_LITE
|
||||
#if defined(USE_MI_ESP32) && !defined(USE_BLE_ESP32)
|
||||
&be_native_module(MI32),
|
||||
&be_native_module(BLE),
|
||||
@ -301,7 +305,7 @@ BERRY_LOCAL bclass_array be_class_table = {
|
||||
#endif // USE_UFILESYS
|
||||
&be_native_class(AudioOpusDecoder),
|
||||
#endif // USE_I2S_AUDIO_BERRY
|
||||
#ifdef USE_BERRY_INT64
|
||||
#if defined(USE_BERRY_INT64) || defined(USE_MATTER_DEVICE)
|
||||
&be_native_class(int64),
|
||||
#endif
|
||||
#endif // TASMOTA
|
||||
|
@ -259,7 +259,6 @@
|
||||
* are not required.
|
||||
* The default is to use the functions in the standard library.
|
||||
**/
|
||||
#ifdef USE_BERRY_PSRAM
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -270,6 +269,7 @@ extern "C" {
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BERRY_PSRAM
|
||||
#define BE_EXPLICIT_MALLOC berry_malloc
|
||||
#define BE_EXPLICIT_FREE berry_free
|
||||
#define BE_EXPLICIT_REALLOC berry_realloc
|
||||
@ -306,6 +306,10 @@ extern "C" {
|
||||
#undef BE_STACK_START
|
||||
#define BE_STACK_START 200
|
||||
#endif // USE_LVGL
|
||||
#ifdef USE_MATTER_DEVICE
|
||||
#undef BE_STACK_START
|
||||
#define BE_STACK_START 256
|
||||
#endif // USE_MATTER_DEVICE
|
||||
#endif // USE_BERRY_DEBUG
|
||||
|
||||
#endif
|
||||
|
@ -178,11 +178,20 @@ static const char* parser_string(bvm *vm, const char *json)
|
||||
}
|
||||
default: be_free(vm, buf, len); return NULL; /* error */
|
||||
}
|
||||
} else if(ch >= 0 && ch <= 0x1f) {
|
||||
/* control characters must be escaped
|
||||
as per https://www.rfc-editor.org/rfc/rfc7159#section-7 */
|
||||
be_free(vm, buf, len);
|
||||
return NULL;
|
||||
} else {
|
||||
*dst++ = (char)ch;
|
||||
}
|
||||
}
|
||||
be_assert(ch == '"');
|
||||
/* require the stack to have some free space for the string,
|
||||
since parsing deeply nested objects might
|
||||
crash the VM due to insufficient stack space. */
|
||||
be_stack_require(vm, 1 + BE_STACK_FREE_MIN);
|
||||
be_pushnstring(vm, buf, cast_int(dst - buf));
|
||||
be_free(vm, buf, len);
|
||||
return json + 1; /* skip '"' */
|
||||
@ -269,6 +278,92 @@ static const char* parser_array(bvm *vm, const char *json)
|
||||
return json;
|
||||
}
|
||||
|
||||
static const char* parser_number(bvm *vm, const char *json)
|
||||
{
|
||||
char c = *json++;
|
||||
bbool is_neg = c == '-';
|
||||
if(is_neg) {
|
||||
c = *json++;
|
||||
if(!is_digit(c)) {
|
||||
/* minus must be followed by digit */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
bint intv = 0;
|
||||
if(c != '0') {
|
||||
/* parse integer part */
|
||||
while(is_digit(c)) {
|
||||
intv = intv * 10 + c - '0';
|
||||
c = *json++;
|
||||
}
|
||||
|
||||
} else {
|
||||
/*
|
||||
Number starts with zero, this is only allowed
|
||||
if the number is just '0' or
|
||||
it has a fractional part or exponent.
|
||||
*/
|
||||
c = *json++;
|
||||
|
||||
}
|
||||
if(c != '.' && c != 'e' && c != 'E') {
|
||||
/*
|
||||
No fractional part or exponent follows, this is an integer.
|
||||
If digits follow after it (for example due to a leading zero)
|
||||
this will cause an error in the calling function.
|
||||
*/
|
||||
be_pushint(vm, intv * (is_neg ? -1 : 1));
|
||||
json--;
|
||||
return json;
|
||||
}
|
||||
breal realval = (breal) intv;
|
||||
if(c == '.') {
|
||||
|
||||
breal deci = 0.0, point = 0.1;
|
||||
/* fractional part */
|
||||
c = *json++;
|
||||
if(!is_digit(c)) {
|
||||
/* decimal point must be followed by digit */
|
||||
return NULL;
|
||||
}
|
||||
while (is_digit(c)) {
|
||||
deci = deci + ((breal)c - '0') * point;
|
||||
point *= (breal)0.1;
|
||||
c = *json++;
|
||||
}
|
||||
|
||||
realval += deci;
|
||||
}
|
||||
if(c == 'e' || c == 'E') {
|
||||
c = *json++;
|
||||
/* exponent part */
|
||||
breal ratio = c == '-' ? (breal)0.1 : 10;
|
||||
if (c == '+' || c == '-') {
|
||||
c = *json++;
|
||||
if(!is_digit(c)) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if(!is_digit(c)) {
|
||||
/* e and sign must be followed by digit */
|
||||
return NULL;
|
||||
}
|
||||
unsigned int e = 0;
|
||||
while (is_digit(c)) {
|
||||
e = e * 10 + c - '0';
|
||||
c = *json++;
|
||||
}
|
||||
/* e > 0 must be here to prevent infinite loops when e overflows */
|
||||
while (e--) {
|
||||
realval *= ratio;
|
||||
}
|
||||
}
|
||||
|
||||
be_pushreal(vm, realval * (is_neg ? -1.0 : 1.0));
|
||||
json--;
|
||||
return json;
|
||||
}
|
||||
|
||||
/* parser json value */
|
||||
static const char* parser_value(bvm *vm, const char *json)
|
||||
{
|
||||
@ -288,11 +383,7 @@ static const char* parser_value(bvm *vm, const char *json)
|
||||
return parser_null(vm, json);
|
||||
default: /* number */
|
||||
if (*json == '-' || is_digit(*json)) {
|
||||
/* check invalid JSON syntax: 0\d+ */
|
||||
if (json[0] == '0' && is_digit(json[1])) {
|
||||
return NULL;
|
||||
}
|
||||
return be_str2num(vm, json);
|
||||
return parser_number(vm, json);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
extern const bclass be_class_list;
|
||||
extern const bclass be_class_map;
|
||||
@ -323,7 +324,7 @@ static void m_solidify_proto(bvm *vm, bbool str_literal, bproto *pr, const char
|
||||
for (int32_t i = 0; i < pr->nproto; i++) {
|
||||
size_t sub_len = strlen(func_name) + 10;
|
||||
char sub_name[sub_len];
|
||||
snprintf(sub_name, sizeof(sub_name), "%s_%d", func_name, i);
|
||||
snprintf(sub_name, sizeof(sub_name), "%s_%"PRId32, func_name, i);
|
||||
m_solidify_proto(vm, str_literal, pr->ptab[i], sub_name, indent+2, fout);
|
||||
logfmt(",\n");
|
||||
}
|
||||
@ -361,7 +362,7 @@ static void m_solidify_proto(bvm *vm, bbool str_literal, bproto *pr, const char
|
||||
logfmt("%*s( &(const binstruction[%2d]) { /* code */\n", indent, "", pr->codesize);
|
||||
for (int pc = 0; pc < pr->codesize; pc++) {
|
||||
uint32_t ins = pr->code[pc];
|
||||
logfmt("%*s 0x%08X, //", indent, "", ins);
|
||||
logfmt("%*s 0x%08"PRIX32", //", indent, "", ins);
|
||||
be_print_inst(ins, pc, fout);
|
||||
bopcode op = IGET_OP(ins);
|
||||
if (op == OP_GETGBL || op == OP_SETGBL) {
|
||||
|
@ -167,6 +167,12 @@ static bstring* find_conststr(const char *str, size_t len)
|
||||
uint32_t hash = str_hash(str, len);
|
||||
bcstring *s = (bcstring*)tab->table[hash % tab->size];
|
||||
for (; s != NULL; s = next(s)) {
|
||||
if (len == 0 && s->slen == 0) {
|
||||
/* special case for the empty string,
|
||||
since we don't want to compare it using strncmp,
|
||||
because str might be NULL */
|
||||
return (bstring*)s;
|
||||
}
|
||||
if (len == s->slen && !strncmp(str, s->s, len)) {
|
||||
return (bstring*)s;
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
import json
|
||||
|
||||
import string
|
||||
# load tests
|
||||
|
||||
def assert_load(text, value)
|
||||
assert(json.load(text) == value)
|
||||
var loaded_val = json.load(text)
|
||||
var ok = loaded_val == value
|
||||
if !ok
|
||||
print(string.format('for JSON \'%s\' expected %s [%s] but got %s [%s]', text, str(value), type(value), str(loaded_val), type(loaded_val)))
|
||||
end
|
||||
assert(ok)
|
||||
end
|
||||
|
||||
def assert_load_failed(text)
|
||||
@ -15,6 +20,13 @@ assert_load('true', true)
|
||||
assert_load('false', false)
|
||||
assert_load('123', 123)
|
||||
assert_load('12.3', 12.3)
|
||||
assert_load('-0.1', -0.1)
|
||||
assert_load('1e2', 1e2)
|
||||
assert_load('1e+2', 1e+2)
|
||||
assert_load('1e-2', 1e-2)
|
||||
assert_load('1E2', 1e2)
|
||||
assert_load('1E+2', 1e+2)
|
||||
assert_load('1.2e7', 1.2e7)
|
||||
assert_load('"abc"', 'abc')
|
||||
# strings
|
||||
assert_load('"\\"\\\\\\/\\b\\f\\n\\r\\t"', '\"\\/\b\f\n\r\t')
|
||||
@ -30,10 +42,22 @@ assert_load_failed('[1, null')
|
||||
# object
|
||||
var o = json.load('{"key": 1}')
|
||||
assert(o['key'] == 1 && o.size() == 1)
|
||||
|
||||
# parsing an empty string used to cause berry to pass a NULL to strncmp
|
||||
# make sure we catch this
|
||||
o = json.load('{"key": ""}')
|
||||
assert(o['key'] == '' && o.size() == 1)
|
||||
|
||||
assert_load_failed('{"ke: 1}')
|
||||
assert_load_failed('{"key": 1x}')
|
||||
assert_load_failed('{"key"}')
|
||||
assert_load_failed('{"key": 1, }')
|
||||
# insanely long, nested object
|
||||
var text = 'null'
|
||||
for i : 0 .. 200
|
||||
text = '{"nested":' + text + ', "num": 1, "bool": true, "str": "abc", "n": null, "arr": [1, 2, 3]}'
|
||||
end
|
||||
json.load(text) # do nothing, just check that it doesn't crash
|
||||
|
||||
# dump tests
|
||||
|
||||
|
50
lib/libesp32/berry/tests/json_advenced.be
Normal file
50
lib/libesp32/berry/tests/json_advenced.be
Normal file
@ -0,0 +1,50 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
|
||||
def assert_load_failed(text)
|
||||
assert(json.load(text) == nil)
|
||||
end
|
||||
|
||||
var input_file = open("tests/json_test_cases.json", "r")
|
||||
var test_cases = json.load(input_file.read())
|
||||
|
||||
# check positive cases
|
||||
var has_failed_positives = false
|
||||
for case_name : test_cases["positive"].keys()
|
||||
var case = test_cases["positive"][case_name]
|
||||
var val = json.load(case)
|
||||
if val == nil && case != "null"
|
||||
print("Failed to load case: " + case_name)
|
||||
has_failed_positives = true
|
||||
end
|
||||
end
|
||||
|
||||
if has_failed_positives
|
||||
assert(false)
|
||||
end
|
||||
|
||||
# check negative cases
|
||||
|
||||
var has_failed_negatives = false
|
||||
for case_name : test_cases["negative"].keys()
|
||||
var case = test_cases["negative"][case_name]
|
||||
|
||||
var val = json.load(case)
|
||||
if val != nil
|
||||
print("Failed to fail case: " + case_name + ", got: " + json.dump(val))
|
||||
has_failed_negatives = true
|
||||
end
|
||||
end
|
||||
|
||||
if has_failed_negatives
|
||||
# assert(false)
|
||||
end
|
||||
|
||||
# check "any" cases, only for crashes
|
||||
|
||||
for case_name : test_cases["any"].keys()
|
||||
var case = test_cases["any"][case_name]
|
||||
var val = json.load(case)
|
||||
end
|
295
lib/libesp32/berry/tests/json_test_cases.json
Normal file
295
lib/libesp32/berry/tests/json_test_cases.json
Normal file
@ -0,0 +1,295 @@
|
||||
{
|
||||
"___comment": "Adapted from https://github.com/nst/JSONTestSuite",
|
||||
"positive": {
|
||||
"array_arraysWithSpaces": "[[] ]",
|
||||
"array_empty-string": "[\"\"]",
|
||||
"array_empty": "[]",
|
||||
"array_ending_with_newline": "[\"a\"]",
|
||||
"array_false": "[false]",
|
||||
"array_heterogeneous": "[null, 1, \"1\", {}]",
|
||||
"array_null": "[null]",
|
||||
"array_with_1_and_newline": "[1\n]",
|
||||
"array_with_leading_space": " [1]",
|
||||
"array_with_several_null": "[1,null,null,null,2]",
|
||||
"array_with_trailing_space": "[2] ",
|
||||
"number": "[123e65]",
|
||||
"number_0e+1": "[0e+1]",
|
||||
"number_0e1": "[0e1]",
|
||||
"number_after_space": "[ 4]",
|
||||
"number_double_close_to_zero": "[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001]\n",
|
||||
"number_int_with_exp": "[20e1]",
|
||||
"number_minus_zero": "[-0]",
|
||||
"number_negative_int": "[-123]",
|
||||
"number_negative_one": "[-1]",
|
||||
"number_negative_zero": "[-0]",
|
||||
"number_real_capital_e": "[1E22]",
|
||||
"number_real_capital_e_neg_exp": "[1E-2]",
|
||||
"number_real_capital_e_pos_exp": "[1E+2]",
|
||||
"number_real_exponent": "[123e45]",
|
||||
"number_real_fraction_exponent": "[123.456e78]",
|
||||
"number_real_neg_exp": "[1e-2]",
|
||||
"number_real_pos_exponent": "[1e+2]",
|
||||
"number_simple_int": "[123]",
|
||||
"number_simple_real": "[123.456789]",
|
||||
"object": "{\"asd\":\"sdf\", \"dfg\":\"fgh\"}",
|
||||
"object_basic": "{\"asd\":\"sdf\"}",
|
||||
"object_duplicated_key": "{\"a\":\"b\",\"a\":\"c\"}",
|
||||
"object_duplicated_key_and_value": "{\"a\":\"b\",\"a\":\"b\"}",
|
||||
"object_empty": "{}",
|
||||
"object_empty_key": "{\"\":0}",
|
||||
"object_escaped_null_in_key": "{\"foo\\u0000bar\": 42}",
|
||||
"object_extreme_numbers": "{ \"min\": -1.0e+28, \"max\": 1.0e+28 }",
|
||||
"object_long_strings": "{\"x\":[{\"id\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}], \"id\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}",
|
||||
"object_simple": "{\"a\":[]}",
|
||||
"object_string_unicode": "{\"title\":\"\\u041f\\u043e\\u043b\\u0442\\u043e\\u0440\\u0430 \\u0417\\u0435\\u043c\\u043b\\u0435\\u043a\\u043e\\u043f\\u0430\" }",
|
||||
"object_with_newlines": "{\n\"a\": \"b\"\n}",
|
||||
"string_1_2_3_bytes_UTF-8_sequences": "[\"\\u0060\\u012a\\u12AB\"]",
|
||||
"string_accepted_surrogate_pair": "[\"\\uD801\\udc37\"]",
|
||||
"string_accepted_surrogate_pairs": "[\"\\ud83d\\ude39\\ud83d\\udc8d\"]",
|
||||
"string_allowed_escapes": "[\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"]",
|
||||
"string_backslash_and_u_escaped_zero": "[\"\\\\u0000\"]",
|
||||
"string_backslash_doublequotes": "[\"\\\"\"]",
|
||||
"string_comments": "[\"a/*b*/c/*d//e\"]",
|
||||
"string_double_escape_a": "[\"\\\\a\"]",
|
||||
"string_double_escape_n": "[\"\\\\n\"]",
|
||||
"string_escaped_control_character": "[\"\\u0012\"]",
|
||||
"string_escaped_noncharacter": "[\"\\uFFFF\"]",
|
||||
"string_in_array": "[\"asd\"]",
|
||||
"string_in_array_with_leading_space": "[ \"asd\"]",
|
||||
"string_last_surrogates_1_and_2": "[\"\\uDBFF\\uDFFF\"]",
|
||||
"string_nbsp_uescaped": "[\"new\\u00A0line\"]",
|
||||
"string_nonCharacterInUTF-8_U+10FFFF": "[\"\udbff\udfff\"]",
|
||||
"string_nonCharacterInUTF-8_U+FFFF": "[\"\uffff\"]",
|
||||
"string_null_escape": "[\"\\u0000\"]",
|
||||
"string_one-byte-utf-8": "[\"\\u002c\"]",
|
||||
"string_pi": "[\"\u03c0\"]",
|
||||
"string_reservedCharacterInUTF-8_U+1BFFF": "[\"\ud82f\udfff\"]",
|
||||
"string_simple_ascii": "[\"asd \"]",
|
||||
"string_space": "\" \"",
|
||||
"string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF": "[\"\\uD834\\uDd1e\"]",
|
||||
"string_three-byte-utf-8": "[\"\\u0821\"]",
|
||||
"string_two-byte-utf-8": "[\"\\u0123\"]",
|
||||
"string_u+2028_line_sep": "[\"\u2028\"]",
|
||||
"string_u+2029_par_sep": "[\"\u2029\"]",
|
||||
"string_uEscape": "[\"\\u0061\\u30af\\u30EA\\u30b9\"]",
|
||||
"string_uescaped_newline": "[\"new\\u000Aline\"]",
|
||||
"string_unescaped_char_delete": "[\"\u007f\"]",
|
||||
"string_unicode": "[\"\\uA66D\"]",
|
||||
"string_unicodeEscapedBackslash": "[\"\\u005C\"]",
|
||||
"string_unicode_2": "[\"\u2342\u3234\u2342\"]",
|
||||
"string_unicode_U+10FFFE_nonchar": "[\"\\uDBFF\\uDFFE\"]",
|
||||
"string_unicode_U+1FFFE_nonchar": "[\"\\uD83F\\uDFFE\"]",
|
||||
"string_unicode_U+200B_ZERO_WIDTH_SPACE": "[\"\\u200B\"]",
|
||||
"string_unicode_U+2064_invisible_plus": "[\"\\u2064\"]",
|
||||
"string_unicode_U+FDD0_nonchar": "[\"\\uFDD0\"]",
|
||||
"string_unicode_U+FFFE_nonchar": "[\"\\uFFFE\"]",
|
||||
"string_unicode_escaped_double_quote": "[\"\\u0022\"]",
|
||||
"string_utf8": "[\"\u20ac\ud834\udd1e\"]",
|
||||
"string_with_del_character": "[\"a\u007fa\"]",
|
||||
"structure_lonely_false": "false",
|
||||
"structure_lonely_int": "42",
|
||||
"structure_lonely_negative_real": "-0.1",
|
||||
"structure_lonely_null": "null",
|
||||
"structure_lonely_string": "\"asd\"",
|
||||
"structure_lonely_true": "true",
|
||||
"structure_string_empty": "\"\"",
|
||||
"structure_trailing_newline": "[\"a\"]\n",
|
||||
"structure_true_in_array": "[true]",
|
||||
"structure_whitespace_array": " [] "
|
||||
},
|
||||
"negative": {
|
||||
"array_1_true_without_comma": "[1 true]",
|
||||
"array_colon_instead_of_comma": "[\"\": 1]",
|
||||
"array_comma_after_close": "[\"\"],",
|
||||
"array_comma_and_number": "[,1]",
|
||||
"array_double_comma": "[1,,2]",
|
||||
"array_double_extra_comma": "[\"x\",,]",
|
||||
"array_extra_close": "[\"x\"]]",
|
||||
"array_extra_comma": "[\"\",]",
|
||||
"array_incomplete": "[\"x\"",
|
||||
"array_incomplete_invalid_value": "[x",
|
||||
"array_inner_array_no_comma": "[3[4]]",
|
||||
"array_items_separated_by_semicolon": "[1:2]",
|
||||
"array_just_comma": "[,]",
|
||||
"array_just_minus": "[-]",
|
||||
"array_missing_value": "[ , \"\"]",
|
||||
"array_newlines_unclosed": "[\"a\",\n4\n,1,",
|
||||
"array_number_and_comma": "[1,]",
|
||||
"array_number_and_several_commas": "[1,,]",
|
||||
"array_spaces_vertical_tab_formfeed": "[\"\u000ba\"\\f]",
|
||||
"array_star_inside": "[*]",
|
||||
"array_unclosed": "[\"\"",
|
||||
"array_unclosed_trailing_comma": "[1,",
|
||||
"array_unclosed_with_new_lines": "[1,\n1\n,1",
|
||||
"array_unclosed_with_object_inside": "[{}",
|
||||
"incomplete_false": "[fals]",
|
||||
"incomplete_null": "[nul]",
|
||||
"incomplete_true": "[tru]",
|
||||
"number_++": "[++1234]",
|
||||
"number_+1": "[+1]",
|
||||
"number_+Inf": "[+Inf]",
|
||||
"number_-01": "[-01]",
|
||||
"number_-1.0.": "[-1.0.]",
|
||||
"number_-2.": "[-2.]",
|
||||
"number_-NaN": "[-NaN]",
|
||||
"number_.-1": "[.-1]",
|
||||
"number_.2e-3": "[.2e-3]",
|
||||
"number_0.1.2": "[0.1.2]",
|
||||
"number_0.3e+": "[0.3e+]",
|
||||
"number_0.3e": "[0.3e]",
|
||||
"number_0.e1": "[0.e1]",
|
||||
"number_0_capital_E+": "[0E+]",
|
||||
"number_0_capital_E": "[0E]",
|
||||
"number_0e+": "[0e+]",
|
||||
"number_0e": "[0e]",
|
||||
"number_1.0e+": "[1.0e+]",
|
||||
"number_1.0e-": "[1.0e-]",
|
||||
"number_1.0e": "[1.0e]",
|
||||
"number_1_000": "[1 000.0]",
|
||||
"number_1eE2": "[1eE2]",
|
||||
"number_2.e+3": "[2.e+3]",
|
||||
"number_2.e-3": "[2.e-3]",
|
||||
"number_2.e3": "[2.e3]",
|
||||
"number_9.e+": "[9.e+]",
|
||||
"number_Inf": "[Inf]",
|
||||
"number_NaN": "[NaN]",
|
||||
"number_U+FF11_fullwidth_digit_one": "[\uff11]",
|
||||
"number_expression": "[1+2]",
|
||||
"number_hex_1_digit": "[0x1]",
|
||||
"number_hex_2_digits": "[0x42]",
|
||||
"number_infinity": "[Infinity]",
|
||||
"number_invalid+-": "[0e+-1]",
|
||||
"number_invalid-negative-real": "[-123.123foo]",
|
||||
"number_minus_infinity": "[-Infinity]",
|
||||
"number_minus_sign_with_trailing_garbage": "[-foo]",
|
||||
"number_minus_space_1": "[- 1]",
|
||||
"number_neg_int_starting_with_zero": "[-012]",
|
||||
"number_neg_real_without_int_part": "[-.123]",
|
||||
"number_neg_with_garbage_at_end": "[-1x]",
|
||||
"number_real_garbage_after_e": "[1ea]",
|
||||
"number_real_without_fractional_part": "[1.]",
|
||||
"number_starting_with_dot": "[.123]",
|
||||
"number_with_alpha": "[1.2a-3]",
|
||||
"number_with_alpha_char": "[1.8011670033376514H-308]",
|
||||
"number_with_leading_zero": "[012]",
|
||||
"object_bad_value": "[\"x\", truth]",
|
||||
"object_bracket_key": "{[: \"x\"}\n",
|
||||
"object_comma_instead_of_colon": "{\"x\", null}",
|
||||
"object_double_colon": "{\"x\"::\"b\"}",
|
||||
"object_emoji": "{\ud83c\udde8\ud83c\udded}",
|
||||
"object_garbage_at_end": "{\"a\":\"a\" 123}",
|
||||
"object_key_with_single_quotes": "{key: 'value'}",
|
||||
"object_missing_colon": "{\"a\" b}",
|
||||
"object_missing_key": "{:\"b\"}",
|
||||
"object_missing_semicolon": "{\"a\" \"b\"}",
|
||||
"object_missing_value": "{\"a\":",
|
||||
"object_no-colon": "{\"a\"",
|
||||
"object_non_string_key": "{1:1}",
|
||||
"object_non_string_key_but_huge_number_instead": "{9999E9999:1}",
|
||||
"object_repeated_null_null": "{null:null,null:null}",
|
||||
"object_several_trailing_commas": "{\"id\":0,,,,,}",
|
||||
"object_single_quote": "{'a':0}",
|
||||
"object_trailing_comma": "{\"id\":0,}",
|
||||
"object_trailing_comment": "{\"a\":\"b\"}/**/",
|
||||
"object_trailing_comment_open": "{\"a\":\"b\"}/**//",
|
||||
"object_trailing_comment_slash_open": "{\"a\":\"b\"}//",
|
||||
"object_trailing_comment_slash_open_incomplete": "{\"a\":\"b\"}/",
|
||||
"object_two_commas_in_a_row": "{\"a\":\"b\",,\"c\":\"d\"}",
|
||||
"object_unquoted_key": "{a: \"b\"}",
|
||||
"object_unterminated-value": "{\"a\":\"a",
|
||||
"object_with_single_string": "{ \"foo\" : \"bar\", \"a\" }",
|
||||
"object_with_trailing_garbage": "{\"a\":\"b\"}#",
|
||||
"single_space": " ",
|
||||
"string_1_surrogate_then_escape": "[\"\\uD800\\\"]",
|
||||
"string_1_surrogate_then_escape_u": "[\"\\uD800\\u\"]",
|
||||
"string_1_surrogate_then_escape_u1": "[\"\\uD800\\u1\"]",
|
||||
"string_1_surrogate_then_escape_u1x": "[\"\\uD800\\u1x\"]",
|
||||
"string_accentuated_char_no_quotes": "[\u00e9]",
|
||||
"string_backslash_00": "[\"\\\u0000\"]",
|
||||
"string_escape_x": "[\"\\x00\"]",
|
||||
"string_escaped_backslash_bad": "[\"\\\\\\\"]",
|
||||
"string_escaped_ctrl_char_tab": "[\"\\\t\"]",
|
||||
"string_escaped_emoji": "[\"\\\ud83c\udf00\"]",
|
||||
"string_incomplete_escape": "[\"\\\"]",
|
||||
"string_incomplete_escaped_character": "[\"\\u00A\"]",
|
||||
"string_incomplete_surrogate": "[\"\\uD834\\uDd\"]",
|
||||
"string_incomplete_surrogate_escape_invalid": "[\"\\uD800\\uD800\\x\"]",
|
||||
"string_invalid_backslash_esc": "[\"\\a\"]",
|
||||
"string_invalid_unicode_escape": "[\"\\uqqqq\"]",
|
||||
"string_leading_uescaped_thinspace": "[\\u0020\"asd\"]",
|
||||
"string_no_quotes_with_bad_escape": "[\\n]",
|
||||
"string_single_doublequote": "\"",
|
||||
"string_single_quote": "['single quote']",
|
||||
"string_single_string_no_double_quotes": "abc",
|
||||
"string_start_escape_unclosed": "[\"\\",
|
||||
"string_unescaped_ctrl_char": "[\"a\u0000a\"]",
|
||||
"string_unescaped_newline": "[\"new\nline\"]",
|
||||
"string_unescaped_tab": "[\"\t\"]",
|
||||
"string_unicode_CapitalU": "\"\\UA66D\"",
|
||||
"string_with_trailing_garbage": "\"\"x",
|
||||
"structure_U+2060_word_joined": "[\u2060]",
|
||||
"structure_UTF8_BOM_no_data": "\ufeff",
|
||||
"structure_angle_bracket_.": "<.>",
|
||||
"structure_angle_bracket_null": "[<null>]",
|
||||
"structure_array_trailing_garbage": "[1]x",
|
||||
"structure_array_with_extra_array_close": "[1]]",
|
||||
"structure_array_with_unclosed_string": "[\"asd]",
|
||||
"structure_ascii-unicode-identifier": "a\u00e5",
|
||||
"structure_capitalized_True": "[True]",
|
||||
"structure_close_unopened_array": "1]",
|
||||
"structure_comma_instead_of_closing_brace": "{\"x\": true,",
|
||||
"structure_double_array": "[][]",
|
||||
"structure_end_array": "]",
|
||||
"structure_lone-open-bracket": "[",
|
||||
"structure_no_data": "",
|
||||
"structure_null-byte-outside-string": "[\u0000]",
|
||||
"structure_number_with_trailing_garbage": "2@",
|
||||
"structure_object_followed_by_closing_object": "{}}",
|
||||
"structure_object_unclosed_no_value": "{\"\":",
|
||||
"structure_object_with_comment": "{\"a\":/*comment*/\"b\"}",
|
||||
"structure_object_with_trailing_garbage": "{\"a\": true} \"x\"",
|
||||
"structure_open_array_apostrophe": "['",
|
||||
"structure_open_array_comma": "[,",
|
||||
"structure_open_array_open_object": "[{",
|
||||
"structure_open_array_open_string": "[\"a",
|
||||
"structure_open_array_string": "[\"a\"",
|
||||
"structure_open_object": "{",
|
||||
"structure_open_object_close_array": "{]",
|
||||
"structure_open_object_comma": "{,",
|
||||
"structure_open_object_open_array": "{[",
|
||||
"structure_open_object_open_string": "{\"a",
|
||||
"structure_open_object_string_with_apostrophes": "{'a'",
|
||||
"structure_open_open": "[\"\\{[\"\\{[\"\\{[\"\\{",
|
||||
"structure_single_star": "*",
|
||||
"structure_trailing_#": "{\"a\":\"b\"}#{}",
|
||||
"structure_uescaped_LF_before_string": "[\\u000A\"\"]",
|
||||
"structure_unclosed_array": "[1",
|
||||
"structure_unclosed_array_partial_null": "[ false, nul",
|
||||
"structure_unclosed_array_unfinished_false": "[ true, fals",
|
||||
"structure_unclosed_array_unfinished_true": "[ false, tru",
|
||||
"structure_unclosed_object": "{\"asd\":\"asd\"",
|
||||
"structure_unicode-identifier": "\u00e5",
|
||||
"structure_whitespace_U+2060_word_joiner": "[\u2060]",
|
||||
"structure_whitespace_formfeed": "[\f]"
|
||||
},
|
||||
"any": {
|
||||
"number_double_huge_neg_exp": "[123.456e-789]",
|
||||
"number_huge_exp": "[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]",
|
||||
"number_neg_int_huge_exp": "[-1e+9999]",
|
||||
"number_pos_double_huge_exp": "[1.5e+9999]",
|
||||
"number_real_neg_overflow": "[-123123e100000]",
|
||||
"number_real_pos_overflow": "[123123e100000]",
|
||||
"number_real_underflow": "[123e-10000000]",
|
||||
"object_key_lone_2nd_surrogate": "{\"\\uDFAA\":0}",
|
||||
"string_1st_surrogate_but_2nd_missing": "[\"\\uDADA\"]",
|
||||
"string_1st_valid_surrogate_2nd_invalid": "[\"\\uD888\\u1234\"]",
|
||||
"string_incomplete_surrogate_and_escape_valid": "[\"\\uD800\\n\"]",
|
||||
"string_incomplete_surrogate_pair": "[\"\\uDd1ea\"]",
|
||||
"string_incomplete_surrogates_escape_valid": "[\"\\uD800\\uD800\\n\"]",
|
||||
"string_invalid_lonely_surrogate": "[\"\\ud800\"]",
|
||||
"string_invalid_surrogate": "[\"\\ud800abc\"]",
|
||||
"string_inverted_surrogates_U+1D11E": "[\"\\uDd1e\\uD834\"]",
|
||||
"string_lone_second_surrogate": "[\"\\uDFAA\"]",
|
||||
"structure_UTF-8_BOM_empty_object": "\ufeff{}"
|
||||
}
|
||||
}
|
@ -1,6 +1,20 @@
|
||||
import json
|
||||
from coc_string import *
|
||||
|
||||
# from https://stackoverflow.com/questions/14945095/how-to-escape-string-for-generated-c (simplified)
|
||||
def escape_c(s, encoding='ascii'):
|
||||
result = ''
|
||||
for c in s:
|
||||
if not (32 <= ord(c) < 127):
|
||||
result += '\\%03o' % ord(c)
|
||||
elif c == '\\':
|
||||
result += "\\\\"
|
||||
elif c == '"':
|
||||
result += "\\\""
|
||||
else:
|
||||
result += c
|
||||
return '"' + result + '"'
|
||||
|
||||
class str_info:
|
||||
def __init__(self):
|
||||
self.hash = 0
|
||||
@ -91,7 +105,7 @@ class str_build:
|
||||
else:
|
||||
next = "NULL"
|
||||
istr += "be_define_const_str("
|
||||
istr += node + ", " + json.dumps(info.str) + ", "
|
||||
istr += node + ", " + escape_c(info.str) + ", "
|
||||
istr += str(info.hash) + "u, " + str(info.extra) + ", "
|
||||
istr += str(len(info.str)) + ", " + next + ");\n"
|
||||
strings[info.str] = istr
|
||||
@ -104,7 +118,7 @@ class str_build:
|
||||
ostr += "\n/* weak strings */\n"
|
||||
for k in self.str_weak:
|
||||
ostr += "be_define_const_str("
|
||||
ostr += escape_operator(k) + ", " + json.dumps(k) + ", "
|
||||
ostr += escape_operator(k) + ", " + escape_c(k) + ", "
|
||||
ostr += "0u, 0, " + str(len(k)) + ", NULL);\n"
|
||||
|
||||
ostr += "\n"
|
||||
|
@ -72,6 +72,13 @@ void int64_set(int64_t *i64, int32_t high, int32_t low) {
|
||||
}
|
||||
BE_FUNC_CTYPE_DECLARE(int64_set, "", ".ii")
|
||||
|
||||
int64_t* int64_fromu32(bvm *vm, uint32_t low) {
|
||||
int64_t* r64 = (int64_t*)be_malloc(vm, sizeof(int64_t));
|
||||
*r64 = low;
|
||||
return r64;
|
||||
}
|
||||
BE_FUNC_CTYPE_DECLARE(int64_fromu32, "int64", "@i")
|
||||
|
||||
int64_t* int64_add(bvm *vm, int64_t *i64, int64_t *j64) {
|
||||
int64_t* r64 = (int64_t*)be_malloc(vm, sizeof(int64_t));
|
||||
// it's possible that arg j64 is nullptr, since class type does allow NULLPTR to come through.
|
||||
@ -197,6 +204,7 @@ class be_class_int64 (scope: global, name: int64) {
|
||||
init, ctype_func(int64_init)
|
||||
deinit, ctype_func(int64_deinit)
|
||||
set, ctype_func(int64_set)
|
||||
fromu32, static_ctype_func(int64_fromu32)
|
||||
|
||||
tostring, ctype_func(int64_tostring)
|
||||
fromstring, static_ctype_func(int64_fromstring)
|
||||
|
@ -59,6 +59,8 @@ fprint("* Compact form for attributes and clusters")
|
||||
fprint("*")
|
||||
fprint("* Generated content, do not edit")
|
||||
fprint("\\*********************************************************************************/")
|
||||
fprint("#include <stddef.h>")
|
||||
fprint("#include <stdint.h>")
|
||||
fprint()
|
||||
fprint("typedef struct {")
|
||||
fprint(" uint16_t id;")
|
||||
@ -89,7 +91,10 @@ for cl:cl_ids
|
||||
var attr_ids_local = k2l(attr_id_name)
|
||||
|
||||
for attr_id:attr_ids_local
|
||||
fprint(string.format(' { 0x%04X, %i, 0x%02X, "%s" },', attr_id, 0, 0, attributes[attr_id]['attributeName']))
|
||||
var reportable = attributes[attr_id].find('reportable', false)
|
||||
var writable = attributes[attr_id].find('writable', false)
|
||||
var flags = (writable ? 0x01 : 0x00) | (reportable ? 0x02 : 0x00)
|
||||
fprint(string.format(' { 0x%04X, %i, 0x%02X, "%s" },', attr_id, 0, flags, attributes[attr_id]['attributeName']))
|
||||
end
|
||||
fprint(' { 0xFFFF, 0, 0x00, NULL },')
|
||||
fprint("};")
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -69,6 +69,12 @@ static void mc_deinit(bvm *vm, matter_counter_t *c) {
|
||||
}
|
||||
BE_FUNC_CTYPE_DECLARE(mc_deinit, "", "@.")
|
||||
|
||||
// do a unisgned int32 comparison
|
||||
bbool mc_is_greater(uint32_t a, uint32_t b) {
|
||||
return a > b;
|
||||
}
|
||||
BE_FUNC_CTYPE_DECLARE(mc_is_greater, "b", "ii")
|
||||
|
||||
static void mc_reset(matter_counter_t *c, int32_t val) {
|
||||
c->counter = val;
|
||||
c->window.reset();
|
||||
@ -175,7 +181,7 @@ static int mc_tostring(bvm *vm) {
|
||||
#include "be_fixed_be_class_Matter_Counter.h"
|
||||
|
||||
/* @const_object_info_begin
|
||||
class be_class_Matter_Counter (scope: global, name: Matter_Counter) {
|
||||
class be_class_Matter_Counter (scope: global, name: Matter_Counter, strings: weak) {
|
||||
_p, var
|
||||
init, ctype_func(mc_init)
|
||||
deinit, ctype_func(mc_deinit)
|
||||
@ -185,6 +191,8 @@ class be_class_Matter_Counter (scope: global, name: Matter_Counter) {
|
||||
val, ctype_func(mc_val)
|
||||
next, ctype_func(mc_next)
|
||||
validate, ctype_func(mc_validate)
|
||||
|
||||
is_greater, static_ctype_func(mc_is_greater) // compare two numbers as unsigned 32 bits
|
||||
}
|
||||
@const_object_info_end */
|
||||
|
||||
|
@ -25,8 +25,6 @@
|
||||
#include "be_constobj.h"
|
||||
#include "be_mapping.h"
|
||||
|
||||
#include "be_matter_qrcode_min_js.h"
|
||||
|
||||
// Matter logo
|
||||
static const uint8_t MATTER_LOGO[] =
|
||||
"<svg style='vertical-align:middle;' width='24' height='24' xmlns='http://www.w3.org/2000/svg' viewBox='100 100 240 240'>"
|
||||
@ -40,6 +38,7 @@ static const uint8_t MATTER_LOGO[] =
|
||||
|
||||
extern const bclass be_class_Matter_Counter;
|
||||
extern const bclass be_class_Matter_Verhoeff;
|
||||
extern const bclass be_class_Matter_QRCode;
|
||||
|
||||
#include "solidify/solidified_Matter_Module.h"
|
||||
|
||||
@ -80,6 +79,34 @@ const char* matter_get_attribute_name(uint16_t cluster, uint16_t attribute) {
|
||||
}
|
||||
BE_FUNC_CTYPE_DECLARE(matter_get_attribute_name, "s", "ii")
|
||||
|
||||
bbool matter_is_attribute_writable(uint16_t cluster, uint16_t attribute) {
|
||||
for (const matter_cluster_t * cl = matterAllClusters; cl->id != 0xFFFF; cl++) {
|
||||
if (cl->id == cluster) {
|
||||
for (const matter_attribute_t * at = cl->attributes; at->id != 0xFFFF; at++) {
|
||||
if (at->id == attribute) {
|
||||
return (at->flags & 0x01) ? btrue : bfalse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bfalse;
|
||||
}
|
||||
BE_FUNC_CTYPE_DECLARE(matter_is_attribute_writable, "b", "ii")
|
||||
|
||||
bbool matter_is_attribute_reportable(uint16_t cluster, uint16_t attribute) {
|
||||
for (const matter_cluster_t * cl = matterAllClusters; cl->id != 0xFFFF; cl++) {
|
||||
if (cl->id == cluster) {
|
||||
for (const matter_attribute_t * at = cl->attributes; at->id != 0xFFFF; at++) {
|
||||
if (at->id == attribute) {
|
||||
return (at->flags & 0x02) ? btrue : bfalse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bfalse;
|
||||
}
|
||||
BE_FUNC_CTYPE_DECLARE(matter_is_attribute_reportable, "b", "ii")
|
||||
|
||||
const char* matter_get_command_name(uint16_t cluster, uint16_t command) {
|
||||
for (const matter_cluster_t * cl = matterAllClusters; cl->id != 0xFFFF; cl++) {
|
||||
if (cl->id == cluster) {
|
||||
@ -102,15 +129,22 @@ BE_FUNC_CTYPE_DECLARE(matter_get_ip_bytes, "&", "s")
|
||||
#include "solidify/solidified_Matter_inspect.h"
|
||||
|
||||
extern const bclass be_class_Matter_TLV; // need to declare it upfront because of circular reference
|
||||
#include "solidify/solidified_Matter_Path.h"
|
||||
#include "solidify/solidified_Matter_TLV.h"
|
||||
#include "solidify/solidified_Matter_IM_Data.h"
|
||||
#include "solidify/solidified_Matter_UDPServer.h"
|
||||
#include "solidify/solidified_Matter_Expirable.h"
|
||||
#include "solidify/solidified_Matter_Fabric.h"
|
||||
#include "solidify/solidified_Matter_Session.h"
|
||||
#include "solidify/solidified_Matter_Session_Store.h"
|
||||
#include "solidify/solidified_Matter_Commissioning_Data.h"
|
||||
#include "solidify/solidified_Matter_Commissioning.h"
|
||||
#include "solidify/solidified_Matter_Message.h"
|
||||
#include "solidify/solidified_Matter_MessageHandler.h"
|
||||
#include "solidify/solidified_Matter_IM_Message.h"
|
||||
#include "solidify/solidified_Matter_IM_Subscription.h"
|
||||
#include "solidify/solidified_Matter_IM.h"
|
||||
#include "solidify/solidified_Matter_Control_Message.h"
|
||||
#include "solidify/solidified_Matter_Plugin.h"
|
||||
#include "solidify/solidified_Matter_Base38.h"
|
||||
#include "solidify/solidified_Matter_UI.h"
|
||||
@ -118,8 +152,14 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
|
||||
|
||||
#include "../generate/be_matter_certs.h"
|
||||
|
||||
#include "solidify/solidified_Matter_Plugin_core.h"
|
||||
#include "solidify/solidified_Matter_Plugin_Relay.h"
|
||||
#include "solidify/solidified_Matter_Plugin_Root.h"
|
||||
#include "solidify/solidified_Matter_Plugin_Device.h"
|
||||
#include "solidify/solidified_Matter_Plugin_OnOff.h"
|
||||
#include "solidify/solidified_Matter_Plugin_Light0.h"
|
||||
#include "solidify/solidified_Matter_Plugin_Light1.h"
|
||||
#include "solidify/solidified_Matter_Plugin_Light2.h"
|
||||
#include "solidify/solidified_Matter_Plugin_Light3.h"
|
||||
#include "solidify/solidified_Matter_Plugin_Temp_Sensor.h"
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Get a bytes() object of the certificate DAC/PAI_Cert
|
||||
@ -145,9 +185,8 @@ static int matter_CD_FFF1_8000(bvm *vm) { return matter_return_static_bytes(vm,
|
||||
|
||||
/* @const_object_info_begin
|
||||
|
||||
module matter (scope: global) {
|
||||
module matter (scope: global, strings: weak) {
|
||||
_LOGO, comptr(MATTER_LOGO)
|
||||
_QRCODE_MINJS, comptr(QRCODE_MINJS)
|
||||
MATTER_OPTION, int(151) // SetOption151 enables Matter
|
||||
|
||||
Verhoeff, class(be_class_Matter_Verhoeff)
|
||||
@ -158,6 +197,8 @@ module matter (scope: global) {
|
||||
|
||||
get_cluster_name, ctype_func(matter_get_cluster_name)
|
||||
get_attribute_name, ctype_func(matter_get_attribute_name)
|
||||
is_attribute_writable, ctype_func(matter_is_attribute_writable)
|
||||
is_attribute_reportable, ctype_func(matter_is_attribute_reportable)
|
||||
get_command_name, ctype_func(matter_get_command_name)
|
||||
get_opcode_name, ctype_func(matter_get_opcode_name)
|
||||
TLV, class(be_class_Matter_TLV)
|
||||
@ -238,7 +279,12 @@ module matter (scope: global) {
|
||||
UDPPacket_sent, class(be_class_Matter_UDPPacket_sent)
|
||||
UDPServer, class(be_class_Matter_UDPServer)
|
||||
|
||||
// Expirable
|
||||
Expirable, class(be_class_Matter_Expirable)
|
||||
Expirable_list, class(be_class_Matter_Expirable_list)
|
||||
|
||||
// Sessions
|
||||
Fabric, class(be_class_Matter_Fabric)
|
||||
Session, class(be_class_Matter_Session)
|
||||
Session_Store, class(be_class_Matter_Session_Store)
|
||||
|
||||
@ -247,11 +293,23 @@ module matter (scope: global) {
|
||||
MessageHandler, class(be_class_Matter_MessageHandler)
|
||||
|
||||
// Interation Model
|
||||
Response_container, class(be_class_Matter_Response_container)
|
||||
Path, class(be_class_Matter_Path)
|
||||
IM_Status, class(be_class_Matter_IM_Status)
|
||||
IM_InvokeResponse, class(be_class_Matter_IM_InvokeResponse)
|
||||
IM_WriteResponse, class(be_class_Matter_IM_WriteResponse)
|
||||
IM_ReportData, class(be_class_Matter_IM_ReportData)
|
||||
IM_ReportDataSubscribed, class(be_class_Matter_IM_ReportDataSubscribed)
|
||||
IM_SubscribeResponse, class(be_class_Matter_IM_SubscribeResponse)
|
||||
IM_SubscribedHeartbeat, class(be_class_Matter_IM_SubscribedHeartbeat)
|
||||
IM_Subscription, class(be_class_Matter_IM_Subscription)
|
||||
IM_Subscription_Shop, class(be_class_Matter_IM_Subscription_Shop)
|
||||
IM, class(be_class_Matter_IM)
|
||||
Plugin_core, class(be_class_Matter_Plugin_core)
|
||||
Control_Message, class(be_class_Matter_Control_Message)
|
||||
UI, class(be_class_Matter_UI)
|
||||
|
||||
// QR Code
|
||||
QRCode, class(be_class_Matter_QRCode)
|
||||
|
||||
// Base38 for QR Code
|
||||
Base38, class(be_class_Matter_Base38)
|
||||
|
||||
@ -265,11 +323,17 @@ module matter (scope: global) {
|
||||
DAC_Cert_FFF1_8000, func(matter_DAC_Cert_FFF1_8000)
|
||||
DAC_Pub_FFF1_8000, func(matter_DAC_Pub_FFF1_8000)
|
||||
DAC_Priv_FFF1_8000, func(matter_DAC_Priv_FFF1_8000)
|
||||
CD_FFF1_8000, func(matter_CD_FFF1_8000) // Certification Declaration
|
||||
CD_FFF1_8000, func(matter_CD_FFF1_8000) // Certification Declaration
|
||||
|
||||
// Plugins
|
||||
Plugin_core, class(be_class_Matter_Plugin_core) // Generic behavior common to all devices
|
||||
Plugin_Relay, class(be_class_Matter_Plugin_Relay) // Relay behavior (OnOff)
|
||||
Plugin_Root, class(be_class_Matter_Plugin_Root) // Generic behavior common to all devices
|
||||
Plugin_Device, class(be_class_Matter_Plugin_Device) // Generic device (abstract)
|
||||
Plugin_OnOff, class(be_class_Matter_Plugin_OnOff) // Relay/Light behavior (OnOff)
|
||||
Plugin_Light0, class(be_class_Matter_Plugin_Light0) // OnOff Light
|
||||
Plugin_Light1, class(be_class_Matter_Plugin_Light1) // Dimmable Light
|
||||
Plugin_Light2, class(be_class_Matter_Plugin_Light2) // Color Temperature Light
|
||||
Plugin_Light3, class(be_class_Matter_Plugin_Light3) // Extended Color Light
|
||||
Plugin_Temp_Sensor, class(be_class_Matter_Plugin_Temp_Sensor) // Temperature Sensor
|
||||
}
|
||||
|
||||
@const_object_info_end */
|
||||
|
107
lib/libesp32/berry_matter/src/be_matter_qrcode.c
Normal file
107
lib/libesp32/berry_matter/src/be_matter_qrcode.c
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
be_matter_qrcode.cpp - implements Matter QRCode encoder as UTF8
|
||||
|
||||
Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "be_constobj.h"
|
||||
#include "be_mapping.h"
|
||||
#include "be_mem.h"
|
||||
#include "be_exec.h"
|
||||
#include "qrcodegen.h"
|
||||
|
||||
/******************************************************************************************************\
|
||||
*
|
||||
*
|
||||
*
|
||||
\******************************************************************************************************/
|
||||
|
||||
// `matter.QRCode.encode_str(content:string) -> map`
|
||||
//
|
||||
int32_t qr_encode_str(bvm *vm) {
|
||||
int32_t argc = be_top(vm);
|
||||
if (argc >= 1 && be_isstring(vm, 1)) {
|
||||
const char * data_str = be_tostring(vm, 1);
|
||||
size_t data_len = strlen(data_str);
|
||||
|
||||
int32_t qr_version = qrcodegen_getMinFitVersion(qrcodegen_Ecc_MEDIUM, data_len);
|
||||
if (qr_version <= 0) { be_return_nil(vm); }
|
||||
int32_t qr_size = qrcodegen_version2size(qr_version);
|
||||
if (qr_size <= 0) { be_return_nil(vm); }
|
||||
|
||||
uint8_t * qr0 = (uint8_t *) be_os_malloc(qrcodegen_BUFFER_LEN_FOR_VERSION(qr_version));
|
||||
if (!qr0) { be_throw(vm, BE_MALLOC_FAIL); }
|
||||
uint8_t * data_tmp = (uint8_t *) be_os_malloc(qrcodegen_BUFFER_LEN_FOR_VERSION(qr_version));
|
||||
if (!qr0) { be_os_free(qr0); be_throw(vm, BE_MALLOC_FAIL); }
|
||||
|
||||
bool ok = qrcodegen_encodeText(data_str, data_tmp, qr0, qrcodegen_Ecc_MEDIUM, qr_version, qr_version, qrcodegen_Mask_AUTO, true);
|
||||
|
||||
if(!ok) {
|
||||
be_os_free(qr0);
|
||||
be_os_free(data_tmp);
|
||||
be_return_nil(vm);
|
||||
}
|
||||
|
||||
qr_size = qrcodegen_getSize(qr0);
|
||||
size_t len = qr_size * qr_size;
|
||||
|
||||
be_newobject(vm, "map");
|
||||
be_map_insert_int(vm, "size", qr_size);
|
||||
be_map_insert_int(vm, "version", qr_version);
|
||||
|
||||
be_pushstring(vm, "bitmap");
|
||||
be_newobject(vm, "list");
|
||||
|
||||
for (uint32_t i = 0; i < qr_size; i++) {
|
||||
char line[qr_size];
|
||||
|
||||
for (uint32_t j = 0; j < qr_size; j++) {
|
||||
line[j] = qrcodegen_getModule(qr0, i, j) ? '*' : ' ';
|
||||
}
|
||||
|
||||
be_pushnstring(vm, line, qr_size);
|
||||
be_data_push(vm, -2);
|
||||
be_pop(vm, 1);
|
||||
}
|
||||
|
||||
be_pop(vm, 1);
|
||||
be_data_insert(vm, -3);
|
||||
be_pop(vm, 2);
|
||||
|
||||
be_pop(vm, 1);
|
||||
|
||||
be_os_free(qr0);
|
||||
be_os_free(data_tmp);
|
||||
|
||||
be_return(vm);
|
||||
}
|
||||
be_raise(vm, "type_error", NULL);
|
||||
}
|
||||
|
||||
#include "be_fixed_be_class_Matter_QRCode.h"
|
||||
|
||||
/* @const_object_info_begin
|
||||
class be_class_Matter_QRCode (scope: global, name: Matter_QRCode, strings: weak) {
|
||||
encode_str, static_func(qr_encode_str)
|
||||
|
||||
// UTF8 basic blocs for QR Codes
|
||||
// empty, str(" ")
|
||||
// lowhalf, str("\342\226\204")
|
||||
// uphalf, str("\342\226\200")
|
||||
// full, str("\342\226\210")
|
||||
}
|
||||
@const_object_info_end */
|
File diff suppressed because one or more lines are too long
@ -85,7 +85,7 @@ BE_FUNC_CTYPE_DECLARE(vh_validate, "b", "s")
|
||||
#include "be_fixed_be_class_Matter_Verhoeff.h"
|
||||
|
||||
/* @const_object_info_begin
|
||||
class be_class_Matter_Verhoeff (scope: global, name: Matter_Verhoeff) {
|
||||
class be_class_Matter_Verhoeff (scope: global, name: Matter_Verhoeff, strings: weak) {
|
||||
checksum, static_ctype_func(vh_checksum)
|
||||
validate, static_ctype_func(vh_validate)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Matter_Commissioning.be - suppport for Matter Commissioning process
|
||||
# Matter_Commissioning.be - suppport for Matter Commissioning process PASE and CASE
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
@ -35,43 +35,34 @@ class Matter_Commisioning_Context
|
||||
|
||||
var responder # reference to the caller, sending packets
|
||||
var device # root device object
|
||||
var spake
|
||||
var future_initiator_session_id
|
||||
var future_local_session_id
|
||||
# used by TT hash
|
||||
var PBKDFParamRequest, PBKDFParamResponse
|
||||
# PAKE
|
||||
var y # 32 bytes random known only by verifier
|
||||
var pA, pB, cA, cB
|
||||
var Ke
|
||||
# CASE
|
||||
var ResponderEph_priv, ResponderEph_pub
|
||||
var initiatorEph_pub
|
||||
# Session data
|
||||
var session_timestamp
|
||||
var I2RKey, R2IKey, AttestationChallenge
|
||||
# is commissioning window open
|
||||
var window_open
|
||||
|
||||
|
||||
def init(responder)
|
||||
import crypto
|
||||
self.responder = responder
|
||||
self.device = responder.device
|
||||
# generate y once
|
||||
self.y = crypto.random(32)
|
||||
end
|
||||
|
||||
self.window_open = true # auto-commissioning for now
|
||||
#############################################################
|
||||
def add_session(local_session_id, initiator_session_id, i2r, r2i, ac)
|
||||
import string
|
||||
# create session object
|
||||
tasmota.log(string.format("MTR: add_session local_session_id=%i initiator_session_id=%i", local_session_id, initiator_session_id), 3)
|
||||
|
||||
var session = self.device.sessions.create_session(local_session_id, initiator_session_id)
|
||||
session.set_keys(i2r, r2i, ac)
|
||||
end
|
||||
|
||||
def process_incoming(msg)
|
||||
#
|
||||
if !self.window_open
|
||||
if !self.device.is_commissioning_open() && msg.opcode >= 0x20 && msg.opcode <= 0x24
|
||||
tasmota.log("MTR: commissioning not open", 2)
|
||||
return false
|
||||
end
|
||||
|
||||
tasmota.log("MTR: received message " + matter.inspect(msg), 3)
|
||||
if msg.opcode == 0x20
|
||||
if msg.opcode == 0x10
|
||||
# don't need to do anything, the message is acked already before this call
|
||||
elif msg.opcode == 0x20
|
||||
return self.parse_PBKDFParamRequest(msg)
|
||||
elif msg.opcode == 0x22
|
||||
return self.parse_Pake1(msg)
|
||||
@ -81,28 +72,68 @@ class Matter_Commisioning_Context
|
||||
return self.parse_Sigma1(msg)
|
||||
elif msg.opcode == 0x32
|
||||
return self.parse_Sigma3(msg)
|
||||
elif msg.opcode == 0x40
|
||||
return self.parse_StatusReport(msg)
|
||||
else
|
||||
import string
|
||||
tasmota.log(string.format("MTR: >????????? Unknown OpCode (secure channel) %02X", msg.opcode), 2)
|
||||
return false
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
#################################################################################
|
||||
# send_status_report
|
||||
#
|
||||
# send a StatusReport message (unencrypted)
|
||||
#
|
||||
# Usage:
|
||||
# # StatusReport(GeneralCode: SUCCESS, ProtocolId: SECURE_CHANNEL, ProtocolCode: SESSION_ESTABLISHMENT_SUCCESS)
|
||||
# var raw = send_status_report(0x00, 0x0000, 0x0000)
|
||||
# self.responder.send_response(raw, msg.remote_ip, msg.remote_port, nil)
|
||||
def send_status_report(msg, general_code, protocol_id, protocol_code, reliable)
|
||||
# now package the response message
|
||||
var resp = msg.build_response(0x40 #-StatusReport-#, reliable)
|
||||
|
||||
var status_raw = bytes()
|
||||
status_raw.add(general_code, 2)
|
||||
status_raw.add(protocol_id, 4)
|
||||
status_raw.add(protocol_code, 4)
|
||||
|
||||
var raw = resp.encode_frame(status_raw)
|
||||
|
||||
self.responder.send_response_frame(resp)
|
||||
end
|
||||
|
||||
def parse_PBKDFParamRequest(msg)
|
||||
import crypto
|
||||
import string
|
||||
var session = msg.session
|
||||
# sanity checks
|
||||
if msg.opcode != 0x20 || msg.local_session_id != 0 || msg.protocol_id != 0
|
||||
raise "protocol_error", "invalid PBKDFParamRequest message"
|
||||
tasmota.log("MTR: invalid PBKDFParamRequest message", 2)
|
||||
tasmota.log("MTR: StatusReport(General Code: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: INVALID_PARAMETER)", 2)
|
||||
var raw = self.send_status_report(msg, 0x01, 0x0000, 0x0002, false)
|
||||
return false
|
||||
end
|
||||
var pbkdfparamreq = matter.PBKDFParamRequest().parse(msg.raw, msg.app_payload_idx)
|
||||
msg.session.set_mode(matter.Session.__PASE)
|
||||
msg.session.set_mode_PASE()
|
||||
|
||||
self.PBKDFParamRequest = msg.raw[msg.app_payload_idx..]
|
||||
session.__Msg1 = msg.raw[msg.app_payload_idx..]
|
||||
|
||||
# sanity check for PBKDFParamRequest
|
||||
if pbkdfparamreq.passcodeId != 0 raise "protocol_error", "non-zero passcode id" end
|
||||
if pbkdfparamreq.passcodeId != 0
|
||||
tasmota.log("MTR: non-zero passcode id", 2)
|
||||
tasmota.log("MTR: StatusReport(General Code: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: INVALID_PARAMETER)", 2)
|
||||
var raw = self.send_status_report(msg, 0x01, 0x0000, 0x0002, false)
|
||||
return false
|
||||
end
|
||||
|
||||
# record the initiator_session_id
|
||||
self.future_initiator_session_id = pbkdfparamreq.initiator_session_id
|
||||
self.future_local_session_id = self.device.sessions.gen_local_session_id()
|
||||
session.__future_initiator_session_id = pbkdfparamreq.initiator_session_id
|
||||
session.__future_local_session_id = self.device.sessions.gen_local_session_id()
|
||||
tasmota.log(string.format("MTR: +Session (%6i) from '[%s]:%i'", session.__future_local_session_id, msg.remote_ip, msg.remote_port), 2)
|
||||
|
||||
# prepare response
|
||||
var pbkdfparamresp = matter.PBKDFParamResponse()
|
||||
@ -110,159 +141,167 @@ class Matter_Commisioning_Context
|
||||
pbkdfparamresp.initiatorRandom = pbkdfparamreq.initiatorRandom
|
||||
# generate 32 bytes random
|
||||
pbkdfparamresp.responderRandom = crypto.random(32)
|
||||
pbkdfparamresp.responderSessionId = self.future_local_session_id
|
||||
pbkdfparamresp.pbkdf_parameters_salt = self.device.salt
|
||||
pbkdfparamresp.pbkdf_parameters_iterations = self.device.iterations
|
||||
tasmota.log("MTR: pbkdfparamresp: " + str(matter.inspect(pbkdfparamresp)), 3)
|
||||
var pbkdfparamresp_raw = pbkdfparamresp.encode()
|
||||
tasmota.log("MTR: pbkdfparamresp_raw: " + pbkdfparamresp_raw.tohex(), 3)
|
||||
pbkdfparamresp.responderSessionId = session.__future_local_session_id
|
||||
pbkdfparamresp.pbkdf_parameters_salt = self.device.commissioning_salt
|
||||
pbkdfparamresp.pbkdf_parameters_iterations = self.device.commissioning_iterations
|
||||
tasmota.log("MTR: pbkdfparamresp: " + str(matter.inspect(pbkdfparamresp)), 4)
|
||||
var pbkdfparamresp_raw = pbkdfparamresp.tlv2raw()
|
||||
tasmota.log("MTR: pbkdfparamresp_raw: " + pbkdfparamresp_raw.tohex(), 4)
|
||||
|
||||
self.PBKDFParamResponse = pbkdfparamresp_raw
|
||||
session.__Msg2 = pbkdfparamresp_raw
|
||||
|
||||
var resp = msg.build_response(0x21 #-PBKDR Response-#, true)
|
||||
var raw = resp.encode(pbkdfparamresp_raw)
|
||||
var raw = resp.encode_frame(pbkdfparamresp_raw)
|
||||
|
||||
self.responder.send_response(raw, msg.remote_ip, msg.remote_port, resp.message_counter)
|
||||
self.responder.send_response_frame(resp)
|
||||
return true
|
||||
end
|
||||
|
||||
def parse_Pake1(msg)
|
||||
import crypto
|
||||
var session = msg.session
|
||||
# sanity checks
|
||||
if msg.opcode != 0x22 || msg.local_session_id != 0 || msg.protocol_id != 0
|
||||
raise "protocol_error", "invalid Pake1 message"
|
||||
tasmota.log("MTR: invalid Pake1 message", 2)
|
||||
tasmota.log("MTR: StatusReport(General Code: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: INVALID_PARAMETER)", 2)
|
||||
var raw = self.send_status_report(msg, 0x01, 0x0000, 0x0002, false)
|
||||
return false
|
||||
end
|
||||
var pake1 = matter.Pake1().parse(msg.raw, msg.app_payload_idx)
|
||||
|
||||
self.pA = pake1.pA
|
||||
tasmota.log("MTR: received pA=" + self.pA.tohex(), 3)
|
||||
|
||||
var pA = pake1.pA
|
||||
# tasmota.log("MTR: received pA=" + pA.tohex(), 4)
|
||||
|
||||
tasmota.log("MTR: spake: " + matter.inspect(self.spake), 3)
|
||||
# instanciate SPAKE
|
||||
self.spake = crypto.SPAKE2P_Matter(self.device.w0, self.device.w1, self.device.L)
|
||||
# for testing purpose, we don't send `w1` to make sure
|
||||
var spake = crypto.SPAKE2P_Matter(self.device.commissioning_w0, nil, self.device.commissioning_L)
|
||||
|
||||
# generate `y` nonce (not persisted)
|
||||
var y = crypto.random(32) # 32 bytes random known only by verifier
|
||||
|
||||
# compute pB
|
||||
self.spake.compute_pB(self.y)
|
||||
self.pB = self.spake.pB
|
||||
tasmota.log("MTR: y=" + self.y.tohex(), 3)
|
||||
tasmota.log("MTR: pb=" + self.pB.tohex(), 3)
|
||||
spake.compute_pB(y)
|
||||
# tasmota.log("MTR: y=" + y.tohex(), 4)
|
||||
# tasmota.log("MTR: pb=" + spake.pB.tohex(), 4)
|
||||
|
||||
# compute ZV
|
||||
self.spake.compute_ZV_verifier(self.pA)
|
||||
tasmota.log("MTR: Z=" + self.spake.Z.tohex(), 3)
|
||||
tasmota.log("MTR: V=" + self.spake.V.tohex(), 3)
|
||||
spake.compute_ZV_verifier(pA)
|
||||
# tasmota.log("MTR: Z=" + spake.Z.tohex(), 4)
|
||||
# tasmota.log("MTR: V=" + spake.V.tohex(), 4)
|
||||
|
||||
var context = crypto.SHA256()
|
||||
context.update(bytes().fromstring(self.Matter_Context_Prefix))
|
||||
context.update(self.PBKDFParamRequest)
|
||||
context.update(self.PBKDFParamResponse)
|
||||
context.update(session.__Msg1)
|
||||
context.update(session.__Msg2)
|
||||
var context_hash = context.out()
|
||||
|
||||
tasmota.log("MTR: Context=" + context_hash.tohex(), 3)
|
||||
# tasmota.log("MTR: Context=" + context_hash.tohex(), 4)
|
||||
|
||||
# add pA
|
||||
self.spake.pA = self.pA
|
||||
spake.pA = pA
|
||||
|
||||
self.spake.set_context(context_hash)
|
||||
self.spake.compute_TT_hash(true) # `true` to indicate it's Matter variant to SPAKE2+
|
||||
spake.set_context(context_hash)
|
||||
spake.compute_TT_hash(true) # `true` to indicate it's Matter variant to SPAKE2+
|
||||
|
||||
tasmota.log("MTR: ------------------------------", 3)
|
||||
tasmota.log("MTR: Context = " + self.spake.Context.tohex(), 3)
|
||||
tasmota.log("MTR: A = " + self.spake.A.tohex(), 3)
|
||||
tasmota.log("MTR: B = " + self.spake.B.tohex(), 3)
|
||||
tasmota.log("MTR: M = " + self.spake.M.tohex(), 3)
|
||||
tasmota.log("MTR: N = " + self.spake.N.tohex(), 3)
|
||||
tasmota.log("MTR: pA = " + self.spake.pA.tohex(), 3)
|
||||
tasmota.log("MTR: pB = " + self.spake.pB.tohex(), 3)
|
||||
tasmota.log("MTR: Z = " + self.spake.Z.tohex(), 3)
|
||||
tasmota.log("MTR: V = " + self.spake.V.tohex(), 3)
|
||||
tasmota.log("MTR: w0 = " + self.spake.w0.tohex(), 3)
|
||||
tasmota.log("MTR: ------------------------------", 3)
|
||||
# tasmota.log("MTR: ------------------------------", 4)
|
||||
# tasmota.log("MTR: Context = " + spake.Context.tohex(), 4)
|
||||
# tasmota.log("MTR: M = " + spake.M.tohex(), 4)
|
||||
# tasmota.log("MTR: N = " + spake.N.tohex(), 4)
|
||||
# tasmota.log("MTR: pA = " + spake.pA.tohex(), 4)
|
||||
# tasmota.log("MTR: pB = " + spake.pB.tohex(), 4)
|
||||
# tasmota.log("MTR: Z = " + spake.Z.tohex(), 4)
|
||||
# tasmota.log("MTR: V = " + spake.V.tohex(), 4)
|
||||
# tasmota.log("MTR: w0 = " + spake.w0.tohex(), 4)
|
||||
# tasmota.log("MTR: ------------------------------", 4)
|
||||
|
||||
tasmota.log("MTR: Kmain =" + self.spake.Kmain.tohex(), 3)
|
||||
# tasmota.log("MTR: Kmain =" + spake.Kmain.tohex(), 4)
|
||||
|
||||
tasmota.log("MTR: KcA =" + self.spake.KcA.tohex(), 3)
|
||||
tasmota.log("MTR: KcB =" + self.spake.KcB.tohex(), 3)
|
||||
tasmota.log("MTR: K_shared=" + self.spake.K_shared.tohex(), 3)
|
||||
tasmota.log("MTR: Ke =" + self.spake.Ke.tohex(), 3)
|
||||
self.cB = self.spake.cB
|
||||
self.Ke = self.spake.Ke
|
||||
tasmota.log("MTR: cB=" + self.cB.tohex(), 3)
|
||||
# tasmota.log("MTR: KcA =" + spake.KcA.tohex(), 4)
|
||||
# tasmota.log("MTR: KcB =" + spake.KcB.tohex(), 4)
|
||||
# tasmota.log("MTR: K_shared=" + spake.K_shared.tohex(), 4)
|
||||
# tasmota.log("MTR: Ke =" + spake.Ke.tohex(), 4)
|
||||
# tasmota.log("MTR: cB=" + spake.cB.tohex(), 4)
|
||||
|
||||
var pake2 = matter.Pake2()
|
||||
pake2.pB = self.pB
|
||||
pake2.cB = self.cB
|
||||
tasmota.log("MTR: pake2: " + matter.inspect(pake2), 3)
|
||||
var pake2_raw = pake2.encode()
|
||||
tasmota.log("MTR: pake2_raw: " + pake2_raw.tohex(), 3)
|
||||
pake2.pB = spake.pB
|
||||
pake2.cB = spake.cB
|
||||
# tasmota.log("MTR: pake2: " + matter.inspect(pake2), 4)
|
||||
var pake2_raw = pake2.tlv2raw()
|
||||
# tasmota.log("MTR: pake2_raw: " + pake2_raw.tohex(), 4)
|
||||
|
||||
session.__spake_cA = spake.cA
|
||||
session.__spake_Ke = spake.Ke
|
||||
|
||||
# now package the response message
|
||||
var resp = msg.build_response(0x23 #-pake-2-#, true) # no reliable flag
|
||||
var raw = resp.encode(pake2_raw)
|
||||
var raw = resp.encode_frame(pake2_raw)
|
||||
|
||||
self.responder.send_response(raw, msg.remote_ip, msg.remote_port, resp.message_counter)
|
||||
self.responder.send_response_frame(resp)
|
||||
return true
|
||||
end
|
||||
|
||||
def parse_Pake3(msg)
|
||||
import crypto
|
||||
var session = msg.session
|
||||
# sanity checks
|
||||
if msg.opcode != 0x24 || msg.local_session_id != 0 || msg.protocol_id != 0
|
||||
raise "protocol_error", "invalid Pake3 message"
|
||||
tasmota.log("MTR: invalid Pake3 message", 2)
|
||||
tasmota.log("MTR: StatusReport(General Code: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: INVALID_PARAMETER)", 2)
|
||||
var raw = self.send_status_report(msg, 0x01, 0x0000, 0x0002, false)
|
||||
return false
|
||||
end
|
||||
var pake3 = matter.Pake3().parse(msg.raw, msg.app_payload_idx)
|
||||
|
||||
self.cA = pake3.cA
|
||||
tasmota.log("MTR: received cA=" + self.cA.tohex(), 3)
|
||||
var cA = pake3.cA
|
||||
# tasmota.log("MTR: received cA=" + cA.tohex(), 4)
|
||||
|
||||
# check the value against computed
|
||||
if self.cA != self.spake.cA raise "protocol_error", "invalid cA received" end
|
||||
if cA != session.__spake_cA
|
||||
tasmota.log("MTR: invalid cA received", 2)
|
||||
tasmota.log("MTR: StatusReport(General Code: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: INVALID_PARAMETER)", 2)
|
||||
var raw = self.send_status_report(msg, 0x01, 0x0000, 0x0002, false)
|
||||
return false
|
||||
end
|
||||
|
||||
# send PakeFinished and compute session key
|
||||
self.session_timestamp = tasmota.rtc()['utc']
|
||||
var session_keys = crypto.HKDF_SHA256().derive(self.Ke, bytes(), bytes().fromstring(self.SEKeys_Info), 48)
|
||||
self.I2RKey = session_keys[0..15]
|
||||
self.R2IKey = session_keys[16..31]
|
||||
self.AttestationChallenge = session_keys[32..47]
|
||||
var created = tasmota.rtc()['utc']
|
||||
var session_keys = crypto.HKDF_SHA256().derive(session.__spake_Ke, bytes(), bytes().fromstring(self.SEKeys_Info), 48)
|
||||
var I2RKey = session_keys[0..15]
|
||||
var R2IKey = session_keys[16..31]
|
||||
var AttestationChallenge = session_keys[32..47]
|
||||
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: session_keys=" + session_keys.tohex(), 3)
|
||||
tasmota.log("MTR: I2RKey =" + self.I2RKey.tohex(), 3)
|
||||
tasmota.log("MTR: R2IKey =" + self.R2IKey.tohex(), 3)
|
||||
tasmota.log("MTR: AC =" + self.AttestationChallenge.tohex(), 3)
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
# tasmota.log("MTR: ******************************", 4)
|
||||
# tasmota.log("MTR: session_keys=" + session_keys.tohex(), 4)
|
||||
# tasmota.log("MTR: I2RKey =" + I2RKey.tohex(), 4)
|
||||
# tasmota.log("MTR: R2IKey =" + R2IKey.tohex(), 4)
|
||||
# tasmota.log("MTR: AC =" + AttestationChallenge.tohex(), 4)
|
||||
# tasmota.log("MTR: ******************************", 4)
|
||||
|
||||
# now package the response message
|
||||
var resp = msg.build_response(0x40 #-StatusReport-#, false) # no reliable flag
|
||||
# StatusReport(GeneralCode: SUCCESS, ProtocolId: SECURE_CHANNEL, ProtocolCode: SESSION_ESTABLISHMENT_SUCCESS)
|
||||
var raw = self.send_status_report(msg, 0x00, 0x0000, 0x0000, false)
|
||||
|
||||
var status_raw = bytes()
|
||||
status_raw.add(0x00, 2) # GeneralCode = SUCCESS
|
||||
status_raw.add(0x0000, 4) # ProtocolID = 0 (PROTOCOL_ID_SECURE_CHANNEL)
|
||||
status_raw.add(0x0000, 4) # ProtocolCode = 0 (SESSION_ESTABLISHMENT_SUCCESS)
|
||||
|
||||
var raw = resp.encode(status_raw)
|
||||
|
||||
self.responder.send_response(raw, msg.remote_ip, msg.remote_port, nil)
|
||||
self.responder.add_session(self.future_local_session_id, self.future_initiator_session_id, self.I2RKey, self.R2IKey, self.AttestationChallenge, self.session_timestamp)
|
||||
self.add_session(session.__future_local_session_id, session.__future_initiator_session_id, I2RKey, R2IKey, AttestationChallenge, created)
|
||||
return true
|
||||
end
|
||||
|
||||
def find_session_by_destination_id(destinationId, initiatorRandom)
|
||||
def find_fabric_by_destination_id(destinationId, initiatorRandom)
|
||||
import crypto
|
||||
# Validate Sigma1 Destination ID, p.162
|
||||
# traverse all existing sessions
|
||||
# traverse all existing fabrics
|
||||
tasmota.log("MTR: SEARCHING: destinationId=" + destinationId.tohex(), 3)
|
||||
for session:self.device.sessions.sessions
|
||||
if session.noc == nil || session.fabric == nil || session.deviceid == nil continue end
|
||||
for fabric : self.device.sessions.fabrics
|
||||
if fabric.noc == nil || fabric.fabric_id == nil || fabric.device_id == nil continue end
|
||||
# compute candidateDestinationId, Section 4.13.2.4.1, “Destination Identifier”
|
||||
var destinationMessage = initiatorRandom + session.get_ca_pub() + session.get_fabric() + session.get_deviceid()
|
||||
var key = session.get_ipk_group_key()
|
||||
var destinationMessage = initiatorRandom + fabric.get_ca_pub() + fabric.fabric_id + fabric.device_id
|
||||
var key = fabric.get_ipk_group_key()
|
||||
tasmota.log("MTR: SIGMA1: destinationMessage=" + destinationMessage.tohex(), 3)
|
||||
tasmota.log("MTR: SIGMA1: key_ipk=" + key.tohex(), 3)
|
||||
tasmota.log("MTR: SIGMA1: key_ipk=" + key.tohex(), 4)
|
||||
var h = crypto.HMAC_SHA256(key)
|
||||
h.update(destinationMessage)
|
||||
var candidateDestinationId = h.out()
|
||||
tasmota.log("MTR: SIGMA1: candidateDestinationId=" + candidateDestinationId.tohex(), 3)
|
||||
if candidateDestinationId == destinationId
|
||||
return session
|
||||
return fabric
|
||||
end
|
||||
end
|
||||
return nil
|
||||
@ -270,130 +309,177 @@ class Matter_Commisioning_Context
|
||||
|
||||
def parse_Sigma1(msg)
|
||||
import crypto
|
||||
import string
|
||||
var session = msg.session
|
||||
# sanity checks
|
||||
if msg.opcode != 0x30 || msg.local_session_id != 0 || msg.protocol_id != 0
|
||||
raise "protocol_error", "invalid Pake1 message"
|
||||
# tasmota.log("MTR: invalid Sigma1 message", 2)
|
||||
tasmota.log("MTR: StatusReport(General Code: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: INVALID_PARAMETER)", 2)
|
||||
var raw = self.send_status_report(msg, 0x01, 0x0000, 0x0002, false)
|
||||
return false
|
||||
end
|
||||
var sigma1 = matter.Sigma1().parse(msg.raw, msg.app_payload_idx)
|
||||
tasmota.log(string.format("MTR: sigma1=%s", matter.inspect(sigma1)), 4)
|
||||
|
||||
self.initiatorEph_pub = sigma1.initiatorEphPubKey
|
||||
session.__initiator_pub = sigma1.initiatorEphPubKey
|
||||
|
||||
# find session
|
||||
var is_resumption = (sigma1.resumptionID != nil && sigma1.initiatorResumeMIC != nil)
|
||||
tasmota.log(string.format("MTR: is_resumption=%i", is_resumption ? 1 : 0), 4)
|
||||
# TODO disable resumption until fixed
|
||||
is_resumption = false
|
||||
|
||||
# Check that it's a resumption
|
||||
var session
|
||||
var session_resumption
|
||||
if is_resumption
|
||||
session = self.device.sessions.find_session_by_resumption_id(sigma1.resumptionID)
|
||||
else
|
||||
session = self.find_session_by_destination_id(sigma1.destinationId, sigma1.initiatorRandom)
|
||||
session_resumption = self.device.sessions.find_session_by_resumption_id(sigma1.resumptionID)
|
||||
tasmota.log(string.format("MTR: session_resumption found session=%s session_resumption=%s", matter.inspect(session), matter.inspect(session_resumption)), 4)
|
||||
if session_resumption == nil || session_resumption._fabric == nil
|
||||
is_resumption = false
|
||||
end
|
||||
end
|
||||
if session == nil raise "valuer_error", "StatusReport(GeneralCode: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: NO_SHARED_TRUST_ROOTS)" end
|
||||
session.source_node_id = msg.source_node_id
|
||||
session.set_mode(matter.Session.__CASE)
|
||||
|
||||
if msg.session
|
||||
self.device.sessions.remove_session(msg.session) # drop the temporary session that was created
|
||||
end
|
||||
msg.session = session
|
||||
session._future_initiator_session_id = sigma1.initiator_session_id # update initiator_session_id
|
||||
session._future_local_session_id = self.device.sessions.gen_local_session_id()
|
||||
self.future_local_session_id = session._future_local_session_id
|
||||
|
||||
|
||||
|
||||
# Check that it's a resumption
|
||||
if is_resumption && session.shared_secret != nil
|
||||
if is_resumption
|
||||
# Resumption p.169
|
||||
var s1rk_salt = sigma1.initiatorRandom + sigma1.resumptionID
|
||||
var s1rk_info = bytes().fromstring("Sigma1_Resume")
|
||||
var s1rk = crypto.HKDF_SHA256().derive(session.shared_secret, s1rk_salt, s1rk_info, 16)
|
||||
var s1rk = crypto.HKDF_SHA256().derive(session_resumption.shared_secret, s1rk_salt, s1rk_info, 16)
|
||||
|
||||
var Resume1MIC_Nonce = bytes().fromstring("NCASE_SigmaR1")
|
||||
var Resume1MIC_Nonce = bytes().fromstring("NCASE_SigmaS1")
|
||||
var encrypted = sigma1.initiatorResumeMIC[0..-17]
|
||||
var tag = sigma1.initiatorResumeMIC[-16..]
|
||||
var ec = crypto.AES_CCM(s1rk, Resume1MIC_Nonce, bytes(), size(encrypted), 16)
|
||||
var Resume1MICPayload = ec.decrypt(encrypted)
|
||||
var decrypted_tag = ec.tag()
|
||||
|
||||
tasmota.log("****************************************", 3)
|
||||
tasmota.log("MTR: * s1rk = " + s1rk.tohex(), 3)
|
||||
tasmota.log("MTR: * tag = " + tag.tohex(), 3)
|
||||
tasmota.log("MTR: * Resume1MICPayload = " + Resume1MICPayload.tohex(), 3)
|
||||
tasmota.log("MTR: * decrypted_tag = " + decrypted_tag.tohex(), 3)
|
||||
tasmota.log("****************************************", 3)
|
||||
tasmota.log("****************************************", 4)
|
||||
tasmota.log("MTR: * s1rk = " + s1rk.tohex(), 4)
|
||||
tasmota.log("MTR: * tag = " + tag.tohex(), 4)
|
||||
tasmota.log("MTR: * Resume1MICPayload = " + Resume1MICPayload.tohex(), 4)
|
||||
tasmota.log("MTR: * decrypted_tag = " + decrypted_tag.tohex(), 4)
|
||||
tasmota.log("****************************************", 4)
|
||||
if tag == decrypted_tag
|
||||
session._fabric = session_resumption._fabric
|
||||
session._source_node_id = msg.source_node_id
|
||||
session.set_mode_CASE()
|
||||
session.__future_initiator_session_id = sigma1.initiator_session_id # update initiator_session_id
|
||||
session.__future_local_session_id = self.device.sessions.gen_local_session_id()
|
||||
tasmota.log(string.format("MTR: +Session (%6i) from '[%s]:%i'", session.__future_local_session_id, msg.remote_ip, msg.remote_port), 2)
|
||||
|
||||
# Generate and Send Sigma2_Resume
|
||||
session.shared_secret = session_resumption.shared_secret
|
||||
session.resumption_id = crypto.random(16) # generate a new resumption id
|
||||
|
||||
# compute S2RK
|
||||
var s2rk_info = bytes().fromstring("Sigma2_Resume") + session.resumption_id
|
||||
var s2rk_salt = sigma1.initiatorRandom + sigma1.resumptionID
|
||||
var s2rk_info = bytes().fromstring("Sigma2_Resume")
|
||||
var s2rk_salt = sigma1.initiatorRandom + session.resumption_id
|
||||
var s2rk = crypto.HKDF_SHA256().derive(session.shared_secret, s2rk_salt, s2rk_info, 16)
|
||||
|
||||
|
||||
# compute Resume2MIC
|
||||
var aes = crypto.AES_CCM(s2rk, bytes().fromstring("NCASE_SigmaR2"), bytes(), 0, 16)
|
||||
var aes = crypto.AES_CCM(s2rk, bytes().fromstring("NCASE_SigmaS2"), bytes(), 0, 16)
|
||||
var Resume2MIC = aes.tag()
|
||||
|
||||
var sigma2resume = matter.Sigma2Resume()
|
||||
sigma2resume.resumptionID = session.resumption_id
|
||||
sigma2resume.responderSessionID = session._future_local_session_id
|
||||
sigma2resume.responderSessionID = session.__future_local_session_id
|
||||
sigma2resume.sigma2ResumeMIC = Resume2MIC
|
||||
|
||||
tasmota.log("****************************************", 4)
|
||||
tasmota.log("MTR: * s2rk = " + s2rk.tohex(), 4)
|
||||
tasmota.log("MTR: * s2rk_salt = " + s2rk_salt.tohex(), 4)
|
||||
tasmota.log("MTR: * new_resumption_id = " + session.resumption_id.tohex(), 4)
|
||||
tasmota.log("MTR: * responderSessionID= " + str(session.__future_local_session_id), 4)
|
||||
tasmota.log("MTR: * sigma2ResumeMIC = " + Resume2MIC.tohex(), 4)
|
||||
tasmota.log("****************************************", 4)
|
||||
# # compute session key, p.178
|
||||
var session_keys = crypto.HKDF_SHA256().derive(session.shared_secret #- input key -#,
|
||||
sigma1.initiatorRandom + sigma1.resumptionID #- salt -#,
|
||||
sigma1.initiatorRandom + session.resumption_id #- salt -#,
|
||||
bytes().fromstring("SessionResumptionKeys") #- info -#,
|
||||
48)
|
||||
var i2r = session_keys[0..15]
|
||||
var r2i = session_keys[16..31]
|
||||
var ac = session_keys[32..47]
|
||||
var session_timestamp = tasmota.rtc()['utc']
|
||||
var created = tasmota.rtc()['utc']
|
||||
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: I2RKey =" + i2r.tohex(), 3)
|
||||
tasmota.log("MTR: R2IKey =" + r2i.tohex(), 3)
|
||||
tasmota.log("MTR: AC =" + ac.tohex(), 3)
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: ******************************", 4)
|
||||
tasmota.log("MTR: I2RKey =" + i2r.tohex(), 4)
|
||||
tasmota.log("MTR: R2IKey =" + r2i.tohex(), 4)
|
||||
tasmota.log("MTR: AC =" + ac.tohex(), 4)
|
||||
tasmota.log("MTR: ******************************", 4)
|
||||
|
||||
var sigma2resume_raw = sigma2resume.encode()
|
||||
session._Msg1 = nil
|
||||
tasmota.log("MTR: sigma2resume_raw: " + sigma2resume_raw.tohex(), 3)
|
||||
var sigma2resume_raw = sigma2resume.tlv2raw()
|
||||
session.__Msg1 = nil
|
||||
tasmota.log("MTR: sigma2resume: " + matter.inspect(sigma2resume), 4)
|
||||
tasmota.log("MTR: sigma2resume_raw: " + sigma2resume_raw.tohex(), 4)
|
||||
|
||||
# now package the response message
|
||||
var resp = msg.build_response(0x33 #-sigma-2-resume-#, true)
|
||||
var raw = resp.encode(sigma2resume_raw)
|
||||
var raw = resp.encode_frame(sigma2resume_raw)
|
||||
|
||||
self.responder.send_response(raw, msg.remote_ip, msg.remote_port, resp.message_counter)
|
||||
self.responder.send_response_frame(resp)
|
||||
|
||||
session.close()
|
||||
session.set_keys(i2r, r2i, ac, session_timestamp)
|
||||
session.set_keys(i2r, r2i, ac, created)
|
||||
|
||||
# CASE Session completed, persist it
|
||||
session._breadcrumb = 0 # clear breadcrumb
|
||||
session.counter_snd_next() # force a first counter. It's important it's used before set_persist(true) to not have a double save
|
||||
session.set_persist(true) # keep session on flash
|
||||
session.set_no_expiration() # never expire
|
||||
session.persist_to_fabric()
|
||||
session.save()
|
||||
|
||||
return true
|
||||
else
|
||||
sigma1.resumptionID = nil
|
||||
is_resumption = false
|
||||
# fall through normal sigma1 (non-resumption)
|
||||
end
|
||||
end
|
||||
|
||||
if sigma1.resumptionID == nil || sigma1.initiatorResumeMIC == nil
|
||||
if !is_resumption
|
||||
# new CASE session, assign to existing fabric
|
||||
var fabric = self.find_fabric_by_destination_id(sigma1.destinationId, sigma1.initiatorRandom)
|
||||
session._fabric = fabric
|
||||
|
||||
if session == nil || session._fabric == nil
|
||||
tasmota.log("MTR: StatusReport(GeneralCode: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: NO_SHARED_TRUST_ROOTS)", 2)
|
||||
var raw = self.send_status_report(msg, 0x01, 0x0000, 0x0001, false)
|
||||
return false
|
||||
end
|
||||
session._source_node_id = msg.source_node_id
|
||||
session.set_mode_CASE()
|
||||
|
||||
session.__future_initiator_session_id = sigma1.initiator_session_id # update initiator_session_id
|
||||
session.__future_local_session_id = self.device.sessions.gen_local_session_id()
|
||||
tasmota.log(string.format("MTR: +Session (%6i) from '[%s]:%i'", session.__future_local_session_id, msg.remote_ip, msg.remote_port), 2)
|
||||
|
||||
tasmota.log("MTR: fabric="+matter.inspect(session._fabric), 4)
|
||||
tasmota.log("MTR: no_private_key="+session._fabric.no_private_key.tohex(), 4)
|
||||
tasmota.log("MTR: noc ="+session._fabric.noc.tohex(), 4)
|
||||
if session._fabric.get_icac()
|
||||
tasmota.log("MTR: icac ="+session._fabric.get_icac().tohex(), 4)
|
||||
end
|
||||
tasmota.log("MTR: root_ca_cert ="+session._fabric.root_ca_certificate.tohex(), 4)
|
||||
|
||||
# Compute Sigma2, p.162
|
||||
session.resumption_id = crypto.random(16)
|
||||
self.ResponderEph_priv = crypto.random(32)
|
||||
self.ResponderEph_pub = crypto.EC_P256().public_key(self.ResponderEph_priv)
|
||||
session.__responder_priv = crypto.random(32)
|
||||
session.__responder_pub = crypto.EC_P256().public_key(session.__responder_priv)
|
||||
tasmota.log("MTR: ResponderEph_priv ="+session.__responder_priv.tohex(), 4)
|
||||
tasmota.log("MTR: ResponderEph_pub ="+session.__responder_pub.tohex(), 4)
|
||||
var responderRandom = crypto.random(32)
|
||||
|
||||
session.shared_secret = crypto.EC_P256().shared_key(self.ResponderEph_priv, sigma1.initiatorEphPubKey)
|
||||
session.shared_secret = crypto.EC_P256().shared_key(session.__responder_priv, sigma1.initiatorEphPubKey)
|
||||
|
||||
var sigma2_tbsdata = matter.TLV.Matter_TLV_struct()
|
||||
sigma2_tbsdata.add_TLV(1, matter.TLV.B2, session.get_noc())
|
||||
sigma2_tbsdata.add_TLV(2, matter.TLV.B2, session.get_icac())
|
||||
sigma2_tbsdata.add_TLV(3, matter.TLV.B2, self.ResponderEph_pub)
|
||||
sigma2_tbsdata.add_TLV(3, matter.TLV.B2, session.__responder_pub)
|
||||
sigma2_tbsdata.add_TLV(4, matter.TLV.B2, sigma1.initiatorEphPubKey)
|
||||
|
||||
var TBSData2Signature = crypto.EC_P256().ecdsa_sign_sha256(session.get_pk(), sigma2_tbsdata.encode())
|
||||
var TBSData2Signature = crypto.EC_P256().ecdsa_sign_sha256(session.get_pk(), sigma2_tbsdata.tlv2raw())
|
||||
|
||||
var sigma2_tbedata = matter.TLV.Matter_TLV_struct()
|
||||
sigma2_tbedata.add_TLV(1, matter.TLV.B2, session.get_noc())
|
||||
@ -402,43 +488,46 @@ class Matter_Commisioning_Context
|
||||
sigma2_tbedata.add_TLV(4, matter.TLV.B2, session.resumption_id)
|
||||
|
||||
# compute TranscriptHash = Crypto_Hash(message = Msg1)
|
||||
tasmota.log("****************************************", 3)
|
||||
session._Msg1 = sigma1.Msg1
|
||||
tasmota.log("MTR: * MSG1 = " + session._Msg1.tohex(), 3)
|
||||
var TranscriptHash = crypto.SHA256().update(session._Msg1).out()
|
||||
tasmota.log("****************************************", 4)
|
||||
session.__Msg1 = sigma1.Msg1
|
||||
tasmota.log("MTR: * resumptionid = " + session.resumption_id.tohex(), 4)
|
||||
tasmota.log("MTR: * MSG1 = " + session.__Msg1.tohex(), 4)
|
||||
var TranscriptHash = crypto.SHA256().update(session.__Msg1).out()
|
||||
tasmota.log("MTR: TranscriptHash =" + TranscriptHash.tohex(), 4)
|
||||
|
||||
# Compute S2K, p.175
|
||||
var s2k_info = bytes().fromstring(self.S2K_Info)
|
||||
var s2k_salt = session.get_ipk_group_key() + responderRandom + self.ResponderEph_pub + TranscriptHash
|
||||
var s2k_salt = session.get_ipk_group_key() + responderRandom + session.__responder_pub + TranscriptHash
|
||||
|
||||
var s2k = crypto.HKDF_SHA256().derive(session.shared_secret, s2k_salt, s2k_info, 16)
|
||||
tasmota.log("MTR: * SharedSecret = " + session.shared_secret.tohex(), 3)
|
||||
tasmota.log("MTR: * s2k_salt = " + s2k_salt.tohex(), 3)
|
||||
tasmota.log("MTR: * s2k = " + s2k.tohex(), 3)
|
||||
tasmota.log("MTR: * SharedSecret = " + session.shared_secret.tohex(), 4)
|
||||
tasmota.log("MTR: * s2k_salt = " + s2k_salt.tohex(), 4)
|
||||
tasmota.log("MTR: * s2k = " + s2k.tohex(), 4)
|
||||
|
||||
var sigma2_tbedata_raw = sigma2_tbedata.encode()
|
||||
var sigma2_tbedata_raw = sigma2_tbedata.tlv2raw()
|
||||
tasmota.log("MTR: * TBEData2Raw = " + sigma2_tbedata_raw.tohex(), 4)
|
||||
# // `AES_CCM.init(secret_key:bytes(16 or 32), iv:bytes(7..13), aad:bytes(), data_len:int, tag_len:int) -> instance`
|
||||
|
||||
var aes = crypto.AES_CCM(s2k, bytes().fromstring(self.TBEData2_Nonce), bytes(), size(sigma2_tbedata_raw), 16)
|
||||
var TBEData2Encrypted = aes.encrypt(sigma2_tbedata_raw) + aes.tag()
|
||||
tasmota.log("MTR: * TBEData2Enc = " + TBEData2Encrypted.tohex(), 3)
|
||||
tasmota.log("****************************************", 3)
|
||||
tasmota.log("MTR: * TBEData2Enc = " + TBEData2Encrypted.tohex(), 4)
|
||||
tasmota.log("****************************************", 4)
|
||||
|
||||
var sigma2 = matter.Sigma2()
|
||||
sigma2.responderRandom = responderRandom
|
||||
sigma2.responderSessionId = self.future_local_session_id
|
||||
sigma2.responderEphPubKey = self.ResponderEph_pub
|
||||
sigma2.responderSessionId = session.__future_local_session_id
|
||||
sigma2.responderEphPubKey = session.__responder_pub
|
||||
sigma2.encrypted2 = TBEData2Encrypted
|
||||
tasmota.log("MTR: sigma2: " + matter.inspect(sigma2), 3)
|
||||
var sigma2_raw = sigma2.encode()
|
||||
session._Msg2 = sigma2_raw
|
||||
tasmota.log("MTR: sigma2_raw: " + sigma2_raw.tohex(), 3)
|
||||
tasmota.log("MTR: sigma2: " + matter.inspect(sigma2), 4)
|
||||
var sigma2_raw = sigma2.tlv2raw()
|
||||
session.__Msg2 = sigma2_raw
|
||||
tasmota.log("MTR: sigma2_raw: " + sigma2_raw.tohex(), 4)
|
||||
|
||||
# now package the response message
|
||||
var resp = msg.build_response(0x31 #-sigma-2-#, true) # no reliable flag
|
||||
var raw = resp.encode(sigma2_raw)
|
||||
var raw = resp.encode_frame(sigma2_raw)
|
||||
|
||||
self.responder.send_response(raw, msg.remote_ip, msg.remote_port, resp.message_counter)
|
||||
self.responder.send_response_frame(resp)
|
||||
return true
|
||||
end
|
||||
|
||||
@ -449,28 +538,29 @@ class Matter_Commisioning_Context
|
||||
import crypto
|
||||
# sanity checks
|
||||
if msg.opcode != 0x32 || msg.local_session_id != 0 || msg.protocol_id != 0
|
||||
raise "protocol_error", "invalid Pake1 message"
|
||||
tasmota.log("MTR: StatusReport(General Code: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: INVALID_PARAMETER)", 2)
|
||||
var raw = self.send_status_report(msg, 0x01, 0x0000, 0x0002, false)
|
||||
return false
|
||||
end
|
||||
var session = msg.session
|
||||
var sigma3 = matter.Sigma3().parse(msg.raw, msg.app_payload_idx)
|
||||
|
||||
tasmota.log("****************************************", 3)
|
||||
tasmota.log("****************************************", 4)
|
||||
# compute TranscriptHash = Crypto_Hash(message = Msg1 || Msg2)
|
||||
var TranscriptHash = crypto.SHA256().update(session._Msg1).update(session._Msg2).out()
|
||||
tasmota.log("MTR: * session = " + str(session), 3)
|
||||
tasmota.log("MTR: session.ipk_epoch_key " + str(session.ipk_epoch_key), 3)
|
||||
tasmota.log("MTR: session.fabric_compressed " + str(session.fabric_compressed), 3)
|
||||
tasmota.log("MTR: * ipk_group_key = " + session.get_ipk_group_key().tohex(), 3)
|
||||
tasmota.log("MTR: * TranscriptHash= " + TranscriptHash.tohex(), 3)
|
||||
var TranscriptHash = crypto.SHA256().update(session.__Msg1).update(session.__Msg2).out()
|
||||
tasmota.log("MTR: * session = " + str(session), 4)
|
||||
tasmota.log("MTR: .ipk_epoch_key=" + str(session.get_ipk_epoch_key()), 4)
|
||||
tasmota.log("MTR: .fabric_compr = " + str(session.get_fabric_compressed()), 4)
|
||||
tasmota.log("MTR: * ipk_group_key = " + session.get_ipk_group_key().tohex(), 4)
|
||||
tasmota.log("MTR: * TranscriptHash= " + TranscriptHash.tohex(), 4)
|
||||
|
||||
var s3k_info = bytes().fromstring(self.S3K_Info)
|
||||
var s3k = crypto.HKDF_SHA256().derive(session.shared_secret, session.get_ipk_group_key() + TranscriptHash, s3k_info, 16)
|
||||
|
||||
tasmota.log("****************************************", 3)
|
||||
# self.ipk_epoch_key == nil || self.fabric_compressed")
|
||||
tasmota.log("MTR: * s3k_salt = " + (session.get_ipk_group_key() + TranscriptHash).tohex(), 3)
|
||||
tasmota.log("MTR: * s3k = " + s3k.tohex(), 3)
|
||||
tasmota.log("****************************************", 3)
|
||||
tasmota.log("****************************************", 4)
|
||||
tasmota.log("MTR: * s3k_salt = " + (session.get_ipk_group_key() + TranscriptHash).tohex(), 4)
|
||||
tasmota.log("MTR: * s3k = " + s3k.tohex(), 4)
|
||||
tasmota.log("****************************************", 4)
|
||||
|
||||
# decrypt
|
||||
var encrypted = sigma3.TBEData3Encrypted[0..-17]
|
||||
@ -478,17 +568,26 @@ class Matter_Commisioning_Context
|
||||
var ec = crypto.AES_CCM(s3k, bytes().fromstring(self.TBEData3_Nonce), bytes(), size(encrypted), 16)
|
||||
var TBEData3 = ec.decrypt(encrypted)
|
||||
var TBETag3 = ec.tag()
|
||||
tasmota.log("MTR: * TBEData3 = " + TBEData3.tohex(), 3)
|
||||
tasmota.log("MTR: * TBETag3 = " + TBETag3.tohex(), 3)
|
||||
tasmota.log("MTR: * tag_sent = " + tag.tohex(), 3)
|
||||
tasmota.log("****************************************", 3)
|
||||
tasmota.log("MTR: * TBEData3 = " + TBEData3.tohex(), 4)
|
||||
tasmota.log("MTR: * TBETag3 = " + TBETag3.tohex(), 4)
|
||||
tasmota.log("MTR: * tag_sent = " + tag.tohex(), 4)
|
||||
tasmota.log("****************************************", 4)
|
||||
|
||||
if TBETag3 != tag raise "value_error", "tag do not match" end
|
||||
if TBETag3 != tag
|
||||
tasmota.log("MTR: Tag don't match", 2)
|
||||
tasmota.log("MTR: StatusReport(General Code: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: INVALID_PARAMETER)", 2)
|
||||
var raw = self.send_status_report(msg, 0x01, 0x0000, 0x0002, false)
|
||||
return false
|
||||
end
|
||||
|
||||
var TBEData3TLV = matter.TLV.parse(TBEData3)
|
||||
tasmota.log("MTR: * TBEData3TLV = " + str(TBEData3TLV), 4)
|
||||
var initiatorNOC = TBEData3TLV.findsubval(1)
|
||||
var initiatorICAC = TBEData3TLV.findsubval(2)
|
||||
var ec_signature = TBEData3TLV.findsubval(3)
|
||||
tasmota.log("MTR: * initiatorNOC = " + str(initiatorNOC), 4)
|
||||
tasmota.log("MTR: * initiatorICAC = " + str(initiatorICAC), 4)
|
||||
tasmota.log("MTR: * ec_signature = " + str(ec_signature), 4)
|
||||
# Success = Crypto_VerifyChain(certificates = [TBEData3.initiatorNOC, TBEData3.initiatorICAC, TrustedRCAC]), when TBEData3.initiatorICAC is present
|
||||
# TODO
|
||||
var initiatorNOCTLV = matter.TLV.parse(initiatorNOC)
|
||||
@ -503,30 +602,41 @@ class Matter_Commisioning_Context
|
||||
var sigma3_tbs = matter.TLV.Matter_TLV_struct()
|
||||
sigma3_tbs.add_TLV(1, matter.TLV.B1, initiatorNOC)
|
||||
sigma3_tbs.add_TLV(2, matter.TLV.B1, initiatorICAC)
|
||||
sigma3_tbs.add_TLV(3, matter.TLV.B1, self.initiatorEph_pub)
|
||||
sigma3_tbs.add_TLV(4, matter.TLV.B1, self.ResponderEph_pub)
|
||||
var sigma3_tbs_raw = sigma3_tbs.encode()
|
||||
sigma3_tbs.add_TLV(3, matter.TLV.B1, session.__initiator_pub)
|
||||
sigma3_tbs.add_TLV(4, matter.TLV.B1, session.__responder_pub)
|
||||
tasmota.log("MTR: * sigma3_tbs = " + str(sigma3_tbs), 4)
|
||||
var sigma3_tbs_raw = sigma3_tbs.tlv2raw()
|
||||
tasmota.log("MTR: * sigma3_tbs_raw= " + sigma3_tbs_raw.tohex(), 4)
|
||||
|
||||
tasmota.log("MTR: * initiatorNOCPubKey = " + initiatorNOCPubKey.tohex(), 3)
|
||||
tasmota.log("MTR: * ec_signature = " + ec_signature.tohex(), 3)
|
||||
tasmota.log("****************************************", 3)
|
||||
tasmota.log("MTR: * initiatorNOCPubKey= " + initiatorNOCPubKey.tohex(), 4)
|
||||
tasmota.log("MTR: * ec_signature = " + ec_signature.tohex(), 4)
|
||||
tasmota.log("****************************************", 4)
|
||||
|
||||
# `crypto.EC_P256().ecdsa_verify_sha256(public_key:bytes(65), message:bytes(), hash:bytes()) -> bool`
|
||||
var sigma3_tbs_valid = crypto.EC_P256().ecdsa_verify_sha256(initiatorNOCPubKey, sigma3_tbs_raw, ec_signature)
|
||||
|
||||
if !sigma3_tbs_valid raise "value_error", "sigma3_tbs does not have a valid signature" end
|
||||
if !sigma3_tbs_valid
|
||||
tasmota.log("MTR: sigma3_tbs does not have a valid signature", 2)
|
||||
tasmota.log("MTR: StatusReport(General Code: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: INVALID_PARAMETER)", 2)
|
||||
var raw = self.send_status_report(msg, 0x01, 0x0000, 0x0002, false)
|
||||
return false
|
||||
end
|
||||
|
||||
# All good, compute new keys
|
||||
tasmota.log("MTR: Sigma3 verified, computing new keys", 3)
|
||||
|
||||
TranscriptHash = crypto.SHA256().update(session._Msg1).update(session._Msg2).update(sigma3.Msg3).out()
|
||||
# we can now free _Msg1 and _Msg2
|
||||
session._Msg1 = nil
|
||||
session._Msg2 = nil
|
||||
TranscriptHash = crypto.SHA256().update(session.__Msg1).update(session.__Msg2).update(sigma3.Msg3).out()
|
||||
tasmota.log("MTR: * __Msg1 = " + session.__Msg1.tohex(), 4)
|
||||
tasmota.log("MTR: * __Msg2 = " + session.__Msg2.tohex(), 4)
|
||||
tasmota.log("MTR: * __Msg3 = " + sigma3.Msg3.tohex(), 4)
|
||||
tasmota.log("MTR: * TranscriptHash = " + TranscriptHash.tohex(), 4)
|
||||
# we can now free __Msg1 and __Msg2
|
||||
session.__Msg1 = nil
|
||||
session.__Msg2 = nil
|
||||
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: shared_secret =" + session.shared_secret.tohex(), 3)
|
||||
tasmota.log("MTR: ipk + hash =" + (session.get_ipk_group_key() + TranscriptHash).tohex(), 3)
|
||||
tasmota.log("MTR: ******************************", 4)
|
||||
tasmota.log("MTR: shared_secret =" + session.shared_secret.tohex(), 4)
|
||||
tasmota.log("MTR: ipk + hash =" + (session.get_ipk_group_key() + TranscriptHash).tohex(), 4)
|
||||
# compute session key
|
||||
var session_keys = crypto.HKDF_SHA256().derive(session.shared_secret #- input key -#,
|
||||
session.get_ipk_group_key() + TranscriptHash #- salt -#,
|
||||
@ -535,35 +645,39 @@ class Matter_Commisioning_Context
|
||||
var i2r = session_keys[0..15]
|
||||
var r2i = session_keys[16..31]
|
||||
var ac = session_keys[32..47]
|
||||
var session_timestamp = tasmota.rtc()['utc']
|
||||
var created = tasmota.rtc()['utc']
|
||||
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: I2RKey =" + i2r.tohex(), 3)
|
||||
tasmota.log("MTR: R2IKey =" + r2i.tohex(), 3)
|
||||
tasmota.log("MTR: AC =" + ac.tohex(), 3)
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: ******************************", 4)
|
||||
tasmota.log("MTR: I2RKey =" + i2r.tohex(), 4)
|
||||
tasmota.log("MTR: R2IKey =" + r2i.tohex(), 4)
|
||||
tasmota.log("MTR: AC =" + ac.tohex(), 4)
|
||||
tasmota.log("MTR: ******************************", 4)
|
||||
|
||||
# Send success status report
|
||||
var resp = msg.build_response(0x40 #-StatusReport-#, true) # reliable flag
|
||||
|
||||
var status_raw = bytes()
|
||||
status_raw.add(0x00, 2) # GeneralCode = SUCCESS
|
||||
status_raw.add(0x0000, 4) # ProtocolID = 0 (PROTOCOL_ID_SECURE_CHANNEL)
|
||||
status_raw.add(0x0000, 4) # ProtocolCode = 0 (SESSION_ESTABLISHMENT_SUCCESS)
|
||||
|
||||
var raw = resp.encode(status_raw)
|
||||
|
||||
self.responder.send_response(raw, msg.remote_ip, msg.remote_port, resp.message_counter)
|
||||
# StatusReport(GeneralCode: SUCCESS, ProtocolId: SECURE_CHANNEL, ProtocolCode: SESSION_ESTABLISHMENT_SUCCESS)
|
||||
var raw = self.send_status_report(msg, 0x00, 0x0000, 0x0000, true)
|
||||
|
||||
session.close()
|
||||
session.set_keys(i2r, r2i, ac, session_timestamp)
|
||||
session.set_keys(i2r, r2i, ac, created)
|
||||
|
||||
# CASE Session completed, persist it
|
||||
session._breadcrumb = 0 # clear breadcrumb
|
||||
session.counter_snd_next() # force a first counter. It's important it's used before set_persist(true) to not have a double save
|
||||
session.set_persist(true) # keep session on flash
|
||||
session.set_no_expiration() # never expire
|
||||
session.persist_to_fabric()
|
||||
session.save()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# placeholder, nothing to run for now
|
||||
def parse_StatusReport(msg)
|
||||
var session = msg.session
|
||||
tasmota.log("MTR: >Status "+msg.raw[msg.app_payload_idx..].tohex(), 2)
|
||||
return false # we don't explicitly ack the message
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# placeholder, nothing to run for now
|
||||
def every_second()
|
||||
|
@ -70,7 +70,7 @@ class Matter_PBKDFParamResponse
|
||||
var SLEEPY_IDLE_INTERVAL
|
||||
var SLEEPY_ACTIVE_INTERVAL
|
||||
|
||||
def encode(b)
|
||||
def tlv2raw(b)
|
||||
var s = matter.TLV.Matter_TLV_struct()
|
||||
# initiatorRandom
|
||||
s.add_TLV(1, matter.TLV.B1, self.initiatorRandom)
|
||||
@ -84,7 +84,7 @@ class Matter_PBKDFParamResponse
|
||||
s2.add_TLV(1, matter.TLV.U4, self.SLEEPY_IDLE_INTERVAL)
|
||||
s2.add_TLV(2, matter.TLV.U4, self.SLEEPY_ACTIVE_INTERVAL)
|
||||
end
|
||||
return s.encode()
|
||||
return s.tlv2raw(b)
|
||||
end
|
||||
end
|
||||
matter.PBKDFParamResponse = Matter_PBKDFParamResponse
|
||||
@ -98,7 +98,7 @@ class Matter_Pake1
|
||||
def parse(b, idx)
|
||||
if idx == nil idx = 0 end
|
||||
var val = matter.TLV.parse(b, idx)
|
||||
tasmota.log("MTR: parsed TLV: " + str(val), 3)
|
||||
tasmota.log("MTR: parsed TLV: " + str(val), 4)
|
||||
|
||||
self.pA = val.getsubval(1)
|
||||
return self
|
||||
@ -113,12 +113,12 @@ class Matter_Pake2
|
||||
var pB # 65 bytes
|
||||
var cB # 32 bytes
|
||||
|
||||
def encode(b)
|
||||
def tlv2raw(b)
|
||||
var s = matter.TLV.Matter_TLV_struct()
|
||||
#
|
||||
s.add_TLV(1, matter.TLV.B1, self.pB)
|
||||
s.add_TLV(2, matter.TLV.B1, self.cB)
|
||||
return s.encode()
|
||||
return s.tlv2raw(b)
|
||||
end
|
||||
end
|
||||
matter.Pake2 = Matter_Pake2
|
||||
@ -130,7 +130,7 @@ class Matter_Pake3
|
||||
def parse(b, idx)
|
||||
if idx == nil idx = 0 end
|
||||
var val = matter.TLV.parse(b, idx)
|
||||
tasmota.log("MTR: parsed TLV: " + str(val), 3)
|
||||
tasmota.log("MTR: parsed TLV: " + str(val), 4)
|
||||
|
||||
self.cA = val.getsubval(1)
|
||||
return self
|
||||
@ -157,7 +157,7 @@ class Matter_Sigma1
|
||||
if idx == nil idx = 0 end
|
||||
var val = matter.TLV.parse(b, idx)
|
||||
self.Msg1 = b[idx..]
|
||||
tasmota.log("MTR: Sigma1 TLV=" + str(val), 3)
|
||||
tasmota.log("MTR: Sigma1 TLV=" + str(val), 4)
|
||||
|
||||
self.initiatorRandom = val.getsubval(1)
|
||||
self.initiator_session_id = val.getsubval(2)
|
||||
@ -168,8 +168,8 @@ class Matter_Sigma1
|
||||
self.SLEEPY_IDLE_INTERVAL = initiatorSEDParams.findsubval(1)
|
||||
self.SLEEPY_ACTIVE_INTERVAL = initiatorSEDParams.findsubval(2)
|
||||
end
|
||||
var resumptionID = val.findsub(6)
|
||||
var initiatorResumeMIC = val.findsub(7)
|
||||
self.resumptionID = val.findsubval(6)
|
||||
self.initiatorResumeMIC = val.findsubval(7)
|
||||
return self
|
||||
end
|
||||
end
|
||||
@ -186,7 +186,7 @@ class Matter_Sigma2
|
||||
var SLEEPY_IDLE_INTERVAL
|
||||
var SLEEPY_ACTIVE_INTERVAL
|
||||
|
||||
def encode(b)
|
||||
def tlv2raw(b)
|
||||
var s = matter.TLV.Matter_TLV_struct()
|
||||
# initiatorRandom
|
||||
s.add_TLV(1, matter.TLV.B1, self.responderRandom)
|
||||
@ -198,7 +198,7 @@ class Matter_Sigma2
|
||||
s2.add_TLV(1, matter.TLV.U4, self.SLEEPY_IDLE_INTERVAL)
|
||||
s2.add_TLV(2, matter.TLV.U4, self.SLEEPY_ACTIVE_INTERVAL)
|
||||
end
|
||||
return s.encode()
|
||||
return s.tlv2raw(b)
|
||||
end
|
||||
end
|
||||
matter.Sigma2 = Matter_Sigma2
|
||||
@ -213,18 +213,18 @@ class Matter_Sigma2Resume
|
||||
var SLEEPY_IDLE_INTERVAL
|
||||
var SLEEPY_ACTIVE_INTERVAL
|
||||
|
||||
def encode(b)
|
||||
def tlv2raw(b)
|
||||
var s = matter.TLV.Matter_TLV_struct()
|
||||
# initiatorRandom
|
||||
s.add_TLV(1, matter.TLV.B1, self.resumptionID)
|
||||
s.add_TLV(2, matter.TLV.B1, self.sigma2ResumeMIC)
|
||||
s.add_TLV(3, matter.TLV.B1, self.responderSessionID)
|
||||
s.add_TLV(3, matter.TLV.U2, self.responderSessionID)
|
||||
if self.SLEEPY_IDLE_INTERVAL != nil || self.SLEEPY_ACTIVE_INTERVAL != nil
|
||||
var s2 = s.add_struct(4)
|
||||
s2.add_TLV(1, matter.TLV.U4, self.SLEEPY_IDLE_INTERVAL)
|
||||
s2.add_TLV(2, matter.TLV.U4, self.SLEEPY_ACTIVE_INTERVAL)
|
||||
end
|
||||
return s.encode()
|
||||
return s.tlv2raw(b)
|
||||
end
|
||||
end
|
||||
matter.Sigma2Resume = Matter_Sigma2Resume
|
||||
@ -240,7 +240,7 @@ class Matter_Sigma3
|
||||
if idx == nil idx = 0 end
|
||||
var val = matter.TLV.parse(b, idx)
|
||||
self.Msg3 = b[idx..]
|
||||
tasmota.log("MTR: Sigma3 TLV=" + str(val), 3)
|
||||
tasmota.log("MTR: Sigma3 TLV=" + str(val), 4)
|
||||
|
||||
self.TBEData3Encrypted = val.getsubval(1)
|
||||
return self
|
||||
|
@ -0,0 +1,79 @@
|
||||
#
|
||||
# Matter_Control_Message.be - suppport for Matter Control Messages (flag C = 1)
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import matter
|
||||
|
||||
#@ solidify:Matter_Control_Message,weak
|
||||
|
||||
#################################################################################
|
||||
# Class Matter_Control_Message
|
||||
#
|
||||
# Control message have C flag = 1
|
||||
# Used primarily for Message Counter Synchronization Protocol (MCSP)
|
||||
#################################################################################
|
||||
class Matter_Control_Message
|
||||
var responder # reference to the caller, sending packets
|
||||
var device # root device object
|
||||
|
||||
def init(responder)
|
||||
import crypto
|
||||
self.responder = responder
|
||||
self.device = responder.device
|
||||
end
|
||||
|
||||
def process_incoming_control_message(msg)
|
||||
|
||||
tasmota.log("MTR: received control message " + matter.inspect(msg), 2)
|
||||
if msg.opcode == 0x00
|
||||
return self.parse_MsgCounterSyncReq(msg)
|
||||
elif msg.opcode == 0x01
|
||||
return self.parse_MsgCounterSyncRsp(msg)
|
||||
else
|
||||
import string
|
||||
tasmota.log(string.format("MTR: >????????? Unknown OpCode (control message) %02X", msg.opcode), 2)
|
||||
return false
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# MsgCounterSyncReq
|
||||
#
|
||||
# Not yet implemented
|
||||
def parse_MsgCounterSyncReq(msg)
|
||||
import string
|
||||
var session = msg.session
|
||||
tasmota.log(string.format("MTR: >MCSyncReq * Not implemented %s", msg.raw[msg.app_payload_idx..].tohex()), 2)
|
||||
return false # we don't explicitly ack the message
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# MsgCounterSyncRsp
|
||||
#
|
||||
# Not yet implemented
|
||||
def parse_MsgCounterSyncRsp(msg)
|
||||
import string
|
||||
var session = msg.session
|
||||
tasmota.log(string.format("MTR: >MCSyncRsp * Not implemented %s", msg.raw[msg.app_payload_idx..].tohex()), 2)
|
||||
return false # we don't explicitly ack the message
|
||||
end
|
||||
|
||||
end
|
||||
matter.Control_Message = Matter_Control_Message
|
File diff suppressed because it is too large
Load Diff
234
lib/libesp32/berry_matter/src/embedded/Matter_Expirable.be
Normal file
234
lib/libesp32/berry_matter/src/embedded/Matter_Expirable.be
Normal file
@ -0,0 +1,234 @@
|
||||
#
|
||||
# Matter_Expirable.be - Support for Matter Expirable and Persistable objects and lists
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import matter
|
||||
|
||||
#@ solidify:Matter_Expirable,weak
|
||||
#@ solidify:Matter_Expirable_list,weak
|
||||
|
||||
#################################################################################
|
||||
# Matter_Expirable class
|
||||
#
|
||||
# Object that can expire after a certain timestamp or
|
||||
# that does not expire and can be persisted
|
||||
#
|
||||
# There are only 3 valid states:
|
||||
# - not expirable and not persistable
|
||||
# - expirable and not persistable
|
||||
# - not expirable and persistable
|
||||
# - (both expirable and persistable is normally not valid but still supported)
|
||||
#
|
||||
#################################################################################
|
||||
class Matter_Expirable
|
||||
var _list # the Expirable_list it belongs to (if any)
|
||||
var _persist # do we persist this sessions or is it remporary (bool)
|
||||
var _expiration # if not `nil` the entry is removed after this timestamp
|
||||
|
||||
#############################################################
|
||||
def init()
|
||||
self._persist = false
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def set_parent_list(l)
|
||||
self._list = l
|
||||
end
|
||||
def get_parent_list()
|
||||
return self._list
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def set_persist(p)
|
||||
self._persist = bool(p)
|
||||
end
|
||||
def does_persist()
|
||||
return self._persist
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# pre and post process when persist
|
||||
def persist_pre()
|
||||
end
|
||||
def persist_post()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# post process after the object was loaded
|
||||
def hydrate_post()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# before_remove
|
||||
#
|
||||
# called right before the element is removed
|
||||
def before_remove()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# set absolute time for expiration
|
||||
def set_no_expiration()
|
||||
self._expiration = nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# set absolute time for expiration
|
||||
def set_expire_time(t)
|
||||
self._expiration = int(t)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# set relative time in the future for expiration (in seconds)
|
||||
def set_expire_in_seconds(s, now)
|
||||
if s == nil return end
|
||||
if now == nil now = tasmota.rtc()['utc'] end
|
||||
self.set_expire_time(now + s)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# set relative time in the future for expiration (in seconds)
|
||||
# returns `true` if expiration date has been reached
|
||||
def has_expired(now)
|
||||
if now == nil now = tasmota.rtc()['utc'] end
|
||||
if self._expiration != nil
|
||||
return now >= self._expiration
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
matter.Expirable = Matter_Expirable
|
||||
|
||||
|
||||
#################################################################################
|
||||
# Matter_Expirable_list class
|
||||
#
|
||||
# Subclass of list handling Expirable(s)
|
||||
#
|
||||
#################################################################################
|
||||
class Matter_Expirable_list : list
|
||||
# no specific attributes
|
||||
# no specific init()
|
||||
|
||||
#############################################################
|
||||
# Accessors with control of arguments classes
|
||||
def push(o)
|
||||
if !isinstance(o, matter.Expirable) raise "type_error", "argument must be of class 'Expirable'" end
|
||||
o.set_parent_list(self)
|
||||
return super(self).push(o)
|
||||
end
|
||||
def setitem(i, o)
|
||||
if !isinstance(o, matter.Expirable) raise "type_error", "argument must be of class 'Expirable'" end
|
||||
o.set_parent_list(self)
|
||||
return super(self).setitem(i, o)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# remove - override
|
||||
#
|
||||
def remove(i)
|
||||
if i >= 0 && i < size(self) self[i].before_remove() end
|
||||
return super(self).remove(i)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# remove_expired
|
||||
#
|
||||
# Check is any object has expired
|
||||
#
|
||||
# returns `true` if persistable objects were actually removed (i.e. needs to persist again), `false` instead
|
||||
def remove_expired()
|
||||
var dirty = false
|
||||
var i = 0
|
||||
while i < size(self)
|
||||
if self[i].has_expired()
|
||||
if self[i]._persist dirty = true end # do we need to save
|
||||
self.remove(i)
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
return dirty
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# iterator over persistable instances
|
||||
#
|
||||
def persistables()
|
||||
var iterator = self.iter()
|
||||
var f = def ()
|
||||
while true
|
||||
var o = iterator()
|
||||
if o._persist return o end
|
||||
end
|
||||
# ends with an exception
|
||||
end
|
||||
|
||||
return f
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Count the number of persistable objects
|
||||
def count_persistables()
|
||||
var ret = 0
|
||||
var idx = 0
|
||||
while idx < size(self)
|
||||
if self[idx]._persist ret += 1 end
|
||||
idx += 1
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# every_second
|
||||
def every_second()
|
||||
self.remove_expired()
|
||||
end
|
||||
|
||||
end
|
||||
matter.Expirable_list = Matter_Expirable_list
|
||||
|
||||
#-
|
||||
|
||||
# tests
|
||||
|
||||
a = matter.Expirable()
|
||||
a.set_persist(true)
|
||||
b = matter.Expirable()
|
||||
c = matter.Expirable()
|
||||
c.set_persist(true)
|
||||
l = matter.Expirable_list()
|
||||
|
||||
|
||||
l.push(a)
|
||||
l.push(b)
|
||||
l.push(c)
|
||||
|
||||
print('---')
|
||||
for e:l print(e) end
|
||||
print('---')
|
||||
for e:l.persistables() print(e) end
|
||||
print('---')
|
||||
|
||||
l.every_second()
|
||||
print(size(l))
|
||||
l[1].set_expire_time(10)
|
||||
l.every_second()
|
||||
print(size(l))
|
||||
|
||||
-#
|
288
lib/libesp32/berry_matter/src/embedded/Matter_Fabric.be
Normal file
288
lib/libesp32/berry_matter/src/embedded/Matter_Fabric.be
Normal file
@ -0,0 +1,288 @@
|
||||
#
|
||||
# Matter_Fabric.be - Support for Matter Fabric
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import matter
|
||||
|
||||
#@ solidify:Matter_Fabric,weak
|
||||
|
||||
# for solidification only
|
||||
class Matter_Expirable end
|
||||
|
||||
#################################################################################
|
||||
# Matter_Fabric class
|
||||
#
|
||||
# Record all information for a fabric that has provisioned
|
||||
#
|
||||
# By convetion:
|
||||
# attributes with a normal name are persisted (unless they are instances)
|
||||
# attributes starting with '_' are not persisted
|
||||
# attributes starting with '__' are cleared when a new session is created
|
||||
#################################################################################
|
||||
class Matter_Fabric : Matter_Expirable
|
||||
static var _MAX_CASE = 5 # maximum number of concurrent CASE sessions per fabric
|
||||
static var _GROUP_SND_INCR = 32 # counter increased when persisting
|
||||
# Group Key Derivation
|
||||
static var _GROUP_KEY = "GroupKey v1.0" # starting with double `_` means it's not writable
|
||||
|
||||
var _store # reference back to session store
|
||||
# timestamp
|
||||
var created
|
||||
# fabric-index
|
||||
var fabric_index # index number for fabrics, starts with `1`
|
||||
var fabric_parent # index of the parent fabric, i.e. the fabric that triggered the provisioning (if nested)
|
||||
# list of active sessions
|
||||
var _sessions # only active CASE sessions that need to be persisted
|
||||
# our own private key
|
||||
var no_private_key # private key of the device certificate (generated at commissioning)
|
||||
# NOC information
|
||||
var root_ca_certificate # root certificate of the initiator
|
||||
var noc # Node Operational Certificate in TLV Matter Certificate
|
||||
var icac # Initiator CA Certificate in TLV Matter Certificate
|
||||
var ipk_epoch_key # timestamp
|
||||
# Information extracted from `noc`
|
||||
var fabric_id # fabric identifier as bytes(8) little endian
|
||||
var fabric_compressed # comrpessed fabric_id identifier, hashed with root_ca public key
|
||||
var device_id # our own device id bytes(8) little endian
|
||||
var fabric_label # set by UpdateFabricLabel
|
||||
# global group counters (send)
|
||||
var counter_group_data_snd # counter for group data
|
||||
var counter_group_ctrl_snd # counter for group command
|
||||
var _counter_group_data_snd_impl# implementation of counter_group_data_snd by matter.Counter()
|
||||
var _counter_group_ctrl_snd_impl# implementation of counter_group_ctrl_snd by matter.Counter()
|
||||
# Admin info extracted from NOC/ICAC
|
||||
var admin_subject
|
||||
var admin_vendor
|
||||
|
||||
#############################################################
|
||||
def init(store)
|
||||
import crypto
|
||||
self._store = store
|
||||
self._sessions = matter.Expirable_list()
|
||||
self.fabric_label = ""
|
||||
self.created = tasmota.rtc()['utc']
|
||||
# init group counters
|
||||
self._counter_group_data_snd_impl = matter.Counter()
|
||||
self._counter_group_ctrl_snd_impl = matter.Counter()
|
||||
self.counter_group_data_snd = self._counter_group_data_snd_impl.next() + self._GROUP_SND_INCR
|
||||
self.counter_group_ctrl_snd = self._counter_group_data_snd_impl.next() + self._GROUP_SND_INCR
|
||||
end
|
||||
|
||||
def get_noc() return self.noc end
|
||||
def get_icac() return self.icac end
|
||||
def get_ipk_epoch_key() return self.ipk_epoch_key end
|
||||
def get_fabric_id() return self.fabric_id end
|
||||
def get_device_id() return self.device_id end
|
||||
def get_fabric_compressed() return self.fabric_compressed end
|
||||
def get_fabric_label() return self.fabric_label end
|
||||
def get_admin_subject() return self.admin_subject end
|
||||
def get_admin_vendor() return self.admin_vendor end
|
||||
def get_ca() return self.root_ca_certificate end
|
||||
def get_fabric_index() return self.fabric_index end
|
||||
|
||||
def set_fabric_index(v) self.fabric_index = v end
|
||||
|
||||
#############################################################
|
||||
# When hydrating from persistance, update counters
|
||||
def hydrate_post()
|
||||
# reset counter_snd to highest known.
|
||||
# We advance it only in case it is actually used
|
||||
# This avoids updaing counters on dead sessions
|
||||
self._counter_group_data_snd_impl.reset(self.counter_group_data_snd)
|
||||
self._counter_group_ctrl_snd_impl.reset(self.counter_group_ctrl_snd)
|
||||
self.counter_group_data_snd = self._counter_group_data_snd_impl.val()
|
||||
self.counter_group_ctrl_snd = self._counter_group_ctrl_snd_impl.val()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Management of security counters
|
||||
#############################################################
|
||||
# Provide the next counter value, and update the last know persisted if needed
|
||||
#
|
||||
def counter_group_data_snd_next()
|
||||
import string
|
||||
var next = self._counter_group_data_snd_impl.next()
|
||||
tasmota.log(string.format("MTR: . Counter_group_data_snd=%i", next), 3)
|
||||
if matter.Counter.is_greater(next, self.counter_group_data_snd)
|
||||
self.counter_group_data_snd = next + self._GROUP_SND_INCR
|
||||
if self.does_persist()
|
||||
# the persisted counter is behind the actual counter
|
||||
self.save()
|
||||
end
|
||||
end
|
||||
return next
|
||||
end
|
||||
#############################################################
|
||||
# Provide the next counter value, and update the last know persisted if needed
|
||||
#
|
||||
def counter_group_ctrl_snd_next()
|
||||
import string
|
||||
var next = self._counter_group_ctrl_snd_impl.next()
|
||||
tasmota.log(string.format("MTR: . Counter_group_ctrl_snd=%i", next), 3)
|
||||
if matter.Counter.is_greater(next, self.counter_group_ctrl_snd)
|
||||
self.counter_group_ctrl_snd = next + self._GROUP_SND_INCR
|
||||
if self.does_persist()
|
||||
# the persisted counter is behind the actual counter
|
||||
self.save()
|
||||
end
|
||||
end
|
||||
return next
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Called before removal
|
||||
def log_new_fabric()
|
||||
import string
|
||||
tasmota.log(string.format("MTR: +Fabric fab='%s'", self.get_fabric_id().copy().reverse().tohex()), 2)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Called before removal
|
||||
def before_remove()
|
||||
import string
|
||||
tasmota.log(string.format("MTR: -Fabric fab='%s' (removed)", self.get_fabric_id().copy().reverse().tohex()), 2)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Operational Group Key Derivation, 4.15.2, p.182
|
||||
def get_ipk_group_key()
|
||||
if self.ipk_epoch_key == nil || self.fabric_compressed == nil return nil end
|
||||
import crypto
|
||||
var hk = crypto.HKDF_SHA256()
|
||||
var info = bytes().fromstring(self._GROUP_KEY)
|
||||
var hash = hk.derive(self.ipk_epoch_key, self.fabric_compressed, info, 16)
|
||||
return hash
|
||||
end
|
||||
|
||||
def get_ca_pub()
|
||||
var ca = self.root_ca_certificate
|
||||
if ca
|
||||
var m = matter.TLV.parse(ca)
|
||||
return m.findsubval(9)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# add session to list of persisted sessions
|
||||
# check for duplicates
|
||||
def add_session(s)
|
||||
if self._sessions.find(s) == nil
|
||||
while size(self._sessions) >= self._MAX_CASE
|
||||
self._sessions.remove(self._sessions.find(self.get_oldest_session()))
|
||||
end
|
||||
self._sessions.push(s)
|
||||
end
|
||||
end
|
||||
|
||||
def get_oldest_session() return self.get_old_recent_session(true) end
|
||||
def get_newest_session() return self.get_old_recent_session(false) end
|
||||
|
||||
# get the oldest or most recent session (oldest indicates direction)
|
||||
def get_old_recent_session(oldest)
|
||||
if size(self._sessions) == 0 return nil end
|
||||
var session = self._sessions[0]
|
||||
var timestamp = session.last_used
|
||||
|
||||
var idx = 1
|
||||
while idx < size(self._sessions)
|
||||
var time2 = self._sessions[idx].last_used
|
||||
if (oldest ? time2 < timestamp : time2 > timestamp)
|
||||
session = self._sessions[idx]
|
||||
timestamp = time2
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
return session
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Fabric::tojson()
|
||||
#
|
||||
# convert a single entry as json
|
||||
# returns a JSON string
|
||||
#############################################################
|
||||
def tojson()
|
||||
import json
|
||||
import string
|
||||
import introspect
|
||||
|
||||
self.persist_pre()
|
||||
var keys = []
|
||||
for k : introspect.members(self)
|
||||
var v = introspect.get(self, k)
|
||||
if type(v) != 'function' && k[0] != '_' keys.push(k) end
|
||||
end
|
||||
keys = matter.sort(keys)
|
||||
|
||||
var r = []
|
||||
for k : keys
|
||||
var v = introspect.get(self, k)
|
||||
if v == nil continue end
|
||||
if isinstance(v, bytes) v = "$$" + v.tob64() end # bytes
|
||||
r.push(string.format("%s:%s", json.dump(str(k)), json.dump(v)))
|
||||
end
|
||||
|
||||
# add sessions
|
||||
var s = []
|
||||
for sess : self._sessions.persistables()
|
||||
s.push(sess.tojson())
|
||||
end
|
||||
if size(s) > 0
|
||||
var s_list = "[" + s.concat(",") + "]"
|
||||
r.push('"_sessions":' + s_list)
|
||||
end
|
||||
|
||||
self.persist_post()
|
||||
return "{" + r.concat(",") + "}"
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# fromjson()
|
||||
#
|
||||
# reads a map and load arguments
|
||||
# returns an new instance of fabric
|
||||
# don't load embedded session, this is done by store
|
||||
# i.e. ignore any key starting with '_'
|
||||
#############################################################
|
||||
static def fromjson(store, values)
|
||||
import string
|
||||
import introspect
|
||||
var self = matter.Fabric(store)
|
||||
|
||||
for k : values.keys()
|
||||
if k[0] == '_' continue end # ignore if key starts with '_'
|
||||
var v = values[k]
|
||||
# standard values
|
||||
if type(v) == 'string'
|
||||
if string.find(v, "0x") == 0 # treat as bytes
|
||||
introspect.set(self, k, bytes().fromhex(v[2..]))
|
||||
elif string.find(v, "$$") == 0 # treat as bytes
|
||||
introspect.set(self, k, bytes().fromb64(v[2..]))
|
||||
else
|
||||
introspect.set(self, k, v)
|
||||
end
|
||||
else
|
||||
introspect.set(self, k, v)
|
||||
end
|
||||
end
|
||||
self.hydrate_post()
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
matter.Fabric = Matter_Fabric
|
@ -19,50 +19,21 @@
|
||||
|
||||
import matter
|
||||
|
||||
#@ solidify:Matter_Response_container,weak
|
||||
#@ solidify:Matter_IM,weak
|
||||
|
||||
#################################################################################
|
||||
# Matter_Response_container
|
||||
#
|
||||
# Used to store all the elements of the reponse to an attribute or command
|
||||
#################################################################################
|
||||
class Matter_Response_container
|
||||
var endpoint
|
||||
var cluster
|
||||
var attribute
|
||||
var command
|
||||
var status
|
||||
|
||||
def tostring()
|
||||
try
|
||||
import string
|
||||
var s = ""
|
||||
s += (self.endpoint != nil ? string.format("[%02X]", self.endpoint) : "[**]")
|
||||
s += (self.cluster != nil ? string.format("%04X/", self.cluster) : "****/")
|
||||
s += (self.attribute != nil ? string.format("%04X", self.attribute) : "")
|
||||
s += (self.command != nil ? string.format("%04X", self.attribute) : "")
|
||||
return s
|
||||
except .. as e, m
|
||||
return "Exception> " + str(e) + ", " + str(m)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
matter.Response_container = Matter_Response_container
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM class
|
||||
#################################################################################
|
||||
class Matter_IM
|
||||
static var MAX_MESSAGE = 1200
|
||||
static var MSG_TIMEOUT = 10000 # 10s
|
||||
var responder
|
||||
var device
|
||||
var subs_shop # subscriptions shop
|
||||
|
||||
def init(responder, device)
|
||||
self.responder = responder
|
||||
var send_queue # list of IM_Message queued for sending as part of exchange-id
|
||||
|
||||
def init(device)
|
||||
self.device = device
|
||||
self.send_queue = []
|
||||
self.subs_shop = matter.IM_Subscription_Shop(self)
|
||||
end
|
||||
|
||||
def process_incoming(msg)
|
||||
@ -71,27 +42,31 @@ class Matter_IM
|
||||
|
||||
var val = matter.TLV.parse(msg.raw, msg.app_payload_idx)
|
||||
|
||||
tasmota.log("MTR: IM TLV: " + str(val), 3)
|
||||
# tasmota.log("MTR: IM TLV: " + str(val), 3)
|
||||
|
||||
var InteractionModelRevision = val.findsubval(0xFF)
|
||||
tasmota.log("MTR: InteractionModelRevision=" + (InteractionModelRevision != nil ? str(InteractionModelRevision) : "nil"), 3)
|
||||
# tasmota.log("MTR: InteractionModelRevision=" + (InteractionModelRevision != nil ? str(InteractionModelRevision) : "nil"), 4)
|
||||
|
||||
var opcode = msg.opcode
|
||||
if opcode == 0x01 # Status Response
|
||||
return self.process_status_response(msg, val)
|
||||
elif opcode == 0x02 # Read Request
|
||||
self.send_ack_now(msg)
|
||||
return self.process_read_request(msg, val)
|
||||
elif opcode == 0x03 # Subscribe Request
|
||||
self.send_ack_now(msg)
|
||||
return self.subscribe_request(msg, val)
|
||||
elif opcode == 0x04 # Subscribe Response
|
||||
return self.subscribe_response(msg, val)
|
||||
elif opcode == 0x05 # Report Data
|
||||
return self.report_data(msg, val)
|
||||
elif opcode == 0x06 # Write Request
|
||||
self.send_ack_now(msg)
|
||||
return self.process_write_request(msg, val)
|
||||
elif opcode == 0x07 # Write Response
|
||||
return self.process_write_response(msg, val)
|
||||
elif opcode == 0x08 # Invoke Request
|
||||
self.send_ack_now(msg)
|
||||
return self.process_invoke_request(msg, val)
|
||||
elif opcode == 0x09 # Invoke Response
|
||||
return self.process_invoke_response(msg, val)
|
||||
@ -103,26 +78,133 @@ class Matter_IM
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# process IM 0x01 Status Response
|
||||
# check whether the ack received is of interest to any
|
||||
# current exchange
|
||||
#
|
||||
# val is the TLV structure
|
||||
# returns `true` if processed, `false` if silently ignored,
|
||||
# or raises an exception
|
||||
def process_status_response(msg, val)
|
||||
# return `true` if handled
|
||||
def process_incoming_ack(msg)
|
||||
import string
|
||||
var status = val.findsubval(0, 0xFF)
|
||||
tasmota.log(string.format("MTR: Status Response = 0x%02X", status), 3)
|
||||
return true
|
||||
# check if there is an exchange_id interested in receiving this
|
||||
var message = self.find_sendqueue_by_exchangeid(msg.exchange_id)
|
||||
tasmota.log(string.format("MTR: process_incoming_ack exch=%i message=%i", msg.exchange_id, message != nil ? 1 : 0), 3)
|
||||
if message
|
||||
return message.ack_received(msg) # dispatch to IM_Message
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# process IM 0x02 Read Request
|
||||
# send Ack response now and don't enqueue it
|
||||
#
|
||||
# returns `true` if packet could be sent
|
||||
def send_ack_now(msg)
|
||||
msg.session._message_handler.send_encrypted_ack(msg, false #-not reliable-#)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# send enqueued responses
|
||||
#
|
||||
# self.send_queue is a list of <Matter_IM_Message>
|
||||
#
|
||||
def send_enqueued(responder)
|
||||
var idx = 0
|
||||
while idx < size(self.send_queue)
|
||||
var message = self.send_queue[idx]
|
||||
|
||||
if !message.finish && message.ready
|
||||
message.send_im(responder) # send message
|
||||
end
|
||||
|
||||
if message.finish
|
||||
tasmota.log("MTR: remove IM message exch="+str(message.resp.exchange_id), 3)
|
||||
self.send_queue.remove(idx)
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# find in send_queue by exchangeid
|
||||
#
|
||||
def find_sendqueue_by_exchangeid(exchangeid)
|
||||
if exchangeid == nil return nil end
|
||||
var idx = 0
|
||||
while idx < size(self.send_queue)
|
||||
var message = self.send_queue[idx]
|
||||
if message.get_exchangeid() == exchangeid
|
||||
return message
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# find in send_queue by exchangeid
|
||||
#
|
||||
def remove_sendqueue_by_exchangeid(exchangeid)
|
||||
if exchangeid == nil return end
|
||||
var idx = 0
|
||||
while idx < size(self.send_queue)
|
||||
if self.send_queue[idx].get_exchangeid() == exchangeid
|
||||
self.send_queue.remove(idx)
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Remove any queued message that expired
|
||||
#
|
||||
def expire_sendqueue()
|
||||
var idx = 0
|
||||
while idx < size(self.send_queue)
|
||||
var message = self.send_queue[idx]
|
||||
if tasmota.time_reached(message.expiration)
|
||||
message.reached_timeout()
|
||||
self.send_queue.remove(idx)
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# process IM 0x01 Status Response
|
||||
#
|
||||
# val is the TLV structure
|
||||
# returns `true` if processed, `false` if silently ignored,
|
||||
# or raises an exception
|
||||
def process_read_request(msg, val)
|
||||
var endpoints = self.device.get_active_endpoints()
|
||||
# return true if we handled the response and ack, false instead
|
||||
def process_status_response(msg, val)
|
||||
import string
|
||||
var status = val.findsubval(0, 0xFF)
|
||||
var message = self.find_sendqueue_by_exchangeid(msg.exchange_id)
|
||||
if status == matter.SUCCESS
|
||||
if message
|
||||
return message.status_ok_received(msg) # re-arm the sending of next packets for the same exchange
|
||||
else
|
||||
tasmota.log(string.format("MTR: >OK (%6i) exch=%i not found", msg.session.local_session_id, msg.exchange_id), 3) # don't show 'SUCCESS' to not overflow logs with non-information
|
||||
end
|
||||
else
|
||||
# error
|
||||
tasmota.log(string.format("MTR: >Status ERROR = 0x%02X", status), 2)
|
||||
if message
|
||||
message.status_error_received(msg)
|
||||
self.remove_sendqueue_by_exchangeid(msg.exchange_id)
|
||||
end
|
||||
end
|
||||
return false # we did not ack the message, do it at higher level
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Inner code shared between read_attributes and subscribe_request
|
||||
#
|
||||
# query: `ReadRequestMessage` or `SubscribeRequestMessage`
|
||||
def _inner_process_read_request(session, query, no_log)
|
||||
import string
|
||||
|
||||
### Inner function to be iterated upon
|
||||
# ret is the ReportDataMessage list to send back
|
||||
@ -138,7 +220,7 @@ class Matter_IM
|
||||
attr_name = attr_name ? " (" + attr_name + ")" : ""
|
||||
# tasmota.log(string.format("MTR: Read Attribute " + str(ctx) + (attr_name ? " (" + attr_name + ")" : ""), 2)
|
||||
# Special case to report unsupported item, if pi==nil
|
||||
var res = (pi != nil) ? pi.read_attribute(msg, ctx) : nil
|
||||
var res = (pi != nil) ? pi.read_attribute(session, ctx) : nil
|
||||
if res != nil
|
||||
var a1 = matter.AttributeReportIB()
|
||||
a1.attribute_data = matter.AttributeDataIB()
|
||||
@ -150,7 +232,9 @@ class Matter_IM
|
||||
a1.attribute_data.data = res
|
||||
|
||||
ret.attribute_reports.push(a1)
|
||||
tasmota.log(string.format("MTR: Read_Attr %s%s - %s", str(ctx), attr_name, str(res)), 2)
|
||||
if !no_log
|
||||
tasmota.log(string.format("MTR: >Read_Attr (%6i) %s%s - %s", session.local_session_id, str(ctx), attr_name, str(res)), 2)
|
||||
end
|
||||
return true # stop expansion since we have a value
|
||||
elif ctx.status != nil
|
||||
if direct
|
||||
@ -164,105 +248,104 @@ class Matter_IM
|
||||
a1.attribute_status.status.status = ctx.status
|
||||
|
||||
ret.attribute_reports.push(a1)
|
||||
tasmota.log(string.format("MTR: Read_Attr %s%s - STATUS: 0x%02X %s", str(ctx), attr_name, ctx.status, ctx.status == matter.UNSUPPORTED_ATTRIBUTE ? "UNSUPPORTED_ATTRIBUTE" : ""), 2)
|
||||
tasmota.log(string.format("MTR: >Read_Attr (%6i) %s%s - STATUS: 0x%02X %s", session.local_session_id, str(ctx), attr_name, ctx.status, ctx.status == matter.UNSUPPORTED_ATTRIBUTE ? "UNSUPPORTED_ATTRIBUTE" : ""), 2)
|
||||
return true
|
||||
end
|
||||
else
|
||||
tasmota.log(string.format("MTR: Read_Attr %s%s - IGNORED", str(ctx), attr_name), 2)
|
||||
tasmota.log(string.format("MTR: >Read_Attr (%6i) %s%s - IGNORED", session.local_session_id, str(ctx), attr_name), 2)
|
||||
# ignore if content is nil and status is undefined
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
var endpoints = self.device.get_active_endpoints()
|
||||
# structure is `ReadRequestMessage` 10.6.2 p.558
|
||||
tasmota.log("MTR: IM:read_request processing start", 3)
|
||||
var ctx = matter.Response_container()
|
||||
var ctx = matter.Path()
|
||||
|
||||
var query = matter.ReadRequestMessage().from_TLV(val)
|
||||
if query.attributes_requests != nil
|
||||
# prepare the response
|
||||
var ret = matter.ReportDataMessage()
|
||||
# ret.suppress_response = true
|
||||
ret.attribute_reports = []
|
||||
# prepare the response
|
||||
var ret = matter.ReportDataMessage()
|
||||
# ret.suppress_response = true
|
||||
ret.attribute_reports = []
|
||||
|
||||
for q:query.attributes_requests
|
||||
# need to do expansion here
|
||||
ctx.endpoint = q.endpoint
|
||||
ctx.cluster = q.cluster
|
||||
ctx.attribute = q.attribute
|
||||
ctx.status = matter.UNSUPPORTED_ATTRIBUTE #default error if returned `nil`
|
||||
|
||||
# expand endpoint
|
||||
if ctx.endpoint == nil || ctx.cluster == nil || ctx.attribute == nil
|
||||
# we need expansion, log first
|
||||
if ctx.cluster != nil && ctx.attribute != nil
|
||||
var attr_name = matter.get_attribute_name(ctx.cluster, ctx.attribute)
|
||||
tasmota.log("MTR: Read_Attr " + str(ctx) + (attr_name ? " (" + attr_name + ")" : ""), 2)
|
||||
else
|
||||
tasmota.log("MTR: Read_Attr " + str(ctx), 2)
|
||||
end
|
||||
|
||||
for q:query.attributes_requests
|
||||
# need to do expansion here
|
||||
ctx.endpoint = q.endpoint
|
||||
ctx.cluster = q.cluster
|
||||
ctx.attribute = q.attribute
|
||||
ctx.status = matter.UNSUPPORTED_ATTRIBUTE #default error if returned `nil`
|
||||
|
||||
# expand endpoint
|
||||
if ctx.endpoint == nil || ctx.cluster == nil || ctx.attribute == nil
|
||||
# we need expansion, log first
|
||||
if ctx.cluster != nil && ctx.attribute != nil
|
||||
var attr_name = matter.get_attribute_name(ctx.cluster, ctx.attribute)
|
||||
tasmota.log(string.format("MTR: >Read_Attr (%6i) %s", session.local_session_id, str(ctx) + (attr_name ? " (" + attr_name + ")" : "")), 2)
|
||||
else
|
||||
tasmota.log(string.format("MTR: >Read_Attr (%6i) %s", session.local_session_id, str(ctx)), 2)
|
||||
end
|
||||
|
||||
# implement concrete expansion
|
||||
self.device.process_attribute_expansion(ctx,
|
||||
/ pi, ctx, direct -> read_single_attribute(ret, pi, ctx, direct)
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
tasmota.log("MTR: ReportDataMessage=" + str(ret), 3)
|
||||
tasmota.log("MTR: ReportDataMessageTLV=" + str(ret.to_TLV()), 3)
|
||||
# implement concrete expansion
|
||||
self.device.process_attribute_expansion(ctx,
|
||||
/ pi, ctx, direct -> read_single_attribute(ret, pi, ctx, direct)
|
||||
)
|
||||
end
|
||||
|
||||
# send the reponse that may need to be chunked if too large to fit in a single UDP message
|
||||
self.send_attr_report(msg, ret)
|
||||
# tasmota.log("MTR: ReportDataMessage=" + str(ret), 3)
|
||||
# tasmota.log("MTR: ReportDataMessageTLV=" + str(ret.to_TLV()), 3)
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# process IM 0x02 Read Request
|
||||
#
|
||||
# val is the TLV structure
|
||||
# returns `true` if processed, `false` if silently ignored,
|
||||
# or raises an exception
|
||||
def process_read_request(msg, val)
|
||||
var query = matter.ReadRequestMessage().from_TLV(val)
|
||||
if query.attributes_requests != nil
|
||||
var ret = self._inner_process_read_request(msg.session, query)
|
||||
self.send_report_data(msg, ret)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def send_attr_report(msg, ret)
|
||||
# class to keep the current chunked reponse
|
||||
class Matter_Attr_Report
|
||||
var ret # return structure as ReportDataMessage TLV structure
|
||||
var resp # response Frame (to keep all fields like session or remote_ip/port)
|
||||
var expiration
|
||||
#############################################################
|
||||
# process IM 0x03 Subscribe Request
|
||||
#
|
||||
def subscribe_request(msg, val)
|
||||
import string
|
||||
var query = matter.SubscribeRequestMessage().from_TLV(val)
|
||||
|
||||
if !query.keep_subscriptions
|
||||
self.subs_shop.remove_by_session(msg.session) # if `keep_subscriptions`, kill all subscriptions from current session
|
||||
end
|
||||
|
||||
# compute the acceptable size
|
||||
tasmota.log("MTR: received SubscribeRequestMessage=" + str(query), 3)
|
||||
|
||||
var msg_sz = 0
|
||||
var elements = 0
|
||||
if size(ret.attribute_reports) > 0
|
||||
msg_sz = size(ret.attribute_reports[0].to_TLV().encode())
|
||||
elements = 1
|
||||
end
|
||||
while msg_sz < self.MAX_MESSAGE && elements < size(ret.attribute_reports)
|
||||
var next_sz = size(ret.attribute_reports[elements].to_TLV().encode())
|
||||
if msg_sz + next_sz < self.MAX_MESSAGE
|
||||
msg_sz += next_sz
|
||||
elements += 1
|
||||
end
|
||||
end
|
||||
|
||||
var next_elemnts = ret.attribute_reports[elements .. ]
|
||||
ret.attribute_reports = ret.attribute_reports[0 .. elements - 1]
|
||||
|
||||
if size(next_elemnts) > 0
|
||||
ret.more_chunked_messages = true
|
||||
end
|
||||
|
||||
var resp = msg.build_response(0x05 #-Report Data-#, true)
|
||||
resp.encode(ret.to_TLV().encode()) # payload in cleartext
|
||||
resp.encrypt()
|
||||
self.responder.send_response(resp.raw, msg.remote_ip, msg.remote_port, resp.message_counter)
|
||||
|
||||
if size(next_elemnts) > 0
|
||||
ret.attribute_reports = next_elemnts
|
||||
var chunked_next = Matter_Attr_Report()
|
||||
chunked_next.ret = ret
|
||||
chunked_next.resp = resp
|
||||
chunked_next.expiration = tasmota.millis() + self.MSG_TIMEOUT
|
||||
var sub = self.subs_shop.new_subscription(msg.session, query)
|
||||
|
||||
# expand a string with all attributes requested
|
||||
var attr_req = []
|
||||
var ctx = matter.Path()
|
||||
for q:query.attributes_requests
|
||||
ctx.endpoint = q.endpoint
|
||||
ctx.cluster = q.cluster
|
||||
ctx.attribute = q.attribute
|
||||
attr_req.push(str(ctx))
|
||||
end
|
||||
tasmota.log(string.format("MTR: >Subscribe (%6i) %s (min=%i, max=%i, keep=%i) sub=%i",
|
||||
msg.session.local_session_id, attr_req.concat(" "), sub.min_interval, sub.max_interval, query.keep_subscriptions ? 1 : 0, sub.subscription_id), 2)
|
||||
|
||||
var ret = self._inner_process_read_request(msg.session, query, true #-no_log-#)
|
||||
# ret is of type `Matter_ReportDataMessage`
|
||||
ret.subscription_id = sub.subscription_id # enrich with subscription id TODO
|
||||
self.send_subscribe_response(msg, ret, sub)
|
||||
return true
|
||||
end
|
||||
|
||||
#############################################################
|
||||
@ -274,8 +357,8 @@ class Matter_IM
|
||||
def process_invoke_request(msg, val)
|
||||
import string
|
||||
# structure is `ReadRequestMessage` 10.6.2 p.558
|
||||
tasmota.log("MTR: IM:invoke_request processing start", 3)
|
||||
var ctx = matter.Response_container()
|
||||
tasmota.log("MTR: IM:invoke_request processing start", 4)
|
||||
var ctx = matter.Path()
|
||||
|
||||
var query = matter.InvokeRequestMessage().from_TLV(val)
|
||||
if query.invoke_requests != nil
|
||||
@ -291,11 +374,22 @@ class Matter_IM
|
||||
ctx.status = matter.UNSUPPORTED_COMMAND #default error if returned `nil`
|
||||
|
||||
var cmd_name = matter.get_command_name(ctx.cluster, ctx.command)
|
||||
if cmd_name == nil cmd_name = string.format("0x%04X/0x02X", ctx.cluster, ctx.command) end
|
||||
tasmota.log(string.format("MTR: >Received_cmd %s from [%s]:%i", cmd_name, msg.remote_ip, msg.remote_port), 2)
|
||||
var res = self.responder.device.invoke_request(msg, q.command_fields, ctx)
|
||||
var res = self.device.invoke_request(msg.session, q.command_fields, ctx)
|
||||
var params_log = (ctx.log != nil) ? "(" + str(ctx.log) + ") " : ""
|
||||
tasmota.log(string.format("MTR: >Command (%6i) %s %s %s", msg.session.local_session_id, str(ctx), cmd_name ? cmd_name : "", params_log), 2)
|
||||
ctx.log = nil
|
||||
var a1 = matter.InvokeResponseIB()
|
||||
if res != nil
|
||||
if res == true || ctx.status == matter.SUCCESS # special case, just respond ok
|
||||
a1.status = matter.CommandStatusIB()
|
||||
a1.status.command_path = matter.CommandPathIB()
|
||||
a1.status.command_path.endpoint = ctx.endpoint
|
||||
a1.status.command_path.cluster = ctx.cluster
|
||||
a1.status.command_path.command = ctx.command
|
||||
a1.status.status = matter.StatusIB()
|
||||
a1.status.status.status = matter.SUCCESS
|
||||
ret.invoke_responses.push(a1)
|
||||
tasmota.log(string.format("MTR: <Replied (%6i) OK exch=%i", msg.session.local_session_id, msg.exchange_id), 2)
|
||||
elif res != nil
|
||||
a1.command = matter.CommandDataIB()
|
||||
a1.command.command_path = matter.CommandPathIB()
|
||||
a1.command.command_path.endpoint = ctx.endpoint
|
||||
@ -305,8 +399,7 @@ class Matter_IM
|
||||
ret.invoke_responses.push(a1)
|
||||
|
||||
cmd_name = matter.get_command_name(ctx.cluster, ctx.command)
|
||||
if cmd_name == nil cmd_name = string.format("0x%04X/0x%02X", ctx.cluster, ctx.command) end
|
||||
tasmota.log(string.format("MTR: <Replied_cmd %s", cmd_name), 2)
|
||||
tasmota.log(string.format("MTR: <Replied (%6i) %s %s", msg.session.local_session_id, str(ctx), cmd_name ? cmd_name : ""), 2)
|
||||
elif ctx.status != nil
|
||||
a1.status = matter.CommandStatusIB()
|
||||
a1.status.command_path = matter.CommandPathIB()
|
||||
@ -316,48 +409,33 @@ class Matter_IM
|
||||
a1.status.status = matter.StatusIB()
|
||||
a1.status.status.status = ctx.status
|
||||
ret.invoke_responses.push(a1)
|
||||
tasmota.log(string.format("MTR: <Replied (%6i) Status=0x%02X exch=%i", msg.session.local_session_id, ctx.status, msg.exchange_id), 2)
|
||||
else
|
||||
tasmota.log(string.format("MTR: _Ignore (%6i) exch=%i", msg.session.local_session_id, msg.exchange_id), 2)
|
||||
# ignore if content is nil and status is undefined
|
||||
end
|
||||
end
|
||||
|
||||
tasmota.log("MTR: invoke_responses="+str(ret.invoke_responses), 3)
|
||||
tasmota.log("MTR: invoke_responses="+str(ret.invoke_responses), 4)
|
||||
if size(ret.invoke_responses) > 0
|
||||
tasmota.log("MTR: InvokeResponse=" + str(ret), 3)
|
||||
tasmota.log("MTR: InvokeResponse=" + str(ret), 4)
|
||||
tasmota.log("MTR: InvokeResponseTLV=" + str(ret.to_TLV()), 3)
|
||||
|
||||
var resp = msg.build_response(0x09 #-Invoke Response-#, true)
|
||||
resp.encode(ret.to_TLV().encode()) # payload in cleartext
|
||||
resp.encrypt()
|
||||
|
||||
self.responder.send_response(resp.raw, msg.remote_ip, msg.remote_port, resp.message_counter)
|
||||
elif msg.x_flag_r # nothing to respond, check if we need a standalone ack
|
||||
var resp = msg.build_standalone_ack()
|
||||
resp.encode()
|
||||
resp.encrypt()
|
||||
# no ecnryption required for ACK
|
||||
self.responder.send_response(resp.raw, msg.remote_ip, msg.remote_port, resp.message_counter)
|
||||
self.send_invoke_response(msg, ret)
|
||||
else
|
||||
return false # we don't send anything, hence the responder sends a simple packet ack
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# process IM 0x03 Subscribe Request
|
||||
#
|
||||
def subscribe_request(msg, val)
|
||||
import string
|
||||
var query = matter.SubscribeRequestMessage().from_TLV(val)
|
||||
tasmota.log("MTR: received SubscribeRequestMessage=" + str(query), 3)
|
||||
return false
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# process IM 0x04 Subscribe Response
|
||||
#
|
||||
def subscribe_response(msg, val)
|
||||
import string
|
||||
var query = matter.SubscribeResponseMessage().from_TLV(val)
|
||||
tasmota.log("MTR: received SubscribeResponsetMessage=" + str(query), 3)
|
||||
tasmota.log("MTR: received SubscribeResponsetMessage=" + str(query), 2)
|
||||
return false
|
||||
end
|
||||
|
||||
@ -367,7 +445,7 @@ class Matter_IM
|
||||
def report_data(msg, val)
|
||||
import string
|
||||
var query = matter.ReportDataMessage().from_TLV(val)
|
||||
tasmota.log("MTR: received ReportDataMessage=" + str(query), 3)
|
||||
tasmota.log("MTR: received ReportDataMessage=" + str(query), 2)
|
||||
return false
|
||||
end
|
||||
|
||||
@ -378,7 +456,100 @@ class Matter_IM
|
||||
import string
|
||||
var query = matter.WriteRequestMessage().from_TLV(val)
|
||||
tasmota.log("MTR: received WriteRequestMessage=" + str(query), 3)
|
||||
return false
|
||||
|
||||
var suppress_response = query.suppress_response
|
||||
# var timed_request = query.timed_request # TODO not supported
|
||||
# var more_chunked_messages = query.more_chunked_messages # TODO not supported
|
||||
|
||||
var endpoints = self.device.get_active_endpoints()
|
||||
|
||||
### Inner function to be iterated upon
|
||||
# ret is the ReportDataMessage list to send back
|
||||
# ctx is the context with endpoint/cluster/attribute
|
||||
# val is the TLV object containing the value
|
||||
# direct is true if error is reported, false if error is silently ignored
|
||||
#
|
||||
# if `pi` is nil, just report the status for ctx.status
|
||||
#
|
||||
# should return true if answered, false if failed
|
||||
def write_single_attribute(ret, pi, ctx, write_data, direct)
|
||||
import string
|
||||
var attr_name = matter.get_attribute_name(ctx.cluster, ctx.attribute)
|
||||
attr_name = attr_name ? " (" + attr_name + ")" : ""
|
||||
# tasmota.log(string.format("MTR: Read Attribute " + str(ctx) + (attr_name ? " (" + attr_name + ")" : ""), 2)
|
||||
# Special case to report unsupported item, if pi==nil
|
||||
ctx.status = matter.UNSUPPORTED_WRITE
|
||||
var res = (pi != nil) ? pi.write_attribute(msg.session, ctx, write_data) : nil
|
||||
if res ctx.status = matter.SUCCESS end # if the cb returns true, the request was processed
|
||||
if ctx.status != nil
|
||||
if direct
|
||||
var a1 = matter.AttributeStatusIB()
|
||||
a1.path = matter.AttributePathIB()
|
||||
a1.status = matter.StatusIB()
|
||||
a1.path.endpoint = ctx.endpoint
|
||||
a1.path.cluster = ctx.cluster
|
||||
a1.path.attribute = ctx.attribute
|
||||
a1.status.status = ctx.status
|
||||
|
||||
ret.write_responses.push(a1)
|
||||
tasmota.log(string.format("MTR: Write_Attr %s%s - STATUS: 0x%02X %s", str(ctx), attr_name, ctx.status, ctx.status == matter.SUCCESS ? "SUCCESS" : ""), 2)
|
||||
return true
|
||||
end
|
||||
else
|
||||
tasmota.log(string.format("MTR: Write_Attr %s%s - IGNORED", str(ctx), attr_name), 2)
|
||||
# ignore if content is nil and status is undefined
|
||||
end
|
||||
end
|
||||
|
||||
# structure is `ReadRequestMessage` 10.6.2 p.558
|
||||
tasmota.log("MTR: IM:write_request processing start", 4)
|
||||
var ctx = matter.Path()
|
||||
|
||||
if query.write_requests != nil
|
||||
# prepare the response
|
||||
var ret = matter.WriteResponseMessage()
|
||||
# ret.suppress_response = true
|
||||
ret.write_responses = []
|
||||
|
||||
for q:query.write_requests # q is AttributeDataIB
|
||||
var path = q.path
|
||||
var write_data = q.data
|
||||
# need to do expansion here
|
||||
ctx.endpoint = path.endpoint
|
||||
ctx.cluster = path.cluster
|
||||
ctx.attribute = path.attribute
|
||||
ctx.status = matter.UNSUPPORTED_ATTRIBUTE #default error if returned `nil`
|
||||
|
||||
# return an error if the expansion is illegal
|
||||
if ctx.cluster == nil || ctx.attribute == nil
|
||||
# force INVALID_ACTION reporting
|
||||
ctx.status = matter.INVALID_ACTION
|
||||
write_single_attribute(ret, nil, ctx, nil, true)
|
||||
continue
|
||||
end
|
||||
|
||||
# expand endpoint
|
||||
if ctx.endpoint == nil
|
||||
# we need expansion, log first
|
||||
var attr_name = matter.get_attribute_name(ctx.cluster, ctx.attribute)
|
||||
tasmota.log("MTR: Write_Attr " + str(ctx) + (attr_name ? " (" + attr_name + ")" : ""), 2)
|
||||
end
|
||||
|
||||
# implement concrete expansion
|
||||
self.device.process_attribute_expansion(ctx,
|
||||
/ pi, ctx, direct -> write_single_attribute(ret, pi, ctx, write_data, direct)
|
||||
)
|
||||
end
|
||||
|
||||
tasmota.log("MTR: ReportWriteMessage=" + str(ret), 4)
|
||||
tasmota.log("MTR: ReportWriteMessageTLV=" + str(ret.to_TLV()), 3)
|
||||
|
||||
# send the reponse that may need to be chunked if too large to fit in a single UDP message
|
||||
if !suppress_response
|
||||
self.send_write_response(msg, ret)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
#############################################################
|
||||
@ -387,7 +558,7 @@ class Matter_IM
|
||||
def process_write_response(msg, val)
|
||||
import string
|
||||
var query = matter.WriteResponseMessage().from_TLV(val)
|
||||
tasmota.log("MTR: received WriteResponseMessage=" + str(query), 3)
|
||||
tasmota.log("MTR: received WriteResponseMessage=" + str(query), 2)
|
||||
return false
|
||||
end
|
||||
|
||||
@ -397,7 +568,7 @@ class Matter_IM
|
||||
def process_invoke_response(msg, val)
|
||||
import string
|
||||
var query = matter.InvokeResponseMessage().from_TLV(val)
|
||||
tasmota.log("MTR: received InvokeResponseMessage=" + str(query), 3)
|
||||
tasmota.log("MTR: received InvokeResponseMessage=" + str(query), 2)
|
||||
return false
|
||||
end
|
||||
|
||||
@ -409,23 +580,113 @@ class Matter_IM
|
||||
var query = matter.TimedRequestMessage().from_TLV(val)
|
||||
tasmota.log("MTR: received TimedRequestMessage=" + str(query), 3)
|
||||
|
||||
tasmota.log(string.format("MTR: >Received_IM TimedRequest=%i from [%s]:%i", query.timeout, msg.remote_ip, msg.remote_port), 2)
|
||||
tasmota.log(string.format("MTR: >Command (%6i) TimedRequest=%i", msg.session.local_session_id, query.timeout), 2)
|
||||
|
||||
# Send success status report
|
||||
var sr = matter.StatusResponseMessage()
|
||||
sr.status = matter.SUCCESS
|
||||
var resp = msg.build_response(0x01 #-Status Response-#, true #-reliable-#)
|
||||
resp.encode(sr.to_TLV().encode()) # payload in cleartext
|
||||
resp.encrypt()
|
||||
self.responder.send_response(resp.raw, msg.remote_ip, msg.remote_port, resp.message_counter)
|
||||
self.send_status(msg, matter.SUCCESS)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# send regular update for data subscribed
|
||||
#
|
||||
def send_subscribe_update(sub)
|
||||
import string
|
||||
var session = sub.session
|
||||
|
||||
# create a fake read request to feed to the ReportData
|
||||
var fake_read = matter.ReadRequestMessage()
|
||||
fake_read.fabric_filtered = false
|
||||
fake_read.attributes_requests = []
|
||||
|
||||
for ctx: sub.updates
|
||||
var p1 = matter.AttributePathIB()
|
||||
p1.endpoint = ctx.endpoint
|
||||
p1.cluster = ctx.cluster
|
||||
p1.attribute = ctx.attribute
|
||||
fake_read.attributes_requests.push(p1)
|
||||
end
|
||||
|
||||
tasmota.log(string.format("MTR: <Sub_Data (%6i) sub=%i", session.local_session_id, sub.subscription_id), 2)
|
||||
sub.is_keep_alive = false # sending an actual data update
|
||||
|
||||
var ret = self._inner_process_read_request(session, fake_read)
|
||||
ret.suppress_response = false
|
||||
ret.subscription_id = sub.subscription_id
|
||||
|
||||
var report_data_msg = matter.IM_ReportDataSubscribed(session._message_handler, session, ret, sub)
|
||||
self.send_queue.push(report_data_msg) # push message to queue
|
||||
self.send_enqueued(session._message_handler) # and send queued messages now
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# send regular update for data subscribed
|
||||
#
|
||||
def send_subscribe_heartbeat(sub)
|
||||
import string
|
||||
var session = sub.session
|
||||
|
||||
tasmota.log(string.format("MTR: <Sub_Alive (%6i) sub=%i", session.local_session_id, sub.subscription_id), 2)
|
||||
sub.is_keep_alive = true # sending keep-alive
|
||||
|
||||
# prepare the response
|
||||
var ret = matter.ReportDataMessage()
|
||||
ret.suppress_response = true
|
||||
ret.subscription_id = sub.subscription_id
|
||||
|
||||
var report_data_msg = matter.IM_SubscribedHeartbeat(session._message_handler, session, ret, sub)
|
||||
self.send_queue.push(report_data_msg) # push message to queue
|
||||
self.send_enqueued(session._message_handler) # and send queued messages now
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# send_status
|
||||
#
|
||||
def send_status(msg, status)
|
||||
self.send_queue.push(matter.IM_Status(msg, status))
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# send_invoke_response
|
||||
#
|
||||
def send_invoke_response(msg, data)
|
||||
self.send_queue.push(matter.IM_InvokeResponse(msg, data))
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# send_invoke_response
|
||||
#
|
||||
def send_write_response(msg, data)
|
||||
self.send_queue.push(matter.IM_WriteResponse(msg, data))
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# send_report_data
|
||||
#
|
||||
def send_report_data(msg, data)
|
||||
self.send_queue.push(matter.IM_ReportData(msg, data))
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# send_report_data
|
||||
#
|
||||
def send_subscribe_response(msg, data, sub)
|
||||
self.send_queue.push(matter.IM_SubscribeResponse(msg, data, sub))
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# placeholder, nothing to run for now
|
||||
def every_second()
|
||||
self.expire_sendqueue()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# dispatch every 250ms click to sub-objects that need it
|
||||
def every_250ms()
|
||||
self.subs_shop.every_250ms()
|
||||
end
|
||||
|
||||
end
|
||||
matter.IM = Matter_IM
|
||||
|
||||
|
@ -670,7 +670,7 @@ class Matter_SubscribeRequestMessage : Matter_IM_Message_base
|
||||
var keep_subscriptions # bool
|
||||
var min_interval_floor # u16
|
||||
var max_interval_ceiling # u16
|
||||
var attribute_requests # array of AttributePathIB
|
||||
var attributes_requests # array of AttributePathIB
|
||||
var event_requests # array of EventPathIB
|
||||
var event_filters # array of EventFilterIB
|
||||
var fabric_filtered # bool
|
||||
@ -679,13 +679,13 @@ class Matter_SubscribeRequestMessage : Matter_IM_Message_base
|
||||
# decode from TLV
|
||||
def from_TLV(val)
|
||||
if val == nil return nil end
|
||||
self.keep_subscriptions = val.findsubval(0)
|
||||
self.min_interval_floor = val.findsubval(1)
|
||||
self.max_interval_ceiling = val.findsubval(2)
|
||||
self.attribute_requests = self.from_TLV_array(val.findsubval(3), matter.AttributePathIB)
|
||||
self.keep_subscriptions = val.findsubval(0, false)
|
||||
self.min_interval_floor = val.findsubval(1, 0)
|
||||
self.max_interval_ceiling = val.findsubval(2, 60)
|
||||
self.attributes_requests = self.from_TLV_array(val.findsubval(3), matter.AttributePathIB)
|
||||
self.event_requests = self.from_TLV_array(val.findsubval(4), matter.EventPathIB)
|
||||
self.event_filters = self.from_TLV_array(val.findsubval(5), matter.EventFilterIB)
|
||||
self.fabric_filtered = val.findsubval(7)
|
||||
self.fabric_filtered = val.findsubval(7, false)
|
||||
self.data_version_filters = self.from_TLV_array(val.findsubval(8), matter.DataVersionFilterIB)
|
||||
return self
|
||||
end
|
||||
@ -696,7 +696,7 @@ class Matter_SubscribeRequestMessage : Matter_IM_Message_base
|
||||
s.add_TLV(0, TLV.BOOL, self.keep_subscriptions)
|
||||
s.add_TLV(1, TLV.U2, self.min_interval_floor)
|
||||
s.add_TLV(2, TLV.U2, self.max_interval_ceiling)
|
||||
self.to_TLV_array(s, 3, self.attribute_requests)
|
||||
self.to_TLV_array(s, 3, self.attributes_requests)
|
||||
self.to_TLV_array(s, 4, self.event_requests)
|
||||
self.to_TLV_array(s, 5, self.event_filters)
|
||||
s.add_TLV(7, TLV.BOOL, self.fabric_filtered)
|
||||
@ -880,7 +880,7 @@ a = matter.AttributePathIB().from_TLV(m)
|
||||
assert(str(a) == "[00]0030/0000")
|
||||
m = a.to_TLV()
|
||||
assert(m.tostring() == "[[2 = 0U, 3 = 48U, 4 = 0U]]")
|
||||
assert(m.encode() == bytes("1724020024033024040018"))
|
||||
assert(m.tlv2raw() == bytes("1724020024033024040018"))
|
||||
|
||||
|
||||
# create DataVersionFilterIB from scratch
|
||||
@ -894,7 +894,7 @@ d = matter.DataVersionFilterIB()
|
||||
d.path = c
|
||||
d.data_version = 10
|
||||
assert(str(d.to_TLV()) == '{0 = [[1 = 1U, 2 = 32U]], 1 = 10U}')
|
||||
assert(d.to_TLV().encode() == bytes('1537002401012402201824010A18'))
|
||||
assert(d.to_TLV().tlv2raw() == bytes('1537002401012402201824010A18'))
|
||||
|
||||
# decode DataVersionFilterIB from scratch
|
||||
m = matter.TLV.parse(bytes("1537002401012402201824010A18"))
|
||||
@ -932,7 +932,7 @@ a1.attribute_data.data = matter.TLV.create_TLV(matter.TLV.UTF1, "Tasmota")
|
||||
assert(str(a1.to_TLV()) == '{0 = {0 = [[2 = 0U, 3 = 48U, 4 = 0U]], 1 = [[0 = 0U, 1 = 0U]]}, 1 = {0 = 1U, 1 = [[2 = 0U, 3 = 48U, 4 = 0U]], 2 = "Tasmota"}}')
|
||||
r.attribute_reports.push(a1)
|
||||
#{0 = 1U, 1 = [{0 = {0 = [[2 = 0U, 3 = 48U, 4 = 0U]], 1 = [[0 = 0U, 1 = 0U]]}, 1 = {0 = 1U, 1 = [[2 = 0U, 3 = 48U, 4 = 0U]], 2 = "Tasmota"}}]}
|
||||
assert(r.to_TLV().encode() == bytes('1524000136011535003700240200240330240400183701240000240100181835012400013701240200240330240400182C02075461736D6F746118181818'))
|
||||
assert(r.to_TLV().tlv2raw() == bytes('1524000136011535003700240200240330240400183701240000240100181835012400013701240200240330240400182C02075461736D6F746118181818'))
|
||||
|
||||
|
||||
# <Matter_AttributeReportIB:{
|
||||
|
456
lib/libesp32/berry_matter/src/embedded/Matter_IM_Message.be
Normal file
456
lib/libesp32/berry_matter/src/embedded/Matter_IM_Message.be
Normal file
@ -0,0 +1,456 @@
|
||||
#
|
||||
# Matter_IM_Message.be - suppport for Matter Interaction Model messages responses
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import matter
|
||||
|
||||
#@ solidify:Matter_IM_Message,weak
|
||||
#@ solidify:Matter_IM_Status,weak
|
||||
#@ solidify:Matter_IM_InvokeResponse,weak
|
||||
#@ solidify:Matter_IM_WriteResponse,weak
|
||||
#@ solidify:Matter_IM_ReportData,weak
|
||||
#@ solidify:Matter_IM_ReportDataSubscribed,weak
|
||||
#@ solidify:Matter_IM_SubscribedHeartbeat,weak
|
||||
#@ solidify:Matter_IM_SubscribeResponse,weak
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM_Message
|
||||
#
|
||||
# Superclass for all IM responses
|
||||
#################################################################################
|
||||
class Matter_IM_Message
|
||||
static var MSG_TIMEOUT = 5000 # 5s
|
||||
var expiration # expiration time for the reporting
|
||||
var resp # response Frame object
|
||||
var ready # bool: ready to send (true) or wait (false)
|
||||
var finish # if true, the message is removed from the queue
|
||||
var data # TLV data of the response (if any)
|
||||
var last_counter # counter value of last sent packet (to match ack)
|
||||
|
||||
# build a response message stub
|
||||
def init(msg, opcode, reliable)
|
||||
self.resp = msg.build_response(opcode, reliable)
|
||||
self.ready = true # by default send immediately
|
||||
self.expiration = tasmota.millis() + self.MSG_TIMEOUT
|
||||
self.last_counter = 0 # avoid `nil` value
|
||||
self.finish = false
|
||||
end
|
||||
|
||||
# the message is being removed due to expiration
|
||||
def reached_timeout()
|
||||
end
|
||||
|
||||
# ack received for previous message, proceed to next (if any)
|
||||
# return true if we manage the ack ourselves, false if it needs to be done upper
|
||||
def ack_received(msg)
|
||||
tasmota.log("MTR: IM_Message ack_received exch="+str(self.resp.exchange_id), 3)
|
||||
self.expiration = tasmota.millis() + self.MSG_TIMEOUT # give more time
|
||||
return false
|
||||
end
|
||||
|
||||
# Status Report OK received for previous message, proceed to next (if any)
|
||||
# return true if we manage the ack ourselves, false if it needs to be done upper
|
||||
def status_ok_received(msg)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: IM_Message status_ok_received exch=%i", self.resp.exchange_id), 3)
|
||||
self.expiration = tasmota.millis() + self.MSG_TIMEOUT # give more time
|
||||
if msg
|
||||
self.resp = msg.build_response(self.resp.opcode, self.resp.x_flag_r, self.resp) # update packet
|
||||
end
|
||||
self.ready = true
|
||||
return true
|
||||
end
|
||||
|
||||
# we received an ACK error, do any necessary cleaning
|
||||
def status_error_received(msg)
|
||||
end
|
||||
|
||||
# get the exchange-id for this message
|
||||
def get_exchangeid()
|
||||
return self.resp.exchange_id
|
||||
end
|
||||
|
||||
# default responder for data
|
||||
def send_im(responder)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: IM_Message send_im exch=%i ready=%i", self.resp.exchange_id, self.ready ? 1 : 0), 3)
|
||||
if !self.ready return false end
|
||||
var resp = self.resp
|
||||
resp.encode_frame(self.data.to_TLV().tlv2raw()) # payload in cleartext
|
||||
resp.encrypt()
|
||||
tasmota.log(string.format("MTR: <snd (%6i) id=%i exch=%i rack=%s", resp.session.local_session_id, resp.message_counter, resp.exchange_id, resp.ack_message_counter),3)
|
||||
responder.send_response_frame(resp)
|
||||
self.last_counter = resp.message_counter
|
||||
self.finish = true # by default we remove the packet after it is sent
|
||||
end
|
||||
|
||||
end
|
||||
matter.IM_Message = Matter_IM_Message
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM_Status
|
||||
#
|
||||
# Send a simple Status Message
|
||||
#################################################################################
|
||||
class Matter_IM_Status : Matter_IM_Message
|
||||
|
||||
def init(msg, status)
|
||||
super(self).init(msg, 0x01 #-Status Response-#, true #-reliable-#)
|
||||
var sr = matter.StatusResponseMessage()
|
||||
sr.status = status
|
||||
self.data = sr
|
||||
end
|
||||
end
|
||||
matter.IM_Status = Matter_IM_Status
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM_InvokeResponse
|
||||
#
|
||||
# Send a simple Status Message
|
||||
#################################################################################
|
||||
class Matter_IM_InvokeResponse : Matter_IM_Message
|
||||
|
||||
def init(msg, data)
|
||||
super(self).init(msg, 0x09 #-Invoke Response-#, true)
|
||||
self.data = data
|
||||
end
|
||||
end
|
||||
matter.IM_InvokeResponse = Matter_IM_InvokeResponse
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM_WriteResponse
|
||||
#
|
||||
# Send a simple Status Message
|
||||
#################################################################################
|
||||
class Matter_IM_WriteResponse : Matter_IM_Message
|
||||
|
||||
def init(msg, data)
|
||||
super(self).init(msg, 0x07 #-Write Response-#, true)
|
||||
self.data = data
|
||||
end
|
||||
|
||||
end
|
||||
matter.IM_WriteResponse = Matter_IM_WriteResponse
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM_ReportData
|
||||
#
|
||||
# Report Data for a Read Request
|
||||
#################################################################################
|
||||
class Matter_IM_ReportData : Matter_IM_Message
|
||||
static var MAX_MESSAGE = 1200 # max bytes size for a single TLV worklaod
|
||||
# the maximum MTU is 1280, which leaves 80 bytes for the rest of the message
|
||||
# section 4.4.4 (p. 114)
|
||||
|
||||
def init(msg, data)
|
||||
super(self).init(msg, 0x05 #-Report Data-#, true)
|
||||
self.data = data
|
||||
end
|
||||
|
||||
# default responder for data
|
||||
def send_im(responder)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: IM_ReportData send_im exch=%i ready=%i", self.resp.exchange_id, self.ready ? 1 : 0), 3)
|
||||
if !self.ready return false end
|
||||
var resp = self.resp # response frame object
|
||||
var data = self.data # TLV data of the response (if any)
|
||||
var was_chunked = data.more_chunked_messages # is this following a chunked packet?
|
||||
|
||||
# compute the acceptable size
|
||||
var msg_sz = 0 # message size up to now
|
||||
var elements = 0 # number of elements added
|
||||
var sz_attribute_reports = (data.attribute_reports != nil) ? size(data.attribute_reports) : 0
|
||||
if sz_attribute_reports > 0
|
||||
msg_sz = data.attribute_reports[0].to_TLV().encode_len()
|
||||
elements = 1
|
||||
end
|
||||
while msg_sz < self.MAX_MESSAGE && elements < sz_attribute_reports
|
||||
var next_sz = data.attribute_reports[elements].to_TLV().encode_len()
|
||||
if msg_sz + next_sz < self.MAX_MESSAGE
|
||||
msg_sz += next_sz
|
||||
elements += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
tasmota.log(string.format("MTR: exch=%i elements=%i msg_sz=%i total=%i", self.get_exchangeid(), elements, msg_sz, sz_attribute_reports), 3)
|
||||
var next_elemnts = []
|
||||
if data.attribute_reports != nil
|
||||
next_elemnts = data.attribute_reports[elements .. ]
|
||||
data.attribute_reports = data.attribute_reports[0 .. elements - 1]
|
||||
data.more_chunked_messages = (size(next_elemnts) > 0)
|
||||
else
|
||||
data.more_chunked_messages = false
|
||||
end
|
||||
|
||||
if was_chunked
|
||||
tasmota.log(string.format("MTR: .Read_Attr next_chunk exch=%i", self.get_exchangeid()), 3)
|
||||
end
|
||||
if data.more_chunked_messages
|
||||
if !was_chunked
|
||||
tasmota.log(string.format("MTR: .Read_Attr first_chunk exch=%i", self.get_exchangeid()), 3)
|
||||
end
|
||||
# tasmota.log("MTR: sending TLV" + str(data), 4)
|
||||
end
|
||||
|
||||
# print(">>>>> send elements before encode")
|
||||
var raw_tlv = self.data.to_TLV()
|
||||
# print(">>>>> send elements before encode 2")
|
||||
var encoded_tlv = raw_tlv.tlv2raw(bytes(self.MAX_MESSAGE)) # takes time
|
||||
# print(">>>>> send elements before encode 3")
|
||||
resp.encode_frame(encoded_tlv) # payload in cleartext, pre-allocate max buffer
|
||||
# print(">>>>> send elements after encode")
|
||||
resp.encrypt()
|
||||
# print(">>>>> send elements after encrypt")
|
||||
tasmota.log(string.format("MTR: <snd (%6i) id=%i exch=%i rack=%s", resp.session.local_session_id, resp.message_counter, resp.exchange_id, resp.ack_message_counter),3)
|
||||
responder.send_response_frame(resp)
|
||||
self.last_counter = resp.message_counter
|
||||
|
||||
if size(next_elemnts) > 0
|
||||
data.attribute_reports = next_elemnts
|
||||
tasmota.log(string.format("MTR: to_be_sent_later size=%i exch=%i", size(data.attribute_reports), resp.exchange_id), 3)
|
||||
self.ready = false # wait for Status Report before continuing sending
|
||||
# keep alive
|
||||
else
|
||||
self.finish = true # finished, remove
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
matter.IM_ReportData = Matter_IM_ReportData
|
||||
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM_ReportDataSubscribed
|
||||
#
|
||||
# Main difference is that we are the spontaneous initiator
|
||||
#################################################################################
|
||||
class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
|
||||
var sub # subscription object
|
||||
var report_data_phase # true during reportdata
|
||||
|
||||
def init(message_handler, session, data, sub)
|
||||
self.resp = matter.Frame.initiate_response(message_handler, session, 0x05 #-Report Data-#, true)
|
||||
self.data = data
|
||||
self.ready = true # by default send immediately
|
||||
self.expiration = tasmota.millis() + self.MSG_TIMEOUT
|
||||
#
|
||||
self.sub = sub
|
||||
self.report_data_phase = true
|
||||
end
|
||||
|
||||
def reached_timeout()
|
||||
self.sub.remove_self()
|
||||
end
|
||||
|
||||
# ack received, confirm the heartbeat
|
||||
def ack_received(msg)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: IM_ReportDataSubscribed ack_received sub=%i", self.sub.subscription_id), 3)
|
||||
super(self).ack_received(msg)
|
||||
if !self.report_data_phase
|
||||
# if ack is received while all data is sent, means that it finished without error
|
||||
if self.sub.is_keep_alive # only if keep-alive, for normal reports, re_arm is called at last StatusReport
|
||||
self.sub.re_arm() # signal that we can proceed to next sub report
|
||||
end
|
||||
return true # proceed to calling send() which removes the message
|
||||
else
|
||||
return false # do nothing
|
||||
end
|
||||
end
|
||||
|
||||
# we received an ACK error, remove subscription
|
||||
def status_error_received(msg)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: IM_ReportDataSubscribed status_error_received sub=%i exch=%i", self.sub.subscription_id, self.resp.exchange_id), 3)
|
||||
self.sub.remove_self()
|
||||
end
|
||||
|
||||
# ack received for previous message, proceed to next (if any)
|
||||
# return true if we manage the ack ourselves, false if it needs to be done upper
|
||||
def status_ok_received(msg)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: IM_ReportDataSubscribed status_ok_received sub=%i exch=%i", self.sub.subscription_id, self.resp.exchange_id), 3)
|
||||
if self.report_data_phase
|
||||
return super(self).status_ok_received(msg)
|
||||
else
|
||||
self.sub.re_arm() # always re_arm at last StatusReport. The only case where it does not happen is during keep-alive, hence we need to lookg for Ack (see above)
|
||||
super(self).status_ok_received(nil)
|
||||
return false # let the caller to the ack
|
||||
end
|
||||
end
|
||||
|
||||
# returns true if transaction is complete (remove object from queue)
|
||||
# default responder for data
|
||||
def send_im(responder)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: IM_ReportDataSubscribed send sub=%i exch=%i ready=%i", self.sub.subscription_id, self.resp.exchange_id, self.ready ? 1 : 0), 3)
|
||||
tasmota.log(string.format("MTR: ReportDataSubscribed::send_im size(self.data.attribute_reports)=%i ready=%s report_data_phase=%s", size(self.data.attribute_reports), str(self.ready), str(self.report_data_phase)), 3)
|
||||
if !self.ready return false end
|
||||
if size(self.data.attribute_reports) > 0 # do we have still attributes to send
|
||||
if self.report_data_phase
|
||||
super(self).send_im(responder)
|
||||
tasmota.log(string.format("MTR: ReportDataSubscribed::send_im called super finish=%i", self.finish), 3)
|
||||
if !self.finish return end # ReportData needs to continue
|
||||
# ReportData is finished
|
||||
self.report_data_phase = false
|
||||
self.ready = false
|
||||
self.finish = false # while a ReadReport would stop here, we continue for subscription
|
||||
else
|
||||
# send a simple ACK
|
||||
var resp = self.resp.build_standalone_ack(false)
|
||||
resp.encode_frame()
|
||||
resp.encrypt()
|
||||
tasmota.log(string.format("MTR: <Ack (%6i) ack=%i id=%i", resp.session.local_session_id, resp.ack_message_counter, resp.message_counter), 3)
|
||||
responder.send_response_frame(resp)
|
||||
self.last_counter = resp.message_counter
|
||||
self.finish = true
|
||||
end
|
||||
|
||||
else
|
||||
# simple heartbeat ReportData
|
||||
if self.report_data_phase
|
||||
super(self).send_im(responder)
|
||||
self.report_data_phase = false
|
||||
else
|
||||
self.finish = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
matter.IM_ReportDataSubscribed = Matter_IM_ReportDataSubscribed
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM_SubscribedHeartbeat
|
||||
#
|
||||
# Send a subscription heartbeat, which is an empty DataReport with no SuppressResponse
|
||||
#
|
||||
# Main difference is that we are the spontaneous initiator
|
||||
#################################################################################
|
||||
class Matter_IM_SubscribedHeartbeat : Matter_IM_ReportData
|
||||
var sub # subscription object
|
||||
|
||||
def init(message_handler, session, data, sub)
|
||||
self.resp = matter.Frame.initiate_response(message_handler, session, 0x05 #-Report Data-#, true)
|
||||
self.data = data
|
||||
self.ready = true # by default send immediately
|
||||
self.expiration = tasmota.millis() + self.MSG_TIMEOUT
|
||||
#
|
||||
self.sub = sub
|
||||
end
|
||||
|
||||
def reached_timeout()
|
||||
self.sub.remove_self()
|
||||
end
|
||||
|
||||
# ack received, confirm the heartbeat
|
||||
def ack_received(msg)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: Matter_IM_SubscribedHeartbeat ack_received sub=%i", self.sub.subscription_id), 3)
|
||||
super(self).ack_received(msg)
|
||||
self.finish = true
|
||||
return true # proceed to calling send() which removes the message
|
||||
end
|
||||
|
||||
# we received an ACK error, remove subscription
|
||||
def status_error_received(msg)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: Matter_IM_SubscribedHeartbeat status_error_received sub=%i exch=%i", self.sub.subscription_id, self.resp.exchange_id), 3)
|
||||
self.sub.remove_self()
|
||||
return false # let the caller to the ack
|
||||
end
|
||||
|
||||
# ack received for previous message, proceed to next (if any)
|
||||
# return true if we manage the ack ourselves, false if it needs to be done upper
|
||||
def status_ok_received(msg)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: Matter_IM_SubscribedHeartbeat status_ok_received sub=%i exch=%i", self.sub.subscription_id, self.resp.exchange_id), 3)
|
||||
return false # let the caller to the ack
|
||||
end
|
||||
|
||||
# default responder for data
|
||||
def send_im(responder)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: Matter_IM_SubscribedHeartbeat send sub=%i exch=%i ready=%i", self.sub.subscription_id, self.resp.exchange_id, self.ready ? 1 : 0), 3)
|
||||
if !self.ready return false end
|
||||
|
||||
super(self).send_im(responder)
|
||||
self.ready = false
|
||||
end
|
||||
end
|
||||
matter.IM_SubscribedHeartbeat = Matter_IM_SubscribedHeartbeat
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM_SubscribeResponse
|
||||
#
|
||||
# Report Data for a Read Request
|
||||
#################################################################################
|
||||
class Matter_IM_SubscribeResponse : Matter_IM_ReportData
|
||||
var sub # subscription object
|
||||
var report_data_phase # true during reportdata
|
||||
|
||||
def init(msg, data, sub)
|
||||
super(self).init(msg, data)
|
||||
self.sub = sub
|
||||
self.report_data_phase = true
|
||||
end
|
||||
|
||||
# default responder for data
|
||||
def send_im(responder)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: Matter_IM_SubscribeResponse send sub=%i ready=%i", self.sub.subscription_id, self.ready ? 1 : 0), 3)
|
||||
if !self.ready return false end
|
||||
if self.report_data_phase
|
||||
super(self).send_im(responder)
|
||||
if self.finish
|
||||
# finished reporting of data, we still need to send SubscribeResponseMessage after next StatusReport
|
||||
self.report_data_phase = false
|
||||
self.finish = false # we continue to subscribe response
|
||||
end
|
||||
self.ready = false # wait for Status Report before continuing sending
|
||||
|
||||
else
|
||||
# send the final SubscribeReponse
|
||||
var resp = self.resp
|
||||
var sr = matter.SubscribeResponseMessage()
|
||||
sr.subscription_id = self.sub.subscription_id
|
||||
sr.max_interval = self.sub.max_interval
|
||||
|
||||
self.resp.opcode = 0x04 #- Subscribe Response -#
|
||||
resp.encode_frame(sr.to_TLV().tlv2raw()) # payload in cleartext
|
||||
resp.encrypt()
|
||||
responder.send_response_frame(resp)
|
||||
self.last_counter = resp.message_counter
|
||||
tasmota.log(string.format("MTR: Send SubscribeResponseMessage sub=%i id=%i", self.sub.subscription_id, resp.message_counter), 3)
|
||||
self.sub.re_arm()
|
||||
self.finish = true # remove exchange
|
||||
end
|
||||
end
|
||||
|
||||
# Status ok received
|
||||
def status_ok_received(msg)
|
||||
import string
|
||||
tasmota.log(string.format("MTR: IM_SubscribeResponse status_ok_received sub=%i exch=%i ack=%i last_counter=%i", self.sub.subscription_id, self.resp.exchange_id, msg.ack_message_counter ? msg.ack_message_counter : 0 , self.last_counter), 3)
|
||||
# once we receive ack, open flow for subscriptions
|
||||
tasmota.log(string.format("MTR: >Sub_OK (%6i) sub=%i", msg.session.local_session_id, self.sub.subscription_id), 2)
|
||||
return super(self).status_ok_received(msg)
|
||||
end
|
||||
|
||||
end
|
||||
matter.IM_SubscribeResponse = Matter_IM_SubscribeResponse
|
254
lib/libesp32/berry_matter/src/embedded/Matter_IM_Subscription.be
Normal file
254
lib/libesp32/berry_matter/src/embedded/Matter_IM_Subscription.be
Normal file
@ -0,0 +1,254 @@
|
||||
#
|
||||
# Matter_IM_Subscription.be - suppport for Matter Interaction Model subscriptions
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import matter
|
||||
|
||||
#@ solidify:Matter_IM_Subscription,weak
|
||||
#@ solidify:Matter_IM_Subscription_Shop,weak
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM_Subscription
|
||||
#
|
||||
# Individual subscription instance
|
||||
#################################################################################
|
||||
class Matter_IM_Subscription
|
||||
static var MAX_INTERVAL_MARGIN = 5 # we always keep 5s margin
|
||||
var subs_shop # pointer to sub shop
|
||||
# parameters of the subscription
|
||||
var subscription_id # id of the subcription as known by requester
|
||||
var session # the session it belongs to
|
||||
var path_list # list of path subscibed to
|
||||
var min_interval # never send data more often than every `min_interval` seconds
|
||||
var max_interval # always send data before `max_interal` seconds or the subscription is lost
|
||||
var fabric_filtered
|
||||
# manage time
|
||||
var not_before # rate-limiting
|
||||
var expiration # expiration epoch, we need to respond before
|
||||
var wait_status # if `true` wait for Status Response before sending anything new
|
||||
var is_keep_alive # was the last message sent an empty keep-alive
|
||||
# updates
|
||||
var updates
|
||||
|
||||
# req: SubscribeRequestMessage
|
||||
def init(subs_shop, id, session, req)
|
||||
self.subs_shop = subs_shop
|
||||
self.subscription_id = id
|
||||
self.session = session
|
||||
# check values for min_interval
|
||||
var min_interval = req.min_interval_floor
|
||||
if min_interval < 0 min_interval = 0 end
|
||||
if min_interval > 60 min_interval = 60 end
|
||||
self.min_interval = min_interval
|
||||
# check values for max_interval
|
||||
var max_interval = req.max_interval_ceiling
|
||||
if max_interval < 60 max_interval = 60 end
|
||||
if max_interval > 3600 max_interval = 3600 end
|
||||
max_interval = 60
|
||||
self.max_interval = max_interval
|
||||
self.wait_status = false
|
||||
|
||||
self.fabric_filtered = req.fabric_filtered
|
||||
|
||||
# get list of path from
|
||||
self.path_list = []
|
||||
|
||||
for q: req.attributes_requests
|
||||
var ctx = matter.Path()
|
||||
ctx.endpoint = q.endpoint
|
||||
ctx.cluster = q.cluster
|
||||
ctx.attribute = q.attribute
|
||||
self.path_list.push(ctx)
|
||||
end
|
||||
|
||||
# update next time interval
|
||||
self.updates = []
|
||||
self.clear_before_arm()
|
||||
self.is_keep_alive = false
|
||||
|
||||
# tasmota.log("MTR: new subsctiption " + matter.inspect(self), 3)
|
||||
end
|
||||
|
||||
# remove self from subs_shop list
|
||||
def remove_self()
|
||||
tasmota.log("MTR: -Sub_Del ( ) sub=" + str(self.subscription_id), 2)
|
||||
self.subs_shop.remove_sub(self)
|
||||
end
|
||||
|
||||
# clear log after it was sent, and re-arm next expiration
|
||||
def clear_before_arm()
|
||||
self.updates.clear()
|
||||
self.wait_status = true
|
||||
end
|
||||
|
||||
# we received a complete ack for previous message, rearm
|
||||
def re_arm()
|
||||
import string
|
||||
self.wait_status = false
|
||||
var now = tasmota.millis()
|
||||
self.expiration = now + (self.max_interval - self.MAX_INTERVAL_MARGIN) * 1000
|
||||
self.not_before = now + self.min_interval * 1000 - 1
|
||||
if !self.is_keep_alive
|
||||
tasmota.log(string.format("MTR: .Sub_Done ( ) sub=%i", self.subscription_id), 2)
|
||||
end
|
||||
end
|
||||
|
||||
# signal that an attribute was updated, to add to the list of reportable
|
||||
def attribute_updated_ctx(ctx, fabric_specific)
|
||||
var idx = 0
|
||||
while idx < size(self.path_list)
|
||||
var filter = self.path_list[idx]
|
||||
if (filter.endpoint == nil || filter.endpoint == ctx.endpoint) &&
|
||||
(filter.cluster == nil || filter.cluster == ctx.cluster) &&
|
||||
(filter.attribute == nil || filter.attribute == ctx.attribute)
|
||||
|
||||
# ready to push the new attribute, check that it
|
||||
self._add_attribute_unique_path(ctx)
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
# add an attribute path for an updated attribute, remove any duplicate
|
||||
def _add_attribute_unique_path(ctx)
|
||||
var idx = 0
|
||||
while idx < size(self.updates)
|
||||
var path = self.updates[idx]
|
||||
if path.endpoint == ctx.endpoint &&
|
||||
path.cluster == ctx.cluster &&
|
||||
path.attribute == ctx.attribute
|
||||
return # already exists in the list, abort
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
self.updates.push(ctx)
|
||||
end
|
||||
|
||||
end
|
||||
matter.IM_Subscription = Matter_IM_Subscription
|
||||
|
||||
#################################################################################
|
||||
# Matter_IM_Subscription_Shop (monad)
|
||||
#
|
||||
# Handles all subscriptions
|
||||
#################################################################################
|
||||
class Matter_IM_Subscription_Shop
|
||||
var subs # list of subscriptions
|
||||
var im # pointer to parent `im` object
|
||||
|
||||
def init(im)
|
||||
self.im = im
|
||||
self.subs = []
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# create a new subscription
|
||||
#
|
||||
# session object
|
||||
# SubscribeRequestMessage request
|
||||
# returns: new subscription
|
||||
def new_subscription(session, req)
|
||||
import crypto
|
||||
var id = crypto.random(2).get(0,2)
|
||||
while self.get_by_id(id)
|
||||
id = crypto.random(2).get(0,2)
|
||||
end
|
||||
|
||||
var sub = matter.IM_Subscription(self, id, session, req)
|
||||
self.subs.push(sub)
|
||||
|
||||
return sub
|
||||
end
|
||||
|
||||
def remove_sub(sub)
|
||||
var idx = 0
|
||||
while idx < size(self.subs)
|
||||
if self.subs[idx] == sub
|
||||
self.subs.remove(idx)
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_by_id(id)
|
||||
var idx = 0
|
||||
while idx < size(self.subs)
|
||||
if self.subs[idx].subscription_id == id
|
||||
return self.subs[idx]
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
def remove_by_session(session)
|
||||
var idx = 0
|
||||
while idx < size(self.subs)
|
||||
if self.subs[idx].session == session
|
||||
self.subs.remove(idx)
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def remove_by_fabric(fabric)
|
||||
for session: fabric._sessions
|
||||
self.remove_by_session(session)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# dispatch every 250ms click to sub-objects that need it
|
||||
def every_250ms()
|
||||
# any data ready to send?
|
||||
var idx = 0
|
||||
while idx < size(self.subs)
|
||||
var sub = self.subs[idx]
|
||||
if !sub.wait_status && size(sub.updates) > 0 && tasmota.time_reached(sub.not_before)
|
||||
self.im.send_subscribe_update(sub)
|
||||
sub.clear_before_arm()
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
|
||||
# any heartbeat needing to be sent
|
||||
idx = 0
|
||||
while idx < size(self.subs)
|
||||
var sub = self.subs[idx]
|
||||
if !sub.wait_status && tasmota.time_reached(sub.expiration)
|
||||
self.im.send_subscribe_heartbeat(sub)
|
||||
sub.clear_before_arm()
|
||||
sub.re_arm() # signal that we can proceed to next sub report
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
# signal that an attribute was updated, to add to the list of reportable
|
||||
def attribute_updated_ctx(ctx, fabric_specific)
|
||||
# signal any relevant subscription
|
||||
var idx = 0
|
||||
while idx < size(self.subs)
|
||||
self.subs[idx].attribute_updated_ctx(ctx, fabric_specific)
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
matter.IM_Subscription_Shop = Matter_IM_Subscription_Shop
|
@ -52,7 +52,7 @@ class Matter_Frame
|
||||
var x_flag_a
|
||||
var x_flag_i
|
||||
var opcode
|
||||
var exchange_id
|
||||
var exchange_id # exchange_id is 16 bits unsigned, we set bit 16 if it's an id generated locally
|
||||
var protocol_id
|
||||
var vendor_id # (opt)
|
||||
var ack_message_counter # (opt)
|
||||
@ -140,6 +140,7 @@ class Matter_Frame
|
||||
|
||||
self.opcode = raw.get(idx+1, 1)
|
||||
self.exchange_id = raw.get(idx+2, 2)
|
||||
if !self.x_flag_i self.exchange_id |= 0x10000 end # special encoding for local exchange_id
|
||||
self.protocol_id = raw.get(idx+4, 2)
|
||||
idx += 6
|
||||
|
||||
@ -170,7 +171,7 @@ class Matter_Frame
|
||||
#
|
||||
# Header is built from attributes
|
||||
# `payload` is a bytes() buffer for the app payload
|
||||
def encode(payload)
|
||||
def encode_frame(payload)
|
||||
var raw = bytes()
|
||||
# compute flags
|
||||
if self.flags == nil
|
||||
@ -210,7 +211,7 @@ class Matter_Frame
|
||||
raw.add(self.x_flags, 1)
|
||||
# opcode (mandatory)
|
||||
raw.add(self.opcode, 1)
|
||||
raw.add(self.exchange_id, 2)
|
||||
raw.add(self.exchange_id & 0xFFFF, 2)
|
||||
raw.add(self.protocol_id, 2)
|
||||
if self.x_flag_a raw.add(self.ack_message_counter, 4) end
|
||||
# finally payload
|
||||
@ -227,38 +228,7 @@ class Matter_Frame
|
||||
#############################################################
|
||||
# Generate a Standalone Acknowledgment
|
||||
# Uses `PROTOCOL_ID_SECURE_CHANNEL` no ecnryption required
|
||||
def build_standalone_ack()
|
||||
import string
|
||||
# send back response
|
||||
var resp = classof(self)(self.message_handler)
|
||||
|
||||
if self.flag_s
|
||||
resp.flag_dsiz = 0x01
|
||||
resp.dest_node_id_8 = self.source_node_id
|
||||
else
|
||||
resp.flag_dsiz = 0x00
|
||||
end
|
||||
resp.session = self.session # also copy the session object
|
||||
# message counter
|
||||
resp.message_counter = self.session.counter_snd.next()
|
||||
resp.local_session_id = self.session.initiator_session_id
|
||||
|
||||
resp.x_flag_i = 0 # not sent by initiator
|
||||
resp.opcode = 0x10 # MRP Standalone Acknowledgement
|
||||
resp.exchange_id = self.exchange_id
|
||||
resp.protocol_id = 0 # PROTOCOL_ID_SECURE_CHANNEL
|
||||
resp.x_flag_a = 1 # ACK of previous message
|
||||
resp.ack_message_counter = self.message_counter
|
||||
resp.x_flag_r = 0
|
||||
|
||||
tasmota.log(string.format("MTR: <Replied %s", matter.get_opcode_name(resp.opcode)), 2)
|
||||
return resp
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Generate response to message with default parameter
|
||||
# does not handle encryption which is done in a later step
|
||||
def build_response(opcode, reliable)
|
||||
def build_standalone_ack(reliable)
|
||||
import string
|
||||
# send back response
|
||||
var resp = classof(self)(self.message_handler)
|
||||
@ -266,6 +236,42 @@ class Matter_Frame
|
||||
resp.remote_ip = self.remote_ip
|
||||
resp.remote_port = self.remote_port
|
||||
|
||||
if self.flag_s
|
||||
resp.flag_dsiz = 0x01
|
||||
resp.dest_node_id_8 = self.source_node_id
|
||||
else
|
||||
resp.flag_dsiz = 0x00
|
||||
end
|
||||
resp.session = self.session # also copy the session object
|
||||
# message counter
|
||||
resp.message_counter = self.session.counter_snd_next()
|
||||
resp.local_session_id = self.session.initiator_session_id
|
||||
|
||||
resp.x_flag_i = (self.x_flag_i ? 0 : 1) # invert the initiator flag
|
||||
resp.opcode = 0x10 # MRP Standalone Acknowledgement
|
||||
resp.exchange_id = self.exchange_id
|
||||
resp.protocol_id = 0 # PROTOCOL_ID_SECURE_CHANNEL
|
||||
resp.x_flag_a = 1 # ACK of previous message
|
||||
resp.ack_message_counter = self.message_counter
|
||||
resp.x_flag_r = reliable ? 1 : 0
|
||||
return resp
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Generate response to message with default parameter
|
||||
# does not handle encryption which is done in a later step
|
||||
#
|
||||
# if 'resp' is not nil, update frame
|
||||
def build_response(opcode, reliable, resp)
|
||||
import string
|
||||
# send back response
|
||||
if resp == nil
|
||||
resp = classof(self)(self.message_handler)
|
||||
end
|
||||
|
||||
resp.remote_ip = self.remote_ip
|
||||
resp.remote_port = self.remote_port
|
||||
|
||||
if self.flag_s
|
||||
resp.flag_dsiz = 0x01
|
||||
resp.dest_node_id_8 = self.source_node_id
|
||||
@ -276,14 +282,14 @@ class Matter_Frame
|
||||
# message counter
|
||||
# if self.session && self.session.initiator_session_id != 0
|
||||
if self.local_session_id != 0 && self.session && self.session.initiator_session_id != 0
|
||||
resp.message_counter = self.session.counter_snd.next()
|
||||
resp.message_counter = self.session.counter_snd_next()
|
||||
resp.local_session_id = self.session.initiator_session_id
|
||||
else
|
||||
resp.message_counter = self.session._counter_insecure_snd.next()
|
||||
resp.local_session_id = 0
|
||||
end
|
||||
|
||||
resp.x_flag_i = 0 # not sent by initiator
|
||||
resp.x_flag_i = (self.x_flag_i ? 0 : 1) # invert the initiator flag
|
||||
resp.opcode = opcode
|
||||
resp.exchange_id = self.exchange_id
|
||||
resp.protocol_id = self.protocol_id
|
||||
@ -296,11 +302,47 @@ class Matter_Frame
|
||||
if resp.local_session_id == 0
|
||||
var op_name = matter.get_opcode_name(resp.opcode)
|
||||
if !op_name op_name = string.format("0x%02X", resp.opcode) end
|
||||
tasmota.log(string.format("MTR: <Replied %s", op_name), 2)
|
||||
tasmota.log(string.format("MTR: <Replied (%6i) %s", resp.session.local_session_id, op_name), 2)
|
||||
end
|
||||
return resp
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Generate a message - we are the initiator
|
||||
#
|
||||
# if 'resp' is not nil, update frame
|
||||
static def initiate_response(message_handler, session, opcode, reliable, resp)
|
||||
import string
|
||||
# send back response
|
||||
if resp == nil
|
||||
resp = matter.Frame(message_handler)
|
||||
end
|
||||
|
||||
resp.remote_ip = session._ip
|
||||
resp.remote_port = session._port
|
||||
|
||||
resp.flag_dsiz = 0x00
|
||||
resp.session = session # also copy the session object
|
||||
# message counter
|
||||
if session && session.initiator_session_id != 0
|
||||
resp.message_counter = session.counter_snd_next()
|
||||
resp.local_session_id = session.initiator_session_id
|
||||
else
|
||||
resp.message_counter = session._counter_insecure_snd.next()
|
||||
resp.local_session_id = 0
|
||||
end
|
||||
|
||||
resp.x_flag_i = 1 # we are the initiator
|
||||
resp.opcode = opcode
|
||||
session._exchange_id += 1 # move to next exchange_id
|
||||
resp.exchange_id = session._exchange_id | 0x10000 # special encoding for local exchange_id
|
||||
resp.protocol_id = 0x0001 # PROTOCOL_ID_INTERACTION_MODEL
|
||||
resp.x_flag_r = reliable ? 1 : 0
|
||||
|
||||
return resp
|
||||
end
|
||||
|
||||
|
||||
#############################################################
|
||||
# decrypt with I2S key
|
||||
# return cleartext or `nil` if failed
|
||||
@ -316,6 +358,7 @@ class Matter_Frame
|
||||
# check privacy flag, p.127
|
||||
if self.sec_p
|
||||
# compute privacy key, p.71
|
||||
tasmota.log("MTR: >>>>>>>>>>>>>>>>>>>> Compute Privacy TODO", 2)
|
||||
var k = session.get_i2r_privacy()
|
||||
var n = bytes().add(self.local_session_id, -2) + mic[5..15] # session in Big Endian
|
||||
var m = self.raw[4 .. self.payload_idx-1]
|
||||
@ -340,25 +383,25 @@ class Matter_Frame
|
||||
n.resize(13) # add zeros
|
||||
end
|
||||
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: i2r =" + i2r.tohex(), 3)
|
||||
tasmota.log("MTR: p =" + p.tohex(), 3)
|
||||
tasmota.log("MTR: a =" + a.tohex(), 3)
|
||||
tasmota.log("MTR: n =" + n.tohex(), 3)
|
||||
tasmota.log("MTR: mic =" + mic.tohex(), 3)
|
||||
tasmota.log("MTR: ******************************", 4)
|
||||
tasmota.log("MTR: i2r =" + i2r.tohex(), 4)
|
||||
tasmota.log("MTR: p =" + p.tohex(), 4)
|
||||
tasmota.log("MTR: a =" + a.tohex(), 4)
|
||||
tasmota.log("MTR: n =" + n.tohex(), 4)
|
||||
tasmota.log("MTR: mic =" + mic.tohex(), 4)
|
||||
|
||||
# decrypt
|
||||
var aes = crypto.AES_CCM(i2r, n, a, size(p), 16)
|
||||
var cleartext = aes.decrypt(p)
|
||||
var tag = aes.tag()
|
||||
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: cleartext =" + cleartext.tohex(), 3)
|
||||
tasmota.log("MTR: tag =" + tag.tohex(), 3)
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: ******************************", 4)
|
||||
tasmota.log("MTR: cleartext =" + cleartext.tohex(), 4)
|
||||
tasmota.log("MTR: tag =" + tag.tohex(), 4)
|
||||
tasmota.log("MTR: ******************************", 4)
|
||||
|
||||
if tag != mic
|
||||
tasmota.log("MTR: rejected packet due to invalid MIC", 3)
|
||||
tasmota.log("MTR: rejected packet due to invalid MIC", 2)
|
||||
return nil
|
||||
end
|
||||
|
||||
@ -384,35 +427,35 @@ class Matter_Frame
|
||||
var n = bytes()
|
||||
n.add(self.flags, 1)
|
||||
n.add(self.message_counter, 4)
|
||||
if session.get_mode() == session.__CASE && session.deviceid
|
||||
n .. session.deviceid
|
||||
if session.is_CASE() && session.get_device_id()
|
||||
n .. session.get_device_id()
|
||||
end
|
||||
n.resize(13) # add zeros
|
||||
|
||||
tasmota.log("MTR: cleartext: " + self.raw.tohex(), 3)
|
||||
# tasmota.log("MTR: cleartext: " + self.raw.tohex(), 4)
|
||||
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: r2i =" + r2i.tohex(), 3)
|
||||
tasmota.log("MTR: p =" + p.tohex(), 3)
|
||||
tasmota.log("MTR: a =" + a.tohex(), 3)
|
||||
tasmota.log("MTR: n =" + n.tohex(), 3)
|
||||
# tasmota.log("MTR: ******************************", 4)
|
||||
# tasmota.log("MTR: r2i =" + r2i.tohex(), 4)
|
||||
# tasmota.log("MTR: p =" + p.tohex(), 4)
|
||||
# tasmota.log("MTR: a =" + a.tohex(), 4)
|
||||
# tasmota.log("MTR: n =" + n.tohex(), 4)
|
||||
|
||||
# decrypt
|
||||
var aes = crypto.AES_CCM(r2i, n, a, size(p), 16)
|
||||
var ciphertext = aes.encrypt(p)
|
||||
var tag = aes.tag()
|
||||
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
tasmota.log("MTR: ciphertext =" + ciphertext.tohex(), 3)
|
||||
tasmota.log("MTR: tag =" + tag.tohex(), 3)
|
||||
tasmota.log("MTR: ******************************", 3)
|
||||
# tasmota.log("MTR: ******************************", 4)
|
||||
# tasmota.log("MTR: ciphertext =" + ciphertext.tohex(), 4)
|
||||
# tasmota.log("MTR: tag =" + tag.tohex(), 4)
|
||||
# tasmota.log("MTR: ******************************", 4)
|
||||
|
||||
# packet is good, put back content in raw
|
||||
self.raw.resize(self.payload_idx) # remove cleartext payload
|
||||
self.raw .. ciphertext # add ciphertext
|
||||
self.raw .. tag # add MIC
|
||||
|
||||
# tasmota.log("MTR: encrypted: " + self.raw.tohex(), 3)
|
||||
# tasmota.log("MTR: encrypted: " + self.raw.tohex(), 4)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
@ -421,7 +464,7 @@ class Matter_Frame
|
||||
var r = matter.Frame(self.message_handler, raw)
|
||||
r.decode_header()
|
||||
r.decode_payload()
|
||||
tasmota.log("MTR: sending decode: " + matter.inspect(r), 3)
|
||||
tasmota.log("MTR: sending decode: " + matter.inspect(r), 4)
|
||||
end
|
||||
end
|
||||
matter.Frame = Matter_Frame
|
||||
|
@ -26,17 +26,47 @@ class Matter_MessageHandler
|
||||
var device # `tansport.msg_send(raw:bytes() [,...]) -> bool` true if succeeded
|
||||
|
||||
# handlers
|
||||
var commissioning
|
||||
var im # handler for Interaction Model
|
||||
# counters
|
||||
var counter_rcv # Global Unencrypted Message Counter incoming
|
||||
var commissioning # Commissioning Context instance, handling the PASE/CASE phases
|
||||
var im # Instance of `matter.IM` handling Interaction Model
|
||||
var control_message # Instance of `matter.Control_Message` for MCSP
|
||||
|
||||
#############################################################
|
||||
def init(device)
|
||||
self.device = device
|
||||
self.commissioning = matter.Commisioning_Context(self)
|
||||
self.im = matter.IM(self, device)
|
||||
self.counter_rcv = matter.Counter()
|
||||
self.im = matter.IM(device)
|
||||
self.control_message = matter.Control_Message(self)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Send a unencrypted Ack if needed
|
||||
#
|
||||
# reliable: do we send as reliable message
|
||||
#
|
||||
def send_simple_ack(frame, reliable)
|
||||
import string
|
||||
if frame.x_flag_r # nothing to respond, check if we need a standalone ack
|
||||
var resp = frame.build_standalone_ack(reliable)
|
||||
resp.encode_frame()
|
||||
tasmota.log(string.format("MTR: <Ack (%6i) ack=%i id=%i %s", resp.session.local_session_id, resp.ack_message_counter, resp.message_counter, reliable ? '{reliable}' : ''), 3)
|
||||
self.send_response_frame(resp)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Send an encrypted Ack if needed
|
||||
#
|
||||
# reliable: do we send as reliable message
|
||||
#
|
||||
def send_encrypted_ack(frame, reliable)
|
||||
import string
|
||||
if frame.x_flag_r # nothing to respond, check if we need a standalone ack
|
||||
var resp = frame.build_standalone_ack(reliable)
|
||||
resp.encode_frame()
|
||||
resp.encrypt()
|
||||
tasmota.log(string.format("MTR: <Ack* (%6i) ack=%i id=%i %s", resp.session.local_session_id, resp.ack_message_counter, resp.message_counter, reliable ? '{reliable}' : ''), 3)
|
||||
self.send_response_frame(resp)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
@ -47,35 +77,51 @@ class Matter_MessageHandler
|
||||
#
|
||||
def msg_received(raw, addr, port)
|
||||
import string
|
||||
var ret = false
|
||||
|
||||
try
|
||||
tasmota.log("MTR: MessageHandler::msg_received raw="+raw.tohex(), 4)
|
||||
# tasmota.log("MTR: MessageHandler::msg_received raw="+raw.tohex(), 4)
|
||||
var frame = matter.Frame(self, raw, addr, port)
|
||||
|
||||
var ok = frame.decode_header()
|
||||
if !ok return false end
|
||||
|
||||
# do we need decryption?
|
||||
if frame.local_session_id == 0 && frame.sec_sesstype == 0
|
||||
if frame.sec_p
|
||||
# Control message
|
||||
tasmota.log("MTR: CONTROL MESSAGE=" + matter.inspect(frame))
|
||||
var session = self.device.sessions.find_session_source_id_unsecure(frame.source_node_id, 90) # 90 seconds max
|
||||
tasmota.log("MTR: find session by source_node_id = " + str(frame.source_node_id) + " session_id = " + str(session.local_session_id), 2)
|
||||
return self.control_message.process_incoming_control_message(frame)
|
||||
elif frame.local_session_id == 0 && frame.sec_sesstype == 0
|
||||
#############################################################
|
||||
### unencrypted session, handled by commissioning
|
||||
var session = self.device.sessions.find_session_source_id_unsecure(frame.source_node_id, 90) # 90 seconds max
|
||||
tasmota.log("MTR: find session by source_node_id = " + str(frame.source_node_id) + "session_id = " + str(session.local_session_id), 3)
|
||||
tasmota.log("MTR: find session by source_node_id = " + str(frame.source_node_id) + " session_id = " + str(session.local_session_id), 3)
|
||||
if addr session._ip = addr end
|
||||
if port session._port = port end
|
||||
session._message_handler = self
|
||||
frame.session = session
|
||||
|
||||
# check if it's a duplicate
|
||||
if !self.counter_rcv.validate(frame.message_counter, false)
|
||||
tasmota.log(string.format("MTR: rejected duplicate unencrypted message = %i ref = %i", frame.message_counter, self.counter_rcv.val()), 3)
|
||||
if !session._counter_insecure_rcv.validate(frame.message_counter, false)
|
||||
tasmota.log(string.format("MTR: . Duplicate unencrypted message = %i ref = %i", frame.message_counter, session._counter_insecure_rcv.val()), 3)
|
||||
self.send_simple_ack(frame, false #-not reliable-#)
|
||||
return false
|
||||
end
|
||||
|
||||
if !frame.decode_payload() return false end
|
||||
self.device.packet_ack(frame.ack_message_counter) # acknowledge packet
|
||||
self.device.received_ack(frame) # remove acknowledge packet from sending list
|
||||
if frame.opcode != 0x10 # don't show `MRP_Standalone_Acknowledgement`
|
||||
var op_name = matter.get_opcode_name(frame.opcode)
|
||||
if !op_name op_name = string.format("0x%02X", frame.opcode) end
|
||||
tasmota.log(string.format("MTR: >Received %s from [%s]:%i", op_name, addr, port), 2)
|
||||
tasmota.log(string.format("MTR: >Received (%6i) %s rid=%i exch=%i from [%s]:%i", session.local_session_id, op_name, frame.message_counter, frame.exchange_id, addr, port), 2)
|
||||
else
|
||||
tasmota.log(string.format("MTR: >rcv Ack (%6i) rid=%i exch=%i ack=%s %sfrom [%s]:%i", session.local_session_id, frame.message_counter, frame.x_flag_r ? "{reliable} " : "", frame.exchange_id, str(frame.ack_message_counter), addr, port), 3)
|
||||
end
|
||||
self.commissioning.process_incoming(frame, addr, port)
|
||||
ret = self.commissioning.process_incoming(frame)
|
||||
# if ret is false, the implicit Ack was not sent
|
||||
if !ret self.send_simple_ack(frame, false #-not reliable-#) end
|
||||
return true
|
||||
else
|
||||
#############################################################
|
||||
@ -84,15 +130,19 @@ class Matter_MessageHandler
|
||||
|
||||
var session = self.device.sessions.get_session_by_local_session_id(frame.local_session_id)
|
||||
if session == nil
|
||||
tasmota.log("MTR: unknown local_session_id "+str(frame.local_session_id), 3)
|
||||
tasmota.log("MTR: frame="+matter.inspect(frame), 3)
|
||||
tasmota.log("MTR: unknown local_session_id="+str(frame.local_session_id), 2)
|
||||
# tasmota.log("MTR: frame="+matter.inspect(frame), 3)
|
||||
return false
|
||||
end
|
||||
if addr session._ip = addr end
|
||||
if port session._port = port end
|
||||
session._message_handler = self
|
||||
frame.session = session # keep a pointer of the session in the message
|
||||
|
||||
# check if it's a duplicate
|
||||
if !session.counter_rcv.validate(frame.message_counter, true)
|
||||
tasmota.log("MTR: rejected duplicate encrypted message = " + str(frame.message_counter) + " counter=" + str(session.counter_rcv.val()), 3)
|
||||
if !session.counter_rcv_validate(frame.message_counter, true)
|
||||
tasmota.log("MTR: . Duplicate encrypted message = " + str(frame.message_counter) + " counter=" + str(session.counter_rcv), 3)
|
||||
self.send_encrypted_ack(frame, false #-not reliable-#)
|
||||
return false
|
||||
end
|
||||
|
||||
@ -104,23 +154,37 @@ class Matter_MessageHandler
|
||||
frame.raw .. cleartext # add cleartext
|
||||
|
||||
# continue decoding
|
||||
tasmota.log(string.format("MTR: idx=%i clear=%s", frame.payload_idx, frame.raw.tohex()), 3)
|
||||
tasmota.log(string.format("MTR: idx=%i clear=%s", frame.payload_idx, frame.raw.tohex()), 4)
|
||||
frame.decode_payload()
|
||||
tasmota.log("MTR: decrypted message: protocol_id:"+str(frame.protocol_id)+" opcode="+str(frame.opcode)+" exchange_id"+str(frame.exchange_id), 3)
|
||||
tasmota.log("MTR: > Decrypted message: protocol_id:"+str(frame.protocol_id)+" opcode="+str(frame.opcode)+" exchange_id="+str(frame.exchange_id & 0xFFFF), 3)
|
||||
|
||||
self.device.packet_ack(frame.ack_message_counter) # acknowledge packet
|
||||
tasmota.log(string.format("MTR: >rcv (%6i) [%02X/%02X] rid=%i exch=%i ack=%s %sfrom [%s]:%i", session.local_session_id, frame.protocol_id, frame.opcode, frame.message_counter, frame.exchange_id, str(frame.ack_message_counter), frame.x_flag_r ? "{reliable} " : "", addr, port), 3)
|
||||
|
||||
self.device.received_ack(frame) # remove acknowledge packet from sending list
|
||||
|
||||
# dispatch according to protocol_id
|
||||
var protocol_id = frame.protocol_id
|
||||
if protocol_id == 0x0000 # PROTOCOL_ID_SECURE_CHANNEL
|
||||
# it should not be encrypted
|
||||
tasmota.log("MTR: PROTOCOL_ID_SECURE_CHANNEL " + matter.inspect(frame), 3)
|
||||
# if frame.opcode == 0x10
|
||||
# end
|
||||
return true
|
||||
if frame.opcode == 0x10 # MRPStandaloneAcknowledgement
|
||||
ret = self.im.process_incoming_ack(frame)
|
||||
if ret
|
||||
self.im.send_enqueued(self)
|
||||
end
|
||||
end
|
||||
ret = true
|
||||
elif protocol_id == 0x0001 # PROTOCOL_ID_INTERACTION_MODEL
|
||||
# dispatch to IM Protocol Messages
|
||||
return self.im.process_incoming(frame, addr, port)
|
||||
ret = self.im.process_incoming(frame)
|
||||
# if `ret` is true, we have something to send
|
||||
if ret
|
||||
self.im.send_enqueued(self)
|
||||
|
||||
else
|
||||
self.send_encrypted_ack(frame, true #-reliable-#)
|
||||
end
|
||||
ret = true
|
||||
|
||||
# -- PROTOCOL_ID_BDX is used for file transfer between devices, not used in Tasmota
|
||||
# elif protocol_id == 0x0002 # PROTOCOL_ID_BDX -- BDX not handled at all in Tasmota
|
||||
@ -132,12 +196,11 @@ class Matter_MessageHandler
|
||||
# return false # ignore for now TODO
|
||||
else
|
||||
tasmota.log("MTR: ignoring unhandled protocol_id:"+str(protocol_id), 3)
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return true
|
||||
return ret
|
||||
except .. as e, m
|
||||
tasmota.log("MTR: MessageHandler::msg_received exception: "+str(e)+";"+str(m))
|
||||
import debug
|
||||
@ -147,18 +210,18 @@ class Matter_MessageHandler
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def send_response(raw, addr, port, id)
|
||||
self.device.msg_send(raw, addr, port, id)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def add_session(local_session_id, initiator_session_id, i2r, r2i, ac, session_timestamp)
|
||||
import string
|
||||
# create session object
|
||||
tasmota.log(string.format("MTR: add_session local_session_id=%i initiator_session_id=%i", local_session_id, initiator_session_id), 3)
|
||||
|
||||
var session = self.device.sessions.create_session(local_session_id, initiator_session_id)
|
||||
session.set_keys(i2r, r2i, ac, session_timestamp)
|
||||
# send a frame to target, usually a response
|
||||
#
|
||||
# We need the following:
|
||||
# msg.raw: raw bytes to send (bytes)
|
||||
# msg.remote_ip: ip address of target (string)
|
||||
# msg.remote_port: port of target (int)
|
||||
# msg.x_flag_r: is the frame expecting a Ack back (int)
|
||||
# msg.message_counter: counter for this message (int)
|
||||
# msg.exchange_id: exchange id (int)
|
||||
# msg.local_session_id: local session (for logging)
|
||||
def send_response_frame(msg)
|
||||
self.device.msg_send(msg)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
@ -167,5 +230,12 @@ class Matter_MessageHandler
|
||||
self.commissioning.every_second()
|
||||
self.im.every_second()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# dispatch every 250ms click to sub-objects that need it
|
||||
def every_250ms()
|
||||
self.im.every_250ms()
|
||||
end
|
||||
|
||||
end
|
||||
matter.MessageHandler = Matter_MessageHandler
|
||||
|
53
lib/libesp32/berry_matter/src/embedded/Matter_Path.be
Normal file
53
lib/libesp32/berry_matter/src/embedded/Matter_Path.be
Normal file
@ -0,0 +1,53 @@
|
||||
#
|
||||
# Matter_IM_Path.be - suppport for Matter simple Path object
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import matter
|
||||
|
||||
#@ solidify:Matter_Path,weak
|
||||
|
||||
#################################################################################
|
||||
# Matter_Path
|
||||
#
|
||||
# Used to store all the elements of the reponse to an attribute or command
|
||||
#################################################################################
|
||||
class Matter_Path
|
||||
var endpoint # endpoint or `nil` if expansion
|
||||
var cluster # cluster or `nil` if expansion
|
||||
var attribute # attribute or `nil` if expansion
|
||||
var command # command
|
||||
var status # status to be returned (matter.SUCCESS or matter.<ERROR>)
|
||||
var log # any string that needs to be logged (used to show significant parameters for commands)
|
||||
|
||||
def tostring()
|
||||
try
|
||||
import string
|
||||
var s = ""
|
||||
s += (self.endpoint != nil ? string.format("[%02X]", self.endpoint) : "[**]")
|
||||
s += (self.cluster != nil ? string.format("%04X/", self.cluster) : "****/")
|
||||
s += (self.attribute != nil ? string.format("%04X", self.attribute) : "")
|
||||
s += (self.command != nil ? string.format("%04X", self.command) : "")
|
||||
if self.attribute == nil && self.command == nil s += "****" end
|
||||
return s
|
||||
except .. as e, m
|
||||
return "Exception> " + str(e) + ", " + str(m)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
matter.Path = Matter_Path
|
@ -23,27 +23,72 @@
|
||||
#@ solidify:Matter_Plugin,weak
|
||||
|
||||
class Matter_Plugin
|
||||
static var EMPTY_LIST = []
|
||||
static var EMPTY_MAP = {}
|
||||
static var CLUSTERS = {
|
||||
0x001D: [0,1,2,3,0xFFFC,0xFFFD], # Descriptor Cluster 9.5 p.453
|
||||
}
|
||||
var device # reference to the `device` global object
|
||||
var endpoints # list of supported endpoints
|
||||
var clusters # map from cluster to list of attributes
|
||||
var endpoint # current endpoint
|
||||
var clusters # map from cluster to list of attributes, typically constructed from CLUSTERS hierachy
|
||||
|
||||
#############################################################
|
||||
# MVC Model
|
||||
#
|
||||
# Model linking the plugin to the Tasmota behavior
|
||||
#############################################################
|
||||
|
||||
#############################################################
|
||||
# Constructor
|
||||
def init(device)
|
||||
#
|
||||
def init(device, endpoint)
|
||||
self.device = device
|
||||
self.endpoints = self.EMPTY_LIST
|
||||
self.clusters = self.EMPTY_LIST
|
||||
self.endpoint = endpoint
|
||||
self.clusters = self.consolidate_clusters()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Stub for updating shadow values (local copies of what we published to the Matter gateway)
|
||||
def update_shadow()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# signal that an attribute has been changed
|
||||
#
|
||||
# If `endpoint` is `nil`, send to all endpoints
|
||||
def attribute_updated(endpoint, cluster, attribute, fabric_specific)
|
||||
if endpoint == nil endpoint = self.endpoint end
|
||||
self.device.attribute_updated(endpoint, cluster, attribute, fabric_specific)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# consolidate_clusters
|
||||
#
|
||||
# Build a consolidated map of all the `CLUSTERS` static vars
|
||||
# from the inheritance hierarchy
|
||||
def consolidate_clusters()
|
||||
def real_super(o) return super(o) end # enclose `super()` in a static function to disable special behavior for super in instances
|
||||
var ret = {}
|
||||
var o = self # start with self
|
||||
while o != nil # when we rich highest class, `super()` returns `nil`
|
||||
var CL = o.CLUSTERS
|
||||
for k: CL.keys()
|
||||
# check if key already exists
|
||||
if !ret.contains(k) ret[k] = [] end
|
||||
for attr: CL[k] # iterate on values
|
||||
if ret[k].find(attr) == nil
|
||||
ret[k].push(attr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = real_super(o)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Which endpoints does it handle (list of numbers)
|
||||
def get_endpoints()
|
||||
return self.endpoints
|
||||
end
|
||||
def get_cluster_map()
|
||||
return self.clusters
|
||||
def get_endpoint()
|
||||
return self.endpoint
|
||||
end
|
||||
def get_cluster_list(ep)
|
||||
var ret = []
|
||||
@ -53,7 +98,7 @@ class Matter_Plugin
|
||||
return ret
|
||||
end
|
||||
def get_attribute_list(ep, cluster)
|
||||
return self.clusters.find(cluster, self.EMPTY_LIST)
|
||||
return self.clusters.find(cluster, [])
|
||||
end
|
||||
|
||||
#############################################################
|
||||
@ -62,50 +107,114 @@ class Matter_Plugin
|
||||
return self.clusters.contains(cluster) && self.endpoints.find(endpoint) != nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# MVC Model
|
||||
#
|
||||
# View reading attributes
|
||||
#############################################################
|
||||
#############################################################
|
||||
# read attribute
|
||||
def read_attribute(msg, ctx)
|
||||
return nil
|
||||
def read_attribute(session, ctx)
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var attribute = ctx.attribute
|
||||
|
||||
if cluster == 0x001D # ========== Descriptor Cluster 9.5 p.453 ==========
|
||||
|
||||
if attribute == 0x0000 # ---------- DeviceTypeList / list[DeviceTypeStruct] ----------
|
||||
var dtl = TLV.Matter_TLV_array()
|
||||
for dt: self.TYPES.keys()
|
||||
var d1 = dtl.add_struct()
|
||||
d1.add_TLV(0, TLV.U2, dt) # DeviceType
|
||||
d1.add_TLV(1, TLV.U2, self.TYPES[dt]) # Revision
|
||||
end
|
||||
return dtl
|
||||
elif attribute == 0x0001 # ---------- ServerList / list[cluster-id] ----------
|
||||
var sl = TLV.Matter_TLV_array()
|
||||
for cl: self.get_cluster_list()
|
||||
sl.add_TLV(nil, TLV.U4, cl)
|
||||
end
|
||||
return sl
|
||||
elif attribute == 0x0002 # ---------- ClientList / list[cluster-id] ----------
|
||||
var cl = TLV.Matter_TLV_array()
|
||||
return cl
|
||||
elif attribute == 0x0003 # ---------- PartsList / list[endpoint-no]----------
|
||||
var pl = TLV.Matter_TLV_array()
|
||||
return pl
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0) #
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 1) # "Initial Release"
|
||||
end
|
||||
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# read event
|
||||
# TODO
|
||||
def read_event(msg, endpoint, cluster, eventid)
|
||||
def read_event(session, endpoint, cluster, eventid)
|
||||
return nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# subscribe attribute
|
||||
# TODO
|
||||
def subscribe_attribute(msg, endpoint, cluster, attribute)
|
||||
def subscribe_attribute(session, endpoint, cluster, attribute)
|
||||
return nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# subscribe event
|
||||
# TODO
|
||||
def subscribe_event(msg, endpoint, cluster, eventid)
|
||||
def subscribe_event(session, endpoint, cluster, eventid)
|
||||
return nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# MVC Model
|
||||
#
|
||||
# Controller write attributes
|
||||
#############################################################
|
||||
#############################################################
|
||||
# write attribute
|
||||
def write_attribute(msg, endpoint, cluster, attribute)
|
||||
def write_attribute(session, ctx, write_data)
|
||||
return nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# MVC Model
|
||||
#
|
||||
# Controller invoke request
|
||||
#############################################################
|
||||
#############################################################
|
||||
# invoke command
|
||||
def invoke_request(msg, val, ctx)
|
||||
def invoke_request(session, val, ctx)
|
||||
return nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# timed request
|
||||
# TODO - should we even support this?
|
||||
def timed_request(msg, val, ctx)
|
||||
def timed_request(session, val, ctx)
|
||||
return nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# parse sensor
|
||||
#
|
||||
# The device calls regularly `tasmota.read_sensors()` and converts
|
||||
# it to json.
|
||||
def parse_sensors(payload)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# every_second
|
||||
def every_second()
|
||||
self.update_shadow() # force reading value and sending subscriptions
|
||||
end
|
||||
end
|
||||
|
||||
matter.Plugin = Matter_Plugin
|
||||
|
114
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Device.be
Normal file
114
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Device.be
Normal file
@ -0,0 +1,114 @@
|
||||
#
|
||||
# Matter_Plugin_Device.be - implements the behavior for a standard Device
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# dummy declaration for solidification
|
||||
class Matter_Plugin end
|
||||
|
||||
#@ solidify:Matter_Plugin_Device,weak
|
||||
|
||||
class Matter_Plugin_Device : Matter_Plugin
|
||||
static var CLUSTERS = {
|
||||
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
|
||||
0x0003: [0,1,0xFFFC,0xFFFD], # Identify 1.2 p.16
|
||||
0x0004: [0,0xFFFC,0xFFFD], # Groups 1.3 p.21
|
||||
}
|
||||
static var TYPES = { 0x0000: 0 } # fake type
|
||||
|
||||
#############################################################
|
||||
# Constructor
|
||||
def init(device, endpoint, tasmota_relay_index)
|
||||
super(self).init(device, endpoint)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# read an attribute
|
||||
#
|
||||
def read_attribute(session, ctx)
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var attribute = ctx.attribute
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0003 # ========== Identify 1.2 p.16 ==========
|
||||
if attribute == 0x0000 # ---------- IdentifyTime / u2 ----------
|
||||
return TLV.create_TLV(TLV.U2, 0) # no identification in progress
|
||||
elif attribute == 0x0001 # ---------- IdentifyType / enum8 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0) # IdentifyType = 0x00 None
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0) # no features
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4) # "new data model format and notation"
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0004 # ========== Groups 1.3 p.21 ==========
|
||||
if attribute == 0x0000 # ---------- ----------
|
||||
return nil # TODO
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0)#
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4)# "new data model format and notation"
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).read_attribute(session, ctx)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Invoke a command
|
||||
#
|
||||
# returns a TLV object if successful, contains the response
|
||||
# or an `int` to indicate a status
|
||||
def invoke_request(session, val, ctx)
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var command = ctx.command
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0003 # ========== Identify 1.2 p.16 ==========
|
||||
|
||||
if command == 0x0000 # ---------- Identify ----------
|
||||
# ignore
|
||||
return true
|
||||
elif command == 0x0001 # ---------- IdentifyQuery ----------
|
||||
# create IdentifyQueryResponse
|
||||
# ID=1
|
||||
# 0=Certificate (octstr)
|
||||
var iqr = TLV.Matter_TLV_struct()
|
||||
iqr.add_TLV(0, TLV.U2, 0) # Timeout
|
||||
ctx.command = 0x00 # IdentifyQueryResponse
|
||||
return iqr
|
||||
elif command == 0x0040 # ---------- TriggerEffect ----------
|
||||
# ignore
|
||||
return true
|
||||
end
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0004 # ========== Groups 1.3 p.21 ==========
|
||||
# TODO
|
||||
return true
|
||||
|
||||
else
|
||||
return super(self).invoke_request(session, val, ctx)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
matter.Plugin_Device = Matter_Plugin_Device
|
166
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Light0.be
Normal file
166
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Light0.be
Normal file
@ -0,0 +1,166 @@
|
||||
#
|
||||
# Matter_Plugin_Light0.be - implements the behavior for a generic Lighting (OnOff only)
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Matter plug-in for core behavior
|
||||
|
||||
# dummy declaration for solidification
|
||||
class Matter_Plugin end
|
||||
|
||||
#@ solidify:Matter_Plugin_Light0,weak
|
||||
|
||||
class Matter_Plugin_Light0 : Matter_Plugin
|
||||
static var CLUSTERS = {
|
||||
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
|
||||
0x0003: [0,1,0xFFFC,0xFFFD], # Identify 1.2 p.16
|
||||
0x0004: [0,0xFFFC,0xFFFD], # Groups 1.3 p.21
|
||||
0x0005: [0,1,2,3,4,5,0xFFFC,0xFFFD], # Scenes 1.4 p.30 - no writable
|
||||
0x0006: [0,0xFFFC,0xFFFD], # On/Off 1.5 p.48
|
||||
}
|
||||
static var TYPES = { 0x0100: 2 } # OnOff Light, but not actually used because Relay is managed by OnOff
|
||||
|
||||
var shadow_onoff
|
||||
|
||||
#############################################################
|
||||
# Constructor
|
||||
def init(device, endpoint)
|
||||
super(self).init(device, endpoint)
|
||||
self.shadow_onoff = false
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Update shadow
|
||||
#
|
||||
def update_shadow()
|
||||
import light
|
||||
var light_status = light.get()
|
||||
var pow = light_status.find('power', nil)
|
||||
if pow != self.shadow_onoff self.attribute_updated(nil, 0x0006, 0x0000) self.shadow_onoff = pow end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# read an attribute
|
||||
#
|
||||
def read_attribute(session, ctx)
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var attribute = ctx.attribute
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0003 # ========== Identify 1.2 p.16 ==========
|
||||
if attribute == 0x0000 # ---------- IdentifyTime / u2 ----------
|
||||
return TLV.create_TLV(TLV.U2, 0) # no identification in progress
|
||||
elif attribute == 0x0001 # ---------- IdentifyType / enum8 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0) # IdentifyType = 0x00 None
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0) # no features
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4) # "new data model format and notation"
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0004 # ========== Groups 1.3 p.21 ==========
|
||||
if attribute == 0x0000 # ---------- ----------
|
||||
return nil # TODO
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0)#
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4)# "new data model format and notation"
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0005 # ========== Scenes 1.4 p.30 - no writable ==========
|
||||
if attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0) # 0 = no Level Control for Lighting
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4) # 0 = no Level Control for Lighting
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
|
||||
if attribute == 0x0000 # ---------- OnOff / bool ----------
|
||||
return TLV.create_TLV(TLV.BOOL, self.shadow_onoff)
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0) # 0 = no Level Control for Lighting
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4) # 0 = no Level Control for Lighting
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).read_attribute(session, ctx)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Invoke a command
|
||||
#
|
||||
# returns a TLV object if successful, contains the response
|
||||
# or an `int` to indicate a status
|
||||
def invoke_request(session, val, ctx)
|
||||
import light
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var command = ctx.command
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0003 # ========== Identify 1.2 p.16 ==========
|
||||
|
||||
if command == 0x0000 # ---------- Identify ----------
|
||||
# ignore
|
||||
return true
|
||||
elif command == 0x0001 # ---------- IdentifyQuery ----------
|
||||
# create IdentifyQueryResponse
|
||||
# ID=1
|
||||
# 0=Certificate (octstr)
|
||||
var iqr = TLV.Matter_TLV_struct()
|
||||
iqr.add_TLV(0, TLV.U2, 0) # Timeout
|
||||
ctx.command = 0x00 # IdentifyQueryResponse
|
||||
return iqr
|
||||
elif command == 0x0040 # ---------- TriggerEffect ----------
|
||||
# ignore
|
||||
return true
|
||||
end
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0004 # ========== Groups 1.3 p.21 ==========
|
||||
# TODO
|
||||
return true
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0005 # ========== Scenes 1.4 p.30 ==========
|
||||
# TODO
|
||||
return true
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
|
||||
if command == 0x0000 # ---------- Off ----------
|
||||
light.set({'power':false})
|
||||
self.update_shadow()
|
||||
return true
|
||||
elif command == 0x0001 # ---------- On ----------
|
||||
light.set({'power':true})
|
||||
self.update_shadow()
|
||||
return true
|
||||
elif command == 0x0002 # ---------- Toggle ----------
|
||||
light.set({'power':!self.shadow_onoff})
|
||||
self.update_shadow()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
matter.Plugin_Light0 = Matter_Plugin_Light0
|
146
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Light1.be
Normal file
146
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Light1.be
Normal file
@ -0,0 +1,146 @@
|
||||
#
|
||||
# Matter_Plugin_Light1.be - implements the behavior for a Light with 1 channel (Dimmer)
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Matter plug-in for core behavior
|
||||
|
||||
# dummy declaration for solidification
|
||||
class Matter_Plugin_Light0 end
|
||||
|
||||
#@ solidify:Matter_Plugin_Light1,weak
|
||||
|
||||
class Matter_Plugin_Light1 : Matter_Plugin_Light0
|
||||
static var CLUSTERS = {
|
||||
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
|
||||
# 0x0003: inherited # Identify 1.2 p.16
|
||||
# 0x0004: inherited # Groups 1.3 p.21
|
||||
# 0x0005: inherited # Scenes 1.4 p.30 - no writable
|
||||
# 0x0006: inherited # On/Off 1.5 p.48
|
||||
0x0008: [0,2,3,0x0F,0x11,0xFFFC,0xFFFD], # Level Control 1.6 p.57
|
||||
}
|
||||
static var TYPES = { 0x0101: 2 } # Dimmable Light
|
||||
|
||||
var shadow_bri
|
||||
# var shadow_onoff # inherited
|
||||
|
||||
#############################################################
|
||||
# Constructor
|
||||
def init(device, endpoint)
|
||||
super(self).init(device, endpoint)
|
||||
self.shadow_bri = 0
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Update shadow
|
||||
#
|
||||
def update_shadow()
|
||||
import light
|
||||
var light_status = light.get()
|
||||
var bri = light_status.find('bri', nil)
|
||||
if bri != nil bri = tasmota.scale_uint(bri, 0, 255, 0, 254) else bri = self.shadow_bri end
|
||||
if bri != self.shadow_bri self.attribute_updated(nil, 0x0008, 0x0000) self.shadow_bri = bri end
|
||||
super(self).update_shadow() # superclass manages 'power'
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# read an attribute
|
||||
#
|
||||
def read_attribute(session, ctx)
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var attribute = ctx.attribute
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0008 # ========== Level Control 1.6 p.57 ==========
|
||||
if attribute == 0x0000 # ---------- CurrentLevel / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, self.shadow_bri)
|
||||
elif attribute == 0x0002 # ---------- MinLevel / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0)
|
||||
elif attribute == 0x0003 # ---------- MaxLevel / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 254)
|
||||
elif attribute == 0x000F # ---------- Options / map8 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0) #
|
||||
elif attribute == 0x0011 # ---------- OnLevel / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, self.shadow_bri)
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0X01) # OnOff
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 5) # "new data model format and notation"
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).read_attribute(session, ctx)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Invoke a command
|
||||
#
|
||||
# returns a TLV object if successful, contains the response
|
||||
# or an `int` to indicate a status
|
||||
def invoke_request(session, val, ctx)
|
||||
import light
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var command = ctx.command
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0008 # ========== Level Control 1.6 p.57 ==========
|
||||
if command == 0x0000 # ---------- MoveToLevel ----------
|
||||
var bri_in = val.findsubval(0) # Hue 0..254
|
||||
var bri = tasmota.scale_uint(bri_in, 0, 254, 0, 255)
|
||||
light.set({'bri': bri})
|
||||
self.update_shadow()
|
||||
ctx.log = "bri:"+str(bri_in)
|
||||
return true
|
||||
elif command == 0x0001 # ---------- Move ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x0002 # ---------- Step ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x0003 # ---------- Stop ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x0004 # ---------- MoveToLevelWithOnOff ----------
|
||||
var bri_in = val.findsubval(0) # Hue 0..254
|
||||
var bri = tasmota.scale_uint(bri_in, 0, 254, 0, 255)
|
||||
var onoff = bri > 0
|
||||
light.set({'bri': bri, 'power': onoff})
|
||||
self.update_shadow()
|
||||
ctx.log = "bri:"+str(bri_in)
|
||||
return true
|
||||
elif command == 0x0005 # ---------- MoveWithOnOff ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x0006 # ---------- StepWithOnOff ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x0007 # ---------- StopWithOnOff ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).invoke_request(session, val, ctx)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
matter.Plugin_Light1 = Matter_Plugin_Light1
|
144
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Light2.be
Normal file
144
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Light2.be
Normal file
@ -0,0 +1,144 @@
|
||||
#
|
||||
# Matter_Plugin_Light2.be - implements the behavior for a Light with 2 channel (CT)
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Matter plug-in for core behavior
|
||||
|
||||
# dummy declaration for solidification
|
||||
class Matter_Plugin_Light1 end
|
||||
|
||||
#@ solidify:Matter_Plugin_Light2,weak
|
||||
|
||||
class Matter_Plugin_Light2 : Matter_Plugin_Light1
|
||||
static var CLUSTERS = {
|
||||
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
|
||||
# 0x0003: inherited # Identify 1.2 p.16
|
||||
# 0x0004: inherited # Groups 1.3 p.21
|
||||
# 0x0005: inherited # Scenes 1.4 p.30 - no writable
|
||||
# 0x0006: inherited # On/Off 1.5 p.48
|
||||
# 0x0008: inherited # Level Control 1.6 p.57
|
||||
0x0300: [7,8,0xF,0x400B,0x400C,0xFFFC,0xFFFD], # Color Control 3.2 p.111
|
||||
}
|
||||
static var TYPES = { 0x010C: 2 } # Color Temperature Light
|
||||
|
||||
var shadow_ct
|
||||
var ct_min, ct_max
|
||||
|
||||
#############################################################
|
||||
# Constructor
|
||||
def init(device, endpoint)
|
||||
super(self).init(device, endpoint)
|
||||
self.shadow_ct = 325
|
||||
self.update_ct_minmax()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Update shadow
|
||||
#
|
||||
def update_shadow()
|
||||
import light
|
||||
self.update_ct_minmax()
|
||||
super(self).update_shadow()
|
||||
var light_status = light.get()
|
||||
var ct = light_status.find('ct', nil)
|
||||
if ct == nil ct = self.shadow_ct end
|
||||
if ct != self.shadow_ct self.attribute_updated(nil, 0x0300, 0x0007) self.shadow_ct = ct end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Update ct_min/max
|
||||
#
|
||||
def update_ct_minmax()
|
||||
var ct_alexa_mode = tasmota.get_option(82) # if set, range is 200..380 instead of 153...500
|
||||
self.ct_min = ct_alexa_mode ? 200 : 153
|
||||
self.ct_max = ct_alexa_mode ? 380 : 500
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# read an attribute
|
||||
#
|
||||
def read_attribute(session, ctx)
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var attribute = ctx.attribute
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0300 # ========== Color Control 3.2 p.111 ==========
|
||||
if attribute == 0x0007 # ---------- ColorTemperatureMireds / u2 ----------
|
||||
return TLV.create_TLV(TLV.U1, self.shadow_ct)
|
||||
elif attribute == 0x0008 # ---------- ColorMode / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 2)# 2 = ColorTemperatureMireds
|
||||
elif attribute == 0x000F # ---------- Options / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0)
|
||||
elif attribute == 0x400B # ---------- ColorTempPhysicalMinMireds / u2 ----------
|
||||
return TLV.create_TLV(TLV.U1, self.ct_min)
|
||||
elif attribute == 0x400C # ---------- ColorTempPhysicalMaxMireds / u2 ----------
|
||||
return TLV.create_TLV(TLV.U1, self.ct_max)
|
||||
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0x10) # CT
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 5) # "new data model format and notation, FeatureMap support"
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).read_attribute(session, ctx)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Invoke a command
|
||||
#
|
||||
# returns a TLV object if successful, contains the response
|
||||
# or an `int` to indicate a status
|
||||
def invoke_request(session, val, ctx)
|
||||
import light
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var command = ctx.command
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0300 # ========== Color Control 3.2 p.111 ==========
|
||||
if command == 0x000A # ---------- MoveToColorTemperature ----------
|
||||
var ct_in = val.findsubval(0) # CT
|
||||
if ct_in < self.ct_min ct_in = self.ct_min end
|
||||
if ct_in > self.ct_max ct_in = self.ct_max end
|
||||
light.set({'ct': ct_in})
|
||||
self.update_shadow()
|
||||
ctx.log = "ct:"+str(ct_in)
|
||||
return true
|
||||
elif command == 0x0047 # ---------- StopMoveStep ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x004B # ---------- MoveColorTemperature ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x004C # ---------- StepColorTemperature ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).invoke_request(session, val, ctx)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
matter.Plugin_Light2 = Matter_Plugin_Light2
|
165
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Light3.be
Normal file
165
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Light3.be
Normal file
@ -0,0 +1,165 @@
|
||||
#
|
||||
# Matter_Plugin_Light3.be - implements the behavior for a Light with 3 channels (RGB)
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Matter plug-in for core behavior
|
||||
|
||||
# dummy declaration for solidification
|
||||
class Matter_Plugin_Light1 end
|
||||
|
||||
#@ solidify:Matter_Plugin_Light3,weak
|
||||
|
||||
class Matter_Plugin_Light3 : Matter_Plugin_Light1
|
||||
static var CLUSTERS = {
|
||||
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
|
||||
# 0x0003: inherited # Identify 1.2 p.16
|
||||
# 0x0004: inherited # Groups 1.3 p.21
|
||||
# 0x0005: inherited # Scenes 1.4 p.30 - no writable
|
||||
# 0x0006: inherited # On/Off 1.5 p.48
|
||||
# 0x0008: inherited # Level Control 1.6 p.57
|
||||
0x0300: [0,1,7,8,0xF,0x4001,0x400A,0xFFFC,0xFFFD],# Color Control 3.2 p.111
|
||||
}
|
||||
static var TYPES = { 0x010D: 2 } # Extended Color Light
|
||||
|
||||
var shadow_hue, shadow_sat
|
||||
|
||||
#############################################################
|
||||
# Constructor
|
||||
def init(device, endpoint)
|
||||
super(self).init(device, endpoint)
|
||||
self.shadow_hue = 0
|
||||
self.shadow_sat = 0
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Update shadow
|
||||
#
|
||||
def update_shadow()
|
||||
import light
|
||||
super(self).update_shadow()
|
||||
var light_status = light.get()
|
||||
var hue = light_status.find('hue', nil)
|
||||
var sat = light_status.find('sat', nil)
|
||||
if hue != nil hue = tasmota.scale_uint(hue, 0, 360, 0, 254) else hue = self.shadow_hue end
|
||||
if sat != nil sat = tasmota.scale_uint(sat, 0, 255, 0, 254) else sat = self.shadow_sat end
|
||||
if hue != self.shadow_hue self.attribute_updated(nil, 0x0300, 0x0000) self.shadow_hue = hue end
|
||||
if sat != self.shadow_sat self.attribute_updated(nil, 0x0300, 0x0001) self.shadow_sat = sat end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# read an attribute
|
||||
#
|
||||
def read_attribute(session, ctx)
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var attribute = ctx.attribute
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0300 # ========== Color Control 3.2 p.111 ==========
|
||||
if attribute == 0x0000 # ---------- CurrentHue / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, self.shadow_hue)
|
||||
elif attribute == 0x0001 # ---------- CurrentSaturation / u2 ----------
|
||||
return TLV.create_TLV(TLV.U1, self.shadow_sat)
|
||||
elif attribute == 0x0007 # ---------- ColorTemperatureMireds / u2 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0)
|
||||
elif attribute == 0x0008 # ---------- ColorMode / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0)# 0 = CurrentHue and CurrentSaturation
|
||||
elif attribute == 0x000F # ---------- Options / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0)
|
||||
elif attribute == 0x4001 # ---------- EnhancedColorMode / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0)
|
||||
elif attribute == 0x400A # ---------- ColorCapabilities / map2 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0)
|
||||
|
||||
# Defined Primaries Information Attribute Set
|
||||
elif attribute == 0x0010 # ---------- NumberOfPrimaries / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0)
|
||||
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0x01) # HS
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 5) # "new data model format and notation, FeatureMap support"
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).read_attribute(session, ctx)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Invoke a command
|
||||
#
|
||||
# returns a TLV object if successful, contains the response
|
||||
# or an `int` to indicate a status
|
||||
def invoke_request(session, val, ctx)
|
||||
import light
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var command = ctx.command
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0300 # ========== Color Control 3.2 p.111 ==========
|
||||
if command == 0x0000 # ---------- MoveToHue ----------
|
||||
var hue_in = val.findsubval(0) # Hue 0..254
|
||||
var hue = tasmota.scale_uint(hue_in, 0, 254, 0, 360)
|
||||
light.set({'hue': hue})
|
||||
self.update_shadow()
|
||||
ctx.log = "hue:"+str(hue_in)
|
||||
return true
|
||||
elif command == 0x0001 # ---------- MoveHue ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x0002 # ---------- StepHue ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x0003 # ---------- MoveToSaturation ----------
|
||||
var sat_in = val.findsubval(0) # Sat 0..254
|
||||
var sat = tasmota.scale_uint(sat_in, 0, 254, 0, 255)
|
||||
light.set({'sat': sat})
|
||||
self.update_shadow()
|
||||
ctx.log = "sat:"+str(sat_in)
|
||||
return true
|
||||
elif command == 0x0004 # ---------- MoveSaturation ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x0005 # ---------- StepSaturation ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
elif command == 0x0006 # ---------- MoveToHueAndSaturation ----------
|
||||
var hue_in = val.findsubval(0) # Hue 0..254
|
||||
var hue = tasmota.scale_uint(hue_in, 0, 254, 0, 360)
|
||||
var sat_in = val.findsubval(1) # Sat 0..254
|
||||
var sat = tasmota.scale_uint(sat_in, 0, 254, 0, 255)
|
||||
light.set({'hue': hue, 'sat': sat})
|
||||
self.update_shadow()
|
||||
ctx.log = "hue:"+str(hue_in)+" sat:"+str(sat_in)
|
||||
return true
|
||||
elif command == 0x0047 # ---------- StopMoveStep ----------
|
||||
# TODO, we don't really support it
|
||||
return true
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).invoke_request(session, val, ctx)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
matter.Plugin_Light3 = Matter_Plugin_Light3
|
223
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_OnOff.be
Normal file
223
lib/libesp32/berry_matter/src/embedded/Matter_Plugin_OnOff.be
Normal file
@ -0,0 +1,223 @@
|
||||
#
|
||||
# Matter_Plugin_OnOff.be - implements the behavior for a Relay (OnOff)
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Matter plug-in for core behavior
|
||||
|
||||
# dummy declaration for solidification
|
||||
class Matter_Plugin end
|
||||
|
||||
#@ solidify:Matter_Plugin_OnOff,weak
|
||||
|
||||
class Matter_Plugin_OnOff : Matter_Plugin
|
||||
static var CLUSTERS = {
|
||||
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
|
||||
0x0003: [0,1,0xFFFC,0xFFFD], # Identify 1.2 p.16
|
||||
0x0004: [0,0xFFFC,0xFFFD], # Groups 1.3 p.21
|
||||
0x0005: [0,1,2,3,4,5,0xFFFC,0xFFFD], # Scenes 1.4 p.30 - no writable
|
||||
0x0006: [0,0xFFFC,0xFFFD], # On/Off 1.5 p.48
|
||||
# 0x0008: [0,15,17,0xFFFC,0xFFFD] # Level Control 1.6 p.57
|
||||
}
|
||||
static var TYPES = { 0x010A: 2 } # On/Off Light
|
||||
|
||||
var tasmota_relay_index # Relay number in Tasmota (zero based)
|
||||
var shadow_onoff # fake status for now # TODO
|
||||
|
||||
#############################################################
|
||||
# Constructor
|
||||
def init(device, endpoint, tasmota_relay_index)
|
||||
super(self).init(device, endpoint)
|
||||
self.get_onoff() # read actual value
|
||||
if tasmota_relay_index == nil tasmota_relay_index = 0 end
|
||||
self.tasmota_relay_index = tasmota_relay_index
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Model
|
||||
#
|
||||
def set_onoff(v)
|
||||
tasmota.set_power(self.tasmota_relay_index, bool(v))
|
||||
self.get_onoff()
|
||||
end
|
||||
#############################################################
|
||||
# get_onoff
|
||||
#
|
||||
# Update shadow and signal any change
|
||||
def get_onoff()
|
||||
var state = tasmota.get_power(self.tasmota_relay_index)
|
||||
if state != nil
|
||||
if self.shadow_onoff != nil && self.shadow_onoff != bool(state)
|
||||
self.onoff_changed() # signal any change
|
||||
end
|
||||
self.shadow_onoff = state
|
||||
end
|
||||
if self.shadow_onoff == nil self.shadow_onoff = false end # avoid any `nil` value when initializing
|
||||
return self.shadow_onoff
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# read an attribute
|
||||
#
|
||||
def read_attribute(session, ctx)
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var attribute = ctx.attribute
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0003 # ========== Identify 1.2 p.16 ==========
|
||||
if attribute == 0x0000 # ---------- IdentifyTime / u2 ----------
|
||||
return TLV.create_TLV(TLV.U2, 0) # no identification in progress
|
||||
elif attribute == 0x0001 # ---------- IdentifyType / enum8 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0) # IdentifyType = 0x00 None
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0) # no features
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4) # "new data model format and notation"
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0004 # ========== Groups 1.3 p.21 ==========
|
||||
if attribute == 0x0000 # ---------- ----------
|
||||
return nil # TODO
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0)#
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4)# "new data model format and notation"
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0005 # ========== Scenes 1.4 p.30 - no writable ==========
|
||||
if attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0) # 0 = no Level Control for Lighting
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4) # 0 = no Level Control for Lighting
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
|
||||
if attribute == 0x0000 # ---------- OnOff / bool ----------
|
||||
return TLV.create_TLV(TLV.BOOL, self.get_onoff())
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0) # 0 = no Level Control for Lighting
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4) # 0 = no Level Control for Lighting
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0008 # ========== Level Control 1.6 p.57 ==========
|
||||
if attribute == 0x0000 # ---------- CurrentLevel / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0x88)
|
||||
elif attribute == 0x000F # ---------- Options / map8 ----------
|
||||
return TLV.create_TLV(TLV.U1, 0) # 0 = no Level Control for Lighting
|
||||
elif attribute == 0x0010 # ---------- OnLevel / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 1) # 0 = no Level Control for Lighting
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0) # 0 = no Level Control for Lighting
|
||||
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
|
||||
return TLV.create_TLV(TLV.U4, 4) # 0 = no Level Control for Lighting
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).read_attribute(session, ctx)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Invoke a command
|
||||
#
|
||||
# returns a TLV object if successful, contains the response
|
||||
# or an `int` to indicate a status
|
||||
def invoke_request(session, val, ctx)
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var command = ctx.command
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0003 # ========== Identify 1.2 p.16 ==========
|
||||
|
||||
if command == 0x0000 # ---------- Identify ----------
|
||||
# ignore
|
||||
return true
|
||||
elif command == 0x0001 # ---------- IdentifyQuery ----------
|
||||
# create IdentifyQueryResponse
|
||||
# ID=1
|
||||
# 0=Certificate (octstr)
|
||||
var iqr = TLV.Matter_TLV_struct()
|
||||
iqr.add_TLV(0, TLV.U2, 0) # Timeout
|
||||
ctx.command = 0x00 # IdentifyQueryResponse
|
||||
return iqr
|
||||
elif command == 0x0040 # ---------- TriggerEffect ----------
|
||||
# ignore
|
||||
return true
|
||||
end
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0004 # ========== Groups 1.3 p.21 ==========
|
||||
# TODO
|
||||
return true
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0005 # ========== Scenes 1.4 p.30 ==========
|
||||
# TODO
|
||||
return true
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
|
||||
if command == 0x0000 # ---------- Off ----------
|
||||
self.set_onoff(false)
|
||||
return true
|
||||
elif command == 0x0001 # ---------- On ----------
|
||||
self.set_onoff(true)
|
||||
return true
|
||||
elif command == 0x0002 # ---------- Toggle ----------
|
||||
self.set_onoff(!self.get_onoff())
|
||||
return true
|
||||
end
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0008 # ========== Level Control 1.6 p.57 ==========
|
||||
if command == 0x0000 # ---------- MoveToLevel ----------
|
||||
return true
|
||||
elif command == 0x0001 # ---------- Move ----------
|
||||
return true
|
||||
elif command == 0x0002 # ---------- Step ----------
|
||||
return true
|
||||
elif command == 0x0003 # ---------- Stop ----------
|
||||
return true
|
||||
elif command == 0x0004 # ---------- MoveToLevelWithOnOff ----------
|
||||
return true
|
||||
elif command == 0x0005 # ---------- MoveWithOnOff ----------
|
||||
return true
|
||||
elif command == 0x0006 # ---------- StepWithOnOff ----------
|
||||
return true
|
||||
elif command == 0x0007 # ---------- StopWithOnOff ----------
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Signal that onoff attribute changed
|
||||
def onoff_changed()
|
||||
self.attribute_updated(nil, 0x0006, 0x0000) # send to all endpoints
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# every_second
|
||||
def every_second()
|
||||
self.get_onoff() # force reading value and sending subscriptions
|
||||
end
|
||||
end
|
||||
matter.Plugin_OnOff = Matter_Plugin_OnOff
|
@ -1,93 +0,0 @@
|
||||
#
|
||||
# Matter_Plugin_Relay.be - implements the behavior for a Relay (OnOff)
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Matter plug-in for core behavior
|
||||
|
||||
# dummy declaration for solidification
|
||||
class Matter_Plugin end
|
||||
|
||||
#@ solidify:Matter_Plugin_Relay,weak
|
||||
|
||||
class Matter_Plugin_Relay : Matter_Plugin
|
||||
static var ENDPOINTS = [ 1 ]
|
||||
static var CLUSTERS = {
|
||||
0x001D: [0,1,2,3],
|
||||
0x0003: [],
|
||||
0x0004: [],
|
||||
0x0005: [],
|
||||
0x0006: [0],
|
||||
0x0008: [],
|
||||
# 0x0406: []
|
||||
}
|
||||
static var TYPES = [ 0x0100 ] # On/Off Light
|
||||
|
||||
#############################################################
|
||||
# Constructor
|
||||
def init(device)
|
||||
super(self).init(device)
|
||||
self.endpoints = self.ENDPOINTS
|
||||
self.clusters = self.CLUSTERS
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# read an attribute
|
||||
#
|
||||
def read_attribute(msg, ctx)
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var attribute = ctx.attribute
|
||||
|
||||
if cluster == 0x001D # ========== Descriptor Cluster 9.5 p.453 ==========
|
||||
|
||||
if attribute == 0x0000 # ---------- DeviceTypeList / list[DeviceTypeStruct] ----------
|
||||
var dtl = TLV.Matter_TLV_array()
|
||||
var d1 = dtl.add_struct()
|
||||
d1.add_TLV(0, TLV.U2, self.TYPES[0]) # DeviceType
|
||||
d1.add_TLV(1, TLV.U2, 1) # Revision
|
||||
return dtl
|
||||
elif attribute == 0x0001 # ---------- ServerList / list[cluster-id] ----------
|
||||
var sl = TLV.Matter_TLV_array()
|
||||
for cl: self.get_cluster_list()
|
||||
sl.add_TLV(nil, TLV.U4, cl)
|
||||
end
|
||||
return sl
|
||||
elif attribute == 0x0002 # ---------- ClientList / list[cluster-id] ----------
|
||||
var cl = TLV.Matter_TLV_array()
|
||||
cl.add_TLV(nil, TLV.U2, 0x0006)
|
||||
return cl
|
||||
elif attribute == 0x0003 # ---------- PartsList / list[endpoint-no]----------
|
||||
var pl = TLV.Matter_TLV_array()
|
||||
return pl
|
||||
end
|
||||
end
|
||||
# no match found, return that the attribute is unsupported end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Invoke a command
|
||||
#
|
||||
# returns a TLV object if successful, contains the response
|
||||
# or an `int` to indicate a status
|
||||
def invoke_request(msg, val, ctx)
|
||||
# no match found, return that the command is unsupported
|
||||
end
|
||||
end
|
||||
matter.Plugin_core = Matter_Plugin_core
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Matter_Plugin_core.be - implements the core features that a Matter device must implemment
|
||||
# Matter_Plugin_Root.be - implements the core features that a Matter device must implemment
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
@ -17,43 +17,43 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Matter plug-in for core behavior
|
||||
# Matter plug-in for root behavior
|
||||
|
||||
# dummy declaration for solidification
|
||||
class Matter_Plugin end
|
||||
|
||||
#@ solidify:Matter_Plugin_core,weak
|
||||
#@ solidify:Matter_Plugin_Root,weak
|
||||
|
||||
class Matter_Plugin_core : Matter_Plugin
|
||||
static var ENDPOINTS = [ 0 ]
|
||||
class Matter_Plugin_Root : Matter_Plugin
|
||||
static var CLUSTERS = {
|
||||
0x001D: [0,1,2,3],
|
||||
0x0028: [0,1,2,3,4,5,6,7,8,9],
|
||||
0x002B: [0,1],
|
||||
0x002C: [0,1,2],
|
||||
0x0030: [0,1,2,3,4],
|
||||
0x0031: [3,0xFFFC],
|
||||
0x0032: [],
|
||||
0x0033: [0,1,2,8],
|
||||
0x0034: [],
|
||||
0x0038: [0,1,7],
|
||||
0x003E: [0,1,2,3,4,5],
|
||||
0x003C: [],
|
||||
0x003F: []
|
||||
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
|
||||
0x001F: [0,2,3,4], # Access Control Cluster, p.461
|
||||
0x0028: [0,1,2,3,4,5,6,7,8,9,0x0A,0x0F,0x12,0x13],# Basic Information Cluster cluster 11.1 p.565
|
||||
# 0x002A: [0,1,2,3], # OTA Software Update Requestor Cluster Definition 11.19.7 p.762
|
||||
0x002B: [0,1], # Localization Configuration Cluster 11.3 p.580
|
||||
0x002C: [0,1,2], # Time Format Localization Cluster 11.4 p.581
|
||||
0x0030: [0,1,2,3,4], # GeneralCommissioning cluster 11.9 p.627
|
||||
0x0031: [3,4,0xFFFC], # Network Commissioning Cluster cluster 11.8 p.606
|
||||
0x0032: [], # Diagnostic Logs Cluster 11.10 p.637
|
||||
0x0033: [0,1,2,8], # General Diagnostics Cluster 11.11 p.642
|
||||
0x0034: [], # Software Diagnostics Cluster 11.12 p.654
|
||||
0x0038: [0,1,7], # Time Synchronization 11.16 p.689
|
||||
0x003C: [0,1,2], # Administrator Commissioning Cluster 11.18 p.725
|
||||
0x003E: [0,1,2,3,4,5], # Node Operational Credentials Cluster 11.17 p.704
|
||||
0x003F: [] # Group Key Management Cluster 11.2 p.572
|
||||
}
|
||||
static var TYPES = { 0x0016: 1 } # Root node
|
||||
|
||||
#############################################################
|
||||
# Constructor
|
||||
def init(device)
|
||||
super(self).init(device)
|
||||
self.endpoints = self.ENDPOINTS
|
||||
self.clusters = self.CLUSTERS
|
||||
def init(device, endpoint)
|
||||
super(self).init(device, endpoint)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# read an attribute
|
||||
#
|
||||
def read_attribute(msg, ctx)
|
||||
def read_attribute(session, ctx)
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
@ -62,7 +62,7 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
if cluster == 0x0030 # ========== GeneralCommissioning cluster 11.9 p.627 ==========
|
||||
|
||||
if attribute == 0x0000 # ---------- Breadcrumb ----------
|
||||
return TLV.create_TLV(TLV.U8, msg.session.breadcrumb)
|
||||
return TLV.create_TLV(TLV.U8, session._breadcrumb)
|
||||
elif attribute == 0x0001 # ---------- BasicCommissioningInfo / BasicCommissioningInfo----------
|
||||
var bci = TLV.Matter_TLV_struct()
|
||||
bci.add_TLV(0, TLV.U2, 60) # FailSafeExpiryLengthSeconds
|
||||
@ -120,9 +120,9 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
end
|
||||
return nwi
|
||||
elif attribute == 0x0001 # ---------- RebootCount u16 ----------
|
||||
return TLV.create_TLV(TLV.U2, tasmota.cmd("Status 1")['StatusPRM']['BootCount'])
|
||||
return TLV.create_TLV(TLV.U2, tasmota.cmd("Status 1", true)['StatusPRM']['BootCount'])
|
||||
elif attribute == 0x0002 # ---------- UpTime u16 ----------
|
||||
return TLV.create_TLV(TLV.U4, tasmota.cmd("Status 11")['StatusSTS']['UptimeSec'])
|
||||
return TLV.create_TLV(TLV.U4, tasmota.cmd("Status 11", true)['StatusSTS']['UptimeSec'])
|
||||
# TODO add later other attributes
|
||||
elif attribute == 0x0008 # ---------- TestEventTriggersEnabled bool ----------
|
||||
return TLV.create_TLV(TLV.BOOL, false) # false - maybe can set to true
|
||||
@ -150,80 +150,125 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
|
||||
if attribute == 0x0000 # ---------- NOCs / list[NOCStruct] ----------
|
||||
var nocl = TLV.Matter_TLV_array() # NOCs, p.711
|
||||
for session: self.device.sessions.sessions_active()
|
||||
for loc_fabric: self.device.sessions.active_fabrics()
|
||||
var nocs = nocl.add_struct(nil)
|
||||
nocs.add_TLV(1, TLV.B2, session.noc) # NOC
|
||||
nocs.add_TLV(2, TLV.B2, session.icac) # ICAC
|
||||
nocs.add_TLV(1, TLV.B2, loc_fabric.get_noc()) # NOC
|
||||
nocs.add_TLV(2, TLV.B2, loc_fabric.get_icac()) # ICAC
|
||||
nocs.add_TLV(0xFE, TLV.U2, loc_fabric.get_fabric_index()) # Label
|
||||
end
|
||||
return nocl
|
||||
elif attribute == 0x0001 # ---------- Fabrics / list[FabricDescriptorStruct] ----------
|
||||
var fabrics = TLV.Matter_TLV_array() # Fabrics, p.711
|
||||
for session: self.device.sessions.sessions_active()
|
||||
var root_ca_tlv = TLV.parse(session.get_ca())
|
||||
for loc_fabric: self.device.sessions.active_fabrics()
|
||||
var root_ca_tlv = TLV.parse(loc_fabric.get_ca())
|
||||
var fab = fabrics.add_struct(nil) # encoding see p.303
|
||||
fab.add_TLV(1, TLV.B2, root_ca_tlv.findsubval(9)) # RootPublicKey
|
||||
fab.add_TLV(2, TLV.U2, session.admin_vendor) # VendorID
|
||||
fab.add_TLV(3, TLV.U8, session.fabric) # FabricID
|
||||
fab.add_TLV(4, TLV.U8, session.deviceid) # NodeID
|
||||
fab.add_TLV(5, TLV.UTF1, session.fabric_label) # Label
|
||||
fab.add_TLV(2, TLV.U2, loc_fabric.get_admin_vendor()) # VendorID
|
||||
fab.add_TLV(3, TLV.U8, loc_fabric.get_fabric_id()) # FabricID
|
||||
fab.add_TLV(4, TLV.U8, loc_fabric.get_device_id()) # NodeID
|
||||
fab.add_TLV(5, TLV.UTF1, loc_fabric.get_fabric_label()) # Label
|
||||
fab.add_TLV(0xFE, TLV.U2, loc_fabric.get_fabric_index()) # idx
|
||||
end
|
||||
return fabrics
|
||||
elif attribute == 0x0002 # ---------- SupportedFabrics / u1 ----------
|
||||
return TLV.create_TLV(TLV.U1, 5) # Max 5 fabrics
|
||||
return TLV.create_TLV(TLV.U1, matter.Fabric._MAX_CASE) # Max 5 fabrics
|
||||
elif attribute == 0x0003 # ---------- CommissionedFabrics / u1 ----------
|
||||
var sessions_active = self.device.sessions.sessions_active()
|
||||
return TLV.create_TLV(TLV.U1, size(sessions_active)) # number of active sessions
|
||||
var fabric_actice = self.device.sessions.count_active_fabrics()
|
||||
return TLV.create_TLV(TLV.U1, fabric_actice) # number of active fabrics
|
||||
elif attribute == 0x0004 # ---------- TrustedRootCertificates / list[octstr] ----------
|
||||
# TODO
|
||||
elif attribute == 0x0005 # ---------- Current FabricIndex / u1 ----------
|
||||
var sessions_active = self.device.sessions.sessions_active()
|
||||
var fabric_index = sessions_active.find(msg.session)
|
||||
if fabric_index == nil fabric_index = 0 end
|
||||
return TLV.create_TLV(TLV.U1, fabric_index) # number of active sessions
|
||||
var fab_index = session._fabric.get_fabric_index()
|
||||
if fab_index == nil fab_index = 0 end # if PASE session, then the fabric index should be zero
|
||||
return TLV.create_TLV(TLV.U1, fab_index) # number of active sessions
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x003C # ========== Administrator Commissioning Cluster 11.18 p.725 ==========
|
||||
# TODO
|
||||
|
||||
if attribute == 0x0000 # ---------- WindowStatus / u8 ----------
|
||||
var commissioning_open = self.device.is_commissioning_open()
|
||||
var basic_commissioning = self.device.is_root_commissioning_open()
|
||||
var val = commissioning_open ? (basic_commissioning ? 2 #-BasicWindowOpen-# : 1 #-EnhancedWindowOpen-#) : 0 #-WindowNotOpen-#
|
||||
return TLV.create_TLV(TLV.U1, val)
|
||||
elif attribute == 0x0001 # ---------- AdminFabricIndex / u16 ----------
|
||||
var admin_fabric = self.device.commissioning_admin_fabric
|
||||
if admin_fabric != nil
|
||||
return TLV.create_TLV(TLV.U2, admin_fabric.get_fabric_index())
|
||||
else
|
||||
return TLV.create_TLV(TLV.NULL, nil)
|
||||
end
|
||||
elif attribute == 0x0002 # ---------- AdminVendorId / u16 ----------
|
||||
var admin_fabric = self.device.commissioning_admin_fabric
|
||||
if admin_fabric != nil
|
||||
return TLV.create_TLV(TLV.U2, admin_fabric.get_admin_vendor())
|
||||
else
|
||||
return TLV.create_TLV(TLV.NULL, nil)
|
||||
end
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0028 # ========== Basic Information Cluster cluster 11.1 p.565 ==========
|
||||
|
||||
if attribute == 0x0000 # ---------- DataModelRevision / u16 ----------
|
||||
return TLV.create_TLV(TLV.U2, 0)
|
||||
if attribute == 0x0000 # ---------- DataModelRevision / CommissioningWindowStatus ----------
|
||||
return TLV.create_TLV(TLV.U2, 1)
|
||||
elif attribute == 0x0001 # ---------- VendorName / string ----------
|
||||
return TLV.create_TLV(TLV.UTF1, "Tasmota")
|
||||
elif attribute == 0x0002 # ---------- VendorID / vendor-id ----------
|
||||
return TLV.create_TLV(TLV.U2, self.device.vendorid) # Vendor ID reserved for development
|
||||
elif attribute == 0x0003 # ---------- ProductName / string ----------
|
||||
return TLV.create_TLV(TLV.UTF1, tasmota.cmd("DeviceName")['DeviceName'])
|
||||
return TLV.create_TLV(TLV.UTF1, tasmota.cmd("DeviceName", true)['DeviceName'])
|
||||
elif attribute == 0x0004 # ---------- ProductID / u16 (opt) ----------
|
||||
return TLV.create_TLV(TLV.U2, 32768) # taken from esp-matter example
|
||||
elif attribute == 0x0005 # ---------- NodeLabel / string ----------
|
||||
return TLV.create_TLV(TLV.UTF1, tasmota.cmd("FriendlyName")['FriendlyName1'])
|
||||
return TLV.create_TLV(TLV.UTF1, tasmota.cmd("FriendlyName", true)['FriendlyName1'])
|
||||
elif attribute == 0x0006 # ---------- Location / string ----------
|
||||
return TLV.create_TLV(TLV.UTF1, "XX") # no location
|
||||
elif attribute == 0x0007 # ---------- HardwareVersion / u16 ----------
|
||||
return TLV.create_TLV(TLV.U2, 0)
|
||||
elif attribute == 0x0008 # ---------- HardwareVersionString / string ----------
|
||||
return TLV.create_TLV(TLV.UTF1, tasmota.cmd("Status 2")['StatusFWR']['Hardware'])
|
||||
return TLV.create_TLV(TLV.UTF1, tasmota.cmd("Status 2", true)['StatusFWR']['Hardware'])
|
||||
elif attribute == 0x0009 # ---------- SoftwareVersion / u32 ----------
|
||||
return TLV.create_TLV(TLV.U2, 0)
|
||||
return TLV.create_TLV(TLV.U2, 1)
|
||||
elif attribute == 0x000A # ---------- SoftwareVersionString / string ----------
|
||||
return TLV.create_TLV(TLV.UTF1, tasmota.cmd("Status 2")['StatusFWR']['Version'])
|
||||
var version_full = tasmota.cmd("Status 2", true)['StatusFWR']['Version']
|
||||
var version_end = string.find(version_full, '(')
|
||||
if version_end > 0 version_full = version_full[0..version_end - 1] end
|
||||
return TLV.create_TLV(TLV.UTF1, version_full)
|
||||
elif attribute == 0x000F # ---------- SerialNumber / string ----------
|
||||
return TLV.create_TLV(TLV.UTF1, tasmota.wifi().find("mac", ""))
|
||||
elif attribute == 0x0012 # ---------- UniqueID / string 32 max ----------
|
||||
return TLV.create_TLV(TLV.UTF1, tasmota.wifi().find("mac", ""))
|
||||
elif attribute == 0x0013 # ---------- CapabilityMinima / CapabilityMinimaStruct ----------
|
||||
var cps = TLV.Matter_TLV_struct()
|
||||
cps.add_TLV(0, TLV.U2, 3) # CaseSessionsPerFabric = 3
|
||||
cps.add_TLV(1, TLV.U2, 3) # SubscriptionsPerFabric = 5
|
||||
return cps
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x003F # ========== Group Key Management Cluster 11.2 p.572 ==========
|
||||
# TODO
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x002A # ========== OTA Software Update Requestor Cluster Definition 11.19.7 p.762 ==========
|
||||
|
||||
if attribute == 0x0000 # ---------- DefaultOTAProviders / list[ProviderLocationStruct] ----------
|
||||
return TLV.Matter_TLV_array() # empty list for now TODO
|
||||
elif attribute == 0x0001 # ---------- UpdatePossible / bool ----------
|
||||
return TLV.create_TLV(TLV.BOOL, 0) # we claim that update is not possible, would require to go to Tasmota UI
|
||||
elif attribute == 0x0002 # ---------- UpdateState / UpdateStateEnum ----------
|
||||
return TLV.create_TLV(TLV.U1, 1) # Idle
|
||||
elif attribute == 0x0003 # ---------- UpdateStateProgress / uint8 ----------
|
||||
return TLV.create_TLV(TLV.NULL, nil) # null, nothing in process
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x002B # ========== Localization Configuration Cluster 11.3 p.580 ==========
|
||||
|
||||
if attribute == 0x0000 # ---------- ActiveLocale / string ----------
|
||||
return TLV.create_TLV(TLV.UTF1, tasmota.locale())
|
||||
elif attribute == 0x0001 # ---------- SupportedLocales / list[string] ----------
|
||||
var locl = TLV.Matter_TLV_list()
|
||||
var locl = TLV.Matter_TLV_array()
|
||||
locl.add_TLV(nil, TLV.UTF1, tasmota.locale())
|
||||
return locl
|
||||
end
|
||||
@ -236,7 +281,7 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
elif attribute == 0x0001 # ---------- ActiveCalendarType / CalendarType ----------
|
||||
return TLV.create_TLV(TLV.U1, 4) # 4 = Gregorian
|
||||
elif attribute == 0x0002 # ---------- SupportedCalendarTypes / list[CalendarType] ----------
|
||||
var callist = TLV.Matter_TLV_list()
|
||||
var callist = TLV.Matter_TLV_array()
|
||||
callist.add_TLV(nil, TLV.create_TLV(TLV.U1, 4))
|
||||
return callist
|
||||
end
|
||||
@ -246,32 +291,25 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
if attribute == 0x0003 # ---------- ConnectMaxTimeSeconds / uint8 ----------
|
||||
return TLV.create_TLV(TLV.U1, 30) # 30 - value taking from example in esp-matter
|
||||
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
|
||||
return TLV.create_TLV(TLV.U4, 0) # 15s ??? TOOD what should we put here?
|
||||
return TLV.create_TLV(TLV.U4, 0x04) # Put Eth for now which should work for any on-network
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x001D # ========== Descriptor Cluster 9.5 p.453 ==========
|
||||
if attribute == 0x0000 # ---------- DeviceTypeList / list[DeviceTypeStruct] ----------
|
||||
elif attribute == 0x0001 # ---------- ServerList / list[cluster-id] ----------
|
||||
var sl = TLV.Matter_TLV_array()
|
||||
for cl: self.get_cluster_list()
|
||||
sl.add_TLV(nil, TLV.U4, cl)
|
||||
end
|
||||
return sl
|
||||
elif attribute == 0x0002 # ---------- ClientList / list[cluster-id] ----------
|
||||
var cl = TLV.Matter_TLV_array()
|
||||
return cl
|
||||
elif attribute == 0x0003 # ---------- PartsList / list[endpoint-no]----------
|
||||
var eps = self.device.get_active_endpoints(true)
|
||||
elif cluster == 0x001D # ========== Descriptor Cluster 9.5 p.453 ==========
|
||||
|
||||
# overwrite PartsList
|
||||
if attribute == 0x0003 # ---------- PartsList / list[endpoint-no]----------
|
||||
var pl = TLV.Matter_TLV_array()
|
||||
var eps = self.device.get_active_endpoints(true)
|
||||
for ep: eps
|
||||
pl.add_TLV(nil, TLV.U2, ep) # add each endpoint
|
||||
end
|
||||
return pl
|
||||
else
|
||||
return super(self).read_attribute(session, ctx)
|
||||
end
|
||||
|
||||
else
|
||||
ctx.status = matter.UNSUPPORTED_CLUSTER
|
||||
return super(self).read_attribute(session, ctx)
|
||||
|
||||
end
|
||||
# no match found, return that the attribute is unsupported
|
||||
@ -282,12 +320,12 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
#
|
||||
# returns a TLV object if successful, contains the response
|
||||
# or an `int` to indicate a status
|
||||
def invoke_request(msg, val, ctx)
|
||||
def invoke_request(session, val, ctx)
|
||||
import crypto
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var command = ctx.command
|
||||
var session = msg.session
|
||||
if cluster == 0x0030 # ========== GeneralCommissioning cluster 11.9 p.627 ==========
|
||||
|
||||
if command == 0x0000 # ---------- ArmFailSafe ----------
|
||||
@ -297,7 +335,7 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
# 1=DebugText
|
||||
var ExpiryLengthSeconds = val.findsubval(0, 900)
|
||||
var Breadcrumb = val.findsubval(1, 0)
|
||||
session.breadcrumb = Breadcrumb
|
||||
session._breadcrumb = Breadcrumb
|
||||
|
||||
var afsr = TLV.Matter_TLV_struct()
|
||||
afsr.add_TLV(0, TLV.U1, 0) # ErrorCode = OK
|
||||
@ -309,7 +347,7 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
var NewRegulatoryConfig = val.findsubval(0) # RegulatoryLocationType Enum
|
||||
var CountryCode = val.findsubval(1, "XX")
|
||||
var Breadcrumb = val.findsubval(2, 0)
|
||||
session.breadcrumb = Breadcrumb
|
||||
session._breadcrumb = Breadcrumb
|
||||
# create SetRegulatoryConfigResponse
|
||||
# ID=1
|
||||
# 0=ErrorCode (OK=0)
|
||||
@ -322,8 +360,10 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
|
||||
elif command == 0x0004 # ---------- CommissioningComplete p.636 ----------
|
||||
# no data
|
||||
session.breadcrumb = 0 # clear breadcrumb
|
||||
session._breadcrumb = 0 # clear breadcrumb
|
||||
session.fabric_completed() # fabric information is complete, persist
|
||||
session.set_no_expiration()
|
||||
session.save()
|
||||
|
||||
# create CommissioningCompleteResponse
|
||||
# ID=1
|
||||
@ -335,7 +375,7 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
ctx.command = 0x05 # CommissioningCompleteResponse
|
||||
|
||||
self.device.start_commissioning_complete_deferred(session)
|
||||
return ccr # trigger a standalone ack
|
||||
return ccr
|
||||
end
|
||||
|
||||
elif cluster == 0x003E # ========== Node Operational Credentials Cluster 11.17 p.704 ==========
|
||||
@ -364,7 +404,7 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
att_elts.add_TLV(1, TLV.B2, matter.CD_FFF1_8000()) # certification_declaration
|
||||
att_elts.add_TLV(2, TLV.B1, AttestationNonce) # attestation_nonce
|
||||
att_elts.add_TLV(3, TLV.U4, tasmota.rtc()['utc']) # timestamp in epoch-s
|
||||
var attestation_message = att_elts.encode()
|
||||
var attestation_message = att_elts.tlv2raw()
|
||||
|
||||
var ac = session.get_ac()
|
||||
var attestation_tbs = attestation_message + ac
|
||||
@ -391,7 +431,7 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
var nocsr_elements = TLV.Matter_TLV_struct()
|
||||
nocsr_elements.add_TLV(1, TLV.B2, csr)
|
||||
nocsr_elements.add_TLV(2, TLV.B1, CSRNonce)
|
||||
var nocsr_elements_message = nocsr_elements.encode()
|
||||
var nocsr_elements_message = nocsr_elements.tlv2raw()
|
||||
# sign with attestation challenge
|
||||
var nocsr_tbs = nocsr_elements_message + session.get_ac()
|
||||
tasmota.log("MTR: nocsr_tbs=" + nocsr_tbs.tohex(), 3)
|
||||
@ -416,6 +456,8 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
elif command == 0x0006 # ---------- AddNOC ----------
|
||||
var NOCValue = val.findsubval(0) # octstr max 400
|
||||
var ICACValue = val.findsubval(1) # octstr max 400
|
||||
# Apple sends an empty ICAC instead of a missing attribute, fix this
|
||||
if size(ICACValue) == 0 ICACValue = nil end
|
||||
var IpkValue = val.findsubval(2) # octstr max 16
|
||||
var CaseAdminSubject = val.findsubval(3)
|
||||
var AdminVendorId = val.findsubval(4)
|
||||
@ -427,32 +469,40 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
|
||||
session.set_noc(NOCValue, ICACValue)
|
||||
session.set_ipk_epoch_key(IpkValue)
|
||||
session.admin_subject = CaseAdminSubject
|
||||
session.admin_vendor = AdminVendorId
|
||||
session.set_admin_subject_vendor(CaseAdminSubject, AdminVendorId)
|
||||
|
||||
# extract important information from NOC
|
||||
var noc_cert = matter.TLV.parse(NOCValue)
|
||||
var dnlist = noc_cert.findsub(6)
|
||||
var fabric = dnlist.findsubval(21)
|
||||
var fabric_id = dnlist.findsubval(21)
|
||||
var deviceid = dnlist.findsubval(17)
|
||||
if !fabric || !deviceid
|
||||
if !fabric_id || !deviceid
|
||||
tasmota.log("MTR: Error: no fabricid nor deviceid in NOC certificate", 2)
|
||||
return false
|
||||
end
|
||||
# convert fo bytes(8)
|
||||
if type(fabric) == 'int' fabric = int64(fabric).tobytes() else fabric = fabric.tobytes() end
|
||||
if type(deviceid) == 'int' deviceid = int64(deviceid).tobytes() else deviceid = deviceid.tobytes() end
|
||||
if type(fabric_id) == 'int' fabric_id = int64.fromu32(fabric_id).tobytes() else fabric_id = fabric_id.tobytes() end
|
||||
if type(deviceid) == 'int' deviceid = int64.fromu32(deviceid).tobytes() else deviceid = deviceid.tobytes() end
|
||||
|
||||
var root_ca = matter.TLV.parse(session.get_ca()).findsubval(9) # extract public key from ca
|
||||
root_ca = root_ca[1..] # remove first byte as per Matter specification
|
||||
var info = bytes().fromstring("CompressedFabric") # as per spec, 4.3.2.2 p.99
|
||||
var hk = crypto.HKDF_SHA256()
|
||||
var fabric_rev = fabric.copy().reverse()
|
||||
var fabric_rev = fabric_id.copy().reverse()
|
||||
var k_fabric = hk.derive(root_ca, fabric_rev, info, 8)
|
||||
session.set_fabric_device(fabric, deviceid, k_fabric)
|
||||
session.set_fabric_device(fabric_id, deviceid, k_fabric, self.device.commissioning_admin_fabric)
|
||||
|
||||
# We have a candidate fabric, add it as expirable for 2 minutes
|
||||
session.persist_to_fabric() # fabric object is completed, persist it
|
||||
session.fabric_candidate()
|
||||
|
||||
# move to next step
|
||||
self.device.start_operational_dicovery_deferred(session)
|
||||
self.device.start_operational_discovery_deferred(session)
|
||||
# session.fabric_completed()
|
||||
tasmota.log("MTR: ------------------------------------------", 3)
|
||||
tasmota.log("MTR: fabric=" + matter.inspect(session._fabric), 3)
|
||||
tasmota.log("MTR: ------------------------------------------", 3)
|
||||
session._fabric.log_new_fabric() # log that we registered a new fabric
|
||||
# create NOCResponse
|
||||
# 0=StatusCode
|
||||
# 1=FabricIndex (1-254) (opt)
|
||||
@ -466,27 +516,161 @@ class Matter_Plugin_core : Matter_Plugin
|
||||
elif command == 0x0009 # ---------- UpdateFabricLabel ----------
|
||||
var label = val.findsubval(0) # Label string max 32
|
||||
session.set_fabric_label(label)
|
||||
tasmota.log(string.format("MTR: . Update fabric '%s' label='%s'", session._fabric.get_fabric_id().copy().reverse().tohex(), str(label)), 2)
|
||||
ctx.status = matter.SUCCESS # OK
|
||||
return nil # trigger a standalone ack
|
||||
|
||||
elif command == 0x000A # ---------- RemoveFabric ----------
|
||||
var index = val.findsubval(0) # FabricIndex
|
||||
var sessions_act = self.device.sessions.sessions_active()
|
||||
if index >= 1 && index <= size(sessions_act)
|
||||
var session_deleted = sessions_act[index - 1]
|
||||
tasmota.log("MTR: removing fabric " + session.fabric.copy().reverse().tohex())
|
||||
self.device.sessions.remove_session()
|
||||
self.device.sessions.save()
|
||||
else
|
||||
# TODO return error 11 InvalidFabricIndex
|
||||
|
||||
for fab: self.device.sessions.active_fabrics()
|
||||
if fab.get_fabric_index() == index
|
||||
tasmota.log("MTR: removing fabric " + fab.get_fabric_id().copy().reverse().tohex(), 2)
|
||||
# defer actual removal to send a response
|
||||
tasmota.set_timer(2000, def () self.device.remove_fabric(fab) end)
|
||||
return true # Ok
|
||||
end
|
||||
end
|
||||
ctx.status = matter.SUCCESS # OK
|
||||
tasmota.log("MTR: RemoveFabric fabric("+str(index)+") not found", 2)
|
||||
ctx.status = matter.INVALID_ACTION
|
||||
ctx.log = "fabric_index:"+str(index)
|
||||
return nil # trigger a standalone ack
|
||||
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x003C # ========== Administrator Commissioning Cluster 11.18 p.725 ==========
|
||||
|
||||
if command == 0x0000 # ---------- OpenCommissioningWindow ----------
|
||||
var timeout = val.findsubval(0) # CommissioningTimeout u2
|
||||
var passcode_verifier = val.findsubval(1) # PAKEPasscodeVerifier octstr
|
||||
var discriminator = val.findsubval(2) # Discriminator u2
|
||||
var iterations = val.findsubval(3) # Iterations u4
|
||||
var salt = val.findsubval(4) # Salt octstr
|
||||
|
||||
tasmota.log(string.format("MTR: OpenCommissioningWindow(timeout=%i, passcode=%s, discriminator=%i, iterations=%i, salt=%s)",
|
||||
timeout, passcode_verifier.tohex(), discriminator, iterations, salt.tohex()), 2)
|
||||
|
||||
# check values
|
||||
if timeout == nil || passcode_verifier == nil || discriminator == nil || iterations == nil || salt == nil
|
||||
ctx.status = matter.INVALID_DATA_TYPE
|
||||
return nil # trigger a standalone ack
|
||||
end
|
||||
if size(passcode_verifier) != 32+65 || size(salt) < 16 || size(salt) > 32
|
||||
tasmota.log("MTR: wrong size for PAKE parameters")
|
||||
ctx.status = matter.CONSTRAINT_ERROR
|
||||
return nil # trigger a standalone ack
|
||||
end
|
||||
|
||||
var w0 = passcode_verifier[0..31]
|
||||
var L = passcode_verifier[32..]
|
||||
|
||||
self.device.start_basic_commissioning(timeout, iterations, discriminator, salt, w0, #-w1,-# L, session.get_fabric())
|
||||
# TODO announce in MDNS
|
||||
return true # OK
|
||||
elif command == 0x0001 # ---------- OpenBasicCommissioningWindow ----------
|
||||
var commissioning_timeout = val.findsubval(0) # CommissioningTimeout
|
||||
tasmota.log("MTR: OpenBasicCommissioningWindow commissioning_timeout="+str(commissioning_timeout), 2)
|
||||
self.device.start_root_basic_commissioning(commissioning_timeout)
|
||||
return true
|
||||
elif command == 0x0002 # ---------- RevokeCommissioning ----------
|
||||
# TODO add checks that the commissioning window was opened by the same fabric
|
||||
self.device.stop_basic_commissioning()
|
||||
return true
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x002A # ========== OTA Software Update Requestor Cluster Definition 11.19.7 p.762 ==========
|
||||
|
||||
if command == 0x0000 # ---------- DefaultOTAProviders ----------
|
||||
return true # OK
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).invoke_request(session, val, ctx)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# write an attribute
|
||||
#
|
||||
def write_attribute(session, ctx, write_data)
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var attribute = ctx.attribute
|
||||
|
||||
# 0x001D no writable attributes
|
||||
# 0x0032 no attributes
|
||||
# 0x0033 no writable attributes
|
||||
# 0x0034 no writable attributes
|
||||
# 0x0038 no mandatory writable attributes
|
||||
# 0x003C no writable attributes
|
||||
# 0x003E no writable attributes
|
||||
|
||||
if cluster == 0x0030 # ========== GeneralCommissioning cluster 11.9 p.627 ==========
|
||||
|
||||
if attribute == 0x0000 # ---------- Breadcrumb ----------
|
||||
if type(write_data) == 'int' || isinstance(write_data, int64)
|
||||
session._breadcrumb = write_data
|
||||
self.attribute_updated(ctx.endpoint, ctx.cluster, ctx.attribute) # TODO should we have a more generalized way each time a write_attribute is triggered, declare the attribute as changed?
|
||||
return true
|
||||
else
|
||||
ctx.status = matter.CONSTRAINT_ERROR
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x001F # ========== Access Control Cluster 9.10 p.461 ==========
|
||||
if attribute == 0x0000 # ACL - list[AccessControlEntryStruct]
|
||||
return true
|
||||
end
|
||||
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0028 # ========== Basic Information Cluster cluster 11.1 p.565 ==========
|
||||
|
||||
if attribute == 0x0005 # ---------- NodeLabel / string ----------
|
||||
# TODO
|
||||
return true
|
||||
elif attribute == 0x0006 # ---------- Location / string ----------
|
||||
# TODO
|
||||
return true
|
||||
end
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x002A # ========== OTA Software Update Requestor Cluster Definition 11.19.7 p.762 ==========
|
||||
|
||||
if attribute == 0x0000 # ---------- DefaultOTAProviders / list[ProviderLocationStruct] ----------
|
||||
return true # silently ignore
|
||||
end
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x002B # ========== Localization Configuration Cluster 11.3 p.580 ==========
|
||||
|
||||
if attribute == 0x0000 # ---------- ActiveLocale / string ----------
|
||||
ctx.status = matter.CONSTRAINT_ERROR # changing locale is not possible
|
||||
return false
|
||||
end
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x002C # ========== Time Format Localization Cluster 11.4 p.581 ==========
|
||||
|
||||
if attribute == 0x0000 # ---------- HourFormat / HourFormat ----------
|
||||
# TODO
|
||||
return true
|
||||
elif attribute == 0x0001 # ---------- ActiveCalendarType / CalendarType ----------
|
||||
# TODO
|
||||
return true
|
||||
end
|
||||
# ====================================================================================================
|
||||
elif cluster == 0x0031 # ========== Network Commissioning Cluster cluster 11.8 p.606 ==========
|
||||
if attribute == 0x0004 # ---------- InterfaceEnabled / bool ----------
|
||||
ctx.status = matter.INVALID_ACTION
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
matter.Plugin_core = Matter_Plugin_core
|
||||
matter.Plugin_Root = Matter_Plugin_Root
|
||||
|
@ -0,0 +1,109 @@
|
||||
#
|
||||
# Matter_Plugin_Temp_Sensor.be - implements the behavior for a Temperature Sensor
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Matter plug-in for core behavior
|
||||
|
||||
# dummy declaration for solidification
|
||||
class Matter_Plugin_Device end
|
||||
|
||||
#@ solidify:Matter_Plugin_Temp_Sensor,weak
|
||||
|
||||
class Matter_Plugin_Temp_Sensor : Matter_Plugin_Device
|
||||
static var CLUSTERS = {
|
||||
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
|
||||
# 0x0003: inherited # Identify 1.2 p.16
|
||||
# 0x0004: inherited # Groups 1.3 p.21
|
||||
0x0402: [0,1,2], # Temperature Measurement p.97 - no writable
|
||||
}
|
||||
static var TYPES = { 0x0302: 2 } # Temperature Sensor, rev 2
|
||||
|
||||
var tasmota_sensor_filter # Rule-type filter to the value, like "ESP32#Temperature"
|
||||
var tasmota_sensor_matcher # Actual matcher object
|
||||
var shadow_temperature # fake status for now # TODO
|
||||
|
||||
#############################################################
|
||||
# Constructor
|
||||
def init(device, endpoint, sensor_filter)
|
||||
super(self).init(device, endpoint)
|
||||
self.tasmota_sensor_filter = sensor_filter
|
||||
self.tasmota_sensor_matcher = tasmota.Rule_Matcher.parse(sensor_filter)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# parse sensor
|
||||
#
|
||||
# The device calls regularly `tasmota.read_sensors()` and converts
|
||||
# it to json.
|
||||
def parse_sensors(payload)
|
||||
if self.tasmota_sensor_matcher
|
||||
var val = real(self.tasmota_sensor_matcher.match(payload))
|
||||
if val != nil
|
||||
# import string
|
||||
# tasmota.log(string.format("MTR: update temperature for endpoint %i - %.1f C", self.endpoint,), 3)
|
||||
if val != self.shadow_temperature
|
||||
self.attribute_updated(nil, 0x0402, 0x0000)
|
||||
end
|
||||
self.shadow_temperature = val
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# get_temperature
|
||||
#
|
||||
# Update shadow and signal any change
|
||||
def get_temperature()
|
||||
return self.shadow_temperature
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# read an attribute
|
||||
#
|
||||
def read_attribute(session, ctx)
|
||||
import string
|
||||
var TLV = matter.TLV
|
||||
var cluster = ctx.cluster
|
||||
var attribute = ctx.attribute
|
||||
|
||||
# ====================================================================================================
|
||||
if cluster == 0x0402 # ========== Temperature Measurement 2.3 p.97 ==========
|
||||
if attribute == 0x0000 # ---------- MeasuredValue / i16 (*100) ----------
|
||||
if self.shadow_temperature != nil
|
||||
return TLV.create_TLV(TLV.I2, int(self.shadow_temperature * 100))
|
||||
else
|
||||
return TLV.create_TLV(TLV.NULL, nil)
|
||||
end
|
||||
elif attribute == 0x0001 # ---------- MinMeasuredValue / i16 (*100) ----------
|
||||
return TLV.create_TLV(TLV.I2, -5000) # -50 °C
|
||||
elif attribute == 0x0002 # ---------- MaxMeasuredValue / i16 (*100) ----------
|
||||
return TLV.create_TLV(TLV.I2, 15000) # 150 °C
|
||||
end
|
||||
|
||||
else
|
||||
return super(self).read_attribute(session, ctx)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# every_second
|
||||
def every_second()
|
||||
self.get_temperature() # force reading value and sending subscriptions
|
||||
end
|
||||
end
|
||||
matter.Plugin_Temp_Sensor = Matter_Plugin_Temp_Sensor
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Matter_Session.be - Support for Matter Sessions and Session Store
|
||||
# Matter_Session.be - Support for Matter Sessions
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
@ -20,7 +20,9 @@
|
||||
import matter
|
||||
|
||||
#@ solidify:Matter_Session,weak
|
||||
#@ solidify:Matter_Session_Store,weak
|
||||
|
||||
# for compilation
|
||||
class Matter_Expirable end
|
||||
|
||||
#################################################################################
|
||||
# Matter_Session class
|
||||
@ -29,76 +31,178 @@ import matter
|
||||
# It can also be retrived by `source_node_id` when `local_session_id` is 0
|
||||
#
|
||||
# By convention, names starting with `_` are not persisted
|
||||
# Names starting with `__` are cleared when session is closed (transition from PASE to CASE or CASE finished)
|
||||
#################################################################################
|
||||
class Matter_Session
|
||||
static var __PASE = 1 # PASE authentication in progress
|
||||
static var __CASE = 2 # CASE authentication in progress
|
||||
var __store # reference back to session store
|
||||
class Matter_Session : Matter_Expirable
|
||||
static var _PASE = 1 # PASE authentication in progress
|
||||
static var _CASE = 2 # CASE authentication in progress
|
||||
static var _COUNTER_SND_INCR = 1024 # counter increased when persisting
|
||||
var _store # reference back to session store
|
||||
# mode for Session. Can be PASE=1, CASE=2, Established=10 none=0
|
||||
var mode
|
||||
# link to a fabric object, temporary and in construction for PASE, persistent for CASE
|
||||
var _fabric
|
||||
# sesions
|
||||
var local_session_id # id for the current local session, starts with 1
|
||||
var initiator_session_id # id used to respond to the initiator
|
||||
var session_timestamp # timestamp (UTC) when the session was created
|
||||
var source_node_id # source node if bytes(8) (opt, used only when session is not established)
|
||||
var created # timestamp (UTC) when the session was created
|
||||
var last_used # timestamp (UTC) when the session was last used
|
||||
var _source_node_id # source node if bytes(8) (opt, used only when session is not established)
|
||||
# session_ids when the session will be active
|
||||
var _future_initiator_session_id
|
||||
var _future_local_session_id
|
||||
var __future_initiator_session_id
|
||||
var __future_local_session_id
|
||||
# counters
|
||||
var counter_rcv # counter for incoming messages
|
||||
var counter_snd # counter for outgoing messages
|
||||
var counter_snd # persisted last highest known counter_snd (it is in advance or equal to the actual last used counter_snd)
|
||||
var _counter_rcv_impl # implementation of counter_rcv by matter.Counter()
|
||||
var _counter_snd_impl # implementation of counter_snd by matter.Counter()
|
||||
var _exchange_id # exchange id for locally initiated transaction, non-persistent
|
||||
# keep track of last known IP/Port of the fabric
|
||||
var _ip # IP of the last received packet (string)
|
||||
var _port # port of the last received packet (int)
|
||||
var _message_handler # pointer to the message handler for this session
|
||||
# non-session counters
|
||||
var _counter_insecure_rcv # counter for incoming messages
|
||||
var _counter_insecure_snd # counter for outgoing messages
|
||||
var _counter_insecure_rcv # counter for incoming messages
|
||||
var _counter_insecure_snd # counter for outgoing messages
|
||||
# encryption keys and challenges
|
||||
var i2rkey # key initiator to receiver (incoming)
|
||||
var r2ikey # key receiver to initiator (outgoing)
|
||||
var _i2r_privacy # cache for the i2r privacy key
|
||||
var attestation_challenge # Attestation challenge
|
||||
var attestation_challenge # Attestation challenge
|
||||
var peer_node_id
|
||||
# breadcrumb
|
||||
var breadcrumb # breadcrumb attribute for this session
|
||||
# our own private key
|
||||
var no_private_key # private key of the device certificate (generated at commissioning)
|
||||
# NOC information
|
||||
var root_ca_certificate # root certificate of the initiator
|
||||
var noc # Node Operational Certificate in TLV Matter Certificate
|
||||
var icac # Initiator CA Certificate in TLV Matter Certificate
|
||||
var ipk_epoch_key # timestamp
|
||||
var _breadcrumb # breadcrumb attribute for this session, prefix `_` so that it is not persisted and untouched
|
||||
# CASE
|
||||
var resumption_id # bytes(16)
|
||||
var shared_secret # ECDH shared secret used in CASE
|
||||
# Information extracted from `noc`
|
||||
var fabric # fabric identifier as bytes(8) little endian
|
||||
var fabric_compressed # comrpessed fabric identifier, hashed with root_ca public key
|
||||
var deviceid # our own device id bytes(8) little endian
|
||||
var fabric_label # set by UpdateFabricLabel
|
||||
# Admin info extracted from NOC/ICAC
|
||||
var admin_subject
|
||||
var admin_vendor
|
||||
var shared_secret # ECDH shared secret used in CASE
|
||||
var __responder_priv, __responder_pub
|
||||
var __initiator_pub
|
||||
# PASE
|
||||
var __spake_cA # crypto.SPAKE2P_Matter object, cA
|
||||
var __spake_Ke # crypto.SPAKE2P_Matter object, Ke
|
||||
# Previous CASE messages for Transcript hash
|
||||
var _Msg1, _Msg2
|
||||
# Expiration
|
||||
var _persist # do we persist this sessions or is it remporary
|
||||
var expiration # if not `nil` the entry is removed after this timestamp
|
||||
var __Msg1, __Msg2
|
||||
|
||||
# below are placeholders for ongoing transactions or chunked responses
|
||||
var _chunked_attr_reports # if not `nil` holds a container for the current _chuked_attr_reports
|
||||
var __chunked_attr_reports # if not `nil` holds a container for the current _chuked_attr_reports
|
||||
|
||||
# Group Key Derivation
|
||||
static var __GROUP_KEY = "GroupKey v1.0" # starting with double `_` means it's not writable
|
||||
static var _GROUP_KEY = "GroupKey v1.0" # starting with double `_` means it's not writable
|
||||
|
||||
#############################################################
|
||||
def init(store, local_session_id, initiator_session_id)
|
||||
self.__store = store
|
||||
def init(store, local_session_id, initiator_session_id, fabric)
|
||||
import crypto
|
||||
self._store = store
|
||||
self.mode = 0
|
||||
self.local_session_id = local_session_id
|
||||
self.initiator_session_id = initiator_session_id
|
||||
self.counter_rcv = matter.Counter()
|
||||
self.counter_snd = matter.Counter()
|
||||
# self.counter_rcv = matter.Counter()
|
||||
# self.counter_snd = matter.Counter()
|
||||
self._counter_snd_impl = matter.Counter()
|
||||
self._counter_rcv_impl = matter.Counter()
|
||||
self.counter_rcv = 0 # avoid nil values
|
||||
self.counter_snd = self._counter_snd_impl.next() + self._COUNTER_SND_INCR
|
||||
#
|
||||
self._counter_insecure_rcv = matter.Counter()
|
||||
self._counter_insecure_snd = matter.Counter()
|
||||
self.breadcrumb = int64()
|
||||
self._breadcrumb = 0
|
||||
self._exchange_id = crypto.random(2).get(0,2) # generate a random 16 bits number, then increment with rollover
|
||||
|
||||
self._fabric = fabric ? fabric : self._store.create_fabric()
|
||||
self.update()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Called before removal
|
||||
def before_remove()
|
||||
import string
|
||||
tasmota.log(string.format("MTR: -Session (%6i) (removed)", self.local_session_id), 3)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Management of security counters
|
||||
#############################################################
|
||||
# Provide the next counter value, and update the last know persisted if needed
|
||||
#
|
||||
def counter_snd_next()
|
||||
import string
|
||||
var next = self._counter_snd_impl.next()
|
||||
tasmota.log(string.format("MTR: . Counter_snd=%i", next), 3)
|
||||
# print(">>> NEXT counter_snd=", self.counter_snd, "_impl=", self._counter_snd_impl.val(), 4)
|
||||
if matter.Counter.is_greater(next, self.counter_snd)
|
||||
self.counter_snd = next + self._COUNTER_SND_INCR
|
||||
if self.does_persist()
|
||||
# the persisted counter is behind the actual counter
|
||||
self.save()
|
||||
end
|
||||
end
|
||||
return next
|
||||
end
|
||||
# #############################################################
|
||||
# # Before savind
|
||||
# def persist_pre()
|
||||
# end
|
||||
|
||||
#############################################################
|
||||
# When hydrating from persistance, update counters
|
||||
def hydrate_post()
|
||||
# reset counter_snd to highest known.
|
||||
# We advance it only in case it is actually used
|
||||
# This avoids updaing counters on dead sessions
|
||||
self._counter_snd_impl.reset(self.counter_snd)
|
||||
self._counter_rcv_impl.reset(self.counter_rcv)
|
||||
self.counter_snd = self._counter_snd_impl.val()
|
||||
self.counter_rcv = self._counter_rcv_impl.val()
|
||||
end
|
||||
#############################################################
|
||||
# Validate received counter
|
||||
def counter_rcv_validate(v, t)
|
||||
var ret = self._counter_rcv_impl.validate(v, t)
|
||||
if ret self.counter_rcv = self._counter_rcv_impl.val() end # update the validated counter
|
||||
return ret
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Update the timestamp or any other information
|
||||
def update()
|
||||
self.last_used = tasmota.rtc()['utc']
|
||||
end
|
||||
|
||||
def set_mode_PASE() self.set_mode(self._PASE) end
|
||||
def set_mode_CASE() self.set_mode(self._CASE) end
|
||||
def is_PASE() return self.mode == self._PASE end
|
||||
def is_CASE() return self.mode == self._CASE end
|
||||
|
||||
#############################################################
|
||||
# Assign a new fabric index
|
||||
def assign_fabric_index()
|
||||
if (self._fabric.get_fabric_index() == nil)
|
||||
self._fabric.set_fabric_index(self._store.next_fabric_idx())
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Register the fabric as complete (end of commissioning)
|
||||
def fabric_completed()
|
||||
self._fabric.set_no_expiration()
|
||||
self._fabric.set_persist(true)
|
||||
self.assign_fabric_index()
|
||||
self._store.add_fabric(self._fabric)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Register the frabric as complete (end of commissioning)
|
||||
def fabric_candidate()
|
||||
self._fabric.set_expire_in_seconds(120) # expire in 2 minutes
|
||||
self.assign_fabric_index()
|
||||
self._store.add_fabric(self._fabric)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Persist to fabric
|
||||
# Add self session to the persisted established CASE session of the fabric
|
||||
def persist_to_fabric()
|
||||
self._fabric.add_session(self)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
@ -106,28 +210,24 @@ class Matter_Session
|
||||
#
|
||||
def close()
|
||||
# close the PASE session, it will be re-opened with a CASE session
|
||||
var persist_save = self._persist
|
||||
self.local_session_id = self._future_local_session_id
|
||||
self.initiator_session_id = self._future_initiator_session_id
|
||||
self.source_node_id = nil
|
||||
self.counter_rcv.reset()
|
||||
self.counter_snd.reset()
|
||||
self.local_session_id = self.__future_local_session_id
|
||||
self.initiator_session_id = self.__future_initiator_session_id
|
||||
self._counter_rcv_impl.reset()
|
||||
self._counter_snd_impl.reset()
|
||||
self.counter_rcv = 0
|
||||
self.counter_snd = self._counter_snd_impl.next()
|
||||
self.i2rkey = nil
|
||||
self._i2r_privacy = nil
|
||||
self.r2ikey = nil
|
||||
self.attestation_challenge = nil
|
||||
self.fabric_label = ""
|
||||
# clear any attribute starting with `_`
|
||||
# clear any attribute starting with `__`
|
||||
import introspect
|
||||
for k : introspect.members(self)
|
||||
var v = introspect.get(self, k)
|
||||
if type(v) != 'function' && type(v) != 'instance' && k[0] == '_' && k[1] != '_'
|
||||
if type(v) != 'function' && type(v) != 'instance' && k[0] == '_' && k[1] == '_'
|
||||
self.(k) = nil
|
||||
end
|
||||
end
|
||||
self._persist = persist_save
|
||||
# self._future_initiator_session_id = nil
|
||||
# self._future_local_session_id = nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
@ -139,30 +239,32 @@ class Matter_Session
|
||||
self._i2r_privacy = nil # clear cache
|
||||
self.r2ikey = r2i
|
||||
self.attestation_challenge = ac
|
||||
self.session_timestamp = st
|
||||
self.created = st
|
||||
end
|
||||
def set_ca(ca)
|
||||
self.root_ca_certificate = ca
|
||||
self._fabric.root_ca_certificate = ca
|
||||
end
|
||||
def set_noc(noc, icac)
|
||||
self.noc = noc
|
||||
self.icac = icac
|
||||
self._fabric.noc = noc
|
||||
self._fabric.icac = icac
|
||||
end
|
||||
def set_ipk_epoch_key(ipk_epoch_key)
|
||||
self.ipk_epoch_key = ipk_epoch_key
|
||||
self._fabric.ipk_epoch_key = ipk_epoch_key
|
||||
end
|
||||
def set_fabric_device(fabric, deviceid, fc)
|
||||
self.fabric = fabric
|
||||
self.deviceid = deviceid
|
||||
self.fabric_compressed = fc
|
||||
self.__store.remove_redundant_session(self)
|
||||
def set_admin_subject_vendor(admin_subject, admin_vendor)
|
||||
self._fabric.admin_subject = admin_subject
|
||||
self._fabric.admin_vendor = admin_vendor
|
||||
end
|
||||
def set_persist(p)
|
||||
self._persist = bool(p)
|
||||
|
||||
def set_fabric_device(fabric_id, device_id, fc, fabric_parent)
|
||||
self._fabric.fabric_id = fabric_id
|
||||
self._fabric.device_id = device_id
|
||||
self._fabric.fabric_compressed = fc
|
||||
self._fabric.fabric_parent = (fabric_parent != nil) ? fabric_parent.get_fabric_index() : nil
|
||||
end
|
||||
def set_fabric_label(s)
|
||||
if type(s) == 'string'
|
||||
self.fabric_label = s
|
||||
self._fabric.fabric_label = s
|
||||
end
|
||||
end
|
||||
|
||||
@ -188,75 +290,45 @@ class Matter_Session
|
||||
return self.attestation_challenge
|
||||
end
|
||||
def get_ca()
|
||||
return self.root_ca_certificate
|
||||
return self._fabric.root_ca_certificate
|
||||
end
|
||||
def get_ca_pub()
|
||||
if self.root_ca_certificate
|
||||
var m = matter.TLV.parse(self.root_ca_certificate)
|
||||
return m.findsubval(9)
|
||||
end
|
||||
return self._fabric.get_ca_pub()
|
||||
end
|
||||
def get_noc() return self.noc end
|
||||
def get_icac() return self.icac end
|
||||
def get_ipk_epoch_key() return self.ipk_epoch_key end
|
||||
def get_fabric() return self.fabric end
|
||||
def get_deviceid() return self.deviceid end
|
||||
def get_fabric_compressed() return self.fabric_compressed end
|
||||
def get_fabric() return self._fabric end
|
||||
def get_noc() return self._fabric.noc end
|
||||
def get_icac() return self._fabric.icac end
|
||||
def get_ipk_epoch_key() return self._fabric.ipk_epoch_key end
|
||||
def get_fabric_id() return self._fabric.fabric_id end
|
||||
def get_device_id() return self._fabric.device_id end
|
||||
def get_fabric_compressed() return self._fabric.fabric_compressed end
|
||||
def get_fabric_label() return self._fabric.fabric_label end
|
||||
def get_admin_subject() return self._fabric.admin_subject end
|
||||
def get_admin_vendor() return self._fabric.admin_vendor end
|
||||
|
||||
#############################################################
|
||||
# Generate a private key (or retrieve it)
|
||||
def get_pk()
|
||||
if !self.no_private_key
|
||||
if !self._fabric.no_private_key
|
||||
import crypto
|
||||
self.no_private_key = crypto.random(32)
|
||||
self._fabric.no_private_key = crypto.random(32)
|
||||
end
|
||||
return self.no_private_key
|
||||
return self._fabric.no_private_key
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Operational Group Key Derivation, 4.15.2, p.182
|
||||
def get_ipk_group_key()
|
||||
if self.ipk_epoch_key == nil || self.fabric_compressed == nil return nil end
|
||||
if self.get_ipk_epoch_key() == nil || self.get_fabric_compressed() == nil return nil end
|
||||
import crypto
|
||||
var hk = crypto.HKDF_SHA256()
|
||||
var info = bytes().fromstring(self.__GROUP_KEY)
|
||||
var hash = hk.derive(self.ipk_epoch_key, self.fabric_compressed, info, 16)
|
||||
var info = bytes().fromstring(self._GROUP_KEY)
|
||||
var hash = hk.derive(self.get_ipk_epoch_key(), self.get_fabric_compressed(), info, 16)
|
||||
return hash
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# set absolute time for expiration
|
||||
def set_no_expiration()
|
||||
self.expiration = nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# set absolute time for expiration
|
||||
def set_expire_time(t)
|
||||
self.expiration = int(t)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# set relative time in the future for expiration (in seconds)
|
||||
def set_expire_in_seconds(s, now)
|
||||
if s == nil return end
|
||||
if now == nil now = tasmota.rtc()['utc'] end
|
||||
self.set_expire_time(now + s)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# set relative time in the future for expiration (in seconds)
|
||||
# returns `true` if expiration date has been reached
|
||||
def has_expired(now)
|
||||
if now == nil now = tasmota.rtc()['utc'] end
|
||||
if self.expiration != nil
|
||||
return now >= self.expiration
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# to_json()
|
||||
# Session::to_json()
|
||||
#
|
||||
# convert a single entry as json
|
||||
# returns a JSON string
|
||||
@ -266,6 +338,7 @@ class Matter_Session
|
||||
import string
|
||||
import introspect
|
||||
|
||||
self.persist_pre()
|
||||
var keys = []
|
||||
for k : introspect.members(self)
|
||||
var v = introspect.get(self, k)
|
||||
@ -278,57 +351,50 @@ class Matter_Session
|
||||
var v = introspect.get(self, k)
|
||||
if v == nil continue end
|
||||
|
||||
if k == "counter_rcv" v = v.val()
|
||||
elif k == "counter_snd" v = v.val() + 256 # take a margin to avoid reusing the same counter
|
||||
if isinstance(v, bytes) v = "$$" + v.tob64() # bytes
|
||||
elif type(v) == 'instance' continue # skip any other instance
|
||||
end
|
||||
|
||||
if isinstance(v, bytes) v = "$$" + v.tob64() end # bytes
|
||||
# if isinstance(v, bytes) v = "0x" + v.tohex() end
|
||||
|
||||
# if type(v) == 'string' v = string.escape(v, true) end
|
||||
r.push(string.format("%s:%s", json.dump(str(k)), json.dump(v)))
|
||||
end
|
||||
self.persist_post()
|
||||
return "{" + r.concat(",") + "}"
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# fromjson()
|
||||
# Session::fromjson()
|
||||
#
|
||||
# reads a map and load arguments
|
||||
# returns an new instance of session
|
||||
#############################################################
|
||||
static def fromjson(store, values)
|
||||
static def fromjson(store, values, fabric)
|
||||
import string
|
||||
import introspect
|
||||
var self = matter.Session(store)
|
||||
var self = matter.Session(store, nil, nil, fabric)
|
||||
|
||||
for k:values.keys()
|
||||
var v = values[k]
|
||||
if k == "counter_rcv" self.counter_rcv.reset(int(v))
|
||||
elif k == "counter_snd" self.counter_snd.reset(int(v))
|
||||
else
|
||||
# standard values
|
||||
if type(v) == 'string'
|
||||
if string.find(v, "0x") == 0 # treat as bytes
|
||||
introspect.set(self, k, bytes().fromhex(v[2..]))
|
||||
elif string.find(v, "$$") == 0 # treat as bytes
|
||||
introspect.set(self, k, bytes().fromb64(v[2..]))
|
||||
else
|
||||
introspect.set(self, k, v)
|
||||
end
|
||||
# standard values
|
||||
if type(v) == 'string'
|
||||
if string.find(v, "0x") == 0 # treat as bytes
|
||||
introspect.set(self, k, bytes().fromhex(v[2..]))
|
||||
elif string.find(v, "$$") == 0 # treat as bytes
|
||||
introspect.set(self, k, bytes().fromb64(v[2..]))
|
||||
else
|
||||
introspect.set(self, k, v)
|
||||
end
|
||||
else
|
||||
introspect.set(self, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
self.hydrate_post()
|
||||
return self
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Callback to Session store
|
||||
def save()
|
||||
self.__store.save()
|
||||
self._store.save_fabrics()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
@ -377,234 +443,6 @@ end
|
||||
matter.Session = Matter_Session
|
||||
|
||||
|
||||
#################################################################################
|
||||
# Matter_Session_Store class
|
||||
#################################################################################
|
||||
class Matter_Session_Store
|
||||
var sessions
|
||||
static var FILENAME = "_matter_sessions.json"
|
||||
|
||||
#############################################################
|
||||
def init()
|
||||
self.sessions = []
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# add session
|
||||
def create_session(local_session_id, initiator_session_id)
|
||||
var session = self.get_session_by_local_session_id(local_session_id)
|
||||
if session != nil self.remove_session(session) end
|
||||
session = matter.Session(self, local_session_id, initiator_session_id)
|
||||
self.sessions.push(session)
|
||||
return session
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# add session
|
||||
def add_session(s, expires_in_seconds)
|
||||
if expires_in_seconds != nil
|
||||
s.set_expire_in_seconds(expires_in_seconds)
|
||||
end
|
||||
self.sessions.push(s)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def get_session_by_local_session_id(id)
|
||||
if id == nil return nil end
|
||||
var sz = size(self.sessions)
|
||||
var i = 0
|
||||
var sessions = self.sessions
|
||||
while i < sz
|
||||
if sessions[i].local_session_id == id return sessions[i] end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def get_session_by_source_node_id(nodeid)
|
||||
if nodeid == nil return nil end
|
||||
var sz = size(self.sessions)
|
||||
var i = 0
|
||||
var sessions = self.sessions
|
||||
while i < sz
|
||||
if sessions[i].source_node_id == nodeid return sessions[i] end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Remove session by reference
|
||||
#
|
||||
def remove_session(s)
|
||||
var i = 0
|
||||
var sessions = self.sessions
|
||||
while i < size(self.sessions)
|
||||
if sessions[i] == s
|
||||
sessions.remove(i)
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Remove session by reference
|
||||
#
|
||||
# remove all other sessions that have the same:
|
||||
# fabric / deviceid / fc
|
||||
def remove_redundant_session(s)
|
||||
var i = 0
|
||||
var sessions = self.sessions
|
||||
while i < size(self.sessions)
|
||||
var session = sessions[i]
|
||||
if session != s && session.fabric == s.fabric && session.deviceid == s.deviceid #- && session.fabric_compressed == s.fabric_compressed -#
|
||||
sessions.remove(i)
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Generate a new local_session_id
|
||||
def gen_local_session_id()
|
||||
import crypto
|
||||
while true
|
||||
var candidate_local_session_id = crypto.random(2).get(0, 2)
|
||||
|
||||
if self.get_session_by_local_session_id(candidate_local_session_id) == nil
|
||||
return candidate_local_session_id
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# remove_expired
|
||||
#
|
||||
# Check is any session has expired
|
||||
def remove_expired()
|
||||
var dirty = false
|
||||
var i = 0
|
||||
var sessions = self.sessions
|
||||
while i < size(self.sessions)
|
||||
if sessions[i].has_expired()
|
||||
if sessions[i]._persist dirty = true end # do we need to save
|
||||
sessions.remove(i)
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
if dirty self.save() end
|
||||
end
|
||||
def every_second()
|
||||
self.remove_expired()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# find or create a session for unencrypted traffic
|
||||
# expires in `expire` seconds
|
||||
def find_session_source_id_unsecure(source_node_id, expire)
|
||||
var session = self.get_session_by_source_node_id(source_node_id)
|
||||
if session == nil
|
||||
session = matter.Session(self, 0, 0)
|
||||
session.source_node_id = source_node_id
|
||||
self.sessions.push(session)
|
||||
end
|
||||
session.set_expire_in_seconds(expire)
|
||||
return session
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# find session by resumption id
|
||||
def find_session_by_resumption_id(resumption_id)
|
||||
if !resumption_id return nil end
|
||||
var i = 0
|
||||
var sessions = self.sessions
|
||||
while i < size(sessions)
|
||||
if sessions[i].resumption_id == resumption_id
|
||||
return sessions[i]
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# list of sessions that are active, i.e. have been
|
||||
# successfully commissioned
|
||||
#
|
||||
def sessions_active()
|
||||
var ret = []
|
||||
var idx = 0
|
||||
while idx < size(self.sessions)
|
||||
var session = self.sessions[idx]
|
||||
if session.get_deviceid() && session.get_fabric()
|
||||
ret.push(session)
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def save()
|
||||
import json
|
||||
self.remove_expired() # clean before saving
|
||||
|
||||
var j = []
|
||||
for v:self.sessions
|
||||
if v._persist
|
||||
j.push(v.tojson())
|
||||
end
|
||||
end
|
||||
var j_size = size(j)
|
||||
j = "[" + j.concat(",") + "]"
|
||||
|
||||
try
|
||||
import string
|
||||
var f = open(self.FILENAME, "w")
|
||||
f.write(j)
|
||||
f.close()
|
||||
tasmota.log(string.format("MTR: Saved %i session(s)", j_size), 2)
|
||||
return j
|
||||
except .. as e, m
|
||||
tasmota.log("MTR: Session_Store::save Exception:" + str(e) + "|" + str(m), 2)
|
||||
return j
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def load()
|
||||
import string
|
||||
try
|
||||
self.sessions = [] # remove any left-over
|
||||
var f = open(self.FILENAME)
|
||||
var s = f.read()
|
||||
f.close()
|
||||
|
||||
import json
|
||||
var j = json.load(s)
|
||||
s = nil
|
||||
tasmota.gc() # clean-up a potential long string
|
||||
|
||||
for v:j # iterate on values
|
||||
var session = matter.Session.fromjson(self, v)
|
||||
if session != nil
|
||||
self.add_session(session)
|
||||
end
|
||||
end
|
||||
|
||||
tasmota.log(string.format("MTR: Loaded %i session(s)", size(self.sessions)), 2)
|
||||
except .. as e, m
|
||||
if e != "io_error"
|
||||
tasmota.log("MTR: Session_Store::load Exception:" + str(e) + "|" + str(m), 2)
|
||||
end
|
||||
end
|
||||
self.remove_expired() # clean after load
|
||||
end
|
||||
end
|
||||
matter.Session_Store = Matter_Session_Store
|
||||
|
||||
#-
|
||||
|
||||
# Unit test
|
||||
|
391
lib/libesp32/berry_matter/src/embedded/Matter_Session_Store.be
Normal file
391
lib/libesp32/berry_matter/src/embedded/Matter_Session_Store.be
Normal file
@ -0,0 +1,391 @@
|
||||
#
|
||||
# Matter_Session_Store.be - Support for Matter Session Store
|
||||
#
|
||||
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import matter
|
||||
|
||||
#@ solidify:Matter_Session_Store,weak
|
||||
|
||||
# for compilation
|
||||
class Matter_Expirable end
|
||||
|
||||
#################################################################################
|
||||
#################################################################################
|
||||
#################################################################################
|
||||
# Matter_Session_Store class
|
||||
#################################################################################
|
||||
#################################################################################
|
||||
#################################################################################
|
||||
class Matter_Session_Store
|
||||
var sessions
|
||||
var fabrics # list of provisioned fabrics
|
||||
static var _FABRICS = "_matter_fabrics.json"
|
||||
|
||||
#############################################################
|
||||
def init()
|
||||
self.sessions = matter.Expirable_list()
|
||||
self.fabrics = matter.Expirable_list()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# add provisioned fabric
|
||||
def add_fabric(fabric)
|
||||
if !isinstance(fabric, matter.Fabric) raise "value_error", "must be of class matter.Fabric" end
|
||||
if self.fabrics.find(fabric) == nil
|
||||
self.remove_redundant_fabric(fabric)
|
||||
self.fabrics.push(fabric)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# remove fabric
|
||||
def remove_fabric(fabric)
|
||||
var idx = 0
|
||||
while idx < size(self.sessions)
|
||||
if self.sessions[idx]._fabric == fabric
|
||||
self.sessions.remove(idx)
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
self.fabrics.remove(self.fabrics.find(fabric)) # fail safe
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Remove redudant fabric
|
||||
#
|
||||
# remove all other fabrics that have the same:
|
||||
# fabric_id / device_id
|
||||
def remove_redundant_fabric(f)
|
||||
var i = 0
|
||||
while i < size(self.fabrics)
|
||||
var fabric = self.fabrics[i]
|
||||
if fabric != f && fabric.fabric_id == f.fabric_id && fabric.device_id == f.device_id
|
||||
self.fabrics.remove(i)
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Returns an iterator on active fabrics
|
||||
def active_fabrics()
|
||||
self.remove_expired() # clean before
|
||||
return self.fabrics.persistables()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Count active fabrics
|
||||
#
|
||||
# Count the number of commissionned fabrics, i.e. persisted
|
||||
def count_active_fabrics()
|
||||
self.remove_expired() # clean before
|
||||
return self.fabrics.count_persistables()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Find fabric by index number
|
||||
#
|
||||
def find_fabric_by_index(fabric_index)
|
||||
for fab : self.active_fabrics()
|
||||
if fab.get_fabric_index() == fabric_index
|
||||
return fab
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Find children fabrics
|
||||
#
|
||||
# Find all children fabrics recursively and collate in array
|
||||
# includes the parent fabric as first element
|
||||
#
|
||||
# Ex:
|
||||
# matter_device.sessions.fabrics[1].fabric_parent = 1
|
||||
# matter_device.sessions.find_children_fabrics(1)
|
||||
#
|
||||
def find_children_fabrics(parent_index)
|
||||
if parent_index == nil return [] end
|
||||
var ret = [ parent_index ]
|
||||
|
||||
def find_children_fabrics_inner(index)
|
||||
for fab: self.active_fabrics()
|
||||
if fab.fabric_parent == index
|
||||
# protect against infinite loops
|
||||
if ret.find() == nil
|
||||
var sub_index = fab.fabric_index
|
||||
ret.push(sub_index)
|
||||
find_children_fabrics_inner(sub_index)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
find_children_fabrics_inner(parent_index)
|
||||
|
||||
# ret contains a list of indices
|
||||
return ret
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Next fabric-idx
|
||||
#
|
||||
# starts at `1`, computes the next available fabric-idx
|
||||
def next_fabric_idx()
|
||||
self.remove_expired() # clean before
|
||||
var next_idx = 1
|
||||
for fab: self.active_fabrics()
|
||||
var fab_idx = fab.fabric_index
|
||||
if type(fab_idx) == 'int' && fab_idx >= next_idx
|
||||
next_idx = fab_idx + 1
|
||||
end
|
||||
end
|
||||
return next_idx
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# add session
|
||||
def create_session(local_session_id, initiator_session_id)
|
||||
var session = self.get_session_by_local_session_id(local_session_id)
|
||||
if session != nil self.remove_session(session) end
|
||||
session = matter.Session(self, local_session_id, initiator_session_id)
|
||||
self.sessions.push(session)
|
||||
return session
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# add session
|
||||
def add_session(s, expires_in_seconds)
|
||||
if expires_in_seconds != nil
|
||||
s.set_expire_in_seconds(expires_in_seconds)
|
||||
end
|
||||
self.sessions.push(s)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def get_session_by_local_session_id(id)
|
||||
if id == nil return nil end
|
||||
var sz = size(self.sessions)
|
||||
var i = 0
|
||||
var sessions = self.sessions
|
||||
while i < sz
|
||||
var session = sessions[i]
|
||||
if session.local_session_id == id
|
||||
session.update()
|
||||
return session
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def get_session_by_source_node_id(nodeid)
|
||||
if nodeid == nil return nil end
|
||||
var sz = size(self.sessions)
|
||||
var i = 0
|
||||
var sessions = self.sessions
|
||||
while i < sz
|
||||
var session = sessions[i]
|
||||
if session._source_node_id == nodeid
|
||||
session.update()
|
||||
return session
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Remove session by reference
|
||||
#
|
||||
def remove_session(s)
|
||||
var i = 0
|
||||
var sessions = self.sessions
|
||||
while i < size(self.sessions)
|
||||
if sessions[i] == s
|
||||
sessions.remove(i)
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Generate a new local_session_id
|
||||
def gen_local_session_id()
|
||||
import crypto
|
||||
while true
|
||||
var candidate_local_session_id = crypto.random(2).get(0, 2)
|
||||
|
||||
if self.get_session_by_local_session_id(candidate_local_session_id) == nil
|
||||
return candidate_local_session_id
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# remove_expired
|
||||
#
|
||||
def remove_expired()
|
||||
self.sessions.every_second()
|
||||
self.fabrics.every_second()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# call remove_expired every second
|
||||
#
|
||||
def every_second()
|
||||
self.remove_expired()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# find or create a session for unencrypted traffic
|
||||
# expires in `expire` seconds
|
||||
def find_session_source_id_unsecure(source_node_id, expire)
|
||||
var session = self.get_session_by_source_node_id(source_node_id)
|
||||
if session == nil
|
||||
session = matter.Session(self, 0, 0)
|
||||
session._source_node_id = source_node_id
|
||||
self.sessions.push(session)
|
||||
session.set_expire_in_seconds(expire)
|
||||
end
|
||||
session.update()
|
||||
return session
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# find session by resumption id
|
||||
def find_session_by_resumption_id(resumption_id)
|
||||
import string
|
||||
if !resumption_id return nil end
|
||||
var i = 0
|
||||
var sessions = self.sessions
|
||||
while i < size(sessions)
|
||||
var session = sessions[i]
|
||||
tasmota.log(string.format("MTR: session.resumption_id=%s vs %s", str(session.resumption_id), str(resumption_id)))
|
||||
if session.resumption_id == resumption_id && session.shared_secret != nil
|
||||
tasmota.log(string.format("MTR: session.shared_secret=%s", str(session.shared_secret)))
|
||||
session.update()
|
||||
return session
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# list of sessions that are active, i.e. have been
|
||||
# successfully commissioned
|
||||
#
|
||||
def sessions_active()
|
||||
var ret = []
|
||||
var idx = 0
|
||||
while idx < size(self.sessions)
|
||||
var session = self.sessions[idx]
|
||||
if session.get_device_id() && session.get_fabric_id()
|
||||
ret.push(session)
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def save_fabrics()
|
||||
import json
|
||||
self.remove_expired() # clean before saving
|
||||
var sessions_saved = 0
|
||||
|
||||
var fabs = []
|
||||
for f : self.fabrics.persistables()
|
||||
for _ : f._sessions.persistables() sessions_saved += 1 end # count persitable sessions
|
||||
fabs.push(f.tojson())
|
||||
end
|
||||
var fabs_size = size(fabs)
|
||||
fabs = "[" + fabs.concat(",") + "]"
|
||||
|
||||
try
|
||||
import string
|
||||
|
||||
var f = open(self._FABRICS, "w")
|
||||
f.write(fabs)
|
||||
f.close()
|
||||
tasmota.log(string.format("MTR: =Saved %i fabric(s) and %i session(s)", fabs_size, sessions_saved), 2)
|
||||
except .. as e, m
|
||||
tasmota.log("MTR: Session_Store::save Exception:" + str(e) + "|" + str(m), 2)
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# load fabrics and associated sessions
|
||||
def load_fabrics()
|
||||
import string
|
||||
try
|
||||
self.sessions = matter.Expirable_list() # remove any left-over
|
||||
self.fabrics = matter.Expirable_list() # remove any left-over
|
||||
var f = open(self._FABRICS)
|
||||
var file_content = f.read()
|
||||
f.close()
|
||||
|
||||
import json
|
||||
var file_json = json.load(file_content)
|
||||
file_content = nil
|
||||
tasmota.gc() # clean-up a potential long string
|
||||
|
||||
for v : file_json # iterate on values
|
||||
# read fabric
|
||||
var fabric = matter.Fabric.fromjson(self, v)
|
||||
fabric.set_no_expiration()
|
||||
fabric.set_persist(true)
|
||||
|
||||
# iterate on sessions
|
||||
var sessions_json = v.find("_sessions", [])
|
||||
|
||||
for sess_json : sessions_json
|
||||
var session = matter.Session.fromjson(self, sess_json, fabric)
|
||||
if session != nil
|
||||
session.set_no_expiration()
|
||||
session.set_persist(true)
|
||||
self.add_session(session)
|
||||
fabric.add_session(session)
|
||||
end
|
||||
end
|
||||
|
||||
self.fabrics.push(fabric)
|
||||
end
|
||||
|
||||
tasmota.log(string.format("MTR: Loaded %i fabric(s)", size(self.fabrics)), 2)
|
||||
except .. as e, m
|
||||
if e != "io_error"
|
||||
tasmota.log("MTR: Session_Store::load Exception:" + str(e) + "|" + str(m), 2)
|
||||
end
|
||||
end
|
||||
# persistables are normally not expiring
|
||||
# if self.remove_expired() # clean after load
|
||||
# self.save_fabrics()
|
||||
# end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def create_fabric()
|
||||
var fabric = matter.Fabric(self)
|
||||
return fabric
|
||||
end
|
||||
end
|
||||
matter.Session_Store = Matter_Session_Store
|
@ -46,6 +46,20 @@ class Matter_TLV
|
||||
]
|
||||
|
||||
# type values (enum like)
|
||||
#
|
||||
# Type|Description
|
||||
# :----|:---
|
||||
# I1 I2 I4|Signed integer of at most (1/2/4) bytes (as 32 bits signed Berry type)
|
||||
# U1 U2 U4|Unsiged integer of at motst (1/2/4) bytes (as 32 bits signed Berry type, be careful when comparing. Use `matter.Counter.is_greater(a,b)`)
|
||||
# I8 U8|Signed/insigned 8 bytes. You can pass `bytes(8)`, `int64()` or `int`. Type is collapsed to a lower type if possible when encoding.
|
||||
# BOOL|boolean, takes `true` and `false`. Abstracts the internal `BTRUE` and `BFALSE` that you don't need to use
|
||||
# FLOAT|32 bites float
|
||||
# UTF1 UTF2|String as UTF, size is encoded as 1 or 2 bytes automatically
|
||||
# B1 B2|raw `bytes()`, size is encoded as 1 or 2 bytes automatically
|
||||
# NULL|takes only `nil` value
|
||||
# STRUCT<BR>ARRAY<BR>LIST<BR>EOC|(internal) Use through abstractions
|
||||
# DOUBLE<BR>UTF4 UTF8<BR>B4 B8|Unsuppored in Tasmota
|
||||
|
||||
static var I1 = 0x00
|
||||
static var I2 = 0x01
|
||||
static var I4 = 0x02
|
||||
@ -107,7 +121,7 @@ class Matter_TLV
|
||||
#############################################################
|
||||
# create simple TLV
|
||||
static def create_TLV(t, value)
|
||||
if value != nil
|
||||
if value != nil || t == 0x14 #-t == matter.TLV.NULL-# # put the actual number for performance
|
||||
var v = _class() # parent is nil
|
||||
v.typ = t
|
||||
v.val = value
|
||||
@ -206,7 +220,7 @@ class Matter_TLV
|
||||
# encode TLV
|
||||
#
|
||||
# appends to the bytes() object
|
||||
def encode(b)
|
||||
def tlv2raw(b)
|
||||
var TLV = self.TLV
|
||||
if b == nil b = bytes() end # start new buffer if none passed
|
||||
|
||||
@ -297,6 +311,83 @@ class Matter_TLV
|
||||
return b
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# compute the length in bytes of encoded TLV without actually
|
||||
# allocating buffers (faster and no memory fragmentation)
|
||||
#
|
||||
# returns a number of bytes
|
||||
def encode_len()
|
||||
var TLV = self.TLV
|
||||
var len = 0
|
||||
|
||||
# special case for bool
|
||||
# we need to change the type according to the value
|
||||
if self.typ == TLV.BFALSE || self.typ == TLV.BTRUE
|
||||
self.typ = bool(self.val) ? TLV.BTRUE : TLV.BFALSE
|
||||
# try to compress ints
|
||||
elif self.typ >= TLV.I2 && self.typ <= TLV.I4
|
||||
var i = int(self.val)
|
||||
if i <= 127 && i >= -128 self.typ = TLV.I1
|
||||
elif i <= 32767 && i >= -32768 self.typ = TLV.I2
|
||||
end
|
||||
elif self.typ >= TLV.U2 && self.typ <= TLV.U4
|
||||
var i = int(self.val)
|
||||
if i <= 255 && i >= 0 self.typ = TLV.U1
|
||||
elif i <= 65535 && i >= 0 self.typ = TLV.U2
|
||||
end
|
||||
elif self.typ >= TLV.B1 && self.typ <= TLV.B8 # encode length as minimum possible
|
||||
if size(self.val) <= 255
|
||||
self.typ = TLV.B1
|
||||
elif size(self.val) <= 65535
|
||||
self.typ = TLV.B2
|
||||
else
|
||||
self.typ = TLV.B4 # B4 is unlikely, B8 is impossible
|
||||
end
|
||||
elif self.typ >= TLV.UTF1 && self.typ <= TLV.UTF8
|
||||
if size(self.val) <= 255
|
||||
self.typ = TLV.UTF1
|
||||
elif size(self.val) <= 65535
|
||||
self.typ = TLV.UTF2
|
||||
else
|
||||
self.typ = TLV.UTF4 # UTF4 is unlikely, UTF8 is impossible
|
||||
end
|
||||
end
|
||||
|
||||
# encode tag and type
|
||||
len += self._encode_tag_len()
|
||||
# encode value
|
||||
|
||||
if self.typ == TLV.I1 || self.typ == TLV.U1
|
||||
len += 1
|
||||
elif self.typ == TLV.I2 || self.typ == TLV.U2
|
||||
len += 2
|
||||
elif self.typ == TLV.I4 || self.typ == TLV.U4
|
||||
len += 4
|
||||
elif self.typ == TLV.I8 || self.typ == TLV.U8
|
||||
len += 8
|
||||
elif self.typ == TLV.BFALSE || self.typ == TLV.BTRUE
|
||||
# push nothing
|
||||
elif self.typ == TLV.FLOAT
|
||||
len += 4
|
||||
elif self.typ == TLV.DOUBLE
|
||||
raise "value_error", "Unsupported type TLV.DOUBLE"
|
||||
elif self.typ == TLV.UTF1
|
||||
len += 1 + size(self.val)
|
||||
elif self.typ == TLV.UTF2
|
||||
len += 2 + size(self.val)
|
||||
elif self.typ == TLV.B1
|
||||
len += 1 + size(self.val)
|
||||
elif self.typ == TLV.B2
|
||||
len += 2 + size(self.val)
|
||||
elif self.typ == TLV.NULL
|
||||
# push nothing
|
||||
else
|
||||
raise "value_error", "unsupported type " + str(self.typ)
|
||||
end
|
||||
|
||||
return len
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# internal_function
|
||||
# encode Tag+Type as the first bytes
|
||||
@ -341,6 +432,39 @@ class Matter_TLV
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# internal_function
|
||||
# compute len of Tag+Type as the first bytes
|
||||
def _encode_tag_len()
|
||||
var tag_number = self.tag_number != nil ? self.tag_number : 0
|
||||
var tag_huge = (tag_number >= 65536) || (tag_number < 0)
|
||||
var tag_control = 0x00
|
||||
if self.tag_vendor != nil
|
||||
# full encoding
|
||||
if tag_huge
|
||||
return 9
|
||||
else
|
||||
return 7
|
||||
end
|
||||
elif self.tag_profile == -1 # Matter Common profile
|
||||
if tag_huge
|
||||
return 5
|
||||
else
|
||||
return 3
|
||||
end
|
||||
elif self.tag_profile != nil
|
||||
if tag_huge
|
||||
return 5
|
||||
else
|
||||
return 3
|
||||
end
|
||||
elif self.tag_sub != nil
|
||||
return 2
|
||||
else # anonymous tag
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Compare the value index with an element
|
||||
# returns:
|
||||
@ -494,22 +618,21 @@ class Matter_TLV
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# encode TLV
|
||||
#
|
||||
# appends to the bytes() object
|
||||
def _encode_inner(b, is_struct)
|
||||
# encode to bytes
|
||||
def tlv2raw(b)
|
||||
if b == nil b = bytes() end
|
||||
# encode tag and type
|
||||
self._encode_tag(b)
|
||||
# sort values
|
||||
var val_list = self.val.copy()
|
||||
if is_struct
|
||||
var val_list = self.val
|
||||
if self.is_struct
|
||||
val_list = val_list.copy()
|
||||
self.sort(val_list)
|
||||
end
|
||||
|
||||
# output each one after the other
|
||||
for v : val_list
|
||||
v.encode(b)
|
||||
v.tlv2raw(b)
|
||||
end
|
||||
|
||||
# add 'end of container'
|
||||
@ -519,9 +642,22 @@ class Matter_TLV
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# encode to bytes
|
||||
def encode(b)
|
||||
return self._encode_inner(b, self.is_struct)
|
||||
# compute the length in bytes of encoded TLV without actually
|
||||
# allocating buffers (faster and no memory fragmentation)
|
||||
#
|
||||
# returns a number of bytes
|
||||
def encode_len()
|
||||
# tag and type
|
||||
var len = self._encode_tag_len()
|
||||
# output each one after the other, order doesn't infulence size
|
||||
var idx = 0
|
||||
while idx < size(self.val)
|
||||
len += self.val[idx].encode_len()
|
||||
idx += 1
|
||||
end
|
||||
# add 'end of container'
|
||||
len += 1
|
||||
return len
|
||||
end
|
||||
|
||||
#############################################################
|
||||
@ -760,117 +896,52 @@ matter.TLV = Matter_TLV
|
||||
# Test
|
||||
import matter
|
||||
|
||||
#load("Matter_TLV.be")
|
||||
def test_TLV(b, s)
|
||||
var m = matter.TLV.parse(b)
|
||||
assert(m.tostring() == s)
|
||||
assert(m.tlv2raw() == b)
|
||||
assert(m.encode_len() == size(b))
|
||||
end
|
||||
|
||||
var m
|
||||
m = matter.TLV.parse(bytes("2502054C"))
|
||||
assert(m.tostring() == "2 = 19461U")
|
||||
assert(m.encode() == bytes("2502054C"))
|
||||
test_TLV(bytes("2502054C"), "2 = 19461U")
|
||||
test_TLV(bytes("0001"), "1")
|
||||
test_TLV(bytes("08"), "false")
|
||||
test_TLV(bytes("09"), "true")
|
||||
|
||||
m = matter.TLV.parse(bytes("0001"))
|
||||
assert(m.tostring() == "1")
|
||||
assert(m.encode() == bytes("0001"))
|
||||
test_TLV(bytes("00FF"), "-1")
|
||||
test_TLV(bytes("05FFFF"), "65535U")
|
||||
|
||||
m = matter.TLV.parse(bytes("08"))
|
||||
assert(m.tostring() == "false")
|
||||
assert(m.encode() == bytes("08"))
|
||||
m = matter.TLV.parse(bytes("09"))
|
||||
assert(m.tostring() == "true")
|
||||
assert(m.encode() == bytes("09"))
|
||||
|
||||
m = matter.TLV.parse(bytes("01FFFF"))
|
||||
assert(m.tostring() == "-1")
|
||||
assert(m.encode() == bytes("00FF"))
|
||||
m = matter.TLV.parse(bytes("05FFFF"))
|
||||
assert(m.tostring() == "65535U")
|
||||
assert(m.encode() == bytes("05FFFF"))
|
||||
|
||||
m = matter.TLV.parse(bytes("0A0000C03F"))
|
||||
assert(m.tostring() == "1.5")
|
||||
assert(m.encode() == bytes("0A0000C03F"))
|
||||
|
||||
m = matter.TLV.parse(bytes("0C06466f6f626172"))
|
||||
assert(m.tostring() == '"Foobar"')
|
||||
assert(m.encode() == bytes("0C06466f6f626172"))
|
||||
|
||||
m = matter.TLV.parse(bytes("1006466f6f626172"))
|
||||
assert(m.tostring() == "466F6F626172")
|
||||
assert(m.encode() == bytes("1006466f6f626172"))
|
||||
|
||||
m = matter.TLV.parse(bytes("e4f1ffeddeedfe55aa2a"))
|
||||
assert(m.tostring() == "0xFFF1::0xDEED:0xAA55FEED = 42U")
|
||||
assert(m.encode() == bytes("e4f1ffeddeedfe55aa2a"))
|
||||
|
||||
|
||||
m = matter.TLV.parse(bytes("300120D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D66"))
|
||||
assert(m.tostring() == "1 = D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D66")
|
||||
assert(m.encode() == bytes("300120D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D66"))
|
||||
test_TLV(bytes("0A0000C03F"), "1.5")
|
||||
test_TLV(bytes("0C06466f6f626172"), '"Foobar"')
|
||||
test_TLV(bytes("1006466f6f626172"), "466F6F626172")
|
||||
test_TLV(bytes("e4f1ffeddeedfe55aa2a"), "0xFFF1::0xDEED:0xAA55FEED = 42U")
|
||||
test_TLV(bytes("300120D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D66"), "1 = D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D66")
|
||||
|
||||
# context specific
|
||||
m = matter.TLV.parse(bytes("24012a"))
|
||||
assert(m.tostring() == "1 = 42U")
|
||||
assert(m.encode() == bytes("24012a"))
|
||||
|
||||
m = matter.TLV.parse(bytes("4401002a"))
|
||||
assert(m.tostring() == "Matter::0x00000001 = 42U")
|
||||
assert(m.encode() == bytes("4401002a"))
|
||||
test_TLV(bytes("24012a"), "1 = 42U")
|
||||
test_TLV(bytes("4401002a"), "Matter::0x00000001 = 42U")
|
||||
|
||||
# int64
|
||||
m = matter.TLV.parse(bytes("030102000000000000"))
|
||||
assert(m.tostring() == "513")
|
||||
assert(m.encode() == bytes("030102000000000000"))
|
||||
|
||||
m = matter.TLV.parse(bytes("070102000000000000"))
|
||||
assert(m.tostring() == "513U")
|
||||
assert(m.encode() == bytes("070102000000000000"))
|
||||
|
||||
m = matter.TLV.parse(bytes("03FFFFFFFFFFFFFFFF"))
|
||||
assert(m.tostring() == "-1")
|
||||
assert(m.encode() == bytes("03FFFFFFFFFFFFFFFF"))
|
||||
|
||||
m = matter.TLV.parse(bytes("07FFFFFFFFFFFFFF7F"))
|
||||
assert(m.tostring() == "9223372036854775807U")
|
||||
assert(m.encode() == bytes("07FFFFFFFFFFFFFF7F"))
|
||||
test_TLV(bytes("030102000000000000"), "513")
|
||||
test_TLV(bytes("070102000000000000"), "513U")
|
||||
test_TLV(bytes("03FFFFFFFFFFFFFFFF"), "-1")
|
||||
test_TLV(bytes("07FFFFFFFFFFFFFF7F"), "9223372036854775807U")
|
||||
|
||||
# structure
|
||||
m = matter.TLV.parse(bytes("1518"))
|
||||
assert(m.tostring() == "{}")
|
||||
assert(m.encode() == bytes("1518"))
|
||||
|
||||
m = matter.TLV.parse(bytes("15300120D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D662502054C240300280418"))
|
||||
assert(m.tostring() == "{1 = D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D66, 2 = 19461U, 3 = 0U, 4 = false}")
|
||||
assert(m.encode() == bytes("15300120D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D662502054C240300280418"))
|
||||
|
||||
m = matter.TLV.parse(bytes("15300120D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D662502054C240300280435052501881325022C011818"))
|
||||
assert(m.tostring() == "{1 = D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D66, 2 = 19461U, 3 = 0U, 4 = false, 5 = {1 = 5000U, 2 = 300U}}")
|
||||
assert(m.encode() == bytes("15300120D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D662502054C240300280435052501881325022C011818"))
|
||||
test_TLV(bytes("1518"), "{}")
|
||||
test_TLV(bytes("15300120D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D662502054C240300280418"), "{1 = D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D66, 2 = 19461U, 3 = 0U, 4 = false}")
|
||||
test_TLV(bytes("15300120D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D662502054C240300280435052501881325022C011818"), "{1 = D2DAEE8760C9B1D1B25E0E2E4DD6ECA8AEF6193C0203761356FCB06BBEDD7D66, 2 = 19461U, 3 = 0U, 4 = false, 5 = {1 = 5000U, 2 = 300U}}")
|
||||
|
||||
# list
|
||||
m = matter.TLV.parse(bytes("1718"))
|
||||
assert(m.tostring() == "[[]]")
|
||||
assert(m.encode() == bytes("1718"))
|
||||
|
||||
m = matter.TLV.parse(bytes("17000120002a000200032000ef18"))
|
||||
assert(m.tostring() == "[[1, 0 = 42, 2, 3, 0 = -17]]")
|
||||
assert(m.encode() == bytes("17000120002a000200032000ef18"))
|
||||
|
||||
test_TLV(bytes("1718"), "[[]]")
|
||||
test_TLV(bytes("17000120002a000200032000ef18"), "[[1, 0 = 42, 2, 3, 0 = -17]]")
|
||||
|
||||
# array
|
||||
m = matter.TLV.parse(bytes("1618"))
|
||||
assert(m.tostring() == "[]")
|
||||
assert(m.encode() == bytes("1618"))
|
||||
|
||||
m = matter.TLV.parse(bytes("160000000100020003000418"))
|
||||
assert(m.tostring() == "[0, 1, 2, 3, 4]")
|
||||
assert(m.encode() == bytes("160000000100020003000418"))
|
||||
test_TLV(bytes("1618"), "[]")
|
||||
test_TLV(bytes("160000000100020003000418"), "[0, 1, 2, 3, 4]")
|
||||
|
||||
# mix
|
||||
m = matter.TLV.parse(bytes("16002a02f067fdff15180a33338f410c0648656c6c6f2118"))
|
||||
assert(m.tostring() == '[42, -170000, {}, 17.9, "Hello!"]')
|
||||
assert(m.encode() == bytes("16002a02f067fdff15180a33338f410c0648656c6c6f2118"))
|
||||
|
||||
m = matter.TLV.parse(bytes("153600172403312504FCFF18172402002403302404001817240200240330240401181724020024033024040218172402002403302404031817240200240328240402181724020024032824040418172403312404031818280324FF0118"))
|
||||
assert(m.tostring() == '{0 = [[[3 = 49U, 4 = 65532U]], [[2 = 0U, 3 = 48U, 4 = 0U]], [[2 = 0U, 3 = 48U, 4 = 1U]], [[2 = 0U, 3 = 48U, 4 = 2U]], [[2 = 0U, 3 = 48U, 4 = 3U]], [[2 = 0U, 3 = 40U, 4 = 2U]], [[2 = 0U, 3 = 40U, 4 = 4U]], [[3 = 49U, 4 = 3U]]], 3 = false, 255 = 1U}')
|
||||
assert(m.encode() == bytes("153600172403312504FCFF18172402002403302404001817240200240330240401181724020024033024040218172402002403302404031817240200240328240402181724020024032824040418172403312404031818280324FF0118"))
|
||||
test_TLV(bytes("16002a02f067fdff15180a33338f410c0648656c6c6f2118"), '[42, -170000, {}, 17.9, "Hello!"]')
|
||||
test_TLV(bytes("153600172403312504FCFF18172402002403302404001817240200240330240401181724020024033024040218172402002403302404031817240200240328240402181724020024032824040418172403312404031818280324FF0118"), '{0 = [[[3 = 49U, 4 = 65532U]], [[2 = 0U, 3 = 48U, 4 = 0U]], [[2 = 0U, 3 = 48U, 4 = 1U]], [[2 = 0U, 3 = 48U, 4 = 2U]], [[2 = 0U, 3 = 48U, 4 = 3U]], [[2 = 0U, 3 = 40U, 4 = 2U]], [[2 = 0U, 3 = 40U, 4 = 4U]], [[3 = 49U, 4 = 3U]]], 3 = false, 255 = 1U}')
|
||||
|
||||
-#
|
||||
|
@ -33,33 +33,28 @@ import matter
|
||||
# A packet that needs to be resent if not acknowledged by the other party
|
||||
#################################################################################
|
||||
class Matter_UDPPacket_sent
|
||||
static var RETRY_MS = 500 # retry every 500 ms
|
||||
static var RETRIES = 4 # retry every 500 ms
|
||||
var raw # bytes() to be sent
|
||||
var addr # ip_address (string)
|
||||
var port # port (int)
|
||||
var msg_id # (int) message identifier that needs to be acknowledged
|
||||
var retries # how many retries are allowed, when `0` drop and log
|
||||
var msg_id # (int) message identifier that needs to be acknowledged, or `nil` if no ack needed
|
||||
var exchange_id # (int) exchange id, to match ack
|
||||
var session_id # (int) session id, for logging only
|
||||
var retries # 0 in first attempts, goes up to RETRIES
|
||||
var next_try # timestamp (millis) when to try again
|
||||
|
||||
def init(raw, addr, port, id)
|
||||
self.raw = raw
|
||||
self.addr = addr
|
||||
self.port = port
|
||||
self.msg_id = id
|
||||
self.retries = self.RETRIES
|
||||
self.next_try = tasmota.millis() + self.RETRY_MS
|
||||
def init(msg)
|
||||
# extract information from msg
|
||||
self.raw = msg.raw
|
||||
self.addr = msg.remote_ip
|
||||
self.port = msg.remote_port
|
||||
self.msg_id = msg.x_flag_r ? msg.message_counter : nil
|
||||
self.exchange_id = (msg.exchange_id != nil) ? msg.exchange_id : 0
|
||||
self.session_id = (msg.local_session_id != nil) ? msg.local_session_id : 0
|
||||
# other information
|
||||
self.retries = 0
|
||||
self.next_try = tasmota.millis() + matter.UDPServer._backoff_time(self.retries)
|
||||
end
|
||||
|
||||
def send(udp_socket)
|
||||
import string
|
||||
var ok = udp_socket.send(self.addr ? self.addr : udp_socket.remote_ip, self.port ? self.port : udp_socket.remote_port, self.raw)
|
||||
if ok
|
||||
tasmota.log(string.format("MTR: sending packet to '[%s]:%i'", self.addr, self.port), 3)
|
||||
else
|
||||
tasmota.log(string.format("MTR: failed to send packet to '[%s]:%i'", self.addr, self.port), 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
matter.UDPPacket_sent = Matter_UDPPacket_sent
|
||||
|
||||
@ -68,31 +63,35 @@ matter.UDPPacket_sent = Matter_UDPPacket_sent
|
||||
#
|
||||
#################################################################################
|
||||
class Matter_UDPServer
|
||||
static var RETRIES = 5 # 6 transmissions max (5 retries) - 1 more than spec `MRP_MAX_TRANSMISSIONS` 4.11.8 p.146
|
||||
static var MAX_PACKETS_READ = 4 # read at most 4 packets per tick
|
||||
var address, port # local address and port
|
||||
var addr, port # local addr and port
|
||||
var listening # true if active
|
||||
var udp_socket
|
||||
var dispatch_cb # callback to call when a message is received
|
||||
var packets_sent # map of packets sent to be acknowledged
|
||||
var packets_sent # list map of packets sent to be acknowledged
|
||||
|
||||
#############################################################
|
||||
def init(address, port)
|
||||
self.address = address ? address : ""
|
||||
# Init UDP Server listening to `addr` and `port` (opt).
|
||||
#
|
||||
# By default, the server listens to `""` (all addresses) and port `5540`
|
||||
def init(addr, port)
|
||||
self.addr = addr ? addr : ""
|
||||
self.port = port ? port : 5540
|
||||
self.listening = false
|
||||
self.packets_sent = {}
|
||||
self.packets_sent = []
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# start the server
|
||||
# raises an exception if something is wrong
|
||||
# registers as device handle
|
||||
# Starts the server.
|
||||
# Registers as device handler to Tasmota
|
||||
#
|
||||
# `cb`: callback to call when a message is received
|
||||
# `cb(packet, from_addr, from_port)`: callback to call when a message is received.
|
||||
# Raises an exception if something is wrong.
|
||||
def start(cb)
|
||||
if !self.listening
|
||||
self.udp_socket = udp()
|
||||
var ok = self.udp_socket.begin(self.address, self.port)
|
||||
var ok = self.udp_socket.begin(self.addr, self.port)
|
||||
if !ok raise "network_error", "could not open UDP server" end
|
||||
self.listening = true
|
||||
self.dispatch_cb = cb
|
||||
@ -101,8 +100,7 @@ class Matter_UDPServer
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# stop the server
|
||||
# remove driver
|
||||
# Stops the server and remove driver
|
||||
def stop()
|
||||
if self.listening
|
||||
self.udp_socket.stop()
|
||||
@ -112,6 +110,11 @@ class Matter_UDPServer
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# At every tick:
|
||||
# Check if a packet has arrived, and dispatch to `cb`.
|
||||
# Read at most `MAX_PACKETS_READ (4) packets at each tick to
|
||||
# avoid any starvation.
|
||||
# Then resend queued outgoing packets.
|
||||
def every_50ms()
|
||||
import string
|
||||
var packet_read = 0
|
||||
@ -122,7 +125,7 @@ class Matter_UDPServer
|
||||
packet_read += 1
|
||||
var from_addr = self.udp_socket.remote_ip
|
||||
var from_port = self.udp_socket.remote_port
|
||||
tasmota.log(string.format("MTR: UDP received from [%s]:%i", from_addr, from_port), 4)
|
||||
tasmota.log(string.format("MTR: UDP received from [%s]:%i", from_addr, from_port), 3)
|
||||
if self.dispatch_cb
|
||||
self.dispatch_cb(packet, from_addr, from_port)
|
||||
end
|
||||
@ -133,41 +136,81 @@ class Matter_UDPServer
|
||||
packet = nil
|
||||
end
|
||||
end
|
||||
self.resend_packets() # resend any packet
|
||||
self._resend_packets() # resend any packet
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def resend_packets()
|
||||
for packet:self.packets_sent
|
||||
# Send packet now.
|
||||
#
|
||||
# Returns `true` if packet was successfully sent.
|
||||
def send(packet)
|
||||
import string
|
||||
var ok = self.udp_socket.send(packet.addr ? packet.addr : self.udp_socket.remote_ip, packet.port ? packet.port : self.udp_socket.remote_port, packet.raw)
|
||||
if ok
|
||||
tasmota.log(string.format("MTR: sending packet to '[%s]:%i'", packet.addr, packet.port), 4)
|
||||
else
|
||||
tasmota.log(string.format("MTR: error sending packet to '[%s]:%i'", packet.addr, packet.port), 2)
|
||||
end
|
||||
return ok
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Resend packets if they have not been acknowledged by receiver
|
||||
# either with direct Ack packet or ack embedded in another packet.
|
||||
# Packets with `id`=`nil` are not resent.
|
||||
# <BR>
|
||||
# Packets are re-sent at most `RETRIES` (4) times, i.e. sent maximum 5 times.
|
||||
# Exponential backoff is added after each resending.
|
||||
# <BR>
|
||||
# If all retries expired, remove packet and log.
|
||||
def _resend_packets()
|
||||
var idx = 0
|
||||
while idx < size(self.packets_sent)
|
||||
var packet = self.packets_sent[idx]
|
||||
if tasmota.time_reached(packet.next_try)
|
||||
tasmota.log("MTR: resending packet id=" + str(packet.msg_id), 3)
|
||||
packet.send(self.udp_socket) # resend
|
||||
packet.retries -= 1
|
||||
if packet.retries <= 0
|
||||
self.packets_sent.remove(packet.msg_id)
|
||||
if packet.retries <= self.RETRIES
|
||||
tasmota.log("MTR: . Resending packet id=" + str(packet.msg_id), 3)
|
||||
self.send(packet)
|
||||
packet.next_try = tasmota.millis() + self._backoff_time(packet.retries)
|
||||
packet.retries += 1
|
||||
idx += 1
|
||||
else
|
||||
packet.next_try = tasmota.millis() + packet.RETRY_MS
|
||||
import string
|
||||
self.packets_sent.remove(idx)
|
||||
tasmota.log(string.format("MTR: . (%6i) Unacked packet '[%s]:%i' msg_id=%i", packet.session_id, packet.addr, packet.port, packet.msg_id), 2)
|
||||
end
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# just received acknowledgment, remove packet from sender
|
||||
def packet_ack(id)
|
||||
# Just received acknowledgment, remove packet from sender
|
||||
def received_ack(msg)
|
||||
var id = msg.ack_message_counter
|
||||
var exch = msg.exchange_id
|
||||
if id == nil return end
|
||||
if self.packets_sent.contains(id)
|
||||
self.packets_sent.remove(id)
|
||||
tasmota.log("MTR: removed packet from sending list id=" + str(id), 3)
|
||||
tasmota.log("MTR: receveived ACK id="+str(id), 3)
|
||||
var idx = 0
|
||||
while idx < size(self.packets_sent)
|
||||
var packet = self.packets_sent[idx]
|
||||
if packet.msg_id == id && packet.exchange_id == exch
|
||||
self.packets_sent.remove(idx)
|
||||
tasmota.log("MTR: . Removed packet from sending list id=" + str(id), 3)
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
def send_response(raw, addr, port, id)
|
||||
var packet = matter.UDPPacket_sent(raw, addr, port, id)
|
||||
packet.send(self.udp_socket) # send
|
||||
if id
|
||||
self.packets_sent[id] = packet
|
||||
# Send a packet, enqueue it if `id` is not `nil`
|
||||
def send_UDP(msg)
|
||||
var packet = matter.UDPPacket_sent(msg)
|
||||
self.send(packet)
|
||||
if packet.msg_id
|
||||
self.packets_sent.push(packet)
|
||||
end
|
||||
end
|
||||
|
||||
@ -175,13 +218,25 @@ class Matter_UDPServer
|
||||
# placeholder, nothing to run for now
|
||||
def every_second()
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# Compute exponential backoff as per 4.11.2.1 p.137
|
||||
static def _backoff_time(n)
|
||||
def power_int(v, n)
|
||||
var r = 1
|
||||
while n > 0
|
||||
r *= v
|
||||
n -= 1
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
import math
|
||||
var i = 300 # SLEEPY_ACTIVE_INTERVAL
|
||||
var rand = real(math.rand() & 0xFF) / 255 # 0..1 with reasonable granularity
|
||||
var n_power = n > 0 ? n - 1 : 0
|
||||
var mrpBackoffTime = i * power_int(1.6, n_power) * (1.0 + rand * 0.25 )
|
||||
return int(mrpBackoffTime)
|
||||
end
|
||||
end
|
||||
matter.UDPServer = Matter_UDPServer
|
||||
|
||||
#-
|
||||
|
||||
import matter
|
||||
var udps = matter.UDPServer()
|
||||
udps.listen()
|
||||
|
||||
-#
|
||||
|
@ -70,12 +70,60 @@ class Matter_UI
|
||||
webserver.content_send(string.format("<p></p><button name='%s' class='button bgrn'>", matter_enabled ? "disable" : "enable"))
|
||||
webserver.content_send(matter_enabled ? "Disable" : "Enable")
|
||||
webserver.content_send(" Matter</button></form></p>")
|
||||
|
||||
|
||||
webserver.content_send("<p></p></fieldset><p></p>")
|
||||
|
||||
return matter_enabled
|
||||
end
|
||||
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
#- Show QR Code
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
def show_qrcode(qr_text)
|
||||
import webserver
|
||||
# QRCode via UTF8
|
||||
var empty = " "
|
||||
var lowhalf = "\342\226\204"
|
||||
var uphalf = "\342\226\200"
|
||||
var full = "\342\226\210"
|
||||
|
||||
var qr = matter.QRCode.encode_str(qr_text)
|
||||
var bitmap = qr['bitmap']
|
||||
var sz = qr['size']
|
||||
|
||||
webserver.content_send('<style>.qr{font-family:monospace; margin:0; padding:0; white-space:pre; font-size:18px; color:#fff; line-height:100%;}</style>')
|
||||
|
||||
|
||||
webserver.content_send("<div style='transform:scale(.8,1); display:inline-block;'>")
|
||||
|
||||
var s = "<div class='qr'>"
|
||||
webserver.content_send(s)
|
||||
s = ""
|
||||
for i: 0 .. sz + 1 s += lowhalf end
|
||||
s += "</div>"
|
||||
webserver.content_send(s)
|
||||
for i: 0 .. (sz+1)/2 - 1
|
||||
s = "<div class='qr' style='background-color:#000;'>" + full
|
||||
for j: 0 .. sz - 1
|
||||
var high = (bitmap[i*2][j] == " ")
|
||||
var low = (i*2+1 < sz) ? (bitmap[i*2+1][j] == " ") : true # default to true for bottom margin if size is odd
|
||||
s += high ? (low ? full : uphalf) : (low ? lowhalf : empty)
|
||||
end
|
||||
s += full
|
||||
s += "</div>"
|
||||
webserver.content_send(s)
|
||||
end
|
||||
# webserver.content_send("</div>")
|
||||
if sz % 2 == 0
|
||||
s = "<div class='qr' style='background-color:#000;'>"
|
||||
for i: 0 .. sz + 1 s += uphalf end
|
||||
s += "/<div>"
|
||||
webserver.content_send(s)
|
||||
end
|
||||
|
||||
webserver.content_send("</div>")
|
||||
end
|
||||
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
#- Show commissioning information and QR Code
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
@ -83,24 +131,40 @@ class Matter_UI
|
||||
import webserver
|
||||
import string
|
||||
|
||||
webserver.content_send("<fieldset><legend><b> Matter Passcode </b></legend><p></p>")
|
||||
var seconds_left = (self.device.commissioning_open - tasmota.millis()) / 1000
|
||||
if seconds_left < 0 seconds_left = 0 end
|
||||
var min_left = (seconds_left + 30) / 60
|
||||
|
||||
webserver.content_send(string.format("<fieldset><legend><b> Commissioning open for %i min </b></legend><p></p>", min_left))
|
||||
|
||||
var pairing_code = self.device.compute_manual_pairing_code()
|
||||
webserver.content_send(string.format("<p>Manual pairing code:<br><b>%s-%s-%s</b></p><hr>", pairing_code[0..3], pairing_code[4..6], pairing_code[7..]))
|
||||
|
||||
var qr_text = self.device.compute_qrcode_content()
|
||||
webserver.content_send('<div id="qrcode"></div>')
|
||||
webserver.content_send(string.format('<script type="text/javascript"> new QRCode(document.getElementById("qrcode"), "%s");</script>', qr_text))
|
||||
webserver.content_send(string.format("<p>%s</p><hr>", qr_text))
|
||||
|
||||
webserver.content_send(string.format("<div><center>"))
|
||||
var qr_text = self.device.compute_qrcode_content()
|
||||
self.show_qrcode(qr_text)
|
||||
webserver.content_send(string.format("<p> %s</p>", qr_text))
|
||||
webserver.content_send(string.format("</div>"))
|
||||
|
||||
webserver.content_send("<p></p></fieldset><p></p>")
|
||||
|
||||
end
|
||||
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
#- Show Passcode / discriminator form
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
def show_passcode_form()
|
||||
import webserver
|
||||
import string
|
||||
|
||||
webserver.content_send("<fieldset><legend><b> Matter Passcode </b></legend><p></p>")
|
||||
webserver.content_send("<form action='/matterc' method='post' >")
|
||||
webserver.content_send("<p>Passcode:</p>")
|
||||
webserver.content_send(string.format("<input type='number' min='1' max='99999998' name='passcode' value='%i'>", self.device.passcode))
|
||||
webserver.content_send(string.format("<input type='number' min='1' max='99999998' name='passcode' value='%i'>", self.device.root_passcode))
|
||||
webserver.content_send("<p>Distinguish id:</p>")
|
||||
webserver.content_send(string.format("<input type='number' min='0' max='2047' name='discriminator' value='%i'>", self.device.discriminator))
|
||||
webserver.content_send(string.format("<input type='number' min='0' max='4095' name='discriminator' value='%i'>", self.device.root_discriminator))
|
||||
webserver.content_send(string.format("<p><input type='checkbox' name='ipv4'%s>IPv4 only</p>", self.device.ipv4only ? " checked" : ""))
|
||||
webserver.content_send("<p></p><button name='passcode' class='button bgrn'>Change</button></form></p>")
|
||||
|
||||
|
||||
webserver.content_send("<p></p></fieldset><p></p>")
|
||||
|
||||
end
|
||||
@ -108,52 +172,42 @@ class Matter_UI
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
#- Show commissioning information and QR Code
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
def show_session_info(p)
|
||||
def show_fabric_info(p)
|
||||
import webserver
|
||||
import string
|
||||
|
||||
webserver.content_send("<fieldset><legend><b> Sessions </b></legend><p></p>")
|
||||
webserver.content_send("<p>Existing sessions:</p>")
|
||||
webserver.content_send("<fieldset><legend><b> Fabrics </b></legend><p></p>")
|
||||
webserver.content_send("<p>Existing fabrics:</p>")
|
||||
|
||||
if size(self.device.sessions.sessions) == 0
|
||||
webserver.content_send("<p><b>None</b></p>")
|
||||
else
|
||||
var i = 0
|
||||
var sz = size(self.device.sessions.sessions)
|
||||
while i < sz
|
||||
var s = self.device.sessions.sessions[i]
|
||||
if s.fabric
|
||||
webserver.content_send(string.format("<fieldset><legend><b> Session %i </b></legend><p></p>", s.local_session_id))
|
||||
if i != 0 webserver.content_send("<hr>") end
|
||||
var fabric_rev = s.fabric.copy().reverse()
|
||||
var deviceid_rev = s.deviceid.copy().reverse()
|
||||
webserver.content_send(string.format("Fabric: %s<br>", fabric_rev.tohex()))
|
||||
webserver.content_send(string.format("Device: %s<br> ", deviceid_rev.tohex()))
|
||||
var first = true
|
||||
for f : self.device.sessions.fabrics.persistables()
|
||||
if !first webserver.content_send("<hr>") end
|
||||
first = false
|
||||
|
||||
webserver.content_send("<form action='/matterc' method='post' ")
|
||||
webserver.content_send("onsubmit='return confirm(\"This will cause a restart.\");'>")
|
||||
webserver.content_send(string.format("<input name='del_session' type='hidden' value='%d'>", s.local_session_id))
|
||||
webserver.content_send("<button name='del' class='button bgrn'>Delete Session</button></form></p>")
|
||||
|
||||
webserver.content_send("<p></p></fieldset><p></p>")
|
||||
end
|
||||
i += 1
|
||||
var label = f.fabric_label
|
||||
if !label label = "<No label>" end
|
||||
label = webserver.html_escape(label) # protect against HTML injection
|
||||
|
||||
webserver.content_send(string.format("<fieldset><legend><b> #%i %s </b></legend><p></p>", f.get_fabric_index(), label))
|
||||
|
||||
var fabric_rev = f.get_fabric_id().copy().reverse()
|
||||
var deviceid_rev = f.get_device_id().copy().reverse()
|
||||
webserver.content_send(string.format("Fabric: %s<br>", fabric_rev.tohex()))
|
||||
webserver.content_send(string.format("Device: %s<br> ", deviceid_rev.tohex()))
|
||||
|
||||
webserver.content_send("<form action='/matterc' method='post'>")
|
||||
webserver.content_send(string.format("<input name='del_fabric' type='hidden' value='%i'>", f.get_fabric_index()))
|
||||
webserver.content_send("<button name='del' class='button bgrn'>Delete Fabric</button></form></p>")
|
||||
|
||||
webserver.content_send("<p></p></fieldset><p></p>")
|
||||
end
|
||||
end
|
||||
|
||||
webserver.content_send("<p></p></fieldset><p></p>")
|
||||
|
||||
end
|
||||
|
||||
|
||||
#######################################################################
|
||||
# Serve qrcode.min.js static file
|
||||
#######################################################################
|
||||
def page_qrcode_min_js()
|
||||
import webserver
|
||||
|
||||
webserver.content_open(200, "text/javascript")
|
||||
webserver.content_send(matter._QRCODE_MINJS)
|
||||
end
|
||||
|
||||
#######################################################################
|
||||
@ -162,17 +216,15 @@ class Matter_UI
|
||||
def page_part_mgr()
|
||||
import webserver
|
||||
import string
|
||||
|
||||
|
||||
if !webserver.check_privileged_access() return nil end
|
||||
|
||||
webserver.content_start("Matter") #- title of the web page -#
|
||||
webserver.content_send_style() #- send standard Tasmota styles -#
|
||||
|
||||
webserver.content_send('<script type="text/javascript" src="qrcode.min.js"></script>')
|
||||
|
||||
if self.show_enable()
|
||||
self.show_commissioning_info()
|
||||
self.show_session_info()
|
||||
self.show_passcode_form()
|
||||
self.show_fabric_info()
|
||||
end
|
||||
webserver.content_button(webserver.BUTTON_CONFIGURATION)
|
||||
webserver.content_stop() #- end of web page -#
|
||||
@ -188,23 +240,24 @@ class Matter_UI
|
||||
import string
|
||||
import partition_core
|
||||
import persist
|
||||
|
||||
|
||||
|
||||
#- check that the partition is valid -#
|
||||
var p = partition_core.Partition()
|
||||
|
||||
try
|
||||
|
||||
|
||||
#---------------------------------------------------------------------#
|
||||
# Change Passcode and/or Passcode
|
||||
#---------------------------------------------------------------------#
|
||||
if webserver.has_arg("passcode") || webserver.has_arg("discriminator")
|
||||
if webserver.has_arg("passcode")
|
||||
self.device.passcode = int(webserver.arg("passcode"))
|
||||
self.device.root_passcode = int(webserver.arg("passcode"))
|
||||
end
|
||||
if webserver.has_arg("discriminator")
|
||||
self.device.discriminator = int(webserver.arg("discriminator"))
|
||||
self.device.root_discriminator = int(webserver.arg("discriminator"))
|
||||
end
|
||||
self.device.ipv4only = webserver.arg("ipv4") == 'on'
|
||||
self.device.save_param()
|
||||
|
||||
#- and force restart -#
|
||||
@ -220,15 +273,21 @@ class Matter_UI
|
||||
#- and force restart -#
|
||||
webserver.redirect("/?rst=")
|
||||
|
||||
elif webserver.has_arg("del_session")
|
||||
var session = self.device.sessions.get_session_by_local_session_id(int(webserver.arg("del_session")))
|
||||
if session != nil
|
||||
self.device.sessions.remove_session(session)
|
||||
self.device.sessions.save()
|
||||
elif webserver.has_arg("del_fabric")
|
||||
var del_fabric = int(webserver.arg("del_fabric"))
|
||||
var idx = 0
|
||||
var fabrics = self.device.sessions.fabrics
|
||||
while idx < size(fabrics)
|
||||
if fabrics[idx].get_fabric_index() == del_fabric
|
||||
self.device.remove_fabric(fabrics[idx])
|
||||
break
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
#- and force restart -#
|
||||
webserver.redirect("/?rst=")
|
||||
webserver.redirect("/matterc?")
|
||||
|
||||
end
|
||||
except .. as e, m
|
||||
@ -244,6 +303,46 @@ class Matter_UI
|
||||
end
|
||||
end
|
||||
|
||||
#- display sensor value in the web UI -#
|
||||
def web_sensor()
|
||||
import webserver
|
||||
import string
|
||||
|
||||
var matter_enabled = tasmota.get_option(matter.MATTER_OPTION)
|
||||
|
||||
if matter_enabled
|
||||
if self.device.is_root_commissioning_open()
|
||||
self.show_commissioning_info()
|
||||
end
|
||||
|
||||
# mtc0 = close, mtc1 = open commissioning
|
||||
var fabrics_count = self.device.sessions.count_active_fabrics()
|
||||
if fabrics_count == 0
|
||||
webserver.content_send(string.format("<div style='text-align:right;font-size:11px;color:#aaa;'>%s</div>", "No active association"))
|
||||
else
|
||||
var plural = fabrics_count > 1
|
||||
webserver.content_send(string.format("<div style='text-align:right;font-size:11px;color:#aaa;'>%s</div>", str(fabrics_count) + " active association" + (plural ? "s" : "")))
|
||||
end
|
||||
|
||||
webserver.content_send(string.format("<button onclick='la(\"&mtc%i=1\");'>", self.device.commissioning_open == nil ? 1 : 0))
|
||||
webserver.content_send(matter._LOGO)
|
||||
if self.device.commissioning_open == nil
|
||||
webserver.content_send(" Open Commissioning</button>")
|
||||
else
|
||||
webserver.content_send(" Close Commissioning</button>")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def web_get_arg()
|
||||
import webserver
|
||||
if webserver.has_arg("mtc0") # Close Commissioning
|
||||
self.device.stop_basic_commissioning()
|
||||
elif webserver.has_arg("mtc1") # Open Commissioning
|
||||
self.device.start_root_basic_commissioning()
|
||||
end
|
||||
end
|
||||
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
# respond to web_add_handler() event to register web listeners
|
||||
#- ---------------------------------------------------------------------- -#
|
||||
@ -253,7 +352,6 @@ class Matter_UI
|
||||
#- we need to register a closure, not just a function, that captures the current instance -#
|
||||
webserver.on("/matterc", / -> self.page_part_mgr(), webserver.HTTP_GET)
|
||||
webserver.on("/matterc", / -> self.page_part_ctl(), webserver.HTTP_POST)
|
||||
webserver.on("/qrcode.min.js", / -> self.page_qrcode_min_js(), webserver.HTTP_GET)
|
||||
end
|
||||
end
|
||||
matter.UI = Matter_UI
|
||||
|
1035
lib/libesp32/berry_matter/src/qrcodegen.c
Normal file
1035
lib/libesp32/berry_matter/src/qrcodegen.c
Normal file
File diff suppressed because it is too large
Load Diff
319
lib/libesp32/berry_matter/src/qrcodegen.h
Normal file
319
lib/libesp32/berry_matter/src/qrcodegen.h
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* QR Code generator library (C)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
* - The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* - The Software is provided "as is", without warranty of any kind, express or
|
||||
* implied, including but not limited to the warranties of merchantability,
|
||||
* fitness for a particular purpose and noninfringement. In no event shall the
|
||||
* authors or copyright holders be liable for any claim, damages or other
|
||||
* liability, whether in an action of contract, tort or otherwise, arising from,
|
||||
* out of or in connection with the Software or the use or other dealings in the
|
||||
* Software.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* This library creates QR Code symbols, which is a type of two-dimension barcode.
|
||||
* Invented by Denso Wave and described in the ISO/IEC 18004 standard.
|
||||
* A QR Code structure is an immutable square grid of black and white cells.
|
||||
* The library provides functions to create a QR Code from text or binary data.
|
||||
* The library covers the QR Code Model 2 specification, supporting all versions (sizes)
|
||||
* from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
|
||||
*
|
||||
* Ways to create a QR Code object:
|
||||
* - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary().
|
||||
* - Low level: Custom-make the list of segments and call
|
||||
* qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced().
|
||||
* (Note that all ways require supplying the desired error correction level and various byte buffers.)
|
||||
*/
|
||||
|
||||
|
||||
/*---- Enum and struct types----*/
|
||||
|
||||
/*
|
||||
* The error correction level in a QR Code symbol.
|
||||
*/
|
||||
enum qrcodegen_Ecc {
|
||||
// Must be declared in ascending order of error protection
|
||||
// so that an internal qrcodegen function works properly
|
||||
qrcodegen_Ecc_LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords
|
||||
qrcodegen_Ecc_MEDIUM , // The QR Code can tolerate about 15% erroneous codewords
|
||||
qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords
|
||||
qrcodegen_Ecc_HIGH , // The QR Code can tolerate about 30% erroneous codewords
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* The mask pattern used in a QR Code symbol.
|
||||
*/
|
||||
enum qrcodegen_Mask {
|
||||
// A special value to tell the QR Code encoder to
|
||||
// automatically select an appropriate mask pattern
|
||||
qrcodegen_Mask_AUTO = -1,
|
||||
// The eight actual mask patterns
|
||||
qrcodegen_Mask_0 = 0,
|
||||
qrcodegen_Mask_1,
|
||||
qrcodegen_Mask_2,
|
||||
qrcodegen_Mask_3,
|
||||
qrcodegen_Mask_4,
|
||||
qrcodegen_Mask_5,
|
||||
qrcodegen_Mask_6,
|
||||
qrcodegen_Mask_7,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Describes how a segment's data bits are interpreted.
|
||||
*/
|
||||
enum qrcodegen_Mode {
|
||||
qrcodegen_Mode_NUMERIC = 0x1,
|
||||
qrcodegen_Mode_ALPHANUMERIC = 0x2,
|
||||
qrcodegen_Mode_BYTE = 0x4,
|
||||
qrcodegen_Mode_KANJI = 0x8,
|
||||
qrcodegen_Mode_ECI = 0x7,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* A segment of character/binary/control data in a QR Code symbol.
|
||||
* The mid-level way to create a segment is to take the payload data
|
||||
* and call a factory function such as qrcodegen_makeNumeric().
|
||||
* The low-level way to create a segment is to custom-make the bit buffer
|
||||
* and initialize a qrcodegen_Segment struct with appropriate values.
|
||||
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
|
||||
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
|
||||
* Moreover, the maximum allowed bit length is 32767 because
|
||||
* the largest QR Code (version 40) has 31329 modules.
|
||||
*/
|
||||
struct qrcodegen_Segment {
|
||||
// The mode indicator of this segment.
|
||||
enum qrcodegen_Mode mode;
|
||||
|
||||
// The length of this segment's unencoded data. Measured in characters for
|
||||
// numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
|
||||
// Always zero or positive. Not the same as the data's bit length.
|
||||
int numChars;
|
||||
|
||||
// The data bits of this segment, packed in bitwise big endian.
|
||||
// Can be null if the bit length is zero.
|
||||
uint8_t *data;
|
||||
|
||||
// The number of valid data bits used in the buffer. Requires
|
||||
// 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8.
|
||||
// The character count (numChars) must agree with the mode and the bit buffer length.
|
||||
int bitLength;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*---- Macro constants and functions ----*/
|
||||
|
||||
#define qrcodegen_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard
|
||||
#define qrcodegen_VERSION_MAX 40 // The maximum version number supported in the QR Code Model 2 standard
|
||||
|
||||
// Calculates the number of bytes needed to store any QR Code up to and including the given version number,
|
||||
// as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];'
|
||||
// can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16).
|
||||
// Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX.
|
||||
#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1)
|
||||
|
||||
// The worst-case number of bytes needed to store one QR Code, up to and including
|
||||
// version 40. This value equals 3918, which is just under 4 kilobytes.
|
||||
// Use this more convenient value to avoid calculating tighter memory bounds for buffers.
|
||||
#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX)
|
||||
|
||||
|
||||
|
||||
/*---- Functions (high level) to generate QR Codes ----*/
|
||||
|
||||
/*
|
||||
* Encodes the given text string to a QR Code, returning true if encoding succeeded.
|
||||
* If the data is too long to fit in any version in the given range
|
||||
* at the given ECC level, then false is returned.
|
||||
* - The input text must be encoded in UTF-8 and contain no NULs.
|
||||
* - The variables ecl and mask must correspond to enum constant values.
|
||||
* - Requires 1 <= minVersion <= maxVersion <= 40.
|
||||
* - The arrays tempBuffer and qrcode must each have a length
|
||||
* of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion).
|
||||
* - After the function returns, tempBuffer contains no useful data.
|
||||
* - If successful, the resulting QR Code may use numeric,
|
||||
* alphanumeric, or byte mode to encode the text.
|
||||
* - In the most optimistic case, a QR Code at version 40 with low ECC
|
||||
* can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string
|
||||
* up to 4296 characters, or any digit string up to 7089 characters.
|
||||
* These numbers represent the hard upper limit of the QR Code standard.
|
||||
* - Please consult the QR Code specification for information on
|
||||
* data capacities per version, ECC level, and text encoding mode.
|
||||
*/
|
||||
bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[],
|
||||
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
|
||||
|
||||
|
||||
/*
|
||||
* Encodes the given binary data to a QR Code, returning true if encoding succeeded.
|
||||
* If the data is too long to fit in any version in the given range
|
||||
* at the given ECC level, then false is returned.
|
||||
* - The input array range dataAndTemp[0 : dataLen] should normally be
|
||||
* valid UTF-8 text, but is not required by the QR Code standard.
|
||||
* - The variables ecl and mask must correspond to enum constant values.
|
||||
* - Requires 1 <= minVersion <= maxVersion <= 40.
|
||||
* - The arrays dataAndTemp and qrcode must each have a length
|
||||
* of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion).
|
||||
* - After the function returns, the contents of dataAndTemp may have changed,
|
||||
* and does not represent useful data anymore.
|
||||
* - If successful, the resulting QR Code will use byte mode to encode the data.
|
||||
* - In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte
|
||||
* sequence up to length 2953. This is the hard upper limit of the QR Code standard.
|
||||
* - Please consult the QR Code specification for information on
|
||||
* data capacities per version, ECC level, and text encoding mode.
|
||||
*/
|
||||
bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[],
|
||||
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
|
||||
|
||||
|
||||
/*---- Functions (low level) to generate QR Codes ----*/
|
||||
|
||||
/*
|
||||
* Renders a QR Code representing the given segments at the given error correction level.
|
||||
* The smallest possible QR Code version is automatically chosen for the output. Returns true if
|
||||
* QR Code creation succeeded, or false if the data is too long to fit in any version. The ECC level
|
||||
* of the result may be higher than the ecl argument if it can be done without increasing the version.
|
||||
* This function allows the user to create a custom sequence of segments that switches
|
||||
* between modes (such as alphanumeric and byte) to encode text in less space.
|
||||
* This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary().
|
||||
* To save memory, the segments' data buffers can alias/overlap tempBuffer, and will
|
||||
* result in them being clobbered, but the QR Code output will still be correct.
|
||||
* But the qrcode array must not overlap tempBuffer or any segment's data buffer.
|
||||
*/
|
||||
bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len,
|
||||
enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]);
|
||||
|
||||
|
||||
/*
|
||||
* Renders a QR Code representing the given segments with the given encoding parameters.
|
||||
* Returns true if QR Code creation succeeded, or false if the data is too long to fit in the range of versions.
|
||||
* The smallest possible QR Code version within the given range is automatically
|
||||
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
|
||||
* may be higher than the ecl argument if it can be done without increasing the
|
||||
* version. The mask number is either between 0 to 7 (inclusive) to force that
|
||||
* mask, or -1 to automatically choose an appropriate mask (which may be slow).
|
||||
* This function allows the user to create a custom sequence of segments that switches
|
||||
* between modes (such as alphanumeric and byte) to encode text in less space.
|
||||
* This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary().
|
||||
* To save memory, the segments' data buffers can alias/overlap tempBuffer, and will
|
||||
* result in them being clobbered, but the QR Code output will still be correct.
|
||||
* But the qrcode array must not overlap tempBuffer or any segment's data buffer.
|
||||
*/
|
||||
bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl,
|
||||
int minVersion, int maxVersion, int mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]);
|
||||
|
||||
|
||||
/*
|
||||
* Tests whether the given string can be encoded as a segment in alphanumeric mode.
|
||||
* A string is encodable iff each character is in the following set: 0 to 9, A to Z
|
||||
* (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*/
|
||||
bool qrcodegen_isAlphanumeric(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
* Tests whether the given string can be encoded as a segment in numeric mode.
|
||||
* A string is encodable iff each character is in the range 0 to 9.
|
||||
*/
|
||||
bool qrcodegen_isNumeric(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
* Returns the number of bytes (uint8_t) needed for the data buffer of a segment
|
||||
* containing the given number of characters using the given mode. Notes:
|
||||
* - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or
|
||||
* the number of needed bits exceeds INT16_MAX (i.e. 32767).
|
||||
* - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096.
|
||||
* - It is okay for the user to allocate more bytes for the buffer than needed.
|
||||
* - For byte mode, numChars measures the number of bytes, not Unicode code points.
|
||||
* - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned.
|
||||
* An actual ECI segment can have shorter data. For non-ECI modes, the result is exact.
|
||||
*/
|
||||
size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given binary data encoded in
|
||||
* byte mode. All input byte arrays are acceptable. Any text string
|
||||
* can be converted to UTF-8 bytes and encoded as a byte mode segment.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given string of decimal digits encoded in numeric mode.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given text string encoded in alphanumeric mode.
|
||||
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
|
||||
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing an Extended Channel Interpretation
|
||||
* (ECI) designator with the given assignment value.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]);
|
||||
|
||||
|
||||
/*---- Functions to extract raw data from QR Codes ----*/
|
||||
|
||||
/*
|
||||
* Returns the side length of the given QR Code, assuming that encoding succeeded.
|
||||
* The result is in the range [21, 177]. Note that the length of the array buffer
|
||||
* is related to the side length - every 'uint8_t qrcode[]' must have length at least
|
||||
* qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1).
|
||||
*/
|
||||
int qrcodegen_getSize(const uint8_t qrcode[]);
|
||||
|
||||
|
||||
/*
|
||||
* Returns the color of the module (pixel) at the given coordinates, which is false
|
||||
* for white or true for black. The top left corner has the coordinates (x=0, y=0).
|
||||
* If the given coordinates are out of bounds, then false (white) is returned.
|
||||
*/
|
||||
bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y);
|
||||
|
||||
/*
|
||||
* Returns the qrcode size of the specified version. Returns -1 on failure
|
||||
*/
|
||||
int qrcodegen_version2size(int version);
|
||||
/*
|
||||
* Returns the min version of the data that can be stored. Returns -1 on failure
|
||||
*/
|
||||
int qrcodegen_getMinFitVersion(enum qrcodegen_Ecc ecl, size_t dataLen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -116,9 +116,9 @@ void be_load_Matter_PBKDFParamRequest_class(bvm *vm) {
|
||||
extern const bclass be_class_Matter_PBKDFParamResponse;
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: encode
|
||||
** Solidified function: tlv2raw
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_PBKDFParamResponse_encode, /* name */
|
||||
be_local_closure(Matter_PBKDFParamResponse_tlv2raw, /* name */
|
||||
be_nested_proto(
|
||||
10, /* nstack */
|
||||
2, /* argc */
|
||||
@ -147,11 +147,11 @@ be_local_closure(Matter_PBKDFParamResponse_encode, /* name */
|
||||
/* K15 */ be_nested_str_weak(pbkdf_parameters_salt),
|
||||
/* K16 */ be_nested_str_weak(SLEEPY_IDLE_INTERVAL),
|
||||
/* K17 */ be_nested_str_weak(SLEEPY_ACTIVE_INTERVAL),
|
||||
/* K18 */ be_nested_str_weak(encode),
|
||||
/* K18 */ be_nested_str_weak(tlv2raw),
|
||||
}),
|
||||
be_str_weak(encode),
|
||||
be_str_weak(tlv2raw),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[70]) { /* code */
|
||||
( &(const binstruction[71]) { /* code */
|
||||
0xB80A0000, // 0000 GETNGBL R2 K0
|
||||
0x88080501, // 0001 GETMBR R2 R2 K1
|
||||
0x8C080502, // 0002 GETMET R2 R2 K2
|
||||
@ -220,8 +220,9 @@ be_local_closure(Matter_PBKDFParamResponse_encode, /* name */
|
||||
0x88240111, // 0041 GETMBR R9 R0 K17
|
||||
0x7C140800, // 0042 CALL R5 4
|
||||
0x8C100512, // 0043 GETMET R4 R2 K18
|
||||
0x7C100200, // 0044 CALL R4 1
|
||||
0x80040800, // 0045 RET 1 R4
|
||||
0x5C180200, // 0044 MOVE R6 R1
|
||||
0x7C100400, // 0045 CALL R4 2
|
||||
0x80040800, // 0046 RET 1 R4
|
||||
})
|
||||
)
|
||||
);
|
||||
@ -237,13 +238,13 @@ be_local_class(Matter_PBKDFParamResponse,
|
||||
be_nested_map(8,
|
||||
( (struct bmapnode*) &(const bmapnode[]) {
|
||||
{ be_const_key_weak(pbkdf_parameters_salt, -1), be_const_var(4) },
|
||||
{ be_const_key_weak(SLEEPY_IDLE_INTERVAL, -1), be_const_var(5) },
|
||||
{ be_const_key_weak(SLEEPY_ACTIVE_INTERVAL, 1), be_const_var(6) },
|
||||
{ be_const_key_weak(responderRandom, -1), be_const_var(1) },
|
||||
{ be_const_key_weak(SLEEPY_ACTIVE_INTERVAL, -1), be_const_var(6) },
|
||||
{ be_const_key_weak(SLEEPY_IDLE_INTERVAL, 1), be_const_var(5) },
|
||||
{ be_const_key_weak(responderSessionId, -1), be_const_var(2) },
|
||||
{ be_const_key_weak(pbkdf_parameters_iterations, -1), be_const_var(3) },
|
||||
{ be_const_key_weak(initiatorRandom, -1), be_const_var(0) },
|
||||
{ be_const_key_weak(responderSessionId, 3), be_const_var(2) },
|
||||
{ be_const_key_weak(encode, -1), be_const_closure(Matter_PBKDFParamResponse_encode_closure) },
|
||||
{ be_const_key_weak(initiatorRandom, 7), be_const_var(0) },
|
||||
{ be_const_key_weak(responderRandom, 3), be_const_var(1) },
|
||||
{ be_const_key_weak(tlv2raw, -1), be_const_closure(Matter_PBKDFParamResponse_tlv2raw_closure) },
|
||||
})),
|
||||
be_str_weak(Matter_PBKDFParamResponse)
|
||||
);
|
||||
@ -270,7 +271,7 @@ be_local_closure(Matter_Pake1_parse, /* name */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[11]) { /* constants */
|
||||
( &(const bvalue[10]) { /* constants */
|
||||
/* K0 */ be_const_int(0),
|
||||
/* K1 */ be_nested_str_weak(matter),
|
||||
/* K2 */ be_nested_str_weak(TLV),
|
||||
@ -278,10 +279,9 @@ be_local_closure(Matter_Pake1_parse, /* name */
|
||||
/* K4 */ be_nested_str_weak(tasmota),
|
||||
/* K5 */ be_nested_str_weak(log),
|
||||
/* K6 */ be_nested_str_weak(MTR_X3A_X20parsed_X20TLV_X3A_X20),
|
||||
/* K7 */ be_const_int(3),
|
||||
/* K8 */ be_nested_str_weak(pA),
|
||||
/* K9 */ be_nested_str_weak(getsubval),
|
||||
/* K10 */ be_const_int(1),
|
||||
/* K7 */ be_nested_str_weak(pA),
|
||||
/* K8 */ be_nested_str_weak(getsubval),
|
||||
/* K9 */ be_const_int(1),
|
||||
}),
|
||||
be_str_weak(parse),
|
||||
&be_const_str_solidified,
|
||||
@ -302,12 +302,12 @@ be_local_closure(Matter_Pake1_parse, /* name */
|
||||
0x5C1C0600, // 000D MOVE R7 R3
|
||||
0x7C180200, // 000E CALL R6 1
|
||||
0x001A0C06, // 000F ADD R6 K6 R6
|
||||
0x581C0007, // 0010 LDCONST R7 K7
|
||||
0x541E0003, // 0010 LDINT R7 4
|
||||
0x7C100600, // 0011 CALL R4 3
|
||||
0x8C100709, // 0012 GETMET R4 R3 K9
|
||||
0x5818000A, // 0013 LDCONST R6 K10
|
||||
0x8C100708, // 0012 GETMET R4 R3 K8
|
||||
0x58180009, // 0013 LDCONST R6 K9
|
||||
0x7C100400, // 0014 CALL R4 2
|
||||
0x90021004, // 0015 SETMBR R0 K8 R4
|
||||
0x90020E04, // 0015 SETMBR R0 K7 R4
|
||||
0x80040000, // 0016 RET 1 R0
|
||||
})
|
||||
)
|
||||
@ -339,9 +339,9 @@ void be_load_Matter_Pake1_class(bvm *vm) {
|
||||
extern const bclass be_class_Matter_Pake2;
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: encode
|
||||
** Solidified function: tlv2raw
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Pake2_encode, /* name */
|
||||
be_local_closure(Matter_Pake2_tlv2raw, /* name */
|
||||
be_nested_proto(
|
||||
8, /* nstack */
|
||||
2, /* argc */
|
||||
@ -361,11 +361,11 @@ be_local_closure(Matter_Pake2_encode, /* name */
|
||||
/* K6 */ be_nested_str_weak(pB),
|
||||
/* K7 */ be_const_int(2),
|
||||
/* K8 */ be_nested_str_weak(cB),
|
||||
/* K9 */ be_nested_str_weak(encode),
|
||||
/* K9 */ be_nested_str_weak(tlv2raw),
|
||||
}),
|
||||
be_str_weak(encode),
|
||||
be_str_weak(tlv2raw),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[21]) { /* code */
|
||||
( &(const binstruction[22]) { /* code */
|
||||
0xB80A0000, // 0000 GETNGBL R2 K0
|
||||
0x88080501, // 0001 GETMBR R2 R2 K1
|
||||
0x8C080502, // 0002 GETMET R2 R2 K2
|
||||
@ -385,8 +385,9 @@ be_local_closure(Matter_Pake2_encode, /* name */
|
||||
0x881C0108, // 0010 GETMBR R7 R0 K8
|
||||
0x7C0C0800, // 0011 CALL R3 4
|
||||
0x8C0C0509, // 0012 GETMET R3 R2 K9
|
||||
0x7C0C0200, // 0013 CALL R3 1
|
||||
0x80040600, // 0014 RET 1 R3
|
||||
0x5C140200, // 0013 MOVE R5 R1
|
||||
0x7C0C0400, // 0014 CALL R3 2
|
||||
0x80040600, // 0015 RET 1 R3
|
||||
})
|
||||
)
|
||||
);
|
||||
@ -401,7 +402,7 @@ be_local_class(Matter_Pake2,
|
||||
NULL,
|
||||
be_nested_map(3,
|
||||
( (struct bmapnode*) &(const bmapnode[]) {
|
||||
{ be_const_key_weak(encode, -1), be_const_closure(Matter_Pake2_encode_closure) },
|
||||
{ be_const_key_weak(tlv2raw, -1), be_const_closure(Matter_Pake2_tlv2raw_closure) },
|
||||
{ be_const_key_weak(cB, -1), be_const_var(1) },
|
||||
{ be_const_key_weak(pB, 0), be_const_var(0) },
|
||||
})),
|
||||
@ -430,7 +431,7 @@ be_local_closure(Matter_Pake3_parse, /* name */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[11]) { /* constants */
|
||||
( &(const bvalue[10]) { /* constants */
|
||||
/* K0 */ be_const_int(0),
|
||||
/* K1 */ be_nested_str_weak(matter),
|
||||
/* K2 */ be_nested_str_weak(TLV),
|
||||
@ -438,10 +439,9 @@ be_local_closure(Matter_Pake3_parse, /* name */
|
||||
/* K4 */ be_nested_str_weak(tasmota),
|
||||
/* K5 */ be_nested_str_weak(log),
|
||||
/* K6 */ be_nested_str_weak(MTR_X3A_X20parsed_X20TLV_X3A_X20),
|
||||
/* K7 */ be_const_int(3),
|
||||
/* K8 */ be_nested_str_weak(cA),
|
||||
/* K9 */ be_nested_str_weak(getsubval),
|
||||
/* K10 */ be_const_int(1),
|
||||
/* K7 */ be_nested_str_weak(cA),
|
||||
/* K8 */ be_nested_str_weak(getsubval),
|
||||
/* K9 */ be_const_int(1),
|
||||
}),
|
||||
be_str_weak(parse),
|
||||
&be_const_str_solidified,
|
||||
@ -462,12 +462,12 @@ be_local_closure(Matter_Pake3_parse, /* name */
|
||||
0x5C1C0600, // 000D MOVE R7 R3
|
||||
0x7C180200, // 000E CALL R6 1
|
||||
0x001A0C06, // 000F ADD R6 K6 R6
|
||||
0x581C0007, // 0010 LDCONST R7 K7
|
||||
0x541E0003, // 0010 LDINT R7 4
|
||||
0x7C100600, // 0011 CALL R4 3
|
||||
0x8C100709, // 0012 GETMET R4 R3 K9
|
||||
0x5818000A, // 0013 LDCONST R6 K10
|
||||
0x8C100708, // 0012 GETMET R4 R3 K8
|
||||
0x58180009, // 0013 LDCONST R6 K9
|
||||
0x7C100400, // 0014 CALL R4 2
|
||||
0x90021004, // 0015 SETMBR R0 K8 R4
|
||||
0x90020E04, // 0015 SETMBR R0 K7 R4
|
||||
0x80040000, // 0016 RET 1 R0
|
||||
})
|
||||
)
|
||||
@ -503,7 +503,7 @@ extern const bclass be_class_Matter_Sigma1;
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Sigma1_parse, /* name */
|
||||
be_nested_proto(
|
||||
9, /* nstack */
|
||||
8, /* nstack */
|
||||
3, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
@ -511,7 +511,7 @@ be_local_closure(Matter_Sigma1_parse, /* name */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[21]) { /* constants */
|
||||
( &(const bvalue[23]) { /* constants */
|
||||
/* K0 */ be_const_int(0),
|
||||
/* K1 */ be_nested_str_weak(matter),
|
||||
/* K2 */ be_nested_str_weak(TLV),
|
||||
@ -521,22 +521,24 @@ be_local_closure(Matter_Sigma1_parse, /* name */
|
||||
/* K6 */ be_nested_str_weak(tasmota),
|
||||
/* K7 */ be_nested_str_weak(log),
|
||||
/* K8 */ be_nested_str_weak(MTR_X3A_X20Sigma1_X20TLV_X3D),
|
||||
/* K9 */ be_const_int(3),
|
||||
/* K10 */ be_nested_str_weak(initiatorRandom),
|
||||
/* K11 */ be_nested_str_weak(getsubval),
|
||||
/* K12 */ be_const_int(1),
|
||||
/* K13 */ be_nested_str_weak(initiator_session_id),
|
||||
/* K14 */ be_const_int(2),
|
||||
/* K15 */ be_nested_str_weak(destinationId),
|
||||
/* K9 */ be_nested_str_weak(initiatorRandom),
|
||||
/* K10 */ be_nested_str_weak(getsubval),
|
||||
/* K11 */ be_const_int(1),
|
||||
/* K12 */ be_nested_str_weak(initiator_session_id),
|
||||
/* K13 */ be_const_int(2),
|
||||
/* K14 */ be_nested_str_weak(destinationId),
|
||||
/* K15 */ be_const_int(3),
|
||||
/* K16 */ be_nested_str_weak(initiatorEphPubKey),
|
||||
/* K17 */ be_nested_str_weak(findsub),
|
||||
/* K18 */ be_nested_str_weak(SLEEPY_IDLE_INTERVAL),
|
||||
/* K19 */ be_nested_str_weak(findsubval),
|
||||
/* K20 */ be_nested_str_weak(SLEEPY_ACTIVE_INTERVAL),
|
||||
/* K21 */ be_nested_str_weak(resumptionID),
|
||||
/* K22 */ be_nested_str_weak(initiatorResumeMIC),
|
||||
}),
|
||||
be_str_weak(parse),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[58]) { /* code */
|
||||
( &(const binstruction[60]) { /* code */
|
||||
0x4C0C0000, // 0000 LDNIL R3
|
||||
0x1C0C0403, // 0001 EQ R3 R2 R3
|
||||
0x780E0000, // 0002 JMPF R3 #0004
|
||||
@ -556,21 +558,21 @@ be_local_closure(Matter_Sigma1_parse, /* name */
|
||||
0x5C1C0600, // 0010 MOVE R7 R3
|
||||
0x7C180200, // 0011 CALL R6 1
|
||||
0x001A1006, // 0012 ADD R6 K8 R6
|
||||
0x581C0009, // 0013 LDCONST R7 K9
|
||||
0x541E0003, // 0013 LDINT R7 4
|
||||
0x7C100600, // 0014 CALL R4 3
|
||||
0x8C10070B, // 0015 GETMET R4 R3 K11
|
||||
0x5818000C, // 0016 LDCONST R6 K12
|
||||
0x8C10070A, // 0015 GETMET R4 R3 K10
|
||||
0x5818000B, // 0016 LDCONST R6 K11
|
||||
0x7C100400, // 0017 CALL R4 2
|
||||
0x90021404, // 0018 SETMBR R0 K10 R4
|
||||
0x8C10070B, // 0019 GETMET R4 R3 K11
|
||||
0x5818000E, // 001A LDCONST R6 K14
|
||||
0x90021204, // 0018 SETMBR R0 K9 R4
|
||||
0x8C10070A, // 0019 GETMET R4 R3 K10
|
||||
0x5818000D, // 001A LDCONST R6 K13
|
||||
0x7C100400, // 001B CALL R4 2
|
||||
0x90021A04, // 001C SETMBR R0 K13 R4
|
||||
0x8C10070B, // 001D GETMET R4 R3 K11
|
||||
0x58180009, // 001E LDCONST R6 K9
|
||||
0x90021804, // 001C SETMBR R0 K12 R4
|
||||
0x8C10070A, // 001D GETMET R4 R3 K10
|
||||
0x5818000F, // 001E LDCONST R6 K15
|
||||
0x7C100400, // 001F CALL R4 2
|
||||
0x90021E04, // 0020 SETMBR R0 K15 R4
|
||||
0x8C10070B, // 0021 GETMET R4 R3 K11
|
||||
0x90021C04, // 0020 SETMBR R0 K14 R4
|
||||
0x8C10070A, // 0021 GETMET R4 R3 K10
|
||||
0x541A0003, // 0022 LDINT R6 4
|
||||
0x7C100400, // 0023 CALL R4 2
|
||||
0x90022004, // 0024 SETMBR R0 K16 R4
|
||||
@ -581,20 +583,22 @@ be_local_closure(Matter_Sigma1_parse, /* name */
|
||||
0x20140805, // 0029 NE R5 R4 R5
|
||||
0x78160007, // 002A JMPF R5 #0033
|
||||
0x8C140913, // 002B GETMET R5 R4 K19
|
||||
0x581C000C, // 002C LDCONST R7 K12
|
||||
0x581C000B, // 002C LDCONST R7 K11
|
||||
0x7C140400, // 002D CALL R5 2
|
||||
0x90022405, // 002E SETMBR R0 K18 R5
|
||||
0x8C140913, // 002F GETMET R5 R4 K19
|
||||
0x581C000E, // 0030 LDCONST R7 K14
|
||||
0x581C000D, // 0030 LDCONST R7 K13
|
||||
0x7C140400, // 0031 CALL R5 2
|
||||
0x90022805, // 0032 SETMBR R0 K20 R5
|
||||
0x8C140711, // 0033 GETMET R5 R3 K17
|
||||
0x8C140713, // 0033 GETMET R5 R3 K19
|
||||
0x541E0005, // 0034 LDINT R7 6
|
||||
0x7C140400, // 0035 CALL R5 2
|
||||
0x8C180711, // 0036 GETMET R6 R3 K17
|
||||
0x54220006, // 0037 LDINT R8 7
|
||||
0x7C180400, // 0038 CALL R6 2
|
||||
0x80040000, // 0039 RET 1 R0
|
||||
0x90022A05, // 0036 SETMBR R0 K21 R5
|
||||
0x8C140713, // 0037 GETMET R5 R3 K19
|
||||
0x541E0006, // 0038 LDINT R7 7
|
||||
0x7C140400, // 0039 CALL R5 2
|
||||
0x90022C05, // 003A SETMBR R0 K22 R5
|
||||
0x80040000, // 003B RET 1 R0
|
||||
})
|
||||
)
|
||||
);
|
||||
@ -633,9 +637,9 @@ void be_load_Matter_Sigma1_class(bvm *vm) {
|
||||
extern const bclass be_class_Matter_Sigma2;
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: encode
|
||||
** Solidified function: tlv2raw
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Sigma2_encode, /* name */
|
||||
be_local_closure(Matter_Sigma2_tlv2raw, /* name */
|
||||
be_nested_proto(
|
||||
9, /* nstack */
|
||||
2, /* argc */
|
||||
@ -663,11 +667,11 @@ be_local_closure(Matter_Sigma2_encode, /* name */
|
||||
/* K14 */ be_nested_str_weak(SLEEPY_ACTIVE_INTERVAL),
|
||||
/* K15 */ be_nested_str_weak(add_struct),
|
||||
/* K16 */ be_nested_str_weak(U4),
|
||||
/* K17 */ be_nested_str_weak(encode),
|
||||
/* K17 */ be_nested_str_weak(tlv2raw),
|
||||
}),
|
||||
be_str_weak(encode),
|
||||
be_str_weak(tlv2raw),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[60]) { /* code */
|
||||
( &(const binstruction[61]) { /* code */
|
||||
0xB80A0000, // 0000 GETNGBL R2 K0
|
||||
0x88080501, // 0001 GETMBR R2 R2 K1
|
||||
0x8C080502, // 0002 GETMET R2 R2 K2
|
||||
@ -726,8 +730,9 @@ be_local_closure(Matter_Sigma2_encode, /* name */
|
||||
0x8820010E, // 0037 GETMBR R8 R0 K14
|
||||
0x7C100800, // 0038 CALL R4 4
|
||||
0x8C0C0511, // 0039 GETMET R3 R2 K17
|
||||
0x7C0C0200, // 003A CALL R3 1
|
||||
0x80040600, // 003B RET 1 R3
|
||||
0x5C140200, // 003A MOVE R5 R1
|
||||
0x7C0C0400, // 003B CALL R3 2
|
||||
0x80040600, // 003C RET 1 R3
|
||||
})
|
||||
)
|
||||
);
|
||||
@ -742,13 +747,13 @@ be_local_class(Matter_Sigma2,
|
||||
NULL,
|
||||
be_nested_map(7,
|
||||
( (struct bmapnode*) &(const bmapnode[]) {
|
||||
{ be_const_key_weak(encrypted2, -1), be_const_var(3) },
|
||||
{ be_const_key_weak(encode, -1), be_const_closure(Matter_Sigma2_encode_closure) },
|
||||
{ be_const_key_weak(tlv2raw, -1), be_const_closure(Matter_Sigma2_tlv2raw_closure) },
|
||||
{ be_const_key_weak(responderEphPubKey, 3), be_const_var(2) },
|
||||
{ be_const_key_weak(responderSessionId, -1), be_const_var(1) },
|
||||
{ be_const_key_weak(SLEEPY_IDLE_INTERVAL, 6), be_const_var(4) },
|
||||
{ be_const_key_weak(SLEEPY_ACTIVE_INTERVAL, 0), be_const_var(5) },
|
||||
{ be_const_key_weak(responderRandom, 3), be_const_var(0) },
|
||||
{ be_const_key_weak(responderEphPubKey, -1), be_const_var(2) },
|
||||
{ be_const_key_weak(SLEEPY_IDLE_INTERVAL, -1), be_const_var(4) },
|
||||
{ be_const_key_weak(SLEEPY_ACTIVE_INTERVAL, 6), be_const_var(5) },
|
||||
{ be_const_key_weak(responderRandom, 1), be_const_var(0) },
|
||||
{ be_const_key_weak(encrypted2, -1), be_const_var(3) },
|
||||
})),
|
||||
be_str_weak(Matter_Sigma2)
|
||||
);
|
||||
@ -763,9 +768,9 @@ void be_load_Matter_Sigma2_class(bvm *vm) {
|
||||
extern const bclass be_class_Matter_Sigma2Resume;
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: encode
|
||||
** Solidified function: tlv2raw
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Sigma2Resume_encode, /* name */
|
||||
be_local_closure(Matter_Sigma2Resume_tlv2raw, /* name */
|
||||
be_nested_proto(
|
||||
9, /* nstack */
|
||||
2, /* argc */
|
||||
@ -775,7 +780,7 @@ be_local_closure(Matter_Sigma2Resume_encode, /* name */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[16]) { /* constants */
|
||||
( &(const bvalue[17]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(matter),
|
||||
/* K1 */ be_nested_str_weak(TLV),
|
||||
/* K2 */ be_nested_str_weak(Matter_TLV_struct),
|
||||
@ -786,16 +791,17 @@ be_local_closure(Matter_Sigma2Resume_encode, /* name */
|
||||
/* K7 */ be_const_int(2),
|
||||
/* K8 */ be_nested_str_weak(sigma2ResumeMIC),
|
||||
/* K9 */ be_const_int(3),
|
||||
/* K10 */ be_nested_str_weak(responderSessionID),
|
||||
/* K11 */ be_nested_str_weak(SLEEPY_IDLE_INTERVAL),
|
||||
/* K12 */ be_nested_str_weak(SLEEPY_ACTIVE_INTERVAL),
|
||||
/* K13 */ be_nested_str_weak(add_struct),
|
||||
/* K14 */ be_nested_str_weak(U4),
|
||||
/* K15 */ be_nested_str_weak(encode),
|
||||
/* K10 */ be_nested_str_weak(U2),
|
||||
/* K11 */ be_nested_str_weak(responderSessionID),
|
||||
/* K12 */ be_nested_str_weak(SLEEPY_IDLE_INTERVAL),
|
||||
/* K13 */ be_nested_str_weak(SLEEPY_ACTIVE_INTERVAL),
|
||||
/* K14 */ be_nested_str_weak(add_struct),
|
||||
/* K15 */ be_nested_str_weak(U4),
|
||||
/* K16 */ be_nested_str_weak(tlv2raw),
|
||||
}),
|
||||
be_str_weak(encode),
|
||||
be_str_weak(tlv2raw),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[53]) { /* code */
|
||||
( &(const binstruction[54]) { /* code */
|
||||
0xB80A0000, // 0000 GETNGBL R2 K0
|
||||
0x88080501, // 0001 GETMBR R2 R2 K1
|
||||
0x8C080502, // 0002 GETMET R2 R2 K2
|
||||
@ -818,37 +824,38 @@ be_local_closure(Matter_Sigma2Resume_encode, /* name */
|
||||
0x58140009, // 0013 LDCONST R5 K9
|
||||
0xB81A0000, // 0014 GETNGBL R6 K0
|
||||
0x88180D01, // 0015 GETMBR R6 R6 K1
|
||||
0x88180D05, // 0016 GETMBR R6 R6 K5
|
||||
0x881C010A, // 0017 GETMBR R7 R0 K10
|
||||
0x88180D0A, // 0016 GETMBR R6 R6 K10
|
||||
0x881C010B, // 0017 GETMBR R7 R0 K11
|
||||
0x7C0C0800, // 0018 CALL R3 4
|
||||
0x880C010B, // 0019 GETMBR R3 R0 K11
|
||||
0x880C010C, // 0019 GETMBR R3 R0 K12
|
||||
0x4C100000, // 001A LDNIL R4
|
||||
0x200C0604, // 001B NE R3 R3 R4
|
||||
0x740E0003, // 001C JMPT R3 #0021
|
||||
0x880C010C, // 001D GETMBR R3 R0 K12
|
||||
0x880C010D, // 001D GETMBR R3 R0 K13
|
||||
0x4C100000, // 001E LDNIL R4
|
||||
0x200C0604, // 001F NE R3 R3 R4
|
||||
0x780E0010, // 0020 JMPF R3 #0032
|
||||
0x8C0C050D, // 0021 GETMET R3 R2 K13
|
||||
0x8C0C050E, // 0021 GETMET R3 R2 K14
|
||||
0x54160003, // 0022 LDINT R5 4
|
||||
0x7C0C0400, // 0023 CALL R3 2
|
||||
0x8C100703, // 0024 GETMET R4 R3 K3
|
||||
0x58180004, // 0025 LDCONST R6 K4
|
||||
0xB81E0000, // 0026 GETNGBL R7 K0
|
||||
0x881C0F01, // 0027 GETMBR R7 R7 K1
|
||||
0x881C0F0E, // 0028 GETMBR R7 R7 K14
|
||||
0x8820010B, // 0029 GETMBR R8 R0 K11
|
||||
0x881C0F0F, // 0028 GETMBR R7 R7 K15
|
||||
0x8820010C, // 0029 GETMBR R8 R0 K12
|
||||
0x7C100800, // 002A CALL R4 4
|
||||
0x8C100703, // 002B GETMET R4 R3 K3
|
||||
0x58180007, // 002C LDCONST R6 K7
|
||||
0xB81E0000, // 002D GETNGBL R7 K0
|
||||
0x881C0F01, // 002E GETMBR R7 R7 K1
|
||||
0x881C0F0E, // 002F GETMBR R7 R7 K14
|
||||
0x8820010C, // 0030 GETMBR R8 R0 K12
|
||||
0x881C0F0F, // 002F GETMBR R7 R7 K15
|
||||
0x8820010D, // 0030 GETMBR R8 R0 K13
|
||||
0x7C100800, // 0031 CALL R4 4
|
||||
0x8C0C050F, // 0032 GETMET R3 R2 K15
|
||||
0x7C0C0200, // 0033 CALL R3 1
|
||||
0x80040600, // 0034 RET 1 R3
|
||||
0x8C0C0510, // 0032 GETMET R3 R2 K16
|
||||
0x5C140200, // 0033 MOVE R5 R1
|
||||
0x7C0C0400, // 0034 CALL R3 2
|
||||
0x80040600, // 0035 RET 1 R3
|
||||
})
|
||||
)
|
||||
);
|
||||
@ -868,7 +875,7 @@ be_local_class(Matter_Sigma2Resume,
|
||||
{ be_const_key_weak(sigma2ResumeMIC, -1), be_const_var(1) },
|
||||
{ be_const_key_weak(responderSessionID, 1), be_const_var(2) },
|
||||
{ be_const_key_weak(SLEEPY_ACTIVE_INTERVAL, -1), be_const_var(4) },
|
||||
{ be_const_key_weak(encode, -1), be_const_closure(Matter_Sigma2Resume_encode_closure) },
|
||||
{ be_const_key_weak(tlv2raw, -1), be_const_closure(Matter_Sigma2Resume_tlv2raw_closure) },
|
||||
})),
|
||||
be_str_weak(Matter_Sigma2Resume)
|
||||
);
|
||||
@ -895,7 +902,7 @@ be_local_closure(Matter_Sigma3_parse, /* name */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[13]) { /* constants */
|
||||
( &(const bvalue[12]) { /* constants */
|
||||
/* K0 */ be_const_int(0),
|
||||
/* K1 */ be_nested_str_weak(matter),
|
||||
/* K2 */ be_nested_str_weak(TLV),
|
||||
@ -905,10 +912,9 @@ be_local_closure(Matter_Sigma3_parse, /* name */
|
||||
/* K6 */ be_nested_str_weak(tasmota),
|
||||
/* K7 */ be_nested_str_weak(log),
|
||||
/* K8 */ be_nested_str_weak(MTR_X3A_X20Sigma3_X20TLV_X3D),
|
||||
/* K9 */ be_const_int(3),
|
||||
/* K10 */ be_nested_str_weak(TBEData3Encrypted),
|
||||
/* K11 */ be_nested_str_weak(getsubval),
|
||||
/* K12 */ be_const_int(1),
|
||||
/* K9 */ be_nested_str_weak(TBEData3Encrypted),
|
||||
/* K10 */ be_nested_str_weak(getsubval),
|
||||
/* K11 */ be_const_int(1),
|
||||
}),
|
||||
be_str_weak(parse),
|
||||
&be_const_str_solidified,
|
||||
@ -932,12 +938,12 @@ be_local_closure(Matter_Sigma3_parse, /* name */
|
||||
0x5C1C0600, // 0010 MOVE R7 R3
|
||||
0x7C180200, // 0011 CALL R6 1
|
||||
0x001A1006, // 0012 ADD R6 K8 R6
|
||||
0x581C0009, // 0013 LDCONST R7 K9
|
||||
0x541E0003, // 0013 LDINT R7 4
|
||||
0x7C100600, // 0014 CALL R4 3
|
||||
0x8C10070B, // 0015 GETMET R4 R3 K11
|
||||
0x5818000C, // 0016 LDCONST R6 K12
|
||||
0x8C10070A, // 0015 GETMET R4 R3 K10
|
||||
0x5818000B, // 0016 LDCONST R6 K11
|
||||
0x7C100400, // 0017 CALL R4 2
|
||||
0x90021404, // 0018 SETMBR R0 K10 R4
|
||||
0x90021204, // 0018 SETMBR R0 K9 R4
|
||||
0x80040000, // 0019 RET 1 R0
|
||||
})
|
||||
)
|
||||
|
@ -0,0 +1,246 @@
|
||||
/* Solidification of Matter_Control_Message.h */
|
||||
/********************************************************************\
|
||||
* Generated code, don't edit *
|
||||
\********************************************************************/
|
||||
#include "be_constobj.h"
|
||||
|
||||
extern const bclass be_class_Matter_Control_Message;
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: parse_MsgCounterSyncRsp
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Control_Message_parse_MsgCounterSyncRsp, /* name */
|
||||
be_nested_proto(
|
||||
11, /* nstack */
|
||||
2, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[11]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(string),
|
||||
/* K1 */ be_nested_str_weak(session),
|
||||
/* K2 */ be_nested_str_weak(tasmota),
|
||||
/* K3 */ be_nested_str_weak(log),
|
||||
/* K4 */ be_nested_str_weak(format),
|
||||
/* K5 */ be_nested_str_weak(MTR_X3A_X20_X3EMCSyncRsp_X20_X2A_X20Not_X20implemented_X20_X25s),
|
||||
/* K6 */ be_nested_str_weak(raw),
|
||||
/* K7 */ be_nested_str_weak(app_payload_idx),
|
||||
/* K8 */ be_const_int(2147483647),
|
||||
/* K9 */ be_nested_str_weak(tohex),
|
||||
/* K10 */ be_const_int(2),
|
||||
}),
|
||||
be_str_weak(parse_MsgCounterSyncRsp),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[17]) { /* code */
|
||||
0xA40A0000, // 0000 IMPORT R2 K0
|
||||
0x880C0301, // 0001 GETMBR R3 R1 K1
|
||||
0xB8120400, // 0002 GETNGBL R4 K2
|
||||
0x8C100903, // 0003 GETMET R4 R4 K3
|
||||
0x8C180504, // 0004 GETMET R6 R2 K4
|
||||
0x58200005, // 0005 LDCONST R8 K5
|
||||
0x88240307, // 0006 GETMBR R9 R1 K7
|
||||
0x40241308, // 0007 CONNECT R9 R9 K8
|
||||
0x88280306, // 0008 GETMBR R10 R1 K6
|
||||
0x94241409, // 0009 GETIDX R9 R10 R9
|
||||
0x8C241309, // 000A GETMET R9 R9 K9
|
||||
0x7C240200, // 000B CALL R9 1
|
||||
0x7C180600, // 000C CALL R6 3
|
||||
0x581C000A, // 000D LDCONST R7 K10
|
||||
0x7C100600, // 000E CALL R4 3
|
||||
0x50100000, // 000F LDBOOL R4 0 0
|
||||
0x80040800, // 0010 RET 1 R4
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: parse_MsgCounterSyncReq
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Control_Message_parse_MsgCounterSyncReq, /* name */
|
||||
be_nested_proto(
|
||||
11, /* nstack */
|
||||
2, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[11]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(string),
|
||||
/* K1 */ be_nested_str_weak(session),
|
||||
/* K2 */ be_nested_str_weak(tasmota),
|
||||
/* K3 */ be_nested_str_weak(log),
|
||||
/* K4 */ be_nested_str_weak(format),
|
||||
/* K5 */ be_nested_str_weak(MTR_X3A_X20_X3EMCSyncReq_X20_X2A_X20Not_X20implemented_X20_X25s),
|
||||
/* K6 */ be_nested_str_weak(raw),
|
||||
/* K7 */ be_nested_str_weak(app_payload_idx),
|
||||
/* K8 */ be_const_int(2147483647),
|
||||
/* K9 */ be_nested_str_weak(tohex),
|
||||
/* K10 */ be_const_int(2),
|
||||
}),
|
||||
be_str_weak(parse_MsgCounterSyncReq),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[17]) { /* code */
|
||||
0xA40A0000, // 0000 IMPORT R2 K0
|
||||
0x880C0301, // 0001 GETMBR R3 R1 K1
|
||||
0xB8120400, // 0002 GETNGBL R4 K2
|
||||
0x8C100903, // 0003 GETMET R4 R4 K3
|
||||
0x8C180504, // 0004 GETMET R6 R2 K4
|
||||
0x58200005, // 0005 LDCONST R8 K5
|
||||
0x88240307, // 0006 GETMBR R9 R1 K7
|
||||
0x40241308, // 0007 CONNECT R9 R9 K8
|
||||
0x88280306, // 0008 GETMBR R10 R1 K6
|
||||
0x94241409, // 0009 GETIDX R9 R10 R9
|
||||
0x8C241309, // 000A GETMET R9 R9 K9
|
||||
0x7C240200, // 000B CALL R9 1
|
||||
0x7C180600, // 000C CALL R6 3
|
||||
0x581C000A, // 000D LDCONST R7 K10
|
||||
0x7C100600, // 000E CALL R4 3
|
||||
0x50100000, // 000F LDBOOL R4 0 0
|
||||
0x80040800, // 0010 RET 1 R4
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: init
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Control_Message_init, /* name */
|
||||
be_nested_proto(
|
||||
4, /* nstack */
|
||||
2, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 3]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(crypto),
|
||||
/* K1 */ be_nested_str_weak(responder),
|
||||
/* K2 */ be_nested_str_weak(device),
|
||||
}),
|
||||
be_str_weak(init),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 5]) { /* code */
|
||||
0xA40A0000, // 0000 IMPORT R2 K0
|
||||
0x90020201, // 0001 SETMBR R0 K1 R1
|
||||
0x880C0302, // 0002 GETMBR R3 R1 K2
|
||||
0x90020403, // 0003 SETMBR R0 K2 R3
|
||||
0x80000000, // 0004 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: process_incoming_control_message
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Control_Message_process_incoming_control_message, /* name */
|
||||
be_nested_proto(
|
||||
9, /* nstack */
|
||||
2, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[14]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(tasmota),
|
||||
/* K1 */ be_nested_str_weak(log),
|
||||
/* K2 */ be_nested_str_weak(MTR_X3A_X20received_X20control_X20message_X20),
|
||||
/* K3 */ be_nested_str_weak(matter),
|
||||
/* K4 */ be_nested_str_weak(inspect),
|
||||
/* K5 */ be_const_int(2),
|
||||
/* K6 */ be_nested_str_weak(opcode),
|
||||
/* K7 */ be_const_int(0),
|
||||
/* K8 */ be_nested_str_weak(parse_MsgCounterSyncReq),
|
||||
/* K9 */ be_const_int(1),
|
||||
/* K10 */ be_nested_str_weak(parse_MsgCounterSyncRsp),
|
||||
/* K11 */ be_nested_str_weak(string),
|
||||
/* K12 */ be_nested_str_weak(format),
|
||||
/* K13 */ be_nested_str_weak(MTR_X3A_X20_X3E_X3F_X3F_X3F_X3F_X3F_X3F_X3F_X3F_X3F_X20Unknown_X20OpCode_X20_X28control_X20message_X29_X20_X2502X),
|
||||
}),
|
||||
be_str_weak(process_incoming_control_message),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[38]) { /* code */
|
||||
0xB80A0000, // 0000 GETNGBL R2 K0
|
||||
0x8C080501, // 0001 GETMET R2 R2 K1
|
||||
0xB8120600, // 0002 GETNGBL R4 K3
|
||||
0x8C100904, // 0003 GETMET R4 R4 K4
|
||||
0x5C180200, // 0004 MOVE R6 R1
|
||||
0x7C100400, // 0005 CALL R4 2
|
||||
0x00120404, // 0006 ADD R4 K2 R4
|
||||
0x58140005, // 0007 LDCONST R5 K5
|
||||
0x7C080600, // 0008 CALL R2 3
|
||||
0x88080306, // 0009 GETMBR R2 R1 K6
|
||||
0x1C080507, // 000A EQ R2 R2 K7
|
||||
0x780A0004, // 000B JMPF R2 #0011
|
||||
0x8C080108, // 000C GETMET R2 R0 K8
|
||||
0x5C100200, // 000D MOVE R4 R1
|
||||
0x7C080400, // 000E CALL R2 2
|
||||
0x80040400, // 000F RET 1 R2
|
||||
0x70020012, // 0010 JMP #0024
|
||||
0x88080306, // 0011 GETMBR R2 R1 K6
|
||||
0x1C080509, // 0012 EQ R2 R2 K9
|
||||
0x780A0004, // 0013 JMPF R2 #0019
|
||||
0x8C08010A, // 0014 GETMET R2 R0 K10
|
||||
0x5C100200, // 0015 MOVE R4 R1
|
||||
0x7C080400, // 0016 CALL R2 2
|
||||
0x80040400, // 0017 RET 1 R2
|
||||
0x7002000A, // 0018 JMP #0024
|
||||
0xA40A1600, // 0019 IMPORT R2 K11
|
||||
0xB80E0000, // 001A GETNGBL R3 K0
|
||||
0x8C0C0701, // 001B GETMET R3 R3 K1
|
||||
0x8C14050C, // 001C GETMET R5 R2 K12
|
||||
0x581C000D, // 001D LDCONST R7 K13
|
||||
0x88200306, // 001E GETMBR R8 R1 K6
|
||||
0x7C140600, // 001F CALL R5 3
|
||||
0x58180005, // 0020 LDCONST R6 K5
|
||||
0x7C0C0600, // 0021 CALL R3 3
|
||||
0x500C0000, // 0022 LDBOOL R3 0 0
|
||||
0x80040600, // 0023 RET 1 R3
|
||||
0x50080000, // 0024 LDBOOL R2 0 0
|
||||
0x80040400, // 0025 RET 1 R2
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified class: Matter_Control_Message
|
||||
********************************************************************/
|
||||
be_local_class(Matter_Control_Message,
|
||||
2,
|
||||
NULL,
|
||||
be_nested_map(6,
|
||||
( (struct bmapnode*) &(const bmapnode[]) {
|
||||
{ be_const_key_weak(parse_MsgCounterSyncRsp, -1), be_const_closure(Matter_Control_Message_parse_MsgCounterSyncRsp_closure) },
|
||||
{ be_const_key_weak(responder, 2), be_const_var(0) },
|
||||
{ be_const_key_weak(parse_MsgCounterSyncReq, -1), be_const_closure(Matter_Control_Message_parse_MsgCounterSyncReq_closure) },
|
||||
{ be_const_key_weak(init, 4), be_const_closure(Matter_Control_Message_init_closure) },
|
||||
{ be_const_key_weak(device, -1), be_const_var(1) },
|
||||
{ be_const_key_weak(process_incoming_control_message, -1), be_const_closure(Matter_Control_Message_process_incoming_control_message_closure) },
|
||||
})),
|
||||
be_str_weak(Matter_Control_Message)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
void be_load_Matter_Control_Message_class(bvm *vm) {
|
||||
be_pushntvclass(vm, &be_class_Matter_Control_Message);
|
||||
be_setglobal(vm, "Matter_Control_Message");
|
||||
be_pop(vm, 1);
|
||||
}
|
||||
/********************************************************************/
|
||||
/* End of solidification */
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,771 @@
|
||||
/* Solidification of Matter_Expirable.h */
|
||||
/********************************************************************\
|
||||
* Generated code, don't edit *
|
||||
\********************************************************************/
|
||||
#include "be_constobj.h"
|
||||
|
||||
extern const bclass be_class_Matter_Expirable;
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: before_remove
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_before_remove, /* name */
|
||||
be_nested_proto(
|
||||
1, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
0, /* has constants */
|
||||
NULL, /* no const */
|
||||
be_str_weak(before_remove),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 1]) { /* code */
|
||||
0x80000000, // 0000 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: set_no_expiration
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_set_no_expiration, /* name */
|
||||
be_nested_proto(
|
||||
2, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(_expiration),
|
||||
}),
|
||||
be_str_weak(set_no_expiration),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 3]) { /* code */
|
||||
0x4C040000, // 0000 LDNIL R1
|
||||
0x90020001, // 0001 SETMBR R0 K0 R1
|
||||
0x80000000, // 0002 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: init
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_init, /* name */
|
||||
be_nested_proto(
|
||||
2, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(_persist),
|
||||
}),
|
||||
be_str_weak(init),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 3]) { /* code */
|
||||
0x50040000, // 0000 LDBOOL R1 0 0
|
||||
0x90020001, // 0001 SETMBR R0 K0 R1
|
||||
0x80000000, // 0002 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: set_expire_time
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_set_expire_time, /* name */
|
||||
be_nested_proto(
|
||||
4, /* nstack */
|
||||
2, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(_expiration),
|
||||
}),
|
||||
be_str_weak(set_expire_time),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 5]) { /* code */
|
||||
0x60080009, // 0000 GETGBL R2 G9
|
||||
0x5C0C0200, // 0001 MOVE R3 R1
|
||||
0x7C080200, // 0002 CALL R2 1
|
||||
0x90020002, // 0003 SETMBR R0 K0 R2
|
||||
0x80000000, // 0004 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: has_expired
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_has_expired, /* name */
|
||||
be_nested_proto(
|
||||
4, /* nstack */
|
||||
2, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 4]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(tasmota),
|
||||
/* K1 */ be_nested_str_weak(rtc),
|
||||
/* K2 */ be_nested_str_weak(utc),
|
||||
/* K3 */ be_nested_str_weak(_expiration),
|
||||
}),
|
||||
be_str_weak(has_expired),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[16]) { /* code */
|
||||
0x4C080000, // 0000 LDNIL R2
|
||||
0x1C080202, // 0001 EQ R2 R1 R2
|
||||
0x780A0003, // 0002 JMPF R2 #0007
|
||||
0xB80A0000, // 0003 GETNGBL R2 K0
|
||||
0x8C080501, // 0004 GETMET R2 R2 K1
|
||||
0x7C080200, // 0005 CALL R2 1
|
||||
0x94040502, // 0006 GETIDX R1 R2 K2
|
||||
0x88080103, // 0007 GETMBR R2 R0 K3
|
||||
0x4C0C0000, // 0008 LDNIL R3
|
||||
0x20080403, // 0009 NE R2 R2 R3
|
||||
0x780A0002, // 000A JMPF R2 #000E
|
||||
0x88080103, // 000B GETMBR R2 R0 K3
|
||||
0x28080202, // 000C GE R2 R1 R2
|
||||
0x80040400, // 000D RET 1 R2
|
||||
0x50080000, // 000E LDBOOL R2 0 0
|
||||
0x80040400, // 000F RET 1 R2
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: set_parent_list
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_set_parent_list, /* name */
|
||||
be_nested_proto(
|
||||
2, /* nstack */
|
||||
2, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(_list),
|
||||
}),
|
||||
be_str_weak(set_parent_list),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 2]) { /* code */
|
||||
0x90020001, // 0000 SETMBR R0 K0 R1
|
||||
0x80000000, // 0001 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: hydrate_post
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_hydrate_post, /* name */
|
||||
be_nested_proto(
|
||||
1, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
0, /* has constants */
|
||||
NULL, /* no const */
|
||||
be_str_weak(hydrate_post),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 1]) { /* code */
|
||||
0x80000000, // 0000 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: set_expire_in_seconds
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_set_expire_in_seconds, /* name */
|
||||
be_nested_proto(
|
||||
6, /* nstack */
|
||||
3, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 4]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(tasmota),
|
||||
/* K1 */ be_nested_str_weak(rtc),
|
||||
/* K2 */ be_nested_str_weak(utc),
|
||||
/* K3 */ be_nested_str_weak(set_expire_time),
|
||||
}),
|
||||
be_str_weak(set_expire_in_seconds),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[15]) { /* code */
|
||||
0x4C0C0000, // 0000 LDNIL R3
|
||||
0x1C0C0203, // 0001 EQ R3 R1 R3
|
||||
0x780E0000, // 0002 JMPF R3 #0004
|
||||
0x80000600, // 0003 RET 0
|
||||
0x4C0C0000, // 0004 LDNIL R3
|
||||
0x1C0C0403, // 0005 EQ R3 R2 R3
|
||||
0x780E0003, // 0006 JMPF R3 #000B
|
||||
0xB80E0000, // 0007 GETNGBL R3 K0
|
||||
0x8C0C0701, // 0008 GETMET R3 R3 K1
|
||||
0x7C0C0200, // 0009 CALL R3 1
|
||||
0x94080702, // 000A GETIDX R2 R3 K2
|
||||
0x8C0C0103, // 000B GETMET R3 R0 K3
|
||||
0x00140401, // 000C ADD R5 R2 R1
|
||||
0x7C0C0400, // 000D CALL R3 2
|
||||
0x80000000, // 000E RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: get_parent_list
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_get_parent_list, /* name */
|
||||
be_nested_proto(
|
||||
2, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(_list),
|
||||
}),
|
||||
be_str_weak(get_parent_list),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 2]) { /* code */
|
||||
0x88040100, // 0000 GETMBR R1 R0 K0
|
||||
0x80040200, // 0001 RET 1 R1
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: does_persist
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_does_persist, /* name */
|
||||
be_nested_proto(
|
||||
2, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(_persist),
|
||||
}),
|
||||
be_str_weak(does_persist),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 2]) { /* code */
|
||||
0x88040100, // 0000 GETMBR R1 R0 K0
|
||||
0x80040200, // 0001 RET 1 R1
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: set_persist
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_set_persist, /* name */
|
||||
be_nested_proto(
|
||||
4, /* nstack */
|
||||
2, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(_persist),
|
||||
}),
|
||||
be_str_weak(set_persist),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 5]) { /* code */
|
||||
0x60080017, // 0000 GETGBL R2 G23
|
||||
0x5C0C0200, // 0001 MOVE R3 R1
|
||||
0x7C080200, // 0002 CALL R2 1
|
||||
0x90020002, // 0003 SETMBR R0 K0 R2
|
||||
0x80000000, // 0004 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: persist_pre
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_persist_pre, /* name */
|
||||
be_nested_proto(
|
||||
1, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
0, /* has constants */
|
||||
NULL, /* no const */
|
||||
be_str_weak(persist_pre),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 1]) { /* code */
|
||||
0x80000000, // 0000 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: persist_post
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_persist_post, /* name */
|
||||
be_nested_proto(
|
||||
1, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
0, /* has constants */
|
||||
NULL, /* no const */
|
||||
be_str_weak(persist_post),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 1]) { /* code */
|
||||
0x80000000, // 0000 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified class: Matter_Expirable
|
||||
********************************************************************/
|
||||
be_local_class(Matter_Expirable,
|
||||
3,
|
||||
NULL,
|
||||
be_nested_map(16,
|
||||
( (struct bmapnode*) &(const bmapnode[]) {
|
||||
{ be_const_key_weak(_expiration, -1), be_const_var(2) },
|
||||
{ be_const_key_weak(set_no_expiration, 9), be_const_closure(Matter_Expirable_set_no_expiration_closure) },
|
||||
{ be_const_key_weak(persist_post, -1), be_const_closure(Matter_Expirable_persist_post_closure) },
|
||||
{ be_const_key_weak(init, -1), be_const_closure(Matter_Expirable_init_closure) },
|
||||
{ be_const_key_weak(has_expired, -1), be_const_closure(Matter_Expirable_has_expired_closure) },
|
||||
{ be_const_key_weak(set_expire_time, 6), be_const_closure(Matter_Expirable_set_expire_time_closure) },
|
||||
{ be_const_key_weak(set_parent_list, 4), be_const_closure(Matter_Expirable_set_parent_list_closure) },
|
||||
{ be_const_key_weak(hydrate_post, -1), be_const_closure(Matter_Expirable_hydrate_post_closure) },
|
||||
{ be_const_key_weak(set_expire_in_seconds, -1), be_const_closure(Matter_Expirable_set_expire_in_seconds_closure) },
|
||||
{ be_const_key_weak(get_parent_list, 8), be_const_closure(Matter_Expirable_get_parent_list_closure) },
|
||||
{ be_const_key_weak(_list, -1), be_const_var(0) },
|
||||
{ be_const_key_weak(does_persist, -1), be_const_closure(Matter_Expirable_does_persist_closure) },
|
||||
{ be_const_key_weak(set_persist, -1), be_const_closure(Matter_Expirable_set_persist_closure) },
|
||||
{ be_const_key_weak(persist_pre, -1), be_const_closure(Matter_Expirable_persist_pre_closure) },
|
||||
{ be_const_key_weak(_persist, 2), be_const_var(1) },
|
||||
{ be_const_key_weak(before_remove, 0), be_const_closure(Matter_Expirable_before_remove_closure) },
|
||||
})),
|
||||
be_str_weak(Matter_Expirable)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
void be_load_Matter_Expirable_class(bvm *vm) {
|
||||
be_pushntvclass(vm, &be_class_Matter_Expirable);
|
||||
be_setglobal(vm, "Matter_Expirable");
|
||||
be_pop(vm, 1);
|
||||
}
|
||||
|
||||
extern const bclass be_class_Matter_Expirable_list;
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: count_persistables
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_list_count_persistables, /* name */
|
||||
be_nested_proto(
|
||||
5, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 3]) { /* constants */
|
||||
/* K0 */ be_const_int(0),
|
||||
/* K1 */ be_nested_str_weak(_persist),
|
||||
/* K2 */ be_const_int(1),
|
||||
}),
|
||||
be_str_weak(count_persistables),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[14]) { /* code */
|
||||
0x58040000, // 0000 LDCONST R1 K0
|
||||
0x58080000, // 0001 LDCONST R2 K0
|
||||
0x600C000C, // 0002 GETGBL R3 G12
|
||||
0x5C100000, // 0003 MOVE R4 R0
|
||||
0x7C0C0200, // 0004 CALL R3 1
|
||||
0x140C0403, // 0005 LT R3 R2 R3
|
||||
0x780E0005, // 0006 JMPF R3 #000D
|
||||
0x940C0002, // 0007 GETIDX R3 R0 R2
|
||||
0x880C0701, // 0008 GETMBR R3 R3 K1
|
||||
0x780E0000, // 0009 JMPF R3 #000B
|
||||
0x00040302, // 000A ADD R1 R1 K2
|
||||
0x00080502, // 000B ADD R2 R2 K2
|
||||
0x7001FFF4, // 000C JMP #0002
|
||||
0x80040200, // 000D RET 1 R1
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: remove
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_list_remove, /* name */
|
||||
be_nested_proto(
|
||||
5, /* nstack */
|
||||
2, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 3]) { /* constants */
|
||||
/* K0 */ be_const_int(0),
|
||||
/* K1 */ be_nested_str_weak(before_remove),
|
||||
/* K2 */ be_nested_str_weak(remove),
|
||||
}),
|
||||
be_str_weak(remove),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[17]) { /* code */
|
||||
0x28080300, // 0000 GE R2 R1 K0
|
||||
0x780A0007, // 0001 JMPF R2 #000A
|
||||
0x6008000C, // 0002 GETGBL R2 G12
|
||||
0x5C0C0000, // 0003 MOVE R3 R0
|
||||
0x7C080200, // 0004 CALL R2 1
|
||||
0x14080202, // 0005 LT R2 R1 R2
|
||||
0x780A0002, // 0006 JMPF R2 #000A
|
||||
0x94080001, // 0007 GETIDX R2 R0 R1
|
||||
0x8C080501, // 0008 GETMET R2 R2 K1
|
||||
0x7C080200, // 0009 CALL R2 1
|
||||
0x60080003, // 000A GETGBL R2 G3
|
||||
0x5C0C0000, // 000B MOVE R3 R0
|
||||
0x7C080200, // 000C CALL R2 1
|
||||
0x8C080502, // 000D GETMET R2 R2 K2
|
||||
0x5C100200, // 000E MOVE R4 R1
|
||||
0x7C080400, // 000F CALL R2 2
|
||||
0x80040400, // 0010 RET 1 R2
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: push
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_list_push, /* name */
|
||||
be_nested_proto(
|
||||
5, /* nstack */
|
||||
2, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 6]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(matter),
|
||||
/* K1 */ be_nested_str_weak(Expirable),
|
||||
/* K2 */ be_nested_str_weak(type_error),
|
||||
/* K3 */ be_nested_str_weak(argument_X20must_X20be_X20of_X20class_X20_X27Expirable_X27),
|
||||
/* K4 */ be_nested_str_weak(set_parent_list),
|
||||
/* K5 */ be_nested_str_weak(push),
|
||||
}),
|
||||
be_str_weak(push),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[17]) { /* code */
|
||||
0x6008000F, // 0000 GETGBL R2 G15
|
||||
0x5C0C0200, // 0001 MOVE R3 R1
|
||||
0xB8120000, // 0002 GETNGBL R4 K0
|
||||
0x88100901, // 0003 GETMBR R4 R4 K1
|
||||
0x7C080400, // 0004 CALL R2 2
|
||||
0x740A0000, // 0005 JMPT R2 #0007
|
||||
0xB0060503, // 0006 RAISE 1 K2 K3
|
||||
0x8C080304, // 0007 GETMET R2 R1 K4
|
||||
0x5C100000, // 0008 MOVE R4 R0
|
||||
0x7C080400, // 0009 CALL R2 2
|
||||
0x60080003, // 000A GETGBL R2 G3
|
||||
0x5C0C0000, // 000B MOVE R3 R0
|
||||
0x7C080200, // 000C CALL R2 1
|
||||
0x8C080505, // 000D GETMET R2 R2 K5
|
||||
0x5C100200, // 000E MOVE R4 R1
|
||||
0x7C080400, // 000F CALL R2 2
|
||||
0x80040400, // 0010 RET 1 R2
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: every_second
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_list_every_second, /* name */
|
||||
be_nested_proto(
|
||||
3, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(remove_expired),
|
||||
}),
|
||||
be_str_weak(every_second),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 3]) { /* code */
|
||||
0x8C040100, // 0000 GETMET R1 R0 K0
|
||||
0x7C040200, // 0001 CALL R1 1
|
||||
0x80000000, // 0002 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: remove_expired
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_list_remove_expired, /* name */
|
||||
be_nested_proto(
|
||||
6, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 5]) { /* constants */
|
||||
/* K0 */ be_const_int(0),
|
||||
/* K1 */ be_nested_str_weak(has_expired),
|
||||
/* K2 */ be_nested_str_weak(_persist),
|
||||
/* K3 */ be_nested_str_weak(remove),
|
||||
/* K4 */ be_const_int(1),
|
||||
}),
|
||||
be_str_weak(remove_expired),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[22]) { /* code */
|
||||
0x50040000, // 0000 LDBOOL R1 0 0
|
||||
0x58080000, // 0001 LDCONST R2 K0
|
||||
0x600C000C, // 0002 GETGBL R3 G12
|
||||
0x5C100000, // 0003 MOVE R4 R0
|
||||
0x7C0C0200, // 0004 CALL R3 1
|
||||
0x140C0403, // 0005 LT R3 R2 R3
|
||||
0x780E000D, // 0006 JMPF R3 #0015
|
||||
0x940C0002, // 0007 GETIDX R3 R0 R2
|
||||
0x8C0C0701, // 0008 GETMET R3 R3 K1
|
||||
0x7C0C0200, // 0009 CALL R3 1
|
||||
0x780E0007, // 000A JMPF R3 #0013
|
||||
0x940C0002, // 000B GETIDX R3 R0 R2
|
||||
0x880C0702, // 000C GETMBR R3 R3 K2
|
||||
0x780E0000, // 000D JMPF R3 #000F
|
||||
0x50040200, // 000E LDBOOL R1 1 0
|
||||
0x8C0C0103, // 000F GETMET R3 R0 K3
|
||||
0x5C140400, // 0010 MOVE R5 R2
|
||||
0x7C0C0400, // 0011 CALL R3 2
|
||||
0x70020000, // 0012 JMP #0014
|
||||
0x00080504, // 0013 ADD R2 R2 K4
|
||||
0x7001FFEC, // 0014 JMP #0002
|
||||
0x80040200, // 0015 RET 1 R1
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: persistables
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_list_persistables, /* name */
|
||||
be_nested_proto(
|
||||
3, /* nstack */
|
||||
1, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
1, /* has sup protos */
|
||||
( &(const struct bproto*[ 1]) {
|
||||
be_nested_proto(
|
||||
2, /* nstack */
|
||||
0, /* argc */
|
||||
0, /* varg */
|
||||
1, /* has upvals */
|
||||
( &(const bupvaldesc[ 1]) { /* upvals */
|
||||
be_local_const_upval(1, 1),
|
||||
}),
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(_persist),
|
||||
}),
|
||||
be_str_weak(_anonymous_),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 9]) { /* code */
|
||||
0x50000200, // 0000 LDBOOL R0 1 0
|
||||
0x78020005, // 0001 JMPF R0 #0008
|
||||
0x68000000, // 0002 GETUPV R0 U0
|
||||
0x7C000000, // 0003 CALL R0 0
|
||||
0x88040100, // 0004 GETMBR R1 R0 K0
|
||||
0x78060000, // 0005 JMPF R1 #0007
|
||||
0x80040000, // 0006 RET 1 R0
|
||||
0x7001FFF7, // 0007 JMP #0000
|
||||
0x80000000, // 0008 RET 0
|
||||
})
|
||||
),
|
||||
}),
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(iter),
|
||||
}),
|
||||
be_str_weak(persistables),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 5]) { /* code */
|
||||
0x8C040100, // 0000 GETMET R1 R0 K0
|
||||
0x7C040200, // 0001 CALL R1 1
|
||||
0x84080000, // 0002 CLOSURE R2 P0
|
||||
0xA0000000, // 0003 CLOSE R0
|
||||
0x80040400, // 0004 RET 1 R2
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified function: setitem
|
||||
********************************************************************/
|
||||
be_local_closure(Matter_Expirable_list_setitem, /* name */
|
||||
be_nested_proto(
|
||||
7, /* nstack */
|
||||
3, /* argc */
|
||||
2, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 6]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(matter),
|
||||
/* K1 */ be_nested_str_weak(Expirable),
|
||||
/* K2 */ be_nested_str_weak(type_error),
|
||||
/* K3 */ be_nested_str_weak(argument_X20must_X20be_X20of_X20class_X20_X27Expirable_X27),
|
||||
/* K4 */ be_nested_str_weak(set_parent_list),
|
||||
/* K5 */ be_nested_str_weak(setitem),
|
||||
}),
|
||||
be_str_weak(setitem),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[18]) { /* code */
|
||||
0x600C000F, // 0000 GETGBL R3 G15
|
||||
0x5C100400, // 0001 MOVE R4 R2
|
||||
0xB8160000, // 0002 GETNGBL R5 K0
|
||||
0x88140B01, // 0003 GETMBR R5 R5 K1
|
||||
0x7C0C0400, // 0004 CALL R3 2
|
||||
0x740E0000, // 0005 JMPT R3 #0007
|
||||
0xB0060503, // 0006 RAISE 1 K2 K3
|
||||
0x8C0C0504, // 0007 GETMET R3 R2 K4
|
||||
0x5C140000, // 0008 MOVE R5 R0
|
||||
0x7C0C0400, // 0009 CALL R3 2
|
||||
0x600C0003, // 000A GETGBL R3 G3
|
||||
0x5C100000, // 000B MOVE R4 R0
|
||||
0x7C0C0200, // 000C CALL R3 1
|
||||
0x8C0C0705, // 000D GETMET R3 R3 K5
|
||||
0x5C140200, // 000E MOVE R5 R1
|
||||
0x5C180400, // 000F MOVE R6 R2
|
||||
0x7C0C0600, // 0010 CALL R3 3
|
||||
0x80040600, // 0011 RET 1 R3
|
||||
})
|
||||
)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Solidified class: Matter_Expirable_list
|
||||
********************************************************************/
|
||||
extern const bclass be_class_list;
|
||||
be_local_class(Matter_Expirable_list,
|
||||
0,
|
||||
&be_class_list,
|
||||
be_nested_map(7,
|
||||
( (struct bmapnode*) &(const bmapnode[]) {
|
||||
{ be_const_key_weak(count_persistables, 4), be_const_closure(Matter_Expirable_list_count_persistables_closure) },
|
||||
{ be_const_key_weak(remove, -1), be_const_closure(Matter_Expirable_list_remove_closure) },
|
||||
{ be_const_key_weak(push, 5), be_const_closure(Matter_Expirable_list_push_closure) },
|
||||
{ be_const_key_weak(every_second, -1), be_const_closure(Matter_Expirable_list_every_second_closure) },
|
||||
{ be_const_key_weak(setitem, 6), be_const_closure(Matter_Expirable_list_setitem_closure) },
|
||||
{ be_const_key_weak(persistables, -1), be_const_closure(Matter_Expirable_list_persistables_closure) },
|
||||
{ be_const_key_weak(remove_expired, -1), be_const_closure(Matter_Expirable_list_remove_expired_closure) },
|
||||
})),
|
||||
be_str_weak(Matter_Expirable_list)
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
void be_load_Matter_Expirable_list_class(bvm *vm) {
|
||||
be_pushntvclass(vm, &be_class_Matter_Expirable_list);
|
||||
be_setglobal(vm, "Matter_Expirable_list");
|
||||
be_pop(vm, 1);
|
||||
}
|
||||
/********************************************************************/
|
||||
/* End of solidification */
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user