Skip to content

Commit

Permalink
Improve failsafe mode
Browse files Browse the repository at this point in the history
Some devices have button connected to a strap pin and cannot be booted with button down (2.5, 1PM).
Instead, we modify the algorithm to allow up to 3 seconds on power up for user to press and hold the button for at least 2 seconds.
During wait LED is blinking fast (100 ms on/off) and stops blinking when
the button is pressed. It then lights up solid when device boots into failsafe mode.

Soft reboots do not have this delay, so most of reboots are just as fast.
  • Loading branch information
rojer committed May 14, 2021
1 parent b1bc803 commit 68d1570
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 73 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ ifeq "$(RELEASE)" "1"
endif

format:
find src -name \*.cpp -o -name \*.hpp | xargs clang-format -i
for d in src lib*; do find $$d -name \*.cpp -o -name \*.hpp | xargs clang-format -i; done

check-format: format
@git diff --exit-code || { printf "\n== Please format your code (run make format) ==\n\n"; exit 1; }
Expand Down
28 changes: 28 additions & 0 deletions libreset/include/shelly_reset.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) Shelly-HomeKit Contributors
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

namespace shelly {

bool WipeDevice();

bool IsSoftReboot();

bool IsFailsafeMode();

} // namespace shelly
25 changes: 25 additions & 0 deletions libreset/mos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
description: Shelly device reset library
type: lib
version: 1.0.0

sources:
- src

includes:
- include

no_implicit_init_deps: true
# Execute as early as possible, before pretty much anything else.
init_after:
- core
init_before:
- adc
- i2c
- file-logger
- homekit-adk
- http-server
- ota-http-server
- rpc-*
- wifi

manifest_version: 2019-07-28
138 changes: 138 additions & 0 deletions libreset/src/shelly_reset.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright (c) Shelly-HomeKit Contributors
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "shelly_reset.hpp"

#include "mgos.hpp"

#if CS_PLATFORM == CS_P_ESP8266
extern "C" {
#include "user_interface.h"
}
extern "C" uint32_t rtc_get_reset_reason(void);
// This location is in RTC memory and was used for MEMP_NUM_TCP_PCB in original
// LWIP It used RTC to communicate with espconn (ugh). This is not used anymore,
// so we can repurpose this location for failsafe flag.
#define RTC_SCRATCH_ADDR 0x600011fc
#define FF_MODE_MAGIC 0x18365472
#endif

#include "shelly_main.hpp"

