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

Wir haben momentan 8 Module mit 14 Stellen an denen die Weiche eingesetzt wird.
Eine davon in /lib (umal.py), der Rest unter /terkin. Keine davon in /dist-packages. 3rd party ist also momentan gar kein Problem.

try / except würde fast überall funktionieren. Dagegen sprechen mAn zwei Dinge: mir widerstrebt es irgendwie, ein Programm bewusst in einen Fehler laufen zu lassen (da bin ich vermutlich altmodisch :slight_smile:) und das Konstrukt wird doof, wenn wir mal einen dritte Hardware unterstützen wollen.
Wenn das keine Hinderungsgründe sind, bau ich das gerne auf try / except um.

Alternativvorschlag: wir bauen ein Modul nur mit den Platformeigenschaften und importieren das bei Bedarf. Das ist vermutlich ‘billiger’ als sys oder try und leichter zu warten und zu lesen.

Wunderbar, hört sich gut an!

Nein, so war das nicht gemeint. try / except sehe ich nur als Fallback für Fälle, wo das Plattformeigenschaftsmodul nicht zur Verfügung steht oder es ungut ist, es zu importieren.

Ja, genau so dachte ich mir das mit der class PlatformInfo aus umal.py. Entweder man

  • importiert und instanziiert die Klasse (erneut), wo man sie braucht, oder man greift wahlweise auf
  • bootloader.platform_info über die globale Variable bootloader oder eben auf
  • das schon bekannte self.application_info.platform_info zurück.

Ah, ok. Das hatte ich nicht verstanden.

Warum funktioniert dann:

from umal import MicroPythonPlatform

    # Keep track of time since boot.
    from terkin.util import GenericChronometer, PycomChronometer
    if bootloader.platform_info.vendor == MicroPythonPlatform.Pycom:
        _chrono = PycomChronometer()
    else:
        _chrono = GenericChronometer()

nicht?

[main.py] INFO: Starting logging
Traceback (most recent call last):
  File "main.py", line 21, in <module>
  File "terkin/logging.py", line 13, in <module>
NameError: name 'bootloader' isn't defined

bootloader ist doch global?
Ich gestehe, das ich mich mit dem objektorientierten manchmal etwas schwer tue. Dazu bin ich den prozeduralen Sprachen zu sehr verhaftet. :slight_smile:

Streich doch mal

und mache dann dort, wo Du “bootloader” benutzen willst, vorher ein “global bootloader”.

#from umal import MicroPythonPlatform
global bootloader

# Keep track of time since boot.
from terkin.util import GenericChronometer, PycomChronometer
if bootloader.platform_info.vendor == MicroPythonPlatform.Pycom:
    _chrono = PycomChronometer()
else:
    _chrono = GenericChronometer()

führt zu:

[main.py] INFO: Starting logging
Traceback (most recent call last):
  File "main.py", line 21, in <module>
  File "terkin/logging.py", line 14, in <module>
NameError: name 'bootloader' isn't defined

Hast Du ein Indiz dafür, dass der Bootloader zu diesem Zeitpunkt bereits angefahren wurde? z.B. die Log-Nachricht [boot.py] INFO: Starting "umal" bootloader müsste da schon zu sehen gewesen sein.

Benutze doch hier im konkreten Fall kurzerhand doch einfach immer den GenericChronometer. Das aktuelle Pycom-Firmware-Release kennt mindestens die Primitiven time.ticks_ms() sowie time.ticks_diff(), daher sollte dieser auch genauso gut mit Pycom funktionieren.

[boot.py] INFO: Python module search path is: ['', '/lib']
[boot.py] INFO: Starting "umal" bootloader
[umal]     INFO: Python module search path is: ['', '/lib', '/dist-packages', '/terkin', '/hiveeyes']
[main.py] INFO: Loading settings
[main.py] INFO: Starting logging
Traceback (most recent call last):
  File "main.py", line 21, in <module>
  File "terkin/logging.py", line 14, in <module>
NameError: name 'bootloader' isn't defined

Jepp, umal wurde geladen.

Blöd. Ich merge jetzt mal Deinen pull request Port to ESP32 WROVER with MicroPython 1.11 by poesel · Pull Request #22 · hiveeyes/hiveeyes-micropython-firmware · GitHub und kümmere mich hernach um entsprechende Verbesserungen bzgl. der Plattformweiche.

Herzlichen Dank für die Basisarbeit!

@Andreas - bei mir ist machine.freq() ein INT. Ist das beim pyboard anders?
Sonst verstehe ich den Sinn der Weiche in device.py nicht.

   if self.application_info.platform_info.vendor == MicroPythonPlatform.Pycom:
        frequency = machine.freq() / 1000000
    else:
        frequency = machine.freq()[0] / 1000000

