Ejemplo n.º 1
0
 def cmd_connect(self, *args):
     # Use can enter either a mac address or the device ID from the list
     if len(args[0][0]) < 4 and self.last_scan:
         try:
             dev_id = int(args[0][0]) - 1
             mac_address = self.last_scan.devices[dev_id].addr
         except Exception:
             logger.error('Bad ID / MAC address : {}'.format(args[0][0]))
             return False
     else:
         mac_address = args[0][0]
     self._magic_blue = MagicBlue(mac_address, self._bulb_version)
     self._magic_blue.connect(self.bluetooth_adapter)
     logger.info('Connected')
Ejemplo n.º 2
0
 def cmd_connect(self, args):
     bulb_version = args[1] if len(args) > 1 else self._bulb_version
     # Use can enter either a mac address or the device ID from the list
     if len(args[0]) < 4 and self.last_scan:
         try:
             dev_id = int(args[0]) - 1
             entry = self.last_scan.devices[dev_id]
             mac_address = entry.addr
             addr_type = entry.addrType
         except Exception:
             logger.error('Bad ID / MAC address : {}'.format(args[0]))
             return False
     else:
         addr_type = "public"
         mac_address = args[0]
     magic_blue = MagicBlue(mac_address,
                            version=bulb_version,
                            addr_type=addr_type)
     magic_blue.connect(self.bluetooth_adapter)
     self._bulbs.append(magic_blue)
     logger.info('Connected')
Ejemplo n.º 3
0
 def cmd_connect(self, *args):
     self._magic_blue = MagicBlue(args[0][0])
     self._magic_blue.connect(self.bluetooth_adapter)
     logger.info('Connected : {}'.format(self._magic_blue.is_connected()))
