added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.powermax-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-powermax" description="Powermax Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-serial</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.powermax/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,68 @@
/**
* 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.powermax.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link PowermaxBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
public class PowermaxBindingConstants {
public static final String BINDING_ID = "powermax";
// List of all Thing Type UIDs
public static final ThingTypeUID BRIDGE_TYPE_SERIAL = new ThingTypeUID(BINDING_ID, "serial");
public static final ThingTypeUID BRIDGE_TYPE_IP = new ThingTypeUID(BINDING_ID, "ip");
public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
public static final ThingTypeUID THING_TYPE_X10 = new ThingTypeUID(BINDING_ID, "x10");
// All supported Bridge types
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(BRIDGE_TYPE_SERIAL, BRIDGE_TYPE_IP).collect(Collectors.toSet()));
// All supported Thing types
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_ZONE, THING_TYPE_X10).collect(Collectors.toSet()));
// List of all Channel ids
public static final String MODE = "mode";
public static final String TROUBLE = "trouble";
public static final String ALERT_IN_MEMORY = "alert_in_memory";
public static final String SYSTEM_STATUS = "system_status";
public static final String READY = "ready";
public static final String WITH_ZONES_BYPASSED = "with_zones_bypassed";
public static final String ALARM_ACTIVE = "alarm_active";
public static final String SYSTEM_ARMED = "system_armed";
public static final String ARM_MODE = "arm_mode";
public static final String TRIPPED = "tripped";
public static final String LAST_TRIP = "last_trip";
public static final String BYPASSED = "bypassed";
public static final String ARMED = "armed";
public static final String LOW_BATTERY = "low_battery";
public static final String PGM_STATUS = "pgm_status";
public static final String X10_STATUS = "x10_status";
public static final String EVENT_LOG = "event_log_%s";
public static final String UPDATE_EVENT_LOGS = "update_event_logs";
public static final String DOWNLOAD_SETUP = "download_setup";
}

View File

@@ -0,0 +1,128 @@
/**
* 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.powermax.internal;
import static org.openhab.binding.powermax.internal.PowermaxBindingConstants.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.powermax.internal.discovery.PowermaxDiscoveryService;
import org.openhab.binding.powermax.internal.handler.PowermaxBridgeHandler;
import org.openhab.binding.powermax.internal.handler.PowermaxThingHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link PowermaxHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.powermax")
public class PowermaxHandlerFactory extends BaseThingHandlerFactory {
private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
private final SerialPortManager serialPortManager;
private final TimeZoneProvider timeZoneProvider;
@Activate
public PowermaxHandlerFactory(final @Reference SerialPortManager serialPortManager,
final @Reference TimeZoneProvider timeZoneProvider) {
this.serialPortManager = serialPortManager;
this.timeZoneProvider = timeZoneProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID) || SUPPORTED_BRIDGE_TYPES_UIDS.contains(thingTypeUID);
}
@Override
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
if (SUPPORTED_BRIDGE_TYPES_UIDS.contains(thingTypeUID)) {
return super.createThing(thingTypeUID, configuration, thingUID, null);
} else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
ThingUID newThingUID;
if (bridgeUID != null && thingUID != null) {
newThingUID = new ThingUID(thingTypeUID, bridgeUID, thingUID.getId());
} else {
newThingUID = thingUID;
}
return super.createThing(thingTypeUID, configuration, newThingUID, bridgeUID);
}
throw new IllegalArgumentException(
"The thing type " + thingTypeUID + " is not supported by the Powermax binding.");
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (SUPPORTED_BRIDGE_TYPES_UIDS.contains(thingTypeUID)) {
PowermaxBridgeHandler handler = new PowermaxBridgeHandler((Bridge) thing, serialPortManager);
registerDiscoveryService(handler);
return handler;
} else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new PowermaxThingHandler(thing, timeZoneProvider);
}
return null;
}
@Override
protected void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof PowermaxBridgeHandler) {
// remove discovery service, if bridge handler is removed
unregisterDiscoveryService((PowermaxBridgeHandler) thingHandler);
}
}
private synchronized void registerDiscoveryService(PowermaxBridgeHandler bridgeHandler) {
PowermaxDiscoveryService discoveryService = new PowermaxDiscoveryService(bridgeHandler);
discoveryService.activate();
discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
private synchronized void unregisterDiscoveryService(PowermaxBridgeHandler bridgeHandler) {
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(bridgeHandler.getThing().getUID());
if (serviceReg != null) {
PowermaxDiscoveryService service = (PowermaxDiscoveryService) bundleContext
.getService(serviceReg.getReference());
serviceReg.unregister();
if (service != null) {
service.deactivate();
}
}
}
}

View File

@@ -0,0 +1,32 @@
/**
* 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.powermax.internal.config;
/**
* The {@link PowermaxIpConfiguration} is responsible for holding
* configuration informations associated to a Powermax IP thing type
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxIpConfiguration {
public String ip;
public Integer tcpPort;
public Integer motionOffDelay;
public Boolean allowArming;
public Boolean allowDisarming;
public String pinCode;
public Boolean forceStandardMode;
public String panelType;
public Boolean autoSyncTime;
}

View File

@@ -0,0 +1,31 @@
/**
* 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.powermax.internal.config;
/**
* The {@link PowermaxSerialConfiguration} is responsible for holding
* configuration informations associated to a Powermax serial thing type
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxSerialConfiguration {
public String serialPort;
public Integer motionOffDelay;
public Boolean allowArming;
public Boolean allowDisarming;
public String pinCode;
public Boolean forceStandardMode;
public String panelType;
public Boolean autoSyncTime;
}

View File

@@ -0,0 +1,26 @@
/**
* 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.powermax.internal.config;
/**
* The {@link PowermaxX10Configuration} is responsible for holding
* configuration informations associated to a Powermax IP thing type
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxX10Configuration {
public static final String DEVICE_NUMBER = "deviceNumber";
public Integer deviceNumber;
}

View File

@@ -0,0 +1,26 @@
/**
* 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.powermax.internal.config;
/**
* The {@link PowermaxZoneConfiguration} is responsible for holding
* configuration informations associated to a Powermax IP thing type
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxZoneConfiguration {
public static final String ZONE_NUMBER = "zoneNumber";
public Integer zoneNumber;
}

View File

@@ -0,0 +1,215 @@
/**
* 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.powermax.internal.connector;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.openhab.binding.powermax.internal.message.PowermaxBaseMessage;
import org.openhab.binding.powermax.internal.message.PowermaxMessageEvent;
import org.openhab.binding.powermax.internal.message.PowermaxMessageEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An abstract class for the communication with the Visonic alarm panel that
* handles stuff common to all communication types
*
* @author Laurent Garnier - Initial contribution
*/
public abstract class PowermaxConnector implements PowermaxConnectorInterface {
private final Logger logger = LoggerFactory.getLogger(PowermaxConnector.class);
private InputStream input;
private OutputStream output;
private boolean connected;
protected String readerThreadName;
private Thread readerThread;
private long waitingForResponse;
private List<PowermaxMessageEventListener> listeners = new ArrayList<>();
public PowermaxConnector(String readerThreadName) {
this.readerThreadName = readerThreadName;
}
@Override
public abstract void open();
@Override
public abstract void close();
/**
* Cleanup everything; to be called when closing the communication
*/
protected void cleanup(boolean closeStreams) {
logger.debug("cleanup(): cleaning up Connection");
if (readerThread != null) {
readerThread.interrupt();
try {
readerThread.join();
} catch (InterruptedException e) {
}
}
if (closeStreams) {
if (output != null) {
try {
output.close();
} catch (IOException e) {
logger.debug("Error while closing the output stream: {}", e.getMessage());
}
}
if (input != null) {
try {
input.close();
} catch (IOException e) {
logger.debug("Error while closing the input stream: {}", e.getMessage());
}
}
}
readerThread = null;
input = null;
output = null;
logger.debug("cleanup(): Connection Cleanup");
}
/**
* Handles an incoming message
*
* @param incomingMessage the received message as a table of bytes
*/
public void handleIncomingMessage(byte[] incomingMessage) {
PowermaxMessageEvent event = new PowermaxMessageEvent(this,
PowermaxBaseMessage.getMessageHandler(incomingMessage));
// send message to event listeners
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).onNewMessageEvent(event);
}
}
@Override
public void sendMessage(byte[] data) {
try {
output.write(data);
output.flush();
} catch (IOException e) {
logger.debug("sendMessage(): Writing error: {}", e.getMessage(), e);
setConnected(false);
}
}
@Override
public int read(byte[] buffer) throws IOException {
return input.read(buffer);
}
@Override
public void addEventListener(PowermaxMessageEventListener listener) {
listeners.add(listener);
}
@Override
public void removeEventListener(PowermaxMessageEventListener listener) {
listeners.remove(listener);
}
/**
* @return the input stream
*/
public InputStream getInput() {
return input;
}
/**
* Set the input stream
*
* @param input the input stream
*/
public void setInput(InputStream input) {
this.input = input;
}
/**
* @return the output stream
*/
public OutputStream getOutput() {
return output;
}
/**
* Set the output stream
*
* @param output the output stream
*/
public void setOutput(OutputStream output) {
this.output = output;
}
/**
* @return true if connected or false if not
*/
@Override
public boolean isConnected() {
return connected;
}
/**
* Set the connection state
*
* @param connected true if connected or false if not
*/
public void setConnected(boolean connected) {
this.connected = connected;
}
/**
* @return the thread that handles the message reading
*/
public Thread getReaderThread() {
return readerThread;
}
/**
* Set the thread that handles the message reading
*
* @param readerThread the thread
*/
public void setReaderThread(Thread readerThread) {
this.readerThread = readerThread;
}
/**
* @return the start time of the time frame to receive a response
*/
public synchronized long getWaitingForResponse() {
return waitingForResponse;
}
/**
* Set the start time of the time frame to receive a response
*
* @param timeLastReceive the time in milliseconds
*/
public synchronized void setWaitingForResponse(long waitingForResponse) {
this.waitingForResponse = waitingForResponse;
}
}

View File

@@ -0,0 +1,70 @@
/**
* 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.powermax.internal.connector;
import java.io.IOException;
import org.openhab.binding.powermax.internal.message.PowermaxMessageEventListener;
/**
* Interface for communication with the Visonic alarm panel
*
* @author Laurent Garnier - Initial contribution
*/
public interface PowermaxConnectorInterface {
/**
* Method for opening a connection to the Visonic alarm panel.
*/
public void open();
/**
* Method for closing a connection to the Visonic alarm panel.
*/
public void close();
/**
* Returns connection status
*
* @return: true if connected or false if not
**/
public boolean isConnected();
/**
* Method for sending a message to the Visonic alarm panel
*
* @param data the message as a table of bytes
**/
public void sendMessage(byte[] data);
/**
* Method for reading data from the Visonic alarm panel
*
* @param buffer the buffer into which the data is read
**/
public int read(byte[] buffer) throws IOException;
/**
* Method for registering an event listener
*
* @param listener the listener to be registered
*/
public void addEventListener(PowermaxMessageEventListener listener);
/**
* Method for removing an event listener
*
* @param listener the listener to be removed
*/
public void removeEventListener(PowermaxMessageEventListener listener);
}

View File

@@ -0,0 +1,155 @@
/**
* 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.powermax.internal.connector;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Arrays;
import org.openhab.binding.powermax.internal.message.PowermaxCommManager;
import org.openhab.binding.powermax.internal.message.PowermaxReceiveType;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that reads messages from the Visonic alarm panel in a dedicated thread
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxReaderThread extends Thread {
private final Logger logger = LoggerFactory.getLogger(PowermaxReaderThread.class);
private static final int READ_BUFFER_SIZE = 20;
private static final int MAX_MSG_SIZE = 0xC0;
private PowermaxConnector connector;
/**
* Constructor
*
* @param in the input stream
* @param connector the object that should handle the received message
* @param threadName the name of the thread
*/
public PowermaxReaderThread(PowermaxConnector connector, String threadName) {
super(threadName);
this.connector = connector;
}
@Override
public void run() {
logger.debug("Data listener started");
byte[] readDataBuffer = new byte[READ_BUFFER_SIZE];
byte[] dataBuffer = new byte[MAX_MSG_SIZE];
int index = 0;
int msgLen = 0;
boolean variableLen = false;
try {
while (!Thread.interrupted()) {
int len = connector.read(readDataBuffer);
if (len > 0) {
for (int i = 0; i < len; i++) {
if (index >= MAX_MSG_SIZE) {
// too many bytes received, try to find new start
if (logger.isDebugEnabled()) {
byte[] logData = Arrays.copyOf(dataBuffer, index);
logger.debug("Truncating message {}", HexUtils.bytesToHex(logData));
}
index = 0;
}
if (index == 0 && readDataBuffer[i] == 0x0D) {
// Preamble
dataBuffer[index++] = readDataBuffer[i];
} else if (index > 0) {
dataBuffer[index++] = readDataBuffer[i];
if (index == 2) {
try {
PowermaxReceiveType msgType = PowermaxReceiveType.fromCode(readDataBuffer[i]);
msgLen = msgType.getLength();
variableLen = ((readDataBuffer[i] & 0x000000FF) > 0x10) && (msgLen == 0);
} catch (IllegalArgumentException arg0) {
msgLen = 0;
variableLen = false;
}
} else if (index == 5 && variableLen) {
msgLen = (readDataBuffer[i] & 0x000000FF) + 7;
} else if ((msgLen == 0 && readDataBuffer[i] == 0x0A) || (index == msgLen)) {
// Postamble
if (readDataBuffer[i] != 0x0A && dataBuffer[index - 1] == 0x43) {
// adjust message length for 0x43
msgLen++;
} else if (checkCRC(dataBuffer, index)) {
// whole message received with a right CRC
byte[] msg = new byte[index];
msg = Arrays.copyOf(dataBuffer, index);
connector.setWaitingForResponse(System.currentTimeMillis());
connector.handleIncomingMessage(msg);
// find new preamble
index = 0;
} else if (msgLen == 0) {
// CRC check failed for a message with an unknown length
logger.debug("Message length is now {} but message is apparently not complete",
index + 1);
} else {
// CRC check failed for a message with a known length
connector.setWaitingForResponse(System.currentTimeMillis());
// find new preamble
index = 0;
}
}
}
}
}
}
} catch (InterruptedIOException e) {
Thread.currentThread().interrupt();
logger.debug("Interrupted via InterruptedIOException");
} catch (IOException e) {
logger.debug("Reading failed: {}", e.getMessage(), e);
}
logger.debug("Data listener stopped");
}
/**
* Check if the CRC inside a received message is valid or not
*
* @param data the buffer containing the message
* @param len the size of the message in the buffer
*
* @return true if the CRC is valid or false if not
*/
private boolean checkCRC(byte[] data, int len) {
byte checksum = PowermaxCommManager.computeCRC(data, len);
byte expected = data[len - 2];
if (checksum != expected) {
byte[] logData = Arrays.copyOf(data, len);
logger.warn("Powermax alarm binding: message CRC check failed (expected {}, got {}, message {})",
String.format("%02X", expected), String.format("%02X", checksum), HexUtils.bytesToHex(logData));
}
return (checksum == expected);
}
}

View File

@@ -0,0 +1,145 @@
/**
* 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.powermax.internal.connector;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.TooManyListenersException;
import org.openhab.core.io.transport.serial.PortInUseException;
import org.openhab.core.io.transport.serial.SerialPort;
import org.openhab.core.io.transport.serial.SerialPortEvent;
import org.openhab.core.io.transport.serial.SerialPortEventListener;
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class for the communication with the Visonic alarm panel through a serial connection
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxSerialConnector extends PowermaxConnector implements SerialPortEventListener {
private final Logger logger = LoggerFactory.getLogger(PowermaxSerialConnector.class);
private final String serialPortName;
private final int baudRate;
private SerialPort serialPort;
private SerialPortManager serialPortManager;
/**
* Constructor
*
* @param serialPortManager the serial port manager
* @param serialPortName the serial port name
* @param baudRate the baud rate to be used
* @param readerThreadName the name of thread to be created
*/
public PowermaxSerialConnector(SerialPortManager serialPortManager, String serialPortName, int baudRate,
String readerThreadName) {
super(readerThreadName);
this.serialPortManager = serialPortManager;
this.serialPortName = serialPortName;
this.baudRate = baudRate;
this.serialPort = null;
}
@Override
public void open() {
logger.debug("open(): Opening Serial Connection");
try {
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
if (portIdentifier != null) {
SerialPort commPort = portIdentifier.open(this.getClass().getName(), 2000);
serialPort = commPort;
serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
serialPort.enableReceiveThreshold(1);
serialPort.enableReceiveTimeout(250);
setInput(serialPort.getInputStream());
setOutput(serialPort.getOutputStream());
getOutput().flush();
if (getInput().markSupported()) {
getInput().reset();
}
// RXTX serial port library causes high CPU load
// Start event listener, which will just sleep and slow down event
// loop
try {
serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);
} catch (TooManyListenersException e) {
logger.debug("Too Many Listeners Exception: {}", e.getMessage(), e);
}
setReaderThread(new PowermaxReaderThread(this, readerThreadName));
getReaderThread().start();
setConnected(true);
} else {
logger.debug("open(): No Such Port: {}", serialPortName);
setConnected(false);
}
} catch (PortInUseException e) {
logger.debug("open(): Port in Use Exception: {}", e.getMessage(), e);
setConnected(false);
} catch (UnsupportedCommOperationException e) {
logger.debug("open(): Unsupported Comm Operation Exception: {}", e.getMessage(), e);
setConnected(false);
} catch (UnsupportedEncodingException e) {
logger.debug("open(): Unsupported Encoding Exception: {}", e.getMessage(), e);
setConnected(false);
} catch (IOException e) {
logger.debug("open(): IO Exception: {}", e.getMessage(), e);
setConnected(false);
}
}
@Override
public void close() {
logger.debug("close(): Closing Serial Connection");
if (serialPort != null) {
serialPort.removeEventListener();
}
super.cleanup(true);
if (serialPort != null) {
serialPort.close();
}
serialPort = null;
setConnected(false);
logger.debug("close(): Serial Connection closed");
}
@Override
public void serialEvent(SerialPortEvent serialPortEvent) {
try {
logger.trace("RXTX library CPU load workaround, sleep forever");
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
}
}
}

View File

@@ -0,0 +1,115 @@
/**
* 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.powermax.internal.connector;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class for the communication with the Visonic alarm panel through a TCP connection
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxTcpConnector extends PowermaxConnector {
private final Logger logger = LoggerFactory.getLogger(PowermaxTcpConnector.class);
private final String ipAddress;
private final int tcpPort;
private final int connectTimeout;
private Socket tcpSocket;
/**
* Constructor.
*
* @param ip the IP address
* @param port the TCP port number
* @param timeout the timeout for socket communications
* @param readerThreadName the name of thread to be created
*/
public PowermaxTcpConnector(String ip, int port, int timeout, String readerThreadName) {
super(readerThreadName);
ipAddress = ip;
tcpPort = port;
connectTimeout = timeout;
}
@Override
public void open() {
logger.debug("open(): Opening TCP Connection");
try {
tcpSocket = new Socket();
tcpSocket.setSoTimeout(250);
SocketAddress socketAddress = new InetSocketAddress(ipAddress, tcpPort);
tcpSocket.connect(socketAddress, connectTimeout);
setInput(tcpSocket.getInputStream());
setOutput(tcpSocket.getOutputStream());
setReaderThread(new PowermaxReaderThread(this, readerThreadName));
getReaderThread().start();
setConnected(true);
} catch (UnknownHostException e) {
logger.debug("open(): Unknown Host Exception: {}", e.getMessage(), e);
setConnected(false);
} catch (SocketException e) {
logger.debug("open(): Socket Exception: {}", e.getMessage(), e);
setConnected(false);
} catch (IOException e) {
logger.debug("open(): IO Exception: {}", e.getMessage(), e);
setConnected(false);
} catch (Exception e) {
logger.debug("open(): Exception: {}", e.getMessage(), e);
setConnected(false);
}
}
@Override
public void close() {
logger.debug("close(): Closing TCP Connection");
super.cleanup(false);
if (tcpSocket != null) {
try {
tcpSocket.close();
} catch (IOException e) {
logger.debug("Error while closing the socket: {}", e.getMessage());
}
}
tcpSocket = null;
setConnected(false);
}
@Override
public int read(byte[] buffer) throws IOException {
try {
return super.read(buffer);
} catch (SocketTimeoutException ignore) {
// ignore this exception, instead return 0 to behave like the serial read
return 0;
}
}
}

View File

@@ -0,0 +1,109 @@
/**
* 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.powermax.internal.console;
import java.util.Arrays;
import java.util.List;
import org.openhab.binding.powermax.internal.handler.PowermaxBridgeHandler;
import org.openhab.core.io.console.Console;
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link PowermaxCommandExtension} is responsible for handling console commands
*
* @author Laurent Garnier - Initial contribution
*/
@Component(service = ConsoleCommandExtension.class)
public class PowermaxCommandExtension extends AbstractConsoleCommandExtension {
private static final String INFO_SETUP = "info_setup";
private static final String DOWNLOAD_SETUP = "download_setup";
private ThingRegistry thingRegistry;
public PowermaxCommandExtension() {
super("powermax", "Interact with the Powermax binding.");
}
@Override
public void execute(String[] args, Console console) {
if (args.length >= 2) {
Thing thing = null;
try {
ThingUID thingUID = new ThingUID(args[0]);
thing = thingRegistry.get(thingUID);
} catch (IllegalArgumentException e) {
thing = null;
}
ThingHandler thingHandler = null;
PowermaxBridgeHandler handler = null;
if (thing != null) {
thingHandler = thing.getHandler();
if (thingHandler instanceof PowermaxBridgeHandler) {
handler = (PowermaxBridgeHandler) thingHandler;
}
}
if (thing == null) {
console.println("Bad thing id '" + args[0] + "'");
printUsage(console);
} else if (thingHandler == null) {
console.println("No handler initialized for the thing id '" + args[0] + "'");
printUsage(console);
} else if (handler == null) {
console.println("'" + args[0] + "' is not a powermax bridge id");
printUsage(console);
} else {
switch (args[1]) {
case INFO_SETUP:
for (String line : handler.getInfoSetup().split("\n")) {
console.println(line);
}
break;
case DOWNLOAD_SETUP:
handler.downloadSetup();
console.println("Command '" + args[1] + "' handled.");
break;
default:
console.println("Unknown Powermax sub command '" + args[1] + "'");
printUsage(console);
break;
}
}
} else {
printUsage(console);
}
}
@Override
public List<String> getUsages() {
return Arrays.asList(new String[] { buildCommandUsage("<bridgeUID> " + INFO_SETUP, "information on setup"),
buildCommandUsage("<bridgeUID> " + DOWNLOAD_SETUP, "download setup") });
}
@Reference
protected void setThingRegistry(ThingRegistry thingRegistry) {
this.thingRegistry = thingRegistry;
}
protected void unsetThingRegistry(ThingRegistry thingRegistry) {
this.thingRegistry = null;
}
}

View File

@@ -0,0 +1,174 @@
/**
* 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.powermax.internal.discovery;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.openhab.binding.powermax.internal.PowermaxBindingConstants;
import org.openhab.binding.powermax.internal.config.PowermaxX10Configuration;
import org.openhab.binding.powermax.internal.config.PowermaxZoneConfiguration;
import org.openhab.binding.powermax.internal.handler.PowermaxBridgeHandler;
import org.openhab.binding.powermax.internal.handler.PowermaxThingHandler;
import org.openhab.binding.powermax.internal.state.PowermaxPanelSettings;
import org.openhab.binding.powermax.internal.state.PowermaxPanelSettingsListener;
import org.openhab.binding.powermax.internal.state.PowermaxX10Settings;
import org.openhab.binding.powermax.internal.state.PowermaxZoneSettings;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PowermaxDiscoveryService} is responsible for discovering
* all enrolled zones and X10 devices
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxDiscoveryService extends AbstractDiscoveryService implements PowermaxPanelSettingsListener {
private final Logger logger = LoggerFactory.getLogger(PowermaxDiscoveryService.class);
private static final int SEARCH_TIME = 5;
private PowermaxBridgeHandler bridgeHandler;
/**
* Creates a PowermaxDiscoveryService with background discovery disabled.
*/
public PowermaxDiscoveryService(PowermaxBridgeHandler bridgeHandler) {
super(PowermaxBindingConstants.SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME, true);
this.bridgeHandler = bridgeHandler;
}
/**
* Activates the Discovery Service.
*/
public void activate() {
bridgeHandler.registerPanelSettingsListener(this);
}
/**
* Deactivates the Discovery Service.
*/
@Override
public void deactivate() {
bridgeHandler.unregisterPanelSettingsListener(this);
}
@Override
protected void startScan() {
logger.debug("Updating discovered things (new scan)");
updateFromSettings(bridgeHandler.getPanelSettings());
}
@Override
public void onPanelSettingsUpdated(PowermaxPanelSettings settings) {
logger.debug("Updating discovered things (global settings updated)");
updateFromSettings(settings);
}
@Override
public void onZoneSettingsUpdated(int zoneNumber, PowermaxPanelSettings settings) {
logger.debug("Updating discovered things (zone {} updated)", zoneNumber);
PowermaxZoneSettings zoneSettings = (settings == null) ? null : settings.getZoneSettings(zoneNumber);
updateFromZoneSettings(zoneNumber, zoneSettings);
}
private void updateFromSettings(PowermaxPanelSettings settings) {
if (settings != null) {
long beforeUpdate = new Date().getTime();
for (int i = 1; i <= settings.getNbZones(); i++) {
PowermaxZoneSettings zoneSettings = settings.getZoneSettings(i);
updateFromZoneSettings(i, zoneSettings);
}
for (int i = 1; i < settings.getNbPGMX10Devices(); i++) {
PowermaxX10Settings deviceSettings = settings.getX10Settings(i);
updateFromDeviceSettings(i, deviceSettings);
}
// Remove not updated discovered things
removeOlderResults(beforeUpdate, bridgeHandler.getThing().getUID());
}
}
private void updateFromZoneSettings(int zoneNumber, PowermaxZoneSettings zoneSettings) {
if (zoneSettings != null) {
// Prevent for adding already known zone
for (Thing thing : bridgeHandler.getThing().getThings()) {
ThingHandler thingHandler = thing.getHandler();
if (thing.getThingTypeUID().equals(PowermaxBindingConstants.THING_TYPE_ZONE)
&& thingHandler instanceof PowermaxThingHandler) {
PowermaxZoneConfiguration config = ((PowermaxThingHandler) thingHandler).getZoneConfiguration();
if (config.zoneNumber == zoneNumber) {
return;
}
}
}
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
ThingUID thingUID = new ThingUID(PowermaxBindingConstants.THING_TYPE_ZONE, bridgeUID,
String.valueOf(zoneNumber));
String sensorType = zoneSettings.getSensorType();
if ("unknown".equalsIgnoreCase(sensorType)) {
sensorType = "Sensor";
}
String name = zoneSettings.getName();
if ("unknown".equalsIgnoreCase(name)) {
name = "Alarm Zone " + zoneNumber;
}
name = sensorType + " " + name;
logger.debug("Adding new Powermax alarm zone {} ({}) to inbox", thingUID, name);
Map<String, Object> properties = new HashMap<>(1);
properties.put(PowermaxZoneConfiguration.ZONE_NUMBER, zoneNumber);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withBridge(bridgeUID).withLabel(name).build();
thingDiscovered(discoveryResult);
}
}
private void updateFromDeviceSettings(int deviceNumber, PowermaxX10Settings deviceSettings) {
if (deviceSettings != null && deviceSettings.isEnabled()) {
// Prevent for adding already known X10 device
for (Thing thing : bridgeHandler.getThing().getThings()) {
ThingHandler thingHandler = thing.getHandler();
if (thing.getThingTypeUID().equals(PowermaxBindingConstants.THING_TYPE_X10)
&& thingHandler instanceof PowermaxThingHandler) {
PowermaxX10Configuration config = ((PowermaxThingHandler) thingHandler).getX10Configuration();
if (config.deviceNumber == deviceNumber) {
return;
}
}
}
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
ThingUID thingUID = new ThingUID(PowermaxBindingConstants.THING_TYPE_X10, bridgeUID,
String.valueOf(deviceNumber));
String name = (deviceSettings.getName() != null) ? deviceSettings.getName()
: ("X10 device " + deviceNumber);
logger.debug("Adding new Powermax X10 device {} ({}) to inbox", thingUID, name);
Map<String, Object> properties = new HashMap<>(1);
properties.put(PowermaxX10Configuration.DEVICE_NUMBER, deviceNumber);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withBridge(bridgeUID).withLabel(name).build();
thingDiscovered(discoveryResult);
}
}
}

