Portierung des Terkin-Datenlogger auf Genuine MicroPython für ESP32

Einleitung

Folgend eine Liste aller Änderungen, um den Terkin Datenlogger zum Laufen zu bringen. Der Fokus liegt hier erstmal auf ‘fehlerfrei’, nicht ‘Funktion’.

Details

terkin/logging.py

  • Es gibt bei uPy keine Timer.Chrono() Methode.
  • Ein kurzes googeln hat dafür keinen Ersatz gefunden. Der ‘längste’ timer läuft nur 12 Tage. Allerdings hab ich nirgends gefunden, wie lange Chrono() laufen kann.
    ESP8266/ESP32 uptime overflow - MicroPython Forum
  • Lösung: _chrono auskommentiert
  • Nachtrag:
    Das fällt einem dann später auf die Füße:
    TypeError: can't convert NoneType to float
    
    in logging.py: hdlr.emit(record)

terkin/pycom.py

  • Die Attribute BROWN_OUT_RESET, PWRON_WAKE, RTC_WAKE existieren nicht.
  • Lösung: auskommentiert

terkin/sensor/touch

terkin/pycom

#wakeup_reason_magic, _ = machine.wake_reason() ->
#wakeup_reason_magic = machine.wake_reason()
  • wake_reason() ist bei uPy ein Int.

terkin/device.py

  • Es gibt natürlich kein Modul pycom.
  • Lösung: auskommentiert.

dist-packages/logging/__init__.py

self.exc(sys.exc_info()[1], msg, *args)
  • sys.exc_info() gibt es in uPy nicht (und habe es auch nicht in der pycom Doku gefunden)
  • Lösung: auskommentiert

terkin/device.py

  • configure_rgb_led auskommentiert. Gibt keine LED.

terkin/device.py

  • machine.info() gibt es nicht.

Jetzt erstmal Pause. :slight_smile:

1 Like

Exzellent. Vielen Dank für den Start und den Einblick in die ersten Schritte bei der Portierungsarbeit!

Absolut verdient – schönen Sonntag!

Ich schreibe Dir gerne später auch noch ein paar Gedanken zu den einzelnen Punkten, die Du bisher auf Deiner Reise streifen konntest.

Kleine Anfrage

Damit ich Dir bzgl. besseres auskommentieren™ ggf. ein wenig entgegenkommen kann, wäre es praktisch, den folgenden Output von Deiner Plattform (aktuelles uPy auf generischem ESP32) zu kennen à la

>>> import os, sys

>>> uos.uname()
(sysname='FiPy', nodename='FiPy', release='1.20.0.rc11', version='v1.9.4-0a38f88 on 2019-05-14', machine='FiPy with ESP32', lorawan='1.0.2', sigfox='1.0.1')

>>> sys.platform
'FiPy'

>>> sys.version
'3.4.0'

>>> sys.implementation
(name='micropython', version=(1, 9, 4))

Diese Werte bekomme ich aus dem Pycom FiPy mit aktueller Development-Firmware von Pycom.

Ausblick

Ich würde dann bei Gelegenheit entsprechende Weichenstellungen innerhalb des Frameworks zur Verfügung stellen und die von Dir angesprochenen Pycom-exklusiven Module ebenfalls entsprechend umlegen, so dass sie bei weiteren Gelegenheiten ggf. für andere uPy Plattformen nachgebaut werden können.

Zwischenfazit

Hauptsache ist natürlich dass der Datenlogger erstmal losläuft – das wäre wirklich ein schönes Erfolgserlebnis. Vielen Dank nochmals!

terkin/network/wifi.py:

original_ssid = self.station.ssid()
original_auth = self.station.auth()

Da kommen einige Folgefehler. Die ganze WLAN Geschichte ist bei pycom & uPy etwas unterschiedlich.

terkin/sensor/system.py:
ADC: Pin muss importiert werden:
from machine import ADC, Pin, enable_irq, disable_irq
Und in SystemBatteryLevel:
#ADC channel used for sampling the raw value.
self.adc = ADC(Pin(34))
statt id=0

