From 1eba620dbcb2a71f886bedc2e45508e98cec2826 Mon Sep 17 00:00:00 2001 From: druciak Date: Sun, 5 Apr 2020 15:57:28 +0200 Subject: [PATCH] [satel] Action for reading the event log added (#7282) Signed-off-by: Krzysztof Goworek Signed-off-by: Eugen Freiter --- bundles/org.openhab.binding.satel/README.md | 86 ++++++++------ .../satel/action/SatelEventLogActions.java | 82 ++++++++++++++ .../handler/SatelEventLogHandler.java | 106 ++++++++++++++++-- .../resources/ESH-INF/i18n/satel.properties | 16 +++ 4 files changed, 246 insertions(+), 44 deletions(-) create mode 100644 bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/action/SatelEventLogActions.java diff --git a/bundles/org.openhab.binding.satel/README.md b/bundles/org.openhab.binding.satel/README.md index 2a452f79905a8..e9f09ea7ae5d5 100644 --- a/bundles/org.openhab.binding.satel/README.md +++ b/bundles/org.openhab.binding.satel/README.md @@ -55,6 +55,10 @@ Example: Bridge satel:ethm-1:home [ host="192.168.0.2", refresh=1000, userCode="1234", encryptionKey="abcdefgh" ] ``` +**NOTE:** There can be only one client connected to ETHM-1 module. +It does not accept new connections if there is already a connection established. +In case you have troubles connecting to the system using this module, please make sure there is no other client (for example installed 1.x version of the binding) already connected to it. + ### int-rs bridge You can configure the following settings for this bridge: @@ -218,6 +222,8 @@ Thing atd-100 KitchenTemp [ id=10, refresh=30 ] ### output +**NOTE:** You can change state of mono/bistable outputs only. + | Name | Type | Description | |---------------|--------|-----------------------------------------------------------| | state | Switch | State of the output | @@ -243,7 +249,9 @@ Thing atd-100 KitchenTemp [ id=10, refresh=30 ] | grade23_set | Switch | Active when Grade2/Grade3 option is set in the system | | user_code | String | Accepts string commands that override configured user code. Send empty string to revert user code to the one in the configuration. | -### event-log +### event-log (deprecated) + +These channels and the thing will be removed in the future release of the binding. Please use `readEvent` rule action instead. | Name | Type | Description | |-------------|----------|----------------------------------------------------------------------------------------| @@ -261,6 +269,31 @@ Thing atd-100 KitchenTemp [ id=10, refresh=30 ] | device_lobatt | Switch | Indicates low battery level in the wireless device | | device_nocomm | Switch | Indicates communication troubles with the wireless device | +## Rule Actions + +### readEvent + +This action allows you to read one record from the event log placed at index given by input parameter. +The result of this action is compatible with channels of `event-log` thing and contains following values: + +| Name | Type | Description | +|-------------|---------------|----------------------------------------------------------------------------------------| +| index | Number | Index of this record in the event log. | +| prev_index | Number | Index of the previous record in the event log. Use this value to iterate over the log. | +| timestamp | ZonedDateTime | Date and time when the event happened. | +| description | String | Textual description of the event. | +| details | String | Details about the event, usually list of objects related to the event. | + +Usage: + +``` + val actions = getActions("satel", "satel:event-log:home:EventLog") + val eventRec = actions.readEvent(-1) + logInfo("EventLog", eventRec.get("description")) +``` + +**NOTE:** To have this action available, you must have `event-log` thing configured in openHAB. + ## Full Example ### satel.things @@ -295,11 +328,6 @@ Rollershutter KITCHEN_BLIND "Kitchen blind" (Satel) { channel="satel:shutter:hom Switch SYSTEM_TROUBLES "Troubles in the system" (Satel) { channel="satel:system:home:System:troubles" } String KEYPAD_CHAR ">" (Satel) String USER_CODE "User code" (Satel) { channel="satel:system:home:System:user_code" } -Number EVENT_LOG_IDX "Event log - current index [%d]" (Satel) { channel="satel:event-log:home:EventLog:index" } -Number EVENT_LOG_PREV "Event log - previous index [%d]" (Satel) { channel="satel:event-log:home:EventLog:prev_index" } -DateTime EVENT_LOG_TIME "Event log - time [%1$tF %1$tR]" (Satel) { channel="satel:event-log:home:EventLog:timestamp" } -String EVENT_LOG_DESCR "Event log - description [%s]" (Satel) { channel="satel:event-log:home:EventLog:description" } -String EVENT_LOG_DET "Event log - details [%s]" (Satel) { channel="satel:event-log:home:EventLog:details" } Switch SIREN_LOBATT "Siren: low battery level" (Satel) { channel="satel:output:home:Siren:device_lobatt" } Switch SIREN_NOCOMM "Siren: no communication" (Satel) { channel="satel:output:home:Siren:device_nocomm" } Number:Temperature KITCHEN_TEMP "Kitchen temperature [%.1f °C]" (Satel) { channel="satel:atd-100:home:KitchenTemp:temperature" } @@ -344,8 +372,6 @@ Frame label="Alarm system" { var String userCode = "" var Timer keypadTimer = null var Timer userCodeTimer = null -var int eventLogCounter = 0 -var String eventLogMsgBody = "" rule "Keypad char entered" when @@ -381,34 +407,30 @@ then ] end -rule "Send event log start" +rule "Send event log" when Item Alarms changed to ON then - eventLogCounter = 0 - eventLogMsgBody = "" - EVENT_LOG_PREV.postUpdate(NULL) - EVENT_LOG_IDX.sendCommand(-1) -end - -rule "Send event log next" -when - Item EVENT_LOG_PREV changed -then - if (EVENT_LOG_PREV.state == NULL) { - - } else if (eventLogCounter == 30) { - sendMail("my@address.net", "Alarm system log", eventLogMsgBody) - // prevent initiating reading when index item is restored during OH startup - EVENT_LOG_IDX.postUpdate(NULL) - } else { - eventLogMsgBody += "\n" + (EVENT_LOG_TIME.state as DateTimeType).format("%1$tF %1$tR") + ": " + EVENT_LOG_DESCR.state - if (EVENT_LOG_DET.state != NULL && EVENT_LOG_DET.state != "") { - eventLogMsgBody += " - " + EVENT_LOG_DET.state - } - eventLogCounter += 1 - EVENT_LOG_IDX.sendCommand(EVENT_LOG_PREV.state) + val actions = getActions("satel", "satel:event-log:home:EventLog") + if (null === actions) { + logInfo("EventLog", "Actions not found, check thing ID") + return } + logInfo("EventLog", "Start") + var msgBody = "" + var eventIdx = -1 + (1..10).forEach[ + val eventRec = actions.readEvent(eventIdx) + val details = eventRec.get("details") + msgBody += "\n" + String::format("%1$tF %1$tR", eventRec.get("timestamp")) + ": " + eventRec.get("description") + if (details != NULL && details != "") { + msgBody += " - " + details + } + eventIdx = eventRec.get("prev_index") + ] + logInfo("EventLog", "End") + // sending notifications via mail requires the mail binding + getActions("mail","mail:smtp:local").sendMail("you@email.net", "Event log", msgBody) end ``` diff --git a/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/action/SatelEventLogActions.java b/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/action/SatelEventLogActions.java new file mode 100644 index 0000000000000..63077c95de61f --- /dev/null +++ b/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/action/SatelEventLogActions.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.satel.action; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.binding.ThingActions; +import org.eclipse.smarthome.core.thing.binding.ThingActionsScope; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.openhab.binding.satel.internal.handler.SatelEventLogHandler; +import org.openhab.core.automation.annotation.ActionInput; +import org.openhab.core.automation.annotation.ActionOutput; +import org.openhab.core.automation.annotation.RuleAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Automation action handler service for reading Satel event log. + * + * @author Krzysztof Goworek - Initial contribution + * @see SatelEventLogHandler + */ +@ThingActionsScope(name = "satel") +@NonNullByDefault +public class SatelEventLogActions implements ThingActions { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private @Nullable SatelEventLogHandler handler; + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + if (handler instanceof SatelEventLogHandler) { + this.handler = (SatelEventLogHandler) handler; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return handler; + } + + @RuleAction(label = "@text/actionReadEventLabel", description = "@text/actionReadEventDesc") + public @ActionOutput(name = "index", type = "java.lang.Integer", label = "@text/actionOutputIndexLabel", description = "@text/actionOutputIndexDesc") @ActionOutput(name = "prev_index", type = "java.lang.Integer", label = "@text/actionOutputPrevIndexLabel", description = "@text/actionOutputPrevIndexDesc") @ActionOutput(name = "timestamp", type = "java.time.ZonedDateTime", label = "@text/actionOutputTimestampLabel", description = "@text/actionOutputTimestampDesc") @ActionOutput(name = "description", type = "java.lang.String", label = "@text/actionOutputDescriptionLabel", description = "@text/actionOutputDescriptionDesc") @ActionOutput(name = "details", type = "java.lang.String", label = "@text/actionOutputDetailsLabel", description = "@text/actionOutputDetailsDesc") Map readEvent( + @ActionInput(name = "index", label = "@text/actionInputIndexLabel", description = "@text/actionInputIndexDesc") @Nullable Number index) { + logger.debug("satel.readEvent called with input: index={}", index); + + Map result = new HashMap<>(); + if (handler != null) { + handler.readEvent(index == null ? -1 : index.intValue()).ifPresent(event -> { + result.put("index", event.getIndex()); + result.put("prev_index", event.getPrevIndex()); + result.put("timestamp", event.getTimestamp()); + result.put("description", event.getDescription()); + result.put("details", event.getDetails()); + }); + } + return result; + } + + public static Map readEvent(@Nullable ThingActions actions, @Nullable Number index) { + if (actions instanceof SatelEventLogActions) { + return ((SatelEventLogActions) actions).readEvent(index); + } else { + throw new IllegalArgumentException("Instance is not a SatelEventLogActions class."); + } + } + +} diff --git a/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/internal/handler/SatelEventLogHandler.java b/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/internal/handler/SatelEventLogHandler.java index c80221858a353..233fd535ddcdb 100644 --- a/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/internal/handler/SatelEventLogHandler.java +++ b/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/internal/handler/SatelEventLogHandler.java @@ -15,6 +15,8 @@ import static org.openhab.binding.satel.internal.SatelBindingConstants.*; import java.nio.charset.Charset; +import java.time.ZonedDateTime; +import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Optional; @@ -32,7 +34,9 @@ import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerService; import org.eclipse.smarthome.core.types.Command; +import org.openhab.binding.satel.action.SatelEventLogActions; import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand; import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand.DeviceType; import org.openhab.binding.satel.internal.command.ReadEventCommand; @@ -63,6 +67,71 @@ public class SatelEventLogHandler extends SatelThingHandler { private @Nullable ScheduledFuture cacheExpirationJob; private Charset encoding = Charset.defaultCharset(); + /** + * Represents single record of the event log. + * + * @author Krzysztof Goworek + * + */ + public static class EventLogEntry { + + private final int index; + private final int prevIndex; + private final ZonedDateTime timestamp; + private final String description; + private final String details; + + private EventLogEntry(int index, int prevIndex, ZonedDateTime timestamp, String description, String details) { + this.index = index; + this.prevIndex = prevIndex; + this.timestamp = timestamp; + this.description = description; + this.details = details; + } + + /** + * @return index of this record entry + */ + public int getIndex() { + return index; + } + + /** + * @return index of the previous record entry in the log + */ + public int getPrevIndex() { + return prevIndex; + } + + /** + * @return date and time when the event occurred + */ + public ZonedDateTime getTimestamp() { + return timestamp; + } + + /** + * @return description of the event + */ + public String getDescription() { + return description; + } + + /** + * @return details about zones, partitions, users, etc + */ + public String getDetails() { + return details; + } + + @Override + public String toString() { + return "EventLogEntry [index=" + index + ", prevIndex=" + prevIndex + ", timestamp=" + timestamp + + ", description=" + description + ", details=" + details + "]"; + } + + } + public SatelEventLogHandler(Thing thing) { super(thing); } @@ -100,7 +169,14 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (CHANNEL_INDEX.equals(channelUID.getId()) && command instanceof DecimalType) { int eventIndex = ((DecimalType) command).intValue(); - withBridgeHandlerPresent(bridgeHandler -> readEvent(eventIndex)); + withBridgeHandlerPresent(bridgeHandler -> readEvent(eventIndex).ifPresent(entry -> { + // update items + updateState(CHANNEL_INDEX, new DecimalType(entry.getIndex())); + updateState(CHANNEL_PREV_INDEX, new DecimalType(entry.getPrevIndex())); + updateState(CHANNEL_TIMESTAMP, new DateTimeType(entry.getTimestamp())); + updateState(CHANNEL_DESCRIPTION, new StringType(entry.getDescription())); + updateState(CHANNEL_DETAILS, new StringType(entry.getDetails())); + })); } } @@ -113,8 +189,19 @@ public void incomingEvent(ConnectionStatusEvent event) { } } - private void readEvent(int eventIndex) { - getEventDescription(eventIndex).ifPresent(eventDesc -> { + @Override + public Collection> getServices() { + return Collections.singleton(SatelEventLogActions.class); + } + + /** + * Reads one record from the event log. + * + * @param eventIndex record index + * @return record data or {@linkplain Optional#empty()} if there is no record under given index + */ + public Optional readEvent(int eventIndex) { + return getEventDescription(eventIndex).flatMap(eventDesc -> { ReadEventCommand readEventCmd = eventDesc.readEventCmd; int currentIndex = readEventCmd.getCurrentIndex(); String eventText = eventDesc.getText(); @@ -191,13 +278,13 @@ private void readEvent(int eventIndex) { + (readEventCmd.getObject() * 32 + readEventCmd.getUserControlNumber()); Optional eventDescNext = getEventDescription(readEventCmd.getNextIndex()); if (!eventDescNext.isPresent()) { - return; + return Optional.empty(); } final EventDescription eventDescNextItem = eventDescNext.get(); if (eventDescNextItem.getKind() != 30) { logger.info("Unexpected event record kind {} at index {}", eventDescNextItem.getKind(), readEventCmd.getNextIndex()); - return; + return Optional.empty(); } readEventCmd = eventDescNextItem.readEventCmd; eventText = eventDescNextItem.getText(); @@ -213,13 +300,8 @@ private void readEvent(int eventIndex) { "object=" + readEventCmd.getObject(), "ucn=" + readEventCmd.getUserControlNumber()); } - // update items - updateState(CHANNEL_INDEX, new DecimalType(currentIndex)); - updateState(CHANNEL_PREV_INDEX, new DecimalType(readEventCmd.getNextIndex())); - updateState(CHANNEL_TIMESTAMP, - new DateTimeType(readEventCmd.getTimestamp().atZone(getBridgeHandler().getZoneId()))); - updateState(CHANNEL_DESCRIPTION, new StringType(eventText)); - updateState(CHANNEL_DETAILS, new StringType(eventDetails)); + return Optional.of(new EventLogEntry(currentIndex, readEventCmd.getNextIndex(), + readEventCmd.getTimestamp().atZone(getBridgeHandler().getZoneId()), eventText, eventDetails)); }); } diff --git a/bundles/org.openhab.binding.satel/src/main/resources/ESH-INF/i18n/satel.properties b/bundles/org.openhab.binding.satel/src/main/resources/ESH-INF/i18n/satel.properties index 5034f2f8472d7..7b4b8644da56d 100644 --- a/bundles/org.openhab.binding.satel/src/main/resources/ESH-INF/i18n/satel.properties +++ b/bundles/org.openhab.binding.satel/src/main/resources/ESH-INF/i18n/satel.properties @@ -1,3 +1,19 @@ # config status config-status.hostEmpty = Host name or IP of ETHM-1 must be provided. config-status.portEmpty = Serial port of INT-RS must be provided. + +# actions +actionReadEventLabel = Read event +actionReadEventDesc = Reads a single record from the event log. +actionInputIndexLabel = Event index +actionInputIndexDesc = Index of the event to read +actionOutputIndexLabel = Current Index +actionOutputIndexDesc = Index of this record in the event log. +actionOutputPrevIndexLabel = Previous Index +actionOutputPrevIndexDesc = Index of the previous record in the event log. Use this value to iterate over the log. +actionOutputTimestampLabel = Date and Time +actionOutputTimestampDesc = Date and time when the event happened. +actionOutputDescriptionLabel = Description +actionOutputDescriptionDesc = Textual description of the event. +actionOutputDetailsLabel = Details +actionOutputDetailsDesc = Additional details about the event, like names of partitions, zones, users, etc.