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)
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)
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)
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
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)
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
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, )
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)
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