Skip to content

Commit

Permalink
Shelly2.5: Power measurement support
Browse files Browse the repository at this point in the history
Currently only exposed in the web UI, not through HomeKit API.
  • Loading branch information
rojer committed Mar 31, 2020
1 parent 4ee9799 commit a963921
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 33 deletions.
18 changes: 12 additions & 6 deletions fs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ <h1 class="" id="sw1_name">SW1 Name</h1>
<div class="form">
<div class="">
<div class="form-control">
<label>State:</label>
<label>Status:</label>
<span id="sw1_state">off</span>
<span id="sw1_power_stats"></span>
</div>
<div class="form-control">
<label></label>
Expand All @@ -67,8 +68,6 @@ <h1 class="" id="sw1_name">SW1 Name</h1>
</button>
</div>
</div>
</div>
<div class="form">
<div class="">
<div class="form-control">
<label>Mode:</label>
Expand Down Expand Up @@ -99,8 +98,9 @@ <h1 class="" id="sw2_name">SW2 Name</h1>
<div class="form">
<div class="">
<div class="form-control">
<label>State:</label>
<label>Status:</label>
<span id="sw2_state">off</span>
<span id="sw2_power_stats"></span>
</div>
<div class="form-control">
<label></label>
Expand All @@ -110,8 +110,6 @@ <h1 class="" id="sw2_name">SW2 Name</h1>
</button>
</div>
</div>
</div>
<div class="form">
<div class="">
<div class="form-control">
<label>Mode:</label>
Expand Down Expand Up @@ -415,6 +413,10 @@ <h1 class="">Firmware</h1>
if (res.data.sw1) {
el("sw1_name").innerText = res.data.sw1.name;
el("sw1_state").innerText = (res.data.sw1.state ? "on" : "off");
if (res.data.sw1.apower !== undefined) {
el("sw1_power_stats").innerText =
", " + Math.round(res.data.sw1.apower) + "W, " + res.data.sw1.aenergy + "Wh";
}
el("sw1_btn_label").innerText = "Turn " + (res.data.sw1.state ? "Off" : "On");
el("sw1_in_mode_" + res.data.sw1.in_mode).selected = true;
el("sw1_persist").checked = res.data.sw1.persist;
Expand All @@ -425,6 +427,10 @@ <h1 class="">Firmware</h1>
if (res.data.sw2) {
el("sw2_name").innerText = res.data.sw2.name;
el("sw2_state").innerText = (res.data.sw2.state ? "on" : "off");
if (res.data.sw2.apower !== undefined) {
el("sw2_power_stats").innerText =
", " + Math.round(res.data.sw2.apower) + "W, " + res.data.sw2.aenergy + "Wh";
}
el("sw2_btn_label").innerText = "Turn " + (res.data.sw2.state ? "Off" : "On");
el("sw2_in_mode_" + res.data.sw2.in_mode).selected = true;
el("sw2_persist").checked = res.data.sw2.persist;
Expand Down
8 changes: 7 additions & 1 deletion mos.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
author: 'Deomid "rojer" Ryabkov'
description: A HomeKit firmware for Shelly switches
version: 1.5.1
version: 1.7.0
platform: esp8266

libs_version: 2.17.0
Expand Down Expand Up @@ -187,6 +187,8 @@ conds:
variant: esp8266-noatca
- origin: https://github.com/mongoose-os-libs/mongoose
variant: esp8266-nossl
- origin: https://github.com/mongoose-os-libs/ade7953
version: master # Until 2.18 is out.
build_vars:
FLASH_SIZE: 2097152
FS_SIZE: 262144
Expand All @@ -202,10 +204,14 @@ conds:
PRODUCT_HW_REV: '"2.5"'
NUM_SWITCHES: 2
MG_ENABLE_SSL: 0
SHELLY_HAVE_PM: 1
# HAP_LOG_LEVEL: 0
config_schema:
- ["device.id", "shellyswitch25-??????"]
- ["wifi.ap.ssid", "shellyswitch25-??????"]
- ["i2c.enable", true]
- ["i2c.sda_gpio", 12]
- ["i2c.scl_gpio", 14]
- ["sw1", "sw", {"title": "SW1 settings"}]
- ["sw1.id", 0]
- ["sw1.name", "Shelly SW1"]
Expand Down
73 changes: 63 additions & 10 deletions src/shelly_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
#endif
#include "mgos_rpc.h"

#ifdef MGOS_HAVE_ADE7953
#include "mgos_ade7953.h"
#endif

#include "HAPPlatform+Init.h"
#include "HAPPlatformAccessorySetup+Init.h"
#include "HAPPlatformKeyValueStore+Init.h"
Expand Down Expand Up @@ -240,13 +244,28 @@ static void check_led(int pin, bool led_act) {
}
}

#ifdef MGOS_HAVE_ADE7953
struct mgos_ade7953 *s_ade7953 = NULL;
#endif

static void shelly_status_timer_cb(void *arg) {
static bool s_tick_tock = false;
LOG(LL_INFO,
("%s uptime: %.2lf, RAM: %lu, %lu free", (s_tick_tock ? "Tick" : "Tock"),
mgos_uptime(), (unsigned long) mgos_get_heap_size(),
(unsigned long) mgos_get_free_heap_size()));
s_tick_tock = !s_tick_tock;
LOG(LL_INFO, ("Uptime: %.2lf, RAM: %lu, %lu free", mgos_uptime(),
(unsigned long) mgos_get_heap_size(),
(unsigned long) mgos_get_free_heap_size()));
#if defined(MGOS_HAVE_ADE7953) && defined(SHELLY_PRINT_POWER_STATS)
float f = 0, v = 0, ia = 0, ib = 0, aea = 0, aeb = 0, apa = 0, apb = 0;
mgos_ade7953_get_frequency(s_ade7953, &f);
mgos_ade7953_get_voltage(s_ade7953, &v);
mgos_ade7953_get_current(s_ade7953, 0, &ia);
mgos_ade7953_get_current(s_ade7953, 1, &ib);
mgos_ade7953_get_apower(s_ade7953, 0, &apa);
mgos_ade7953_get_apower(s_ade7953, 1, &apb);
mgos_ade7953_get_aenergy(s_ade7953, 0, false /* reset */, &aea);
mgos_ade7953_get_aenergy(s_ade7953, 1, false /* reset */, &aeb);
LOG(LL_INFO, (" V=%.3fV f=%.2fHz | IA=%.3fA APA=%.3f AEA=%.3f | "
"IB=%.3fA APB=%.3f AEB=%.3f",
v, f, ia, apa, aea, ib, apb, aeb));
#endif
/* If provisioning information has been provided, start the server. */
shelly_start_hap_server(true /* quiet */);
check_btn(BTN_GPIO, BTN_DOWN);
Expand Down Expand Up @@ -285,10 +304,18 @@ static void shelly_get_info_handler(struct mg_rpc_request_info *ri,
ri,
"{id: %Q, app: %Q, host: %Q, version: %Q, fw_build: %Q, uptime: %d, "
#ifdef MGOS_CONFIG_HAVE_SW1
"sw1: {id: %d, name: %Q, in_mode: %d, persist: %B, state: %B},"
"sw1: {id: %d, name: %Q, in_mode: %d, persist: %B, state: %B"
#ifdef SHELLY_HAVE_PM
", apower: %.3f, aenergy: %.3f"
#endif
"},"
#endif
#ifdef MGOS_CONFIG_HAVE_SW2
"sw2: {id: %d, name: %Q, in_mode: %d, persist: %B, state: %B},"
"sw2: {id: %d, name: %Q, in_mode: %d, persist: %B, state: %B"
#ifdef SHELLY_HAVE_PM
", apower: %.3f, aenergy: %.3f"
#endif
"},"
#endif
"wifi_en: %B, wifi_ssid: %Q, wifi_pass: %Q, "
"hap_provisioned: %B, hap_paired: %B}",
Expand All @@ -299,11 +326,17 @@ static void shelly_get_info_handler(struct mg_rpc_request_info *ri,
mgos_sys_config_get_sw1_id(), mgos_sys_config_get_sw1_name(),
mgos_sys_config_get_sw1_in_mode(),
mgos_sys_config_get_sw1_persist_state(), sw1.state,
#ifdef SHELLY_HAVE_PM
sw1.apower, sw1.aenergy,
#endif
#endif
#ifdef MGOS_CONFIG_HAVE_SW2
mgos_sys_config_get_sw2_id(), mgos_sys_config_get_sw2_name(),
mgos_sys_config_get_sw2_in_mode(),
mgos_sys_config_get_sw2_persist_state(), sw2.state,
#ifdef SHELLY_HAVE_PM
sw2.apower, sw2.aenergy,
#endif
#endif
mgos_sys_config_get_wifi_sta_enable(), (ssid ? ssid : ""),
(pass ? pass : ""), hap_provisioned, hap_paired);
Expand Down Expand Up @@ -343,6 +376,18 @@ enum mgos_app_init_result mgos_app_init(void) {
}
#endif

#ifdef MGOS_HAVE_ADE7953
const struct mgos_ade7953_config ade7953_cfg = {
.voltage_scale = .0000382602,
.voltage_offset = -0.068,
.current_scale = {0.00000949523, 0.00000949523},
.current_offset = {-0.017, -0.017},
.apower_scale = {(1 / 164.0), (1 / 164.0)},
.aenergy_scale = {(1 / 25240.0), (1 / 25240.0)},
};
s_ade7953 = mgos_ade7953_create(mgos_i2c_get_global(), &ade7953_cfg);
#endif

// Key-value store.
HAPPlatformKeyValueStoreCreate(
&s_kvs,
Expand Down Expand Up @@ -385,11 +430,19 @@ enum mgos_app_init_result mgos_app_init(void) {
// Workaround for Shelly2.5: initing SW1 input (GPIO13) somehow causes
// SW2 output (GPIO15) to turn on. Initializing SW2 first fixes it.
#ifdef MGOS_CONFIG_HAVE_SW2
services[i] = shelly_sw_service_create(mgos_sys_config_get_sw2());
services[i] = shelly_sw_service_create(
#ifdef MGOS_HAVE_ADE7953
s_ade7953, 0,
#endif
mgos_sys_config_get_sw2());
if (services[i] != NULL) i++;
#endif
#ifdef MGOS_CONFIG_HAVE_SW1
services[i] = shelly_sw_service_create(mgos_sys_config_get_sw1());
services[i] = shelly_sw_service_create(
#ifdef MGOS_HAVE_ADE7953
s_ade7953, 1,
#endif
mgos_sys_config_get_sw1());
if (services[i] != NULL) i++;
#endif
s_accessory.services = services;
Expand Down
71 changes: 56 additions & 15 deletions src/shelly_sw_service.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@

#include "shelly_sw_service.h"

#include <math.h>

#include "mgos.h"
#ifdef MGOS_HAVE_ADE7953
#include "mgos_ade7953.h"
#endif

#define IID_BASE 0x100
#define IID_STEP 4
Expand All @@ -34,14 +39,22 @@ struct shelly_sw_service_ctx {
HAPAccessoryServerRef *hap_server;
const HAPAccessory *hap_accessory;
const HAPService *hap_service;
bool state;
struct shelly_sw_info info;
bool pb_state;
int change_cnt; // State change counter for reset.
double last_change_ts; // Timestamp of last change (uptime).
#ifdef MGOS_HAVE_ADE7953
struct mgos_ade7953 *ade7953;
int ade7953_channel;
#endif
};

static struct shelly_sw_service_ctx s_ctx[NUM_SWITCHES];

#ifdef SHELLY_HAVE_PM
static void shelly_sw_read_power(void *arg);
#endif

static void do_reset(void *arg) {
struct shelly_sw_service_ctx *ctx = arg;
mgos_gpio_blink(ctx->cfg->out_gpio, 0, 0);
Expand All @@ -57,12 +70,12 @@ static void do_reset(void *arg) {
static void shelly_sw_set_state_ctx(struct shelly_sw_service_ctx *ctx,
bool new_state, const char *source) {
const struct mgos_config_sw *cfg = ctx->cfg;
if (new_state == ctx->state) return;
if (new_state == ctx->info.state) return;
int out_value = (new_state ? cfg->out_on_value : !cfg->out_on_value);
mgos_gpio_write(cfg->out_gpio, out_value);
LOG(LL_INFO, ("%s: %d -> %d (%s) %d", cfg->name, ctx->state, new_state,
LOG(LL_INFO, ("%s: %d -> %d (%s) %d", cfg->name, ctx->info.state, new_state,
source, out_value));
ctx->state = new_state;
ctx->info.state = new_state;
if (ctx->hap_server != NULL) {
HAPAccessoryServerRaiseEvent(ctx->hap_server,
ctx->hap_service->characteristics[1],
Expand Down Expand Up @@ -101,7 +114,7 @@ bool shelly_sw_get_info(int id, struct shelly_sw_info *info) {
if (id < 0 || id >= NUM_SWITCHES) return false;
struct shelly_sw_service_ctx *ctx = &s_ctx[id];
if (ctx == NULL) return false;
info->state = ctx->state;
*info = ctx->info;
return true;
}

Expand Down Expand Up @@ -154,8 +167,8 @@ HAPError shelly_sw_handle_on_read(
void *context) {
struct shelly_sw_service_ctx *ctx = find_ctx(request->service);
const struct mgos_config_sw *cfg = ctx->cfg;
*value = ctx->state;
LOG(LL_INFO, ("%s: READ -> %d", cfg->name, ctx->state));
*value = ctx->info.state;
LOG(LL_INFO, ("%s: READ -> %d", cfg->name, ctx->info.state));
ctx->hap_server = server;
ctx->hap_accessory = request->accessory;
(void) context;
Expand Down Expand Up @@ -215,14 +228,14 @@ static void shelly_sw_in_cb(int pin, void *arg) {
switch ((enum shelly_sw_in_mode) ctx->cfg->in_mode) {
case SHELLY_SW_IN_MODE_MOMENTARY:
if (in_state) { // Only on 0 -> 1 transitions.
shelly_sw_set_state_ctx(ctx, !ctx->state, "button");
shelly_sw_set_state_ctx(ctx, !ctx->info.state, "button");
}
break;
case SHELLY_SW_IN_MODE_TOGGLE:
shelly_sw_set_state_ctx(ctx, in_state, "switch");
break;
case SHELLY_SW_IN_MODE_EDGE:
shelly_sw_set_state_ctx(ctx, !ctx->state, "button");
shelly_sw_set_state_ctx(ctx, !ctx->info.state, "button");
break;
case SHELLY_SW_IN_MODE_DETACHED:
// Nothing to do
Expand All @@ -231,7 +244,28 @@ static void shelly_sw_in_cb(int pin, void *arg) {
(void) pin;
}

HAPService *shelly_sw_service_create(const struct mgos_config_sw *cfg) {
#ifdef SHELLY_HAVE_PM
static void shelly_sw_read_power(void *arg) {
struct shelly_sw_service_ctx *ctx = arg;
#ifdef MGOS_HAVE_ADE7953
float apa = 0, aea = 0;
if (mgos_ade7953_get_apower(ctx->ade7953, ctx->ade7953_channel, &apa)) {
if (fabs(apa) < 0.5) apa = 0; // Suppress noise.
ctx->info.apower = apa;
}
if (mgos_ade7953_get_aenergy(ctx->ade7953, ctx->ade7953_channel,
true /* reset */, &aea)) {
ctx->info.aenergy += aea;
}
#endif
}
#endif

HAPService *shelly_sw_service_create(
#ifdef MGOS_HAVE_ADE7953
struct mgos_ade7953 *ade7953, int ade7953_channel,
#endif
const struct mgos_config_sw *cfg) {
if (!cfg->enable) {
LOG(LL_INFO, ("'%s' is disabled", cfg->name));
mgos_gpio_setup_output(cfg->out_gpio, !cfg->out_on_value);
Expand All @@ -258,19 +292,26 @@ HAPService *shelly_sw_service_create(const struct mgos_config_sw *cfg) {
ctx->cfg = cfg;
ctx->hap_service = svc;
if (cfg->persist_state) {
ctx->state = cfg->state;
ctx->info.state = cfg->state;
} else {
ctx->state = 0;
ctx->info.state = 0;
}
LOG(LL_INFO, ("Exporting '%s' (GPIO out: %d, in: %d, state: %d)", cfg->name,
cfg->out_gpio, cfg->in_gpio, ctx->state));
mgos_gpio_setup_output(cfg->out_gpio,
(ctx->state ? cfg->out_on_value : !cfg->out_on_value));
cfg->out_gpio, cfg->in_gpio, ctx->info.state));
mgos_gpio_setup_output(cfg->out_gpio, (ctx->info.state ? cfg->out_on_value
: !cfg->out_on_value));
mgos_gpio_set_button_handler(cfg->in_gpio, MGOS_GPIO_PULL_NONE,
MGOS_GPIO_INT_EDGE_ANY, 20, shelly_sw_in_cb,
ctx);
if (ctx->cfg->in_mode == SHELLY_SW_IN_MODE_TOGGLE) {
shelly_sw_in_cb(cfg->in_gpio, ctx);
}
#ifdef SHELLY_HAVE_PM
#ifdef MGOS_HAVE_ADE7953
ctx->ade7953 = ade7953;
ctx->ade7953_channel = ade7953_channel;
#endif
mgos_set_timer(1000, MGOS_TIMER_REPEAT, shelly_sw_read_power, ctx);
#endif
return svc;
}
14 changes: 13 additions & 1 deletion src/shelly_sw_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,22 @@
#include "HAP.h"
#include "mgos_sys_config.h"

HAPService *shelly_sw_service_create(const struct mgos_config_sw *cfg);
#ifdef MGOS_HAVE_ADE7953
struct mgos_ade7953;
#endif

HAPService *shelly_sw_service_create(
#ifdef MGOS_HAVE_ADE7953
struct mgos_ade7953 *ade7953, int ade7953_channel,
#endif
const struct mgos_config_sw *cfg);

struct shelly_sw_info {
bool state; // On/off
#ifdef SHELLY_HAVE_PM
float apower; // Active power, Watts.
float aenergy; // Accumulated active power, Watt-hours.
#endif
};
bool shelly_sw_get_info(int id, struct shelly_sw_info *info);

Expand Down

0 comments on commit a963921

Please sign in to comment.