diff --git a/Doc/Building.md b/Doc/Building.md index 7048d98..0ac0f59 100644 --- a/Doc/Building.md +++ b/Doc/Building.md @@ -9,4 +9,5 @@ cp build/mpy-cross ``` The `upload.sh` script will manage building this MicroPython project. If you get an error on the `Erase` step, that's -fine and due to the files not existing to be erased. \ No newline at end of file +fine and due to the files not existing to be erased. Make sure you aren't connected to the device's serial port when +running the script. \ No newline at end of file diff --git a/src/lib/networking/cell_modem.py b/src/lib/networking/cell_modem.py index b294235..77d28a8 100644 --- a/src/lib/networking/cell_modem.py +++ b/src/lib/networking/cell_modem.py @@ -5,7 +5,7 @@ import time import machine from machine import Pin -from config import CELLULAR_APN, CELLULAR_STARTUP_TIMEOUT +from config import CELLULAR_APN, CELLULAR_STARTUP_TIMEOUT, TRACCAR_CONN_MODE from lib.logging import logger, LogLevel from lib.runtime import timeout, uTimeoutError, run_with_timeout @@ -15,7 +15,6 @@ PIN_BEE_UART_TXD = 2 bee_power_pin = Pin(PIN_BEE_POWER, Pin.OUT) bee_power_pin.value(0) -time.sleep(0.5) bee_power_pin.value(1) @@ -43,7 +42,7 @@ class CellularModem: if not res: raise OSError("Modem not responding") - async def send_command(self, command, require_ok: bool = False) -> tuple: + async def send_command(self, command, require_ok: bool = False, expecing_plus: bool = False, read_until: bytes = None, clean_lines: bool = True) -> tuple: lines = [] async with self._lock: while self.uart.any(): @@ -56,9 +55,20 @@ class CellularModem: lines.append(raw_line) if raw_line == b'ERROR\r\n': raise ModemErrorResponse(code=(command, lines)) - if len(lines) and lines[-1] == b'OK\r\n': - break - response = tuple(x for x in [x.decode().strip('\r\n') for x in lines] if len(x)) + if len(lines): + print(raw_line) + if expecing_plus: + if lines[-1].decode().startswith('+'): + break + elif read_until: + if lines[-1] == read_until: + break + elif lines[-1] == b'OK\r\n': + break + if clean_lines: + response = tuple(x for x in [x.decode().strip('\r\n') for x in lines] if len(x)) + else: + response = lines if require_ok: if response[-1] != 'OK': raise ModemErrorResponse(code=(command, lines)) @@ -70,7 +80,7 @@ cellular = CellularModem() def _parse_csq(csq_value: int): if csq_value == 99: - return 'disconnected' + return 'unknown' elif csq_value == 31: return '51 dBm or greater' elif csq_value == 0: @@ -84,7 +94,7 @@ def _parse_csq(csq_value: int): async def cellular_wake_modem(): - i = 5 + i = 2 while i > 0: resp = await cellular.send_command('AT') if len(resp) == 1 and resp[0] == 'OK': @@ -98,7 +108,11 @@ async def cellular_wake_modem(): async def cellular_wait_cpsi(): while True: cpsi_resp = await cellular.send_command('AT+CPSI?') - parts = cpsi_resp[0].lstrip('+CPSI: ').split(',') + try: + parts = cpsi_resp[0].lstrip('+CPSI: ').split(',') + except: + print(cpsi_resp) + raise if parts[1] == 'Online': return True, parts elif parts[1] == 'Low Power Mode': @@ -140,20 +154,13 @@ async def cellular_signal_strength(): return signal_strength -async def cellular_ip(): - @timeout(3) - async def inner(): - try: - await cellular.send_command('AT+IPADDR') - except ModemErrorResponse as e: - if 'Network not opened' in e.lines[0]: - return '0.0.0.0' - +async def cellular_ip() -> str: try: - ip = await inner() - except uTimeoutError: - ip = '0.0.0.0' - return ip + ip_resp = await cellular.send_command('AT+IPADDR') + if ip_resp[0].startswith('+IPADDR: '): + return ip_resp[0].strip('+IPADDR: ') + except ModemErrorResponse: + return '0.0.0.0' async def cellular_check_service_ready(): @@ -165,10 +172,6 @@ async def cellular_check_service_ready(): async def restart_modem(): - async def reset(): - await cellular.send_command('ATZ', require_ok=True) - - await run_with_timeout(func=reset, timeout_sec=2) await asyncio.sleep(0.5) bee_power_pin.value(0) time.sleep(0.5) @@ -192,8 +195,17 @@ async def cellular_check_connected(): @timeout(CELLULAR_STARTUP_TIMEOUT) async def start_modem(): while True: - if (await run_with_timeout(func=cellular_wake_modem, timeout_sec=30)).failure: - logger('Modem wake-up timed out', source='CELL', level=LogLevel.error) + failures = -1 + try: + if (await run_with_timeout(func=cellular_wake_modem, timeout_sec=5)).failure: + logger('Modem wake-up timed out', source='CELL', level=LogLevel.error) + failures += 1 + if failures == 11: + machine.reset() + await restart_modem() + continue + except ModemErrorResponse: + # Sporadic ERROR responses await restart_modem() continue @@ -239,24 +251,108 @@ async def start_modem(): await restart_modem() continue - await cellular.send_command(f'AT+CGSOCKCONT=1,"IP","{CELLULAR_APN}"', require_ok=True) - await cellular.send_command('AT+CSOCKSETPN=1', require_ok=True) - await cellular.send_command('AT+CIPMODE=0', require_ok=True) - await cellular.send_command('AT+NETOPEN', require_ok=True) - break + try: + await cellular.send_command(f'AT+CGSOCKCONT=1,"IP","{CELLULAR_APN}"', require_ok=True) + await cellular.send_command('AT+CSOCKSETPN=1', require_ok=True) + await cellular.send_command('AT+CIPMODE=0', require_ok=True) + # await cellular.send_command('AT+CNMP=38', require_ok=True) + await cellular.send_command('AT+NETOPEN', require_ok=True) + break + except ModemErrorResponse as e: + print(e) + await restart_modem() + break + + 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') - logger(f'IP: {await cellular_ip()}', source='CELL') logger(f'Signal strength: {await cellular_signal_strength()}', source='CELL') + logger('Modem initialized', source='CELL') + + +async def cellular_start_http(): + try: + await cellular.send_command('AT+HTTPINIT') + return True + except ModemErrorResponse: + return False async def start_modem_task(): logger('Initalizing modem', source='CELL') - while True: - try: - gc.collect() - await start_modem() + + async def inner(): + while True: + try: + gc.collect() + await start_modem() + except uTimeoutError: + await asyncio.sleep(10) + continue + + if TRACCAR_CONN_MODE == 'http': + await asyncio.sleep(0.5) + success = False + for i in range(30): + httpinit = await run_with_timeout(func=cellular_start_http, timeout_sec=5) + if httpinit.failure: + logger('AT+HTTPINIT timeout', source='CELL', level=LogLevel.error) + await restart_modem() + continue + if not httpinit.result: + logger('AT+HTTPINIT failure', source='CELL', level=LogLevel.error) + await restart_modem() + continue + else: + success = True + if not success: + continue + break - except uTimeoutError: - await asyncio.sleep(10) + + while True: + x = await run_with_timeout(func=inner, timeout_sec=300) + if not x.failure: + break + gc.collect() + + await cellular_send_http('http://postman-echo.com/ip', 'get') + # TODO: loop to reconnect modem + + +@timeout(10) +async def cellular_send_http(url: str, method: str, data: str = None): + m = method.upper() + if m == 'GET': + mode = 0 + elif m == 'POST': + if not data: + raise Exception + mode = 1 + else: + raise Exception + + await cellular.send_command(f'AT+HTTPPARA="URL","{url}"') + _, resp = await cellular.send_command(f'AT+HTTPACTION={mode}', expecing_plus=True) + + _, status_code, data_len = resp.strip('+HTTPACTION: ').split(',') + if status_code != '200': + logger(f'HTTP {m} failed: {status_code}', source='CELL', level=LogLevel.error) + return + + data = await cellular.send_command(f'AT+HTTPREAD={data_len}', read_until=b'+HTTPREAD:0\r\n', clean_lines=False) + if data[0] != b'OK\r\n': + raise Exception + data = list(data) + del data[0] + del data[0] + del data[-1] + print(data) + + +asyncio.run(start_modem_task())