Almost works well now

This commit is contained in:
Arne van Iterson 2024-03-23 00:31:42 +01:00
parent 9d51792402
commit 1db35ad377
8 changed files with 165 additions and 64 deletions

View File

@ -25,23 +25,23 @@ blueprint:
default: 1 default: 1
selector: selector:
number: number:
min: -255 min: -10000
max: 255 max: 10000
display_value_max: display_value_max:
name: Display Value Max name: Display Value Max
description: The maximum value to display description: The maximum value to display
default: 100 default: 100
selector: selector:
number: number:
min: -255 min: -10000
max: 255 max: 10000
display_segments: display_segments:
name: Amount of segments on face name: Amount of segments on face
description: How many values to display between the min and max description: How many values to display between the min and max
default: 10 default: 10
selector: selector:
number: number:
min: 2 min: 1
max: 20 max: 20
variables: variables:
@ -62,5 +62,5 @@ action:
data: data:
topic_template: "{{ 'homeassistant/ead/' ~ topic_name }}" topic_template: "{{ 'homeassistant/ead/' ~ topic_name }}"
qos: "0" qos: "0"
retain: false retain: true
payload_template: '{{ ''{ "name":"'' ~ display_name ~ ''", "val":"'' ~ states(sensor) ~ ''", "unit":"'' ~ display_unit ~ ''", "min":"'' ~ display_value_min ~ ''", "max":"'' ~ display_value_max ~ ''", "segm":"'' ~ display_segments ~ ''"}'' }}' payload_template: '{{ ''{ "name":"'' ~ display_name ~ ''", "val":"'' ~ states(sensor) ~ ''", "unit":"'' ~ display_unit ~ ''", "min":"'' ~ display_value_min ~ ''", "max":"'' ~ display_value_max ~ ''", "segm":"'' ~ display_segments ~ ''"}'' }}'

View File

@ -8,7 +8,7 @@
; Please visit documentation for the other options and examples ; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[env:esp] [env]
platform = espressif32 platform = espressif32
board = esp-wrover-kit board = esp-wrover-kit
framework = arduino framework = arduino
@ -22,3 +22,8 @@ lib_deps =
adafruit/Adafruit GFX Library@^1.11.7 adafruit/Adafruit GFX Library@^1.11.7
knolleary/PubSubClient@^2.8 knolleary/PubSubClient@^2.8
lennarthennigs/Button2@^2.3.2 lennarthennigs/Button2@^2.3.2
bblanchon/ArduinoJson@^7.0.4
[env:dev]
build_type = debug
debug_build_flags = -DDEBUG

View File

