Vergleich der Implementierung (Teil 5)

Die Finanzverwaltung existiert nun in zwei Architekturen. Nun kein ein Vergleich erfolgen. Welches ist für eine private Anwendung die sinnvollere Architektur?

Die folgenden Kriterien zieht der Vergleich heran:

  • Größe der Programmdatei
  • Performance
  • Komplexität der Weiterentwicklung

Nach dem Vergleich folgt die Bewertung. Hierdurch ist die Entscheidung für eine zu wählende Architektur möglich.

Größenvergleich

Ohne dem kommen die Kerl doch nie aus *g*. Allerdings ladet der Inhalt der Archive erst mal im Speicher. Folglich startet das Programm mit dem kleineren Archiv schneller und läßt mehr Freiraum für Daten. Mit 26,8 zu 44,7 MB haben hier klar die Events die Nase vorn. Bei den Events entfällt JPA mit allen zugehörigen Bibliotheken zudem die Datenbank. Folglich steckt hier auch sehr viel weniger in der Jar-Datei.

Vergleich der Performance

Den Vergleich der Performance gilt es zu differenzieren. Ich mag einmal die Event-Variante mit einer echten Datenbank-Version vergleichen, beide male landen die Daten in einer Datei. Allerdings teste ich auch die In-Memory-Datenbank, hierdurch versuche ich einen Vergleich zwischen der reinen Architektur zu ermöglichen. Denn hier zählt nicht die Zeit eines Dateizugriffs.

Die einzelnen Werte fasst die folgende Tabelle zusammen:

Kriterium Redux JPA-In-Memory JPA-Datei
Start-Zeit ohne Daten 2 s 4 -5 s 5 s
Start-Zeit mit Daten 2,5s  – 4,4 s
Konto lesen ohne Daten 4-5 ms 5 ms 5 ms
Konto lesen mit Daten 3 ms 4-5 ms 5 ms
Buchungen lesen 3-4 ms 11-12 ms 11 ms
Buchen 4 ms 12 ms 12 ms

Zunächst gilt es zu bemerken: wer misst misst Mist! Die Zahlen sind natürlich abhängig von meinem Rechner und was er sonst noch zu tun hat.

Zunächst gab es jeweils ein Warm-Up mit 3 lesenden bzw. 5 schreibenden Aufrufen. Die Buchungen je Konto habe ich gelesen nach 5 Warm-Up + 50 Buchungen. Mehr Buchungen auf einen Streich kann keine Oberfläche sinnvoll darstellen und folglich unrealistisch. Anschließend folgten weiter 1000 Buchungen, damit für einen Start mit Buchungen auch Futter da war. Die Startzeit mit Buchungen ist für Redux eine Herausforderung. Bevor es weiter arbeiten kann, hat es alle alten Actions zu lesen und zu verarbeiten. Natürlich dauert das umso länger, je mehr Action vorliegen.

Allgemein ist die Event-Verarbeitung schneller als die Datenbank-Alternative. Schließlich gibt es auch weniger technischen Overhead. Trotz der gut 1000 Buchungen ist selbst die Startzeit geringer als mit der Datenbank. Ich habe mal einen Start mit 10.000 Buchungen gemacht. Damit Dauerte der Redux-Start etwa so lange, wie die JPA.
Der Vergleich zwischen einer in Memory und einer in die Datei schreibenden Datei bringt dagegen keine nennenswerten Unterschiede.

Vergleich der Erweiterbarkeit

Wie steht es um die Erweiterbarkeit? Dazu möchte ich die Arbeitsschritte für das Einfügen einer neuen Entität aufzählen.

Schritte für die JPA-Implementierung

  • Zunächst ist eine Modell-Klasse anzulegen.
  • Die Modell-Klasse benötigt JPA-Annotationen
  • Es folgen das Repository und ggf.
  • Serviceklassen zur Darstellung neue Anwendungsfälle
  • Das Datenbankschema ist zu erweitern

Schritte für Redux

  • Auch hier ist eine Modell-Klasse anzulegen
  • Es folgen Action und Reducer für die neuen Anwendungsfälle
  • ggf. ist der Status zu erweitern.

Vergleich zwischen den Implementierungen

Ein generisches Speichern von Actions vorausgesetzt, ist Redux praktisch leichter umzusetzen. Die verwendeten Technologie ist dort schlicht Java. Reducer ggf. Filter auf den State sind reine Funktionen und somit auch recht leicht testbar. Im Gegensatz dazu sind im JPA-Umfeld unterschiedliche Techniken zu beherrschen: Annotationen, Spring-Data-Konventionen bzw. JQL-Abfragen, SQL für’s Schema – das ganze Datenbankabhängig. Abschließend sind etliche Tests eher Integrations- als Unittest.

finally

Unter dem Strich ist Redux kleiner und schneller, jedenfalls bei dieser Datenmenge. Bei einem deutlich größeren Datenvolumen, käme der Speicher des Redux ohne weitere Maßnahmen an seine Grenzen. Die notwendigen Erweiterungen könnten dann für die Datenbank sprechen.

Die gemessenen Zeiten sind generell für ein kleines Programm im Heimgebrauch zu vernachlässigen. Umso mehr wiegt die Erweiterbar- und Wartbarkeit. Hier ist der Redux-Ansatz weniger komplex.

Schnell und einfach spricht bei den gegebenen Annahmen mehr für den Redux-Ansatz. Folglich werde ich ihn weiter vorantreiben.

Happy coding!

Events zur Umsetzung einer Finanzverwaltung (Teil 4)

Im letzten Teil habe ich die kleine Finanzverwaltung mit Hilfe einer relationalen Datenbank und der Java Persistence API realisiert. Diesmal möchte ich eine Alternative zu diesem gängigen Setup umsetzen. Ich versuche eine Umsetzung mit Events, die das Programm einfach in einer Datei speichert.

Warum eine Alternative mit Events?

Die Programmierung gegen eine Datenbank hat aufgrund der “objektrelationalen Unverträglichkeit” ihre Tücken. Erweiterungen z.B. Vererbungen machen Abfragen komplex und machmal langsamer.

Für die heimische Applikation ist eine Datenbank möglicherweise übertrieben. Zumal die die Datenmenge ist überschaubar ist. Sind es in einem Monat 25 Buchungen, so scheint mir das viel zu sein. Auf Sicht von 10 Jahren kommen so gerade mal 3000 Buchungen zusammen. Die passen heutzutage entspannt in den Speicher. Folglich reicht es die Buchungen in einer Datei zu speichern und beim Programmstart wieder zu lesen.

Dieses Vorgehen ist auch Event Sourcing, wofür Buchhaltung ein Paradebeispiel ist. Die Buchungen bilden die Kontosalden, praktisch der Status des Systems zu einem Zeitpunkt. Die Finanzverwaltung kann ihn durch erneutes “abspielen” der Buchungen jederzeit wieder herstellen.

Ein Status kann als Snapshot gespeichert sein. Dieser historischer Zustand – in diesem Beispiel die Kontostände – ist dann schnell gelesen, ohne alle Buchungen erneut lesen zu müssen. Architektonisch geht dieser Ansatz in Richtung CQRS.

Ein “Events” basiertes System: Redux

In der Webprogrammierung gibt es mit Redux ein Event-basiertes Vorgehen, um den Status im Client zentral zu verwalten. Mir gefällt dieser Ansatz sehr gut, da er das Event-Prinzip sehr schlicht umsetzt.

Eine Action ist der Anstoß zu einer Änderung des Status. Den ermittelt der Reducer, bei dem es sich um eine Funktion handelt. Die Argumente dieser Funktion sind die Action und der aktuelle State. Das Ergebnis ist ein neuer State. Dieser berücksichtigt die Änderungen, die eine Action auslöst.

Redux-Prinzip

Die Action ist ein unveränderliches Objekt, welches für die Umsetzung erforderliche Daten enthalten kann.
Wie bereits erwähnt ist der Reducer eine Funktion. Ein definierter Status führt mit der gleichen Action zu einem immer gleichen neuen State, wodurch der Reducer leicht testbar ist.
Speichere ich also die Actions und ermittle – ausgehend von einem initialen Status – sukzessive den aktuellen Status, betreibe ich mit wenigen Elementen letztlich nichts anderes als Event Sourcing.

Die Schnittstelle für die Programmierung ist der Store.

Der Store speichert den State und stellt die Dispatch-Operation bereit. Sie ermittelt mit Hilde des Reducers und der übergebenen Action den neuen State und ersetzt den alten.
Der Stores ermöglicht dem Programmierer auch Zugriff auf den Status. Ebenfalls bietet der Store das verfolgen von Änderungen an (Observer-Pattern). In Javascript-artigen Sprachen steht dafür die Möglichkeit Fallback-Funktionen zu registrieren (“subscribe()”). Die Java-Konvention ist dagegen eher das registrieren eines Listeners.

In Webclient-Implementierungen lösen die Abarbeitung von Actions sogenannte Effekte aus. Die setzen dort Seiteneffekte um, insbesondere der Zugriff auf ein Backend. In der Finanzverwaltung spielt das erst mal keine Rolle, denn das Redux-Prinzip soll ja gerade im Backend umgesetzt werden.

Eine einfache Redux-Implementierung

Als Programmierschnittstelle dient ein Store, dessen Schnittstelle ich – wie zuvor beschrieben – erst mal allgemein definiere:

public abstract class Store<T extends State> {