Ja, dort ist das ein Quadrupel aus core- und bus frequencies. Die Weiche muss hier wohl noch entsprechend aufgemöbelt werden, falls das bei Dir grade hängt. Entweder über die platform_info Konstanten oder per Typüberprüfung à la

frequency = machine.freq()
if type(frequency) is tuple:
    frequency = frequency[0]
frequency = frequency / 1000000

So wäre es universeller und agnostischer gegenüber der Plattformweiche – at all ;].

1 Like

Da gefällt mir aber das besser:

     if self.application_info.platform_info.mcu == McuFamily.STM32:
         frequency = machine.freq()[0] / 1000000
     else:
         frequency = machine.freq() / 1000000

Wenn Du schon so ne schicke Weiche gebastelt hast, wollen wir sie auch nutzen. :wink:
Ist dann besser nachzuvollziehen.

Noch ein Punkt:

>   log.info('Reading configuration file {}'.format(filepath))
>         try:
>             with open(filepath, "r") as instream:
>                 payload = instream.read()
>                 data = json.loads(payload)
>                 return data
> 
>         except Exception as ex:
>             log.exc(ex, 'Reading configuration from "{}" failed'.format(filepath))

ergibt:

   13.1520 [terkin.configuration     ] ERROR  : Reading configuration from "/settings-user.json" failed
Traceback (most recent call last):
  File "terkin/configuration.py", line 153, in load
  OSError: [Errno 2] ENOENT

Den ERROR verstehe ich - die Datei gibt es noch nicht. Warum fängt der except dann den OSERROR aber nicht ab?

Müsste abgefangen werden, die Exception wird aber ausgegeben und das Programm sollte weiterlaufen. Nicht?

Tut es auch. Bin nur beim durchsehen drüber gestolpert. Wenn das kein Problem ist, werde ich es fürderhin ignorieren. :slight_smile:

Letzte Frage für heute. Der I2C-Bus ist bei Vanilla etwas anders als bei Pycom. Gibts da beim Pyboard noch was anderes? Sonst würde das so aussehen (core.py):

def start(self):
    # Todo: Improve error handling.
    try:
        if platform_info.vendor == MicroPythonPlatform.vanilla:
            self.adapter = I2C(self.number, sda = self.pins['sda'], scl = self.pins['scl'], baudrate=100000)
        else:
            self.adapter = I2C(self.number, mode=I2C.MASTER, pins=(self.pins['sda'], self.pins['scl']), baudrate=100000)
        self.scan_devices()
    except Exception as ex:
        log.exc(ex, 'I2C hardware driver failed')

Richtig ausprobiert hab ichs noch nicht, weil noch keine entsprechende Hardware dranhängt.

Just go ahead and take care of any typos – is it Vanilla with a capital "V" actually?

Dito!

Müsste/könnte:

from onewire.onewire import OneWire

nicht:

from onewire import OneWire

heißen? Das erstere funktioniert bei mir nicht, das letztere schon. Da ich aber schon mal etwas wirre Problemem mit dem PATH hatte, wollte ich nachfragen, bevor ich was fixe, was kein Problem ist.

Hmm, habe gerade bemerkt, das onewire Standardmäßig in uPy enthalten ist. Vermutlich ist das das Problem.
Gibts einen Grund, warum wir eine andere Version in dist-packages haben?

Ja, beim Genuine MicroPython (1.11) mittlerweile bereits auch in einer “guten” Variante, bei der die Timing-kritischen Dinge in C implementiert sind [1].

Das ist leider bei Pycom MicroPython (1.9.4) [2] noch nicht der Fall. Der hier verwendete Treiber ist [3] und selbst der ist schrottig, so dass wir auf eine bessere Variante von [4] ausgewichen sind.


  1. micropython/extmod/modonewire.c at master · micropython/micropython · GitHub ↩︎

  2. GitHub - pycom/pycom-micropython-sigfox: A fork of MicroPython with the ESP32 port customized to run on Pycom's IoT multi-network modules. ↩︎

  3. pycom-libraries/lib/onewire/onewire.py at master · pycom/pycom-libraries · GitHub ↩︎

  4. GitHub - robert-hh/pycom-libraries: MicroPython libraries and examples that work out of the box on Pycom's IoT modules ↩︎

1 Like

Warum ist denn die Logik für den Start des mode- und http-server invertiert?

def start_services(self):

    # Start UDP server for pulling device into maintenance mode.
    if self.settings.get('services.api.modeserver.enabled', False):
        try:
            self.start_modeserver()
        except Exception as ex:
            log.exc(ex, 'Starting mode server failed')

    # Start HTTP server
    if self.settings.get('services.api.http.enabled', False):
        try:
            self.start_httpserver()
        except Exception as ex:
            log.exc(ex, 'Starting HTTP server failed')