Exemple #1
0
class WiSUN(object):
    STATE_ERROR = const(-1)
    STATE_INITIALIZING = const(0)
    STATE_SCANNING = const(1)
    STATE_CONNECTING = const(2)
    STATE_CONNECTED = const(3)

    @staticmethod
    def __thread_proc(obj: WiSUN) -> None:
        runner = obj.__run()  # type: SleepAwaitable
        try:
            while True:
                next(runner)
                runner.send(None)
        except StopIteration:
            pass
        print('WiSUN thread stopped')

    def __init__(self, uart: machine.UART, wake_up: Optional[machine.Pin],
                 reset: machine.Pin):
        self.__lock = _thread.allocate_lock()
        self.__thread = None
        self.__bp35 = BP35A1(uart, wake_up, reset)
        self.__state = WiSUN.STATE_INITIALIZING
        self.__l = logging.Logger('WiSUN')
        self.__route_b_id = None  # type: Optional[str]
        self.__route_b_password = None  # type: Optional[str]
        self.__instant_power = None  # type: Optional[float]
        self.__cumulative_power = None  # type: Optional[float]
        self.__timestamp = None  # type: Optional[float]
        self.__wait = WaitEvent()
        self.__update_interval = 30

    def start(self,
              route_b_id: str,
              route_b_password: str,
              update_interval: int = 30) -> None:
        self.__route_b_id = route_b_id
        self.__route_b_password = route_b_password
        self.__update_interval = update_interval
        self.__thread = _thread.start_new_thread(WiSUN.__thread_proc, (self, ))

    def __make_values(self) -> Dict[str, Any]:
        return {
            'instant_power': self.__instant_power,
            'cumulative_power': self.__cumulative_power,
            'timestamp': self.__timestamp,
            'state': self.__state,
        }

    def __notify_values(self) -> None:
        values = self.__make_values()
        self.__wait.notify(values)

    def state(self) -> int:
        self.__lock.acquire()
        value = self.__state
        self.__lock.release()
        return value

    def __set_state(self, state: int) -> None:
        self.__lock.acquire()
        self.__state = state
        self.__notify_values()
        self.__lock.release()

    def values(self) -> Dict[str, Any]:
        self.__lock.acquire()
        values = self.__make_values()
        self.__lock.release()
        return values

    def wait_values(self, timeout: float = -1) -> Optional[Dict[str, Any]]:
        return self.__wait.wait()

    async def __run(self):
        response_buffer = bytearray(1024)

        while True:
            self.__set_state(WiSUN.STATE_INITIALIZING)
            if not await self.__bp35.reset():
                self.__l.error('Failed to reset the Wi-SUN module.')
                self.__set_state(WiSUN.STATE_ERROR)
                await asyncio.sleep(1)
                continue
            if not await self.__bp35.set_password(self.__route_b_password,
                                                  timeout=5000):
                self.__l.error('Failed to set password.')
                continue
            if not await self.__bp35.set_route_b_id(self.__route_b_id,
                                                    timeout=5000):
                self.__l.error('Failed to set route-b ID.')
                continue

            while True:
                self.__set_state(WiSUN.STATE_SCANNING)
                self.__l.info("Scanning PANs...")
                success, pans = await self.__bp35.scan(0xffffffff,
                                                       6,
                                                       timeout=1000,
                                                       scan_timeout=30000)
                if success:
                    self.__l.info("PANs detected: {0}".format(pans))
                    break
                else:
                    self.__l.error("Failed to scan PANs")

            if len(pans) == 0:
                continue

            ## Connecting
            self.__set_state(WiSUN.STATE_CONNECTING)
            gc.collect()
            pan = pans[0]  # type: Dict[bytes, bytes]
            if not (b'Channel' in pan and b'Pan ID' in pan and b'Addr' in pan):
                self.__l.error("Invalid PAN information")
                continue

            channel = int(str(pan[b'Channel'], 'utf-8'), 16)
            pan_id = str(pan[b'Pan ID'], 'utf-8')
            mac_address = str(pan[b'Addr'], 'utf-8')

            if not await self.__bp35.set_channel(channel, timeout=1000):
                self.__l.error("Failed to set channel.")
                continue
            if not await self.__bp35.set_pan_id(pan_id, timeout=1000):
                self.__l.error("Failed to set PAN ID")
                continue
            ll_address = await self.__bp35.get_link_local_address(mac_address,
                                                                  timeout=1000)
            if ll_address is None:
                self.__l.error("Failed to get link local address")
                continue

            if not await self.__bp35.connect_to(ll_address, timeout=10000):
                self.__l.error("Failed to connect to the coordinator.")
                continue

            self.__set_state(WiSUN.STATE_CONNECTED)
            no_response_count = 0
            no_response_reset_count = 0
            while True:
                gc.collect()
                if await self.__bp35.send_to(True,
                                             ll_address,
                                             0xe1a,
                                             getPropertyFrame.bytes(),
                                             timeout=10000):
                    response = await self.__bp35.receive(response_buffer,
                                                         timeout=10000)
                    if response is None:
                        no_response_count += 1
                        if no_response_count >= 6:  # Reset if there are no responses more than 6 times.
                            self.__l.info("No response. Perform reset.")
                            no_response_reset_count += 1
                            break
                    else:
                        no_response_count = 0
                        frame = EchonetLiteFrame(response)
                        if not frame.is_valid():
                            self.__l.info("invalid frame {0}".format(
                                bytes(response)))
                        else:
                            seoj = frame.seoj()
                            esv = frame.esv()

                            instant_power = None  # type: Optional[float]
                            instant_current = None  # type: Optional[Tuple[float,float]]
                            coefficient = 1
                            cumulative_unit = 1.0
                            cumulative_value = None

                            self.__l.debug("seoj={0}, esv={1}".format(
                                seoj, esv))
                            if seoj == b'\x02\x88\x01' and esv == EchonetLiteFrame.ESV_GET_RES:  # Is the response for the request to read property?
                                for mv in frame.target_properies():
                                    if mv[0] == PROPERTY_INSTANT_POWER and mv[
                                            1] == 4 and len(
                                                mv) == 6:  # instant power
                                        power = struct.unpack('>i', mv[2:])[0]
                                        self.__l.info(
                                            'Power={0}[W]'.format(power))
                                        instant_power = power
                                    elif mv[0] == PROPERTY_INSTANT_CURRENT and mv[
                                            1] == 4 and len(
                                                mv) == 6:  # instant current
                                        current_r, current_t = struct.unpack(
                                            '>hh', mv[2:])
                                        self.__l.info(
                                            'Current R={0},T={1}[dA]'.format(
                                                current_r, current_t))
                                        instant_current = (current_r,
                                                           current_t)
                                    elif mv[0] == PROPERTY_COEFFICIENT and mv[
                                            1] == 4 and len(
                                                mv) == 6:  # coefficient
                                        coefficient = struct.unpack(
                                            '>I', mv[2:])[0]
                                        self.__l.debug(
                                            'Coefficient={0}'.format(
                                                coefficient))
                                    elif mv[0] == PROPERTY_CUMULATIVE_UNIT and mv[
                                            1] == 1 and len(mv) == 3:  # unit
                                        unit = mv[2]
                                        if unit == 0x00:
                                            cumulative_unit = 1.0e0
                                        elif unit == 0x01:
                                            cumulative_unit = 1.0e-1
                                        elif unit == 0x02:
                                            cumulative_unit = 1.0e-2
                                        elif unit == 0x03:
                                            cumulative_unit = 1.0e-3
                                        elif unit == 0x04:
                                            cumulative_unit = 1.0e-4
                                        elif unit == 0x0a:
                                            cumulative_unit = 1.0e+1
                                        elif unit == 0x0b:
                                            cumulative_unit = 1.0e+2
                                        elif unit == 0x0c:
                                            cumulative_unit = 1.0e+3
                                        elif unit == 0x0d:
                                            cumulative_unit = 1.0e+4
                                        self.__l.debug(
                                            'CumulativeUnit={0}'.format(
                                                cumulative_unit))
                                    elif mv[0] == PROPERTY_CUMULATIVE_VALUE and mv[
                                            1] == 4 and len(
                                                mv) == 6:  # cumulative power
                                        cumulative_value = struct.unpack(
                                            '>I', mv[2:])[0]
                                        self.__l.debug(
                                            'CumulativeValue={0}'.format(
                                                cumulative_value))

                                updated = False
                                self.__lock.acquire()
                                if instant_power is not None:
                                    self.__instant_power = instant_power
                                    updated = True
                                if instant_current is not None:
                                    self.__instant_current = instant_current
                                    updated = True
                                if cumulative_value is not None:
                                    self.__cumulative_power = cumulative_value * cumulative_unit
                                    updated = True
                                self.__lock.release()

                                if updated:
                                    self.__timestamp = time.time()
                                    self.__notify_values()

                                await asyncio.sleep(self.__update_interval)