	protected T state;
	protected List<Listener> listeners = new ArrayList<>();
	
	public Store(StateInitializer<T> initializer) {
		state = initializer.initialState(this);
	}
	
	public abstract void dispatch(Action<?> action);

	public T getState() {
		return state;
	}
	
	public void addListener(Listener listener) {
		listeners.add(listener);
	}
}

Der erste Teil dieser Reihe hat drei Anwendungsfälle beschrieben. Buchungen zu erstellen ist die einzige schreibende Funktion. Sie ist als Action zu implementieren, da sie den Status der Anwendung ändert.

Die Umsetzung erfolgt in der Dispatch-Methode. Sie könnte z.B. generisch registrierten Reducer-Funktionen weiterleiten. Die damit verbundene Komplexität ist für eine erste Implementierung jedoch unnötig.

	@Override
	public void dispatch(Action<?> action) {
		switch (action.getType()) {
		case BuchenAction.TYPE:
			state = BuchhaltungReducer.buche(state, (BuchenAction)action);
			break;
		default:
			throw new IllegalArgumentException("ungekannte Action: "+action.getType());
		}
		listeners.forEach( listener -> listener.fireAction(action) );
	}

Der BuchhaltungState unterscheidet zwischen Grundbuch und Hauptbuch, wodurch die typische Sicht auf die Buchungen und Konten gegeben sind. Diese sind Listen und zeigen auf Objekte, deren Relationen wiederum auf Objekte zeigen. Durch diese Pointer liegen die Konten und Buchungen nur einmal miteinander verknüpft im Speicher vor. Deshalb entfallen Redundanzen und die Speicherbelastung ist vergleichsweise gering.
Der State ist als unveränderliches Objekt vorgesehen. Im Ideal sollte ein neuer State eine Kopie mit Änderungen sein. Zumindest ist dieses Vorgehen sinnvoll, wenn der Store parallel Angesteuert würde.

Im Code der Dispatch-Methode ist die statische Reducer-Funktion zu erkennen. Sie übernimmt eine Buchung aus der Action, baut alle Relationen zu den beteiligten Objekten auf und sorgt für die Anpassung der Salden. Die Programmierung für den Prototyp ist dabei recht “schmutzig”:

	public static BuchhaltungState buche(BuchhaltungState state, Action<?> action) {
		BuchenAction buchenAction = (BuchenAction)action;
		Buchung buchung = buchenAction.getPayload();

		state.getGrundbuch().add(buchung);
		
		buchung.getUmsaetze().stream().forEach( umsatz -> {
			umsatz.setBuchung(buchung);
			
			Konto konto;
			try {
				konto = selectKontoByName(state, umsatz.getKonto().getName());
			} catch (UnkownEntityException e) {
				konto = new Konto();
				konto.setName(umsatz.getKonto().getName());
				state.getHauptbuch().add(konto);
			}
			konto.verbuche(umsatz);
		});
		
		return state;
	}

Einmal erzeugt sie den State nicht neu. Das wäre technisch aufwendig und würde den Code erst mal nur aufblähen.
Nicht vorhandene Konten legt die Methode direkt an. Eine konsequente Umsetzung müsste mit eine “Konto-Anlegen-Action” arbeiten, wobei diese wiederum fachlich zu hinterfragen ist. Diese einfache Implementierung ist erst mal ausreichend, um mit der relationalen Implementierung verglichen zu werden.
BuchenAction sind wieder unveränderliches Objekte, die eine Buchung aufnehmen. Die Action verwendet Modell-Klassen. Die Buchung bildet die Spitze einer Objekt-Hierarchie. Sie ist leicht zu serialisieren, indem über diesen Baum iteriert wird.

Aus dem Status sind nun die Konten und deren Buchungen zu lesen, um lesenden Anwendungsfälle zu realisieren. Filter sind wieder statische Funktionen, die den Status und ggf. Parameter erhalten.

	public static Konto selectKontoByName(BuchhaltungState state, String kontoName) {
		return state.getHauptbuch().stream()
				.filter( konto -> konto.getName().equals(kontoName))
				.findFirst()
				.orElseThrow( () -> new UnkownEntityException("Konto mit Namen: "+kontoName));
	}
	
	public static List<Konto> selectKonten(BuchhaltungState state) {
		return state.getHauptbuch();
	}
	
	public static List<Buchung> selectBuchungByKontoName(BuchhaltungState state, String kontoName) {
		return selectKontoByName(state, kontoName)
			.getUmsaetze().stream()
			.map( umsatz -> umsatz.getBuchung())
			.collect(Collectors.toList());
	}

Redux in die API einbauen

Der Controller benötigt einen Store. Mit Filter und Action bildet er dann die API ab.

	@GetMapping("konto")
	public List<Konto> findAllKonten() {
		return kontoMapper.domainKontoListToApiKontoList(
				selectKonten(store.getState()));
	}
	
	@GetMapping("konto/{kontoName}/umsatz")
	public List<Buchung> findKontoBuchungenByKontoName(@PathVariable("kontoName") String kontoName) {
		return buchungMapper.domainBuchungListToApiBuchungListe(
				selectBuchungByKontoName(store.getState(), kontoName));
	}

	@PostMapping("buche")
	public ResponseEntity<String> buche(@RequestBody Buchung buchung) {
		
		try {
			store.dispatch(
					BuchhaltungActions.buche(
							buchungMapper.apiBuchungToDomainBuchung(buchung)));
		} catch (Exception e) {
			return ResponseEntity.unprocessableEntity().body("Fehler: " + e.getMessage());
		}
		
		return ResponseEntity.accepted().body("Verarbeitet");
	}

Speichern und Laden der Daten

Der Store ist die zentrale Redux-API. Er realisiert auch das speichern und lesen von Daten. Für den BuchhaltungStore ist ein LogListener pflicht. Dieser hängt eine ausgeführte Action an eine Action-Datei an.

Sobald der Store intern initialisiert ist, liest er diese Datei ein und führt die gelesenen Actions aus, wodurch er den bei Programmende erreichten Status wieder hergestellt.

finally

Nun existieren zwei Prototypen für eine Finanzverwaltung, die es zu vergleichen und bewerten gilt. Davon beim nächsten mal mehr.

Happy coding!

 

 

 

Implementieren der Finanzverwaltung mit JPA (Teil 3)

Dieser Beitrag beschreibt die Implementierung der Schnittstelle aus dem letzten Teil. Zur Speicherung dient eine relationale Datenbank – H2. Auf sie greift das Programm mittels der Java Persistente API (JPA) in der “Geschmacksrichtung” Spring-Data-JPA zu.

Implementieren der Datenbankanbindung

Das Modell mit JPA-Annotationen implementieren

Für die Speicherung der Daten in eine relationale Datenbank kommt die Java Persistente API (JPA) zum Einsatz. Die Abbildung der Model-Klassen in ein Datenbank-Schema erfolgt dabei mittels Annotationen.

@Data
@Entity
public class Buchung {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String verwendung;
	private String empfaenger;
	
	@ToString.Exclude
	@OneToMany(mappedBy = "buchung", cascade = CascadeType.ALL, 
                      fetch = FetchType.EAGER, orphanRemoval = true)
	private List<Umsatz> umsaetze = new ArrayList<>();
	
	public void addUmsatz(Umsatz umsatz) {
		getUmsaetze().add(umsatz);
	}
}

@Data
@Entity
public class Umsatz {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private LocalDate valuta;
	private BigDecimal betrag;
	
	@ManyToOne
	@JoinColumn(name = "konto_id", nullable = false,
                    updatable = false)
	private Konto konto;
	
	@ManyToOne
	@JoinColumn(name = "buchung_id", nullable = false, 
                    updatable = false)
	private Buchung buchung;
}

@Data
@Entity
public class Konto {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String name;
	private BigDecimal saldo = BigDecimal.ZERO;
	
	@OneToMany(mappedBy = "konto")
	private List<Umsatz> umsaetze = new ArrayList<>();

	public void verbuche(Umsatz umsatz) {
		umsaetze.add(umsatz);
		saldo = saldo.add(umsatz.getBetrag());
	}
}

Technisches Setup

Das Umfeld und die Zugriffe ist durch Spring-Data-JPA einfach zu implementieren. Die Umsetzung ist analog des JPA-Guides implementiert.

Als Datenbank dient H2, die als Abhängigkeit im Projekt eingebunden ist. Sie steht damit im Klassenpfad. Der Spring-Data-Starter sorgt ohne zusätzliche Konfiguration erst mal für eine In-Memory-Datenbank.

In der Datei application.properties ist die Eigenschaft spring.jpa.hibernate.ddl-auto=update gesetzt. Spring-Data verwendete Hiberante, das bei jedem Start das Datenbankschema prüft. Da die In-Memory-Datenbank nach dem Start leer ist, entsteht hier immer ein vollständige Schema.

Die Datenbankzugriffe implementieren

Die eigentlichen Zugriffe implementieren Repository-Interfaces, die CrudRepository von Spring-Data erweitern. Typische Zugriffe, wie Speichen, alle Daten laden, Löschen, etc. sind damit durch den Framework gegeben.

Spring Data ermöglicht das Erstellen von Abfragen auf Attribute, indem einer Abfrage-Methode eine Konventionen der Benennung einhält. Konten sollen per Name zu finden sein. Diese einfache Abfrage ist gegeben durch:

Konto findByName(String name);

