class Channel:
    class Type:
        BIDIRECTIONAL_RECEIVE = 0x00
        BIDIRECTIONAL_TRANSMIT = 0x10

        SHARED_BIDIRECTIONAL_RECEIVE = 0x20
        SHARED_BIDIRECTIONAL_TRANSMIT = 0x30

        UNIDIRECTIONAL_RECEIVE_ONLY = 0x40
        UNIDIRECTIONAL_TRANSMIT_ONLY = 0x50

    def __init__(self, id, node, ant):
        self.id = id
        self._node = node
        self._ant = ant

    def wait_for_event(self, ok_codes):
        return wait_for_event(ok_codes, self._node._events, self._node._event_cond)

    def wait_for_response(self, event_id):
        return wait_for_response(
            event_id, self._node._responses, self._node._responses_cond
        )

    def wait_for_special(self, event_id):
        return wait_for_special(
            event_id, self._node._responses, self._node._responses_cond
        )

    def _assign(self, channelType, networkNumber, ext_assign):
        self._ant.assign_channel(self.id, channelType, networkNumber, ext_assign)
        return self.wait_for_response(Message.ID.ASSIGN_CHANNEL)

    def _unassign(self):
        pass

    def open(self):
        self._ant.open_channel(self.id)
        return self.wait_for_response(Message.ID.OPEN_CHANNEL)

    def open_rx_scan_mode(self):
        self._ant.open_rx_scan_mode()
        return self.wait_for_response(Message.ID.OPEN_RX_SCAN_MODE)

    def close(self):
        self._ant.close_channel(self.id)
        return self.wait_for_response(Message.ID.CLOSE_CHANNEL)

    def set_id(self, deviceNum, deviceType, transmissionType):
        self._ant.set_channel_id(self.id, deviceNum, deviceType, transmissionType)
        return self.wait_for_response(Message.ID.SET_CHANNEL_ID)

    def set_period(self, messagePeriod):
        self._ant.set_channel_period(self.id, messagePeriod)
        return self.wait_for_response(Message.ID.SET_CHANNEL_PERIOD)

    def set_search_timeout(self, timeout):
        self._ant.set_channel_search_timeout(self.id, timeout)
        return self.wait_for_response(Message.ID.SET_CHANNEL_SEARCH_TIMEOUT)

    def set_rf_freq(self, rfFreq):
        self._ant.set_channel_rf_freq(self.id, rfFreq)
        return self.wait_for_response(Message.ID.SET_CHANNEL_RF_FREQ)

    def enable_extended_messages(self, enable):
        self._ant.enable_extended_messages(self.id, enable)
        return self.wait_for_response(Message.ID.ENABLE_EXT_RX_MESGS)

    def set_search_waveform(self, waveform):
        self._ant.set_search_waveform(self.id, waveform)
        return self.wait_for_response(Message.ID.SET_SEARCH_WAVEFORM)

    def request_message(self, messageId):
        _logger.debug("requesting message %#02x", messageId)
        self._ant.request_message(self.id, messageId)
        _logger.debug("done requesting message %#02x", messageId)
        return self.wait_for_special(messageId)

    def send_broadcast_data(self, data):
        _logger.debug("send broadcast data %s", self.id)
        self._ant.send_broadcast_data(self.id, data)

    def send_acknowledged_data(self, data):
        try:
            _logger.debug("send acknowledged data %s", self.id)
            self._ant.send_acknowledged_data(self.id, data)
            self.wait_for_event([Message.Code.EVENT_TRANSFER_TX_COMPLETED])
            _logger.debug("done sending acknowledged data %s", self.id)
        except TransferFailedException:
            _logger.warning("failed to send acknowledged data %s, retrying", self.id)
            self.send_acknowledged_data(data)

    def send_burst_transfer_packet(self, channelSeq, data, first):
        _logger.debug("send burst transfer packet %s", data)
        self._ant.send_burst_transfer_packet(channelSeq, data, first)

    def send_burst_transfer(self, data):
        try:
            _logger.debug("send burst transfer %s", self.id)
            self._ant.send_burst_transfer(self.id, data)
            self.wait_for_event([Message.Code.EVENT_TRANSFER_TX_START])
            self.wait_for_event([Message.Code.EVENT_TRANSFER_TX_COMPLETED])
            _logger.debug("done sending burst transfer %s", self.id)
        except TransferFailedException:
            _logger.warning("failed to send burst transfer %s, retrying", self.id)
            self.send_burst_transfer(data) class Node:
    def __init__(self):
    def new_channel(self, ctype, network_number=0x00, ext_assign=None):
    def request_message(self, messageId):
    def set_network_key(self, network, key):
    def wait_for_event(self, ok_codes):
    def wait_for_response(self, event_id):
    def wait_for_special(self, event_id):
    def _worker_response(self, channel, event, data):
    def _worker_event(self, channel, event, data):
    def _worker(self):
    def _main(self):
    def start(self):
    def stop(self): class Beacon:
    class ClientDeviceState:
        LINK = 0x00  # 0b0000
        AUTHENTICATION = 0x01  # 0b0001
        TRANSPORT = 0x02  # 0b0010
        BUSY = 0x03  # 0b0011

    BEACON_ID = 0x43

    def __init__(self, status_byte_1, status_byte_2, authentication_type, descriptor):
        self._status_byte_1 = status_byte_1
        self._status_byte_2 = status_byte_2
        self._authentication_type = authentication_type
        self._descriptor = descriptor

    def is_data_available(self):
        return bool(self._status_byte_1 & 0x20)  # 0b00100000

    def is_upload_enabled(self):
        return bool(self._status_byte_1 & 0x10)  # 0b00010000

    def is_pairing_enabled(self):
        return bool(self._status_byte_1 & 0x08)  # 0b00001000

    def get_channel_period(self):
        return self._status_byte_1 & 0x07  # 0b00000111, TODO

    def get_client_device_state(self):
        return self._status_byte_2 & 0x0F  # 0b00001111, TODO

    def get_serial(self):
        return struct.unpack("<I", self._descriptor)[0]

    def get_descriptor(self):
        return struct.unpack("<HH", self._descriptor)

    @staticmethod
    def parse(data):
        mark, status_byte_1, status_byte_2, authentication_type = struct.unpack(
            "<BBBB4x", data
        )

        assert mark == Beacon.BEACON_ID

        return Beacon(status_byte_1, status_byte_2, authentication_type, data[4:]) class LinkCommand(Command):
    _id = Command.Type.LINK
    _format = Command._format + "BBI"

    def __init__(self, channel_frequency, channel_period, host_serial_number):
        Command.__init__(self)
        self._add_argument("channel_frequency", channel_frequency)
        self._add_argument("channel_period", channel_period)
        self._add_argument("host_serial_number", host_serial_number) class DownloadRequest(Command):
    _id = Command.Type.DOWNLOAD_REQUEST
    _format = Command._format + "HIx?HI"

    def __init__(
        self, data_index, data_offset, initial_request, crc_seed, maximum_block_size=0
    ):
        Command.__init__(self)
        self._add_argument("data_index", data_index)
        self._add_argument("data_offset", data_offset)
        self._add_argument("initial_request", initial_request)
        self._add_argument("crc_seed", crc_seed)
        self._add_argument("maximum_block_size", maximum_block_size) class DownloadResponse(Command):
    class Response:
        OK = 0
        NOT_EXIST = 1
        NOT_READABLE = 2
        NOT_READY = 3
        INVALID_REQUEST = 4
        INCORRECT_CRC = 5

    _id = Command.Type.DOWNLOAD_RESPONSE
    _format = None

    def __init__(self, response, remaining, offset, size, data, crc):
        Command.__init__(self)
        self._add_argument("response", response)
        self._add_argument("remaining", remaining)
        self._add_argument("offset", offset)
        self._add_argument("size", size)
        self._add_argument("data", data)
        self._add_argument("crc", crc)

    @classmethod
    def _parse_args(cls, data):
        if data[2] == DownloadResponse.Response.OK:
            return (
                struct.unpack("<BBBxIII", data[0:16])
                + (data[16:-8],)
                + struct.unpack("<6xH", data[-8:])
            )
        else:
            return (
                struct.unpack("<BBBxIII", data[0:16]) + (array.array("B", []),) + (0,)
            ) class AuthenticateCommand(AuthenticateBase):
    class Request:
        PASS_THROUGH = 0
        SERIAL = 1
        PAIRING = 2
        PASSKEY_EXCHANGE = 3

    _id = Command.Type.AUTHENTICATE

    def __init__(self, command_type, host_serial_number, data=[]):
        AuthenticateBase.__init__(self, command_type, host_serial_number, data) class AuthenticateResponse(AuthenticateBase):
    class Response:
        NOT_AVAILABLE = 0
        ACCEPT = 1
        REJECT = 2

    _id = Command.Type.AUTHENTICATE_RESPONSE

    def __init__(self, response_type, client_serial_number, data=[]):
        AuthenticateBase.__init__(self, response_type, client_serial_number, data) class DisconnectCommand(Command):
    class Type:
        RETURN_LINK = 0
        RETURN_BROADCAST = 1

    _id = Command.Type.DISCONNECT
    _format = Command._format + "BBBxxx"

    def __init__(self, command_type, time_duration, application_specific_duration):
        Command.__init__(self)
        self._add_argument("command_type", command_type)
        self._add_argument("time_duration", time_duration)
        self._add_argument(
            "application_specific_duration", application_specific_duration
        ) class UploadRequest(Command):
    _id = Command.Type.UPLOAD_REQUEST
    _format = Command._format + "HI4xI"

    def __init__(self, data_index, max_size, data_offset):
        Command.__init__(self)
        self._add_argument("data_index", data_index)
        self._add_argument("max_size", max_size)
        self._add_argument("data_offset", data_offset) class UploadResponse(Command):
    class Response:
        OK = 0
        NOT_EXIST = 1
        NOT_WRITEABLE = 2
        NOT_ENOUGH_SPACE = 3
        INVALID_REQUEST = 4
        NOT_READY = 5

    _id = Command.Type.UPLOAD_RESPONSE
    _format = Command._format + "BxIII6xH"

    def __init__(
        self, response, last_data_offset, maximum_file_size, maximum_block_size, crc
    ):
        Command.__init__(self)
        self._add_argument("response", response)
        self._add_argument("last_data_offset", last_data_offset)
        self._add_argument("maximum_file_size", maximum_file_size)
        self._add_argument("maximum_block_size", maximum_block_size)
        self._add_argument("crc", crc) class UploadDataCommand(Command):
    _id = Command.Type.UPLOAD_DATA
    _format = None

    def __init__(self, crc_seed, data_offset, data, crc):
        Command.__init__(self)
        self._add_argument("crc_seed", crc_seed)
        self._add_argument("data_offset", data_offset)
        self._add_argument("data", data)
        self._add_argument("crc", crc)

    def get(self):
        arguments = list(self._get_arguments())
        header = struct.pack("<BBHI", *arguments[:4])
        footer = struct.pack("<6xH", self._get_argument("crc"))
        data = array.array("B", header)
        data.extend(self._get_argument("data"))
        data.extend(array.array("B", footer))
        return data

    @classmethod
    def _parse_args(cls, data):
        return (
            struct.unpack("<BBHI", data[0:8])
            + (data[8:-8],)
            + struct.unpack("<6xH", data[-8:])
        ) class UploadDataResponse(Command):
    class Response:
        OK = 0
        FAILED = 1

    _id = Command.Type.UPLOAD_DATA_RESPONSE
    _format = Command._format + "B5x"

    def __init__(self, response):
        Command.__init__(self)
        self._add_argument("response", response) class EraseRequestCommand(Command):
    _id = Command.Type.ERASE_REQUEST
    _format = Command._format + "I2x"

    def __init__(self, data_file_index):
        Command.__init__(self)
        self._add_argument("data_file_index", data_file_index) class EraseResponse(Command):
    class Response:
        ERASE_SUCCESSFUL = 0
        ERASE_FAILED = 1
        NOT_READY = 2

    _id = Command.Type.ERASE_RESPONSE
    _format = Command._format + "B5x"

    def __init__(self, response):
        Command.__init__(self)
        self._add_argument("response", response) class CreateFile(Request):
    _id = CommandPipe.Type.CREATE_FILE
    _format = None

    def __init__(self, size, data_type, identifier, identifier_mask):
        CommandPipe.__init__(self)
        self._add_argument("size", size)
        self._add_argument("data_type", data_type)
        self._add_argument("identifier", identifier)
        self._add_argument("identifier_mask", identifier_mask)

    def get(self):
        arguments = list(self._get_arguments())
        data = array.array("B", struct.pack(CommandPipe._format + "IB", *arguments[:4]))
        data.extend(self._get_argument("identifier"))
        data.extend([0])
        data.extend(self._get_argument("identifier_mask"))
        return data

    @classmethod
    def _parse_args(cls, data):
        return (
            struct.unpack(Command._format + "IB", data[0:9])
            + (data[9:12],)
            + (data[13:16],)
        ) class Response(CommandPipe):
    class Response:
        OK = 0
        FAILED = 1
        REJECTED = 2
        NOT_SUPPORTED = 3

    _id = CommandPipe.Type.RESPONSE
    _format = CommandPipe._format + "BxBx"

    def __init__(self, request_id, response):
        CommandPipe.__init__(self)
        self._add_argument("request_id", request_id)
        self._add_argument("response", response) class Time(CommandPipe):
    class Format:
        DIRECTORY = 0
        SYSTEM = 1
        COUNTER = 2

    _id = CommandPipe.Type.TIME
    _format = CommandPipe._format + "IIBxxx"

    def __init__(self, current_time, system_time, time_format):
        CommandPipe.__init__(self)
        self._add_argument("current_time", current_time)
        self._add_argument("system_time", system_time)
        self._add_argument("time_format", time_format) class TimeResponse(Response):
    _format = Response._format + "xxxxxxxx"

    def __init__(self, request_id, response):
        Response.__init__(self, request_id, response) class Directory:
    def __init__(self, version, time_format, current_system_time, last_modified, files):
        self._version = version
        self._time_format = time_format
        self._current_system_time = current_system_time
        self._last_modified = last_modified
        self._files = files

    def get_version(self):
        return self._version

    def get_time_format(self):
        return self._time_format

    def get_current_system_time(self):
        return self._current_system_time

    def get_last_modified(self):
        return self._last_modified

    def get_files(self):
        return self._files

    def print_list(self):
        print("Index\tType\tFIT Type\tFIT Number\tSize\tDate\tFIT Flags\tFlags")
        for f in self.get_files():
            print(
                f.get_index(),
                "\t",
                f.get_type(),
                "\t",
                f.get_fit_sub_type(),
                "\t",
                f.get_fit_file_number(),
                "\t",
                f.get_size(),
                "\t",
                f.get_date(),
                "\t",
                f._typ_flags,
                "\t",
                f.get_flags_string(),
            )

    @staticmethod
    def parse(data):
        _logger.debug("Parse '%s' as directory", data)

        # Header
        (
            version,
            structure_length,
            time_format,
            current_system_time,
            last_modified,
        ) = struct.unpack("<BBB5xII", data[:16])

        version_major = (version & 0xF0) >> 4
        version_minor = version & 0x0F

        files = []
        for offset in range(16, len(data), 16):
            item_data = data[offset : offset + 16]
            _logger.debug(
                " - (%d - %d) %d, %s", offset, offset + 16, len(item_data), item_data
            )
            files.append(File.parse(item_data))
        return Directory(
            (version_major, version_minor),
            time_format,
            current_system_time,
            last_modified,
            files,
        ) def crc(data, seed=0x0000):
    rem = seed
    for byte in data:
        rem ^= byte
        for _ in range(0, 8):
            if rem & 0x0001:
                rem = rem >> 1
                rem ^= 0xA001
            else:
                rem = rem >> 1

    return rem