Ejemplo n.º 4
0
class MagicBlueShell:
    def __init__(self, bluetooth_adapter):
        self.available_cmds = [
            {'cmd': 'help', 'func': self.list_commands, 'params': '', 'help': 'Show this help', 'con_required': False},
            {'cmd': 'list_devices', 'func': self.cmd_list_devices, 'params': '', 'help': 'List Bluetooth LE devices in range', 'con_required': False},
            {'cmd': 'connect', 'func': self.cmd_connect, 'params': 'mac_address', 'help': 'Connect to light bulb', 'con_required': False},
            {'cmd': 'disconnect', 'func': self.cmd_disconnect, 'params': '', 'help': 'Disconnect from current light bulb', 'con_required': True},
            {'cmd': 'set_color', 'func': self.cmd_set_color, 'params': 'name|hexvalue', 'help': "Change bulb's color", 'con_required': True},
            {'cmd': 'set_warm_light', 'func': self.cmd_set_warm_light, 'params': 'intensity[0.0-1.0]', 'help': "Set warm light", 'con_required': True},
            {'cmd': 'turn', 'func': self.cmd_turn, 'params': 'on|off', 'help': "Turn on / off the bulb", 'con_required': True},
            {'cmd': 'exit', 'func': self.cmd_exit, 'params': '', 'help': 'Exit the script', 'con_required': False}
        ]
        self.bluetooth_adapter = bluetooth_adapter
        self._magic_blue = None

    def start_interactive_mode(self):
        print('Magic Blue interactive shell v{}'.format(__version__))
        print('Type "help" to see what you can do')

        try:
            str_cmd = ''
            while str_cmd != 'exit':
                str_cmd = input('> ')
                self.exec_cmd(str_cmd)
        except EOFError:  # Catch CTRL+D
            self.cmd_exit()

    def exec_cmd(self, str_cmd):
        cmd = self._get_command(str_cmd)
        if cmd is not None:
            if cmd['con_required'] and not (self._magic_blue and self._magic_blue.is_connected()):
                logger.error('You must be connected to magic blue bulb to run this command')
            else:
                if self._check_args(str_cmd, cmd):
                    cmd['func'](str_cmd.split()[1:])
        else:
            logger.error('Command "{}" is not available. Type "help" to see what you can do'.format(str_cmd.split()[0]))

    def print_usage(self, str_cmd):
        cmd = self._get_command(str_cmd)
        if cmd is not None:
            print('Usage: {} {}'.format(cmd['cmd'], cmd['params']))
        else:
            logger.error('Unknow command {}'.format(str_cmd))
        return False

    def cmd_list_devices(self, *args):
        try:
            service = DiscoveryService(self.bluetooth_adapter)
        except RuntimeError as e:
            logger.error('Problem with the Bluetooth adapter : {}'.format(e))
            return False

        print('Listing Bluetooth LE devices in range. Press CTRL+C to stop searching.')
        print('{: <12} {: <12}'.format('Name', 'Mac address'))
        print('{: <12} {: <12}'.format('----', '-----------'))

        try:
            while 42:
                for device_mac, device_name in service.discover(2).items():
                    print('{: <12} {: <12}'.format(device_name, device_mac))
                print('---------------')
                time.sleep(1)
        except KeyboardInterrupt:
            print('\n')
        except RuntimeError as e:
            logger.error('Error while trying to list bluetooth devices : {}'.format(e))

    def cmd_connect(self, *args):
        self._magic_blue = MagicBlue(args[0][0])
        self._magic_blue.connect(self.bluetooth_adapter)
        logger.info('Connected : {}'.format(self._magic_blue.is_connected()))

    def cmd_disconnect(self, *args):
        self._magic_blue.disconnect()
        self._magic_blue = None

    def cmd_turn(self, *args):
        if args[0][0] == 'on':
            self._magic_blue.turn_on()
        else:
            self._magic_blue.turn_off()

    def cmd_set_color(self, *args):
        color = args[0][0]
        try:
            if color.startswith('#'):
                self._magic_blue.set_color(webcolors.hex_to_rgb(color))
            else:
                self._magic_blue.set_color(webcolors.name_to_rgb(color))
        except ValueError as e:
            logger.error('Invalid color value : {}'.format(str(e)))
            self.print_usage('set_color')

    def cmd_set_warm_light(self, *args):
        try:
            self._magic_blue.set_warm_light(float(args[0][0]))
        except ValueError as e:
            logger.error('Invalid intensity value : {}'.format(str(e)))
            self.print_usage('set_color')

    def list_commands(self, *args):
        print(' ----------------------------')
        print('| List of available commands |')
        print(' ----------------------------')
        print('{: <16}{: <25}{}'.format('COMMAND', 'PARAMETERS', 'DETAILS'))
        print('{: <16}{: <25}{}'.format('-------', '----------', '-------'))
        print('\n'.join(['{: <16}{: <25}{}'.format(command['cmd'], command['params'], command['help']) for command in self.available_cmds]))

    def cmd_exit(self, *args):
        print('Bye !')

    def _check_args(self, str_cmd, cmd):
        expected_nb_args = len(cmd['params'].split())
        args = str_cmd.split()[1:]
        if len(args) != expected_nb_args:
            self.print_usage(str_cmd.split()[0])
            return False
        return True

    def _get_command(self, str_cmd):
        str_cmd = str_cmd.split()[0]
        return next((item for item in self.available_cmds if item['cmd'] == str_cmd), None)
