Skip to content

Commit

Permalink
New features: deep sleep + MQTT LWT/birth messages (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
sidoh authored Mar 30, 2020
1 parent 7b21fd8 commit 577a801
Show file tree
Hide file tree
Showing 18 changed files with 3,552 additions and 1,923 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Template-oriented driver for e-paper displays using Arduino. Define a layout wi
- [Setup](#setup)
- [Requirements](#requirements)
- [Quickstart](#quickstart)
- [Deep Sleep](#deep-sleep)
- [Concepts](#concepts)
- [Variables](#variables)
- [Regions](#regions)
Expand Down Expand Up @@ -61,6 +62,17 @@ The [examples directory](./examples) has a few sample templates. Here are a few
1. Setup WiFi. A setup AP will appear named `epaper_XXXXXX`. The default password is **waveshare**.
1. Visit the Web UI to configure further.

## Deep Sleep

e-paper templates can function in _deep sleep_ mode. When configured, the system will continuously:

1. Wake from sleep
2. Check if a configurable GPIO pin is set. If it is, stays awake until next reboot
3. Otherwise, stay awake for a configurable period to receive updates and refresh the screen.
4. Put both the ESP32 and the e-paper display into deep sleep mode.

This is useful if trying to conserve power. Deep sleep mode can be configured in the "Power" tab within the web UI.

## SPI Bus and Pin Selection

The ESP32 has 4 SPI busses, however, one is reserved for the chips Flash. (SPI0) and another is often used by PSRAM (SP1).
Expand Down
109 changes: 91 additions & 18 deletions lib/HTTP/EpaperWebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,21 @@ EpaperWebServer::EpaperWebServer(
settings.web.port == 0 ? 80 : settings.web.port, authProvider))
, port(settings.web.port)
, changeFn(nullptr)
, cancelSleepFn(nullptr)
, wsServer("/socket")
{
driver->onVariableUpdate(std::bind(&EpaperWebServer::handleVariableUpdate, this, _1, _2));
driver->onRegionUpdate(std::bind(&EpaperWebServer::handleRegionUpdate, this, _1, _2, _3));
, deepSleepActive(false) {
driver->onVariableUpdate(
std::bind(&EpaperWebServer::handleVariableUpdate, this, _1, _2));
driver->onRegionUpdate(
std::bind(&EpaperWebServer::handleRegionUpdate, this, _1, _2, _3));
}

EpaperWebServer::~EpaperWebServer() { server.reset(); }

void EpaperWebServer::setDeepSleepActive(bool deepSleepActive) {
this->deepSleepActive = deepSleepActive;
}

uint16_t EpaperWebServer::getPort() const { return port; }

void EpaperWebServer::handleClient() { wsServer.cleanupClients(); }
Expand Down Expand Up @@ -119,9 +126,13 @@ void EpaperWebServer::begin() {
.on(HTTP_GET, std::bind(&EpaperWebServer::handleGetScreens, this, _1));

server.buildHandler("/api/v1/resolve_variables")
.on(HTTP_GET, std::bind(&EpaperWebServer::handleResolveVariables, this, _1));
.on(HTTP_GET,
std::bind(&EpaperWebServer::handleResolveVariables, this, _1));

server.buildHandler("/firmware").handleOTA();
server.buildHandler("/firmware")
.on(HTTP_POST,
std::bind(&EpaperWebServer::handleFirmwareUpdateComplete, this, _1),
std::bind(&EpaperWebServer::handleFirmwareUpdateUpload, this, _1));

server.onNotFound([this](AsyncWebServerRequest* request) {
if (request->url() == "/" || request->url().startsWith("/app")) {
Expand All @@ -135,17 +146,19 @@ void EpaperWebServer::begin() {
});

wsServer.onEvent([this](AsyncWebSocket* server,
AsyncWebSocketClient* client,
AwsEventType type,
void* arg,
uint8_t* data,
size_t len
) {
AsyncWebSocketClient* client,
AwsEventType type,
void* arg,
uint8_t* data,
size_t len) {
if (type == WS_EVT_DATA) {
AwsFrameInfo* info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
if (info->final && info->index == 0 && info->len == len &&
info->opcode == WS_TEXT) {
StaticJsonDocument<1024> reqBuffer;
auto err = deserializeJson(reqBuffer, reinterpret_cast<const char*>(data), len);
auto err = deserializeJson(reqBuffer,
reinterpret_cast<const char*>(data),
len);

if (err) {
Serial.println(reinterpret_cast<const char*>(data));
Expand All @@ -163,7 +176,9 @@ void EpaperWebServer::begin() {
size_t len = measureJson(responseBuffer);

AsyncWebSocketMessageBuffer* buffer = server->makeBuffer(len);
serializeJson(responseBuffer, reinterpret_cast<char*>(buffer->get()), len+1);
serializeJson(responseBuffer,
reinterpret_cast<char*>(buffer->get()),
len + 1);
client->text(buffer);
}
}
Expand All @@ -176,7 +191,48 @@ void EpaperWebServer::begin() {
server.begin();
}

void EpaperWebServer::handleVariableUpdate(const String& name, const String& value) {
void EpaperWebServer::handleFirmwareUpdateUpload(RequestContext& request) {
if (request.upload.index == 0) {
if (request.rawRequest->contentLength() > 0) {
if (this->cancelSleepFn) {
this->cancelSleepFn();
}

Update.begin(request.rawRequest->contentLength());
#if defined(ESP8266)
Update.runAsync(true);
#endif
}
}

if (Update.size() > 0) {
if (Update.write(request.upload.data, request.upload.length) != request.upload.length) {
Update.printError(Serial);

#if defined(ESP32)
Update.abort();
#endif
}

if (request.upload.isFinal) {
if (! Update.end(true)) {
Update.printError(Serial);
#if defined(ESP32)
Update.abort();
#endif
}
}
}
}

void EpaperWebServer::handleFirmwareUpdateComplete(RequestContext& request) {
request.rawRequest->send(200, "text/plain", "success");
delay(1000);
ESP.restart();
}

void EpaperWebServer::handleVariableUpdate(
const String& name, const String& value) {
StaticJsonDocument<128> responseBuffer;
responseBuffer["type"] = "variable";
JsonObject body = responseBuffer.createNestedObject("body");
Expand All @@ -185,12 +241,15 @@ void EpaperWebServer::handleVariableUpdate(const String& name, const String& val

size_t len = measureJson(responseBuffer);
AsyncWebSocketMessageBuffer* buffer = wsServer.makeBuffer(len);
serializeJson(responseBuffer, reinterpret_cast<char*>(buffer->get()), len+1);
serializeJson(responseBuffer,
reinterpret_cast<char*>(buffer->get()),
len + 1);

wsServer.textAll(buffer);
}

void EpaperWebServer::handleRegionUpdate(const String& regionId, const String& variableName, const String& value) {
void EpaperWebServer::handleRegionUpdate(
const String& regionId, const String& variableName, const String& value) {
StaticJsonDocument<128> responseBuffer;
responseBuffer["type"] = "region";
JsonObject body = responseBuffer.createNestedObject("body");
Expand All @@ -200,7 +259,9 @@ void EpaperWebServer::handleRegionUpdate(const String& regionId, const String& v

size_t len = measureJson(responseBuffer);
AsyncWebSocketMessageBuffer* buffer = wsServer.makeBuffer(len);
serializeJson(responseBuffer, reinterpret_cast<char*>(buffer->get()), len+1);
serializeJson(responseBuffer,
reinterpret_cast<char*>(buffer->get()),
len + 1);

wsServer.textAll(buffer);
}
Expand All @@ -225,9 +286,15 @@ void EpaperWebServer::handlePostSystem(RequestContext& request) {
if (strCommand.equalsIgnoreCase("reboot")) {
ESP.restart();
request.response.json[F("success")] = true;
} else if (strCommand.equalsIgnoreCase("cancel_sleep")) {
if (this->cancelSleepFn != nullptr) {
this->cancelSleepFn();
}
request.response.json[F("success")] = true;
} else {
request.response.json[F("error")] = F("Unhandled command");
request.response.setCode(400);
return;
}
}

Expand All @@ -239,6 +306,8 @@ void EpaperWebServer::handleGetSystem(RequestContext& request) {
request.response.json["variant"] = QUOTE(FIRMWARE_VARIANT);
request.response.json["free_heap"] = freeHeap;
request.response.json["sdk_version"] = ESP.getSdkVersion();
request.response.json["uptime"] = millis();
request.response.json["deep_sleep_active"] = this->deepSleepActive;
}

void EpaperWebServer::handleDeleteVariable(RequestContext& request) {
Expand Down Expand Up @@ -581,6 +650,10 @@ void EpaperWebServer::onSettingsChange(std::function<void()> changeFn) {
this->changeFn = changeFn;
}

void EpaperWebServer::onCancelSleep(EpaperWebServer::OnCancelSleepFn cancelSleepFn) {
this->cancelSleepFn = cancelSleepFn;
}

void EpaperWebServer::handleGetScreens(RequestContext& request) {
JsonArray screens = request.response.json.createNestedArray(F("screens"));

Expand Down
9 changes: 9 additions & 0 deletions lib/HTTP/EpaperWebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ using RequestContext = RichHttpConfig::RequestContextType;
class EpaperWebServer {
public:
using OnChangeFn = std::function<void()>;
using OnCancelSleepFn = std::function<void()>;

EpaperWebServer(DisplayTemplateDriver*& driver, Settings& settings);
~EpaperWebServer();

void onSettingsChange(OnChangeFn changeFn);
void onCancelSleep(OnCancelSleepFn cancelSleepFn);
void setDeepSleepActive(bool deepSleepActive);
void begin();
uint16_t getPort() const;
void handleClient();
Expand All @@ -34,7 +37,13 @@ class EpaperWebServer {
RichHttpServer<RichHttpConfig> server;
uint16_t port;
OnChangeFn changeFn;
OnCancelSleepFn cancelSleepFn;
AsyncWebSocket wsServer;
bool deepSleepActive;

// firmware update handlers
void handleFirmwareUpdateUpload(RequestContext& request);
void handleFirmwareUpdateComplete(RequestContext& request);

// Variable update observer
void handleVariableUpdate(const String& name, const String& value);
Expand Down
48 changes: 41 additions & 7 deletions lib/MQTT/MqttClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,24 @@ void MqttClient::internalCallback(MqttClient* client) {
}
#endif

const char* MqttClient::CONNECTED_STATUS = "connected";
const char* MqttClient::DISCONNECTED_STATUS = "disconnected";

MqttClient::MqttClient(
String domain,
uint16_t port,
String variableTopicPattern,
String username,
String password
String password,
String clientStatusTopic
) : port(port)
, domain(domain)
, username(username)
, password(password)
, lastConnectAttempt(0)
, variableUpdateCallback(NULL)
, topicPattern(variableTopicPattern)
, clientStatusTopic(clientStatusTopic)
{
this->topicPatternBuffer = new char[topicPattern.length() + 1];
strcpy(this->topicPatternBuffer, this->topicPattern.c_str());
Expand All @@ -46,6 +51,7 @@ MqttClient::~MqttClient() {
delete this->topicPatternTokens;
delete this->topicPatternBuffer;

updateStatus(MqttClient::DISCONNECTED_STATUS);
mqttClient.disconnect();

#if defined(ESP32)
Expand All @@ -70,6 +76,17 @@ void MqttClient::begin() {
timersMap.insert(std::make_pair(reconnectTimer, this));
#endif

if (this->clientStatusTopic.length() > 0) {
mqttClient.setWill(
this->clientStatusTopic.c_str(),
// QoS = 1 (at least once)
1,
// retain = true
true,
MqttClient::DISCONNECTED_STATUS
);
}

// Setup callbacks
mqttClient.onConnect(std::bind(&MqttClient::connectCallback, this, _1));
mqttClient.onDisconnect(std::bind(&MqttClient::disconnectCallback, this, _1));
Expand Down Expand Up @@ -109,14 +126,31 @@ void MqttClient::disconnectCallback(AsyncMqttClientDisconnectReason reason) {
}

void MqttClient::connectCallback(bool sessionPresent) {
String topic = this->topicPattern;
topic.replace(String(":") + MQTT_TOPIC_VARIABLE_NAME_TOKEN, "+");
if (this->topicPattern.length() > 0) {
String topic = this->topicPattern;
topic.replace(String(":") + MQTT_TOPIC_VARIABLE_NAME_TOKEN, "+");

#ifdef MQTT_DEBUG
printf_P(PSTR("MqttClient - subscribing to topic: %s\n"), topic.c_str());
#endif
#ifdef MQTT_DEBUG
printf_P(PSTR("MqttClient - subscribing to topic: %s\n"), topic.c_str());
#endif

mqttClient.subscribe(topic.c_str(), 0);
mqttClient.subscribe(topic.c_str(), 0);
}

updateStatus(MqttClient::CONNECTED_STATUS);
}

void MqttClient::updateStatus(const char* status) {
if (this->clientStatusTopic.length() > 0) {
mqttClient.publish(
this->clientStatusTopic.c_str(),
// QoS = 1 (at least once)
1,
// retain = true
true,
status
);
}
}

void MqttClient::messageCallback(
Expand Down
8 changes: 7 additions & 1 deletion lib/MQTT/MqttClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@ class MqttClient {
uint16_t port,
String variableTopicPattern,
String username,
String password
String password,
String clientStatusTopic
);
~MqttClient();

void begin();
void onVariableUpdate(TVariableUpdateFn fn);
void updateStatus(const char* status);

static const char* CONNECTED_STATUS;
static const char* DISCONNECTED_STATUS;

private:
AsyncMqttClient mqttClient;
Expand All @@ -52,6 +57,7 @@ class MqttClient {
String topicPattern;
char* topicPatternBuffer;
TokenIterator* topicPatternTokens;
String clientStatusTopic;

void connect();

Expand Down
3 changes: 3 additions & 0 deletions lib/Settings/EnvironmentConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ extern "C" {
#define EPD_DEFAULT_DC_PIN D3
#define EPD_DEFAULT_RST_PIN D4
#define EPD_DEFAULT_BUSY_PIN 4
#define EPD_DEFAULT_SLEEP_OVERRIDE_PIN D1
#elif defined(ESP32)
#define EPD_DEFAULT_SPI_BUS HSPI // HSPI == 2 | VSPI == 3
#define EPD_DEFAULT_DC_PIN 17
#define EPD_DEFAULT_RST_PIN 16
#define EPD_DEFAULT_BUSY_PIN 7
// Pins 34-39 don't have internal pull-up or pull-down resistors
#define EPD_DEFAULT_SLEEP_OVERRIDE_PIN 25
#endif
Loading

0 comments on commit 577a801

Please sign in to comment.