From 92dc2759174e6afeb358e62740bef22ea33e7efe Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Fri, 15 May 2026 16:53:04 +0200 Subject: [PATCH 01/12] matter initial commit --- ...pplikationsbeschreibung-SmartHomeBridge.md | 11 +- src/Alarm/MatterAlarm.cpp | 86 ++++ src/Alarm/MatterAlarm.h | 15 + src/Baggages/Help_de/BRI-Matter.md | 5 + src/Baggages/Help_de/BRI-Name.md | 1 - src/Dimmer/MatterDimmer.cpp | 51 +++ src/Dimmer/MatterDimmer.h | 15 + src/Display/MatterDisplay.cpp | 102 +++++ src/Display/MatterDisplay.h | 18 + src/DoorWindow/MatterDoorWindow.cpp | 54 +++ src/DoorWindow/MatterDoorWindow.h | 17 + src/Fan/MatterFan.cpp | 63 +++ src/Fan/MatterFan.h | 19 + src/Jalousie/MatterJalousie.cpp | 33 ++ src/Jalousie/MatterJalousie.h | 14 + src/Lock/MatterLock.cpp | 58 +++ src/Lock/MatterLock.h | 18 + src/Matter/MatterBridgeCommon.h | 128 ++++++ src/Matter/MatterBridgeDeviceBase.h | 31 ++ src/MatterBridge.cpp | 386 ++++++++++++++++++ src/MatterBridge.h | 49 +++ src/Media/MatterMedia.cpp | 56 +++ src/Media/MatterMedia.h | 17 + src/RGB/MatterRGB.cpp | 143 +++++++ src/RGB/MatterRGB.h | 20 + src/Rolladen/MatterRolladen.cpp | 50 +++ src/Rolladen/MatterRolladen.h | 16 + src/Scene/MatterScene.cpp | 38 ++ src/Scene/MatterScene.h | 15 + src/SmartHomeBridge.share.xml | 23 +- src/SmartHomeBridgeModule.cpp | 8 + src/Switch/MatterSwitch.cpp | 44 ++ src/Switch/MatterSwitch.h | 15 + src/Thermostat/MatterThermostat.cpp | 135 ++++++ src/Thermostat/MatterThermostat.h | 23 ++ 35 files changed, 1771 insertions(+), 6 deletions(-) create mode 100644 src/Alarm/MatterAlarm.cpp create mode 100644 src/Alarm/MatterAlarm.h create mode 100644 src/Baggages/Help_de/BRI-Matter.md create mode 100644 src/Dimmer/MatterDimmer.cpp create mode 100644 src/Dimmer/MatterDimmer.h create mode 100644 src/Display/MatterDisplay.cpp create mode 100644 src/Display/MatterDisplay.h create mode 100644 src/DoorWindow/MatterDoorWindow.cpp create mode 100644 src/DoorWindow/MatterDoorWindow.h create mode 100644 src/Fan/MatterFan.cpp create mode 100644 src/Fan/MatterFan.h create mode 100644 src/Jalousie/MatterJalousie.cpp create mode 100644 src/Jalousie/MatterJalousie.h create mode 100644 src/Lock/MatterLock.cpp create mode 100644 src/Lock/MatterLock.h create mode 100644 src/Matter/MatterBridgeCommon.h create mode 100644 src/Matter/MatterBridgeDeviceBase.h create mode 100644 src/MatterBridge.cpp create mode 100644 src/MatterBridge.h create mode 100644 src/Media/MatterMedia.cpp create mode 100644 src/Media/MatterMedia.h create mode 100644 src/RGB/MatterRGB.cpp create mode 100644 src/RGB/MatterRGB.h create mode 100644 src/Rolladen/MatterRolladen.cpp create mode 100644 src/Rolladen/MatterRolladen.h create mode 100644 src/Scene/MatterScene.cpp create mode 100644 src/Scene/MatterScene.h create mode 100644 src/Switch/MatterSwitch.cpp create mode 100644 src/Switch/MatterSwitch.h create mode 100644 src/Thermostat/MatterThermostat.cpp create mode 100644 src/Thermostat/MatterThermostat.h diff --git a/doc/Applikationsbeschreibung-SmartHomeBridge.md b/doc/Applikationsbeschreibung-SmartHomeBridge.md index 0b8bf26..8542eb6 100644 --- a/doc/Applikationsbeschreibung-SmartHomeBridge.md +++ b/doc/Applikationsbeschreibung-SmartHomeBridge.md @@ -16,12 +16,19 @@ In den Basiseinstellungen wird die Auswahl der angebunden Smart Home System vorg Name der Smart Home Bridge. Bei manchen Smart Home Systemen wird dieser beim Koppeln angezeigt. - +## Unterstütze Smarthomesysteme + Folgende Smart Home Systeme werden unterstüzt: -#### Apple HomeKit +### Matter + +Über die Matter Unterstützung können KNX Geräte über matter fähige Geräte gesteuert werden. +Z.B. Google Home, Apple Home + + +### Apple HomeKit Über Apple HomeKit können die KNX Geräte gesteuert werden. Es werden bis zu 149 Geräte unterstützt, jedoch derzeit nur maximal 60 empfohlen. Das Limit ergibt sich aus eine Beschränkung der Anzahl der Accessories bei einem HomeKit Gerät mit maximal 150. Eines davon repräsentiert das Bridge Gerät. diff --git a/src/Alarm/MatterAlarm.cpp b/src/Alarm/MatterAlarm.cpp new file mode 100644 index 0000000..f49eeb9 --- /dev/null +++ b/src/Alarm/MatterAlarm.cpp @@ -0,0 +1,86 @@ +#include "Alarm/MatterAlarm.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include + +MatterAlarmBridge::MatterAlarmBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterAlarmBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + uint32_t matterType = esp_matter::endpoint::occupancy_sensor::get_device_type_id(); + switch (_channel->getAlarmType()) + { + case AlarmTypeContact: + matterType = esp_matter::endpoint::contact_sensor::get_device_type_id(); + break; + case AlarmTypeCarbonMonoxid: + case AlarmTypeSmoke: + matterType = esp_matter::endpoint::smoke_co_alarm::get_device_type_id(); + break; + case AlarmTypeCarbonDioxid: + matterType = esp_matter::endpoint::air_quality_sensor::get_device_type_id(); + break; + case AlarmTypeLeak: + matterType = esp_matter::endpoint::water_leak_detector::get_device_type_id(); + break; + default: + matterType = esp_matter::endpoint::occupancy_sensor::get_device_type_id(); + break; + } + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), matterType, + static_cast(this)); + if (_device != nullptr) + setDetected(_channel->mainFunctionValue()); +} + +void MatterAlarmBridge::setDetected(bool detected) +{ + if (_device == nullptr) + return; + + auto alarmType = _channel->getAlarmType(); + if (alarmType == AlarmTypeContact || alarmType == AlarmTypeLeak) + { + matterbridge::reportBool(_device->persistent_info.device_endpoint_id, matterbridge::boolStateClusterId, + matterbridge::boolStateAttrId, detected); + } + else if (alarmType == AlarmTypeSmoke) + { + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::smokeCoAlarmClusterId, + matterbridge::smokeStateAttrId, + detected ? static_cast(chip::app::Clusters::SmokeCoAlarm::AlarmStateEnum::kCritical) + : static_cast(chip::app::Clusters::SmokeCoAlarm::AlarmStateEnum::kNormal)); + } + else if (alarmType == AlarmTypeCarbonMonoxid) + { + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::smokeCoAlarmClusterId, + matterbridge::coStateAttrId, + detected ? static_cast(chip::app::Clusters::SmokeCoAlarm::AlarmStateEnum::kCritical) + : static_cast(chip::app::Clusters::SmokeCoAlarm::AlarmStateEnum::kNormal)); + } + else if (alarmType == AlarmTypeCarbonDioxid) + { + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::airQualityClusterId, + matterbridge::measuredAirQualityAttrId, + detected ? static_cast(chip::app::Clusters::AirQuality::AirQualityEnum::kPoor) + : static_cast(chip::app::Clusters::AirQuality::AirQualityEnum::kGood)); + } + else + { + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::occupancyClusterId, + matterbridge::occupancyAttrId, detected ? 1 : 0); + } +} + +void MatterAlarmBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t, + uint32_t, esp_matter_attr_val_t *) +{ +} \ No newline at end of file diff --git a/src/Alarm/MatterAlarm.h b/src/Alarm/MatterAlarm.h new file mode 100644 index 0000000..8419f6d --- /dev/null +++ b/src/Alarm/MatterAlarm.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "Alarm/KnxChannelAlarm.h" + +class MatterAlarmBridge final : public MatterBridgeDeviceBase, public AlarmBridge +{ +public: + explicit MatterAlarmBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setDetected(bool detected) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/Baggages/Help_de/BRI-Matter.md b/src/Baggages/Help_de/BRI-Matter.md new file mode 100644 index 0000000..d62f36f --- /dev/null +++ b/src/Baggages/Help_de/BRI-Matter.md @@ -0,0 +1,5 @@ +### Matter + +Über die Matter Unterstützung können KNX Geräte über matter fähige Geräte gesteuert werden. +Z.B. Google Home, Apple Home + diff --git a/src/Baggages/Help_de/BRI-Name.md b/src/Baggages/Help_de/BRI-Name.md index 3b52cc6..caa90d0 100644 --- a/src/Baggages/Help_de/BRI-Name.md +++ b/src/Baggages/Help_de/BRI-Name.md @@ -3,4 +3,3 @@ Name der Smart Home Bridge. Bei manchen Smart Home Systemen wird dieser beim Koppeln angezeigt. - diff --git a/src/Dimmer/MatterDimmer.cpp b/src/Dimmer/MatterDimmer.cpp new file mode 100644 index 0000000..b51a908 --- /dev/null +++ b/src/Dimmer/MatterDimmer.cpp @@ -0,0 +1,51 @@ +#include "Dimmer/MatterDimmer.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include "knxprod.h" +#include + +MatterDimmerBridge::MatterDimmerBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterDimmerBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + uint32_t matterType = ParamBRI_CHDeviceType == 20 + ? esp_matter::endpoint::dimmable_light::get_device_type_id() + : esp_matter::endpoint::dimmable_plugin_unit::get_device_type_id(); + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), matterType, + static_cast(this)); + if (_device != nullptr) + setBrightness(_channel->mainFunctionValue() ? 254 : 0); +} + +void MatterDimmerBridge::setBrightness(uint8_t brightness) +{ + if (_device == nullptr) + return; + + matterbridge::reportBool(_device->persistent_info.device_endpoint_id, matterbridge::onOffClusterId, + matterbridge::onOffAttrId, brightness > 0); + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::levelControlClusterId, + matterbridge::currentLevelAttrId, + static_cast(std::clamp(brightness, 0, 254))); +} + +void MatterDimmerBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + if (clusterId == matterbridge::onOffClusterId && attributeId == matterbridge::onOffAttrId) + { + _channel->commandPower(this, val->val.b); + } + else if (clusterId == matterbridge::levelControlClusterId && attributeId == matterbridge::currentLevelAttrId) + { + _channel->commandBrightness(this, val->val.u8); + } +} \ No newline at end of file diff --git a/src/Dimmer/MatterDimmer.h b/src/Dimmer/MatterDimmer.h new file mode 100644 index 0000000..af2f621 --- /dev/null +++ b/src/Dimmer/MatterDimmer.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "Dimmer/KnxChannelDimmer.h" + +class MatterDimmerBridge final : public MatterBridgeDeviceBase, public DimmerBridge +{ +public: + explicit MatterDimmerBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setBrightness(uint8_t brightness) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/Display/MatterDisplay.cpp b/src/Display/MatterDisplay.cpp new file mode 100644 index 0000000..9d44775 --- /dev/null +++ b/src/Display/MatterDisplay.cpp @@ -0,0 +1,102 @@ +#include "Display/MatterDisplay.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include + +MatterDisplayBridge::MatterDisplayBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterDisplayBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + _displayType = _channel->getDisplayType(); + switch (_displayType) + { + case DisplayTypeTemperature: + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::temperature_sensor::get_device_type_id(), + static_cast(this)); + break; + case DisplayTypeHumidity: + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::humidity_sensor::get_device_type_id(), + static_cast(this)); + break; + case DisplayTypeLux: + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::light_sensor::get_device_type_id(), + static_cast(this)); + break; + case DisplayTypeRain: + case DisplayTypeSnow: + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::rain_sensor::get_device_type_id(), + static_cast(this)); + break; + case DisplayTypeWind: + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::air_quality_sensor::get_device_type_id(), + static_cast(this)); + break; + default: + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::occupancy_sensor::get_device_type_id(), + static_cast(this)); + break; + } +} + +void MatterDisplayBridge::setValue(double value) +{ + if (_device == nullptr) + return; + + switch (_displayType) + { + case DisplayTypeTemperature: + matterbridge::reportI16(_device->persistent_info.device_endpoint_id, matterbridge::temperatureClusterId, + matterbridge::measuredTemperatureAttrId, + static_cast(std::lround(value * 100.0))); + break; + case DisplayTypeHumidity: + matterbridge::reportU16(_device->persistent_info.device_endpoint_id, matterbridge::humidityClusterId, + matterbridge::measuredHumidityAttrId, + static_cast(std::clamp(std::lround(value * 100.0), 0, 10000))); + break; + case DisplayTypeLux: + matterbridge::reportU16(_device->persistent_info.device_endpoint_id, matterbridge::illuminanceClusterId, + matterbridge::measuredIlluminanceAttrId, + static_cast(std::clamp(std::lround(value), 0, 65534))); + break; + case DisplayTypeRain: + case DisplayTypeSnow: + matterbridge::reportBool(_device->persistent_info.device_endpoint_id, matterbridge::boolStateClusterId, + matterbridge::boolStateAttrId, value > 0.0); + break; + case DisplayTypeWind: + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::airQualityClusterId, + matterbridge::measuredAirQualityAttrId, + value > 0.0 ? static_cast(chip::app::Clusters::AirQuality::AirQualityEnum::kFair) + : static_cast(chip::app::Clusters::AirQuality::AirQualityEnum::kGood)); + break; + default: + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::occupancyClusterId, + matterbridge::occupancyAttrId, + static_cast(std::clamp(std::lround(value), 0, 1))); + break; + } +} + +void MatterDisplayBridge::setValue(const char *) +{ +} + +void MatterDisplayBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t, + uint32_t, esp_matter_attr_val_t *) +{ +} \ No newline at end of file diff --git a/src/Display/MatterDisplay.h b/src/Display/MatterDisplay.h new file mode 100644 index 0000000..283579c --- /dev/null +++ b/src/Display/MatterDisplay.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "Display/KnxChannelDisplay.h" + +class MatterDisplayBridge final : public MatterBridgeDeviceBase, public DeviceBridge +{ + DisplayType _displayType = DisplayTypeText; + +public: + explicit MatterDisplayBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setValue(double value) override; + void setValue(const char *value) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/DoorWindow/MatterDoorWindow.cpp b/src/DoorWindow/MatterDoorWindow.cpp new file mode 100644 index 0000000..b115691 --- /dev/null +++ b/src/DoorWindow/MatterDoorWindow.cpp @@ -0,0 +1,54 @@ +#include "DoorWindow/MatterDoorWindow.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include + +MatterDoorWindowBridge::MatterDoorWindowBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterDoorWindowBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::window_covering_device::get_device_type_id(), + static_cast(this)); + if (_device != nullptr) + setPosition(_channel->mainFunctionValue() ? 100 : 0); +} + +void MatterDoorWindowBridge::setPosition(uint8_t position) +{ + if (_device == nullptr) + return; + + auto matterPosition = static_cast(std::clamp(position, 0, 100) * 100); + auto currentVal = matterbridge::u16Value(matterPosition); + auto targetVal = matterbridge::u16Value(matterPosition); + esp_matter::attribute::report(_device->persistent_info.device_endpoint_id, matterbridge::windowCoveringClusterId, + matterbridge::currentPositionLiftPercent100thsAttrId, ¤tVal); + esp_matter::attribute::report(_device->persistent_info.device_endpoint_id, matterbridge::windowCoveringClusterId, + matterbridge::targetPositionLiftPercent100thsAttrId, &targetVal); +} + +void MatterDoorWindowBridge::setMovement(DoorWindowMoveState) +{ +} + +void MatterDoorWindowBridge::setObstructionDetected(bool) +{ +} + +void MatterDoorWindowBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + if (clusterId == matterbridge::windowCoveringClusterId && + attributeId == matterbridge::targetPositionLiftPercent100thsAttrId) + { + _channel->commandPosition(this, static_cast(val->val.u16 / 100)); + } +} \ No newline at end of file diff --git a/src/DoorWindow/MatterDoorWindow.h b/src/DoorWindow/MatterDoorWindow.h new file mode 100644 index 0000000..168fa4a --- /dev/null +++ b/src/DoorWindow/MatterDoorWindow.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "DoorWindow/KnxChannelDoorWindow.h" + +class MatterDoorWindowBridge final : public MatterBridgeDeviceBase, public DoorWindowBridge +{ +public: + explicit MatterDoorWindowBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setPosition(uint8_t position) override; + void setMovement(DoorWindowMoveState movement) override; + void setObstructionDetected(bool obstructionDetected) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/Fan/MatterFan.cpp b/src/Fan/MatterFan.cpp new file mode 100644 index 0000000..1811ed0 --- /dev/null +++ b/src/Fan/MatterFan.cpp @@ -0,0 +1,63 @@ +#include "Fan/MatterFan.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include "knxprod.h" +#include + +MatterFanBridge::MatterFanBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterFanBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::fan::get_device_type_id(), + static_cast(this)); + if (_device != nullptr) + { + setPower(_channel->mainFunctionValue()); + setAutomatic(_automatic); + } +} + +void MatterFanBridge::setAutomatic(bool automatic) +{ + uint8_t _channelIndex = _channel != nullptr ? _channel->channelIndex() : 0; + if (ParamBRI_CHFanAutomatic == 0) + return; + + _automatic = automatic; + if (_device == nullptr) + return; + + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::fanControlClusterId, + matterbridge::fanModeAttrId, automatic ? 0 : 1); +} + +void MatterFanBridge::setPower(bool on) +{ + _power = on; + if (_device == nullptr) + return; + + matterbridge::reportBool(_device->persistent_info.device_endpoint_id, matterbridge::onOffClusterId, + matterbridge::onOffAttrId, on); + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::fanControlClusterId, + matterbridge::percentCurrentAttrId, on ? 100 : 0); + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::fanControlClusterId, + matterbridge::percentSettingAttrId, on ? 100 : 0); +} + +void MatterFanBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + if (clusterId == matterbridge::onOffClusterId && attributeId == matterbridge::onOffAttrId) + _channel->commandPower(this, val->val.b); + else if (clusterId == matterbridge::fanControlClusterId && attributeId == matterbridge::fanModeAttrId) + _channel->commandAutomatic(this, val->val.u8 == 0); +} \ No newline at end of file diff --git a/src/Fan/MatterFan.h b/src/Fan/MatterFan.h new file mode 100644 index 0000000..3b6c2fe --- /dev/null +++ b/src/Fan/MatterFan.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "Fan/KnxChannelFan.h" + +class MatterFanBridge final : public MatterBridgeDeviceBase, public FanBridge +{ + bool _automatic = false; + bool _power = false; + +public: + explicit MatterFanBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setAutomatic(bool automatic) override; + void setPower(bool on) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/Jalousie/MatterJalousie.cpp b/src/Jalousie/MatterJalousie.cpp new file mode 100644 index 0000000..1835700 --- /dev/null +++ b/src/Jalousie/MatterJalousie.cpp @@ -0,0 +1,33 @@ +#include "Jalousie/MatterJalousie.h" + +#include "Matter/MatterBridgeCommon.h" + +MatterJalousieBridge::MatterJalousieBridge(MatterBridge *bridge) : MatterRolladenBridge(bridge) +{ +} + +void MatterJalousieBridge::setSlatPosition(uint8_t slatPosition) +{ + if (_device == nullptr) + return; + + auto tilt = static_cast(std::clamp(slatPosition, 0, 100) * 100); + auto currentVal = matterbridge::u16Value(tilt); + auto targetVal = matterbridge::u16Value(tilt); + esp_matter::attribute::report(_device->persistent_info.device_endpoint_id, matterbridge::windowCoveringClusterId, + matterbridge::currentPositionTiltPercent100thsAttrId, ¤tVal); + esp_matter::attribute::report(_device->persistent_info.device_endpoint_id, matterbridge::windowCoveringClusterId, + matterbridge::targetPositionTiltPercent100thsAttrId, &targetVal); +} + +void MatterJalousieBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + MatterRolladenBridge::handleMatterAttribute(type, clusterId, attributeId, val); + + if (clusterId == matterbridge::windowCoveringClusterId && + attributeId == matterbridge::targetPositionTiltPercent100thsAttrId) + { + static_cast(_channel)->commandSlatPosition(this, static_cast(val->val.u16 / 100)); + } +} \ No newline at end of file diff --git a/src/Jalousie/MatterJalousie.h b/src/Jalousie/MatterJalousie.h new file mode 100644 index 0000000..e57195a --- /dev/null +++ b/src/Jalousie/MatterJalousie.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Rolladen/MatterRolladen.h" +#include "Jalousie/KnxChannelJalousie.h" + +class MatterJalousieBridge final : public MatterRolladenBridge +{ +public: + explicit MatterJalousieBridge(MatterBridge *bridge); + + void setSlatPosition(uint8_t slatPosition) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/Lock/MatterLock.cpp b/src/Lock/MatterLock.cpp new file mode 100644 index 0000000..5d7adb8 --- /dev/null +++ b/src/Lock/MatterLock.cpp @@ -0,0 +1,58 @@ +#include "Lock/MatterLock.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include + +MatterLockBridge::MatterLockBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterLockBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::door_lock::get_device_type_id(), + static_cast(this)); + if (_device != nullptr) + setLocked(_channel->mainFunctionValue()); +} + +void MatterLockBridge::setLocked(bool lock) +{ + if (_device == nullptr) + return; + + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::doorLockClusterId, + matterbridge::lockStateAttrId, + lock ? static_cast(chip::app::Clusters::DoorLock::DlLockState::kLocked) + : static_cast(chip::app::Clusters::DoorLock::DlLockState::kUnlocked)); +} + +void MatterLockBridge::setBlocked(bool) +{ +} + +void MatterLockBridge::setUnlocking(bool) +{ +} + +void MatterLockBridge::setLocking(bool) +{ +} + +void MatterLockBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + if (attributeId == matterbridge::lockStateAttrId) + { + const auto lockState = static_cast(val->val.u8); + if (lockState == static_cast(chip::app::Clusters::DoorLock::DlLockState::kLocked)) + _channel->commandLock(this, true); + else if (lockState == static_cast(chip::app::Clusters::DoorLock::DlLockState::kUnlocked)) + _channel->commandLock(this, false); + } +} \ No newline at end of file diff --git a/src/Lock/MatterLock.h b/src/Lock/MatterLock.h new file mode 100644 index 0000000..2a15dae --- /dev/null +++ b/src/Lock/MatterLock.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "Lock/KnxChannelLock.h" + +class MatterLockBridge final : public MatterBridgeDeviceBase, public LockBridge +{ +public: + explicit MatterLockBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setLocked(bool lock) override; + void setBlocked(bool lock) override; + void setUnlocking(bool unlocking) override; + void setLocking(bool locking) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/Matter/MatterBridgeCommon.h b/src/Matter/MatterBridgeCommon.h new file mode 100644 index 0000000..835576a --- /dev/null +++ b/src/Matter/MatterBridgeCommon.h @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include + +#ifdef INADDR_NONE +#undef INADDR_NONE +#endif + +#include +#include +#include + +namespace matterbridge +{ +constexpr uint32_t onOffClusterId = chip::app::Clusters::OnOff::Id; +constexpr uint32_t levelControlClusterId = chip::app::Clusters::LevelControl::Id; +constexpr uint32_t colorControlClusterId = chip::app::Clusters::ColorControl::Id; +constexpr uint32_t fanControlClusterId = chip::app::Clusters::FanControl::Id; +constexpr uint32_t thermostatClusterId = chip::app::Clusters::Thermostat::Id; +constexpr uint32_t doorLockClusterId = chip::app::Clusters::DoorLock::Id; +constexpr uint32_t windowCoveringClusterId = chip::app::Clusters::WindowCovering::Id; +constexpr uint32_t basicInformationClusterId = chip::app::Clusters::BasicInformation::Id; +constexpr uint32_t occupancyClusterId = chip::app::Clusters::OccupancySensing::Id; +constexpr uint32_t smokeCoAlarmClusterId = chip::app::Clusters::SmokeCoAlarm::Id; +constexpr uint32_t humidityClusterId = chip::app::Clusters::RelativeHumidityMeasurement::Id; +constexpr uint32_t temperatureClusterId = chip::app::Clusters::TemperatureMeasurement::Id; +constexpr uint32_t illuminanceClusterId = chip::app::Clusters::IlluminanceMeasurement::Id; +constexpr uint32_t airQualityClusterId = chip::app::Clusters::AirQuality::Id; +constexpr uint32_t boolStateClusterId = chip::app::Clusters::BooleanState::Id; + +constexpr uint32_t onOffAttrId = chip::app::Clusters::OnOff::Attributes::OnOff::Id; +constexpr uint32_t currentLevelAttrId = chip::app::Clusters::LevelControl::Attributes::CurrentLevel::Id; +constexpr uint32_t currentHueAttrId = chip::app::Clusters::ColorControl::Attributes::CurrentHue::Id; +constexpr uint32_t currentSaturationAttrId = chip::app::Clusters::ColorControl::Attributes::CurrentSaturation::Id; +constexpr uint32_t fanModeAttrId = chip::app::Clusters::FanControl::Attributes::FanMode::Id; +constexpr uint32_t percentSettingAttrId = chip::app::Clusters::FanControl::Attributes::PercentSetting::Id; +constexpr uint32_t percentCurrentAttrId = chip::app::Clusters::FanControl::Attributes::PercentCurrent::Id; +constexpr uint32_t systemModeAttrId = chip::app::Clusters::Thermostat::Attributes::SystemMode::Id; +// RunningMode may be absent in some generated CHIP namespace variants; use the spec attribute id directly. +constexpr uint32_t runningModeAttrId = 0x001E; +constexpr uint32_t localTemperatureAttrId = chip::app::Clusters::Thermostat::Attributes::LocalTemperature::Id; +constexpr uint32_t heatingSetpointAttrId = chip::app::Clusters::Thermostat::Attributes::OccupiedHeatingSetpoint::Id; +constexpr uint32_t coolingSetpointAttrId = chip::app::Clusters::Thermostat::Attributes::OccupiedCoolingSetpoint::Id; +constexpr uint32_t lockStateAttrId = chip::app::Clusters::DoorLock::Attributes::LockState::Id; +constexpr uint32_t targetPositionTiltPercent100thsAttrId = chip::app::Clusters::WindowCovering::Attributes::TargetPositionTiltPercent100ths::Id; +constexpr uint32_t currentPositionTiltPercent100thsAttrId = chip::app::Clusters::WindowCovering::Attributes::CurrentPositionTiltPercent100ths::Id; +constexpr uint32_t currentPositionLiftPercent100thsAttrId = chip::app::Clusters::WindowCovering::Attributes::CurrentPositionLiftPercent100ths::Id; +constexpr uint32_t targetPositionLiftPercent100thsAttrId = chip::app::Clusters::WindowCovering::Attributes::TargetPositionLiftPercent100ths::Id; +constexpr uint32_t occupancyAttrId = chip::app::Clusters::OccupancySensing::Attributes::Occupancy::Id; +constexpr uint32_t boolStateAttrId = chip::app::Clusters::BooleanState::Attributes::StateValue::Id; +constexpr uint32_t smokeStateAttrId = chip::app::Clusters::SmokeCoAlarm::Attributes::SmokeState::Id; +constexpr uint32_t coStateAttrId = chip::app::Clusters::SmokeCoAlarm::Attributes::COState::Id; +constexpr uint32_t measuredTemperatureAttrId = chip::app::Clusters::TemperatureMeasurement::Attributes::MeasuredValue::Id; +constexpr uint32_t measuredHumidityAttrId = chip::app::Clusters::RelativeHumidityMeasurement::Attributes::MeasuredValue::Id; +constexpr uint32_t measuredIlluminanceAttrId = chip::app::Clusters::IlluminanceMeasurement::Attributes::MeasuredValue::Id; +constexpr uint32_t measuredAirQualityAttrId = chip::app::Clusters::AirQuality::Attributes::AirQuality::Id; +constexpr uint32_t nodeLabelAttrId = chip::app::Clusters::BasicInformation::Attributes::NodeLabel::Id; + +inline esp_matter_attr_val_t boolValue(bool value) +{ + esp_matter_attr_val_t result{}; + result.type = ESP_MATTER_VAL_TYPE_BOOLEAN; + result.val.b = value; + return result; +} + +inline esp_matter_attr_val_t u8Value(uint8_t value) +{ + esp_matter_attr_val_t result{}; + result.type = ESP_MATTER_VAL_TYPE_UINT8; + result.val.u8 = value; + return result; +} + +inline esp_matter_attr_val_t i16Value(int16_t value) +{ + esp_matter_attr_val_t result{}; + result.type = ESP_MATTER_VAL_TYPE_INT16; + result.val.i16 = value; + return result; +} + +inline esp_matter_attr_val_t u16Value(uint16_t value) +{ + esp_matter_attr_val_t result{}; + result.type = ESP_MATTER_VAL_TYPE_UINT16; + result.val.u16 = value; + return result; +} + +inline esp_matter_attr_val_t textValue(const char *value) +{ + std::string copy = value != nullptr ? value : ""; + return esp_matter_char_str(copy.data(), static_cast(copy.size())); +} + +inline esp_err_t reportBool(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, bool value) +{ + auto reported = boolValue(value); + return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); +} + +inline esp_err_t reportU8(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, uint8_t value) +{ + auto reported = u8Value(value); + return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); +} + +inline esp_err_t reportI16(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, int16_t value) +{ + auto reported = i16Value(value); + return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); +} + +inline esp_err_t reportU16(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, uint16_t value) +{ + auto reported = u16Value(value); + return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); +} + +inline esp_err_t reportText(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, const char *value) +{ + auto reported = textValue(value); + return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); +} +} \ No newline at end of file diff --git a/src/Matter/MatterBridgeDeviceBase.h b/src/Matter/MatterBridgeDeviceBase.h new file mode 100644 index 0000000..5d1c899 --- /dev/null +++ b/src/Matter/MatterBridgeDeviceBase.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +class MatterBridge; + +class MatterBridgeDeviceBase +{ +protected: + MatterBridge *_bridge = nullptr; + esp_matter_bridge::device_t *_device = nullptr; + +public: + explicit MatterBridgeDeviceBase(MatterBridge *bridge) : _bridge(bridge) {} + + virtual ~MatterBridgeDeviceBase() + { + if (_device != nullptr) + esp_matter_bridge::remove_device(_device); + } + + uint16_t endpointId() const + { + return _device != nullptr ? _device->persistent_info.device_endpoint_id : 0; + } + + virtual void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) = 0; +}; \ No newline at end of file diff --git a/src/MatterBridge.cpp b/src/MatterBridge.cpp new file mode 100644 index 0000000..00e9d7f --- /dev/null +++ b/src/MatterBridge.cpp @@ -0,0 +1,386 @@ +#include "MatterBridge.h" + +#include "SmartHomeBridgeModule.h" + +#include "Matter/MatterBridgeDeviceBase.h" + +#include "Switch/MatterSwitch.h" +#include "Dimmer/MatterDimmer.h" +#include "RGB/MatterRGB.h" +#include "Jalousie/MatterJalousie.h" +#include "Rolladen/MatterRolladen.h" +#include "Thermostat/MatterThermostat.h" +#include "Display/MatterDisplay.h" +#include "Alarm/MatterAlarm.h" +#include "Fan/MatterFan.h" +#include "DoorWindow/MatterDoorWindow.h" +#include "Scene/MatterScene.h" +#include "Lock/MatterLock.h" +#include "Media/MatterMedia.h" + +#include +#include + +#include + +namespace +{ +esp_err_t addSwitchEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::on_off_switch::config_t config; + return esp_matter::endpoint::on_off_switch::add(endpoint, &config); +} + +esp_err_t addOutletEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::on_off_plugin_unit::config_t config; + return esp_matter::endpoint::on_off_plugin_unit::add(endpoint, &config); +} + +esp_err_t addOnOffLightEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::on_off_light::config_t config; + return esp_matter::endpoint::on_off_light::add(endpoint, &config); +} + +esp_err_t addDimmerEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::dimmable_plugin_unit::config_t config; + return esp_matter::endpoint::dimmable_plugin_unit::add(endpoint, &config); +} + +esp_err_t addDimmableLightEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::dimmable_light::config_t config; + return esp_matter::endpoint::dimmable_light::add(endpoint, &config); +} + +esp_err_t addRGBEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::extended_color_light::config_t config; + return esp_matter::endpoint::extended_color_light::add(endpoint, &config); +} + +esp_err_t addRolladenEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::window_covering_device::config_t config; + return esp_matter::endpoint::window_covering_device::add(endpoint, &config); +} + +esp_err_t addThermostatEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::thermostat::config_t config; + return esp_matter::endpoint::thermostat::add(endpoint, &config); +} + +esp_err_t addFanEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::fan::config_t config; + return esp_matter::endpoint::fan::add(endpoint, &config); +} + +esp_err_t addLockEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::door_lock::config_t config; + return esp_matter::endpoint::door_lock::add(endpoint, &config); +} + +esp_err_t addTemperatureEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::temperature_sensor::config_t config; + return esp_matter::endpoint::temperature_sensor::add(endpoint, &config); +} + +esp_err_t addHumidityEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::humidity_sensor::config_t config; + return esp_matter::endpoint::humidity_sensor::add(endpoint, &config); +} + +esp_err_t addLightEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::light_sensor::config_t config; + return esp_matter::endpoint::light_sensor::add(endpoint, &config); +} + +esp_err_t addOccupancyEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::occupancy_sensor::config_t config; + return esp_matter::endpoint::occupancy_sensor::add(endpoint, &config); +} + +esp_err_t addContactEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::contact_sensor::config_t config; + return esp_matter::endpoint::contact_sensor::add(endpoint, &config); +} + +esp_err_t addLeakEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::water_leak_detector::config_t config; + return esp_matter::endpoint::water_leak_detector::add(endpoint, &config); +} + +esp_err_t addSmokeEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::smoke_co_alarm::config_t config; + return esp_matter::endpoint::smoke_co_alarm::add(endpoint, &config); +} + +esp_err_t addAirQualityEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::air_quality_sensor::config_t config; + return esp_matter::endpoint::air_quality_sensor::add(endpoint, &config); +} + +esp_err_t addRainEndpoint(esp_matter::endpoint_t *endpoint) +{ + esp_matter::endpoint::rain_sensor::config_t config; + return esp_matter::endpoint::rain_sensor::add(endpoint, &config); +} + +esp_err_t addForDeviceType(esp_matter::endpoint_t *endpoint, uint32_t deviceTypeId) +{ + if (deviceTypeId == esp_matter::endpoint::on_off_switch::get_device_type_id()) + return addSwitchEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::on_off_plugin_unit::get_device_type_id()) + return addOutletEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::on_off_light::get_device_type_id()) + return addOnOffLightEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::dimmable_plugin_unit::get_device_type_id()) + return addDimmerEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::dimmable_light::get_device_type_id()) + return addDimmableLightEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::extended_color_light::get_device_type_id()) + return addRGBEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::window_covering_device::get_device_type_id()) + return addRolladenEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::thermostat::get_device_type_id()) + return addThermostatEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::fan::get_device_type_id()) + return addFanEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::door_lock::get_device_type_id()) + return addLockEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::temperature_sensor::get_device_type_id()) + return addTemperatureEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::humidity_sensor::get_device_type_id()) + return addHumidityEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::light_sensor::get_device_type_id()) + return addLightEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::occupancy_sensor::get_device_type_id()) + return addOccupancyEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::contact_sensor::get_device_type_id()) + return addContactEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::water_leak_detector::get_device_type_id()) + return addLeakEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::smoke_co_alarm::get_device_type_id()) + return addSmokeEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::air_quality_sensor::get_device_type_id()) + return addAirQualityEndpoint(endpoint); + if (deviceTypeId == esp_matter::endpoint::rain_sensor::get_device_type_id()) + return addRainEndpoint(endpoint); + + return ESP_OK; +} + +MatterBridgeDeviceBase *asDeviceBase(void *privData) +{ + return reinterpret_cast(privData); +} +} // namespace + +esp_matter::node_t *MatterBridge::node() const +{ + return _node; +} + +uint16_t MatterBridge::parentEndpointId() const +{ + return 0; +} + +const std::string MatterBridge::name() +{ + return "MatterBridge"; +} + +esp_err_t MatterBridge::configureNode(esp_matter::node_t *node) +{ + esp_matter::attribute::set_callback(attributeCallback); + return esp_matter_bridge::initialize(node, deviceTypeCallback); +} + +void MatterBridge::initialize(SmartHomeBridgeModule *bridge) +{ + BridgeBase::initialize(bridge); + + esp_matter::node::config_t config{}; + std::string nodeLabel = bridge != nullptr ? bridge->getNameInUTF8() : "SmartHomeBridge"; + std::strncpy(config.root_node.basic_information.node_label, nodeLabel.c_str(), + sizeof(config.root_node.basic_information.node_label) - 1); + + _node = esp_matter::node::create(&config, attributeCallback, nullptr, this); + if (_node == nullptr) + { + logErrorP("Matter node creation failed"); + return; + } + + if (configureNode(_node) != ESP_OK) + logErrorP("Matter bridge initialization failed"); +} + +void MatterBridge::start(SmartHomeBridgeModule *bridge) +{ + BridgeBase::start(bridge); + if (startMatter() != ESP_OK) + logErrorP("Matter start failed"); +} + +esp_err_t MatterBridge::startMatter() +{ + return esp_matter::start(nullptr); +} + +void MatterBridge::loop() +{ +} + +void MatterBridge::processInputKo(GroupObject &) +{ +} + +void MatterBridge::getInformation(String &result) +{ + result += "

