diff --git a/assets/sai.js b/assets/sai.js
index 3d98622..e5ad7ce 100644
--- a/assets/sai.js
+++ b/assets/sai.js
@@ -1051,8 +1051,9 @@ function createBuilderDiv(plat) {
});
const menuItems = [
- { label: `<b>SAI Hash:</b> ${plat.sai_hash}` },
- { label: `<b>LWS Hash:</b> ${plat.lws_hash}` },
+ { label: `<b>SAI:</b> ${plat.sai_hash}` },
+ { label: `<b>LWS:</b> ${plat.lws_hash}` },
+ /*
{
label: "Update SAI",
callback: () => {
@@ -1063,8 +1064,37 @@ function createBuilderDiv(plat) {
sai.send(JSON.stringify(rebuildMsg));
}
}
+ */
];
+ if (plat.power_managed && authd) {
+ if (plat.stay_on) {
+ menuItems.push({
+ label: "Release Stay",
+ callback: () => {
+ const stayMsg = {
+ schema: "com.warmcat.sai.stay",
+ builder_name: plat.name.split('.')[0],
+ stay_on: 0
+ };
+ sai.send(JSON.stringify(stayMsg));
+ }
+ });
+ } else {
+ menuItems.push({
+ label: "Stay On",
+ callback: () => {
+ const stayMsg = {
+ schema: "com.warmcat.sai.stay",
+ builder_name: plat.name.split('.')[0],
+ stay_on: 1
+ };
+ sai.send(JSON.stringify(stayMsg));
+ }
+ });
+ }
+ }
+
platDiv.addEventListener("contextmenu", function(event) {
if (!authd)
return;
diff --git a/src/common/include/private.h b/src/common/include/private.h
index 9177117..563efee 100644
--- a/src/common/include/private.h
+++ b/src/common/include/private.h
@@ -459,6 +459,8 @@ typedef struct sai_plat {
unsigned int job_limit;
char windows;
+ char power_managed;
+ char stay_on;
int index; /* used to create unique build dir path */
} sai_plat_t;
@@ -531,7 +533,44 @@ typedef struct sai_build_metric_db {
int parallel;
} sai_build_metric_db_t;
+/*
+ * Browser -> sai-web -> sai-server -> sai-power
+ *
+ * A browser user wants to set or release a "stay" on a builder, so it won't
+ * power down automatically when idle.
+ */
+typedef struct sai_stay {
+ lws_dll2_t list;
+ char builder_name[64];
+ char stay_on; /* 0 = release, 1 = set */
+} sai_stay_t;
+
+/* sai-power -> sai-server, tells it the builders it can manage */
+typedef struct sai_power_managed_builder {
+ lws_dll2_t list;
+ char name[64];
+ char stay_on;
+} sai_power_managed_builder_t;
+
+typedef struct sai_power_managed_builders {
+ lws_dll2_t list;
+ lws_dll2_owner_t builders; /* sai_power_managed_builder_t */
+} sai_power_managed_builders_t;
+
+
+typedef struct sai_stay_state_update {
+ lws_dll2_t list;
+ char builder_name[64];
+ char stay_on;
+} sai_stay_state_update_t;
+
+
extern const lws_struct_map_t
+ lsm_stay[2],
+ lsm_schema_stay[1],
+ lsm_power_managed_builder[2],
+ lsm_power_managed_builders_list[1],
+ lsm_schema_power_managed_builders[1],
lsm_schema_json_map_task[],
lsm_schema_sq3_map_task[],
lsm_schema_sq3_map_event[],
@@ -561,8 +600,8 @@ extern const lws_struct_map_t
lsm_load_report_members[9]
;
extern const lws_struct_map_t lsm_build_metric[12];
-extern const lws_struct_map_t lsm_plat[8];
-extern const lws_struct_map_t lsm_plat_for_json[11];
+extern const lws_struct_map_t lsm_plat[10];
+extern const lws_struct_map_t lsm_plat_for_json[13];
extern const lws_ss_info_t ssi_said_logproxy;
extern struct lws_ss_handle *ssh[3];
diff --git a/src/common/struct-metadata.c b/src/common/struct-metadata.c
index 2ffe9b3..9ad8618 100644
--- a/src/common/struct-metadata.c
+++ b/src/common/struct-metadata.c
@@ -92,6 +92,8 @@ const lws_struct_map_t lsm_plat[] = { /* !!! keep extern length in common/includ
LSM_CARRAY (sai_plat_t, sai_hash, "sai_hash"),
LSM_CARRAY (sai_plat_t, lws_hash, "lws_hash"),
LSM_UNSIGNED (sai_plat_t, windows, "windows"),
+ LSM_UNSIGNED (sai_plat_t, power_managed, "power_managed"),
+ LSM_UNSIGNED (sai_plat_t, stay_on, "stay_on"),
};
// This is the map for serializing to JSON
@@ -107,6 +109,8 @@ const lws_struct_map_t lsm_plat_for_json[] = {
LSM_CARRAY(sai_plat_t, sai_hash, "sai_hash"),
LSM_CARRAY(sai_plat_t, lws_hash, "lws_hash"),
LSM_UNSIGNED(sai_plat_t, windows, "windows"),
+ LSM_UNSIGNED(sai_plat_t, power_managed, "power_managed"),
+ LSM_UNSIGNED(sai_plat_t, stay_on, "stay_on"),
};
const lws_struct_map_t lsm_schema_map_plat_simple[] = {
@@ -307,7 +311,43 @@ const lws_struct_map_t lsm_power_state[] = {
LSM_SIGNED(sai_power_state_t, powering_down, "powering_down"),
};
-
const lws_struct_map_t lsm_schema_sq3_map_artifact[] = {
LSM_SCHEMA_DLL2 (sai_artifact_t, list, NULL, lsm_artifact, "artifacts"),
};
+
+const lws_struct_map_t lsm_stay[] = {
+ LSM_CARRAY(sai_stay_t, builder_name, "builder_name"),
+ LSM_UNSIGNED(sai_stay_t, stay_on, "stay_on"),
+};
+
+const lws_struct_map_t lsm_schema_stay[] = {
+ LSM_SCHEMA(sai_stay_t, NULL, lsm_stay, "com.warmcat.sai.power.stay"),
+};
+
+
+const lws_struct_map_t lsm_power_managed_builder[] = {
+ LSM_CARRAY(sai_power_managed_builder_t, name, "name"),
+ LSM_UNSIGNED(sai_power_managed_builder_t, stay_on, "stay_on"),
+};
+
+const lws_struct_map_t lsm_power_managed_builders_list[] = {
+ LSM_LIST(sai_power_managed_builders_t, builders,
+ sai_power_managed_builder_t, list, NULL,
+ lsm_power_managed_builder, "builders"),
+};
+
+const lws_struct_map_t lsm_schema_power_managed_builders[] = {
+ LSM_SCHEMA(sai_power_managed_builders_t, NULL,
+ lsm_power_managed_builders_list,
+ "com.warmcat.sai.power_managed_builders"),
+};
+
+const lws_struct_map_t lsm_stay_state_update[] = {
+ LSM_CARRAY(sai_stay_state_update_t, builder_name, "builder_name"),
+ LSM_UNSIGNED(sai_stay_state_update_t, stay_on, "stay_on"),
+};
+
+const lws_struct_map_t lsm_schema_stay_state_update[] = {
+ LSM_SCHEMA(sai_stay_state_update_t, NULL, lsm_stay_state_update,
+ "com.warmcat.sai.stay_state_update"),
+};
diff --git a/src/power/CMakeLists.txt b/src/power/CMakeLists.txt
index 3948ef5..979852e 100644
--- a/src/power/CMakeLists.txt
+++ b/src/power/CMakeLists.txt
@@ -6,6 +6,7 @@ set(SRCS
p-conf.c
p-comms.c
p-smartplug.c
+ p-api.c
)
set(requirements 1)
diff --git a/src/power/p-api.c b/src/power/p-api.c
new file mode 100644
index 0000000..8c9e635
--- /dev/null
+++ b/src/power/p-api.c
@@ -0,0 +1,434 @@
+/*
+ * sai-power
+ *
+ * Copyright (C) 2019 - 2025 Andy Green <andy@warmcat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation:
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ *
+ * This is the h1 API that can be used on the LAN side
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#if defined(__linux__)
+#include <unistd.h>
+#endif
+
+#if defined(__APPLE__)
+#include <sys/stat.h> /* for mkdir() */
+#include <unistd.h> /* for chown() */
+#endif
+
+#include "p-private.h"
+
+extern struct lws_spawn_piped *lsp_wol;
+
+extern struct sai_power power;
+
+
+static void
+saip_sul_action_power_off(struct lws_sorted_usec_list *sul)
+{
+ saip_server_plat_t *sp = lws_container_of(sul,
+ saip_server_plat_t, sul_delay_off);
+
+ if (!sp->power_off_url) {
+ lwsl_notice("%s: no power_off_url for %s\n", __func__, sp->host);
+ return;
+ }
+ if (!sp->ss_tasmota_off) {
+ lwsl_notice("%s: no power_off ss for %s\n", __func__, sp->host);
+ return;
+ }
+
+ lwsl_warn("%s: powering off host %s\n", __func__, sp->host);
+ saip_notify_server_power_state(sp->host, 0, 1);
+
+ if (lws_ss_client_connect(sp->ss_tasmota_off))
+ lwsl_ss_err(sp->ss_tasmota_off, "failed to connect tasmota OFF secure stream");
+}
+
+saip_server_plat_t *
+find_platform(struct sai_power *pwr, const char *host)
+{
+ lws_start_foreach_dll(struct lws_dll2 *, px, pwr->sai_server_owner.head) {
+ saip_server_t *s = lws_container_of(px, saip_server_t, list);
+
+ lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) {
+ saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list);
+
+ if (!strcmp(host, sp->host))
+ return sp;
+
+ } lws_end_foreach_dll(px1);
+ } lws_end_foreach_dll(px);
+
+ return NULL;
+}
+
+void
+saip_notify_server_stay_state(const char *plat_name, int stay_on)
+{
+ saip_server_t *sps;
+ sai_stay_state_update_t *ssu;
+
+ /* Find the first (usually only) configured sai-server connection */
+ if (!power.sai_server_owner.head) {
+ lwsl_warn("%s: No sai-server configured to notify\n", __func__);
+ return;
+ }
+ sps = lws_container_of(power.sai_server_owner.head, saip_server_t, list);
+ if (!sps->ss) {
+ lwsl_warn("%s: Not connected to sai-server to notify\n", __func__);
+ return;
+ }
+
+ /* Allocate and queue the notification message */
+ ssu = malloc(sizeof(*ssu));
+ if (!ssu)
+ return;
+
+ memset(ssu, 0, sizeof(*ssu));
+ lws_strncpy(ssu->builder_name, plat_name, sizeof(ssu->builder_name));
+ ssu->stay_on = (char)stay_on;
+
+ /* The per-connection user object for the server link is a saip_server_link_t */
+ {
+ saip_server_link_t *pss = (saip_server_link_t *)lws_ss_to_user_object(sps->ss);
+ lws_dll2_add_tail(&ssu->list, &pss->stay_state_update_owner);
+ }
+
+ /* Request a writable callback to send the message */
+ if (lws_ss_request_tx(sps->ss))
+ lwsl_ss_warn(sps->ss, "Unable to request tx");
+
+ lwsl_notice("%s: Queued notification for %s\n", __func__, plat_name);
+}
+
+void
+saip_set_stay(const char *builder_name, int stay_on)
+{
+ saip_server_plat_t *sp = find_platform(&power, builder_name);
+ saip_server_link_t *pss;
+ saip_server_t *sps;
+
+ if (!sp)
+ return;
+
+ sp->stay = (char)stay_on;
+ saip_notify_server_stay_state(builder_name, stay_on);
+
+ if (stay_on) {
+ if (sp->power_on_mac) {
+ saip_notify_server_power_state(sp->host, 1, 0);
+ write(lws_spawn_get_fd_stdxxx(lsp_wol, 0),
+ sp->power_on_mac, strlen(sp->power_on_mac));
+ }
+ if (sp->power_on_url) {
+ if (lws_ss_client_connect(sp->ss_tasmota_on))
+ lwsl_notice("%s: tasmota connect failed\n", __func__);
+ saip_notify_server_power_state(sp->host, 1, 0);
+ }
+ } else {
+ /*
+ * power-off is delayed, so we just set the stay flag...
+ * but let's cancel any pending power-off
+ */
+ lws_sul_cancel(&sp->sul_delay_off);
+ }
+
+
+ /* Find the first (usually only) configured sai-server connection */
+ if (!power.sai_server_owner.head) {
+ lwsl_warn("%s: No sai-server configured to notify\n", __func__);
+ return;
+ }
+
+ sps = lws_container_of(power.sai_server_owner.head, saip_server_t, list);
+ pss = (saip_server_link_t *)lws_ss_to_user_object(sps->ss);
+
+ saip_queue_stay_info(sps, sp, pss);
+}
+
+/*
+ * local-side h1 server for builders to connect to
+ */
+
+LWS_SS_USER_TYPEDEF
+ char payload[200];
+ size_t size;
+ size_t pos;
+} local_srv_t;
+
+static lws_ss_state_return_t
+local_srv_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
+ int *flags)
+{
+ local_srv_t *g = (local_srv_t *)userobj;
+ lws_ss_state_return_t r = LWSSSSRET_OK;
+
+ if (g->size == g->pos)
+ return LWSSSSRET_TX_DONT_SEND;
+
+ if (*len > g->size - g->pos)
+ *len = g->size - g->pos;
+
+ if (!g->pos)
+ *flags |= LWSSS_FLAG_SOM;
+
+ memcpy(buf, g->payload + g->pos, *len);
+ g->pos += *len;
+
+ if (g->pos != g->size) /* more to do */
+ r = lws_ss_request_tx(lws_ss_from_user(g));
+ else
+ *flags |= LWSSS_FLAG_EOM;
+
+ lwsl_ss_info(lws_ss_from_user(g), "TX %zu, flags 0x%x, r %d", *len,
+ (unsigned int)*flags, (int)r);
+
+ return r;
+}
+
+static lws_ss_state_return_t
+local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
+ lws_ss_tx_ordinal_t ack)
+{
+ local_srv_t *g = (local_srv_t *)userobj;
+ sai_power_managed_builders_t *pmb;
+ sai_power_managed_builder_t *b;
+ char *path = NULL, pn[128];
+ saip_server_link_t *pss;
+ saip_server_plat_t *sp;
+ saip_server_t *sps;
+ int apo = 0;
+ size_t len;
+
+ // lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name((int)state));
+
+ switch ((int)state) {
+ case LWSSSCS_CREATING:
+ return lws_ss_request_tx(lws_ss_from_user(g));
+
+ case LWSSSCS_SERVER_TXN:
+
+ lws_ss_get_metadata(lws_ss_from_user(g), "path", (const void **)&path, &len);
+ lwsl_ss_user(lws_ss_from_user(g), "LWSSSCS_SERVER_TXN path '%.*s' (%d)", (int)len, path, (int)len);
+
+ /*
+ * path is containing a string like "/power-off/b32"
+ * match the last part to a known platform and find out how
+ * to power that off
+ */
+
+ if (lws_ss_set_metadata(lws_ss_from_user(g), "mime", "text/html", 9))
+ return LWSSSSRET_DISCONNECT_ME;
+
+ /*
+ * A transaction is starting on an accepted connection. Say
+ * that we're OK with the transaction, prepare the user
+ * object with the response, and request tx to start sending it.
+ */
+ lws_ss_server_ack(lws_ss_from_user(g), 0);
+
+ g->pos = 0;
+
+ if (len == 1 && path[0] == '/') {
+ /* print controllable platforms */
+
+ g->size = 0;
+
+ lws_start_foreach_dll(struct lws_dll2 *, px, power.sai_server_owner.head) {
+ saip_server_t *s = lws_container_of(px, saip_server_t, list);
+
+ lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) {
+ saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list);
+
+ if (g->size)
+ g->payload[g->size++] = ',';
+ g->size = g->size + (size_t)lws_snprintf(g->payload + g->size, sizeof(g->payload) - g->size - 3, "%s", sp->host);
+
+ } lws_end_foreach_dll(px1);
+ } lws_end_foreach_dll(px);
+
+ g->payload[g->size] = '\0';
+ goto bail;
+ }
+
+ if (len > 6 && !strncmp(path, "/stay/", 6)) {
+ lws_strnncpy(pn, &path[6], len - 6, sizeof(pn));
+
+ sp = find_platform(&power, pn);
+
+ if (sp)
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "%c", '0' + sp->stay);
+ else
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "unknown host %s", pn);
+ goto bail;
+ }
+
+ if (len > 10 && !strncmp(path, "/power-on/", 10)) {
+ lws_strnncpy(pn, &path[10], len - 10, sizeof(pn));
+ sp = find_platform(&power, pn);
+ if (!sp) {
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "Unable to find host %s", pn);
+ goto bail;
+ }
+ if (sp->power_on_mac) {
+ saip_notify_server_power_state(sp->host, 1, 0);
+ if (write(lws_spawn_get_fd_stdxxx(lsp_wol, 0),
+ sp->power_on_mac, strlen(sp->power_on_mac)) !=
+ (ssize_t)strlen(sp->power_on_mac))
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "Write to resume %s failed %d", pn, errno);
+ else
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "Resumed %s with stay", pn);
+ sp->stay = 1;
+ goto bail;
+ }
+ if (!sp->power_on_url) {
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "no power-on-url entry for %s", pn);
+ goto bail;
+ }
+
+ if (lws_ss_client_connect(sp->ss_tasmota_on)) {
+ lwsl_ss_err(sp->ss_tasmota_off, "failed to connect tasmota ON secure stream");
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "power-on ss failed create %s", sp->host);
+ goto bail;
+ }
+
+ lwsl_warn("%s: powered on host %s\n", __func__, sp->host);
+ sp->stay = 1; /* so builder can understand it's manual */
+ saip_notify_server_power_state(sp->host, 1, 0);
+
+ pmb = malloc(sizeof(*pmb));
+ if (!pmb)
+ return 1;
+ memset(pmb, 0, sizeof(*pmb));
+
+ b = malloc(sizeof(*b));
+ if (!b) {
+ free(pmb);
+ return 1;
+ }
+ memset(b, 0, sizeof(*b));
+
+ lws_strncpy(b->name, sp->host, sizeof(b->name));
+ b->stay_on = sp->stay;
+
+ sps = lws_container_of(power.sai_server_owner.head, saip_server_t, list);
+ pss = (saip_server_link_t *)lws_ss_to_user_object(sps->ss);
+
+ lws_dll2_add_tail(&b->list, &pmb->builders);
+ lws_dll2_add_tail(&pmb->list, &pss->managed_builders_owner);
+
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "Manually powered on %s", sp->host);
+ goto bail;
+ }
+
+ if (len > 16 && !strncmp(path, "/auto-power-off/", 16)) {
+ apo = 1;
+ lws_strnncpy(pn, &path[16], len - 16, sizeof(pn));
+ goto power_off;
+ }
+
+ if (len < 11 || strncmp(path, "/power-off/", 11)) {
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "URL path needs to start with /power-off/");
+ goto bail;
+ }
+
+ lws_strnncpy(pn, &path[11], len - 11, sizeof(pn));
+
+power_off:
+
+ /*
+ * Let's have a look at the platform
+ */
+
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "Unable to find host %s", pn);
+
+ sp = find_platform(&power, pn);
+ if (sp) {
+
+ if (apo) {
+ char needs[128];
+
+ /*
+ * Since it's not a manual request,
+ * we should deny it if any deps still need us
+ */
+
+ needs[0] = '\0';
+ lws_start_foreach_dll(struct lws_dll2 *, px1, sp->dependencies_owner.head) {
+ saip_server_plat_t *sp1 = lws_container_of(px1, saip_server_plat_t, dependencies_list);
+
+ if (sp1->needed)
+ lws_snprintf(needs, sizeof(needs) - 1 - strlen(needs), "%s ", sp1->name);
+
+ } lws_end_foreach_dll(px1);
+
+ if (needs[0] || sp->needed) {
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "NAK: %s needed: %d, deps needed: '%s'",
+ pn, sp->needed, needs);
+ goto bail;
+ }
+ }
+
+ /*
+ * OK this is it, schedule it to happen
+ */
+ lws_sul_schedule(lws_ss_cx_from_user(g), 0,
+ &sp->sul_delay_off,
+ saip_sul_action_power_off,
+ 3 * LWS_USEC_PER_SEC);
+
+ lwsl_warn("%s: scheduled powering off host %s\n",
+ __func__, sp->host);
+
+ g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
+ "ACK: Scheduled powering off host %s", sp->host);
+
+ sp->stay = 0; /* reset any manual power up */
+ }
+
+bail:
+ return lws_ss_request_tx_len(lws_ss_from_user(g),
+ (unsigned long)g->size);
+ }
+
+ return LWSSSSRET_OK;
+}
+
+
+LWS_SS_INFO("local", local_srv_t)
+ .tx = local_srv_tx,
+ .state = local_srv_state,
+};
diff --git a/src/power/p-comms.c b/src/power/p-comms.c
index 4ace8e7..b6523ba 100644
--- a/src/power/p-comms.c
+++ b/src/power/p-comms.c
@@ -35,14 +35,6 @@ static const lws_struct_map_t lsm_schema_power_state[] = {
"com.warmcat.sai.powerstate"),
};
-LWS_SS_USER_TYPEDEF
- char payload[200];
- size_t size;
- size_t pos;
-
- lws_dll2_owner_t ps_owner;
-} saip_server_link_t;
-
void
saip_notify_server_power_state(const char *plat_name, int up, int down)
{
@@ -83,6 +75,59 @@ saip_notify_server_power_state(const char *plat_name, int up, int down)
lwsl_notice("%s: Queued notification for %s\n", __func__, plat_name);
}
+int
+saip_queue_stay_info(saip_server_t *sps, saip_server_plat_t *sp, saip_server_link_t *pss)
+{
+ sai_power_managed_builders_t *pmb;
+ sai_power_managed_builder_t *b;
+
+ pmb = malloc(sizeof(*pmb));
+ if (!pmb)
+ return 1;
+
+ memset(pmb, 0, sizeof(*pmb));
+
+ /* queue the update for the builder state */
+
+ b = malloc(sizeof(*b));
+ if (!b) {
+ free(pmb);
+ return 1;
+ }
+
+ memset(b, 0, sizeof(*b));
+ lws_strncpy(b->name, sp->host, sizeof(b->name));
+ b->stay_on = sp->stay;
+
+ lws_dll2_add_tail(&b->list, &pmb->builders);
+ lws_dll2_add_tail(&pmb->list, &pss->managed_builders_owner);
+
+ if (lws_ss_request_tx(sps->ss))
+ lwsl_ss_warn(sps->ss, "Unable to request tx");
+
+ return 0;
+}
+
+int
+saip_builder_bringup(saip_server_t *sps, saip_server_plat_t *sp, saip_server_link_t *pss)
+{
+ saip_notify_server_power_state(sp->name, 1, 0);
+
+ if (!strcmp(sp->power_on_type, "wol")) {
+ lwsl_notice("%s: triggering WOL\n", __func__);
+ write(lws_spawn_get_fd_stdxxx(lsp_wol, 0),
+ sp->power_on_mac, strlen(sp->power_on_mac));
+ }
+
+ if (!strcmp(sp->power_on_type, "tasmota")) {
+ lwsl_ss_notice(sp->ss_tasmota_on, "starting tasmota");
+ if (lws_ss_client_connect(sp->ss_tasmota_on))
+ lwsl_ss_err(sp->ss_tasmota_on, "failed to connect tasmota ON secure stream");
+ }
+
+ return saip_queue_stay_info(sps, sp, pss);
+}
+
static lws_ss_state_return_t
saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
@@ -91,10 +136,29 @@ saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
const char *p = (const char *)buf, *end = (const char *)buf + len;
char plat[128], benched[4096];
size_t n, bp = 0;
+ lws_struct_args_t a;
+ struct lejp_ctx ctx;
lwsl_info("%s: len %d, flags: %d (saip_server_t %p)\n", __func__, (int)len, flags, (void *)sps);
lwsl_hexdump_info(buf, len);
+ memset(&a, 0, sizeof(a));
+ a.map_st[0] = lsm_schema_stay;
+ a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_stay);
+ a.ac_block_size = 512;
+
+ lws_struct_json_init_parse(&ctx, NULL, &a);
+ if (lejp_parse(&ctx, (uint8_t *)buf, (int)len) >= 0 && a.dest) {
+ sai_stay_t *stay = (sai_stay_t *)a.dest;
+
+ lwsl_warn("%s: received stay %s: %d\n", __func__, stay->builder_name, stay->stay_on);
+
+ saip_set_stay(stay->builder_name, stay->stay_on);
+ lwsac_free(&a.ac);
+ return 0;
+ }
+ lwsac_free(&a.ac);
+
lws_start_foreach_dll(struct lws_dll2 *, px, sps->sai_plat_owner.head) {
saip_server_plat_t *sp = lws_container_of(px, saip_server_plat_t, list);
sp->needed = 0;
@@ -148,25 +212,14 @@ saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
if (sp->needed) {
lwsl_notice("%s: Needed builders: %s\n", __func__, sp->name);
- saip_notify_server_power_state(sp->name, 1, 0);
-
/*
* Server said this platform or at least one dependency
* has pending jobs. sai-power config says this builder
* can do jobs on that platform. Let's make sure it
* is powered on.
*/
- if (!strcmp(sp->power_on_type, "wol")) {
- lwsl_notice("%s: triggering WOL\n", __func__);
- write(lws_spawn_get_fd_stdxxx(lsp_wol, 0),
- sp->power_on_mac, strlen(sp->power_on_mac));
- }
-
- if (!strcmp(sp->power_on_type, "tasmota")) {
- lwsl_ss_notice(sp->ss_tasmota_on, "starting tasmota");
- if (lws_ss_client_connect(sp->ss_tasmota_on))
- lwsl_ss_err(sp->ss_tasmota_on, "failed to connect tasmota ON secure stream");
- }
+
+ saip_builder_bringup(sps, sp, pss);
} else {
bp += (size_t)lws_snprintf(&benched[bp], sizeof(benched) - bp - 1, "%s%s", !bp ? "" : ", ", sp->name);
@@ -178,8 +231,6 @@ saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
if (bp)
lwsl_notice("%s: Benched builders: %s\n", __func__, benched);
-
-
(void)sps;
return 0;
@@ -192,26 +243,68 @@ saip_m_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
saip_server_link_t *pss = (saip_server_link_t *)userobj;
lws_struct_serialize_t *js;
- if (!pss->ps_owner.head)
- return LWSSSSRET_TX_DONT_SEND;
+ if (pss->managed_builders_owner.head) {
+ sai_power_managed_builders_t *pmb = lws_container_of(pss->managed_builders_owner.head,
+ sai_power_managed_builders_t, list);
+
+ js = lws_struct_json_serialize_create(lsm_schema_power_managed_builders,
+ LWS_ARRAY_SIZE(lsm_schema_power_managed_builders), 0, pmb);
+ if (!js)
+ lwsl_ss_warn(lws_ss_from_user(pss), "Failed to serialize managed builder");
+ else
+ /* crashes here walking pmb */
+ if (lws_struct_json_serialize(js, buf, *len, len) == LSJS_RESULT_FINISH)
+ *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM;
+
+ lws_dll2_remove(&pmb->list);
+ free(pmb);
+ goto sendify;
+ }
+
+ if (pss->stay_state_update_owner.head) {
+ sai_stay_state_update_t *ssu = lws_container_of(pss->stay_state_update_owner.head,
+ sai_stay_state_update_t, list);
+ js = lws_struct_json_serialize_create(lsm_schema_stay_state_update,
+ LWS_ARRAY_SIZE(lsm_schema_stay_state_update), 0, ssu);
+ if (!js)
+ lwsl_ss_warn(lws_ss_from_user(pss), "Failed to serialize state update");
+ else
+ if (lws_struct_json_serialize(js, buf, *len, len) == LSJS_RESULT_FINISH)
+ *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM;
+
+ lws_dll2_remove(&ssu->list);
+ free(ssu);
+ goto sendify;
+ }
- /* Dequeue the first pending notification */
- sai_power_state_t *ps = lws_container_of(pss->ps_owner.head,
- sai_power_state_t, list);
+ if (pss->ps_owner.head) {
+ /* Dequeue the first pending notification */
+ sai_power_state_t *ps = lws_container_of(pss->ps_owner.head, sai_power_state_t, list);
+
+ js = lws_struct_json_serialize_create(lsm_schema_power_state, 1, 0, ps);
+ if (!js)
+ lwsl_ss_warn(lws_ss_from_user(pss), "Failed to serialize state update");
+ else
+ if (lws_struct_json_serialize(js, buf, *len, len) == LSJS_RESULT_FINISH)
+ *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM;
- js = lws_struct_json_serialize_create(lsm_schema_power_state, 1, 0, ps);
- if (js && lws_struct_json_serialize(js, buf, *len, len) == LSJS_RESULT_FINISH) {
- *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM;
lws_dll2_remove(&ps->list);
free(ps);
+ goto sendify;
}
+
+ return LWSSSSRET_TX_DONT_SEND;
+
+sendify:
lws_struct_json_serialize_destroy(&js);
/* If there are more to send, request another writable callback */
- if (pss->ps_owner.head)
+ if (pss->ps_owner.head || pss->managed_builders_owner.head || pss->stay_state_update_owner.head)
if (lws_ss_request_tx(lws_ss_from_user(pss)))
lwsl_ss_warn(lws_ss_from_user(pss), "tx request failed");
+ lwsl_hexdump_notice(buf, *len);
+
return LWSSSSRET_OK;
}
@@ -292,9 +385,37 @@ saip_m_state(void *userobj, void *sh, lws_ss_constate_t state,
break;
case LWSSSCS_CONNECTED:
- lwsl_info("%s: CONNECTED: %p\n", __func__, sps->ss);
- return lws_ss_request_tx(sps->ss);
+ {
+ saip_server_link_t *pss = (saip_server_link_t *)userobj;
+ sai_power_managed_builders_t *pmb = malloc(sizeof(*pmb));
+
+ lwsl_ss_notice(sps->ss, "@@@@@@@@@@@@@@ sai-power CONNECTED to server");
+
+ if (!pmb)
+ return LWSSSSRET_DISCONNECT_ME;
+
+ memset(pmb, 0, sizeof(*pmb));
+
+ lws_start_foreach_dll(struct lws_dll2 *, p,
+ sps->sai_plat_owner.head) {
+ saip_server_plat_t *sp = lws_container_of(p,
+ saip_server_plat_t, list);
+ sai_power_managed_builder_t *b = malloc(sizeof(*b));
+ if (!b)
+ continue;
+
+ memset(b, 0, sizeof(*b));
+ lws_strncpy(b->name, sp->host, sizeof(b->name));
+ b->stay_on = sp->stay;
+
+ lws_dll2_add_tail(&b->list, &pmb->builders);
+ } lws_end_foreach_dll(p);
+
+ lws_dll2_add_tail(&pmb->list, &pss->managed_builders_owner);
+
+ return lws_ss_request_tx(sps->ss);
+ }
case LWSSSCS_DISCONNECTED:
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, pss->ps_owner.head) {
sai_power_state_t *ps = lws_container_of(d, sai_power_state_t, list);
diff --git a/src/power/p-intake.c b/src/power/p-intake.c
deleted file mode 100644
index 01a3ef7..0000000
--- a/src/power/p-intake.c
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Sai server
- *
- * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation:
- * version 2.1 of the License.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301 USA
- *
- * This is a ws server run by sai-power, which accepts JSON config from builders
- * that want to use it as a helper for managing their power state.
- */
-
-#include <libwebsockets.h>
-#include <string.h>
-#include <signal.h>
-#include <time.h>
-#include <stdio.h>
-#include <fcntl.h>
-
-#include "p-private.h"
-
-#include "../common/struct-metadata.c"
-
-
-static int
-p_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
- void *in, size_t len)
-{
- struct vhd *vhd = (struct vhd *)lws_protocol_vh_priv_get(
- lws_get_vhost(wsi), lws_get_protocol(wsi));
- uint8_t buf[LWS_PRE + 8192], *start = &buf[LWS_PRE], *p = start,
- *end = &buf[sizeof(buf) - LWS_PRE - 1];
- struct pss *pss = (struct pss *)user;
- const char *pvo_resources;
- int n;
-
- (void)end;
- (void)p;
-
- switch (reason) {
- case LWS_CALLBACK_PROTOCOL_INIT:
-
- break;
-
- /*
- * ws connections from builders
- */
-
- case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
- return 0;
-
- case LWS_CALLBACK_ESTABLISHED:
- pss->wsi = wsi;
- pss->vhd = vhd;
- if (!vhd)
- return -1;
-
- if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) {
- if (lws_hdr_copy(wsi, (char *)start, 64,
- WSI_TOKEN_GET_URI) < 0)
- return -1;
- }
-#if defined(LWS_ROLE_H2)
- else
- if (lws_hdr_copy(wsi, (char *)start, 64,
- WSI_TOKEN_HTTP_COLON_PATH) < 0)
- return -1;
-#endif
-
- if (!memcmp((char *)start, "/sai", 4))
- start += 4;
-
- if (!strcmp((char *)start, "/builder")) {
- lwsl_info("%s: ESTABLISHED: builder\n", __func__);
- pss->wsi = wsi;
- /*
- * this adds our pss part, but not the logical builder
- * yet, until we get the ws rx
- */
- lws_dll2_add_head(&pss->same, &vhd->builders);
- break;
- }
-
- lwsl_err("%s: unknown URL '%s'\n", __func__, start);
-
- return -1;
-
- case LWS_CALLBACK_CLOSED:
- lwsac_free(&pss->query_ac);
-
- lwsl_wsi_user(wsi, "CLOSED builder->power connection", __func__);
- /* remove pss from vhd->builders */
- lws_dll2_remove(&pss->same);
-
- /*
- * Destroy any the builder-tracking objects that
- * were using this departing connection
- */
-
- lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
- vhd->server.builder_owner.head) {
- sai_plat_t *cb = lws_container_of(p, sai_plat_t,
- sai_plat_list);
-
- if (cb->wsi == wsi) {
- /* remove builder object itself from server list */
- cb->wsi = NULL;
- lws_dll2_remove(&cb->sai_plat_list);
- /*
- * free the deserialized builder object,
- * everything he pointed to was overallocated
- * when his deep copy was made
- */
- free(cb);
- }
-
- } lws_end_foreach_dll_safe(p, p1);
-
- sais_resource_wellknown_remove_pss(&pss->vhd->server, pss);
-
- if (pss->blob_artifact) {
- sqlite3_blob_close(pss->blob_artifact);
- pss->blob_artifact = NULL;
- }
-
- if (pss->pdb_artifact) {
- sais_event_db_close(pss->vhd, &pss->pdb_artifact);
- pss->pdb_artifact = NULL;
- }
-
- /*
- * Update the sai-webs about the builder removal, so they
- * can update their connected browsers
- */
- lwsl_wsi_warn(wsi, "LWS_CALLBACK_CLOSED: doing WSS_PREPARE_BUILDER_SUMMARY\n");
-
- sais_list_builders(vhd);
- break;
-
- case LWS_CALLBACK_RECEIVE:
- break;
-
- case LWS_CALLBACK_SERVER_WRITEABLE:
- if (!vhd) {
- lwsl_notice("%s: no vhd\n", __func__);
- break;
- }
-
- return sais_ws_json_tx_builder(vhd, pss, buf, sizeof(buf));
-
- default:
-passthru:
- break;
- }
-
- return lws_callback_http_dummy(wsi, reason, user, in, len);
-}
-
-const struct lws_protocols protocol_ws_power =
- { "com-warmcat-sai-power", p_callback_ws, sizeof(struct pss), 0 };
diff --git a/src/power/p-private.h b/src/power/p-private.h
index 390d071..822e39a 100644
--- a/src/power/p-private.h
+++ b/src/power/p-private.h
@@ -132,6 +132,18 @@ struct jpargs {
int next_plat_index;
};
+LWS_SS_USER_TYPEDEF
+ char payload[200];
+ size_t size;
+ size_t pos;
+
+ lws_dll2_owner_t ps_owner;
+ lws_dll2_owner_t managed_builders_owner;
+ lws_dll2_owner_t stay_state_update_owner;
+} saip_server_link_t;
+
+
+
extern struct sai_power power;
extern const lws_ss_info_t ssi_saip_server_link_t, ssi_saip_smartplug_t;
extern const struct lws_protocols protocol_com_warmcat_sai, protocol_ws_power;
@@ -143,3 +155,7 @@ extern void saip_config_destroy(struct sai_power *power);
extern void
saip_notify_server_power_state(const char *plat_name, int up, int down);
+void
+saip_set_stay(const char *builder_name, int stay_on);
+int
+saip_queue_stay_info(saip_server_t *sps, saip_server_plat_t *sp, saip_server_link_t *pss);
diff --git a/src/power/p-sai.c b/src/power/p-sai.c
index 0a372ff..cf9ee95 100644
--- a/src/power/p-sai.c
+++ b/src/power/p-sai.c
@@ -81,6 +81,8 @@ struct lws_spawn_piped *lsp_wol;
struct sai_power power;
+extern const lws_ss_info_t ssi_local_srv_t;
+
static const char * const default_ss_policy =
"{"
"\"retry\": [" /* named backoff / retry strategies */
@@ -183,289 +185,6 @@ static const struct lws_protocols *pprotocols[] = {
NULL
};
-static void
-saip_sul_action_power_off(struct lws_sorted_usec_list *sul)
-{
- saip_server_plat_t *sp = lws_container_of(sul,
- saip_server_plat_t, sul_delay_off);
-
- if (!sp->power_off_url) {
- lwsl_notice("%s: no power_off_url for %s\n", __func__, sp->host);
- return;
- }
- if (!sp->ss_tasmota_off) {
- lwsl_notice("%s: no power_off ss for %s\n", __func__, sp->host);
- return;
- }
-
- lwsl_warn("%s: powering off host %s\n", __func__, sp->host);
- saip_notify_server_power_state(sp->host, 0, 1);
-
- if (lws_ss_client_connect(sp->ss_tasmota_off))
- lwsl_ss_err(sp->ss_tasmota_off, "failed to connect tasmota OFF secure stream");
-}
-
-saip_server_plat_t *
-find_platform(struct sai_power *pwr, const char *host)
-{
- lws_start_foreach_dll(struct lws_dll2 *, px, pwr->sai_server_owner.head) {
- saip_server_t *s = lws_container_of(px, saip_server_t, list);
-
- lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) {
- saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list);
-
- if (!strcmp(host, sp->host))
- return sp;
-
- } lws_end_foreach_dll(px1);
- } lws_end_foreach_dll(px);
-
- return NULL;
-}
-
-
-/*
- * local-side h1 server for builders to connect to
- */
-
-LWS_SS_USER_TYPEDEF
- char payload[200];
- size_t size;
- size_t pos;
-} local_srv_t;
-
-static lws_ss_state_return_t
-local_srv_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
- int *flags)
-{
- local_srv_t *g = (local_srv_t *)userobj;
- lws_ss_state_return_t r = LWSSSSRET_OK;
-
- if (g->size == g->pos)
- return LWSSSSRET_TX_DONT_SEND;
-
- if (*len > g->size - g->pos)
- *len = g->size - g->pos;
-
- if (!g->pos)
- *flags |= LWSSS_FLAG_SOM;
-
- memcpy(buf, g->payload + g->pos, *len);
- g->pos += *len;
-
- if (g->pos != g->size) /* more to do */
- r = lws_ss_request_tx(lws_ss_from_user(g));
- else
- *flags |= LWSSS_FLAG_EOM;
-
- lwsl_ss_info(lws_ss_from_user(g), "TX %zu, flags 0x%x, r %d", *len,
- (unsigned int)*flags, (int)r);
-
- return r;
-}
-
-static lws_ss_state_return_t
-local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
- lws_ss_tx_ordinal_t ack)
-{
- local_srv_t *g = (local_srv_t *)userobj;
- char *path = NULL, pn[128];
- saip_server_plat_t *sp;
- int apo = 0;
- size_t len;
-
- // lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name((int)state));
-
- switch ((int)state) {
- case LWSSSCS_CREATING:
- return lws_ss_request_tx(lws_ss_from_user(g));
-
- case LWSSSCS_SERVER_TXN:
-
- lws_ss_get_metadata(lws_ss_from_user(g), "path", (const void **)&path, &len);
- lwsl_ss_user(lws_ss_from_user(g), "LWSSSCS_SERVER_TXN path '%.*s' (%d)", (int)len, path, (int)len);
-
- /*
- * path is containing a string like "/power-off/b32"
- * match the last part to a known platform and find out how
- * to power that off
- */
-
- if (lws_ss_set_metadata(lws_ss_from_user(g), "mime", "text/html", 9))
- return LWSSSSRET_DISCONNECT_ME;
-
- /*
- * A transaction is starting on an accepted connection. Say
- * that we're OK with the transaction, prepare the user
- * object with the response, and request tx to start sending it.
- */
- lws_ss_server_ack(lws_ss_from_user(g), 0);
-
- g->pos = 0;
-
- if (len == 1 && path[0] == '/') {
- /* print controllable platforms */
-
- g->size = 0;
-
- lws_start_foreach_dll(struct lws_dll2 *, px, power.sai_server_owner.head) {
- saip_server_t *s = lws_container_of(px, saip_server_t, list);
-
- lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) {
- saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list);
-
- if (g->size)
- g->payload[g->size++] = ',';
- g->size = g->size + (size_t)lws_snprintf(g->payload + g->size, sizeof(g->payload) - g->size - 3, "%s", sp->host);
-
- } lws_end_foreach_dll(px1);
- } lws_end_foreach_dll(px);
-
- g->payload[g->size] = '\0';
- goto bail;
- }
-
- if (len > 6 && !strncmp(path, "/stay/", 6)) {
- lws_strnncpy(pn, &path[6], len - 6, sizeof(pn));
-
- sp = find_platform(&power, pn);
-
- if (sp)
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "%c", '0' + sp->stay);
- else
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "unknown host %s", pn);
- goto bail;
- }
-
- if (len > 10 && !strncmp(path, "/power-on/", 10)) {
- lws_strnncpy(pn, &path[10], len - 10, sizeof(pn));
- sp = find_platform(&power, pn);
- if (!sp) {
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "Unable to find host %s", pn);
- goto bail;
- }
- if (sp->power_on_mac) {
- saip_notify_server_power_state(sp->host, 1, 0);
- if (write(lws_spawn_get_fd_stdxxx(lsp_wol, 0),
- sp->power_on_mac, strlen(sp->power_on_mac)) !=
- (ssize_t)strlen(sp->power_on_mac))
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "Write to resume %s failed %d", pn, errno);
- else
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "Resumed %s with stay", pn);
- sp->stay = 1;
- goto bail;
- }
- if (!sp->power_on_url) {
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "no power-on-url entry for %s", pn);
- goto bail;
- }
-
- if (lws_ss_client_connect(sp->ss_tasmota_on)) {
- lwsl_ss_err(sp->ss_tasmota_off, "failed to connect tasmota ON secure stream");
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "power-on ss failed create %s", sp->host);
- goto bail;
- }
-
- lwsl_warn("%s: powered on host %s\n", __func__, sp->host);
- saip_notify_server_power_state(sp->host, 1, 0);
-
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "Manually powered on %s", sp->host);
-
- sp->stay = 1; /* so builder can understand it's manual */
- goto bail;
- }
-
- if (len > 16 && !strncmp(path, "/auto-power-off/", 16)) {
- apo = 1;
- lws_strnncpy(pn, &path[16], len - 16, sizeof(pn));
- goto power_off;
- }
-
- if (len < 11 || strncmp(path, "/power-off/", 11)) {
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "URL path needs to start with /power-off/");
- goto bail;
- }
-
- lws_strnncpy(pn, &path[11], len - 11, sizeof(pn));
-
-power_off:
-
- /*
- * Let's have a look at the platform
- */
-
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "Unable to find host %s", pn);
-
- sp = find_platform(&power, pn);
- if (sp) {
-
- if (apo) {
- char needs[128];
-
- /*
- * Since it's not a manual request,
- * we should deny it if any deps still need us
- */
-
- needs[0] = '\0';
- lws_start_foreach_dll(struct lws_dll2 *, px1, sp->dependencies_owner.head) {
- saip_server_plat_t *sp1 = lws_container_of(px1, saip_server_plat_t, dependencies_list);
-
- if (sp1->needed)
- lws_snprintf(needs, sizeof(needs) - 1 - strlen(needs), "%s ", sp1->name);
-
- } lws_end_foreach_dll(px1);
-
- if (needs[0] || sp->needed) {
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "NAK: %s needed: %d, deps needed: '%s'",
- pn, sp->needed, needs);
- goto bail;
- }
- }
-
- /*
- * OK this is it, schedule it to happen
- */
- lws_sul_schedule(lws_ss_cx_from_user(g), 0,
- &sp->sul_delay_off,
- saip_sul_action_power_off,
- 3 * LWS_USEC_PER_SEC);
-
- lwsl_warn("%s: scheduled powering off host %s\n",
- __func__, sp->host);
-
- g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "ACK: Scheduled powering off host %s", sp->host);
-
- sp->stay = 0; /* reset any manual power up */
- }
-
-bail:
- return lws_ss_request_tx_len(lws_ss_from_user(g),
- (unsigned long)g->size);
- }
-
- return LWSSSSRET_OK;
-}
-
-
-LWS_SS_INFO("local", local_srv_t)
- .tx = local_srv_tx,
- .state = local_srv_state,
-};
-
-
static int
app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
int current, int target)
diff --git a/src/server/s-comms.c b/src/server/s-comms.c
index e122be3..53c4e44 100644
--- a/src/server/s-comms.c
+++ b/src/server/s-comms.c
@@ -740,7 +740,7 @@ s_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
}
if (!strcmp((char *)start, "/power")) {
- lwsl_info("%s: ESTABLISHED: power\n", __func__);
+ lwsl_notice("%s: ESTABLISHED: power connection\n", __func__);
pss->wsi = wsi;
pss->is_power = 1;
lws_dll2_add_head(&pss->same, &vhd->sai_powers);
@@ -793,17 +793,23 @@ s_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
struct lejp_ctx ctx;
lws_struct_args_t a;
sai_power_state_t *ps;
- const lws_struct_map_t lsm_schema_power_state[] = {
+ const lws_struct_map_t lsm_schema_map_power[] = {
LSM_SCHEMA(sai_power_state_t, NULL, lsm_power_state,
"com.warmcat.sai.powerstate"),
+ LSM_SCHEMA(sai_power_managed_builders_t, NULL,
+ lsm_power_managed_builders_list,
+ "com.warmcat.sai.power_managed_builders"),
+ LSM_SCHEMA(sai_stay_state_update_t, NULL,
+ lsm_stay_state_update,
+ "com.warmcat.sai.stay_state_update"),
};
/* This is a message from sai-power */
lwsl_notice("RX from sai-power: %.*s\n", (int)len, (const char *)in);
memset(&a, 0, sizeof(a));
- a.map_st[0] = lsm_schema_power_state;
- a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_power_state);
+ a.map_st[0] = lsm_schema_map_power;
+ a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_map_power);
a.ac_block_size = 512;
lws_struct_json_init_parse(&ctx, NULL, &a);
@@ -813,17 +819,79 @@ s_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
break; // Exit case
}
- ps = (sai_power_state_t *)a.dest;
- if (ps->powering_up) {
- lwsl_notice("sai-power is powering up: %s\n", ps->host);
- sais_set_builder_power_state(vhd, ps->host, 1, 0);
- } else if (ps->powering_down) {
- lwsl_notice("sai-power is powering down: %s\n", ps->host);
- sais_set_builder_power_state(vhd, ps->host, 0, 1);
+ switch (a.top_schema_index) {
+ case 0: /* powerstate */
+ ps = (sai_power_state_t *)a.dest;
+ if (ps->powering_up) {
+ lwsl_notice("sai-power is powering up: %s\n", ps->host);
+ sais_set_builder_power_state(vhd, ps->host, 1, 0);
+ } else if (ps->powering_down) {
+ lwsl_notice("sai-power is powering down: %s\n", ps->host);
+ sais_set_builder_power_state(vhd, ps->host, 0, 1);
+ }
+ break;
+
+ case 1: {
+ sai_power_managed_builders_t *pmb = (sai_power_managed_builders_t *)a.dest;
+
+ lws_start_foreach_dll(struct lws_dll2 *, p, pmb->builders.head) {
+ sai_power_managed_builder_t *b = lws_container_of(p,
+ sai_power_managed_builder_t, list);
+ char q[256];
+ lwsl_notice("%s: Marking builder %s as power-managed\n",
+ __func__, b->name);
+ lws_snprintf(q, sizeof(q),
+ "UPDATE builders SET power_managed=1 WHERE name = '%s' OR name LIKE '%s.%%'",
+ b->name, b->name);
+ if (sai_sqlite3_statement(vhd->server.pdb, q, "set power_managed"))
+ lwsl_err("%s: Failed to mark builder %s as power-managed\n",
+ __func__, b->name);
+
+ sai_plat_t *cb;
+ lws_start_foreach_dll(struct lws_dll2 *, p2,
+ vhd->server.builder_owner.head) {
+ cb = lws_container_of(p2, sai_plat_t,
+ sai_plat_list);
+ const char *dot = strchr(cb->name, '.');
+ if (dot && !strncmp(cb->name, b->name, (size_t)(dot - cb->name)))
+ cb->stay_on = b->stay_on;
+ } lws_end_foreach_dll(p2);
+
+ } lws_end_foreach_dll(p);
+
+ sais_list_builders(vhd);
+
+ break;
+ }
+ case 2: {
+ sai_stay_state_update_t *ssu = (sai_stay_state_update_t *)a.dest;
+ sai_plat_t *cb;
+
+ lwsl_notice("%s: Received stay_state_update for %s, stay_on=%d\n",
+ __func__, ssu->builder_name, ssu->stay_on);
+
+ lws_start_foreach_dll(struct lws_dll2 *, p,
+ vhd->server.builder_owner.head) {
+ cb = lws_container_of(p, sai_plat_t,
+ sai_plat_list);
+
+ const char *dot = strchr(cb->name, '.');
+
+ if (dot && !strncmp(cb->name, ssu->builder_name, (size_t)(dot - cb->name))) {
+ lwsl_notice("%s: Updating builder %s stay_on from %d to %d\n",
+ __func__, cb->name, cb->stay_on, ssu->stay_on);
+ cb->stay_on = ssu->stay_on;
+ sais_list_builders(vhd);
+ break;
+ }
+ } lws_end_foreach_dll(p);
+
+ break;
+ }
}
lwsac_free(&a.ac);
- break; // Exit case
+ break;
}
/*
diff --git a/src/server/s-private.h b/src/server/s-private.h
index 57d6b9c..7707193 100644
--- a/src/server/s-private.h
+++ b/src/server/s-private.h
@@ -112,6 +112,7 @@ struct pss {
lws_dll2_owner_t platform_owner; /* sai_platform_t builder offers */
lws_dll2_owner_t task_cancel_owner; /* sai_platform_t builder offers */
lws_dll2_owner_t rebuild_owner;
+ lws_dll2_owner_t stay_owner;
lws_dll2_owner_t aft_owner; /* for statefully spooling artifact info */
lws_dll2_owner_t res_owner; /* sai_resource_requisition_t
* owner of resource objects related
diff --git a/src/server/s-websrv.c b/src/server/s-websrv.c
index a0a4635..89835d6 100644
--- a/src/server/s-websrv.c
+++ b/src/server/s-websrv.c
@@ -90,6 +90,8 @@ static const lws_struct_map_t lsm_schema_json_map[] = {
"com.warmcat.sai.rebuild"),
LSM_SCHEMA (sai_browse_rx_platreset_t, NULL, lsm_browser_platreset,
"com.warmcat.sai.platreset"),
+ LSM_SCHEMA (sai_stay_t, NULL, lsm_stay,
+ "com.warmcat.sai.stay"),
};
enum {
@@ -101,6 +103,7 @@ enum {
SAIS_WS_WEBSRV_RX_VIEWERCOUNT,
SAIS_WS_WEBSRV_RX_REBUILD,
SAIS_WS_WEBSRV_RX_PLATRESET,
+ SAIS_WS_WEBSRV_RX_STAY,
};
void
@@ -218,7 +221,6 @@ sais_websrv_broadcast(struct lws_ss_handle *hsrv, const char *str, size_t len)
int
sais_list_builders(struct vhd *vhd)
{
- lwsl_warn("%s: ENTRY\n", __func__);
lws_dll2_owner_t db_builders_owner;
struct lwsac *ac = NULL;
char *p = vhd->json_builders, *end = p + sizeof(vhd->json_builders),
@@ -253,12 +255,20 @@ sais_list_builders(struct vhd *vhd)
live_builder = sais_builder_from_uuid(vhd, builder_from_db->name, __FILE__, __LINE__);
if (live_builder) {
+ lwsl_notice("%s: live_builder %s found, stay_on: %d, copying to db_builder (stay_on: %d)\n",
+ __func__, live_builder->name, live_builder->stay_on, builder_from_db->stay_on);
builder_from_db->online = 1;
lws_strncpy(builder_from_db->peer_ip, live_builder->peer_ip,
sizeof(builder_from_db->peer_ip));
+ builder_from_db->stay_on = live_builder->stay_on;
} else
builder_from_db->online = 0;
+ if (builder_from_db->power_managed)
+ lwsl_notice("%s: builder %s is power managed (stay: %d)\n",
+ __func__, builder_from_db->name,
+ builder_from_db->stay_on);
+
builder_from_db->powering_up = 0;
builder_from_db->powering_down = 0;
@@ -296,6 +306,7 @@ sais_list_builders(struct vhd *vhd)
p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "]}");
+ lwsl_notice("%s: Broadcasting builder list: %s\n", __func__, vhd->json_builders);
sais_websrv_broadcast(vhd->h_ss_websrv, vhd->json_builders,
lws_ptr_diff_size_t(p, vhd->json_builders));
@@ -741,6 +752,25 @@ websrvss_ws_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
} lws_end_foreach_dll(p);
}
break;
+
+ case SAIS_WS_WEBSRV_RX_STAY:
+ {
+ sai_stay_t *stay = (sai_stay_t *)a.dest;
+
+ lws_start_foreach_dll(struct lws_dll2 *, p,
+ m->vhd->sai_powers.head) {
+ struct pss *pss_power = lws_container_of(p, struct pss, same);
+ sai_stay_t *s = malloc(sizeof(*s));
+ if (s) {
+ *s = *stay;
+ lws_dll2_add_tail(&s->list, &pss_power->stay_owner);
+ lws_callback_on_writable(pss_power->wsi);
+ }
+ } lws_end_foreach_dll(p);
+
+ lwsac_free(&a.ac);
+ break;
+ }
}
return 0;
diff --git a/src/server/s-ws-builder.c b/src/server/s-ws-builder.c
index 8d35e54..c02b6d3 100644
--- a/src/server/s-ws-builder.c
+++ b/src/server/s-ws-builder.c
@@ -522,16 +522,21 @@ handle:
sai_plat_t *live_cb;
/*
- * Step 1: Upsert this platform into the persistent database.
+ * Step 1: Update this platform in the persistent database.
*/
- build->online = 1;
- build->last_seen = (uint64_t)lws_now_secs();
- lws_strncpy(build->peer_ip, pss->peer_ip, sizeof(build->peer_ip));
- if (lws_struct_sq3_upsert(vhd->server.pdb, "builders", lsm_plat,
- LWS_ARRAY_SIZE(lsm_plat), build, "name")) {
+ char q[1024];
+
+ lws_snprintf(q, sizeof(q),
+ "INSERT INTO builders (name, platform, online, last_seen, peer_ip, sai_hash, lws_hash, windows) "
+ "VALUES ('%s', '%s', 1, %llu, '%s', '%s', '%s', %d) "
+ "ON CONFLICT(name) DO UPDATE SET online=1, last_seen=excluded.last_seen, "
+ "peer_ip=excluded.peer_ip, sai_hash=excluded.sai_hash, lws_hash=excluded.lws_hash",
+ build->name, build->platform, (unsigned long long)lws_now_secs(),
+ pss->peer_ip, build->sai_hash, build->lws_hash, build->windows);
+
+ if (sai_sqlite3_statement(vhd->server.pdb, q, "upsert builder"))
lwsl_err("%s: Failed to upsert builder %s\n",
- __func__, build->name);
- }
+ __func__, build->name);
/*
* Step 2: Update the long-lived, malloc'd in-memory list.
@@ -1097,6 +1102,27 @@ sais_ws_json_tx_builder(struct vhd *vhd, struct pss *pss, uint8_t *buf,
goto send_json;
}
+ if (pss->stay_owner.head) {
+ /*
+ * Pending stay message to send
+ */
+ sai_stay_t *s = lws_container_of(pss->stay_owner.head,
+ sai_stay_t, list);
+
+ js = lws_struct_json_serialize_create(lsm_schema_stay,
+ LWS_ARRAY_SIZE(lsm_schema_stay), 0, s);
+ if (!js)
+ return 1;
+
+ n = (int)lws_struct_json_serialize(js, p, lws_ptr_diff_size_t(end, p), &w);
+ lws_struct_json_serialize_destroy(&js);
+
+ lws_dll2_remove(&s->list);
+ free(s);
+
+ goto send_json;
+ }
+
if (pss->rebuild_owner.head) {
/*
* Pending rebuild message to send
diff --git a/src/web/w-websrv.c b/src/web/w-websrv.c
index d83ee53..3ef960b 100644
--- a/src/web/w-websrv.c
+++ b/src/web/w-websrv.c
@@ -298,6 +298,7 @@ saiw_lp_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
*flags = (som ? LWSSS_FLAG_SOM : 0) | (final ? LWSSS_FLAG_EOM : 0);
lwsl_ss_notice(m->ss, "Sending %d ssflags %d", (int)*len, (int)*flags);
+ lwsl_hexdump_notice(buf, *len);
if (m->wbltx)
return lws_ss_request_tx(m->ss);
diff --git a/src/web/w-ws-browser.c b/src/web/w-ws-browser.c
index 28ea478..cbccfa3 100644
--- a/src/web/w-ws-browser.c
+++ b/src/web/w-ws-browser.c
@@ -80,6 +80,8 @@ static const lws_struct_map_t lsm_schema_json_map_bwsrx[] = {
"com.warmcat.sai.rebuild"),
LSM_SCHEMA (sai_browse_rx_platreset_t, NULL, lsm_browser_platreset,
"com.warmcat.sai.platreset"),
+ LSM_SCHEMA (sai_stay_t, NULL, lsm_stay,
+ "com.warmcat.sai.stay"),
};
enum {
@@ -93,6 +95,7 @@ enum {
SAIM_WS_BROWSER_RX_JS_HELLO,
SAIM_WS_BROWSER_RX_REBUILD,
SAIM_WS_BROWSER_RX_PLATRESET,
+ SAIM_WS_BROWSER_RX_STAY,
};
@@ -510,6 +513,17 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf,
saiw_websrv_queue_tx(vhd->h_ss_websrv, buf, bl, ss_flags);
break;
+ case SAIM_WS_BROWSER_RX_STAY:
+ if (!sais_conn_auth(pss))
+ goto auth_error;
+
+ /*
+ * User is asking us to set or release a stay on a builder
+ */
+
+ saiw_websrv_queue_tx(vhd->h_ss_websrv, buf, bl, ss_flags);
+ break;
+
case SAIM_WS_BROWSER_RX_TASKREBUILDLASTSTEP:
if (!sais_conn_auth(pss))
goto auth_error;