Merge branch 'development' into prerelease-13.1

This commit is contained in:
Theo Arends 2023-08-12 14:25:15 +02:00
commit 5a7a82e975
353 changed files with 29985 additions and 19852 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.10
- [ ] The code change is tested and works with Tasmota core ESP32 V.2.0.11
- [ ] 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**_

View File

@ -119,6 +119,7 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up
| USE_MGS | - | - / x | - | x | - | - |
| USE_SGP30 | - | - / x | - | x | - | - |
| USE_SGP40 | - | - / x | - | x | - | - |
| USE_SGP4X | - | - / x | - | - | - | - |
| USE_SEN5X | - | - / x | - | x | - | - |
| USE_SI1145 | - | - / - | - | - | - | - |
| USE_LM75AD | - | - / x | - | x | - | - |
@ -127,6 +128,7 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up
| USE_MCP23XXX_DRV | - | - / - | - | - | - | - |
| USE_PCA9632 | - | - / - | - | - | - | - |
| USE_PCA9685 | - | - / - | - | - | - | - |
| USE_PCA9685_V2 | - | - / - | - | - | - | - |
| USE_MPR121 | - | - / - | - | - | - | - |
| USE_CCS811 | - | - / - | - | x | - | - |
| USE_CCS811_V2 | - | - / x | - | - | - | - |

View File

