12
12
*/
13
13
package org .openhab .binding .mqtt .homeassistant .internal .component ;
14
14
15
+ import java .util .HashMap ;
16
+ import java .util .Map ;
17
+ import java .util .Objects ;
18
+ import java .util .TreeMap ;
19
+
15
20
import org .eclipse .jdt .annotation .NonNullByDefault ;
16
- import org .eclipse . jdt . annotation . Nullable ;
21
+ import org .openhab . binding . mqtt . generic . ChannelState ;
17
22
import org .openhab .binding .mqtt .generic .values .TextValue ;
23
+ import org .openhab .binding .mqtt .homeassistant .internal .ComponentChannel ;
18
24
import org .openhab .binding .mqtt .homeassistant .internal .ComponentChannelType ;
19
25
import org .openhab .binding .mqtt .homeassistant .internal .config .dto .AbstractChannelConfiguration ;
26
+ import org .openhab .binding .mqtt .homeassistant .internal .exception .ConfigurationException ;
27
+ import org .openhab .core .config .core .Configuration ;
28
+ import org .openhab .core .library .types .StringType ;
20
29
import org .openhab .core .thing .type .AutoUpdatePolicy ;
30
+ import org .openhab .core .types .Command ;
31
+ import org .openhab .core .types .CommandDescriptionBuilder ;
32
+ import org .openhab .core .types .CommandOption ;
21
33
22
34
import com .google .gson .annotations .SerializedName ;
23
35
30
42
public class Scene extends AbstractComponent <Scene .ChannelConfiguration > {
31
43
public static final String SCENE_CHANNEL_ID = "scene" ;
32
44
45
+ // A command that has already been processed and routed to the correct Value,
46
+ // and should be immediately published. This will be the payloadOn value from
47
+ // the configuration
48
+ private static class SceneCommand extends StringType {
49
+ SceneCommand (String value ) {
50
+ super (value );
51
+ }
52
+ }
53
+
54
+ // A value that can provide a proper CommandDescription with values and labels
55
+ class SceneValue extends TextValue {
56
+ SceneValue () {
57
+ super ();
58
+ }
59
+
60
+ @ Override
61
+ public CommandDescriptionBuilder createCommandDescription () {
62
+ CommandDescriptionBuilder builder = super .createCommandDescription ();
63
+ objectIdToScene .forEach ((k , v ) -> builder .withCommandOption (new CommandOption (k , v .getName ())));
64
+ return builder ;
65
+ }
66
+ }
67
+
33
68
/**
34
69
* Configuration class for MQTT component
35
70
*/
@@ -39,23 +74,106 @@ static class ChannelConfiguration extends AbstractChannelConfiguration {
39
74
}
40
75
41
76
@ SerializedName ("command_topic" )
42
- protected @ Nullable String commandTopic ;
77
+ protected String commandTopic = "" ;
43
78
44
79
@ SerializedName ("payload_on" )
45
80
protected String payloadOn = "ON" ;
46
81
}
47
82
83
+ // Keeps track of discrete command topics, and one SceneValue that uses that topic
84
+ private final Map <String , ChannelState > topicsToChannelStates = new HashMap <>();
85
+ private final Map <String , ChannelConfiguration > objectIdToScene = new TreeMap <>();
86
+ private final Map <String , ChannelConfiguration > labelToScene = new HashMap <>();
87
+
88
+ private final SceneValue value = new SceneValue ();
89
+ private ComponentChannel channel ;
90
+
48
91
public Scene (ComponentFactory .ComponentConfiguration componentConfiguration ) {
49
92
super (componentConfiguration , ChannelConfiguration .class );
50
93
51
- TextValue value = new TextValue (new String [] { channelConfiguration .payloadOn });
94
+ if (channelConfiguration .commandTopic .isEmpty ()) {
95
+ throw new ConfigurationException ("command_topic is required" );
96
+ }
97
+
98
+ // Name the channel with a constant, not the component ID
99
+ // So that we only end up with a single channel for all scenes
100
+ componentId = SCENE_CHANNEL_ID ;
101
+ groupId = null ;
52
102
53
- buildChannel (SCENE_CHANNEL_ID , ComponentChannelType .STRING , value , getName (),
103
+ channel = buildChannel (SCENE_CHANNEL_ID , ComponentChannelType .STRING , value , getName (),
54
104
componentConfiguration .getUpdateListener ())
55
105
.commandTopic (channelConfiguration .commandTopic , channelConfiguration .isRetain (),
56
106
channelConfiguration .getQos ())
57
- .withAutoUpdatePolicy (AutoUpdatePolicy .VETO ).build ();
107
+ .commandFilter (this ::handleCommand ).withAutoUpdatePolicy (AutoUpdatePolicy .VETO ).build ();
108
+ topicsToChannelStates .put (channelConfiguration .commandTopic , channel .getState ());
109
+ addScene (this );
110
+ }
58
111
59
- finalizeChannels ();
112
+ ComponentChannel getChannel () {
113
+ return channel ;
114
+ }
115
+
116
+ private void addScene (Scene scene ) {
117
+ ChannelConfiguration channelConfiguration = scene .getChannelConfiguration ();
118
+ objectIdToScene .put (scene .getHaID ().objectID , channelConfiguration );
119
+ labelToScene .put (channelConfiguration .getName (), channelConfiguration );
120
+
121
+ if (!topicsToChannelStates .containsKey (channelConfiguration .commandTopic )) {
122
+ hiddenChannels .add (scene .getChannel ());
123
+ topicsToChannelStates .put (channelConfiguration .commandTopic , scene .getChannel ().getState ());
124
+ }
125
+ }
126
+
127
+ private boolean handleCommand (Command command ) {
128
+ // This command has already been processed by the rest of this method,
129
+ // so just return immediately.
130
+ if (command instanceof SceneCommand ) {
131
+ return true ;
132
+ }
133
+
134
+ String valueStr = command .toString ();
135
+ ChannelConfiguration sceneConfig = objectIdToScene .get (valueStr );
136
+ if (sceneConfig == null ) {
137
+ sceneConfig = labelToScene .get (command .toString ());
138
+ }
139
+ if (sceneConfig == null ) {
140
+ throw new IllegalArgumentException ("Value " + valueStr + " not within range" );
141
+ }
142
+
143
+ ChannelState state = Objects .requireNonNull (topicsToChannelStates .get (sceneConfig .commandTopic ));
144
+ // This will end up calling this same method, so be sure no further processing is done
145
+ state .publishValue (new SceneCommand (sceneConfig .payloadOn ));
146
+
147
+ return false ;
148
+ }
149
+
150
+ @ Override
151
+ public String getName () {
152
+ return "Scene" ;
153
+ }
154
+
155
+ @ Override
156
+ public boolean mergeable (AbstractComponent <?> other ) {
157
+ return other instanceof Scene ;
158
+ }
159
+
160
+ @ Override
161
+ public boolean merge (AbstractComponent <?> other ) {
162
+ Scene newScene = (Scene ) other ;
163
+ Configuration newConfiguration = mergeChannelConfiguration (channel , newScene );
164
+
165
+ addScene (newScene );
166
+
167
+ // Recreate the channel so that the configuration will have all the scenes
168
+ stop ();
169
+ channel = buildChannel (SCENE_CHANNEL_ID , ComponentChannelType .STRING , value , "Scene" ,
170
+ componentConfiguration .getUpdateListener ())
171
+ .withConfiguration (newConfiguration )
172
+ .commandTopic (channelConfiguration .commandTopic , channelConfiguration .isRetain (),
173
+ channelConfiguration .getQos ())
174
+ .commandFilter (this ::handleCommand ).withAutoUpdatePolicy (AutoUpdatePolicy .VETO ).build ();
175
+ // New ChannelState created; need to make sure we're referencing the correct one
176
+ topicsToChannelStates .put (channelConfiguration .commandTopic , channel .getState ());
177
+ return true ;
60
178
}
61
179
}
0 commit comments