Ejemplo n.º 5
0
class MagicBlueShell:
    class Cmd:
        def __init__(self,
                     cmd_str,
                     func,
                     conn_required,
                     help='',
                     params=None,
                     aliases=None):
            self.cmd_str = cmd_str
            self.func = func
            self.conn_required = conn_required
            self.help = help
            self.params = params or []
            self.aliases = aliases or []

    def __init__(self, bluetooth_adapter, bulb_version=7):
        # List available commands and their usage. 'con_required' define if
        # we need to be connected to a device for the command to run
        self.available_cmds = [
            MagicBlueShell.Cmd('help',
                               self.list_commands,
                               False,
                               help='Show this help'),
            MagicBlueShell.Cmd('list_devices',
                               self.cmd_list_devices,
                               False,
                               help='List Bluetooth LE devices in range',
                               aliases=['ls']),
            MagicBlueShell.Cmd('connect',
                               self.cmd_connect,
                               False,
                               help='Connect to light bulb',
                               params=['mac_address or ID']),
            MagicBlueShell.Cmd('disconnect',
                               self.cmd_disconnect,
                               True,
                               help='Disconnect from current light bulb'),
            MagicBlueShell.Cmd('set_color',
                               self.cmd_set_color,
                               True,
                               help="Change bulb's color",
                               params=['name or hexadecimal value']),
            MagicBlueShell.Cmd('set_warm_light',
                               self.cmd_set_warm_light,
                               True,
                               help='Set warm light',
                               params=['intensity[0.0-1.0]']),
            MagicBlueShell.Cmd('turn',
                               self.cmd_turn,
                               True,
                               help='Turn on / off the bulb',
                               params=['on|off']),
            MagicBlueShell.Cmd('read',
                               self.cmd_read,
                               True,
                               help='Read device_info/datetime from the bulb',
                               params=['name|device_info|date_time']),
            MagicBlueShell.Cmd('exit',
                               self.cmd_exit,
                               False,
                               help='Exit the script')
        ]

        self.bluetooth_adapter = bluetooth_adapter
        self._bulb_version = bulb_version
        self._magic_blue = None
        self._devices = []
        self.last_scan = None

    def start_interactive_mode(self):
        print('Magic Blue interactive shell v{}'.format(__version__))
        print('Type "help" for a list of available commands')

        str_cmd = ''
        while str_cmd != 'exit':
            try:
                str_cmd = input('> ').strip()
                if str_cmd:
                    self.exec_cmd(str_cmd)
            except (EOFError, KeyboardInterrupt):  # Catch Ctrl+D / Ctrl+C
                self.cmd_exit()
                return
            except Exception as e:
                logger.error('Unexpected error with command "{}": {}'.format(
                    str_cmd, str(e)))

    def exec_cmd(self, str_cmd):
        cmd = self._get_command(str_cmd)
        if cmd is not None:
            if cmd.conn_required and not (self._magic_blue
                                          and self._magic_blue.is_connected()):
                logger.error('You must be connected to run this command')
            elif self._check_args(str_cmd, cmd):
                cmd.func(str_cmd.split()[1:])
        else:
            logger.error('"{}" is not a valid command.'
                         'Type "help" to see what you can do'.format(
                             str_cmd.split()[0]))

    def print_usage(self, str_cmd):
        cmd = self._get_command(str_cmd)
        if cmd is not None:
            print('Usage: {} {}'.format(cmd.cmd_str, ' '.join(cmd.params)))
        else:
            logger.error('Unknown command {}'.format(str_cmd))
        return False

    def cmd_list_devices(self, *args):
        scan_time = 300
        try:
            self.last_scan = ScanDelegate()
            scanner = Scanner().withDelegate(self.last_scan)

            print('Listing Bluetooth LE devices in range for {} seconds. '
                  'Press CTRL+C to abort searching.'.format(scan_time))
            print('{: <5} {: <30} {: <12}'.format('ID', 'Name', 'Mac address'))
            print('{: <5} {: <30} {: <12}'.format('--', '----', '-----------'))

            scanner.scan(scan_time)
        except KeyboardInterrupt:
            print('\n')
        except RuntimeError as e:
            logger.error('Problem with the Bluetooth adapter : {}'.format(e))
            return False

    def cmd_connect(self, *args):
        # Use can enter either a mac address or the device ID from the list
        if len(args[0][0]) < 4 and self.last_scan:
            try:
                dev_id = int(args[0][0]) - 1
                entry = self.last_scan.devices[dev_id]
                mac_address = entry.addr
                addr_type = entry.addrType
            except Exception:
                logger.error('Bad ID / MAC address : {}'.format(args[0][0]))
                return False
        else:
            addr_type = None
            mac_address = args[0][0]
        self._magic_blue = MagicBlue(mac_address,
                                     version=self._bulb_version,
                                     addr_type=addr_type)
        self._magic_blue.connect(self.bluetooth_adapter)
        logger.info('Connected')

    def cmd_disconnect(self, *args):
        self._magic_blue.disconnect()
        self._magic_blue = None

    def cmd_turn(self, *args):
        if args[0][0] == 'on':
            self._magic_blue.turn_on()
        else:
            self._magic_blue.turn_off()

    def cmd_read(self, *args):
        if args[0][0] == 'name':
            name = self._magic_blue.get_device_name()
            logger.info('Received name: {}'.format(name))
        elif args[0][0] == 'device_info':
            device_info = self._magic_blue.get_device_info()
            logger.info('Received device_info: {}'.format(device_info))
        elif args[0][0] == 'date_time':
            datetime_ = self._magic_blue.get_date_time()
            logger.info('Received datetime: {}'.format(datetime_))

    def cmd_set_color(self, *args):
        color = args[0][0]
        try:
            if color.startswith('#'):
                self._magic_blue.set_color(webcolors.hex_to_rgb(color))
            else:
                self._magic_blue.set_color(webcolors.name_to_rgb(color))
        except ValueError as e:
            logger.error('Invalid color value : {}'.format(str(e)))
            self.print_usage('set_color')

    def cmd_set_warm_light(self, *args):
        try:
            self._magic_blue.set_warm_light(float(args[0][0]))
        except ValueError as e:
            logger.error('Invalid intensity value : {}'.format(str(e)))
            self.print_usage('set_color')

    def list_commands(self, *args):
        print(' ----------------------------')
        print('| List of available commands |')
        print(' ----------------------------')
        print('{: <16}{: <30}{}'.format('COMMAND', 'PARAMETERS', 'DETAILS'))
        print('{: <16}{: <30}{}'.format('-------', '----------', '-------'))
        for command in self.available_cmds:
            print('{: <16}{: <30}{}'.format(command.cmd_str,
                                            ' '.join(command.params),
                                            command.help))
            for alias in command.aliases:
                print('{: <16}{: <30}{}'.format(alias, '//', '//'))

    def cmd_exit(self, *args):
        print('Bye !')

    def _check_args(self, str_cmd, cmd):
        expected_nb_args = len(cmd.params)
        args = str_cmd.split()[1:]
        if len(args) != expected_nb_args:
            self.print_usage(str_cmd.split()[0])
            return False
        return True

    def _get_command(self, str_cmd):
        str_cmd = str_cmd.split()[0]
        return next((item for item in self.available_cmds
                     if item.cmd_str == str_cmd or str_cmd in item.aliases),
                    None)
