mirror of https://github.com/arendst/Tasmota.git
Merge branch 'development' into prerelease-13.1
This commit is contained in:
commit
5a7a82e975
|
@ -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**_
|
||||
|
|
|
@ -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 | - | - | - | - |
|
||||
|
|
89
CHANGELOG.md
89
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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 |
|
||||
| |
|
||||
|
|
|
@ -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
|
||||
|
|
129
RELEASENOTES.md
129
RELEASENOTES.md
|
@ -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)
|
||||
|
|
50
TEMPLATES.md
50
TEMPLATES.md
|
@ -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}
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]**
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -34,4 +34,5 @@ build_flags =
|
|||
${env.build_flags}
|
||||
-Werror
|
||||
-Wno-error=switch
|
||||
-Wno-error=switch-unreachable
|
||||
-D_IR_ENABLE_DEFAULT_=false
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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.
|
|
@ -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).
|
|
@ -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 |
|
@ -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)
|
||||
#######################################
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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_ */
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)")
|
|
@ -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}')
|
||||
|
|
|
@ -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
|
|
@ -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] '}';
|
||||
|
|
|
@ -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`
|
|
@ -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)|(\\)))"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
@ -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"
|
|
@ -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": {
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
#################################################################################
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
#############################################################
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue