コード例 #1
0
    def __init__(self, eventmanager, vrx_config, node_frequencies):
        self.Events = eventmanager
        self.logger = logging.getLogger(self.__class__.__name__)
        # Stored receiver data
        self.rx_data = {}

        #ClearView API object
        self._cv = clearview.ClearView(return_formatted_commands=True)

        self.config = self.validate_config(vrx_config)

        # TODO the subscribe topics subscribe it to a node number by default
        # Don't hack by making node number a wildcard

        # TODO: pass in "CV1 to the MQTT_CLIENT because
        # there can be multiple clients, one for each protocol.
        # The MQTT_CLIENT should not know about what it is supposed to be doing
        # The VRxController can then run multiple clients, but duplicate messaging will have to be avoided
        # This could be done in the publisher by only passing messages to the clients that need it

        self._mqttc = MQTT_Client(client_id="VRxController",
                                  broker_ip=self.config["HOST"],
                                  subscribe_topics=None)

        self._add_subscribe_callbacks()
        self._mqttc.loop_start()
        self.num_nodes = len(node_frequencies)

        self.node_number_range = (0, 7)
        self._nodes = [
            VRxNode(self._mqttc,
                    self._cv,
                    n,
                    node_frequencies[n],
                    node_number_range=self.node_number_range)
            for n in range(self.num_nodes)
        ]
        self._node_broadcast = VRxBroadcastNode(self._mqttc, self._cv)

        # Events
        self.Events.on(Evt.STARTUP, 'VRx', self.do_startup)
        self.Events.on(Evt.HEAT_SET, 'VRx', self.do_heat_set)
        self.Events.on(Evt.RACE_STAGE, 'VRx', self.do_race_stage, {}, 75)
        self.Events.on(Evt.RACE_START, 'VRx', self.do_race_start, {}, 75)
        self.Events.on(Evt.RACE_FINISH, 'VRx', self.do_race_finish)
        self.Events.on(Evt.RACE_STOP, 'VRx', self.do_race_stop)
        self.Events.on(Evt.RACE_LAP_RECORDED, 'VRx', self.do_lap_recorded, {},
                       200, True)
        self.Events.on(Evt.LAPS_CLEAR, 'VRx', self.do_laps_clear)
        self.Events.on(Evt.LAP_DELETE, 'VRx', self.do_lap_recorded)
        self.Events.on(Evt.FREQUENCY_SET, 'VRx', self.do_frequency_set, {},
                       200, True)
        self.Events.on(Evt.MESSAGE_INTERRUPT, 'VRx', self.do_send_message)
        self.Events.on(Evt.OPTION_SET, 'VRx', self.validate_option)

        # Options
        if Options.get('osd_lapHeader') is False:
            Options.set('osd_lapHeader', 'L')
        if Options.get('osd_positionHeader') is False:
            Options.set('osd_positionHeader', '')
コード例 #2
0
    def __init__(self, eventmanager, VRxServer, node_frequencies):
        self.Events = eventmanager

        self._cv = clearview.ClearView(return_formatted_commands=True)

        #TODO the subscribe topics subscribe it to a node number by default
        #Don't hack by making node number *

        #TODO: pass in "CV1 to the MQTT_CLIENT maybe. "
        # There will be multiple clients, one for each protocol"
        # Probably best to manually add the subscriptions and callbacks
        self._mqttc = MQTT_Client(client_id="VRxController",
                                  broker_ip=VRxServer,
                                  subscribe_topics=None)

        self._add_subscribe_callbacks()
        self._mqttc.loop_start()
        num_nodes = len(node_frequencies)

        self._nodes = [
            VRxNode(self._mqttc, n, node_frequencies[n], self._cv)
            for n in range(8)
        ]
        self._node_broadcast = VRxBroadcastNode(self._mqttc, -1, None,
                                                self._cv)

        #TODO is this the right way. Store data twice? Maybe it should be a member var.
        # If the decorators worked...
        self._frequency = [node.node_frequency for node in self._nodes]
        self._lock_status = [node.node_lock_status for node in self._nodes]
        self._camera_type = [node.node_camera_type for node in self._nodes]

        # Events
        self.Events.on(Evt.STARTUP, 'VRx', self.do_startup, {}, 200)
        self.Events.on(Evt.RACESTART, 'VRx', self.do_racestart, {}, 200)
        self.rxdata = {}