namespace shelly {

static bool s_failsafe_mode = false;

// Executed very early, pretty much nothing is available here.
extern "C" void mgos_app_preinit(void) {
#if RST_GPIO_INIT >= 0
mgos_gpio_setup_output(RST_GPIO_INIT, 0);
#endif
#if LED_GPIO >= 0
mgos_gpio_setup_output(LED_GPIO, !LED_ON);
#endif
#if BTN_GPIO >= 0
mgos_gpio_setup_input(BTN_GPIO,
(BTN_DOWN ? MGOS_GPIO_PULL_DOWN : MGOS_GPIO_PULL_UP));
#if CS_PLATFORM == CS_P_ESP8266
// system_get_rst_info() is not available yet so we're on our own.
uint32_t rr = rtc_get_reset_reason();
uint32_t rir = READ_PERI_REG(RTC_STORE0); // rst_info.reason
// If this is not a power up / CH_PD reset, skip.
if (!(rr == 1 && rir == REASON_DEFAULT_RST)) {
s_failsafe_mode = (READ_PERI_REG(RTC_SCRATCH_ADDR) == FF_MODE_MAGIC);
WRITE_PERI_REG(RTC_SCRATCH_ADDR, 0);
return;
}
#endif
// Give the user 3 seconds to press the button and hold it for 2 seconds.
int num_down = 0;
for (int i = 0; (i < 30 || num_down > 0) && num_down < 20; i++) {
mgos_msleep(100);
bool down = (mgos_gpio_read(BTN_GPIO) == BTN_DOWN);
if (down) {
mgos_cd_putc('!');
num_down++;
} else {
mgos_cd_putc('.');
num_down = 0;
}
#if LED_GPIO >= 0
if (down) {
mgos_gpio_write(LED_GPIO, !LED_ON);
} else {
mgos_gpio_toggle(LED_GPIO);
}
#endif
}
mgos_cd_putc('\n');
#if LED_GPIO >= 0
mgos_gpio_write(LED_GPIO, !LED_ON);
#endif
if (num_down >= 20) {
s_failsafe_mode = true;
}
#endif
}

bool IsFailsafeMode() {
return s_failsafe_mode;
}

bool WipeDevice() {
LOG(LL_INFO, ("== Wiping configuration"));
static const char *s_wipe_files[] = {
"conf2.json",
"conf9.json",
KVS_FILE_NAME,
AUTH_FILE_NAME,
};
bool wiped = false;
for (const char *wipe_fn : s_wipe_files) {
if (remove(wipe_fn) == 0) wiped = true;
}
#if defined(MGOS_HAVE_VFS_FS_SPIFFS) || defined(MGOS_HAVE_VFS_FS_LFS)
if (wiped) {
mgos_vfs_gc("/");
}
#endif
return wiped;
}

bool IsSoftReboot() {
#if CS_PLATFORM == CS_P_ESP8266
const struct rst_info *ri = system_get_rst_info();
return (ri->reason == REASON_SOFT_RESTART);
#else
return false;
#endif
}

extern "C" bool mgos_libreset_init(void) {
if (!IsFailsafeMode()) return true;
if (WipeDevice()) {
LOG(LL_INFO, ("== Wiped config, rebooting"));
#ifdef RTC_SCRATCH_ADDR
WRITE_PERI_REG(RTC_SCRATCH_ADDR, FF_MODE_MAGIC);
#endif
mgos_system_restart_after(100); // Not needed, but just in case.
return false; // Will reboot the device.
}
return true;
}

} // namespace shelly
4 changes: 2 additions & 2 deletions mos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,16 @@ cdefs:
HAP_LOG_LEVEL: 0 # This saves ~44K on esp8266.

libs:
- origin: ./libreset
- origin: https://github.com/mongoose-os-libs/core
- origin: https://github.com/mongoose-os-libs/file-logger
- origin: https://github.com/mongoose-os-libs/homekit-adk
- origin: https://github.com/mongoose-os-libs/mqtt
- origin: https://github.com/mongoose-os-libs/http-server
- origin: https://github.com/mongoose-os-libs/ota-http-server
- origin: https://github.com/mongoose-os-libs/rpc-service-config
- origin: https://github.com/mongoose-os-libs/rpc-service-fs
- origin: https://github.com/mongoose-os-libs/rpc-service-ota
- origin: https://github.com/mongoose-os-libs/rpc-uart
- origin: https://github.com/mongoose-os-libs/http-server
- origin: https://github.com/mongoose-os-libs/rpc-mqtt
- origin: https://github.com/mongoose-os-libs/rpc-ws
- origin: https://github.com/mongoose-os-libs/sntp
Expand Down
73 changes: 7 additions & 66 deletions src/shelly_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@
#if CS_PLATFORM == CS_P_ESP8266
#include "esp_coredump.h"
#include "esp_rboot.h"
extern "C" {
#include "user_interface.h"
}
#endif

#include "HAP.h"
Expand Down Expand Up @@ -154,7 +151,6 @@ static std::vector<std::unique_ptr<PowerMeter>> s_pms;
static std::vector<std::unique_ptr<mgos::hap::Accessory>> s_accs;
static std::vector<const HAPAccessory *> s_hap_accs;
static std::unique_ptr<TempSensor> s_sys_temp_sensor;
static bool s_failsafe_mode = false;
std::vector<std::unique_ptr<Component>> g_comps;

template <class T>
Expand All @@ -174,28 +170,6 @@ PowerMeter *FindPM(int id) {
return FindById(s_pms, id);
}

