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 add sensors: Humidity, Pressure, Illuminance; optimize memory #18441

Merged
merged 1 commit into from
Apr 17, 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 @@ -29,6 +29,7 @@ All notable changes to this project will be documented in this file.
- Berry `instrospect.name()` to get names of functions, modules and classes (#18422)
- Berry add `searchall()` and `matchall()` to `re` module and pre-compiled patterns (#18429)
- Matter automatically exposes all detected Temperature sensors (#18430)
- Matter add sensors: Humidity, Pressure, Illuminance; optimize memory

### Changed
- ESP32 LVGL library from v8.3.5 to v8.3.6 (no functional change)
Expand Down
2 changes: 1 addition & 1 deletion lib/libesp32/berry/default/berry_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@
#define BE_USE_OS_MODULE 0
#define BE_USE_GLOBAL_MODULE 1
#define BE_USE_SYS_MODULE 1
#define BE_USE_DEBUG_MODULE 0
#define BE_USE_DEBUG_MODULE 1
#define BE_USE_GC_MODULE 1
#define BE_USE_SOLIDIFY_MODULE 0
#define BE_USE_INTROSPECT_MODULE 1
Expand Down
12 changes: 10 additions & 2 deletions lib/libesp32/berry_matter/src/be_matter_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
#include "solidify/solidified_Matter_Plugin_Light1.h"
#include "solidify/solidified_Matter_Plugin_Light2.h"
#include "solidify/solidified_Matter_Plugin_Light3.h"
#include "solidify/solidified_Matter_Plugin_Temp_Sensor.h"
#include "solidify/solidified_Matter_Plugin_Sensor.h"
#include "solidify/solidified_Matter_Plugin_Sensor_Pressure.h"
#include "solidify/solidified_Matter_Plugin_Sensor_Temp.h"
#include "solidify/solidified_Matter_Plugin_Sensor_Light.h"
#include "solidify/solidified_Matter_Plugin_Sensor_Humidity.h"

/*********************************************************************************************\
* Get a bytes() object of the certificate DAC/PAI_Cert
Expand Down Expand Up @@ -333,7 +337,11 @@ module matter (scope: global, strings: weak) {
Plugin_Light1, class(be_class_Matter_Plugin_Light1) // Dimmable Light
Plugin_Light2, class(be_class_Matter_Plugin_Light2) // Color Temperature Light
Plugin_Light3, class(be_class_Matter_Plugin_Light3) // Extended Color Light
Plugin_Temp_Sensor, class(be_class_Matter_Plugin_Temp_Sensor) // Temperature Sensor
Plugin_Sensor, class(be_class_Matter_Plugin_Sensor) // Generic Sensor
Plugin_Sensor_Pressure, class(be_class_Matter_Plugin_Sensor_Pressure) // Pressure Sensor
Plugin_Sensor_Temp, class(be_class_Matter_Plugin_Sensor_Temp) // Temperature Sensor
Plugin_Sensor_Light, class(be_class_Matter_Plugin_Sensor_Light) // Light Sensor
Plugin_Sensor_Humidity, class(be_class_Matter_Plugin_Sensor_Humidity) // Humidity Sensor
}

@const_object_info_end */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,10 @@ class Matter_Commisioning_Context
end
end
# TODO if there is only 1 fabric, we can try to use it anyways
if size(self.device.sessions.fabrics) == 1
tasmota.log("MTR: *** Could not find fabric, trying only fabric in store", 2)
return self.device.sessions.fabrics[0]
end
# if size(self.device.sessions.fabrics) == 1
# tasmota.log("MTR: *** Could not find fabric, trying only fabric in store", 2)
# return self.device.sessions.fabrics[0]
# end
return nil
end

Expand Down
55 changes: 51 additions & 4 deletions lib/libesp32/berry_matter/src/embedded/Matter_Device.be
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ class Matter_Device
var pairing_code = self.compute_manual_pairing_code()
tasmota.log(string.format("MTR: Manual pairing code: %s-%s-%s", pairing_code[0..3], pairing_code[4..6], pairing_code[7..]), 2)

# output MQTT
var qr_code = self.compute_qrcode_content()
tasmota.publish_result(string.format('{"Matter":{"Commissioning":1,"PairingCode":"%s","QRCode":"%s"}}', pairing_code, qr_code), 'Matter')

# compute PBKDF
self._compute_pbkdf(self.root_passcode, self.root_iterations, self.root_salt)
self.start_basic_commissioning(timeout_s, self.root_iterations, self.root_discriminator, self.root_salt, self.root_w0, #-self.root_w1,-# self.root_L, nil)
Expand Down Expand Up @@ -202,6 +206,9 @@ class Matter_Device
#############################################################
# Stop PASE commissioning, mostly called when CASE is about to start
def stop_basic_commissioning()
if self.is_root_commissioning_open()
tasmota.publish_result('{"Matter":{"Commissioning":0}}', 'Matter')
end
self.commissioning_open = nil

self.mdns_remove_PASE()
Expand Down Expand Up @@ -548,6 +555,7 @@ class Matter_Device
ctx.cluster = cl
ctx.attribute = at
var finished = cb(pi, ctx, direct) # call the callback with the plugin and the context
# tasmota.log("MTR: gc="+str(tasmota.gc()), 2)
if direct && finished return end
end
end
Expand Down Expand Up @@ -955,23 +963,62 @@ class Matter_Device
var sensor_2 = sensors[k1]
if isinstance(sensor_2, map) && sensor_2.contains("Temperature")
var temp_rule = k1 + "#Temperature"
self.plugins.push(matter.Plugin_Temp_Sensor(self, endpoint, temp_rule))
self.plugins.push(matter.Plugin_Sensor_Temp(self, endpoint, temp_rule))
tasmota.log(string.format("MTR: Endpoint:%i Temperature (%s)", endpoint, temp_rule), 2)
endpoint += 1
end
if endpoint > 0x28 break end
end

# pressure sensors
# they are starting at endpoint `40..47` (8 max)
endpoint = 0x28
for k1:self.k2l(sensors)
var sensor_2 = sensors[k1]
if isinstance(sensor_2, map) && sensor_2.contains("Pressure")
var temp_rule = k1 + "#Pressure"
self.plugins.push(matter.Plugin_Sensor_Pressure(self, endpoint, temp_rule))
tasmota.log(string.format("MTR: Endpoint:%i Pressure (%s)", endpoint, temp_rule), 2)
endpoint += 1
end
if endpoint > 0x2F break end
end

# light sensors
# they are starting at endpoint `48..55` (8 max)
endpoint = 0x30
for k1:self.k2l(sensors)
var sensor_2 = sensors[k1]
if isinstance(sensor_2, map) && sensor_2.contains("Illuminance")
var temp_rule = k1 + "#Illuminance"
self.plugins.push(matter.Plugin_Sensor_Light(self, endpoint, temp_rule))
tasmota.log(string.format("MTR: Endpoint:%i Light (%s)", endpoint, temp_rule), 2)
endpoint += 1
end
if endpoint > 0x38 break end
end

# huidity sensors
# they are starting at endpoint `56..63` (8 max)
endpoint = 0x38
for k1:self.k2l(sensors)
var sensor_2 = sensors[k1]
if isinstance(sensor_2, map) && sensor_2.contains("Humidity")
var temp_rule = k1 + "#Humidity"
self.plugins.push(matter.Plugin_Sensor_Humidity(self, endpoint, temp_rule))
tasmota.log(string.format("MTR: Endpoint:%i Humidity (%s)", endpoint, temp_rule), 2)
endpoint += 1
end
if endpoint > 0x40 break end
end

end

# get keys of a map in sorted order
static def k2l(m) var l=[] if m==nil return l end for k:m.keys() l.push(k) end
for i:1..size(l)-1 var k = l[i] var j = i while (j > 0) && (l[j-1] > k) l[j] = l[j-1] j -= 1 end l[j] = k end return l
end


# keys to llist

end
matter.Device = Matter_Device

Expand Down
44 changes: 31 additions & 13 deletions lib/libesp32/berry_matter/src/embedded/Matter_IM.be
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,11 @@ class Matter_IM
attr_name = attr_name ? " (" + attr_name + ")" : ""
# Special case to report unsupported item, if pi==nil
var res = (pi != nil) ? pi.read_attribute(session, ctx) : nil
var found = true # stop expansion since we have a value
var a1_raw # this is the bytes() block we need to add to response (or nil)
if res != nil
var res_str = str(res) # get the value with anonymous tag before it is tagged, for logging

var a1 = matter.AttributeReportIB()
a1.attribute_data = matter.AttributeDataIB()
a1.attribute_data.data_version = 1
Expand All @@ -233,16 +237,15 @@ class Matter_IM

var a1_tlv = a1.to_TLV()
var a1_len = a1_tlv.encode_len()
var a1_bytes = bytes(a1_len)
var a2 = TLV.create_TLV(TLV.RAW, a1_tlv.tlv2raw(a1_bytes))
var a1_bytes = bytes(a1_len) # pre-size bytes() to the actual size
a1_raw = a1_tlv.tlv2raw(a1_bytes)
# tasmota.log(string.format("MTR: guessed len=%i actual=%i '%s'", a1_len, size(a1_raw), a1_raw.tohex()), 2)

ret.attribute_reports.push(a2)
if !no_log
tasmota.log(string.format("MTR: >Read_Attr (%6i) %s%s - %s", session.local_session_id, str(ctx), attr_name, str(res)), 2)
end
return true # stop expansion since we have a value
tasmota.log(string.format("MTR: >Read_Attr (%6i) %s%s - %s", session.local_session_id, str(ctx), attr_name, res_str), 2)
end
elif ctx.status != nil
if direct
if direct # we report an error only if a concrete direct read, not with wildcards
var a1 = matter.AttributeReportIB()
a1.attribute_status = matter.AttributeStatusIB()
a1.attribute_status.path = matter.AttributePathIB()
Expand All @@ -254,18 +257,33 @@ class Matter_IM

var a1_tlv = a1.to_TLV()
var a1_len = a1_tlv.encode_len()
var a1_bytes = bytes(a1_len)
var a2 = TLV.create_TLV(TLV.RAW, a1_tlv.tlv2raw(a1_bytes))

ret.attribute_reports.push(a2)
var a1_bytes = bytes(a1_len) # pre-size bytes() to the actual size
a1_raw = a1_tlv.tlv2raw(a1_bytes)

tasmota.log(string.format("MTR: >Read_Attr (%6i) %s%s - STATUS: 0x%02X %s", session.local_session_id, str(ctx), attr_name, ctx.status, ctx.status == matter.UNSUPPORTED_ATTRIBUTE ? "UNSUPPORTED_ATTRIBUTE" : ""), 2)
return true
end
else
tasmota.log(string.format("MTR: >Read_Attr (%6i) %s%s - IGNORED", session.local_session_id, str(ctx), attr_name), 2)
# ignore if content is nil and status is undefined
return false
found = false
end

# check if we still have enough room in last block
if a1_raw # do we have bytes to add, and it's not zero size
if size(ret.attribute_reports) == 0
ret.attribute_reports.push(a1_raw) # push raw binary instead of a TLV
else # already blocks present, see if we can add to the latest, or need to create a new block
var last_block = ret.attribute_reports[-1]
if size(last_block) + size(a1_raw) <= matter.IM_ReportData.MAX_MESSAGE
# add to last block
last_block .. a1_raw
else
ret.attribute_reports.push(a1_raw) # push raw binary instead of a TLV
end
end
end

return found # return true if we had a match
end

var endpoints = self.device.get_active_endpoints()
Expand Down
23 changes: 4 additions & 19 deletions lib/libesp32/berry_matter/src/embedded/Matter_IM_Message.be
Original file line number Diff line number Diff line change
Expand Up @@ -171,26 +171,11 @@ class Matter_IM_ReportData : Matter_IM_Message
var data = self.data # TLV data of the response (if any)
var was_chunked = data.more_chunked_messages # is this following a chunked packet?

# compute the acceptable size
var msg_sz = 0 # message size up to now
var elements = 0 # number of elements added
var sz_attribute_reports = (data.attribute_reports != nil) ? size(data.attribute_reports) : 0
if sz_attribute_reports > 0
msg_sz = data.attribute_reports[0].to_TLV().encode_len()
elements = 1
end
while msg_sz < self.MAX_MESSAGE && elements < sz_attribute_reports
var next_sz = data.attribute_reports[elements].to_TLV().encode_len()
if msg_sz + next_sz < self.MAX_MESSAGE
msg_sz += next_sz
elements += 1
else
break
end
end
# the message were grouped by right-sized binaries upfront, we just need to send one block at time
var elements = 1 # number of elements added

# tasmota.log(string.format("MTR: exch=%i elements=%i msg_sz=%i total=%i", self.get_exchangeid(), elements, msg_sz, sz_attribute_reports), 3)
var next_elemnts = []
var next_elemnts
if data.attribute_reports != nil
next_elemnts = data.attribute_reports[elements .. ]
data.attribute_reports = data.attribute_reports[0 .. elements - 1]
Expand Down Expand Up @@ -222,7 +207,7 @@ class Matter_IM_ReportData : Matter_IM_Message
responder.send_response_frame(resp)
self.last_counter = resp.message_counter

if size(next_elemnts) > 0
if next_elemnts != nil && size(next_elemnts) > 0
data.attribute_reports = next_elemnts
tasmota.log(string.format("MTR: to_be_sent_later size=%i exch=%i", size(data.attribute_reports), resp.exchange_id), 3)
self.ready = false # wait for Status Report before continuing sending
Expand Down
76 changes: 76 additions & 0 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Sensor.be
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#
# Matter_Plugin_Sensor.be - implements the behavior for a generic Sensor
#
# 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,weak

class Matter_Plugin_Sensor : Matter_Plugin_Device
var tasmota_sensor_filter # Rule-type filter to the value, like "ESP32#Temperature"
var tasmota_sensor_matcher # Actual matcher object
var shadow_value # Last known value

#############################################################
# Constructor
def init(device, endpoint, sensor_filter)
super(self).init(device, endpoint)
self.tasmota_sensor_filter = sensor_filter
self.tasmota_sensor_matcher = tasmota.Rule_Matcher.parse(sensor_filter)
end

#############################################################
# parse sensor
#
# The device calls regularly `tasmota.read_sensors()` and converts
# it to json.
def parse_sensors(payload)
if self.tasmota_sensor_matcher
var val = self.pre_value(real(self.tasmota_sensor_matcher.match(payload)))
if val != nil
if val != self.shadow_value
self.valued_changed(val)
end
self.shadow_value = val
end
end
end

#############################################################
# Called when the value changed compared to shadow value
#
# This must be overriden.
# This is where you call `self.attribute_updated(nil, <cluster>, <attribute>)`
def valued_changed(val)
# self.attribute_updated(nil, 0x0402, 0x0000)
end

#############################################################
# Pre-process value
#
# This must be overriden.
# This allows to convert the raw sensor value to the target one, typically int
def pre_value(val)
return val
end

end
matter.Plugin_Sensor = Matter_Plugin_Sensor
Loading