Einzig das Laden der Buchungen je Konto ist etwas komplexer, deshalb ist ein Ansatz via Konvention der Benennung hier nicht sinnvoll. Hier hilft eine @Query-Annotation mit einer Abfrage, die in JPAs Quere-Language definiert ist.

public interface BuchungRepository extends CrudRepository<Buchung, String>{
	@Query(
	 "select b from Buchung b "
	 + "join b.umsaetze u "
	+ "where u.konto.name = :kontoName "
	+ "order by u.valuta desc, b.id desc ")
	List<Buchung> findByKontoName(String kontoName);
}

Implemtentieren der Service-Klassen

Lesende Funktionen

Die Controller-Klasse des letzten Teil delegiert alle Logik an Service-Klassen. Die lesenden Anwendungsfälle setzten die Repository-Interfaces um, an die Service-Klassen Abfragen einfach nur weiterleiten. Die Service-Ebene als Zwischenstation hat allerdings Vorteile.

  • Service-Klassen sind zentraler Ansatzpunkt, an dem Transaktionsverhalten definiert werden kann.
  • Die API-Ebene kann auf zusätzliche Importe von Repositories verichten.

Schreibende Funktion: buchen

Die Kern-Funktion ist somit im BuchungService das buchen, das letztlich Jahrhunderte alte Praxis der doppelten Buchführung technisch umsetzt:

  1. Der Buchführende trägt Geschäftsvorfälle laufend in ein Journal (Grundbuch) ein. (Anwendungsfall “Buchung erstellen”)
  2. Anschließend erfolgt ein Übertrag der Kontoumsätze je Konto in das Hauptbuch, wodurch die Bewegungen auf einem Konto sichtbar sind (ermöglicht den Anwendungsfall “Umsatz anzeigen”)
  3. Abschließend erfolgt die Saldierung der Konten und der aktuelle Kontostand ist auf den Konten nachvollziehbar (Anwendungsfall “Konten abfragen”)

Die Reihenfolge der Bearbeitung ist dabei insbesondere der Abhängigkeiten der Datenbank geschuldet.

@Transactional	
public void buche(Buchung buchung) {
  for (Umsatz umsatz : buchung.getUmsaetze()) {
    			
    Konto konto = kontoRepository.findByName(umsatz.getKonto().getName());
    if (isNull(konto)) {
      konto = kontoRepository.save(umsatz.getKonto());
      konto.setSaldo(BigDecimal.ZERO);
    }
    umsatz.setKonto(konto);
    umsatz.setBuchung(buchung);
    konto.getUmsaetze().add(umsatz);

    konto.setSaldo(konto.getSaldo().add(umsatz.getBetrag()));

    kontoRepository.save(konto);
  }
  buchungRepository.save(buchung);	
}

Zunächst erfolgt das Buchen in einer Transaktion. Ich spreche alle Konten an oder mache keine Änderung. Es kommt so nicht zu einer plötzlichen Geldvermehrung, ohne dass auch jemand zahlt.

Die Verarbeitung arbeitet zunächst alle in der übergebenen Buchung genannten Umsätze ab.
Zu jedem Umsatz der übergebenen Buchung sucht die Methode zunächst ein Konto mit dem genannten Namen. Sollte es nicht existieren, so wird dieses Konto angelegt.
Als nächstes entstehen die bidirektionalen Relationen zwischen den Entitäten Buchung-Umsatz-Konto. Sie sind in der übergebenen Struktur einseitig schon vorhanden, benötigen aber noch die technische Gegenrichtung.

Abschließend berechnet und setzt die buchen-Methode den neuen Saldo des Kontos und speichert es.

Nach der Verarbeitung ist nun noch die Buchung selbst zu speichern. Dabei wandern auch die zugehörigen Umsätze in der Datenbank, da diese im JPA-Mapping kaskadierend an die Buchung gebunden sind.

finally

Damit liegt eine erste, einfache Implementierung vor. Sie ist mehr aus einem Reflex entstanden, der im beruflichen Kontext geübt ist. Gibt es nicht noch einen weiteren Weg, um eine Finanzverwaltung für den Heimgebrauch zu implementieren? Doch die gibt es. Dazu im nächsten Teil mehr. Anschließend möchte ich beide Implementierungen vergleichen, und den vielversprechenderen Weg weiter implementieren.

Happy coding!

Die Backend-Schnittstelle für die Finanzverwaltung (Teil 2)

Im ersten Teil habe ich das initiale Modell und die ersten Anforderungen der Anwendung beschrieben. Nun geht es um die Backend-Schnittstelle, die die beschriebenen Anwendungsfälle ermöglicht.

Das Projekt für die Anwendung

Als Basis für – insbesondere das Backend – dient Spring Boot. Mit Hilfe einer unterstützenden Entwicklungsumgebung oder auf der Startseite ist das Projekt schnell konfiguriert.

Ich ergänze das Projekt mit den Startern bzw. Abhängigkeiten

  • Spring Web,
  • Lombok,
  • Spring Boot DevTools,
  • Spring Data JPA und
  • H2

Für die (REST-)Schnittstelle benötige ich den Web-Starter. Mit Lombok spare ich mir den Standardcode (Getter, Setter, …). Die DevTools unterstützen die Entwicklung. Beispielsweise startet der Server nach Änderungen automatisch neu, damit die Änderungen wirksam werden.

Die übrigen Starter sind Grundlage für eine erste konkrete Implementierung der beschriebenen Anwendungsfälle.

Entwicklungshilfe

Für die Entwicklung und den Test füge ich Swagger hinzu. Die pom.xml erhält die Abhängigkeiten

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>${springfox.swagger2.version}</version>
</dependency>

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>${springfox.swagger2.version}</version>
</dependency>		

In den Property setze ich die aktuelle Version (aktuell 2.9.2) ein.

Es fehlt noch die Konfiguration, dann ist die Test-Oberfläche unter http://localhost:8080/swagger-ui.html verfügbar. 

@Configuration
@EnableSwagger2
public class SwaggerConfig {

	@Bean
    public Docket api() { 
        return new Docket(DocumentationType.SWAGGER_2)  
          .select()                                  
          .apis(RequestHandlerSelectors.any())              
          .paths(PathSelectors.any())                          
          .build();                                           
    }
}

Die Backend-Schnittstelle

Domain Modell

Zunächst lege ich das Domain-Modell in einem eigenen Package an. Die Umsetzung entspricht dem Diagramm aus dem vorhergehenden Beitrag.

@Data
public class Buchung {

	private String verwendung;
	private String empfaenger;
	private List<Umsatz> umsaetze = new ArrayList<>();
	
	public void addUmsatz(Umsatz umsatz) {
		getUmsaetze().add(umsatz);
	}
}
@Data
public class Umsatz {

	private LocalDate valuta;
	private BigDecimal betrag;
	private Konto konto;
	private Buchung buchung;
}

Das Konto enthält die Operation verbuche(.), die einen Umsatz zum Konto aufnimmt. Dabei übernimmt sie den Umsatz in die Liste der zum Konto gehörenden Umsätze und addiert gleichzeitig den Betrag aus dem Umsatz zum Saldo. Da Soll- und Haben-Beträge über das Vorzeichen unterschieden sind, ist keine weitere Logik notwendig.

@Data
public class Konto {

	private String name;
	private BigDecimal saldo;
	private List<Umsatz> umsaetze = new ArrayList<>();

	public void verbuche(Umsatz umsatz) {
		umsaetze.add(umsatz);
		saldo = saldo.add(umsatz.getBetrag());
	}
}

Die REST-Schnittstelle der Anwendung

Die Backend-Schnittstelle ist eine REST-Schnittstelle und entsteht in Spring durch eine Controller-Klasse. Dank der Spring Boot ist die Umsetzung einfach. Wer sich dabei unsicher ist, findet in den Spring Boot Guides schnelle Anleitungen.

Die BuchhaltungController-Klasse setzt dabei die im vorausgegangenen Beitrag definierten drei Anwendungsfälle um:

  • Buchen erstellen
  • Konten abfragen
  • Umsatz anzeigen

Die Umsetzung ist noch nicht final, denn beispielsweise ist die Abfrage der Buchungen je Konto ohne Paging nicht sinnvoll. Die Liste wird nach einiger Zeit einfach zu lang, was zu unnötigen Verzögerungen beim Laden, Übertragen und Darstellen in der Oberfläche führt. Auch mag ich das Konto lieber über eine technische Id abfragen, um Problemen mit Leerzeichen oder Sonderzeichen vorzubeugen.

@RestController
@RequestMapping("api/buchhaltung")
public class BuchhaltungController {

	@GetMapping("konto")
	public List<Konto> findAllKonten() {
	    throw new UnsupportedOperationException();
	}
	
	@GetMapping("konto/{kontoName}/umsatz")
	public List<Buchung> findKontoBuchungenByKontoName(
                @PathVariable("kontoName") String kontoName) {
	    throw new UnsupportedOperationException();
	}

	@PostMapping("buche")
	public ResponseEntity<String> buche(
                @RequestBody Buchung buchung) {
	    throw new UnsupportedOperationException();
	}
}

Eigene Klassen für die Datenübertragung in der Backend-Schnittstelle

