@@ -872,15 +872,14 @@ export default class HomeAssistant extends Extension {
872
872
discovery_payload : {
873
873
name : null ,
874
874
state_topic : true ,
875
- state_value_template : '{{ value_json.fan_state }}' ,
876
875
command_topic : true ,
877
- command_topic_postfix : 'fan_state' ,
878
876
} ,
879
877
} ;
880
878
881
- const speed = ( firstExpose as zhc . Fan ) . features . filter ( isEnumExpose ) . find ( ( e ) => e . name === 'mode' ) ;
879
+ const modeEmulatedSpeed = ( firstExpose as zhc . Fan ) . features . filter ( isEnumExpose ) . find ( ( e ) => e . name === 'mode' ) ;
880
+ const nativeSpeed = ( firstExpose as zhc . Fan ) . features . filter ( isNumericExpose ) . find ( ( e ) => e . name === 'speed' ) ;
882
881
883
- if ( speed ) {
882
+ if ( modeEmulatedSpeed ) {
884
883
// A fan entity in Home Assistant 2021.3 and above may have a speed,
885
884
// controlled by a percentage from 1 to 100, and/or non-speed presets.
886
885
// The MQTT Fan integration allows the speed percentage to be mapped
@@ -894,9 +893,9 @@ export default class HomeAssistant extends Extension {
894
893
// ZCL. This supports a generic ZCL HVAC Fan Control fan. "Off" is
895
894
// always a valid speed.
896
895
let speeds = [ 'off' ] . concat (
897
- [ 'low' , 'medium' , 'high' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' ] . filter ( ( s ) => speed . values . includes ( s ) ) ,
896
+ [ 'low' , 'medium' , 'high' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' ] . filter ( ( s ) => modeEmulatedSpeed . values . includes ( s ) ) ,
898
897
) ;
899
- let presets = [ 'on' , 'auto' , 'smart' ] . filter ( ( s ) => speed . values . includes ( s ) ) ;
898
+ let presets = [ 'on' , 'auto' , 'smart' ] . filter ( ( s ) => modeEmulatedSpeed . values . includes ( s ) ) ;
900
899
901
900
if ( [ '99432' ] . includes ( definition ! . model ) ) {
902
901
// The Hampton Bay 99432 fan implements 4 speeds using the ZCL
@@ -908,22 +907,39 @@ export default class HomeAssistant extends Extension {
908
907
}
909
908
910
909
const allowed = [ ...speeds , ...presets ] ;
911
- speed . values . forEach ( ( s ) => assert ( allowed . includes ( s . toString ( ) ) ) ) ;
910
+ modeEmulatedSpeed . values . forEach ( ( s ) => assert ( allowed . includes ( s . toString ( ) ) ) ) ;
912
911
const percentValues = speeds . map ( ( s , i ) => `'${ s } ':${ i } ` ) . join ( ', ' ) ;
913
912
const percentCommands = speeds . map ( ( s , i ) => `${ i } :'${ s } '` ) . join ( ', ' ) ;
914
913
const presetList = presets . map ( ( s ) => `'${ s } '` ) . join ( ', ' ) ;
915
914
916
915
discoveryEntry . discovery_payload . percentage_state_topic = true ;
917
- discoveryEntry . discovery_payload . percentage_command_topic = true ;
918
- discoveryEntry . discovery_payload . percentage_value_template = `{{ {${ percentValues } }[value_json.${ speed . property } ] | default('None') }}` ;
916
+ discoveryEntry . discovery_payload . percentage_command_topic = 'fan_mode' ;
917
+ discoveryEntry . discovery_payload . percentage_value_template = `{{ {${ percentValues } }[value_json.${ modeEmulatedSpeed . property } ] | default('None') }}` ;
919
918
discoveryEntry . discovery_payload . percentage_command_template = `{{ {${ percentCommands } }[value] | default('') }}` ;
920
919
discoveryEntry . discovery_payload . speed_range_min = 1 ;
921
920
discoveryEntry . discovery_payload . speed_range_max = speeds . length - 1 ;
922
921
assert ( presets . length !== 0 ) ;
923
922
discoveryEntry . discovery_payload . preset_mode_state_topic = true ;
924
923
discoveryEntry . discovery_payload . preset_mode_command_topic = 'fan_mode' ;
925
- discoveryEntry . discovery_payload . preset_mode_value_template = `{{ value_json.${ speed . property } if value_json.${ speed . property } in [${ presetList } ] else 'None' | default('None') }}` ;
924
+ discoveryEntry . discovery_payload . preset_mode_value_template = `{{ value_json.${ modeEmulatedSpeed . property } if value_json.${ modeEmulatedSpeed . property } in [${ presetList } ] else 'None' | default('None') }}` ;
926
925
discoveryEntry . discovery_payload . preset_modes = presets ;
926
+
927
+ // Emulate state based on mode
928
+ discoveryEntry . discovery_payload . state_value_template = '{{ value_json.fan_state }}' ;
929
+ discoveryEntry . discovery_payload . command_topic_postfix = 'fan_state' ;
930
+ } else if ( nativeSpeed ) {
931
+ discoveryEntry . discovery_payload . percentage_state_topic = true ;
932
+ discoveryEntry . discovery_payload . percentage_command_topic = 'speed' ;
933
+ discoveryEntry . discovery_payload . percentage_value_template = `{{ value_json.${ nativeSpeed . property } | default('None') }}` ;
934
+ discoveryEntry . discovery_payload . percentage_command_template = `{{ value | default('') }}` ;
935
+ discoveryEntry . discovery_payload . speed_range_min = nativeSpeed . value_min ;
936
+ discoveryEntry . discovery_payload . speed_range_max = nativeSpeed . value_max ;
937
+
938
+ // Speed-controlled fans generally have an onOff cluster, use that for state
939
+ discoveryEntry . discovery_payload . state_value_template = '{{ value_json.state }}' ;
940
+ discoveryEntry . discovery_payload . command_topic_postfix = 'state' ;
941
+ } else {
942
+ assert ( false , 'Fans need to be either mode- or speed-controlled' ) ;
927
943
}
928
944
929
945
discoveryEntries . push ( discoveryEntry ) ;
@@ -1621,7 +1637,7 @@ export default class HomeAssistant extends Extension {
1621
1637
}
1622
1638
1623
1639
if ( payload . percentage_command_topic ) {
1624
- payload . percentage_command_topic = `${ baseTopic } /${ commandTopicPrefix } set/fan_mode ` ;
1640
+ payload . percentage_command_topic = `${ baseTopic } /${ commandTopicPrefix } set/${ payload . percentage_command_topic } ` ;
1625
1641
}
1626
1642
1627
1643
if ( payload . preset_mode_state_topic ) {
0 commit comments