/** * Copyright (c) 2009-2016 Freedomotic team http://freedomotic.com * <p> * This file is part of Freedomotic * <p> * 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. * <p> * 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. * <p> * 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/>. */ package com.freedomotic.plugins.devices.arduinows; import com.freedomotic.api.EventTemplate; import com.freedomotic.api.Protocol; import com.freedomotic.events.ProtocolRead; import com.freedomotic.exceptions.PluginRuntimeException; import com.freedomotic.exceptions.PluginStartupException; import com.freedomotic.exceptions.UnableToExecuteException; import com.freedomotic.helpers.HttpHelper; import com.freedomotic.reactions.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A sensor for the Arduino Weather Shield developed by www.ethermania.com * * @author Mauro Cicolella */ public class ArduinoWeatherShield extends Protocol { private static final Logger LOG = LoggerFactory.getLogger(ArduinoWeatherShield.class.getName()); private final int POLLING_TIME = configuration.getIntProperty("time-between-reads", 1000); private final String DELIMITER = configuration.getStringProperty("delimiter", ":"); private final int TUPLES_COUNT = configuration.getTuples().size(); private final int MAX_FAILURES = configuration.getIntProperty("max-failures", 0); private List<Board> boards = new ArrayList<>(); private HttpHelper http = new HttpHelper(); /** * */ public ArduinoWeatherShield() { super("Arduino WeatherShield", "/arduino-weathershield/arduinows-manifest.xml"); } @Override public void onStart() throws PluginStartupException { setPollingWait(POLLING_TIME); http.setConnectionTimeout(5000); loadBoards(); if (boards.isEmpty()) { throw new PluginStartupException("No boards defined in confguration file or configuration format is wrong"); } setDescription(boards.size() + " board(s) configured"); } @Override public void onStop() { //release resources boards.clear(); boards = null; setPollingWait(-1); //disable polling //display the default description setDescription(configuration.getStringProperty("description", "ArduinoWeatherShield")); } @Override protected void onRun() throws PluginRuntimeException { for (Board board : boards) { readValuesWithRetry(board, MAX_FAILURES); } } private void loadBoards() { if (boards == null) { boards = new ArrayList<>(); } for (int i = 0; i < TUPLES_COUNT; i++) { // filter the tuples with "object.class" property String result = configuration.getTuples().getProperty(i, "object.class"); // if the tuple hasn't an "object.class" property it's a board configuration one if (result == null) { String IP_TO_QUERY = configuration.getTuples().getStringProperty(i, "ip-to-query", "192.168.0.150"); int PORT_TO_QUERY = configuration.getTuples().getIntProperty(i, "port-to-query", 80); Board board = new Board(IP_TO_QUERY, PORT_TO_QUERY); LOG.info("Arduino Weathershield board configured to be" + " queried at {}:{}", new Object[]{IP_TO_QUERY, PORT_TO_QUERY}); boards.add(board); } } } private void readValues(Board board) throws IOException { String urlString = "http://" + board.getIpAddress() + ":" + board.getPort(); String data = http.retrieveContent(urlString); String dataSensors = parseBody(data); if (!dataSensors.isEmpty()) { LOG.info("Read data from Arduino Weathershield: {}", dataSensors); try { notifyReadValues(board, dataSensors); } catch (IllegalArgumentException ex) { notifyCriticalError("Error while notifying Arduino Weathershield data", ex); } } } private void readValuesWithRetry(Board board, int maxFailures) throws PluginRuntimeException { try { readValues(board); } catch (IOException e) { if (maxFailures > 0) { readValuesWithRetry(board, --maxFailures); } else { throw new PluginRuntimeException("Cannot connect to Arduino Weathershield board at " + board.getIpAddress() + ":" + board.getPort(), ex); } } } private void notifyReadValues(Board board, String parametersValue) throws IllegalArgumentException { String address = board.getIpAddress() + DELIMITER + board.getPort(); String values[] = parametersValue.split(DELIMITER); if (!(values.length == 3)) { throw new IllegalArgumentException("Cannot parse string " + parametersValue + " to extract three numeric values separated by '" + DELIMITER + "'"); } // disabled control for now - looking for a good isNumber implementation //else { // for (int i = 0; i < 3; i++) { // if (!isNumber(values[i])) { // throw new IllegalArgumentException("Error while parsing string '" // + parametersValue + "'" + values[i] + "' is not a number"); // } //} //} //building the event ProtocolRead event = new ProtocolRead(this, "arduino-weathershield", address + DELIMITER + "T"); //adding some optional information to the event event.addProperty("board.ip", board.getIpAddress()); event.addProperty("board.port", Integer.toString(board.getPort())); event.addProperty("sensor.temperature", values[0]); event.addProperty("object.class", "Thermometer"); event.addProperty("autodiscovery.allow-clones", "false"); event.addProperty("object.name", "Arduino WeatherShield Thermometer"); //publish the event on the messaging bus this.notifyEvent(event); event = new ProtocolRead(this, "arduino-weathershield", address + DELIMITER + "P"); //adding some optional information to the event event.addProperty("board.ip", board.getIpAddress()); event.addProperty("board.port", Integer.toString(board.getPort())); event.addProperty("sensor.pressure", values[1]); event.addProperty("object.class", "Barometer"); event.addProperty("autodiscovery.allow-clones", "false"); event.addProperty("object.name", "Arduino WeatherShield Barometer"); //publish the event on the messaging bus this.notifyEvent(event); event = new ProtocolRead(this, "arduino-weathershield", address + DELIMITER + "H"); //adding some optional information to the event event.addProperty("board.ip", board.getIpAddress()); event.addProperty("board.port", Integer.toString(board.getPort())); event.addProperty("sensor.humidity", values[2]); event.addProperty("object.class", "Hygrometer"); event.addProperty("autodiscovery.allow-clones", "false"); event.addProperty("object.name", "Arduino WeatherShield Hygrometer"); //publish the event on the messaging bus this.notifyEvent(event); } private boolean isNumber(String stringNumber) { //TODO: this regex is far from being precise, test it with http://www.regexr.com/ return stringNumber.matches("-?\\d+(\\.\\d+)?"); } @Override protected void onCommand(Command c) throws UnableToExecuteException { throw new UnsupportedOperationException("Not supported yet."); } @Override protected boolean canExecute(Command c) { throw new UnsupportedOperationException("Not supported yet."); } @Override protected void onEvent(EventTemplate event) { throw new UnsupportedOperationException("Not supported yet."); } // extract sensors data from <body></body> tags private static String parseBody(String html) { Pattern p = Pattern.compile("<body.*?>(?<Content>([^<]|<[^/]|</[^b]|</b[^o])*)", Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(html); if (m.find()) { String element = m.group(1); return element; } else { return ""; } } }