3e959b3df9
Everything a user sees or reads is now English: both boards' labels, status replies and logs, the ESP Screens editor and Alerts cheatsheet, and the README and guides (docs/TILES.md, CALIBRATING.md, ACCEPTANCE.md and TROUBLESHOOTING.md replace the Dutch file names). Labels follow Home Assistant's own English names (Heat/Cool modes, Heating/Cooling actions, Away, Returning to dock, weather conditions), dates use English abbreviations and the top bar writes numbers the English way. Docs use HA 2026.9's menu names (Settings -> Apps, App store, Settings -> Tools). Unused legacy files (home-like.yaml, buttons.yaml, a.out) are removed. Firmware 0.2.34 names its entities in English. Home Assistant registers a renamed ESPHome entity under a new entity ID, so the inbox text.<screen>_tegelinstellingen becomes text.<screen>_tile_settings and the layout stored under the old ID would be lost. The manager now recognises the screen by its device, during a run or after a restart, and moves its layout and update history along; the Update button and the nightly round follow the screen through the rename. Screens that still run 0.2.33 or older keep working: both languages are matched. Main is the only release branch from now on. Tested on a Home Assistant Yellow (HA 2026.9.1) with both bench boards: see docs/TEST_RESULTS_0240.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
84 lines
4.1 KiB
Python
84 lines
4.1 KiB
Python
"""Guided USB pixel verification for GT911, without resistive calibration or HA actions."""
|
|
import argparse
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
import re
|
|
import statistics
|
|
import time
|
|
from calibrate import write_file
|
|
|
|
TARGETS = [('top-left', (24, 24)), ('top-right', (455, 24)),
|
|
('bottom-right', (455, 455)), ('bottom-left', (24, 455)), ('center', (240, 240))]
|
|
PATTERN = re.compile(r'GT911 press x=(\d+) y=(\d+) test=([01])')
|
|
|
|
|
|
def screen_point(x, y, rotation=0):
|
|
if rotation == 0: return x, y
|
|
if rotation == 90: return y, 479-x
|
|
if rotation == 180: return 479-x, 479-y
|
|
if rotation == 270: return 479-y, x
|
|
raise ValueError('Rotation must be 0, 90, 180, or 270.')
|
|
|
|
|
|
def verify(data):
|
|
if data.get('screen') != [480,480] or len(data.get('points',[])) != 5:
|
|
raise ValueError('Expected five measurement points on a 480x480 GT911 screen.')
|
|
result=[]
|
|
for p,(name,target) in zip(data['points'],TARGETS):
|
|
if p.get('name') != name or len(p.get('samples',[])) != 3:
|
|
raise ValueError('Follow the five targets; each target requires three separate taps.')
|
|
samples=[]
|
|
for xy in p['samples']:
|
|
if len(xy)!=2 or any(type(v) is not int or not 0<=v<480 for v in xy):
|
|
raise ValueError('Touch coordinates fall outside 480x480.')
|
|
samples.append(screen_point(*xy, data.get('rotation',0)))
|
|
center=tuple(statistics.median(x[i] for x in samples) for i in (0,1))
|
|
error=math.dist(center,target)
|
|
spread=max(math.dist(x,center) for x in samples)
|
|
result.append(dict(name=name,error_px=round(error,1),spread_px=round(spread,1)))
|
|
if error>16 or spread>18:
|
|
raise ValueError(f'{name}: error {error:.1f}px / spread {spread:.1f}px. Check rotation and the GT911 transform; do not use an ADC fit.')
|
|
return result
|
|
|
|
|
|
def main():
|
|
parser=argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--port', help='USB port; firmware must show the GT911 measurement screen')
|
|
parser.add_argument('--output',type=Path,default=Path('measurements-guition.json'))
|
|
parser.add_argument('--input',type=Path,help='Check an existing measurement file without USB')
|
|
parser.add_argument('--rotation',type=int,choices=(0,90,180,270),default=0)
|
|
args=parser.parse_args()
|
|
try:
|
|
if args.input:
|
|
data=json.loads(args.input.read_text())
|
|
else:
|
|
import serial
|
|
if not args.port: raise ValueError('--port is required for a physical measurement.')
|
|
if args.output.exists(): raise ValueError('Measurement file already exists; choose a different name.')
|
|
data=dict(screen=[480,480],rotation=args.rotation,points=[])
|
|
with serial.Serial(args.port,115200,timeout=.2) as port:
|
|
for name,xy in TARGETS:
|
|
input(f'{name} {xy}: press Enter, then tap only this crosshair three times, releasing in between. ')
|
|
port.reset_input_buffer()
|
|
samples=[]; deadline=time.monotonic()+90; last=0
|
|
while len(samples)<3 and time.monotonic()<deadline:
|
|
m=PATTERN.search(port.readline().decode(errors='replace'))
|
|
if not m: continue
|
|
if m[3]!='1': raise ValueError('The isolated GT911 measurement screen is not active.')
|
|
if time.monotonic()-last < .4:continue
|
|
samples.append([int(m[1]),int(m[2])]);last=time.monotonic()
|
|
print(f' {len(samples)}/3',flush=True)
|
|
if len(samples)!=3:raise ValueError(f'{name}: not enough taps received.')
|
|
data['points'].append(dict(name=name,samples=samples))
|
|
write_file(args.output,json.dumps(data,indent=2)+'\n')
|
|
for row in verify(data):print(row)
|
|
print('PASS: all five GT911 targets verified; no HA actions performed.')
|
|
except (ValueError,OSError,TypeError,KeyError,EOFError) as exc:
|
|
parser.exit(1,f'Error: {exc}\n')
|
|
except KeyboardInterrupt:
|
|
parser.exit(130,'Measurement aborted.\n')
|
|
|
|
|
|
if __name__=='__main__': main()
|