@@ -21,8 +21,9 @@ local lua_socket = require "socket"
21
21
local utils = require " st.utils"
22
22
local device_lib = require " st.device"
23
23
local embedded_cluster_utils = require " embedded-cluster-utils"
24
- -- Include driver-side definitions when lua libs api version is < 11
25
24
local version = require " version"
25
+
26
+ -- Include driver-side definitions when lua libs api version is < 11
26
27
if version .api < 11 then
27
28
clusters .ElectricalEnergyMeasurement = require " ElectricalEnergyMeasurement"
28
29
clusters .ElectricalPowerMeasurement = require " ElectricalPowerMeasurement"
@@ -515,7 +516,8 @@ local function do_configure(driver, device)
515
516
if device :get_field (BUTTON_DEVICE_PROFILED ) then
516
517
return
517
518
end
518
- local level_eps = embedded_cluster_utils .get_endpoints (device , clusters .LevelControl .ID )
519
+ local fan_eps = device :get_endpoints (clusters .FanControl .ID )
520
+ local level_eps = device :get_endpoints (clusters .LevelControl .ID )
519
521
local energy_eps = embedded_cluster_utils .get_endpoints (device , clusters .ElectricalEnergyMeasurement .ID )
520
522
local power_eps = embedded_cluster_utils .get_endpoints (device , clusters .ElectricalPowerMeasurement .ID )
521
523
local valve_eps = embedded_cluster_utils .get_endpoints (device , clusters .ValveConfigurationAndControl .ID )
@@ -536,6 +538,8 @@ local function do_configure(driver, device)
536
538
{feature_bitmap = clusters .ValveConfigurationAndControl .types .Feature .LEVEL }) > 0 then
537
539
profile_name = profile_name .. " -level"
538
540
end
541
+ elseif # fan_eps > 0 then
542
+ profile_name = " light-color-level-fan"
539
543
end
540
544
541
545
if profile_name then
@@ -585,46 +589,44 @@ local function find_child(parent, ep_id)
585
589
return parent :get_child_by_parent_assigned_key (string.format (" %d" , ep_id ))
586
590
end
587
591
588
- local function try_build_button_component_map (device , main_endpoint , button_eps )
592
+ local function build_button_component_map (device , main_endpoint , button_eps )
589
593
-- create component mapping on the main profile button endpoints
590
- if STATIC_BUTTON_PROFILE_SUPPORTED [# button_eps ] then
591
- local component_map = {}
592
- component_map [" main" ] = main_endpoint
593
- for component_num , ep in ipairs (button_eps ) do
594
- if ep ~= main_endpoint then
595
- local button_component = " button" .. component_num
596
- component_map [button_component ] = ep
594
+ table.sort (button_eps )
595
+ local component_map = {}
596
+ component_map [" main" ] = main_endpoint
597
+ for component_num , ep in ipairs (button_eps ) do
598
+ if ep ~= main_endpoint then
599
+ local button_component = " button"
600
+ if # button_eps > 1 then
601
+ button_component = button_component .. component_num
597
602
end
603
+ component_map [button_component ] = ep
598
604
end
599
- device :set_field (COMPONENT_TO_ENDPOINT_MAP_BUTTON , component_map , {persist = true })
600
605
end
606
+ device :set_field (COMPONENT_TO_ENDPOINT_MAP_BUTTON , component_map , {persist = true })
601
607
end
602
608
603
609
local function build_button_profile (device , main_endpoint , num_button_eps )
604
- local profile_name
605
- local battery_supported
610
+ local profile_name = string.gsub (num_button_eps .. " -button" , " 1%-" , " " ) -- remove the "1-" in a device with 1 button ep
606
611
if device_type_supports_button_switch_combination (device , main_endpoint ) then
607
- profile_name = " light-level-" .. num_button_eps .. " -button"
608
- else
609
- profile_name = num_button_eps .. " -button"
610
- battery_supported = # device :get_endpoints (clusters .PowerSource .ID , {feature_bitmap = clusters .PowerSource .types .PowerSourceFeature .BATTERY }) > 0
611
- if battery_supported then
612
- local attribute_list_read = im .InteractionRequest (im .InteractionRequest .RequestType .READ , {})
613
- attribute_list_read :merge (clusters .PowerSource .attributes .AttributeList :read ())
614
- device :send (attribute_list_read )
615
- end
612
+ profile_name = " light-level-" .. profile_name
616
613
end
617
-
618
- if not battery_supported then -- battery profiles are configured later, in power_source_attribute_list_handler
619
- profile_name = string.gsub (profile_name , " 1%-" , " " ) -- remove the "1-" in a device with 1 button ep
614
+ local battery_supported = # device :get_endpoints (clusters .PowerSource .ID , {feature_bitmap = clusters .PowerSource .types .PowerSourceFeature .BATTERY }) > 0
615
+ if battery_supported then -- battery profiles are configured later, in power_source_attribute_list_handler
616
+ local attribute_list_read = im .InteractionRequest (im .InteractionRequest .RequestType .READ , {})
617
+ attribute_list_read :merge (clusters .PowerSource .attributes .AttributeList :read ())
618
+ device :send (attribute_list_read )
619
+ else
620
620
device :try_update_metadata ({profile = profile_name })
621
621
end
622
622
device :set_field (BUTTON_DEVICE_PROFILED , true )
623
623
end
624
624
625
- local function try_build_child_switch_profiles (driver , device , switch_eps , main_endpoint )
625
+ local function build_child_switch_profiles (driver , device , main_endpoint )
626
626
local num_switch_server_eps = 0
627
627
local parent_child_device = false
628
+ local switch_eps = device :get_endpoints (clusters .OnOff .ID )
629
+ table.sort (switch_eps )
628
630
for _ , ep in ipairs (switch_eps ) do
629
631
if device :supports_server_cluster (clusters .OnOff .ID , ep ) then
630
632
num_switch_server_eps = num_switch_server_eps + 1
@@ -656,58 +658,52 @@ local function try_build_child_switch_profiles(driver, device, switch_eps, main_
656
658
device :set_field (IS_PARENT_CHILD_DEVICE , true , {persist = true })
657
659
end
658
660
659
- device :set_field (SWITCH_INITIALIZED , true , {persist = true })
660
-
661
661
-- this is needed in initialize_buttons_and_switches
662
662
return num_switch_server_eps
663
663
end
664
664
665
- local function handle_light_switch_with_onOff_server_clusters (device , main_endpoint , num_switch_server_eps )
666
- local cluster_id = 0
667
- for _ , ep in ipairs (device .endpoints ) do
668
- -- main_endpoint only supports server cluster by definition of get_endpoints()
669
- if main_endpoint == ep .endpoint_id then
670
- for _ , dt in ipairs (ep .device_types ) do
671
- -- no device type that is not in the switch subset should be considered.
672
- if (ON_OFF_SWITCH_ID <= dt .device_type_id and dt .device_type_id <= ON_OFF_COLOR_DIMMER_SWITCH_ID ) then
673
- cluster_id = math.max (cluster_id , dt .device_type_id )
674
- end
665
+ local function handle_light_switch_with_onOff_server_clusters (device , main_endpoint )
666
+ local cluster_id = 0
667
+ for _ , ep in ipairs (device .endpoints ) do
668
+ -- main_endpoint only supports server cluster by definition of get_endpoints()
669
+ if main_endpoint == ep .endpoint_id then
670
+ for _ , dt in ipairs (ep .device_types ) do
671
+ -- no device type that is not in the switch subset should be considered.
672
+ if (ON_OFF_SWITCH_ID <= dt .device_type_id and dt .device_type_id <= ON_OFF_COLOR_DIMMER_SWITCH_ID ) then
673
+ cluster_id = math.max (cluster_id , dt .device_type_id )
675
674
end
676
- break
677
675
end
676
+ break
678
677
end
678
+ end
679
679
680
- if device_type_profile_map [cluster_id ] then
681
- device :try_update_metadata ({profile = device_type_profile_map [cluster_id ]})
682
- end
680
+ if device_type_profile_map [cluster_id ] then
681
+ device :try_update_metadata ({profile = device_type_profile_map [cluster_id ]})
682
+ end
683
683
end
684
684
685
685
local function initialize_buttons_and_switches (driver , device , main_endpoint )
686
- local switch_eps = device :get_endpoints (clusters .OnOff .ID )
687
686
local button_eps = device :get_endpoints (clusters .Switch .ID , {feature_bitmap = clusters .Switch .types .SwitchFeature .MOMENTARY_SWITCH })
688
- table.sort (switch_eps )
689
- table.sort (button_eps )
690
-
691
- -- All button endpoints found will be added as additional components in the profile containing the main_endpoint.
692
- -- The resulting endpoint to component map is saved in the COMPONENT_TO_ENDPOINT_MAP_BUTTON field
693
- try_build_button_component_map (device , main_endpoint , button_eps )
694
-
695
- -- Without support for bindings, only clusters that are implemented as server are counted. This count is handled
696
- -- while building switch child profiles
697
- local num_switch_server_eps = try_build_child_switch_profiles (driver , device , switch_eps , main_endpoint )
698
-
699
- if # button_eps > 0 then
687
+ if tbl_contains (STATIC_BUTTON_PROFILE_SUPPORTED , # button_eps ) then
700
688
build_button_profile (device , main_endpoint , # button_eps )
689
+ -- All button endpoints found will be added as additional components in the profile containing the main_endpoint.
690
+ -- The resulting endpoint to component map is saved in the COMPONENT_TO_ENDPOINT_MAP_BUTTON field
691
+ build_button_component_map (device , main_endpoint , button_eps )
701
692
configure_buttons (device )
702
- return
703
693
end
704
694
695
+ -- Without support for bindings, only clusters that are implemented as server are counted. This count is handled
696
+ -- while building switch child profiles
697
+ local num_switch_server_eps = build_child_switch_profiles (driver , device , main_endpoint )
698
+
705
699
-- We do not support the Light Switch device types because they require OnOff to be implemented as 'client', which requires us to support bindings.
706
700
-- However, this workaround profiles devices that claim to be Light Switches, but that break spec and implement OnOff as 'server'.
707
701
-- Note: since their device type isn't supported, these devices join as a matter-thing.
708
702
if num_switch_server_eps > 0 and detect_matter_thing (device ) then
709
- handle_light_switch_with_onOff_server_clusters (device , main_endpoint , num_switch_server_eps )
703
+ handle_light_switch_with_onOff_server_clusters (device , main_endpoint )
710
704
end
705
+
706
+ device :set_field (SWITCH_INITIALIZED , true , {persist = true })
711
707
end
712
708
713
709
local function detect_bridge (device )
@@ -881,6 +877,31 @@ local function handle_set_level(driver, device, cmd)
881
877
end
882
878
end
883
879
880
+ local function set_fan_mode (driver , device , cmd )
881
+ local fan_mode_id
882
+ if cmd .args .fanMode == capabilities .fanMode .fanMode .low .NAME then
883
+ fan_mode_id = clusters .FanControl .attributes .FanMode .LOW
884
+ elseif cmd .args .fanMode == capabilities .fanMode .fanMode .medium .NAME then
885
+ fan_mode_id = clusters .FanControl .attributes .FanMode .MEDIUM
886
+ elseif cmd .args .fanMode == capabilities .fanMode .fanMode .high .NAME then
887
+ fan_mode_id = clusters .FanControl .attributes .FanMode .HIGH
888
+ elseif cmd .args .fanMode == capabilities .fanMode .fanMode .auto .NAME then
889
+ fan_mode_id = clusters .FanControl .attributes .FanMode .AUTO
890
+ else
891
+ fan_mode_id = clusters .FanControl .attributes .FanMode .OFF
892
+ end
893
+ if fan_mode_id then
894
+ local fan_ep = device :get_endpoints (clusters .FanControl .ID )[1 ]
895
+ device :send (clusters .FanControl .attributes .FanMode :write (device , fan_ep , fan_mode_id ))
896
+ end
897
+ end
898
+
899
+ local function set_fan_speed_percent (driver , device , cmd )
900
+ local speed = math.floor (cmd .args .percent )
901
+ local fan_ep = device :get_endpoints (clusters .FanControl .ID )[1 ]
902
+ device :send (clusters .FanControl .attributes .PercentSetting :write (device , fan_ep , speed ))
903
+ end
904
+
884
905
local function handle_refresh (driver , device , cmd )
885
906
-- Note: no endpoint specified indicates a wildcard endpoint
886
907
local req = clusters .OnOff .attributes .OnOff :read (device )
@@ -1338,6 +1359,73 @@ local function humidity_attr_handler(driver, device, ib, response)
1338
1359
end
1339
1360
end
1340
1361
1362
+ local function fan_mode_handler (driver , device , ib , response )
1363
+ if ib .data .value == clusters .FanControl .attributes .FanMode .OFF then
1364
+ device :emit_event_for_endpoint (ib .endpoint_id , capabilities .fanMode .fanMode (" off" ))
1365
+ elseif ib .data .value == clusters .FanControl .attributes .FanMode .LOW then
1366
+ device :emit_event_for_endpoint (ib .endpoint_id , capabilities .fanMode .fanMode (" low" ))
1367
+ elseif ib .data .value == clusters .FanControl .attributes .FanMode .MEDIUM then
1368
+ device :emit_event_for_endpoint (ib .endpoint_id , capabilities .fanMode .fanMode (" medium" ))
1369
+ elseif ib .data .value == clusters .FanControl .attributes .FanMode .HIGH then
1370
+ device :emit_event_for_endpoint (ib .endpoint_id , capabilities .fanMode .fanMode (" high" ))
1371
+ else
1372
+ device :emit_event_for_endpoint (ib .endpoint_id , capabilities .fanMode .fanMode (" auto" ))
1373
+ end
1374
+ end
1375
+
1376
+ local function fan_mode_sequence_handler (driver , device , ib , response )
1377
+ local supportedFanModes
1378
+ if ib .data .value == clusters .FanControl .attributes .FanModeSequence .OFF_LOW_MED_HIGH then
1379
+ supportedFanModes = {
1380
+ capabilities .fanMode .fanMode .off .NAME ,
1381
+ capabilities .fanMode .fanMode .low .NAME ,
1382
+ capabilities .fanMode .fanMode .medium .NAME ,
1383
+ capabilities .fanMode .fanMode .high .NAME
1384
+ }
1385
+ elseif ib .data .value == clusters .FanControl .attributes .FanModeSequence .OFF_LOW_HIGH then
1386
+ supportedFanModes = {
1387
+ capabilities .fanMode .fanMode .off .NAME ,
1388
+ capabilities .fanMode .fanMode .low .NAME ,
1389
+ capabilities .fanMode .fanMode .high .NAME
1390
+ }
1391
+ elseif ib .data .value == clusters .FanControl .attributes .FanModeSequence .OFF_LOW_MED_HIGH_AUTO then
1392
+ supportedFanModes = {
1393
+ capabilities .fanMode .fanMode .off .NAME ,
1394
+ capabilities .fanMode .fanMode .low .NAME ,
1395
+ capabilities .fanMode .fanMode .medium .NAME ,
1396
+ capabilities .fanMode .fanMode .high .NAME ,
1397
+ capabilities .fanMode .fanMode .auto .NAME
1398
+ }
1399
+ elseif ib .data .value == clusters .FanControl .attributes .FanModeSequence .OFF_LOW_HIGH_AUTO then
1400
+ supportedFanModes = {
1401
+ capabilities .fanMode .fanMode .off .NAME ,
1402
+ capabilities .fanMode .fanMode .low .NAME ,
1403
+ capabilities .fanMode .fanMode .high .NAME ,
1404
+ capabilities .fanMode .fanMode .auto .NAME
1405
+ }
1406
+ elseif ib .data .value == clusters .FanControl .attributes .FanModeSequence .OFF_ON_AUTO then
1407
+ supportedFanModes = {
1408
+ capabilities .fanMode .fanMode .off .NAME ,
1409
+ capabilities .fanMode .fanMode .high .NAME ,
1410
+ capabilities .fanMode .fanMode .auto .NAME
1411
+ }
1412
+ else
1413
+ supportedFanModes = {
1414
+ capabilities .fanMode .fanMode .off .NAME ,
1415
+ capabilities .fanMode .fanMode .high .NAME
1416
+ }
1417
+ end
1418
+ local event = capabilities .fanMode .supportedFanModes (supportedFanModes , {visibility = {displayed = false }})
1419
+ device :emit_event_for_endpoint (ib .endpoint_id , event )
1420
+ end
1421
+
1422
+ local function fan_speed_percent_attr_handler (driver , device , ib , response )
1423
+ if ib .data .value == nil or ib .data .value < 0 or ib .data .value > 100 then
1424
+ return
1425
+ end
1426
+ device :emit_event_for_endpoint (ib .endpoint_id , capabilities .fanSpeedPercent .percent (ib .data .value ))
1427
+ end
1428
+
1341
1429
local matter_driver_template = {
1342
1430
lifecycle_handlers = {
1343
1431
init = device_init ,
@@ -1399,6 +1487,11 @@ local matter_driver_template = {
1399
1487
[clusters .TemperatureMeasurement .attributes .MeasuredValue .ID ] = temperature_attr_handler ,
1400
1488
[clusters .TemperatureMeasurement .attributes .MinMeasuredValue .ID ] = temp_attr_handler_factory (TEMP_MIN ),
1401
1489
[clusters .TemperatureMeasurement .attributes .MaxMeasuredValue .ID ] = temp_attr_handler_factory (TEMP_MAX ),
1490
+ },
1491
+ [clusters .FanControl .ID ] = {
1492
+ [clusters .FanControl .attributes .FanModeSequence .ID ] = fan_mode_sequence_handler ,
1493
+ [clusters .FanControl .attributes .FanMode .ID ] = fan_mode_handler ,
1494
+ [clusters .FanControl .attributes .PercentCurrent .ID ] = fan_speed_percent_attr_handler
1402
1495
}
1403
1496
},
1404
1497
event = {
@@ -1464,6 +1557,13 @@ local matter_driver_template = {
1464
1557
clusters .TemperatureMeasurement .attributes .MeasuredValue ,
1465
1558
clusters .TemperatureMeasurement .attributes .MinMeasuredValue ,
1466
1559
clusters .TemperatureMeasurement .attributes .MaxMeasuredValue
1560
+ },
1561
+ [capabilities .fanMode .ID ] = {
1562
+ clusters .FanControl .attributes .FanModeSequence ,
1563
+ clusters .FanControl .attributes .FanMode
1564
+ },
1565
+ [capabilities .fanSpeedPercent .ID ] = {
1566
+ clusters .FanControl .attributes .PercentCurrent
1467
1567
}
1468
1568
},
1469
1569
subscribed_events = {
@@ -1499,6 +1599,12 @@ local matter_driver_template = {
1499
1599
},
1500
1600
[capabilities .level .ID ] = {
1501
1601
[capabilities .level .commands .setLevel .NAME ] = handle_set_level
1602
+ },
1603
+ [capabilities .fanMode .ID ] = {
1604
+ [capabilities .fanMode .commands .setFanMode .NAME ] = set_fan_mode
1605
+ },
1606
+ [capabilities .fanSpeedPercent .ID ] = {
1607
+ [capabilities .fanSpeedPercent .commands .setPercent .NAME ] = set_fan_speed_percent
1502
1608
}
1503
1609
},
1504
1610
supported_capabilities = {
@@ -1517,7 +1623,9 @@ local matter_driver_template = {
1517
1623
capabilities .battery ,
1518
1624
capabilities .batteryLevel ,
1519
1625
capabilities .temperatureMeasurement ,
1520
- capabilities .relativeHumidityMeasurement
1626
+ capabilities .relativeHumidityMeasurement ,
1627
+ capabilities .fanMode ,
1628
+ capabilities .fanSpeedPercent
1521
1629
},
1522
1630
sub_drivers = {
1523
1631
require (" eve-energy" ),
0 commit comments