/**
*
* 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.phwsw;
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.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
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.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 ETHProgettiHwSw extends Protocol {
private static ArrayList<Board> boards = 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);
/**
* Initializations
*/
public ETHProgettiHwSw() {
super("ETH Progetti HwSw", "/phwsw/progettihwsw-manifest.xml");
setPollingWait(POLLING_TIME);
}
private void loadBoards() {
if (boards == null) {
boards = new ArrayList<Board>();
}
setDescription("Reading status changes from"); //empty description
for (int i = 0; i < BOARD_NUMBER; i++) {
String ipToQuery;
String lineToMonitorize;
int portToQuery;
int digitalInputNumber;
int analogInputNumber;
int relayNumber;
int startingRelay;
ipToQuery = configuration.getTuples().getStringProperty(i, "ip-to-query", "192.168.1.201");
portToQuery = configuration.getTuples().getIntProperty(i, "port-to-query", 80);
lineToMonitorize = configuration.getTuples().getStringProperty(i, "line-to-monitorize", "led");
relayNumber = configuration.getTuples().getIntProperty(i, "relay-number", 8);
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);
Board board = new Board(ipToQuery, portToQuery, relayNumber, analogInputNumber,
digitalInputNumber, lineToMonitorize, startingRelay);
boards.add(board);
setDescription(getDescription() + " " + ipToQuery + ":" + portToQuery + ":" + lineToMonitorize + ";");
}
}
/**
* Connection to boards
*/
private boolean connect(String address, int port) {
Freedomotic.logger.info("Trying to connect to ethernet relay 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) {
Freedomotic.logger.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() {
super.onStart();
POLLING_TIME = configuration.getIntProperty("polling-time", 1000);
BOARD_NUMBER = configuration.getTuples().size();
setPollingWait(POLLING_TIME);
loadBoards();
}
@Override
public void onStop() {
super.onStop();
//release resources
boards.clear();
boards = null;
setPollingWait(-1); //disable polling
//display the default description
setDescription(configuration.getStringProperty("description", "ETH Progetti HWSW"));
}
@Override
protected void onRun() {
for (Board board : boards) {
evaluateDiffs(getXMLStatusFile(board), board); //parses the xml and crosscheck the data with the previous read
try {
Thread.sleep(POLLING_TIME);
} catch (InterruptedException ex) {
Logger.getLogger(ETHProgettiHwSw.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private Document getXMLStatusFile(Board 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(ETHProgettiHwSw.class.getName()).log(Level.SEVERE, null, ex);
}
Document doc = null;
String statusFileURL = null;
try {
statusFileURL = "http://" + board.getIpAddress() + ":"
+ Integer.toString(board.getPort()) + "/status.xml";
Freedomotic.logger.info("Progetti HWSW Eth Sensor 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();
Freedomotic.logger.severe(Freedomotic.getStackTraceInfo(ex));
} catch (Exception ex) {
disconnect();
this.stop();
setDescription("Unable to connect to " + statusFileURL);
Freedomotic.logger.severe(Freedomotic.getStackTraceInfo(ex));
}
return doc;
}
private void evaluateDiffs(Document doc, Board board) {
//parses xml
if (doc != null && board != null) {
Node n = doc.getFirstChild();
NodeList nl = n.getChildNodes();
int startingRelay = board.getStartingRelay();
for (int i = startingRelay; i < nl.getLength(); i++) {
try {
// converts i into hexadecimal value (string) and sends the parameters
String tagName = board.getLineToMonitorize() + HexIntConverter.convert(i);
Freedomotic.logger.severe("Progetti HWSW Sensor monitorize tags " + tagName);
sendChanges(i, board, doc.getElementsByTagName(tagName).item(0).getTextContent());
} catch (DOMException dOMException) {
//do nothing
} catch (NumberFormatException numberFormatException) {
//do nothing
} catch (NullPointerException ex) {
//do nothing
}
}
}
}
private void sendChanges(int relayLine, Board board, String status) {
// if starting-relay = 0 then increments relayLine to start from 1 not from zero
if (board.getStartingRelay() == 0) {
relayLine++;
}
//reconstruct freedom object address
String address = board.getIpAddress() + ":" + board.getPort() + ":" + relayLine;
Freedomotic.logger.info("Sending Progetti HWSW protocol read event for object address '" + address + "'. It's readed status is " + status);
//building the event
ProtocolRead event = new ProtocolRead(this, "SNT084Eth8R8I", address); //IP:PORT:RELAYLINE
// relay lines - status=0 -> off; status=1 -> on
if (board.getLineToMonitorize().equalsIgnoreCase("led")) {
if (status.equals("0")) {
event.addProperty("isOn", "false");
} else {
event.addProperty("isOn", "true");
}
} else // digital inputs - status=up -> off; status=dn -> on
if (board.getLineToMonitorize().equalsIgnoreCase("btn")) {
if (status.equalsIgnoreCase("up")) {
event.addProperty("isOn", "false");
} else {
event.addProperty("isOn", "true");
}
} else {
// analog inputs - status=0 -> off; status>0 -> on
if (board.getLineToMonitorize().equalsIgnoreCase("ptn")) {
if (status.equalsIgnoreCase("0")) {
event.addProperty("isOn", "false");
} else {
event.addProperty("isOn", "true");
}
}
}
//adding some optional information to the event
event.addProperty("boardIP", board.getIpAddress());
event.addProperty("boardPort", new Integer(board.getPort()).toString());
event.addProperty("relayLine", new Integer(relayLine).toString());
//publish the event on the messaging bus
this.notifyEvent(event);
}
/**
* Actuator side
*/
@Override
public void onCommand(Command c) throws UnableToExecuteException {
//get connection paramentes address:port from received freedom command
String delimiter = configuration.getProperty("address-delimiter");
address = c.getProperty("address").split(delimiter);
//connect to the ethernet board
boolean connected = false;
try {
connected = connect(address[0], Integer.parseInt(address[1]));
} catch (ArrayIndexOutOfBoundsException outEx) {
Freedomotic.logger.severe("The object address '" + c.getProperty("address") + "' is not properly formatted. Check it!");
throw new UnableToExecuteException();
} catch (NumberFormatException numberFormatException) {
Freedomotic.logger.severe(address[1] + " is not a valid ethernet port to connect to");
throw new UnableToExecuteException();
}
if (connected) {
String message = createMessage(c);
String expectedReply = c.getProperty("expected-reply");
try {
String reply = sendToBoard(message);
if ((reply != null) && (!reply.equals(expectedReply))) {
//TODO: implement reply check
}
} catch (IOException iOException) {
setDescription("Unable to send the message to host " + address[0] + " on port " + address[1]);
Freedomotic.logger.severe("Unable to send the message to host " + address[0] + " on port " + address[1]);
throw new UnableToExecuteException();
} finally {
disconnect();
}
} else {
throw new UnableToExecuteException();
}
}
private String sendToBoard(String message) throws IOException {
String receivedReply = null;
if (outputStream != null) {
outputStream.writeBytes(message);
outputStream.flush();
inputStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
try {
receivedReply = inputStream.readLine(); // read device reply
} catch (IOException iOException) {
throw new IOException();
}
}
return receivedReply;
}
// create message to send to the board
// this part must be changed to relect board protocol
public String createMessage(Command c) {
String message = null;
String page = null;
String behavior = null;
String relay = null;
if (c.getProperty("command").equals("RELAY")) {
// convert relay number (integer) into hexadecimal value (string) for board compatibility
relay = HexIntConverter.convert(Integer.parseInt(address[2]) - 1);
// convert freedom behavior(on/off) to board behavior (1/0)
if (c.getProperty("behavior").equals("on")) {
behavior = "1";
}
if (c.getProperty("behavior").equals("off")) {
behavior = "0";
}
page = "forms.htm?led" + relay + "=" + behavior;
}
if (c.getProperty("command").equals("TOGGLE")) {
// mapping relay line -> protocol
relay = address[2]; // toggle range from 1
int time = Integer.parseInt(c.getProperty("time-in-ms"));
int seconds = time / 1000;
String relayLine = configuration.getProperty("TOGGLE" + seconds + "S" + relay);
//compose requested link
page = "toggle.cgi?toggle=" + relayLine;
}
// http request sending to the board
message = "GET /" + page + " HTTP 1.1\r\n\r\n";
Freedomotic.logger.info("Sending 'GET /" + page + " HTTP 1.1' to relay board");
return (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.");
}
}