View File

@@ -0,0 +1,637 @@
/**
* 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.powermax.internal.handler;
import static org.openhab.binding.powermax.internal.PowermaxBindingConstants.*;
import java.util.EventObject;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.powermax.internal.config.PowermaxIpConfiguration;
import org.openhab.binding.powermax.internal.config.PowermaxSerialConfiguration;
import org.openhab.binding.powermax.internal.message.PowermaxCommManager;
import org.openhab.binding.powermax.internal.state.PowermaxArmMode;
import org.openhab.binding.powermax.internal.state.PowermaxPanelSettings;
import org.openhab.binding.powermax.internal.state.PowermaxPanelSettingsListener;
import org.openhab.binding.powermax.internal.state.PowermaxPanelType;
import org.openhab.binding.powermax.internal.state.PowermaxState;
import org.openhab.binding.powermax.internal.state.PowermaxStateEvent;
import org.openhab.binding.powermax.internal.state.PowermaxStateEventListener;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PowermaxBridgeHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxBridgeHandler extends BaseBridgeHandler implements PowermaxStateEventListener {
private final Logger logger = LoggerFactory.getLogger(PowermaxBridgeHandler.class);
private static final long ONE_MINUTE = TimeUnit.MINUTES.toMillis(1);
/** Default delay in milliseconds to reset a motion detection */
private static final long DEFAULT_MOTION_OFF_DELAY = TimeUnit.MINUTES.toMillis(3);
private static final int NB_EVENT_LOG = 10;
private static final PowermaxPanelType DEFAULT_PANEL_TYPE = PowermaxPanelType.POWERMAX_PRO;
private static final int JOB_REPEAT = 20;
private static final int MAX_DOWNLOAD_ATTEMPTS = 3;
private ScheduledFuture<?> globalJob;
private List<PowermaxPanelSettingsListener> listeners = new CopyOnWriteArrayList<>();
/** The delay in milliseconds to reset a motion detection */
private long motionOffDelay;
/** The PIN code to use for arming/disarming the Powermax alarm system from openHAB */
private String pinCode;
/** Force the standard mode rather than trying using the Powerlink mode */
private boolean forceStandardMode;
/** The object to store the current state of the Powermax alarm system */
private PowermaxState currentState;
/** The object in charge of the communication with the Powermax alarm system */
private PowermaxCommManager commManager;
private int remainingDownloadAttempts;
private SerialPortManager serialPortManager;
public PowermaxBridgeHandler(Bridge thing, SerialPortManager serialPortManager) {
super(thing);
this.serialPortManager = serialPortManager;
}
public PowermaxState getCurrentState() {
return currentState;
}
public PowermaxPanelSettings getPanelSettings() {
return (commManager == null) ? null : commManager.getPanelSettings();
}
@Override
public void initialize() {
logger.debug("initializing handler for thing {}", getThing().getUID());
commManager = null;
String threadName = "OH-binding-" + getThing().getUID().getAsString();
String errorMsg = null;
if (getThing().getThingTypeUID().equals(BRIDGE_TYPE_SERIAL)) {
errorMsg = initializeBridgeSerial(getConfigAs(PowermaxSerialConfiguration.class), threadName);
} else if (getThing().getThingTypeUID().equals(BRIDGE_TYPE_IP)) {
errorMsg = initializeBridgeIp(getConfigAs(PowermaxIpConfiguration.class), threadName);
} else {
errorMsg = "Unexpected thing type " + getThing().getThingTypeUID();
}
if (errorMsg == null) {
if (globalJob == null || globalJob.isCancelled()) {
// Delay the startup in case the handler is restarted immediately
globalJob = scheduler.scheduleWithFixedDelay(() -> {
try {
logger.debug("Powermax job...");
updateMotionSensorState();
if (isConnected()) {
checkKeepAlive();
commManager.retryDownloadSetup(remainingDownloadAttempts);
} else {
tryReconnect();
}
} catch (Exception e) {
logger.warn("Exception in scheduled job: {}", e.getMessage(), e);
}
}, 10, JOB_REPEAT, TimeUnit.SECONDS);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMsg);
}
}
private String initializeBridgeSerial(PowermaxSerialConfiguration config, String threadName) {
String errorMsg = null;
if (config.serialPort != null && !config.serialPort.trim().isEmpty()
&& !config.serialPort.trim().startsWith("rfc2217")) {
motionOffDelay = getMotionOffDelaySetting(config.motionOffDelay, DEFAULT_MOTION_OFF_DELAY);
boolean allowArming = getBooleanSetting(config.allowArming, false);
boolean allowDisarming = getBooleanSetting(config.allowDisarming, false);
pinCode = config.pinCode;
forceStandardMode = getBooleanSetting(config.forceStandardMode, false);
PowermaxPanelType panelType = getPanelTypeSetting(config.panelType, DEFAULT_PANEL_TYPE);
boolean autoSyncTime = getBooleanSetting(config.autoSyncTime, false);
PowermaxArmMode.DISARMED.setAllowedCommand(allowDisarming);
PowermaxArmMode.ARMED_HOME.setAllowedCommand(allowArming);
PowermaxArmMode.ARMED_AWAY.setAllowedCommand(allowArming);
PowermaxArmMode.ARMED_HOME_INSTANT.setAllowedCommand(allowArming);
PowermaxArmMode.ARMED_AWAY_INSTANT.setAllowedCommand(allowArming);
PowermaxArmMode.ARMED_NIGHT.setAllowedCommand(allowArming);
PowermaxArmMode.ARMED_NIGHT_INSTANT.setAllowedCommand(allowArming);
commManager = new PowermaxCommManager(config.serialPort, panelType, forceStandardMode, autoSyncTime,
serialPortManager, threadName);
} else {
if (config.serialPort != null && config.serialPort.trim().startsWith("rfc2217")) {
errorMsg = "Please use the IP Connection thing type for a serial over IP connection.";
} else {
errorMsg = "serialPort setting must be defined in thing configuration";
}
}
return errorMsg;
}
private String initializeBridgeIp(PowermaxIpConfiguration config, String threadName) {
String errorMsg = null;
if (config.ip != null && !config.ip.trim().isEmpty() && config.tcpPort != null) {
motionOffDelay = getMotionOffDelaySetting(config.motionOffDelay, DEFAULT_MOTION_OFF_DELAY);
boolean allowArming = getBooleanSetting(config.allowArming, false);
boolean allowDisarming = getBooleanSetting(config.allowDisarming, false);
pinCode = config.pinCode;
forceStandardMode = getBooleanSetting(config.forceStandardMode, false);
PowermaxPanelType panelType = getPanelTypeSetting(config.panelType, DEFAULT_PANEL_TYPE);
boolean autoSyncTime = getBooleanSetting(config.autoSyncTime, false);
PowermaxArmMode.DISARMED.setAllowedCommand(allowDisarming);
PowermaxArmMode.ARMED_HOME.setAllowedCommand(allowArming);
PowermaxArmMode.ARMED_AWAY.setAllowedCommand(allowArming);
PowermaxArmMode.ARMED_HOME_INSTANT.setAllowedCommand(allowArming);
PowermaxArmMode.ARMED_AWAY_INSTANT.setAllowedCommand(allowArming);
PowermaxArmMode.ARMED_NIGHT.setAllowedCommand(allowArming);
PowermaxArmMode.ARMED_NIGHT_INSTANT.setAllowedCommand(allowArming);
commManager = new PowermaxCommManager(config.ip, config.tcpPort, panelType, forceStandardMode, autoSyncTime,
threadName);
} else {
errorMsg = "ip and port settings must be defined in thing configuration";
}
return errorMsg;
}
@Override
public void dispose() {
logger.debug("Handler disposed for thing {}", getThing().getUID());
if (globalJob != null && !globalJob.isCancelled()) {
globalJob.cancel(true);
globalJob = null;
}
closeConnection();
commManager = null;
super.dispose();
}
/*
* Set the state of items linked to motion sensors to OFF when the last trip is older
* than the value defined by the variable motionOffDelay
*/
private void updateMotionSensorState() {
long now = System.currentTimeMillis();
if (currentState != null) {
boolean update = false;
PowermaxState updateState = commManager.createNewState();
PowermaxPanelSettings panelSettings = getPanelSettings();
for (int i = 1; i <= panelSettings.getNbZones(); i++) {
if (panelSettings.getZoneSettings(i) != null && panelSettings.getZoneSettings(i).isMotionSensor()
&& currentState.isLastTripBeforeTime(i, now - motionOffDelay)) {
update = true;
updateState.setSensorTripped(i, false);
}
}
if (update) {
updateChannelsFromAlarmState(TRIPPED, updateState);
currentState.merge(updateState);
}
}
}
/*
* Check that we receive a keep alive message during the last minute
*/
private void checkKeepAlive() {
long now = System.currentTimeMillis();
if (Boolean.TRUE.equals(currentState.isPowerlinkMode()) && (currentState.getLastKeepAlive() != null)
&& ((now - currentState.getLastKeepAlive()) > ONE_MINUTE)) {
// Let Powermax know we are alive
commManager.sendRestoreMessage();
currentState.setLastKeepAlive(now);
}
}
private void tryReconnect() {
logger.debug("trying to reconnect...");
closeConnection();
currentState = commManager.createNewState();
if (openConnection()) {
updateStatus(ThingStatus.ONLINE);
if (forceStandardMode) {
currentState.setPowerlinkMode(false);
updateChannelsFromAlarmState(MODE, currentState);
processPanelSettings();
} else {
commManager.startDownload();
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Reconnection failed");
}
}
/**
* Open a TCP or Serial connection to the Powermax Alarm Panel
*
* @return true if the connection has been opened
*/
private synchronized boolean openConnection() {
if (commManager != null) {
commManager.addEventListener(this);
commManager.open();
}
remainingDownloadAttempts = MAX_DOWNLOAD_ATTEMPTS;
logger.debug("openConnection(): {}", isConnected() ? "connected" : "disconnected");
return isConnected();
}
/**
* Close TCP or Serial connection to the Powermax Alarm Panel and remove the Event Listener
*/
private synchronized void closeConnection() {
if (commManager != null) {
commManager.close();
commManager.removeEventListener(this);
}
logger.debug("closeConnection(): disconnected");
}
private boolean isConnected() {
return commManager == null ? false : commManager.isConnected();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Received command {} from channel {}", command, channelUID.getId());
if (command instanceof RefreshType) {
updateChannelsFromAlarmState(channelUID.getId(), currentState);
} else {
switch (channelUID.getId()) {
case ARM_MODE:
try {
PowermaxArmMode armMode = PowermaxArmMode.fromShortName(command.toString());
armCommand(armMode);
} catch (IllegalArgumentException e) {
logger.debug("Powermax alarm binding: invalid command {}", command);
}
break;
case SYSTEM_ARMED:
if (command instanceof OnOffType) {
armCommand(
command.equals(OnOffType.ON) ? PowermaxArmMode.ARMED_AWAY : PowermaxArmMode.DISARMED);
} else {
logger.debug("Command of type {} while OnOffType is expected. Command is ignored.",
command.getClass().getSimpleName());
}
break;
case PGM_STATUS:
pgmCommand(command);
break;
case UPDATE_EVENT_LOGS:
downloadEventLog();
break;
case DOWNLOAD_SETUP:
downloadSetup();
break;
default:
logger.debug("No available command for channel {}. Command is ignored.", channelUID.getId());
break;
}
}
}
private void armCommand(PowermaxArmMode armMode) {
if (!isConnected()) {
logger.debug("Powermax alarm binding not connected. Arm command is ignored.");
} else {
commManager.requestArmMode(armMode,
currentState.isPowerlinkMode() ? getPanelSettings().getFirstPinCode() : pinCode);
}
}
private void pgmCommand(Command command) {
if (!isConnected()) {
logger.debug("Powermax alarm binding not connected. PGM command is ignored.");
} else {
commManager.sendPGMX10(command, null);
}
}
public void x10Command(Byte deviceNr, Command command) {
if (!isConnected()) {
logger.debug("Powermax alarm binding not connected. X10 command is ignored.");
} else {
commManager.sendPGMX10(command, deviceNr);
}
}
public void zoneBypassed(byte zoneNr, boolean bypassed) {
if (!isConnected()) {
logger.debug("Powermax alarm binding not connected. Zone bypass command is ignored.");
} else if (!Boolean.TRUE.equals(currentState.isPowerlinkMode())) {
logger.debug("Powermax alarm binding: Bypass option only supported in Powerlink mode");
} else if (!getPanelSettings().isBypassEnabled()) {
logger.debug("Powermax alarm binding: Bypass option not enabled in panel settings");
} else {
commManager.sendZoneBypass(bypassed, zoneNr, getPanelSettings().getFirstPinCode());
}
}
private void downloadEventLog() {
if (!isConnected()) {
logger.debug("Powermax alarm binding not connected. Event logs command is ignored.");
} else {
commManager
.requestEventLog(currentState.isPowerlinkMode() ? getPanelSettings().getFirstPinCode() : pinCode);
}
}
public void downloadSetup() {
if (!isConnected()) {
logger.debug("Powermax alarm binding not connected. Download setup command is ignored.");
} else if (!Boolean.TRUE.equals(currentState.isPowerlinkMode())) {
logger.debug("Powermax alarm binding: download setup only supported in Powerlink mode");
} else if (commManager.isDownloadRunning()) {
logger.debug("Powermax alarm binding: download setup not started as one is in progress");
} else {
commManager.startDownload();
if (currentState.getLastKeepAlive() != null) {
currentState.setLastKeepAlive(System.currentTimeMillis());
}
}
}
public String getInfoSetup() {
return (getPanelSettings() == null) ? "" : getPanelSettings().getInfo();
}
@Override
public void onNewStateEvent(EventObject event) {
PowermaxStateEvent stateEvent = (PowermaxStateEvent) event;
PowermaxState updateState = stateEvent.getState();
if (Boolean.TRUE.equals(currentState.isPowerlinkMode())
&& Boolean.TRUE.equals(updateState.isDownloadSetupRequired())) {
// After Enrolling Powerlink or if a reset is required
logger.debug("Powermax alarm binding: Reset");
commManager.startDownload();
if (currentState.getLastKeepAlive() != null) {
currentState.setLastKeepAlive(System.currentTimeMillis());
}
} else if (Boolean.FALSE.equals(currentState.isPowerlinkMode()) && updateState.getLastKeepAlive() != null) {
// Were are in standard mode but received a keep alive message
// so we switch in PowerLink mode
logger.debug("Powermax alarm binding: Switching to Powerlink mode");
commManager.startDownload();
}
boolean doProcessSettings = (updateState.isPowerlinkMode() != null);
for (int i = 1; i <= getPanelSettings().getNbZones(); i++) {
if (Boolean.TRUE.equals(updateState.isSensorArmed(i))
&& Boolean.TRUE.equals(currentState.isSensorBypassed(i))) {
updateState.setSensorArmed(i, false);
}
}
updateState.keepOnlyDifferencesWith(currentState);
updateChannelsFromAlarmState(updateState);
currentState.merge(updateState);
PowermaxPanelSettings panelSettings = getPanelSettings();
if (!updateState.getUpdatedZoneNames().isEmpty()) {
for (Integer zoneIdx : updateState.getUpdatedZoneNames().keySet()) {
if (panelSettings.getZoneSettings(zoneIdx) != null) {
for (PowermaxPanelSettingsListener listener : listeners) {
listener.onZoneSettingsUpdated(zoneIdx, panelSettings);
}
}
}
}
if (doProcessSettings) {
// There is a change of mode (standard or Powerlink)
processPanelSettings();
commManager.exitDownload();
}
}
private void processPanelSettings() {
if (commManager.processPanelSettings(currentState.isPowerlinkMode())) {
for (PowermaxPanelSettingsListener listener : listeners) {
listener.onPanelSettingsUpdated(getPanelSettings());
}
remainingDownloadAttempts = 0;
} else {
logger.info("Powermax alarm binding: setup download failed!");
for (PowermaxPanelSettingsListener listener : listeners) {
listener.onPanelSettingsUpdated(null);
}
remainingDownloadAttempts--;
}
updatePropertiesFromPanelSettings();
if (currentState.isPowerlinkMode()) {
logger.debug("Powermax alarm binding: running in Powerlink mode");
commManager.sendRestoreMessage();
} else {
logger.debug("Powermax alarm binding: running in Standard mode");
commManager.getInfosWhenInStandardMode();
}
}
/**
* Update channels to match a new alarm system state
*
* @param state: the alarm system state
*/
private void updateChannelsFromAlarmState(PowermaxState state) {
updateChannelsFromAlarmState(null, state);
}
/**
* Update channels to match a new alarm system state
*
* @param channel: filter on a particular channel; if null, consider all channels
* @param state: the alarm system state
*/
private synchronized void updateChannelsFromAlarmState(String channel, PowermaxState state) {
if (state == null) {
return;
}
if (((channel == null) || channel.equals(MODE)) && isLinked(MODE) && (state.getPanelMode() != null)) {
updateState(MODE, new StringType(state.getPanelMode()));
}
if (((channel == null) || channel.equals(SYSTEM_STATUS)) && isLinked(SYSTEM_STATUS)
&& (state.getStatusStr() != null)) {
updateState(SYSTEM_STATUS, new StringType(state.getStatusStr()));
}
if (((channel == null) || channel.equals(READY)) && isLinked(READY) && (state.isReady() != null)) {
updateState(READY, state.isReady() ? OnOffType.ON : OnOffType.OFF);
}
if (((channel == null) || channel.equals(WITH_ZONES_BYPASSED)) && isLinked(WITH_ZONES_BYPASSED)
&& (state.isBypass() != null)) {
updateState(WITH_ZONES_BYPASSED, state.isBypass() ? OnOffType.ON : OnOffType.OFF);
}
if (((channel == null) || channel.equals(ALARM_ACTIVE)) && isLinked(ALARM_ACTIVE)
&& (state.isAlarmActive() != null)) {
updateState(ALARM_ACTIVE, state.isAlarmActive() ? OnOffType.ON : OnOffType.OFF);
}
if (((channel == null) || channel.equals(TROUBLE)) && isLinked(TROUBLE) && (state.isTrouble() != null)) {
updateState(TROUBLE, state.isTrouble() ? OnOffType.ON : OnOffType.OFF);
}
if (((channel == null) || channel.equals(ALERT_IN_MEMORY)) && isLinked(ALERT_IN_MEMORY)
&& (state.isAlertInMemory() != null)) {
updateState(ALERT_IN_MEMORY, state.isAlertInMemory() ? OnOffType.ON : OnOffType.OFF);
}
if (((channel == null) || channel.equals(SYSTEM_ARMED)) && isLinked(SYSTEM_ARMED)
&& (state.isArmed() != null)) {
updateState(SYSTEM_ARMED, state.isArmed() ? OnOffType.ON : OnOffType.OFF);
}
if (((channel == null) || channel.equals(ARM_MODE)) && isLinked(ARM_MODE)
&& (state.getShortArmMode() != null)) {
updateState(ARM_MODE, new StringType(state.getShortArmMode()));
}
if (((channel == null) || channel.equals(PGM_STATUS)) && isLinked(PGM_STATUS)
&& (state.getPGMX10DeviceStatus(0) != null)) {
updateState(PGM_STATUS, state.getPGMX10DeviceStatus(0) ? OnOffType.ON : OnOffType.OFF);
}
for (int i = 1; i <= NB_EVENT_LOG; i++) {
String channel2 = String.format(EVENT_LOG, i);
if (((channel == null) || channel.equals(channel2)) && isLinked(channel2)
&& (state.getEventLog(i) != null)) {
updateState(channel2, new StringType(state.getEventLog(i)));
}
}
for (Thing thing : getThing().getThings()) {
if (thing.getHandler() != null) {
PowermaxThingHandler handler = (PowermaxThingHandler) thing.getHandler();
if (handler != null) {
if (thing.getThingTypeUID().equals(THING_TYPE_ZONE)) {
if ((channel == null) || channel.equals(TRIPPED)) {
handler.updateChannelFromAlarmState(TRIPPED, state);
}
if ((channel == null) || channel.equals(LAST_TRIP)) {
handler.updateChannelFromAlarmState(LAST_TRIP, state);
}
if ((channel == null) || channel.equals(BYPASSED)) {
handler.updateChannelFromAlarmState(BYPASSED, state);
}
if ((channel == null) || channel.equals(ARMED)) {
handler.updateChannelFromAlarmState(ARMED, state);
}
if ((channel == null) || channel.equals(LOW_BATTERY)) {
handler.updateChannelFromAlarmState(LOW_BATTERY, state);
}
} else if (thing.getThingTypeUID().equals(THING_TYPE_X10)) {
if ((channel == null) || channel.equals(X10_STATUS)) {
handler.updateChannelFromAlarmState(X10_STATUS, state);
}
}
}
}
}
}
/**
* Update properties to match the alarm panel settings
*/
private void updatePropertiesFromPanelSettings() {
String value;
Map<String, String> properties = editProperties();
PowermaxPanelSettings panelSettings = getPanelSettings();
value = (panelSettings.getPanelType() != null) ? panelSettings.getPanelType().getLabel() : null;
if (value != null && !value.isEmpty()) {
properties.put(Thing.PROPERTY_MODEL_ID, value);
}
value = panelSettings.getPanelSerial();
if (value != null && !value.isEmpty()) {
properties.put(Thing.PROPERTY_SERIAL_NUMBER, value);
}
value = panelSettings.getPanelEprom();
if (value != null && !value.isEmpty()) {
properties.put(Thing.PROPERTY_HARDWARE_VERSION, value);
}
value = panelSettings.getPanelSoftware();
if (value != null && !value.isEmpty()) {
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, value);
}
updateProperties(properties);
}
public boolean registerPanelSettingsListener(PowermaxPanelSettingsListener listener) {
boolean inList = true;
if (!listeners.contains(listener)) {
inList = listeners.add(listener);
}
return inList;
}
public boolean unregisterPanelSettingsListener(PowermaxPanelSettingsListener listener) {
return listeners.remove(listener);
}
private boolean getBooleanSetting(Boolean value, boolean defaultValue) {
return value != null ? value.booleanValue() : defaultValue;
}
private long getMotionOffDelaySetting(Integer value, long defaultValue) {
return value != null ? value.intValue() * ONE_MINUTE : defaultValue;
}
private PowermaxPanelType getPanelTypeSetting(String value, PowermaxPanelType defaultValue) {
PowermaxPanelType result;
if (value != null) {
try {
result = PowermaxPanelType.fromLabel(value);
} catch (IllegalArgumentException e) {
result = defaultValue;
logger.debug("Powermax alarm binding: panel type not configured correctly");
}
} else {
result = defaultValue;
}
return result;
}
}

View File

