Files
Flipdot-display/src/esp32/web/page_json.cpp
T

150 lines
5.2 KiB
C++
Raw Normal View History

#include "web/page_json.h"
#include <Arduino.h>
#include <string.h>
#include "logic/settings.h"
static const char *const TYPES[] = {"clock", "date", "ha", "text", "countdown", "stopwatch", "image", "bar"};
static const char *const ALIGNS[] = {"left", "center", "right"};
static const char *const OVERLAYS[] = {"top", "box", "invert"};
static const char *const LANGUAGES[] = {"nl", "en"};
static const char *const SHOWS[] = {"number", "duration", "time", "map"};
static const char *const UNITS[] = {"s", "min", "h"};
template <size_t N> static int indexOf(const char *const (&names)[N], const char *name)
{
for (size_t i = 0; i < N; i++)
if (name && strcmp(names[i], name) == 0)
return i;
return -1;
}
static const char *DEGREE_UTF8 = "\xC2\xB0";
static String withDegrees(const char *text)
{
String s = text;
s.replace(String(DEGREE_SIGN), DEGREE_UTF8);
return s;
}
void pageToJson(const DynamicPage &page, JsonObject out)
{
char time[6];
JsonArray slots = out["slots"].to<JsonArray>();
for (int i = 0; i < page.slot_count; i++)
{
JsonObject slot = slots.add<JsonObject>();
formatTime(page.slots[i].start, time);
slot["start"] = String(time);
slot["bitmap"] = page.slots[i].bitmap;
}
JsonArray elements = out["elements"].to<JsonArray>();
for (int i = 0; i < page.element_count; i++)
{
const PageElement &e = page.elements[i];
JsonObject o = elements.add<JsonObject>();
o["type"] = TYPES[(int)e.type];
o["x"] = e.x;
o["y"] = e.y;
o["align"] = ALIGNS[(int)e.align];
o["overlay"] = OVERLAYS[(int)e.overlay];
o["font"] = e.font;
o["format"] = e.format;
o["language"] = LANGUAGES[(int)e.language];
o["decimals"] = e.decimals;
o["text"] = withDegrees(e.text);
o["suffix"] = withDegrees(e.suffix);
o["entity"] = e.entity;
o["attribute"] = e.attribute;
o["factor"] = e.factor;
o["scroll_width"] = e.scroll_width;
o["scroll_speed"] = e.scroll_speed;
char target[17];
formatTarget(e.target, target);
o["target"] = String(target);
o["show"] = SHOWS[(int)e.show];
o["input_unit"] = UNITS[e.input_unit];
o["auto_unit"] = e.auto_unit;
o["absolute"] = e.absolute;
o["map"] = withDegrees(e.map);
o["bar_width"] = e.bar_width;
o["bar_height"] = e.bar_height;
o["bar_max"] = e.bar_max;
o["bar_frame"] = e.bar_frame;
}
}
// False when the text does not fit. The degree sign (UTF-8) becomes DEGREE_SIGN.
static bool copyText(JsonVariantConst value, char *out, size_t size)
{
String text = value | "";
text.replace(DEGREE_UTF8, String(DEGREE_SIGN));
if (text.length() >= size)
return false;
strcpy(out, text.c_str());
return true;
}
const char *pageFromJson(JsonVariantConst in, DynamicPage &page)
{
page = DynamicPage();
JsonArrayConst slots = in["slots"];
JsonArrayConst elements = in["elements"];
if (slots.size() > (size_t)PAGE_SLOTS)
return "Too many time slots (8 at most)";
if (elements.size() > (size_t)PAGE_ELEMENTS)
return "Too many elements (8 at most)";
for (JsonVariantConst s : slots)
{
PageSlot &slot = page.slots[page.slot_count++];
if (!parseTime(s["start"] | "", slot.start))
return "Times must look like 08:30";
if (!copyText(s["bitmap"], slot.bitmap, sizeof(slot.bitmap)))
return "Bitmap name too long";
}
for (JsonVariantConst o : elements)
{
PageElement &e = page.elements[page.element_count++];
int type = indexOf(TYPES, o["type"]), align = indexOf(ALIGNS, o["align"] | "left"),
overlay = indexOf(OVERLAYS, o["overlay"] | "top"), language = indexOf(LANGUAGES, o["language"] | "nl");
if (type < 0 || align < 0 || overlay < 0 || language < 0)
return "Unknown element setting";
e.type = (ElementType)type;
e.align = (Align)align;
e.overlay = (Overlay)overlay;
e.language = (Language)language;
// Range checks are in validatePage(); out of range numbers must not wrap
e.x = constrain(o["x"] | 0, -1000, 1000);
e.y = constrain(o["y"] | 0, -1000, 1000);
e.format = constrain(o["format"] | 0, 0, 255);
e.decimals = constrain(o["decimals"] | (int)DECIMALS_AS_IS, -100, 100);
e.factor = o["factor"] | 1.0f;
e.scroll_width = constrain(o["scroll_width"] | 0, 0, 255);
e.scroll_speed = constrain(o["scroll_speed"] | 8, 0, 255);
int show = indexOf(SHOWS, o["show"] | "number"), unit = indexOf(UNITS, o["input_unit"] | "s");
if (show < 0 || unit < 0)
return "Unknown Home Assistant value setting";
e.show = (HaShow)show;
e.input_unit = unit;
e.auto_unit = o["auto_unit"] | false;
e.absolute = o["absolute"] | false;
e.bar_width = constrain(o["bar_width"] | 40, 0, 255);
e.bar_height = constrain(o["bar_height"] | 3, 0, 255);
e.bar_max = o["bar_max"] | 100.0f;
e.bar_frame = o["bar_frame"] | true;
if (!copyText(o["map"], e.map, sizeof(e.map)))
return "The list of texts is too long (63 characters)";
const char *target = o["target"] | "";
if (e.type == ElementType::Countdown && !parseTarget(target, e.target))
return "Countdown: enter a date and time";
if (!copyText(o["font"], e.font, sizeof(e.font)) || !copyText(o["text"], e.text, sizeof(e.text)) ||
!copyText(o["suffix"], e.suffix, sizeof(e.suffix)) || !copyText(o["entity"], e.entity, sizeof(e.entity)) ||
!copyText(o["attribute"], e.attribute, sizeof(e.attribute)))
return "Text too long (32 characters, unit 16, entity 64)";
}
return validatePage(page);
}