Exemple #2
0
class IOExpander(object):
    "I/O Expander on I2C bus"

    REG_INPUT = const(0)
    REG_OUTPUT = const(1)
    REG_INVERSION = const(2)
    REG_CONFIG = const(3)

    def __init__(self,
                 i2c: machine.I2C,
                 address: int,
                 output: int = 0x00,
                 inversion: int = 0xf0,
                 direction: int = 0xff):
        """
        Initialize I/O expander driver
        i2c: machine.I2C object on which the I/O expander is.
        address: I2C slave address of the I/O expander.
        """
        self.__i2c = i2c
        self.__address = address
        self.__regs = memoryview(
            bytearray([0x00, output, inversion, direction]))
        self.__apply()

    def __apply(self) -> None:
        for i in range(1, 4):
            self.__i2c.writeto_mem(self.__address, i, self.__regs[i:i + 1])

    def configure(self, direction: int, inversion: int, output: int) -> None:
        self.__regs[IOExpander.REG_OUTPUT] = output
        self.__regs[IOExpander.REG_INVERSION] = inversion
        self.__regs[IOExpander.REG_CONFIG] = direction
        self.__apply()

    def input(self) -> int:
        self.__i2c.readfrom_into(
            self.__address, IOExpander.REG_INPUT,
            self.__regs[IOExpander.REG_INPUT:IOExpander.REG_INPUT + 1])
        return self.__regs[IOExpander.REG_INPUT]

    def last_input(self) -> int:
        return self.__regs[IOExpander.REG_INPUT]

    def output(self,
               value: Optional[int, None] = None,
               mask: int = 0xff) -> int:
        if value is not None:
            self.__set_masked(IOExpander.REG_OUTPUT, value, mask)
        return self.__regs[IOExpander.REG_OUTPUT]

    def direction(self,
                  value: Optional[int, None] = None,
                  mask: int = 0xff) -> int:
        if value is not None:
            self.__set_masked(IOExpander.REG_CONFIG, value, mask)
        return self.__regs[IOExpander.REG_CONFIG]

    def __set_masked(self, reg: int, value: int, mask: int) -> None:
        self.__regs[reg] = (self.__regs[reg] & ~mask) | value
        self.__i2c.writeto_mem(self.__address, reg, self.__regs[reg:reg + 1])

    def pin(self, pin: int) -> IOExpanderPin:
        return IOExpanderPin(self, pin)
Exemple #3
0
        data_len = len(data) if data is not None else 0
        self._m[self._next_offset + 1] = data_len
        if data_len > 0:
            self._m[self._next_offset + 2:self._next_offset + 2 +
                    data_len] = data
        self._next_offset += 2 + data_len
        self.opc(self.opc() + 1)

    def get_length(self) -> int:
        return self._next_offset

    def bytes(self) -> memoryview:
        return self._m[:self._next_offset + 1]


PROPERTY_COEFFICIENT = const(0xd3)  # 係数
PROPERTY_CUMULATIVE_VALUE = const(0xe0)  # 積算電力量計測値
PROPERTY_CUMULATIVE_UNIT = const(0xe1)  # 積算電力量単位
PROPERTY_INSTANT_POWER = const(0xe7)  # 瞬時電力計測値
PROPERTY_INSTANT_CURRENT = const(0xe8)  # 瞬時電流計測値

# Construct ECHONETlite request frame
getPropertyFrame = EchonetLiteFrame(bytearray(64))
getPropertyFrame.init()
getPropertyFrame.tid(0x0000)  # TID = 0x0000
getPropertyFrame.seoj(
    b'\x05\xff\x01')  # SEOJ 送信元EOJ クラスグループ=管理・操作関連機器, クラス=コントローラ
getPropertyFrame.deoj(
    b'\x02\x88\x01')  # DEOJ 送信先EOJ クラスグループ=住宅・設備関連機器, クラス=低圧スマート電力量メータ
