Using a state machine (Schrittkette) with MicroPython

Introduction

The State Pattern solves many problems, untangles the code and saves one’s sanity.

Disclaimer: I love state machines :slight_smile:. Thrown at the right problems they are quite useful.

Proposal

Since we (potentially) have bee scales in different roles in different configurations doing very similar things I thought it would be useful to draw a little graph. Please comment.

The graph assumes the following:

There are 3 types of scales

  • Solo - sends data directly to Kotori
  • Node - sends data to a gateway
  • Gateway - collects data from nodes and sends them to Kotori

Depending on the infrastructure at hand there are different radio hardware requirements for the three.
A gateway may or may not be a scale itself.

Each scale can be in one of theses modes:

  • Normal - use deep sleep
  • Maintenance - temporary, use light sleep
  • Power - permanent, use light sleep

Power is for scales that are attached to net power and don’t need to save energy. The difference to maintenance is that it is switched from normal for a time only and will automatically fall back.

The graph is obviously not very detailed but I hope it gives a good overview. I’ve changed it three times will writing this post. I’m sure this is not the final version. :roll_eyes:

2 Likes

OT: btw, wir haben hier im Discourse ein graphviz-Plugin installiert, damit könntest Du dot-Graphen hier direkt reinpacken, wenn Du möchtest (im Moment ist das plugin noch ausgeschaltet, könnte aber angeschaltet werden).

1 Like

Da kam jetzt leider weniger feedback als ich erhofft hatte.

Meine Bitte: schaut euch das nochmal an. Das ist erstmal nicht so spannen, weil es an sich keine neue Funktionalität bringt. Es ist aber IMHO Voraussetzung dafür, das wir flexibler werden.
Momentan laufen die Dinge sehr linear ab und Ausnahmen werden über ifs gehandhabt. Das ist auch ok und richtig, solange die Dinge linear sind. Geht man aber weg von ‘pycom-WLAN-Strom-eine Beute’ (*) dann vervielfältigen sich die Verzweigungen sehr schnell und das wird dann hässlich im Code.

(*) natürlich kann der Code mehr als das, er ist aber in diese Richtung ausgelegt

Kompliziert wird es auch weil wir je nach Übertragungsart verschiedene Daten senden bzw nicht senden. WLAN und GSM sind dabei sehr ähnlich, wir können darüber key-value pairs schicken, d.h. jeder Datensatz ist selbsterklärend. Anders ist es bei LoRa/TTN, wenn wir hier etwas mehr Daten schicken, bekommen wir das mit key-values nicht mehr in einer payload unter.

Hier ist nun die Frage wo wir die Daten wieder zusammenbasteln? Man könnte das auf dem Gateway machen man könnte das auf TTN-Ebene application machen oder eben erst bei der Ankunft bei Kotori oder gar erst in Grafana-Dashboard!

Interessant könnte auch sein normale Daten von Push-Informationen zu unterscheiden. Das wird dann relevant, wenn wir auf dem node Daten sammeln und um Energie zu speichern erst als Bündel von x Datensätzen verschicken. Angenommen wir wollen auch einen Schwarmalarm implementieren, dann können wir nicht 12 Stunden Daten sammeln und nach den 12 Stunden auf dem Server “sagen” “gestern gab’s übrigens einen Schwarm”, sondern dann müsste rudimentäre Analytik schon auf dem node stattfinden, der selbst Push-Nachrichten schickt oder zumindest vorzeitig seine Daten ab den Server übermittelt.

Ähnliches Problem bei Audiodaten. Wir wollen die perspektivisch auch nicht als recording auf den Server schicken, sondern irgendwie aggregiert. Entweder als FFT oder sogar im Idealfall mit einem Algorithmus der für uns wichtige Informationen bezüglich Bienen als Status übermittelt.

Was wir bisher noch gar nicht haben ist die Rückübertragung von Daten. In unserem Fall Konfigurationsdaten. In den Wintermonaten könnte das Messintervall länger sein um Strom zu sparen. Bei den Bienen passiert dann nicht so viel und wir könnten so mit ähnlichem Informationsgehalt Strom sparen. Wenn der node immer am Netz hängt können wir die Informationen immer schicken, falls der node im deep sleep mode ist müsste der nach jeder (?) Datenübertragung nach veränderten Konfigurationsparameter anfragen. Das bedeutet dann auch, dass die (oder ausgewählte) Konfigurationsparameter nicht nur auf dem node sind, sondern eben auch auf dem Server.

Das sind alles richtige Fragen und gute Überlegungen, aber eigentlich schon einen Schritt zu weit.

Mir ging es jetzt zunächst um die Programmstruktur. Momentan läuft es (etwas vereinfacht):
Booten -> Initialisieren -> Messen -> Senden -> Schlafen -> Aufwachen -> Messen -> Senden usw…

Bei einer Umstellung auf Schrittkette würde sich daran auch erstmal nichts ändern. Das ist ja auch der korrekte Ablauf für diesen Anwendungszweck. Schaut man sich die obige Graphik an, sieht man, das “Messen -> Senden -> Schlafen -> Aufwachen” etwa dem linken Pfad entspricht. D.h. der bisherige Ablauf kann dann weiterhin abgebildet werden.
Mit einer Schrittkette lassen sich aber andere Verzweigungen sehr einfach realisieren, ohne das es zu unübersichtlich wird.

