package at.bakery.kippen.server;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXB;
import at.bakery.kippen.common.AbstractData;
import at.bakery.kippen.common.data.ContainerData;
import at.bakery.kippen.common.json.JSONDataSerializer;
import at.bakery.kippen.config.CommandConfig;
import at.bakery.kippen.config.Configuration;
import at.bakery.kippen.config.EventConfig;
import at.bakery.kippen.config.ObjectConfig;
import at.bakery.kippen.config.Param;
import at.bakery.kippen.config.TypeEnum;
import at.bakery.kippen.server.command.AbletonDeviceCommand;
import at.bakery.kippen.server.command.AbletonMasterDeviceCommand;
import at.bakery.kippen.server.command.AbletonPlayCommand;
import at.bakery.kippen.server.command.AbletonStopCommand;
import at.bakery.kippen.server.command.Command;
import at.bakery.kippen.server.command.MasterVolumeCommand;
import at.bakery.kippen.server.command.SendSocketDataCommand;
import at.bakery.kippen.server.command.ToggleMuteCommand;
import at.bakery.kippen.server.objects.AbstractKippenObject;
import at.bakery.kippen.server.objects.BallObject;
import at.bakery.kippen.server.objects.BarrelObject;
import at.bakery.kippen.server.objects.CubeObject;
//TODO make server listen to changes in the xml config file so changes can be applied at runtime.
public class KippenServer {
static Logger log = Logger.getLogger(KippenServer.class.getName());
public static Level LOG_LEVEL = Level.INFO;
private Configuration _config;
private HashMap<String, AbstractKippenObject> objectMap = new HashMap<String, AbstractKippenObject>();
public static void main(String args[]) {
try {
final KippenServer server = new KippenServer();
server.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void start() throws Exception {
initObjects();
Executor workerExecutor = Executors.newCachedThreadPool();
final ServerSocket serverSock = new ServerSocket(10001);
log.info("Kippen Server is starting up ...");
try {
while (true) {
final Socket client = serverSock.accept();
final NetworkInterface ni = NetworkInterface.getByInetAddress(client.getInetAddress());
final String clientId;
if (ni != null && ni.getHardwareAddress() != null) {
clientId = new String(ni.getHardwareAddress());
} else {
clientId = client.getInetAddress().getHostAddress();
}
log.info("Client " + clientId + " connected ...");
workerExecutor.execute(new Runnable() {
@Override
public void run() {
try {
BufferedReader ois = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF8"));
while (true) {
// first line is canonical class name of event
String dataType = ois.readLine();
if (dataType == null) {
break;
}
if (dataType.isEmpty()) {
continue;
}
// second line is JSON data
String dataLine = ois.readLine();
if (dataLine == null) {
break;
}
// set receive time stamp
long receiveTime = System.currentTimeMillis();
AbstractData data = JSONDataSerializer.deserialize(dataType, dataLine);
if (data == null) {
continue;
}
if (data instanceof ContainerData == false) {
// TODO process battery, etc.
continue;
}
// pick the client and process all received data
AbstractKippenObject object = objectMap.get(data.getClientId());
System.out.println(data.getClientId().substring(0,3));
if (object == null) {
log.warning("Client MAC address " + data.getClientId() + " is not registered");
return;
}
// process each data packet
ContainerData containerData = (ContainerData) data;
// check lag and drop packet if necessary
long lag = System.currentTimeMillis() - containerData.getTimestamp();
if (lag > 5000) {
// System.err.println("Dropping packet, lag is "
// + lag + "ms");
// continue;
}
// lag in bounds, process ...
object.processData(containerData.accData);
object.processData(containerData.avgAccData);
object.processData(containerData.moveData);
object.processData(containerData.shakeData);
object.processData(containerData.cubeData);
object.processData(containerData.barrelData);
System.out.println(containerData.getClientId());
// System.out.println(object);
// System.out.println(containerData);
}
} catch (Exception ex) {
log.severe("Client " + clientId + " died ...");
ex.printStackTrace();
}
}
});
}
} finally {
serverSock.close();
}
}
public Configuration getConfiguration() {
return _config;
}
private void initObjects() {
_config = JAXB.unmarshal(new File("config.xml"), Configuration.class);
// for each object set the commands and events
for (ObjectConfig objectConfig : _config.getObjects().getObjectConfig()) {
String mac = objectConfig.getMac();
if (objectConfig.getType() == TypeEnum.CUBE) {
log.info("Registering a CUBE with MAC " + mac);
// make new kippen object
CubeObject cubeKippObject = new CubeObject(mac, _config.getTimeoutMinutes());
// add the new object to the server object map
objectMap.put(objectConfig.getMac(), cubeKippObject);
// for all events the object reacts to
for (EventConfig e : objectConfig.getEvents().getEventConfig()) {
List<Command> commands = makeCommands(e.getCommands().getCommandConfig());
switch (e.getEventType()) {
case EventTypes.SIDECHANGE:
log.info("Registering side change event");
cubeKippObject.setCommandsForEvents(EventTypes.SIDECHANGE, commands);
break;
case EventTypes.SHAKE:
log.info("Registering shake event");
cubeKippObject.setCommandsForEvents(EventTypes.SHAKE, commands);
break;
case EventTypes.TIMEOUT:
log.info("Registering timeout event");
cubeKippObject.setCommandsForEvents(EventTypes.TIMEOUT, commands);
break;
case EventTypes.MOVE:
log.info("Registering move event");
cubeKippObject.setCommandsForEvents(EventTypes.MOVE, commands);
break;
// add other events here
default:
break;
}
}
} else if (objectConfig.getType() == TypeEnum.BARREL) {
log.info("Registering a BARREL with MAC " + mac);
// make new kippen object
BarrelObject barrelKippObject = new BarrelObject(mac, _config.getTimeoutMinutes());
// add the new object to the server object map
objectMap.put(objectConfig.getMac(), barrelKippObject);
// for all events the object reacts to
for (EventConfig e : objectConfig.getEvents().getEventConfig()) {
List<Command> commands = makeCommands(e.getCommands().getCommandConfig());
switch (e.getEventType()) {
// case EventTypes.SHAKE:
// log.info("Registering shake event");
// barrelKippObject.setCommandsForEvents(EventTypes.SHAKE,
// commands);
// break;
case EventTypes.ROLLCHANGE:
log.info("Registering roll event");
barrelKippObject.setCommandsForEvents(EventTypes.ROLLCHANGE, commands);
break;
// case EventTypes.TIMEOUT:
// log.info("Registering roll event");
// barrelKippObject.setCommandsForEvents(EventTypes.TIMEOUT,
// commands);
// break;
// add other events here
default:
break;
}
// objectMap.put(mac, barrelKippObject);
}
} else if (objectConfig.getType() == TypeEnum.BALL) {
log.info("Registering a BALL with MAC " + mac);
BallObject ballObject = new BallObject(mac, _config.getTimeoutMinutes());
// add the new object to the server object map
objectMap.put(mac, ballObject);
for (EventConfig e : objectConfig.getEvents().getEventConfig()) {
List<Command> commands = makeCommands(e.getCommands().getCommandConfig());
switch (e.getEventType()) {
case EventTypes.MOVE:
log.info("Registering move event for ball");
ballObject.setCommandsForEvents(EventTypes.MOVE, commands);
break;
}
}
}
}
}
private List<Command> makeCommands(List<CommandConfig> configList) {
ArrayList<Command> commandList = new ArrayList<Command>();
for (CommandConfig c : configList) {
switch (c.getCommandType()) {
case "ABLETONPLAY":
log.log(Level.INFO, "Registering ABLETONPLAY command");
commandList.add(new AbletonPlayCommand(getCommandParamValue("trackNumber", c.getParam())));
break;
case "STOPTRACK":
log.log(Level.INFO, "Registering ABLETONSTOP command");
commandList.add(new AbletonStopCommand(getCommandParamValue("trackNumber", c.getParam())));
break;
case "TOGGLEMUTE":
log.log(Level.INFO, "Registering TOGGLEMUTE command");
commandList.add(new ToggleMuteCommand(Integer.valueOf(getCommandParamValue("trackNumber", c.getParam()))));
break;
case "MASTERVOLUME":
log.log(Level.INFO, "Registering MASTERVOLUME command");
commandList.add(new MasterVolumeCommand());
break;
case "SENDSOCKETDATA":
log.log(Level.INFO, "Registering SENDSOCKETDATA command");
commandList.add(new SendSocketDataCommand(c.getParam()));
break;
case "ABLETONDEVICE":
log.log(Level.INFO, "Registering ABLETONDEVICE command");
int trackNumber = Integer.valueOf(getCommandParamValue("trackNumber", c.getParam()));
int deviceNumber = Integer.valueOf(getCommandParamValue("deviceNumber", c.getParam()));
int parameterNumber = Integer.valueOf(getCommandParamValue("parameterNumber", c.getParam()));
commandList.add(new AbletonDeviceCommand(trackNumber, deviceNumber, parameterNumber));
break;
case "ABLETONMASTERDEVICE":
log.log(Level.INFO, "Registering ABLETONMASTERDEVICE command");
int masterDeviceNumber = Integer.valueOf(getCommandParamValue("deviceNumber", c.getParam()));
int masterParameterNumber = Integer.valueOf(getCommandParamValue("parameterNumber", c.getParam()));
commandList.add(new AbletonMasterDeviceCommand(masterDeviceNumber, masterParameterNumber));
break;
default:
break;
}
}
return commandList;
}
private String getCommandParamValue(String key, List<Param> commandParam) {
for (Param param : commandParam) {
if (param.getKey().equalsIgnoreCase(key)) {
return param.getValue();
}
}
return null;
}
}