getPropertyFrame.esv(EchonetLiteFrame.ESV_GET)  # プロパティ読み出し要求
getPropertyFrame.add_property(PROPERTY_COEFFICIENT, None)
Exemple #4
0
class BP35A1(object):
    "Controls Rohm BP35A1 Wi-SUN ECHONET module"
    CR = const(0x0d)
    LF = const(0x0a)
    SPC = const(0x20)

    IOEXPANDER_REG_OUTPUT = 0x02
    IOEXPANDER_OUTPUT_WKUP = 0x01
    IOEXPANDER_OUTPUT_RESET = 0x02
    IOEXPANDER_OUTPUT_RTS = 0x04

    def __init__(self, uart: machine.UART, wkup: Optional[machine.Pin],
                 reset: machine.Pin) -> None:
        "Construct BP35A1 instance"
        self.__l = logging.Logger('BP35A1')
        self.__uart = uart
        self.__wkup = wkup
        self.__reset = reset
        self.__buffer = bytearray(1024)

    def initialize(self) -> None:
        "Initialize I/O ports and peripherals to communicate with the module."
        if self.__wkup is not None:
            self.__wkup.value(True)
        self.__reset.value(False)  # Assert RESET
        self.__uart.init(baudrate=115200, timeout=5000)

    async def reset(self) -> bool:
        "Reset the module."

        self.__reset.value(False)  # Assert RESET at least 1 [ms]
        await asyncio.sleep_ms(10)  # /
        self.__reset.value(True)  # Deassert RESET
        await asyncio.sleep_ms(
            3000
        )  # We must wait 3000 milliseconds after RESET pin is deasserted. (HW spec p.14)

        responded = False
        for trial in range(15):
            self.write_command(b'SKVER')
            if await self.wait_response(b'SKVER', timeout=500) is not None:
                if await self.wait_response(b'EVER', timeout=500) is not None:
                    if await self.wait_response(b'OK',
                                                timeout=500) is not None:
                        responded = True
                        break
        if not responded:
            self.__l.info("The module did not respond within timeout period.")
            return False

        # Disable command echo-back
        if not await self.write_command_wait(
                b'SKSREG SFE 0', b'OK', timeout=1000):
            self.__l.error("Failed to initialize the module.")

        # Check ERXUDP format
        self.write_command(b'ROPT', eol=b'\r')
        response = await self.read_response_into(self.__buffer,
                                                 0,
                                                 eol_cr_only=True,
                                                 timeout=5000)
        if response is not None:
            self.__l.debug('ROPT: %s', self.__buffer[0:response])
        if response is None or response < 5 or self.__buffer[0:3] != b'OK ':
            self.__l.error('failed to read ERXUDP format.')
            return False
        if self.__buffer[3:5] == b'00':
            self.__l.info('ERXUDP format is binary. No need to change.')
        else:
            # ERXUDP output is printable hexadecimal format.
            self.__l.info('Changing ERXUDP format...')
            self.write_command(b'WOPT 00', eol=b'\r')
            if await self.wait_response(b'OK', eol_cr_only=True,
                                        timeout=5000) is None:
                self.__l.error('Failed to change ERXUDP format.')
                return False

        return True

    async def set_password(self, password: str, timeout: int = None) -> bool:
        "Generate PSK from the password and register it."
        length = len(password)
        if length == 0 or length > 32:
            raise ValueError(
                'The password length must be from 1 to 32 inclusive.')
        command = bytes('SKSETPWD {0:02X} {1}'.format(length, password),
                        'utf-8')
        return await self.write_command_wait(command, b'OK', timeout=timeout)

    async def set_route_b_id(self,
                             route_b_id: str,
                             timeout: Optional[int] = None) -> bool:
        "Sets Route-B ID"
        length = len(route_b_id)
        if length != 32:
            raise ValueError('The Route-B ID length must be 32.')
        command = bytes('SKSETRBID ' + route_b_id, 'utf-8')
        return await self.write_command_wait(command, b'OK', timeout=timeout)

    async def scan(
        self,
        channel_mask: int,
        scan_duration: int,
        timeout: Optional[int] = None,
        scan_timeout: Optional[int] = None
    ) -> Tuple[bool, List[Dict[bytes, bytes]]]:
        "Perform active scan and collect PAN list."
        if scan_duration < 0 or scan_duration > 14:
            raise ValueError('scan_duration must be from 0 to 14 inclusive.')
        command = bytes(
            'SKSCAN 2 {0:8X} {1}'.format(channel_mask, scan_duration), 'utf-8')
        if not await self.write_command_wait(command, b'OK', timeout=timeout):
            return False, []

        buffer = bytearray(1024)
        mv = memoryview(buffer)
        pans = []  # type: List[Dict[bytes, bytes]]
        pan = None  # type: Optional[Dict[bytes, bytes]]
        while True:
            response_length = await self.read_response_into(
                buffer, timeout=scan_timeout)
            if response_length is None:
                # Timed out.
                return False, pans

            response = mv[:response_length]  # type: memoryview
            self.__l.debug("response: %s", bytes(response))
            if response == b'EPANDESC':
                if pan is not None:
                    pans.append(pan)
                pan = {}
            elif response_length >= 8 and response[:8] == b'EVENT 22':  # end of scan
                if pan is not None:  # Add the last PAN
                    pans.append(pan)
                break
            elif response_length >= 6 and response[:6] == b'EVENT ':  # Other event
                pass  # Just ignore this response.
            else:
                if pan is not None:
                    pair = bytes(response)  # type: bytes
                    key, value = pair.strip().split(b':', 2)
                    if value is None:
                        pan[b'PairID'] = key
                    else:
                        pan[key] = value

        return True, pans

    async def set_channel(self,
                          channel: int,
                          timeout: Optional[int] = None) -> bool:
        "Sets communication channel"
        command = bytes('SKSREG S2 {0:02X}'.format(channel), 'utf-8')
        return await self.write_command_wait(command, b'OK', timeout=timeout)

    async def set_pan_id(self,
                         pan_id: str,
                         timeout: Optional[int] = None) -> bool:
        "Sets PAN ID"
        command = b'SKSREG S3 ' + bytes(pan_id, 'utf-8')
        return await self.write_command_wait(command, b'OK', timeout=timeout)

    async def get_link_local_address(
            self,
            mac_address: str,
            timeout: Optional[int] = None) -> Optional[str]:
        "Gets the link local address from the corresponding MAC address."
        command = b'SKLL64 ' + bytes(mac_address, 'utf-8')
        self.write_command(command)
        buffer = bytearray(1024)
        response_length = await self.read_response_into(buffer,
                                                        timeout=timeout)
        if response_length is None:
            return None
        else:
            return str(buffer[:response_length], 'utf-8')

    async def connect_to(self,
                         ll_address: str,
                         timeout: Optional[int] = None) -> bool:
        "Connect to a PAA as a PaC"
        command = b'SKJOIN ' + bytes(ll_address, 'utf-8')
        if not await self.write_command_wait(command, b'OK', timeout=timeout):
            return False

        # Wait until EVENT 0x25 (PANA connection has completed successfully) arrives.
        mv = memoryview(self.__buffer)
        while True:
            response_length = await self.read_response_into(self.__buffer,
                                                            timeout=timeout)
            if response_length is None:
                # timed out
                return False
            else:
                self.__l.debug("response: %s", self.__buffer[:response_length])
                if response_length > 8:
                    if mv[:8] == b'EVENT 24':
                        # PANA connection failed.
                        return False
                    elif mv[:8] == b'EVENT 25':
                        # PANA connection complete
                        return True

    async def send_to(self,
                      do_encrypt: bool,
                      ll_address: str,
                      port: int,
                      data: bytes,
                      timeout: Optional[int] = None) -> bool:
        encrypt_flag = '1' if do_encrypt else '0'
        command = bytes(
            'SKSENDTO 1 {0} {1:04X} {2} {3:04X} '.format(
                ll_address, port, encrypt_flag, len(data)), 'utf-8')
        self.write(command)
        self.write(data)
        return await self.wait_response(b'OK', timeout=timeout) is not None

    async def read_response_block(
            self,
            buffer: bytearray,
            offset: int = 0,
            timeout: Optional[int] = None) -> Optional[int]:
        buffer_length = len(buffer)
        response_length = 0
        start_time_ms = time.ticks_ms()
        while True:
            c = self.__readchar()
            if c < 0:
                if timeout is not None and (time.ticks_ms() -
                                            start_time_ms) >= timeout:
                    return None
                try:
                    await asyncio.sleep_ms(1)
                except asyncio.CancelledError:
                    return None
                continue
            # self.__l.debug('%c', c)
            if c == BP35A1.CR or c == BP35A1.LF:
                pass
            elif c == BP35A1.SPC:
                return response_length
            else:
                buffer[offset + response_length] = c
                response_length += 1
                if offset + response_length == buffer_length:
                    return response_length

    async def receive(self,
                      buffer: bytearray,
                      timeout: Optional[int] = None) -> Optional[memoryview]:
        head = b'ERXUDP'
        head_len = len(head)
        mv = memoryview(buffer)
        start_time_ms = time.ticks_ms()
        while timeout is None or time.ticks_ms() - start_time_ms < timeout:
            response = await self.read_response_block(mv, timeout=timeout)
            if response is None or response != head_len or mv[:
                                                              head_len] != head:
                pass
            else:
                # self.__l.debug('response:{0}'.format(bytes(mv[:response])))
                break
        block_count = 1
        while True:
            response = await self.read_response_block(mv, timeout=timeout)
            if response is None:
                return None
            self.__l.debug('response block{0}:{1}'.format(
                block_count, bytes(mv[:response])))
            block_count += 1
            if block_count == 8:
                data_len = int(str(mv[:response], 'utf-8'), 16)
                break

        self.__l.debug('ERXUDP DATALEN={0}'.format(data_len))
        bytes_read = self.__uart.readinto(mv[:data_len])
        return mv[:bytes_read + 1]

    def write(self, s: bytes) -> None:
        self.__l.debug('<- %s', s)
        self.__uart.write(s)

    def read(self, length: int) -> bytes:
        return self.__uart.read(length)

    def write_command(self, command: bytes, eol: bytes = b'\r\n') -> None:
        self.__l.debug('<- %s', command)
        self.__uart.write(command)
        self.__uart.write(eol)

    async def write_command_wait(self,
                                 command: bytes,
                                 expected_response: bytes,
                                 timeout: Optional[int] = None) -> bool:
        self.write_command(command)
        return await self.wait_response(expected_response,
                                        timeout=timeout) is not None

    def __readchar(self) -> int:
        char_buffer = bytearray(1)
        n = self.__uart.readinto(char_buffer)
        return -1 if n is None or n == 0 else char_buffer[0]

    async def read_response_into(
            self,
            buffer: bytearray,
            offset: int = 0,
            eol_cr_only: bool = False,
            timeout: Optional[int] = None) -> Optional[int]:
        buffer_length = len(buffer)
        response_length = 0
        state = 0
        start_time_ms = time.ticks_ms()
        while True:
            c = self.__readchar()
            if c < 0:
                if timeout is not None and (time.ticks_ms() -
                                            start_time_ms) >= timeout:
                    return None
                try:
                    await asyncio.sleep_ms(1)
                except asyncio.CancelledError:
                    return None
                continue

            #self.__l.debug('S:%d R:%c', state, c)
            if state == 0 and c == BP35A1.CR:
                if response_length == 0:
                    state = 0
                elif eol_cr_only:
                    return response_length
                else:
                    state = 1
            elif state == 0 and c == BP35A1.LF:
                state = 0
            elif state == 0:
                buffer[offset + response_length] = c
                response_length += 1
                if offset + response_length == buffer_length:
                    return response_length
            elif state == 1 and c == BP35A1.LF:
                return response_length
            elif state == 1 and c == BP35A1.CR:
                state = 1
            elif state == 1:
                state = 0

    async def wait_response(self,
                            expected_response: bytes,
                            max_response_size: int = 1024,
                            eol_cr_only: bool = False,
                            timeout: Optional[int] = None) -> Optional[bytes]:
        self.__l.debug('wait_response: target=%s', expected_response)
        response = memoryview(self.__buffer) if len(
            self.__buffer) <= max_response_size else bytearray(
                max_response_size)
        expected_length = len(expected_response)
        while True:
            length = await self.read_response_into(response,
                                                   eol_cr_only=eol_cr_only,
                                                   timeout=timeout)
            if length is None: return None
            self.__l.debug("wait_response: response=%s", response[:length])
            if length >= expected_length and response[:
                                                      expected_length] == expected_response:
                return response[:length]

    async def wait_response_into(
            self,
            expected_response: bytes,
            response_buffer: bytearray,
            timeout: Optional[int] = None) -> Optional[memoryview]:
        self.__l.debug('wait_response_into: target=%s', expected_response)
        expected_length = len(expected_response)
        mv = memoryview(response_buffer)
        while True:
            length = await self.read_response_into(response_buffer,
                                                   timeout=timeout)
            if length is None: return None
            self.__l.debug("wait_response_into: response=%s",
                           bytes(mv[:length]))
            if length >= expected_length and mv[:
                                                expected_length] == expected_response:
                return mv[:length]

    async def wait_prompt(self,
                          expected_prompt: bytes,
                          timeout: Optional[int] = None) -> bool:
        prompt_length = len(expected_prompt)
        index = 0
        start_time_ms = time.ticks_ms()

        while True:
            c = self.__readchar()
            if c < 0:
                if time.ticks_ms() - start_time_ms > timeout:
                    return False
                await asyncio.sleep_ms(1)
                continue
            if expected_prompt[index] == c:
                index += 1
                if index == prompt_length:
                    return True
            else:
                index = 0

    async def execute_command(
            self,
            command: bytes,
            response_buffer: bytearray,
            index: int = 0,
            expected_response_predicate: Callable[[memoryview], bool] = None,
            expected_response_list: List[bytes] = [b'OK'],
            timeout: int = None) -> Tuple[bool, List[memoryview]]:
        assert expected_response_predicate is not None or expected_response_list is not None
        if expected_response_predicate is None:
            expected_response_predicate = lambda mv: mv in expected_response_list
        self.write_command(command)
        buffer_length = len(response_buffer)
        responses = []  # type: List[memoryview]
        mv = memoryview(response_buffer)
        while True:
            length = await self.read_response_into(response_buffer,
                                                   index,
                                                   timeout=timeout)
            if length is None:
                return (False, responses)
            response = mv[index:index + length]
            responses.append(response)
            if expected_response_predicate(response):
                return (True, responses)
            index += length

    async def execute_command_single_response(
            self,
            command: bytes,
            starts_with: bytes = None,
            timeout: Optional[int] = None) -> Optional[bytes]:
        result, responses = await self.execute_command(command,
                                                       self.__buffer,
                                                       timeout=timeout)
        if not result: return None
        starts_with_length = len(starts_with) if starts_with is not None else 0

        for response in responses:  # type: Union[memoryview, bytes]
            if starts_with_length == 0 and len(response) > 0:
                response = bytes(response)
                self.__l.debug('-> %s', response)
                return response
            if starts_with_length > 0 and len(
                    response
            ) >= starts_with_length and response[:
                                                 starts_with_length] == starts_with:
                response = bytes(response)
                self.__l.debug('-> %s', response)
                return response
        return None