import array
import datetime
import logging
import threading
import queue
import ant.fs.command
from ant.easy.channel import Channel
from ant.easy.node import Node, Message
from ant.fs.beacon import Beacon
from ant.fs.command import (
    LinkCommand,
    DownloadRequest,
    DownloadResponse,
    AuthenticateCommand,
    AuthenticateResponse,
    DisconnectCommand,
    UploadRequest,
    UploadResponse,
    UploadDataCommand,
    UploadDataResponse,
    EraseRequestCommand,
    EraseResponse,
)
from ant.fs.commandpipe import CreateFile, Response, Time, TimeResponse
from ant.fs.file import Directory
from ant.fs.commons import crc
            _logger.debug("Link level")
            beacon = self._get_beacon()
            if self.on_link(beacon):
                for i in range(0, 5):
                    beacon = self._get_beacon()
                    if (
                        beacon.get_client_device_state()
                        == Beacon.ClientDeviceState.AUTHENTICATION
                    ):
                        _logger.debug("Auth layer")
                        if self.on_authentication(beacon):
                            _logger.debug("Authenticated")
                            beacon = self._get_beacon()
                            self.on_transport(beacon)
                        self.disconnect()
                        break
        finally:
            _logger.debug("Run 5")
            self.stop()

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

    def _on_command(self, data):
        c = 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=15.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):
        data = c.get()
        if len(data) == 8:
            self._channel.send_acknowledged_data(data)
        else:
            self._channel.send_burst_transfer(data)

    # Application actions are defined from here
    # =======================================================================

    # These should be overloaded:

    def setup_channel(self, channel):
        pass

    def on_link(self, beacon):
        pass

    def on_authentication(self, beacon):
        pass

    def on_transport(self, beacon):
        pass

    # Shouldn't have to touch these:

    def start(self):
        self._main()

    def stop(self):
        self._node.stop()

    def _send_commandpipe(self, data):
        # print "send commandpipe", data
        self.upload(0xFFFE, data)

    def _get_commandpipe(self):
        # print "get commandpipe"
        return ant.fs.commandpipe.parse(self.download(0xFFFE))

    def create(self, typ, data, callback=None):
        # print "create", typ
        request = CreateFile(len(data), 0x80, [typ, 0x00, 0x00], [0x00, 0xFF, 0xFF])
        self._send_commandpipe(request.get())
        result = self._get_commandpipe()
        # result._debug()

        if result.get_response() != Response.Response.OK:
            raise AntFSCreateFileException(
                "Could not create file", result.get_response()
            )

        # print "create result", result, result.get_index(), result.get_data_type(), result.get_identifier()
        # d = self.download_directory()

        # Inform the application that the upload request was successfully created
        if callback is not None:
            callback(0)

        self.upload(result.get_index(), data, callback)
        return result.get_index()

    def upload(self, index, data, callback=None):
        # print "upload", index, len(data)

        iteration = 0
        while True:

            # Request Upload

            # Continue using Last Data Offset (special MAX_ULONG value)
            request_offset = 0 if iteration == 0 else 0xFFFFFFFF

            self._send_command(UploadRequest(index, len(data), request_offset))