2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.satel.internal.handler;
15 import java.util.Collection;
16 import java.util.LinkedList;
17 import java.util.Optional;
18 import java.util.concurrent.atomic.AtomicBoolean;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.satel.internal.command.IntegraStateCommand;
23 import org.openhab.binding.satel.internal.command.SatelCommand;
24 import org.openhab.binding.satel.internal.event.ConnectionStatusEvent;
25 import org.openhab.binding.satel.internal.event.IntegraStateEvent;
26 import org.openhab.binding.satel.internal.event.NewStatesEvent;
27 import org.openhab.binding.satel.internal.types.StateType;
28 import org.openhab.core.thing.Channel;
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.types.Command;
33 import org.openhab.core.types.RefreshType;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * The {@link SatelStateThingHandler} is base thing handler class for all state holding things.
40 * @author Krzysztof Goworek - Initial contribution
43 public abstract class SatelStateThingHandler extends SatelThingHandler {
45 private final Logger logger = LoggerFactory.getLogger(SatelStateThingHandler.class);
47 private final AtomicBoolean requiresRefresh = new AtomicBoolean(true);
49 public SatelStateThingHandler(Thing thing) {
54 public void handleCommand(ChannelUID channelUID, Command command) {
55 logger.debug("New command for {}: {}", channelUID, command);
57 if (command == RefreshType.REFRESH) {
58 this.requiresRefresh.set(true);
60 withBridgeHandlerPresent(bridgeHandler -> {
61 if (bridgeHandler.getUserCode().isEmpty()) {
62 logger.info("Cannot control devices without providing valid user code. Command has not been sent.");
64 convertCommand(channelUID, command)
65 .ifPresent(satelCommand -> bridgeHandler.sendCommand(satelCommand, true));
72 public void incomingEvent(ConnectionStatusEvent event) {
73 logger.trace("Handling incoming event: {}", event);
74 // we have just connected, change thing's status and force refreshing
75 if (event.isConnected()) {
76 updateStatus(ThingStatus.ONLINE);
77 requiresRefresh.set(true);
82 public void incomingEvent(NewStatesEvent event) {
83 logger.trace("Handling incoming event: {}", event);
84 // refresh all states that have changed
85 withBridgeHandlerPresent(bridgeHandler -> {
86 for (SatelCommand command : getRefreshCommands(event)) {
87 bridgeHandler.sendCommand(command, true);
93 @SuppressWarnings("PMD.CompareObjectsWithEquals")
94 public void incomingEvent(IntegraStateEvent event) {
95 logger.trace("Handling incoming event: {}", event);
96 // update thing's state unless it should accept commands only
97 if (getThingConfig().isCommandOnly()) {
100 for (Channel channel : getThing().getChannels()) {
101 ChannelUID channelUID = channel.getUID();
102 if (isLinked(channel.getUID())) {
103 StateType stateType = getStateType(channelUID.getId());
104 if (stateType != StateType.NONE && event.hasDataForState(stateType)) {
105 int bitNbr = getStateBitNbr(stateType);
106 boolean invertState = getThingConfig().isStateInverted();
107 updateSwitch(channelUID, event.isSet(stateType, bitNbr) ^ invertState);
114 * Returns bit number of given state type for this thing.
115 * This number addresses the bit in bit set sent to or received from alarm system.
116 * Usually this number is the device identifier.
118 * @param stateType state type
119 * @return bit number in state bit set
121 protected int getStateBitNbr(StateType stateType) {
122 return getThingConfig().getId() - 1;
126 * Converts openHAB command sent to a channel into Satel message.
128 * @param channel channel the command was sent to
129 * @param command sent command
130 * @return Satel message that reflects sent command
132 protected abstract Optional<SatelCommand> convertCommand(ChannelUID channel, Command command);
135 * Derived handlers must return appropriate state type for channels they support.
136 * If given channel is not supported by a handler, it should return {@linkplain StateType#NONE}.
138 * @param channelId channel identifier to get state type for
139 * @return object that represents state type
140 * @see #getChannel(StateType)
142 protected abstract StateType getStateType(String channelId);
145 * Returns channel for given state type. Usually channels have the same ID as state type name they represent.
147 * @param stateType state type to get channel for
148 * @return channel object
149 * @see #getStateType(String)
151 protected @Nullable Channel getChannel(StateType stateType) {
152 final String channelId = stateType.toString().toLowerCase();
153 final Channel channel = getThing().getChannel(channelId);
154 if (channel == null) {
155 logger.debug("Missing channel for {}", stateType);
161 * Returns list of commands required to update thing state basing on event describing changes since last refresh.
163 * @param event list of state changes since last refresh
164 * @return collection of {@link IntegraStateCommand}
166 @SuppressWarnings("PMD.CompareObjectsWithEquals")
167 protected Collection<SatelCommand> getRefreshCommands(NewStatesEvent event) {
168 final boolean hasExtPayload = getBridgeHandler().getIntegraType().hasExtPayload();
169 final Collection<SatelCommand> result = new LinkedList<>();
170 final boolean forceRefresh = requiresRefresh();
171 for (Channel channel : getThing().getChannels()) {
172 StateType stateType = getStateType(channel.getUID().getId());
173 if (stateType != StateType.NONE && isLinked(channel.getUID())) {
174 if (forceRefresh || event.isNew(stateType.getRefreshCommand())) {
175 result.add(new IntegraStateCommand(stateType, hasExtPayload));
183 * Checks if this thing requires unconditional refresh of all its channels.
184 * Clears the flag afterwards.
186 * @return if <code>true</code> this thing requires full refresh
188 protected boolean requiresRefresh() {
189 return requiresRefresh.getAndSet(false);