Hier ein paar Beispiele in Python, um zu demonstrieren, wie sowas aussehen könnte:
https://python-3-patterns-idioms-test.readthedocs.io/en/latest/StateMachine.html


https://www.python-course.eu/finite_state_machine.php


und die github Seite dazu:

Naja, wie du oben schreibst unterscheidet sich das ganze nur durch die Art des sleep modes? Worauf möchtest du hinaus? Normalerweisr sind doch gateway und node unterschiedliche Geräte oder denkst du an Szenarien bei den ein Gerät sowohl Gateway als auch node ist!?

Was ist bei der Grafik oben der Unterschied zwischen “solo” und “node”? Vielleicht muss auch nur LoRa auf die anderes Seite zu BLE, also einmal IP-basierte übertragenden vs. Übertragung mit anderen Protokollen?

Ich glaube, wir reden ein wenig aneinander vorbei. Ich möchte hier nur die (IMHO) Notwendigkeit einer Umstellung des Programmcodes vorstellen. Da sollte man schon vorher darüber sprechen und kann nicht einfach einen PR schicken. Den haut mir @Andreas dann um die Ohren. ;)

Was der Code dann tut, ist der nächste Schritt. Die vorgeschlagene Änderung muss natürlich das Vorhandene weiter ermöglichen und Änderungen vereinfachen.

Nein, die sleep modes gibt es jetzt schon. Hier geht es nur um die Art und Weise, wann und ob welcher sleep mode ausgeführt wird.

Ich denke auch, das gateway und node verschiedene Geräte sein werden. Mein Ziel ist es aber vor allem, das derselbe Code auf beiden Geräten läuft. Idealerweise muss man dann nur einmal in den settings einstellen, welche Funktion das Gerät ausfüllen soll.

Ein “solo” ist ein Gerät wir es jetzt haben. Es misst Daten und kommuniziert (auf welchem Pfad auch immer) direkt mit Kotori.
Ein “node” misst Daten und schickt diese an ein gateway im Nahbereich. Für den Fall gedacht, wenn es mehrere Beuten am Stand gibt und man Lora oder LTE braucht, um Kotori zu erreichen.

Nur mal ganz fix, weil wir jetzt das graphviz Plugin haben (Danke @weef!)
So sieht der Ablauf momentan aus (SEHR vereinfacht):

1 Like

I just found another state machine library, explicitly designated to run on MicroPython.

About

The goal of this library is to give you a close to the State Pattern simplicity with much more flexibility. And, if needed, the full state machine functionality, including FSM, HSM, PDA and other tasty things.

Features

  • Finite State Machine (FSM)
  • Hierarchical State Machine (HSM) with Internal/External/Local transitions
  • Pushdown Automaton (PDA)
  • Transition callbacks - action, before, after
  • State hooks - enter, exit, and other event handlers
  • Entry and exit actions are associated with states, not transitions
  • Events may be anything as long as they’re hashable
  • States history and transition to previous states
  • Conditional transitions (if/elif/else-like logic)
  • Explicit behaviour (no method or attribute is added to the object containing a state machine)
  • No need to extend a class with State Machine class (composition over inheritance)
  • Fast (even with hundreds of transition rules)
  • Not too many pythonisms, so that it’s easily portable to other languages (ie. JavaScript).
  • Micropython support

Repository

Evaluation

Events / Hooks

I already told @poesel that I liked the event-based machinery of GitHub - fgmacedo/python-statemachine: Python Finite State Machines made easy., it supports callbacks when running events or when entering/exiting states.

GitHub - pgularski/pysm: Versatile and flexible Python State Machine library also supports different ways to attach event handlers, essentially covering the whole shebang:

MicroPython support

What better could we wish for?

1 Like

You might also like this.

In case somebody wants to monitor a statemachine:

What’s not (yet) mentioned there: There is a diagram-plugin for Grafana available, “a diagram can be defined using the Mermaid JS syntax”, taken from the advertisement:

There’s another plugin, FlowCharting - I used that one already but don’t see it currently working with Grafana 6.4.x.

Talking about the whole idea of @poesel, I think it is a good one to introduce such thing especially when the firmware shall become universal/generic for “whatever it should to”. @clemens raised the Gretchenfragen already on “how to deal with compression” and “how to channel alerts with priority”.

In my head the question “when do we want to switch to IP” raises? From my profession I would say as early as possible, but I don’t know enough about LoRa und Bluetooth. Although TCP/IP via LoRa shall be possibe, it would mostly a waste of bytes on that band, too. And concerning compression; our well favoured LTE-NB-IoT-band (native TCP/IPv4) can be considered as expensive, too.

But I am leaving the topic and these thoughts belong proably more to our evergreen-thread Erarbeitung eines kanonischen Datenschemas für imkerliche Meßdaten (I am preparing some post there, too … but good thing wants while have! scnr)