Ejemplo n.º 6
0
class MagicBlueShell:
    def __init__(self, bluetooth_adapter):
        self.available_cmds = [{
            'cmd': 'help',
            'func': self.list_commands,
            'params': '',
            'help': 'Show this help',
            'con_required': False
        }, {
            'cmd': 'list_devices',
            'func': self.cmd_list_devices,
            'params': '',
            'help': 'List Bluetooth LE devices in range',
            'con_required': False
        }, {
            'cmd': 'connect',
            'func': self.cmd_connect,
            'params': 'mac_address',
            'help': 'Connect to light bulb',
            'con_required': False
        }, {
            'cmd': 'disconnect',
            'func': self.cmd_disconnect,
            'params': '',
            'help': 'Disconnect from current light bulb',
            'con_required': True
        }, {
            'cmd': 'set_color',
            'func': self.cmd_set_color,
            'params': 'name|hexvalue',
            'help': "Change bulb's color",
            'con_required': True
        }, {
            'cmd': 'set_warm_light',
            'func': self.cmd_set_warm_light,
            'params': 'intensity[0.0-1.0]',
            'help': "Set warm light",
            'con_required': True
        }, {
            'cmd': 'turn',
            'func': self.cmd_turn,
            'params': 'on|off',
            'help': "Turn on / off the bulb",
            'con_required': True
        }, {
            'cmd': 'exit',
            'func': self.cmd_exit,
            'params': '',
            'help': 'Exit the script',
            'con_required': False
        }]
        self.bluetooth_adapter = bluetooth_adapter
        self._magic_blue = None

    def start_interactive_mode(self):
        print('Magic Blue interactive shell v{}'.format(__version__))
        print('Type "help" to see what you can do')

        try:
            str_cmd = ''
            while str_cmd != 'exit':
                str_cmd = input('> ')
                self.exec_cmd(str_cmd)
        except EOFError:  # Catch CTRL+D
            self.cmd_exit()

    def exec_cmd(self, str_cmd):
        cmd = self._get_command(str_cmd)
        if cmd is not None:
            if cmd['con_required'] and not (self._magic_blue and
                                            self._magic_blue.is_connected()):
                logger.error(
                    'You must be connected to magic blue bulb to run this command'
                )
            else:
                if self._check_args(str_cmd, cmd):
                    cmd['func'](str_cmd.split()[1:])
        else:
            logger.error(
                'Command "{}" is not available. Type "help" to see what you can do'
                .format(str_cmd.split()[0]))

    def print_usage(self, str_cmd):
        cmd = self._get_command(str_cmd)
        if cmd is not None:
            print('Usage: {} {}'.format(cmd['cmd'], cmd['params']))
        else:
            logger.error('Unknow command {}'.format(str_cmd))
        return False

    def cmd_list_devices(self, *args):
        try:
            service = DiscoveryService(self.bluetooth_adapter)
        except RuntimeError as e:
            logger.error('Problem with the Bluetooth adapter : {}'.format(e))
            return False

        print(
            'Listing Bluetooth LE devices in range. Press CTRL+C to stop searching.'
        )
        print('{: <12} {: <12}'.format('Name', 'Mac address'))
        print('{: <12} {: <12}'.format('----', '-----------'))

        try:
            while 42:
                for device_mac, device_name in service.discover(2).items():
                    print('{: <12} {: <12}'.format(device_name, device_mac))
                print('---------------')
                time.sleep(1)
        except KeyboardInterrupt:
            print('\n')
        except RuntimeError as e:
            logger.error(
                'Error while trying to list bluetooth devices : {}'.format(e))

    def cmd_connect(self, *args):
        self._magic_blue = MagicBlue(args[0][0])
        self._magic_blue.connect(self.bluetooth_adapter)
        logger.info('Connected : {}'.format(self._magic_blue.is_connected()))

    def cmd_disconnect(self, *args):
        self._magic_blue.disconnect()
        self._magic_blue = None

    def cmd_turn(self, *args):
        if args[0][0] == 'on':
            self._magic_blue.turn_on()
        else:
            self._magic_blue.turn_off()

    def cmd_set_color(self, *args):
        color = args[0][0]
        try:
            if color.startswith('#'):
                self._magic_blue.set_color(webcolors.hex_to_rgb(color))
            else:
                self._magic_blue.set_color(webcolors.name_to_rgb(color))
        except ValueError as e:
            logger.error('Invalid color value : {}'.format(str(e)))
            self.print_usage('set_color')

    def cmd_set_warm_light(self, *args):
        try:
            self._magic_blue.set_warm_light(float(args[0][0]))
        except ValueError as e:
            logger.error('Invalid intensity value : {}'.format(str(e)))
            self.print_usage('set_color')

    def list_commands(self, *args):
        print(' ----------------------------')
        print('| List of available commands |')
        print(' ----------------------------')
        print('{: <16}{: <25}{}'.format('COMMAND', 'PARAMETERS', 'DETAILS'))
        print('{: <16}{: <25}{}'.format('-------', '----------', '-------'))
        print('\n'.join([
            '{: <16}{: <25}{}'.format(command['cmd'], command['params'],
                                      command['help'])
            for command in self.available_cmds
        ]))

    def cmd_exit(self, *args):
        print('Bye !')

    def _check_args(self, str_cmd, cmd):
        expected_nb_args = len(cmd['params'].split())
        args = str_cmd.split()[1:]
        if len(args) != expected_nb_args:
            self.print_usage(str_cmd.split()[0])
            return False
        return True

    def _get_command(self, str_cmd):
        str_cmd = str_cmd.split()[0]
        return next(
            (item for item in self.available_cmds if item['cmd'] == str_cmd),
            None)
