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
selector:
number:
min: -255
max: 255
min: -10000
max: 10000
display_value_max:
name: Display Value Max
description: The maximum value to display
default: 100
selector:
number:
min: -255
max: 255
min: -10000
max: 10000
display_segments:
name: Amount of segments on face
description: How many values to display between the min and max
default: 10
selector:
number:
min: 2
min: 1
max: 20
variables:
@ -62,5 +62,5 @@ action:
data:
topic_template: "{{ 'homeassistant/ead/' ~ topic_name }}"
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 ~ ''"}'' }}'

View File

@ -8,7 +8,7 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp]
[env]
platform = espressif32
board = esp-wrover-kit
framework = arduino
@ -22,3 +22,8 @@ lib_deps =
adafruit/Adafruit GFX Library@^1.11.7
knolleary/PubSubClient@^2.8
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)
{
this->display->init(115200);
} else {
}
else
{
this->display->init(0);
}
@ -37,7 +39,7 @@ void EinkAnalogDisplay::centerText(const char *text, int16_t x, int16_t y)
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->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);
}
void EinkAnalogDisplay::drawMeta(const char *wireless_str) {
// Draw WiFi and Build info
void EinkAnalogDisplay::drawMeta(const char *wireless_str, uint8_t page_cur, uint8_t page_total)
{
// Draw WiFi and page info
display->drawBitmap(0, 114, gfx_wireless, 5, 7, GxEPD_BLACK);
display->setFont(&Pixeltype8pt7b);
display->setCursor(8, 119);
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
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->setCursor(display->width() - tbw - 8, 119);
display->print(build_str);
display->print(page_str);
}
int EinkAnalogDisplay::setArm(float value)
@ -158,8 +162,10 @@ int EinkAnalogDisplay::setArm(float value)
// NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin;
// 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);
this->value = value;
return 1;
}
else

View File

@ -32,9 +32,10 @@ private:
public:
EinkAnalogDisplay(GxEPD2_BW<GxEPD2_213_B72, (uint16_t)250U> *display, uint8_t dacPin, bool GxEPD_DEBUG = false);
~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);
void drawMeta(const char *wireless_str);
void drawMeta(const char *wireless_str, uint8_t page_cur, uint8_t page_total);
int16_t value;
};
#endif

View File

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

View File

@ -6,7 +6,8 @@ MqttTopic::MqttTopic(WiFiClient *espClient) : wifiClient(espClient), client(PubS
server.fromString(MQTT_ADDR);
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()
@ -29,22 +30,55 @@ void MqttTopic::callback(char *topic, byte *payload, unsigned int length)
D_print("Topic: ");
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)
{
// Check if token exists in topics
if (std::find(std::begin(topics), std::end(topics), token) != std::end(topics))
bool tokenExists = false;
size_t j = 0;
for (; j < available; j++)
{
if (strcmp(topics[j], token) == 0)
{
tokenExists = true;
break;
}
}
if (tokenExists)
{
// Token exists
D_println("Token exists");
data[j].val = result.val;
}
else
{
// Add token to topics
D_print((char*)topics);
strcpy(topics[available], token);
data[available] = result;
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
{
@ -53,13 +87,6 @@ void MqttTopic::callback(char *topic, byte *payload, unsigned int length)
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()

View File

@ -7,22 +7,47 @@
// MQTT
#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
{
private:
WiFiClient* wifiClient;
PubSubClient client;
JsonDocument doc;
// Home assistant topic should follow /homeassistant/ead/unique_id
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:
MqttTopic(WiFiClient* espClient);
~MqttTopic();
void callback(char *topic, byte *payload, unsigned int length);
void loop();
MqttTopicData data[10]; // Data for each topic
uint8_t available = 0; // Topics present
uint8_t current = 0; // Current topic
};
#endif

View File

@ -3,32 +3,14 @@
WiFiMulti WiFiMulti;
WiFiClient espClient;
// Buttons
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));
// Helpers
EinkAnalogDisplay ead(&display, DAC1);
MqttTopic mqtt(&espClient);
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 updateView();
void btnHandler(Button2 &btn);
void setup()
{
@ -73,10 +55,14 @@ void setup()
esp_deep_sleep_start();
}
display.fillScreen(GxEPD_WHITE);
ead.drawFace(0, 9, "Solax Prod.", "kW", 10);
ead.drawMeta(WiFi.localIP().toString().c_str());
display.nextPage();
// Wait for content
while (mqtt.available == 0)
{
mqtt.loop();
delay(100);
}
updateView();
}
void loop()
@ -84,4 +70,62 @@ void loop()
btn_L.loop();
btn_R.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;
}
}