From 76473fe7a8186950474185668dde4cd5c5f6baf2 Mon Sep 17 00:00:00 2001 From: Offer Shmuely Date: Sun, 15 Dec 2024 03:00:07 +0200 Subject: [PATCH] BatAnalog & BatCheck: use LVGL, support LiHV, auto cell count detection (#165) --- .../c480x272/WIDGETS/BattAnalog/lib_log.lua | 115 ++++ .../WIDGETS/BattAnalog/lib_sensors.lua | 103 +++ .../WIDGETS/BattAnalog/lib_widget_tools.lua | 228 +++++-- sdcard/c480x272/WIDGETS/BattAnalog/logic.lua | 355 +++++++++++ sdcard/c480x272/WIDGETS/BattAnalog/main.lua | 601 +++--------------- .../c480x272/WIDGETS/BattAnalog/ui_lvgl.lua | 306 +++++++++ .../WIDGETS/BattCheck/lib_sensors.lua | 103 +++ sdcard/c480x272/WIDGETS/BattCheck/main.lua | 9 +- 8 files changed, 1233 insertions(+), 587 deletions(-) create mode 100644 sdcard/c480x272/WIDGETS/BattAnalog/lib_log.lua create mode 100644 sdcard/c480x272/WIDGETS/BattAnalog/lib_sensors.lua create mode 100644 sdcard/c480x272/WIDGETS/BattAnalog/logic.lua create mode 100644 sdcard/c480x272/WIDGETS/BattAnalog/ui_lvgl.lua create mode 100644 sdcard/c480x272/WIDGETS/BattCheck/lib_sensors.lua diff --git a/sdcard/c480x272/WIDGETS/BattAnalog/lib_log.lua b/sdcard/c480x272/WIDGETS/BattAnalog/lib_log.lua new file mode 100644 index 00000000..c23e6e8a --- /dev/null +++ b/sdcard/c480x272/WIDGETS/BattAnalog/lib_log.lua @@ -0,0 +1,115 @@ +local app_name, script_dir = ... + +local ENABLE_LOG_TO_CONSOLE = true +local ENABLE_LOG_TO_FILE = false + + +local M = {} +M.app_name = app_name +M.script_dir = script_dir + +local function is_simulator() + local _, rv = getVersion() + return string.sub(rv, -5) == "-simu" +end + +local log = { + outfile = script_dir .. "/app.log", + enable_file = ENABLE_LOG_TO_FILE, + enable_console = ENABLE_LOG_TO_CONSOLE and is_simulator(), + current_level = nil, + + -- func + trace = nil, + debug = nil, + info = nil, + warn = nil, + error = nil, + fatal = nil, + + levels = { + trace = 1, + debug = 2, + info = 3, + warn = 4, + error = 5, + fatal = 6, + no_logs = 99 + } +} +log.current_level = log.levels["info"] -- trace|debug|info|warn|error|fatal + + +local function round(x, increment) + increment = increment or 1 + x = x / increment + return (x > 0 and math.floor(x + .5) or math.ceil(x - .5)) * increment +end + +local _tostring = tostring + +local function tostring(...) + local t = {} + for i = 1, select('#', ...) do + local x = select(i, ...) + if type(x) == "number" then + x = round(x, .01) + end + t[#t + 1] = _tostring(x) + end + return table.concat(t, " ") +end + +function M.do_log(iLevel, ulevel, fmt, ...) + if log.enable_console == false and log.enable_file == false then + return + end + + if iLevel < log.current_level then + --below the log level + return + end + + local num_arg = #{ ... } + local msg + if num_arg > 0 then + msg = string.format(fmt, ...) + else + msg = fmt + end + + --local lineinfo = "f.lua:0" + --local msg2 = string.format("[%-4s][%-8s] %s: %s", ulevel, M.app_name, lineinfo, msg) + local msg2 = string.format("[%-8s][%-4s] %s", M.app_name, ulevel, msg) + + -- output to console + print(msg2) + + -- Output to log file + if log.enable_file == true and log.outfile then + local fp = io.open(log.outfile, "a") + io.write(fp, msg2 .. "\n") + io.close(fp) + end +end + +function M.trace(fmt, ...) + M.do_log(log.levels.trace, "TRACE", fmt, ...) +end +function M.debug(fmt, ...) + M.do_log(log.levels.debug, "DEBUG", fmt, ...) +end +function M.info(fmt, ...) + M.do_log(log.levels.info, "INFO", fmt, ...) +end +function M.warn(fmt, ...) + M.do_log(log.levels.warn, "WARN", fmt, ...) +end +function M.error(fmt, ...) + M.do_log(log.levels.error, "ERROR", fmt, ...) +end +function M.fatal(fmt, ...) + M.do_log(log.levels.fatal, "FATAL", fmt, ...) +end + +return M diff --git a/sdcard/c480x272/WIDGETS/BattAnalog/lib_sensors.lua b/sdcard/c480x272/WIDGETS/BattAnalog/lib_sensors.lua new file mode 100644 index 00000000..9131dcde --- /dev/null +++ b/sdcard/c480x272/WIDGETS/BattAnalog/lib_sensors.lua @@ -0,0 +1,103 @@ +local m_log, app_name = ... + +local M = {} +M.m_log = m_log +M.app_name = app_name + +--function cache +local math_floor = math.floor +local math_fmod = math.fmod +local string_gmatch = string.gmatch +local string_gsub = string.gsub +local string_len = string.len +local string_sub = string.sub +local string_char = string.char +local string_byte = string.byte + + +--------------------------------------------------------------------------------------------------- +local function log(fmt, ...) + m_log.info(fmt, ...) +end +--------------------------------------------------------------------------------------------------- + +function M.split(text) + local cnt = 0 + local result = {} + for val in string_gmatch(string_gsub(text, ",,", ", ,"), "([^,]+),?") do + cnt = cnt + 1 + result[cnt] = val + end + --m_log.info("split: #col: %d (%s)", cnt, text) + --m_log.info("split: #col: %d (1-%s, 2-%s)", cnt, result[1], result[2]) + return result, cnt +end + +function M.split_pipe(text) + -- m_log.info("split_pipe(%s)", text) + local cnt = 0 + local result = {} + for val in string.gmatch(string.gsub(text, "||", "| |"), "([^|]+)|?") do + cnt = cnt + 1 + result[cnt] = val + end + m_log.info("split_pipe: #col: %d (%s)", cnt, text) + m_log.info("split_pipe: #col: %d [1-%s, 2-%s, ...]", cnt, result[1], result[2]) + return result, cnt +end + +-- remove trailing and leading whitespace from string. +-- http://en.wikipedia.org/wiki/Trim_(programming) +function M.trim(s) + if s == nil then + return nil + end + return (string.gsub(s, "^%s*(.-)%s*$", "%1")) +end + +function M.trim_safe(s) + if s == nil then + return "" + end + return (string.gsub(s, "^%s*(.-)%s*$", "%1")) + --string.gsub(text, ",,", ", ,") +end + +function M.findSourceId(sourceNameList) + local interesting_sources = {} + for i = 200, 400 do + local name = getSourceName(i) + if name ~= nil then + -- workaround for bug in getFiledInfo() -- ???? why? + if string.byte(string.sub(name, 1, 1)) > 127 then name = string.sub(name, 2, -1) end + if string.byte(string.sub(name, 1, 1)) > 127 then name = string.sub(name, 2, -1) end + + for _, sourceName in ipairs(sourceNameList) do + -- print(string.format("init_compare_source: [%s(%d)][%s] (is =? %s)", name, i, sourceName, (name == sourceName))) + if (string.lower(name) == string.lower(sourceName)) then + print(string.format("init_compare_source (collecting): [%s(%d)] == [%s]", name, i, sourceName)) + interesting_sources[#interesting_sources + 1] = {i,name} + end + end + end + end + + -- find the source with highest priority + for _, sourceName in ipairs(sourceNameList) do + for _, source in ipairs(interesting_sources) do + local idx = source[1] + local name = source[2] + -- print(string.format("init_compare_source: is_needed? [%s(%d)]", name, idx)) + if (string.lower(name) == string.lower(sourceName)) then + print(string.format("init_compare_source: we have: %s", sourceName)) + print(string.format("init_compare_source (found): [%s(%d)] == [%s]", name, idx, sourceName)) + return idx + end + end + print(string.format("init_compare_source: we do not have: %s", sourceName)) + end + return 1 +end + + +return M diff --git a/sdcard/c480x272/WIDGETS/BattAnalog/lib_widget_tools.lua b/sdcard/c480x272/WIDGETS/BattAnalog/lib_widget_tools.lua index 54d8c24a..03b07639 100644 --- a/sdcard/c480x272/WIDGETS/BattAnalog/lib_widget_tools.lua +++ b/sdcard/c480x272/WIDGETS/BattAnalog/lib_widget_tools.lua @@ -1,7 +1,7 @@ -local m_log, app_name = ... +local m_log, app_name, useLvgl = ... local M = {} ---M.m_log = m_log +M.m_log = m_log M.app_name = app_name M.tele_src_name = nil M.tele_src_id = nil @@ -16,19 +16,32 @@ local FONT_12 = MIDSIZE -- 12px local FONT_8 = 0 -- Default 8px local FONT_6 = SMLSIZE -- 6px -local FONT_LIST = {FONT_6, FONT_8, FONT_12, FONT_16, FONT_38} +-- better font size names +M.FONT_SIZES = { + FONT_6 = SMLSIZE, -- 6px + FONT_8 = 0, -- Default 8px + FONT_12 = MIDSIZE, -- 12px + FONT_16 = DBLSIZE, -- 16px + FONT_38 = XXLSIZE, -- 38px +} +M.FONT_LIST = { + FONT_6, + FONT_8, + FONT_12, + FONT_16, + FONT_38, +} --------------------------------------------------------------------------------------------------- local function log(fmt, ...) - --m_log.info(fmt, ...) - print("[" .. M.app_name .. "] " .. string.format(fmt, ...)) + m_log.info(fmt, ...) end --------------------------------------------------------------------------------------------------- -- const's local UNIT_ID_TO_STRING = { "V", "A", "mA", "kts", "m/s", "f/s", "km/h", "mph", "m", "f", - "°C", "°F", "%", "mAh", "W", "mW", "dB", "rpm", "g", "°", + "°C", "°F", "%", "mAh", "W", "mW", "dB", "rpm", "g", "°", "rad", "ml", "fOz", "ml/m", "Hz", "mS", "uS", "km" } @@ -56,9 +69,10 @@ end --------------------------------------------------------------------------------------------------- function M.periodicInit() - local t = {} - t.startTime = -1; - t.durationMili = -1; + local t = { + startTime = -1, + durationMili = -1 + } return t end @@ -107,6 +121,11 @@ end --------------------------------------------------------------------------------------------------- function M.isTelemetryAvailable() + local is_telem = getRSSI() + return is_telem > 0 +end + +function M.isTelemetryAvailableOld() -- select telemetry source if not M.tele_src_id then --log("select telemetry source") @@ -178,105 +197,206 @@ function M.detectResetEvent(wgt, callback_onTelemetryResetEvent) end --------------------------------------------------------------------------------------------------- -function M.getSensorPrecession(sensorName) +function M.getSensorInfoByName(sensorName) + local sensors = {} for i=0, 30, 1 do + local s1 = {} local s2 = model.getSensor(i) + --type (number) 0 = custom, 1 = calculated + s1.type = s2.type --name (string) Name + s1.name = s2.name --unit (number) See list of units in the appendix of the OpenTX Lua Reference Guide + s1.unit = s2.unit --prec (number) Number of decimals + s1.prec = s2.prec --id (number) Only custom sensors + s1.id = s2.id --instance (number) Only custom sensors + s1.instance = s2.instance --formula (number) Only calculated sensors. 0 = Add etc. see list of formula choices in Companion popup + s1.formula = s2.formula - --log("getSensorPrecession: %d. name: %s, unit: %s , prec: %s , id: %s , instance: %s ", i, s2.name, s2.unit, s2.prec, s2.id, s2.instance) + log("getSensorInfo: %d. name: %s, unit: %s , prec: %s , id: %s , instance: %s ", i, s2.name, s2.unit, s2.prec, s2.id, s2.instance) if s2.name == sensorName then - return s2.prec + return s1 end end - return -1 + return nil + end + + function M.getSensorPrecession(sensorName) + local sensorInfo = M.getSensorInfoByName(sensorName) + if (sensorInfo == nil) then + log("getSensorPrecession: not found sensor [%s]", sensorName) + return -1 + end + + log("getSensorPrecession: name: %s, prec: %s , id: %s", sensorInfo.name, sensorInfo.prec, sensorInfo.id) + return sensorInfo.prec end ---function M.getSensors() --- local sensors = {} --- for i=0, 30, 1 do --- local s1 = {} --- local s2 = model.getSensor(i) --- --- --type (number) 0 = custom, 1 = calculated --- s1.type = s2.type --- --name (string) Name --- s1.name = s2.name --- --unit (number) See list of units in the appendix of the OpenTX Lua Reference Guide --- s1.unit = s2.unit --- --prec (number) Number of decimals --- s1.prec = s2.prec --- --id (number) Only custom sensors --- s1.id = s2.id --- --instance (number) Only custom sensors --- s1.instance = s2.instance --- --formula (number) Only calculated sensors. 0 = Add etc. see list of formula choices in Companion popup --- s1.formula = s2.formula --- --- s1.appendix --- --- log("getSensorPrecession: %d. name: %s, unit: %s , prec: %s , id: %s , instance: %s ", i, s2.name, s2.unit, s2.prec, s2.id, s2.instance) --- --- if s2.name == sensorName then --- return s2.prec --- end --- end --- --- return -1 ---end + +-- function M.getSensorId(sensorName) +-- local sensorInfo = M.getSensorInfoByName(sensorName) +-- if (sensorInfo == nil) then +-- log("getSensorId: not found sensor [%s]", sensorName) +-- return -1 +-- end + +-- log("getSensorId: name: %s, prec: %s , id: %s", sensorInfo.name, sensorInfo.prec, sensorInfo.id) +-- return sensorInfo.id +-- end + + +function M.isSensorExist(sensorName) + local sensorInfo = M.getSensorInfoByName(sensorName) + local is_exist = (sensorInfo ~= nil) + log("getSensorInfo: [%s] is_exist: %s", sensorName, is_exist) + return is_exist + end --------------------------------------------------------------------------------------------------- -- workaround for bug in getFiledInfo() -- ???? why? function M.cleanInvalidCharFromGetFiledInfo(sourceName) - - if string.byte(string.sub(sourceName, 1, 1)) > 127 then + if string.byte(string.sub(sourceName, 1, 1)) > 127 then sourceName = string.sub(sourceName, 2, -1) end if string.byte(string.sub(sourceName, 1, 1)) > 127 then sourceName = string.sub(sourceName, 2, -1) end + return sourceName +end +-- workaround for bug in getSourceName() +function M.getSourceNameCleaned(source) + local sourceName = getSourceName(source) + if (sourceName == nil) then + return "N/A" + end + local sourceName = M.cleanInvalidCharFromGetFiledInfo(sourceName) return sourceName end ------------------------------------------------------------------------------------------------------ + function M.getFontSizeRelative(orgFontSize, delta) - for i = 1, #FONT_LIST do - if FONT_LIST[i] == orgFontSize then + for i = 1, #M.FONT_LIST do + if M.FONT_LIST[i] == orgFontSize then local newIndex = i + delta - newIndex = math.min(newIndex, #FONT_LIST) + newIndex = math.min(newIndex, #M.FONT_LIST) newIndex = math.max(newIndex, 1) - return FONT_LIST[newIndex] + return M.FONT_LIST[newIndex] end end return orgFontSize end +function M.getFontIndex(fontSize, defaultFontSize) + for i = 1, #M.FONT_LIST do + -- log("M.FONT_SIZES[%d]: %d (%d)", i, M.FONT_LIST[i], fontSize) + if M.FONT_LIST[i] == fontSize then + return i + end + end + return defaultFontSize +end + ------------------------------------------------------------------------------------------------------ +-- function M.lcdSizeTextFixed(txt, font_size) +-- local ts_w, ts_h = lcd.sizeText(txt, font_size) + +-- local v_offset = 0 +-- if font_size == FONT_38 then +-- if (useLvgl==true) then +-- v_offset = -7 +-- return ts_w-3, ts_h +2*v_offset-14, v_offset +-- else +-- v_offset = -14 +-- end +-- elseif font_size == FONT_16 then +-- v_offset = -8 +-- elseif font_size == FONT_12 then +-- v_offset = -6 +-- elseif font_size == FONT_8 then +-- v_offset = -4 +-- elseif font_size == FONT_6 then +-- v_offset = -3 +-- end +-- return ts_w, ts_h +2*v_offset, v_offset +-- end + function M.lcdSizeTextFixed(txt, font_size) local ts_w, ts_h = lcd.sizeText(txt, font_size) local v_offset = 0 if font_size == FONT_38 then - v_offset = -15 + if (useLvgl==true) then + v_offset = -7 + ts_h = 61 + return ts_w-3, ts_h+v_offset-14, v_offset + -- return ts_w-3, ts_h +2*v_offset-14, v_offset + else + v_offset = -14 + ts_h = 61 + end elseif font_size == FONT_16 then v_offset = -8 + ts_h = 30 elseif font_size == FONT_12 then v_offset = -6 + ts_h = 23 elseif font_size == FONT_8 then v_offset = -4 + ts_h = 16 elseif font_size == FONT_6 then - v_offset = -3 + v_offset = -4 + ts_h = 13 + end + -- return ts_w, ts_h +2*v_offset, v_offset + return ts_w, ts_h+v_offset, v_offset +end + +function M.getFontSize(wgt, txt, max_w, max_h, max_font_size) + log("getFontSize() [%s] %dx%d", txt, max_w, max_h) + local maxFontIndex = M.getFontIndex(max_font_size, nil) + + if M.getFontIndex(FONT_38, nil) <= maxFontIndex then + local w, h, v_offset = M.lcdSizeTextFixed(txt, FONT_38) + if w <= max_w and h <= max_h then + log("[%s] FONT_38 %dx%d", txt, w, h) + return FONT_38, w, h, v_offset + else + log("[%s] FONT_38 %dx%d (too small)", txt, w, h) + end + end + + + w, h, v_offset = M.lcdSizeTextFixed(txt, FONT_16) + if w <= max_w and h <= max_h then + log("[%s] FONT_16 %dx%d", txt, w, h) + return FONT_16, w, h, v_offset end - return ts_w, ts_h +2*v_offset, v_offset + + w, h, v_offset = M.lcdSizeTextFixed(txt, FONT_12) + if w <= max_w and h <= max_h then + log("[%s] FONT_12 %dx%d", txt, w, h) + return FONT_12, w, h, v_offset + end + + w, h, v_offset = M.lcdSizeTextFixed(txt, FONT_8) + if w <= max_w and h <= max_h then + log("[%s] FONT_8 %dx%d", txt, w, h) + return FONT_8, w, h, v_offset + end + + w, h, v_offset = M.lcdSizeTextFixed(txt, FONT_6) + log("[%s] FONT_6 %dx%d", txt, w, h) + return FONT_6, w, h, v_offset end ------------------------------------------------------------------------------------------------------ diff --git a/sdcard/c480x272/WIDGETS/BattAnalog/logic.lua b/sdcard/c480x272/WIDGETS/BattAnalog/logic.lua new file mode 100644 index 00000000..ed73496c --- /dev/null +++ b/sdcard/c480x272/WIDGETS/BattAnalog/logic.lua @@ -0,0 +1,355 @@ + + +-- The widget table will be returned to the main script. +local wgt = { + options = nil, + zone = nil, + counter = 0, + text_color = 0, + + telemResetCount = 0, + telemResetLowestMinRSSI = 101, + no_telem_blink = 0, + isDataAvailable = 0, + vMax = 0, + vMin = 0, + vTotalLive = 0, + vPercent = 0, + cellCount = 1, + cell_detected = false, + autoCellDetection = nil, + vCellLive = 0, + mainValue = 0, + secondaryValue = 0, + + source_name = "", +} + +-- Data gathered from commercial lipo sensors +local percent_list_lipo = { + { {3.000, 0}}, + { {3.093, 1}, {3.196, 2}, {3.301, 3}, {3.401, 4}, {3.477, 5}, {3.544, 6}, {3.601, 7}, {3.637, 8}, {3.664, 9}, {3.679, 10} }, + { {3.683, 11}, {3.689, 12}, {3.692, 13}, {3.705, 14}, {3.710, 15}, {3.713, 16}, {3.715, 17}, {3.720, 18}, {3.731, 19}, {3.735, 20} }, + { {3.744, 21}, {3.753, 22}, {3.756, 23}, {3.758, 24}, {3.762, 25}, {3.767, 26}, {3.774, 27}, {3.780, 28}, {3.783, 29}, {3.786, 30} }, + { {3.789, 31}, {3.794, 32}, {3.797, 33}, {3.800, 34}, {3.802, 35}, {3.805, 36}, {3.808, 37}, {3.811, 38}, {3.815, 39}, {3.818, 40} }, + { {3.822, 41}, {3.825, 42}, {3.829, 43}, {3.833, 44}, {3.836, 45}, {3.840, 46}, {3.843, 47}, {3.847, 48}, {3.850, 49}, {3.854, 50} }, + { {3.857, 51}, {3.860, 52}, {3.863, 53}, {3.866, 54}, {3.870, 55}, {3.874, 56}, {3.879, 57}, {3.888, 58}, {3.893, 59}, {3.897, 60} }, + { {3.902, 61}, {3.906, 62}, {3.911, 63}, {3.918, 64}, {3.923, 65}, {3.928, 66}, {3.939, 67}, {3.943, 68}, {3.949, 69}, {3.955, 70} }, + { {3.961, 71}, {3.968, 72}, {3.974, 73}, {3.981, 74}, {3.987, 75}, {3.994, 76}, {4.001, 77}, {4.007, 78}, {4.014, 79}, {4.021, 80} }, + { {4.029, 81}, {4.036, 82}, {4.044, 83}, {4.052, 84}, {4.062, 85}, {4.074, 86}, {4.085, 87}, {4.095, 88}, {4.105, 89}, {4.111, 90} }, + { {4.116, 91}, {4.120, 92}, {4.125, 93}, {4.129, 94}, {4.135, 95}, {4.145, 96}, {4.176, 97}, {4.179, 98}, {4.193, 99}, {4.200,100} }, +} + +-- from: https://electric-scooter.guide/guides/electric-scooter-battery-voltage-chart/ +local percent_list_lion = { + { { 2.800, 0 }, { 2.840, 1 }, { 2.880, 2 }, { 2.920, 3 }, { 2.960, 4 } }, + { { 3.000, 5 }, { 3.040, 6 }, { 3.080, 7 }, { 3.096, 8 }, { 3.112, 9 } }, + { { 3.128, 10 }, { 3.144, 11 }, { 3.160, 12 }, { 3.176, 13 }, { 3.192, 14 } }, + { { 3.208, 15 }, { 3.224, 16 }, { 3.240, 17 }, { 3.256, 18 }, { 3.272, 19 } }, + { { 3.288, 20 }, { 3.304, 21 }, { 3.320, 22 }, { 3.336, 23 }, { 3.352, 24 } }, + { { 3.368, 25 }, { 3.384, 26 }, { 3.400, 27 }, { 3.416, 28 }, { 3.432, 29 } }, + { { 3.448, 30 }, { 3.464, 31 }, { 3.480, 32 }, { 3.496, 33 }, { 3.504, 34 } }, + { { 3.512, 35 }, { 3.520, 36 }, { 3.528, 37 }, { 3.536, 38 }, { 3.544, 39 } }, + { { 3.552, 40 }, { 3.560, 41 }, { 3.568, 42 }, { 3.576, 43 }, { 3.584, 44 } }, + { { 3.592, 45 }, { 3.600, 46 }, { 3.608, 47 }, { 3.616, 48 }, { 3.624, 49 } }, + { { 3.632, 50 }, { 3.640, 51 }, { 3.648, 52 }, { 3.656, 53 }, { 3.664, 54 } }, + { { 3.672, 55 }, { 3.680, 56 }, { 3.688, 57 }, { 3.696, 58 }, { 3.704, 59 } }, + { { 3.712, 60 }, { 3.720, 61 }, { 3.728, 62 }, { 3.736, 63 }, { 3.744, 64 } }, + { { 3.752, 65 }, { 3.760, 66 }, { 3.768, 67 }, { 3.776, 68 }, { 3.784, 69 } }, + { { 3.792, 70 }, { 3.800, 71 }, { 3.810, 72 }, { 3.820, 73 }, { 3.830, 74 } }, + { { 3.840, 75 }, { 3.850, 76 }, { 3.860, 77 }, { 3.870, 78 }, { 3.880, 79 } }, + { { 3.890, 80 }, { 3.900, 81 }, { 3.910, 82 }, { 3.920, 83 }, { 3.930, 84 } }, + { { 3.940, 85 }, { 3.950, 86 }, { 3.960, 87 }, { 3.970, 88 }, { 3.980, 89 } }, + { { 3.990, 90 }, { 4.000, 91 }, { 4.010, 92 }, { 4.030, 93 }, { 4.050, 94 } }, + { { 4.070, 95 }, { 4.090, 96 } }, + { { 4.10, 100}, { 4.15,100 }, { 4.20, 100} }, +} + +local percent_list_hv = { + { {3.000, 0}}, + { {3.093, 1}, {3.196, 2}, {3.301, 3}, {3.401, 4}, {3.477, 5}, {3.544, 6}, {3.601, 7}, {3.637, 8}, {3.664, 9}, {3.679, 10} }, + { {3.683, 11}, {3.689, 12}, {3.692, 13}, {3.705, 14}, {3.710, 15}, {3.713, 16}, {3.715, 17}, {3.720, 18}, {3.731, 19}, {3.735, 20} }, + { {3.744, 21}, {3.753, 22}, {3.756, 23}, {3.758, 24}, {3.762, 25}, {3.767, 26}, {3.774, 27}, {3.780, 28}, {3.783, 29}, {3.786, 30} }, + { {3.789, 31}, {3.794, 32}, {3.797, 33}, {3.800, 34}, {3.802, 35}, {3.805, 36}, {3.808, 37}, {3.811, 38}, {3.815, 39}, {3.828, 40} }, + { {3.832, 41}, {3.836, 42}, {3.841, 43}, {3.846, 44}, {3.850, 45}, {3.855, 46}, {3.859, 47}, {3.864, 48}, {3.868, 49}, {3.873, 50} }, + { {3.877, 51}, {3.881, 52}, {3.885, 53}, {3.890, 54}, {3.895, 55}, {3.900, 56}, {3.907, 57}, {3.917, 58}, {3.924, 59}, {3.929, 60} }, + { {3.936, 61}, {3.942, 62}, {3.949, 63}, {3.957, 64}, {3.964, 65}, {3.971, 66}, {3.984, 67}, {3.990, 68}, {3.998, 69}, {4.006, 70} }, + { {4.015, 71}, {4.024, 72}, {4.032, 73}, {4.042, 74}, {4.050, 75}, {4.060, 76}, {4.069, 77}, {4.078, 78}, {4.088, 79}, {4.098, 80} }, + { {4.109, 81}, {4.119, 82}, {4.130, 83}, {4.141, 84}, {4.154, 85}, {4.169, 86}, {4.184, 87}, {4.197, 88}, {4.211, 89}, {4.220, 90} }, + { {4.229, 91}, {4.237, 92}, {4.246, 93}, {4.254, 94}, {4.264, 95}, {4.278, 96}, {4.302, 97}, {4.320, 98}, {4.339, 99}, {4.350,100} }, +} + +local voltageRanges_lipo = {4.3, 8.6, 12.9, 17.2, 21.5, 25.8, 30.1, 34.4, 38.7, 43.0, 47.3, 51.6} +local voltageRanges_lion = {4.2, 8.4, 12.6, 16.8, 21, 25.2, 29.4, 33.6, 37.8, 42, 46.2, 50.4, 54.6} +local voltageRanges_hv = {4.45, 8.9, 13.35, 17.8, 22.25, 26.7, 31.15, 35.6, 40.05, 44.5, 48.95, 53.4, 57.85} + +-------------------------------------------------------------- + +function wgt.update_logic(wgt, options) + if (wgt == nil) then + return + end + + wgt.periodic1 = wgt.tools.periodicInit() + wgt.cell_detected = false + + if (wgt.options.cbCellCount == 1) then + wgt.autoCellDetection = true + else + wgt.autoCellDetection = false + wgt.cellCount = wgt.options.cbCellCount - 1 + end + wgt.log("cbCellCount: %s, autoCellDetection: %s, cellCount: %s", wgt.options.cbCellCount, wgt.autoCellDetection, wgt.cellCount) + + wgt.source_name = "" + if (type(wgt.options.sensor) == "number") then + local source_name = getSourceName(wgt.options.sensor) + if (source_name ~= nil) then + if string.byte(string.sub(source_name, 1, 1)) > 127 then + source_name = string.sub(source_name, 2, -1) -- ???? why? + end + if string.byte(string.sub(source_name, 1, 1)) > 127 then + source_name = string.sub(source_name, 2, -1) -- ???? why? + end + -- wgt.log(string.format("source_name: %s", source_name)) + wgt.source_name = source_name + end + else + wgt.source_name = wgt.options.sensor + end + + wgt.options.isTotalVoltage = wgt.options.isTotalVoltage % 2 -- modulo due to bug that cause the value to be other than 0|1 + + -- wgt.log("wgt.options.batt_type: %s", wgt.options.batt_type) +end + +-------------------------------------------------------------- + +-- clear old telemetry data upon reset event +function wgt.onTelemetryResetEvent(wgt) + wgt.log("telemetry reset event detected.") + wgt.telemResetCount = wgt.telemResetCount + 1 + + wgt.vTotalLive = 0 + wgt.vCellLive = 0 + wgt.vMin = 99 + wgt.vMax = 0 + wgt.cellCount = 1 + wgt.cell_detected = false + -- wgt.tools.periodicStart(wgt.periodic1, CELL_DETECTION_TIME * 1000) +end + +--- This function return the percentage remaining in a single Lipo cel +function wgt.getCellPercent(cellValue) + if cellValue == nil then + return 0 + end + + -- in case somehow voltage is higher, don't return nil + if (cellValue > 4.2) then + return 100 + end + + local _percentList = percent_list_lipo + if wgt.options.batt_type == 1 then + _percentList = percent_list_lipo + elseif wgt.options.batt_type == 2 then + _percentList = percent_list_hv + elseif wgt.options.batt_type == 3 then + _percentList = percent_list_lion + end + + local result = 0 + for i1, v1 in ipairs(_percentList) do + -- log(string.format("sub-list#: %s, head:%f, length: %d, last: %.3f", i1,v1[1][1], #v1, v1[#v1][1])) + -- is the cellVal < last-value-on-sub-list? (first-val:v1[1], last-val:v1[#v1]) + if (cellValue <= v1[#v1][1]) then + -- cellVal is in this sub-list, find the exact value + -- log("this is the list") + for i2, v2 in ipairs(v1) do + -- log(string.format("cell#: %s, %.3f--> %d%%", i2,v2[1], v2[2])) + if v2[1] >= cellValue then + result = v2[2] + -- log(string.format("result: %d%%", result)) + -- cpuProfilerAdd(wgt, 'cell-perc', t4); + return result + end + end + end + end + + -- for i, v in ipairs(_percentListSplit) do + -- if v[1] >= cellValue then + -- result = v[2] + -- break + -- end + -- end + return result +end + +local function calcCellCount(singleVoltage) + local voltageRanges = voltageRanges_lipo + + if wgt.options.batt_type == 1 then + voltageRanges = voltageRanges_lipo + elseif wgt.options.batt_type == 2 then + voltageRanges = voltageRanges_hv + elseif wgt.options.batt_type == 3 then + voltageRanges = voltageRanges_lion + end + + for i = 1, #voltageRanges do + if singleVoltage < voltageRanges[i] then + -- log("calcCellCount %s --> %s", singleVoltage, i) + return i + end + end + + log("no match found" .. singleVoltage) + return 1 +end + +--- This function returns a table with cels values +function wgt.calculateBatteryData() + + local v = getValue(wgt.options.sensor) + local fieldinfo = getFieldInfo(wgt.options.sensor) + -- log("wgt.options.sensor: " .. wgt.options.sensor) + + if type(v) == "table" then + -- multi cell values using FLVSS liPo Voltage Sensor + if (#v > 1) then + wgt.isDataAvailable = false + -- log("FLVSS liPo Voltage Sensor, not supported") + return + end + elseif v ~= nil and v >= 1 then + -- single cell or VFAS lipo sensor + if fieldinfo then + -- log(wgt.source_name .. ", value: " .. fieldinfo.name .. "=" .. v) + else + -- log("only one cell using Ax lipo sensor") + end + else + -- no telemetry available + wgt.isDataAvailable = false + if fieldinfo then + -- log("no telemetry data: " .. fieldinfo['name'] .. "=??") + else + -- log("no telemetry data") + end + return + end + + if wgt.autoCellDetection == true then + if (wgt.cell_detected == true) then + -- log("permanent cellCount: " .. wgt.cellCount) + else + local newCellCount = calcCellCount(v) + if (wgt.tools.periodicHasPassed(wgt.periodic1, false)) then + wgt.cell_detected = true + wgt.cellCount = newCellCount + else + local duration_passed = wgt.tools.periodicGetElapsedTime(wgt.periodic1, false) + -- log(string.format("detecting cells: %ss, %d/%d msec", newCellCount, duration_passed, wgt.tools.getDurationMili(wgt.periodic1))) + + -- this is necessary for simu where cell-count can change + if newCellCount ~= wgt.cellCount then + wgt.vMin = 99 + wgt.vMax = 0 + end + wgt.cellCount = newCellCount + end + end + -- else + -- log("cellCount:autoCellDetection=%s", wgt.autoCellDetection) + end + -- calc highest of all cells + if v > wgt.vMax then + wgt.vMax = v + end + + wgt.vTotalLive = v + wgt.vCellLive = wgt.vTotalLive / wgt.cellCount + wgt.vPercent = wgt.getCellPercent(wgt.vCellLive) + + -- log("wgt.vCellLive: ".. wgt.vCellLive) + -- log("wgt.vPercent: ".. wgt.vPercent) + + -- mainValue + if wgt.options.isTotalVoltage == 0 then + wgt.mainValue = wgt.vCellLive + wgt.secondaryValue = wgt.vTotalLive + elseif wgt.options.isTotalVoltage == 1 then + wgt.mainValue = wgt.vTotalLive + wgt.secondaryValue = wgt.vCellLive + else + wgt.mainValue = "-1" + wgt.secondaryValue = "-2" + end + + --- calc lowest main voltage + if wgt.mainValue < wgt.vMin and wgt.mainValue > 1 then + -- min 1v to consider a valid reading + wgt.vMin = wgt.mainValue + end + + wgt.isDataAvailable = true + if wgt.cell_detected == true then + wgt.tools.periodicStart(wgt.periodic1, CELL_DETECTION_TIME * 1000) + end + +end + +-- color for battery +-- This function returns green at 100%, red bellow 30% and graduate in between +function wgt.getPercentColor(percent) + if percent < 30 then + return lcd.RGB(0xff, 0, 0) + else + g = math.floor(0xdf * percent / 100) + r = 0xdf - g + return lcd.RGB(r, g, 0) + end +end + +-- color for cell +-- This function returns green at gvalue, red at rvalue and graduate in between +function wgt.getRangeColor(value, green_value, red_value) + local range = math.abs(green_value - red_value) + if range == 0 then + return lcd.RGB(0, 0xdf, 0) + end + if value == nil then + return lcd.RGB(0, 0xdf, 0) + end + + if green_value > red_value then + if value > green_value then + return lcd.RGB(0, 0xdf, 0) + end + if value < red_value then + return lcd.RGB(0xdf, 0, 0) + end + g = math.floor(0xdf * (value - red_value) / range) + r = 0xdf - g + return lcd.RGB(r, g, 0) + else + if value > green_value then + return lcd.RGB(0, 0xdf, 0) + end + if value < red_value then + return lcd.RGB(0xdf, 0, 0) + end + r = math.floor(0xdf * (value - green_value) / range) + g = 0xdf - r + return lcd.RGB(r, g, 0) + end +end + +function wgt.background() + wgt.tools.detectResetEvent(wgt, wgt.onTelemetryResetEvent) + wgt.calculateBatteryData() +end + +return wgt diff --git a/sdcard/c480x272/WIDGETS/BattAnalog/main.lua b/sdcard/c480x272/WIDGETS/BattAnalog/main.lua index efc66c98..d1dc4e80 100644 --- a/sdcard/c480x272/WIDGETS/BattAnalog/main.lua +++ b/sdcard/c480x272/WIDGETS/BattAnalog/main.lua @@ -1,7 +1,7 @@ --[[ ######################################################################### # # -# Telemetry Widget script for FrSky Horus/RadioMaster TX16s # +# Telemetry Widget script for RadioMaster TX16S # # Copyright "Offer Shmuely" # # # # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html # @@ -18,7 +18,7 @@ ######################################################################### --- This widget display a graphical representation of a Lipo/Li-ion (not other types) battery level, +-- This widget display a graphical representation of a Lipo/Li-ion/LIHV (not other types) battery level, -- it will automatically detect the cell amount of the battery. -- it will take a lipo/li-ion voltage that received as a single value (as opposed to multi cell values send while using FLVSS liPo Voltage Sensor) -- common sources are: @@ -34,563 +34,104 @@ -- Widget to display the levels of Lipo battery from single analog source -- Author : Offer Shmuely --- Date: 2021-2023 +-- Date: 2021-2024 local app_name = "BattAnalog" -local app_ver = "0.8" +local app_ver = "1.1" local CELL_DETECTION_TIME = 8 +local lib_sensors = loadScript("/WIDGETS/" .. app_name .. "/lib_sensors.lua", "tcd")(m_log,app_name) +local DEFAULT_SOURCE = lib_sensors.findSourceId( {"cell","VFAS","RxBt","A1", "A2"}) +local useLvgl = true local _options = { - { "Sensor" , SOURCE, 0 }, -- default to 'A1' - { "Color" , COLOR , YELLOW }, - { "Show_Total_Voltage", BOOL , 0 }, -- 0=Show as average Lipo cell level, 1=show the total voltage (voltage as is) - { "Lithium_Ion" , BOOL , 0 }, -- 0=LIPO battery, 1=LI-ION (18650/21500) - { "Lithium_HV" , BOOL , 0 }, -- 0=LIPO battery, 1=LiHV 4.35V + {"sensor" , SOURCE, DEFAULT_SOURCE }, + {"batt_type" , CHOICE, 1 , {"LiPo", "LiPo-HV (high voltage)", "Li-Ion"} }, + {"cbCellCount" , CHOICE, 1 , {"Auto Detection", "1 cell", "2 cell", "3 cell", "4 cell", "5 cell", "6 cell", "8 cell", "10 cell", "12 cell", "14 cell"} }, + {"isTotalVoltage" , BOOL , 0 }, -- 0=Show as average Lipo cell level, 1=show the total voltage (voltage as is) + {"color" , COLOR , YELLOW }, } --- Data gathered from commercial lipo sensors -local percent_list_lipo = { - { {3.000, 0}}, - { {3.093, 1}, {3.196, 2}, {3.301, 3}, {3.401, 4}, {3.477, 5}, {3.544, 6}, {3.601, 7}, {3.637, 8}, {3.664, 9}, {3.679, 10} }, - { {3.683, 11}, {3.689, 12}, {3.692, 13}, {3.705, 14}, {3.710, 15}, {3.713, 16}, {3.715, 17}, {3.720, 18}, {3.731, 19}, {3.735, 20} }, - { {3.744, 21}, {3.753, 22}, {3.756, 23}, {3.758, 24}, {3.762, 25}, {3.767, 26}, {3.774, 27}, {3.780, 28}, {3.783, 29}, {3.786, 30} }, - { {3.789, 31}, {3.794, 32}, {3.797, 33}, {3.800, 34}, {3.802, 35}, {3.805, 36}, {3.808, 37}, {3.811, 38}, {3.815, 39}, {3.818, 40} }, - { {3.822, 41}, {3.825, 42}, {3.829, 43}, {3.833, 44}, {3.836, 45}, {3.840, 46}, {3.843, 47}, {3.847, 48}, {3.850, 49}, {3.854, 50} }, - { {3.857, 51}, {3.860, 52}, {3.863, 53}, {3.866, 54}, {3.870, 55}, {3.874, 56}, {3.879, 57}, {3.888, 58}, {3.893, 59}, {3.897, 60} }, - { {3.902, 61}, {3.906, 62}, {3.911, 63}, {3.918, 64}, {3.923, 65}, {3.928, 66}, {3.939, 67}, {3.943, 68}, {3.949, 69}, {3.955, 70} }, - { {3.961, 71}, {3.968, 72}, {3.974, 73}, {3.981, 74}, {3.987, 75}, {3.994, 76}, {4.001, 77}, {4.007, 78}, {4.014, 79}, {4.021, 80} }, - { {4.029, 81}, {4.036, 82}, {4.044, 83}, {4.052, 84}, {4.062, 85}, {4.074, 86}, {4.085, 87}, {4.095, 88}, {4.105, 89}, {4.111, 90} }, - { {4.116, 91}, {4.120, 92}, {4.125, 93}, {4.129, 94}, {4.135, 95}, {4.145, 96}, {4.176, 97}, {4.179, 98}, {4.193, 99}, {4.200,100} }, -} - --- from: https://electric-scooter.guide/guides/electric-scooter-battery-voltage-chart/ -local percent_list_lion = { - { { 2.800, 0 }, { 2.840, 1 }, { 2.880, 2 }, { 2.920, 3 }, { 2.960, 4 } }, - { { 3.000, 5 }, { 3.040, 6 }, { 3.080, 7 }, { 3.096, 8 }, { 3.112, 9 } }, - { { 3.128, 10 }, { 3.144, 11 }, { 3.160, 12 }, { 3.176, 13 }, { 3.192, 14 } }, - { { 3.208, 15 }, { 3.224, 16 }, { 3.240, 17 }, { 3.256, 18 }, { 3.272, 19 } }, - { { 3.288, 20 }, { 3.304, 21 }, { 3.320, 22 }, { 3.336, 23 }, { 3.352, 24 } }, - { { 3.368, 25 }, { 3.384, 26 }, { 3.400, 27 }, { 3.416, 28 }, { 3.432, 29 } }, - { { 3.448, 30 }, { 3.464, 31 }, { 3.480, 32 }, { 3.496, 33 }, { 3.504, 34 } }, - { { 3.512, 35 }, { 3.520, 36 }, { 3.528, 37 }, { 3.536, 38 }, { 3.544, 39 } }, - { { 3.552, 40 }, { 3.560, 41 }, { 3.568, 42 }, { 3.576, 43 }, { 3.584, 44 } }, - { { 3.592, 45 }, { 3.600, 46 }, { 3.608, 47 }, { 3.616, 48 }, { 3.624, 49 } }, - { { 3.632, 50 }, { 3.640, 51 }, { 3.648, 52 }, { 3.656, 53 }, { 3.664, 54 } }, - { { 3.672, 55 }, { 3.680, 56 }, { 3.688, 57 }, { 3.696, 58 }, { 3.704, 59 } }, - { { 3.712, 60 }, { 3.720, 61 }, { 3.728, 62 }, { 3.736, 63 }, { 3.744, 64 } }, - { { 3.752, 65 }, { 3.760, 66 }, { 3.768, 67 }, { 3.776, 68 }, { 3.784, 69 } }, - { { 3.792, 70 }, { 3.800, 71 }, { 3.810, 72 }, { 3.820, 73 }, { 3.830, 74 } }, - { { 3.840, 75 }, { 3.850, 76 }, { 3.860, 77 }, { 3.870, 78 }, { 3.880, 79 } }, - { { 3.890, 80 }, { 3.900, 81 }, { 3.910, 82 }, { 3.920, 83 }, { 3.930, 84 } }, - { { 3.940, 85 }, { 3.950, 86 }, { 3.960, 87 }, { 3.970, 88 }, { 3.980, 89 } }, - { { 3.990, 90 }, { 4.000, 91 }, { 4.010, 92 }, { 4.030, 93 }, { 4.050, 94 } }, - { { 4.070, 95 }, { 4.090, 96 } }, - { { 4.10, 100}, { 4.15,100 }, { 4.20, 100} }, -} - -local percent_list_hv = { - { {3.000, 0}}, - { {3.093, 1}, {3.196, 2}, {3.301, 3}, {3.401, 4}, {3.477, 5}, {3.544, 6}, {3.601, 7}, {3.637, 8}, {3.664, 9}, {3.679, 10} }, - { {3.683, 11}, {3.689, 12}, {3.692, 13}, {3.705, 14}, {3.710, 15}, {3.713, 16}, {3.715, 17}, {3.720, 18}, {3.731, 19}, {3.735, 20} }, - { {3.744, 21}, {3.753, 22}, {3.756, 23}, {3.758, 24}, {3.762, 25}, {3.767, 26}, {3.774, 27}, {3.780, 28}, {3.783, 29}, {3.786, 30} }, - { {3.789, 31}, {3.794, 32}, {3.797, 33}, {3.800, 34}, {3.802, 35}, {3.805, 36}, {3.808, 37}, {3.811, 38}, {3.815, 39}, {3.828, 40} }, - { {3.832, 41}, {3.836, 42}, {3.841, 43}, {3.846, 44}, {3.850, 45}, {3.855, 46}, {3.859, 47}, {3.864, 48}, {3.868, 49}, {3.873, 50} }, - { {3.877, 51}, {3.881, 52}, {3.885, 53}, {3.890, 54}, {3.895, 55}, {3.900, 56}, {3.907, 57}, {3.917, 58}, {3.924, 59}, {3.929, 60} }, - { {3.936, 61}, {3.942, 62}, {3.949, 63}, {3.957, 64}, {3.964, 65}, {3.971, 66}, {3.984, 67}, {3.990, 68}, {3.998, 69}, {4.006, 70} }, - { {4.015, 71}, {4.024, 72}, {4.032, 73}, {4.042, 74}, {4.050, 75}, {4.060, 76}, {4.069, 77}, {4.078, 78}, {4.088, 79}, {4.098, 80} }, - { {4.109, 81}, {4.119, 82}, {4.130, 83}, {4.141, 84}, {4.154, 85}, {4.169, 86}, {4.184, 87}, {4.197, 88}, {4.211, 89}, {4.220, 90} }, - { {4.229, 91}, {4.237, 92}, {4.246, 93}, {4.254, 94}, {4.264, 95}, {4.278, 96}, {4.302, 97}, {4.320, 98}, {4.339, 99}, {4.350,100} }, -} - -local voltageRanges_lipo = {4.3,8.6,12.9,17.2,21.5,25.8,30.1,34.4,38.7,43.0,47.3,51.6} -local voltageRanges_lion = {4.2,8.4,12.6,16.8,21,25.2,29.4,33.6,37.8,42,46.2,50.4,54.6} -local voltageRanges_hv = {4.45,8.9,13.35,17.8,22.25,26.7,31.15,35.6,40.05,44.5,48.95,53.4,57.85} - - -local defaultSensor = "RxBt" -- RxBt / A1 / A3/ VFAS / Batt - --------------------------------------------------------------- -local function log(s) - --print("[" .. app_name .. "]" .. s) -end --------------------------------------------------------------- - -local function update(wgt, options) - if (wgt == nil) then - return - end - - wgt.options = options - wgt.periodic1 = wgt.tools.periodicInit() - wgt.cell_detected = false - - -- use default if user did not set, So widget is operational on "select widget" - if wgt.options.Sensor == 0 then - wgt.options.Sensor = defaultSensor - end - - wgt.options.source_name = "" - if (type(wgt.options.Sensor) == "number") then - local source_name = getSourceName(wgt.options.Sensor) - if (source_name ~= nil) then - if string.byte(string.sub(source_name, 1, 1)) > 127 then - source_name = string.sub(source_name, 2, -1) -- ???? why? - end - if string.byte(string.sub(source_name, 1, 1)) > 127 then - source_name = string.sub(source_name, 2, -1) -- ???? why? - end - log(string.format("source_name: %s", source_name)) - wgt.options.source_name = source_name - end - else - wgt.options.source_name = wgt.options.Sensor - end - - wgt.options.Show_Total_Voltage = wgt.options.Show_Total_Voltage % 2 -- modulo due to bug that cause the value to be other than 0|1 - - log(string.format("wgt.options.Lithium_Ion: %s", wgt.options.Lithium_Ion)) - log(string.format("wgt.options.Lithium_HV: %s", wgt.options.Lithium_HV)) +local function translate(nam) + local translations = { + sensor = "Voltage Sensor", + color = "Text Color", + isTotalVoltage="Show Total Voltage", + batt_type="Battery Type", + cellCount = "Cell Count (0=auto)", + cbCellCount = "Cell Count", + } + return translations[nam] end local function create(zone, options) - local wgt = { - zone = zone, - options = options, - counter = 0, - text_color = 0, - - telemResetCount = 0, - telemResetLowestMinRSSI = 101, - no_telem_blink = 0, - isDataAvailable = 0, - vMax = 0, - vMin = 0, - vTotalLive = 0, - vPercent = 0, - cellCount = 1, - cell_detected = false, - vCellLive = 0, - mainValue = 0, - secondaryValue = 0 - } - -- imports - wgt.ToolsClass = loadScript("/WIDGETS/" .. app_name .. "/lib_widget_tools.lua", "tcd") - wgt.tools = wgt.ToolsClass(nil, app_name) - - update(wgt, options) - return wgt -end - --- clear old telemetry data upon reset event -local function onTelemetryResetEvent(wgt) - log("telemetry reset event detected.") - wgt.telemResetCount = wgt.telemResetCount + 1 - - wgt.vTotalLive = 0 - wgt.vCellLive = 0 - wgt.vMin = 99 - wgt.vMax = 0 - wgt.cellCount = 1 - wgt.cell_detected = false - --wgt.tools.periodicStart(wgt.periodic1, CELL_DETECTION_TIME * 1000) -end - ---- This function return the percentage remaining in a single Lipo cel -local function getCellPercent(wgt, cellValue) - if cellValue == nil then - return 0 - end - - -- in case somehow voltage is higher, don't return nil - if (cellValue > 4.2) then - return 100 - end - - local _percentListSplit = percent_list_lipo - if wgt.options.Lithium_Ion == 1 and wgt.options.Lithium_HV == 0 then - _percentListSplit = percent_list_lion - end - if wgt.options.Lithium_Ion == 0 and wgt.options.Lithium_HV == 1 then - _percentListSplit = percent_list_hv - end - - for i1, v1 in ipairs(_percentListSplit) do - --log(string.format("sub-list#: %s, head:%f, length: %d, last: %.3f", i1,v1[1][1], #v1, v1[#v1][1])) - --is the cellVal < last-value-on-sub-list? (first-val:v1[1], last-val:v1[#v1]) - if (cellValue <= v1[#v1][1]) then - -- cellVal is in this sub-list, find the exact value - --log("this is the list") - for i2, v2 in ipairs(v1) do - --log(string.format("cell#: %s, %.3f--> %d%%", i2,v2[1], v2[2])) - if v2[1] >= cellValue then - result = v2[2] - --log(string.format("result: %d%%", result)) - --cpuProfilerAdd(wgt, 'cell-perc', t4); - return result - end - end - end - end - - --for i, v in ipairs(_percentListSplit) do - -- if v[1] >= cellValue then - -- result = v[2] - -- break - -- end - --end - return result -end - --- Only invoke this function once. -local function calcCellCount(wgt, singleVoltage) - local voltageRanges = voltageRanges_lipo - - if wgt.options.Lithium_Ion == 1 and wgt.options.Lithium_HV == 0 then - voltageRanges = voltageRanges_lion - end - if wgt.options.Lithium_Ion == 0 and wgt.options.Lithium_HV == 1 then - voltageRanges = voltageRanges_hv - end - - for i = 1, #voltageRanges do - if singleVoltage < voltageRanges[i] then - log("calcCellCount %s --> %s", singleVoltage, i) - return i - end - end - - log("no match found" .. singleVoltage) - return 1 -end - ---- This function returns a table with cels values -local function calculateBatteryData(wgt) - - local v = getValue(wgt.options.Sensor) - local fieldinfo = getFieldInfo(wgt.options.Sensor) - log("wgt.options.Sensor: " .. wgt.options.Sensor) - - if type(v) == "table" then - -- multi cell values using FLVSS liPo Voltage Sensor - if (#v > 1) then - wgt.isDataAvailable = false - local txt = "FLVSS liPo Voltage Sensor, not supported" - log(txt) - return - end - elseif v ~= nil and v >= 1 then - -- single cell or VFAS lipo sensor - if fieldinfo then - log(wgt.options.source_name .. ", value: " .. fieldinfo.name .. "=" .. v) - else - log("only one cell using Ax lipo sensor") - end - else - -- no telemetry available - wgt.isDataAvailable = false - if fieldinfo then - log("no telemetry data: " .. fieldinfo['name'] .. "=??") - else - log("no telemetry data") - end - return - end - - if (wgt.cell_detected == true) then - log("permanent cellCount: " .. wgt.cellCount) - else - local newCellCount = calcCellCount(wgt, v) - if (wgt.tools.periodicHasPassed(wgt.periodic1, false)) then - wgt.cell_detected = true - wgt.cellCount = newCellCount - else - local duration_passed = wgt.tools.periodicGetElapsedTime(wgt.periodic1, false) - log(string.format("detecting cells: %ss, %d/%d msec", newCellCount, duration_passed, wgt.tools.getDurationMili(wgt.periodic1))) - - -- this is necessary for simu where cell-count can change - if newCellCount ~= wgt.cellCount then - wgt.vMin = 99 - wgt.vMax = 0 - end - wgt.cellCount = newCellCount - end - end - - -- calc highest of all cells - if v > wgt.vMax then - wgt.vMax = v - end - - wgt.vTotalLive = v - wgt.vCellLive = wgt.vTotalLive / wgt.cellCount - wgt.vPercent = getCellPercent(wgt, wgt.vCellLive) - - -- log("wgt.vCellLive: ".. wgt.vCellLive) - -- log("wgt.vPercent: ".. wgt.vPercent) - - -- mainValue - if wgt.options.Show_Total_Voltage == 0 then - wgt.mainValue = wgt.vCellLive - wgt.secondaryValue = wgt.vTotalLive - elseif wgt.options.Show_Total_Voltage == 1 then - wgt.mainValue = wgt.vTotalLive - wgt.secondaryValue = wgt.vCellLive - else - wgt.mainValue = "-1" - wgt.secondaryValue = "-2" - end - - --- calc lowest main voltage - if wgt.mainValue < wgt.vMin and wgt.mainValue > 1 then - -- min 1v to consider a valid reading - wgt.vMin = wgt.mainValue - end - - wgt.isDataAvailable = true - if wgt.cell_detected == true then - wgt.tools.periodicStart(wgt.periodic1, CELL_DETECTION_TIME * 1000) - end - - -end - - --- color for battery --- This function returns green at 100%, red bellow 30% and graduate in between -local function getPercentColor(percent) - if percent < 30 then - return lcd.RGB(0xff, 0, 0) - else - g = math.floor(0xdf * percent / 100) - r = 0xdf - g - return lcd.RGB(r, g, 0) - end -end - --- color for cell --- This function returns green at gvalue, red at rvalue and graduate in between -local function getRangeColor(value, green_value, red_value) - local range = math.abs(green_value - red_value) - if range == 0 then - return lcd.RGB(0, 0xdf, 0) - end - if value == nil then - return lcd.RGB(0, 0xdf, 0) - end - - if green_value > red_value then - if value > green_value then - return lcd.RGB(0, 0xdf, 0) - end - if value < red_value then - return lcd.RGB(0xdf, 0, 0) - end - g = math.floor(0xdf * (value - red_value) / range) - r = 0xdf - g - return lcd.RGB(r, g, 0) - else - if value > green_value then - return lcd.RGB(0, 0xdf, 0) - end - if value < red_value then - return lcd.RGB(0xdf, 0, 0) - end - r = math.floor(0xdf * (value - green_value) / range) - g = 0xdf - r - return lcd.RGB(r, g, 0) - end -end - -local function drawBattery(wgt, myBatt) - -- fill batt - local fill_color = getPercentColor(wgt.vPercent) - lcd.drawFilledRectangle(wgt.zone.x + myBatt.x, wgt.zone.y + myBatt.y + myBatt.h - math.floor(wgt.vPercent / 100 * (myBatt.h - myBatt.cath_h)), myBatt.w, math.floor(wgt.vPercent / 100 * (myBatt.h - myBatt.cath_h)), fill_color) - - -- draw battery segments - lcd.drawRectangle(wgt.zone.x + myBatt.x, wgt.zone.y + myBatt.y + myBatt.cath_h, myBatt.w, myBatt.h - myBatt.cath_h, WHITE, 2) - for i = 1, myBatt.h - myBatt.cath_h - myBatt.segments_h, myBatt.segments_h do - lcd.drawRectangle(wgt.zone.x + myBatt.x, wgt.zone.y + myBatt.y + myBatt.cath_h + i, myBatt.w, myBatt.segments_h, WHITE, 1) - end - - -- draw plus terminal - local tw = 4 - local th = 4 - lcd.drawFilledRectangle(wgt.zone.x + myBatt.x + myBatt.w / 2 - myBatt.cath_w / 2 + tw / 2, wgt.zone.y + myBatt.y, myBatt.cath_w - tw, myBatt.cath_h, WHITE) - lcd.drawFilledRectangle(wgt.zone.x + myBatt.x + myBatt.w / 2 - myBatt.cath_w / 2, wgt.zone.y + myBatt.y + th, myBatt.cath_w, myBatt.cath_h - th, WHITE) - --lcd.drawText(wgt.zone.x + myBatt.x + 20, wgt.zone.y + myBatt.y + 5, string.format("%2.0f%%", wgt.vPercent), LEFT + MIDSIZE + wgt.text_color) - --lcd.drawText(wgt.zone.x + myBatt.x + 20, wgt.zone.y + myBatt.y + 5, string.format("%2.1fV", wgt.mainValue), LEFT + MIDSIZE + wgt.text_color) -end - ---- Zone size: 70x39 top bar -local function refreshZoneTiny(wgt) - local myString = string.format("%2.2fV", wgt.mainValue) - - -- write text - lcd.drawText(wgt.zone.x + wgt.zone.w - 25, wgt.zone.y + 5, wgt.vPercent .. "%", RIGHT + SMLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(wgt.zone.x + wgt.zone.w - 25, wgt.zone.y + 20, myString, RIGHT + SMLSIZE + wgt.text_color + wgt.no_telem_blink) - - -- draw battery - local batt_color = wgt.options.Color - lcd.drawRectangle(wgt.zone.x + 50, wgt.zone.y + 9, 16, 25, batt_color, 2) - lcd.drawFilledRectangle(wgt.zone.x + 50 + 4, wgt.zone.y + 7, 6, 3, batt_color) - local rect_h = math.floor(25 * wgt.vPercent / 100) - lcd.drawFilledRectangle(wgt.zone.x + 50, wgt.zone.y + 9 + 25 - rect_h, 16, rect_h, batt_color + wgt.no_telem_blink) -end - ---- Zone size: 160x32 1/8th -local function refreshZoneSmall(wgt) - local myBatt = { ["x"] = 5, ["y"] = 5, ["w"] = wgt.zone.w - 10, ["h"] = wgt.zone.h - 9, ["segments_w"] = 25, ["color"] = WHITE, ["cath_w"] = 6, ["cath_h"] = 20 } - - -- fill battery - local fill_color = getPercentColor(wgt.vPercent) - lcd.drawGauge(myBatt.x, myBatt.y, myBatt.w, myBatt.h, wgt.vPercent, 100, fill_color) - - -- draw battery - lcd.drawRectangle(myBatt.x, myBatt.y, myBatt.w, myBatt.h, WHITE, 2) - - -- write text - local topLine = string.format(" %2.2f V %2.0f %%", wgt.mainValue, wgt.vPercent) - lcd.drawText(myBatt.x + 15, myBatt.y + 1, topLine, MIDSIZE + wgt.text_color + wgt.no_telem_blink) -end - ---- Zone size: 180x70 1/4th (with sliders/trim) ---- Zone size: 225x98 1/4th (no sliders/trim) -local function refreshZoneMedium(wgt) - local myBatt = { ["x"] = 0, ["y"] = 0, ["w"] = 50, ["h"] = wgt.zone.h, ["segments_w"] = 15, ["color"] = WHITE, ["cath_w"] = 26, ["cath_h"] = 10, ["segments_h"] = 16 } - - -- draw values - lcd.drawText(wgt.zone.x + myBatt.w + 10, wgt.zone.y, string.format("%2.2f V", wgt.mainValue), DBLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(wgt.zone.x + myBatt.w + 12, wgt.zone.y + 30, string.format("%2.0f %%", wgt.vPercent), MIDSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(wgt.zone.x + wgt.zone.w - 5, wgt.zone.y + wgt.zone.h - 55, wgt.options.source_name, RIGHT + SMLSIZE + wgt.text_color + wgt.no_telem_blink) - if wgt.options.Show_Total_Voltage == 0 then - lcd.drawText(wgt.zone.x + wgt.zone.w - 5, wgt.zone.y + wgt.zone.h - 35, string.format("%2.2fV %dS", wgt.secondaryValue, wgt.cellCount), RIGHT + SMLSIZE + wgt.text_color + wgt.no_telem_blink) - else - --lcd.drawText(wgt.zone.x, wgt.zone.y + 40, string.format("%2.2fV", wgt.mainValue), DBLSIZE + wgt.text_color + wgt.no_telem_blink) - end - lcd.drawText(wgt.zone.x + wgt.zone.w - 5, wgt.zone.y + wgt.zone.h - 20, string.format("Min %2.2fV", wgt.vMin), RIGHT + SMLSIZE + wgt.text_color + wgt.no_telem_blink) - - -- more info if 1/4 is high enough (without trim & slider) - if wgt.zone.h > 80 then + local m_log = loadScript("/WIDGETS/" .. app_name .. "/lib_log.lua", "tcd")(app_name, "/WIDGETS/" .. app_name) + local wgt = loadScript("/WIDGETS/" .. app_name .. "/logic.lua")(m_log) + wgt.tools = loadScript("/WIDGETS/" .. app_name .. "/lib_widget_tools.lua", "tcd")(m_log, app_name, useLvgl) + wgt.zone = zone + wgt.options = options + wgt.m_log = m_log + wgt.log = function(fmt, ...) + wgt.m_log.info(fmt, ...) end - drawBattery(wgt, myBatt) -end - ---- Zone size: 192x152 1/2 -local function refreshZoneLarge(wgt) - local myBatt = { ["x"] = 0, ["y"] = 0, ["w"] = 76, ["h"] = wgt.zone.h, ["segments_h"] = 30, ["color"] = WHITE, ["cath_w"] = 30, ["cath_h"] = 10 } - - lcd.drawText(wgt.zone.x + wgt.zone.w, wgt.zone.y + 10, string.format("%2.2f V", wgt.mainValue), RIGHT + DBLSIZE + wgt.text_color) - lcd.drawText(wgt.zone.x + wgt.zone.w, wgt.zone.y + 40, wgt.vPercent .. " %", RIGHT + DBLSIZE + wgt.text_color) - - lcd.drawText(wgt.zone.x + wgt.zone.w, wgt.zone.y + wgt.zone.h - 53, wgt.options.source_name, RIGHT + SMLSIZE + wgt.text_color) - lcd.drawText(wgt.zone.x + wgt.zone.w, wgt.zone.y + wgt.zone.h - 35, string.format("%2.2fV %dS", wgt.secondaryValue, wgt.cellCount), RIGHT + SMLSIZE + wgt.text_color) - lcd.drawText(wgt.zone.x + wgt.zone.w, wgt.zone.y + wgt.zone.h - 20, string.format("min %2.2fV", wgt.vMin), RIGHT + SMLSIZE + wgt.text_color + wgt.no_telem_blink) - - drawBattery(wgt, myBatt) - -end - ---- Zone size: 390x172 1/1 ---- Zone size: 460x252 1/1 (no sliders/trim/topbar) -local function refreshZoneXLarge(wgt) - local x = wgt.zone.x - local w = wgt.zone.w - local y = wgt.zone.y - local h = wgt.zone.h - - local myBatt = { ["x"] = 10, ["y"] = 0, ["w"] = 80, ["h"] = h, ["segments_h"] = 30, ["color"] = WHITE, ["cath_w"] = 30, ["cath_h"] = 10 } - - -- draw right text section - --lcd.drawText(x + w, y + myBatt.y + 0, string.format("%2.2f V %2.0f%%", wgt.mainValue, wgt.vPercent), RIGHT + XXLSIZE + wgt.text_color + wgt.no_telem_blink) - --lcd.drawText(x + w, y + myBatt.y + 0, string.format("%2.2f V", wgt.mainValue), RIGHT + XXLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + 150, y + myBatt.y + 0, string.format("%2.2f V", wgt.mainValue), XXLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + 150, y + myBatt.y + 70, wgt.options.source_name, DBLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + w, y + myBatt.y + 80, string.format("%2.0f%%", wgt.vPercent), RIGHT + DBLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + w, y + h - 60, string.format("%2.2fV %dS", wgt.secondaryValue, wgt.cellCount), RIGHT + DBLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + w, y + h - 30, string.format("min %2.2fV", wgt.vMin), RIGHT + DBLSIZE + wgt.text_color + wgt.no_telem_blink) - drawBattery(wgt, myBatt) - return + loadScript("/WIDGETS/" .. app_name .. "/ui_lvgl")(wgt) + return wgt end ---- Zone size: 460x252 - app mode (full screen) -local function refreshAppMode(wgt, event, touchState) - if (touchState and touchState.tapCount == 2) or (event and event == EVT_VIRTUAL_EXIT) then - lcd.exitFullScreen() - end - - local x = 0 - local y = 0 - local w = LCD_W - local h = LCD_H - 20 - - local myBatt = { ["x"] = 10, ["y"] = 10, ["w"] = 90, ["h"] = h, ["segments_h"] = 30, ["color"] = WHITE, ["cath_w"] = 30, ["cath_h"] = 10 } - - if (event ~= nil) then - log("event: " .. event) - end +-- This function allow updates when you change widgets settings +local function update(wgt, options) + wgt.options = options - -- draw right text section - --lcd.drawText(x + w - 20, y + myBatt.y + 0, string.format("%2.2f V %2.0f%%", wgt.mainValue, wgt.vPercent), RIGHT + XXLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + 180, y + 0, wgt.options.source_name, DBLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + 180, y + 30, string.format("%2.2f V", wgt.mainValue), XXLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + 180, y + 90, string.format("%2.0f %%", wgt.vPercent), XXLSIZE + wgt.text_color + wgt.no_telem_blink) + wgt.batt_height = wgt.zone.h --??? + wgt.batt_width = wgt.zone.w + -- wgt.log("batt_11 width: " .. wgt.batt_width .. ", height: " .. wgt.batt_height) - lcd.drawText(x + w - 20, y + h - 90, string.format("%2.2fV", wgt.secondaryValue), RIGHT + DBLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + w - 20, y + h - 60, string.format("%dS", wgt.cellCount), RIGHT + DBLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + w - 20, y + h - 30, string.format("min %2.2fV", wgt.vMin), RIGHT + DBLSIZE + wgt.text_color + wgt.no_telem_blink) - drawBattery(wgt, myBatt) - return + wgt.update_logic(wgt, options) + wgt.update_ui() end --- This function allow recording of lowest cells when widget is in background local function background(wgt) - if (wgt == nil) then return end - - wgt.tools.detectResetEvent(wgt, onTelemetryResetEvent) + wgt.background() +end - calculateBatteryData(wgt) +local function getDxByStick(stk) + local v = getValue(stk) + if math.abs(v) < 150 then return 0 end + local d = math.ceil(v / 90) + return d end local function refresh(wgt, event, touchState) + local is_need_update = false - if (wgt == nil) then return end - if type(wgt) ~= "table" then return end - if (wgt.options == nil) then return end - if (wgt.zone == nil) then return end - if (wgt.options.Show_Total_Voltage == nil) then return end + local dw = getDxByStick("ail") + wgt.batt_width = wgt.batt_width + dw + wgt.batt_width = math.max(10, math.min(480, wgt.batt_width)) + is_need_update = is_need_update or (dw ~= 0) - background(wgt) + local dh = getDxByStick("ele") + wgt.batt_height = wgt.batt_height - dh + wgt.batt_height = math.max(10, math.min(272, wgt.batt_height)) + is_need_update = is_need_update or (dh ~= 0) - if wgt.options.Lithium_Ion == 1 and wgt.options.Lithium_HV == 1 then - lcd.drawText(0,0, "Invalid settings", MIDSIZE + BLINK) - lcd.drawText(0,30, "can not set LI-ION & LIHV", 0 +BLINK) - return - end - - - if wgt.isDataAvailable then - wgt.no_telem_blink = 0 - wgt.text_color = wgt.options.Color - else - wgt.no_telem_blink = INVERS + BLINK - wgt.text_color = GREY - end - - if (event ~= nil) then - refreshAppMode(wgt, event, touchState) - return - end - - if wgt.zone.w > 380 and wgt.zone.h > 165 then refreshZoneXLarge(wgt) - elseif wgt.zone.w > 180 and wgt.zone.h > 145 then refreshZoneLarge(wgt) - elseif wgt.zone.w > 170 and wgt.zone.h > 65 then refreshZoneMedium(wgt) - elseif wgt.zone.w > 150 and wgt.zone.h > 28 then refreshZoneSmall(wgt) - elseif wgt.zone.w > 65 and wgt.zone.h > 35 then refreshZoneTiny(wgt) + if (is_need_update == true) then + wgt.zone.w = wgt.batt_width + wgt.zone.h = wgt.batt_height + wgt.update_ui() end + wgt.refresh(event, touchState) end -return { name = app_name, options = _options, create = create, update = update, background = background, refresh = refresh } +return { + name = app_name, + options = _options, + create = create, + update = update, + background = background, + refresh = refresh, + translate=translate, + useLvgl = true +} diff --git a/sdcard/c480x272/WIDGETS/BattAnalog/ui_lvgl.lua b/sdcard/c480x272/WIDGETS/BattAnalog/ui_lvgl.lua new file mode 100644 index 00000000..6b95bc6a --- /dev/null +++ b/sdcard/c480x272/WIDGETS/BattAnalog/ui_lvgl.lua @@ -0,0 +1,306 @@ +local wgt = ... + +LVGL_DEF = { + type = { + LABEL = "label", + RECTANGLE = "rectangle", + CIRCLE = "circle", + ARC = "arc", + IMAGE = "image", + QRCODE = "qrcode", + }, + type_script = { + BUTTON = "button", + TOGGLE = "toggle", + TEXTEDIT = "textEdit", + NUMBEREDIT = "numberEdit", + CHOICE = "choice", + SLIDER = "slider", + PAGE = "page", + } + -- "meter" +} + +-- better font names +local FONT_38 = XXLSIZE -- 38px +local FONT_16 = DBLSIZE -- 16px +local FONT_12 = MIDSIZE -- 12px +local FONT_8 = 0 -- Default 8px +local FONT_6 = SMLSIZE -- 6px + +local space = 10 + +local function log(fmt, ...) + wgt.log(fmt, ...) +end + +local function getMainValue() + return string.format("%2.2fV", wgt.mainValue) +end + +local function getVPercent() + return string.format("%2.0f%%", wgt.vPercent) +end + +local function getSecondaryValue() + -- return string.format("%2.2fV %dS", wgt.secondaryValue, wgt.cellCount) + return string.format("%2.2fV", wgt.secondaryValue) +end + +local function getCellCount() + return string.format("%dS", wgt.cellCount) +end + +local function getSecondaryValueCell() + return string.format("%2.2fV %dS", wgt.secondaryValue, wgt.cellCount) +end + +local function getVMin() + return string.format("min %2.2fV", wgt.vMin) +end + +local function getTxtColor() + return wgt.text_color +end + +local function getVPercentColor() + return (wgt.vPercent < 30) and RED or wgt.text_color +end + +local function getFillColor() + return wgt.getPercentColor(wgt.vPercent) +end + +local function calcBattSize() + local x = wgt.zone.x + space + local y = wgt.zone.y + space + local w = 0 + local h = wgt.zone.h - 2*space + if (h > 110) then + w = math.floor(h * 0.50) + elseif (h > 80) then + w = math.floor(h * 0.60) + else + w = math.floor(h * 0.80) + end + return x, y, w, h +end + +local function layoutBatt() + local bx, by , bw, bh = calcBattSize() + + -- terminal size + local th1 = math.floor(bh*0.04) + local th2 = math.floor(bh*0.05) + local th = th1+th2 + local tw1 = bw / 2 * 0.8 + local tw2 = bw / 2 + local tx1 = bx + (bw - tw1) / 2 + local tx2 = bx + (bw - tw2) / 2 + + -- box size + local bbx = bx + local bby = by + th + local bbw = bw + local bbh = bh - th + + local shd = (bh>120) and 3 or ((bh>120) and 2 or 1) -- shaddow + local isNeedShaddow = (bh>80) and true or false + + local lytBatt = { + -- plus terminal + {type=LVGL_DEF.type.RECTANGLE, x=tx1, y=bby-th , w=tw1, h=th1*2, color=WHITE, filled=true, rounded=5}, + {type=LVGL_DEF.type.RECTANGLE, x=tx2, y=bby-th2 , w=tw2, h=th2 , color=WHITE, filled=true, rounded=5}, + {type=LVGL_DEF.type.RECTANGLE, x=tx2, y=bby-th2/2, w=tw2, h=th2/2, color=WHITE, filled=true, rounded=0}, + + -- fill batt + {type=LVGL_DEF.type.RECTANGLE, x=bx, y=bby, w=bbw, h=0, filled=true, color=getFillColor, rounded=bw*0.1, + size=(function() return bbw, math.floor(wgt.vPercent / 100 * bbh) end), + pos=(function() return bx, bby + bbh - math.floor(wgt.vPercent / 100 * bbh) end)}, + + -- battery outline shaddow + -- {type=LVGL_DEF.type.RECTANGLE, x=bx+2, y=bby+3, w=bbw, h=bbh, thickness=3,color=GREY, rounded=bw*0.1}, + + -- -- battery segments shaddow + -- {type=LVGL_DEF.type.RECTANGLE, x=bx+1+2, y=bby + (1 * bbh / 5)+shd, w=bbw-2, h=2, filled=true, color=GREY}, + -- {type=LVGL_DEF.type.RECTANGLE, x=bx+1+2, y=bby + (2 * bbh / 5)+shd, w=bbw-2, h=2, filled=true, color=GREY}, + -- {type=LVGL_DEF.type.RECTANGLE, x=bx+1+2, y=bby + (3 * bbh / 5)+shd, w=bbw-2, h=2, filled=true, color=GREY}, + -- {type=LVGL_DEF.type.RECTANGLE, x=bx+1+2, y=bby + (4 * bbh / 5)+shd, w=bbw-2, h=2, filled=true, color=GREY}, + + -- battery segments shaddow + {type=LVGL_DEF.type.RECTANGLE, x=bx+1, y=bby + (1 * bbh / 5) , w=bbw-2, h=1, thickness=1, color=WHITE}, + {type=LVGL_DEF.type.RECTANGLE, x=bx+1, y=bby + (2 * bbh / 5) , w=bbw-2, h=1, thickness=1, color=WHITE}, + {type=LVGL_DEF.type.RECTANGLE, x=bx+1, y=bby + (3 * bbh / 5) , w=bbw-2, h=1, thickness=1, color=WHITE}, + {type=LVGL_DEF.type.RECTANGLE, x=bx+1, y=bby + (4 * bbh / 5) , w=bbw-2, h=1, thickness=1, color=WHITE}, + + -- battery outline + {type=LVGL_DEF.type.RECTANGLE, x=bx, y=bby, w=bbw, h=bbh, thickness=2,color=WHITE, rounded=bw*0.1}, + + -- {type=LVGL_DEF.type.RECTANGLE, x=bx, y=by, w=bw, h=bh,color=BLUE}, + } + + if isNeedShaddow then + -- battery outline shaddow + lytBatt[#lytBatt+1] = {type=LVGL_DEF.type.RECTANGLE, x=bx+2, y=bby+3, w=bbw, h=bbh, thickness=3,color=GREY, rounded=bw*0.1} + -- battery segments shaddow + lytBatt[#lytBatt+1] = {type=LVGL_DEF.type.RECTANGLE, x=bx+1+2, y=bby + (1 * bbh / 5)+shd, w=bbw-2, h=2, filled=true, color=GREY} + lytBatt[#lytBatt+1] = {type=LVGL_DEF.type.RECTANGLE, x=bx+1+2, y=bby + (2 * bbh / 5)+shd, w=bbw-2, h=2, filled=true, color=GREY} + lytBatt[#lytBatt+1] = {type=LVGL_DEF.type.RECTANGLE, x=bx+1+2, y=bby + (3 * bbh / 5)+shd, w=bbw-2, h=2, filled=true, color=GREY} + lytBatt[#lytBatt+1] = {type=LVGL_DEF.type.RECTANGLE, x=bx+1+2, y=bby + (4 * bbh / 5)+shd, w=bbw-2, h=2, filled=true, color=GREY} + end + + lvgl.build(lytBatt) + + local batSize = { + x = bx, + y = by, + w = bw, + h = bh, + xw = bx + bw, + yh = by + bh, + } + return batSize +end + +--- Zone size: 70x39 top bar +local function layoutZoneTopbar() + local bx = wgt.zone.w - 20 + local by = 2 + local bw = 18 + local bh = wgt.zone.h - 4 + + local lytTxt = { + -- battery values + {type=LVGL_DEF.type.LABEL, x=0, y=20, w=bx - 3, font=FONT_6+RIGHT, text=getMainValue, + -- color=(function() return (wgt.vPercent < 30) and RED or wgt.text_color end) + color=getVPercentColor + }, + {type=LVGL_DEF.type.LABEL, x=0, y=5, w=bx - 3, font=FONT_6+RIGHT, text=getVPercent, color=getVPercentColor}, + + -- plus terminal + {type=LVGL_DEF.type.RECTANGLE, x=bx+4, y=by-6, w=bw-8, h=6, filled=true, color=getTxtColor}, + + -- fill batt + {type=LVGL_DEF.type.RECTANGLE, x=bx, y=by, w=bw, h=0, filled=true, color=getFillColor, + size=(function() return bw, math.floor(wgt.vPercent / 100 * (bh)) end), + pos=(function() return bx, by + bh - math.floor(wgt.vPercent / 100 * (bh)) end)}, + + -- battery outline + {type=LVGL_DEF.type.RECTANGLE, x=bx, y=by, w=bw, h=bh, thickness=2, color=getTxtColor}, + } + + lvgl.build(lytTxt) +end + +local function layoutTextZoneNormal(batSize) + local next_y = space + local left_w = wgt.zone.w-(batSize.w +10) + local left_h = wgt.zone.h + + local txtSizes = { + vMain = {x=nil,y=nil, font=nil}, + percent = {}, + source = {}, + vSec = {}, + cellCount = {}, + vMin = {}, + } + + local fSizeMainV, w, h, v_offset = wgt.tools.getFontSize(wgt, "99.99 V", left_w, left_h, FONT_38) + txtSizes.vMain = {x=batSize.xw +10, y=next_y +v_offset, font=fSizeMainV} + + next_y = next_y + h + 10 + left_h = wgt.zone.h - next_y + + local fSizePercent, w, h, v_offset = wgt.tools.getFontSize(wgt, "100 %", left_w, left_h, fSizeMainV) + txtSizes.percent = {x=batSize.xw +12, y=next_y +v_offset, font=fSizePercent} + next_y = next_y + h + 10 + left_h = wgt.zone.h - next_y + + + local max_w = 0 + local sec_x = LCD_W + local sec_font = FONT_16 + local sec_dh = 0 + local line_space = 5 + + sec_font = wgt.tools.getFontSize(wgt, "AAA", left_w - batSize.w, left_h/3, FONT_16) + + + -- source + local ts_w, ts_h, v_offset = wgt.tools.lcdSizeTextFixed(wgt.source_name, sec_font) + sec_dh = ts_h + 5 + line_space = ts_h * 0 + sec_x = math.min(sec_x, wgt.zone.w -ts_w -space) + txtSizes.source = {y=wgt.zone.h +v_offset -space +line_space -sec_dh*3, visible=(function() return wgt.options.isTotalVoltage == 0 end), visible = true} + + + -- vSec + cell count + local ts_w, ts_h, v_offset = wgt.tools.lcdSizeTextFixed("99.99 V 12s", sec_font) + sec_x = math.min(sec_x, wgt.zone.w -ts_w -space) + txtSizes.vSec = {y=wgt.zone.h +v_offset -space +line_space -sec_dh*2, visible=(function() return wgt.options.isTotalVoltage == 0 end)} + + -- vMin + local ts_w, ts_h, v_offset = wgt.tools.lcdSizeTextFixed(getVMin(), sec_font) + sec_x = math.min(sec_x, wgt.zone.w -ts_w -space) + txtSizes.vMin = {y=wgt.zone.h +v_offset -space +line_space -sec_dh*1, visible = false} + + + local lytTxt = { + -- main value + {type=LVGL_DEF.type.LABEL, x=txtSizes.vMain.x, y=txtSizes.vMain.y, font=txtSizes.vMain.font, text=getMainValue, color=getTxtColor}, + {type=LVGL_DEF.type.LABEL, x=txtSizes.percent.x, y=txtSizes.percent.y, font=txtSizes.percent.font, text=getVPercent, color=getTxtColor}, + -- -- source name + -- {type=LVGL_DEF.type.LABEL, x=sec_x, y=txtSizes.source.y, font=sec_font, text=wgt.source_name, color=getTxtColor, visible=txtSizes.source.visible}, + {type=LVGL_DEF.type.LABEL, x=sec_x, y=txtSizes.source.y, font=sec_font, text=wgt.source_name, color=getTxtColor}, -- , visible=txtSizes.vSec.visible + -- secondary value & cells + {type=LVGL_DEF.type.LABEL, x=sec_x, y=txtSizes.vSec.y, font=sec_font, text=getSecondaryValueCell, color=getTxtColor}, -- , visible=txtSizes.vSec.visible + -- min voltage + {type=LVGL_DEF.type.LABEL, x=sec_x, y=txtSizes.vMin.y, font=sec_font, text=getVMin, color=getTxtColor}, -- , visible="false" + } + + lvgl.build(lytTxt) +end + +local function layoutZoneNormal() + local batSize = layoutBatt() + layoutTextZoneNormal(batSize) +end + +function wgt.refresh(event, touchState) + wgt.tools.detectResetEvent(wgt, wgt.onTelemetryResetEvent) + wgt.calculateBatteryData() + + if wgt.isDataAvailable then + wgt.text_color = wgt.options.color + else + wgt.text_color = GREY + end +end + +function wgt.update_ui() + lvgl.clear() + + -- local text = "TEST" + -- local font_size = FONT_38 + -- local ts_w, ts_h, v_offset = wgt.tools.lcdSizeTextFixed(text, font_size) + -- local myString = string.format("%sx%s (%s,%s)", wgt.zone.w, wgt.zone.h, wgt.zone.x, wgt.zone.y) + -- lytZone = { + -- {type=LVGL_DEF.type.RECTANGLE, x=0, y=0, w=ts_w, h=ts_h, color=RED, filled=false}, + -- {type=LVGL_DEF.type.LABEL, text="TEST", x=0, y=0 + v_offset, font=font_size, color=BLACK}, + -- show spaces + -- {type=LVGL_DEF.type.RECTANGLE, x=wgt.zone.x, y=wgt.zone.y, w=wgt.zone.w, h=wgt.zone.h, color=BLUE, filled=false, thickness=space}, + -- show zone size + -- {type=LVGL_DEF.type.LABEL, text=myString, x=wgt.zone.x+wgt.zone.w/2, y=wgt.zone.y+wgt.zone.h/2, font=FONT_6, color=BLACK}, + -- } + -- lvgl.build(lytZone) + + if wgt.zone.w < 75 and wgt.zone.h < 45 then + layoutZoneTopbar() + else + layoutZoneNormal() + end + +end + +return wgt diff --git a/sdcard/c480x272/WIDGETS/BattCheck/lib_sensors.lua b/sdcard/c480x272/WIDGETS/BattCheck/lib_sensors.lua new file mode 100644 index 00000000..9131dcde --- /dev/null +++ b/sdcard/c480x272/WIDGETS/BattCheck/lib_sensors.lua @@ -0,0 +1,103 @@ +local m_log, app_name = ... + +local M = {} +M.m_log = m_log +M.app_name = app_name + +--function cache +local math_floor = math.floor +local math_fmod = math.fmod +local string_gmatch = string.gmatch +local string_gsub = string.gsub +local string_len = string.len +local string_sub = string.sub +local string_char = string.char +local string_byte = string.byte + + +--------------------------------------------------------------------------------------------------- +local function log(fmt, ...) + m_log.info(fmt, ...) +end +--------------------------------------------------------------------------------------------------- + +function M.split(text) + local cnt = 0 + local result = {} + for val in string_gmatch(string_gsub(text, ",,", ", ,"), "([^,]+),?") do + cnt = cnt + 1 + result[cnt] = val + end + --m_log.info("split: #col: %d (%s)", cnt, text) + --m_log.info("split: #col: %d (1-%s, 2-%s)", cnt, result[1], result[2]) + return result, cnt +end + +function M.split_pipe(text) + -- m_log.info("split_pipe(%s)", text) + local cnt = 0 + local result = {} + for val in string.gmatch(string.gsub(text, "||", "| |"), "([^|]+)|?") do + cnt = cnt + 1 + result[cnt] = val + end + m_log.info("split_pipe: #col: %d (%s)", cnt, text) + m_log.info("split_pipe: #col: %d [1-%s, 2-%s, ...]", cnt, result[1], result[2]) + return result, cnt +end + +-- remove trailing and leading whitespace from string. +-- http://en.wikipedia.org/wiki/Trim_(programming) +function M.trim(s) + if s == nil then + return nil + end + return (string.gsub(s, "^%s*(.-)%s*$", "%1")) +end + +function M.trim_safe(s) + if s == nil then + return "" + end + return (string.gsub(s, "^%s*(.-)%s*$", "%1")) + --string.gsub(text, ",,", ", ,") +end + +function M.findSourceId(sourceNameList) + local interesting_sources = {} + for i = 200, 400 do + local name = getSourceName(i) + if name ~= nil then + -- workaround for bug in getFiledInfo() -- ???? why? + if string.byte(string.sub(name, 1, 1)) > 127 then name = string.sub(name, 2, -1) end + if string.byte(string.sub(name, 1, 1)) > 127 then name = string.sub(name, 2, -1) end + + for _, sourceName in ipairs(sourceNameList) do + -- print(string.format("init_compare_source: [%s(%d)][%s] (is =? %s)", name, i, sourceName, (name == sourceName))) + if (string.lower(name) == string.lower(sourceName)) then + print(string.format("init_compare_source (collecting): [%s(%d)] == [%s]", name, i, sourceName)) + interesting_sources[#interesting_sources + 1] = {i,name} + end + end + end + end + + -- find the source with highest priority + for _, sourceName in ipairs(sourceNameList) do + for _, source in ipairs(interesting_sources) do + local idx = source[1] + local name = source[2] + -- print(string.format("init_compare_source: is_needed? [%s(%d)]", name, idx)) + if (string.lower(name) == string.lower(sourceName)) then + print(string.format("init_compare_source: we have: %s", sourceName)) + print(string.format("init_compare_source (found): [%s(%d)] == [%s]", name, idx, sourceName)) + return idx + end + end + print(string.format("init_compare_source: we do not have: %s", sourceName)) + end + return 1 +end + + +return M diff --git a/sdcard/c480x272/WIDGETS/BattCheck/main.lua b/sdcard/c480x272/WIDGETS/BattCheck/main.lua index c0aeab2a..696405c1 100755 --- a/sdcard/c480x272/WIDGETS/BattCheck/main.lua +++ b/sdcard/c480x272/WIDGETS/BattCheck/main.lua @@ -1,6 +1,6 @@ ---- ######################################################################### ---- # # ----- # Telemetry Widget script for FrSky Horus/RadioMaster TX16s # +---- # Telemetry Widget script for RadioMaster TX16S # ---- # Copyright (C) EdgeTX # -----# # ---- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html # @@ -20,10 +20,13 @@ -- 3djc & Offer Shmuely -- Date: 2022 local app_name = "BattCheck" -local app_ver = "0.8" +local app_ver = "0.9" + +local lib_sensors = loadScript("/WIDGETS/" .. app_name .. "/lib_sensors.lua", "tcd")(m_log,app_name) +local DEFAULT_SOURCE = lib_sensors.findSourceId( {"Cels"}) local _options = { - { "Sensor" , SOURCE, 0 }, -- default to 'Cels' + { "Sensor" , SOURCE, DEFAULT_SOURCE }, -- default to 'Cels' { "Color" , COLOR , YELLOW }, { "Shadow" , BOOL , 0 }, { "LowestCell" , BOOL , 1 }, -- 0=main voltage display shows all-cell-voltage, 1=main voltage display shows lowest-cell