/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.tacmi.internal; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.openhab.binding.tacmi.TACmiBindingProvider; import org.openhab.binding.tacmi.internal.message.AnalogMessage; import org.openhab.binding.tacmi.internal.message.AnalogValue; import org.openhab.binding.tacmi.internal.message.DigitalMessage; import org.openhab.binding.tacmi.internal.message.Message; import org.openhab.binding.tacmi.internal.message.MessageType; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.types.Command; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implement this class if you are going create an actively polling service like * querying a Website/Device. * * @author Timo Wendt * @since 1.8.0 */ public class TACmiBinding extends AbstractActiveBinding<TACmiBindingProvider> { private static final Logger logger = LoggerFactory.getLogger(TACmiBinding.class); /** * The BundleContext. This is only valid when the bundle is ACTIVE. It is * set in the activate() method and must not be accessed anymore once the * deactivate() method was called or before activate() was called. */ private BundleContext bundleContext; /** * the refresh interval which is used to poll values from the TACmi server * (optional, defaults to 60000ms) */ private long refreshInterval = 1000; /** * IP or hostname of the CMI This is set in the activate method */ // private String cmiAddress; private InetAddress cmiAddress; /** * Port the cmi is listening on and also sending to */ private static int cmiPort = 5441; /** * Connection socket */ private DatagramSocket clientSocket = null; public TACmiBinding() { } /** * Called by the SCR to activate the component with its configuration read * from CAS * * @param bundleContext * BundleContext of the Bundle that defines this component * @param configuration * Configuration properties for this component obtained from the * ConfigAdmin service */ public void activate(final BundleContext bundleContext, final Map<String, Object> configuration) { this.bundleContext = bundleContext; // to override the default refresh interval one has to add a // parameter to openhab.cfg like <bindingName>:refresh=<intervalInMs> String refreshIntervalString = (String) configuration.get("refresh"); if (StringUtils.isNotBlank(refreshIntervalString)) { refreshInterval = Long.parseLong(refreshIntervalString); } // set cmiAddress from configuration // cmiAddress = (String) configuration.get("cmiAddress"); try { cmiAddress = InetAddress.getByName((String) configuration.get("cmiAddress")); } catch (UnknownHostException e1) { logger.error("Failed to get IP of CMI from configuration"); setProperlyConfigured(false); return; } try { clientSocket = new DatagramSocket(cmiPort); } catch (SocketException e) { logger.error("Failed to create Socket for receiving UDP packets from CMI"); setProperlyConfigured(true); return; } setProperlyConfigured(true); } /** * Called by the SCR to deactivate the component when either the * configuration is removed or mandatory references are no longer satisfied * or the component has simply been stopped. * * @param reason * Reason code for the deactivation:<br> * <ul> * <li>0 – Unspecified * <li>1 – The component was disabled * <li>2 – A reference became unsatisfied * <li>3 – A configuration was changed * <li>4 – A configuration was deleted * <li>5 – The component was disposed * <li>6 – The bundle was stopped * </ul> */ public void deactivate(final int reason) { this.bundleContext = null; clientSocket.close(); } /** * @{inheritDoc */ @Override protected long getRefreshInterval() { return refreshInterval; } /** * @{inheritDoc */ @Override protected String getName() { return "TACmi Refresh Service"; } /** * @{inheritDoc */ @Override protected void execute() { logger.trace("execute() method is called!"); try { clientSocket.setBroadcast(true); clientSocket.setSoTimeout(120000); byte[] receiveData = new byte[14]; DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); clientSocket.receive(receivePacket); byte[] data = receivePacket.getData(); Message message; if (data[1] > 0) { logger.debug("Processing analog message"); message = new AnalogMessage(data); } else if (data[1] == 0) { logger.debug("Processing digital message"); message = new DigitalMessage(data); } else { logger.debug("Invalid message received"); return; } logger.debug(message.toString()); for (TACmiBindingProvider provider : providers) { for (String itemName : provider.getItemNames()) { logger.debug("Processing item: " + itemName); int portNumber = provider.getPortNumber(itemName); if (provider.getCanNode(itemName) == message.canNode && provider.getPortType(itemName).equals(message.getType().toString().toLowerCase())) { if (message.hasPortnumber(portNumber)) { if (message.getType() == MessageType.A) { AnalogValue value = ((AnalogMessage) message).getAnalogValue(portNumber); if (value.value != null) { logger.debug("Updating item: " + itemName + " with value: " + value.value); eventPublisher.postUpdate(itemName, new DecimalType(value.value)); } } else { OnOffType state = ((DigitalMessage) message).getPortStateAsOnOffType(portNumber); logger.debug("Updating item {} with state {}", itemName, state); eventPublisher.postUpdate(itemName, state); } } else { logger.debug("Portnumber {} not included in message", portNumber); } } else { logger.debug("CAN Node does not match"); } } } } catch (SocketTimeoutException te) { logger.info("Receive timeout on CoE socket, retrying ..."); } catch (Exception e) { logger.error("Error in execute: ", e); } logger.trace("TACmi execute() finished"); } /** * @throws UnknownHostException * @{inheritDoc */ @Override protected void internalReceiveCommand(String itemName, Command command) { logger.debug("internalReceiveCommand({},{}) is called!", itemName, command); for (TACmiBindingProvider provider : providers) { int canNode = provider.getCanNode(itemName); String portType = provider.getPortType(itemName); int portNumber = provider.getPortNumber(itemName); logger.trace("Type: {}, portNumber: {}, command: {}", portType, portNumber, command.toString()); byte[] messageBytes; if (portType.equals("d") && portNumber == 1 && command instanceof OnOffType) { boolean state = OnOffType.ON.equals(command) ? true : false; DigitalMessage message = new DigitalMessage((byte) canNode, state); messageBytes = message.getRaw(); } else if (portType.equals("a") && (portNumber - 1) % 4 == 0 && command instanceof DecimalType) { TACmiMeasureType measureType = provider.getMeasureType(itemName); AnalogMessage message = new AnalogMessage((byte) canNode, 1, (DecimalType) command, measureType); messageBytes = message.getRaw(); } else { logger.info("Not sending command: portType: {}, portNumber: {}, command: {}", portType, portNumber, command.toString()); return; } DatagramPacket packet = new DatagramPacket(messageBytes, messageBytes.length, cmiAddress, TACmiBinding.cmiPort); try { clientSocket.send(packet); } catch (IOException e) { logger.warn("Error sending message: {}, {}", e.getClass().getName(), e.getMessage()); } } } protected void addBindingProvider(TACmiBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(TACmiBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } }