diff --git a/src/lib/networking/cell_conn.py b/src/lib/networking/cell_conn.py index e69de29..cb70210 100644 --- a/src/lib/networking/cell_conn.py +++ b/src/lib/networking/cell_conn.py @@ -0,0 +1,91 @@ +import asyncio + +from lib.logging import logger, LogLevel +from lib.networking.cell_modem import cell_modem, DEBUG_XBEE +from lib.runtime import runtime_timeout + + +async def cell_udp_open(host: str, port: int): + # Perform a DNS resolution to get the IP of the host + cdnsgip = (await cell_modem.send_and_receive(f'AT+CDNSGIP="{host}"'.encode(), timeout=10000)) + if not len(cdnsgip): + logger(f'Failed to get IP of "{host}" -> {cdnsgip}', source='CELL', level=LogLevel.error) + return False + ip = None + if host in cdnsgip[0]: + # Find the start of the IP address + start = cdnsgip[0].find(',"', cdnsgip[0].find(host)) + if start != -1: + start += 2 # Skip the ," characters + # Find the end of the IP address + end = cdnsgip[0].find('"', start) + if end != -1: + # Extract the IP address + ip = cdnsgip[start:end] + if not ip: + ip = host + + cipopen = await cell_modem.send_and_receive(f'AT+CIPOPEN=0,"UDP","{ip}",{port},8000'.encode()) + if cipopen[0] != '+CIPOPEN: 0,0': + logger(f'Failed to open UDP connection to "{host}:{port}" -> {cipopen}', source='CELL', level=LogLevel.error) + return False + return ip + + +async def cell_udp_send(host: str, port: int, data: bytes): + resolved_ip = await cell_udp_open(host, port) + if resolved_ip is False: + return False + + buffer = b'' + found = False + + data_len = len(data) + cell_modem.uart.write(f'AT+CIPSEND=0,{data_len},"{resolved_ip}",{port}\r') + await asyncio.sleep(0.1) + cell_modem.uart.write(data) + while not found: + resp = await cell_modem.xb_read(1) + if resp: + if DEBUG_XBEE: + logger(f'cell_udp_send :: {resp}', source='CELL', level=LogLevel.debug) + buffer += resp + if b'\r\nERROR' in buffer: + return False + else: + for a in [b'OK\r\n\r\n+CIPSEND:', b'\r\nRECV FROM:']: + if a in buffer: + found = True + return buffer + + +async def cell_udp_close(): + await cell_modem.send_and_receive(b'AT+CIPCLOSE=0') + + +async def cell_udp_receive(timeout: int): + @runtime_timeout(timeout) + async def inner(): + buffer = b'' + length = None + while True: + r = await cell_modem.xb_read(0.5) + if r: + buffer += r + response = buffer.decode('utf-8') + + # If we haven't found the length yet, try to find it + if length is None: + start = response.find('+IPD') + if start > -1: + end = response.find('\r\n', start) + if end > -1: # We have found the length + length = int(response[start + 4:end]) + response = response[end + 2:] # Remove '+IPD' and length from response + buffer = response.encode() # Update buffer + + # If we know the length, check if we have received the entire message + if length is not None and len(buffer) >= length: + return response[:length] + + return await inner() diff --git a/src/lib/networking/cell_modem.py b/src/lib/networking/cell_modem.py index 2367ca6..6619d28 100644 --- a/src/lib/networking/cell_modem.py +++ b/src/lib/networking/cell_modem.py @@ -19,6 +19,9 @@ bee_power_pin.value(0) class ModemError(Exception): def __init__(self, message: str, sent_command: bytes, response: list): + self.message = message + self.sent_command = sent_command + self.response = response super().__init__(f'{message}: {sent_command} -> {response}') @@ -29,6 +32,8 @@ async def xb_toggle_power(): class CellularModem: + # TODO: add lock that is used in send_and_recieve as well as by things that do raw reading + def __init__(self): self.uart = UART(2, baudrate=115200, timeout=4, rx=PIN_BEE_UART_RXD, tx=PIN_BEE_UART_TXD) @@ -69,12 +74,14 @@ class CellularModem: data = b'' expected_not_found = 0 while time.ticks_diff(time.ticks_ms(), t) < timeout: - read = await self.xb_read(50) + read = await self.xb_read(0.5) if read and read != data_to_send: if VERBOSE_XBEE_COMM: print(f'<====@ {read}') data += read - if read_until is not None and data.endswith(read_until): + if b'ERROR' in data: + raise ModemError('Recieved error', sent_command=data_to_send, response=data) + elif read_until is not None and data.endswith(read_until): break elif expected is not None: if data.endswith(expected): @@ -123,9 +130,11 @@ async def cellular_signal_strength(): async def cellular_ip() -> str: try: - ip_resp = await cell_modem.send_and_receive(b'AT+IPADDR') - if ip_resp[0].startswith('+IPADDR: '): - return ip_resp[0].strip('+IPADDR: ') + resp = await cell_modem.send_and_receive(b'AT+IPADDR') + if len(resp) and resp[0].startswith('+IPADDR: '): + return resp[0].strip('+IPADDR: ') + else: + return '0.0.0.0' except ModemError: return '0.0.0.0' @@ -133,6 +142,7 @@ async def cellular_ip() -> str: @runtime_timeout(CELLULAR_STARTUP_TIMEOUT) async def start_modem(): await xb_toggle_power() + cell_modem.xb_purge() while True: logger('Setting up modem', source='CELL') failures = 0 @@ -143,7 +153,10 @@ async def start_modem(): break else: if DEBUG_XBEE: - logger(f'Modem start failed: {at}', source='CELL', level=LogLevel.debug) + msg = 'Modem start failed' + if len(at): + msg += str(at) + logger(msg, source='CELL', level=LogLevel.debug) failures += 1 if failures == 5: return False @@ -166,10 +179,13 @@ async def start_modem(): # Assuming model is SIM7600 cpin = await cell_modem.send_and_receive(b'AT+CPIN?') - if not cpin[0].endswith(': READY'): + if len(cpin) and not cpin[0].endswith(': READY'): logger('NO SIM CARD', source='CELL', level=LogLevel.error) await asyncio.sleep(30) return False + elif not len(cpin): + logger(f'SIM card check failed: {cpin}', source='CELL', level=LogLevel.error) + return False signal_strength = await cellular_signal_strength() logger(f'Signal strength: {signal_strength}', source='CELL') @@ -211,16 +227,28 @@ async def start_modem(): logger(f'Modem start failed: {b"AT+CGACT?"} -> {cgact}', source='CELL', level=LogLevel.error) return False - await cell_modem.send_and_receive(f'AT+CGSOCKCONT=1,"IP","{CELLULAR_APN}"'.encode()) - await cell_modem.send_and_receive(b'AT+CSOCKSETPN=1', read_until=None, expected=b'OK\r\n') - await cell_modem.send_and_receive(b'AT+CIPMODE=0', read_until=None, expected=b'OK\r\n') - await cell_modem.send_and_receive(b'AT+NETOPEN', read_until=None, expected=b'OK\r\n') + try: + await cell_modem.send_and_receive(f'AT+CGSOCKCONT=1,"IP","{CELLULAR_APN}"'.encode()) + await cell_modem.send_and_receive(b'AT+CSOCKSETPN=1', read_until=None, expected=b'OK\r\n') + await cell_modem.send_and_receive(b'AT+CIPMODE=0', read_until=None, expected=b'OK\r\n') + await cell_modem.send_and_receive(b'AT+NETOPEN', read_until=None, expected=b'OK\r\n') + except ModemError as e: + logger(f'Modem start failed: {e.message} :: {e.sent_command} -> {e.response}', source='CELL', level=LogLevel.error) + return False - ip_get = await run_with_timeout(func=cellular_ip, timeout_sec=3) - if not ip_get.failure: - logger(f'IP: {ip_get.result}', source='CELL') - else: - logger(f'IP: 0.0.0.0', source='CELL') + ip = None + for i in range(10): + ip_get = await run_with_timeout(func=cellular_ip, timeout_sec=3) + if not ip_get.failure and ip_get.result != '0.0.0.0': + ip = ip_get.result + logger(f'IP: {ip}', source='CELL') + break + if DEBUG_XBEE: + logger(f'Waiting for an IP', source='CELL', level=LogLevel.debug) + await asyncio.sleep(1) + if ip is None: + logger(f'Did not get an IP', source='CELL', level=LogLevel.error) + return False return True @@ -229,11 +257,13 @@ async def start_modem_task(): while True: if await start_modem(): break - if not (await cell_modem.send_and_receive(b'AT+CPOF')): + try: + if not (await cell_modem.send_and_receive(b'AT+CPOF')): + await xb_toggle_power() + except ModemError: await xb_toggle_power() await asyncio.sleep(2) - - + logger('Modem ready', source='CELL') while True: await asyncio.sleep(100) diff --git a/src/lib/networking/message.py b/src/lib/networking/message.py new file mode 100644 index 0000000..59ab271 --- /dev/null +++ b/src/lib/networking/message.py @@ -0,0 +1,5 @@ +def freematics_checksum(data): + cs = 0 + for i in data: + cs += ord(i) + return '{:02x}'.format(cs & 0xFF)