├── .gitignore ├── platformio.ini ├── lib └── readme.txt ├── .travis.yml └── src ├── images.h ├── AdsbExchangeClient.h ├── AdsbExchangeClient.cpp └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .pioenvs 2 | .clang_complete 3 | .gcc-flags.json 4 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Project Configuration File 3 | # 4 | # A detailed documentation with the EXAMPLES is located here: 5 | # http://docs.platformio.org/en/latest/projectconf.html 6 | # 7 | 8 | # A sign `#` at the beginning of the line indicates a comment 9 | # Comment lines are ignored. 10 | 11 | # Simple and base environment 12 | # [env:mybaseenv] 13 | # platform = %INSTALLED_PLATFORM_NAME_HERE% 14 | # framework = 15 | # board = 16 | # 17 | # Automatic targets - enable auto-uploading 18 | # targets = upload 19 | 20 | [env:d1_mini] 21 | platform = espressif 22 | framework = arduino 23 | #561: Json streaming parser, 562: OLED, 563: Weather Station, 567: WifiManager 24 | lib_install = 561,562,563,567 25 | board = d1_mini 26 | #upload_speed = 230400 27 | #upload_port = ESP8266-OTA-b6868.local 28 | -------------------------------------------------------------------------------- /lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | |--Bar 12 | | | |--docs 13 | | | |--examples 14 | | | |--src 15 | | | |- Bar.c 16 | | | |- Bar.h 17 | | |--Foo 18 | | | |- Foo.c 19 | | | |- Foo.h 20 | | |- readme.txt --> THIS FILE 21 | |- platformio.ini 22 | |--src 23 | |- main.c 24 | 25 | Then in `src/main.c` you should use: 26 | 27 | #include 28 | #include 29 | 30 | // rest H/C/CPP code 31 | 32 | PlatformIO will find your libraries automatically, configure preprocessor's 33 | include paths and build them. 34 | 35 | See additional options for PlatformIO Library Dependency Finder `lib_*`: 36 | 37 | http://docs.platformio.org/en/latest/projectconf.html#lib-install 38 | 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/en/latest/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/en/latest/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/en/latest/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # 39 | # script: 40 | # - platformio run 41 | 42 | 43 | # 44 | # Template #2: The project is intended to by used as a library with examples 45 | # 46 | 47 | # language: python 48 | # python: 49 | # - "2.7" 50 | # 51 | # sudo: false 52 | # cache: 53 | # directories: 54 | # - "~/.platformio" 55 | # 56 | # env: 57 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 58 | # - PLATFORMIO_CI_SRC=examples/file.ino 59 | # - PLATFORMIO_CI_SRC=path/to/test/directory 60 | # 61 | # install: 62 | # - pip install -U platformio 63 | # 64 | # script: 65 | # - platformio ci --lib="." --board=TYPE_1 --board=TYPE_2 --board=TYPE_N 66 | -------------------------------------------------------------------------------- /src/images.h: -------------------------------------------------------------------------------- 1 | #define WiFi_Logo_width 60 2 | #define WiFi_Logo_height 36 3 | const char WiFi_Logo_bits[] PROGMEM = { 4 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 5 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 6 | 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 7 | 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 8 | 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 9 | 0xFF, 0x03, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 10 | 0x00, 0xFF, 0xFF, 0xFF, 0x07, 0xC0, 0x83, 0x01, 0x80, 0xFF, 0xFF, 0xFF, 11 | 0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00, 12 | 0xC0, 0xFF, 0xFF, 0x7C, 0x00, 0x60, 0x0C, 0x00, 0xC0, 0x31, 0x46, 0x7C, 13 | 0xFC, 0x77, 0x08, 0x00, 0xE0, 0x23, 0xC6, 0x3C, 0xFC, 0x67, 0x18, 0x00, 14 | 0xE0, 0x23, 0xE4, 0x3F, 0x1C, 0x00, 0x18, 0x00, 0xE0, 0x23, 0x60, 0x3C, 15 | 0x1C, 0x70, 0x18, 0x00, 0xE0, 0x03, 0x60, 0x3C, 0x1C, 0x70, 0x18, 0x00, 16 | 0xE0, 0x07, 0x60, 0x3C, 0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C, 17 | 0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00, 18 | 0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00, 0xE0, 0x8F, 0x71, 0x3C, 19 | 0x1C, 0x70, 0x18, 0x00, 0xC0, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x08, 0x00, 20 | 0xC0, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x0C, 0x00, 0x80, 0xFF, 0xFF, 0x1F, 21 | 0x00, 0x00, 0x06, 0x00, 0x80, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x07, 0x00, 22 | 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 23 | 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 24 | 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 25 | 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | }; 29 | 30 | const char emptySymbol[] PROGMEM = { 31 | B00000000, 32 | B00000000, 33 | B00000000, 34 | B00000000, 35 | B00000000, 36 | B00000000, 37 | B00000000, 38 | B00000000 39 | }; 40 | 41 | const char activeSymbol[] PROGMEM = { 42 | B00000000, 43 | B00000000, 44 | B00011000, 45 | B00100100, 46 | B01000010, 47 | B01000010, 48 | B00100100, 49 | B00011000 50 | }; 51 | 52 | const char inactiveSymbol[] PROGMEM = { 53 | B00000000, 54 | B00000000, 55 | B00000000, 56 | B00000000, 57 | B00011000, 58 | B00011000, 59 | B00000000, 60 | B00000000 61 | }; 62 | -------------------------------------------------------------------------------- /src/AdsbExchangeClient.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2015 by Daniel Eichhorn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | See more at http://blog.squix.ch 24 | */ 25 | 26 | #pragma once 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #define CURRENT 0 34 | #define TEMP 1 35 | 36 | #define MAX_AGE_MILLIS 15000 37 | 38 | 39 | class AdsbExchangeClient: public JsonListener { 40 | private: 41 | int counter = 0; 42 | String currentKey = ""; 43 | String from[2] = {"", ""}; 44 | String to[2] = {"", ""}; 45 | String altitude[2] = {"", ""}; 46 | double distance[2] = {0.0, 0.0}; 47 | double currentMinDistance = 1000.0; 48 | String aircraftType[2] = {"", ""}; 49 | String operatorCode[2] = {"", ""}; 50 | double heading[2] = {0.0, 0.0}; 51 | long lastSightingMillis = 0; 52 | 53 | public: 54 | AdsbExchangeClient(); 55 | 56 | void updateVisibleAircraft(String searchQuery); 57 | 58 | String getFrom(); 59 | String getFromIcao(); 60 | String getTo(); 61 | String getToIcao(); 62 | String getAltitude(); 63 | double getDistance(); 64 | String getAircraftType(); 65 | String getOperatorCode(); 66 | double getHeading(); 67 | int getNumberOfVisibleAircrafts(); 68 | boolean isAircraftVisible(); 69 | 70 | virtual void whitespace(char c); 71 | 72 | virtual void startDocument(); 73 | 74 | virtual void key(String key); 75 | 76 | virtual void value(String value); 77 | 78 | virtual void endArray(); 79 | 80 | virtual void endObject(); 81 | 82 | virtual void endDocument(); 83 | 84 | virtual void startArray(); 85 | 86 | virtual void startObject(); 87 | }; 88 | -------------------------------------------------------------------------------- /src/AdsbExchangeClient.cpp: -------------------------------------------------------------------------------- 1 | #include "AdsbExchangeClient.h" 2 | 3 | 4 | AdsbExchangeClient::AdsbExchangeClient() { 5 | 6 | } 7 | 8 | void AdsbExchangeClient::updateVisibleAircraft(String searchQuery) { 9 | JsonStreamingParser parser; 10 | parser.setListener(this); 11 | WiFiClient client; 12 | 13 | // http://public-api.adsbexchange.com/VirtualRadar/AircraftList.json?lat=47.437691&lng=8.568854&fDstL=0&fDstU=20&fAltL=0&fAltU=5000 14 | const char host[] = "public-api.adsbexchange.com"; 15 | String url = "/VirtualRadar/AircraftList.json?" + searchQuery; 16 | 17 | const int httpPort = 80; 18 | if (!client.connect(host, httpPort)) { 19 | Serial.println("connection failed"); 20 | return; 21 | } 22 | 23 | 24 | Serial.print("Requesting URL: "); 25 | Serial.println(url); 26 | 27 | // This will send the request to the server 28 | client.print(String("GET ") + url + " HTTP/1.1\r\n" + 29 | "Host: " + host + "\r\n" + 30 | "Connection: close\r\n\r\n"); 31 | 32 | int retryCounter = 0; 33 | while(!client.available()) { 34 | Serial.println("."); 35 | delay(1000); 36 | retryCounter++; 37 | if (retryCounter > 10) { 38 | return; 39 | } 40 | } 41 | 42 | int pos = 0; 43 | boolean isBody = false; 44 | char c; 45 | 46 | int size = 0; 47 | client.setNoDelay(false); 48 | while(client.connected()) { 49 | while((size = client.available()) > 0) { 50 | c = client.read(); 51 | if (c == '{' || c == '[') { 52 | isBody = true; 53 | } 54 | if (isBody) { 55 | parser.parse(c); 56 | } 57 | } 58 | } 59 | endDocument(); 60 | } 61 | 62 | String AdsbExchangeClient::getFrom() { 63 | if (from[CURRENT].length() >=4) { 64 | int firstComma = from[CURRENT].indexOf(","); 65 | return from[CURRENT].substring(5, firstComma); 66 | } 67 | return ""; 68 | } 69 | String AdsbExchangeClient::getFromIcao() { 70 | if (from[CURRENT].length() >=4) { 71 | return from[CURRENT].substring(0,4); 72 | } 73 | return ""; 74 | } 75 | String AdsbExchangeClient::getTo() { 76 | if (to[CURRENT].length() >=4) { 77 | int firstComma = to[CURRENT].indexOf(","); 78 | return to[CURRENT].substring(5, firstComma); 79 | } 80 | return ""; 81 | } 82 | 83 | String AdsbExchangeClient::getToIcao() { 84 | if (to[CURRENT].length() >=4) { 85 | return to[CURRENT].substring(0,4); 86 | } 87 | return ""; 88 | } 89 | String AdsbExchangeClient::getAltitude(){ 90 | return altitude[CURRENT]; 91 | } 92 | double AdsbExchangeClient::getDistance() { 93 | return distance[CURRENT]; 94 | 95 | } 96 | String AdsbExchangeClient::getAircraftType() { 97 | return aircraftType[CURRENT]; 98 | 99 | } 100 | String AdsbExchangeClient::getOperatorCode() { 101 | return operatorCode[CURRENT]; 102 | } 103 | 104 | double AdsbExchangeClient::getHeading() { 105 | return heading[CURRENT]; 106 | } 107 | 108 | void AdsbExchangeClient::whitespace(char c) { 109 | 110 | } 111 | 112 | void AdsbExchangeClient::startDocument() { 113 | counter = 0; 114 | currentMinDistance = 1000.0; 115 | } 116 | 117 | void AdsbExchangeClient::key(String key) { 118 | currentKey = key; 119 | } 120 | 121 | void AdsbExchangeClient::value(String value) { 122 | /*String from = ""; 123 | String to = ""; 124 | String altitude = ""; 125 | String aircraftType = ""; 126 | String currentKey = ""; 127 | String operator = ""; 128 | 129 | 130 | "Type": "A319", 131 | "Mdl": "Airbus A319 112", 132 | 133 | "From": "LSZH Z\u00c3\u00bcrich, Zurich, Switzerland", 134 | "To": "LEMD Madrid Barajas, Spain", 135 | "Op": "Swiss International Air Lines", 136 | "OpIcao": "SWR", 137 | "Dst": 6.23, 138 | "Year": "1996" 139 | */ 140 | if (currentKey == "Id") { 141 | counter++; 142 | } else if (currentKey == "From") { 143 | from[TEMP] = value; 144 | } else if (currentKey == "To") { 145 | to[TEMP] = value; 146 | } else if (currentKey == "OpIcao") { 147 | operatorCode[TEMP] = value; 148 | } else if (currentKey == "Dst") { 149 | distance[TEMP] = value.toFloat(); 150 | } else if (currentKey == "Mdl") { 151 | aircraftType[TEMP] = value; 152 | } else if (currentKey == "Trak") { 153 | heading[TEMP] = value.toFloat(); 154 | } else if (currentKey == "Alt") { 155 | altitude[TEMP] = value; 156 | } else if (currentKey == "Trt") { 157 | if (distance[TEMP] < currentMinDistance) { 158 | currentMinDistance = distance[TEMP]; 159 | Serial.println("Found a closer aircraft"); 160 | from[CURRENT] = from[TEMP]; 161 | to[CURRENT] = to[TEMP]; 162 | altitude[CURRENT] = altitude[TEMP]; 163 | distance[CURRENT] = distance[TEMP]; 164 | aircraftType[CURRENT] = aircraftType[TEMP]; 165 | operatorCode[CURRENT] = operatorCode[TEMP]; 166 | heading[CURRENT] = heading[TEMP]; 167 | } 168 | } 169 | Serial.println(currentKey + "=" + value); 170 | } 171 | 172 | int AdsbExchangeClient::getNumberOfVisibleAircrafts() { 173 | return counter; 174 | } 175 | 176 | void AdsbExchangeClient::endArray() { 177 | 178 | } 179 | 180 | void AdsbExchangeClient::endObject() { 181 | 182 | } 183 | 184 | void AdsbExchangeClient::endDocument() { 185 | Serial.println("Flights: " + String(counter)); 186 | if (counter == 0 && lastSightingMillis < millis() - MAX_AGE_MILLIS) { 187 | for (int i = 0; i < 2; i++) { 188 | from[i] = ""; 189 | to[i] = ""; 190 | altitude[i] = ""; 191 | distance[i] = 1000.0; 192 | aircraftType[i] = ""; 193 | operatorCode[i] = ""; 194 | heading[i] = 0.0; 195 | } 196 | } else if (counter > 0) { 197 | lastSightingMillis = millis(); 198 | } 199 | } 200 | 201 | boolean AdsbExchangeClient::isAircraftVisible() { 202 | return counter > 0 || lastSightingMillis > millis() - MAX_AGE_MILLIS; 203 | } 204 | 205 | void AdsbExchangeClient::startArray() { 206 | 207 | } 208 | 209 | void AdsbExchangeClient::startObject() { 210 | 211 | } 212 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2016 by Daniel Eichhorn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | See more at http://blog.squix.ch 24 | */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #include "SSD1306Wire.h" 37 | //#include "SH1106Wire.h" 38 | #include "OLEDDisplayUi.h" 39 | #include "Wire.h" 40 | #include "images.h" 41 | 42 | #include "TimeClient.h" 43 | #include "AdsbExchangeClient.h" 44 | 45 | 46 | /*************************** 47 | * Begin Settings 48 | **************************/ 49 | // Please read http://blog.squix.org/weatherstation-getting-code-adapting-it 50 | // for setup instructions 51 | 52 | #define HOSTNAME "ESP8266-OTA-" 53 | 54 | // Setup 55 | const int UPDATE_INTERVAL_SECS_LONG = 15; // Update every 15 seconds if no airplanes around 56 | const int UPDATE_INTERVAL_SECS_SHORT = 3; // Update every 3 seconds if there are airplanes 57 | 58 | int currentUpdateInterval = UPDATE_INTERVAL_SECS_LONG; 59 | long lastUpdate = 0; 60 | 61 | // Check http://www.virtualradarserver.co.uk/Documentation/Formats/AircraftList.aspx 62 | // to craft this query to your needs 63 | const String QUERY_STRING = "lat=47.424341887&lng=8.568778038&fDstL=0&fDstU=10&fAltL=0&fAltL=1500&fAltU=10000"; 64 | 65 | const int UTC_OFFSET = 2; 66 | 67 | const float pi = 3.141; 68 | 69 | // Display Settings 70 | const int I2C_DISPLAY_ADDRESS = 0x3c; 71 | const int SDA_PIN = D2; 72 | const int SDC_PIN = D3; 73 | 74 | // Initialize the oled display for address 0x3c 75 | // sda-pin=14 and sdc-pin=12 76 | SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN); 77 | //SH1106Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN); 78 | OLEDDisplayUi ui( &display ); 79 | 80 | /*************************** 81 | * End Settings 82 | **************************/ 83 | 84 | TimeClient timeClient(UTC_OFFSET); 85 | 86 | AdsbExchangeClient adsbClient; 87 | 88 | // flag changed in the ticker function every 10 minutes 89 | bool readyForUpdate = false; 90 | 91 | Ticker ticker; 92 | 93 | //declaring prototypes 94 | void configModeCallback (WiFiManager *myWiFiManager); 95 | void drawProgress(OLEDDisplay *display, int percentage, String label); 96 | void drawOtaProgress(unsigned int, unsigned int); 97 | void updateData(OLEDDisplay *display); 98 | void drawCurrentAirplane1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); 99 | void drawCurrentAirplane2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); 100 | void drawCurrentAirplane3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); 101 | void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state); 102 | void drawTextAsBigAsPossible(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y, String text, int maxWidth); 103 | void drawHeading(OLEDDisplay *display, int x, int y, double heading); 104 | void checkReadyForUpdate(); 105 | int8_t getWifiQuality(); 106 | 107 | 108 | // Add frames 109 | // this array keeps function pointers to all frames 110 | // frames are the single views that slide from right to left 111 | FrameCallback frames[] = { drawCurrentAirplane1, drawCurrentAirplane2, drawCurrentAirplane3 }; 112 | int numberOfFrames = 3; 113 | 114 | OverlayCallback overlays[] = { drawHeaderOverlay }; 115 | int numberOfOverlays = 1; 116 | 117 | void setup() { 118 | // Turn On VCC 119 | pinMode(D4, OUTPUT); 120 | digitalWrite(D4, HIGH); 121 | Serial.begin(115200); 122 | 123 | // initialize dispaly 124 | display.init(); 125 | display.clear(); 126 | display.display(); 127 | 128 | //display.flipScreenVertically(); 129 | display.setFont(ArialMT_Plain_10); 130 | display.setTextAlignment(TEXT_ALIGN_CENTER); 131 | display.setContrast(255); 132 | 133 | //WiFiManager 134 | //Local intialization. Once its business is done, there is no need to keep it around 135 | WiFiManager wifiManager; 136 | // Uncomment for testing wifi manager 137 | //wifiManager.resetSettings(); 138 | wifiManager.setAPCallback(configModeCallback); 139 | 140 | //or use this for auto generated name ESP + ChipID 141 | wifiManager.autoConnect(); 142 | 143 | //Manual Wifi 144 | //WiFi.begin(WIFI_SSID, WIFI_PWD); 145 | String hostname(HOSTNAME); 146 | hostname += String(ESP.getChipId(), HEX); 147 | WiFi.hostname(hostname); 148 | 149 | 150 | int counter = 0; 151 | while (WiFi.status() != WL_CONNECTED) { 152 | delay(500); 153 | Serial.print("."); 154 | display.clear(); 155 | display.drawString(64, 10, "Connecting to WiFi"); 156 | display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbol : inactiveSymbol); 157 | display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbol : inactiveSymbol); 158 | display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbol : inactiveSymbol); 159 | display.display(); 160 | 161 | counter++; 162 | } 163 | 164 | ui.setTargetFPS(30); 165 | 166 | //Hack until disableIndicator works: 167 | //Set an empty symbol 168 | ui.setActiveSymbol(emptySymbol); 169 | ui.setInactiveSymbol(emptySymbol); 170 | ui.disableIndicator(); 171 | 172 | // You can change the transition that is used 173 | // SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN 174 | ui.setFrameAnimation(SLIDE_LEFT); 175 | 176 | ui.setFrames(frames, numberOfFrames); 177 | 178 | ui.setOverlays(overlays, numberOfOverlays); 179 | 180 | // Inital UI takes care of initalising the display too. 181 | ui.init(); 182 | 183 | // Setup OTA 184 | Serial.println("Hostname: " + hostname); 185 | ArduinoOTA.setHostname((const char *)hostname.c_str()); 186 | ArduinoOTA.onProgress(drawOtaProgress); 187 | ArduinoOTA.begin(); 188 | 189 | updateData(&display); 190 | 191 | //Check every second 192 | ticker.attach(1, checkReadyForUpdate); 193 | 194 | } 195 | 196 | void loop() { 197 | // If there are airplanes query often 198 | if (adsbClient.getNumberOfVisibleAircrafts() == 0) { 199 | currentUpdateInterval = UPDATE_INTERVAL_SECS_LONG; 200 | } else { 201 | currentUpdateInterval = UPDATE_INTERVAL_SECS_SHORT; 202 | } 203 | 204 | if (readyForUpdate && ui.getUiState()->frameState == FIXED) { 205 | updateData(&display); 206 | } 207 | 208 | int remainingTimeBudget = ui.update(); 209 | 210 | if (remainingTimeBudget > 0) { 211 | // You can do some work here 212 | // Don't do stuff if you are below your 213 | // time budget. 214 | ArduinoOTA.handle(); 215 | delay(remainingTimeBudget); 216 | } 217 | 218 | 219 | } 220 | 221 | void configModeCallback (WiFiManager *myWiFiManager) { 222 | Serial.println("Entered config mode"); 223 | Serial.println(WiFi.softAPIP()); 224 | //if you used auto generated SSID, print it 225 | Serial.println(myWiFiManager->getConfigPortalSSID()); 226 | display.clear(); 227 | display.setTextAlignment(TEXT_ALIGN_CENTER); 228 | display.setFont(ArialMT_Plain_10); 229 | display.drawString(64, 10, "Wifi Manager"); 230 | display.drawString(64, 20, "Please connect to AP"); 231 | display.drawString(64, 30, myWiFiManager->getConfigPortalSSID()); 232 | display.drawString(64, 40, "To setup Wifi Configuration"); 233 | display.display(); 234 | } 235 | 236 | void drawProgress(OLEDDisplay *display, int percentage, String label) { 237 | display->clear(); 238 | display->setTextAlignment(TEXT_ALIGN_CENTER); 239 | display->setFont(ArialMT_Plain_10); 240 | display->drawString(64, 10, label); 241 | display->drawProgressBar(2, 28, 124, 10, percentage); 242 | display->display(); 243 | } 244 | 245 | void drawOtaProgress(unsigned int progress, unsigned int total) { 246 | display.clear(); 247 | display.setTextAlignment(TEXT_ALIGN_CENTER); 248 | display.setFont(ArialMT_Plain_10); 249 | display.drawString(64, 10, "OTA Update"); 250 | display.drawProgressBar(2, 28, 124, 10, progress / (total / 100)); 251 | display.display(); 252 | } 253 | 254 | void updateData(OLEDDisplay *display) { 255 | readyForUpdate = false; 256 | adsbClient.updateVisibleAircraft(QUERY_STRING); 257 | lastUpdate = millis(); 258 | } 259 | 260 | void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) { 261 | 262 | if (adsbClient.isAircraftVisible()) { 263 | display->setTextAlignment(TEXT_ALIGN_LEFT); 264 | display->setFont(ArialMT_Plain_10); 265 | display->drawString(0, 10, "Dst:"); 266 | display->drawString(64, 10, "Alt:"); 267 | display->drawString(0, 32, "Head:"); 268 | display->setFont(ArialMT_Plain_16); 269 | display->drawString(0, 20, String(adsbClient.getDistance()) + "km"); 270 | display->drawString(64, 20, adsbClient.getAltitude() + "ft"); 271 | display->drawString(0, 42, String(adsbClient.getHeading()) + "°"); 272 | 273 | drawHeading(display, 78, 52, adsbClient.getHeading()); 274 | 275 | } 276 | 277 | int8_t quality = getWifiQuality(); 278 | for (int8_t i = 0; i < 4; i++) { 279 | for (int8_t j = 0; j < 2 * (i + 1); j++) { 280 | if (quality > i * 25 || j == 0) { 281 | display->setPixel(120 + 2 * i, 63 - j); 282 | } 283 | } 284 | } 285 | 286 | } 287 | 288 | // converts the dBm to a range between 0 and 100% 289 | int8_t getWifiQuality() { 290 | int32_t dbm = WiFi.RSSI(); 291 | if(dbm <= -100) { 292 | return 0; 293 | } else if(dbm >= -50) { 294 | return 100; 295 | } else { 296 | return 2 * (dbm + 100); 297 | } 298 | } 299 | 300 | void drawCurrentAirplane1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 301 | if (adsbClient.isAircraftVisible()) { 302 | display->setTextAlignment(TEXT_ALIGN_LEFT); 303 | display->setFont(ArialMT_Plain_10); 304 | 305 | display->drawString(0 + x, 0 + y, "From: " + adsbClient.getFrom()); 306 | } 307 | } 308 | 309 | void drawCurrentAirplane2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 310 | if (adsbClient.isAircraftVisible()) { 311 | display->setTextAlignment(TEXT_ALIGN_LEFT); 312 | display->setFont(ArialMT_Plain_10); 313 | 314 | display->drawString(0 + x, 0 + y, "To: " + adsbClient.getTo()); 315 | } 316 | } 317 | 318 | void drawCurrentAirplane3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 319 | if (adsbClient.isAircraftVisible()) { 320 | display->setTextAlignment(TEXT_ALIGN_LEFT); 321 | display->setFont(ArialMT_Plain_10); 322 | 323 | display->drawString(0 + x, 0 + y, "Type: " + adsbClient.getAircraftType()); 324 | } 325 | } 326 | 327 | void drawHeading(OLEDDisplay *display, int x, int y, double heading) { 328 | int degrees[] = {0, 170, 190, 0}; 329 | display->drawCircle(x, y, 10); 330 | int radius = 8; 331 | for (int i = 0; i < 3; i++) { 332 | int x1 = cos((-450 + (heading + degrees[i])) * pi / 180.0) * radius + x; 333 | int y1 = sin((-450 + (heading + degrees[i])) * pi / 180.0) * radius + y; 334 | int x2 = cos((-450 + (heading + degrees[i + 1])) * pi / 180.0) * radius + x; 335 | int y2 = sin((-450 + (heading + degrees[i + 1])) * pi / 180.0) * radius + y; 336 | display->drawLine(x1, y1, x2, y2); 337 | 338 | } 339 | } 340 | 341 | void checkReadyForUpdate() { 342 | // Only do light work in ticker callback 343 | if (lastUpdate < millis() - currentUpdateInterval * 1000) { 344 | readyForUpdate = true; 345 | } 346 | } 347 | --------------------------------------------------------------------------------