/**
*
* 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.zway;
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.things.EnvObjectLogic;
import com.freedomotic.reactions.Command;
import java.io.*;
import java.net.*;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.net.www.http.HttpClient;
/**
*
*
* @author Mauro Cicolella
*/
public class ZWay extends Protocol {
private static final Logger LOG = LoggerFactory.getLogger(ZWay.class.getName());
Map<String, Board> devices = new HashMap<String, Board>();
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 SEND_COMMAND_URL = configuration.getStringProperty("send-command-url", "ZWaveAPI/Run");
/**
* Initializations
*/
public ZWay() {
super("ZWay", "/zway/zway-manifest.xml");
setPollingWait(POLLING_TIME);
}
private void loadBoards() {
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 autoConfiguration;
String objectClass;
String alias;
int portToQuery;
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");
autoConfiguration = configuration.getTuples().getStringProperty(i, "auto-configuration", "false");
objectClass = configuration.getTuples().getStringProperty(i, "object.class", "Light");
Board board = new Board(ipToQuery, portToQuery, alias, autoConfiguration, objectClass);
// 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 ZWay 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.error("Unable to connect to host {} on port {}", address, 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();
}
@Override
public void onStop() {
//release resources
devices.clear();
devices = null;
setPollingWait(-1); //disable polling
//display the default description
setDescription(configuration.getStringProperty("description", "ZWay"));
}
@Override
protected void onRun() {
Set<String> keySet = devices.keySet();
String objectAddress;
if (keySet.size() > 0) {
for (String key : keySet) {
try {
Board board = devices.get(key);
try {
// retrieve a list of configured objects with "zway" protocol
List<EnvObjectLogic> objectsList = getApi().things().findByProtocol("zway");
if (objectsList.size() > 0) {
for (Iterator it = objectsList.iterator(); it.hasNext();) {
EnvObjectLogic object = (EnvObjectLogic) it.next();
// read status for each object
readStatus(board, object);
LOG.info("Object address:" + object.getPojo().getPhisicalAddress() + " type:" + object.getPojo().getType());
}
}
} catch (MalformedURLException ex) {
LOG.error(ex.getLocalizedMessage());
} catch (IOException ex) {
LOG.error(ex.getLocalizedMessage());
}
} catch (UnableToExecuteException ex) {
LOG.error(ex.getLocalizedMessage());
}
}
}
try {
Thread.sleep(POLLING_TIME);
} catch (InterruptedException ex) {
LOG.error(ex.getLocalizedMessage());
}
}
/**
* @param c
* @throws UnableToExecuteException
*/
@Override
public void onCommand(Command c) throws UnableToExecuteException {
//get connection paramentes address:port from received freedomotic command
String delimiter = configuration.getProperty("address-delimiter");
address = c.getProperty("address").split(delimiter); // in the format IP:PORT:ID:INSTANCE
Board board = (Board) devices.get(address[0]);
String ip_board = board.getIpAddress();
int port_board = board.getPort();
//connect to the ethernet board
boolean connected = false;
try {
connected = connect(ip_board, port_board);
} catch (ArrayIndexOutOfBoundsException outEx) {
LOG.error("The object address '" + c.getProperty("address") + "' is not properly formatted. Check it!");
throw new UnableToExecuteException();
} catch (NumberFormatException numberFormatException) {
LOG.error(port_board + " is not a valid ethernet port to connect to");
throw new UnableToExecuteException();
}
if (connected) {
String message = createCommandMessage(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]);
LOG.error("Unable to send the message to host " + address[0] + " on port " + address[1]);
throw new UnableToExecuteException();
} finally {
disconnect();
}
} else {
throw new UnableToExecuteException();
}
}
public void readStatus(Board board, EnvObjectLogic object) throws UnableToExecuteException, MalformedURLException, IOException {
String delimiter = configuration.getProperty("address-delimiter");
String addressComponents[] = object.getPojo().getPhisicalAddress().split(delimiter);
String deviceAddress = addressComponents[1];
String deviceInstance = addressComponents[2];
String objectType = addressComponents[3];
String type[] = objectType.split(".");
// sends an update status request .Get() method
String path = "http://" + board.getIpAddress() + ":" + board.getPort() + "/" + SEND_COMMAND_URL + "/devices[" + deviceAddress + "].instances[" + deviceInstance + "]."
+ configuration.getProperty(objectType) + ".Get()";
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setUseCaches(false);
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
wr.flush();
wr.close();
// reads a sensor value
path = "http://" + board.getIpAddress() + ":" + board.getPort() + "/" + SEND_COMMAND_URL + "/devices[" + deviceAddress + "].instances[" + deviceInstance + "]."
+ configuration.getProperty(objectType);
url = new URL(path);
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setUseCaches(false);
wr = new DataOutputStream(connection.getOutputStream());
wr.flush();
wr.close();
BufferedReader br = new BufferedReader(new InputStreamReader((connection.getInputStream())));
String output = null;
String readValue = "";
while ((output = br.readLine()) != null) {
readValue += output;
}
System.out.println(Float.parseFloat(readValue));
//building the event
ProtocolRead event = new ProtocolRead(this, "zway", board.getAlias() + delimiter + deviceAddress + delimiter + deviceInstance + delimiter + objectType);
//adding some optional information to the event
event.addProperty("read.value", String.valueOf(Float.parseFloat(readValue)));
event.addProperty("object.type", objectType);
//publishes the event on the messaging bus
this.notifyEvent(event);
connection.disconnect();
}
private String sendToBoard(String message) throws IOException {
String receivedReply = null;
String line = null;
if (outputStream != null) {
outputStream.writeBytes(message);
outputStream.flush();
inputStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
try {
while ((line = inputStream.readLine()) != null) {
receivedReply = receivedReply + line; // read device reply
}
} catch (IOException iOException) {
throw new IOException();
}
}
return receivedReply;
}
// create command message to send to the board
// this part must be changed to reflect the board protocol
/**
*
* @param c
* @return
*/
public String createCommandMessage(Command c) {
String message = null;
String page = null;
String command = null;
Integer objectID = 0;
Integer instanceID = 0;
objectID = Integer.parseInt(address[1]);
instanceID = Integer.parseInt(address[2]);
command = c.getProperty("command");
page = "/" + SEND_COMMAND_URL + "/" + "devices[" + objectID + "].instances[" + instanceID + "]." + command;
// http request sending to the board
message = "POST " + page + " HTTP/1.1\r\n\r\n";
LOG.info("Sending " + message);
//LOG.info("Unix timestamp " + getUnixTimeStamp() + "\n");
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.");
}
/**
* Retrieves a key from value in the hashmap
*
* @param hm
* @param value
* @return
*/
public static Object getKeyFromValue(Map hm, Object value) {
for (Object o : hm.keySet()) {
if (hm.get(o).equals(value)) {
return o;
}
}
return null;
}
private int getUnixTimeStamp() {
Date date = new Date();
int iTimeStamp = (int) (date.getTime() * .001);
return iTimeStamp;
}
}