Пример #1
0
 def do_repl(self, args):
     self.__doc__ = super().do_repl.__doc__
     try:
         super().do_repl(args)
     except WebSocketConnectionClosedException as e:
         TerminalPrinter.print_error("Connection lost to repl")
         self._disconnect()
Пример #2
0
    def bno_test(self):
        self.guard_open(
        )  # No need to guard for antenna initialization when doing diagnostics

        print("Input the SDA pin and SCL of the BNO device to test")
        try:
            sda = int(input("SDA Pin#: "))
            scl = int(input("SCL Pin#: "))
        except ValueError:
            TerminalPrinter.print_error(
                "Invalid type for pin number. Try again using only decimal numbers"
            )
            return

        bno_test_diagnostics = self.invoker.bno_diagnostics(sda, scl)
        print("---")
        print("I2C bus usable?", bno_test_diagnostics.i2c_bus_scannable)
        if len(bno_test_diagnostics.i2c_addresses) == 0:
            print("I2C address detected? False")
        else:
            print("I2C address detected? True, addresses =",
                  bno_test_diagnostics.i2c_addresses)
        print("BNO connection established?",
              bno_test_diagnostics.bno_object_created)
        print("BNO calibrated?", bno_test_diagnostics.bno_object_calibrated)
Пример #3
0
 def track(self, sat_name):
     self.guard_open()
     self.guard_init()
     imu_enabled = self.invoker.config_get("use_imu")
     if imu_enabled == 'False':
         TerminalPrinter.print_track_warning()
     self.invoker.set_tracking(True)
     latitude = float(self.invoker.config_get("latitude"))
     longitude = float(self.invoker.config_get("longitude"))
     asyncio.run(self._start_track(sat_name, (latitude, longitude)))
Пример #4
0
    def calibrate(self):
        self.guard_open()
        print("Detecting calibration status ...")
        data = self.invoker.imu_calibration_status()
        data = (data['system'], data['gyroscope'], data['accelerometer'],
                data['magnetometer'])
        if not data:
            # TODO: should TerminalPrinter methods be static?
            TerminalPrinter().print_error("Error connecting to BNO055.")
            return

        system_level, gyro_level, accel_level, magnet_level = data
        system_calibrated = system_level > 0
        gyro_calibrated = gyro_level > 0
        accel_calibrated = accel_level > 0
        magnet_calibrated = magnet_level > 0
        components_calibrated = (system_calibrated, gyro_calibrated,
                                 accel_calibrated, magnet_calibrated)
        TerminalPrinter()._display_initial_calibration_status(
            components_calibrated)

        waiting_dot_count = 4
        dot_counter = 0
        sleep(1)
        while not (magnet_calibrated and accel_calibrated and gyro_calibrated):
            sleep(0.5)
            old_calibration_status = (system_calibrated, gyro_calibrated,
                                      accel_calibrated, magnet_calibrated)
            system_calibrated, gyro_calibrated, accel_calibrated, magnet_calibrated = TerminalPrinter(
            )._display_loop_calibration_status(data, old_calibration_status,
                                               waiting_dot_count, dot_counter)

            # Re-fetch calibration data
            data = self.invoker.imu_calibration_status()
            data = (data['system'], data['gyroscope'], data['accelerometer'],
                    data['magnetometer'])
            if not data:
                TerminalPrinter().print_error("Error connecting to BNO055.")
                return

            dot_counter = (dot_counter + 1) % waiting_dot_count

        print(
            f"System calibration complete: {TerminalPrinter.YES_DISPLAY_STRING}"
        )
        print("Saving calibration data ...")
        # self.do_save_calibration(None)
        self.save_calibration()
        print("Calibration data is now saved to config.")
Пример #5
0
    def __init__(self, color=False, caching=False, reset=False):
        """Creates Cmd-based shell object.

        Keyword arguments:
        color -- support colored text in the shell
        caching -- support caching the results of functions like 'ls'
        reset -- hard reset device via DTR. (serial connection only)
        """
        super().__init__(color, caching, reset)

        self.client = AntennyClient(self.caching)
        self.printer = TerminalPrinter()
        self._intro()
        self._set_prompt_path()
        self.emptyline = lambda: None
Пример #6
0
 def _set_prompt_path(self):
     """Prompt that appears at the beginning of every line in the shell."""
     if self.fe is not None:
         pwd = self.fe.pwd()
     else:
         pwd = "/"
     self.prompt = TerminalPrinter.prompt(pwd)
Пример #7
0
 def _intro(self):
     self.intro = TerminalPrinter.intro()