Das wars schon. Jetzt läuft es durch, verstirbt dann aber nach einer Weile am Wifi.
Jetzt muss es nur noch funktionieren! :grinning:

>>> uos.uname()
(sysname='esp32', nodename='esp32', release='1.11.0', version='v1.11-219-gaf5c998f3 on 2019-08-18', machine='ESP32 module (spiram) with ESP32')

>>> sys.platform
'esp32'

>>> sys.version
'3.4.0'

>>> sys.implementation
(name='micropython', version=(1, 11, 0))
1 Like

Man könnte natürlich in diesem Stil:

    if sys.platform == 'FiPy':

        # Save the default ssid and auth for restoring AP mode later
        original_ssid = self.station.ssid()
        original_auth = self.station.auth()

        # Setup network interface.
        self.station.init()

    elif sys.platform == 'esp32':

        pass    # not sure if we have to do anything here

Das ganze trennen. Das wird aber etwas eklig zu pflegen. Andererseits sind ja aber auch nicht sooo viele Stellen. Das ganze sauber zu trennen ist auch Aufwand. Und andere Plattformen haben wieder andere Trennstellen.

?

1 Like

Genau so werden wir das machen. Allerdings nicht ganz so spezifisch auf FiPy festgenagelt, sondern einmal leicht abstrahiert auf Pycom oder so. Manche Dinge werden vermutlich auch nicht plattform-spezifisch sein, sondern – u.U. nur implizit durch die Umstände bedingt – spezifisch zur MicroPython-Version zu handhaben sein.

Es ist auf jeden Fall Arbeit, aber es wird sich lohnen.

Ok. Wie läuft das dann praktisch ab? Per git clone und dann pull request? Oder anders?

Ich müsste dann zumindest alle auskommentierten Stellen ‘ordentlich’ machen, sodass es zumindest auf dem FiPy noch läuft.
Danach dann die fehlenden Funktionen nachbauen.

Kannst Du mir die Exception nennen, die bei Dir auftritt, wenn man es so ausführt, wie es für Pycom passt?

>>> from machine import ADC
>>> ADC(id=0)
>>> from machine import ADC
>>> ADC(id=0)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: function takes 1 positional arguments but 0 were given
1 Like

Ja, das wäre am praktischsten. Wenn Du Dich daran direkt hands-on beteiligen willst, wäre es sinnvoll, sich ein wenig mit dem Sandbox-Betrieb (make et al.) vertraut zu machen.

Wir nehmen aber natürlich auch Patches in anderen Formen an – Hauptsache es geht! ;]

Teilweise komme ich Dir da gerade schon entgegen, wurde nur gerade abgelenkt. In ein paar Stunden wirst Du entsprechende Mechanismen dafür im Framework finden, die die Portierung vielleicht schon ein wenig leichter und gleichzeitig “ordentlicher” lassen werden.

@poesel wäre der FiPy / WiPy eine Hardware-Option für dich, wir haben da noch einige Baustellen, bei denen wir gerne Unterstützung annehmen oder wurde das, was du mir BLE vorhast nicht funktionieren?

Prinzipiell schon. Der WiPy entspricht ja mehr oder minder einem esp32.
Aber warum? Wenn wir jetzt Hiveeyes für uPy 1.11 fit machen, macht es eigentlich keinen Unterschied mehr, auf welcher Hardware es läuft.

Es ist vermutlich doch einiges an Arbeit und ich sehe gerade andere Stellen, die nötiger beackert werden müssten.

Hi Markus,

der neue MicroPython Universal Bootloader umal.py bringt nun ein wenig Infrastruktur mit, um Plattformweichen zur Laufzeit zu realisieren. Bei hiveeyes-micropython-firmware/device.py at 0.6.0 · hiveeyes/hiveeyes-micropython-firmware · GitHub wird das nun beispielsweise bei der Ansteuerung der RGB LED eingesetzt.