コード例 #3
0
class VRxController:
    def __init__(self, eventmanager, vrx_config, race_obj, seat_frequencies):
        self.Events = eventmanager
        self.RACE = race_obj
        self.logger = logging.getLogger(self.__class__.__name__)
        self.logger.setLevel(logging.DEBUG)
        self.logger.debug("TEST DEBUG")
        self.logger.info("TEST INFO")
        print(self.logger.getEffectiveLevel())
        # Stored receiver data
        self.rx_data = {}

        #ClearView API object
        # self._cv = clearview.ClearView(return_formatted_commands=True)

        self.config = self.validate_config(vrx_config)

        # TODO the subscribe topics subscribe it to a seat number by default
        # Don't hack by making seat number a wildcard

        # TODO: pass in "CV1 to the MQTT_CLIENT because
        # there can be multiple clients, one for each protocol.
        # The MQTT_CLIENT should not know about what it is supposed to be doing
        # The VRxController can then run multiple clients, but duplicate messaging will have to be avoided
        # This could be done in the publisher by only passing messages to the clients that need it

        self._mqttc = MQTT_Client(client_id="VRxController",
                                  broker_ip=self.config["HOST"],
                                  subscribe_topics=None)

        self._add_subscribe_callbacks()
        self._mqttc.loop_start()
        self.num_seats = len(seat_frequencies)

        self.seat_number_range = (0, 7)
        self._seats = [
            VRxSeat(self._mqttc,
                    n,
                    seat_frequencies[n],
                    seat_number_range=self.seat_number_range)
            for n in range(self.num_seats)
        ]
        self._seat_broadcast = VRxBroadcastSeat(self._mqttc)

        # Events
        self.Events.on(Evt.STARTUP, 'VRx', self.do_startup)
        self.Events.on(Evt.HEAT_SET, 'VRx', self.do_heat_set)
        self.Events.on(Evt.RACE_STAGE, 'VRx', self.do_race_stage, {}, 75)
        self.Events.on(Evt.RACE_START, 'VRx', self.do_race_start, {}, 75)
        self.Events.on(Evt.RACE_FINISH, 'VRx', self.do_race_finish)
        self.Events.on(Evt.RACE_STOP, 'VRx', self.do_race_stop)
        self.Events.on(Evt.RACE_LAP_RECORDED, 'VRx', self.do_lap_recorded, {},
                       200, True)
        self.Events.on(Evt.LAPS_CLEAR, 'VRx', self.do_laps_clear)
        self.Events.on(Evt.LAP_DELETE, 'VRx', self.do_lap_recorded)
        self.Events.on(Evt.FREQUENCY_SET, 'VRx', self.do_frequency_set, {},
                       200, True)
        self.Events.on(Evt.MESSAGE_INTERRUPT, 'VRx', self.do_send_message)
        self.Events.on(Evt.OPTION_SET, 'VRx', self.validate_option)

        # Options
        if Options.get('osd_lapHeader') is False:
            Options.set('osd_lapHeader', 'L')
        if Options.get('osd_positionHeader') is False:
            Options.set('osd_positionHeader', '')

    def validate_config(self, supplied_config):
        """Ensure config values are within range and reasonable values"""

        default_config = {
            'HOST': 'localhost',
        }
        saved_config = default_config

        for k, v_default in default_config.items():
            if k not in supplied_config:
                self.logger.warning(
                    "VRX Config does not include config key '%s'. Using '%s'" %
                    (k, v_default))
            else:
                saved_config[k] = supplied_config[k]

        return saved_config

    def validate_option(self, args):
        """Ensure config values are within range and reasonable values"""
        if 'option' in args:
            if args['option'] in ['osd_lapHeader', 'osd_positionHeader']:
                cv_csum = clearview.comspecs.clearview_specs["message_csum"]
                config_item = args['value']

                if len(config_item) == 1:
                    if config_item == cv_csum:
                        self.logger.error(
                            "Cannot use reserved character '%s' in '%s'" %
                            (cv_csum, args['option']))
                        Options.set(args['option'], '')
                elif cv_csum in config_item:
                    self.logger.error(
                        "Cannot use reserved character '%s' in '%s'" %
                        (cv_csum, args['option']))
                    Options.set(args['option'], '')

    def do_startup(self, arg):
        self.logger.info("VRx Control Starting up")

        self._seat_broadcast.reset_lock()
        # Request status of all receivers (static and variable)
        self.request_static_status()
        self.request_variable_status()
        self._seat_broadcast.turn_off_osd()

        for i in range(self.num_seats):
            self.get_seat_lock_status(i)
            gevent.spawn(self.set_seat_frequency, i,
                         self._seats[i]._seat_frequency)

        # Update the DB with receivers that exist and their status
        # (Because the pi was already running, they should all be connected to the broker)
        # Even if the server.py is restarted, the broker continues to run:)

    def do_heat_set(self, arg):
        self.logger.info("VRx Signaling Heat Set")
        try:
            heat_id = arg["heat_id"]
        except KeyError:
            self.logger.error(
                "Unable to send callsigns. heat_id not found in event.")
            return

        for heatseat in Database.HeatNode.query.filter_by(
                heat_id=heat_id).all():
            heatseat_index = heatseat.seat_index
            if heatseat_index < self.num_seats:  # TODO this may break with non-contiguous nodes
                if heatseat.pilot_id != Database.PILOT_ID_NONE:
                    pilot = Database.Pilot.query.get(heatseat.pilot_id)
                    self.set_message_direct(heatseat_index, pilot.callsign)
                else:
                    self.set_message_direct(heatseat_index, __("-None-"))

    def do_race_stage(self, arg):
        self.logger.info("VRx Signaling Race Stage")
        self.set_message_direct(VRxALL, __("Ready"))

    def do_race_start(self, arg):
        self.logger.info("VRx Signaling Race Start")
        self.set_message_direct(VRxALL, __("Go"))

    def do_race_finish(self, arg):
        self.logger.info("VRx Signaling Race Finish")
        self.set_message_direct(VRxALL, __("Finish"))

    def do_race_stop(self, arg):
        self.logger.info("VRx Signaling Race Stop")
        self.set_message_direct(VRxALL, __("Race Stopped. Land Now."))

    def do_send_message(self, arg):
        self.set_message_direct(VRxALL, arg['message'])

    def do_laps_clear(self, arg):
        self.logger.info("VRx Signaling Laps Clear")
        self.set_message_direct(VRxALL, "---")

    def do_frequency_set(self, arg):
        self.logger.info("Setting frequency from event")
        try:
            seat_index = arg["nodeIndex"]
        except KeyError:
            self.logger.error(
                "Unable to set frequency. nodeIndex not found in event")
            return
        try:
            frequency = arg["frequency"]
        except KeyError:
            self.logger.error(
                "Unable to set frequency. frequency not found in event")
            return

        self.set_seat_frequency(seat_index, frequency)

    def do_lap_recorded(self, args):
        '''
        *** TODO: Formatting for hardware (OSD length, etc.)
        '''

        RESULTS_TIMEOUT = 5  # maximum time to wait for results to generate
        LAP_HEADER = Options.get('osd_lapHeader')
        POS_HEADER = Options.get('osd_positionHeader')

        if 'node_index' in args:
            seat_index = args['node_index']
        else:
            self.logger.warning('Failed to send results: Seat not specified')
            return False

        # wait for results to generate
        time_now = monotonic()
        timeout = time_now + RESULTS_TIMEOUT
        while self.RACE.cacheStatus != Results.CacheStatus.VALID and time_now < timeout:
            time_now = monotonic()
            gevent.sleep()

        if self.RACE.cacheStatus == Results.CacheStatus.VALID:
            '''
            Get relevant results
            '''

            results = self.RACE.results

            # select correct results
            # *** leaderboard = results[results['meta']['primary_leaderboard']]
            win_condition = self.RACE.format.win_condition

            if win_condition == WinCondition.FASTEST_3_CONSECUTIVE:
                leaderboard = results['by_consecutives']
            elif win_condition == WinCondition.FASTEST_LAP:
                leaderboard = results['by_fastest_lap']
            else:
                # WinCondition.MOST_LAPS
                # WinCondition.FIRST_TO_LAP_X
                # WinCondition.NONE
                leaderboard = results['by_race_time']

            # get this seat's results
            for index, result in enumerate(leaderboard):
                if result['node'] == seat_index:  #TODO issue408
                    rank_index = index
                    break

            # check for best lap
            is_best_lap = False
            if result['fastest_lap_raw'] == result['last_lap_raw']:
                is_best_lap = True

            # get the next faster results
            next_rank_split = None
            next_rank_split_result = None
            if result['position'] > 1:
                next_rank_split_result = leaderboard[rank_index - 1]

                if next_rank_split_result['total_time_raw']:
                    if win_condition == WinCondition.FASTEST_3_CONSECUTIVE:
                        if next_rank_split_result['consecutives_raw']:
                            next_rank_split = result[
                                'consecutives_raw'] - next_rank_split_result[
                                    'consecutives_raw']
                    elif win_condition == WinCondition.FASTEST_LAP:
                        if next_rank_split_result['fastest_lap_raw']:
                            next_rank_split = result[
                                'last_lap_raw'] - next_rank_split_result[
                                    'fastest_lap_raw']
                    else:
                        # WinCondition.MOST_LAPS
                        # WinCondition.FIRST_TO_LAP_X
                        next_rank_split = result[
                            'total_time_raw'] - next_rank_split_result[
                                'total_time_raw']
                        next_rank_split_fastest = ''
            else:
                # check split to self
                next_rank_split_result = leaderboard[rank_index]

                if win_condition == WinCondition.FASTEST_3_CONSECUTIVE or win_condition == WinCondition.FASTEST_LAP:
                    if next_rank_split_result['fastest_lap_raw']:
                        if result['last_lap_raw'] > next_rank_split_result[
                                'fastest_lap_raw']:
                            next_rank_split = result[
                                'last_lap_raw'] - next_rank_split_result[
                                    'fastest_lap_raw']

            # get the next slower results
            prev_rank_split = None
            prev_rank_split_result = None
            if rank_index + 1 in leaderboard:
                prev_rank_split_result = leaderboard[rank_index - 1]

                if prev_rank_split_result['total_time_raw']:
                    if win_condition == WinCondition.FASTEST_3_CONSECUTIVE:
                        if prev_rank_split_result['consecutives_raw']:
                            prev_rank_split = result[
                                'consecutives_raw'] - prev_rank_split_result[
                                    'consecutives_raw']
                            prev_rank_split_fastest = prev_rank_split
                    elif win_condition == WinCondition.FASTEST_LAP:
                        if prev_rank_split_result['fastest_lap_raw']:
                            prev_rank_split = result[
                                'last_lap_raw'] - prev_rank_split_result[
                                    'fastest_lap_raw']
                            prev_rank_split_fastest = result[
                                'fastest_lap_raw'] - prev_rank_split_result[
                                    'fastest_lap_raw']
                    else:
                        # WinCondition.MOST_LAPS
                        # WinCondition.FIRST_TO_LAP_X
                        prev_rank_split = result[
                            'total_time_raw'] - prev_rank_split_result[
                                'total_time_raw']
                        prev_rank_split_fastest = ''

            # get the fastest result
            first_rank_split = None
            first_rank_split_result = None
            if result['position'] > 2:
                first_rank_split_result = leaderboard[0]

                if next_rank_split_result['total_time_raw']:
                    if win_condition == WinCondition.FASTEST_3_CONSECUTIVE:
                        if first_rank_split_result['consecutives_raw']:
                            first_rank_split = result[
                                'consecutives_raw'] - first_rank_split_result[
                                    'consecutives_raw']
                    elif win_condition == WinCondition.FASTEST_LAP:
                        if first_rank_split_result['fastest_lap_raw']:
                            first_rank_split = result[
                                'last_lap_raw'] - first_rank_split_result[
                                    'fastest_lap_raw']
                    else:
                        # WinCondition.MOST_LAPS
                        # WinCondition.FIRST_TO_LAP_X
                        first_rank_split = result[
                            'total_time_raw'] - first_rank_split_result[
                                'total_time_raw']
            '''
            Set up output objects
            '''

            osd = {
                'position_prefix': POS_HEADER,
                'position': str(result['position']),
                'callsign': result['callsign'],
                'lap_prefix': LAP_HEADER,
                'lap_number': '',
                'last_lap_time': '',
                'total_time': result['total_time'],
                'total_time_laps': result['total_time_laps'],
                'consecutives': result['consecutives'],
                'is_best_lap': is_best_lap,
            }

            if result['laps']:
                osd['lap_number'] = str(result['laps'])
                osd['last_lap_time'] = result['last_lap']
            else:
                osd['lap_prefix'] = ''
                osd['lap_number'] = __('HS')
                osd['last_lap_time'] = result['total_time']
                osd['is_best_lap'] = False

            if next_rank_split:
                osd_next_split = {
                    'position_prefix': POS_HEADER,
                    'position': str(next_rank_split_result['position']),
                    'callsign': next_rank_split_result['callsign'],
                    'split_time': RHUtils.time_format(next_rank_split),
                }

                osd_next_rank = {
                    'position_prefix': POS_HEADER,
                    'position': str(next_rank_split_result['position']),
                    'callsign': next_rank_split_result['callsign'],
                    'lap_prefix': LAP_HEADER,
                    'lap_number': '',
                    'last_lap_time': '',
                    'total_time': result['total_time'],
                }

                if next_rank_split_result['laps']:
                    osd_next_rank['lap_number'] = str(
                        next_rank_split_result['laps'])
                    osd_next_rank['last_lap_time'] = next_rank_split_result[
                        'last_lap']
                else:
                    osd_next_rank['lap_prefix'] = ''
                    osd_next_rank['lap_number'] = __('HS')
                    osd_next_rank['last_lap_time'] = next_rank_split_result[
                        'total_time']

            if first_rank_split:
                osd_first_split = {
                    'position_prefix': POS_HEADER,
                    'position': str(first_rank_split_result['position']),
                    'callsign': first_rank_split_result['callsign'],
                    'split_time': RHUtils.time_format(first_rank_split),
                }
            '''
            Format and send messages
            '''

            # "Pos-Callsign L[n]|0:00:00"
            message = osd['position_prefix'] + osd['position'] + '-' + osd[
                'callsign'][:10] + ' ' + osd['lap_prefix'] + osd[
                    'lap_number'] + '|' + osd['last_lap_time']

            if win_condition == WinCondition.FASTEST_3_CONSECUTIVE:
                # "Pos-Callsign L[n]|0:00:00 | #/0:00.000" (current | best consecutives)
                if result['laps'] >= 3:
                    message += ' | 3/' + osd['consecutives']
                elif result['laps'] == 2:
                    message += ' | 2/' + osd['total_time_laps']

            elif win_condition == WinCondition.FASTEST_LAP:
                if next_rank_split:
                    # pilot in 2nd or lower
                    # "Pos-Callsign L[n]|0:00:00 / +0:00.000 Callsign"
                    message += ' / +' + osd_next_split[
                        'split_time'] + ' ' + osd_next_split['callsign'][:10]
                elif osd['is_best_lap']:
                    # pilot in 1st and is best lap
                    # "Pos:Callsign L[n]:0:00:00 / Best"
                    message += ' / ' + __('Best Lap')
            else:
                # WinCondition.MOST_LAPS
                # WinCondition.FIRST_TO_LAP_X
                # WinCondition.NONE

                # "Pos-Callsign L[n]|0:00:00 / +0:00.000 Callsign"
                if next_rank_split:
                    message += ' / +' + osd_next_split[
                        'split_time'] + ' ' + osd_next_split['callsign'][:10]

            # send message to crosser
            seat_dest = seat_index
            self.set_message_direct(seat_dest, message)
            self.logger.debug('msg n{1}:  {0}'.format(message, seat_dest))

            # show split when next pilot crosses
            if next_rank_split:
                if win_condition == WinCondition.FASTEST_3_CONSECUTIVE or win_condition == WinCondition.FASTEST_LAP:
                    # don't update
                    pass

                else:
                    # WinCondition.MOST_LAPS
                    # WinCondition.FIRST_TO_LAP_X
                    # WinCondition.NONE

                    # update pilot ahead with split-behind

                    # "Pos-Callsign L[n]|0:00:00"
                    message = osd_next_rank['position_prefix'] + osd_next_rank[
                        'position'] + '-' + osd_next_rank[
                            'callsign'][:10] + ' ' + osd_next_rank[
                                'lap_prefix'] + osd_next_rank[
                                    'lap_number'] + '|' + osd_next_rank[
                                        'last_lap_time']

                    # "Pos-Callsign L[n]|0:00:00 / -0:00.000 Callsign"
                    message += ' / -' + osd_next_split[
                        'split_time'] + ' ' + osd['callsign'][:10]

                    seat_dest = leaderboard[rank_index - 1]['node']
                    self.set_message_direct(seat_dest, message)
                    self.logger.debug('msg n{1}:  {0}'.format(
                        message, seat_dest))

        else:
            self.logger.warning(
                'Failed to send results: Results not available')
            return False

    ##############
    ## MQTT Status
    ##############

    def request_static_status(self, seat_number=VRxALL):
        if seat_number == VRxALL:
            seat = self._seat_broadcast
            seat.request_static_status()
        else:
            self._seats[seat_number].request_static_status()

    def request_variable_status(self, seat_number=VRxALL):
        if seat_number == VRxALL:
            seat = self._seat_broadcast
            seat.request_variable_status()
        else:
            self._seats[seat_number].request_variable_status()

    ##############
    ## Seat Number
    ##############

    def set_seat_number(self,
                        desired_seat_num=None,
                        current_seat_num=None,
                        serial_num=None):
        """Sets the seat subscription number to desired_number

        If targetting all devices at a certain seat, use 'current_seat_num'
        If targetting a single receiver serial number, use 'serial_num'
        If targetting all receivers, don't supply either 'current_seat_num' or 'serial_num'
        """
        MIN_SEAT_NUM = self.seat_number_range[0]
        MAX_SEAT_NUM = self.seat_number_range[1]
        desired_seat_num = int(desired_seat_num)
        if not MIN_SEAT_NUM <= desired_seat_num <= MAX_SEAT_NUM:
            return ValueError(
                "Desired Seat Number %s out of range in set_seat_number" %
                desired_seat_num)

        if current_seat_num is not None:
            current_seat_num = int(current_seat_num)
            if not MIN_SEAT_NUM <= current_seat_num <= MAX_SEAT_NUM:
                return ValueError(
                    "Desired Seat Number %s out of range in set_seat_number" %
                    current_seat_num)
            self._seats[current_seat_num].set_seat_number(desired_seat_num)
            return

        if serial_num is not None:
            topic = mqtt_publish_topics["cv1"][
                "receiver_command_esp_targeted_topic"][0] % serial_num
            cmd = json.dumps({"seat": str(desired_seat_num)})
            self._mqttc.publish(topic, cmd)
            self.rx_data[serial_num]["needs_config"] = True
            return

        raise NotImplementedError("TODO Broadcast set all seat number")

    ###########
    # Frequency
    ###########

    def set_seat_frequency(self, seat_number, frequency):
        fmsg = __("Frequency Change: ") + str(frequency)
        seat = self._seats[seat_number]
        seat.set_seat_frequency(frequency)

    def set_target_frequency(self, target, frequency):
        if frequency != RHUtils.FREQUENCY_ID_NONE:
            topic = mqtt_publish_topics["cv1"][
                "receiver_command_esp_targeted_topic"][0] % target

            # For ClearView, set the band and channel
            cv_bc = clearview.comspecs.frequency_to_bandchannel_dict(frequency)
            if cv_bc:
                self._mqttc.publish(topic, json.dumps(cv_bc))
            else:
                self.logger.warning("Unable to set ClearView frequency to %s",
                                    frequency)

            self.logger.debug("Set frequency for %s to %d", target, frequency)

    def get_seat_frequency(self, seat_number, frequency):
        self._seats[seat_number].seat_frequency

    #############
    # Lock Status
    #############

    # @property
    # def lock_status(self):
    #     self._lock_status = [seat.seat_lock_status for seat in self._seats]
    #     return self._lock_status

    def get_seat_lock_status(self, seat_number=VRxALL):
        if seat_number == VRxALL:
            seat = self._seat_broadcast
            seat.get_seat_lock_status()
        else:
            seat = self._seats[seat_number]
            seat.get_seat_lock_status()

        #return self._seats[seat_number].seat_lock_status

    #############
    # Camera Type
    #############

    @property
    def camera_type(self):
        self._camera_type = [seat.seat_camera_type for seat in self._seats]
        return self._camera_type

    @camera_type.setter
    def camera_type(self, camera_types):
        """ set the receiver camera types
        camera_types: dict
            key: seat_number
            value: desired camera_type in ['N','P','A']
        """
        for seat_index in camera_types:
            c = camera_types[seat_index]
            self._seats[seat_index].seat_camera_type = c

    def set_seat_camera_type(self, seat_number, camera_type):
        self._seats[seat_number].seat_camera_type = camera_type

    def get_seat_camera_type(self, seat_number, camera_type):
        self._seats[seat_number].seat_camera_type

    ##############
    # OSD Messages
    ##############

    def set_message_direct(self, seat_number, message):
        """set a message directly. Truncated if over length"""
        if message == None:
            self.logger.error("No message")
            return

        if seat_number == VRxALL:
            seat = self._seat_broadcast
            seat.set_message_direct(message)
        else:
            self._seats[seat_number].set_message_direct(message)

    #############################
    # Private Functions for MQTT
    #############################

    def _add_subscribe_callbacks(self):
        for rx_type in mqtt_subscribe_topics:
            topics = mqtt_subscribe_topics[rx_type]

            # All response
            topic_tuple = topics["receiver_response_all"]
            self._add_subscribe_callback(topic_tuple, self.on_message_resp_all)

            # Seat response
            topic_tuple = topics["receiver_response_seat"]
            self._add_subscribe_callback(topic_tuple,
                                         self.on_message_resp_seat)

            # Connection
            topic_tuple = topics["receiver_connection"]
            self._add_subscribe_callback(topic_tuple,
                                         self.on_message_connection)

            # Targeted Response
            topic_tuple = topics["receiver_response_targeted"]
            self._add_subscribe_callback(topic_tuple,
                                         self.on_message_resp_targeted)

    def _add_subscribe_callback(self, topic_tuple, callback):
        formatter_name = topic_tuple[1]

        if formatter_name in [
                "#", "+"
        ]:  # subscibe to all at single level (+) or recursively all (#)
            topic = topic_tuple[0] % formatter_name
        elif formatter_name is None:
            topic = topic_tuple[0]
        elif isinstance(topic_tuple, tuple):
            raise ValueError("Uncaptured formatter_name: %s" % formatter_name)
        elif isinstance(topic_tuple, str):
            topic = topic_tuple
        else:
            raise TypeError("topic_tuple not of correct type: %s" %
                            topic_tuple)

        self._mqttc.message_callback_add(topic, callback)
        self._mqttc.subscribe(topic)

    def perform_initial_receiver_config(self, target):
        """ Given the unique identifier of a receiver, perform the initial config"""
        initial_config_success = False

        try:
            sn = self.rx_data[target]["seat"]
        except KeyError:
            self.logger.info("No seat number available for %s yet", target)
        else:
            self.logger.info("Performing initial configuration for %s", target)

            seat_number = int(self.rx_data[target]['seat'])
            seat = self._seats[seat_number]
            frequency = seat.seat_frequency
            self.set_target_frequency(target, frequency)
            self.turn_off_osd_targeted(target)

            # TODO: send most relevant OSD information

            self.rx_data[target]["needs_config"] = False
            initial_config_success = True

        return initial_config_success

    def on_message_connection(self, client, userdata, message):
        rx_name = message.topic.split('/')[1]

        if rx_name == 'VRxController':
            return

        connection_status = message.payload
        self.logger.info("Found MQTT device: %s => %s" %
                         (rx_name, connection_status))
        rx_data = self.rx_data.setdefault(rx_name,
                                          {"connection": connection_status})

        if int(connection_status) == 1:
            self.logger.info(
                "Device %s is not yet configured by the server after a successful connection. Conducting some config now"
                % rx_name)
            rx_data["needs_config"] = True

            # Start by requesting the status of the device that just joined.
            # At this point, it could be any MQTT device becaue we haven't filtered by receivers.
            # See TODO in on_message_status
            self.req_status_targeted("variable", rx_name)
            self.req_status_targeted("static", rx_name)

        #TODO only fire event if the data changed
        self.Events.trigger(Evt.VRX_DATA_RECEIVE, {
            'rx_name': rx_name,
        })

    def on_message_resp_all(self, client, userdata, message):
        payload = message.payload
        self.logger.info("TODO on_message_resp_all => %s" % (payload.strip()))

    def on_message_resp_seat(self, client, userdata, message):
        topic = message.topic
        seat_number = topic[-1]
        payload = message.payload
        self.logger.info("TODO on_message_resp_seat for seat %s => %s" %
                         (seat_number, payload.strip()))

    def on_message_resp_targeted(self, client, userdata, message):
        topic = message.topic
        rx_name = topic.split('/')[-1]
        payload = message.payload
        if len(payload) >= MINIMUM_PAYLOAD:
            rx_data = self.rx_data.setdefault(
                rx_name,
                {"connection": "1"})  #TODO this is probably not needed
            try:
                extracted_data = json.loads(payload)

            except:
                self.logger.warning("Can't load json data from '%s' of '%s'",
                                    rx_name, payload)
                self.logger.debug(traceback.format_exc())
                rx_data["valid_rx"] = False
            else:
                rx_data["valid_rx"] = True
                rx_data.update(extracted_data)

                if "lock" in extracted_data:
                    rep_lock = extracted_data["lock"]

                    rx_data["chosen_camera_type"] = rep_lock[0]
                    rx_data["cam_forced_or_auto"] = rep_lock[1]
                    rx_data["lock_status"] = rep_lock[2]

                #TODO only fire event if the data changed
                self.Events.trigger(Evt.VRX_DATA_RECEIVE, {
                    'rx_name': rx_name,
                })

                if rx_data["needs_config"] == True and rx_data[
                        "valid_rx"] == True:
                    self.perform_initial_receiver_config(rx_name)

    def req_status_targeted(self, mode="variable", serial_num=None):
        """Ask a targeted receiver for its status.
        Inputs:
            *mode: ["variable","static"]
            *serial_num: The devices's unique serial number to target it
        """

        if mode not in ["variable", "static"]:
            self.logger.error("Incorrect mode in req_status_targeted")
            return None
        if serial_num not in self.rx_data.keys():
            self.logger.error("RX %s does not exist", serial_num)
            return None

        topic = mqtt_publish_topics["cv1"][
            "receiver_command_esp_targeted_topic"][0] % serial_num
        if mode == "variable":
            cmd = ESP_COMMANDS["Request Variable Status"]
        elif mode == "static":
            cmd = ESP_COMMANDS["Request Static Status"]
        else:
            raise Exception("Error checking mode has failed")
        self._mqttc.publish(topic, cmd)

    def turn_off_osd_targeted(self, target):
        """Turns off all OSD elements except user message"""
        topic = mqtt_publish_topics["cv1"][
            "receiver_command_esp_targeted_topic"][0] % target
        cmd = json.dumps({"osd_visibility": "D"})
        self._mqttc.publish(topic, cmd)
        return cmd

    def turn_on_osd_targeted(self, target):
        """Turns on all OSD elements except user message"""
        topic = mqtt_publish_topics["cv1"][
            "receiver_command_esp_targeted_topic"][0] % target
        cmd = json.dumps({"osd_visibility": "E"})
        self._mqttc.publish(topic, cmd)
        return cmd