@@ -0,0 +1,281 @@
/**
* 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.powermax.internal.handler;
import static org.openhab.binding.powermax.internal.PowermaxBindingConstants.*;
import java.time.Instant;
import java.time.ZonedDateTime;
import org.openhab.binding.powermax.internal.config.PowermaxX10Configuration;
import org.openhab.binding.powermax.internal.config.PowermaxZoneConfiguration;
import org.openhab.binding.powermax.internal.state.PowermaxPanelSettings;
import org.openhab.binding.powermax.internal.state.PowermaxPanelSettingsListener;
import org.openhab.binding.powermax.internal.state.PowermaxState;
import org.openhab.binding.powermax.internal.state.PowermaxX10Settings;
import org.openhab.binding.powermax.internal.state.PowermaxZoneSettings;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PowermaxThingHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxThingHandler extends BaseThingHandler implements PowermaxPanelSettingsListener {
private final Logger logger = LoggerFactory.getLogger(PowermaxThingHandler.class);
private static final int ZONE_NR_MIN = 1;
private static final int ZONE_NR_MAX = 64;
private static final int X10_NR_MIN = 1;
private static final int X10_NR_MAX = 16;
private final TimeZoneProvider timeZoneProvider;
private PowermaxBridgeHandler bridgeHandler;
public PowermaxThingHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
super(thing);
this.timeZoneProvider = timeZoneProvider;
}
@Override
public void initialize() {
logger.debug("Initializing handler for thing {}", getThing().getUID());
Bridge bridge = getBridge();
if (bridge == null) {
initializeThingState(null, null);
} else {
initializeThingState(bridge.getHandler(), bridge.getStatus());
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
logger.debug("Bridge status changed to {} for thing {}", bridgeStatusInfo, getThing().getUID());
Bridge bridge = getBridge();
initializeThingState((bridge == null) ? null : bridge.getHandler(), bridgeStatusInfo.getStatus());
}
private void initializeThingState(ThingHandler bridgeHandler, ThingStatus bridgeStatus) {
if (bridgeHandler != null && bridgeStatus != null) {
if (bridgeStatus == ThingStatus.ONLINE) {
boolean validConfig = false;
String errorMsg = "Unexpected thing type " + getThing().getThingTypeUID();
if (getThing().getThingTypeUID().equals(THING_TYPE_ZONE)) {
PowermaxZoneConfiguration config = getConfigAs(PowermaxZoneConfiguration.class);
if (config.zoneNumber != null && config.zoneNumber >= ZONE_NR_MIN
&& config.zoneNumber <= ZONE_NR_MAX) {
validConfig = true;
} else {
errorMsg = "zoneNumber setting must be defined in thing configuration and set between "
+ ZONE_NR_MIN + " and " + ZONE_NR_MAX;
}
} else if (getThing().getThingTypeUID().equals(THING_TYPE_X10)) {
PowermaxX10Configuration config = getConfigAs(PowermaxX10Configuration.class);
if (config.deviceNumber != null && config.deviceNumber >= X10_NR_MIN
&& config.deviceNumber <= X10_NR_MAX) {
validConfig = true;
} else {
errorMsg = "deviceNumber setting must be defined in thing configuration and set between "
+ X10_NR_MIN + " and " + X10_NR_MAX;
}
}
if (validConfig) {
updateStatus(ThingStatus.UNKNOWN);
logger.debug("Set handler status to UNKNOWN for thing {} (bridge ONLINE)", getThing().getUID());
this.bridgeHandler = (PowermaxBridgeHandler) bridgeHandler;
this.bridgeHandler.registerPanelSettingsListener(this);
onPanelSettingsUpdated(this.bridgeHandler.getPanelSettings());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMsg);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
logger.debug("Set handler status to OFFLINE for thing {} (bridge OFFLINE)", getThing().getUID());
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
logger.debug("Set handler status to OFFLINE for thing {}", getThing().getUID());
}
}
@Override
public void dispose() {
logger.debug("Handler disposed for thing {}", getThing().getUID());
if (bridgeHandler != null) {
bridgeHandler.unregisterPanelSettingsListener(this);
}
super.dispose();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Received command {} from channel {}", command, channelUID.getId());
if (bridgeHandler == null) {
return;
} else if (command instanceof RefreshType) {
updateChannelFromAlarmState(channelUID.getId(), bridgeHandler.getCurrentState());
} else {
switch (channelUID.getId()) {
case BYPASSED:
if (command instanceof OnOffType) {
bridgeHandler.zoneBypassed(getConfigAs(PowermaxZoneConfiguration.class).zoneNumber.byteValue(),
command.equals(OnOffType.ON));
} else {
logger.debug("Command of type {} while OnOffType is expected. Command is ignored.",
command.getClass().getSimpleName());
}
break;
case X10_STATUS:
bridgeHandler.x10Command(getConfigAs(PowermaxX10Configuration.class).deviceNumber.byteValue(),
command);
break;
default:
logger.debug("No available command for channel {}. Command is ignored.", channelUID.getId());
break;
}
}
}
/**
* Update channel to match a new alarm system state
*
* @param channel: the channel
* @param state: the alarm system state
*/
public void updateChannelFromAlarmState(String channel, PowermaxState state) {
if (state == null || channel == null || !isLinked(channel)) {
return;
}
if (getThing().getThingTypeUID().equals(THING_TYPE_ZONE)) {
int num = getConfigAs(PowermaxZoneConfiguration.class).zoneNumber.intValue();
if (channel.equals(TRIPPED) && (state.isSensorTripped(num) != null)) {
updateState(TRIPPED, state.isSensorTripped(num) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
} else if (channel.equals(LAST_TRIP) && (state.getSensorLastTripped(num) != null)) {
ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(state.getSensorLastTripped(num)),
timeZoneProvider.getTimeZone());
updateState(LAST_TRIP, new DateTimeType(zoned));
} else if (channel.equals(BYPASSED) && (state.isSensorBypassed(num) != null)) {
updateState(BYPASSED, state.isSensorBypassed(num) ? OnOffType.ON : OnOffType.OFF);
} else if (channel.equals(ARMED) && (state.isSensorArmed(num) != null)) {
updateState(ARMED, state.isSensorArmed(num) ? OnOffType.ON : OnOffType.OFF);
} else if (channel.equals(LOW_BATTERY) && (state.isSensorLowBattery(num) != null)) {
updateState(LOW_BATTERY, state.isSensorLowBattery(num) ? OnOffType.ON : OnOffType.OFF);
}
} else if (getThing().getThingTypeUID().equals(THING_TYPE_X10)) {
int num = getConfigAs(PowermaxX10Configuration.class).deviceNumber.intValue();
if (channel.equals(X10_STATUS) && (state.getPGMX10DeviceStatus(num) != null)) {
updateState(X10_STATUS, state.getPGMX10DeviceStatus(num) ? OnOffType.ON : OnOffType.OFF);
}
}
}
@Override
public void onPanelSettingsUpdated(PowermaxPanelSettings settings) {
if (getThing().getThingTypeUID().equals(THING_TYPE_ZONE)) {
PowermaxZoneConfiguration config = getConfigAs(PowermaxZoneConfiguration.class);
onZoneSettingsUpdated(config.zoneNumber, settings);
} else if (getThing().getThingTypeUID().equals(THING_TYPE_X10)) {
if (isNotReadyForThingStatusUpdate()) {
return;
}
PowermaxX10Configuration config = getConfigAs(PowermaxX10Configuration.class);
PowermaxX10Settings deviceSettings = (settings == null) ? null
: settings.getX10Settings(config.deviceNumber);
if (settings == null) {
if (getThing().getStatus() != ThingStatus.UNKNOWN) {
updateStatus(ThingStatus.UNKNOWN);
logger.debug("Set handler status to UNKNOWN for thing {}", getThing().getUID());
}
} else if (deviceSettings == null || !deviceSettings.isEnabled()) {
if (getThing().getStatus() != ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Disabled device");
logger.debug("Set handler status to OFFLINE for thing {} (X10 device {} disabled)",
getThing().getUID(), config.deviceNumber);
}
} else if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
logger.debug("Set handler status to ONLINE for thing {} (X10 device {} enabled)", getThing().getUID(),
config.deviceNumber);
}
}
}
@Override
public void onZoneSettingsUpdated(int zoneNumber, PowermaxPanelSettings settings) {
if (getThing().getThingTypeUID().equals(THING_TYPE_ZONE)) {
PowermaxZoneConfiguration config = getConfigAs(PowermaxZoneConfiguration.class);
if (zoneNumber == config.zoneNumber) {
if (isNotReadyForThingStatusUpdate()) {
return;
}
PowermaxZoneSettings zoneSettings = (settings == null) ? null
: settings.getZoneSettings(config.zoneNumber);
if (settings == null) {
if (getThing().getStatus() != ThingStatus.UNKNOWN) {
updateStatus(ThingStatus.UNKNOWN);
logger.debug("Set handler status to UNKNOWN for thing {}", getThing().getUID());
}
} else if (zoneSettings == null) {
if (getThing().getStatus() != ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Zone not paired");
logger.debug("Set handler status to OFFLINE for thing {} (zone number {} not paired)",
getThing().getUID(), config.zoneNumber);
}
} else if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
logger.debug("Set handler status to ONLINE for thing {} (zone number {} paired)",
getThing().getUID(), config.zoneNumber);
}
}
}
}
private boolean isNotReadyForThingStatusUpdate() {
return (getThing().getStatus() == ThingStatus.OFFLINE)
&& ((getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR)
|| (getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE)
|| (getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_UNINITIALIZED));
}
public PowermaxZoneConfiguration getZoneConfiguration() {
return getConfigAs(PowermaxZoneConfiguration.class);
}
public PowermaxX10Configuration getX10Configuration() {
return getConfigAs(PowermaxX10Configuration.class);
}
}

View File

@@ -0,0 +1,52 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxState;
/**
* A class for ACK message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxAckMessage extends PowermaxBaseMessage {
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxAckMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
PowermaxState updatedState = null;
if (commManager.getLastSendMsg().getSendType() == PowermaxSendType.EXIT) {
updatedState = commManager.createNewState();
updatedState.setPowerlinkMode(true);
updatedState.setDownloadMode(false);
}
return updatedState;
}
}

View File

@@ -0,0 +1,78 @@
/**
* 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.powermax.internal.message;
/**
* All defined alarm types
*
* @author Laurent Garnier - Initial contribution
*/
public enum PowermaxAlarmType {
ALARM_TYPE_1(0x01, "Intruder"),
ALARM_TYPE_2(0x02, "Intruder"),
ALARM_TYPE_3(0x03, "Intruder"),
ALARM_TYPE_4(0x04, "Intruder"),
ALARM_TYPE_5(0x05, "Intruder"),
ALARM_TYPE_6(0x06, "Tamper"),
ALARM_TYPE_7(0x07, "Tamper"),
ALARM_TYPE_8(0x08, "Tamper"),
ALARM_TYPE_9(0x09, "Tamper"),
ALARM_TYPE_10(0x0B, "Panic"),
ALARM_TYPE_11(0x0C, "Panic"),
ALARM_TYPE_12(0x20, "Fire"),
ALARM_TYPE_13(0x23, "Emergency"),
ALARM_TYPE_14(0x49, "Gas"),
ALARM_TYPE_15(0x4D, "Flood");
private int code;
private String label;
private PowermaxAlarmType(int code, String label) {
this.code = code;
this.label = label;
}
/**
* @return the code identifying the alarm type
*/
public int getCode() {
return code;
}
/**
* @return the label associated to the alarm type
*/
public String getLabel() {
return label;
}
/**
* Get the ENUM value from its identifying code
*
* @param code the identifying code
*
* @return the corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this code
*/
public static PowermaxAlarmType fromCode(int code) throws IllegalArgumentException {
for (PowermaxAlarmType alarmType : PowermaxAlarmType.values()) {
if (alarmType.getCode() == code) {
return alarmType;
}
}
throw new IllegalArgumentException("Invalid code: " + code);
}
}

View File

@@ -0,0 +1,224 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxState;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A base class for handling a message with the Visonic alarm system
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxBaseMessage {
private final Logger logger = LoggerFactory.getLogger(PowermaxBaseMessage.class);
private byte[] rawData;
private int code;
private PowermaxSendType sendType;
private PowermaxReceiveType receiveType;
/**
* Constructor.
*
* @param message the message as a buffer of bytes
*/
public PowermaxBaseMessage(byte[] message) {
this.sendType = null;
decodeMessage(message);
}
/**
* Constructor.
*
* @param sendType the type of a message to be sent
*/
public PowermaxBaseMessage(PowermaxSendType sendType) {
this(sendType, null);
}
/**
* Constructor.
*
* @param sendType the type of a message to be sent
* @param param the dynamic part of a message to be sent; null if no dynamic part
*/
public PowermaxBaseMessage(PowermaxSendType sendType, byte[] param) {
this.sendType = sendType;
byte[] message = new byte[sendType.getMessage().length + 3];
int index = 0;
message[index++] = 0x0D;
for (int i = 0; i < sendType.getMessage().length; i++) {
if ((param != null) && (sendType.getParamPosition() != null) && (i >= sendType.getParamPosition())
&& (i < (sendType.getParamPosition() + param.length))) {
message[index++] = param[i - sendType.getParamPosition()];
} else {
message[index++] = sendType.getMessage()[i];
}
}
message[index++] = 0x00;
message[index++] = 0x0A;
decodeMessage(message);
}
/**
* Extract information from the buffer of bytes and set class attributes
*
* @param data the message as a buffer of bytes
*/
private void decodeMessage(byte[] data) {
rawData = data;
code = rawData[1] & 0x000000FF;
try {
receiveType = PowermaxReceiveType.fromCode((byte) code);
} catch (IllegalArgumentException e) {
receiveType = null;
}
}
/**
* Work to be done when receiving a message from the Visonic alarm system
*
* @return a new state containing all changes driven by the message
*/
public PowermaxState handleMessage(PowermaxCommManager commManager) {
// Send an ACK if needed
if (isAckRequired() && commManager != null) {
commManager.sendAck(this, (byte) 0x02);
}
if (logger.isDebugEnabled()) {
logger.debug("{}message handled by class {}: {}", (receiveType == null) ? "Unsupported " : "",
this.getClass().getSimpleName(), this);
}
return null;
}
/**
* @return the raw data of the message (buffer of bytes)
*/
public byte[] getRawData() {
return rawData;
}
/**
* @return the identifying code of the message (second byte in the buffer)
*/
public int getCode() {
return code;
}
/**
* @return the type of the message to be sent
*/
public PowermaxSendType getSendType() {
return sendType;
}
public void setSendType(PowermaxSendType sendType) {
this.sendType = sendType;
}
/**
* @return the type of the received message
*/
public PowermaxReceiveType getReceiveType() {
return receiveType;
}
/**
* @return true if the received message requires the sending of an ACK
*/
public boolean isAckRequired() {
return receiveType == null || receiveType.isAckRequired();
}
@Override
public String toString() {
String str = "\n - Raw data = " + HexUtils.bytesToHex(rawData);
str += "\n - type = " + String.format("%02X", code);
if (sendType != null) {
str += " ( " + sendType.toString() + " )";
} else if (receiveType != null) {
str += " ( " + receiveType.toString() + " )";
}
return str;
}
/**
* Instantiate a class for handling a received message The class depends on the message.
*
* @param message the received message as a buffer of bytes
*
* @return a new class instance
*/
public static PowermaxBaseMessage getMessageHandler(byte[] message) {
PowermaxBaseMessage msgHandler;
try {
PowermaxReceiveType msgType = PowermaxReceiveType.fromCode(message[1]);
switch (msgType) {
case ACK:
msgHandler = new PowermaxAckMessage(message);
break;
case TIMEOUT:
msgHandler = new PowermaxTimeoutMessage(message);
break;
case DENIED:
msgHandler = new PowermaxDeniedMessage(message);
break;
case DOWNLOAD_RETRY:
msgHandler = new PowermaxDownloadRetryMessage(message);
break;
case SETTINGS:
case SETTINGS_ITEM:
msgHandler = new PowermaxSettingsMessage(message);
break;
case INFO:
msgHandler = new PowermaxInfoMessage(message);
break;
case EVENT_LOG:
msgHandler = new PowermaxEventLogMessage(message);
break;
case ZONESNAME:
msgHandler = new PowermaxZonesNameMessage(message);
break;
case STATUS:
msgHandler = new PowermaxStatusMessage(message);
break;
case ZONESTYPE:
msgHandler = new PowermaxZonesTypeMessage(message);
break;
case PANEL:
msgHandler = new PowermaxPanelMessage(message);
break;
case POWERLINK:
msgHandler = new PowermaxPowerlinkMessage(message);
break;
case POWERMASTER:
msgHandler = new PowermaxPowerMasterMessage(message);
break;
default:
msgHandler = new PowermaxBaseMessage(message);
break;
}
} catch (IllegalArgumentException e) {
msgHandler = new PowermaxBaseMessage(message);
}
return msgHandler;
}
}

View File

