// =====Code for the Arduino R4_Slave====
#include <Arduino.h>
#include <Wire.h>
#include <WiFiS3.h>
#include <WiFiSSLClient.h>
#include <ArduinoHttpClient.h>
// ================= USER SETTINGS =================
static const char* WIFI_SSID = "wifi ssid";
static const char* WIFI_PASS = "wifipassword";
static const float LATITUDE = 39.1518f;
static const float LONGITUDE = -77.9822f;
static const uint8_t I2C_ADDR = 0x12;
static const uint32_t WEATHER_PERIOD_OK_MS = 10UL * 60UL * 1000UL; // 10 min
static const uint32_t WEATHER_PERIOD_FAIL_MS = 60UL * 1000UL; // 60 sec base fail retry
static const uint32_t FAIL_BACKOFF_MAX_MS = 10UL * 60UL * 1000UL; // cap at 10 min
static const char* WEATHER_HOST = "api.open-meteo.com";
static const int WEATHER_PORT = 443;
static const uint16_t WIFI_CONNECT_WINDOW_MS = 3500;
static const uint16_t WIFI_IP_WINDOW_MS = 1200;
static const uint16_t HTTP_TIMEOUT_MS = 9000;
static const uint32_t STALE_AFTER_MS = 40UL * 60UL * 1000UL; // 40 min (matches R3)
// ================= STATE =================
static int16_t g_tempF10 = 0; // temp * 10 (F)
static uint16_t g_wmoCode = 0; // weathercode
static uint32_t g_lastOkMs = 0; // millis at last successful fetch
static bool g_wifiOk = false; // connected + real IP
static bool g_haveWeather = false; // ever fetched successfully since boot
static uint32_t g_lastAttemptMs = 0;
static uint32_t g_failBackoffMs = WEATHER_PERIOD_FAIL_MS;
// Cached I2C packet (8 bytes)
static volatile uint8_t g_pkt[8] = {0};
// ================= LED =================
static const uint32_t LED_FAST_MS = 120;
static const uint32_t LED_SLOW_MS = 700;
static uint32_t led_last_ms = 0;
static bool led_state = false;
static void led_update() {
bool stale = false;
if (g_haveWeather && g_lastOkMs != 0) stale = ((millis() - g_lastOkMs) > STALE_AFTER_MS);
if (g_haveWeather && !stale) {
digitalWrite(LED_BUILTIN, HIGH);
return;
}
uint32_t now = millis();
if (!g_wifiOk) {
if (now - led_last_ms >= LED_FAST_MS) {
led_last_ms = now;
led_state = !led_state;
digitalWrite(LED_BUILTIN, led_state ? HIGH : LOW);
}
return;
}
if (now - led_last_ms >= LED_SLOW_MS) {
led_last_ms = now;
led_state = !led_state;
digitalWrite(LED_BUILTIN, led_state ? HIGH : LOW);
}
}
// ================= UTILS =================
static uint8_t crc_xor(const uint8_t* p, size_t n) {
uint8_t c = 0;
for (size_t i = 0; i < n; i++) c ^= p[i];
return c;
}
static bool ip_is_nonzero(IPAddress ip) {
return !(ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0);
}
static void wifi_ensure_connected() {
if (WiFi.status() == WL_CONNECTED && ip_is_nonzero(WiFi.localIP())) {
g_wifiOk = true;
return;
}
g_wifiOk = false;
Serial.println("WiFi: connecting...");
WiFi.begin(WIFI_SSID, WIFI_PASS);
uint32_t start = millis();
while (millis() - start < WIFI_CONNECT_WINDOW_MS) {
if (WiFi.status() == WL_CONNECTED) {
uint32_t ipStart = millis();
while (millis() - ipStart < WIFI_IP_WINDOW_MS) {
IPAddress ip = WiFi.localIP();
if (ip_is_nonzero(ip)) {
g_wifiOk = true;
Serial.print("WiFi: connected, IP=");
Serial.println(ip);
return;
}
delay(25);
}
Serial.print("WiFi: connected but no IP, IP=");
Serial.println(WiFi.localIP());
g_wifiOk = false;
return;
}
delay(50);
}
Serial.println("WiFi: connect timeout");
g_wifiOk = false;
}
// Parse helpers
static bool parse_float_after_key(const String &s, int startAt, const char* key, float &out) {
int idx = s.indexOf(key, startAt);
if (idx < 0) return false;
idx += (int)strlen(key);
while (idx < (int)s.length() && s[idx] == ' ') idx++;
int end = idx;
while (end < (int)s.length()) {
char c = s[end];
if ((c >= '0' && c <= '9') || c == '-' || c == '.') end++;
else break;
}
if (end <= idx) return false;
out = s.substring(idx, end).toFloat();
return true;
}
static bool parse_int_after_key(const String &s, int startAt, const char* key, int &out) {
int idx = s.indexOf(key, startAt);
if (idx < 0) return false;
idx += (int)strlen(key);
while (idx < (int)s.length() && s[idx] == ' ') idx++;
int end = idx;
while (end < (int)s.length()) {
char c = s[end];
if (c >= '0' && c <= '9') end++;
else break;
}
if (end <= idx) return false;
out = s.substring(idx, end).toInt();
return true;
}
static bool fetch_weather_once(int16_t &tempF10_out, uint16_t &code_out) {
if (!g_wifiOk) return false;
// DNS sanity check (only costs a tiny bit)
IPAddress hostIp;
int dnsOk = WiFi.hostByName(WEATHER_HOST, hostIp);
Serial.print("DNS ");
Serial.print(WEATHER_HOST);
Serial.print(" ok=");
Serial.print(dnsOk);
Serial.print(" ip=");
Serial.println(hostIp);
if (dnsOk == 0 || !ip_is_nonzero(hostIp)) return false;
WiFiSSLClient ssl;
HttpClient client(ssl, WEATHER_HOST, WEATHER_PORT);
client.setHttpResponseTimeout(HTTP_TIMEOUT_MS);
char path[240];
snprintf(
path, sizeof(path),
"/v1/forecast?latitude=%.4f&longitude=%.4f¤t_weather=true&temperature_unit=fahrenheit&timezone=auto",
(double)LATITUDE, (double)LONGITUDE
);
Serial.print("HTTP GET ");
Serial.println(path);
client.get(path);
int status = client.responseStatusCode();
Serial.print("HTTP status: ");
Serial.println(status);
String body = client.responseBody();
Serial.print("Body len: ");
Serial.println(body.length());
Serial.print("Body head: ");
Serial.println(body.substring(0, 140));
if (status != 200) return false;
if (body.length() < 50) return false;
int cw = body.indexOf("\"current_weather\"");
if (cw < 0) {
Serial.println("No current_weather object");
return false;
}
float tF = NAN;
int weathercode = -1;
bool okT = parse_float_after_key(body, cw, "\"temperature\":", tF);
bool okC = parse_int_after_key(body, cw, "\"weathercode\":", weathercode);
if (!okT || !okC || isnan(tF) || weathercode < 0) return false;
tempF10_out = (int16_t)lroundf(tF * 10.0f);
if (weathercode > 65535) weathercode = 65535;
code_out = (uint16_t)weathercode;
return true;
}
static void rebuild_packet_cache() {
uint32_t lastOk = g_lastOkMs;
uint32_t ageMs = (lastOk == 0) ? 0xFFFFFFFFUL : (millis() - lastOk);
uint32_t ageSec32 = (ageMs == 0xFFFFFFFFUL) ? 65535UL : (ageMs / 1000UL);
if (ageSec32 > 65535UL) ageSec32 = 65535UL;
uint16_t ageSec = (uint16_t)ageSec32;
bool stale = false;
if (g_haveWeather && lastOk != 0) stale = ((millis() - lastOk) > STALE_AFTER_MS);
uint8_t pkt[8];
int16_t t = g_tempF10;
uint16_t c = g_wmoCode;
pkt[0] = (uint8_t)(t & 0xFF);
pkt[1] = (uint8_t)((t >> 8) & 0xFF);
pkt[2] = (uint8_t)(c & 0xFF);
pkt[3] = (uint8_t)((c >> 8) & 0xFF);
pkt[4] = (uint8_t)(ageSec & 0xFF);
pkt[5] = (uint8_t)((ageSec >> 8) & 0xFF);
// bit0 = wifiOk, bit1 = haveWeather, bit2 = stale
uint8_t flags = 0;
if (g_wifiOk) flags |= (1 << 0);
if (g_haveWeather) flags |= (1 << 1);
if (stale) flags |= (1 << 2);
pkt[6] = flags;
pkt[7] = crc_xor(pkt, 7);
noInterrupts();
for (uint8_t i = 0; i < 8; i++) g_pkt[i] = pkt[i];
interrupts();
}
static void weather_update_if_due() {
uint32_t now = millis();
uint32_t period = g_haveWeather ? WEATHER_PERIOD_OK_MS : g_failBackoffMs;
if (g_lastAttemptMs != 0 && (now - g_lastAttemptMs) < period) return;
g_lastAttemptMs = now;
wifi_ensure_connected();
if (!g_wifiOk) {
Serial.println("Weather: skip (no wifi)");
// backoff grows when failing before first success
if (!g_haveWeather) {
g_failBackoffMs = min(g_failBackoffMs * 2UL, FAIL_BACKOFF_MAX_MS);
Serial.print("Backoff now ");
Serial.print(g_failBackoffMs / 1000UL);
Serial.println("s");
}
rebuild_packet_cache();
return;
}
int16_t newTempF10 = 0;
uint16_t newCode = 0;
bool ok = fetch_weather_once(newTempF10, newCode);
if (ok) {
g_tempF10 = newTempF10;
g_wmoCode = newCode;
g_lastOkMs = now;
g_haveWeather = true;
g_failBackoffMs = WEATHER_PERIOD_FAIL_MS; // reset backoff
Serial.println("Weather: OK");
} else {
Serial.println("Weather: FAIL (keeping last good if any)");
if (!g_haveWeather) {
g_failBackoffMs = min(g_failBackoffMs * 2UL, FAIL_BACKOFF_MAX_MS);
Serial.print("Backoff now ");
Serial.print(g_failBackoffMs / 1000UL);
Serial.println("s");
}
}
rebuild_packet_cache();
}
// I2C request: send cached 8-byte packet (FAST)
static void onI2CRequest() {
Wire.write((const uint8_t*)g_pkt, 8);
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
Serial.begin(115200);
delay(300);
Serial.println("R4 Weather Slave starting...");
Wire.begin(I2C_ADDR);
Wire.onRequest(onI2CRequest);
g_tempF10 = 0;
g_wmoCode = 0;
g_lastOkMs = 0;
g_wifiOk = false;
g_haveWeather = false;
g_lastAttemptMs = 0;
g_failBackoffMs = WEATHER_PERIOD_FAIL_MS;
rebuild_packet_cache();
weather_update_if_due();
}
void loop() {
weather_update_if_due();
led_update();
delay(10);
}
Rhea Rae
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.