Exemple #5
0
class BMP280(object):
    "BMP280 Digital Pressure sensor driver"

    DEFAULT_ADDRESS = const(0x77)

    OVERSAMPLING_SKIPPED = const(0x0)
    OVERSAMPLING_1  = const(0x1)
    OVERSAMPLING_2  = const(0x2)
    OVERSAMPLING_4  = const(0x3)
    OVERSAMPLING_8  = const(0x4)
    OVERSAMPLING_16 = const(0x5)

    POWERMODE_SLEEP = const(0x0)
    POWERMODE_FORCED = const(0x1)
    POWERMODE_NORMAL = const(0x3)

    STANDBY_0_5  = const(0x0)
    STANDBY_62_5 = const(0x1)
    STANDBY_125  = const(0x2)
    STANDBY_250  = const(0x3)
    STANDBY_500  = const(0x4)
    STANDBY_1000 = const(0x5)
    STANDBY_2000 = const(0x6)
    STANDBY_4000 = const(0x7)

    IIR_OFF = const(0x0)
    IIR_2   = const(0x1)
    IIR_4   = const(0x2)
    IIR_8   = const(0x3)
    IIR_16  = const(0x4)

    def __init__(self, i2c:machine.I2C, address:int):
        "Construct BMP280 driver for the device which has 'address' on the 'i2c' bus."
        self.__l = logging.Logger('BMP280')
        self.__i2c = i2c
        self.__address = address
        

    def reset(self) -> bool:
        "Reset this BMP280 device."

        self.__i2c.writeto_mem(self.__address, 0xe0, bytes(0xb6))
        result = self.__i2c.readfrom_mem(self.__address, 0xd0, 1)
        if not len(result) == 1 and result[0] == 0x58:
            return False
        # Read trimming data
        calibration_bytes = self.__i2c.readfrom_mem(self.__address, 0x88, 26)
        if len(calibration_bytes) != 26:
            return False
        cal = struct.unpack('<HhhHhhhhhhhh', calibration_bytes)
        self.__dig_T1 = float(cal[ 0])
        self.__dig_T2 = float(cal[ 1])
        self.__dig_T3 = float(cal[ 2])
        self.__dig_P1 = float(cal[ 3])
        self.__dig_P2 = float(cal[ 4])
        self.__dig_P3 = float(cal[ 5])
        self.__dig_P4 = float(cal[ 6])
        self.__dig_P5 = float(cal[ 7])
        self.__dig_P6 = float(cal[ 8])
        self.__dig_P7 = float(cal[ 9])
        self.__dig_P8 = float(cal[10])
        self.__dig_P9 = float(cal[11])
        
        return True

    def configure(self, power_mode:int=POWERMODE_NORMAL, oversampling_pressure:int=OVERSAMPLING_1, oversampling_temperature:int=OVERSAMPLING_1, standby_period:int=STANDBY_1000, iir_coefficient:int=IIR_OFF):
        "Configure this BMP280 device"
        # Enter to SLEEP mode to update config register.
        self.__i2c.writeto(self.__address, bytes((0xf4, 0x00)))
        # Update config register and ctrl_meas register.
        config = ((standby_period&7) << 5) | ((iir_coefficient&7) << 2)
        self.__i2c.writeto(self.__address, bytes((0xf5, config)))
        ctrl_meas = ((oversampling_temperature&7) << 5) | ((oversampling_pressure&7) << 2) | (power_mode&3)
        self.__i2c.writeto(self.__address, bytes((0xf4, ctrl_meas)))
        
    
    def read_raw(self) -> bytes:
        "Read measured data from this device and return it without calibration."
        try:
            return self.__i2c.readfrom_mem(self.__address, 0xf7, 6)
        except OSError:
            return None

    def read(self) -> (float, float):
        "Read measured data and calculate calibrated values. This function returns 2-ple whose first element is measured pressure value in [P] and second element is measured temperature value in [C]."
        raw = self.read_raw()
        if raw is None:
            return None

        raw_P, xlsb_P, raw_T, xlsb_T = struct.unpack('>HBHB', raw)
        adc_T = (raw_T << 4) | (xlsb_T >> 4)
        adc_P = (raw_P << 4) | (xlsb_P >> 4)
        self.__l.debug("adc_T=%d, adc_P=%d", adc_T, adc_P)

        # Calibration formula from BMP280 datasheet.
        v1 = ((adc_T/16384.0) - (self.__dig_T1/1024.0))*self.__dig_T2
        v2 = (adc_T/131072.0) - (self.__dig_T1/8192.0)
        v2 = v2*v2*self.__dig_T3
        t_fine = v1 + v2
        T = (v1 + v2)/5120.0

        v1 = t_fine/2.0 - 64000.0
        v2 = v1*v1*self.__dig_P6/32768.0
        v2 = v2 + v1*self.__dig_P5*2.0
        v2 = v2/4.0 + self.__dig_P4*65536.0
        v1 = (self.__dig_P3*v1*v1/524288.0 + self.__dig_P2*v1)/524288.0
        v1 = (1.0 + v1/32768.0)*self.__dig_P1
        if v1 == 0.0:
            P = 0.0
        else:
            P = 1048576.0 - adc_P
            P = (P - (v2 / 4096.0)) * 6250.0 / v1
            self.__l.debug("P_before_comp: %f", P)
            v1 = self.__dig_P9*P*P/2147483648.0
            v2 = P*self.__dig_P8/32768.0
            P = P + (v1 + v2  + self.__dig_P7)/16.0
        return (P, T)
