Merge branch 'development' into prerelease-12.5

This commit is contained in:
Theo Arends 2023-04-15 15:13:16 +02:00
commit 3f43db93f0
811 changed files with 180436 additions and 22088 deletions

View File

@ -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
View File

@ -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"
}
]
}

View File

@ -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 | - | - |

View File

@ -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

View File

@ -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 |
| |

View File

@ -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

View File

@ -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 dont 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

View File

@ -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)

View File

@ -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}
```

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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);

View File

@ -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 dUtilisation 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

View File

@ -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":

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,11 @@
*~
Doxyfile*
doxygen_sqlite3.db
html# osx
.DS_Store
# doxygen
Doxyfile*
doxygen_sqlite3.db
html
*.tmp

View 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;
}

View 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

View File

@ -0,0 +1,49 @@
# Adafruit PM2.5 Air Quality sensor [![Build Status](https://github.com/adafruit/Adafruit_PM25AQI/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_PM25AQI/actions)[![Documentation](https://github.com/adafruit/ci-arduino/blob/master/assets/doxygen_badge.svg)](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

View 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 dont 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 communitys 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.

View File

@ -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);
}

View 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

View 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.

View File

@ -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"),

View File

@ -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;

View File

@ -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;

View File

@ -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:

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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) {

View File

@ -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;
}

View File

@ -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

View 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

View 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{}"
}
}

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -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 */

View File

@ -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 */

View 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

View File

@ -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)
}

View File

@ -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()

View File

@ -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

View File

@ -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

View 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))
-#

View 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

View File

@ -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

View File

@ -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:{

View 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

View 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

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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}')
-#

View File

@ -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()
-#

View File

@ -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>&nbsp;Matter Passcode&nbsp;</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>&nbsp;Commissioning open for %i min&nbsp;</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>&nbsp;Matter Passcode&nbsp;</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>&nbsp;Sessions&nbsp;</b></legend><p></p>")
webserver.content_send("<p>Existing sessions:</p>")
webserver.content_send("<fieldset><legend><b>&nbsp;Fabrics&nbsp;</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>&nbsp;Session %i&nbsp;</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>&nbsp;", 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>&nbsp;#%i %s&nbsp;</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>&nbsp;", 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

File diff suppressed because it is too large Load Diff

View 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

View File

@ -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
})
)

View File

@ -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 */

View File

@ -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