Hi All!
I couldn’t find an existing project for this, so I decided to make my own and share it!
The Sixfab Pico LTE is based on a Raspberry Pi Pico W and has a Quectel BG95-M3 LTE modem built onto it. It’s currently $49.00 USD (+shipping) and includes 1 year of data (up to 1GB).
Sixfab provides several examples to work with, you could use MQTT for this using their library. I have MQTT running, but didn’t want to expose it to the internet and I found that the GPSLogger integration configures it’s own webhook - so that works for me!
To configure this, you need a few setup pieces:
- In Home Assistant, configure GPSLogger. This will automatically setup a Webhook for you. Save the URL you get, you’ll need it in the code.
- Configure Thonny or your Python IDE of choice. This is technically optional, but Thonny gives you an easy way to load the library and Python script directly onto the Pico
- On the Raspberry Pi Pico, load the pico_lte library from Sixfab
- Load the Python script onto the pico and name it “main.py” so it runs automatically when power is connected.
Script:
url="<place your webhook URL here>"
device_name = "<your device name here>"
import time
import math
from machine import ADC
from pico_lte.core import PicoLTE
from pico_lte.common import debug
from pico_lte.utils.status import Status
def get_volt():
R1 = 10040
R2 = 2208
pot = ADC(27)
pot_val = pot.read_u16()
vout = ((pot_val/65535) * 3.3)/ (R2/(R1+R2));
return vout
def dms2dd(degrees, minutes, seconds, direction):
degmin = minutes+"."+seconds
dd = float(degrees) + float(degmin)/60;
if direction == 'S' or direction == 'W':
dd *= -1
return dd;
def decode(coord):
x = coord.split(".")
head = x[0]
tail = x[1]
deg = head[0:-2]
min = head[-2:]
sec = tail[:4]
dir = tail[-1:]
return dms2dd(deg, min, sec, dir)
def get_temp():
sensor_temp = machine.ADC(4)
conversion_factor = 3.3 / (65535)
reading = sensor_temp.read_u16() * conversion_factor
temperature = 27 - (reading - 0.706)/0.001721
ftemp = temperature * 9 / 5 + 32
return ftemp
fix = False
picoLTE = PicoLTE()
led = machine.Pin(22, machine.Pin.OUT)
picoLTE.peripherals.adjust_neopixel(0, 0, 0)
led.off()
while True:
# First go to GNSS prior mode and turn on GPS.
picoLTE.gps.set_priority(0)
time.sleep(3)
picoLTE.gps.turn_on(accuracy=3)
debug.info("Trying to fix GPS...")
for _ in range(0, 45):
result = picoLTE.gps.get_location()
debug.info(result)
if result["status"] == Status.SUCCESS:
debug.debug("GPS Fixed. Getting location data...")
loc = result.get("value")
debug.info("Lat-Lon:", loc)
loc_message = ",".join(word for word in loc)
resp = result["response"][0].split(',')
fix = True
break
time.sleep(2) # 45*2 = 90 seconds timeout for GPS fix.
if fix:
# Go to WWAN prior mode and turn off GPS.
picoLTE.gps.set_priority(1)
picoLTE.gps.turn_off()
debug.info("Sending message to the server...")
picoLTE.network.register_network()
picoLTE.http.set_context_id()
picoLTE.network.get_pdp_ready()
picoLTE.http.set_custom_header()
lat = decode(loc[0])
lon = decode(loc[1])
temp = get_temp()
volt = round(get_volt(),2)
payload = "latitude="+str(lat)+"&longitude="+str(lon)+"&device="+device_name+"&accuracy="+str(resp[3])+"&speed="+str(resp[7])+"&direction="+str(resp[6])+"&provider="+str(temp)+"&battery="+str(volt)
picoLTE.http.set_server_url(url)
result = picoLTE.http.post(data=payload)
debug.info(result)
if result["status"] == Status.SUCCESS:
debug.info("Message sent successfully.")
led.on()
fix = False
picoLTE.network.deactivate_pdp_context()
cnt = 30 # Start at 30 minute update intervals
if (float(resp[7]) > 0):
cnt = 1 #Once a minute if moving
while (cnt > 0):
cnt = cnt - 1
if (get_temp() > 85 and cnt > 5):
cnt = 5 #Once every 5 minutes if too warm
time.sleep(60) # 30 seconds between each request.
led.off()
A few points I have customized:
- I’m using ADC on GP27 as a voltage monitor for the 12v battery and created a divider circuit on a small PCB beside it. You can remove that if you don’t want to monitor voltage.
- I’m monitoring temperature on the RPi’s temp sensor, but will be switching over to an external sensor on GP28 soon since the built in sensor reads really hot all the time
- I’m outputting the temperature to the “provider” field, since I’m not using that for anything else. That way I don’t have to make a 2nd call to update a separate temperature entity.
Once this is all done, it shows up as a device under your GPSLogger integration and you can set zones for warnings in automations or trigger off of voltage/temperature events.