@@ -0,0 +1,699 @@
/**
* 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.powermax.internal.message;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.EventObject;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.powermax.internal.connector.PowermaxConnector;
import org.openhab.binding.powermax.internal.connector.PowermaxSerialConnector;
import org.openhab.binding.powermax.internal.connector.PowermaxTcpConnector;
import org.openhab.binding.powermax.internal.state.PowermaxArmMode;
import org.openhab.binding.powermax.internal.state.PowermaxPanelSettings;
import org.openhab.binding.powermax.internal.state.PowermaxPanelType;
import org.openhab.binding.powermax.internal.state.PowermaxState;
import org.openhab.binding.powermax.internal.state.PowermaxStateEvent;
import org.openhab.binding.powermax.internal.state.PowermaxStateEventListener;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.types.Command;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that manages the communication with the Visonic alarm system
*
* Visonic does not provide a specification of the RS232 protocol and, thus,
* the binding uses the available protocol specification given at the domoticaforum
* http://www.domoticaforum.eu/viewtopic.php?f=68&t=6581
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxCommManager implements PowermaxMessageEventListener {
private final Logger logger = LoggerFactory.getLogger(PowermaxCommManager.class);
private static final int DEFAULT_TCP_PORT = 80;
private static final int TCP_CONNECTION_TIMEOUT = 5000;
private static final int DEFAULT_BAUD_RATE = 9600;
private static final int WAITING_DELAY_FOR_RESPONSE = 750;
private static final long DELAY_BETWEEN_SETUP_DOWNLOADS = TimeUnit.SECONDS.toMillis(45);
private final ScheduledExecutorService scheduler;
/** The object to store the current settings of the Powermax alarm system */
private PowermaxPanelSettings panelSettings;
/** Panel type used when in standard mode */
private PowermaxPanelType panelType;
private boolean forceStandardMode;
private boolean autoSyncTime;
private List<PowermaxStateEventListener> listeners = new ArrayList<>();
/** The serial or TCP connecter used to communicate with the Powermax alarm system */
private PowermaxConnector connector;
/** The last message sent to the the Powermax alarm system */
private PowermaxBaseMessage lastSendMsg;
/** The message queue of messages to be sent to the the Powermax alarm system */
private ConcurrentLinkedQueue<PowermaxBaseMessage> msgQueue = new ConcurrentLinkedQueue<>();
/** The time in milliseconds the last download of the panel setup was requested */
private Long lastTimeDownloadRequested;
/** The boolean indicating if the download of the panel setup is in progress or not */
private boolean downloadRunning;
/** The time in milliseconds used to set time and date */
private Long syncTimeCheck;
/**
* Constructor for Serial Connection
*
* @param sPort the serial port name
* @param panelType the panel type to be used when in standard mode
* @param forceStandardMode true to force the standard mode rather than trying using the Powerlink mode
* @param autoSyncTime true for automatic sync time
* @param serialPortManager the serial port manager
* @param threadName the prefix name of threads to be created
*/
public PowermaxCommManager(String sPort, PowermaxPanelType panelType, boolean forceStandardMode,
boolean autoSyncTime, SerialPortManager serialPortManager, String threadName) {
this.panelType = panelType;
this.forceStandardMode = forceStandardMode;
this.autoSyncTime = autoSyncTime;
this.panelSettings = new PowermaxPanelSettings(panelType);
this.scheduler = ThreadPoolManager.getScheduledPool(threadName + "-sender");
String serialPort = (sPort != null && !sPort.trim().isEmpty()) ? sPort.trim() : null;
if (serialPort != null) {
connector = new PowermaxSerialConnector(serialPortManager, serialPort, DEFAULT_BAUD_RATE,
threadName + "-reader");
} else {
connector = null;
}
}
/**
* Constructor for TCP connection
*
* @param ip the IP address
* @param port TCP port number; default port is used if value <= 0
* @param panelType the panel type to be used when in standard mode
* @param forceStandardMode true to force the standard mode rather than trying using the Powerlink mode
* @param autoSyncTime true for automatic sync time
* @param serialPortManager
* @param threadName the prefix name of threads to be created
*/
public PowermaxCommManager(String ip, int port, PowermaxPanelType panelType, boolean forceStandardMode,
boolean autoSyncTime, String threadName) {
this.panelType = panelType;
this.forceStandardMode = forceStandardMode;
this.autoSyncTime = autoSyncTime;
this.panelSettings = new PowermaxPanelSettings(panelType);
this.scheduler = ThreadPoolManager.getScheduledPool(threadName + "-sender");
String ipAddress = (ip != null && !ip.trim().isEmpty()) ? ip.trim() : null;
int tcpPort = (port > 0) ? port : DEFAULT_TCP_PORT;
if (ipAddress != null) {
connector = new PowermaxTcpConnector(ipAddress, tcpPort, TCP_CONNECTION_TIMEOUT, threadName + "-reader");
} else {
connector = null;
}
}
/**
* Add event listener
*
* @param listener the listener to be added
*/
public synchronized void addEventListener(PowermaxStateEventListener listener) {
listeners.add(listener);
if (connector != null) {
connector.addEventListener(this);
}
}
/**
* Remove event listener
*
* @param listener the listener to be removed
*/
public synchronized void removeEventListener(PowermaxStateEventListener listener) {
if (connector != null) {
connector.removeEventListener(this);
}
listeners.remove(listener);
}
/**
* Connect to the Powermax alarm system
*
* @return true if connected or false if not
*/
public boolean open() {
if (connector != null) {
connector.open();
}
lastSendMsg = null;
msgQueue = new ConcurrentLinkedQueue<>();
return isConnected();
}
/**
* Close the connection to the Powermax alarm system.
*
* @return true if connected or false if not
*/
public boolean close() {
if (connector != null) {
connector.close();
}
lastTimeDownloadRequested = null;
downloadRunning = false;
return isConnected();
}
/**
* @return true if connected to the Powermax alarm system or false if not
*/
public boolean isConnected() {
return (connector != null) && connector.isConnected();
}
/**
* @return the current settings of the Powermax alarm system
*/
public PowermaxPanelSettings getPanelSettings() {
return panelSettings;
}
/**
* Process and store all the panel settings from the raw buffers
*
* @param PowerlinkMode true if in Powerlink mode or false if in standard mode
*
* @return true if no problem encountered to get all the settings; false if not
*/
public boolean processPanelSettings(boolean powerlinkMode) {
return panelSettings.process(powerlinkMode, panelType, powerlinkMode ? syncTimeCheck : null);
}
/**
* @return a new instance of PowermaxState
*/
public PowermaxState createNewState() {
return new PowermaxState(panelSettings);
}
/**
* @return the last message sent to the Powermax alarm system
*/
public synchronized PowermaxBaseMessage getLastSendMsg() {
return lastSendMsg;
}
@Override
public void onNewMessageEvent(EventObject event) {
PowermaxMessageEvent messageEvent = (PowermaxMessageEvent) event;
PowermaxBaseMessage message = messageEvent.getMessage();
if (logger.isDebugEnabled()) {
logger.debug("onNewMessageReceived(): received message {}",
(message.getReceiveType() != null) ? message.getReceiveType()
: String.format("%02X", message.getCode()));
}
if (forceStandardMode && message instanceof PowermaxPowerlinkMessage) {
message = new PowermaxBaseMessage(message.getRawData());
}
PowermaxState updateState = message.handleMessage(this);
if (updateState != null) {
if (updateState.getUpdateSettings() != null) {
panelSettings.updateRawSettings(updateState.getUpdateSettings());
}
if (!updateState.getUpdatedZoneNames().isEmpty()) {
for (Integer zoneIdx : updateState.getUpdatedZoneNames().keySet()) {
panelSettings.updateZoneName(zoneIdx, updateState.getUpdatedZoneNames().get(zoneIdx));
}
}
if (!updateState.getUpdatedZoneInfos().isEmpty()) {
for (Integer zoneIdx : updateState.getUpdatedZoneInfos().keySet()) {
panelSettings.updateZoneInfo(zoneIdx, updateState.getUpdatedZoneInfos().get(zoneIdx));
}
}
PowermaxStateEvent newEvent = new PowermaxStateEvent(this, updateState);
// send message to event listeners
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).onNewStateEvent(newEvent);
}
}
}
/**
* Compute the CRC of a message
*
* @param data the buffer containing the message
* @param len the size of the message in the buffer
*
* @return the computed CRC
*/
public static byte computeCRC(byte[] data, int len) {
long checksum = 0;
for (int i = 1; i < (len - 2); i++) {
checksum = checksum + (data[i] & 0x000000FF);
}
checksum = 0xFF - (checksum % 0xFF);
if (checksum == 0xFF) {
checksum = 0;
}
return (byte) checksum;
}
/**
* Send an ACK for a received message
*
* @param msg the received message object
* @param ackType the type of ACK to be sent
*
* @return true if the ACK was sent or false if not
*/
public synchronized boolean sendAck(PowermaxBaseMessage msg, byte ackType) {
int code = msg.getCode();
byte[] rawData = msg.getRawData();
byte[] ackData;
if ((code >= 0x80) || ((code < 0x10) && (rawData[rawData.length - 3] == 0x43))) {
ackData = new byte[] { 0x0D, ackType, 0x43, 0x00, 0x0A };
} else {
ackData = new byte[] { 0x0D, ackType, 0x00, 0x0A };
}
if (logger.isDebugEnabled()) {
logger.debug("sendAck(): sending message {}", HexUtils.bytesToHex(ackData));
}
boolean done = sendMessage(ackData);
if (!done) {
logger.debug("sendAck(): failed");
}
return done;
}
/**
* Send a message to the Powermax alarm panel to change arm mode
*
* @param armMode the arm mode
* @param pinCode the PIN code. A string of 4 characters is expected
*
* @return true if the message was sent or false if not
*/
public boolean requestArmMode(PowermaxArmMode armMode, String pinCode) {
logger.debug("requestArmMode(): armMode = {}", armMode.getShortName());
boolean done = false;
if (!armMode.isAllowedCommand()) {
logger.debug("Powermax alarm binding: requested arm mode {} rejected", armMode.getShortName());
} else if ((pinCode == null) || (pinCode.length() != 4)) {
logger.debug("Powermax alarm binding: requested arm mode {} rejected due to invalid PIN code",
armMode.getShortName());
} else {
try {
byte[] dynPart = new byte[3];
dynPart[0] = armMode.getCommandCode();
dynPart[1] = (byte) Integer.parseInt(pinCode.substring(0, 2), 16);
dynPart[2] = (byte) Integer.parseInt(pinCode.substring(2, 4), 16);
done = sendMessage(new PowermaxBaseMessage(PowermaxSendType.ARM, dynPart), false, 0, true);
} catch (NumberFormatException e) {
logger.debug("Powermax alarm binding: requested arm mode {} rejected due to invalid PIN code",
armMode.getShortName());
}
}
return done;
}
/**
* Send a message to the Powermax alarm panel to change PGM or X10 zone state
*
* @param action the requested action. Allowed values are: OFF, ON, DIM, BRIGHT
* @param device the X10 device number. null is expected for PGM
*
* @return true if the message was sent or false if not
*/
public boolean sendPGMX10(Command action, Byte device) {
logger.debug("sendPGMX10(): action = {}, device = {}", action, device);
boolean done = false;
Map<String, Byte> codes = new HashMap<>();
codes.put("OFF", (byte) 0x00);
codes.put("ON", (byte) 0x01);
codes.put("DIM", (byte) 0x0A);
codes.put("BRIGHT", (byte) 0x0B);
Byte code = codes.get(action.toString());
if (code == null) {
logger.debug("Powermax alarm binding: invalid PGM/X10 command: {}", action);
} else if ((device != null) && ((device < 1) || (device >= panelSettings.getNbPGMX10Devices()))) {
logger.debug("Powermax alarm binding: invalid X10 device id: {}", device);
} else {
int val = (device == null) ? 1 : (1 << device);
byte[] dynPart = new byte[3];
dynPart[0] = code;
dynPart[1] = (byte) (val & 0x000000FF);
dynPart[2] = (byte) (val >> 8);
done = sendMessage(new PowermaxBaseMessage(PowermaxSendType.X10PGM, dynPart), false, 0);
}
return done;
}
/**
* Send a message to the Powermax alarm panel to bypass a zone or to not bypass a zone
*
* @param bypass true to bypass the zone; false to not bypass the zone
* @param zone the zone number (first zone is number 1)
* @param pinCode the PIN code. A string of 4 characters is expected
*
* @return true if the message was sent or false if not
*/
public boolean sendZoneBypass(boolean bypass, byte zone, String pinCode) {
logger.debug("sendZoneBypass(): bypass = {}, zone = {}", bypass ? "true" : "false", zone);
boolean done = false;
if ((pinCode == null) || (pinCode.length() != 4)) {
logger.debug("Powermax alarm binding: zone bypass rejected due to invalid PIN code");
} else if ((zone < 1) || (zone > panelSettings.getNbZones())) {
logger.debug("Powermax alarm binding: invalid zone number: {}", zone);
} else {
try {
int val = (1 << (zone - 1));
byte[] dynPart = new byte[10];
dynPart[0] = (byte) Integer.parseInt(pinCode.substring(0, 2), 16);
dynPart[1] = (byte) Integer.parseInt(pinCode.substring(2, 4), 16);
int i;
for (i = 2; i < 10; i++) {
dynPart[i] = 0;
}
i = bypass ? 2 : 6;
dynPart[i++] = (byte) (val & 0x000000FF);
dynPart[i++] = (byte) ((val >> 8) & 0x000000FF);
dynPart[i++] = (byte) ((val >> 16) & 0x000000FF);
dynPart[i++] = (byte) ((val >> 24) & 0x000000FF);
done = sendMessage(new PowermaxBaseMessage(PowermaxSendType.BYPASS, dynPart), false, 0, true);
if (done) {
done = sendMessage(new PowermaxBaseMessage(PowermaxSendType.BYPASSTAT), false, 0);
}
} catch (NumberFormatException e) {
logger.debug("Powermax alarm binding: zone bypass rejected due to invalid PIN code");
}
}
return done;
}
/**
* Send a message to set the alarm time and date using the system time and date
*
* @return true if the message was sent or false if not
*/
public boolean sendSetTime() {
logger.debug("sendSetTime()");
boolean done = false;
if (autoSyncTime) {
GregorianCalendar cal = new GregorianCalendar();
if (cal.get(Calendar.YEAR) >= 2000) {
logger.debug("sendSetTime(): sync time {}",
String.format("%02d/%02d/%04d %02d:%02d:%02d", cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.MONTH) + 1, cal.get(Calendar.YEAR), cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND)));
byte[] dynPart = new byte[6];
dynPart[0] = (byte) cal.get(Calendar.SECOND);
dynPart[1] = (byte) cal.get(Calendar.MINUTE);
dynPart[2] = (byte) cal.get(Calendar.HOUR_OF_DAY);
dynPart[3] = (byte) cal.get(Calendar.DAY_OF_MONTH);
dynPart[4] = (byte) (cal.get(Calendar.MONTH) + 1);
dynPart[5] = (byte) (cal.get(Calendar.YEAR) - 2000);
done = sendMessage(new PowermaxBaseMessage(PowermaxSendType.SETTIME, dynPart), false, 0);
cal.set(Calendar.MILLISECOND, 0);
syncTimeCheck = cal.getTimeInMillis();
} else {
logger.info(
"Powermax alarm binding: time not synchronized; please correct the date/time of your openHAB server");
syncTimeCheck = null;
}
} else {
syncTimeCheck = null;
}
return done;
}
/**
* Send a message to the Powermax alarm panel to get all the event logs
*
* @param pinCode the PIN code. A string of 4 characters is expected
*
* @return true if the message was sent or false if not
*/
public boolean requestEventLog(String pinCode) {
logger.debug("requestEventLog()");
boolean done = false;
if ((pinCode == null) || (pinCode.length() != 4)) {
logger.debug("Powermax alarm binding: requested event log rejected due to invalid PIN code");
} else {
try {
byte[] dynPart = new byte[3];
dynPart[0] = (byte) Integer.parseInt(pinCode.substring(0, 2), 16);
dynPart[1] = (byte) Integer.parseInt(pinCode.substring(2, 4), 16);
done = sendMessage(new PowermaxBaseMessage(PowermaxSendType.EVENTLOG, dynPart), false, 0, true);
} catch (NumberFormatException e) {
logger.debug("Powermax alarm binding: requested event log rejected due to invalid PIN code");
}
}
return done;
}
/**
* Start downloading panel setup
*
* @return true if the message was sent or the sending is delayed; false in other cases
*/
public synchronized boolean startDownload() {
if (downloadRunning) {
return false;
} else {
lastTimeDownloadRequested = System.currentTimeMillis();
downloadRunning = true;
return sendMessage(PowermaxSendType.DOWNLOAD);
}
}
/**
* Act the exit of the panel setup
*/
public synchronized void exitDownload() {
downloadRunning = false;
}
public void retryDownloadSetup(int remainingAttempts) {
long now = System.currentTimeMillis();
if ((remainingAttempts > 0) && !isDownloadRunning() && ((lastTimeDownloadRequested == null)
|| ((now - lastTimeDownloadRequested) >= DELAY_BETWEEN_SETUP_DOWNLOADS))) {
// We wait at least 45 seconds before each retry to download the panel setup
logger.debug("Powermax alarm binding: try again downloading setup");
startDownload();
}
}
public void getInfosWhenInStandardMode() {
sendMessage(PowermaxSendType.ZONESNAME);
sendMessage(PowermaxSendType.ZONESTYPE);
sendMessage(PowermaxSendType.STATUS);
}
public void sendRestoreMessage() {
sendMessage(PowermaxSendType.RESTORE);
}
/**
* @return true if a download of the panel setup is in progress
*/
public boolean isDownloadRunning() {
return downloadRunning;
}
/**
* @return the time in milliseconds the last download of the panel setup was requested or null if not yet requested
*/
public Long getLastTimeDownloadRequested() {
return lastTimeDownloadRequested;
}
/**
* Send a ENROLL message
*
* @return true if the message was sent or the sending is delayed; false in other cases
*/
public boolean enrollPowerlink() {
return sendMessage(new PowermaxBaseMessage(PowermaxSendType.ENROLL), true, 0);
}
/**
* Send a message or delay the sending if time frame for receiving response is not ended
*
* @param msgType the message type to be sent
*
* @return true if the message was sent or the sending is delayed; false in other cases
*/
public boolean sendMessage(PowermaxSendType msgType) {
return sendMessage(new PowermaxBaseMessage(msgType), false, 0);
}
/**
* Delay the sending of a message
*
* @param msgType the message type to be sent
* @param waitTime the delay in seconds to wait
*
* @return true if the sending is delayed; false in other cases
*/
public boolean sendMessageLater(PowermaxSendType msgType, int waitTime) {
return sendMessage(new PowermaxBaseMessage(msgType), false, waitTime);
}
private synchronized boolean sendMessage(PowermaxBaseMessage msg, boolean immediate, int waitTime) {
return sendMessage(msg, immediate, waitTime, false);
}
/**
* Send a message or delay the sending if time frame for receiving response is not ended
*
* @param msg the message to be sent
* @param immediate true if the message has to be send without considering timing
* @param waitTime the delay in seconds to wait
* @param doNotLog true if the message contains data that must not be logged
*
* @return true if the message was sent or the sending is delayed; false in other cases
*/
private synchronized boolean sendMessage(PowermaxBaseMessage msg, boolean immediate, int waitTime,
boolean doNotLog) {
if ((waitTime > 0) && (msg != null)) {
logger.debug("sendMessage(): delay ({} s) sending message (type {})", waitTime, msg.getSendType());
// Don't queue the message
PowermaxBaseMessage msgToSendLater = new PowermaxBaseMessage(msg.getRawData());
msgToSendLater.setSendType(msg.getSendType());
scheduler.schedule(() -> {
sendMessage(msgToSendLater, false, 0);
}, waitTime, TimeUnit.SECONDS);
return true;
}
if (msg == null) {
msg = msgQueue.peek();
if (msg == null) {
logger.debug("sendMessage(): nothing to send");
return false;
}
}
// Delay sending if time frame for receiving response is not ended
long delay = WAITING_DELAY_FOR_RESPONSE - (System.currentTimeMillis() - connector.getWaitingForResponse());
PowermaxBaseMessage msgToSend = msg;
if (!immediate) {
msgToSend = msgQueue.peek();
if (msgToSend != msg) {
logger.debug("sendMessage(): add message in queue (type {})", msg.getSendType());
msgQueue.offer(msg);
msgToSend = msgQueue.peek();
}
if ((msgToSend != msg) && (delay > 0)) {
return true;
} else if ((msgToSend == msg) && (delay > 0)) {
if (delay < 100) {
delay = 100;
}
logger.debug("sendMessage(): delay ({} ms) sending message (type {})", delay, msgToSend.getSendType());
scheduler.schedule(() -> {
sendMessage(null, false, 0);
}, delay, TimeUnit.MILLISECONDS);
return true;
} else {
msgToSend = msgQueue.poll();
}
}
if (logger.isDebugEnabled()) {
logger.debug("sendMessage(): sending {} message {}", msgToSend.getSendType(),
doNotLog ? "***" : HexUtils.bytesToHex(msgToSend.getRawData()));
}
boolean done = sendMessage(msgToSend.getRawData());
if (done) {
lastSendMsg = msgToSend;
connector.setWaitingForResponse(System.currentTimeMillis());
if (!immediate && (msgQueue.peek() != null)) {
logger.debug("sendMessage(): delay sending next message (type {})", msgQueue.peek().getSendType());
scheduler.schedule(() -> {
sendMessage(null, false, 0);
}, WAITING_DELAY_FOR_RESPONSE, TimeUnit.MILLISECONDS);
}
} else {
logger.debug("sendMessage(): failed");
}
return done;
}
/**
* Send a message to the Powermax alarm panel
*
* @param data the data buffer containing the message to be sent
*
* @return true if the message was sent or false if not
*/
private boolean sendMessage(byte[] data) {
boolean done = false;
if (isConnected()) {
data[data.length - 2] = computeCRC(data, data.length);
connector.sendMessage(data);
done = connector.isConnected();
} else {
logger.debug("sendMessage(): aborted (not connected)");
}
return done;
}
}

View File

@@ -0,0 +1,60 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class for DENIED message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxDeniedMessage extends PowermaxBaseMessage {
private final Logger logger = LoggerFactory.getLogger(PowermaxDeniedMessage.class);
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxDeniedMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
PowermaxState updatedState = null;
PowermaxSendType lastSendType = commManager.getLastSendMsg().getSendType();
if (lastSendType == PowermaxSendType.EVENTLOG || lastSendType == PowermaxSendType.ARM
|| lastSendType == PowermaxSendType.BYPASS) {
logger.debug("Powermax alarm binding: invalid PIN code");
} else if (lastSendType == PowermaxSendType.DOWNLOAD) {
logger.debug("Powermax alarm binding: openHAB Powerlink not enrolled");
updatedState = commManager.createNewState();
updatedState.setPowerlinkMode(false);
}
return updatedState;
}
}

View File

@@ -0,0 +1,61 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxState;
/**
* A class for DOWNLOAD RETRY message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxDownloadRetryMessage extends PowermaxBaseMessage {
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxDownloadRetryMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
byte[] message = getRawData();
int waitTime = message[4] & 0x000000FF;
commManager.sendMessageLater(PowermaxSendType.DOWNLOAD, waitTime);
return null;
}
@Override
public String toString() {
String str = super.toString();
byte[] message = getRawData();
int waitTime = message[4] & 0x000000FF;
str += "\n - wait time = " + waitTime + " seconds";
return str;
}
}

View File

@@ -0,0 +1,170 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxPanelSettings;
import org.openhab.binding.powermax.internal.state.PowermaxState;
/**
* A class for EVENTLOG message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxEventLogMessage extends PowermaxBaseMessage {
public static final String[] LOG_EVENT_TABLE = new String[] { "None", "Interior Alarm", "Perimeter Alarm",
"Delay Alarm", "24h Silent Alarm", "24h Audible Alarm", "Tamper", "Control Panel Tamper", "Tamper Alarm",
"Tamper Alarm", "Communication Loss", "Panic From Keyfob", "Panic From Control Panel", "Duress",
"Confirm Alarm", "General Trouble", "General Trouble Restore", "Interior Restore", "Perimeter Restore",
"Delay Restore", "24h Silent Restore", "24h Audible Restore", "Tamper Restore",
"Control Panel Tamper Restore", "Tamper Restore", "Tamper Restore", "Communication Restore", "Cancel Alarm",
"General Restore", "Trouble Restore", "Not used", "Recent Close", "Fire", "Fire Restore", "No Active",
"Emergency", "No used", "Disarm Latchkey", "Panic Restore", "Supervision (Inactive)",
"Supervision Restore (Active)", "Low Battery", "Low Battery Restore", "AC Fail", "AC Restore",
"Control Panel Low Battery", "Control Panel Low Battery Restore", "RF Jamming", "RF Jamming Restore",
"Communications Failure", "Communications Restore", "Telephone Line Failure", "Telephone Line Restore",
"Auto Test", "Fuse Failure", "Fuse Restore", "Keyfob Low Battery", "Keyfob Low Battery Restore",
"Engineer Reset", "Battery Disconnect", "1-Way Keypad Low Battery", "1-Way Keypad Low Battery Restore",
"1-Way Keypad Inactive", "1-Way Keypad Restore Active", "Low Battery", "Clean Me", "Fire Trouble",
"Low Battery", "Battery Restore", "AC Fail", "AC Restore", "Supervision (Inactive)",
"Supervision Restore (Active)", "Gas Alert", "Gas Alert Restore", "Gas Trouble", "Gas Trouble Restore",
"Flood Alert", "Flood Alert Restore", "X-10 Trouble", "X-10 Trouble Restore", "Arm Home", "Arm Away",
"Quick Arm Home", "Quick Arm Away", "Disarm", "Fail To Auto-Arm", "Enter To Test Mode",
"Exit From Test Mode", "Force Arm", "Auto Arm", "Instant Arm", "Bypass", "Fail To Arm", "Door Open",
"Communication Established By Control Panel", "System Reset", "Installer Programming", "Wrong Password",
"Not Sys Event", "Not Sys Event", "Extreme Hot Alert", "Extreme Hot Alert Restore", "Freeze Alert",
"Freeze Alert Restore", "Human Cold Alert", "Human Cold Alert Restore", "Human Hot Alert",
"Human Hot Alert Restore", "Temperature Sensor Trouble", "Temperature Sensor Trouble Restore",
// new values partition models
"PIR Mask", "PIR Mask Restore", "", "", "", "", "", "", "", "", "", "", "Alarmed", "Restore", "Alarmed",
"Restore", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Exit Installer", "Enter Installer",
"", "", "", "", "" };
public static final String[] LOG_USER_TABLE = new String[] { "System", "Zone 1", "Zone 2", "Zone 3", "Zone 4",
"Zone 5", "Zone 6", "Zone 7", "Zone 8", "Zone 9", "Zone 10", "Zone 11", "Zone 12", "Zone 13", "Zone 14",
"Zone 15", "Zone 16", "Zone 17", "Zone 18", "Zone 19", "Zone 20", "Zone 21", "Zone 22", "Zone 23",
"Zone 24", "Zone 25", "Zone 26", "Zone 27", "Zone 28", "Zone 29", "Zone 30", "Fob 1", "Fob 2", "Fob 3",
"Fob 4", "Fob 5", "Fob 6", "Fob 7", "Fob 8", "User 1", "User 2", "User 3", "User 4", "User 5", "User 6",
"User 7", "User 8", "Pad 1", "Pad 2", "Pad 3", "Pad 4", "Pad 5", "Pad 6", "Pad 7", "Pad 8", "Siren 1",
"Siren 2", "2Pad 1", "2Pad 2", "2Pad 3", "2Pad 4", "X10 1", "X10 2", "X10 3", "X10 4", "X10 5", "X10 6",
"X10 7", "X10 8", "X10 9", "X10 10", "X10 11", "X10 12", "X10 13", "X10 14", "X10 15", "PGM", "GSM",
"P-LINK", "PTag 1", "PTag 2", "PTag 3", "PTag 4", "PTag 5", "PTag 6", "PTag 7", "PTag 8" };
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxEventLogMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
PowermaxPanelSettings panelSettings = commManager.getPanelSettings();
PowermaxState updatedState = commManager.createNewState();
byte[] message = getRawData();
int eventNum = message[3] & 0x000000FF;
eventNum--;
if (eventNum == 0) {
int eventCnt = message[2] & 0x000000FF;
updatedState.setEventLogSize(eventCnt - 1);
} else {
int second = message[4] & 0x000000FF;
int minute = message[5] & 0x000000FF;
int hour = message[6] & 0x000000FF;
int day = message[7] & 0x000000FF;
int month = message[8] & 0x000000FF;
int year = (message[9] & 0x000000FF) + 2000;
byte eventZone = message[10];
byte logEvent = message[11];
String logEventStr = ((logEvent & 0x000000FF) < LOG_EVENT_TABLE.length)
? LOG_EVENT_TABLE[logEvent & 0x000000FF]
: "UNKNOWN";
String logUserStr = ((eventZone & 0x000000FF) < LOG_USER_TABLE.length)
? LOG_USER_TABLE[eventZone & 0x000000FF]
: "UNKNOWN";
String eventStr;
if (panelSettings.getPanelType().getPartitions() > 1) {
String part;
if ((second & 0x01) == 0x01) {
part = "Part. 1";
} else if ((second & 0x02) == 0x02) {
part = "Part. 2";
} else if ((second & 0x04) == 0x04) {
part = "Part. 3";
} else {
part = "Panel";
}
eventStr = String.format("%02d/%02d/%04d %02d:%02d / %s: %s (%s)", day, month, year, hour, minute, part,
logEventStr, logUserStr);
} else {
eventStr = String.format("%02d/%02d/%04d %02d:%02d:%02d: %s (%s)", day, month, year, hour, minute,
second, logEventStr, logUserStr);
}
updatedState.setEventLogSize(eventNum);
updatedState.setEventLog(eventNum, eventStr);
}
return updatedState;
}
@Override
public String toString() {
String str = super.toString();
byte[] message = getRawData();
int eventNum = message[3] & 0x000000FF;
str += "\n - event number = " + eventNum;
if (eventNum == 1) {
int eventCnt = message[2] & 0x000000FF;
str += "\n - event count = " + eventCnt;
} else {
int second = message[4] & 0x000000FF;
int minute = message[5] & 0x000000FF;
int hour = message[6] & 0x000000FF;
int day = message[7] & 0x000000FF;
int month = message[8] & 0x000000FF;
int year = (message[9] & 0x000000FF) + 2000;
byte eventZone = message[10];
byte logEvent = message[11];
String logEventStr = ((logEvent & 0x000000FF) < LOG_EVENT_TABLE.length)
? LOG_EVENT_TABLE[logEvent & 0x000000FF]
: "UNKNOWN";
String logUserStr = ((eventZone & 0x000000FF) < LOG_USER_TABLE.length)
? LOG_USER_TABLE[eventZone & 0x000000FF]
: "UNKNOWN";
str += "\n - event " + eventNum + " date & time = "
+ String.format("%02d/%02d/%04d %02d:%02d:%02d", day, month, year, hour, minute, second);
str += "\n - event " + eventNum + " zone code = " + String.format("%08X - %s", eventZone, logUserStr);
str += "\n - event " + eventNum + " event code = " + String.format("%08X - %s", logEvent, logEventStr);
}
return str;
}
}

View File

@@ -0,0 +1,85 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxPanelType;
import org.openhab.binding.powermax.internal.state.PowermaxState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class for INFO message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxInfoMessage extends PowermaxBaseMessage {
private final Logger logger = LoggerFactory.getLogger(PowermaxInfoMessage.class);
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxInfoMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
PowermaxState updatedState = commManager.createNewState();
byte[] message = getRawData();
PowermaxPanelType panelType = null;
try {
panelType = PowermaxPanelType.fromCode(message[7]);
} catch (IllegalArgumentException e) {
logger.debug("Powermax alarm binding: unknwon panel type for code {}", message[7] & 0x000000FF);
panelType = null;
}
logger.debug("Reading panel settings");
updatedState.setDownloadMode(true);
commManager.sendMessage(PowermaxSendType.DL_PANELFW);
commManager.sendMessage(PowermaxSendType.DL_SERIAL);
commManager.sendMessage(PowermaxSendType.DL_ZONESTR);
commManager.sendSetTime();
if ((panelType != null) && panelType.isPowerMaster()) {
commManager.sendMessage(PowermaxSendType.DL_MR_SIRKEYZON);
}
commManager.sendMessage(PowermaxSendType.START);
commManager.sendMessage(PowermaxSendType.EXIT);
return updatedState;
}
@Override
public String toString() {
String str = super.toString();
byte[] message = getRawData();
byte panelTypeNr = message[7];
str += "\n - panel type number = " + String.format("%02X", panelTypeNr);
return str;
}
}

View File

@@ -0,0 +1,38 @@
/**
* 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.powermax.internal.message;
import java.util.EventObject;
/**
* Event for messages received from the Visonic alarm panel
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxMessageEvent extends EventObject {
private static final long serialVersionUID = 1L;
private PowermaxBaseMessage message;
public PowermaxMessageEvent(Object source, PowermaxBaseMessage message) {
super(source);
this.message = message;
}
/**
* @return the message object built from the received message
*/
public PowermaxBaseMessage getMessage() {
return message;
}
}

View File

@@ -0,0 +1,31 @@
/**
* 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.powermax.internal.message;
import java.util.EventListener;
import java.util.EventObject;
/**
* Powermax Alarm Event Listener interface. Handles incoming Powermax Alarm message events
*
* @author Laurent Garnier - Initial contribution
*/
public interface PowermaxMessageEventListener extends EventListener {
/**
* Event handler method for incoming Powermax Alarm message events
*
* @param event the event object
*/
public void onNewMessageEvent(EventObject event);
}

View File

