Een kwestie van software

Deze GPS-tracker is anders, en wel op een heel goede manier vanuit ontwikkelaar- en klantperspectief. Het is een open platform, iets wat bij GPS-trackers zelden het geval is. Firmware met basisfuncties voor het volgen van voertuigen is af fabriek geïnstalleerd (wat met andere systemen uiteraard ook mogelijk is). Het grote verschil is dat u voor deze GPS-tracker zelf software kunt schrijven – en dat hoeft voor de verandering niet per se in C/C++. Elk apparaat wordt geleverd met de ZERYNTH-middleware en een geïntegreerde licentie voor de daarin ondergebrachte Python-omgeving. ZERYNTH Studio, dat bij de fabrikant kan worden gedownload, is vereist voor ontwikkeling. Zelfs als u niet met Python aan de slag wilt, moet u dat toch doen, omdat de geïntegreerde licentie voor de Python-omgeving op de MCU anders verloren zou gaan wanneer een Python-programma niet minstens één keer met succes is overgedragen en de licentie op de lokale computer daarbij in veiligheid is gebracht. Bij ZERYNTH Studio is het verplicht om een account bij de fabrikant te hebben om het te kunnen gebruiken.



ZERYNTH Studio zelf is wat de editor betreft heel eenvoudig gehouden; wanneer u alleen een editor voor de broncode wilt, bent u beter af met andere gratis software. Onder andere Visual Code wordt officieel ondersteund. De licentie voor de Python-omgeving kan en moet in veiligheid worden gebracht voordat verdere acties worden ondernomen. Daarna kan de eerste test van de GPS tracker worden gestart. De firmware op het apparaat kan alleen gegevens uitwisselen met het Fortebit-portal, waarvoor op zijn beurt een account nodig is. Dit account is niet gratis; bij aankoop van de GPS-tracker kunt u het echter 30 dagen gratis uitproberen. Iedereen die een wagenpark of andere zaken wil monitoren, stelt zijn eigen eisen aan de gegevensverwerking en -voorbereiding. Daarom gaan we hier niet nader op dit portal in, omdat het ons vooral om de hardware gaat en niet om het telematica-portal.

Vanaf hier moet u zelf aan de slag om de demo-applicatie gegevens te laten uitwisselen met het systeem waarvoor u gekozen hebt. Ook wanneer u al een portal voor het volgen van voertuiggegevens hebt, kunt u deze stap niet vermijden. Dit is iets dat nog voor verbetering vatbaar is ten opzichte van de overigens goede eerste indruk. Wilt u niet met Python werken, dan kunt u via het Arduino-framework de GPS-tracker voorzien van nieuwe software. De benodigde software en Board Packages zijn te vinden op het ForteBit GitHub-account en kunnen met behulp van de Board Manager in de Arduino IDE worden geïntegreerd.
 

We hebben het Python-voorbeeld herschreven om de tracker uit te proberen (het betreft demosoftware die niet geschikt voor ‘echt’ gebruik). De demo laat echter zien hoe de hardware kan worden aangesloten op een MQTT-broker naar keuze. Met een paar regels Python worden de aanpassingen doorgevoerd en snel in de hardware geladen. De GPS-gegevens worden naar een MQTT-makelaar naar keuze gestuurd.

Overzicht

De open hardware-benadering van de GPS-tracker en de flexibiliteit in de keuze van de ontwikkelomgeving en programmeertaal maken de Fortebit Polaris 3G Kit+ tot een zeer interessant platform, zeker wanneer u uw eigen speciale functies wilt implementeren. De documentatie en ondersteuning met voorbeelden voor Python en de Arduino-omgeving zijn echter nog voor uitbreiding vatbaar – hopelijk maakt de fabrikant daar nog werk van.

Om de Fortebit Polaris 3G Kit+ met een MQTT-broker naar keuze uit te proberen, geven we hieronder het gewijzigde main.py-bestand:

 