Ejemplo n.º 7
0
 def cmd_connect(self, *args):
     self._magic_blue = MagicBlue(args[0][0])
     self._magic_blue.connect(self.bluetooth_adapter)
     logger.info('Connected : {}'.format(self._magic_blue.is_connected()))
Ejemplo n.º 8
0
class MagicBlueShell:
    def __init__(self):
        self.available_cmds = [
            {"cmd": "help", "func": self.list_commands, "params": "", "help": "Show this help", "con_required": False},
            {
                "cmd": "list_devices",
                "func": self.cmd_list_devices,
                "params": "",
                "help": "List Bluetooth LE devices in range",
                "con_required": False,
            },
            {
                "cmd": "connect",
                "func": self.cmd_connect,
                "params": "mac_address",
                "help": "Connect to light bulb",
                "con_required": False,
            },
            {
                "cmd": "disconnect",
                "func": self.cmd_disconnect,
                "params": "",
                "help": "Disconnect from current light bulb",
                "con_required": True,
            },
            {
                "cmd": "set_color",
                "func": self.cmd_set_color,
                "params": "name|hexvalue",
                "help": "Change bulb's color",
                "con_required": True,
            },
            {
                "cmd": "turn",
                "func": self.cmd_turn,
                "params": "on|off",
                "help": "Turn on / off the bulb",
                "con_required": True,
            },
            {"cmd": "exit", "func": self.cmd_exit, "params": "", "help": "Exit the script", "con_required": False},
        ]
        self._magic_blue = None

    def start_interactive_mode(self):
        print("Magic Blue interactive shell v{}".format(__version__))
        print('Type "help" to see what you can do')

        try:
            str_cmd = ""
            while str_cmd != "exit":
                str_cmd = input("> ")
                self.exec_cmd(str_cmd)
        except EOFError:  # Catch CTRL+D
            self.cmd_exit()

    def exec_cmd(self, str_cmd):
        cmd = self._get_command(str_cmd)
        if cmd is not None:
            if cmd["con_required"] and not (self._magic_blue and self._magic_blue.is_connected()):
                logger.error("You must be connected to magic blue bulb to run this command")
            else:
                if self._check_args(str_cmd, cmd):
                    cmd["func"](str_cmd.split()[1:])
        else:
            logger.error('Command "{}" is not available. Type "help" to see what you can do'.format(str_cmd.split()[0]))

    def print_usage(self, str_cmd):
        cmd = self._get_command(str_cmd)
        if cmd is not None:
            print("Usage: {} {}".format(cmd["cmd"], cmd["params"]))
        else:
            logger.error("Unknow command {}".format(str_cmd))
        return False

    def cmd_list_devices(self, *args):
        print("Listing Bluetooth LE devices in range. Press CTRL+C to stop searching.")
        service = DiscoveryService()

        print("{: <12} {: <12}".format("Name", "Mac address"))
        print("{: <12} {: <12}".format("----", "-----------"))

        try:
            while 42:
                for device_mac, device_name in service.discover(2).items():
                    print("{: <12} {: <12}".format(device_name, device_mac))
                print("---------------")
                time.sleep(1)
        except KeyboardInterrupt:
            print("\n")

    def cmd_connect(self, *args):
        self._magic_blue = MagicBlue(args[0][0])
        self._magic_blue.connect()
        logger.info("Connected : {}".format(self._magic_blue.is_connected()))

    def cmd_disconnect(self, *args):
        self._magic_blue.disconnect()
        self._magic_blue = None

    def cmd_turn(self, *args):
        if args[0][0] == "on":
            self._magic_blue.turn_on()
        else:
            self._magic_blue.turn_off()

    def cmd_set_color(self, *args):
        color = args[0][0]
        try:
            if color.startswith("#"):
                self._magic_blue.set_color(webcolors.hex_to_rgb(color))
            else:
                self._magic_blue.set_color(webcolors.name_to_rgb(color))
        except ValueError as e:
            logger.error("Invalid color value : {}".format(str(e)))
            self.print_usage("set_color")

    def list_commands(self, *args):
        print(" ----------------------------")
        print("| List of available commands |")
        print(" ----------------------------")
        print("{: <16}{: <25}{}".format("COMMAND", "PARAMETERS", "DETAILS"))
        print("{: <16}{: <25}{}".format("-------", "----------", "-------"))
        print(
            "\n".join(
                [
                    "{: <16}{: <25}{}".format(command["cmd"], command["params"], command["help"])
                    for command in self.available_cmds
                ]
            )
        )

    def cmd_exit(self, *args):
        print("Bye !")

    def _check_args(self, str_cmd, cmd):
        expected_nb_args = len(cmd["params"].split())
        args = str_cmd.split()[1:]
        if len(args) != expected_nb_args:
            self.print_usage(str_cmd.split()[0])
            return False
        return True

    def _get_command(self, str_cmd):
        str_cmd = str_cmd.split()[0]
        return next((item for item in self.available_cmds if item["cmd"] == str_cmd), None)
