/** * * Copyright (c) 2009-2016 Freedomotic team http://freedomotic.com * * This file is part of Freedomotic * * This Program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * This Program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * Freedomotic; see the file COPYING. If not, see * <http://www.gnu.org/licenses/>. */ /** * @author Mauro Cicolella */ package com.freedomotic.plugins.devices.mysensors; import com.freedomotic.api.EventTemplate; import com.freedomotic.api.Protocol; import com.freedomotic.events.ProtocolRead; import com.freedomotic.exceptions.PluginStartupException; import com.freedomotic.exceptions.UnableToExecuteException; import com.freedomotic.helpers.SerialHelper; import com.freedomotic.helpers.SerialPortListener; import com.freedomotic.reactions.Command; import com.freedomotic.things.EnvObjectLogic; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import jssc.SerialPortException; public class MySensors extends Protocol { private static final Logger LOG = Logger.getLogger(MySensors.class.getName()); // Type of message private final String PRESENTATION = "0"; private final String SET_VARIABLE = "1"; private final String REQ_VARIABLE = "2"; private final String INTERNAL = "3"; private final String STREAM = "3"; private String PORTNAME = configuration.getStringProperty("serial.port", "/dev/ttyACM0"); private Integer BAUDRATE = configuration.getIntProperty("serial.baudrate", 9600); private Integer DATABITS = configuration.getIntProperty("serial.databits", 8); private Integer PARITY = configuration.getIntProperty("serial.parity", 0); private Integer STOPBITS = configuration.getIntProperty("serial.stopbits", 1); private SerialHelper serial; Map<String, String> deviceNodeIDTable = new HashMap<>(); public MySensors() { super("MySensors", "/mysensors/mysensors-manifest.xml"); setPollingWait(-1); //disables polling } @Override public void onStart() throws PluginStartupException { loadConfiguredObjects(); try { serial = new SerialHelper(PORTNAME, BAUDRATE, DATABITS, STOPBITS, PARITY, new SerialPortListener() { @Override public void onDataAvailable(String data) { LOG.log(Level.INFO, "MySensors received ''{0}'' ", data); manageMessage(data); } }); serial.setChunkTerminator("\n"); } catch (SerialPortException ex) { throw new PluginStartupException("Error: " + ex.getMessage(), ex); } } @Override protected void onRun() { } @Override public void onStop() { if (serial != null) { if (serial.disconnect()) { serial = null; } else { LOG.log(Level.WARNING, "Impossible to disconnect from ''{0}'' ", serial.getPortName()); } } } @Override protected void onCommand(Command c) throws IOException, UnableToExecuteException { String address = c.getProperty("address"); String IDMessageType = c.getProperty("id-message-type"); String ack = c.getProperty("ack"); String subType = c.getProperty("sub-type"); String payload = c.getProperty("payload"); String message = address + ";" + IDMessageType + ";" + ack + ";" + subType + ";" + payload + "\n"; writeSerialData(message); } @Override protected boolean canExecute(Command c) { throw new UnsupportedOperationException("Not supported yet."); } @Override protected void onEvent(EventTemplate event) { throw new UnsupportedOperationException("Not supported yet."); } private void manageMessage(String data) { String nodeID; String childSensorID; String messageType; String ack; String subType; String payload; String[] message = data.split(";"); if (message.length >= 5) { nodeID = message[0]; childSensorID = message[1]; messageType = message[2]; ack = message[3]; subType = message[4]; payload = message[5]; switch (messageType) { case INTERNAL: manageInternalMessage(nodeID, childSensorID, ack, subType, payload); break; case SET_VARIABLE: manageSetMessage(nodeID, childSensorID, ack, subType, payload); break; } } else { LOG.log(Level.WARNING, "Data format incorrect"); } } private void manageSetMessage(String nodeID, String childSensorID, String ack, String subType, String payload) { ProtocolRead event = new ProtocolRead(this, "mysensors", nodeID + ";" + childSensorID); String subTypeName = SetReqSubType.fromInt(Integer.valueOf(subType)).toString(); String objectClass = configuration.getProperty(subTypeName); if (objectClass != null) { event.addProperty("object.class", objectClass); event.addProperty("object.name", objectClass + " " + nodeID + ":" + childSensorID); LOG.info("Created object " + objectClass + " with address " + nodeID + ":" + childSensorID); } // adds isOn property only for lights if (subType.equalsIgnoreCase("V_LIGHT")) { if (payload.equalsIgnoreCase("1")) { event.addProperty("sensor.isOn", "true"); } else { event.addProperty("sensor.isOn", "false"); } } event.addProperty("sensor.value", payload); this.notifyEvent(event); } private void manageInternalMessage(String nodeID, String childSensorID, String ack, String subType, String payload) { InternalSubType internalSubType = InternalSubType.I_ID_REQUEST; switch (internalSubType) { case I_ID_REQUEST: if (nodeID.equalsIgnoreCase("255") && childSensorID.equalsIgnoreCase("255")) { // get a new available nodeID String newNodeID = getAvailableNodeID(); if (!newNodeID.equalsIgnoreCase("-1")) { LOG.log(Level.INFO, "Node-ID assigned ''{0}'' ", newNodeID); sendMessage(nodeID, childSensorID, INTERNAL, ack, subType, newNodeID); } else { LOG.log(Level.WARNING, "No more Node-ID available"); } } break; } } private void sendMessage(String nodeID, String childSensorID, String messageType, String ack, String subType, String payload) { InternalSubType internalSubType = InternalSubType.I_ID_REQUEST; StringBuilder messageToSend = new StringBuilder(); messageToSend.append(nodeID).append(";").append(childSensorID).append(";").append(messageType).append(";").append(ack).append(";").append(internalSubType.I_ID_RESPONSE).append(";").append(payload).append("\n"); writeSerialData(messageToSend.toString()); } /** * Returns an available NodeID for AUTO-id feature * * @return * @throws Exception */ private String getAvailableNodeID() { boolean addressAvailable = false; int i = 0; for (i = 1; i < 254; i++) { if (!deviceNodeIDTable.containsKey(String.valueOf(i))) { addressAvailable = true; break; } } if (addressAvailable) { deviceNodeIDTable.put(String.valueOf(i), null); return String.valueOf(i); } return ("-1"); } /* * Creates a list of configured objects in Freedomotic to determine the next * available nodeID for AUTO-ID feature */ private void loadConfiguredObjects() { ArrayList<EnvObjectLogic> configuredObjects = (ArrayList<EnvObjectLogic>) getApi().things().findByProtocol("mysensors"); for (EnvObjectLogic obj : configuredObjects) { String phisicalAddress = obj.getPojo().getPhisicalAddress(); String[] addrComponents = phisicalAddress.split("\n"); deviceNodeIDTable.put(addrComponents[0], obj.getPojo().getName()); } } private void writeSerialData(String data) { try { LOG.log(Level.INFO, "Sending ''{0}'' to MySensors gateway", data); serial.write(data); } catch (SerialPortException ex) { LOG.log(Level.SEVERE, "Impossible to write on serial for " + ex.getLocalizedMessage()); } } }