Für die Datenübertragung in der Schnittstelle möchte ich eigene Transfer-Klassen einsetzten. Die Services sollen mit Domain-Objekten arbeiten. Deren Klassen möchte ich allerdings frei von Schnittstellen spezifischen Abhängigkeiten halten, und die wären für die Umwandlung in JSON erforderlich. Insbesondere sind die Relationen bidirektional implementiert, was ohne Zutun wegen zu endlosen Rekursionen führen würde. Die API-Klassen haben das folgende Aussehen:

@Data
public class Buchung {
	private String verwendung;
	private String empfaenger;
	private List<Umsatz> umsaetze = new ArrayList<>();
}

@Data
public class Umsatz {
	private LocalDate valuta;
	private BigDecimal betrag;
	private Konto konto;
}

@Data
@JsonInclude(Include.NON_NULL)
public class Konto {
	private String name;
	private BigDecimal saldo;
}

Im Konto sorgt die JsonInclude-Annation dafür, dass nur Felder in das JSON geschrieben werden, die auch tatsächlich Werte enthalten.

Die Übertragung erfolgt in Mapper-Klassen.

@Component
public class KontoMapper {
	public List<Konto> domainKontoListToApiKontoList(
                List<buchhaltung.domain.Konto> domainKonten) {
	    return domainKonten.stream()
		.map( domainKonto ->
                        domainKontoToApiKonto(domainKonto) )
				.collect(Collectors.toList());
	}

	public Konto domainKontoToApiKonto(
            buchhaltung.domain.Konto domainKonto) {
		Konto apiKonto = new Konto();
		apiKonto.setName(domainKonto.getName());
		apiKonto.setSaldo(domainKonto.getSaldo());
		return apiKonto;
	}
}

Der Konto-Mapper steht als Beispiel für das Codierungsmuster. Die Formalität sollte helfen, diese Transformation später ggf. durch Hilfsbibliotheken zu automatisieren, beispielsweise durch MapStruct.

Das folgende Bild zeigt in der Übersicht die Struktur der Schnittstelle.

Backend-Schnittstelle
Rest-Schnittstelle mit Mapper

Der Abschluss

Ergänzt um Service-Klassen mit Methoden-Signaturen entsteht als Schnittstelle der folgende Controller:

@RestController
@RequestMapping("api/buchhaltung")
public class BuchhaltungController {
	private KontoService kontoService;
	private BuchungService buchungService;
	private KontoMapper kontoMapper;
	private BuchungMapper buchungMapper;

	public BuchhaltungController(
			KontoService kontoService, 
			BuchungService buchungService, 
			KontoMapper kontoMapper,
			BuchungMapper buchungMapper) {

		this.kontoService = kontoService;
		this.buchungService = buchungService;
		this.kontoMapper = kontoMapper;
		this.buchungMapper = buchungMapper;
	}

	@GetMapping("konto")
	public List<Konto> findAllKonten() {
		return kontoMapper.domainKontoListToApiKontoList(
				kontoService.findAll());
	}
	
	@GetMapping("konto/{kontoName}/umsatz")
	public List<Buchung> findKontoBuchungenByKontoName(
           @PathVariable("kontoName") String kontoName) {
	    return buchungMapper
             .domainBuchungListToApiBuchungListe(
                buchungService
                  .findBuchungByKontoName(kontoName));
	}

	@PostMapping("buche")
	public ResponseEntity<String> buche(
                @RequestBody Buchung buchung) {
	  try {
            buchungService.buche(
              buchungMapper.apiBuchungToDomainBuchung(buchung));
	  } catch (Exception e) {
	    return ResponseEntity
                .unprocessableEntity()
                .body("Fehler: " + e.getMessage());
	  }
	  return ResponseEntity.accepted().body("Verarbeitet");
	}
}

finally

Die Schnittstelle ist damit beschrieben und es steht die eigentliche Implementierung an. Mein erster Wurf wird auf einer relationalen Datenbank zur Speicherung basieren. Für die Anbindung auf der grünen Wiese, verwende ich die Java Persistente API (JPA). Doch dazu beim nächsten mal mehr.

Happy Coding!

Anwendung entwickeln für zu Hause – eine Finanzverwaltung

Mein Quicken ist in die Jahre gekommen. Mag ich das aber wirklich erneuern, schließlich arbeite ich als Software-Entwickler? Da reift die Überlegung eine Anwendung selbst zu entwickeln.

Die Anforderungen der Anwendung

Bevor ein erster Prototyp entsteht, gilt es erst mal einfache Anforderungen zusammen zu tragen.

Was sollte ein erster Prototyp können? Eine einfache Tabelle mit Datum, Beschreibung und Betrag reicht nicht. Das wäre praktisch ein Kassenbuch. Ich habe dann doch mehr als ein Konto und – Hand aufs Herz – ein Kassenbuch geht mit jeder Tabellenkalkulation.
Die doppelte Buchführung sollte es schon sein. Das ist es in Quicken auch, die Gegenkonten heißen dort halt nur Kategorie. Mehrteilige Buchungen sollten auch möglich sein. Vom Giro-Konto buchen beispielsweise die Stadtwerke ab, der Betrag setzt sich dabei aus den Strom- und den Gas-Kosten zusammen. Wollte ich einen Vergleich dieser Kosten für alternative Anbieter machen, müßte ich das schon trennen können.

Die Modellierung ist nicht neu. Was auf der Arbeit funktioniert, gilt auch für die Software zu Hause: das Rad nicht neu erfinden. Zumindest ist das Datenmodell schon da – zumindest im Prinzip. Martin Fowler hat sich schon vor einiger Zeit Gedanken dazu gemacht: https://martinfowler.com/eaaDev/AccountingNarrative.html

Das Datenmodell

Die erste Wurf für das Modell der Anwendung sieht wie folgt aus. Das ist eine eingedeutschte Variante des Fowler-Vorschlages.

Klassenmodell
Klassenmodell der Anwendung

Die gute doppelte Buchführung: eine Buchung ist immer Soll an Haben oder als Text-Format: “Per Soll-Konto, Betrag an Haben-Konto, Betrag”, wobei es jeweils mehrer Soll- und Haben-Konten geben kann. Die Summe der Soll und Haben-Beträge muss 0 sein. Soll-Beträge sind positiv und Haben-Beträge negativ. Das entspricht dem gewohnten Gefüge, dass Guthaben positiv sind und Schulden negativ.

Die Buchung fasst die (Konto-)Umsätze zusammen. Jeder Umsatz kennt seine Buchung – kann ohne diese auch gar nicht existieren – und er kennt das Konto, welches er anspricht.

Der Saldo des Kontos ergibt sich formal als die Summe der Beträge aller Umsätze des Kontos. Fraglich ist, ob der immer aufs Neue berechnet wird oder der letzte Stand gespeichert ist. Im Unternehmen wäre die Antwort das speichern des Saldos, da die Berechnung auf sehr viele Umsätze zu lange dauert und im Verlauf der Zeit immer länger. Ich lasses es dabei.

Erste Anwendungsfälle der Anwendung

Die folgenden Anwendungsfälle mag ist initial umsetzten:

  • Buchen erstellen
    Das ist praktisch das Aufbauen des Hauptbuches. Initial mag ich ein abgesprochenes Konto automatisch anlegen, wenn es noch nicht existiert. Das spart erst mal einen Anwendungsfall “Konto anlegen”.
  • Konten abfragen
    Ich bekomme eine Liste aller existierenden Konten mit deren aktuellen Salden. Das ist praktisch das Grundbuch.
  • Umsatz anzeigen
    Zuletzt mag ich noch die Umsätze eines Kontos ansehen können. Hier sind die Details sichtbar, um zum Beispiel Ausgaben zu vergleichen oder Positionen zu kontrollieren.

Die technische Basis – ein erster Ansatz

Finanzdaten mag ich nicht unnötig offen haben, also ist der private Rechner meine Zielplattform. Was liegt näher als das für eine kaufmännische Anwendung gewohnte Setup zu wählen.

Auf einem Rechner einen Browser für die Anwendung? Warum nicht, denn was ist mit HTML, CSS und Javascript nicht machbar. Diesen Ansatz verfolgt beispielsweise das Project Electronjs. Hier kommt vollständig Javascript zum Einsatz und es entsteht eine ausführbare Binärdatei, die für unterschiedliche Betriebssysteme erstellt werden kann.

Nun bin ich in – gerade was die Fachlogik angeht – mehr in der Java-Welt zu Hause. Dank der Graal VM ist auch hier mal eine kompilierte Version denkbar und die Entwicklung geht schneller von der Hand.

Aus der täglichen Gewohnheit mag ich mit den Mitteln arbeiten, die ich bereits gut kenne. Das bedeutet: relationale Datenbank, Spring Boot als Backend und Angular für die Oberfläche.

Natürlich bleibt das ganze zu überdenken. Für den Zugang ist ein Browser mit der entsprechenden URL zu starten. Das ist allerdings lösbar.

Für die Datenbank bietet sich eine eingebundene (embedded) Variante an, um zusätzliche Installationen zu vermeiden, z.B. H2.
Da das Datenmodell auf der “grünen Wiese” entsteht, möchte ich als Anbindung an die Datenbank die Java Persistente API (JPA) nutzen, da JPA das Schema direkt erstellen kann und somit Arbeit spart.

Die nächsten Schritte zur Anwendung

Im nächsten Beitrag beschreibe ich zunächst die Schnittstelle des Backends, welches die umzusetzenden Anwendungsfälle darstellt.

