2022-01-15 22:03:24 +00:00
|
|
|
# TouchScreen calibration
|
2022-10-23 17:47:04 +01:00
|
|
|
#
|
|
|
|
# rm DisplayCalibrate.tapp; zip -j -0 DisplayCalibrate.tapp ts_calibrate/*
|
|
|
|
#
|
2022-01-15 22:03:24 +00:00
|
|
|
var ts_calibrate = module("ts_calibrate")
|
|
|
|
|
|
|
|
ts_calibrate.init = def (m)
|
|
|
|
class TS_Calibrate
|
|
|
|
var l1, l2 # LVGL lines used to draw a cross
|
|
|
|
var p1, p2, p3, p4 # points needs to be kept in memory across calls
|
|
|
|
var pt_arr1, pt_arr2
|
|
|
|
var f20 # font Montserrat 20
|
|
|
|
var scr_orig # original screen
|
|
|
|
var scr_ts # screen used for calibration
|
|
|
|
var hres, vres
|
|
|
|
var instr_label # label for instructions
|
|
|
|
#
|
|
|
|
var raw_pts # 4x raw points for 4 corners
|
|
|
|
var raw_pts_weight # weight for current corner, used for an aveage measure
|
|
|
|
#
|
|
|
|
var state_closure # closure to call at next iteration
|
|
|
|
var state_corner # which corner are we now tracking
|
|
|
|
#
|
|
|
|
var m_line # display.ini parsed, array of 2 string (before and after :M line)
|
|
|
|
|
|
|
|
static msg_prefix = "Touch Screen calibration\n"
|
|
|
|
static msg_touch = "Press the center of\n"
|
|
|
|
static msg_corners = [ "upper left cross", "upper right cross", "lower left cross", "lower right cross" ]
|
|
|
|
static msg_ok = "Calibration successful\nRestarting in 5 seconds"
|
|
|
|
static msg_nok = "Calibration failed\nPlease try again"
|
|
|
|
|
|
|
|
static round = def (v) return int(v + 0.5) end
|
|
|
|
|
|
|
|
def init()
|
|
|
|
end
|
|
|
|
|
|
|
|
def start()
|
|
|
|
self.state_corner= -1 # no corner being tracked yet
|
|
|
|
self.m_line = nil
|
|
|
|
|
|
|
|
# check display.ini to see if touchscreen is present with ':M' line in display.ini
|
|
|
|
self.m_line = self.load_m_line()
|
|
|
|
if !self.m_line
|
|
|
|
tasmota.log("TS : Abort, no touchscreen ':M' line present in 'display.ini'", 2)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
lv.start()
|
|
|
|
|
|
|
|
# check if `DisplayRotate` is set to zero
|
|
|
|
var display_rotate_ret = tasmota.cmd("DisplayRotate")
|
|
|
|
if display_rotate_ret != nil && display_rotate_ret["DisplayRotate"] != 0
|
|
|
|
tasmota.log("TS : Calibration requires 'DisplayRotate 0'")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
self.raw_pts = [ lv.point(), lv.point(), lv.point(), lv.point() ]
|
|
|
|
|
|
|
|
self.scr_orig = lv.scr_act() # save the current screen to restore it later
|
|
|
|
self.f20 = lv.montserrat_font(20) # load embedded Montserrat 20
|
|
|
|
self.hres = lv.get_hor_res()
|
|
|
|
self.vres = lv.get_ver_res()
|
|
|
|
|
|
|
|
self.scr_ts = lv.obj(0) # create a new temporary screen for the calibration
|
|
|
|
lv.scr_load(self.scr_ts)
|
|
|
|
|
|
|
|
self.instr_label = lv.label(self.scr_ts)
|
|
|
|
self.instr_label.center()
|
|
|
|
if self.f20 != nil self.instr_label.set_style_text_font(self.f20, lv.PART_MAIN | lv.STATE_DEFAULT) end
|
|
|
|
self.instr_label.set_text(self.msg_prefix)
|
|
|
|
|
|
|
|
self.l1 = lv.line(self.scr_ts)
|
|
|
|
self.l2 = lv.line(self.scr_ts)
|
|
|
|
self.l1.set_style_line_width(2, lv.PART_MAIN | lv.STATE_DEFAULT)
|
|
|
|
self.l2.set_style_line_width(2, lv.PART_MAIN | lv.STATE_DEFAULT)
|
|
|
|
|
|
|
|
self.p1 = lv.point()
|
|
|
|
self.p2 = lv.point()
|
|
|
|
self.p3 = lv.point()
|
|
|
|
self.p4 = lv.point()
|
|
|
|
|
|
|
|
# register ourselves as driver
|
|
|
|
tasmota.add_driver(self)
|
|
|
|
|
|
|
|
# start calibrate in 2 seconds
|
|
|
|
tasmota.set_timer(2000, /-> self.do_next_corner())
|
|
|
|
end
|
|
|
|
|
|
|
|
def do_cross_n(n)
|
|
|
|
self.state_corner = n
|
|
|
|
self.raw_pts_weight = 0 # reset weight (for average of measures)
|
|
|
|
|
|
|
|
if n == 0 self.draw_cross(20,20,30)
|
|
|
|
elif n == 1 self.draw_cross(self.hres - 20, 20, 30)
|
|
|
|
elif n == 2 self.draw_cross(20, self.vres - 20, 30)
|
|
|
|
else self.draw_cross(self.hres - 20, self.vres - 20, 30)
|
|
|
|
end
|
|
|
|
# set message
|
|
|
|
self.instr_label.set_text(self.msg_prefix + self.msg_touch + self.msg_corners[n])
|
|
|
|
end
|
|
|
|
|
|
|
|
# remove and restore previous state
|
|
|
|
def del()
|
|
|
|
lv.scr_load(self.scr_orig) # restore previous screen
|
|
|
|
self.scr_ts.del() # delete all objects
|
|
|
|
tasmota.remove_driver(self)
|
|
|
|
end
|
|
|
|
|
|
|
|
# draw cross
|
2022-10-23 17:47:04 +01:00
|
|
|
def draw_cross(x, y, sz)
|
|
|
|
var sz2 = sz / 2
|
2022-01-15 22:03:24 +00:00
|
|
|
self.p1.x = x - sz2
|
|
|
|
self.p1.y = y
|
|
|
|
self.p2.x = x + sz2
|
|
|
|
self.p2.y = y
|
|
|
|
|
|
|
|
self.pt_arr1 = lv.lv_point_arr([self.p1, self.p2])
|
|
|
|
self.l1.set_points(self.pt_arr1, 2)
|
|
|
|
|
|
|
|
self.p3.x = x
|
|
|
|
self.p3.y = y - sz2
|
|
|
|
self.p4.x = x
|
|
|
|
self.p4.y = y + sz2
|
|
|
|
|
|
|
|
self.pt_arr2 = lv.lv_point_arr([self.p3, self.p4])
|
|
|
|
self.l2.set_points(self.pt_arr2, 2)
|
|
|
|
end
|
|
|
|
|
|
|
|
def every_50ms()
|
|
|
|
#
|
|
|
|
if self.state_closure self.state_closure() end
|
|
|
|
end
|
|
|
|
|
|
|
|
def track_touch_screen()
|
|
|
|
var tracking = lv.get_ts_calibration()
|
|
|
|
if tracking.state
|
|
|
|
# screen is pressed, compute an average of all previous measures
|
|
|
|
self.raw_pts[self.state_corner].x = (self.raw_pts[self.state_corner].x * self.raw_pts_weight + tracking.raw_x) / (self.raw_pts_weight + 1)
|
|
|
|
self.raw_pts[self.state_corner].y = (self.raw_pts[self.state_corner].y * self.raw_pts_weight + tracking.raw_y) / (self.raw_pts_weight + 1)
|
|
|
|
self.raw_pts_weight += 1 # we now have 1 more measure
|
|
|
|
else
|
|
|
|
# screen is not pressed anymore
|
|
|
|
if (self.raw_pts_weight >= 3)
|
|
|
|
self.state_closure = nil # stop tracking
|
|
|
|
tasmota.set_timer(0, /-> self.do_next_corner()) # defer to next corner
|
|
|
|
|
|
|
|
end # we need at least 3 succesful measures do consider complete
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def do_next_corner()
|
|
|
|
# start the measure of the next corner
|
|
|
|
self.state_corner += 1
|
|
|
|
if self.state_corner <= 3
|
|
|
|
self.do_cross_n(self.state_corner)
|
|
|
|
self.state_closure = /-> self.track_touch_screen()
|
|
|
|
else
|
|
|
|
# finished
|
|
|
|
self.l1.del()
|
|
|
|
self.l2.del()
|
|
|
|
self.finish()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# All values are computed and correct, log results and store to 'display.ini'
|
|
|
|
def finish()
|
|
|
|
# calibration is finished, do the housekeeping
|
|
|
|
var p0x = real(self.raw_pts[0].x)
|
|
|
|
var p0y = real(self.raw_pts[0].y)
|
|
|
|
var p1x = real(self.raw_pts[1].x)
|
|
|
|
var p1y = real(self.raw_pts[1].y)
|
|
|
|
var p2x = real(self.raw_pts[2].x)
|
|
|
|
var p2y = real(self.raw_pts[2].y)
|
|
|
|
var p3x = real(self.raw_pts[3].x)
|
|
|
|
var p3y = real(self.raw_pts[3].y)
|
2023-06-23 17:33:31 +01:00
|
|
|
tasmota.log(format("TS : Calibration (%i,%i) - (%i,%i) - (%i,%i) - (%i,%i)",
|
2022-01-15 22:03:24 +00:00
|
|
|
int(p0x), int(p0y), int(p1x), int(p1y), int(p2x), int(p2y), int(p3x), int(p3y))
|
|
|
|
, 2)
|
|
|
|
var m_line = self.calc_geometry(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, self.hres, self.vres, 20)
|
|
|
|
var ok = false
|
|
|
|
if m_line
|
|
|
|
ok = self.update_display_ini(m_line)
|
|
|
|
end
|
|
|
|
if ok
|
|
|
|
self.instr_label.set_text(self.msg_prefix + self.msg_ok)
|
|
|
|
else
|
|
|
|
self.instr_label.set_text(self.msg_prefix + self.msg_nok)
|
|
|
|
end
|
|
|
|
tasmota.set_timer(3000, /->self.cleanup(ok))
|
|
|
|
end
|
|
|
|
|
|
|
|
# cleanup display, remove any widgets created
|
|
|
|
def cleanup(restart)
|
|
|
|
self.del()
|
|
|
|
|
|
|
|
if (restart)
|
|
|
|
tasmota.cmd("Restart 1")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Find 'display.ini' file either in root folder or in autoconf file
|
|
|
|
# and check that it contains a line ':M*'
|
|
|
|
# Returns an array of 2 strings (before and after :M line)
|
|
|
|
# or 'nil' if not found
|
|
|
|
def load_m_line()
|
|
|
|
try
|
|
|
|
import re
|
|
|
|
import path
|
|
|
|
|
|
|
|
# try display.ini at root
|
|
|
|
var disp_ini
|
|
|
|
if path.exists("display.ini")
|
|
|
|
var disp_f = open("display.ini")
|
|
|
|
disp_ini = disp_f.read()
|
|
|
|
disp_f.close()
|
|
|
|
elif autoconf.get_current_module_path() && path.exists(autoconf.get_current_module_path() + "#display.ini")
|
|
|
|
var disp_f = open(autoconf.get_current_module_path() + "#display.ini")
|
|
|
|
disp_ini = disp_f.read()
|
|
|
|
disp_f.close()
|
|
|
|
else
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
# look for ":M" line
|
|
|
|
var sp = re.split(":M.*?\n", disp_ini)
|
|
|
|
if size(sp) == 2
|
|
|
|
return sp # found
|
|
|
|
end
|
|
|
|
except .. as e, m
|
|
|
|
tasmota.log("TS : Couldn't open 'display.ini': "+str(e)+" '"+str(m)+"'")
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
# try to update 'display.ini' if present in file-system
|
|
|
|
def update_display_ini(m_line)
|
|
|
|
try
|
|
|
|
|
|
|
|
# found
|
|
|
|
var disp_ini = self.m_line[0] + str(m_line) + "\n" + self.m_line[1]
|
|
|
|
|
|
|
|
# write back file
|
|
|
|
var disp_f = open("display.ini", "w")
|
|
|
|
disp_f.write(disp_ini)
|
|
|
|
disp_f.close()
|
|
|
|
tasmota.log("TS : Successfully updated 'display.ini'", 2)
|
|
|
|
return true
|
|
|
|
except .. as e, m
|
|
|
|
tasmota.log("TS : Error updating 'display.ini': "+str(e)+" '"+str(m)+"'")
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
def calc_geometry(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, hres, vres, padding)
|
|
|
|
import math
|
|
|
|
|
2023-06-23 17:33:31 +01:00
|
|
|
tasmota.log(format("TS : Geometry (%i,%i) (%i,%i) (%i,%i) (%i,%i) - %ix%i pad %i",
|
2022-01-15 22:03:24 +00:00
|
|
|
int(p0x), int(p0y),
|
|
|
|
int(p1x), int(p1y),
|
|
|
|
int(p2x), int(p2y),
|
|
|
|
int(p3x), int(p3y),
|
|
|
|
int(hres), int(vres), int(padding)
|
|
|
|
), 3)
|
|
|
|
|
|
|
|
var vec_01_x = p1x - p0x
|
|
|
|
var vec_01_y = p1y - p0y
|
|
|
|
var norm_01 = math.sqrt(vec_01_x * vec_01_x + vec_01_y * vec_01_y)
|
|
|
|
|
|
|
|
var vec_02_x = p2x - p0x
|
|
|
|
var vec_02_y = p2y - p0y
|
|
|
|
var norm_02 = math.sqrt(vec_01_x * vec_01_x + vec_01_y * vec_01_y)
|
|
|
|
|
|
|
|
var scalar_01_02 = vec_01_x * vec_02_x + vec_01_y * vec_02_y
|
|
|
|
var cos_th_01_02 = math.abs(scalar_01_02) / norm_01 / norm_02
|
|
|
|
|
|
|
|
tasmota.log("cos_th_01_02=" + str(cos_th_01_02), 4)
|
|
|
|
|
|
|
|
if (cos_th_01_02 > 0.05)
|
|
|
|
tasmota.log("TS : Wrong geometry - bad angle. Try again.", 2)
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
# Now check the center is valid
|
|
|
|
var center_03_x = (p0x + p3x) / 2
|
|
|
|
var center_03_y = (p0y + p3y) / 2
|
|
|
|
var center_12_x = (p1x + p2x) / 2
|
|
|
|
var center_12_y = (p1y + p2y) / 2
|
|
|
|
var norm_delta_centers = ((center_12_x - center_03_x) * (center_12_x - center_03_x) + (center_12_y - center_03_y) * (center_12_y - center_03_y)) / norm_01 / norm_02
|
|
|
|
tasmota.log("norm_delta_centers=" + str(norm_delta_centers), 4)
|
|
|
|
|
|
|
|
if (norm_delta_centers > 0.02)
|
|
|
|
tasmota.log("TS : Wrong geometry - bad center. Try again", 2)
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
var xmin = (p0x + p2x) / 2
|
|
|
|
var xmax = (p1x + p3x) / 2
|
|
|
|
var ymin = (p0y + p1y) / 2
|
|
|
|
var ymax = (p2y + p3y) / 2
|
|
|
|
tasmota.log("raw xmin=" + str(xmin) + " xmax=" + str(xmax) + " ymin=" + str(ymin) + " ymax=" + str(ymax), 4)
|
|
|
|
|
|
|
|
var range_x = xmax - xmin
|
|
|
|
var range_y = ymax - ymin
|
|
|
|
|
|
|
|
tasmota.log("range_x=" + str(range_x) + " range_y=" + str(range_y), 4)
|
|
|
|
var extend_x = (range_x / (hres - 2*padding) * hres - range_x) / 2
|
|
|
|
var extend_y = (range_y / (vres - 2*padding) * vres - range_y) / 2
|
|
|
|
|
|
|
|
xmin -= extend_x
|
|
|
|
xmax += extend_x
|
|
|
|
ymin -= extend_y
|
|
|
|
ymax += extend_y
|
|
|
|
tasmota.log("final xmin=" + str(xmin) + " xmax=" + str(xmax) + " ymin=" + str(ymin) + " ymax=" + str(ymax), 4)
|
|
|
|
|
2023-06-23 17:33:31 +01:00
|
|
|
var M_string = format(":M,%i,%i,%i,%i", int(xmin), int(xmax), int(ymin), int(ymax))
|
|
|
|
tasmota.log(format("TS : Add this to display.ini '%s'", M_string))
|
2022-01-15 22:03:24 +00:00
|
|
|
return M_string
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return TS_Calibrate()
|
|
|
|
end
|
|
|
|
|
|
|
|
return ts_calibrate
|