Exemple #6
0
class LTEModule(object):
    "Controls Quectel EC21 LTE Module"
    CR = const(0x0d)
    LF = const(0x0a)

    SOCKET_TCP = const(0)
    SOCKET_UDP = const(1)

    MAX_CONNECT_ID = const(12)
    MAX_SOCKET_DATA_SIZE = const(1460)

    def __init__(self):
        self.__l = logging.Logger('LTEModule')

        self.__pin_reset_module = pyb.Pin('RESET_MODULE')
        self.__pin_dtr_module = pyb.Pin('DTR_MODULE')
        self.__pin_pwrkey_module = pyb.Pin('PWRKEY_MODULE')
        self.__pin_module_power = pyb.Pin('M_POWR')
        self.__pin_module_status = pyb.Pin('STATUS')
        self.__pin_disable_module = pyb.Pin('W_DISABLE')
        self.__pin_wakeup_module = pyb.Pin('WAKEUP_IN')

        self.__uart = pyb.UART(2)
        self.__urcs = None
        self.__connections = []

    def initialize(self) -> None:
        "Initialize I/O ports and peripherals to communicate with the module."
        self.__l.debug('initialize')

        self.__pin_reset_module.init(pyb.Pin.OUT_PP)
        self.__pin_dtr_module.init(pyb.Pin.OUT_PP)
        self.__pin_pwrkey_module.init(pyb.Pin.OUT_PP)
        self.__pin_module_power.init(pyb.Pin.OUT_PP)
        self.__pin_module_status.init(pyb.Pin.IN)
        self.__pin_disable_module.init(pyb.Pin.OUT_PP)
        self.__pin_wakeup_module.init(pyb.Pin.OUT_PP)

        self.__pin_dtr_module.off()
        self.__pin_pwrkey_module.off()
        self.__pin_module_power.off()
        self.__pin_reset_module.on()
        self.__pin_disable_module.on()
        self.__pin_wakeup_module.off()

        self.__uart.init(baudrate=115200, timeout=5000, timeout_char=1000)

    def set_supply_power(self, to_supply: bool):
        "Enable/Disable power supply to the module."
        self.__pin_module_power.value(1 if to_supply else 0)

    async def reset(self) -> bool:
        "Reset the module."
        self.__pin_reset_module.off()
        await asyncio.sleep_ms(200)
        while self.__uart.any():
            self.__uart.read(self.__uart.any())
        self.__pin_reset_module.on()
        await asyncio.sleep_ms(300)

        for trial in range(15):
            if await self.wait_response(b'RDY') is not None:
                return True
        self.__l.info("The module did not respond within timeout period.")
        return False

    async def wait_busy(self, max_trials: int = 50) -> bool:
        "Wait while the module is busy."
        self.__l.debug('Waiting busy...')
        for trial in range(max_trials):
            if not self.is_busy():
                return True
            await asyncio.sleep_ms(100)
        self.__l.debug('Failed.')
        return False

    async def turn_on(self) -> bool:
        "Turn on the module."
        await asyncio.sleep_ms(100)
        self.__pin_pwrkey_module.on()
        await asyncio.sleep_ms(200)
        self.__pin_pwrkey_module.off()

        if not await self.wait_busy():
            self.__l.info("The module is still busy.")
            return False

        for trial in range(15):
            if await self.wait_response(b'RDY') is not None:
                return True
        self.__l.info("The module did not respond within timeout period.")
        return False

    async def turn_on_or_reset(self) -> bool:
        "Turn on or reset the module and wait until the LTE commucation gets available."
        self.__urcs = []

        if self.is_busy():
            if not await self.turn_on():
                return False
        else:
            if not await self.reset():
                return False

        if not await self.write_command_wait(
                b'AT', b'OK'):  # Check if the module can accept commands.
            self.__l.info("The module did not respond.")
            return False
        if not await self.write_command_wait(b'ATE0',
                                             b'OK'):  # Disable command echo
            self.__l.info("Failed to disable command echo.")
            return False
        if not await self.write_command_wait(
                b'AT+QURCCFG="urcport","uart1"',
                b'OK'):  # Use UART1 port to receive URC
            self.__l.info("Failed to configure the module UART port.")
            return False

        buffer = bytearray(1024)
        result, responses = await self.execute_command(
            b'AT+QSCLK=1', buffer, expected_response_list=[b'OK', b'ERROR'])
        if not result:
            return False

        self.__l.info('Waiting SIM goes active...')
        while True:
            result, responses = await self.execute_command(b'AT+CPIN?',
                                                           buffer,
                                                           timeout=1000)
            self.__l.info('AT+CPIN result={0}, response={1}'.format(
                result, len(responses)))
            if len(responses) == 0: return False
            if result:

                return True

    async def get_IMEI(self) -> str:
        "Gets International Mobile Equipment Identity (IMEI)"
        response = await self.execute_command_single_response(b'AT+GSN')
        return str(response, 'utf-8') if response is not None else None

    async def get_IMSI(self) -> str:
        "Gets International Mobile Subscriber Identity (IMSI)"
        response = await self.execute_command_single_response(b'AT+CIMI')
        return str(response, 'utf-8') if response is not None else None

    async def get_phone_number(self) -> str:
        "Gets phone number (subscriber number)"
        response = await self.execute_command_single_response(
            b'AT+CNUM', b'+CNUM:')
        return str(response[6:], 'utf-8') if response is not None else None

    async def get_RSSI(self) -> Tuple[int, int]:
        "Gets received signal strength indication (RSSI)"
        response = await self.execute_command_single_response(
            b'AT+CSQ', b'+CSQ:')
        if response is None:
            return None
        try:
            s = str(response[5:], 'utf-8')
            rssi, ber = s.split(',', 2)
            return (int(rssi), int(ber))
        except ValueError:
            return None

    async def activate(self,
                       access_point: str,
                       user: str,
                       password: str,
                       timeout: int = None) -> bool:
        self.__l.info("Activating network...")
        while True:
            # Read network registration status.
            response = await self.execute_command_single_response(
                b'AT+CGREG?', b'+CGREG:', timeout)
            if response is None:
                raise LTEModuleError('Failed to get registration status.')
            s = str(response, 'utf-8')
            self.__l.debug('AT+CGREG?:%s', s)
            n, stat = s.split(',')[:2]
            if stat == '0' or stat == '4':  # Not registered and not searching (0), or unknown (4).
                raise LTEModuleError('Invalid registration status.')
            elif stat == '1' or stat == '5':  # Registered.
                break

        while True:
            # Read EPS network registration status
            response = await self.execute_command_single_response(
                b'AT+CEREG?', b'+CEREG:', timeout)
            if response is None:
                raise LTEModuleError('Failed to get registration status.')
            s = str(response, 'utf-8')
            self.__l.debug('AT+CEREG?:%s', s)
            n, stat = s.split(',')[:2]
            if stat == '0' or stat == '4':  # Not registered and not searching (0), or unknown (4).
                raise LTEModuleError('Invalid registration status.')
            elif stat == '1' or stat == '5':  # Registered.
                break
        # Configure TCP/IP contect parameters
        # contextID,context_type,APN,username,password,authentication
        # context_type  : IPv4 = 1, IPv4/v6 = 2
        # authentication: None = 0, PAP = 1, CHAP = 2, PAP or CHAP = 3
        command = bytes(
            'AT+QICSGP=1,1,"{0}","{1}","{2}",1'.format(access_point, user,
                                                       password), 'utf-8')
        if not await self.write_command_wait(command, b'OK', timeout):
            return False
        # Activate a PDP context
        if not await self.write_command_wait(b'AT+QIACT=1', b'OK', timeout):
            return False
        if not await self.write_command_wait(b'AT+QIACT?', b'OK', timeout):
            return False

        return True

    async def get_ip_address(self,
                             host: str,
                             timeout: int = 60 * 1000) -> List[str]:
        """
        Get IP address from hostname using DNS.

        :param str host:        An address of the remote host.  
        :return:                A list of IP addresses corresponding to the hostname.
        :raises LTEModuleError: If the communication module failed to open a new socket.
        """
        assert (host is not None)

        await self.__process_remaining_urcs(timeout=timeout)

        buffer = bytearray(1024)

        try:
            # Query host address.
            self.__l.debug("Querying DNS: {0}".format(host))
            command = bytes('AT+QIDNSGIP=1,"{0}"'.format(host), 'utf-8')
            if not await self.write_command_wait(
                    command, b'OK', timeout=timeout):
                raise LTEModuleError('Failed to get IP.')

            self.__l.debug("Waiting response...")
            response = await self.wait_response(b'+QIURC: "dnsgip"',
                                                timeout=timeout)  # type:bytes
            if response is None:
                return None
            self.__l.debug("QIURC: {0}".format(response))
            fields = str(response, 'utf-8').split(',')

            if len(fields) < 4 or int(fields[1]) != 0:
                return None
            count = int(fields[2])
            ipaddrs = []
            for i in range(count):
                mv = await self.wait_response_into(b'+QIURC: "dnsgip",',
                                                   response_buffer=buffer,
                                                   timeout=1000)
                if mv is not None:
                    ipaddrs.append(str(mv[18:-1],
                                       'utf-8'))  # strip double-quote
            return ipaddrs
        except ValueError:
            return None

        except asyncio.CancelledError:
            pass

    async def socket_open(self,
                          host: str,
                          port: int,
                          socket_type: int,
                          timeout: int = 30 * 1000) -> int:
        """
        Open a new socket to communicate with a host.

        :param str host:        An address of the remote host.  
        :param int port:        Port number of the remote host.
        :param int socket_type: Socket type. SOCKET_TCP or SOCKET_UDP
        :return:                Connection ID of opened socket if success. Otherwise raise LTEModuleError.
        :raises LTEModuleError: If the communication module failed to open a new socket.
        """
        assert (host is not None)
        assert (port is not None and 0 <= port and port <= 65535)
        if socket_type == LTEModule.SOCKET_TCP:
            socket_type_name = 'TCP'
        elif socket_type == LTEModule.SOCKET_UDP:
            socket_type_name = 'UDP'
        else:
            socket_type_name = None
        assert (socket_type_name is not None)

        await self.__process_remaining_urcs(timeout=timeout)

        buffer = bytearray(1024)

        # new_connect_id = None
        # for connect_id in range(LTEModule.MAX_CONNECT_ID):
        #     if connect_id not in self.__connections:
        #         new_connect_id = connect_id
        #         break
        # if new_connect_id is None:
        #     raise LTEModuleError('No connection resources available.')

        # Read current connections and find unused connection.
        success, responses = await self.execute_command(b'AT+QISTATE?',
                                                        buffer,
                                                        timeout=timeout)
        if not success:
            raise LTEModuleError('Failed to get socket status')
        connect_id_in_use = set()
        for response in responses:
            if len(response) < 10 or response[:10] != b'+QISTATE: ': continue
            s = str(bytes(response[10:]), 'utf-8')
            self.__l.info(s)
            params = s.split(',', 1)
            connect_id = int(params[0])
            connect_id_in_use.add(connect_id)


