Пример #1
0
                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)
Пример #2
0
        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)
Пример #3
0
        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)
Пример #4
0
                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)
Пример #5
0
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()
Пример #6
0
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()