From c8e475d90d9a22afa4e9420789f063d7fee34809 Mon Sep 17 00:00:00 2001 From: Nathan Hendler Date: Mon, 18 Jul 2022 07:21:48 -0700 Subject: [PATCH] Context/App ISS/Weather switching example --- arduino-lib/Apple2Idiot/A2I_commands.h | 5 +- .../15_fixed_essid_weather-arduino.ino | 187 ++++++++++++ .../weather_commands.h | 13 + .../15_fixed_essid_weather-basic/WEATHER.BAS | 6 +- .../make-disk.bash | 2 +- .../apple2-cc65/src/globals.h | 10 +- .../apple2-cc65/src/main.c | 3 + .../apple2-cc65/src/menu.c | 4 +- .../apple2-cc65/weather.apple2 | Bin 6008 -> 6036 bytes .../apple2-cc65/weather.dsk | Bin 143360 -> 143360 bytes .../20-multiapp-arduino.ino | 164 ++++++++++ examples/20-multiapp-arduino/README.md | 7 + examples/20-multiapp-arduino/a2i_chess.cpp | 281 ++++++++++++++++++ examples/20-multiapp-arduino/a2i_chess.h | 66 ++++ examples/20-multiapp-arduino/a2i_iss.cpp | 92 ++++++ examples/20-multiapp-arduino/a2i_iss.h | 43 +++ examples/20-multiapp-arduino/a2i_weather.cpp | 119 ++++++++ examples/20-multiapp-arduino/a2i_weather.h | 47 +++ examples/20-multiapp-arduino/chess_commands.h | 38 +++ .../20-multiapp-arduino/credentials.h.sample | 12 + examples/20-multiapp-arduino/iss_commands.h | 9 + .../oldino.foo} | 0 .../20-multiapp-arduino/weather_commands.h | 13 + 23 files changed, 1110 insertions(+), 11 deletions(-) create mode 100644 examples/15_fixed_essid_weather-arduino/15_fixed_essid_weather-arduino.ino create mode 100644 examples/15_fixed_essid_weather-arduino/weather_commands.h create mode 100644 examples/20-multiapp-arduino/20-multiapp-arduino.ino create mode 100644 examples/20-multiapp-arduino/README.md create mode 100644 examples/20-multiapp-arduino/a2i_chess.cpp create mode 100644 examples/20-multiapp-arduino/a2i_chess.h create mode 100644 examples/20-multiapp-arduino/a2i_iss.cpp create mode 100644 examples/20-multiapp-arduino/a2i_iss.h create mode 100644 examples/20-multiapp-arduino/a2i_weather.cpp create mode 100644 examples/20-multiapp-arduino/a2i_weather.h create mode 100644 examples/20-multiapp-arduino/chess_commands.h create mode 100644 examples/20-multiapp-arduino/credentials.h.sample create mode 100644 examples/20-multiapp-arduino/iss_commands.h rename examples/{15_fixed_essid_weather-arduino/15_fixed_essid_weather-cc65.ino => 20-multiapp-arduino/oldino.foo} (100%) create mode 100644 examples/20-multiapp-arduino/weather_commands.h diff --git a/arduino-lib/Apple2Idiot/A2I_commands.h b/arduino-lib/Apple2Idiot/A2I_commands.h index c3d1cf4..60ef6f3 100644 --- a/arduino-lib/Apple2Idiot/A2I_commands.h +++ b/arduino-lib/Apple2Idiot/A2I_commands.h @@ -37,8 +37,9 @@ # PROGRAM IDS # ################################################*/ -#define APP_WEATHER 200 // Unimplemented -#define APP_CHESS 201 // Unimplemented +#define APP_WEATHER 200 +#define APP_CHESS 201 +#define APP_ISS 202 /*################################################ diff --git a/examples/15_fixed_essid_weather-arduino/15_fixed_essid_weather-arduino.ino b/examples/15_fixed_essid_weather-arduino/15_fixed_essid_weather-arduino.ino new file mode 100644 index 0000000..b18df07 --- /dev/null +++ b/examples/15_fixed_essid_weather-arduino/15_fixed_essid_weather-arduino.ino @@ -0,0 +1,187 @@ +/* +Use this program with the Apple2idIOT card and the basic programs RRAM, WRAM and CMDROT to read/write and rot13 +a single string contained within the dual port ram on the card. +CA = 49664 +AA = CA + 1 +*/ + +// Load Wi-Fi library +#include +#include +#include +#include + +Apple2Idiot a2i = Apple2Idiot(); + +#define COMMAND_SET_COUNTRY 20 +#define COMMAND_SET_CITY 21 +#define COMMAND_FETCH_WEATHER 25 + +#include "credentials.h" + +//const char* wifi_ssid = "HotelMcCoy-Guest"; +//const char* wifi_password = "travelforall"; +char wifi_ssid[] = WIFI_SSID; // your network SSID (name) +char wifi_password[] = WIFI_PASSWORD; // your network password + +/*******************/ +/* Weather Service */ +/*******************/ + +const String weather_service_api_key= "0ab97bbbea58592d7c9d64067a34d2d0"; +const String weather_url = "http://api.openweathermap.org/data/2.5/weather?"; + +String country_code = "US"; +String city_name = "Tucson"; + +/*******************/ +/* Misc */ +/*******************/ + +const long readLoopInterval = 100; // millis +unsigned long lastReadLoopTime = 0; + +byte lastAppleCommand = 0; + +/*################################################ +# Setup # +################################################*/ + +void setup() { + Serial.begin(115200); + + Serial.println("Starting 15_fixed_essid_weather-cc65.ino after a quick delay so that we have time to connect a serial monitor after restart and see what's going on"); + delay(3000); + Serial.println("Starting 15_fixed_essid_weather-cc65.ino"); + + a2i.init(); + + Serial.println(""); + Serial.println("Starting wifi..."); + Serial.print(" connecting to: "); + Serial.println(wifi_ssid); + + WiFi.begin(wifi_ssid, wifi_password); + while (WiFi.status() != WL_CONNECTED) { + delay(600); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected successfully"); + Serial.print("Got IP: "); + Serial.println(WiFi.localIP()); //Show ESP32 IP on serial + + Serial.println("Setup done"); +} + +/*################################################ +# Functions # +################################################*/ + +byte fetch_weather() { + + byte result = 0; + HTTPClient http; + //const String request_url = weather_url + "q=Tucson,us&APPID=" + weather_service_api_key; + const String request_url = weather_url + "q=" + city_name + "," + country_code + "&APPID=" + WEATHER_ACCESS_TOKEN; + Serial.println(request_url); + http.begin(request_url); + int httpCode = http.GET(); //Make the request + delay(10); + + if (httpCode > 0) { //Check for the returning code + String payload = http.getString(); + //Serial.println(httpCode); + Serial.println("++++++++++++++++++++++++"); + Serial.println(payload); + Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<200> filter; + filter["weather"][0]["main"] = true; + filter["weather"][0]["description"] = true; + filter["main"]["humidity"] = true; + filter["main"]["temp"] = true; + filter["wind"]["speed"] = true; + filter["wind"]["deg"] = true; + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload, DeserializationOption::Filter(filter)); + //DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + } else { + Serial.println("----------------------"); + serializeJsonPretty(doc, Serial); + Serial.println("----------------------"); + serializeJsonPretty(doc["wind"], Serial); + Serial.println("----------------------"); + String temp = doc["main"]["temp"]; + String humidity = doc["main"]["humidity"]; + String wind_speed = doc["wind"]["speed"]; + String wind_deg = doc["wind"]["deg"]; + String weather_description1 = doc["weather"][0]["main"]; + String weather_description2 = doc["weather"][0]["description"]; + int address_counter = a2i.write_string_to_shared_ram(temp, SHARED_RAM_START_ADDRESS); + address_counter = a2i.write_string_to_shared_ram(humidity, address_counter + 1); + address_counter = a2i.write_string_to_shared_ram(wind_speed, address_counter + 1); + address_counter = a2i.write_string_to_shared_ram(wind_deg, address_counter + 1); + address_counter = a2i.write_string_to_shared_ram(weather_description1, address_counter + 1); + address_counter = a2i.write_string_to_shared_ram(weather_description2, address_counter + 1); + } + result = ACK; + } + else { + Serial.println("Error on HTTP request"); + result = ERR; + } + + http.end(); //Free the resources + return result; +} + + +/*################################################ +# Main # +################################################*/ + +void loop() { + + if ((millis() - lastReadLoopTime) > readLoopInterval) { + byte command_byte = a2i.read_data(APPLE_COMMAND_ADDRESS); + if (command_byte == RAM_BUSY) { + Serial.println("Command Read: RAM BUSY"); + } + else if (command_byte != lastAppleCommand){ + byte result = 0; + Serial.print("Command Switch command_byte: "); + Serial.println(command_byte); + switch(command_byte) { + case COMMAND_SET_COUNTRY: + Serial.println("COMMAND_SET_COUNTRY"); + a2i.write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + country_code = a2i.read_string_from_ram(SHARED_RAM_START_ADDRESS); + Serial.println("Received: ["+country_code+"]"); + a2i.write_data(APPLE_COMMAND_ADDRESS, ACK); + a2i.write_data(ESP_COMMAND_ADDRESS, EOT); // notify Apple IIe we are done processing command byte + break; + case COMMAND_SET_CITY: + Serial.println("COMMAND_SET_CITY"); + a2i.write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + city_name = a2i.read_string_from_ram(SHARED_RAM_START_ADDRESS); + Serial.println("Received: ["+city_name+"]"); + a2i.write_data(APPLE_COMMAND_ADDRESS, ACK); + a2i.write_data(ESP_COMMAND_ADDRESS, EOT); // notify Apple IIe we are done processing command byte + break; + case COMMAND_FETCH_WEATHER: + Serial.println("COMMAND_FETCH_WEATHER"); + a2i.write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + result = fetch_weather(); + a2i.write_data(APPLE_COMMAND_ADDRESS, ACK); + a2i.write_data(ESP_COMMAND_ADDRESS, result); // notify Apple IIe we are done processing command byte + break; + } + lastAppleCommand = command_byte; + } + lastReadLoopTime = millis(); + } +} diff --git a/examples/15_fixed_essid_weather-arduino/weather_commands.h b/examples/15_fixed_essid_weather-arduino/weather_commands.h new file mode 100644 index 0000000..2d06744 --- /dev/null +++ b/examples/15_fixed_essid_weather-arduino/weather_commands.h @@ -0,0 +1,13 @@ +#ifndef A2I_CHESS_COMMANDS_H +#define A2I_CHESS_COMMANDS_H + +/* Apple II <-> ESP Commands */ +#define COMMAND_SET_COUNTRY 20 +#define COMMAND_SET_CITY 21 +#define COMMAND_FETCH_WEATHER 25 + +/* Responses */ +//#define CHESS_INVALID_MOVE 123 +//#define CHESS_VALID_MOVE 124 + +#endif diff --git a/examples/15_fixed_essid_weather-basic/WEATHER.BAS b/examples/15_fixed_essid_weather-basic/WEATHER.BAS index d14e459..17ff823 100644 --- a/examples/15_fixed_essid_weather-basic/WEATHER.BAS +++ b/examples/15_fixed_essid_weather-basic/WEATHER.BAS @@ -23,15 +23,15 @@ 1010 WS$ = CC$ 1020 WA = AA +1 1030 GOSUB 10000 -1040 POKE AA,200 : REM SET COUNTRY +1040 POKE AA,20 : REM SET COUNTRY 1050 GOTO 100 2000 INPUT "CITY: ";CITY$ 2010 WS$ = CITY$ 2020 WA = AA +1 2030 GOSUB 10000 -2040 POKE AA,201 : REM SET CITY +2040 POKE AA,21 : REM SET CITY 2050 GOTO 100 -3000 POKE AA,205 : REM FETCH WEATHER +3000 POKE AA,25 : REM FETCH WEATHER 3010 GOTO 100 4000 HOME 4005 MAXSTR = 0 diff --git a/examples/15_fixed_essid_weather-basic/make-disk.bash b/examples/15_fixed_essid_weather-basic/make-disk.bash index 139439f..480de1c 100644 --- a/examples/15_fixed_essid_weather-basic/make-disk.bash +++ b/examples/15_fixed_essid_weather-basic/make-disk.bash @@ -1,4 +1,4 @@ -diskname='15_fixed_essid_weather-cc65.dsk' +diskname='15_fixed_essid_weather-basic.dsk' echo "-------------------------" echo "Files on disk image (before):" diff --git a/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/globals.h b/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/globals.h index ce9ed9c..5131623 100644 --- a/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/globals.h +++ b/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/globals.h @@ -15,8 +15,12 @@ #define APPLE_COMMAND_ADDRESS 0xC201 #define RAM_DATA_START_ADDRESS 0xC202 -#define COMMAND_SET_COUNTRY 200 -#define COMMAND_SET_CITY 201 -#define COMMAND_FETCH_WEATHER 205 +#define APP_ID 200 + +#define COMMAND_SET_COUNTRY 20 +#define COMMAND_SET_CITY 21 +#define COMMAND_FETCH_WEATHER 25 + +#define DEFAULT_COUNTRY_CODE "US" #endif diff --git a/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/main.c b/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/main.c index 6133030..7480aed 100644 --- a/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/main.c +++ b/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/main.c @@ -23,9 +23,12 @@ unsigned int address_offset = 0; void main(void) { + unsigned char key; clrscr(); + write_byte(APPLE_COMMAND_ADDRESS, APP_ID); + while (key != ASCII_5) { // Last menu item should be Quit drawMenuBar(); gotoxy(0,2); diff --git a/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/menu.c b/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/menu.c index b40c70a..be96d40 100644 --- a/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/menu.c +++ b/examples/15_fixed_essid_weather-cc65/apple2-cc65/src/menu.c @@ -7,8 +7,8 @@ #include "menu.h" #define MENU_LENGTH 5 -#define MENU_WORD_LENGTH 8 -unsigned char menuEntries[MENU_LENGTH][MENU_WORD_LENGTH] = {"Country", "City", "Fetch", "Display", "Quit"}; +#define MENU_WORD_LENGTH 9 +unsigned char menuEntries[MENU_LENGTH][MENU_WORD_LENGTH] = {"Country ", "City ", "Fetch ", "Display ", "Quit"}; void drawMenuBar() { static unsigned char i; diff --git a/examples/15_fixed_essid_weather-cc65/apple2-cc65/weather.apple2 b/examples/15_fixed_essid_weather-cc65/apple2-cc65/weather.apple2 index 1301a3f4dc8e57d8ef28558f33fc9a39adb4f6b3..7e86be96bb579dce5b01edc84c24b8e908d69814 100644 GIT binary patch delta 3615 zcmai1e{2)?6+b(%;~%llb`txX;LG<*OApewm3S;NR&*W-w|R)AwT+w zKLRV!j3hsr1S(OZF_A*2gD3${CK<#Q8V|k2(v004b1`SG9SRhp(>}1??^hk@&48A} zU9-(oX^t5>;F#dh1L=&ZFD9Bu?0X=Ptt6Yn723IVcF{5uB}dmd?ITLFGK(P-wwpqb zosqy?AQb8%K=_Ax(TmY;L%d!ER9>hH?p4RY2+*imXt_G0L;I7R!w%6prvOW0v+0W@ zpWHc*$PX zqx@@K4_c)^F(FqWT3moq&@_?ii`{ox1jQZZ=LQ@WPDUgc}bs3Q7 zoa%BQ?>p5xAQzl!J&^MbP$t)Pa-k<}^YH38TJPhS0S&Qp0J{=qGmBaDVc~2ppGr|Z zCz6`C7&R3`HO#ZQCT;g?Q#ezbhlM*eC!A3n2#4o|GsJQ^Xnqo8TGiq1RyI8dDpK^` z7MzJpROd!6niu(Avx4));DnzFCC`PFsZi!zh&U$h^nS~i^o9W@E9$fBD@e83&$La2 z_^D{9yX{BFqA&$A}0IoAKr`1U+wvU<=s`X!;wQYp!Vl5+`gvUee_8gR)BCTa3_(EV&@lCD6gV6HIa*EMg0rg9sp7BoyE?QnBf$>1=dok4hT z2N?sNf{N*S;!^ZI;LuMEU$aWlW+K$5ok74Qs$-bePvDG%;Eenp^dFSzos=kvV%qK? z9WrQKzKyue#^~A^jG%XX{1-@`PDg;Gvv#zW0TEe?)aby40nL12e?)QM$9W4zCurFs zkrRtD3|GUZ`Wyabj`xq4|F|!r&{*@LO*CgNAgqV!nz0#iUp?k8lh^MFhV-?TaMYT5D8 zL4K*Q#r9mZhD8N*T60R4v1zogAKt(bb&+Nt9UylUpb@6IuXtoiavrqe3D1AvL6v4n zT5&e0E&(PFXFVzc#)h+b>LZ#xX~$s3{lOz47}nM_EJg>C4y-OV{6S^%yr`Atoj93~ z%X?h7$Bn^jwN|35&e5yZ^;|gV#=<6hC`#*|>%2W)EcudUSi(uDO@LNk(A-dK-iKjp z7GQ3JPkk|)tF~%YuqgnSSwafnG)qg89*ow~hxgPvk9ly`+T$bL5FO|b(QO1QotH6pI4_^x75o@vn_k?kS;>4FKa9YX7M4BU0 z>90@o`M4YIPOMn5mV;CpeLw@7B`o;o7n4dZ!Eo3ak$O2c9`EgbIVRk2RIe7N&&Ec~ zBDHt--aQdz8Jk&7UX4l}o2e&^n9D74G@@dEizIT*1N90pcrX7cdR8Nt8W`G-CWTU0F!+v-d!n(`X`W((JMa#zvas{jki{u7bA&%qsw5N9P$akKF9!8|&?W?vc_Q z`d6Xo;I8=4_L8&H8lz5Yg$i-3RN=Vp7~6CE5?YG5(LW)N7efHFg<-ZXj4wj#ZD@ny?wb>LBhUg}ceNI0C_j=EYFxlUZvYCt7 znrQ6a+LdmJNXtG_LRtI7gmwc)6BQZD2(NoeSFFU!5;jvwdYg(3?PA(il8L3C5y(BA z7)_8TiJ>m^bjh)m7=Xwo?u_tPPZQG_9C-e*~ac#i#WjvLD*Jb2o2sROt*v% z@A8H=1|}2NRm9g|;)Acn$)z~eJ@f{@3@4Z4L1=W8?afs=%>e^isl%Ci9N}_k;hcA& z)mt3zMQwu9w+%A;k?;Gt;B8!(@~w-WCnH)fyK;oh472E$MFg>>OUsj|`9rMcN+p=K zLoBe}ZHHO@FbkD!1Z*HW%!=mG7=MJ75B0^yfg5IJwlCI#tRCeMn>oxz5G1x$Pt2U% z*j@W5NpWSiC|QH$zer{PykKOF{GQH@JPF%IJOhajb*J7))?vP0_;W!x|F=OokGtiD zOyu$3mlzVtf~v;pmiW4s zcvE*oHZ?>j%3UIy@|Q8qQYp8BjYz?_V+W72Lv>{`yjci##Gs=mu6H?i;zIHBdH zb`G`2W1YJsTK^@7#@WhUC*hJeiKX$M@BM|mNmvyqb8luNaxA_){=>+#rH_~X7jB&+ A?*IS* delta 3682 zcma)93v3kU5#Brd?0fjU=R5nnXSnq&;bkD;K%j1_JRoW1S{vM?DguGhEsw?)aY+Jc zNgC;n$CU2**S_rD($;y^&rlTQ!~f;@@237trq_UC^?qK@gPx}lDTY|yu%U2?Cb=l z1EHXa01^ME7poY%+#p(2fCa^t;6Zh4j0laIf|hGB7PP;5c+RWjO)J3S_*D7|$tQQ( zEAob*wlW}T%d4DAV*H37$_r=4)Pm}4)YLAhEFro@FwE9zoh!x8Y42Hj*c|JP1N_CX zC3zh;iqNXQR@&sD3uucP>gC6Mt5Rwa=`EA*MWN^H?j)bwikGI2%Qy=7w`o@mw^8 zmipP)qt+K^Sd-N^T0d%hX@)UbeY-K41`mOcOj+j=;AHjf)-#N+PZ{4Q#$@$>7{{h+ z_HVV^?hdiW(q$)%pKSAwtbS+DL3Sru#{r|vLM!V4Xg{~L?Be6e+sbIWZINA3Tl9=A zvg@Se#)w+8Ewi(?)hcX}os(vRdnBZNBJ6`@ti&Oc$>EoJVY@v~?OBF{)n+h{k1eMb zS~7G=mN9V%9%h?OdLck)x-c3hEAbL)>X*Q9!7&nPb0L433=Riu!nv#ufj!bGteB;z zJOP`LgLiFTQ%W0%%o=Zm#4J@94KnrT@r*?AjQkPxG0Y5^)k&37DW+ivEXb%adI|U+ z*`sSyM1WLK`~tMelz=3a@xYS|ipW}_(j6lmrgNlI&__YM3XrC?t14jU;Kfdc53$|t zRac#f_I~H5hXwsMm_<-a^Ue@q^9cKO5TA*?jr(9etvJ%{sbsnx-R@8BZ%TJmU9D_g zVtD%(f=B)!e@Sc{M#tgE9T2oTDliS>(CWCtpJ2MXdX6B006lsW%zL95FmDTpME7!i zK5KXsaD9HlAfWw8_-7b?M|UUrg3@V=7fY)Z*U5J3$Yn?d?$#yR8xO;q;Y3GcCm~&p zM>I-)#qG^6X3wO%#1eUjdtEHVf{RX>KGijF84kDOSFvEsGd<}J@&peJA(~$t&?wb+ z^coBXCh*1Mrc1g8nTW9%nd^`V7=X-|kjXb5Gdb zu=@enNQ*Z?%D{q_ThKBF=yj=yHw9FkrUykt?-od7AaD0XW4A5O^NU)>LA@h+ITa+V zjfXb2n1$E^@ebfVybF9y(Ab*EHR{dBajzgQrwTgabgGM`uK=E=f4{HN*ZUP@@>=eY zZYW;(7fp*(BT z`O?;-L!uHul{#a$>9Kb~3<72700Cup#Y99b;3Pk%+ek|KZo!uYoIVD}Zuq(WJgIYbnc^t1}fBEq5X#(_zM%LnitJSH~f$`p9dADzwYfz2yWpb}?_rkpB5M`Fsqz0I1tE&e8 zmI;E$@5P=(-yW7yPTXc?CIf~VXVgHm&0jVu*Z20mo)OHL!#L~HO|kuMaip{Fz= z-5ouha6&HkMw;Fvn9ogVO-{#x6oS> zry2tBzy(IhCl3#~dL1vdJW{6Dx-2SQemDs0XAMo7j4^2z>%_5M#3d}poF}&}X?7Dg z*3D8a!vIjjFx%!N=D|P#G*O)59OT(9PuF*b5aS{-Q06f40BQ` z*M+^_!nWobyy?Y3t}|R;gcJDjLV2QSPToW$(F*J2Il=9ioD_UD7rmwB`@!1S1qM3p z%IgQ&z7`hVFCmCsO=i9}DIQ==e|M5;IKU#?+>m6&B#XtZMrrIUJPJVe!)*iOZ6S642K!OFVT?r94oD6@!R8+ncKayXICQL uxJRX}AA``#F4=P$KiX=gJn`QC-)gI6p>%%X8dlKai4}=o3aiTJl>ZmbU1D+o diff --git a/examples/15_fixed_essid_weather-cc65/apple2-cc65/weather.dsk b/examples/15_fixed_essid_weather-cc65/apple2-cc65/weather.dsk index c4bf06cf70ce53db14fb6db044c9f9d6edbd78ba..d81f16014be3909eda2ab5b43e595932da4a587f 100644 GIT binary patch delta 3873 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 +#include +//#include +//#include +#include "credentials.h" +#include "a2i_weather.h" +#include "a2i_iss.h" + +#define AUTO_CONNECT_TO_WIFI 1 +#define AUTO_CONNECT_TIMEOUT 5 + +Apple2Idiot a2i = Apple2Idiot(); +//HTTPClient http; + +/*################################################ +# Applications we're going to support # +# +# An Apple ][ running this card may want to run +# several different "apps" that utilize the card. +# These "apps" are classes (instantiated below) +# which the main loop uses to handle/manage +# communication with the Apple. For example, the +# card may want to handle requests to support +# programs on the Apple ][ such as a wifi +# selector, a chess game, and a weather lookup +# app. +################################################*/ + +//Chess chess_app = Chess(); +Weather weather_app = Weather(); +Iss iss_app = Iss(); + +#define N_APPS 2 +//byte app_ids[N_APPS] = {APP_WEATHER, APP_ISS}; +byte app_ids[N_APPS] = {APP_ISS, APP_WEATHER}; + +/*******************/ +/* Variables */ +/*******************/ + +byte current_app_id; + +const long mainLoopInterval = 100; // millis +//const long mainLoopInterval = 10000; // millis +unsigned long lastMainLoopTime = 0; +byte lastAppleCommand = 0; + +/*################################################ +# Setup # +################################################*/ + +void setup() { + Serial.begin(115200); + + a2i.init(); + //chess_app.init(&a2i, &http); + //weather_app.init(&a2i, &http); + +#ifdef AUTO_CONNECT_TO_WIFI + Serial.println(""); + Serial.print("Starting wifi, connecting to: "); + Serial.println(WIFI_SSID); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + byte wifi_loop_count = 0; + while (WiFi.status() != WL_CONNECTED) { + delay(600); + wifi_loop_count++; + Serial.print("."); + if (wifi_loop_count > AUTO_CONNECT_TIMEOUT) { + break; + } + } + if (WiFi.status() == WL_CONNECTED) { + Serial.println(""); + Serial.println("WiFi connected successfully"); + Serial.print("Got IP: "); + Serial.println(WiFi.localIP()); //Show ESP32 IP on serial + } else { + Serial.println(""); + Serial.println("WiFi connection failed."); + } +#else + // Set WiFi to station mode and disconnect from an AP if it was previously connected + Serial.println("Wifi autoconnect not enabled. No wifi connection attempted."); + WiFi.mode(WIFI_STA); + WiFi.disconnect(); +#endif + + Serial.println("Setup done"); + current_app_id = app_ids[0]; + +} + +/*################################################ +# Main # +################################################*/ + +void loop() { + + if ((millis() - lastMainLoopTime) > mainLoopInterval) { + //Serial.print("."); + byte command_byte = a2i.read_data(APPLE_COMMAND_ADDRESS); + if (command_byte == RAM_BUSY) { + Serial.println("Command Read: RAM BUSY"); + } + else if (command_byte != lastAppleCommand){ + lastAppleCommand = command_byte; + byte result = 0; + Serial.print("Handling command_byte: "); + Serial.println(command_byte); + + /* Do we need to switch app context? */ + for (int i=0; i +#include +#include +#include +#include +#include +#include "chess_commands.h" +#include "a2i_chess.h" + + +void Chess::init(Apple2Idiot *a2ip, HTTPClient *httpp) { + a2i = a2ip; + http = httpp; + //strcpy(game_string, "a2a3e7e5e2e4"); +} + +byte Chess::handleCommand(byte command) { + switch(command) { + case CHESS_MAKE_MOVE: { + a2i->write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + Serial.println("CHESS() MAKE_MOVE"); + String move_string; + move_string = a2i->read_string_from_ram(SHARED_RAM_START_ADDRESS); + Serial.println("Received move: ["+move_string+"]"); + byte result = makeMove(move_string); + int address_counter = a2i->write_string_to_shared_ram(last_ai_move, SHARED_RAM_START_ADDRESS); + getBoard(); + for (int i=0; i<9; i++) { + address_counter = a2i->write_string_to_shared_ram(game_board[i], address_counter + 1); + } + a2i->write_data(ESP_COMMAND_ADDRESS, result); + a2i->read_ram(11); + break; + } + case CHESS_NEW_GAME: { + a2i->write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + Serial.println("CHESS_NEW_GAME"); + strcpy(game_string, ""); + break; + } + default: { + return COMMAND_NOT_FOUND; + } + } +} + +byte Chess::makeMove(String move_string) { + /* Apple says MAKE_MOVE and sends "a2a3" + * + * The basic flow... + * + * validateMove() We check that it's valid. + * If it is we accept the move and add it to game_string + * getGameStatus() Then we check the status and return (did someone win or lose?) + * getAIMove() Then we get the AI move. + * Then we check the status and return (did someone win or lose?) + */ + if (validateMove(move_string) == CHESS_VALID_MOVE) { + strcat(game_string, move_string.c_str()); + } else { + return CHESS_INVALID_MOVE; + } + char* game_status; + //b = getGameStatus(game_string); + game_status = getGameStatus(game_string); + Serial.print("after player move game_status:"); Serial.println(game_status); + + if (strcmp(game_status, "in_progress") == 0) { + //char* ai_move; + //ai_move = getAIMove(); + //strcat(game_string, ai_move); + //last_ai_move = getAIMove(); + strcpy(last_ai_move, getAIMove()); + strcat(game_string, last_ai_move); + game_status = getGameStatus(game_string); + Serial.print("after AI move game_status:"); Serial.println(game_status); + if (strcmp(game_status, "in_progress") == 0) { return STATUS_IN_PROGRESS; } + else if (strcmp(game_status, "black_won") == 0) { return STATUS_BLACK_WON; } + else if (strcmp(game_status, "white_won") == 0) { return STATUS_WHITE_WON; } + else if (strcmp(game_status, "white_won_resign") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "black_won_resign") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "stalemate") == 0) { return STATUS_STALEMATE; } + else if (strcmp(game_status, "insufficient_material") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "fifty_rule_move") == 0) { return STATUS_FIFTY_RULE_MOVE; } + else if (strcmp(game_status, "threefold_repitition") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "unknown") == 0) { return STATUS_UNKNOWN; } else { return STATUS_ERROR; } + } + else if (strcmp(game_status, "black_won") == 0) { return STATUS_BLACK_WON; } + else if (strcmp(game_status, "white_won") == 0) { return STATUS_WHITE_WON; } + else if (strcmp(game_status, "white_won_resign") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "black_won_resign") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "stalemate") == 0) { return STATUS_STALEMATE; } + else if (strcmp(game_status, "insufficient_material") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "fifty_rule_move") == 0) { return STATUS_FIFTY_RULE_MOVE; } + else if (strcmp(game_status, "threefold_repitition") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "unknown") == 0) { return STATUS_UNKNOWN; } else { return STATUS_ERROR; } +} + +char* Chess::getGameStatus(char* game_string) { + Serial.print("getGameStatus() "); Serial.println(game_status); + + char api_request[MAX_STR_LEN]; + sprintf(api_request, "%s/status/%s", api_entry_point, game_string); + Serial.print(" "); Serial.println(api_request); + http->begin(api_request); + int httpCode = http->GET(); //Make the request + delay(10); + if (httpCode > 0) { //Check for the returning code + Serial.println(" Success on HTTP request"); + String payload = http->getString(); + //Serial.println("++++++++++++++++++++++++"); + //Serial.println(payload); + //Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F(" deserializeJson() failed: ")); + Serial.println(error.f_str()); + return "error"; + } else { + Serial.println("----------------------"); + serializeJsonPretty(doc, Serial); + Serial.println(); + Serial.println("----------------------"); + //return doc["gameStatus"]; + return (char *)doc["gameStatus"].as(); + } + } else { + Serial.println("Error on HTTP request"); + return "error"; + } + // Don't know how we could get here without it being an error. + + return "error"; +} + +char* Chess::getAIMove() { + Serial.print("getAIMove() "); Serial.println(game_status); + + char api_request[MAX_STR_LEN]; + sprintf(api_request, "%s/next_best/%s", api_entry_point, game_string); + Serial.print(" "); Serial.println(api_request); + http->begin(api_request); + int httpCode = http->GET(); //Make the request + delay(10); + if (httpCode > 0) { //Check for the returning code + Serial.println(" Success on HTTP request"); + String payload = http->getString(); + //Serial.println("++++++++++++++++++++++++"); + //Serial.println(payload); + //Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F(" deserializeJson() failed: ")); + Serial.println(error.f_str()); + return "jsonerror"; + } else { + Serial.println("----------------------"); + serializeJsonPretty(doc, Serial); + Serial.println(); + Serial.println("----------------------"); + //return doc["gameStatus"]; + return (char *)doc["bestNext"].as(); + } + } else { + Serial.println("Error on HTTP request"); + return "geterror"; + } + // Don't know how we could get here without it being an error. + + return "unknownerror"; +} + +byte Chess::validateMove(String move_string) { + Serial.print("validateMove() "); Serial.println(move_string); + char api_request[MAX_STR_LEN]; + sprintf(api_request, "%s/valid_move/%s%s", api_entry_point, game_string, move_string); + Serial.print(" "); Serial.println(api_request); + http->begin(api_request); + int httpCode = http->GET(); //Make the request + delay(10); + if (httpCode > 0) { //Check for the returning code + Serial.println(" Success on HTTP request"); + String payload = http->getString(); + //Serial.println("++++++++++++++++++++++++"); + //Serial.println(payload); + //Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return ERR; + } else { + Serial.println("----------------------"); + serializeJsonPretty(doc, Serial); + Serial.println(); + Serial.println("----------------------"); + bool validMove = doc["validMove"]; + if (validMove) { + // good move + Serial.println(" Valid move!"); + return CHESS_VALID_MOVE; + } else { + Serial.println(" Invalid move!"); + return CHESS_INVALID_MOVE; + } + } + } else { + Serial.println("Error on HTTP request"); + return ERR; + } + // Don't know how we could get here without it being an error. + return ERR; +} + +void Chess::removeSubstr (char *string, char *sub) { + char *match = string; + int len = strlen(sub); + while ((match = strstr(match, sub))) { + *match = '\0'; + strcat(string, match+len); + match++; + } +} + +void Chess::getBoard() { + Serial.println("getBoard() "); + char api_request[MAX_STR_LEN]; + sprintf(api_request, "%s/board_string/%s", api_entry_point, game_string); + Serial.print(" "); Serial.println(api_request); + http->begin(api_request); + int httpCode = http->GET(); //Make the request + delay(10); + if (httpCode > 0) { //Check for the returning code + Serial.println(" Success on HTTP request"); + String payload = http->getString(); + //Serial.println("++++++++++++++++++++++++"); + //Serial.println(payload); + //Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + } else { + //Serial.println("----------------------"); + //serializeJsonPretty(doc, Serial); + //Serial.println(); + //Serial.println("----------------------"); + //Serial.println(" Break up the board..."); + //Serial.print(" BOARD:"); Serial.println((char *)doc["board"].as()); + char* pch = NULL; + pch = strtok((char *)doc["board"].as(), "\n"); + //Serial.print(" pch:");Serial.println(pch); + int row_count = 0; + while (pch != NULL) { + char board_line[30]; + strcpy(board_line, pch); + removeSubstr(board_line, "[37m"); + removeSubstr(board_line, "[0m"); + removeSubstr(board_line, "\n"); + removeSubstr(board_line, "\e"); // remove escape (ASCII 27) + removeSubstr(board_line, "\e"); // remove escape (ASCII 27) + //Serial.print("(");Serial.print(row_count);Serial.print(")"); + //Serial.print(board_line); Serial.println("|"); + strcpy(game_board[row_count], board_line); // valid + pch = strtok(NULL, "\n"); + row_count++; + } + Serial.println(); + for (int i=0; i<9; i++) { + Serial.print("[");Serial.print(game_board[i]);Serial.println("]"); + } + } + } else { + Serial.println("Error on HTTP request"); + } + // Don't know how we could get here without it being an error. +} diff --git a/examples/20-multiapp-arduino/a2i_chess.h b/examples/20-multiapp-arduino/a2i_chess.h new file mode 100644 index 0000000..bb691b0 --- /dev/null +++ b/examples/20-multiapp-arduino/a2i_chess.h @@ -0,0 +1,66 @@ +#ifndef A2I_CHESS_H +#define A2I_CHESS_H + +#include +#include +#include +#include +#include "chess_commands.h" + + +#define MAX_GAME_SIZE 110 * 4 // This is probably not enough, but it's fine for development. + // https://chess.stackexchange.com/questions/2506/what-is-the-average-length-of-a-game-of-chess + // times four because one move is "e7e5" + +class Chess { + + public: + byte appId = APP_CHESS; // This is "registered" with A2I_commands.h which is part of Apple2Idiot.h + // This id is sent from the Apple to the ESP to tell the esp what app + // is currently active. The main loop of the ESP sketch then knows to use + // this class to respond to incoming commands from the Apple. + + + char game_string[MAX_GAME_SIZE]; // This is probably not enough, but it's fine for development. + + char game_status[25]; + char last_player_move[5]; // "a2a4" + char last_ai_move[5]; // "8g76" + + //char game_board[9][20]; + char game_board[9][22] = { + "8 r n b q k b n r", + "7 p p p p p p p p", + "6 . . . . . . . .", + "5 . . . . . . . .", + "4 . . . . . . . .", + "3 . . . . . . . .", + "2 P P P P P P P P", + "1 R N B Q K B N R", + " a b c d e f g h" + }; + + void init(Apple2Idiot *a2ip, HTTPClient *httpp); + char* getGameStatus(char* game_status); + byte makeMove(String move_string); + byte handleCommand(byte command); + byte validateMove(String move_string); + char* getAIMove(); + void getBoard(); + + private: + + Apple2Idiot *a2i; + HTTPClient *http; + + void removeSubstr (char *string, char *sub); + const char api_entry_point[32] = "http://chess-api.herokuapp.com"; + /* Remember, flexible array won't work + * in a class, so don't try to do this... + * const char foo[] = "hello world"; + */ + +}; + +#endif + diff --git a/examples/20-multiapp-arduino/a2i_iss.cpp b/examples/20-multiapp-arduino/a2i_iss.cpp new file mode 100644 index 0000000..c2fb02a --- /dev/null +++ b/examples/20-multiapp-arduino/a2i_iss.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include +#include "iss_commands.h" +#include "a2i_iss.h" + + +void Iss::init(Apple2Idiot *a2ip, HTTPClient *httpp) { + a2i = a2ip; + http = httpp; + Serial.println("ISS()::init()"); +} + +byte Iss::handleCommand(byte command) { + Serial.println("ISS() handleCommand"); + switch(command) { + case COMMAND_GET_ISS: { + Serial.println("ISS() COMMAND_GET_ISS"); + a2i->write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + Serial.println("ISS() before fetch_iss()"); + return fetch_iss(); + } + default: { + Serial.print("ISS() COMMAND_NOT_FOUND: "); + Serial.println(command); + return COMMAND_NOT_FOUND; + } + } +} + +byte Iss::fetch_iss() { + + byte result = 0; + Apple2Idiot a2i; + HTTPClient http; + + char api_request[MAX_STR_LEN]; + sprintf(api_request, "%s", api_entry_point); + Serial.print(" "); Serial.println(api_request); + http.begin(api_request); + + int httpCode = http.GET(); //Make the request + Serial.print(" "); Serial.println("After GET()"); + delay(10); + Serial.print(" httpCode:"); Serial.println(httpCode); + + if (httpCode > 0) { //Check for the returning code + String payload = http.getString(); + //Serial.println(httpCode); + Serial.println("++++++++++++++++++++++++"); + Serial.println(payload); + Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<200> filter; + filter["iss_position"]["latitude"] = true; + filter["iss_position"]["longitude"] = true; + filter["timestamp"] = true; + filter["message"]["humidity"] = true; + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload, DeserializationOption::Filter(filter)); + //DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + } else { + Serial.println("----------------------"); + serializeJsonPretty(doc, Serial); + Serial.println("----------------------"); + serializeJsonPretty(doc["iss_position"], Serial); + Serial.println("----------------------"); + String latitude = doc["iss_position"]["latitude"]; + String longitude = doc["iss_position"]["longitude"]; + String timestamp = doc["timestamp"]; + String message = doc["messasge"]; + int address_counter = a2i.write_string_to_shared_ram(latitude, SHARED_RAM_START_ADDRESS); + address_counter = a2i.write_string_to_shared_ram(longitude, address_counter + 1); + address_counter = a2i.write_string_to_shared_ram(timestamp, address_counter + 1); + address_counter = a2i.write_string_to_shared_ram(message, address_counter + 1); + } + result = ACK; + } + else { + Serial.println("Error on HTTP request"); + result = ERR; + } + + http.end(); //Free the resources + return result; +} + diff --git a/examples/20-multiapp-arduino/a2i_iss.h b/examples/20-multiapp-arduino/a2i_iss.h new file mode 100644 index 0000000..596bddc --- /dev/null +++ b/examples/20-multiapp-arduino/a2i_iss.h @@ -0,0 +1,43 @@ +#ifndef A2I_ISS_H +#define A2I_ISS_H + +#include +#include +#include +#include +#include "iss_commands.h" + +class Iss { + + public: + // This is "registered" with A2I_commands.h which is part of Apple2Idiot.h This + // id is sent from the Apple to the ESP to tell the esp what app is currently + // active. The main loop of the ESP sketch then knows to use this class to + // respond to incoming commands from the Apple. + byte appId = APP_ISS; + + void init(Apple2Idiot *a2ip, HTTPClient *httpp); + //char* getGameStatus(char* game_status); + //byte makeMove(String move_string); + byte handleCommand(byte command); + byte fetch_iss(); + //byte validateMove(String move_string); + //char* getAIMove(); + //void getBoard(); + + private: + + Apple2Idiot *a2i; + HTTPClient *http; + + void removeSubstr (char *string, char *sub); + const char api_entry_point[49] = "http://api.open-notify.org/iss-now.json"; + /* Remember, flexible array won't work + * in a class, so don't try to do this... + * const char foo[] = "hello world"; + */ + +}; + +#endif + diff --git a/examples/20-multiapp-arduino/a2i_weather.cpp b/examples/20-multiapp-arduino/a2i_weather.cpp new file mode 100644 index 0000000..bcd7410 --- /dev/null +++ b/examples/20-multiapp-arduino/a2i_weather.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include "weather_commands.h" +#include "a2i_weather.h" +#include "credentials.h" + + +void Weather::init(Apple2Idiot *a2ip, HTTPClient *httpp) { + a2i = a2ip; + http = httpp; + Serial.println("WEATHER()::init()"); +} + +byte Weather::handleCommand(byte command) { + Serial.println("WEATHER() handleCommand"); + switch(command) { + case COMMAND_FETCH_WEATHER: { + Serial.println("WEATHER() COMMAND_FETCH_WEATHER"); + a2i->write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + Serial.println("WEATHER() before fetch_weather()"); + return fetch_weather(); + } + case COMMAND_SET_COUNTRY: { + Serial.println("WEATHER() COMMAND_SET_COUNTRY"); + a2i->write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + country_code = a2i->read_string_from_ram(SHARED_RAM_START_ADDRESS); + Serial.println("Received CountryCode: ["+country_code+"]"); + a2i->write_data(APPLE_COMMAND_ADDRESS, ACK); + a2i->write_data(ESP_COMMAND_ADDRESS, EOT); // notify Apple IIe we are done processing command byte + return ACK; + //break; + } + case COMMAND_SET_CITY: { + //a2i->write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + Serial.print("WEATHER() COMMAND_SET_CITY"); + a2i->write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + city_name = a2i->read_string_from_ram(SHARED_RAM_START_ADDRESS); + Serial.println("Received CityName: ["+city_name+"]"); + a2i->write_data(APPLE_COMMAND_ADDRESS, ACK); + a2i->write_data(ESP_COMMAND_ADDRESS, EOT); // notify Apple IIe we are done processing command byte + return ACK; + } + default: { + Serial.print("WEATHER() COMMAND_NOT_FOUND: "); + Serial.println(command); + return COMMAND_NOT_FOUND; + } + } +} + +byte Weather::fetch_weather() { + + byte result = 0; + Apple2Idiot a2i; + HTTPClient http; + + char api_request[MAX_STR_LEN]; + sprintf(api_request, "%sq=%s,%s&APPID=%s", api_entry_point, city_name, country_code, WEATHER_ACCESS_TOKEN); + //sprintf(api_request, "http://api.openweathermap.org/data/2.5/weather?q=TUCSON,US&APPID=0ab97bbbea58592d7c9d64067a34d2d0"); + Serial.print(" "); Serial.println(api_request); + http.begin(api_request); + + int httpCode = http.GET(); //Make the request + Serial.print(" "); Serial.println("After GET()"); + delay(10); + + if (httpCode > 0) { //Check for the returning code + String payload = http.getString(); + //Serial.println(httpCode); + Serial.println("++++++++++++++++++++++++"); + Serial.println(payload); + Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<200> filter; + filter["weather"][0]["main"] = true; + filter["weather"][0]["description"] = true; + filter["main"]["humidity"] = true; + filter["main"]["temp"] = true; + filter["wind"]["speed"] = true; + filter["wind"]["deg"] = true; + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload, DeserializationOption::Filter(filter)); + //DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + } else { + Serial.println("----------------------"); + serializeJsonPretty(doc, Serial); + Serial.println("----------------------"); + serializeJsonPretty(doc["wind"], Serial); + Serial.println("----------------------"); + String temp = doc["main"]["temp"]; + String humidity = doc["main"]["humidity"]; + String wind_speed = doc["wind"]["speed"]; + String wind_deg = doc["wind"]["deg"]; + String weather_description1 = doc["weather"][0]["main"]; + String weather_description2 = doc["weather"][0]["description"]; + int address_counter = a2i.write_string_to_shared_ram(temp, SHARED_RAM_START_ADDRESS); + address_counter = a2i.write_string_to_shared_ram(humidity, address_counter + 1); + address_counter = a2i.write_string_to_shared_ram(wind_speed, address_counter + 1); + address_counter = a2i.write_string_to_shared_ram(wind_deg, address_counter + 1); + address_counter = a2i.write_string_to_shared_ram(weather_description1, address_counter + 1); + address_counter = a2i.write_string_to_shared_ram(weather_description2, address_counter + 1); + } + result = ACK; + } + else { + Serial.println("Error on HTTP request"); + result = ERR; + } + + http.end(); //Free the resources + return result; +} + diff --git a/examples/20-multiapp-arduino/a2i_weather.h b/examples/20-multiapp-arduino/a2i_weather.h new file mode 100644 index 0000000..28703c2 --- /dev/null +++ b/examples/20-multiapp-arduino/a2i_weather.h @@ -0,0 +1,47 @@ +#ifndef A2I_CHESS_H +#define A2I_CHESS_H + +#include +#include +#include +#include +#include "weather_commands.h" +#include "credentials.h" + +class Weather { + + public: + // This is "registered" with A2I_commands.h which is part of Apple2Idiot.h This + // id is sent from the Apple to the ESP to tell the esp what app is currently + // active. The main loop of the ESP sketch then knows to use this class to + // respond to incoming commands from the Apple. + byte appId = APP_WEATHER; + + String country_code = "US"; + String city_name = "Tucson"; + + void init(Apple2Idiot *a2ip, HTTPClient *httpp); + //char* getGameStatus(char* game_status); + //byte makeMove(String move_string); + byte handleCommand(byte command); + byte fetch_weather(); + //byte validateMove(String move_string); + //char* getAIMove(); + //void getBoard(); + + private: + + Apple2Idiot *a2i; + HTTPClient *http; + + void removeSubstr (char *string, char *sub); + const char api_entry_point[49] = "http://api.openweathermap.org/data/2.5/weather?"; + /* Remember, flexible array won't work + * in a class, so don't try to do this... + * const char foo[] = "hello world"; + */ + +}; + +#endif + diff --git a/examples/20-multiapp-arduino/chess_commands.h b/examples/20-multiapp-arduino/chess_commands.h new file mode 100644 index 0000000..7721c90 --- /dev/null +++ b/examples/20-multiapp-arduino/chess_commands.h @@ -0,0 +1,38 @@ +#ifndef A2I_CHESS_COMMANDS_H +#define A2I_CHESS_COMMANDS_H + +/* Apple II <-> ESP Commands */ +#define CHESS_NEW_GAME 10 +#define CHESS_GET_AI_MOVE 20 +#define CHESS_GET_GAME_STATUS 22 +#define CHESS_GET_BOARD 23 +#define CHESS_MAKE_MOVE 21 + +/* Responses */ +#define CHESS_INVALID_MOVE 123 +#define CHESS_VALID_MOVE 124 + +/* + * Responses from API... + * "in_progress" + * "black_won" + * "white_won" + * "white_won_resign" + * "black_won_resign" + * "stalemate" + * "insufficient_material" + * "fifty_rule_move" + * "threefold_repitition" + * "unknown" + */ + +#define STATUS_IN_PROGRESS 200 +#define STATUS_BLACK_WON 201 +#define STATUS_WHITE_WON 202 +#define STATUS_STALEMATE 205 +#define STATUS_FIFTY_RULE_MOVE 207 +#define STATUS_UNKNOWN 209 +#define STATUS_UNHANDLED 210 +#define STATUS_ERROR 211 + +#endif diff --git a/examples/20-multiapp-arduino/credentials.h.sample b/examples/20-multiapp-arduino/credentials.h.sample new file mode 100644 index 0000000..cf506a6 --- /dev/null +++ b/examples/20-multiapp-arduino/credentials.h.sample @@ -0,0 +1,12 @@ +#ifndef CREDENTIALS_H +#define CREDENTIALS_H + +#include "credentials.h" +// copy credentials.h.sample to credentials.h and edit +// so it contains your passwords and tokens. + +#define WIFI_SSID "XXXXXXXXXXXXXXX"; +#define WIFI_PASSWORD "XXXXXXXXXXXXXXX"; +#define WEATHER_ACCESS_TOKEN "XXXXXXXXXXXXXXX" + +#endif diff --git a/examples/20-multiapp-arduino/iss_commands.h b/examples/20-multiapp-arduino/iss_commands.h new file mode 100644 index 0000000..584b77c --- /dev/null +++ b/examples/20-multiapp-arduino/iss_commands.h @@ -0,0 +1,9 @@ +#ifndef A2I_ISS_COMMANDS_H +#define A2I_ISS_COMMANDS_H + +/* Apple II <-> ESP Commands */ +#define COMMAND_GET_ISS 20 + +/* Responses */ + +#endif diff --git a/examples/15_fixed_essid_weather-arduino/15_fixed_essid_weather-cc65.ino b/examples/20-multiapp-arduino/oldino.foo similarity index 100% rename from examples/15_fixed_essid_weather-arduino/15_fixed_essid_weather-cc65.ino rename to examples/20-multiapp-arduino/oldino.foo diff --git a/examples/20-multiapp-arduino/weather_commands.h b/examples/20-multiapp-arduino/weather_commands.h new file mode 100644 index 0000000..7d83ede --- /dev/null +++ b/examples/20-multiapp-arduino/weather_commands.h @@ -0,0 +1,13 @@ +#ifndef A2I_WEATHER_COMMANDS_H +#define A2I_WEATHER_COMMANDS_H + +/* Apple II <-> ESP Commands */ +#define COMMAND_SET_COUNTRY 20 +#define COMMAND_SET_CITY 21 +#define COMMAND_FETCH_WEATHER 25 + +/* Responses */ +//#define CHESS_INVALID_MOVE 123 +//#define CHESS_VALID_MOVE 124 + +#endif