Skip to content

Commit

Permalink
[paradoxalarm] Limit maximum zones and partitions with new parameters…
Browse files Browse the repository at this point in the history
… and more channels added to partition thing (openhab#6792)

* Implementation of maxZones and maxPartitions parameters

* Reworked creation of EvoCommunicator to use builder pattern
* Added support for additional, non-mandatory parameters maxZones and
maxPartitions which limit the maximum amount of zones and partitions
that are used during refresh.
* Changed Factory to create builder instead of communicator
* Renamed classes to represent properly the new object creation design

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
  • Loading branch information
theater authored and andrewfg committed Aug 31, 2020
1 parent fffc640 commit e1e68f6
Show file tree
Hide file tree
Showing 15 changed files with 460 additions and 193 deletions.
49 changes: 44 additions & 5 deletions bundles/org.openhab.binding.paradoxalarm/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Paradox Alarm System binding

This binding is intended to provide basic support for Paradox Alarm system.

Currently the binding does not support active communication, i.e. you cannot change states (arming, disarming). The intention is to use it only for monitoring of your security system.
With the power of openHAB this binding can be used for complex decision rules combining motion/magnetic sensor or whole partitions states with different scenarios.


Examples:

* All partitions are armed, therefore there is no one at home.
Expand All @@ -25,7 +24,7 @@ Currently binding supports the following panels: EVO192, EVO48(not tested), EVO9

## Things configuration

### IP150 parameters
### IP150 bridge parameters

| Parameter | Description |
|-------------------|----------------------------------------|
Expand All @@ -34,10 +33,12 @@ Currently binding supports the following panels: EVO192, EVO48(not tested), EVO9
| pcPassword | The code 3012 setting. Default value is 0000.|
| ipAddress | IP address of your IP150.|
| port | The port used for data communication. Default value is 10000.|
| panelType | Not mandatory. Will be used if discovery does not identify the panel. Otherwise provide EVO48, EVO96, EVO192, etc...|
| panelType | Optional parameter. Will be used if discovery does not identify the panel. Otherwise provide EVO48, EVO96, EVO192, etc...|
| reconnectWaitTime | Value is in seconds. The time to wait before a reconnect occurs after socket timeout.|
| maxPartitions | Optional parameter which sets maximum partitions to use during refresh. If not set, maximum allowed amount from panelType will be used.|
| maxZones | Optional parameter which sets maximum zones to use during refresh. If not set, maximum allowed amount from panelType will be used.|

### IP150 channels
### IP150 bridge channels

| Channel | Description |
|---------------------|------------------------------------------------|
Expand All @@ -55,6 +56,36 @@ Currently binding supports the following panels: EVO192, EVO48(not tested), EVO9
|--------|------------------------------------------------------------------------------------|
| id | The numeric ID of the zone/partition |

### Partition channels:

| Channel | Type | Description |
|--------------------------|---------|---------------------------------------------------------------------------------------------|
| partitionLabel | String | Label of partition inside Paradox configuration |
| state | String |State of partition (armed, disarmed, in alarm) |
| additionalState | String | This used to be a channel where all different states were consolidated as semi-colon separated string. With implementation of each state as channel additional states should be no longer used. (deprecated channel) |
| readyToArm | Switch | Partition is Ready to arm |
| inExitDelay | Switch | Partition is in Exit delay |
| inEntryDelay | Switch | Partition in Entry Delay |
| inTrouble | Switch | Partition has trouble |
| alarmInMemory | Switch | Partition has alarm in memory |
| zoneBypass | Switch | Partition is in Zone Bypass |
| zoneInTamperTrouble | Switch | Partition is in Tamper Trouble |
| zoneInLowBatteryTrouble | Switch | Partition has zone in Low Battery Trouble |
| zoneInFireLoopTrouble | Switch | Partition has zone in Fire Loop Trouble |
| zoneInSupervisionTrouble | Switch | Partition has zone in Supervision Trouble |
| stayInstantReady | Switch | Partition is in state Stay Instant Ready |
| forceReady | Switch | Partition is in state Force Ready |
| bypassReady | Switch | Partition is in state Bypass Ready |
| inhibitReady | Switch | Partition is in state Inhibit Ready |
| allZonesClosed | Contact | All zones in partition are currently closed |

### Zone channels:

| Channel | Type | Description |
|-----------------|---------|--------------------------------------------------------------------------------|
| zoneLabel | String | Label of zone inside Paradox configuration |
| openedState | Contact | Zone opened / closed |
| tamperedState | Switch | Zone is tampered / not tampered |
## Example things configuration

```java
Expand Down Expand Up @@ -136,3 +167,11 @@ Currently binding supports the following panels: EVO192, EVO48(not tested), EVO9
}
}
```
## Acknowledgements
This binding would not be possible without the reverse engineering of the byte level protocol and the development by other authors in python, C# and other languages. Many thanks to the following authors and their respective github repositories for their development that helped in creating this binding:
João Paulo Barraca - https://github.com/ParadoxAlarmInterface/pai
Jean Henning - repository not available
Tertuish - https://github.com/Tertiush/ParadoxIP150v2 / https://github.com/Tertiush/ParadoxIP150
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
import org.openhab.binding.paradoxalarm.internal.communication.messages.RamRequestPayload;
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxException;
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxRuntimeException;
import org.openhab.binding.paradoxalarm.internal.model.EntityType;
import org.openhab.binding.paradoxalarm.internal.model.PanelType;
import org.openhab.binding.paradoxalarm.internal.model.ZoneStateFlags;
Expand All @@ -49,15 +50,20 @@ public class EvoCommunicator extends GenericCommunicator implements IParadoxComm

private MemoryMap memoryMap;

// Map of EntityTypes (Key:EntityType (Zone, Partition), Value: Map<entity IDs, Label>
private Map<EntityType, Map<Integer, String>> entityLabelsMap = new HashMap<>();

private PanelType panelType = PanelType.UNKNOWN;
private Integer maxPartitions;
private Integer maxZones;

public EvoCommunicator(String ipAddress, int tcpPort, String ip150Password, String pcPassword,
ScheduledExecutorService scheduler, PanelType panelType) throws UnknownHostException, IOException {
private EvoCommunicator(String ipAddress, int tcpPort, String ip150Password, String pcPassword,
ScheduledExecutorService scheduler, PanelType panelType, Integer maxPartitions, Integer maxZones)
throws UnknownHostException, IOException {
super(ipAddress, tcpPort, ip150Password, pcPassword, scheduler);
this.panelType = panelType;
this.maxPartitions = maxPartitions;
this.maxZones = maxZones;
logger.debug("PanelType={}, max partitions={}, max Zones={}", panelType, maxPartitions, maxZones);
initializeMemoryMap();
}

Expand Down Expand Up @@ -106,7 +112,7 @@ private void retrievePartitionLabel(int partitionNo) {
try {
IPPacketPayload payload = new EpromRequestPayload(address, labelLength);
ParadoxIPPacket readEpromIPPacket = new ParadoxIPPacket(payload)
.setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST).setUnknown0((byte) 0x14);
.setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST).setUnknown0((byte) 0x14);

IRequest epromRequest = new EpromRequest(partitionNo, EntityType.PARTITION, readEpromIPPacket);
submitRequest(epromRequest);
Expand Down Expand Up @@ -229,8 +235,8 @@ private void readRAM(int blockNo) {
submitRequest(ramRequest);
} catch (ParadoxException e) {
logger.debug(
"Unable to create request payload from provided bytes to read. blockNo={}, bytes to read={}. Exception={}",
blockNo, RAM_BLOCK_SIZE, e.getMessage());
"Unable to create request payload from provided bytes to read. blockNo={}, bytes to read={}. Exception={}",
blockNo, RAM_BLOCK_SIZE, e.getMessage());
}
}

