mirror of https://github.com/arendst/Tasmota.git
892 lines
30 KiB
Arduino
892 lines
30 KiB
Arduino
|
/*
|
||
|
xdrv_13_display.ino - Display support for Sonoff-Tasmota
|
||
|
|
||
|
Copyright (C) 2018 Theo Arends
|
||
|
|
||
|
This program is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation, either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#if defined(USE_I2C) || defined(USE_SPI)
|
||
|
#ifdef USE_DISPLAY
|
||
|
|
||
|
#define DISPLAY_MAX_DRIVERS 16 // Max number of display drivers/models supported by xdsp_interface.ino
|
||
|
|
||
|
#define DISPLAY_SCREEN_COLS 40 // Max number of columns to display
|
||
|
#define DISPLAY_SCREEN_ROWS 8 // Max number of lines to display for LCD and Oled using local screen buffer
|
||
|
|
||
|
#define DISPLAY_LOG_COLS DISPLAY_SCREEN_COLS +1 // Number of characters in display log buffer line +1
|
||
|
#define DISPLAY_LOG_ROWS 32 // Number of lines in display log buffer
|
||
|
|
||
|
#define D_CMND_DISPLAY "Display"
|
||
|
#define D_CMND_DISP_ADDRESS "Address"
|
||
|
#define D_CMND_DISP_COLS "Cols"
|
||
|
#define D_CMND_DISP_DIMMER "Dimmer"
|
||
|
#define D_CMND_DISP_MODE "Mode"
|
||
|
#define D_CMND_DISP_MODEL "Model"
|
||
|
#define D_CMND_DISP_REFRESH "Refresh"
|
||
|
#define D_CMND_DISP_ROWS "Rows"
|
||
|
#define D_CMND_DISP_SIZE "Size"
|
||
|
#define D_CMND_DISP_TEXT "Text"
|
||
|
|
||
|
enum XdspFunctions { FUNC_DISPLAY_INIT_DRIVER, FUNC_DISPLAY_INIT, FUNC_DISPLAY_EVERY_50_MSECOND, FUNC_DISPLAY_EVERY_SECOND,
|
||
|
FUNC_DISPLAY_MODEL, FUNC_DISPLAY_MODE, FUNC_DISPLAY_POWER,
|
||
|
FUNC_DISPLAY_CLEAR, FUNC_DISPLAY_DRAW_FRAME,
|
||
|
FUNC_DISPLAY_DRAW_HLINE, FUNC_DISPLAY_DRAW_VLINE, FUNC_DISPLAY_DRAW_LINE,
|
||
|
FUNC_DISPLAY_DRAW_CIRCLE, FUNC_DISPLAY_FILL_CIRCLE,
|
||
|
FUNC_DISPLAY_DRAW_RECTANGLE, FUNC_DISPLAY_FILL_RECTANGLE,
|
||
|
FUNC_DISPLAY_FONT_SIZE, FUNC_DISPLAY_ROTATION, FUNC_DISPLAY_DRAW_STRING, FUNC_DISPLAY_ONOFF };
|
||
|
|
||
|
enum DisplayInitModes { DISPLAY_INIT_MODE, DISPLAY_INIT_PARTIAL, DISPLAY_INIT_FULL };
|
||
|
|
||
|
enum DisplayCommands { CMND_DISP_MODEL, CMND_DISP_MODE, CMND_DISP_REFRESH, CMND_DISP_DIMMER, CMND_DISP_COLS, CMND_DISP_ROWS,
|
||
|
CMND_DISP_SIZE, CMND_DISP_TEXT, CMND_DISP_ADDRESS };
|
||
|
const char kDisplayCommands[] PROGMEM =
|
||
|
D_CMND_DISP_MODEL "|" D_CMND_DISP_MODE "|" D_CMND_DISP_REFRESH "|" D_CMND_DISP_DIMMER "|" D_CMND_DISP_COLS "|" D_CMND_DISP_ROWS "|"
|
||
|
D_CMND_DISP_SIZE "|" D_CMND_DISP_TEXT "|" D_CMND_DISP_ADDRESS ;
|
||
|
|
||
|
const char S_JSON_DISPLAY_COMMAND_VALUE[] PROGMEM = "{\"" D_CMND_DISPLAY "%s\":\"%s\"}";
|
||
|
const char S_JSON_DISPLAY_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_DISPLAY "%s\":%d}";
|
||
|
const char S_JSON_DISPLAY_COMMAND_INDEX_NVALUE[] PROGMEM = "{\"" D_CMND_DISPLAY "%s%d\":%d}";
|
||
|
|
||
|
char disp_log_buffer[DISPLAY_LOG_ROWS][DISPLAY_LOG_COLS];
|
||
|
char disp_temp[2]; // C or F
|
||
|
uint8_t disp_log_buffer_idx = 0;
|
||
|
uint8_t disp_log_buffer_ptr = 0;
|
||
|
bool disp_log_buffer_active = false;
|
||
|
uint8_t disp_refresh = 1;
|
||
|
uint8_t disp_power = 0;
|
||
|
uint8_t disp_device = 0;
|
||
|
uint8_t disp_subscribed = 0;
|
||
|
|
||
|
int16_t disp_xpos = 0;
|
||
|
int16_t disp_ypos = 0;
|
||
|
|
||
|
uint8_t dsp_init;
|
||
|
uint8_t dsp_font;
|
||
|
uint8_t dsp_flag;
|
||
|
uint8_t dsp_on;
|
||
|
uint8_t dsp_rotation;
|
||
|
uint16_t dsp_x;
|
||
|
uint16_t dsp_y;
|
||
|
uint16_t dsp_x2;
|
||
|
uint16_t dsp_y2;
|
||
|
uint16_t dsp_rad;
|
||
|
uint16_t dsp_color;
|
||
|
int16_t dsp_len;
|
||
|
char *dsp_str;
|
||
|
|
||
|
void DisplayInit(uint8_t mode)
|
||
|
{
|
||
|
dsp_init = mode;
|
||
|
XdspCall(FUNC_DISPLAY_INIT);
|
||
|
}
|
||
|
|
||
|
void DisplayClear()
|
||
|
{
|
||
|
XdspCall(FUNC_DISPLAY_CLEAR);
|
||
|
}
|
||
|
|
||
|
void DisplayDrawHLine(uint16_t x, uint16_t y, int16_t len, uint16_t color)
|
||
|
{
|
||
|
dsp_x = x;
|
||
|
dsp_y = y;
|
||
|
dsp_len = len;
|
||
|
dsp_color = color;
|
||
|
XdspCall(FUNC_DISPLAY_DRAW_HLINE);
|
||
|
}
|
||
|
|
||
|
void DisplayDrawVLine(uint16_t x, uint16_t y, int16_t len, uint16_t color)
|
||
|
{
|
||
|
dsp_x = x;
|
||
|
dsp_y = y;
|
||
|
dsp_len = len;
|
||
|
dsp_color = color;
|
||
|
XdspCall(FUNC_DISPLAY_DRAW_VLINE);
|
||
|
}
|
||
|
|
||
|
void DisplayDrawLine(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color)
|
||
|
{
|
||
|
dsp_x = x;
|
||
|
dsp_y = y;
|
||
|
dsp_x2 = x2;
|
||
|
dsp_y2 = y2;
|
||
|
dsp_color = color;
|
||
|
XdspCall(FUNC_DISPLAY_DRAW_LINE);
|
||
|
}
|
||
|
|
||
|
void DisplayDrawCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color)
|
||
|
{
|
||
|
dsp_x = x;
|
||
|
dsp_y = y;
|
||
|
dsp_rad = rad;
|
||
|
dsp_color = color;
|
||
|
XdspCall(FUNC_DISPLAY_DRAW_CIRCLE);
|
||
|
}
|
||
|
|
||
|
void DisplayDrawFilledCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color)
|
||
|
{
|
||
|
dsp_x = x;
|
||
|
dsp_y = y;
|
||
|
dsp_rad = rad;
|
||
|
dsp_color = color;
|
||
|
XdspCall(FUNC_DISPLAY_FILL_CIRCLE);
|
||
|
}
|
||
|
|
||
|
void DisplayDrawRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color)
|
||
|
{
|
||
|
dsp_x = x;
|
||
|
dsp_y = y;
|
||
|
dsp_x2 = x2;
|
||
|
dsp_y2 = y2;
|
||
|
dsp_color = color;
|
||
|
XdspCall(FUNC_DISPLAY_DRAW_RECTANGLE);
|
||
|
}
|
||
|
|
||
|
void DisplayDrawFilledRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color)
|
||
|
{
|
||
|
dsp_x = x;
|
||
|
dsp_y = y;
|
||
|
dsp_x2 = x2;
|
||
|
dsp_y2 = y2;
|
||
|
dsp_color = color;
|
||
|
XdspCall(FUNC_DISPLAY_FILL_RECTANGLE);
|
||
|
}
|
||
|
|
||
|
void DisplayDrawFrame()
|
||
|
{
|
||
|
XdspCall(FUNC_DISPLAY_DRAW_FRAME);
|
||
|
}
|
||
|
|
||
|
void DisplaySetFontorSize(uint8_t font)
|
||
|
{
|
||
|
Settings.display_size = font;
|
||
|
XdspCall(FUNC_DISPLAY_FONT_SIZE);
|
||
|
}
|
||
|
|
||
|
void DisplaySetRotation(uint8_t rotation)
|
||
|
{
|
||
|
dsp_rotation = rotation;
|
||
|
XdspCall(FUNC_DISPLAY_ROTATION);
|
||
|
}
|
||
|
|
||
|
void DisplayDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag)
|
||
|
{
|
||
|
dsp_x = x;
|
||
|
dsp_y = y;
|
||
|
dsp_str = str;
|
||
|
dsp_color = color;
|
||
|
dsp_flag = flag;
|
||
|
XdspCall(FUNC_DISPLAY_DRAW_STRING);
|
||
|
}
|
||
|
|
||
|
void DisplayOnOff(uint8_t on)
|
||
|
{
|
||
|
dsp_on = on;
|
||
|
XdspCall(FUNC_DISPLAY_ONOFF);
|
||
|
}
|
||
|
|
||
|
// get asci number until delimiter and return asci number lenght and value
|
||
|
uint8_t atoiv(char *cp, int16_t *res)
|
||
|
{
|
||
|
uint8_t index = 0;
|
||
|
*res = atoi(cp);
|
||
|
while (*cp) {
|
||
|
if ((*cp>='0' && *cp<='9') || (*cp=='-')) {
|
||
|
cp++;
|
||
|
index++;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
// get asci number until delimiter and return asci number lenght and value
|
||
|
uint8_t atoiV(char *cp, uint16_t *res)
|
||
|
{
|
||
|
uint8_t index = 0;
|
||
|
*res = atoi(cp);
|
||
|
while (*cp) {
|
||
|
if (*cp>='0' && *cp<='9') {
|
||
|
cp++;
|
||
|
index++;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************/
|
||
|
|
||
|
#define DISPLAY_BUFFER_COLS 128 // Max number of characters in linebuf
|
||
|
|
||
|
void DisplayText()
|
||
|
{
|
||
|
uint8_t lpos;
|
||
|
uint8_t escape = 0;
|
||
|
uint8_t var;
|
||
|
uint8_t font_x = 6;
|
||
|
uint8_t font_y = 8;
|
||
|
uint8_t fontnumber = 1;
|
||
|
int16_t lin = 0;
|
||
|
int16_t col = 0;
|
||
|
int16_t fill = 0;
|
||
|
int16_t temp;
|
||
|
int16_t temp1;
|
||
|
uint16_t color = 0;
|
||
|
|
||
|
char linebuf[DISPLAY_BUFFER_COLS];
|
||
|
char *dp = linebuf;
|
||
|
char *cp = XdrvMailbox.data;
|
||
|
|
||
|
memset(linebuf, ' ', sizeof(linebuf));
|
||
|
linebuf[sizeof(linebuf)-1] = 0;
|
||
|
*dp = 0;
|
||
|
|
||
|
while (*cp) {
|
||
|
if (!escape) {
|
||
|
// check for escape
|
||
|
if (*cp == '[') {
|
||
|
escape = 1;
|
||
|
cp++;
|
||
|
// if string in buffer print it
|
||
|
if ((uint32_t)dp - (uint32_t)linebuf) {
|
||
|
if (!fill) { *dp = 0; }
|
||
|
if (col > 0 && lin > 0) {
|
||
|
// use col and lin
|
||
|
DisplayDrawStringAt(col, lin, linebuf, color, 1);
|
||
|
} else {
|
||
|
// use disp_xpos, disp_ypos
|
||
|
DisplayDrawStringAt(disp_xpos, disp_ypos, linebuf, color, 0);
|
||
|
}
|
||
|
memset(linebuf, ' ', sizeof(linebuf));
|
||
|
linebuf[sizeof(linebuf)-1] = 0;
|
||
|
dp = linebuf;
|
||
|
}
|
||
|
} else {
|
||
|
// copy chars
|
||
|
if (dp < (linebuf + DISPLAY_BUFFER_COLS)) { *dp++ = *cp++; }
|
||
|
}
|
||
|
} else {
|
||
|
// check escapes
|
||
|
if (*cp == ']') {
|
||
|
escape = 0;
|
||
|
cp++;
|
||
|
} else {
|
||
|
// analyze escapes
|
||
|
switch (*cp++) {
|
||
|
case 'z':
|
||
|
// clear display
|
||
|
DisplayClear();
|
||
|
disp_xpos = 0;
|
||
|
disp_ypos = 0;
|
||
|
col = 0;
|
||
|
lin = 0;
|
||
|
break;
|
||
|
case 'i':
|
||
|
// init display with partial update
|
||
|
DisplayInit(DISPLAY_INIT_PARTIAL);
|
||
|
break;
|
||
|
case 'I':
|
||
|
// init display with full refresh
|
||
|
DisplayInit(DISPLAY_INIT_FULL);
|
||
|
break;
|
||
|
case 'o':
|
||
|
DisplayOnOff(0);
|
||
|
break;
|
||
|
case 'O':
|
||
|
DisplayOnOff(1);
|
||
|
break;
|
||
|
case 'x':
|
||
|
// set disp_xpos
|
||
|
var = atoiv(cp, &disp_xpos);
|
||
|
cp += var;
|
||
|
break;
|
||
|
case 'y':
|
||
|
// set disp_ypos
|
||
|
var = atoiv(cp, &disp_ypos);
|
||
|
cp += var;
|
||
|
break;
|
||
|
case 'l':
|
||
|
// text line lxx
|
||
|
var = atoiv(cp, &lin);
|
||
|
cp += var;
|
||
|
//display.setCursor(display.getCursorX(),(lin-1)*font_y*txtsize);
|
||
|
break;
|
||
|
case 'c':
|
||
|
// text column cxx
|
||
|
var = atoiv(cp, &col);
|
||
|
cp += var;
|
||
|
//display.setCursor((col-1)*font_x*txtsize,display.getCursorY());
|
||
|
break;
|
||
|
case 'C':
|
||
|
// text color cxx
|
||
|
var = atoiV(cp, &color);
|
||
|
cp += var;
|
||
|
break;
|
||
|
case 'p':
|
||
|
// pad field with spaces fxx
|
||
|
var = atoiv(cp, &fill);
|
||
|
cp += var;
|
||
|
linebuf[fill] = 0;
|
||
|
break;
|
||
|
case 'h':
|
||
|
// hor line to
|
||
|
var = atoiv(cp, &temp);
|
||
|
cp += var;
|
||
|
if (temp < 0) {
|
||
|
DisplayDrawHLine(disp_xpos + temp, disp_ypos, -temp, color);
|
||
|
} else {
|
||
|
DisplayDrawHLine(disp_xpos, disp_ypos, temp, color);
|
||
|
}
|
||
|
disp_xpos += temp;
|
||
|
break;
|
||
|
case 'v':
|
||
|
// vert line to
|
||
|
var = atoiv(cp, &temp);
|
||
|
cp += var;
|
||
|
if (temp < 0) {
|
||
|
DisplayDrawVLine(disp_xpos, disp_ypos + temp, -temp, color);
|
||
|
} else {
|
||
|
DisplayDrawVLine(disp_xpos, disp_ypos, temp, color);
|
||
|
}
|
||
|
disp_ypos += temp;
|
||
|
break;
|
||
|
case 'L':
|
||
|
// any line to
|
||
|
var = atoiv(cp, &temp);
|
||
|
cp += var;
|
||
|
cp++;
|
||
|
var = atoiv(cp, &temp1);
|
||
|
cp += var;
|
||
|
DisplayDrawLine(disp_xpos, disp_ypos, temp, temp1, color);
|
||
|
disp_xpos += temp;
|
||
|
break;
|
||
|
case 'k':
|
||
|
// circle
|
||
|
var = atoiv(cp, &temp);
|
||
|
cp += var;
|
||
|
DisplayDrawCircle(disp_xpos, disp_ypos, temp, color);
|
||
|
break;
|
||
|
case 'K':
|
||
|
// filled circle
|
||
|
var = atoiv(cp, &temp);
|
||
|
cp += var;
|
||
|
DisplayDrawFilledCircle(disp_xpos, disp_ypos, temp, color);
|
||
|
break;
|
||
|
case 'r':
|
||
|
// rectangle
|
||
|
var = atoiv(cp, &temp);
|
||
|
cp += var;
|
||
|
cp++;
|
||
|
var = atoiv(cp, &temp1);
|
||
|
cp += var;
|
||
|
DisplayDrawRectangle(disp_xpos, disp_ypos, temp, temp1, color);
|
||
|
break;
|
||
|
case 'R':
|
||
|
// filled rectangle
|
||
|
var = atoiv(cp, &temp);
|
||
|
cp += var;
|
||
|
cp++;
|
||
|
var = atoiv(cp, &temp1);
|
||
|
cp += var;
|
||
|
DisplayDrawFilledRectangle(disp_xpos, disp_ypos, temp, temp1, color);
|
||
|
break;
|
||
|
case 't': {
|
||
|
if (dp < (linebuf + DISPLAY_BUFFER_COLS) -5) {
|
||
|
snprintf_P(dp, 5, PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute);
|
||
|
dp += 5;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case 'd':
|
||
|
// force draw grafics buffer
|
||
|
DisplayDrawFrame();
|
||
|
break;
|
||
|
case 's':
|
||
|
case 'f':
|
||
|
// size or font sx
|
||
|
DisplaySetFontorSize(*cp&3);
|
||
|
cp += 1;
|
||
|
break;
|
||
|
case 'a':
|
||
|
// rotation angle
|
||
|
DisplaySetRotation(*cp&3);
|
||
|
cp+=1;
|
||
|
break;
|
||
|
default:
|
||
|
// unknown escape
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("Unknown Escape"));
|
||
|
goto exit;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
exit:
|
||
|
// now draw buffer
|
||
|
if ((uint32_t)dp - (uint32_t)linebuf) {
|
||
|
if (!fill) { *dp = 0; }
|
||
|
if (col > 0 && lin > 0) {
|
||
|
// use col and lin
|
||
|
DisplayDrawStringAt(col, lin, linebuf, color, 1);
|
||
|
} else {
|
||
|
// use disp_xpos, disp_ypos
|
||
|
DisplayDrawStringAt(disp_xpos, disp_ypos, linebuf, color, 0);
|
||
|
}
|
||
|
}
|
||
|
// draw buffer
|
||
|
DisplayDrawFrame();
|
||
|
}
|
||
|
|
||
|
void DisplayLogBufferIdxInc()
|
||
|
{
|
||
|
disp_log_buffer_idx++;
|
||
|
if (DISPLAY_LOG_ROWS == disp_log_buffer_idx) {
|
||
|
disp_log_buffer_idx = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DisplayLogBufferPtrInc()
|
||
|
{
|
||
|
disp_log_buffer_ptr++;
|
||
|
if (DISPLAY_LOG_ROWS == disp_log_buffer_ptr) {
|
||
|
disp_log_buffer_ptr = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
void DisplayPrintLog()
|
||
|
{
|
||
|
disp_refresh--;
|
||
|
if (!disp_refresh) {
|
||
|
disp_refresh = Settings.display_refresh;
|
||
|
disp_log_buffer_active = (disp_log_buffer_idx != disp_log_buffer_ptr);
|
||
|
if (disp_log_buffer_active) {
|
||
|
XdspPrintLog(disp_log_buffer[disp_log_buffer_ptr]);
|
||
|
DisplayLogBufferPtrInc();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
void DisplayLogBufferInit()
|
||
|
{
|
||
|
disp_log_buffer_idx = 0;
|
||
|
disp_log_buffer_ptr = 0;
|
||
|
disp_log_buffer_active = false;
|
||
|
disp_refresh = Settings.display_refresh;
|
||
|
|
||
|
snprintf_P(disp_log_buffer[disp_log_buffer_idx], sizeof(disp_log_buffer[disp_log_buffer_idx]), PSTR(D_VERSION " %s"), my_version);
|
||
|
DisplayLogBufferIdxInc();
|
||
|
snprintf_P(disp_log_buffer[disp_log_buffer_idx], sizeof(disp_log_buffer[disp_log_buffer_idx]), PSTR("Display mode %d"), Settings.display_mode);
|
||
|
DisplayLogBufferIdxInc();
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************\
|
||
|
* Sensors
|
||
|
\*********************************************************************************************/
|
||
|
|
||
|
enum SensorQuantity {
|
||
|
JSON_TEMPERATURE,
|
||
|
JSON_HUMIDITY, JSON_LIGHT, JSON_NOISE, JSON_AIRQUALITY,
|
||
|
JSON_PRESSURE, JSON_PRESSUREATSEALEVEL,
|
||
|
JSON_ILLUMINANCE,
|
||
|
JSON_GAS,
|
||
|
JSON_YESTERDAY, JSON_TOTAL, JSON_TODAY,
|
||
|
JSON_PERIOD,
|
||
|
JSON_POWERFACTOR, JSON_COUNTER, JSON_ANALOG_INPUT, JSON_UV_LEVEL,
|
||
|
JSON_CURRENT,
|
||
|
JSON_VOLTAGE,
|
||
|
JSON_POWERUSAGE,
|
||
|
JSON_CO2 };
|
||
|
const char kSensorQuantity[] PROGMEM =
|
||
|
D_JSON_TEMPERATURE "|" // degrees
|
||
|
D_JSON_HUMIDITY "|" D_JSON_LIGHT "|" D_JSON_NOISE "|" D_JSON_AIRQUALITY "|" // percentage
|
||
|
D_JSON_PRESSURE "|" D_JSON_PRESSUREATSEALEVEL "|" // hPa
|
||
|
D_JSON_ILLUMINANCE "|" // lx
|
||
|
D_JSON_GAS "|" // kOhm
|
||
|
D_JSON_YESTERDAY "|" D_JSON_TOTAL "|" D_JSON_TODAY "|" // kWh
|
||
|
D_JSON_PERIOD "|" // Wh
|
||
|
D_JSON_POWERFACTOR "|" D_JSON_COUNTER "|" D_JSON_ANALOG_INPUT "|" D_JSON_UV_LEVEL "|" // No unit
|
||
|
D_JSON_CURRENT "|" // Ampere
|
||
|
D_JSON_VOLTAGE "|" // Volt
|
||
|
D_JSON_POWERUSAGE "|" // Watt
|
||
|
D_JSON_CO2 ; // ppm
|
||
|
|
||
|
void DisplayJsonValue(const char *topic, const char* device, const char* mkey, const char* value)
|
||
|
{
|
||
|
char quantity[TOPSZ];
|
||
|
char spaces[Settings.display_cols[0]];
|
||
|
char source[Settings.display_cols[0] - Settings.display_cols[1]];
|
||
|
char svalue[Settings.display_cols[1] +1];
|
||
|
|
||
|
ShowFreeMem(PSTR("DisplayJsonValue"));
|
||
|
|
||
|
memset(spaces, 0x20, sizeof(spaces));
|
||
|
spaces[sizeof(spaces) -1] = '\0';
|
||
|
// snprintf_P(source, sizeof(source), PSTR("%s/%s%s"), topic, mkey, (DISP_MATRIX == Settings.display_model) ? "" : spaces); // pow1/Voltage
|
||
|
snprintf_P(source, sizeof(source), PSTR("%s/%s%s"), topic, mkey, spaces); // pow1/Voltage
|
||
|
|
||
|
int quantity_code = GetCommandCode(quantity, sizeof(quantity), mkey, kSensorQuantity);
|
||
|
if ((-1 == quantity_code) || !strcmp_P(mkey, S_RSLT_POWER)) { // Ok: Power, Not ok: POWER
|
||
|
return;
|
||
|
}
|
||
|
if (JSON_TEMPERATURE == quantity_code) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s~%s"), value, disp_temp);
|
||
|
}
|
||
|
else if ((quantity_code >= JSON_HUMIDITY) && (quantity_code <= JSON_AIRQUALITY)) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s%%"), value);
|
||
|
}
|
||
|
else if ((quantity_code >= JSON_PRESSURE) && (quantity_code <= JSON_PRESSUREATSEALEVEL)) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_PRESSURE), value);
|
||
|
}
|
||
|
else if (JSON_ILLUMINANCE == quantity_code) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_LUX), value);
|
||
|
}
|
||
|
else if (JSON_GAS == quantity_code) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_KILOOHM), value);
|
||
|
}
|
||
|
else if ((quantity_code >= JSON_YESTERDAY) && (quantity_code <= JSON_TODAY)) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_KILOWATTHOUR), value);
|
||
|
}
|
||
|
else if (JSON_PERIOD == quantity_code) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_WATTHOUR), value);
|
||
|
}
|
||
|
else if ((quantity_code >= JSON_POWERFACTOR) && (quantity_code <= JSON_UV_LEVEL)) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s"), value);
|
||
|
}
|
||
|
else if (JSON_CURRENT == quantity_code) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_AMPERE), value);
|
||
|
}
|
||
|
else if (JSON_VOLTAGE == quantity_code) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_VOLT), value);
|
||
|
}
|
||
|
else if (JSON_POWERUSAGE == quantity_code) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_WATT), value);
|
||
|
}
|
||
|
else if (JSON_CO2 == quantity_code) {
|
||
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_PARTS_PER_MILLION), value);
|
||
|
}
|
||
|
snprintf_P(disp_log_buffer[disp_log_buffer_idx], sizeof(disp_log_buffer[disp_log_buffer_idx]), PSTR("%s %s"), source, svalue);
|
||
|
|
||
|
// snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "mkey [%s], source [%s], value [%s], quantity_code %d, log_buffer [%s]"), mkey, source, value, quantity_code, disp_log_buffer[disp_log_buffer_idx]);
|
||
|
// AddLog(LOG_LEVEL_DEBUG);
|
||
|
|
||
|
DisplayLogBufferIdxInc();
|
||
|
}
|
||
|
|
||
|
void DisplayAnalyzeJson(char *topic, char *json)
|
||
|
{
|
||
|
// //tele/pow2/STATE {"Time":"2017-09-20T11:53:03", "Uptime":10, "Vcc":3.123, "POWER":"ON", "Wifi":{"AP":2, "SSId":"indebuurt2", "RSSI":68, "APMac":"00:22:6B:FE:8E:20"}}
|
||
|
// //tele/pow2/ENERGY {"Time":"2017-09-20T11:53:03", "Total":6.522, "Yesterday":0.150, "Today":0.073, "Period":0.5, "Power":12.1, "Factor":0.56, "Voltage":210.1, "Current":0.102}
|
||
|
|
||
|
// tele/pow1/SENSOR = {"Time":"2018-01-02T17:13:17","ENERGY":{"Total":13.091,"Yesterday":0.060,"Today":0.046,"Period":0.2,"Power":9.8,"Factor":0.49,"Voltage":206.8,"Current":0.096}}
|
||
|
// tele/dual/STATE {"Time":"2017-09-20T11:53:03","Uptime":25,"Vcc":3.178,"POWER1":"OFF","POWER2":"OFF","Wifi":{"AP":2,"SSId":"indebuurt2","RSSI":100,"APMac":"00:22:6B:FE:8E:20"}}
|
||
|
// tele/sc/SENSOR {"Time":"2017-09-20T11:53:09","Temperature":24.0,"Humidity":16.0,"Light":30,"Noise":20,"AirQuality":100,"TempUnit":"C"}
|
||
|
// tele/rf1/SENSOR {"Time":"2017-09-20T11:53:23","BH1750":{"Illuminance":57}}
|
||
|
// tele/wemos5/SENSOR {"Time":"2017-09-20T11:53:53","SHT1X":{"Temperature":20.1,"Humidity":58.9},"HTU21":{"Temperature":20.7,"Humidity":58.5},"BMP280":{"Temperature":21.6,"Pressure":1020.3},"TempUnit":"C"}
|
||
|
// tele/th1/SENSOR {"Time":"2017-09-20T11:54:48","DS18B20":{"Temperature":49.7},"TempUnit":"C"}
|
||
|
|
||
|
const char *tempunit;
|
||
|
|
||
|
// char jsonStr[MESSZ];
|
||
|
// strlcpy(jsonStr, json, sizeof(jsonStr)); // Save original before destruction by JsonObject
|
||
|
String jsonStr = json; // Move from stack to heap to fix watchdogs (20180626)
|
||
|
|
||
|
StaticJsonBuffer<1024> jsonBuf;
|
||
|
JsonObject &root = jsonBuf.parseObject(jsonStr);
|
||
|
if (root.success()) {
|
||
|
|
||
|
tempunit = root[D_JSON_TEMPERATURE_UNIT];
|
||
|
if (tempunit) {
|
||
|
snprintf_P(disp_temp, sizeof(disp_temp), PSTR("%s"), tempunit);
|
||
|
// snprintf_P(log_data, sizeof(log_data), disp_temp);
|
||
|
// AddLog(LOG_LEVEL_DEBUG);
|
||
|
}
|
||
|
|
||
|
for (JsonObject::iterator it = root.begin(); it != root.end(); ++it) {
|
||
|
JsonVariant value = it->value;
|
||
|
if (value.is<JsonObject>()) {
|
||
|
JsonObject& Object2 = value;
|
||
|
for (JsonObject::iterator it2 = Object2.begin(); it2 != Object2.end(); ++it2) {
|
||
|
JsonVariant value2 = it2->value;
|
||
|
if (value2.is<JsonObject>()) {
|
||
|
JsonObject& Object3 = value2;
|
||
|
for (JsonObject::iterator it3 = Object3.begin(); it3 != Object3.end(); ++it3) {
|
||
|
DisplayJsonValue(topic, it->key, it3->key, it3->value.as<const char*>()); // Sensor 56%
|
||
|
}
|
||
|
} else {
|
||
|
DisplayJsonValue(topic, it->key, it2->key, it2->value.as<const char*>()); // Sensor 56%
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
DisplayJsonValue(topic, it->key, it->key, it->value.as<const char*>()); // Topic 56%
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************\
|
||
|
* Public
|
||
|
\*********************************************************************************************/
|
||
|
|
||
|
void DisplayInitDriver()
|
||
|
{
|
||
|
XdspCall(FUNC_DISPLAY_INIT_DRIVER);
|
||
|
|
||
|
// snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "Display model %d"), Settings.display_model);
|
||
|
// AddLog(LOG_LEVEL_DEBUG);
|
||
|
|
||
|
if (Settings.display_model) {
|
||
|
devices_present++;
|
||
|
disp_device = devices_present;
|
||
|
snprintf_P(disp_temp, sizeof(disp_temp), PSTR("%c"), TempUnit());
|
||
|
|
||
|
DisplayLogBufferInit();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DisplaySetPower()
|
||
|
{
|
||
|
disp_power = bitRead(XdrvMailbox.index, disp_device -1);
|
||
|
if (Settings.display_model) {
|
||
|
XdspCall(FUNC_DISPLAY_POWER);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DisplayMqttSubscribe()
|
||
|
{
|
||
|
/* Subscribe to tele messages only
|
||
|
* Supports the following FullTopic formats
|
||
|
* - %prefix%/%topic%
|
||
|
* - home/%prefix%/%topic%
|
||
|
* - home/level2/%prefix%/%topic% etc.
|
||
|
*/
|
||
|
// if (Settings.display_mode &0x04) {
|
||
|
if (Settings.display_model) {
|
||
|
|
||
|
char stopic[TOPSZ];
|
||
|
char ntopic[TOPSZ];
|
||
|
|
||
|
ntopic[0] = '\0';
|
||
|
strlcpy(stopic, Settings.mqtt_fulltopic, sizeof(stopic));
|
||
|
char *tp = strtok(stopic, "/");
|
||
|
while (tp != NULL) {
|
||
|
if (!strcmp_P(tp, PSTR(MQTT_TOKEN_PREFIX))) {
|
||
|
break;
|
||
|
}
|
||
|
strncat_P(ntopic, PSTR("+/"), sizeof(ntopic)); // Add single-level wildcards
|
||
|
tp = strtok(NULL, "/");
|
||
|
}
|
||
|
strncat(ntopic, Settings.mqtt_prefix[2], sizeof(ntopic)); // Subscribe to tele messages
|
||
|
strncat_P(ntopic, PSTR("/#"), sizeof(ntopic)); // Add multi-level wildcard
|
||
|
MqttSubscribe(ntopic);
|
||
|
disp_subscribed = 1;
|
||
|
} else {
|
||
|
disp_subscribed = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
boolean DisplayMqttData()
|
||
|
{
|
||
|
if (disp_subscribed) {
|
||
|
char stopic[TOPSZ];
|
||
|
|
||
|
snprintf_P(stopic, sizeof(stopic) , PSTR("%s/"), Settings.mqtt_prefix[2]); // tele/
|
||
|
char *tp = strstr(XdrvMailbox.topic, stopic);
|
||
|
if (tp) { // tele/sonoff/SENSOR
|
||
|
if (Settings.display_mode &0x04) {
|
||
|
tp = tp + strlen(stopic); // sonoff/SENSOR
|
||
|
char *topic = strtok(tp, "/"); // sonoff
|
||
|
DisplayAnalyzeJson(topic, XdrvMailbox.data);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void DisplayLocalSensor()
|
||
|
{
|
||
|
if ((Settings.display_mode &0x02) && (0 == tele_period)) {
|
||
|
DisplayAnalyzeJson(mqtt_topic, mqtt_data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************\
|
||
|
* Commands
|
||
|
\*********************************************************************************************/
|
||
|
|
||
|
boolean DisplayCommand()
|
||
|
{
|
||
|
char command [CMDSZ];
|
||
|
boolean serviced = true;
|
||
|
uint8_t disp_len = strlen(D_CMND_DISPLAY); // Prep for string length change
|
||
|
|
||
|
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_DISPLAY), disp_len)) { // Prefix
|
||
|
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic +disp_len, kDisplayCommands);
|
||
|
if (-1 == command_code) {
|
||
|
serviced = false; // Unknown command
|
||
|
}
|
||
|
else if (CMND_DISP_MODEL == command_code) {
|
||
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < DISPLAY_MAX_DRIVERS)) {
|
||
|
uint8_t last_display_model = Settings.display_model;
|
||
|
Settings.display_model = XdrvMailbox.payload;
|
||
|
if (XdspCall(FUNC_DISPLAY_MODEL)) {
|
||
|
restart_flag = 2; // Restart to re-init interface and add/Remove MQTT subscribe
|
||
|
} else {
|
||
|
Settings.display_model = last_display_model;
|
||
|
}
|
||
|
}
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_DISPLAY_COMMAND_NVALUE, command, Settings.display_model);
|
||
|
}
|
||
|
else if (CMND_DISP_MODE == command_code) {
|
||
|
/*
|
||
|
* Matrix LCD / Oled TFT
|
||
|
* 0 = Text Text Text
|
||
|
* 1 = Text up and time Time
|
||
|
* 2 = Date Local sensors Local sensors
|
||
|
* 3 = Day Local sensors and time Local sensors and time
|
||
|
* 4 = Mqtt left and time Mqtt (incl local) sensors Mqtt (incl local) sensors
|
||
|
* 5 = Mqtt up and time Mqtt (incl local) sensors and time Mqtt (incl local) sensors and time
|
||
|
*/
|
||
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 5)) {
|
||
|
uint8_t last_display_mode = Settings.display_mode;
|
||
|
Settings.display_mode = XdrvMailbox.payload;
|
||
|
if (!disp_subscribed) {
|
||
|
restart_flag = 2; // Restart to Add/Remove MQTT subscribe
|
||
|
} else {
|
||
|
if (last_display_mode && !Settings.display_mode) { // Switch to mode 0
|
||
|
DisplayInit(DISPLAY_INIT_MODE);
|
||
|
DisplayClear();
|
||
|
}
|
||
|
if (!last_display_mode && Settings.display_mode) { // Switch to non mode 0
|
||
|
DisplayInit(DISPLAY_INIT_MODE);
|
||
|
DisplayLogBufferInit();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_DISPLAY_COMMAND_NVALUE, command, Settings.display_mode);
|
||
|
}
|
||
|
else if (CMND_DISP_REFRESH == command_code) {
|
||
|
if ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 7)) {
|
||
|
Settings.display_refresh = XdrvMailbox.payload;
|
||
|
}
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_DISPLAY_COMMAND_NVALUE, command, Settings.display_refresh);
|
||
|
}
|
||
|
else if ((CMND_DISP_COLS == command_code) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
|
||
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= DISPLAY_SCREEN_COLS)) {
|
||
|
Settings.display_cols[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
||
|
}
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_DISPLAY_COMMAND_INDEX_NVALUE, command, XdrvMailbox.index, Settings.display_cols[XdrvMailbox.index -1]);
|
||
|
}
|
||
|
else if (CMND_DISP_DIMMER == command_code) {
|
||
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
||
|
Settings.display_dimmer = ((XdrvMailbox.payload +1) * 100) / 666; // Correction for Domoticz (0 - 15)
|
||
|
if (Settings.display_dimmer && !(disp_power)) {
|
||
|
ExecuteCommandPower(disp_device, POWER_ON, SRC_DISPLAY);
|
||
|
}
|
||
|
else if (!Settings.display_dimmer && disp_power) {
|
||
|
ExecuteCommandPower(disp_device, POWER_OFF, SRC_DISPLAY);
|
||
|
}
|
||
|
}
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_DISPLAY_COMMAND_NVALUE, command, Settings.display_dimmer);
|
||
|
}
|
||
|
else if (CMND_DISP_ROWS == command_code) {
|
||
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= DISPLAY_SCREEN_ROWS)) {
|
||
|
Settings.display_rows = XdrvMailbox.payload;
|
||
|
}
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_DISPLAY_COMMAND_NVALUE, command, Settings.display_rows);
|
||
|
}
|
||
|
else if (CMND_DISP_SIZE == command_code) {
|
||
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 4)) {
|
||
|
Settings.display_size = XdrvMailbox.payload;
|
||
|
}
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_DISPLAY_COMMAND_NVALUE, command, Settings.display_size);
|
||
|
}
|
||
|
else if (CMND_DISP_TEXT == command_code) {
|
||
|
mqtt_data[0] = '\0';
|
||
|
if (disp_device && XdrvMailbox.data_len > 0) {
|
||
|
if (!Settings.display_mode) {
|
||
|
DisplayText();
|
||
|
} else {
|
||
|
strlcpy(disp_log_buffer[disp_log_buffer_idx], XdrvMailbox.data, sizeof(disp_log_buffer[disp_log_buffer_idx]));
|
||
|
DisplayLogBufferIdxInc();
|
||
|
}
|
||
|
} else {
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("No Text"));
|
||
|
}
|
||
|
if (mqtt_data[0] == '\0') {
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_DISPLAY_COMMAND_VALUE, command, XdrvMailbox.data);
|
||
|
}
|
||
|
}
|
||
|
else if ((CMND_DISP_ADDRESS == command_code) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 8)) {
|
||
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 255)) {
|
||
|
Settings.display_address[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
||
|
}
|
||
|
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_DISPLAY_COMMAND_INDEX_NVALUE, command, XdrvMailbox.index, Settings.display_address[XdrvMailbox.index -1]);
|
||
|
}
|
||
|
else serviced = false; // Unknown command
|
||
|
}
|
||
|
else serviced = false; // Unknown command
|
||
|
|
||
|
return serviced;
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************\
|
||
|
* Interface
|
||
|
\*********************************************************************************************/
|
||
|
|
||
|
#define XDRV_98
|
||
|
|
||
|
boolean Xdrv98(byte function)
|
||
|
{
|
||
|
boolean result = false;
|
||
|
|
||
|
if ((i2c_flg || spi_flg) && XdspPresent()) {
|
||
|
switch (function) {
|
||
|
case FUNC_PRE_INIT:
|
||
|
DisplayInitDriver();
|
||
|
break;
|
||
|
case FUNC_EVERY_50_MSECOND:
|
||
|
if (Settings.display_model && Settings.display_mode) { XdspCall(FUNC_DISPLAY_EVERY_50_MSECOND); }
|
||
|
break;
|
||
|
case FUNC_EVERY_SECOND:
|
||
|
if (Settings.display_model && Settings.display_mode) { XdspCall(FUNC_DISPLAY_EVERY_SECOND); }
|
||
|
break;
|
||
|
case FUNC_COMMAND:
|
||
|
result = DisplayCommand();
|
||
|
break;
|
||
|
case FUNC_MQTT_SUBSCRIBE:
|
||
|
DisplayMqttSubscribe();
|
||
|
break;
|
||
|
case FUNC_MQTT_DATA:
|
||
|
result = DisplayMqttData();
|
||
|
break;
|
||
|
case FUNC_SET_POWER:
|
||
|
DisplaySetPower();
|
||
|
break;
|
||
|
case FUNC_SHOW_SENSOR:
|
||
|
DisplayLocalSensor();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
#endif // USE_DISPLAY
|
||
|
#endif // USE_I2C or USE_SPI
|