Пример #8
0
class NyanShell(mpfshell.MpFileShell):
    """Extension of MPFShell that adds NyanSat-specific features"""

    def __init__(self, color=False, caching=False, reset=False):
        """Creates Cmd-based shell object.

        Keyword arguments:
        color -- support colored text in the shell
        caching -- support caching the results of functions like 'ls'
        reset -- hard reset device via DTR. (serial connection only)
        """
        super().__init__(color, caching, reset)

        self.client = AntennyClient(self.caching)
        self.printer = TerminalPrinter()
        self._intro()
        self._set_prompt_path()
        self.emptyline = lambda: None

    def _intro(self):
        self.intro = self.printer.intro()

    def _disconnect(self):
        return super()._MpFileShell__disconnect()

    def _connect(self, port):
        """
        Creates FileExplorers. Also creates a AntennyClient (which creates an invoker) using
        FileExplorer's self.con object.
        """
        super()._MpFileShell__connect(port)
        self._set_prompt_path()
        self.client.initialize(self.fe)

    def _set_prompt_path(self):
        """Prompt that appears at the beginning of every line in the shell."""
        if self.fe is not None:
            pwd = self.fe.pwd()
        else:
            pwd = "/"
        self.prompt = self.printer.prompt(pwd)

    def do_open(self, args):
        """open <TARGET>
        Open connection to device with given target. TARGET might be:
        - a serial port, e.g.       ttyUSB0, ser:/dev/ttyUSB0
        - a telnet host, e.g        tn:192.168.1.1 or tn:192.168.1.1,login,passwd
        - a websocket host, e.g.    ws:192.168.1.1 or ws:192.168.1.1,passwd
        """
        arg_properties = [
            CLIArgumentProperty(
                str,
                None
            )
        ]
        parsed_args = parse_cli_args(args, 'open', 1, arg_properties)
        port, = parsed_args
        if (
                not port.startswith("ser:/dev/")
                and not port.startswith("ser:COM")
                and not port.startswith("tn:")
                and not port.startswith("ws:")
        ):

            if platform.system() == "Windows":
                args = "ser:" + args
            else:
                args = "ser:/dev/" + args

        return self._connect(args)

    def do_repl(self, args):
        self.__doc__ = super().do_repl.__doc__
        try:
            super().do_repl(args)
        except WebSocketConnectionClosedException as e:
            self.printer.print_error("Connection lost to repl")
            self._disconnect()

    @cli_handler
    def do_setup(self, args):
        """setup <CONFIG_FILE>
        Interactive script to populate a config file.
        Switches to new config after finishing setup.
        To keep config persistent after reboots, name it "config.json"
        """
        arg_properties = [
            CLIArgumentProperty(
                str,
                None
            )
        ]
        parsed_args = parse_cli_args(args, 'setup', 1, arg_properties)
        name, = parsed_args
        self.client.setup(name)

    @cli_handler
    def do_set(self, args):
        """set <CONFIG_PARAM> <NEW_VAL>
        Set a parameter in the configuration file to a new value."""
        arg_properties = [
            CLIArgumentProperty(
                str,
                None
            ),
            CLIArgumentProperty(
                str,
                None
            )
        ]
        parsed_args = parse_cli_args(args, 'set', 2, arg_properties)
        key, new_val = parsed_args
        self.client.set(key, new_val)

    def complete_set(self, *args):
        """Tab completion for 'set' command."""
        return [key for key in self.client.prompts.keys() if key.startswith(args[0])]

    def do_configs(self, args):
        """configs
        Print a list of all configuration parameters."""
        self.client.configs()

    @cli_handler
    def do_switch(self, args):
        """switch <CONFIG_FILE>
        Switch to using a different config file."""
        arg_properties = [
            CLIArgumentProperty(
                str,
                None
            )
        ]
        parsed_args = parse_cli_args(args, 'switch', 1, arg_properties)
        name, = parsed_args
        self.client.switch(name)

    def do_i2ctest(self, args):
        """i2ctest
        Scan an i2c bus for i2c device addresses
        """
        self.client.i2ctest()

    def do_bnotest(self, args):
        """bnotest
        Return some diagnostic data that may be helpful in connecting a BNO055 device.
        """
        arg_properties = []
        _ = parse_cli_args(args, 'bnotest', 0, arg_properties)
        self.client.bno_test()

    def do_pwmtest(self, args):
        """pwmtest
        Return some diagnostic data that may be helpful in connecting a PCA9685 device.
        """
        arg_properties = []
        _ = parse_cli_args(args, 'pwmtest', 0, arg_properties)
        self.client.pwm_test()

    def do_calibrate(self, args):
        """calibrate
        Detect IMU calibration status and provide instructions on how to
        calibrate if necessary. If calibration is necessary, wait for the user
        to calibrate the device and cease waiting once all sensors are
        calibrated. Regardless of whether calibration is necessary or not, save
        the calibration profile to the config at the end.
        """
        arg_properties = []
        parsed_args = parse_cli_args(args, 'calibrate', 0, arg_properties)
        self.client.calibrate()

    def complete_switch(self, *args):
        """Tab completion for switch command."""
        try:
            files = self.fe.ls(add_dirs=False)
        except Exception:
            files = []
        return [f for f in files if f.startswith(args[0]) and f.endswith(".json")]

    def _calibration_wait_message(self, gyro_calibrated, accel_calibrated, magnet_calibrated, use_ellipsis=True):
        """
        generate a human-readable message that indicates which components remain
        to be calibrated, e.g. if all the arguments are true, then it should
        return the string "waiting for gyroscope, accelerometer and magnetometer
        to be calibrated...".
        """
        components = ((['gyroscope'] if not gyro_calibrated else []) +
                      (['accelerometer'] if not accel_calibrated else []) +
                      (['magnetometer'] if not magnet_calibrated else []))
        components_list_string = ', '.join(components[:-2] + [" and ".join(components[-2:])])
        if components:
            return ("waiting for " + components_list_string +
                    " to be calibrated" + ("..." if use_ellipsis else ""))
        else:
            return "all components calibrated!"

    def do_save_calibration(self, args):
        """save_calibration
        Save current IMU calibration data to the current configuration.
        """
        self.client.save_calibration()

    def do_upload_calibration(self, args):
        """upload_calibration
        Upload the currently stored calibration data to the connected IMU.
        """
        self.client.upload_calibration()

    @cli_handler
    def do_motortest(self, args):
        """motortest <EL | AZ> <ANGLE>
        Test the motors to plot their accuracy against the measured IMU values.
        """
        arg_properties = [
            CLIArgumentProperty(
                str,
                {
                    'EL', 'AZ'
                }
            ),
            CLIArgumentProperty(
                float,
                None
            )
        ]
        parsed_args = parse_cli_args(args, 'motortest', 2, arg_properties)
        motor, pos = parsed_args
        self.client.motor_test(motor, pos)

    @cli_handler
    def do_startmotion(self, args):
        """startmotion <INITIAL AZIMUTH> <INITIAL ELEVATION>
        Sets the azimuth and elevation to the provided values and enables motion.
        """
        arg_properties = [
            CLIArgumentProperty(
                float,
                None
            ),
            CLIArgumentProperty(
                float,
                None
            )
        ]
        parsed_args = parse_cli_args(args, 'startmotion', 2, arg_properties)
        az, el = parsed_args
        self.client.startmotion(az, el)

    @cli_handler
    def do_elevation(self, args):
        """elevation <ELEVATION>
        Set the elevation to the level given in degrees by the first argument.
        """
        arg_properties = [
            CLIArgumentProperty(
                float,
                None
            )
        ]
        parsed_args = parse_cli_args(args, 'elevation', 1, arg_properties)
        el, = parsed_args
        self.client.elevation(el)

    @cli_handler
    def do_azimuth(self, args):
        """azimuth <AZIMUTH>
        Set the azimuth to the level given in degrees by the first argument.
        """
        # TODO: merge this function with do_elevation in one move command

        arg_properties = [
            CLIArgumentProperty(
                float,
                None
            )
        ]
        parsed_args = parse_cli_args(args, 'azimuth', 1, arg_properties)
        az, = parsed_args
        self.client.azimuth(az)

    @cli_handler
    def do_antkontrol(self, args):
        """antkontrol <start | status>
        Create a new global AntKontrol instance or query the status of an existing one
        """
        arg_properties = [
            CLIArgumentProperty(
                str,
                {
                    'start', 'status'
                }
            )
        ]
        parsed_args = parse_cli_args(args, 'antkontrol', 1, arg_properties)
        mode, = parsed_args
        self.client.antkontrol(mode)

    @cli_handler
    def do_track(self, args):
        """track <SATELLITE_NAME>
        Tracks a satellite across the sky. Satellite data is taken from Active-Space-Stations file from Celestrak."""
        arg_properties = [
            CLIArgumentProperty(
                str,
                None
            )
        ]
        parsed_args = parse_cli_args(args, 'track', 1, arg_properties)
        sat_name, = parsed_args
        self.client.track(sat_name)

    def do_cancel(self, args):
        """cancel
        Cancel tracking mode.
        """
        self.client.cancel()

    def do_wifi(self, args):
        """wifi
        Run the WiFi setup script.
        """
        self.client.wifi_setup()
Пример #9
0
 def wrapper(*args, **kwargs):
     try:
         func(*args, **kwargs)
     except AntennyException as e:
         TerminalPrinter.print_error(e.msg + '\n')
         print_board_error(e)