Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4917f57d16 | ||
|
|
6f6ea0e0fd |
@@ -3,3 +3,4 @@
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.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
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
enum class MenuAction
|
||||
{
|
||||
ShowInternetDetails,
|
||||
ShowHelloWorld,
|
||||
BackToCarousel
|
||||
};
|
||||
|
||||
+5
-1
@@ -4,9 +4,13 @@
|
||||
#include <U8g2lib.h>
|
||||
|
||||
#include "am2302_sensor.h"
|
||||
#include "internet_time.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 drawEnvironmentScreen(U8G2 &u8g2);
|
||||
void drawWeatherIconScreen(U8G2 &u8g2);
|
||||
|
||||
@@ -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,10 @@
|
||||
#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
|
||||
|
||||
#endif
|
||||
@@ -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;
|
||||
}
|
||||
+27
-1
@@ -4,8 +4,10 @@
|
||||
|
||||
#include "am2302_sensor.h"
|
||||
#include "carousel.h"
|
||||
#include "internet_time.h"
|
||||
#include "menu.h"
|
||||
#include "screens.h"
|
||||
#include "wifi_connection.h"
|
||||
|
||||
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, U8X8_PIN_NONE);
|
||||
|
||||
@@ -14,16 +16,19 @@ int lastButtonSelectState = LOW;
|
||||
uint8_t buttonNext = A4;
|
||||
uint8_t buttonSelect = A5;
|
||||
Am2302Sensor am2302(A3);
|
||||
WifiConnection wifiConnection;
|
||||
InternetTime internetTime;
|
||||
|
||||
enum class AppMode
|
||||
{
|
||||
Carousel,
|
||||
Menu,
|
||||
InternetDetails,
|
||||
HelloWorld
|
||||
};
|
||||
|
||||
Screen screens[] = {
|
||||
{ drawScreen1 },
|
||||
{ drawTimeScreen },
|
||||
{ drawEnvironmentScreen },
|
||||
{ drawWeatherIconScreen }
|
||||
};
|
||||
@@ -36,6 +41,7 @@ void setup() {
|
||||
pinMode(buttonNext, INPUT);
|
||||
pinMode(buttonSelect, INPUT);
|
||||
am2302.begin();
|
||||
wifiConnection.begin(millis());
|
||||
|
||||
u8g2.begin();
|
||||
u8g2.clearBuffer();
|
||||
@@ -52,6 +58,9 @@ void openMenu()
|
||||
void selectMenuOption()
|
||||
{
|
||||
switch (menu.select()) {
|
||||
case MenuAction::ShowInternetDetails:
|
||||
appMode = AppMode::InternetDetails;
|
||||
break;
|
||||
case MenuAction::ShowHelloWorld:
|
||||
appMode = AppMode::HelloWorld;
|
||||
break;
|
||||
@@ -73,6 +82,7 @@ void handleNextButton()
|
||||
case AppMode::Menu:
|
||||
menu.nextOption();
|
||||
break;
|
||||
case AppMode::InternetDetails:
|
||||
case AppMode::HelloWorld:
|
||||
break;
|
||||
}
|
||||
@@ -82,6 +92,7 @@ void handleSelectButton()
|
||||
{
|
||||
switch (appMode) {
|
||||
case AppMode::Carousel:
|
||||
case AppMode::InternetDetails:
|
||||
case AppMode::HelloWorld:
|
||||
openMenu();
|
||||
break;
|
||||
@@ -103,6 +114,8 @@ bool displayNeedsRedraw()
|
||||
return carousel.consumeDirty();
|
||||
case AppMode::Menu:
|
||||
return menu.consumeDirty();
|
||||
case AppMode::InternetDetails:
|
||||
return wifiConnection.consumeDirty();
|
||||
case AppMode::HelloWorld:
|
||||
return false;
|
||||
}
|
||||
@@ -119,6 +132,9 @@ void drawActiveView()
|
||||
case AppMode::Menu:
|
||||
menu.draw(u8g2);
|
||||
break;
|
||||
case AppMode::InternetDetails:
|
||||
drawInternetConnectionScreen(u8g2, wifiConnection.info());
|
||||
break;
|
||||
case AppMode::HelloWorld:
|
||||
drawHelloWorldScreen(u8g2);
|
||||
break;
|
||||
@@ -127,6 +143,16 @@ void drawActiveView()
|
||||
|
||||
void loop(){
|
||||
const unsigned long now = millis();
|
||||
wifiConnection.update(now);
|
||||
internetTime.update(now, wifiConnection.info().connected);
|
||||
|
||||
if (internetTime.consumeDirty()) {
|
||||
setInternetTimeInfo(internetTime.info());
|
||||
if (appMode == AppMode::Carousel) {
|
||||
carousel.requestRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (appMode == AppMode::Carousel) {
|
||||
carousel.update(now);
|
||||
}
|
||||
|
||||
+4
-1
@@ -3,6 +3,7 @@
|
||||
namespace
|
||||
{
|
||||
const char *menuOptions[] = {
|
||||
"Internet",
|
||||
"Hello world",
|
||||
"Back"
|
||||
};
|
||||
@@ -13,6 +14,8 @@ MenuAction actionForIndex(size_t index)
|
||||
{
|
||||
switch (index) {
|
||||
case 0:
|
||||
return MenuAction::ShowInternetDetails;
|
||||
case 1:
|
||||
return MenuAction::ShowHelloWorld;
|
||||
default:
|
||||
return MenuAction::BackToCarousel;
|
||||
@@ -30,7 +33,7 @@ void Menu::draw(U8G2 &u8g2)
|
||||
u8g2.drawStr(0, 12, "Menu");
|
||||
|
||||
for (size_t i = 0; i < menuOptionCount; i++) {
|
||||
const uint8_t y = 32 + (i * 18);
|
||||
const uint8_t y = 28 + (i * 14);
|
||||
if (i == selectedIndex_) {
|
||||
u8g2.drawStr(0, y, ">");
|
||||
}
|
||||
|
||||
+57
-3
@@ -3,6 +3,15 @@
|
||||
namespace
|
||||
{
|
||||
Am2302Reading environmentReading = {0.0F, 0.0F, false};
|
||||
InternetTimeInfo internetTimeInfo = {false, 0, 0, 0, 0, 0};
|
||||
|
||||
void printTwoDigits(U8G2 &u8g2, uint8_t value)
|
||||
{
|
||||
if (value < 10) {
|
||||
u8g2.print("0");
|
||||
}
|
||||
u8g2.print(value);
|
||||
}
|
||||
|
||||
void drawSun(U8G2 &u8g2, uint8_t x, uint8_t y)
|
||||
{
|
||||
@@ -27,10 +36,33 @@ void drawCloud(U8G2 &u8g2, uint8_t x, uint8_t y)
|
||||
}
|
||||
}
|
||||
|
||||
void drawScreen1(U8G2 &u8g2)
|
||||
void setInternetTimeInfo(InternetTimeInfo timeInfo)
|
||||
{
|
||||
u8g2.setFont(u8g2_font_ncenB14_tr);
|
||||
u8g2.drawStr(0, 20, "Screen 1");
|
||||
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)
|
||||
@@ -40,6 +72,28 @@ void drawHelloWorldScreen(U8G2 &u8g2)
|
||||
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)
|
||||
{
|
||||
environmentReading = reading;
|
||||
|
||||
@@ -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