class ArduinoApi(FirmwareClass):

    """access to Arduino public API functions."""
    firmware_id = 'A'

    LOW, HIGH = 0, 1
    INPUT, OUTPUT, INPUT_PULLUP = 0, 1, 2
    LSBFIRST, MSBFIRST = 0, 1

    @arduinomethod('dw')
    def digitalWrite(self, pin, value):
        pass

    @returns(int)
    @arduinomethod('r')
    def digitalRead(self, pin):
        pass

    @arduinomethod('aw')
    def analogWrite(self, pin, value):
        pass

    @returns(int)
    @arduinomethod('a')
    def analogRead(self, pin):
        pass

    @arduinomethod('pm')
    def pinMode(self, pin, mode):
        pass

    @returns(int)
    @arduinomethod('m')
    def millis(self):
        pass

    @returns(int)
    @arduinomethod('pi')
    def pulseIn(self, pin, value):
        pass
        
    @returns(int)
    @arduinomethod('s')
    def shiftOut(self, dataPin, clockPin, bitOrder, value):
        """Shifts out a byte of data one bit at a time.

        :param dataPin: the pin on which to output each bit (int)
        :param clockPin: the pin to toggle once the dataPin has been set to the correct value (int)
        :param bitOrder: which order to shift out the bits; either MSBFIRST or LSBFIRST.
        :param value: the data to shift out. (byte)

        http://arduino.cc/en/Reference/shiftOut

        """ class ArduinoCore(FirmwareClass):

    """Access to Arduino functions which are not part of the public API.
    AVR only. SAM has different interface. 
    """

    cfg_h_name = 'USE_ArduinoCore'
    firmware_id = 'Core'

    @memoized
    @returns(int)
    @arduinomethod
    def digitalPinToBitMask(self, pin):
        """nonpublic Arduino function."""

    @memoized
    @returns(int)
    @arduinomethod
    def digitalPinToPort(self, pin):
        """nonpublic Arduino function."""

    @memoized
    @returns(int)
    @arduinomethod
    def digitalPinToTimer(self, pin):
        """nonpublic Arduino function."""

    @memoized
    @returns(int)
    @arduinomethod
    def analogInPinToBit(self, pin):
        """nonpublic Arduino function."""

    @memoized
    @returns(int)
    @arduinomethod
    def portOutputRegister(self, port):
        """nonpublic Arduino function.

        @return: address

        """

    @memoized
    @returns(int)
    @arduinomethod
    def portInputRegister(self, port):
        """nonpublic Arduino function.

        @return: address

        """

    @memoized
    @returns(int)
    @arduinomethod
    def portModeRegister(self, port):
        """nonpublic Arduino function.

        @return: address

        """ class PinFeature(object):

    def __init__(self, define, register, core, ram, api):
        self.A0 = define.get('A0')
        self.define = define
        self.register = register
        self.core = core
        self.ram = ram
        self.api = api

    @memoized
    def get(self, name):
        return (
            ArduinoPin(
                name,
                self.count,
                self.define,
                self.register,
                self.core,
                self.ram,
                self.api)
        )

    @property
    @memoized
    def count_analog(self):
        """Count of analog pins."""
        return len(self.names_analog)

    @property
    @memoized
    def count_digital(self):
        """Count of digital pins."""
        return len(self.names_digital)

    @property
    @memoized
    def count(self):
        """Count of all pins.
        """
        return self.define.get('NUM_DIGITAL_PINS')

    @property
    def names(self):
        """List of all pin names."""
        return self.names_digital + self.names_analog

    @property
    def names_digital(self):
        """List of digital pin names."""
        A0 = self.A0
        return ['D%s' % x for x in range(0, A0)]

    @property
    def names_analog(self):
        """List of analog pin names."""
        A0 = self.A0
        return (
            ['A%s' % (x - A0) for x in range(A0, self.count)]
        ) class ClassInfo(object):

    """Which classes are compiled into the firmware?"""

    def __init__(self, connection):
        self.firmware_class_status = dict()
        self.unknown_firmware_ids = []
        self.firmware_name_list = []
        
        self._arr = ClassInfoArray(connection=connection)

        ls = [self._arr.name(i) for i in range(self._arr.count)]
        assert len(ls)
        self.firmware_id_list = sorted(ls)

        for cls in check4firmware.names.values():
            self.firmware_class_status[cls.__name__] = False

        for x in self.firmware_id_list:
            cls = check4firmware.names.get(x)
            if cls:
                self.firmware_name_list.append(str(cls.__name__))
                self.firmware_class_status[cls.__name__] = True
            else:
                self.firmware_name_list.append(str(x))
                self.unknown_firmware_ids.append(x) class FirmwareMissingFeatureError(Exception):
    pass class Counter(object):

    """Access to counter.

    Examples:

        Counter.read()

    original lib: http://www.pjrc.com/teensy/td_libs_FreqCount.html

    """
    def __init__(self, connection=None, F_CPU=None):
        self.connection = connection
        self.lib = CounterLib(connection)
        self.multiplier = 1
        if F_CPU:
            self.multiplier = self.calculate_multiplier(F_CPU)

    @classmethod
    def calculate_multiplier(cls, F_CPU):
        multiplier = 1
        # calculate multiplier for non supported frequencies
        if F_CPU >= 16000000:
            multiplier = F_CPU / 16000000
        elif F_CPU >= 8000000:
            multiplier = F_CPU / 8000000
        elif F_CPU >= 4000000:
            multiplier = F_CPU / 4000000
        elif F_CPU >= 2000000:
            multiplier = F_CPU / 2000000
        elif F_CPU >= 1000000:
            multiplier = F_CPU / 1000000
        else:
            multiplier = 1000000 / F_CPU
        return multiplier

    def read(self, gate_time=1):
        """read pulse count under ``gate_time`` seconds."""
        gate_time_ms = int(1000 * gate_time)
        self.lib.begin(gate_time_ms)
        try:
            time.sleep(gate_time * 1.1)
            n = self.lib.read()
        finally:
            self.lib.end()
        return n * self.multiplier

    def read_frequency(self, gate_time=1):
        """read frequency in hertz using ``gate_time`` in seconds."""
        return self.read(gate_time) / gate_time

    def error(self, gate_time=1):
        """frequency measurement error in hertz using ``gate_time`` in
        seconds."""
        return self.multiplier / gate_time class DefineFeature(object):

    """Access to firmware constants.

    Examples:

        a=ArduinoTree()
        a.define.get('F_CPU') # AVR frequency
        a.define.get('__DATE__') # firmware upload date

    """

    def __init__(self, connection=None):
        self._arr = DefineArray(connection)

    @property
    @memoized
    def as_dict(self):
        """return all constants and their values in a dictionary."""
        d = dict()
        for i in range(self._arr.count):
            d[self._arr.name(i)] = self._arr.value(i)
        assert len(d)
        return d

    @property
    def names(self):
        return sorted(self.as_dict.keys())

    @property
    @memoized
    def count(self):
        return len(self.as_dict)

    def get(self, name, defaultValue=None):
        """get a constant.

        Examples:

            a=ArduinoTree()
            a.define.get('F_CPU') # AVR frequency

        """
        count = self._arr.count
        assert count
        for i in range(count):
            n = self._arr.name(i)
            if n == name:
                return self._arr.value(i)
        return defaultValue class EEPROM(FirmwareClass):
    cfg_h_name = 'USE_EEPROM'
    firmware_id = 'EEPROM'

    @arduinomethod
    def write(self, address, value):
        pass

    @returns(int)
    @arduinomethod
    def read(self, address):
        pass

    @property
    @memoized
    @returns(int)
    @arduinomethod
    def size(self):
        pass def firmware_info(define_dict):
    d = dict()
    d['avr_name'] = avr_name(define_dict['MCU'])
    d['arduino_version'] = arduino_version(define_dict['ARDUINO'])
    d['compile_datetime'] = compile_datetime(define_dict['__DATE__'],
                                             define_dict['__TIME__'],
                                             )
    d['gcc_version'] = define_dict['__VERSION__']
    d['libc_version'] = libc_version(define_dict['__AVR_LIBC_VERSION__'])
    d['libc_date'] = libc_date(define_dict['__AVR_LIBC_DATE_'])

    return d class RAM(FirmwareClass):
    cfg_h_name = 'USE_RAM'
    firmware_id = 'RAM'

    @arduinomethod
    def write(self, address, value):
        pass

    @returns(int)
    @arduinomethod
    def read(self, address):
        pass

    @property
    @memoized
    @returns(int)
    @arduinomethod
    def size(self):
        pass

    @returns(int)
    @arduinomethod
    def free(self):
        pass class RegisterFeature(object):

    def __init__(self, connection=None):
        self._arr = RegisterArray(connection)

    @property
    @memoized
    def name_index_dict(self):
        d = dict()
        for i in range(self._arr.count()):
            d[self._arr.name(i)] = i
        assert len(d)
        return d

    @property
    def names(self):
        """Get all register names as a sorted list."""
        return sorted(self.name_index_dict.keys())

    @property
    @memoized
    def count(self):
        """Get register count."""
        return len(self.name_index_dict.keys())

    @memoized
    def get(self, name):
        try:
            index = self.name_index_dict[name]
        except KeyError:
            raise ValueError('Unknown register: %s' % name)
        return Register(index, self._arr)

    @property
    @memoized
    def proxy(self):
        return RegisterProxy(self._arr, self.name_index_dict) class Watchdog(FirmwareClass):

    """Direct access to watchdog timer.

    Details: http://www.nongnu.org/avr-libc/user-manual/group__avr__watchdog.html

    """
    cfg_h_name = 'USE_Watchdog'

    WDTO_15MS = 0
    WDTO_30MS = 1
    WDTO_60MS = 2
    WDTO_120MS = 3
    WDTO_250MS = 4
    WDTO_500MS = 5
    WDTO_1S = 6
    WDTO_2S = 7
    WDTO_4S = 8
    WDTO_8S = 9

    @arduinomethod
    def reset(self):
        """Reset the watchdog timer. (wdt_reset)

        When the watchdog timer is enabled,
        a call to this instruction is required before the timer expires,
        otherwise a watchdog-initiated device reset will occur.

        """

    @arduinomethod
    def enable(self, timeout):
        """Enable the watchdog timer, configuring it for expiry after
        timeout. (wdt_enable)"""

    @arduinomethod
    def disable(self):
        """Disable the watchdog timer, if possible. (wdt_disable)""" class Wire(FirmwareClass):

    """access to Wire library
    http://arduino.cc/en/reference/wire
    """
    firmware_id = 'Wire'
    cfg_h_name = 'USE_Wire'

    @arduinomethod
    def begin(self, address=None):
        """Initiate the Wire library and join the I2C bus as a master or slave.
        This should normally be called only once.

        :param address: the 7-bit slave address (optional); if not specified,
            join the bus as a master.
        :returns: None

        http://arduino.cc/en/Reference/WireBegin

        """

    @returns(int)
    @arduinomethod
    def requestFrom(self, address, quantity, stop=True):
        """Used by the master to request bytes from a slave device. The bytes
        may then be retrieved with the available() and read() functions.

        :param address: the 7-bit address of the device to request bytes from
        :param quantity: the number of bytes to request
        :param stop: boolean. true will send a stop message after the request,
            releasing the bus. false will continually send a restart after the request,
            keeping the connection active.
        :returns: byte : the number of bytes returned from the slave device

        http://arduino.cc/en/Reference/WireRequestFrom

        """

    @arduinomethod
    def beginTransmission(self, address):
        """Begin a transmission to the I2C slave device with the given address.
        Subsequently, queue bytes for transmission with the write() function
        and transmit them by calling endTransmission().

        :param address: the 7-bit address of the device to transmit to
        :returns: None

        http://arduino.cc/en/Reference/WireBeginTransmission

        """

    @returns(int)
    @arduinomethod
    def endTransmission(self, stop=True):
        """Ends a transmission to a slave device that was begun by
        beginTransmission() and transmits the bytes that were queued by
        write().

        :param stop: boolean. true will send a stop message,
            releasing the bus after transmission. false will send a restart,
            keeping the connection active.
        :returns: byte, which indicates the status of the transmission:
            0:success
            1:data too long to fit in transmit buffer
            2:received NACK on transmit of address
            3:received NACK on transmit of data
            4:other error

        http://arduino.cc/en/Reference/WireEndTransmission

        """

    @returns(int)
    @arduinomethod
    def write(self, value):
        """Writes data from a slave device in response to a request from a
        master, or queues bytes for transmission from a master to slave device.

        (in-between calls to beginTransmission() and endTransmission()).

        :param value: a value to send as a single byte
        :returns: byte: write() will return the number of bytes written

        http://arduino.cc/en/Reference/WireWrite

        """

    @returns(int)
    @arduinomethod
    def available(self, pin, value):
        """Returns the number of bytes available for retrieval with read().
        This should be called on a master device after a call to requestFrom()
        or on a slave inside the onReceive() handler.

        :returns: The number of bytes available for reading.

        http://arduino.cc/en/Reference/WireAvailable

        """

    @returns(int)
    @arduinomethod
    def read(self):
        """Reads a byte that was transmitted from a slave device to a master
        after a call to requestFrom() or was transmitted from a master to a
        slave.

        :returns: The next byte received

        http://arduino.cc/en/Reference/WireRead

        """ class Esp(FirmwareClass):
    firmware_id = 'Esp'

    @arduinomethod
    def wdtEnable(self):
        pass

    @arduinomethod
    def wdtDisable(self):
        pass

    @arduinomethod
    def wdtFeed(self):
        pass

    @arduinomethod
    def reset(self):
        pass

    @arduinomethod
    def restart(self):
        pass

    @returns(int)
    @arduinomethod
    def getVcc(self):
        pass

    @arduinomethod
    def getFreeHeap(self):
        pass

    @arduinomethod
    def getChipId(self):
        pass

    @arduinomethod
    def getSdkVersion(self):
        pass

    @arduinomethod
    def getBootVersion(self):
        pass

    @arduinomethod
    def getBootMode(self):
        pass

    @arduinomethod
    def getCpuFreqMHz(self):
        pass

    @arduinomethod
    def getFlashChipId(self):
        pass

    @arduinomethod
    def getFlashChipRealSize(self):
        pass

    @arduinomethod
    def getFlashChipSize(self):
        pass

    @arduinomethod
    def getFlashChipSpeed(self):
        pass

    @arduinomethod
    def getFlashChipMode(self):
        pass

    @arduinomethod
    def getFlashChipSizeByChipId(self):
        pass

    @arduinomethod
    def getResetReason(self):
        pass

    @arduinomethod
    def getResetInfo(self):
        pass

    @arduinomethod
    def getSketchSize(self):
        pass

    @arduinomethod
    def getFreeSketchSpace(self):
        pass

    @arduinomethod
    def flashEraseSector(self):
        pass

    @arduinomethod
    def flashWrite(self):
        pass

    @arduinomethod
    def flashRead(self):
        pass
