예제 #1
0
 def test_assigning_script_to_wrong_button_fails_parsing(self):
     self.assertRaises(
         ConfigError, lambda: RobotConfig.from_string('''
     {
         "robotConfig": [],
         "blocklyList": [
             {
                 "pythonCode": "",
                 "assignments": {
                     "buttons": [
                         {"id": 33, "priority": 0}
                     ]
                 }
             }
         ]
     }'''))
     self.assertRaises(
         ConfigError, lambda: RobotConfig.from_string('''
     {
         "robotConfig": [],
         "blocklyList": [
             {
                 "pythonCode": "",
                 "assignments": {
                     "buttons": [
                         {"id": "nan", "priority": 0}
                     ]
                 }
             }
         ]
     }'''))
예제 #2
0
    def test_empty_or_null_motors_are_not_configured(self):
        json = '''
        {
            "robotConfig": {
                "motors": [
                    {
                        "name": "M1",
                        "type": 1,
                        "side": 0
                    },
                    {},
                    null,
                    {
                        "name": "M4",
                        "type": 1,
                        "side": 1
                    }
                ]
            },
            "blocklyList": []
        }'''

        config = RobotConfig.from_string(json)

        self.assertEqual(Motors.RevvyMotor, config.motors[1])
        self.assertEqual(None, config.motors[2])
        self.assertEqual(None, config.motors[3])
        self.assertEqual(Motors.RevvyMotor, config.motors[4])
예제 #3
0
    def test_scripts_can_be_assigned_to_multiple_analog_channels(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "pythonCode": "{SOURCE}",
                    "assignments": {
                        "analog": [
                            {"channels": [0, 1], "priority": 1}
                        ]
                    }
                }
            ]
        }'''.replace('{SOURCE}', b64_encode_str("some code"))
        config = RobotConfig.from_string(json)

        self.assertEqual(1, len(config.controller.analog))

        script_name = '[script 0] analog channels 0, 1'

        script_descriptor = config.controller.analog[0]['script']

        self.assertListEqual([0, 1], config.controller.analog[0]['channels'])
        self.assertEqual(script_name, script_descriptor.name)
        self.assertTrue(callable(script_descriptor.runnable))
        self.assertEqual(1, script_descriptor.priority)
예제 #4
0
    def test_empty_or_null_sensors_are_not_configured(self):
        json = '''
        {
            "robotConfig": {
                "sensors": [
                    {
                        "name": "S1",
                        "type": 1
                    },
                    {},
                    null,
                    {
                        "name": "S4",
                        "type": 2
                    }
                ]
            },
            "blocklyList": []
        }'''

        config = RobotConfig.from_string(json)

        self.assertEqual(Sensors.Ultrasonic, config.sensors[1])
        self.assertEqual(None, config.sensors[2])
        self.assertEqual(None, config.sensors[3])
        self.assertEqual(Sensors.BumperSwitch, config.sensors[4])
예제 #5
0
    def test_scripts_can_be_assigned_to_multiple_buttons(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "pythonCode": "{SOURCE}",
                    "assignments": {
                        "buttons": [
                            {"id": 0, "priority": 2},
                            {"id": 2, "priority": 0}
                        ]
                    }
                }
            ]
        }'''.replace('{SOURCE}', b64_encode_str("some code"))
        config = RobotConfig.from_string(json)

        script_names = ['[script 0] button 0', '[script 1] button 2']

        self.assertEqual(script_names[0], config.controller.buttons[0].name)
        self.assertEqual(script_names[1], config.controller.buttons[2].name)

        self.assertTrue(callable(config.controller.buttons[0].runnable))
        self.assertTrue(callable(config.controller.buttons[2].runnable))

        self.assertEqual(2, config.controller.buttons[0].priority)
        self.assertEqual(0, config.controller.buttons[2].priority)
