Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Matter support for Occupancy via Switch (experimental) #18742

Merged
merged 1 commit into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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