]> git.basschouten.com Git - openhab-addons.git/blob
c497b215882a42b0d2b92aaea083c76c8aee7c79
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.nikobus.internal.handler;
14
15 import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.CHANNEL_OUTPUT_PREFIX;
16
17 import java.util.ArrayList;
18 import java.util.EnumSet;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.openhab.binding.nikobus.internal.protocol.NikobusCommand;
27 import org.openhab.binding.nikobus.internal.protocol.SwitchModuleCommandFactory;
28 import org.openhab.binding.nikobus.internal.protocol.SwitchModuleGroup;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.RefreshType;
35 import org.openhab.core.types.State;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * The {@link NikobusSwitchModuleHandler} is responsible for communication between Nikobus modules and binding.
41  *
42  * @author Boris Krivonog - Initial contribution
43  */
44 @NonNullByDefault
45 abstract class NikobusModuleHandler extends NikobusBaseThingHandler {
46     private final EnumSet<SwitchModuleGroup> pendingRefresh = EnumSet.noneOf(SwitchModuleGroup.class);
47     private final Logger logger = LoggerFactory.getLogger(NikobusModuleHandler.class);
48     private final Map<String, Integer> cachedStates = new HashMap<>();
49     private final List<ChannelUID> linkedChannels = new ArrayList<>();
50
51     protected NikobusModuleHandler(Thing thing) {
52         super(thing);
53     }
54
55     @Override
56     public void dispose() {
57         super.dispose();
58
59         synchronized (cachedStates) {
60             cachedStates.clear();
61         }
62
63         synchronized (pendingRefresh) {
64             pendingRefresh.clear();
65         }
66     }
67
68     @Override
69     public void handleCommand(ChannelUID channelUID, Command command) {
70         if (command instanceof RefreshType) {
71             refreshChannel(channelUID);
72         } else {
73             processWrite(channelUID, command);
74         }
75     }
76
77     private void refreshChannel(ChannelUID channelUID) {
78         logger.debug("Refreshing channel '{}'", channelUID.getId());
79
80         if (!isLinked(channelUID)) {
81             logger.debug("Refreshing channel '{}' skipped since it is not linked", channelUID.getId());
82             return;
83         }
84
85         updateGroup(SwitchModuleGroup.mapFromChannel(channelUID));
86     }
87
88     @Override
89     public void channelLinked(ChannelUID channelUID) {
90         synchronized (linkedChannels) {
91             linkedChannels.add(channelUID);
92         }
93         super.channelLinked(channelUID);
94     }
95
96     @Override
97     public void channelUnlinked(ChannelUID channelUID) {
98         synchronized (linkedChannels) {
99             linkedChannels.remove(channelUID);
100         }
101         super.channelUnlinked(channelUID);
102     }
103
104     public void refreshModule() {
105         Set<SwitchModuleGroup> groups = new HashSet<>();
106         synchronized (linkedChannels) {
107             for (ChannelUID channelUID : linkedChannels) {
108                 groups.add(SwitchModuleGroup.mapFromChannel(channelUID));
109             }
110         }
111
112         if (groups.isEmpty()) {
113             logger.debug("Nothing to refresh for '{}'", thing.getUID());
114             return;
115         }
116
117         logger.debug("Refreshing {} - {}", thing.getUID(), groups);
118
119         for (SwitchModuleGroup group : groups) {
120             updateGroup(group);
121         }
122     }
123
124     public void requestStatus(SwitchModuleGroup group) {
125         updateGroup(group);
126     }
127
128     private void updateGroup(SwitchModuleGroup group) {
129         synchronized (pendingRefresh) {
130             if (pendingRefresh.contains(group)) {
131                 logger.debug("Refresh already scheduled for group {} of module '{}'", group, getAddress());
132                 return;
133             }
134
135             pendingRefresh.add(group);
136         }
137
138         logger.debug("Refreshing group {} of switch module '{}'", group, getAddress());
139
140         NikobusPcLinkHandler pcLink = getPcLink();
141         if (pcLink != null) {
142             NikobusCommand command = SwitchModuleCommandFactory.createReadCommand(getAddress(), group,
143                     result -> processStatusUpdate(result, group));
144             pcLink.sendCommand(command);
145         }
146     }
147
148     private void processStatusUpdate(NikobusCommand.Result result, SwitchModuleGroup group) {
149         try {
150             String responsePayload = result.get();
151
152             logger.debug("processStatusUpdate '{}' for group {} in module '{}'", responsePayload, group, getAddress());
153
154             if (thing.getStatus() != ThingStatus.ONLINE) {
155                 updateStatus(ThingStatus.ONLINE);
156             }
157
158             // Update channel's statuses based on response.
159             for (int i = 0; i < group.getCount(); i++) {
160                 String channelId = CHANNEL_OUTPUT_PREFIX + (i + group.getOffset());
161                 String responseDigits = responsePayload.substring(9 + (i * 2), 11 + (i * 2));
162
163                 int value = Integer.parseInt(responseDigits, 16);
164
165                 updateStateAndCacheValue(channelId, value);
166             }
167         } catch (Exception e) {
168             logger.warn("Processing response for '{}'-{} failed with {}", getAddress(), group, e.getMessage(), e);
169             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
170         } finally {
171             synchronized (pendingRefresh) {
172                 pendingRefresh.remove(group);
173             }
174         }
175     }
176
177     private void updateStateAndCacheValue(String channelId, int value) {
178         if (value < 0x00 || value > 0xff) {
179             throw new IllegalArgumentException("Invalid range. 0x00 - 0xff expected but got value " + value);
180         }
181
182         logger.debug("setting channel '{}' to {}", channelId, value);
183
184         synchronized (cachedStates) {
185             cachedStates.put(channelId, value);
186         }
187
188         updateState(channelId, stateFromValue(value));
189     }
190
191     @SuppressWarnings({ "unused", "null" })
192     private void processWrite(ChannelUID channelUID, Command command) {
193         StringBuilder commandPayload = new StringBuilder();
194         SwitchModuleGroup group = SwitchModuleGroup.mapFromChannel(channelUID);
195
196         for (int i = group.getOffset(); i < group.getOffset() + group.getCount(); i++) {
197             String channelId = CHANNEL_OUTPUT_PREFIX + i;
198             Integer digits;
199
200             if (channelId.equals(channelUID.getId())) {
201                 digits = valueFromCommand(command);
202                 updateStateAndCacheValue(channelId, digits.intValue());
203             } else {
204                 synchronized (cachedStates) {
205                     digits = cachedStates.get(channelId);
206                 }
207             }
208
209             if (digits == null) {
210                 commandPayload.append("00");
211                 logger.warn("no cached value found for '{}' in module '{}'", channelId, getAddress());
212             } else {
213                 commandPayload.append(String.format("%02X", digits.intValue()));
214             }
215         }
216
217         NikobusPcLinkHandler pcLink = getPcLink();
218         if (pcLink != null) {
219             pcLink.sendCommand(SwitchModuleCommandFactory.createWriteCommand(getAddress(), group,
220                     commandPayload.toString(), this::processWriteCommandResponse));
221         }
222     }
223
224     private void processWriteCommandResponse(NikobusCommand.Result result) {
225         try {
226             String responsePayload = result.get();
227
228             logger.debug("processWriteCommandResponse '{}'", responsePayload);
229
230             if (thing.getStatus() != ThingStatus.ONLINE) {
231                 updateStatus(ThingStatus.ONLINE);
232             }
233         } catch (Exception e) {
234             logger.warn("Processing write confirmation failed with {}", e.getMessage());
235             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
236         }
237     }
238
239     protected abstract int valueFromCommand(Command command);
240
241     protected abstract State stateFromValue(int value);
242 }