1212-- See the License for the specific language governing permissions and
1313-- limitations under the License.
1414
15- -- ZCL
15+ local battery_defaults = require " st.zigbee.defaults.battery_defaults"
16+ local capabilities = require " st.capabilities"
1617local zcl_clusters = require " st.zigbee.zcl.clusters"
17- local TemperatureMeasurement = zcl_clusters .TemperatureMeasurement
18+ local device_management = require " st.zigbee.device_management"
19+
20+ local IASZone = zcl_clusters .IASZone
21+ local IlluminanceMeasurement = zcl_clusters .IlluminanceMeasurement
1822local OccupancySensing = zcl_clusters .OccupancySensing
1923local PowerConfiguration = zcl_clusters .PowerConfiguration
20- local battery_defaults = require " st.zigbee.defaults.battery_defaults "
24+ local TemperatureMeasurement = zcl_clusters . TemperatureMeasurement
2125
22- local capabilities = require " st.capabilities"
26+ local BATTERY_MIN_VOLTAGE = 2.3
27+ local BATTERY_MAX_VOLTAGE = 3.0
28+ local DEFAULT_OCCUPIED_TO_UNOCCUPIED_DELAY = 240
29+ local DEFAULT_UNOCCUPIED_TO_OCCUPIED_DELAY = 0
30+ local DEFAULT_UNOCCUPIED_TO_OCCUPIED_THRESHOLD = 0
2331
24- local FRIENT_TEMP_CONFIG = {
25- minimum_interval = 30 ,
26- maximum_interval = 300 ,
27- reportable_change = 100 ,
28- endpoint = 0x26
29- }
32+ local OCCUPANCY_ENDPOINT = 0x22
33+ local TAMPER_ENDPOINT = 0x23
34+ local POWER_CONFIGURATION_ENDPOINT = 0x23
35+ local TEMPERATURE_ENDPOINT = 0x26
36+ local ILLUMINANCE_ENDPOINT = 0x27
3037
31- local FRIENT_BATTERY_CONFIG = {
32- minimum_interval = 30 ,
33- maximum_interval = 21600 ,
34- reportable_change = 1 ,
35- endpoint = 0x23
38+ local FRIENT_DEVICE_FINGERPRINTS = {
39+ { mfr = " frient A/S" , model = " MOSZB-140" },
40+ { mfr = " frient A/S" , model = " MOSZB-141" }
3641}
3742
43+ local function can_handle_frient_motion_sensor (opts , driver , device )
44+ for _ , fingerprint in ipairs (FRIENT_DEVICE_FINGERPRINTS ) do
45+ if device :get_manufacturer () == fingerprint .mfr and device :get_model () == fingerprint .model then
46+ return true
47+ end
48+ end
49+ return false
50+ end
51+
3852local function occupancy_attr_handler (driver , device , occupancy , zb_rx )
39- device :emit_event (
40- occupancy .value == 1 and capabilities .motionSensor .motion .active () or capabilities .motionSensor .motion .inactive ()
41- )
53+ device :emit_event (occupancy .value == 0x01 and capabilities .motionSensor .motion .active () or capabilities .motionSensor .motion .inactive ())
54+ end
55+
56+ local function generate_event_from_zone_status (driver , device , zone_status , zb_rx )
57+ device :emit_event (zone_status :is_tamper_set () and capabilities .tamperAlert .tamper .detected () or capabilities .tamperAlert .tamper .clear ())
58+ end
59+
60+ local function ias_zone_status_attr_handler (driver , device , attr_val , zb_rx )
61+ generate_event_from_zone_status (driver , device , attr_val , zb_rx )
62+ end
63+
64+ local function ias_zone_status_change_handler (driver , device , zb_rx )
65+ generate_event_from_zone_status (driver , device , zb_rx .body .zcl_body .zone_status , zb_rx )
66+ end
67+
68+
69+ local CONFIGURATIONS = {
70+ [OCCUPANCY_ENDPOINT ] = {
71+ cluster = OccupancySensing .ID ,
72+ attribute = OccupancySensing .attributes .Occupancy .ID ,
73+ data_type = OccupancySensing .attributes .Occupancy .base_type ,
74+ minimum_interval = 0 ,
75+ maximum_interval = 3600 ,
76+ endpoint = OCCUPANCY_ENDPOINT
77+ },
78+ [TAMPER_ENDPOINT ] = {
79+ cluster = IASZone .ID ,
80+ attribute = IASZone .attributes .ZoneStatus .ID ,
81+ minimum_interval = 30 ,
82+ maximum_interval = 300 ,
83+ data_type = IASZone .attributes .ZoneStatus .base_type ,
84+ reportable_change = 1 ,
85+ endpoint = TAMPER_ENDPOINT
86+ },
87+ [TEMPERATURE_ENDPOINT ] = {
88+ cluster = TemperatureMeasurement .ID ,
89+ attribute = TemperatureMeasurement .attributes .MeasuredValue .ID ,
90+ minimum_interval = 30 ,
91+ maximum_interval = 3600 ,
92+ data_type = TemperatureMeasurement .attributes .MeasuredValue .base_type ,
93+ reportable_change = 10 ,
94+ endpoint = TEMPERATURE_ENDPOINT
95+ },
96+ [ILLUMINANCE_ENDPOINT ] = {
97+ cluster = IlluminanceMeasurement .ID ,
98+ attribute = IlluminanceMeasurement .attributes .MeasuredValue .ID ,
99+ data_type = IlluminanceMeasurement .attributes .MeasuredValue .base_type ,
100+ minimum_interval = 10 ,
101+ maximum_interval = 3600 ,
102+ reportable_change = 0x2711 ,
103+ endpoint = ILLUMINANCE_ENDPOINT
104+ }
105+ }
106+
107+ local function device_init (driver , device )
108+ battery_defaults .build_linear_voltage_init (BATTERY_MIN_VOLTAGE , BATTERY_MAX_VOLTAGE )(driver , device )
109+
110+ local attribute
111+ attribute = CONFIGURATIONS [OCCUPANCY_ENDPOINT ]
112+ -- binding is directly triggered for specific endpoint in do_configure
113+ device :add_monitored_attribute (attribute )
114+
115+ if device :supports_capability_by_id (capabilities .temperatureMeasurement .ID ) then
116+ attribute = CONFIGURATIONS [TEMPERATURE_ENDPOINT ]
117+ device :add_configured_attribute (attribute )
118+ device :add_monitored_attribute (attribute )
119+ end
120+ if device :supports_capability_by_id (capabilities .illuminanceMeasurement .ID ) then
121+ attribute = CONFIGURATIONS [ILLUMINANCE_ENDPOINT ]
122+ device :add_configured_attribute (attribute )
123+ device :add_monitored_attribute (attribute )
124+ end
125+ if device :supports_capability_by_id (capabilities .tamperAlert .ID ) then
126+ attribute = CONFIGURATIONS [TAMPER_ENDPOINT ]
127+ device :add_configured_attribute (attribute )
128+ device :add_monitored_attribute (attribute )
129+ end
42130end
43131
132+ local function device_added (driver , device )
133+ device :emit_event (capabilities .motionSensor .motion .inactive ())
134+ if device :supports_capability_by_id (capabilities .tamperAlert .ID ) then
135+ device :emit_event (capabilities .tamperAlert .tamper .clear ())
136+ end
137+ end
138+
139+ local function do_refresh (driver , device )
140+ device :send (OccupancySensing .attributes .Occupancy :read (device ):to_endpoint (OCCUPANCY_ENDPOINT ))
141+ device :send (PowerConfiguration .attributes .BatteryVoltage :read (device ):to_endpoint (POWER_CONFIGURATION_ENDPOINT ))
142+
143+ if device :supports_capability_by_id (capabilities .temperatureMeasurement .ID ) then
144+ device :send (TemperatureMeasurement .attributes .MeasuredValue :read (device ):to_endpoint (TEMPERATURE_ENDPOINT ))
145+ end
146+ if device :supports_capability_by_id (capabilities .illuminanceMeasurement .ID ) then
147+ device :send (IlluminanceMeasurement .attributes .MeasuredValue :read (device ):to_endpoint (ILLUMINANCE_ENDPOINT ))
148+ end
149+ if device :supports_capability_by_id (capabilities .tamperAlert .ID ) then
150+ device :send (IASZone .attributes .ZoneStatus :read (device ):to_endpoint (TAMPER_ENDPOINT ))
151+ end
152+ end
153+
154+
44155local function do_configure (driver , device )
45156 device :configure ()
46- device :send (PowerConfiguration .attributes .BatteryVoltage :configure_reporting (
47- device ,
48- FRIENT_BATTERY_CONFIG .minimum_interval ,
49- FRIENT_BATTERY_CONFIG .maximum_interval ,
50- FRIENT_BATTERY_CONFIG .reportable_change
51- ):to_endpoint (FRIENT_BATTERY_CONFIG .endpoint ))
52- device :send (TemperatureMeasurement .attributes .MeasuredValue :configure_reporting (
53- device ,
54- FRIENT_TEMP_CONFIG .minimum_interval ,
55- FRIENT_TEMP_CONFIG .maximum_interval ,
56- FRIENT_TEMP_CONFIG .reportable_change
57- ):to_endpoint (FRIENT_TEMP_CONFIG .endpoint ))
157+ device :send (device_management .build_bind_request (
158+ device ,
159+ zcl_clusters .OccupancySensing .ID ,
160+ driver .environment_info .hub_zigbee_eui ,
161+ OCCUPANCY_ENDPOINT
162+ ))
163+
164+ device :send (OccupancySensing .attributes .PIROccupiedToUnoccupiedDelay :write (device , tonumber (DEFAULT_OCCUPIED_TO_UNOCCUPIED_DELAY )):to_endpoint (OCCUPANCY_ENDPOINT ))
165+ device :send (OccupancySensing .attributes .PIRUnoccupiedToOccupiedDelay :write (device , tonumber (DEFAULT_UNOCCUPIED_TO_OCCUPIED_DELAY )):to_endpoint (OCCUPANCY_ENDPOINT ))
166+ device :send (OccupancySensing .attributes .PIRUnoccupiedToOccupiedThreshold :write (device , tonumber (DEFAULT_UNOCCUPIED_TO_OCCUPIED_THRESHOLD )):to_endpoint (OCCUPANCY_ENDPOINT ))
167+ device :send (OccupancySensing .attributes .Occupancy :configure_reporting (device , 0 , 3600 ):to_endpoint (OCCUPANCY_ENDPOINT ))
168+
169+ device .thread :call_with_delay (5 , function ()
170+ do_refresh (driver , device )
171+ end )
58172end
59173
60- local function added_handler (driver , device )
61- device :refresh ()
174+ local function info_changed (driver , device , event , args )
175+ for name , value in pairs (device .preferences ) do
176+ if (device .preferences [name ] ~= nil and args .old_st_store .preferences [name ] ~= device .preferences [name ]) then
177+ if (name == " temperatureSensitivity" ) then
178+ local input = device .preferences .temperatureSensitivity
179+ local temperatureSensitivity = math.floor (input * 100 + 0.5 )
180+ device :send (TemperatureMeasurement .attributes .MeasuredValue :configure_reporting (device , 30 , 3600 , temperatureSensitivity ):to_endpoint (TEMPERATURE_ENDPOINT ))
181+ elseif (name == " occupiedToUnoccupiedD" ) then
182+ local occupiedToUnoccupiedDelay = device .preferences .occupiedToUnoccupiedD or DEFAULT_OCCUPIED_TO_UNOCCUPIED_DELAY
183+ device :send (OccupancySensing .attributes .PIROccupiedToUnoccupiedDelay :write (device , occupiedToUnoccupiedDelay ):to_endpoint (OCCUPANCY_ENDPOINT ))
184+ elseif (name == " unoccupiedToOccupiedD" ) then
185+ local occupiedToUnoccupiedD = device .preferences .unoccupiedToOccupiedD or DEFAULT_UNOCCUPIED_TO_OCCUPIED_DELAY
186+ device :send (OccupancySensing .attributes .PIRUnoccupiedToOccupiedDelay :write (device , occupiedToUnoccupiedD ):to_endpoint (OCCUPANCY_ENDPOINT ))
187+ elseif (name == " unoccupiedToOccupiedT" ) then
188+ local unoccupiedToOccupiedThreshold = device .preferences .unoccupiedToOccupiedT or DEFAULT_UNOCCUPIED_TO_OCCUPIED_THRESHOLD
189+ device :send (OccupancySensing .attributes .PIRUnoccupiedToOccupiedThreshold :write (device ,unoccupiedToOccupiedThreshold ):to_endpoint (OCCUPANCY_ENDPOINT ))
190+ end
191+ end
192+ end
62193end
63194
64- local frient_driver = {
65- NAME = " Frient Sensor" ,
195+ local frient_motion_driver = {
196+ NAME = " frient motion driver" ,
197+ lifecycle_handlers = {
198+ added = device_added ,
199+ doConfigure = do_configure ,
200+ init = device_init ,
201+ infoChanged = info_changed
202+ },
66203 zigbee_handlers = {
204+ cluster = {
205+ [IASZone .ID ] = {
206+ [IASZone .client .commands .ZoneStatusChangeNotification .ID ] = ias_zone_status_change_handler
207+ }
208+ },
67209 attr = {
68210 [OccupancySensing .ID ] = {
69211 [OccupancySensing .attributes .Occupancy .ID ] = occupancy_attr_handler
212+ },
213+ [IASZone .ID ] = {
214+ [IASZone .attributes .ZoneStatus .ID ] = ias_zone_status_attr_handler
70215 }
71216 }
72217 },
73- lifecycle_handlers = {
74- init = battery_defaults . build_linear_voltage_init ( 2.3 , 3.0 ),
75- added = added_handler ,
76- doConfigure = do_configure
218+ capability_handlers = {
219+ [ capabilities . refresh . ID ] = {
220+ [ capabilities . refresh . commands . refresh . NAME ] = do_refresh
221+ }
77222 },
78- can_handle = function (opts , driver , device , ...)
79- return device :get_manufacturer () == " frient A/S"
80- end
223+ can_handle = can_handle_frient_motion_sensor
81224}
82-
83- return frient_driver
225+ return frient_motion_driver
0 commit comments