Skip to content

Commit 5d5fcb0

Browse files
committed
Add ability to specify initial state on power-on
Fixes #22
1 parent 7fe7f52 commit 5d5fcb0

File tree

5 files changed

+103
-47
lines changed

5 files changed

+103
-47
lines changed

fs/index.html

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,13 @@ <h1 class="" id="sw1_name">SW1 Name</h1>
8080
</select>
8181
</div>
8282
<div class="form-control">
83-
<label for="sw1_persist">Persist state:</label>
84-
<input type="checkbox" id="sw1_persist">
83+
<label for="sw1_initial">Initial state:</label>
84+
<select id="sw1_initial">
85+
<option id="sw1_initial_0" value="0">Off</option>
86+
<option id="sw1_initial_1" value="1">On</option>
87+
<option id="sw1_initial_2" value="2">Last</option>
88+
<option id="sw1_initial_3" value="3">Input</option>
89+
</select>
8590
</div>
8691
<div class="form-control">
8792
<label for="sw1_auto_off">Auto off:</label>
@@ -130,8 +135,13 @@ <h1 class="" id="sw2_name">SW2 Name</h1>
130135
</select>
131136
</div>
132137
<div class="form-control">
133-
<label for="sw2_persist">Persist state:</label>
134-
<input type="checkbox" id="sw2_persist">
138+
<label for="sw2_initial">Initial state:</label>
139+
<select id="sw2_initial">
140+
<option id="sw2_initial_0" value="0">Off</option>
141+
<option id="sw2_initial_1" value="1">On</option>
142+
<option id="sw2_initial_2" value="2">Last</option>
143+
<option id="sw2_initial_3" value="3">Input</option>
144+
</select>
135145
</div>
136146
<div class="form-control">
137147
<label for="sw2_auto_off">Auto off:</label>
@@ -361,7 +371,7 @@ <h1 class="">Firmware</h1>
361371
);
362372
}
363373

