From 70959eebbef7c217da1857aecf6f8caada53328b Mon Sep 17 00:00:00 2001 From: shmuely Date: Tue, 21 Jan 2025 00:49:13 +0200 Subject: [PATCH 1/3] feat(BattAnalog): update 480x320 widget --- .../c480x320/WIDGETS/BattAnalog/lib_log.lua | 115 ++++ .../WIDGETS/BattAnalog/lib_sensors.lua | 103 ++++ .../WIDGETS/BattAnalog/lib_widget_tools.lua | 470 +++++++++++++++ sdcard/c480x320/WIDGETS/BattAnalog/logic.lua | 356 +++++++++++ sdcard/c480x320/WIDGETS/BattAnalog/main.lua | 562 ++++-------------- .../c480x320/WIDGETS/BattAnalog/ui_lvgl.lua | 306 ++++++++++ 6 files changed, 1451 insertions(+), 461 deletions(-) create mode 100644 sdcard/c480x320/WIDGETS/BattAnalog/lib_log.lua create mode 100644 sdcard/c480x320/WIDGETS/BattAnalog/lib_sensors.lua create mode 100644 sdcard/c480x320/WIDGETS/BattAnalog/lib_widget_tools.lua create mode 100644 sdcard/c480x320/WIDGETS/BattAnalog/logic.lua create mode 100644 sdcard/c480x320/WIDGETS/BattAnalog/ui_lvgl.lua diff --git a/sdcard/c480x320/WIDGETS/BattAnalog/lib_log.lua b/sdcard/c480x320/WIDGETS/BattAnalog/lib_log.lua new file mode 100644 index 00000000..c23e6e8a --- /dev/null +++ b/sdcard/c480x320/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/c480x320/WIDGETS/BattAnalog/lib_sensors.lua b/sdcard/c480x320/WIDGETS/BattAnalog/lib_sensors.lua new file mode 100644 index 00000000..9131dcde --- /dev/null +++ b/sdcard/c480x320/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/c480x320/WIDGETS/BattAnalog/lib_widget_tools.lua b/sdcard/c480x320/WIDGETS/BattAnalog/lib_widget_tools.lua new file mode 100644 index 00000000..03b07639 --- /dev/null +++ b/sdcard/c480x320/WIDGETS/BattAnalog/lib_widget_tools.lua @@ -0,0 +1,470 @@ +local m_log, app_name, useLvgl = ... + +local M = {} +M.m_log = m_log +M.app_name = app_name +M.tele_src_name = nil +M.tele_src_id = nil + +local getTime = getTime +local lcd = lcd + +-- 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 + +-- 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, ...) +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", "°", + "rad", "ml", "fOz", "ml/m", "Hz", "mS", "uS", "km" +} + +function M.unitIdToString(unitId) + if unitId == nil then + return "" + end + -- UNIT_RAW + if unitId == "0" then + return "" + end + + --log("idUnit: " .. unitId) + + if (unitId > 0 and unitId <= #UNIT_ID_TO_STRING) then + local txtUnit = UNIT_ID_TO_STRING[unitId] + --log("txtUnit: " .. txtUnit) + return txtUnit + end + + --return "-#-" + return "" +end + +--------------------------------------------------------------------------------------------------- + +function M.periodicInit() + local t = { + startTime = -1, + durationMili = -1 + } + return t +end + +function M.periodicStart(t, durationMili) + t.startTime = getTime(); + t.durationMili = durationMili; +end + +function M.periodicHasPassed(t, show_log) + -- not started yet + if (t.durationMili <= 0) then + return false; + end + + local elapsed = getTime() - t.startTime; + --log('elapsed: %d (t.durationMili: %d)', elapsed, t.durationMili) + if show_log == true then + log('elapsed: %0.1f/%0.1f sec', elapsed/100, t.durationMili/1000) + end + local elapsedMili = elapsed * 10; + if (elapsedMili < t.durationMili) then + return false; + end + return true; +end + +function M.periodicGetElapsedTime(t, show_log) + local elapsed = getTime() - t.startTime; + local elapsedMili = elapsed * 10; + if show_log == true then + log('elapsed: %0.1f/%0.1f sec', elapsed/100, t.durationMili/1000) + end + return elapsedMili; +end + +function M.periodicReset(t) + t.startTime = getTime(); + --log("periodicReset()"); + M.periodicGetElapsedTime(t) +end + +function M.getDurationMili(t) + return t.durationMili +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") + local tele_src = getFieldInfo("RSSI") + if not tele_src then tele_src = getFieldInfo("1RSS") end + if not tele_src then tele_src = getFieldInfo("2RSS") end + if not tele_src then tele_src = getFieldInfo("RQly") end + if not tele_src then tele_src = getFieldInfo("VFR%") end + if not tele_src then tele_src = getFieldInfo("TRSS") end + if not tele_src then tele_src = getFieldInfo("RxBt") end + if not tele_src then tele_src = getFieldInfo("A1") end + + if tele_src == nil then + --log("no telemetry sensor found") + M.tele_src_id = nil + M.tele_src_name = "---" + return false + else + --log("telemetry sensor found: " .. tele_src.name) + M.tele_src_id = tele_src.id + M.tele_src_name = tele_src.name + end + end + + if M.tele_src_id == nil then + return false + end + + local rx_val = getValue(M.tele_src_id) + if rx_val ~= 0 then + return true + end + return false +end + +--------------------------------------------------------------------------------------------------- + +-- workaround to detect telemetry-reset event, until a proper implementation on the lua interface will be created +-- this workaround assume that: +-- RSSI- is always going down +-- RSSI- is reset on the C++ side when a telemetry-reset is pressed by user +-- widget is calling this func on each refresh/background +-- on event detection, the function onTelemetryResetEvent() will be trigger +-- +function M.detectResetEvent(wgt, callback_onTelemetryResetEvent) + local currMinRSSI = getValue('RSSI-') + if (currMinRSSI == nil) then + log("telemetry reset event: can not be calculated") + return + end + if (currMinRSSI == wgt.telemResetLowestMinRSSI) then + --log("telemetry reset event: not found") + return + end + + if (currMinRSSI < wgt.telemResetLowestMinRSSI) then + -- rssi just got lower, record it + wgt.telemResetLowestMinRSSI = currMinRSSI + --log("telemetry reset event: not found") + return + end + + -- reset telemetry detected + wgt.telemResetLowestMinRSSI = 101 + log("telemetry reset event detected") + + -- notify event + callback_onTelemetryResetEvent(wgt) +end + +--------------------------------------------------------------------------------------------------- + +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("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 s1 + end + end + + 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.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 + 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, #M.FONT_LIST do + if M.FONT_LIST[i] == orgFontSize then + local newIndex = i + delta + newIndex = math.min(newIndex, #M.FONT_LIST) + newIndex = math.max(newIndex, 1) + 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 + 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 = -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 + + 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 + +------------------------------------------------------------------------------------------------------ +function M.drawText(x, y, text, font_size, text_color, bg_color) + local ts_w, ts_h, v_offset = M.lcdSizeTextFixed(text, font_size) + lcd.drawRectangle(x, y, ts_w, ts_h, BLUE) + lcd.drawText(x, y + v_offset, text, font_size + text_color) + return ts_w, ts_h, v_offset +end + +function M.drawBadgedText(txt, txtX, txtY, font_size, text_color, bg_color) + local ts_w, ts_h, v_offset = M.lcdSizeTextFixed(txt, font_size) + local v_space = 2 + local bdg_h = v_space + ts_h + v_space + local r = bdg_h / 2 + lcd.drawFilledCircle(txtX , txtY + r, r, bg_color) + lcd.drawFilledCircle(txtX + ts_w , txtY + r, r, bg_color) + lcd.drawFilledRectangle(txtX, txtY , ts_w, bdg_h, bg_color) + + lcd.drawText(txtX, txtY + v_offset + v_space, txt, font_size + text_color) + + --lcd.drawRectangle(txtX, txtY , ts_w, bdg_h, RED) -- dbg +end + +function M.drawBadgedTextCenter(txt, txtX, txtY, font_size, text_color, bg_color) + local ts_w, ts_h, v_offset = M.lcdSizeTextFixed(txt, font_size) + local r = ts_h / 2 + local x = txtX - ts_w/2 + local y = txtY - ts_h/2 + lcd.drawFilledCircle(x + r * 0.3, y + r, r, bg_color) + lcd.drawFilledCircle(x - r * 0.3 + ts_w , y + r, r, bg_color) + lcd.drawFilledRectangle(x, y, ts_w, ts_h, bg_color) + + lcd.drawText(x, y + v_offset, txt, font_size + text_color) + + -- dbg + --lcd.drawRectangle(x, y , ts_w, ts_h, RED) -- dbg + --lcd.drawLine(txtX-30, txtY, txtX+30, txtY, SOLID, RED) -- dbg + --lcd.drawLine(txtX, txtY-20, txtX, txtY+20, SOLID, RED) -- dbg +end + +------------------------------------------------------------------------------------------------------ +-- usage: +--log("bbb----------------------------------------------------------") +--wgt.tools.heap_dump(wgt, 0, 60) +--log("ccc----------------------------------------------------------") +function M.heap_dump(tbl, indent, max_dept) + local spaces = string.rep(" ", indent) + if max_dept == 0 then + log(spaces .. "---- max dept ----") + return + end + max_dept = max_dept -1 + indent = indent or 0 + + for key, value in pairs(tbl) do + if key ~= "_G" then + if type(value) == "table" then + --log(spaces .. key .. " (table) = {") + log(spaces .. key .. " = {") + M.heap_dump(value, indent + 1, max_dept) + log(spaces .. "}") + else + log(spaces .. key .. " = " .. tostring(value)) + end + end + end +end +------------------------------------------------------------------------------------------------------ + +return M diff --git a/sdcard/c480x320/WIDGETS/BattAnalog/logic.lua b/sdcard/c480x320/WIDGETS/BattAnalog/logic.lua new file mode 100644 index 00000000..c723bfb4 --- /dev/null +++ b/sdcard/c480x320/WIDGETS/BattAnalog/logic.lua @@ -0,0 +1,356 @@ + + +-- 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.30, 8.60, 12.90, 17.20, 21.50, 25.80, 30.10, 34.40, 38.70, 43.00, 47.30, 51.60} +--local voltageRanges_lion={4.20, 8.40, 12.60, 16.80, 21.00, 25.20, 29.40, 33.60, 37.80, 42.00, 46.20, 50.40} +local voltageRanges_lion = {4.30, 8.60, 12.90, 17.20, 21.50, 25.80, 30.10, 34.40, 38.70, 43.00, 47.30, 51.60} +local voltageRanges_hv = {4.45, 8.90, 13.35, 17.80, 22.25, 26.70, 31.15, 35.60, 40.05, 44.50, 48.95, 53.40} + +-------------------------------------------------------------- + +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/c480x320/WIDGETS/BattAnalog/main.lua b/sdcard/c480x320/WIDGETS/BattAnalog/main.lua index 52b96ef7..c5507f25 100644 --- a/sdcard/c480x320/WIDGETS/BattAnalog/main.lua +++ b/sdcard/c480x320/WIDGETS/BattAnalog/main.lua @@ -1,494 +1,134 @@ ----- ######################################################################### ----- # # ----- # Telemetry Widget script for FrSky Horus/RadioMaster TX16s # ----- # Copyright (C) EdgeTX # ------# # ----- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html # ----- # # ----- # This program is free software; you can redistribute it and/or modify # ----- # it under the terms of the GNU General Public License version 2 as # ----- # published by the Free Software Foundation. # ----- # # ----- # 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. # ----- # # ----- ######################################################################### - --- This widget display a graphical representation of a Lipo (not other types) battery level, it will automatically detect the cell amount of the battery. --- it will take a lipo voltage that received as a single value (as opposed to multi cell values send while using FLVSS liPo Voltage Sensor) +--[[ +######################################################################### +# # +# Telemetry Widget script for RadioMaster TX16S # +# Copyright "Offer Shmuely" # +# # +# License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html # +# # +# This program is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License version 2 as # +# published by the Free Software Foundation. # +# # +# 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. # +# # +######################################################################### + + +-- 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: -- * Transmitter Battery +-- * expressLRS pwm receivers (ER6/ER8/SuperP14ch) -- * FrSky VFAS -- * A1/A2 analog voltage -- * mini quad flight controller -- * radio-master 168 -- * OMP m2 heli +]] -- Widget to display the levels of Lipo battery from single analog source --- Offer Shmuely --- Date: 2022 --- ver: 0.3 +-- Author : Offer Shmuely +-- Date: 2021-2024 +local app_name = "BattAnalog" +local app_ver = "1.2" + +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) + {"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 myArrayPercentList = { { 3, 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.71, 15 }, { 3.713, 16 }, { 3.715, 17 }, { 3.72, 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.78, 28 }, { 3.783, 29 }, { 3.786, 30 }, { 3.789, 31 }, { 3.794, 32 }, { 3.797, 33 }, { 3.8, 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.84, 46 }, { 3.843, 47 }, { 3.847, 48 }, { 3.85, 49 }, { 3.854, 50 }, { 3.857, 51 }, { 3.86, 52 }, { 3.863, 53 }, { 3.866, 54 }, { 3.87, 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.12, 92 }, { 4.125, 93 }, { 4.129, 94 }, { 4.135, 95 }, { 4.145, 96 }, { 4.176, 97 }, { 4.179, 98 }, { 4.193, 99 }, { 4.2, 100 } } -local defaultSensor = "RxBt" -- RxBt / A1 / A3/ VFAS /RxBt - --------------------------------------------------------------- -local function log(s) - --print("BattAnalog: " .. s) +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 update(wgt, options) - if (wgt == nil) then - return - end - - wgt.options = options - - -- 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.Show_Total_Voltage = wgt.options.Show_Total_Voltage % 2 -- modulo due to bug that cause the value to be other than 0|1 -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 = 0, - vCellLive = 0, - - mainValue = 0, - secondaryValue = 0 - } - - update(wgt, options) - return wgt -end - --- clear old telemetry data upon reset event -local function onTelemetryResetEvent(wgt) - wgt.telemResetCount = wgt.telemResetCount + 1 - - wgt.vTotalLive = 0 - wgt.vCellLive = 0 - wgt.vMin = 99 - wgt.vMax = 0 - wgt.cellCount = 0 -end - - --- workaround to detect telemetry-reset event, until a proper implementation on the lua interface will be created --- this workaround assume that: --- RSSI- is always going down --- RSSI- is reset on the C++ side when a telemetry-reset is pressed by user --- widget is calling this func on each refresh/background --- on event detection, the function onTelemetryResetEvent() will be trigger --- -local function detectResetEvent(wgt) - - local currMinRSSI = getValue('RSSI-') - if (currMinRSSI == nil) then - return - end - if (currMinRSSI == wgt.telemResetLowestMinRSSI) then - return - end - - if (currMinRSSI < wgt.telemResetLowestMinRSSI) then - -- rssi just got lower, record it - wgt.telemResetLowestMinRSSI = currMinRSSI - return - end - - - -- reset telemetry detected - wgt.telemResetLowestMinRSSI = 101 - - -- notify event - onTelemetryResetEvent(wgt) - -end - ---- This function return the percentage remaining in a single Lipo cel -local function 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 - - for i, v in ipairs(myArrayPercentList) do - if v[1] >= cellValue then - result = v[2] - break - end - end - return result -end - -local function calcCellCount(wgt, singleVoltage) - if singleVoltage < 4.3 then - return 1 - elseif singleVoltage < 8.6 then - return 2 - elseif singleVoltage < 12.9 then - return 3 - elseif singleVoltage < 17.2 then - return 4 - elseif singleVoltage < 21.5 then - return 5 - elseif singleVoltage < 25.8 then - return 6 - elseif singleVoltage < 30.1 then - return 7 - elseif singleVoltage < 34.4 then - return 8 - elseif singleVoltage < 38.7 then - return 9 - elseif singleVoltage < 43.0 then - return 10 - elseif singleVoltage < 47.3 then - return 11 - elseif singleVoltage < 51.6 then - return 12 - 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) - log("v: " .. v) - - 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("single value: " .. fieldinfo['name'] .. "=" .. v) - else - log("only one cell using Ax lipo sensor") + -- imports + 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 - else - -- no telemetry available - wgt.isDataAvailable = false - if fieldinfo then - log("no telemetry data: " .. fieldinfo['name'] .. "=??") - else - log("no telemetry data") - end - return - end - - local newCellCount = calcCellCount(wgt, v) - - -- this is necessary for simu where cell-count can change - if newCellCount ~= wgt.cellCount then - wgt.vMin = 99 - wgt.vMax = 0 - end - - -- calc highest of all cells - if v > wgt.vMax then - wgt.vMax = v - end - - wgt.cellCount = newCellCount - wgt.vTotalLive = v - wgt.vCellLive = wgt.vTotalLive / wgt.cellCount - wgt.vPercent = getCellPercent(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 - -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 + loadScript("/WIDGETS/" .. app_name .. "/ui_lvgl")(wgt) + return wgt 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) +-- This function allow updates when you change widgets settings +local function update(wgt, options) + wgt.options = options + + wgt.batt_height = wgt.zone.h --??? + wgt.batt_width = wgt.zone.w + -- wgt.log("batt_11 width: " .. wgt.batt_width .. ", height: " .. wgt.batt_height) + + + local ver, radio, maj, minor, rev, osname = getVersion() + wgt.is_valid_ver = (maj == 2 and minor >= 11) + if wgt.is_valid_ver==false then + local lytIvalidVer = { + { + type=LVGL_DEF.type.LABEL, x=0, y=0, font=0, + text="!! this widget \nis supported only \non ver 2.11 and above", + color=RED + } + } + lvgl.build(lytIvalidVer) + return 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 1/8th top bar -local function refreshZoneTiny(wgt) - local myString = string.format("%2.1fV", 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"] = 0, ["y"] = 0, ["w"] = 155, ["h"] = 35, ["segments_w"] = 25, ["color"] = WHITE, ["cath_w"] = 6, ["cath_h"] = 20 } - - -- fill battery - local fill_color = getPercentColor(wgt.vPercent) - lcd.drawGauge(wgt.zone.x, wgt.zone.y, myBatt.w, myBatt.h, wgt.vPercent, 100, fill_color) - - -- draw battery - lcd.drawRectangle(wgt.zone.x + myBatt.x, wgt.zone.y + myBatt.y, myBatt.w, myBatt.h, WHITE, 2) - - -- write text - local topLine = string.format("%2.1fV %2.0f%%", wgt.mainValue, wgt.vPercent) - lcd.drawText(wgt.zone.x + 20, wgt.zone.y + 2, 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.1fV", wgt.mainValue), DBLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(wgt.zone.x + myBatt.w + 10, wgt.zone.y + 30, string.format("%2.0f%%", wgt.vPercent), MIDSIZE + wgt.text_color + wgt.no_telem_blink) - if wgt.options.Show_Total_Voltage == 0 then - lcd.drawText(wgt.zone.x + wgt.zone.w, wgt.zone.y + wgt.zone.h -35, string.format("%2.1fV %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.1fV", wgt.mainValue), DBLSIZE + wgt.text_color + wgt.no_telem_blink) - end - 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) - - -- more info if 1/4 is high enough (without trim & slider) - if wgt.zone.h > 80 then - 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 + 0, string.format("%2.1fV", wgt.mainValue), RIGHT + DBLSIZE + wgt.text_color) - lcd.drawText(wgt.zone.x + wgt.zone.w, wgt.zone.y + 30, wgt.vPercent .. "%", RIGHT + DBLSIZE + wgt.text_color) - lcd.drawText(wgt.zone.x + wgt.zone.w, wgt.zone.y + wgt.zone.h - 35, string.format("%2.1fV %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) + wgt.update_logic(wgt, options) + wgt.update_ui() 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.1fV %2.0f%%", wgt.mainValue, wgt.vPercent), RIGHT + XXLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + w, y +h - 60 , string.format("%2.1fV %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 -end - - ---- Zone size: 460x252 - app mode (full screen) -local function refreshAppMode(wgt, event, touchState) - local x = 0 - local w = 460 - local y = 0 - local h = 252 - - local myBatt = { ["x"] = 10, ["y"] = 0, ["w"] = 80, ["h"] = h, ["segments_h"] = 30, ["color"] = WHITE, ["cath_w"] = 30, ["cath_h"] = 10 } - - if (event ~= nil) then - log("event: " .. event) - end - - -- draw right text section - lcd.drawText(x + w, y + myBatt.y + 0, string.format("%2.1fV %2.0f%%", wgt.mainValue, wgt.vPercent), RIGHT + XXLSIZE + wgt.text_color + wgt.no_telem_blink) - lcd.drawText(x + w, y +h - 60, string.format("%2.1fV %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 -end - --- This function allow recording of lowest cells when widget is in background local function background(wgt) - if (wgt == nil) then - return - end - - detectResetEvent(wgt) - - calculateBatteryData(wgt) + wgt.background() +end +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) - 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 - - detectResetEvent(wgt) - - calculateBatteryData(wgt) - - 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) - elseif 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) - end - + wgt.refresh(event, touchState) end -return { name = "BattAnalog", 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/c480x320/WIDGETS/BattAnalog/ui_lvgl.lua b/sdcard/c480x320/WIDGETS/BattAnalog/ui_lvgl.lua new file mode 100644 index 00000000..faed7d39 --- /dev/null +++ b/sdcard/c480x320/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)} + + + -- 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 From d796447e3e580622f215b9271beb1a7b251981e3 Mon Sep 17 00:00:00 2001 From: shmuely Date: Tue, 21 Jan 2025 00:49:54 +0200 Subject: [PATCH 2/3] feat(GaugeRotary): update 480x320 widget --- .../WIDGETS/GaugeRotary/gauge_core.lua | 355 ++++++----- .../c480x320/WIDGETS/GaugeRotary/lib_log.lua | 110 ++++ .../WIDGETS/GaugeRotary/lib_widget_tools.lua | 300 +++++++++ sdcard/c480x320/WIDGETS/GaugeRotary/main.lua | 572 ++++++++++-------- 4 files changed, 943 insertions(+), 394 deletions(-) create mode 100644 sdcard/c480x320/WIDGETS/GaugeRotary/lib_log.lua create mode 100644 sdcard/c480x320/WIDGETS/GaugeRotary/lib_widget_tools.lua diff --git a/sdcard/c480x320/WIDGETS/GaugeRotary/gauge_core.lua b/sdcard/c480x320/WIDGETS/GaugeRotary/gauge_core.lua index 09a02784..0e2091f5 100644 --- a/sdcard/c480x320/WIDGETS/GaugeRotary/gauge_core.lua +++ b/sdcard/c480x320/WIDGETS/GaugeRotary/gauge_core.lua @@ -1,171 +1,218 @@ -local HighAsGreen, p2 = ... +local m_log, m_tools, HighAsGreen = ... -local self = {} -self.HighAsGreen = HighAsGreen +local M = {} +M.m_log = m_log +M.tools = m_tools +M.HighAsGreen = HighAsGreen +-- 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 lcd = lcd -------------------------------------------------------------- -local function log(s) - --return; - print("Gauge_core: " .. s) +local function log(fmt, ...) + m_log.info(fmt, ...) end -------------------------------------------------------------- -function self.drawArm(armX, armY, armR, percentageValue, color, isFull) - --min = 5.54 - --max = 0.8 - - local degrees - if isFull then - degrees = 5.51 - (4.74 * percentageValue / 100) - else - --degrees = 4.74 - (3.14 * percentageValue / 100) - degrees = 5.05 - (3.84 * percentageValue / 100) - end - - --log("percentageValue: " .. percentageValue .. ", degrees: " .. degrees) - local xh = math.floor(armX + (math.sin(degrees) * armR)) - local yh = math.floor(armY + (math.cos(degrees) * armR)) - - --lcd.setColor(CUSTOM_COLOR, lcd.RGB(0, 0, 255)) - --lcd.setColor(CUSTOM_COLOR, lcd.RGB(255, 255, 255)) - lcd.setColor(CUSTOM_COLOR, color) - - local x1 = math.floor(armX - (math.sin(0) * (20 / 2.3))) - local y1 = math.floor(armY - (math.cos(0) * (20 / 2.3))) - local x2 = math.floor(armX - (math.sin(3) * (20 / 2.3))) - local y2 = math.floor(armY - (math.cos(3) * (20 / 2.3))) - lcd.drawFilledTriangle(x1, y1, x2, y2, xh, yh, CUSTOM_COLOR) +function M.drawArm(armX, armY, armR, percentageValue, color, isFull) + --min = 5.54 + --max = 0.8 + + local degrees + if isFull then + degrees = 5.51 - (4.74 * percentageValue / 100) + else + --degrees = 4.74 - (3.14 * percentageValue / 100) + degrees = 5.05 - (3.84 * percentageValue / 100) + end + + --log("percentageValue: " .. percentageValue .. ", degrees: " .. degrees) + local xh = math.floor(armX + (math.sin(degrees) * armR)) + local yh = math.floor(armY + (math.cos(degrees) * armR)) + + --lcd.setColor(CUSTOM_COLOR, lcd.RGB(0, 0, 255)) + --lcd.setColor(CUSTOM_COLOR, lcd.RGB(255, 255, 255)) + lcd.setColor(CUSTOM_COLOR, color) + + local x1 = math.floor(armX - (math.sin(0) * (20 / 2.3))) + local y1 = math.floor(armY - (math.cos(0) * (20 / 2.3))) + local x2 = math.floor(armX - (math.sin(3) * (20 / 2.3))) + local y2 = math.floor(armY - (math.cos(3) * (20 / 2.3))) + lcd.drawFilledTriangle(x1, y1, x2, y2, xh, yh, CUSTOM_COLOR) end --- This function returns green at gvalue, red at rvalue and graduate in between -function self.getRangeColor(value, red_value, green_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 +-- This function returns green at green_value, red at red_value and graduate in between +function M.getRangeColor(value, red_value, green_value) + if value == nil or red_value == nil or green_value == nil then + return lcd.RGB(0, 0xdf, 0) + end + + local range = math.abs(green_value - red_value) + if range == 0 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 + local g = math.floor(0xdf * (value - red_value) / range) + local 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 + local r = math.floor(0xdf * (value - green_value) / range) + local g = 0xdf - r + return lcd.RGB(r, g, 0) + end end -function self.drawGauge(centerX, centerY, centreR, isFull, percentageValue, percentageValueMin, percentageValueMax, txt1, txt2) - - local fender = 4 - local tickWidth = 9 - local armCenterR = centreR / 2.5 - local armR = centreR - 8 - local txtSize = DBLSIZE - if centreR < 65 then - txtSize = MIDSIZE - end - if centreR < 30 then - txtSize = SMLSIZE - end - - -- main gauge background - if isFull then - lcd.drawFilledCircle(centerX, centerY, centreR, lcd.RGB(0x1A1A1A)) - else - lcd.drawPie(centerX,centerY,centreR, -110,110, lcd.RGB(0x1A1A1A)) - end - - -- fender - if isFull then - lcd.drawAnnulus(centerX, centerY, centreR - fender, centreR, 0, 360, BLACK) - else - lcd.drawAnnulus(centerX, centerY, centreR - fender, centreR, -110, 110, BLACK) - end - - -- ticks - local to_tick - local tick_offset - local tick_step = 10 - if isFull then - to_tick = 210 - tick_offset = 250 - else - to_tick = 210 - tick_offset = 250 - end - if (centreR < 100) then - tick_step = 10 + 0.15 * (100 - centreR) - end - for i = 0, to_tick, tick_step do - --log("HighAsGreen: " .. self.HighAsGreen) - if (self.HighAsGreen == 1) then - lcd.setColor(CUSTOM_COLOR, self.getRangeColor(i, 0, to_tick - 10)) +function M.drawGauge(centerX, centerY, centerR, isFull, percentageValue, percentageValueMin, percentageValueMax, txt1, value_fmt_min, value_fmt_max, txt2) + if value_fmt_min == nil then + value_fmt_min = "" + end + if value_fmt_max == nil then + value_fmt_max = "" + end + if txt1 == nil then + txt1 = "" + end + if txt2 == nil then + txt2 = "" + end + + local fender = 4 + local tickWidth = 9 + local armCenterR = centerR / 2.5 + local armR = centerR - 8 + local txtSize = FONT_16 + local txtSizeMinMax = FONT_16 + if centerR < 70 then + txtSize = FONT_12 + end + if centerR < 60 then + txtSize = FONT_8 + end + if centerR < 50 then + txtSize = FONT_6 + end + + if centerR < 90 then + txtSizeMinMax = FONT_12 + end + if centerR < 80 then + txtSizeMinMax = FONT_8 + end + if centerR < 60 then + txtSizeMinMax = FONT_6 + end + if centerR < 50 then + txtSizeMinMax = FONT_6 + end + + -- main gauge background + if isFull then + lcd.drawFilledCircle(centerX, centerY, centerR, lcd.RGB(0x1A1A1A)) + else + lcd.drawPie(centerX, centerY, centerR, -110, 110, lcd.RGB(0x1A1A1A)) + end + + -- fender + if isFull then + lcd.drawAnnulus(centerX, centerY, centerR - fender, centerR, 0, 360, BLACK) + else + lcd.drawAnnulus(centerX, centerY, centerR - fender, centerR, -110, 110, BLACK) + end + + -- ticks + local to_tick = 210 + local tick_offset = 250 + local tick_step = 10 + if isFull then + to_tick = 210 + tick_offset = 250 + end + if (centerR < 100) then + tick_step = 10 + 0.15 * (100 - centerR) + end + for i = 0, to_tick, tick_step do + --log("HighAsGreen: " .. M.HighAsGreen) + if (M.HighAsGreen == 1) then + local newColor = M.getRangeColor(i, 0, to_tick - 10) + lcd.setColor(CUSTOM_COLOR, newColor) + else + local newColor = M.getRangeColor(i, to_tick - 10, 0) + lcd.setColor(CUSTOM_COLOR, newColor) + end + lcd.drawAnnulus(centerX, centerY, centerR - fender - 3 - tickWidth, centerR - fender - 3, tick_offset + i, tick_offset + i + 7, CUSTOM_COLOR) + --lcd.drawAnnulus(centerX, centerY, centerR -fender -3 -tickWidth, centerR -fender -3 , 250 +i, 250 +i +7, YELLOW) + --lcd.drawAnnulus(centerX, centerY, centerR -fender -3 -tickWidth -15, centerR -fender -3 -tickWidth -4 , 250 +i, 250 +i +7, RED) + end + --lcd.drawPie(centerX,centerY,centerR - fender, 0,20) + + local armColor = lcd.RGB(255, 255, 255) + local armColorMin, armColorMax + if (M.HighAsGreen == 1) then + armColorMin = lcd.RGB(200, 0, 0) + armColorMax = lcd.RGB(0, 200, 0) else - lcd.setColor(CUSTOM_COLOR, self.getRangeColor(i, to_tick - 10, 0)) - --lcd.setColor(CUSTOM_COLOR, self.getRangeColor(i, 120 , 30)) - end - lcd.drawAnnulus(centerX, centerY, centreR - fender - 3 - tickWidth, centreR - fender - 3, tick_offset + i, tick_offset + i + 7, CUSTOM_COLOR) - --lcd.drawAnnulus(centerX, centerY, centreR -fender -3 -tickWidth, centreR -fender -3 , 250 +i, 250 +i +7, YELLOW) - --lcd.drawAnnulus(centerX, centerY, centreR -fender -3 -tickWidth -15, centreR -fender -3 -tickWidth -4 , 250 +i, 250 +i +7, RED) - end - --lcd.drawPie(centerX,centerY,centreR - fender, 0,20) - - local armColor = lcd.RGB(255, 255, 255) - local armColorMin, armColorMax - if (self.HighAsGreen == 1) then - armColorMin = lcd.RGB(200, 0, 0) - armColorMax = lcd.RGB(0, 200, 0) - else - armColorMin = lcd.RGB(0, 200, 0) - armColorMax = lcd.RGB(200, 0, 0) - end - - --self.drawArm(centerX, centerY, armR, 0, armColorMin, isFull) - --self.drawArm(centerX, centerY, armR, 10, armColorMin, isFull) - --self.drawArm(centerX, centerY, armR, 50, armColorMin, isFull) - --self.drawArm(centerX, centerY, armR, 90, armColorMin, isFull) - --self.drawArm(centerX, centerY, armR, 100, armColorMin, isFull) - - if percentageValueMin ~= nil and percentageValueMax ~= nil then - self.drawArm(centerX, centerY, armR, percentageValueMin, armColorMin, isFull) - self.drawArm(centerX, centerY, armR, percentageValueMax, armColorMax, isFull) - end - self.drawArm(centerX, centerY, armR, percentageValue, armColor, isFull) - - -- hide the base of the arm - lcd.drawFilledCircle(centerX, centerY, armCenterR, BLACK) - lcd.drawAnnulus(centerX, centerY, armCenterR-2, armCenterR, 0, 360, + armColorMin = lcd.RGB(0, 200, 0) + armColorMax = lcd.RGB(200, 0, 0) + end + + --M.drawArm(centerX, centerY, armR, 0, armColorMin, isFull) + --M.drawArm(centerX, centerY, armR, 10, armColorMin, isFull) + --M.drawArm(centerX, centerY, armR, 50, armColorMin, isFull) + --M.drawArm(centerX, centerY, armR, 90, armColorMin, isFull) + --M.drawArm(centerX, centerY, armR, 100, armColorMin, isFull) + + if percentageValueMin ~= nil and percentageValueMax ~= nil then + M.drawArm(centerX, centerY, armR, percentageValueMin, armColorMin, isFull) + M.drawArm(centerX, centerY, armR, percentageValueMax, armColorMax, isFull) + end + M.drawArm(centerX, centerY, armR, percentageValue, armColor, isFull) + + -- hide the base of the arm + lcd.drawFilledCircle(centerX, centerY, armCenterR, BLACK) + lcd.drawAnnulus(centerX, centerY, armCenterR - 2, armCenterR, 0, 360, --lcd.RGB(255, 255, 0) - lcd.RGB(192, 192, 192) - ) + lcd.RGB(192, 192, 192) + ) - -- text in center - lcd.drawText(centerX + 0, centerY - 8, txt2, CENTER + SMLSIZE + WHITE) -- XXLSIZE/DBLSIZE/MIDSIZE/SMLSIZE + -- text in center + lcd.drawText(centerX + 0, centerY - 8, txt2, CENTER + FONT_6 + WHITE) -- FONT_38/FONT_16/FONT_12/FONT_6 - -- text below - if isFull then - --lcd.drawText(centerX + 8, centerY + 30, txt1, CENTER + txtSize + WHITE) - lcd.drawText(centerX + 0, centerY + armCenterR + 2, txt1, CENTER + txtSize + WHITE) - else - -- no text below in flat mode - end + + -- text min/max + if centerR > 60 then + local y = centerY + (centerR - centerR * 0.6) + -- text min + M.tools.drawBadgedTextCenter(value_fmt_min, centerX - armCenterR -15, y, txtSizeMinMax, armColorMin, GREY) + + -- text max + M.tools.drawBadgedTextCenter(value_fmt_max, centerX + armCenterR + 15, y, txtSizeMinMax, armColorMax, GREY) + end + + -- text below + if isFull then + M.tools.drawBadgedTextCenter(txt1, centerX, centerY + centerR * 0.7, txtSize, WHITE, GREY) + else + -- no text below in flat mode + end end -return self \ No newline at end of file +return M diff --git a/sdcard/c480x320/WIDGETS/GaugeRotary/lib_log.lua b/sdcard/c480x320/WIDGETS/GaugeRotary/lib_log.lua new file mode 100644 index 00000000..ee29fcce --- /dev/null +++ b/sdcard/c480x320/WIDGETS/GaugeRotary/lib_log.lua @@ -0,0 +1,110 @@ +local app_name, script_dir = ... + +local ENABLE_LOG_TO_CONSOLE = false +local ENABLE_LOG_TO_FILE = false + + +local M = {} +M.app_name = app_name +M.script_dir = script_dir + +local log = { + outfile = script_dir .. "/app.log", + enable_file = ENABLE_LOG_TO_FILE, + enable_console = ENABLE_LOG_TO_CONSOLE, + 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 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/c480x320/WIDGETS/GaugeRotary/lib_widget_tools.lua b/sdcard/c480x320/WIDGETS/GaugeRotary/lib_widget_tools.lua new file mode 100644 index 00000000..317047e8 --- /dev/null +++ b/sdcard/c480x320/WIDGETS/GaugeRotary/lib_widget_tools.lua @@ -0,0 +1,300 @@ +local m_log, app_name = ... + +local M = {} +M.m_log = m_log +M.app_name = app_name +M.tele_src_name = nil +M.tele_src_id = nil + +local getTime = getTime +local lcd = lcd + +-- 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 function log(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", "°", + "rad", "ml", "fOz", "ml/m", "Hz", "mS", "uS", "km" +} + +function M.unitIdToString(unitId) + if unitId == nil then + return "" + end + -- UNIT_RAW + if unitId == "0" then + return "" + end + + --log("idUnit: " .. unitId) + + if (unitId > 0 and unitId <= #UNIT_ID_TO_STRING) then + local txtUnit = UNIT_ID_TO_STRING[unitId] + --log("txtUnit: " .. txtUnit) + return txtUnit + end + + return "-#-" +end + +--------------------------------------------------------------------------------------------------- + +function M.periodicInit() + local t = {} + t.startTime = -1; + t.durationMili = -1; + return t +end + +function M.periodicStart(t, durationMili) + t.startTime = getTime(); + t.durationMili = durationMili; +end + +function M.periodicHasPassed(t) + -- not started yet + if (t.durationMili <= 0) then + return false; + end + + local elapsed = getTime() - t.startTime; + log('elapsed: %d (t.durationMili: %d)', elapsed, t.durationMili) + local elapsedMili = elapsed * 10; + if (elapsedMili < t.durationMili) then + return false; + end + return true; +end + +function M.periodicGetElapsedTime(t) + local elapsed = getTime() - t.startTime; + log("elapsed: %d",elapsed); + local elapsedMili = elapsed * 10; + log("elapsedMili: %d",elapsedMili); + return elapsedMili; +end + +function M.periodicReset(t) + t.startTime = getTime(); + log("periodicReset()"); + M.periodicGetElapsedTime(t) +end + +function M.getDurationMili(t) + return t.durationMili +end + +--------------------------------------------------------------------------------------------------- + +function M.isTelemetryAvailable() + -- select telemetry source + if not M.tele_src_id then + log("select telemetry source") + local tele_src = getFieldInfo("RSSI") + if not tele_src then tele_src = getFieldInfo("RxBt") end + if not tele_src then tele_src = getFieldInfo("A1") end + if not tele_src then tele_src = getFieldInfo("A2") end + if not tele_src then tele_src = getFieldInfo("1RSS") end + if not tele_src then tele_src = getFieldInfo("2RSS") end + if not tele_src then tele_src = getFieldInfo("RQly") end + if not tele_src then tele_src = getFieldInfo("TRSS") end + if not tele_src then tele_src = getFieldInfo("VFR%") end + + if tele_src == nil then + log("no telemetry sensor found") + M.tele_src_id = nil + M.tele_src_name = "---" + return false + else + log("telemetry sensor found: " .. tele_src.name) + M.tele_src_id = tele_src.id + M.tele_src_name = tele_src.name + end + end + + if M.tele_src_id == nil then + return false + end + + local rx_val = getValue(M.tele_src_id) + if rx_val ~= 0 then + return true + end + return false +end + +--------------------------------------------------------------------------------------------------- + +-- workaround to detect telemetry-reset event, until a proper implementation on the lua interface will be created +-- this workaround assume that: +-- RSSI- is always going down +-- RSSI- is reset on the C++ side when a telemetry-reset is pressed by user +-- widget is calling this func on each refresh/background +-- on event detection, the function onTelemetryResetEvent() will be trigger +-- +function M.detectResetEvent(wgt, callback_onTelemetryResetEvent) + + local currMinRSSI = getValue('RSSI-') + if (currMinRSSI == nil) then + log("telemetry reset event: can not be calculated") + return + end + if (currMinRSSI == wgt.telemResetLowestMinRSSI) then + --log("telemetry reset event: not found") + return + end + + if (currMinRSSI < wgt.telemResetLowestMinRSSI) then + -- rssi just got lower, record it + wgt.telemResetLowestMinRSSI = currMinRSSI + --log("telemetry reset event: not found") + return + end + + -- reset telemetry detected + wgt.telemResetLowestMinRSSI = 101 + log("telemetry reset event detected") + + -- notify event + callback_onTelemetryResetEvent(wgt) +end + +--------------------------------------------------------------------------------------------------- +function M.getSensorPrecession(sensorName) + + for i=0, 30, 1 do + local s2 = model.getSensor(i) + --type (number) 0 = custom, 1 = calculated + --name (string) Name + --unit (number) See list of units in the appendix of the OpenTX Lua Reference Guide + --prec (number) Number of decimals + --id (number) Only custom sensors + --instance (number) Only custom sensors + --formula (number) Only calculated sensors. 0 = Add etc. see list of formula choices in Companion popup + + --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.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 + +--------------------------------------------------------------------------------------------------- +-- workaround for bug in getFiledInfo() -- ???? why? +function M.cleanInvalidCharFromGetFiledInfo(sourceName) + + 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 + +------------------------------------------------------------------------------------------------------ + +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 = -11 + elseif font_size == FONT_16 then + v_offset = -5 + elseif font_size == FONT_12 then + v_offset = -4 + elseif font_size == FONT_8 then + v_offset = -3 + elseif font_size == FONT_6 then + v_offset = 0 + end + return ts_w, ts_h, v_offset +end + +------------------------------------------------------------------------------------------------------ + +function M.drawBadgedText(txt, txtX, txtY, font_size, text_color, bg_color) + local ts_w, ts_h, v_offset = M.lcdSizeTextFixed(txt, font_size) + ts_h = ts_h + v_offset * 2 + local r = ts_h / 2 + lcd.drawFilledCircle(txtX , txtY + r, r, bg_color) + lcd.drawFilledCircle(txtX + ts_w , txtY + r, r, bg_color) + lcd.drawFilledRectangle(txtX, txtY , ts_w, ts_h, bg_color) + + lcd.drawText(txtX, txtY + v_offset, txt, font_size + text_color) + + --lcd.drawRectangle(txtX, txtY , ts_w, ts_h, RED) -- dbg +end + +function M.drawBadgedTextCenter(txt, txtX, txtY, font_size, text_color, bg_color) + local ts_w, ts_h, v_offset = M.lcdSizeTextFixed(txt, font_size) + ts_h = ts_h + v_offset * 2 + local r = ts_h / 2 + local x = txtX - ts_w/2 + local y = txtY - ts_h/2 + lcd.drawFilledCircle(x + r * 0.3, y + r, r, bg_color) + lcd.drawFilledCircle(x - r * 0.3 + ts_w , y + r, r, bg_color) + lcd.drawFilledRectangle(x, y, ts_w, ts_h, bg_color) + + lcd.drawText(x, y + v_offset, txt, font_size + text_color) + + -- dbg + --lcd.drawRectangle(x, y , ts_w, ts_h, RED) -- dbg + --lcd.drawLine(txtX-30, txtY, txtX+30, txtY, SOLID, RED) -- dbg + --lcd.drawLine(txtX, txtY-20, txtX, txtY+20, SOLID, RED) -- dbg +end + +------------------------------------------------------------------------------------------------------ + +return M diff --git a/sdcard/c480x320/WIDGETS/GaugeRotary/main.lua b/sdcard/c480x320/WIDGETS/GaugeRotary/main.lua index a5e7232c..04fbaf10 100644 --- a/sdcard/c480x320/WIDGETS/GaugeRotary/main.lua +++ b/sdcard/c480x320/WIDGETS/GaugeRotary/main.lua @@ -1,20 +1,22 @@ ----- ######################################################################### ----- # # ----- # Telemetry Widget script for FrSky Horus/RadioMaster TX16s # ----- # Copyright (C) EdgeTX # ------# # ----- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html # ----- # # ----- # This program is free software; you can redistribute it and/or modify # ----- # it under the terms of the GNU General Public License version 2 as # ----- # published by the Free Software Foundation. # ----- # # ----- # 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. # ----- # # ----- ######################################################################### +--[[ +######################################################################### +# # +# Telemetry Widget script for FrSky Horus/RadioMaster TX16s # +# Copyright "Offer Shmuely" # +# # +# License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html # +# # +# This program is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License version 2 as # +# published by the Free Software Foundation. # +# # +# 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. # +# # +######################################################################### + -- This Rotary Gauge widget display a fancy old style analog gauge with needle -- Options: @@ -31,276 +33,366 @@ -- * Transmitter Battery -- * batt-capacity -- * A1/A2 analog voltage +]] + --- Version: 0.1 -- Author : Offer Shmuely +-- Date: 2021-2023 +local app_name = "GaugeRotary" +local app_ver = "0.8" + -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", "°", "rad", "ml", "fOz", "ml/m", "Hz", "uS", "km" } +-- consts local DEFAULT_MIN_MAX = { - {"RSSI" , 0, 100, 0}, - {"1RSS" , -120, 0, 0}, - {"2RSS" , -120, 0, 0}, - {"RQly" , 0, 100, 0}, - {"RxBt" , 4, 10, 1}, - {"TxBt" , 6, 8.4, 1}, - {"Batt" , 6, 8.4, 1}, - {"cell" ,3.5, 4.2, 1}, - {"Fuel" , 0, 100, 0}, - {"Vibr" , 0, 100, 0}, - {"Temp" , 30,120, 0}, - {"Tmp1" , 30,120, 0}, - {"Tmp2" , 30,120, 0}, + { "RSSI", 0, 100, 0 }, + { "1RSS", -120, 0, 0 }, + { "2RSS", -120, 0, 0 }, + { "RQly", 0, 100, 0 }, + { "RxBt", 4, 10, 1 }, + { "TxBt", 6, 8.4, 1 }, + { "Batt", 6, 8.4, 1 }, + { "cell", 3.5, 4.2, 1 }, + { "Fuel", 0, 100, 0 }, + { "Vibr", 0, 100, 0 }, + { "Temp", 30, 120, 0 }, + { "Tmp1", 30, 120, 0 }, + { "Tmp2", 30, 120, 0 }, } +-- 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 + +-- backward compatibility +local ver, radio, maj, minor, rev, osname = getVersion() +local DEFAULT_SOURCE = 1 +if maj == 2 and minor == 7 then + -- for 2.7.x + DEFAULT_SOURCE = 253 -- RSSI=253, TxBt=243, RxBt=256 +elseif maj == 2 and minor >= 8 then + -- for 2.8.x + DEFAULT_SOURCE = 306 -- RSSI +end + + local _options = { - { "Source", SOURCE, 253 }, -- RSSI - --{ "Source", SOURCE, 243 }, -- TxBt - --{ "Source", SOURCE, 256 }, -- RxBt - { "Min", VALUE, -1, -1024, 1024 }, - { "Max", VALUE, -1, -1024, 1024 }, - { "HighAsGreen", BOOL, 1 }, - { "Precision", VALUE, 1 , 0 , 1} + { "Source", SOURCE, DEFAULT_SOURCE }, -- RSSI + --{ "Source", SOURCE, 243 }, -- TxBt + --{ "Source", SOURCE, 256 }, -- RxBt + { "Min", VALUE, -1, -1024, 1024 }, + { "Max", VALUE, -1, -1024, 10000 }, + { "HighAsGreen", BOOL, 1 }, + { "Precision", VALUE, 1, 0, 1 } } +-- imports +local LibLogClass = loadScript("/WIDGETS/" .. app_name .. "/lib_log.lua", "tcd") +local LibWidgetToolsClass = loadScript("/WIDGETS/" .. app_name .. "/lib_widget_tools.lua", "tcd") +local GaugeClass = loadScript("/WIDGETS/" .. app_name .. "/gauge_core.lua", "tcd") + +local m_log = LibLogClass(app_name, "/WIDGETS/" .. app_name) + -------------------------------------------------------------- -local function log(s) - return; - --print("GaugeRotary: " .. s) +local function log(...) + m_log.info(...) end -------------------------------------------------------------- local function setAutoMinMax(wgt) - if wgt.options.Min ~= -1 and wgt.options.Max ~= -1 then - --if wgt.options.Min ~= wgt.options.Max then - print("GaugeRotary-setting: " .. "no need for AutoMinMax") - return - end - - print("GaugeRotary-setting: " .. "AutoMinMax") - local sourceName = getSourceName(wgt.options.Source) - -- workaround for bug in getFiledInfo() - if string.byte(string.sub(sourceName,1,1)) > 127 then - sourceName = string.sub(sourceName,2,-1) -- ???? why? - end - print("GaugeRotary-setting: " .. "AutoMinMax, source:" .. sourceName) - - for i=1, #DEFAULT_MIN_MAX, 1 do - local def_key = DEFAULT_MIN_MAX[i][1] - local def_min = DEFAULT_MIN_MAX[i][2] - local def_max = DEFAULT_MIN_MAX[i][3] - local def_precision = DEFAULT_MIN_MAX[i][4] - - if def_key == sourceName then - log(string.format("setting min-max from default: %s: min:%d, max:%d, precision:%d", def_key, def_min, def_max, def_precision)) - wgt.options.Min = def_min - wgt.options.Max = def_max - wgt.options.precision = def_precision - break + -- log("setAutoMinMax(wgt.options.Min: %d, wgt.options.Max: %d) ", wgt.options.Min, wgt.options.Max) + if wgt.options.Min ~= -1 or wgt.options.Max ~= -1 then + --if wgt.options.Min ~= wgt.options.Max then + log("GaugeRotary-setting: " .. "no need for AutoMinMax") + return end - end - - if wgt.options.Min == wgt.options.Max then - print("GaugeRotary-setting: " .. "AutoMinMax else") - wgt.options.Min = 0 - wgt.options.Max = 100 - end -end - -local function create(zone, options) - local GaugeClass = loadScript("/WIDGETS/GaugeRotary/gauge_core.lua") + log("GaugeRotary-setting: " .. "AutoMinMax") + local sourceName = getSourceName(wgt.options.Source) + if (sourceName == nil) then return end - local wgt = { - zone = zone, - options = options, - gauge1 = GaugeClass(options.HighAsGreen, 2) - } + -- workaround for bug in getFiledInfo() + if string.byte(string.sub(sourceName, 1, 1)) > 127 then + sourceName = string.sub(sourceName, 2, -1) -- ???? why? + end + log("GaugeRotary-setting: " .. "AutoMinMax, source:" .. sourceName) + + for i = 1, #DEFAULT_MIN_MAX, 1 do + local def_key = DEFAULT_MIN_MAX[i][1] + local def_min = DEFAULT_MIN_MAX[i][2] + local def_max = DEFAULT_MIN_MAX[i][3] + local def_precision = DEFAULT_MIN_MAX[i][4] + + if def_key == sourceName then + log("setting min-max from default: %s: min:%d, max:%d, precision:%d", def_key, def_min, def_max, def_precision) + wgt.options.Min = def_min + wgt.options.Max = def_max + wgt.options.precision = def_precision + break + end + end - setAutoMinMax(wgt) + if wgt.options.Min == wgt.options.Max then + log("GaugeRotary-setting: " .. "AutoMinMax else") + wgt.options.Min = 0 + wgt.options.Max = 100 + end - return wgt end local function update(wgt, options) - wgt.options = options - setAutoMinMax(wgt) - wgt.gauge1.HighAsGreen = wgt.options.HighAsGreen + wgt.options = options + wgt.gauge1 = GaugeClass(m_log, wgt.tools, options.HighAsGreen) + setAutoMinMax(wgt) +end + +local function create(zone, options) + local wgt = { + zone = zone, + options = options, + last_value = -1, + last_value_min = -1, + last_value_max = -1, + gauge1 = nil + } + + wgt.tools = LibWidgetToolsClass(m_log, app_name) + + update(wgt, options) + return wgt end --- ----------------------------------------------------------------------------------------------------- +-------------------------------------------------------------------------------------------------------- local function getPercentageValue(value, options_min, options_max) - if value == nil then - return nil - end - - local percentageValue = value - options_min; - percentageValue = (percentageValue / (options_max - options_min)) * 100 - percentageValue = tonumber(percentageValue) - percentageValue = math.floor( percentageValue ) - - if percentageValue > 100 then - percentageValue = 100 - elseif percentageValue < 0 then - percentageValue = 0 - end - - log("getPercentageValue(" .. value .. ", " .. options_min .. ", " .. options_max .. ")-->" .. percentageValue) - return percentageValue + if value == nil then + return nil + end + + local percentageValue = value - options_min; + percentageValue = (percentageValue / (options_max - options_min)) * 100 + percentageValue = tonumber(percentageValue) + percentageValue = math.floor(percentageValue) + + if percentageValue > 100 then + percentageValue = 100 + elseif percentageValue < 0 then + percentageValue = 0 + end + + log("getPercentageValue(%s, %s, %s)-->%s", value, options_min, options_max, percentageValue) + return percentageValue end local function getWidgetValue(wgt) - local currentValue = getValue(wgt.options.Source) - local sourceName = getSourceName(wgt.options.Source) - log("aaaaaa: ".. sourceName) - log("aaaaaa: ".. sourceName .. ": " .. string.byte(string.sub(sourceName, 1, 1))) - - -- workaround for bug in getFiledInfo() - if string.byte(string.sub(sourceName,1,1)) > 127 then - sourceName = string.sub(sourceName,2,-1) -- ???? why? - end - --log("Source: " .. wgt.options.Source .. ",name: " .. sourceName) - - --local currentValue = getValue(wgt.options.Source) / 10.24 - - local fieldinfo = getFieldInfo(wgt.options.Source) - if (fieldinfo == nil) then - log(string.format("getFieldInfo(%s)==nil", wgt.options.Source)) - return sourceName, -1, nil, nil, "" - end - - local txtUnit = "-" - if (fieldinfo.unit) then - --log("have unit") - if (fieldinfo.unit > 0 and fieldinfo.unit < #UNIT_ID_TO_STRING) then - txtUnit = UNIT_ID_TO_STRING[fieldinfo.unit] + local currentValue = getValue(wgt.options.Source) + local sourceName = getSourceName(wgt.options.Source) + log("[%s-%s],currentValue: %s" , wgt.options.Source, sourceName, currentValue) + + local fieldinfo = getFieldInfo(wgt.options.Source) + if (fieldinfo == nil) then + log("getFieldInfo(%s)==nil", wgt.options.Source) + return sourceName, -1, nil, nil, "" + end + + local txtUnit = wgt.tools.unitIdToString(fieldinfo.unit) + if type(currentValue) == "table" then + txtUnit = "v" + end + + --- if table, sum of all cells + if type(currentValue) == "table" then + local cellSum = 0 + for k, v in pairs(currentValue) do + cellSum = cellSum + v + end + currentValue = cellSum + end + + -- workaround for bug in getSourceName() + sourceName = wgt.tools.cleanInvalidCharFromGetFiledInfo(sourceName) + + + --log("") + --log("id: %s", fieldinfo.id) + --log(" sourceName: %s", sourceName) + --log(" curr: %2.1f", currentValue) + --log(" name: %s", fieldinfo.name) + --log(" desc: %s", fieldinfo.desc) + --log(" idUnit: %s", fieldinfo.unit) + --log(" txtUnit: %s", txtUnit) + + if (wgt.tools.isTelemetryAvailable()) then + + -- try to get min/max value (if exist) + local minValue, maxValue, source_min_id, source_max_id + + if source_min_id == nil or source_max_id == nil then + source_min_obj = getFieldInfo(sourceName .. "-") + if source_min_obj ~= nil then + source_min_id = source_min_obj.id + end + source_max_obj = getFieldInfo(sourceName .. "+") + if source_min_obj ~= nil then + source_max_id = source_max_obj.id + end + end + if source_min_id ~= nil and source_max_id ~= nil then + minValue = getValue(source_min_id) + maxValue = getValue(source_max_id) + end + + wgt.last_value = currentValue + wgt.last_value_min = minValue + wgt.last_value_max = maxValue + + --log("min/max: ["..sourceName.."]" .. minValue .. " < " .. currentValue .. " < " .. maxValue) + return sourceName, currentValue, minValue, maxValue, txtUnit + else + log("overriding value with last_value: " .. wgt.last_value) + return sourceName, wgt.last_value, wgt.last_value_min, wgt.last_value_max, txtUnit end - end - - log("") - log(string.format("id: %s", fieldinfo.id)) - log(string.format(" sourceName: %s", sourceName)) - log(string.format(" curr: %2.1f", currentValue)) - log(string.format(" name: %s", fieldinfo.name)) - log(string.format(" desc: %s", fieldinfo.desc)) - log(string.format(" idUnit: %s", fieldinfo.unit)) - log(string.format(" txtUnit: %s", txtUnit)) - - -- try to get min/max value (if exist) - local minValue = getValue(sourceName .. "-") - local maxValue = getValue(sourceName .. "+") - --log("min/max: " .. minValue .. " < " .. currentValue .. " < " .. maxValue) - - return sourceName, currentValue, minValue, maxValue, txtUnit end -local function refresh_app_mode(wgt, event, touchState, w_name, value, minValue, maxValue, w_unit, percentageValue, percentageValueMin, percentageValueMax) - local w_name, value, minValue, maxValue, w_unit = getWidgetValue(wgt) - if (value == nil) then - return - end +local function refresh_app_mode(wgt, event, touchState) + if (touchState and touchState.tapCount == 2) or (event and event == EVT_VIRTUAL_EXIT) then + lcd.exitFullScreen() + end + + local w_name, value, minValue, maxValue, w_unit = getWidgetValue(wgt) - local percentageValue = getPercentageValue(value, wgt.options.Min, wgt.options.Max) - local percentageValueMin = getPercentageValue(minValue, wgt.options.Min, wgt.options.Max) - local percentageValueMax = getPercentageValue(maxValue, wgt.options.Min, wgt.options.Max) + local percentageValue = getPercentageValue(value, wgt.options.Min, wgt.options.Max) + local percentageValueMin = getPercentageValue(minValue, wgt.options.Min, wgt.options.Max) + local percentageValueMax = getPercentageValue(maxValue, wgt.options.Min, wgt.options.Max) - local zone_w = 460 - local zone_h = 252 + local zone_w = 460 + local zone_h = 252 - local centerX = zone_w / 2 - wgt.gauge1.drawGauge(centerX, 120, 110, false, percentageValue, percentageValueMin, percentageValueMax, percentageValue .. w_unit, w_name) - lcd.drawText(10, 10, string.format("%d%s", percentageValue, w_unit), XXLSIZE + YELLOW) + local centerX = zone_w / 2 + wgt.gauge1.drawGauge(centerX, 120, 110, false, percentageValue, percentageValueMin, percentageValueMax, value .. w_unit, w_name) + lcd.drawText(10, 10, string.format("%d%s", value, w_unit), FONT_38 + YELLOW) - -- min / max - wgt.gauge1.drawGauge(100, 180, 50, false, percentageValueMin, nil, nil, "", w_name) - wgt.gauge1.drawGauge(zone_w - 100, 180, 50, false, percentageValueMax, nil, nil, "", w_name) - lcd.drawText(50, 230, string.format("Min: %d%s", percentageValueMin, w_unit), MIDSIZE) - lcd.drawText(350, 230, string.format("Max: %d%s", percentageValueMax, w_unit), MIDSIZE) + -- min / max + wgt.gauge1.drawGauge(100, 180, 50, false, percentageValueMin, nil, nil, "", w_name) + wgt.gauge1.drawGauge(zone_w - 100, 180, 50, false, percentageValueMax, nil, nil, "", w_name) + lcd.drawText(50, 230, string.format("Min: %d%s", minValue, w_unit), FONT_12) + lcd.drawText(350, 230, string.format("Max: %d%s", maxValue, w_unit), FONT_12) end +local function refresh_widget(wgt) + local w_name, value, minValue, maxValue, w_unit = getWidgetValue(wgt) + if (value == nil) then + return + end + + local percentageValue = getPercentageValue(value, wgt.options.Min, wgt.options.Max) + local percentageValueMin = getPercentageValue(minValue, wgt.options.Min, wgt.options.Max) + local percentageValueMax = getPercentageValue(maxValue, wgt.options.Min, wgt.options.Max) + + local value_fmt_min = "" + local value_fmt_max = "" + local value_fmt = "" + if wgt.options.precision == 0 then + value_fmt = string.format("%2.0f%s", value, w_unit) + if minValue ~= nil then + value_fmt_min = string.format("%2.0f%s", minValue, w_unit) + end + if maxValue ~= nil then + value_fmt_max = string.format("%2.0f%s", maxValue, w_unit) + end + else + value_fmt = string.format("%2.1f%s", value, w_unit) + if minValue ~= nil then + value_fmt_min = string.format("%2.1f%s", minValue, w_unit) + end + if maxValue ~= nil then + value_fmt_max = string.format("%2.1f%s", maxValue, w_unit) + end + end -local function refresh_widget(wgt, w_name, value, minValue, maxValue, w_unit, percentageValue, percentageValueMin, percentageValueMax) - local w_name, value, minValue, maxValue, w_unit = getWidgetValue(wgt) - if (value == nil) then - return - end - - local percentageValue = getPercentageValue(value, wgt.options.Min, wgt.options.Max) - local percentageValueMin = getPercentageValue(minValue, wgt.options.Min, wgt.options.Max) - local percentageValueMax = getPercentageValue(maxValue, wgt.options.Min, wgt.options.Max) - - local value_fmt = "" - if wgt.options.precision == 0 then - value_fmt = string.format("%2.0f%s", value, w_unit) - else - value_fmt = string.format("%2.1f%s", value, w_unit) - end - - -- calculate low-profile or full-circle - local isFull = true - if wgt.zone.h < 60 then - lcd.drawText(wgt.zone.x + 10, wgt.zone.y, "too small for GaugeRotary", SMLSIZE + RED) - return - elseif wgt.zone.h < 90 then - log("widget too low (" .. wgt.zone.h .. ")") - if wgt.zone.w * 1.2 > wgt.zone.h then - log("wgt wider then height, use low profile ") - isFull = false + -- calculate low-profile or full-circle + local isFull = true + if wgt.zone.h < 60 then + lcd.drawText(wgt.zone.x + 10, wgt.zone.y, "too small for GaugeRotary", FONT_6 + RED) + return + elseif wgt.zone.h < 90 then + log("widget too low (" .. wgt.zone.h .. ")") + if wgt.zone.w * 1.2 > wgt.zone.h then + log("wgt wider then height, use low profile ") + isFull = false + end end - end - local centerR, centerX, centerY + local centerR, centerX, centerY + + if isFull then + centerR = math.min(wgt.zone.h, wgt.zone.w) / 2 + --local centerX = wgt.zone.x + (wgt.zone.w / 2) + centerX = wgt.zone.x + wgt.zone.w - centerR + centerY = wgt.zone.y + (wgt.zone.h / 2) + else + centerR = wgt.zone.h - 20 + centerX = wgt.zone.x + wgt.zone.w - centerR + centerY = wgt.zone.y + wgt.zone.h - 20 + end - if isFull then - centerR = math.min(wgt.zone.h, wgt.zone.w) / 2 - --local centerX = wgt.zone.x + (wgt.zone.w / 2) - centerX = wgt.zone.x + wgt.zone.w - centerR - centerY = wgt.zone.y + (wgt.zone.h / 2) - else - centerR = wgt.zone.h - 20 - centerX = wgt.zone.x + wgt.zone.w - centerR - centerY = wgt.zone.y + wgt.zone.h - 20 - end + wgt.gauge1.drawGauge(centerX, centerY, centerR, isFull, percentageValue, percentageValueMin, percentageValueMax, value_fmt, value_fmt_min, value_fmt_max, w_name) + --lcd.drawText(wgt.zone.x, wgt.zone.y, value_fmt, FONT_38 + YELLOW) - wgt.gauge1.drawGauge(centerX, centerY, centerR, isFull, percentageValue, percentageValueMin, percentageValueMax, value_fmt, w_name) - --lcd.drawText(wgt.zone.x, wgt.zone.y, value_fmt, XXLSIZE + YELLOW) -end + -- display min max + if isFull == false then + lcd.drawText(wgt.zone.x, wgt.zone.y + 20, value_fmt, FONT_8 + YELLOW) + lcd.drawText(wgt.zone.x + 0, wgt.zone.y + 40, "Min: " .. value_fmt_min, FONT_6) + lcd.drawText(wgt.zone.x + 0, wgt.zone.y + 55, "Max: " .. value_fmt_max, FONT_6) + end + + if wgt.tools.isTelemetryAvailable() == false then + lcd.drawText(wgt.zone.x, wgt.zone.y + wgt.zone.h / 2, "Disconnected...", FONT_12 + WHITE + BLINK) + end +end local function refresh(wgt, event, touchState) - if (wgt == nil) then return end - if (wgt.options == nil) then return end - if (wgt.zone == nil) then return end - - --lcd.drawRectangle(wgt.zone.x, wgt.zone.y, wgt.zone.w, wgt.zone.h, BLACK) - - local ver, radio, maj, minor, rev, osname = getVersion() - --log("version: " .. ver) - if osname ~= "EdgeTX" then - local err = string.format("supported only on EdgeTX: ", osname) - log(err) - lcd.drawText(0, 0, err, SMLSIZE) - return - end - if maj == 2 and minor < 7 then - local err = string.format("NOT supported ver: %s", ver) - log(err) - lcd.drawText(0, 0, err, SMLSIZE) - return - end - - if (event ~= nil) then - -- full screen (app mode) - refresh_app_mode(wgt, event, touchState) - else - -- regular screen - refresh_widget(wgt) - end - - -- widget load (debugging) --- lcd.drawText(wgt.zone.x + 10, wgt.zone.y, string.format("load: %d%%", getUsage()), SMLSIZE + GREY) -- ??? + if (wgt == nil) then return end + if (wgt.options == nil) then return end + if (wgt.zone == nil) then return end + local sourceName = getSourceName(wgt.options.Source) + if (sourceName == nil) then + lcd.drawText(wgt.zone.x, wgt.zone.y + wgt.zone.h / 2, "No source selected...", FONT_12 + WHITE + BLINK) + return + end + + --lcd.drawRectangle(wgt.zone.x, wgt.zone.y, wgt.zone.w, wgt.zone.h, BLACK) + + local ver, radio, maj, minor, rev, osname = getVersion() + --log("version: " .. ver) + if osname ~= "EdgeTX" then + local err = string.format("supported only on EdgeTX: ", osname) + log(err) + lcd.drawText(0, 0, err, FONT_6) + return + end + if maj == 2 and minor < 7 then + local err = string.format("NOT supported ver: %s", ver) + log(err) + lcd.drawText(0, 0, err, FONT_6) + return + end + + if (event ~= nil) then + -- full screen (app mode) + refresh_app_mode(wgt, event, touchState) + else + -- regular screen + refresh_widget(wgt) + end + + -- widget load (debugging) + --lcd.drawText(wgt.zone.x + wgt.zone.w, wgt.zone.y, string.format("load: %d%%", getUsage()), FONT_6 + GREY + RIGHT) -- ??? end -return { name = "GaugeRotary", options = _options, create = create, update = update, refresh = refresh } +return { name = app_name, options = _options, create = create, update = update, refresh = refresh } From e18b090aae17d437d42240d617cca47284b2ec85 Mon Sep 17 00:00:00 2001 From: shmuely Date: Tue, 21 Jan 2025 00:50:12 +0200 Subject: [PATCH 3/3] feat(Timer2): update 480x320 widget --- sdcard/c480x320/WIDGETS/Timer2/main.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sdcard/c480x320/WIDGETS/Timer2/main.lua b/sdcard/c480x320/WIDGETS/Timer2/main.lua index c53f81f8..66e04efb 100644 --- a/sdcard/c480x320/WIDGETS/Timer2/main.lua +++ b/sdcard/c480x320/WIDGETS/Timer2/main.lua @@ -1,7 +1,7 @@ -- A Timer version that fill better the widget area -- Offer Shmuely --- Date: 2022 --- ver: 0.7 +-- Date: 2021-2023 +-- ver: 0.8 local options = { { "TextColor", COLOR, YELLOW }, @@ -121,7 +121,7 @@ local function refresh(wgt, event, touchState) -- calculate timer info local timerInfo = getTimerHeader(wgt, t1) - timer_info_w, timer_info_h = lcd.sizeText(timerInfo, SMLSIZE) + local timer_info_w, timer_info_h = lcd.sizeText(timerInfo, SMLSIZE) -- calculate timer time local time_str, isNegative = formatTime(wgt, t1) @@ -129,14 +129,15 @@ local function refresh(wgt, event, touchState) local zone_w = wgt.zone.w local zone_h = wgt.zone.h + local textColor if isNegative == true then textColor = RED else textColor = wgt.options.TextColor end - font_size_header = SMLSIZE - if (event ~= nil) then -- full screen + local font_size_header = SMLSIZE + if (event ~= nil) then -- app mode (full screen) font_size = XXLSIZE font_size_header = DBLSIZE zone_w = 460