/**
*
* 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/>.
*/
package com.freedomotic.plugins.devices.phwswethv2;
import com.freedomotic.api.EventTemplate;
import com.freedomotic.api.Protocol;
import com.freedomotic.app.Freedomotic;
import com.freedomotic.events.ProtocolRead;
import com.freedomotic.exceptions.UnableToExecuteException;
import com.freedomotic.reactions.Command;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.codec.binary.Base64;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
public class ProgettiHwSwEthv2 extends Protocol {
private static final Logger LOG = Logger.getLogger(ProgettiHwSwEthv2.class.getName());
private static ArrayList<Board> boards = null;
private Map<String, Board> devices = new HashMap<String, Board>();
private Set<String> keySet = null;
private static int BOARD_NUMBER = 1;
private static int POLLING_TIME = 1000;
private Socket socket = null;
private DataOutputStream outputStream = null;
private BufferedReader inputStream = null;
private String[] address = null;
private int SOCKET_TIMEOUT = configuration.getIntProperty("socket-timeout", 1000);
private String GET_STATUS_URL = configuration.getStringProperty("get-status-url", "status.xml");
private String CHANGE_STATE_RELAY_URL = configuration.getStringProperty("change-state-relay-url", "forms.htm?led");
private String TOGGLE_RELAY_URL = configuration.getStringProperty("toggle-relay-url", "toggle.cgi?toggle=");
/**
* Initializations
*/
public ProgettiHwSwEthv2() {
super("ProgettiHwSwEth", "/phwswethv2/phwswethv2-manifest.xml");
setPollingWait(POLLING_TIME);
}
private void loadBoards() {
if (boards == null) {
boards = new ArrayList<Board>();
}
if (devices == null) {
devices = new HashMap<String, Board>();
}
setDescription("Reading status changes from"); //empty description
for (int i = 0; i < BOARD_NUMBER; i++) {
String ipToQuery;
String ledTag;
String tempTag;
String digitalInputTag;
String analogInputTag;
String autoConfiguration;
String objectClass;
String alias;
String monitorRelay;
String monitorTemperature;
String monitorAnalogInput;
String monitorDigitalInput;
String authentication;
String username;
String password;
int portToQuery;
int digitalInputNumber;
int analogInputNumber;
int relayNumber;
int temperatureNumber;
int startingRelay;
ipToQuery = configuration.getTuples().getStringProperty(i, "ip-to-query", "192.168.1.201");
portToQuery = configuration.getTuples().getIntProperty(i, "port-to-query", 80);
alias = configuration.getTuples().getStringProperty(i, "alias", "default");
relayNumber = configuration.getTuples().getIntProperty(i, "relay-number", 8);
temperatureNumber = configuration.getTuples().getIntProperty(i, "temperature-number", 1);
analogInputNumber = configuration.getTuples().getIntProperty(i, "analog-input-number", 4);
digitalInputNumber = configuration.getTuples().getIntProperty(i, "digital-input-number", 4);
startingRelay = configuration.getTuples().getIntProperty(i, "starting-relay", 0);
authentication = configuration.getTuples().getStringProperty(i, "authentication", "false");
username = configuration.getTuples().getStringProperty(i, "username", "ftp");
password = configuration.getTuples().getStringProperty(i, "password", "2406");
ledTag = configuration.getTuples().getStringProperty(i, "led-tag", "led");
tempTag = configuration.getTuples().getStringProperty(i, "temp-tag", "temp");
digitalInputTag = configuration.getTuples().getStringProperty(i, "digital-input-tag", "btn");
analogInputTag = configuration.getTuples().getStringProperty(i, "analog-input-tag", "pot");
autoConfiguration = configuration.getTuples().getStringProperty(i, "auto-configuration", "false");
monitorRelay = configuration.getTuples().getStringProperty(i, "monitor-relay", "true");
monitorTemperature = configuration.getTuples().getStringProperty(i, "monitor-temperature", "true");
monitorAnalogInput = configuration.getTuples().getStringProperty(i, "monitor-analog-input", "true");
monitorDigitalInput = configuration.getTuples().getStringProperty(i, "monitor-digital-input", "true");
objectClass = configuration.getTuples().getStringProperty(i, "object.class", "Light");
Board board = new Board(ipToQuery, portToQuery, alias, relayNumber, temperatureNumber, analogInputNumber,
digitalInputNumber, startingRelay, ledTag, tempTag, digitalInputTag, analogInputTag, autoConfiguration,
objectClass, monitorRelay, monitorTemperature, monitorAnalogInput, monitorDigitalInput,
authentication, username, password);
boards.add(board);
// add board object and its alias as key for the hashmap
devices.put(alias, board);
setDescription(getDescription() + " " + ipToQuery + ":" + portToQuery + ";");
}
}
/**
* Connection to boards
*/
private boolean connect(String address, int port) {
LOG.info("Trying to connect to ProgettiHwSw board on address " + address + ':' + port);
try {
//TimedSocket is a non-blocking socket with timeout on exception
socket = TimedSocket.getSocket(address, port, SOCKET_TIMEOUT);
socket.setSoTimeout(SOCKET_TIMEOUT); //SOCKET_TIMEOUT ms of waiting on socket read/write
BufferedOutputStream buffOut = new BufferedOutputStream(socket.getOutputStream());
outputStream = new DataOutputStream(buffOut);
return true;
} catch (IOException e) {
LOG.severe("Unable to connect to host " + address + " on port " + port);
return false;
}
}
private void disconnect() {
// close streams and socket
try {
inputStream.close();
outputStream.close();
socket.close();
} catch (Exception ex) {
//do nothing. Best effort
}
}
/**
* Sensor side
*/
@Override
public void onStart() {
POLLING_TIME = configuration.getIntProperty("polling-time", 1000);
BOARD_NUMBER = configuration.getTuples().size();
setPollingWait(POLLING_TIME);
loadBoards();
// select all boards in the devices hashmap to evaluate their status
keySet = devices.keySet();
}
@Override
public void onStop() {
//release resources
boards.clear();
boards = null;
devices.clear();
devices = null;
setPollingWait(-1); //disable polling
//display the default description
setDescription(configuration.getStringProperty("description", "ProgettiHwSwEth"));
}
@Override
protected void onRun() {
for (String key : keySet) {
Board board = devices.get(key);
//System.out.println("Richiesta per "+board.getAlias());
evaluateDiffs(getXMLStatusFile(board), board);
}
try {
Thread.sleep(POLLING_TIME);
} catch (InterruptedException ex) {
Logger.getLogger(ProgettiHwSwEthv2.class.getName()).log(Level.SEVERE, null, ex);
}
}
private Document getXMLStatusFile(Board board) {
final Board b = board;
//get the xml file from the socket connection
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = null;
try {
dBuilder = dbFactory.newDocumentBuilder();
} catch (ParserConfigurationException ex) {
Logger.getLogger(ProgettiHwSwEthv2.class.getName()).log(Level.SEVERE, null, ex);
}
Document doc = null;
String statusFileURL = null;
try {
if (board.getAuthentication().equalsIgnoreCase("true")) {
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(b.getUsername(), b.getPassword().toCharArray());
}
});
statusFileURL = "http://" + b.getIpAddress() + ":"
+ Integer.toString(b.getPort()) + "/protect/" + GET_STATUS_URL;
} else {
statusFileURL = "http://" + b.getIpAddress() + ":"
+ Integer.toString(b.getPort()) + "/" + GET_STATUS_URL;
}
LOG.info("ProgettiHwSwEth gets relay status from file " + statusFileURL);
doc = dBuilder.parse(new URL(statusFileURL).openStream());
doc.getDocumentElement().normalize();
} catch (ConnectException connEx) {
disconnect();
this.stop();
this.setDescription("Connection timed out, no reply from the board at " + statusFileURL);
} catch (SAXException ex) {
disconnect();
this.stop();
LOG.severe(Freedomotic.getStackTraceInfo(ex));
} catch (Exception ex) {
disconnect();
this.stop();
setDescription("Unable to connect to " + statusFileURL);
LOG.severe(Freedomotic.getStackTraceInfo(ex));
}
return doc;
}
private void evaluateDiffs(Document doc, Board board) {
//parses xml
if (doc != null && board != null) {
Node n = doc.getFirstChild();
if (board.getMonitorRelay().equalsIgnoreCase("true")) {
valueTag(doc, board, board.getRelayNumber(), board.getLedTag(), 0);
}
if (board.getMonitorTemperature().equalsIgnoreCase("true")) {
valueTag(doc, board, board.getTemperatureNumber(), board.getTempTag(), 0);
}
if (board.getMonitorDigitalInput().equalsIgnoreCase("true")) {
valueTag(doc, board, board.getDigitalInputNumber(), board.getDigitalInputTag(), 0);
}
if (board.getMonitorAnalogInput().equalsIgnoreCase("true")) {
valueTag(doc, board, board.getAnalogInputNumber(), board.getAnalogInputTag(), 0);
}
}
}
private void valueTag(Document doc, Board board, Integer nl, String tag, int startingRelay) {
for (int i = startingRelay; i < nl; i++) {
try {
String tagName = tag + HexIntConverter.convert(i);
// control for storing value
if (tag.equalsIgnoreCase(board.getLedTag())) {
if (!(board.getRelayStatus(i) == Integer.parseInt(doc.getElementsByTagName(tagName).item(0).getTextContent()))) {
sendChanges(i, board, doc.getElementsByTagName(tagName).item(0).getTextContent(), tag);
board.setRelayStatus(i, Integer.parseInt(doc.getElementsByTagName(tagName).item(0).getTextContent()));
}
}
if (tag.equalsIgnoreCase(board.getTempTag())) {
if (!(board.getTemperatureStatus(i) == Float.parseFloat(doc.getElementsByTagName(tagName).item(0).getTextContent()))) {
sendChanges(i, board, doc.getElementsByTagName(tagName).item(0).getTextContent(), tag);
board.setTemperatureStatus(i, Float.parseFloat(doc.getElementsByTagName(tagName).item(0).getTextContent()));
}
}
if (tag.equalsIgnoreCase(board.getAnalogInputTag())) {
if (!(board.getAnalogInputValue(i) == Integer.parseInt(doc.getElementsByTagName(tagName).item(0).getTextContent()))) {
sendChanges(i, board, doc.getElementsByTagName(tagName).item(0).getTextContent(), tag);
board.setAnalogInputValue(i, Integer.parseInt(doc.getElementsByTagName(tagName).item(0).getTextContent()));
}
}
if (tag.equalsIgnoreCase(board.getDigitalInputTag())) {
if (!(board.getDigitalInputValue(i) == doc.getElementsByTagName(tagName).item(0).getTextContent())) {
sendChanges(i, board, doc.getElementsByTagName(tagName).item(0).getTextContent(), tag);
board.setDigitalInputValue(i, doc.getElementsByTagName(tagName).item(0).getTextContent());
}
}
} catch (DOMException dOMException) {
//do nothing
LOG.severe("DOMException " + dOMException);
} catch (NumberFormatException numberFormatException) {
//do nothing
} catch (NullPointerException ex) {
//do nothing
}
}
}
private void sendChanges(int relayLine, Board board, String status, String tag) {
// if starting-relay = 0 then increments relayLine to start from 1 not from zero
if (board.getStartingRelay() == 0) {
relayLine++;
}
//reconstruct freedomotic object address
String address = board.getAlias() + ":" + relayLine + ":" + tag;
LOG.info("Sending ProgettiHwSw protocol read event for object address '" + address + "'. It's readed status is " + status);
//building the event
ProtocolRead event = new ProtocolRead(this, "phwswethv2", address); //IP:PORT:RELAYLINE
// relay lines - status=0 -> off; status=1 -> on
if (tag.equalsIgnoreCase(board.getLedTag())) {
if (status.equals("0")) {
event.addProperty("isOn", "false");
} else {
event.addProperty("isOn", "true");
//if autoconfiguration is true create an object if not already exists
if (board.getAutoConfiguration().equalsIgnoreCase("true")) {
event.addProperty("object.class", board.getObjectClass());
event.addProperty("object.name", address);
}
}
} else // digital inputs status = up -> off/open; status = dn -> on/closed
if (tag.equalsIgnoreCase(board.getDigitalInputTag())) {
if (status.equalsIgnoreCase("up")) {
event.addProperty("isOn", "false");
event.addProperty("isOpen", "true");
} else {
event.addProperty("isOn", "true");
event.addProperty("isOpen", "false");
}
} else // temperature inputs value = float/number
if (tag.equalsIgnoreCase(board.getTempTag())) {
event.addProperty("sensor.temperature", status);
} else {
// analog inputs status = 0 -> off; status > 0 -> on
if (tag.equalsIgnoreCase(board.getAnalogInputTag())) {
if (status.equalsIgnoreCase("0")) {
event.addProperty("isOn", "false");
} else {
event.addProperty("isOn", "true");
}
event.addProperty("analog.input.value", status);
}
}
//publish the event on the messaging bus
this.notifyEvent(event);
}
/**
* Actuator side
*/
@Override
public void onCommand(Command c) throws UnableToExecuteException {
String delimiter = configuration.getProperty("address-delimiter");
address = c.getProperty("address").split(delimiter);
Board board = (Board) devices.get(address[0]);
if (c.getProperty("command").equals("CHANGE-STATE-RELAY")) {
changeRelayStatus(board, c);
}
if (c.getProperty("command").equals("TOGGLE-RELAY")) {
toggleRelay(board, c);
}
}
private void changeRelayStatus(Board board, Command c) {
try {
URL url = null;
URLConnection urlConnection;
String delimiter = configuration.getProperty("address-delimiter");
String[] address = c.getProperty("address").split(delimiter);
String relayNumber = HexIntConverter.convert(Integer.parseInt(address[1]) - 1);
// if required set the authentication
if (board.getAuthentication().equalsIgnoreCase("true")) {
String authString = board.getUsername() + ":" + board.getPassword();
byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
String authStringEnc = new String(authEncBytes);
//Create a URL for the desired page
url = new URL("http://" + board.getIpAddress() + ":" + board.getPort() + "/protect/" + CHANGE_STATE_RELAY_URL + relayNumber + "=" + c.getProperty("status"));
urlConnection = url.openConnection();
urlConnection.setRequestProperty("Authorization", "Basic " + authStringEnc);
} else {
//Create a URL for the desired page
url = new URL("http://" + board.getIpAddress() + ":" + board.getPort() + "/" + CHANGE_STATE_RELAY_URL + relayNumber + "=" + c.getProperty("status"));
urlConnection = url.openConnection();
}
LOG.info("Freedomotic sends the command " + url);
InputStream is = urlConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int numCharsRead;
char[] charArray = new char[1024];
StringBuffer sb = new StringBuffer();
while ((numCharsRead = isr.read(charArray)) > 0) {
sb.append(charArray, 0, numCharsRead);
}
String result = sb.toString();
} catch (MalformedURLException e) {
LOG.severe("Change relay status malformed URL " + e.toString());
} catch (IOException e) {
LOG.severe("Change relay status IOexception" + e.toString());
}
}
private void toggleRelay(Board board, Command c) {
try {
URL url = null;
URLConnection urlConnection;
String delimiter = configuration.getProperty("address-delimiter");
String[] address = c.getProperty("address").split(delimiter);
String relayNumber = address[1];
int time = Integer.parseInt(c.getProperty("time-in-ms"));
int seconds = time / 1000;
String relayLine = configuration.getProperty("TOGGLE" + seconds + "S" + relayNumber);
// if required set the authentication
if (board.getAuthentication().equalsIgnoreCase("true")) {
String authString = board.getUsername() + ":" + board.getPassword();
byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
String authStringEnc = new String(authEncBytes);
//Create a URL for the desired page
url = new URL("http://" + board.getIpAddress() + ":" + board.getPort() + "/protect/" + TOGGLE_RELAY_URL + relayLine);
urlConnection = url.openConnection();
urlConnection.setRequestProperty("Authorization", "Basic " + authStringEnc);
} else {
//Create a URL for the desired page
url = new URL("http://" + board.getIpAddress() + ":" + board.getPort() + "/" + TOGGLE_RELAY_URL + relayLine);
urlConnection = url.openConnection();
}
LOG.info("Freedomotic sends the command " + url);
InputStream is = urlConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int numCharsRead;
char[] charArray = new char[1024];
StringBuffer sb = new StringBuffer();
while ((numCharsRead = isr.read(charArray)) > 0) {
sb.append(charArray, 0, numCharsRead);
}
String result = sb.toString();
} catch (MalformedURLException e) {
LOG.severe("Change relay status malformed URL " + e.toString());
} catch (IOException e) {
LOG.severe("Change relay status IOexception" + e.toString());
}
}
@Override
protected boolean canExecute(Command c) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
protected void onEvent(EventTemplate event) {
throw new UnsupportedOperationException("Not supported yet.");
}
// retrieve a key from value in the hashmap
public static Object getKeyFromValue(Map hm, Object value) {
for (Object o : hm.keySet()) {
if (hm.get(o).equals(value)) {
return o;
}
}
return null;
}
}