// Executed very early, pretty much nothing is available here.
extern "C" void mgos_app_preinit(void) {
#if RST_GPIO_INIT >= 0
mgos_gpio_setup_output(RST_GPIO_INIT, 0);
#endif
#if BTN_GPIO >= 0
mgos_gpio_setup_input(BTN_GPIO,
(BTN_DOWN ? MGOS_GPIO_PULL_DOWN : MGOS_GPIO_PULL_UP));
int i = 10;
while (mgos_gpio_read(BTN_GPIO) == BTN_DOWN && i > 0) {
mgos_msleep(10);
i--;
}
if (i == 0) {
s_failsafe_mode = true;
#if LED_GPIO >= 0
mgos_gpio_setup_output(LED_GPIO, LED_ON);
#endif
}
#endif
}

static void DoReset(void *arg) {
intptr_t out_gpio = (intptr_t) arg;
if (out_gpio >= 0) {
Expand Down Expand Up @@ -856,34 +830,6 @@ extern "C" bool mgos_ota_merge_fs_should_copy_file(const char *old_fs_path,
return (stat(buf, &st) != 0);
}

bool WipeDevice() {
static const char *s_wipe_files[] = {
"conf2.json",
"conf9.json",
KVS_FILE_NAME,
AUTH_FILE_NAME,
};
bool wiped = false;
for (const char *wipe_fn : s_wipe_files) {
if (remove(wipe_fn) == 0) wiped = true;
}
#if defined(MGOS_HAVE_VFS_FS_SPIFFS) || defined(MGOS_HAVE_VFS_FS_LFS)
if (wiped) {
mgos_vfs_gc("/");
}
#endif
return wiped;
}

bool IsSoftReboot() {
#if CS_PLATFORM == CS_P_ESP8266
const struct rst_info *ri = system_get_rst_info();
return (ri->reason == REASON_SOFT_RESTART);
#else
return false;
#endif
}

static void HTTPHandler(struct mg_connection *nc, int ev, void *ev_data,
void *user_data) {
if (ev != MG_EV_HTTP_REQUEST) return;
Expand Down Expand Up @@ -912,15 +858,12 @@ void InitApp() {
struct mg_http_endpoint_opts opts = {};
mgos_register_http_endpoint_opt("/", HTTPHandler, opts);

if (s_failsafe_mode) {
if (WipeDevice()) {
LOG(LL_INFO, ("== Wiped config, rebooting"));
mgos_system_restart_after(100);
return;
} else {
LOG(LL_INFO, ("== Failsafe mode, not initializing the app"));
shelly_rpc_service_init(nullptr, nullptr, nullptr);
}
if (IsFailsafeMode()) {
LOG(LL_INFO, ("== Failsafe mode, not initializing the app"));
shelly_rpc_service_init(nullptr, nullptr, nullptr);
#if LED_GPIO >= 0
mgos_gpio_setup_output(LED_GPIO, LED_ON);
#endif
return;
}

Expand Down Expand Up @@ -981,9 +924,7 @@ void InitApp() {

} // namespace shelly

extern "C" {
enum mgos_app_init_result mgos_app_init(void) {
extern "C" enum mgos_app_init_result mgos_app_init(void) {
shelly::InitApp();
return MGOS_APP_INIT_SUCCESS;
}
}
5 changes: 1 addition & 4 deletions src/shelly_main.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "shelly_input.hpp"
#include "shelly_output.hpp"
#include "shelly_pm.hpp"
#include "shelly_reset.hpp"
#include "shelly_temp_sensor.hpp"

#define AUTH_USER "admin"
Expand Down Expand Up @@ -55,10 +56,6 @@ void HandleInputResetSequence(Input *in, int out_gpio, Input::Event ev,

void RestartService();

bool WipeDevice();

bool IsSoftReboot();

StatusOr<int> GetSystemTemperature();

#define SHELLY_SERVICE_FLAG_UPDATE (1 << 0)
Expand Down

0 comments on commit 68d1570

Please sign in to comment.