Dokumentation der 'xMZ-Plattform' (Development Version)

xMZ-Mod-Touch Gasmesszentrale mit Modbus Interface

Diese Version beinhaltet alle Informationen die bei der Entwicklung der 'xMZ-Plattform' angefallen sind. Diese Informationen bilden die Grundlage der "Dokumentation der 'xMZ-Plattform'".

Links

Funktionsbeschreibung

Die xMZ-Plattform besteht aus einem Serverteil (xmz-server) und einem oder mehreren so genannten Clients (z.B. xmz-gui).

Ein Client kann zum Beispiel eine Oberfläche (die so genannte GUI) direkt an der Messzentrale sein. Aber auch Sichtplätze oder Zugriffe via Webbrowswer werden als Clientzugriffe bezeichnet.

Funktionsbeschreibung: Server

Der Server startet so früh wie möglich nach dem Start des Betriebssystem.

Beim Start des Servers wird dessen Version ($CARGO_PKG_VERSION) auf STDOUT ausgegeben.

Im Kapitel Verwaltung: Server wird erklärt wie man den Server neu starten, und den Status des Servers prüfen kann.

Bootstrapping, Konfiguration und Laufzeit Informationen

Beim allerersten Start des Servers wird die Server Instanz aus einer initialen Konfigurationsdatein () erstellt. Dieser Vorgang wird auch als Bootstrapping bezeichnet. Siehe "Implementation: Bootstrapping, Konfiguration und Laufzeit Informationen"

Konnte aus dieser ersten Konfiguration eine funktionale Server Intanz gestartet werden, wird eine Datei mit den Laufzeit Informationen (der aktuelle Zustand des Servers) erzeugt.

Alle weiteren Starts des Servers verwenden diese Laufzeit Informationen.

Dies gewährleistet das Daten wie die Laufzeit "persistent" gespeichert werden können (d.h. das diese nicht nach einem Neustart verloren gehen).

Kann keine Instanz erstellt werden ist das ein Critical Fehler!! Der Server startet in diesem Fall nicht!

Datei: Initale Konfiguration, Bootstrapping

  • /boot/xmz-server.toml

Diese Datein kann über die Umgebungsvariable XMZ_SERVER_CONFIGURATION_PATH geändert werden.

Datei: Laufzeit Informationen

  • /var/cache/xmz-server/status

Diese Datein kann über die Umgebungsvariable XMZ_SERVER_RUNTIME_INFO_PATH geändert werden.

Konfiguration Server

Wie in der Implementation: Serverkonfiguration beschreiben, werden zur Konfiguration der Serverkomponenten Umbgebungsvariablen verwendet.

Es gibt verschiedene Arten dieser Environment Variablen:

  • XMZ_SERVER_ - diese Variablen steuern Eigenschaften der Server Komponente
  • ROCKET_ - diese Variablen steuern Eigenschaften der Rocket Web Komponente

Die Umgebungsvariablen können "von Hand" beim Start der xmz-server Anwendung übergeben werden. In Produktivsystemen werden die Variablen via des systemd Unit Files übergeben.

Click to show xmz-server.service

# xmz-server.service
#
# systemd Unit für die xMZ-Plattform
#
[Unit]
Description="Server der xMZ-Platform"
After=weston.service

[Service]
Type=simple
Environment=XDG_RUNTIME_DIR=/run/user/0
Environment=XMZ_HARDWARE=0.1.0
Environment=LANG=de_DE.UTF-8
Environment="ROCKET_ENV=prod"
Environment="ROCKET_ADDRESS=127.0.0.1"
Environment="ROCKET_PORT=1337"
Environment="ROCKET_LOG=critical"
ExecStart=/usr/bin/xmz-server
Restart=always
RestartSec=10s

Übersicht Umgebungsvariablen

UmbgebunsvariableFunktionsbeschreibung
XMZ_SERVER_CONFIGURATION_PATHSpeicherort der initialen Bootstrap Konfigurationsdatei
XMZ_SERVER_RUNTIME_INFO_PATHSpeicherort der Laufzeit Informationen
ROCKET_ENVRocket Umgebung
ROCKET_ADDRESSAdresse über die die Rocket Instanz erreichbar ist

Fehlerbehandlung

Die Fehler können auf STDERR ausgegeben werden.

Server Tread: json API

Die Serverinstanz startet ein HTTP Server (rocket) mit einer json API die zur Komunikation mit weiteren Komponenten (GUI, Websites) dient.

