DAY BY DAY zu Entwicklung fehlertoleranter Software für eingebettete Echtzeitsysteme WS 2023/24
(EN google-translate)
(PL google-translate)
Übersicht
|
Allgemeines
Die Lehrveranstaltung "Entwicklung fehlertoleranter Software für eingebettete Echtzeitsysteme" enthält sowohl theoretische, praktische, seminaristische und Projekt-bezogene Anteile. Sie folgt zudem einem bestimmten Arbeitszyklus, in dem diese Anteile miteinander verzahnt sind:
|
Chronologisches Verzeichnis der im Verlauf des Semesters behandelten Themen
#1 Mi 27.09.2023
1 Überblick zum Kurs
Inhalte:
|
2 Einführung der Pattern für fehlertoleranten Software-Entwurf

3 Überblick zu Java / Processing



int x=0; int y=0; int z=0; public void setup() { size(600,300); frameRate(30); } public void draw() { background(x,y,z); fill(255,0,0); rect(0,0,100,100); fill(0,255,0); rect(100,0,100,100); } public void mousePressed() { if(mouseX<100 && mouseY<100) { x=255; y=0; z=0; } if(mouseX>100 && mouseX<200 && mouseY<100) { x=0; y=255; z=0; } }
Code 0-1: Sketch, der auf Klicks die Hintergrundfarbe ändert. Im Unterricht entstanden.
4 Vorbesprechung der Projektthemen
#2 Mi 04.10.2023
Organisatorisches: Android-Tablets
|
Themen
|
zu 1. Wiederholung fault-error-failure

zu 2. Übersicht über alle Fehlertoleranz-Pattern
Anforderung an Ihre Projektarbeit: Bezugnahme auf mehrere Fehlertoleranz-Pattern und Vorführung simulierter Fehlersituationen, bei denen die Pattern zum Einsatz kommen