#
        new_connect_id = None
        for connect_id in range(LTEModule.MAX_CONNECT_ID):
            if connect_id not in connect_id_in_use and connect_id not in self.__connections:
                new_connect_id = connect_id
                break
        if new_connect_id is None:
            raise LTEModuleError('No connection resources available.')

        # Open socket.
        self.__l.info('Connecting[id={0}] {1}:{2}'.format(
            connect_id, host, port))
        command = bytes(
            'AT+QIOPEN=1,{0},"{1}","{2}",{3},0,0'.format(
                connect_id, socket_type_name, host, port), 'utf-8')
        if not await self.write_command_wait(command, b'OK', timeout=timeout):
            raise LTEModuleError('Failed to open socket. OK')
        response = await self.wait_response(bytes(
            '+QIOPEN: {0},'.format(connect_id), 'utf-8'),
                                            timeout=timeout)
        if response is None:
            raise LTEModuleError('Failed to open socket. QIOPEN')
        error = str(response, 'utf-8').split(',')[1]
        if error != '0':
            raise LTEModuleError(
                'Failed to open socket. error={0}'.format(error))

        self.__l.info('Connected[id={0}]'.format(connect_id))
        self.__connections.append(connect_id)
        return connect_id

    async def socket_send(self,
                          connect_id: int,
                          data: bytes,
                          offset: int = 0,
                          length: int = None,
                          timeout: int = None) -> bool:
        """
        Send a packet to destination.
        """
        assert (0 <= connect_id and connect_id <= LTEModule.MAX_CONNECT_ID)
        await self.__process_remaining_urcs(timeout=timeout)
        if connect_id not in self.__connections:
            return False

        length = len(data) if length is None else length
        if length == 0:
            return True
        assert (length <= LTEModule.MAX_SOCKET_DATA_SIZE)

        command = bytes('AT+QISEND={0},{1}'.format(connect_id, length),
                        'utf-8')
        self.write_command(command)
        if not await self.wait_prompt(b'> ', timeout=timeout):
            return False
        mv = memoryview(data)
        self.__uart.write(mv[offset:offset + length])
        return await self.wait_response(b'SEND OK',
                                        timeout=timeout) is not None

    async def socket_receive(self,
                             connect_id: int,
                             buffer: bytearray,
                             offset: int = 0,
                             length: int = None,
                             timeout: int = None) -> int:
        assert (0 <= connect_id and connect_id <= LTEModule.MAX_CONNECT_ID)
        await self.__process_remaining_urcs(timeout=timeout)
        if connect_id not in self.__connections:
            return False

        length = len(buffer) if length is None else length
        if length == 0:
            return True
        assert (length <= LTEModule.MAX_SOCKET_DATA_SIZE)

        command = bytes('AT+QIRD={0},{1}'.format(connect_id, length), 'utf-8')
        self.write_command(command)
        response = await self.wait_response(b'+QIRD: ', timeout=timeout)
        if response is None:
            return None
        actual_length = int(str(response[7:], 'utf-8'))
        # self.__l.debug('receive length=%d', actual_length)
        if actual_length == 0:
            return 0 if await self.wait_response(
                b'OK', timeout=timeout) is not None else None
        mv = memoryview(buffer)
        bytes_read = self.__uart.readinto(mv[offset:offset + length],
                                          actual_length)
        # self.__l.debug('bytes read=%d', bytes_read)
        # self.__l.debug('bytes=%s', buffer[offset:offset+length])
        return actual_length if bytes_read == actual_length and await self.wait_response(
            b'OK', timeout=timeout) is not None else None

    async def socket_close(self, connect_id: int, timeout: int = None) -> bool:
        assert (0 <= connect_id and connect_id <= LTEModule.MAX_CONNECT_ID)
        if connect_id not in self.__connections:
            return False
        self.__l.info('Closing connection {0}'.format(connect_id))
        command = bytes('AT+QICLOSE={0}'.format(connect_id), 'utf-8')
        await self.write_command_wait(command,
                                      expected_response=b'OK',
                                      timeout=timeout)
        self.__l.info('Closed connection {0}'.format(connect_id))
        self.__connections.remove(connect_id)
        return True

    def socket_is_connected(self, connect_id: int) -> bool:
        return connect_id in self.__connections and (
            "closed", connect_id) not in self.__urcs

    def is_busy(self) -> bool:
        return bool(self.__pin_module_status.value())

    def write(self, s: bytes) -> None:
        self.__l.debug('<- ' + s)
        self.__uart.write(s)

    def read(self, length: int) -> bytes:
        return self.__uart.read(length)

    def write_command(self, command: bytes) -> None:
        self.__l.debug('<- %s', command)
        self.__uart.write(command)
        self.__uart.write('\r')

    async def write_command_wait(self,
                                 command: bytes,
                                 expected_response: bytes,
                                 timeout: int = None) -> bool:
        self.write_command(command)
        return await self.wait_response(expected_response,
                                        timeout=timeout) is not None

    async def read_response_into(self,
                                 buffer: bytearray,
                                 offset: int = 0,
                                 timeout: int = None) -> int:
        while True:
            length = await self.__read_response_into(buffer=buffer,
                                                     offset=offset,
                                                     timeout=timeout)
            mv = memoryview(buffer)
            if length is not None and length >= 8 and mv[0:8] == b"+QIURC: ":
                #self.__l.info("URC: {0}".format(str(mv[:length], 'utf-8')))
                if length > 17 and mv[8:16] == b'"closed"':
                    connect_id = int(str(mv[17:length], 'utf-8'))
                    self.__l.info("Connection {0} closed".format(connect_id))
                    self.__urcs.append(("closed", connect_id))
                    continue

            return length

    async def __read_response_into(self,
                                   buffer: bytearray,
                                   offset: int = 0,
                                   timeout: int = None) -> int:
        buffer_length = len(buffer)
        response_length = 0
        state = 0
        start_time_ms = time.ticks_ms()
        while True:
            c = self.__uart.readchar()
            if c < 0:
                if timeout is not None and (time.ticks_ms() -
                                            start_time_ms) >= timeout:
                    return None
                try:
                    await asyncio.sleep_ms(1)
                except asyncio.CancelledError:
                    return None
                continue

            #self.__l.debug('S:%d R:%c', state, c)
            if state == 0 and c == LTEModule.CR:
                state = 1
            elif state == 1 and c == LTEModule.LF:
                state = 2
            elif state == 1 and c == LTEModule.CR:
                state = 1
            elif state == 1 and c != LTEModule.LF:
                response_length = 0
                state = 0
            elif state == 2 and c == LTEModule.CR:
                if response_length == 0:
                    state = 1  # Maybe there is another corresponding CR-LF followed by actual response data. So we have to return to state 1.
                else:
                    state = 4
            elif state == 2 and c != LTEModule.CR:
                buffer[offset + response_length] = c
                response_length += 1
                if offset + response_length == buffer_length:
                    state = 3
            elif state == 3 and c == LTEModule.CR:
                state = 4
            elif state == 4 and c == LTEModule.LF:
                return response_length

    async def __process_remaining_urcs(self, timeout: int = None):
        for urc_type, urc_params in self.__urcs:
            if urc_type == 'closed':
                await self.socket_close(urc_params, timeout=timeout)
        self.__urcs.clear()

    async def wait_response(self,
                            expected_response: bytes,
                            max_response_size: int = 1024,
                            timeout: int = None) -> bytes:
        self.__l.debug('wait_response: target=%s', expected_response)
        response = bytearray(max_response_size)
        expected_length = len(expected_response)
        while True:
            length = await self.read_response_into(response, timeout=timeout)
            if length is None: return None
            self.__l.debug("wait_response: response=%s", response[:length])
            if length >= expected_length and response[:
                                                      expected_length] == expected_response:
                return response[:length]

    async def wait_response_into(self,
                                 expected_response: bytes,
                                 response_buffer: bytearray,
                                 timeout: int = None) -> memoryview:
        self.__l.debug('wait_response_into: target=%s', expected_response)
        expected_length = len(expected_response)
        mv = memoryview(response_buffer)
        while True:
            length = await self.read_response_into(response_buffer,
                                                   timeout=timeout)
            if length is None: return None
            self.__l.debug("wait_response_into: response=%s",
                           str(mv[:length], 'utf-8'))
            if length >= expected_length and mv[:
                                                expected_length] == expected_response:
                return mv[:length]

    async def wait_prompt(self,
                          expected_prompt: bytes,
                          timeout: int = None) -> bool:
        prompt_length = len(expected_prompt)
        index = 0
        start_time_ms = time.ticks_ms()

        while True:
            c = self.__uart.readchar()
            if c < 0:
                if time.ticks_ms() - start_time_ms > timeout:
                    return False
                await asyncio.sleep_ms(1)
                continue
            if expected_prompt[index] == c:
                index += 1
                if index == prompt_length:
                    return True
            else:
                index = 0

    async def execute_command(
            self,
            command: bytes,
            response_buffer: bytearray,
            index: int = 0,
            expected_response_predicate: Callable[[memoryview], bool] = None,
            expected_response_list: List[bytes] = [b'OK'],
            timeout: int = None) -> Tuple[bool, List[memoryview]]:
        assert expected_response_predicate is not None or expected_response_list is not None
        if expected_response_predicate is None:
            expected_response_predicate = lambda mv: mv in expected_response_list
        self.write_command(command)
        buffer_length = len(response_buffer)
        responses = []
        mv = memoryview(response_buffer)
        while True:
            length = await self.read_response_into(response_buffer,
                                                   index,
                                                   timeout=timeout)
            if length is None:
                return (False, responses)
            response = mv[index:index + length]
            responses.append(response)
            if expected_response_predicate(response):
                return (True, responses)
            index += length

    async def execute_command_single_response(self,
                                              command: bytes,
                                              starts_with: bytes = None,
                                              timeout: int = None) -> bytes:
        buffer = bytearray(1024)
        result, responses = await self.execute_command(command,
                                                       buffer,
                                                       timeout=timeout)
        if not result: return None
        starts_with_length = len(starts_with) if starts_with is not None else 0

        for response in responses:
            if starts_with_length == 0 and len(response) > 0:
                response = bytes(response)
                self.__l.debug('-> %s', response)
                return response
            if starts_with_length > 0 and len(
                    response
            ) >= starts_with_length and response[:
                                                 starts_with_length] == starts_with:
                response = bytes(response)
                self.__l.debug('-> %s', response)
                return response
        return None