Server Tread: Persistent Data

In regelmäßigen Abständen speichert der Server die aktuellen Laufzeit Informationen unter: /var/cache/xmz-server/status

Server Tread: Sensoren auslesen

Die Serverinstanz starete ein internen Thread der alle konfigurierten Sensoren abfragt.

Server Tread: Auswertung

In diesem Thread werden folgende Komponenten ausgewertet:

  • Zonen
  • Laufzeit berechnent und,
  • die Spannungsversorgung,
  • Ladestand der Accu

Links

Implementation

Default Implementationen

  • alle Komponenten des Servers sollten Default Implementationen besizen

verwendete Crates

Unter anderen wurden folgende Crates verwendet.

serde

Serialisation und Deserialisation von Datenstrukturen. Wird von vielen verschiedenen Crates verwendet. https://crates.io/crates/serde

toml

Dateisystemformat der xmz-server Konfiguration. https://crates.io/crates/toml

bincode

bincode ist ein kompaktes, binäres Datenformat. Die Laufzeit Informationen des xmz-server werden in diesem Format gespeichert. https://crates.io/crates/bincode

Links

Server

Implementationsdetails des xmz-server.

Datentypen der Sensoren

Die Sensor Datentypen werden in Vec<Arc<Mutex<Box<Sensor + Send>>>> Containern gespeichert. Zu dieser Speicherung gibt es warscheinlich keine Alternative. Versuch die Box<Sensor + Send> in einfachen Vec<T> zu speichern scheitern schon in der update Funktion des Servers. Auch der Versuch die Box<Sensor + Send> in Arc<Mutex<Vec<Box<Sensor + Send>>>> zu speichern scheiterten an dem Versuch diese Struktur thread safe zu machen, auch hier was schon in der update Funktion des Servers schluss.

https://play.rust-lang.org/?gist=47a87dad21335e4fc96478ad5b44a3e2&version=stable&mode=debug

Implementation: Serverstart

Der Serverstart wird mit einem systemd Unit File realisiert.

https://github.com/zzeroo/meta-xmz-mod-touch/tree/master/recipes-xmz-mod-touch/xmz-server-init

systemd Unit

# xmz-server.service
#
# systemd Unit für die xMZ-Plattform
#
[Unit]
Description="Server der xMZ-Platform"
After=weston.service

[Service]
Type=simple
Environment=XDG_RUNTIME_DIR=/run/user/0
Environment=XMZ_HARDWARE=0.1.0
Environment=LANG=de_DE.UTF-8
Environment="ROCKET_ENV=prod"
Environment="ROCKET_ADDRESS=127.0.0.1"
Environment="ROCKET_PORT=1337"
Environment="ROCKET_LOG=critical"
ExecStart=/usr/bin/xmz-server
Restart=always
RestartSec=10s

Implementation: Bootstrapping, Konfiguration und Laufzeit Informationen

Flussdiagramm Bootrapping, Konfiguration

Draw.io

Datenformat Laufzeit Information

Die Laufzeit Informationen werden unter /var/cache/xmz-server/status im bincode Format gespeichert.

Die initiale Konfiguration /boot/xmz-server.toml wird im toml Datenformat gespeichert.

Speicherort: Laufzeit Informationen

  • /var/cache/xmz-server/status

Speicherort: Initale Konfiguration, Bootstrapping

  • /boot/xmz-server.toml

Links

Implementation: Serverkonfiguration

Die Konfiguration des Servers entspricht den "Vorgaben" einer 12-factor Anwendungen.

Es wird das configure Crate verwendet.

Das Dateiformat der Konfigurationsdatei ist toml.

Links

Sensoren

Die Sensoren sind als Trait definiert.

Funktionen des Traits

  • value (Direktwert)
  • average (Mittelwert)
  • update (Update der Werte)

Die konkreten Sensortypen (Analog 4-20mA, CO/NO2 Modbus Kombisensor) implementieren dann diesen Sensor Trait.

Jedem Messwert ist ein Zeitstempel zugeordnet. Dieser wird bei der Mittelwert- bildung verwendet.

Jede Sensor Instanz speichert die Messwerte der letzten 60 Minuten. Ältere Mess- werte werden verworfen.

Sensortypen

RA-GAS CO Sensor Modbus (CO/NO2 Kombisensor mit Modbus Interface)

RA-GAS NO2 Sensor Modbus (CO/NO2 Kombisensor mit Modbus Interface)

