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.solarwatt.internal.handler;
15 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
17 import java.text.MessageFormat;
20 import javax.measure.Unit;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.solarwatt.internal.channel.SolarwattChannelTypeProvider;
25 import org.openhab.binding.solarwatt.internal.configuration.SolarwattThingConfiguration;
26 import org.openhab.binding.solarwatt.internal.domain.SolarwattChannel;
27 import org.openhab.binding.solarwatt.internal.domain.model.Device;
28 import org.openhab.core.library.CoreItemFactory;
29 import org.openhab.core.library.unit.Units;
30 import org.openhab.core.thing.Bridge;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.binding.BaseThingHandler;
36 import org.openhab.core.thing.binding.BridgeHandler;
37 import org.openhab.core.thing.binding.builder.ChannelBuilder;
38 import org.openhab.core.thing.binding.builder.ThingBuilder;
39 import org.openhab.core.thing.type.ChannelKind;
40 import org.openhab.core.thing.type.ChannelTypeUID;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.RefreshType;
43 import org.openhab.core.types.util.UnitUtils;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * The {@link SimpleDeviceHandler} bundles everything related to generic talking to devices.
50 * @author Sven Carstens - Initial contribution
53 public class SimpleDeviceHandler extends BaseThingHandler {
54 private final Logger logger = LoggerFactory.getLogger(SimpleDeviceHandler.class);
56 private final SolarwattChannelTypeProvider channelTypeProvider;
58 public SimpleDeviceHandler(Thing thing, SolarwattChannelTypeProvider channelTypeProvider) {
60 this.channelTypeProvider = channelTypeProvider;
64 * Bring the thing online and update state from the bridge.
67 public void initialize() {
68 final EnergyManagerHandler bridgeHandler = this.getEnergyManagerHandler();
69 if (bridgeHandler != null) {
70 this.initDeviceChannels();
71 this.updateDeviceProperties();
72 this.updateDeviceChannels();
74 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
75 "Received null bridge while initializing!");
80 * Process the command for this thing.
82 * Only refresh is supported in this case.
84 * @param channelUID channel for which the command was issued
85 * @param command to execute
88 public void handleCommand(ChannelUID channelUID, Command command) {
89 final EnergyManagerHandler bridgeHandler = this.getEnergyManagerHandler();
90 if (bridgeHandler != null) {
91 if (command instanceof RefreshType) {
92 this.updateDeviceProperties();
93 this.updateDeviceChannels();
96 this.logger.warn("Thing {} has no bridgeHandler for Bridge {}", this.getThing().getUID(),
97 this.getThing().getBridgeUID());
102 * Update the state of all channels.
104 protected void updateDeviceChannels() {
105 // find device for the thing
106 Device device = this.getDevice();
108 if (device != null) {
109 device.getStateValues().forEach(this::updateState);
114 * Assert that all {@link org.openhab.core.thing.type.ChannelType}s are registered for this thing.
116 protected void initDeviceChannels() {
117 // find device for the thing
118 Device device = this.getDevice();
120 if (device != null) {
121 device.getSolarwattChannelSet().forEach((channelTag, solarwattChannel) -> {
122 this.assertChannel(solarwattChannel);
128 * Update the properties for this device.
130 protected void updateDeviceProperties() {
131 // find device for the thing
132 Device device = this.getDevice();
134 if (device != null) {
136 Map<String, String> properties = this.editProperties();
137 this.putProperty(properties, PROPERTY_ID_NAME, device.getIdName());
138 this.putProperty(properties, PROPERTY_ID_FIRMWARE, device.getIdFirmware());
139 this.putProperty(properties, PROPERTY_ID_MANUFACTURER, device.getIdManufacturer());
140 this.updateProperties(properties);
142 // relay state of device to status
143 this.updateStatus(device.getStateDevice());
147 private void putProperty(Map<String, String> properties, String name, @Nullable String value) {
149 properties.put(name, value);
154 * Assert that all channels inside of our thing are well defined.
156 * Only channels which can not be found are created.
158 * @param solarwattChannel channel description with name and unit
160 protected void assertChannel(SolarwattChannel solarwattChannel) {
161 ChannelUID channelUID = new ChannelUID(this.getThing().getUID(), solarwattChannel.getChannelName());
162 ChannelTypeUID channelType = this.channelTypeProvider.assertChannelType(solarwattChannel);
163 if (this.getThing().getChannel(channelUID) == null) {
164 ThingBuilder thingBuilder = this.editThing();
165 thingBuilder.withChannel(getChannelBuilder(solarwattChannel, channelUID, channelType).build());
167 this.updateThing(thingBuilder.build());
172 * Get a builder for a channel type according to the {@link SolarwattChannel}
174 * @param solarwattChannel channel type definition
175 * @param channelUID uid of the channel
176 * @param channelType uid of the channel type
177 * @return builder for that channel type
179 public static ChannelBuilder getChannelBuilder(SolarwattChannel solarwattChannel, ChannelUID channelUID,
180 ChannelTypeUID channelType) {
181 String itemType = CoreItemFactory.STRING;
182 Unit<?> unit = solarwattChannel.getUnit();
184 String dimension = UnitUtils.getDimensionName(unit);
186 if (Units.PERCENT.equals(unit)) {
187 // strangely it is Angle
188 dimension = ":Dimensionless";
191 itemType = CoreItemFactory.NUMBER;
192 if (dimension != null && !dimension.isEmpty()) {
193 itemType = CoreItemFactory.NUMBER + ":" + dimension;
196 ChannelBuilder channelBuilder = ChannelBuilder.create(channelUID, itemType);
198 channelBuilder.withLabel(solarwattChannel.getChannelName()).withType(channelType).withDescription(MessageFormat
199 .format("Value for {0} with Unit: {1}", solarwattChannel.getChannelName(), solarwattChannel.getUnit()))
200 .withKind(ChannelKind.STATE);
201 return channelBuilder;
205 * Get the {@link EnergyManagerHandler}.
207 * Only the {@link EnergyManagerHandler} has knowledge about the devices itself.
209 * @return instance responsible for this handler
211 protected @Nullable EnergyManagerHandler getEnergyManagerHandler() {
212 Bridge bridge = this.getBridge();
213 if (bridge != null) {
214 BridgeHandler bridgeHandler = bridge.getHandler();
215 if (bridgeHandler instanceof EnergyManagerHandler energyManagerHandler) {
216 return energyManagerHandler;
218 // happens while dynamically reloading the binding
219 this.logger.warn("BridgeHandler is not implementing EnergyManagerHandler {}", bridgeHandler);
222 // this handler can't work without a bridge
223 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
224 "Received null bridge while initializing!");
231 * Get the {@link Device} from the {@link EnergyManagerHandler}.
233 * @return model with values
235 protected @Nullable Device getDevice() {
236 final EnergyManagerHandler bridgeHandler = this.getEnergyManagerHandler();
238 if (bridgeHandler != null) {
239 Map<String, Device> bridgeDevices = bridgeHandler.getDevices();
240 if (bridgeDevices != null) {
241 return bridgeDevices.get(this.getConfigAs(SolarwattThingConfiguration.class).guid);
245 this.logger.warn("Device not found for thing with guid {}",
246 this.getConfigAs(SolarwattThingConfiguration.class).guid);