Matter

"; + result += "Matter bridge aktiv"; +} + +bool MatterBridge::processCommand(const std::string cmd, bool) +{ + if (cmd == "mr") + { + factoryReset(); + return true; + } + return false; +} + +void MatterBridge::showHelp() +{ + openknx.console.printHelpLine("mr", "Matter factory reset"); +} + +esp_err_t MatterBridge::factoryReset() +{ + return esp_matter_bridge::factory_reset(); +} + +esp_err_t MatterBridge::deviceTypeCallback(esp_matter::endpoint_t *endpoint, uint32_t deviceTypeId, void *) +{ + return addForDeviceType(endpoint, deviceTypeId); +} + +esp_err_t MatterBridge::attributeCallback(esp_matter::attribute::callback_type_t type, uint16_t endpointId, + uint32_t clusterId, uint32_t attributeId, + esp_matter_attr_val_t *val, void *privData) +{ + auto device = asDeviceBase(privData); + if (device == nullptr || val == nullptr || type == esp_matter::attribute::PRE_UPDATE) + return ESP_OK; + + device->handleMatterAttribute(type, clusterId, attributeId, val); + (void)endpointId; + return ESP_OK; +} + +SwitchBridge *MatterBridge::createSwitch(KnxChannelSwitch &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterSwitchBridge(this); + channel.add(bridge); + return bridge; +} + +DimmerBridge *MatterBridge::createDimmer(KnxChannelDimmer &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterDimmerBridge(this); + channel.add(bridge); + return bridge; +} + +RGBBridge *MatterBridge::createRGB(KnxChannelRGB &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterRGBBridge(this); + channel.add(bridge); + return bridge; +} + +RolladenBridge *MatterBridge::createJalousien(KnxChannelJalousie &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterJalousieBridge(this); + channel.add(bridge); + return bridge; +} + +RolladenBridge *MatterBridge::createRolladen(KnxChannelRolladen &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterRolladenBridge(this); + channel.add(bridge); + return bridge; +} + +ThermostatBridge *MatterBridge::createThermostat(KnxChannelThermostat &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterThermostatBridge(this); + channel.add(bridge); + return bridge; +} + +DeviceBridge *MatterBridge::createDisplay(KnxChannelDisplay &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterDisplayBridge(this); + channel.add(bridge); + return bridge; +} + +AlarmBridge *MatterBridge::createSensor(KnxChannelAlarm &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterAlarmBridge(this); + channel.add(bridge); + return bridge; +} + +FanBridge *MatterBridge::createFan(KnxChannelFan &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterFanBridge(this); + channel.add(bridge); + return bridge; +} + +DoorWindowBridge *MatterBridge::createDoorWindow(KnxChannelDoorWindow &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterDoorWindowBridge(this); + channel.add(bridge); + return bridge; +} + +SceneBridge *MatterBridge::createScene(KnxChannelScene &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterSceneBridge(this); + channel.add(bridge); + return bridge; +} + +LockBridge *MatterBridge::createLock(KnxChannelLock &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterLockBridge(this); + channel.add(bridge); + return bridge; +} + +MediaBridge *MatterBridge::createMedia(KnxChannelMedia &channel, uint8_t, uint8_t) +{ + auto bridge = new MatterMediaBridge(this); + channel.add(bridge); + return bridge; +} diff --git a/src/MatterBridge.h b/src/MatterBridge.h new file mode 100644 index 0000000..2aa3b09 --- /dev/null +++ b/src/MatterBridge.h @@ -0,0 +1,49 @@ +#pragma once + +#include "BridgeBase.h" +#include +#include + +class MatterBridge : public BridgeBase +{ + esp_matter::node_t *_node = nullptr; + +public: + MatterBridge() = default; + + esp_matter::node_t *node() const; + uint16_t parentEndpointId() const; + + virtual SwitchBridge *createSwitch(KnxChannelSwitch &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual DimmerBridge *createDimmer(KnxChannelDimmer &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual RGBBridge *createRGB(KnxChannelRGB &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual RolladenBridge *createJalousien(KnxChannelJalousie &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual RolladenBridge *createRolladen(KnxChannelRolladen &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual ThermostatBridge *createThermostat(KnxChannelThermostat &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual DeviceBridge *createDisplay(KnxChannelDisplay &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual AlarmBridge *createSensor(KnxChannelAlarm &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual FanBridge *createFan(KnxChannelFan &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual DoorWindowBridge *createDoorWindow(KnxChannelDoorWindow &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual SceneBridge *createScene(KnxChannelScene &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual LockBridge *createLock(KnxChannelLock &channel, uint8_t _channelIndex, uint8_t deviceType) override; + virtual MediaBridge *createMedia(KnxChannelMedia &channel, uint8_t _channelIndex, uint8_t deviceType) override; + + virtual const std::string name() override; + virtual void initialize(SmartHomeBridgeModule *bridge) override; + virtual void start(SmartHomeBridgeModule *bridge) override; + virtual void loop() override; + virtual void processInputKo(GroupObject &ko) override; + virtual void getInformation(String &result) override; + virtual bool processCommand(const std::string cmd, bool diagnoseKo) override; + virtual void showHelp() override; + +private: + static esp_err_t deviceTypeCallback(esp_matter::endpoint_t *endpoint, uint32_t deviceTypeId, void *privData); + static esp_err_t attributeCallback(esp_matter::attribute::callback_type_t type, uint16_t endpointId, + uint32_t clusterId, uint32_t attributeId, + esp_matter_attr_val_t *val, void *privData); + + esp_err_t configureNode(esp_matter::node_t *node); + esp_err_t startMatter(); + esp_err_t factoryReset(); +}; diff --git a/src/Media/MatterMedia.cpp b/src/Media/MatterMedia.cpp new file mode 100644 index 0000000..cdb60c3 --- /dev/null +++ b/src/Media/MatterMedia.cpp @@ -0,0 +1,56 @@ +#include "Media/MatterMedia.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include + +MatterMediaBridge::MatterMediaBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterMediaBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::dimmable_light::get_device_type_id(), + static_cast(this)); +} + +void MatterMediaBridge::setPlay(bool play) +{ + if (_device == nullptr) + return; + + matterbridge::reportBool(_device->persistent_info.device_endpoint_id, matterbridge::onOffClusterId, + matterbridge::onOffAttrId, play); +} + +void MatterMediaBridge::setVolume(uint8_t volume) +{ + if (_device == nullptr) + return; + + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::levelControlClusterId, + matterbridge::currentLevelAttrId, volume); +} + +void MatterMediaBridge::setTitle(const char *text) +{ + if (_device == nullptr) + return; + + matterbridge::reportText(_device->persistent_info.device_endpoint_id, matterbridge::basicInformationClusterId, + matterbridge::nodeLabelAttrId, text); +} + +void MatterMediaBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + if (clusterId == matterbridge::onOffClusterId && attributeId == matterbridge::onOffAttrId) + _channel->commandPlay(this, val->val.b); + else if (clusterId == matterbridge::levelControlClusterId && attributeId == matterbridge::currentLevelAttrId) + _channel->commandVolume(this, val->val.u8); +} \ No newline at end of file diff --git a/src/Media/MatterMedia.h b/src/Media/MatterMedia.h new file mode 100644 index 0000000..6400281 --- /dev/null +++ b/src/Media/MatterMedia.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "Media/KnxChannelMedia.h" + +class MatterMediaBridge final : public MatterBridgeDeviceBase, public MediaBridge +{ +public: + explicit MatterMediaBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setPlay(bool play) override; + void setVolume(uint8_t volume) override; + void setTitle(const char *text) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/RGB/MatterRGB.cpp b/src/RGB/MatterRGB.cpp new file mode 100644 index 0000000..146c734 --- /dev/null +++ b/src/RGB/MatterRGB.cpp @@ -0,0 +1,143 @@ +#include "RGB/MatterRGB.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include + +namespace +{ +uint8_t clampHue(uint32_t rgb) +{ + auto red = static_cast((rgb >> 16) & 0xFF) / 255.0f; + auto green = static_cast((rgb >> 8) & 0xFF) / 255.0f; + auto blue = static_cast(rgb & 0xFF) / 255.0f; + auto maxValue = std::max({red, green, blue}); + auto minValue = std::min({red, green, blue}); + auto delta = maxValue - minValue; + + if (delta == 0.0f) + return 0; + + float hue = 0.0f; + if (maxValue == red) + hue = 60.0f * std::fmod(((green - blue) / delta), 6.0f); + else if (maxValue == green) + hue = 60.0f * (((blue - red) / delta) + 2.0f); + else + hue = 60.0f * (((red - green) / delta) + 4.0f); + + if (hue < 0.0f) + hue += 360.0f; + + return static_cast(std::clamp(static_cast(std::lround(hue * 254.0f / 360.0f)), 0, 254)); +} + +uint8_t clampSaturation(uint32_t rgb) +{ + auto red = static_cast((rgb >> 16) & 0xFF) / 255.0f; + auto green = static_cast((rgb >> 8) & 0xFF) / 255.0f; + auto blue = static_cast(rgb & 0xFF) / 255.0f; + auto maxValue = std::max({red, green, blue}); + auto minValue = std::min({red, green, blue}); + auto saturation = maxValue == 0.0f ? 0.0f : ((maxValue - minValue) / maxValue); + return static_cast(std::clamp(static_cast(std::lround(saturation * 254.0f)), 0, 254)); +} + +uint32_t rgbFromHueSaturation(uint8_t hue, uint8_t saturation, uint8_t brightness) +{ + auto h = static_cast(hue) * 360.0f / 254.0f; + auto s = static_cast(saturation) / 254.0f; + auto v = static_cast(brightness) / 254.0f; + + auto c = v * s; + auto x = c * (1.0f - std::fabs(std::fmod(h / 60.0f, 2.0f) - 1.0f)); + auto m = v - c; + + float red = 0.0f; + float green = 0.0f; + float blue = 0.0f; + + if (h < 60.0f) { red = c; green = x; } + else if (h < 120.0f) { red = x; green = c; } + else if (h < 180.0f) { green = c; blue = x; } + else if (h < 240.0f) { green = x; blue = c; } + else if (h < 300.0f) { red = x; blue = c; } + else { red = c; blue = x; } + + auto toByte = [m](float component) { + return static_cast(std::clamp(static_cast(std::lround((component + m) * 255.0f)), 0, 255)); + }; + + return (static_cast(toByte(red)) << 16) | + (static_cast(toByte(green)) << 8) | + static_cast(toByte(blue)); +} +} + +MatterRGBBridge::MatterRGBBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterRGBBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::extended_color_light::get_device_type_id(), + static_cast(this)); + if (_device != nullptr) + setPower(_channel->mainFunctionValue()); +} + +void MatterRGBBridge::setPower(bool on) +{ + if (_device == nullptr) + return; + + matterbridge::reportBool(_device->persistent_info.device_endpoint_id, matterbridge::onOffClusterId, + matterbridge::onOffAttrId, on); +} + +void MatterRGBBridge::setRGB(uint32_t rgb) +{ + if (_device == nullptr) + return; + + _hue = clampHue(rgb); + _saturation = clampSaturation(rgb); + _brightness = std::max(1, static_cast((((rgb >> 16) & 0xFF) + ((rgb >> 8) & 0xFF) + (rgb & 0xFF)) / 3)); + + matterbridge::reportBool(_device->persistent_info.device_endpoint_id, matterbridge::onOffClusterId, + matterbridge::onOffAttrId, rgb != 0); + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::levelControlClusterId, + matterbridge::currentLevelAttrId, _brightness); + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::colorControlClusterId, + matterbridge::currentHueAttrId, _hue); + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::colorControlClusterId, + matterbridge::currentSaturationAttrId, _saturation); +} + +void MatterRGBBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + if (clusterId == matterbridge::onOffClusterId && attributeId == matterbridge::onOffAttrId) + { + _channel->commandPower(this, val->val.b); + } + else if (clusterId == matterbridge::levelControlClusterId && attributeId == matterbridge::currentLevelAttrId) + { + _brightness = val->val.u8; + _channel->commandRGB(this, rgbFromHueSaturation(_hue, _saturation, _brightness)); + } + else if (clusterId == matterbridge::colorControlClusterId) + { + if (attributeId == matterbridge::currentHueAttrId) + _hue = val->val.u8; + else if (attributeId == matterbridge::currentSaturationAttrId) + _saturation = val->val.u8; + + _channel->commandRGB(this, rgbFromHueSaturation(_hue, _saturation, _brightness)); + } +} \ No newline at end of file diff --git a/src/RGB/MatterRGB.h b/src/RGB/MatterRGB.h new file mode 100644 index 0000000..1fe0949 --- /dev/null +++ b/src/RGB/MatterRGB.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "RGB/KnxChannelRGB.h" + +class MatterRGBBridge final : public MatterBridgeDeviceBase, public RGBBridge +{ + uint8_t _hue = 0; + uint8_t _saturation = 0; + uint8_t _brightness = 254; + +public: + explicit MatterRGBBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setRGB(uint32_t rgb) override; + void setPower(bool on) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/Rolladen/MatterRolladen.cpp b/src/Rolladen/MatterRolladen.cpp new file mode 100644 index 0000000..740e5a8 --- /dev/null +++ b/src/Rolladen/MatterRolladen.cpp @@ -0,0 +1,50 @@ +#include "Rolladen/MatterRolladen.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include + +MatterRolladenBridge::MatterRolladenBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterRolladenBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::window_covering_device::get_device_type_id(), + static_cast(this)); + if (_device != nullptr) + setPosition(_channel->mainFunctionValue() ? 100 : 0); +} + +void MatterRolladenBridge::setPosition(uint8_t position) +{ + if (_device == nullptr) + return; + + auto matterPosition = static_cast(std::clamp(position, 0, 100) * 100); + auto currentVal = matterbridge::u16Value(matterPosition); + auto targetVal = matterbridge::u16Value(matterPosition); + esp_matter::attribute::report(_device->persistent_info.device_endpoint_id, matterbridge::windowCoveringClusterId, + matterbridge::currentPositionLiftPercent100thsAttrId, ¤tVal); + esp_matter::attribute::report(_device->persistent_info.device_endpoint_id, matterbridge::windowCoveringClusterId, + matterbridge::targetPositionLiftPercent100thsAttrId, &targetVal); +} + +void MatterRolladenBridge::setMovement(MoveState) +{ +} + +void MatterRolladenBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + if (clusterId == matterbridge::windowCoveringClusterId && + attributeId == matterbridge::targetPositionLiftPercent100thsAttrId) + { + _channel->commandPosition(this, static_cast(val->val.u16 / 100)); + } +} \ No newline at end of file diff --git a/src/Rolladen/MatterRolladen.h b/src/Rolladen/MatterRolladen.h new file mode 100644 index 0000000..9ef5444 --- /dev/null +++ b/src/Rolladen/MatterRolladen.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "Rolladen/KnxChannelRolladen.h" + +class MatterRolladenBridge : public MatterBridgeDeviceBase, public RolladenBridge +{ +public: + explicit MatterRolladenBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setPosition(uint8_t position) override; + void setMovement(MoveState movement) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/Scene/MatterScene.cpp b/src/Scene/MatterScene.cpp new file mode 100644 index 0000000..10620fa --- /dev/null +++ b/src/Scene/MatterScene.cpp @@ -0,0 +1,38 @@ +#include "Scene/MatterScene.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include + +MatterSceneBridge::MatterSceneBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterSceneBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::on_off_light::get_device_type_id(), + static_cast(this)); + if (_device != nullptr) + setActivating(false); +} + +void MatterSceneBridge::setActivating(bool activating) +{ + if (_device == nullptr) + return; + + matterbridge::reportBool(_device->persistent_info.device_endpoint_id, matterbridge::onOffClusterId, + matterbridge::onOffAttrId, activating); +} + +void MatterSceneBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + if (clusterId == matterbridge::onOffClusterId && attributeId == matterbridge::onOffAttrId && val->val.b) + _channel->commandActivate(this); +} \ No newline at end of file diff --git a/src/Scene/MatterScene.h b/src/Scene/MatterScene.h new file mode 100644 index 0000000..d7dd80e --- /dev/null +++ b/src/Scene/MatterScene.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "Scene/KnxChannelScene.h" + +class MatterSceneBridge final : public MatterBridgeDeviceBase, public SceneBridge +{ +public: + explicit MatterSceneBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setActivating(bool activating) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/SmartHomeBridge.share.xml b/src/SmartHomeBridge.share.xml index e920786..d4c3bee 100644 --- a/src/SmartHomeBridge.share.xml +++ b/src/SmartHomeBridge.share.xml @@ -8,6 +8,7 @@ + @@ -457,6 +458,8 @@ + + @@ -471,10 +474,12 @@ - + - - + + + + @@ -483,12 +488,14 @@ + + @@ -519,6 +526,7 @@ + @@ -529,6 +537,15 @@ + + + + + + + + + diff --git a/src/SmartHomeBridgeModule.cpp b/src/SmartHomeBridgeModule.cpp index 3188496..b8e2d42 100644 --- a/src/SmartHomeBridgeModule.cpp +++ b/src/SmartHomeBridgeModule.cpp @@ -6,6 +6,7 @@ #include #include "HomeKitBridge.h" #include "HueBridge.h" +#include "MatterBridge.h" #endif #include "SmartHomeBridgeModule.h" #include "./Switch/KnxChannelSwitch.h" @@ -78,6 +79,13 @@ void SmartHomeBridgeModule::setup() #ifndef SMARTHOMEBRIDGE_DEVICESONLY webServer = new WebServer(webServerPort); + bool matterEnabled = ParamBRI_MatterEnabled; + if (matterEnabled) + { + logDebugP("Matter enabled"); + addBridge(new MatterBridge()); + } + bool homeKitEnabled = ParamBRI_HomeKitEnabled; if (homeKitEnabled) { diff --git a/src/Switch/MatterSwitch.cpp b/src/Switch/MatterSwitch.cpp new file mode 100644 index 0000000..433ddd7 --- /dev/null +++ b/src/Switch/MatterSwitch.cpp @@ -0,0 +1,44 @@ +#include "Switch/MatterSwitch.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include "knxprod.h" +#include + +MatterSwitchBridge::MatterSwitchBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterSwitchBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + uint32_t matterType = esp_matter::endpoint::on_off_switch::get_device_type_id(); + if (ParamBRI_CHDeviceType == 11) + matterType = esp_matter::endpoint::on_off_plugin_unit::get_device_type_id(); + else if (ParamBRI_CHDeviceType == 20) + matterType = esp_matter::endpoint::on_off_light::get_device_type_id(); + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), matterType, + static_cast(this)); + if (_device != nullptr) + setPower(_channel->mainFunctionValue()); +} + +void MatterSwitchBridge::setPower(bool value) +{ + if (_device == nullptr) + return; + + matterbridge::reportBool(_device->persistent_info.device_endpoint_id, matterbridge::onOffClusterId, + matterbridge::onOffAttrId, value); +} + +void MatterSwitchBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + if (clusterId == matterbridge::onOffClusterId && attributeId == matterbridge::onOffAttrId) + _channel->commandPower(this, val->val.b); +} \ No newline at end of file diff --git a/src/Switch/MatterSwitch.h b/src/Switch/MatterSwitch.h new file mode 100644 index 0000000..bccb1f9 --- /dev/null +++ b/src/Switch/MatterSwitch.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "Switch/KnxChannelSwitch.h" + +class MatterSwitchBridge final : public MatterBridgeDeviceBase, public SwitchBridge +{ +public: + explicit MatterSwitchBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setPower(bool value) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file diff --git a/src/Thermostat/MatterThermostat.cpp b/src/Thermostat/MatterThermostat.cpp new file mode 100644 index 0000000..98f9876 --- /dev/null +++ b/src/Thermostat/MatterThermostat.cpp @@ -0,0 +1,135 @@ +#include "Thermostat/MatterThermostat.h" + +#include "MatterBridge.h" +#include "Matter/MatterBridgeCommon.h" + +#include + +MatterThermostatBridge::MatterThermostatBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) +{ +} + +void MatterThermostatBridge::setup(uint8_t _channelIndex) +{ + if (_bridge == nullptr || _bridge->node() == nullptr) + return; + + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + esp_matter::endpoint::thermostat::get_device_type_id(), + static_cast(this)); + if (_device != nullptr) + { + setCurrentTemperature(_currentTemperature); + setTargetTemperature(_targetTemperature); + setMode(_mode); + setCurrentState(_currentState); + } +} + +void MatterThermostatBridge::setTargetTemperature(double temperature) +{ + _targetTemperature = temperature; + if (_device == nullptr) + return; + + matterbridge::reportI16(_device->persistent_info.device_endpoint_id, matterbridge::thermostatClusterId, + matterbridge::heatingSetpointAttrId, + static_cast(std::lround(temperature * 100.0))); + matterbridge::reportI16(_device->persistent_info.device_endpoint_id, matterbridge::thermostatClusterId, + matterbridge::coolingSetpointAttrId, + static_cast(std::lround(temperature * 100.0))); +} + +void MatterThermostatBridge::setCurrentTemperature(double temperature) +{ + _currentTemperature = temperature; + if (_device == nullptr) + return; + + matterbridge::reportI16(_device->persistent_info.device_endpoint_id, matterbridge::thermostatClusterId, + matterbridge::localTemperatureAttrId, + static_cast(std::lround(temperature * 100.0))); +} + +void MatterThermostatBridge::setMode(ThermostatMode mode) +{ + _mode = mode; + if (_device == nullptr) + return; + + auto matterMode = chip::app::Clusters::Thermostat::SystemModeEnum::kOff; + switch (mode) + { + case ThermostatModeHeating: + matterMode = chip::app::Clusters::Thermostat::SystemModeEnum::kHeat; + break; + case ThermostatModeCooling: + matterMode = chip::app::Clusters::Thermostat::SystemModeEnum::kCool; + break; + case ThermostatModeAutoHeatingCooling: + matterMode = chip::app::Clusters::Thermostat::SystemModeEnum::kAuto; + break; + case ThermostatModeOff: + default: + matterMode = chip::app::Clusters::Thermostat::SystemModeEnum::kOff; + break; + } + + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::thermostatClusterId, + matterbridge::systemModeAttrId, static_cast(matterMode)); +} + +void MatterThermostatBridge::setCurrentState(ThermostatCurrentState currentState) +{ + _currentState = currentState; + if (_device == nullptr) + return; + + auto matterState = chip::app::Clusters::Thermostat::ThermostatRunningModeEnum::kOff; + switch (currentState) + { + case ThermostatCurrentStateHeating: + matterState = chip::app::Clusters::Thermostat::ThermostatRunningModeEnum::kHeat; + break; + case ThermostatCurrentStateCooling: + matterState = chip::app::Clusters::Thermostat::ThermostatRunningModeEnum::kCool; + break; + case ThermostatCurrentStateOff: + default: + matterState = chip::app::Clusters::Thermostat::ThermostatRunningModeEnum::kOff; + break; + } + + matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::thermostatClusterId, + matterbridge::runningModeAttrId, static_cast(matterState)); +} + +void MatterThermostatBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) +{ + if (clusterId != matterbridge::thermostatClusterId) + return; + + if (attributeId == matterbridge::heatingSetpointAttrId || attributeId == matterbridge::coolingSetpointAttrId) + { + _channel->commandTargetTemperature(this, static_cast(val->val.i16) / 100.0); + } + else if (attributeId == matterbridge::systemModeAttrId) + { + switch (val->val.u8) + { + case static_cast(chip::app::Clusters::Thermostat::SystemModeEnum::kHeat): + _channel->commandMode(this, ThermostatModeHeating); + break; + case static_cast(chip::app::Clusters::Thermostat::SystemModeEnum::kCool): + _channel->commandMode(this, ThermostatModeCooling); + break; + case static_cast(chip::app::Clusters::Thermostat::SystemModeEnum::kAuto): + _channel->commandMode(this, ThermostatModeAutoHeatingCooling); + break; + default: + _channel->commandMode(this, ThermostatModeOff); + break; + } + } +} \ No newline at end of file diff --git a/src/Thermostat/MatterThermostat.h b/src/Thermostat/MatterThermostat.h new file mode 100644 index 0000000..d6d0f8b --- /dev/null +++ b/src/Thermostat/MatterThermostat.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Matter/MatterBridgeDeviceBase.h" +#include "Thermostat/KnxChannelThermostat.h" + +class MatterThermostatBridge final : public MatterBridgeDeviceBase, public ThermostatBridge +{ + double _targetTemperature = 21.0; + double _currentTemperature = 21.0; + ThermostatMode _mode = ThermostatModeOff; + ThermostatCurrentState _currentState = ThermostatCurrentStateOff; + +public: + explicit MatterThermostatBridge(MatterBridge *bridge); + + void setup(uint8_t channelIndex) override; + void setTargetTemperature(double temperature) override; + void setCurrentTemperature(double temperature) override; + void setMode(ThermostatMode mode) override; + void setCurrentState(ThermostatCurrentState currentState) override; + void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, + uint32_t attributeId, esp_matter_attr_val_t *val) override; +}; \ No newline at end of file From 87471aefda5d372ed6845e5a5c24cb679b25befe Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sat, 16 May 2026 09:01:58 +0200 Subject: [PATCH 02/12] Advertising working (Maybe issue with mDNS, OTA Update deactivated) --- src/Alarm/HomeKitAlarm.cpp | 2 +- src/Dimmer/HomeKitDimmer.cpp | 2 +- src/Display/HomeKitDisplay.cpp | 2 +- src/DoorWindow/HomeKitDoorWindow.cpp | 2 +- src/Espalexa.h | 2 +- src/Fan/HomeKitFan.cpp | 2 +- src/HomeKitBridge.cpp | 2 +- src/HomeKitBridge.h | 2 +- src/Jalousie/HomeKitJalousie.cpp | 2 +- src/Lock/HomeKitLock.cpp | 2 +- src/MatterBridge.cpp | 25 ++++++++++ src/Media/HomeKitMedia.cpp | 2 +- src/RGB/HomeKitRGB.cpp | 2 +- src/Rolladen/HomeKitRolladen.cpp | 2 +- src/Scene/HomeKitScene.cpp | 2 +- src/SmartHomeBridge.templ.xml | 2 +- src/SmartHomeBridgeModule.cpp | 72 +++++++++++++++------------- src/Switch/HomeKitSwitch.cpp | 2 +- src/Thermostat/HomeKitThermostat.cpp | 2 +- 19 files changed, 82 insertions(+), 49 deletions(-) diff --git a/src/Alarm/HomeKitAlarm.cpp b/src/Alarm/HomeKitAlarm.cpp index a52dc44..185a50d 100644 --- a/src/Alarm/HomeKitAlarm.cpp +++ b/src/Alarm/HomeKitAlarm.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitAlarm.h" HomeKitAlarm::HomeKitAlarm(int device) : diff --git a/src/Dimmer/HomeKitDimmer.cpp b/src/Dimmer/HomeKitDimmer.cpp index 3478c3f..77da57a 100644 --- a/src/Dimmer/HomeKitDimmer.cpp +++ b/src/Dimmer/HomeKitDimmer.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitDimmer.h" diff --git a/src/Display/HomeKitDisplay.cpp b/src/Display/HomeKitDisplay.cpp index 66af28d..327f7bc 100644 --- a/src/Display/HomeKitDisplay.cpp +++ b/src/Display/HomeKitDisplay.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitDisplay.h" diff --git a/src/DoorWindow/HomeKitDoorWindow.cpp b/src/DoorWindow/HomeKitDoorWindow.cpp index 517c10a..fb751cb 100644 --- a/src/DoorWindow/HomeKitDoorWindow.cpp +++ b/src/DoorWindow/HomeKitDoorWindow.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitDoorWindow.h" diff --git a/src/Espalexa.h b/src/Espalexa.h index 6513c44..92cd59f 100644 --- a/src/Espalexa.h +++ b/src/Espalexa.h @@ -140,7 +140,7 @@ class Espalexa : public RequestHandler { // construct 'globally unique' Json dict key fitting into signed int inline int encodeLightKey(uint8_t idx) { - static_assert(ESPALEXA_MAXDEVICES <= 128, ""); + // static_assert(ESPALEXA_MAXDEVICES <= 128, ""); return (mac24<<7) | idx; } diff --git a/src/Fan/HomeKitFan.cpp b/src/Fan/HomeKitFan.cpp index 121e2f3..f348453 100644 --- a/src/Fan/HomeKitFan.cpp +++ b/src/Fan/HomeKitFan.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitFan.h" diff --git a/src/HomeKitBridge.cpp b/src/HomeKitBridge.cpp index 40934ed..b5bb529 100644 --- a/src/HomeKitBridge.cpp +++ b/src/HomeKitBridge.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitBridge.h" #include "SmartHomeBridgeModule.h" diff --git a/src/HomeKitBridge.h b/src/HomeKitBridge.h index e66bf5f..07c482e 100644 --- a/src/HomeKitBridge.h +++ b/src/HomeKitBridge.h @@ -1,5 +1,5 @@ #pragma once -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeSpan.h" #include "SmartHomeBridgeModule.h" diff --git a/src/Jalousie/HomeKitJalousie.cpp b/src/Jalousie/HomeKitJalousie.cpp index b1d8e35..4f13f03 100644 --- a/src/Jalousie/HomeKitJalousie.cpp +++ b/src/Jalousie/HomeKitJalousie.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitJalousie.h" diff --git a/src/Lock/HomeKitLock.cpp b/src/Lock/HomeKitLock.cpp index 230d8dd..beb2966 100644 --- a/src/Lock/HomeKitLock.cpp +++ b/src/Lock/HomeKitLock.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitLock.h" diff --git a/src/MatterBridge.cpp b/src/MatterBridge.cpp index 00e9d7f..e2694cb 100644 --- a/src/MatterBridge.cpp +++ b/src/MatterBridge.cpp @@ -20,6 +20,9 @@ #include #include +#include +#include +#include #include @@ -214,6 +217,23 @@ void MatterBridge::initialize(SmartHomeBridgeModule *bridge) { BridgeBase::initialize(bridge); + // Pre-init CHIP before node::create() can register connectivity handlers. + // This avoids PostEventOrDie() on a NULL chip event queue when WiFi events + // arrive between initialize() and start(). + CHIP_ERROR chipErr = chip::Platform::MemoryInit(); + if (chipErr != CHIP_NO_ERROR && chipErr != CHIP_ERROR_INCORRECT_STATE) + { + logErrorP("Matter MemoryInit failed: %" CHIP_ERROR_FORMAT, chipErr.Format()); + return; + } + + chipErr = chip::DeviceLayer::PlatformMgr().InitChipStack(); + if (chipErr != CHIP_NO_ERROR && chipErr != CHIP_ERROR_INCORRECT_STATE) + { + logErrorP("Matter InitChipStack failed: %" CHIP_ERROR_FORMAT, chipErr.Format()); + return; + } + esp_matter::node::config_t config{}; std::string nodeLabel = bridge != nullptr ? bridge->getNameInUTF8() : "SmartHomeBridge"; std::strncpy(config.root_node.basic_information.node_label, nodeLabel.c_str(), @@ -228,6 +248,8 @@ void MatterBridge::initialize(SmartHomeBridgeModule *bridge) if (configureNode(_node) != ESP_OK) logErrorP("Matter bridge initialization failed"); + else + logInfoP("Matter bridge initialized"); } void MatterBridge::start(SmartHomeBridgeModule *bridge) @@ -239,6 +261,9 @@ void MatterBridge::start(SmartHomeBridgeModule *bridge) esp_err_t MatterBridge::startMatter() { + // Arduino/OpenKNX may already run mDNS. ESP-Matter discovery initializes + // its own advertiser and fails if mDNS is already active. + MDNS.end(); return esp_matter::start(nullptr); } diff --git a/src/Media/HomeKitMedia.cpp b/src/Media/HomeKitMedia.cpp index 1e3693c..f69672d 100644 --- a/src/Media/HomeKitMedia.cpp +++ b/src/Media/HomeKitMedia.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitMedia.h" #if 0 diff --git a/src/RGB/HomeKitRGB.cpp b/src/RGB/HomeKitRGB.cpp index 81f4ef8..23c0b44 100644 --- a/src/RGB/HomeKitRGB.cpp +++ b/src/RGB/HomeKitRGB.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitRGB.h" #include "rgbconvert.h" diff --git a/src/Rolladen/HomeKitRolladen.cpp b/src/Rolladen/HomeKitRolladen.cpp index e74c33a..33a3e49 100644 --- a/src/Rolladen/HomeKitRolladen.cpp +++ b/src/Rolladen/HomeKitRolladen.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitRolladen.h" HomeKitRolladen::HomeKitRolladen(int device) : diff --git a/src/Scene/HomeKitScene.cpp b/src/Scene/HomeKitScene.cpp index eb68eb6..7ed206b 100644 --- a/src/Scene/HomeKitScene.cpp +++ b/src/Scene/HomeKitScene.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitScene.h" diff --git a/src/SmartHomeBridge.templ.xml b/src/SmartHomeBridge.templ.xml index 0e8fb59..7ccf9f3 100644 --- a/src/SmartHomeBridge.templ.xml +++ b/src/SmartHomeBridge.templ.xml @@ -162,7 +162,7 @@ - + diff --git a/src/SmartHomeBridgeModule.cpp b/src/SmartHomeBridgeModule.cpp index b8e2d42..9ac6b98 100644 --- a/src/SmartHomeBridgeModule.cpp +++ b/src/SmartHomeBridgeModule.cpp @@ -4,7 +4,9 @@ #ifndef SMARTHOMEBRIDGE_DEVICESONLY #include #include +#ifdef SMARTHOMEBRIDGE_HOMEKIT #include "HomeKitBridge.h" +#endif #include "HueBridge.h" #include "MatterBridge.h" #endif @@ -85,13 +87,19 @@ void SmartHomeBridgeModule::setup() logDebugP("Matter enabled"); addBridge(new MatterBridge()); } + else + { + logDebugP("Matter disabled"); + } +#ifdef SMARTHOMEBRIDGE_HOMEKIT bool homeKitEnabled = ParamBRI_HomeKitEnabled; if (homeKitEnabled) { logDebugP("Homekit enabled"); addBridge(new HomeKitBridge()); } +#endif bool hueEnabled = ParamBRI_HueEnabled; if (hueEnabled) @@ -252,38 +260,38 @@ void SmartHomeBridgeModule::startBridge() "/update", HTTP_POST, [this]() { webServer->sendHeader("Connection", "close"); - webServer->send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); - ESP.restart(); }, - [this]() - { - HTTPUpload &upload = webServer->upload(); - if (upload.status == UPLOAD_FILE_START) - { - Serial.printf("Update: %s\n", upload.filename.c_str()); - if (!Update.begin(UPDATE_SIZE_UNKNOWN)) - { // start with max available size - Update.printError(Serial); - } - } - else if (upload.status == UPLOAD_FILE_WRITE) - { - /* flashing firmware to ESP*/ - if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) - { - Update.printError(Serial); - } - } - else if (upload.status == UPLOAD_FILE_END) - { - if (Update.end(true)) - { // true to set the size to the current progress - Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); - } - else - { - Update.printError(Serial); - } - } + // webServer->send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); + // ESP.restart(); }, + // [this]() + // { + // HTTPUpload &upload = webServer->upload(); + // if (upload.status == UPLOAD_FILE_START) + // { + // Serial.printf("Update: %s\n", upload.filename.c_str()); + // if (!Update.begin(UPDATE_SIZE_UNKNOWN)) + // { // start with max available size + // Update.printError(Serial); + // } + // } + // else if (upload.status == UPLOAD_FILE_WRITE) + // { + // /* flashing firmware to ESP*/ + // if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) + // { + // Update.printError(Serial); + // } + // } + // else if (upload.status == UPLOAD_FILE_END) + // { + // if (Update.end(true)) + // { // true to set the size to the current progress + // Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); + // } + // else + // { + // Update.printError(Serial); + // } + // } }); #endif logDebugP("Initialize briges"); diff --git a/src/Switch/HomeKitSwitch.cpp b/src/Switch/HomeKitSwitch.cpp index 41be6d8..5e72d9c 100644 --- a/src/Switch/HomeKitSwitch.cpp +++ b/src/Switch/HomeKitSwitch.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitSwitch.h" diff --git a/src/Thermostat/HomeKitThermostat.cpp b/src/Thermostat/HomeKitThermostat.cpp index 4bf9e67..128f187 100644 --- a/src/Thermostat/HomeKitThermostat.cpp +++ b/src/Thermostat/HomeKitThermostat.cpp @@ -1,4 +1,4 @@ -#ifndef SMARTHOMEBRIDGE_DEVICESONLY +#if !defined(SMARTHOMEBRIDGE_DEVICESONLY) && defined(SMARTHOMEBRIDGE_HOMEKIT) #include "HomeKitThermostat.h" HomeKitThermostat::HomeKitThermostat(int device) : From 02b26344b1bbd34557ae816309a0c8a1f45c3565 Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sat, 16 May 2026 10:51:52 +0200 Subject: [PATCH 03/12] Setup code --- src/MatterBridge.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/MatterBridge.cpp b/src/MatterBridge.cpp index e2694cb..2d43cda 100644 --- a/src/MatterBridge.cpp +++ b/src/MatterBridge.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,15 @@ namespace { +// Matter setup passcode used during commissioning (Apple Home pairing). +// Allowed format: +// - digits only (numeric), because CHIP API expects uint32_t +// - exactly 8 digits when entered in Home app +// - no letters/special chars +// Note: Some values are forbidden by Matter spec (e.g. 00000000, 11111111, +// 12345678, ...). Keep a non-trivial test value for development. +constexpr uint32_t kMatterSetupPasscode = 20202021; + esp_err_t addSwitchEndpoint(esp_matter::endpoint_t *endpoint) { esp_matter::endpoint::on_off_switch::config_t config; @@ -234,6 +244,20 @@ void MatterBridge::initialize(SmartHomeBridgeModule *bridge) return; } + if (chip::DeviceLayer::CommissionableDataProvider *provider = chip::DeviceLayer::GetCommissionableDataProvider(); + provider != nullptr) + { + chipErr = provider->SetSetupPasscode(kMatterSetupPasscode); + if (chipErr != CHIP_NO_ERROR) + logErrorP("Matter SetSetupPasscode failed: %" CHIP_ERROR_FORMAT, chipErr.Format()); + else + logInfoP("Matter setup code set from source constant"); + } + else + { + logErrorP("Matter CommissionableDataProvider missing"); + } + esp_matter::node::config_t config{}; std::string nodeLabel = bridge != nullptr ? bridge->getNameInUTF8() : "SmartHomeBridge"; std::strncpy(config.root_node.basic_information.node_label, nodeLabel.c_str(), From 62b2d9f32c0db3d19c7041bc9e4fe0d78ad94719 Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sat, 16 May 2026 12:16:04 +0200 Subject: [PATCH 04/12] Switch working --- src/MatterBridge.cpp | 19 ++++++++++++++++++- src/MatterBridge.h | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/MatterBridge.cpp b/src/MatterBridge.cpp index 2d43cda..5ee2fc7 100644 --- a/src/MatterBridge.cpp +++ b/src/MatterBridge.cpp @@ -209,7 +209,9 @@ esp_matter::node_t *MatterBridge::node() const uint16_t MatterBridge::parentEndpointId() const { - return 0; + // Bridged endpoints must be children of the Aggregator endpoint. + // Root endpoint (0) only exposes the bridge accessory itself. + return _aggregatorEndpointId; } const std::string MatterBridge::name() @@ -220,6 +222,21 @@ const std::string MatterBridge::name() esp_err_t MatterBridge::configureNode(esp_matter::node_t *node) { esp_matter::attribute::set_callback(attributeCallback); + + // The bridge topology requires an Aggregator endpoint to act as parent + // for all bridged child endpoints. Without it, create_device() fails + // with "Parent endpoint is invalid". + esp_matter::endpoint::aggregator::config_t aggConfig{}; + esp_matter::endpoint_t *aggEndpoint = esp_matter::endpoint::aggregator::create( + node, &aggConfig, esp_matter::ENDPOINT_FLAG_NONE, nullptr); + if (aggEndpoint == nullptr) + { + logErrorP("Matter: failed to create aggregator endpoint"); + return ESP_FAIL; + } + _aggregatorEndpointId = esp_matter::endpoint::get_id(aggEndpoint); + logInfoP("Matter: aggregator endpoint id=%u", (unsigned)_aggregatorEndpointId); + return esp_matter_bridge::initialize(node, deviceTypeCallback); } diff --git a/src/MatterBridge.h b/src/MatterBridge.h index 2aa3b09..a12744d 100644 --- a/src/MatterBridge.h +++ b/src/MatterBridge.h @@ -7,6 +7,7 @@ class MatterBridge : public BridgeBase { esp_matter::node_t *_node = nullptr; + uint16_t _aggregatorEndpointId = 1; // set after aggregator endpoint is created public: MatterBridge() = default; From 33bb387d1113285e6b12f019e01ffd1773280c04 Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sat, 16 May 2026 14:44:00 +0200 Subject: [PATCH 05/12] Dimmer and Switch working, Pairing Code not working (still default) --- ...pplikationsbeschreibung-SmartHomeBridge.md | 46 +++++--- src/Alarm/MatterAlarm.cpp | 3 + src/Baggages/Help_de/BRI-Apple-HomeKit.md | 1 + src/Baggages/Help_de/BRI-Matter.md | 3 +- ...lungscode.md => BRI-PairingCodeHomeKit.md} | 0 src/Baggages/Help_de/BRI-PairingCodeMatter.md | 13 +++ src/Dimmer/MatterDimmer.cpp | 3 + src/Display/MatterDisplay.cpp | 3 + src/DoorWindow/MatterDoorWindow.cpp | 3 + src/Fan/MatterFan.cpp | 1 + src/HomeKitBridge.cpp | 2 +- src/ISO8859_15ToUTF8.cpp | 14 ++- src/Lock/MatterLock.cpp | 3 + src/Matter/MatterBridgeCommon.h | 72 +++++++++++- src/MatterBridge.cpp | 109 ++++++++++++++---- src/MatterBridge.h | 2 +- src/Media/MatterMedia.cpp | 2 + src/RGB/MatterRGB.cpp | 3 + src/Rolladen/MatterRolladen.cpp | 3 + src/Scene/MatterScene.cpp | 3 + src/SmartHomeBridge.share.xml | 43 ++++--- src/SmartHomeBridgeModule.cpp | 13 +-- src/SmartHomeBridgeModule.h | 3 +- src/Switch/MatterSwitch.cpp | 7 +- src/Thermostat/MatterThermostat.cpp | 1 + 25 files changed, 282 insertions(+), 74 deletions(-) rename src/Baggages/Help_de/{BRI-Kopplungscode.md => BRI-PairingCodeHomeKit.md} (100%) create mode 100644 src/Baggages/Help_de/BRI-PairingCodeMatter.md diff --git a/doc/Applikationsbeschreibung-SmartHomeBridge.md b/doc/Applikationsbeschreibung-SmartHomeBridge.md index 8542eb6..7de324f 100644 --- a/doc/Applikationsbeschreibung-SmartHomeBridge.md +++ b/doc/Applikationsbeschreibung-SmartHomeBridge.md @@ -24,9 +24,24 @@ Folgende Smart Home Systeme werden unterstüzt: ### Matter -Über die Matter Unterstützung können KNX Geräte über matter fähige Geräte gesteuert werden. +Über die Matter Unterstützung können KNX Geräte über Matter fähige Geräte gesteuert werden. Z.B. Google Home, Apple Home + + +#### Kopplungscode + +Über Apple HomeKit oder Google Home können die KNX Geräte gesteuert werden. +Der Kopplungscode wird nur beim erstmaligen Koppeln der Bridge verwendet. +Das Koppeln von weiteren Apps erfolgt in der App, in der die erste Kopplung durchgeführt wurde. + +Koppelung in Apple Home: + +- "Gerät hinzufügen" wählen +- "Weitere Optionen..." wählen +- Nun sollte die Bridge sichtbar sein. Den Kopplungscode der in ETS eingestellt wurde (Standardwert 29710235) eingeben und die Meldung das es sich um ein nicht zertifiziertes Gerät handelt bestätigen. +- Danach den Setup-Wizard für alle Geräte durchführen. + ### Apple HomeKit @@ -41,6 +56,21 @@ Nachdem die Bridge über ETS programmiert wurde, kann sie mit Apple Homekit verb Sollte nachträglich eine Gerätetype sich ändern, z.B. eine Lampe wird zu einer Jalousie, oder eine Untertype ändert sich z.B. ein CO2 Sensor wird zu einem Kontakt, muss zuerst das Gerät deaktiviert werden. Danach die in der Home App warten bis das Gerät verschwunden ist und danach kann das Gerät wieder aktiviert werden. + + +#### Kopplungscode + +Über Apple HomeKit können die KNX Geräte gesteuert werden. Es werden bis zu 149 Geräte unterstützt. Das Limit ergibt sich aus eine Beschränkung der Anzahl der Accessories bei einem HomeKit Gerät mit maximal 150. Eines davon repräsentiert das Bridge Gerät. + +Nachdem die Bridge über ETS programmiert wurde, kann sie mit Apple Homekit verbunden: + +- "Gerät hinzufügen" wählen +- "Weitere Optionen..." wählen +- Nun sollte die Bridge sichtbar sein. Den Kopplungscode der in ETS eingestellt wurde (Standardwert 46637726) eingeben und die Meldung das es sich um ein nicht zertifiziertes Gerät handelt bestätigen. +- Danach den Setup-Wizard für alle Geräte durchführen. + +Sollte nachträglich eine Gerätetype sich ändern, z.B. eine Lampe wird zu einer Jalousie, oder eine Untertype ändert sich z.B. ein CO2 Sensor wird zu einem Kontakt, muss zuerst das Gerät deaktiviert werden. Danach die in der Home App warten bis das Gerät verschwunden ist und danach kann das Gerät wieder aktiviert werden. + ### Hue Emulation (Nur für Alexa) @@ -69,20 +99,6 @@ Die Philips HUE Emulation unterstützt nur Lampen. Geräte mit dieser Einstellun Die Philips HUE Emulation unterstützt nur Lampen. Geräte mit dieser Einstellung können aber als Dimmbare Lampe in HUE emuliert werde, um trotzdem eine Steuerung über die Smart Home Bridge zu Ermöglichen. - -### Kopplungscode - -Über Apple HomeKit können die KNX Geräte gesteuert werden. Es werden bis zu 149 Geräte unterstützt. Das Limit ergibt sich aus eine Beschränkung der Anzahl der Accessories bei einem HomeKit Gerät mit maximal 150. Eines davon repräsentiert das Bridge Gerät. - -Nachdem die Bridge über ETS programmiert wurde, kann sie mit Apple Homekit verbunden: - -- "Gerät hinzufügen" wählen -- "Weitere Optionen..." wählen -- Nun sollte die Bridge sichtbar sein. Den Kopplungscode der in ETS eingestellt wurde (Standardwert 46637726) eingeben und die Meldung das es sich um ein nicht zertifiziertes Gerät handelt bestätigen. -- Danach den Setup-Wizard für alle Geräte durchführen. - -Sollte nachträglich eine Gerätetype sich ändern, z.B. eine Lampe wird zu einer Jalousie, oder eine Untertype ändert sich z.B. ein CO2 Sensor wird zu einem Kontakt, muss zuerst das Gerät deaktiviert werden. Danach die in der Home App warten bis das Gerät verschwunden ist und danach kann das Gerät wieder aktiviert werden. - # Gerätetypen diff --git a/src/Alarm/MatterAlarm.cpp b/src/Alarm/MatterAlarm.cpp index f49eeb9..7cd83f6 100644 --- a/src/Alarm/MatterAlarm.cpp +++ b/src/Alarm/MatterAlarm.cpp @@ -38,7 +38,10 @@ void MatterAlarmBridge::setup(uint8_t _channelIndex) _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), matterType, static_cast(this)); if (_device != nullptr) + { + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); setDetected(_channel->mainFunctionValue()); + } } void MatterAlarmBridge::setDetected(bool detected) diff --git a/src/Baggages/Help_de/BRI-Apple-HomeKit.md b/src/Baggages/Help_de/BRI-Apple-HomeKit.md index 6c9a9a7..29dc8a2 100644 --- a/src/Baggages/Help_de/BRI-Apple-HomeKit.md +++ b/src/Baggages/Help_de/BRI-Apple-HomeKit.md @@ -11,3 +11,4 @@ Nachdem die Bridge über ETS programmiert wurde, kann sie mit Apple Homekit verb Sollte nachträglich eine Gerätetype sich ändern, z.B. eine Lampe wird zu einer Jalousie, oder eine Untertype ändert sich z.B. ein CO2 Sensor wird zu einem Kontakt, muss zuerst das Gerät deaktiviert werden. Danach die in der Home App warten bis das Gerät verschwunden ist und danach kann das Gerät wieder aktiviert werden. + diff --git a/src/Baggages/Help_de/BRI-Matter.md b/src/Baggages/Help_de/BRI-Matter.md index d62f36f..fcd0887 100644 --- a/src/Baggages/Help_de/BRI-Matter.md +++ b/src/Baggages/Help_de/BRI-Matter.md @@ -1,5 +1,6 @@ ### Matter -Über die Matter Unterstützung können KNX Geräte über matter fähige Geräte gesteuert werden. +Über die Matter Unterstützung können KNX Geräte über Matter fähige Geräte gesteuert werden. Z.B. Google Home, Apple Home + diff --git a/src/Baggages/Help_de/BRI-Kopplungscode.md b/src/Baggages/Help_de/BRI-PairingCodeHomeKit.md similarity index 100% rename from src/Baggages/Help_de/BRI-Kopplungscode.md rename to src/Baggages/Help_de/BRI-PairingCodeHomeKit.md diff --git a/src/Baggages/Help_de/BRI-PairingCodeMatter.md b/src/Baggages/Help_de/BRI-PairingCodeMatter.md new file mode 100644 index 0000000..9a47e6a --- /dev/null +++ b/src/Baggages/Help_de/BRI-PairingCodeMatter.md @@ -0,0 +1,13 @@ +### Kopplungscode + +Über Apple HomeKit oder Google Home können die KNX Geräte gesteuert werden. +Der Kopplungscode wird nur beim erstmaligen Koppeln der Bridge verwendet. +Das Koppeln von weiteren Apps erfolgt in der App, in der die erste Kopplung durchgeführt wurde. + +Koppelung in Apple Home: + +- "Gerät hinzufügen" wählen +- "Weitere Optionen..." wählen +- Nun sollte die Bridge sichtbar sein. Den Kopplungscode der in ETS eingestellt wurde (Standardwert 29710235) eingeben und die Meldung das es sich um ein nicht zertifiziertes Gerät handelt bestätigen. +- Danach den Setup-Wizard für alle Geräte durchführen. + diff --git a/src/Dimmer/MatterDimmer.cpp b/src/Dimmer/MatterDimmer.cpp index b51a908..274b643 100644 --- a/src/Dimmer/MatterDimmer.cpp +++ b/src/Dimmer/MatterDimmer.cpp @@ -22,7 +22,10 @@ void MatterDimmerBridge::setup(uint8_t _channelIndex) _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), matterType, static_cast(this)); if (_device != nullptr) + { + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); setBrightness(_channel->mainFunctionValue() ? 254 : 0); + } } void MatterDimmerBridge::setBrightness(uint8_t brightness) diff --git a/src/Display/MatterDisplay.cpp b/src/Display/MatterDisplay.cpp index 9d44775..a706843 100644 --- a/src/Display/MatterDisplay.cpp +++ b/src/Display/MatterDisplay.cpp @@ -49,6 +49,9 @@ void MatterDisplayBridge::setup(uint8_t _channelIndex) static_cast(this)); break; } + + if (_device != nullptr) + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); } void MatterDisplayBridge::setValue(double value) diff --git a/src/DoorWindow/MatterDoorWindow.cpp b/src/DoorWindow/MatterDoorWindow.cpp index b115691..b79baed 100644 --- a/src/DoorWindow/MatterDoorWindow.cpp +++ b/src/DoorWindow/MatterDoorWindow.cpp @@ -18,7 +18,10 @@ void MatterDoorWindowBridge::setup(uint8_t _channelIndex) esp_matter::endpoint::window_covering_device::get_device_type_id(), static_cast(this)); if (_device != nullptr) + { + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); setPosition(_channel->mainFunctionValue() ? 100 : 0); + } } void MatterDoorWindowBridge::setPosition(uint8_t position) diff --git a/src/Fan/MatterFan.cpp b/src/Fan/MatterFan.cpp index 1811ed0..54b8d1a 100644 --- a/src/Fan/MatterFan.cpp +++ b/src/Fan/MatterFan.cpp @@ -20,6 +20,7 @@ void MatterFanBridge::setup(uint8_t _channelIndex) static_cast(this)); if (_device != nullptr) { + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); setPower(_channel->mainFunctionValue()); setAutomatic(_automatic); } diff --git a/src/HomeKitBridge.cpp b/src/HomeKitBridge.cpp index b5bb529..4771f68 100644 --- a/src/HomeKitBridge.cpp +++ b/src/HomeKitBridge.cpp @@ -37,7 +37,7 @@ void HomeKitBridge::initialize(SmartHomeBridgeModule *bridge) _bridge = bridge; homeSpan.useEthernet(); homeSpan.setSerialInputDisable(true); - homeSpan.setPairingCode((const char *)ParamBRI_PairingCode); + homeSpan.setPairingCode(ParamBRI_PairingCodeHomeKitStr.c_str()); homeSpan.setPortNum(8080); homeSpan.begin(Category::Bridges, bridge->getNameInUTF8()); new SpanAccessory(); diff --git a/src/ISO8859_15ToUTF8.cpp b/src/ISO8859_15ToUTF8.cpp index b3676f4..bf22395 100644 --- a/src/ISO8859_15ToUTF8.cpp +++ b/src/ISO8859_15ToUTF8.cpp @@ -432,7 +432,9 @@ const char8_t* cp1252_UTF8[128] = { const char8_t** defaultConverter = DEFAULT_CHAR_MAPPING; -const char* convertISO8859_15ToUTF8(const char* iso) +namespace +{ +const char* convertISO8859_15ToUTF8_impl(const char* iso, bool allwaysMakeCopy = false) { int bufferlength = 0; int i = 0; @@ -452,7 +454,7 @@ const char* convertISO8859_15ToUTF8(const char* iso) break; } } - if (!replacementNeeded) + if (!replacementNeeded && !allwaysMakeCopy) return iso; char* cUtf8 = (char*) HS_MALLOC(bufferlength); size_t bufferIndex = 0; @@ -476,10 +478,16 @@ const char* convertISO8859_15ToUTF8(const char* iso) } return cUtf8; } +} + +const char* convertISO8859_15ToUTF8(const char* iso) +{ + return convertISO8859_15ToUTF8_impl(iso); +} std::string convertISO8859_15ToUTF8_string(const char* c1252) { - const char* converted = convertISO8859_15ToUTF8(c1252); + const char* converted = convertISO8859_15ToUTF8_impl(c1252); std::string result(converted); if (converted != c1252) { diff --git a/src/Lock/MatterLock.cpp b/src/Lock/MatterLock.cpp index 5d7adb8..801183a 100644 --- a/src/Lock/MatterLock.cpp +++ b/src/Lock/MatterLock.cpp @@ -18,7 +18,10 @@ void MatterLockBridge::setup(uint8_t _channelIndex) esp_matter::endpoint::door_lock::get_device_type_id(), static_cast(this)); if (_device != nullptr) + { + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); setLocked(_channel->mainFunctionValue()); + } } void MatterLockBridge::setLocked(bool lock) diff --git a/src/Matter/MatterBridgeCommon.h b/src/Matter/MatterBridgeCommon.h index 835576a..da9f2ee 100644 --- a/src/Matter/MatterBridgeCommon.h +++ b/src/Matter/MatterBridgeCommon.h @@ -3,6 +3,7 @@ #include #include #include +#include #ifdef INADDR_NONE #undef INADDR_NONE @@ -22,6 +23,7 @@ constexpr uint32_t thermostatClusterId = chip::app::Clusters::Thermostat::Id; constexpr uint32_t doorLockClusterId = chip::app::Clusters::DoorLock::Id; constexpr uint32_t windowCoveringClusterId = chip::app::Clusters::WindowCovering::Id; constexpr uint32_t basicInformationClusterId = chip::app::Clusters::BasicInformation::Id; +constexpr uint32_t bridgedDeviceBasicInformationClusterId = chip::app::Clusters::BridgedDeviceBasicInformation::Id; constexpr uint32_t occupancyClusterId = chip::app::Clusters::OccupancySensing::Id; constexpr uint32_t smokeCoAlarmClusterId = chip::app::Clusters::SmokeCoAlarm::Id; constexpr uint32_t humidityClusterId = chip::app::Clusters::RelativeHumidityMeasurement::Id; @@ -57,6 +59,8 @@ constexpr uint32_t measuredHumidityAttrId = chip::app::Clusters::RelativeHumidit constexpr uint32_t measuredIlluminanceAttrId = chip::app::Clusters::IlluminanceMeasurement::Attributes::MeasuredValue::Id; constexpr uint32_t measuredAirQualityAttrId = chip::app::Clusters::AirQuality::Attributes::AirQuality::Id; constexpr uint32_t nodeLabelAttrId = chip::app::Clusters::BasicInformation::Attributes::NodeLabel::Id; +constexpr uint32_t productNameAttrId = chip::app::Clusters::BasicInformation::Attributes::ProductName::Id; +constexpr uint32_t bridgedNodeLabelAttrId = chip::app::Clusters::BridgedDeviceBasicInformation::Attributes::NodeLabel::Id; inline esp_matter_attr_val_t boolValue(bool value) { @@ -96,33 +100,99 @@ inline esp_matter_attr_val_t textValue(const char *value) return esp_matter_char_str(copy.data(), static_cast(copy.size())); } +inline bool hasAttribute(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId) +{ + if (esp_matter::cluster::get(endpointId, clusterId) == nullptr) + return false; + + return esp_matter::attribute::get(endpointId, clusterId, attributeId) != nullptr; +} + inline esp_err_t reportBool(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, bool value) { + if (!hasAttribute(endpointId, clusterId, attributeId)) + return ESP_ERR_NOT_FOUND; + auto reported = boolValue(value); return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); } inline esp_err_t reportU8(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, uint8_t value) { - auto reported = u8Value(value); + if (!hasAttribute(endpointId, clusterId, attributeId)) + return ESP_ERR_NOT_FOUND; + + auto attribute = esp_matter::attribute::get(endpointId, clusterId, attributeId); + + esp_matter_attr_val_t current{}; + esp_matter_attr_val_t reported = u8Value(value); + if (esp_matter::attribute::get_val(attribute, ¤t) == ESP_OK && + current.type == ESP_MATTER_VAL_TYPE_NULLABLE_UINT8) + { + reported = esp_matter_nullable_uint8(value); + } + return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); } inline esp_err_t reportI16(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, int16_t value) { + if (!hasAttribute(endpointId, clusterId, attributeId)) + return ESP_ERR_NOT_FOUND; + auto reported = i16Value(value); return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); } inline esp_err_t reportU16(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, uint16_t value) { + if (!hasAttribute(endpointId, clusterId, attributeId)) + return ESP_ERR_NOT_FOUND; + auto reported = u16Value(value); return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); } inline esp_err_t reportText(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, const char *value) { + if (!hasAttribute(endpointId, clusterId, attributeId)) + return ESP_ERR_NOT_FOUND; + auto reported = textValue(value); return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); } + +inline esp_err_t setDeviceName(esp_matter_bridge::device_t *device, const char *name) +{ + if (device == nullptr || name == nullptr) + return ESP_ERR_INVALID_ARG; + + uint16_t endpointId = device->persistent_info.device_endpoint_id; + if (hasAttribute(endpointId, bridgedDeviceBasicInformationClusterId, bridgedNodeLabelAttrId)) + { + return reportText(endpointId, bridgedDeviceBasicInformationClusterId, bridgedNodeLabelAttrId, name); + } + if (hasAttribute(endpointId, basicInformationClusterId, nodeLabelAttrId)) + { + return reportText(endpointId, basicInformationClusterId, nodeLabelAttrId, name); + } + + return ESP_ERR_NOT_FOUND; +} + +inline esp_err_t setEndpointName(uint16_t endpointId, const char *name) +{ + if (name == nullptr) + return ESP_ERR_INVALID_ARG; + + return reportText(endpointId, basicInformationClusterId, nodeLabelAttrId, name); +} + +inline esp_err_t setEndpointProductName(uint16_t endpointId, const char *name) +{ + if (name == nullptr) + return ESP_ERR_INVALID_ARG; + + return reportText(endpointId, basicInformationClusterId, productNameAttrId, name); +} } \ No newline at end of file diff --git a/src/MatterBridge.cpp b/src/MatterBridge.cpp index 5ee2fc7..fb54e06 100644 --- a/src/MatterBridge.cpp +++ b/src/MatterBridge.cpp @@ -2,6 +2,7 @@ #include "SmartHomeBridgeModule.h" +#include "Matter/MatterBridgeCommon.h" #include "Matter/MatterBridgeDeviceBase.h" #include "Switch/MatterSwitch.h" @@ -22,21 +23,44 @@ #include #include #include +#include #include +#include #include +#include #include namespace { -// Matter setup passcode used during commissioning (Apple Home pairing). -// Allowed format: -// - digits only (numeric), because CHIP API expects uint32_t -// - exactly 8 digits when entered in Home app -// - no letters/special chars -// Note: Some values are forbidden by Matter spec (e.g. 00000000, 11111111, -// 12345678, ...). Keep a non-trivial test value for development. -constexpr uint32_t kMatterSetupPasscode = 20202021; +constexpr uint32_t kDefaultMatterSetupPasscode = 20202021; + +bool tryParseMatterPasscode(const std::string &text, uint32_t &out) +{ + if (text.size() != 8) + return false; + + for (unsigned char c : text) + { + if (!std::isdigit(c)) + return false; + } + + try + { + size_t consumed = 0; + unsigned long parsed = std::stoul(text, &consumed, 10); + if (consumed != text.size()) + return false; + out = static_cast(parsed); + return true; + } + catch (...) + { + return false; + } +} + esp_err_t addSwitchEndpoint(esp_matter::endpoint_t *endpoint) { @@ -209,8 +233,6 @@ esp_matter::node_t *MatterBridge::node() const uint16_t MatterBridge::parentEndpointId() const { - // Bridged endpoints must be children of the Aggregator endpoint. - // Root endpoint (0) only exposes the bridge accessory itself. return _aggregatorEndpointId; } @@ -223,20 +245,16 @@ esp_err_t MatterBridge::configureNode(esp_matter::node_t *node) { esp_matter::attribute::set_callback(attributeCallback); - // The bridge topology requires an Aggregator endpoint to act as parent - // for all bridged child endpoints. Without it, create_device() fails - // with "Parent endpoint is invalid". - esp_matter::endpoint::aggregator::config_t aggConfig{}; - esp_matter::endpoint_t *aggEndpoint = esp_matter::endpoint::aggregator::create( - node, &aggConfig, esp_matter::ENDPOINT_FLAG_NONE, nullptr); - if (aggEndpoint == nullptr) + esp_matter::endpoint::aggregator::config_t aggregatorConfig{}; + esp_matter::endpoint_t *aggregatorEndpoint = + esp_matter::endpoint::aggregator::create(node, &aggregatorConfig, esp_matter::ENDPOINT_FLAG_NONE, nullptr); + if (aggregatorEndpoint == nullptr) { - logErrorP("Matter: failed to create aggregator endpoint"); + logErrorP("Matter aggregator endpoint creation failed"); return ESP_FAIL; } - _aggregatorEndpointId = esp_matter::endpoint::get_id(aggEndpoint); - logInfoP("Matter: aggregator endpoint id=%u", (unsigned)_aggregatorEndpointId); + _aggregatorEndpointId = esp_matter::endpoint::get_id(aggregatorEndpoint); return esp_matter_bridge::initialize(node, deviceTypeCallback); } @@ -244,6 +262,44 @@ void MatterBridge::initialize(SmartHomeBridgeModule *bridge) { BridgeBase::initialize(bridge); + // Resolve intended passcode + uint32_t matterSetupPasscode = kDefaultMatterSetupPasscode; + if (!tryParseMatterPasscode(ParamBRI_PairingCodeMatterStr, matterSetupPasscode)) + { + logErrorP("Matter setup code invalid ('%s'), using fallback %u", + ParamBRI_PairingCodeMatterStr.c_str(), (unsigned)kDefaultMatterSetupPasscode); + matterSetupPasscode = kDefaultMatterSetupPasscode; + } + else if (!chip::PayloadContents::IsValidSetupPIN(matterSetupPasscode)) + { + logErrorP("Matter setup code not allowed by Matter rules (%u), using fallback %u", + (unsigned)matterSetupPasscode, (unsigned)kDefaultMatterSetupPasscode); + matterSetupPasscode = kDefaultMatterSetupPasscode; + } + + // NVS must be updated BEFORE InitChipStack(), because the + // LegacyTemporaryCommissionableDataProvider reads and caches salt/verifier + // from NVS during ConfigurationManagerImpl::Init() (called by InitChipStack). + // Clearing the old verifier here forces CHIP to recompute it from the new passcode. + { + using cfg = chip::DeviceLayer::Internal::ESP32Config; + uint32_t nvsPasscode = 0; + bool nvsHasPin = (cfg::ReadConfigValue(cfg::kConfigKey_SetupPinCode, nvsPasscode) == CHIP_NO_ERROR); + if (!nvsHasPin || nvsPasscode != matterSetupPasscode) + { + logInfoP("Matter passcode changed (%u -> %u): updating NVS before CHIP init", + (unsigned)nvsPasscode, (unsigned)matterSetupPasscode); + cfg::WriteConfigValue(cfg::kConfigKey_SetupPinCode, matterSetupPasscode); + cfg::ClearConfigValue(cfg::kConfigKey_Spake2pVerifier); + cfg::ClearConfigValue(cfg::kConfigKey_Spake2pSalt); + cfg::ClearConfigValue(cfg::kConfigKey_Spake2pIterationCount); + } + else + { + logInfoP("Matter passcode unchanged (%u), NVS kept", (unsigned)matterSetupPasscode); + } + } + // Pre-init CHIP before node::create() can register connectivity handlers. // This avoids PostEventOrDie() on a NULL chip event queue when WiFi events // arrive between initialize() and start(). @@ -261,14 +317,16 @@ void MatterBridge::initialize(SmartHomeBridgeModule *bridge) return; } + // Belt-and-suspenders: also set the in-memory passcode on the provider + // that was registered by InitChipStack. if (chip::DeviceLayer::CommissionableDataProvider *provider = chip::DeviceLayer::GetCommissionableDataProvider(); provider != nullptr) { - chipErr = provider->SetSetupPasscode(kMatterSetupPasscode); + chipErr = provider->SetSetupPasscode(matterSetupPasscode); if (chipErr != CHIP_NO_ERROR) - logErrorP("Matter SetSetupPasscode failed: %" CHIP_ERROR_FORMAT, chipErr.Format()); + logInfoP("Matter SetSetupPasscode (in-memory) not supported: %" CHIP_ERROR_FORMAT, chipErr.Format()); else - logInfoP("Matter setup code set from source constant"); + logInfoP("Matter setup code set: %u", (unsigned)matterSetupPasscode); } else { @@ -290,7 +348,9 @@ void MatterBridge::initialize(SmartHomeBridgeModule *bridge) if (configureNode(_node) != ESP_OK) logErrorP("Matter bridge initialization failed"); else - logInfoP("Matter bridge initialized"); + { + logInfoP("Matter bridge initialized (aggregator endpoint=%u)", (unsigned)_aggregatorEndpointId); + } } void MatterBridge::start(SmartHomeBridgeModule *bridge) @@ -327,6 +387,7 @@ bool MatterBridge::processCommand(const std::string cmd, bool) if (cmd == "mr") { factoryReset(); + logInfoP("Matter factory reset triggered"); return true; } return false; diff --git a/src/MatterBridge.h b/src/MatterBridge.h index a12744d..0e53147 100644 --- a/src/MatterBridge.h +++ b/src/MatterBridge.h @@ -7,7 +7,7 @@ class MatterBridge : public BridgeBase { esp_matter::node_t *_node = nullptr; - uint16_t _aggregatorEndpointId = 1; // set after aggregator endpoint is created + uint16_t _aggregatorEndpointId = 0; public: MatterBridge() = default; diff --git a/src/Media/MatterMedia.cpp b/src/Media/MatterMedia.cpp index cdb60c3..c394856 100644 --- a/src/Media/MatterMedia.cpp +++ b/src/Media/MatterMedia.cpp @@ -17,6 +17,8 @@ void MatterMediaBridge::setup(uint8_t _channelIndex) _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), esp_matter::endpoint::dimmable_light::get_device_type_id(), static_cast(this)); + if (_device != nullptr) + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); } void MatterMediaBridge::setPlay(bool play) diff --git a/src/RGB/MatterRGB.cpp b/src/RGB/MatterRGB.cpp index 146c734..902683d 100644 --- a/src/RGB/MatterRGB.cpp +++ b/src/RGB/MatterRGB.cpp @@ -88,7 +88,10 @@ void MatterRGBBridge::setup(uint8_t _channelIndex) esp_matter::endpoint::extended_color_light::get_device_type_id(), static_cast(this)); if (_device != nullptr) + { + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); setPower(_channel->mainFunctionValue()); + } } void MatterRGBBridge::setPower(bool on) diff --git a/src/Rolladen/MatterRolladen.cpp b/src/Rolladen/MatterRolladen.cpp index 740e5a8..2f47a4a 100644 --- a/src/Rolladen/MatterRolladen.cpp +++ b/src/Rolladen/MatterRolladen.cpp @@ -18,7 +18,10 @@ void MatterRolladenBridge::setup(uint8_t _channelIndex) esp_matter::endpoint::window_covering_device::get_device_type_id(), static_cast(this)); if (_device != nullptr) + { + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); setPosition(_channel->mainFunctionValue() ? 100 : 0); + } } void MatterRolladenBridge::setPosition(uint8_t position) diff --git a/src/Scene/MatterScene.cpp b/src/Scene/MatterScene.cpp index 10620fa..205b950 100644 --- a/src/Scene/MatterScene.cpp +++ b/src/Scene/MatterScene.cpp @@ -18,7 +18,10 @@ void MatterSceneBridge::setup(uint8_t _channelIndex) esp_matter::endpoint::on_off_light::get_device_type_id(), static_cast(this)); if (_device != nullptr) + { + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); setActivating(false); + } } void MatterSceneBridge::setActivating(bool activating) diff --git a/src/SmartHomeBridge.share.xml b/src/SmartHomeBridge.share.xml index d4c3bee..5a3a886 100644 --- a/src/SmartHomeBridge.share.xml +++ b/src/SmartHomeBridge.share.xml @@ -77,8 +77,11 @@ - - + + + + + @@ -454,7 +457,7 @@ - + @@ -462,9 +465,11 @@ - - - + + + + + @@ -479,7 +484,7 @@ - + @@ -496,6 +501,9 @@ + + + @@ -505,10 +513,6 @@ - - - - @@ -571,12 +575,23 @@ + + + + + + + + + + + - + - - + + diff --git a/src/SmartHomeBridgeModule.cpp b/src/SmartHomeBridgeModule.cpp index 9ac6b98..ba6a8da 100644 --- a/src/SmartHomeBridgeModule.cpp +++ b/src/SmartHomeBridgeModule.cpp @@ -46,18 +46,9 @@ const std::string SmartHomeBridgeModule::version() return MODULE_SmartHomeBridge_Version; } -SmartHomeBridgeModule::~SmartHomeBridgeModule() -{ - if (_utf8Name != nullptr) - { - delete _utf8Name; - _utf8Name = nullptr; - } -} - const char *SmartHomeBridgeModule::getNameInUTF8() { - return _utf8Name; + return _utf8Name.c_str(); } void SmartHomeBridgeModule::setup(bool configured) @@ -76,7 +67,7 @@ void SmartHomeBridgeModule::setup() #else logDebugP("Setup Bridge"); #endif - _utf8Name = convertISO8859_15ToUTF8((const char *)ParamBRI_BridgeName); + _utf8Name = convertISO8859_15ToUTF8_string((const char *)ParamBRI_BridgeName); #ifndef SMARTHOMEBRIDGE_DEVICESONLY webServer = new WebServer(webServerPort); diff --git a/src/SmartHomeBridgeModule.h b/src/SmartHomeBridgeModule.h index 939298b..823d4e1 100644 --- a/src/SmartHomeBridgeModule.h +++ b/src/SmartHomeBridgeModule.h @@ -19,7 +19,7 @@ class SmartHomeBridgeModule : public ChannelOwnerModule const uint16_t webServerPort = 80; WebServer* webServer = nullptr; #endif - const char* _utf8Name = nullptr; + std::string _utf8Name = ""; DynamicPointerArray* bridgeInterfaces = nullptr; volatile bool started = false; void startBridge(); @@ -38,7 +38,6 @@ class SmartHomeBridgeModule : public ChannelOwnerModule virtual OpenKNX::Channel* createChannel(uint8_t _channelIndex /* this parameter is used in macros, do not rename */); public: SmartHomeBridgeModule(); - ~SmartHomeBridgeModule(); KnxChannelBase* getChannel(uint8_t channelIndex); void addBridge(BridgeBase* bridge); const char* getNameInUTF8(); diff --git a/src/Switch/MatterSwitch.cpp b/src/Switch/MatterSwitch.cpp index 433ddd7..7e22116 100644 --- a/src/Switch/MatterSwitch.cpp +++ b/src/Switch/MatterSwitch.cpp @@ -15,7 +15,9 @@ void MatterSwitchBridge::setup(uint8_t _channelIndex) if (_bridge == nullptr || _bridge->node() == nullptr) return; - uint32_t matterType = esp_matter::endpoint::on_off_switch::get_device_type_id(); + // Use actuator device types for bridged KNX channels. on_off_switch is a + // controller type and is not suitable for Apple Home accessories. + uint32_t matterType = esp_matter::endpoint::on_off_light::get_device_type_id(); if (ParamBRI_CHDeviceType == 11) matterType = esp_matter::endpoint::on_off_plugin_unit::get_device_type_id(); else if (ParamBRI_CHDeviceType == 20) @@ -24,7 +26,10 @@ void MatterSwitchBridge::setup(uint8_t _channelIndex) _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), matterType, static_cast(this)); if (_device != nullptr) + { + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); setPower(_channel->mainFunctionValue()); + } } void MatterSwitchBridge::setPower(bool value) diff --git a/src/Thermostat/MatterThermostat.cpp b/src/Thermostat/MatterThermostat.cpp index 98f9876..b0fc131 100644 --- a/src/Thermostat/MatterThermostat.cpp +++ b/src/Thermostat/MatterThermostat.cpp @@ -19,6 +19,7 @@ void MatterThermostatBridge::setup(uint8_t _channelIndex) static_cast(this)); if (_device != nullptr) { + matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); setCurrentTemperature(_currentTemperature); setTargetTemperature(_targetTemperature); setMode(_mode); From def14514cd379bb771e2a1ee659f5e625767edc6 Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sat, 16 May 2026 15:05:04 +0200 Subject: [PATCH 06/12] Provisioning provider replaced to fix ignored setup code from ETS --- src/MatterBridge.cpp | 153 +++++++++++++++++++++++++++++++++++++++++-- src/MatterBridge.h | 1 + 2 files changed, 147 insertions(+), 7 deletions(-) diff --git a/src/MatterBridge.cpp b/src/MatterBridge.cpp index fb54e06..0907af7 100644 --- a/src/MatterBridge.cpp +++ b/src/MatterBridge.cpp @@ -24,9 +24,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -35,6 +37,112 @@ namespace { constexpr uint32_t kDefaultMatterSetupPasscode = 20202021; +class RuntimeCommissionableDataProvider : public chip::DeviceLayer::CommissionableDataProvider +{ +public: + CHIP_ERROR SetBackingProvider(chip::DeviceLayer::CommissionableDataProvider *provider) + { + _backingProvider = provider; + return CHIP_NO_ERROR; + } + + CHIP_ERROR SetRuntimePasscode(uint32_t passcode) + { + if (!chip::PayloadContents::IsValidSetupPIN(passcode)) + return CHIP_ERROR_INVALID_ARGUMENT; + + _passcode = passcode; + for (size_t i = 0; i < sizeof(_salt); ++i) + _salt[i] = static_cast(esp_random() & 0xFF); + + chip::Crypto::Spake2pVerifier verifier; + CHIP_ERROR err = verifier.Generate(_iterationCount, chip::ByteSpan(_salt, sizeof(_salt)), _passcode); + if (err != CHIP_NO_ERROR) + return err; + + chip::MutableByteSpan serialized(_verifier); + err = verifier.Serialize(serialized); + if (err != CHIP_NO_ERROR) + return err; + + _verifierLen = serialized.size(); + + _isInitialized = true; + return CHIP_NO_ERROR; + } + + CHIP_ERROR GetSetupDiscriminator(uint16_t &setupDiscriminator) override + { + if (_backingProvider != nullptr) + return _backingProvider->GetSetupDiscriminator(setupDiscriminator); + + setupDiscriminator = 3840; + return CHIP_NO_ERROR; + } + + CHIP_ERROR SetSetupDiscriminator(uint16_t setupDiscriminator) override + { + if (_backingProvider != nullptr) + return _backingProvider->SetSetupDiscriminator(setupDiscriminator); + + (void)setupDiscriminator; + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + CHIP_ERROR GetSpake2pIterationCount(uint32_t &iterationCount) override + { + VerifyOrReturnError(_isInitialized, CHIP_ERROR_INCORRECT_STATE); + iterationCount = _iterationCount; + return CHIP_NO_ERROR; + } + + CHIP_ERROR GetSpake2pSalt(chip::MutableByteSpan &saltBuf) override + { + VerifyOrReturnError(_isInitialized, CHIP_ERROR_INCORRECT_STATE); + if (saltBuf.size() < sizeof(_salt)) + return CHIP_ERROR_BUFFER_TOO_SMALL; + + memcpy(saltBuf.data(), _salt, sizeof(_salt)); + saltBuf.reduce_size(sizeof(_salt)); + return CHIP_NO_ERROR; + } + + CHIP_ERROR GetSpake2pVerifier(chip::MutableByteSpan &verifierBuf, size_t &outVerifierLen) override + { + VerifyOrReturnError(_isInitialized, CHIP_ERROR_INCORRECT_STATE); + outVerifierLen = _verifierLen; + if (verifierBuf.size() < outVerifierLen) + return CHIP_ERROR_BUFFER_TOO_SMALL; + + memcpy(verifierBuf.data(), _verifier, outVerifierLen); + verifierBuf.reduce_size(outVerifierLen); + return CHIP_NO_ERROR; + } + + CHIP_ERROR GetSetupPasscode(uint32_t &setupPasscode) override + { + VerifyOrReturnError(_isInitialized, CHIP_ERROR_INCORRECT_STATE); + setupPasscode = _passcode; + return CHIP_NO_ERROR; + } + + CHIP_ERROR SetSetupPasscode(uint32_t setupPasscode) override + { + return SetRuntimePasscode(setupPasscode); + } + +private: + chip::DeviceLayer::CommissionableDataProvider *_backingProvider = nullptr; + uint32_t _passcode = kDefaultMatterSetupPasscode; + uint32_t _iterationCount = 10000; + uint8_t _salt[16]{}; + uint8_t _verifier[chip::Crypto::kSpake2p_VerifierSerialized_Length]{}; + size_t _verifierLen = 0; + bool _isInitialized = false; +}; + +RuntimeCommissionableDataProvider sRuntimeCommissionableDataProvider; + bool tryParseMatterPasscode(const std::string &text, uint32_t &out) { if (text.size() != 8) @@ -276,6 +384,7 @@ void MatterBridge::initialize(SmartHomeBridgeModule *bridge) (unsigned)matterSetupPasscode, (unsigned)kDefaultMatterSetupPasscode); matterSetupPasscode = kDefaultMatterSetupPasscode; } + _matterSetupPasscode = matterSetupPasscode; // NVS must be updated BEFORE InitChipStack(), because the // LegacyTemporaryCommissionableDataProvider reads and caches salt/verifier @@ -285,18 +394,18 @@ void MatterBridge::initialize(SmartHomeBridgeModule *bridge) using cfg = chip::DeviceLayer::Internal::ESP32Config; uint32_t nvsPasscode = 0; bool nvsHasPin = (cfg::ReadConfigValue(cfg::kConfigKey_SetupPinCode, nvsPasscode) == CHIP_NO_ERROR); - if (!nvsHasPin || nvsPasscode != matterSetupPasscode) + if (!nvsHasPin || nvsPasscode != _matterSetupPasscode) { logInfoP("Matter passcode changed (%u -> %u): updating NVS before CHIP init", - (unsigned)nvsPasscode, (unsigned)matterSetupPasscode); - cfg::WriteConfigValue(cfg::kConfigKey_SetupPinCode, matterSetupPasscode); + (unsigned)nvsPasscode, (unsigned)_matterSetupPasscode); + cfg::WriteConfigValue(cfg::kConfigKey_SetupPinCode, _matterSetupPasscode); cfg::ClearConfigValue(cfg::kConfigKey_Spake2pVerifier); cfg::ClearConfigValue(cfg::kConfigKey_Spake2pSalt); cfg::ClearConfigValue(cfg::kConfigKey_Spake2pIterationCount); } else { - logInfoP("Matter passcode unchanged (%u), NVS kept", (unsigned)matterSetupPasscode); + logInfoP("Matter passcode unchanged (%u), NVS kept", (unsigned)_matterSetupPasscode); } } @@ -322,11 +431,11 @@ void MatterBridge::initialize(SmartHomeBridgeModule *bridge) if (chip::DeviceLayer::CommissionableDataProvider *provider = chip::DeviceLayer::GetCommissionableDataProvider(); provider != nullptr) { - chipErr = provider->SetSetupPasscode(matterSetupPasscode); + chipErr = provider->SetSetupPasscode(_matterSetupPasscode); if (chipErr != CHIP_NO_ERROR) logInfoP("Matter SetSetupPasscode (in-memory) not supported: %" CHIP_ERROR_FORMAT, chipErr.Format()); else - logInfoP("Matter setup code set: %u", (unsigned)matterSetupPasscode); + logInfoP("Matter setup code set: %u", (unsigned)_matterSetupPasscode); } else { @@ -365,7 +474,36 @@ esp_err_t MatterBridge::startMatter() // Arduino/OpenKNX may already run mDNS. ESP-Matter discovery initializes // its own advertiser and fails if mDNS is already active. MDNS.end(); - return esp_matter::start(nullptr); + + esp_err_t err = esp_matter::start(nullptr); + if (err != ESP_OK) + return err; + + if (chip::DeviceLayer::CommissionableDataProvider *provider = chip::DeviceLayer::GetCommissionableDataProvider(); + provider != nullptr) + { + CHIP_ERROR chipErr = sRuntimeCommissionableDataProvider.SetBackingProvider(provider); + if (chipErr != CHIP_NO_ERROR) + logErrorP("Matter runtime provider backing set failed: %" CHIP_ERROR_FORMAT, chipErr.Format()); + + chipErr = sRuntimeCommissionableDataProvider.SetRuntimePasscode(_matterSetupPasscode); + if (chipErr != CHIP_NO_ERROR) + logErrorP("Matter runtime provider passcode setup failed: %" CHIP_ERROR_FORMAT, chipErr.Format()); + else + { + chip::DeviceLayer::SetCommissionableDataProvider(&sRuntimeCommissionableDataProvider); + logInfoP("Matter runtime commissionable provider installed"); + } + + uint32_t effectivePasscode = 0; + chipErr = chip::DeviceLayer::GetCommissionableDataProvider()->GetSetupPasscode(effectivePasscode); + if (chipErr == CHIP_NO_ERROR) + logInfoP("Matter effective setup code after start: %u", (unsigned)effectivePasscode); + else + logInfoP("Matter GetSetupPasscode not supported after start: %" CHIP_ERROR_FORMAT, chipErr.Format()); + } + + return ESP_OK; } void MatterBridge::loop() @@ -388,6 +526,7 @@ bool MatterBridge::processCommand(const std::string cmd, bool) { factoryReset(); logInfoP("Matter factory reset triggered"); + openknx.restart(); return true; } return false; diff --git a/src/MatterBridge.h b/src/MatterBridge.h index 0e53147..aae29dd 100644 --- a/src/MatterBridge.h +++ b/src/MatterBridge.h @@ -8,6 +8,7 @@ class MatterBridge : public BridgeBase { esp_matter::node_t *_node = nullptr; uint16_t _aggregatorEndpointId = 0; + uint32_t _matterSetupPasscode = 20202021; public: MatterBridge() = default; From 9f20589c8397ace0adaca9da7a6fff274aec9459 Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sat, 16 May 2026 16:24:20 +0200 Subject: [PATCH 07/12] Device name setting working --- src/Display/MatterDisplay.cpp | 2 + src/Matter/MatterBridgeCommon.h | 83 ++++++++++++++++++++++++----- src/Matter/MatterBridgeDeviceBase.h | 3 +- src/Media/MatterMedia.cpp | 2 + 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/src/Display/MatterDisplay.cpp b/src/Display/MatterDisplay.cpp index a706843..58aaca3 100644 --- a/src/Display/MatterDisplay.cpp +++ b/src/Display/MatterDisplay.cpp @@ -51,7 +51,9 @@ void MatterDisplayBridge::setup(uint8_t _channelIndex) } if (_device != nullptr) + { matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); + } } void MatterDisplayBridge::setValue(double value) diff --git a/src/Matter/MatterBridgeCommon.h b/src/Matter/MatterBridgeCommon.h index da9f2ee..691f7e1 100644 --- a/src/Matter/MatterBridgeCommon.h +++ b/src/Matter/MatterBridgeCommon.h @@ -11,6 +11,7 @@ #include #include +#include #include namespace matterbridge @@ -96,8 +97,8 @@ inline esp_matter_attr_val_t u16Value(uint16_t value) inline esp_matter_attr_val_t textValue(const char *value) { - std::string copy = value != nullptr ? value : ""; - return esp_matter_char_str(copy.data(), static_cast(copy.size())); + const char *safeValue = value != nullptr ? value : ""; + return esp_matter_char_str(const_cast(safeValue), static_cast(std::strlen(safeValue))); } inline bool hasAttribute(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId) @@ -162,20 +163,78 @@ inline esp_err_t reportText(uint16_t endpointId, uint32_t clusterId, uint32_t at return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); } -inline esp_err_t setDeviceName(esp_matter_bridge::device_t *device, const char *name) +inline esp_err_t updateText(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, const char *value) { - if (device == nullptr || name == nullptr) - return ESP_ERR_INVALID_ARG; + if (!hasAttribute(endpointId, clusterId, attributeId)) + return ESP_ERR_NOT_FOUND; - uint16_t endpointId = device->persistent_info.device_endpoint_id; - if (hasAttribute(endpointId, bridgedDeviceBasicInformationClusterId, bridgedNodeLabelAttrId)) + auto updated = textValue(value); + return esp_matter::attribute::update(endpointId, clusterId, attributeId, &updated); +} + +inline void ensureOptionalNameAttributes(uint16_t endpointId) +{ + static char empty[] = ""; + + auto bridgedCluster = esp_matter::cluster::get(endpointId, bridgedDeviceBasicInformationClusterId); + if (bridgedCluster != nullptr) { - return reportText(endpointId, bridgedDeviceBasicInformationClusterId, bridgedNodeLabelAttrId, name); + if (esp_matter::attribute::get(endpointId, bridgedDeviceBasicInformationClusterId, bridgedNodeLabelAttrId) == nullptr) + esp_matter::cluster::bridged_device_basic_information::attribute::create_node_label(bridgedCluster, empty, 0); + + if (esp_matter::attribute::get(endpointId, bridgedDeviceBasicInformationClusterId, productNameAttrId) == nullptr) + esp_matter::cluster::bridged_device_basic_information::attribute::create_product_name(bridgedCluster, empty, 0); } - if (hasAttribute(endpointId, basicInformationClusterId, nodeLabelAttrId)) + + auto basicCluster = esp_matter::cluster::get(endpointId, basicInformationClusterId); + if (basicCluster != nullptr) { - return reportText(endpointId, basicInformationClusterId, nodeLabelAttrId, name); + if (esp_matter::attribute::get(endpointId, basicInformationClusterId, nodeLabelAttrId) == nullptr) + esp_matter::cluster::basic_information::attribute::create_node_label(basicCluster, empty, 0); + + if (esp_matter::attribute::get(endpointId, basicInformationClusterId, productNameAttrId) == nullptr) + esp_matter::cluster::basic_information::attribute::create_product_name(basicCluster, empty, 0); } +} + +inline esp_err_t setAndUpdateText(uint16_t endpointId, uint32_t clusterId, uint32_t attributeId, const char *value) +{ + if (!hasAttribute(endpointId, clusterId, attributeId)) + return ESP_ERR_NOT_FOUND; + + auto attribute = esp_matter::attribute::get(endpointId, clusterId, attributeId); + auto initial = textValue(value); + esp_err_t err = esp_matter::attribute::set_val(attribute, &initial); + if (err != ESP_OK) + return err; + + return updateText(endpointId, clusterId, attributeId, value); +} + +inline esp_err_t setDeviceName(esp_matter_bridge::device_t *device, const char *name) +{ + if (device == nullptr || name == nullptr) + return ESP_ERR_INVALID_ARG; + + uint16_t endpointId = device->persistent_info.device_endpoint_id; + ensureOptionalNameAttributes(endpointId); + + esp_err_t bridgedErr = setAndUpdateText(endpointId, bridgedDeviceBasicInformationClusterId, bridgedNodeLabelAttrId, name); + esp_err_t basicErr = setAndUpdateText(endpointId, basicInformationClusterId, nodeLabelAttrId, name); + esp_err_t bridgedProductErr = setAndUpdateText(endpointId, bridgedDeviceBasicInformationClusterId, productNameAttrId, name); + esp_err_t basicProductErr = setAndUpdateText(endpointId, basicInformationClusterId, productNameAttrId, name); + + if (bridgedErr == ESP_OK || basicErr == ESP_OK || bridgedProductErr == ESP_OK || basicProductErr == ESP_OK) + return ESP_OK; + + if (bridgedErr != ESP_ERR_NOT_FOUND) + return bridgedErr; + if (basicErr != ESP_ERR_NOT_FOUND) + return basicErr; + if (bridgedProductErr != ESP_ERR_NOT_FOUND) + return bridgedProductErr; + if (basicProductErr != ESP_ERR_NOT_FOUND) + return basicProductErr; return ESP_ERR_NOT_FOUND; } @@ -185,7 +244,7 @@ inline esp_err_t setEndpointName(uint16_t endpointId, const char *name) if (name == nullptr) return ESP_ERR_INVALID_ARG; - return reportText(endpointId, basicInformationClusterId, nodeLabelAttrId, name); + return updateText(endpointId, basicInformationClusterId, nodeLabelAttrId, name); } inline esp_err_t setEndpointProductName(uint16_t endpointId, const char *name) @@ -193,6 +252,6 @@ inline esp_err_t setEndpointProductName(uint16_t endpointId, const char *name) if (name == nullptr) return ESP_ERR_INVALID_ARG; - return reportText(endpointId, basicInformationClusterId, productNameAttrId, name); + return updateText(endpointId, basicInformationClusterId, productNameAttrId, name); } } \ No newline at end of file diff --git a/src/Matter/MatterBridgeDeviceBase.h b/src/Matter/MatterBridgeDeviceBase.h index 5d1c899..02b4901 100644 --- a/src/Matter/MatterBridgeDeviceBase.h +++ b/src/Matter/MatterBridgeDeviceBase.h @@ -1,6 +1,7 @@ #pragma once #include +#include "MatterBridgeCommon.h" #include #include @@ -28,4 +29,4 @@ class MatterBridgeDeviceBase virtual void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, uint32_t attributeId, esp_matter_attr_val_t *val) = 0; -}; \ No newline at end of file +}; diff --git a/src/Media/MatterMedia.cpp b/src/Media/MatterMedia.cpp index c394856..27c9551 100644 --- a/src/Media/MatterMedia.cpp +++ b/src/Media/MatterMedia.cpp @@ -18,7 +18,9 @@ void MatterMediaBridge::setup(uint8_t _channelIndex) esp_matter::endpoint::dimmable_light::get_device_type_id(), static_cast(this)); if (_device != nullptr) + { matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); + } } void MatterMediaBridge::setPlay(bool play) From c0015a9ae8cc66af7206387215800a18aa9d21e6 Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sun, 17 May 2026 02:01:24 +0200 Subject: [PATCH 08/12] Fix dimmer scaleing --- src/Dimmer/MatterDimmer.cpp | 22 ++++++++++++++++++---- src/MatterBridge.cpp | 16 ++++++++++++++-- src/MatterBridge.h | 1 + src/SmartHomeBridgeModule.cpp | 2 +- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/Dimmer/MatterDimmer.cpp b/src/Dimmer/MatterDimmer.cpp index 274b643..7bef4cd 100644 --- a/src/Dimmer/MatterDimmer.cpp +++ b/src/Dimmer/MatterDimmer.cpp @@ -10,6 +10,21 @@ MatterDimmerBridge::MatterDimmerBridge(MatterBridge *bridge) : MatterBridgeDevic { } +namespace +{ +uint8_t percentToMatterLevel(uint8_t percent) +{ + uint16_t clamped = static_cast(std::clamp(percent, 0, 100)); + return static_cast((clamped * 254u + 50u) / 100u); +} + +uint8_t matterLevelToPercent(uint8_t level) +{ + uint16_t clamped = static_cast(std::clamp(level, 0, 254)); + return static_cast((clamped * 100u + 127u) / 254u); +} +} + void MatterDimmerBridge::setup(uint8_t _channelIndex) { if (_bridge == nullptr || _bridge->node() == nullptr) @@ -24,7 +39,7 @@ void MatterDimmerBridge::setup(uint8_t _channelIndex) if (_device != nullptr) { matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); - setBrightness(_channel->mainFunctionValue() ? 254 : 0); + setBrightness(_channel->mainFunctionValue() ? 100 : 0); } } @@ -36,8 +51,7 @@ void MatterDimmerBridge::setBrightness(uint8_t brightness) matterbridge::reportBool(_device->persistent_info.device_endpoint_id, matterbridge::onOffClusterId, matterbridge::onOffAttrId, brightness > 0); matterbridge::reportU8(_device->persistent_info.device_endpoint_id, matterbridge::levelControlClusterId, - matterbridge::currentLevelAttrId, - static_cast(std::clamp(brightness, 0, 254))); + matterbridge::currentLevelAttrId, percentToMatterLevel(brightness)); } void MatterDimmerBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t, uint32_t clusterId, @@ -49,6 +63,6 @@ void MatterDimmerBridge::handleMatterAttribute(esp_matter::attribute::callback_t } else if (clusterId == matterbridge::levelControlClusterId && attributeId == matterbridge::currentLevelAttrId) { - _channel->commandBrightness(this, val->val.u8); + _channel->commandBrightness(this, matterLevelToPercent(val->val.u8)); } } \ No newline at end of file diff --git a/src/MatterBridge.cpp b/src/MatterBridge.cpp index 0907af7..8bb8b18 100644 --- a/src/MatterBridge.cpp +++ b/src/MatterBridge.cpp @@ -443,8 +443,11 @@ void MatterBridge::initialize(SmartHomeBridgeModule *bridge) } esp_matter::node::config_t config{}; - std::string nodeLabel = bridge != nullptr ? bridge->getNameInUTF8() : "SmartHomeBridge"; - std::strncpy(config.root_node.basic_information.node_label, nodeLabel.c_str(), + _bridgeNodeLabel = bridge != nullptr ? bridge->getNameInUTF8() : ""; + if (_bridgeNodeLabel.empty()) + _bridgeNodeLabel = "SmartHomeBridge"; + logInfoP("Matter bridge node label: '%s'", _bridgeNodeLabel.c_str()); + std::strncpy(config.root_node.basic_information.node_label, _bridgeNodeLabel.c_str(), sizeof(config.root_node.basic_information.node_label) - 1); _node = esp_matter::node::create(&config, attributeCallback, nullptr, this); @@ -479,6 +482,15 @@ esp_err_t MatterBridge::startMatter() if (err != ESP_OK) return err; + // After start(), set NodeLabel and ProductName on ep 0. + // esp_matter::start() restores persistent attribute values from NVS and may + // overwrite what was set via set_val before start. Calling update() after + // start() ensures Apple Home reads the correct bridge name during commissioning. + matterbridge::updateText(0, matterbridge::basicInformationClusterId, + matterbridge::nodeLabelAttrId, _bridgeNodeLabel.c_str()); + matterbridge::updateText(0, matterbridge::basicInformationClusterId, + matterbridge::productNameAttrId, _bridgeNodeLabel.c_str()); + if (chip::DeviceLayer::CommissionableDataProvider *provider = chip::DeviceLayer::GetCommissionableDataProvider(); provider != nullptr) { diff --git a/src/MatterBridge.h b/src/MatterBridge.h index aae29dd..a2ef8e4 100644 --- a/src/MatterBridge.h +++ b/src/MatterBridge.h @@ -9,6 +9,7 @@ class MatterBridge : public BridgeBase esp_matter::node_t *_node = nullptr; uint16_t _aggregatorEndpointId = 0; uint32_t _matterSetupPasscode = 20202021; + std::string _bridgeNodeLabel; public: MatterBridge() = default; diff --git a/src/SmartHomeBridgeModule.cpp b/src/SmartHomeBridgeModule.cpp index ba6a8da..53ad7bb 100644 --- a/src/SmartHomeBridgeModule.cpp +++ b/src/SmartHomeBridgeModule.cpp @@ -67,7 +67,7 @@ void SmartHomeBridgeModule::setup() #else logDebugP("Setup Bridge"); #endif - _utf8Name = convertISO8859_15ToUTF8_string((const char *)ParamBRI_BridgeName); + _utf8Name = convertISO8859_15ToUTF8_string(ParamBRI_BridgeNameStr.c_str()); #ifndef SMARTHOMEBRIDGE_DEVICESONLY webServer = new WebServer(webServerPort); From 2d84729bb766636162aa54ee50fbf252f9364c8c Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sun, 17 May 2026 12:46:40 +0200 Subject: [PATCH 09/12] Jalousien support --- src/Jalousie/MatterJalousie.cpp | 30 ++++++++++++++++++++++++------ src/Jalousie/MatterJalousie.h | 1 + src/Matter/MatterBridgeCommon.h | 11 ++++++++++- src/Rolladen/MatterRolladen.cpp | 24 ++++++++++++++++++------ 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/Jalousie/MatterJalousie.cpp b/src/Jalousie/MatterJalousie.cpp index 1835700..e35ca6f 100644 --- a/src/Jalousie/MatterJalousie.cpp +++ b/src/Jalousie/MatterJalousie.cpp @@ -6,18 +6,36 @@ MatterJalousieBridge::MatterJalousieBridge(MatterBridge *bridge) : MatterRollade { } +void MatterJalousieBridge::setup(uint8_t channelIndex) +{ + MatterRolladenBridge::setup(channelIndex); + + if (_device == nullptr) + return; + + // Create optional Tilt Percent100ths attributes (not created by window_covering_device). + auto ep = _device->persistent_info.device_endpoint_id; + auto wcCluster = esp_matter::cluster::get(ep, matterbridge::windowCoveringClusterId); + if (wcCluster != nullptr) + { + esp_matter::cluster::window_covering::attribute::create_current_position_tilt_percent_100ths( + wcCluster, nullable()); + esp_matter::cluster::window_covering::attribute::create_target_position_tilt_percent_100ths( + wcCluster, nullable()); + } +} + void MatterJalousieBridge::setSlatPosition(uint8_t slatPosition) { if (_device == nullptr) return; + auto ep = _device->persistent_info.device_endpoint_id; auto tilt = static_cast(std::clamp(slatPosition, 0, 100) * 100); - auto currentVal = matterbridge::u16Value(tilt); - auto targetVal = matterbridge::u16Value(tilt); - esp_matter::attribute::report(_device->persistent_info.device_endpoint_id, matterbridge::windowCoveringClusterId, - matterbridge::currentPositionTiltPercent100thsAttrId, ¤tVal); - esp_matter::attribute::report(_device->persistent_info.device_endpoint_id, matterbridge::windowCoveringClusterId, - matterbridge::targetPositionTiltPercent100thsAttrId, &targetVal); + matterbridge::reportU16(ep, matterbridge::windowCoveringClusterId, + matterbridge::currentPositionTiltPercent100thsAttrId, tilt); + matterbridge::reportU16(ep, matterbridge::windowCoveringClusterId, + matterbridge::targetPositionTiltPercent100thsAttrId, tilt); } void MatterJalousieBridge::handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, diff --git a/src/Jalousie/MatterJalousie.h b/src/Jalousie/MatterJalousie.h index e57195a..8960060 100644 --- a/src/Jalousie/MatterJalousie.h +++ b/src/Jalousie/MatterJalousie.h @@ -8,6 +8,7 @@ class MatterJalousieBridge final : public MatterRolladenBridge public: explicit MatterJalousieBridge(MatterBridge *bridge); + void setup(uint8_t channelIndex) override; void setSlatPosition(uint8_t slatPosition) override; void handleMatterAttribute(esp_matter::attribute::callback_type_t type, uint32_t clusterId, uint32_t attributeId, esp_matter_attr_val_t *val) override; diff --git a/src/Matter/MatterBridgeCommon.h b/src/Matter/MatterBridgeCommon.h index 691f7e1..bb98422 100644 --- a/src/Matter/MatterBridgeCommon.h +++ b/src/Matter/MatterBridgeCommon.h @@ -150,7 +150,16 @@ inline esp_err_t reportU16(uint16_t endpointId, uint32_t clusterId, uint32_t att if (!hasAttribute(endpointId, clusterId, attributeId)) return ESP_ERR_NOT_FOUND; - auto reported = u16Value(value); + auto attribute = esp_matter::attribute::get(endpointId, clusterId, attributeId); + + esp_matter_attr_val_t current{}; + esp_matter_attr_val_t reported = u16Value(value); + if (esp_matter::attribute::get_val(attribute, ¤t) == ESP_OK && + current.type == ESP_MATTER_VAL_TYPE_NULLABLE_UINT16) + { + reported = esp_matter_nullable_uint16(nullable(value)); + } + return esp_matter::attribute::report(endpointId, clusterId, attributeId, &reported); } diff --git a/src/Rolladen/MatterRolladen.cpp b/src/Rolladen/MatterRolladen.cpp index 2f47a4a..790bf55 100644 --- a/src/Rolladen/MatterRolladen.cpp +++ b/src/Rolladen/MatterRolladen.cpp @@ -20,6 +20,19 @@ void MatterRolladenBridge::setup(uint8_t _channelIndex) if (_device != nullptr) { matterbridge::setDeviceName(_device, _channel->getNameInUTF8()); + + // Create optional Lift Percent100ths attributes (mandatory only under PA feature flag, + // so window_covering_device does not create them automatically). + auto ep = _device->persistent_info.device_endpoint_id; + auto wcCluster = esp_matter::cluster::get(ep, matterbridge::windowCoveringClusterId); + if (wcCluster != nullptr) + { + esp_matter::cluster::window_covering::attribute::create_current_position_lift_percent_100ths( + wcCluster, nullable()); + esp_matter::cluster::window_covering::attribute::create_target_position_lift_percent_100ths( + wcCluster, nullable()); + } + setPosition(_channel->mainFunctionValue() ? 100 : 0); } } @@ -29,13 +42,12 @@ void MatterRolladenBridge::setPosition(uint8_t position) if (_device == nullptr) return; + auto ep = _device->persistent_info.device_endpoint_id; auto matterPosition = static_cast(std::clamp(position, 0, 100) * 100); - auto currentVal = matterbridge::u16Value(matterPosition); - auto targetVal = matterbridge::u16Value(matterPosition); - esp_matter::attribute::report(_device->persistent_info.device_endpoint_id, matterbridge::windowCoveringClusterId, - matterbridge::currentPositionLiftPercent100thsAttrId, ¤tVal); - esp_matter::attribute::report(_device->persistent_info.device_endpoint_id, matterbridge::windowCoveringClusterId, - matterbridge::targetPositionLiftPercent100thsAttrId, &targetVal); + matterbridge::reportU16(ep, matterbridge::windowCoveringClusterId, + matterbridge::currentPositionLiftPercent100thsAttrId, matterPosition); + matterbridge::reportU16(ep, matterbridge::windowCoveringClusterId, + matterbridge::targetPositionLiftPercent100thsAttrId, matterPosition); } void MatterRolladenBridge::setMovement(MoveState) From 05f3cf8a037a8c8110c72994bdaf7ad1fca1d6df Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sun, 17 May 2026 13:24:14 +0200 Subject: [PATCH 10/12] Late channel state update --- src/Alarm/KnxChannelAlarm.cpp | 19 ++++++- src/Alarm/KnxChannelAlarm.h | 2 + src/ChannelOwnerModule.h | 1 + src/Dimmer/KnxChannelDimmer.cpp | 16 ++++++ src/Dimmer/KnxChannelDimmer.h | 2 + src/Display/KnxChannelDisplay.cpp | 20 +++++++- src/Display/KnxChannelDisplay.h | 2 + src/DoorWindow/KnxChannelDoorWindow.cpp | 66 +++++++++++++++---------- src/DoorWindow/KnxChannelDoorWindow.h | 2 + src/Fan/KnxChannelFan.cpp | 22 ++++++++- src/Fan/KnxChannelFan.h | 2 + src/Jalousie/KnxChannelJalousie.cpp | 17 +++++++ src/Jalousie/KnxChannelJalousie.h | 2 + src/KnxChannelBase.h | 2 + src/Lock/KnxChannelLock.cpp | 16 ++++++ src/Lock/KnxChannelLock.h | 2 + src/Media/KnxChannelMedia.cpp | 16 ++++++ src/Media/KnxChannelMedia.h | 2 + src/RGB/KnxChannelRGB.cpp | 22 +++++++-- src/RGB/KnxChannelRGB.h | 2 + src/Rolladen/KnxChannelRolladen.cpp | 16 ++++++ src/Rolladen/KnxChannelRolladen.h | 2 + src/Scene/KnxChannelScene.cpp | 17 ++++++- src/Scene/KnxChannelScene.h | 2 + src/SmartHomeBridgeModule.cpp | 12 +++++ src/Switch/KnxChannelSwitch.cpp | 16 ++++++ src/Switch/KnxChannelSwitch.h | 2 + src/Thermostat/KnxChannelThermostat.cpp | 16 ++++++ src/Thermostat/KnxChannelThermostat.h | 2 + 29 files changed, 286 insertions(+), 34 deletions(-) diff --git a/src/Alarm/KnxChannelAlarm.cpp b/src/Alarm/KnxChannelAlarm.cpp index 7d87fa3..4fc1e33 100644 --- a/src/Alarm/KnxChannelAlarm.cpp +++ b/src/Alarm/KnxChannelAlarm.cpp @@ -28,8 +28,23 @@ void KnxChannelAlarm::add(AlarmBridge* sensorBridge) { sensorBridges.push_back(sensorBridge); sensorBridge->initialize(this); - auto value = mainFunctionValue(); - sensorBridge->setDetected(value); +} + +void KnxChannelAlarm::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto sensorBridge = static_cast(bridge); + sensorBridge->setDetected(mainFunctionValue()); +} + +void KnxChannelAlarm::syncAllBridgeStates() +{ + for (auto it = sensorBridges.begin(); it != sensorBridges.end(); ++it) + { + syncBridgeState(*it); + } } void KnxChannelAlarm::remove(AlarmBridge* sensorBridge) diff --git a/src/Alarm/KnxChannelAlarm.h b/src/Alarm/KnxChannelAlarm.h index 30d40fc..0e92ce9 100644 --- a/src/Alarm/KnxChannelAlarm.h +++ b/src/Alarm/KnxChannelAlarm.h @@ -31,6 +31,8 @@ class KnxChannelAlarm : public KnxChannelBase void remove(AlarmBridge* sensorBridge); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; AlarmType getAlarmType(); protected: virtual void setup() override; diff --git a/src/ChannelOwnerModule.h b/src/ChannelOwnerModule.h index 1f4b940..450302d 100644 --- a/src/ChannelOwnerModule.h +++ b/src/ChannelOwnerModule.h @@ -10,6 +10,7 @@ class ChannelOwnerModule : public OpenKNX::Module OpenKNX::Channel** _pChannels = nullptr; public: void setNumberOfChannels(uint8_t numberOfChannels); + uint8_t numberOfChannels() const { return _numberOfChannels; } ~ChannelOwnerModule(); virtual OpenKNX::Channel* createChannel(uint8_t _channelIndex /* this parameter is used in macros, do not rename */); diff --git a/src/Dimmer/KnxChannelDimmer.cpp b/src/Dimmer/KnxChannelDimmer.cpp index 20ac41a..1d3fd8d 100644 --- a/src/Dimmer/KnxChannelDimmer.cpp +++ b/src/Dimmer/KnxChannelDimmer.cpp @@ -35,9 +35,25 @@ void KnxChannelDimmer::add(DimmerBridge *dimmerBridge) { dimmerBridges.push_back(dimmerBridge); dimmerBridge->initialize(this); +} + +void KnxChannelDimmer::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto dimmerBridge = static_cast(bridge); dimmerBridge->setBrightness(koGet(KO_DIMMER_FEEDBACK)); } +void KnxChannelDimmer::syncAllBridgeStates() +{ + for (auto it = dimmerBridges.begin(); it != dimmerBridges.end(); ++it) + { + syncBridgeState(*it); + } +} + void KnxChannelDimmer::remove(DimmerBridge *dimmerBridge) { dimmerBridges.remove(dimmerBridge); diff --git a/src/Dimmer/KnxChannelDimmer.h b/src/Dimmer/KnxChannelDimmer.h index d5a11fd..186b582 100644 --- a/src/Dimmer/KnxChannelDimmer.h +++ b/src/Dimmer/KnxChannelDimmer.h @@ -18,6 +18,8 @@ class KnxChannelDimmer : public KnxChannelBase void remove(DimmerBridge* dimmerBridge); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: uint8_t lastBrighness = 100; diff --git a/src/Display/KnxChannelDisplay.cpp b/src/Display/KnxChannelDisplay.cpp index 5a0bc92..3d3584d 100644 --- a/src/Display/KnxChannelDisplay.cpp +++ b/src/Display/KnxChannelDisplay.cpp @@ -87,13 +87,29 @@ void KnxChannelDisplay::add(DeviceBridge *DeviceBridge) { DeviceBridges.push_back(DeviceBridge); DeviceBridge->initialize(this); +} + +void KnxChannelDisplay::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto deviceBridge = static_cast(bridge); if (getDisplayType() == DisplayType::DisplayTypeText) { - DeviceBridge->setValue(lastStringValue.c_str()); + deviceBridge->setValue(lastStringValue.c_str()); } else { - DeviceBridge->setValue(lastValue); + deviceBridge->setValue(lastValue); + } +} + +void KnxChannelDisplay::syncAllBridgeStates() +{ + for (auto it = DeviceBridges.begin(); it != DeviceBridges.end(); ++it) + { + syncBridgeState(*it); } } diff --git a/src/Display/KnxChannelDisplay.h b/src/Display/KnxChannelDisplay.h index 4888744..202423e 100644 --- a/src/Display/KnxChannelDisplay.h +++ b/src/Display/KnxChannelDisplay.h @@ -39,6 +39,8 @@ class KnxChannelDisplay : public KnxChannelBase void remove(DeviceBridge* DeviceBridge); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: virtual void setup() override; diff --git a/src/DoorWindow/KnxChannelDoorWindow.cpp b/src/DoorWindow/KnxChannelDoorWindow.cpp index 9a76cfc..1c4e8a0 100644 --- a/src/DoorWindow/KnxChannelDoorWindow.cpp +++ b/src/DoorWindow/KnxChannelDoorWindow.cpp @@ -39,6 +39,14 @@ void KnxChannelDoorWindow::add(DoorWindowBridge* interface) { interfaces.push_back(interface); interface->initialize(this); +} + +void KnxChannelDoorWindow::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto interface = static_cast(bridge); switch ((KnxChannelDoorWindowFeedback) ParamBRI_CHDoorWindowFeedbackType) { case KnxChannelDoorWindowFeedback::DoorWindowFeedbackPercentage: @@ -49,7 +57,7 @@ void KnxChannelDoorWindow::add(DoorWindowBridge* interface) break; case KnxChannelDoorWindowFeedback::DoorWindowFeedbackClosed: interface->setPosition(koGet(KO_FEEDBACK_BIT) ? 0 : 100); - break; + break; } interface->setMovement(_currentMovement); if (ParamBRI_CHDoorWindowObstructionDetection) @@ -61,7 +69,14 @@ void KnxChannelDoorWindow::add(DoorWindowBridge* interface) interface->setObstructionDetected(false); } interface->mainFunctionValueChanged(); +} +void KnxChannelDoorWindow::syncAllBridgeStates() +{ + for (auto it = interfaces.begin(); it != interfaces.end(); ++it) + { + syncBridgeState(*it); + } } void KnxChannelDoorWindow::remove(DoorWindowBridge* interface) @@ -141,7 +156,7 @@ bool KnxChannelDoorWindow::commandPosition(DoorWindowBridge* interface, uint8_t return true; } logDebugP("Received changed. Position: %d", position); - + bool sendPosition = true; if (position == 0 || position == 100) { @@ -156,7 +171,7 @@ bool KnxChannelDoorWindow::commandPosition(DoorWindowBridge* interface, uint8_t return true; } } - + switch (getDoorWindowHandling()) { case DoorWindowHandling::DoorWindowHandlingSendOpenAndClose: @@ -238,7 +253,7 @@ void KnxChannelDoorWindow::processInputKo(GroupObject &ko) { if (isKo(ko, KO_FEEDBACK_PERCENT)) { - uint8_t position = currentPosition(); + uint8_t position = koGet(KO_FEEDBACK_PERCENT); if (ParamBRI_CHDoorWindowUsePercent) koSetWithoutSend(KO_POSITION, position); for (auto it = interfaces.begin(); it != interfaces.end(); ++it) @@ -249,35 +264,27 @@ void KnxChannelDoorWindow::processInputKo(GroupObject &ko) mainFunctionValueChanged(); } - else if (isKo(ko, KO_OBSTRUCTION_DETECTED)) + else if (isKo(ko, KO_FEEDBACK_BIT)) { - bool obstructionDetected = koGet(KO_OBSTRUCTION_DETECTED); + uint8_t position = currentPosition(); + if (ParamBRI_CHDoorWindowUsePercent) + koSetWithoutSend(KO_POSITION, position); for (auto it = interfaces.begin(); it != interfaces.end(); ++it) { - (*it)->setObstructionDetected(obstructionDetected); + (*it)->setPosition(position); + (*it)->mainFunctionValueChanged(); } + mainFunctionValueChanged(); } else if (isKo(ko, KO_CLOSING_FEEDBACK) || isKo(ko, KO_OPENING_FEEDBACK)) { - bool down = koGet(KO_CLOSING_FEEDBACK); - bool up = koGet(KO_OPENING_FEEDBACK); - auto value = DoorWindowMoveState::DoorWindowMoveStateHold; - if (down) - { + bool closing = koGet(KO_CLOSING_FEEDBACK); + bool opening = koGet(KO_OPENING_FEEDBACK); + DoorWindowMoveState value = DoorWindowMoveState::DoorWindowMoveStateHold; + if (closing && !opening) value = DoorWindowMoveState::DoorWindowMoveStateClosing; - koSetWithoutSend(KO_OPENING_FEEDBACK, false); - logDebugP("Moving down"); - } - else if(up) - { + else if (!closing && opening) value = DoorWindowMoveState::DoorWindowMoveStateOpening; - koSetWithoutSend(KO_CLOSING_FEEDBACK, false); - logDebugP("Moving up"); - } - else - { - logDebugP("Stopping move"); - } _currentMovement = value; for (auto it = interfaces.begin(); it != interfaces.end(); ++it) { @@ -285,7 +292,16 @@ void KnxChannelDoorWindow::processInputKo(GroupObject &ko) (*it)->mainFunctionValueChanged(); } mainFunctionValueChanged(); - + } + else if (isKo(ko, KO_OBSTRUCTION_DETECTED)) + { + bool value = koGet(KO_OBSTRUCTION_DETECTED); + for (auto it = interfaces.begin(); it != interfaces.end(); ++it) + { + (*it)->setObstructionDetected(value); + (*it)->mainFunctionValueChanged(); + } + mainFunctionValueChanged(); } } diff --git a/src/DoorWindow/KnxChannelDoorWindow.h b/src/DoorWindow/KnxChannelDoorWindow.h index 79ac9d4..0e32900 100644 --- a/src/DoorWindow/KnxChannelDoorWindow.h +++ b/src/DoorWindow/KnxChannelDoorWindow.h @@ -36,6 +36,8 @@ class KnxChannelDoorWindow : public KnxChannelBase void remove(DoorWindowBridge* interface); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: volatile bool updatePosition = false; diff --git a/src/Fan/KnxChannelFan.cpp b/src/Fan/KnxChannelFan.cpp index 06a9073..14a38c0 100644 --- a/src/Fan/KnxChannelFan.cpp +++ b/src/Fan/KnxChannelFan.cpp @@ -28,8 +28,28 @@ void KnxChannelFan::add(FanBridge *fanBridge) { fanBridges.push_back(fanBridge); fanBridge->initialize(this); +} + +void KnxChannelFan::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + bool automatic = koGet(KO_AUTOMATIC_FEEDBACK); + if (ParamBRI_CHFanKoAutomaticFeedback == 1) + automatic = !automatic; + + auto fanBridge = static_cast(bridge); fanBridge->setPower(koGet(KO_SWITCH_FEEDBACK)); - fanBridge->setAutomatic(koGet(KO_AUTOMATIC_FEEDBACK)); + fanBridge->setAutomatic(automatic); +} + +void KnxChannelFan::syncAllBridgeStates() +{ + for (auto it = fanBridges.begin(); it != fanBridges.end(); ++it) + { + syncBridgeState(*it); + } } void KnxChannelFan::remove(FanBridge *fanBridge) diff --git a/src/Fan/KnxChannelFan.h b/src/Fan/KnxChannelFan.h index 89ec71e..e1767cf 100644 --- a/src/Fan/KnxChannelFan.h +++ b/src/Fan/KnxChannelFan.h @@ -19,6 +19,8 @@ class KnxChannelFan : public KnxChannelBase void remove(FanBridge* fanBridge); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: virtual void setup() override; diff --git a/src/Jalousie/KnxChannelJalousie.cpp b/src/Jalousie/KnxChannelJalousie.cpp index e13b44f..b768e50 100644 --- a/src/Jalousie/KnxChannelJalousie.cpp +++ b/src/Jalousie/KnxChannelJalousie.cpp @@ -26,9 +26,26 @@ void KnxChannelJalousie::deleteBridgeDevice(ChannelBridge *device) void KnxChannelJalousie::add(RolladenBridge* interface) { KnxChannelRolladen::add(interface); +} + +void KnxChannelJalousie::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + KnxChannelRolladen::syncBridgeState(bridge); + auto interface = static_cast(bridge); interface->setSlatPosition(koGet(KO_SLAT_POSITION_FEEDBACK)); } +void KnxChannelJalousie::syncAllBridgeStates() +{ + for (auto it = interfaces.begin(); it != interfaces.end(); ++it) + { + syncBridgeState(*it); + } +} + void KnxChannelJalousie::remove(RolladenBridge* interface) { interfaces.remove(interface); diff --git a/src/Jalousie/KnxChannelJalousie.h b/src/Jalousie/KnxChannelJalousie.h index a8c0cf9..0c24266 100644 --- a/src/Jalousie/KnxChannelJalousie.h +++ b/src/Jalousie/KnxChannelJalousie.h @@ -12,6 +12,8 @@ class KnxChannelJalousie : public KnxChannelRolladen void remove(RolladenBridge* interface); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: virtual void setup() override; diff --git a/src/KnxChannelBase.h b/src/KnxChannelBase.h index 300887a..ee212ff 100644 --- a/src/KnxChannelBase.h +++ b/src/KnxChannelBase.h @@ -84,6 +84,8 @@ class KnxChannelBase : public OpenKNX::Channel, public Component const char* getNameInUTF8(); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) = 0; virtual void deleteBridgeDevice(ChannelBridge* device) = 0; + virtual void syncBridgeState(ChannelBridge* bridge) {} + virtual void syncAllBridgeStates() {} virtual void commandMainFunctionClick() = 0; virtual bool supportMainFunctionClick() { return true; } virtual std::string currentValueAsString() = 0; diff --git a/src/Lock/KnxChannelLock.cpp b/src/Lock/KnxChannelLock.cpp index 41778a9..3f22b24 100644 --- a/src/Lock/KnxChannelLock.cpp +++ b/src/Lock/KnxChannelLock.cpp @@ -29,10 +29,26 @@ void KnxChannelLock::add(LockBridge *lockBridge) { lockBridges.push_back(lockBridge); lockBridge->initialize(this); +} + +void KnxChannelLock::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto lockBridge = static_cast(bridge); lockBridge->setLocked(isLocked()); lockBridge->setBlocked(koGet(KO_BLOCKED_FEEDBACK)); } +void KnxChannelLock::syncAllBridgeStates() +{ + for (auto it = lockBridges.begin(); it != lockBridges.end(); ++it) + { + syncBridgeState(*it); + } +} + void KnxChannelLock::remove(LockBridge *lockBridge) { lockBridges.remove(lockBridge); diff --git a/src/Lock/KnxChannelLock.h b/src/Lock/KnxChannelLock.h index 4493814..454d6a4 100644 --- a/src/Lock/KnxChannelLock.h +++ b/src/Lock/KnxChannelLock.h @@ -27,6 +27,8 @@ class KnxChannelLock : public KnxChannelBase void remove(LockBridge* lockBridge); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: virtual void setup() override; virtual void processInputKo(GroupObject& ko) override; diff --git a/src/Media/KnxChannelMedia.cpp b/src/Media/KnxChannelMedia.cpp index c80430d..7a08bd4 100644 --- a/src/Media/KnxChannelMedia.cpp +++ b/src/Media/KnxChannelMedia.cpp @@ -30,11 +30,27 @@ void KnxChannelMedia::add(MediaBridge *mediaBridge) { mediaBridges.push_back(mediaBridge); mediaBridge->initialize(this); +} + +void KnxChannelMedia::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto mediaBridge = static_cast(bridge); mediaBridge->setPlay(koGet(KO_PLAY_FEEDBACK)); mediaBridge->setVolume(koGet(KO_VOLUME_FEEDBACK)); mediaBridge->setTitle(koGet(KO_TITLE)); } +void KnxChannelMedia::syncAllBridgeStates() +{ + for (auto it = mediaBridges.begin(); it != mediaBridges.end(); ++it) + { + syncBridgeState(*it); + } +} + void KnxChannelMedia::remove(MediaBridge *mediaBridge) { mediaBridges.remove(mediaBridge); diff --git a/src/Media/KnxChannelMedia.h b/src/Media/KnxChannelMedia.h index 2fa391e..e3e6d44 100644 --- a/src/Media/KnxChannelMedia.h +++ b/src/Media/KnxChannelMedia.h @@ -20,6 +20,8 @@ class KnxChannelMedia : public KnxChannelBase void remove(MediaBridge* mediaBridge); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: virtual void setup() override; diff --git a/src/RGB/KnxChannelRGB.cpp b/src/RGB/KnxChannelRGB.cpp index 96ff27e..a8e6efc 100644 --- a/src/RGB/KnxChannelRGB.cpp +++ b/src/RGB/KnxChannelRGB.cpp @@ -46,11 +46,27 @@ void KnxChannelRGB::add(RGBBridge* RGBBridge) { RGBBridges.push_back(RGBBridge); RGBBridge->initialize(this); - RGBBridge->setRGB(lastColor); +} + +void KnxChannelRGB::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto rgbBridge = static_cast(bridge); + rgbBridge->setRGB(lastColor); if (ParamBRI_CHLightRGBUseSwitchKO) - RGBBridge->setPower(koGet(KO_POWER_FEEDBACK)); + rgbBridge->setPower(koGet(KO_POWER_FEEDBACK)); else - RGBBridge->setPower((uint32_t) koGet(KO_RGB_FEEDBACK) > 0); + rgbBridge->setPower((uint32_t) koGet(KO_RGB_FEEDBACK) > 0); +} + +void KnxChannelRGB::syncAllBridgeStates() +{ + for (auto it = RGBBridges.begin(); it != RGBBridges.end(); ++it) + { + syncBridgeState(*it); + } } void KnxChannelRGB::remove(RGBBridge* RGBBridge) diff --git a/src/RGB/KnxChannelRGB.h b/src/RGB/KnxChannelRGB.h index 4958c80..35d8550 100644 --- a/src/RGB/KnxChannelRGB.h +++ b/src/RGB/KnxChannelRGB.h @@ -21,6 +21,8 @@ class KnxChannelRGB : public KnxChannelBase void remove(RGBBridge* RGBBridge); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: uint32_t lastColor = 0xFFFFFF; diff --git a/src/Rolladen/KnxChannelRolladen.cpp b/src/Rolladen/KnxChannelRolladen.cpp index b45b9c0..54052f8 100644 --- a/src/Rolladen/KnxChannelRolladen.cpp +++ b/src/Rolladen/KnxChannelRolladen.cpp @@ -30,9 +30,25 @@ void KnxChannelRolladen::add(RolladenBridge* interface) { interfaces.push_back(interface); interface->initialize(this); +} + +void KnxChannelRolladen::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto interface = static_cast(bridge); interface->setPosition(koGet(KO_POSITION_FEEDBACK)); } +void KnxChannelRolladen::syncAllBridgeStates() +{ + for (auto it = interfaces.begin(); it != interfaces.end(); ++it) + { + syncBridgeState(*it); + } +} + void KnxChannelRolladen::remove(RolladenBridge* interface) { interfaces.remove(interface); diff --git a/src/Rolladen/KnxChannelRolladen.h b/src/Rolladen/KnxChannelRolladen.h index d0c970a..da4d06d 100644 --- a/src/Rolladen/KnxChannelRolladen.h +++ b/src/Rolladen/KnxChannelRolladen.h @@ -36,6 +36,8 @@ class KnxChannelRolladen : public KnxChannelBase void remove(RolladenBridge* interface); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: volatile bool updatePosition = false; diff --git a/src/Scene/KnxChannelScene.cpp b/src/Scene/KnxChannelScene.cpp index 17b908a..cc8d298 100644 --- a/src/Scene/KnxChannelScene.cpp +++ b/src/Scene/KnxChannelScene.cpp @@ -25,8 +25,23 @@ void KnxChannelScene::add(SceneBridge *sceneBridge) { sceneBridges.push_back(sceneBridge); sceneBridge->initialize(this); - sceneBridge->setActivating(false); +} + +void KnxChannelScene::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto sceneBridge = static_cast(bridge); + sceneBridge->setActivating(_lastActivatiation != 0); +} +void KnxChannelScene::syncAllBridgeStates() +{ + for (auto it = sceneBridges.begin(); it != sceneBridges.end(); ++it) + { + syncBridgeState(*it); + } } void KnxChannelScene::remove(SceneBridge *sceneBridge) diff --git a/src/Scene/KnxChannelScene.h b/src/Scene/KnxChannelScene.h index 902af5e..b598d0e 100644 --- a/src/Scene/KnxChannelScene.h +++ b/src/Scene/KnxChannelScene.h @@ -23,6 +23,8 @@ class KnxChannelScene : public KnxChannelBase void remove(SceneBridge* sceneBridge); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: virtual void setup() override; virtual void loop() override; diff --git a/src/SmartHomeBridgeModule.cpp b/src/SmartHomeBridgeModule.cpp index 53ad7bb..450947f 100644 --- a/src/SmartHomeBridgeModule.cpp +++ b/src/SmartHomeBridgeModule.cpp @@ -300,6 +300,18 @@ void SmartHomeBridgeModule::startBridge() for (auto it = bridgeInterfaces->begin(); it != bridgeInterfaces->end(); ++it) (*it)->start(this); + // Push channel feedback state after all bridges are fully started. + // This avoids initial bridge reports during endpoint creation/startup. + if (_pChannels != nullptr) + { + for (uint8_t channelIndex = 0; channelIndex < numberOfChannels(); ++channelIndex) + { + auto channel = static_cast(_pChannels[channelIndex]); + if (channel != nullptr) + channel->syncAllBridgeStates(); + } + } + #ifndef SMARTHOMEBRIDGE_DEVICESONLY webServer->begin(); #endif diff --git a/src/Switch/KnxChannelSwitch.cpp b/src/Switch/KnxChannelSwitch.cpp index 2d56f76..f7beb05 100644 --- a/src/Switch/KnxChannelSwitch.cpp +++ b/src/Switch/KnxChannelSwitch.cpp @@ -26,9 +26,25 @@ void KnxChannelSwitch::add(SwitchBridge *switchBridge) { switchBridges.push_back(switchBridge); switchBridge->initialize(this); +} + +void KnxChannelSwitch::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto switchBridge = static_cast(bridge); switchBridge->setPower(koGet(KO_SWITCH_FEEDBACK)); } +void KnxChannelSwitch::syncAllBridgeStates() +{ + for (auto it = switchBridges.begin(); it != switchBridges.end(); ++it) + { + syncBridgeState(*it); + } +} + void KnxChannelSwitch::remove(SwitchBridge *switchBridge) { switchBridges.remove(switchBridge); diff --git a/src/Switch/KnxChannelSwitch.h b/src/Switch/KnxChannelSwitch.h index 51471b9..b24ffe3 100644 --- a/src/Switch/KnxChannelSwitch.h +++ b/src/Switch/KnxChannelSwitch.h @@ -18,6 +18,8 @@ class KnxChannelSwitch : public KnxChannelBase void remove(SwitchBridge* switchBridge); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: virtual void setup() override; virtual void processInputKo(GroupObject& ko) override; diff --git a/src/Thermostat/KnxChannelThermostat.cpp b/src/Thermostat/KnxChannelThermostat.cpp index 8b13a9f..bff1b35 100644 --- a/src/Thermostat/KnxChannelThermostat.cpp +++ b/src/Thermostat/KnxChannelThermostat.cpp @@ -39,6 +39,14 @@ void KnxChannelThermostat::add(ThermostatBridge *thermostatBridge) { thermostatBridges.push_back(thermostatBridge); thermostatBridge->initialize(this); +} + +void KnxChannelThermostat::syncBridgeState(ChannelBridge *bridge) +{ + if (bridge == nullptr) + return; + + auto thermostatBridge = static_cast(bridge); thermostatBridge->setCurrentTemperature(koGet(KO_CURRENT_TEMPERATUR_FEEDBACK)); thermostatBridge->setTargetTemperature(koGet(KO_TARGET_TEMPERATURE_FEEDBACK)); if (ParamBRI_CHThermostatFeedbackKoType == 0) @@ -62,6 +70,14 @@ void KnxChannelThermostat::add(ThermostatBridge *thermostatBridge) updateBridgeFromKo(KO_COOLING_ACTIVE_PERCENT_FEEDBACK, thermostatBridge); } +void KnxChannelThermostat::syncAllBridgeStates() +{ + for (auto it = thermostatBridges.begin(); it != thermostatBridges.end(); ++it) + { + syncBridgeState(*it); + } +} + void KnxChannelThermostat::remove(ThermostatBridge *thermostatBridge) { thermostatBridges.remove(thermostatBridge); diff --git a/src/Thermostat/KnxChannelThermostat.h b/src/Thermostat/KnxChannelThermostat.h index 9ded813..907fbef 100644 --- a/src/Thermostat/KnxChannelThermostat.h +++ b/src/Thermostat/KnxChannelThermostat.h @@ -45,6 +45,8 @@ class KnxChannelThermostat : public KnxChannelBase void remove(ThermostatBridge* thermostatBridge); virtual ChannelBridge* createBridgeDevice(BridgeBase& bridge) override; virtual void deleteBridgeDevice(ChannelBridge* device) override; + virtual void syncBridgeState(ChannelBridge* bridge) override; + virtual void syncAllBridgeStates() override; protected: virtual void setup() override; virtual void processInputKo(GroupObject& ko) override; From 91e4a3959c174a762be9da2c6c9a3db8f15e108b Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sun, 17 May 2026 15:29:35 +0200 Subject: [PATCH 11/12] Markise / Rolladen / Jalousie type --- src/Rolladen/MatterRolladen.cpp | 43 ++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/Rolladen/MatterRolladen.cpp b/src/Rolladen/MatterRolladen.cpp index 790bf55..1a44df9 100644 --- a/src/Rolladen/MatterRolladen.cpp +++ b/src/Rolladen/MatterRolladen.cpp @@ -5,6 +5,43 @@ #include +namespace +{ +constexpr uint8_t kDeviceTypeRolladen = 31; +constexpr uint8_t kDeviceTypeMarkise = 32; + +// WindowCovering EndProductType enum values (Matter spec). +constexpr uint8_t kEndProductTypeRollerShade = 0x00; +constexpr uint8_t kEndProductTypeRollerShutter = 0x11; +constexpr uint8_t kEndProductTypeInteriorVenetianBlind = 0x0C; +constexpr uint8_t kEndProductTypeAwningTerracePatio = 0x13; + +void applyWindowCoveringEndProductType(uint16_t endpointId, uint8_t channelDeviceType) +{ + auto wcCluster = esp_matter::cluster::get(endpointId, matterbridge::windowCoveringClusterId); + if (wcCluster == nullptr) + return; + + uint8_t endProductType = kEndProductTypeRollerShade; + if (channelDeviceType == 30) + endProductType = kEndProductTypeInteriorVenetianBlind; + else if (channelDeviceType == 32) + endProductType = kEndProductTypeAwningTerracePatio; + else if (channelDeviceType == 31) + endProductType = kEndProductTypeRollerShutter; + + constexpr uint32_t endProductTypeAttrId = chip::app::Clusters::WindowCovering::Attributes::EndProductType::Id; + if (!matterbridge::hasAttribute(endpointId, matterbridge::windowCoveringClusterId, endProductTypeAttrId)) + { + esp_matter::cluster::window_covering::attribute::create_end_product_type(wcCluster, endProductType); + return; + } + + auto updated = matterbridge::u8Value(endProductType); + esp_matter::attribute::update(endpointId, matterbridge::windowCoveringClusterId, endProductTypeAttrId, &updated); +} +} // namespace + MatterRolladenBridge::MatterRolladenBridge(MatterBridge *bridge) : MatterBridgeDeviceBase(bridge) { } @@ -14,7 +51,7 @@ void MatterRolladenBridge::setup(uint8_t _channelIndex) if (_bridge == nullptr || _bridge->node() == nullptr) return; - _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), + _device = esp_matter_bridge::create_device(_bridge->node(), _bridge->parentEndpointId(), esp_matter::endpoint::window_covering_device::get_device_type_id(), static_cast(this)); if (_device != nullptr) @@ -31,8 +68,8 @@ void MatterRolladenBridge::setup(uint8_t _channelIndex) wcCluster, nullable()); esp_matter::cluster::window_covering::attribute::create_target_position_lift_percent_100ths( wcCluster, nullable()); - } - + } + applyWindowCoveringEndProductType(ep, ParamBRI_CHDeviceType); setPosition(_channel->mainFunctionValue() ? 100 : 0); } } From e90fafe6ffeb8b78d0cc00f807fbae584ff44b0c Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sun, 17 May 2026 18:48:13 +0200 Subject: [PATCH 12/12] Add config logs --- src/MatterBridge.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/MatterBridge.cpp b/src/MatterBridge.cpp index 8bb8b18..a24dc73 100644 --- a/src/MatterBridge.cpp +++ b/src/MatterBridge.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,37 @@ namespace { constexpr uint32_t kDefaultMatterSetupPasscode = 20202021; +void logMatterEndpointBudget() +{ +#ifdef CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT + constexpr unsigned dynamicCount = CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; +#else + constexpr unsigned dynamicCount = 0; +#endif + +#ifdef FIXED_ENDPOINT_COUNT + constexpr unsigned fixedCount = FIXED_ENDPOINT_COUNT; +#else + constexpr unsigned fixedCount = 0; +#endif + +#ifdef MAX_ENDPOINT_COUNT + constexpr unsigned maxCount = MAX_ENDPOINT_COUNT; +#else + constexpr unsigned maxCount = dynamicCount + fixedCount; +#endif + +#ifdef CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT + constexpr int sdkconfigDynamic = CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT; +#else + constexpr int sdkconfigDynamic = -1; +#endif + + logInfo("MatterBridge", + "Matter endpoint budget: dynamic=%u fixed=%u max=%u sdkconfig_dynamic=%d", + dynamicCount, fixedCount, maxCount, sdkconfigDynamic); +} + class RuntimeCommissionableDataProvider : public chip::DeviceLayer::CommissionableDataProvider { public: @@ -369,6 +401,7 @@ esp_err_t MatterBridge::configureNode(esp_matter::node_t *node) void MatterBridge::initialize(SmartHomeBridgeModule *bridge) { BridgeBase::initialize(bridge); + logMatterEndpointBudget(); // Resolve intended passcode uint32_t matterSetupPasscode = kDefaultMatterSetupPasscode;