Dann schau ich mir die zugehörige Implementierung an. Da steckt etwas Forschung drin. Denn diese Anwendung ist nicht für ein großes Finanzunternehmen gedacht. Im Sinne des KISS-Prinzips gibt es vielleicht Alternativen.

Bis dahin: Happy coding!

Für warme Momente: der Teemann

Der Teemann

Es war in der Vorweihnachtszeit. Draußen ist es nass und kalt und zum Aufwärmen trinke ich öfter mal einen Tee. Tee bin ich nicht gewohnt, weshalb ich gut auf die Zeit des Ziehens achte. Zieht der Tee nämlich zu lange, bringt er mich zum einschlafen. Ich habe mich dabei an denn Teehasen “Teeodohr” erinnert. Das war mal ein Projekt aus der Zeitschrift Make, in dem ein 3D-gedruckter Hase mit seinem Ohr den Teebeutel nach bestimmter Zeit aus der Tasse zieht. Nach langer Pause war das eine schöne Idee für ein eigenes kleines Projekt. Nur gab es einige Tücken. Ich besitze keinen 3D-Drucker und bin auch nicht erfahren mit der Laubsäge. Überhaupt passt ein Hase nicht recht in die Vorweihnachtszeit. Wenn es auch im Rheinland eher selten schneit, so ist es mehr die Jahreszeit für einen Schneemann. Das ist es, ich baue einen Teemann!

Die Technik

Das Rad brauchte ich nicht neu erfinden. Die Make hat bereits vorgearbeitet: Ein Servomotor senkt und hebt den Teebeutel. Zwei Tasten ermöglichen die Bedienung, wobei der eine Taster die Zieh-Zeit auswählt und der zweite den Vorgang startet oder auch vorzeitig unterbricht. Zudem sind LEDs als Status vorgesehen. Sie signalisieren insbesondere die laufende Zeit.

Diese Konfiguration empfinde ich allerdings als zu eingeschränkt, denn es gibt nur zwei vorgegebene Zieh-Zeiten. Es macht aber einen Unterschied, ob ich einen schwarzen Tee drei Minuten oder einen grünen zwei Minuten ziehen lasse. Der Kräutertee darf auch gerne länger im Wasserbad verweilen. Ich beschloss deshalb, die Ursprüngliche Steuerung etwas zu modifizieren. Eine Taste ist weiterhin für die Zeit zuständig. Sie wird in Schritten von jeweils einer Minute herauf gezählt, bis die neun erreicht ist. Dann beginnt das Zählen wieder von vorn. Die Anzahl der zu ziehenden Minuten ist somit einstellig und einfach mit einer 7-Segment-Anzeige darstellbar. Der Punkt der Anzeige blinkt, wenn der Tee zieht.

Zwei Taster, ein Servo-Motor, eine 7-Segment-Anzeige und ein Signalgeber, der den fertigen Tee verkündet, dafür hat ein Arduino noch immer ausreichend Anschlüsse.

Die rote 7-Segment-Anzeige ist direkt am Arduino angeschlossen, wobei Diode noch einen 220 Ohm Widerstand spendiert bekommt.  Die Anschlüsse sind D2 für Segment “a”, D3 für “b” bis D9 für den Punkt. An D10 hängt der Tongeber und D11 ist für den Servo-Motor gedacht. Die beiden Taster sind mit den analogen Eingängen A1 für die Zeit und A2 für Start/Stop verbunden.

Die konkrete Wahl der Ausgänge ist einmal der Funktion geschuldet. Die Ansteuerung des Signalgeber und des Servo-Motor erfordert Pulsweitenmodulation (PWM). Für die Anzeige liegen die Ausgänge direkt nebeneinander, so das die Verbindung mit einer Anzeige-Platine und die Ansteuerung einfach ist (siehe unten).

Das Programm des Teemann

Das zugehörige Programm ist auf Githup zu finden. Es basiert auf der Make-Vorlage zu Teeodohr. Einige Schlenker des Servo-Motors, habe ich allerdings entfallen lassen. Sie könnten den schlichte Aufbau des Teemanns sonst in Mitleidenschaft ziehen.

Neu ist die Ansteuerung der 7-Segment-Anzeige. Der anzuzeigende Wert steht in einer globalen Variable time. Er wird durch den Minuten-Schalter erhöht, bis zum Wert neun und anschließend wieder auf eins gesetzt. Während des Ziehens, reduziert sich der Wert im Minutentakt.

Bei der eigentlichen Ansteuerung hilf ein konstantes Bytefeld z wie Ziffer. Die Bits eines Bytes steht für den Zustand eines Segments: an oder aus. Die Position des Bytes im Feld ist der Index für die darzustellende Ziffer. Angenommen es verbleiben noch zwei Minuten zu ziehen, dann gibt z[time] das dritte Byte im Feld zurück, den die Indizierung beginnt mit Null.

Dieses Byte bekommt die Prozedur segmente für die Ansteuerung. Darin ist eine Schleife, die für die anzusteuernden Ausgänge von 2 bis 9 läuft. In der Schleife wird für den aktuellen Ausgang dieser ein oder aus geschaltet, abhängig vom vordersten Bit des Parameters. Nachdem ein Bit für die Ansteuerung gesorgt hat, schiebt das Programm alle Bits um eine Stelle nach links. Das zuvor betrachtete Bit entfällt und das nächste kann zur Steuerung herangezogen werden.

Die Ansteuerung des Punktes erfolgt unabhängig davon in der Zählschleife des Ziehens. Das Timing erfolgt mit delay-Anweisungen, wodurch die Zeitnahme etwas ungenau ist. Die paar Millisekunden wird ein Tee aber durchaus ertragen.

Der Aufbau

Praktisch und Mechanisch ungeübt ist das für mich der härteste Teil. Die Elektronik ist freischwebend auf zwei Lockraster-Platinen verlötet. Eine Platine mit dem Arduino Nano und die zweite für die Anzeige, die auch die Widerstände enthält. Die Verbindungen sind alle über Stecker auch wieder zu lösen.

Der Teemann selbst bekommt aus eine Basisplatte aus einem Postkartenbrettchen. Durch ein mittiges Loch kommt eine M3 Gewindestange, die mit Scheibe und Mutter beidseitig fest verschraubt wird. Die Mutter auf der Unterseite landet dabei in dem Abschnitt, der für die Postkarte bestimmt ist, so dass sie versenkt ist und das Brett gerade aufliegt.

Auf die Stange kommen hohle Styropor Kugeln. Sie sind mit einem scharfen Messer gut zu bearbeiten. In die untere Kugel gibt es einen Abschnitt für das Panel, welches ich mit der Laubsäge erstellt habe. In die obere Kugel wird seitlich der Servo-Motor eingelassen. Die Kabel passen durch kleine Löscher in den Kugeln.

Last but nut least bekommt der Teemann einen Kopf aus einer kleineren, massiven Styropor-Kugel, die auf das Ende der Gewindestange gesteckt wird.

Der Servo bewegt die Teebeutel mit Hilfe eines stärkeren Drahtes, der per Heißkleber an den Servo und mit einer kleinen Klammer versehen ist.

Mach mal Tee man

Es ist Zeit für einen Testlauf. Das ganze funktioniert nicht schlecht, doch wie man sieht ist etwas Tuning notwendig:

Teemann_bekleckert

Die Winkel für den Servo-Motor habe ich aus dem Teeodohr-Projekt übernommen. Die gehen offensichtlich ziemlich auf den Latz. Hier liegt ein Vorteil der Stromversorgung über den Arduino-USB-Anschluss. Dies ist auch gleichzeitig der Programmierzugang und nach einer Anpassung funktioniert das dann ganze dann auch wie gewünscht.

Teemann_in_aktion

Die Uhrzeit im Griff mit Timer und Unterbrechung

Die Anzeige ist aufgebaut, jetzt fehlt noch die Software mit dem richtigen Timing und die Uhr läuft. Damit das funktioniert gilt es nun die im Mikrocontroller enthaltenen Timer nutzbar zu machen.

Warum nicht wie im Testprogramm?

Ja, das Testprogramm hat die Anzeige gesteuert. Allerdings zeitlich nicht sehr präzise. Ein genauerer Blick auf das Blink-Programm macht den Haken jedoch deutlich.

void loop() {
  digitalWrite(13, HIGH);
  delay(1000);
  digitalWrite(13, LOW);
  delay(1000);
}

Das Programm schaltet den Ausgang ein, wartet eine Sekunde, schaltet den Ausgang aus und wartet wieder. Anschließend beginnt der Zyklus von vorne. Die Verzögerungen mögen exakt eine Sekunde dauern, aber wie lange dauert das Schalten? Schon die Zahl der Kommandos kann durch bedingte Ausführungen, beispielsweise einen Übertrag auf die nächste Stelle unterschiedlich sein.  Folglich ist die Laufzeit unterschiedlich. Das Multiplex braucht nur ausreichend schnell sein, so dass die Anzeige nicht flimmert. Für eine genaue Uhr taugt das allerdings nicht.

Die Zeit im Griff durch Unterbrechung

Was meint das? Im Mikrocontroller des Arduino sind drei sogenannte Timer/Counter integriert. Im Grunde handelt es sich um Zähler. Sie zählen Ereignisse wie den Systemtakt und führen eine gegeben Funktion aus, wenn ein definierter Wert oder die Zählerobergrenze erreicht ist. Das Ausführen startet der Timer mit Hilfe einer Unterbrechung – einem Interrupt. Der Interrupt unterbricht das laufende Programm und ruft den Unterbrechungscode auf. Ein Beispiel dafür ist der Reset, der durch einen Taster auf dem Arduino-Board ausgelöst werden kann. Der Mikrocontroller beginnt dann mit der Ausführung seines Programmes von ganz vorne.

