Author: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Sat Dec 27 10:59:57 2025 +0000
Fix PCON startup behavior, UI sync, and data persistence
- Backend: Disable "Rule 1" (Discovery) power-on in `src/power/p-sai.c` to prevent unwanted power toggling on `sai-power` restart.
- Backend: Implement change detection in `sul_pcon_check_cb` to notify `sai-server` via `saip_queue_stay_info` when PCON state actually changes, fixing UI desynchronization.
- Frontend: Add `pcon_energy_cache` in `assets/sai.js` to persist energy data across builder list redraws, preventing data flickering/loss.
- Frontend: Ensure explicit queuing of `pcon_control` messages in `src/web/w-ws-browser.c` to resolve potential forwarding issues.
diff --git a/assets/sai.js b/assets/sai.js
index 7d976c9..7889afc 100644
--- a/assets/sai.js
+++ b/assets/sai.js
@@ -401,6 +401,7 @@ var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, exptimer, auth_user
/* Global caches for reconcilation */
var pcon_topology = {};
+var pcon_energy_cache = {};
var last_builder_list = [];
function createPconDiv(pcon) {
@@ -1406,6 +1407,18 @@ function createPconDiv(pcon) {
let type = pcon.type ? `(${pcon.type})` : "";
header.innerHTML = `⏻ ${hsanitize(pcon.name)} ${hsanitize(type)}`;
+
+ if (pcon_energy_cache[pcon.name]) {
+ const d = pcon_energy_cache[pcon.name];
+ let stats = document.createElement("span");
+ stats.className = "pcon-stats";
+ stats.style.marginLeft = "10px";
+ stats.style.fontSize = "0.9em";
+ stats.style.color = "#666";
+ stats.textContent = `${d.voltage_v}V ${d.active_power_w}W ${d.current_ma}mA today:${(d.energy_today_wh/1000).toFixed(3)}kWh`;
+ header.appendChild(stats);
+ }
+
pconDiv.appendChild(header);
/* Context menu for PCON */
@@ -1733,6 +1746,7 @@ function ws_open_sai()
case "com.warmcat.sai.pcon_energy":
if (jso.items) {
jso.items.forEach(item => {
+ pcon_energy_cache[item.name] = item;
const pconDiv = document.getElementById("pcon-" + item.name);
if (pconDiv) {
let header = pconDiv.querySelector(".pcon-header");
diff --git a/src/power/p-sai.c b/src/power/p-sai.c
index ad7d4ff..2bd5c67 100644
--- a/src/power/p-sai.c
+++ b/src/power/p-sai.c
@@ -197,6 +197,8 @@ static const struct lws_protocols *pprotocols[] = {
void
sul_pcon_check_cb(lws_sorted_usec_list_t *sul)
{
+ int any_changed = 0;
+
/* Iterate all PCONs */
lws_start_foreach_dll(struct lws_dll2 *, p, power.sai_pcon_owner.head) {
saip_pcon_t *pc = lws_container_of(p, saip_pcon_t, list);
@@ -204,11 +206,12 @@ sul_pcon_check_cb(lws_sorted_usec_list_t *sul)
/* Rule 1: No builders registered -> Turn ON (Cold start / Discovery) */
if (!pc->registered_builders_owner.count) {
- target_on = 1;
- lwsl_info("%s: PCON %s has 0 builders -> Force ON\n", __func__, pc->name);
+ /* target_on = 1; */
+ /* lwsl_info("%s: PCON %s has 0 builders -> Force ON\n", __func__, pc->name); */
}
+
/* Rule 2: User Keep On -> Turn ON */
- else if (pc->user_keep_on) {
+ if (pc->user_keep_on) {
target_on = 1;
lwsl_info("%s: PCON %s has user keep on -> Force ON\n", __func__, pc->name);
}
@@ -223,14 +226,28 @@ sul_pcon_check_cb(lws_sorted_usec_list_t *sul)
lwsl_notice("%s: PCON %s ON (target=1, current=%d)\n", __func__, pc->name, pc->on);
pc->on = 1;
saip_switch(pc, 1);
+ any_changed = 1;
} else if (!target_on && pc->on) {
lwsl_notice("%s: PCON %s OFF (target=0, current=%d)\n", __func__, pc->name, pc->on);
pc->on = 0;
saip_switch(pc, 0);
+ any_changed = 1;
}
} lws_end_foreach_dll(p);
+ if (any_changed) {
+ lws_start_foreach_dll_safe(struct lws_dll2 *, mp, mp1,
+ power.sai_server_owner.head) {
+ saip_server_t *sps = lws_container_of(mp, struct saip_server, list);
+ saip_queue_stay_info(sps);
+ if (sps->ss) {
+ if (lws_ss_request_tx(sps->ss))
+ lwsl_warn("%s: failed to request tx\n", __func__);
+ }
+ } lws_end_foreach_dll_safe(mp, mp1);
+ }
+
/* Schedule next check */
lws_sul_schedule(power.context, 0, &power.sul_pcon_check, sul_pcon_check_cb, 5 * LWS_US_PER_SEC);
}