Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.

### Changed
- LVGL library from v9.4.0 to v9.5.0
- Matter improved parameters handling

### Fixed

Expand Down
96 changes: 96 additions & 0 deletions lib/libesp32/berry_matter/MATTER_ICD_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Matter ICD (Intermittently Connected Device) Implementation

## Overview

The ICD Management Cluster (0x0046) is always enabled in Tasmota's Matter implementation per Matter 1.4.1 specification. Since Tasmota devices are mains-powered WiFi devices that are always connected, the cluster reports the device as a basic SIT (Short Idle Time) device without optional features.

## What is ICD?

ICD (Intermittently Connected Device) is a Matter feature that allows devices to communicate their availability patterns to controllers. Per Matter 1.4.1 spec section 9.17:

- **SIT (Short Idle Time)**: Devices with idle periods < 15 seconds - used for always-on devices
- **LIT (Long Idle Time)**: Devices with idle periods >= 15 seconds - used for battery-powered sleepy devices

Tasmota devices use **SIT mode** since they are always connected via WiFi.

## Implementation Details

### ICD Management Cluster (0x0046)

The ICD Management Cluster is always present on the Root endpoint (endpoint 0).

Per Matter 1.4.1 spec section 9.17.4, the cluster has optional features:
- **CIP** (Check-In Protocol Support): For LIT devices to notify clients
- **UAT** (User Active Mode Trigger): For user-triggered wake
- **LITS** (Long Idle Time Support): For LIT operation mode
- **DSLS** (Dynamic SIT/LIT Support): For switching between modes

Tasmota implements **none of these optional features** (FeatureMap = 0x00) since it's an always-on device.

### Supported Attributes (per spec section 9.17.6)

| Attribute | ID | Type | Value | Conformance | Description |
|-----------|-----|------|-------|-------------|-------------|
| IdleModeDuration | 0x0000 | uint32 | 1 | Mandatory | 1 second (minimum per spec) |
| ActiveModeDuration | 0x0001 | uint32 | 300 | Mandatory | 300ms (spec default) |
| ActiveModeThreshold | 0x0002 | uint16 | 300 | Mandatory | 300ms (spec default) |

### Attributes NOT Implemented (require optional features)

| Attribute | ID | Conformance | Reason |
|-----------|-----|-------------|--------|
| RegisteredClients | 0x0003 | CIP | Requires Check-In Protocol |
| ICDCounter | 0x0004 | CIP | Requires Check-In Protocol |
| ClientsSupportedPerFabric | 0x0005 | CIP | Requires Check-In Protocol |
| UserActiveModeTriggerHint | 0x0006 | UAT | Requires User Active Mode Trigger |
| UserActiveModeTriggerInstruction | 0x0007 | UAT | Requires User Active Mode Trigger |
| OperatingMode | 0x0008 | LITS | Requires Long Idle Time Support |
| MaximumCheckInBackoff | 0x0009 | CIP | Requires Check-In Protocol |

### Cluster Configuration

- **Cluster Revision**: 3 (Matter 1.4.1)
- **Feature Map**: 0x00 (no optional features)

### mDNS Advertisement

Per Matter 1.4.1 spec section 4.3.4, the following TXT records are included:

| Key | Value | Description |
|-----|-------|-------------|
| SII | 500 | SESSION_IDLE_INTERVAL in ms (spec default) |
| SAI | 300 | SESSION_ACTIVE_INTERVAL in ms (spec default) |

**Note**: The `ICD` key is NOT advertised because per spec: "The key SHALL NOT be provided by a Node that does not support the ICD Long Idle Time operating mode."

### Files Modified

1. **Matter_Plugin_1_Root.be**
- ICD Management Cluster (0x0046) with attributes [0,1,2]
- Fixed attribute values per spec defaults

2. **Matter_Plugin_0.be**
- Cluster revision 3 in CLUSTER_REVISIONS
- Feature map 0x00 in FEATURE_MAPS

3. **Matter_z_Commissioning.be**
- SII/SAI values in mDNS PASE announcement (spec defaults)

## Why This Implementation?

1. **Spec Compliance**: Only mandatory attributes are implemented; optional features (CIP, UAT, LITS) are not needed for always-on devices

