/**
* Copyright (c) 2010-2015, openHAB.org and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.myhome.fcrisciani.connector;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.myhome.fcrisciani.datastructure.action.Action;
import com.myhome.fcrisciani.datastructure.command.CommandOPEN;
import com.myhome.fcrisciani.datastructure.command.DelayInterval;
import com.myhome.fcrisciani.exception.MalformedCommandOPEN;
import com.myhome.fcrisciani.queue.PriorityCommandQueue;
import com.myhome.fcrisciani.queue.PriorityQueueThread;
/**
* This is the class that abstract completely the complexity of communicating
* with the OpenWebNet protocol to a MyHome webserver After having an instance
* of this class it is possible to send and receive OpenWebNet messages directly
* form here This class handle also a tail of commands that are sent with an
* inter-command delay that assures the correct execution of them on the myhome
* plant
*
* @author Flavio Crisciani
* @serial 1.0
* @since 1.7.0
*/
public class MyHomeJavaConnector {
private static final Logger logger = LoggerFactory.getLogger(MyHomeJavaConnector.class);
// ----- TYPES ----- //
// ---- MEMBERS ---- //
public String ip = null; // MyHome webserver IP address
public int port = 0; // MyHome webserver port
private Socket commandSk = null; // Socket for command sending
private Semaphore commandMutex = null; // Mutex for the send command section
private Socket monitorSk = null; // Socket for plant monitoring
private PriorityCommandQueue commandQueue = null; // Queue of commands
private Thread commandQueueThread = null; // Queue command thread
// ---- METHODS ---- //
/**
* Evaluates if the command that is going to be sent is a valid command
*
* @param commandString
* is the command in string format
* @return returns true if the format is correct
*/
private boolean checkCommandFormat(String commandString) {
if (commandString.matches("\\*[#0-9]+[*#0-9]*##")) {
return true;
}
return false;
}
/**
* Sends a command as a string on the command socket passed
*
* @param sk
* command socket on which send the command
* @param command
* string representing the open command
* @throws IOException
* in case of communication error
*/
private void sendCommandOPEN(final Socket sk, final String command) throws IOException {
if (command != null) {
PrintWriter output = new PrintWriter(sk.getOutputStream());
output.write(command);
output.flush();
}
}
/**
* Receives an array of commands ended with a ACK or NACK
*
* @param sk
* socket used to read commands
* @return the array of commands received
* @throws IOException
* in case of communication error
*/
private String[] receiveCommandOPEN(final Socket sk) throws IOException {
BufferedReader inputStream = new BufferedReader(new InputStreamReader(sk.getInputStream()));
String[] newMessage = MyHomeSocketFactory.readUntilAckNack(inputStream);
return newMessage;
}
/**
* Receive message from a monitor socket
*
* @param sk
* monitor socket used to read commands
* @return the command received during monitoring
* @throws IOException
* in case of communication error
*/
private String receiveMonitorOPEN(final Socket sk) throws IOException {
BufferedReader inputStream = new BufferedReader(new InputStreamReader(sk.getInputStream()));
String newMessage = MyHomeSocketFactory.readUntilDelimiter(inputStream);
return newMessage;
}
/* PUBLIC */
/**
* Create an instance of this class, need the IP address and port of the
* webserver to connect to
*
* @param ip
* IP address of the webserver
* @param port
* port number of the webserver
*/
public MyHomeJavaConnector(final String ip, final int port) {
super();
this.ip = ip;
this.port = port;
logger.debug("Created MyHomeJavaConnector with ip = {} and port = {}", ip, port);
this.commandMutex = new Semaphore(1, true);
this.commandQueue = new PriorityCommandQueue();
this.commandQueueThread = new Thread(new PriorityQueueThread(this, commandQueue), "TailThread");
this.commandQueueThread.start();
}
/* COMMAND SESSION */
/* Command Sending Sync */
/**
* Send a command synchronously and atomically, create a new command socket,
* sends the command and returns command results before closing the socket
* created
*
* @param command
* string representing the command to send
* @return the array of commands received as a result of the command sent
* @throws MalformedCommandOPEN
*/
public String[] sendCommandSync(final String command) throws MalformedCommandOPEN {
if (checkCommandFormat(command)) {
try {
commandMutex.acquire();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
/** START CRITICAL SECTION */
String[] result = null;
try {
commandSk = MyHomeSocketFactory.openCommandSession(ip, port);
sendCommandOPEN(commandSk, command);
result = receiveCommandOPEN(commandSk);
// Assure an intertime between messages that can be sent with
// multiple call
Thread.sleep(300);
MyHomeSocketFactory.disconnect(commandSk);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
commandMutex.release();
/** END CRITICAL SECTION */
return result;
} else {
throw new MalformedCommandOPEN(command);
}
}
/**
* Send a command synchronously and atomically, create a new command socket,
* sends the command and returns command results before closing the socket
* created
*
* @param command
* instance of the commandOpen to send
* @return the array of commands received as a result of the command sent
* @throws MalformedCommandOPEN
*/
public String[] sendCommandSync(final CommandOPEN command) throws MalformedCommandOPEN {
return sendCommandSync(command.getCommandString());
}
/* Command Sending Async */
/**
* Send a command asynchronously, this is queued with a priority and sent
* automatically
*
* @param command
* string representing the command to send
* @param priority
* queue priority {1 = HIGH, 2 = MEDIUM, 3 = LOW}
* @throws MalformedCommandOPEN
*/
public void sendCommandAsync(final String command, final int priority) throws MalformedCommandOPEN {
if (checkCommandFormat(command)) {
if (priority == 1) {
commandQueue.addHighLevel(command);
} else if (priority == 2) {
commandQueue.addMediumLevel(command);
} else {
commandQueue.addLowLevel(command);
}
} else {
throw new MalformedCommandOPEN(command);
}
}
/**
* Send a command asynchronously, this is queued with a priority and sent
* automatically
*
* @param command
* instance of the commandOpen to send
* @param priority
* queue priority {1 = HIGH, 2 = MEDIUM, 3 = LOW}
* @throws MalformedCommandOPEN
*/
public void sendCommandAsync(final CommandOPEN command, final int priority) throws MalformedCommandOPEN {
sendCommandAsync(command.getCommandString(), priority);
}
/**
* Send a list of commands asynchronously, these are queued with a priority
* and sent automatically
*
* @param commandList
* array of instances of the commandOpen to send
* @param priority
* queue priority {1 = HIGH, 2 = MEDIUM, 3 = LOW}
* @throws MalformedCommandOPEN
*/
public void sendCommandListAsync(final CommandOPEN[] commandList, final int priority) throws MalformedCommandOPEN {
for (CommandOPEN command : commandList) {
sendCommandAsync(command.getCommandString(), priority);
}
}
/**
* Send an Action asynchronously, all its commands are queued with a
* priority and sent automatically
*
* @param action
* instance of Action to send
* @param priority
* queue priority {1 = HIGH, 2 = MEDIUM, 3 = LOW}
* @throws MalformedCommandOPEN
*/
public void sendAction(final Action action, final int priority) throws MalformedCommandOPEN {
ArrayList<CommandOPEN> commandList = action.getCommandList();
for (CommandOPEN command : commandList) {
if (command != null) {
if (command instanceof DelayInterval && ((DelayInterval) command).getDelayInMillisecond() > 0) {
try {
Thread.sleep(((DelayInterval) command).getDelayInMillisecond());
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
sendCommandAsync(command, priority);
}
}
}
}
/* MONITOR SESSION */
/**
* Start a monitoring session
*
* @throws IOException
* in case of communication error
*/
public void startMonitoring() throws IOException {
monitorSk = MyHomeSocketFactory.openMonitorSession(ip, port);
}
/**
* Reads the next message from the monitor session, note: you always must
* call the method {@link startMonitoring()} before start reading from a
* monitor session. This call is blocking on the socket but there is a
* timeout of 45s after that an exception is thrown. The OpenWebNet protocol
* states that after 30s the connection is automatically closed, for this
* reason the monitor socket periodically receive some keep-alive message.
* In case of connection drop the socket timeout fires and this method tries
* to establish again the connection forever notifying the attempt number.
*
* @return the message from the monitor session
* @throws InterruptedException
* notify problem on sleep method
*/
public String readMonitoring() throws InterruptedException {
String result = null;
int retry = 0;
do {
try {
result = receiveMonitorOPEN(monitorSk);
} catch (IOException e) {
try {
MyHomeSocketFactory.disconnect(monitorSk);
} catch (IOException e1) {
}
retry++;
Thread.sleep(1000);
logger.error("Monitor connection problem. Attempting retry {}.", retry);
try {
startMonitoring();
} catch (IOException e1) {
}
}
} while (result == null);
return result;
}
/**
* Close the monitor session
*
* @throws IOException
* in case of communication error
*/
public void stopMonitoring() throws IOException {
MyHomeSocketFactory.disconnect(monitorSk);
}
}