@@ -0,0 +1,104 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxState;
/**
* A class for PANEL message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxPanelMessage extends PowermaxBaseMessage {
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxPanelMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
PowermaxState updatedState = commManager.createNewState();
byte[] message = getRawData();
int msgCnt = message[2] & 0x000000FF;
for (int i = 1; i <= msgCnt; i++) {
byte eventZone = message[2 + 2 * i];
byte logEvent = message[3 + 2 * i];
int eventType = logEvent & 0x0000007F;
String logEventStr = (eventType < PowermaxEventLogMessage.LOG_EVENT_TABLE.length)
? PowermaxEventLogMessage.LOG_EVENT_TABLE[eventType]
: "UNKNOWN";
String logUserStr = ((eventZone & 0x000000FF) < PowermaxEventLogMessage.LOG_USER_TABLE.length)
? PowermaxEventLogMessage.LOG_USER_TABLE[eventZone & 0x000000FF]
: "UNKNOWN";
updatedState.setPanelStatus(logEventStr + " (" + logUserStr + ")");
String alarmStatus;
try {
PowermaxAlarmType alarmType = PowermaxAlarmType.fromCode(eventType);
alarmStatus = alarmType.getLabel();
} catch (IllegalArgumentException e) {
alarmStatus = "None";
}
updatedState.setAlarmType(alarmStatus);
String troubleStatus;
try {
PowermaxTroubleType troubleType = PowermaxTroubleType.fromCode(eventType);
troubleStatus = troubleType.getLabel();
} catch (IllegalArgumentException e) {
troubleStatus = "None";
}
updatedState.setTroubleType(troubleStatus);
if (eventType == 0x60) {
// System reset
updatedState.setDownloadSetupRequired(true);
}
}
return updatedState;
}
@Override
public String toString() {
String str = super.toString();
byte[] message = getRawData();
int msgCnt = message[2] & 0x000000FF;
str += "\n - event count = " + msgCnt;
for (int i = 1; i <= msgCnt; i++) {
byte eventZone = message[2 + 2 * i];
byte logEvent = message[3 + 2 * i];
str += "\n - event " + i + " zone code = " + String.format("%08X", eventZone);
str += "\n - event " + i + " event code = " + String.format("%08X", logEvent);
}
return str;
}
}

View File

@@ -0,0 +1,69 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxState;
/**
* A class for PowerMaster message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxPowerMasterMessage extends PowermaxBaseMessage {
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxPowerMasterMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
byte[] message = getRawData();
byte msgType = message[2];
byte subType = message[3];
if ((msgType == 0x03) && (subType == 0x39)) {
commManager.sendMessage(PowermaxSendType.POWERMASTER_ZONE_STAT1);
commManager.sendMessage(PowermaxSendType.POWERMASTER_ZONE_STAT2);
}
return null;
}
@Override
public String toString() {
String str = super.toString();
byte[] message = getRawData();
byte msgType = message[2];
byte subType = message[3];
byte msgLen = message[4];
str += "\n - type = " + String.format("%02X", msgType);
str += "\n - subtype = " + String.format("%02X", subType);
str += "\n - msgLen = " + String.format("%02X", msgLen);
return str;
}
}

View File

@@ -0,0 +1,86 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class for POWERLINK message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxPowerlinkMessage extends PowermaxBaseMessage {
private final Logger logger = LoggerFactory.getLogger(PowermaxPowerlinkMessage.class);
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxPowerlinkMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
PowermaxState updatedState = null;
byte[] message = getRawData();
byte subType = message[2];
if (subType == 0x03) {
// keep alive message
commManager.sendAck(this, (byte) 0x02);
updatedState = commManager.createNewState();
updatedState.setLastKeepAlive(System.currentTimeMillis());
} else if (subType == 0x0A && message[4] == 0x01) {
logger.debug("Powermax alarm binding: Enrolling Powerlink");
commManager.enrollPowerlink();
updatedState = commManager.createNewState();
updatedState.setDownloadSetupRequired(true);
} else {
commManager.sendAck(this, (byte) 0x02);
}
return updatedState;
}
@Override
public String toString() {
String str = super.toString();
byte[] message = getRawData();
byte subType = message[2];
if (subType == 0x03) {
str += "\n - sub type = keep alive";
} else if (subType == 0x0A) {
str += "\n - sub type = enroll";
str += "\n - enroll = " + String.format("%02X", message[4]);
} else {
str += "\n - sub type = " + String.format("%02X", subType);
}
return str;
}
}

View File

@@ -0,0 +1,88 @@
/**
* 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.powermax.internal.message;
/**
* Used to map received messages from the Visonic alarm panel to a ENUM value
*
* @author Laurent Garnier - Initial contribution
*/
public enum PowermaxReceiveType {
ACK((byte) 0x02, 0, false),
TIMEOUT((byte) 0x06, 0, false),
DENIED((byte) 0x08, 0, true),
STOP((byte) 0x0B, 0, true),
DOWNLOAD_RETRY((byte) 0x25, 14, true),
SETTINGS((byte) 0x33, 14, true),
INFO((byte) 0x3C, 14, true),
SETTINGS_ITEM((byte) 0x3F, 0, true),
EVENT_LOG((byte) 0xA0, 15, true),
ZONESNAME((byte) 0xA3, 15, true),
STATUS((byte) 0xA5, 15, true),
ZONESTYPE((byte) 0xA6, 15, true),
PANEL((byte) 0xA7, 15, true),
POWERLINK((byte) 0xAB, 15, false),
POWERMASTER((byte) 0xB0, 0, true),
F1((byte) 0xF1, 9, false);
private byte code;
private int length;
private boolean ackRequired;
private PowermaxReceiveType(byte code, int length, boolean ackRequired) {
this.code = code;
this.length = length;
this.ackRequired = ackRequired;
}
/**
* @return the code identifying the message (second byte in the message)
*/
public byte getCode() {
return code;
}
/**
* @return the message expected length
*/
public int getLength() {
return length;
}
/**
* @return true if the received message requires the sending of an ACK, false if not
*/
public boolean isAckRequired() {
return ackRequired;
}
/**
* Get the ENUM value from its identifying code
*
* @param code the identifying code
*
* @return the corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this code
*/
public static PowermaxReceiveType fromCode(byte code) throws IllegalArgumentException {
for (PowermaxReceiveType messageType : PowermaxReceiveType.values()) {
if (messageType.getCode() == code) {
return messageType;
}
}
throw new IllegalArgumentException("Invalid code: " + code);
}
}

View File

