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

Source Code for Module mosp.core

  1  """Mobile Security & Privacy Simulator core""" 
  2   
  3  import sys 
  4  import random 
  5  import math 
  6  import time 
  7  import datetime 
  8  import struct 
  9  from heapq import heappush, heappop 
 10  from struct import pack, unpack 
 11  import logging 
 12   
 13  import socket 
 14   
 15  from SimPy import SimulationRT 
 16  from SimPy.SimulationRT import hold, passivate 
 17   
 18  from locations import PersonWakeUp 
 19  import group 
 20  import collide 
 21  import monitors 
 22  from geo import utils 
 23  from geo import osm 
 24   
 25  __author__ = "B. Henne, F. Ludwig, P. Tute" 
 26  __contact__ = "henne@dcsec.uni-hannover.de" 
 27  __copyright__ = "(c) 2010-2012, DCSec, Leibniz Universitaet Hannover, Germany" 
 28  __license__ = "GPLv3" 
29 30 31 -class Simulation(SimulationRT.SimulationRT):
32 """A MOSP Simulation. 33 34 Extends SimPy's SimulationRT (Simulation with Real Time Synchronization) 35 to store MOSP specific data and implement specific methods. 36 @author: F. Ludwig 37 @author: P. Tute 38 @author: B. Henne""" 39
40 - def __init__(self, geo, start_timestamp=None, rel_speed=None, seed=1, allow_dup=False):
41 """Initialize the MOSP Simulation. 42 43 @param geo: geo model for simulation, a mosp.geo.osm.OSMModel extending the mops.collide.World 44 @param start_timestamp: time.time timestamp when simulation starts - used to calc DateTime of simlation out of simulation ticks. 45 @param rel_speed: (SimPy) ratio simulation time over wallclock time; example: rel_speed=200 executes 200 units of simulation time in about one second 46 @param seed: seed for simulation random generator 47 @param allow_dup: allow duplicates? only one or multiple Simulations can be startet at once 48 """ 49 SimulationRT.SimulationRT.__init__(self) 50 assert allow_dup or osm.GLOBAL_SIM is None 51 osm.GLOBAL_SIM = self 52 self.initialize() # init SimPy.SimulationRT 53 self.geo = geo #: the geo-modell of the Simulation 54 self.monitors = [] #: contains all Monitors of the Simulation 55 self.rel_speed = rel_speed if rel_speed else 1 56 self.start_timestamp = self.start_timestamp if start_timestamp else time.time() 57 self.random = random.Random(seed) #: central simulation-wide random generator 58 self.next_person_id = 0 #: the next id that will be given to a new person 59 self.persons = group.PersonGroup() #: stores simulated Persons 60 self.removed_persons = {} #: stores removed Persons for later use 61 self.person_alarm_clock = PersonWakeUp('Omni-present person wake up alarm', self) #: central Process for waking up Persons on pause 62 self.messages = [] #: stores scheduled Calls of PersonGroups for execution as zombie's infect() 63 group.Message.sim = self 64 geo.initialize(self) # load OSM data, load/calculate routing table and grid
65
66 - def add_monitor(self, monitor_cls, tick=1, **kwargs):
67 """Add a Monitor to Simulation to produce any kind of output. 68 @param monitor_cls: the monitor class from mops.monitors 69 @param tick: new monitor will observe every tick tick 70 @param kwargs: keyword parameters for monitor class instantiation 71 @return: reference to new, added monitor instance 72 """ 73 mon = monitor_cls('mon'+str(len(self.monitors)), self, tick, kwargs) 74 self.monitors.append(mon) 75 return mon
76
77 - def add_persons(self, pers_cls, n=1, monitor=None, args=None):
78 """Add a Person to Simulation. 79 @param pers_cls: the person class (inherited from mosp.core.Person) 80 @param n: the number of new, added instances of pers_cls 81 @param monitor: (list of) monitor(s) the person(s) shall be observed by 82 @param args: dictionary of arguments for pers_cls instantiation 83 """ 84 if not args: 85 args = {} 86 for i in xrange(n): 87 seed = self.random.randrange(2**24) # must be >> number of persons! 88 pers = pers_cls(self.next_person_id, self, random.Random(seed), **args) 89 self.next_person_id += 1 90 if monitor is not None: 91 if isinstance(monitor, monitors.EmptyMonitor): 92 # a single monitor 93 monitor.append(pers) 94 elif hasattr(monitor,'__iter__'): 95 # an iterable list of monitors 96 for mon in monitor: 97 mon.append(pers) 98 self.activate(pers, pers.go(), 0) 99 self.persons.add(pers)
100
101 - def get_person(self, id):
102 """Find a person by its ID. 103 104 @param: the ID of the person 105 @return: the person or None, if the ID was not found 106 107 """ 108 result = self.persons.filter(p_id=id) 109 if len(result) == 1: 110 return result.pop() 111 else: 112 return None
113
114 - def del_person(self, person):
115 """Remove a person from the simulation. 116 117 The person will be saved in self.removed_persons for later reference. 118 @todo: try using Process.cancel() from SimPy (is broken atm and may stay broken...try again with new go) 119 @todo: maybe remove person from monitors 120 @param person: the person to be removed. The person must have been added and must be active. 121 """ 122 self.removed_persons[person.p_id] = person 123 person.stop_actions(True) 124 person.remove_from_sim = True 125 self.persons.remove(person)
126
127 - def readd_person(self, id, changes={}):
128 """Add a previously removed person to the simulation again. 129 130 @param id: id of the removed person 131 @type id: int 132 @param changes: changes to be made when reinserting the person 133 @type changes: dict with pairs of 'variable_name': value (variable_name in a string) 134 """ 135 if id not in self.removed_persons: 136 print 'Tried to re-add unknown person...ignored.' 137 print id, type(id) 138 print self.removed_persons 139 return 140 person = self.removed_persons[id] 141 person.__dict__.update(changes) 142 person.current_way.persons.append(person) 143 person.current_coords = person.current_coords_impl 144 for a in person._stopped_actions_for_removal: 145 a.start() 146 person._stopped_actions_for_removal = [] 147 self.persons.add(person) 148 # Bad hack: unterminate 149 person._terminated = False 150 person._nextTime = None 151 self.activate(person, person.go()) 152 person.readd_actions()
153
154 - def run(self, until, real_time, monitor=True):
155 """Runs Simulation after setup. 156 157 @param until: simulation runs until this tick 158 @param real_time: run in real-time? or as fast as possible 159 @param monitor: start defined monitors? 160 """ 161 if monitor: 162 if len(self.monitors) == 0: 163 raise monitors.NoSimulationMonitorDefinedException('at mosp.Simulation.run()') 164 for mon in self.monitors: 165 mon.init() 166 167 # alarm for person.pause_movement() 168 self.activate(self.person_alarm_clock, self.person_alarm_clock.serve(), 0) 169 170 # based on code from SimPy.SimulationRT.simulate 171 self.rtstart = self.wallclock() 172 self.rtset(self.rel_speed) 173 174 last_event_time = 0 175 avg_delta = 0 176 while self._timestamps and not self._stop: 177 next_event_time = self.peek() 178 179 # new implementation of real_time, only rel_speed = 1 180 if real_time and self.rel_speed == 1: 181 now = time.time() 182 sim_now = self.start_timestamp + self.now() 183 delta = now - sim_now 184 #avg_delta = 0.8 * avg_delta + 0.2 * delta 185 186 #print '-'*50 187 #print 'delta (>0, wenn sim zu langsam)', delta 188 #print 'avg_delta', avg_delta 189 if delta < 0: 190 time.sleep(-delta) 191 192 if last_event_time != next_event_time: 193 pass # replaces next logging statement 194 #logging.debug('Simulation.run.next_event_time = %s' % next_event_time) 195 last_event_time = next_event_time 196 if next_event_time > until: 197 break 198 199 #print '-'*50 200 #print 'delta (>0, wenn sim zu langsam)', delta 201 #print 'avg_delta', avg_delta 202 if real_time and self.rel_speed != 1: 203 delay = ( 204 next_event_time / self.rel_speed - 205 (self.wallclock() - self.rtstart) 206 ) 207 if delay > 0: time.sleep(delay) 208 209 # do communication stuff 210 while self.messages and self.messages[0].time < next_event_time: 211 # execute messages 212 heappop(self.messages)() # execute __call__() of popped object 213 214 self.step() 215 216 # There are still events in the timestamps list and the simulation 217 # has not been manually stopped. This means we have reached the stop 218 # time. 219 for m in self.monitors: 220 m.end() 221 if not self._stop and self._timestamps: 222 self._t = until 223 return 'SimPy: Normal exit' 224 else: 225 return 'SimPy: No activities scheduled'
226
227 - def __getstate__(self):
228 """Returns Simulation information for pickling using the pickle module.""" 229 state = self.__dict__ 230 #print 'get state', state 231 del state['sim'] 232 return state
233
234 235 -class DateTime(datetime.datetime):
236 """Simulation DateTime extension. 237 @author: F. Ludwig""" 238 @classmethod
239 - def fromtick(self, sim, tick):
240 """Returns a DateTime object representing simulation time. 241 @return: DateTime object with time as sim.start_timestamp+tick/sim_rel_speed""" 242 ts = sim.start_timestamp + float(tick) / sim.rel_speed 243 dt = self.fromtimestamp(ts) 244 dt.sim = sim 245 dt.tick = tick 246 return dt
247
248 249 -class PersonActionBase(SimulationRT.Process):
250 """Base class for Person actions. Every action is an own SimPy Process. 251 @author: F. Ludwig"""
252 - def go(self):
253 """Person executes action (self.func) every self.every ticks.""" 254 while True: 255 yield hold, self, self.every 256 answer = self.func(self.pers) 257 if answer: 258 self.every = answer
259
260 261 -def action(every, start=True):
262 """MOSP action decorator executes an action defined by an method regularly using a PersonAction(SimPy Process). 263 @param every: action is executed every every ticks 264 @param start: start action immediately? 265 @author: F. Ludwig""" 266 def re(func): 267 class PersonAction(PersonActionBase): 268 """Encapsulates a person's action executes regularly.""" 269 def __init__(self, pers, sim): 270 """Inits the PersonAction. Starts it if requested.""" 271 name = pers.name + '_action_' + str(id(self)) 272 SimulationRT.Process.__init__(self, name=name, sim=sim) 273 self.every = every 274 self.func = func 275 self.pers = pers 276 if start: 277 self.start()
278 279 def start(self, at='undefined', delay='undefined'): 280 """Starts the action. 281 @param at: start tick 282 @param delay: start action with delay of ticks""" 283 self.sim.activate(self, self.go(), at=at, delay=delay) 284 285 def stop(self): 286 """Stops an action.""" 287 self.pers.cancel(self) 288 pass # replaces next logging statement 289 #logging.debug('t=%s action.stop: stopped action %s / %s of person %s' % (self.sim.now(), self.func, self.name, self.pers.id)) 290 291 func.action = PersonAction 292 return func 293 return re 294
295 -def start_action(action, at='undefined', delay='undefined'):
296 """Starts an action that has not been started (or stopped?) before. 297 @author: F. Ludwig""" 298 action.im_self._actions[action.im_func].start(at=at, delay=delay)
299
300 -def stop_action(action):
301 """Stops an action. 302 @author: B. Henne""" 303 action.im_self._actions[action.im_func].stop()
304
305 -def distXY2XY(x1, y1, x2, y2):
306 """Return the distance of two coordinate pairs (four values). 307 @author: B. Henne""" 308 return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
309
310 -def distN2N(n1, n2):
311 """Return the distance of two coordinate pairs (two pairs). 312 @param n1: List/2-tuple [x, y] 313 @param n2: List/2-tuple [x, y] 314 @author: B. Henne""" 315 return math.sqrt((n2[0] - n1[0])**2 + (n2[1] - n1[1])**2)
316
317 318 -class Person(SimulationRT.Process, collide.Point):
319 """A basic simulated Person."""
320 - def __init__(self, id, sim, random, speed=1.4, **kwargs):
321 """Initializes the Person. 322 @param id: unique id of a person 323 @param sim: reference to superordinate mosp.core.Simulation 324 @param random: random object for Person - use superordinate mosp.core.Simulation.random 325 @param speed: basic walking speed of person in meter/tick 326 @param kwargs: additonal keyword arguments intended for inheriting classes 327 @author: F. Ludwig 328 @author: P. Tute 329 @author: B. Henne 330 """ 331 SimulationRT.Process.__init__(self, name='p' + str(id), sim=sim) 332 333 #self.next_target() 334 # private person-intern data 335 self._p_speed = speed #: speed of Person 336 self._random = random #: random object of the Person 337 self._start_time = sim.now() #: the tick when this person started the current walk from last to target node 338 self._duration = 0 #: time to next round of go() 339 self._location_offset = [0, 0] #: used by pause_movement as input for random offset to current position while pausing 340 self._actions = {} #: stores all actions of the Person 341 self._stopped_actions = [] #: stores stopped actions 342 self._stopped_actions_for_removal = [] #: stores actions that were stopped when removing the person from the simulation 343 self._messages = [] #: stores callback messages of the Person - calling back must still be implemented (todo) 344 345 # location information 346 self.last_node = self.get_random_way_node(include_exits=False) #: node where current walking starts 347 self.next_node = self.last_node #: node where current walking stops, next node to walk to 348 self.start_node = None #: start node of current routed walk 349 self.dest_node = self.next_node #: final node of current routed walk 350 self.current_way = self._random.choice(self.next_node.ways.values()) #: current way the Person is assigned to, used for collision 351 self.current_way.persons.append(self) 352 self.road_orthogonal_offset = self.current_way.width #: walking offset from midline for road width implementation - 2-tuple - see next_target_coord 353 self.current_coords = self.current_coords_impl #: returns current coordinates of Person 354 self.last_coord = [0, 0] #: coordinates where current walking starts = coordinates of last_node plus offset 355 self.target_coord = [0, 0] #: coordinates where current walking stops = coordinates of next_node plus offset 356 self.last_coord = [self.last_node.x, self.last_node.y] 357 self.target_coord = [self.next_node.x, self.next_node.y] 358 359 # steering the person 360 self.need_next_target = False #: if True, self.next_target() will be called and this will be set to False 361 self.passivate = False #: if true, Person is passivated on next round of go() 362 self.passivate_with_stop_actions = False #: if True, the Person's actions are stopped when Person is passivated 363 self.stop_all_actions = False #: if true: all Person's actions are stopped on next round of go() 364 self.remove_from_sim = False #: if True: Person's event loop will be broken on next round of go() to remove Person from simulation 365 366 # properties of the person 367 # we use variables with prefix p_ for properties 368 # thought about a dict, but but want have most performance 369 self.p_id = id #: property id: unique id of the Person 370 self.p_color = 0 #: property color id: marker color of Person - DEPRECATED, use color_rgba instead 371 self.p_color_rgba = (0.1, 0.1, 1.0, 0.9) #: property color: marker color of Person as RGBA 4-tuple 372 373 # actions 374 for name, obj in self.__class__.__dict__.items(): 375 if hasattr(obj, 'action') and issubclass(obj.action, PersonActionBase): 376 self._actions[obj] = obj.action(self, sim)
377
378 - def __getstate__(self):
379 """Returns Person information for pickling using the pickle module.""" 380 re = self.__dict__ 381 del re['sim'] 382 #del re['current_coords'] 383 384 return re
385
386 - def get_random_way_node(self, include_exits=True):
387 """Returns a random way_node from sim.way_nodes. 388 389 Formerly known as get_random_node(). Todo: push to geo model at mosp.geo! 390 @param include_exits: if True, also nodes marked as border nodes are regarded""" 391 if not include_exits: 392 possible_targets = [n for n in self.sim.geo.way_nodes if 'border' not in n.tags] 393 else: 394 possible_targets = self.sim.geo.way_nodes 395 return self._random.choice(possible_targets)
396
397 - def current_coords_impl(self):
398 """Calculates the current position from remaining time 399 using last_coord and target_coord to implement road width""" 400 if self._duration == 0: 401 return self.target_coord 402 last = self.last_coord 403 target = self.target_coord 404 completed = (float(self.sim.now() - self._start_time) / self._duration) 405 return last[0] + (target[0] - last[0])* completed, last[1] + (target[1] - last[1])* completed
406 407 # def current_coords_no_road_width(self): 408 # """Calculates the current position from remaining time 409 # using last_node's and next_node's x/y values""" 410 # target = self.next_node 411 # if self._duration == 0: 412 # return (int(target.x), int(target.y)) 413 # last = self.last_node 414 # completed = (float(self.sim.now() - self._start_time) / self._duration) 415 # return int(last.x + (target.x - last.x) * completed), int(last.y + (target.y - last.y) * completed) 416 417 @property
418 - def x(self):
419 """Should not be used. In most cases x and y are needed the same time. 420 So properties x and y duplicate the number of calls of current_coords().""" 421 return self.current_coords()[0]
422 423 @property
424 - def y(self):
425 """Should not be used. In most cases x and y are needed the same time. 426 So properties x and y duplicate the number of calls of current_coords().""" 427 return self.current_coords()[1]
428
429 - def collide_circle(self, x, y, radius):
430 """Checks if this person collides with the given circle. 431 Overwrites point.collide_circle for performance optimization: 432 call current_coords only once per x-y-pair""" 433 current_coords = self.current_coords() 434 if current_coords is None: 435 # Might happen when an external person did not receive coordinates yet 436 return False 437 selfx, selfy = current_coords 438 return math.sqrt((selfx - x)**2 + (selfy - y)**2) <= radius
439
440 - def collide_rectangle(self, x_min, y_min, x_max, y_max):
441 """Checks if this person collides with the given rectangle. 442 Overwrites point.collide_rectangle for performance optimization: 443 call current_coords only once per x-y-pair""" 444 selfx, selfy = self.current_coords() 445 return (x_min <= selfx <= x_max and 446 y_min <= selfy <= y_max)
447
448 - def get_speed(self):
449 """property speed: movement speed of the Person.""" 450 return self._p_speed
451
452 - def set_speed(self, speed):
453 """Sets movement speed of the Person.""" 454 self._p_speed = speed 455 self.interrupt(self) # to change walking speed in next round of go()
456 457 p_speed = property(get_speed, set_speed) 458
459 - def _get_classname(self):
460 return self.__class__.__name__
461 462 p_agenttype = property(_get_classname) 463
464 - def get_properties(self):
465 """Return all the person's properties (variables and properties p_*) as a dictionary.""" 466 properties = {} 467 for v in self.__dict__.iteritems(): 468 if v[0].startswith('p_'): 469 properties[v[0]] = v[1] 470 ## does not include parent properties! 471 ##for p in [q for q in self.__class__.__dict__.items() if type(q[1]) == property]: 472 ## if p[0].startswith('p_'): 473 ## properties[p[0]] = p[1].__get__(self) 474 # see http://stackoverflow.com/questions/9377819/how-to-access-properties-of-python-super-classes-e-g-via-class-dict 475 # v1: 476 #for cls in self.__class__.__mro__: 477 # #if issubclass(cls, Person): 478 # for p in [q for q in cls.__dict__.items() if type(q[1]) == property]: 479 # if p[0].startswith('p_'): 480 # properties[p[0]] = p[1].__get__(self) 481 # v2: 482 for attr_name in dir(self.__class__): 483 attr = getattr(self.__class__, attr_name) 484 if isinstance(attr, property) and attr_name.startswith('p_'): 485 properties[attr_name] = attr.__get__(self) 486 return properties
487
488 - def next_target(self):
489 """Finds a new target to move to. Sets next_node, maybe dest_node. Must be overwritten with an implementation.""" 490 return
491
492 - def next_target_coord(self):
493 """Calculates new target coordinates based on next_node coordinates 494 plus orthogonal to road offset defined by self.road_orthogonal_offset. 495 Examples of different values for self.road_orthogonal_offset: 496 - [0,0]: No road width 497 - [0,5]: random offset 0-5m to the right of the road in movement direction. 498 Walking on a two way single lane road, walking in the right side. 499 - [3,5]: random offset 3-5m to the right of the road in movement direction. 500 Walking on the right sidewalk, road width 3m, sidewalk width 2m. 501 - [-2,2]: random offset of 0-2m to the left and the right of the way. 502 Walking on both 2m width lanes, the own and the opposite direction's lane in both directions 503 Same as walking on a 6m width street in both directions, no distinct lanes.""" 504 target = self.next_node 505 last = self.last_node 506 if target == last: 507 return [target.x, target.y] 508 r = self._random.uniform(*self.road_orthogonal_offset) 509 #direction = last.ways[target].directions[target] 510 offset_angle = (last.ways[target].directions[target]-90)/180*math.pi 511 return [target.x-r*math.cos(offset_angle), target.y-r*math.sin(offset_angle)]
512
513 - def go(self):
514 """The main-loop of every person. 515 516 The way a person behaves can be modified by implementing own versions of 517 Person.handle_interrupts 518 Person.think, 519 Person.receive and 520 Person.next_target. 521 @author: P. Tute 522 523 """ 524 sleep = 1 525 if self.last_node == self.next_node: 526 self._duration = 0 527 else: 528 self._duration = self.calculate_duration() 529 # the actual go-loop 530 while True: 531 yield hold, self, max(sleep, 1) 532 # handle interrupts, receive messages and think 533 for m in [m for m in self._messages if self.sim.now() >= m[0]]: 534 if self.receive(m[1], m[2]): 535 self._messages.remove(m) 536 if self.interrupted(): 537 for callb, args, kwargs in self._messages: 538 callb(*args, **kwargs) 539 self._messages.remove((callb, args, kwargs)) 540 self.handle_interrupts() 541 self.last_coord = self.current_coords() 542 # this also handles changes in speed due to recalculating self._duration 543 self._duration = self.calculate_duration() 544 sleep = self.think() 545 # stop actions if necessary 546 if self.stop_all_actions: 547 self.stop_all_actions = False 548 self.stop_actions() 549 # remove from sim if necessary 550 if self.remove_from_sim: 551 self.remove_from_sim = False 552 curr = self.current_coords() 553 self.last_stop_coords = [ curr[0] + self._location_offset[0], curr[1] + self._location_offset[1] ] 554 self.current_coords = lambda: self.last_stop_coords 555 self.current_way.persons.remove(self) 556 return 557 # stop all actions if necessary 558 if self.stop_all_actions: 559 self.stop_all_actions = False 560 self.stop_actions() 561 # passivate if necessary 562 if self.passivate: 563 self._duration = 0 564 curr = self.current_coords() 565 self.last_stop_coords = [ curr[0] + self._location_offset[0], curr[1] + self._location_offset[1] ] 566 self.current_coords = lambda: self.last_stop_coords 567 if self.passivate_with_stop_actions: 568 self.stop_actions() 569 yield passivate, self 570 # find new self.next_node if necessary 571 if self.need_next_target: 572 self.next_target() 573 self.need_next_target = False 574 self.last_coord = self.target_coord 575 self.target_coord = self.next_target_coord() 576 if self.next_node is not self.last_node: 577 self.current_way.persons.remove(self) 578 self.current_way = self.last_node.ways[self.next_node] 579 self.current_way.persons.append(self) 580 self.road_orthogonal_offset = self.current_way.width # set width of next road segment - TODO here... 581 # double code here? target_coord must be calculated (again) after next way of user has been set 582 # To Be Refactured 583 self.target_coord = self.next_target_coord() 584 self._duration = self.calculate_duration() 585 self._start_time = self.sim.now() 586 # determine length of sleep 587 self._duration = max(self.calculate_duration(), 588 1) 589 if sleep < 1: 590 sleep = self._duration 591 else: 592 sleep = min(sleep, self._duration)
593 #move 594
595 - def calculate_duration(self):
596 """Calculate the time needed for walking to the next node.""" 597 return int(math.ceil(distN2N(self.last_coord, self.target_coord) / self.p_speed))
598
599 - def reactivate(self, at = 'undefined', delay = 'undefined', prior = False):
600 """Reactivates passivated person and optionally restarts stopped actions.""" 601 self.passivate = False 602 self._location_offset = [0, 0] 603 self.current_coords = self.current_coords_impl 604 self.sim.reactivate(self, at, delay, prior) 605 if self.passivate_with_stop_actions: 606 self.restart_actions()
607
608 - def pause_movement(self, duration, location_offset_xy=0, deactivateActions=False):
609 """Stops movement of person. Currently only works with passivating at next_node. 610 Currently cannot be used to stop on a way like after infect! 611 @param duration: pause duration 612 @param location_offset_xy: optional random offset to be added to current location when stopping. 613 @param deactivateActions: deactive actions while pausing?""" 614 self.passivate = True 615 self._location_offset = [ self._random.randint(0, int(location_offset_xy)), self._random.randint(0, int(location_offset_xy)) ] 616 self.passivate_with_stop_actions = deactivateActions 617 # substract 1 from duration so that self acts again at tick sim.now() + duration. Would be sim.now() + duration otherwise! 618 self.sim.person_alarm_clock.interact(self, duration=duration-1)
619
620 - def act_at_node(self, node):
621 """Actions of the person when arriving at a node. To be overwritten with an implementation. 622 623 This method is executes when the Person arrives at a node. 624 @param node: is the next_node the Person arrives at""" 625 pass
626
627 - def think(self):
628 """Think about what to do next. 629 630 This method can include all logic of the person (what to do, where to go etc.). 631 Decisions could be made by using flags for example. 632 This is where a new self.dest_node and self.start_node should be set if necessary. 633 self.next_node should not be set here. This should be done in self.next_target. 634 @return: time until next wakeup (int, ticks), returning a negative number or 0 will cause self.go() to find a time 635 @note: This method provides only the most basic functionality. Overwrite (and if necessary call) it to implement own behaviour. 636 """ 637 if self._start_time + self._duration <= self.sim.now(): 638 # target node has been reached 639 self.next_node.on_visit(self) 640 self.act_at_node(self.next_node) 641 self.need_next_target = True 642 return -1
643
644 - def handle_interrupts(self):
645 """Find out, what caused an interrupt and act accordingly. 646 647 This method is called whenever a person is interrupted. 648 This is the place to implement own reactions to interrupts. Calling methods that were send via a send() call is done BEFORE this method is called. Handling pause, stop, removal and change of movement speed is done automatically AFTER this method is called. Removing the corresponding flag (setting it to False) in this method allows for handling these things on your own. 649 @note: This method does nothing per default. Implement it to react to interrupts. If you do not want or need to react to interrupts, ignore this method. 650 """ 651 pass
652
653 - def send(self, receiver, message, earliest_arrival=None, interrupt=False):
654 """Send a message to another Person. 655 656 Person.receive() must be implemented for anything to actually happen. Without implementation of that method, messages will simply be removed at earliest_arrival. 657 Messages are received, whenever the receiver wakes up, before interrupts are handled or think() is called. 658 659 @param receiver: a Person to receive the message 660 @type receiver: mosp.Person or group.PersonGroup 661 @param message: any type of message to be delivered. Implement Person.receive() accordingly. 662 @param earliest_arrival: the earliest tick to receive the message. Default is the next tick (self.sim.now() + 1). 663 @param interrupt: interrupt the receiver, after this message was queued. 664 665 """ 666 if earliest_arrival == None: 667 earliest_arrival = self.sim.now() + 1 668 if type(receiver) == group.PersonGroup: 669 for rec in receiver: 670 rec._messages.append([earliest_arrival, message, self]) 671 if interrupt: 672 self.interrupt(rec) 673 else: 674 receiver._messages.append([earliest_arrival, message, self]) 675 if interrupt: 676 self.interrupt(rec)
677
678 - def receive(self, message, sender):
679 """Receive a message and handle it. 680 681 Removal from the message queue and earliest arrival time are handled automatically. 682 683 @param message: a message from the persons message queue. 684 @param sender: the sender of the message. 685 @return: True if the message should be removed from the queue, False else. It will then be delivered again in the next cycle. 686 687 """ 688 return True
689
690 - def start_action(self, action):
691 """Start an action.""" 692 self._actions[action].start()
693
694 - def stop_action(self, action):
695 """Stop an action.""" 696 self._actions[action].stop()
697
698 - def stop_actions(self, removal=False):
699 """Stop all actions of the Person. 700 701 For all actions if the Person: call stop and store as stopped action 702 @param removal: used to signal removal of the person. Actions are then stored in seperate list. 703 """ 704 for a in self._actions.values(): 705 if a.active(): 706 if removal: 707 self._stopped_actions_for_removal.append(a) 708 else: 709 self._stopped_actions.append(a) 710 a.stop()
711
712 - def call_stop_actions(self):
713 """Sets stop_all_actions und interrupts.""" 714 self.stop_all_actions = True 715 self.interrupt(self)
716
717 - def restart_actions(self):
718 """Restarts all actions that where stopped via stop_actions.""" 719 for a in self._stopped_actions: 720 a.start() 721 self._stopped_actions = []
722
723 - def get_near(self, dist, self_included=True):
724 """Returns Persons near this Person. 725 @param dist: lookup radius 726 @param self_included: if True, this Person itself is included in resulting PersonGroup 727 @return: PersonGroup containing all Persons in distance 728 @rtype: mosp.group.PersonGroup""" 729 current_coords = self.current_coords() 730 re = group.PersonGroup() 731 if current_coords is None: 732 # Person does not have coordinates yet. This might happen with external devices as persons. 733 return re 734 x, y = current_coords 735 for element in self.sim.geo.collide_circle(x, y, dist): 736 if isinstance(element, Person): 737 if element.collide_circle(x, y, dist): 738 if self_included or element != self: 739 re.add(element) 740 else: 741 for person in element.persons: 742 if person.collide_circle(x, y, dist): 743 if self_included or person != self: 744 re.add(person) 745 return re
746
747 - def readd_actions(self):
748 """Do things, when person is readded via Simulation.readd(). 749 750 This does nothing by default. It could be implemented by a simulation, if needed.""" 751 pass
752