#include "web/page_json.h" #include #include #include "logic/settings.h" static const char *const TYPES[] = {"clock", "date", "ha", "text", "countdown", "stopwatch", "image"}; 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 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(); for (int i = 0; i < page.slot_count; i++) { JsonObject slot = slots.add(); formatTime(page.slots[i].start, time); slot["start"] = String(time); slot["bitmap"] = page.slots[i].bitmap; } JsonArray elements = out["elements"].to(); for (int i = 0; i < page.element_count; i++) { const PageElement &e = page.elements[i]; JsonObject o = elements.add(); 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); } } // 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; 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); }