예제 #6
0
    def test_scripts_can_be_assigned_to_multiple_analog_channels(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "pythonCode": "{SOURCE}",
                    "assignments": {
                        "analog": [
                            {"channels": [0, 1], "priority": 1}
                        ]
                    }
                }
            ]
        }'''.replace('{SOURCE}', b64_encode_str("some code"))
        config = RobotConfig.from_string(json)

        self.assertEqual(1, len(config.controller.analog))

        self.assertEqual('user_script_0',
                         config.controller.analog[0]['script'])
        self.assertListEqual([0, 1], config.controller.analog[0]['channels'])
        self.assertEqual('some code',
                         config.scripts['user_script_0']['script'])
        self.assertEqual(1, config.scripts['user_script_0']['priority'])
예제 #7
0
    def test_scripts_can_be_assigned_to_multiple_buttons(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "pythonCode": "{SOURCE}",
                    "assignments": {
                        "buttons": [
                            {"id": 0, "priority": 2},
                            {"id": 2, "priority": 0}
                        ]
                    }
                }
            ]
        }'''.replace('{SOURCE}', b64_encode_str("some code"))
        config = RobotConfig.from_string(json)

        self.assertEqual('user_script_0', config.controller.buttons[0])
        self.assertEqual('user_script_1', config.controller.buttons[2])

        self.assertEqual('some code',
                         config.scripts['user_script_0']['script'])
        self.assertEqual('some code',
                         config.scripts['user_script_1']['script'])

        self.assertEqual(2, config.scripts['user_script_0']['priority'])
        self.assertEqual(0, config.scripts['user_script_1']['priority'])
예제 #8
0
    def test_scripts_can_be_assigned_to_every_type_at_once(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "pythonCode": "{SOURCE}",
                    "assignments": {
                        "buttons": [{"id": 1, "priority": 0}],
                        "analog": [{"channels": [0, 1], "priority": 1}],
                        "background": 3
                    }
                }
            ]
        }'''.replace('{SOURCE}', b64_encode_str("some code"))
        config = RobotConfig.from_string(json)

        self.assertEqual(1, len(config.background_scripts))

        self.assertEqual('user_script_0',
                         config.controller.analog[0]['script'])
        self.assertEqual('user_script_1', config.controller.buttons[1])
        self.assertEqual('user_script_2', config.background_scripts[0])
        self.assertEqual('some code',
                         config.scripts['user_script_0']['script'])
        self.assertEqual('some code',
                         config.scripts['user_script_1']['script'])
        self.assertEqual('some code',
                         config.scripts['user_script_2']['script'])
        self.assertEqual(0, config.scripts['user_script_1']['priority'])
        self.assertEqual(1, config.scripts['user_script_0']['priority'])
        self.assertEqual(3, config.scripts['user_script_2']['priority'])
예제 #9
0
    def test_scripts_can_be_assigned_to_every_type_at_once(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "pythonCode": "{SOURCE}",
                    "assignments": {
                        "buttons": [{"id": 1, "priority": 0}],
                        "analog": [{"channels": [0, 1], "priority": 1}],
                        "background": 3
                    }
                }
            ]
        }'''.replace('{SOURCE}', b64_encode_str("some code"))
        config = RobotConfig.from_string(json)

        self.assertEqual(1, len(config.background_scripts))

        script_names = [
            '[script 0] analog channels 0, 1', '[script 1] button 1',
            '[script 2] background'
        ]

        self.assertEqual(script_names[0],
                         config.controller.analog[0]['script'].name)
        self.assertEqual(script_names[1], config.controller.buttons[1].name)
        self.assertEqual(script_names[2], config.background_scripts[0].name)
        self.assertTrue(
            callable(config.controller.analog[0]['script'].runnable))
        self.assertTrue(callable(config.controller.buttons[1].runnable))
        self.assertTrue(callable(config.background_scripts[0].runnable))
        self.assertEqual(1, config.controller.analog[0]['script'].priority)
        self.assertEqual(0, config.controller.buttons[1].priority)
        self.assertEqual(3, config.background_scripts[0].priority)
예제 #10
0
    def test_valid_config_needs_robotConfig_and_blocklies_keys(self):
        with self.subTest("Blockly only"):
            config = RobotConfig.from_string('{"blocklies": []}')
            self.assertIsNone(config)

        with self.subTest("Robot Config only"):
            config = RobotConfig.from_string('{"robotConfig": []}')
            self.assertIsNone(config)

        with self.subTest("Both"):
            config = RobotConfig.from_string(
                '{"robotConfig": [], "blocklyList": []}')
            self.assertIsNotNone(config)

        with self.subTest("Both, lowercase"):
            config = RobotConfig.from_string(
                '{"robotconfig": [], "blocklylist": []}')
            self.assertIsNotNone(config)
