Compare commits
5
Commits
5e88eee23b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cca4688fd6 | ||
|
|
f143c16c25 | ||
|
|
4917f57d16 | ||
|
|
6f6ea0e0fd | ||
|
|
76606c00e3 |
@@ -3,3 +3,4 @@
|
|||||||
.vscode/c_cpp_properties.json
|
.vscode/c_cpp_properties.json
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
.vscode/ipch
|
.vscode/ipch
|
||||||
|
include/wifi_credentials.h
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef INTERNET_TIME_H
|
||||||
|
#define INTERNET_TIME_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
struct InternetTimeInfo
|
||||||
|
{
|
||||||
|
bool valid;
|
||||||
|
uint16_t year;
|
||||||
|
uint8_t month;
|
||||||
|
uint8_t day;
|
||||||
|
uint8_t hour;
|
||||||
|
uint8_t minute;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InternetTime
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InternetTime();
|
||||||
|
void update(unsigned long now, bool wifiConnected);
|
||||||
|
InternetTimeInfo info() const;
|
||||||
|
bool consumeDirty();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void fetch(unsigned long now);
|
||||||
|
void updateCurrentTime(unsigned long now);
|
||||||
|
InternetTimeInfo infoFromEpoch(unsigned long epoch) const;
|
||||||
|
|
||||||
|
unsigned long lastEpoch_;
|
||||||
|
unsigned long lastSyncMillis_;
|
||||||
|
unsigned long nextAttempt_;
|
||||||
|
InternetTimeInfo current_;
|
||||||
|
bool dirty_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef MENU_H
|
||||||
|
#define MENU_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <U8g2lib.h>
|
||||||
|
|
||||||
|
enum class MenuAction
|
||||||
|
{
|
||||||
|
ShowInternetDetails,
|
||||||
|
ShowHelloWorld,
|
||||||
|
BackToCarousel
|
||||||
|
};
|
||||||
|
|
||||||
|
class Menu
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Menu();
|
||||||
|
void draw(U8G2 &u8g2);
|
||||||
|
void nextOption();
|
||||||
|
MenuAction select();
|
||||||
|
bool consumeDirty();
|
||||||
|
void requestRedraw();
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t selectedIndex_;
|
||||||
|
bool dirty_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
+9
-2
@@ -4,10 +4,17 @@
|
|||||||
#include <U8g2lib.h>
|
#include <U8g2lib.h>
|
||||||
|
|
||||||
#include "am2302_sensor.h"
|
#include "am2302_sensor.h"
|
||||||
|
#include "internet_time.h"
|
||||||
|
#include "weather_forecast.h"
|
||||||
|
#include "wifi_connection.h"
|
||||||
|
|
||||||
void drawScreen1(U8G2 &u8g2);
|
void setInternetTimeInfo(InternetTimeInfo timeInfo);
|
||||||
|
void drawTimeScreen(U8G2 &u8g2);
|
||||||
|
void drawHelloWorldScreen(U8G2 &u8g2);
|
||||||
|
void drawInternetConnectionScreen(U8G2 &u8g2, WifiConnectionInfo wifiInfo);
|
||||||
void setEnvironmentReading(Am2302Reading reading);
|
void setEnvironmentReading(Am2302Reading reading);
|
||||||
void drawEnvironmentScreen(U8G2 &u8g2);
|
void drawEnvironmentScreen(U8G2 &u8g2);
|
||||||
void drawWeatherIconScreen(U8G2 &u8g2);
|
void setWeatherForecastInfo(WeatherForecastInfo forecastInfo);
|
||||||
|
void drawWeatherForecastScreen(U8G2 &u8g2);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#ifndef WEATHER_FORECAST_H
|
||||||
|
#define WEATHER_FORECAST_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
struct WeatherForecastInfo
|
||||||
|
{
|
||||||
|
bool valid;
|
||||||
|
float currentTemperatureC;
|
||||||
|
float minTemperatureC;
|
||||||
|
float maxTemperatureC;
|
||||||
|
float precipitationMm;
|
||||||
|
uint8_t precipitationProbabilityPercent;
|
||||||
|
int weatherCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WeatherForecast
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WeatherForecast();
|
||||||
|
void update(unsigned long now, bool wifiConnected);
|
||||||
|
WeatherForecastInfo info() const;
|
||||||
|
bool consumeDirty();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void fetch(unsigned long now);
|
||||||
|
bool readResponse(String &body);
|
||||||
|
bool parseForecast(const String &body, WeatherForecastInfo &forecast) const;
|
||||||
|
bool parseFloatArray(const String &body, const char *name, float *values, size_t maxCount, size_t &count) const;
|
||||||
|
bool parseIntArray(const String &body, const char *name, int *values, size_t maxCount, size_t &count) const;
|
||||||
|
|
||||||
|
WeatherForecastInfo current_;
|
||||||
|
unsigned long nextAttempt_;
|
||||||
|
bool dirty_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef WIFI_CONNECTION_H
|
||||||
|
#define WIFI_CONNECTION_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <IPAddress.h>
|
||||||
|
|
||||||
|
struct WifiConnectionInfo
|
||||||
|
{
|
||||||
|
bool shieldAvailable;
|
||||||
|
bool connected;
|
||||||
|
IPAddress ipAddress;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WifiConnection
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WifiConnection();
|
||||||
|
void begin(unsigned long now);
|
||||||
|
void update(unsigned long now);
|
||||||
|
WifiConnectionInfo info() const;
|
||||||
|
bool consumeDirty();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void connect(unsigned long now);
|
||||||
|
void recordStatus();
|
||||||
|
|
||||||
|
unsigned long nextAttempt_;
|
||||||
|
bool shieldAvailable_;
|
||||||
|
bool connected_;
|
||||||
|
IPAddress ipAddress_;
|
||||||
|
bool dirty_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef WIFI_CREDENTIALS_H
|
||||||
|
#define WIFI_CREDENTIALS_H
|
||||||
|
|
||||||
|
const char WIFI_SSID[] = "your-wifi-name";
|
||||||
|
const char WIFI_PASSWORD[] = "your-wifi-password";
|
||||||
|
|
||||||
|
// Offset from UTC in seconds. Examples: Finland winter 7200, Finland summer 10800.
|
||||||
|
#define TIME_UTC_OFFSET_SECONDS 0
|
||||||
|
|
||||||
|
// Coordinates used for the weather forecast.
|
||||||
|
#define WEATHER_LATITUDE 60.1699
|
||||||
|
#define WEATHER_LONGITUDE 24.9384
|
||||||
|
|
||||||
|
#endif
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
#include "carousel.h"
|
#include "carousel.h"
|
||||||
|
|
||||||
const unsigned long pageInterval = 5000;
|
const unsigned long pageInterval = 30000;
|
||||||
|
|
||||||
Carousel::Carousel(Screen *screens, size_t screenCount)
|
Carousel::Carousel(Screen *screens, size_t screenCount)
|
||||||
: screens_(screens),
|
: screens_(screens),
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
#include "internet_time.h"
|
||||||
|
|
||||||
|
#include <WiFi101.h>
|
||||||
|
|
||||||
|
#include "wifi_credentials.h"
|
||||||
|
|
||||||
|
#ifndef TIME_UTC_OFFSET_SECONDS
|
||||||
|
#define TIME_UTC_OFFSET_SECONDS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const unsigned long retryInterval = 15000;
|
||||||
|
const unsigned long refreshInterval = 3600000;
|
||||||
|
const unsigned long secondsPerDay = 86400;
|
||||||
|
|
||||||
|
bool isLeapYear(uint16_t year)
|
||||||
|
{
|
||||||
|
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t daysInMonth(uint16_t year, uint8_t month)
|
||||||
|
{
|
||||||
|
const uint8_t days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||||
|
if (month == 2 && isLeapYear(year)) {
|
||||||
|
return 29;
|
||||||
|
}
|
||||||
|
return days[month - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long applyTimeOffset(unsigned long epoch)
|
||||||
|
{
|
||||||
|
if (TIME_UTC_OFFSET_SECONDS >= 0) {
|
||||||
|
return epoch + TIME_UTC_OFFSET_SECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned long offset = static_cast<unsigned long>(-TIME_UTC_OFFSET_SECONDS);
|
||||||
|
return epoch > offset ? epoch - offset : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetTime::InternetTime()
|
||||||
|
: lastEpoch_(0),
|
||||||
|
lastSyncMillis_(0),
|
||||||
|
nextAttempt_(0),
|
||||||
|
current_({false, 0, 0, 0, 0, 0}),
|
||||||
|
dirty_(true) {}
|
||||||
|
|
||||||
|
void InternetTime::update(unsigned long now, bool wifiConnected)
|
||||||
|
{
|
||||||
|
if (current_.valid) {
|
||||||
|
updateCurrentTime(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wifiConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now >= nextAttempt_) {
|
||||||
|
fetch(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetTimeInfo InternetTime::info() const
|
||||||
|
{
|
||||||
|
return current_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InternetTime::consumeDirty()
|
||||||
|
{
|
||||||
|
const bool d = dirty_;
|
||||||
|
dirty_ = false;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternetTime::fetch(unsigned long now)
|
||||||
|
{
|
||||||
|
const unsigned long epoch = WiFi.getTime();
|
||||||
|
nextAttempt_ = now + retryInterval;
|
||||||
|
|
||||||
|
if (epoch == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastEpoch_ = epoch;
|
||||||
|
lastSyncMillis_ = now;
|
||||||
|
nextAttempt_ = now + refreshInterval;
|
||||||
|
updateCurrentTime(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternetTime::updateCurrentTime(unsigned long now)
|
||||||
|
{
|
||||||
|
const unsigned long elapsedSeconds = (now - lastSyncMillis_) / 1000;
|
||||||
|
const InternetTimeInfo updated = infoFromEpoch(lastEpoch_ + elapsedSeconds);
|
||||||
|
|
||||||
|
if (!current_.valid ||
|
||||||
|
current_.year != updated.year ||
|
||||||
|
current_.month != updated.month ||
|
||||||
|
current_.day != updated.day ||
|
||||||
|
current_.hour != updated.hour ||
|
||||||
|
current_.minute != updated.minute) {
|
||||||
|
current_ = updated;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetTimeInfo InternetTime::infoFromEpoch(unsigned long epoch) const
|
||||||
|
{
|
||||||
|
unsigned long adjustedEpoch = applyTimeOffset(epoch);
|
||||||
|
unsigned long days = adjustedEpoch / secondsPerDay;
|
||||||
|
unsigned long secondsToday = adjustedEpoch % secondsPerDay;
|
||||||
|
|
||||||
|
uint16_t year = 1970;
|
||||||
|
while (true) {
|
||||||
|
const uint16_t yearDays = isLeapYear(year) ? 366 : 365;
|
||||||
|
if (days < yearDays) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
days -= yearDays;
|
||||||
|
year++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t month = 1;
|
||||||
|
while (true) {
|
||||||
|
const uint8_t monthDays = daysInMonth(year, month);
|
||||||
|
if (days < monthDays) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
days -= monthDays;
|
||||||
|
month++;
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetTimeInfo info = {
|
||||||
|
true,
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
static_cast<uint8_t>(days + 1),
|
||||||
|
static_cast<uint8_t>(secondsToday / 3600),
|
||||||
|
static_cast<uint8_t>((secondsToday % 3600) / 60)
|
||||||
|
};
|
||||||
|
return info;
|
||||||
|
}
|
||||||
+154
-11
@@ -4,48 +4,191 @@
|
|||||||
|
|
||||||
#include "am2302_sensor.h"
|
#include "am2302_sensor.h"
|
||||||
#include "carousel.h"
|
#include "carousel.h"
|
||||||
|
#include "internet_time.h"
|
||||||
|
#include "menu.h"
|
||||||
#include "screens.h"
|
#include "screens.h"
|
||||||
|
#include "weather_forecast.h"
|
||||||
|
#include "wifi_connection.h"
|
||||||
|
|
||||||
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, U8X8_PIN_NONE);
|
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, U8X8_PIN_NONE);
|
||||||
|
|
||||||
int lastButtonNextState = LOW;
|
int lastButtonNextState = LOW;
|
||||||
uint8_t buttonNext = A4;
|
int lastButtonSelectState = LOW;
|
||||||
Am2302Sensor am2302(A3);
|
uint8_t buttonNext = A3;
|
||||||
|
uint8_t buttonSelect = A4;
|
||||||
|
Am2302Sensor am2302(A5);
|
||||||
|
WifiConnection wifiConnection;
|
||||||
|
InternetTime internetTime;
|
||||||
|
WeatherForecast weatherForecast;
|
||||||
|
|
||||||
|
enum class AppMode
|
||||||
|
{
|
||||||
|
Carousel,
|
||||||
|
Menu,
|
||||||
|
InternetDetails,
|
||||||
|
HelloWorld
|
||||||
|
};
|
||||||
|
|
||||||
Screen screens[] = {
|
Screen screens[] = {
|
||||||
{ drawScreen1 },
|
{ drawTimeScreen },
|
||||||
{ drawEnvironmentScreen },
|
{ drawEnvironmentScreen },
|
||||||
{ drawWeatherIconScreen }
|
{ drawWeatherForecastScreen }
|
||||||
};
|
};
|
||||||
Carousel carousel(screens, sizeof(screens) / sizeof(screens[0]));
|
Carousel carousel(screens, sizeof(screens) / sizeof(screens[0]));
|
||||||
|
Menu menu;
|
||||||
|
AppMode appMode = AppMode::Carousel;
|
||||||
|
bool appDirty = true;
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
pinMode(buttonNext, INPUT);
|
pinMode(buttonNext, INPUT);
|
||||||
|
pinMode(buttonSelect, INPUT);
|
||||||
am2302.begin();
|
am2302.begin();
|
||||||
|
wifiConnection.begin(millis());
|
||||||
|
|
||||||
u8g2.begin();
|
u8g2.begin();
|
||||||
u8g2.clearBuffer();
|
u8g2.clearBuffer();
|
||||||
u8g2.sendBuffer();
|
u8g2.sendBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void openMenu()
|
||||||
|
{
|
||||||
|
appMode = AppMode::Menu;
|
||||||
|
menu.requestRedraw();
|
||||||
|
appDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectMenuOption()
|
||||||
|
{
|
||||||
|
switch (menu.select()) {
|
||||||
|
case MenuAction::ShowInternetDetails:
|
||||||
|
appMode = AppMode::InternetDetails;
|
||||||
|
break;
|
||||||
|
case MenuAction::ShowHelloWorld:
|
||||||
|
appMode = AppMode::HelloWorld;
|
||||||
|
break;
|
||||||
|
case MenuAction::BackToCarousel:
|
||||||
|
appMode = AppMode::Carousel;
|
||||||
|
carousel.requestRedraw();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
appDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleNextButton()
|
||||||
|
{
|
||||||
|
switch (appMode) {
|
||||||
|
case AppMode::Carousel:
|
||||||
|
carousel.nextScreen();
|
||||||
|
break;
|
||||||
|
case AppMode::Menu:
|
||||||
|
menu.nextOption();
|
||||||
|
break;
|
||||||
|
case AppMode::InternetDetails:
|
||||||
|
case AppMode::HelloWorld:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleSelectButton()
|
||||||
|
{
|
||||||
|
switch (appMode) {
|
||||||
|
case AppMode::Carousel:
|
||||||
|
case AppMode::InternetDetails:
|
||||||
|
case AppMode::HelloWorld:
|
||||||
|
openMenu();
|
||||||
|
break;
|
||||||
|
case AppMode::Menu:
|
||||||
|
selectMenuOption();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool displayNeedsRedraw()
|
||||||
|
{
|
||||||
|
if (appDirty) {
|
||||||
|
appDirty = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (appMode) {
|
||||||
|
case AppMode::Carousel:
|
||||||
|
return carousel.consumeDirty();
|
||||||
|
case AppMode::Menu:
|
||||||
|
return menu.consumeDirty();
|
||||||
|
case AppMode::InternetDetails:
|
||||||
|
return wifiConnection.consumeDirty();
|
||||||
|
case AppMode::HelloWorld:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawActiveView()
|
||||||
|
{
|
||||||
|
switch (appMode) {
|
||||||
|
case AppMode::Carousel:
|
||||||
|
carousel.draw(u8g2);
|
||||||
|
break;
|
||||||
|
case AppMode::Menu:
|
||||||
|
menu.draw(u8g2);
|
||||||
|
break;
|
||||||
|
case AppMode::InternetDetails:
|
||||||
|
drawInternetConnectionScreen(u8g2, wifiConnection.info());
|
||||||
|
break;
|
||||||
|
case AppMode::HelloWorld:
|
||||||
|
drawHelloWorldScreen(u8g2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void loop(){
|
void loop(){
|
||||||
const unsigned long now = millis();
|
const unsigned long now = millis();
|
||||||
|
wifiConnection.update(now);
|
||||||
|
internetTime.update(now, wifiConnection.info().connected);
|
||||||
|
weatherForecast.update(now, wifiConnection.info().connected);
|
||||||
|
|
||||||
|
if (internetTime.consumeDirty()) {
|
||||||
|
setInternetTimeInfo(internetTime.info());
|
||||||
|
if (appMode == AppMode::Carousel) {
|
||||||
|
carousel.requestRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weatherForecast.consumeDirty()) {
|
||||||
|
setWeatherForecastInfo(weatherForecast.info());
|
||||||
|
if (appMode == AppMode::Carousel) {
|
||||||
|
carousel.requestRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appMode == AppMode::Carousel) {
|
||||||
carousel.update(now);
|
carousel.update(now);
|
||||||
|
}
|
||||||
|
|
||||||
if (am2302.update(now)) {
|
if (am2302.update(now)) {
|
||||||
setEnvironmentReading(am2302.reading());
|
setEnvironmentReading(am2302.reading());
|
||||||
|
if (appMode == AppMode::Carousel) {
|
||||||
carousel.requestRedraw();
|
carousel.requestRedraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
const int currentButtonState = digitalRead(buttonNext);
|
|
||||||
if (lastButtonNextState == LOW && currentButtonState == HIGH) {
|
|
||||||
carousel.nextScreen();
|
|
||||||
}
|
}
|
||||||
lastButtonNextState = currentButtonState;
|
|
||||||
|
|
||||||
if (carousel.consumeDirty()) {
|
const int currentButtonNextState = digitalRead(buttonNext);
|
||||||
|
if (lastButtonNextState == LOW && currentButtonNextState == HIGH) {
|
||||||
|
handleNextButton();
|
||||||
|
}
|
||||||
|
lastButtonNextState = currentButtonNextState;
|
||||||
|
|
||||||
|
const int currentButtonSelectState = digitalRead(buttonSelect);
|
||||||
|
if (lastButtonSelectState == LOW && currentButtonSelectState == HIGH) {
|
||||||
|
handleSelectButton();
|
||||||
|
}
|
||||||
|
lastButtonSelectState = currentButtonSelectState;
|
||||||
|
|
||||||
|
if (displayNeedsRedraw()) {
|
||||||
u8g2.clearBuffer();
|
u8g2.clearBuffer();
|
||||||
carousel.draw(u8g2);
|
drawActiveView();
|
||||||
u8g2.sendBuffer();
|
u8g2.sendBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#include "menu.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const char *menuOptions[] = {
|
||||||
|
"Internet",
|
||||||
|
"Hello world",
|
||||||
|
"Back"
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t menuOptionCount = sizeof(menuOptions) / sizeof(menuOptions[0]);
|
||||||
|
|
||||||
|
MenuAction actionForIndex(size_t index)
|
||||||
|
{
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
return MenuAction::ShowInternetDetails;
|
||||||
|
case 1:
|
||||||
|
return MenuAction::ShowHelloWorld;
|
||||||
|
default:
|
||||||
|
return MenuAction::BackToCarousel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu::Menu()
|
||||||
|
: selectedIndex_(0),
|
||||||
|
dirty_(true) {}
|
||||||
|
|
||||||
|
void Menu::draw(U8G2 &u8g2)
|
||||||
|
{
|
||||||
|
u8g2.setFont(u8g2_font_6x12_tr);
|
||||||
|
u8g2.drawStr(0, 12, "Menu");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < menuOptionCount; i++) {
|
||||||
|
const uint8_t y = 28 + (i * 14);
|
||||||
|
if (i == selectedIndex_) {
|
||||||
|
u8g2.drawStr(0, y, ">");
|
||||||
|
}
|
||||||
|
u8g2.drawStr(12, y, menuOptions[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
dirty_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::nextOption()
|
||||||
|
{
|
||||||
|
selectedIndex_ = (selectedIndex_ + 1) % menuOptionCount;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuAction Menu::select()
|
||||||
|
{
|
||||||
|
return actionForIndex(selectedIndex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Menu::consumeDirty()
|
||||||
|
{
|
||||||
|
const bool d = dirty_;
|
||||||
|
dirty_ = false;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::requestRedraw()
|
||||||
|
{
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
+138
-21
@@ -3,34 +3,120 @@
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
Am2302Reading environmentReading = {0.0F, 0.0F, false};
|
Am2302Reading environmentReading = {0.0F, 0.0F, false};
|
||||||
|
InternetTimeInfo internetTimeInfo = {false, 0, 0, 0, 0, 0};
|
||||||
|
WeatherForecastInfo weatherForecastInfo = {false, 0.0F, 0.0F, 0.0F, 0.0F, 0, 0};
|
||||||
|
|
||||||
void drawSun(U8G2 &u8g2, uint8_t x, uint8_t y)
|
void printTwoDigits(U8G2 &u8g2, uint8_t value)
|
||||||
{
|
{
|
||||||
u8g2.drawDisc(x, y, 11);
|
if (value < 10) {
|
||||||
u8g2.drawLine(x, y - 18, x, y - 14);
|
u8g2.print("0");
|
||||||
u8g2.drawLine(x, y + 14, x, y + 18);
|
}
|
||||||
u8g2.drawLine(x - 18, y, x - 14, y);
|
u8g2.print(value);
|
||||||
u8g2.drawLine(x + 14, y, x + 18, y);
|
|
||||||
u8g2.drawLine(x - 13, y - 13, x - 10, y - 10);
|
|
||||||
u8g2.drawLine(x + 10, y - 10, x + 13, y - 13);
|
|
||||||
u8g2.drawLine(x - 13, y + 13, x - 10, y + 10);
|
|
||||||
u8g2.drawLine(x + 10, y + 10, x + 13, y + 13);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawCloud(U8G2 &u8g2, uint8_t x, uint8_t y)
|
const char *weatherDescription(int code)
|
||||||
{
|
{
|
||||||
u8g2.drawDisc(x + 18, y + 14, 12);
|
switch (code) {
|
||||||
u8g2.drawDisc(x + 34, y + 8, 17);
|
case 0:
|
||||||
u8g2.drawDisc(x + 52, y + 15, 13);
|
return "Clear";
|
||||||
u8g2.drawBox(x + 17, y + 15, 38, 14);
|
case 1:
|
||||||
u8g2.drawRBox(x + 8, y + 18, 58, 15, 7);
|
case 2:
|
||||||
|
return "Partly cloudy";
|
||||||
|
case 3:
|
||||||
|
return "Cloudy";
|
||||||
|
case 45:
|
||||||
|
case 48:
|
||||||
|
return "Fog";
|
||||||
|
case 51:
|
||||||
|
case 53:
|
||||||
|
case 55:
|
||||||
|
case 56:
|
||||||
|
case 57:
|
||||||
|
return "Drizzle";
|
||||||
|
case 61:
|
||||||
|
case 63:
|
||||||
|
case 65:
|
||||||
|
case 66:
|
||||||
|
case 67:
|
||||||
|
return "Rain";
|
||||||
|
case 71:
|
||||||
|
case 73:
|
||||||
|
case 75:
|
||||||
|
case 77:
|
||||||
|
return "Snow";
|
||||||
|
case 80:
|
||||||
|
case 81:
|
||||||
|
case 82:
|
||||||
|
return "Showers";
|
||||||
|
case 85:
|
||||||
|
case 86:
|
||||||
|
return "Snow showers";
|
||||||
|
case 95:
|
||||||
|
case 96:
|
||||||
|
case 99:
|
||||||
|
return "Thunder";
|
||||||
|
default:
|
||||||
|
return "Forecast";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawScreen1(U8G2 &u8g2)
|
void setInternetTimeInfo(InternetTimeInfo timeInfo)
|
||||||
|
{
|
||||||
|
internetTimeInfo = timeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawTimeScreen(U8G2 &u8g2)
|
||||||
|
{
|
||||||
|
if (!internetTimeInfo.valid) {
|
||||||
|
u8g2.setFont(u8g2_font_6x12_tr);
|
||||||
|
u8g2.drawStr(0, 24, "Time has not");
|
||||||
|
u8g2.drawStr(0, 40, "been updated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.setFont(u8g2_font_6x12_tr);
|
||||||
|
u8g2.setCursor(0, 14);
|
||||||
|
printTwoDigits(u8g2, internetTimeInfo.day);
|
||||||
|
u8g2.print(".");
|
||||||
|
printTwoDigits(u8g2, internetTimeInfo.month);
|
||||||
|
u8g2.print(".");
|
||||||
|
u8g2.print(internetTimeInfo.year);
|
||||||
|
|
||||||
|
u8g2.setFont(u8g2_font_ncenB24_tr);
|
||||||
|
u8g2.setCursor(0, 54);
|
||||||
|
printTwoDigits(u8g2, internetTimeInfo.hour);
|
||||||
|
u8g2.print(":");
|
||||||
|
printTwoDigits(u8g2, internetTimeInfo.minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawHelloWorldScreen(U8G2 &u8g2)
|
||||||
{
|
{
|
||||||
u8g2.setFont(u8g2_font_ncenB14_tr);
|
u8g2.setFont(u8g2_font_ncenB14_tr);
|
||||||
u8g2.drawStr(0, 20, "Screen 1");
|
u8g2.drawStr(0, 30, "Hello");
|
||||||
|
u8g2.drawStr(0, 54, "world");
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawInternetConnectionScreen(U8G2 &u8g2, WifiConnectionInfo wifiInfo)
|
||||||
|
{
|
||||||
|
u8g2.setFont(u8g2_font_6x12_tr);
|
||||||
|
u8g2.drawStr(0, 12, "Internet");
|
||||||
|
|
||||||
|
if (!wifiInfo.shieldAvailable) {
|
||||||
|
u8g2.drawStr(0, 34, "WiFi shield missing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wifiInfo.connected) {
|
||||||
|
u8g2.setFont(u8g2_font_ncenB14_tr);
|
||||||
|
u8g2.drawStr(0, 42, "Disconnected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.drawStr(0, 32, "Connected");
|
||||||
|
u8g2.drawStr(0, 48, "IP address:");
|
||||||
|
u8g2.setCursor(0, 64);
|
||||||
|
u8g2.print(wifiInfo.ipAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setEnvironmentReading(Am2302Reading reading)
|
void setEnvironmentReading(Am2302Reading reading)
|
||||||
@@ -58,8 +144,39 @@ void drawEnvironmentScreen(U8G2 &u8g2)
|
|||||||
u8g2.print(" %");
|
u8g2.print(" %");
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawWeatherIconScreen(U8G2 &u8g2)
|
void setWeatherForecastInfo(WeatherForecastInfo forecastInfo)
|
||||||
{
|
{
|
||||||
drawSun(u8g2, 78, 22);
|
weatherForecastInfo = forecastInfo;
|
||||||
drawCloud(u8g2, 29, 24);
|
}
|
||||||
|
|
||||||
|
void drawWeatherForecastScreen(U8G2 &u8g2)
|
||||||
|
{
|
||||||
|
u8g2.setFont(u8g2_font_6x12_tr);
|
||||||
|
u8g2.drawStr(0, 12, "Weather next 12h");
|
||||||
|
|
||||||
|
if (!weatherForecastInfo.valid) {
|
||||||
|
u8g2.drawStr(0, 34, "Weather has not");
|
||||||
|
u8g2.drawStr(0, 50, "been updated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.setCursor(0, 28);
|
||||||
|
u8g2.print("Now ");
|
||||||
|
u8g2.print(weatherForecastInfo.currentTemperatureC, 1);
|
||||||
|
u8g2.print(" C ");
|
||||||
|
u8g2.print(weatherDescription(weatherForecastInfo.weatherCode));
|
||||||
|
|
||||||
|
u8g2.setCursor(0, 42);
|
||||||
|
u8g2.print("Temp ");
|
||||||
|
u8g2.print(weatherForecastInfo.minTemperatureC, 0);
|
||||||
|
u8g2.print("-");
|
||||||
|
u8g2.print(weatherForecastInfo.maxTemperatureC, 0);
|
||||||
|
u8g2.print(" C");
|
||||||
|
|
||||||
|
u8g2.setCursor(0, 56);
|
||||||
|
u8g2.print("Rain ");
|
||||||
|
u8g2.print(weatherForecastInfo.precipitationProbabilityPercent);
|
||||||
|
u8g2.print("% ");
|
||||||
|
u8g2.print(weatherForecastInfo.precipitationMm, 1);
|
||||||
|
u8g2.print(" mm");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,247 @@
|
|||||||
|
#include "weather_forecast.h"
|
||||||
|
|
||||||
|
#include <WiFi101.h>
|
||||||
|
|
||||||
|
#include "wifi_credentials.h"
|
||||||
|
|
||||||
|
#ifndef WEATHER_LATITUDE
|
||||||
|
#define WEATHER_LATITUDE 0.0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WEATHER_LONGITUDE
|
||||||
|
#define WEATHER_LONGITUDE 0.0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const char weatherHost[] = "api.open-meteo.com";
|
||||||
|
const unsigned long retryInterval = 300000;
|
||||||
|
const unsigned long refreshInterval = 3600000;
|
||||||
|
const size_t forecastHours = 12;
|
||||||
|
const size_t maxResponseLength = 3500;
|
||||||
|
|
||||||
|
const char *weatherRequestPath()
|
||||||
|
{
|
||||||
|
static String path;
|
||||||
|
path = "/v1/forecast?latitude=";
|
||||||
|
path += String(WEATHER_LATITUDE, 4);
|
||||||
|
path += "&longitude=";
|
||||||
|
path += String(WEATHER_LONGITUDE, 4);
|
||||||
|
path += "&hourly=temperature_2m,precipitation_probability,precipitation,weather_code";
|
||||||
|
path += "&forecast_hours=12&timezone=auto&forecast_days=1";
|
||||||
|
return path.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *arrayStartForName(const String &body, const char *name)
|
||||||
|
{
|
||||||
|
const String key = String("\"") + name + "\":[";
|
||||||
|
const int start = body.indexOf(key);
|
||||||
|
if (start < 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return body.c_str() + start + key.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void skipWhitespace(const char *&cursor)
|
||||||
|
{
|
||||||
|
while (*cursor == ' ' || *cursor == '\n' || *cursor == '\r' || *cursor == '\t') {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WeatherForecast::WeatherForecast()
|
||||||
|
: current_({false, 0.0F, 0.0F, 0.0F, 0.0F, 0, 0}),
|
||||||
|
nextAttempt_(0),
|
||||||
|
dirty_(true) {}
|
||||||
|
|
||||||
|
void WeatherForecast::update(unsigned long now, bool wifiConnected)
|
||||||
|
{
|
||||||
|
if (!wifiConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now >= nextAttempt_) {
|
||||||
|
fetch(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WeatherForecastInfo WeatherForecast::info() const
|
||||||
|
{
|
||||||
|
return current_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WeatherForecast::consumeDirty()
|
||||||
|
{
|
||||||
|
const bool d = dirty_;
|
||||||
|
dirty_ = false;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WeatherForecast::fetch(unsigned long now)
|
||||||
|
{
|
||||||
|
nextAttempt_ = now + retryInterval;
|
||||||
|
|
||||||
|
String body;
|
||||||
|
if (!readResponse(body)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WeatherForecastInfo forecast;
|
||||||
|
if (!parseForecast(body, forecast)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_ = forecast;
|
||||||
|
dirty_ = true;
|
||||||
|
nextAttempt_ = now + refreshInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WeatherForecast::readResponse(String &body)
|
||||||
|
{
|
||||||
|
WiFiClient client;
|
||||||
|
if (!client.connect(weatherHost, 80)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.print("GET ");
|
||||||
|
client.print(weatherRequestPath());
|
||||||
|
client.println(" HTTP/1.0");
|
||||||
|
client.print("Host: ");
|
||||||
|
client.println(weatherHost);
|
||||||
|
client.println("Connection: close");
|
||||||
|
client.println();
|
||||||
|
|
||||||
|
const unsigned long started = millis();
|
||||||
|
bool inBody = false;
|
||||||
|
char headerTail[4] = {0, 0, 0, 0};
|
||||||
|
while ((client.connected() || client.available()) && millis() - started < 10000) {
|
||||||
|
while (client.available()) {
|
||||||
|
const char c = client.read();
|
||||||
|
|
||||||
|
if (!inBody) {
|
||||||
|
headerTail[0] = headerTail[1];
|
||||||
|
headerTail[1] = headerTail[2];
|
||||||
|
headerTail[2] = headerTail[3];
|
||||||
|
headerTail[3] = c;
|
||||||
|
inBody = headerTail[0] == '\r' &&
|
||||||
|
headerTail[1] == '\n' &&
|
||||||
|
headerTail[2] == '\r' &&
|
||||||
|
headerTail[3] == '\n';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.length() < maxResponseLength) {
|
||||||
|
body += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.stop();
|
||||||
|
return body.indexOf("\"hourly\"") >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WeatherForecast::parseForecast(const String &body, WeatherForecastInfo &forecast) const
|
||||||
|
{
|
||||||
|
float temperatures[forecastHours];
|
||||||
|
float precipitation[forecastHours];
|
||||||
|
int precipitationProbability[forecastHours];
|
||||||
|
int weatherCodes[forecastHours];
|
||||||
|
size_t temperatureCount = 0;
|
||||||
|
size_t precipitationCount = 0;
|
||||||
|
size_t probabilityCount = 0;
|
||||||
|
size_t weatherCodeCount = 0;
|
||||||
|
|
||||||
|
if (!parseFloatArray(body, "temperature_2m", temperatures, forecastHours, temperatureCount) ||
|
||||||
|
!parseFloatArray(body, "precipitation", precipitation, forecastHours, precipitationCount) ||
|
||||||
|
!parseIntArray(body, "precipitation_probability", precipitationProbability, forecastHours, probabilityCount) ||
|
||||||
|
!parseIntArray(body, "weather_code", weatherCodes, forecastHours, weatherCodeCount)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temperatureCount == 0 ||
|
||||||
|
precipitationCount == 0 ||
|
||||||
|
probabilityCount == 0 ||
|
||||||
|
weatherCodeCount == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float minTemperature = temperatures[0];
|
||||||
|
float maxTemperature = temperatures[0];
|
||||||
|
float totalPrecipitation = 0.0F;
|
||||||
|
uint8_t maxProbability = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < temperatureCount; i++) {
|
||||||
|
if (temperatures[i] < minTemperature) {
|
||||||
|
minTemperature = temperatures[i];
|
||||||
|
}
|
||||||
|
if (temperatures[i] > maxTemperature) {
|
||||||
|
maxTemperature = temperatures[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < precipitationCount; i++) {
|
||||||
|
totalPrecipitation += precipitation[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < probabilityCount; i++) {
|
||||||
|
if (precipitationProbability[i] > maxProbability) {
|
||||||
|
maxProbability = precipitationProbability[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forecast = {
|
||||||
|
true,
|
||||||
|
temperatures[0],
|
||||||
|
minTemperature,
|
||||||
|
maxTemperature,
|
||||||
|
totalPrecipitation,
|
||||||
|
maxProbability,
|
||||||
|
weatherCodes[0]
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WeatherForecast::parseFloatArray(const String &body, const char *name, float *values, size_t maxCount, size_t &count) const
|
||||||
|
{
|
||||||
|
const char *cursor = arrayStartForName(body, name);
|
||||||
|
if (cursor == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
while (*cursor != '\0' && *cursor != ']' && count < maxCount) {
|
||||||
|
skipWhitespace(cursor);
|
||||||
|
values[count++] = atof(cursor);
|
||||||
|
while (*cursor != '\0' && *cursor != ',' && *cursor != ']') {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
if (*cursor == ',') {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WeatherForecast::parseIntArray(const String &body, const char *name, int *values, size_t maxCount, size_t &count) const
|
||||||
|
{
|
||||||
|
const char *cursor = arrayStartForName(body, name);
|
||||||
|
if (cursor == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
while (*cursor != '\0' && *cursor != ']' && count < maxCount) {
|
||||||
|
skipWhitespace(cursor);
|
||||||
|
values[count++] = atoi(cursor);
|
||||||
|
while (*cursor != '\0' && *cursor != ',' && *cursor != ']') {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
if (*cursor == ',') {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#include "wifi_connection.h"
|
||||||
|
|
||||||
|
#include <WiFi101.h>
|
||||||
|
|
||||||
|
#include "wifi_credentials.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const unsigned long reconnectInterval = 30000;
|
||||||
|
}
|
||||||
|
|
||||||
|
WifiConnection::WifiConnection()
|
||||||
|
: nextAttempt_(0),
|
||||||
|
shieldAvailable_(true),
|
||||||
|
connected_(false),
|
||||||
|
ipAddress_(0, 0, 0, 0),
|
||||||
|
dirty_(true) {}
|
||||||
|
|
||||||
|
void WifiConnection::begin(unsigned long now)
|
||||||
|
{
|
||||||
|
shieldAvailable_ = WiFi.status() != WL_NO_SHIELD;
|
||||||
|
connect(now);
|
||||||
|
recordStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiConnection::update(unsigned long now)
|
||||||
|
{
|
||||||
|
if (!shieldAvailable_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
recordStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now >= nextAttempt_) {
|
||||||
|
connect(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
recordStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
WifiConnectionInfo WifiConnection::info() const
|
||||||
|
{
|
||||||
|
WifiConnectionInfo current = {
|
||||||
|
shieldAvailable_,
|
||||||
|
connected_,
|
||||||
|
ipAddress_
|
||||||
|
};
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WifiConnection::consumeDirty()
|
||||||
|
{
|
||||||
|
const bool d = dirty_;
|
||||||
|
dirty_ = false;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiConnection::connect(unsigned long now)
|
||||||
|
{
|
||||||
|
if (!shieldAvailable_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
nextAttempt_ = now + reconnectInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiConnection::recordStatus()
|
||||||
|
{
|
||||||
|
const bool currentConnected = WiFi.status() == WL_CONNECTED;
|
||||||
|
IPAddress currentIpAddress(0, 0, 0, 0);
|
||||||
|
if (currentConnected) {
|
||||||
|
currentIpAddress = WiFi.localIP();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connected_ != currentConnected || ipAddress_ != currentIpAddress) {
|
||||||
|
connected_ = currentConnected;
|
||||||
|
ipAddress_ = currentIpAddress;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user