Eine Frage der Software

Dieser GPS-Tracker ist anders, und das in einem aus Entwickler- und Kundensicht sehr gutem Sinne. Es handelt sich um eine offene Plattform, etwas das bei GPS-Trackern sonst eher seltener der Fall ist. Ab Werk ist eine Firmware mit Basisfunktionen für das Tracken von Fahrzeugen installiert, etwas das andere Systeme natürlich auch erlauben. Der große Unterschied liegt darin, dass für diesen GPS-Tracker eigene Software geschrieben werden kann und dieses Mal nicht nur C/C++ als Programmiersprache zur Verfügung steht. Jedes Gerät wird mit der ZERYNTH Middleware und einer integrierten Lizenz für die darin enthaltene Python-Umgebung ausgeliefert. Für die Entwicklung wird das ZERYNTH Studio benötigt, das beim Hersteller heruntergeladen werden kann. Auch wer nicht mit Python loslegen möchte, sollte dieses durchführen, da die integrierte Lizenz für die Python-Umgebung auf der MCU sonst verloren gehen würde, wenn nicht zumindest einmal ein Python-Programm erfolgreich übertragen und auf dem Weg auch die Lizenz auf dem lokalen Rechner gesichert wird. Etwas das beim ZERYNTH Studio erwähnt werden muss, ist der Zwang eines Accounts beim Hersteller, um das ZERYNTH Studio nutzen zu können.



Das ZERYNTH Studio selbst ist vom Editor her schlicht gehalten; wer einen reinen Editor für den Quelltext haben möchte, der ist mit anderer kostenfreier Software besser bedient. Es wird unter anderem Visual Code offiziell unterstützt. Vor weiteren Aktionen kann und sollte die Lizenz für die Python-Umgebung gesichert werden. Nachdem das geschehen ist, kann mit dem ersten Test des GPS-Trackers begonnen werden. Die Firmware auf dem Gerät kann von Haus aus nur mit dem Portal von Fortebit Daten austauschen, das wiederum einen Account benötigt. Dieser Account ist kostenpflichtig und kann einmalig mit dem Erwerb des GPS-Tracker für 30 Tage ohne Kosten getestet werden. Jeder der eine Flotte von Fahrzeugen oder anderen Dinge überwachen möchte, hat seine ganz eigenen Anforderrungen an die Datenverarbeitung und Aufbereitung. Daher wird hier das Portal auch nicht weiter beachtet, da es hier in erster Linie um die Hardware gehen soll und nicht um das Telematik-Portal.


Ab diesem Punkt muss selber Hand angelegt werden, um die Demoapplikation dazu zu bewegen, die Daten mit einem eigenen System der Wahl auszutauschen. Auch wer selbst ein Portal zum Tracken von Fahrzeugdaten betreibt, kommt um diesen Schritt nicht herum. Dies ist etwas, das gegenüber dem ansonsten guten ersten Eindruck noch Raum für Verbesserungen bietet. Wer nicht mit Python arbeiten möchte, kann den GPS-Tracker über das Arduino Framework mit neuer Software versorgen. Die nötige Software und Board Packages lassen sich auf dem GitHub Account von ForteBit finden und per Boardmanager in die Arduino IDE integrieren.

Für einen kurzen Testlauf wurde das Python-Beispiel umgestaltet (es handelt sich um Demo-Software, ist also nicht für einen Produktivbetrieb geeignet). Die Demo zeigt aber, wie die Hardware an einen MQTT-Broker eigener Wahl angebunden werden kann. Mit ein paar Zeilen Python sind die Anpassungen umgesetzt und zügig auf die Hardware übertragen. Die GPS-Daten werden an einen MQTT-Broker eigener Wahl gesendet.

Zusammenfassung

Der offene Hardwareansatz des GPS-Tracker und die Flexibilität bei der Auswahl der Entwicklungsumgebung und Programmiersprache machen das Fortebit Polaris 3G+ Kit zu einer sehr interessanten Plattform, vor allem wenn eigene Sonderfunktionen realisiert werden sollen. Die Dokumentation und Unterstützung mit Beispielen für Python und die Arduino-Welt sind sicherlich noch ausbaufähig, das Ganze wird hoffentlich auch durch den Hersteller vorangetrieben.
Um das Fortebit Polaris 3G+ mit dem eigenen MQTT-Broker zu starten ist nachfolgend die modifizierte main.py gelistet:

 

# 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()