├── src ├── ESPWiFi.h ├── ESPHTTPClient.h ├── NTPClient.h ├── Astronomy.h ├── TimeClient.h ├── ThingspeakClient.h ├── WorldClockClient.h ├── ThingspeakClient.cpp ├── AerisSunMoon.h ├── AerisObservations.h ├── OpenWeatherMapCurrent.h ├── MetOfficeClient.h ├── TimeClient.cpp ├── OpenWeatherMapForecast.h ├── NTPClient.cpp ├── AerisForecasts.h ├── SunMoonCalc.h ├── WorldClockClient.cpp ├── OpenWeatherMapOneCall.h ├── Astronomy.cpp ├── AerisSunMoon.cpp ├── OpenWeatherMapCurrent.cpp └── OpenWeatherMapForecast.cpp ├── resources ├── CurrentWeatherReflection.jpg └── ThingPulse-ESP8266-Weather-Station.jpeg ├── .gitignore ├── adjust_src_dir.py ├── library.properties ├── library.json ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml ├── workflows │ ├── platformio_publish.yaml │ └── main.yml └── ISSUE_TEMPLATE.md ├── lib └── readme.txt ├── License ├── platformio.ini ├── examples ├── WeatherStationDemo │ ├── WeatherStationImages.h │ └── WeatherStationDemo.ino ├── PlaneSpotterDemo │ ├── images.h │ ├── AdsbExchangeClient.h │ ├── AdsbExchangeClient.cpp │ └── PlaneSpotterDemo.ino ├── AstronomyDemo │ └── AstronomyDemo.ino ├── OpenWeatherMapOneCallDemo │ └── OpenWeatherMapOneCallDemo.ino ├── SunMoonCalcDemo │ └── SunMoonCalcDemo.ino ├── AerisSunMoonDemo │ └── AerisSunMoonDemo.ino ├── OpenWeatherMapCurrentDemo │ └── OpenWeatherMapCurrentDemo.ino ├── WorldClockDemo │ └── WorldClockDemo.ino ├── OpenWeatherMapForecastDemo │ └── OpenWeatherMapForecastDemo.ino ├── AerisObservationDemo │ └── AerisObservationDemo.ino └── AerisForecastsDemo │ └── AerisForecastsDemo.ino ├── CONTRIBUTING.md └── README.md /src/ESPWiFi.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP8266) 2 | #include 3 | #else 4 | #include 5 | #endif 6 | -------------------------------------------------------------------------------- /src/ESPHTTPClient.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP8266) 2 | #include 3 | #else 4 | #include 5 | #endif 6 | -------------------------------------------------------------------------------- /resources/CurrentWeatherReflection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThingPulse/esp8266-weather-station/HEAD/resources/CurrentWeatherReflection.jpg -------------------------------------------------------------------------------- /resources/ThingPulse-ESP8266-Weather-Station.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThingPulse/esp8266-weather-station/HEAD/resources/ThingPulse-ESP8266-Weather-Station.jpeg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | **/.DS_Store 3 | 4 | .metadata 5 | .project 6 | RemoteSystemsTempFiles 7 | 8 | .pio 9 | .pioenvs 10 | .piolibdeps 11 | .vscode 12 | .clang_complete 13 | .gcc-flags.json 14 | -------------------------------------------------------------------------------- /adjust_src_dir.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | Import("env") 4 | env['PROJECT_SRC_DIR'] = env['PROJECT_DIR'] + os.sep + "examples" + os.sep + env["PIOENV"] 5 | print("Setting the project directory to: {}".format(env['PROJECT_SRC_DIR'])) 6 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP8266 Weather Station 2 | version=2.3.0 3 | author=ThingPulse 4 | maintainer=ThingPulse 5 | sentence=ESP8266 based internet connected Weather Station 6 | paragraph=ESP8266 based internet connected Weather Station 7 | category=Display 8 | url=https://github.com/ThingPulse/esp8266-weather-station 9 | architectures=esp8266,esp32 10 | depends=Json Streaming Parser 11 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ESP8266 Weather Station", 3 | "keywords": "embedded, display, oled", 4 | "description": "A WiFi connected information display", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/ThingPulse/esp8266-weather-station" 8 | }, 9 | "authors": { 10 | "name": "Daniel Eichhorn, ThingPulse", 11 | "url": "https://thingpulse.com" 12 | }, 13 | "frameworks": "arduino", 14 | "platforms": [ 15 | "espressif8266", 16 | "espressif32" 17 | ], 18 | "version": "2.3.0" 19 | } 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes #\. 2 | 3 | Make sure all boxes are checked (add x inside the brackets) when you submit your contribution, remove this sentence before doing so. 4 | 5 | - [ ] This PR is compliant with the [other contributing guidelines](https://github.com/ThingPulse/esp8266-weather-station/blob/master/CONTRIBUTING.md) as well (if not, please describe why). 6 | - [ ] I have thoroughly tested my contribution. 7 | - [ ] Should this code require changes to documentation I will contribute those to [https://github.com/ThingPulse/docs](https://github.com/ThingPulse/docs). 8 | 9 | \ 10 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 180 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 14 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/platformio_publish.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10 5 | 6 | name: PlatformIO Publish 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v3 13 | - name: Install Python 14 | uses: actions/setup-python@v4 15 | with: 16 | python-version: '3.9' 17 | - name: Install PlatformIO Core 18 | run: pip install --upgrade platformio 19 | - name: Publish 20 | # requires the PLATFORMIO_AUTH_TOKEN env variable: https://docs.platformio.org/en/latest/envvars.html#envvar-PLATFORMIO_AUTH_TOKEN 21 | # currently msr's account 22 | run: pio pkg publish --owner thingpulse --no-interactive 23 | -------------------------------------------------------------------------------- /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 | More information about PlatformIO Library Dependency Finder 36 | - http://docs.platformio.org/page/librarymanager/ldf.html 37 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Use one of the two templates below and **delete the rest**. 2 | 3 | 8<------------------------ BUG REPORT ----------------------------------------- 4 | ### Expected behavior 5 | 6 | ### Actual behavior 7 | 8 | ``` 9 | Add serial console output here 10 | ``` 11 | 12 | ### Test code 13 | Provide a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) which will reproduce the problem. 14 | 15 | ```c 16 | // add code here 17 | ``` 18 | ### Weather Station version 19 | Which branch are you on? If you know the Git revision then add it here as well. 20 | 21 | ### Hardware 22 | Describe whether you run the code on the ThingPulse Weather Station Color kit or on some custom hardware. 23 | 24 | 8<------------------------ END BUG REPORT ------------------------------------- 25 | 26 | 27 | 8<------------------------ FEATURE REQUEST ------------------------------------ 28 | ### Missing feature 29 | 30 | ### Justification 31 | Tell us why you would like to see this feature added. 32 | 33 | ### Workarounds 34 | Are there any workarounds you currently have in place because the feature is missing? 35 | 36 | 8<------------------------ END FEATURE REQUEST -------------------------------- 37 | -------------------------------------------------------------------------------- /src/NTPClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Arduino.h" 4 | 5 | #include 6 | #include 7 | 8 | #define SEVENZYYEARS 2208988800UL 9 | #define NTP_PACKET_SIZE 48 10 | 11 | class NTPClient { 12 | private: 13 | WiFiUDP _udp; 14 | 15 | const char* _poolServerName = "time.nist.gov"; // Default time server 16 | int _port = 1337; 17 | int _timeOffset; 18 | 19 | unsigned int _updateInterval = 60000; // In ms 20 | 21 | unsigned long _currentEpoc; // In s 22 | unsigned long _lastUpdate = 0; // In ms 23 | 24 | byte _packetBuffer[NTP_PACKET_SIZE]; 25 | 26 | void sendNTPPacket(IPAddress _timeServerIP); 27 | 28 | public: 29 | NTPClient(int timeOffset); 30 | NTPClient(const char* poolServerName); 31 | NTPClient(const char* poolServerName, int timeOffset); 32 | NTPClient(const char* poolServerName, int timeOffset, int updateInterval); 33 | 34 | void begin(); 35 | void update(); 36 | void forceUpdate(); 37 | 38 | String getHours(); 39 | String getMinutes(); 40 | String getSeconds(); 41 | String getFormattedTime(); 42 | 43 | unsigned long getRawTime(); 44 | }; 45 | -------------------------------------------------------------------------------- /src/Astronomy.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #pragma once 25 | #include 26 | #include 27 | 28 | 29 | class Astronomy { 30 | private: 31 | 32 | public: 33 | typedef struct MoonData { 34 | uint8_t phase; 35 | double illumination; 36 | } MoonData; 37 | 38 | Astronomy(); 39 | 40 | uint8_t calculateMoonPhase(time_t timestamp); 41 | uint8_t calculateMoonPhase(uint16_t year, uint8_t month, uint8_t day); 42 | MoonData calculateMoonData(time_t timestamp); 43 | MoonData calculateMoonData(uint16_t year, uint8_t month, uint8_t day); 44 | 45 | }; 46 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # documentation at https://docs.platformio.org/en/latest/integration/ci/github-actions.html 2 | 3 | name: PlatformIO CI 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | example: 13 | - examples/AerisForecastsDemo 14 | - examples/AerisObservationDemo 15 | - examples/AerisSunMoonDemo 16 | - examples/AstronomyDemo 17 | - examples/OpenWeatherMapCurrentDemo 18 | - examples/OpenWeatherMapForecastDemo 19 | # - examples/PlaneSpotterDemo doesn't work on ESP32 due to WiFi Manager dependency 20 | - examples/SunMoonCalcDemo 21 | - examples/WeatherStationDemo 22 | - examples/WorldClockDemo 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Cache pip 26 | uses: actions/cache@v2 27 | with: 28 | path: ~/.cache/pip 29 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 30 | restore-keys: ${{ runner.os }}-pip- 31 | - name: Cache PlatformIO 32 | uses: actions/cache@v2 33 | with: 34 | path: ~/.platformio 35 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 36 | - name: Set up Python 37 | uses: actions/setup-python@v2 38 | - name: Install PlatformIO 39 | run: | 40 | python -m pip install --upgrade pip 41 | pip install --upgrade platformio 42 | - name: Install library dependencies 43 | run: pio lib -g install "JsonStreamingParser" "thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.2.0" "WifiManager@>=0.15.0-beta" 44 | - name: Run PlatformIO 45 | run: pio ci --lib="." --project-option="lib_deps=JsonStreamingParser" --board=nodemcuv2 --board=d1_mini --board=esp-wrover-kit 46 | env: 47 | PLATFORMIO_CI_SRC: ${{ matrix.example }} 48 | -------------------------------------------------------------------------------- /src/TimeClient.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by Daniel Eichhorn, ThingPulse 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 https://thingpulse.com 24 | */ 25 | #pragma once 26 | 27 | #include 28 | 29 | #define NTP_PACKET_SIZE 48 30 | 31 | class TimeClient { 32 | 33 | private: 34 | float myUtcOffset = 0; 35 | long localEpoc = 0; 36 | unsigned long localMillisAtUpdate; 37 | 38 | const char* ntpServerName = "time.nist.gov"; 39 | unsigned int localPort = 2390; 40 | 41 | byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets 42 | 43 | 44 | public: 45 | TimeClient(float utcOffset); 46 | void updateTime(); 47 | void setUtcOffset(float utcOffset); 48 | String getHours(); 49 | String getMinutes(); 50 | String getSeconds(); 51 | String getFormattedTime(); 52 | long getCurrentEpoch(); 53 | long getCurrentEpochWithUtcOffset(); 54 | 55 | }; 56 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; http://docs.platformio.org/page/projectconf.html 10 | 11 | 12 | ; ********************************************************************************************************************* 13 | ; 2021-06-13 14 | ; 15 | ; It is a pity PlatformIO does not support building such Arduino libraries locally out of the box. The current solution 16 | ; evolved out of a discussion at https://community.platformio.org/t/build-an-arduino-library-for-esp8266-locally/10656 17 | ; Occasionally the build fails because PIO somehow gets the dependency tree completely wrong. Running the build again 18 | ; usually fixes it. 19 | ; 20 | ; The default configuration will build *all* examples but you can easily uncomment the envs you do not need if you are 21 | ; working on just a single example. 22 | ; ********************************************************************************************************************* 23 | 24 | [platformio] 25 | lib_dir = $PROJECT_DIR 26 | 27 | [env] 28 | extra_scripts = pre:adjust_src_dir.py 29 | ; https://github.com/platformio/platform-espressif8266/releases/tag/v3.2.0 30 | platform = espressif8266@3.2.0 31 | board = d1_mini 32 | framework = arduino 33 | upload_speed = 921600 34 | board_build.f_cpu = 160000000L 35 | lib_deps = JsonStreamingParser 36 | thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.2.0 37 | 38 | ; comment the ones you do not need 39 | [env:AerisForecastsDemo] 40 | [env:AerisObservationDemo] 41 | [env:AerisSunMoonDemo] 42 | [env:AstronomyDemo] 43 | [env:OpenWeatherMapCurrentDemo] 44 | [env:OpenWeatherMapForecastDemo] 45 | [env:OpenWeatherMapOneCallDemo] 46 | [env:PlaneSpotterDemo] 47 | [env:SunMoonCalcDemo] 48 | [env:WeatherStationDemo] 49 | [env:WorldClockDemo] 50 | -------------------------------------------------------------------------------- /src/ThingspeakClient.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 MAX_FORECAST_PERIODS 7 34 | 35 | class ThingspeakClient: public JsonListener { 36 | private: 37 | // Thingspeak has a maximum of 8 fields 38 | String lastFields[8]; 39 | String fieldLabels[8]; 40 | String createdAt; 41 | boolean isHeader = true; 42 | String currentKey = ""; 43 | 44 | public: 45 | ThingspeakClient(); 46 | 47 | void getLastChannelItem(String channelId, String readApiKey); 48 | 49 | String getFieldLabel(int index); 50 | 51 | String getFieldValue(int index); 52 | 53 | String getCreatedAt(); 54 | 55 | virtual void whitespace(char c); 56 | 57 | virtual void startDocument(); 58 | 59 | virtual void key(String key); 60 | 61 | virtual void value(String value); 62 | 63 | virtual void endArray(); 64 | 65 | virtual void endObject(); 66 | 67 | virtual void endDocument(); 68 | 69 | virtual void startArray(); 70 | 71 | virtual void startObject(); 72 | }; 73 | -------------------------------------------------------------------------------- /examples/WeatherStationDemo/WeatherStationImages.h: -------------------------------------------------------------------------------- 1 | #define WiFi_Logo_width 60 2 | #define WiFi_Logo_height 36 3 | const uint8_t 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 uint8_t activeSymbole[] PROGMEM = { 31 | B00000000, 32 | B00000000, 33 | B00011000, 34 | B00100100, 35 | B01000010, 36 | B01000010, 37 | B00100100, 38 | B00011000 39 | }; 40 | 41 | const uint8_t inactiveSymbole[] PROGMEM = { 42 | B00000000, 43 | B00000000, 44 | B00000000, 45 | B00000000, 46 | B00011000, 47 | B00011000, 48 | B00000000, 49 | B00000000 50 | }; 51 | -------------------------------------------------------------------------------- /examples/PlaneSpotterDemo/images.h: -------------------------------------------------------------------------------- 1 | #define WiFi_Logo_width 60 2 | #define WiFi_Logo_height 36 3 | const uint8_t 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 uint8_t emptySymbol[] PROGMEM = { 31 | B00000000, 32 | B00000000, 33 | B00000000, 34 | B00000000, 35 | B00000000, 36 | B00000000, 37 | B00000000, 38 | B00000000 39 | }; 40 | 41 | const uint8_t activeSymbol[] PROGMEM = { 42 | B00000000, 43 | B00000000, 44 | B00011000, 45 | B00100100, 46 | B01000010, 47 | B01000010, 48 | B00100100, 49 | B00011000 50 | }; 51 | 52 | const uint8_t inactiveSymbol[] PROGMEM = { 53 | B00000000, 54 | B00000000, 55 | B00000000, 56 | B00000000, 57 | B00011000, 58 | B00011000, 59 | B00000000, 60 | B00000000 61 | }; 62 | -------------------------------------------------------------------------------- /src/WorldClockClient.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 | 34 | class WorldClockClient: public JsonListener { 35 | private: 36 | unsigned long millisOfDayAtUpdate = 0; 37 | unsigned long localMillisAtUpdate; 38 | boolean isHeader = true; 39 | String currentKey = ""; 40 | String myLanguage; 41 | String myCountry; 42 | String* myTimeZoneIds; 43 | int myNumberOfTimeZoneIds; 44 | String myDateFormat; 45 | 46 | int currentTimeZoneIndex; 47 | long* timeZoneOffsetToUtcMillis; 48 | 49 | public: 50 | WorldClockClient(String language, String country, String dateFormat, int numberOfTimeZones, String* timeZoneIds); 51 | 52 | void updateTime(); 53 | 54 | String getFormattedTime(int timeZoneIndex); 55 | 56 | String getHours(int timeZoneIndex); 57 | 58 | String getMinutes(int timeZoneIndex); 59 | 60 | String getSeconds(int timeZoneIndex); 61 | 62 | long getSecondsOfDay(int timeZoneIndex); 63 | 64 | virtual void whitespace(char c); 65 | 66 | virtual void startDocument(); 67 | 68 | virtual void key(String key); 69 | 70 | virtual void value(String value); 71 | 72 | virtual void endArray(); 73 | 74 | virtual void endObject(); 75 | 76 | virtual void endDocument(); 77 | 78 | virtual void startArray(); 79 | 80 | virtual void startObject(); 81 | }; 82 | -------------------------------------------------------------------------------- /examples/PlaneSpotterDemo/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/ThingspeakClient.cpp: -------------------------------------------------------------------------------- 1 | #include "ThingspeakClient.h" 2 | 3 | 4 | ThingspeakClient::ThingspeakClient() { 5 | 6 | } 7 | 8 | void ThingspeakClient::getLastChannelItem(String channelId, String readApiKey) { 9 | JsonStreamingParser parser; 10 | parser.setListener(this); 11 | WiFiClient client; 12 | 13 | // http://api.thingspeak.com/channels/CHANNEL_ID/feeds.json?results=2&api_key=API_KEY 14 | const char host[] = "api.thingspeak.com"; 15 | String path = "/channels/" + channelId +"/feeds.json?results=1&api_key=" + readApiKey; 16 | 17 | const int httpPort = 80; 18 | if (!client.connect(host, httpPort)) { 19 | Serial.println("connection failed"); 20 | return; 21 | } 22 | 23 | Serial.print("Requesting path: "); 24 | Serial.println(path); 25 | 26 | // This will send the request to the server 27 | client.print(String("GET ") + path + " HTTP/1.1\r\n" + 28 | "Host: " + host + "\r\n" + 29 | "Connection: close\r\n\r\n"); 30 | 31 | int retryCounter = 0; 32 | while(!client.available()) { 33 | Serial.println("."); 34 | delay(1000); 35 | retryCounter++; 36 | if (retryCounter > 10) { 37 | return; 38 | } 39 | } 40 | 41 | boolean isBody = false; 42 | char c; 43 | 44 | client.setNoDelay(false); 45 | while (client.connected() || client.available()) { 46 | if (client.available()) { 47 | c = client.read(); 48 | if (c == '{' || c == '[') { 49 | isBody = true; 50 | } 51 | if (isBody) { 52 | parser.parse(c); 53 | } 54 | } 55 | // give WiFi and TCP/IP libraries a chance to handle pending events 56 | yield(); 57 | } 58 | client.stop(); 59 | } 60 | 61 | void ThingspeakClient::whitespace(char c) { 62 | 63 | } 64 | 65 | void ThingspeakClient::startDocument() { 66 | 67 | } 68 | 69 | void ThingspeakClient::key(String key) { 70 | if (key == "channel") { 71 | isHeader = true; 72 | } else if (key == "feeds") { 73 | isHeader = false; 74 | } 75 | currentKey = key; 76 | } 77 | 78 | void ThingspeakClient::value(String value) { 79 | //Serial.println(currentKey +": " + value); 80 | 81 | for (int i = 1; i < 9; i++) { 82 | String fieldKey = "field" + String(i); 83 | 84 | if (currentKey == fieldKey) { 85 | if (isHeader) { 86 | fieldLabels[i-1] = value; 87 | } else { 88 | lastFields[i-1] = value; 89 | Serial.println(fieldKey + ": " + value); 90 | } 91 | 92 | } 93 | } 94 | 95 | 96 | } 97 | 98 | 99 | String ThingspeakClient::getFieldLabel(int index) { 100 | return fieldLabels[index]; 101 | } 102 | 103 | String ThingspeakClient::getFieldValue(int index) { 104 | return lastFields[index]; 105 | } 106 | 107 | String ThingspeakClient::getCreatedAt() { 108 | return createdAt; 109 | } 110 | 111 | void ThingspeakClient::endArray() { 112 | 113 | } 114 | 115 | void ThingspeakClient::endObject() { 116 | 117 | } 118 | 119 | void ThingspeakClient::endDocument() { 120 | 121 | } 122 | 123 | void ThingspeakClient::startArray() { 124 | 125 | } 126 | 127 | void ThingspeakClient::startObject() { 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/AerisSunMoon.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #pragma once 25 | #include 26 | #include 27 | #include 28 | 29 | typedef struct AerisSunMoonData { 30 | uint64_t sunRise; // "rise":1493291184, 31 | String sunRiseISO; // "riseISO":"2017-04-27T06:06:24-05:00", 32 | uint64_t sunSet; // "set":1493342079, 33 | String sunSetISO; //"setISO":"2017-04-27T20:14:39-05:00", 34 | uint64_t sunTransit; // "transit":1493316631, 35 | String sunTransitISO; // "transitISO":"2017-04-27T13:10:31-05:00", 36 | boolean midnightSun; // "midnightSun":false, 37 | boolean polarNight; // "polarNight":false, 38 | uint64_t moonRise; //"rise":1493295480, 39 | String moonRiseISO; // "riseISO":"2017-04-27T07:18:00-05:00", 40 | uint64_t moonSet; // "set":1493347800, 41 | String moonSetISO; // "setISO":"2017-04-27T21:50:00-05:00", 42 | uint64_t moonTransit; // "transit":1493321340, 43 | String moonTransitISO; // "transitISO":"2017-04-27T14:29:00-05:00", 44 | uint64_t moonUnderfoot; // "underfoot":1493276400, 45 | String moonUnderfootISO; // "underfootISO":"2017-04-27T02:00:00-05:00", 46 | float moonPhase; // "phase":0.0516, 47 | String moonPhaseName; // "name":"waxing crescent", 48 | uint8_t moonIllum; // "illum":3, 49 | float moonAge; // "age":1.52, 50 | float moonAngle; // "angle":0.55 51 | } AerisSunMoonData; 52 | 53 | class AerisSunMoon: public JsonListener { 54 | private: 55 | const String host = "api.aerisapi.com"; 56 | const uint8_t port = 80; 57 | boolean isMetric = true; 58 | String currentKey; 59 | String currentParent; 60 | AerisSunMoonData *sunMoonData; 61 | 62 | 63 | void doUpdate(AerisSunMoonData *sunMoonData, String url); 64 | 65 | public: 66 | AerisSunMoon(); 67 | void updateSunMoon(AerisSunMoonData *sunMoonData, String clientId, String clientKey, String location); 68 | 69 | virtual void whitespace(char c); 70 | 71 | virtual void startDocument(); 72 | 73 | virtual void key(String key); 74 | 75 | virtual void value(String value); 76 | 77 | virtual void endArray(); 78 | 79 | virtual void endObject(); 80 | 81 | virtual void endDocument(); 82 | 83 | virtual void startArray(); 84 | 85 | virtual void startObject(); 86 | }; 87 | -------------------------------------------------------------------------------- /src/AerisObservations.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #pragma once 25 | //#define NIGHTICONS 26 | #include 27 | #include 28 | #include 29 | 30 | typedef struct AerisObservationsData { 31 | uint64_t timestamp; 32 | String dateTimeISO; 33 | int16_t tempC; 34 | int16_t tempF; 35 | int16_t dewpointC; 36 | int16_t dewpointF; 37 | uint8_t humidity; 38 | uint16_t pressureMB; 39 | float pressureIN; 40 | uint16_t spressureMB; 41 | float spressureIN; 42 | uint16_t altimeterMB; 43 | float altimeterIN; 44 | uint16_t windSpeedKTS; 45 | uint16_t windSpeedKPH; 46 | uint16_t windSpeedMPH; 47 | uint16_t windDirDEG; 48 | String windDir; 49 | uint16_t windGustKTS; 50 | uint16_t windGustKPH; 51 | uint16_t windGustMPH; 52 | String flightRule; 53 | float visibilityKM; 54 | float visibilityMI; 55 | String weather; 56 | String weatherShort; 57 | String weatherCoded; 58 | String weatherPrimary; 59 | String weatherPrimaryCoded; 60 | String cloudsCoded; 61 | String icon; 62 | String iconMeteoCon; 63 | int16_t heatindexC; 64 | int16_t heatindexF; 65 | int16_t windchillC; 66 | int16_t windchillF; 67 | int16_t feelslikeC; 68 | int16_t feelslikeF; 69 | boolean isDay; 70 | uint64_t sunrise; 71 | String sunriseISO; 72 | uint64_t sunset; 73 | String sunsetISO; 74 | uint16_t snowDepthCM; 75 | uint16_t snowDepthIN; 76 | uint16_t precipMM; 77 | uint16_t precipIN; 78 | uint16_t solradWM2; 79 | String solradMethod; 80 | uint16_t light; 81 | uint16_t sky; 82 | } AerisObservationsData; 83 | 84 | class AerisObservations: public JsonListener { 85 | private: 86 | const String host = "api.aerisapi.com"; 87 | const uint8_t port = 80; 88 | boolean isMetric = true; 89 | String currentKey; 90 | String currentParent; 91 | AerisObservationsData *observations; 92 | 93 | 94 | void doUpdate(AerisObservationsData *conditions, String url); 95 | 96 | public: 97 | AerisObservations(); 98 | void updateObservations(AerisObservationsData *observations, String clientId, String clientKey, String location); 99 | 100 | String getMeteoconIcon(String icon); 101 | virtual void whitespace(char c); 102 | 103 | virtual void startDocument(); 104 | 105 | virtual void key(String key); 106 | 107 | virtual void value(String value); 108 | 109 | virtual void endArray(); 110 | 111 | virtual void endObject(); 112 | 113 | virtual void endDocument(); 114 | 115 | virtual void startArray(); 116 | 117 | virtual void startObject(); 118 | }; 119 | -------------------------------------------------------------------------------- /examples/AstronomyDemo/AstronomyDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse 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 https://thingpulse.com 24 | */ 25 | 26 | #include 27 | #if defined(ESP8266) 28 | #include 29 | #else 30 | #include 31 | #endif 32 | #include 33 | #include 34 | #include "Astronomy.h" 35 | 36 | /** 37 | * WiFi Settings 38 | */ 39 | const char* WIFI_SSID = "yourssid"; 40 | const char* WIFI_PASSWORD = "yourpassw0rd"; 41 | 42 | // initiate the WifiClient 43 | WiFiClient wifiClient; 44 | 45 | #define UTC_OFFSET 1 46 | #define DST_OFFSET 1 47 | #define NTP_SERVERS "us.pool.ntp.org", "time.nist.gov", "pool.ntp.org" 48 | #define EPOCH_1970 49 | 50 | const String MOON_PHASES[] = {"New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous", 51 | "Full Moon", "Waning Gibbous", "Third quarter", "Waning Crescent"}; 52 | 53 | Astronomy astronomy; 54 | 55 | /** 56 | * Helping funtions 57 | */ 58 | void connectWifi() { 59 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 60 | Serial.print("Connecting to "); 61 | Serial.println(WIFI_SSID); 62 | while (WiFi.status() != WL_CONNECTED) { 63 | delay(500); 64 | Serial.print("."); 65 | } 66 | Serial.println(""); 67 | Serial.println("WiFi connected!"); 68 | Serial.println(WiFi.localIP()); 69 | Serial.println(); 70 | } 71 | 72 | void setup() { 73 | Serial.begin(115200); 74 | delay(500); 75 | connectWifi(); 76 | Serial.println(); 77 | configTime(UTC_OFFSET, DST_OFFSET * 60, NTP_SERVERS); 78 | Serial.println("Syncing time..."); 79 | while(time(nullptr) <= 10000) { 80 | Serial.print("."); 81 | delay(100); 82 | } 83 | Serial.println("Time sync'ed"); 84 | 85 | // prepare the input values 86 | time_t now = time(nullptr); 87 | struct tm * timeinfo = localtime (&now); 88 | 89 | // now calculate the moon phase by the timestamp 90 | uint8_t phase = astronomy.calculateMoonPhase(now); 91 | Serial.printf("Moon Phase: %s\n", MOON_PHASES[phase].c_str()); 92 | 93 | // another option is to use year, month and day (in month) 94 | phase = astronomy.calculateMoonPhase(timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday); 95 | Serial.printf("Moon Phase: %s\n", MOON_PHASES[phase].c_str()); 96 | 97 | // if you also need illumination there are two more methods which return a struct 98 | Astronomy::MoonData moonData = astronomy.calculateMoonData(now); 99 | Serial.printf("Moon Phase: %s\n", MOON_PHASES[moonData.phase].c_str()); 100 | Serial.printf("Illumination: %f\n", moonData.illumination); 101 | 102 | moonData = astronomy.calculateMoonData(timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday); 103 | Serial.printf("Moon Phase: %s\n", MOON_PHASES[moonData.phase].c_str()); 104 | Serial.printf("Illumination: %f\n", moonData.illumination); 105 | } 106 | 107 | void loop() { 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/OpenWeatherMapCurrent.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #pragma once 25 | #include 26 | #include 27 | 28 | typedef struct OpenWeatherMapCurrentData { 29 | // "lon": 8.54, 30 | float lon; 31 | // "lat": 47.37 32 | float lat; 33 | // "id": 521, 34 | uint16_t weatherId; 35 | // "main": "Rain", 36 | String main; 37 | // "description": "shower rain", 38 | String description; 39 | // "icon": "09d" 40 | String icon; 41 | String iconMeteoCon; 42 | // "temp": 290.56, 43 | float temp; 44 | // feels_like 290.87 45 | float feelsLike; 46 | // "pressure": 1013, 47 | uint16_t pressure; 48 | // "humidity": 87, 49 | uint8_t humidity; 50 | // "temp_min": 289.15, 51 | float tempMin; 52 | // "temp_max": 292.15 53 | float tempMax; 54 | // visibility: 10000, 55 | uint16_t visibility; 56 | // "wind": {"speed": 1.5}, 57 | float windSpeed; 58 | // "wind": {deg: 226.505}, 59 | float windDeg; 60 | // "clouds": {"all": 90}, 61 | uint8_t clouds; 62 | // "dt": 1527015000, 63 | uint32_t observationTime; 64 | // "country": "CH", 65 | String country; 66 | // "sunrise": 1526960448, 67 | uint32_t sunrise; 68 | // "sunset": 1527015901 69 | uint32_t sunset; 70 | // "name": "Zurich", 71 | String cityName; 72 | } OpenWeatherMapCurrentData; 73 | 74 | class OpenWeatherMapCurrent: public JsonListener { 75 | private: 76 | const String host = "api.openweathermap.org"; 77 | const uint8_t port = 80; 78 | String currentKey; 79 | String currentParent; 80 | OpenWeatherMapCurrentData *data; 81 | uint8_t weatherItemCounter = 0; 82 | boolean metric = true; 83 | String language; 84 | 85 | void doUpdate(OpenWeatherMapCurrentData *data, String path); 86 | String buildPath(String appId, String locationParameter); 87 | 88 | public: 89 | OpenWeatherMapCurrent(); 90 | // deprecated as per https://openweathermap.org/current#builtin 91 | void updateCurrent(OpenWeatherMapCurrentData *data, String appId, String location); 92 | void updateCurrent(OpenWeatherMapCurrentData *data, String appId, float lat, float lon); 93 | // deprecated as per https://openweathermap.org/current#builtin 94 | void updateCurrentById(OpenWeatherMapCurrentData *data, String appId, String locationId); 95 | 96 | void setMetric(boolean metric) {this->metric = metric;} 97 | boolean isMetric() { return metric; } 98 | void setLanguage(String language) { this->language = language; } 99 | String getLanguage() { return language; } 100 | 101 | String getMeteoconIcon(String icon); 102 | virtual void whitespace(char c); 103 | 104 | virtual void startDocument(); 105 | 106 | virtual void key(String key); 107 | 108 | virtual void value(String value); 109 | 110 | virtual void endArray(); 111 | 112 | virtual void endObject(); 113 | 114 | virtual void endDocument(); 115 | 116 | virtual void startArray(); 117 | 118 | virtual void startObject(); 119 | }; 120 | -------------------------------------------------------------------------------- /src/MetOfficeClient.h: -------------------------------------------------------------------------------- 1 | /* The MIT License (MIT) 2 | * 3 | * Copyright (c) 2017 by Will Jenkins 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 | * Based on WundergroundClient created by Daniel Eichhorn: 24 | * https://github.com/squix78/esp8266-weather-station 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | 32 | class MetOfficeClient: public JsonListener { 33 | private: 34 | String currentKey; 35 | String currentParent = ""; 36 | String currentDate = ""; 37 | String currentForecast = ""; 38 | String secondForecast = ""; 39 | String thirdForecast = ""; 40 | String fourthForecast = ""; 41 | String locationName = ""; 42 | String temperature = ""; 43 | String feelsLikeTemp = ""; 44 | String windDirection = ""; 45 | String windSpeed = ""; 46 | String windGust = ""; 47 | String maxUvIndex = ""; 48 | String weatherType = ""; 49 | String precipProb = ""; 50 | String forecastArray[36] = { }; // String array to hold the segmented forecast data for the required 4 periods 51 | int count = 0; // Count used to populate the above array with required forecast values on parsing 52 | 53 | void doUpdate(String url); 54 | 55 | public: 56 | MetOfficeClient(); 57 | virtual void whitespace(char c); 58 | virtual void startDocument(); 59 | virtual void key(String key); 60 | virtual void value(String value); 61 | virtual void endArray(); 62 | virtual void endObject(); 63 | virtual void endDocument(); 64 | virtual void startArray(); 65 | virtual void startObject(); 66 | 67 | void updateConditions(String ThirdForecastPeriod, int location, String apiKey); 68 | 69 | String getLocationName(); 70 | String getCurrentDate(); 71 | String getCurrentForecastTime(); 72 | String getCurrentWindDirection(); 73 | String getCurrentFeelsLikeTemp(); 74 | String getCurrentWindGust(); 75 | String getCurrentPrecipProb(); 76 | String getCurrentWindSpeed(); 77 | String getCurrentTemperature(); 78 | String getCurrentWeatherType(); 79 | String getCurrentMaxUvIndex(); 80 | String getSecondForecastTime(); 81 | String getSecondWindDirection(); 82 | String getSecondFeelsLikeTemp(); 83 | String getSecondWindGust(); 84 | String getSecondPrecipProb(); 85 | String getSecondWindSpeed(); 86 | String getSecondTemperature(); 87 | String getSecondWeatherType(); 88 | String getSecondMaxUvIndex(); 89 | String getThirdForecastTime(); 90 | String getThirdWindDirection(); 91 | String getThirdFeelsLikeTemp(); 92 | String getThirdWindGust(); 93 | String getThirdPrecipProb(); 94 | String getThirdWindSpeed(); 95 | String getThirdTemperature(); 96 | String getThirdWeatherType(); 97 | String getThirdMaxUvIndex(); 98 | String getFourthForecastTime(); 99 | String getFourthWindDirection(); 100 | String getFourthFeelsLikeTemp(); 101 | String getFourthWindGust(); 102 | String getFourthPrecipProb(); 103 | String getFourthWindSpeed(); 104 | String getFourthTemperature(); 105 | String getFourthWeatherType(); 106 | String getFourthMaxUvIndex(); 107 | String getWeatherIconName(String weatherType); 108 | 109 | }; 110 | -------------------------------------------------------------------------------- /src/TimeClient.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by Daniel Eichhorn, ThingPulse 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 https://thingpulse.com 24 | */ 25 | 26 | #include "TimeClient.h" 27 | 28 | TimeClient::TimeClient(float utcOffset) { 29 | myUtcOffset = utcOffset; 30 | } 31 | 32 | void TimeClient::setUtcOffset(float utcOffset) { 33 | myUtcOffset = utcOffset; 34 | } 35 | 36 | void TimeClient::updateTime() { 37 | WiFiClient client; 38 | const int httpPort = 80; 39 | if (!client.connect("google.com", httpPort)) { 40 | Serial.println("connection failed"); 41 | return; 42 | } 43 | 44 | // This will send the request to the server 45 | client.print(String("GET / HTTP/1.1\r\n") + 46 | String("Host: google.com\r\n") + 47 | String("Connection: close\r\n\r\n")); 48 | int repeatCounter = 0; 49 | while(!client.available() && repeatCounter < 10) { 50 | delay(1000); 51 | Serial.println("."); 52 | repeatCounter++; 53 | } 54 | 55 | String line; 56 | client.setNoDelay(false); 57 | while (client.connected() || client.available()) { 58 | if (client.available()) { 59 | line = client.readStringUntil('\n'); 60 | line.toUpperCase(); 61 | // example: 62 | // date: Thu, 19 Nov 2015 20:25:40 GMT 63 | if (line.startsWith("DATE: ")) { 64 | Serial.println(line.substring(23, 25) + ":" + line.substring(26, 28) + ":" + line.substring(29, 31)); 65 | int parsedHours = line.substring(23, 25).toInt(); 66 | int parsedMinutes = line.substring(26, 28).toInt(); 67 | int parsedSeconds = line.substring(29, 31).toInt(); 68 | Serial.println(String(parsedHours) + ":" + String(parsedMinutes) + ":" + String(parsedSeconds)); 69 | 70 | localEpoc = (parsedHours * 60 * 60 + parsedMinutes * 60 + parsedSeconds); 71 | Serial.println(localEpoc); 72 | localMillisAtUpdate = millis(); 73 | } 74 | } 75 | // give WiFi and TCP/IP libraries a chance to handle pending events 76 | yield(); 77 | } 78 | client.stop(); 79 | } 80 | 81 | String TimeClient::getHours() { 82 | if (localEpoc == 0) { 83 | return "--"; 84 | } 85 | int hours = ((getCurrentEpochWithUtcOffset() % 86400L) / 3600) % 24; 86 | if (hours < 10) { 87 | return "0" + String(hours); 88 | } 89 | return String(hours); // print the hour (86400 equals secs per day) 90 | 91 | } 92 | String TimeClient::getMinutes() { 93 | if (localEpoc == 0) { 94 | return "--"; 95 | } 96 | int minutes = ((getCurrentEpochWithUtcOffset() % 3600) / 60); 97 | if (minutes < 10 ) { 98 | // In the first 10 minutes of each hour, we'll want a leading '0' 99 | return "0" + String(minutes); 100 | } 101 | return String(minutes); 102 | } 103 | String TimeClient::getSeconds() { 104 | if (localEpoc == 0) { 105 | return "--"; 106 | } 107 | int seconds = getCurrentEpochWithUtcOffset() % 60; 108 | if ( seconds < 10 ) { 109 | // In the first 10 seconds of each minute, we'll want a leading '0' 110 | return "0" + String(seconds); 111 | } 112 | return String(seconds); 113 | } 114 | 115 | String TimeClient::getFormattedTime() { 116 | return getHours() + ":" + getMinutes() + ":" + getSeconds(); 117 | } 118 | 119 | long TimeClient::getCurrentEpoch() { 120 | return localEpoc + ((millis() - localMillisAtUpdate) / 1000); 121 | } 122 | 123 | long TimeClient::getCurrentEpochWithUtcOffset() { 124 | return fmod(round(getCurrentEpoch() + 3600 * myUtcOffset + 86400L), 86400L); 125 | } 126 | -------------------------------------------------------------------------------- /src/OpenWeatherMapForecast.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #pragma once 25 | #include 26 | #include 27 | #include 28 | 29 | typedef struct OpenWeatherMapForecastData { 30 | // {"dt":1527066000, 31 | uint32_t observationTime; 32 | // "main":{ 33 | // "temp":17.35, 34 | float temp; 35 | // "feels_like": 16.99, 36 | float feelsLike; 37 | // "temp_min":16.89, 38 | float tempMin; 39 | // "temp_max":17.35, 40 | float tempMax; 41 | // "pressure":970.8, 42 | float pressure; 43 | // "sea_level":1030.62, 44 | float pressureSeaLevel; 45 | // "grnd_level":970.8, 46 | float pressureGroundLevel; 47 | // "humidity":97, 48 | uint8_t humidity; 49 | // "temp_kf":0.46 50 | // },"weather":[{ 51 | // "id":802, 52 | uint16_t weatherId; 53 | // "main":"Clouds", 54 | String main; 55 | // "description":"scattered clouds", 56 | String description; 57 | // "icon":"03d" 58 | String icon; 59 | String iconMeteoCon; 60 | // }],"clouds":{"all":44}, 61 | uint8_t clouds; 62 | // "wind":{ 63 | // "speed":1.77, 64 | float windSpeed; 65 | // "deg":207.501 66 | float windDeg; 67 | // rain: {3h: 0.055}, 68 | float rain; 69 | // },"sys":{"pod":"d"} 70 | // dt_txt: "2018-05-23 09:00:00" 71 | String observationTimeText; 72 | 73 | } OpenWeatherMapForecastData; 74 | 75 | class OpenWeatherMapForecast: public JsonListener { 76 | private: 77 | const String host = "api.openweathermap.org"; 78 | const uint8_t port = 80; 79 | String currentKey; 80 | String currentParent; 81 | OpenWeatherMapForecastData *data; 82 | uint8_t weatherItemCounter = 0; 83 | uint8_t maxForecasts; 84 | uint8_t currentForecast; 85 | boolean metric = true; 86 | String language = "en"; 87 | const uint8_t *allowedHours; 88 | uint8_t allowedHoursCount = 0; 89 | boolean isCurrentForecastAllowed = true; 90 | 91 | uint8_t doUpdate(OpenWeatherMapForecastData *data, String path); 92 | String buildPath(String appId, String locationParameter); 93 | 94 | public: 95 | OpenWeatherMapForecast(); 96 | // deprecated as per https://openweathermap.org/current#builtin 97 | uint8_t updateForecasts(OpenWeatherMapForecastData *data, String appId, String location, uint8_t maxForecasts); 98 | uint8_t updateForecasts(OpenWeatherMapForecastData *data, String appId, float lat, float lon, uint8_t maxForecasts); 99 | // deprecated as per https://openweathermap.org/current#builtin 100 | uint8_t updateForecastsById(OpenWeatherMapForecastData *data, String appId, String locationId, uint8_t maxForecasts); 101 | 102 | void setMetric(boolean metric) { this->metric = metric; } 103 | boolean isMetric() { return this->metric; } 104 | void setLanguage(String language) { this->language = language; } 105 | String getLanguage() { return this->language; } 106 | void setAllowedHours(const uint8_t *allowedHours, uint8_t allowedHoursCount) { 107 | this->allowedHours = allowedHours; 108 | this->allowedHoursCount = allowedHoursCount; 109 | } 110 | 111 | 112 | String getMeteoconIcon(String icon); 113 | virtual void whitespace(char c); 114 | 115 | virtual void startDocument(); 116 | 117 | virtual void key(String key); 118 | 119 | virtual void value(String value); 120 | 121 | virtual void endArray(); 122 | 123 | virtual void endObject(); 124 | 125 | virtual void endDocument(); 126 | 127 | virtual void startArray(); 128 | 129 | virtual void startObject(); 130 | }; 131 | -------------------------------------------------------------------------------- /examples/OpenWeatherMapOneCallDemo/OpenWeatherMapOneCallDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2020 by Chris Klinger, https://chrisklinger.de 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 | 24 | #include 25 | 26 | #if defined(ESP8266) 27 | #include 28 | #else 29 | #include 30 | #endif 31 | #include 32 | #include 33 | #include "OpenWeatherMapOneCall.h" 34 | 35 | // See https://docs.thingpulse.com/how-tos/openweathermap-key/ 36 | String OPEN_WEATHER_MAP_APP_ID = "changeme"; 37 | /* 38 | Go to https://www.latlong.net/ and search for a location. Go through the 39 | result set and select the entry closest to the actual location you want to display 40 | data for. Use Latitude and Longitude values here. 41 | */ 42 | float OPEN_WEATHER_MAP_LOCATTION_LAT = 52.520008; // Berlin, DE 43 | float OPEN_WEATHER_MAP_LOCATTION_LON = 13.404954; // Berlin, DE 44 | /* 45 | Arabic - ar, Bulgarian - bg, Catalan - ca, Czech - cz, German - de, Greek - el, 46 | English - en, Persian (Farsi) - fa, Finnish - fi, French - fr, Galician - gl, 47 | Croatian - hr, Hungarian - hu, Italian - it, Japanese - ja, Korean - kr, 48 | Latvian - la, Lithuanian - lt, Macedonian - mk, Dutch - nl, Polish - pl, 49 | Portuguese - pt, Romanian - ro, Russian - ru, Swedish - se, Slovak - sk, 50 | Slovenian - sl, Spanish - es, Turkish - tr, Ukrainian - ua, Vietnamese - vi, 51 | Chinese Simplified - zh_cn, Chinese Traditional - zh_tw. 52 | */ 53 | String OPEN_WEATHER_MAP_LANGUAGE = "en"; 54 | boolean IS_METRIC = false; 55 | 56 | /** 57 | * WiFi Settings 58 | */ 59 | #if defined(ESP8266) 60 | const char* ESP_HOST_NAME = "esp-" + ESP.getFlashChipId(); 61 | #else 62 | const char* ESP_HOST_NAME = "esp-" + ESP.getEfuseMac(); 63 | #endif 64 | const char* WIFI_SSID = "yourssid"; 65 | const char* WIFI_PASSWORD = "yourpassw0rd"; 66 | 67 | // initiate the WifiClient 68 | WiFiClient wifiClient; 69 | 70 | 71 | /** 72 | * Helping funtions 73 | */ 74 | void connectWifi() { 75 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 76 | Serial.print("Connecting to "); 77 | Serial.println(WIFI_SSID); 78 | while (WiFi.status() != WL_CONNECTED) { 79 | delay(500); 80 | Serial.print("."); 81 | } 82 | Serial.println(""); 83 | Serial.println("WiFi connected!"); 84 | Serial.println(WiFi.localIP()); 85 | Serial.println(); 86 | } 87 | 88 | OpenWeatherMapOneCallData openWeatherMapOneCallData; 89 | 90 | /** 91 | * SETUP 92 | */ 93 | void setup() { 94 | 95 | Serial.begin(115200); 96 | delay(500); 97 | connectWifi(); 98 | 99 | Serial.println(); 100 | 101 | OpenWeatherMapOneCall *oneCallClient = new OpenWeatherMapOneCall(); 102 | oneCallClient->setMetric(IS_METRIC); 103 | oneCallClient->setLanguage(OPEN_WEATHER_MAP_LANGUAGE); 104 | 105 | long executionStart = millis(); 106 | oneCallClient->update(&openWeatherMapOneCallData, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATTION_LAT, OPEN_WEATHER_MAP_LOCATTION_LON); 107 | delete oneCallClient; 108 | oneCallClient = nullptr; 109 | 110 | Serial.println( "Current Weather: "); 111 | Serial.println( String(openWeatherMapOneCallData.current.temp, 1) + (IS_METRIC ? "°C" : "°F") ); 112 | Serial.println( openWeatherMapOneCallData.current.weatherDescription ); 113 | 114 | Serial.println( "Forecasts: "); 115 | 116 | for(int i = 0; i < 5; i++) 117 | { 118 | if(openWeatherMapOneCallData.daily[i].dt > 0) { 119 | Serial.println("dt: " + String(openWeatherMapOneCallData.daily[i].dt) ); 120 | Serial.println("temp: " + String(openWeatherMapOneCallData.daily[i].tempDay, 1) + (IS_METRIC ? "°C" : "°F") ); 121 | Serial.println("desc: " + openWeatherMapOneCallData.daily[i].weatherDescription); 122 | Serial.println(); 123 | } 124 | } 125 | 126 | } 127 | 128 | 129 | /** 130 | * LOOP 131 | */ 132 | void loop() { 133 | 134 | } 135 | -------------------------------------------------------------------------------- /examples/SunMoonCalcDemo/SunMoonCalcDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #include 25 | #if defined(ESP8266) 26 | #include 27 | #else 28 | #include 29 | #endif 30 | #include 31 | #include 32 | #include "SunMoonCalc.h" 33 | 34 | /** 35 | * WiFi Settings 36 | */ 37 | const char* WIFI_SSID = "yourssid"; 38 | const char* WIFI_PASSWORD = "yourpassw0rd"; 39 | 40 | // initiate the WifiClient 41 | WiFiClient wifiClient; 42 | 43 | /** 44 | * Helper funtions 45 | */ 46 | void connectWifi() { 47 | Serial.print("Connecting to "); 48 | Serial.println(WIFI_SSID); 49 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 50 | while (WiFi.status() != WL_CONNECTED) { 51 | delay(500); 52 | Serial.print("."); 53 | } 54 | Serial.println(""); 55 | Serial.println("WiFi connected!"); 56 | Serial.println(WiFi.localIP()); 57 | } 58 | 59 | void printResult(SunMoonCalc::Result result) { 60 | Serial.println("Sun"); 61 | Serial.println("\tRise: " + formatTime(result.sun.rise)); 62 | Serial.println("\tNoon: " + formatTime(result.sun.transit)); 63 | Serial.println("\tSet: " + formatTime(result.sun.set)); 64 | Serial.printf("\tAzimuth: %f°\n", result.sun.azimuth); 65 | Serial.printf("\tElevation: %f°\n", result.sun.elevation); 66 | Serial.printf("\tDistance: %fkm\n", result.sun.distance); 67 | Serial.println("Moon"); 68 | Serial.println("\tRise: " + formatTime(result.moon.rise)); 69 | Serial.println("\tNoon: " + formatTime(result.moon.transit)); 70 | Serial.println("\tSet: " + formatTime(result.moon.set)); 71 | Serial.printf("\tAzimuth: %f°\n", result.moon.azimuth); 72 | Serial.printf("\tElevation: %f°\n", result.moon.elevation); 73 | Serial.printf("\tDistance: %fkm", result.moon.distance); 74 | Serial.printf("\tAge: %f days\n", result.moon.age); 75 | Serial.printf("\tIllumination: %f%\n", result.moon.illumination * 100); 76 | Serial.println("\tPhase: " + result.moon.phase.name); 77 | Serial.printf("\tBright limb angle: %frad\n", result.moon.brightLimbAngle); 78 | Serial.printf("\tPosition angle of axis: %frad\n", result.moon.axisPositionAngle); 79 | Serial.printf("\tParallactic angle: %frad\n", result.moon.parallacticAngle); 80 | } 81 | 82 | String padWithZeroBelowTen(int d) { 83 | return d < 10 ? "0" + String(d) : String(d); 84 | } 85 | 86 | String formatTime(time_t timestamp) { 87 | tm *date = gmtime(×tamp); 88 | String year = "" + String(date->tm_year + 1900); 89 | String month = padWithZeroBelowTen(date->tm_mon + 1); 90 | String day = padWithZeroBelowTen(date->tm_mday); 91 | return year + "-" + month + "-" + day + " " + padWithZeroBelowTen(date->tm_hour) + ":" + 92 | padWithZeroBelowTen(date->tm_min) + ":" + padWithZeroBelowTen(date->tm_sec) + " UTC"; 93 | } 94 | // END helper functions 95 | 96 | void setup() { 97 | Serial.begin(115200); 98 | Serial.println(); 99 | delay(500); 100 | connectWifi(); 101 | Serial.println(); 102 | 103 | Serial.println("Syncing time..."); 104 | configTime(0, 0, "pool.ntp.org"); 105 | // some explanations about this POSIX format: https://www.ibm.com/developerworks/aix/library/au-aix-posix/ 106 | // -> represents a central European timezone identifier such as Europe/Berlin 107 | setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 0); 108 | while(time(nullptr) <= 100000) { 109 | Serial.print("."); 110 | delay(100); 111 | } 112 | Serial.println("Time sync'ed"); 113 | 114 | // prepare the time input value 115 | time_t tnow = time(nullptr); 116 | Serial.println(String(ctime(&tnow))); 117 | 118 | // 'now' has to be UTC, lat/lng in degrees not raadians 119 | SunMoonCalc smCalc = SunMoonCalc(tnow, 47.366, 8.533); 120 | const SunMoonCalc::Result result = smCalc.calculateSunAndMoonData(); 121 | 122 | // for reference you may want to compare results (remember: they're in UTC!) to https://www.timeanddate.com/moon/ 123 | printResult(result); 124 | } 125 | 126 | void loop() { 127 | // no-op, only run the code once 128 | } 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ThingPulse Weather Station 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to the ThingPulse Weather Station project on GitHub. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. 6 | 7 | It is appreciated if you raise an issue _before_ you start changing the code, discussing the proposed change; emphasizing that you are proposing to develop the patch yourself, and outlining the strategy for implementation. This type of discussion is what we should be doing on the issues list and it is better to do this before or in parallel to developing the patch rather than having "you should have done it this way" type of feedback on the PR itself. 8 | 9 | ### Table Of Contents 10 | * [General remarks](#general-remarks) 11 | * [Writing Documentation](#writing-documentation) 12 | * [Working with Git and GitHub](#working-with-git-and-github) 13 | * [General flow](#general-flow) 14 | * [Keeping your fork in sync](#keeping-your-fork-in-sync) 15 | * [Commit messages](#commit-messages) 16 | 17 | ## General remarks 18 | We are a friendly and welcoming community and look forward to your contributions. Once your contribution is integrated into this repository we feel responsible for it. Therefore, be prepared for constructive feedback. Before we merge anything we need to ensure that it fits in and is consistent with the rest of code. 19 | If you made something really cool but won't spend the time to integrate it into this upstream project please still share it in your fork on GitHub. If you mention it in an issue we'll take a look at it anyway. 20 | 21 | ## Writing Documentation 22 | ThingPulse maintains documentation for its products at [https://github.com/thingpulse/docs/](https://github.com/thingpulse/docs/). If you contribute features for this project that require altering the respective product guide then we ask you to prepare a pull request with the necessary documentation changes as well. 23 | 24 | ## Working with Git and GitHub 25 | 26 | Avoid intermediate merge commits. [Rebase](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) your feature branch onto `master` to pull updates and verify your local changes against them before placing the pull request. 27 | 28 | ### General flow 29 | 1. [Fork](https://help.github.com/articles/fork-a-repo) this repository on GitHub. 30 | 1. [Create a branch](https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/#creating-a-branch) in your fork on GitHub **based on the `master` branch**. 31 | 1. Clone the fork on your machine with `git clone https://github.com//.git` 32 | 1. `cd ` then run `git remote add upstream https://github.com/ThingPulse/esp8266-weather-station` 33 | 1. `git checkout ` 34 | 1. Make changes to the code base and commit them using e.g. `git commit -a -m 'Look ma, I did it'` 35 | 1. When you're done: 36 | 1. Think about [squashing (some of) your commits](http://www.andrewconnell.com/blog/squash-multiple-git-commits-into-one). There are [several ways](http://stackoverflow.com/a/5201642/131929) to do this. There's no need to squash everything into a single commit as GitHub offers to do this when we merge your changes. However, you might want to trim your commit history to relevant chunks. 37 | 1. Bring your fork up-to-date with the upstream repo ([see below](#keeping-your-fork-in-sync)). Then rebase your branch on `master` running `git rebase master`. 38 | 1. `git push` 39 | 1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request/) (PR) on GitHub. 40 | 41 | This is just one way of doing things. If you're proficient in Git matters you're free to choose your own. If you want to read more then the [GitHub chapter in the Git book](http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project#The-GitHub-Flow) is a way to start. [GitHub's own documentation](https://help.github.com/categories/collaborating/) contains a wealth of information as well. 42 | 43 | ### Keeping your fork in sync 44 | You need to sync your fork with the upstream repository from time to time, latest before you rebase (see flow above). 45 | 46 | 1. `git fetch upstream` 47 | 1. `git checkout master` 48 | 1. `git merge upstream/master` 49 | 50 | ### Commit messages 51 | 52 | From: [http://git-scm.com/book/ch5-2.html](http://git-scm.com/book/ch5-2.html) 53 |
54 | Short (50 chars or less) summary of changes
55 | 
56 | More detailed explanatory text, if necessary.  Wrap it to about 72
57 | characters or so.  In some contexts, the first line is treated as the
58 | subject of an email and the rest of the text as the body.  The blank
59 | line separating the summary from the body is critical (unless you omit
60 | the body entirely); tools like rebase can get confused if you run the
61 | two together.
62 | 
63 | Further paragraphs come after blank lines.
64 | 
65 |  - Bullet points are okay, too
66 | 
67 |  - Typically a hyphen or asterisk is used for the bullet, preceded by a
68 |    single space, with blank lines in between, but conventions vary here
69 | 
70 | 71 | Don't forget to [reference affected issues](https://help.github.com/articles/closing-issues-via-commit-messages/) in the commit message to have them closed automatically on GitHub. 72 | 73 | [Amend](https://help.github.com/articles/changing-a-commit-message/) your commit messages if necessary to make sure what the world sees on GitHub is as expressive and meaningful as possible. 74 | -------------------------------------------------------------------------------- /src/NTPClient.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * Copyright (c) 2015 by Fabrice Weinberg 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 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | * SOFTWARE. 20 | */ 21 | 22 | #include "NTPClient.h" 23 | 24 | NTPClient::NTPClient(int timeOffset) { 25 | this->_timeOffset = timeOffset; 26 | } 27 | 28 | NTPClient::NTPClient(const char* poolServerName) { 29 | this->_poolServerName = poolServerName; 30 | } 31 | 32 | NTPClient::NTPClient(const char* poolServerName, int timeOffset) { 33 | this->_timeOffset = timeOffset; 34 | this->_poolServerName = poolServerName; 35 | } 36 | 37 | NTPClient::NTPClient(const char* poolServerName, int timeOffset, int updateInterval) { 38 | this->_timeOffset = timeOffset; 39 | this->_poolServerName = poolServerName; 40 | this->_updateInterval = updateInterval; 41 | } 42 | 43 | void NTPClient::begin() { 44 | #ifdef DEBUG_NTPClient 45 | Serial.println("Begin NTPClient"); 46 | Serial.print("Start udp connection on port: "); 47 | Serial.println(this->_port); 48 | #endif 49 | this->_udp.begin(this->_port); 50 | this->forceUpdate(); 51 | } 52 | 53 | void NTPClient::forceUpdate() { 54 | #ifdef DEBUG_NTPClient 55 | Serial.println("Update from NTP Server"); 56 | #endif 57 | 58 | IPAddress address; 59 | WiFi.hostByName(this->_poolServerName, address); 60 | 61 | this->sendNTPPacket(address); 62 | 63 | // Wait till data is there or timeout... 64 | byte timeout = 0; 65 | int cb = 0; 66 | do { 67 | delay ( 10 ); 68 | cb = this->_udp.parsePacket(); 69 | if (timeout > 100) return; // timeout after 1000 ms 70 | timeout++; 71 | } while (cb == 0); 72 | 73 | this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time 74 | 75 | this->_udp.read(this->_packetBuffer, NTP_PACKET_SIZE); 76 | 77 | unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]); 78 | unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]); 79 | // combine the four bytes (two words) into a long integer 80 | // this is NTP time (seconds since Jan 1 1900): 81 | unsigned long secsSince1900 = highWord << 16 | lowWord; 82 | 83 | this->_currentEpoc = secsSince1900 - SEVENZYYEARS; 84 | } 85 | 86 | void NTPClient::update() { 87 | unsigned long runtime = millis(); 88 | if (runtime - this->_lastUpdate >= this->_updateInterval && this->_updateInterval != 0) { 89 | this->forceUpdate(); 90 | } 91 | } 92 | 93 | unsigned long NTPClient::getRawTime() { 94 | return this->_timeOffset + // User offset 95 | this->_currentEpoc + // Epoc returned by the NTP server 96 | ((millis() - this->_lastUpdate) / 1000); // Time since last update 97 | } 98 | 99 | String NTPClient::getHours() { 100 | return String((this->getRawTime() % 86400L) / 3600); 101 | } 102 | String NTPClient::getMinutes() { 103 | return String((this->getRawTime() % 3600) / 60); 104 | } 105 | 106 | String NTPClient::getSeconds() { 107 | return String(this->getRawTime() % 60); 108 | } 109 | 110 | String NTPClient::getFormattedTime() { 111 | unsigned long rawTime = this->getRawTime(); 112 | unsigned long hours = (rawTime % 86400L) / 3600; 113 | String hoursStr = hours < 10 ? "0" + String(hours) : String(hours); 114 | 115 | unsigned long minutes = (rawTime % 3600) / 60; 116 | String minuteStr = minutes < 10 ? "0" + String(minutes) : String(minutes); 117 | 118 | unsigned long seconds = rawTime % 60; 119 | String secondStr = seconds < 10 ? "0" + String(seconds) : String(seconds); 120 | 121 | return hoursStr + ":" + minuteStr + ":" + secondStr; 122 | } 123 | 124 | void NTPClient::sendNTPPacket(IPAddress ip) { 125 | // set all bytes in the buffer to 0 126 | memset(this->_packetBuffer, 0, NTP_PACKET_SIZE); 127 | // Initialize values needed to form NTP request 128 | // (see URL above for details on the packets) 129 | this->_packetBuffer[0] = 0b11100011; // LI, Version, Mode 130 | this->_packetBuffer[1] = 0; // Stratum, or type of clock 131 | this->_packetBuffer[2] = 6; // Polling Interval 132 | this->_packetBuffer[3] = 0xEC; // Peer Clock Precision 133 | // 8 bytes of zero for Root Delay & Root Dispersion 134 | this->_packetBuffer[12] = 49; 135 | this->_packetBuffer[13] = 0x4E; 136 | this->_packetBuffer[14] = 49; 137 | this->_packetBuffer[15] = 52; 138 | 139 | // all NTP fields have been given values, now 140 | // you can send a packet requesting a timestamp: 141 | this->_udp.beginPacket(ip, 123); //NTP requests are to port 123 142 | this->_udp.write(this->_packetBuffer, NTP_PACKET_SIZE); 143 | this->_udp.endPacket(); 144 | } 145 | -------------------------------------------------------------------------------- /src/AerisForecasts.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #pragma once 25 | #include 26 | #include 27 | #include 28 | 29 | typedef struct AerisForecastData { 30 | uint64_t timestamp; // 1526706000 31 | String validTime; // "2018-05-19T07:00:00+02:00" 32 | String dateTimeISO; //"2018-05-19T07:00:00+02:00" 33 | int16_t maxTempC; //20 34 | int16_t maxTempF; //69 35 | int16_t minTempC; //14 36 | int16_t minTempF; // 56 37 | int16_t avgTempC; // 17 38 | int16_t avgTempF; // 62 39 | int16_t tempC; // null 40 | int16_t tempF; // null 41 | int16_t pop; // 20 42 | float precipMM; // 3.53 43 | float precipIN; // 0.14 44 | float iceaccum; // null 45 | float iceaccumMM; // null 46 | float iceaccumIN; // null 47 | uint8_t maxHumidity; // 82 48 | uint8_t minHumidity; // 53 49 | uint8_t humidity; // 68 50 | uint8_t uvi; // 6 51 | uint16_t pressureMB; // 1018 52 | float pressureIN; // 30.06 53 | uint8_t sky; // 99 54 | uint16_t snowCM; // 0 55 | uint16_t snowIN; // 0 56 | int16_t feelslikeC; // 14 57 | int16_t feelslikeF; // 56 58 | int16_t minFeelslikeC; // 14 59 | int16_t minFeelslikeF; // 56 60 | int16_t maxFeelslikeC; // 20 61 | int16_t maxFeelslikeF; // 69 62 | int16_t avgFeelslikeC; // 17 63 | int16_t avgFeelslikeF; // 63 64 | int16_t dewpointC; // 11 65 | int16_t dewpointF; // 51 66 | int16_t maxDewpointC; // 13 67 | int16_t maxDewpointF; // 55 68 | int16_t minDewpointC; // 10 69 | int16_t minDewpointF; // 51 70 | int16_t avgDewpointC; // 11 71 | int16_t avgDewpointF; // 52 72 | uint16_t windDirDEG; // 2 73 | String windDir; // "N" 74 | uint16_t windDirMaxDEG; // 40 75 | String windDirMax; // "NE" 76 | int16_t windDirMinDEG; // 39 77 | String windDirMin; // "NE" 78 | uint16_t windGustKTS; // 6 79 | uint16_t windGustKPH; // 11 80 | uint16_t windGustMPH; // 7 81 | uint16_t windSpeedKTS; // 4 82 | uint16_t windSpeedKPH; // 7 83 | uint16_t windSpeedMPH; // 5 84 | uint16_t windSpeedMaxKTS; // 6 85 | uint16_t windSpeedMaxKPH; // 11 86 | uint16_t windSpeedMaxMPH; // 7 87 | uint16_t windSpeedMinKTS; // 1 88 | uint16_t windSpeedMinKPH; // 2 89 | uint16_t windSpeedMinMPH; // 1 90 | uint16_t windDir80mDEG; // 5 91 | String windDir80m; // "N" 92 | uint16_t windDirMax80mDEG; // 40 93 | String windDirMax80m; // "NE" 94 | uint16_t windDirMin80mDEG; // 39 95 | String windDirMin80m; // "NE" 96 | uint16_t windGust80mKTS; // 9 97 | uint16_t windGust80mKPH; // 17 98 | uint16_t windGust80mMPH; // 11 99 | uint16_t windSpeed80mKTS; // 6 100 | uint16_t windSpeed80mKPH; // 11 101 | uint16_t windSpeed80mMPH; // 7 102 | uint16_t windSpeedMax80mKTS; // 9 103 | uint16_t windSpeedMax80mKPH; // 17 104 | uint16_t windSpeedMax80mMPH; // 11 105 | uint16_t windSpeedMin80mKTS; // 4 106 | uint16_t windSpeedMin80mKPH; // 7 107 | uint16_t windSpeedMin80mMPH; // 4 108 | String weather; // "Cloudy with Drizzle" 109 | String weatherPrimary; // "Drizzle" 110 | String weatherPrimaryCoded; // "IS:VL:RW" 111 | String cloudsCoded; // "OV" 112 | String icon; // "drizzle.png" 113 | String iconMeteoCon; // Q 114 | boolean isDay; // true 115 | uint64_t sunrise; // 1526701425 116 | String sunriseISO; // "2018-05-19T05:43:45+02:00" 117 | uint64_t sunset; // 1526756450 118 | String sunsetISO; // "2018-05-19T21:00:50+02:00" 119 | } AerisForecastData; 120 | 121 | class AerisForecasts: public JsonListener { 122 | private: 123 | const String host = "api.aerisapi.com"; 124 | const uint8_t port = 80; 125 | boolean isMetric = true; 126 | String currentKey; 127 | String currentParent; 128 | AerisForecastData *forecasts; 129 | uint8_t maxForecasts; 130 | uint8_t currentForecast; 131 | 132 | 133 | void doUpdate(AerisForecastData *forecasts, String url, uint8_t maxForecasts); 134 | 135 | public: 136 | AerisForecasts(); 137 | void updateForecasts(AerisForecastData *forecasts, String clientId, String clientKey, String location, uint8_t maxForecasts); 138 | 139 | String getMeteoconIcon(String icon); 140 | 141 | 142 | virtual void whitespace(char c); 143 | 144 | virtual void startDocument(); 145 | 146 | virtual void key(String key); 147 | 148 | virtual void value(String value); 149 | 150 | virtual void endArray(); 151 | 152 | virtual void endObject(); 153 | 154 | virtual void endDocument(); 155 | 156 | virtual void startArray(); 157 | 158 | virtual void startObject(); 159 | }; 160 | -------------------------------------------------------------------------------- /src/SunMoonCalc.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #pragma once 25 | #include 26 | #include 27 | 28 | class SunMoonCalc { 29 | public: 30 | typedef struct Sun { 31 | double azimuth; 32 | double elevation; 33 | double riseJd; // jd = Julian Day 34 | double setJd; 35 | double transitJd; 36 | time_t rise; // timestamp in UTC 37 | time_t set; 38 | time_t transit; 39 | double transitElevation; 40 | double distance; 41 | } Sun; 42 | 43 | typedef struct MoonPhase { 44 | uint8_t index; 45 | String name; 46 | } MoonPhase; 47 | 48 | typedef struct Moon { 49 | double azimuth; 50 | double elevation; 51 | double riseJd; // jd = Julian Day 52 | double setJd; 53 | double transitJd; 54 | time_t rise; // timestamp in UTC 55 | time_t set; 56 | time_t transit; 57 | double age; 58 | double transitElevation; 59 | double distance; 60 | double illumination; 61 | double axisPositionAngle; 62 | double brightLimbAngle; 63 | double parallacticAngle; 64 | MoonPhase phase; 65 | } Moon; 66 | 67 | typedef struct Result { 68 | Sun sun; 69 | Moon moon; 70 | } Result; 71 | 72 | SunMoonCalc(time_t timestamp, double lat, double lon); 73 | SunMoonCalc(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, double lat, 74 | double lon); 75 | 76 | Result calculateSunAndMoonData(); 77 | 78 | private: 79 | enum TWILIGHT { 80 | /** 81 | * Event ID for calculation of rising and setting times for astronomical 82 | * twilight. In this case, the calculated time will be the time when the 83 | * center of the object is at -18 degrees of geometrical elevation below the 84 | * astronomical horizon. At this time astronomical observations are possible 85 | * because the sky is dark enough. 86 | */ 87 | TWILIGHT_ASTRONOMICAL, 88 | /** 89 | * Event ID for calculation of rising and setting times for nautical 90 | * twilight. In this case, the calculated time will be the time when the 91 | * center of the object is at -12 degrees of geometric elevation below the 92 | * astronomical horizon. 93 | */ 94 | TWILIGHT_NAUTICAL, 95 | /** 96 | * Event ID for calculation of rising and setting times for civil twilight. 97 | * In this case, the calculated time will be the time when the center of the 98 | * object is at -6 degrees of geometric elevation below the astronomical 99 | * horizon. 100 | */ 101 | TWILIGHT_CIVIL, 102 | /** 103 | * The standard value of 34' for the refraction at the local horizon. 104 | */ 105 | HORIZON_34arcmin 106 | }; 107 | 108 | typedef struct PositionalData { 109 | double longitude; 110 | double latitude; 111 | double distance; 112 | double angularRadius; 113 | } PositionalData; 114 | 115 | double t; 116 | double jd_UT; 117 | double TTminusUT; 118 | double lat; // internal value is in radians! 119 | double lon; // internal value is in radians! 120 | double slongitude; // sun longitude 121 | double sanomaly; // sun anomaly 122 | double moonAge; // this is calculated as a by-product in getMoonPosition() 123 | TWILIGHT twilight = HORIZON_34arcmin; 124 | 125 | void setInternalTime(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second); 126 | void setUTDate(double jd); 127 | PositionalData getSunPosition(); 128 | PositionalData getMoonPosition(); 129 | double *doCalc(PositionalData position); 130 | double normalizeRadians(double r); 131 | double calculateTwilightAdjustment(PositionalData position) const; 132 | double obtainAccurateRiseSetTransit(double riseSetJd, int index, int niter, bool sun); 133 | double *getMoonDiskOrientationAngles(double lst, double sunRA, double sunDec, double moonLon, double moonLat, 134 | double moonRA, double moonDec); 135 | SunMoonCalc::MoonPhase calculateMoonPhase(double lunarAge) const; 136 | Result translateToHumanReadable(Result result) const; 137 | time_t fromJulian(double julianDays) const; 138 | double toJulian(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) const; 139 | }; 140 | -------------------------------------------------------------------------------- /examples/AerisSunMoonDemo/AerisSunMoonDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #include 25 | 26 | #if defined(ESP8266) 27 | #include 28 | #else 29 | #include 30 | #endif 31 | #include 32 | #include "AerisSunMoon.h" 33 | 34 | /** 35 | * Aeris Weather 36 | */ 37 | 38 | // initiate the Aeris client 39 | AerisSunMoon sunMoonClient; 40 | 41 | String AERIS_CLIENT_ID = "tWOmsRUXe4EFTHQKmUKOK"; 42 | String AERIS_SECRET_KEY = "gRoMoapOyg46HwB7dRmoVPaJ0vUgAiud1CFWuLfF"; 43 | String AERIS_LOCATION = "Zurich,CH"; 44 | 45 | /** 46 | * WiFi Settings 47 | */ 48 | #if defined(ESP8266) 49 | const char* ESP_HOST_NAME = "esp-" + ESP.getFlashChipId(); 50 | #else 51 | const char* ESP_HOST_NAME = "esp-" + ESP.getEfuseMac(); 52 | #endif 53 | const char* WIFI_SSID = "yourssid"; 54 | const char* WIFI_PASSWORD = "yourpassw0rd"; 55 | 56 | // initiate the WifiClient 57 | WiFiClient wifiClient; 58 | 59 | 60 | 61 | /** 62 | * Helping funtions 63 | */ 64 | void connectWifi() { 65 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 66 | Serial.print("Connecting to "); 67 | Serial.println(WIFI_SSID); 68 | while (WiFi.status() != WL_CONNECTED) { 69 | delay(500); 70 | Serial.print("."); 71 | } 72 | Serial.println(""); 73 | Serial.println("WiFi connected!"); 74 | Serial.println(WiFi.localIP()); 75 | Serial.println(); 76 | } 77 | 78 | 79 | /** 80 | * SETUP 81 | */ 82 | void setup() { 83 | Serial.begin(115200); 84 | delay(500); 85 | connectWifi(); 86 | 87 | Serial.println(); 88 | Serial.println("\n\nNext Loop-Step: " + String(millis()) + ":"); 89 | 90 | AerisSunMoonData sunMoonData; 91 | sunMoonClient.updateSunMoon(&sunMoonData, AERIS_CLIENT_ID, AERIS_SECRET_KEY, AERIS_LOCATION); 92 | 93 | Serial.println("------------------------------------"); 94 | 95 | // uint64_t sunRise; // "rise":1493291184, 96 | Serial.printf("sunRise: %d\n", sunMoonData.sunRise); 97 | // String sunRiseISO; // "riseISO":"2017-04-27T06:06:24-05:00", 98 | Serial.printf("sunRiseISO: %s\n", sunMoonData.sunRiseISO.c_str()); 99 | // uint64_t sunSet; // "set":1493342079, 100 | Serial.printf("sunSet: %d\n", sunMoonData.sunSet); 101 | // String sunSetISO; //"setISO":"2017-04-27T20:14:39-05:00", 102 | Serial.printf("sunSetISO: %s\n", sunMoonData.sunSetISO.c_str()); 103 | // uint64_t sunTransit; // "transit":1493316631, 104 | Serial.printf("sunTransit: %d\n", sunMoonData.sunTransit); 105 | // String sunTransitISO; // "transitISO":"2017-04-27T13:10:31-05:00", 106 | Serial.printf("sunTransitISO: %s\n", sunMoonData.sunTransitISO.c_str()); 107 | // boolean midnightSun; // "midnightSun":false, 108 | Serial.printf("midnightSun: %d\n", sunMoonData.midnightSun); 109 | // boolean polarNight; // "polarNight":false, 110 | Serial.printf("polarNight: %d\n", sunMoonData.polarNight); 111 | // uint64_t moonRise; //"rise":1493295480, 112 | Serial.printf("moonRise: %d\n", sunMoonData.moonRise); 113 | // String moonRiseISO; // "riseISO":"2017-04-27T07:18:00-05:00", 114 | Serial.printf("moonRiseISO: %s\n", sunMoonData.moonRiseISO.c_str()); 115 | // uint64_t moonSet; // "set":1493347800, 116 | Serial.printf("moonSet: %d\n", sunMoonData.moonSet); 117 | // String moonSetISO; // "setISO":"2017-04-27T21:50:00-05:00", 118 | Serial.printf("moonSetISO: %s\n", sunMoonData.moonSetISO.c_str()); 119 | // uint64_t moonTransit; // "transit":1493321340, 120 | Serial.printf("moonTransit: %d\n", sunMoonData.moonTransit); 121 | // String moonTransitISO; // "transitISO":"2017-04-27T14:29:00-05:00", 122 | Serial.printf("moonTransitISO: %s\n", sunMoonData.moonTransitISO.c_str()); 123 | // uint64_t moonUnderfoot; // "underfoot":1493276400, 124 | Serial.printf("moonUnderfoot: %d\n", sunMoonData.moonUnderfoot); 125 | // String moonUnderfootISO; // "underfootISO":"2017-04-27T02:00:00-05:00", 126 | Serial.printf("moonUnderfootISO: %s\n", sunMoonData.moonUnderfootISO.c_str()); 127 | // float moonPhase; // "phase":0.0516, 128 | Serial.printf("moonPhase: %f\n", sunMoonData.moonPhase); 129 | // String moonPhaseName; // "name":"waxing crescent", 130 | Serial.printf("moonPhaseName: %s\n", sunMoonData.moonPhaseName.c_str()); 131 | // uint8_t moonIllum; // "illum":3, 132 | Serial.printf("moonIllum: %d\n", sunMoonData.moonIllum); 133 | // float moonAge; // "age":1.52, 134 | Serial.printf("moonAge: %f\n", sunMoonData.moonAge); 135 | // float moonAngle; // "angle":0.55 136 | Serial.printf("moonAngle: %f\n", sunMoonData.moonAngle); 137 | Serial.println(); 138 | Serial.println("---------------------------------------------------/\n"); 139 | 140 | } 141 | 142 | 143 | /** 144 | * LOOP 145 | */ 146 | void loop() { 147 | 148 | } 149 | -------------------------------------------------------------------------------- /examples/PlaneSpotterDemo/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://global.adsbexchange.com/VirtualRadar/AircraftList.json?lat=47.437691&lng=8.568854&fDstL=0&fDstU=20&fAltL=0&fAltU=5000 14 | const char host[] = "global.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 | client.setNoDelay(false); 46 | while (client.available() || client.connected()) { 47 | while (client.available()) { 48 | c = client.read(); 49 | if (c == '{' || c == '[') { 50 | isBody = true; 51 | } 52 | if (isBody) { 53 | parser.parse(c); 54 | } 55 | } 56 | client.stop(); 57 | } 58 | endDocument(); 59 | } 60 | 61 | String AdsbExchangeClient::getFrom() { 62 | if (from[CURRENT].length() >=4) { 63 | int firstComma = from[CURRENT].indexOf(","); 64 | return from[CURRENT].substring(5, firstComma); 65 | } 66 | return ""; 67 | } 68 | String AdsbExchangeClient::getFromIcao() { 69 | if (from[CURRENT].length() >=4) { 70 | return from[CURRENT].substring(0,4); 71 | } 72 | return ""; 73 | } 74 | String AdsbExchangeClient::getTo() { 75 | if (to[CURRENT].length() >=4) { 76 | int firstComma = to[CURRENT].indexOf(","); 77 | return to[CURRENT].substring(5, firstComma); 78 | } 79 | return ""; 80 | } 81 | 82 | String AdsbExchangeClient::getToIcao() { 83 | if (to[CURRENT].length() >=4) { 84 | return to[CURRENT].substring(0,4); 85 | } 86 | return ""; 87 | } 88 | String AdsbExchangeClient::getAltitude(){ 89 | return altitude[CURRENT]; 90 | } 91 | double AdsbExchangeClient::getDistance() { 92 | return distance[CURRENT]; 93 | 94 | } 95 | String AdsbExchangeClient::getAircraftType() { 96 | return aircraftType[CURRENT]; 97 | 98 | } 99 | String AdsbExchangeClient::getOperatorCode() { 100 | return operatorCode[CURRENT]; 101 | } 102 | 103 | double AdsbExchangeClient::getHeading() { 104 | return heading[CURRENT]; 105 | } 106 | 107 | void AdsbExchangeClient::whitespace(char c) { 108 | 109 | } 110 | 111 | void AdsbExchangeClient::startDocument() { 112 | counter = 0; 113 | currentMinDistance = 1000.0; 114 | } 115 | 116 | void AdsbExchangeClient::key(String key) { 117 | currentKey = key; 118 | } 119 | 120 | void AdsbExchangeClient::value(String value) { 121 | /*String from = ""; 122 | String to = ""; 123 | String altitude = ""; 124 | String aircraftType = ""; 125 | String currentKey = ""; 126 | String operator = ""; 127 | 128 | 129 | "Type": "A319", 130 | "Mdl": "Airbus A319 112", 131 | 132 | "From": "LSZH Z\u00c3\u00bcrich, Zurich, Switzerland", 133 | "To": "LEMD Madrid Barajas, Spain", 134 | "Op": "Swiss International Air Lines", 135 | "OpIcao": "SWR", 136 | "Dst": 6.23, 137 | "Year": "1996" 138 | */ 139 | if (currentKey == "Id") { 140 | counter++; 141 | } else if (currentKey == "From") { 142 | from[TEMP] = value; 143 | } else if (currentKey == "To") { 144 | to[TEMP] = value; 145 | } else if (currentKey == "OpIcao") { 146 | operatorCode[TEMP] = value; 147 | } else if (currentKey == "Dst") { 148 | distance[TEMP] = value.toFloat(); 149 | } else if (currentKey == "Mdl") { 150 | aircraftType[TEMP] = value; 151 | } else if (currentKey == "Trak") { 152 | heading[TEMP] = value.toFloat(); 153 | } else if (currentKey == "Alt") { 154 | altitude[TEMP] = value; 155 | } else if (currentKey == "Trt") { 156 | if (distance[TEMP] < currentMinDistance) { 157 | currentMinDistance = distance[TEMP]; 158 | Serial.println("Found a closer aircraft"); 159 | from[CURRENT] = from[TEMP]; 160 | to[CURRENT] = to[TEMP]; 161 | altitude[CURRENT] = altitude[TEMP]; 162 | distance[CURRENT] = distance[TEMP]; 163 | aircraftType[CURRENT] = aircraftType[TEMP]; 164 | operatorCode[CURRENT] = operatorCode[TEMP]; 165 | heading[CURRENT] = heading[TEMP]; 166 | } 167 | } 168 | Serial.println(currentKey + "=" + value); 169 | } 170 | 171 | int AdsbExchangeClient::getNumberOfVisibleAircrafts() { 172 | return counter; 173 | } 174 | 175 | void AdsbExchangeClient::endArray() { 176 | 177 | } 178 | 179 | void AdsbExchangeClient::endObject() { 180 | 181 | } 182 | 183 | void AdsbExchangeClient::endDocument() { 184 | Serial.println("Flights: " + String(counter)); 185 | if (counter == 0 && lastSightingMillis < millis() - MAX_AGE_MILLIS) { 186 | for (int i = 0; i < 2; i++) { 187 | from[i] = ""; 188 | to[i] = ""; 189 | altitude[i] = ""; 190 | distance[i] = 1000.0; 191 | aircraftType[i] = ""; 192 | operatorCode[i] = ""; 193 | heading[i] = 0.0; 194 | } 195 | } else if (counter > 0) { 196 | lastSightingMillis = millis(); 197 | } 198 | } 199 | 200 | boolean AdsbExchangeClient::isAircraftVisible() { 201 | return counter > 0 || lastSightingMillis > millis() - MAX_AGE_MILLIS; 202 | } 203 | 204 | void AdsbExchangeClient::startArray() { 205 | 206 | } 207 | 208 | void AdsbExchangeClient::startObject() { 209 | 210 | } 211 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThingPulse ESP8266 Weather Station 2 | 3 | [![PlatformIO Registry](https://badges.registry.platformio.org/packages/thingpulse/library/ESP8266%20Weather%20Station.svg)](https://registry.platformio.org/libraries/thingpulse/ESP8266%20Weather%20Station) 4 | [![PlatformIO CI](https://github.com/ThingPulse/esp8266-weather-station/actions/workflows/main.yml/badge.svg)](https://github.com/ThingPulse/esp8266-weather-station/actions) 5 | 6 | 7 | This code works best with the NodeMCU V2 ESP8266 module and an 0.96" OLED display. 8 | To get you up and running in no time we created a kit which contains all the necessary parts: 9 | [https://thingpulse.com/product/esp8266-iot-electronics-starter-kit-weatherstation-planespotter-worldclock/](https://thingpulse.com/product/esp8266-iot-electronics-starter-kit-weatherstation-planespotter-worldclock/) 10 | 11 | By buying this and [other kits](https://thingpulse.com/shop/) from us you are funding maintenance and development of this library. Thank you! 12 | 13 | [![ThingPulse ESP8266 WeatherStation Classic Kit](resources/ThingPulse-ESP8266-Weather-Station.jpeg)](https://thingpulse.com/product/esp8266-iot-electronics-starter-kit-weatherstation-planespotter-worldclock/) 14 | 15 | ## Service level promise 16 | 17 |
18 | This is a ThingPulse prime project. See our open-source commitment declaration for what this means.
19 | 20 | ## Install and configure Arduino IDE 21 | 22 | Make sure you use a version of the Arduino IDE which is supported by the ESP8266 platform. Follow the [tutorial on our documentation platform](https://docs.thingpulse.com/how-tos/Arduino-IDE-for-ESP8266/). 23 | 24 | ## Install libraries in Arduino IDE 25 | 26 | Install the following libraries with your Arduino Library Manager in `Sketch` > `Include Library` > `Manage Libraries...` 27 | * ESP8266 Weather Station 28 | * JSON Streaming Parser by Daniel Eichhorn 29 | * ESP8266 OLED Driver for SSD1306 display by Daniel Eichhorn. **Use Version 3.0.0 or higher!** 30 | 31 | ## Prepare the software 32 | * [Create an API Key](https://docs.thingpulse.com/how-tos/openweathermap-key/) for OpenWeatherMap 33 | * In the Arduino IDE go to `File` > `Examples` > `ESP8266 Weather Station` > `Weather Station Demo` 34 | * Enter the OpenWeatherMap API Key 35 | * Enter your WiFi credentials 36 | * Adjust the location according to OpenWeatherMap API, e.g. Zurich, CH 37 | * Adjust UTC offset 38 | 39 | ## Setup for PlatformIO 40 | 41 | If you are using the PlatformIO environment for building 42 | 43 | * choose one of the available IDE integration or the Atom based IDE 44 | * install libraries 561, 562 and 563 with "platformio lib install" 45 | * adapt the [WeatherStationDemo.ino](examples/WeatherStationDemo/WeatherStationDemo.ino) file to your needs (see details above) 46 | 47 | 48 | ## Available Modules 49 | * **Time Client**: simple class which uses the header date and time to set the clock 50 | * **NTP Client**: a NTP based time class written by Fabrice Weinberg 51 | * **OpenWeatherMap Client**: A REST client for the OpenWeatherMap.com service, providing weather information 52 | * **Aeris Client**: Client for the service provided by aerisweather.com. Fully functional initial version. After the Wunderground incident (see [upgrade notes](#upgrade-notes)) we first targeted Aeris before we settled with OpenWeatherMap. This code is unmaintained but will remain part of this library for the time being. 53 | * **Thingspeak Client**: fetches data from Thingspeak which you might have collected with another sensor node and posted there. 54 | * **Astronomy**: algorithms to calculate current lunar phase and illumination. 55 | * **SunMoonCalc**: a calculator for sun and moon properties for a given date & time and location. This implementation is port of a [Java class by T. Alonso Albi](http://conga.oan.es/~alonso/doku.php?id=blog:sun_moon_position) from OAN (Spain). 56 | 57 | ## Why Weather Station as a library? 58 | 59 | I realized that more and more the Weather Station was becoming a general framework for displaying data over WiFi to one of these pretty displays. But everyone would have different ways or sources for data and having the important part of the library would rather be the classes which fetch the data then the main class. 60 | So if you write data fetchers which might be of interest to others please contact me to integrate them here or offer your code as extension library yourself and call it something like esp8266-weather-station-. 61 | We will gladly list it here as third party library... 62 | 63 | ## Upgrade Notes 64 | 65 | **Version 2, January 2020, removes WU support, see below** 66 | 67 | **Replace Wunderground with OpenWeatherMap as weather data provider** 68 | 69 | The weather information provider we used so far (Wunderground) [recently stopped their free tier](https://thingpulse.com/weather-underground-no-longer-providing-free-api-keys/) without previous notice on May 15, 2018. This release adds support for a new provider with a free tier for weather information: OpenWeatherMap.com. The basic demo (WeatherStationDemo) has been adapted to use this new API through the OpenWeatherMapCurrent and OpenWeatherMapForecast REST clients. 70 | 71 | Sadly OpenWeatherMap provides less information than Wunderground did (or still does). If you are missing attributes in the response documents then please [contact the OpenWeatherMap team](https://openweathermap.desk.com/customer/portal/emails/new). 72 | 73 | **ESP8266 OLED Library upgrade** 74 | 75 | The ESP8266 OLED Library changed a lot with the latest release of version 3.0.0. We fixed many bugs and improved performance and changed the API a little bit. This means that you might have to adapt your Weather Station Code if you created it using the older 2.x.x version of the library. Either compare your code to the updated WeatherStationDemo or read through the [upgrade guide](https://github.com/ThingPulse/esp8266-oled-ssd1306/blob/master/UPGRADE-3.0.md) 76 | 77 | ## Deprecation notes 78 | 79 | | Announcement | Module | Removal | 80 | |---------------|---------|----------| 81 | | 2024-09-19 | OWM requests by city name and city ID, see [their documentation](https://openweathermap.org/current#builtin) | | 82 | | 2018-06-13 | all **Wunderground** related code, see [our blog](https://thingpulse.com/hello-openweathermap-bye-bye-wunderground/) for details | January 2020, version 2.0.0 | 83 | -------------------------------------------------------------------------------- /examples/OpenWeatherMapCurrentDemo/OpenWeatherMapCurrentDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #include 25 | 26 | #if defined(ESP8266) 27 | #include 28 | #else 29 | #include 30 | #endif 31 | #include 32 | #include 33 | #include "OpenWeatherMapCurrent.h" 34 | 35 | 36 | // initiate the client 37 | OpenWeatherMapCurrent client; 38 | 39 | // See https://docs.thingpulse.com/how-tos/openweathermap-key/ 40 | String OPEN_WEATHER_MAP_APP_ID = "XXX"; 41 | /* 42 | Use the OWM GeoCoder API to find lat/lon for your city: https://openweathermap.org/api/geocoding-api 43 | Or use any other geocoding service. 44 | Or go to https://openweathermap.org, search for your city and monitor the calls in the browser dev console :) 45 | */ 46 | // Example: Zurich, Switzerland 47 | float OPEN_WEATHER_MAP_LOCATION_LAT = 47.3667; 48 | float OPEN_WEATHER_MAP_LOCATION_LON = 8.55; 49 | /* 50 | Arabic - ar, Bulgarian - bg, Catalan - ca, Czech - cz, German - de, Greek - el, 51 | English - en, Persian (Farsi) - fa, Finnish - fi, French - fr, Galician - gl, 52 | Croatian - hr, Hungarian - hu, Italian - it, Japanese - ja, Korean - kr, 53 | Latvian - la, Lithuanian - lt, Macedonian - mk, Dutch - nl, Polish - pl, 54 | Portuguese - pt, Romanian - ro, Russian - ru, Swedish - se, Slovak - sk, 55 | Slovenian - sl, Spanish - es, Turkish - tr, Ukrainian - ua, Vietnamese - vi, 56 | Chinese Simplified - zh_cn, Chinese Traditional - zh_tw. 57 | */ 58 | String OPEN_WEATHER_MAP_LANGUAGE = "en"; 59 | boolean IS_METRIC = true; 60 | 61 | /** 62 | * WiFi Settings 63 | */ 64 | #if defined(ESP8266) 65 | const char* ESP_HOST_NAME = "esp-" + ESP.getFlashChipId(); 66 | #else 67 | const char* ESP_HOST_NAME = "esp-" + ESP.getEfuseMac(); 68 | #endif 69 | const char* WIFI_SSID = "yourssid"; 70 | const char* WIFI_PASSWORD = "yourpassw0rd"; 71 | 72 | // initiate the WifiClient 73 | WiFiClient wifiClient; 74 | 75 | 76 | 77 | /** 78 | * Helping funtions 79 | */ 80 | void connectWifi() { 81 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 82 | Serial.print("Connecting to "); 83 | Serial.println(WIFI_SSID); 84 | while (WiFi.status() != WL_CONNECTED) { 85 | delay(500); 86 | Serial.print("."); 87 | } 88 | Serial.println(""); 89 | Serial.println("WiFi connected!"); 90 | Serial.println(WiFi.localIP()); 91 | Serial.println(); 92 | } 93 | 94 | 95 | /** 96 | * SETUP 97 | */ 98 | void setup() { 99 | Serial.begin(115200); 100 | delay(500); 101 | connectWifi(); 102 | 103 | Serial.println(); 104 | Serial.println("\n\nNext Loop-Step: " + String(millis()) + ":"); 105 | 106 | OpenWeatherMapCurrentData data; 107 | client.setLanguage(OPEN_WEATHER_MAP_LANGUAGE); 108 | client.setMetric(IS_METRIC); 109 | client.updateCurrent(&data, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_LAT, OPEN_WEATHER_MAP_LOCATION_LON); 110 | 111 | Serial.println("------------------------------------"); 112 | 113 | // "lon": 8.54, float lon; 114 | Serial.printf("lon: %f\n", data.lon); 115 | // "lat": 47.37 float lat; 116 | Serial.printf("lat: %f\n", data.lat); 117 | // "id": 521, weatherId weatherId; 118 | Serial.printf("weatherId: %d\n", data.weatherId); 119 | // "main": "Rain", String main; 120 | Serial.printf("main: %s\n", data.main.c_str()); 121 | // "description": "shower rain", String description; 122 | Serial.printf("description: %s\n", data.description.c_str()); 123 | // "icon": "09d" String icon; String iconMeteoCon; 124 | Serial.printf("icon: %s\n", data.icon.c_str()); 125 | Serial.printf("iconMeteoCon: %s\n", data.iconMeteoCon.c_str()); 126 | // "temp": 290.56, float temp; 127 | Serial.printf("temp: %f\n", data.temp); 128 | // "pressure": 1013, uint16_t pressure; 129 | Serial.printf("pressure: %d\n", data.pressure); 130 | // "humidity": 87, uint8_t humidity; 131 | Serial.printf("humidity: %d\n", data.humidity); 132 | // "temp_min": 289.15, float tempMin; 133 | Serial.printf("tempMin: %f\n", data.tempMin); 134 | // "temp_max": 292.15 float tempMax; 135 | Serial.printf("tempMax: %f\n", data.tempMax); 136 | // "wind": {"speed": 1.5}, float windSpeed; 137 | Serial.printf("windSpeed: %f\n", data.windSpeed); 138 | // "wind": {"deg": 1.5}, float windDeg; 139 | Serial.printf("windDeg: %f\n", data.windDeg); 140 | // "clouds": {"all": 90}, uint8_t clouds; 141 | Serial.printf("clouds: %d\n", data.clouds); 142 | // "dt": 1527015000, uint64_t observationTime; 143 | time_t time = data.observationTime; 144 | Serial.printf("observationTime: %d, full date: %s", data.observationTime, ctime(&time)); 145 | // "country": "CH", String country; 146 | Serial.printf("country: %s\n", data.country.c_str()); 147 | // "sunrise": 1526960448, uint32_t sunrise; 148 | time = data.sunrise; 149 | Serial.printf("sunrise: %d, full date: %s", data.sunrise, ctime(&time)); 150 | // "sunset": 1527015901 uint32_t sunset; 151 | time = data.sunset; 152 | Serial.printf("sunset: %d, full date: %s", data.sunset, ctime(&time)); 153 | 154 | // "name": "Zurich", String cityName; 155 | Serial.printf("cityName: %s\n", data.cityName.c_str()); 156 | Serial.println(); 157 | Serial.println("---------------------------------------------------/\n"); 158 | 159 | } 160 | 161 | 162 | /** 163 | * LOOP 164 | */ 165 | void loop() { 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/WorldClockClient.cpp: -------------------------------------------------------------------------------- 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 | 27 | #include "WorldClockClient.h" 28 | 29 | 30 | WorldClockClient::WorldClockClient(String language, String country, String dateFormat, int numberOfTimeZones, String* timeZoneIds) { 31 | myLanguage = language; 32 | myCountry = country; 33 | myDateFormat = dateFormat; 34 | myNumberOfTimeZoneIds = numberOfTimeZones; 35 | myTimeZoneIds = timeZoneIds; 36 | timeZoneOffsetToUtcMillis = (long*) malloc(numberOfTimeZones * sizeof(long)); 37 | } 38 | 39 | void WorldClockClient::updateTime() { 40 | JsonStreamingParser parser; 41 | parser.setListener(this); 42 | WiFiClient client; 43 | 44 | const char host[] = "oleddisplay.squix.ch"; 45 | String url = "/rest/time"; 46 | 47 | const int httpPort = 80; 48 | if (!client.connect(host, httpPort)) { 49 | Serial.println("connection failed"); 50 | return; 51 | } 52 | 53 | 54 | Serial.print("Requesting URL: "); 55 | Serial.println(url); 56 | 57 | // {"language":"de","country":"CH","timeZoneIds":["Europe/Zurich", "Europe/London"],"dateFormat":"dd.MM.YYYY"} 58 | String timeZoneIdJson = "\"timeZoneIds\":["; 59 | for (int i = 0; i < myNumberOfTimeZoneIds; i++) { 60 | if (i > 0) { 61 | timeZoneIdJson +=","; 62 | } 63 | timeZoneIdJson += "\"" + myTimeZoneIds[i] + "\""; 64 | } 65 | timeZoneIdJson += "]"; 66 | String request = "{\"language\":\"" 67 | + myLanguage + "\",\"country\":\"" 68 | + myCountry + "\"," 69 | + timeZoneIdJson +",\"dateFormat\":\"" 70 | + myDateFormat +"\"}\r\n\r\n"; 71 | Serial.println("Request: " + request); 72 | // This will send the request to the server 73 | client.print("POST " + url + " HTTP/1.1\r\n" + 74 | "Host: " + host + "\r\n" + 75 | "Content-Length: " + String(request.length()) + "\r\n" + 76 | "Connection: close\r\n\r\n"); 77 | 78 | client.println(request); 79 | 80 | int retryCounter = 0; 81 | while(!client.available()) { 82 | Serial.println("."); 83 | delay(1000); 84 | retryCounter++; 85 | if (retryCounter > 10) { 86 | return; 87 | } 88 | } 89 | 90 | boolean isBody = false; 91 | char c; 92 | client.setNoDelay(false); 93 | while (client.connected() || client.available()) { 94 | if (client.available()) { 95 | c = client.read(); 96 | if (c == '{' || c == '[') { 97 | isBody = true; 98 | } 99 | if (isBody) { 100 | parser.parse(c); 101 | } 102 | } 103 | // give WiFi and TCP/IP libraries a chance to handle pending events 104 | yield(); 105 | } 106 | client.stop(); 107 | } 108 | 109 | 110 | String WorldClockClient::getFormattedTime(int timeZoneIndex) { 111 | return getHours(timeZoneIndex) + ":" + getMinutes(timeZoneIndex) + ":" + getSeconds(timeZoneIndex); 112 | } 113 | 114 | String WorldClockClient::getHours(int timeZoneIndex) { 115 | if (millisOfDayAtUpdate == 0) { 116 | return "--"; 117 | } 118 | int hours = ((getSecondsOfDay(timeZoneIndex) % 86400L) / 3600) % 24; 119 | if (hours < 10) { 120 | return "0" + String(hours); 121 | } 122 | return String(hours); // print the hour (86400 equals secs per day) 123 | } 124 | 125 | String WorldClockClient::getMinutes(int timeZoneIndex) { 126 | if (millisOfDayAtUpdate == 0) { 127 | return "--"; 128 | } 129 | int minutes = ((getSecondsOfDay(timeZoneIndex) % 3600) / 60); 130 | if (minutes < 10 ) { 131 | // In the first 10 minutes of each hour, we'll want a leading '0' 132 | return "0" + String(minutes); 133 | } 134 | return String(minutes); 135 | } 136 | 137 | String WorldClockClient::getSeconds(int timeZoneIndex) { 138 | if (millisOfDayAtUpdate == 0) { 139 | return "--"; 140 | } 141 | int seconds = getSecondsOfDay(timeZoneIndex) % 60; 142 | if ( seconds < 10 ) { 143 | // In the first 10 seconds of each minute, we'll want a leading '0' 144 | return "0" + String(seconds); 145 | } 146 | return String(seconds); 147 | 148 | } 149 | 150 | long WorldClockClient::getSecondsOfDay(int timeZoneIndex) { 151 | return (millisOfDayAtUpdate + millis() - localMillisAtUpdate + timeZoneOffsetToUtcMillis[timeZoneIndex]) / 1000; 152 | } 153 | 154 | void WorldClockClient::whitespace(char c) { 155 | 156 | } 157 | 158 | void WorldClockClient::startDocument() { 159 | 160 | } 161 | 162 | void WorldClockClient::key(String key) { 163 | currentKey = key; 164 | } 165 | 166 | void WorldClockClient::value(String value) { 167 | Serial.println(currentKey + ": " + value); 168 | if (currentKey == "millisOfDayUtc") { 169 | millisOfDayAtUpdate = value.toInt(); 170 | localMillisAtUpdate = millis(); 171 | } else if (currentKey == "index") { 172 | currentTimeZoneIndex = value.toInt(); 173 | Serial.println("\n-->Current index: " + String(currentTimeZoneIndex)); 174 | } else if (currentKey == "timeZoneOffsetToUtcMillis") { 175 | Serial.println("\n-->Index: " + String(currentTimeZoneIndex)); 176 | Serial.println("\n-->value: " + value); 177 | timeZoneOffsetToUtcMillis[currentTimeZoneIndex] = value.toInt(); 178 | } 179 | } 180 | 181 | void WorldClockClient::endArray() { 182 | 183 | } 184 | 185 | void WorldClockClient::endObject() { 186 | 187 | } 188 | 189 | void WorldClockClient::endDocument() { 190 | 191 | } 192 | 193 | void WorldClockClient::startArray() { 194 | 195 | } 196 | 197 | void WorldClockClient::startObject() { 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/OpenWeatherMapOneCall.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2020 by Chris Klinger 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 | 24 | #pragma once 25 | #include 26 | #include 27 | 28 | typedef struct OpenWeatherMapOneCallCurrentData { 29 | // "dt":1587216739 30 | uint32_t dt; 31 | // "sunrise":1587182465 32 | uint32_t sunrise; 33 | // "sunset":1587233389 34 | uint32_t sunset; 35 | // "temp": 290.56 36 | float temp; 37 | // "feels_like": 290.56 38 | float feels_like; 39 | // "pressure": 290.56 40 | uint16_t pressure; 41 | // "humidity": 87 42 | uint8_t humidity; 43 | //"dew_point": -3.24 44 | float dew_point; 45 | // "uvi": 4.5 46 | float uvi; 47 | // "clouds": 0 48 | uint8_t clouds; 49 | // visibility: 10000 50 | uint16_t visibility; 51 | // "wind_speed": 1.5 52 | float windSpeed; 53 | // "wind_deg": 0 54 | float windDeg; 55 | // "id": 800 56 | uint16_t weatherId; 57 | // "main": "Rain" 58 | String weatherMain; 59 | // "description": "shower rain" 60 | String weatherDescription; 61 | // "icon": "09d" 62 | String weatherIcon; 63 | String weatherIconMeteoCon; 64 | 65 | } OpenWeatherMapOneCallCurrentData; 66 | 67 | typedef struct OpenWeatherMapOneCallHourlyData { 68 | // "dt":1587216739 69 | uint32_t dt; 70 | // "temp": 290.56 71 | float temp; 72 | // "feels_like": 290.56 73 | float feels_like; 74 | // "pressure": 290.56 75 | uint16_t pressure; 76 | // "humidity": 87 77 | uint8_t humidity; 78 | //"dew_point": -3.24 79 | float dew_point; 80 | // "clouds": 0 81 | uint8_t clouds; 82 | // "wind_speed": 1.5 83 | float windSpeed; 84 | // "wind_deg": 0 85 | float windDeg; 86 | // "id": 800 87 | uint16_t weatherId; 88 | // "main": "Rain" 89 | String weatherMain; 90 | // "description": "shower rain" 91 | String weatherDescription; 92 | // "icon": "09d" 93 | String weatherIcon; 94 | String weatherIconMeteoCon; 95 | 96 | } OpenWeatherMapOneCallHourlyData; 97 | 98 | typedef struct OpenWeatherMapOneCallDailyData { 99 | // "dt":1587216739 100 | uint32_t dt; 101 | // "sunrise":1587182465 102 | uint32_t sunrise; 103 | // "sunset":1587233389 104 | uint32_t sunset; 105 | // "temp": {"day": 17.72} 106 | float tempDay; 107 | // "temp": {"min": 17.72} 108 | float tempMin; 109 | // "temp": {"max": 17.72} 110 | float tempMax; 111 | // "temp": {"night": 17.72} 112 | float tempNight; 113 | // "temp": {"eve": 17.72} 114 | float tempEve; 115 | // "temp": {"morn": 17.72} 116 | float tempMorn; 117 | // "feels_like": {"day": 17.72} 118 | float feels_likeDay; 119 | // "feels_like": {"night": 17.72} 120 | float feels_likeNight; 121 | // "feels_like": {"eve": 17.72} 122 | float feels_likeEve; 123 | // "feels_like": {"morn": 17.72} 124 | float feels_likeMorn; 125 | // "pressure": 290.56 126 | uint16_t pressure; 127 | // "humidity": 87 128 | uint8_t humidity; 129 | //"dew_point": -3.24 130 | float dew_point; 131 | // "wind_speed": 1.5 132 | float windSpeed; 133 | // "wind_deg": 0 134 | float windDeg; 135 | // "id": 800 136 | uint16_t weatherId; 137 | // "main": "Rain" 138 | String weatherMain; 139 | // "description": "shower rain" 140 | String weatherDescription; 141 | // "icon": "09d" 142 | String weatherIcon; 143 | String weatherIconMeteoCon; 144 | // "clouds": 0 145 | uint8_t clouds; 146 | // "rain": 5.97 147 | float rain; 148 | // "snow": 0.15 149 | float snow; 150 | // "uvi": 4.5 151 | float uvi; 152 | 153 | } OpenWeatherMapOneCallDailyData; 154 | 155 | typedef struct OpenWeatherMapOneCallData { 156 | // "lon": 8.54, 157 | float lon; 158 | // "lat": 47.37 159 | float lat; 160 | // "timezone": "America/Chicago" 161 | String timezone; 162 | // "current": {} 163 | OpenWeatherMapOneCallCurrentData current; 164 | // "hourly": [...] 165 | OpenWeatherMapOneCallHourlyData hourly[49]; 166 | // "daily": [...] 167 | OpenWeatherMapOneCallDailyData daily[8]; 168 | } OpenWeatherMapOneCallData; 169 | 170 | class OpenWeatherMapOneCall: public JsonListener { 171 | private: 172 | const String host = "api.openweathermap.org"; 173 | const uint8_t port = 80; 174 | String currentKey = "ROOT"; 175 | String currentParent; 176 | OpenWeatherMapOneCallData *data; 177 | uint8_t weatherItemCounter = 0; 178 | uint8_t dailyItemCounter = 0; 179 | uint8_t hourlyItemCounter = 0; 180 | 181 | boolean metric = true; 182 | String language; 183 | uint8_t maxDailyForecasts = 5; 184 | uint8_t maxHourlyForecasts = 12; 185 | uint8_t *allowedHours; 186 | uint8_t allowedHoursCount = 0; 187 | uint8_t currentForecast; 188 | 189 | void doUpdate(OpenWeatherMapOneCallData *data, String path); 190 | String buildPath(String appId, float lat, float lon); 191 | 192 | public: 193 | OpenWeatherMapOneCall(); 194 | void update(OpenWeatherMapOneCallData *data, String appId, float lat, float lon); 195 | 196 | void setMetric(boolean metric) {this->metric = metric;} 197 | boolean isMetric() { return metric; } 198 | void setLanguage(String language) { this->language = language; } 199 | String getLanguage() { return language; } 200 | 201 | String getMeteoconIcon(String icon); 202 | 203 | virtual void whitespace(char c); 204 | virtual void startDocument(); 205 | virtual void key(String key); 206 | virtual void value(String value); 207 | virtual void endArray(); 208 | virtual void endObject(); 209 | virtual void endDocument(); 210 | virtual void startArray(); 211 | virtual void startObject(); 212 | }; 213 | 214 | -------------------------------------------------------------------------------- /src/Astronomy.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #include "Astronomy.h" 25 | 26 | /* pi/180 */ 27 | #define RPD 1.74532925199e-2 28 | 29 | Astronomy::Astronomy() { 30 | 31 | } 32 | 33 | /** 34 | * Convenience method to calculate moon phase by unix time stamp. See calculateMoonPhase(int year, int month, int day) 35 | * for details. 36 | */ 37 | uint8_t Astronomy::calculateMoonPhase(time_t timestamp) { 38 | struct tm* timeInfo; 39 | timeInfo = localtime(×tamp); 40 | return calculateMoonPhase(timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday); 41 | } 42 | /** 43 | * Calculates the moon phase for a given date, accurate to 1 segment. The result is in the range 0..7. 44 | * 0 => new moon 45 | * 4 => full moon 46 | * 47 | * Source: https://www.voidware.com/moon_phase.htm 48 | */ 49 | uint8_t Astronomy::calculateMoonPhase(uint16_t year, uint8_t month, uint8_t day) { 50 | 51 | // This floating point moon phase algorithm works by simply dividing the lunar month of 29.53 days into the number 52 | // of days elapsed since a known new moon. So, it’s highly simplified, but should be good enough to get quarter 53 | // phases. 54 | 55 | uint32_t yearAsDays; 56 | uint16_t monthAsDays; 57 | double daysSinceReferenceNewMoon; 58 | double moonCycles; 59 | uint16_t completedMoonCycles; 60 | double moonAge; 61 | uint8_t phase; 62 | 63 | // This adjustment ultimately comes from the calculation to turn y/m/d into a whole number of days offset. It’s a 64 | // modified version of the Julian Day number, but here it’s been simplified to work only between years 2000/1/1 and 65 | // 2099/12/31. 66 | if (month < 3) { 67 | year--; 68 | month += 12; 69 | } 70 | month++; 71 | 72 | yearAsDays = 365.25 * year; // 365.25 -> mean length of a calendar year 73 | monthAsDays = 30.6 * month; // 30.6 -> mean length of a month 74 | daysSinceReferenceNewMoon = yearAsDays + monthAsDays + day - 694039.09; // number of days since known new moon on 1900-01-01, 694039.09 -> days elapsed since zero 75 | moonCycles = daysSinceReferenceNewMoon / 29.53; // 29.53 -> long-term average moon cycle duration in days 76 | completedMoonCycles = moonCycles; // "casting" to int to get *completed* moon cycles i.e. only integer part 77 | moonAge = moonCycles - completedMoonCycles; // subtract integer part to leave fractional part which represents the current moon age 78 | phase = moonAge * 8 + 0.5; // scale fraction from 0-8 and round by adding 0.5 79 | phase = phase & 7; // 0 and 8 are the same so turn 8 into 0 80 | return phase; 81 | } 82 | 83 | /** 84 | * Convenience method to calculate moon phase and illumination by unix time stamp. See 85 | * calculateMoonData(int year, int month, int day) for details. 86 | */ 87 | Astronomy::MoonData Astronomy::calculateMoonData(time_t timestamp) { 88 | struct tm* timeInfo; 89 | timeInfo = localtime(×tamp); 90 | return calculateMoonData(timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday); 91 | } 92 | /** 93 | * Calculates the moon phase and illumination for a given date, accurate to 1 segment. 94 | * The result is in the range 0..7 for the phase. 95 | * 0 => new moon 96 | * 4 => full moon 97 | * Illumination ranges from 0.0 (new moon) to 1.0 (full moon). 98 | * 99 | * Source: Hugh from https://www.voidware.com 100 | */ 101 | Astronomy::MoonData Astronomy::calculateMoonData(uint16_t year, uint8_t month, uint8_t day) { 102 | 103 | MoonData moonData; 104 | 105 | // from Gregorian year, month, day, calculate the Julian Day number 106 | uint8_t c; 107 | uint32_t jd; 108 | if (month < 3) { 109 | --year; 110 | month += 10; 111 | } else month -= 2; 112 | 113 | c = year / 100; 114 | jd = 30.59 * month; 115 | jd += 365.25 * year; 116 | jd += day; 117 | jd += c / 4 - c; 118 | 119 | // adjust to Julian centuries from noon, the day specified. 120 | double t = (jd - 730455.5) / 36525; 121 | 122 | // following calculation from Astronomical Algorithms, Jean Meeus 123 | // D, M, MM from (47.2, 47.3, 47.3 page 338) 124 | 125 | // mean elongation of the moon 126 | double D = 297.8501921 + t * (445267.1114034 + 127 | t * (-0.0018819 + t * (1.0 / 545868 - t / 113065000))); 128 | 129 | // suns mean anomaly 130 | double M = 357.5291092 + t * (35999.0502909 + t * (-0.0001536 + t / 24490000)); 131 | 132 | // moons mean anomaly 133 | double MM = 134.9633964 + 134 | t * (477198.8675055 + t * (0.0087414 + t * (1.0 / 69699 - t / 14712000))); 135 | 136 | // (48.4 p346) 137 | double i = 180 - D 138 | - 6.289 * sin(MM * RPD) 139 | + 2.100 * sin(M * RPD) 140 | - 1.274 * sin((2 * D - MM) * RPD) 141 | - 0.658 * sin(2 * D * RPD) 142 | - 0.214 * sin(2 * MM * RPD) 143 | - 0.110 * sin(D * RPD); 144 | 145 | if (i < 0) i = -i; 146 | if (i >= 360) i -= floor(i / 360) * 360; 147 | 148 | // (48.1 p 345) 149 | // this is the proportion illuminated calculated from `i`, the phase angle 150 | double k = (1 + cos(i * RPD)) / 2; 151 | 152 | // but for the `phase` don't use the phase angle 153 | // instead just consider the 0-360 cycle to get equal parts per phase 154 | uint8_t ki = i / 22.5; 155 | if (++ki == 16) ki = 0; 156 | ki = (ki / 2 + 4) & 7; 157 | 158 | moonData.phase = ki; 159 | moonData.illumination = k; 160 | 161 | return moonData; 162 | } 163 | -------------------------------------------------------------------------------- /examples/WorldClockDemo/WorldClockDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by Daniel Eichhorn - ThingPulse 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 https://thingpulse.com 24 | */ 25 | 26 | #include 27 | 28 | #if defined(ESP8266) 29 | #include 30 | #else 31 | #include 32 | #endif 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "icons.h" 41 | #include "fonts.h" 42 | 43 | 44 | 45 | /*************************** 46 | * Begin Settings 47 | **************************/ 48 | // WIFI 49 | const char* WIFI_SSID = "yourssid"; 50 | const char* WIFI_PWD = "yourpassw0rd"; 51 | 52 | // Setup 53 | const int UPDATE_INTERVAL_SECS = 10 * 60; // Update every 10 minutes 54 | 55 | // Display Settings 56 | const int I2C_DISPLAY_ADDRESS = 0x3c; 57 | const int SDA_PIN = SDA; 58 | const int SCL_PIN = SCL; 59 | 60 | SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SCL_PIN); 61 | OLEDDisplayUi ui ( &display ); 62 | 63 | /*************************** 64 | * End Settings 65 | **************************/ 66 | String timeZoneIds [] = {"America/New_York", "Europe/London", "Europe/Paris", "Australia/Sydney"}; 67 | WorldClockClient worldClockClient("de", "CH", "E, dd. MMMMM yyyy", 4, timeZoneIds); 68 | 69 | 70 | // flag changed in the ticker function every 10 minutes 71 | bool readyForUpdate = false; 72 | 73 | String lastUpdate = "--"; 74 | 75 | Ticker ticker; 76 | 77 | 78 | void updateData(OLEDDisplay *display) { 79 | drawProgress(display, 50, "Updating Time..."); 80 | worldClockClient.updateTime(); 81 | drawProgress(display, 100, "Done..."); 82 | readyForUpdate = false; 83 | delay(1000); 84 | } 85 | 86 | void drawProgress(OLEDDisplay *display, int percentage, String label) { 87 | display->clear(); 88 | display->setTextAlignment(TEXT_ALIGN_CENTER); 89 | display->setFont(ArialMT_Plain_10); 90 | display->drawString(64, 10, label); 91 | display->drawProgressBar(10, 28, 108, 12, percentage); 92 | display->display(); 93 | } 94 | 95 | void drawClock(OLEDDisplay *display, int x, int y, int timeZoneIndex, String city, const uint8_t* icon) { 96 | display->setTextAlignment(TEXT_ALIGN_LEFT); 97 | display->setFont(ArialMT_Plain_10); 98 | display->drawString(x + 60, y + 5, city); 99 | display->setFont(Crushed_Plain_36); 100 | display->drawXbm(x, y, 60, 60, icon); 101 | display->drawString(x + 60, y + 15, worldClockClient.getHours(timeZoneIndex) + ":" + worldClockClient.getMinutes(timeZoneIndex)); 102 | 103 | } 104 | 105 | void drawFrame1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 106 | drawClock(display, x, y, 0, "New York", new_york_bits); 107 | } 108 | 109 | void drawFrame2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 110 | drawClock(display, x, y, 1, "London", london_bits); 111 | } 112 | 113 | void drawFrame3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 114 | drawClock(display, x, y, 2, "Paris", paris_bits); 115 | } 116 | 117 | void drawFrame4(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 118 | drawClock(display, x, y, 3, "Sydney", sydney_bits); 119 | } 120 | 121 | 122 | void setReadyForWeatherUpdate() { 123 | Serial.println("Setting readyForUpdate to true"); 124 | readyForUpdate = true; 125 | } 126 | 127 | // this array keeps function pointers to all frames 128 | // frames are the single views that slide from right to left 129 | FrameCallback frames[] = { drawFrame1, drawFrame2, drawFrame3, drawFrame4}; 130 | int numberOfFrames = 4; 131 | 132 | void setup() { 133 | Serial.begin(115200); 134 | Serial.println(); 135 | Serial.println(); 136 | 137 | // initialize dispaly 138 | display.init(); 139 | display.clear(); 140 | display.display(); 141 | 142 | //display.flipScreenVertically(); 143 | display.setFont(ArialMT_Plain_10); 144 | display.setTextAlignment(TEXT_ALIGN_CENTER); 145 | display.setContrast(255); 146 | 147 | WiFi.begin(WIFI_SSID, WIFI_PWD); 148 | 149 | int counter = 0; 150 | while (WiFi.status() != WL_CONNECTED) { 151 | delay(500); 152 | Serial.print("."); 153 | display.clear(); 154 | display.drawString(64, 10, "Connecting to WiFi"); 155 | display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbol : inactiveSymbol); 156 | display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbol : inactiveSymbol); 157 | display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbol : inactiveSymbol); 158 | display.display(); 159 | 160 | counter++; 161 | } 162 | 163 | ui.setTargetFPS(30); 164 | 165 | // You can change this to 166 | // TOP, LEFT, BOTTOM, RIGHT 167 | ui.setIndicatorPosition(BOTTOM); 168 | 169 | // Defines where the first frame is located in the bar. 170 | ui.setIndicatorDirection(LEFT_RIGHT); 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 | // Add frames 177 | ui.setFrames(frames, numberOfFrames); 178 | 179 | // Inital UI takes care of initalising the display too. 180 | ui.init(); 181 | 182 | Serial.println(""); 183 | 184 | updateData(&display); 185 | 186 | ticker.attach(UPDATE_INTERVAL_SECS, setReadyForWeatherUpdate); 187 | 188 | } 189 | 190 | void loop() { 191 | 192 | if (readyForUpdate && ui.getUiState()->frameState == FIXED) { 193 | updateData(&display); 194 | } 195 | 196 | int remainingTimeBudget = ui.update(); 197 | 198 | if (remainingTimeBudget > 0) { 199 | // You can do some work here 200 | // Don't do stuff if you are below your 201 | // time budget. 202 | delay(remainingTimeBudget); 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /examples/OpenWeatherMapForecastDemo/OpenWeatherMapForecastDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #include 25 | 26 | #if defined(ESP8266) 27 | #include 28 | #else 29 | #include 30 | #endif 31 | #include 32 | #include 33 | #include "OpenWeatherMapForecast.h" 34 | 35 | 36 | // initiate the client 37 | OpenWeatherMapForecast client; 38 | 39 | // See https://docs.thingpulse.com/how-tos/openweathermap-key/ 40 | String OPEN_WEATHER_MAP_APP_ID = "XXX"; 41 | /* 42 | Use the OWM GeoCoder API to find lat/lon for your city: https://openweathermap.org/api/geocoding-api 43 | Or use any other geocoding service. 44 | Or go to https://openweathermap.org, search for your city and monitor the calls in the browser dev console :) 45 | */ 46 | // Example: Zurich, Switzerland 47 | float OPEN_WEATHER_MAP_LOCATION_LAT = 47.3667; 48 | float OPEN_WEATHER_MAP_LOCATION_LON = 8.55; 49 | /* 50 | Arabic - ar, Bulgarian - bg, Catalan - ca, Czech - cz, German - de, Greek - el, 51 | English - en, Persian (Farsi) - fa, Finnish - fi, French - fr, Galician - gl, 52 | Croatian - hr, Hungarian - hu, Italian - it, Japanese - ja, Korean - kr, 53 | Latvian - la, Lithuanian - lt, Macedonian - mk, Dutch - nl, Polish - pl, 54 | Portuguese - pt, Romanian - ro, Russian - ru, Swedish - se, Slovak - sk, 55 | Slovenian - sl, Spanish - es, Turkish - tr, Ukrainian - ua, Vietnamese - vi, 56 | Chinese Simplified - zh_cn, Chinese Traditional - zh_tw. 57 | */ 58 | String OPEN_WEATHER_MAP_LANGUAGE = "en"; 59 | boolean IS_METRIC = false; 60 | uint8_t MAX_FORECASTS = 15; 61 | 62 | /** 63 | * WiFi Settings 64 | */ 65 | #if defined(ESP8266) 66 | const char* ESP_HOST_NAME = "esp-" + ESP.getFlashChipId(); 67 | #else 68 | const char* ESP_HOST_NAME = "esp-" + ESP.getEfuseMac(); 69 | #endif 70 | const char* WIFI_SSID = "yourssid"; 71 | const char* WIFI_PASSWORD = "yourpassw0rd"; 72 | 73 | // initiate the WifiClient 74 | WiFiClient wifiClient; 75 | 76 | 77 | 78 | /** 79 | * Helping funtions 80 | */ 81 | void connectWifi() { 82 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 83 | Serial.print("Connecting to "); 84 | Serial.println(WIFI_SSID); 85 | while (WiFi.status() != WL_CONNECTED) { 86 | delay(500); 87 | Serial.print("."); 88 | } 89 | Serial.println(""); 90 | Serial.println("WiFi connected!"); 91 | Serial.println(WiFi.localIP()); 92 | Serial.println(); 93 | } 94 | 95 | 96 | /** 97 | * SETUP 98 | */ 99 | void setup() { 100 | Serial.begin(115200); 101 | delay(500); 102 | connectWifi(); 103 | 104 | Serial.println(); 105 | Serial.println("\n\nNext Loop-Step: " + String(millis()) + ":"); 106 | 107 | OpenWeatherMapForecastData data[MAX_FORECASTS]; 108 | client.setMetric(IS_METRIC); 109 | client.setLanguage(OPEN_WEATHER_MAP_LANGUAGE); 110 | uint8_t allowedHours[] = {0,12}; 111 | client.setAllowedHours(allowedHours, 2); 112 | uint8_t foundForecasts = client.updateForecasts(data, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_LAT, OPEN_WEATHER_MAP_LOCATION_LON, MAX_FORECASTS); 113 | Serial.printf("Found %d forecasts in this call\n", foundForecasts); 114 | Serial.println("------------------------------------"); 115 | time_t time; 116 | for (uint8_t i = 0; i < foundForecasts; i++) { 117 | Serial.printf("---\nForecast number: %d\n", i); 118 | // {"dt":1527066000, uint32_t observationTime; 119 | time = data[i].observationTime; 120 | Serial.printf("observationTime: %d, full date: %s", data[i].observationTime, ctime(&time)); 121 | // "main":{ 122 | // "temp":17.35, float temp; 123 | Serial.printf("temp: %f\n", data[i].temp); 124 | // "feels_like": 16.99, float feelsLike; 125 | Serial.printf("feels-like temp: %f\n", data[i].feelsLike); 126 | // "temp_min":16.89, float tempMin; 127 | Serial.printf("tempMin: %f\n", data[i].tempMin); 128 | // "temp_max":17.35, float tempMax; 129 | Serial.printf("tempMax: %f\n", data[i].tempMax); 130 | // "pressure":970.8, float pressure; 131 | Serial.printf("pressure: %f\n", data[i].pressure); 132 | // "sea_level":1030.62, float pressureSeaLevel; 133 | Serial.printf("pressureSeaLevel: %f\n", data[i].pressureSeaLevel); 134 | // "grnd_level":970.8, float pressureGroundLevel; 135 | Serial.printf("pressureGroundLevel: %f\n", data[i].pressureGroundLevel); 136 | // "humidity":97, uint8_t humidity; 137 | Serial.printf("humidity: %d\n", data[i].humidity); 138 | // "temp_kf":0.46 139 | // },"weather":[{ 140 | // "id":802, uint16_t weatherId; 141 | Serial.printf("weatherId: %d\n", data[i].weatherId); 142 | // "main":"Clouds", String main; 143 | Serial.printf("main: %s\n", data[i].main.c_str()); 144 | // "description":"scattered clouds", String description; 145 | Serial.printf("description: %s\n", data[i].description.c_str()); 146 | // "icon":"03d" String icon; String iconMeteoCon; 147 | Serial.printf("icon: %s\n", data[i].icon.c_str()); 148 | Serial.printf("iconMeteoCon: %s\n", data[i].iconMeteoCon.c_str()); 149 | // }],"clouds":{"all":44}, uint8_t clouds; 150 | Serial.printf("clouds: %d\n", data[i].clouds); 151 | // "wind":{ 152 | // "speed":1.77, float windSpeed; 153 | Serial.printf("windSpeed: %f\n", data[i].windSpeed); 154 | // "deg":207.501 float windDeg; 155 | Serial.printf("windDeg: %f\n", data[i].windDeg); 156 | // rain: {3h: 0.055}, float rain; 157 | Serial.printf("rain: %f\n", data[i].rain); 158 | // },"sys":{"pod":"d"} 159 | // dt_txt: "2018-05-23 09:00:00" String observationTimeText; 160 | Serial.printf("observationTimeText: %s\n", data[i].observationTimeText.c_str()); 161 | } 162 | 163 | Serial.println(); 164 | Serial.println("---------------------------------------------------/\n"); 165 | 166 | } 167 | 168 | 169 | /** 170 | * LOOP 171 | */ 172 | void loop() { 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/AerisSunMoon.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #include 25 | #include 26 | #include "AerisSunMoon.h" 27 | 28 | AerisSunMoon::AerisSunMoon() { 29 | 30 | } 31 | 32 | void AerisSunMoon::updateSunMoon(AerisSunMoonData *sunMoonData, String clientId, String clientSecret, String location) { 33 | doUpdate(sunMoonData, "/sunmoon/" + location + "?client_id=" + clientId + "&client_secret=" + clientSecret); 34 | } 35 | 36 | void AerisSunMoon::doUpdate(AerisSunMoonData *sunMoonData, String path) { 37 | this->sunMoonData = sunMoonData; 38 | 39 | unsigned long lostTest = 10000UL; 40 | unsigned long lost_do = millis(); 41 | 42 | JsonStreamingParser parser; 43 | parser.setListener(this); 44 | Serial.printf("[HTTP] Requesting resource at http://%s:%u%s\n", host.c_str(), port, path.c_str()); 45 | 46 | WiFiClient client; 47 | #if defined(ESP8266) 48 | if (client.connect(host, port)) { 49 | #else 50 | if (client.connect(host.c_str(), port)) { 51 | #endif 52 | bool isBody = false; 53 | char c; 54 | Serial.println("[HTTP] connected, now GETting data"); 55 | client.print("GET " + path + " HTTP/1.1\r\n" 56 | "Host: " + host + "\r\n" 57 | "Connection: close\r\n\r\n"); 58 | 59 | while (client.connected() || client.available()) { 60 | if (client.available()) { 61 | if ((millis() - lost_do) > lostTest) { 62 | Serial.println("[HTTP] lost in client with a timeout"); 63 | client.stop(); 64 | ESP.restart(); 65 | } 66 | c = client.read(); 67 | if (c == '{' || c == '[') { 68 | isBody = true; 69 | } 70 | if (isBody) { 71 | parser.parse(c); 72 | } 73 | } 74 | // give WiFi and TCP/IP libraries a chance to handle pending events 75 | yield(); 76 | } 77 | client.stop(); 78 | } else { 79 | Serial.println("[HTTP] failed to connect to host"); 80 | } 81 | this->sunMoonData = nullptr; 82 | } 83 | 84 | void AerisSunMoon::whitespace(char c) { 85 | Serial.println("whitespace"); 86 | } 87 | 88 | void AerisSunMoon::startDocument() { 89 | Serial.println("start document"); 90 | } 91 | 92 | void AerisSunMoon::key(String key) { 93 | currentKey = String(key); 94 | } 95 | 96 | void AerisSunMoon::value(String value) { 97 | // uint64_t sunRise; // "rise":1493291184, 98 | if (currentParent == "sun") { 99 | if (currentKey == "rise") { 100 | this->sunMoonData->sunRise = value.toInt(); 101 | } 102 | // String sunRiseISO; // "riseISO":"2017-04-27T06:06:24-05:00", 103 | if (currentKey == "riseISO") { 104 | this->sunMoonData->sunRiseISO = value; 105 | } 106 | // uint64_t sunSet; // "set":1493342079, 107 | if (currentKey == "set") { 108 | this->sunMoonData->sunSet = value.toInt(); 109 | } 110 | // String sunSetISO; //"setISO":"2017-04-27T20:14:39-05:00", 111 | if (currentKey == "setISO") { 112 | this->sunMoonData->sunSetISO = value; 113 | } 114 | // uint64_t sunTransit; // "transit":1493316631, 115 | if (currentKey == "transit") { 116 | this->sunMoonData->sunTransit = value.toInt(); 117 | } 118 | // String sunTransitISO; // "transitISO":"2017-04-27T13:10:31-05:00", 119 | if (currentKey == "transitISO") { 120 | this->sunMoonData->sunTransitISO = value; 121 | } 122 | // boolean midnightSun; // "midnightSun":false, 123 | if (currentKey == "midnightSun") { 124 | this->sunMoonData->midnightSun = (value == "true" ? true : false); 125 | } 126 | // boolean polarNight; // "polarNight":false, 127 | if (currentKey == "polarNight") { 128 | this->sunMoonData->polarNight = (value == "true" ? true : false); 129 | } 130 | } 131 | if (currentParent == "moon") { 132 | // uint64_t moonRise; //"rise":1493295480, 133 | if (currentKey == "rise") { 134 | this->sunMoonData->moonRise = value.toInt(); 135 | } 136 | // String moonRiseISO; // "riseISO":"2017-04-27T07:18:00-05:00", 137 | if (currentKey == "riseISO") { 138 | this->sunMoonData->moonRiseISO = value; 139 | } 140 | // uint64_t moonSet; // "set":1493347800, 141 | if (currentKey == "set") { 142 | this->sunMoonData->moonSet = value.toInt(); 143 | } 144 | // String moonSetISO; // "setISO":"2017-04-27T21:50:00-05:00", 145 | if (currentKey == "setISO") { 146 | this->sunMoonData->moonSetISO = value; 147 | } 148 | // uint64_t moonTransit; // "transit":1493321340, 149 | if (currentKey == "transit") { 150 | this->sunMoonData->moonTransit = value.toInt(); 151 | } 152 | // String moonTransitISO; // "transitISO":"2017-04-27T14:29:00-05:00", 153 | if (currentKey == "transitISO") { 154 | this->sunMoonData->moonTransitISO = value; 155 | } 156 | // uint64_t moonUnderfoot; // "underfoot":1493276400, 157 | if (currentKey == "underfoot") { 158 | this->sunMoonData->moonUnderfoot = value.toInt(); 159 | } 160 | // String moonUnderfootISO; // "underfootISO":"2017-04-27T02:00:00-05:00", 161 | if (currentKey == "underfootISO") { 162 | this->sunMoonData->moonUnderfootISO = value; 163 | } 164 | } 165 | if (currentParent == "phase") { 166 | // float moonPhase; // "phase":0.0516, 167 | if (currentKey == "phase") { 168 | this->sunMoonData->moonPhase = value.toFloat(); 169 | } 170 | // String moonPhaseName; // "name":"waxing crescent", 171 | if (currentKey == "name") { 172 | this->sunMoonData->moonPhaseName = value; 173 | } 174 | // uint8_t moonIllum; // "illum":3, 175 | if (currentKey == "illum") { 176 | this->sunMoonData->moonIllum = value.toInt(); 177 | } 178 | // float moonAge; // "age":1.52, 179 | if (currentKey == "age") { 180 | this->sunMoonData->moonAge = value.toFloat(); 181 | } 182 | // float moonAngle; // "angle":0.55 183 | if (currentKey == "angle") { 184 | this->sunMoonData->moonAngle = value.toFloat(); 185 | } 186 | } 187 | } 188 | 189 | void AerisSunMoon::endArray() { 190 | 191 | } 192 | 193 | 194 | void AerisSunMoon::startObject() { 195 | Serial.println("Starting new object: " + currentKey); 196 | currentParent = currentKey; 197 | } 198 | 199 | void AerisSunMoon::endObject() { 200 | currentParent = ""; 201 | } 202 | 203 | void AerisSunMoon::endDocument() { 204 | 205 | } 206 | 207 | void AerisSunMoon::startArray() { 208 | 209 | } 210 | -------------------------------------------------------------------------------- /examples/AerisObservationDemo/AerisObservationDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #include 25 | 26 | #if defined(ESP8266) 27 | #include 28 | #else 29 | #include 30 | #endif 31 | #include 32 | #include "AerisObservations.h" 33 | 34 | /** 35 | * Aeris Weather 36 | */ 37 | 38 | // initiate the WundergoundClient 39 | AerisObservations observations; 40 | 41 | String AERIS_CLIENT_ID = "tWOmsRUXe4EFTHQKmUKOK"; 42 | String AERIS_SECRET_KEY = "gRoMoapOyg46HwB7dRmoVPaJ0vUgAiud1CFWuLfF"; 43 | String AERIS_LOCATION = "Zurich,CH"; 44 | 45 | /** 46 | * WiFi Settings 47 | */ 48 | #if defined(ESP8266) 49 | const char* ESP_HOST_NAME = "esp-" + ESP.getFlashChipId(); 50 | #else 51 | const char* ESP_HOST_NAME = "esp-" + ESP.getEfuseMac(); 52 | #endif 53 | const char* WIFI_SSID = "yourssid"; 54 | const char* WIFI_PASSWORD = "yourpassw0rd"; 55 | 56 | // initiate the WifiClient 57 | WiFiClient wifiClient; 58 | 59 | 60 | 61 | /** 62 | * Helping funtions 63 | */ 64 | void connectWifi() { 65 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 66 | Serial.print("Connecting to "); 67 | Serial.println(WIFI_SSID); 68 | while (WiFi.status() != WL_CONNECTED) { 69 | delay(500); 70 | Serial.print("."); 71 | } 72 | Serial.println(""); 73 | Serial.println("WiFi connected!"); 74 | Serial.println(WiFi.localIP()); 75 | Serial.println(); 76 | } 77 | 78 | 79 | /** 80 | * SETUP 81 | */ 82 | void setup() { 83 | Serial.begin(115200); 84 | delay(500); 85 | connectWifi(); 86 | 87 | Serial.println(); 88 | Serial.println("\n\nNext Loop-Step: " + String(millis()) + ":"); 89 | 90 | AerisObservationsData observationData; 91 | observations.updateObservations(&observationData, AERIS_CLIENT_ID, AERIS_SECRET_KEY, AERIS_LOCATION); 92 | 93 | Serial.println("------------------------------------"); 94 | 95 | 96 | // uint64_t timestamp; 97 | Serial.printf("timestamp: %d\n", observationData.timestamp); 98 | // String dateTimeISO; 99 | Serial.printf("dateTimeISO: %s\n", observationData.dateTimeISO.c_str()); 100 | // sint16_t tempC; 101 | Serial.printf("tempC: %d\n", observationData.tempC); 102 | // sint16_t tempF; 103 | Serial.printf("tempF: %d\n", observationData.tempF); 104 | // sint16_t dewpointC; 105 | Serial.printf("dewpointC: %d\n", observationData.dewpointC); 106 | // sint16_t dewpointF; 107 | Serial.printf("dewpointF: %d\n", observationData.dewpointF); 108 | // uint8_t humidity; 109 | Serial.printf("humidity: %d\n", observationData.humidity); 110 | // uint16_t pressureMB; 111 | Serial.printf("pressureMB: %d\n", observationData.pressureMB); 112 | // float pressureIN; 113 | Serial.printf("pressureIN: %f\n", observationData.pressureIN); 114 | // uint16_t spressureMB; 115 | Serial.printf("spressureMB: %d\n", observationData.spressureMB); 116 | // float spressureIN; 117 | Serial.printf("spressureIN: %f\n", observationData.spressureIN); 118 | // uint16_t altimeterMB; 119 | Serial.printf("altimeterMB: %d\n", observationData.altimeterMB); 120 | // float altimeterIN; 121 | Serial.printf("altimeterIN: %f\n", observationData.altimeterIN); 122 | // uint16_t windSpeedKTS; 123 | Serial.printf("windSpeedKTS: %d\n", observationData.windSpeedKTS); 124 | // uint16_t windSpeedKPH; 125 | Serial.printf("windSpeedKPH: %d\n", observationData.windSpeedKPH); 126 | // uint16_t windSpeedMPH; 127 | Serial.printf("windSpeedMPH: %d\n", observationData.windSpeedMPH); 128 | // uint16_t windDirDEG; 129 | Serial.printf("windDirDEG: %d\n", observationData.windDirDEG); 130 | // String windDir; 131 | Serial.printf("windDir: %s\n", observationData.windDir.c_str()); 132 | // uint16_t windGustKTS; 133 | Serial.printf("windGustKTS: %d\n", observationData.windGustKTS); 134 | // uint16_t windGustKPH; 135 | Serial.printf("windGustKPH: %d\n", observationData.windGustKPH); 136 | // uint16_t windGustMPH; 137 | Serial.printf("windGustMPH: %d\n", observationData.windGustMPH); 138 | // String flightRule; 139 | Serial.printf("flightRule: %s\n", observationData.flightRule.c_str()); 140 | // float visibilityKM; 141 | Serial.printf("visibilityKM: %f\n", observationData.visibilityKM); 142 | // float visibilityMI; 143 | Serial.printf("visibilityMI: %f\n", observationData.visibilityMI); 144 | // String weather; 145 | Serial.printf("weather: %s\n", observationData.weather.c_str()); 146 | // String weatherShort; 147 | Serial.printf("weatherShort: %s\n", observationData.weatherShort.c_str()); 148 | // String weatherCoded; 149 | Serial.printf("weatherCoded: %s\n", observationData.weatherCoded.c_str()); 150 | // String weatherPrimary; 151 | Serial.printf("weatherPrimary: %s\n", observationData.weatherPrimary.c_str()); 152 | // String weatherPrimaryCoded; 153 | Serial.printf("weatherPrimaryCoded: %s\n", observationData.weatherPrimaryCoded.c_str()); 154 | // String cloudsCoded; 155 | Serial.printf("cloudsCoded: %s\n", observationData.cloudsCoded.c_str()); 156 | // String icon; 157 | Serial.printf("icon: %s\n", observationData.icon.c_str()); 158 | // String iconMeteoCon; 159 | Serial.printf("iconMeteoCon: %s\n", observationData.iconMeteoCon.c_str()); 160 | // sint16_t heatindexC; 161 | Serial.printf("heatindexC: %d\n", observationData.heatindexC); 162 | // sint16_t heatindexF; 163 | Serial.printf("heatindexF: %d\n", observationData.heatindexF); 164 | // sint16_t windchillC; 165 | Serial.printf("windchillC: %d\n", observationData.windchillC); 166 | // sint16_t windchillF; 167 | Serial.printf("windchillF: %d\n", observationData.windchillF); 168 | // sint16_t feelslikeC; 169 | Serial.printf("feelslikeC: %d\n", observationData.feelslikeC); 170 | // sint16_t feelslikeF; 171 | Serial.printf("feelslikeF: %d\n", observationData.feelslikeF); 172 | // boolean isDay; 173 | Serial.printf("isDay: %d\n", observationData.isDay); 174 | // uint64_t sunrise; 175 | Serial.printf("sunrise: %d\n", observationData.sunrise); 176 | // String sunriseISO; 177 | Serial.printf("sunriseISO: %s\n", observationData.sunriseISO.c_str()); 178 | // uint64_t sunset; 179 | Serial.printf("sunset: %d\n", observationData.sunset); 180 | // String sunsetISO; 181 | Serial.printf("sunsetISO: %s\n", observationData.sunsetISO.c_str()); 182 | // uint16_t snowDepthCM; 183 | Serial.printf("snowDepthCM: %d\n", observationData.snowDepthCM); 184 | // uint16_t snowDepthIN; 185 | Serial.printf("snowDepthIN: %d\n", observationData.snowDepthIN); 186 | // uint16_t precipMM; 187 | Serial.printf("precipMM: %d\n", observationData.precipMM); 188 | // uint16_t precipIN; 189 | Serial.printf("precipIN: %d\n", observationData.precipIN); 190 | // uint16_t solradWM2; 191 | Serial.printf("solradWM2: %d\n", observationData.solradWM2); 192 | // String solradMethod; 193 | Serial.printf("solradMethod: %s\n", observationData.solradMethod.c_str()); 194 | // uint16_t light; 195 | Serial.printf("light: %d\n", observationData.light); 196 | // uint16_t sky; 197 | Serial.printf("sky: %d\n", observationData.sky); 198 | Serial.println(); 199 | Serial.println("---------------------------------------------------/\n"); 200 | 201 | } 202 | 203 | 204 | /** 205 | * LOOP 206 | */ 207 | void loop() { 208 | 209 | } 210 | -------------------------------------------------------------------------------- /src/OpenWeatherMapCurrent.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #include 25 | #include 26 | #include "OpenWeatherMapCurrent.h" 27 | 28 | OpenWeatherMapCurrent::OpenWeatherMapCurrent() { 29 | 30 | } 31 | 32 | void OpenWeatherMapCurrent::updateCurrent(OpenWeatherMapCurrentData *data, String appId, String location) { 33 | doUpdate(data, buildPath(appId, "q=" + location)); 34 | } 35 | void OpenWeatherMapCurrent::updateCurrent(OpenWeatherMapCurrentData *data, String appId, float lat, float lon) { 36 | doUpdate(data, buildPath(appId, "lat=" + String(lat) + "&lon=" + String(lon))); 37 | } 38 | 39 | void OpenWeatherMapCurrent::updateCurrentById(OpenWeatherMapCurrentData *data, String appId, String locationId) { 40 | doUpdate(data, buildPath(appId, "id=" + locationId)); 41 | } 42 | 43 | String OpenWeatherMapCurrent::buildPath(String appId, String locationParameter) { 44 | String units = metric ? "metric" : "imperial"; 45 | return "/data/2.5/weather?" + locationParameter + "&appid=" + appId + "&units=" + units + "&lang=" + language; 46 | } 47 | 48 | void OpenWeatherMapCurrent::doUpdate(OpenWeatherMapCurrentData *data, String path) { 49 | unsigned long lostTest = 10000UL; 50 | unsigned long lost_do = millis(); 51 | this->weatherItemCounter = 0; 52 | this->data = data; 53 | JsonStreamingParser parser; 54 | parser.setListener(this); 55 | Serial.printf("[HTTP] Requesting resource at http://%s:%u%s\n", host.c_str(), port, path.c_str()); 56 | 57 | WiFiClient client; 58 | #if defined(ESP8266) 59 | if (client.connect(host, port)) { 60 | #else 61 | if (client.connect(host.c_str(), port)) { 62 | #endif 63 | bool isBody = false; 64 | char c; 65 | Serial.println("[HTTP] connected, now GETting data"); 66 | client.print("GET " + path + " HTTP/1.1\r\n" 67 | "Host: " + host + "\r\n" 68 | "Connection: close\r\n\r\n"); 69 | 70 | while (client.connected() || client.available()) { 71 | if (client.available()) { 72 | if ((millis() - lost_do) > lostTest) { 73 | Serial.println("[HTTP] lost in client with a timeout"); 74 | client.stop(); 75 | ESP.restart(); 76 | } 77 | c = client.read(); 78 | if (c == '{' || c == '[') { 79 | isBody = true; 80 | } 81 | if (isBody) { 82 | parser.parse(c); 83 | } 84 | } 85 | // give WiFi and TCP/IP libraries a chance to handle pending events 86 | yield(); 87 | } 88 | client.stop(); 89 | } else { 90 | Serial.println("[HTTP] failed to connect to host"); 91 | } 92 | this->data = nullptr; 93 | } 94 | 95 | void OpenWeatherMapCurrent::whitespace(char c) { 96 | Serial.println("whitespace"); 97 | } 98 | 99 | void OpenWeatherMapCurrent::startDocument() { 100 | Serial.println("start document"); 101 | } 102 | 103 | void OpenWeatherMapCurrent::key(String key) { 104 | currentKey = String(key); 105 | } 106 | 107 | void OpenWeatherMapCurrent::value(String value) { 108 | // "lon": 8.54, float lon; 109 | if (currentKey == "lon") { 110 | this->data->lon = value.toFloat(); 111 | } 112 | // "lat": 47.37 float lat; 113 | if (currentKey == "lat") { 114 | this->data->lat = value.toFloat(); 115 | } 116 | // weatherItemCounter: only get the first item if more than one is available 117 | if (currentParent == "weather" && weatherItemCounter == 0) { 118 | // "id": 521, weatherId weatherId; 119 | if (currentKey == "id") { 120 | this->data->weatherId = value.toInt(); 121 | } 122 | // "main": "Rain", String main; 123 | if (currentKey == "main") { 124 | this->data->main = value; 125 | } 126 | // "description": "shower rain", String description; 127 | if (currentKey == "description") { 128 | this->data->description = value; 129 | } 130 | // "icon": "09d" String icon; 131 | //String iconMeteoCon; 132 | if (currentKey == "icon") { 133 | this->data->icon = value; 134 | this->data->iconMeteoCon = getMeteoconIcon(value); 135 | } 136 | 137 | } 138 | 139 | // "temp": 290.56, float temp; 140 | if (currentKey == "temp") { 141 | this->data->temp = value.toFloat(); 142 | } 143 | // "feels_like": 290.87, float feelsLike; 144 | if (currentKey == "feels_like") { 145 | this->data->feelsLike = value.toFloat(); 146 | } 147 | // "pressure": 1013, uint16_t pressure; 148 | if (currentKey == "pressure") { 149 | this->data->pressure = value.toInt(); 150 | } 151 | // "humidity": 87, uint8_t humidity; 152 | if (currentKey == "humidity") { 153 | this->data->humidity = value.toInt(); 154 | } 155 | // "temp_min": 289.15, float tempMin; 156 | if (currentKey == "temp_min") { 157 | this->data->tempMin = value.toFloat(); 158 | } 159 | // "temp_max": 292.15 float tempMax; 160 | if (currentKey == "temp_max") { 161 | this->data->tempMax = value.toFloat(); 162 | } 163 | // visibility: 10000, uint16_t visibility; 164 | if (currentKey == "visibility") { 165 | this->data->visibility = value.toInt(); 166 | } 167 | // "wind": {"speed": 1.5}, float windSpeed; 168 | if (currentKey == "speed") { 169 | this->data->windSpeed = value.toFloat(); 170 | } 171 | // "wind": {deg: 226.505}, float windDeg; 172 | if (currentKey == "deg") { 173 | this->data->windDeg = value.toFloat(); 174 | } 175 | // "clouds": {"all": 90}, uint8_t clouds; 176 | if (currentKey == "all") { 177 | this->data->clouds = value.toInt(); 178 | } 179 | // "dt": 1527015000, uint64_t observationTime; 180 | if (currentKey == "dt") { 181 | this->data->observationTime = value.toInt(); 182 | } 183 | // "country": "CH", String country; 184 | if (currentKey == "country") { 185 | this->data->country = value; 186 | } 187 | // "sunrise": 1526960448, uint32_t sunrise; 188 | if (currentKey == "sunrise") { 189 | this->data->sunrise = value.toInt(); 190 | } 191 | // "sunset": 1527015901 uint32_t sunset; 192 | if (currentKey == "sunset") { 193 | this->data->sunset = value.toInt(); 194 | } 195 | // "name": "Zurich", String cityName; 196 | if (currentKey == "name") { 197 | this->data->cityName = value; 198 | } 199 | } 200 | 201 | void OpenWeatherMapCurrent::endArray() { 202 | 203 | } 204 | 205 | 206 | void OpenWeatherMapCurrent::startObject() { 207 | currentParent = currentKey; 208 | } 209 | 210 | void OpenWeatherMapCurrent::endObject() { 211 | if (currentParent == "weather") { 212 | weatherItemCounter++; 213 | } 214 | currentParent = ""; 215 | } 216 | 217 | void OpenWeatherMapCurrent::endDocument() { 218 | 219 | } 220 | 221 | void OpenWeatherMapCurrent::startArray() { 222 | 223 | } 224 | 225 | 226 | String OpenWeatherMapCurrent::getMeteoconIcon(String icon) { 227 | // clear sky 228 | // 01d 229 | if (icon == "01d") { 230 | return "B"; 231 | } 232 | // 01n 233 | if (icon == "01n") { 234 | return "C"; 235 | } 236 | // few clouds 237 | // 02d 238 | if (icon == "02d") { 239 | return "H"; 240 | } 241 | // 02n 242 | if (icon == "02n") { 243 | return "4"; 244 | } 245 | // scattered clouds 246 | // 03d 247 | if (icon == "03d") { 248 | return "N"; 249 | } 250 | // 03n 251 | if (icon == "03n") { 252 | return "5"; 253 | } 254 | // broken clouds 255 | // 04d 256 | if (icon == "04d") { 257 | return "Y"; 258 | } 259 | // 04n 260 | if (icon == "04n") { 261 | return "%"; 262 | } 263 | // shower rain 264 | // 09d 265 | if (icon == "09d") { 266 | return "R"; 267 | } 268 | // 09n 269 | if (icon == "09n") { 270 | return "8"; 271 | } 272 | // rain 273 | // 10d 274 | if (icon == "10d") { 275 | return "Q"; 276 | } 277 | // 10n 278 | if (icon == "10n") { 279 | return "7"; 280 | } 281 | // thunderstorm 282 | // 11d 283 | if (icon == "11d") { 284 | return "P"; 285 | } 286 | // 11n 287 | if (icon == "11n") { 288 | return "6"; 289 | } 290 | // snow 291 | // 13d 292 | if (icon == "13d") { 293 | return "W"; 294 | } 295 | // 13n 296 | if (icon == "13n") { 297 | return "#"; 298 | } 299 | // mist 300 | // 50d 301 | if (icon == "50d") { 302 | return "M"; 303 | } 304 | // 50n 305 | if (icon == "50n") { 306 | return "M"; 307 | } 308 | // Nothing matched: N/A 309 | return ")"; 310 | 311 | } 312 | -------------------------------------------------------------------------------- /src/OpenWeatherMapForecast.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #include 25 | #include 26 | #include "OpenWeatherMapForecast.h" 27 | 28 | OpenWeatherMapForecast::OpenWeatherMapForecast() { 29 | 30 | } 31 | 32 | uint8_t OpenWeatherMapForecast::updateForecasts(OpenWeatherMapForecastData *data, String appId, String location, uint8_t maxForecasts) { 33 | this->maxForecasts = maxForecasts; 34 | return doUpdate(data, buildPath(appId, "q=" + location)); 35 | } 36 | 37 | uint8_t OpenWeatherMapForecast::updateForecasts(OpenWeatherMapForecastData *data, String appId, float lat, float lon, uint8_t maxForecasts) { 38 | this->maxForecasts = maxForecasts; 39 | return doUpdate(data, buildPath(appId, "lat=" + String(lat) + "&lon=" + String(lon))); 40 | } 41 | 42 | uint8_t OpenWeatherMapForecast::updateForecastsById(OpenWeatherMapForecastData *data, String appId, String locationId, uint8_t maxForecasts) { 43 | this->maxForecasts = maxForecasts; 44 | return doUpdate(data, buildPath(appId, "id=" + locationId)); 45 | } 46 | 47 | String OpenWeatherMapForecast::buildPath(String appId, String locationParameter) { 48 | String units = metric ? "metric" : "imperial"; 49 | return "/data/2.5/forecast?" + locationParameter + "&appid=" + appId + "&units=" + units + "&lang=" + language; 50 | } 51 | 52 | uint8_t OpenWeatherMapForecast::doUpdate(OpenWeatherMapForecastData *data, String path) { 53 | unsigned long lostTest = 10000UL; 54 | unsigned long lost_do = millis(); 55 | this->weatherItemCounter = 0; 56 | this->currentForecast = 0; 57 | this->data = data; 58 | JsonStreamingParser parser; 59 | parser.setListener(this); 60 | Serial.printf("[HTTP] Requesting resource at http://%s:%u%s\n", host.c_str(), port, path.c_str()); 61 | 62 | WiFiClient client; 63 | #if defined(ESP8266) 64 | if (client.connect(host, port)) { 65 | #else 66 | if (client.connect(host.c_str(), port)) { 67 | #endif 68 | bool isBody = false; 69 | char c; 70 | Serial.println("[HTTP] connected, now GETting data"); 71 | client.print("GET " + path + " HTTP/1.1\r\n" 72 | "Host: " + host + "\r\n" 73 | "Connection: close\r\n\r\n"); 74 | 75 | while (client.connected() || client.available()) { 76 | if (client.available()) { 77 | if ((millis() - lost_do) > lostTest) { 78 | Serial.println("[HTTP] lost in client with a timeout"); 79 | client.stop(); 80 | ESP.restart(); 81 | } 82 | c = client.read(); 83 | if (c == '{' || c == '[') { 84 | isBody = true; 85 | } 86 | if (isBody) { 87 | parser.parse(c); 88 | } 89 | } 90 | // give WiFi and TCP/IP libraries a chance to handle pending events 91 | yield(); 92 | } 93 | client.stop(); 94 | } else { 95 | Serial.println("[HTTP] failed to connect to host"); 96 | } 97 | this->data = nullptr; 98 | return currentForecast; 99 | } 100 | 101 | void OpenWeatherMapForecast::whitespace(char c) { 102 | Serial.println("whitespace"); 103 | } 104 | 105 | void OpenWeatherMapForecast::startDocument() { 106 | Serial.println("start document"); 107 | } 108 | 109 | void OpenWeatherMapForecast::key(String key) { 110 | currentKey = String(key); 111 | } 112 | 113 | void OpenWeatherMapForecast::value(String value) { 114 | if (currentForecast >= maxForecasts) { 115 | return; 116 | } 117 | // {"dt":1527066000, uint32_t observationTime; 118 | if (currentKey == "dt") { 119 | data[currentForecast].observationTime = value.toInt(); 120 | 121 | if (allowedHoursCount > 0) { 122 | time_t time = data[currentForecast].observationTime; 123 | struct tm* timeInfo; 124 | timeInfo = gmtime(&time); 125 | uint8_t currentHour = timeInfo->tm_hour; 126 | for (uint8_t i = 0; i < allowedHoursCount; i++) { 127 | if (currentHour == allowedHours[i]) { 128 | isCurrentForecastAllowed = true; 129 | return; 130 | } 131 | } 132 | isCurrentForecastAllowed = false; 133 | return; 134 | } 135 | } 136 | if (!isCurrentForecastAllowed) { 137 | return; 138 | } 139 | // "main":{ 140 | // "temp":17.35, float temp; 141 | if (currentKey == "temp") { 142 | data[currentForecast].temp = value.toFloat(); 143 | // initialize potentially empty values: 144 | data[currentForecast].rain = 0;; 145 | } 146 | // "feels_like": 16.99, float feelsLike; 147 | if (currentKey == "feels_like") { 148 | data[currentForecast].feelsLike = value.toFloat(); 149 | } 150 | // "temp_min":16.89, float tempMin; 151 | if (currentKey == "temp_min") { 152 | data[currentForecast].tempMin = value.toFloat(); 153 | } 154 | // "temp_max":17.35,float tempMax; 155 | if (currentKey == "temp_max") { 156 | data[currentForecast].tempMax = value.toFloat(); 157 | } 158 | // "pressure":970.8,float pressure; 159 | if (currentKey == "pressure") { 160 | data[currentForecast].pressure = value.toFloat(); 161 | } 162 | // "sea_level":1030.62,float pressureSeaLevel; 163 | if (currentKey == "sea_level") { 164 | data[currentForecast].pressureSeaLevel = value.toFloat(); 165 | } 166 | // "grnd_level":970.8,float pressureGroundLevel; 167 | if (currentKey == "grnd_level") { 168 | data[currentForecast].pressureGroundLevel = value.toFloat(); 169 | } 170 | // "":97,uint8_t humidity; 171 | if (currentKey == "humidity") { 172 | data[currentForecast].humidity = value.toInt(); 173 | } 174 | // "temp_kf":0.46 175 | // },"weather":[{ 176 | 177 | if (currentParent == "weather") { 178 | // "id":802,uint16_t weatherId; 179 | if (currentKey == "id") { 180 | data[currentForecast].weatherId = value.toInt(); 181 | } 182 | 183 | // "main":"Clouds",String main; 184 | if (currentKey == "main") { 185 | data[currentForecast].main = value; 186 | } 187 | // "description":"scattered clouds",String description; 188 | if (currentKey == "description") { 189 | data[currentForecast].description = value; 190 | } 191 | // "icon":"03d" String icon; String iconMeteoCon; 192 | if (currentKey == "icon") { 193 | data[currentForecast].icon = value; 194 | data[currentForecast].iconMeteoCon = getMeteoconIcon(value); 195 | } 196 | } 197 | // }],"clouds":{"all":44},uint8_t clouds; 198 | if (currentKey == "all") { 199 | data[currentForecast].clouds = value.toInt(); 200 | } 201 | // "wind":{ 202 | // "speed":1.77, float windSpeed; 203 | if (currentKey == "speed") { 204 | data[currentForecast].windSpeed = value.toFloat(); 205 | } 206 | // "deg":207.501 float windDeg; 207 | if (currentKey == "deg") { 208 | data[currentForecast].windDeg = value.toFloat(); 209 | } 210 | // rain: {3h: 0.055}, float rain; 211 | if (currentKey == "3h") { 212 | data[currentForecast].rain = value.toFloat(); 213 | } 214 | // },"sys":{"pod":"d"} 215 | // dt_txt: "2018-05-23 09:00:00" String observationTimeText; 216 | if (currentKey == "dt_txt") { 217 | data[currentForecast].observationTimeText = value; 218 | // this is not super save, if there is no dt_txt item we'll never get all forecasts; 219 | currentForecast++; 220 | } 221 | } 222 | 223 | void OpenWeatherMapForecast::endArray() { 224 | 225 | } 226 | 227 | 228 | void OpenWeatherMapForecast::startObject() { 229 | currentParent = currentKey; 230 | } 231 | 232 | void OpenWeatherMapForecast::endObject() { 233 | if (currentParent == "weather") { 234 | weatherItemCounter++; 235 | } 236 | currentParent = ""; 237 | } 238 | 239 | void OpenWeatherMapForecast::endDocument() { 240 | 241 | } 242 | 243 | void OpenWeatherMapForecast::startArray() { 244 | 245 | } 246 | 247 | 248 | String OpenWeatherMapForecast::getMeteoconIcon(String icon) { 249 | // clear sky 250 | // 01d 251 | if (icon == "01d") { 252 | return "B"; 253 | } 254 | // 01n 255 | if (icon == "01n") { 256 | return "C"; 257 | } 258 | // few clouds 259 | // 02d 260 | if (icon == "02d") { 261 | return "H"; 262 | } 263 | // 02n 264 | if (icon == "02n") { 265 | return "4"; 266 | } 267 | // scattered clouds 268 | // 03d 269 | if (icon == "03d") { 270 | return "N"; 271 | } 272 | // 03n 273 | if (icon == "03n") { 274 | return "5"; 275 | } 276 | // broken clouds 277 | // 04d 278 | if (icon == "04d") { 279 | return "Y"; 280 | } 281 | // 04n 282 | if (icon == "04n") { 283 | return "%"; 284 | } 285 | // shower rain 286 | // 09d 287 | if (icon == "09d") { 288 | return "R"; 289 | } 290 | // 09n 291 | if (icon == "09n") { 292 | return "8"; 293 | } 294 | // rain 295 | // 10d 296 | if (icon == "10d") { 297 | return "Q"; 298 | } 299 | // 10n 300 | if (icon == "10n") { 301 | return "7"; 302 | } 303 | // thunderstorm 304 | // 11d 305 | if (icon == "11d") { 306 | return "P"; 307 | } 308 | // 11n 309 | if (icon == "11n") { 310 | return "6"; 311 | } 312 | // snow 313 | // 13d 314 | if (icon == "13d") { 315 | return "W"; 316 | } 317 | // 13n 318 | if (icon == "13n") { 319 | return "#"; 320 | } 321 | // mist 322 | // 50d 323 | if (icon == "50d") { 324 | return "M"; 325 | } 326 | // 50n 327 | if (icon == "50n") { 328 | return "M"; 329 | } 330 | // Nothing matched: N/A 331 | return ")"; 332 | 333 | } 334 | -------------------------------------------------------------------------------- /examples/AerisForecastsDemo/AerisForecastsDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 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 | 24 | #include 25 | 26 | #if defined(ESP8266) 27 | #include 28 | #else 29 | #include 30 | #endif 31 | #include 32 | #include "AerisForecasts.h" 33 | 34 | /** 35 | * Aeris Weather 36 | */ 37 | #define MAX_FORECASTS 3 38 | AerisForecasts client; 39 | 40 | String AERIS_CLIENT_ID = "tWOmsRUXe4EFTHQKmUKOK"; 41 | String AERIS_SECRET_KEY = "gRoMoapOyg46HwB7dRmoVPaJ0vUgAiud1CFWuLfF"; 42 | String AERIS_LOCATION = "Zurich,CH"; 43 | 44 | /** 45 | * WiFi Settings 46 | */ 47 | #if defined(ESP8266) 48 | const char* ESP_HOST_NAME = "esp-" + ESP.getFlashChipId(); 49 | #else 50 | const char* ESP_HOST_NAME = "esp-" + ESP.getEfuseMac(); 51 | #endif 52 | const char* WIFI_SSID = "yourssid"; 53 | const char* WIFI_PASSWORD = "yourpassw0rd"; 54 | 55 | // initiate the WifiClient 56 | WiFiClient wifiClient; 57 | 58 | void print(const char* name, String value) { 59 | Serial.printf("%s: %s\n", name, value.c_str()); 60 | } 61 | 62 | void print(String name, uint64_t value) { 63 | Serial.printf("%s: %d\n", name.c_str(), value); 64 | } 65 | 66 | void print(String name, uint32_t value) { 67 | Serial.printf("%s: %d\n", name.c_str(), value); 68 | } 69 | 70 | void print(String name, uint16_t value) { 71 | Serial.printf("%s: %d\n", name.c_str(), value); 72 | } 73 | 74 | void print(String name, int16_t value) { 75 | Serial.printf("%s: %d\n", name.c_str(), value); 76 | } 77 | 78 | void print(String name, uint8_t value) { 79 | Serial.printf("%s: %d\n", name.c_str(), value); 80 | } 81 | 82 | void print(String name, float value) { 83 | Serial.printf("%s: %f\n", name.c_str(), value); 84 | } 85 | 86 | /** 87 | * Helping funtions 88 | */ 89 | void connectWifi() { 90 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 91 | Serial.print("Connecting to "); 92 | Serial.println(WIFI_SSID); 93 | while (WiFi.status() != WL_CONNECTED) { 94 | delay(500); 95 | Serial.print("."); 96 | } 97 | Serial.println(""); 98 | Serial.println("WiFi connected!"); 99 | Serial.println(WiFi.localIP()); 100 | Serial.println(); 101 | } 102 | 103 | 104 | /** 105 | * SETUP 106 | */ 107 | void setup() { 108 | Serial.begin(115200); 109 | delay(500); 110 | connectWifi(); 111 | 112 | Serial.println(); 113 | Serial.println("\n\nNext Loop-Step: " + String(millis()) + ":"); 114 | 115 | AerisForecastData forecasts[3]; 116 | client.updateForecasts(forecasts, AERIS_CLIENT_ID, AERIS_SECRET_KEY, AERIS_LOCATION, MAX_FORECASTS); 117 | 118 | Serial.println("------------------------------------"); 119 | for (int i = 0; i < MAX_FORECASTS; i++) { 120 | // uint64_t timestamp; // 1526706000 121 | print("timestamp", forecasts[i].timestamp); 122 | // String validTime; // "2018-05-19T07:00:00+02:00" 123 | print("validTime", forecasts[i].validTime); 124 | // String dateTimeISO; //"2018-05-19T07:00:00+02:00" 125 | print("dateTimeISO", forecasts[i].dateTimeISO); 126 | // sint16_t maxTempC; //20 127 | print("maxTempC", forecasts[i].maxTempC); 128 | // sint16_t maxTempF; //69 129 | print("maxTempF", forecasts[i].maxTempF); 130 | // sint16_t minTempC; //14 131 | print("minTempC", forecasts[i].minTempC); 132 | // sint16_t minTempF; // 56 133 | print("minTempF", forecasts[i].minTempF); 134 | // sint16_t avgTempC; // 17 135 | print("avgTempC", forecasts[i].avgTempC); 136 | // sint16_t avgTempF; // 62 137 | print("avgTempF", forecasts[i].avgTempF); 138 | // sint16_t tempC; // null 139 | print("tempC", forecasts[i].tempC); 140 | // sint16_t tempF; // null 141 | print("tempF", forecasts[i].tempF); 142 | // sint16_t pop; // 20 143 | print("pop", forecasts[i].pop); 144 | // float precipMM; // 3.53 145 | print("precipMM", forecasts[i].precipMM); 146 | // float precipIN; // 0.14 147 | print("precipIN", forecasts[i].precipIN); 148 | // float iceaccum; // null 149 | print("iceaccum", forecasts[i].iceaccum); 150 | // float iceaccumMM; // null 151 | print("iceaccumMM", forecasts[i].iceaccumMM); 152 | // float iceaccumIN; // null 153 | print("iceaccumIN", forecasts[i].iceaccumIN); 154 | // uint8_t maxHumidity; // 82 155 | print("maxHumidity", forecasts[i].maxHumidity); 156 | // uint8_t minHumidity; // 53 157 | print("minHumidity", forecasts[i].minHumidity); 158 | // uint8_t humidity; // 68 159 | print("humidity", forecasts[i].humidity); 160 | // uint8_t uvi; // 6 161 | print("uvi", forecasts[i].uvi); 162 | // uint16_t pressureMB; // 1018 163 | print("pressureMB", forecasts[i].pressureMB); 164 | // float pressureIN; // 30.06 165 | print("pressureIN", forecasts[i].pressureIN); 166 | // uint8_t sky; // 99 167 | print("sky", forecasts[i].sky); 168 | // uint16_t snowCM; // 0 169 | print("snowCM", forecasts[i].snowCM); 170 | // uint16_t snowIN; // 0 171 | print("snowIN", forecasts[i].snowIN); 172 | // sint16_t feelslikeC; // 14 173 | print("feelslikeC", forecasts[i].feelslikeC); 174 | // sint16_t feelslikeF; // 56 175 | print("feelslikeF", forecasts[i].feelslikeF); 176 | // sint16_t minFeelslikeC; // 14 177 | print("minFeelslikeC", forecasts[i].minFeelslikeC); 178 | // sint16_t minFeelslikeF; // 56 179 | print("minFeelslikeF", forecasts[i].minFeelslikeF); 180 | // sint16_t maxFeelslikeC; // 20 181 | print("maxFeelslikeC", forecasts[i].maxFeelslikeC); 182 | // sint16_t maxFeelslikeF; // 69 183 | print("maxFeelslikeF", forecasts[i].maxFeelslikeF); 184 | // sint16_t avgFeelslikeC; // 17 185 | print("avgFeelslikeC", forecasts[i].avgFeelslikeC); 186 | // sint16_t avgFeelslikeF; // 63 187 | print("avgFeelslikeF", forecasts[i].avgFeelslikeF); 188 | // sint16_t dewpointC; // 11 189 | print("dewpointC", forecasts[i].dewpointC); 190 | // sint16_t dewpointF; // 51 191 | print("dewpointF", forecasts[i].dewpointF); 192 | // sint16_t maxDewpointC; // 13 193 | print("maxDewpointC", forecasts[i].maxDewpointC); 194 | // sint16_t maxDewpointF; // 55 195 | print("maxDewpointF", forecasts[i].maxDewpointF); 196 | // sint16_t minDewpointC; // 10 197 | print("minDewpointC", forecasts[i].minDewpointC); 198 | // sint16_t minDewpointF; // 51 199 | print("minDewpointF", forecasts[i].minDewpointF); 200 | // sint16_t avgDewpointC; // 11 201 | print("avgDewpointC", forecasts[i].avgDewpointC); 202 | // sint16_t avgDewpointF; // 52 203 | print("avgDewpointF", forecasts[i].avgDewpointF); 204 | // uint16_t windDirDEG; // 2 205 | print("windDirDEG", forecasts[i].windDirDEG); 206 | // String windDir; // "N" 207 | print("windDir", forecasts[i].windDir); 208 | // uint16_t windDirMaxDEG; // 40 209 | print("windDirMaxDEG", forecasts[i].windDirMaxDEG); 210 | // String windDirMax; // "NE" 211 | print("windDirMax", forecasts[i].windDirMax); 212 | // sint16_t windDirMinDEG; // 39 213 | print("windDirMinDEG", forecasts[i].windDirMinDEG); 214 | // String windDirMin; // "NE" 215 | print("windDirMin", forecasts[i].windDirMin); 216 | // uint16_t windGustKTS; // 6 217 | print("windGustKTS", forecasts[i].windGustKTS); 218 | // uint16_t windGustKPH; // 11 219 | print("windGustKPH", forecasts[i].windGustKPH); 220 | // uint16_t windGustMPH; // 7 221 | print("windGustMPH", forecasts[i].windGustMPH); 222 | // uint16_t windSpeedKTS; // 4 223 | print("windSpeedKTS", forecasts[i].windSpeedKTS); 224 | // uint16_t windSpeedKPH; // 7 225 | print("windSpeedKPH", forecasts[i].windSpeedKPH); 226 | // uint16_t windSpeedMPH; // 5 227 | print("windSpeedMPH", forecasts[i].windSpeedMPH); 228 | // uint16_t windSpeedMaxKTS; // 6 229 | print("windSpeedMaxKTS", forecasts[i].windSpeedMaxKTS); 230 | // uint16_t windSpeedMaxKPH; // 11 231 | print("windSpeedMaxKPH", forecasts[i].windSpeedMaxKPH); 232 | // uint16_t windSpeedMaxMPH; // 7 233 | print("windSpeedMaxMPH", forecasts[i].windSpeedMaxMPH); 234 | // uint16_t windSpeedMinKTS; // 1 235 | print("windSpeedMinKTS", forecasts[i].windSpeedMinKTS); 236 | // uint16_t windSpeedMinKPH; // 2 237 | print("validTime", forecasts[i].windSpeedMinKPH); 238 | // uint16_t windSpeedMinMPH; // 1 239 | print("windSpeedMinMPH", forecasts[i].windSpeedMinMPH); 240 | // uint16_t windDir80mDEG; // 5 241 | print("windDir80mDEG", forecasts[i].windDir80mDEG); 242 | // String windDir80m; // "N" 243 | print("windDir80m", forecasts[i].windDir80m); 244 | // uint16_t windDirMax80mDEG; // 40 245 | print("windDirMax80mDEG", forecasts[i].windDirMax80mDEG); 246 | // String windDirMax80m; // "NE" 247 | print("windDirMax80m", forecasts[i].windDirMax80m); 248 | // uint16_t windDirMin80mDEG; // 39 249 | print("windDirMin80mDEG", forecasts[i].windDirMin80mDEG); 250 | // String windDirMin80m; // "NE" 251 | print("windDirMin80m", forecasts[i].windDirMin80m); 252 | // uint16_t windGust80mKTS; // 9 253 | print("windGust80mKTS", forecasts[i].windGust80mKTS); 254 | // uint16_t windGust80mKPH; // 17 255 | print("windGust80mKPH", forecasts[i].windGust80mKPH); 256 | // uint16_t windGust80mMPH; // 11 257 | print("windGust80mMPH", forecasts[i].windGust80mMPH); 258 | // uint16_t windSpeed80mKTS; // 6 259 | print("windSpeed80mKTS", forecasts[i].windSpeed80mKTS); 260 | // uint16_t windSpeed80mKPH; // 11 261 | print("windSpeed80mKPH", forecasts[i].windSpeed80mKPH); 262 | // uint16_t windSpeed80mMPH; // 7 263 | print("windSpeed80mMPH", forecasts[i].windSpeed80mMPH); 264 | // uint16_t windSpeedMax80mKTS; // 9 265 | print("windSpeedMax80mKTS", forecasts[i].windSpeedMax80mKTS); 266 | // uint16_t windSpeedMax80mKPH; // 17 267 | print("windSpeedMax80mKPH", forecasts[i].windSpeedMax80mKPH); 268 | // uint16_t windSpeedMax80mMPH; // 11 269 | print("windSpeedMax80mMPH", forecasts[i].windSpeedMax80mMPH); 270 | // uint16_t windSpeedMin80mKTS; // 4 271 | print("windSpeedMin80mKTS", forecasts[i].windSpeedMin80mKTS); 272 | // uint16_t windSpeedMin80mKPH; // 7 273 | print("windSpeedMin80mKPH", forecasts[i].windSpeedMin80mKPH); 274 | // uint16_t windSpeedMin80mMPH; // 4 275 | print("windSpeedMin80mMPH", forecasts[i].windSpeedMin80mMPH); 276 | // String weather; // "Cloudy with Drizzle" 277 | print("weather", forecasts[i].weather); 278 | // String weatherPrimary; // "Drizzle" 279 | print("weatherPrimary", forecasts[i].weatherPrimary); 280 | // String weatherPrimaryCoded; // "IS:VL:RW" 281 | print("weatherPrimaryCoded", forecasts[i].weatherPrimaryCoded); 282 | // String cloudsCoded; // "OV" 283 | print("cloudsCoded", forecasts[i].cloudsCoded); 284 | // String icon; // "drizzle.png" 285 | print("icon", forecasts[i].icon); 286 | // String iconMeteoCon; // Q 287 | print("iconMeteoCon", forecasts[i].iconMeteoCon); 288 | // boolean isDay; // true 289 | print("isDay", forecasts[i].isDay ? "true" : "false"); 290 | // uint64_t sunrise; // 1526701425 291 | print("sunrise", forecasts[i].sunrise); 292 | // String sunriseISO; // "2018-05-19T05:43:45+02:00" 293 | print("sunriseISO", forecasts[i].sunriseISO); 294 | // uint64_t sunset; // 1526756450 295 | print("sunset", forecasts[i].sunset); 296 | // String sunsetISO; // "2018-05-19T21:00:50+02:00" 297 | print("sunsetISO", forecasts[i].sunsetISO); 298 | 299 | Serial.println(); 300 | Serial.println("---------------------------------------------------/\n"); 301 | } 302 | } 303 | 304 | 305 | /** 306 | * LOOP 307 | */ 308 | void loop() { 309 | 310 | } 311 | -------------------------------------------------------------------------------- /examples/PlaneSpotterDemo/PlaneSpotterDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by Daniel Eichhorn - ThingPulse 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 https://thingpulse.com 24 | */ 25 | #include 26 | #if defined(ESP8266) 27 | #include 28 | #include 29 | #else 30 | #include 31 | #include 32 | #include 33 | #endif 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | //#include "SSD1306Wire.h" 44 | #include "SH1106Wire.h" 45 | #include "OLEDDisplayUi.h" 46 | #include "Wire.h" 47 | #include "images.h" 48 | 49 | #include "TimeClient.h" 50 | #include "AdsbExchangeClient.h" 51 | 52 | /************** 53 | * Required Libraries: 54 | * - Weather Station by Daniel Eichhorn 55 | * - WifiManager by tzapu 56 | * - ESP8266 OLED Driver by Daniel Eichhorn, Fabrice Weinberg 57 | * - Json Streaming Parser by Daniel Eichhorn 58 | */ 59 | 60 | 61 | /*************************** 62 | * Begin Settings 63 | **************************/ 64 | // Please read http://blog.squix.org/weatherstation-getting-code-adapting-it 65 | // for setup instructions 66 | 67 | #define HOSTNAME "ESP8266-OTA-" 68 | 69 | // Setup 70 | const int UPDATE_INTERVAL_SECS_LONG = 15; // Update every 15 seconds if no airplanes around 71 | const int UPDATE_INTERVAL_SECS_SHORT = 3; // Update every 3 seconds if there are airplanes 72 | 73 | int currentUpdateInterval = UPDATE_INTERVAL_SECS_LONG; 74 | long lastUpdate = 0; 75 | 76 | // Check http://www.virtualradarserver.co.uk/Documentation/Formats/AircraftList.aspx 77 | // to craft this query to your needs 78 | const String QUERY_STRING = "lat=47.424341887&lng=8.568778038&fDstL=0&fDstU=10&fAltL=0&fAltL=1500&fAltU=10000"; 79 | 80 | const int UTC_OFFSET = 2; 81 | 82 | const float pi = 3.141; 83 | 84 | // Display Settings 85 | const int I2C_DISPLAY_ADDRESS = 0x3c; 86 | const int SDA_PIN = D3; 87 | const int SDC_PIN = D4; 88 | 89 | // Initialize the oled display for address 0x3c 90 | // sda-pin=14 and sdc-pin=12 91 | //SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN); 92 | SH1106Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN); 93 | OLEDDisplayUi ui( &display ); 94 | 95 | /*************************** 96 | * End Settings 97 | **************************/ 98 | 99 | TimeClient timeClient(UTC_OFFSET); 100 | 101 | AdsbExchangeClient adsbClient; 102 | 103 | // flag changed in the ticker function every 10 minutes 104 | bool readyForUpdate = false; 105 | 106 | Ticker ticker; 107 | 108 | //declaring prototypes 109 | void configModeCallback (WiFiManager *myWiFiManager); 110 | void drawProgress(OLEDDisplay *display, int percentage, String label); 111 | void drawOtaProgress(unsigned int, unsigned int); 112 | void updateData(OLEDDisplay *display); 113 | void drawCurrentAirplane1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); 114 | void drawCurrentAirplane2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); 115 | void drawCurrentAirplane3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); 116 | void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state); 117 | void drawTextAsBigAsPossible(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y, String text, int maxWidth); 118 | void drawHeading(OLEDDisplay *display, int x, int y, double heading); 119 | void checkReadyForUpdate(); 120 | int8_t getWifiQuality(); 121 | 122 | 123 | // Add frames 124 | // this array keeps function pointers to all frames 125 | // frames are the single views that slide from right to left 126 | FrameCallback frames[] = { drawCurrentAirplane1, drawCurrentAirplane2, drawCurrentAirplane3 }; 127 | int numberOfFrames = 3; 128 | 129 | OverlayCallback overlays[] = { drawHeaderOverlay }; 130 | int numberOfOverlays = 1; 131 | 132 | void setup() { 133 | Serial.begin(115200); 134 | 135 | // initialize dispaly 136 | display.init(); 137 | display.clear(); 138 | display.display(); 139 | 140 | //display.flipScreenVertically(); 141 | display.setFont(ArialMT_Plain_10); 142 | display.setTextAlignment(TEXT_ALIGN_CENTER); 143 | display.setContrast(255); 144 | 145 | //WiFiManager 146 | //Local intialization. Once its business is done, there is no need to keep it around 147 | WiFiManager wifiManager; 148 | // Uncomment for testing wifi manager 149 | //wifiManager.resetSettings(); 150 | wifiManager.setAPCallback(configModeCallback); 151 | 152 | //or use this for auto generated name ESP + ChipID 153 | wifiManager.autoConnect(); 154 | 155 | //Manual Wifi 156 | //WiFi.begin(WIFI_SSID, WIFI_PWD); 157 | String hostname(HOSTNAME); 158 | hostname += String(ESP.getChipId(), HEX); 159 | WiFi.hostname(hostname); 160 | 161 | 162 | int counter = 0; 163 | while (WiFi.status() != WL_CONNECTED) { 164 | delay(500); 165 | Serial.print("."); 166 | display.clear(); 167 | display.drawString(64, 10, "Connecting to WiFi"); 168 | display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbol : inactiveSymbol); 169 | display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbol : inactiveSymbol); 170 | display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbol : inactiveSymbol); 171 | display.display(); 172 | 173 | counter++; 174 | } 175 | 176 | ui.setTargetFPS(30); 177 | 178 | //Hack until disableIndicator works: 179 | //Set an empty symbol 180 | ui.setActiveSymbol(emptySymbol); 181 | ui.setInactiveSymbol(emptySymbol); 182 | ui.disableIndicator(); 183 | 184 | // You can change the transition that is used 185 | // SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN 186 | ui.setFrameAnimation(SLIDE_LEFT); 187 | 188 | ui.setFrames(frames, numberOfFrames); 189 | 190 | ui.setOverlays(overlays, numberOfOverlays); 191 | 192 | // Inital UI takes care of initalising the display too. 193 | ui.init(); 194 | 195 | // Setup OTA 196 | Serial.println("Hostname: " + hostname); 197 | ArduinoOTA.setHostname((const char *)hostname.c_str()); 198 | ArduinoOTA.onProgress(drawOtaProgress); 199 | ArduinoOTA.begin(); 200 | 201 | updateData(&display); 202 | 203 | //Check every second 204 | ticker.attach(1, checkReadyForUpdate); 205 | 206 | } 207 | 208 | void loop() { 209 | // If there are airplanes query often 210 | if (adsbClient.getNumberOfVisibleAircrafts() == 0) { 211 | currentUpdateInterval = UPDATE_INTERVAL_SECS_LONG; 212 | } else { 213 | currentUpdateInterval = UPDATE_INTERVAL_SECS_SHORT; 214 | } 215 | 216 | if (readyForUpdate && ui.getUiState()->frameState == FIXED) { 217 | updateData(&display); 218 | } 219 | 220 | int remainingTimeBudget = ui.update(); 221 | 222 | if (remainingTimeBudget > 0) { 223 | // You can do some work here 224 | // Don't do stuff if you are below your 225 | // time budget. 226 | ArduinoOTA.handle(); 227 | delay(remainingTimeBudget); 228 | } 229 | 230 | 231 | } 232 | 233 | void configModeCallback (WiFiManager *myWiFiManager) { 234 | Serial.println("Entered config mode"); 235 | Serial.println(WiFi.softAPIP()); 236 | //if you used auto generated SSID, print it 237 | Serial.println(myWiFiManager->getConfigPortalSSID()); 238 | display.clear(); 239 | display.setTextAlignment(TEXT_ALIGN_CENTER); 240 | display.setFont(ArialMT_Plain_10); 241 | display.drawString(64, 10, "Wifi Manager"); 242 | display.drawString(64, 20, "Please connect to AP"); 243 | display.drawString(64, 30, myWiFiManager->getConfigPortalSSID()); 244 | display.drawString(64, 40, "To setup Wifi Configuration"); 245 | display.display(); 246 | } 247 | 248 | void drawProgress(OLEDDisplay *display, int percentage, String label) { 249 | display->clear(); 250 | display->setTextAlignment(TEXT_ALIGN_CENTER); 251 | display->setFont(ArialMT_Plain_10); 252 | display->drawString(64, 10, label); 253 | display->drawProgressBar(2, 28, 124, 10, percentage); 254 | display->display(); 255 | } 256 | 257 | void drawOtaProgress(unsigned int progress, unsigned int total) { 258 | display.clear(); 259 | display.setTextAlignment(TEXT_ALIGN_CENTER); 260 | display.setFont(ArialMT_Plain_10); 261 | display.drawString(64, 10, "OTA Update"); 262 | display.drawProgressBar(2, 28, 124, 10, progress / (total / 100)); 263 | display.display(); 264 | } 265 | 266 | void updateData(OLEDDisplay *display) { 267 | readyForUpdate = false; 268 | adsbClient.updateVisibleAircraft(QUERY_STRING); 269 | lastUpdate = millis(); 270 | } 271 | 272 | void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) { 273 | 274 | if (adsbClient.isAircraftVisible()) { 275 | display->setTextAlignment(TEXT_ALIGN_LEFT); 276 | display->setFont(ArialMT_Plain_10); 277 | display->drawString(0, 10, "Dst:"); 278 | display->drawString(64, 10, "Alt:"); 279 | display->drawString(0, 32, "Head:"); 280 | display->setFont(ArialMT_Plain_16); 281 | display->drawString(0, 20, String(adsbClient.getDistance()) + "km"); 282 | display->drawString(64, 20, adsbClient.getAltitude() + "ft"); 283 | display->drawString(0, 42, String(adsbClient.getHeading()) + "°"); 284 | 285 | drawHeading(display, 78, 52, adsbClient.getHeading()); 286 | 287 | } 288 | 289 | int8_t quality = getWifiQuality(); 290 | for (int8_t i = 0; i < 4; i++) { 291 | for (int8_t j = 0; j < 2 * (i + 1); j++) { 292 | if (quality > i * 25 || j == 0) { 293 | display->setPixel(120 + 2 * i, 63 - j); 294 | } 295 | } 296 | } 297 | 298 | } 299 | 300 | // converts the dBm to a range between 0 and 100% 301 | int8_t getWifiQuality() { 302 | int32_t dbm = WiFi.RSSI(); 303 | if(dbm <= -100) { 304 | return 0; 305 | } else if(dbm >= -50) { 306 | return 100; 307 | } else { 308 | return 2 * (dbm + 100); 309 | } 310 | } 311 | 312 | void drawCurrentAirplane1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 313 | if (adsbClient.isAircraftVisible()) { 314 | display->setTextAlignment(TEXT_ALIGN_LEFT); 315 | display->setFont(ArialMT_Plain_10); 316 | 317 | display->drawString(0 + x, 0 + y, "From: " + adsbClient.getFrom()); 318 | } 319 | } 320 | 321 | void drawCurrentAirplane2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 322 | if (adsbClient.isAircraftVisible()) { 323 | display->setTextAlignment(TEXT_ALIGN_LEFT); 324 | display->setFont(ArialMT_Plain_10); 325 | 326 | display->drawString(0 + x, 0 + y, "To: " + adsbClient.getTo()); 327 | } 328 | } 329 | 330 | void drawCurrentAirplane3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 331 | if (adsbClient.isAircraftVisible()) { 332 | display->setTextAlignment(TEXT_ALIGN_LEFT); 333 | display->setFont(ArialMT_Plain_10); 334 | 335 | display->drawString(0 + x, 0 + y, "Type: " + adsbClient.getAircraftType()); 336 | } 337 | } 338 | 339 | void drawHeading(OLEDDisplay *display, int x, int y, double heading) { 340 | int degrees[] = {0, 170, 190, 0}; 341 | display->drawCircle(x, y, 10); 342 | int radius = 8; 343 | for (int i = 0; i < 3; i++) { 344 | int x1 = cos((-450 + (heading + degrees[i])) * pi / 180.0) * radius + x; 345 | int y1 = sin((-450 + (heading + degrees[i])) * pi / 180.0) * radius + y; 346 | int x2 = cos((-450 + (heading + degrees[i + 1])) * pi / 180.0) * radius + x; 347 | int y2 = sin((-450 + (heading + degrees[i + 1])) * pi / 180.0) * radius + y; 348 | display->drawLine(x1, y1, x2, y2); 349 | 350 | } 351 | } 352 | 353 | void checkReadyForUpdate() { 354 | // Only do light work in ticker callback 355 | if (lastUpdate < millis() - currentUpdateInterval * 1000) { 356 | readyForUpdate = true; 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /examples/WeatherStationDemo/WeatherStationDemo.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by Daniel Eichhorn - ThingPulse 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 https://thingpulse.com 24 | */ 25 | 26 | #include 27 | 28 | #if defined(ESP8266) 29 | #include 30 | #include // settimeofday_cb() 31 | #else 32 | #include 33 | #endif 34 | #include 35 | #include 36 | 37 | // time 38 | #include // time() ctime() 39 | #include // struct timeval 40 | 41 | #include "SSD1306Wire.h" 42 | #include "OLEDDisplayUi.h" 43 | #include "Wire.h" 44 | #include "OpenWeatherMapCurrent.h" 45 | #include "OpenWeatherMapForecast.h" 46 | #include "WeatherStationFonts.h" 47 | #include "WeatherStationImages.h" 48 | 49 | 50 | /*************************** 51 | * Begin Settings 52 | **************************/ 53 | 54 | // WIFI 55 | const char* WIFI_SSID = "yourssid"; 56 | const char* WIFI_PWD = "yourpassw0rd"; 57 | 58 | #define TZ 2 // (utc+) TZ in hours 59 | #define DST_MN 60 // use 60mn for summer time in some countries 60 | 61 | // Setup 62 | const int UPDATE_INTERVAL_SECS = 20 * 60; // Update every 20 minutes 63 | 64 | // Display Settings 65 | const int I2C_DISPLAY_ADDRESS = 0x3c; 66 | #if defined(ESP8266) 67 | const int SDA_PIN = D3; 68 | const int SDC_PIN = D4; 69 | #else 70 | const int SDA_PIN = 5; //D3; 71 | const int SDC_PIN = 4; //D4; 72 | #endif 73 | 74 | 75 | // OpenWeatherMap Settings 76 | // Sign up here to get an API key: 77 | // https://docs.thingpulse.com/how-tos/openweathermap-key/ 78 | String OPEN_WEATHER_MAP_APP_ID = "XXX"; 79 | /* 80 | Use the OWM GeoCoder API to find lat/lon for your city: https://openweathermap.org/api/geocoding-api 81 | Or use any other geocoding service. 82 | Or go to https://openweathermap.org, search for your city and monitor the calls in the browser dev console :) 83 | */ 84 | // Example: Zurich, Switzerland 85 | float OPEN_WEATHER_MAP_LOCATION_LAT = 47.3667; 86 | float OPEN_WEATHER_MAP_LOCATION_LON = 8.55; 87 | 88 | // Pick a language code from this list: 89 | // Arabic - ar, Bulgarian - bg, Catalan - ca, Czech - cz, German - de, Greek - el, 90 | // English - en, Persian (Farsi) - fa, Finnish - fi, French - fr, Galician - gl, 91 | // Croatian - hr, Hungarian - hu, Italian - it, Japanese - ja, Korean - kr, 92 | // Latvian - la, Lithuanian - lt, Macedonian - mk, Dutch - nl, Polish - pl, 93 | // Portuguese - pt, Romanian - ro, Russian - ru, Swedish - se, Slovak - sk, 94 | // Slovenian - sl, Spanish - es, Turkish - tr, Ukrainian - ua, Vietnamese - vi, 95 | // Chinese Simplified - zh_cn, Chinese Traditional - zh_tw. 96 | String OPEN_WEATHER_MAP_LANGUAGE = "de"; 97 | const uint8_t MAX_FORECASTS = 4; 98 | 99 | const boolean IS_METRIC = true; 100 | 101 | // Adjust according to your language 102 | const String WDAY_NAMES[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; 103 | const String MONTH_NAMES[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; 104 | 105 | /*************************** 106 | * End Settings 107 | **************************/ 108 | // Initialize the oled display for address 0x3c 109 | // sda-pin=14 and sdc-pin=12 110 | SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN); 111 | OLEDDisplayUi ui( &display ); 112 | 113 | OpenWeatherMapCurrentData currentWeather; 114 | OpenWeatherMapCurrent currentWeatherClient; 115 | 116 | OpenWeatherMapForecastData forecasts[MAX_FORECASTS]; 117 | OpenWeatherMapForecast forecastClient; 118 | 119 | #define TZ_MN ((TZ)*60) 120 | #define TZ_SEC ((TZ)*3600) 121 | #define DST_SEC ((DST_MN)*60) 122 | time_t now; 123 | 124 | // flag changed in the ticker function every 10 minutes 125 | bool readyForWeatherUpdate = false; 126 | 127 | String lastUpdate = "--"; 128 | 129 | long timeSinceLastWUpdate = 0; 130 | 131 | //declaring prototypes 132 | void drawProgress(OLEDDisplay *display, int percentage, String label); 133 | void updateData(OLEDDisplay *display); 134 | void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); 135 | void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); 136 | void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); 137 | void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex); 138 | void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state); 139 | void setReadyForWeatherUpdate(); 140 | 141 | 142 | // Add frames 143 | // this array keeps function pointers to all frames 144 | // frames are the single views that slide from right to left 145 | FrameCallback frames[] = { drawDateTime, drawCurrentWeather, drawForecast }; 146 | int numberOfFrames = 3; 147 | 148 | OverlayCallback overlays[] = { drawHeaderOverlay }; 149 | int numberOfOverlays = 1; 150 | 151 | void setup() { 152 | Serial.begin(115200); 153 | Serial.println(); 154 | Serial.println(); 155 | 156 | // initialize dispaly 157 | display.init(); 158 | display.clear(); 159 | display.display(); 160 | 161 | //display.flipScreenVertically(); 162 | display.setFont(ArialMT_Plain_10); 163 | display.setTextAlignment(TEXT_ALIGN_CENTER); 164 | display.setContrast(255); 165 | 166 | WiFi.begin(WIFI_SSID, WIFI_PWD); 167 | 168 | int counter = 0; 169 | while (WiFi.status() != WL_CONNECTED) { 170 | delay(500); 171 | Serial.print("."); 172 | display.clear(); 173 | display.drawString(64, 10, "Connecting to WiFi"); 174 | display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbole : inactiveSymbole); 175 | display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbole : inactiveSymbole); 176 | display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbole : inactiveSymbole); 177 | display.display(); 178 | 179 | counter++; 180 | } 181 | // Get time from network time service 182 | configTime(TZ_SEC, DST_SEC, "pool.ntp.org"); 183 | 184 | ui.setTargetFPS(30); 185 | 186 | ui.setActiveSymbol(activeSymbole); 187 | ui.setInactiveSymbol(inactiveSymbole); 188 | 189 | // You can change this to 190 | // TOP, LEFT, BOTTOM, RIGHT 191 | ui.setIndicatorPosition(BOTTOM); 192 | 193 | // Defines where the first frame is located in the bar. 194 | ui.setIndicatorDirection(LEFT_RIGHT); 195 | 196 | // You can change the transition that is used 197 | // SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN 198 | ui.setFrameAnimation(SLIDE_LEFT); 199 | 200 | ui.setFrames(frames, numberOfFrames); 201 | 202 | ui.setOverlays(overlays, numberOfOverlays); 203 | 204 | // Inital UI takes care of initalising the display too. 205 | ui.init(); 206 | 207 | Serial.println(""); 208 | 209 | updateData(&display); 210 | 211 | } 212 | 213 | void loop() { 214 | 215 | if (millis() - timeSinceLastWUpdate > (1000L*UPDATE_INTERVAL_SECS)) { 216 | setReadyForWeatherUpdate(); 217 | timeSinceLastWUpdate = millis(); 218 | } 219 | 220 | if (readyForWeatherUpdate && ui.getUiState()->frameState == FIXED) { 221 | updateData(&display); 222 | } 223 | 224 | int remainingTimeBudget = ui.update(); 225 | 226 | if (remainingTimeBudget > 0) { 227 | // You can do some work here 228 | // Don't do stuff if you are below your 229 | // time budget. 230 | delay(remainingTimeBudget); 231 | } 232 | 233 | 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 updateData(OLEDDisplay *display) { 246 | drawProgress(display, 10, "Updating time..."); 247 | drawProgress(display, 30, "Updating weather..."); 248 | currentWeatherClient.setMetric(IS_METRIC); 249 | currentWeatherClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE); 250 | currentWeatherClient.updateCurrent(¤tWeather, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_LAT, OPEN_WEATHER_MAP_LOCATION_LON); 251 | drawProgress(display, 50, "Updating forecasts..."); 252 | forecastClient.setMetric(IS_METRIC); 253 | forecastClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE); 254 | uint8_t allowedHours[] = {12}; 255 | forecastClient.setAllowedHours(allowedHours, sizeof(allowedHours)); 256 | forecastClient.updateForecasts(forecasts, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_LAT, OPEN_WEATHER_MAP_LOCATION_LON, MAX_FORECASTS); 257 | 258 | readyForWeatherUpdate = false; 259 | drawProgress(display, 100, "Done..."); 260 | delay(1000); 261 | } 262 | 263 | 264 | 265 | void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 266 | now = time(nullptr); 267 | struct tm* timeInfo; 268 | timeInfo = localtime(&now); 269 | char buff[16]; 270 | 271 | 272 | display->setTextAlignment(TEXT_ALIGN_CENTER); 273 | display->setFont(ArialMT_Plain_10); 274 | String date = WDAY_NAMES[timeInfo->tm_wday]; 275 | 276 | sprintf_P(buff, PSTR("%s, %02d/%02d/%04d"), WDAY_NAMES[timeInfo->tm_wday].c_str(), timeInfo->tm_mday, timeInfo->tm_mon+1, timeInfo->tm_year + 1900); 277 | display->drawString(64 + x, 5 + y, String(buff)); 278 | display->setFont(ArialMT_Plain_24); 279 | 280 | sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec); 281 | display->drawString(64 + x, 15 + y, String(buff)); 282 | display->setTextAlignment(TEXT_ALIGN_LEFT); 283 | } 284 | 285 | void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 286 | display->setFont(ArialMT_Plain_10); 287 | display->setTextAlignment(TEXT_ALIGN_CENTER); 288 | display->drawString(64 + x, 38 + y, currentWeather.description); 289 | 290 | display->setFont(ArialMT_Plain_24); 291 | display->setTextAlignment(TEXT_ALIGN_LEFT); 292 | String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F"); 293 | display->drawString(60 + x, 5 + y, temp); 294 | 295 | display->setFont(Meteocons_Plain_36); 296 | display->setTextAlignment(TEXT_ALIGN_CENTER); 297 | display->drawString(32 + x, 0 + y, currentWeather.iconMeteoCon); 298 | } 299 | 300 | 301 | void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { 302 | drawForecastDetails(display, x, y, 0); 303 | drawForecastDetails(display, x + 44, y, 1); 304 | drawForecastDetails(display, x + 88, y, 2); 305 | } 306 | 307 | void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) { 308 | time_t observationTimestamp = forecasts[dayIndex].observationTime; 309 | struct tm* timeInfo; 310 | timeInfo = localtime(&observationTimestamp); 311 | display->setTextAlignment(TEXT_ALIGN_CENTER); 312 | display->setFont(ArialMT_Plain_10); 313 | display->drawString(x + 20, y, WDAY_NAMES[timeInfo->tm_wday]); 314 | 315 | display->setFont(Meteocons_Plain_21); 316 | display->drawString(x + 20, y + 12, forecasts[dayIndex].iconMeteoCon); 317 | String temp = String(forecasts[dayIndex].temp, 0) + (IS_METRIC ? "°C" : "°F"); 318 | display->setFont(ArialMT_Plain_10); 319 | display->drawString(x + 20, y + 34, temp); 320 | display->setTextAlignment(TEXT_ALIGN_LEFT); 321 | } 322 | 323 | void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) { 324 | now = time(nullptr); 325 | struct tm* timeInfo; 326 | timeInfo = localtime(&now); 327 | char buff[14]; 328 | sprintf_P(buff, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min); 329 | 330 | display->setColor(WHITE); 331 | display->setFont(ArialMT_Plain_10); 332 | display->setTextAlignment(TEXT_ALIGN_LEFT); 333 | display->drawString(0, 54, String(buff)); 334 | display->setTextAlignment(TEXT_ALIGN_RIGHT); 335 | String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F"); 336 | display->drawString(128, 54, temp); 337 | display->drawHorizontalLine(0, 52, 128); 338 | } 339 | 340 | void setReadyForWeatherUpdate() { 341 | Serial.println("Setting readyForUpdate to true"); 342 | readyForWeatherUpdate = true; 343 | } 344 | --------------------------------------------------------------------------------