added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user