Expand Glitch operations hub, cabinet authoring, deployment, and RFID support
Operator hub - Introduce Escape Gallery branding and Glitch: the Dark Machine room views - Add cabinet illustrations, Pac-Man marquee/gameplay artwork, and waiting states - Add device feedback panels with RFID readings, input/output states, and stale status - Add logged operator hardware interventions with request deduplication Room and cabinet authoring - Add a visual custom-PCB connection editor with per-cabinet device inventory - Support device IDs, bindings, wiring notes, input polarity, and validation - Add physical-room label/dimension toggles and editable doorway annotations - Preserve doorway changes through undo, save, and revert - Expand cabinet profiles, room layout, and Glitch scenario resources Scenarios and gameplay - Add per-assignment game configuration and runtime feature validation - Add timed test wins and Frogger individual-home objectives - Add the three-cabinet timed/score/Frogger test scenario - Add Cabinet 1 Pac-Man RFID credits, credit sound, and CREDIT 0/1 display - Require physical Start after an accepted scan and prevent duplicate credits Pi hardware and deployment - Add GPIO feedback sampling and a separate multiplexed RFID helper - Validate reader pin ownership and input/output conflicts - Add SSH cabinet startup and Pi desktop-session service support - Add master packaging, site setup, SSH enrollment, and staged cabinet releases - Verify deployment manifests and support explicit activation/status/log commands Documentation and verification - Consolidate the root README around the current architecture and setup workflow - Add the complete master/Pi/RFID export and deployment walkthrough - Document network isolation, credentials, card identification, and rollback - Add PCB, room-editor, feedback, credit, deployment, and controller tests - Isolate protocol tests from designer-edited cabinet configurations Known limitations - RFID helper compilation and physical Pi commissioning remain unverified - Fleet provisioning does not yet configure RFID-only installations completely - Broader Pac-Man tests retain a ghost-capture assertion failure - Staged exports must be rebuilt and checked before deploying runtime 2.3
This commit is contained in:
1 parent
705ec31e0b
commit
c1b2195834
90 files changed
+4969
-456
No files matched your search
+32
-4
@@ -70,6 +70,10 @@ def plan(config_path, mode):
|
||||
url_host = '127.0.0.1' if bind == '0.0.0.0' else bind
|
||||
url = f'http://{url_host}:{port}'
|
||||
jobs.append(dict(name='controller', command=[sys.executable, '-u', str(script), '--bind', bind, '--port', str(port), '--database', str(database), '--catalog', str(catalog)], env=server_env, port=(bind, port), ready=url+'/', directory=database.parent))
|
||||
if value('controller', 'hub_config'):
|
||||
hub_path = path('controller', 'hub_config', '')
|
||||
require_file(hub_path)
|
||||
jobs[-1]['command'] += ['--hub-config', str(hub_path)]
|
||||
if mode in ('cabinet', 'all'):
|
||||
executable = path('cabinet', 'executable', '../../ArcadeEscape.exe' if os.name == 'nt' else '../../ArcadeEscape.arm64')
|
||||
require_file(executable)
|
||||
@@ -92,13 +96,21 @@ def plan(config_path, mode):
|
||||
if not sys.platform.startswith('linux'): raise ValueError('Real GPIO requires the Linux Pi; set gpio = false on Windows')
|
||||
if importlib.util.find_spec('gpiozero') is None or importlib.util.find_spec('lgpio') is None:
|
||||
raise ValueError('Install Pi dependencies first: sudo apt install python3-gpiozero python3-lgpio')
|
||||
pins = [int(p.strip()) for p in value('gpio', 'pins', '17').split(',')]
|
||||
if not pins or len(set(pins)) != len(pins) or any(p < 0 or p > 27 for p in pins): raise ValueError('GPIO pins must be unique BCM numbers 0..27')
|
||||
pins = [int(p.strip()) for p in value('gpio', 'pins', '17').split(',') if p.strip()]
|
||||
if len(set(pins)) != len(pins) or any(p < 0 or p > 27 for p in pins): raise ValueError('GPIO pins must be unique BCM numbers 0..27')
|
||||
script = ROOT / 'tools/pi_gpio/pi_gpio_bridge.py'
|
||||
require_file(script)
|
||||
ledger = path('gpio', 'ledger', '~/.local/state/arcade_gpio/requests.sqlite3')
|
||||
bridge_env = dict(env, GPIOZERO_PIN_FACTORY='lgpio')
|
||||
jobs.append(dict(name='gpio', command=[sys.executable, '-u', str(script), '--controller', value('gpio', 'controller', cabinet_id+'_gpio'), '--pins', ','.join(map(str,pins)), '--ledger', str(ledger)], env=bridge_env, port=('127.0.0.1', 8765), ready='http://127.0.0.1:8765/heartbeat', directory=ledger.parent))
|
||||
if value('gpio', 'devices'):
|
||||
devices_path = path('gpio', 'devices', '')
|
||||
require_file(devices_path)
|
||||
jobs[-1]['command'] += ['--devices', str(devices_path)]
|
||||
if value('gpio', 'rfid_helper'):
|
||||
helper_path = path('gpio', 'rfid_helper', '')
|
||||
require_file(helper_path)
|
||||
jobs[-1]['command'] += ['--rfid-helper', str(helper_path)]
|
||||
args = [str(executable), '--', '--managed-cabinet', '--cabinet='+cabinet_id]
|
||||
if real_gpio: args.append('--gpio=real')
|
||||
jobs.append(dict(name='cabinet', command=args, env=cabinet_env, port=None, ready=None, directory=executable.parent))
|
||||
@@ -127,7 +139,7 @@ def wait_ready(job, process):
|
||||
raise RuntimeError(f"{job['name']} did not become ready; check its log")
|
||||
|
||||
|
||||
def run(jobs, logs, open_browser=False):
|
||||
def run(jobs, logs, open_browser=False, remote_targets=None):
|
||||
check_ports(jobs)
|
||||
logs.mkdir(parents=True, exist_ok=True)
|
||||
children, files = [], []
|
||||
@@ -144,6 +156,9 @@ def run(jobs, logs, open_browser=False):
|
||||
if job['name'] == 'controller':
|
||||
print('Operator: '+job['ready'], flush=True)
|
||||
if open_browser: webbrowser.open(job['ready'])
|
||||
if remote_targets:
|
||||
from remote_cabinets import start_and_check
|
||||
start_and_check(remote_targets, job)
|
||||
print('Running. Ctrl+C stops this stack. Exiting the game also stops services started here.', flush=True)
|
||||
while True:
|
||||
for job, process in children:
|
||||
@@ -178,12 +193,25 @@ def main():
|
||||
try:
|
||||
print('Launcher configuration: '+str(Path(args.config).resolve()), flush=True)
|
||||
jobs, logs = plan(args.config, args.mode)
|
||||
from remote_cabinets import load_targets
|
||||
remote_targets = load_targets(args.config, args.mode)
|
||||
if remote_targets:
|
||||
controller = next(j for j in jobs if j['name'] == 'controller')
|
||||
catalog_path = controller['command'][controller['command'].index('--catalog')+1]
|
||||
known = json.loads(Path(catalog_path).read_text(encoding='utf-8'))['cabinets']
|
||||
tokens = json.loads(controller['env']['ARCADE_CABINET_TOKENS'])
|
||||
for target in remote_targets:
|
||||
if target['id'] not in known or target['id'] not in tokens:
|
||||
raise ValueError('Remote cabinet must be registered and have a controller token: '+target['id'])
|
||||
if any(j['name']=='cabinet' and '--cabinet='+target['id'] in j['command'] for j in jobs):
|
||||
raise ValueError('Cannot launch the same cabinet identity locally and remotely')
|
||||
if args.check:
|
||||
check_ports(jobs)
|
||||
print('Configuration and ports OK. Planned services: '+', '.join(j['name'] for j in jobs))
|
||||
print('Logs: '+str(logs))
|
||||
print('Remote start targets (not contacted by --check): '+', '.join(t['id'] for t in remote_targets))
|
||||
return 0
|
||||
return run(jobs, logs, args.browser)
|
||||
return run(jobs, logs, args.browser, remote_targets)
|
||||
except (ValueError, OSError, RuntimeError, configparser.Error) as exc:
|
||||
print('Startup failed: '+str(exc), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
Reference in new issue
Block a user