def run(self): track_log.info('Track Sim Starting...') track = Track() # The track contains all it's devices and locos. # Start each track component-device's simulation thread # These devices exists "on" the track and simulate their own # operation. # TODO: Bases, Waysides, etc for l in track.locos.values(): l.sim.start() # Update sim time multiplier if needed while True: try: time_iplier = self.timeq.get(timeout=.1) for l in track.locos.values(): l.sim.time_iplier = time_iplier track_log.info('Time Multiplier Set: ' + str(time_iplier)) except Queue.Empty: pass # debug # for l in track.locos.values(): # status_str = 'Loco ' + l.ID + ': ' # status_str += str(l.speed) + ' @ ' + str(l.coords.marker) # status_str += ' (' + str(l.coords.long) + ',' + str(l.coords.lat) + ')' # status_str += '. Bases in range: ' # status_str += ', '.join([b.ID for b in l.bases_inrange]) # status_str += ' Conns: ' # status_str += ', '.join([c.conn_to.ID for c in l.conns.values() if c.conn_to]) # track_log.info(status_str) sleep(REFRESH_TIME)
def loco_messaging(loco): """ Real-time simulation of a locomotives's messaging system. Maintains connections to bases in range of loco's position. # TODO: send/fetch msgs over them. This function is intended to be run as a Thread. """ while loco.sim.running: sleep(MSG_INTERVAL) # Sleep for specified interval # Drop all out of range base connections and keep alive existing # in-range connections lconns = loco.conns.values() for conn in [c for c in lconns if c.connected() is True]: if conn.conn_to not in loco.bases_inrange: conn.disconnect() else: conn.keep_alive() open_conns = [c for c in lconns if c.connected() is False] used_bases = [c.conn_to for c in lconns if c.connected() is True] for i, conn in enumerate(open_conns): try: if loco.bases_inrange[i] not in used_bases: conn.connect(loco.bases_inrange[i]) except IndexError: break # No (or no more) bases in range to consider # Ensure at least one active connection conns = [c for c in lconns if c.connected() is True] if not conns: err_str = ' skipping msg send/recv - No active comms.' track_log.warn(loco.name + err_str) continue # Try again next iteration # Send status msg over active connections, breaking on first success. status_msg = get_6000_msg(loco) for conn in conns: try: conn.send(status_msg) info_str = ' - Sent status msg over ' + conn.conn_to.name track_log.info(loco.name + info_str) except Exception as e: track_log.warn(loco.name + ' send failed: ' + str(e)) # Fetch incoming cad msgs over active connections, breaking on success. for conn in conns: cad_msg = None try: cad_msg = conn.fetch(loco.emp_addr) except Queue.Empty: break # No msgs (or no more msgs) to receive. except Exception as e: track_log.warn(loco.name + ' fetch failed: ' + str(e)) continue # Try the next connecion # Process cad msg, if msg and if actually for this loco if cad_msg and cad_msg.payload.get('ID') == loco.ID: try: # TODO: Update track restrictions/loco locations track_log.info(loco.name + ' - CAD msg processed.') except: track_log.error(loco.name + ' - Received invalid CAD msg.') break # Either way, the msg was fetched # TODO: ACK w/broker else: err_str = ' - active connections exist, but msg fetch/recv failed.' track_log.error(loco.name + err_str)
def loco_movement(loco): """ Real-time simulation of a locomotive's on-track movement. Also determines base stations in range of locos current position. This function is intended to be run as a Thread. """ def _brake(): """ Apply the adaptive braking algorithm. """ raise NotImplementedError def _set_heading(prev_mp, curr_mp): """ Sets loco heading based on current and prev lat/long """ lat1 = radians(prev_mp.lat) lat2 = radians(curr_mp.lat) long_diff = radians(prev_mp.long - curr_mp.long) a = cos(lat1) * sin(lat2) b = (sin(lat1) * cos(lat2) * cos(long_diff)) x = sin(long_diff) * cos(lat2) y = a - b deg = degrees(atan2(x, y)) compass_bearing = (deg + 360) % 360 loco.heading = compass_bearing # Start makeup_dist = 0 if not loco.direction or not loco.coords or loco.speed is None: raise ValueError('Cannot simulate an unintialized Locomotive.') while loco.sim.running: sleep(MSG_INTERVAL) # Sleep for specified interval # Move, if at speed if loco.speed > 0: # Determine dist traveled since last iteration, including # makeup distance, if any. hours = REFRESH_TIME / 3600.0 # Seconds to hours, for mph hours = loco.sim.time_iplier * hours # Apply sim time rate dist = loco.speed * hours * 1.0 # distance = speed * time dist += makeup_dist # Set sign of dist based on dir of travel if loco.direction == 'decreasing': dist *= -1 # Get next location and any makeup distance new_mp, dist = loco.track._get_next_mp(loco.coords, dist) # If no new_mp was returned, assume end of track if not new_mp: err_str = ' - At end of track. Reversing.' track_log.info(loco.name + err_str) makeup_dist = 0 if loco.direction == 'decreasing': loco.direction = 'increasing' else: loco.direction = 'decreasing' # Else update the loco accordingly else: _set_heading(loco.coords, new_mp) loco.coords = new_mp makeup_dist = dist # Determine base stations in range of current position loco.bases_inrange = [ b for b in loco.track.bases.values() if b.covers_location(loco.coords) ]