From a242811ec8697f00726f28cb1b8128c6e8f372bf Mon Sep 17 00:00:00 2001 From: s-hadinger <49731213+s-hadinger@users.noreply.github.com> Date: Tue, 29 Sep 2026 15:50:13 +0200 Subject: [PATCH] Matter Soil Sensor device type with Soil Measurement cluster (Matter 1.6.0) (#25088) --- CHANGELOG.md | 3 +- .../MATTER_1.4.1_TO_1.6.0_CHANGELOG.md | 9 +- .../MATTER_1.6.0_DETAILED_GAP_ANALYSIS.md | 2 +- .../berry_matter/src/be_matter_module.c | 3 + .../embedded/Matter_Plugin_3_Sensor_Soil.be | 160 ++++++++++++++++++ .../Matter_Plugin_8_Bridge_Sensor_Soil.be | 40 +++++ .../Matter_Plugin_9_Virt_Sensor_Soil.be | 46 +++++ .../berry_matter/src/embedded/Matter_UI.be | 8 +- .../src/embedded/Matter_z_Autoconf.be | 11 ++ 9 files changed, 275 insertions(+), 7 deletions(-) create mode 100644 lib/libesp32/berry_matter/src/embedded/Matter_Plugin_3_Sensor_Soil.be create mode 100644 lib/libesp32/berry_matter/src/embedded/Matter_Plugin_8_Bridge_Sensor_Soil.be create mode 100644 lib/libesp32/berry_matter/src/embedded/Matter_Plugin_9_Virt_Sensor_Soil.be diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e79daf20..0540d5028 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ All notable changes to this project will be documented in this file. ## [15.6.0.2] ### Added - DALI-2 push button bindings to control gear targets with command `DaliBind` (toggle, on, off, up, down) (#25073) -- Matter add Electrical Power Measurement cluster to On/Off Plug-in Unit (#24922) +- Matter Electrical Power Measurement cluster to On/Off Plug-in Unit (#24922) +- Matter Soil Sensor device type with Soil Measurement cluster (Matter 1.6.0) ### Breaking Changed diff --git a/lib/libesp32/berry_matter/MATTER_1.4.1_TO_1.6.0_CHANGELOG.md b/lib/libesp32/berry_matter/MATTER_1.4.1_TO_1.6.0_CHANGELOG.md index eced43498..e291b401e 100644 --- a/lib/libesp32/berry_matter/MATTER_1.4.1_TO_1.6.0_CHANGELOG.md +++ b/lib/libesp32/berry_matter/MATTER_1.4.1_TO_1.6.0_CHANGELOG.md @@ -225,7 +225,12 @@ The following changes are part of Matter 1.6.1 (released September 2026, PR #735 - Commodity Tariff cluster (0x0700) read implementation - Device Energy Management cluster (0x0098) for power measurement + demand response hints - **Timeframe:** 3-4 weeks -- **Integration:** Extend existing power sensor plugins; enable smart scheduling based on tariff + +### Phase 2b: Tariff & Price (deferred) +- Commodity Price (0x0095, Rev 4) and Commodity Tariff (0x0700, Rev 1) +- **Why deferred:** per the Device Library, both clusters are only hosted by the Electrical Energy Tariff device type (0x0513), which must be a child of a Meter Reference Point (0x0512) endpoint with Identify, and must carry Descriptor TagList semantic tags (Commodity Tariff Commodity/Chronology namespaces, plus Grid/Import/AC in the basic topology). The Root Node also needs the TimeSyncCond condition. Commodity Tariff alone carries 14+ attributes with deeply nested structs. +- **Prerequisites:** Namespace Specification 1.6 (to confirm namespace/tag IDs), a parent/child virtual composition (PartsList override, as in `Matter_Plugin_9_Virt_HVAC.be`), Descriptor TagList support (FeatureMap bit 0 on 0x001D) +- **Data source:** none native in Tasmota; values would be pushed via `MtrUpdate` (rules/scripts/MQTT) ### Phase 3: Closures Unified Architecture (v1.6.0, medium-value features) - Closure device type (0x0230) with Closure Control (0x0104) and Closure Dimension (0x0105) clusters @@ -240,6 +245,8 @@ The following changes are part of Matter 1.6.1 (released September 2026, PR #735 - **Timeframe:** 1 week - **Integration:** Extends existing humidity/temperature sensor plugins +- Doorbell device type (0x0148) — optional, can defer to maintenance release + ### Phase 5: Validation & Compliance Testing (v1.6.0) - Multi-controller interoperability (Apple Home, Google Home, Alexa, SmartThings, Home Assistant) - DataModelRevision = 20 verification diff --git a/lib/libesp32/berry_matter/MATTER_1.6.0_DETAILED_GAP_ANALYSIS.md b/lib/libesp32/berry_matter/MATTER_1.6.0_DETAILED_GAP_ANALYSIS.md index d7c4df5a9..e313af6ac 100644 --- a/lib/libesp32/berry_matter/MATTER_1.6.0_DETAILED_GAP_ANALYSIS.md +++ b/lib/libesp32/berry_matter/MATTER_1.6.0_DETAILED_GAP_ANALYSIS.md @@ -342,7 +342,7 @@ The Tasmota Matter implementation is currently aligned with **Matter 1.4.1** (Da | **DataModelRevision** | 18 | 20 | ⚠️ Not updated | <1 day | | **Closures clusters** | Window Covering (legacy) | Closure Control/Dimension (new) | ❌ Missing | 2 weeks | | **Energy Management** | 0x0090/0x0091 only | Add Commodity Price/Tariff, Device EM, EVSE | ❌ Missing | 3-4 weeks | -| **Soil Measurement** | None | Soil Sensor (0x0045) + cluster | ❌ Missing | 1 week | +| **Soil Measurement** | None | Soil Sensor (0x0045) + cluster | ✅ Done | — | | **Doorbell** | None | 0x0148/0x0141/0x0143 | ❌ Missing | 1 week | | **Access Control** | Partial | Complete read + rev updates | ⚠️ Partial | 3-5 days | | **Group Key Management** | Rev 2 | Rev 4 | ⚠️ Outdated | 1 week | diff --git a/lib/libesp32/berry_matter/src/be_matter_module.c b/lib/libesp32/berry_matter/src/be_matter_module.c index 29200308a..d9333fb22 100644 --- a/lib/libesp32/berry_matter/src/be_matter_module.c +++ b/lib/libesp32/berry_matter/src/be_matter_module.c @@ -240,6 +240,8 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because #include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Illuminance.h" #include "solidify/solidified_Matter_Plugin_3_Sensor_Humidity.h" #include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Humidity.h" +#include "solidify/solidified_Matter_Plugin_3_Sensor_Soil.h" +#include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Soil.h" #include "solidify/solidified_Matter_Plugin_2_Sensor_Boolean.h" #include "solidify/solidified_Matter_Plugin_3_Sensor_Occupancy.h" #include "solidify/solidified_Matter_Plugin_3_Sensor_OnOff.h" @@ -266,6 +268,7 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because #include "solidify/solidified_Matter_Plugin_8_Bridge_Sensor_Temp.h" #include "solidify/solidified_Matter_Plugin_8_Bridge_Sensor_Illuminance.h" #include "solidify/solidified_Matter_Plugin_8_Bridge_Sensor_Humidity.h" +#include "solidify/solidified_Matter_Plugin_8_Bridge_Sensor_Soil.h" #include "solidify/solidified_Matter_Plugin_8_Bridge_Sensor_Occupancy.h" #include "solidify/solidified_Matter_Plugin_8_Bridge_Sensor_Contact.h" #include "solidify/solidified_Matter_Plugin_8_Bridge_Sensor_Flow.h" diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_3_Sensor_Soil.be b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_3_Sensor_Soil.be new file mode 100644 index 000000000..d5a9013f9 --- /dev/null +++ b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_3_Sensor_Soil.be @@ -0,0 +1,160 @@ +# +# Matter_Plugin_Sensor_Soil.be - implements the behavior for a Soil Sensor +# +# Copyright (C) 2026 Ludovic BOUÉ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +################################################################################# +# Matter 1.6.0 Device Specification +################################################################################# +# Device Type: Soil Sensor (0x0045) +# Device Type Revision: 1 (Matter 1.5+) +# Class: Simple | Scope: Endpoint +# +# CLUSTERS (Server): +# - 0x0430: Soil Measurement (M) +# - 0x0003: Identify (M) +# - 0x0402: Temperature Measurement (O) - not exposed by this plugin +# +# NOTES: +# - Reads the `Moisture` value (percent) reported by Tasmota soil sensors, +# e.g. Adafruit Seesaw capacitive soil sensor (xsns_81), or BLE plant sensors +# - Soil temperature (optional on this device type) can be exposed with a +# separate `temperature` endpoint using the same sensor filter prefix +################################################################################# + +################################################################################# +# Matter 1.6.0 Soil Measurement Cluster (0x0430) +################################################################################# +# Cluster Revision: 1 (Matter 1.5+) +# Role: Application | Scope: Endpoint | PICS: SOIL +# +# ATTRIBUTES: +# ID | Name | Type | Quality | Conf +# -------|--------------------------------|-------------------------|---------|----- +# 0x0000 | SoilMoistureMeasurementLimits | MeasurementAccuracyStruct | F | M +# 0x0001 | SoilMoistureMeasuredValue | percent (uint8) | X | M +# +# SoilMoistureMeasurementLimits constraints (spec 2.15.4.1): +# - MeasurementType = SoilMoisture (17) +# - MinMeasuredValue 0..99, MaxMeasuredValue (Min+1)..100 +# - a single AccuracyRanges entry covering [Min, Max], with only PercentMax +# set; PercentMax (percent100ths) must be <= 10 (checked by TC_SOIL_2_1) +# +# SoilMoistureMeasuredValue: water content of the soil in percent, +# null until the first reading. +################################################################################# + +import matter + +# Matter plug-in for core behavior + +#@ solidify:Matter_Plugin_Sensor_Soil,weak + +class Matter_Plugin_Sensor_Soil : Matter_Plugin_Sensor + static var TYPE = "soil" # name of the plug-in in json + static var DISPLAY_NAME = "Soil Moisture" # display name of the plug-in + static var JSON_NAME = "Moisture" # Name of the sensor attribute in JSON payloads + static var UPDATE_COMMANDS = matter.UC_LIST(_class, "Moisture") + static var CLUSTERS = matter.consolidate_clusters(_class, { + 0x0430: [0,1], # Soil Measurement 2.15 - no writable + }) + static var TYPES = { 0x0045: 1 } # Soil Sensor, rev 1 + + # static var MT_SOIL_MOISTURE = 17 # MeasurementTypeEnum SoilMoisture + # static var PERCENT_MAX = 10 # percent100ths, upper bound checked by TC_SOIL_2_1 (and used by the CHIP reference server) + + ############################################################# + # Pre-process value + # + # Tasmota reports moisture in percent, Matter uses a uint8 percent + # Clamp in the float domain before converting, since `int()` of NaN + # or out-of-range floats is undefined, then round to nearest + def pre_value(val) + if val == nil || val != val return nil end # nil or NaN + if val < 0 val = 0 end + if val > 100 val = 100 end + return int(val + 0.5) + end + + ############################################################# + # Called when the value changed compared to shadow value + def value_changed() + self.attribute_updated(0x0430, 0x0001) + end + + ############################################################# + # update_virtual + # + # Clamp values received from `MtrUpdate {"Name":"...", "Moisture":42}` + def update_virtual(payload) + var val = payload.find(self.JSON_NAME) + if val != nil + payload[self.JSON_NAME] = self.pre_value(real(val)) + end + super(self).update_virtual(payload) + end + + ############################################################# + # build SoilMoistureMeasurementLimits + def _build_limits() + var TLV = matter.TLV + var lim = TLV.Matter_TLV_struct() + lim.add_TLV(0, 0x06 #-TLV.U4-#, 17 #-MT_SOIL_MOISTURE-#) # MeasurementType + lim.add_TLV(1, 0x08 #-TLV.BOOL-#, true) # Measured + lim.add_TLV(2, 0x03 #-TLV.I8-#, 0) # MinMeasuredValue + lim.add_TLV(3, 0x03 #-TLV.I8-#, 100) # MaxMeasuredValue + var ranges = lim.add_array(4) # AccuracyRanges + var r = ranges.add_struct() + r.add_TLV(0, 0x03 #-TLV.I8-#, 0) # RangeMin + r.add_TLV(1, 0x03 #-TLV.I8-#, 100) # RangeMax + r.add_TLV(2, 0x06 #-TLV.U4-#, 10 #-PERCENT_MAX-#) # PercentMax + return lim + end + + ############################################################# + # read an attribute + # + def read_attribute(session, ctx, tlv_solo) + var cluster = ctx.cluster + var attribute = ctx.attribute + + # ==================================================================================================== + if cluster == 0x0430 # ========== Soil Measurement 2.15 ========== + if attribute == 0x0000 # ---------- SoilMoistureMeasurementLimits / MeasurementAccuracyStruct ---------- + return self._build_limits() + elif attribute == 0x0001 # ---------- SoilMoistureMeasuredValue / percent ---------- + return tlv_solo.set_or_nil(0x06 #-TLV.U4-#, self.shadow_value != nil ? int(self.shadow_value) : nil) + end + + end + return super(self).read_attribute(session, ctx, tlv_solo) + end + + ############################################################# + # web_values + # + # Show values of the remote device as HTML + def web_values() + import webserver + self.web_values_prefix() # display '| ' and name if present + webserver.content_send(format("🌱 %i%%", self.shadow_value != nil ? int(self.shadow_value) : nil)) + end + ############################################################# + ############################################################# + +end +matter.Plugin_Sensor_Soil = Matter_Plugin_Sensor_Soil diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_8_Bridge_Sensor_Soil.be b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_8_Bridge_Sensor_Soil.be new file mode 100644 index 000000000..d1e52becc --- /dev/null +++ b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_8_Bridge_Sensor_Soil.be @@ -0,0 +1,40 @@ +# +# Matter_Plugin_Bridge_Sensor_Soil.be - implements base class for a Soil Sensor via HTTP to Tasmota +# +# Copyright (C) 2026 Ludovic BOUÉ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +################################################################################# +# Matter 1.6.0 Bridge Variant - Soil Sensor via HTTP +################################################################################# +# INHERITS FROM: Matter_Plugin_Sensor_Soil (Matter_Plugin_3_Sensor_Soil.be) +# VARIANT TYPE: Bridge (Remote HTTP Device) +# DEVICE TYPE: Soil Sensor (0x0045) - See base class +# CLUSTERS: Soil Measurement (0x0430) - See Matter_Plugin_3_Sensor_Soil.be +# TYPE: "http_soil" | BRIDGE: true +################################################################################# + +import matter + +# Matter plug-in for core behavior + +#@ solidify:Matter_Plugin_Bridge_Sensor_Soil,weak + +class Matter_Plugin_Bridge_Sensor_Soil : Matter_Plugin_Sensor_Soil + static var BRIDGE = true # flag as bridged device + static var TYPE = "http_soil" # name of the plug-in in json +end +matter.Plugin_Bridge_Sensor_Soil = Matter_Plugin_Bridge_Sensor_Soil diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_9_Virt_Sensor_Soil.be b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_9_Virt_Sensor_Soil.be new file mode 100644 index 000000000..cb61d32b0 --- /dev/null +++ b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_9_Virt_Sensor_Soil.be @@ -0,0 +1,46 @@ +# +# Matter_Plugin_9_Virt_Sensor_Soil.be - implements Virtual Soil Sensor +# +# Copyright (C) 2026 Ludovic BOUÉ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +################################################################################# +# Matter 1.6.0 Virtual Variant - Soil Sensor +################################################################################# +# INHERITS FROM: Matter_Plugin_Sensor_Soil (Matter_Plugin_3_Sensor_Soil.be) +# VARIANT TYPE: Virtual +# DEVICE TYPE: Soil Sensor (0x0045) - See base class for specification +# CLUSTERS: Soil Measurement (0x0430) - See Matter_Plugin_3_Sensor_Soil.be +# TYPE: "v_soil" | VIRTUAL: true +# +# TESTING: +# MtrUpdate {"Name":"Garden", "Moisture":42} +################################################################################# + +import matter + +# Matter plug-in for core behavior + +#@ solidify:Matter_Plugin_Virt_Sensor_Soil,weak + +class Matter_Plugin_Virt_Sensor_Soil : Matter_Plugin_Sensor_Soil + static var TYPE = "v_soil" # name of the plug-in in json + static var DISPLAY_NAME = "v.Soil Moisture" # display name of the plug-in + + static var SCHEMA = nil # no parameter + static var VIRTUAL = true # virtual device +end +matter.Plugin_Virt_Sensor_Soil = Matter_Plugin_Virt_Sensor_Soil diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_UI.be b/lib/libesp32/berry_matter/src/embedded/Matter_UI.be index 49f0db1e6..24514cbb4 100644 --- a/lib/libesp32/berry_matter/src/embedded/Matter_UI.be +++ b/lib/libesp32/berry_matter/src/embedded/Matter_UI.be @@ -190,20 +190,20 @@ class Matter_UI "|relay|relay_power|light0|light1|light2|light3|shutter|shutter+tilt" "|gensw_btn" "|temperature|pressure|illuminance|humidity|occupancy|onoff|contact|flow|rain|waterleak" - "|airquality" + "|airquality|soil" static var _CLASSES_TYPES_VIRTUAL = "-virtual|v_relay|v_relay_power|v_light0|v_light1|v_light2|v_light3" "|v_fan|v_hvac|v_hvac_option" "|v_temp|v_pressure|v_illuminance|v_humidity|v_occupancy|v_contact|v_flow|v_rain|v_waterleak" - "|v_airquality" + "|v_airquality|v_soil" static var _CLASSES_TYPES2= "|http_relay|http_relay_power|http_light0|http_light1|http_light2|http_light3" "|http_temperature|http_pressure|http_illuminance|http_humidity" "|http_occupancy|http_contact|http_flow|http_rain|http_waterleak" - "|http_airquality" + "|http_airquality|http_soil" static var _CLASSES_TYPES3= "|mqtt_relay|mqtt_light0|mqtt_light1|mqtt_light2|mqtt_light3" "|mqtt_temperature|mqtt_pressure|mqtt_illuminance|mqtt_humidity" "|mqtt_occupancy|mqtt_contact|mqtt_flow|mqtt_rain|mqtt_waterleak" - "|mqtt_airquality" + "|mqtt_airquality|mqtt_soil" var device var matter_enabled diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_z_Autoconf.be b/lib/libesp32/berry_matter/src/embedded/Matter_z_Autoconf.be index cc1d06394..d673154e3 100644 --- a/lib/libesp32/berry_matter/src/embedded/Matter_z_Autoconf.be +++ b/lib/libesp32/berry_matter/src/embedded/Matter_z_Autoconf.be @@ -233,6 +233,17 @@ class Matter_Autoconf end end + # soil moisture sensors + # Skip CHIRP sensors (xsns_48): their `Moisture` is raw capacitance, not percent + import string + for k1: k2l(sensors) + var sensor_2 = sensors[k1] + if isinstance(sensor_2, map) && sensor_2.contains("Moisture") && string.find(k1, "CHIRP") != 0 + var soil_rule = k1 + "#Moisture" + ret.push({'type':'soil','filter':soil_rule}) + end + end + return ret end