# POLARIS_FORTEBIT
# Created at 2019-07-26 11:34:26.282569
# modified by someone(tm) 06-08-2020 for generic MQTT ( unsecured ) transport
# to use test.mosquitto.org as mqtt broker
# not for production use at all !
from mqtt import mqtt
from fortebit.polaris import polaris
from fortebit.polaris import modem
from fortebit.polaris import gnss
import vm
import sfw
vm.set_option(vm.VM_OPT_RESET_ON_EXCEPTION, 1)
vm.set_option(vm.VM_OPT_TRACE_ON_EXCEPTION, 1)
vm.set_option(vm.VM_OPT_RESET_ON_HARDFAULT, 1)
vm.set_option(vm.VM_OPT_TRACE_ON_HARDFAULT, 1)
import streams
s = streams.serial()

import mcu
import timestamp
import timers
import ssl
import requests
import threading
from wireless import gsm
import utils

sleep(1000)

# CONFIG
poll_time = 100                     # poll inputs at twice the specified period in ms
gps_period = 10000                  # gps lat,lon and speed telemetry period in ms
update_period = 6 * gps_period      # other telemetry data period in ms
no_ignition_period = 300000         # no ignition telemetry data period in ms

fw_version = "1.11"

# POLARIS INIT
try:
    print("Polaris default app")
    polaris.init()
    
    print("MCU UID:", [hex(b) for b in mcu.uid()])
    print("VM info:", vm.info())
    print("FW version:", fw_version)
    print("Watchdog was triggered:", sfw.watchdog_triggered())
    
    polaris.ledRedOn()
    polaris.ledGreenOff()

except Exception as e:
    print("Failed polaris init with", e)
    sleep(500)
    mcu.reset()


# INIT HW

try:
    print("Initializing Modem...")
    modem = modem.init()
    print("Initializing GNSS...")
    gnss = gnss.init()
    # verify preconditions and start utility thread
    utils.start()

    print("Starting Accelerometer...")
    import accel
    accel.start()
    print("Starting GNSS...")
    gnss.start()
    gnss.set_rate(2000)

    print("Starting Modem...")
    modem.startup()
    
    # enable modem/gnss utilities
    utils.modem = modem
    utils.gnss = gnss

    sfw.watchdog(0, 30000)
    sfw.kick()
    if utils.check_terminal(s):
        utils.do_terminal(s)

    minfo = gsm.mobile_info()
    print("Modem:", minfo)
    
    # enable SMS checking
    utils.check_sms = True
except Exception as e:
    print("Failed init hw with", e)
    sleep(500)
    mcu.reset()


# GATHERING SETTINGS
name = None
apn = None
email = None

try:
    print("Read settings...")
    settings = utils.readSettings()
    sfw.kick()

    if "apn" in settings:
        apn = settings["apn"]
        print("APN:", apn)

    if "email" in settings:
        email = settings["email"]
        print("Email:", email)

    if "name" in settings:
        name = settings["name"]
        print("Name:", name)

    if apn is not None and not utils.validate_apn(apn):
        print("Invalid APN!")
        apn = None
    if email is not None and not utils.validate_email(email):
        print("Invalid email!")
        email = None
    if name is None:
        name = "Polaris"
        print("Saving name:", name)
        settings["name"] = name
        utils.saveSettings(settings)
    if apn is None:
        print("APN is not defined, can't connect to Internet!")
        apn = utils.request_apn(s)
        print("Saving email:", email)
        print("Saving APN:", apn)
        settings["apn"] = apn
        utils.saveSettings(settings)
    if email is None:
        print("email is not defined, can't register to Cloud!")
        email = utils.request_email(s)
        settings["email"] = email
        utils.saveSettings(settings)
except Exception as e:
    print("Failed gathering settings with", e)
    sleep(500)
    mcu.reset()


# GSM ATTACH
try:
    print("Waiting for network...",end='')
    for _ in range(150):
        sfw.kick()
        ninfo = gsm.network_info()
        if ninfo[6]:
            break
        sleep(1000)
        if (_ % 10) == 9:
            print('.',end='')
    else:
        raise TimeoutError
    print("ok!")
    print("Attached to network:", ninfo)

    print("Activating data connection...")
    for _ in range(3):
        try:
            gsm.attach(apn)
            break
        except Exception as e:
            print("Retrying...", e)
        try:
            gsm.detach()
            sleep(5000 * (_ + 1))
        except:
            pass
    else:
        raise TimeoutError
    linfo = gsm.link_info()
    print("Connection parameters:", linfo)