Der weitere commit Start making Terkin platform-agnostic · hiveeyes/hiveeyes-micropython-firmware@ba9c72e · GitHub setzt ein paar der von Dir aufgezählten Dinge so um, dass es ab jetzt u.U. auch ohne weitere Anpassungen (auskommentieren) an einigen Stellen klappen könnte.

Manche der von Dir erwähnten Änderungen sind jedoch noch nicht umgesetzt, aber immerhin die meisten. Herzlichen Dank für Deine Eingaben!

Das WiFi-Modul zu portieren liegt noch vor uns.

Viele Grüße,
Andreas.

Folgend alle Änderungen, die an 0.6. gemacht werden müssen, damit es mit uPy 1.11 läuft.
Und um es vorweg zu nehmen: es läuft! :smile:

  • /, /lib & /dist-packages reichen:

umal.py, l.65ff:

if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:
        # Extend by path containing frozen modules.
        sys.path[0:0] = ['/flash/lib-mpy']
        # Extend by all paths required for running the sandboxed firmware.
        sys.path.extend(['/flash/dist-packages', '/flash/terkin', '/flash/hiveeyes'])
    else:
        # Extend by all paths required for running the sandboxed firmware.
        sys.path.extend(['/dist-packages'])
  • Timer.Chrono() gibt es nicht. Braucht man zumindest für die uptime auch nicht. Wird aber glaub ich irgendwo noch zum Zeitnehmen verwendet.

Logging:

def getLogger(name=None, level=logging.INFO):
global _chrono

# Keep track of time since boot.
if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:
    if _chrono is None:
        from machine import Timer
        _chrono = Timer.Chrono()
        _chrono.start()
  • s.oben utime.time() = Sekunden seit boot

    class TimedLogRecord(logging.LogRecord):
    def __init__(self, *args, **kwargs):
      super().__init__(*args, **kwargs)
      if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:
          try:
              self.tdelta = _chrono.read()
          except:
              self.tdelta = None
      else:
          self.tdelta = utime.time()
    
  • sys.exc_info existiert nicht. Habe auch keinen Ersatz gefunden, also erstmal None

Dist-packages/logging/init.py:

def exception(self, msg, *args):
    if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:
        self.exc(sys.exc_info()[1], msg, *args)
    else:
        self.exc(None, msg, *args)
  • wie gesagt, /flash existiert nicht. Außerdem gibt es /backup nicht. Ich vermute, das wird von make erzeugt, fehlt dann aber im VSC.

Terkin/configuration:

if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:
    CONFIG_PATH = '/flash'
    BACKUP_PATH = '/flash/backup'
else:
    CONFIG_PATH = '/'
    BACKUP_PATH = '/backup'
  • das ganze LTE Zeug fehlt natürlich und pycom auch

Terkin/device:

    if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:
        import pycom

  • syntax für mac ist unterschiedlich

    Terkin/network/wifi:
    def print_address_status(self):
      if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:
          mac_address = self.humanize_mac_addresses(self.station.mac())
      else:
          mac_address = self.humanize_mac_addresses(self.config('mac'))
      ifconfig = self.station.ifconfig()
      log.info('WiFi STA: Networking address (MAC): %s', mac_address)
      log.info('WiFi STA: Networking address (IP):  %s', ifconfig)
    
  • pycom pins sind strings, uPy ints. Das ist nicht hübsch, geht aber

Terkin/sensor/system:

    # ADC channel used for sampling the raw value.
    try:
        self.adc = ADC(id=0)
    except TypeError:
        from machine import Pin
        if type(self.pin) == str:
            self.adc = ADC(Pin(int(self.pin[1:])))
        else:
            self.adc = ADC(Pin(self.pin))

Und das wars schon. :slight_smile:

2 Likes

Genauer gesagt bedeutet ‘läuft’ eigentlich ‘läuft mit Fehlern’ :slight_smile:

