]> git.basschouten.com Git - openhab-addons.git/blob
aeca47f9a42fac17116a698ae7ac731eb2c305cd
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.russound.internal.rio.system;
14
15 import java.util.concurrent.atomic.AtomicBoolean;
16 import java.util.regex.Matcher;
17 import java.util.regex.Pattern;
18
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.russound.internal.net.SocketSession;
21 import org.openhab.binding.russound.internal.net.SocketSessionListener;
22 import org.openhab.binding.russound.internal.rio.AbstractRioProtocol;
23 import org.openhab.binding.russound.internal.rio.RioConstants;
24 import org.openhab.binding.russound.internal.rio.RioHandlerCallback;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.library.types.StringType;
27 import org.openhab.core.thing.ThingStatus;
28 import org.openhab.core.thing.ThingStatusDetail;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33  * This is the protocol handler for the Russound System. This handler will issue the protocol commands and will
34  * process the responses from the Russound system.
35  *
36  * @author Tim Roberts - Initial contribution
37  */
38 class RioSystemProtocol extends AbstractRioProtocol {
39     // Logger
40     private final Logger logger = LoggerFactory.getLogger(RioSystemProtocol.class);
41
42     // Protocol Constants
43     private static final String SYS_VERSION = "version"; // 12 max
44     private static final String SYS_STATUS = "status"; // 12 max
45     private static final String SYS_LANG = "language"; // 12 max
46
47     // Response patterns
48     private static final Pattern RSP_VERSION = Pattern.compile("(?i)^S VERSION=\"(.+)\"$");
49     private static final Pattern RSP_FAILURE = Pattern.compile("(?i)^E (.*)");
50     private static final Pattern RSP_SYSTEMNOTIFICATION = Pattern.compile("(?i)^[SN] System\\.(\\w+)=\"(.*)\"$");
51
52     // all on state (there is no corresponding value)
53     private final AtomicBoolean allOn = new AtomicBoolean(false);
54
55     /**
56      * This represents our ping command. There is no ping command in the protocol so we simply send an empty command to
57      * keep things alive (and not generate any errors)
58      */
59     private static final String CMD_PING = "";
60
61     /**
62      * Constructs the protocol handler from given parameters
63      *
64      * @param session a non-null {@link SocketSession} (may be connected or disconnected)
65      * @param callback a non-null {@link RioHandlerCallback} to callback
66      */
67     RioSystemProtocol(SocketSession session, RioHandlerCallback callback) {
68         super(session, callback);
69     }
70
71     /**
72      * Attempts to log into the system. The russound system requires no login, so we immediately execute any
73      * {@link #postLogin()} commands.
74      *
75      * @return always null to indicate a successful login
76      */
77     String login() {
78         postLogin();
79         return null;
80     }
81
82     /**
83      * Post successful login stuff - mark us online, start watching the system and refresh some attributes
84      */
85     private void postLogin() {
86         logger.info("Russound System now connected");
87         statusChanged(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
88         watchSystem(true);
89
90         refreshSystemStatus();
91         refreshVersion();
92         refreshSystemLanguage();
93     }
94
95     /**
96      * Pings the server with out ping command to keep the connection alive
97      */
98     void ping() {
99         sendCommand(CMD_PING);
100     }
101
102     /**
103      * Refreshes the firmware version of the system
104      */
105     void refreshVersion() {
106         sendCommand(SYS_VERSION);
107     }
108
109     /**
110      * Helper method to refresh a system keyname
111      *
112      * @param keyName a non-null, non-empty keyname
113      * @throws IllegalArgumentException if keyname is null or empty
114      */
115     private void refreshSystemKey(String keyName) {
116         if (keyName == null || keyName.trim().length() == 0) {
117             throw new IllegalArgumentException("keyName cannot be null or empty");
118         }
119
120         sendCommand("GET System." + keyName);
121     }
122
123     /**
124      * Refresh the system status
125      */
126     void refreshSystemAllOn() {
127         stateChanged(RioConstants.CHANNEL_SYSALLON, OnOffType.from(allOn.get()));
128     }
129
130     /**
131      * Refresh the system language
132      */
133     void refreshSystemLanguage() {
134         refreshSystemKey(SYS_LANG);
135     }
136
137     /**
138      * Refresh the system status
139      */
140     void refreshSystemStatus() {
141         refreshSystemKey(SYS_STATUS);
142     }
143
144     /**
145      * Turns on/off watching for system notifications
146      *
147      * @param on true to turn on, false to turn off
148      */
149     void watchSystem(boolean on) {
150         sendCommand("WATCH SYSTEM " + (on ? "ON" : "OFF"));
151     }
152
153     /**
154      * Sets all zones on
155      *
156      * @param on true to turn all zones on, false otherwise
157      */
158     void setSystemAllOn(boolean on) {
159         sendCommand("EVENT C[1].Z[1]!All" + (on ? "On" : "Off"));
160         allOn.set(on);
161         refreshSystemAllOn();
162     }
163
164     /**
165      * Sets the system language (currently can only be english, chinese or russian). Case does not matter - will be
166      * converted to uppercase for the system.
167      *
168      * @param language a non-null, non-empty language to set
169      * @throws IllegalArgumentException if language is null, empty or not (english, chinese or russian).
170      */
171     void setSystemLanguage(String language) {
172         if (language == null || language.trim().length() == 0) {
173             throw new IllegalArgumentException("Language cannot be null or an empty string");
174         }
175
176         if ("|ENGLISH|CHINESE|RUSSIAN|".indexOf("|" + language + "|") == -1) {
177             throw new IllegalArgumentException("Language can only be ENGLISH, CHINESE or RUSSIAN: " + language);
178         }
179         sendCommand("SET System." + SYS_LANG + " " + language.toUpperCase());
180     }
181
182     /**
183      * Handles the version notification
184      *
185      * @param m a non-null matcher
186      * @param resp a possibly null, possibly empty response
187      */
188     void handleVersionNotification(Matcher m, String resp) {
189         if (m == null) {
190             throw new IllegalArgumentException("m (matcher) cannot be null");
191         }
192         if (m.groupCount() == 1) {
193             final String version = m.group(1);
194             setProperty(RioConstants.PROPERTY_SYSVERSION, version);
195         } else {
196             logger.warn("Invalid System Notification response: '{}'", resp);
197         }
198     }
199
200     /**
201      * Handles any system notifications returned by the russound system
202      *
203      * @param m a non-null matcher
204      * @param resp a possibly null, possibly empty response
205      */
206     void handleSystemNotification(Matcher m, String resp) {
207         if (m == null) {
208             throw new IllegalArgumentException("m (matcher) cannot be null");
209         }
210         if (m.groupCount() == 2) {
211             final String key = m.group(1).toLowerCase();
212             final String value = m.group(2);
213
214             switch (key) {
215                 case SYS_LANG:
216                     stateChanged(RioConstants.CHANNEL_SYSLANG, new StringType(value));
217                     break;
218                 case SYS_STATUS:
219                     stateChanged(RioConstants.CHANNEL_SYSSTATUS, OnOffType.from("ON".equals(value)));
220                     break;
221
222                 default:
223                     logger.warn("Unknown system notification: '{}'", resp);
224                     break;
225             }
226         } else {
227             logger.warn("Invalid System Notification response: '{}'", resp);
228         }
229     }
230
231     /**
232      * Handles any error notifications returned by the russound system
233      *
234      * @param m a non-null matcher
235      * @param resp a possibly null, possibly empty response
236      */
237     private void handleFailureNotification(Matcher m, String resp) {
238         logger.debug("Error notification: {}", resp);
239     }
240
241     /**
242      * Implements {@link SocketSessionListener#responseReceived(String)} to try to process the response from the
243      * russound system. This response may be for other protocol handler - so ignore if we don't recognize the response.
244      *
245      * @param a possibly null, possibly empty response
246      */
247     @Override
248     public void responseReceived(@Nullable String response) {
249         if (response == null || response.isEmpty()) {
250             return;
251         }
252
253         Matcher m = RSP_VERSION.matcher(response);
254         if (m.matches()) {
255             handleVersionNotification(m, response);
256             return;
257         }
258
259         m = RSP_SYSTEMNOTIFICATION.matcher(response);
260         if (m.matches()) {
261             handleSystemNotification(m, response);
262             return;
263         }
264
265         m = RSP_FAILURE.matcher(response);
266         if (m.matches()) {
267             handleFailureNotification(m, response);
268             return;
269         }
270     }
271
272     /**
273      * Overrides the default implementation to turn watch off ({@link #watchSystem(boolean)}) before calling the dispose
274      */
275     @Override
276     public void dispose() {
277         watchSystem(false);
278         super.dispose();
279     }
280 }