feat: integrate scheduler and full status display (landscape 160x80)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,80 +1,71 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "display_ui.h"
|
|
||||||
#include "sensors.h"
|
#include "sensors.h"
|
||||||
#include "rtc_time.h"
|
#include "rtc_time.h"
|
||||||
|
#include "display_ui.h"
|
||||||
#include "forecast.h"
|
#include "forecast.h"
|
||||||
#include "history.h"
|
#include "history.h"
|
||||||
#include "app_settings.h"
|
#include "app_settings.h"
|
||||||
|
#include "state.h"
|
||||||
|
|
||||||
static void i2cScan() {
|
AppState g_state;
|
||||||
Serial.println(F("I2C scan:"));
|
|
||||||
byte found = 0;
|
static unsigned long tSample = 0;
|
||||||
for (byte addr = 1; addr < 127; addr++) {
|
static unsigned long tHistory = 0;
|
||||||
Wire.beginTransmission(addr);
|
|
||||||
if (Wire.endTransmission() == 0) {
|
static void sampleNow() {
|
||||||
Serial.printf(" device at 0x%02X\n", addr);
|
g_state.now = rtcNow();
|
||||||
found++;
|
g_state.absHpa = readAbsPressureHpa();
|
||||||
}
|
g_state.mslHpa = toSeaLevelHpa(g_state.absHpa, settings.altitudeM);
|
||||||
|
g_state.tempC = readTemperatureC();
|
||||||
|
|
||||||
|
float d;
|
||||||
|
g_state.haveTrend = historyTrendDelta(d);
|
||||||
|
if (g_state.haveTrend) {
|
||||||
|
g_state.trend = classifyTrend(d);
|
||||||
|
g_state.forecast = computeForecast(g_state.mslHpa, g_state.trend, g_state.now.month);
|
||||||
|
} else {
|
||||||
|
g_state.trend = TREND_STEADY;
|
||||||
}
|
}
|
||||||
Serial.printf(" %d device(s) found\n", found);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
delay(200);
|
delay(200);
|
||||||
Serial.println(F("\n=== Weather Predictor booting ==="));
|
Serial.println(F("\n=== Weather Predictor ==="));
|
||||||
Wire.begin(I2C_SDA, I2C_SCL); // MUST come before any I2C driver begin()
|
Wire.begin(I2C_SDA, I2C_SCL);
|
||||||
i2cScan();
|
|
||||||
|
|
||||||
displayBegin();
|
displayBegin();
|
||||||
displaySplash("Weather", "Predictor v0.1");
|
displaySplash("Weather", "Predictor");
|
||||||
|
|
||||||
if (!sensorsBegin()) Serial.println(F("BMP180 not found!"));
|
if (!sensorsBegin()) Serial.println(F("BMP180 not found!"));
|
||||||
if (!rtcBegin()) Serial.println(F("DS3231 not found!"));
|
if (!rtcBegin()) Serial.println(F("DS3231 not found!"));
|
||||||
if (rtcLostPower()) {
|
if (rtcLostPower()) {
|
||||||
Serial.println(F("RTC lost power -> setting to build time"));
|
Serial.println(F("RTC lost power -> temporary time set"));
|
||||||
rtcSet(RtcTime{2026, 7, 17, 12, 0, 0}); // temporary; NTP will correct later
|
rtcSet(RtcTime{2026, 7, 17, 12, 0, 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- forecast self-test (remove after Task 8) ---
|
|
||||||
struct { float p; Trend tr; } cases[] = {
|
|
||||||
{1030, TREND_STEADY}, {1030, TREND_RISING}, {1030, TREND_FALLING},
|
|
||||||
{1000, TREND_STEADY}, {1000, TREND_FALLING}, {970, TREND_FALLING},
|
|
||||||
};
|
|
||||||
for (auto& c : cases) {
|
|
||||||
Forecast f = computeForecast(c.p, c.tr, 7);
|
|
||||||
Serial.printf("p=%.0f trend=%d -> %c [%s] (%s)\n",
|
|
||||||
c.p, c.tr, f.letter, f.text, categoryShort(f.category));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- history self-test (remove after Task 8) ---
|
|
||||||
historyBegin();
|
|
||||||
uint32_t base = 1000000000UL;
|
|
||||||
for (int i = 0; i < 40; i++) // 40 samples @ 5 min = 3h20m, pressure falling
|
|
||||||
historyAdd(base + (uint32_t)i * 300, 1015.0f - i * 0.2f, 20.0f);
|
|
||||||
float d;
|
|
||||||
if (historyTrendDelta(d))
|
|
||||||
Serial.printf("history count=%d 3h delta=%.2f hPa trend=%d\n",
|
|
||||||
historyCount(), d, classifyTrend(d));
|
|
||||||
else
|
|
||||||
Serial.println(F("history: not enough data for trend"));
|
|
||||||
|
|
||||||
// --- settings self-test (remove after Task 8) ---
|
|
||||||
settingsBegin();
|
settingsBegin();
|
||||||
Serial.printf("settings: alt=%.0f tz=%d lat=%.4f lon=%.4f\n",
|
historyBegin();
|
||||||
settings.altitudeM, settings.tzOffsetMin, settings.lat, settings.lon);
|
|
||||||
settings.altitudeM += 1.0f; // change and persist to test round-trip
|
// Seed first sample immediately.
|
||||||
settingsSave();
|
sampleNow();
|
||||||
Serial.println(F("settings: incremented altitude and saved"));
|
historyAdd(rtcEpoch(), g_state.mslHpa, g_state.tempC);
|
||||||
|
displayRender(g_state);
|
||||||
|
tSample = tHistory = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
RtcTime t = rtcNow();
|
unsigned long now = millis();
|
||||||
float abs_ = readAbsPressureHpa();
|
|
||||||
Serial.printf("%04u-%02u-%02u %02u:%02u:%02u abs=%.1f msl=%.1f t=%.1f\n",
|
if (now - tSample >= SAMPLE_INTERVAL_MS) {
|
||||||
t.year, t.month, t.day, t.hour, t.minute, t.second,
|
tSample = now;
|
||||||
abs_, toSeaLevelHpa(abs_, DEFAULT_ALTITUDE_M), readTemperatureC());
|
sampleNow();
|
||||||
delay(2000);
|
displayRender(g_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now - tHistory >= HISTORY_INTERVAL_MS) {
|
||||||
|
tHistory = now;
|
||||||
|
historyAdd(rtcEpoch(), g_state.mslHpa, g_state.tempC);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,15 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "display_ui.h"
|
#include "display_ui.h"
|
||||||
|
|
||||||
static Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
|
// Non-static so displayRender() can reference it via extern.
|
||||||
|
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
|
||||||
|
|
||||||
void displayBegin() {
|
void displayBegin() {
|
||||||
pinMode(TFT_BLK, OUTPUT);
|
pinMode(TFT_BLK, OUTPUT);
|
||||||
digitalWrite(TFT_BLK, HIGH); // backlight on
|
digitalWrite(TFT_BLK, HIGH); // backlight on
|
||||||
tft.initR(INITR_MINI160x80); // 0.96" 80x160 ST7735S
|
tft.initR(INITR_MINI160x80); // 0.96" ST7735S
|
||||||
tft.invertDisplay(false); // this panel needs inversion; flip if colours look wrong
|
tft.invertDisplay(false); // this panel: no inversion
|
||||||
tft.setRotation(3); // portrait, 80 wide x 160 tall
|
tft.setRotation(3); // landscape, 160 wide x 80 tall
|
||||||
tft.fillScreen(ST77XX_BLACK);
|
tft.fillScreen(ST77XX_BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,9 +20,83 @@ void displaySplash(const char* line1, const char* line2) {
|
|||||||
tft.fillScreen(ST77XX_BLACK);
|
tft.fillScreen(ST77XX_BLACK);
|
||||||
tft.setTextColor(ST77XX_WHITE);
|
tft.setTextColor(ST77XX_WHITE);
|
||||||
tft.setTextSize(2);
|
tft.setTextSize(2);
|
||||||
tft.setCursor(4, 40);
|
tft.setCursor(4, 20);
|
||||||
tft.print(line1);
|
tft.print(line1);
|
||||||
tft.setTextSize(1);
|
tft.setTextSize(1);
|
||||||
tft.setCursor(4, 70);
|
tft.setCursor(4, 50);
|
||||||
tft.print(line2);
|
tft.print(line2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simple 34x34 weather icon at (x, y).
|
||||||
|
static void drawIcon(WxCategory c, int x, int y) {
|
||||||
|
uint16_t sun = ST77XX_YELLOW;
|
||||||
|
uint16_t cloud = ST77XX_WHITE;
|
||||||
|
uint16_t rain = ST77XX_CYAN;
|
||||||
|
switch (c) {
|
||||||
|
case WX_FINE:
|
||||||
|
tft.fillCircle(x + 17, y + 17, 11, sun);
|
||||||
|
break;
|
||||||
|
case WX_FAIR:
|
||||||
|
tft.fillCircle(x + 12, y + 12, 8, sun);
|
||||||
|
tft.fillRoundRect(x + 8, y + 18, 24, 12, 6, cloud);
|
||||||
|
break;
|
||||||
|
case WX_CHANGEABLE:
|
||||||
|
tft.fillRoundRect(x + 4, y + 12, 26, 14, 7, cloud);
|
||||||
|
break;
|
||||||
|
case WX_RAIN:
|
||||||
|
tft.fillRoundRect(x + 4, y + 8, 26, 14, 7, cloud);
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
tft.drawFastVLine(x + 9 + i * 8, y + 24, 8, rain);
|
||||||
|
break;
|
||||||
|
case WX_STORM:
|
||||||
|
tft.fillRoundRect(x + 4, y + 8, 26, 14, 7, cloud);
|
||||||
|
tft.fillTriangle(x + 16, y + 22, x + 12, y + 32, x + 20, y + 30, ST77XX_YELLOW);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tft.drawRect(x + 4, y + 10, 26, 18, cloud); // unknown / collecting
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layout for landscape 160 x 80.
|
||||||
|
void displayRender(const AppState& s) {
|
||||||
|
tft.fillScreen(ST77XX_BLACK);
|
||||||
|
tft.setTextColor(ST77XX_WHITE);
|
||||||
|
char buf[24];
|
||||||
|
|
||||||
|
// Time (large), top-left
|
||||||
|
snprintf(buf, sizeof(buf), "%02u:%02u", s.now.hour, s.now.minute);
|
||||||
|
tft.setTextSize(2);
|
||||||
|
tft.setCursor(4, 4);
|
||||||
|
tft.print(buf);
|
||||||
|
|
||||||
|
// Icon, top-right
|
||||||
|
drawIcon(s.haveTrend ? s.forecast.category : WX_UNKNOWN, 120, 4);
|
||||||
|
|
||||||
|
// Date
|
||||||
|
snprintf(buf, sizeof(buf), "%04u-%02u-%02u", s.now.year, s.now.month, s.now.day);
|
||||||
|
tft.setTextSize(1);
|
||||||
|
tft.setCursor(4, 26);
|
||||||
|
tft.print(buf);
|
||||||
|
|
||||||
|
// Pressure + trend arrow
|
||||||
|
snprintf(buf, sizeof(buf), "%.0f hPa", s.mslHpa);
|
||||||
|
tft.setCursor(4, 40);
|
||||||
|
tft.print(buf);
|
||||||
|
const char* arrow = (s.trend == TREND_RISING) ? "^" : (s.trend == TREND_FALLING ? "v" : "=");
|
||||||
|
tft.setCursor(96, 40);
|
||||||
|
tft.setTextColor(ST77XX_CYAN);
|
||||||
|
tft.print(arrow);
|
||||||
|
tft.setTextColor(ST77XX_WHITE);
|
||||||
|
|
||||||
|
// Temperature
|
||||||
|
snprintf(buf, sizeof(buf), "%.1f C", s.tempC);
|
||||||
|
tft.setCursor(4, 52);
|
||||||
|
tft.print(buf);
|
||||||
|
|
||||||
|
// Forecast short label (full width, bottom row)
|
||||||
|
tft.setTextColor(ST77XX_YELLOW);
|
||||||
|
tft.setCursor(4, 66);
|
||||||
|
tft.print(s.haveTrend ? categoryShort(s.forecast.category) : "Collecting...");
|
||||||
|
tft.setTextColor(ST77XX_WHITE);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include "state.h"
|
||||||
|
|
||||||
void displayBegin();
|
void displayBegin();
|
||||||
void displaySplash(const char* line1, const char* line2);
|
void displaySplash(const char* line1, const char* line2);
|
||||||
|
void displayRender(const AppState& s);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "rtc_time.h"
|
||||||
|
#include "forecast.h"
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
float absHpa;
|
||||||
|
float mslHpa;
|
||||||
|
float tempC;
|
||||||
|
Trend trend;
|
||||||
|
Forecast forecast;
|
||||||
|
RtcTime now;
|
||||||
|
bool haveTrend;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern AppState g_state;
|
||||||
Reference in New Issue
Block a user