Die Timer zählen vollständig unabhängig vom laufenden Programm. Zählen sie nun den Systemtakt,  bilden sie eine gute Basis für eine Uhr. Der ist immerhin quarzgenau bei 16000000 Impulse in der Sekunde.

Was gibt es für Timer?

Das hängt vom zugrundeliegen Controller ab. Auf dem Arduino stehen die beiden 8-Bit-Timer/Counter 0 und 2 sowie der 16-Bit-Timer/Counter 1 zur Verfügung.

Die 8-Bit Zähler können bis 255 Zählen, Timer 1 schafft es immerhin bis 65535. Anschließend beginnen sie wieder bei 0. Wenn sie von vorn beginnen, erfährt der Zähler einen Überlauf. Den kann er durch einen Interrupt markieren. Genauso ist ein Interrupt konfigurierbar, sobald ein bestimmter Wert erreicht ist.

 

Timer 2 könnte auch externe Ereignisse zählen. An den dafür vorgesehenen Eingängen betreibt der Arduino allerdings seinen Quarz. Eine mögliche Taktquelle für die Timer ist wie gesagt der Systemtakt. Die 16 MHz sind recht schnell für den Zahlenumfang der Timer und die Zähler laufen in Bruchteilen einer Sekunde über. Für längere Distanzen kann der Takt vor dem Zähler vorgeeilt werden. Als Vorteile sind die 8/32/64/128/256 und 1024 möglich.

Die Programmierung

Die Timer besitzen diverse Kontrollregister. Die Register sind Adressen im Programmraum. Die Programmierung erfolgt durch setzen bestimmter Steuer-Bits dieser Register. Die Arduino IDE basiert letztlich auf dem C++ für die Atmel-Prozessoren. Die Header definieren Konstanten für die Adressen und die Steuerbits, mit denen die Programmierung einbänglicher ist.

Die benötigten Register sind im Einzelnen

  • OCRxy
    Output Compare Register. Hier können zwei Vergleichswerte hinterlegt werden, bei deren Erreichen ein TIMERx_COMPy_vect-Interrupt ausgelöst wird. x ist die Nummer des Timers (0,1, 2). y steht für die beiden möglichen Werte (A und B)
  • TCCRxy
    Timer/Counter Control Register: Einstellen des Vorteile
  • TIMSKx
    Timer/Counter Interrupt Mark Register: welche Interrupts soll ein Timer aus auslösen

Weiterhin sind noch die Funktionen cli() – Interrupts deaktivieren – und das Gegenstück sei() – Interrupt aktivieren wichtig. Während der Timerprogrammierung sollten die Interrupts deaktiviert sein, damit die Programmierung nicht unterbrochen wird und ein bisher erfolgte Programmierung ein ungewolltes Verhalten hinterlässt.

Der auszuführende Code für einen Interrupt ist in einer Callback-Funktion zu hinterlegen:

ISR(Interruptname) {
   // auszuführender Code
}

Damit die Interrupts sich nicht „selbst überholen“ sollte der Code natürlich möglichst kurz sein.

Eine Übungsprogramm

Um mich mit der Programmierung vertraut zu machen, schadet ein einfaches Beispielsprogramm nicht. Ich mag das Blink-Programm dafür mit Hilfe eines Timers umsetzen. Der dabei entstandene Code findet sich auch auf Github.

Natürlich ist bei diesem Programm ein abweichender Setup-Code erforderlich. Die eigentliche Aktivität erfolgt allerdings in der Interrupt-Behandlung (ISR(.)) infolge dessen ist die Loop-Prozedur leer. Der Arduino wartet. Ist die vorgegebene Zeit des Timers um, schaltet die Interrupt-Behandlung den Ausgang der Onboard-LED um.

Bevor ich kurz auf die Timereinstellungen eingehe, möchte ich noch kurz auf eine Besonderheit bei der Deklaration der Zustandsvariable für den Status des Ausgangs hinweisen:

volatile boolean blink = false;

Der Compiler versucht zu optimieren. Stellt er nun fest, dass eine Variable in einem Codeblock nicht geschrieben wird, könnte er sie infolgedessen durch eine Konstante ersetzen. Soll also eine Variable  in einem Block festgelegt und in einem anderen ausgelesen werden, müssen das dem Compiler mitteilen. Genau dafür ist die Variable blink als volatile deklariert.

Kommen wir nun zu den Einstellungen. Ich möchte die delay(.)-Anweisungen durch die Interrupt-behandlung ersetzen. Diese schaltet die Leuchtdiode ein und vice versa. Der Aufruf sollte entsprechend einmal in der Sekunde erfolgen.

Dafür wähle ich den 16-Bit-Timer. Er erhält den Systemtakt geteilt durch 1014. Damit ist die Eingangsfrequenz 16.000.000 / 1014 = 15.625 Hz. Zählt der Timer nun bis 15624 und startet mit dem nächsten Takt den Interrupt, ist folglich der Aufruf je Sekunde erreicht.

Die Details zu den Steuerregistern

Die Bits der Register sind ausführlich im Datenblatt von Atmel beschrieben. Damit der Timer bei erreichen des Zielwertes zurückgesetzt wird, ist in das TCCR1B Register die WGM-Bits entsprechend zu setzen WGM12 auf 1 die beiden anderen auf 0. Sie befinden sich im TCCR1A-Register.

Der Precaller wird über die CS-Bits gesteuert, die ebenfalls im TCCR1B-Register zu finden sind. Daraus ergeben sich die Zeilen

TCCR1A = 0;
TCCR1B = 0;
// Set CS12 and CS10 bits for 1:1024 prescaler
TCCR1B |= (1 << CS12) | (1 << CS10);
// turn on CTC mode
TCCR1B |= (1 << WGM12);

Das Vergleichsregister A ist mit den oben genannten 15624 zu füllen (es könnte genauso gut B Verwendung finden)

OCR1A = 15624;

Zuletzt muss der Vergleichseinheit A gesagt werden, den Interrupt bei erreichen zu aktivieren

TIMSK1 |= (1 << OCIE1A);

Vor der Programmierung des Timers sind Interrupts zu deaktivieren und anschließend wieder zu aktivieren.

Resumee

Das Blink-Programm auf Timer Basis zeigt einen sehr schönen Weg zur Steuerung der Digitaluhr auf. Die Zeitmessung damit ist quarzgenau. Die Programmierung kann durch diese “Event”-Behandlung auch etwas leichter werden.

Einen Zeitgeber für eine Sekunde ist nun schon gefunden. Der nächste Beitrag betreibt die Anzeige und Zählt dann die Sekunden.

Bisherige Beiträge zum Winterprojekt

 

Winterprojekt: Die Anzeige der Digitaluhr

Das neue Jahr hat begonnen und es kann mit der Anzeige weitergehen. Dieser Beitrag beschreibt insbesondere die Hardware, also den Aufbau und die Verdrahtung. Für den Test gibt es ein erstes kleines Programm.

Die eigentliche Anzeige

Eine digitale Uhr benötigt jeweils zwei Ziffern für die Stunden und die Minuten. Die Anzeige besteht demnach aus vier 7-Segment-Anzeigen. in einer ersten Hochrechnung sind folglich für die Ansteuerung 7 x 4 = 28 digitale Ausgänge erforderlich. So viele Ausgänge besitzt der Arduino allerdings nicht. Deshalb erfolgt die Ansteuerung multiplex.

Das Multiplex-Prinzip

Bei Bildschirmen oder Fernsehern gibt es immer eine Frequenzangabe, Die Angabe von beispielsweise 50 Hertz (Hz) bedeutet, dass fünfzig mal in der Sekunde das Bild erneuert wird. Unser Auge ist träge, deshalb sind schnelle, einzelne Bilder nicht mehr zu unterscheiden. Entsprechend verschmelzen sie zu einer laufenden Bewegung.

Mit den Ziffern der Uhr funktioniert es genauso. Eigentlich zeigen die 7-Segment-Anzeigen nacheinander ihre Ziffern an. Erfolgt der Wechsel allerdings schnell genug, so sehen unsere Augen vier leuchtende Ziffern.

Für die Ansteuerung benötigt dieses Verfahren sieben Ausgänge gleichzeitig für alle vier Segmente. Vier weitere Ausgänge steuern jeweils den gemeinsamen Gegenpol der Segmente und wählen die jeweils anzuzeigende Ziffer aus. Die Anzahl der benötigten Ausgänge reduziert sich immerhin von 28 auf 11 und die kann der Arduino ansteuern.

Unter Strom

Mit den Strom einer einzelne Leuchtdiode kann der Arduino umgehen. An den gemeinsamen Gegenpol geben beispielsweise für die Ziffer Acht immerhin sieben Dioden Strom ab. In Foren liest man häufiger der Arduino kann bis zu 50 mA liefern. Laut Datenblatt sind es allerdings nur 40 mA. Da die blauen Dioden sehr hell sind, könnten größere Vorwiderstände gewählt werden, beispielsweise 1 Kiloohm. Von 5 Volt fallen 2,8 Volt Spannung an der Diode ab und folglich verbleiben 2,2 Volt am Widerstand. Geteilt durch 1000 Ohm mal 7 Segmente = 15,4 mA, das funktioniert. Bei einem Experiment ergab sich allerdings das die Ausgänge meines Arduino unterschiedlich viel Strom liefern, so dass ein Segment deutlich dunkler war als die anderen. Nach einem Tausch war es wieder das Segment dieses Ausgangs, das dunkler war. Das ist unschön, deshalb habe ich den Strom mit Hilfe von Transistoren geschaltet.

