{"id":431,"date":"2021-01-27T17:17:36","date_gmt":"2021-01-27T16:17:36","guid":{"rendered":"https:\/\/followthescore.org\/schueler-labor\/?p=431"},"modified":"2021-02-14T09:14:56","modified_gmt":"2021-02-14T08:14:56","slug":"verkehrsampel-v2","status":"publish","type":"post","link":"https:\/\/followthescore.org\/schueler-labor\/verkehrsampel-v2\/","title":{"rendered":"Verkehrsampel V2"},"content":{"rendered":"\n<p>In diesem Programm sind die abstrakteren Konzepte (<em>Traffic light, state<\/em>) von den niedrigen Ebenen (<em>lamp, LEDs<\/em>) getrennt.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/\/ Two traffic lights, using the standard pattern (R,R+Y,G,Y).\n\n\/\/ version 2\n\n#include &lt;Adafruit_NeoPixel.h&gt;\n\n\/\/ we use a single strip of 14 LEDs, two LEDs per lamp\n\/\/ traffic light A with three lamps:  RED( 1, 2), YELLOW( 3, 4), GREEN( 5, 6)\n\/\/ traffic light B with three lamps:  RED(11,12), YELLOW( 9,10), GREEN( 7, 8)\nAdafruit_NeoPixel strip = Adafruit_NeoPixel(14,26);\n\n \/\/ position of the first LED of each LAMP for each TL\nint ledPos&#x5B;2]&#x5B;3] = { \n    {1,3,5},            \/\/ tlA\n    {11,9,7}            \/\/ tlB\n};\n\n#define PIX_PER_LAMP 2                      \/\/ number of led pixels per lamp\n\nint tlA=0, tlB=1;                           \/\/ the two Traffic Lights\n\n\n\/\/ switching times; note that &quot;red time&quot; is only the overlap between the traffic lights\n\/\/ id addition a TL will always be red when its opposing TL shows yellow or green\n\n\/\/ standard times per STATE (durations in msec)\nint stdTimes&#x5B;4] = { 500, 1000, 10000, 2000 };\n\n\/\/ specific times for each TL\nint times&#x5B;2]&#x5B;4];                    \/\/ specific durations for each traffic light\n\n\/\/ symbolic constants for the different STATES of a TL\n#define STOP                0       \/\/ red lamp on\n#define ALERT               1       \/\/ simultaneously red and yellow on\n#define GO                  2       \/\/ green lamp on\n#define HALT                3       \/\/ yellow lamp on\n\n\/\/ the three base colors; note that brightness will be adjusted separately\nuint32_t colors&#x5B;3] = {\n    strip.Color( 255,   0,   0 ),       \/\/ RED\n    strip.Color( 128, 128,   0 ),       \/\/ YELLOW\n    strip.Color(   0, 255,   0 )        \/\/ GREEN\n};\n\/\/ symbolic constants for the lamp color index\n#define RED_LAMP     0\n#define YELLOW_LAMP  1\n#define GREEN_LAMP   2\n\n\n\/*\n * We have several layers of functions, from TOP to BOTTOM:\n *   (7) setGreenTime       : configure switching times\n *   (6) peformCycle        : walk through all states in the correct sequence\n *   (5) stop,alert,go,halt : change state of TrafficLight switching appropriate lamp(s)\n *   (4) xOff               : switch a certain lamp (x) OFF\n *   (3) on, off            : switch a lamp on\/off using its fixed color\n *   (2) setColor           : assign a color to an arbitrary lamp, changing all of its LEDs\n *   (1) showFor            : transfer LED settings to the strip and wait  for the appropriate time\n *\/\n\n\/\/ Layer (1) : transfer LED states to the strip and wait for the specified time\nvoid showFor(int msec) {\n    strip.show();\n    delay(msec);\n}\n\n\/\/ Layer (2) : set a single lamp to an arbitrary color\nvoid setColor(int tlNr, int lampNr, uint32_t color) {\n    strip.fill(color,ledPos&#x5B;tlNr]&#x5B;lampNr],PIX_PER_LAMP);    \n}\n\n\/\/ Layer (3) : switch a single lamp ON or OFF using its fixed color\nvoid on(int tlNr, int lampNr) {\n    setColor(tlNr,lampNr,colors&#x5B;lampNr]);    \/\/ each lamp has its fixed color\n}\nvoid off(int tlNr, int lampNr) {\n    setColor(tlNr,lampNr,0);    \/\/ color 0 == BLACK == OFF\n}\n\n\/\/ Layer (4) : switch OFF a certain lamp\nvoid RedOff(int tlNr) {\n    off(tlNr,RED_LAMP);\n}\nvoid YellowOff(int tlNr) {\n    off(tlNr,YELLOW_LAMP);\n}\nvoid GreenOff(int tlNr) {\n    off(tlNr,GREEN_LAMP);\n}\n\n\/\/ Layer (5) : change state of TL switching the necessary lamps ON or OFF\nvoid stop(int tlNr) {\n    YellowOff(tlNr);\n    on(tlNr,RED_LAMP);\n    showFor(times&#x5B;tlNr]&#x5B;STOP]);\n}\nvoid alert(int tlNr) {\n    on(tlNr,YELLOW_LAMP);\n    showFor(times&#x5B;tlNr]&#x5B;ALERT]);\n}\nvoid go(int tlNr) {\n    RedOff(tlNr);\n    YellowOff(tlNr);\n    on(tlNr,GREEN_LAMP);\n    showFor(times&#x5B;tlNr]&#x5B;GO]);\n}\nvoid halt(int tlNr) {\n    GreenOff(tlNr);\n    on(tlNr,YELLOW_LAMP);\n    showFor(times&#x5B;tlNr]&#x5B;HALT]);\n}\n\n\/\/ Layer (6) : walk through all states in the correct sequence\nvoid performCycle(int tlNr) {\n    stop        (tlNr);\n    alert       (tlNr);\n    go          (tlNr);\n    halt        (tlNr);\n    stop        (tlNr);   \n}\n\n\/\/ Layer (7) : configure switching times (adjust duration of GREEN_TIME)\nvoid setGreenTime(int tlNr, int greenTime) {\n    for (int d=STOP;d&lt;=HALT;d++) {\n        times&#x5B;tlNr]&#x5B;d] = stdTimes&#x5B;d];\n    }    \n    times&#x5B;tlNr]&#x5B;GO] = greenTime;\n}\n\n\nvoid setup() {\n    Serial.begin(115200);\n    delay(100);\n\n    Serial.println(&quot;Traffic Lights starting ..&quot;);\n    strip.fill();\n    strip.setBrightness(20);\n\n    setGreenTime(tlA,  5000);\n    setGreenTime(tlB, 10000);\n\n    stop(tlA);\n    stop(tlB);\n}\n\nvoid loop() {\n\n    performCycle(tlA);\n    performCycle(tlB);\n}\n\n<\/pre><\/div>\n\n\n<p><strong><em>Ja, so ist es viel besser!<\/em><\/strong><\/p>\n\n\n\n<p><strong><em>Fr\u00fcher dachte man, dass ein Programm besonders gut ist, wenn es viele Kommentare enth\u00e4lt. Man hat sogar Programme geschrieben, die andere Programme analysieren und den Anteil der Kommentarzeilen ermitteln. Gut bezahlt wurde man mitunter nur, wenn man eine bestimmte &#8222;Quote&#8220; erreichte. Heute sehen wir das zum Gl\u00fcck ein wenig differenzierter.<\/em><\/strong><\/p>\n\n\n\n<p><strong><em>Damals waren die Programmiersprachen noch nicht so weit entwickelt und der Code war sehr technisch. Um ihn zu verstehen, war man auf Kommentare angewiesen, vor allem, wenn man ein Programm \u00e4ndern sollte, das ein anderere Softwerker fabriziert hatte. Au\u00dferdem gab es noch viele Programmierer, die am Anfang Assembler gelernt hatten und bestimmte Gewohnheiten beibehielten, die ihren Programmcode sehr effizient aber auch schwer verst\u00e4ndlich machten.<\/em><\/strong><\/p>\n\n\n\n<p>Und heute? Soo viele Kommentare haben wir ja gar nicht verwendet.<\/p>\n\n\n\n<p><strong><em>Ihr habt das richtige Ma\u00df gefunden! Wenn man sprechende Variablennamen benutzt, spricht der Code oft f\u00fcr sich selber. Man kann die Kommentare dann dazu nutzen, abstraktere Konzepte zu erl\u00e4utern, wie etwa die &#8222;Schichtung &#8220; der Funktionen. So soll es sein!<\/em><\/strong><\/p>\n\n\n\n<p>Dann haben wir also das perfekte Programm geschrieben?! Nichts mehr zu meckern?<\/p>\n\n\n\n<p><strong>Oh, vor 30 Jahren h\u00e4tte man das so gesehen. Seither hat sich jedoch ein Programmierkonzept durchgesetzt, das man als &#8222;Objektorientierung&#8220; bezeichnet. Das m\u00fc\u00dft ihr noch benutzen, damit euer Programm als gut gelten kann.<\/strong><br><br><strong>Um das Prinzip zu verstehen, vergessen wir einmal f\u00fcr einen Moment die Ampeln. Reden wir stattdessen \u00fcber Hunde!<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/\/ This little program demonstrates the idea of OBJECT ORIENTATION.\n\/\/ version 1\n\n\/\/ message logging to the serial port with time stamp\nvoid debug(String message) {\n    Serial.println(message);\n}\n\n\/\/ ============== INTERFACE of class DOG\n\nclass Dog {\n    \/\/ a dog can sleep, eat and play.\n    \n    public:\n        Dog(String name);\n        void sleep(); \n        void eat();\n        void play();\n    private:\n        String name;\n};\n\n\/\/ ============= IMPLEMENTATION of class DOG\n\nDog::Dog(String name) {\n    this-&gt;name=name;\n}\n\nvoid Dog::sleep() {\n    debug(name+&quot;: I am sleeping&quot;);\n}\nvoid Dog::eat() {\n    debug(name+&quot;: I am eating&quot;);\n}\nvoid Dog::play() {\n    debug(name+&quot;: I am playing&quot;);\n}\n\n\n\/\/ ================ USING class DOG\n\nvoid setup() {\n    \/\/ setup serial port\n    Serial.begin(115200);\n    delay(1000);\n\n    Dog fluffy = Dog(&quot;Fluffy&quot;);\n    Dog bobby = Dog(&quot;Bobby&quot;);\n\n    fluffy.sleep();\n    fluffy.eat();\n    bobby.eat();\n    fluffy.play();\n}\n\nvoid loop() {}\n<\/pre><\/div>\n\n\n<p><strong><em>Das Programm liest sich ganz nat\u00fcrlich, oder? Im <code>INTERFACE<\/code> der Klasse Dog wird beschrieben, was ein <code>Hund<\/code> so tun kann, in der <code>IMPLEMENTATION<\/code> wird genau erkl\u00e4rt, WIE er das tut und bei der <code>BENUTZUNG<\/code> der Klasse im <code>setup<\/code>() werden zwei Hunde (=<code>Objekte<\/code> vom Typ <code>Hund<\/code>) erzeugt, denen man dann verschiedene Anweisungen gibt. Ein <code>Hund<\/code> berichtet bei jedem Aufruf aus der Ich-Perspektive, was er gerade tut. Als Ergebnis sieht man im seriellen Monitor:<\/em><\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nFluffy: I am sleeping\nFluffy: I am eating\nBobby: I am eating\nFluffy: I am playing\n<\/pre><\/div>\n\n\n<p>Also ja, man versteht es schon irgendwie, aber da ist jetzt schon ziemlich wenig Kommentar&#8230;<\/p>\n\n\n\n<p><strong><em>Ertappt. Ja, das INTERFACE k\u00f6nnte Kommentare vertragen, vor allem, wenn man die Funktionen um Parameter erweitert: WIE LANGE soll der Hund schlafen, WAS soll er fressen, WOMIT spielen? Wenn man ein &#8222;echtes&#8220; Interface schreibt, dokumentiert man alles sehr gut, wie etwa zul\u00e4ssige Wertebereiche. Negative Zeitangaben sind beispielsweise definitiv unzul\u00e4ssig. Und nat\u00fcrlich m\u00fcsste man in der Dokumentation des INTERFACE von <code>Dog::sleep(unsigned int duration);<\/code> auch die physikalische Einheit der Dauer nennen (Sekunden, Stunden?). Ach ja, es sollte wohl auch ein H\u00f6chstdauer geben. Die Funktion kann auf unterschiedliche Weise damit umgehen, wenn der Aufrufer einen zu gro\u00dfen Wert angibt: Sie kann sich wehren und gar nicht &#8222;schlafen&#8220; oder sie kann den Wert auf den maximal zul\u00e4ssigen Wert k\u00fcrzen. Wie sie reagieren wird, muss im Interface dokumentiert werden. <\/em><\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><strong><em>Jetzt kommt aber der eigentliche Clou: Klassen haben die M\u00f6glichkeit, ihren ZUSTAND zu speichern. Die Funktionen (man nennt sie auch METHODEN) k\u00f6nnen diese Zust\u00e4nde PR\u00dcFEN, ihr Verhalten davon abh\u00e4ngig machen und auch die Zust\u00e4nde VER\u00c4NDERN. Die Zust\u00e4nde sind \u00fcbrigens quasi das Privateigentum der Objekte der Klasse. Ob ein Hund hungrig ist, kann der Aufrufer erst einmal nicht erfahren &#8211; es sei denn, der Hund bietet ihm daf\u00fcr eine Methode an, wie z.B. <code>boolean isHungry().<\/code> All das sieht man im folgenden Beispiel: <\/em><\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/\/ This little program demonstrates the idea of OBJECT ORIENTATION.\n\/\/ version 2\n\n\/\/ message logging to the serial port with time stamp\nvoid debug(String message) {\n    Serial.println(message);\n}\n\n\/\/ INTERFACE\n\nclass Dog {\n    \/\/ a dog can sleep, eat and play.\n    \/\/ It gets tired by playing and will be hungry after sleeping\n    \n    public:\n        Dog(String name);\n        void sleep(); \n        void eat();\n        void play();\n        boolean isHungry();\n    private:\n        bool hungry;\n        bool tired;\n        String name;\n};\n\n\/\/ IMPLEMENTATION\n\nDog::Dog(String name) {\n    this-&gt;name=name;\n    hungry=false;\n    tired=false;\n}\n\nvoid Dog::sleep() {\n    if (hungry) {\n        debug(name+&quot;: cannot sleep. Give me a bone, please!&quot;);\n    }\n    else if (tired) {\n        debug(name+&quot;: sleeping ..&quot;);\n        tired=false;\n        hungry=true;\n    }\n    else {\n        debug(name+&quot;: I am not tired&quot;);\n    }\n}\nvoid Dog::eat() {\n    if (hungry) {\n        debug(name+&quot;: yes, time for eating -- tastes good!&quot;);\n        hungry=false;\n    }\n    else {\n        debug(name+&quot;: I am not hungry&quot;);\n    }\n}\nvoid Dog::play() {\n    if (!hungry &amp;&amp; !tired)  {\n        debug(name+&quot;: yes, playing is great fun!&quot;);\n        tired=true;\n        hungry=true;\n    }\n    else {\n        debug(name+&quot;: I do not want to play now.&quot;);\n    }\n}\n\nboolean Dog::isHungry() {\n    return hungry;\n}\n\n\/\/ USAGE\n\nvoid setup() {\n    \/\/ setup serial port\n    Serial.begin(115200);\n    delay(1000);\n\n    Dog fluffy = Dog(&quot;Fluffy&quot;);\n    \n    fluffy.sleep();\n    fluffy.play();\n    fluffy.sleep();\n    debug(&quot;Fluffy might be hungry? : &quot;+String(fluffy.isHungry()));\n    fluffy.eat();\n    debug(&quot;still hungry, Fluffy? &quot;+String(fluffy.isHungry()));\n    fluffy.sleep();\n    fluffy.play();\n    fluffy.sleep();\n    fluffy.eat();\n}\n\nvoid loop() {}\n<\/pre><\/div>\n\n\n<p><strong><em>Das Prgramm erzeugt folgende Ausgabe:<\/em><\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nFluffy: I am not tired\nFluffy: yes, playing is great fun!\nFluffy: cannot sleep. Give me a bone, please!\nFluffy might be hungry? : 1\nFluffy: yes, time for eating -- tastes good!\nstill hungry, Fluffy? 0\nFluffy: sleeping ..\nFluffy: I do not want to play now.\nFluffy: cannot sleep. Give me a bone, please!\nFluffy: yes, time for eating -- tastes good!\n<\/pre><\/div>\n\n\n<p><em><strong>Damit das Programm nicht zu gro\u00df wird, verlagert man das INTERFACE in eine Datei namens <code>Dog.h<\/code> und die Implementation in eine Datei namens <code>Dog.cpp<\/code>. Um im <code>setup()<\/code> des Hauptprogramms auf die Klasse <code>Dog<\/code> Bezug nehmen zu k\u00f6nnen, muss man deren Interface \u00fcber <code>#include \"Dog.h\"<\/code> einbinden. Damit die Datei <code>Dog.cpp<\/code> korrekt \u00fcbersetzt werden kann, muss sie \u00fcbrigens auch selbst ihr eigenes Interface per <code>#include<\/code> einbinden. Die Dateien <code>Dog.cpp<\/code> und <code>Dog.h<\/code> m\u00fcssen im gleichen Verzeichnis gespeichert werden wie die <code>Dog_V2.ino<\/code> Datei. Au\u00dferdem muss man die Option &#8222;externen Editor benutzen&#8220; in der Arduino IDE aktivieren. Probiert es aus, denn bei der n\u00e4chsten Version eurer Verkehrsampel solltet ihr es genau so machen! Lest aber vorher noch einiges im Web \u00fcber Objektorientierung nach.<\/strong><\/em><\/p>\n\n\n\n<p>Anmerkung: Das Zerlegen eines Programms in mehrere einzelne Quellen, die getrennt \u00fcbersetzt werden k\u00f6nnen, ist ein universelles Prinzip, das man in praktisch allen Programmiersprachen findet. Wenn wir irgendeine Bibliothek verwenden (z.B. die Bibliothek f\u00fcr LED-Streifen), dann benutzen wir exakt dasselbe Prinzip: Unser eigenes Programm inkludiert das INTERFACE (also die Header Datei, welche die Autoren der Bibliothek ztur Verf\u00fcgung gestellt haben), beim \u00dcbersetzungsvorgang wird nicht nur unser eigener Quelltext \u00fcbersetzt, sondern auch der Quelltext der Bibliothek, und beim Bindevorgang (&#8222;Linking&#8220;) werden alle \u00fcbersetzten Teile (&#8222;verschiebbarer Objektcode&#8220;) hintereinander arrangiert und zu einem Ganzen (dem &#8222;Lademodul&#8220;) verbunden.<\/p>\n\n\n\n<p>Wenn man Programme zerlegt, werden nicht selten kleinere Ungenauigkeiten sichtbar. In unserem Hunde-Beispiel benutzt beispielsweise die Klasse <code>Dog<\/code> die Funktion <code>debug(String)<\/code>. Solange der Code der Klasse Dog sich innerhalb der Datei des Hauptprogramms befindet, ist das kein Problem, denn <code>debug<\/code> wurde gleich zu Beginn des Hauptprogramms definiert. Isoliert man<code> Dog.cpp <\/code>jedoch als separate Datei, so stellt der Compiler beim \u00dcbersetzen dieser Datei fest, dass ihm <code>debug(..)<\/code> unbekannt ist. Man k\u00f6nnte dies heilen, in dem man eine weitere Datei &#8222;<code>Debug.h<\/code>&#8220; schafft, dort die Funktion deklariert und diese neue Header-Datei sowohl im Haputprogramm inkludiert, als auch in <code>Dog.cpp<\/code>. Wir haben bei unserem kleinen Umbau das Problem anders gel\u00f6st: <code>Dog<\/code> hat eine eigene Auskunftsfunktion namens <code>void tell(String text);<\/code> bekommen und benutzt nun diese.<\/p>\n\n\n\n<div class=\"wp-block-file\"><a href=\"https:\/\/followthescore.org\/schueler-labor\/wp-content\/uploads\/2021\/01\/Dog_V3.zip\">Dog_V3<\/a><a href=\"https:\/\/followthescore.org\/schueler-labor\/wp-content\/uploads\/2021\/01\/Dog_V3.zip\" class=\"wp-block-file__button\" download>Die Version mit den aufgteilten Dateien herunterladen<\/a><\/div>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Zur\u00fcck zu unserer Ampel: Wir wollen eine Klasse namens<code> TrafficLight <\/code>schaffen und in einen separate Datei <code>TrafficLight.cpp<\/code> auslagern. Das Interface packen wir in <code>TrafficLight.h<\/code> und inkludieren es sowohl im Hauptprogramm als auch in der Implementierung der Klasse.<br><br>Auf diese Weise ensteht die &#8222;obere&#8220; Schicht in unserer Software, n\u00e4mlich der Code in der *.ino-Datei und die &#8222;untere&#8220; Schicht steckt in der Klasse <code>TrafficLight<\/code>. Wenn wir diese gut strukturieren, dann gibt es auch dort wieder Funktionen wie red(), green() oder <code>off()<\/code>, die etwas abstrakter sind und andere &#8222;niedrige&#8220; Funktionen, die sich darum k\u00fcmmern, dass die richtigen LEDs angesprochen werden. Bei der Software-Entwicklung ist es ein wichtiges Ziel, technische Details nach unten zu verlagern, damit der Programmcode auf den h\u00f6heren Ebenen besser verst\u00e4ndlich ist.<\/p>\n\n\n\n<p>In der n\u00e4chsten Version der Software wollen wir auch Funktionen f\u00fcr das Ein- und Ausschalten der Ampelanlage hinzuf\u00fcgen.<\/p>\n\n\n\n<p><strong>Weiter zur Version 3 der Verkehrsampel&#8230;<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In diesem Programm sind die abstrakteren Konzepte (Traffic light, state) von den niedrigen Ebenen (lamp, LEDs) getrennt. Ja, so ist es viel besser! Fr\u00fcher dachte man, dass ein Programm besonders gut ist, wenn es viele Kommentare enth\u00e4lt. Man hat sogar Programme geschrieben, die andere Programme analysieren und den Anteil der Kommentarzeilen ermitteln. Gut bezahlt wurde&hellip; <a class=\"more-link\" href=\"https:\/\/followthescore.org\/schueler-labor\/verkehrsampel-v2\/\"><span class=\"screen-reader-text\">Verkehrsampel V2<\/span> weiterlesen<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/followthescore.org\/schueler-labor\/wp-json\/wp\/v2\/posts\/431"}],"collection":[{"href":"https:\/\/followthescore.org\/schueler-labor\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/followthescore.org\/schueler-labor\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/followthescore.org\/schueler-labor\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/followthescore.org\/schueler-labor\/wp-json\/wp\/v2\/comments?post=431"}],"version-history":[{"count":18,"href":"https:\/\/followthescore.org\/schueler-labor\/wp-json\/wp\/v2\/posts\/431\/revisions"}],"predecessor-version":[{"id":483,"href":"https:\/\/followthescore.org\/schueler-labor\/wp-json\/wp\/v2\/posts\/431\/revisions\/483"}],"wp:attachment":[{"href":"https:\/\/followthescore.org\/schueler-labor\/wp-json\/wp\/v2\/media?parent=431"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/followthescore.org\/schueler-labor\/wp-json\/wp\/v2\/categories?post=431"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/followthescore.org\/schueler-labor\/wp-json\/wp\/v2\/tags?post=431"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}