def capabilities_success(capabilities_dict): capabilities = Capabilities() capabilities.from_json_dict(capabilities_dict['capabilities']) # For devices that have no device storage, attempt to set a track if capabilities.storage.tracks == 0: # Now figure out if the device already has a track set, if not, set one. Enough callbacks yet?! def track_fail(*args): Logger.error("DashboardView: _race_setup(), could not get track config") def track_success(track_config): self._track_config = TrackConfig() self._track_config.fromJson(track_config['trackCfg']) Logger.debug("DashboardView: _race_setup(), got track config: {}".format(self._track_config)) # Only clear way to know if the track in the track config is a real track or not is by # checking start/finish point. If both are 0, not a track. Can't use track id because a # track id of 0 can be a user defined track if self._track_config.track.startLine.latitude == 0 and \ self._track_config.track.startLine.longitude == 0: # Prompt for track! # Scheduling once because this callback is not in the UI thread and if we update # the UI now we'll crash >:\ Clock.schedule_once(lambda dt: self._load_race_setup_view()) self._rc_api.getTrackCfg(track_success, track_fail)
def query_available_configs(capabilities_dict): capabilities_dict = capabilities_dict.get('capabilities') capabilities = Capabilities() capabilities.from_json_dict(capabilities_dict, self.connected_version) cmdSequence = [ RcpCmd('ver', self.sendGetVersion), RcpCmd('capabilities', self.getCapabilities), RcpCmd('lapCfg', self.getLapCfg), RcpCmd('trackCfg', self.getTrackCfg), RcpCmd('canCfg', self.getCanCfg), RcpCmd('obd2Cfg', self.getObd2Cfg), RcpCmd('connCfg', self.getConnectivityCfg), RcpCmd('trackDb', self.getTrackDb) ] if capabilities.has_gps: cmdSequence.append(RcpCmd('gpsCfg', self.getGpsCfg)) if capabilities.has_imu: cmdSequence.append(RcpCmd('imuCfg', self.getImuCfg)) if capabilities.has_can_channel: cmdSequence.append( RcpCmd('canChanCfg', self.get_can_channels_config)) if capabilities.has_script: cmdSequence.append(RcpCmd('scriptCfg', self.getScript)) if capabilities.has_analog: cmdSequence.append(RcpCmd('analogCfg', self.getAnalogCfg)) if capabilities.has_timer: cmdSequence.append(RcpCmd('timerCfg', self.getTimerCfg)) if capabilities.has_gpio: cmdSequence.append(RcpCmd('gpioCfg', self.getGpioCfg)) if capabilities.has_pwm: cmdSequence.append(RcpCmd('pwmCfg', self.getPwmCfg)) if capabilities.has_wifi: cmdSequence.append(RcpCmd('wifiCfg', self.get_wifi_config)) if capabilities.has_sd_logging: cmdSequence.append( RcpCmd('sdLogCtrlCfg', self.get_sdlog_control_config)) if capabilities.has_camera_control: cmdSequence.append( RcpCmd('camCtrlCfg', self.get_camera_control_config)) self._queue_multiple( cmdSequence, 'rcpCfg', lambda rcpJson: self.getRcpCfgCallback( cfg, rcpJson, winCallback), failCallback)
def query_available_configs(capabilities_dict): capabilities_dict = capabilities_dict.get('capabilities') capabilities = Capabilities() capabilities.from_json_dict(capabilities_dict, self.connected_version) cmdSequence = [RcpCmd('ver', self.sendGetVersion), RcpCmd('capabilities', self.getCapabilities), RcpCmd('lapCfg', self.getLapCfg), RcpCmd('trackCfg', self.getTrackCfg), RcpCmd('canCfg', self.getCanCfg), RcpCmd('obd2Cfg', self.getObd2Cfg), RcpCmd('connCfg', self.getConnectivityCfg), RcpCmd('trackDb', self.getTrackDb) ] if capabilities.has_gps: cmdSequence.append(RcpCmd('gpsCfg', self.getGpsCfg)) if capabilities.has_imu: cmdSequence.append(RcpCmd('imuCfg', self.getImuCfg)) if capabilities.has_can_channel: cmdSequence.append(RcpCmd('canChanCfg', self.get_can_channels_config)) if capabilities.has_script: cmdSequence.append(RcpCmd('scriptCfg', self.getScript)) if capabilities.has_analog: cmdSequence.append(RcpCmd('analogCfg', self.getAnalogCfg)) if capabilities.has_timer: cmdSequence.append(RcpCmd('timerCfg', self.getTimerCfg)) if capabilities.has_gpio: cmdSequence.append(RcpCmd('gpioCfg', self.getGpioCfg)) if capabilities.has_pwm: cmdSequence.append(RcpCmd('pwmCfg', self.getPwmCfg)) if capabilities.has_wifi: cmdSequence.append(RcpCmd('wifiCfg', self.get_wifi_config)) if capabilities.has_sd_logging: cmdSequence.append(RcpCmd('sdLogCtrlCfg', self.get_sdlog_control_config)) if capabilities.has_camera_control: cmdSequence.append(RcpCmd('camCtrlCfg', self.get_camera_control_config)) self._queue_multiple(cmdSequence, 'rcpCfg', lambda rcpJson: self.getRcpCfgCallback(cfg, rcpJson, winCallback), failCallback)
def capabilities_success(capabilities_dict): capabilities = Capabilities() capabilities.from_json_dict( capabilities_dict['capabilities']) # For devices that have no device storage, attempt to set a track if capabilities.storage.tracks == 0: # Now figure out if the device already has a track set, if not, set one. Enough callbacks yet?! def track_fail(*args): Logger.error( "DashboardView: _race_setup(), could not get track config" ) def track_success(track_config): self._track_config = TrackConfig() self._track_config.fromJson( track_config['trackCfg']) Logger.debug( "DashboardView: _race_setup(), got track config: {}" .format(self._track_config)) # Only clear way to know if the track in the track config is a real track or not is by # checking start/finish point. If both are 0, not a track. Can't use track id because a # track id of 0 can be a user defined track if self._track_config.track.startLine.latitude == 0 and \ self._track_config.track.startLine.longitude == 0: # Prompt for track! # Scheduling once because this callback is not in the UI thread and if we update # the UI now we'll crash >:\ Clock.schedule_once( lambda dt: self._load_race_setup_view()) self._rc_api.getTrackCfg(track_success, track_fail)
class DataBusPump(object): """Responsible for dispatching raw JSON API messages into a format the DataBus can consume. Attempts to detect asynchronous messaging mode, where messages are streamed to the DataBusPump. If Async mode not detected, a polling thread is created to simulate this. """ # Telemetry rate when we're actively needing the stream (logging, dashboard, etc) TELEMETRY_RATE_ACTIVE_HZ = 50 # Telemetry rate when we're idle TELEMETRY_RATE_IDLE_HZ = 1 SAMPLE_POLL_EXCEPTION_RECOVERY = 10.0 SAMPLES_TO_WAIT_FOR_META = 5 # Main app views that the DataBusPump should set the active telemetry rate TELEMETRY_ACTIVE_VIEWS = ['dash'] def __init__(self, **kwargs): super(DataBusPump, self).__init__(**kwargs) self._rc_api = None self._data_bus = None self.sample = Sample() self._sample_event = Event() self._poll = Event() self._sample_thread = None self._meta_is_stale_counter = 0 self.rc_capabilities = None self._should_run = False self._running = False self._starting = False self._auto_streaming_supported = False self._current_view = None self._is_recording = False @property def is_telemetry_active(self): return self._current_view in DataBusPump.TELEMETRY_ACTIVE_VIEWS or self._is_recording @property def current_sample_rate(self): return DataBusPump.TELEMETRY_RATE_ACTIVE_HZ if self.is_telemetry_active else DataBusPump.TELEMETRY_RATE_IDLE_HZ def on_view_change(self, view_name): """ View change listener, if the view being displayed is a view we want to record for, start recording. If not and we are currently recording, stop :param view_name: :return: """ self._current_view = view_name self._start_telemetry() def _on_session_recording(self, instance, is_recording): self._is_recording = is_recording self._start_telemetry() def _start_telemetry(self): capabilities = self.rc_capabilities if capabilities is not None and capabilities.has_streaming: self._rc_api.start_telemetry(self.current_sample_rate) def start(self, data_bus, rc_api, session_recorder, auto_streaming_supported): Logger.debug("DataBusPump: start()") if self._running or self._starting: Logger.debug("DataBusPump: start(), already running, aborting") return self._should_run = True self._auto_streaming_supported = auto_streaming_supported if self._poll.is_set(): # since we're already running, simply # request updated metadata self.meta_is_stale() return self._rc_api = rc_api self._data_bus = data_bus self._session_recorder = session_recorder session_recorder.bind(on_recording=self._on_session_recording) rc_api.addListener('s', self.on_sample) rc_api.addListener('meta', self.on_meta) rc_api.add_connect_listener(self.on_connect) rc_api.add_disconnect_listener(self.on_disconnect) self._start() def _start(self): if self._running or self._starting: Logger.debug( "DataBusPump: _start(), already running or starting, aborting") return self._poll.set() # Only BT supports auto-streaming data, the rest we have to stream or poll if not self._auto_streaming_supported: self._start_sample_streaming() def on_connect(self): if self._should_run: self._start() def on_disconnect(self): self._stop(True) def _start_sample_streaming(self): # First need to figure out if the connected RC supports the streaming api Logger.info("DataBusPump: Checking for streaming API support") def handle_capabilities(capabilities_dict): self.rc_capabilities = Capabilities() self.rc_capabilities.from_json_dict( capabilities_dict['capabilities']) if self.rc_capabilities.has_streaming: # Send streaming command Logger.info("DataBusPump: device supports streaming") self._start_telemetry() else: Logger.info( "DataBusPump: connected device does not support streaming api" ) self._start_polling_telemetry() self._running = True def handle_capabilities_fail(*args): Logger.error( "DataBusPump: Failed to get capabilities, can't determine if device supports streaming API. Assuming polling" ) self._start_polling_telemetry() self._poll.set() self._rc_api.get_capabilities(handle_capabilities, handle_capabilities_fail) self._starting = True def _start_polling_telemetry(self): if self._sample_thread: return t = Thread(target=self.sample_worker) t.start() self._sample_thread = t def stop(self): """ Public method to stop databuspump """ self._should_run = False self._stop() def _stop(self, disconnected=False): """ Private method to stop databuspump, leaves self._should_run alone so if we're stopping because of a disconnect, we will start up again on reconnect """ self._poll.clear() self._running = False self._starting = False if self.rc_capabilities and self.rc_capabilities.has_streaming and not disconnected: self._rc_api.stop_telemetry() return if self._sample_thread is not None: try: self._sample_thread.join() except Exception as e: Logger.warning( 'DataBusPump: Failed to join sample_worker: {}'.format(e)) def on_meta(self, meta_json): metas = self.sample.metas metas.fromJson(meta_json.get('meta')) self._data_bus.update_channel_meta(metas) self._meta_is_stale_counter = 0 def on_sample(self, sample_json): sample = self.sample dataBus = self._data_bus try: sample.fromJson(sample_json) if sample.updated_meta: dataBus.update_channel_meta(sample.metas) dataBus.update_samples(sample) self._sample_event.set() except SampleMetaException: # this is to prevent repeated sample meta requests self._request_meta_handler() def _request_meta_handler(self): if self._meta_is_stale_counter <= 0: Logger.info('DataBusPump: Sample Meta is stale, requesting meta') self._meta_is_stale_counter = DataBusPump.SAMPLES_TO_WAIT_FOR_META self.request_meta() else: self._meta_is_stale_counter -= 1 def stopDataPump(self): self._poll.clear() self._sample_thread.join() def meta_is_stale(self): self.request_meta() def request_meta(self): self._rc_api.get_meta() def sample_worker(self): rc_api = self._rc_api Logger.info('DataBusPump: sample_worker polling starting') while self._poll.is_set(): try: rc_api.sample() sleep(1.0 / self.current_sample_rate) except Exception as e: Logger.error('DataBusPump: Exception in sample_worker: ' + str(e)) sleep(DataBusPump.SAMPLE_POLL_EXCEPTION_RECOVERY) Logger.info('DataBusPump: sample_worker exiting') safe_thread_exit()
class DataBusPump(object): """Responsible for dispatching raw JSON API messages into a format the DataBus can consume. Attempts to detect asynchronous messaging mode, where messages are streamed to the DataBusPump. If Async mode not detected, a polling thread is created to simulate this. """ # Telemetry rate when we're actively needing the stream (logging, dashboard, etc) TELEMETRY_RATE_ACTIVE_HZ = 50 # Telemetry rate when we're idle TELEMETRY_RATE_IDLE_HZ = 1 SAMPLE_POLL_EXCEPTION_RECOVERY = 10.0 SAMPLES_TO_WAIT_FOR_META = 5 # Main app views that the DataBusPump should set the active telemetry rate TELEMETRY_ACTIVE_VIEWS = ['dash'] def __init__(self, **kwargs): super(DataBusPump, self).__init__(**kwargs) self._rc_api = None self._data_bus = None self.sample = Sample() self._sample_event = Event() self._poll = Event() self._sample_thread = None self._meta_is_stale_counter = 0 self.rc_capabilities = None self._should_run = False self._running = False self._starting = False self._auto_streaming_supported = False self._current_view = None self._is_recording = False @property def is_telemetry_active(self): return self._current_view in DataBusPump.TELEMETRY_ACTIVE_VIEWS or self._is_recording @property def current_sample_rate(self): return DataBusPump.TELEMETRY_RATE_ACTIVE_HZ if self.is_telemetry_active else DataBusPump.TELEMETRY_RATE_IDLE_HZ def on_view_change(self, view_name): """ View change listener, if the view being displayed is a view we want to record for, start recording. If not and we are currently recording, stop :param view_name: :return: """ self._current_view = view_name self._start_telemetry() def _on_session_recording(self, instance, is_recording): self._is_recording = is_recording self._start_telemetry() def _start_telemetry(self): capabilities = self.rc_capabilities if capabilities is not None and capabilities.has_streaming: self._rc_api.start_telemetry(self.current_sample_rate) def start(self, data_bus, rc_api, session_recorder, auto_streaming_supported): Logger.debug("DataBusPump: start()") if self._running or self._starting: Logger.debug("DataBusPump: start(), already running, aborting") return self._should_run = True self._auto_streaming_supported = auto_streaming_supported if self._poll.is_set(): # since we're already running, simply # request updated metadata self.meta_is_stale() return self._rc_api = rc_api self._data_bus = data_bus self._session_recorder = session_recorder session_recorder.bind(on_recording=self._on_session_recording) rc_api.addListener('s', self.on_sample) rc_api.addListener('meta', self.on_meta) rc_api.add_connect_listener(self.on_connect) rc_api.add_disconnect_listener(self.on_disconnect) self._start() def _start(self): if self._running or self._starting: Logger.debug("DataBusPump: _start(), already running or starting, aborting") return self._poll.set() # Only BT supports auto-streaming data, the rest we have to stream or poll if not self._auto_streaming_supported: self._start_sample_streaming() def on_connect(self): if self._should_run: self._start() def on_disconnect(self): self._stop(True) def _start_sample_streaming(self): # First need to figure out if the connected RC supports the streaming api Logger.info("DataBusPump: Checking for streaming API support") def handle_capabilities(capabilities_dict): self.rc_capabilities = Capabilities() self.rc_capabilities.from_json_dict(capabilities_dict['capabilities']) if self.rc_capabilities.has_streaming: # Send streaming command Logger.info("DataBusPump: device supports streaming") self._start_telemetry() else: Logger.info("DataBusPump: connected device does not support streaming api") self._start_polling_telemetry() self._running = True def handle_capabilities_fail(*args): Logger.error("DataBusPump: Failed to get capabilities, can't determine if device supports streaming API. Assuming polling") self._start_polling_telemetry() self._poll.set() raise Exception("DataBusPump: Failed to get capabilities for streaming API support") self._rc_api.get_capabilities(handle_capabilities, handle_capabilities_fail) self._starting = True def _start_polling_telemetry(self): if self._sample_thread: return t = Thread(target=self.sample_worker) t.start() self._sample_thread = t def stop(self): """ Public method to stop databuspump """ self._should_run = False self._stop() def _stop(self, disconnected=False): """ Private method to stop databuspump, leaves self._should_run alone so if we're stopping because of a disconnect, we will start up again on reconnect """ self._poll.clear() self._running = False self._starting = False if self.rc_capabilities and self.rc_capabilities.has_streaming and not disconnected: self._rc_api.stop_telemetry() return if self._sample_thread is not None: try: self._sample_thread.join() except Exception as e: Logger.warning('DataBusPump: Failed to join sample_worker: {}'.format(e)) def on_meta(self, meta_json): metas = self.sample.metas metas.fromJson(meta_json.get('meta')) self._data_bus.update_channel_meta(metas) self._meta_is_stale_counter = 0 def on_sample(self, sample_json): sample = self.sample dataBus = self._data_bus try: sample.fromJson(sample_json) if sample.updated_meta: dataBus.update_channel_meta(sample.metas) dataBus.update_samples(sample) self._sample_event.set() except SampleMetaException: # this is to prevent repeated sample meta requests self._request_meta_handler() def _request_meta_handler(self): if self._meta_is_stale_counter <= 0: Logger.info('DataBusPump: Sample Meta is stale, requesting meta') self._meta_is_stale_counter = DataBusPump.SAMPLES_TO_WAIT_FOR_META self.request_meta() else: self._meta_is_stale_counter -= 1 def stopDataPump(self): self._poll.clear() self._sample_thread.join() def meta_is_stale(self): self.request_meta() def request_meta(self): self._rc_api.get_meta() def sample_worker(self): rc_api = self._rc_api Logger.info('DataBusPump: sample_worker polling starting') while self._poll.is_set(): try: rc_api.sample() sleep(1.0 / self.current_sample_rate) except Exception as e: Logger.error('DataBusPump: Exception in sample_worker: ' + str(e)) sleep(DataBusPump.SAMPLE_POLL_EXCEPTION_RECOVERY) Logger.info('DataBusPump: sample_worker exiting') safe_thread_exit()