345 lines
8.6 KiB
Java
345 lines
8.6 KiB
Java
package de.srsoftware.web4rail;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.BufferedWriter;
|
|
import java.io.FileReader;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.net.Socket;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.Scanner;
|
|
import java.util.concurrent.TimeoutException;
|
|
|
|
import org.json.JSONObject;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import de.keawe.tools.translations.Translation;
|
|
import de.srsoftware.web4rail.tags.Button;
|
|
import de.srsoftware.web4rail.tags.Fieldset;
|
|
import de.srsoftware.web4rail.tags.Form;
|
|
import de.srsoftware.web4rail.tags.Input;
|
|
import de.srsoftware.web4rail.tags.Label;
|
|
|
|
/**
|
|
* abstraction of a SRCP daemon (control unit)
|
|
*
|
|
* @author Stephan Richter, SRSoftware
|
|
*
|
|
*/
|
|
public class ControlUnit extends Thread implements Constants{
|
|
private static final Logger LOG = LoggerFactory.getLogger(ControlUnit.class);
|
|
private static final String DEFAULT_HOST = "localhost";
|
|
private static final int DEFAULT_PORT = 4303;
|
|
private static final String HOST = "host";
|
|
private static final String PORT = "port";
|
|
private static final String BUS = "bus";
|
|
|
|
private String host = DEFAULT_HOST;
|
|
private int port = DEFAULT_PORT;
|
|
private int bus = 0;
|
|
private boolean stopped = true;
|
|
private LinkedList<Command> queue = new LinkedList<Command>();
|
|
private Socket socket;
|
|
private Scanner scanner;
|
|
private boolean power = false;
|
|
private Plan plan;
|
|
|
|
public ControlUnit(Plan plan) {
|
|
this.plan = plan;
|
|
}
|
|
|
|
/**
|
|
* @return stops the loop at the next interval
|
|
*/
|
|
public ControlUnit end() {
|
|
stopped = true;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* performs a handshake as specified in the SRCP protocol
|
|
* @throws TimeoutException
|
|
* @throws IOException
|
|
*/
|
|
private void handshake() throws TimeoutException, IOException {
|
|
String proto = null;
|
|
if (scanner.hasNext()) {
|
|
String line = scanner.nextLine();
|
|
LOG.debug("recv: "+line);
|
|
for (String part : line.split(";")) {
|
|
part = part.trim();
|
|
if (part.startsWith("SRCP ")) proto = part.substring(5);
|
|
}
|
|
if (proto == null) throw new IOException("Handshake failed: "+line);
|
|
if (!proto.startsWith("0.8.")) throw new IOException("Unsupported protocol: "+proto);
|
|
} else throw new IOException("Handshake expected.");
|
|
|
|
Command command = new Command("SET PROTOCOL SRCP "+proto);
|
|
send(command);
|
|
if (!command.reply().succeeded()) throw new IOException("Handshake failed: "+command.reply());
|
|
|
|
command = new Command("SET CONNECTIONMODE SRCP COMMAND"); // preset following mode: COMMAND MODE
|
|
send(command);
|
|
if (!command.reply().succeeded()) throw new IOException("Handshake failed: "+command.reply());
|
|
|
|
command = new Command("GO"); // switch mode
|
|
send(command);
|
|
if (!command.reply().succeeded()) throw new IOException("Handshake failed: "+command.reply());
|
|
}
|
|
|
|
/**
|
|
* @return json string containing the connection information
|
|
*/
|
|
private JSONObject json() {
|
|
JSONObject json = new JSONObject();
|
|
json.put(HOST, host);
|
|
json.put(PORT, port);
|
|
json.put(BUS, bus);
|
|
return json;
|
|
}
|
|
|
|
/**
|
|
* load connection information from file
|
|
* @param filename
|
|
* @throws IOException
|
|
*/
|
|
public void load(String filename) throws IOException {
|
|
BufferedReader file = new BufferedReader(new FileReader(filename));
|
|
JSONObject json = new JSONObject(file.readLine());
|
|
file.close();
|
|
if (json.has(PORT)) port = json.getInt(PORT);
|
|
if (json.has(BUS)) bus = json.getInt(BUS);
|
|
if (json.has(HOST)) host = json.getString(HOST);
|
|
}
|
|
|
|
/**
|
|
* test method
|
|
* @param args
|
|
* @throws InterruptedException
|
|
*/
|
|
public static void main(String[] args) throws InterruptedException {
|
|
ControlUnit cu = new ControlUnit(null).setEndpoint("Modellbahn", DEFAULT_PORT).setBus(1).restart();
|
|
Thread.sleep(1000);
|
|
cu.queue(new Command("SET {} POWER ON") {
|
|
|
|
@Override
|
|
public void onSuccess() {
|
|
LOG.debug("Power on");
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(Reply reply) {
|
|
LOG.debug("Was not able to turn power on: {}",reply.message());
|
|
}
|
|
|
|
});
|
|
Thread.sleep(1000);
|
|
cu.end();
|
|
}
|
|
|
|
/**
|
|
* process actions related to the SRCP daemon
|
|
* @param params
|
|
* @return
|
|
*/
|
|
public Object process(HashMap<String, String> params) {
|
|
switch (params.get(ACTION)) {
|
|
case ACTION_CONNECT:
|
|
restart();
|
|
return t("Control unit (re)started.");
|
|
case ACTION_EMERGENCY:
|
|
return emergency();
|
|
case ACTION_POWER:
|
|
return togglePower();
|
|
case ACTION_PROPS:
|
|
return properties();
|
|
case ACTION_UPDATE:
|
|
return update(params);
|
|
}
|
|
|
|
return t("Unknown action: {}",params.get(ACTION));
|
|
}
|
|
|
|
/**
|
|
* turn of power immediately
|
|
* @return
|
|
*/
|
|
public Object emergency() {
|
|
power = true;
|
|
return togglePower();
|
|
}
|
|
|
|
/**
|
|
* generate a properties view for the client
|
|
* @return
|
|
*/
|
|
public Object properties() {
|
|
Window win = new Window("cu-props", t("Properties of the control unit"));
|
|
Form form = new Form();
|
|
new Input(ACTION,ACTION_UPDATE).hideIn(form);
|
|
new Input(REALM,REALM_CU).hideIn(form);
|
|
Fieldset fieldset = new Fieldset(t("Server connection"));
|
|
new Input(HOST,host).addTo(new Label(t("Hostname"))).addTo(fieldset);
|
|
new Input(PORT,port).numeric().addTo(new Label(t("Port"))).addTo(fieldset);
|
|
new Input(BUS,bus).numeric().addTo(new Label(t("Bus"))).addTo(fieldset);
|
|
new Button(t("Save")).addTo(fieldset).addTo(form).addTo(win);
|
|
|
|
fieldset = new Fieldset("Actions");
|
|
new Button(t("Connect"),"connectCu();").addTo(fieldset).addTo(win);
|
|
return win;
|
|
}
|
|
|
|
/**
|
|
* add a command to the queue of commands to be sent to the server
|
|
* @param command
|
|
* @return
|
|
*/
|
|
public Command queue(Command command) {
|
|
queue.add(command);
|
|
return command;
|
|
}
|
|
|
|
/**
|
|
* Should close the server connection and establish new server connection
|
|
* @return
|
|
*/
|
|
public ControlUnit restart() {
|
|
end();
|
|
start();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* thread, that repeatedly checks the queue for new commands and sends them to the SRCP daemon
|
|
*/
|
|
@Override
|
|
public void run() {
|
|
while (!stopped) {
|
|
try {
|
|
if (queue.isEmpty()) {
|
|
Thread.sleep(10);
|
|
} else {
|
|
Command command = queue.pop();
|
|
send(command);
|
|
}
|
|
} catch (InterruptedException | IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
try {
|
|
socket.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* save settings to file
|
|
* @param filename
|
|
* @throws IOException
|
|
*/
|
|
public void save(String filename) throws IOException {
|
|
BufferedWriter file = new BufferedWriter(new FileWriter(filename));
|
|
file.write(json()+"\n");
|
|
file.close();
|
|
}
|
|
|
|
/**
|
|
* send command to Server
|
|
* @param command
|
|
* @return
|
|
* @throws IOException
|
|
*/
|
|
private void send(Command command) throws IOException {
|
|
if (command == null || command.toString() == null) return;
|
|
String data = command.toString().replace("{}", ""+bus);
|
|
socket.getOutputStream().write((data+"\n").getBytes(StandardCharsets.US_ASCII));
|
|
LOG.info("sent {}.",data);
|
|
command.readReplyFrom(scanner);
|
|
}
|
|
|
|
/**
|
|
* defines the bus on the SRCP deamon, to which commands shall be assigned
|
|
* @param bus
|
|
* @return
|
|
*/
|
|
private ControlUnit setBus(int bus) {
|
|
this.bus = bus;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* set up the connection endpoint
|
|
* @param newHost
|
|
* @param newPort
|
|
* @return
|
|
*/
|
|
public ControlUnit setEndpoint(String newHost, int newPort){
|
|
host = newHost;
|
|
port = newPort;
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public synchronized void start() {
|
|
try {
|
|
socket = new Socket(host, port);
|
|
scanner = new Scanner(socket.getInputStream());
|
|
handshake();
|
|
stopped = false;
|
|
} catch (IOException | TimeoutException e) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
super.start();
|
|
}
|
|
|
|
/**
|
|
* shorthand for Translation.get(text,fills)
|
|
* @param text
|
|
* @param fills
|
|
* @return
|
|
*/
|
|
private static String t(String text,Object...fills) {
|
|
return Translation.get(Application.class, text, fills);
|
|
}
|
|
|
|
/**
|
|
* togge power on/off at the SRCP daemon
|
|
* @return
|
|
*/
|
|
private Command togglePower() {
|
|
power = !power;
|
|
String PW = power?"ON":"OFF";
|
|
Command command = new Command("SET {} POWER "+PW) {
|
|
|
|
@Override
|
|
public void onSuccess() {
|
|
plan.stream(t("Turned power {}.",PW));
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(Reply reply) {
|
|
plan.stream(t("Was not able to turn power {}!",PW));
|
|
}
|
|
|
|
};
|
|
return queue(command);
|
|
}
|
|
|
|
|
|
/**
|
|
* update connection parameters
|
|
* @param params
|
|
* @return
|
|
*/
|
|
public String update(HashMap<String, String> params) {
|
|
if (params.containsKey(HOST)) host = params.get(HOST);
|
|
if (params.containsKey(PORT)) port = Integer.parseInt(params.get(PORT));
|
|
if (params.containsKey(BUS)) bus = Integer.parseInt(params.get(BUS));
|
|
return t("Updated control unit settings");
|
|
}
|
|
}
|