@ -8,7 +8,9 @@ EinkAnalogDisplay::EinkAnalogDisplay(GxEPD2_BW<GxEPD2_213_B72, (uint16_t)250U> *
if (GxEPD_DEBUG) if (GxEPD_DEBUG)
{ {
this->display->init(115200); this->display->init(115200);
} else { }
else
{
this->display->init(0); this->display->init(0);
} }
@ -37,7 +39,7 @@ void EinkAnalogDisplay::centerText(const char *text, int16_t x, int16_t y)
display->print(text); display->print(text);
} }
void EinkAnalogDisplay::drawFace(int16_t min, int16_t max, char *name, char* unit, uint8_t lines) void EinkAnalogDisplay::drawFace(int16_t min, int16_t max, const char *name, const char *unit, uint8_t lines)
{ {
this->min = min; this->min = min;
this->max = max; this->max = max;
@ -130,22 +132,24 @@ void EinkAnalogDisplay::drawFace(int16_t min, int16_t max, char *name, char* uni
this->centerText(unit, origin[0], 95); this->centerText(unit, origin[0], 95);
} }
void EinkAnalogDisplay::drawMeta(const char *wireless_str) { void EinkAnalogDisplay::drawMeta(const char *wireless_str, uint8_t page_cur, uint8_t page_total)
// Draw WiFi and Build info {
// Draw WiFi and page info
display->drawBitmap(0, 114, gfx_wireless, 5, 7, GxEPD_BLACK); display->drawBitmap(0, 114, gfx_wireless, 5, 7, GxEPD_BLACK);
display->setFont(&Pixeltype8pt7b); display->setFont(&Pixeltype8pt7b);
display->setCursor(8, 119); display->setCursor(8, 119);
display->print(wireless_str); display->print(wireless_str);
char build_str[] = __DATE__; char page_str[8] = {}; // Should never exceed 8 characters (XX / XX + terminator), more than 99 pages would be impractical
sprintf(page_str, "%d / %d", page_cur, page_total);
int16_t tbx, tby; // Text box x, y int16_t tbx, tby; // Text box x, y
uint16_t tbw, tbh; // Text box W, H uint16_t tbw, tbh; // Text box W, H
display->getTextBounds(build_str, 0, 121, &tbx, &tby, &tbw, &tbh); display->getTextBounds(page_str, 0, 121, &tbx, &tby, &tbw, &tbh);
display->drawBitmap(display->width() - 5, 114, gfx_build, 5, 7, GxEPD_BLACK); display->drawBitmap(display->width() - 5, 114, gfx_build, 5, 7, GxEPD_BLACK);
display->setCursor(display->width() - tbw - 8, 119); display->setCursor(display->width() - tbw - 8, 119);
display->print(build_str); display->print(page_str);
} }
int EinkAnalogDisplay::setArm(float value) int EinkAnalogDisplay::setArm(float value)
@ -158,8 +162,10 @@ int EinkAnalogDisplay::setArm(float value)
// NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin; // NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin;
// new_value = ((old_value - old_min) / (old_max - old_min)) * (new_max - new_min) + new_min // new_value = ((old_value - old_min) / (old_max - old_min)) * (new_max - new_min) + new_min
uint8_t result = round(((float)(value - this->min) / (float)(this->max - this->min)) * 255.0); uint8_t result = round(((float)((float)(value - this->min) / (float)(this->max - this->min)) * (float)DAC_OFFSET) * 255.0);
dacWrite(this->dac, result); dacWrite(this->dac, result);
this->value = value;
return 1; return 1;
} }
else else

View File

@ -32,9 +32,10 @@ private:
public: public:
EinkAnalogDisplay(GxEPD2_BW<GxEPD2_213_B72, (uint16_t)250U> *display, uint8_t dacPin, bool GxEPD_DEBUG = false); EinkAnalogDisplay(GxEPD2_BW<GxEPD2_213_B72, (uint16_t)250U> *display, uint8_t dacPin, bool GxEPD_DEBUG = false);
~EinkAnalogDisplay(); ~EinkAnalogDisplay();
void drawFace(int16_t min, int16_t max, char *name, char *unit, uint8_t lines = 5); void drawFace(int16_t min, int16_t max, const char *name, const char *unit, uint8_t lines);
int setArm(float value); int setArm(float value);
void drawMeta(const char *wireless_str); void drawMeta(const char *wireless_str, uint8_t page_cur, uint8_t page_total);
int16_t value;
}; };
#endif #endif

View File

@ -4,9 +4,6 @@
#include <Arduino.h> #include <Arduino.h>
#include <string.h> #include <string.h>
// Debug
#define DEBUG 1
#if DEBUG #if DEBUG
#define D_SerialBegin(...) Serial.begin(__VA_ARGS__); #define D_SerialBegin(...) Serial.begin(__VA_ARGS__);
#define D_print(...) Serial.print(__VA_ARGS__) #define D_print(...) Serial.print(__VA_ARGS__)
@ -17,23 +14,19 @@
#define D_println(...) #define D_println(...)
#endif #endif
// Wifi
#include <WiFi.h> #include <WiFi.h>
#include <WiFiMulti.h> #include <WiFiMulti.h>
// MQTT
#define MQTT_ADDR "192.168.2.196" #define MQTT_ADDR "192.168.2.196"
#define MQTT_PORT 1883 #define MQTT_PORT 1883
// DAC
#define DAC1 25 #define DAC1 25
#define DAC_OFFSET 1.05 // Analog pointer is low-key garbage
// Buttons
#include "Button2.h" #include "Button2.h"
#define BTN_L 22 #define BTN_L 22
#define BTN_R 21 #define BTN_R 21
// Local helper files
#include "./einkanalog/einkanalog.h" #include "./einkanalog/einkanalog.h"
#include "./helpers/mqtt.h" #include "./helpers/mqtt.h"

