Browse Source

Add Deep sleep mode & provisioning functional verification

Also:
- Device registration during provisioning
- Add WiFi.setHostname call
- Deep sleep tweaks
- Move heartbeat() to the correct place
- Start of display configuration (#1)
- Refactor WiFi init into zw_wifi, consume also in prov func verification
tags/v0.2.3.0
Ryan Joseph 1 year ago
parent
commit
d7db7d1371
12 changed files with 190 additions and 59 deletions
  1. +1
    -1
      scripts/otp-generate.pl
  2. +2
    -1
      scripts/update.pl
  3. +42
    -40
      zero_watch.ino
  4. +2
    -1
      zw_common.h
  5. +26
    -3
      zw_displays.cpp
  6. +2
    -0
      zw_displays.h
  7. +57
    -13
      zw_provision.cpp
  8. +13
    -0
      zw_provision.h
  9. +6
    -0
      zw_redis.cpp
  10. +2
    -0
      zw_redis.h
  11. +28
    -0
      zw_wifi.cpp
  12. +9
    -0
      zw_wifi.h

+ 1
- 1
scripts/otp-generate.pl View File

@@ -50,4 +50,4 @@ for ($i = 0; $i < length($targetHost); $i++) {

$currentLc = $currentLc & 0xFFFF;
print STDERR "[OTP] F: $currentLc\n";
print "$currentLc";
print "$currentLc\n";

+ 2
- 1
scripts/update.pl View File

@@ -5,8 +5,9 @@ my $lp = $0;
$lp =~ s/$bn//ig;
my $updateJson = `$lp/release-info.pl $ARGV[0] $ARGV[1]`;
my $otp = `$lp/otp-generate.pl $ARGV[2] $ARGV[3] 2> /dev/null`;
chomp($otp);
$updateJson =~ s/(\"otp\":\s+)(0)/\1$otp/ig;
$updateJson =~ s/\s*//ig;
my $cmd = "redis-cli -h 192.168.1.252 -a '$ARGV[3]' set $ARGV[2]:config:update '$updateJson'";
print "SENDING:\n\t$cmd\n";
print "RESULT:\n\t" . `$cmd`;
print "RESULT:\n\t" . `$cmd`;

+ 42
- 40
zero_watch.ino View File

@@ -13,6 +13,9 @@
#include "zw_displays.h"
#include "zw_otp.h"
#include "zw_ota.h"
#include "zw_wifi.h"

#define DEEP_SLEEP_MODE_ENABLE 1

#define CONTROL_POINT_SEP_CHAR '#'
#define SER_BAUD 115200
@@ -31,10 +34,13 @@ ZWAppConfig gConfig = {
.refresh = DEF_REFRESH,
.debug = DEBUG,
.publishLogs = false,
.pauseRefresh = false};
.pauseRefresh = false,
.deepSleepMode = DEEP_SLEEP_MODE_ENABLE
};
ZWRedis *gRedis = NULL;
DisplaySpec *gDisplays = NULL;
void (*gPublishLogsEmit)(const char *fmt, ...);
unsigned long gBootCount = 0;
unsigned long long gSecondsSinceBoot = 0;
unsigned long long gLastRefreshTick = 0;
int _last_free = 0;
@@ -45,33 +51,6 @@ hw_timer_t *__isrTimer = NULL;
portMUX_TYPE __isrMutex = portMUX_INITIALIZER_UNLOCKED;
volatile unsigned long __isrCount = 0;

bool wifi_init()
{
dprint("Disabling WiFi AP\n");
WiFi.mode(WIFI_MODE_STA);
WiFi.enableAP(false);

auto bstat = WiFi.begin(EEPROMCFG_WiFiSSID, EEPROMCFG_WiFiPass);
dprint("Connecting to to '%s'...\n", EEPROMCFG_WiFiSSID);
dprint("WiFi.begin() -> %d\n", bstat);

runAnimation(gDisplays[0].disp, "light_loop", true);
gDisplays[0].disp->clear();
gDisplays[0].disp->showNumberHexEx(0xffff, 64, true);
// TODO: timeout!
int _c = 0;
while (WiFi.status() != WL_CONNECTED)
{
gDisplays[0].disp->showNumberHexEx((++_c << 8) + 0xff, 64, true);
}

zlog("WiFi adapter %s connected to '%s' as %s\n", WiFi.macAddress().c_str(),
EEPROMCFG_WiFiSSID, WiFi.localIP().toString().c_str());
gDisplays[0].disp->showNumberHexEx(0xFF00 | WiFi.status(), 64, false);

return true;
}

bool processGetValue(String &imEmit, ZWRedisResponder &responder)
{
bool matched = true;
@@ -247,6 +226,12 @@ bool processUpdate(String &updateJson, ZWRedisResponder &responder)
return false;
}

bool processDisplaysConfig(String &updateJson, ZWRedisResponder &responder)
{
dprint("GOT DISPLAYS CONFIG: %s\n", updateJson.c_str());
return false;
}

void redis_publish_logs_emit(const char *fmt, ...)
{
// this function should never be called before gRedis is valid
@@ -316,6 +301,8 @@ void readConfigAndUserKeys()

UPDATE_IF_CHANGED(pauseRefresh);

UPDATE_IF_CHANGED(deepSleepMode);

if (dirty)
{
auto badCount = gRedis->updateConfig(curCfg);
@@ -330,6 +317,7 @@ void readConfigAndUserKeys()
gRedis->handleUserKey(":config:getValue", processGetValue);
gRedis->handleUserKey(":config:controlPoint", processControlPoint);
gRedis->handleUserKey(":config:update", processUpdate);
gRedis->handleUserKey(":config:displays", processDisplaysConfig);
}
}

@@ -343,9 +331,9 @@ void heartbeat()
zlog("WARNING: heartbeat failed!\n");
}

if ((__hb_count++ % CHECKIN_EVERY_X_REFRESH))
if (gConfig.deepSleepMode || (__hb_count++ % CHECKIN_EVERY_X_REFRESH))
{
gRedis->checkin(gSecondsSinceBoot, WiFi.localIP().toString().c_str(),
gRedis->checkin(gConfig.deepSleepMode ? gBootCount : gSecondsSinceBoot, WiFi.localIP().toString().c_str(),
immediateLatency, gUDRA, gConfig.refresh * CHECKIN_EVERY_X_REFRESH * CHECKIN_EXPIRY_MULT);
}
}
@@ -363,6 +351,14 @@ void tick(bool forceUpdate = false)
updateDisplay(w);

_last_free = ESP.getFreeHeap();

if (gConfig.deepSleepMode) {
heartbeat();
zlog("Deep-sleeping for %ds...\n", gConfig.refresh);
Serial.flush();
esp_sleep_enable_timer_wakeup(gConfig.refresh * 1e6);
esp_deep_sleep_start();
}
}