Ich hab bei Wifi angefangen und das jetzt soweit, das es tatsächlich funktioniert. Problem: der connect dauert ziemlich lange und dann ist der Logger schon weiter. Das fällt mir jetzt aber schwer, das korrekt so einzubauen, das alles Notwendige darauf wartet. Da bräuchte ich etwas Hilfe.

Ich hab meine Version angehängt.
wifi.py (14,3 KB)

Anbei auch das startup log. Ich hab ein exit() nach dem ersten loop eingebaut. Daher das verfrühte Programmende.
hiveeyes_log_1908251813.zip (5,4 KB)

1 Like

Noch ein paar Bits gefunden:

Terkin/datalogger

  • esp32 kann BT nicht ausschalten

     if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:
     self.device.power_off_bluetooth()
    
  • die neueren esp32 haben keinen Temperatursensor mehr

      system_sensors = [
          SystemMemoryFree,
          #SystemTemperature,
          SystemBatteryLevel,
          SystemUptime,
      ]
    

Terkin/util:

  • kein crypto

    def random_from_crypto():
      if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:
        import crypto
        r = crypto.getrandbits(32)
      else:
        import urandom
        r = urandom.getrandbits(32)
    return ((r[0]<<24) + (r[1]<<16) + (r[2]<<8) + r[3]) / 4294967295.0
    

Terkin/system:

  • kein init

      # Power on ADC.
      if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:
          self.adc.init()
    

Terkin/wifi:

  • stats gibts nicht

    def read(self):
    
      if self.station is None or sys.platform == 'esp32'
          return
    ...
2 Likes

Hi Markus,

neben MAX17043 mit MicroPython auch an dieser Stelle vielen herzlichen Dank! Wir werden die Änderungen durcharbeiten und in den aktuellen Softwarestand einpflegen.

Sofern das Sandbox-Setup nun u.U. auch für Dich klappt [1], könntest Du die Änderungen ggf. auch per Pull Request zur Verfügung stellen, wenn Du dafür Zeit, Lust und Muße findest. Ansonsten sind wir aber auch über Deine Beschreibungen und die Publikation der notwendigen Änderungen sehr dankbar.

Viele Grüße,
Andreas.


  1. Setup der Terkin-Datenlogger Sandbox schlägt fehl ↩︎

Was ist die beste Methode für eine Codeweiche zwischen pycom und uPy bzw. anderer zukünftiger hardware?

Man könnte ganz stumpf überall sowas abfragen:

if sys.platform in ['WiPy', 'LoPy', 'GPy', 'FiPy']:

Das ist weder hübsch noch wartbar, funktioniert aber überal (wenn man sys importiert).

@Andreas hat vorgeschlagen das so zu machen:

if self.application_info.platform_info.vendor == MicroPythonPlatform.Pycom:

Das funktioniert dann aber an solchen Stellen wie in terkin/configuration.py nicht

class TerkinConfiguration:
    
    A flexible configuration manager.
   
    if self.application_info.platform_info.vendor == MicroPythonPlatform.Pycom:
        CONFIG_PATH = '/flash'
        BACKUP_PATH = '/flash/backup'
    else:
        CONFIG_PATH = '/'
        BACKUP_PATH = '/backup'

weil self hier ein anderes Objekt ist.
Wie macht man das also richtig?

1 Like

Hi Markus,

Du hast Recht, das funktioniert so nicht 1:1 an allen Stellen. Vor allem innerhalb von 3rd-party Bibliotheken will man meist keine zusätzlichen Abhängigkeiten schaffen. In diesem Fall bleibt der Rückgriff auf ein ducktyping-artiges Vorgehen per try / except.

In dem bei Dir vorliegenden Fall – für Plattformweichen innerhalb der Konfigurationseinstellungen – müssen wir u.U. nochmal anders auf den Kontext schauen, weil wir ja aus der aktuellen Python-basierten Konfiguration beizeiten auch eine JSON-Repräsentation erzeugen wollen und wir dann schauen müssen, wie das gut zusammengeht.

Viele Grüße,
Andreas.