예제 #11
0
    def __init__(self,
                 interface: RevvyControl,
                 revvy,
                 sound_paths,
                 sw_version,
                 default_config=None):
        print("RobotManager: __init__()")
        self.needs_interrupting = True

        self._configuring = False
        self._robot = Robot(interface, sound_paths, sw_version)
        self._interface = interface
        self._ble = revvy
        self._default_configuration = default_config or RobotConfig()

        self._status_update_thread = periodic(self._update, 0.02,
                                              "RobotStatusUpdaterThread")
        self._background_fn_lock = Lock()
        self._background_fns = []

        rc = RemoteController()
        rcs = RemoteControllerScheduler(rc)
        rcs.on_controller_detected(self._on_controller_detected)
        rcs.on_controller_lost(self._on_controller_lost)

        self._remote_controller = rc
        self._remote_controller_scheduler = rcs
        self._remote_controller_thread = create_remote_controller_thread(rcs)

        self._resources = {
            'led_ring': Resource(),
            'drivetrain': Resource(),
            'sound': Resource(),
            **{
                'motor_{}'.format(port.id): Resource()
                for port in self._robot.motors
            },
            **{
                'sensor_{}'.format(port.id): Resource()
                for port in self._robot.sensors
            }
        }

        revvy['live_message_service'].register_message_handler(
            self._remote_controller_scheduler.data_ready)
        revvy.on_connection_changed(self._on_connection_changed)

        self._scripts = ScriptManager(self)
        self._config = self._default_configuration

        self._status_code = RevvyStatusCode.OK
        self.exited = False
    def test_named_ports_can_be_accessed_via_robot_interface(self):
        """This test case verifies that PortCollection and RobotConfig use the same port numbering conventions"""
        config_str = '''{
            "robotConfig": {
                "motors": [
                    {
                        "name": "M1",
                        "type": 1,
                        "reversed": 0,
                        "side": 0
                    },
                    {
                        "name": "M2",
                        "type": 2,
                        "reversed": 1,
                        "side": 1
                    }
                ],
                "sensors": [
                    {
                        "name": "S1",
                        "type": 1
                    },
                    {
                        "name": "S2",
                        "type": 2
                    },
                    {
                        "name": "S3",
                        "type": 0
                    },
                    {
                        "name": "S4",
                        "type": 1
                    }
                ]
            },
            "blocklyList": []
        }'''
        config = RobotConfig.from_string(config_str)

        motors = PortCollection([2, 3, 5, 7])
        motors.aliases.update(config.motors.names)

        sensors = PortCollection([3, 5, 7, 9])
        sensors.aliases.update(config.sensors.names)

        self.assertEqual(3, motors["M2"])
        self.assertEqual(9, sensors["S4"])
예제 #13
0
 def test_scripts_without_code_or_script_name_fail(self):
     self.assertRaises(
         ConfigError, lambda: RobotConfig.from_string('''
     {
         "robotConfig": [],
         "blocklyList": [
             {
                 "assignments": {
                     "buttons": [
                         {"id": 0, "priority": 2},
                         {"id": 2, "priority": 0}
                     ]
                 }
             }
         ]
     }'''))