View File

@ -6,7 +6,8 @@ MqttTopic::MqttTopic(WiFiClient *espClient) : wifiClient(espClient), client(PubS
server.fromString(MQTT_ADDR); server.fromString(MQTT_ADDR);
this->client.setServer(server, MQTT_PORT); this->client.setServer(server, MQTT_PORT);
this->client.setCallback([&](char *a, byte *b, unsigned int c){ this->callback(a, b, c); }); this->client.setCallback([&](char *a, byte *b, unsigned int c)
{ this->callback(a, b, c); });
} }
MqttTopic::~MqttTopic() MqttTopic::~MqttTopic()
@ -29,22 +30,55 @@ void MqttTopic::callback(char *topic, byte *payload, unsigned int length)
D_print("Topic: "); D_print("Topic: ");
D_println(token); D_println(token);
// Check if subtopic has been found deserializeJson(this->doc, payload);
MqttTopicData result = {
.name = {},
.val = this->doc["val"].as<float>(),
.unit = {},
.min = this->doc["min"].as<signed int>(),
.max = this->doc["max"].as<signed int>(),
.segm = this->doc["segm"].as<unsigned int>()};
// Strings need to be copied to be able to switch pages
strcpy(result.name, this->doc["name"].as<const char *>());
strcpy(result.unit, this->doc["unit"].as<const char *>());
// Check if token exists in topics
if (i == 2) if (i == 2)
{ {
// Check if token exists in topics bool tokenExists = false;
if (std::find(std::begin(topics), std::end(topics), token) != std::end(topics)) size_t j = 0;
for (; j < available; j++)
{
if (strcmp(topics[j], token) == 0)
{
tokenExists = true;
break;
}
}
if (tokenExists)
{ {
// Token exists // Token exists
D_println("Token exists"); D_println("Token exists");
data[j].val = result.val;
} }
else else
{ {
// Add token to topics // Add token to topics
D_print((char*)topics);
strcpy(topics[available], token); strcpy(topics[available], token);
data[available] = result;
available++; available++;
} }
D_println(data[j].name);
D_println(data[j].val);
D_println(data[j].unit);
D_println(data[j].min);
D_println(data[j].max);
D_println(data[j].segm);
D_println();
} }
else else
{ {
@ -53,13 +87,6 @@ void MqttTopic::callback(char *topic, byte *payload, unsigned int length)
return; return;
} }
// char array[length + 1] = {};
// memcpy(array, payload, length);
// array[length] = '\0';
// D_println(String(array).toFloat() / 1000.0);
// ead.setArm(String(array).toFloat() / 1000.0);
} }
void MqttTopic::loop() void MqttTopic::loop()

View File

@ -7,22 +7,47 @@
// MQTT // MQTT
#include <PubSubClient.h> #include <PubSubClient.h>
// JSON
#include <ArduinoJson.h>
// {
// "name": "Huiskamer",
// "val": "17.98",
// "unit": "°C",
// "min": "0",
// "max": "40",
// "segm": "10"
// }
struct MqttTopicData
{
char name[16]; // Max 16 characters + null terminator
float val;
char unit[4]; // Max 3 characters + null terminator
int32_t min;
int32_t max;
uint8_t segm;
};
class MqttTopic class MqttTopic
{ {
private: private:
WiFiClient* wifiClient; WiFiClient* wifiClient;
PubSubClient client; PubSubClient client;
JsonDocument doc;
// Home assistant topic should follow /homeassistant/ead/unique_id // Home assistant topic should follow /homeassistant/ead/unique_id
char topics[10][8]; // Array of topics available, we'll go with 10 for now char topics[10][8]; // Array of topics available, we'll go with 10 for now
uint8_t available = 0; // Topics present
uint8_t current = 0; // Current topic
public: public:
MqttTopic(WiFiClient* espClient); MqttTopic(WiFiClient* espClient);
~MqttTopic(); ~MqttTopic();
void callback(char *topic, byte *payload, unsigned int length); void callback(char *topic, byte *payload, unsigned int length);
void loop(); void loop();
MqttTopicData data[10]; // Data for each topic
uint8_t available = 0; // Topics present
uint8_t current = 0; // Current topic
}; };
#endif #endif