コード例 #4
0
class VRxController:
    """Every video receiver has the following methods and data attributes"""
    controllers = {}
    '''
    list of vcs
    [address] = {
        (status)
    }
    '''

    primary = []
    '''
    maps specific VRx to nodes (primary assignments)
    [address, address, None, ... ]
    '''
    def __init__(self, eventmanager, VRxServer, node_frequencies):
        self.Events = eventmanager

        self._cv = clearview.ClearView(return_formatted_commands=True)

        #TODO the subscribe topics subscribe it to a node number by default
        #Don't hack by making node number *

        #TODO: pass in "CV1 to the MQTT_CLIENT maybe. "
        # There will be multiple clients, one for each protocol"
        # Probably best to manually add the subscriptions and callbacks
        self._mqttc = MQTT_Client(client_id="VRxController",
                                  broker_ip=VRxServer,
                                  subscribe_topics=None)

        self._add_subscribe_callbacks()
        self._mqttc.loop_start()
        num_nodes = len(node_frequencies)

        self._nodes = [
            VRxNode(self._mqttc, n, node_frequencies[n], self._cv)
            for n in range(8)
        ]
        self._node_broadcast = VRxBroadcastNode(self._mqttc, -1, None,
                                                self._cv)

        #TODO is this the right way. Store data twice? Maybe it should be a member var.
        # If the decorators worked...
        self._frequency = [node.node_frequency for node in self._nodes]
        self._lock_status = [node.node_lock_status for node in self._nodes]
        self._camera_type = [node.node_camera_type for node in self._nodes]

        # Events
        self.Events.on(Evt.STARTUP, 'VRx', self.do_startup, {}, 200)
        self.Events.on(Evt.RACESTART, 'VRx', self.do_racestart, {}, 200)
        self.rxdata = {}

    def do_startup(self, arg):
        print("vrxc: Doing startup")

        # Request status of all receivers (static and variable)
        self.request_static_status(all=True)
        self.request_variable_status(all=True)

        for n in range(8):
            print("LOCK STATUS ", n)
            self.get_node_lock_status(n)

        # Update the DB with receivers that exist and their status
        # (Because the pi was already running, they should all be connected to the broker)
        # Even if the server.py is restarted, the broker continues to run:)

        # Set the receiver's frequencies based on band/channel

    def do_racestart(self, arg):
        self.set_message_direct(VRxALL, "GO!")

    ##############
    ## MQTT Status
    ##############
    def request_static_status(self, node_number=VRxALL, all=False):
        #self._mqttc.publish()
        #TODO
        print("TODO request static status")
        #raise NotImplementedError

    def request_variable_status(self, node_number=VRxALL, all=False):
        #self._mqttc.publish()
        #TODO
        print("TODO request variable status")
        #raise NotImplementedError

    ###########
    # Frequency
    ###########
    @property
    def frequency(self):
        return [node.node_frequency for node in self._nodes]

    @frequency.setter
    def frequency(self, frequencies):
        """ set the receiver frequencies
        frequencies: dict
            key: node_number
            value: desired frequency
        """
        for node_number in frequencies:
            f = frequencies[node_number]
            self._nodes[node_number].node_frequency = f

    def set_node_frequency(self, node_number, frequency):
        node = self._nodes[node_number]
        node.set_node_frequency(frequency)

    def get_node_frequency(self, node_number, frequency):
        self._nodes[node_number].node_frequency

    #############
    # Lock Status
    #############

    @property
    def lock_status(self):
        self._lock_status = [node.node_lock_status for node in self._nodes]
        return self._lock_status

    def get_node_lock_status(self, node_number):
        node = self._nodes[node_number]
        report_req = node.get_node_lock_status()

        #return self._nodes[node_number].node_lock_status

    #############
    # Camera Type
    #############

    @property
    def camera_type(self):
        self._camera_type = [node.node_camera_type for node in self._nodes]
        return self._camera_type

    @camera_type.setter
    def camera_type(self, camera_types):
        """ set the receiver camera types
        camera_types: dict
            key: node_number
            value: desired camera_type in ['N','P','A']
        """
        for node_index in camera_types:
            c = camera_types[node_index]
            self._nodes[node_index].node_camera_type = c

    def set_node_camera_type(self, node_number, camera_type):
        self._nodes[node_number].node_camera_type = camera_type

    def get_node_camera_type(self, node_number, camera_type):
        self._nodes[node_number].node_camera_type

    ##############
    # OSD Messages
    ##############

    def set_message_direct(self, node_number, message):
        """set a message directly. Truncated if over length"""

    #############################
    # Private Functions for MQTT
    #############################

    def _add_subscribe_callbacks(self):
        for rx_type in mqtt_subscribe_topics:
            topics = mqtt_subscribe_topics[rx_type]

            # All response
            topic_tuple = topics["receiver_response_all"]
            self._add_subscribe_callback(topic_tuple, self.on_message_resp_all)

            # Node response
            print(topics)
            topic_tuple = topics["receiver_response_node"]
            self._add_subscribe_callback(topic_tuple,
                                         self.on_message_resp_node)

            # Connection
            topic_tuple = topics["receiver_connection"]
            self._add_subscribe_callback(topic_tuple,
                                         self.on_message_connection)

            # Targetted Response
            topic_tuple = topics["receiver_response_targeted"]
            self._add_subscribe_callback(topic_tuple,
                                         self.on_message_resp_targeted)

    def _add_subscribe_callback(self, topic_tuple, callback):
        formatter_name = topic_tuple[1]

        if formatter_name in [
                "#", "+"
        ]:  # subscibe to all at single level (+) or recursively all (#)
            topic = topic_tuple[0] % formatter_name
        elif formatter_name is None:
            topic = topic_tuple[0]
        elif isinstance(topic_tuple, tuple):
            raise ValueError("Uncaptured formatter_name: %s" % formatter_name)
        elif isinstance(topic_tuple, str):
            topic = topic_tuple
        else:
            raise TypeError("topic_tuple not of correct type: %s" %
                            topic_tuple)

        self._mqttc.message_callback_add(topic, callback)
        self._mqttc.subscribe(topic)

    def on_message_connection(self, client, userdata, message):
        rx_name = message.topic
        connection_status = message.payload
        printc("Connection message received:", rx_name, "=>",
               connection_status)
        try:
            self.rxdata[rx_name]["connection"] = connection_status
        except KeyError:
            self.rxdata[rx_name] = {"connection": connection_status}

    def on_message_resp_all(self, client, userdata, message):
        payload = message.payload
        printc("TODO on_message_resp_all => %s" % (payload))

    def on_message_resp_node(self, client, userdata, message):
        topic = message.topic
        node_number = topic[-1]
        payload = message.payload
        printc("TODO on_message_resp_node for node %s => %s" %
               (node_number, payload))

    def on_message_resp_targeted(self, client, userdata, message):
        topic = message.topic
        rx_name = topic.split('/')[-1]
        payload = message.payload
        printc("TODO on_message_resp_targeted for receiver %s => %s" %
               (rx_name, payload))