Das war ein schweres Stück Arbeit. Ganz ohne Hilfe ist es auch nicht gegangen. Aber jetzt sind wir stolze Besitzer von drei Dateien, die zusammen die beiden Ampeln steuern. Perfekt, oder?
Dürfen wir vorstellen? Die Header Datei des TrafficLight
:
#ifndef TRAFFIC_LIGHT_H
#define TRAFFIC_LIGHT_H
#include <Adafruit_NeoPixel.h>
class TrafficLight {
// A traffic light with adjustable brightness.
// The traffic light consists of three lamps.
// Each lamp consists of one or more LED pixels.
// symbolic constants for the different STATES of a traffic light
#define TL_OFF 0 // all lights are off
#define TL_STOP 1 // red lamp on
#define TL_ALERT 2 // simultaneously red and yellow on
#define TL_GO 3 // green lamp on
#define TL_HALT 4 // yellow lamp on
// symbolic constants for the lamp color index
#define TL_RED_LAMP 0
#define TL_YELLOW_LAMP 1
#define TL_GREEN_LAMP 2
public:
TrafficLight();
// when creating a new TrafficLight we need a name, a reference to the LED strip,
// the LED positions (per lamp) and the number of LEDs per lamp
TrafficLight(String name, Adafruit_NeoPixel *strip, const int *ledPos, int pixPerLamp);
/*
* We have several layers of functions, from TOP to BOTTOM:
* (7) setGreenTime, addSensor, setBrightness : configuration
* (6) performCycle : walk through all states in the correct sequence
* (5) off,stop,alert,go,halt : change state of TrafficLight switching appropriate lamp(s)
* (4) ..On, ..Off, clear : switch a certain lamp (x) ON or OFF, clear all lamps
* (3) lampOn, lampOff : switch a lamp on/off using its defined color
* (2) setColor : assign a color to an arbitrary lamp, changing all of its LEDs
* (1) showFor, showForMin : transfer LED settings to the strip and wait for the appropriate time
*/
void addSensor(int sensorPin);
void setGreenTime(int time); // in msec
void setBrightness(byte brightness); // 0=off, 255 is maximum
void performCycle();
void off();
void stop();
void alert();
void go();
void halt();
void redOn();
void redOff();
void yellowOn();
void yellowOff();
void greenOn();
void greenOff();
void clear();
void lampOn(int lampNr); // from top to bottom, 0=red, 1=yellow, 2=green
void lampOff(int lampNr);
void showFor(int time); // in msec
bool showForMin(int time, int pct); // show for 'time' or shorter, but at least 'pct' percent of 'time'
void show(); // just show and return
private:
void setColor(int lampNr, uint32_t color); // we use the lower 24 bits as RRGGBB color
// message logging to the serial port with time stamp
int64_t startedAt;
void debug(String message);
String name;
Adafruit_NeoPixel *strip;
int times[TL_HALT+1]; // specific times per STATE in msec
int ledPos[TL_GREEN_LAMP+1]; // first LED of each lamp
int pixPerLamp; // number of pixels per lamp
int sensorPin; // optional pin for traffic detection sensor
uint32_t colors[TL_GREEN_LAMP+1]; // the three base colors; brightness may be adjusted separately
};
#endif
Hey, das sieht gut aus (diesmal ganz ehrlich!). Schön, dass ihr erkannt habt, dass man den Begriff der lamp
benötigt. Das war der entscheidende Erkenntnisschritt! Man sieht gut, dass die höheren Funktionen für den Zustandswechsel nur Lampen kennen und nicht wissen, ob es LEDs sind oder z.B. Glühbirnen! So soll es sein. Erst auf einer niedrigeren Ebene der Funktionen ist der Zusammenhang zwischen Lampen und LEDs bekannt.
Prima, dass ihr das „Verriegelungsprinzip gegen Mehrfach-Inklusion“ eingebaut habt. Sicher habt ihr auch im Internet nachgelesen, wozu das gut ist. Zeigt mal das Hauptprogramm!
Wir dachten, dass jetzt eigentlich die Implementierung der TrafficLight
Klasse an der Reihe wäre?
Das sehe ich anders. Überlegt doch einmal, wie ihr es mit der Neopixel-Bibliothek gemacht habt! Ich wette, ihr habt kein Interesse an dem Quellcode der Bibliothek gehabt – falls ihr überhaupt wisst, wo er innerhalb der Arduino IDE zu finden ist 😉
Ist das jetzt wirklich wahr? Das war echt anstrengend! Wenn man etwas gut hin bekommen hat, dann finden die anderen es unwichtig?? Für uns ist es WICHTIG! Manche Sachen waren außerdem echt schwer zu programmieren …
Sorry, aber ihr müsst lernen, damit zu leben. Das Vertrauen der Benutzer eurer Klasse sollte euch strahlen lassen. Die haben nämlich eine andere Perspektive: Sie freuen sich, dass sie die Innereien eurer Klasse gerade NICHT verstehen müssen. Sie haben ihre eigenen Probleme zu lösen und wollen sich auf ihrer (höheren) Abstraktionsebene bewegen. Deswegen ist ein gut entworfenes Interface ja so wichtig. Also zeigt endlich das Hauptprogramm her!
Wenn es denn sein muss, hier ist TrafficLights_V3.ino
:
// Two traffic lights, using the standard pattern (R,R+Y,G,Y).
//
// The GREEN TIME for each traffic light can be configured
// A sensor signal shortens the GREEN TIME for the crossing line
// This might be improved (e.g. mutual blocking of sensors)
// special light sequences are used to switch the whole system ON and OFF
//
// version 3
#include "TrafficLight.h"
// setup the LED strip
// ===================
int numberOfPixels = 16;
int stripPin = 26;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(numberOfPixels,stripPin);
// declare the two traffic lights
// ==============================
TrafficLight tlA, tlB;
// setup
// =====
void setup() {
// setup serial line for debugging
Serial.begin(115200);
delay(100);
// define two traffic lights
Serial.println("Traffic Lights starting (initially OFF) ..");
tlA = TrafficLight( "Hasengasse", &strip, (const int[]) { 9,8,7}, 1 );
tlA.setBrightness(20);
tlA.setGreenTime(5000);
tlB = TrafficLight( "Fuchsweg", &strip, (const int[]) { 4,3,2}, 1);
tlB.setBrightness(2);
tlB.setGreenTime(10000);
tlB.addSensor(27);
}
// functions to handle all traffic lights in the same way
// ======================================================
void allOff() {
tlA.off();
tlB.off();
}
void allClear() {
tlA.clear();
tlB.clear();
}
void allSetLamp(int lamp) {
allClear();
tlA.lampOn(lamp);
tlA.show();
tlB.lampOn(lamp);
tlB.show();
}
void allBlink(int cycles) {
allOff();
for (int n=0;n<cycles;n++) {
allSetLamp(TL_YELLOW_LAMP);
delay(800);
allOff();
delay(800);
}
}
// start up sequence : yellow blink -- steady yellow -- red
// ========================================================
void startUp() {
// blink all traffic lights two times
allBlink(2);
// steady yellow
allSetLamp(TL_YELLOW_LAMP);
delay(2000);
// all red for some time
allSetLamp(TL_RED_LAMP);
delay(2000);
}
// standardCycle
// =============
void standardCycle() {
tlA.performCycle();
tlB.performCycle();
}
// shut down sequence : yellow blink -- off
// ========================================
void shutDown() {
// blink
allBlink(2);
// switch OFF
allOff();
}
// loop
// ====
void loop() {
// begin of day
Serial.println("WAKING UP");
startUp();
// during the day (simulate two full cycles)
Serial.println("STARTING DAYLIGHT CYCLES");
for (int n=0;n<2;n++) {
standardCycle();
}
// go asleep at night
Serial.println("GOING ASLEEP");
shutDown();
delay(5000);
}
Wir haben viele kleine Funktionen entworfen, damit es möglichst gut lesbar ist. Unsere Etagen sehen so aus
- setup und loop
- wakeup, standardCycle, shutDown
- allClear, allOff, allSetLamp, allBlink
Das ist recht gut gelungen! Ich hab da allerdings noch einen kleinen Vorschlag: Ihr könntet die unteren beiden Schichten in eine eigene, neue Klasse packen. Ich wüsste sogar einen guten Namen dafür…
Ampelsteuerung
vielleicht? Ach so, auf Englisch … TrafficLightControl
?
Nein, ich hab an TrafficLightSystem
gedacht. Ich habe nämlich im Kopf, dass vielleicht noch Fahrbahn-Sensoren dazu kommen, Fußgängerampeln mit nur zwei Lampen und mit einem Signalknopf. All das zusammen gibt ein „System“. Klar muss das System „gesteuert“ werden, damit die Lichter schalten. Aber Klassen benennt man eigentlich eher ungern nach ihren Tätigkeiten – so wie man einen Menschen zwar manchmal z.B. als Schwimmer bezeichnet – aber das erfasst sein Wesen eben nur zum Teil. Eine Klasse ist zuerst einmal ein Etwas, das aus bestimmten Teilen besteht, die Zustände haben können (Also: Das TraficLight besteht aus Lampen, die bestimmte Farben zeigen). Die Aktionen (Methoden) der Klasse benutzen die Teile, verändern Zustände, kurz sie beeinflussen das „System“. Auch wenn es eine klar definierte Haupttätigkeit wie „Schalten“ gibt, benennt man ein Klasse nicht so gern nach der Haupttätigkeit.
Ja, das leuchtet ein. Wenn man zum Beispiel die Fahrbahnsensoren zur Verkehrszählung benutzen würde, dann hätte das ja gar nicht mehr unbedingt etwas mit der Ampel-Schalt-Funktion zu tun. Die Ampelanlage (= der deutsche Begriff für TrafficLightSystem
) könnte ja sogar nachts zählen, wenn sie abgeschaltet ist!
Habt ihr noch genug Energie für einen weiteren Umbau? Die Aufgabe des Hauptprogramms ist es dann nur noch, das System zu konfigurieren und die Betriebszeiten vorzugeben – also ziemlich genau das, was in setup
und loop
jetzt bereits steckt. Ihr seht, das eigentliche Hauptprogramm driftet immer weiter nach oben weg und die Arbeit wird auf den tieferen Ebenen erledigt – ganz wie im echten Leben…
Führt bitte noch eine Fußgängerampel ein; ihre Grünphase soll deutlich kürzer sein als die der Autos. Schaut euch mal eine echte Ampelanlage an! Das Grünsignal für die Fußgänger kommt meist ein klein wenig vor den Autos, die in die gleiche Richtung fahren. Das soll verhindern, dass abbiegende Autos den Omis noch eben mal schnell vor die Füße fahren.