|
|
|
@ -1,86 +1,97 @@
|
|
|
|
|
import asyncio
|
|
|
|
|
import gc
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
import machine
|
|
|
|
|
from machine import Pin
|
|
|
|
|
from machine import UART, Pin
|
|
|
|
|
|
|
|
|
|
from config import CELLULAR_APN, CELLULAR_STARTUP_TIMEOUT, TRACCAR_HOST
|
|
|
|
|
from config import CELLULAR_STARTUP_TIMEOUT, CELLULAR_APN
|
|
|
|
|
from lib.logging import logger, LogLevel
|
|
|
|
|
from lib.runtime import timeout, uTimeoutError, run_with_timeout
|
|
|
|
|
from lib.runtime import runtime_timeout, uTimeoutError, run_with_timeout
|
|
|
|
|
|
|
|
|
|
PIN_BEE_POWER = 27
|
|
|
|
|
PIN_BEE_UART_RXD = 35
|
|
|
|
|
PIN_BEE_UART_TXD = 2
|
|
|
|
|
|
|
|
|
|
VERBOSE_XBEE_COMM = False
|
|
|
|
|
DEBUG_XBEE = True
|
|
|
|
|
bee_power_pin = Pin(PIN_BEE_POWER, Pin.OUT)
|
|
|
|
|
bee_power_pin.value(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ModemError(Exception):
|
|
|
|
|
def __init__(self, message: str, sent_command: bytes, response: list):
|
|
|
|
|
super().__init__(f'{message}: {sent_command} -> {response}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def xb_toggle_power():
|
|
|
|
|
bee_power_pin.value(0)
|
|
|
|
|
await asyncio.sleep(0.2)
|
|
|
|
|
bee_power_pin.value(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ModemErrorResponse(Exception):
|
|
|
|
|
def __init__(self, message="Modem returned an error", code: tuple = None):
|
|
|
|
|
self.command = code[0]
|
|
|
|
|
self.lines = code[1]
|
|
|
|
|
super().__init__(f'{message}: {code}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CellularModem:
|
|
|
|
|
_lock = asyncio.Lock()
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.uart = machine.UART(2, baudrate=115200, timeout=4, rx=PIN_BEE_UART_RXD, tx=PIN_BEE_UART_TXD)
|
|
|
|
|
self.swriter = asyncio.StreamWriter(self.uart, {})
|
|
|
|
|
self.sreader = asyncio.StreamReader(self.uart)
|
|
|
|
|
asyncio.create_task(self.keep_alive())
|
|
|
|
|
self.uart = UART(2, baudrate=115200, timeout=4, rx=PIN_BEE_UART_RXD, tx=PIN_BEE_UART_TXD)
|
|
|
|
|
|
|
|
|
|
async def keep_alive(self):
|
|
|
|
|
while True:
|
|
|
|
|
await asyncio.sleep(40)
|
|
|
|
|
async with self._lock:
|
|
|
|
|
res = await self.send_command("AT")
|
|
|
|
|
if not res:
|
|
|
|
|
raise OSError("Modem not responding")
|
|
|
|
|
async def xb_read(self, timeout):
|
|
|
|
|
@runtime_timeout(timeout)
|
|
|
|
|
async def inner():
|
|
|
|
|
if self.uart.any():
|
|
|
|
|
return self.uart.read()
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
try:
|
|
|
|
|
return await inner()
|
|
|
|
|
except uTimeoutError:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def xb_purge(self):
|
|
|
|
|
while self.uart.any():
|
|
|
|
|
# Clear any initial garbage
|
|
|
|
|
self.uart.read(1)
|
|
|
|
|
await self.swriter.awrite(f'{command}\r\n')
|
|
|
|
|
for i in range(10):
|
|
|
|
|
raw_line = await self.sreader.readline()
|
|
|
|
|
print(raw_line)
|
|
|
|
|
if len(raw_line.decode().strip('\r\n')) > 0 and raw_line != f'{command}\r\r\n'.encode():
|
|
|
|
|
lines.append(raw_line)
|
|
|
|
|
if raw_line == b'ERROR\r\n':
|
|
|
|
|
raise ModemErrorResponse(code=(command, lines))
|
|
|
|
|
if len(lines):
|
|
|
|
|
if expecing_plus:
|
|
|
|
|
if lines[-1].decode().startswith('+'):
|
|
|
|
|
self.uart.read()
|
|
|
|
|
|
|
|
|
|
async def send_and_receive(self, data_to_send: bytes, read_until: any = b'OK\r\n', expected: bytes = None, timeout: int = 1000) -> list:
|
|
|
|
|
if not isinstance(data_to_send, bytes):
|
|
|
|
|
raise Exception
|
|
|
|
|
if read_until is not None and not isinstance(read_until, bytes):
|
|
|
|
|
raise Exception
|
|
|
|
|
if data_to_send.endswith(b'\r\n'):
|
|
|
|
|
raise Exception
|
|
|
|
|
if read_until is not None and expected is not None:
|
|
|
|
|
raise Exception
|
|
|
|
|
|
|
|
|
|
data_to_send += b'\r\n'
|
|
|
|
|
|
|
|
|
|
if VERBOSE_XBEE_COMM:
|
|
|
|
|
print(f'@====> {data_to_send}')
|
|
|
|
|
self.uart.write(data_to_send)
|
|
|
|
|
|
|
|
|
|
# Wait and read the response
|
|
|
|
|
t = time.ticks_ms()
|
|
|
|
|
data = b''
|
|
|
|
|
expected_not_found = 0
|
|
|
|
|
while time.ticks_diff(time.ticks_ms(), t) < timeout:
|
|
|
|
|
read = await self.xb_read(50)
|
|
|
|
|
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):
|
|
|
|
|
break
|
|
|
|
|
elif read_until:
|
|
|
|
|
if lines[-1] == read_until:
|
|
|
|
|
elif expected is not None:
|
|
|
|
|
if data.endswith(expected):
|
|
|
|
|
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))
|
|
|
|
|
return response
|
|
|
|
|
expected_not_found += 1
|
|
|
|
|
if expected_not_found == 3:
|
|
|
|
|
raise ModemError(f'Did not recieve expected {expected}', sent_command=data_to_send, response=data)
|
|
|
|
|
return [x.decode() for x in data.replace(b'\r\r\n', b'\r\n').split(b'\r\n') if len(x)] if data else []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cellular = CellularModem()
|
|
|
|
|
cell_modem = CellularModem()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_csq(csq_value: int):
|
|
|
|
|
def parse_csq(csq_value: int):
|
|
|
|
|
if csq_value == 99:
|
|
|
|
|
return 'unknown'
|
|
|
|
|
return 'unknown (likely disconnected)'
|
|
|
|
|
elif csq_value == 31:
|
|
|
|
|
return '51 dBm or greater'
|
|
|
|
|
elif csq_value == 0:
|
|
|
|
@ -93,58 +104,13 @@ def _parse_csq(csq_value: int):
|
|
|
|
|
return 'Invalid CSQ value'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def cellular_wake_modem():
|
|
|
|
|
i = 2
|
|
|
|
|
while i > 0:
|
|
|
|
|
resp = await cellular.send_command('AT')
|
|
|
|
|
if len(resp) == 1 and resp[0] == 'OK':
|
|
|
|
|
i -= 1
|
|
|
|
|
else:
|
|
|
|
|
logger('\n'.join(resp), source='CELL')
|
|
|
|
|
i += 1
|
|
|
|
|
await asyncio.sleep(0.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def cellular_wait_cpsi():
|
|
|
|
|
while True:
|
|
|
|
|
cpsi_resp = await cellular.send_command('AT+CPSI?')
|
|
|
|
|
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':
|
|
|
|
|
return False, parts
|
|
|
|
|
await asyncio.sleep(0.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def cellular_wait_creg():
|
|
|
|
|
# https://community.hologram.io/t/sim7600-in-ovms-board-t-mobile-registration-denied/4616/12
|
|
|
|
|
async def check():
|
|
|
|
|
resp = await cellular.send_command('AT+CREG?')
|
|
|
|
|
if len(resp) == 2 and resp[0].startswith('+CREG: '):
|
|
|
|
|
return resp[0] == '+CREG: 0,1' or resp[0] == '+CREG: 0,5', resp
|
|
|
|
|
else:
|
|
|
|
|
raise Exception(resp)
|
|
|
|
|
|
|
|
|
|
i = 0
|
|
|
|
|
while True:
|
|
|
|
|
ready, code = await check()
|
|
|
|
|
if ready:
|
|
|
|
|
return ready, code
|
|
|
|
|
i += 1
|
|
|
|
|
await asyncio.sleep(0.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def cellular_signal_strength():
|
|
|
|
|
@timeout(10)
|
|
|
|
|
@runtime_timeout(10)
|
|
|
|
|
async def inner():
|
|
|
|
|
resp = await cellular.send_command('AT+CSQ')
|
|
|
|
|
resp = await cell_modem.send_and_receive(b'AT+CSQ')
|
|
|
|
|
if len(resp) == 2 and resp[0].startswith('+CSQ: '):
|
|
|
|
|
s = int(resp[0].lstrip('+CSQ: ').split(',')[0])
|
|
|
|
|
return _parse_csq(s)
|
|
|
|
|
return parse_csq(s)
|
|
|
|
|
else:
|
|
|
|
|
raise Exception(resp)
|
|
|
|
|
|
|
|
|
@ -157,112 +123,98 @@ async def cellular_signal_strength():
|
|
|
|
|
|
|
|
|
|
async def cellular_ip() -> str:
|
|
|
|
|
try:
|
|
|
|
|
ip_resp = await cellular.send_command('AT+IPADDR')
|
|
|
|
|
ip_resp = await cell_modem.send_and_receive(b'AT+IPADDR')
|
|
|
|
|
if ip_resp[0].startswith('+IPADDR: '):
|
|
|
|
|
return ip_resp[0].strip('+IPADDR: ')
|
|
|
|
|
except ModemErrorResponse:
|
|
|
|
|
except ModemError:
|
|
|
|
|
return '0.0.0.0'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def cellular_check_service_ready():
|
|
|
|
|
resp = await cellular.send_command('AT+CGATT?')
|
|
|
|
|
if len(resp) == 2 and resp[0].startswith('+CGATT: '):
|
|
|
|
|
return resp[0] == '+CGATT: 1', resp[0]
|
|
|
|
|
else:
|
|
|
|
|
raise Exception(resp)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def restart_modem():
|
|
|
|
|
await asyncio.sleep(0.5)
|
|
|
|
|
bee_power_pin.value(0)
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
bee_power_pin.value(1)
|
|
|
|
|
await asyncio.sleep(0.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def cellular_check_connected():
|
|
|
|
|
cpsi = await run_with_timeout(func=cellular_wait_cpsi, timeout_sec=1)
|
|
|
|
|
if cpsi.failure:
|
|
|
|
|
return False
|
|
|
|
|
if cpsi.result[1][0] != 'NO SERVICE':
|
|
|
|
|
return False
|
|
|
|
|
if (await cellular_ip()) == '0.0.0.0':
|
|
|
|
|
return False
|
|
|
|
|
if (await cellular_signal_strength()) == 'disconnected':
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@timeout(CELLULAR_STARTUP_TIMEOUT)
|
|
|
|
|
@runtime_timeout(CELLULAR_STARTUP_TIMEOUT)
|
|
|
|
|
async def start_modem():
|
|
|
|
|
await xb_toggle_power()
|
|
|
|
|
while True:
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
sim_resp = await cellular.send_command('AT+CPIN?')
|
|
|
|
|
if not sim_resp[0].endswith(' READY'):
|
|
|
|
|
msg = '\n'.join(sim_resp)
|
|
|
|
|
logger(f'SIM card not ready: {msg}', source='CELL', level=LogLevel.error)
|
|
|
|
|
await restart_modem()
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
cmds = ['ATE0', 'ATI']
|
|
|
|
|
for cmd in cmds:
|
|
|
|
|
logger('Setting up modem', source='CELL')
|
|
|
|
|
failures = 0
|
|
|
|
|
while True:
|
|
|
|
|
init_resp = await cellular.send_command(cmd)
|
|
|
|
|
logger('\n'.join((x for x in init_resp if x != 'OK')), source='CELL', level=LogLevel.info)
|
|
|
|
|
if init_resp[-1] != 'OK':
|
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
|
at = await cell_modem.send_and_receive(b'AT')
|
|
|
|
|
if len(at) and at[-1] == 'OK':
|
|
|
|
|
logger('Modem woken up', source='CELL')
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
simcom_model_resp = await cellular.send_command('AT+SIMCOMATI')
|
|
|
|
|
model = simcom_model_resp[1]
|
|
|
|
|
if not model.startswith('Model:'):
|
|
|
|
|
raise Exception
|
|
|
|
|
if 'SIM7600' not in model:
|
|
|
|
|
raise Exception('Not implemented')
|
|
|
|
|
|
|
|
|
|
cpsi = await run_with_timeout(func=cellular_wait_cpsi, timeout_sec=1)
|
|
|
|
|
if cpsi.failure:
|
|
|
|
|
logger('AT+CPSI timeout', source='CELL', level=LogLevel.error)
|
|
|
|
|
await restart_modem()
|
|
|
|
|
continue
|
|
|
|
|
if not cpsi.result[0]:
|
|
|
|
|
logger('Bad AT+CPSI status', source='CELL', level=LogLevel.error)
|
|
|
|
|
await restart_modem()
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
creg = await run_with_timeout(func=cellular_wait_creg, timeout_sec=1)
|
|
|
|
|
if creg.failure:
|
|
|
|
|
logger('AT+CREG timeout, no signal?', source='CELL', level=LogLevel.warning)
|
|
|
|
|
elif not creg.result[0]:
|
|
|
|
|
logger(f'Bad AT+CREG response: {creg.result[1]}', source='CELL', level=LogLevel.error)
|
|
|
|
|
await restart_modem()
|
|
|
|
|
if DEBUG_XBEE:
|
|
|
|
|
logger(f'Modem start failed: {at}', source='CELL', level=LogLevel.debug)
|
|
|
|
|
failures += 1
|
|
|
|
|
if failures == 5:
|
|
|
|
|
return False
|
|
|
|
|
await asyncio.sleep(0.5)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
await cellular.send_command(f'AT+CGDCONT=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()
|
|
|
|
|
await cell_modem.send_and_receive(b'ATE0', expected=b'OK\r\n', read_until=None)
|
|
|
|
|
except ModemError as e:
|
|
|
|
|
logger(f'Modem start failed: {e}', source='CELL', level=LogLevel.error)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
ati = await cell_modem.send_and_receive(b'ATI')
|
|
|
|
|
if not isinstance(ati, list) or ati[-1] != 'OK':
|
|
|
|
|
logger(f'Modem start failed: {b"ATI"} -> {ati}', source='CELL', level=LogLevel.error)
|
|
|
|
|
return False
|
|
|
|
|
for line in ati:
|
|
|
|
|
if line != 'OK':
|
|
|
|
|
logger(line, source='CELL')
|
|
|
|
|
# Assuming model is SIM7600
|
|
|
|
|
|
|
|
|
|
cpin = await cell_modem.send_and_receive(b'AT+CPIN?')
|
|
|
|
|
if not cpin[0].endswith(': READY'):
|
|
|
|
|
logger('NO SIM CARD', source='CELL', level=LogLevel.error)
|
|
|
|
|
await asyncio.sleep(30)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
signal_strength = await cellular_signal_strength()
|
|
|
|
|
logger(f'Signal strength: {signal_strength}', source='CELL')
|
|
|
|
|
|
|
|
|
|
cpsi = await cell_modem.send_and_receive(b'AT+CPSI?')
|
|
|
|
|
if 'OK' not in cpsi:
|
|
|
|
|
logger(f'Modem start failed: {b"AT+CPSI?"} -> {cpsi}', source='CELL', level=LogLevel.error)
|
|
|
|
|
return False
|
|
|
|
|
cpsi_parts = cpsi[0].lstrip('+CPSI: ').split(',')
|
|
|
|
|
if cpsi_parts[0] == 'NO SERVICE':
|
|
|
|
|
logger('No service', source='CELL', level=LogLevel.warning)
|
|
|
|
|
await asyncio.sleep(10)
|
|
|
|
|
return False
|
|
|
|
|
elif cpsi_parts[1] != 'Online':
|
|
|
|
|
logger(f'Modem not online: {",".join(cpsi_parts)}', source='CELL', level=LogLevel.error)
|
|
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
logger(f'Connected to {cpsi_parts[0]}', source='CELL')
|
|
|
|
|
|
|
|
|
|
for cmd in [b'AT+CREG?', b'AT+CGREG?']:
|
|
|
|
|
r = await cell_modem.send_and_receive(cmd)
|
|
|
|
|
if len(r):
|
|
|
|
|
# Page 85, 185
|
|
|
|
|
if ' 0,2' in r[0] or ' 0,0' in r[0]:
|
|
|
|
|
logger(f'Modem not attached to network: {r}', source='CELL', level=LogLevel.warning)
|
|
|
|
|
await asyncio.sleep(30)
|
|
|
|
|
return False
|
|
|
|
|
elif ' 0,1' not in r[0] and ' 0,5' not in r[0]:
|
|
|
|
|
logger(f'Modem start failed: {cmd} -> {r}', source='CELL', level=LogLevel.error)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
cgact = await cell_modem.send_and_receive(b'AT+CGACT?')
|
|
|
|
|
success = False
|
|
|
|
|
for line in cgact:
|
|
|
|
|
if '+CGACT: 1,' in line:
|
|
|
|
|
success = True
|
|
|
|
|
break
|
|
|
|
|
if not success:
|
|
|
|
|
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')
|
|
|
|
|
|
|
|
|
|
ip_get = await run_with_timeout(func=cellular_ip, timeout_sec=3)
|
|
|
|
|
if not ip_get.failure:
|
|
|
|
@ -270,122 +222,19 @@ async def start_modem():
|
|
|
|
|
else:
|
|
|
|
|
logger(f'IP: 0.0.0.0', source='CELL')
|
|
|
|
|
|
|
|
|
|
logger(f'Signal strength: {await cellular_signal_strength()}', source='CELL')
|
|
|
|
|
logger('Modem initialized', source='CELL')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def cellular_start_http():
|
|
|
|
|
if TRACCAR_HOST.startswith('http://'):
|
|
|
|
|
code = 'AT+HTTPINIT'
|
|
|
|
|
elif TRACCAR_HOST.startswith('https://'):
|
|
|
|
|
code = 'AT+CCHSTART'
|
|
|
|
|
else:
|
|
|
|
|
raise Exception
|
|
|
|
|
try:
|
|
|
|
|
if TRACCAR_HOST.startswith('https://'):
|
|
|
|
|
await cellular.send_command('AT+CSSLCFG="sslversion",0,4')
|
|
|
|
|
await cellular.send_command('AT+CSSLCFG="authmode",0,0')
|
|
|
|
|
await cellular.send_command('AT+CSSLCFG="ignorelocaltime",0,1')
|
|
|
|
|
await cellular.send_command('AT+CSSLCFG="enableSNI",0,1')
|
|
|
|
|
await cellular.send_command('AT+CCHSET=1')
|
|
|
|
|
await cellular.send_command('AT+CCHSSLCFG=0,0')
|
|
|
|
|
await cellular.send_command(code)
|
|
|
|
|
return True
|
|
|
|
|
except ModemErrorResponse:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def start_modem_task():
|
|
|
|
|
logger('Initalizing modem', source='CELL')
|
|
|
|
|
|
|
|
|
|
async def inner():
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
gc.collect()
|
|
|
|
|
await start_modem()
|
|
|
|
|
except uTimeoutError:
|
|
|
|
|
await asyncio.sleep(10)
|
|
|
|
|
continue
|
|
|
|
|
if await start_modem():
|
|
|
|
|
break
|
|
|
|
|
if not (await cell_modem.send_and_receive(b'AT+CPOF')):
|
|
|
|
|
await xb_toggle_power()
|
|
|
|
|
await asyncio.sleep(2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
x = await run_with_timeout(func=inner, timeout_sec=300)
|
|
|
|
|
if not x.failure:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
gc.collect()
|
|
|
|
|
|
|
|
|
|
cellular.uart.write('AT+CIPOPEN=0,"UDP","64.227.105.164",10001,8000')
|
|
|
|
|
data = 'Hello!!'
|
|
|
|
|
cellular.uart.write(f'AT+CIPSEND=0,{len(data.encode())},"64.227.105.164",10001')
|
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
|
cellular.uart.write(data)
|
|
|
|
|
await cellular.send_command('AT+CIPCLOSE=0')
|
|
|
|
|
|
|
|
|
|
# TODO: loop to reconnect modem
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@timeout(30)
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
# 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 cellular.send_command('AT+CRESET')
|
|
|
|
|
# # await restart_modem()
|
|
|
|
|
# # break
|
|
|
|
|
|
|
|
|
|
await cellular_start_http()
|
|
|
|
|
|
|
|
|
|
if TRACCAR_HOST.startswith('http://'):
|
|
|
|
|
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)
|
|
|
|
|
elif TRACCAR_HOST.startswith('https://'):
|
|
|
|
|
# HTTPS does not work.
|
|
|
|
|
# ModemErrorResponse: Modem returned an error: ('AT+CCHOPEN=0,"postman-echo.com",443,2', [b'ERROR\r\n'])
|
|
|
|
|
parts = url.replace('https://', '', 1).split('/')
|
|
|
|
|
host = parts.pop(0)
|
|
|
|
|
path = '/' + '/'.join(parts)
|
|
|
|
|
await cellular.send_command(f'AT+CCHOPEN=0,"{host}",443,2')
|
|
|
|
|
content = f"""GET {path} HTTP/1.1
|
|
|
|
|
Host: {host}
|
|
|
|
|
User-Agent: Freematics
|
|
|
|
|
Proxy-Connection: keep-alive
|
|
|
|
|
Content-Length: 0"""
|
|
|
|
|
await cellular.send_command(f'AT+CCHSEND=0,{len(content.encode())}')
|
|
|
|
|
resp = await cellular.send_command('AT+CCHRECV=0', read_until=b'+CCHRECV: 0,0\r\n')
|
|
|
|
|
print(resp)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
raise Exception
|
|
|
|
|
|
|
|
|
|
if data[0] != b'OK\r\n':
|
|
|
|
|
raise Exception
|
|
|
|
|
data = list(data)
|
|
|
|
|
del data[0]
|
|
|
|
|
del data[0]
|
|
|
|
|
del data[-1]
|
|
|
|
|
data_clean = ''.join([x.decode() for x in data]).strip('\r\n')
|
|
|
|
|
return data_clean
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
asyncio.run(start_modem_task())
|
|
|
|
|
await asyncio.sleep(100)
|
|
|
|
|
# TODO: handle reconnection here
|
|
|
|
|