void __isr()
@@ -387,8 +383,8 @@ void loop()
{
gLastRefreshTick = gSecondsSinceBoot;
readConfigAndUserKeys();
heartbeat();
tick();
heartbeat();
}
}

@@ -400,19 +396,24 @@ void setup()
verifyProvisioning();

zlog("\n%s v" ZEROWATCH_VER " starting...\n", gHostname.c_str());
#if DEEP_SLEEP_MODE_ENABLE
zlog("Deep sleep mode enabled by default\n");
#endif

if (!(gDisplays = zwdisplayInit(gHostname)))
{
dprint("Display init failed, halting forever\n");
__haltOrCatchFire();
}
if (!gConfig.deepSleepMode) {
auto verNum = String(ZEROWATCH_VER);
verNum.replace(".", "");
gDisplays[0].disp->showNumberDec(verNum.toInt(), true);
delay(2000);
}

auto verNum = String(ZEROWATCH_VER);
verNum.replace(".", "");
gDisplays[0].disp->showNumberDec(verNum.toInt(), true);
delay(2000);

if (!wifi_init())
if (!zwWiFiInit(gHostname.c_str(), gConfig))
{
dprint("WiFi init failed, halting forever\n");
__haltOrCatchFire();
@@ -436,8 +437,8 @@ void setup()
readConfigAndUserKeys();

zlog("Fully initialized! (debug %sabled)\n", gConfig.debug ? "en" : "dis");
if (gConfig.debug)
if (gConfig.debug && !gConfig.deepSleepMode)
delay(5000);

gPublishLogsEmit = redis_publish_logs_emit;
@@ -447,8 +448,9 @@ void setup()
timerAlarmWrite(__isrTimer, 1000000, true);
timerAlarmEnable(__isrTimer);

zlog("Boot count: %d\n", gRedis->incrementBootcount());
gBootCount = gRedis->incrementBootcount();
zlog("%s v" ZEROWATCH_VER " up & running\n", gHostname.c_str());
zlog("Boot count: %lu\n", gBootCount);

tick(true);
}