Exemple #7
0
class SHT31(object):
    REPEATABILITY_HIGH = const(0)
    REPEATABILITY_MEDIUM = const(1)
    REPEATABILITY_LOW = const(2)
    MPS_0_5 = const(0)
    MPS_1   = const(1)
    MPS_2   = const(2)
    MPS_4   = const(3)
    MPS_10  = const(4)
    
    PERIODIC_MSB = [
        0x20,
        0x21,
        0x22,
        0x23,
        0x27,
    ]
    PERIODIC_LSB = {
        (REPEATABILITY_HIGH  , MPS_0_5): 0x32,
        (REPEATABILITY_MEDIUM, MPS_0_5): 0x24,
        (REPEATABILITY_LOW   , MPS_0_5): 0x2f,
        (REPEATABILITY_HIGH  , MPS_1  ): 0x30,
        (REPEATABILITY_MEDIUM, MPS_1  ): 0x26,
        (REPEATABILITY_LOW   , MPS_1  ): 0x2d,
        (REPEATABILITY_HIGH  , MPS_2  ): 0x36,
        (REPEATABILITY_MEDIUM, MPS_2  ): 0x20,
        (REPEATABILITY_LOW   , MPS_2  ): 0x2b,
        (REPEATABILITY_HIGH  , MPS_4  ): 0x34,
        (REPEATABILITY_MEDIUM, MPS_4  ): 0x22,
        (REPEATABILITY_LOW   , MPS_4  ): 0x29,
        (REPEATABILITY_HIGH  , MPS_10 ): 0x37,
        (REPEATABILITY_MEDIUM, MPS_10 ): 0x21,
        (REPEATABILITY_LOW   , MPS_10 ): 0x2a,
    }
    def __init__(self, i2c:machine.I2C, address:int):
        self.__l = logging.Logger('SHT31')
        self.__i2c = i2c
        self.__address = address

    def reset(self) -> bool:
        return self.__i2c.writeto(self.__address, bytes((0x30, 0xa2)), True) == 2

    def start_measurement(self, repeatability:int=REPEATABILITY_LOW, mps:int=MPS_1):
        if mps < 0 or len(SHT31.PERIODIC_MSB) <= mps:
            raise ValueError()
        if (repeatability, mps) not in SHT31.PERIODIC_LSB:
            raise ValueError()
        msb = SHT31.PERIODIC_MSB[mps]
        lsb = SHT31.PERIODIC_LSB[(repeatability, mps)]

        return self.__i2c.writeto(self.__address, bytes((msb, lsb)), True) == 2

    def stop_measurement(self):
        return self.__i2c.writeto(self.__address, bytes((0x30, 0x93)), True) == 2

    def set_heater(self, enable_heater:bool) -> bool:
        lsb = 0x6d if enable_heater else 0x66
        return self.__i2c.writeto(self.__address, bytes((0x30, lsb)), True) == 2

    def read_raw(self) -> bytes:
        try:
            return self.__i2c.readfrom_mem(self.__address, 0xe000, 6, addrsize=16)
        except OSError:
            return None

    def read(self) -> (float, float):
        raw = self.read_raw()
        if raw is None:
            return (None, None)
        values = struct.unpack('>HBHB', raw)
        return (
            values[0]/65535.0*175 -45,
            values[2]/65535.0*315 -49,
        )
Exemple #8
0
class GSM(object):
    "Wrapper of GSM to run GSM process in background thread"
    STATE_ERROR        = const(-1)
    STATE_INITIALIZING = const(0)
    STATE_ACTIVATING   = const(1)
    STATE_CONNECTING   = const(2)
    STATE_CONNECTED    = const(3)
    
    @staticmethod
    def __thread_proc(obj: GSM) -> None:
        runner = obj.__run() # type: SleepAwaitable
        try:
            while True:
                next(runner)
                runner.send(None)
        except StopIteration:
            pass
        print('GSM thread stopped')

    def __init__(self, uart: machine.UART):
        self.__lock = _thread.allocate_lock()
        self.__thread = None
        self.__gsm = _GSM(uart)
        self.__state = GSM.STATE_INITIALIZING
        self.__ifconfig = None # type: Optional[Tuple[str,str,str,str]]
        self.__l = logging.Logger('GSM')
        self.__wait = WaitEvent()
    
    def start(self, apn:str, user:str, password:str, timeout:int=30000) -> None:
        self.__apn = apn
        self.__user = user
        self.__password = password
        self.__timeout = timeout
        self.__gsm.initialize()
        self.__thread = _thread.start_new_thread(GSM.__thread_proc, (self,))

    
    def state(self) -> int:
        self.__lock.acquire()
        value = self.__state
        self.__lock.release()
        return value
    
    def __make_values(self) -> Dict[str, Union[Optional[Tuple[str,str,str,str]],int]]:
        return {
            'ifconfig': self.__ifconfig,
            'state': self.__state,
        }
    def __notify_values(self) -> None:
        values = self.__make_values()
        self.__wait.notify(values)
    
    def __set_state(self, state: int) -> None:
        self.__lock.acquire()
        self.__state = state
        self.__notify_values()
        self.__lock.release()
    
    def values(self) -> Dict[str, Union[Optional[Tuple[str,str,str,str]],int]]:
        self.__lock.acquire()
        values = self.__make_values()
        self.__lock.release()
        return values
    
    def is_connected(self) -> bool:
        v = self.values()
        return v is not None and v['ifconfig'] is not None
    
    def ifconfig(self) -> Optional[Tuple[str,str,str,str]]:
        v = self.values()
        return v['ifconfig'] if v is not None else None
    
    def wait_values(self, timeout: float = -1) -> Optional[Dict[str, Union[str,int]]]:
        return self.__wait.wait()
    
    async def __run(self):
        while True:
            if not await self.__gsm.reset():
                self.__l.error('Failed to reset GSM module.')
                self.__ifconfig = None
                self.__set_state(GSM.STATE_ERROR)
                await asyncio.sleep(1)
                continue
            self.__set_state(GSM.STATE_ACTIVATING)
        
            if not await self.__gsm.activate(self.__apn, self.__user, self.__password, self.__timeout):
                self.__l.error('Failed to activae network.')
                self.__set_state(GSM.STATE_ERROR)
                await asyncio.sleep(1)
                continue
            self.__set_state(GSM.STATE_CONNECTING)
            
            while not self.__gsm.__ppp.isconnected():
                await asyncio.sleep(1)
            self.__ifconfig = self.__gsm.__ppp.ifconfig()
            self.__set_state(GSM.STATE_CONNECTED)

            while True:    
                await asyncio.sleep(1)
