Matter ability to add or remove endpoint in bridge mode (code only) (#18790)

This commit is contained in:
s-hadinger 2023-06-04 19:35:36 +02:00 committed by GitHub
parent c09165c7b2
commit e66439cb50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 4336 additions and 3952 deletions

View File

@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
## [12.5.0.4]
### Added
- Matter ability to add or remove endpoint in bridge mode (code only)
### Breaking Changed

View File

@ -62,6 +62,7 @@ class Matter_Device
var root_discriminator # as `int`
var root_passcode # as `int`
var ipv4only # advertize only IPv4 addresses (no IPv6)
var next_ep # next endpoint to be allocated for bridge, start at 51
# context for PBKDF
var root_iterations # PBKDF number of iterations
# PBKDF information used only during PASE (freed afterwards)
@ -87,6 +88,7 @@ class Matter_Device
self.vendorid = self.VENDOR_ID
self.productid = self.PRODUCT_ID
self.root_iterations = self.PBKDF_ITERATIONS
self.next_ep = 51 # start at endpoint 51 for dynamically allocated endpoints
self.root_salt = crypto.random(16)
self.ipv4only = false
self.load_param()
@ -128,6 +130,7 @@ class Matter_Device
self.autoconf_device()
# for now read sensors every 30 seconds
# TODO still needed?
tasmota.add_cron("*/30 * * * * *", def () self._trigger_read_sensors() end, "matter_sensors_30s")
self._start_udp(self.UDP_PORT)
@ -610,7 +613,7 @@ class Matter_Device
def save_param()
import string
import json
var j = string.format('{"distinguish":%i,"passcode":%i,"ipv4only":%s', self.root_discriminator, self.root_passcode, self.ipv4only ? 'true':'false')
var j = string.format('{"distinguish":%i,"passcode":%i,"ipv4only":%s,"nextep":%i', self.root_discriminator, self.root_passcode, self.ipv4only ? 'true':'false', self.next_ep)
if self.plugins_persist
j += ',"config":'
j += json.dump(self.plugins_config)
@ -638,16 +641,17 @@ class Matter_Device
var f = open(self.FILENAME)
var s = f.read()
f.close()
import json
var j = json.load(s)
self.root_discriminator = j.find("distinguish", self.root_discriminator)
self.root_passcode = j.find("passcode", self.root_passcode)
self.ipv4only = bool(j.find("ipv4only", false))
self.next_ep = j.find("nextep", self.next_ep)
self.plugins_config = j.find("config")
if self.plugins_config
self._load_plugins_config(self.plugins_config)
if self.plugins_config != nil
tasmota.log("MTR: load_config = " + str(self.plugins_config), 3)
self.adjust_next_ep()
self.plugins_persist = true
end
except .. as e, m
@ -674,7 +678,7 @@ class Matter_Device
# 'config' is a map
# Ex:
# {'32': {'filter': 'AXP192#Temperature', 'type': 'temperature'}, '40': {'filter': 'BMP280#Pressure', 'type': 'pressure'}, '34': {'filter': 'SHT3X#Temperature', 'type': 'temperature'}, '33': {'filter': 'BMP280#Temperature', 'type': 'temperature'}, '1': {'relay': 0, 'type': 'relay'}, '56': {'filter': 'SHT3X#Humidity', 'type': 'humidity'}, '0': {'type': 'root'}}
def _load_plugins_config(config)
def _instantiate_plugins_from_config(config)
import string
var endpoints = self.k2l_num(config)
@ -995,9 +999,12 @@ class Matter_Device
if size(self.plugins) > 0 return end # already configured
self.plugins_config = self.autoconf_device_map()
tasmota.log("MTR: autoconfig = " + str(self.plugins_config), 3)
self._load_plugins_config(self.plugins_config)
if !self.plugins_persist
self.plugins_config = self.autoconf_device_map()
self.adjust_next_ep()
tasmota.log("MTR: autoconfig = " + str(self.plugins_config), 3)
end
self._instantiate_plugins_from_config(self.plugins_config)
if !self.plugins_persist && self.sessions.count_active_fabrics() > 0
self.plugins_persist = true
@ -1084,8 +1091,6 @@ class Matter_Device
var sensors = json.load(tasmota.read_sensors())
# temperature sensors
# they are starting at endpoint `32..39` (8 max)
endpoint = 0x20
for k1:self.k2l(sensors)
var sensor_2 = sensors[k1]
if isinstance(sensor_2, map) && sensor_2.contains("Temperature")
@ -1093,12 +1098,9 @@ class Matter_Device
m[str(endpoint)] = {'type':'temperature','filter':temp_rule}
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")
@ -1106,12 +1108,9 @@ class Matter_Device
m[str(endpoint)] = {'type':'pressure','filter':temp_rule}
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")
@ -1119,12 +1118,9 @@ class Matter_Device
m[str(endpoint)] = {'type':'illuminance','filter':temp_rule}
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")
@ -1132,9 +1128,8 @@ class Matter_Device
m[str(endpoint)] = {'type':'humidity','filter':temp_rule}
endpoint += 1
end
if endpoint > 0x40 break end
end
# tasmota.publish_result('{"Matter":{"Initialized":1}}', 'Matter')
# tasmota.publish_result('{"Matter":{"Initialized":1}}', 'Matter') # MQTT is not yet connected
return m
end
@ -1195,6 +1190,103 @@ class Matter_Device
tasmota.log("MTR: registered classes "+str(self.k2l(self.plugins_classes)), 3)
end
#############################################################
# Dynamic adding and removal of endpoints (bridge mode)
#############################################################
# Add endpoint
#
# Args:
# `pi_class_name`: name of the type of pluging, ex: `light3`
# `plugin_conf`: map of configuration as native Berry map
# returns endpoint number newly allocated, or `nil` if failed
def bridge_add_endpoint(pi_class_name, plugin_conf)
var pi_class = self.plugins_classes.find(pi_class_name)
if pi_class == nil tasmota.log("MTR: unknown class name '"+str(pi_class_name)+"' skipping", 2) return end
# get the next allocated endpoint number
var ep = self.next_ep
var ep_str = str(ep)
var pi = pi_class(self, ep, plugin_conf)
self.plugins.push(pi)
# add to in-memoru config
# Example: {'filter': 'AXP192#Temperature', 'type': 'temperature'}
var pi_conf = {'type': pi_class_name}
# copy args
for k:plugin_conf.keys()
pi_conf[k] = plugin_conf[k]
end
# add to main
self.plugins_config[ep_str] = pi_conf
self.plugins_persist = true
self.next_ep += 1 # increment next allocated endpoint before saving
# try saving parameters
self.save_param()
self.signal_endpoints_changed()
return ep
end
#############################################################
# Remove an existing endpoint
#
def bridge_remove_endpoint(ep)
import string
import json
var ep_str = str(ep)
var config
var f_in
if !self.plugins_config.contains(ep_str)
tasmota.log("MTR: Cannot remove an enpoint not configured: " + ep_str, 3)
return
end
self.plugins_config.remove(ep_str)
self.plugins_persist = true
# try saving parameters
self.save_param()
self.signal_endpoints_changed()
# now remove from in-memory configuration
var idx = 0
while idx < size(self.plugins)
if ep == self.plugins[idx].get_endpoint()
self.plugins.remove(idx)
self.signal_endpoints_changed()
break
else
idx += 1
end
end
end
#############################################################
# Signal to controller that endpoints changed via subcriptions
#
def signal_endpoints_changed()
# mark parts lists as changed
self.attribute_updated(0x0000, 0x001D, 0x0003, false)
self.attribute_updated(0xFF00, 0x001D, 0x0003, false)
end
#############################################################
# Adjust next_ep
#
# Make sure that next_ep (used to allow dynamic endpoints)
# will not collide with an existing ep
def adjust_next_ep()
for k: self.plugins_config.keys()
var ep = int(k)
if ep >= self.next_ep
self.next_ep = ep + 1
end
end
end
#####################################################################
# Events
#####################################################################

View File

@ -205,7 +205,7 @@ class Matter_Fabric : Matter_Expirable
# Called before removal
def log_new_fabric()
import string
tasmota.log(string.format("MTR: +Fabric fab='%s'", self.get_fabric_id().copy().reverse().tohex()), 2)
tasmota.log(string.format("MTR: +Fabric fab='%s' vendorid=0x%04X", self.get_fabric_id().copy().reverse().tohex(), self.admin_vendor), 2)
end
#############################################################

View File

@ -50,7 +50,7 @@ class Matter_Plugin_Aggregator : Matter_Plugin
var pl = TLV.Matter_TLV_array()
var eps = self.device.get_active_endpoints(true)
for ep: eps
if ep != 0xFF00
if ep < 0xFF00
pl.add_TLV(nil, TLV.U2, ep) # add each endpoint
end
end

View File

@ -205,7 +205,7 @@ class Matter_UI
if !label label = "<No label>" end
label = webserver.html_escape(label) # protect against HTML injection
webserver.content_send(string.format("<fieldset><legend><b>&nbsp;#%i %s&nbsp;</b></legend><p></p>", f.get_fabric_index(), label))
webserver.content_send(string.format("<fieldset><legend><b>&nbsp;#%i %s</b> (0x%04X)&nbsp;</legend><p></p>", f.get_fabric_index(), label, f.get_admin_vendor()))
var fabric_rev = f.get_fabric_id().copy().reverse()
var deviceid_rev = f.get_device_id().copy().reverse()
@ -235,7 +235,7 @@ class Matter_UI
webserver.content_send("<form action='/matterc' method='post'")
webserver.content_send("onsubmit='return confirm(\"This will RESET the configuration to the default. You will need to associate again.\");'>")
webserver.content_send("<button name='auto' class='button bred'>Reset to default</button><p></p></form>")
webserver.content_send("<button name='auto' class='button bred'>Reset and Auto-discover</button><p></p></form>")
webserver.content_send("<form action='/matterc' method='post'")
webserver.content_send("onsubmit='return confirm(\"Changing the configuration requires to associate again.\");'>")

View File

@ -307,21 +307,22 @@ be_local_closure(Matter_Fabric_log_new_fabric, /* name */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[10]) { /* constants */
( &(const bvalue[11]) { /* constants */
/* K0 */ be_nested_str_weak(string),
/* K1 */ be_nested_str_weak(tasmota),
/* K2 */ be_nested_str_weak(log),
/* K3 */ be_nested_str_weak(format),
/* K4 */ be_nested_str_weak(MTR_X3A_X20_X2BFabric_X20_X20_X20_X20fab_X3D_X27_X25s_X27),
/* K4 */ be_nested_str_weak(MTR_X3A_X20_X2BFabric_X20_X20_X20_X20fab_X3D_X27_X25s_X27_X20vendorid_X3D0x_X2504X),
/* K5 */ be_nested_str_weak(get_fabric_id),
/* K6 */ be_nested_str_weak(copy),
/* K7 */ be_nested_str_weak(reverse),
/* K8 */ be_nested_str_weak(tohex),
/* K9 */ be_const_int(2),
/* K9 */ be_nested_str_weak(admin_vendor),
/* K10 */ be_const_int(2),
}),
be_str_weak(log_new_fabric),
&be_const_str_solidified,
( &(const binstruction[17]) { /* code */
( &(const binstruction[18]) { /* code */
0xA4060000, // 0000 IMPORT R1 K0
0xB80A0200, // 0001 GETNGBL R2 K1
0x8C080502, // 0002 GETMET R2 R2 K2
@ -335,10 +336,11 @@ be_local_closure(Matter_Fabric_log_new_fabric, /* name */
0x7C1C0200, // 000A CALL R7 1
0x8C1C0F08, // 000B GETMET R7 R7 K8
0x7C1C0200, // 000C CALL R7 1
0x7C100600, // 000D CALL R4 3
0x58140009, // 000E LDCONST R5 K9
0x7C080600, // 000F CALL R2 3
0x80000000, // 0010 RET 0
0x88200109, // 000D GETMBR R8 R0 K9
0x7C100800, // 000E CALL R4 4
0x5814000A, // 000F LDCONST R5 K10
0x7C080600, // 0010 CALL R2 3
0x80000000, // 0011 RET 0
})
)
);

View File

@ -60,7 +60,7 @@ be_local_closure(Matter_Plugin_Aggregator_read_attribute, /* name */
0x5C281200, // 0014 MOVE R10 R9
0x7C280000, // 0015 CALL R10 0
0x542EFEFF, // 0016 LDINT R11 65280
0x202C140B, // 0017 NE R11 R10 R11
0x142C140B, // 0017 LT R11 R10 R11
0x782E0004, // 0018 JMPF R11 #001E
0x8C2C0F09, // 0019 GETMET R11 R7 K9
0x4C340000, // 001A LDNIL R13

View File

@ -884,7 +884,7 @@ be_local_closure(Matter_UI_show_plugins_configuration, /* name */
/* K3 */ be_nested_str_weak(_X3Cfieldset_X3E_X3Clegend_X3E_X3Cb_X3E_X26nbsp_X3BCurrent_X20Configuration_X26nbsp_X3B_X3C_X2Fb_X3E_X3C_X2Flegend_X3E_X3Cp_X3E_X3C_X2Fp_X3E),
/* K4 */ be_nested_str_weak(_X3Cform_X20action_X3D_X27_X2Fmatterc_X27_X20method_X3D_X27post_X27),
/* K5 */ be_nested_str_weak(onsubmit_X3D_X27return_X20confirm_X28_X22This_X20will_X20RESET_X20the_X20configuration_X20to_X20the_X20default_X2E_X20You_X20will_X20need_X20to_X20associate_X20again_X2E_X22_X29_X3B_X27_X3E),
/* K6 */ be_nested_str_weak(_X3Cbutton_X20name_X3D_X27auto_X27_X20class_X3D_X27button_X20bred_X27_X3EReset_X20to_X20default_X3C_X2Fbutton_X3E_X3Cp_X3E_X3C_X2Fp_X3E_X3C_X2Fform_X3E),
/* K6 */ be_nested_str_weak(_X3Cbutton_X20name_X3D_X27auto_X27_X20class_X3D_X27button_X20bred_X27_X3EReset_X20and_X20Auto_X2Ddiscover_X3C_X2Fbutton_X3E_X3Cp_X3E_X3C_X2Fp_X3E_X3C_X2Fform_X3E),
/* K7 */ be_nested_str_weak(onsubmit_X3D_X27return_X20confirm_X28_X22Changing_X20the_X20configuration_X20requires_X20to_X20associate_X20again_X2E_X22_X29_X3B_X27_X3E),
/* K8 */ be_nested_str_weak(_X3Ctable_X20style_X3D_X27width_X3A100_X25_X27_X3E),
/* K9 */ be_nested_str_weak(_X3Ctr_X3E_X3Ctd_X20width_X3D_X2735_X27_X3E_X3Cb_X3EEp_X2E_X3C_X2Fb_X3E_X3C_X2Ftd_X3E_X3Ctd_X3E_X3Cb_X3EType_X3C_X2Fb_X3E_X3C_X2Ftd_X3E_X3Ctd_X3E_X3Cb_X3EParam_X3C_X2Fb_X3E_X3C_X2Ftd_X3E_X3C_X2Ftr_X3E),
@ -1488,7 +1488,7 @@ be_local_closure(Matter_UI_show_fabric_info, /* name */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[30]) { /* constants */
( &(const bvalue[31]) { /* constants */
/* K0 */ be_nested_str_weak(webserver),
/* K1 */ be_nested_str_weak(string),
/* K2 */ be_nested_str_weak(content_send),
@ -1505,24 +1505,25 @@ be_local_closure(Matter_UI_show_fabric_info, /* name */
/* K13 */ be_nested_str_weak(_X3CNo_X20label_X3E),
/* K14 */ be_nested_str_weak(html_escape),
/* K15 */ be_nested_str_weak(format),
/* K16 */ be_nested_str_weak(_X3Cfieldset_X3E_X3Clegend_X3E_X3Cb_X3E_X26nbsp_X3B_X23_X25i_X20_X25s_X26nbsp_X3B_X3C_X2Fb_X3E_X3C_X2Flegend_X3E_X3Cp_X3E_X3C_X2Fp_X3E),
/* K16 */ be_nested_str_weak(_X3Cfieldset_X3E_X3Clegend_X3E_X3Cb_X3E_X26nbsp_X3B_X23_X25i_X20_X25s_X3C_X2Fb_X3E_X20_X280x_X2504X_X29_X26nbsp_X3B_X3C_X2Flegend_X3E_X3Cp_X3E_X3C_X2Fp_X3E),
/* K17 */ be_nested_str_weak(get_fabric_index),
/* K18 */ be_nested_str_weak(get_fabric_id),
/* K19 */ be_nested_str_weak(copy),
/* K20 */ be_nested_str_weak(reverse),
/* K21 */ be_nested_str_weak(get_device_id),
/* K22 */ be_nested_str_weak(Fabric_X3A_X20_X25s_X3Cbr_X3E),
/* K23 */ be_nested_str_weak(tohex),
/* K24 */ be_nested_str_weak(Device_X3A_X20_X25s_X3Cbr_X3E_X26nbsp_X3B),
/* K25 */ be_nested_str_weak(_X3Cform_X20action_X3D_X27_X2Fmatterc_X27_X20method_X3D_X27post_X27_X20onsubmit_X3D_X27return_X20confirm_X28_X22Are_X20you_X20sure_X3F_X22_X29_X3B_X27_X3E),
/* K26 */ be_nested_str_weak(_X3Cinput_X20name_X3D_X27del_fabric_X27_X20type_X3D_X27hidden_X27_X20value_X3D_X27_X25i_X27_X3E),
/* K27 */ be_nested_str_weak(_X3Cbutton_X20name_X3D_X27del_X27_X20class_X3D_X27button_X20bgrn_X27_X3EDelete_X20Fabric_X3C_X2Fbutton_X3E_X3C_X2Fform_X3E_X3C_X2Fp_X3E),
/* K28 */ be_nested_str_weak(_X3Cp_X3E_X3C_X2Fp_X3E_X3C_X2Ffieldset_X3E_X3Cp_X3E_X3C_X2Fp_X3E),
/* K29 */ be_nested_str_weak(stop_iteration),
/* K18 */ be_nested_str_weak(get_admin_vendor),
/* K19 */ be_nested_str_weak(get_fabric_id),
/* K20 */ be_nested_str_weak(copy),
/* K21 */ be_nested_str_weak(reverse),
/* K22 */ be_nested_str_weak(get_device_id),
/* K23 */ be_nested_str_weak(Fabric_X3A_X20_X25s_X3Cbr_X3E),
/* K24 */ be_nested_str_weak(tohex),
/* K25 */ be_nested_str_weak(Device_X3A_X20_X25s_X3Cbr_X3E_X26nbsp_X3B),
/* K26 */ be_nested_str_weak(_X3Cform_X20action_X3D_X27_X2Fmatterc_X27_X20method_X3D_X27post_X27_X20onsubmit_X3D_X27return_X20confirm_X28_X22Are_X20you_X20sure_X3F_X22_X29_X3B_X27_X3E),
/* K27 */ be_nested_str_weak(_X3Cinput_X20name_X3D_X27del_fabric_X27_X20type_X3D_X27hidden_X27_X20value_X3D_X27_X25i_X27_X3E),
/* K28 */ be_nested_str_weak(_X3Cbutton_X20name_X3D_X27del_X27_X20class_X3D_X27button_X20bgrn_X27_X3EDelete_X20Fabric_X3C_X2Fbutton_X3E_X3C_X2Fform_X3E_X3C_X2Fp_X3E),
/* K29 */ be_nested_str_weak(_X3Cp_X3E_X3C_X2Fp_X3E_X3C_X2Ffieldset_X3E_X3Cp_X3E_X3C_X2Fp_X3E),
/* K30 */ be_nested_str_weak(stop_iteration),
}),
be_str_weak(show_fabric_info),
&be_const_str_solidified,
( &(const binstruction[102]) { /* code */
( &(const binstruction[104]) { /* code */
0xA4060000, // 0000 IMPORT R1 K0
0xA40A0200, // 0001 IMPORT R2 K1
0x8C0C0302, // 0002 GETMET R3 R1 K2
@ -1541,7 +1542,7 @@ be_local_closure(Matter_UI_show_fabric_info, /* name */
0x8C0C0302, // 000F GETMET R3 R1 K2
0x58140008, // 0010 LDCONST R5 K8
0x7C0C0400, // 0011 CALL R3 2
0x7002004E, // 0012 JMP #0062
0x70020050, // 0012 JMP #0064
0x500C0200, // 0013 LDBOOL R3 1 0
0x60100010, // 0014 GETGBL R4 G16
0x88140105, // 0015 GETMBR R5 R0 K5
@ -1550,7 +1551,7 @@ be_local_closure(Matter_UI_show_fabric_info, /* name */
0x8C140B0A, // 0018 GETMET R5 R5 K10
0x7C140200, // 0019 CALL R5 1
0x7C100200, // 001A CALL R4 1
0xA8020042, // 001B EXBLK 0 #005F
0xA8020044, // 001B EXBLK 0 #0061
0x5C140800, // 001C MOVE R5 R4
0x7C140000, // 001D CALL R5 0
0x5C180600, // 001E MOVE R6 R3
@ -1573,58 +1574,60 @@ be_local_closure(Matter_UI_show_fabric_info, /* name */
0x8C300B11, // 002F GETMET R12 R5 K17
0x7C300200, // 0030 CALL R12 1
0x5C340C00, // 0031 MOVE R13 R6
0x7C240800, // 0032 CALL R9 4
0x7C1C0400, // 0033 CALL R7 2
0x8C1C0B12, // 0034 GETMET R7 R5 K18
0x7C1C0200, // 0035 CALL R7 1
0x8C1C0F13, // 0036 GETMET R7 R7 K19
0x8C380B12, // 0032 GETMET R14 R5 K18
0x7C380200, // 0033 CALL R14 1
0x7C240A00, // 0034 CALL R9 5
0x7C1C0400, // 0035 CALL R7 2
0x8C1C0B13, // 0036 GETMET R7 R5 K19
0x7C1C0200, // 0037 CALL R7 1
0x8C1C0F14, // 0038 GETMET R7 R7 K20
0x7C1C0200, // 0039 CALL R7 1
0x8C200B15, // 003A GETMET R8 R5 K21
0x7C200200, // 003B CALL R8 1
0x8C201113, // 003C GETMET R8 R8 K19
0x8C1C0F15, // 003A GETMET R7 R7 K21
0x7C1C0200, // 003B CALL R7 1
0x8C200B16, // 003C GETMET R8 R5 K22
0x7C200200, // 003D CALL R8 1
0x8C201114, // 003E GETMET R8 R8 K20
0x7C200200, // 003F CALL R8 1
0x8C240302, // 0040 GETMET R9 R1 K2
0x8C2C050F, // 0041 GETMET R11 R2 K15
0x58340016, // 0042 LDCONST R13 K22
0x8C380F17, // 0043 GETMET R14 R7 K23
0x7C380200, // 0044 CALL R14 1
0x7C2C0600, // 0045 CALL R11 3
0x7C240400, // 0046 CALL R9 2
0x8C240302, // 0047 GETMET R9 R1 K2
0x8C2C050F, // 0048 GETMET R11 R2 K15
0x58340018, // 0049 LDCONST R13 K24
0x8C381117, // 004A GETMET R14 R8 K23
0x7C380200, // 004B CALL R14 1
0x7C2C0600, // 004C CALL R11 3
0x7C240400, // 004D CALL R9 2
0x8C240302, // 004E GETMET R9 R1 K2
0x582C0019, // 004F LDCONST R11 K25
0x7C240400, // 0050 CALL R9 2
0x8C240302, // 0051 GETMET R9 R1 K2
0x8C2C050F, // 0052 GETMET R11 R2 K15
0x5834001A, // 0053 LDCONST R13 K26
0x8C380B11, // 0054 GETMET R14 R5 K17
0x7C380200, // 0055 CALL R14 1
0x7C2C0600, // 0056 CALL R11 3
0x7C240400, // 0057 CALL R9 2
0x8C240302, // 0058 GETMET R9 R1 K2
0x582C001B, // 0059 LDCONST R11 K27
0x7C240400, // 005A CALL R9 2
0x8C240302, // 005B GETMET R9 R1 K2
0x582C001C, // 005C LDCONST R11 K28
0x7C240400, // 005D CALL R9 2
0x7001FFBC, // 005E JMP #001C
0x5810001D, // 005F LDCONST R4 K29
0xAC100200, // 0060 CATCH R4 1 0
0xB0080000, // 0061 RAISE 2 R0 R0
0x8C0C0302, // 0062 GETMET R3 R1 K2
0x5814001C, // 0063 LDCONST R5 K28
0x7C0C0400, // 0064 CALL R3 2
0x80000000, // 0065 RET 0
0x8C201115, // 0040 GETMET R8 R8 K21
0x7C200200, // 0041 CALL R8 1
0x8C240302, // 0042 GETMET R9 R1 K2
0x8C2C050F, // 0043 GETMET R11 R2 K15
0x58340017, // 0044 LDCONST R13 K23
0x8C380F18, // 0045 GETMET R14 R7 K24
0x7C380200, // 0046 CALL R14 1
0x7C2C0600, // 0047 CALL R11 3
0x7C240400, // 0048 CALL R9 2
0x8C240302, // 0049 GETMET R9 R1 K2
0x8C2C050F, // 004A GETMET R11 R2 K15
0x58340019, // 004B LDCONST R13 K25
0x8C381118, // 004C GETMET R14 R8 K24
0x7C380200, // 004D CALL R14 1
0x7C2C0600, // 004E CALL R11 3
0x7C240400, // 004F CALL R9 2
0x8C240302, // 0050 GETMET R9 R1 K2
0x582C001A, // 0051 LDCONST R11 K26
0x7C240400, // 0052 CALL R9 2
0x8C240302, // 0053 GETMET R9 R1 K2
0x8C2C050F, // 0054 GETMET R11 R2 K15
0x5834001B, // 0055 LDCONST R13 K27
0x8C380B11, // 0056 GETMET R14 R5 K17
0x7C380200, // 0057 CALL R14 1
0x7C2C0600, // 0058 CALL R11 3
0x7C240400, // 0059 CALL R9 2
0x8C240302, // 005A GETMET R9 R1 K2
0x582C001C, // 005B LDCONST R11 K28
0x7C240400, // 005C CALL R9 2
0x8C240302, // 005D GETMET R9 R1 K2
0x582C001D, // 005E LDCONST R11 K29
0x7C240400, // 005F CALL R9 2
0x7001FFBA, // 0060 JMP #001C
0x5810001E, // 0061 LDCONST R4 K30
0xAC100200, // 0062 CATCH R4 1 0
0xB0080000, // 0063 RAISE 2 R0 R0
0x8C0C0302, // 0064 GETMET R3 R1 K2
0x5814001D, // 0065 LDCONST R5 K29
0x7C0C0400, // 0066 CALL R3 2
0x80000000, // 0067 RET 0
})
)
);