package com.bernard.sboard.osc; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.time.Instant; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import com.bernard.sboard.SBoard; import com.bernard.sboard.config.OscConfig; import com.illposed.osc.OSCBadDataEvent; import com.illposed.osc.OSCBundle; import com.illposed.osc.OSCMessage; import com.illposed.osc.OSCPacket; import com.illposed.osc.OSCPacketDispatcher; import com.illposed.osc.OSCPacketEvent; import com.illposed.osc.OSCPacketListener; import com.illposed.osc.transport.OSCPortIn; import com.illposed.osc.transport.OSCPortInBuilder; import com.illposed.osc.transport.OSCPortOut; import com.illposed.osc.transport.OSCPortOutBuilder; public class OscServer implements OSCPacketListener{ Logger log = LogManager.getLogger(OscServer.class); SBoard sboard; OscConfig config; OSCPortIn inport; Map controllers; Timer globalTimer; OSCPacketDispatcher opd; Timer oscDispacher; Lock oscLock; Map>> frequentListeners; public OscServer(SBoard sboard, OscConfig config) { this.sboard = sboard; this.config = config; this.frequentListeners = new HashMap<>(); this.controllers = new HashMap<>(); this.oscLock = new ReentrantLock(); } public void setup() throws IOException { oscDispacher = new Timer(); inport = new OSCPortInBuilder() .setNetworkProtocol(config.networkProtocol) .setLocalPort(config.inputport) .setPacketListener(this) .build(); inport.startListening(); globalTimer = new Timer(); globalTimer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { updateFrequentListeners(); } }, config.frequentUpdatePeriod, config.frequentUpdatePeriod); } @Override public void handlePacket(OSCPacketEvent event) { oscLock.lock(); recursivePacketDispach(event.getPacket()); oscLock.unlock(); } @Override public void handleBadData(OSCBadDataEvent event) { log.warn("OSC Message not understood", event.getException()); } private void dispachMessageToSBoard(OSCMessage message) { SOscMessage smessage = new SOscMessage(this, message); log.debug("Received OSC message: "+message.getAddress()+" "+message.getArguments()); if(OscUtils.addrMatch(smessage.getContainer(0),"sboard") && OscUtils.addrMatch(smessage.getContainer(1),"osc")) { // Alors, le message nous est nous est destiné if(OscUtils.addrMatch(smessage.getContainer(1), "register")) { // REGISTER [ int int int int UUID, string Name, string IPAddr, int port ] UUID regUUID = new UUID( ((int)smessage.getArguments().get(0))*0xFFFFL + ((int)smessage.getArguments().get(1)), ((int)smessage.getArguments().get(2))*0xFFFFL + ((int)smessage.getArguments().get(3))); String name = (String)smessage.getArguments().get(4); SocketAddress address = new InetSocketAddress((String)smessage.getArguments().get(5), (int)smessage.getArguments().get(6)); int newuid = 0; do { newuid = sboard.random.nextInt(); } while (!controllers.containsKey(newuid)); OSCPortOut opo; try { opo = new OSCPortOutBuilder() .setRemotePort((int)smessage.getArguments().get(6)) .setRemoteSocketAddress(address) .setNetworkProtocol(config.networkProtocol) .build(); OscRemoteController orc = new OscRemoteController(this, newuid, name, opo); OSCMessage answer = new OSCMessage("/sboard/osc/registered", List.of( (int)(regUUID.getLeastSignificantBits()>>32), (int)(regUUID.getLeastSignificantBits()&0xFFFF), (int)(regUUID.getMostSignificantBits()>>32), (int)(regUUID.getMostSignificantBits()&0xFFFF), newuid)); controllers.put(newuid, orc); orc.send(answer); } catch (IOException e) { log.error("Could not register OSC reciever", e); } } } else { sboard.dispatchOscMessage(smessage); } } // Le synchronized permet de forcer les messages dans un même bundle à être executés sequentiellement. private void recursivePacketDispach(OSCPacket packet) { if(packet instanceof OSCMessage) { dispachMessageToSBoard((OSCMessage)packet); } else if (packet instanceof OSCBundle) { OSCBundle bundle = (OSCBundle) packet; if(!bundle.getTimestamp().isImmediate()) { Date dispachDate = bundle.getTimestamp().toDate(Date.from(Instant.now())); if(!dispachDate.before(Date.from(Instant.now()))) { oscDispacher.schedule(new TimerTask() { @Override public void run() { oscLock.lock(); recursivePacketDispach(packet); oscLock.unlock(); } }, dispachDate); return; } } // At this point, we should dispach the message immediatly for(OSCPacket oscp : bundle.getPackets()) recursivePacketDispach(oscp); } } public OscRemoteController getSender(int uid) { return controllers.get(uid); } public void close() throws IOException{ inport.close(); for(OscRemoteController ctrl : controllers.values()) ctrl.close(); } /* Values Listeners */ public void declareFrequentUpdate(String address, Supplier getter, OscRemoteController controller) { synchronized(frequentListeners) { frequentListeners.putIfAbsent(controller, new HashSet<>()); frequentListeners.get(controller).add(new FrequentListener<>(address, getter, controller)); } } public void removeRegularListen(String address, OscRemoteController controller) { synchronized(frequentListeners) { if(frequentListeners.containsKey(controller)) frequentListeners.get(controller).removeIf(fl -> fl.address.equals(address)); } } public void updateFrequentListeners() { synchronized(frequentListeners) { for(OscRemoteController ctrl : frequentListeners.keySet()) { OSCBundle bundle = new OSCBundle(); for(FrequentListener fl : frequentListeners.get(ctrl)) { OSCMessage toSend = new OSCMessage(fl.address,List.of(fl.getter.get())); bundle.addPacket(toSend); } ctrl.send(bundle); } } } protected class FrequentListener { String address; Supplier getter; OscRemoteController controller; public FrequentListener(String address, Supplier getter, OscRemoteController controller) { this.address = address; this.getter = getter; this.controller = controller; } } }