Skip to content

Commit

Permalink
Matter support for Occupancy via Switch (experimental) (#18742)
Browse files Browse the repository at this point in the history
  • Loading branch information
s-hadinger authored May 29, 2023
1 parent 3f094c9 commit 83e47fa
Show file tree
Hide file tree
Showing 22 changed files with 1,614 additions and 717 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
- HASPmota `meta` attribute and improved `berry_run`
- Matter Border Router for ESP8266 (experimental)
- Display descriptor for ST7735 128x160 display
- Matter support for Occupancy via Switch (experimental)

### Breaking Changed
- Matter relay number starts at 1 instead of 0 to match Tasmota numbering
Expand Down
4 changes: 4 additions & 0 deletions lib/libesp32/berry_matter/src/be_matter_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
#include "solidify/solidified_Matter_Plugin_Sensor_Temp.h"
#include "solidify/solidified_Matter_Plugin_Sensor_Illuminance.h"
#include "solidify/solidified_Matter_Plugin_Sensor_Humidity.h"
#include "solidify/solidified_Matter_Plugin_Sensor_Occupancy.h"
#include "solidify/solidified_Matter_Plugin_Bridge_HTTP.h"
#include "solidify/solidified_Matter_Plugin_Bridge_OnOff.h"
#include "solidify/solidified_Matter_Plugin_Bridge_Light0.h"
Expand All @@ -213,6 +214,7 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
#include "solidify/solidified_Matter_Plugin_Bridge_Sensor_Temp.h"
#include "solidify/solidified_Matter_Plugin_Bridge_Sensor_Illuminance.h"
#include "solidify/solidified_Matter_Plugin_Bridge_Sensor_Humidity.h"
#include "solidify/solidified_Matter_Plugin_Bridge_Sensor_Occupancy.h"

/*********************************************************************************************\
* Get a bytes() object of the certificate DAC/PAI_Cert
Expand Down Expand Up @@ -399,6 +401,7 @@ module matter (scope: global, strings: weak) {
Plugin_Sensor_Temp, class(be_class_Matter_Plugin_Sensor_Temp) // Temperature Sensor
Plugin_Sensor_Illuminance, class(be_class_Matter_Plugin_Sensor_Illuminance) // Illuminance Sensor
Plugin_Sensor_Humidity, class(be_class_Matter_Plugin_Sensor_Humidity) // Humidity Sensor
Plugin_Sensor_Occupancy, class(be_class_Matter_Plugin_Sensor_Occupancy) // Occupancy Sensor
Plugin_Bridge_HTTP, class(be_class_Matter_Plugin_Bridge_HTTP) // HTTP bridge superclass
Plugin_Bridge_OnOff, class(be_class_Matter_Plugin_Bridge_OnOff) // HTTP Relay/Light behavior (OnOff)
Plugin_Bridge_Light0, class(be_class_Matter_Plugin_Bridge_Light0) // HTTP OnOff Light
Expand All @@ -410,6 +413,7 @@ module matter (scope: global, strings: weak) {
Plugin_Bridge_Sensor_Temp, class(be_class_Matter_Plugin_Bridge_Sensor_Temp) // HTTP Temperature sensor
Plugin_Bridge_Sensor_Illuminance, class(be_class_Matter_Plugin_Bridge_Sensor_Illuminance) // HTTP Illuminance sensor
Plugin_Bridge_Sensor_Humidity, class(be_class_Matter_Plugin_Bridge_Sensor_Humidity) // HTTP Humidity sensor
Plugin_Bridge_Sensor_Occupancy, class(be_class_Matter_Plugin_Bridge_Sensor_Occupancy) // HTTP Occupancy sensor
}
@const_object_info_end */
Expand Down
2 changes: 1 addition & 1 deletion lib/libesp32/berry_matter/src/embedded/Matter_Message.be
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ class Matter_Frame
var m = self.raw[4 .. self.payload_idx-1]
var m_clear = crypto.AES_CTR(k).decrypt(m, n, 2)
# replace in-place
self.raw = self.raw[0..3] + m_clear + m[self.self.payload_idx .. ]
self.raw = self.raw[0..3] + m_clear + m[self.payload_idx .. ]
end

# use AES_CCM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,10 @@ class Matter_Plugin_Bridge_HTTP : Matter_Plugin_Device
webserver.content_send("| <-- (" + self.NAME + ") -->")
end

# Show on/off value as html
def web_value_onoff(onoff)
var onoff_html = (onoff != nil ? (onoff ? "<b>On</b>" : "Off") : "")
return onoff_html
end
end
matter.Plugin_Bridge_HTTP = Matter_Plugin_Bridge_HTTP
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,8 @@ class Matter_Plugin_Bridge_Light0 : Matter_Plugin_Bridge_HTTP
def web_values()
import webserver
import string
webserver.content_send(string.format("| Light %s", self.web_value_onoff()))
webserver.content_send(string.format("| Light %s", self.web_value_onoff(self.shadow_onoff)))
end

# Show on/off value as html
def web_value_onoff()
var onoff_html = (self.shadow_onoff != nil ? (self.shadow_onoff ? "<b>On</b>" : "Off") : "")
return onoff_html
end

end
matter.Plugin_Bridge_Light0 = Matter_Plugin_Bridge_Light0
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class Matter_Plugin_Bridge_Light1 : Matter_Plugin_Bridge_Light0
def web_values()
import webserver
import string
webserver.content_send(string.format("| Light %s %s", self.web_value_onoff(), self.web_value_dimmer()))
webserver.content_send(string.format("| Light %s %s", self.web_value_onoff(self.shadow_onoff), self.web_value_dimmer()))
end

# Show on/off value as html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class Matter_Plugin_Bridge_Light2 : Matter_Plugin_Bridge_Light1
import webserver
import string
webserver.content_send(string.format("| Light %s %s %s",
self.web_value_onoff(), self.web_value_dimmer(),
self.web_value_onoff(self.shadow_onoff), self.web_value_dimmer(),
self.web_value_ct()))
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class Matter_Plugin_Bridge_Light3 : Matter_Plugin_Bridge_Light1
import webserver
import string
webserver.content_send(string.format("| Light %s %s %s",
self.web_value_onoff(), self.web_value_dimmer(),
self.web_value_onoff(self.shadow_onoff), self.web_value_dimmer(),
self.web_value_RGB()))
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class Matter_Plugin_Bridge_OnOff : Matter_Plugin_Bridge_Light0
def web_values()
import webserver
import string
webserver.content_send(string.format("| Relay %i %s", self.tasmota_relay_index, self.web_value_onoff()))
webserver.content_send(string.format("| Relay %i %s", self.tasmota_relay_index, self.web_value_onoff(self.shadow_onoff)))
end

end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#
# Matter_Plugin_Bridge_Sensor_Occupancy.be - implements base class for a Occupancy Sensor via HTTP to Tasmota
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

# Matter plug-in for core behavior

# dummy declaration for solidification
class Matter_Plugin_Bridge_HTTP end

#@ solidify:Matter_Plugin_Bridge_Sensor_Occupancy,weak

class Matter_Plugin_Bridge_Sensor_Occupancy : Matter_Plugin_Bridge_HTTP
static var TYPE = "http_occupancy" # name of the plug-in in json
static var NAME = "&#x1F517; Occupancy" # display name of the plug-in
static var ARG = "switch" # additional argument name (or empty if none)
static var ARG_TYPE = / x -> int(x) # function to convert argument to the right type
static var UPDATE_TIME = 5000 # update every 5s
static var UPDATE_CMD = "Status 8" # command to send for updates

static var CLUSTERS = {
0x0406: [0,1,2,0xFFFC,0xFFFD], # Occupancy Sensing p.105 - no writable
}
static var TYPES = { 0x0107: 2, 0x0013: 1 } # Occupancy Sensor, rev 2

var tasmota_switch_index # Switch number in Tasmota (one based)
var shadow_occupancy

#############################################################
# Constructor
def init(device, endpoint, arguments)
super(self).init(device, endpoint, arguments)
self.tasmota_switch_index = int(arguments.find(self.ARG #-'relay'-#, 1))
if self.tasmota_switch_index <= 0 self.tasmota_switch_index = 1 end
end

#############################################################
# Stub for updating shadow values (local copies of what we published to the Matter gateway)
#
# This call is synnchronous and blocking.
def parse_update(data, index)
if index == 8 # Status 8
var state = false

state = (data.find("Switch" + str(self.tasmota_switch_index)) == "ON")

if self.shadow_occupancy != nil && self.shadow_occupancy != bool(state)
self.attribute_updated(0x0406, 0x0000)
end
self.shadow_occupancy = state
end
end

#############################################################
# read an attribute
#
def read_attribute(session, ctx)
import string
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute

# ====================================================================================================
if cluster == 0x0406 # ========== Occupancy Sensing ==========
if attribute == 0x0000 # ---------- Occupancy / U8 ----------
if self.shadow_occupancy != nil
return TLV.create_TLV(TLV.U1, self.shadow_occupancy)
else
return TLV.create_TLV(TLV.NULL, nil)
end
elif attribute == 0x0001 # ---------- OccupancySensorType / enum8 ----------
return TLV.create_TLV(TLV.U1, 3) # physical contact
elif attribute == 0x0002 # ---------- OccupancySensorTypeBitmap / u8 ----------
return TLV.create_TLV(TLV.U1, 0) # unknown
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0)
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
return TLV.create_TLV(TLV.U4, 3) # 4 = New data model format and notation
end

else
return super(self).read_attribute(session, ctx)
end
end

#############################################################
# web_values
#
# Show values of the remote device as HTML
def web_values()
import webserver
import string
webserver.content_send(string.format("| Occupancy%i %s", self.tasmota_switch_index, self.web_value_onoff(self.shadow_occupancy)))
end

end
matter.Plugin_Bridge_Sensor_Occupancy = Matter_Plugin_Bridge_Sensor_Occupancy
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#
# Matter_Plugin_Sensor_Occupancy.be - implements the behavior for a Occupany Switch
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

# Matter plug-in for core behavior

# dummy declaration for solidification
class Matter_Plugin_Device end

#@ solidify:Matter_Plugin_Sensor_Occupancy,weak

class Matter_Plugin_Sensor_Occupancy : Matter_Plugin_Device
static var TYPE = "occupancy" # name of the plug-in in json
static var NAME = "Occupancy" # display name of the plug-in
static var ARG = "switch" # additional argument name (or empty if none)
static var ARG_TYPE = / x -> int(x) # function to convert argument to the right type
static var UPDATE_TIME = 5000 # update every 250ms
static var CLUSTERS = {
0x0406: [0,1,2,0xFFFC,0xFFFD], # Occupancy Sensing p.105 - no writable
}
static var TYPES = { 0x0107: 2 } # Occupancy Sensor, rev 2

var tasmota_switch_index # Switch number in Tasmota (one based)
var shadow_occupancy

#############################################################
# Constructor
def init(device, endpoint, arguments)
super(self).init(device, endpoint, arguments)
self.tasmota_switch_index = int(arguments.find(self.ARG #-'relay'-#, 1))
if self.tasmota_switch_index <= 0 self.tasmota_switch_index = 1 end
end

#############################################################
# Update shadow
#
def update_shadow()
super(self).update_shadow()

import json
var ret = tasmota.cmd("Status 8", true)
if ret != nil
var j = json.load(ret)
if j != nil
var state = false
state = (j.find("Switch" + str(self.tasmota_switch_index)) == "ON")

if self.shadow_occupancy != nil && self.shadow_occupancy != bool(state)
self.attribute_updated(0x0406, 0x0000)
end
self.shadow_occupancy = state
end
end
end

#############################################################
# read an attribute
#
def read_attribute(session, ctx)
import string
var TLV = matter.TLV
var cluster = ctx.cluster
var attribute = ctx.attribute

# ====================================================================================================
if cluster == 0x0406 # ========== Occupancy Sensing ==========
if attribute == 0x0000 # ---------- Occupancy / U8 ----------
if self.shadow_occupancy != nil
return TLV.create_TLV(TLV.U1, self.shadow_occupancy)
else
return TLV.create_TLV(TLV.NULL, nil)
end
elif attribute == 0x0001 # ---------- OccupancySensorType / enum8 ----------
return TLV.create_TLV(TLV.U1, 3) # physical contact
elif attribute == 0x0002 # ---------- OccupancySensorTypeBitmap / u8 ----------
return TLV.create_TLV(TLV.U1, 0) # unknown
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0)
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
return TLV.create_TLV(TLV.U4, 3) # 4 = New data model format and notation
end

else
return super(self).read_attribute(session, ctx)
end
end

end
matter.Plugin_Sensor_Occupancy = Matter_Plugin_Sensor_Occupancy
3 changes: 2 additions & 1 deletion lib/libesp32/berry_matter/src/embedded/Matter_UI.be
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ import matter
class Matter_UI
static var _ROOT_TYPES = "root"
static var _CLASSES_TYPES = "|relay|light0|light1|light2|light3|shutter|shutter+tilt"
"|temperature|pressure|illuminance|humidity"
"|temperature|pressure|illuminance|humidity|occupancy"
static var _CLASSES_TYPES2= "-http|http_relay|http_light0|http_light1|http_light2|http_light3"
"|http_temperature|http_pressure|http_illuminance|http_humidity"
"|http_occupancy"
var device

def init(device)
Expand Down
Loading

0 comments on commit 83e47fa

Please sign in to comment.