]> git.basschouten.com Git - openhab-addons.git/blob
b1273073d0a5a786287bb97860c609c471e80795
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.powermax.internal.state;
14
15 import static org.openhab.binding.powermax.internal.PowermaxBindingConstants.*;
16
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.powermax.internal.message.PowermaxMessageConstants;
24 import org.openhab.core.i18n.TimeZoneProvider;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.library.types.StringType;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * A class to store the state of the alarm system
32  *
33  * @author Laurent Garnier - Initial contribution
34  */
35 public class PowermaxState extends PowermaxStateContainer {
36
37     private final Logger logger = LoggerFactory.getLogger(PowermaxState.class);
38
39     // For values that are mapped to channels, use a channel name constant from
40     // PowermaxBindingConstants. For values used internally but not mapped to
41     // channels, use a unique name starting with "_".
42
43     public BooleanValue powerlinkMode = new BooleanValue(this, "_powerlink_mode");
44     public BooleanValue downloadMode = new BooleanValue(this, "_download_mode");
45     public BooleanValue ready = new BooleanValue(this, READY);
46     public BooleanValue bypass = new BooleanValue(this, WITH_ZONES_BYPASSED);
47     public BooleanValue alarmActive = new BooleanValue(this, ALARM_ACTIVE);
48     public BooleanValue trouble = new BooleanValue(this, TROUBLE);
49     public BooleanValue alertInMemory = new BooleanValue(this, ALERT_IN_MEMORY);
50     public BooleanValue ringing = new BooleanValue(this, RINGING);
51     public DateTimeValue ringingSince = new DateTimeValue(this, "_ringing_since");
52     public StringValue statusStr = new StringValue(this, SYSTEM_STATUS);
53     public StringValue armMode = new StringValue(this, "_arm_mode");
54     public BooleanValue downloadSetupRequired = new BooleanValue(this, "_download_setup_required");
55     public DateTimeValue lastKeepAlive = new DateTimeValue(this, "_last_keepalive");
56     public DateTimeValue lastMessageTime = new DateTimeValue(this, LAST_MESSAGE_TIME);
57
58     public DynamicValue<Boolean> isArmed = new DynamicValue<>(this, SYSTEM_ARMED, () -> {
59         return isArmed();
60     }, () -> {
61         return isArmed() ? OnOffType.ON : OnOffType.OFF;
62     });
63
64     public DynamicValue<String> panelMode = new DynamicValue<>(this, MODE, () -> {
65         return getPanelMode();
66     }, () -> {
67         return new StringType(getPanelMode());
68     });
69
70     public DynamicValue<String> shortArmMode = new DynamicValue<>(this, ARM_MODE, () -> {
71         return getShortArmMode();
72     }, () -> {
73         return new StringType(getShortArmMode());
74     });
75
76     public DynamicValue<String> activeAlerts = new DynamicValue<>(this, ACTIVE_ALERTS, () -> {
77         return getActiveAlerts();
78     }, () -> {
79         return new StringType(getActiveAlerts());
80     });
81
82     public DynamicValue<Boolean> pgmStatus = new DynamicValue<>(this, PGM_STATUS, () -> {
83         return getPGMX10DeviceStatus(0);
84     }, () -> {
85         return getPGMX10DeviceStatus(0) ? OnOffType.ON : OnOffType.OFF;
86     });
87
88     private PowermaxPanelSettings panelSettings;
89     private PowermaxZoneState[] zones;
90     private Boolean[] pgmX10DevicesStatus;
91     private byte[] updateSettings;
92     private String[] eventLog;
93     private Map<Integer, Byte> updatedZoneNames;
94     private Map<Integer, Integer> updatedZoneInfos;
95     private List<PowermaxActiveAlert> activeAlertList;
96     private List<PowermaxActiveAlert> activeAlertQueue;
97
98     private enum PowermaxAlertAction {
99         ADD,
100         CLEAR,
101         CLEAR_ALL
102     }
103
104     private class PowermaxActiveAlert {
105         public final @Nullable PowermaxAlertAction action;
106         public final int zone;
107         public final int code;
108
109         public PowermaxActiveAlert(@Nullable PowermaxAlertAction action, int zone, int code) {
110             this.action = action;
111             this.zone = zone;
112             this.code = code;
113         }
114     }
115
116     /**
117      * Constructor (default values)
118      */
119     public PowermaxState(PowermaxPanelSettings panelSettings, TimeZoneProvider timeZoneProvider) {
120         super(timeZoneProvider);
121         this.panelSettings = panelSettings;
122
123         zones = new PowermaxZoneState[panelSettings.getNbZones()];
124         for (int i = 0; i < panelSettings.getNbZones(); i++) {
125             zones[i] = new PowermaxZoneState(timeZoneProvider);
126         }
127         pgmX10DevicesStatus = new Boolean[panelSettings.getNbPGMX10Devices()];
128         updatedZoneNames = new HashMap<>();
129         updatedZoneInfos = new HashMap<>();
130         activeAlertList = new ArrayList<>();
131         activeAlertQueue = new ArrayList<>();
132
133         // Most fields will get populated by the initial download, but we set
134         // the ringing indicator in response to an alarm message. We have no
135         // other way to know if the siren is ringing so we'll initialize it to
136         // false.
137
138         this.ringing.setValue(false);
139     }
140
141     /**
142      * Return the PowermaxZoneState object for a given zone. If the zone number is
143      * out of range, returns a dummy PowermaxZoneState object that won't be
144      * persisted. The return value is never null, so it's safe to chain method
145      * calls.
146      *
147      * @param zone the index of the zone (first zone is index 1)
148      * @return the zone state object (or a dummy zone state)
149      */
150     public PowermaxZoneState getZone(int zone) {
151         if ((zone < 1) || (zone > zones.length)) {
152             logger.warn("Received update for invalid zone {}", zone);
153             return new PowermaxZoneState(timeZoneProvider);
154         } else {
155             return zones[zone - 1];
156         }
157     }
158
159     /**
160      * Get the status of a PGM or X10 device
161      *
162      * @param device the index of the PGM/X10 device (0 s for PGM; for X10 device is index 1)
163      *
164      * @return the status (true or false)
165      */
166     public Boolean getPGMX10DeviceStatus(int device) {
167         return ((device < 0) || (device >= pgmX10DevicesStatus.length)) ? null : pgmX10DevicesStatus[device];
168     }
169
170     /**
171      * Set the status of a PGM or X10 device
172      *
173      * @param device the index of the PGM/X10 device (0 s for PGM; for X10 device is index 1)
174      * @param status true or false
175      */
176     public void setPGMX10DeviceStatus(int device, Boolean status) {
177         if ((device >= 0) && (device < pgmX10DevicesStatus.length)) {
178             this.pgmX10DevicesStatus[device] = status;
179         }
180     }
181
182     /**
183      * Get the raw buffer containing all the settings
184      *
185      * @return the raw buffer as a table of bytes
186      */
187     public byte[] getUpdateSettings() {
188         return updateSettings;
189     }
190
191     /**
192      * Set the raw buffer containing all the settings
193      *
194      * @param updateSettings the raw buffer as a table of bytes
195      */
196     public void setUpdateSettings(byte[] updateSettings) {
197         this.updateSettings = updateSettings;
198     }
199
200     /**
201      * Get the number of entries in the event log
202      *
203      * @return the number of entries
204      */
205     public int getEventLogSize() {
206         return (eventLog == null) ? 0 : eventLog.length;
207     }
208
209     /**
210      * Set the number of entries in the event log
211      *
212      * @param size the number of entries
213      */
214     public void setEventLogSize(int size) {
215         eventLog = new String[size];
216     }
217
218     /**
219      * Get one entry from the event logs
220      *
221      * @param index the entry index (1 for the most recent entry)
222      *
223      * @return the entry value (event)
224      */
225     public String getEventLog(int index) {
226         return ((index < 1) || (index > getEventLogSize())) ? null : eventLog[index - 1];
227     }
228
229     /**
230      * Set one entry from the event logs
231      *
232      * @param index the entry index (1 for the most recent entry)
233      * @param event the entry value (event)
234      */
235     public void setEventLog(int index, String event) {
236         if ((index >= 1) && (index <= getEventLogSize())) {
237             this.eventLog[index - 1] = event;
238         }
239     }
240
241     public Map<Integer, Byte> getUpdatedZoneNames() {
242         return updatedZoneNames;
243     }
244
245     public void updateZoneName(int zoneIdx, byte zoneNameIdx) {
246         this.updatedZoneNames.put(zoneIdx, zoneNameIdx);
247     }
248
249     public Map<Integer, Integer> getUpdatedZoneInfos() {
250         return updatedZoneInfos;
251     }
252
253     public void updateZoneInfo(int zoneIdx, int zoneInfo) {
254         this.updatedZoneInfos.put(zoneIdx, zoneInfo);
255     }
256
257     // This is an attempt to add persistence to an otherwise (mostly) stateless class.
258     // All of the other values are either present or null, and it's easy to build a
259     // delta state based only on which values are non-null. But these system events
260     // are different because each event can be set by one message and cleared by a
261     // later message. So to preserve the semantics of the state class, we'll keep a
262     // queue of incoming changes, and apply them only when the delta state is resolved.
263
264     public boolean hasActiveAlertsQueued() {
265         return !activeAlertQueue.isEmpty();
266     }
267
268     public String getActiveAlerts() {
269         if (activeAlertList.isEmpty()) {
270             return "None";
271         }
272
273         List<String> alerts = new ArrayList<>();
274
275         activeAlertList.forEach(e -> {
276             String message = PowermaxMessageConstants.getSystemEvent(e.code).toString();
277             String alert = e.zone == 0 ? message
278                     : String.format("%s (%s)", message, panelSettings.getZoneOrUserName(e.zone));
279
280             alerts.add(alert);
281         });
282
283         return String.join(", ", alerts);
284     }
285
286     public void addActiveAlert(int zoneIdx, int code) {
287         PowermaxActiveAlert alert = new PowermaxActiveAlert(PowermaxAlertAction.ADD, zoneIdx, code);
288         activeAlertQueue.add(alert);
289     }
290
291     public void clearActiveAlert(int zoneIdx, int code) {
292         PowermaxActiveAlert alert = new PowermaxActiveAlert(PowermaxAlertAction.CLEAR, zoneIdx, code);
293         activeAlertQueue.add(alert);
294     }
295
296     public void clearAllActiveAlerts() {
297         PowermaxActiveAlert alert = new PowermaxActiveAlert(PowermaxAlertAction.CLEAR_ALL, 0, 0);
298         activeAlertQueue.add(alert);
299     }
300
301     public void resolveActiveAlerts(@Nullable PowermaxState previousState) {
302         copyActiveAlertsFrom(previousState);
303
304         activeAlertQueue.forEach(alert -> {
305             if (alert.action == PowermaxAlertAction.CLEAR_ALL) {
306                 activeAlertList.clear();
307             } else {
308                 activeAlertList.removeIf(e -> e.zone == alert.zone && e.code == alert.code);
309
310                 if (alert.action == PowermaxAlertAction.ADD) {
311                     activeAlertList.add(new PowermaxActiveAlert(null, alert.zone, alert.code));
312                 }
313             }
314         });
315     }
316
317     private void copyActiveAlertsFrom(@Nullable PowermaxState state) {
318         activeAlertList = new ArrayList<>();
319
320         if (state != null) {
321             state.activeAlertList.forEach(alert -> {
322                 activeAlertList.add(new PowermaxActiveAlert(null, alert.zone, alert.code));
323             });
324         }
325     }
326
327     /**
328      * Get the panel mode
329      *
330      * @return either Download or Powerlink or Standard
331      */
332     public String getPanelMode() {
333         String mode = null;
334         if (Boolean.TRUE.equals(downloadMode.getValue())) {
335             mode = "Download";
336         } else if (Boolean.TRUE.equals(powerlinkMode.getValue())) {
337             mode = "Powerlink";
338         } else if (Boolean.FALSE.equals(powerlinkMode.getValue())) {
339             mode = "Standard";
340         }
341         return mode;
342     }
343
344     /**
345      * Get whether or not the current arming mode is considered as armed
346      *
347      * @return true or false
348      */
349     public Boolean isArmed() {
350         return isArmed(armMode.getValue());
351     }
352
353     /**
354      * Get whether or not an arming mode is considered as armed
355      *
356      * @param armMode the arming mode
357      *
358      * @return true or false; null if mode is unexpected
359      */
360     private static Boolean isArmed(String armMode) {
361         Boolean result = null;
362         if (armMode != null) {
363             try {
364                 PowermaxArmMode mode = PowermaxArmMode.fromName(armMode);
365                 result = mode.isArmed();
366             } catch (IllegalArgumentException e) {
367                 result = Boolean.FALSE;
368             }
369         }
370         return result;
371     }
372
373     /**
374      * Get the short description associated to the current arming mode
375      *
376      * @return the short description
377      */
378     public String getShortArmMode() {
379         return getShortArmMode(armMode.getValue());
380     }
381
382     /**
383      * Get the short name associated to an arming mode
384      *
385      * @param armMode the arming mode
386      *
387      * @return the short name or null if mode is unexpected
388      */
389     private static String getShortArmMode(String armMode) {
390         String result = null;
391         if (armMode != null) {
392             try {
393                 PowermaxArmMode mode = PowermaxArmMode.fromName(armMode);
394                 result = mode.getShortName();
395             } catch (IllegalArgumentException e) {
396                 result = armMode;
397             }
398         }
399         return result;
400     }
401
402     /**
403      * Keep only data that are different from another state and reset all others data to undefined
404      *
405      * @param otherState the other state
406      */
407     public void keepOnlyDifferencesWith(PowermaxState otherState) {
408         for (int zone = 1; zone <= zones.length; zone++) {
409             PowermaxZoneState thisZone = getZone(zone);
410             PowermaxZoneState otherZone = otherState.getZone(zone);
411
412             for (int i = 0; i < thisZone.getValues().size(); i++) {
413                 Value<?> thisValue = thisZone.getValues().get(i);
414                 Value<?> otherValue = otherZone.getValues().get(i);
415
416                 if ((thisValue.getValue() != null) && thisValue.getValue().equals(otherValue.getValue())) {
417                     thisValue.setValue(null);
418                 }
419             }
420         }
421
422         for (int i = 0; i < pgmX10DevicesStatus.length; i++) {
423             if ((getPGMX10DeviceStatus(i) != null)
424                     && getPGMX10DeviceStatus(i).equals(otherState.getPGMX10DeviceStatus(i))) {
425                 setPGMX10DeviceStatus(i, null);
426             }
427         }
428
429         for (int i = 0; i < getValues().size(); i++) {
430             Value<?> thisValue = getValues().get(i);
431             Value<?> otherValue = otherState.getValues().get(i);
432
433             if ((thisValue.getValue() != null) && thisValue.getValue().equals(otherValue.getValue())) {
434                 thisValue.setValue(null);
435             }
436         }
437
438         if (hasActiveAlertsQueued()) {
439             resolveActiveAlerts(otherState);
440         }
441     }
442
443     /**
444      * Update (override) the current state data from another state, ignoring in this other state
445      * the undefined data
446      *
447      * @param update the other state to consider for the update
448      */
449     public void merge(PowermaxState update) {
450         for (int zone = 1; zone <= zones.length; zone++) {
451             PowermaxZoneState thisZone = getZone(zone);
452             PowermaxZoneState otherZone = update.getZone(zone);
453
454             for (int i = 0; i < thisZone.getValues().size(); i++) {
455                 Value<?> thisValue = thisZone.getValues().get(i);
456                 Value<?> otherValue = otherZone.getValues().get(i);
457
458                 if (otherValue.getValue() != null) {
459                     thisValue.setValueUnsafe(otherValue.getValue());
460                 }
461             }
462         }
463
464         for (int i = 0; i < pgmX10DevicesStatus.length; i++) {
465             if (update.getPGMX10DeviceStatus(i) != null) {
466                 setPGMX10DeviceStatus(i, update.getPGMX10DeviceStatus(i));
467             }
468         }
469
470         for (int i = 0; i < getValues().size(); i++) {
471             Value<?> thisValue = getValues().get(i);
472             Value<?> otherValue = update.getValues().get(i);
473
474             if (otherValue.getValue() != null) {
475                 thisValue.setValueUnsafe(otherValue.getValue());
476             }
477         }
478
479         if (update.getEventLogSize() > getEventLogSize()) {
480             setEventLogSize(update.getEventLogSize());
481         }
482         for (int i = 1; i <= getEventLogSize(); i++) {
483             if (update.getEventLog(i) != null) {
484                 setEventLog(i, update.getEventLog(i));
485             }
486         }
487
488         if (update.hasActiveAlertsQueued()) {
489             copyActiveAlertsFrom(update);
490         }
491     }
492
493     @Override
494     public String toString() {
495         String str = "Bridge state:";
496
497         for (Value<?> value : getValues()) {
498             if ((value.getChannel() != null) && (value.getValue() != null)) {
499                 String channel = value.getChannel();
500                 String vStr = value.getValue().toString();
501                 String state = value.getState().toString();
502
503                 str += "\n - " + channel + " = " + vStr;
504                 if (!vStr.equals(state)) {
505                     str += " (" + state + ")";
506                 }
507             }
508         }
509
510         for (int i = 0; i < pgmX10DevicesStatus.length; i++) {
511             if (getPGMX10DeviceStatus(i) != null) {
512                 str += String.format("\n - %s status = %s", (i == 0) ? "PGM device" : String.format("X10 device %d", i),
513                         getPGMX10DeviceStatus(i) ? "ON" : "OFF");
514             }
515         }
516
517         for (int i = 1; i <= zones.length; i++) {
518             for (Value<?> value : zones[i - 1].getValues()) {
519                 if ((value.getChannel() != null) && (value.getValue() != null)) {
520                     String channel = value.getChannel();
521                     String vStr = value.getValue().toString();
522                     String state = value.getState().toString();
523
524                     str += String.format("\n - sensor zone %d %s = %s", i, channel, vStr);
525                     if (!vStr.equals(state)) {
526                         str += " (" + state + ")";
527                     }
528                 }
529             }
530         }
531
532         for (int i = 1; i <= getEventLogSize(); i++) {
533             if (getEventLog(i) != null) {
534                 str += "\n - event log " + i + " = " + getEventLog(i);
535             }
536         }
537
538         String alarms = getActiveAlerts();
539         str += "\n - active alarms/alerts = " + (alarms == null ? "null" : alarms);
540
541         return str;
542     }
543 }