Der Transistor als Schalter

In der Restekiste habe ich noch BC 547C Transistoren. Die Verstärkung (das “C”) ist für diese Anwendung eigentlich übertrieben, trotzdem erfüllen sie ihren Zweck. Sie können mit einem kleinen Strom einen großen schalten. Bei den von mir verwendeten 220 Ohm Widerständen ist das 7 x (2,2/220) = ca. 70 mA. Meine kleine Testschaltung sieht wie folgt aus:

Schalten mit dem Transistor

Auf diesem Video habe ich das Schalten durch den Arduino durch einen Taster simuliert. Diese Schaltung kann ich dementsprechend auf den gemeinsamen Pol aller 7-Segement-Anzeigen anwenden.

Der Schaltplan für die Anzeige insgesamt ist

Schaltung Anzeige

Testprogamme

Die Funktion ziffer(int) aus dem letzten Beitrag sowie das Feld mit den Ansteuerungscodes benutze ich genauso wie im letzten Beitrag. Die Initialisierungsschleife des Setup nimmt zudem vier weiteren Ausgänge 3 bis 6 auf, die ebenfalls als Ausgang eingestellt werden. Die Werte für die darzustellenden Ziffern enthält das Feld

int ausgabe[] = { 4, 3, 2, 1};

Im Loop rufe ich zunächst nur eine Prozedur  doAusgabe() auf, in der eine Schleife die nächste Ziffer schaltet.

for(int z=0; z<4; z++) {
  selectZiffer(z);
  ziffer(ausgabe[z]);
  delay(5);
}

Sie selektiert zunächst das zu schaltende Segment, steuert anschließend die Segmente entsprechend der zugehörigen Ziffer an und wartet. Die Prozedur selectZiffer(int) schaltet alle außer dem angeben Segment ein.

void selectZiffer(int z) {
  for(int i=0; i<4; i++) {
    if(i==z) digitalWrite(6-i, HIGH);
    else digitalWrite(6-i, LOW);
  }
}

Die Verzögerung in doAusgabe() definiert im Moment für die maximale Wiederholrate. Ist die Wartezeit zu lang, so flimmert die Anzeige oder extremer, der Wechsel ist erkennbar. Für den Funktionstest ist das allerdings so gewollt. So lässt sich der Ablauf schließlich auf gut verfolgen.

Resumee

Technisch funktioniert die Anzeige nun. Ein kleines Programm existiert bereits, doch das ist ausbaufähig. Die “softe” Seite der Anzeige ist Thema des nächsten Beitrags.

Bisherige Beiträge zum Winterprojekt

Winterprojekt: Ansteuern der 7-Segment-Anzeige

Mit der nun gelieferten 7-Segment-Anzeige kann das die Programmierung und die Ansteuerung durch den Arduino erfolgen. Zunächst bin ich auf Draht …

Der Anschluss

Was steckt in der 7-Segment-Anzeige?

Die 7-Segment-Anzeig hat gleich eine Überraschung bereit: sie hat eine gemeinsame Anode.

schaltplan-7-segmentDie einzelne LED des vorausgehenden Blogs wurde durch einen aktiven Ausgang (high, 5 Volt) aktiviert. Für die Segment-Anzeige dagegen heisst es umdenken. Nur die Kathoden der einzelnen Dioden sind zugänglich, folglich kann der Arduino sie nur durch ein Low-Pegel, 0 Volt, zum leuchten bringen. Das gilt es anschließend bei der Programmierung zu berücksichtigen.
Anschlussbelegung 7-Segment-AnzeigeDer Schaltplan der Anzeige enthält die Nummern der Pins für das jeweilige Segment. Zudem bezeichnen die Buchstaben “a” bis “f” die jeweiligen Dioden und den Dezimalpunkt. Anhand der Beschreibung aus dem Datenblatt können nun die Verbindungen zum Arduino hergestellt werden. Zwischen jedem Digitalausgang und dem Anschluß habe ich zudem ein Widerstand geschaltet.

Die Verdrahtung

Prinzipiell könnte auch ein einzelner Widerstand an der gemeinsamen Anode genutzt werden. Hierdurch wird allerdings der Strom für alle Dioden begrenzt. Wenn für eine Ziffer 8 alle Segmente leuchten, teilt er sich auf und ist folglich je Diode niedriger als bei weniger leuchten Segmenten. Die Helligkeit könnte dadurch abhängig von der dargestellten Ziffer schwanken. Ich sag mal könnte, weil die Helligkeit durch den Strom nur schlecht steuerbar ist. Beim Ausprobieren habe ich keinen Unterschied sehen können. Durch Materialstreuung könnte es aber dennoch passieren und sähe dann unschön aus.

Die Ansteuerung der sieben Segmente erfordern ebenfalls sieben Digitalausgänge. Ich habe mich dazu entschieden die Ausgänge 13 – Segment “a” bis 7 – Segment “g” zu nutzen. Schließlich besitzen die Digital-Pins 1 und 2 Sonderfunktionen, die ich nicht unnötig blockieren möchte.

Die Software

Das Rahmenprogramm

In der Initialisierung der Ansteuerung definiere ich in einer Schleife die Digitalen Ausgänge und setze sie in einen definierten Zustand.

void setup() {
  for(int i=13; i>6; i--) {
    pinMode(i, OUTPUT);
    digitalWrite(i, HIGH); // Erst mal ausschalten
  }
}

Für den Test soll die 7-Segment-Anzeige die Ziffern 0 bis 9 anzeigen. Jedoch erfolgt nach jeder Ausgabe eine Verzögerung, sonst könnten den Ziffernwechsel wegen der hohen Geschwindigkeit nicht wahrnehmen.

void loop() {
  for(int i=0; i<10; i++) {
    ziffer(i);
    delay(1000);
  }
}

Nachdem die Schleife die Ziffern durchlaufen hat, lässt die übergeordnete Endlosschleife die Ziffern erneut anzeigen. Der spannendere Teil der Ansteuerung liegt darin, wie die Ziffern der 7-Segement-Anzeige auszugeben sind. Dafür ist Prozedur ziffer(int) zuständig.

Ansteuerung der 7-Segment-Anzeige

Ein erster Ansatz

Der erste Gedanke zur Umsetzung ist eine direkte Aufteilung nach anzuzeigender Ziffer

