Almost works well now
This commit is contained in:
parent
9d51792402
commit
1db35ad377
@ -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 ~ ''"}'' }}'
|
||||
|
@ -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
|
@ -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;
|
||||
@ -69,7 +71,7 @@ void EinkAnalogDisplay::drawFace(int16_t min, int16_t max, char *name, char* uni
|
||||
const float angle = startAngle + (angleTotal / (float)angleDiv) * i;
|
||||
|
||||
const float len1 = (armSizeMm / displayMmPx) + 2; // Begin of ind. line
|
||||
const float len3 = len1 + 6; // Text pos for indicator line
|
||||
const float len3 = len1 + 6; // Text pos for indicator line
|
||||
|
||||
const float len2 = (i == 0 || i == angleDiv) ? (len1 - lineSizeEx) : (len1 - lineSizeDef);
|
||||
|
||||
@ -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
|
||||
|
@ -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
|
@ -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"
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
92
src/main.cpp
92
src/main.cpp
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user