Put ESP Screens in English and keep layouts when entity IDs change (0.2.40)

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>
This commit is contained in:
Max Gramser
2026-09-15 02:08:43 +02:00
parent f60eed3ff6
commit 3e959b3df9
87 changed files with 5049 additions and 11167 deletions
+17 -17
View File
@@ -8,8 +8,8 @@ import statistics
import time
from calibrate import write_file
TARGETS = [('linksboven', (24, 24)), ('rechtsboven', (455, 24)),
('rechtsonder', (455, 455)), ('linksonder', (24, 455)), ('midden', (240, 240))]
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])')
@@ -18,35 +18,35 @@ def screen_point(x, y, rotation=0):
if rotation == 90: return y, 479-x
if rotation == 180: return 479-x, 479-y
if rotation == 270: return 479-y, x
raise ValueError('Rotatie moet 0, 90, 180 of 270 zijn.')
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('Verwacht vijf meetpunten op een 480x480 GT911-scherm.')
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('Volg de vijf doelen; ieder doel vereist drie aparte tikken.')
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('Touchcoördinaten vallen buiten 480x480.')
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}: fout {error:.1f}px / spreiding {spread:.1f}px. Controleer rotatie en GT911-transform; geen ADC-fit gebruiken.')
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-poort; firmware moet het GT911-meetscherm tonen')
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='Controleer een bestaand meetbestand zonder USB')
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:
@@ -54,30 +54,30 @@ def main():
data=json.loads(args.input.read_text())
else:
import serial
if not args.port: raise ValueError('--port is nodig voor een fysieke meting.')
if args.output.exists(): raise ValueError('Meetbestand bestaat al; kies een andere naam.')
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}: druk Enter en tik daarna drie keer alleen dit kruisje, met loslaten ertussen. ')
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('Het geïsoleerde GT911-meetscherm is niet actief.')
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}: onvoldoende tikken ontvangen.')
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: alle vijf GT911-doelen geverifieerd; geen HA-acties uitgevoerd.')
print('PASS: all five GT911 targets verified; no HA actions performed.')
except (ValueError,OSError,TypeError,KeyError,EOFError) as exc:
parser.exit(1,f'Fout: {exc}\n')
parser.exit(1,f'Error: {exc}\n')
except KeyboardInterrupt:
parser.exit(130,'Meting afgebroken.\n')
parser.exit(130,'Measurement aborted.\n')
if __name__=='__main__': main()