/** * Copyright (c) 2010-2016, openHAB.org and others. * * 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.upb.internal; import java.util.Map; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.openhab.binding.upb.UPBBindingProvider; import org.openhab.binding.upb.internal.UPBMessage.Type; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import gnu.io.CommPortIdentifier; import gnu.io.NoSuchPortException; import gnu.io.PortInUseException; import gnu.io.SerialPort; import gnu.io.UnsupportedCommOperationException; /** * Binding for Universal Powerline Bus (UPB) that reads and writes messages to * and from the UPB modem. * * @author cvanorman * @since 1.9.0 */ public class UPBBinding extends AbstractActiveBinding<UPBBindingProvider> implements UPBReader.Listener { private static final Logger logger = LoggerFactory.getLogger(UPBBinding.class); private String port; private byte network = 0; private SerialPort serialPort; private UPBReader upbReader; private UPBWriter upbWriter; /** * the refresh interval which is used to poll values from the UPB server * (optional, defaults to 3600000ms) */ private long refreshInterval = 3600000; /** * 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) { String refreshIntervalString = (String) configuration.get("refresh"); if (StringUtils.isNotBlank(refreshIntervalString)) { refreshInterval = Long.parseLong(refreshIntervalString); } parseConfiguration(configuration); logger.info("UPB binding starting up..."); try { serialPort = openSerialPort(); upbReader = new UPBReader(serialPort.getInputStream()); upbWriter = new UPBWriter(serialPort.getOutputStream(), upbReader); } catch (Exception e) { throw new RuntimeException("Failed to open serial port.", e); } upbReader.addListener(this); setProperlyConfigured(true); } /** * Called by the SCR when the configuration of a binding has been changed * through the ConfigAdmin service. * * @param configuration * Updated configuration properties */ public void modified(final Map<String, Object> configuration) { parseConfiguration(configuration); } private void parseConfiguration(final Map<String, Object> configuration) { port = ObjectUtils.toString(configuration.get("port"), null); network = Integer.valueOf(ObjectUtils.toString(configuration.get("network"), "0")).byteValue(); logger.debug("Parsed UPB configuration:"); logger.debug("Serial port: {}", port); logger.debug("UPB Network: {}", network & 0xff); } /** * 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) { logger.info("UPB binding shutting down..."); if (upbReader != null) { upbReader.shutdown(); } if (upbWriter != null) { upbWriter.shutdown(); } if (serialPort != null) { logger.debug("Closing serial port"); serialPort.close(); } } /** * {@inheritDoc} */ @Override protected long getRefreshInterval() { return refreshInterval; } /** * {@inheritDoc} */ @Override protected String getName() { return "UPB Service"; } /** * {@inheritDoc} */ @Override protected void execute() { // the frequently executed code (polling) goes here ... for (UPBBindingProvider p : providers) { for (String s : p.getItemNames()) { UPBBindingConfig config = p.getConfig(s); if (!config.isLink()) { MessageBuilder message = MessageBuilder.create().network(network).destination(config.getId()) .command(UPBMessage.Command.REPORT_STATE.toByte()); // Here we write the command to the PIM. upbWriter.queueMessage(message); } } } } /** * {@inheritDoc} */ @Override protected void internalReceiveCommand(String itemName, Command command) { UPBBindingConfig config = getConfig(itemName); if (config != null) { byte[] commandByte = { UPBMessage.Command.DEACTIVATE.toByte() }; if (command == OnOffType.ON) { commandByte = new byte[] { UPBMessage.Command.ACTIVATE.toByte() }; } else if (command instanceof PercentType) { commandByte = new byte[] { UPBMessage.Command.GOTO.toByte(), ((PercentType) command).byteValue() }; } MessageBuilder message = MessageBuilder.create().network(network).destination(config.getId()) .link(config.isLink()).command(commandByte); // Here we write the command to the PIM. upbWriter.queueMessage(message); } } private String getItemName(byte id, boolean link) { for (UPBBindingProvider p : providers) { for (String itemName : p.getItemNames()) { UPBBindingConfig config = p.getConfig(itemName); if (config != null && config.getId() == id && config.isLink() == link) { return itemName; } } } return null; } private UPBBindingConfig getConfig(String itemName) { for (UPBBindingProvider p : providers) { UPBBindingConfig config = p.getConfig(itemName); if (config != null) { return config; } } return null; } private SerialPort openSerialPort() { SerialPort serialPort = null; CommPortIdentifier portId; try { portId = CommPortIdentifier.getPortIdentifier(port); } catch (NoSuchPortException e1) { throw new RuntimeException("Port does not exist", e1); } if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) { if (portId.getName().equals(port)) { try { serialPort = portId.open("UPB", 1000); } catch (PortInUseException e) { throw new RuntimeException("Port is in use", e); } try { serialPort.setSerialPortParams(4800, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE); serialPort.enableReceiveTimeout(100); } catch (UnsupportedCommOperationException e) { throw new RuntimeException("Failed to configure serial port"); } } } return serialPort; } /** * {@inheritDoc} */ @Override public void messageReceived(UPBMessage message) { if (message.getType() != Type.MESSAGE_REPORT) { return; } String sourceName = getItemName(message.getSource(), false); String destinationName = getItemName(message.getDestination(), message.getControlWord().isLink()); UPBBindingConfig sourceConfig = getConfig(sourceName); UPBBindingConfig destinationConfig = getConfig(destinationName); String itemName = isValidId(message.getDestination()) ? destinationName : sourceName; UPBBindingConfig config = isValidId(message.getDestination()) ? destinationConfig : sourceConfig; if (itemName == null || config == null) { logger.debug("Received message for unknown {} with id {}.", message.getControlWord().isLink() ? "Link" : "Device", message.getDestination() & 0xff); return; } State newState = null; byte level = 100; switch (message.getCommand()) { case GOTO: case DEVICE_STATE: case ACTIVATE: if (message.getArguments() != null && message.getArguments().length > 0) { level = message.getArguments()[0]; } else { level = (byte) (message.getCommand() == UPBMessage.Command.ACTIVATE ? 100 : 0); } // Links will send FF (-1) for their level. if (level == -1 || level >= 100 || (level > 0 && !config.isDimmable())) { newState = OnOffType.ON; } else if (level == 0) { newState = OnOffType.OFF; } else { newState = new PercentType(level); } break; case DEACTIVATE: newState = OnOffType.OFF; break; default: break; } if (newState != null) { logger.debug("Posting update: {},{}", itemName, newState); eventPublisher.postUpdate(itemName, newState); } } private boolean isValidId(byte id) { return id != 0 && id != -1; } }