Package mosp :: Module monitors
[hide private]
[frames] | no frames]

Source Code for Module mosp.monitors

  1  # -*- coding: utf-8 -*- 
  2  """Monitors for simulation output creation 
  3   
  4  Output to console, to child process, to file, aso. for analyzing or as input to visualization.""" 
  5   
  6  from SimPy.SimulationRT import Process, hold 
  7  import sys 
  8  import os 
  9  import struct 
 10  import socket 
 11  from geo import utm 
 12  import gui.gimp_palette.gimp_palette as palette 
 13   
 14  __author__ = "B. Henne, P. Tute" 
 15  __contact__ = "henne@dcsec.uni-hannover.de" 
 16  __copyright__ = "(c) 2010-2012, DCSec, Leibniz Universitaet Hannover, Germany" 
 17  __license__ = "GPLv3" 
 18   
 19   
20 -class NoSimulationMonitorDefinedException(Exception):
21 """Exception raised if no monitor was defined. 22 23 Simulation.run() or even Simulation.add_person() does not make 24 sense, if no monitor is defined. Why we should start a simulation 25 with no output? 26 @author: B. Henne"""
27 - def __init__(self, value):
28 self.value = value
29 - def __str__(self):
30 return repr(self.value)
31 32
33 -class EmptyMonitor(Process, list):
34 """This monitor does nothing. 35 36 Other monitors should inherit from this one. 37 38 Usage example see mosp_examples/random_wiggler.py 39 @author: B. Henne 40 @author: P. Tute"""
41 - def __init__(self, name, sim, tick, kwargs):
42 """Initialize the monitor. 43 44 tick specifies the duration in ticks between two monitoring actions. 45 The monitor does something in Monitor.observe() every tick ticks. 46 name is its name, sim is the simulation. 47 @param name: unique string name of monitor 48 @param sim: reference to simulation 49 @param tick: monitoring is done every tick ticks 50 @param kwargs: additional keyword arguments for monitor""" 51 Process.__init__(self, name=name, sim=sim) 52 list.__init__(self) 53 self.tick = tick
54
55 - def init(self):
56 """Init the monitor once.""" 57 sys.stderr.write("EmptyMonitor init() -- you should use a fully implemented monitor instead.\n") 58 pass
59
60 - def observe(self):
61 """This should be done each self.tick ticks. 62 63 The monitoring code: A monitor is a SimPy Process.""" 64 pass
65
66 - def center_on_lat_lon(self, lat, lon):
67 pass
68
69 - def draw_point(self, id, lat, lon, radius, color, ttl=0):
70 pass
71
72 - def draw_circle(self, id, center_lat, center_lon, radius, filled, color, ttl=0):
73 pass
74
75 - def draw_rectangle(self, id, lat_bottom, lat_top, lon_left, lon_right, line_width, filled, color, ttl=0):
76 pass
77
78 - def draw_text(self, id, lat, lon, offset_x, offset_y, font_size, color, text, ttl=0):
79 pass
80
81 - def remove_object(self, type, id):
82 pass
83
84 - def end(self):
85 """This can be called after a simulation ends. 86 87 Only necessary if the monitor is supposed to do something after the simulation ended.""" 88 pass
89 90
91 -class PipePlayerMonitor(EmptyMonitor):
92 """This monitor writes person movement data via struct to stdout. 93 94 Output is created for piping in player.py. 95 96 Usage example: python random_wiggler.py | python player.py 97 @author: B. Henne""" 98 99 FORMAT = '<BIII' #: struct.pack format of Person information 100 FORMAT_LEN = struct.calcsize(FORMAT) #: length of FORMAT in bytes 101 102 start_tick = 0 #: monitor starts with this tick 103
104 - def init(self):
105 """Prints init values for player.py to stdout and activates monitor process.""" 106 print len(self) + 2 #XXX dest_node marker hack 107 print '%f' % self.sim.geo.bounds['minlat'] 108 print '%f' % self.sim.geo.bounds['minlon'] 109 print '%f' % self.sim.geo.bounds['maxlat'] 110 print '%f' % self.sim.geo.bounds['maxlon'] 111 print self.sim.geo.zone 112 sys.stdout.flush() 113 self.sim.activate(self, self.observe(), self.start_tick)
114
115 - def observe(self):
116 """Prints person ids and coordinates to stdout.""" 117 while 42: 118 yield hold, self, self.tick 119 for pers in self: 120 pos = pers.current_coords() 121 sys.stdout.write('xxxx') 122 sys.stdout.write('\x00' + 123 struct.pack(self.FORMAT, pers.p_color, pers.p_id, pos[0], pos[1])) 124 125 sys.stdout.write('\x01' + struct.pack('I', self.sim.now())) 126 sys.stdout.flush()
127 128
129 -class SocketPlayerMonitor(EmptyMonitor):
130 131 """Sends output of simulation to viewer via sockets. 132 133 @author: P. Tute 134 """ 135 136 MESSAGES = {'coords': '\x00', 137 'point': '\x01', 138 'rectangle': '\x02', 139 'circle': '\x03', 140 'triangle': '\x04', 141 'text': '\x05', 142 'heatmap': '\x06', 143 'direct-text': '\x07', 144 'delete': '\xFD', 145 'draw': '\xFE', 146 'simulation_ended': '\xFF', 147 } 148 149 FORMATS = {'coords': '!dd', 150 'point': '!iddi4dd', 151 'rectangle': '!i4di?4dd', 152 'circle': '!iddi?4dd', 153 'triangle': '!i2d2d2d?4dd', 154 'text': '!iddiii4did', 155 'heatmap': '!ddi4d', 156 'direct-text': '!iiii4did', 157 'delete': '!i', 158 } 159 160 start_tick = 0 161
162 - def __init__(self, name, sim, tick, kwargs):
163 """Initialize the monitor.""" 164 EmptyMonitor.__init__(self, name, sim, tick, kwargs) 165 if 'host' in kwargs: 166 self.host = kwargs['host'] 167 else: 168 self.host = 'localhost' 169 if 'port' in kwargs: 170 self.port = kwargs['port'] 171 else: 172 self.port = 60001 173 if 'drawbb' in kwargs: 174 self.draw_bb = kwargs['drawbb'] 175 else: 176 self.draw_bb = True 177 178 palette_filename = os.path.join(os.path.dirname(palette.__file__), 'Visibone.gpl') 179 self.color_palette = palette.GimpPalette(palette_filename) 180 self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 181 self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 182 self.s.bind(('localhost',60001)) 183 self.s.listen(1) 184 print 'Waiting for viewer to connect...' 185 self.conn, self.addr = self.s.accept()
186
187 - def init(self):
188 """Send coordinates to center camera on and start observing.""" 189 190 center_lat = self.sim.geo.bounds['minlat'] + (self.sim.geo.bounds['maxlat'] - self.sim.geo.bounds['minlat']) / 2 191 center_lon = self.sim.geo.bounds['minlon'] + (self.sim.geo.bounds['maxlon'] - self.sim.geo.bounds['minlon']) / 2 192 self.center_on_lat_lon(center_lat, center_lon) 193 # send viewer bounding box to draw 194 if self.draw_bb: 195 data = struct.pack(self.FORMATS['rectangle'], 196 0, 197 self.sim.geo.bounds['minlat'], 198 self.sim.geo.bounds['minlon'], 199 self.sim.geo.bounds['maxlat'], 200 self.sim.geo.bounds['maxlon'], 201 1, 202 False, 203 1.0, 0.1, 0.1, 1.0, 204 0) 205 self.conn.send(self.MESSAGES['rectangle'] + data) 206 207 self.conn.send(self.MESSAGES['draw']) 208 self.sim.activate(self, self.observe(), self.start_tick)
209
210 - def end(self):
211 """Send a signal to the viewer to announce simulation end.""" 212 self.conn.send(self.MESSAGES['simulation_ended'])
213
214 - def observe(self):
215 """Send person coordinates and other data to draw them as points.""" 216 while 42: 217 yield hold, self, self.tick 218 for pers in self: 219 pos = pers.current_coords() 220 if pos is None: 221 continue 222 lon, lat = utm.utm_to_latlong(pos[0], pos[1], self.sim.geo.zone) 223 (r, g, b, a) = pers.p_color_rgba 224 ttl = 0 225 # add 50000 to pers.p_id so the chance of id-collisions when drawing other points is smaller 226 data = struct.pack(self.FORMATS['point'], pers.p_id+50000, lat, lon, 2, r, g, b, a, ttl) 227 self.conn.send(self.MESSAGES['point'] + data) 228 self.conn.send(self.MESSAGES['draw'])
229
230 - def center_on_lat_lon(self, lat, lon):
231 """Center the connected viewer on specified coordinates. 232 233 @param lat: Latitude to center on 234 @type lat: float 235 @param lon: Longitude to center on 236 @type lon: float 237 238 """ 239 240 coords = struct.pack(self.FORMATS['coords'], lat, lon) 241 self.conn.send(self.MESSAGES['coords'] + coords)
242
243 - def parse_color(self, color):
244 """Determine the way color is defined and return an RGBA-tuple. 245 246 @param color: The color that is to be parsed 247 @type color: string containing the name of a color, or tuple with 4 floats in range [0,1] 248 249 """ 250 251 if isinstance(color, tuple) or isinstance(color, list): 252 if len(color) != 4: 253 raise AttributeError('color-tuple must be of length 4') 254 re = [] 255 for i in color: 256 if isinstance(i, float) and 0 <= i <= 1: 257 re.append(i) 258 else: 259 raise AttributeError('colors in tuple must be floats in [0,1]') 260 elif isinstance(color, basestring): 261 try: 262 re = self.color_palette.rgba(color) 263 except KeyError: 264 print 'Color "%s" not found. Using black instead.' % color 265 re = (0.0, 0.0, 0.0, 1.0) 266 return re
267
268 - def draw_point(self, id, lat, lon, radius, color, ttl=0):
269 """Send a point to be drawn to the viewer. 270 271 @param id: ID of the drawn point. Should be unique or another point will be replaced. 272 @type id: int 273 @param lat: Latitude to draw point at. 274 @type lat: float 275 @param lon: Longitude to draw point at. 276 @type lon: float 277 @param radius: A radius of 0 means a size of one pixel. A radius of n means n pixels will be drawn in each direction around the center-pixel. 278 @type radius: int 279 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color. 280 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string 281 @param ttl: Specifies a time to live for the point in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed. 282 @type ttl: int 283 284 """ 285 286 (r, g, b, a) = self.parse_color(color) 287 data = struct.pack(self.FORMATS['point'], 288 id, lat, lon, radius, r, g, b, a, ttl) 289 self.conn.send(self.MESSAGES['point'] + data) 290 self.conn.send(self.MESSAGES['draw'])
291
292 - def draw_circle(self, id, center_lat, center_lon, radius, filled, color, ttl=0):
293 """Send a circle to be drawn to the viewer. 294 295 @param id: ID of the drawn circle. Should be unique or another circle will be replaced. 296 @type id: int 297 @param center_lat: Latitude to center circle at. 298 @type center_lat: float 299 @param center_lon: Longitude to center circle at. 300 @type center_lon: float 301 @param radius: Radius of the circle, in meter 302 @type radius: int 303 @param filled: Signals whether the circle should be filled or hollow. 304 @type filled: boolean 305 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color. 306 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string 307 @param ttl: Specifies a time to live for the circle in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed. 308 @type ttl: int 309 310 """ 311 312 (r, g, b, a) = self.parse_color(color) 313 data = struct.pack(self.FORMATS['circle'], 314 id, 315 center_lat, 316 center_lon, 317 radius, 318 filled, 319 r, g, b, a, 320 ttl) 321 self.conn.send(self.MESSAGES['circle'] + data) 322 self.conn.send(self.MESSAGES['draw'])
323
324 - def draw_rectangle(self, id, lat_bottom, lat_top, lon_left, lon_right, line_width, filled, color, ttl=0):
325 """Send a rectangle to be drawn to the viewer. 326 327 @param id: ID of the drawn rectangle. Should be unique or another rectangle will be replaced. 328 @type id: int 329 @param lat_top: Latitude of top of the rectangle. 330 @type lat_top: float 331 @param lat_bottom: Latitude of bottom of the rectangle. 332 @type lat_bottom: float 333 @param lon_left: Longitude of left side of rectangle. 334 @type lon_left: float 335 @param lon_right: Longitude of right side of rectangle. 336 @type lon_right: float 337 @param line_width: thickness of the lines of the rectangle, if it is not filled 338 @type line_width: int 339 @param filled: Signals whether the rectangle should be filled or hollow. 340 @type filled: boolean 341 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color. 342 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string 343 @param ttl: Specifies a time to live for the rectangle in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed. 344 @type ttl: int 345 346 """ 347 348 (r, g, b, a) = self.parse_color(color) 349 data = struct.pack(self.FORMATS['rectangle'], 350 id, 351 lat_bottom, lon_left, 352 lat_top, lon_right, 353 line_width, filled, 354 r, g, b, a, 355 ttl) 356 self.conn.send(self.MESSAGES['rectangle'] + data) 357 self.conn.send(self.MESSAGES['draw'])
358
359 - def draw_triangle(self, id, lat1, lon1, lat2, lon2, lat3, lon3, filled, color, ttl=0):
360 """Send a triangle to be drawn to the viewer. 361 362 @param id: ID of the drawn triangle. Should be unique or another triangle will be replaced. 363 @type id: int 364 @param lat1: Latitude of the first corner of the triangle. 365 @type lat1: float 366 @param lon1: Longitude of first corner of triangle. 367 @type lon1: float 368 @param lat2: Latitude of the second corner of the triangle. 369 @type lat2: float 370 @param lon2: Longitude of second corner of triangle. 371 @type lon2: float 372 @param lat3: Latitude of the third corner of the triangle. 373 @type lat3: float 374 @param lon3: Longitude of third corner of triangle. 375 @type lon3: float 376 @param filled: Signals whether the triangle should be filled or hollow. 377 @type filled: boolean 378 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color. 379 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string 380 @param ttl: Specifies a time to live for the triangle in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed. 381 @type ttl: int 382 383 """ 384 385 (r, g, b, a) = self.parse_color(color) 386 data = struct.pack(self.FORMATS['triangle'], 387 id, 388 lat1, lon1, 389 lat2, lon2, 390 lat3, lon3, 391 filled, 392 r, g, b, a, 393 ttl) 394 self.conn.send(self.MESSAGES['triangle'] + data)
395
396 - def draw_text_to_screen(self, id, x, y, font_size, color, text, ttl=0):
397 """Draw a text in the viewer. 398 399 This method uses x, y coordinates of the player instead of lat/lon. It can be used to permanently draw something on the viewer window. IDs are no shared with normal text-IDs. 400 401 @param id: Should be unique or another text will be replaced. 402 @type id: int 403 @param x: x-coordinate of left side of the text, negative value means 'from right' instead of 'from left' (positive value) of screen 404 @type x: int 405 @param y: y-coordinate of bottom side of text, negative value means 'from top' instead of 'from bottom' (positive value) of screen 406 @type y: int 407 @param font_size: Fontsize in points 408 @type font_size: int 409 @param text: The actual text to display in the viewer 410 @type text: string 411 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color. 412 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string 413 @param ttl: Specifies a time to live for the rectangle in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed. 414 @type ttl: int 415 416 """ 417 (r, g, b, a) = self.parse_color(color) 418 text_struct = struct.pack('!' + 'c' * len(text), *[l for l in text]) 419 data = struct.pack(self.FORMATS['direct-text'], 420 id, 421 x, y, 422 font_size, 423 r, g, b, a, 424 struct.calcsize('!' + 'c' * len(text)), 425 ttl) 426 self.conn.send(self.MESSAGES['direct-text'] + data + text_struct) 427 self.conn.send(self.MESSAGES['draw'])
428
429 - def draw_text(self, id, lat, lon, offset_x, offset_y, font_size, color, text, ttl=0):
430 """Draw a text in the viewer. 431 432 @param id: Should be unique or another text will be replaced. 433 @type id: int 434 @param lat: Latitude of bottom of the text 435 @type lat: float 436 @param lon: Longitude of left side of text 437 @type lon: float 438 @param offset_x: An offset added to the given coordinates, specified in meters 439 @type offset_x: int 440 @param offset_y: An offset added to the given coordinates, specified in meters 441 @type offset_y: int 442 @param font_size: Fontsize in points 443 @type font_size: int 444 @param text: The actual text to display in the viewer 445 @type text: string 446 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color. 447 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string 448 @param ttl: Specifies a time to live for the rectangle in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed. 449 @type ttl: int 450 451 """ 452 453 (r, g, b, a) = self.parse_color(color) 454 text_struct = struct.pack('!' + 'c' * len(text), *[l for l in text]) 455 data = struct.pack(self.FORMATS['text'], 456 id, 457 lat, lon, 458 offset_x, offset_y, 459 font_size, 460 r, g, b, a, 461 struct.calcsize('!' + 'c' * len(text)), 462 ttl) 463 self.conn.send(self.MESSAGES['text'] + data + text_struct) 464 self.conn.send(self.MESSAGES['draw'])
465
466 - def add_heatmap_blip(self, lat, lon, radius, color):
467 """Draw another heatmap-blip in the viewer. 468 469 @param lat: Latitude to draw point at. 470 @type lat: float 471 @param lon: Longitude to draw point at. 472 @type lon: float 473 @param radius: A radius of 0 means a size of one pixel. A radius of n means n pixels will be drawn in each direction around the center-pixel. 474 @type radius: int 475 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color. 476 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string 477 478 """ 479 480 (r, g, b, a) = self.parse_color(color) 481 data = struct.pack(self.FORMATS['heatmap'], 482 lat, lon, 483 radius, 484 r, g, b, a) 485 self.conn.send(self.MESSAGES['heatmap'] + data) 486 self.conn.send(self.MESSAGES['draw'])
487
488 - def remove_object(self, type, id):
489 """Remove a drawing object from the viewer. 490 491 @param type: The type of the object to be removed (e. g. 'rectangle') 492 @type type: string 493 @param id: ID of the object to be removed 494 @type id: int 495 496 """ 497 498 data = struct.pack(self.FORMATS['delete'], 499 id) 500 try: 501 self.conn.send(self.MESSAGES['delete'] + self.MESSAGES[type] + data) 502 except KeyError: 503 print 'type unknown. Delete ignored.' 504 return
505 506
507 -class ChildprocessPlayerChamplainMonitor(EmptyMonitor):
508 """Output of simulation to player.py via internal pipe to subprocess. 509 510 Usage example: python random_wiggler.py 511 @author: B. Henne""" 512 513 FORMAT = '<BIII' #: struct.pack format of Person information 514 FORMAT_LEN = struct.calcsize(FORMAT) #: length of FORMAT in bytes 515 516 start_tick = 0 #: monitor starts with this tick 517
518 - def __init__(self, name, sim, tick, kwargs):
519 """Inits the monitor and the subprocess.""" 520 import subprocess 521 import atexit 522 EmptyMonitor.__init__(self, name, sim, tick, kwargs) 523 self.player = None 524 player_cwd = None 525 if 'cwd' in kwargs: 526 player_cwd = kwargs['cwd'] 527 from os.path import dirname 528 self.player = subprocess.Popen(['python', dirname(__file__)+'/gui/playerChamplain.py'], shell=False, cwd=player_cwd, 529 stdin=subprocess.PIPE, stdout=subprocess.PIPE) # stdout can be used for back channel 530 if hasattr(self.player, 'terminate'): 531 atexit.register(self.player.terminate) 532 elif hasattr(self.player, 'kill'): 533 atexit.register(self.player.kill) # does not work with pypy, only with cpython 534 elif hasattr(self.player, 'wait'): 535 atexit.register(self.player.wait) # the pypy way
536
537 - def init(self):
538 """Activates the monitor.""" 539 self.sim.activate(self, self.observe(), self.start_tick)
540
541 - def observe(self):
542 """Pipes init data, person ids and coordinates to subprocess.""" 543 # init player.py 544 self.write('%s\n' % (len(self) + 2)) #XXX dest_node marker hack) 545 self.write('%f\n' % self.sim.geo.bounds['minlat']) 546 self.write('%f\n' % self.sim.geo.bounds['minlon']) 547 self.write('%f\n' % self.sim.geo.bounds['maxlat']) 548 self.write('%f\n' % self.sim.geo.bounds['maxlon']) 549 self.write('%d\n' % self.sim.geo.zone) 550 self.flush() 551 while 42: 552 for pers in self: 553 pos = pers.current_coords() 554 self.write('\x00' + 555 struct.pack(self.FORMAT, pers.p_color, pers.p_id, pos[0], pos[1])) 556 self.write('\x01' + struct.pack('I', self.sim.now())) 557 self.flush() 558 yield hold, self, self.tick
559
560 - def write(self, output):
561 """Write to player.py subprocess.""" 562 self.player.stdin.write(output)
563
564 - def flush(self):
565 """Flush output to player.py subprocess.""" 566 self.player.stdin.flush()
567 568
569 -class RecordFilePlayerMonitor(EmptyMonitor):
570 """Records output for player.py in a file to playback later. 571 572 Usage example: python random_wiggler.py 573 and later: slowcat.py -d .02 < simoutputfile | python player.py 574 @author: B. Henne""" 575 576 FORMAT = '<BIII' 577 FORMAT_LEN = struct.calcsize(FORMAT) 578 579 start_tick = 0 580
581 - def __init__(self, name, sim, tick, kwargs):
582 """Inits the monitor and opens the file.""" 583 from time import time 584 EmptyMonitor.__init__(self, name, sim, tick, kwargs) 585 filename = '/tmp/mosp_RecordFilePlayerMonitor_'+str(int(time())) 586 if 'filename' in kwargs and kwargs['filename'] is not None: 587 filename = kwargs['filename'] 588 self.f = open(filename, 'wb')
589
590 - def init(self):
591 """Starts the monitor.""" 592 self.sim.activate(self, self.observe(), self.start_tick)
593
594 - def observe(self):
595 """Writes init data, person ids and coordinates.""" 596 # init player.py 597 self.write('%s\n' % (len(self) + 2)) #XXX dest_node marker hack) 598 self.write('%f\n' % self.sim.geo.bounds['minlat']) 599 self.write('%f\n' % self.sim.geo.bounds['minlon']) 600 self.write('%f\n' % self.sim.geo.bounds['maxlat']) 601 self.write('%f\n' % self.sim.geo.bounds['maxlon']) 602 self.write('%d\n' % self.sim.geo.zone) 603 self.flush() 604 while 42: 605 for pers in self: 606 pos = pers.current_coords() 607 self.write('\x00' + 608 struct.pack(self.FORMAT, pers.p_color, pers.p_id, pos[0], pos[1])) 609 self.write('\x01' + struct.pack('I', self.sim.now())) 610 self.flush() 611 yield hold, self, self.tick
612
613 - def write(self, output):
614 """Write to file self.f""" 615 self.f.write(output)
616
617 - def flush(self):
618 """Flush output to file self.f""" 619 self.f.flush()
620