Ejemplo n.º 9
0
 def cmd_connect(self, *args):
     self._magic_blue = MagicBlue(args[0][0])
     self._magic_blue.connect()
     logger.info("Connected : {}".format(self._magic_blue.is_connected()))
Ejemplo n.º 10
0
class MagicBlueShell:
    class Cmd:
        def __init__(self, cmd_str, func, conn_required, help='', params=None,
                     aliases=None):
            self.cmd_str = cmd_str
            self.func = func
            self.conn_required = conn_required
            self.help = help
            self.params = params or []
            self.aliases = aliases or []

    def __init__(self, bluetooth_adapter, bulb_version=7):
        # List available commands and their usage. 'con_required' define if
        # we need to be connected to a device for the command to run
        self.available_cmds = [
            MagicBlueShell.Cmd('help', self.list_commands, False,
                               help='Show this help'),
            MagicBlueShell.Cmd('list_devices', self.cmd_list_devices, False,
                               help='List Bluetooth LE devices in range',
                               aliases=['ls']),
            MagicBlueShell.Cmd('connect', self.cmd_connect, False,
                               help='Connect to light bulb',
                               params=['mac_address or ID']),
            MagicBlueShell.Cmd('disconnect', self.cmd_disconnect, True,
                               help='Disconnect from current light bulb'),
            MagicBlueShell.Cmd('set_color', self.cmd_set_color, True,
                               help="Change bulb's color",
                               params=['name or hexadecimal value']),
            MagicBlueShell.Cmd('set_warm_light', self.cmd_set_warm_light, True,
                               help='Set warm light',
                               params=['intensity[0.0-1.0]']),
            MagicBlueShell.Cmd('turn', self.cmd_turn, True,
                               help='Turn on / off the bulb',
                               params=['on|off']),
            MagicBlueShell.Cmd('exit', self.cmd_exit, False,
                               help='Exit the script')
        ]

        self.bluetooth_adapter = bluetooth_adapter
        self._bulb_version = bulb_version
        self._magic_blue = None
        self.last_scan = None

    def start_interactive_mode(self):
        print('Magic Blue interactive shell v{}'.format(__version__))
        print('Type "help" for a list of available commands')

        str_cmd = ''
        while str_cmd != 'exit':
            try:
                str_cmd = input('> ').strip()
                if str_cmd:
                    self.exec_cmd(str_cmd)
            except (EOFError, KeyboardInterrupt):  # Catch Ctrl+D / Ctrl+C
                self.cmd_exit()
                return
            except Exception as e:
                logger.error('Unexpected error with command "{}": {}'
                             .format(str_cmd, str(e)))

    def exec_cmd(self, str_cmd):
        cmd = self._get_command(str_cmd)
        if cmd is not None:
            if cmd.conn_required and not (self._magic_blue and
                                          self._magic_blue.is_connected()):
                logger.error('You must be connected to run this command')
            elif self._check_args(str_cmd, cmd):
                cmd.func(str_cmd.split()[1:])
        else:
            logger.error('"{}" is not a valid command.'
                         'Type "help" to see what you can do'
                         .format(str_cmd.split()[0]))

    def print_usage(self, str_cmd):
        cmd = self._get_command(str_cmd)
        if cmd is not None:
            print('Usage: {} {}'.format(cmd.cmd_str, ' '.join(cmd.params)))
        else:
            logger.error('Unknown command {}'.format(str_cmd))
        return False

    def cmd_list_devices(self, *args):
        try:
            self.last_scan = ScanDelegate()
            scanner = Scanner().withDelegate(self.last_scan)
            print('Listing Bluetooth LE devices in range for 5 minutes.'
                  'Press CTRL+C to stop searching.')
            print('{: <5} {: <30} {: <12}'.format('ID', 'Name', 'Mac address'))
            print('{: <5} {: <30} {: <12}'.format('--', '----', '-----------'))
            scanner.scan(350)
        except KeyboardInterrupt:
            print('\n')
        except RuntimeError as e:
            logger.error('Problem with the Bluetooth adapter : {}'.format(e))
            return False

    def cmd_connect(self, *args):
        # Use can enter either a mac address or the device ID from the list
        if len(args[0][0]) < 4 and self.last_scan:
            try:
                dev_id = int(args[0][0]) - 1
                mac_address = self.last_scan.devices[dev_id].addr
            except Exception:
                logger.error('Bad ID / MAC address : {}'.format(args[0][0]))
                return False
        else:
            mac_address = args[0][0]
        self._magic_blue = MagicBlue(mac_address, self._bulb_version)
        self._magic_blue.connect(self.bluetooth_adapter)
        logger.info('Connected')

    def cmd_disconnect(self, *args):
        self._magic_blue.disconnect()
        self._magic_blue = None

    def cmd_turn(self, *args):
        if args[0][0] == 'on':
            self._magic_blue.turn_on()
        else:
            self._magic_blue.turn_off()

    def cmd_set_color(self, *args):
        color = args[0][0]
        try:
            if color.startswith('#'):
                self._magic_blue.set_color(webcolors.hex_to_rgb(color))
            else:
                self._magic_blue.set_color(webcolors.name_to_rgb(color))
        except ValueError as e:
            logger.error('Invalid color value : {}'.format(str(e)))
            self.print_usage('set_color')

    def cmd_set_warm_light(self, *args):
        try:
            self._magic_blue.set_warm_light(float(args[0][0]))
        except ValueError as e:
            logger.error('Invalid intensity value : {}'.format(str(e)))
            self.print_usage('set_color')

    def list_commands(self, *args):
        print(' ----------------------------')
        print('| List of available commands |')
        print(' ----------------------------')
        print('{: <16}{: <30}{}'.format('COMMAND', 'PARAMETERS', 'DETAILS'))
        print('{: <16}{: <30}{}'.format('-------', '----------', '-------'))
        for command in self.available_cmds:
            print('{: <16}{: <30}{}'.format(
                    command.cmd_str, ' '.join(command.params), command.help))
            for alias in command.aliases:
                print('{: <16}{: <30}{}'.format(alias, '//', '//'))

    def cmd_exit(self, *args):
        print('Bye !')

    def _check_args(self, str_cmd, cmd):
        expected_nb_args = len(cmd.params)
        args = str_cmd.split()[1:]
        if len(args) != expected_nb_args:
            self.print_usage(str_cmd.split()[0])
            return False
        return True

    def _get_command(self, str_cmd):
        str_cmd = str_cmd.split()[0]
        return next((item for item in self.available_cmds
                     if item.cmd_str == str_cmd or str_cmd in item.aliases
                     ), None)