Exemple #9
0
class _GSM(object):
    "Controls u-blox GSM Module"
    CR = const(0x0d)
    LF = const(0x0a)

    SOCKET_TCP = const(0)
    SOCKET_UDP = const(1)

    MAX_CONNECT_ID = const(12)
    MAX_SOCKET_DATA_SIZE = const(1460)

    def __init__(self, uart: machine.UART):
        self.__l = logging.Logger('GSM')
        self.__uart = uart
        self.__urcs = None #type: Optional[List[bytes]]
        self.__ppp = None #type: network.PPP
        self.__buffer = bytearray(1024)

    def initialize(self) -> None:
        "Initialize I/O ports and peripherals to communicate with the module."
        self.__l.debug('initialize')
        
        self.__uart.init(baudrate=115200, timeout=100)

    async def reset(self, timeout=1000) -> bool:
        "Turn on or reset the module and wait until the LTE commucation gets available."
        self.__urcs = []

        self.write_command(b'~+++')
        if not await self.write_command_wait(b'AT', b'OK', timeout=timeout):    # Check if the module can accept commands.
            self.__l.info("The module did not respond.")
            return False
        if not await self.write_command_wait(b'ATZ', b'OK', timeout=timeout):  # Reset module.
            self.__l.info("Failed to reset the module.")
            return False
        await asyncio.sleep_ms(100)
        if not await self.write_command_wait(b'ATE0', b'OK', timeout=timeout):  # Disable command echo
            self.__l.info("Failed to disable command echo.")
            return False
        if not await self.write_command_wait(b'AT+CFUN=1', b'OK', timeout=timeout):  # Enable RF.
            self.__l.info("Failed to enable RF.")
            return False
        
        buffer = bytearray(1024)
        
        self.__l.info('Waiting SIM goes active...')
        while True:
            result, responses = await self.execute_command(b'AT+CPIN?', buffer, timeout=1000)
            self.__l.info('AT+CPIN result={0}, response={1}'.format(result, len(responses)))
            if len(responses) == 0: return False
            if result: 
                return True

    async def get_IMEI(self) -> Optional[str]:
        "Gets International Mobile Equipment Identity (IMEI)"
        response = await self.execute_command_single_response(b'AT+GSN')
        return str(response, 'utf-8') if response is not None else None
    
    async def get_IMSI(self) -> Optional[str]:
        "Gets International Mobile Subscriber Identity (IMSI)"
        response = await self.execute_command_single_response(b'AT+CIMI')
        return str(response, 'utf-8') if response is not None else None

    async def get_phone_number(self) -> Optional[str]:
        "Gets phone number (subscriber number)"
        response = await self.execute_command_single_response(b'AT+CNUM', b'+CNUM:')
        return str(response[6:], 'utf-8') if response is not None else None

    async def get_RSSI(self) -> Optional[Tuple[int,int]]:
        "Gets received signal strength indication (RSSI)"
        response = await self.execute_command_single_response(b'AT+CSQ', b'+CSQ:')
        if response is None:
            return None
        try:
            s = str(response[5:], 'utf-8')
            rssi, ber = s.split(',', 2)
            return (int(rssi), int(ber))
        except ValueError:
            return None
    
    async def activate(self, access_point:str, user:str, password:str, timeout:int=None) -> bool:
        self.__l.info("Activating network...")
        while True:
            # Read network registration status.
            response = await self.execute_command_single_response(b'AT+CGREG?', b'+CGREG:', timeout)
            if response is None:
                raise GSMError('Failed to get registration status.')
            s = str(response, 'utf-8')
            self.__l.debug('AT+CGREG?:%s', s)
            n, stat = s.split(',')[:2]
            if stat == '4':  # Not registered and not searching (0), or unknown (4).
                raise GSMError('Invalid registration status.')
            elif stat == '0':
                await asyncio.sleep_ms(500)
            elif stat == '1' or stat == '5': # Registered.
                break
        # No action
        if not await self.write_command_wait(b'AT&D0', b'OK', timeout):
            return False
        
        # Enable verbose error
        if not await self.write_command_wait(b'AT+CMEE=2', b'OK', timeout):
            return False

        # Define a PDP context
        command = bytes('AT+CGDCONT=1,"IP","{0}"'.format(access_point), 'utf-8')
        if not await self.write_command_wait(command, b'OK', timeout):
            return False
        
        # Activate a PDP context
        if not await self.write_command_wait(b'AT+CGACT=1', b'OK', timeout):
            return False
        if not await self.write_command_wait(b'AT+CGACT?', b'OK', timeout):
            return False
        
        # Enter to PPP mode
        if not await self.write_command_wait(b'AT+CGDATA="PPP",1', b'CONNECT', timeout):
            return False

        # Construct PPP
        self.__l.info("Initializing PPP...")
        self.__ppp = network.PPP(self.__uart)

        # Activate PPP
        self.__l.info("Activating PPP...")
        self.__ppp.active(True)

        # Connect
        self.__l.info("Connecting PPP...")
        self.__ppp.connect(authmode=self.__ppp.AUTH_PAP, username=user, password=password)
        
        self.__l.info("PPP Connected.")
        return True
    

    def write(self, s:bytes) -> None:
        self.__l.debug('<- ' + str(s, 'utf-8'))
        self.__uart.write(s)
    
    def read(self, length:int) -> bytes:
        return self.__uart.read(length)
    
    def write_command(self, command:bytes) -> None:
        self.__l.debug('<- %s', command)
        self.__uart.write(command)
        self.__uart.write(b'\r')

    async def write_command_wait(self, command:bytes, expected_response:bytes, timeout:int=None) -> bool:
        self.write_command(command)
        return await self.wait_response(expected_response, timeout=timeout) is not None


    async def read_response_into(self, buffer:WriteableBufferType, offset:int=0, timeout:int=None) -> Optional[int]:
        buffer_length = len(buffer)
        response_length = 0
        state = 0
        start_time_ms = time.ticks_ms()
        cb = bytearray(1)
        while True:
            n = self.__uart.readinto(cb) #type: int
            if n is None:
                if timeout is not None and (time.ticks_ms()-start_time_ms) >= timeout:
                    return None
                try:
                    await asyncio.sleep_ms(1)
                except asyncio.CancelledError:
                    return None
                continue
            c = cb[0]

            # self.__l.debug('S:%d R:%c', state, c)
            if state == 0 and c == _GSM.CR:
                state = 1
            elif state == 1 and c == _GSM.LF:
                state = 2
            elif state == 1 and c == _GSM.CR:
                state = 1
            elif state == 1 and c != _GSM.LF:
                response_length = 0
                state = 0
            elif state == 2 and c == _GSM.CR:
                if response_length == 0:
                    state = 1   # Maybe there is another corresponding CR-LF followed by actual response data. So we have to return to state 1.
                else:
                    state = 4
            elif state == 2 and c != _GSM.CR:
                buffer[offset+response_length] = c
                response_length += 1
                if offset+response_length == buffer_length:
                    state = 3
            elif state == 3 and c == _GSM.CR:
                state = 4
            elif state == 4 and c == _GSM.LF:
                return response_length
    
    async def wait_response(self, expected_response:bytes, max_response_size:int=1024, timeout:Optional[int]=None) -> Optional[WriteableBufferType]:
        self.__l.debug('wait_response: target=%s', expected_response)
        response = memoryview(self.__buffer) if len(self.__buffer) <= max_response_size else bytearray(max_response_size)
        expected_length = len(expected_response)
        while True:
            length = await self.read_response_into(response, timeout=timeout)
            if length is None: return None
            self.__l.debug("wait_response: response=%s", str(response[:length], 'utf-8'))
            if length >= expected_length and response[:expected_length] == expected_response:
                return response[:length]
    
    async def wait_response_into(self, expected_response:bytes, response_buffer:bytearray, timeout:Optional[int]=None) -> Optional[WriteableBufferType]:
        self.__l.debug('wait_response_into: target=%s', expected_response)
        expected_length = len(expected_response)
        mv = memoryview(response_buffer)
        while True:
            length = await self.read_response_into(response_buffer, timeout=timeout)
            if length is None: return None
            self.__l.debug("wait_response_into: response=%s", str(mv[:length], 'utf-8'))
            if length >= expected_length and mv[:expected_length] == expected_response:
                return mv[:length]

    async def wait_prompt(self, expected_prompt:bytes, timeout:Optional[int]=None) -> bool:
        prompt_length = len(expected_prompt)
        index = 0
        start_time_ms = time.ticks_ms()
    
        while True:
            c = self.__uart.readchar()
            if c < 0:
                if time.ticks_ms() - start_time_ms > timeout:
                    return False
                await asyncio.sleep_ms(1)
                continue
            if expected_prompt[index] == c:
                index += 1
                if index == prompt_length:
                    return True
            else:
                index = 0
        
    async def execute_command(self, command:bytes, response_buffer:bytearray, index:int=0, expected_response_predicate:Callable[[memoryview],bool]=None, expected_response_list:List[bytes]=[b'OK'], timeout:int=None) -> Tuple[bool, List[memoryview]]:
        assert expected_response_predicate is not None or expected_response_list is not None
        if expected_response_predicate is None:
            expected_response_predicate = lambda mv: mv in expected_response_list 
        self.write_command(command)
        responses = []
        mv = memoryview(response_buffer)
        while True:
            length = await self.read_response_into(response_buffer, index, timeout=timeout)
            if length is None:
                return (False, responses)
            response = mv[index:index+length]
            responses.append(response)
            if expected_response_predicate(response):
                return (True, responses)
            index += length

    async def execute_command_single_response(self, command:bytes, starts_with:bytes=None, timeout:Optional[int]=None) -> Optional[BufferType]:
        result, responses = await self.execute_command(command, self.__buffer, timeout=timeout)
        if not result: return None
        starts_with_length = len(starts_with) if starts_with is not None else 0

        for response in responses:
            if starts_with_length == 0 and len(response) > 0:
                response = bytes(response)
                self.__l.debug('-> %s', response)
                return response
            if starts_with_length > 0 and len(response) >= starts_with_length and response[:starts_with_length] == starts_with:
                response = bytes(response)
                self.__l.debug('-> %s', response)
                return response
        return None