+ 2
- 1
zw_common.h View File

@@ -1,7 +1,7 @@
#ifndef __ZW_COMMON__H__
#define __ZW_COMMON__H__

#define ZEROWATCH_VER "0.2.2.3"
#define ZEROWATCH_VER "0.2.2.15"
#define DEBUG 1

struct ZWAppConfig
@@ -11,6 +11,7 @@ struct ZWAppConfig
bool debug;
bool publishLogs;
bool pauseRefresh;
bool deepSleepMode;
};

void __haltOrCatchFire();


+ 26
- 3
zw_displays.cpp View File

@@ -1,5 +1,6 @@
#include "zw_displays.h"
#include "zw_logging.h"
#include "zw_common.h"

// TODO: get rid of these externs! (and associated includes!)
extern unsigned long immediateLatency;
@@ -22,6 +23,9 @@ AnimStep light_loop[] = {{0, 1}, {1, 1}, {2, 1}, {3, 1}, {3, 2}, {3, 4}, {3, 8},

void __runAnimation(TM1637Display *d, AnimStep *anim, bool cE = false, int s = 0)
{
if (gConfig.deepSleepMode)
return;

uint8_t v[] = {0, 0, 0, 0};
for (AnimStep *w = anim; w->bits != -1 && w->digit != -1; w++)
{
@@ -77,6 +81,7 @@ DisplaySpec gDisplays_EZERO[] = {

DisplaySpec gDisplays_ETEST[] = {
{26, 25, nullptr, {"zero:sensor:DHTXX:temperature_fahrenheit:.list", 0, 11, 0.0, 0, noop, d_tempf}},
{33, 32, nullptr, {"zero:sensor:DHTXX:relative_humidity:.list", 0, 5, 0.0, 0, noop, d_humidPercent}},
{-1, -1, nullptr, {nullptr, -1, -1, -1.0, -1, noop, d_def}}};

DisplaySpec *zwdisplayInit(String &hostname)
@@ -91,7 +96,7 @@ DisplaySpec *zwdisplayInit(String &hostname)
{
retSpec = gDisplays_AMINI;
}
else if (hostname.equals("etest"))
else if (hostname.equals("etest") || hostname.equals("espresso"))
{
retSpec = gDisplays_ETEST;
}
@@ -108,12 +113,13 @@ DisplaySpec *zwdisplayInit(String &hostname)
zlog("Setting up display #%d with clock=%d DIO=%d\n",
(int)(spec - retSpec), spec->clockPin, spec->dioPin);
spec->disp = new TM1637Display(spec->clockPin, spec->dioPin);
spec->disp->clear();
if (!gConfig.deepSleepMode)
spec->disp->clear();
spec->disp->setBrightness(gConfig.brightness);
__runAnimation(spec->disp, full_loop, false, 5);
}

if (gConfig.debug)
if (gConfig.debug && !gConfig.deepSleepMode)
{
EXEC_ALL_DISPS(retSpec, showNumberDecEx((int)(walk - retSpec), 0,
false, 4 - (int)(walk - retSpec), (int)(walk - retSpec)));
@@ -212,3 +218,20 @@ void demoMode(DisplaySpec *displayListStart)
zlog("Demo! Bleep bloop!\n");
EXEC_WITH_EACH_DISP(displayListStart, demoForDisp);
}

String displayConfigAsJson(DisplaySpec* displayListStart)
{
#define BUFLEN 512
static char _buf[BUFLEN];
bzero(_buf, BUFLEN);
String build = "[";
for (DisplaySpec *walk = displayListStart; walk->clockPin != -1 && walk->dioPin != -1; walk++)
{
snprintf(_buf, BUFLEN,
"{\"clockPin\":%d,\"dioPin\":%d,\"listKey\":\"%s\",\"startIdx\":%d,\"endIdx\":%d}",
walk->clockPin, walk->dioPin, walk->spec.listKey, walk->spec.startIdx, walk->spec.endIdx);
build += String(_buf) + ((walk+1)->clockPin != -1 ? "," : "");
}
build += "]";
return build;
}

+ 2
- 0
zw_displays.h View File

@@ -41,6 +41,8 @@ void runAnimation(TM1637Display *d, String animation, bool cE = false, int s = 0

void demoMode(DisplaySpec* displayListStart);

String displayConfigAsJson(DisplaySpec* displayListStart);

#define EXEC_ALL_DISPS(DISPLIST_START, EXEC_ME) \
do \
{ \


+ 57
- 13
zw_provision.cpp View File

@@ -1,5 +1,9 @@
#include "zw_provision.h"
#include "zw_logging.h"
#include "zw_wifi.h"
#include "zw_redis.h"

#include <WiFi.h>

char *EEPROMCFG_WiFiSSID = NULL;
char *EEPROMCFG_WiFiPass = NULL;
@@ -120,7 +124,7 @@ void provisionUnit()
dprint("\tOTA host: \t%s\n", ZWPROV_OTA_HOST);
dprint("***** WILL COMMIT THESE VALUES TO EEPROM IN %d SECONDS.... *****\n", ZWPROV_MODE_WRITE_DELAY);
delay(ZWPROV_MODE_WRITE_DELAY * 1000);
dprint("ZeroWatch provisioning mode writing provisioning values...\n");
dprint("***** COMMITTING ABOVE VALUES TO EEPROM: *****\n");

auto hostname = String(ZWPROV_HOSTNAME);
zwassert(EEPROM.writeString(ZW_EEPROM_HOSTNAME_ADDR, hostname) == hostname.length());
@@ -135,7 +139,7 @@ void provisionUnit()
lengths[4] = strlen(ZWPROV_REDIS_PASSWORD);
lengths[5] = strlen(ZWPROV_OTA_HOST);

dprint("ZeroWatch provisioning LENGTHS: ");
dprint("*** ZeroWatch provisioning: lengths: ");
for (int i = 0; i < CFG_ELEMENTS; i++)
{
dprint("%d ", lengths[i]);
@@ -145,7 +149,7 @@ void provisionUnit()
auto writeLen = EEPROM.writeBytes(CFG_EEPROM_ADDR, lengths, CFG_HEADER_SIZE);
if (writeLen != CFG_HEADER_SIZE)
{
dprint("ZeroWatch provisioning ERROR: Config write (%d) failed\n", writeLen);
dprint("*** ZeroWatch provisioning ERROR: Config write (%d) failed\n", writeLen);
return;
}

@@ -161,12 +165,12 @@ void provisionUnit()

for (int i = 0; i < CFG_ELEMENTS; i++)
{
dprint("ZeroWatch provisioning: Writing %d bytes of element %d to address %d\n",
dprint("*** ZeroWatch provisioning: Writing %d bytes of element %d to address %d\n",
lengths[i], i, offset);
writeLen = EEPROM.writeBytes(offset, ptrptrs[i], lengths[i]);
if (writeLen != lengths[i])
{
dprint("ZeroWatch provisioning ERROR: element %d failed to write (%d != %d)\n", i, writeLen, lengths[i]);
dprint("*** ZeroWatch provisioning ERROR: element %d failed to write (%d != %d)\n", i, writeLen, lengths[i]);
return;
}
offset += lengths[i];
@@ -174,29 +178,69 @@ void provisionUnit()

if (EEPROM.commit())
{
dprint("ZeroWatch provisioning: Write complete, verifying data...\n");

// TODO: hostname check
// TODO: FUNCTIONAL verification (?) (wifi, redis) - optional maybe
dprint("*** ZeroWatch provisioning: Write complete, verifying data...\n");

if (checkUnitProvisioning())
{
auto hnVerify = EEPROM.readString(ZW_EEPROM_HOSTNAME_ADDR);
zwassert(!memcmp(ZWPROV_HOSTNAME, hnVerify.c_str(), strlen(ZWPROV_HOSTNAME)));
zwassert(!memcmp(ptrptrs[0], EEPROMCFG_WiFiSSID, lengths[0]));
zwassert(!memcmp(ptrptrs[1], EEPROMCFG_WiFiPass, lengths[1]));
zwassert(!memcmp(ptrptrs[2], EEPROMCFG_RedisHost, lengths[2]));
zwassert(!memcmp(ptrptrs[3], &EEPROMCFG_RedisPort, lengths[3]));
zwassert(!memcmp(ptrptrs[4], EEPROMCFG_RedisPass, lengths[4]));
zwassert(!memcmp(ptrptrs[5], EEPROMCFG_OTAHost, lengths[5]));
dprint("ZeroWatch provisioning SUCCESS!\n");

dprint("*** ZeroWatch provisioning: data verified correctly!\n");

#if ZEROWATCH_PROVISIONING_OPTION_FUNCTIONAL_VERIFICATION
dprint("*** ZeroWatch provisioning: starting functional check...\n");

ZWAppConfig blankConfig;
if (!zwWiFiInit(hnVerify.c_str(), blankConfig))
{
dprint("*** ZeroWatch provisioning: WiFi check failed\n");
__haltOrCatchFire();
}

ZWRedisHostConfig redisConfig = {
.host = EEPROMCFG_RedisHost,
.port = EEPROMCFG_RedisPort,
.password = EEPROMCFG_RedisPass};
ZWRedis redisCheck(hnVerify, redisConfig);

if (!redisCheck.connect())
{
dprint("*** ZeroWatch provisioning: Redis check failed\n");
__haltOrCatchFire();
}

if (ZWPROV_FUNCTIONAL_VERIFICATION_REGISTRY_DEVICE_IN)
{
dprint("*** ZeroWatch provisioning: registering device in '%s'...\n",
ZWPROV_FUNCTIONAL_VERIFICATION_REGISTRY_DEVICE_IN);

if (!redisCheck.registerDevice(ZWPROV_FUNCTIONAL_VERIFICATION_REGISTRY_DEVICE_IN,
ZWPROV_HOSTNAME, WiFi.macAddress().c_str()))
{
dprint("*** ZeroWatch provisioning WARNING: registration failed (or already registered)\n");
}
}

#else
dprint("*** ZeroWatch provisioning WARNING: functional verification will be SKIPPED\n");
#endif

dprint("\n\n***** ZeroWatch provisioning completed successfully!!\n");
}
else
{
dprint("ZeroWatch provisioning FAILED! :shrug:\n");
dprint("*** ZeroWatch provisioning FAILED! :shrug:\n");
}
}
else
{
dprint("ZeroWatch provisioning: commit failed\n");
dprint("*** ZeroWatch provisioning: commit failed\n");
}

__haltOrCatchFire();
@@ -225,7 +269,7 @@ void verifyProvisioning()
}

// TODO: better place for this? I'm sure there is one...
if (!(gHostname.equals("ezero") || gHostname.equals("amini") || gHostname.equals("etest")))
if (!(gHostname.equals("ezero") || gHostname.equals("amini") || gHostname.equals("etest") || gHostname.equals("espresso")))
{
zlog("ERROR: Unrecognized hostname '%s', halting forever!\n", __hnShadowBuf);
__haltOrCatchFire();


+ 13
- 0
zw_provision.h View File

@@ -5,7 +5,19 @@

// provisioning definitions
#define ZEROWATCH_PROVISIONING_MODE 0

#if ZEROWATCH_PROVISIONING_MODE

#define ZEROWATCH_PROVISIONING_OPTION_FUNCTIONAL_VERIFICATION 1
#define ZEROWATCH_PROVISIONING_OPTION_REGISTER_DEVICE 1

#if ZEROWATCH_PROVISIONING_OPTION_FUNCTIONAL_VERIFICATION && \
ZEROWATCH_PROVISIONING_OPTION_REGISTER_DEVICE
#define ZWPROV_FUNCTIONAL_VERIFICATION_REGISTRY_DEVICE_IN "rpjios.__meta.hostreg"
#else
#define ZWPROV_FUNCTIONAL_VERIFICATION_REGISTRY_DEVICE_IN NULL
#endif

// these will be written to EEPROM
#define ZWPROV_HOSTNAME ""
#define ZWPROV_WIFI_SSID ""
@@ -14,6 +26,7 @@
#define ZWPROV_REDIS_PASSWORD ""
#define ZWPROV_REDIS_PORT 6379
#define ZWPROV_OTA_HOST ""

#endif

#define ZW_EEPROM_SIZE 32


+ 6
- 0
zw_redis.cpp View File

@@ -102,6 +102,7 @@ ZWAppConfig ZWRedis::readConfig()
_lastReadConfig.debug = (bool)dg.toInt();
_lastReadConfig.publishLogs = (bool)pl.toInt();
_lastReadConfig.pauseRefresh = (bool)pu.toInt();
_lastReadConfig.deepSleepMode = (bool)connection.redis->get(REDIS_KEY(":config:deepSleepMode")).toInt();

return _lastReadConfig;
}
@@ -197,6 +198,11 @@ bool ZWRedis::clearControlPoint()
return connection.redis->del(REDIS_KEY(":config:controlPoint"));
}

bool ZWRedis::registerDevice(const char* registryName, const char* hostname, const char* ident)
{
return connection.redis->hset(registryName, ident, hostname);
}

void ZWRedisResponder::setValue(const char *format, ...)
{
#define BUFLEN 2048


+ 2
- 0
zw_redis.h View File

@@ -95,6 +95,8 @@ public:

bool clearControlPoint();

bool registerDevice(const char* registryName, const char* hostname, const char* ident);

private:
ZWAppConfig _lastReadConfig;
};


+ 28
- 0
zw_wifi.cpp View File

@@ -0,0 +1,28 @@
#include <WiFi.h>
#include "zw_logging.h"
#include "zw_displays.h"
#include "zw_provision.h"

bool zwWiFiInit(const char *hostname, ZWAppConfig config)
{
dprint("Disabling WiFi AP\n");
WiFi.mode(WIFI_MODE_STA);
WiFi.enableAP(false);

if (!WiFi.setHostname(hostname))
{
dprint("WARNING: failed to set hostname\n");
}

auto bstat = WiFi.begin(EEPROMCFG_WiFiSSID, EEPROMCFG_WiFiPass);
dprint("Connecting to to '%s'...\n", EEPROMCFG_WiFiSSID);
dprint("WiFi.begin() -> %d\n", bstat);

// TODO: timeout!
while (WiFi.status() != WL_CONNECTED) {}

zlog("WiFi adapter %s connected to '%s' as %s\n", WiFi.macAddress().c_str(),
EEPROMCFG_WiFiSSID, WiFi.localIP().toString().c_str());

return true;
}

+ 9
- 0
zw_wifi.h View File

@@ -0,0 +1,9 @@
#ifndef __ZW_WIFI__H__
#define __ZW_WIFI__H__

#include "zw_common.h"
#include "zw_logging.h"

bool zwWiFiInit(const char *hostname, ZWAppConfig config);

#endif

Loading…
Cancel
Save