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!
- /, /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.
Genauer gesagt bedeutet ‘läuft’ eigentlich ‘läuft mit Fehlern’
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)
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 ...
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.
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?
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.
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 ) 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 Variablebootloader
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.
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 ;].
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.
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?