Expand Down Expand Up @@ -284,11 +290,109 @@ public void initializeData() {
}

private void initializeEpromData() {
for (int i = 0; i < panelType.getPartitions(); i++) {
for (int i = 0; i < maxPartitions; i++) {
retrievePartitionLabel(i);
}
for (int i = 0; i < panelType.getZones(); i++) {
for (int i = 0; i < maxZones; i++) {
retrieveZoneLabel(i);
}
}

public static class EvoCommunicatorBuilder implements ICommunicatorBuilder {

private final Logger logger = LoggerFactory.getLogger(EvoCommunicatorBuilder.class);

// Mandatory parameters
private PanelType panelType;
private String ipAddress;
private String ip150Password;
private ScheduledExecutorService scheduler;

// Non mandatory or with predefined values
private Integer maxPartitions;
private Integer maxZones;
private int tcpPort = 10000;
private String pcPassword = "0000";

EvoCommunicatorBuilder(PanelType panelType) {
this.panelType = panelType;
}

@Override
public IParadoxCommunicator build() {
if (panelType != PanelType.EVO48 && panelType != PanelType.EVO96 && panelType != PanelType.EVO192) {
throw new ParadoxRuntimeException("Unknown or unsupported panel type. Type=" + panelType);
}

if (ipAddress == null || ipAddress.isEmpty()) {
throw new ParadoxRuntimeException("IP address cannot be empty !");
}

if (ip150Password == null || ip150Password.isEmpty()) {
throw new ParadoxRuntimeException("Password for IP150 cannot be empty !");
}

if (scheduler == null) {
throw new ParadoxRuntimeException("Scheduler is mandatory parameter !");
}

if (maxPartitions == null || maxPartitions < 1) {
this.maxPartitions = panelType.getPartitions();
}

if (maxZones == null || maxZones < 1) {
this.maxZones = panelType.getZones();
}

try {
return new EvoCommunicator(ipAddress, tcpPort, ip150Password, pcPassword, scheduler, panelType,
maxPartitions, maxZones);
} catch (IOException e) {
logger.warn("Unable to create communicator for Panel={}. Message={}", panelType, e.getMessage());
throw new ParadoxRuntimeException(e);
}
}

@Override
public ICommunicatorBuilder withMaxZones(Integer maxZones) {
this.maxZones = maxZones;
return this;
}

@Override
public ICommunicatorBuilder withMaxPartitions(Integer maxPartitions) {
this.maxPartitions = maxPartitions;
return this;
}

@Override
public ICommunicatorBuilder withIp150Password(String ip150Password) {
this.ip150Password = ip150Password;
return this;
}

@Override
public ICommunicatorBuilder withPcPassword(String pcPassword) {
this.pcPassword = pcPassword;
return this;
}

@Override
public ICommunicatorBuilder withIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
return this;
}

@Override
public ICommunicatorBuilder withTcpPort(Integer tcpPort) {
this.tcpPort = tcpPort;
return this;
}

@Override
public ICommunicatorBuilder withScheduler(ScheduledExecutorService scheduler) {
this.scheduler = scheduler;
return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -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.paradoxalarm.internal.communication;

import java.util.concurrent.ScheduledExecutorService;

/**
* The {@link ICommunicatorBuilder} is representing the functionality of communicator builders.
* The idea is to ease initialization of communicators which can have lots of parameters.
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface ICommunicatorBuilder {
ICommunicatorBuilder withMaxZones(Integer zones);

ICommunicatorBuilder withMaxPartitions(Integer partitions);

ICommunicatorBuilder withIp150Password(String ip150Password);

ICommunicatorBuilder withPcPassword(String pcPassword);

ICommunicatorBuilder withIpAddress(String ipAddress);

ICommunicatorBuilder withTcpPort(Integer tcpPort);

ICommunicatorBuilder withScheduler(ScheduledExecutorService scheduler);

IParadoxCommunicator build();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* 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.paradoxalarm.internal.communication;

import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxRuntimeException;
import org.openhab.binding.paradoxalarm.internal.model.PanelType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link ParadoxBuilderFactory} used to create the proper communicator builder objects for different panel
* types.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxBuilderFactory {

private final Logger logger = LoggerFactory.getLogger(ParadoxBuilderFactory.class);

public ICommunicatorBuilder createBuilder(PanelType panelType) {
switch (panelType) {
case EVO48:
case EVO96:
case EVO192:
logger.debug("Creating new builder for Paradox {} system", panelType);
return new EvoCommunicator.EvoCommunicatorBuilder(panelType);
default:
throw new ParadoxRuntimeException("Unsupported panel type: " + panelType);
}
}
}
Loading

0 comments on commit e1e68f6

Please sign in to comment.