except Exception as e:
    print("Network failure", e)
    sleep(500)
    mcu.reset()



# TELEMETRY LOOP
try:
    accel.get_sigma()  # reset accumulated value
    sleep(500)

    last_time = -(no_ignition_period + gps_period) # force sending data immediately
    counter = 0
    sos = -1
    connected = True
    ignition = None
    sos = None
    telemetry = {}
    disconn_time = None

    while True:
        # allow other threads to run while waiting
        sleep(poll_time*2)
        sfw.kick()
        now_time = timers.now()

        if utils.check_terminal(s):
            utils.do_terminal(s)

        # read inputs
        old_ign = ignition
        ignition = polaris.getIgnitionStatus()
        old_sos = sos
        sos = polaris.getEmergencyStatus()
        extra_send = False



        # led waiting status
        utils.status_led(False, ignition, connected)

        if sos != old_sos:
            telemetry['sos'] = sos
            extra_send = True

        if ignition != old_ign:
            telemetry['ignition'] = ignition
            extra_send = True

        if not extra_send:
            if ignition == 0:
                # sleep as indicated by rate for no ignition
                if now_time - last_time < no_ignition_period - poll_time:
                    continue
                extra_send = True
            else:
                # sleep as indicated by rate
                if now_time - last_time < gps_period - poll_time:
                    continue

        ts = modem.rtc()
        #print("MODEM RTC =", ts)

        if counter % (update_period / gps_period) == 0 or extra_send:
            telemetry['ignition'] = ignition
            telemetry['sos'] = sos

            if polaris.isBatteryBackup():
                telemetry['charger'] = -1
            else:
                telemetry['battery'] = utils.decimal(3, polaris.readMainVoltage())
                telemetry['charger'] = polaris.getChargerStatus()

            telemetry['backup'] = utils.decimal(3, polaris.readBattVoltage())
            telemetry['temperature'] = utils.decimal(2, accel.get_temperature())
            telemetry['sigma'] = utils.decimal(3, accel.get_sigma())

            pr = accel.get_pitchroll()
            telemetry['pitch'] = utils.decimal(1, pr[0])
            telemetry['roll'] = utils.decimal(1, pr[1])

        if gnss.has_fix():
            fix = gnss.fix()
            # only transmit position when it's accurate
            if fix[6] < 2.5:
                telemetry['latitude'] = utils.decimal(6, fix[0])
                telemetry['longitude'] = utils.decimal(6, fix[1])
                telemetry['speed'] = utils.decimal(1, fix[3])
                if counter % (update_period / gps_period) == 0 or extra_send:
                    telemetry['altitude'] = utils.decimal(1, fix[2])
                    telemetry['COG'] = utils.decimal(1, fix[4])
            if counter % (update_period / gps_period) == 0 or extra_send:
                telemetry['nsat'] = fix[5]
                telemetry['HDOP'] = utils.decimal(2, fix[6])
            # replace timestamp with GPS
            ts = fix[9]
        elif ts is not None and ts[0] < 2019:
            ts = None

        if ts is not None:
            ts = str(timestamp.to_unix(ts)) + "000"

        counter += 1
        last_time = now_time

        # led sending status
        utils.status_led(True, ignition, connected)
        telemetry['counter']=counter
        telemetry['dev_ts']=ts
        
        sfw.kick()
        # set the mqtt id to "zerynth-mqtt"
        client = mqtt.Client("zerynth-mqtt",True)
        # and try to connect to "test.mosquitto.org"
      
        for retry in range(10):
            try:
                client.connect("test.mosquitto.org", 60)
                break
            except Exception as e:
                print("connecting...")
        print("connected.")
        print("Publishing:", telemetry)
        
        client.publish("test", str(telemetry))
        client.loop()  
        ok = False;
        

except Exception as e:
    print("Failed telemetry loop", e)
    sleep(500)
    mcu.reset()