@@ -0,0 +1,140 @@
/**
* 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.powermax.internal.message;
/**
* Used to map messages to be sent to the Visonic alarm panel to a ENUM value
*
* @author Laurent Garnier - Initial contribution
*/
public enum PowermaxSendType {
INIT(new byte[] { (byte) 0xAB, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, null, null),
ZONESNAME(new byte[] { (byte) 0xA3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, null,
PowermaxReceiveType.ZONESNAME),
ZONESTYPE(new byte[] { (byte) 0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, null,
PowermaxReceiveType.ZONESTYPE),
X10NAMES(new byte[] { (byte) 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, null, null),
RESTORE(new byte[] { (byte) 0xAB, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, null,
PowermaxReceiveType.STATUS),
ENROLL(new byte[] { (byte) 0xAB, 0x0A, 0x00, 0x00, 'O', 'H', 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, null, null),
EVENTLOG(new byte[] { (byte) 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, 4,
PowermaxReceiveType.EVENT_LOG),
ARM(new byte[] { (byte) 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, 3, null),
STATUS(new byte[] { (byte) 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, null,
PowermaxReceiveType.STATUS),
BYPASSTAT(new byte[] { (byte) 0xA2, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, null,
PowermaxReceiveType.STATUS),
X10PGM(new byte[] { (byte) 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, 6, null),
BYPASS(new byte[] { (byte) 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 }, 1, null),
DOWNLOAD(new byte[] { 0x24, 0x00, 0x00, 'O', 'H', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.INFO),
SETTIME(new byte[] { 0x46, (byte) 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF }, 3,
null),
DL_TIME(new byte[] { 0x3E, (byte) 0xF8, 0x00, 0x06, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_COMMDEF(new byte[] { 0x3E, 0x01, 0x01, 0x1E, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_PHONENRS(new byte[] { 0x3E, 0x36, 0x01, 0x20, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_PINCODES(new byte[] { 0x3E, (byte) 0xFA, 0x01, 0x10, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_PGMX10(new byte[] { 0x3E, 0x14, 0x02, (byte) 0xD5, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_PARTITIONS(new byte[] { 0x3E, 0x00, 0x03, (byte) 0xF0, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_PANELFW(new byte[] { 0x3E, 0x00, 0x04, 0x20, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_SERIAL(new byte[] { 0x3E, 0x30, 0x04, 0x08, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_ZONES(new byte[] { 0x3E, 0x00, 0x09, 0x78, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_KEYFOBS(new byte[] { 0x3E, 0x78, 0x09, 0x40, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_2WKEYPAD(new byte[] { 0x3E, 0x00, 0x0A, 0x08, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_1WKEYPAD(new byte[] { 0x3E, 0x20, 0x0A, 0x40, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_SIRENS(new byte[] { 0x3E, 0x60, 0x0A, 0x08, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_X10NAMES(new byte[] { 0x3E, 0x30, 0x0B, 0x10, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_ZONENAMES(new byte[] { 0x3E, 0x40, 0x0B, 0x1E, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_EVENTLOG(new byte[] { 0x3E, (byte) 0xDF, 0x04, 0x28, 0x03, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_ZONESTR(new byte[] { 0x3E, 0x00, 0x19, 0x00, 0x02, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_ZONECUSTOM(new byte[] { 0x3E, (byte) 0xA0, 0x1A, 0x50, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_MR_ZONENAMES(new byte[] { 0x3E, 0x60, 0x09, 0x40, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_MR_PINCODES(new byte[] { 0x3E, (byte) 0x98, 0x0A, 0x60, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_MR_SIRENS(new byte[] { 0x3E, (byte) 0xE2, (byte) 0xB6, 0x50, 0x00, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 },
null, PowermaxReceiveType.SETTINGS_ITEM),
DL_MR_KEYPADS(new byte[] { 0x3E, 0x32, (byte) 0xB7, 0x40, 0x01, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_MR_ZONES(new byte[] { 0x3E, 0x72, (byte) 0xB8, (byte) 0x80, 0x02, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 },
null, PowermaxReceiveType.SETTINGS_ITEM),
DL_MR_SIRKEYZON(
new byte[] { 0x3E, (byte) 0xE2, (byte) 0xB6, 0x10, 0x04, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
DL_ALL(new byte[] { 0x3E, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS_ITEM),
SER_TYPE(new byte[] { 0x5A, 0x30, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, null,
PowermaxReceiveType.SETTINGS),
START(new byte[] { 0x0A }, null, PowermaxReceiveType.SETTINGS),
EXIT(new byte[] { 0x0F }, null, null),
POWERMASTER_ZONE_STAT1(
new byte[] { (byte) 0xB0, 0x01, 0x04, 0x06, 0x02, (byte) 0xFF, 0x08, 0x03, 0x00, 0x00, 0x43 }, null,
PowermaxReceiveType.POWERMASTER),
POWERMASTER_ZONE_STAT2(
new byte[] { (byte) 0xB0, 0x01, 0x07, 0x06, 0x02, (byte) 0xFF, 0x08, 0x03, 0x00, 0x00, 0x43 }, null,
PowermaxReceiveType.POWERMASTER),
POWERMASTER_ZONE_NAME(new byte[] { (byte) 0xB0, 0x01, 0x21, 0x02, 0x05, 0x00, 0x43 }, null,
PowermaxReceiveType.POWERMASTER),
POWERMASTER_ZONE_TYPE1(new byte[] { (byte) 0xB0, 0x01, 0x2D, 0x02, 0x05, 0x00, 0x43 }, null,
PowermaxReceiveType.POWERMASTER);
private byte[] message;
private Integer paramPosition;
private PowermaxReceiveType expectedResponseType;
private PowermaxSendType(byte[] message, Integer paramPosition, PowermaxReceiveType expectedResponseType) {
this.message = message;
this.paramPosition = paramPosition;
this.expectedResponseType = expectedResponseType;
}
/**
* @return the message buffer, preamble and postamble not included
*/
public byte[] getMessage() {
return message;
}
/**
* @return the position of the parameter in the message buffer
*/
public Integer getParamPosition() {
return paramPosition;
}
/**
* @return the ENUM value of the expected message as response
*/
public PowermaxReceiveType getExpectedResponseType() {
return expectedResponseType;
}
}

View File

@@ -0,0 +1,91 @@
/**
* 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.powermax.internal.message;
import java.util.Arrays;
import org.openhab.binding.powermax.internal.state.PowermaxState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class for SETTINGS and SETTINGS_ITEM messages handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxSettingsMessage extends PowermaxBaseMessage {
private final Logger logger = LoggerFactory.getLogger(PowermaxSettingsMessage.class);
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxSettingsMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
PowermaxState updatedState = commManager.createNewState();
byte[] message = getRawData();
int index = message[2] & 0x000000FF;
int page = message[3] & 0x000000FF;
int length = 0;
if (getReceiveType() == PowermaxReceiveType.SETTINGS) {
length = message.length - 6;
updatedState.setUpdateSettings(Arrays.copyOfRange(message, 2, 2 + 2 + length));
} else if (getReceiveType() == PowermaxReceiveType.SETTINGS_ITEM) {
length = message[4] & 0x000000FF;
byte[] data = new byte[length + 2];
int i = 0;
for (int j = 2; j <= 3; j++) {
data[i++] = message[j];
}
for (int j = 0; j < length; j++) {
data[i++] = message[j + 5];
}
updatedState.setUpdateSettings(data);
}
if (logger.isDebugEnabled()) {
logger.debug("Received Powermax setting page {} index {} length {}", String.format("%02X (%d)", page, page),
String.format("%02X (%d)", index, index), String.format("%02X (%d)", length, length));
}
return updatedState;
}
@Override
public String toString() {
String str = super.toString();
byte[] message = getRawData();
int index = message[2] & 0x000000FF;
int page = message[3] & 0x000000FF;
str += "\n - page = " + String.format("%02X (%d)", page, page);
str += "\n - index = " + String.format("%02X (%d)", index, index);
return str;
}
}

View File

@@ -0,0 +1,226 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxArmMode;
import org.openhab.binding.powermax.internal.state.PowermaxPanelSettings;
import org.openhab.binding.powermax.internal.state.PowermaxState;
import org.openhab.binding.powermax.internal.state.PowermaxZoneSettings;
/**
* A class for STATUS message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxStatusMessage extends PowermaxBaseMessage {
private static final String[] EVENT_TYPE_TABLE = new String[] { "None", "Tamper Alarm", "Tamper Restore", "Open",
"Closed", "Violated (Motion)", "Panic Alarm", "RF Jamming", "Tamper Open", "Communication Failure",
"Line Failure", "Fuse", "Not Active", "Low Battery", "AC Failure", "Fire Alarm", "Emergency",
"Siren Tamper", "Siren Tamper Restore", "Siren Low Battery", "Siren AC Fail" };
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxStatusMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
PowermaxPanelSettings panelSettings = commManager.getPanelSettings();
PowermaxState updatedState = commManager.createNewState();
byte[] message = getRawData();
byte eventType = message[3];
if (eventType == 0x02) {
int zoneStatus = (message[4] & 0x000000FF) | ((message[5] << 8) & 0x0000FF00)
| ((message[6] << 16) & 0x00FF0000) | ((message[7] << 24) & 0xFF000000);
int batteryStatus = (message[8] & 0x000000FF) | ((message[9] << 8) & 0x0000FF00)
| ((message[10] << 16) & 0x00FF0000) | ((message[11] << 24) & 0xFF000000);
for (int i = 1; i <= panelSettings.getNbZones(); i++) {
updatedState.setSensorTripped(i, ((zoneStatus >> (i - 1)) & 0x1) > 0);
updatedState.setSensorLowBattery(i, ((batteryStatus >> (i - 1)) & 0x1) > 0);
}
} else if (eventType == 0x04) {
byte sysStatus = message[4];
byte sysFlags = message[5];
byte eventZone = message[6];
byte zoneEType = message[7];
int x10Status = (message[10] & 0x000000FF) | ((message[11] << 8) & 0x0000FF00);
String zoneETypeStr = ((zoneEType & 0x000000FF) < EVENT_TYPE_TABLE.length)
? EVENT_TYPE_TABLE[zoneEType & 0x000000FF]
: "UNKNOWN";
if (zoneEType == 0x03) {
updatedState.setSensorTripped(eventZone, Boolean.TRUE);
updatedState.setSensorLastTripped(eventZone, System.currentTimeMillis());
} else if (zoneEType == 0x04) {
updatedState.setSensorTripped(eventZone, Boolean.FALSE);
} else if (zoneEType == 0x05) {
PowermaxZoneSettings zone = panelSettings.getZoneSettings(eventZone);
if ((zone != null) && zone.getSensorType().equalsIgnoreCase("unknown")) {
zone.setSensorType("Motion");
}
updatedState.setSensorTripped(eventZone, Boolean.TRUE);
updatedState.setSensorLastTripped(eventZone, System.currentTimeMillis());
}
// PGM & X10 devices
for (int i = 0; i < panelSettings.getNbPGMX10Devices(); i++) {
updatedState.setPGMX10DeviceStatus(i, ((x10Status >> i) & 0x1) > 0);
}
String sysStatusStr = "";
if ((sysFlags & 0x1) == 1) {
sysStatusStr = sysStatusStr + "Ready, ";
updatedState.setReady(true);
} else {
sysStatusStr = sysStatusStr + "Not ready, ";
updatedState.setReady(false);
}
if (((sysFlags >> 1) & 0x1) == 1) {
sysStatusStr = sysStatusStr + "Alert in memory, ";
updatedState.setAlertInMemory(true);
} else {
updatedState.setAlertInMemory(false);
}
if (((sysFlags >> 2) & 0x1) == 1) {
sysStatusStr = sysStatusStr + "Trouble, ";
updatedState.setTrouble(true);
} else {
updatedState.setTrouble(false);
}
if (((sysFlags >> 3) & 0x1) == 1) {
sysStatusStr = sysStatusStr + "Bypass on, ";
updatedState.setBypass(true);
} else {
updatedState.setBypass(false);
for (int i = 1; i <= panelSettings.getNbZones(); i++) {
updatedState.setSensorBypassed(i, false);
}
}
if (((sysFlags >> 4) & 0x1) == 1) {
sysStatusStr = sysStatusStr + "Last 10 seconds, ";
}
if (((sysFlags >> 5) & 0x1) == 1) {
sysStatusStr = sysStatusStr + zoneETypeStr;
if (eventZone == 0xFF) {
sysStatusStr = sysStatusStr + " from Panel, ";
} else if (eventZone > 0) {
sysStatusStr = sysStatusStr + String.format(" in Zone %d, ", eventZone);
} else {
sysStatusStr = sysStatusStr + ", ";
}
}
if (((sysFlags >> 6) & 0x1) == 1) {
sysStatusStr = sysStatusStr + "Status changed, ";
}
if (((sysFlags >> 7) & 0x1) == 1) {
sysStatusStr = sysStatusStr + "Alarm event, ";
updatedState.setAlarmActive(true);
} else {
updatedState.setAlarmActive(false);
}
sysStatusStr = sysStatusStr.substring(0, sysStatusStr.length() - 2);
String statusStr;
try {
PowermaxArmMode armMode = PowermaxArmMode.fromCode(sysStatus & 0x000000FF);
statusStr = armMode.getName();
} catch (IllegalArgumentException e) {
statusStr = "UNKNOWN";
}
updatedState.setArmMode(statusStr);
updatedState.setStatusStr(statusStr + ", " + sysStatusStr);
for (int i = 1; i <= panelSettings.getNbZones(); i++) {
PowermaxZoneSettings zone = panelSettings.getZoneSettings(i);
if (zone != null) {
// mode: armed or not: 4=armed home; 5=armed away
int mode = sysStatus & 0x0000000F;
// Zone is shown as armed if
// the sensor type always triggers an alarm
// or the system is armed away (mode = 5)
// or the system is armed home (mode = 4) and the zone is not interior(-follow)
boolean armed = (!zone.getType().equalsIgnoreCase("Non-Alarm") && (zone.isAlwaysInAlarm()
|| (mode == 0x5) || ((mode == 0x4) && !zone.getType().equalsIgnoreCase("Interior-Follow")
&& !zone.getType().equalsIgnoreCase("Interior"))));
updatedState.setSensorArmed(i, armed);
}
}
} else if (eventType == 0x06) {
int zoneBypass = (message[8] & 0x000000FF) | ((message[9] << 8) & 0x0000FF00)
| ((message[10] << 16) & 0x00FF0000) | ((message[11] << 24) & 0xFF000000);
for (int i = 1; i <= panelSettings.getNbZones(); i++) {
updatedState.setSensorBypassed(i, ((zoneBypass >> (i - 1)) & 0x1) > 0);
}
}
return updatedState;
}
@Override
public String toString() {
String str = super.toString();
byte[] message = getRawData();
byte eventType = message[3];
str += "\n - event type = " + String.format("%02X", eventType);
if (eventType == 0x02) {
int zoneStatus = (message[4] & 0x000000FF) | ((message[5] << 8) & 0x0000FF00)
| ((message[6] << 16) & 0x00FF0000) | ((message[7] << 24) & 0xFF000000);
int batteryStatus = (message[8] & 0x000000FF) | ((message[9] << 8) & 0x0000FF00)
| ((message[10] << 16) & 0x00FF0000) | ((message[11] << 24) & 0xFF000000);
str += "\n - zone status = " + String.format("%08X", zoneStatus);
str += "\n - battery status = " + String.format("%08X", batteryStatus);
} else if (eventType == 0x04) {
byte sysStatus = message[4];
byte sysFlags = message[5];
byte eventZone = message[6];
byte zoneEType = message[7];
int x10Status = (message[10] & 0x000000FF) | ((message[11] << 8) & 0x0000FF00);
String zoneETypeStr = ((zoneEType & 0x000000FF) < EVENT_TYPE_TABLE.length)
? EVENT_TYPE_TABLE[zoneEType & 0x000000FF]
: "UNKNOWN";
str += "\n - system status = " + String.format("%02X", sysStatus);
str += "\n - system flags = " + String.format("%02X", sysFlags);
str += "\n - event zone = " + eventZone;
str += String.format("\n - zone event type = %02X (%s)", zoneEType, zoneETypeStr);
str += "\n - X10 status = " + String.format("%04X", x10Status);
} else if (eventType == 0x06) {
int zoneBypass = (message[8] & 0x000000FF) | ((message[9] << 8) & 0x0000FF00)
| ((message[10] << 16) & 0x00FF0000) | ((message[11] << 24) & 0xFF000000);
str += "\n - zone bypass = " + String.format("%08X", zoneBypass);
}
return str;
}
}

View File

@@ -0,0 +1,44 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxState;
/**
* A class for TIMEOUT message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxTimeoutMessage extends PowermaxBaseMessage {
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxTimeoutMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager != null) {
commManager.sendMessage(PowermaxSendType.EXIT);
}
return null;
}
}

View File

@@ -0,0 +1,77 @@
/**
* 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.powermax.internal.message;
/**
* All defined trouble types
*
* @author Laurent Garnier - Initial contribution
*/
public enum PowermaxTroubleType {
TROUBLE_TYPE_1(0x0A, "Communication"),
TROUBLE_TYPE_2(0x0F, "General"),
TROUBLE_TYPE_3(0x29, "Battery"),
TROUBLE_TYPE_4(0x2B, "Power"),
TROUBLE_TYPE_5(0x2D, "Battery"),
TROUBLE_TYPE_6(0x2F, "Jamming"),
TROUBLE_TYPE_7(0x31, "Communication"),
TROUBLE_TYPE_8(0x33, "Telephone"),
TROUBLE_TYPE_9(0x36, "Power"),
TROUBLE_TYPE_10(0x38, "Battery"),
TROUBLE_TYPE_11(0x3B, "Battery"),
TROUBLE_TYPE_12(0x3C, "Battery"),
TROUBLE_TYPE_13(0x40, "Battery"),
TROUBLE_TYPE_14(0x43, "Battery");
private int code;
private String label;
private PowermaxTroubleType(int code, String label) {
this.code = code;
this.label = label;
}
/**
* @return the code identifying the trouble type
*/
public int getCode() {
return code;
}
/**
* @return the label associated to the trouble type
*/
public String getLabel() {
return label;
}
/**
* Get the ENUM value from its identifying code
*
* @param code the identifying code
*
* @return the corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this code
*/
public static PowermaxTroubleType fromCode(int code) throws IllegalArgumentException {
for (PowermaxTroubleType troubleType : PowermaxTroubleType.values()) {
if (troubleType.getCode() == code) {
return troubleType;
}
}
throw new IllegalArgumentException("Invalid code: " + code);
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxState;
/**
* A class for ZONESNAME message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxZonesNameMessage extends PowermaxBaseMessage {
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxZonesNameMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
PowermaxState updatedState = commManager.createNewState();
byte[] message = getRawData();
int rowCnt = message[3] & 0x000000FF;
for (int i = 1; i <= 8; i++) {
int zoneIdx = (rowCnt - 1) * 8 + i;
byte zoneNameIdx = message[3 + i];
updatedState.updateZoneName(zoneIdx, zoneNameIdx);
}
return updatedState;
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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.powermax.internal.message;
import org.openhab.binding.powermax.internal.state.PowermaxState;
/**
* A class for ZONESTYPE message handling
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxZonesTypeMessage extends PowermaxBaseMessage {
/**
* Constructor
*
* @param message
* the received message as a buffer of bytes
*/
public PowermaxZonesTypeMessage(byte[] message) {
super(message);
}
@Override
public PowermaxState handleMessage(PowermaxCommManager commManager) {
super.handleMessage(commManager);
if (commManager == null) {
return null;
}
PowermaxState updatedState = commManager.createNewState();
byte[] message = getRawData();
int rowCnt = message[3] & 0x000000FF;
for (int i = 1; i <= 8; i++) {
int zoneIdx = (rowCnt - 1) * 8 + i;
int zoneInfo = (message[3 + i] & 0x000000FF) - 0x0000001E;
updatedState.updateZoneInfo(zoneIdx, zoneInfo);
}
return updatedState;
}
}

View File

@@ -0,0 +1,69 @@
/**
* 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.powermax.internal.state;
/**
* All defined sensor types for Master panels
*
* @author Laurent Garnier - Initial contribution
*/
public enum PowermasterSensorType {
SENSOR_TYPE_1((byte) 0x01, "Motion"),
SENSOR_TYPE_2((byte) 0x04, "Camera"),
SENSOR_TYPE_3((byte) 0x16, "Smoke"),
SENSOR_TYPE_4((byte) 0x1A, "Temperature"),
SENSOR_TYPE_5((byte) 0x2A, "Magnet"),
SENSOR_TYPE_6((byte) 0xFE, "Wired");
private byte code;
private String label;
private PowermasterSensorType(byte code, String label) {
this.code = code;
this.label = label;
}
/**
* @return the code identifying the sensor type
*/
public byte getCode() {
return code;
}
/**
* @return the label associated to the sensor type
*/
public String getLabel() {
return label;
}
/**
* Get the ENUM value from its identifying code
*
* @param code the identifying code
*
* @return the corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this code
*/
public static PowermasterSensorType fromCode(byte code) throws IllegalArgumentException {
for (PowermasterSensorType sensorType : PowermasterSensorType.values()) {
if (sensorType.getCode() == code) {
return sensorType;
}
}
throw new IllegalArgumentException("Invalid code: " + code);
}
}

View File

@@ -0,0 +1,170 @@
/**
* 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.powermax.internal.state;
/**
* All defined arm modes
*
* @author Laurent Garnier - Initial contribution
*/
public enum PowermaxArmMode {
DISARMED(0, "Disarmed", "Disarmed", false, (byte) 0x00, false),
HOME_EXIT_DELAY(1, "Home Exit Delay", "ExitDelay", false, (byte) 0xFF, false),
AWAY_EXIT_DELAY(2, "Away Exit Delay", "ExitDelay", false, (byte) 0xFF, false),
ENTRY_DELAY(3, "Entry Delay", "EntryDelay", true, (byte) 0xFF, false),
ARMED_HOME(4, "Armed Home", "Stay", true, (byte) 0x04, false),
ARMED_AWAY(5, "Armed Away", "Armed", true, (byte) 0x05, false),
USER_TEST(6, "User Test", "UserTest", false, (byte) 0xFF, false),
DOWNLOADING(7, "Downloading", "NotReady", false, (byte) 0xFF, false),
PROGRAMMING(8, "Programming", "NotReady", false, (byte) 0xFF, false),
INSTALLER(9, "Installer", "NotReady", false, (byte) 0xFF, false),
HOME_BYPASS(10, "Home Bypass", "Force", true, (byte) 0xFF, false),
AWAY_BYPASS(11, "Away Bypass", "Force", true, (byte) 0xFF, false),
READY(12, "Ready", "Ready", false, (byte) 0xFF, false),
NOT_READY(13, "Not Ready", "NotReady", false, (byte) 0xFF, false),
ARMED_NIGHT(14, "Armed Night", "Night", true, (byte) 0x04, false),
ARMED_NIGHT_INSTANT(15, "Armed Night Instant", "NightInstant", true, (byte) 0x14, false),
DISARMED_INSTANT(16, "Disarmed Instant", "DisarmedInstant", false, (byte) 0xFF, false),
HOME_INSTANT_EXIT_DELAY(17, "Home Instant Exit Delay", "ExitDelay", false, (byte) 0xFF, false),
AWAY_INSTANT_EXIT_DELAY(18, "Away Instant Exit Delay", "ExitDelay", false, (byte) 0xFF, false),
ENTRY_DELAY_INSTANT(19, "Entry Delay Instant", "EntryDelay", true, (byte) 0xFF, false),
ARMED_HOME_INSTANT(20, "Armed Home Instant", "StayInstant", true, (byte) 0x14, false),
ARMED_AWAY_INSTANT(21, "Armed Away Instant", "ArmedInstant", true, (byte) 0x15, false);
private int code;
private String name;
private String shortName;
private boolean armed;
private byte commandCode;
private boolean allowedCommand;
private PowermaxArmMode(int code, String name, String shortName, boolean armed, byte commandCode,
boolean allowedCommand) {
this.code = code;
this.name = name;
this.shortName = shortName;
this.armed = armed;
this.commandCode = commandCode;
this.allowedCommand = allowedCommand;
}
/**
* @return the code identifying the mode
*/
public int getCode() {
return code;
}
/**
* @return the full mode name
*/
public String getName() {
return name;
}
/**
* @return the short name
*/
public String getShortName() {
return shortName;
}
/**
* @return true if the mode is considered as armed
*/
public boolean isArmed() {
return armed;
}
/**
* @return the command code
*/
public byte getCommandCode() {
return commandCode;
}
/**
* @return true if the mode is an allowed command
*/
public boolean isAllowedCommand() {
return allowedCommand;
}
/**
* Set whether the mode is an allowed command or not
* To be allowed, the mode must have a valid command code.
*
* @param allowedCommand true if the mode must be an allowed command
*/
public void setAllowedCommand(boolean allowedCommand) {
this.allowedCommand = getCommandCode() == 0xFF ? false : allowedCommand;
}
/**
* Get the ENUM value from its code
*
* @param code the code identifying the mode
*
* @return the corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this code
*/
public static PowermaxArmMode fromCode(int code) throws IllegalArgumentException {
for (PowermaxArmMode mode : PowermaxArmMode.values()) {
if (mode.getCode() == code) {
return mode;
}
}
throw new IllegalArgumentException("Invalid code: " + code);
}
/**
* Get the ENUM value from its name
*
* @param name the full mode name
*
* @return the corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this name
*/
public static PowermaxArmMode fromName(String name) throws IllegalArgumentException {
for (PowermaxArmMode mode : PowermaxArmMode.values()) {
if (mode.getName().equalsIgnoreCase(name)) {
return mode;
}
}
throw new IllegalArgumentException("Invalid name: " + name);
}
/**
* Get the ENUM value from its short name
*
* @param shortName the mode short name
*
* @return the first corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this short name
*/
public static PowermaxArmMode fromShortName(String shortName) throws IllegalArgumentException {
for (PowermaxArmMode mode : PowermaxArmMode.values()) {
if (mode.getShortName().equalsIgnoreCase(shortName)) {
return mode;
}
}
throw new IllegalArgumentException("Invalid short name: " + shortName);
}
}

View File

@@ -0,0 +1,817 @@
/**
* 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.powermax.internal.state;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.openhab.binding.powermax.internal.message.PowermaxSendType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class to store all the settings of the alarm system
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxPanelSettings {
private final Logger logger = LoggerFactory.getLogger(PowermaxPanelSettings.class);
/** Number of PGM and X10 devices managed by the system */
private static final int NB_PGM_X10_DEVICES = 16;
/** Raw buffers for settings */
private Byte[][] rawSettings;
private PowermaxPanelType panelType;
private String[] phoneNumbers;
private int bellTime;
private boolean silentPanic;
private boolean quickArm;
private boolean bypassEnabled;
private boolean partitionsEnabled;
private String[] pinCodes;
private String panelEprom;
private String panelSoftware;
private String panelSerial;
private PowermaxZoneSettings[] zoneSettings;
private PowermaxX10Settings[] x10Settings;
private boolean[] keypad1wEnrolled;
private boolean[] keypad2wEnrolled;
private boolean[] sirensEnrolled;
/**
* Constructor
*
* @param defaultPanelType the default panel type to consider
*/
public PowermaxPanelSettings(PowermaxPanelType defaultPanelType) {
rawSettings = new Byte[0x100][];
panelType = defaultPanelType;
phoneNumbers = new String[4];
bellTime = 4;
int zoneCnt = panelType.getWireless() + panelType.getWired();
zoneSettings = new PowermaxZoneSettings[zoneCnt];
x10Settings = new PowermaxX10Settings[NB_PGM_X10_DEVICES];
}
/**
* @return the panel type
*/
public PowermaxPanelType getPanelType() {
return panelType;
}
/**
* @return true if bypassing zones is enabled; false if not
*/
public boolean isBypassEnabled() {
return bypassEnabled;
}
/**
* @return true if partitions usage is enabled; false if not
*/
public boolean isPartitionsEnabled() {
return partitionsEnabled;
}
/**
* @return the panel EEPROM version
*/
public String getPanelEprom() {
return panelEprom;
}
/**
* @return the panel software version
*/
public String getPanelSoftware() {
return panelSoftware;
}
/**
* @return the panel serial ID
*/
public String getPanelSerial() {
return panelSerial;
}
/**
* @return the number of zones
*/
public int getNbZones() {
return zoneSettings.length;
}
/**
* Get the settings relative to a zone
*
* @param zone the zone index (from 1 to NumberOfZones)
*
* @return the settings of the zone
*/
public PowermaxZoneSettings getZoneSettings(int zone) {
return ((zone < 1) || (zone > zoneSettings.length)) ? null : zoneSettings[zone - 1];
}
/**
* @return the number of PGM and X10 devices managed by the system
*/
public int getNbPGMX10Devices() {
return NB_PGM_X10_DEVICES;
}
/**
* Get the settings relative to the PGM
*
* @return the settings of the PGM
*/
public PowermaxX10Settings getPGMSettings() {
return x10Settings[0];
}
/**
* Get the settings relative to a X10 device
*
* @param idx the index (from 1 to 15)
*
* @return the settings of the X10 device
*/
public PowermaxX10Settings getX10Settings(int idx) {
return ((idx < 1) || (idx >= x10Settings.length)) ? null : x10Settings[idx];
}
/**
* @param idx the keypad index (first is 1)
*
* @return true if the 1 way keypad is enrolled; false if not
*/
public boolean isKeypad1wEnrolled(int idx) {
return ((keypad1wEnrolled == null) || (idx < 1) || (idx >= keypad1wEnrolled.length)) ? false
: keypad1wEnrolled[idx - 1];
}
/**
* @param idx the keypad index (first is 1)
*
* @return true if the 2 way keypad is enrolled; false if not
*/
public boolean isKeypad2wEnrolled(int idx) {
return ((keypad2wEnrolled == null) || (idx < 1) || (idx >= keypad2wEnrolled.length)) ? false
: keypad2wEnrolled[idx - 1];
}
/**
* @param idx the siren index (first is 1)
*
* @return true if the siren is enrolled; false if not
*/
public boolean isSirenEnrolled(int idx) {
return ((sirensEnrolled == null) || (idx < 1) || (idx >= sirensEnrolled.length)) ? false
: sirensEnrolled[idx - 1];
}
/**
* @return the PIN code of the first user of null if unknown (standard mode)
*/
public String getFirstPinCode() {
return (pinCodes == null) ? null : pinCodes[0];
}
public void updateRawSettings(byte[] data) {
if ((data == null) || (data.length < 3)) {
return;
}
int start = 0;
int end = data.length - 3;
int index = data[0] & 0x000000FF;
int page = data[1] & 0x000000FF;
int pageMin = page + (index + start) / 0x100;
int indexPageMin = (index + start) % 0x100;
int pageMax = page + (index + end) / 0x100;
int indexPageMax = (index + end) % 0x100;
index = 2;
for (int i = pageMin; i <= pageMax; i++) {
start = 0;
end = 0xFF;
if (i == pageMin) {
start = indexPageMin;
}
if (i == pageMax) {
end = indexPageMax;
}
if (rawSettings[i] == null) {
rawSettings[i] = new Byte[0x100];
}
for (int j = start; j <= end; j++) {
rawSettings[i][j] = data[index++];
}
}
}
private byte[] readSettings(PowermaxSendType msgType, int start, int end) {
byte[] message = msgType.getMessage();
int page = message[2] & 0x000000FF;
int index = message[1] & 0x000000FF;
return readSettings(page, index + start, index + end);
}
private byte[] readSettings(int page, int start, int end) {
int pageMin = page + start / 0x100;
int indexPageMin = start % 0x100;
int pageMax = page + end / 0x100;
int indexPageMax = end % 0x100;
int index = 0;
boolean missingData = false;
for (int i = pageMin; i <= pageMax; i++) {
int start2 = 0;
int end2 = 0xFF;
if (i == pageMin) {
start2 = indexPageMin;
}
if (i == pageMax) {
end2 = indexPageMax;
}
index += end2 - start2 + 1;
for (int j = start2; j <= end2; j++) {
if ((rawSettings[i] == null) || (rawSettings[i][j] == null)) {
missingData = true;
break;
}
}
if (missingData) {
break;
}
}
if (missingData) {
logger.debug("readSettings({}, {}, {}): missing data", page, start, end);
return null;
}
byte[] result = new byte[index];
index = 0;
for (int i = pageMin; i <= pageMax; i++) {
int start2 = 0;
int end2 = 0xFF;
if (i == pageMin) {
start2 = indexPageMin;
}
if (i == pageMax) {
end2 = indexPageMax;
}
for (int j = start2; j <= end2; j++) {
result[index++] = rawSettings[i][j];
}
}
return result;
}
private String readSettingsAsString(PowermaxSendType msgType, int start, int end) {
byte[] message = msgType.getMessage();
int page = message[2] & 0x000000FF;
int index = message[1] & 0x000000FF;
String result = null;
byte[] data = readSettings(page, index + start, index + end);
if ((data != null) && ((data[0] & 0x000000FF) != 0x000000FF)) {
result = "";
for (int i = 0; i < data.length; i++) {
boolean endStr = false;
switch (data[i] & 0x000000FF) {
case 0:
endStr = true;
break;
case 1:
result += "é";
break;
case 3:
result += "è";
break;
case 5:
result += "à";
break;
default:
if ((data[i] & 0x000000FF) >= 0x20) {
try {
result += new String(data, i, 1, "US-ASCII");
} catch (UnsupportedEncodingException e) {
logger.debug("Unhandled character code {}", data[i]);
}
} else {
logger.debug("Unhandled character code {}", data[i]);
}
break;
}
if (endStr) {
break;
}
}
result = result.trim();
}
return result;
}
/**
* Process and store all the panel settings from the raw buffers
*
* @param PowerlinkMode true if in Powerlink mode or false if in standard mode
* @param defaultPanelType the default panel type to consider if not found in the raw buffers
* @param timeSet the time in milliseconds used to set time and date; null if no sync time requested
*
* @return true if no problem encountered to get all the settings; false if not
*/
@SuppressWarnings("null")
public boolean process(boolean PowerlinkMode, PowermaxPanelType defaultPanelType, Long timeSet) {
logger.debug("Process settings Powerlink {}", PowerlinkMode);
boolean result = true;
boolean result2;
byte[] data;
// Identify panel type
panelType = defaultPanelType;
if (PowerlinkMode) {
data = readSettings(PowermaxSendType.DL_SERIAL, 7, 7);
if (data != null) {
try {
panelType = PowermaxPanelType.fromCode(data[0]);
} catch (IllegalArgumentException e) {
logger.debug("Powermax alarm binding: unknwon panel type for code {}", data[0] & 0x000000FF);
panelType = defaultPanelType;
}
} else {
logger.debug("Cannot get panel type");
result = false;
}
}
int zoneCnt = panelType.getWireless() + panelType.getWired();
int customCnt = panelType.getCustomZones();
int userCnt = panelType.getUserCodes();
int partitionCnt = panelType.getPartitions();
int sirenCnt = panelType.getSirens();
int keypad1wCnt = panelType.getKeypads1w();
int keypad2wCnt = panelType.getKeypads2w();
phoneNumbers = new String[4];
bellTime = 4;
silentPanic = false;
quickArm = false;
bypassEnabled = false;
partitionsEnabled = false;
pinCodes = new String[userCnt];
panelEprom = null;
panelSoftware = null;
panelSerial = null;
zoneSettings = new PowermaxZoneSettings[zoneCnt];
x10Settings = new PowermaxX10Settings[NB_PGM_X10_DEVICES];
keypad1wEnrolled = new boolean[keypad1wCnt];
keypad2wEnrolled = new boolean[keypad2wCnt];
sirensEnrolled = new boolean[sirenCnt];
if (PowerlinkMode) {
// Check time and date
data = readSettings(PowermaxSendType.DL_TIME, 0, 5);
if (data != null) {
GregorianCalendar cal = new GregorianCalendar();
cal.set(Calendar.MILLISECOND, 0);
cal.set(Calendar.SECOND, data[0] & 0x000000FF);
cal.set(Calendar.MINUTE, data[1] & 0x000000FF);
cal.set(Calendar.HOUR_OF_DAY, data[2] & 0x000000FF);
cal.set(Calendar.DAY_OF_MONTH, data[3] & 0x000000FF);
cal.set(Calendar.MONTH, (data[4] & 0x000000FF) - 1);
cal.set(Calendar.YEAR, (data[5] & 0x000000FF) + 2000);
long timeRead = cal.getTimeInMillis();
logger.debug("Powermax alarm binding: time {}",
String.format("%02d/%02d/%04d %02d:%02d:%02d", cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.MONTH) + 1, cal.get(Calendar.YEAR), cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND)));
// Check if time sync was OK
if (timeSet != null) {
long delta = (timeRead - timeSet) / 1000;
if (delta <= 5) {
logger.debug("Powermax alarm binding: time sync OK (delta {} s)", delta);
} else {
logger.info("Powermax alarm binding: time sync failed ! (delta {} s)", delta);
}
}
} else {
logger.debug("Cannot get time and date settings");
result = false;
}
// Process zone names
result2 = true;
for (int i = 0; i < (26 + customCnt); i++) {
String str = readSettingsAsString(PowermaxSendType.DL_ZONESTR, i * 16, (i + 1) * 16 - 1);
if (str != null) {
try {
PowermaxZoneName zoneName = PowermaxZoneName.fromId(i);
zoneName.setName(str);
} catch (IllegalArgumentException e) {
logger.debug("Zone id out of bounds {}", i);
}
} else {
result2 = false;
}
}
if (!result2) {
logger.debug("Cannot get all zone names");
result = false;
}
// Process communication settings
result2 = true;
for (int i = 0; i < phoneNumbers.length; i++) {
data = readSettings(PowermaxSendType.DL_PHONENRS, 8 * i, 8 * i + 7);
if (data != null) {
for (int j = 0; j < 8; j++) {
if ((data[j] & 0x000000FF) != 0x000000FF) {
if (j == 0) {
phoneNumbers[i] = "";
}
if (phoneNumbers[i] != null) {
phoneNumbers[i] += String.format("%02X", data[j] & 0x000000FF);
}
}
}
} else {
result2 = false;
}
}
if (!result2) {
logger.debug("Cannot get all communication settings");
result = false;
}
// Process alarm settings
data = readSettings(PowermaxSendType.DL_COMMDEF, 0, 0x1B);
if (data != null) {
bellTime = data[3] & 0x000000FF;
silentPanic = (data[0x19] & 0x00000010) == 0x00000010;
quickArm = (data[0x1A] & 0x00000008) == 0x00000008;
bypassEnabled = (data[0x1B] & 0x000000C0) != 0;
} else {
logger.debug("Cannot get alarm settings");
result = false;
}
// Process user PIN codes
data = readSettings(
panelType.isPowerMaster() ? PowermaxSendType.DL_MR_PINCODES : PowermaxSendType.DL_PINCODES, 0,
2 * userCnt - 1);
if (data != null) {
for (int i = 0; i < userCnt; i++) {
pinCodes[i] = String.format("%02X%02X", data[i * 2] & 0x000000FF, data[i * 2 + 1] & 0x000000FF);
}
} else {
logger.debug("Cannot get PIN codes");
result = false;
}
// Process EEPROM version
panelEprom = readSettingsAsString(PowermaxSendType.DL_PANELFW, 0, 15);
if (panelEprom == null) {
logger.debug("Cannot get EEPROM version");
result = false;
}
// Process software version
panelSoftware = readSettingsAsString(PowermaxSendType.DL_PANELFW, 16, 31);
if (panelSoftware == null) {
logger.debug("Cannot get software version");
result = false;
}
// Process serial ID
panelSerial = "";
data = readSettings(PowermaxSendType.DL_SERIAL, 0, 5);
if (data != null) {
for (int i = 0; i <= 5; i++) {
if ((data[i] & 0x000000FF) != 0x000000FF) {
panelSerial += String.format("%02X", data[i] & 0x000000FF);
} else {
panelSerial += ".";
}
}
} else {
logger.debug("Cannot get serial ID");
result = false;
}
// Check if partitions are enabled
byte[] partitions = readSettings(PowermaxSendType.DL_PARTITIONS, 0, 0x10 + zoneCnt);
if (partitions != null) {
partitionsEnabled = (partitions[0] & 0x000000FF) == 1;
} else {
logger.debug("Cannot get partitions information");
result = false;
}
if (!partitionsEnabled) {
partitionCnt = 1;
}
// Process zone settings
data = readSettings(PowermaxSendType.DL_ZONES, 0, zoneCnt * 4 - 1);
byte[] zoneNr = null;
byte[] dataMr = null;
if (panelType.isPowerMaster()) {
zoneNr = readSettings(PowermaxSendType.DL_MR_ZONENAMES, 0, zoneCnt - 1);
dataMr = readSettings(PowermaxSendType.DL_MR_ZONES, 0, zoneCnt * 10 - 2);
} else {
zoneNr = readSettings(PowermaxSendType.DL_ZONENAMES, 0, zoneCnt - 1);
}
if ((data != null) && (zoneNr != null)) {
byte[] zero3 = new byte[] { 0, 0, 0 };
byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
for (int i = 0; i < zoneCnt; i++) {
String zoneName;
try {
PowermaxZoneName zone = PowermaxZoneName.fromId(zoneNr[i] & 0x0000001F);
zoneName = zone.getName();
} catch (IllegalArgumentException e) {
logger.debug("Zone id out of bounds {}", zoneNr[i] & 0x0000001F);
zoneName = null;
}
boolean zoneEnrolled;
byte zoneInfo;
byte sensorTypeCode;
String sensorTypeStr;
if (panelType.isPowerMaster()) {
zoneEnrolled = !Arrays.equals(Arrays.copyOfRange(dataMr, i * 10 + 4, i * 10 + 9), zero5);
zoneInfo = data[i];
sensorTypeCode = dataMr[i * 10 + 5];
try {
PowermasterSensorType sensorType = PowermasterSensorType.fromCode(sensorTypeCode);
sensorTypeStr = sensorType.getLabel();
} catch (IllegalArgumentException e) {
sensorTypeStr = null;
}
} else {
zoneEnrolled = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
zoneInfo = data[i * 4 + 3];
sensorTypeCode = data[i * 4 + 2];
try {
PowermaxSensorType sensorType = PowermaxSensorType
.fromCode((byte) (sensorTypeCode & 0x0000000F));
sensorTypeStr = sensorType.getLabel();
} catch (IllegalArgumentException e) {
sensorTypeStr = null;
}
}
if (zoneEnrolled) {
byte zoneType = (byte) (zoneInfo & 0x0000000F);
byte zoneChime = (byte) ((zoneInfo >> 4) & 0x00000003);
boolean[] part = new boolean[partitionCnt];
if (partitionCnt > 1) {
for (int j = 0; j < partitionCnt; j++) {
part[j] = (partitions != null) ? ((partitions[0x11 + i] & (1 << j)) != 0) : true;
}
} else {
part[0] = true;
}
zoneSettings[i] = new PowermaxZoneSettings(zoneName, zoneType, zoneChime, sensorTypeStr, part);
}
}
} else {
logger.debug("Cannot get zone settings");
result = false;
}
data = readSettings(PowermaxSendType.DL_PGMX10, 0, 148);
zoneNr = readSettings(PowermaxSendType.DL_X10NAMES, 0, NB_PGM_X10_DEVICES - 2);
if ((data != null) && (zoneNr != null)) {
for (int i = 0; i < NB_PGM_X10_DEVICES; i++) {
boolean enabled = false;
String zoneName = null;
for (int j = 0; j <= 8; j++) {
if (data[5 + i + j * 0x10] != 0) {
enabled = true;
break;
}
}
if (i > 0) {
try {
PowermaxZoneName zone = PowermaxZoneName.fromId(zoneNr[i - 1] & 0x0000001F);
zoneName = zone.getName();
} catch (IllegalArgumentException e) {
logger.debug("Zone id out of bounds {}", zoneNr[i - 1] & 0x0000001F);
zoneName = null;
}
}
x10Settings[i] = new PowermaxX10Settings(zoneName, enabled);
}
} else {
logger.debug("Cannot get PGM / X10 settings");
result = false;
}
if (panelType.isPowerMaster()) {
// Process 2 way keypad settings
data = readSettings(PowermaxSendType.DL_MR_KEYPADS, 0, keypad2wCnt * 10 - 1);
if (data != null) {
byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
for (int i = 0; i < keypad2wCnt; i++) {
keypad2wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 10 + 4, i * 10 + 9), zero5);
}
} else {
logger.debug("Cannot get 2 way keypad settings");
result = false;
}
// Process siren settings
data = readSettings(PowermaxSendType.DL_MR_SIRENS, 0, sirenCnt * 10 - 1);
if (data != null) {
byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
for (int i = 0; i < sirenCnt; i++) {
sirensEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 10 + 4, i * 10 + 9), zero5);
}
} else {
logger.debug("Cannot get siren settings");
result = false;
}
} else {
// Process 1 way keypad settings
data = readSettings(PowermaxSendType.DL_1WKEYPAD, 0, keypad1wCnt * 4 - 1);
if (data != null) {
byte[] zero2 = new byte[] { 0, 0 };
for (int i = 0; i < keypad1wCnt; i++) {
keypad1wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 2), zero2);
}
} else {
logger.debug("Cannot get 1 way keypad settings");
result = false;
}
// Process 2 way keypad settings
data = readSettings(PowermaxSendType.DL_2WKEYPAD, 0, keypad2wCnt * 4 - 1);
if (data != null) {
byte[] zero3 = new byte[] { 0, 0, 0 };
for (int i = 0; i < keypad2wCnt; i++) {
keypad2wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
}
} else {
logger.debug("Cannot get 2 way keypad settings");
result = false;
}
// Process siren settings
data = readSettings(PowermaxSendType.DL_SIRENS, 0, sirenCnt * 4 - 1);
if (data != null) {
byte[] zero3 = new byte[] { 0, 0, 0 };
for (int i = 0; i < sirenCnt; i++) {
sirensEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
}
} else {
logger.debug("Cannot get siren settings");
result = false;
}
}
} else {
if (!partitionsEnabled) {
partitionCnt = 1;
}
boolean[] part = new boolean[partitionCnt];
for (int j = 0; j < partitionCnt; j++) {
part[j] = true;
}
for (int i = 0; i < zoneCnt; i++) {
zoneSettings[i] = new PowermaxZoneSettings(null, (byte) 0xFF, (byte) 0xFF, null, part);
}
for (int i = 0; i < NB_PGM_X10_DEVICES; i++) {
x10Settings[i] = new PowermaxX10Settings(null, true);
}
}
return result;
}
/**
* Update the name of a zone
*
* @param zoneIdx the zone index (first zone is index 1)
* @param zoneNameIdx the index in the table of zone names
*/
public void updateZoneName(int zoneIdx, byte zoneNameIdx) {
PowermaxZoneSettings zone = getZoneSettings(zoneIdx);
if (zone != null) {
String name;
try {
PowermaxZoneName zoneName = PowermaxZoneName.fromId(zoneNameIdx & 0x0000001F);
name = zoneName.getName();
} catch (IllegalArgumentException e) {
logger.debug("Zone id out of bounds {}", zoneNameIdx & 0x0000001F);
name = null;
}
zone.setName(name);
}
}
/**
* Update the type of a zone
*
* @param zoneIdx the zone index (first zone is index 1)
* @param zoneInfo the zone info as an internal code
*/
public void updateZoneInfo(int zoneIdx, int zoneInfo) {
PowermaxZoneSettings zone = getZoneSettings(zoneIdx);
if (zone != null) {
zone.setType((byte) (zoneInfo & 0x0000000F));
}
}
public String getInfo() {
String str = "\nPanel is of type " + panelType.getLabel();
int zoneCnt = panelType.getWireless() + panelType.getWired();
int partitionCnt = panelType.getPartitions();
int sirenCnt = panelType.getSirens();
int keypad1wCnt = panelType.getKeypads1w();
int keypad2wCnt = panelType.getKeypads2w();
// int customCnt = panelType.getCustomZones();
if (!partitionsEnabled) {
partitionCnt = 1;
}
// for (int i = 0; i < (26 + customCnt); i++) {
// String name;
// try {
// PowermaxZoneName zoneName = PowermaxZoneName.fromId(i);
// name = zoneName.getName();
// } catch (IllegalArgumentException e) {
// logger.debug("Zone id out of bounds {}", i);
// name = null;
// }
// str += String.format("\nZone name %d; %s", i + 1, name);
// }
for (int i = 0; i < phoneNumbers.length; i++) {
if (phoneNumbers[i] != null) {
str += String.format("\nPhone number %d: %s", i + 1, phoneNumbers[i]);
}
}
str += String.format("\nBell time: %d minutes", bellTime);
str += String.format("\nSilent panic: %s", silentPanic ? "enabled" : "disabled");
str += String.format("\nQuick arm: %s", quickArm ? "enabled" : "disabled");
str += String.format("\nZone bypass: %s", bypassEnabled ? "enabled" : "disabled");
str += String.format("\nEPROM: %s", (panelEprom != null) ? panelEprom : "Undefined");
str += String.format("\nSW: %s", (panelSoftware != null) ? panelSoftware : "Undefined");
str += String.format("\nSerial: %s", (panelSerial != null) ? panelSerial : "Undefined");
str += String.format("\nUse partitions: %s", partitionsEnabled ? "enabled" : "disabled");
str += String.format("\nNumber of partitions: %d", partitionCnt);
for (int i = 0; i < zoneCnt; i++) {
if (zoneSettings[i] != null) {
String partStr = "";
for (int j = 1; j <= partitionCnt; j++) {
if (zoneSettings[i].isInPartition(j)) {
partStr += j + " ";
}
}
str += String.format("\nZone %d %s: %s (chime = %s; sensor type = %s; partitions = %s)", i + 1,
zoneSettings[i].getName(), zoneSettings[i].getType(), zoneSettings[i].getChime(),
zoneSettings[i].getSensorType(), partStr);
}
}
for (int i = 0; i < NB_PGM_X10_DEVICES; i++) {
if (x10Settings[i] != null && x10Settings[i].isEnabled()) {
str += String.format("\n%s: %s enabled", (i == 0) ? "PGM" : ("X10 " + i),
(x10Settings[i].getName() != null) ? x10Settings[i].getName() : "");
}
}
for (int i = 1; i <= sirenCnt; i++) {
if (isSirenEnrolled(i)) {
str += String.format("\nSiren %d enrolled", i);
}
}
for (int i = 1; i <= keypad1wCnt; i++) {
if (isKeypad1wEnrolled(i)) {
str += String.format("\nKeypad 1w %d enrolled", i);
}
}
for (int i = 1; i <= keypad2wCnt; i++) {
if (isKeypad2wEnrolled(i)) {
str += String.format("\nKeypad 2w %d enrolled", i);
}
}
return "Powermax alarm binding:" + str;
}
}

View File

@@ -0,0 +1,39 @@
/**
* 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.powermax.internal.state;
/**
* The {@link PowermaxPanelSettingsListener} is a listener for updated
* alarm panel settings
*
* @author Laurent Garnier - Initial contribution
*/
public interface PowermaxPanelSettingsListener {
/**
* This method is called when the bridge thing handler identifies
* a change in the alarm panel settings
*
* @param settings the updated alarm panel settings or null if the panel settings are unknown
*/
public void onPanelSettingsUpdated(PowermaxPanelSettings settings);
/**
* This method is called when the bridge thing handler identifies
* a change in one zone settings
*
* @param zoneNumber the zone number
* @param settings the updated alarm panel settings or null if the panel settings are unknown
*/
public void onZoneSettingsUpdated(int zoneNumber, PowermaxPanelSettings settings);
}

View File

@@ -0,0 +1,195 @@
/**
* 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.powermax.internal.state;
/**
* Used to store main characteristics of each Visonic alarm panel type in an ENUM
*
* @author Laurent Garnier - Initial contribution
*/
public enum PowermaxPanelType {
POWERMAX((byte) 0, "PowerMax", 1, 250, 8, 8, 2, 2, 8, 0, 28, 2, 0),
POWERMAX_PLUS((byte) 1, "PowerMax+", 1, 250, 8, 8, 2, 2, 8, 0, 28, 2, 5),
POWERMAX_PRO((byte) 2, "PowerMaxPro", 1, 250, 8, 8, 2, 2, 8, 8, 28, 2, 5),
POWERMAX_COMPLETE((byte) 3, "PowerMaxComplete", 1, 250, 8, 8, 2, 2, 8, 0, 28, 2, 5),
POWERMAX_PRO_PART((byte) 4, "PowerMaxProPart", 3, 250, 8, 8, 2, 2, 8, 8, 28, 2, 5),
POWERMAX_COMPLETE_PART((byte) 5, "PowerMaxCompletePart", 3, 250, 8, 8, 2, 2, 8, 8, 28, 2, 5),
POWERMAX_EXPRESS((byte) 6, "PowerMaxExpress", 1, 250, 8, 8, 2, 2, 8, 0, 28, 1, 5),
POWERMASTER_10((byte) 7, "PowerMaster10", 3, 250, 8, 0, 8, 4, 8, 8, 29, 1, 5),
POWERMASTER_30((byte) 8, "PowerMaster30", 3, 1000, 32, 0, 32, 8, 48, 32, 62, 2, 5);
private byte code;
private String label;
private int partitions;
private int events;
private int keyfobs;
private int keypads1w;
private int keypads2w;
private int sirens;
private int userCodes;
private int prontags;
private int wireless;
private int wired;
private int customZones;
private PowermaxPanelType(byte code, String label, int partitions, int events, int keyfobs, int keypads1w,
int keypads2w, int sirens, int userCodes, int prontags, int wireless, int wired, int customZones) {
this.code = code;
this.label = label;
this.partitions = partitions;
this.events = events;
this.keyfobs = keyfobs;
this.keypads1w = keypads1w;
this.keypads2w = keypads2w;
this.sirens = sirens;
this.userCodes = userCodes;
this.prontags = prontags;
this.wireless = wireless;
this.wired = wired;
this.customZones = customZones;
}
/**
* @return the code (number) stored in the panel setup
*/
public byte getCode() {
return code;
}
/**
* @return the panel type as a string
*/
public String getLabel() {
return label;
}
/**
* @return the number of managed partitions
*/
public int getPartitions() {
return partitions;
}
/**
* @return the number of events stored in the event log
*/
public int getEvents() {
return events;
}
/**
* @return the number of managed keyfobs
*/
public int getKeyfobs() {
return keyfobs;
}
/**
* @return the number of managed uni-directional keypads
*/
public int getKeypads1w() {
return keypads1w;
}
/**
* @return the number of managed bi-directional keypads
*/
public int getKeypads2w() {
return keypads2w;
}
/**
* @return the number of managed sirens
*/
public int getSirens() {
return sirens;
}
/**
* @return the number of managed user codes
*/
public int getUserCodes() {
return userCodes;
}
public int getProntags() {
return prontags;
}
/**
* @return the number of managed wireless zones
*/
public int getWireless() {
return wireless;
}
/**
* @return the number of managed wired zones
*/
public int getWired() {
return wired;
}
/**
* @return the number of zones that can be customized by the user
*/
public int getCustomZones() {
return customZones;
}
/**
* @return true is the panel is a PowerMaster panel type
*/
public boolean isPowerMaster() {
return this == PowermaxPanelType.POWERMASTER_10 || this == PowermaxPanelType.POWERMASTER_30;
}
/**
* Get the ENUM value from its code number
*
* @param panelCode the code stored by the panel
*
* @return the corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this code
*/
public static PowermaxPanelType fromCode(byte panelCode) throws IllegalArgumentException {
for (PowermaxPanelType panelType : PowermaxPanelType.values()) {
if (panelType.getCode() == panelCode) {
return panelType;
}
}
throw new IllegalArgumentException("Invalid code: " + panelCode);
}
/**
* Get the ENUM value from its label
*
* @param label the label
*
* @return the corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this label
*/
public static PowermaxPanelType fromLabel(String label) throws IllegalArgumentException {
for (PowermaxPanelType panelType : PowermaxPanelType.values()) {
if (panelType.getLabel().equalsIgnoreCase(label)) {
return panelType;
}
}
throw new IllegalArgumentException("Invalid label: " + label);
}
}

View File

@@ -0,0 +1,72 @@
/**
* 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.powermax.internal.state;
/**
* All defined sensor types for all panels except Master panels
*
* @author Laurent Garnier - Initial contribution
*/
public enum PowermaxSensorType {
MOTION_SENSOR_1((byte) 0x03, "Motion"),
MOTION_SENSOR_2((byte) 0x04, "Motion"),
MAGNET_SENSOR_1((byte) 0x05, "Magnet"),
MAGNET_SENSOR_2((byte) 0x06, "Magnet"),
MAGNET_SENSOR_3((byte) 0x07, "Magnet"),
SMOKE_SENSOR((byte) 0x0A, "Smoke"),
GAS_SENSOR((byte) 0x0B, "Gas"),
MOTION_SENSOR_3((byte) 0x0C, "Motion"),
WIRED_SENSOR((byte) 0x0F, "Wired");
private byte code;
private String label;
private PowermaxSensorType(byte code, String label) {
this.code = code;
this.label = label;
}
/**
* @return the code identifying the sensor type
*/
public byte getCode() {
return code;
}
/**
* @return the label associated to the sensor type
*/
public String getLabel() {
return label;
}
/**
* Get the ENUM value from its identifying code
*
* @param code the identifying code
*
* @return the corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this code
*/
public static PowermaxSensorType fromCode(byte code) throws IllegalArgumentException {
for (PowermaxSensorType sensorType : PowermaxSensorType.values()) {
if (sensorType.getCode() == code) {
return sensorType;
}
}
throw new IllegalArgumentException("Invalid code: " + code);
}
}

View File

@@ -0,0 +1,828 @@
/**
* 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.powermax.internal.state;
import java.util.HashMap;
import java.util.Map;
/**
* A class to store the state of the alarm system
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxState {
private Boolean powerlinkMode;
private Boolean downloadMode;
private PowermaxZoneState[] zones;
private Boolean[] pgmX10DevicesStatus;
private Boolean ready;
private Boolean bypass;
private Boolean alarmActive;
private Boolean trouble;
private Boolean alertInMemory;
private String statusStr;
private String armMode;
private Boolean downloadSetupRequired;
private Long lastKeepAlive;
private byte[] updateSettings;
private String panelStatus;
private String alarmType;
private String troubleType;
private String[] eventLog;
private Map<Integer, Byte> updatedZoneNames;
private Map<Integer, Integer> updatedZoneInfos;
/**
* Constructor (default values)
*/
public PowermaxState(PowermaxPanelSettings panelSettings) {
zones = new PowermaxZoneState[panelSettings.getNbZones()];
for (int i = 0; i < panelSettings.getNbZones(); i++) {
zones[i] = new PowermaxZoneState();
}
pgmX10DevicesStatus = new Boolean[panelSettings.getNbPGMX10Devices()];
updatedZoneNames = new HashMap<>();
updatedZoneInfos = new HashMap<>();
}
/**
* Get the current mode (standard or Powerlink)
*
* @return true when the current mode is Powerlink; false when standard
*/
public Boolean isPowerlinkMode() {
return powerlinkMode;
}
/**
* Set the current mode (standard or Powerlink)
*
* @param powerlinkMode true for Powerlink or false for standard
*/
public void setPowerlinkMode(Boolean powerlinkMode) {
this.powerlinkMode = powerlinkMode;
}
/**
* Get whether or not the setup is being downloaded
*
* @return true when downloading the setup
*/
public Boolean isDownloadMode() {
return downloadMode;
}
/**
* Set whether or not the setup is being downloaded
*
* @param downloadMode true when downloading the setup
*/
public void setDownloadMode(Boolean downloadMode) {
this.downloadMode = downloadMode;
}
/**
* Get whether or not the zone sensor is tripped
*
* @param zone the index of the zone (first zone is index 1)
*
* @return true when the zone sensor is tripped
*/
public Boolean isSensorTripped(int zone) {
return ((zone < 1) || (zone > zones.length)) ? null : zones[zone - 1].isTripped();
}
/**
* Set whether or not the zone sensor is tripped
*
* @param zone the index of the zone (first zone is index 1)
* @param tripped true if tripped
*/
public void setSensorTripped(int zone, Boolean tripped) {
if ((zone >= 1) && (zone <= zones.length)) {
this.zones[zone - 1].setTripped(tripped);
}
}
/**
* Get the timestamp when the zone sensor was last tripped
*
* @param zone the index of the zone (first zone is index 1)
*
* @return the timestamp
*/
public Long getSensorLastTripped(int zone) {
return ((zone < 1) || (zone > zones.length)) ? null : zones[zone - 1].getLastTripped();
}
/**
* Set the timestamp when the zone sensor was last tripped
*
* @param zone the index of the zone (first zone is index 1)
* @param lastTripped the timestamp
*/
public void setSensorLastTripped(int zone, Long lastTripped) {
if ((zone >= 1) && (zone <= zones.length)) {
this.zones[zone - 1].setLastTripped(lastTripped);
}
}
/**
* Compare the sensor last trip with a given time
*
* @param zone the index of the zone (first zone is index 1)
* @param refTime the time in ms to compare with
*
* @return true if the sensor is tripped and last trip is older than the given time
*/
public boolean isLastTripBeforeTime(int zone, long refTime) {
return ((zone < 1) || (zone > zones.length)) ? false : zones[zone - 1].isLastTripBeforeTime(refTime);
}
/**
* Get whether or not the battery of the zone sensor is low
*
* @param zone the index of the zone (first zone is index 1)
*
* @return true when the battery is low
*/
public Boolean isSensorLowBattery(int zone) {
return ((zone < 1) || (zone > zones.length)) ? null : zones[zone - 1].isLowBattery();
}
/**
* Set whether or not the battery of the zone sensor is low
*
* @param zone the index of the zone (first zone is index 1)
* @param lowBattery true if battery is low
*/
public void setSensorLowBattery(int zone, Boolean lowBattery) {
if ((zone >= 1) && (zone <= zones.length)) {
this.zones[zone - 1].setLowBattery(lowBattery);
}
}
/**
* Get whether or not the zone sensor is bypassed
*
* @param zone the index of the zone (first zone is index 1)
*
* @return true if bypassed
*/
public Boolean isSensorBypassed(int zone) {
return ((zone < 1) || (zone > zones.length)) ? null : zones[zone - 1].isBypassed();
}
/**
* Set whether or not the zone sensor is bypassed
*
* @param zone the index of the zone (first zone is index 1)
* @param bypassed true if bypassed
*/
public void setSensorBypassed(int zone, Boolean bypassed) {
if ((zone >= 1) && (zone <= zones.length)) {
this.zones[zone - 1].setBypassed(bypassed);
}
}
/**
* Get whether or not the zone sensor is armed
*
* @param zone the index of the zone (first zone is index 1)
*
* @return true if armed
*/
public Boolean isSensorArmed(int zone) {
return ((zone < 1) || (zone > zones.length)) ? null : zones[zone - 1].isArmed();
}
/**
* Set whether or not the zone sensor is armed
*
* @param zone the index of the zone (first zone is index 1)
* @param armed true if armed
*/
public void setSensorArmed(int zone, Boolean armed) {
if ((zone >= 1) && (zone <= zones.length)) {
this.zones[zone - 1].setArmed(armed);
}
}
/**
* Get the status of a PGM or X10 device
*
* @param device the index of the PGM/X10 device (0 s for PGM; for X10 device is index 1)
*
* @return the status (true or false)
*/
public Boolean getPGMX10DeviceStatus(int device) {
return ((device < 0) || (device >= pgmX10DevicesStatus.length)) ? null : pgmX10DevicesStatus[device];
}
/**
* Set the status of a PGM or X10 device
*
* @param device the index of the PGM/X10 device (0 s for PGM; for X10 device is index 1)
* @param status true or false
*/
public void setPGMX10DeviceStatus(int device, Boolean status) {
if ((device >= 0) && (device < pgmX10DevicesStatus.length)) {
this.pgmX10DevicesStatus[device] = status;
}
}
/**
* Get whether or not the panel is ready
*
* @return true if ready
*/
public Boolean isReady() {
return ready;
}
/**
* Set whether or not the panel is ready
*
* @param ready true if ready
*/
public void setReady(Boolean ready) {
this.ready = ready;
}
/**
* Get whether or not at least one zone is bypassed
*
* @return true if at least one zone is bypassed
*/
public Boolean isBypass() {
return bypass;
}
/**
* Set whether or not at least one zone is bypassed
*
* @param bypass true if at least one zone is bypassed
*/
public void setBypass(Boolean bypass) {
this.bypass = bypass;
}
/**
* Get whether or not the alarm is active
*
* @return true if active
*/
public Boolean isAlarmActive() {
return alarmActive;
}
/**
* Set whether or not the alarm is active
*
* @param alarmActive true if the alarm is active
*/
public void setAlarmActive(Boolean alarmActive) {
this.alarmActive = alarmActive;
}
/**
* Get whether or not the panel is identifying a trouble
*
* @return true if the panel is identifying a trouble
*/
public Boolean isTrouble() {
return trouble;
}
/**
* Set whether or not the panel is identifying a trouble
*
* @param trouble true if trouble is identified
*/
public void setTrouble(Boolean trouble) {
this.trouble = trouble;
}
/**
* Get whether or not the panel has saved an alert in memory
*
* @return true if the panel has saved an alert in memory
*/
public Boolean isAlertInMemory() {
return alertInMemory;
}
/**
* Set whether or not the panel has saved an alert in memory
*
* @param alertInMemory true if an alert is saved in memory
*/
public void setAlertInMemory(Boolean alertInMemory) {
this.alertInMemory = alertInMemory;
}
/**
* Get the partition status
*
* @return the status as a short string
*/
public String getStatusStr() {
return statusStr;
}
/**
* Set the partition status
*
* @param statusStr the status as a short string
*/
public void setStatusStr(String statusStr) {
this.statusStr = statusStr;
}
/**
* Get the arming name
*
* @return the arming mode
*/
public String getArmMode() {
return armMode;
}
/**
* Set the arming name
*
* @param armMode the arming name
*/
public void setArmMode(String armMode) {
this.armMode = armMode;
}
/**
* Get whether or not the setup downloading is required
*
* @return true when downloading the setup is required
*/
public Boolean isDownloadSetupRequired() {
return downloadSetupRequired;
}
/**
* Set whether or not the setup downloading is required
*
* @param downloadSetupRequired true when downloading setup is required
*/
public void setDownloadSetupRequired(Boolean downloadSetupRequired) {
this.downloadSetupRequired = downloadSetupRequired;
}
/**
* Get the timestamp of the last received "keep alive" message
*
* @return the timestamp
*/
public Long getLastKeepAlive() {
return lastKeepAlive;
}
/**
* Set the timestamp of the last received "keep alive" message
*
* @param lastKeepAlive the timestamp
*/
public void setLastKeepAlive(Long lastKeepAlive) {
this.lastKeepAlive = lastKeepAlive;
}
/**
* Get the raw buffer containing all the settings
*
* @return the raw buffer as a table of bytes
*/
public byte[] getUpdateSettings() {
return updateSettings;
}
/**
* Set the raw buffer containing all the settings
*
* @param updateSettings the raw buffer as a table of bytes
*/
public void setUpdateSettings(byte[] updateSettings) {
this.updateSettings = updateSettings;
}
/**
* Get the panel status
*
* @return the panel status
*/
public String getPanelStatus() {
return panelStatus;
}
/**
* Set the panel status
*
* @param panelStatus the status as a short string
*/
public void setPanelStatus(String panelStatus) {
this.panelStatus = panelStatus;
}
/**
* Get the kind of the current alarm identified by the panel
*
* @return the kind of the current alarm; null if no alarm
*/
public String getAlarmType() {
return alarmType;
}
/**
* Set the kind of the current alarm identified by the panel
*
* @param alarmType the kind of alarm (set it to null if no alarm)
*/
public void setAlarmType(String alarmType) {
this.alarmType = alarmType;
}
/**
* Get the kind of the current trouble identified by the panel
*
* @return the kind of the current trouble; null if no trouble
*/
public String getTroubleType() {
return troubleType;
}
/**
* Set the kind of the current trouble identified by the panel
*
* @param troubleType the kind of trouble (set it to null if no trouble)
*/
public void setTroubleType(String troubleType) {
this.troubleType = troubleType;
}
/**
* Get the number of entries in the event log
*
* @return the number of entries
*/
public int getEventLogSize() {
return (eventLog == null) ? 0 : eventLog.length;
}
/**
* Set the number of entries in the event log
*
* @param size the number of entries
*/
public void setEventLogSize(int size) {
eventLog = new String[size];
}
/**
* Get one entry from the event logs
*
* @param index the entry index (1 for the most recent entry)
*
* @return the entry value (event)
*/
public String getEventLog(int index) {
return ((index < 1) || (index > getEventLogSize())) ? null : eventLog[index - 1];
}
/**
* Set one entry from the event logs
*
* @param index the entry index (1 for the most recent entry)
* @param event the entry value (event)
*/
public void setEventLog(int index, String event) {
if ((index >= 1) && (index <= getEventLogSize())) {
this.eventLog[index - 1] = event;
}
}
public Map<Integer, Byte> getUpdatedZoneNames() {
return updatedZoneNames;
}
public void updateZoneName(int zoneIdx, byte zoneNameIdx) {
this.updatedZoneNames.put(zoneIdx, zoneNameIdx);
}
public Map<Integer, Integer> getUpdatedZoneInfos() {
return updatedZoneInfos;
}
public void updateZoneInfo(int zoneIdx, int zoneInfo) {
this.updatedZoneInfos.put(zoneIdx, zoneInfo);
}
/**
* Get the panel mode
*
* @return either Download or Powerlink or Standard
*/
public String getPanelMode() {
String mode = null;
if (Boolean.TRUE.equals(downloadMode)) {
mode = "Download";
} else if (Boolean.TRUE.equals(powerlinkMode)) {
mode = "Powerlink";
} else if (Boolean.FALSE.equals(powerlinkMode)) {
mode = "Standard";
}
return mode;
}
/**
* Get whether or not the current arming mode is considered as armed
*
* @return true or false
*/
public Boolean isArmed() {
return isArmed(getArmMode());
}
/**
* Get whether or not an arming mode is considered as armed
*
* @param armMode the arming mode
*
* @return true or false; null if mode is unexpected
*/
private static Boolean isArmed(String armMode) {
Boolean result = null;
if (armMode != null) {
try {
PowermaxArmMode mode = PowermaxArmMode.fromName(armMode);
result = mode.isArmed();
} catch (IllegalArgumentException e) {
result = Boolean.FALSE;
}
}
return result;
}
/**
* Get the short description associated to the current arming mode
*
* @return the short description
*/
public String getShortArmMode() {
return getShortArmMode(getArmMode());
}
/**
* Get the short name associated to an arming mode
*
* @param armMode the arming mode
*
* @return the short name or null if mode is unexpected
*/
private static String getShortArmMode(String armMode) {
String result = null;
if (armMode != null) {
try {
PowermaxArmMode mode = PowermaxArmMode.fromName(armMode);
result = mode.getShortName();
} catch (IllegalArgumentException e) {
result = armMode;
}
}
return result;
}
/**
* Keep only data that are different from another state and reset all others data to undefined
*
* @param otherState the other state
*/
public void keepOnlyDifferencesWith(PowermaxState otherState) {
for (int i = 1; i <= zones.length; i++) {
if ((isSensorTripped(i) != null) && isSensorTripped(i).equals(otherState.isSensorTripped(i))) {
setSensorTripped(i, null);
}
if ((getSensorLastTripped(i) != null)
&& getSensorLastTripped(i).equals(otherState.getSensorLastTripped(i))) {
setSensorLastTripped(i, null);
}
if ((isSensorLowBattery(i) != null) && isSensorLowBattery(i).equals(otherState.isSensorLowBattery(i))) {
setSensorLowBattery(i, null);
}
if ((isSensorBypassed(i) != null) && isSensorBypassed(i).equals(otherState.isSensorBypassed(i))) {
setSensorBypassed(i, null);
}
if ((isSensorArmed(i) != null) && isSensorArmed(i).equals(otherState.isSensorArmed(i))) {
setSensorArmed(i, null);
}
}
for (int i = 0; i < pgmX10DevicesStatus.length; i++) {
if ((getPGMX10DeviceStatus(i) != null)
&& getPGMX10DeviceStatus(i).equals(otherState.getPGMX10DeviceStatus(i))) {
setPGMX10DeviceStatus(i, null);
}
}
if ((ready != null) && ready.equals(otherState.isReady())) {
ready = null;
}
if ((bypass != null) && bypass.equals(otherState.isBypass())) {
bypass = null;
}
if ((alarmActive != null) && alarmActive.equals(otherState.isAlarmActive())) {
alarmActive = null;
}
if ((trouble != null) && trouble.equals(otherState.isTrouble())) {
trouble = null;
}
if ((alertInMemory != null) && alertInMemory.equals(otherState.isAlertInMemory())) {
alertInMemory = null;
}
if ((statusStr != null) && statusStr.equals(otherState.getStatusStr())) {
statusStr = null;
}
if ((armMode != null) && armMode.equals(otherState.getArmMode())) {
armMode = null;
}
if ((lastKeepAlive != null) && lastKeepAlive.equals(otherState.getLastKeepAlive())) {
lastKeepAlive = null;
}
if ((panelStatus != null) && panelStatus.equals(otherState.getPanelStatus())) {
panelStatus = null;
}
if ((alarmType != null) && alarmType.equals(otherState.getAlarmType())) {
alarmType = null;
}
if ((troubleType != null) && troubleType.equals(otherState.getTroubleType())) {
troubleType = null;
}
}
/**
* Update (override) the current state data from another state, ignoring in this other state
* the undefined data
*
* @param update the other state to consider for the update
*/
public void merge(PowermaxState update) {
if (update.isPowerlinkMode() != null) {
powerlinkMode = update.isPowerlinkMode();
}
if (update.isDownloadMode() != null) {
downloadMode = update.isDownloadMode();
}
for (int i = 1; i <= zones.length; i++) {
if (update.isSensorTripped(i) != null) {
setSensorTripped(i, update.isSensorTripped(i));
}
if (update.getSensorLastTripped(i) != null) {
setSensorLastTripped(i, update.getSensorLastTripped(i));
}
if (update.isSensorLowBattery(i) != null) {
setSensorLowBattery(i, update.isSensorLowBattery(i));
}
if (update.isSensorBypassed(i) != null) {
setSensorBypassed(i, update.isSensorBypassed(i));
}
if (update.isSensorArmed(i) != null) {
setSensorArmed(i, update.isSensorArmed(i));
}
}
for (int i = 0; i < pgmX10DevicesStatus.length; i++) {
if (update.getPGMX10DeviceStatus(i) != null) {
setPGMX10DeviceStatus(i, update.getPGMX10DeviceStatus(i));
}
}
if (update.isReady() != null) {
ready = update.isReady();
}
if (update.isBypass() != null) {
bypass = update.isBypass();
}
if (update.isAlarmActive() != null) {
alarmActive = update.isAlarmActive();
}
if (update.isTrouble() != null) {
trouble = update.isTrouble();
}
if (update.isAlertInMemory() != null) {
alertInMemory = update.isAlertInMemory();
}
if (update.getStatusStr() != null) {
statusStr = update.getStatusStr();
}
if (update.getArmMode() != null) {
armMode = update.getArmMode();
}
if (update.getLastKeepAlive() != null) {
lastKeepAlive = update.getLastKeepAlive();
}
if (update.getPanelStatus() != null) {
panelStatus = update.getPanelStatus();
}
if (update.getAlarmType() != null) {
alarmType = update.getAlarmType();
}
if (update.getTroubleType() != null) {
troubleType = update.getTroubleType();
}
if (update.getEventLogSize() > getEventLogSize()) {
setEventLogSize(update.getEventLogSize());
}
for (int i = 1; i <= getEventLogSize(); i++) {
if (update.getEventLog(i) != null) {
setEventLog(i, update.getEventLog(i));
}
}
}
@Override
public String toString() {
String str = "";
if (powerlinkMode != null) {
str += "\n - powerlink mode = " + (powerlinkMode ? "yes" : "no");
}
if (downloadMode != null) {
str += "\n - download mode = " + (downloadMode ? "yes" : "no");
}
for (int i = 1; i <= zones.length; i++) {
if (isSensorTripped(i) != null) {
str += String.format("\n - sensor zone %d %s", i, isSensorTripped(i) ? "tripped" : "untripped");
}
if (getSensorLastTripped(i) != null) {
str += String.format("\n - sensor zone %d last trip %d", i, getSensorLastTripped(i));
}
if (isSensorLowBattery(i) != null) {
str += String.format("\n - sensor zone %d %s", i, isSensorLowBattery(i) ? "low battery" : "battery ok");
}
if (isSensorBypassed(i) != null) {
str += String.format("\n - sensor zone %d %sbypassed", i, isSensorBypassed(i) ? "" : "not ");
}
if (isSensorArmed(i) != null) {
str += String.format("\n - sensor zone %d %s", i, isSensorArmed(i) ? "armed" : "disarmed");
}
}
for (int i = 0; i < pgmX10DevicesStatus.length; i++) {
if (getPGMX10DeviceStatus(i) != null) {
str += String.format("\n - %s status = %s", (i == 0) ? "PGM device" : String.format("X10 device %d", i),
getPGMX10DeviceStatus(i) ? "ON" : "OFF");
}
}
if (ready != null) {
str += "\n - ready = " + (ready ? "yes" : "no");
}
if (bypass != null) {
str += "\n - bypass = " + (bypass ? "yes" : "no");
}
if (alarmActive != null) {
str += "\n - alarm active = " + (alarmActive ? "yes" : "no");
}
if (trouble != null) {
str += "\n - trouble = " + (trouble ? "yes" : "no");
}
if (alertInMemory != null) {
str += "\n - alert in memory = " + (alertInMemory ? "yes" : "no");
}
if (statusStr != null) {
str += "\n - status = " + statusStr;
}
if (armMode != null) {
str += "\n - arm mode = " + armMode;
}
if (lastKeepAlive != null) {
str += "\n - last keep alive = " + lastKeepAlive;
}
if (panelStatus != null) {
str += "\n - panel status = " + panelStatus;
}
if (alarmType != null) {
str += "\n - alarm type = " + alarmType;
}
if (troubleType != null) {
str += "\n - trouble type = " + troubleType;
}
for (int i = 1; i <= getEventLogSize(); i++) {
if (getEventLog(i) != null) {
str += "\n - event log " + i + " = " + getEventLog(i);
}
}
return str;
}
}

View File

@@ -0,0 +1,38 @@
/**
* 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.powermax.internal.state;
import java.util.EventObject;
/**
* Event for state received from the Visonic alarm panel
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxStateEvent extends EventObject {
private static final long serialVersionUID = 1L;
private PowermaxState state;
public PowermaxStateEvent(Object source, PowermaxState state) {
super(source);
this.state = state;
}
/**
* @return the state object built from the received message
*/
public PowermaxState getState() {
return state;
}
}

View File

@@ -0,0 +1,31 @@
/**
* 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.powermax.internal.state;
import java.util.EventListener;
import java.util.EventObject;
/**
* Powermax Alarm state Listener interface. Handles Powermax Alarm state events
*
* @author Laurent Garnier - Initial contribution
*/
public interface PowermaxStateEventListener extends EventListener {
/**
* Event handler method for Powermax Alarm state events
*
* @param event the event object
*/
public void onNewStateEvent(EventObject event);
}

View File

@@ -0,0 +1,43 @@
/**
* 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.powermax.internal.state;
/**
* A class to store the settings of an X10 device
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxX10Settings {
private String name;
private boolean enabled;
public PowermaxX10Settings(String name, boolean enabled) {
this.name = name;
this.enabled = enabled;
}
/**
* @return the name of the X10 device
*/
public String getName() {
return name;
}
/**
* @return true if the X10 device is enabled; false if not
*/
public boolean isEnabled() {
return enabled;
}
}

View File

@@ -0,0 +1,104 @@
/**
* 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.powermax.internal.state;
/**
* All panel zone names
*
* @author Laurent Garnier - Initial contribution
*/
public enum PowermaxZoneName {
ZONE_0(0, "Attic"),
ZONE_1(1, "Back door"),
ZONE_2(2, "Basement"),
ZONE_3(3, "Bathroom"),
ZONE_4(4, "Bedroom"),
ZONE_5(5, "Child room"),
ZONE_6(6, "Closet"),
ZONE_7(7, "Den"),
ZONE_8(8, "Dining room"),
ZONE_9(9, "Downstairs"),
ZONE_10(10, "Emergency"),
ZONE_11(11, "Fire"),
ZONE_12(12, "Front door"),
ZONE_13(13, "Garage"),
ZONE_14(14, "Garage door"),
ZONE_15(15, "Guest room"),
ZONE_16(16, "Hall"),
ZONE_17(17, "Kitchen"),
ZONE_18(18, "Laundry room"),
ZONE_19(19, "Living room"),
ZONE_20(20, "Master bathroom"),
ZONE_21(21, "Master bedroom"),
ZONE_22(22, "Office"),
ZONE_23(23, "Upstairs"),
ZONE_24(24, "Utility room"),
ZONE_25(25, "Yard"),
ZONE_26(26, "Custom 1"),
ZONE_27(27, "Custom 2"),
ZONE_28(28, "Custom 3"),
ZONE_29(29, "Custom 4"),
ZONE_30(30, "Custom 5"),
ZONE_31(31, "Not Installed");
private int id;
private String name;
private PowermaxZoneName(int id, String name) {
this.id = id;
this.name = name;
}
/**
* @return the zone id
*/
public int getId() {
return id;
}
/**
* @return the zone name
*/
public String getName() {
return name;
}
/**
* Update the zone name
*
* @param name the new zone name
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the ENUM value from its id
*
* @param id the zone id
*
* @return the corresponding ENUM value
*
* @throws IllegalArgumentException if no ENUM value corresponds to this id
*/
public static PowermaxZoneName fromId(int id) throws IllegalArgumentException {
for (PowermaxZoneName zone : PowermaxZoneName.values()) {
if (zone.getId() == id) {
return zone;
}
}
throw new IllegalArgumentException("Invalid id: " + id);
}
}

View File

@@ -0,0 +1,121 @@
/**
* 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.powermax.internal.state;
/**
* A class to store the settings of a zone
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxZoneSettings {
private static final String[] ZONE_TYPES = { "Non-Alarm", "Emergency", "Flood", "Gas", "Delay 1", "Delay 2",
"Interior-Follow", "Perimeter", "Perimeter-Follow", "24 Hours Silent", "24 Hours Audible", "Fire",
"Interior", "Home Delay", "Temperature", "Outdoor" };
private static final String[] ZONE_CHIMES = { "Off", "Melody", "Zone" };
private String name;
private String type;
private String chime;
private String sensorType;
private boolean[] partitions;
private boolean alwaysInAlarm;
public PowermaxZoneSettings(String name, byte type, byte chime, String sensorType, boolean[] partitions) {
this.name = name;
this.type = ((type & 0x000000FF) < ZONE_TYPES.length) ? ZONE_TYPES[type & 0x000000FF] : null;
this.chime = ((chime & 0x000000FF) < ZONE_CHIMES.length) ? ZONE_CHIMES[chime & 0x000000FF] : null;
this.sensorType = sensorType;
this.partitions = partitions;
this.alwaysInAlarm = ((type == 2) || (type == 3) || (type == 9) || (type == 10) || (type == 11)
|| (type == 14));
}
/**
* @return the zone name
*/
public String getName() {
return (name == null) ? "Unknown" : name;
}
/**
* Set the zone name
*
* @param name the zone name
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the zone type
*/
public String getType() {
return (type == null) ? "Unknown" : type;
}
/**
* Set the zone type
*
* @param type the zone type as an internal code
*/
public void setType(byte type) {
this.type = ((type & 0x000000FF) < ZONE_TYPES.length) ? ZONE_TYPES[type & 0x000000FF] : null;
this.alwaysInAlarm = ((type == 2) || (type == 3) || (type == 9) || (type == 10) || (type == 11)
|| (type == 14));
}
public String getChime() {
return (chime == null) ? "Unknown" : chime;
}
/**
* @return the sensor type of this zone
*/
public String getSensorType() {
return (sensorType == null) ? "Unknown" : sensorType;
}
/**
* Set the sensor type of this zone
*
* @param sensorType the sensor type
*/
public void setSensorType(String sensorType) {
this.sensorType = sensorType;
}
/**
* @return true if the sensor type of this zone is a motion sensor
*/
public boolean isMotionSensor() {
return PowermaxSensorType.MOTION_SENSOR_1.getLabel().equalsIgnoreCase(getSensorType());
}
/**
* @param number the partition number (first partition is number 1)
*
* @return true if the zone is attached to this partition; false if not
*/
public boolean isInPartition(int number) {
return ((number <= 0) || (number > partitions.length)) ? false : partitions[number - 1];
}
/**
* @return true if the zone type is always in alarm; false if not
*/
public boolean isAlwaysInAlarm() {
return alwaysInAlarm;
}
}

View File

@@ -0,0 +1,79 @@
/**
* 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.powermax.internal.state;
/**
* A class to store the state of a zone
*
* @author Laurent Garnier - Initial contribution
*/
public class PowermaxZoneState {
private Boolean tripped;
private Long lastTripped;
private Boolean lowBattery;
private Boolean bypassed;
private Boolean armed;
public PowermaxZoneState() {
tripped = null;
lastTripped = null;
lowBattery = null;
bypassed = null;
armed = null;
}
public Boolean isTripped() {
return tripped;
}
public void setTripped(Boolean tripped) {
this.tripped = tripped;
}
public Long getLastTripped() {
return lastTripped;
}
public void setLastTripped(Long lastTripped) {
this.lastTripped = lastTripped;
}
public boolean isLastTripBeforeTime(long refTime) {
return isTripped() == Boolean.TRUE && getLastTripped() != null && getLastTripped() < refTime;
}
public Boolean isLowBattery() {
return lowBattery;
}
public void setLowBattery(Boolean lowBattery) {
this.lowBattery = lowBattery;
}
public Boolean isBypassed() {
return bypassed;
}
public void setBypassed(Boolean bypassed) {
this.bypassed = bypassed;
}
public Boolean isArmed() {
return armed;
}
public void setArmed(Boolean armed) {
this.armed = armed;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="powermax" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Powermax Binding</name>
<description>The Powermax binding interfaces with Visonic PowerMax and PowerMaster alarm panel series.</description>
<author>Laurent Garnier</author>
</binding:binding>

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="powermax"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="mode" advanced="true">
<item-type>String</item-type>
<label>System Mode</label>
<description>Current mode can be Standard, Powerlink or Download</description>
<state readOnly="true" pattern="%s">
<options>
<option value="Download">Download</option>
<option value="Powerlink">Powerlink</option>
<option value="Standard">Standard</option>
</options>
</state>
</channel-type>
<channel-type id="trouble">
<item-type>Switch</item-type>
<label>Trouble Detected</label>
<description>Whether or not a trouble is detected</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="alert_in_memory" advanced="true">
<item-type>Switch</item-type>
<label>Alert in Memory</label>
<description>Whether or not an alert is saved in system memory</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="system_status">
<item-type>String</item-type>
<label>System Status</label>
<description>A short status summary of the system</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="ready" advanced="true">
<item-type>Switch</item-type>
<label>System Ready</label>
<description>Whether or not the system is ready for arming</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="with_zones_bypassed" advanced="true">
<item-type>Switch</item-type>
<label>With Zones Bypassed</label>
<description>Whether or not at least one zone is bypassed</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="alarm_active">
<item-type>Switch</item-type>
<label>Alarm Active</label>
<description>Whether or not an alarm is active</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="system_armed">
<item-type>Switch</item-type>
<label>System Armed</label>
<description>Whether or not the system is armed</description>
</channel-type>
<channel-type id="arm_mode">
<item-type>String</item-type>
<label>System Arm Mode</label>
<state readOnly="false" pattern="%s">
<options>
<option value="Disarmed">Disarmed</option>
<option value="Stay">Armed Home</option>
<option value="Armed">Armed Away</option>
<option value="StayInstant">Armed Home Instant</option>
<option value="ArmedInstant">Armed Away Instant</option>
<option value="Night">Armed Night</option>
<option value="NightInstant">Armed Night Instant</option>
<option value="EntryDelay">Entry Delay</option>
<option value="ExitDelay">Exit Delay</option>
<option value="NotReady">Not Ready</option>
<option value="Ready">Ready</option>
<option value="UserTest">User Test</option>
<option value="Force">Bypass</option>
</options>
</state>
</channel-type>
<channel-type id="tripped">
<item-type>Contact</item-type>
<label>Zone Tripped</label>
<description>Whether or not the zone is tripped</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="last_trip">
<item-type>DateTime</item-type>
<label>Zone Last Trip</label>
<description>Timestamp when the zone was last tripped</description>
<state readOnly="true" pattern="%1$tH:%1$tM"></state>
</channel-type>
<channel-type id="bypassed">
<item-type>Switch</item-type>
<label>Zone Bypassed</label>
<description>Whether or not the zone is bypassed</description>
</channel-type>
<channel-type id="armed">
<item-type>Switch</item-type>
<label>Zone Armed</label>
<description>Whether or not the zone is armed</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="pgm_status" advanced="true">
<item-type>Switch</item-type>
<label>PGM Status</label>
</channel-type>
<channel-type id="x10_status">
<item-type>String</item-type>
<label>X10 Device Status</label>
<state readOnly="false" pattern="%s">
<options>
<option value="ON">On</option>
<option value="OFF">Off</option>
<option value="DIM">Dim</option>
<option value="BRIGHT">Bright</option>
</options>
</state>
</channel-type>
<channel-type id="event_log" advanced="true">
<item-type>String</item-type>
<label>Event Log Entry</label>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="update_event_logs" advanced="true">
<item-type>Switch</item-type>
<label>Update Event Logs</label>
<description>Switch command to update the event logs</description>
</channel-type>
<channel-type id="download_setup" advanced="true">
<item-type>Switch</item-type>
<label>Download Setup</label>
<description>Switch command to download the setup</description>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="powermax"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="ip">
<label>IP Connection</label>
<description>This bridge represents the IP connection to the alarm system.</description>
<channels>
<channel id="system_status" typeId="system_status"/>
<channel id="system_armed" typeId="system_armed"/>
<channel id="arm_mode" typeId="arm_mode"/>
<channel id="alarm_active" typeId="alarm_active"/>
<channel id="ready" typeId="ready"/>
<channel id="with_zones_bypassed" typeId="with_zones_bypassed"/>
<channel id="trouble" typeId="trouble"/>
<channel id="alert_in_memory" typeId="alert_in_memory"/>
<channel id="pgm_status" typeId="pgm_status"/>
<channel id="mode" typeId="mode"/>
<channel id="event_log_1" typeId="event_log"/>
<channel id="event_log_2" typeId="event_log"/>
<channel id="event_log_3" typeId="event_log"/>
<channel id="event_log_4" typeId="event_log"/>
<channel id="event_log_5" typeId="event_log"/>
<channel id="event_log_6" typeId="event_log"/>
<channel id="event_log_7" typeId="event_log"/>
<channel id="event_log_8" typeId="event_log"/>
<channel id="event_log_9" typeId="event_log"/>
<channel id="event_log_10" typeId="event_log"/>
<channel id="update_event_logs" typeId="update_event_logs"/>
<channel id="download_setup" typeId="download_setup"/>
</channels>
<properties>
<property name="vendor">Visonic</property>
</properties>
<config-description>
<parameter name="ip" type="text" required="true">
<context>network-address</context>
<label>IP Address</label>
<description>The IP address to use for connecting to the Ethernet interface of the alarm system.</description>
</parameter>
<parameter name="tcpPort" type="integer" min="1" max="65535" required="true">
<label>TCP Port</label>
<description>The TCP port to use for connecting to the Ethernet interface of the alarm system.</description>
</parameter>
<parameter name="motionOffDelay" type="integer" min="1" unit="min" required="false">
<label>Motion Reset Delay</label>
<description>The delay in minutes to reset a motion detection.</description>
<default>3</default>
</parameter>
<parameter name="allowArming" type="boolean" required="false">
<label>Allow Arming</label>
<description>Enable or disable arming the alarm system from openHAB.</description>
<default>false</default>
</parameter>
<parameter name="allowDisarming" type="boolean" required="false">
<label>Allow Disarming</label>
<description>Enable or disable disarming the alarm system from openHAB.</description>
<default>false</default>
</parameter>
<parameter name="pinCode" type="text" required="false">
<context>password</context>
<label>PIN Code</label>
<description>The PIN code to use for arming/disarming the alarm system from openHAB. Not required except when
Powerlink mode cannot be used.</description>
<advanced>true</advanced>
</parameter>
<parameter name="forceStandardMode" type="boolean" required="false">
<label>Force Standard Mode</label>
<description>Force the standard mode rather than trying using the Powerlink mode.</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
<parameter name="panelType" type="text" required="false">
<label>Panel Type</label>
<description>Define the panel type. Only required when forcing the standard mode.</description>
<limitToOptions>true</limitToOptions>
<options>
<option value="PowerMax">PowerMax</option>
<option value="PowerMax+">PowerMax+</option>
<option value="PowerMaxPro">PowerMax Pro</option>
<option value="PowerMaxComplete">PowerMax Complete</option>
<option value="PowerMaxProPart">PowerMax Pro Part</option>
<option value="PowerMaxCompletePart">PowerMax Complete Part</option>
<option value="PowerMaxExpress">PowerMax Express</option>
<option value="PowerMaster10">PowerMaster 10</option>
<option value="PowerMaster30">PowerMaster 30</option>
</options>
<default>PowerMaxPro</default>
<advanced>true</advanced>
</parameter>
<parameter name="autoSyncTime" type="boolean" required="false">
<label>Sync Time</label>
<description>Automatic sync time at openHAB startup.</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="powermax"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="serial">
<label>Serial Connection</label>
<description>This bridge represents the serial connection to the alarm system.</description>
<channels>
<channel id="system_status" typeId="system_status"/>
<channel id="system_armed" typeId="system_armed"/>
<channel id="arm_mode" typeId="arm_mode"/>
<channel id="alarm_active" typeId="alarm_active"/>
<channel id="ready" typeId="ready"/>
<channel id="with_zones_bypassed" typeId="with_zones_bypassed"/>
<channel id="trouble" typeId="trouble"/>
<channel id="alert_in_memory" typeId="alert_in_memory"/>
<channel id="pgm_status" typeId="pgm_status"/>
<channel id="mode" typeId="mode"/>
<channel id="event_log_1" typeId="event_log"/>
<channel id="event_log_2" typeId="event_log"/>
<channel id="event_log_3" typeId="event_log"/>
<channel id="event_log_4" typeId="event_log"/>
<channel id="event_log_5" typeId="event_log"/>
<channel id="event_log_6" typeId="event_log"/>
<channel id="event_log_7" typeId="event_log"/>
<channel id="event_log_8" typeId="event_log"/>
<channel id="event_log_9" typeId="event_log"/>
<channel id="event_log_10" typeId="event_log"/>
<channel id="update_event_logs" typeId="update_event_logs"/>
<channel id="download_setup" typeId="download_setup"/>
</channels>
<properties>
<property name="vendor">Visonic</property>
</properties>
<config-description>
<parameter name="serialPort" type="text" required="true">
<context>serial-port</context>
<limitToOptions>false</limitToOptions>
<label>Serial Port</label>
<description>The serial port to use for connecting to the serial interface of the alarm system e.g. COM1 for Windows
and /dev/ttyS0 or /dev/ttyUSB0 for Linux.</description>
</parameter>
<parameter name="motionOffDelay" type="integer" min="1" unit="min" required="false">
<label>Motion Reset Delay</label>
<description>The delay in minutes to reset a motion detection.</description>
<default>3</default>
</parameter>
<parameter name="allowArming" type="boolean" required="false">
<label>Allow Arming</label>
<description>Enable or disable arming the alarm system from openHAB.</description>
<default>false</default>
</parameter>
<parameter name="allowDisarming" type="boolean" required="false">
<label>Allow Disarming</label>
<description>Enable or disable disarming the alarm system from openHAB.</description>
<default>false</default>
</parameter>
<parameter name="pinCode" type="text" required="false">
<context>password</context>
<label>PIN Code</label>
<description>The PIN code to use for arming/disarming the alarm system from openHAB. Not required except when
Powerlink mode cannot be used.</description>
<advanced>true</advanced>
</parameter>
<parameter name="forceStandardMode" type="boolean" required="false">
<label>Force Standard Mode</label>
<description>Force the standard mode rather than trying using the Powerlink mode.</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
<parameter name="panelType" type="text" required="false">
<label>Panel Type</label>
<description>Define the panel type. Only required when forcing the standard mode.</description>
<limitToOptions>true</limitToOptions>
<options>
<option value="PowerMax">PowerMax</option>
<option value="PowerMax+">PowerMax+</option>
<option value="PowerMaxPro">PowerMax Pro</option>
<option value="PowerMaxComplete">PowerMax Complete</option>
<option value="PowerMaxProPart">PowerMax Pro Part</option>
<option value="PowerMaxCompletePart">PowerMax Complete Part</option>
<option value="PowerMaxExpress">PowerMax Express</option>
<option value="PowerMaster10">PowerMaster 10</option>
<option value="PowerMaster30">PowerMaster 30</option>
</options>
<default>PowerMaxPro</default>
<advanced>true</advanced>
</parameter>
<parameter name="autoSyncTime" type="boolean" required="false">
<label>Sync Time</label>
<description>Automatic sync time at openHAB startup.</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="powermax"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="x10">
<supported-bridge-type-refs>
<bridge-type-ref id="serial"/>
<bridge-type-ref id="ip"/>
</supported-bridge-type-refs>
<label>X10 Device</label>
<description>This thing represents a physical X10 device.</description>
<channels>
<channel id="x10_status" typeId="x10_status"/>
</channels>
<config-description>
<parameter name="deviceNumber" type="integer" min="1" max="16" required="true">
<label>Device Number</label>
<description>The device number.</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="powermax"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="zone">
<supported-bridge-type-refs>
<bridge-type-ref id="serial"/>
<bridge-type-ref id="ip"/>
</supported-bridge-type-refs>
<label>Alarm Zone</label>
<description>This thing represents a physical device such as a door, window or a motion sensor.</description>
<channels>
<channel id="tripped" typeId="tripped"/>
<channel id="armed" typeId="armed"/>
<channel id="last_trip" typeId="last_trip"/>
<channel id="low_battery" typeId="system.low-battery"/>
<channel id="bypassed" typeId="bypassed"/>
</channels>
<config-description>
<parameter name="zoneNumber" type="integer" min="1" max="64" required="true">
<label>Zone Number</label>
<description>The zone number.</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>