/* SNIPPET #276 */ var intervalSec = 60 var postUrl = "http://nippari.com:63152" var prevTimestamp = null var prevTotalAct = null var clockOffsetMs = 0 function escape(str) { var s = "" var c = "" for (var i = 0; i < str.length; i++) { c = str.charAt(i) if (c === "\\") s += "\\\\" else if (c === "\"") s += "\\\"" else s += c } return s } function toJson(obj) { var out = [] for (var k in obj) { var v = obj[k] var val = (typeof v === "string") ? "\"" + escape(v) + "\"" : (typeof v === "boolean") ? (v ? "true" : "false") : isNaN(v) ? "\"" + escape(v) + "\"" : v out.push("\"" + escape(k) + "\":" + val) } return "{" + out.join(",") + "}" } function postReading(mac, timestamp, total_act) { var payload = { mac: mac, timestamp: timestamp, total_act: total_act } var body = toJson(payload) Shelly.call("HTTP.POST", { url: postUrl, body: body, timeout: 5, headers: { "Content-Type": "application/json" } }, function (resp, err) { if (err) { print("❌ HTTP.POST failed:", err) } else { print("✔ Sent:", body) } }) } function sendEnergyReading() { Shelly.call("EMData.GetStatus", { id: 0 }, function (em, err) { if (!em || err || typeof em.total_act !== "number") { print("⚠ EMData.GetStatus failed:", err) return } Shelly.call("Sys.GetStatus", {}, function (sys, err2) { var now = Date.now() + clockOffsetMs var mac = sys && sys.mac ? sys.mac : "unknown" var total = em.total_act if (prevTimestamp !== null && prevTotalAct !== null) { var prev = prevTimestamp var prevVal = prevTotalAct var deltaMs = now - prev var deltaAct = total - prevVal var boundary = Math.floor(now / (15 * 60 * 1000)) * (15 * 60 * 1000) if (prev < boundary && now > boundary) { var portionBefore = (boundary - prev) / deltaMs var act1 = prevVal + deltaAct * portionBefore var act2 = total postReading(mac, boundary - 1, act1) postReading(mac, boundary, act2) } else { postReading(mac, now, total) } } else { print("📦 First reading only: total_act =", total, "at", now) } prevTimestamp = now prevTotalAct = total }) }) } function syncClockOffset() { Shelly.call("Sys.GetStatus", {}, function (sys, err) { if (err || !sys.unixtime) { print("⚠ NTP sync failed") return } var ntpNow = sys.unixtime * 1000 var localNow = Date.now() clockOffsetMs = ntpNow - localNow print("⏱ Clock offset synced:", clockOffsetMs, "ms") var jitterMin = 22 * 60 * 60 * 1000 // 22h var jitterMax = 26 * 60 * 60 * 1000 // 26h var nextDelay = jitterMin + Math.floor(Math.random() * (jitterMax - jitterMin)) Timer.set(nextDelay, false, syncClockOffset) }) } function waitForWiFi() { Shelly.call("WiFi.GetStatus", {}, function (res) { if (res && res.ssid && res.sta_ip) { print("Wi-Fi ready:", res.ssid) syncClockOffset() sendEnergyReading() Timer.set(intervalSec * 1000, true, sendEnergyReading) } else { print("⏳ Waiting for Wi-Fi...") Timer.set(5000, false, waitForWiFi) } }) } // Start print("Boot: waiting for Wi-Fi...") waitForWiFi()