Almost works well now
This commit is contained in:
parent
9d51792402
commit
1db35ad377
@ -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 ~ ''"}'' }}'
|
||||||
|
@ -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
|
@ -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
|
||||||
|
@ -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
|
@ -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"
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
92
src/main.cpp
92
src/main.cpp
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user