2. **Simplicity**: No configuration needed since Tasmota devices are always-on WiFi devices

3. **Controller Compatibility**: Controllers can read the ICD cluster to understand the device is always reachable

## Spec References

- Matter 1.4.1 Core Specification
- Section 9.17: ICD Management Cluster
- Section 4.3.4: Common TXT Key/Value Pairs
- Section 4.13.1: Session Parameters

---
*Implementation Date: January 2026*
*Based on Matter 1.4.1 Core Specification*
33 changes: 0 additions & 33 deletions lib/libesp32/berry_matter/src/be_matter_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,36 +26,6 @@
#include "be_mapping.h"
#include <stdio.h>

// Matter logo
static const uint8_t MATTER_LOGO[] =
"<svg style='vertical-align:middle;' width='24' height='24' xmlns='http://www.w3.org/2000/svg' viewBox='100 100 240 240'>"
"<defs><style>.cls-1{fill:none}.cls-2{fill:#FFFFFF;}</style></defs><rect class='cls-1' "
"width='420' height='420'/><path class='cls-2' d='"
"M167,156.88a71,71,0,0,0,32.1,14.73v-62.8l12.79-7.38,12.78,7.38v62.8a71.09,71.09,0,0,0,32.11-14.73"
"L280,170.31a96.92,96.92,0,0,1-136.33,0Zm28.22,160.37A96.92,96.92,0,0,0,127,199.19v26.87a71.06,"
"71.06,0,0,1,28.82,20.43l-54.39,31.4v14.77L114.22,300l54.38-31.4a71,71,0,0,1,3.29,35.17Zm101.5-"
"118.06a96.93,96.93,0,0,0-68.16,118.06l23.27-13.44a71.1,71.1,0,0,1,3.29-35.17L309.46,300l12.78-"
"7.38V277.89l-54.39-31.4a71.13,71.13,0,0,1,28.82-20.43Z'/></svg>";

// Matter stylesheet
static const uint8_t MATTER_STYLESHEET[] =
"<style>"
".bxm{height:14px;width:14px;display:inline-block;border:1px solid currentColor;background-color:var(--cl,#fff)}"
".ztdm td:not(:first-child){width:20px;font-size:70%}"
".ztdm td:last-child{width:45px}"
".ztdm .bt{margin-right:10px;}"
".htrm{line-height:20px}"
"</style>";

static const uint8_t MATTER_ADD_ENDPOINT_HINTS_JS[] =
"<script type='text/javascript'>"
"function otm(arg_name,val){"
"var s=eb(arg_name);"
"s.placeholder=(val in hm)?hl[hm[val]]:\"\";"
"s.title=s.placeholder;"
"};"
"</script>";

extern uint32_t matter_convert_seconds_to_dhm(uint32_t seconds, char *unit, uint32_t *color, bbool days);

