]> git.basschouten.com Git - openhab-addons.git/blob
1bb9c7385ea29e6624c2cfa1dd4b8fe30c8bca86
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.paradoxalarm.internal.communication;
14
15 import java.io.IOException;
16 import java.net.UnknownHostException;
17 import java.nio.charset.StandardCharsets;
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.concurrent.ScheduledExecutorService;
24 import java.util.concurrent.TimeUnit;
25
26 import org.openhab.binding.paradoxalarm.internal.communication.messages.EpromRequestPayload;
27 import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderMessageType;
28 import org.openhab.binding.paradoxalarm.internal.communication.messages.IPayload;
29 import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
30 import org.openhab.binding.paradoxalarm.internal.communication.messages.RamRequestPayload;
31 import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxException;
32 import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxRuntimeException;
33 import org.openhab.binding.paradoxalarm.internal.model.EntityType;
34 import org.openhab.binding.paradoxalarm.internal.model.PanelType;
35 import org.openhab.binding.paradoxalarm.internal.model.ZoneStateFlags;
36 import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 /**
41  * The {@link EvoCommunicator} is responsible for handling communication to Evo192 alarm system via IP150 interface.
42  *
43  * @author Konstantin Polihronov - Initial contribution
44  */
45 public class EvoCommunicator extends GenericCommunicator implements IParadoxCommunicator {
46
47     private static final byte RAM_BLOCK_SIZE = (byte) 64;
48
49     private final Logger logger = LoggerFactory.getLogger(EvoCommunicator.class);
50
51     private MemoryMap memoryMap;
52
53     private Map<EntityType, Map<Integer, String>> entityLabelsMap = new HashMap<>();
54
55     private PanelType panelType = PanelType.UNKNOWN;
56     private Integer maxPartitions;
57     private Integer maxZones;
58
59     private EvoCommunicator(String ipAddress, int tcpPort, String ip150Password, String pcPassword,
60             ScheduledExecutorService scheduler, PanelType panelType, Integer maxPartitions, Integer maxZones,
61             boolean useEncryption) throws UnknownHostException, IOException {
62         super(ipAddress, tcpPort, ip150Password, pcPassword, scheduler, useEncryption);
63         this.panelType = panelType;
64         this.maxPartitions = maxPartitions;
65         this.maxZones = maxZones;
66         logger.debug("PanelType={}, Max Partitions={}, Max Zones={}", panelType, maxPartitions, maxZones);
67         initializeMemoryMap();
68     }
69
70     @Override
71     protected void receiveEpromResponse(IResponse response) {
72         byte[] payload = response.getPayload();
73         if (payload != null) {
74             EpromRequest request = (EpromRequest) response.getRequest();
75             int entityId = request.getEntityId();
76             EntityType entityType = request.getEntityType();
77             updateEntityLabel(entityType, entityId, payload);
78         } else {
79             logger.debug("Wrong parsed result. Probably wrong data received in response. Response={}", response);
80             return;
81         }
82     }
83
84     @Override
85     protected void receiveRamResponse(IResponse response) {
86         byte[] payload = response.getPayload();
87         if (payload != null && payload.length >= RAM_BLOCK_SIZE) {
88             RamRequest request = (RamRequest) response.getRequest();
89             int ramBlockNumber = request.getRamBlockNumber();
90             memoryMap.updateElement(ramBlockNumber, payload);
91             if (logger.isTraceEnabled()) {
92                 logger.trace("Result for ramBlock={} is [{}]", ramBlockNumber, ParadoxUtil.byteArrayToString(payload));
93             }
94
95             // Trigger listeners update when last memory page update is received
96             if (ramBlockNumber == panelType.getRamPagesNumber()) {
97                 updateListeners();
98             }
99         } else {
100             logger.debug("Wrong parsed result. Probably wrong data received in response");
101             return;
102         }
103     }
104
105     private void updateEntityLabel(EntityType entityType, int entityId, byte[] payload) {
106         String label = createString(payload);
107         logger.debug("{} label updated to: {}", entityType, label);
108
109         entityLabelsMap.get(entityType).put(entityId, label);
110     }
111
112     private void retrievePartitionLabel(int partitionNo) {
113         logger.debug("Submitting request for partition label: {}", partitionNo);
114         int address = 0x3A6B + (partitionNo) * 107;
115         byte labelLength = 16;
116
117         try {
118             IPayload payload = new EpromRequestPayload(address, labelLength);
119             ParadoxIPPacket readEpromIPPacket = createSerialPassthroughPacket(payload);
120
121             IRequest epromRequest = new EpromRequest(partitionNo, EntityType.PARTITION, readEpromIPPacket, this);
122             submitRequest(epromRequest);
123         } catch (ParadoxException e) {
124             logger.debug("Error creating request for with number={}, Exception={} ", partitionNo, e.getMessage());
125         }
126     }
127
128     private ParadoxIPPacket createSerialPassthroughPacket(IPayload payload) {
129         ParadoxIPPacket packet = new ParadoxIPPacket(payload.getBytes());
130         packet.setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST).setUnknown0((byte) 0x14);
131         return packet;
132     }
133
134     private void retrieveZoneLabel(int zoneNumber) {
135         logger.debug("Submitting request for zone label: {}", zoneNumber);
136         final byte labelLength = 16;
137
138         try {
139             int address;
140             if (zoneNumber < 96) {
141                 address = 0x430 + (zoneNumber) * 16;
142             } else {
143                 address = 0x62F7 + (zoneNumber - 96) * 16;
144             }
145
146             IPayload payload = new EpromRequestPayload(address, labelLength);
147             ParadoxIPPacket readEpromIPPacket = createSerialPassthroughPacket(payload);
148
149             IRequest epromRequest = new EpromRequest(zoneNumber, EntityType.ZONE, readEpromIPPacket, this);
150             submitRequest(epromRequest);
151         } catch (ParadoxException e) {
152             logger.debug("Error creating request with number={}, Exception={} ", zoneNumber, e.getMessage());
153         }
154     }
155
156     @Override
157     public List<byte[]> getPartitionFlags() {
158         List<byte[]> result = new ArrayList<>();
159
160         byte[] element = memoryMap.getElement(2);
161         byte[] firstBlock = Arrays.copyOfRange(element, 32, 64);
162
163         element = memoryMap.getElement(3);
164         byte[] secondBlock = Arrays.copyOfRange(element, 0, 16);
165         byte[] mergeByteArrays = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
166         for (int i = 0; i < mergeByteArrays.length; i += 6) {
167             result.add(Arrays.copyOfRange(mergeByteArrays, i, i + 6));
168         }
169
170         return result;
171     }
172
173     @Override
174     public ZoneStateFlags getZoneStateFlags() {
175         ZoneStateFlags result = new ZoneStateFlags();
176
177         byte[] firstPage = memoryMap.getElement(0);
178         byte[] secondPage = memoryMap.getElement(8);
179
180         int pageOffset = panelType == PanelType.EVO48 ? 34 : 40;
181         byte[] firstBlock = Arrays.copyOfRange(firstPage, 28, pageOffset);
182         if (panelType != PanelType.EVO192) {
183             result.setZonesOpened(firstBlock);
184         } else {
185             byte[] secondBlock = Arrays.copyOfRange(secondPage, 0, 12);
186             byte[] zonesOpened = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
187             result.setZonesOpened(zonesOpened);
188         }
189
190         pageOffset = panelType == PanelType.EVO48 ? 46 : 52;
191         firstBlock = Arrays.copyOfRange(firstPage, 40, pageOffset);
192         if (panelType != PanelType.EVO192) {
193             result.setZonesTampered(firstBlock);
194         } else {
195             byte[] secondBlock = Arrays.copyOfRange(secondPage, 12, 24);
196             byte[] zonesTampered = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
197             result.setZonesTampered(zonesTampered);
198         }
199
200         pageOffset = panelType == PanelType.EVO48 ? 58 : 64;
201         firstBlock = Arrays.copyOfRange(firstPage, 52, pageOffset);
202         if (panelType != PanelType.EVO192) {
203             result.setZonesTampered(firstBlock);
204         } else {
205             byte[] secondBlock = Arrays.copyOfRange(secondPage, 24, 36);
206             byte[] zonesLowBattery = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
207             result.setZonesLowBattery(zonesLowBattery);
208         }
209
210         return result;
211     }
212
213     public void initializeMemoryMap() {
214         for (EntityType type : EntityType.values()) {
215             entityLabelsMap.put(type, new HashMap<>());
216         }
217
218         List<byte[]> ramCache = new ArrayList<>(panelType.getRamPagesNumber() + 1);
219         for (int i = 0; i <= panelType.getRamPagesNumber(); i++) {
220             ramCache.add(new byte[0]);
221         }
222         memoryMap = new MemoryMap(ramCache);
223     }
224
225     @Override
226     public void refreshMemoryMap() {
227         if (!isOnline()) {
228             logger.debug("Attempt to refresh memory map was made, but communicator is not online. Skipping update.");
229             return;
230         }
231
232         SyncQueue queue = SyncQueue.getInstance();
233         synchronized (queue) {
234             for (int i = 1; i <= panelType.getRamPagesNumber(); i++) {
235                 submitRamRequest(i);
236             }
237         }
238     }
239
240     private void submitRamRequest(int blockNo) {
241         try {
242             logger.trace("Creating RAM page {} read request", blockNo);
243             IPayload payload = new RamRequestPayload(blockNo, RAM_BLOCK_SIZE);
244             ParadoxIPPacket ipPacket = createSerialPassthroughPacket(payload);
245             IRequest ramRequest = new RamRequest(blockNo, ipPacket, this);
246             submitRequest(ramRequest);
247         } catch (ParadoxException e) {
248             logger.debug(
249                     "Unable to create request payload from provided bytes to read. blockNo={}, bytes to read={}. Exception={}",
250                     blockNo, RAM_BLOCK_SIZE, e.getMessage());
251         }
252     }
253
254     private String createString(byte[] payloadResult) {
255         return new String(payloadResult, StandardCharsets.US_ASCII);
256     }
257
258     @Override
259     public void executeCommand(String command) {
260         IP150Command ip150Command = IP150Command.valueOf(command);
261         switch (ip150Command) {
262             case LOGIN:
263                 startLoginSequence();
264                 return;
265             case LOGOUT:
266                 CommunicationState.LOGOUT.runPhase(this);
267                 return;
268             case RESET:
269                 CommunicationState.LOGOUT.runPhase(this);
270                 scheduler.schedule(this::startLoginSequence, 5, TimeUnit.SECONDS);
271                 return;
272             default:
273                 logger.debug("Command {} not implemented.", command);
274         }
275     }
276
277     @Override
278     public MemoryMap getMemoryMap() {
279         return memoryMap;
280     }
281
282     public Map<EntityType, Map<Integer, String>> getEntityLabelsMap() {
283         return entityLabelsMap;
284     }
285
286     @Override
287     public Map<Integer, String> getPartitionLabels() {
288         return entityLabelsMap.get(EntityType.PARTITION);
289     }
290
291     @Override
292     public Map<Integer, String> getZoneLabels() {
293         return entityLabelsMap.get(EntityType.ZONE);
294     }
295
296     @Override
297     public void initializeData() {
298         synchronized (SyncQueue.getInstance()) {
299             initializeEpromData();
300             refreshMemoryMap();
301         }
302     }
303
304     private void initializeEpromData() {
305         for (int i = 0; i < maxPartitions; i++) {
306             retrievePartitionLabel(i);
307         }
308         for (int i = 0; i < maxZones; i++) {
309             retrieveZoneLabel(i);
310         }
311     }
312
313     public static class EvoCommunicatorBuilder implements ICommunicatorBuilder {
314
315         private final Logger logger = LoggerFactory.getLogger(EvoCommunicatorBuilder.class);
316
317         // Mandatory parameters
318         private PanelType panelType;
319         private String ipAddress;
320         private String ip150Password;
321         private ScheduledExecutorService scheduler;
322
323         // Non mandatory or with predefined values
324         private Integer maxPartitions;
325         private Integer maxZones;
326         private int tcpPort = 10000;
327         private String pcPassword = "0000";
328
329         private boolean useEncryption;
330
331         EvoCommunicatorBuilder(PanelType panelType) {
332             this.panelType = panelType;
333         }
334
335         @Override
336         public IParadoxCommunicator build() {
337             if (ipAddress == null || ipAddress.isEmpty()) {
338                 final String msg = "IP address cannot be empty !";
339                 logger.debug(msg);
340                 throw new ParadoxRuntimeException(msg);
341             }
342
343             if (ip150Password == null || ip150Password.isEmpty()) {
344                 final String msg = "Password for IP150 cannot be empty !";
345                 logger.debug(msg);
346                 throw new ParadoxRuntimeException(msg);
347             }
348
349             if (scheduler == null) {
350                 final String msg = "Scheduler is mandatory parameter !";
351                 logger.debug(msg);
352                 throw new ParadoxRuntimeException(msg);
353             }
354
355             if (maxPartitions == null || maxPartitions < 1) {
356                 this.maxPartitions = panelType.getPartitions();
357             }
358
359             if (maxZones == null || maxZones < 1) {
360                 this.maxZones = panelType.getZones();
361             }
362
363             try {
364                 return new EvoCommunicator(ipAddress, tcpPort, ip150Password, pcPassword, scheduler, panelType,
365                         maxPartitions, maxZones, useEncryption);
366             } catch (IOException e) {
367                 logger.warn("Unable to create communicator for Panel={}. Message={}", panelType, e.getMessage());
368                 throw new ParadoxRuntimeException(e);
369             }
370         }
371
372         @Override
373         public ICommunicatorBuilder withMaxZones(Integer maxZones) {
374             this.maxZones = maxZones;
375             return this;
376         }
377
378         @Override
379         public ICommunicatorBuilder withMaxPartitions(Integer maxPartitions) {
380             this.maxPartitions = maxPartitions;
381             return this;
382         }
383
384         @Override
385         public ICommunicatorBuilder withIp150Password(String ip150Password) {
386             this.ip150Password = ip150Password;
387             return this;
388         }
389
390         @Override
391         public ICommunicatorBuilder withPcPassword(String pcPassword) {
392             this.pcPassword = pcPassword;
393             return this;
394         }
395
396         @Override
397         public ICommunicatorBuilder withIpAddress(String ipAddress) {
398             this.ipAddress = ipAddress;
399             return this;
400         }
401
402         @Override
403         public ICommunicatorBuilder withTcpPort(Integer tcpPort) {
404             this.tcpPort = tcpPort;
405             return this;
406         }
407
408         @Override
409         public ICommunicatorBuilder withScheduler(ScheduledExecutorService scheduler) {
410             this.scheduler = scheduler;
411             return this;
412         }
413
414         @Override
415         public ICommunicatorBuilder withEncryption(boolean useEncryption) {
416             this.useEncryption = useEncryption;
417             return this;
418         }
419     }
420 }