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