char* matter_seconds_to_dhm(int32_t seconds) {
Expand Down Expand Up @@ -310,9 +280,6 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
/* @const_object_info_begin

module matter (scope: global, strings: weak) {
_LOGO, comptr(MATTER_LOGO)
_STYLESHEET, comptr(MATTER_STYLESHEET)
_ADD_ENDPOINT_JS, comptr(MATTER_ADD_ENDPOINT_HINTS_JS)
MATTER_OPTION, int(151) // SetOption151 enables Matter
AGGREGATOR_ENDPOINT, int(0x0001) // some controllers require aggregator to be endpoint 1
START_ENDPOINT, int(0x0002) // endpoint where to start devices
Expand Down
29 changes: 3 additions & 26 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_0.be
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ class Matter_Plugin
# Global type system for plugins
static var TYPE = "" # name of the plug-in in json
static var DISPLAY_NAME = "" # display name of the plug-in
static var ARG = "" # additional argument name (or empty if none)
static var ARG_TYPE = / x -> str(x) # function to convert argument to the right type
static var ARG_HINT = "_Not used_" # Hint for entering the Argument (inside 'placeholder')

# Note: SCHEMA is only defined in subclasses that have UI parameters
# Check for presence with: var schema = self.find('SCHEMA') or if cl.SCHEMA != nil
# Behavior of the plugin, frequency at which `update_shadow()` is called
static var UPDATE_TIME = 5000 # default is every 5 seconds
static var VIRTUAL = false # set to true only for virtual devices
Expand Down Expand Up @@ -544,29 +544,6 @@ matter_device.events.dump()
#############################################################
# UI Methods
#############################################################
# ui_conf_to_string
#
# Convert the current plugin parameters to a single string
static def ui_conf_to_string(cl, conf)
var arg_name = cl.ARG
var arg = arg_name ? str(conf.find(arg_name, '')) : ''
# print("MTR: ui_conf_to_string", conf, cl, arg_name, arg)
return arg
end

#############################################################
# ui_string_to_conf
#
# Convert the string in UI to actual parameters added to the map
static def ui_string_to_conf(cl, conf, arg)
var arg_name = cl.ARG
var arg_type = cl.ARG_TYPE
if arg && arg_name
conf[arg_name] = arg_type(arg)
end
# print("ui_string_to_conf", conf, arg)
return conf
end

#############################################################
# append_state_json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ import matter
class Matter_Plugin_Fan : Matter_Plugin_Device
static var TYPE = "fan" # name of the plug-in in json
static var DISPLAY_NAME = "Fan" # display name of the plug-in
# static var ARG = "" # additional argument name (or empty if none)
# static var ARG = "" # no additional argument (inherited from superclass)
static var CLUSTERS = matter.consolidate_clusters(_class, {
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
# 0x0003: inherited # Identify 1.2 p.16
Expand Down
10 changes: 6 additions & 4 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_2_Light0.be
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,11 @@ import matter
class Matter_Plugin_Light0 : Matter_Plugin_Device
static var TYPE = "light0" # name of the plug-in in json
static var DISPLAY_NAME = "Light 0 OnOff" # display name of the plug-in
static var ARG = "relay" # additional argument name (or empty if none)
static var ARG_TYPE = / x -> int(x) # function to convert argument to the right type
static var ARG_HINT = "Relay<x> number"

static var SCHEMA = "relay|" # arg name
"l:Relay number|" # label (display name)
"t:i|" # type: int
"h:Relay<x> number" # hint
static var UPDATE_TIME = 250 # update every 250ms
static var CLUSTERS = matter.consolidate_clusters(_class, {
# 0x001D: inherited # Descriptor Cluster 9.5 p.453
Expand Down Expand Up @@ -154,7 +156,7 @@ class Matter_Plugin_Light0 : Matter_Plugin_Device
def parse_configuration(config)
super(self).parse_configuration(config)
# with Light0 we always need relay number but we don't for Light1/2/3 so self.tasmota_relay_index may be `nil`
self.tasmota_relay_index = int(config.find(self.ARG #-'relay'-#, nil))
self.tasmota_relay_index = int(config.find('relay', nil))
if (self.tasmota_relay_index != nil && self.tasmota_relay_index <= 0) self.tasmota_relay_index = 1 end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ import matter
#@ solidify:Matter_Plugin_Sensor,weak

class Matter_Plugin_Sensor : Matter_Plugin_Device
static var ARG = "filter" # additional argument name (or empty if none)
static var ARG_HINT = "Filter pattern"

static var SCHEMA = "filter|" # arg name
"l:Filter|" # label (display name)
"h:Filter pattern" # hint (type defaults to text)
static var UPDATE_CMD = "Status 10" # command to send for updates
static var UPDATE_TIME = 5000 # update sensor every 5s
static var JSON_NAME = "" # Name of the sensor attribute in JSON payloads
Expand Down Expand Up @@ -110,7 +112,7 @@ class Matter_Plugin_Sensor : Matter_Plugin_Device
# Parse configuration map
def parse_configuration(config)
super(self).parse_configuration(config)
self.tasmota_sensor_filter = config.find(self.ARG#-'filter'-#)
self.tasmota_sensor_filter = config.find('filter')
if self.tasmota_sensor_filter
self.tasmota_sensor_matcher = tasmota.Rule_Matcher.parse(self.tasmota_sensor_filter)
end
Expand Down
Loading