Compare commits

..
2 Commits
11 changed files with 401 additions and 6 deletions
+1
View File
@@ -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
+36
View File
@@ -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
+1
View File
@@ -6,6 +6,7 @@
enum class MenuAction enum class MenuAction
{ {
ShowInternetDetails,
ShowHelloWorld, ShowHelloWorld,
BackToCarousel BackToCarousel
}; };
+5 -1
View File
@@ -4,9 +4,13 @@
#include <U8g2lib.h> #include <U8g2lib.h>
#include "am2302_sensor.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 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 drawWeatherIconScreen(U8G2 &u8g2);
+34
View File
@@ -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
+10
View File
@@ -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
+142
View File
@@ -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
View File
@@ -4,8 +4,10 @@
#include "am2302_sensor.h" #include "am2302_sensor.h"
#include "carousel.h" #include "carousel.h"
#include "internet_time.h"
#include "menu.h" #include "menu.h"
#include "screens.h" #include "screens.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);
@@ -14,16 +16,19 @@ int lastButtonSelectState = LOW;
uint8_t buttonNext = A4; uint8_t buttonNext = A4;
uint8_t buttonSelect = A5; uint8_t buttonSelect = A5;
Am2302Sensor am2302(A3); Am2302Sensor am2302(A3);
WifiConnection wifiConnection;
InternetTime internetTime;
enum class AppMode enum class AppMode
{ {
Carousel, Carousel,
Menu, Menu,
InternetDetails,
HelloWorld HelloWorld
}; };
Screen screens[] = { Screen screens[] = {
{ drawScreen1 }, { drawTimeScreen },
{ drawEnvironmentScreen }, { drawEnvironmentScreen },
{ drawWeatherIconScreen } { drawWeatherIconScreen }
}; };
@@ -36,6 +41,7 @@ void setup() {
pinMode(buttonNext, INPUT); pinMode(buttonNext, INPUT);
pinMode(buttonSelect, INPUT); pinMode(buttonSelect, INPUT);
am2302.begin(); am2302.begin();
wifiConnection.begin(millis());
u8g2.begin(); u8g2.begin();
u8g2.clearBuffer(); u8g2.clearBuffer();
@@ -52,6 +58,9 @@ void openMenu()
void selectMenuOption() void selectMenuOption()
{ {
switch (menu.select()) { switch (menu.select()) {
case MenuAction::ShowInternetDetails:
appMode = AppMode::InternetDetails;
break;
case MenuAction::ShowHelloWorld: case MenuAction::ShowHelloWorld:
appMode = AppMode::HelloWorld; appMode = AppMode::HelloWorld;
break; break;
@@ -73,6 +82,7 @@ void handleNextButton()
case AppMode::Menu: case AppMode::Menu:
menu.nextOption(); menu.nextOption();
break; break;
case AppMode::InternetDetails:
case AppMode::HelloWorld: case AppMode::HelloWorld:
break; break;
} }
@@ -82,6 +92,7 @@ void handleSelectButton()
{ {
switch (appMode) { switch (appMode) {
case AppMode::Carousel: case AppMode::Carousel:
case AppMode::InternetDetails:
case AppMode::HelloWorld: case AppMode::HelloWorld:
openMenu(); openMenu();
break; break;
@@ -103,6 +114,8 @@ bool displayNeedsRedraw()
return carousel.consumeDirty(); return carousel.consumeDirty();
case AppMode::Menu: case AppMode::Menu:
return menu.consumeDirty(); return menu.consumeDirty();
case AppMode::InternetDetails:
return wifiConnection.consumeDirty();
case AppMode::HelloWorld: case AppMode::HelloWorld:
return false; return false;
} }
@@ -119,6 +132,9 @@ void drawActiveView()
case AppMode::Menu: case AppMode::Menu:
menu.draw(u8g2); menu.draw(u8g2);
break; break;
case AppMode::InternetDetails:
drawInternetConnectionScreen(u8g2, wifiConnection.info());
break;
case AppMode::HelloWorld: case AppMode::HelloWorld:
drawHelloWorldScreen(u8g2); drawHelloWorldScreen(u8g2);
break; break;
@@ -127,6 +143,16 @@ void drawActiveView()
void loop(){ void loop(){
const unsigned long now = millis(); 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) { if (appMode == AppMode::Carousel) {
carousel.update(now); carousel.update(now);
} }
+4 -1
View File
@@ -3,6 +3,7 @@
namespace namespace
{ {
const char *menuOptions[] = { const char *menuOptions[] = {
"Internet",
"Hello world", "Hello world",
"Back" "Back"
}; };
@@ -13,6 +14,8 @@ MenuAction actionForIndex(size_t index)
{ {
switch (index) { switch (index) {
case 0: case 0:
return MenuAction::ShowInternetDetails;
case 1:
return MenuAction::ShowHelloWorld; return MenuAction::ShowHelloWorld;
default: default:
return MenuAction::BackToCarousel; return MenuAction::BackToCarousel;
@@ -30,7 +33,7 @@ void Menu::draw(U8G2 &u8g2)
u8g2.drawStr(0, 12, "Menu"); u8g2.drawStr(0, 12, "Menu");
for (size_t i = 0; i < menuOptionCount; i++) { 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_) { if (i == selectedIndex_) {
u8g2.drawStr(0, y, ">"); u8g2.drawStr(0, y, ">");
} }
+57 -3
View File
@@ -3,6 +3,15 @@
namespace namespace
{ {
Am2302Reading environmentReading = {0.0F, 0.0F, false}; 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) 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); internetTimeInfo = timeInfo;
u8g2.drawStr(0, 20, "Screen 1"); }
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) void drawHelloWorldScreen(U8G2 &u8g2)
@@ -40,6 +72,28 @@ void drawHelloWorldScreen(U8G2 &u8g2)
u8g2.drawStr(0, 54, "world"); 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)
{ {
environmentReading = reading; environmentReading = reading;
+84
View File
@@ -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;
}
}