diff --git a/bluesky/__init__.py b/bluesky/__init__.py index 847fecdebd..565708f9ed 100644 --- a/bluesky/__init__.py +++ b/bluesky/__init__.py @@ -11,7 +11,7 @@ INIT, OP, HOLD, END = list(range(4)) NUMEVENTS = 16 -SetNodeIdType, SetActiveNodeType, AddNodeType, SimStateEventType, BatchEventType, \ +SetNodeIdType, SetActiveNodeType, NodesChanged, SimStateEventType, BatchEventType, \ PanZoomEventType, ACDataEventType, SimInfoEventType, StackTextEventType, \ StackInitEventType, ShowDialogEventType, DisplayFlagEventType, \ RouteDataEventType, DisplayShapeEventType, \ diff --git a/bluesky/io/__init__.py b/bluesky/io/__init__.py index 5203a73346..01deef0cae 100644 --- a/bluesky/io/__init__.py +++ b/bluesky/io/__init__.py @@ -1,5 +1,6 @@ from bluesky import settings from bluesky.io.node import Node +from bluesky.io.iodata import IOData if not settings.is_sim: from bluesky.io.iomanager import IOManager from bluesky.io.client import Client diff --git a/bluesky/io/client.py b/bluesky/io/client.py index 1ad12cb25e..dfa612cff9 100644 --- a/bluesky/io/client.py +++ b/bluesky/io/client.py @@ -1,27 +1,31 @@ import zmq +import msgpack from bluesky.tools import Signal +from bluesky.io.npcodec import encode_ndarray, decode_ndarray class Client(object): def __init__(self): ctx = zmq.Context.instance() - self.event_io = ctx.socket(zmq.DEALER) - self.stream_in = ctx.socket(zmq.SUB) - self.poller = zmq.Poller() - self.host_id = b'' - self.client_id = 0 - self.sender_id = b'' + self.event_io = ctx.socket(zmq.DEALER) + self.stream_in = ctx.socket(zmq.SUB) + self.poller = zmq.Poller() + self.host_id = b'' + self.client_id = 0 + self.sender_id = b'' + self.known_nodes = dict() # Signals + self.nodes_changed = Signal() self.event_received = Signal() self.stream_received = Signal() def connect(self): self.event_io.connect('tcp://localhost:9000') - self.event_io.send(b'REGISTER') - msg = self.event_io.recv() - self.client_id = 256 * msg[-2] + msg[-1] - self.host_id = msg[:5] + self.send_event(b'REGISTER') + data = self.event_io.recv_multipart()[-1] + self.client_id = 256 * data[-2] + data[-1] + self.host_id = data[:5] print('Client {} connected to host {}'.format(self.client_id, self.host_id)) self.stream_in.connect('tcp://localhost:9001') self.stream_in.setsockopt(zmq.SUBSCRIBE, b'') @@ -34,29 +38,34 @@ def receive(self): try: socks = dict(self.poller.poll(0)) if socks.get(self.event_io) == zmq.POLLIN: - self.sender_id = self.event_io.recv() - data = self.event_io.recv_pyobj() - print('received event data') + res = self.event_io.recv_multipart() + self.sender_id = res[0] + name = res[1] + data = msgpack.unpackb(res[2], object_hook=decode_ndarray, encoding='utf-8') + if name == b'NODESCHANGED': + self.known_nodes.update(data) + self.nodes_changed.emit(data) + + print('received {} event data'.format(name)) print(data) - self.event_received.emit(data, self.sender_id) + self.event_received.emit(name, data, self.sender_id) if socks.get(self.stream_in) == zmq.POLLIN: - nameandid = self.stream_in.recv() - stream_name = nameandid[:-8] - sender_id = nameandid[-8:] - data = self.stream_in.recv_pyobj() - self.stream_received.emit(data, stream_name, sender_id) + res = self.stream_in.recv_multipart() + + name = res[0][:-8] + sender_id = res[0][-8:] + data = msgpack.unpackb(res[1], object_hook=decode_ndarray, encoding='utf-8') + self.stream_received.emit(name, data, sender_id) except zmq.ZMQError: return False def addnodes(self, count=1): - self.event_io.send(bytearray((count,)), zmq.SNDMORE) - self.event_io.send(b'ADDNODES') + self.send_event(b'ADDNODES', count) - def send_event(self, data, target=None): + def send_event(self, name, data=None, target=None): # On the sim side, target is obtained from the currently-parsed stack command - self.event_io.send(target or b'*', zmq.SNDMORE) - self.event_io.send_pyobj(data) + self.event_io.send_multipart([target or b'*', name, msgpack.packb(data, default=encode_ndarray, use_bin_type=True)]) - def send_stream(self, data, name): + def send_stream(self, name, data): pass diff --git a/bluesky/io/iomanager.py b/bluesky/io/iomanager.py index bea4a8c91b..61228f923f 100644 --- a/bluesky/io/iomanager.py +++ b/bluesky/io/iomanager.py @@ -3,34 +3,37 @@ from threading import Thread from subprocess import Popen import zmq +import msgpack class IOManager(Thread): def __init__(self): super(IOManager, self).__init__() - self.localnodes = list() + self.spawned_processes = list() self.running = True + self.nodes = dict() def addnodes(self, count=1): for _ in range(count): p = Popen([sys.executable, 'BlueSky_qtgl.py', '--node']) - self.localnodes.append(p) + self.spawned_processes.append(p) def run(self): # Get ZMQ context ctx = zmq.Context.instance() - # Convenience class for event connection handling class EventConn: + ''' Convenience class for event connection handling. ''' # Generate one host ID for this host - host_id = b'\x00' + os.urandom(5) + host_id = b'\x00' + os.urandom(4) - def __init__(self, endpoint): + def __init__(self, endpoint, connection_key): self.sock = ctx.socket(zmq.ROUTER) self.sock.bind(endpoint) self.namefromid = dict() self.idfromname = dict() self.conn_count = 0 + self.conn_key = connection_key def __eq__(self, sock): # Compare own socket to socket returned from poller.poll @@ -40,23 +43,27 @@ def register(self, connid): # The connection ID consists of the host id plus the index of the # new connection encoded in two bytes. self.conn_count += 1 - name = self.host_id + bytearray((self.conn_count // 256, self.conn_count % 256)) + name = self.host_id + self.conn_key + \ + bytearray((self.conn_count // 256, self.conn_count % 256)) self.namefromid[connid] = name self.idfromname[name] = connid return name # Create connection points for clients - fe_event = EventConn('tcp://*:9000') + fe_event = EventConn('tcp://*:9000', b'c') fe_stream = ctx.socket(zmq.XPUB) fe_stream.bind('tcp://*:9001') # Create connection points for sim workers - be_event = EventConn('tcp://*:10000') + be_event = EventConn('tcp://*:10000', b'w') be_stream = ctx.socket(zmq.XSUB) be_stream.bind('tcp://*:10001') + # We start with zero nodes + self.nodes[EventConn.host_id] = 0 + # Create poller for both event connection points and the stream reader poller = zmq.Poller() poller.register(fe_event.sock, zmq.POLLIN) @@ -88,19 +95,31 @@ def register(self, connid): be_stream.send_multipart(msg) else: # Select the correct source and destination - src, dest = (fe_event, be_event) if sock == fe_event else (be_event, fe_event) + srcisclient = (sock == fe_event) + src, dest = (fe_event, be_event) if srcisclient else (be_event, fe_event) + + # Message format: [sender, target, name, data] + sender, target, eventname, data = msg - if msg[-1] == b'REGISTER': + if eventname == b'REGISTER': # This is a registration message for a new connection # Send reply with connection name - sock.send_multipart([msg[0], src.register(msg[0])]) + msg[-1] = src.register(msg[0]) + src.sock.send_multipart(msg) + if srcisclient: + src.sock.send_multipart([msg[0], src.host_id, b'NODESCHANGED', msgpack.packb(self.nodes, use_bin_type=True)]) + else: + self.nodes[src.host_id] += 1 + data = msgpack.packb({src.host_id : self.nodes[src.host_id]}, use_bin_type=True) + for connid in dest.namefromid: + dest.sock.send_multipart([connid, src.host_id, b'NODESCHANGED', data]) - elif msg[-1] == b'ADDNODES': + elif msg[2] == b'ADDNODES': # This is a request to start new nodes. - count = msg[-2] + count = msgpack.unpackb(msg[3]) self.addnodes(count) - elif msg[-1] == b'QUIT': + elif msg[2] == b'QUIT': # TODO: send quit to all self.running = False @@ -119,5 +138,5 @@ def register(self, connid): dest.sock.send_multipart(msg) # Wait for all nodes to finish - for n in self.localnodes: + for n in self.spawned_processes: n.wait() diff --git a/bluesky/io/node.py b/bluesky/io/node.py index 8eff7e696c..4dd0f0683b 100644 --- a/bluesky/io/node.py +++ b/bluesky/io/node.py @@ -1,9 +1,10 @@ """ Node encapsulates the sim process, and manages process I/O. """ from threading import Thread import zmq +import msgpack from bluesky import stack from bluesky.tools import Timer - +from bluesky.io.npcodec import encode_ndarray, decode_ndarray class IOThread(Thread): ''' Separate thread for node I/O. ''' @@ -24,9 +25,6 @@ def run(self): poller.register(be_event, zmq.POLLIN) poller.register(be_stream, zmq.POLLIN) - fe_event.send(b'REGISTER') - be_event.send(fe_event.recv()) - while True: try: poll_socks = dict(poller.poll(None)) @@ -65,12 +63,13 @@ def init(self): # Start the I/O thread, and receive from it this node's ID self.iothread.start() - self.nodeid = self.event_io.recv() + self.send_event(b'REGISTER') + self.nodeid = self.event_io.recv_multipart()[-1] print('Node started, id={}'.format(self.nodeid)) - def event(self, data, sender_id): + def event(self, eventname, eventdata, sender_id): ''' Event data handler. Reimplemented in Simulation. ''' - print('Received data from {}'.format(sender_id)) + print('Received {} data from {}'.format(eventname, sender_id)) def step(self): ''' Perform one iteration step. Reimplemented in Simulation. ''' @@ -85,7 +84,7 @@ def start(self): self.run() # Send quit event to the worker thread and wait for it to close. - self.event_io.send('QUIT') + self.event_io.send(b'QUIT') self.iothread.join() def quit(self): @@ -97,10 +96,12 @@ def run(self): while self.running: # Get new events from the I/O thread while self.poll(): - sender_id = self.event_io.recv() - data = self.event_io.recv_pyobj() + res = self.event_io.recv_multipart() + sender_id = res[0] + name = res[1] + data = msgpack.unpackb(res[2], object_hook=decode_ndarray, encoding='utf-8') print('Node received event') - self.event(data, sender_id) + self.event(name, data, sender_id) # Perform a simulation step self.step() @@ -116,15 +117,11 @@ def poll(self): return False def addnodes(self, count=1): - self.event_io.send(bytearray((count,)), zmq.SNDMORE) - self.event_io.send(b'ADDNODES') + self.send_event(b'ADDNODES', count) - def send_event(self, data, target=None): + def send_event(self, name, data=None, target=None): # On the sim side, target is obtained from the currently-parsed stack command - self.event_io.send(stack.sender() or b'*', zmq.SNDMORE) - self.event_io.send_pyobj(data) + self.event_io.send_multipart([stack.sender() or b'*', name, msgpack.packb(data, default=encode_ndarray, use_bin_type=True)]) - def send_stream(self, data, name): - # self.stream_out.send(bytearray(name + self.nodeid, 'ascii'), zmq.SNDMORE) - self.stream_out.send(bytearray(name, 'ascii') + self.nodeid, zmq.SNDMORE) - self.stream_out.send_pyobj(data) + def send_stream(self, name, data): + self.stream_out.send_multipart([name + self.nodeid, msgpack.packb(data, default=encode_ndarray, use_bin_type=True)]) diff --git a/bluesky/io/npcodec.py b/bluesky/io/npcodec.py new file mode 100644 index 0000000000..f64a8fc2e5 --- /dev/null +++ b/bluesky/io/npcodec.py @@ -0,0 +1,16 @@ +import numpy as np + +def encode_ndarray(o): + '''Msgpack encoder for numpy arrays.''' + if isinstance(o, np.ndarray): + return {b'numpy': True, + b'type': o.dtype.str, + b'shape': o.shape, + b'data': o.tobytes()} + return o + +def decode_ndarray(o): + '''Msgpack decoder for numpy arrays.''' + if o.get(b'numpy'): + return np.fromstring(o[b'data'], dtype=np.dtype(o[b'type'])).reshape(o[b'shape']) + return o diff --git a/bluesky/simulation/qtgl/screenio.py b/bluesky/simulation/qtgl/screenio.py index b65b9e915b..463c7f3770 100644 --- a/bluesky/simulation/qtgl/screenio.py +++ b/bluesky/simulation/qtgl/screenio.py @@ -60,14 +60,14 @@ def reset(self): self.prevtime = 0.0 # Communicate reset to gui - bs.sim.send_event(DisplayFlagEvent('RESET', 'ALL')) + bs.sim.send_event(b'RESET', b'ALL') def echo(self, text): - bs.sim.send_event(StackTextEvent(disptext=text)) + bs.sim.send_event(b'ECHO', text) def cmdline(self, text): - bs.sim.send_event(StackTextEvent(cmdtext=text)) + bs.sim.send_event(b'CMDLINE', text) def getviewlatlon(self): lat0 = self.ctrlat - 1.0 / self.scrzoom @@ -81,13 +81,7 @@ def zoom(self, zoom, absolute=True): self.scrzoom = zoom else: self.scrzoom *= zoom - bs.sim.send_event(PanZoomEvent(zoom=zoom, absolute=absolute)) - - def symbol(self): - bs.sim.send_event(DisplayFlagEvent('SYM')) - - def trails(self,sw): - bs.sim.send_event(DisplayFlagEvent('TRAIL',sw)) + bs.sim.send_event(b'PANZOOM', dict(zoom=zoom, absolute=absolute)) def pan(self, *args): ''' Move center of display, relative of to absolute position lat,lon ''' @@ -102,7 +96,16 @@ def pan(self, *args): else: self.ctrlat, self.ctrlon = args - bs.sim.send_event(PanZoomEvent(pan=(self.ctrlat, self.ctrlon), absolute=True)) + bs.sim.send_event(b'PANZOOM', dict(pan=(self.ctrlat, self.ctrlon), absolute=True)) + + def symbol(self): + bs.sim.send_event(b'DISPLAYFLAG', dict(switch='SYM')) + + def feature(self, switch, argument=None): + bs.sim.send_event(b'DISPLAYFLAG', dict(switch=switch, args=argument)) + + def trails(self,sw): + bs.sim.send_event(b'DISPLAYFLAG', dict(switch='TRAIL', args=sw)) def showroute(self, acid): ''' Toggle show route for this aircraft ''' @@ -111,26 +114,18 @@ def showroute(self, acid): def addnavwpt(self, name, lat, lon): ''' Add custom waypoint to visualization ''' - bs.sim.send_event(DisplayFlagEvent('DEFWPT', (name, lat, lon))) + bs.sim.send_event(b'DISPLAYFLAG', dict(switch='DEFWPT', args=(name, lat, lon))) return True - def showssd(self, *param): - ''' Conflict prevention display - Show solution space diagram, indicating potential conflicts''' - bs.sim.send_event(DisplayFlagEvent('SSD', param)) - def show_file_dialog(self): - bs.sim.send_event(ShowDialogEvent()) + bs.sim.send_event(b'SHOWDIALOG', dict(dialog='OPENFILE')) return '' def show_cmd_doc(self, cmd=''): - bs.sim.send_event(ShowDialogEvent(1, cmd=cmd)) - - def feature(self, switch, argument=''): - bs.sim.send_event(DisplayFlagEvent(switch, argument)) + bs.sim.send_event(b'SHOWDIALOG', dict(dialog='DOC', args=cmd)) def filteralt(self, *args): - bs.sim.send_event(DisplayFlagEvent('FILTERALT', args)) + bs.sim.send_event(b'DISPLAYFLAG', dict(switch='FILTERALT', args=args)) def objappend(self, objtype, objname, data_in): """Add a drawing object to the radar screen using the following inpouts: @@ -186,14 +181,14 @@ def objappend(self, objtype, objname, data_in): data[0::2] = latCircle # Fill array lat0,lon0,lat1,lon1.... data[1::2] = lonCircle - bs.sim.send_event(DisplayShapeEvent(objname, data)) + bs.sim.send_event(b'ADDSHAPE', dict(name=objname, data=data)) - def event(self, event, sender_id): + def event(self, eventname, eventdata, sender_id): print('Received event from {}'.format(sender_id)) - if event.type() == PanZoomEventType: - self.ctrlat = event.pan[0] - self.ctrlon = event.pan[1] - self.scrzoom = event.zoom + if eventname == b'PANZOOM': + self.ctrlat = eventdata['pan'][0] + self.ctrlon = eventdata['pan'][1] + self.scrzoom = eventdata['zoom'] return True return False @@ -205,70 +200,70 @@ def send_siminfo(self): t = time.time() dt = np.maximum(t - self.prevtime, 0.00001) # avoid divide by 0 speed = (self.samplecount - self.prevcount) / dt * bs.sim.simdt - bs.sim.send_stream(SimInfoEvent(speed, bs.sim.simdt, bs.sim.simt, - bs.sim.simtclock, bs.traf.ntraf, bs.sim.state, stack.get_scenname()), 'SIMINFO') + bs.sim.send_stream(b'SIMINFO', (speed, bs.sim.simdt, bs.sim.simt, + bs.sim.simtclock, bs.traf.ntraf, bs.sim.state, stack.get_scenname())) self.prevtime = t self.prevcount = self.samplecount def send_aircraft_data(self): - data = ACDataEvent() - data.simt = bs.sim.simt - data.id = bs.traf.id - data.lat = bs.traf.lat - data.lon = bs.traf.lon - data.alt = bs.traf.alt - data.tas = bs.traf.tas - data.cas = bs.traf.cas - data.iconf = bs.traf.asas.iconf - data.confcpalat = bs.traf.asas.latowncpa - data.confcpalon = bs.traf.asas.lonowncpa - data.trk = bs.traf.hdg - data.vs = bs.traf.vs + data = dict() + data['simt'] = bs.sim.simt + data['id'] = bs.traf.id + data['lat'] = bs.traf.lat + data['lon'] = bs.traf.lon + data['alt'] = bs.traf.alt + data['tas'] = bs.traf.tas + data['cas'] = bs.traf.cas + data['iconf'] = bs.traf.asas.iconf + data['confcpalat'] = bs.traf.asas.latowncpa + data['confcpalon'] = bs.traf.asas.lonowncpa + data['trk'] = bs.traf.hdg + data['vs'] = bs.traf.vs # Trails, send only new line segments to be added - data.swtrails = bs.traf.trails.active - data.traillat0 = bs.traf.trails.newlat0 - data.traillon0 = bs.traf.trails.newlon0 - data.traillat1 = bs.traf.trails.newlat1 - data.traillon1 = bs.traf.trails.newlon1 + data['swtrails'] = bs.traf.trails.active + data['traillat0'] = bs.traf.trails.newlat0 + data['traillon0'] = bs.traf.trails.newlon0 + data['traillat1'] = bs.traf.trails.newlat1 + data['traillon1'] = bs.traf.trails.newlon1 bs.traf.trails.clearnew() # Last segment which is being built per aircraft - data.traillastlat = bs.traf.trails.lastlat - data.traillastlon = bs.traf.trails.lastlon + data['traillastlat'] = bs.traf.trails.lastlat + data['traillastlon'] = bs.traf.trails.lastlon # Conflict statistics - data.nconf_tot = len(bs.traf.asas.conflist_all) - data.nlos_tot = len(bs.traf.asas.LOSlist_all) - data.nconf_exp = len(bs.traf.asas.conflist_exp) - data.nlos_exp = len(bs.traf.asas.LOSlist_exp) - data.nconf_cur = len(bs.traf.asas.conflist_now) - data.nlos_cur = len(bs.traf.asas.LOSlist_now) + data['nconf_tot'] = len(bs.traf.asas.conflist_all) + data['nlos_tot'] = len(bs.traf.asas.LOSlist_all) + data['nconf_exp'] = len(bs.traf.asas.conflist_exp) + data['nlos_exp'] = len(bs.traf.asas.LOSlist_exp) + data['nconf_cur'] = len(bs.traf.asas.conflist_now) + data['nlos_cur'] = len(bs.traf.asas.LOSlist_now) # Transition level as defined in traf - data.translvl = bs.traf.translvl + data['translvl'] = bs.traf.translvl - bs.sim.send_stream(data, 'ACDATA') + bs.sim.send_stream(b'ACDATA', data) def send_route_data(self): if self.route_acid: - data = RouteDataEvent() - data.acid = self.route_acid + data = dict() + data['acid'] = self.route_acid idx = bs.traf.id2idx(self.route_acid) if idx >= 0: route = bs.traf.ap.route[idx] - data.iactwp = route.iactwp + data['iactwp'] = route.iactwp # We also need the corresponding aircraft position - data.aclat = bs.traf.lat[idx] - data.aclon = bs.traf.lon[idx] + data['aclat'] = bs.traf.lat[idx] + data['aclon'] = bs.traf.lon[idx] - data.wplat = route.wplat - data.wplon = route.wplon + data['wplat'] = route.wplat + data['wplon'] = route.wplon - data.wpalt = route.wpalt - data.wpspd = route.wpspd + data['wpalt'] = route.wpalt + data['wpspd'] = route.wpspd - data.wpname = route.wpname + data['wpname'] = route.wpname - bs.sim.send_stream(data, 'ACROUTE') # Send route data to GUI + bs.sim.send_stream(b'ROUTEDATA', data) # Send route data to GUI diff --git a/bluesky/simulation/qtgl/simevents.py b/bluesky/simulation/qtgl/simevents.py index 70ba03f270..7c11d9b0f0 100644 --- a/bluesky/simulation/qtgl/simevents.py +++ b/bluesky/simulation/qtgl/simevents.py @@ -89,17 +89,20 @@ def __init__(self, dialog_type=0, **extraattr): class RouteDataEvent(EventBase): - aclat = [] - wplat = [] - wplon = [] - wpalt = [] - wpspd = [] - wpname = [] - iactwp = -1 - acid = "" - - def __init__(self): + def __init__(self, data=None): super(RouteDataEvent, self).__init__(RouteDataEventType) + self.aclat = [] + self.wplat = [] + self.wplon = [] + self.wpalt = [] + self.wpspd = [] + self.wpname = [] + self.iactwp = -1 + self.acid = '' + + # Update values + if data: + self.__dict__.update(data) class DisplayShapeEvent(EventBase): @@ -113,12 +116,29 @@ def __init__(self, name, data=None): class ACDataEvent(EventBase): - lat = lon = alt = tas = trk = vs = iconf = confcpalat = confcpalon = id = [] - nconf_tot = nlos_tot = nconf_exp = nlos_exp = nconf_cur = nlos_cur = 0 - - def __init__(self): + def __init__(self, data=None): super(ACDataEvent, self).__init__(ACDataEventType) - + self.lat = [] + self.lon = [] + self.alt = [] + self.tas = [] + self.trk = [] + self.vs = [] + self.iconf = [] + self.confcpalat = [] + self.confcpalon = [] + self.id = [] + self.nconf_tot = 0 + self.nlos_tot = 0 + self.nconf_exp = 0 + self.nlos_exp = 0 + self.nconf_cur = 0 + self.nlos_cur = 0 + self.translvl = 0.0 + + # Update values + if data: + self.__dict__.update(data) class AMANEvent(EventBase): ids = iafs = eats = etas = delays = rwys = spdratios = [] diff --git a/bluesky/simulation/qtgl/simulation.py b/bluesky/simulation/qtgl/simulation.py index 4a7d5e83b6..8c237ca8ce 100644 --- a/bluesky/simulation/qtgl/simulation.py +++ b/bluesky/simulation/qtgl/simulation.py @@ -56,9 +56,9 @@ def __init__(self): # self.metric = Metric() def prepare(self): - # Send list of stack functions available in this sim to gui at start - stackdict = {cmd : val[0][len(cmd) + 1:] for cmd, val in stack.cmddict.items()} - self.send_event(StackInitEvent(stackdict)) + # TODO Send list of stack functions available in this sim to gui at start + # stackdict = {cmd : val[0][len(cmd) + 1:] for cmd, val in stack.cmddict.items()} + # self.send_event(b'STACKINIT', stackdict) self.syst = int(time.time() * 1000.0) def step(self): @@ -178,7 +178,7 @@ def benchmark(self, fname='IC', dt=300.0): self.benchdt = dt def sendState(self): - self.send_event(SimStateEvent(self.state)) + self.send_event(b'STATECHANGE', self.state) def addNodes(self, count): # TODO Addnodes function @@ -190,31 +190,31 @@ def batch(self, filename): result = stack.openfile(filename) if result: scentime, scencmd = stack.get_scendata() - self.send_event(BatchEvent(scentime, scencmd)) + self.send_event(b'BATCH', dict(scentime=scentime, scencmd=scencmd)) self.reset() return result - def event(self, event, sender_id): + def event(self, eventname, eventdata, sender_id): # Keep track of event processing event_processed = False - if event.type() == StackTextEventType: + if eventname == b'STACKCMD': # We received a single stack command. Add it to the existing stack - stack.stack(event.cmdtext, sender_id) + stack.stack(eventdata, sender_id) event_processed = True - elif event.type() == BatchEventType: + elif eventname == b'BATCH': # We are in a batch simulation, and received an entire scenario. Assign it to the stack. self.reset() - stack.set_scendata(event.scentime, event.scencmd) + stack.set_scendata(eventdata['scentime'], eventdata['scencmd']) self.op() event_processed = True - elif event.type() == SimQuitEventType: + elif eventname == b'QUIT': # BlueSky is quitting self.quit() else: # This is either an unknown event or a gui event. - event_processed = bs.scr.event(event, sender_id) + event_processed = bs.scr.event(eventname, eventdata, sender_id) return event_processed diff --git a/bluesky/stack/stack.py b/bluesky/stack/stack.py index 5cfe0e18a1..3dd95de885 100644 --- a/bluesky/stack/stack.py +++ b/bluesky/stack/stack.py @@ -432,7 +432,7 @@ def init(): "ND": [ "ND acid", "txt", - lambda acid: bs.scr.feature("ND", acid), + lambda acid: bs.scr.feature('ND', acid), "Show navigation display with CDTI" ], "NOISE": [ @@ -606,7 +606,7 @@ def init(): "SSD": [ "SSD ALL/CONFLICTS/OFF or SSD acid0, acid1, ...", "txt,[...]", - bs.scr.showssd, + lambda *args: bs.scr.feature('SSD', args), "Show state-space diagram (=conflict prevention display/predictive ASAS)" ], "SWRAD": [ diff --git a/bluesky/ui/qtgl/console.py b/bluesky/ui/qtgl/console.py index 69be49584f..c3d9869419 100644 --- a/bluesky/ui/qtgl/console.py +++ b/bluesky/ui/qtgl/console.py @@ -36,12 +36,12 @@ def addStackHelp(self, nodeid, stackdict): self.initialized = True self.lineEdit.setHtml('>>') - def stack(self, text, sender_id = None): + def stack(self, text): # Add command to the command history self.command_history.append(text) self.echo(text) # Send stack command to sim process - io.send_event(StackTextEvent(cmdtext=text, sender_id=sender_id)) + io.send_event(b'STACKCMD', text) self.cmdline_stacked.emit(self.cmd, self.args) # reset commandline and the autocomplete history self.setCmdline('') diff --git a/bluesky/ui/qtgl/gui.py b/bluesky/ui/qtgl/gui.py index 824ed41a11..1f60ceb7b2 100644 --- a/bluesky/ui/qtgl/gui.py +++ b/bluesky/ui/qtgl/gui.py @@ -51,7 +51,6 @@ def __init__(self): self.mousepos = (0, 0) self.prevmousepos = (0, 0) self.panzoomchanged = False - self.simt = 0.0 self.initialized = False # Enable HiDPI support (Qt5 only) @@ -91,7 +90,7 @@ def start(self): def quit(self): self.closeAllWindows() - def on_simevent_received(self, event, sender_id): + def on_simevent_received(self, eventname, eventdata, sender_id): ''' Processing of events from simulation nodes. ''' # initialization order problem: TODO if not self.initialized: @@ -101,7 +100,16 @@ def on_simevent_received(self, event, sender_id): io.nodes_changed.emit(sender_id) io.actnode(sender_id) - if event.type() == PanZoomEventType: + if eventname == b'RESET': + # Clear data for this sender + self.radarwidget.clearNodeData(sender_id) + elif eventname == b'ECHO': + self.win.console.echo(eventdata) + elif eventname == b'CMDLINE': + self.win.console.setCmdline(eventdata) + + elif eventname == b'PANZOOM': + event = PanZoomEvent(**eventdata) if event.zoom is not None: event.origin = (self.radarwidget.width / 2, self.radarwidget.height / 2) @@ -112,105 +120,105 @@ def on_simevent_received(self, event, sender_id): # send the pan/zoom event to the radarwidget self.radarwidget.event(event) - elif event.type() == DisplayShapeEventType: - self.radarwidget.updatePolygon(event.name, event.data) - - elif event.type() == StackTextEventType: - if event.disptext: - self.win.console.echo(event.disptext) - if event.cmdtext: - self.win.console.setCmdline(event.cmdtext) - - elif event.type() == StackInitEventType: - self.win.console.addStackHelp(sender_id, event.stackdict) - - elif event.type() == ShowDialogEventType: - if event.dialog_type == event.filedialog_type: - self.show_file_dialog() - elif event.dialog_type == event.docwin_type: - self.show_doc_window(event.cmd) - - elif event.type() == DisplayFlagEventType: + elif eventname == 'DISPLAYFLAG': + flag = eventdata.get('switch') + args = eventdata.get('args') # Switch/toggle/cycle radar screen features e.g. from SWRAD command - if event.switch == 'RESET': - # Clear data for this sender - self.radarwidget.clearNodeData(sender_id) + if flag == "SYM": + # For now only toggle PZ + self.radarwidget.show_pz = not self.radarwidget.show_pz # Coastlines - elif event.switch == "GEO": + elif flag == "GEO": self.radarwidget.show_coast = not self.radarwidget.show_coast # FIR boundaries - elif event.switch == "FIR": + elif flag == "FIR": self.radarwidget.showfir = not self.radarwidget.showfir # Airport: 0 = None, 1 = Large, 2= All - elif event.switch == "APT": + elif flag == "APT": self.radarwidget.show_apt = not self.radarwidget.show_apt # Waypoint: 0 = None, 1 = VOR, 2 = also WPT, 3 = Also terminal area wpts - elif event.switch == "VOR" or event.switch == "WPT" or event.switch == "WP" or event.switch == "NAV": - self.radarwidget.show_apt = not self.radarwidget.show_apt + elif flag == "VOR" or flag == "WPT" or flag == "WP" or flag == "NAV": + self.radarwidget.show_wpt = not self.radarwidget.show_wpt # Satellite image background on/off - elif event.switch == "SAT": + elif flag == "SAT": self.radarwidget.show_map = not self.radarwidget.show_map # Satellite image background on/off - elif event.switch == "TRAF": + elif flag == "TRAF": self.radarwidget.show_traf = not self.radarwidget.show_traf # ND window for selected aircraft - elif event.switch == "ND": - self.nd.setAircraftID(event.argument) + elif flag == "ND": + if args: + self.nd.setAircraftID(args) self.nd.setVisible(not self.nd.isVisible()) - elif event.switch == "SSD": - self.radarwidget.show_ssd(event.argument) - - elif event.switch == "SYM": - # For now only toggle PZ - self.radarwidget.show_pz = not self.radarwidget.show_pz + elif flag == "SSD": + self.radarwidget.show_ssd(args) - elif event.switch == "DEFWPT": - self.radarwidget.defwpt(event.argument) + elif flag == "DEFWPT": + self.radarwidget.defwpt(args) - elif event.switch == "FILTERALT": + elif flag == "FILTERALT": # First argument is an on/off flag nact = self.radarwidget.nodedata[sender_id] - if event.argument[0]: - nact.filteralt = event.argument[1:] + if args[0]: + nact.filteralt = args[1:] else: nact.filteralt = False - def on_simstream_received(self, data, streamname, sender_id): + elif eventname == b'SHOWDIALOG': + dialog = eventdata.get('dialog') + args = eventdata.get('args') + if dialog == 'OPENFILE': + self.show_file_dialog() + elif dialog == 'DOC': + self.show_doc_window(args) + + elif eventname == b'ADDSHAPE': + self.radarwidget.updatePolygon(eventdata['name'], eventdata['data']) + + # TODO stack init + # elif event.type() == StackInitEventType: + # self.win.console.addStackHelp(sender_id, event.stackdict) + + + + + + def on_simstream_received(self, streamname, data, sender_id): # temp: set actnode if not io.actnode(): io.nodes_changed.emit(sender_id) io.actnode(sender_id) - if data.type() == ACDataEventType: - self.acdata = data - self.radarwidget.update_aircraft_data(data) - if self.nd.ac_id in data.id: - idx = data.id.index(self.nd.ac_id.upper()) - lat = data.lat[idx] - lon = data.lon[idx] - trk = data.trk[idx] - tas = data.tas[idx] - self.nd.update_aircraft_data(idx, lat, lon, tas, trk, len(data.lat)) - - elif data.type() == RouteDataEventType: - self.routedata = data - self.radarwidget.update_route_data(data) - - elif data.type() == SimInfoEventType: - simt = tim2txt(data.simt)[:-3] - simtclock = tim2txt(data.simtclock)[:-3] - self.win.setNodeInfo(sender_id, simt, data.scenname) + if streamname == b'SIMINFO': + speed, simdt, simt, simtclock, ntraf, state, scenname = data + simt = tim2txt(simt)[:-3] + simtclock = tim2txt(simtclock)[:-3] + self.win.setNodeInfo(sender_id, simt, scenname) if sender_id == io.actnode(): - self.simt = data.simt self.win.siminfoLabel.setText(u't: %s, \u0394t: %.2f, Speed: %.1fx, UTC: %s, Mode: %s, Aircraft: %d, Conflicts: %d/%d, LoS: %d/%d' - % (simt, data.simdt, data.sys_freq, simtclock, self.modes[data.mode], data.n_ac, self.acdata.nconf_cur, self.acdata.nconf_tot, self.acdata.nlos_cur, self.acdata.nlos_tot)) + % (simt, simdt, speed, simtclock, self.modes[state], ntraf, self.acdata.nconf_cur, self.acdata.nconf_tot, self.acdata.nlos_cur, self.acdata.nlos_tot)) + + elif streamname == b'ACDATA': + self.acdata = ACDataEvent(data) + self.radarwidget.update_aircraft_data(self.acdata) + if self.nd.ac_id in self.acdata.id: + idx = self.acdata.id.index(self.nd.ac_id.upper()) + lat = self.acdata.lat[idx] + lon = self.acdata.lon[idx] + trk = self.acdata.trk[idx] + tas = self.acdata.tas[idx] + self.nd.update_aircraft_data(idx, lat, lon, tas, trk, len(self.acdata.lat)) + + elif streamname == b'ROUTEDATA': + self.routedata = RouteDataEvent(data) + self.radarwidget.update_route_data(self.routedata) def notify(self, receiver, event): # Keep track of event processing @@ -298,8 +306,8 @@ def notify(self, receiver, event): # Update pan/zoom to simulation thread only when the pan/zoom gesture is finished elif (event.type() == QEvent.MouseButtonRelease or event.type() == QEvent.TouchEnd) and self.panzoomchanged: self.panzoomchanged = False - io.send_event(PanZoomEvent( pan=(self.radarwidget.panlat, self.radarwidget.panlon), - zoom=self.radarwidget.zoom, absolute=True)) + io.send_event(b'PANZOOM', dict(pan=(self.radarwidget.panlat, self.radarwidget.panlon), + zoom=self.radarwidget.zoom, absolute=True)) # If we've just processed a change to pan and/or zoom, send the event to the radarwidget if panzoom is not None: diff --git a/bluesky/ui/qtgl/guiio.py b/bluesky/ui/qtgl/guiio.py index 7b6df31a40..150f610842 100644 --- a/bluesky/ui/qtgl/guiio.py +++ b/bluesky/ui/qtgl/guiio.py @@ -14,8 +14,8 @@ _timer = None # Signals -nodes_changed = Signal() activenode_changed = Signal() +nodes_changed = _client.nodes_changed event_received = _client.event_received stream_received = _client.stream_received @@ -29,8 +29,8 @@ def init(): _timer.timeout.connect(_client.receive) _timer.start(10) -def send_event(data, target=None): - _client.send_event(data, target or _act) +def send_event(name, data=None, target=None): + _client.send_event(name, data, target or _act) def actnode(newact=None): if newact is not None: diff --git a/bluesky/ui/qtgl/mainwindow.py b/bluesky/ui/qtgl/mainwindow.py index 6e4ab1331f..01ce1c74c3 100644 --- a/bluesky/ui/qtgl/mainwindow.py +++ b/bluesky/ui/qtgl/mainwindow.py @@ -211,15 +211,15 @@ def buttonClicked(self): elif self.sender() == self.ic: self.app.show_file_dialog() elif self.sender() == self.sameic: - io.send_event(StackTextEvent(cmdtext='IC IC')) + io.send_event(b'STACKCMD', 'IC IC') elif self.sender() == self.hold: - io.send_event(StackTextEvent(cmdtext='HOLD')) + io.send_event(b'STACKCMD', 'HOLD') elif self.sender() == self.op: - io.send_event(StackTextEvent(cmdtext='OP')) + io.send_event(b'STACKCMD', 'OP') elif self.sender() == self.fast: - io.send_event(StackTextEvent(cmdtext='FF')) + io.send_event(b'STACKCMD', 'FF') elif self.sender() == self.fast10: - io.send_event(StackTextEvent(cmdtext='FF 0:0:10')) + io.send_event(b'STACKCMD', 'FF 0:0:10') elif self.sender() == self.showac: self.radarwidget.show_traf = not self.radarwidget.show_traf elif self.sender() == self.showpz: @@ -241,4 +241,4 @@ def buttonClicked(self): elif self.sender() == self.showmap: self.radarwidget.show_map = not self.radarwidget.show_map elif self.sender() == self.action_Save: - io.send_event(StackTextEvent(cmdtext='SAVEIC')) + io.send_event(b'STACKCMD', 'SAVEIC') diff --git a/comm_data_types.rtf b/comm_data_types.rtf new file mode 100644 index 0000000000..8c30ac9cde --- /dev/null +++ b/comm_data_types.rtf @@ -0,0 +1,36 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf830 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +\paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 + +\f0\b\fs24 \cf0 bluesky events: +\b0 \ +\ +stack echo / cmdline\ +\ +displayflagevent\ + symbol, trail, custom waypoints, SSD, feature, filteralt\ +\ +displayshapeevent\ +\ +showdialog\ + filedialog, docwindow\ +\ +panzoomevent\ +\ + +\b bluesky periodic data:\ + +\b0 \ +siminfo\ +\ +acdata\ + a/c state, trails\ +\ +routedata\ +\ +amandata\ +\ +plotdata} \ No newline at end of file