[satel] Event log reading refactored with other fixes (#18328)

* [satel] Event log reading refactored with other fixes:
- partition keypad decoding for I128 WRL
- replaced N/A text with device number when device name is unavailable
- user decoding for certain cases fixed

Signed-off-by: Krzysztof Goworek <krzysztof.goworek@gmail.com>
pull/18343/head
druciak 2025-02-28 17:09:06 +01:00 committed by GitHub
parent 64616a0c55
commit d98bf37cf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1263 additions and 232 deletions

View File

@ -14,25 +14,20 @@ package org.openhab.binding.satel.internal.handler;
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
import java.nio.charset.Charset;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.satel.internal.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;
import org.openhab.binding.satel.internal.command.ReadEventDescCommand;
import org.openhab.binding.satel.internal.event.ConnectionStatusEvent;
import org.openhab.binding.satel.internal.types.IntegraType;
import org.openhab.binding.satel.internal.util.DeviceNameResolver;
import org.openhab.binding.satel.internal.util.EventLogReader;
import org.openhab.binding.satel.internal.util.EventLogReader.EventDescription;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
@ -56,21 +51,16 @@ public class SatelEventLogHandler extends SatelThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_EVENTLOG);
private static final String NOT_AVAILABLE_TEXT = "N/A";
private static final String DETAILS_SEPARATOR = ", ";
private static final long CACHE_CLEAR_INTERVAL = TimeUnit.MINUTES.toMillis(30);
private final Logger logger = LoggerFactory.getLogger(SatelEventLogHandler.class);
private final Map<String, @Nullable EventDescriptionCacheEntry> eventDescriptions = new ConcurrentHashMap<>();
private final Map<String, @Nullable String> deviceNameCache = new ConcurrentHashMap<>();
private @Nullable EventLogReader eventLogReader;
private @Nullable ScheduledFuture<?> cacheExpirationJob;
private Charset encoding = Charset.defaultCharset();
/**
* Represents single record of the event log.
*
* @author Krzysztof Goworek
*
*/
public static class EventLogEntry {
@ -138,14 +128,13 @@ public class SatelEventLogHandler extends SatelThingHandler {
public void initialize() {
super.initialize();
withBridgeHandlerPresent(bridgeHandler -> {
this.encoding = bridgeHandler.getEncoding();
});
withBridgeHandlerPresent(bridgeHandler -> this.eventLogReader = new EventLogReader(bridgeHandler,
new DeviceNameResolver(bridgeHandler)));
final ScheduledFuture<?> cacheExpirationJob = this.cacheExpirationJob;
if (cacheExpirationJob == null || cacheExpirationJob.isCancelled()) {
// for simplicity all cache entries are cleared every 30 minutes
this.cacheExpirationJob = scheduler.scheduleWithFixedDelay(deviceNameCache::clear, CACHE_CLEAR_INTERVAL,
this.cacheExpirationJob = scheduler.scheduleWithFixedDelay(this::clearCache, CACHE_CLEAR_INTERVAL,
CACHE_CLEAR_INTERVAL, TimeUnit.MILLISECONDS);
}
}
@ -159,6 +148,7 @@ public class SatelEventLogHandler extends SatelThingHandler {
cacheExpirationJob.cancel(true);
}
this.cacheExpirationJob = null;
this.eventLogReader = null;
}
@Override
@ -199,223 +189,32 @@ public class SatelEventLogHandler extends SatelThingHandler {
* @return record data or {@linkplain Optional#empty()} if there is no record under given index
*/
public Optional<EventLogEntry> readEvent(int eventIndex) {
return getEventDescription(eventIndex).flatMap(eventDesc -> {
ReadEventCommand readEventCmd = eventDesc.readEventCmd;
int currentIndex = readEventCmd.getCurrentIndex();
String eventText = eventDesc.getText();
boolean upperZone = getBridgeHandler().getIntegraType() == IntegraType.I256_PLUS
&& readEventCmd.getUserControlNumber() > 0;
String eventDetails;
switch (eventDesc.getKind()) {
case 0:
eventDetails = "";
break;
case 1:
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
+ DETAILS_SEPARATOR + getZoneExpanderKeypadDescription(readEventCmd.getSource(), upperZone);
break;
case 2:
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
+ DETAILS_SEPARATOR + getUserDescription(readEventCmd.getSource());
break;
case 3:
eventDetails = getDeviceDescription(DeviceType.EXPANDER, readEventCmd.getPartitionKeypad())
+ DETAILS_SEPARATOR + getUserDescription(readEventCmd.getSource());
break;
case 4:
eventDetails = getZoneExpanderKeypadDescription(readEventCmd.getSource(), upperZone);
break;
case 5:
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition());
break;
case 6:
eventDetails = getDeviceDescription(DeviceType.KEYPAD, readEventCmd.getPartition())
+ DETAILS_SEPARATOR + getUserDescription(readEventCmd.getSource());
break;
case 7:
eventDetails = getUserDescription(readEventCmd.getSource());
break;
case 8:
eventDetails = getDeviceDescription(DeviceType.EXPANDER, readEventCmd.getSource());
break;
case 9:
eventDetails = getTelephoneDescription(readEventCmd.getSource());
break;
case 11:
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
+ DETAILS_SEPARATOR + getDataBusDescription(readEventCmd.getSource());
break;
case 12:
if (readEventCmd.getSource() <= getBridgeHandler().getIntegraType().getOnMainboard()) {
eventDetails = getOutputExpanderDescription(readEventCmd.getSource(), upperZone);
} else {
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
+ DETAILS_SEPARATOR + getOutputExpanderDescription(readEventCmd.getSource(), upperZone);
}
break;
case 13:
if (readEventCmd.getSource() <= 128) {
eventDetails = getOutputExpanderDescription(readEventCmd.getSource(), upperZone);
} else {
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
+ DETAILS_SEPARATOR + getOutputExpanderDescription(readEventCmd.getSource(), upperZone);
}
break;
case 14:
eventDetails = getTelephoneDescription(readEventCmd.getPartition()) + DETAILS_SEPARATOR
+ getUserDescription(readEventCmd.getSource());
break;
case 15:
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
+ DETAILS_SEPARATOR + getDeviceDescription(DeviceType.TIMER, readEventCmd.getSource());
break;
case 31:
// this description consists of two records, so we must read additional record from the log
eventDetails = "." + readEventCmd.getSource() + "."
+ (readEventCmd.getObject() * 32 + readEventCmd.getUserControlNumber());
Optional<EventDescription> eventDescNext = getEventDescription(readEventCmd.getNextIndex());
if (eventDescNext.isEmpty()) {
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 Optional.empty();
}
readEventCmd = eventDescNextItem.readEventCmd;
eventText = eventDescNextItem.getText();
eventDetails = getDeviceDescription(DeviceType.KEYPAD, readEventCmd.getPartition())
+ DETAILS_SEPARATOR + "ip: " + readEventCmd.getSource() + "."
+ (readEventCmd.getObject() * 32 + readEventCmd.getUserControlNumber()) + eventDetails;
break;
case 32:
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
+ DETAILS_SEPARATOR + getDeviceDescription(DeviceType.ZONE, readEventCmd.getSource());
break;
default:
logger.info("Unsupported device kind code {} at index {}", eventDesc.getKind(),
readEventCmd.getCurrentIndex());
eventDetails = String.join(DETAILS_SEPARATOR, "kind=" + eventDesc.getKind(),
"partition=" + readEventCmd.getPartition(), "source=" + readEventCmd.getSource(),
"object=" + readEventCmd.getObject(), "ucn=" + readEventCmd.getUserControlNumber());
}
return Optional.of(new EventLogEntry(currentIndex, readEventCmd.getNextIndex(),
readEventCmd.getTimestamp().atZone(getBridgeHandler().getZoneId()), eventText, eventDetails));
});
}
private Optional<EventDescription> getEventDescription(int eventIndex) {
ReadEventCommand readEventCmd = new ReadEventCommand(eventIndex);
if (!getBridgeHandler().sendCommand(readEventCmd, false)) {
logger.info("Unable to read event record for given index: {}", eventIndex);
final EventLogReader eventLogReader = this.eventLogReader;
if (eventLogReader == null) {
logger.warn("Unable to read event: handler is not properly initialized");
return Optional.empty();
} else if (readEventCmd.isEmpty()) {
logger.info("No record under given index: {}", eventIndex);
return Optional.empty();
} else {
return Optional.of(readEventDescription(readEventCmd));
}
}
private static class EventDescriptionCacheEntry {
private final String eventText;
private final int descKind;
EventDescriptionCacheEntry(String eventText, int descKind) {
this.eventText = eventText;
this.descKind = descKind;
}
String getText() {
return eventText;
return readEvent(eventLogReader, eventIndex);
}
private Optional<EventLogEntry> readEvent(EventLogReader eventLogReader, int eventIndex) {
return eventLogReader.readEvent(eventIndex)
.map(eventDescription -> combineWithDetails(eventLogReader, eventDescription));
}
private EventLogEntry combineWithDetails(EventLogReader eventLogReader, EventDescription eventDescription) {
String eventDetails = eventLogReader.buildDetails(eventDescription);
return new EventLogEntry(eventDescription.getCurrentIndex(), eventDescription.getNextIndex(),
eventDescription.getTimestamp().atZone(getBridgeHandler().getZoneId()), eventDescription.getText(),
eventDetails);
}
private void clearCache() {
final EventLogReader eventLogReader = this.eventLogReader;
if (eventLogReader != null) {
eventLogReader.clearCache();
}
int getKind() {
return descKind;
}
}
private static class EventDescription extends EventDescriptionCacheEntry {
private final ReadEventCommand readEventCmd;
EventDescription(ReadEventCommand readEventCmd, String eventText, int descKind) {
super(eventText, descKind);
this.readEventCmd = readEventCmd;
}
}
private EventDescription readEventDescription(ReadEventCommand readEventCmd) {
int eventCode = readEventCmd.getEventCode();
boolean restore = readEventCmd.isRestore();
String mapKey = String.format("%d_%b", eventCode, restore);
EventDescriptionCacheEntry mapValue = eventDescriptions.computeIfAbsent(mapKey, k -> {
ReadEventDescCommand cmd = new ReadEventDescCommand(eventCode, restore, true);
if (!getBridgeHandler().sendCommand(cmd, false)) {
logger.info("Unable to read event description: {}, {}", eventCode, restore);
return null;
}
return new EventDescriptionCacheEntry(cmd.getText(encoding), cmd.getKind());
});
if (mapValue == null) {
return new EventDescription(readEventCmd, NOT_AVAILABLE_TEXT, 0);
} else {
return new EventDescription(readEventCmd, mapValue.getText(), mapValue.getKind());
}
}
private String getOutputExpanderDescription(int deviceNumber, boolean upperOutput) {
if (deviceNumber == 0) {
return "mainboard";
} else if (deviceNumber <= 128) {
return getDeviceDescription(DeviceType.OUTPUT, upperOutput ? 128 + deviceNumber : deviceNumber);
} else if (deviceNumber <= 192) {
return getDeviceDescription(DeviceType.EXPANDER, deviceNumber);
} else {
return "invalid output|expander device: " + deviceNumber;
}
}
private String getZoneExpanderKeypadDescription(int deviceNumber, boolean upperZone) {
if (deviceNumber == 0) {
return "mainboard";
} else if (deviceNumber <= 128) {
return getDeviceDescription(DeviceType.ZONE, upperZone ? 128 + deviceNumber : deviceNumber);
} else if (deviceNumber <= 192) {
return getDeviceDescription(DeviceType.EXPANDER, deviceNumber);
} else {
return getDeviceDescription(DeviceType.KEYPAD, deviceNumber);
}
}
private String getUserDescription(int deviceNumber) {
return deviceNumber == 0 ? "user: unknown" : getDeviceDescription(DeviceType.USER, deviceNumber);
}
private String getDataBusDescription(int deviceNumber) {
return "data bus: " + deviceNumber;
}
private String getTelephoneDescription(int deviceNumber) {
return deviceNumber == 0 ? "telephone: unknown" : getDeviceDescription(DeviceType.TELEPHONE, deviceNumber);
}
private String getDeviceDescription(DeviceType deviceType, int deviceNumber) {
return String.format("%s: %s", deviceType.name().toLowerCase(), readDeviceName(deviceType, deviceNumber));
}
private String readDeviceName(DeviceType deviceType, int deviceNumber) {
String cacheKey = String.format("%s_%d", deviceType, deviceNumber);
String result = deviceNameCache.computeIfAbsent(cacheKey, k -> {
ReadDeviceInfoCommand cmd = new ReadDeviceInfoCommand(deviceType, deviceNumber);
if (!getBridgeHandler().sendCommand(cmd, false)) {
logger.info("Unable to read device info: {}, {}", deviceType, deviceNumber);
return null;
}
return cmd.getName(encoding);
});
return result == null ? NOT_AVAILABLE_TEXT : result;
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright (c) 2010-2025 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.internal.util;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand;
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand.DeviceType;
import org.openhab.binding.satel.internal.handler.SatelBridgeHandler;
import org.openhab.binding.satel.internal.handler.SatelEventLogHandler;
import org.openhab.binding.satel.internal.types.IntegraType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper class for getting device names from the alarm system.
* Used for friendly descriptions in event log. Names are cached to speed up repeating requests.
*
* @author Krzysztof Goworek - Initial contribution
* @see SatelEventLogHandler
*/
@NonNullByDefault
public class DeviceNameResolver {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Map<String, @Nullable String> nameCache = new ConcurrentHashMap<>();
private final SatelBridgeHandler bridgeHandler;
public DeviceNameResolver(SatelBridgeHandler bridgeHandler) {
this.bridgeHandler = bridgeHandler;
}
/**
* Clears all cached names.
*/
public void clearCache() {
nameCache.clear();
}
/**
* Returns name of the device with given type and number.
*
* @param deviceType device type
* @param deviceNumber device number
* @return device name
*/
public String resolve(DeviceType deviceType, int deviceNumber) {
return String.format("%s: %s", deviceType.name().toLowerCase(), readDeviceName(deviceType, deviceNumber));
}
/**
* Returns name of output or expander with given number.
*
* @param deviceNumber device number
* @param upperOutput if {@code true} it is upper half of outputs
* @return name of output or expander, depending on device number
*/
public String resolveOutputExpander(int deviceNumber, boolean upperOutput) {
if (deviceNumber == 0) {
return "mainboard";
} else if (deviceNumber <= 128) {
return resolve(DeviceType.OUTPUT, upperOutput ? 128 + deviceNumber : deviceNumber);
} else if (deviceNumber <= 192) {
return resolve(DeviceType.EXPANDER, deviceNumber);
} else {
return "invalid output|expander device: " + deviceNumber;
}
}
/**
* Returns name of zone or expander or keypad with given number.
*
* @param deviceNumber device number
* @param upperZone if {@code true} it is upper half of zones
* @return name of zone or expander or keypad, depending on device number
*/
public String resolveZoneExpanderKeypad(int deviceNumber, boolean upperZone) {
if (deviceNumber == 0) {
return "mainboard";
} else if (deviceNumber <= 128) {
return resolve(DeviceType.ZONE, upperZone ? 128 + deviceNumber : deviceNumber);
} else if (deviceNumber <= 192) {
return resolve(DeviceType.EXPANDER, deviceNumber);
} else {
return resolve(DeviceType.KEYPAD, deviceNumber);
}
}
/**
* Returns name of partition keypad with given number.
*
* @param deviceNumber device number
* @return name of partition keypad
*/
public String resolvePartitionKeypad(int deviceNumber) {
boolean wrlBoard = bridgeHandler.getIntegraType() == IntegraType.I128_LEON
|| bridgeHandler.getIntegraType() == IntegraType.I128_SIM300;
// Integra 128-WRL has only one expander bus exposed on the mainboard, it can only have 32 expanders
// On the second bus it has connected embedded ABAX and GSM modules
if (wrlBoard && deviceNumber > 32) {
return "mainboard";
} else {
return resolve(DeviceType.EXPANDER, deviceNumber);
}
}
/**
* Returns name of user with given number.
*
* @param deviceNumber device number
* @return name of user
*/
public String resolveUser(int deviceNumber) {
return switch (deviceNumber) {
case 0 -> "user: unknown";
case 249 -> "INT-AV";
case 250 -> "ACCO NET";
case 251 -> "SMS";
case 252 -> "timer";
case 253 -> "function zone";
case 254 -> "Quick arm";
case 255 -> "service";
default -> resolve(DeviceType.USER, deviceNumber);
};
}
/**
* Returns name of data bus given number.
*
* @param deviceNumber device number
* @return name of data bus
*/
public String resolveDataBus(int deviceNumber) {
return "data bus: " + deviceNumber;
}
/**
* Returns name of telephone with given number.
*
* @param deviceNumber device number
* @return name of telephone
*/
public String resolveTelephone(int deviceNumber) {
return deviceNumber == 0 ? "telephone: unknown" : resolve(DeviceType.TELEPHONE, deviceNumber);
}
private String readDeviceName(DeviceType deviceType, int deviceNumber) {
String cacheKey = String.format("%s_%d", deviceType, deviceNumber);
String result = nameCache.computeIfAbsent(cacheKey, k -> {
ReadDeviceInfoCommand cmd = new ReadDeviceInfoCommand(deviceType, deviceNumber);
if (!bridgeHandler.sendCommand(cmd, false)) {
logger.warn("Unable to read device info: {}, {}", deviceType, deviceNumber);
return null;
}
return cmd.getName(bridgeHandler.getEncoding());
});
return result == null ? Integer.toString(deviceNumber) : result;
}
}

View File

@ -0,0 +1,260 @@
/*
* Copyright (c) 2010-2025 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.internal.util;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand.DeviceType;
import org.openhab.binding.satel.internal.command.ReadEventCommand;
import org.openhab.binding.satel.internal.command.ReadEventDescCommand;
import org.openhab.binding.satel.internal.handler.SatelBridgeHandler;
import org.openhab.binding.satel.internal.types.IntegraType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is a helper class for reading event log records.
*
* @author Krzysztof Goworek - Initial contribution
*/
@NonNullByDefault
public class EventLogReader {
private static final String DETAILS_SEPARATOR = ", ";
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Map<String, @Nullable EventDescriptionCacheEntry> eventDescriptions = new ConcurrentHashMap<>();
private final SatelBridgeHandler bridgeHandler;
private final DeviceNameResolver deviceNameResolver;
public EventLogReader(SatelBridgeHandler bridgeHandler, DeviceNameResolver deviceNameResolver) {
this.bridgeHandler = bridgeHandler;
this.deviceNameResolver = deviceNameResolver;
}
/**
* Reads one record from the event log.
*
* @param eventIndex record index
* @return record description wrapped in {@linkplain Optional} or {@linkplain Optional#empty()} if there is no
* record under given index or read command failed
*/
public Optional<EventDescription> readEvent(int eventIndex) {
ReadEventCommand readEventCmd = new ReadEventCommand(eventIndex);
if (!bridgeHandler.sendCommand(readEventCmd, false)) {
logger.warn("Unable to read event record for given index: {}", eventIndex);
return Optional.empty();
} else if (readEventCmd.isEmpty()) {
logger.warn("No record under given index: {}", eventIndex);
return Optional.empty();
} else {
return Optional.of(readEventDescription(readEventCmd));
}
}
/**
* Builds detailed text description for given event record.
*
* @param eventDescription event record
* @return string with event details
*/
public String buildDetails(EventDescription eventDescription) {
String eventDetails = getDetails(eventDescription);
if (eventDescription.isMultipartEvent()) {
// this description consists of two records, so we must read additional record from the log
eventDetails = readSecondPartOfDetails(eventDescription).orElse("") + eventDetails;
}
return eventDetails;
}
/**
* Removes all device names from the cache.
*/
public void clearCache() {
deviceNameResolver.clearCache();
}
private static class EventDescriptionCacheEntry {
private final String eventText;
private final int descKind;
private EventDescriptionCacheEntry(String eventText, int descKind) {
this.eventText = eventText;
this.descKind = descKind;
}
public String getText() {
return eventText;
}
int getKind() {
return descKind;
}
}
/**
* Contains decoded data of an event record.
*/
public class EventDescription extends EventDescriptionCacheEntry {
private final int currentIndex;
private int nextIndex;
private final int userControlNumber;
private final int partition;
private final int partitionKeypad;
private final int source;
private final int object;
private final LocalDateTime timestamp;
EventDescription(ReadEventCommand readEventCmd, String eventText, int descKind) {
super(eventText, descKind);
this.currentIndex = readEventCmd.getCurrentIndex();
this.nextIndex = readEventCmd.getNextIndex();
this.userControlNumber = readEventCmd.getUserControlNumber();
this.partition = readEventCmd.getPartition();
this.partitionKeypad = readEventCmd.getPartitionKeypad();
this.source = readEventCmd.getSource();
this.object = readEventCmd.getObject();
this.timestamp = readEventCmd.getTimestamp();
}
public int getCurrentIndex() {
return currentIndex;
}
public int getNextIndex() {
return nextIndex;
}
public LocalDateTime getTimestamp() {
return timestamp;
}
private void setNextIndex(int nextIndex) {
this.nextIndex = nextIndex;
}
private int getUserControlNumber() {
return userControlNumber;
}
private int getPartition() {
return partition;
}
private int getPartitionKeypad() {
return partitionKeypad;
}
private int getSource() {
return source;
}
private int getObject() {
return object;
}
private boolean isMultipartEvent() {
return getKind() == 31;
}
private boolean isSecondPart() {
if (getKind() != 30) {
logger.warn("Unexpected event record kind {} at index {}", getKind(), getCurrentIndex());
return false;
}
return true;
}
}
private EventDescription readEventDescription(ReadEventCommand readEventCmd) {
int eventCode = readEventCmd.getEventCode();
boolean restore = readEventCmd.isRestore();
String mapKey = String.format("%d_%b", eventCode, restore);
EventDescriptionCacheEntry mapValue = eventDescriptions.computeIfAbsent(mapKey, k -> {
ReadEventDescCommand cmd = new ReadEventDescCommand(eventCode, restore, true);
if (!bridgeHandler.sendCommand(cmd, false)) {
logger.warn("Unable to read event description: {}, {}", eventCode, restore);
return null;
}
return new EventDescriptionCacheEntry(cmd.getText(bridgeHandler.getEncoding()), cmd.getKind());
});
if (mapValue == null) {
String eventText = String.format("event #%d%s", eventCode, restore ? " (restore)" : "");
return new EventDescription(readEventCmd, eventText, 0);
} else {
return new EventDescription(readEventCmd, mapValue.getText(), mapValue.getKind());
}
}
private String getDetails(EventDescription eventDesc) {
boolean upperZone = bridgeHandler.getIntegraType() == IntegraType.I256_PLUS
&& eventDesc.getUserControlNumber() > 0;
return switch (eventDesc.getKind()) {
case 0 -> "";
case 1 -> deviceNameResolver.resolve(DeviceType.PARTITION, eventDesc.getPartition()) + DETAILS_SEPARATOR
+ deviceNameResolver.resolveZoneExpanderKeypad(eventDesc.getSource(), upperZone);
case 2 -> deviceNameResolver.resolve(DeviceType.PARTITION, eventDesc.getPartition()) + DETAILS_SEPARATOR
+ deviceNameResolver.resolveUser(eventDesc.getSource());
case 3 -> deviceNameResolver.resolvePartitionKeypad(eventDesc.getPartitionKeypad()) + DETAILS_SEPARATOR
+ deviceNameResolver.resolveUser(eventDesc.getSource());
case 4 -> deviceNameResolver.resolveZoneExpanderKeypad(eventDesc.getSource(), upperZone);
case 5 -> deviceNameResolver.resolve(DeviceType.PARTITION, eventDesc.getPartition());
case 6 -> deviceNameResolver.resolve(DeviceType.KEYPAD, eventDesc.getPartition()) + DETAILS_SEPARATOR
+ deviceNameResolver.resolveUser(eventDesc.getSource());
case 7 -> deviceNameResolver.resolveUser(eventDesc.getSource());
case 8 -> deviceNameResolver.resolve(DeviceType.EXPANDER, eventDesc.getSource());
case 9 -> deviceNameResolver.resolveTelephone(eventDesc.getSource());
case 11 -> deviceNameResolver.resolve(DeviceType.PARTITION, eventDesc.getPartition()) + DETAILS_SEPARATOR
+ deviceNameResolver.resolveDataBus(eventDesc.getSource());
case 12 -> (eventDesc.getSource() <= bridgeHandler.getIntegraType().getOnMainboard())
? deviceNameResolver.resolveOutputExpander(eventDesc.getSource(), upperZone)
: deviceNameResolver.resolve(DeviceType.PARTITION, eventDesc.getPartition()) + DETAILS_SEPARATOR
+ deviceNameResolver.resolveOutputExpander(eventDesc.getSource(), upperZone);
case 13 -> (eventDesc.getSource() <= 128)
? deviceNameResolver.resolveOutputExpander(eventDesc.getSource(), upperZone)
: deviceNameResolver.resolve(DeviceType.PARTITION, eventDesc.getPartition()) + DETAILS_SEPARATOR
+ deviceNameResolver.resolveOutputExpander(eventDesc.getSource(), upperZone);
case 14 -> deviceNameResolver.resolveTelephone(eventDesc.getPartition()) + DETAILS_SEPARATOR
+ deviceNameResolver.resolveUser(eventDesc.getSource());
case 15 -> deviceNameResolver.resolve(DeviceType.PARTITION, eventDesc.getPartition()) + DETAILS_SEPARATOR
+ deviceNameResolver.resolve(DeviceType.TIMER, eventDesc.getSource());
case 30 ->
deviceNameResolver.resolve(DeviceType.KEYPAD, eventDesc.getPartition()) + DETAILS_SEPARATOR + "ip: "
+ eventDesc.getSource() + "." + (eventDesc.getObject() * 32 + eventDesc.getUserControlNumber());
case 31 ->
"." + eventDesc.getSource() + "." + (eventDesc.getObject() * 32 + eventDesc.getUserControlNumber());
case 32 -> deviceNameResolver.resolve(DeviceType.PARTITION, eventDesc.getPartition()) + DETAILS_SEPARATOR
+ deviceNameResolver.resolve(DeviceType.ZONE, eventDesc.getSource());
default -> {
logger.warn("Unsupported device kind code {} at index {}", eventDesc.getKind(),
eventDesc.getCurrentIndex());
yield String.join(DETAILS_SEPARATOR, "kind=" + eventDesc.getKind(),
"partition=" + eventDesc.getPartition(), "source=" + eventDesc.getSource(),
"object=" + eventDesc.getObject(), "ucn=" + eventDesc.getUserControlNumber());
}
};
}
private Optional<String> readSecondPartOfDetails(EventDescription eventDesc) {
return readEvent(eventDesc.getNextIndex()).filter(EventDescription::isSecondPart).map(descNext -> {
eventDesc.setNextIndex(descNext.getNextIndex());
return getDetails(descNext);
});
}
}

View File

@ -0,0 +1,152 @@
/*
* Copyright (c) 2010-2025 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.internal.handler;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collection;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.satel.internal.action.SatelEventLogActions;
import org.openhab.binding.satel.internal.event.ConnectionStatusEvent;
import org.openhab.binding.satel.internal.util.EventLogReader;
import org.openhab.binding.satel.internal.util.EventLogReader.EventDescription;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.thing.internal.ThingImpl;
/**
* @author Krzysztof Goworek - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
class SatelEventLogHandlerTest {
@Mock
private ThingHandlerCallback callback;
@Mock
private SatelBridgeHandler bridgeHandler;
@Mock
private EventLogReader eventLogReader;
private final Thing thing = new ThingImpl(THING_TYPE_EVENTLOG, "thingId");
@InjectMocks
private final SatelEventLogHandler testSubject = new SatelEventLogHandler(thing);
@Test
void handleCommandShouldNotUpdateStateWhenOtherChannelIsGiven() {
testSubject.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_DESCRIPTION), new DecimalType(0));
verify(eventLogReader, never()).readEvent(0);
verify(callback, never()).stateUpdated(any(), any());
}
@Test
void handleCommandShouldNotUpdateStateWhenOtherCommandIsGiven() {
testSubject.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_INDEX), new StringType(""));
verify(eventLogReader, never()).readEvent(0);
verify(callback, never()).stateUpdated(any(), any());
}
@Test
void handleCommandShouldNotUpdateStateWhenEventLogReaderIsNotPresent() {
testSubject.dispose();
testSubject.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_INDEX), new DecimalType(0));
verify(eventLogReader, never()).readEvent(0);
verify(callback, never()).stateUpdated(any(), any());
}
@Test
void handleCommandShouldNotUpdateStateWhenReadEventFailed() {
testSubject.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_INDEX), new DecimalType(0));
verify(eventLogReader).readEvent(0);
verify(callback, never()).stateUpdated(any(), any());
}
@Test
void handleCommandShouldUpdateStateWhenSendCommandSucceeded() {
LocalDateTime timestamp = LocalDateTime.parse("2020-03-12T12:34:56");
EventDescription eventDescription = mock(EventDescription.class);
when(eventDescription.getCurrentIndex()).thenReturn(1);
when(eventDescription.getNextIndex()).thenReturn(2);
when(eventDescription.getTimestamp()).thenReturn(timestamp);
when(eventDescription.getText()).thenReturn("description");
when(eventLogReader.readEvent(0)).thenReturn(Optional.of(eventDescription));
when(eventLogReader.buildDetails(same(eventDescription))).thenReturn("details");
when(bridgeHandler.getZoneId()).thenReturn(ZoneId.systemDefault());
testSubject.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_INDEX), new DecimalType(0));
verify(callback).stateUpdated(new ChannelUID(thing.getUID(), CHANNEL_INDEX), new DecimalType(1));
verify(callback).stateUpdated(new ChannelUID(thing.getUID(), CHANNEL_PREV_INDEX), new DecimalType(2));
verify(callback).stateUpdated(new ChannelUID(thing.getUID(), CHANNEL_TIMESTAMP),
new DateTimeType(timestamp.atZone(ZoneId.systemDefault())));
verify(callback).stateUpdated(new ChannelUID(thing.getUID(), CHANNEL_DESCRIPTION),
new StringType("description"));
verify(callback).stateUpdated(new ChannelUID(thing.getUID(), CHANNEL_DETAILS), new StringType("details"));
}
@Test
void incomingEventShouldUpdateStatusIfConnected() {
testSubject.incomingEvent(new ConnectionStatusEvent(true));
ArgumentCaptor<ThingStatusInfo> statusCaptor = ArgumentCaptor.forClass(ThingStatusInfo.class);
verify(callback).statusUpdated(eq(thing), statusCaptor.capture());
assertEquals(ThingStatus.ONLINE, statusCaptor.getValue().getStatus());
}
@Test
void incomingEventShouldNotUpdateStatusIfNotConnected() {
testSubject.incomingEvent(new ConnectionStatusEvent(false));
verify(callback, never()).statusUpdated(eq(thing), any());
}
@Test
void getServicesShouldReturnEventLogActions() {
Collection<Class<? extends ThingHandlerService>> result = testSubject.getServices();
assertEquals(1, result.size());
assertTrue(result.contains(SatelEventLogActions.class));
}
@Test
void readEvent() {
}
}

View File

@ -0,0 +1,254 @@
/*
* Copyright (c) 2010-2025 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.internal.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand;
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand.DeviceType;
import org.openhab.binding.satel.internal.event.EventDispatcher;
import org.openhab.binding.satel.internal.handler.SatelBridgeHandler;
import org.openhab.binding.satel.internal.protocol.SatelMessage;
import org.openhab.binding.satel.internal.types.IntegraType;
/**
* @author Krzysztof Goworek - Initial contribution
*/
class DeviceNameResolverTest {
private final SatelBridgeHandler bridgeHandler = mock(SatelBridgeHandler.class);
private final EventDispatcher eventDispatcher = mock(EventDispatcher.class);
private final DeviceNameResolver testSubject = new DeviceNameResolver(bridgeHandler);
@BeforeEach
void setUpBridgeHandler() {
when(bridgeHandler.getEncoding()).thenReturn(StandardCharsets.US_ASCII);
}
@Test
void resolveShouldReadDeviceName() {
setUpResponse("partition name");
String result = testSubject.resolve(DeviceType.PARTITION, 1);
assertEquals("partition: partition name", result);
}
@Test
void resolveShouldCacheDevice() {
setUpResponse("partition name");
testSubject.resolve(DeviceType.PARTITION, 1);
String result = testSubject.resolve(DeviceType.PARTITION, 1);
assertEquals("partition: partition name", result);
verify(bridgeHandler, times(1)).sendCommand(any(), eq(false));
}
@Test
void resolveShouldReturnDeviceNumberWhenNameNotAvailable() {
when(bridgeHandler.sendCommand(any(), eq(false))).thenReturn(false);
String result = testSubject.resolve(DeviceType.PARTITION, 1);
assertEquals("partition: 1", result);
}
@Test
void clearCacheShouldRemoveCachedName() {
setUpResponse("partition name");
testSubject.resolve(DeviceType.PARTITION, 1);
testSubject.clearCache();
testSubject.resolve(DeviceType.PARTITION, 1);
verify(bridgeHandler, times(2)).sendCommand(any(), eq(false));
}
@Test
void resolveOutputExpanderShouldReturnMainboardWhenDeviceNumberIsZero() {
assertEquals("mainboard", testSubject.resolveOutputExpander(0, false));
}
@Test
void resolveOutputExpanderShouldReturnOutputName() {
setUpResponse("output name");
String result = testSubject.resolveOutputExpander(1, false);
assertEquals("output: output name", result);
}
@Test
void resolveOutputExpanderShouldReturnResolveUpperOutputName() {
when(bridgeHandler.sendCommand(any(), eq(false))).thenReturn(false);
String result = testSubject.resolveOutputExpander(1, true);
assertEquals("output: 129", result);
}
@Test
void resolveOutputExpanderShouldReturnExpanderName() {
setUpResponse("expander name");
String result = testSubject.resolveOutputExpander(129, false);
assertEquals("expander: expander name", result);
}
@Test
void resolveOutputExpanderShouldHandleInvalidDeviceNumber() {
String result = testSubject.resolveOutputExpander(193, false);
assertEquals("invalid output|expander device: 193", result);
}
@Test
void resolveZoneExpanderKeypadShouldReturnMainboardWhenDeviceNumberIsZero() {
assertEquals("mainboard", testSubject.resolveZoneExpanderKeypad(0, false));
}
@Test
void resolveZoneExpanderKeypadShouldReturnZoneName() {
setUpResponse("zone name");
String result = testSubject.resolveZoneExpanderKeypad(1, false);
assertEquals("zone: zone name", result);
}
@Test
void resolveZoneExpanderKeypadShouldReturnUpperZoneName() {
when(bridgeHandler.sendCommand(any(), eq(false))).thenReturn(false);
String result = testSubject.resolveZoneExpanderKeypad(1, true);
assertEquals("zone: 129", result);
}
@Test
void resolveZoneExpanderKeypadShouldReturnExpanderName() {
setUpResponse("expander name");
String result = testSubject.resolveZoneExpanderKeypad(129, false);
assertEquals("expander: expander name", result);
}
@Test
void resolveZoneExpanderKeypadShouldReturnKeypadName() {
setUpResponse("keypad name");
String result = testSubject.resolveZoneExpanderKeypad(193, false);
assertEquals("keypad: keypad name", result);
}
@Test
void resolvePartitionKeypadShouldReturnPartitionKeypadNameForSecondBus() {
setUpResponse("keypad name");
String result = testSubject.resolvePartitionKeypad(33);
assertEquals("expander: keypad name", result);
}
@Test
void resolvePartitionKeypadShouldReturnPartitionKeypadNameForWrlLeonBoard() {
when(bridgeHandler.getIntegraType()).thenReturn(IntegraType.I128_LEON);
setUpResponse("keypad name");
String result = testSubject.resolvePartitionKeypad(1);
assertEquals("expander: keypad name", result);
}
@Test
void resolvePartitionKeypadShouldReturnMainboardForWrlLeonBoard() {
when(bridgeHandler.getIntegraType()).thenReturn(IntegraType.I128_LEON);
String result = testSubject.resolvePartitionKeypad(33);
assertEquals("mainboard", result);
}
@Test
void resolvePartitionKeypadShouldReturnMainboardForWrlSim300Board() {
when(bridgeHandler.getIntegraType()).thenReturn(IntegraType.I128_SIM300);
String result = testSubject.resolvePartitionKeypad(33);
assertEquals("mainboard", result);
}
@Test
void resolveUserShouldReturnUserName() {
setUpResponse("user name");
String result = testSubject.resolveUser(1);
assertEquals("user: user name", result);
}
@ParameterizedTest
@CsvSource({ "0,user: unknown", "249,INT-AV", "250,ACCO NET", "251,SMS", "252,timer", "253,function zone",
"254,Quick arm", "255,service" })
void resolveUserShouldReturnStaticNameForSpecificDeviceNumber(int deviceNumber, String expectedResult) {
String result = testSubject.resolveUser(deviceNumber);
assertEquals(expectedResult, result);
}
@Test
void resolveDataBusShouldReturnDataBusName() {
assertEquals("data bus: 1", testSubject.resolveDataBus(1));
}
@Test
void resolveTelephoneShouldReturnTelephoneName() {
setUpResponse("telephone name");
String result = testSubject.resolveTelephone(1);
assertEquals("telephone: telephone name", result);
}
@Test
void resolveTelephoneShouldReturnUnknownTelephoneWhenDeviceNumberIs0() {
String result = testSubject.resolveTelephone(0);
assertEquals("telephone: unknown", result);
}
void setUpResponse(String deviceName) {
when(bridgeHandler.sendCommand(isA(ReadDeviceInfoCommand.class), eq(false))).thenAnswer(invocationOnMock -> {
ReadDeviceInfoCommand cmd = invocationOnMock.getArgument(0);
byte[] payload = new byte[19];
byte[] nameBytes = deviceName.getBytes(StandardCharsets.US_ASCII);
System.arraycopy(nameBytes, 0, payload, 3, nameBytes.length);
cmd.handleResponse(eventDispatcher, new SatelMessage(ReadDeviceInfoCommand.COMMAND_CODE, payload));
return true;
});
}
}

View File

@ -0,0 +1,394 @@
/*
* Copyright (c) 2010-2025 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.internal.util;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand.DeviceType;
import org.openhab.binding.satel.internal.command.ReadEventCommand;
import org.openhab.binding.satel.internal.command.ReadEventDescCommand;
import org.openhab.binding.satel.internal.event.EventDispatcher;
import org.openhab.binding.satel.internal.handler.SatelBridgeHandler;
import org.openhab.binding.satel.internal.protocol.SatelMessage;
import org.openhab.binding.satel.internal.types.IntegraType;
import org.openhab.binding.satel.internal.util.EventLogReader.EventDescription;
/**
* @author Krzysztof Goworek - Initial contribution
*/
class EventLogReaderTest {
private final SatelBridgeHandler bridgeHandler = mock(SatelBridgeHandler.class);
private final DeviceNameResolver deviceNameResolver = mock(DeviceNameResolver.class);
private final EventDispatcher eventDispatcher = mock(EventDispatcher.class);
private final EventLogReader testSubject = new EventLogReader(bridgeHandler, deviceNameResolver);
@BeforeEach
void setUpBridgeHandler() {
when(bridgeHandler.getEncoding()).thenReturn(StandardCharsets.US_ASCII);
}
@Test
void readEventShouldReturnEmptyResultWhenCommandFailed() {
when(bridgeHandler.sendCommand(isA(ReadEventCommand.class), eq(false))).thenReturn(false);
Optional<EventDescription> result = testSubject.readEvent(0);
assertFalse(result.isPresent());
}
@Test
void readEventShouldReturnEmptyResultWhenInvalidIndexIsGiven() {
setUpReadEventResponse(new byte[0]);
Optional<EventDescription> result = testSubject.readEvent(0);
assertFalse(result.isPresent());
}
@Test
void readEventShouldReturnEventCodeWhenReadDescriptionFailed() {
setUpReadEventResponse(new byte[] { 0x30, 0x01, 0x10, 0x00, 0x00, 0x01 });
Optional<EventDescription> result = testSubject.readEvent(0);
assertTrue(result.isPresent());
EventDescription eventDescription = result.get();
assertEquals(0, eventDescription.getKind());
assertEquals("event #1", eventDescription.getText());
}
@Test
void readEventShouldReturnEventCodeWithRestoreFlagWhenReadDescriptionFailed() {
setUpReadEventResponse(new byte[] { 0x30, 0x01, 0x10, 0x00, 0x04, 0x01 });
Optional<EventDescription> result = testSubject.readEvent(0);
assertTrue(result.isPresent());
EventDescription eventDescription = result.get();
assertEquals(0, eventDescription.getKind());
assertEquals("event #1 (restore)", eventDescription.getText());
}
@Test
void readEventShouldReturnEventDescription() {
setUpReadEventResponse(new byte[] { 0x30, 0x01, 0x10, 0x00, 0x04, 0x01 });
setUpReadEventDescResponse(new byte[] { 0x00, 0x00, 0x55, 0x00, 0x00, 'A', 'r', 'm' });
Optional<EventDescription> result = testSubject.readEvent(0);
assertTrue(result.isPresent());
EventDescription eventDescription = result.get();
assertEquals(0x55, eventDescription.getKind());
assertEquals("Arm", eventDescription.getText());
}
@Test
void readEventShouldCacheEventDescription() {
setUpReadEventResponse(new byte[] { 0x30, 0x01, 0x10, 0x00, 0x04, 0x01 });
setUpReadEventDescResponse(new byte[0]);
testSubject.readEvent(0);
testSubject.readEvent(0);
verify(bridgeHandler).sendCommand(isA(ReadEventDescCommand.class), eq(false));
}
@Test
void buildDetailsShouldReturnDetailsForKind0() {
String result = testSubject.buildDetails(createEventDescription(0));
assertEquals("", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind1() {
when(deviceNameResolver.resolve(DeviceType.PARTITION, 40)).thenReturn("partition");
when(deviceNameResolver.resolveZoneExpanderKeypad(70, false)).thenReturn("zone|expander|keypad");
String result = testSubject.buildDetails(createEventDescription(1));
assertEquals("partition, zone|expander|keypad", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind2() {
when(deviceNameResolver.resolve(DeviceType.PARTITION, 40)).thenReturn("partition");
when(deviceNameResolver.resolveUser(70)).thenReturn("user");
String result = testSubject.buildDetails(createEventDescription(2));
assertEquals("partition, user", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind3() {
when(deviceNameResolver.resolvePartitionKeypad(50)).thenReturn("partition keypad");
when(deviceNameResolver.resolveUser(70)).thenReturn("user");
String result = testSubject.buildDetails(createEventDescription(3));
assertEquals("partition keypad, user", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind4() {
when(bridgeHandler.getIntegraType()).thenReturn(IntegraType.I256_PLUS);
when(deviceNameResolver.resolveZoneExpanderKeypad(70, true)).thenReturn("zone|expander|keypad");
String result = testSubject.buildDetails(createEventDescription(4));
assertEquals("zone|expander|keypad", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind5() {
when(deviceNameResolver.resolve(DeviceType.PARTITION, 40)).thenReturn("partition");
String result = testSubject.buildDetails(createEventDescription(5));
assertEquals("partition", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind6() {
when(deviceNameResolver.resolve(DeviceType.KEYPAD, 40)).thenReturn("keypad");
when(deviceNameResolver.resolveUser(70)).thenReturn("user");
String result = testSubject.buildDetails(createEventDescription(6));
assertEquals("keypad, user", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind7() {
when(deviceNameResolver.resolveUser(70)).thenReturn("user");
String result = testSubject.buildDetails(createEventDescription(7));
assertEquals("user", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind8() {
when(deviceNameResolver.resolve(DeviceType.EXPANDER, 70)).thenReturn("expander");
String result = testSubject.buildDetails(createEventDescription(8));
assertEquals("expander", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind9() {
when(deviceNameResolver.resolveTelephone(70)).thenReturn("telephone");
String result = testSubject.buildDetails(createEventDescription(9));
assertEquals("telephone", result);
}
@Test
void buildDetailsShouldReturnDefaultDetailsForKind10() {
String result = testSubject.buildDetails(createEventDescription(10));
assertEquals("kind=10, partition=40, source=70, object=1, ucn=30", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind11() {
when(deviceNameResolver.resolve(DeviceType.PARTITION, 40)).thenReturn("partition");
when(deviceNameResolver.resolveDataBus(70)).thenReturn("data bus");
String result = testSubject.buildDetails(createEventDescription(11));
assertEquals("partition, data bus", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind12() {
when(bridgeHandler.getIntegraType()).thenReturn(IntegraType.I256_PLUS);
when(deviceNameResolver.resolveOutputExpander(5, false)).thenReturn("output|expander");
String result = testSubject.buildDetails(createEventDescription(12, 0, 5));
assertEquals("output|expander", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind12WithPartition() {
when(bridgeHandler.getIntegraType()).thenReturn(IntegraType.I256_PLUS);
when(deviceNameResolver.resolve(DeviceType.PARTITION, 40)).thenReturn("partition");
when(deviceNameResolver.resolveOutputExpander(70, true)).thenReturn("output|expander");
String result = testSubject.buildDetails(createEventDescription(12));
assertEquals("partition, output|expander", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind13() {
when(deviceNameResolver.resolveOutputExpander(70, false)).thenReturn("output|expander");
String result = testSubject.buildDetails(createEventDescription(13));
assertEquals("output|expander", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind13WithPartition() {
when(deviceNameResolver.resolve(DeviceType.PARTITION, 40)).thenReturn("partition");
when(deviceNameResolver.resolveOutputExpander(130, false)).thenReturn("output|expander");
String result = testSubject.buildDetails(createEventDescription(13, 0, 130));
assertEquals("partition, output|expander", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind14() {
when(deviceNameResolver.resolveTelephone(40)).thenReturn("telephone");
when(deviceNameResolver.resolveUser(70)).thenReturn("user");
String result = testSubject.buildDetails(createEventDescription(14));
assertEquals("telephone, user", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind15() {
when(deviceNameResolver.resolve(DeviceType.PARTITION, 40)).thenReturn("partition");
when(deviceNameResolver.resolve(DeviceType.TIMER, 70)).thenReturn("timer");
String result = testSubject.buildDetails(createEventDescription(15));
assertEquals("partition, timer", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind30() {
when(deviceNameResolver.resolve(DeviceType.KEYPAD, 40)).thenReturn("keypad");
String result = testSubject.buildDetails(createEventDescription(30));
assertEquals("keypad, ip: 70.62", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind31() {
String result = testSubject.buildDetails(createEventDescription(31));
assertEquals(".70.62", result);
}
@Test
void buildDetailsShouldReturnDetailsForKind32() {
when(deviceNameResolver.resolve(DeviceType.PARTITION, 40)).thenReturn("partition");
when(deviceNameResolver.resolve(DeviceType.ZONE, 70)).thenReturn("zone");
String result = testSubject.buildDetails(createEventDescription(32));
assertEquals("partition, zone", result);
}
@Test
void buildDetailsShouldReturnDefaultDetailsForKind33() {
String result = testSubject.buildDetails(createEventDescription(33));
assertEquals("kind=33, partition=40, source=70, object=1, ucn=30", result);
}
@Test
void buildDetailsShouldReadEventForKind31() {
setUpReadEventResponse(new byte[] { 0x30, 0x01, 0x10, 0x00, 0x14, 0x01, (byte) 192, (byte) 168 });
setUpReadEventDescResponse(new byte[] { 0x00, 0x00, 30, 0x00, 0x00, 'A', 'r', 'm' });
when(deviceNameResolver.resolve(DeviceType.KEYPAD, 3)).thenReturn("keypad");
EventDescription eventDescription = createEventDescription(31);
String result = testSubject.buildDetails(eventDescription);
assertEquals("keypad, ip: 192.168.70.62", result);
assertEquals(0, eventDescription.getNextIndex());
}
@Test
void buildDetailsShouldSkipNextEventForKind31() {
setUpReadEventResponse(new byte[] { 0x30, 0x01, 0x10, 0x00, 0x14, 0x01, (byte) 192, (byte) 168 });
setUpReadEventDescResponse(new byte[] { 0x00, 0x00, 29, 0x00, 0x00, 'A', 'r', 'm' });
EventDescription eventDescription = createEventDescription(31);
String result = testSubject.buildDetails(eventDescription);
assertEquals(".70.62", result);
assertEquals(20, eventDescription.getNextIndex());
}
@Test
void clearCacheShouldClearDeviceNameCache() {
testSubject.clearCache();
verify(deviceNameResolver).clearCache();
}
private void setUpReadEventResponse(byte[] responseBytes) {
when(bridgeHandler.sendCommand(isA(ReadEventCommand.class), eq(false))).thenAnswer(invocationOnMock -> {
ReadEventCommand cmd = invocationOnMock.getArgument(0);
byte[] payload = new byte[14];
System.arraycopy(responseBytes, 0, payload, 0, responseBytes.length);
cmd.handleResponse(eventDispatcher, new SatelMessage(ReadEventCommand.COMMAND_CODE, payload));
return true;
});
}
private void setUpReadEventDescResponse(byte[] responseBytes) {
when(bridgeHandler.sendCommand(isA(ReadEventDescCommand.class), eq(false))).thenAnswer(invocationOnMock -> {
ReadEventDescCommand cmd = invocationOnMock.getArgument(0);
byte[] payload = new byte[51];
System.arraycopy(responseBytes, 0, payload, 0, responseBytes.length);
cmd.handleResponse(eventDispatcher, new SatelMessage(ReadEventDescCommand.COMMAND_CODE, payload));
return true;
});
}
private EventDescription createEventDescription(int descKind) {
return createEventDescription(descKind, 30, 70);
}
private EventDescription createEventDescription(int descKind, int userControlNumber, int source) {
return testSubject.new EventDescription(mockReadEventCommand(userControlNumber, source), "", descKind);
}
private ReadEventCommand mockReadEventCommand(int userControlNumber, int source) {
ReadEventCommand result = mock(ReadEventCommand.class);
when(result.getCurrentIndex()).thenReturn(10);
when(result.getNextIndex()).thenReturn(20);
when(result.getUserControlNumber()).thenReturn(userControlNumber);
when(result.getPartition()).thenReturn(40);
when(result.getPartitionKeypad()).thenReturn(50);
when(result.getObject()).thenReturn(1);
when(result.getSource()).thenReturn(source);
when(result.getTimestamp()).thenReturn(LocalDateTime.parse("2020-03-12T12:34:56"));
return result;
}
}