implement cellular connection
This commit is contained in:
parent
18be663658
commit
0593a52501
|
@ -2,6 +2,7 @@ ESP32_GENERIC-20240602-v1.23.0.bin
|
||||||
config.py
|
config.py
|
||||||
.idea
|
.idea
|
||||||
test.py
|
test.py
|
||||||
|
mpy-cross
|
||||||
|
|
||||||
# ---> Python
|
# ---> Python
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
Compiling the MicroPython sourcecode to `.mpy` turns it into "frozen bytecode" which results in lower memory consumption
|
||||||
|
during execution.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/micropython/micropython.git
|
||||||
|
cd micropython/mpy-cross
|
||||||
|
make
|
||||||
|
cp build/mpy-cross <project root>
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
|
@ -7,11 +7,22 @@ TRACCAR_HOST = 'https://traccar.example.com'
|
||||||
# WiFi
|
# WiFi
|
||||||
WIFI_SSID = 'homenetwork'
|
WIFI_SSID = 'homenetwork'
|
||||||
WIFI_PASSWORD = 'password123'
|
WIFI_PASSWORD = 'password123'
|
||||||
WIFI_CONNECT_TIMEOUT = 10
|
|
||||||
|
# Cellular
|
||||||
|
CELLULAR_APN = 'hologram'
|
||||||
|
CELLULAR_STARTUP_TIMEOUT = 60 # restart the modem if it doesn't initalize
|
||||||
|
|
||||||
|
# GNSS
|
||||||
|
GNSS_LOCATION_RETRY = 5
|
||||||
|
GNSS_OFFLINE_BUFFER_SIZE = 200
|
||||||
|
|
||||||
# Intervals
|
# Intervals
|
||||||
# All in seconds.
|
# All in seconds.
|
||||||
INTERVAL_WIFI_SCAN = 10
|
INTERVAL_WIFI_SCAN = 10
|
||||||
INTERVAL_NTP_SYNC = 86400
|
INTERVAL_CLOCK_SYNC = 86400
|
||||||
INTERVAL_ACTIVE_POSITION_SEND = 120 # only when active and moving
|
INTERVAL_ACTIVE_POSITION_SEND = 120 # only when active and moving
|
||||||
INTERVAL_CLOCK_DRIFT_COMP = 1800
|
INTERVAL_CLOCK_DRIFT_COMP = 1800
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
TIMEOUT_WIFI_CONNECT = 10
|
||||||
|
TIMEOUT_GNSS_LOCATION = 300
|
||||||
|
|
|
@ -18,39 +18,29 @@ GPS_BAUDRATE = 115200
|
||||||
|
|
||||||
uart = UART(1, baudrate=GPS_BAUDRATE, rx=PIN_GPS_UART_RXD, tx=PIN_GPS_UART_TXD)
|
uart = UART(1, baudrate=GPS_BAUDRATE, rx=PIN_GPS_UART_RXD, tx=PIN_GPS_UART_TXD)
|
||||||
gps_power_pin = Pin(PIN_GPS_POWER, Pin.OUT)
|
gps_power_pin = Pin(PIN_GPS_POWER, Pin.OUT)
|
||||||
gps_power_pin.value(0) # Turn off GPS power initially
|
gps_power_pin.value(1) # Turn on GPS power initially
|
||||||
|
|
||||||
|
|
||||||
async def read_gps_uart(want: str):
|
async def read_gps_uart(want: str):
|
||||||
m = MicropyGPS(location_formatting='dd')
|
m = MicropyGPS(location_formatting='dd')
|
||||||
buffer = b''
|
|
||||||
decoded = ''
|
|
||||||
sreader = asyncio.StreamReader(uart)
|
sreader = asyncio.StreamReader(uart)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if uart.any():
|
try:
|
||||||
c = await sreader.read(1)
|
decoded = (await sreader.readline()).decode()
|
||||||
if c == b'\r':
|
except UnicodeError:
|
||||||
continue
|
# We see sporadic decode errors for some reason, probably on strings we don't care about.
|
||||||
if c == b'\n':
|
continue
|
||||||
try:
|
|
||||||
decoded = buffer.decode()
|
|
||||||
except UnicodeError:
|
|
||||||
# We see sporadic decode errors for some reason, probably on strings we don't care about.
|
|
||||||
continue
|
|
||||||
buffer = b''
|
|
||||||
else:
|
|
||||||
buffer += c
|
|
||||||
if decoded.startswith(want):
|
if decoded.startswith(want):
|
||||||
for x in decoded:
|
for x in decoded:
|
||||||
m.update(x)
|
m.update(x)
|
||||||
if m.latitude[0] > 0 and m.longitude[0] > 0:
|
if m.latitude[0] > 0 and m.longitude[0] > 0:
|
||||||
return m
|
return m
|
||||||
decoded = ''
|
|
||||||
await asyncio.sleep(0.05)
|
await asyncio.sleep(0.05)
|
||||||
|
|
||||||
|
|
||||||
async def get_position():
|
async def get_position():
|
||||||
|
# TODO: fix altitude always being 0
|
||||||
gps_power_pin.value(1) # Always make sure the GPS is on.
|
gps_power_pin.value(1) # Always make sure the GPS is on.
|
||||||
|
|
||||||
@timeout(TIMEOUT_GNSS_LOCATION)
|
@timeout(TIMEOUT_GNSS_LOCATION)
|
||||||
|
@ -73,7 +63,7 @@ async def get_position():
|
||||||
gps_power_pin.value(1)
|
gps_power_pin.value(1)
|
||||||
|
|
||||||
# Set the clock if it's time.
|
# Set the clock if it's time.
|
||||||
if interval_tracker.check('ntp_sync'):
|
if interval_tracker.check('clock_sync'):
|
||||||
initialize_rtc(p)
|
initialize_rtc(p)
|
||||||
logger('Updated time', source='GPS')
|
logger('Updated time', source='GPS')
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ class IntervalTracker:
|
||||||
|
|
||||||
interval_tracker = IntervalTracker(
|
interval_tracker = IntervalTracker(
|
||||||
wifi_scan=INTERVAL_WIFI_SCAN,
|
wifi_scan=INTERVAL_WIFI_SCAN,
|
||||||
ntp_sync=INTERVAL_NTP_SYNC,
|
clock_sync=INTERVAL_CLOCK_SYNC,
|
||||||
active_position_send=INTERVAL_ACTIVE_POSITION_SEND,
|
active_position_send=INTERVAL_ACTIVE_POSITION_SEND,
|
||||||
clock_drift_comp=INTERVAL_CLOCK_DRIFT_COMP
|
clock_drift_comp=INTERVAL_CLOCK_DRIFT_COMP
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,272 @@
|
||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
|
||||||
|
import machine
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
from config import CELLULAR_APN, CELLULAR_STARTUP_TIMEOUT
|
||||||
|
from lib.logging import logger, LogLevel
|
||||||
|
from lib.runtime import timeout, uTimeoutError
|
||||||
|
|
||||||
|
PIN_BEE_POWER = 27
|
||||||
|
PIN_BEE_UART_RXD = 35
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
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 send_command(self, command, require_ok: bool = False) -> tuple:
|
||||||
|
lines = []
|
||||||
|
async with self._lock:
|
||||||
|
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()
|
||||||
|
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) 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 require_ok:
|
||||||
|
if response[-1] != 'OK':
|
||||||
|
raise ModemErrorResponse(code=(command, lines))
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
cellular = CellularModem()
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_csq(csq_value: int):
|
||||||
|
if csq_value == 99:
|
||||||
|
return 'disconnected'
|
||||||
|
elif csq_value == 31:
|
||||||
|
return '51 dBm or greater'
|
||||||
|
elif csq_value == 0:
|
||||||
|
return '113 dBm or less'
|
||||||
|
elif csq_value == 1:
|
||||||
|
return '111 dBm'
|
||||||
|
elif 2 <= csq_value <= 30:
|
||||||
|
return f'{109 - (csq_value - 2) * 2} dBm'
|
||||||
|
else:
|
||||||
|
return 'Invalid CSQ value'
|
||||||
|
|
||||||
|
|
||||||
|
async def cellular_wake_modem():
|
||||||
|
i = 5
|
||||||
|
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?')
|
||||||
|
parts = cpsi_resp[0].lstrip('+CPSI: ').split(',')
|
||||||
|
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():
|
||||||
|
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)
|
||||||
|
async def inner():
|
||||||
|
resp = await cellular.send_command('AT+CSQ')
|
||||||
|
if len(resp) == 2 and resp[0].startswith('+CSQ: '):
|
||||||
|
s = int(resp[0].lstrip('+CSQ: ').split(',')[0])
|
||||||
|
return _parse_csq(s)
|
||||||
|
else:
|
||||||
|
raise Exception(resp)
|
||||||
|
|
||||||
|
try:
|
||||||
|
signal_strength = await inner()
|
||||||
|
except uTimeoutError:
|
||||||
|
signal_strength = -1
|
||||||
|
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'
|
||||||
|
|
||||||
|
try:
|
||||||
|
ip = await inner()
|
||||||
|
except uTimeoutError:
|
||||||
|
ip = '0.0.0.0'
|
||||||
|
return ip
|
||||||
|
|
||||||
|
|
||||||
|
class RunWithtimeoutResult:
|
||||||
|
def __init__(self, failure: bool, result: any):
|
||||||
|
self.failure = failure
|
||||||
|
self.result = result
|
||||||
|
|
||||||
|
|
||||||
|
async def run_with_timeout(timeout_sec: int, func, *args, **kwargs) -> RunWithtimeoutResult:
|
||||||
|
@timeout(timeout_sec)
|
||||||
|
async def inner():
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await inner()
|
||||||
|
return RunWithtimeoutResult(False, result)
|
||||||
|
except uTimeoutError:
|
||||||
|
return RunWithtimeoutResult(True, None)
|
||||||
|
|
||||||
|
|
||||||
|
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():
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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:
|
||||||
|
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.error)
|
||||||
|
if init_resp[-1] != 'OK':
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
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, network offline?', 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()
|
||||||
|
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
|
||||||
|
|
||||||
|
logger(f'IP: {await cellular_ip()}', source='CELL')
|
||||||
|
logger(f'Signal strength: {await cellular_signal_strength()}', source='CELL')
|
||||||
|
|
||||||
|
|
||||||
|
async def start_modem_task():
|
||||||
|
logger('Initalizing modem', source='CELL')
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
await start_modem()
|
||||||
|
break
|
||||||
|
except uTimeoutError:
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
# TODO: loop to reconnect modem
|
|
@ -1,3 +1,4 @@
|
||||||
|
from lib.networking.cell_modem import cellular_check_connected
|
||||||
from lib.networking.wifi import wifi
|
from lib.networking.wifi import wifi
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,9 +8,11 @@ class ConnectionTypes:
|
||||||
offline = 'Offline'
|
offline = 'Offline'
|
||||||
|
|
||||||
|
|
||||||
def select_connection_type():
|
async def select_connection_type():
|
||||||
if wifi.is_connected():
|
if wifi.is_connected():
|
||||||
return ConnectionTypes.wifi
|
return ConnectionTypes.wifi
|
||||||
# if cellular.is_connected():
|
elif await cellular_check_connected():
|
||||||
|
print('CELLULAR DATA NOT IMPLEMENTED')
|
||||||
|
return ConnectionTypes.offline
|
||||||
else:
|
else:
|
||||||
return ConnectionTypes.offline
|
return ConnectionTypes.offline
|
||||||
|
|
|
@ -13,12 +13,12 @@ from lib.networking.wifi import wifi
|
||||||
from lib.traccar.request import TraccarGetRequest, WifiInfo
|
from lib.traccar.request import TraccarGetRequest, WifiInfo
|
||||||
|
|
||||||
|
|
||||||
def send_to_traccar(position: Position, wifi_info: WifiInfo, cell_info):
|
async def send_to_traccar(position: Position, wifi_info: WifiInfo, cell_info):
|
||||||
led_on()
|
led_on()
|
||||||
gc.collect()
|
gc.collect()
|
||||||
event = None
|
event = None
|
||||||
params = 'None'
|
params = 'None'
|
||||||
if select_connection_type() == ConnectionTypes.wifi:
|
if (await select_connection_type()) == ConnectionTypes.wifi:
|
||||||
event = TraccarGetRequest(
|
event = TraccarGetRequest(
|
||||||
timestamp=position.timestamp,
|
timestamp=position.timestamp,
|
||||||
lat=position.latitude,
|
lat=position.latitude,
|
||||||
|
@ -42,7 +42,7 @@ def send_to_traccar(position: Position, wifi_info: WifiInfo, cell_info):
|
||||||
else:
|
else:
|
||||||
position_buffer.push(event)
|
position_buffer.push(event)
|
||||||
logger(f'Offline buffer: {position_buffer.size()}/{GNSS_OFFLINE_BUFFER_SIZE}', source='STORE')
|
logger(f'Offline buffer: {position_buffer.size()}/{GNSS_OFFLINE_BUFFER_SIZE}', source='STORE')
|
||||||
if select_connection_type() != ConnectionTypes.offline:
|
if (await select_connection_type()) != ConnectionTypes.offline:
|
||||||
logger(params, source='NET')
|
logger(params, source='NET')
|
||||||
led_off()
|
led_off()
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
39
src/main.py
39
src/main.py
|
@ -1,38 +1,3 @@
|
||||||
import asyncio
|
from run import entrypoint
|
||||||
import gc
|
|
||||||
|
|
||||||
from lib.interval_tracker import interval_tracker
|
entrypoint()
|
||||||
from lib.networking.wifi import wifi
|
|
||||||
from lib.ntp import Ntp
|
|
||||||
from lib.runtime import reboot
|
|
||||||
from lib.traccar.send import send_to_traccar, assemble_position_message
|
|
||||||
from startup import startup
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
await startup()
|
|
||||||
# TODO: background thread to sync items in the position buffer when we go back online
|
|
||||||
|
|
||||||
while True:
|
|
||||||
gc.collect()
|
|
||||||
if interval_tracker.check('wifi_scan'):
|
|
||||||
if not wifi.is_connected():
|
|
||||||
wifi.connect(ignore_missing=True)
|
|
||||||
if interval_tracker.check('active_position_send'):
|
|
||||||
send_to_traccar(*(await assemble_position_message()))
|
|
||||||
if interval_tracker.check('clock_drift_comp'):
|
|
||||||
Ntp.drift_compensate(Ntp.drift_us())
|
|
||||||
gc.collect()
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
asyncio.run(main())
|
|
||||||
except Exception as e:
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
sys.print_exception(e)
|
|
||||||
print('\n-- REBOOTING --\n\n')
|
|
||||||
time.sleep(1)
|
|
||||||
reboot()
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import asyncio
|
||||||
|
import gc
|
||||||
|
|
||||||
|
from lib.interval_tracker import interval_tracker
|
||||||
|
from lib.networking.cell_modem import start_modem_task
|
||||||
|
from lib.networking.wifi import wifi
|
||||||
|
from lib.ntp import Ntp
|
||||||
|
from lib.runtime import reboot
|
||||||
|
from lib.traccar.send import send_to_traccar, assemble_position_message
|
||||||
|
from startup import startup
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
asyncio.create_task(start_modem_task())
|
||||||
|
await startup()
|
||||||
|
# TODO: background thread to sync items in the position buffer when we go back online
|
||||||
|
|
||||||
|
while True:
|
||||||
|
gc.collect()
|
||||||
|
if interval_tracker.check('wifi_scan'):
|
||||||
|
if not wifi.is_connected():
|
||||||
|
wifi.connect(ignore_missing=True)
|
||||||
|
if interval_tracker.check('active_position_send'):
|
||||||
|
await send_to_traccar(*(await assemble_position_message()))
|
||||||
|
if interval_tracker.check('clock_drift_comp'):
|
||||||
|
Ntp.drift_compensate(Ntp.drift_us())
|
||||||
|
gc.collect()
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def entrypoint():
|
||||||
|
try:
|
||||||
|
asyncio.run(main())
|
||||||
|
except Exception as e:
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
sys.print_exception(e)
|
||||||
|
print('\n-- REBOOTING --\n\n')
|
||||||
|
time.sleep(1)
|
||||||
|
reboot()
|
|
@ -56,7 +56,7 @@ async def startup():
|
||||||
print('====================')
|
print('====================')
|
||||||
|
|
||||||
# Send the first message
|
# Send the first message
|
||||||
send_to_traccar(*(await assemble_position_message(position)))
|
await send_to_traccar(*(await assemble_position_message(position)))
|
||||||
|
|
||||||
|
|
||||||
def validate_config():
|
def validate_config():
|
||||||
|
|
21
upload.sh
21
upload.sh
|
@ -1,15 +1,30 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
|
||||||
|
echo "Building..."
|
||||||
|
SRC_DIR="$SCRIPT_DIR/src"
|
||||||
|
TMP_DIR=$(mktemp -d)
|
||||||
|
find "$SRC_DIR" -name '*.py' | while read -r FILE
|
||||||
|
do
|
||||||
|
REL_PATH="${FILE#"$SRC_DIR"/}"
|
||||||
|
OUT_DIR="$TMP_DIR/$(dirname "$REL_PATH")"
|
||||||
|
BASE_NAME=$(basename "$FILE")
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
"$SCRIPT_DIR/mpy-cross" "$FILE" -o "$OUT_DIR/${BASE_NAME%.py}.mpy"
|
||||||
|
done
|
||||||
|
rm "$TMP_DIR/main.mpy"
|
||||||
|
cp "$SRC_DIR/main.py" "$TMP_DIR"
|
||||||
|
|
||||||
echo "Erasing files..."
|
echo "Erasing files..."
|
||||||
"$SCRIPT_DIR"/venv/bin/ampy --port /dev/ttyUSB0 rmdir /lib
|
"$SCRIPT_DIR"/venv/bin/ampy --port /dev/ttyUSB0 rmdir /lib
|
||||||
|
|
||||||
echo "Uploading..."
|
echo "Uploading..."
|
||||||
"$SCRIPT_DIR"/venv/bin/ampy --port /dev/ttyUSB0 put "$SCRIPT_DIR/src/" / || exit
|
"$SCRIPT_DIR"/venv/bin/ampy --port /dev/ttyUSB0 put "$TMP_DIR" /
|
||||||
"$SCRIPT_DIR"/venv/bin/ampy --port /dev/ttyUSB0 rm /config.sample.py
|
"$SCRIPT_DIR"/venv/bin/ampy --port /dev/ttyUSB0 rm /config.sample.mpy
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
|
||||||
echo "Resetting..."
|
echo "Resetting..."
|
||||||
"$SCRIPT_DIR"/venv/bin/ampy --port /dev/ttyUSB0 run --no-output "$SCRIPT_DIR/src/reset.py" || exit
|
"$SCRIPT_DIR"/venv/bin/ampy --port /dev/ttyUSB0 run --no-output "$SCRIPT_DIR/src/reset.py"
|
||||||
|
|
||||||
# Uploading can take a while so make a sound when it's done
|
# Uploading can take a while so make a sound when it's done
|
||||||
# https://unix.stackexchange.com/questions/1974/how-do-i-make-my-pc-speaker-beep/163716#163716
|
# https://unix.stackexchange.com/questions/1974/how-do-i-make-my-pc-speaker-beep/163716#163716
|
||||||
|
|
Loading…
Reference in New Issue