Ejemplo n.º 1
0
    def start(self, callback = None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_start"):
            callback.on_start(self)
            
        self._node = Node(self._vendor_id, self._product_id)
        _logger.debug("Manager: Node initialized")

        self._status_msg("Request basic information...")
        m = self._node.request_message(Message.ID.RESPONSE_VERSION)
        self._status_msg("  ANT version:   {}", struct.unpack("<10sx", m[2])[0])
        m = self._node.request_message(Message.ID.RESPONSE_CAPABILITIES)
        self._status_msg("  Capabilities:  {}", m[2])
        m = self._node.request_message(Message.ID.RESPONSE_SERIAL_NUMBER)
        self._status_msg("  Serial number: {}", struct.unpack("<I", m[2])[0])
        _logger.debug("Application: retrieved basic info")

        self._status_msg("Starting system...")

        NETWORK_KEY= [0xa8, 0xa4, 0x23, 0xb9, 0xf5, 0x5e, 0x63, 0xc1]

        self._node.reset_system()
        self._node.set_network_key(0x00, NETWORK_KEY)

        self._channel = self._node.new_channel(Channel.Type.BIDIRECTIONAL_RECEIVE)
        self._channel.on_broadcast_data = self._on_data
        self._channel.on_burst_data = self._on_data
        self._setup_channel()
            
        self._worker_thread = threading.Thread(target=self._node.start, name="ant.fs")
        self._worker_thread.start()
        
        if callback and hasattr(callback, "started"):
            callback.started(self)
Ejemplo n.º 2
0
    def start(self, callback=None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_start"):
            callback.on_start(self)

        self._node = Node(self._vendor_id, self._product_id)
        _logger.debug("Manager: Node initialized")

        self._status_msg("Request basic information...")
        m = self._node.request_message(Message.ID.RESPONSE_VERSION)
        self._status_msg("  ANT version:   {}",
                         struct.unpack("<10sx", m[2])[0])
        m = self._node.request_message(Message.ID.RESPONSE_CAPABILITIES)
        self._status_msg("  Capabilities:  {}", m[2])
        m = self._node.request_message(Message.ID.RESPONSE_SERIAL_NUMBER)
        self._status_msg("  Serial number: {}", struct.unpack("<I", m[2])[0])
        _logger.debug("Application: retrieved basic info")

        self._status_msg("Starting system...")

        NETWORK_KEY = [0xa8, 0xa4, 0x23, 0xb9, 0xf5, 0x5e, 0x63, 0xc1]

        self._node.reset_system()
        self._node.set_network_key(0x00, NETWORK_KEY)

        self._channel = self._node.new_channel(
            Channel.Type.BIDIRECTIONAL_RECEIVE)
        self._channel.on_broadcast_data = self._on_data
        self._channel.on_burst_data = self._on_data
        self._setup_channel()

        self._worker_thread = threading.Thread(target=self._node.start,
                                               name="ant.fs")
        self._worker_thread.start()

        if callback and hasattr(callback, "started"):
            callback.started(self)
Ejemplo n.º 3
0
class Manager(object):
    _vendor_id  = 0x0fcf
    _vendor_name = "ant.py"
    _product_id = 0x1008
    _product_name = "ant-fs manager"
    _serial_number = 1337
    
    _frequency     = 19    # 0 to 124, x - 2400 (in MHz)
    
    def __init__(self, **kwargs):
        _logger.debug("Initializing ANT-FS Manager")
        self._statuslogger = kwargs.get("statuslogger")
        self._callback = kwargs.get("callback")
        if (not self._statuslogger and 
                self._callback and
                hasattr(self._callback, "status_message")):
            self._statuslogger = self._callback
        self._status_msg("Initializing ANT-FS Manager")
            
        if self._callback and hasattr(self._callback, "get_ant_product_ids"):
            ids = self._callback.get_ant_product_ids(self)
            if len(ids) == 5:
                (self._vendor_id, self._product_id,
                 self._vendor_name, self._product_name,
                 self._serial_number) = ids
            elif len(ids) == 3:
                (self._vendor_name, self._product_name,
                 self._serial_number) = ids
            
        _logger.debug("Vendor: %x (%s)", self._vendor_id, self._vendor_name)
        _logger.debug("Product: %x (%s)", self._product_id, self._product_name)
        _logger.debug("Serial#: %x", self._serial_number)
        
        self.keep_alive = kwargs.get("keep_alive", True)
        _logger.debug("Keep-alive: %s", self.keep_alive)
        
        self._timer = None
        if self.keep_alive:
            self._timer_lock = threading.RLock()

        self._lock = threading.RLock()
        self._queue = Queue.Queue()
        self._beacons = Queue.Queue()

        
    #===========================================================================
    # P R I V A T E  M E T H O D S
    #===========================================================================
    
    def _status_msg(self, msg, *args):
        if self._statuslogger:
            self._statuslogger.status_message(msg, *args)
        
    def _on_beacon(self, data):
        b = Beacon.parse(data)
        self._beacons.put(b)

    def _on_command(self, data):
        c = sweattrails.device.ant.fs.command.parse(data)
        self._queue.put(c)

    def _on_data(self, data):
        #print "_on_data", data, len(data)
        if data[0] == 0x43:
            self._on_beacon(data[:8])
            if len(data[8:]) > 0:
                self._on_command(data[8:])
        elif data[0] == 0x44:
            self._on_command(data)
    
    def _get_beacon(self):
        b = self._beacons.get()
        self._beacons.task_done()
        return b
    
    def _get_command(self, timeout=3.0):
        _logger.debug("Get command, t%d, s%d", timeout, self._queue.qsize())
        c = self._queue.get(True, timeout)
        self._queue.task_done()
        return c
    
    def _send_command(self, c, ignore_timeout = False):
        _logger.debug("Sending '%s' command", c.__class__.__name__)
        data = c.get()
        if len(data) <= 8:
            self._channel.send_acknowledged_data(data, ignore_timeout)
        else:
            self._channel.send_burst_transfer(data)
            
    def _setup_channel(self):
        # FIXME: Make channel params configurable by callback
        self._channel.set_period(4096)
        self._channel.set_search_timeout(255)
        self._channel.set_rf_freq(50)
        self._channel.set_search_waveform([0x53, 0x00])
        self._channel.set_id(0, 0x01, 0)
        
        self._channel.open()
        #channel.request_message(Message.ID.RESPONSE_CHANNEL_STATUS)
        self._status_msg("Searching...")

    def _keep_alive(self, ping_now = True):
        if self.keep_alive:
            _logger.debug("Sending keep-alive Ping command")
            with self._timer_lock:
                self._timer = None
                if ping_now:
                    self.ping()
                self._timer = threading.Timer(1, self._keep_alive)
                self._timer.start()
        
    def _cancel_keep_alive(self):
        if self.keep_alive:
            _logger.debug("Canceling keep-alive timer")
            with self._timer_lock:
                if self._timer:
                    self._timer.cancel()
                    self._timer = None
    
    #===========================================================================
    # P U B L I C  M E T H O D S
    #===========================================================================
    
    def authenticate(self, callback = None):
        callback = callback or self._callback
        if hasattr(callback, "on_authentication"):
            callback.on_authentication(self)
        if hasattr(callback, "get_ant_serial_number"):
            self._serial_number = callback.get_ant_serial_number(self)
        self._peer_serial, self._peer_name = self.authentication_serial()
        passkey = callback.get_passkey(self, self._peer_serial)
        self._status_msg("Authenticating with {} ({})...", self._peer_name, self._peer_serial)
        _logger.debug("serial %s, %r, %r", self._peer_name, self._peer_serial, passkey)
        
        if passkey is not None:
            self._status_msg("Authenticating with {} ({})... Passkey... ", self._peer_name, self._peer_serial)
            try:
                self.authentication_passkey(passkey)
                self._status_msg("Authenticating with {} ({})... Passkey... OK.", self._peer_name, self._peer_serial)
            except sweattrails.device.ant.fs.manager.AntFSAuthenticationException:
                self._status_msg("Authenticating with {} ({})... Passkey... FAILED", self._peer_name, self._peer_serial)
                raise
        else:
            self.log("Authenticating with {} ({})... Pairing... ", self._peer_name, self._peer_serial)
            try:
                passkey = self.authentication_pair(callback.get_ant_product_name())
                callback.set_passkey(self, self._peer_serial, passkey)
                self._status_msg("Authenticating with {} ({})... Pairing... OK.", self._peer_name, self._peer_serial)
            except sweattrails.device.ant.fs.manager.AntFSAuthenticationException:
                self._status_msg("Authenticating with {} ({})... Pairing... FAILED", self._peer_name, self._peer_serial)
                raise
        if hasattr(callback, "authenticated"):
            callback.authenticated(self)

    def connect(self, callback = None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_connect"):
            callback.on_connect(self)
        try:
            self._beacon = self._get_beacon()
            if self.link(callback):
                _logger.debug("connect: Link established")
                for _ in range(0, 5):
                    self._beacon = self._get_beacon()
                    if self._beacon.get_client_device_state() == Beacon.ClientDeviceState.AUTHENTICATION:
                        _logger.debug("connect: Beacon found")
                        self.authenticate(callback)
                        self._beacon = self._get_beacon()
                        self._keep_alive(False)
                        if callback and hasattr(callback, "connected"):
                            callback.connected(self)
                        return
            raise AntConnectionException()
        except:
            _logger.exception("ANT Manager connect")
            raise
        
    def start(self, callback = None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_start"):
            callback.on_start(self)
            
        self._node = Node(self._vendor_id, self._product_id)
        _logger.debug("Manager: Node initialized")

        self._status_msg("Request basic information...")
        m = self._node.request_message(Message.ID.RESPONSE_VERSION)
        self._status_msg("  ANT version:   {}", struct.unpack("<10sx", m[2])[0])
        m = self._node.request_message(Message.ID.RESPONSE_CAPABILITIES)
        self._status_msg("  Capabilities:  {}", m[2])
        m = self._node.request_message(Message.ID.RESPONSE_SERIAL_NUMBER)
        self._status_msg("  Serial number: {}", struct.unpack("<I", m[2])[0])
        _logger.debug("Application: retrieved basic info")

        self._status_msg("Starting system...")

        NETWORK_KEY= [0xa8, 0xa4, 0x23, 0xb9, 0xf5, 0x5e, 0x63, 0xc1]

        self._node.reset_system()
        self._node.set_network_key(0x00, NETWORK_KEY)

        self._channel = self._node.new_channel(Channel.Type.BIDIRECTIONAL_RECEIVE)
        self._channel.on_broadcast_data = self._on_data
        self._channel.on_burst_data = self._on_data
        self._setup_channel()
            
        self._worker_thread = threading.Thread(target=self._node.start, name="ant.fs")
        self._worker_thread.start()
        
        if callback and hasattr(callback, "started"):
            callback.started(self)

    def stop(self, callback = None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_stop"):
            callback.on_stop(self)
        self._node.stop()
        if callback and hasattr(callback, "stopped"):
            callback.stopped(self)
    
    def erase(self, index):
        pass
    
    def upload(self, index, data):
        pass
    
    def download(self, index, callback = None):
        callback = callback or self._callback
        offset  = 0
        crc     = 0
        data    = array.array('B')
        with self._lock:
            while True:
                _logger.debug("Download %d, o%d, c%d", index, offset, crc)
                self._send_command(DownloadRequest(index, offset, True, crc))
                _logger.debug("Wait for response...")
                try:
                    response = self._get_command()
                    if response._get_argument("response") == DownloadResponse.Response.OK:
                        _logger.debug("Response OK")
                        remaining    = response._get_argument("remaining")
                        offset       = response._get_argument("offset")
                        total        = offset + remaining
                        size         = response._get_argument("size")
                        if debug_protocol:
                            _logger.debug("remaining %d offset %d total %d size %d", remaining, offset, total, response._get_argument("size"))
                        data[offset:total] = response._get_argument("data")[:remaining]
                        if callback != None:
                            progress = int((float(total) / float(response._get_argument("size"))) * 100)
                            if hasattr(callback, "progress"):
                                callback.progress(progress)
                            else:
                                callback(progress)
                        if total == size:
                            return data
                        crc = response._get_argument("crc")
                        if debug_protocol:
                            _logger.debug("crc: %d", crc)
                        offset = total
                    else:
                        raise AntFSDownloadException(response._get_argument("response"))
                except Queue.Empty:
                    _logger.debug("Download %d timeout", index)
                except AntFSException:
                    raise
                except:
                    _logger.exception("Exception in download. Attempting to recover...")
    
    def download_directory(self, callback = None):
        data = self.download(0, callback)
        return Directory.parse(data)
    
    def ping(self):
        """
            Pings the device. Used by the keep-alive thread.
        """
        with self._lock:
            self._send_command(PingCommand())

    def get_beacon(self):
        return self._beacon
    
    def get_channel(self):
        return self._channel

    def link(self, callback = None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_link"):
            callback.on_link(self)
        with self._lock:
            self._channel.request_message(Message.ID.RESPONSE_CHANNEL_ID)
            self._send_command(LinkCommand(self._frequency, 4, self._serial_number))
           
            # New period, search timeout
            self._channel.set_period(4096)
            self._channel.set_search_timeout(3)
            self._channel.set_rf_freq(self._frequency)
        if callback and hasattr(callback, "linked"):
            callback.on_linked(self)
        return True

    def authentication_serial(self):
        with self._lock:
            self._send_command(AuthenticateCommand(
                    AuthenticateCommand.Request.SERIAL,
                    self._serial_number))
            response = self._get_command()
            return (response.get_serial(), response.get_data_string())

    def authentication_passkey(self, passkey):
        with self._lock:
            self._send_command(AuthenticateCommand(
                    AuthenticateCommand.Request.PASSKEY_EXCHANGE,
                    self._serial_number, passkey))
    
            response = self._get_command()
            if response._get_argument("type") == AuthenticateResponse.Response.ACCEPT:
                return response.get_data_array()
            else:
                raise AntFSAuthenticationException(response._get_argument("type"))

    def authentication_pair(self, friendly_name):
        with self._lock:
            data = array.array('B', map(ord, list(friendly_name)))
            self._send_command(AuthenticateCommand(
                    AuthenticateCommand.Request.PAIRING,
                    self._serial_number, data))
    
            response = self._get_command(30)
            if response._get_argument("type") == AuthenticateResponse.Response.ACCEPT:
                return response.get_data_array()
            else:
                raise AntFSAuthenticationException(response._get_argument("type"))

    def disconnect(self, callback = None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_disconnect"):
            callback.on_disconnect(self)
        self._cancel_keep_alive()
        with self._lock:
            d = DisconnectCommand(DisconnectCommand.Type.RETURN_LINK, 0, 0)
            self._send_command(d, True)
        if callback and hasattr(callback, "disconnected"):
            callback.disconnected(self)
Ejemplo n.º 4
0
class Manager(object):
    _vendor_id = 0x0fcf
    _vendor_name = "ant.py"
    _product_id = 0x1009
    _product_name = "ant-fs manager"
    _serial_number = 1337

    _frequency = 19  # 0 to 124, x - 2400 (in MHz)

    def __init__(self, **kwargs):
        _logger.debug("Initializing ANT-FS Manager")
        self._statuslogger = kwargs.get("statuslogger")
        self._callback = kwargs.get("callback")
        if (not self._statuslogger and self._callback
                and hasattr(self._callback, "status_message")):
            self._statuslogger = self._callback
        self._status_msg("Initializing ANT-FS Manager")

        if self._callback and hasattr(self._callback, "get_ant_product_ids"):
            ids = self._callback.get_ant_product_ids(self)
            if len(ids) == 5:
                (self._vendor_id, self._product_id, self._vendor_name,
                 self._product_name, self._serial_number) = ids
            elif len(ids) == 3:
                (self._vendor_name, self._product_name,
                 self._serial_number) = ids

        _logger.debug("Vendor: %x (%s)", self._vendor_id, self._vendor_name)
        _logger.debug("Product: %x (%s)", self._product_id, self._product_name)
        _logger.debug("Serial#: %x", self._serial_number)

        self.keep_alive = kwargs.get("keep_alive", True)
        _logger.debug("Keep-alive: %s", self.keep_alive)

        self._timer = None
        if self.keep_alive:
            self._timer_lock = threading.RLock()

        self._lock = threading.RLock()
        self._queue = queue.Queue()
        self._beacons = queue.Queue()

    #===========================================================================
    # P R I V A T E  M E T H O D S
    #===========================================================================

    def _status_msg(self, msg, *args):
        if self._statuslogger:
            self._statuslogger.status_message(msg, *args)

    def _on_beacon(self, data):
        b = Beacon.parse(data)
        self._beacons.put(b)

    def _on_command(self, data):
        c = sweattrails.device.ant.fs.command.parse(data)
        self._queue.put(c)

    def _on_data(self, data):
        #print "_on_data", data, len(data)
        if data[0] == 0x43:
            self._on_beacon(data[:8])
            if len(data[8:]) > 0:
                self._on_command(data[8:])
        elif data[0] == 0x44:
            self._on_command(data)

    def _get_beacon(self):
        b = self._beacons.get()
        self._beacons.task_done()
        return b

    def _get_command(self, timeout=3.0):
        _logger.debug("Get command, t%d, s%d", timeout, self._queue.qsize())
        c = self._queue.get(True, timeout)
        self._queue.task_done()
        return c

    def _send_command(self, c, ignore_timeout=False):
        _logger.debug("Sending '%s' command", c.__class__.__name__)
        data = c.get()
        if len(data) <= 8:
            self._channel.send_acknowledged_data(data, ignore_timeout)
        else:
            self._channel.send_burst_transfer(data)

    def _setup_channel(self):
        # FIXME: Make channel params configurable by callback
        self._channel.set_period(4096)
        self._channel.set_search_timeout(255)
        self._channel.set_rf_freq(50)
        self._channel.set_search_waveform([0x53, 0x00])
        self._channel.set_id(0, 0x01, 0)

        self._channel.open()
        #channel.request_message(Message.ID.RESPONSE_CHANNEL_STATUS)
        self._status_msg("Searching...")

    def _keep_alive(self, ping_now=True):
        if self.keep_alive:
            _logger.debug("Sending keep-alive Ping command")
            with self._timer_lock:
                self._timer = None
                if ping_now:
                    self.ping()
                self._timer = threading.Timer(1, self._keep_alive)
                self._timer.start()

    def _cancel_keep_alive(self):
        if self.keep_alive:
            _logger.debug("Canceling keep-alive timer")
            with self._timer_lock:
                if self._timer:
                    self._timer.cancel()
                    self._timer = None

    #===========================================================================
    # P U B L I C  M E T H O D S
    #===========================================================================

    def authenticate(self, callback=None):
        callback = callback or self._callback
        if hasattr(callback, "on_authentication"):
            callback.on_authentication(self)
        if hasattr(callback, "get_ant_serial_number"):
            self._serial_number = callback.get_ant_serial_number(self)
        self._peer_serial, self._peer_name = self.authentication_serial()
        passkey = callback.get_passkey(self, self._peer_serial)
        self._status_msg("Authenticating with {} ({})...", self._peer_name,
                         self._peer_serial)
        _logger.debug("serial %s, %r, %r", self._peer_name, self._peer_serial,
                      passkey)

        if passkey is not None:
            self._status_msg("Authenticating with {} ({})... Passkey... ",
                             self._peer_name, self._peer_serial)
            try:
                self.authentication_passkey(passkey)
                self._status_msg(
                    "Authenticating with {} ({})... Passkey... OK.",
                    self._peer_name, self._peer_serial)
            except sweattrails.device.ant.fs.manager.AntFSAuthenticationException:
                self._status_msg(
                    "Authenticating with {} ({})... Passkey... FAILED",
                    self._peer_name, self._peer_serial)
                raise
        else:
            self._status_msg("Authenticating with {} ({})... Pairing... ",
                             self._peer_name, self._peer_serial)
            try:
                passkey = self.authentication_pair(self._product_name)
                callback.set_passkey(self, self._peer_serial, passkey)
                self._status_msg(
                    "Authenticating with {} ({})... Pairing... OK.",
                    self._peer_name, self._peer_serial)
            except sweattrails.device.ant.fs.manager.AntFSAuthenticationException:
                self._status_msg(
                    "Authenticating with {} ({})... Pairing... FAILED",
                    self._peer_name, self._peer_serial)
                raise
        if hasattr(callback, "authenticated"):
            callback.authenticated(self)

    def connect(self, callback=None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_connect"):
            callback.on_connect(self)
        try:
            self._beacon = self._get_beacon()
            if self.link(callback):
                _logger.debug("connect: Link established")
                for _ in range(0, 5):
                    self._beacon = self._get_beacon()
                    if self._beacon.get_client_device_state(
                    ) == Beacon.ClientDeviceState.AUTHENTICATION:
                        _logger.debug("connect: Beacon found")
                        self.authenticate(callback)
                        self._beacon = self._get_beacon()
                        self._keep_alive(False)
                        if callback and hasattr(callback, "connected"):
                            callback.connected(self)
                        return
            raise AntConnectionException()
        except:
            _logger.exception("ANT Manager connect")
            raise

    def start(self, callback=None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_start"):
            callback.on_start(self)

        self._node = Node(self._vendor_id, self._product_id)
        _logger.debug("Manager: Node initialized")

        self._status_msg("Request basic information...")
        m = self._node.request_message(Message.ID.RESPONSE_VERSION)
        self._status_msg("  ANT version:   {}",
                         struct.unpack("<10sx", m[2])[0])
        m = self._node.request_message(Message.ID.RESPONSE_CAPABILITIES)
        self._status_msg("  Capabilities:  {}", m[2])
        m = self._node.request_message(Message.ID.RESPONSE_SERIAL_NUMBER)
        self._status_msg("  Serial number: {}", struct.unpack("<I", m[2])[0])
        _logger.debug("Application: retrieved basic info")

        self._status_msg("Starting system...")

        NETWORK_KEY = [0xa8, 0xa4, 0x23, 0xb9, 0xf5, 0x5e, 0x63, 0xc1]

        self._node.reset_system()
        self._node.set_network_key(0x00, NETWORK_KEY)

        self._channel = self._node.new_channel(
            Channel.Type.BIDIRECTIONAL_RECEIVE)
        self._channel.on_broadcast_data = self._on_data
        self._channel.on_burst_data = self._on_data
        self._setup_channel()

        self._worker_thread = threading.Thread(target=self._node.start,
                                               name="ant.fs")
        self._worker_thread.start()

        if callback and hasattr(callback, "started"):
            callback.started(self)

    def stop(self, callback=None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_stop"):
            callback.on_stop(self)
        if hasattr(self, "_node"):
            self._node.stop()
        if callback and hasattr(callback, "stopped"):
            callback.stopped(self)

    def erase(self, index):
        pass

    def upload(self, index, data):
        pass

    def download(self, index, callback=None):
        callback = callback or self._callback
        offset = 0
        crc = 0
        data = array.array('B')
        with self._lock:
            while True:
                _logger.debug("Download %d, o%d, c%d", index, offset, crc)
                self._send_command(DownloadRequest(index, offset, True, crc))
                _logger.debug("Wait for response...")
                try:
                    response = self._get_command()
                    if response._get_argument(
                            "response") == DownloadResponse.Response.OK:
                        _logger.debug("Response OK")
                        remaining = response._get_argument("remaining")
                        offset = response._get_argument("offset")
                        total = offset + remaining
                        size = response._get_argument("size")
                        if debug_protocol:
                            _logger.debug(
                                "remaining %d offset %d total %d size %d",
                                remaining, offset, total,
                                response._get_argument("size"))
                        data[offset:total] = response._get_argument(
                            "data")[:remaining]
                        if callback != None:
                            progress = int(
                                (float(total) /
                                 float(response._get_argument("size"))) * 100)
                            if hasattr(callback, "progress"):
                                callback.progress(progress)
                            else:
                                callback(progress)
                        if total == size:
                            return data
                        crc = response._get_argument("crc")
                        if debug_protocol:
                            _logger.debug("crc: %d", crc)
                        offset = total
                    else:
                        raise AntFSDownloadException(
                            response._get_argument("response"))
                except Queue.Empty:
                    _logger.debug("Download %d timeout", index)
                except AntFSException:
                    raise
                except:
                    _logger.exception(
                        "Exception in download. Attempting to recover...")

    def download_directory(self, callback=None):
        data = self.download(0, callback)
        return Directory.parse(data)

    def ping(self):
        """
            Pings the device. Used by the keep-alive thread.
        """
        with self._lock:
            self._send_command(PingCommand())

    def get_beacon(self):
        return self._beacon

    def get_channel(self):
        return self._channel

    def link(self, callback=None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_link"):
            callback.on_link(self)
        with self._lock:
            self._channel.request_message(Message.ID.RESPONSE_CHANNEL_ID)
            self._send_command(
                LinkCommand(self._frequency, 4, self._serial_number))

            # New period, search timeout
            self._channel.set_period(4096)
            self._channel.set_search_timeout(3)
            self._channel.set_rf_freq(self._frequency)
        if callback and hasattr(callback, "linked"):
            callback.on_linked(self)
        return True

    def authentication_serial(self):
        with self._lock:
            self._send_command(
                AuthenticateCommand(AuthenticateCommand.Request.SERIAL,
                                    self._serial_number))
            response = self._get_command()
            return (response.get_serial(), response.get_data_string())

    def authentication_passkey(self, passkey):
        with self._lock:
            self._send_command(
                AuthenticateCommand(
                    AuthenticateCommand.Request.PASSKEY_EXCHANGE,
                    self._serial_number, passkey))

            response = self._get_command()
            if response._get_argument(
                    "type") == AuthenticateResponse.Response.ACCEPT:
                return response.get_data_array()
            else:
                raise AntFSAuthenticationException(
                    response._get_argument("type"))

    def authentication_pair(self, friendly_name):
        with self._lock:
            data = array.array('B', map(ord, list(friendly_name)))
            self._send_command(
                AuthenticateCommand(AuthenticateCommand.Request.PAIRING,
                                    self._serial_number, data))

            response = self._get_command(30)
            if response._get_argument(
                    "type") == AuthenticateResponse.Response.ACCEPT:
                return response.get_data_array()
            else:
                raise AntFSAuthenticationException(
                    response._get_argument("type"))

    def disconnect(self, callback=None):
        callback = callback or self._callback
        if callback and hasattr(callback, "on_disconnect"):
            callback.on_disconnect(self)
        self._cancel_keep_alive()
        with self._lock:
            d = DisconnectCommand(DisconnectCommand.Type.RETURN_LINK, 0, 0)
            self._send_command(d, True)
        if callback and hasattr(callback, "disconnected"):
            callback.disconnected(self)