364-
function sw_save_common(cfg_key, in_mode, persist, auto_off, auto_off_delay, spinner) {
374+
function sw_save_common(cfg_key, in_mode, initial_state, auto_off, auto_off_delay, spinner) {
365375
spinner.className = "spin";
366376
var data = {
367377
config: {},
@@ -370,7 +380,7 @@ <h1 class="">Firmware</h1>
370380
};
371381
data.config[cfg_key] = {
372382
in_mode: parseInt(in_mode),
373-
persist_state: persist,
383+
initial_state: parseInt(initial_state),
374384
auto_off: auto_off,
375385
auto_off_delay: dateStringToSeconds(auto_off_delay)
376386
};
@@ -410,15 +420,15 @@ <h1 class="">Firmware</h1>
410420
}
411421

412422
function twoDigitString(num) {
413-
return num.toLocaleString(undefined, {minimumIntegerDigits: 2})
423+
return num.toLocaleString(undefined, {minimumIntegerDigits: 2})
414424
}
415425

416426
el("sw1_save_btn").onclick = function() {
417427
if (isValid("sw1")){
418428
sw_save_common(
419429
"sw1",
420430
el("sw1_in_mode").value,
421-
el("sw1_persist").checked,
431+
el("sw1_initial").value,
422432
el("sw1_auto_off").checked,
423433
el("sw1_auto_off_delay").value,
424434
el("sw1_save_spinner"),
@@ -431,7 +441,7 @@ <h1 class="">Firmware</h1>
431441
sw_save_common(
432442
"sw2",
433443
el("sw2_in_mode").value,
434-
el("sw2_persist").checked,
444+
el("sw2_initial").value,
435445
el("sw2_auto_off").checked,
436446
el("sw2_auto_off_delay").value,
437447
el("sw2_save_spinner"),
@@ -473,7 +483,7 @@ <h1 class="">Firmware</h1>
473483
}
474484
el("sw1_btn_label").innerText = "Turn " + (res.data.sw1.state ? "Off" : "On");
475485
el("sw1_in_mode_" + res.data.sw1.in_mode).selected = true;
476-
el("sw1_persist").checked = res.data.sw1.persist;
486+
el("sw1_initial_" + res.data.sw1.initial).selected = true;
477487
el("sw1_auto_off").checked = res.data.sw1.auto_off;
478488
el("sw1_auto_off_delay").disabled = !res.data.sw1.auto_off;
479489
el("sw1_auto_off_delay").value = secondsToDateString(res.data.sw1.auto_off_delay);
@@ -490,7 +500,7 @@ <h1 class="">Firmware</h1>
490500
}
491501
el("sw2_btn_label").innerText = "Turn " + (res.data.sw2.state ? "Off" : "On");
492502
el("sw2_in_mode_" + res.data.sw2.in_mode).selected = true;
493-
el("sw2_persist").checked = res.data.sw2.persist;
503+
el("sw2_initial_" + res.data.sw2.initial).selected = true;
494504
el("sw2_auto_off").checked = res.data.sw2.auto_off;
495505
el("sw2_auto_off_delay").disabled = !res.data.sw2.auto_off;
496506
el("sw2_auto_off_delay").value = secondsToDateString(res.data.sw2.auto_off_delay);

mos.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,14 @@ config_schema:
2828
- ["sw.in_gpio", "i", -1, {"Input sensing pin"}]
2929
- ["sw.in_mode", "i", 1, {"0 - Momentary, 1 - Toggle, 2 - Edge, 3 - Detached"}]
3030
- ["sw.state", "b", false, {"State of the switch"}]
31-
- ["sw.persist_state", "b", false, {"Whether state of the switch should be persisted across reboots."}]
31+
- ["sw.initial_state", "i", 3, {"Initial state on power-on: 0 - off, 1 - on, 2 - restore last state, 3 - matches input if in toggle mode, otherwise off"}]
3232
- ["sw.auto_off", "b", false, {"Whether the switch should automatically turn OFF after turning ON"}]
3333
- ["sw.auto_off_delay", "i", -1, {"Delay for automatically turning OFF, in seconds"}]
3434

35+
- ["shelly.cfg_version", "i", 0, {"Configuration version"}]
36+
# Deprecated settings, only kept to enable migration.
37+
- ["sw.persist_state", "b", false, {"Deprecated"}] # Since cfg v1
38+
3539

3640
build_vars:
3741
# Enables storing setup info in the config and a simple RPC service to configure it.

src/shelly_main.c

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,16 +304,16 @@ static void shelly_get_info_handler(struct mg_rpc_request_info *ri,
304304
ri,
305305
"{id: %Q, app: %Q, host: %Q, version: %Q, fw_build: %Q, uptime: %d, "
306306
#ifdef MGOS_CONFIG_HAVE_SW1
307-
"sw1: {id: %d, name: %Q, in_mode: %d, persist: %B, state: %B, auto_off: "
308-
"%B, auto_off_delay: %d"
307+
"sw1: {id: %d, name: %Q, in_mode: %d, initial: %d, "
308+
"state: %B, auto_off: %B, auto_off_delay: %d"
309309
#ifdef SHELLY_HAVE_PM
310310
", apower: %.3f, aenergy: %.3f"
311311
#endif
312312
"},"
313313
#endif
314314
#ifdef MGOS_CONFIG_HAVE_SW2
315-
"sw2: {id: %d, name: %Q, in_mode: %d, persist: %B, state: %B, auto_off: "
316-
"%B, auto_off_delay: %d"
315+
"sw2: {id: %d, name: %Q, in_mode: %d, initial: %d, "
316+
"state: %B, auto_off: %B, auto_off_delay: %d"
317317
#ifdef SHELLY_HAVE_PM
318318
", apower: %.3f, aenergy: %.3f"
319319
#endif
@@ -327,7 +327,7 @@ static void shelly_get_info_handler(struct mg_rpc_request_info *ri,
327327
#ifdef MGOS_CONFIG_HAVE_SW1
328328
mgos_sys_config_get_sw1_id(), mgos_sys_config_get_sw1_name(),
329329
mgos_sys_config_get_sw1_in_mode(),
330-
mgos_sys_config_get_sw1_persist_state(), sw1.state,
330+
mgos_sys_config_get_sw1_initial_state(), sw1.state,
331331
mgos_sys_config_get_sw1_auto_off(),
332332
mgos_sys_config_get_sw1_auto_off_delay(),
333333
#ifdef SHELLY_HAVE_PM
@@ -337,7 +337,7 @@ static void shelly_get_info_handler(struct mg_rpc_request_info *ri,
337337
#ifdef MGOS_CONFIG_HAVE_SW2
338338
mgos_sys_config_get_sw2_id(), mgos_sys_config_get_sw2_name(),
339339
mgos_sys_config_get_sw2_in_mode(),
340-
mgos_sys_config_get_sw2_persist_state(), sw2.state,
340+
mgos_sys_config_get_sw2_initial_state(), sw2.state,
341341
mgos_sys_config_get_sw2_auto_off(),
342342
mgos_sys_config_get_sw2_auto_off_delay(),
343343
#ifdef SHELLY_HAVE_PM
@@ -370,6 +370,25 @@ static void shelly_set_switch_handler(struct mg_rpc_request_info *ri,
370370
(void) fi;
371371
}
372372

373+
static bool shelly_cfg_migrate(void) {
374+
bool changed = false;
375+
if (mgos_sys_config_get_shelly_cfg_version() == 0) {
376+
#ifdef MGOS_CONFIG_HAVE_SW1
377+
if (mgos_sys_config_get_sw1_persist_state()) {
378+
mgos_sys_config_set_sw1_initial_state(SHELLY_SW_INITIAL_STATE_LAST);
379+
}
380+
#endif
381+
#ifdef MGOS_CONFIG_HAVE_SW2
382+
if (mgos_sys_config_get_sw2_persist_state()) {
383+
mgos_sys_config_set_sw2_initial_state(SHELLY_SW_INITIAL_STATE_LAST);
384+
}
385+
#endif
386+
mgos_sys_config_set_shelly_cfg_version(1);
387+
changed = true;
388+
}
389+
return changed;
390+
}
391+
373392
enum mgos_app_init_result mgos_app_init(void) {
374393
#ifdef MGOS_HAVE_OTA_COMMON
375394
if (mgos_ota_is_first_boot()) {
@@ -382,6 +401,11 @@ enum mgos_app_init_result mgos_app_init(void) {
382401
}
383402
#endif
384403

404+
if (shelly_cfg_migrate()) {
405+
mgos_sys_config_save(&mgos_sys_config, false /* try_once */,
406+
NULL /* msg */);
407+
}
408+
385409
#ifdef MGOS_HAVE_ADE7953
386410
const struct mgos_ade7953_config ade7953_cfg = {
387411
.voltage_scale = .0000382602,

src/shelly_sw_service.c

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,6 @@
2727
#define IID_BASE 0x100
2828
#define IID_STEP 4
2929

30-
enum shelly_sw_in_mode {
31-
SHELLY_SW_IN_MODE_MOMENTARY = 0,
32-
SHELLY_SW_IN_MODE_TOGGLE = 1,
33-
SHELLY_SW_IN_MODE_EDGE = 2,
34-
SHELLY_SW_IN_MODE_DETACHED = 3,
35-
};
36-
3730
struct shelly_sw_service_ctx {
3831
const struct mgos_config_sw *cfg;
3932
HAPAccessoryServerRef *hap_server;
@@ -43,6 +36,7 @@ struct shelly_sw_service_ctx {
4336
bool pb_state;
4437
int change_cnt; // State change counter for reset.
4538
double last_change_ts; // Timestamp of last change (uptime).
39+
mgos_timer_id auto_off_timer_id;
4640
#ifdef MGOS_HAVE_ADE7953
4741
struct mgos_ade7953 *ade7953;
4842
int ade7953_channel;
@@ -51,8 +45,6 @@ struct shelly_sw_service_ctx {
5145

5246
static struct shelly_sw_service_ctx s_ctx[NUM_SWITCHES];
5347

54-
static int s_auto_off_timer_id = MGOS_INVALID_TIMER_ID;
55-
5648
static void do_auto_off(void *arg);
5749

5850
#ifdef SHELLY_HAVE_PM
@@ -73,11 +65,12 @@ static void do_reset(void *arg) {
7365

7466
static void handle_auto_off(struct shelly_sw_service_ctx *ctx,
7567
const char *source, bool new_state) {
76-
if (s_auto_off_timer_id != MGOS_INVALID_TIMER_ID) {
68+
if (ctx->auto_off_timer_id != MGOS_INVALID_TIMER_ID) {
7769
// Cancel timer if state changes so that only the last timer is triggered if
7870
// state changes multiple times
79-
mgos_clear_timer(s_auto_off_timer_id);
80-
s_auto_off_timer_id = MGOS_INVALID_TIMER_ID;
71+
mgos_clear_timer(ctx->auto_off_timer_id);
72+
ctx->auto_off_timer_id = MGOS_INVALID_TIMER_ID;
73+
LOG(LL_INFO, ("%d: Cleared auto-off timer", ctx->cfg->id));
8174
}
8275

8376
const struct mgos_config_sw *cfg = ctx->cfg;
@@ -88,16 +81,17 @@ static void handle_auto_off(struct shelly_sw_service_ctx *ctx,
8881

8982
if (!new_state) return;
9083

91-
s_auto_off_timer_id =
84+
ctx->auto_off_timer_id =
9285
mgos_set_timer(cfg->auto_off_delay * 1000, 0, do_auto_off, ctx);
86+
LOG(LL_INFO, ("%d: Set auto-off timer for %d", cfg->id, cfg->auto_off_delay));
9387
}
9488

9589
static void shelly_sw_set_state_ctx(struct shelly_sw_service_ctx *ctx,
9690
bool new_state, const char *source) {
9791
const struct mgos_config_sw *cfg = ctx->cfg;
98-
if (new_state == ctx->info.state) return;
9992
int out_value = (new_state ? cfg->out_on_value : !cfg->out_on_value);
100-
mgos_gpio_write(cfg->out_gpio, out_value);
93+
mgos_gpio_setup_output(cfg->out_gpio, out_value);
94+
if (new_state == ctx->info.state) return;
10195
LOG(LL_INFO, ("%s: %d -> %d (%s) %d", cfg->name, ctx->info.state, new_state,
10296
source, out_value));
10397
ctx->info.state = new_state;
@@ -106,10 +100,12 @@ static void shelly_sw_set_state_ctx(struct shelly_sw_service_ctx *ctx,
106100
ctx->hap_service->characteristics[1],
107101
ctx->hap_service, ctx->hap_accessory);
108102
}
109-
if (cfg->persist_state) {
103+
if (cfg->state != new_state) {
110104
((struct mgos_config_sw *) cfg)->state = new_state;
111-
mgos_sys_config_save(&mgos_sys_config, false /* try_once */,
112-
NULL /* msg */);
105+
if (cfg->initial_state == SHELLY_SW_INITIAL_STATE_LAST) {
106+
mgos_sys_config_save(&mgos_sys_config, false /* try_once */,
107+
NULL /* msg */);
108+
}
113109
}
114110

115111
double now = mgos_uptime();
@@ -131,9 +127,10 @@ static void shelly_sw_set_state_ctx(struct shelly_sw_service_ctx *ctx,
131127
}
132128

133129
static void do_auto_off(void *arg) {
134-
s_auto_off_timer_id = MGOS_INVALID_TIMER_ID;
135130
struct shelly_sw_service_ctx *ctx = arg;
136131
const struct mgos_config_sw *cfg = ctx->cfg;
132+
ctx->auto_off_timer_id = MGOS_INVALID_TIMER_ID;
133+
LOG(LL_INFO, ("%d: Auto-off timer fired", cfg->id));
137134
if (cfg->auto_off) {
138135
// Don't set state if auto off has been disabled during timer run
139136
shelly_sw_set_state_ctx(ctx, false, "auto_off");
@@ -329,21 +326,28 @@ HAPService *shelly_sw_service_create(
329326
struct shelly_sw_service_ctx *ctx = &s_ctx[cfg->id];
330327
ctx->cfg = cfg;
331328
ctx->hap_service = svc;
332-
if (cfg->persist_state) {
333-
ctx->info.state = cfg->state;
334-
} else {
335-
ctx->info.state = 0;
336-
}
329+
ctx->auto_off_timer_id = MGOS_INVALID_TIMER_ID;
337330
LOG(LL_INFO, ("Exporting '%s' (GPIO out: %d, in: %d, state: %d)", cfg->name,
338331
cfg->out_gpio, cfg->in_gpio, ctx->info.state));
339-
mgos_gpio_setup_output(cfg->out_gpio, (ctx->info.state ? cfg->out_on_value
340-
: !cfg->out_on_value));
332+
333+
switch ((enum shelly_sw_initial_state) cfg->initial_state) {
334+
case SHELLY_SW_INITIAL_STATE_OFF:
335+
shelly_sw_set_state_ctx(ctx, false, "init");
336+
break;
337+
case SHELLY_SW_INITIAL_STATE_ON:
338+
shelly_sw_set_state_ctx(ctx, true, "init");
339+
break;
340+
case SHELLY_SW_INITIAL_STATE_LAST:
341+
shelly_sw_set_state_ctx(ctx, cfg->state, "init");
342+
break;
343+
case SHELLY_SW_INITIAL_STATE_INPUT:
344+
if (cfg->in_mode == SHELLY_SW_IN_MODE_TOGGLE) {
345+
shelly_sw_in_cb(cfg->in_gpio, ctx);
346+
}
347+
}
341348
mgos_gpio_set_button_handler(cfg->in_gpio, MGOS_GPIO_PULL_NONE,
342349
MGOS_GPIO_INT_EDGE_ANY, 20, shelly_sw_in_cb,
343350
ctx);
344-
if (ctx->cfg->in_mode == SHELLY_SW_IN_MODE_TOGGLE) {
345-
shelly_sw_in_cb(cfg->in_gpio, ctx);
346-
}
347351
#ifdef SHELLY_HAVE_PM
348352
#ifdef MGOS_HAVE_ADE7953
349353
ctx->ade7953 = ade7953;

src/shelly_sw_service.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@
2222
struct mgos_ade7953;
2323
#endif
2424

25+
enum shelly_sw_in_mode {
26+
SHELLY_SW_IN_MODE_MOMENTARY = 0,
27+
SHELLY_SW_IN_MODE_TOGGLE = 1,
28+
SHELLY_SW_IN_MODE_EDGE = 2,
29+
SHELLY_SW_IN_MODE_DETACHED = 3,
30+
};
31+
32+
enum shelly_sw_initial_state {
33+
SHELLY_SW_INITIAL_STATE_OFF = 0,
34+
SHELLY_SW_INITIAL_STATE_ON = 1,
35+
SHELLY_SW_INITIAL_STATE_LAST = 2,
36+
SHELLY_SW_INITIAL_STATE_INPUT = 3,
37+
};
38+
2539
HAPService *shelly_sw_service_create(
2640
#ifdef MGOS_HAVE_ADE7953
2741
struct mgos_ade7953 *ade7953, int ade7953_channel,

0 commit comments

Comments
 (0)