from nanpy.arduinoapi import ArduinoApi
from nanpy.arduinocore import ArduinoCore
from nanpy.arduinopin import PinFeature
from nanpy.classinfo import ClassInfo, FirmwareMissingFeatureError
from nanpy.counter import Counter
from nanpy.define import DefineFeature
from nanpy.eeprom import EEPROM
from nanpy.fwinfo import firmware_info
from nanpy.memo import memoized
from nanpy.ram import RAM
from nanpy.register import RegisterFeature
from nanpy.serialmanager import serial_manager
from nanpy.vcc import Vcc
from nanpy.watchdog import Watchdog
from nanpy.wire import Wire
from nanpy.esp import Esp
import time


class ArduinoTree(object):

    """Object tree model of an Arduino.

    Examples:

        a=ArduinoTree()
        a.pin.get(9).read_digital_value()

    """

    def __init__(self, connection=None):
        ''
        self.connection = connection
        if not connection:
            self.connection = serial_manager
        if not hasattr(self.connection, 'classinfo'):
            self.connection.classinfo = ClassInfo(self.connection)

    @property
    @memoized
    def api(self):
        """Access to Arduino API."""
        return ArduinoApi(self.connection)

    @property
    @memoized
    def pin(self):
        """Object-oriented representation of an Arduino pin"""