zu 4. Programmieraufgabe zur Konsolidierung des bisher Erlernten
|
Recherchieren Sie:
|
public void setup() { size(600,300); frameRate(30); background(42,60,80); } String x="AN"; String y="AUS"; public void draw() { } public void mousePressed() { if(mouseX<300) { background(42,60,80); textSize(50); text(x,270,150); } if(mouseX>300) { background(42,60,80); textSize(50); text(y,270,150); } }
Code 0-2: Studentische Lösung
boolean an = false; public void setup() { size(600,300); frameRate(30); } public void draw() { background(255); fill(0); textSize(30); textAlign(CENTER,CENTER); //text(""+an,width/2,height/2); if(an==true) text("AN",width/2,height/2); else text("AUS",width/2,height/2); } public void mousePressed() { if(mouseX<width/2) { an=true; } else { an=false; } }
Code 0-3: Zweite Lösung
zu 5. Unified Modelling Language (UML)
Anforderung an Ihre Projektarbeit: Dartstellung und Präsentation der gesamten Softwarestruktur anhand von UML-Diagrammen









zu 6. Objekt Orientierte Programmierung (OOP) am Beispiel eines selbst programmierten beschrifteten Funktionsknopfes
Knopf-Programm mit selbst geschriebener Klasse Knopf:
Knopf knopf1; Knopf knopf2; int x; public void setup() { size(600,300); frameRate(30); knopf1 = new Knopf(10,10,200,50); knopf2 = new Knopf(10,70,200,50); } public void draw() { background(255); knopf1.draw(); knopf2.draw(); } public void mousePressed() { knopf1.clicked(mouseX,mouseY); knopf2.clicked(mouseX,mouseY); }
Code 0-4: Hauptprogramm.
public class Knopf { private int x,y,breite,hoehe; private boolean an; public Knopf(int x,int y,int breite,int hoehe) { this.an = false; this.x = x; this.y = y; this.breite = breite; this.hoehe = hoehe; } public void draw() { if(this.an) fill(0,255,0); else fill(255,0,0); rect(x,y,breite,hoehe); } public void clicked(int x, int y) { if(x>this.x && x<this.x+this.breite && y>this.y && y<this.y+this.hoehe) { this.an=!this.an; } } }
Code 0-5: Klasse Knopf.
Verwendung von Interfaces (Schnittstellen)
Knopf knopf1; Knopf knopf2; Toaster toaster; Foehn foehn; int x; public void setup() { size(600,300); frameRate(30); toaster = new Toaster(); foehn = new Foehn(); knopf1 = new Knopf(10,10,200,50,foehn); knopf2 = new Knopf(10,70,200,50,toaster); } public void draw() { background(255); knopf1.draw(); knopf2.draw(); } public void mousePressed() { knopf1.clicked(mouseX,mouseY); knopf2.clicked(mouseX,mouseY); }
Code 0-6: Hauptprogramm.
public class Knopf { private int x,y,breite,hoehe; private boolean an; private iGeraet geraet; public Knopf(int x,int y,int breite,int hoehe,iGeraet geraet) { this.an = false; this.x = x; this.y = y; this.breite = breite; this.hoehe = hoehe; this.geraet = geraet; this.geraet.stop(); } public void draw() { if(this.an) fill(0,255,0); else fill(255,0,0); rect(x,y,breite,hoehe); } public void clicked(int x, int y) { if(x>this.x && x<this.x+this.breite && y>this.y && y<this.y+this.hoehe) { this.an=!this.an; if(this.an) geraet.start(); else geraet.stop(); } } }
Code 0-7: Klasse Knopf.
public interface iGeraet { public abstract void start(); public abstract void stop(); } public class Foehn implements iGeraet { public void start() { println("Föhn gestartet"); } public void stop() { println("Föhn gestoppt"); } } public class Toaster implements iGeraet { public void start() { println("Toaster gestartet"); } public void stop() { println("Toaster gestoppt"); } }
Code 0-8: Interface iGeraet und spezielle Geräte.



Mausmethoden bei Processing:


ACHTUNG: mouseClicked() funktioniert nicht beim Touchscreen mit dem Tablet, es muss statt dessen mousePressed() benutzt werden!




Siehe die bei den Beispielen in Processing:
|
#3 Mi 11.10.2023
Themen
|
1. Wiederholungen: Mitigation, OOP (Klasse, Objekt, Schnittstelle)






2. Das Fehlertoleranz-Pattern Recovery-Block
3. Programmierübung
|
|
Hilfestellung:
|

int MODE = 0; long time = 0; long time_start = 0; public void setup() { size(600,300); frameRate(30); time_start = System.currentTimeMillis(); } public void draw() { background(255); if (MODE == 0) // Wahlbildschirm { fill(255); rect(0,0,300,300); rect(300,0,300,300); fill(0); textSize(30); text("Zur THB Rallye", width/4, height/2); text("Zur THB Führung", 3*width/4, height/2); textAlign(CENTER); time = System.currentTimeMillis(); if (time - time_start >= 10000) { MODE = 2; } } else if (MODE == 1) // THB Rallye { fill(0); textSize(30); text("THB Rallye", 300, 120); textAlign(CENTER); fill(255); rect(0,0,100,50); fill(0); textSize(20); text("zurück", 50, 25); } else // THB Führung (MODE == 2) { fill(0); textSize(30); text("THB Führung", 300, 120); textAlign(CENTER); fill(255); rect(0,0,100,50); fill(0); textSize(20); text("zurück", 50, 25); } } public void mousePressed() { if (MODE == 0) // Wahlbildschirm { if(mouseX<width/2) // width = Fensterbreite { MODE = 1; } else //if(mouseX>width/2) { MODE = 2; } } else if (MODE == 1) // THB Rallye { if(mouseX<100 && mouseY<50) // width = Fensterbreite { time_start = System.currentTimeMillis(); MODE = 0; } } else // THB Führung (MODE == 2) { if(mouseX<100 && mouseY<50) // width = Fensterbreite { time_start = System.currentTimeMillis(); MODE = 0; } } }
Code 0-9: Besucher_App.zip -- studentische Beispiellösung.
Modularisierung:
|
weiter in Richtung OOP:




4. Beispielhafte Umsetzung des Recovery-Block-Patterns
Die vorangehende Programmierübung soll nun die Grundlage bilden, das Recovery-Block-Pattern als gut erkennbares Modul zu implementieren.
#4 Mi 18.10.2023 #block Wegen Überschneidung mit einer Sitzung findet ab spätestens 14:20Uhr eine umfangreiche Übung statt, deren Ergebnisse dann erst kommende Woche besprochen werden.
Themen
|
1. Neue Pattern: Maximize Human Particitaption und Minimize Human Intervention
2. Nachbesprechung zur beispielhaften Umsetzung des Recovery-Block-Patterns

3. Exportieren von Projekten mit Processing / Import nach BlueJ

Bild 0-1: Optionen beim Exportieren von Projektren (Sketchen) in Processing.

4. UML-Darstellung der Software-Struktur zur Umsetzung des Recovery-Block-Patterns

Bild 0-2: UML-Diagramm zu Besucher_App7 mit BlueJ.

Bild 0-3: UML-Klassendiagramm zur Klasse Rallyse mit BlueJ.

Bild 0-4: Java-Quelltext zur Klasse Rallye, so, wie sie in BlueJ importiert wurde.

Bild 0-5: Teilansicht des Quelltexts zum nach BlueJ exportierten Hauptprogramm Besucher_App7.
5. Darstellung von Bildern mit Processing
Siehe Beispiele innerhalb der Processing IDE unter: Datei/Beispiele/Basics/Image
6. Umfangreiche Übung
Aufgabe 1
Erinnern Sie sich an Ihnen bekannte, konkrete technische Beispiele, bei denen "Maximize Human Participation" und "Minimize Human Intervention" realisiert wurde. Notieren und diskutieren Sie diese Beispiele.
Aufgabe 2
Ergänzen Sie in nachfolgendem Sketch in sinnvoller Weise das fehlende Interface und testen Sie das dann lauffähige Programm:
import java.util.Random; Random zufall = new Random(System.currentTimeMillis()); iFigur[] figur; public void setup() { size(600,400); frameRate(30); figur = new iFigur[100]; int ii=0; for(int i=0;i<50;i++) figur[ii++] = new BlauesQuadrat(zufall.nextInt(width),zufall.nextInt(height)); for(int i=0;i<50;i++) figur[ii++] = new RoterKreis(zufall.nextInt(width),zufall.nextInt(height)); } public void draw() { background(255); for(int i=0;i<figur.length;i++) figur[i].draw(); } //### Fügen Sie hier das fehlende Interface ein. ### public class BlauesQuadrat implements iFigur { int x,y; public BlauesQuadrat(int x, int y) { this.x = x; this.y = y; } public void draw() { noStroke(); fill(0,0,255,100); rect(x,y,30,30); } } public class RoterKreis implements iFigur { int x,y; public RoterKreis(int x, int y) { this.x = x; this.y = y; } public void draw() { noStroke(); fill(255,0,0,100); ellipse(x,y,20,20); } }
Code 0-10: Sketch zu Aufgabe 2

Bild 0-6: Beispiel-Ergebnis nach Start des Sketches.
Beantworten Sie sich folgende Fragen zum Sketch:
|
Ergänzen Sie eine Funktionalität, durch die sich alle Figuren zufällig bewegen. Dies soll dadurch geschehen, indem bei jedem Durchlauf von draw() x oder y jeder Figur zufällig inkrementiert oder dekrementiert wird.
Aufgabe 3
Nehmen Sie den Quelltext aus Aufgabe 2 als Grundlage.
Um in BlueJ die untergeordneten Klassen zu befähigen, die Mal-Befehle zu verwenden, musste das Objekt PApplet bei den Klassen registriert werden und beispielsweise fill(255); durch pap.fill(255); ersetzt werden.
Dieses Vorgehen sorgt grundsätzlich für mehr Transparenz der Software, da so klar ist, woher die Befehle fill(), rect() ellipse() und so weiter stammen.
|
Aufgabe 4
Analysieren Sie das Beispielprogramm Datei/Beispiele/Basics/Image/LoadDisplayImage.
Suchen Sie im Internet nach einem Bild der Mensa der THB, einem des Technikgebäudes und einem des Haptgebäudes.
Machen Sie mit dem Werkzeug Zubehör/Bildschirmfoto von jedem dieser drei Bilder einen Screenshot und speichern es jeweils unter mensa.png, technik.png, haupt.png ab.
Erzeugen Sie einen Neuen Sketch "Diashow001" und legen in den Sketchordner die drei Bilder.
Programmieren Sie Ihren Sketch zunächst so, dass das Bild mensa.png angezeigt wird.
Überlegen Sie sich nun ein Konzept, mit dem es möglich ist, die drei Bilder durch wiederholtes Klicken mit der Maus zyklisch anzuschauen. Setzen Sie dieses minimale Konzept einer Diashow in Diashow002 um.
Aufgabe 5
Grundlage bildet Diashow002 aus Aufgabe 4.
Versuchen Sie Besucher_App7 so umzuschreiben, dass die Klasse Besucher die Funktionalität der Diashow aus Diashow002 erhält. Speichern Sie dazu Besucher_App7 zunächst als Besucher_App8 ab.
Aufgabe 6
Grundlage bildet Besucher_App8 aus Aufgabe 5.
Versuchen Sie das vor zwei Wochen entwickelte Konzept zur Umsetzung von Buttons (Funktionsknöpfen, Testsketch011AnAus.zip, siehe oben) zur Anwendung zu bringen, um den Knopf "zurück" damit umzusetzen. Speichern Sie dazu zunächst Besucher_App8 als Besucher_App9 ab und arbeiten Sie darin weiter.

Die Knöpfe in Testsketch011AnAus haben keine Beschriftung. Beheben Sie diesen Mangel durch Ergänzung und Verwendung eines geeigneten Konstruktors.
#5 Mi 25.10.2023
Themen
|
1. Neues Pattern: Escalation
2. Beispiellösungen zu der Übung von letzter Woche
Lösung zu Aufgabe 1
|
Lösung zu Aufgabe 2
public interface iFigur { public abstract void draw(); }
Code 0-11: Fehlender Code.
Erzeugen von Bewegung:
public void draw() { ... x+=zufall.nextInt(3)-1; y+=zufall.nextInt(3)-1; }
Code 0-12: Zusatzcode in den beiden draw()-Methoden von BlauesQuadrat und RoterKreis
Lösung zu Aufgabe 3
import java.util.Random; Random zufall = new Random(System.currentTimeMillis()); iFigur[] figur; public void setup() { figur = new iFigur[100]; int ii=0; for(int i=0;i<50;i++) figur[ii++] = new BlauesQuadrat(zufall.nextInt(width),zufall.nextInt(height),this); for(int i=0;i<50;i++) figur[ii++] = new RoterKreis(zufall.nextInt(width),zufall.nextInt(height),this); size(600,600); } public void draw() { background(255); for(int i=0;i<figur.length;i++) figur[i].draw(); } public interface iFigur { public abstract void draw(); } public class BlauesQuadrat implements iFigur { int x,y; PApplet pap; public BlauesQuadrat(int x, int y, PApplet pap) { this.x = x; this.y = y; this.pap = pap; } public void draw() { pap.noStroke(); pap.fill(0,0,255,100); pap.rect(x,y,30,30); x+=zufall.nextInt(3)-1; y+=zufall.nextInt(3)-1; } } public class RoterKreis implements iFigur { int x,y; PApplet pap; public RoterKreis(int x, int y, PApplet pap) { this.x = x; this.y = y; this.pap = pap; } public void draw() { pap.noStroke(); pap.fill(255,0,0,100); pap.ellipse(x,y,20,20); x+=zufall.nextInt(3)-1; y+=zufall.nextInt(3)-1; } }
Code 0-13: Musterlösung zu Aufgabe 3 (mit Bewegung).
Lösung zu Aufgabe 4
PImage[] img; int index=0; public void setup() { img = new PImage[3]; img[0]=loadImage("mensa.png"); img[1]=loadImage("technik.png"); img[2]=loadImage("haupt.png"); //size(600,400); fullScreen(); } public void draw() { image(img[index],0,0,width,height); } public void mousePressed() { index++; index%=img.length; }
Code 0-14: Diashow002
Lösung zu Aufgabe 5
public class Besucher implements iMode { PImage[] img; int index=0; public Besucher() { img = new PImage[3]; img[0]=loadImage("mensa.png"); img[1]=loadImage("technik.png"); img[2]=loadImage("haupt.png"); } public void draw() { fill(0); textSize(30); //text("THB Führung", 300, 120); image(img[index],0,0,width,height); textAlign(CENTER); fill(255); rect(0,0,100,50); fill(0); textSize(20); text("zurück", 50, 25); } public int setMode(int mode, int mx, int my) { if(mx>=0 && mx<100 && my>=0 && my<50) { mode = 0; } else if(mx>=0 && my>=0) { index++; index%=img.length; } return mode; } }
Code 0-15: Veränderte Klasse Besucher. Nur diese muss geändert werden (und die Bilder in den Sketchordner kopiert werden).
Lösung zu Aufgabe 6
... wird im Unterricht gemeinsam besprochen und umgesetzt.




3. Erste Schritte mit den Android-Tablets
Um auf das Tablet Android-Apps von Processing aus übertragen zu können, müssen auf dem Tablet die Entwickleroptionen aktivert und konfiguriert werden.
1. Sieben mal auf die Build-Nummer tippen, um die Entwickleroptionen zu aktivieren
Dazu:

Bild 0-7: 1.1 Entwickleroptionen öffnen.

Bild 0-8: Infos zu Tablet öffnen.

Bild 0-9: Softwareinformationen öffnen.

Bild 0-10: Dort sieben mal auf die Buildnummer tippen, um die Entwickleroptionen zu aktivieren.
2. Entwickleroptionen konfigurieren
Dazu:

Bild 0-11: In Einstellungen zu Entwickleroptionen gehen.

Bild 0-12: "Aktiv lassen" aktivieren.

Bild 0-13: "USB-Debugging" und "adb-autorisierungsteimeout deaktiviert" aktivieren

Bild 0-14: weitere aktivieren.
|
#6 Mi 01.11.2023
Themen
|
2. QR-Codes nutzen
Hier wurde eine Library zu QR-Codes mit Processing aus dem jahr 2016 mit zwei Testbeispielen zur Verfügung gestellt:

Kombination mit der Kamera:
Sketchpermissions:
|
QR-Codes testweise mit Linux erzeugen, Beispiel, im Terminal einzugeben:
|

|
Man muss den QR-Code sehr groß machen und sehr genau ein umscheibendes Quadrat aufnehmen.
3. GPS nutzen
|

Bild 0-15: Sketchpermissions für Geolocation.
Sketchpermissions: Jede App muss ausweisen, welche Sensoren sie benutzt und ob sie auf Benutzerdaten zugreift.
4. OOP-Übung mit GPS
Ausgangspunkt sei LocationDistance2.
|
public interface iFaultObserver { public abstract boolean isOkay(); }
Code 0-16: Quelltext der einzubauenden Schnittstelle.
Entwerfen Sie in Form eines UML-ähnlichen Diagramms eine Software-Architektur für ein Projekt, das in folgender Weise arbeiten soll:
|
Tipp: Bevor Sie die geforderte Software-Architektur umsetzen, sollten Sie grob die geforderte Funktionalität auf die einfachste Weise umsetzen.
Ausbaustufen der Lösung, Ausgangspunkt: Geolocation



#7 Mi 08.11.2023
Themen
|
1. Neue Fehlertoleranzpattern: Correcting Audits und Someone in Charge
2. KRcode ;-)
Es wurde bisher ein Verfahren getestet, bei dem festgestellt werden kann, ob eine Person ein bestimmtes Ziel erreicht hat und dann Infos über das Ziel bereit gestellt wurden: Das Verfahren sollte mit QR-Codes arbeiten. Diese sind an markanten Punkten bei den Sehenswürdigkeiten angebracht und sollten durch die App-Benutzer gescannt werden.
Hier traten nun gravierende Schwierigkeiten auf: Da die verfügbare Library nur unverzerrte Codes scannen kann, ist es sehr schwierig den Code so in den Kamerafokus zu bekommen, dass er dann erkannt wird. Das macht die ganze Sache unpraktikabel.
Eine Lösung könnte es sein, einen eigenen Scancode zu entwickeln, der robuster ist, sich also auch dann scannen lässt, wenn man den Code nicht so gut im Fokus hat. Was die Sache erleichtern könnte, wäre, dass nicht unbedingt sehr viel Information übertragen werden muss. Im Grunde würde es schon reichen, wenn eine bloße Nummer übermittelt wird, die jedem Ort, der angesteuert wird, vergeben wird. Die eigentlichen Infos können ohne weiteres in der App gespeichert sein.
Grundidee
Nimmt man kreisförmige Ringe als Grundlage für den Code, hat man schon einmal den Vorteil, dass der Winkel egal ist, in dem der Code mit der Kamera erfasst wird.
Zwei immer gleiche äußere Ringe könnten dazu dienen, zu prüfen, ob ein gültiger Code im Fokus ist.
Die inneren Ringe sind dann in der Farbe veränderlich und kodieren dann eine Zahl.
Passend zur Corporate Identity der THB könnten die Ringe rot sein.
Ob der Scan richtig erfasst wird, könnte daran festgemacht werden, dass immer vier Punkte um 90 Grad verdreht die gleiche Information liefern:

Bild 0-16: In den vier schwarzen Punkten muss die gleiche Farbe zu sehen sein, damit der Code als lesbar erkannt wird.
Die geringere Informationsdichte des Codes sollte also zu einer höheren Fehlertoleranz beim Scannnen führen.
Es wäre noch denkbar, den Code als Zahl ins Zentrum zu schreiben und dem Benutzer die Möglichkeit zu geben, den Code auch einzutippen, je nach Anwendungsfall.
Umsetzung
Mögliches Beispiel eines solchen Codes nebst einem Codegenerator in Processing:

Bild 0-17: Kodierung der Zahl 11 mit KRcode.
int ZAHL=0; String name="code"; boolean SAVE=false; public void setup() { size(500,500); frameRate(2); } public void draw() { float w = width; background(255); noStroke(); fill(255,0,0); ellipse(w/2,w/2,w*0.9,w*0.9); fill(255,255,255); ellipse(w/2,w/2,w*0.8,w*0.8); fill(255,0,0); ellipse(w/2,w/2,w*0.7,w*0.7); fill(255,255,255); ellipse(w/2,w/2,w*0.6,w*0.6); int Z=ZAHL; String text1 = ""; for(int i=0;i<4;i++) { float d = 0.5-(float)i*0.1; text1=Z%2+" | "+text1; if(Z%2==1) fill(255,0,0); else fill(255); ellipse(w/2,w/2,w*d,w*d); Z/=2; } text1 = " | "+text1+" = "+ZAHL; fill(255); ellipse(w/2,w/2,w*0.1,w*0.1); fill(0); textSize(12); textAlign(LEFT,TOP); text(text1,10,20); textAlign(CENTER); textSize(20); text(""+ZAHL,w/2,w/2+w/70); if(ZAHL<16) { save(name+ZAHL+".png"); ZAHL++; } }
Code 0-17: Processing-Sketch zur Generierung der Codes für die Zahlen 0 bis 15.

Entwicklung eines minimalen Beispielprogramms zum Scannen von KRcodes
Unter Verwendung der Library Ketai, ist nachfolgend eine minimalistische eines Scan-Programms zu sehen.
Sobald ein gültiger Code im Fokus ist, wird der Hintergrund von rot auf grün gesetzt und der erfasste Code angezeigt.

Bild 0-18: Screenshot bei Verwendung von KRscan002, wenn ein KRcode korrekt erfasst wurde, hier die Zahl 3.
import ketai.camera.*; KetaiCamera cam; int BREITE = 640; int HOEHE = 480; int[][] bild; // 4*8 Datenpunkte, immer von innen nach Außen gescannt, siehe auswerten() int[][] data = new int[4][8]; //erste 4: Zahl, zweite 4: äußere Ringe int ZAHL = -1; //erkannte Zahl int LETZTEZAHL = -1; //zuletzt erfolgreich erkannte Zahl void setup() { bild = new int[HOEHE][BREITE]; fullScreen(); orientation(LANDSCAPE); cam = new KetaiCamera(this, BREITE, HOEHE, 24); } void draw() { if(ZAHL<0) { background(255,0,0); } else { background(0,255,0); } if(!cam.isStarted()) { cam.start(); } if (cam != null && cam.isStarted()) { image(cam, 0, 0, BREITE, HOEHE); } fill(255); rect(BREITE,0,BREITE,HOEHE); noStroke(); for(int i=0;i<bild.length;i++) { for(int k=0;k<bild[i].length;k++) { if(bild[i][k]==0) { fill(0); rect(BREITE+k,0+i,1,1); } else if(bild[i][k]==1) { fill(0,255,0); //erkannt, logisch 1 ellipse(BREITE+k,0+i,5,5); } else if(bild[i][k]==2) { fill(255,0,0); //erkannt, logisch 0 ellipse(BREITE+k,0+i,5,5); } } } stroke(0); noFill(); int durchmesser = (int)round(0.9f*(float)HOEHE); ellipse(BREITE/2,HOEHE/2,durchmesser,durchmesser); //data ausgeben (gescannte Binärcodes): fill(0); textSize(20); for(int i=0;i<data.length;i++) { for(int k=0;k<data[i].length;k++) { text(""+data[i][k],60+k*20,height-200+i*30); } } //erkannte Zahle ausgeben: textSize(60); fill(0); text(""+ZAHL,width/2,height-200); fill(127); text("("+LETZTEZAHL+")",width/2+width/4,height-200); } /** Liefert die gescannte Zahl, oder -1 */ public int auswerten(int[][] bild) { int dx = (int)round(0.05f*(float)HOEHE);//Schrittweite von einem Ring zum nächsten int im = HOEHE/2; //Zentrum y int km = BREITE/2; //Zentrum x for(int k=0;k<data[0].length;k++) { int p1 = km+dx+dx/2+dx*k; int p2 = km-dx-dx/2-dx*k; int p3 = im+dx+dx/2+dx*k; int p4 = im-dx-dx/2-dx*k; if(bild[im][p1]==0) //horizontal nach rechts { bild[im][p1]=1; //logisch 1 erkannt data[0][k]=1; } else { bild[im][p1]=2; // logiscgh 0 erkannt data[0][k]=0; } if(bild[im][p2]==0) //horizontal nach links { bild[im][p2]=1; //logisch 1 erkannt data[1][k]=1; } else { bild[im][p2]=2; // logiscgh 0 erkannt data[1][k]=0; } if(bild[p3][km]==0) //vertikal nach unten { bild[p3][km]=1; //logisch 1 erkannt data[2][k]=1; } else { bild[p3][km]=2; // logiscgh 0 erkannt data[2][k]=0; } if(bild[p4][km]==0) //vertikal nach oben { bild[p4][km]=1; //logisch 1 erkannt data[3][k]=1; } else { bild[p4][km]=2; // logiscgh 0 erkannt data[3][k]=0; } } //1. prüfen, ob alle Winkel die gleichen Daten enthalten: boolean OKAY = true; for(int i=0;i<data.length-1;i++) for(int ii=i+1;ii<data.length;ii++) for(int k=0;k<data[i].length;k++) if(data[i][k]!=data[ii][k]) OKAY=false; //2. prüfen, ob der Code der äußeren Ringe 0101 ist: if(OKAY) { if(data[0][7]==1 && data[0][6]==0 && data[0][5]==1 && data[0][4]==0) OKAY=true; else OKAY=false; } if(OKAY) { //Zahl berechnen (eine Richtung reicht) int Z = 0; for(int k=0;k<4;k++) { Z*=2; Z+=data[0][k]; } return Z; } else { return -1; } } void onCameraPreviewEvent() { cam.read(); cam.updatePixels(); if(cam.pixels!=null) { int ii=0; for(int i=0;i<bild.length;i++) { for(int k=0;k<bild[i].length;k++) { int pix = cam.pixels[ii]; int bb = (pix & 0xFF); int gg = (pix & 0xFF00)>>8; int rr = (pix&0xFF0000)>>16; int p = rr-((gg*2)/3)-((bb*2)/3); //***NEU*** if(p>0) bild[i][k] = 0; //schwarz, wenn rot else bild[i][k] = 255; // weiss wenn anderes ii++; } } ZAHL = auswerten(bild); if(ZAHL>=0) LETZTEZAHL = ZAHL; } }
Code 0-18: KRscan002 (Entstanden, indem zunächst CameraGettingStarted aus der Ketai-Library unter neuem Namen gespeichert und dann modifiziert wurde.)

ÜBUNG
|
|
|
3. Ideensammlung für die Abschlussprojekte
Interessant wäre Konzepte, die deutliche Alternativen zu der voran umgesetzten App bilden.
4. Planung eines Referenzprojektes als Ausgangspunkt
|
#8 Mi 15.11.2023
Themen
|
1. Neue Fehlertoleranzpattern: Redundancy und Maintanance Interface
2. Beispiellösung für Besucherrallye mit KRcode


import ketai.camera.*; int MODE = 0; long T = 0; long Tstart = 0; String[] texte = { "Scanne den KRcode am Startpunkt", //MODE0 "Scanne den KRcode bei Station 1", //MODE1 "Scanne den KRcode bei Station 2", //MODE2 "Scanne den KRcode bei Station 3", //MODE3 "Scanne den KRcode bei Station 4", //MODE4 (Ziel) "Ziel erreicht in "+(T/1000)+" Sekunden! RESET: scanne 5" //MODE 5 }; Kamera kamera; KRscan krscan; int BREITE = 640; int HOEHE = 480; void setup() { fullScreen(); orientation(LANDSCAPE); kamera = new Kamera(this); krscan = new KRscan(); } void draw() { int ZAHL = krscan.getZahl(); if(ZAHL<0) { background(255,0,0); } else { if(krscan.getLetzteZahl()==MODE) { if(MODE==0) Tstart = System.currentTimeMillis(); if(MODE==4) { T = System.currentTimeMillis() - Tstart; texte[5]="Ziel erreicht in "+(T/1000)+" Sekunden! RESET: scanne 5"; //MODE 5 } MODE++; MODE%=texte.length; } background(0,255,0); } kamera.draw(0,0); krscan.drawZielkreis(0,0); int[][] bild = kamera.getBild(); krscan.auswerten(bild); textSize(40); fill(255,255,0); text(texte[MODE],10,height/2+height/4); if(MODE>=1 && MODE<=4) { text("Die Zeit läuft: "+((System.currentTimeMillis()-Tstart)/1000)+"s",10,height/2+height/4+height/8); } }
Code 0-19: Hauptprogramm bei KRscan004Rallye.
3. Entwicklung eines Referenzprojektes
Dazu zunächst: Analyse der Programmiertechniken, die die Lösungen KRscan003Rallye und KRscan004Rallye ermöglicht haben.
|
import ketai.camera.*; public class Kamera extends KetaiCamera implements Runnable { PApplet pap; int BREITE = 640; int HOEHE = 480; int[][] bild; public Kamera(PApplet pap) { super(pap, 640, 480, 24); this.pap = pap; bild = new int[HOEHE][BREITE]; (new Thread(this)).start(); } public void run() { while(true) { if(!this.isStarted()) { this.start(); } try { Thread.sleep(500); } catch(Exception e) { } } } public int[][] getBild() { if(!this.isStarted()) return bild; this.read(); this.updatePixels(); if(this.pixels!=null) { int ii=0; for(int i=0;i<bild.length;i++) { for(int k=0;k<bild[i].length;k++) { int pix = this.pixels[ii]; int bb = (pix & 0xFF); int gg = (pix & 0xFF00)>>8; int rr = (pix&0xFF0000)>>16; int p = rr-((gg*2)/3)-((bb*2)/3); if(p>0) bild[i][k] = 0; //schwarz, wenn rot else bild[i][k] = 255; // weiss wenn anderes ii++; } } } return bild; } public void draw(int x, int y) { if(this.isStarted()) pap.image(this, x, y, BREITE, HOEHE); } public void drawBild(int x, int y) { fill(255); rect(x,y,BREITE,HOEHE); noStroke(); for(int i=0;i<bild.length;i++) { for(int k=0;k<bild[i].length;k++) { if(bild[i][k]==0) { fill(0); rect(x+k,y+i,1,1); } else if(bild[i][k]==1) { fill(0,255,0); //erkannt, logisch 1 ellipse(x+k,y+i,5,5); } else if(bild[i][k]==2) { fill(255,0,0); //erkannt, logisch 0 ellipse(x+k,y+i,5,5); } } } } }
Code 0-20: Klasse Kamera zur Analyse.

Bild 0-19: UML Objekt-Diagramm zur Klasse Kamera.
|

Bild 0-20: UML Objekt-Diagramm zum Projekt KRscan004Rallye
|


Praktisches Schema für den Arbeitsablauf bei der Entwicklung von Software: Agiler Softwareentwurf

User Stories zur Leitung des weiteren Entwicklungsprozesses des Referenzprojektes ausgehend von KRscan004Rallye
|
ÜBUNG zu KRscan003Rallye und KRscan004Rallye
|
|

4. Konkretisierung der Ideen für die Abschlussprojekte
ÜBUNG
Formulieren Sie Userstories zu Ihrem geplanten Abschlussprojekt und starten einen ersten "daily scrum".
ArrayList
ArrayList<int[]> arr; public void setup() { arr = new ArrayList<int[]>(); arr.add(new int[] {300,400}); arr.add(new int[] {400,400}); arr.add(new int[] {400,300}); arr.add(new int[] {300,400}); size(600,600); } public void draw() { background(255); int[] b = arr.get(0); stroke(0); for(int i=1;i<arr.size();i++) { int[] a = arr.get(i); line(a[0],a[1],b[0],b[1]); b=a; } }
Code 0-21: ArrayList-Beispiel
#8 Mi 22.11.2023
Beginn mit den Projektarbeiten
|
|
#9 Mi 29.11.2023
Themen
|
1. Präsentation einiger Snippets
In der letzten Besprechung der Projektthemen wurden einige Anforderungen identifiziert, zu denen nachfolgend kleine Beispielprogramme bereitgestellt werden, die aufzeigen, wie bestimmte Dinge bewerkstelligt werden können.
Alle diese kleinen Beispielprogramme (Snippets) haben gemeinsam, dass sie genau eine Sache zeigen, jedoch keine weitere Funktionalität. Das macht die Programme kurz. Zum anderen obliegt es den Projektteams die Transferleistung zu erbringen, die Beispiele nötigenfalls für ihre jeweilige Softwarearchitektur anzupassen und zu integrieren.
Stets ist bei den Snippets die zu demonstrierende Funktionalität in einer Klasse eingebettet, die dann einfache Objektmethoden bereitstellt, um die Funktionalität im Hauptprogramm (draw()) zu nutzen.
Die einzufügenden Libraries können im Klassentab angegeben werden. Das spart Ärger beim Übertragen auf ein anderes Projekt.

Bild 0-21: Screenshot zu Snippet1

Snippet #1 -- Beschleunigung -- Anzeige des Richtungsvektors in der Ebene, wohin die Erdbeschleunigung weist
Ausgangspunkt der Entwicklung ist das Beispiel Accelerometer aus der Ketai-Library.
Beschleunigung beschleunigung; PImage thblogo; public void setup() { beschleunigung = new Beschleunigung(this); thblogo = loadImage("thb2.png"); fullScreen(); orientation(LANDSCAPE); frameRate(30); } /** Callback-Methode ist leider nicht in die Klasse Beschleunigung kapselbar: */ public void onAccelerometerEvent(float x, float y, float z) { beschleunigung.onAccelerometerEvent(x,y,z); } public void draw() { background(255); fill(127); rect(0,0,height,height); stroke(255); strokeWeight(height/100.0); float[] vek = beschleunigung.getRichtung(); float radius = height/2.0; float laenge = sqrt(vek[0]*vek[0]+vek[1]*vek[1]); if(laenge>0.0) line(radius,radius,radius+vek[1]*radius/laenge,radius+vek[0]*radius/laenge); strokeWeight(1.0); image(thblogo,width-height/8,0.0,height/8,height/8); fill(0); noStroke(); textSize(36); text("Snippet #1",height+10,50); text("Beschleunigungsrichtung",height+10,150); }
Code 0-22: Hauptprogramm von Snippet1_Beschleunigung
import ketai.sensors.*; /** Analyse von api ketai.sensors.KetaiSensor http://ketai.org/reference/sensors/ketaisensor/ */ public class Beschleunigung extends PApplet { KetaiSensor sensor; float accX, accY, accZ; public Beschleunigung(PApplet pap) { sensor = new KetaiSensor(pap); sensor.start(); } public void onAccelerometerEvent(float x, float y, float z) { accX = x; accY = y; accZ = z; } public float[] getRichtung() { if(accX!=0.0 || accY!=0.0 || accZ!=0.0) { float laenge = sqrt(accX*accX+accY*accY+accZ*accZ); return new float[] {accX/laenge,accY/laenge,accZ/laenge}; } else { return new float[] {0.0,0.0,0.0}; } } }
Code 0-23: Klasse Beschleunigung in Snippet1_Beschleunigung
Snippet #2 -- UDP-Nachrichten (Kommunikation zwischen den Tablets)
Hierzu muss die UDP-Library installiert werden!
Ausgangspunkt ist das Beispiel udp aus der UDP-Library.
|
HINWEIS: In der Android-Version des Sketches wird versucht



Bild 0-22: Beim PC empfangene Nachricht vom Tablet. Hier spätestens kann die IP des Tablets erkannt werden.

Bild 0-23: Screenshot zu Snippet2 (Tablet nach Empfang der PC-Nachricht).
Ergänzung: Verwendung der Broadcast-Adresse
Um zu vermeiden, die Ziel-IP hart zu kodieren, kann einfach an alle im Subnetz angemeldeten Geräte gresendet werden, indem die Broadcast-Adresse verwendet wird:

Bild 0-24: Ausgabe nach "ifconfig" im Terminal. Die Broadcastadresse wurde markiert, hier: 192.168.147.255
Schließen Sie alle anderen Apps, bevor Sie Snippet2 testen, da sonst der Datenaustausch eventuell nicht funktioniert.