예제 #14
0
    def test_lower_case_script_name_is_accepted(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "builtinscriptname": "drive_2sticks",
                    "assignments": {
                        "analog": [{"channels": [0, 1], "priority": 2}]
                    }
                }
            ]
        }'''
        config = RobotConfig.from_string(json)

        self.assertEqual('user_script_0',
                         config.controller.analog[0]['script'])
        self.assertTrue(callable(config.scripts['user_script_0']['script']))
예제 #15
0
    def on_message_updated(self, message: ReceivedLongMessage):
        message_type = message.message_type
        self._log(f'Received message: {message_type}')

        if message_type == LongMessageType.TEST_KIT:
            test_script_source = message.data.decode()
            self._log(f'Running test script: {test_script_source}')

            script_descriptor = ScriptDescriptor(
                "test_kit", str_to_func(test_script_source), 0)

            def start_script():
                self._log("Starting new test script")
                handle = self._robot._scripts.add_script(script_descriptor)
                handle.on_stopped(partial(self._robot.configure, None))

                # start can't run in on_stopped handler because overwriting script causes deadlock
                self._robot.run_in_background(handle.start)

            self._robot.configure(empty_robot_config, start_script)

        elif message_type == LongMessageType.CONFIGURATION_DATA:
            message_data = message.data.decode()
            self._log(f'New configuration: {message_data}')

            try:
                parsed_config = RobotConfig.from_string(message_data)

                if self._ignore_config:
                    self._log('New configuration ignored')
                else:
                    self._robot.configure(parsed_config,
                                          self._robot.start_remote_controller)
            except ConfigError:
                self._log(traceback.format_exc())

        elif message_type == LongMessageType.FRAMEWORK_DATA:
            self._robot.robot.status.robot_status = RobotStatus.Updating
            self._progress.set_indeterminate()
            self._robot.request_update()

        elif message_type == LongMessageType.ASSET_DATA:
            extract_asset_longmessage(self._storage, self._asset_dir)
예제 #16
0
    def test_type0_objects_dont_require_additional_keys(self):
        json = '''
        {
            "robotConfig": {
                "motors": [
                    {
                        "type": 0
                    }
                ],
                "sensors": [
                    {
                        "type": 0
                    }
                ]
            },
            "blocklyList": []
        }'''

        config = RobotConfig.from_string(json)
        self.assertIsNotNone(config)
예제 #17
0
    def test_lower_case_pythoncode_is_accepted(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "pythoncode": "{SOURCE}",
                    "assignments": {
                        "background": 3
                    }
                }
            ]
        }'''.replace('{SOURCE}', b64_encode_str("some code"))
        config = RobotConfig.from_string(json)

        self.assertEqual(1, len(config.background_scripts))

        self.assertEqual('user_script_0', config.background_scripts[0])
        self.assertEqual('some code',
                         config.scripts['user_script_0']['script'])
        self.assertEqual(3, config.scripts['user_script_0']['priority'])
예제 #18
0
    def test_scripts_can_be_configured_to_run_in_background(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "pythonCode": "{SOURCE}",
                    "assignments": {
                        "background": 3
                    }
                }
            ]
        }'''.replace('{SOURCE}', b64_encode_str("some code"))
        config = RobotConfig.from_string(json)

        self.assertEqual(1, len(config.background_scripts))

        script_name = '[script 0] background'

        self.assertEqual(script_name, config.background_scripts[0].name)
        self.assertEqual(3, config.background_scripts[0].priority)
예제 #19
0
    def test_motor_side_and_reversed_is_ignored_for_normal_motors(self):
        json = '''
        {
            "robotConfig": {
                "motors": [
                    {
                        "name": "M1",
                        "type": 1,
                        "reversed": 0,
                        "side": 0
                    },
                    {
                        "name": "M2",
                        "type": 1,
                        "reversed": 1,
                        "side": 0
                    },
                    {
                        "name": "M3",
                        "type": 1,
                        "reversed": 0,
                        "side": 1
                    },
                    {
                        "name": "M4",
                        "type": 1,
                        "reversed": 1,
                        "side": 1
                    }
                ]
            },
            "blocklyList": []
        }'''

        config = RobotConfig.from_string(json)

        self.assertEqual(Motors.RevvyMotor, config.motors[1])
        self.assertEqual(Motors.RevvyMotor, config.motors[2])
        self.assertEqual(Motors.RevvyMotor, config.motors[3])
        self.assertEqual(Motors.RevvyMotor, config.motors[4])
예제 #20
0
    def test_lower_case_pythoncode_is_accepted(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "pythoncode": "{SOURCE}",
                    "assignments": {
                        "background": 3
                    }
                }
            ]
        }'''.replace('{SOURCE}', b64_encode_str("some code"))
        config = RobotConfig.from_string(json)

        self.assertEqual(1, len(config.background_scripts))

        script_name = '[script 0] background'

        self.assertEqual(script_name, config.background_scripts[0].name)
        self.assertTrue(callable(config.background_scripts[0].runnable))
        self.assertEqual(3, config.background_scripts[0].priority)
예제 #21
0
    def test_builtin_scripts_can_be_referenced(self):
        json = '''
        {
            "robotConfig": [],
            "blocklyList": [
                {
                    "builtinScriptName": "drive_2sticks",
                    "assignments": {
                        "analog": [{"channels": [0, 1], "priority": 2}]
                    }
                }
            ]
        }'''
        config = RobotConfig.from_string(json)

        script_name = '[script 0] analog channels 0, 1'
        self.assertEqual(script_name,
                         config.controller.analog[0]['script'].name)
        self.assertTrue(
            callable(config.controller.analog[0]['script'].runnable))
        self.assertEqual(drive_2sticks,
                         config.controller.analog[0]['script'].runnable)