#         if not self.register:
#             return None
#         if not self.define:
#             return None
#         if not self.core:
#             return None
#         if not self.ram:
#             return None
        return PinFeature(self.define, self.register, self.core, self.ram, self.api)

    @property
    @memoized
    def define(self):
        """Access to firmware constants."""
        return DefineFeature(self.connection)

    @property
    @memoized
    def register(self):
        """Direct access to AVR registers."""
        try:
            return RegisterFeature(self.connection)
        except FirmwareMissingFeatureError:
            return None

    @property
    @memoized
    def watchdog(self):
        """Direct access to watchdog timer."""
        return Watchdog(self.connection)

    @property
    @memoized
    def eeprom(self):
        """Access to EEPROM."""
        return EEPROM(self.connection)

    @property
    @memoized
    def ram(self):
        """Access to RAM."""
        try:
            return RAM(self.connection)
        except FirmwareMissingFeatureError:
            return None

    @property
    @memoized
    def counter(self):
        """Access to counter."""
        return Counter(self.connection, F_CPU=self.define.get('F_CPU'))

    @property
    @memoized
    def core(self):
        """Access to Arduino functions which are not part of the public API."""
        try:
            return ArduinoCore(self.connection)
        except FirmwareMissingFeatureError:
            return None

    @property
    @memoized
    def vcc(self):
        """Access to VCC."""
        if not self.register:
            return None
        if not self.define:
            return None
        return Vcc(self.register, MCU=self.define.get('MCU'))

    def soft_reset(self):
        """Resets the AVR, the registers will be reset to their known, default
        settings.

        Details: http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_softreset

        """
        self.watchdog.enable(self.watchdog.WDTO_60MS)
        time.sleep(0.2)

        # TODO: after restart the first read is not correct (why?)
        # This command helps cleaning the read buffer
        self.connection.flush_input()

    @property
    @memoized
    def wire(self):
        """Access to Wire."""

        return Wire(self.connection)