4-20mA Analog Sensor an MR-CI4 (Metz Konnect Modbus Interface)

Simmulation Sensor 15Minuten Mittelwert (15min 0, 15min max) "||||||___"

Simmulation Sensor 30Minuten Mittelwert (30min 0, 30min max) "||||||||||||______"

Sägezahn "/_/_/__"

Tablou "/||_/||__"

Random "|_/|/||||_"

Links

Zonen

Web und JSON API

GET Requests die von der Web/ JSON-API unterstützt werden:

JSON API

Die JSON API ist unter der URL /api/ erreichbar

GUI

Verwaltung

Verwaltung: Server

Über eine serielle Schnittstelle oder eine SSH Verbindung kann der Server verwaltet werden. Dazu können die systemd Werkszeuge systemctl und journalctl verwendet werden.

Server Status

systemctl status xmz-server

Server stoppen

systemctl stop xmz-server

Server starten

systemctl start xmz-server

Alternative kann auch der restart Befehl verwendet werden. Hier wird der Server vorab beendet, wenn er im Moment aktiv ist, und anschließend neu gestartet.

systemctl restart xmz-server

Buildsystem

  • via Docker
  • https://github.com/zzeroo/easy-build
docker build -t zzeroo/build-yocto ../build-yocto/
mkdir src/easy-build/shared
cd src/easy-build/shared/
docker run -ti --volume=(pwd):/home/build/ zzeroo/build-yocto:latest

Dokumentation

Die Dokumentation wird mit der Software mdBook realisiert.

Es gibt zwei Arten Dokumentation, eine für Endanwender und Kunden, sowie eine Version für Entwickler und Techniker.

Erstellung der Dokumentation

Die Dokumentation wird via Github Pages gehostet. Branch gh-pages URL https://$USERNAME.github.io/$REPOSITORY

Gebaut mit TravisCI

Travis ist die Quelle des allseits beliebten Badges wie diese Travis Buildstatus

Installation mdBook

Wie auf der mdBook Projektseite beschrieben wird vorab die Software mdBook auf dem lokalen Entwickler PC installiert.

cargo install mdbook

Dateisystemlayout erstellen

Als nächstes wird ein Verzeichis erstellt das den Quellcode des mdBook enthält.

mkdir xmz-doc

Git Repository erstellen

Wie alle Komponenten der [xMZ-Plattform][xmz] wird auch der Quellcode des mdBook in der Versionskontrolle GIT, auf Github gehalten.

Wir erstellen also nun erst einmal ein lokales, leeres Git Repository

cd xmz-doc
git init .

mdBook initalisieren

Nun wird ein leeres mdBook initalisiert. Hierbei werden ein paar grundlegende Dateien angelegt.

Die Frage ob eine .gitignore Datei angelegt werden soll muss mit ja (y) beantwortet werden.

mdbook init .

Git Repo füllen

Das Dateisystemlayout sollte nun in etwa so aussehen:

$ tree
.
├── book
├── book.toml
├── LICENSE
├── README.md
└── src
    ├── chapter_1.md
    └── SUMMARY.md

2 directories, 5 files

Die Dateien README.md und LICENSE habe ich manuell eingefügt, dieser Schritt ist optional.

Mit dem Befehl

git add .

werden nun die Dateien in die Git Versionskontrolle aufgenommen. Das heist Dateien und Ordner die in der .gitignore Datei gelistet sind werden ignoriert.

Anschließend wir mit dem Befehl,

git commit -a -m "Erster Commit, mdBook Grundstruktur"

dieser Stand in die Änderungsliste aufgenommen.

Git Repo auf Github veröffentlichen

Als nächstes legen wir auf Github.com ein Repository an.

Klickt dazu in eurem Profil auf den grünen "New repository" Button ...

und füllt die entsprechenden Felder aus.

Nachdem auf den grünen Button "Create repository" geklickt wurde kann die Remote Adresse in das lokale Git Repository eingetragen werden.

git remote add origin git@github.com:Kliemann-Service-GmbH/xmz-doc.git

Anschließend wird der master Branch in den remoten Origin Zweig gepusht.

git push -u origin master

Das Repository ist nun unter der URL https://github.com/Kliemann-Service-GmbH/xmz-doc erreichbar.

Repo in Travis aktivieren

Als nächstes müssen wir das Github Repository in Travis aktivieren.

Dazu öffnen wir die URL https://travis-ci.org/ im Browser.