View File

@ -3,32 +3,14 @@
WiFiMulti WiFiMulti; WiFiMulti WiFiMulti;
WiFiClient espClient; WiFiClient espClient;
// Buttons
Button2 btn_L, btn_R; Button2 btn_L, btn_R;
// Display
GxEPD2_BW<GxEPD2_213_B72, GxEPD2_213_B72::HEIGHT> display(GxEPD2_213_B72(/*CS=5*/ SS, /*DC=*/17, /*RST=*/16, /*BUSY=*/4)); GxEPD2_BW<GxEPD2_213_B72, GxEPD2_213_B72::HEIGHT> display(GxEPD2_213_B72(/*CS=5*/ SS, /*DC=*/17, /*RST=*/16, /*BUSY=*/4));
// Helpers
EinkAnalogDisplay ead(&display, DAC1); EinkAnalogDisplay ead(&display, DAC1);
MqttTopic mqtt(&espClient); MqttTopic mqtt(&espClient);
void btnHandler(Button2 &btn) void updateView();
{ void btnHandler(Button2 &btn);
switch (btn.getType())
{
case single_click:
break;
case double_click:
D_print("double ");
break;
}
D_print("click ");
D_print("on button #");
D_print((btn == btn_L) ? "L" : "R");
D_println();
}
void setup() void setup()
{ {
@ -73,10 +55,14 @@ void setup()
esp_deep_sleep_start(); esp_deep_sleep_start();
} }
display.fillScreen(GxEPD_WHITE); // Wait for content
ead.drawFace(0, 9, "Solax Prod.", "kW", 10); while (mqtt.available == 0)
ead.drawMeta(WiFi.localIP().toString().c_str()); {
display.nextPage(); mqtt.loop();
delay(100);
}
updateView();
} }
void loop() void loop()
@ -84,4 +70,62 @@ void loop()
btn_L.loop(); btn_L.loop();
btn_R.loop(); btn_R.loop();
mqtt.loop(); mqtt.loop();
// Only update arm if topic has not been changed
if (mqtt.data[mqtt.current].val != ead.value)
{
ead.setArm(mqtt.data[mqtt.current].val);
}
}
void updateView()
{
display.fillScreen(GxEPD_WHITE);
// Update display
ead.drawFace(
mqtt.data[mqtt.current].min,
mqtt.data[mqtt.current].max,
mqtt.data[mqtt.current].name,
mqtt.data[mqtt.current].unit,
mqtt.data[mqtt.current].segm
);
ead.drawMeta(WiFi.localIP().toString().c_str(), mqtt.current + 1, mqtt.available);
// Update arm
ead.setArm(mqtt.data[mqtt.current].val);
display.nextPage();
}
void btnHandler(Button2 &btn)
{
switch (btn.getType())
{
case single_click:
if (btn == btn_L)
{
mqtt.current = (mqtt.current - 1) % mqtt.available;
}
else
{
mqtt.current = (mqtt.current + 1) % mqtt.available;
}
updateView();
break;
case double_click:
if (btn == btn_L)
{
display.fillScreen(GxEPD_WHITE);
ead.drawFace(0, 100, "Calibrate", "", 11);
ead.setArm(100);
display.nextPage();
}
else
{
updateView();
}
break;
}
} }