@ -1,7 +1,92 @@
# Changelog
All notable changes to this project will be documented in this file.
## [Released]
## [Released] - Development
## [13.1.0] 20230815
- Release Quentin
## [13.0.0.4] 20230815
### Added
- ESP32 prepare for Arduino Core v3 and esp-idf v5 (#19264)
### Changed
- Console height from default 318 pixels to viewport (#19241)
- Shutter button hold behaviour with grouptopic (#19263)
- Thermostat improvements (#19279)
- PID controller improvements (#19285)
## [13.0.0.3] 20230805
### Added
- Support for MAX17043 fuel-gauge systems Lipo batteries (#18788)
- Support for multiple PCA9685 with extended functionality (#18805)
- Zigbee decode Aqara 0000/FF01 attribute 03 as Temperature (#19210)
- Berry bytes `get` and `set` work for 3 bytes values (#19225)
- Matter support for fabric_filtered request (for Google compatibility) (#19249)
### Changed
- Initial ``DisplayMode`` from 1 to 0 and ``DisplayDimmmer`` from 10% to 50% (#19138)
- ESP32 Framework (Arduino Core) from v2.0.10 to v2.0.11
- Berry `mqtt.publish` now distinguishes between `string` and `bytes` (#19196)
- IRremoteESP8266 library from v2.8.5 to v2.8.6
- ESP32 autodetect flashsize and adjust filesystem (#19215)
- Reduced log level for TeleInfo (#19216)
- Matter increased polling frequency for local switches/occupancy (#19242)
### Fixed
- Initial battery level percentage (#19160)
- Berry SK6812_GRBW crash (#19166)
- ESP8266 SPI initialization for scripter, filesystem and MFRC522 (#19209)
- Zero cross dimmer minimum interrupt time (#19211)
- Fade would fail when the difference between start and target would be too small (#19248)
- Inverted shutter (#19243)
- Matter support for large atribute responses (#19252)
- Matter auto-configuration Relay indices (#19255)
## [13.0.0.2] 20230721
### Added
- Partition Wizard is now able to convert to safeboot from Shelly partition layout (#19034)
- Matter mini-profiler (#19075)
- Berry `_class` can be used in `static var` initialization code (#19088)
- Berry `energy.update_total()` to call `EnergyUpdateTotal()` from energy driver (#19117)
- Support for DeepSleep battery level percentage (#19134)
- Berry metrics for memory allocation/deallocation/reallocation (#19150)
- Berry `tasmota.loglevel()` and `tasmota.rtc_utc()` for faster performance (#19152)
- Berry AES CCM decrypting in a single call to avoid any object allocation (#19153)
### Changed
- ESP32 shutter driver support up to 16 shutters (#18295)
- Configuration backup and restore now backup and restore ``.xdrvsetXXX`` files too (#18295)
- Berry extend `range(lower, upper, incr)` to arbitrary increment (#19120)
- Berry updated syntax highlighting plugin for VSCode (#19123)
- Matter latency improvement for single attribute reads and single commands (#19158)
## [13.0.0.1] 20230708
### Added
- Command ``Delay -1`` to wait until next second (#18984)
- Matter add option to disable bridge mode (#18992)
- Support for SGP41 TVOC/NOx Sensor (#18880)
- Command ``BrRestart`` to restart the Berry VM (experimental) (#19003)
- Command ``Restart 9`` to save all changes and go into deepsleep waiting for a reset (#19024)
- Berry added `getgbl` performance counter to `debug.counters()` (#19070)
### Breaking Changed
- Berry `bool( [] )` and `bool( {} )` now evaluate as `false` (#18986)
- Berry `import strict` now detects useless expr without side effects (#18997)
### Changed
- Matter support for temperature in Fahrenheit (`SetOption8 1`) (#18987)
- Matter improve responsiveness (#19002)
- ESP32 LVGL library from v8.3.7 to v8.3.8 (no functional change)
- Matter improve latency for remote commands (#19072)
### Fixed
- Berry various fixes for Walrus Operator (#18982)
- MiElHVAC power commands regression from v12.4.0.1 (#18923)
- `BrRestart` now supports web handlers to work after Berry restart
### Removed
- Support for ESP32-C3 with chip rev below 3 (old development boards)
## [13.0.0] 20230626
- Release Qasim
@ -69,7 +154,7 @@ All notable changes to this project will be documented in this file.
### Added
- Matter support for Shutters with Tilt
- Matter POC for remote Relay
- Support for Zero-Cross Dimmer on ESP32, changed calculation on EPS8266, high resolution control e.g. Solar: `ZCDimmerSet`
- Support for Zero-Cross Dimmer on ESP32, changed calculation on ESP8266, high resolution control e.g. Solar: `ZCDimmerSet`
- ESP32 Enhanced Shutterbuttons functionality to control tilt position, additionally incr/decr possible to position and tilt.
- ESP32 command ``Shuttersetup`` for "Shelly 2.5 pro" automatic calibration and setup (experimental)
- Berry `tcpclientasync` class for non-blocking TCP client

View File

@ -80,7 +80,16 @@ In addition to @arendst the following code is mainly owned by:
| xdrv_66_tm1638 | @arendst
| xdrv_67_mcp23xxx | @arendst
| xdrv_68_zerocrossDimmer.ino | @stefanbode
| |
| xdrv_69_pca9557 | @cctweaker
| xdrv_70 |
| xdrv_71 |
| xdrv_72 |
| xdrv_73 |
| xdrv_74 |
| xdrv_75 |
| xdrv_76 |
| xdrv_77 |
| xdrv_78 |
| xdrv_79_esp32_ble | @staars, @btsimonh
| xdrv_81_esp32_webcam | @gemu, @philrich
| xdrv_82_esp32_ethernet | @arendst
@ -208,6 +217,8 @@ In addition to @arendst the following code is mainly owned by:
| xsns_106_gdk101 | @Szewcson
| xsns_107_gm861 | @arendst
| xsns_108_tc74 | Michael Loftis
| xsns_109_sgp4x | Andrew Klaus
| xsns_110_max17043 | Vincent de Groot
| |
| Libraries |
| |

View File

@ -9,6 +9,7 @@ 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
1 | USE_PCA9685_V2 | 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
@ -116,4 +117,6 @@ Index | Define | Driver | Device | Address(es) | Description
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
80 | USE_TC74 | xsns_108 | TC74 | 0x48 - 0x4F | Temperature sensor
81 | USE_PCA9557 | xdrv_69 | PCA95xx | 0x18 - 0x1F | 8-bit I/O expander as virtual button/switch/relay
81 | USE_PCA9557 | xdrv_69 | PCA95xx | 0x18 - 0x1F | 8-bit I/O expander as virtual button/switch/relay
82 | USE_SGP4X | xsns_109 | SGP4X | 0x59 | Gas (TVOC/NOx index)
83 | USE_MAX17043 | xsns_110 | MAX17043 | 0x36 | Fuel-gauge for 3.7 Volt Lipo battery

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.10**.
This release will be supported from ESP32/Arduino library Core version **2.0.11**.
Support of ESP8266 Core versions before 2.7.4.9 and ESP32 Core versions before 2.0.10 have been removed.
Support of ESP8266 Core versions before 2.7.4.9 and ESP32 Core versions before 2.0.11 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-13.0.0
- http://ota.tasmota.com/tasmota/release-13.1.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.10**.
The following binary downloads have been compiled with ESP32/Arduino library core version **2.0.11**.
- **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-13.0.0
- https://ota.tasmota.com/tasmota32/release-13.1.0
The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasmota.com/tasmota32/release/tasmota32.bin``
@ -110,75 +110,64 @@ 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 v13.0.0 Qasim
## Changelog v13.1.0 Quentin
### Added
- Command ``SetOption152 0/1`` to select two (0 = default) pin bistable or one (1) pin latching relay control [#18386](https://github.com/arendst/Tasmota/issues/18386)
- Command ``I2cScan0`` to scan both busses on ESP32 with one command
- Command ``WifiPower 0`` to enable dynamic wifi power based on RSSI by @TD-er [#15443](https://github.com/arendst/Tasmota/issues/15443)
- Command ``WifiPower 1`` to restore default wifi power
- Support for TC74 temperature sensor by Michael Loftis [#18042](https://github.com/arendst/Tasmota/issues/18042)
- Support for GM861 1D and 2D bar code reader [#18399](https://github.com/arendst/Tasmota/issues/18399)
- Support for PCA9557 8-bit I/O expander [#18632](https://github.com/arendst/Tasmota/issues/18632)
- Display descriptor for ST7735 128x160 display [#18741](https://github.com/arendst/Tasmota/issues/18741)
- Zigbee support for air sensors [#18665](https://github.com/arendst/Tasmota/issues/18665)
- Zigbee firmware for Sonoff-ZB-Pro v20230507 [#18968](https://github.com/arendst/Tasmota/issues/18968)
- ESP32 command ``Shuttersetup`` for "Shelly 2.5 pro" automatic calibration and setup (experimental)
- ESP32 Enhanced Shutterbuttons functionality to control tilt position, additionally incr/decr possible to position and tilt.
- Berry RS256 crypto algorithm (RSASSA-MCKS1_v1-5 with SHA256) used for JWT [#18763](https://github.com/arendst/Tasmota/issues/18763)
- Berry `tcpclientasync` class for non-blocking TCP client
- Berry `set_lsb_justified(bool)` to `AudioOutputI2S` [#18774](https://github.com/arendst/Tasmota/issues/18774)
- Berry `string.format()` now automatically converts type according to format [#18890](https://github.com/arendst/Tasmota/issues/18890)
- Berry global function `format` as a simpler syntax to `string.format` [#18925](https://github.com/arendst/Tasmota/issues/18925)
- Berry f-strings as an alternative to string formatting [#18937](https://github.com/arendst/Tasmota/issues/18937)
- Berry Walrus operator ':=' [#18963](https://github.com/arendst/Tasmota/issues/18963)
- HASPmota `meta` attribute and improved `berry_run` [#18685](https://github.com/arendst/Tasmota/issues/18685)
- Matter sensors Humidity, Pressure, Illuminance [#18441](https://github.com/arendst/Tasmota/issues/18441)
- Matter allow `Matter#Initialized` rule once the device is configured [#18451](https://github.com/arendst/Tasmota/issues/18451)
- Matter UI to change endpoints configuration [#18498](https://github.com/arendst/Tasmota/issues/18498)
- Matter support for Shutters with Tilt [#18509](https://github.com/arendst/Tasmota/issues/18509)
- Matter support for async HTTP used for bridged devices and remote relays [#18656](https://github.com/arendst/Tasmota/issues/18656)
- Matter bridge for ESP8266 remote endpoints (experimental) [#18734](https://github.com/arendst/Tasmota/issues/18734)
- Matter support for Occupancy via Switch (experimental) [#18742](https://github.com/arendst/Tasmota/issues/18742)
- Matter ability to add or remove endpoint in bridge mode (code only) [#18790](https://github.com/arendst/Tasmota/issues/18790)
- Matter controller's Vendor Name to logs and UI [#18794](https://github.com/arendst/Tasmota/issues/18794)
- Matter redesigned UI [#18855](https://github.com/arendst/Tasmota/issues/18855)
- Matter support for Contact Sensor [#18882](https://github.com/arendst/Tasmota/issues/18882)
- Matter friendly-name (NodeLabel) to each endpoint [#18897](https://github.com/arendst/Tasmota/issues/18897)
- Matter display the remote Device Name instead of IP address [#18961](https://github.com/arendst/Tasmota/issues/18961)
- Command ``BrRestart`` to restart the Berry VM (experimental) [#19003](https://github.com/arendst/Tasmota/issues/19003)
- Command ``Delay -1`` to wait until next second [#18984](https://github.com/arendst/Tasmota/issues/18984)
- Command ``Restart 9`` to save all changes and go into deepsleep waiting for a reset [#19024](https://github.com/arendst/Tasmota/issues/19024)
- Support for MAX17043 fuel-gauge systems Lipo batteries [#18788](https://github.com/arendst/Tasmota/issues/18788)
- Support for multiple PCA9685 with extended functionality [#18805](https://github.com/arendst/Tasmota/issues/18805)
- Support for SGP41 TVOC/NOx Sensor [#18880](https://github.com/arendst/Tasmota/issues/18880)
- Support for DeepSleep battery level percentage [#19134](https://github.com/arendst/Tasmota/issues/19134)
- Zigbee decode Aqara 0000/FF01 attribute 03 as Temperature [#19210](https://github.com/arendst/Tasmota/issues/19210)
- ESP32 prepare for Arduino Core v3 and esp-idf v5 [#19264](https://github.com/arendst/Tasmota/issues/19264)
- Berry `getgbl` performance counter to `debug.counters()` [#19070](https://github.com/arendst/Tasmota/issues/19070)
- Berry `_class` can be used in `static var` initialization code [#19088](https://github.com/arendst/Tasmota/issues/19088)
- Berry `energy.update_total()` to call `EnergyUpdateTotal()` from energy driver [#19117](https://github.com/arendst/Tasmota/issues/19117)
- Berry `tasmota.loglevel()` and `tasmota.rtc_utc()` for faster performance [#19152](https://github.com/arendst/Tasmota/issues/19152)
- Berry metrics for memory allocation/deallocation/reallocation [#19150](https://github.com/arendst/Tasmota/issues/19150)
- Berry AES CCM decrypting in a single call to avoid any object allocation [#19153](https://github.com/arendst/Tasmota/issues/19153)
- Berry bytes `get` and `set` work for 3 bytes values [#19225](https://github.com/arendst/Tasmota/issues/19225)
- Partition Wizard is now able to convert to safeboot from Shelly partition layout [#19034](https://github.com/arendst/Tasmota/issues/19034)
- Matter option to disable bridge mode [#18992](https://github.com/arendst/Tasmota/issues/18992)
- Matter mini-profiler [#19075](https://github.com/arendst/Tasmota/issues/19075)
- Matter support for fabric_filtered request (for Google compatibility) [#19249](https://github.com/arendst/Tasmota/issues/19249)
### Breaking Changed
- Change command ``FileUpload`` index binary data detection from >199 to >299
- Matter relay number starts at 1 instead of 0 to match Tasmota numbering
- Berry `bool( [] )` and `bool( {} )` now evaluate as `false` [#18986](https://github.com/arendst/Tasmota/issues/18986)
- Berry `import strict` now detects useless expression without side effects [#18997](https://github.com/arendst/Tasmota/issues/18997)
### Changed
- AdafruitFingerprint library from v2.0.4 to v2.1.0
- IRremoteESP8266 library from v2.8.4 to v2.8.5
- ESP32 Framework (Core) from v2.0.7 to v2.0.10
- ESP32 Binaries size increase of 300k due to Matter support may need [Partition Wizard](https://tasmota.github.io/docs/Tasmota-Application/#partition-management)
- InfluxDb resolves DNS name before request [#18015](https://github.com/arendst/Tasmota/issues/18015)
- Refactored Zero Cross Dimmer [#18481](https://github.com/arendst/Tasmota/issues/18481)
- Energy power delta report delayed by two seconds allowing hardware to stabilize [#17751](https://github.com/arendst/Tasmota/issues/17751)
- Shutter sliders in WEBGUI automatically appear and disappear during configuration and update during movement [#18701](https://github.com/arendst/Tasmota/issues/18701)
- Berry `webclient.url_encode()` is now a static class method, no change required to existing code [#18775](https://github.com/arendst/Tasmota/issues/18775)
- IRremoteESP8266 library from v2.8.5 to v2.8.6
- ESP32 Framework (Arduino Core) from v2.0.10 to v2.0.11
- ESP32 LVGL library from v8.3.7 to v8.3.8 (no functional change)
- Initial ``DisplayMode`` from 1 to 0 and ``DisplayDimmmer`` from 10% to 50% [#19138](https://github.com/arendst/Tasmota/issues/19138)
- Configuration backup and restore now supports ``.xdrvsetXXX`` files too [#18295](https://github.com/arendst/Tasmota/issues/18295)
- Reduced log level for TeleInfo [#19216](https://github.com/arendst/Tasmota/issues/19216)
- Console height from default 318 pixels to viewport [#19241](https://github.com/arendst/Tasmota/issues/19241)
- Shutter button hold behaviour with grouptopic [#19263](https://github.com/arendst/Tasmota/issues/19263)
- Thermostat improvements [#19279](https://github.com/arendst/Tasmota/issues/19279)
- PID controller improvements [#19285](https://github.com/arendst/Tasmota/issues/19285)
- ESP32 shutter driver support up to 16 shutters [#18295](https://github.com/arendst/Tasmota/issues/18295)
- ESP32 autodetect flashsize and adjust filesystem [#19215](https://github.com/arendst/Tasmota/issues/19215)
- Berry extend `range(lower, upper, incr)` to arbitrary increment [#19120](https://github.com/arendst/Tasmota/issues/19120)
- Berry updated syntax highlighting plugin for VSCode [#19123](https://github.com/arendst/Tasmota/issues/19123)
- Berry `mqtt.publish` now distinguishes between `string` and `bytes` [#19196](https://github.com/arendst/Tasmota/issues/19196)
- Matter support for temperature in Fahrenheit (`SetOption8 1`) [#18987](https://github.com/arendst/Tasmota/issues/18987)
- Matter improve responsiveness [#19002](https://github.com/arendst/Tasmota/issues/19002)
- Matter improve latency for remote commands [#19072](https://github.com/arendst/Tasmota/issues/19072)
- Matter improve latency for single attribute reads and single commands [#19158](https://github.com/arendst/Tasmota/issues/19158)
- Matter increased polling frequency for local switches/occupancy [#19242](https://github.com/arendst/Tasmota/issues/19242)
### Fixed
- ESP8266 no update on Energy Export Active regression from v12.3.1.3
- NovaSDS GUI values [#18444](https://github.com/arendst/Tasmota/issues/18444)
- LED PWM ac_dimmer curve was wrongly applied instead of Gamma regression from v12.2.0.5 [#18666](https://github.com/arendst/Tasmota/issues/18666)
- Shutter bootloop using more than 4 shutters [#18673](https://github.com/arendst/Tasmota/issues/18673)
- Inverted shutter now reflect status also in WEBGUI and several minor fixes to make "inverted" consistant [#18701](https://github.com/arendst/Tasmota/issues/18701)
- Interaction of ``SetOption92``, ``VirtualCT``, and ``RGBWWTable`` [#18768](https://github.com/arendst/Tasmota/issues/18768)
- Freeze BMP readings before deepsleep [#18720](https://github.com/arendst/Tasmota/issues/18720)
- NeoPool NPFiltration switch result [#18871](https://github.com/arendst/Tasmota/issues/18871)
- ESP32 Partition_Manager.tapp
- ESP32 InfluxDb initial connection delays using HTTPClient [#18015](https://github.com/arendst/Tasmota/issues/18015)
- ESP32 AIThinker webcam issues [#18652](https://github.com/arendst/Tasmota/issues/18652)
- ESP32 SPI initialization for MFRC522 [#18711](https://github.com/arendst/Tasmota/issues/18711)
- ESP32 Neopixel busy time adjustment [#18723](https://github.com/arendst/Tasmota/issues/18723)
- HASPmota event when value is non-integer [#18229](https://github.com/arendst/Tasmota/issues/18229)
- Berry a rare condition when a GC causes a memory corruption
- Berry rules for string comparisons [#18464](https://github.com/arendst/Tasmota/issues/18464)
- Berry parser error with upvals in closures [#18902](https://github.com/arendst/Tasmota/issues/18902)
- Zigbee attributes handling in Berry mapping [#18747](https://github.com/arendst/Tasmota/issues/18747)
- Zigbee regression with ``SetOption101`` [#18884](https://github.com/arendst/Tasmota/issues/18884)
- Matter fabric provisioning from CASE session for iOS 16.5 [#18709](https://github.com/arendst/Tasmota/issues/18709)
- Berry Walrus Operator [#18982](https://github.com/arendst/Tasmota/issues/18982)
- MiElHVAC power commands regression from v12.4.0.1 [#18923](https://github.com/arendst/Tasmota/issues/18923)
- Zero cross dimmer minimum interrupt time [#19211](https://github.com/arendst/Tasmota/issues/19211)
- Fade would fail when the difference between start and target would be too small [#19248](https://github.com/arendst/Tasmota/issues/19248)
- Inverted shutter [#19243](https://github.com/arendst/Tasmota/issues/19243)
- ESP8266 SPI initialization for scripter, filesystem and MFRC522 [#19209](https://github.com/arendst/Tasmota/issues/19209)
- Matter support for large atribute responses [#19252](https://github.com/arendst/Tasmota/issues/19252)
- Matter auto-configuration Relay indices [#19255](https://github.com/arendst/Tasmota/issues/19255)
### Removed
- Support for ESP32-C3 with chip revision below 3 (old development boards)

View File

@ -5,7 +5,7 @@
# Templates
Find below the available templates as of June 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 August 2023. More template information can be found in the [Tasmota Device Templates Repository](http://blakadder.github.io/templates)
## Adapter Board
```
@ -226,6 +226,7 @@ MS-108 In-Wall {"NAME":"MS-108","GPIO":[0,0,0,0,161,160,0,0,224,0,
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}
wesmartify Smart Home Aktor Shut It TASMOTA {"NAME":"ITzy","GPIO":[0,0,0,0,225,224,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":54}
```
## Curtain Switch
@ -257,10 +258,11 @@ 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}
CurrySmarter Power Monitoring 30A {"NAME":"30A Breaker on Led","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}
Martin Jerry 30A Circuit Breaker {"NAME":"30A Breaker","GPIO":[0,0,0,0,7584,224,0,0,2720,32,2656,2624,320,0],"FLAG":0,"BASE":18}
OpenEnergyMonitor WiFi MQTT Thermostat {"NAME":"MQTT-RELAY","GPIO":[32,0,1,0,0,224,0,0,0,0,0,0,320,0],"FLAG":0,"BASE":18}
RocketController ASTRA Controller {"NAME":"ASTRA R4A4","GPIO":[1,1,1,1,576,1,1,1,480,1,1,1,3232,3200,1,1,0,640,608,1,0,224,225,1152,0,0,0,0,227,226,160,161,162,0,0,163],"FLAG":0,"BASE":1}
Shelly Pro 1 {"NAME":"Shelly Pro 1","GPIO":[0,1,0,1,768,0,0,0,672,704,736,0,0,0,5600,6214,0,0,0,5568,0,0,0,0,0,0,0,0,0,0,0,32,4736,0,160,0],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,10000,10000,3350"}
@ -295,6 +297,7 @@ 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 CONNECTPLUS {"NAME":"Dasduino CONNECT","GPIO":[1,1,1376,1,640,608,1,1,1,1,1,1,1,1],"FLAG":0,"BASE":18}
Dasduino CONNECTPLUS {"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}
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}
KinCony 128 Channel Controller {"NAME":"KC868-A128","GPIO":[0,1,0,1,609,640,1,1,1,3232,3200,641,608,1,5600,0,0,1,0,5568,0,1,0,0,0,0,0,0,0,0,4705,4707,4706,0,0,4704],"FLAG":0,"BASE":1}
@ -392,7 +395,7 @@ Shelly Vintage 7W 750lm 2700k {"NAME":"Shelly Vintage","GPIO":[0,0,0,0,416,0,0,
Shelly Vintage 7W 750lm 2700k {"NAME":"Shelly Vintage","GPIO":[0,0,0,0,416,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
SmartDGM 9W 806lm {"NAME":"L-WB9W1","GPIO":[0,0,0,0,0,416,0,0,160,0,0,0,0,0],"FLAG":0,"BASE":18}
Smitch 10W 6500K {"NAME":"Smitch Ambience SB-0110","GPIO":[0,0,0,0,416,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
Smitch 10W 6500K {"NAME":"Smitch Ambience SB-0110","GPIO":[0,0,0,0,416,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
Smitch 10W 6500K {"NAME":"Smitch 10W 6500K Dimmable Bulb (SB0110 - E27)","GPIO":[0,0,0,0,0,416,0,0,0,417,0,0,0,0],"FLAG":0,"BASE":1}
TCP Smart 806lm Warm White {"NAME":"TCP Smart Clas","GPIO":[0,0,0,0,0,0,0,0,0,416,0,0,0,0],"FLAG":0,"BASE":1}
TCP Smart 810lm Filament {"NAME":"TCP Filament","GPIO":[0,0,0,0,0,0,0,0,0,0,448,0,0,0],"FLAG":0,"BASE":18}
TCP Smart 810lm Filament {"NAME":"TCP Filament","GPIO":[0,0,0,0,0,0,0,0,0,0,448,0,0,0],"FLAG":0,"BASE":18}
@ -605,6 +608,7 @@ AI Universal Remote {"NAME":"YTF IR Controller","GPIO":[1,1,1,1,320,108
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}
Athom {"NAME":"Athom_IR_Remote","GPIO":[32,0,0,0,1056,1088,0,0,0,576,0,0,0,0],"FLAG":0,"BASE":18}
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"}
Automate Things IR Bridge {"NAME":"AT-IRBR-1.4","GPIO":[1088,0,0,0,1056,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18,"CMND":"Module 0"}
auvisio S06 {"NAME":"NX-4519-675","GPIO":[0,0,0,0,288,1088,0,0,0,0,1056,0,0,0],"FLAG":0,"BASE":18}
@ -710,6 +714,7 @@ ZJ-WF-ESP-A v1.1 {"NAME":"RGB2","GPIO":[0,0,0,0,0,0,0,0,417,416,418,
## LED Strip
```
Aldi Casalux RGB {"NAME":"DW-RGB-WI01","GPIO":[1088,0,0,0,416,0,0,0,417,0,418,0,0,0],"FLAG":0,"BASE":18}
ARLEC 5m Colour Changing "Not available"
Arlec Smart 1m CCT LED Strip Light {"NAME":"ALD155HA","GPIO":[0,0,1088,0,0,416,0,0,0,417,0,0,0,0],"FLAG":0,"BASE":18}
Arlec Smart 2m LED Colour Changing Strip Light {"NAME":"Arlec_Light_Strip","GPIO":[1,1,1088,1,416,419,1,1,417,420,418,0,1,1],"FLAG":0,"BASE":18}
Arlec Smart 5m White & Colour Changing {"NAME":"ALD556HA","GPIO":[0,0,1088,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
@ -794,7 +799,7 @@ Deta 18W 1900lm T8 Tube {"NAME":"DETA Smart LED","GPIO":[0,0,0,0,0,0,0,0,0,
electriQ MOODL Ambiance Lamp {"NAME":"ElectriQ MOODL","GPIO":[0,4640,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
Gosund Table Lamp {"NAME":"Gosund LB3","GPIO":[0,0,0,0,0,418,0,0,417,419,0,416,0,0],"FLAG":0,"BASE":18}
Hama Wall Light Square, 10 cm, IP 44 {"NAME":"Hama Wifi Wall Light","GPIO":[0,0,0,0,0,416,0,0,0,417,0,0,0,1],"FLAG":0,"BASE":18}
HiFree Table Lamp {"NAME":"TuyaMCU","GPIO":[108,1,107,1,1,1,1,1,1,1,1,1,1,1],"FLAG":0,"BASE":18}
HiFree Table Lamp {"NAME":"TuyaMCU","GPIO":[2304,1184,2272,1184,1184,1184,1184,1184,1184,1184,1184,1184,1184,0],"FLAG":0,"BASE":18}
Hugoai Table Lamp {"NAME":"HG02","GPIO":[1,1,1,1,1,1,0,0,1,1,1,1,1,0],"FLAG":0,"BASE":54,"CMND":"TuyaMCU 11,20 | TuyaMCU 26,21 | TuyaMCU 21,22 | TuyaMCU 23,23 | TuyaMCU 24,24 | DimmerRange 34,1000"}
Iwoole Table Lamp {"NAME":"GLOBELAMP","GPIO":[0,0,0,0,419,0,0,0,417,418,416,0,0,0],"FLAG":0,"BASE":18}
Lepro Bedroom Lamp {"NAME":"Lepro 902101-US","GPIO":[1,1,1,1,1,1,0,0,1,1,1,1,1,0],"FLAG":0,"BASE":54,"CMND":"TuyaMCU 11,20 | TuyaMCU 26,21 | TuyaMCU 21,22 | TuyaMCU 23,23 | TuyaMCU 24,24 | DimmerRange 34,1000"}
@ -866,6 +871,7 @@ Xystec USB3.0 4 Port Hub {"NAME":"Xystec USB Hub","GPIO":[0,0,0,0,224,0,0,0,
DT-Light ESP8285 Lighting {"NAME":"DMP-L1","GPIO":[1,1,0,1,1,1,0,0,1,1,1,1,1,1],"FLAG":0,"BASE":18}
ESP-01D {"NAME":"ESP-01D","GPIO":[1,1,0,1,1,0,0,0,1,0,1,0,0,0],"FLAG":0,"BASE":18}
ESP-01S {"NAME":"ESP-01","GPIO":[1,1,1,1,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
ESP-02S TYWE2S Replacement {"NAME":"ESP-02S","GPIO":[1,1,1,1,1,1,0,0,1,1,1,0,0,1],"FLAG":0,"BASE":18}
ESP-12 {"NAME":"ESP-12","GPIO":[1,1,1,1,1,1,0,0,1,1,1,1,1,1],"FLAG":0,"BASE":18}
ESP-15F {"NAME":"ESP-15F","GPIO":[1,1,0,1,1,1,0,0,0,544,0,0,0,0],"FLAG":0,"BASE":18}
ESP-M2 {"NAME":"ESP-M2","GPIO":[1,1,1,1,1,1,0,0,1,1,1,1,0,1],"FLAG":0,"BASE":18}
@ -877,7 +883,6 @@ M5Stack M5Stamp Pico {"NAME":"M5Stamp Pico","GPIO":[1,1,0,1,0,0,0,0,0,0,
MTools 16 Channel ESP32 Relay Driver 5V DC {"NAME":"16ch Board","GPIO":[1,1,237,1,232,1,1,1,228,231,1,1,233,230,234,235,0,238,239,236,0,224,227,226,0,0,0,0,229,225,1,1,1,0,0,1],"FLAG":0,"BASE":1}
Shelly Universal Input/Output {"NAME":"Shelly Uni","GPIO":[320,0,0,0,225,1216,0,0,192,193,194,224,0,4864],"FLAG":0,"BASE":18}
Sinilink MODBUS Interface {"NAME":"XY-WFPOW","GPIO":[0,8768,544,8800,32,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
TYWE2S Replacement {"NAME":"ESP-02S","GPIO":[1,1,1,1,1,1,0,0,1,1,1,0,0,1],"FLAG":0,"BASE":18}
```
## Motion Sensor
@ -946,6 +951,7 @@ Ledvance Smart+ 16A {"NAME":"LEDVANCE Smart Wifi Outdoor Plug","GPIO":[
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 16A {"NAME":"NX-4655-675","GPIO":[0,0,0,0,320,576,0,0,224,32,0,0,0,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}
@ -968,6 +974,7 @@ Signstek EOP03-EU {"NAME":"Signstek EOP03","GPIO":[0,0,0,0,320,321,0,
SK03 {"NAME":"SK03 Outdoor","GPIO":[32,0,0,0,2688,2656,0,0,2624,321,320,224,0,0],"FLAG":0,"BASE":57}
STITCH {"NAME":"STITCH 35556","GPIO":[1,1,1,1,225,321,0,0,224,320,32,1,1,0],"FLAG":0,"BASE":18}
Suraielec 40A Heavy Duty {"NAME":"Suraielec UBTW01B","GPIO":[0,0,0,0,544,0,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
Tate Guard ZUM-26EU-X Dual {"NAME":"Tate Guard","GPIO":[0,0,0,0,224,288,0,0,225,288,32,0,0,0],"FLAG":0,"BASE":18}
Teckin SS31 {"NAME":"Teckin SS31","GPIO":[1,1,1,1,320,321,1,1,224,32,225,1,1,1],"FLAG":0,"BASE":18}
Teckin SS33 {"NAME":"Teckin SS31","GPIO":[0,0,0,226,320,321,0,0,224,32,225,0,0,1],"FLAG":0,"BASE":18}
Teckin SS42 {"NAME":"Teckin SS42","GPIO":[0,0,0,0,320,321,0,0,224,32,225,0,0,0],"FLAG":0,"BASE":18}
@ -1069,6 +1076,7 @@ Avatto 10A {"NAME":"Avatto NAS-WR01W 10A 2021-12","GPIO":[0,0,
Avatto JH-G01E {"NAME":"AVATTO JH-G01E","GPIO":[0,3072,0,3104,0,0,0,0,32,320,224,0,0,0],"FLAG":0,"BASE":41}
Avatto OT06 16A {"NAME":"Avatto OT06","GPIO":[32,0,0,0,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":49}
Avatto OT08 {"NAME":"Avatto OT08","GPIO":[416,0,418,0,417,2720,0,0,2624,32,2656,224,0,0],"FLAG":0,"BASE":18}
Avidsen Home {"NAME":"Avidsen HomePlug","GPIO":[0,0,0,0,224,35,0,0,289,288,0,0,0,0],"FLAG":0,"BASE":18}
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}
@ -1137,6 +1145,7 @@ Coosa SP1 {"NAME":"COOSA SP1","GPIO":[321,1,320,1,0,2720,0,0,
CooWoo {"NAME":"CooWoo AW01","GPIO":[0,0,0,0,288,160,0,0,256,0,0,0,0,0],"FLAG":0,"BASE":18}
CozyLife HomeKit 16A {"NAME":"CozyLife 16A","GPIO":[0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,2720,0,0,2656,576,0,224,2624,0,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
CrazyLynX WiFi {"NAME":"CrazyLynX","GPIO":[0,0,0,0,321,320,0,0,224,32,0,0,0,4704],"FLAG":0,"BASE":18}
Crest Single Power Adaptor {"NAME":"SHSPM1","GPIO":[0,0,0,32,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":52}
Crest Smart Home Single Power Adaptor with 2 USB {"NAME":"Medion","GPIO":[0,0,0,32,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":52}
CurrySmarter 16A {"NAME":"CurrySmarter 16A","GPIO":[0,0,0,32,0,0,0,0,0,320,224,0,0,0],"FLAG":0,"BASE":18}
CurrySmarter 16A Power Monitoring {"NAME":"Currysmarter XH-TW2P","GPIO":[0,0,0,2624,32,320,0,0,224,2720,2656,0,0,0],"FLAG":0,"BASE":18}
@ -1189,8 +1198,6 @@ Etekcity 15A {"NAME":"ESW15-US","GPIO":[0,0,0,0,0,224,0,0,2656,2
Etekcity 8A {"NAME":"ESW01-USA","GPIO":[0,0,0,0,224,544,0,0,2656,2688,32,2592,288,0],"FLAG":0,"BASE":55}
EU3S {"NAME":"AWOW BSD33","GPIO":[0,0,320,0,0,2720,0,0,2624,32,2656,224,0,0],"FLAG":0,"BASE":18}
Eva Logik {"NAME":"EVA LOGIK Plug","GPIO":[1,32,1,1,1,1,0,0,1,288,224,1,1,0],"FLAG":0,"BASE":18}
EZPlug V1 OpenSource {"NAME":"EZPlug V1","GPIO":[0,0,0,32,0,0,0,0,0,320,224,0,0,0],"FLAG":0,"BASE":1}
EZPlug+ V1 {"NAME":"EZPlug+ V1","GPIO":[0,0,0,32,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":1}
Febite {"NAME":"Febite","GPIO":[320,0,0,0,0,2720,0,0,224,32,2656,0,0,0],"FLAG":0,"BASE":1}
Feit Electric PLUG/WIFI {"NAME":"Feit Wifi Plug","GPIO":[0,0,0,320,0,0,0,0,224,0,32,0,0,0],"FLAG":0,"BASE":18}
FK-PW901U {"NAME":"FK-PW901U","GPIO":[320,1,1,1,1,226,0,0,224,32,227,225,1,0],"FLAG":0,"BASE":18}
@ -1311,6 +1318,7 @@ JuoYou 16A {"NAME":"Juoyou ZY-OYD","GPIO":[0,0,0,32,2720,2656,
JVMAC-EU01 {"NAME":"JVMAC","GPIO":[0,32,0,0,0,0,0,0,0,320,224,0,0,0],"FLAG":0,"BASE":18}
Kaforto KW-US-801 {"NAME":"Kaforto US-801","GPIO":[32,576,0,227,2720,2656,0,0,2624,225,224,226,0,0],"FLAG":0,"BASE":18}
Kauf esphome {"NAME":"KAUF Plug","GPIO":[576,0,320,0,224,2720,0,0,2624,32,2656,0,0,0],"FLAG":0,"BASE":18}
Kauf esphome {"NAME":"Kauf Plug","GPIO":[0,320,0,32,2720,2656,0,0,321,224,2624,0,0,0],"FLAG":0,"BASE":18}
Kimire S12 {"NAME":"Kimire S12","GPIO":[1,1,1,32,1,1,0,0,1,320,224,1,1,0],"FLAG":0,"BASE":18}
King-Link KL-US-WF002 {"NAME":"Kinglink-plug","GPIO":[0,0,0,0,0,224,0,0,288,32,0,0,0,0],"FLAG":0,"BASE":18}
Kisslink SP200 {"NAME":"Kisslink SP200","GPIO":[0,0,0,0,320,321,0,0,224,32,0,0,0,4704],"FLAG":0,"BASE":18}
@ -1408,8 +1416,8 @@ NGS Loop Track 16A {"NAME":"LOOP","GPIO":[0,0,320,0,0,0,0,0,0,32,0,224
Nightlight and AC Outlet {"NAME":"SWN03","GPIO":[32,0,0,0,0,0,1,1,416,0,0,224,0,0],"FLAG":0,"BASE":18}
Nishica SM-PW701I {"NAME":"SM-PW701I","GPIO":[1,1,1,1,1,1,1,1,224,288,32,1,1,1],"FLAG":0,"BASE":18}
Nivian {"NAME":"Nivian Smart Socket","GPIO":[0,0,320,0,0,2688,0,0,2624,32,2656,224,0,0],"FLAG":0,"BASE":18}
Nous 16A {"NAME":"NOUS A1T","GPIO":[32,0,0,0,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":49}
Nous A1 {"NAME":"NOUS A1","GPIO":[320,0,576,0,2656,2720,0,0,2624,32,0,224,0,0],"FLAG":0,"BASE":45}
Nous A1T 16A {"NAME":"NOUS A1T","GPIO":[32,0,0,0,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":49}
NX-SM112 {"NAME":"NX-SM112v3","GPIO":[0,0,0,0,2720,2656,0,0,576,32,2592,224,0,0],"FLAG":0,"BASE":45}
NX-SM200 {"NAME":"NX-SM200","GPIO":[320,0,0,0,0,2720,0,0,224,32,2656,321,2624,0],"FLAG":0,"BASE":18}
NX-SM210 {"NAME":"NX-SM210","GPIO":[0,32,0,0,0,0,0,0,0,320,224,0,0,0],"FLAG":0,"BASE":18}
@ -1417,7 +1425,7 @@ NX-SM223 {"NAME":"Smart Thurmm","GPIO":[0,32,0,0,0,0,0,0,0,3
Oakter Oak Plug Plus 16A {"NAME":"Oakter OakPlug Plus","GPIO":[0,0,0,0,224,0,0,0,544,320,0,0,0,0],"FLAG":0,"BASE":18}
Oakter OakPlug Mini 10A {"NAME":"Oakter OakPlug Mini","GPIO":[0,0,0,0,224,0,0,0,544,320,0,0,0,0],"FLAG":0,"BASE":18}
Oakter OakPlug Plus (old) {"NAME":"Oakter OakPlug Plus (old)","GPIO":[0,0,0,0,224,0,0,0,0,320,0,0,544,0],"FLAG":0,"BASE":18}
Obi Stecker {"NAME":"OBI Socket","GPIO":[1,1,0,1,288,224,0,0,290,1,32,0,1,4704],"FLAG":0,"BASE":51}
Obi Stecker {"NAME":"Euromate","GPIO":[1,1,1,1,288,224,1,1,289,1,32,1,1,1],"FLAG":0,"BASE":18}
Obi Stecker 2 {"NAME":"OBI Socket 2","GPIO":[0,0,0,0,224,32,0,0,320,289,0,0,0,0],"FLAG":0,"BASE":61}
OFFONG 16A {"NAME":"OFFONG P1","GPIO":[0,32,0,0,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":18}
Oittm Smart {"NAME":"Oittm","GPIO":[0,0,0,0,224,320,0,0,32,0,0,0,0,0],"FLAG":0,"BASE":1}
@ -1437,6 +1445,7 @@ OxaOxe NX-SP202 v2 {"NAME":"oxaoxe-dold","GPIO":[320,0,0,2624,32,2720,
OZWI Smart {"NAME":"OZWI Smart Plug","GPIO":[0,0,0,0,288,0,0,0,224,32,544,0,0,0],"FLAG":0,"BASE":18}
Panamalar Nightlight {"NAME":"Panamalar EWN0","GPIO":[32,0,0,0,0,0,1,1,416,0,0,224,0,0],"FLAG":0,"BASE":18}
Panamalar NX-SM200 {"NAME":"NX-SM200","GPIO":[0,0,0,0,320,2720,0,0,2624,32,2656,224,0,4704],"FLAG":0,"BASE":18}
Polycam Hohm Lanre 16A {"NAME":"SLV1910001","GPIO":[0,0,0,32,2720,2656,0,0,2624,576,224,0,0,0],"FLAG":0,"BASE":18}
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}
@ -1543,6 +1552,8 @@ Teckin SP27 {"NAME":"Teckin SP27","GPIO":[320,1,1,1,1,1,0,0,1,3
Tellur 16A 2 Ports {"NAME":"Tellur WiFi Smart Socket","GPIO":[0,0,0,2624,96,2688,0,0,224,33,2656,225,0,0],"FLAG":0,"BASE":18}
Tellur 1USB 10A {"NAME":"Tellur TTL331021","GPIO":[0,0,544,0,288,0,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
Tflag NX-SM100 {"NAME":"NX-SM100","GPIO":[320,0,0,0,0,2720,0,0,224,32,2656,321,2624,0],"FLAG":0,"BASE":18}
TH3D EZPlug V1 {"NAME":"EZPlug V1","GPIO":[0,0,0,32,0,0,0,0,0,320,224,0,0,0],"FLAG":0,"BASE":1}
TH3D EZPlug+ V1 {"NAME":"EZPlug+ V1","GPIO":[0,0,0,32,2720,2656,0,0,2624,320,224,0,0,0],"FLAG":0,"BASE":1}
TikLok TL650 {"NAME":"TikLok Mini","GPIO":[0,0,0,0,321,0,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
Timethinker C338 {"NAME":"C338","GPIO":[32,0,1,0,0,0,0,0,224,288,1,0,0,0],"FLAG":0,"BASE":1}
Timethinker TK04 {"NAME":"TimethinkerEU","GPIO":[1,1,1,1,32,1,0,0,1,288,224,1,0,0],"FLAG":0,"BASE":18}
@ -1562,6 +1573,7 @@ Treatlife Dimmable {"NAME":"DP20","GPIO":[0,2272,0,2304,0,0,0,0,0,0,0,
Treatlife Smart {"NAME":"Treatlife SK50","GPIO":[1,1,1,1,320,576,1,1,224,1,32,1,1,1],"FLAG":0,"BASE":18}
Tuya 16A Nightlight {"NAME":"Nightlight","GPIO":[225,0,320,0,226,227,0,0,34,64,0,224,0,0],"FLAG":0,"BASE":18}
U10 Series {"NAME":"WIFI-Socket","GPIO":[1,32,1,1,1,1,1,1,1,320,224,1,1,4704],"FLAG":0,"BASE":18}
Ucomen Night Light {"NAME":"UCOMEN Plug","GPIO":[0,0,0,0,544,320,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
UltraBrite {"NAME":"UltraBrite Smart Plug","GPIO":[1,1,1,1,288,289,1,1,224,32,1,1,1,1],"FLAG":0,"BASE":18}
Ultralink UL-P01W {"NAME":"UL-P01W","GPIO":[0,288,0,32,2720,2656,0,0,2624,544,224,0,0,0],"FLAG":0,"BASE":18}
Unlocked Automation 15A {"NAME":"UA 100","GPIO":[0,0,0,0,320,321,0,0,224,32,0,0,0,1],"FLAG":0,"BASE":18}
@ -1613,7 +1625,7 @@ XS-A18 {"NAME":"XS-A18","GPIO":[416,0,417,0,0,418,0,0,0,32
XS-A23 {"NAME":"XS-A23","GPIO":[320,1,0,2624,32,2720,0,0,0,33,2656,224,225,0],"FLAG":0,"BASE":45}
XS-SSA01 {"NAME":"XS-SSA01","GPIO":[1,0,0,1,0,0,0,0,320,32,1,224,1,0],"FLAG":0,"BASE":18}
XS-SSA01 v2 {"NAME":"XS-SSA01","GPIO":[1,32,1,1,1,1,0,0,320,1,1,224,1,0],"FLAG":0,"BASE":18}
XS-SSA05 {"NAME":"XS-SSA05","GPIO":[257,1,1,2624,1,2688,0,0,224,32,2656,258,1,4704],"FLAG":0,"BASE":18}
XS-SSA05 {"NAME":"XS-SSA05","GPIO":[320,0,0,2624,544,2688,0,0,224,32,2656,0,0,4704],"FLAG":0,"BASE":18}
XS-SSA06 {"NAME":"XS-SSA06","GPIO":[416,0,417,0,0,418,0,0,0,64,0,224,0,0],"FLAG":0,"BASE":18}
Yagala SWA9 {"NAME":"SWA9","GPIO":[0,0,0,0,288,224,0,0,0,32,0,0,0,0],"FLAG":0,"BASE":18}
Yelomin JH-G01E {"NAME":"Yelomin","GPIO":[0,3072,0,3104,0,0,0,0,32,320,224,0,0,0],"FLAG":0,"BASE":18}
@ -1706,12 +1718,13 @@ Konesky Type 1 {"NAME":"Konesky","GPIO":[0,0,0,0,228,225,0,0,227,3
Koogeek KLOE4 {"NAME":"Koogeek KLOE4","GPIO":[0,320,0,32,225,224,0,0,226,227,228,0,0,4704],"FLAG":0,"BASE":18}
Larkkey 4AC 4USB {"NAME":"LARKKEY Strip","GPIO":[0,544,0,32,225,224,0,0,226,227,228,0,0,0],"FLAG":0,"BASE":18}
LeFun SK2 {"NAME":"LeFun SK2","GPIO":[0,0,0,32,225,224,0,0,226,227,228,0,0,0],"FLAG":0,"BASE":18}
Lidl Silvercrest Zigbee {"NAME":"Lidl Silvercrest HG06338","GPIO":[0,288,0,0,225,226,0,0,32,224,576,0,0,0],"FLAG":0,"BASE":18}
LITEdge Smart Power Strip {"NAME":"LITEEdge Power Strip","GPIO":[227,0,0,0,288,289,0,0,224,32,225,226,228,0],"FLAG":0,"BASE":18}
Luminea 3AC+4USB 16A {"NAME":"Luminea-NX4473","GPIO":[0,320,0,32,225,224,0,0,0,226,227,0,0,0],"FLAG":0,"BASE":18}
Maxcio ZLD-34EU-W {"NAME":"MAXCIO","GPIO":[0,320,0,32,225,224,0,0,0,226,227,0,0,4704],"FLAG":0,"BASE":18}
Merkury Innovations Smart Surge {"NAME":"Merkury Power Strip MIC-SW002-199L","GPIO":[288,0,289,0,228,32,0,0,225,224,226,0,259,0],"FLAG":0,"BASE":18}
Merkury Innovations SmartSurge {"NAME":"Merkury MI-SW001","GPIO":[288,0,289,0,228,32,0,0,225,224,226,0,227,0],"FLAG":0,"BASE":18}
Meross 4AC 4USB {"NAME":"HamaStrip","GPIO":[0,544,0,32,225,224,0,0,226,227,228,0,0,0],"FLAG":0,"BASE":18}
Meross 4AC 4USB {"NAME":"MSS425F","GPIO":[0,544,0,32,225,224,0,0,226,227,260,0,0,0],"FLAG":0,"BASE":18}
Meross MSS425 {"NAME":"Meross MSS425","GPIO":[260,0,0,0,320,0,0,0,224,32,225,226,259,0],"FLAG":0,"BASE":18}
Mirabella Genio 4 Outlet Power Board with 2 USB {"NAME":"Genio i002340","GPIO":[320,0,0,0,224,225,0,0,226,32,227,228,0,0],"FLAG":0,"BASE":18}
Mirabella Genio Powerboard {"NAME":"Genio Powerboa","GPIO":[224,288,0,0,226,225,0,0,228,32,229,227,0,0],"FLAG":0,"BASE":18}
@ -1742,6 +1755,7 @@ Tellur 3AC 4USB {"NAME":"Tellur","GPIO":[0,320,0,32,225,224,0,0,0,2
Tessan {"NAME":"TESSAN A4L-BK","GPIO":[0,0,0,227,226,0,0,0,224,0,225,0,0,0],"FLAG":0,"BASE":18}
Tonbux SM-SO301-U {"NAME":"Tonbux SM-SO30","GPIO":[320,0,0,0,256,0,0,0,258,257,259,0,228,0],"FLAG":0,"BASE":18}
Useelink {"NAME":"Useelink","GPIO":[288,0,0,321,256,32,0,0,258,257,259,0,228,0],"FLAG":0,"BASE":18}
Useelink 4AC 2USB {"NAME":"306 Power Strip","GPIO":[576,0,576,291,259,32,0,0,257,258,256,0,228,0],"FLAG":0,"BASE":18}
Vivitar HA-1007 {"NAME":"Vivitar HA-1007 Power Strip","GPIO":[544,0,0,0,227,228,0,0,225,224,226,0,35,1],"FLAG":0,"BASE":18}
Vivitar HA-1007-AU {"NAME":"HA-1007-AU","GPIO":[320,32,0,322,256,321,0,0,258,257,259,0,228,0],"FLAG":0,"BASE":18}
wesmartify essentials 3-socket 2 USB {"NAME":"Essentials Smart Home 3-socket USB Power Strip","GPIO":[0,0,0,0,544,226,0,0,224,32,225,0,0,0],"FLAG":0,"BASE":18}
@ -1883,6 +1897,8 @@ iQtech 9W 800lm {"NAME":"iQ-Tech RGBCCT 9W 800LM","GPIO":[0,0,0,0,4
Jeeo TF-QPZ13 800lm {"NAME":"Jeeo","GPIO":[0,0,0,0,2912,416,0,0,417,2976,2944,0,0,0],"FLAG":0,"BASE":18}
Julun JL-021 5W Candle {"NAME":"E14 RGBCCT","GPIO":[0,0,0,0,419,420,0,0,417,418,416,0,0,0],"FLAG":0,"BASE":18}
Kauf esphome 10W {"NAME":"Kauf Bulb","GPIO":[0,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
Kauf esphome A15 5W {"NAME":"Kauf Bulb","GPIO":[0,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
Kauf esphome A19 7W {"NAME":"Kauf Bulb","GPIO":[0,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
KHSUIN BR30 13W 1300lm {"NAME":"KHSUIN 13W BR30","GPIO":[0,0,0,0,416,420,0,0,417,419,418,0,0,0],"FLAG":0,"BASE":18}
Kogan 10W 1050lm {"NAME":"Kogan RGB+CCT","GPIO":[1,1,1,0,416,419,1,1,417,452,418,1,1,1],"FLAG":0,"BASE":18}
Kohree 600lm {"NAME":"Kohree VHP560","GPIO":[0,0,0,0,416,420,0,0,417,419,418,0,0,0],"FLAG":0,"BASE":18}
@ -2133,6 +2149,7 @@ Nedis 6W 470lm {"NAME":"nedis Bulb","GPIO":[0,0,0,0,416,419,0,0,41
Nedis A60 800lm {"NAME":"Nedis RGBW","GPIO":[0,0,0,0,2912,416,0,0,0,2976,2944,0,0,4704],"FLAG":0,"BASE":18}
Nedis C10 350lm {"NAME":"Nedis WIFILC10","GPIO":[0,0,0,0,418,416,0,0,419,417,420,0,0,4704],"FLAG":0,"BASE":18}
Nedis PAR16 330lm {"NAME":"Nedis GU10","GPIO":[0,0,0,0,418,416,0,0,419,417,420,0,0,0],"FLAG":0,"BASE":18}
NGteco {"NAME":"NGTECO L100","GPIO":[0,0,0,0,0,418,0,0,417,0,416,419,0,0],"FLAG":0,"BASE":18}
Novostella UT55506 10W 1050lm {"NAME":"Novostella 10W","GPIO":[0,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
Onforu 7W 700lm {"NAME":"Onforu RGBW","GPIO":[0,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
Orbecco 5W 400lm {"NAME":"Orbecco Bulb","GPIO":[0,0,0,0,0,0,0,0,3008,0,3040,0,0,0],"FLAG":0,"BASE":27}
@ -2164,6 +2181,7 @@ Teckin 7.5W 800lm {"NAME":"Teckin SB60","GPIO":[0,0,0,0,416,419,0,0,4
Teckin SB50 800lm {"NAME":"Teckin SB50","GPIO":[0,0,0,0,419,0,0,0,417,418,416,0,0,0],"FLAG":0,"BASE":18}
Teckin SB50 v2 800lm {"NAME":"Teckin SB50","GPIO":[0,0,0,0,416,0,0,0,417,419,418,0,0,0],"FLAG":0,"BASE":18}
Teckin SB51 800lm {"NAME":"Teckin SB51","GPIO":[0,0,0,0,419,0,0,0,417,418,416,0,0,0],"FLAG":0,"BASE":18}
TH3D EZBulb V1 {"NAME":"EZBulb V1","GPIO":[0,0,0,0,416,419,0,0,417,0,418,0,0,0],"FLAG":0,"BASE":18}
TikLOk TL530 A19 7.5W 800lm {"NAME":"TikLOk WW-CW-L","GPIO":[0,0,0,0,0,416,0,0,417,0,0,0,0,0],"FLAG":0,"BASE":18}
TVLive 7.5W 800lm {"NAME":"TVLIVE RGBCW","GPIO":[0,0,0,0,416,419,0,0,417,420,418,0,0,0],"FLAG":0,"BASE":18}
Utorch LE7 600lm {"NAME":"Utorch LE7","GPIO":[0,0,0,0,0,417,0,0,418,0,419,416,0,0],"FLAG":0,"BASE":18}
@ -2573,9 +2591,9 @@ Sonoff TX T3 EU 2 Gang {"NAME":"Sonoff T3 TX 2CH","GPIO":[32,1,1,1,0,225,3
Sonoff TX T3 EU 3 Gang {"NAME":"TX T3EU3C","GPIO":[32,1,0,1,226,225,33,34,224,576,0,0,0,0],"FLAG":0,"BASE":30}
Sonoff TX T3 US 3 Gang {"NAME":"TX T3US3C","GPIO":[32,1,0,1,226,225,33,34,224,576,0,0,0,0],"FLAG":0,"BASE":30}
Sonoff TX T4 EU No Neutral 1 Gang {"NAME":"Sonoff T4 1CH","GPIO":[32,1,1,1,0,0,0,0,224,320,0,0,0,0],"FLAG":0,"BASE":28}
Sonoff TX Ultimate 1 Gang {"NAME":"TX Ultimate 1","GPIO":[0,0,7808,0,7840,3872,0,0,0,1376,0,7776,0,0,224,3232,0,480,3200,0,0,0,3840,0,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1,"CMND":"Pixels 27"}
Sonoff TX Ultimate 2 Gang {"NAME":"TX Ultimate 1","GPIO":[0,0,7808,0,7840,3872,0,0,0,1376,0,7776,0,225,224,3232,0,480,3200,0,0,0,3840,0,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1,"CMND":"Pixels 27"}
Sonoff TX Ultimate 3 Gang {"NAME":"TX Ultimate 1","GPIO":[0,0,7808,0,7840,3872,0,0,0,1376,0,7776,0,225,224,3232,0,480,3200,0,0,0,3840,226,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1,"CMND":"Pixels 27"}
Sonoff TX Ultimate 1 Gang {"NAME":"TX Ultimate 1","GPIO":[0,0,7808,0,7840,3872,0,0,0,1376,0,7776,0,0,224,3232,0,480,3200,0,0,0,3840,0,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1,"CMND":"Backlog Pixels 28"}
Sonoff TX Ultimate 2 Gang {"NAME":"TX Ultimate 2","GPIO":[0,0,7808,0,7840,3872,0,0,0,1376,0,7776,0,225,224,3232,0,480,3200,0,0,0,3840,0,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1,"CMND":"Backlog Pixels 28"}
Sonoff TX Ultimate 3 Gang {"NAME":"TX Ultimate 3","GPIO":[0,0,7808,0,7840,3872,0,0,0,1376,0,7776,0,225,224,3232,0,480,3200,0,0,0,3840,226,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1,"CMND":"Backlog Pixels 28"}
SPC Hera {"NAME":"SPC HERA","GPIO":[544,0,0,32,224,0,0,0,0,0,288,0,0,0],"FLAG":0,"BASE":18}
SRL 2 Gang {"NAME":"SRL 4WW Switch","GPIO":[0,0,0,0,0,33,0,0,32,224,0,225,0,0],"FLAG":0,"BASE":18}
SRL 4 Gang {"NAME":"SRL 4WW Switch","GPIO":[0,0,0,34,226,33,0,0,32,224,227,225,35,0],"FLAG":0,"BASE":18}
@ -2687,7 +2705,9 @@ 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 Channel Touch {"NAME":"NOUS L1T","GPIO":[544,0,1,32,0,0,0,0,0,224,288,0,0,0],"FLAG":0,"BASE":1}
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}
Nous 2 Channel Touch {"NAME":"NOUS L2T","GPIO":[544,289,1,32,225,33,0,0,0,224,288,0,0,0],"FLAG":0,"BASE":1}
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}
@ -2786,6 +2806,7 @@ Tuya Gas/Water {"NAME":"Valve FM101","GPIO":[320,0,0,0,224,0,0,0,0
Aigostar P40 {"NAME":"Aigostar 8433325212278","GPIO":[0,0,0,0,544,288,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
Aseer THWFS01 {"NAME":"ASEER-THWFS01","GPIO":[320,33,544,323,2720,2656,0,0,2624,225,321,224,32,0],"FLAG":0,"BASE":18}
Athom {"NAME":"Athom SK01","GPIO":[0,0,0,3104,0,32,0,0,224,320,0,0,0,0],"FLAG":0,"BASE":18}
Athom 16A UK {"NAME":"Athom SK03-TAS","GPIO":[0,0,0,3104,0,32,0,0,224,576,0,0,0,0],"FLAG":0,"BASE":18}
Bestten LO-2-W {"NAME":"BESTTEN LO-2-W","GPIO":[0,0,0,0,576,32,0,0,224,0,0,0,0,0],"FLAG":0,"BASE":18}
BingoElec 16A {"NAME":"BingoElecPower","GPIO":[0,0,0,0,288,289,1,1,224,32,0,0,1,1],"FLAG":0,"BASE":18}
BlitzWolf SHP8 {"NAME":"SHP8","GPIO":[0,320,0,32,2720,2656,0,0,2624,289,224,0,0,0],"FLAG":0,"BASE":64}
@ -2816,6 +2837,7 @@ Milfra UK Double USB Chager Twin {"NAME":"Milfra TBU02","GPIO":[0,0,0,0,288,33,
Moes 16A {"NAME":"WK-EU(FR/UK)16M","GPIO":[0,288,0,32,2720,2656,0,0,2624,224,0,0,0,0],"FLAG":0,"BASE":18}
Moes WWK Glass Panel {"NAME":"Smart Socket","GPIO":[0,0,0,0,288,289,0,0,224,32,0,0,0,0],"FLAG":0,"BASE":18}
Oittm 120 {"NAME":"Oittm WS01","GPIO":[32,0,0,0,0,2592,0,0,224,2656,2688,288,0,0],"FLAG":0,"BASE":18}
PFS Presa Smart {"NAME":"PFS_PresaSmart","GPIO":[1,1,1,1,288,289,1,1,224,32,0,1,1,1],"FLAG":0,"BASE":18}
PS-1607 {"NAME":"PS-1607","GPIO":[32,0,0,0,0,225,33,0,224,0,0,0,0,0],"FLAG":0,"BASE":18}
Smanergy KA10 {"NAME":"KA10","GPIO":[0,320,0,32,2720,2656,0,0,2624,289,224,0,0,0],"FLAG":0,"BASE":64}
Sonoff IW100 {"NAME":"Sonoff IW100","GPIO":[32,3072,0,3104,0,0,0,0,224,544,0,0,0,0],"FLAG":0,"BASE":41}

44
boards/esp32c6.json Normal file
View File

@ -0,0 +1,44 @@
{
"build": {
"arduino":{
"ldscript": "esp32c6_out.ld"
},
"core": "esp32",
"extra_flags": "-DESP32_4M -DESP32C6",
"f_cpu": "160000000L",
"f_flash": "80000000L",
"flash_mode": "dio",
"mcu": "esp32c6",
"variant": "esp32c6",
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv"
},
"connectivity": [
"wifi",
"bluetooth"
],
"debug": {
"openocd_target": "esp32c6.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "Espressif Generic ESP32-C6 >= 4M Flash, Tasmota 2880k Code/OTA, 320k FS",
"upload": {
"arduino": {
"flash_extra_images": [
[
"0x10000",
"variants/tasmota/tasmota32c6-safeboot.bin"
]
]
},
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 460800
},
"url": "https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32c6/esp32-c6-devkitc-1/index.html",
"vendor": "Espressif"
}

View File

@ -39,6 +39,8 @@ TasmotaSerial *tms_obj_list[16];
#ifdef ESP32
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_rom_gpio.h"
static uint32_t tasmota_serial_uart_bitmap = 0; // Assigned UARTs
@ -466,6 +468,7 @@ size_t TasmotaSerial::write(uint8_t b) {
return size;
}
#ifdef ESP8266
void IRAM_ATTR TasmotaSerial::rxRead(void) {
if (!m_nwmode) {
uint32_t start = ESP.getCycleCount();
@ -586,3 +589,4 @@ void IRAM_ATTR TasmotaSerial::rxRead(void) {
}
}
}
#endif // ESP8266

View File

@ -10,8 +10,8 @@
This library enables you to **send _and_ receive** infra-red signals on an [ESP8266](https://github.com/esp8266/Arduino) or an
[ESP32](https://github.com/espressif/arduino-esp32) using the [Arduino framework](https://www.arduino.cc/) using common 940nm IR LEDs and common IR receiver modules. e.g. TSOP{17,22,24,36,38,44,48}* demodulators etc.
## v2.8.5 Now Available
Version 2.8.5 of the library is now [available](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). You can view the [Release Notes](ReleaseNotes.md) for all the significant changes.
## v2.8.6 Now Available
Version 2.8.6 of the library is now [available](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). You can view the [Release Notes](ReleaseNotes.md) for all the significant changes.
#### Upgrading from pre-v2.0
Usage of the library has been slightly changed in v2.0. You will need to change your usage to work with v2.0 and beyond. You can read more about the changes required on our [Upgrade to v2.0](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Upgrading-to-v2.0) page.

View File

@ -11,8 +11,8 @@
Diese Programmbibliothek ermöglicht das **Senden _und_ Empfangen** von Infrarotsignalen mit [ESP8266](https://github.com/esp8266/Arduino)- und
[ESP32](https://github.com/espressif/arduino-esp32)-Mikrocontrollern mithilfe des [Arduino-Frameworks](https://www.arduino.cc/) und handelsüblichen 940nm Infrarot-LEDs undIR-Empfängermodulen, wie zum Beispiel TSOP{17,22,24,36,38,44,48}*-Demodulatoren.
## v2.8.5 jetzt verfügbar
Version 2.8.5 der Bibliothek ist nun [verfügbar](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). Die [Versionshinweise](ReleaseNotes.md) enthalten alle wichtigen Neuerungen.
## v2.8.6 jetzt verfügbar
Version 2.8.6 der Bibliothek ist nun [verfügbar](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). Die [Versionshinweise](ReleaseNotes.md) enthalten alle wichtigen Neuerungen.
#### Hinweis für Nutzer von Versionen vor v2.0
Die Benutzung der Bibliothek hat sich mit Version 2.0 leicht geändert. Einige Anpassungen im aufrufenden Code werden nötig sein, um mit Version ab 2.0 korrekt zu funktionieren. Mehr zu den Anpassungen finden sich auf unserer [Upgrade to v2.0](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Upgrading-to-v2.0)-Seite.

View File

@ -10,8 +10,8 @@
Cette librairie vous permetra de **recevoir et d'envoyer des signaux** infrarouge sur le protocole [ESP8266](https://github.com/esp8266/Arduino) ou sur le protocole
[ESP32](https://github.com/espressif/arduino-esp32) en utilisant le [Arduino framework](https://www.arduino.cc/) qui utilise la norme 940nm IR LEDs et le module basique de reception d'onde IR. Exemple : TSOP{17,22,24,36,38,44,48}* modules etc.
## v2.8.5 disponible
Version 2.8.5 de la libraire est maintenant [disponible](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). Vous pouvez voir le [Release Notes](ReleaseNotes.md) pour tous les changements importants.
## v2.8.6 disponible
Version 2.8.6 de la libraire est maintenant [disponible](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). Vous pouvez voir le [Release Notes](ReleaseNotes.md) pour tous les changements importants.
#### mise à jour depuis pre-v2.0
L'utilisation de la librairie à un peu changer depuis la version in v2.0. Si vous voulez l'utiliser vous devrez changer votre utilisation aussi. Vous pouvez vous renseigner sur les précondition d'utilisation ici : [Upgrade to v2.0](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Upgrading-to-v2.0) page.

View File

@ -10,8 +10,8 @@
Deze library maakt het mogelijk om Infraroodsignalen **te versturen en ontvangen** via het [Arduino framework](https://www.arduino.cc/) met veelgebruikte 940nm IR LEDs en IR ontvang modules. b.v. TSOP{17,22,24,36,38,44,48}* demodulatoren enz.
## v2.8.5 nu beschikbaar
Versie 2.8.5 van de bibliotheek is nu [beschikbaar](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). Bekijk de [Release Notes](ReleaseNotes.md) voor alle belangrijke veranderingen.
## v2.8.6 nu beschikbaar
Versie 2.8.6 van de bibliotheek is nu [beschikbaar](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). Bekijk de [Release Notes](ReleaseNotes.md) voor alle belangrijke veranderingen.
#### Upgraden vanaf pre-v2.0
Het gebruik van de bibliotheek is enigszins gewijzigd in v2.0. Je zult het gebruik moeten aanpassen om te kunnen werken met v2.0 en hoger. Je kunt meer lezen over de vereiste aanpassingen op onze [Upgraden naar v2.0](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Upgrading-to-v2.0) pagina.

View File

@ -1,5 +1,23 @@
# Release Notes
## _v2.8.6 (20230727)_
**[Bug Fixes]**
- Ensure `IRCoolixAC::toCommon()` returns `kNoTempValue` when no sensor temp is detected. (#2015 #2012)
- Fix compilation dependency of LG on Samsung send protocol (#2011 #2010)
- Fix missing parameter in call to `IRac::gree()` (#2008 #2007)
**[Features]**
- IRac: Ensure the `sleep` parameter is used for the `FUJITSU_AC` protocol. (#1992 #1991)
**[Misc]**
- Allow the BlynkIRRemote.ino code to compile again. (#2016)
- do not list WHIRLPOOL_AC unconditionally as supported protocol (#2003)
- IRUtils:typeToString() — simplify (#2002)
- Fix brand Green -> Gree (#1994)
- Fix undefined `std::round` compilation error (#1989)
## _v2.8.5 (20230508)_
**[Bug Fixes]**

View File

@ -1,6 +1,6 @@
<!--- WARNING: Do NOT edit this file directly.
It is generated by './tools/scrape_supported_devices.py'.
Last generated: Mon 08 May 2023 07:06:16 +0000 --->
Last generated: Thu 27 Jul 2023 05:37:11 +0000 --->
# IR Protocols supported by this library
| Protocol | Brand | Model | A/C Model | Detailed A/C Support |
@ -65,8 +65,7 @@
| [JVC](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_JVC.cpp) | **JVC** | PTU94023B remote | | - |
| [Kelon](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelon.cpp) | **[Hisense](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelon.h)** | AST-09UW4RVETG00A A/C (KELON168) | | Yes |
| [Kelon](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelon.cpp) | **[Kelon](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelon.h)** | AST-09UW4RVETG00A A/C (KELON168)<BR>DG11R2-01 remote (KELON168)<BR>ON/OFF 9000-12000 (KELON) | | Yes |
| [Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.cpp) | **[Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.h)** | YAP0F8 remote | | Yes |
| [Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.cpp) | **[Green](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.h)** | YAPOF3 remote | | Yes |
| [Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.cpp) | **[Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.h)** | YAP0F8 remote<BR>YAPOF3 remote | | Yes |
| [Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.cpp) | **[Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.h)** | KSV26CRC A/C<BR>KSV26HRC A/C<BR>KSV35CRC A/C<BR>KSV35HRC A/C<BR>KSV53HRC A/C<BR>KSV62HRC A/C<BR>KSV70CRC A/C<BR>KSV70HRC A/C<BR>KSV80HRC A/C<BR>YALIF Remote | | Yes |
| [Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.cpp) | **[Sharp](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.h)** | A5VEY A/C<BR>YB1FA remote | | Yes |
| [LG](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_LG.cpp) | **[General Electric](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_LG.h)** | 6711AR2853M Remote (LG - GE6711AR2853M)<BR>AG1BH09AW101 A/C (LG - GE6711AR2853M) | | Yes |

View File

@ -104,6 +104,8 @@
/* Comment this out to disable prints and save space */
#define BLYNK_PRINT Serial
#define BLYNK_TEMPLATE_ID "TMPL••••••••" // Made up values. Please Change.
#define BLYNK_TEMPLATE_NAME "My First Device" // Please Change.
#if defined(ESP8266)
#include <ESP8266WiFi.h>

View File

@ -64,4 +64,9 @@ build_flags = -D_IR_LOCALE_=zh-CN ; Chinese (Simplified)
; Build the library with all protocols disabled to flush out #if/#ifdef issues &
; any compiler warnings, by turning them into errors.
[env:shakedown_no_protocols]
build_flags = -D_IR_ENABLE_DEFAULT_=false -Werror -Wno-error=switch
build_flags =
${env.build_flags}
-Werror
-Wno-error=switch
-Wno-error=switch-unreachable
-D_IR_ENABLE_DEFAULT_=false

View File

@ -34,4 +34,5 @@ build_flags =
${env.build_flags}
-Werror
-Wno-error=switch
-Wno-error=switch-unreachable
-D_IR_ENABLE_DEFAULT_=false

View File

@ -1,6 +1,6 @@
{
"name": "IRremoteESP8266",
"version": "2.8.5",
"version": "2.8.6",
"keywords": "infrared, ir, remote, esp8266, esp32",
"description": "Send and receive infrared signals with multiple protocols (ESP8266/ESP32)",
"repository":

View File

@ -1,5 +1,5 @@
name=IRremoteESP8266
version=2.8.5
version=2.8.6
author=David Conran, Sebastien Warin, Mark Szabo, Ken Shirriff
maintainer=David Conran, Mark Szabo, Sebastien Warin, Roi Dayan, Massimiliano Pinto, Christian Nilsson
sentence=Send and receive infrared signals with multiple protocols (ESP8266/ESP32)

View File

@ -13,6 +13,11 @@
#include <string>
#endif
#include <cmath>
#if __cplusplus >= 201103L && defined(_GLIBCXX_USE_C99_MATH_TR1)
using std::roundf;
#else
using ::roundf;
#endif
#include "IRsend.h"
#include "IRremoteESP8266.h"
#include "IRtext.h"
@ -366,7 +371,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) {
#if SEND_YORK
case decode_type_t::YORK:
#endif
#if SEND_WHIRLPOOL_AC
case decode_type_t::WHIRLPOOL_AC:
#endif
return true;
default:
return false;
@ -489,9 +496,9 @@ void IRac::argo(IRArgoAC *ac,
ac->begin();
ac->setPower(on);
ac->setMode(ac->convertMode(mode));
ac->setTemp(static_cast<uint8_t>(std::round(degrees)));
ac->setTemp(static_cast<uint8_t>(roundf(degrees)));
if (sensorTemp != kNoTempValue) {
ac->setSensorTemp(static_cast<uint8_t>(std::round(sensorTemp)));
ac->setSensorTemp(static_cast<uint8_t>(roundf(sensorTemp)));
}
ac->setiFeel(iFeel);
ac->setFan(ac->convertFan(fan));
@ -537,7 +544,7 @@ void IRac::argoWrem3_ACCommand(IRArgoAC_WREM3 *ac, const bool on,
ac->setMode(ac->convertMode(mode));
ac->setTemp(degrees);
if (sensorTemp != kNoTempValue) {
ac->setSensorTemp(static_cast<uint8_t>(std::round(sensorTemp)));
ac->setSensorTemp(static_cast<uint8_t>(roundf(sensorTemp)));
}
ac->setiFeel(iFeel);
ac->setFan(ac->convertFan(fan));
@ -563,7 +570,7 @@ void IRac::argoWrem3_ACCommand(IRArgoAC_WREM3 *ac, const bool on,
void IRac::argoWrem3_iFeelReport(IRArgoAC_WREM3 *ac, const float sensorTemp) {
ac->begin();
ac->setMessageType(argoIrMessageType_t::IFEEL_TEMP_REPORT);
ac->setSensorTemp(static_cast<uint8_t>(std::round(sensorTemp)));
ac->setSensorTemp(static_cast<uint8_t>(roundf(sensorTemp)));
ac->send();
}
@ -738,7 +745,7 @@ void IRac::coolix(IRCoolixAC *ac,
// No Econo setting available.
// No Quiet setting available.
if (sensorTemp != kNoTempValue) {
ac->setSensorTemp(static_cast<uint8_t>(std::round(sensorTemp)));
ac->setSensorTemp(static_cast<uint8_t>(roundf(sensorTemp)));
} else {
ac->clearSensorTemp();
}
@ -1128,7 +1135,7 @@ void IRac::ecoclim(IREcoclimAc *ac,
ac->setTemp(degrees);
ac->setFan(ac->convertFan(fan));
if (sensorTemp != kNoTempValue) {
ac->setSensorTemp(static_cast<uint8_t>(std::round(sensorTemp)));
ac->setSensorTemp(static_cast<uint8_t>(roundf(sensorTemp)));
} else {
ac->setSensorTemp(degrees); //< Set to the desired temp
// until we can disable.
@ -1174,7 +1181,7 @@ void IRac::electra(IRElectraAc *ac,
ac->setMode(ac->convertMode(mode));
ac->setTemp(degrees);
if (sensorTemp != kNoTempValue) {
ac->setSensorTemp(static_cast<uint8_t>(std::round(sensorTemp)));
ac->setSensorTemp(static_cast<uint8_t>(roundf(sensorTemp)));
}
ac->setFan(ac->convertFan(fan));
ac->setSwingV(swingv != stdAc::swingv_t::kOff);
@ -2288,7 +2295,7 @@ void IRac::sanyo(IRSanyoAc *ac,
ac->setMode(ac->convertMode(mode));
ac->setTemp(degrees);
if (sensorTemp != kNoTempValue) {
ac->setSensorTemp(static_cast<uint8_t>(std::round(sensorTemp)));
ac->setSensorTemp(static_cast<uint8_t>(roundf(sensorTemp)));
} else {
ac->setSensorTemp(degrees); // Set the sensor temp to the desired
// (normal) temp.
@ -3229,7 +3236,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
fujitsu(&ac, (fujitsu_ac_remote_model_t)send.model, send.power, send.mode,
send.celsius, send.degrees, send.fanspeed,
send.swingv, send.swingh, send.quiet,
send.turbo, send.econo, send.filter, send.clean);
send.turbo, send.econo, send.filter, send.clean, send.sleep);
break;
}
#endif // SEND_FUJITSU_AC
@ -3249,7 +3256,8 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
_modulation);
gree(&ac, (gree_ac_remote_model_t)send.model, send.power, send.mode,
send.celsius, send.degrees, send.fanspeed, send.swingv, send.swingh,
send.turbo, send.econo, send.light, send.clean, send.sleep);
send.iFeel, send.turbo, send.econo, send.light, send.clean,
send.sleep);
break;
}
#endif // SEND_GREE

View File

@ -412,10 +412,6 @@ 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
@ -429,10 +425,6 @@ 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

@ -58,7 +58,7 @@
// Minor version number (x.X.x)
#define _IRREMOTEESP8266_VERSION_MINOR 8
// Patch version number (x.x.X)
#define _IRREMOTEESP8266_VERSION_PATCH 5
#define _IRREMOTEESP8266_VERSION_PATCH 6
// Macro to convert version info into an integer
#define _IRREMOTEESP8266_VERSION_VAL(major, minor, patch) \
(((major) << 16) | ((minor) << 8) | (patch))

View File

@ -310,11 +310,12 @@ class IRsend {
void sendSherwood(uint64_t data, uint16_t nbits = kSherwoodBits,
uint16_t repeat = kSherwoodMinRepeat);
#endif
#if SEND_SAMSUNG
// `sendSAMSUNG()` is required by `sendLG()`
#if (SEND_SAMSUNG || SEND_LG)
void sendSAMSUNG(const uint64_t data, const uint16_t nbits = kSamsungBits,
const uint16_t repeat = kNoRepeat);
uint32_t encodeSAMSUNG(const uint8_t customer, const uint8_t command);
#endif
#endif // (SEND_SAMSUNG || SEND_LG)
#if SEND_SAMSUNG36
void sendSamsung36(const uint64_t data, const uint16_t nbits = kSamsung36Bits,
const uint16_t repeat = kNoRepeat);

View File

@ -145,16 +145,12 @@ String typeToString(const decode_type_t protocol, const bool isRepeat) {
result = kUnknownStr;
} else {
auto *ptr = reinterpret_cast<const char*>(kAllProtocolNamesStr);
if (protocol > kLastDecodeType || protocol == decode_type_t::UNKNOWN) {
result = kUnknownStr;
} else {
for (uint16_t i = 0; i <= protocol && STRLEN(ptr); i++) {
if (i == protocol) {
result = FPSTR(ptr);
break;
}
ptr += STRLEN(ptr) + 1;
for (uint16_t i = 0; i <= protocol && STRLEN(ptr); i++) {
if (i == protocol) {
result = FPSTR(ptr);
break;
}
ptr += STRLEN(ptr) + 1;
}
}
if (isRepeat) {

View File

@ -549,6 +549,9 @@ stdAc::state_t IRCoolixAC::toCommon(const stdAc::state_t *prev) const {
result.mode = toCommonMode(getMode());
result.degrees = getTemp();
result.sensorTemperature = getSensorTemp();
if (result.sensorTemperature == kCoolixSensorTempIgnoreCode) {
result.sensorTemperature = kNoTempValue;
}
result.iFeel = getZoneFollow();
result.fanspeed = toCommonFanSpeed(getFan());
return result;

View File

@ -13,7 +13,7 @@
// Brand: Kelvinator, Model: KSV70CRC A/C
// Brand: Kelvinator, Model: KSV70HRC A/C
// Brand: Kelvinator, Model: KSV80HRC A/C
// Brand: Green, Model: YAPOF3 remote
// Brand: Gree, Model: YAPOF3 remote
// Brand: Gree, Model: YAP0F8 remote
// Brand: Sharp, Model: YB1FA remote
// Brand: Sharp, Model: A5VEY A/C

View File

@ -82,7 +82,8 @@ using irutils::addTempToString;
using irutils::addToggleToString;
using irutils::minsToString;
#if SEND_SAMSUNG
// This sending protocol is used by some other protocols. e.g. LG.
#if (SEND_SAMSUNG || SEND_LG)
/// Send a 32-bit Samsung formatted message.
/// Status: STABLE / Should be working.
/// @param[in] data The message to be sent.
@ -112,7 +113,7 @@ uint32_t IRsend::encodeSAMSUNG(const uint8_t customer, const uint8_t command) {
return ((revcommand ^ 0xFF) | (revcommand << 8) | (revcustomer << 16) |
(revcustomer << 24));
}
#endif
#endif // (SEND_SAMSUNG || SEND_LG)
#if DECODE_SAMSUNG
/// Decode the supplied Samsung 32-bit message.

View File

@ -669,7 +669,8 @@ TEST(TestIRac, Fujitsu) {
false, // Turbo (Powerful)
false, // Econo
true, // Filter
true); // Clean
true, // Clean
-1); // Sleep
ASSERT_EQ(ardb1_expected, ac.toString());
ac._irsend.makeDecodeResult();
EXPECT_TRUE(capture.decode(&ac._irsend.capture));
@ -719,7 +720,8 @@ TEST(TestIRac, Fujitsu) {
false, // Turbo (Powerful)
false, // Econo
true, // Filter
true); // Clean
true, // Clean
-1); // Sleep
ASSERT_EQ(arry4_expected, ac.toString());
ac._irsend.makeDecodeResult();
EXPECT_TRUE(capture.decode(&ac._irsend.capture));
@ -742,8 +744,9 @@ TEST(TestIRac, Fujitsu) {
false, // Quiet
false, // Turbo (Powerful)
false, // Econo
false, // Filter
false); // Clean
false, // Filter
false, // Clean
-1); // Sleep
ASSERT_EQ(arrew4e_expected, ac.toString());
ac._irsend.makeDecodeResult();
EXPECT_TRUE(capture.decode(&ac._irsend.capture));

View File

@ -492,6 +492,9 @@ TEST(TestCoolixACClass, SetGetClearSensorTempAndZoneFollow) {
EXPECT_EQ(
"Power: On, Mode: 3 (Heat), Fan: 6 (Zone Follow), Temp: 24C, "
"Zone Follow: On, Sensor Temp: 19C", ac.toString());
// For #Issue2012
EXPECT_EQ(19, ac.getSensorTemp());
EXPECT_EQ(19, ac.toCommon().sensorTemperature);
}
TEST(TestCoolixACClass, SpecialModesAndReset) {
@ -1068,3 +1071,36 @@ TEST(TestDecodeCoolix48, SyntheticSelfDecode) {
"m552s5244",
irsend.outputStr());
}
// Test for issue https://github.com/crankyoldgit/IRremoteESP8266/issues/2012#issuecomment-1650098971
TEST(TestCoolixACClass, Issue2012) {
IRrecv irrecv(kGpioUnused);
IRCoolixAC ac(kGpioUnused);
ac.stateReset();
ac.setRaw(0xB21FD8);
EXPECT_EQ(
"Power: On, Mode: 2 (Auto), Fan: 0 (Auto0), Temp: 26C, "
"Zone Follow: Off, Sensor Temp: Off",
ac.toString());
EXPECT_EQ(kNoTempValue, ac.toCommon().sensorTemperature);
ac.setRaw(0xB21F98);
EXPECT_EQ(
"Power: On, Mode: 2 (Auto), Fan: 0 (Auto0), Temp: 27C, "
"Zone Follow: Off, Sensor Temp: Off",
ac.toString());
EXPECT_EQ(kNoTempValue, ac.toCommon().sensorTemperature);
ac.setRaw(0xB21F88);
EXPECT_EQ(
"Power: On, Mode: 2 (Auto), Fan: 0 (Auto0), Temp: 28C, "
"Zone Follow: Off, Sensor Temp: Off",
ac.toString());
EXPECT_EQ(kNoTempValue, ac.toCommon().sensorTemperature);
ac.setRaw(0xB27BE0);
EXPECT_EQ(
"Power: Off",
ac.toString());
EXPECT_EQ(kNoTempValue, ac.toCommon().sensorTemperature);
}

View File

@ -1,6 +1,6 @@
{
"name": "IRremoteESP8266",
"version": "2.8.5",
"version": "2.8.6",
"keywords": "infrared, ir, remote, esp8266, esp32",
"description": "Send and receive infrared signals with multiple protocols (ESP8266/ESP32)",
"repository":

View File

@ -0,0 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.0] - 2021-11-17
Initial release
[0.1.0]: https://github.com/Sensirion/arduino-i2c-sgp41/releases/tag/0.1.0

View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2021, Sensirion AG
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 holder 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 AND CONTRIBUTORS "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 OR CONTRIBUTORS 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

@ -0,0 +1,78 @@
# Sensirion I2C SGP41 Arduino Library
This is the Sensirion SGP41 library for Arduino using the
modules I2C interface.
[<center><img src="images/SGP41.png" width="300px"></center>](https://www.sensirion.com/en/environmental-sensors/gas-sensors/sgp41)
Click [here](https://www.sensirion.com/en/environmental-sensors/gas-sensors/sgp41) to learn more about the SGP41 sensor.
# Installation
To install, download the latest release as .zip file and add it to your
[Arduino IDE](http://www.arduino.cc/en/main/software) via
Sketch => Include Library => Add .ZIP Library...
Don't forget to **install the dependencies** listed below the same way via `Add
.ZIP Library`
Note: Installation via the Arduino Library Manager is coming soon.
# Dependencies
* [Sensirion Core](https://github.com/Sensirion/arduino-core)
# Quick Start
1. Connect the SGP41 Sensor to your Arduino board's standard
I2C bus. Check the pinout of your Arduino board to find the correct pins.
The pinout of the SGP41 Sensor board can be found in the
data sheet.
* **VDD** of the SEK-SGP41 to the **3.3V** of your Arduino board
* **GND** of the SEK-SGP41 to the **GND** of your Arduino board
* **SCL** of the SEK-SGP41 to the **SCL** of your Arduino board
* **SDA** of the SEK-SGP41 to the **SDA** of your Arduino board
2. Open the `exampleUsage` sample project within the Arduino IDE
File => Examples => Sensirion I2C SGP41 => exampleUsage
3. Click the `Upload` button in the Arduino IDE or
Sketch => Upload
4. When the upload process has finished, open the `Serial Monitor` or `Serial
Plotter` via the `Tools` menu to observe the measurement values. Note that
the `Baud Rate` in the corresponding window has to be set to `115200 baud`.
# Contributing
**Contributions are welcome!**
We develop and test this driver using our company internal tools (version
control, continuous integration, code review etc.) and automatically
synchronize the master branch with GitHub. But this doesn't mean that we don't
respond to issues or don't accept pull requests on GitHub. In fact, you're very
welcome to open issues or create pull requests :)
This Sensirion library uses
[`clang-format`](https://releases.llvm.org/download.html) to standardize the
formatting of all our `.cpp` and `.h` files. Make sure your contributions are
formatted accordingly:
The `-i` flag will apply the format changes to the files listed.
```bash
clang-format -i src/*.cpp src/*.h
```
Note that differences from this formatting will result in a failed build until
they are fixed.
# License
See [LICENSE](LICENSE).

View File

@ -0,0 +1,124 @@
/*
* I2C-Generator: 0.3.0
* Yaml Version: 0.1.0
* Template Version: 0.7.0-62-g3d691f9
*/
/*
* Copyright (c) 2021, Sensirion AG
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * 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.
*
* * Neither the name of Sensirion AG 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 AND CONTRIBUTORS "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 OR CONTRIBUTORS 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.
*/
#include <Arduino.h>
#include <SensirionI2CSgp41.h>
#include <Wire.h>
SensirionI2CSgp41 sgp41;
// time in seconds needed for NOx conditioning
uint16_t conditioning_s = 10;
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(100);
}
Wire.begin();
uint16_t error;
char errorMessage[256];
sgp41.begin(Wire);
uint16_t serialNumber[3];
uint8_t serialNumberSize = 3;
error = sgp41.getSerialNumber(serialNumber, serialNumberSize);
if (error) {
Serial.print("Error trying to execute getSerialNumber(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else {
Serial.print("SerialNumber:");
Serial.print("0x");
for (size_t i = 0; i < serialNumberSize; i++) {
uint16_t value = serialNumber[i];
Serial.print(value < 4096 ? "0" : "");
Serial.print(value < 256 ? "0" : "");
Serial.print(value < 16 ? "0" : "");
Serial.print(value, HEX);
}
Serial.println();
}
uint16_t testResult;
error = sgp41.executeSelfTest(testResult);
if (error) {
Serial.print("Error trying to execute executeSelfTest(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else if (testResult != 0xD400) {
Serial.print("executeSelfTest failed with error: ");
Serial.println(testResult);
}
}
void loop() {
uint16_t error;
char errorMessage[256];
uint16_t defaultRh = 0x8000;
uint16_t defaultT = 0x6666;
uint16_t srawVoc = 0;
uint16_t srawNox = 0;
delay(1000);
if (conditioning_s > 0) {
// During NOx conditioning (10s) SRAW NOx will remain 0
error = sgp41.executeConditioning(defaultRh, defaultT, srawVoc);
conditioning_s--;
} else {
// Read Measurement
error = sgp41.measureRawSignals(defaultRh, defaultT, srawVoc, srawNox);
}
if (error) {
Serial.print("Error trying to execute measureRawSignals(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else {
Serial.print("SRAW_VOC:");
Serial.print(srawVoc);
Serial.print("\t");
Serial.print("SRAW_NOx:");
Serial.println(srawNox);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

View File

@ -0,0 +1,27 @@
#######################################
# Syntax Coloring Map
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SensirionI2CSgp41 KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
executeConditioning KEYWORD2
measureRawSignals KEYWORD2
executeSelfTest KEYWORD2
turnHeaterOff KEYWORD2
getSerialNumber KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
sgp41 KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -0,0 +1,10 @@
name=Sensirion I2C SGP41
version=0.1.0
author=Sensirion
maintainer=Sensirion
sentence=Library for the SGP41 sensor family by Sensirion
paragraph=Enables you to use the SGP41 via I2C.
url=https://github.com/Sensirion/arduino-i2c-sgp41
category=Sensors
depends=Sensirion Core
includes=SensirionI2CSgp41.h

View File

@ -0,0 +1,219 @@
/*
* Copyright (c) 2021, Sensirion AG
* Copyright (c) 2023, Andrew Klaus (Removing delay functions)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * 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.
*
* * Neither the name of Sensirion AG 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 AND CONTRIBUTORS "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 OR CONTRIBUTORS 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.
*/
#include "SensirionI2CSgp4x.h"
#include "Arduino.h"
#include "SensirionCore.h"
#include <Wire.h>
#define SGP4X_I2C_ADDRESS 0x59
SensirionI2CSgp4x::SensirionI2CSgp4x() {
}
void SensirionI2CSgp4x::begin(TwoWire& i2cBus) {
_i2cBus = &i2cBus;
}
uint16_t SensirionI2CSgp4x::sendConditioningCmd(uint16_t defaultRh,
uint16_t defaultT) {
uint16_t error;
uint8_t buffer[8];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x2612, buffer, 8);
error = txFrame.addUInt16(defaultRh);
error |= txFrame.addUInt16(defaultT);
if (error) {
return error;
}
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
return error;
}
uint16_t SensirionI2CSgp4x::readConditioningValue(uint16_t& srawVoc){
// This must run at least 50ms after initiateConditioning
uint16_t error;
uint8_t buffer[8];
SensirionI2CRxFrame rxFrame(buffer, 8);
error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 3,
rxFrame, *_i2cBus);
if (error) {
return error;
}
error |= rxFrame.getUInt16(srawVoc);
return error;
}
uint16_t SensirionI2CSgp4x::sendRawSignalsCmd(uint16_t relativeHumidity,
uint16_t temperature) {
uint16_t error;
uint8_t buffer[8];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x2619, buffer, 8);
error = txFrame.addUInt16(relativeHumidity);
error |= txFrame.addUInt16(temperature);
if (error) {
return error;
}
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
return error;
}
uint16_t SensirionI2CSgp4x::readRawSignalsValue(uint16_t& srawVoc,
uint16_t& srawNox) {
// This must run 50ms after initiateRawSignals
uint16_t error;
uint8_t buffer[6];
SensirionI2CRxFrame rxFrame(buffer, 6);
error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 6,
rxFrame, *_i2cBus);
if (error) {
return error;
}
error |= rxFrame.getUInt16(srawVoc);
error |= rxFrame.getUInt16(srawNox);
return error;
}
uint16_t SensirionI2CSgp4x::sendRawSignalCmd(uint16_t relativeHumidity,
uint16_t temperature) {
uint16_t error;
uint8_t buffer[8];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x260F, buffer, 8);
error = txFrame.addUInt16(relativeHumidity);
error |= txFrame.addUInt16(temperature);
if (error) {
return error;
}
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
return error;
}
uint16_t SensirionI2CSgp4x::readRawSignalValue(uint16_t& srawVoc) {
uint16_t error;
uint8_t buffer[8];
SensirionI2CRxFrame rxFrame(buffer, 8);
error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 3,
rxFrame, *_i2cBus);
if (error) {
return error;
}
error |= rxFrame.getUInt16(srawVoc);
return error;
}
uint16_t SensirionI2CSgp4x::sendSelfTestCmd() {
uint16_t error;
uint8_t buffer[3];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x280E, buffer, 3);
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
return error;
}
uint16_t SensirionI2CSgp4x::readSelfTestValue(uint16_t& testResult) {
// Must run 320ms after initiateSelfTest
uint16_t error;
uint8_t buffer[3];
SensirionI2CRxFrame rxFrame(buffer, 3);
error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 3,
rxFrame, *_i2cBus);
if (error) {
return error;
}
error |= rxFrame.getUInt16(testResult);
return error;
}
uint16_t SensirionI2CSgp4x::turnHeaterOff() {
uint16_t error;
uint8_t buffer[2];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x3615, buffer, 2);
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
delay(1);
return error;
}
uint16_t SensirionI2CSgp4x::getSerialNumber(uint16_t serialNumber[],
uint8_t serialNumberSize) {
uint16_t error;
uint8_t buffer[9];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x3682, buffer, 9);
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
if (error) {
return error;
}
delay(1);
SensirionI2CRxFrame rxFrame(buffer, 9);
error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 9,
rxFrame, *_i2cBus);
if (error) {
return error;
}
error |= rxFrame.getUInt16(serialNumber[0]);
error |= rxFrame.getUInt16(serialNumber[1]);
error |= rxFrame.getUInt16(serialNumber[2]);
return error;
}

View File

@ -0,0 +1,145 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* I2C-Generator: 0.3.0
* Yaml Version: 0.1.0
* Template Version: 0.7.0-62-g3d691f9
*/
/*
* Copyright (c) 2021, Sensirion AG
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * 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.
*
* * Neither the name of Sensirion AG 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 AND CONTRIBUTORS "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 OR CONTRIBUTORS 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.
*/
#ifndef SENSIRIONI2CSGP4X_H
#define SENSIRIONI2CSGP4X_H
#include <Wire.h>
#include <SensirionCore.h>
class SensirionI2CSgp4x {
public:
SensirionI2CSgp4x();
/**
* begin() - Initializes the SensirionI2CSgp4x class.
*
* @param serial Arduino stream object to be communicated with.
*
*/
void begin(TwoWire& i2cBus);
/**
* executeConditioning() - This command starts the conditioning, i.e., the
* VOC pixel will be operated at the same temperature as it is by calling
* the sgp41_measure_raw command while the NOx pixel will be operated at a
* different temperature for conditioning. This command returns only the
* measured raw signal of the VOC pixel SRAW_VOC as 2 bytes (+ 1 CRC byte).
*
* @param defaultRh Default conditions for relative humidty.
*
* @param defaultT Default conditions for temperature.
*
* @param srawVoc u16 unsigned integer directly provides the raw signal
* SRAW_VOC in ticks which is proportional to the logarithm of the
* resistance of the sensing element.
*
* @return 0 on success, an error code otherwise
*/
uint16_t sendConditioningCmd(uint16_t defaultRh, uint16_t defaultT);
uint16_t readConditioningValue(uint16_t& srawVoc);
/**
* measureRawSignals() - This command starts/continues the VOC+NOx
* measurement mode
*
* @param relativeHumidity Leaves humidity compensation disabled by sending
* the default value 0x8000 (50%RH) or enables humidity compensation when
* sending the relative humidity in ticks (ticks = %RH * 65535 / 100)
*
* @param temperature Leaves humidity compensation disabled by sending the
* default value 0x6666 (25 degC) or enables humidity compensation when
* sending the temperature in ticks (ticks = (degC + 45) * 65535 / 175)
*
* @param srawVoc u16 unsigned integer directly provides the raw signal
* SRAW_VOC in ticks which is proportional to the logarithm of the
* resistance of the sensing element.
*
* @param srawNox u16 unsigned integer directly provides the raw signal
* SRAW_NOX in ticks which is proportional to the logarithm of the
* resistance of the sensing element.
*
* @return 0 on success, an error code otherwise
*/
uint16_t sendRawSignalsCmd(uint16_t relativeHumidity, uint16_t temperature);
uint16_t readRawSignalsValue(uint16_t& srawVoc, uint16_t& srawNox);
uint16_t sendRawSignalCmd(uint16_t relativeHumidity, uint16_t temperature);
uint16_t readRawSignalValue(uint16_t& srawVoc);
/**
* executeSelfTest() - This command triggers the built-in self-test checking
* for integrity of both hotplate and MOX material and returns the result of
* this test as 2 bytes
*
* @param testResult 0xXX 0xYY: ignore most significant byte 0xXX. The four
* least significant bits of the least significant byte 0xYY provide
* information if the self-test has or has not passed for each individual
* pixel. All zero mean all tests passed successfully. Check the datasheet
* for more detailed information.
*
* @return 0 on success, an error code otherwise
*/
uint16_t sendSelfTestCmd(void);
uint16_t readSelfTestValue(uint16_t& testResult);
/**
* turnHeaterOff() - This command turns the hotplate off and stops the
* measurement. Subsequently, the sensor enters the idle mode.
*
* @return 0 on success, an error code otherwise
*/
uint16_t turnHeaterOff(void);
/**
* getSerialNumber() - This command provides the decimal serial number of
* the SGP41 chip by returning 3x2 bytes.
*
* @param serialNumber 48-bit unique serial number
*
* @return 0 on success, an error code otherwise
*/
uint16_t getSerialNumber(uint16_t serialNumber[], uint8_t serialNumberSize);
private:
TwoWire* _i2cBus = nullptr;
};
#endif /* SENSIRIONI2CSGP4X_H */

View File

@ -0,0 +1,582 @@
/*
* Copyright (c) 2022, Sensirion AG
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * 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.
*
* * Neither the name of Sensirion AG 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 AND CONTRIBUTORS "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 OR CONTRIBUTORS 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.
*/
#include "sensirion_gas_index_algorithm.h"
#include <math.h>
static void GasIndexAlgorithm__init_instances(GasIndexAlgorithmParams* params);
static void GasIndexAlgorithm__mean_variance_estimator__set_parameters(
GasIndexAlgorithmParams* params);
static void GasIndexAlgorithm__mean_variance_estimator__set_states(
GasIndexAlgorithmParams* params, float mean, float std, float uptime_gamma);
static float GasIndexAlgorithm__mean_variance_estimator__get_std(
const GasIndexAlgorithmParams* params);
static float GasIndexAlgorithm__mean_variance_estimator__get_mean(
const GasIndexAlgorithmParams* params);
static bool GasIndexAlgorithm__mean_variance_estimator__is_initialized(
GasIndexAlgorithmParams* params);
static void GasIndexAlgorithm__mean_variance_estimator___calculate_gamma(
GasIndexAlgorithmParams* params);
static void GasIndexAlgorithm__mean_variance_estimator__process(
GasIndexAlgorithmParams* params, float sraw);
static void
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
GasIndexAlgorithmParams* params, float X0, float K);
static float GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
GasIndexAlgorithmParams* params, float sample);
static void
GasIndexAlgorithm__mox_model__set_parameters(GasIndexAlgorithmParams* params,
float SRAW_STD, float SRAW_MEAN);
static float
GasIndexAlgorithm__mox_model__process(GasIndexAlgorithmParams* params,
float sraw);
static void GasIndexAlgorithm__sigmoid_scaled__set_parameters(
GasIndexAlgorithmParams* params, float X0, float K, float offset_default);
static float
GasIndexAlgorithm__sigmoid_scaled__process(GasIndexAlgorithmParams* params,
float sample);
static void GasIndexAlgorithm__adaptive_lowpass__set_parameters(
GasIndexAlgorithmParams* params);
static float
GasIndexAlgorithm__adaptive_lowpass__process(GasIndexAlgorithmParams* params,
float sample);
void GasIndexAlgorithm_init_with_sampling_interval(
GasIndexAlgorithmParams* params, int32_t algorithm_type,
float sampling_interval) {
params->mAlgorithm_Type = algorithm_type;
params->mSamplingInterval = sampling_interval;
if ((algorithm_type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) {
params->mIndex_Offset = GasIndexAlgorithm_NOX_INDEX_OFFSET_DEFAULT;
params->mSraw_Minimum = GasIndexAlgorithm_NOX_SRAW_MINIMUM;
params->mGating_Max_Duration_Minutes =
GasIndexAlgorithm_GATING_NOX_MAX_DURATION_MINUTES;
params->mInit_Duration_Mean = GasIndexAlgorithm_INIT_DURATION_MEAN_NOX;
params->mInit_Duration_Variance =
GasIndexAlgorithm_INIT_DURATION_VARIANCE_NOX;
params->mGating_Threshold = GasIndexAlgorithm_GATING_THRESHOLD_NOX;
} else {
params->mIndex_Offset = GasIndexAlgorithm_VOC_INDEX_OFFSET_DEFAULT;
params->mSraw_Minimum = GasIndexAlgorithm_VOC_SRAW_MINIMUM;
params->mGating_Max_Duration_Minutes =
GasIndexAlgorithm_GATING_VOC_MAX_DURATION_MINUTES;
params->mInit_Duration_Mean = GasIndexAlgorithm_INIT_DURATION_MEAN_VOC;
params->mInit_Duration_Variance =
GasIndexAlgorithm_INIT_DURATION_VARIANCE_VOC;
params->mGating_Threshold = GasIndexAlgorithm_GATING_THRESHOLD_VOC;
}
params->mIndex_Gain = GasIndexAlgorithm_INDEX_GAIN;
params->mTau_Mean_Hours = GasIndexAlgorithm_TAU_MEAN_HOURS;
params->mTau_Variance_Hours = GasIndexAlgorithm_TAU_VARIANCE_HOURS;
params->mSraw_Std_Initial = GasIndexAlgorithm_SRAW_STD_INITIAL;
GasIndexAlgorithm_reset(params);
}
void GasIndexAlgorithm_init(GasIndexAlgorithmParams* params,
int32_t algorithm_type) {
GasIndexAlgorithm_init_with_sampling_interval(
params, algorithm_type, GasIndexAlgorithm_DEFAULT_SAMPLING_INTERVAL);
}
void GasIndexAlgorithm_reset(GasIndexAlgorithmParams* params) {
params->mUptime = 0.f;
params->mSraw = 0.f;
params->mGas_Index = 0;
GasIndexAlgorithm__init_instances(params);
}
static void GasIndexAlgorithm__init_instances(GasIndexAlgorithmParams* params) {
GasIndexAlgorithm__mean_variance_estimator__set_parameters(params);
GasIndexAlgorithm__mox_model__set_parameters(
params, GasIndexAlgorithm__mean_variance_estimator__get_std(params),
GasIndexAlgorithm__mean_variance_estimator__get_mean(params));
if ((params->mAlgorithm_Type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) {
GasIndexAlgorithm__sigmoid_scaled__set_parameters(
params, GasIndexAlgorithm_SIGMOID_X0_NOX,
GasIndexAlgorithm_SIGMOID_K_NOX,
GasIndexAlgorithm_NOX_INDEX_OFFSET_DEFAULT);
} else {
GasIndexAlgorithm__sigmoid_scaled__set_parameters(
params, GasIndexAlgorithm_SIGMOID_X0_VOC,
GasIndexAlgorithm_SIGMOID_K_VOC,
GasIndexAlgorithm_VOC_INDEX_OFFSET_DEFAULT);
}
GasIndexAlgorithm__adaptive_lowpass__set_parameters(params);
}
void GasIndexAlgorithm_get_sampling_interval(
const GasIndexAlgorithmParams* params, float* sampling_interval) {
*sampling_interval = params->mSamplingInterval;
}
void GasIndexAlgorithm_get_states(const GasIndexAlgorithmParams* params,
float* state0, float* state1) {
*state0 = GasIndexAlgorithm__mean_variance_estimator__get_mean(params);
*state1 = GasIndexAlgorithm__mean_variance_estimator__get_std(params);
return;
}
void GasIndexAlgorithm_set_states(GasIndexAlgorithmParams* params, float state0,
float state1) {
GasIndexAlgorithm__mean_variance_estimator__set_states(
params, state0, state1, GasIndexAlgorithm_PERSISTENCE_UPTIME_GAMMA);
GasIndexAlgorithm__mox_model__set_parameters(
params, GasIndexAlgorithm__mean_variance_estimator__get_std(params),
GasIndexAlgorithm__mean_variance_estimator__get_mean(params));
params->mSraw = state0;
}
void GasIndexAlgorithm_set_tuning_parameters(
GasIndexAlgorithmParams* params, int32_t index_offset,
int32_t learning_time_offset_hours, int32_t learning_time_gain_hours,
int32_t gating_max_duration_minutes, int32_t std_initial,
int32_t gain_factor) {
params->mIndex_Offset = ((float)(index_offset));
params->mTau_Mean_Hours = ((float)(learning_time_offset_hours));
params->mTau_Variance_Hours = ((float)(learning_time_gain_hours));
params->mGating_Max_Duration_Minutes =
((float)(gating_max_duration_minutes));
params->mSraw_Std_Initial = ((float)(std_initial));
params->mIndex_Gain = ((float)(gain_factor));
GasIndexAlgorithm__init_instances(params);
}
void GasIndexAlgorithm_get_tuning_parameters(
const GasIndexAlgorithmParams* params, int32_t* index_offset,
int32_t* learning_time_offset_hours, int32_t* learning_time_gain_hours,
int32_t* gating_max_duration_minutes, int32_t* std_initial,
int32_t* gain_factor) {
*index_offset = ((int32_t)(params->mIndex_Offset));
*learning_time_offset_hours = ((int32_t)(params->mTau_Mean_Hours));
*learning_time_gain_hours = ((int32_t)(params->mTau_Variance_Hours));
*gating_max_duration_minutes =
((int32_t)(params->mGating_Max_Duration_Minutes));
*std_initial = ((int32_t)(params->mSraw_Std_Initial));
*gain_factor = ((int32_t)(params->mIndex_Gain));
return;
}
void GasIndexAlgorithm_process(GasIndexAlgorithmParams* params, int32_t sraw,
int32_t* gas_index) {
if ((params->mUptime <= GasIndexAlgorithm_INITIAL_BLACKOUT)) {
params->mUptime = (params->mUptime + params->mSamplingInterval);
} else {
if (((sraw > 0) && (sraw < 65000))) {
if ((sraw < (params->mSraw_Minimum + 1))) {
sraw = (params->mSraw_Minimum + 1);
} else if ((sraw > (params->mSraw_Minimum + 32767))) {
sraw = (params->mSraw_Minimum + 32767);
}
params->mSraw = ((float)((sraw - params->mSraw_Minimum)));
}
if (((params->mAlgorithm_Type ==
GasIndexAlgorithm_ALGORITHM_TYPE_VOC) ||
GasIndexAlgorithm__mean_variance_estimator__is_initialized(
params))) {
params->mGas_Index =
GasIndexAlgorithm__mox_model__process(params, params->mSraw);
params->mGas_Index = GasIndexAlgorithm__sigmoid_scaled__process(
params, params->mGas_Index);
} else {
params->mGas_Index = params->mIndex_Offset;
}
params->mGas_Index = GasIndexAlgorithm__adaptive_lowpass__process(
params, params->mGas_Index);
if ((params->mGas_Index < 0.5f)) {
params->mGas_Index = 0.5f;
}
if ((params->mSraw > 0.f)) {
GasIndexAlgorithm__mean_variance_estimator__process(params,
params->mSraw);
GasIndexAlgorithm__mox_model__set_parameters(
params,
GasIndexAlgorithm__mean_variance_estimator__get_std(params),
GasIndexAlgorithm__mean_variance_estimator__get_mean(params));
}
}
*gas_index = ((int32_t)((params->mGas_Index + 0.5f)));
return;
}
static void GasIndexAlgorithm__mean_variance_estimator__set_parameters(
GasIndexAlgorithmParams* params) {
params->m_Mean_Variance_Estimator___Initialized = false;
params->m_Mean_Variance_Estimator___Mean = 0.f;
params->m_Mean_Variance_Estimator___Sraw_Offset = 0.f;
params->m_Mean_Variance_Estimator___Std = params->mSraw_Std_Initial;
params->m_Mean_Variance_Estimator___Gamma_Mean =
(((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING *
GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) *
(params->mSamplingInterval / 3600.f)) /
(params->mTau_Mean_Hours + (params->mSamplingInterval / 3600.f)));
params->m_Mean_Variance_Estimator___Gamma_Variance =
((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
(params->mSamplingInterval / 3600.f)) /
(params->mTau_Variance_Hours + (params->mSamplingInterval / 3600.f)));
if ((params->mAlgorithm_Type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) {
params->m_Mean_Variance_Estimator___Gamma_Initial_Mean =
(((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING *
GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) *
params->mSamplingInterval) /
(GasIndexAlgorithm_TAU_INITIAL_MEAN_NOX +
params->mSamplingInterval));
} else {
params->m_Mean_Variance_Estimator___Gamma_Initial_Mean =
(((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING *
GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) *
params->mSamplingInterval) /
(GasIndexAlgorithm_TAU_INITIAL_MEAN_VOC +
params->mSamplingInterval));
}
params->m_Mean_Variance_Estimator___Gamma_Initial_Variance =
((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
params->mSamplingInterval) /
(GasIndexAlgorithm_TAU_INITIAL_VARIANCE + params->mSamplingInterval));
params->m_Mean_Variance_Estimator__Gamma_Mean = 0.f;
params->m_Mean_Variance_Estimator__Gamma_Variance = 0.f;
params->m_Mean_Variance_Estimator___Uptime_Gamma = 0.f;
params->m_Mean_Variance_Estimator___Uptime_Gating = 0.f;
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = 0.f;
}
static void GasIndexAlgorithm__mean_variance_estimator__set_states(
GasIndexAlgorithmParams* params, float mean, float std,
float uptime_gamma) {
params->m_Mean_Variance_Estimator___Mean = mean;
params->m_Mean_Variance_Estimator___Std = std;
params->m_Mean_Variance_Estimator___Uptime_Gamma = uptime_gamma;
params->m_Mean_Variance_Estimator___Initialized = true;
}
static float GasIndexAlgorithm__mean_variance_estimator__get_std(
const GasIndexAlgorithmParams* params) {
return params->m_Mean_Variance_Estimator___Std;
}
static float GasIndexAlgorithm__mean_variance_estimator__get_mean(
const GasIndexAlgorithmParams* params) {
return (params->m_Mean_Variance_Estimator___Mean +
params->m_Mean_Variance_Estimator___Sraw_Offset);
}
static bool GasIndexAlgorithm__mean_variance_estimator__is_initialized(
GasIndexAlgorithmParams* params) {
return params->m_Mean_Variance_Estimator___Initialized;
}
static void GasIndexAlgorithm__mean_variance_estimator___calculate_gamma(
GasIndexAlgorithmParams* params) {
float uptime_limit;
float sigmoid_gamma_mean;
float gamma_mean;
float gating_threshold_mean;
float sigmoid_gating_mean;
float sigmoid_gamma_variance;
float gamma_variance;
float gating_threshold_variance;
float sigmoid_gating_variance;
uptime_limit = (GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX -
params->mSamplingInterval);
if ((params->m_Mean_Variance_Estimator___Uptime_Gamma < uptime_limit)) {
params->m_Mean_Variance_Estimator___Uptime_Gamma =
(params->m_Mean_Variance_Estimator___Uptime_Gamma +
params->mSamplingInterval);
}
if ((params->m_Mean_Variance_Estimator___Uptime_Gating < uptime_limit)) {
params->m_Mean_Variance_Estimator___Uptime_Gating =
(params->m_Mean_Variance_Estimator___Uptime_Gating +
params->mSamplingInterval);
}
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
params, params->mInit_Duration_Mean,
GasIndexAlgorithm_INIT_TRANSITION_MEAN);
sigmoid_gamma_mean =
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
gamma_mean = (params->m_Mean_Variance_Estimator___Gamma_Mean +
((params->m_Mean_Variance_Estimator___Gamma_Initial_Mean -
params->m_Mean_Variance_Estimator___Gamma_Mean) *
sigmoid_gamma_mean));
gating_threshold_mean =
(params->mGating_Threshold +
((GasIndexAlgorithm_GATING_THRESHOLD_INITIAL -
params->mGating_Threshold) *
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->m_Mean_Variance_Estimator___Uptime_Gating)));
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
params, gating_threshold_mean,
GasIndexAlgorithm_GATING_THRESHOLD_TRANSITION);
sigmoid_gating_mean =
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->mGas_Index);
params->m_Mean_Variance_Estimator__Gamma_Mean =
(sigmoid_gating_mean * gamma_mean);
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
params, params->mInit_Duration_Variance,
GasIndexAlgorithm_INIT_TRANSITION_VARIANCE);
sigmoid_gamma_variance =
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
gamma_variance =
(params->m_Mean_Variance_Estimator___Gamma_Variance +
((params->m_Mean_Variance_Estimator___Gamma_Initial_Variance -
params->m_Mean_Variance_Estimator___Gamma_Variance) *
(sigmoid_gamma_variance - sigmoid_gamma_mean)));
gating_threshold_variance =
(params->mGating_Threshold +
((GasIndexAlgorithm_GATING_THRESHOLD_INITIAL -
params->mGating_Threshold) *
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->m_Mean_Variance_Estimator___Uptime_Gating)));
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
params, gating_threshold_variance,
GasIndexAlgorithm_GATING_THRESHOLD_TRANSITION);
sigmoid_gating_variance =
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->mGas_Index);
params->m_Mean_Variance_Estimator__Gamma_Variance =
(sigmoid_gating_variance * gamma_variance);
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes =
(params->m_Mean_Variance_Estimator___Gating_Duration_Minutes +
((params->mSamplingInterval / 60.f) *
(((1.f - sigmoid_gating_mean) *
(1.f + GasIndexAlgorithm_GATING_MAX_RATIO)) -
GasIndexAlgorithm_GATING_MAX_RATIO)));
if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes < 0.f)) {
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = 0.f;
}
if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes >
params->mGating_Max_Duration_Minutes)) {
params->m_Mean_Variance_Estimator___Uptime_Gating = 0.f;
}
}
static void GasIndexAlgorithm__mean_variance_estimator__process(
GasIndexAlgorithmParams* params, float sraw) {
float delta_sgp;
float c;
float additional_scaling;
if ((params->m_Mean_Variance_Estimator___Initialized == false)) {
params->m_Mean_Variance_Estimator___Initialized = true;
params->m_Mean_Variance_Estimator___Sraw_Offset = sraw;
params->m_Mean_Variance_Estimator___Mean = 0.f;
} else {
if (((params->m_Mean_Variance_Estimator___Mean >= 100.f) ||
(params->m_Mean_Variance_Estimator___Mean <= -100.f))) {
params->m_Mean_Variance_Estimator___Sraw_Offset =
(params->m_Mean_Variance_Estimator___Sraw_Offset +
params->m_Mean_Variance_Estimator___Mean);
params->m_Mean_Variance_Estimator___Mean = 0.f;
}
sraw = (sraw - params->m_Mean_Variance_Estimator___Sraw_Offset);
GasIndexAlgorithm__mean_variance_estimator___calculate_gamma(params);
delta_sgp = ((sraw - params->m_Mean_Variance_Estimator___Mean) /
GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING);
if ((delta_sgp < 0.f)) {
c = (params->m_Mean_Variance_Estimator___Std - delta_sgp);
} else {
c = (params->m_Mean_Variance_Estimator___Std + delta_sgp);
}
additional_scaling = 1.f;
if ((c > 1440.f)) {
additional_scaling = ((c / 1440.f) * (c / 1440.f));
}
params->m_Mean_Variance_Estimator___Std =
(sqrtf((additional_scaling *
(GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING -
params->m_Mean_Variance_Estimator__Gamma_Variance))) *
sqrtf(
((params->m_Mean_Variance_Estimator___Std *
(params->m_Mean_Variance_Estimator___Std /
(GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
additional_scaling))) +
(((params->m_Mean_Variance_Estimator__Gamma_Variance *
delta_sgp) /
additional_scaling) *
delta_sgp))));
params->m_Mean_Variance_Estimator___Mean =
(params->m_Mean_Variance_Estimator___Mean +
((params->m_Mean_Variance_Estimator__Gamma_Mean * delta_sgp) /
GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING));
}
}
static void
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
GasIndexAlgorithmParams* params, float X0, float K) {
params->m_Mean_Variance_Estimator___Sigmoid__K = K;
params->m_Mean_Variance_Estimator___Sigmoid__X0 = X0;
}
static float GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
GasIndexAlgorithmParams* params, float sample) {
float x;
x = (params->m_Mean_Variance_Estimator___Sigmoid__K *
(sample - params->m_Mean_Variance_Estimator___Sigmoid__X0));
if ((x < -50.f)) {
return 1.f;
} else if ((x > 50.f)) {
return 0.f;
} else {
return (1.f / (1.f + expf(x)));
}
}
static void
GasIndexAlgorithm__mox_model__set_parameters(GasIndexAlgorithmParams* params,
float SRAW_STD, float SRAW_MEAN) {
params->m_Mox_Model__Sraw_Std = SRAW_STD;
params->m_Mox_Model__Sraw_Mean = SRAW_MEAN;
}
static float
GasIndexAlgorithm__mox_model__process(GasIndexAlgorithmParams* params,
float sraw) {
if ((params->mAlgorithm_Type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) {
return (((sraw - params->m_Mox_Model__Sraw_Mean) /
GasIndexAlgorithm_SRAW_STD_NOX) *
params->mIndex_Gain);
} else {
return (((sraw - params->m_Mox_Model__Sraw_Mean) /
(-1.f * (params->m_Mox_Model__Sraw_Std +
GasIndexAlgorithm_SRAW_STD_BONUS_VOC))) *
params->mIndex_Gain);
}
}
static void GasIndexAlgorithm__sigmoid_scaled__set_parameters(
GasIndexAlgorithmParams* params, float X0, float K, float offset_default) {
params->m_Sigmoid_Scaled__K = K;
params->m_Sigmoid_Scaled__X0 = X0;
params->m_Sigmoid_Scaled__Offset_Default = offset_default;
}
static float
GasIndexAlgorithm__sigmoid_scaled__process(GasIndexAlgorithmParams* params,
float sample) {
float x;
float shift;
x = (params->m_Sigmoid_Scaled__K * (sample - params->m_Sigmoid_Scaled__X0));
if ((x < -50.f)) {
return GasIndexAlgorithm_SIGMOID_L;
} else if ((x > 50.f)) {
return 0.f;
} else {
if ((sample >= 0.f)) {
if ((params->m_Sigmoid_Scaled__Offset_Default == 1.f)) {
shift = ((500.f / 499.f) * (1.f - params->mIndex_Offset));
} else {
shift = ((GasIndexAlgorithm_SIGMOID_L -
(5.f * params->mIndex_Offset)) /
4.f);
}
return (((GasIndexAlgorithm_SIGMOID_L + shift) / (1.f + expf(x))) -
shift);
} else {
return ((params->mIndex_Offset /
params->m_Sigmoid_Scaled__Offset_Default) *
(GasIndexAlgorithm_SIGMOID_L / (1.f + expf(x))));
}
}
}
static void GasIndexAlgorithm__adaptive_lowpass__set_parameters(
GasIndexAlgorithmParams* params) {
params->m_Adaptive_Lowpass__A1 =
(params->mSamplingInterval /
(GasIndexAlgorithm_LP_TAU_FAST + params->mSamplingInterval));
params->m_Adaptive_Lowpass__A2 =
(params->mSamplingInterval /
(GasIndexAlgorithm_LP_TAU_SLOW + params->mSamplingInterval));
params->m_Adaptive_Lowpass___Initialized = false;
}
static float
GasIndexAlgorithm__adaptive_lowpass__process(GasIndexAlgorithmParams* params,
float sample) {
float abs_delta;
float F1;
float tau_a;
float a3;
if ((params->m_Adaptive_Lowpass___Initialized == false)) {
params->m_Adaptive_Lowpass___X1 = sample;
params->m_Adaptive_Lowpass___X2 = sample;
params->m_Adaptive_Lowpass___X3 = sample;
params->m_Adaptive_Lowpass___Initialized = true;
}
params->m_Adaptive_Lowpass___X1 =
(((1.f - params->m_Adaptive_Lowpass__A1) *
params->m_Adaptive_Lowpass___X1) +
(params->m_Adaptive_Lowpass__A1 * sample));
params->m_Adaptive_Lowpass___X2 =
(((1.f - params->m_Adaptive_Lowpass__A2) *
params->m_Adaptive_Lowpass___X2) +
(params->m_Adaptive_Lowpass__A2 * sample));
abs_delta =
(params->m_Adaptive_Lowpass___X1 - params->m_Adaptive_Lowpass___X2);
if ((abs_delta < 0.f)) {
abs_delta = (-1.f * abs_delta);
}
F1 = expf((GasIndexAlgorithm_LP_ALPHA * abs_delta));
tau_a = (((GasIndexAlgorithm_LP_TAU_SLOW - GasIndexAlgorithm_LP_TAU_FAST) *
F1) +
GasIndexAlgorithm_LP_TAU_FAST);
a3 = (params->mSamplingInterval / (params->mSamplingInterval + tau_a));
params->m_Adaptive_Lowpass___X3 =
(((1.f - a3) * params->m_Adaptive_Lowpass___X3) + (a3 * sample));
return params->m_Adaptive_Lowpass___X3;
}

View File

@ -0,0 +1,290 @@
/*
* Copyright (c) 2022, Sensirion AG
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * 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.
*
* * Neither the name of Sensirion AG 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 AND CONTRIBUTORS "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 OR CONTRIBUTORS 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.
*/
#ifndef GASINDEXALGORITHM_H_
#define GASINDEXALGORITHM_H_
#include <stdint.h>
#ifndef __cplusplus
#if __STDC_VERSION__ >= 199901L
#include <stdbool.h>
#else
#ifndef bool
#define bool int
#define true 1
#define false 0
#endif // bool
#endif // __STDC_VERSION__
#endif // __cplusplus
// Should be set by the building toolchain
#ifndef LIBRARY_VERSION_NAME
#define LIBRARY_VERSION_NAME "3.2.0"
#endif
#define GasIndexAlgorithm_ALGORITHM_TYPE_VOC (0)
#define GasIndexAlgorithm_ALGORITHM_TYPE_NOX (1)
#define GasIndexAlgorithm_DEFAULT_SAMPLING_INTERVAL (1.f)
#define GasIndexAlgorithm_INITIAL_BLACKOUT (45.f)
#define GasIndexAlgorithm_INDEX_GAIN (230.f)
#define GasIndexAlgorithm_SRAW_STD_INITIAL (50.f)
#define GasIndexAlgorithm_SRAW_STD_BONUS_VOC (220.f)
#define GasIndexAlgorithm_SRAW_STD_NOX (2000.f)
#define GasIndexAlgorithm_TAU_MEAN_HOURS (12.f)
#define GasIndexAlgorithm_TAU_VARIANCE_HOURS (12.f)
#define GasIndexAlgorithm_TAU_INITIAL_MEAN_VOC (20.f)
#define GasIndexAlgorithm_TAU_INITIAL_MEAN_NOX (1200.f)
#define GasIndexAlgorithm_INIT_DURATION_MEAN_VOC ((3600.f * 0.75f))
#define GasIndexAlgorithm_INIT_DURATION_MEAN_NOX ((3600.f * 4.75f))
#define GasIndexAlgorithm_INIT_TRANSITION_MEAN (0.01f)
#define GasIndexAlgorithm_TAU_INITIAL_VARIANCE (2500.f)
#define GasIndexAlgorithm_INIT_DURATION_VARIANCE_VOC ((3600.f * 1.45f))
#define GasIndexAlgorithm_INIT_DURATION_VARIANCE_NOX ((3600.f * 5.70f))
#define GasIndexAlgorithm_INIT_TRANSITION_VARIANCE (0.01f)
#define GasIndexAlgorithm_GATING_THRESHOLD_VOC (340.f)
#define GasIndexAlgorithm_GATING_THRESHOLD_NOX (30.f)
#define GasIndexAlgorithm_GATING_THRESHOLD_INITIAL (510.f)
#define GasIndexAlgorithm_GATING_THRESHOLD_TRANSITION (0.09f)
#define GasIndexAlgorithm_GATING_VOC_MAX_DURATION_MINUTES ((60.f * 3.f))
#define GasIndexAlgorithm_GATING_NOX_MAX_DURATION_MINUTES ((60.f * 12.f))
#define GasIndexAlgorithm_GATING_MAX_RATIO (0.3f)
#define GasIndexAlgorithm_SIGMOID_L (500.f)
#define GasIndexAlgorithm_SIGMOID_K_VOC (-0.0065f)
#define GasIndexAlgorithm_SIGMOID_X0_VOC (213.f)
#define GasIndexAlgorithm_SIGMOID_K_NOX (-0.0101f)
#define GasIndexAlgorithm_SIGMOID_X0_NOX (614.f)
#define GasIndexAlgorithm_VOC_INDEX_OFFSET_DEFAULT (100.f)
#define GasIndexAlgorithm_NOX_INDEX_OFFSET_DEFAULT (1.f)
#define GasIndexAlgorithm_LP_TAU_FAST (20.0f)
#define GasIndexAlgorithm_LP_TAU_SLOW (500.0f)
#define GasIndexAlgorithm_LP_ALPHA (-0.2f)
#define GasIndexAlgorithm_VOC_SRAW_MINIMUM (20000)
#define GasIndexAlgorithm_NOX_SRAW_MINIMUM (10000)
#define GasIndexAlgorithm_PERSISTENCE_UPTIME_GAMMA ((3.f * 3600.f))
#define GasIndexAlgorithm_TUNING_INDEX_OFFSET_MIN (1)
#define GasIndexAlgorithm_TUNING_INDEX_OFFSET_MAX (250)
#define GasIndexAlgorithm_TUNING_LEARNING_TIME_OFFSET_HOURS_MIN (1)
#define GasIndexAlgorithm_TUNING_LEARNING_TIME_OFFSET_HOURS_MAX (1000)
#define GasIndexAlgorithm_TUNING_LEARNING_TIME_GAIN_HOURS_MIN (1)
#define GasIndexAlgorithm_TUNING_LEARNING_TIME_GAIN_HOURS_MAX (1000)
#define GasIndexAlgorithm_TUNING_GATING_MAX_DURATION_MINUTES_MIN (0)
#define GasIndexAlgorithm_TUNING_GATING_MAX_DURATION_MINUTES_MAX (3000)
#define GasIndexAlgorithm_TUNING_STD_INITIAL_MIN (10)
#define GasIndexAlgorithm_TUNING_STD_INITIAL_MAX (5000)
#define GasIndexAlgorithm_TUNING_GAIN_FACTOR_MIN (1)
#define GasIndexAlgorithm_TUNING_GAIN_FACTOR_MAX (1000)
#define GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING (64.f)
#define GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING \
(8.f)
#define GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX (32767.f)
/**
* Struct to hold all parameters and states of the gas algorithm.
*/
typedef struct {
int mAlgorithm_Type;
float mSamplingInterval;
float mIndex_Offset;
int32_t mSraw_Minimum;
float mGating_Max_Duration_Minutes;
float mInit_Duration_Mean;
float mInit_Duration_Variance;
float mGating_Threshold;
float mIndex_Gain;
float mTau_Mean_Hours;
float mTau_Variance_Hours;
float mSraw_Std_Initial;
float mUptime;
float mSraw;
float mGas_Index;
bool m_Mean_Variance_Estimator___Initialized;
float m_Mean_Variance_Estimator___Mean;
float m_Mean_Variance_Estimator___Sraw_Offset;
float m_Mean_Variance_Estimator___Std;
float m_Mean_Variance_Estimator___Gamma_Mean;
float m_Mean_Variance_Estimator___Gamma_Variance;
float m_Mean_Variance_Estimator___Gamma_Initial_Mean;
float m_Mean_Variance_Estimator___Gamma_Initial_Variance;
float m_Mean_Variance_Estimator__Gamma_Mean;
float m_Mean_Variance_Estimator__Gamma_Variance;
float m_Mean_Variance_Estimator___Uptime_Gamma;
float m_Mean_Variance_Estimator___Uptime_Gating;
float m_Mean_Variance_Estimator___Gating_Duration_Minutes;
float m_Mean_Variance_Estimator___Sigmoid__K;
float m_Mean_Variance_Estimator___Sigmoid__X0;
float m_Mox_Model__Sraw_Std;
float m_Mox_Model__Sraw_Mean;
float m_Sigmoid_Scaled__K;
float m_Sigmoid_Scaled__X0;
float m_Sigmoid_Scaled__Offset_Default;
float m_Adaptive_Lowpass__A1;
float m_Adaptive_Lowpass__A2;
bool m_Adaptive_Lowpass___Initialized;
float m_Adaptive_Lowpass___X1;
float m_Adaptive_Lowpass___X2;
float m_Adaptive_Lowpass___X3;
} GasIndexAlgorithmParams;
/**
* Initialize the gas index algorithm parameters for the specified algorithm
* type and reset its internal states. Call this once at the beginning.
* @param params Pointer to the GasIndexAlgorithmParams struct
* @param algorithm_type 0 (GasIndexAlgorithm_ALGORITHM_TYPE_VOC) for VOC or
* 1 (GasIndexAlgorithm_ALGORITHM_TYPE_NOX) for NOx
*/
void GasIndexAlgorithm_init(GasIndexAlgorithmParams* params,
int32_t algorithm_type);
/**
* Initialize the gas index algorithm parameters for the specified algorithm
* type and reset its internal states. Call this once at the beginning.
* @param params Pointer to the GasIndexAlgorithmParams struct
* @param algorithm_type 0 (GasIndexAlgorithm_ALGORITHM_TYPE_VOC) for VOC or
* 1 (GasIndexAlgorithm_ALGORITHM_TYPE_NOX) for NOx
* @param sampling_interval The sampling interval in seconds the algorithm is
* called. Tested for 1s and 10s.
*/
void GasIndexAlgorithm_init_with_sampling_interval(
GasIndexAlgorithmParams* params, int32_t algorithm_type,
float sampling_interval);
/**
* Reset the internal states of the gas index algorithm. Previously set tuning
* parameters are preserved. Call this when resuming operation after a
* measurement interruption.
* @param params Pointer to the GasIndexAlgorithmParams struct
*/
void GasIndexAlgorithm_reset(GasIndexAlgorithmParams* params);
/**
* Get current algorithm states. Retrieved values can be used in
* GasIndexAlgorithm_set_states() to resume operation after a short
* interruption, skipping initial learning phase.
* NOTE: This feature can only be used for VOC algorithm type and after at least
* 3 hours of continuous operation.
* @param params Pointer to the GasIndexAlgorithmParams struct
* @param state0 State0 to be stored
* @param state1 State1 to be stored
*/
void GasIndexAlgorithm_get_states(const GasIndexAlgorithmParams* params,
float* state0, float* state1);
/**
* Set previously retrieved algorithm states to resume operation after a short
* interruption, skipping initial learning phase. This feature should not be
* used after interruptions of more than 10 minutes. Call this once after
* GasIndexAlgorithm_init() or GasIndexAlgorithm_reset() and the optional
* GasIndexAlgorithm_set_tuning_parameters(), if desired. Otherwise, the
* algorithm will start with initial learning phase.
* NOTE: This feature can only be used for VOC algorithm type.
* @param params Pointer to the GasIndexAlgorithmParams struct
* @param state0 State0 to be restored
* @param state1 State1 to be restored
*/
void GasIndexAlgorithm_set_states(GasIndexAlgorithmParams* params, float state0,
float state1);
/**
* Set parameters to customize the gas index algorithm. Call this once after
* GasIndexAlgorithm_init() and before optional GasIndexAlgorithm_set_states(),
* if desired. Otherwise, the default values will be used.
*
* @param params Pointer to the GasIndexAlgorithmParams
* struct
* @param index_offset Gas index representing typical (average)
* conditions. Range 1..250,
* default 100 for VOC and 1 for NOx
* @param learning_time_offset_hours Time constant of long-term estimator for
* offset. Past events will be forgotten
* after about twice the learning time.
* Range 1..1000 [hours], default 12 [hours]
* @param learning_time_gain_hours Time constant of long-term estimator for
* gain. Past events will be forgotten
* after about twice the learning time.
* Range 1..1000 [hours], default 12 [hours]
* NOTE: This value is not relevant for NOx
* algorithm type
* @param gating_max_duration_minutes Maximum duration of gating (freeze of
* estimator during high gas index signal).
* 0 (no gating) or range 1..3000 [minutes],
* default 180 [minutes] for VOC and
* 720 [minutes] for NOx
* @param std_initial Initial estimate for standard deviation.
* Lower value boosts events during initial
* learning period, but may result in larger
* device-to-device variations.
* Range 10..5000, default 50
* NOTE: This value is not relevant for NOx
* algorithm type
* @param gain_factor Factor used to scale applied gain value
* when calculating gas index. Range 1..1000,
* default 230
*/
void GasIndexAlgorithm_set_tuning_parameters(
GasIndexAlgorithmParams* params, int32_t index_offset,
int32_t learning_time_offset_hours, int32_t learning_time_gain_hours,
int32_t gating_max_duration_minutes, int32_t std_initial,
int32_t gain_factor);
/**
* Get current parameters to customize the gas index algorithm.
* Refer to GasIndexAlgorithm_set_tuning_parameters() for description of the
* parameters.
*/
void GasIndexAlgorithm_get_tuning_parameters(
const GasIndexAlgorithmParams* params, int32_t* index_offset,
int32_t* learning_time_offset_hours, int32_t* learning_time_gain_hours,
int32_t* gating_max_duration_minutes, int32_t* std_initial,
int32_t* gain_factor);
/**
* Get the sampling interval parameter used by the algorithm.
*/
void GasIndexAlgorithm_get_sampling_interval(
const GasIndexAlgorithmParams* params, float* sampling_interval);
/**
* Calculate the gas index value from the raw sensor value.
*
* @param params Pointer to the GasIndexAlgorithmParams struct
* @param sraw Raw value from the SGP4x sensor
* @param gas_index Calculated gas index value from the raw sensor value. Zero
* during initial blackout period and 1..500 afterwards
*/
void GasIndexAlgorithm_process(GasIndexAlgorithmParams* params, int32_t sraw,
int32_t* gas_index);
#endif /* GASINDEXALGORITHM_H_ */

View File

@ -111,18 +111,6 @@ void WiFiClass32::scrubDNS(void) {
} else {
dns_setserver(i, IP4_ADDR_ANY);
}
#else // USE_IPV6
uint32_t ip_dns = ip_addr_get_ip4_u32(dns_getserver(i));
// Step 1. save valid values from DNS
if (has_v4 && (uint32_t)ip_dns != 0) {
ip_addr_set_ip4_u32_val(dns_save4[i], ip_dns);
}
// Step 2. scrub addresses not supported
if (!has_v4) {
ip_addr_set_ip4_u32_val(dns_save4[i], 0L);
}
// Step 3. restore saved value
dns_setserver(i, &dns_save4[i]);
#endif // USE_IPV6
}
// AddLog(LOG_LEVEL_DEBUG, "IP>: DNS: from(%s %s) to (%s %s) has4/6:%i-%i", dns_entry0.c_str(), dns_entry1.c_str(), IPAddress(dns_getserver(0)).toString().c_str(), IPAddress(dns_getserver(1)).toString().c_str(), has_v4, has_v6);

View File

@ -76,8 +76,8 @@ public:
void scrubDNS(void);
protected:
ip_addr_t dns_save4[DNS_MAX_SERVERS] = {}; // IPv4 DNS servers
#ifdef USE_IPV6
ip_addr_t dns_save4[DNS_MAX_SERVERS] = {}; // IPv4 DNS servers
ip_addr_t dns_save6[DNS_MAX_SERVERS] = {}; // IPv6 DNS servers
#endif // USE_IPV6
};

View File

@ -16,6 +16,7 @@
#ifdef ESP32
#include "Arduino.h"
#include "esp_idf_version.h"
#include "esp8266toEsp32.h"
#include "driver/ledc.h"
@ -33,7 +34,11 @@ enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_D
#else
#define LEDC_DEFAULT_CLK LEDC_AUTO_CLK
#endif
#define LEDC_MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM
#if (ESP_IDF_VERSION_MAJOR >= 5)
#define LEDC_MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDTH
#else
#define LEDC_MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM
#endif
// define our limits to ease any change from esp-idf
#define MAX_TIMERS LEDC_TIMER_MAX // 4 timers for all ESP32 variants

View File

@ -53,7 +53,7 @@ uint8_t ledcReadResolution(uint8_t chan);
// was not yet attached.
//
// Returns: hardware channel number, or -1 if it failed
int analogAttach(uint32_t pin, bool output_invert = false); // returns the ledc channel, or -1 if failed. This is implicitly called by analogWrite if the channel was not already allocated
int32_t analogAttach(uint32_t pin, bool output_invert = false); // returns the ledc channel, or -1 if failed. This is implicitly called by analogWrite if the channel was not already allocated
// change both freq and range
// `0`: set to global value

Binary file not shown.

View File

@ -1038,7 +1038,9 @@ BERRY_API int be_pcall(bvm *vm, int argc)
return be_protectedcall(vm, f, argc);
}
#ifdef __GNUC__
__attribute__((noreturn))
#endif
BERRY_API void be_raise(bvm *vm, const char *except, const char *msg)
{
be_pushstring(vm, except);

View File

@ -356,6 +356,40 @@ static uint16_t buf_get2_be(buf_impl* attr, size_t offset)
return 0;
}
static uint32_t buf_get3_le(buf_impl* attr, size_t offset)
{
if ((int32_t)offset + 2 < attr->len) {
return attr->bufptr[offset] | (attr->bufptr[offset+1] << 8) | (attr->bufptr[offset+2] << 16);
}
return 0;
}
static uint16_t buf_get3_be(buf_impl* attr, size_t offset)
{
if ((int32_t)offset + 2 < attr->len) {
return attr->bufptr[offset+2] | (attr->bufptr[offset+1] << 8) | (attr->bufptr[offset] << 16);
}
return 0;
}
static void buf_set3_le(buf_impl* attr, size_t offset, uint32_t data)
{
if ((int32_t)offset + 2 < attr->len) {
attr->bufptr[offset] = data & 0xFF;
attr->bufptr[offset+1] = (data >> 8) & 0xFF;
attr->bufptr[offset+2] = (data >> 16) & 0xFF;
}
}
static void buf_set3_be(buf_impl* attr, size_t offset, uint32_t data)
{
if ((int32_t)offset + 2 < attr->len) {
attr->bufptr[offset+2] = data & 0xFF;
attr->bufptr[offset+1] = (data >> 8) & 0xFF;
attr->bufptr[offset] = (data >> 16) & 0xFF;
}
}
static void buf_set4_le(buf_impl* attr, size_t offset, uint32_t data)
{
if ((int32_t)offset + 3 < attr->len) {
@ -832,12 +866,18 @@ static int m_get(bvm *vm, bbool sign)
case 2: ret = buf_get2_le(&attr, idx);
if (sign) { ret = (int16_t)(uint16_t) ret; }
break;
case 3: ret = buf_get3_le(&attr, idx);
if (sign & (ret & 0x800000)) { ret = ret | 0xFF000000; }
break;
case 4: ret = buf_get4_le(&attr, idx); break;
case -2: ret = buf_get2_be(&attr, idx);
if (sign) { ret = (int16_t)(uint16_t) ret; }
break;
case -3: ret = buf_get3_be(&attr, idx);
if (sign & (ret & 0x800000)) { ret = ret | 0xFF000000; }
break;
case -4: ret = buf_get4_be(&attr, idx); break;
default: be_raise(vm, "type_error", "size must be -4, -2, -1, 0, 1, 2 or 4.");
default: be_raise(vm, "type_error", "size must be -4, -3, -2, -1, 0, 1, 2, 3 or 4.");
}
be_pop(vm, argc - 1);
if (vsize != 0) {
@ -911,10 +951,12 @@ static int m_set(bvm *vm)
case -1: /* fallback below */
case 1: buf_set1(&attr, idx, value); break;
case 2: buf_set2_le(&attr, idx, value); break;
case 3: buf_set3_le(&attr, idx, value); break;
case 4: buf_set4_le(&attr, idx, value); break;
case -2: buf_set2_be(&attr, idx, value); break;
case -3: buf_set3_be(&attr, idx, value); break;
case -4: buf_set4_be(&attr, idx, value); break;
default: be_raise(vm, "type_error", "size must be -4, -2, -1, 0, 1, 2 or 4.");
default: be_raise(vm, "type_error", "size must be -4, -3, -2, -1, 0, 1, 2, 3 or 4.");
}
be_pop(vm, argc - 1);
m_write_attributes(vm, 1, &attr); /* update attributes */

View File

@ -78,20 +78,11 @@ static int codeABx(bfuncinfo *finfo, bopcode op, int a, int bx)
return codeinst(finfo, ISET_OP(op) | ISET_RA(a) | ISET_Bx(bx));
}
/* Move value from register b to register a */
static void code_move_nooptim(bfuncinfo *finfo, int a, int b)
{
if (isK(b)) {
codeABx(finfo, OP_LDCONST, a, b & 0xFF);
} else {
codeABC(finfo, OP_MOVE, a, b, 0);
}
}
/* Move value from register b to register a */
/* Check the previous instruction to compact both instruction as one if possible */
/* If b is a constant, add LDCONST or add MOVE otherwise */
static void code_move(bfuncinfo *finfo, int a, int b)
/* returns false if the move operation happened, or true if there was a register optimization and `b` should be replaced by `a` */
static bbool code_move(bfuncinfo *finfo, int a, int b)
{
if (finfo->pc) { /* If not the first instruction of the function */
binstruction *i = be_vector_end(&finfo->code); /* get the last instruction */
@ -101,11 +92,23 @@ static void code_move(bfuncinfo *finfo, int a, int b)
int x = IGET_RA(*i), y = IGET_RKB(*i), z = IGET_RKC(*i);
if (b == x && (a == y || (op < OP_NEG && a == z))) {
*i = (*i & ~IRA_MASK) | ISET_RA(a);
return;
return btrue;
}
}
if (!isK(b)) { /* OP_MOVE */
/* check if the previous OP_MOVE is not identical */
binstruction mov = ISET_OP(OP_MOVE) | ISET_RA(a) | ISET_RKB(b) | ISET_RKC(0);
if (mov == *i) {
return btrue; /* previous instruction is the same move, remove duplicate */
}
}
}
code_move_nooptim(finfo, a, b);
if (isK(b)) {
codeABx(finfo, OP_LDCONST, a, b & 0xFF);
} else {
codeABC(finfo, OP_MOVE, a, b, 0);
}
return bfalse;
}
/* Free register at top (checks that it´s a register) */
@ -113,7 +116,7 @@ static void code_move(bfuncinfo *finfo, int a, int b)
static void free_expreg(bfuncinfo *finfo, bexpdesc *e)
{
/* release temporary register */
if (e && e->type == ETREG) {
if (e && e->type == ETREG && e->v.idx == finfo->freereg - 1) { /* free ETREG only if it's top of stack */
be_code_freeregs(finfo, 1);
}
}
@ -690,10 +693,10 @@ int be_code_setvar(bfuncinfo *finfo, bexpdesc *e1, bexpdesc *e2, bbool keep_reg)
switch (e1->type) {
case ETLOCAL: /* It can't be ETREG. */
if (e1->v.idx != src) {
if (keep_reg) {
code_move_nooptim(finfo, e1->v.idx, src); /* always do explicit move */
} else {
code_move(finfo, e1->v.idx, src); /* do explicit move only if needed */
bbool reg_optimized = code_move(finfo, e1->v.idx, src); /* do explicit move only if needed */
if (reg_optimized) {
free_expreg(finfo, e2); /* free source (checks only ETREG) */
*e2 = *e1; /* now e2 is e1 ETLOCAL */
}
}
break;
@ -725,7 +728,7 @@ int be_code_nextreg(bfuncinfo *finfo, bexpdesc *e)
{
int dst = finfo->freereg;
int src = exp2anyreg(finfo, e); /* get variable register index */
if (e->type != ETREG) { /* move local and const to new register */
if ((e->type != ETREG) || (src < dst - 1)) { /* move local and const to new register, don't move if already top of stack */
code_move(finfo, dst, src);
be_code_allocregs(finfo, 1);
} else {

View File

@ -167,14 +167,45 @@ static int m_counters(bvm *vm)
map_insert(vm, "call", vm->counter_call);
map_insert(vm, "get", vm->counter_get);
map_insert(vm, "set", vm->counter_set);
map_insert(vm, "getgbl", vm->counter_get_global);
map_insert(vm, "try", vm->counter_try);
map_insert(vm, "raise", vm->counter_exc);
map_insert(vm, "objects", vm->counter_gc_kept);
map_insert(vm, "mem_alloc", vm->counter_mem_alloc);
map_insert(vm, "mem_free", vm->counter_mem_free);
map_insert(vm, "mem_realloc", vm->counter_mem_realloc);
be_pop(vm, 1);
be_return(vm);
}
#endif
static int m_allocs(bvm *vm) {
#if BE_USE_PERF_COUNTERS
be_pushint(vm, vm->counter_mem_alloc);
be_return(vm);
#else
be_return_nil(vm);
#endif
}
static int m_frees(bvm *vm) {
#if BE_USE_PERF_COUNTERS
be_pushint(vm, vm->counter_mem_free);
be_return(vm);
#else
be_return_nil(vm);
#endif
}
static int m_reallocs(bvm *vm) {
#if BE_USE_PERF_COUNTERS
be_pushint(vm, vm->counter_mem_realloc);
be_return(vm);
#else
be_return_nil(vm);
#endif
}
#if !BE_USE_PRECOMPILED_OBJECT
be_native_module_attr_table(debug) {
be_native_module_function("attrdump", m_attrdump),
@ -207,6 +238,10 @@ module debug (scope: global, depend: BE_USE_DEBUG_MODULE) {
top, func(m_top)
varname, func(m_varname), BE_DEBUG_VAR_INFO
upvname, func(m_upvname), BE_DEBUG_VAR_INFO
// individual counters
allocs, func(m_allocs)
frees, func(m_frees)
reallocs, func(m_reallocs)
}
@const_object_info_end */
#include "../generate/be_fixed_debug.h"

View File

@ -41,7 +41,7 @@ static const char* const kwords_tab[] = {
"for", "def", "end", "class", "break", "continue",
"return", "true", "false", "nil", "var", "do",
"import", "as", "try", "except", "raise", "static",
// ".f"
":=",
};
void be_lexerror(blexer *lexer, const char *msg)

View File

@ -262,6 +262,14 @@ static int m_size(bvm *vm)
be_return(vm);
}
static int m_tobool(bvm *vm)
{
be_getmember(vm, 1, ".p");
list_check_data(vm, 1);
be_pushbool(vm, be_data_size(vm, -1) > 0);
be_return(vm);
}
static int m_resize(bvm *vm)
{
be_getmember(vm, 1, ".p");
@ -507,6 +515,7 @@ void be_load_listlib(bvm *vm)
{ "reverse", m_reverse },
{ "copy", m_copy },
{ "keys", m_keys },
{ "tobool", m_tobool }
{ "..", m_connect },
{ "+", m_merge },
{ "==", m_equal },
@ -536,6 +545,7 @@ class be_class_list (scope: global, name: list) {
reverse, func(m_reverse)
copy, func(m_copy)
keys, func(m_keys)
tobool, func(m_tobool)
.., func(m_connect)
+, func(m_merge)
==, func(m_equal)

View File

@ -150,6 +150,14 @@ static int m_size(bvm *vm)
be_return(vm);
}
static int m_tobool(bvm *vm)
{
be_getmember(vm, 1, ".p");
map_check_data(vm, 1);
be_pushbool(vm, be_data_size(vm, -1) > 0);
be_return(vm);
}
static int iter_closure(bvm *vm)
{
/* for better performance, we operate the upvalues
@ -229,6 +237,7 @@ void be_load_maplib(bvm *vm)
{ "insert", m_insert },
{ "iter", m_iter },
{ "keys", m_keys },
{ "tobool", m_tobool }
{ NULL, NULL }
};
be_regclass(vm, "map", members);
@ -249,6 +258,7 @@ class be_class_map (scope: global, name: map) {
insert, func(m_insert)
iter, func(m_iter)
keys, func(m_keys)
tobool, func(m_tobool)
}
@const_object_info_end */
#include "../generate/be_fixed_be_class_map.h"

View File

@ -60,12 +60,18 @@ BERRY_API void* be_realloc(bvm *vm, void *ptr, size_t old_size, size_t new_size)
while (1) {
/* Case 1: new allocation */
#if BE_USE_PERF_COUNTERS
vm->counter_mem_alloc++;
#endif
if (!ptr || (old_size == 0)) {
block = malloc_from_pool(vm, new_size);
}
/* Case 2: deallocate */
else if (new_size == 0) {
#if BE_USE_PERF_COUNTERS
vm->counter_mem_free++;
#endif
if (ptr == NULL) { return NULL; } /* safeguard */
#if BE_USE_DEBUG_GC
memset(ptr, 0xFF, old_size); /* fill the structure with invalid pointers */
@ -76,6 +82,9 @@ BERRY_API void* be_realloc(bvm *vm, void *ptr, size_t old_size, size_t new_size)
/* Case 3: reallocate with a different size */
else if (new_size && old_size) { // TODO we already know they are not null TODO
#if BE_USE_PERF_COUNTERS
vm->counter_mem_realloc++;
#endif
if (new_size <= POOL32_SIZE || old_size <=POOL32_SIZE) {
/* complex case with different pools */
if (new_size <= POOL16_SIZE && old_size <= POOL16_SIZE) {

View File

@ -196,6 +196,7 @@ static void begin_block(bfuncinfo *finfo, bblockinfo *binfo, int type)
finfo->binfo = binfo; /* tell parser this is the current block */
binfo->type = (bbyte)type;
binfo->hasupval = 0;
binfo->sideeffect = 0;
binfo->beginpc = finfo->pc; /* set starting pc for this block */
binfo->nactlocals = (bbyte)be_list_count(finfo->local); /* count number of local variables in previous block */
if (type & BLOCK_LOOP) {
@ -796,6 +797,7 @@ static void call_expr(bparser *parser, bexpdesc *e)
int argc = 0, base;
int ismember = e->type == ETMEMBER;
parser->finfo->binfo->sideeffect = 1; /* has side effect */
/* func '(' [exprlist] ')' */
check_var(parser, e);
/* code function index to next register */
@ -1030,11 +1032,13 @@ static void assign_expr(bparser *parser)
bexpdesc e;
btokentype op;
int line = parser->lexer.linenumber;
parser->finfo->binfo->sideeffect = 0; /* reinit side effect marker */
expr(parser, &e); /* left expression */
check_symbol(parser, &e);
op = get_assign_op(parser);
if (op != OP_NOT_ASSIGN) { /* assign operator */
bexpdesc e1;
parser->finfo->binfo->sideeffect = 1;
scan_next_token(parser);
compound_assign(parser, op, &e, &e1);
if (check_newvar(parser, &e)) { /* new variable */
@ -1110,6 +1114,9 @@ static void sub_expr(bparser *parser, bexpdesc *e, int prio)
check_var(parser, e); /* check that left part is valid */
scan_next_token(parser); /* move to next token */
be_code_prebinop(finfo, op, e); /* and or */
if (op == OptConnect) {
parser->finfo->binfo->sideeffect = 1;
}
init_exp(&e2, ETVOID, 0);
sub_expr(parser, &e2, binary_op_prio(op)); /* parse right side */
if ((e2.type == ETVOID) && (op == OptConnect)) {
@ -1131,9 +1138,12 @@ static void walrus_expr(bparser *parser, bexpdesc *e)
sub_expr(parser, e, ASSIGN_OP_PRIO); /* left expression */
btokentype op = next_type(parser);
if (op == OptWalrus) {
check_symbol(parser, e);
bexpdesc e1 = *e; /* copy var to e1, e will get the result of expression */
parser->finfo->binfo->sideeffect = 1; /* has side effect */
scan_next_token(parser); /* skip ':=' */
expr(parser, e);
check_var(parser, e);
if (check_newvar(parser, &e1)) { /* new variable */
new_var(parser, e1.v.s, e);
}
@ -1556,7 +1566,25 @@ static void class_stmt(bparser *parser)
new_var(parser, name, &e);
be_code_class(parser->finfo, &e, c);
class_inherit(parser, &e);
bblockinfo binfo;
begin_block(parser->finfo, &binfo, 0);
bstring *class_str = parser_newstr(parser, "_class"); /* we always define `_class` local variable */
if (e.type == ETLOCAL) {
bexpdesc e1; /* if inline class, we add a second local variable for _class */
init_exp(&e1, ETLOCAL, 0);
e1.v.idx = new_localvar(parser, class_str);
be_code_setvar(parser->finfo, &e1, &e, 1);
} else { /* if global class, we just reuse the newly created class in the register */
init_exp(&e, ETLOCAL, 0);
e.v.idx = new_localvar(parser, class_str);
}
begin_varinfo(parser, class_str);
class_block(parser, c, &e);
end_block(parser);
be_class_compress(parser->vm, c); /* compress class size */
match_token(parser, KeyEnd); /* skip 'end' */
} else {
@ -1756,6 +1784,9 @@ static void throw_stmt(bparser *parser)
static void statement(bparser *parser)
{
/* save value of sideeffect */
bbyte sideeffect = parser->finfo->binfo->sideeffect;
parser->finfo->binfo->sideeffect = 1; /* by default declare side effect */
switch (next_type(parser)) {
case KeyIf: if_stmt(parser); break;
case KeyWhile: while_stmt(parser); break;
@ -1770,8 +1801,16 @@ static void statement(bparser *parser)
case KeyVar: var_stmt(parser); break;
case KeyTry: try_stmt(parser); break;
case KeyRaise: throw_stmt(parser); break;
case OptSemic: scan_next_token(parser); break; /* empty statement */
default: expr_stmt(parser); break;
case OptSemic:
parser->finfo->binfo->sideeffect = sideeffect; /* restore sideeffect */
scan_next_token(parser); break; /* empty statement */
default:
parser->finfo->binfo->sideeffect = sideeffect; /* restore sideeffect */
expr_stmt(parser);
if (comp_is_strict(parser->vm) && parser->finfo->binfo->sideeffect == 0) {
push_error(parser, "strict: expression without side effect detected");
}
break;
}
be_assert(parser->finfo->freereg >= be_list_count(parser->finfo->local));
}

View File

@ -53,6 +53,7 @@ typedef struct bblockinfo {
bbyte nactlocals; /* number of active local variables */
bbyte type; /* block type mask */
bbyte hasupval; /* has upvalue mark */
bbyte sideeffect; /* did the last expr/statement had a side effect */
int breaklist; /* break list */
int beginpc; /* begin pc */
int continuelist; /* continue list */

View File

@ -11,31 +11,70 @@
static int m_init(bvm *vm)
{
int argc = be_top(vm);
if (argc < 3) { be_raise(vm, "value_error", "missing arguments"); }
if (!be_isint(vm, 2) || !be_isint(vm, 3)) { be_raise(vm, "value_error", "arguments must be 'int'"); }
be_pushvalue(vm, 2);
be_setmember(vm, 1, "__lower__");
be_pop(vm, 1);
be_pushvalue(vm, 3);
be_setmember(vm, 1, "__upper__");
int incr = 1; /* default increment is '1' */
if (argc >= 4) {
if (!be_isint(vm, 4)) { be_raise(vm, "value_error", "arguments must be 'int'"); }
incr = be_toint(vm, 4);
if (incr == 0) { be_raise(vm, "value_error", "increment cannot be zero"); }
}
be_pushint(vm, incr);
be_setmember(vm, 1, "__incr__");
be_return_nil(vm);
}
static int m_tostring(bvm *vm)
{
be_pushstring(vm, "(");
be_getmember(vm, 1, "__lower__");
be_tostring(vm, -1);
be_strconcat(vm, -2);
be_pop(vm, 1);
be_pushstring(vm, "..");
be_strconcat(vm, -2);
be_pop(vm, 1);
be_getmember(vm, 1, "__upper__");
be_tostring(vm, -1);
be_strconcat(vm, -2);
be_pop(vm, 1);
be_pushstring(vm, ")");
be_strconcat(vm, -2);
be_getmember(vm, 1, "__incr__");
int incr = be_toint(vm, -1);
be_pop(vm, 1);
if (incr == 1) {
be_pushstring(vm, "(");
be_getmember(vm, 1, "__lower__");
be_tostring(vm, -1);
be_strconcat(vm, -2);
be_pop(vm, 1);
be_pushstring(vm, "..");
be_strconcat(vm, -2);
be_pop(vm, 1);
be_getmember(vm, 1, "__upper__");
be_tostring(vm, -1);
be_strconcat(vm, -2);
be_pop(vm, 1);
be_pushstring(vm, ")");
be_strconcat(vm, -2);
be_pop(vm, 1);
} else {
be_pushstring(vm, "range(");
be_getmember(vm, 1, "__lower__");
be_tostring(vm, -1);
be_strconcat(vm, -2);
be_pop(vm, 1);
be_pushstring(vm, ", ");
be_strconcat(vm, -2);
be_pop(vm, 1);
be_getmember(vm, 1, "__upper__");
be_tostring(vm, -1);
be_strconcat(vm, -2);
be_pop(vm, 1);
be_pushstring(vm, ", ");
be_strconcat(vm, -2);
be_pop(vm, 1);
be_getmember(vm, 1, "__incr__");
be_tostring(vm, -1);
be_strconcat(vm, -2);
be_pop(vm, 1);
be_pushstring(vm, ")");
be_strconcat(vm, -2);
be_pop(vm, 1);
}
be_return(vm);
}
@ -51,13 +90,30 @@ static int m_lower(bvm *vm)
be_return(vm);
}
static int m_incr(bvm *vm)
{
be_getmember(vm, 1, "__incr__");
be_return(vm);
}
static int m_setrange(bvm *vm)
{
int argc = be_top(vm);
if (argc < 3) { be_raise(vm, "value_error", "missing arguments"); }
if (!be_isint(vm, 2) || !be_isint(vm, 3)) { be_raise(vm, "value_error", "arguments must be 'int'"); }
be_pushvalue(vm, 2);
be_setmember(vm, 1, "__lower__");
be_pop(vm, 1);
be_pushvalue(vm, 3);
be_setmember(vm, 1, "__upper__");
int incr = 1; /* default increment is '1' */
if (argc >= 4) {
if (!be_isint(vm, 4)) { be_raise(vm, "value_error", "arguments must be 'int'"); }
incr = be_toint(vm, 4);
if (incr == 0) { be_raise(vm, "value_error", "increment cannot be zero"); }
}
be_pushint(vm, incr);
be_setmember(vm, 1, "__incr__");
be_return_nil(vm);
}
@ -68,25 +124,30 @@ static int iter_closure(bvm *vm)
bntvclos *func = var_toobj(vm->cf->func);
bvalue *uv0 = be_ntvclos_upval(func, 0)->value;
bvalue *uv1 = be_ntvclos_upval(func, 1)->value;
bvalue *uv2 = be_ntvclos_upval(func, 2)->value;
bint lower = var_toint(uv0); /* upvalue[0] => lower */
bint upper = var_toint(uv1); /* upvalue[1] => upper */
if (lower > upper) {
bint incr = var_toint(uv2); /* upvalue[2] => incr */
if ((incr > 0 && lower > upper) || (incr < 0 && lower < upper)) {
be_stop_iteration(vm);
}
var_toint(uv0) = lower + 1; /* set upvale[0] */
var_toint(uv0) = lower + incr; /* set upvale[0] */
be_pushint(vm, lower); /* push the return value */
be_return(vm);
}
static int m_iter(bvm *vm)
{
be_pushntvclosure(vm, iter_closure, 2);
be_pushntvclosure(vm, iter_closure, 3);
be_getmember(vm, 1, "__lower__");
be_setupval(vm, -2, 0);
be_pop(vm, 1);
be_getmember(vm, 1, "__upper__");
be_setupval(vm, -2, 1);
be_pop(vm, 1);
be_getmember(vm, 1, "__incr__");
be_setupval(vm, -2, 2);
be_pop(vm, 1);
be_return(vm);
}
@ -96,6 +157,7 @@ void be_load_rangelib(bvm *vm)
static const bnfuncinfo members[] = {
{ "__lower__", NULL },
{ "__upper__", NULL },
{ "__incr__", NULL },
{ "init", m_init },
{ "tostring", m_tostring },
{ "lower", m_lower },
@ -111,10 +173,12 @@ void be_load_rangelib(bvm *vm)
class be_class_range (scope: global, name: range) {
__lower__, var
__upper__, var
__incr__, var
init, func(m_init)
tostring, func(m_tostring)
lower, func(m_lower)
upper, func(m_upper)
incr, func(m_incr)
setrange, func(m_setrange)
iter, func(m_iter)
}

View File

@ -38,7 +38,6 @@ void be_vector_remove_end(bvector *vector);
void be_vector_resize(bvm *vm, bvector *vector, int count);
void be_vector_clear(bvector *vector);
void* be_vector_release(bvm *vm, bvector *vector);
void* be_vector_release_32(bvm *vm, bvector *vector); /* specialized call for 32 bits aligned accesses */
int be_nextsize(int value);
#endif

View File

@ -507,10 +507,14 @@ BERRY_API bvm* be_vm_new(void)
vm->counter_call = 0;
vm->counter_get = 0;
vm->counter_set = 0;
vm->counter_get_global = 0;
vm->counter_try = 0;
vm->counter_exc = 0;
vm->counter_gc_kept = 0;
vm->counter_gc_freed = 0;
vm->counter_mem_alloc = 0;
vm->counter_mem_free = 0;
vm->counter_mem_realloc = 0;
#endif
return vm;
}
@ -579,6 +583,9 @@ newframe: /* a new call frame */
dispatch();
}
opcase(GETNGBL): { /* get Global by name */
#if BE_USE_PERF_COUNTERS
vm->counter_get_global++;
#endif
bvalue *v = RA();
bvalue *b = RKB();
if (var_isstr(b)) {

View File

@ -114,10 +114,14 @@ struct bvm {
uint32_t counter_call; /* counter for calls, VM or native */
uint32_t counter_get; /* counter for GETMBR or GETMET */
uint32_t counter_set; /* counter for SETMBR */
uint32_t counter_get_global; /* counter for GETNBGL */
uint32_t counter_try; /* counter for `try` statement */
uint32_t counter_exc; /* counter for raised exceptions */
uint32_t counter_gc_kept; /* counter for objects scanned by last gc */
uint32_t counter_gc_freed; /* counter for objects freed by last gc */
uint32_t counter_mem_alloc; /* counter for memory allocations */
uint32_t counter_mem_free; /* counter for memory frees */
uint32_t counter_mem_realloc; /* counter for memory reallocations */
uint32_t micros_gc0;
uint32_t micros_gc1;

View File

@ -478,15 +478,18 @@ typedef bclass_ptr bclass_array[];
#endif
/**
* @def PROTO_RUNTIME_BLOCK
* @def BE_DEBUG_SOURCE_FILE
* @brief conditional block in bproto depending on compilation options
*
*/
#if BE_SOURCE_FILE
#if BE_DEBUG_SOURCE_FILE
#define PROTO_SOURCE_FILE(n) \
((bstring*) _source), /**< source */
((bstring*) n), /**< source */
#define PROTO_SOURCE_FILE_STR(n) \
be_local_const_str(n##_str_source), /**< source */
#else
#define PROTO_SOURCE_FILE(n)
#define PROTO_SOURCE_FILE_STR(n)
#endif
/**
@ -496,8 +499,8 @@ typedef bclass_ptr bclass_array[];
*/
#if BE_DEBUG_RUNTIME_INFO
#define PROTO_RUNTIME_BLOCK \
NULL, /**< varinfo */ \
0, /**< nvarinfo */
NULL, /**< lineinfo */ \
0, /**< nlineinfo */
#else
#define PROTO_RUNTIME_BLOCK
#endif
@ -538,7 +541,7 @@ typedef bclass_ptr bclass_array[];
BE_IIF(_is_subproto)((struct bproto**)&_name##_subproto,NULL), /**< bproto **ptab */ \
(binstruction*) &_name##_code, /**< code */ \
be_local_const_str(_name##_str_name), /**< name */ \
be_local_const_str(_name##_str_source), /**< source */ \
PROTO_SOURCE_FILE_STR(_name) /**< source */ \
PROTO_RUNTIME_BLOCK /**< */ \
PROTO_VAR_INFO_BLOCK /**< */ \
}
@ -2062,7 +2065,9 @@ BERRY_API void be_exit(bvm *vm, int status);
* @param except
* @param msg
*/
#ifdef __GNUC__
__attribute__((noreturn))
#endif
BERRY_API void be_raise(bvm *vm, const char *except, const char *msg);
/**

View File

@ -36,7 +36,13 @@ assert(bool(3.5) == true)
assert(bool('') == false) # changed behavior
assert(bool('a') == true)
assert(bool(list) == true)
assert(bool(list()) == true)
assert(bool(list()) == false) # changed behavior
assert(bool([]) == false) # changed behavior
assert(bool([0]) == true)
assert(bool(map()) == false) # changed behavior
assert(bool({}) == false) # changed behavior
assert(bool({false:false}) == true)
assert(bool({nil:nil}) == false)# changed behavior - `nil` key is ignored so the map is empty
import introspect
assert(bool(introspect.toptr(0x1000)) == true)

View File

@ -140,4 +140,18 @@ assert(classname(a) == 'A')
assert(classname(b) == 'B')
assert(A.B.f() == 1)
assert(b.g() == 2)
assert(super(B) == nil)
assert(super(A.B) == nil)
#- `_class` initializer can now be used in initializer code -#
class A
static var a = 1
static var b = _class
static var c = [_class.a, _class.b]
static def f(x)
return _class
end
end
assert(A.a == 1)
assert(A.b == A)
assert(A.c == [1, A])
assert(A.f(1) == A)

View File

@ -0,0 +1,39 @@
# test for ranges
# expand a range object as list
def expand(iter)
var ret = []
for i: iter
ret.push(i)
end
return ret
end
assert(expand(0..5) == [0, 1, 2, 3, 4, 5])
assert(expand(0..0) == [0])
assert(expand(5..0) == [])
var r = 1..5
assert(r.lower() == 1)
assert(r.upper() == 5)
assert(r.incr() == 1)
assert(expand(range(0,5)) == [0, 1, 2, 3, 4, 5])
assert(expand(range(0,5,2)) == [0, 2, 4])
assert(expand(range(0,5,12)) == [0])
assert(expand(range(0,5,-1)) == [])
assert(expand(range(5,0,-1)) == [5, 4, 3, 2, 1, 0])
assert(expand(range(5,0,-2)) == [5, 3, 1])
assert(expand(range(5,5,-2)) == [5])
assert(expand(range(0,5,-2)) == [])
def assert_value_error(c)
try
compile(c)()
assert(false, 'unexpected execution flow')
except 'value_error' as e, m
end
end
# range with increment zero shoud raise an error
assert_value_error("range(1,2,0)")

View File

@ -147,3 +147,25 @@ assert(string.format("%s", nil) == 'nil')
assert(string.format("%s", true) == 'true')
assert(string.format("%s", false) == 'false')
# format is now synonym to string.format
assert(format == string.format)
assert(format("%.1f", 3) == '3.0')
# f-strings
assert(f"" == '')
assert(f'' == '')
assert(f"abc\n\r\t" == 'abc\n\r\t')
assert(f'{{a}}' == '{a}')
assert(f'\\\\' == '\\\\')
assert(f"A = {1+1}" == 'A = 2')
assert(f"A = {1+1:s}" == 'A = 2')
assert(f"A = {1+1:i}" == 'A = 2')
assert(f"A = {1+1:04i}" == 'A = 0002')
assert(f"P = {3.1415:.2f}" == 'P = 3.14')
var a = 'foobar{0}'
assert(f"S = {a}" == 'S = foobar{0}')
assert(f"S = {a:i}" == 'S = 0')
assert(f"{a=}" == 'a=foobar{0}')

View File

@ -0,0 +1,35 @@
# test for the walrus operator
var a = 1
assert((a := a + 1) == 2)
assert((a := a + 1) == 3)
def f()
var defer = 10
var ret = []
for i:0..100
if (defer := defer - 1) == 0
ret.push(i)
defer = 10
end
end
return ret
end
assert(f() == [9, 19, 29, 39, 49, 59, 69, 79, 89, 99])
# test for expressions with no side effects
def assert_attribute_error(c)
try
compile(c)()
assert(false, 'unexpected execution flow')
except 'syntax_error' as e, m
end
end
# below the expressions `a` or `a b` have no side effect and do not generate any code, this is an error when in strict mode
import strict
var a, b
assert_attribute_error("var a,b def f() a b end")
assert_attribute_error("var a,b def f() a end")
# while the following does have side effect
def f() a := b end

View File

@ -5,9 +5,10 @@ block = {statement};
(* statement define *)
statement = class_stmt | func_stmt | var_stmt | if_stmt | while_stmt |
for_stmt | break_stmt | return_stmt | expr_stmt | import_stmt |
try_stmt | throw_stmt | ';';
try_stmt | throw_stmt | do_stmt | ';';
if_stmt = 'if' expr block {'elif' expr block} ['else' block] 'end';
while_stmt = 'while' expr block 'end';
do_stmt = 'do' block 'end';
for_stmt = 'for' ID ':' expr block 'end';
break_stmt = 'break' | 'continue';
return_stmt = 'return' [expr];
@ -28,7 +29,7 @@ throw_stmt = 'raise' expr [',' expr];
var_stmt = 'var' ID ['=' expr] {',' ID ['=' expr]};
(* expression define *)
expr_stmt = expr [assign_op expr];
expr = suffix_expr | unop expr | expr binop expr | range_expr | cond_expr;
expr = suffix_expr | unop expr | expr binop expr | range_expr | cond_expr | walrus_expr;
cond_expr = expr '?' expr ':' expr; (* conditional expression *)
assign_op = '=' | '+=' | '-=' | '*=' | '/=' |
'%=' | '&=' | '|=' | '^=' | '<<=' | '>>=';
@ -36,9 +37,11 @@ binop = '<' | '<=' | '==' | '!=' | '>' | '>=' | '||' | '&&' |
'<<' | '>>' | '&' | '|' | '^' | '+' | '-' | '*' | '/' | '%';
range_expr = expr '..' [expr]
unop = '-' | '!' | '~';
walrus_expr = expr ':=' expr
suffix_expr = primary_expr {call_expr | ('.' ID) | '[' expr ']'};
primary_expr = '(' expr ')' | simple_expr | list_expr | map_expr | anon_func | lambda_expr;
simple_expr = INTEGER | REAL | STRING | ID | 'true' | 'false' | 'nil';
simple_expr = INTEGER | REAL | STRING | ID | 'true' | 'false' | 'nil' | f_string;
f_string = 'f' STRING
call_expr = '(' [expr {',' expr}] ')';
list_expr = '[' {expr ','} [expr] ']';
map_expr = '{' {expr ':' expr ','} [expr ':' expr] '}';

View File

@ -4,4 +4,5 @@ All notable changes to the "none" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased]
- Initial release
- Initial release
- add support for f-strings, `_class` and indent on `do`

View File

@ -26,7 +26,7 @@
["'", "'"]
],
"indentationRules": {
"increaseIndentPattern": "(^((\\s*(class|while|for|if|elif|else|try|except))|(.*\\bdef))\\b((?!\\b(end)\\b).)*)$",
"increaseIndentPattern": "(^((\\s*(class|while|for|if|elif|else|try|except|do))|(.*\\bdef))\\b((?!\\b(end)\\b).)*)$",
"decreaseIndentPattern": "^\\s*((\\b(elif|else|except|end)\\b)|(\\)))"
}
}

View File

@ -2,7 +2,7 @@
"name": "berry",
"displayName": "Berry Script Language",
"description": "A small embedded script language.",
"version": "0.1.0",
"version": "1.1.0",
"publisher": "skiars",
"engines": {
"vscode": "^1.15.1"

View File

@ -44,11 +44,55 @@
"patterns": [
{
"name": "string.quoted.double.berry",
"match": "\"(\\\\.|[^\"])*\""
"begin": "(\"|')",
"end": "\\1",
"patterns": [
{
"name": "constant.character.escape.berry",
"match": "(\\\\x[\\h]{2})|(\\\\[0-7]{3})|(\\\\\\\\)|(\\\\\")|(\\\\')|(\\\\a)|(\\\\b)|(\\\\f)|(\\\\n)|(\\\\r)|(\\\\t)|(\\\\v)"
}
]
},
{
"name": "string.quoted.single.berry",
"match": "'(\\\\.|[^'])*'"
"name": "string.quoted.other.berry",
"begin": "f(\"|')",
"end": "\\1",
"patterns": [
{
"name": "constant.character.escape.berry",
"match": "(\\\\x[\\h]{2})|(\\\\[0-7]{3})|(\\\\\\\\)|(\\\\\")|(\\\\')|(\\\\a)|(\\\\b)|(\\\\f)|(\\\\n)|(\\\\r)|(\\\\t)|(\\\\v)"
},
{
"name": "string.quoted.other.berry",
"match": "\\{\\{[^\\}]*\\}\\}"
},
{
"name": "keyword.other.unit.berry",
"begin": "\\{",
"end": "\\}",
"patterns": [
{
"include": "#keywords"
},
{
"include": "#numbers"
},
{
"include": "#identifier"
},
{
"include": "#operator"
},
{
"include": "#member"
},
{
"include": "#function"
}
]
}
]
}
]
},
@ -67,7 +111,7 @@
"keywords": {
"patterns": [{
"name": "keyword.berry",
"match": "\\b(var|static|def|class|true|false|nil|self|super|import|as)\\b"
"match": "\\b(var|static|def|class|true|false|nil|self|super|import|as|_class)\\b"
}]
},
"identifier": {

View File

@ -1,5 +1,5 @@
{
"name": "Berry int64 implementation for 32 bits architceture",
"name": "Berry int64 implementation for 32 bits architecture",
"version": "1.0",
"description": "Berry int64",
"license": "MIT",

View File

@ -22,10 +22,10 @@ enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_D
* We allow 4 parameters, or 3 if method (first arg is `self`)
* This could be extended if needed
\*********************************************************************************************/
typedef int32_t (*berry_callback_t)(int32_t v0, int32_t v1, int32_t v2, int32_t v3);
static int32_t call_berry_cb(int32_t num, int32_t v0, int32_t v1, int32_t v2, int32_t v3);
typedef int (*berry_callback_t)(int v0, int v1, int v2, int v3);
static int call_berry_cb(int num, int v0, int v1, int v2, int v3);
#define BERRY_CB(n) int32_t berry_cb_##n(int32_t v0, int32_t v1, int32_t v2, int32_t v3) { return call_berry_cb(n, v0, v1, v2, v3); }
#define BERRY_CB(n) int berry_cb_##n(int v0, int v1, int v2, int v3) { return call_berry_cb(n, v0, v1, v2, v3); }
// list the callbacks
BERRY_CB(0);
BERRY_CB(1);
@ -85,7 +85,7 @@ typedef struct be_callback_handler_list_t {
static be_callback_hook be_cb_hooks[BE_MAX_CB] = {0};
static int32_t be_cb_gen_cb(bvm *vm);
static int be_cb_gen_cb(bvm *vm);
static be_callback_handler_list_t be_callback_default_gen_cb = {
NULL,
be_const_func(&be_cb_gen_cb),
@ -102,7 +102,7 @@ static be_callback_handler_list_t *be_callback_handler_list_head = &be_callback_
*
* arg1: function (or closure)
\*********************************************************************************************/
static int32_t be_cb_add_handler(bvm *vm) {
static int be_cb_add_handler(bvm *vm) {
int32_t top = be_top(vm);
if (top >= 1 && be_isfunction(vm, 1)) {
bvalue *v = be_indexof(vm, 1);
@ -129,7 +129,7 @@ static int32_t be_cb_add_handler(bvm *vm) {
*
* No args
\*********************************************************************************************/
static int32_t be_cb_list_handlers(bvm *vm) {
static int be_cb_list_handlers(bvm *vm) {
be_newobject(vm, "list");
for (be_callback_handler_list_t *elt = be_callback_handler_list_head; elt != NULL; elt = elt->next) {
if (elt->vm == vm) { /* on purpose don't show the default handler, just pretend it's not there since it's default */
@ -153,7 +153,7 @@ static int32_t be_cb_list_handlers(bvm *vm) {
* arg2: type name for callback (optional)
* argN: any other callback specific arguments (unlimited number, passed as-is)
\*********************************************************************************************/
static int32_t be_cb_make_cb(bvm *vm) {
static int be_cb_make_cb(bvm *vm) {
int32_t argc = be_top(vm);
if (argc >= 1 && be_isfunction(vm, 1)) {
@ -187,7 +187,7 @@ static int32_t be_cb_make_cb(bvm *vm) {
*
* arg1: function (or closure)
\*********************************************************************************************/
static int32_t be_cb_gen_cb(bvm *vm) {
static int be_cb_gen_cb(bvm *vm) {
int32_t top = be_top(vm);
// tasmota_log_C(LOG_LEVEL_DEBUG, "BRY: gen_cb() called");
if (top >= 1 && be_isfunction(vm, 1)) {
@ -217,7 +217,7 @@ static int32_t be_cb_gen_cb(bvm *vm) {
* `get_cb_list`: Return the list of callbacks for this vm
*
\*********************************************************************************************/
static int32_t be_cb_get_cb_list(bvm *vm) {
static int be_cb_get_cb_list(bvm *vm) {
be_newobject(vm, "list");
for (uint32_t i=0; i < BE_MAX_CB; i++) {
if (be_cb_hooks[i].vm) {
@ -242,7 +242,7 @@ static int32_t be_cb_get_cb_list(bvm *vm) {
* We allow 4 parameters, or 3 if method (first arg is `self`)
* This could be extended if needed
\*********************************************************************************************/
static int32_t call_berry_cb(int32_t num, int32_t v0, int32_t v1, int32_t v2, int32_t v3) {
static int call_berry_cb(int num, int v0, int v1, int v2, int v3) {
// call berry cb dispatcher
int32_t ret = 0;
// retrieve vm and function
@ -271,6 +271,26 @@ static int32_t call_berry_cb(int32_t num, int32_t v0, int32_t v1, int32_t v2, in
return ret;
}
/*********************************************************************************************\
* `be_cb_deinit`:
* Clean any callback for this VM, they shouldn't call the registerd function anymore
\*********************************************************************************************/
void be_cb_deinit(bvm *vm) {
// remove all cb for this vm
for (int32_t slot = 0; slot < BE_MAX_CB; slot++) {
if (be_cb_hooks[slot].vm == vm) {
be_cb_hooks[slot].vm = NULL;
be_cb_hooks[slot].f.type == BE_NIL;
}
}
// remove the vm gen_cb for this vm
for (be_callback_handler_list_t **elt_ptr = &be_callback_handler_list_head; *elt_ptr != NULL; elt_ptr = &(*elt_ptr)->next) {
if (((*elt_ptr)->next != NULL) && ((*elt_ptr)->next->vm == vm)) {
(*elt_ptr)->next = (*elt_ptr)->next->next;
}
}
}
/* @const_object_info_begin
module cb (scope: global) {
gen_cb, func(be_cb_gen_cb)

View File

@ -109,6 +109,8 @@ extern int be_check_arg_type(bvm *vm, int arg_start, int argc, const char * arg_
extern int be_call_c_func(bvm *vm, const void * func, const char * return_type, const char * arg_type);
extern int be_call_ctype_func(bvm *vm, const void *definition); /* handler for Berry vm */
extern void be_cb_deinit(bvm *vm); /* remove all callbacks from the VM (just before shutdown of VM) */
#ifdef __cplusplus
}
#endif

View File

@ -31,6 +31,7 @@ static uint8_t ip_bytes[16] = {};
extern "C" const void* matter_get_ip_bytes(const char* ip_str, size_t* ret_len) {
IPAddress ip;
if (ip.fromString(ip_str)) {
#ifdef USE_IPV6
if (ip.isV4()) {
uint32_t ip_32 = ip;
memcpy(ip_bytes, &ip_32, 4);
@ -39,6 +40,11 @@ extern "C" const void* matter_get_ip_bytes(const char* ip_str, size_t* ret_len)
memcpy(ip_bytes, ip.raw6(), 16);
*ret_len = 16;
}
#else
uint32_t ip_32 = ip;
memcpy(ip_bytes, &ip_32, 4);
*ret_len = 4;
#endif
return ip_bytes;
} else {
*ret_len = 0;

View File

@ -204,6 +204,7 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
#include "solidify/solidified_Matter_Base38.h"
#include "solidify/solidified_Matter_UI.h"
#include "solidify/solidified_Matter_Device.h"
#include "solidify/solidified_Matter_Profiler.h"
#include "../generate/be_matter_certs.h"
@ -287,6 +288,7 @@ module matter (scope: global, strings: weak) {
sort, closure(matter_sort_closure)
jitter, closure(matter_jitter_closure)
inspect, closure(matter_inspect_closure)
Profiler, class(be_class_Matter_Profiler)
// Status codes
SUCCESS, int(0x00)
@ -336,6 +338,7 @@ module matter (scope: global, strings: weak) {
StatusIB, class(be_class_Matter_StatusIB)
StatusResponseMessage, class(be_class_Matter_StatusResponseMessage)
ReadRequestMessage, class(be_class_Matter_ReadRequestMessage)
ReadRequestMessage_solo, class(be_class_Matter_ReadRequestMessage_solo)
ReportDataMessage, class(be_class_Matter_ReportDataMessage)
SubscribeRequestMessage, class(be_class_Matter_SubscribeRequestMessage)
SubscribeResponseMessage, class(be_class_Matter_SubscribeResponseMessage)
@ -343,6 +346,7 @@ module matter (scope: global, strings: weak) {
WriteResponseMessage, class(be_class_Matter_WriteResponseMessage)
TimedRequestMessage, class(be_class_Matter_TimedRequestMessage)
InvokeRequestMessage, class(be_class_Matter_InvokeRequestMessage)
InvokeRequestMessage_solo, class(be_class_Matter_InvokeRequestMessage_solo)
InvokeResponseMessage, class(be_class_Matter_InvokeResponseMessage)
// Matter Commisioning messages

View File

@ -32,7 +32,7 @@
// `matter.QRCode.encode_str(content:string) -> map`
//
int32_t qr_encode_str(bvm *vm) {
int 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);

View File

@ -130,7 +130,7 @@ class Matter_Commisioning_Context
# record the initiator_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(format("MTR: New_Session(%6i) from '[%s]:%i'", session.__future_local_session_id, msg.remote_ip, msg.remote_port), 3)
tasmota.log(format("MTR: +Session (%6i) from '[%s]:%i'", session.__future_local_session_id, msg.remote_ip, msg.remote_port), 3)
# prepare response
var pbkdfparamresp = matter.PBKDFParamResponse()
@ -235,7 +235,7 @@ class Matter_Commisioning_Context
var raw = resp.encode_frame(pake2_raw)
# log the fact that a new commissioning is starting
tasmota.log(format("MTR: New Commissioning (PASE id=%i) from [%s]:%i", session.__future_local_session_id, session._ip, session._port))
tasmota.log(format("MTR: New Commissioning (PASE id=%i) from [%s]:%i", session.__future_local_session_id, session._ip, session._port), 2)
self.responder.send_response_frame(resp)
return true
@ -265,7 +265,7 @@ class Matter_Commisioning_Context
end
# send PakeFinished and compute session key
var created = tasmota.rtc()['utc']
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]
@ -289,18 +289,22 @@ class Matter_Commisioning_Context
import crypto
# Validate Sigma1 Destination ID, p.162
# traverse all existing fabrics
tasmota.log("MTR: SEARCHING: destinationId=" + destinationId.tohex(), 4)
if tasmota.loglevel(4)
tasmota.log("MTR: SEARCHING: destinationId=" + destinationId.tohex(), 4)
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 + fabric.get_ca_pub() + fabric.fabric_id + fabric.device_id
var key = fabric.get_ipk_group_key()
tasmota.log("MTR: SIGMA1: destinationMessage=" + destinationMessage.tohex(), 4)
# tasmota.log("MTR: SIGMA1: destinationMessage=" + destinationMessage.tohex(), 4)
# 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(), 4)
if tasmota.loglevel(4)
tasmota.log("MTR: SIGMA1: candidateDestinationId=" + candidateDestinationId.tohex(), 4)
end
if candidateDestinationId == destinationId
return fabric
end
@ -370,7 +374,7 @@ class Matter_Commisioning_Context
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(format("MTR: New_Session(%6i) from '[%s]:%i'", session.__future_local_session_id, msg.remote_ip, msg.remote_port), 3)
tasmota.log(format("MTR: +Session (%6i) from '[%s]:%i'", session.__future_local_session_id, msg.remote_ip, msg.remote_port), 3)
# Generate and Send Sigma2_Resume
session.shared_secret = session_resumption.shared_secret
@ -406,7 +410,7 @@ class Matter_Commisioning_Context
var i2r = session_keys[0..15]
var r2i = session_keys[16..31]
var ac = session_keys[32..47]
var created = tasmota.rtc()['utc']
var created = tasmota.rtc_utc()
# tasmota.log("MTR: ******************************", 4)
# tasmota.log("MTR: I2RKey =" + i2r.tohex(), 4)
@ -458,7 +462,7 @@ class Matter_Commisioning_Context
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(format("MTR: New_Session(%6i) from '[%s]:%i'", session.__future_local_session_id, msg.remote_ip, msg.remote_port), 3)
tasmota.log(format("MTR: +Session (%6i) from '[%s]:%i'", session.__future_local_session_id, msg.remote_ip, msg.remote_port), 3)
# tasmota.log("MTR: fabric="+matter.inspect(session._fabric), 4)
# tasmota.log("MTR: no_private_key="+session.get_pk().tohex(), 4)
@ -538,7 +542,7 @@ class Matter_Commisioning_Context
var raw = resp.encode_frame(sigma2_raw)
# log the fact that a new connection is starting
tasmota.log(format("MTR: New Connection (CASE id=%i) from [%s]:%i", session.__future_local_session_id, session._ip, session._port))
tasmota.log(format("MTR: New Connection (CASE id=%i) from [%s]:%i", session.__future_local_session_id, session._ip, session._port), 2)
self.responder.send_response_frame(resp)
return true
@ -658,7 +662,7 @@ class Matter_Commisioning_Context
var i2r = session_keys[0..15]
var r2i = session_keys[16..31]
var ac = session_keys[32..47]
var created = tasmota.rtc()['utc']
var created = tasmota.rtc_utc()
# tasmota.log("MTR: ******************************", 4)
# tasmota.log("MTR: I2RKey =" + i2r.tohex(), 4)

View File

@ -35,6 +35,7 @@ class Matter_Device
var plugins_config # map of JSON configuration for plugins
var plugins_config_remotes # map of information on each remote under "remotes" key, '{}' when empty
var udp_server # `matter.UDPServer()` object
var profiler
var message_handler # `matter.MessageHandler()` object
var sessions # `matter.Session_Store()` objet
var ui
@ -63,6 +64,7 @@ class Matter_Device
var root_discriminator # as `int`
var root_passcode # as `int`
var ipv4only # advertize only IPv4 addresses (no IPv6)
var disable_bridge_mode # default is bridge mode, this flag disables this mode for some non-compliant controllers
var next_ep # next endpoint to be allocated for bridge, start at 1
# context for PBKDF
var root_iterations # PBKDF number of iterations
@ -79,6 +81,7 @@ class Matter_Device
return
end # abort if SetOption 151 is not set
matter.profiler = matter.Profiler()
self.started = false
self.tick = 0
self.plugins = []
@ -92,6 +95,7 @@ class Matter_Device
self.next_ep = 1 # start at endpoint 1 for dynamically allocated endpoints
self.root_salt = crypto.random(16)
self.ipv4only = false
self.disable_bridge_mode = false
self.load_param()
self.sessions = matter.Session_Store(self)
@ -172,18 +176,24 @@ class Matter_Device
#####################################################################
# Remove a fabric and clean all corresponding values and mDNS entries
def remove_fabric(fabric_parent)
var sub_fabrics = self.sessions.find_children_fabrics(fabric_parent.get_fabric_index())
if sub_fabrics == nil return end
for fabric_index : sub_fabrics
var fabric = self.sessions.find_fabric_by_index(fabric_index)
if fabric != nil
tasmota.log("MTR: removing fabric " + fabric.get_fabric_id().copy().reverse().tohex(), 2)
self.message_handler.im.subs_shop.remove_by_fabric(fabric)
self.mdns_remove_op_discovery(fabric)
self.sessions.remove_fabric(fabric)
end
def remove_fabric(fabric)
if fabric != nil
tasmota.log("MTR: removing fabric " + fabric.get_fabric_id().copy().reverse().tohex(), 2)
self.message_handler.im.subs_shop.remove_by_fabric(fabric)
self.mdns_remove_op_discovery(fabric)
self.sessions.remove_fabric(fabric)
end
# var sub_fabrics = self.sessions.find_children_fabrics(fabric_parent.get_fabric_index())
# if sub_fabrics == nil return end
# for fabric_index : sub_fabrics
# var fabric = self.sessions.find_fabric_by_index(fabric_index)
# if fabric != nil
# tasmota.log("MTR: removing fabric " + fabric.get_fabric_id().copy().reverse().tohex(), 2)
# self.message_handler.im.subs_shop.remove_by_fabric(fabric)
# self.mdns_remove_op_discovery(fabric)
# self.sessions.remove_fabric(fabric)
# end
# end
self.sessions.save_fabrics()
end
@ -379,7 +389,7 @@ class Matter_Device
if self.udp_server return end # already started
if port == nil port = 5540 end
tasmota.log("MTR: Starting UDP server on port: " + str(port), 2)
self.udp_server = matter.UDPServer("", port)
self.udp_server = matter.UDPServer(self, "", port)
self.udp_server.start(/ raw, addr, port -> self.msg_received(raw, addr, port))
end
@ -427,7 +437,7 @@ class Matter_Device
var fabric = session.get_fabric()
var fabric_id = fabric.get_fabric_id().copy().reverse().tohex()
var vendor_name = fabric.get_admin_vendor_name()
tasmota.log(format("MTR: --- Commissioning complete for Fabric '%s' (Vendor %s) ---", fabric_id, vendor_name), 2)
tasmota.log(f"MTR: --- Commissioning complete for Fabric '{fabric_id}' (Vendor {vendor_name}) ---", 2)
self.stop_basic_commissioning() # by default close commissioning when it's complete
end
@ -510,18 +520,15 @@ class Matter_Device
end
var endpoint = ctx.endpoint
# var endpoint_mono = [ endpoint ]
var endpoint_found = false # did any endpoint match
var cluster = ctx.cluster
# var cluster_mono = [ cluster ]
var cluster_found = false
var attribute = ctx.attribute
# var attribute_mono = [ attribute ]
var endpoint_found = false # did any endpoint match
var cluster_found = false
var attribute_found = false
var direct = (ctx.endpoint != nil) && (ctx.cluster != nil) && (ctx.attribute != nil) # true if the target is a precise attribute, false if it results from an expansion and error are ignored
# tasmota.log(format("MTR: process_attribute_expansion %s", str(ctx)), 4)
# tasmota.log(f"MTR: process_attribute_expansion {str(ctx))}", 4)
# build the list of candidates
@ -537,7 +544,7 @@ class Matter_Device
endpoint_found = true
# now explore the cluster list for 'ep'
var cluster_list = pi.get_cluster_list(ep) # cluster_list is the actual list of candidate cluster for this pluging and endpoint
var cluster_list = pi.get_cluster_list() # cluster_list is the actual list of candidate cluster for this pluging and endpoint
# tasmota.log(format("MTR: pi=%s ep=%s cl_list=%s", str(pi), str(ep), str(cluster_list)), 4)
for cl: cluster_list
if cluster != nil && cl != cluster continue end # skip if specific cluster and no match
@ -546,7 +553,7 @@ class Matter_Device
cluster_found = true
# now filter on attributes
var attr_list = pi.get_attribute_list(ep, cl)
var attr_list = pi.get_attribute_list(cl)
# tasmota.log(format("MTR: pi=%s ep=%s cl=%s at_list=%s", str(pi), str(ep), str(cl), str(attr_list)), 4)
for at: attr_list
if attribute != nil && at != attribute continue end # skip if specific attribute and no match
@ -591,6 +598,44 @@ class Matter_Device
end
end
#############################################################
# Optimized version for a single endpoint/cluster/attribute
#
# Retrieve the plugin for a read
def process_attribute_read_solo(ctx)
var endpoint = ctx.endpoint
# var endpoint_found = false # did any endpoint match
var cluster = ctx.cluster
# var cluster_found = false
var attribute = ctx.attribute
# var attribute_found = false
# all 3 elements must be non-nil
if endpoint == nil || cluster == nil || attribute == nil return nil end
# look for plugin
var pi = self.find_plugin_by_endpoint(endpoint)
if pi == nil # endpoint not found
ctx.status = matter.UNSUPPORTED_ENDPOINT
return nil
end
# check cluster
if !pi.contains_cluster(cluster)
ctx.status = matter.UNSUPPORTED_CLUSTER
return nil
end
# attribute list
if !pi.contains_attribute(cluster, attribute)
ctx.status = matter.UNSUPPORTED_ATTRIBUTE
return nil
end
# all good
return pi
end
#############################################################
# Return the list of endpoints from all plugins (distinct), exclud endpoint zero if `exclude_zero` is `true`
def get_active_endpoints(exclude_zero)
@ -628,7 +673,7 @@ class Matter_Device
import json
self.update_remotes_info() # update self.plugins_config_remotes
var j = format('{"distinguish":%i,"passcode":%i,"ipv4only":%s,"nextep":%i', self.root_discriminator, self.root_passcode, self.ipv4only ? 'true':'false', self.next_ep)
var j = format('{"distinguish":%i,"passcode":%i,"ipv4only":%s,"disable_bridge_mode":%s,"nextep":%i', self.root_discriminator, self.root_passcode, self.ipv4only ? 'true':'false', self.disable_bridge_mode ? 'true':'false', self.next_ep)
if self.plugins_persist
j += ',"config":'
j += json.dump(self.plugins_config)
@ -642,7 +687,7 @@ class Matter_Device
var f = open(self.FILENAME, "w")
f.write(j)
f.close()
tasmota.log(format("MTR: =Saved parameters%s", self.plugins_persist ? " and configuration" : ""), 3)
tasmota.log(format("MTR: =Saved parameters%s", self.plugins_persist ? " and configuration" : ""), 2)
return j
except .. as e, m
tasmota.log("MTR: Session_Store::save Exception:" + str(e) + "|" + str(m), 2)
@ -693,6 +738,7 @@ class Matter_Device
self.root_discriminator = j.find("distinguish", self.root_discriminator)
self.root_passcode = j.find("passcode", self.root_passcode)
self.ipv4only = bool(j.find("ipv4only", false))
self.disable_bridge_mode = bool(j.find("disable_bridge_mode", false))
self.next_ep = j.find("nextep", self.next_ep)
self.plugins_config = j.find("config")
if self.plugins_config != nil
@ -1106,13 +1152,13 @@ class Matter_Device
if !r_st13.contains(k) break end # no more SHTxxx
var d = r_st13[k]
tasmota.log(format("MTR: '%s' = %s", k, str(d)), 3)
var relay1 = d.find('Relay1', 0) - 1 # relay base 0 or -1 if none
var relay2 = d.find('Relay2', 0) - 1 # relay base 0 or -1 if none
var relay1 = d.find('Relay1', -1) # relay base 1 or -1 if none
var relay2 = d.find('Relay2', -1) # relay base 1 or -1 if none
if relay1 >= 0 relays_reserved.push(relay1) end # mark relay1/2 as non-relays
if relay2 >= 0 relays_reserved.push(relay2) end
if relay1 > 0 relays_reserved.push(relay1 - 1) end # mark relay1/2 as non-relays
if relay2 > 0 relays_reserved.push(relay2 - 1) end
tasmota.log(format("MTR: relay1 = %s, relay2 = %s", relay1, relay2), 3)
tasmota.log(f"MTR: {relay1=} {relay2=}", 3)
# is there tilt support
var tilt_array = d.find('TiltConfig')
var tilt_config = tilt_array && (tilt_array[2] > 0)
@ -1131,7 +1177,7 @@ class Matter_Device
while relay_index < relay_count
if relays_reserved.find(relay_index) == nil # if relay is actual relay
m[str(endpoint)] = {'type':'relay','relay':relay_index}
m[str(endpoint)] = {'type':'relay','relay':relay_index + 1} # Relay index start with 1
endpoint += 1
end
relay_index += 1
@ -1312,23 +1358,23 @@ class Matter_Device
self.plugins_config.remove(ep_str)
self.plugins_persist = true
# try saving parameters
self.save_param()
self.signal_endpoints_changed()
# now remove from in-memory configuration
var idx = 0
while idx < size(self.plugins)
if ep == self.plugins[idx].get_endpoint()
self.plugins.remove(idx)
self.signal_endpoints_changed()
break
else
idx += 1
end
end
# clean any orphan remote
self.clean_remotes()
# try saving parameters
self.save_param()
self.signal_endpoints_changed()
end
#############################################################
@ -1412,13 +1458,15 @@ class Matter_Device
def clean_remotes()
import introspect
# print("clean_remotes", self.http_remotes)
# init all remotes with count 0
if self.http_remotes
if self.http_remotes # tests if `self.http_remotes` is not `nil` and not empty
var remotes_map = {} # key: remote object, value: count of references
for http_remote: self.http_remotes
remotes_map[http_remote] = 0
end
# print("remotes_map", remotes_map)
# scan all endpoints
for pi: self.plugins
@ -1428,16 +1476,23 @@ class Matter_Device
end
end
# print("remotes_map2", remotes_map)
# tasmota.log("MTR: remotes references: " + str(remotes_map), 3)
var remote_to_remove = [] # we first get the list of remotes to remove, to not interfere with map iterator
for remote:remotes_map.keys()
if remotes_map[remote] == 0
# remove
tasmota.log("MTR: remove unused remote: " + remote.addr, 3)
remote.close()
self.http_remotes.remove(remote)
remote_to_remove.push(remote)
end
end
for remote: remote_to_remove
tasmota.log("MTR: remove unused remote: " + remote.addr, 3)
remote.close()
self.http_remotes.remove(remote.addr)
end
end
end

View File

@ -96,7 +96,7 @@ class Matter_Expirable
# 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
if now == nil now = tasmota.rtc_utc() end
self.set_expire_time(now + s)
end
@ -104,7 +104,7 @@ class Matter_Expirable
# 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 now == nil now = tasmota.rtc_utc() end
if self._expiration != nil
return now >= self._expiration
end

View File

@ -75,7 +75,7 @@ class Matter_Fabric : Matter_Expirable
self._store = store
self._sessions = matter.Expirable_list()
self.fabric_label = ""
self.created = tasmota.rtc()['utc']
self.created = tasmota.rtc_utc()
# init group counters
self._counter_group_data_snd_impl = matter.Counter()
self._counter_group_ctrl_snd_impl = matter.Counter()
@ -247,7 +247,9 @@ class Matter_Fabric : Matter_Expirable
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()))
var session_deleted = self.get_oldest_session()
self._sessions.remove(self._sessions.find(session_deleted))
self._store.remove_session(session_deleted)
end
self._sessions.push(s)
end

View File

@ -177,7 +177,7 @@ class Matter_HTTP_remote : Matter_HTTP_async
end
# reduce the update time after a read is succesful
self.change_schedule(self.UPDATE_CMD0, self.UPDATE_TIME2)
self.change_schedule(self.UPDATE_CMD5, self.UPDATE_TIME2)
end
if changed self.info_changed() end
@ -189,7 +189,7 @@ class Matter_HTTP_remote : Matter_HTTP_async
if alive
# device is known to be reachable
self.reachable = true
self.reachable_utc = tasmota.rtc()['utc']
self.reachable_utc = tasmota.rtc_utc()
else
self.reachable = false
end
@ -344,7 +344,7 @@ class Matter_HTTP_remote : Matter_HTTP_async
var seconds = -1 # default if no known value
if self.reachable_utc != nil
seconds = tasmota.rtc()['utc'] - self.reachable_utc
seconds = tasmota.rtc_utc() - self.reachable_utc
end
return matter.seconds_to_dhm(seconds)
end

View File

@ -28,30 +28,53 @@ class Matter_IM
var device
var subs_shop # subscriptions shop
var send_queue # list of IM_Message queued for sending as part of exchange-id
var send_queue # list of IM_Message queued for sending as part of exchange-id
var read_request_solo # instance of ReadRequestMessage_solo to optimize single reads
var invoke_request_solo # instance of InvokeRequestMessage_solo to optimize single reads
var tlv_solo # instance of Matter_TLV_item for simple responses
def init(device)
self.device = device
self.send_queue = []
self.subs_shop = matter.IM_Subscription_Shop(self)
self.read_request_solo = matter.ReadRequestMessage_solo()
self.invoke_request_solo = matter.InvokeRequestMessage_solo()
self.tlv_solo = matter.TLV.Matter_TLV_item()
end
def process_incoming(msg)
# messages are always TLV, decode payload
# tasmota.log("MTR: received IM message " + matter.inspect(msg), 3)
var opcode = msg.opcode
# Fast-Track processing
# first pass is optimized for simple frequent messages and avoids complete decoding of TLV
if opcode == 0x02 # Read Request
var read_request_solo = self.read_request_solo.from_raw(msg.raw, msg.app_payload_idx)
if read_request_solo != nil
# tasmota.log(f"MTR: process_incoming {read_request_solo=}")
return self.process_read_request_solo(msg, read_request_solo)
end
elif opcode == 0x08 # Invoke Request
var invoke_request_solo = self.invoke_request_solo.from_raw(msg.raw, msg.app_payload_idx)
# tasmota.log(f"MTR: {invoke_request_solo=} {msg.raw[msg.app_payload_idx .. ].tohex()} {msg.app_payload_idx=} {msg.raw.tohex()}")
if invoke_request_solo != nil
return self.process_invoke_request_solo(msg, invoke_request_solo)
end
end
# tasmota.log("MTR: received IM message " + matter.inspect(msg), 3)
var val = matter.TLV.parse(msg.raw, msg.app_payload_idx)
# tasmota.log("MTR: IM TLV: " + str(val), 3)
var InteractionModelRevision = val.findsubval(0xFF)
# var InteractionModelRevision = val.findsubval(0xFF)
# 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)
# self.send_ack_now(msg) # to improve latency, we don't automatically Ack on invoke request
return self.process_read_request(msg, val)
elif opcode == 0x03 # Subscribe Request
self.send_ack_now(msg)
@ -66,7 +89,7 @@ class Matter_IM
elif opcode == 0x07 # Write Response
return self.process_write_response(msg, val)
elif opcode == 0x08 # Invoke Request
self.send_ack_now(msg)
# self.send_ack_now(msg) # to improve latency, we don't automatically Ack on invoke request
return self.process_invoke_request(msg, val)
elif opcode == 0x09 # Invoke Response
return self.process_invoke_response(msg, val)
@ -97,6 +120,7 @@ class Matter_IM
#
# returns `true` if packet could be sent
def send_ack_now(msg)
if msg == nil return end
msg.session._message_handler.send_encrypted_ack(msg, false #-not reliable-#)
end
@ -201,7 +225,7 @@ class Matter_IM
# Inner code shared between read_attributes and subscribe_request
#
# query: `ReadRequestMessage` or `SubscribeRequestMessage`
def _inner_process_read_request(session, query, no_log)
def _inner_process_read_request(session, query, msg, no_log)
### Inner function to be iterated upon
# ret is the ReportDataMessage list to send back
@ -215,48 +239,55 @@ class Matter_IM
var TLV = matter.TLV
var attr_name = matter.get_attribute_name(ctx.cluster, ctx.attribute)
attr_name = attr_name ? " (" + attr_name + ")" : ""
# Special case to report unsupported item, if pi==nil
var res = (pi != nil) ? pi.read_attribute(session, ctx) : nil
var res = (pi != nil) ? pi.read_attribute(session, ctx, self.tlv_solo) : nil
var found = true # stop expansion since we have a value
var a1_raw # this is the bytes() block we need to add to response (or nil)
var a1_raw_or_list # contains either a bytes() buffer to append, or a list of bytes(), or nil
if res != nil
var res_str = str(res) # get the value with anonymous tag before it is tagged, for logging
var res_str = ""
if !no_log
res_str = res.to_str_val() # get the value with anonymous tag before it is tagged, for logging
end
var a1 = matter.AttributeReportIB()
a1.attribute_data = matter.AttributeDataIB()
a1.attribute_data.data_version = 1
a1.attribute_data.path = matter.AttributePathIB()
a1.attribute_data.path.endpoint = ctx.endpoint
a1.attribute_data.path.cluster = ctx.cluster
a1.attribute_data.path.attribute = ctx.attribute
a1.attribute_data.data = res
# check if too big to encode as a single packet
if (res.is_list || res.is_array) && res.encode_len() > matter.IM_ReportData.MAX_MESSAGE
# tasmota.log(f"MTR: >>>>>> long response", 3)
a1_raw_or_list = [] # we return a list of block
var a1_raw = bytes(48)
var empty_list = TLV.Matter_TLV_array()
self.attributedata2raw(a1_raw, ctx, empty_list, false)
a1_raw_or_list.push(a1_raw)
# tasmota.log(f"MTR: >>>>>> long response global DELETE {a1_raw.tohex()}", 3)
var a1_tlv = a1.to_TLV()
var a1_len = a1_tlv.encode_len()
var a1_bytes = bytes(a1_len) # pre-size bytes() to the actual size
a1_raw = a1_tlv.tlv2raw(a1_bytes)
# tasmota.log(format("MTR: guessed len=%i actual=%i '%s'", a1_len, size(a1_raw), a1_raw.tohex()), 2)
for elt:res.val
a1_raw = bytes(48)
# var list_item = TLV.Matter_TLV_array()
# list_item.val.push(elt)
self.attributedata2raw(a1_raw, ctx, elt, true #- add ListIndex:null -#)
# tasmota.log(f"MTR: >>>>>> long response global ADD {a1_raw.tohex()}", 3)
a1_raw_or_list.push(a1_raw)
end
# tasmota.log(f"MTR: >>>>>> long response global {a1_raw_or_list}", 3)
else
# normal encoding
# encode directly raw bytes()
a1_raw_or_list = bytes(48) # pre-reserve 48 bytes
self.attributedata2raw(a1_raw_or_list, ctx, res)
end
if !no_log
tasmota.log(format("MTR: >Read_Attr (%6i) %s%s - %s", session.local_session_id, str(ctx), attr_name, res_str), 3)
tasmota.log(f"MTR: >Read_Attr ({session.local_session_id:6i}) {ctx}{attr_name} - {res_str}", 3)
end
elif ctx.status != nil
if direct # we report an error only if a concrete direct read, not with wildcards
var a1 = matter.AttributeReportIB()
a1.attribute_status = matter.AttributeStatusIB()
a1.attribute_status.path = matter.AttributePathIB()
a1.attribute_status.status = matter.StatusIB()
a1.attribute_status.path.endpoint = ctx.endpoint
a1.attribute_status.path.cluster = ctx.cluster
a1.attribute_status.path.attribute = ctx.attribute
a1.attribute_status.status.status = ctx.status
# encode directly raw bytes()
a1_raw_or_list = bytes(48) # pre-reserve 48 bytes
self.attributestatus2raw(a1_raw_or_list, ctx, ctx.status)
var a1_tlv = a1.to_TLV()
var a1_len = a1_tlv.encode_len()
var a1_bytes = bytes(a1_len) # pre-size bytes() to the actual size
a1_raw = a1_tlv.tlv2raw(a1_bytes)
tasmota.log(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" : ""), 3)
if tasmota.loglevel(3)
tasmota.log(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" : ""), 3)
end
end
else
tasmota.log(format("MTR: >Read_Attr (%6i) %s%s - IGNORED", session.local_session_id, str(ctx), attr_name), 3)
@ -264,27 +295,55 @@ class Matter_IM
found = false
end
# check if we still have enough room in last block
if a1_raw # do we have bytes to add, and it's not zero size
# a1_raw_or_list if either nil, bytes(), of list(bytes())
var idx = isinstance(a1_raw_or_list, list) ? 0 : nil # index in list, or nil if non-list
while a1_raw_or_list != nil
var elt = (idx == nil) ? a1_raw_or_list : a1_raw_or_list[idx] # dereference
if size(ret.attribute_reports) == 0
ret.attribute_reports.push(a1_raw) # push raw binary instead of a TLV
ret.attribute_reports.push(elt) # push raw binary instead of a TLV
else # already blocks present, see if we can add to the latest, or need to create a new block
var last_block = ret.attribute_reports[-1]
if size(last_block) + size(a1_raw) <= matter.IM_ReportData.MAX_MESSAGE
if size(last_block) + size(elt) <= matter.IM_ReportData.MAX_MESSAGE
# add to last block
last_block .. a1_raw
last_block .. elt
else
ret.attribute_reports.push(a1_raw) # push raw binary instead of a TLV
ret.attribute_reports.push(elt) # push raw binary instead of a TLV
end
end
if idx == nil
a1_raw_or_list = nil # stop loop
else
idx += 1
if idx >= size(a1_raw_or_list)
a1_raw_or_list = nil # stop loop
end
end
end
# check if we still have enough room in last block
# if a1_raw_or_list # do we have bytes to add, and it's not zero size
# if size(ret.attribute_reports) == 0
# ret.attribute_reports.push(a1_raw_or_list) # push raw binary instead of a TLV
# else # already blocks present, see if we can add to the latest, or need to create a new block
# var last_block = ret.attribute_reports[-1]
# if size(last_block) + size(a1_raw_or_list) <= matter.IM_ReportData.MAX_MESSAGE
# # add to last block
# last_block .. a1_raw_or_list
# else
# ret.attribute_reports.push(a1_raw_or_list) # push raw binary instead of a TLV
# end
# end
# end
return found # return true if we had a match
end
var endpoints = self.device.get_active_endpoints()
# structure is `ReadRequestMessage` 10.6.2 p.558
var ctx = matter.Path()
ctx.msg = msg
# prepare the response
var ret = matter.ReportDataMessage()
@ -296,6 +355,7 @@ class Matter_IM
ctx.endpoint = q.endpoint
ctx.cluster = q.cluster
ctx.attribute = q.attribute
ctx.fabric_filtered = query.fabric_filtered
ctx.status = matter.UNSUPPORTED_ATTRIBUTE #default error if returned `nil`
# expand endpoint
@ -322,6 +382,290 @@ class Matter_IM
return ret
end
#############################################################
# path2raw
#
# Encodes endpoint/cluster/attribute as `AttributePathIB` elements
# Takes sub-tag
#
# 1 = AttributePathIB
# 0 = EnableTagCompression bool opt
# 1 = Node
# 2 = Endpoint
# 3 = Cluste
# 4 = Attribute
# 5 = ListIndex (opt)
#
# 3701 1 = LIST
# 2402 01 2 = 1U (U1)
# 2403 39 3 = 0x39U (U1)
# 2404 11 4 = 0x11U (U1)
# [OPTIONAL ListIndex]
# 3405 5 = NULL
# 18
def path2raw(raw, ctx, sub_tag, list_index_null)
# open struct
raw.add(0x37, 1) # add 37
raw.add(sub_tag, 1) # add sub_tag
# add endpoint
if ctx.endpoint <= 0xFF # endpoint is 16 bits max
raw.add(0x2402, -2) # add 2402
raw.add(ctx.endpoint, 1)
else
raw.add(0x2502, -2) # add 2502
raw.add(ctx.endpoint, 2)
end
# add cluster
if ctx.cluster <= 0xFF # cluster is 32 bits max
raw.add(0x2403, -2) # add 2403
raw.add(ctx.cluster, 1)
elif ctx.cluster <= 0xFFFF
raw.add(0x2503, -2) # add 2503
raw.add(ctx.cluster, 2)
else
raw.add(0x2603, -2) # add 2603
raw.add(ctx.cluster, 4)
end
# add attribute
if ctx.attribute <= 0xFF # cluster is 32 bits max
raw.add(0x2404, -2) # add 2404
raw.add(ctx.attribute, 1)
elif ctx.attribute <= 0xFFFF
raw.add(0x2504, -2) # add 2504
raw.add(ctx.attribute, 2)
else
raw.add(0x2604, -2) # add 2604
raw.add(ctx.attribute, 4)
end
# do we add ListIndex: null
if list_index_null
raw.add(0x3405, -2) # add 3405
end
# close
raw.add(0x18, 1) # add 18
end
#############################################################
# attributedata2raw
#
# generate a raw version of AttributeDataIB
#
# Typical answer
#
# AttributeReportIB
# 0 = AttributeStatusIB
# 1 = AttributeDataIB
# 0 = DataVersion U1
# 1 = AttributePathIB
# 0 = EnableTagCompression bool opt
# 1 = Node
# 2 = Endpoint
# 3 = Cluste
# 4 = Attribute
# 5 = ListIndex (opt)
# 2 = Data
#
# 153601.15350124000137012402012403392404111829021818.1824FF0118
# 15350124000137012402012403392404111829021818
# 1535012400013701
# 240201240339240411
# 1829021818
#
# 15
# 3501 1 = {}
# 2400 01 0 = 1U (U1)
# 3701 1 = LIST
# 2402 01 2 = 1U (U1)
# 2403 39 3 = 0x39U (U1)
# 2404 11 4 = 0x11U (U1)
# [OPTIONAL ListIndex]
# 3405 5 = NULL
#
# 18
# 2902 2 = True
# 18
# 18
def attributedata2raw(raw, ctx, val, list_index_null)
raw.add(0x15350124, -4) # add 15350124
raw.add(0x0001, -2) # add 0001
self.path2raw(raw, ctx, 0x01, list_index_null)
# add value with tag 2
val.tag_sub = 2
val.tlv2raw(raw)
# close 2 structs
raw.add(0x1818, -2)
end
#############################################################
# attributedata2raw
#
# generate a raw version of AttributeStatusIB
#
#
# Typical answer
#
# AttributeReportIB
# 0 = AttributeStatusIB
# 0 = AttributePathIB
# 0 = EnableTagCompression bool opt
# 1 = Node
# 2 = Endpoint
# 3 = Cluste
# 4 = Attribute
# 5 = ListIndex (opt)
# 1 = StatusIB
# 0 = Status (u1)
# 1 = ClusterStatus (u1)
# 1 = AttributeDataIB
#
# 15360115350037002402012403022404031835012400041818181824FF0118
# 153601 1535003700 - 2402012403022404031835012400041818181824FF0118
#
# 15
# 3601
#
# 15
# 3500 0 = struct
# 3700 0 = list
# 240201 2 = 1U endpoint
# 240302 3 = 2U cluster
# 240403 4 = 3U attribute
# 18
# 3501 1 = struct
# 240004 0 = 4U status
# 18
# 18
# 18
#
# 18
# 24FF01
# 18
def attributestatus2raw(raw, ctx, status)
raw.add(0x15, 1) # add 15
raw.add(0x3500, -2) # add 3500
self.path2raw(raw, ctx, 0x00)
raw.add(0x3501, -2) # add 3501 for status
# status
if ctx.status <= 255
raw.add(0x2400, -2) # add 2400
raw.add(ctx.status, 1)
else
raw.add(0x2500, -2) # add 2500
raw.add(ctx.status, 2)
end
# close
raw.add(0x1818, -2) # add 1818
raw.add(0x18, 1) # add 18
end
#############################################################
# attributedata2raw
#
# generate a raw version of InvokeResponseIB()
# Typical answer
#
# 1535013700240011240122240244183501240000181818
#
# 0 = CommandDataIB
# 0 = CommandPathIB
# 0 = endpoint u2
# 1 = cluster u4
# 2 = command u4
# 1 = <fields>
# 1 = CommandStatusIB
# 0 = CommandPathIB
# 0 = endpoint u2
# 1 = cluster u4
# 2 = command u4
# 1 = StatusIB
# 0 = status u1
# 1 = ClusterStatus u1
#
# 1535013700240011240122240244183501240000181818
# 15
# 3501 1 = struct
# 3700 0 = list
# 240011 0 = endpoint
# 240122 1 = cluster
# 240244 2 = command
# 18
# 3501 1 = struct
# 240000 0 = 0 (status)
# 18
# 18
# 18
#
# 1535003700240011240122240244182401031818
# 15
# 3500 0 = struct
# 3700 0 = list
# 240011 0 = endpoint
# 240122 1 = cluster
# 240244 2 = command
# 18
# 240103 1 = <field>
# 18
# 18
def invokeresponse2raw(raw, ctx, val)
raw.add(0x15, 1) # add 15
if val == nil
raw.add(0x3501, -2) # add 3500
else
raw.add(0x3500, -2) # add 3500
end
raw.add(0x3700, -2) # add 3700
# add endpoint
if ctx.endpoint <= 0xFF # endpoint is 16 bits max
raw.add(0x2400, -2) # add 2400
raw.add(ctx.endpoint, 1)
else
raw.add(0x2500, -2) # add 2500
raw.add(ctx.endpoint, 2)
end
# add cluster
if ctx.cluster <= 0xFF # cluster is 32 bits max
raw.add(0x2401, -2) # add 2401
raw.add(ctx.cluster, 1)
elif ctx.cluster <= 0xFFFF
raw.add(0x2501, -2) # add 2501
raw.add(ctx.cluster, 2)
else
raw.add(0x2601, -2) # add 2601
raw.add(ctx.cluster, 4)
end
# add attribute
if ctx.command <= 0xFF # cluster is 32 bits max
raw.add(0x2402, -2) # add 2402
raw.add(ctx.command, 1)
elif ctx.command <= 0xFFFF
raw.add(0x2502, -2) # add 2502
raw.add(ctx.command, 2)
else
raw.add(0x2602, -2) # add 2602
raw.add(ctx.command, 4)
end
raw.add(0x18, 1) # add 18
# either value or statuc
if val == nil
var status = ctx.status
if status == nil status = matter.SUCCESS end
raw.add(0x3501, -2) # add 3501
raw.add(0x2400, -2) # add 2400
raw.add(ctx.status, 1) # add status:1
raw.add(0x18, 1) # add 18
else
val.tag_sub = 1 # set sub_tag for reponse
val.tlv2raw(raw)
end
# close
raw.add(0x1818, -2) # add 1818
end
#############################################################
# process IM 0x02 Read Request
#
@ -329,15 +673,130 @@ class Matter_IM
# returns `true` if processed, `false` if silently ignored,
# or raises an exception
def process_read_request(msg, val)
matter.profiler.log("read_request_start")
# matter.profiler.log(str(val))
var query = matter.ReadRequestMessage().from_TLV(val)
# matter.profiler.log(str(query))
if query.attributes_requests != nil
var ret = self._inner_process_read_request(msg.session, query)
var ret = self._inner_process_read_request(msg.session, query, msg)
self.send_report_data(msg, ret)
end
return true
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_solo(msg, ctx)
# prepare fallback error
ctx.status = matter.INVALID_ACTION
ctx.msg = msg
# find pi for this endpoint/cluster/attribute
var pi = self.device.process_attribute_read_solo(ctx)
var res = nil
# matter.profiler.log("read_request_solo pi ok")
# tasmota.log(f"MTR: process_read_request_solo {pi=}")
var raw # this is the bytes() block we need to add to response (or nil)
if pi != nil
ctx.status = matter.UNSUPPORTED_ATTRIBUTE # new fallback error
res = pi.read_attribute(msg.session, ctx, self.tlv_solo)
end
matter.profiler.log("read_request_solo read done")
if res != nil
# check if the payload is a complex structure and too long to fit in a single response packet
if (res.is_list || res.is_array) && res.encode_len() > matter.IM_ReportData.MAX_MESSAGE
# revert to standard
# the attribute will be read again, but it's hard to avoid it
res = nil # indicated to GC that we don't need it again
tasmota.log(f"MTR: Response to big, revert to non-solo", 3)
var val = matter.TLV.parse(msg.raw, msg.app_payload_idx)
return self.process_read_request(msg, val)
end
# encode directly raw bytes()
raw = bytes(48) # pre-reserve 48 bytes
raw.add(0x15, 1) # add 15
raw.add(0x3601, -2) # add 3601
self.attributedata2raw(raw, ctx, res)
# add suffix 1824FF0118
raw.add(0x1824FF01, -4) # add 1824FF01
raw.add(0x18, 1) # add 18
elif ctx.status != nil
# encode directly raw bytes()
raw = bytes(48) # pre-reserve 48 bytes
raw.add(0x15, 1) # add 15
raw.add(0x3601, -2) # add 3601
self.attributestatus2raw(raw, ctx, ctx.status)
# add suffix 1824FF0118
raw.add(0x1824FF01, -4) # add 1824FF01
raw.add(0x18, 1) # add 18
else
tasmota.log(f"MTR: >Read_Attr ({msg.session.local_session_id:6i}) {ctx} - IGNORED", 3)
return false
end
# send packet
var resp = msg.build_response(0x05 #-Report Data-#, true)
var responder = self.device.message_handler
var msg_raw = msg.raw
msg_raw.clear()
resp.encode_frame(raw, msg_raw) # payload in cleartext
resp.encrypt()
if tasmota.loglevel(4)
tasmota.log(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), 4)
end
responder.send_response_frame(resp)
# postpone lengthy operations after sending back response
matter.profiler.log("RESPONSE SENT")
var attr_name = matter.get_attribute_name(ctx.cluster, ctx.attribute)
attr_name = attr_name ? " (" + attr_name + ")" : ""
if res != nil
var res_str = res.to_str_val() # get the value with anonymous tag before it is tagged, for logging
if tasmota.loglevel(3)
tasmota.log(f"MTR: >Read_Attr1({msg.session.local_session_id:6i}) {ctx}{attr_name} - {res_str}", 3)
end
# if matter.profiler.active && tasmota.loglevel(3)
# tasmota.log(f"MTR: {raw=}", 3) # TODO remove before flight
# end
elif ctx.status != nil
var unsupported_attribute = (ctx.status == matter.UNSUPPORTED_ATTRIBUTE ? "UNSUPPORTED_ATTRIBUTE" : "")
if tasmota.loglevel(3)
tasmota.log(f"MTR: >Read_Attr1({msg.session.local_session_id:6i}) {ctx}{attr_name} - STATUS: 0x{ctx.status:02X} {unsupported_attribute}", 3)
end
# if matter.profiler.active && tasmota.loglevel(3)
# tasmota.log(f"MTR: {raw=}", 3) # TODO remove before flight
# end
else
if tasmota.loglevel(3)
tasmota.log(f"MTR: >Read_Attr1({msg.session.local_session_id:6i}) {ctx}{attr_name} - IGNORED", 3)
end
end
# matter.profiler.log("read_request_solo end")
return true
end
#############################################################
# process IM 0x03 Subscribe Request
#
@ -355,16 +814,20 @@ class Matter_IM
# expand a string with all attributes requested
var attr_req = []
var ctx = matter.Path()
ctx.msg = msg
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(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), 3)
tasmota.log(format("MTR: >Subscribe (%6i) %s (min=%i, max=%i, keep=%i) sub=%i fabric_filtered=%s",
msg.session.local_session_id, attr_req.concat(" "), sub.min_interval, sub.max_interval, query.keep_subscriptions ? 1 : 0, sub.subscription_id, query.fabric_filtered), 3)
if query.event_requests != nil && size(query.event_requests) > 0
tasmota.log(f"MTR: >Subscribe (%6i) event_requests_size={size(query.event_requests)}", 3)
end
var ret = self._inner_process_read_request(msg.session, query, true #-no_log-#)
var ret = self._inner_process_read_request(msg.session, query, msg, 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)
@ -378,9 +841,12 @@ class Matter_IM
# returns `true` if processed, `false` if silently ignored,
# or raises an exception
def process_invoke_request(msg, val)
# import debug
# structure is `ReadRequestMessage` 10.6.2 p.558
# tasmota.log("MTR: IM:invoke_request processing start", 4)
matter.profiler.log("invoke_request_start")
var ctx = matter.Path()
ctx.msg = msg
var query = matter.InvokeRequestMessage().from_TLV(val)
if query.invoke_requests != nil
@ -398,52 +864,44 @@ class Matter_IM
var cmd_name = matter.get_command_name(ctx.cluster, ctx.command)
var ctx_str = str(ctx) # keep string before invoking, it is modified by response
var res = self.device.invoke_request(msg.session, q.command_fields, ctx)
matter.profiler.log("COMMAND DONE")
var params_log = (ctx.log != nil) ? "(" + str(ctx.log) + ") " : ""
tasmota.log(format("MTR: >Command (%6i) %s %s %s", msg.session.local_session_id, ctx_str, cmd_name ? cmd_name : "", params_log), ctx.endpoint != 0 ? 2 : 3 #- don't log for endpoint 0 -# )
# tasmota.log("MTR: Perf/Command = " + str(debug.counters()), 4)
ctx.log = nil
var a1 = matter.InvokeResponseIB()
var raw = bytes(32)
# var a1 = matter.InvokeResponseIB()
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(format("MTR: <Replied (%6i) OK exch=%i", msg.session.local_session_id, msg.exchange_id), 3)
ctx.status = matter.SUCCESS
self.invokeresponse2raw(raw, ctx, nil)
ret.invoke_responses.push(raw)
if tasmota.loglevel(3)
tasmota.log(f"MTR: <Replied ({msg.session.local_session_id:6i}) OK exch={msg.exchange_id:i}", 3)
end
elif res != nil
a1.command = matter.CommandDataIB()
a1.command.command_path = matter.CommandPathIB()
a1.command.command_path.endpoint = ctx.endpoint
a1.command.command_path.cluster = ctx.cluster
a1.command.command_path.command = ctx.command
a1.command.command_fields = res
ret.invoke_responses.push(a1)
self.invokeresponse2raw(raw, ctx, res)
ret.invoke_responses.push(raw)
cmd_name = matter.get_command_name(ctx.cluster, ctx.command)
tasmota.log(format("MTR: <Replied (%6i) %s %s", msg.session.local_session_id, str(ctx), cmd_name ? cmd_name : ""), 3)
if !cmd_name cmd_name = "" end
if tasmota.loglevel(3)
tasmota.log(f"MTR: <Replied ({msg.session.local_session_id:6i}) {ctx} {cmd_name}", 3)
end
elif ctx.status != nil
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 = ctx.status
ret.invoke_responses.push(a1)
tasmota.log(format("MTR: <Replied (%6i) Status=0x%02X exch=%i", msg.session.local_session_id, ctx.status, msg.exchange_id), 3)
self.invokeresponse2raw(raw, ctx, nil)
ret.invoke_responses.push(raw)
if tasmota.loglevel(3)
tasmota.log(f"MTR: <Replied ({msg.session.local_session_id:6i}) Status=0x{ctx.status:02X} exch={msg.exchange_id:i}", 3)
end
else
tasmota.log(format("MTR: _Ignore (%6i) exch=%i", msg.session.local_session_id, msg.exchange_id), 3)
if tasmota.loglevel(3)
tasmota.log(f"MTR: _Ignore ({msg.session.local_session_id:6i}) exch={msg.exchange_id:i}", 3)
end
# ignore if content is nil and status is undefined
end
end
# tasmota.log("MTR: invoke_responses="+str(ret.invoke_responses), 4)
if size(ret.invoke_responses) > 0
# tasmota.log("MTR: InvokeResponse=" + str(ret), 4)
# tasmota.log("MTR: InvokeResponseTLV=" + str(ret.to_TLV()), 3)
self.send_invoke_response(msg, ret)
else
return false # we don't send anything, hence the responder sends a simple packet ack
@ -452,6 +910,78 @@ class Matter_IM
end
end
#############################################################
# process IM 0x08 Invoke Request
#
# val is the TLV structure
# returns `true` if processed, `false` if silently ignored,
# or raises an exception
def process_invoke_request_solo(msg, ctx)
# import debug
matter.profiler.log("invoke_request_solo_start")
ctx.msg = msg
ctx.status = matter.UNSUPPORTED_COMMAND #default error if returned `nil`
var cmd_name = matter.get_command_name(ctx.cluster, ctx.command)
var ctx_str = str(ctx) # keep string before invoking, it is modified by response
var res = self.device.invoke_request(msg.session, ctx.command_fields, ctx)
matter.profiler.log("COMMAND DONE")
var params_log = (ctx.log != nil) ? "(" + str(ctx.log) + ") " : ""
var cmd_log_level = ctx.endpoint != 0 ? 2 : 3 #- don't log for endpoint 0 -#
if tasmota.loglevel(cmd_log_level)
tasmota.log(format("MTR: >Command1 (%6i) %s %s %s", msg.session.local_session_id, ctx_str, cmd_name ? cmd_name : "", params_log), cmd_log_level)
end
# tasmota.log("MTR: Perf/Command = " + str(debug.counters()), 4)
ctx.log = nil
var raw = bytes(48)
# prefix 1528003601
raw.add(0x15280036, -4) # add 15280036
raw.add(0x01, 1) # add 01
if res == true || ctx.status == matter.SUCCESS # special case, just respond ok
ctx.status = matter.SUCCESS
self.invokeresponse2raw(raw, ctx, nil)
if tasmota.loglevel(3)
tasmota.log(f"MTR: <Replied ({msg.session.local_session_id:6i}) OK exch={msg.exchange_id:i}", 3)
end
elif res != nil
self.invokeresponse2raw(raw, ctx, res)
if !cmd_name cmd_name = "" end
if tasmota.loglevel(3)
tasmota.log(f"MTR: <Replied ({msg.session.local_session_id:6i}) {ctx} {cmd_name}", 3)
end
elif ctx.status != nil
self.invokeresponse2raw(raw, ctx, nil)
if tasmota.loglevel(3)
tasmota.log(f"MTR: <Replied ({msg.session.local_session_id:6i}) Status=0x{ctx.status:02X} exch={msg.exchange_id:i}", 3)
end
else
if tasmota.loglevel(3)
tasmota.log(f"MTR: _Ignore ({msg.session.local_session_id:6i}) exch={msg.exchange_id:i}", 3)
end
# ignore if content is nil and status is undefined
return false
end
# add suffix 1824FF0118
raw.add(0x1824FF01, -4) # add 1824FF01
raw.add(0x18, 1) # add 18
# tasmota.log(f"MTR: raw={raw.tohex()}", 3)
var resp = msg.build_response(0x09 #-Invoke Response-#, true)
var responder = self.device.message_handler
var msg_raw = msg.raw
msg_raw.clear()
resp.encode_frame(raw, msg_raw) # payload in cleartext
resp.encrypt()
responder.send_response_frame(resp)
matter.profiler.log("RESPONSE SENT")
return true
end
#############################################################
# process IM 0x04 Subscribe Response
#
@ -523,6 +1053,7 @@ class Matter_IM
# structure is `ReadRequestMessage` 10.6.2 p.558
# tasmota.log("MTR: IM:write_request processing start", 4)
var ctx = matter.Path()
ctx.msg = msg
if query.write_requests != nil
# prepare the response
@ -626,7 +1157,7 @@ class Matter_IM
tasmota.log(format("MTR: <Sub_Data (%6i) sub=%i", session.local_session_id, sub.subscription_id), 3)
sub.is_keep_alive = false # sending an actual data update
var ret = self._inner_process_read_request(session, fake_read)
var ret = self._inner_process_read_request(session, fake_read, nil #-no msg-#)
ret.suppress_response = false
ret.subscription_id = sub.subscription_id

View File

@ -40,6 +40,7 @@ import matter
#@ solidify:matter.StatusIB,weak
#@ solidify:matter.StatusResponseMessage,weak
#@ solidify:matter.ReadRequestMessage,weak
#@ solidify:matter.ReadRequestMessage_solo,weak
#@ solidify:matter.ReportDataMessage,weak
#@ solidify:matter.SubscribeRequestMessage,weak
#@ solidify:matter.SubscribeResponseMessage,weak
@ -47,6 +48,7 @@ import matter
#@ solidify:matter.WriteResponseMessage,weak
#@ solidify:matter.TimedRequestMessage,weak
#@ solidify:matter.InvokeRequestMessage,weak
#@ solidify:matter.InvokeRequestMessage_solo,weak
#@ solidify:matter.InvokeResponseMessage,weak
#################################################################################
@ -627,6 +629,238 @@ class Matter_ReadRequestMessage : Matter_IM_Message_base
end
matter.ReadRequestMessage = Matter_ReadRequestMessage
#################################################################################
# ReadRequestMessage class optimized for a simple solo argument
#################################################################################
class Matter_Path end # for compilation
class Matter_ReadRequestMessage_solo : Matter_Path
# var endpoint # int
# var cluster # int
# var attribute # int
# var fabric_filtered # bool or nil
def from_raw(raw, idx)
self.reset()
# must start with 15360017
var sz = size(raw)
var val
if raw.get(idx, -4) != 0x15360017 return nil end
idx += 4
while idx < sz
# expect 24 xx yy or 25 xx yyyy (little endian)
var tag = raw.get(idx+1, 1)
var b0 = raw.get(idx, 1)
if b0 == 0x24 # u1
val = raw.get(idx+2, 1)
idx += 3
elif b0 == 0x25 # u2
val = raw.get(idx+2, 2)
idx += 4
else
break
end
if tag == 2
self.endpoint = val
elif tag == 3
self.cluster = val
elif tag == 4
self.attribute = val
else
return nil # unsupported tag
end
end
# expect 18 18
val = raw.get(idx, -2)
if val != 0x1818 return nil end
idx += 2
# fabric_filtered: 2803 or 2903
val = raw.get(idx, -2)
if val == 0x2803
self.fabric_filtered = false
idx += 2
elif val == 0x2903
self.fabric_filtered = true
idx += 2
end
# skip 24FFxx
val = raw.get(idx, -2)
if val == 0x24FF idx += 3 end
# expect 18
if raw.get(idx, 1) != 0x18 return nil end
idx += 1
# sanity check
if self.endpoint == nil ||
self.cluster == nil ||
self.attribute == nil ||
self.fabric_filtered == nil
return nil
end
# all good
return self
end
# Example: read_attr Reachable
# 153600172402012403392404111818290324FF0118
# 15 structure
# 3600 tag 00 array
# 17 list
# 2402 tag 02 u1 "endpoint"
# 01
# 2403 tag 03 u1 "cluster"
# 39 57U
# 2404 tag 04 u1 "attribute"
# 11 17U
# 18
# 18
# 2903 tag 03 booltrue
# 24FF tag FF u1
# 01 1U
# 18
# {0 = [[[2 = 1U, 3 = 57U, 4 = 17U]]], 3 = true, 255 = 1U}
end
matter.ReadRequestMessage_solo = Matter_ReadRequestMessage_solo
# test
# b = bytes('0000153600172402012403392404111818290324FF0118')
# m = matter.ReadRequestMessage_solo()
# mm = m.from_raw(b, 2)
# print(m)
#################################################################################
# InvokeRequestMessage class optimized for a simple solo argument
#################################################################################
class Matter_Path end # for compilation
class Matter_InvokeRequestMessage_solo : Matter_Path
var SuppressResponse
var TimedRequest
var command_fields
# var endpoint # int
# var cluster # int
# var attribute # int
# var fabric_filtered # bool or nil
def reset()
var n = nil
super(self).reset()
self.SuppressResponse = n
self.TimedRequest = n
self.command_fields = n
end
def from_raw(raw, idx)
self.reset()
# must start with 15360017
var sz = size(raw)
var val
if raw.get(idx, 1) != 0x15 return nil end
idx += 1
# check SuppressResponse (optional)
val = raw.get(idx, -2)
if val == 0x2800 || val == 0x2900
self.SuppressResponse = (val == 0x2900)
idx += 2
end
# check TimedRequest (optional)
val = raw.get(idx, -2)
if val == 0x2801 || val == 0x2901
self.SuppressResponse = (val == 0x2901)
idx += 2
end
# start of CommandDataIB
if raw.get(idx, -2) != 0x3602 return nil end
idx += 2
if raw.get(idx, 1) != 0x15 return nil end
idx += 1
if raw.get(idx, -2) != 0x3700 return nil end
idx += 2
#
while idx < sz
# expect 24 xx yy or 25 xx yyyy (little endian)
var tag = raw.get(idx+1, 1)
var b0 = raw.get(idx, 1)
if b0 == 0x24 # u1
val = raw.get(idx+2, 1)
idx += 3
elif b0 == 0x25 # u2
val = raw.get(idx+2, 2)
idx += 4
else
break
end
if tag == 0
self.endpoint = val
elif tag == 1
self.cluster = val
elif tag == 2
self.command = val
else
return nil # unsupported tag
end
end
if raw.get(idx, 1) != 0x18 return nil end
idx += 1
# command_fields
if raw.get(idx, -2) != 0x3501 return nil end
self.command_fields = matter.TLV.parse(raw, idx)
idx = self.command_fields.next_idx # skip structure
# close
if raw.get(idx, -2) != 0x1818 return nil end
idx += 2
if raw.get(idx, -4) != 0x24FF0118 return nil end
# all good
return self
end
# Example: command OnOff
# {0 = false, 1 = false, 2 = [{0 = [[0 = 1U, 1 = 6U, 2 = 0U]], 1 = {}}], 255 = 1U}
# 1528002801360215370024000124010624020018350118181824FF0118
#
# 15
# 2800 0 = false SuppressResponse
# 2801 1 = false TimedRequest
# 3602 2 = list of CommandDataIB
# 15
# 3700 0 = CommandPathIB
# 240001 0 = 1U endpoint
# 240106 1 = 6U cluster
# 240200 2 = 0U command
# 18
# 3501 1 = struct
# 18
# 18
# 18
# 24FF01 FF = 1U
# 18
# 15 structure
# 3600 tag 00 array
# 17 list
# 2402 tag 02 u1 "endpoint"
# 01
# 2403 tag 03 u1 "cluster"
# 39 57U
# 2404 tag 04 u1 "attribute"
# 11 17U
# 18
# 18
# 2903 tag 03 booltrue
# 24FF tag FF u1
# 01 1U
# 18
# {0 = [[[2 = 1U, 3 = 57U, 4 = 17U]]], 3 = true, 255 = 1U}
end
matter.InvokeRequestMessage_solo = Matter_InvokeRequestMessage_solo
#################################################################################
# ReportDataMessage class
#################################################################################

View File

@ -44,11 +44,16 @@ class Matter_IM_Message
# build a response message stub
def init(msg, opcode, reliable)
self.reset(msg, opcode, reliable)
end
def reset(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
self.data = nil
end
# the message is being removed due to expiration
@ -88,10 +93,18 @@ class Matter_IM_Message
def send_im(responder)
# tasmota.log(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
# import debug
var resp = self.resp
resp.encode_frame(self.data.to_TLV().tlv2raw()) # payload in cleartext
var data_tlv = self.data.to_TLV()
# matter.profiler.log(str(data_tlv))
var data_raw = data_tlv.tlv2raw() # payload in cleartext
# matter.profiler.log(data_raw.tohex())
resp.encode_frame(data_raw) # payload in cleartext
resp.encrypt()
tasmota.log(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), 4)
if tasmota.loglevel(4)
tasmota.log(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), 4)
end
# tasmota.log("MTR: Perf/Send = " + str(debug.counters()), 4)
responder.send_response_frame(resp)
self.last_counter = resp.message_counter
self.finish = true # by default we remove the packet after it is sent
@ -295,7 +308,9 @@ class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
var resp = self.resp.build_standalone_ack(false)
resp.encode_frame()
resp.encrypt()
tasmota.log(format("MTR: <Ack (%6i) ack=%i id=%i", resp.session.local_session_id, resp.ack_message_counter, resp.message_counter), 4)
if tasmota.loglevel(4)
tasmota.log(format("MTR: <Ack (%6i) ack=%i id=%i", resp.session.local_session_id, resp.ack_message_counter, resp.message_counter), 4)
end
responder.send_response_frame(resp)
self.last_counter = resp.message_counter
self.finish = true
@ -420,7 +435,9 @@ class Matter_IM_SubscribeResponse : Matter_IM_ReportData
def status_ok_received(msg)
# tasmota.log(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(format("MTR: >Sub_OK (%6i) sub=%i", msg.session.local_session_id, self.sub.subscription_id), 3)
if tasmota.loglevel(3)
tasmota.log(format("MTR: >Sub_OK (%6i) sub=%i", msg.session.local_session_id, self.sub.subscription_id), 3)
end
return super(self).status_ok_received(msg)
end

View File

@ -171,8 +171,12 @@ class Matter_Frame
#
# Header is built from attributes
# `payload` is a bytes() buffer for the app payload
def encode_frame(payload)
var raw = bytes()
#
# you can pass a `raw` bytes() object to be used
def encode_frame(payload, raw)
if raw == nil
raw = bytes(16 + (payload ? size(payload) : 0))
end
# compute flags
if self.flags == nil
self.flags = 0x00
@ -220,7 +224,7 @@ class Matter_Frame
raw .. payload
end
self.debug(raw)
# self.debug(raw)
self.raw = raw
return raw
end
@ -343,11 +347,15 @@ class Matter_Frame
#############################################################
# decrypt with I2S key
# return cleartext or `nil` if failed
#
# frame.raw is decrypted in-place and the MIC is removed
# returns true if successful
def decrypt()
import crypto
var session = self.session
var raw = self.raw
var mic = raw[-16..] # take last 16 bytes as signature
var payload_idx = self.payload_idx
var tag_len = 16
# decrypt the message with `i2r` key
var i2r = session.get_i2r()
@ -357,6 +365,7 @@ class Matter_Frame
# compute privacy key, p.71
tasmota.log("MTR: >>>>>>>>>>>>>>>>>>>> Compute Privacy TODO", 2)
var k = session.get_i2r_privacy()
var mic = raw[-16..] # take last 16 bytes as signature
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]
var m_clear = crypto.AES_CTR(k).decrypt(m, n, 2)
@ -364,11 +373,9 @@ class Matter_Frame
self.raw = self.raw[0..3] + m_clear + m[self.payload_idx .. ]
end
# use AES_CCM
var a = raw[0 .. self.payload_idx - 1]
var p = raw[self.payload_idx .. -17]
# recompute nonce
var n = bytes()
var n = self.message_handler._n_bytes # use cached bytes() object to avoid allocation
n.clear()
n.add(self.flags, 1)
n.add(self.message_counter, 4)
if self.source_node_id
@ -381,28 +388,26 @@ class Matter_Frame
end
# tasmota.log("MTR: ******************************", 4)
# tasmota.log("MTR: raw =" + raw.tohex(), 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: p =" + raw[payload_idx .. -17].tohex(), 4)
# tasmota.log("MTR: a =" + raw[0 .. payload_idx - 1].tohex(), 4)
# tasmota.log("MTR: n =" + n.tohex(), 4)
# tasmota.log("MTR: mic =" + mic.tohex(), 4)
# tasmota.log("MTR: mic =" + raw[-16..].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: ******************************", 4)
# tasmota.log("MTR: cleartext =" + cleartext.tohex(), 4)
# tasmota.log("MTR: tag =" + tag.tohex(), 4)
# tasmota.log("MTR: ******************************", 4)
if tag != mic
var ret = crypto.AES_CCM.decrypt1(i2r, # secret key
n, 0, size(n), # nonce / IV
raw, 0, payload_idx, # aad
raw, payload_idx, size(raw) - payload_idx - tag_len, # encrypted - decrypted in-place
raw, size(raw) - tag_len, tag_len) # MIC
if ret
# succcess
raw.resize(size(raw) - tag_len) # remove MIC
else
tasmota.log("MTR: rejected packet due to invalid MIC", 3)
return nil
end
return cleartext
return ret
end
#############################################################
@ -413,15 +418,15 @@ class Matter_Frame
import crypto
var raw = self.raw
var session = self.session
var payload_idx = self.payload_idx
var tag_len = 16
# encrypt the message with `i2r` key
var r2i = session.get_r2i()
# use AES_CCM
var a = raw[0 .. self.payload_idx - 1]
var p = raw[self.payload_idx .. ]
# recompute nonce
var n = bytes()
var n = self.message_handler._n_bytes # use cached bytes() object to avoid allocation
n.clear()
n.add(self.flags, 1)
n.add(self.message_counter, 4)
if session.is_CASE() && session.get_device_id()
@ -429,30 +434,14 @@ class Matter_Frame
end
n.resize(13) # add zeros
# tasmota.log("MTR: cleartext: " + self.raw.tohex(), 4)
# encrypt
raw.resize(size(raw) + tag_len) # make room for MIC
var ret = crypto.AES_CCM.encrypt1(r2i, # secret key
n, 0, size(n), # nonce / IV
raw, 0, payload_idx, # aad
raw, payload_idx, size(raw) - payload_idx - tag_len, # encrypted - decrypted in-place
raw, size(raw) - tag_len, tag_len) # MIC
# 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: ******************************", 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(), 4)
end
#############################################################

View File

@ -30,12 +30,16 @@ class Matter_MessageHandler
var im # Instance of `matter.IM` handling Interaction Model
var control_message # Instance of `matter.Control_Message` for MCSP
# cache for decryption bytes
var _n_bytes # size 16 minimal, used by frame_buffer for decryption
#############################################################
def init(device)
self.device = device
self.commissioning = matter.Commisioning_Context(self)
self.im = matter.IM(device)
self.control_message = matter.Control_Message(self)
self._n_bytes = bytes(16)
end
#############################################################
@ -47,7 +51,9 @@ class Matter_MessageHandler
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(format("MTR: <Ack (%6i) ack=%i id=%i %s", resp.session.local_session_id, resp.ack_message_counter, resp.message_counter, reliable ? '{reliable}' : ''), 4)
if tasmota.loglevel(4)
tasmota.log(format("MTR: <Ack (%6i) ack=%i id=%i %s", resp.session.local_session_id, resp.ack_message_counter, resp.message_counter, reliable ? '{reliable}' : ''), 4)
end
self.send_response_frame(resp)
end
end
@ -62,7 +68,9 @@ class Matter_MessageHandler
var resp = frame.build_standalone_ack(reliable)
resp.encode_frame()
resp.encrypt()
tasmota.log(format("MTR: <Ack* (%6i) ack=%i id=%i %s", resp.session.local_session_id, resp.ack_message_counter, resp.message_counter, reliable ? '{reliable}' : ''), 4)
if tasmota.loglevel(4)
tasmota.log(format("MTR: <Ack* (%6i) ack=%i id=%i %s", resp.session.local_session_id, resp.ack_message_counter, resp.message_counter, reliable ? '{reliable}' : ''), 4)
end
self.send_response_frame(resp)
end
end
@ -76,11 +84,13 @@ class Matter_MessageHandler
def msg_received(raw, addr, port)
var ret = false
matter.profiler.log("msg_received")
try
# tasmota.log("MTR: MessageHandler::msg_received raw="+raw.tohex(), 4)
var frame = matter.Frame(self, raw, addr, port)
var ok = frame.decode_header()
# matter.profiler.log("msg_received_header_decoded")
if !ok return false end
# do we need decryption?
@ -102,7 +112,9 @@ class Matter_MessageHandler
# check if it's a duplicate
if !session._counter_insecure_rcv.validate(frame.message_counter, false)
tasmota.log(format("MTR: . Duplicate unencrypted message = %i ref = %i", frame.message_counter, session._counter_insecure_rcv.val()), 4)
if tasmota.loglevel(4)
tasmota.log(format("MTR: . Duplicate unencrypted message = %i ref = %i", frame.message_counter, session._counter_insecure_rcv.val()), 4)
end
self.send_simple_ack(frame, false #-not reliable-#)
return false
end
@ -112,9 +124,13 @@ class Matter_MessageHandler
if frame.opcode != 0x10 # don't show `MRP_Standalone_Acknowledgement`
var op_name = matter.get_opcode_name(frame.opcode)
if !op_name op_name = format("0x%02X", frame.opcode) end
tasmota.log(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), 3)
if tasmota.loglevel(3)
tasmota.log(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), 3)
end
else
tasmota.log(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), 4)
if tasmota.loglevel(4)
tasmota.log(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), 4)
end
end
ret = self.commissioning.process_incoming(frame)
# if ret is false, the implicit Ack was not sent
@ -123,14 +139,19 @@ class Matter_MessageHandler
else
#############################################################
# encrypted message
tasmota.log(format("MTR: decode header: local_session_id=%i message_counter=%i", frame.local_session_id, frame.message_counter), 4)
# matter.profiler.log("msg_received_header_encrypted_message_received")
if tasmota.loglevel(4)
tasmota.log(format("MTR: decode header: local_session_id=%i message_counter=%i", frame.local_session_id, frame.message_counter), 4)
end
var session = self.device.sessions.get_session_by_local_session_id(frame.local_session_id)
# matter.profiler.log("msg_received_header_session_retrieved")
if session == nil
tasmota.log("MTR: unknown local_session_id="+str(frame.local_session_id), 3)
# tasmota.log("MTR: frame="+matter.inspect(frame), 3)
return false
end
# matter.profiler.log("msg_received_session_found")
if addr session._ip = addr end
if port session._port = port end
session._message_handler = self
@ -138,22 +159,26 @@ class Matter_MessageHandler
# check if it's a duplicate
if !session.counter_rcv_validate(frame.message_counter, true)
tasmota.log("MTR: . Duplicate encrypted message = " + str(frame.message_counter) + " counter=" + str(session.counter_rcv), 4)
if tasmota.loglevel(3)
tasmota.log("MTR: . Duplicate encrypted message = " + str(frame.message_counter) + " counter=" + str(session.counter_rcv), 3)
end
self.send_encrypted_ack(frame, false #-not reliable-#)
return false
end
var cleartext = frame.decrypt()
if !cleartext return false end
var decrypt_ok = frame.decrypt()
matter.profiler.log("msg_received_header_frame_decrypted")
if !decrypt_ok return false end
# packet is good, put back content in raw
frame.raw = frame.raw[0 .. frame.payload_idx - 1] # remove encrypted payload
frame.raw .. cleartext # add cleartext
# matter.profiler.log("msg_received_payload_undecoded")
# continue decoding
# tasmota.log(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 & 0xFFFF), 4)
# matter.profiler.log("msg_received_payload_decoded")
if tasmota.loglevel(4)
tasmota.log("MTR: > Decrypted message: protocol_id:"+str(frame.protocol_id)+" opcode="+str(frame.opcode)+" exchange_id="+str(frame.exchange_id & 0xFFFF), 4)
end
# tasmota.log(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)
@ -173,7 +198,9 @@ class Matter_MessageHandler
ret = true
elif protocol_id == 0x0001 # PROTOCOL_ID_INTERACTION_MODEL
# dispatch to IM Protocol Messages
matter.profiler.log("process_IM_start")
ret = self.im.process_incoming(frame)
matter.profiler.log("process_IM_end")
# if `ret` is true, we have something to send
if ret
self.im.send_enqueued(self)
@ -220,6 +247,7 @@ class Matter_MessageHandler
# msg.exchange_id: exchange id (int)
# msg.local_session_id: local session (for logging)
def send_response_frame(msg)
matter.profiler.log("send_response_frame")
self.device.msg_send(msg)
end

View File

@ -30,9 +30,24 @@ 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 fabric_filtered # bool or nil
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)
var msg # reference of the original message
# reset the object, allows reuse of the same object
def reset()
var n = nil # it's actually more compact code to load `nil` into a register and assign all members
self.endpoint = n
self.cluster = n
self.attribute = n
self.fabric_filtered = n
self.command = n
self.status = n
self.log = n
self.msg = n
end
def tostring()
try
@ -41,6 +56,7 @@ class Matter_Path
s += (self.cluster != nil ? format("%04X/", self.cluster) : "****/")
s += (self.attribute != nil ? format("%04X", self.attribute) : "")
s += (self.command != nil ? format("%04X", self.command) : "")
if self.fabric_filtered s += "!" end
if self.attribute == nil && self.command == nil s += "****" end
return s
except .. as e, m

View File

@ -63,6 +63,15 @@ class Matter_Plugin
self.parse_configuration(config)
end
# proxy for the same method in IM
def ack_request(ctx)
var msg = ctx.msg
if msg != nil
self.device.message_handler.im.send_ack_now(msg)
end
ctx.msg = nil
end
#############################################################
# parse_configuration
#
@ -142,16 +151,32 @@ class Matter_Plugin
def get_endpoint()
return self.endpoint
end
def get_cluster_list(ep)
def get_cluster_list()
var ret = []
for k: self.clusters.keys()
ret.push(k)
end
return ret
end
def get_attribute_list(ep, cluster)
def contains_cluster(cluster)
return self.clusters.contains(cluster)
end
def get_attribute_list(cluster)
return self.clusters.find(cluster, [])
end
def contains_attribute(cluster, attribute)
var attr_list = self.clusters.find(cluster)
if attr_list != nil
var idx = 0
while idx < size(attr_list)
if attr_list[idx] == attribute
return true
end
idx += 1
end
end
return false
end
#############################################################
# Does it handle this endpoint and this cluster
@ -166,7 +191,10 @@ class Matter_Plugin
#############################################################
#############################################################
# read attribute
def read_attribute(session, ctx)
#
# Arg:
# `tlv_solo` contains an instance of `Matter_TLV_item` to avoid allocating a new object with TLV.create_TLV
def read_attribute(session, ctx, tlv_solo)
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute
@ -195,18 +223,18 @@ class Matter_Plugin
var pl = TLV.Matter_TLV_array()
return pl
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0) #
return tlv_solo.set(TLV.U4, 0) #
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
return TLV.create_TLV(TLV.U4, 1) # "Initial Release"
return tlv_solo.set(TLV.U4, 1) # "Initial Release"
end
# ====================================================================================================
elif cluster == 0x0039 # ========== Bridged Device Basic Information 9.13 p.485 ==========
if attribute == 0x0011 # ---------- Reachable / bool ----------
return TLV.create_TLV(TLV.BOOL, 1) # by default we are reachable
return tlv_solo.set(TLV.BOOL, 1) # by default we are reachable
else
return super(self).read_attribute(session, ctx) # rest is handled by 0x0028
return super(self).read_attribute(session, ctx, tlv_solo) # rest is handled by 0x0028
end
else
return nil

View File

@ -37,7 +37,7 @@ class Matter_Plugin_Aggregator : Matter_Plugin
#############################################################
# read an attribute
#
def read_attribute(session, ctx)
def read_attribute(session, ctx, tlv_solo)
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute
@ -55,11 +55,11 @@ class Matter_Plugin_Aggregator : Matter_Plugin
end
return pl
else
return super(self).read_attribute(session, ctx)
return super(self).read_attribute(session, ctx, tlv_solo)
end
else
return super(self).read_attribute(session, ctx)
return super(self).read_attribute(session, ctx, tlv_solo)
end
# no match found, return that the attribute is unsupported

View File

@ -175,7 +175,7 @@ class Matter_Plugin_Bridge_HTTP : Matter_Plugin_Device
#############################################################
# read attribute
#
def read_attribute(session, ctx)
def read_attribute(session, ctx, tlv_solo)
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute
@ -187,35 +187,35 @@ class Matter_Plugin_Bridge_HTTP : Matter_Plugin_Device
if attribute == 0x0003 # ---------- ProductName / string ----------
var name = self.http_remote.get_info().find("name")
if name
return TLV.create_TLV(TLV.UTF1, name)
return tlv_solo.set(TLV.UTF1, name)
else
return TLV.create_TLV(TLV.NULL, nil)
return tlv_solo.set(TLV.NULL, nil)
end
elif attribute == 0x000A # ---------- SoftwareVersionString / string ----------
var version_full = self.http_remote.get_info().find("version")
if version_full
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)
return tlv_solo.set(TLV.UTF1, version_full)
else
return TLV.create_TLV(TLV.NULL, nil)
return tlv_solo.set(TLV.NULL, nil)
end
elif attribute == 0x000F || attribute == 0x0012 # ---------- SerialNumber || UniqueID / string ----------
var mac = self.http_remote.get_info().find("mac")
if mac
return TLV.create_TLV(TLV.UTF1, mac)
return tlv_solo.set(TLV.UTF1, mac)
else
return TLV.create_TLV(TLV.NULL, nil)
return tlv_solo.set(TLV.NULL, nil)
end
elif attribute == 0x0011 # ---------- Reachable / bool ----------
# self.is_reachable_lazy_sync() # Not needed anymore
return TLV.create_TLV(TLV.BOOL, self.http_remote.reachable) # TODO find a way to do a ping
return tlv_solo.set(TLV.BOOL, self.http_remote.reachable) # TODO find a way to do a ping
else
return super(self).read_attribute(session, ctx)
return super(self).read_attribute(session, ctx, tlv_solo)
end
else
return super(self).read_attribute(session, ctx)
return super(self).read_attribute(session, ctx, tlv_solo)
end
end

View File

@ -30,7 +30,7 @@ class Matter_Plugin_Bridge_Light0 : Matter_Plugin_Bridge_HTTP
static var TYPE = "http_light0" # name of the plug-in in json
static var NAME = "Light 0 On" # display name of the plug-in
static var ARG = "relay" # additional argument name (or empty if none)
static var ARG_HINT = "Enter Power<x> number"
static var ARG_HINT = "Power<x> number"
static var ARG_TYPE = / x -> int(x) # function to convert argument to the right type
# static var UPDATE_TIME = 3000 # update every 3s
# static var UPDATE_CMD = "Status 11" # command to send for updates
@ -83,13 +83,14 @@ class Matter_Plugin_Bridge_Light0 : Matter_Plugin_Bridge_HTTP
var ret = self.call_remote_sync("Power" + str(self.tasmota_relay_index), v ? "1" : "0")
if ret != nil
self.parse_update(ret, 11) # update shadow from return value
# self.tick = self.device.tick # prevent an explicit Status11 for as it is not needed if the subscription update is sent in same tick
end
end
#############################################################
# read an attribute
#
def read_attribute(session, ctx)
def read_attribute(session, ctx, tlv_solo)
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute
@ -98,15 +99,15 @@ class Matter_Plugin_Bridge_Light0 : Matter_Plugin_Bridge_HTTP
if cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
self.update_shadow_lazy()
if attribute == 0x0000 # ---------- OnOff / bool ----------
return TLV.create_TLV(TLV.BOOL, self.shadow_onoff)
return tlv_solo.set(TLV.BOOL, self.shadow_onoff)
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0) # 0 = no Level Control for Lighting
return tlv_solo.set(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
return tlv_solo.set(TLV.U4, 4) # 0 = no Level Control for Lighting
end
else
return super(self).read_attribute(session, ctx)
return super(self).read_attribute(session, ctx, tlv_solo)
end
end
@ -122,7 +123,6 @@ class Matter_Plugin_Bridge_Light0 : Matter_Plugin_Bridge_HTTP
# ====================================================================================================
if cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
self.update_shadow_lazy()
if command == 0x0000 # ---------- Off ----------
self.set_onoff(false)
return true

View File

@ -78,7 +78,7 @@ class Matter_Plugin_Bridge_Light1 : Matter_Plugin_Bridge_Light0
#############################################################
# read an attribute
#
def read_attribute(session, ctx)
def read_attribute(session, ctx, tlv_solo)
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute
@ -88,30 +88,30 @@ class Matter_Plugin_Bridge_Light1 : Matter_Plugin_Bridge_Light0
self.update_shadow_lazy()
if attribute == 0x0000 # ---------- CurrentLevel / u1 ----------
if self.shadow_bri != nil
return TLV.create_TLV(TLV.U1, self.shadow_bri)
return tlv_solo.set(TLV.U1, self.shadow_bri)
else
return TLV.create_TLV(TLV.NULL, nil)
return tlv_solo.set(TLV.NULL, nil)
end
elif attribute == 0x0002 # ---------- MinLevel / u1 ----------
return TLV.create_TLV(TLV.U1, 0)
return tlv_solo.set(TLV.U1, 0)
elif attribute == 0x0003 # ---------- MaxLevel / u1 ----------
return TLV.create_TLV(TLV.U1, 254)
return tlv_solo.set(TLV.U1, 254)
elif attribute == 0x000F # ---------- Options / map8 ----------
return TLV.create_TLV(TLV.U1, 0) #
return tlv_solo.set(TLV.U1, 0) #
elif attribute == 0x0011 # ---------- OnLevel / u1 ----------
if self.shadow_bri != nil
return TLV.create_TLV(TLV.U1, self.shadow_bri)
return tlv_solo.set(TLV.U1, self.shadow_bri)
else
return TLV.create_TLV(TLV.NULL, nil)
return tlv_solo.set(TLV.NULL, nil)
end
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0X01) # OnOff
return tlv_solo.set(TLV.U4, 0X01) # OnOff
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
return TLV.create_TLV(TLV.U4, 5) # "new data model format and notation"
return tlv_solo.set(TLV.U4, 5) # "new data model format and notation"
end
else
return super(self).read_attribute(session, ctx)
return super(self).read_attribute(session, ctx, tlv_solo)
end
end
@ -127,7 +127,6 @@ class Matter_Plugin_Bridge_Light1 : Matter_Plugin_Bridge_Light0
# ====================================================================================================
if cluster == 0x0008 # ========== Level Control 1.6 p.57 ==========
self.update_shadow_lazy()
if command == 0x0000 # ---------- MoveToLevel ----------
var bri_in = val.findsubval(0) # Hue 0..254
self.set_bri(bri_in)

View File

@ -95,7 +95,7 @@ class Matter_Plugin_Bridge_Light2 : Matter_Plugin_Bridge_Light1
#############################################################
# read an attribute
#
def read_attribute(session, ctx)
def read_attribute(session, ctx, tlv_solo)
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute
@ -105,29 +105,29 @@ class Matter_Plugin_Bridge_Light2 : Matter_Plugin_Bridge_Light1
self.update_shadow_lazy()
if attribute == 0x0007 # ---------- ColorTemperatureMireds / u2 ----------
if self.shadow_ct != nil
return TLV.create_TLV(TLV.U1, self.shadow_ct)
return tlv_solo.set(TLV.U1, self.shadow_ct)
else
return TLV.create_TLV(TLV.NULL, nil)
return tlv_solo.set(TLV.NULL, nil)
end
elif attribute == 0x0008 # ---------- ColorMode / u1 ----------
return TLV.create_TLV(TLV.U1, 2)# 2 = ColorTemperatureMireds
return tlv_solo.set(TLV.U1, 2)# 2 = ColorTemperatureMireds
elif attribute == 0x000F # ---------- Options / u1 ----------
return TLV.create_TLV(TLV.U1, 0)
return tlv_solo.set(TLV.U1, 0)
elif attribute == 0x400B # ---------- ColorTempPhysicalMinMireds / u2 ----------
return TLV.create_TLV(TLV.U1, self.ct_min)
return tlv_solo.set(TLV.U1, self.ct_min)
elif attribute == 0x400C # ---------- ColorTempPhysicalMaxMireds / u2 ----------
return TLV.create_TLV(TLV.U1, self.ct_max)
return tlv_solo.set(TLV.U1, self.ct_max)
elif attribute == 0x400A # ---------- ColorCapabilities / map32 ----------
return TLV.create_TLV(TLV.U4, 0x10) # CT
return tlv_solo.set(TLV.U4, 0x10) # CT
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0x10) # CT
return tlv_solo.set(TLV.U4, 0x10) # CT
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
return TLV.create_TLV(TLV.U4, 5) # "new data model format and notation, FeatureMap support"
return tlv_solo.set(TLV.U4, 5) # "new data model format and notation, FeatureMap support"
end
else
return super(self).read_attribute(session, ctx)
return super(self).read_attribute(session, ctx, tlv_solo)
end
end
@ -143,7 +143,6 @@ class Matter_Plugin_Bridge_Light2 : Matter_Plugin_Bridge_Light1
# ====================================================================================================
if cluster == 0x0300 # ========== Color Control 3.2 p.111 ==========
self.update_shadow_lazy()
if command == 0x000A # ---------- MoveToColorTemperature ----------
var ct_in = val.findsubval(0) # CT
if ct_in < self.ct_min ct_in = self.ct_min end

View File

@ -95,7 +95,7 @@ class Matter_Plugin_Bridge_Light3 : Matter_Plugin_Bridge_Light1
#############################################################
# read an attribute
#
def read_attribute(session, ctx)
def read_attribute(session, ctx, tlv_solo)
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute
@ -105,39 +105,39 @@ class Matter_Plugin_Bridge_Light3 : Matter_Plugin_Bridge_Light1
self.update_shadow_lazy()
if attribute == 0x0000 # ---------- CurrentHue / u1 ----------
if self.shadow_hue != nil
return TLV.create_TLV(TLV.U1, self.shadow_hue)
return tlv_solo.set(TLV.U1, self.shadow_hue)
else
return TLV.create_TLV(TLV.NULL, nil)
return tlv_solo.set(TLV.NULL, nil)
end
elif attribute == 0x0001 # ---------- CurrentSaturation / u2 ----------
if self.shadow_sat != nil
return TLV.create_TLV(TLV.U1, self.shadow_sat)
return tlv_solo.set(TLV.U1, self.shadow_sat)
else
return TLV.create_TLV(TLV.NULL, nil)
return tlv_solo.set(TLV.NULL, nil)
end
elif attribute == 0x0007 # ---------- ColorTemperatureMireds / u2 ----------
return TLV.create_TLV(TLV.U1, 0)
return tlv_solo.set(TLV.U1, 0)
elif attribute == 0x0008 # ---------- ColorMode / u1 ----------
return TLV.create_TLV(TLV.U1, 0)# 0 = CurrentHue and CurrentSaturation
return tlv_solo.set(TLV.U1, 0)# 0 = CurrentHue and CurrentSaturation
elif attribute == 0x000F # ---------- Options / u1 ----------
return TLV.create_TLV(TLV.U1, 0)
return tlv_solo.set(TLV.U1, 0)
elif attribute == 0x4001 # ---------- EnhancedColorMode / u1 ----------
return TLV.create_TLV(TLV.U1, 0)
return tlv_solo.set(TLV.U1, 0)
elif attribute == 0x400A # ---------- ColorCapabilities / map32 ----------
return TLV.create_TLV(TLV.U4, 0x01) # HS
return tlv_solo.set(TLV.U4, 0x01) # HS
# Defined Primaries Information Attribute Set
elif attribute == 0x0010 # ---------- NumberOfPrimaries / u1 ----------
return TLV.create_TLV(TLV.U1, 0)
return tlv_solo.set(TLV.U1, 0)
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0x01) # HS
return tlv_solo.set(TLV.U4, 0x01) # HS
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
return TLV.create_TLV(TLV.U4, 5) # "new data model format and notation, FeatureMap support"
return tlv_solo.set(TLV.U4, 5) # "new data model format and notation, FeatureMap support"
end
else
return super(self).read_attribute(session, ctx)
return super(self).read_attribute(session, ctx, tlv_solo)
end
end
@ -153,7 +153,6 @@ class Matter_Plugin_Bridge_Light3 : Matter_Plugin_Bridge_Light1
# ====================================================================================================
if cluster == 0x0300 # ========== Color Control 3.2 p.111 ==========
self.update_shadow_lazy()
if command == 0x0000 # ---------- MoveToHue ----------
var hue_in = val.findsubval(0) # Hue 0..254
self.set_hue(hue_in)

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