Ein Klick auf das Plus Symbol führt zu einer Übersicht der Github Repositories.

Travis aktualisert die Liste nicht automatisch wenn wir auf Github ein neues Repo erstellt haben müssen wir mit dem Button Sync account die Travis-Repo-Liste aktualisieren.

Anschließend wird mti dem kleinen Schalter vor dem Namen des Repos das Repository in Travis aktiviert.

Travis einbinden

Travis benutzt die Datei .travis.yml zur Konfiguration.

Lege nun diese Datei im Root der mdBook Quellen an.

language: rust
branches:
  only:
  - master
before_install:
    - set -e
    - rustup self update
install:
    - source ~/.cargo/env || true
    - true
script:
    - true
after_success: |
  [ $TRAVIS_BRANCH = master ] &&
  [ $TRAVIS_PULL_REQUEST = false ] &&
  cargo install --git https://github.com/rust-lang-nursery/mdBook.git &&
  mdbook build &&
  sudo pip install ghp-import &&
  ghp-import -n book &&
  git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages

Travis Berechtigungen

Jetzt muss der Zugriff von Travis auf unser Github Repository aktiviert werden.

Schließe das Fenster nicht! Der Token ist nur in diesem Moment sichtbar. Im nächsten Befehl musst du $YOUR_TOKEN durch den grün hinterlegten Token ersetzen.

travis encrypt GH_TOKEN=$YOUR_TOKEN --add env.global

Jetzt kann das Fester mit dem Token geschlossen werden. Der letzte Befehl hat die Datei .travis.yml verändert und eine secure: Sektion angefügt.

Die Datei .travis.yml wird nun auch in die Versionskontrolle aufgenommen.

git add .travis.yml
git commit -m "Add Travis"
git push

Travis Results

Nun sollte unter der URL https://$YOUR_GITHUB_USER.github.io/$YOUR_REPO das gerenderte mdBook aufzurufen sein.

Die Github Einstellungen findet man unter //Settings// -> //Github Pages//

Links

TODO

  • Image sichern
    • $USER Anstelle von root
    • SSH Zugriff ausschließlich via KEY
    • SSH Key per Image
  • Image: Kernel, Spectre und Meltdown beobachten, unkritisch zur Zeit
  • Image: individueller Hostname
  • xmz-doc: Anleitung SSH Verbindung herstellen
  • xmz-doc: systemctl Befehler server, gui, usw. (Funktionsbeschreibung Server)

Links

Observer Pattern

/// https://z0ltan.wordpress.com/2017/06/23/the-observer-pattern-in-rust/
extern crate rand;

trait Observable<T> {
    fn register(&mut self, observer: Box<Observer<Item = T>>);
}

trait Observer {
    type Item;

    fn notify(&self, val: &Self::Item);
}

struct EvenCounter {
    counter: u32,
    observers: Vec<Box<Observer<Item = u32>>>,
}

impl EvenCounter {
    fn new() -> Self {
        EvenCounter {
            counter: 0u32,
            observers: Vec::new(),
        }
    }

    fn run(&mut self) {
        loop {
            use std::thread;
            use std::time::Duration;

            thread::sleep(Duration::from_millis(self.get_rand_duration()));

            self.counter += 1;

            if self.counter % 2 == 0 {
                for observer in self.observers.iter() {
                    observer.notify(&self.counter);
                }
            }
        }
    }

    fn get_rand_duration(&self) -> u64 {
        use rand::{Rng, thread_rng};
        let mut rng = thread_rng();
        rng.gen_range(0, 1000)
    }
}

impl Observable<u32> for EvenCounter {
    fn register(&mut self, observer: Box<Observer<Item = u32>>) {
        self.observers.push(observer);
    }
}


struct EvenObserver {
    name: String,
}

impl EvenObserver {
    fn new(name: String) -> Self {
        EvenObserver { name }
    }

    fn name(&self) -> &str {
        &self.name
    }
}

impl Observer for EvenObserver {
    type Item = u32;

    fn notify(&self, val: &Self::Item) {
        println!("'{}' got: {}", self.name(), val);
    }
}

fn main() {
    let mut foo = EvenCounter::new();
    let (bar, baz, quux) = (Box::new(EvenObserver::new("bar".to_string())),
                            Box::new(EvenObserver::new("baz".to_string())),
                            Box::new(EvenObserver::new("quux".to_string())));

    foo.register(bar);
    foo.register(baz);
    foo.register(quux);

    foo.run();
}