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