void ziffer(int z) {
  switch(z) {
  case 0:
   ziffer0();
   break;
  case 1:...

und eine zugehörige Aktivieren der Ausgänge. Für die Ziffer 0 auf der 7-Segement-Anzeige sieht das beispielsweise wie folgt aus.

void ziffer0() {
  digitalWrite(A, LOW); 
  digitalWrite(B, LOW);
  digitalWrite(C, LOW);
  digitalWrite(D, LOW);
  digitalWrite(E, LOW);
  digitalWrite(F, LOW);
  digitalWrite(G, HIGH);
}

Das Positive vorweg: es funktioniert. Das geht aber auch anders und besser. Zum einen ist es einfach toll, wenn sich eine Funktion “knackig” ausdrücken lässt. Ein kompakterer Code ist im Folgenden auch besser zu pflegen. Als Nebeneffekt kann aber Speicherersparnis dabei herauskommen. Zur Erinnerung: der Arduino hat 2K freien Speicher und 32K Programmspeicher. Das ist in dieser Baureihe sogar recht viel, denn die kleinen Brüder des verwendeten Chips besitzen nur 16K oder gar nur 8K Programmspeicher. Wenn ein Programm dort hinein passt, wird eine Serie halt auch preiswerter.

Vermeiden von doppelten Code

Was geht also besser? Für jede Ziffer auf der 7-Segment-Anzeige gibt es eine eigene Prozedur, wie die oben dargestellte. Mit Ausnahme der Pegel sieht jedoch jede dieser Prozedur gleich aus. Das ist eine unnötige Wiederholung, die zudem der Faulheit einer Entwicklers widerspricht. Soll nun an der Grundstruktur etwas geändert werden, ist danach jede der anderen Prozeduren anzupassen. Immerhin zehn Prozeduren nach dem gleichen Schema ändern.

Die Alternative heisst ein Feld definieren. Darin stehen nur die Pegel und eine Schleife weist sie jeweiligen Ausgang zu:

const int ZIFFERN[][] = {
          { LOW, LOW, LOW, LOW, LOW, LOW, HIGH },
          { ... 

        }

...

void ziffer(int z) {;
  for(int i=0; i<7; i++) { 
    digitalWrite(13-i, ZIFFERN[z][i]);
  }
}

Meine ursprüngliche Lösung belegt bei mir 1296 Bytes Programm- und 9 Bytes dynamischen Speicher. Die neue dagegen 1128 Bytes Programm- und 149 Bytes dynamischen Speicher. Die “schönere” Lösung ist kleiner benötigt trotzdem viel Platz für das Feld mit den Pegeln, und zwar vom ohnehin kleinen dynamischen Speicher.

Speicheroptimierung

Die Ansteuerung der 7-Segment-Anzeige kann nun noch über eine kompaktere Speicherung der Pegel optimiert werden. Für die Ausgabe ist eigentlich nur interessant, ob er an oder aus geschaltet wird. Als Datentyp habe ich zuvor int gewählt. Dieser Datentyp benötigt deutlich mehr Platz als nur ein Bit. Die sieben Pegel für die 7-Segement-Anzeige finden aber auch als Bit in einem Byte platz. Die Pegel lassen sich dann beispielsweise wir folgt ablegen:

const byte ZIFFERN[] = {
    B0000001, // 0
    B1001111, // 1
    B0010010, // 2
    B0000110, // 3
    B1001100, // 4
    B0100100, // 5
    B0100000, // 6
    B0001111, // 7
    B0000000, // 8
    B0000100, // 9
  };

Für den westlichen Menschen ist eine Kodierung der Segmente von links nach rechts nachvollziehbarer. Entsprechend steht das erste Bit nach dem B für Segment “a”, das zweite für “b”, etc.

Bitte ein Bit

Danach muss die  Prozedur das richtige Bit für den zugehörigen Ausgang setzen. Meine Lösung:

void ziffer(int z) {;
  for(int i=0; i<7; i++) {
    digitalWrite(13-i, (ZIFFERN[z] & 1 << (6-i))==0?LOW:HIGH);
  }
}

Hier spiele ich mit Bit-Operationen. Das ist allerdings nicht auf einen Blick zu lesen. Mit ZIFFERN[z] bekomme ich das Bytes für die gewünschte Ziffer. Die Laufvariable i zählt durch die sieben möglichen Segmente. Für das Segment “c” hat die Variable i den Wert 2. Das ist das dritte Segment, aber die Schleife beginnt mit 0 und nicht mit 1. Mit 1 << (6-i) erhalte ich ein Byte, das nur an der 6-2=4-ten Stelle eine 1 hat. Bei sieben Bits ist das die dritte Stelle von Links, sie soll in den Konstanten die “c”-Stelle darstellen. Die Vorgabe und das Zielsegment verknüpfe ich mit einem logischen “Und”. Das Ergebnis für die jeweiligen Eingangsdaten ist dann

Ziffern-Bit Masken-Bit Ergebnis
0 0 0
0 1 0
1 0 0
1 1 1

Ist also für Segment “c” eine 1 codiert, so sorgt die entstandene Maske B10000 für ein Byte, das ebenfalls eine 1 enthält. Dessen Zahlenwert ist größer als 0. Sonst ist der Wert des Bytes 0, alle Bits auf 0. Diesen Wert prüft nun der ?-Operator und setzt den jeweiligen entsprechend Ausgang auf LOW oder HIGH.

Tipp

Die Programmierung klappt nicht immer auf anhieb. Beispielsweise ist das letzte Bit der Maske auf der Stelle 0, folglich ist der intuitive Ansatz (7-i) für die Maske falsch. Es gibt einen einfachen Weg, um das zu prüfen: einfach Zwischenwerte auf die Serielle Schnittstelle zum Rechner ausgeben lassen. Dazu ist im Setup die Schnittstelle mit Serial.beginn(9600); zu initialisieren. Ausgaben erfolgen dann mit Serial.println(<wert>); Den seriellen Monitor (die Lupe oben links im Fenster) in der IDE starten, die bei der Initialisierung angegebene Geschwindigkeit (9600 Baud) auswählen und schon können wir dem Programm bei der Arbeit zusehen.

Zum Abschluss

Die für die einzelne 7-Segment-Anzeige gefundene Lösung belegt 1000 Bytes Programm- und 19 Bytes dynamischen Speicher. Offensichtlich lohnt es sich Gedanken über seine Programme zu machen. Wenn die Idee dann auch noch funktioniert … Film der zählenden Anzeige

Bisherige Beiträge

Winterprojekt: LED there be light – Aufbau der Anzeige

Die Grundlegende Hardware liegt vor und die Software ist eingerichtet. Also wird das Winterprojekt Digitaluhr nun konkreter. Im Folgenden soll die Anzeige entstehen, die ich schrittweise entwickeln möchte. Zunächst mit einer externen, blinkenden LED, wie in diesem Film.

Die technische Basis

Die Ziffern der Uhr werden mit 7-Segmentanzeigen aufgebaut. Zuerst beginne ich mit der Ansteuerung einer einzelnen externen LED. Die späteren Ziffern sind genauso zu betreiben. Die Segmentanzeigen bestehen allerdings aus mehrere Dioden.

Das aktive Element: die LED

LED ist die Abkürzung für light-emitting Diode, zu deutsch Licht-emittierende Diode. Inzwischen kennen wir sie als Strom sparende Alternative zu Glühlampen. Die Stromsparer bestehen dabei aus den eigentlichen Leuchtdioden und ein wenig Elektronik.

Aktives Bauelement LED
Eine blaue LED

Eine einzelne LED aus dem Elektronik-Laden sieht z.B. aus wie im folgenden Bild. Es gibt sie in unterschiedlichen Größen.

Als Diode leitet sie nur in eine Richtung. Die Anode ist mit dem Pluspol zu verbinden, die Kathode mit dem Minuspol. Nur bei dieser Polung leuchtet die LED. Sollte eine Leuchtdiode bei einem Aufbau also nicht wie erwartet funktionieren, sollte deshalb der Steckrichtung überprüft werden. Die Anode ist im Build am längeren Anschluss zu erkennen.

Die LED leuchtet ab einer bestimmten Spannung, die von der Farbe abhängig ist (siehe auch Elektronik Kompendium). Für rote Dioden ist das 1,6 Volt. Bei blauen wie im Beispiel immerhin schon 2,9 Volt. Erst nachdem diese Spannung überschritten ist, leuchtet die Diode. Die Betriebsspannung des Arduino beträgt nun 5V. Das ist über der Grenzspannung, aber auch oberhalb der zulässigen Marke von ca. 4V. Deshalb ist für Leuchtdioden regelmäßig ein Vorwiderstand erforderlich.

Der Widerstand

Strom begrenzen, Spannungen teilen, Widerstände sind passive Bauteile und werden häufiger gebraucht. Hier eben als Vorwiderstand zur LED. Details über Widerstände gibt es z.B. auf Wikipedia. Hier gibt es unter Anderem auch den Farbcode, mit dem der Wert eines Widerstandes ablesbar ist.

Größe des Torwiderstand ermitteln

Die Grundlagen der Widerstandsberechnung können im Internet gefunden werde, beispielsweise im Elektronik Kompendium. Des Weiteren findet sich dort ein Widerstandsrechner. Am Widerstand sollen 5 – 2,9 = 2,1 Volt abfallen also quasi “vernichtet werden”. Die LED benötigt einen Strom zwischen 10 und 20 mA. Der Widerstandsrechner schlägt hier 18 mA vor. Das ist nicht der maximal mögliche Wert, ein Sicherheitsabstand schadet aber nicht.

Mit Hilfe des Ohmschen Gesetzes ergibt sich aus 2,1 Volt / 0,018 A = 116,66 Ohm. Sicherheitshalber sollte dieser Wert eher auf- als abgerundet werden. Der nächste Wert aus den typischen Reihen ist dann 120 Ohm (Farbcode braun, rot, braun).

Der Aufbau

Auf Bilder und Videos, wie das in der Einleitung, ist der Aufbau eher schwer zu erkennen. Deshalb möchte ich den Aufbau in Schalt- und Steckplänen dokumentieren. Mit Hilfe von Fritzing – einem freien Programm – ist das Ergebnis durchaus ansehnlich.

Schaltplan

Schaltplan externe LEDDer Schaltplan enthält nur die wesentlichen Informationen. In diesem Fall das Arduino-Board, den Widerstand, die Leuchtdiode und natürlich die Verbindungen der Bauteile.

Für die externe Ansteuerung, habe ich den digitalen Ausgang zwölf gewählt. Als Programm dient wieder Blink wie im letzten Beitrag, nur ist der Ausgang geändert, damit ist nachvollziehbar, dass die Programmänderung wirksam ist.

Am Ausgang direkt ist der Widerstand mit einem der beiden Drähte angeschlossen, die andere Seite führt zur Anode der Leuchtdiode, der Anode. Im Bild oben war das der längere der beiden Anschlüsse. Die andere Seite wird an Masse (Englisch “ground” – GND) am Board angeschlossen. Das Symbol der Diode deutet als Pfeil die Flussrichtung an. Schaltet das Programm den Ausgang nun auf an, high, 5 Volt, so heutet die LED.

Der Aufbau

Der Aufbau – wie im Video der Einleitung zu erkennen – ist erst mal auf einem Steckbord realisiert.

Aufbau auf der Steckplatine

Wichtig ist hier auf die richtige Polung der LED zu achten. Falls sie nicht wie erwartet blinkt, könnte ein drehen helfen.

In der Entwicklungsumgebung kann nun, wie im letzten Blog, das Blink-Programm aufgerufen werden. Hierin ist nun die Portnummer sowohl in setup() als auch in loop() von 13 auf 12 zu ändern. Hochladen in den Arduino und schon geht es los.

Als nächstes geht es weiterhin um die Anzeige der Digitaluhr. Anstelle der einzelnen LED soll dann eine 7-Segementanzeige Ziffern anzeigen.

Bisherige Beiträge