예제 #22
0
    def test_valid_config_needs_robotConfig_and_blocklies_keys(self):
        with self.subTest("Blockly only"):
            self.assertRaises(
                ConfigError,
                lambda: RobotConfig.from_string('{"blocklies": []}'))

        with self.subTest("Robot Config only"):
            self.assertRaises(
                ConfigError,
                lambda: RobotConfig.from_string('{"robotConfig": []}'))

        # these should not raise ConfigError
        with self.subTest("Both"):
            RobotConfig.from_string('{"robotConfig": [], "blocklyList": []}')

        with self.subTest("Both, lowercase"):
            RobotConfig.from_string('{"robotconfig": [], "blocklylist": []}')
예제 #23
0
    def test_sensors_are_parsed_as_list_of_sensors(self):
        json = '''
        {
            "robotConfig": {
                "sensors": [
                    {
                        "name": "S1",
                        "type": 1
                    },
                    {
                        "name": "S2",
                        "type": 2
                    },
                    {
                        "name": "S3",
                        "type": 0
                    },
                    {
                        "name": "S4",
                        "type": 0
                    }
                ]
            },
            "blocklyList": []
        }'''

        config = RobotConfig.from_string(json)

        self.assertEqual(Sensors.Ultrasonic, config.sensors[1])
        self.assertEqual(Sensors.BumperSwitch, config.sensors[2])
        self.assertEqual(None, config.sensors[3])
        self.assertEqual(None, config.sensors[4])

        self.assertEqual(1, config.sensors.names["S1"])
        self.assertEqual(2, config.sensors.names["S2"])
        # not configured ports does not have names
        self.assertNotIn("S3", config.sensors.names)
        self.assertNotIn("S4", config.sensors.names)
예제 #24
0
 def test_not_valid_config_is_ignored(self):
     config = RobotConfig.from_string('not valid json')
     self.assertIsNone(config)
예제 #25
0
 def test_not_valid_config_is_ignored(self):
     self.assertRaises(ConfigError,
                       lambda: RobotConfig.from_string('not valid json'))
예제 #26
0
    def test_motors_are_parsed_as_list_of_motors(self):
        json = '''
        {
            "robotConfig": {
                "motors": [
                    {
                        "name": "M1",
                        "type": 0,
                        "reversed": 0,
                        "side": 0
                    },
                    {
                        "name": "M2",
                        "type": 2,
                        "reversed": 0,
                        "side": 0
                    },
                    {
                        "name": "M3",
                        "type": 2,
                        "reversed": 1,
                        "side": 0
                    },
                    {
                        "name": "M4",
                        "type": 1,
                        "reversed": 1
                    },
                    {
                        "name": "M5",
                        "type": 2,
                        "reversed": 0,
                        "side": 1
                    },
                    {
                        "name": "M6",
                        "type": 2,
                        "reversed": 1,
                        "side": 1
                    }
                ]
            },
            "blocklyList": []
        }'''

        config = RobotConfig.from_string(json)

        self.assertEqual(None, config.motors[1])

        # drivetrain left
        self.assertEqual(Motors.RevvyMotor_CCW,
                         config.motors[2])  # normal left
        self.assertEqual(Motors.RevvyMotor, config.motors[3])  # reversed left

        # drivetrain right
        self.assertEqual(Motors.RevvyMotor, config.motors[5])  # normal right
        self.assertEqual(Motors.RevvyMotor_CCW,
                         config.motors[6])  # reversed right

        self.assertEqual(Motors.RevvyMotor,
                         config.motors[4])  # motor, no 'side', no 'reversed'

        self.assertListEqual([2, 3], config.drivetrain['left'])
        self.assertListEqual([5, 6], config.drivetrain['right'])

        self.assertNotIn(
            "M1",
            config.motors.names)  # not configured port does not have name
        self.assertEqual(2, config.motors.names["M2"])
        self.assertEqual(3, config.motors.names["M3"])
        self.assertEqual(4, config.motors.names["M4"])
        self.assertEqual(5, config.motors.names["M5"])
        self.assertEqual(6, config.motors.names["M6"])