Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
"node/no-extraneous-require": "off",
"node/no-unsupported-features/node-builtins": "off",
"no-empty": "warn",
"no-mixed-spaces-and-tabs": "warn"
"no-mixed-spaces-and-tabs": "warn",
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0, "maxBOF": 0 }],
"no-trailing-spaces": "error",
"padded-blocks": ["error", "never"],
"eol-last": ["error", "always"]
}
}
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ logs/
dist/
release/
bin/
/release-builds
/logs/*.log
release-builds/
logs/*.log
dev/agents/
34 changes: 34 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Mini4wdChrono is a Mini4wd lap timer program with race management. It is aimed at Mini4wd clubs who want to host races.
It is a standalone program which is meant to be paired with a hardware lap timer.

## Your role
- You are a backend developer, expert in plain javascript and electron.
- Your job is to help upgrading parts of the program and to develop new features.

## Project knowledge
- **Tech Stack:** Electron with vanilla javascript. Plain html with Bulma for css.
- **Architecture:** IPC-based separation between main and renderer processes
- Main process handles all hardware communication (johnny-five, serialport v13, firmata)
- Renderer process handles UI and race logic
- Communication via Electron IPC (contextIsolation: false)

- **File Structure:**
- `index.html` - main frontend
- `window.js` - Electron main process entry point (handles IPC, hardware, file system)
- `preload.js` - Exposes IPC APIs to renderer via `window.electronAPI`
- `js/` – Application source code (renderer process)
- `js/main.js` – Renderer initialization and IPC event handlers
- `js/client.js` - Race handling logic and program orchestrator
- `js/chrono.js` - Lap timer logic with microsecond-precision timestamps (DO NOT change this file)
- `js/ui.js` - Frontend rendering logic
- `js/led_managers/` - LED abstraction layer (talks to main process via IPC)

## Boundaries
- Only change code in the `js/` folder, and other javascript files in the root directory.
- Do not change `js/chrono.js` which contains battle-tested lap timer logic.
- Do not change the `index.html` and css files.
- Hardware operations must go through IPC - never use johnny-five or serialport directly in renderer
- Timestamps for lap timing are captured in main process at sensor trigger for accuracy

## Lap timer hardware
Mini4wdChrono connects via usb to a physical lap timer. The lap timer looks like a bridge over the three lanes of a mini4wd track. It is powered by an arduino with firmata firmware, and has 3 light sensor to detect the cars passing under the lap timer. It also sports an rgb led strip for visual feedback and a buzzer for alerts.
3 changes: 0 additions & 3 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,10 @@
"label-best-speed-km": "Best speed km/h",
"label-custom-title": "Your team/club name",
"label-starting-tab": "Starting screen",
"label-led-type": "LED type",
"label-led-animation": "LED animation at race start (only for RGB strip)",
"button-led-animation-full": "Animation + countdown",
"button-led-animation-cd": "Countdown only",
"button-led-animation-off": "None",
"button-led-type-normal": "3 single LEDs",
"button-led-type-strip": "Multicolor LED strip",
"label-usb-port": "USB Port",
"label-direction": "Direction",
"label-reverse": "Invert left-right",
Expand Down
22 changes: 14 additions & 8 deletions i18n/i18n.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
'use strict';

const { app } = require('electron').remote;
const fs = require('fs');
const path = require('path');
let loadedLanguage;

module.exports = i18n;

function i18n() {
const tnpath = path.join(__dirname, app.getLocale().substring(0, 2) + '.json');
// tnpath = path.join(__dirname, 'it.json'); // uncomment this line to force italian language
if (fs.existsSync(tnpath)) {
loadedLanguage = JSON.parse(fs.readFileSync(tnpath), 'utf8');
}
else {
loadedLanguage = JSON.parse(fs.readFileSync(path.join(__dirname, 'en.json'), 'utf8'));
// Load English by default, we'll try to get the proper locale asynchronously
loadedLanguage = JSON.parse(fs.readFileSync(path.join(__dirname, 'en.json'), 'utf8'));

// Try to load the correct locale asynchronously
if (window.electronAPI && window.electronAPI.getAppLocale) {
window.electronAPI.getAppLocale().then(locale => {
const tnpath = path.join(__dirname, locale.substring(0, 2) + '.json');
// tnpath = path.join(__dirname, 'it.json'); // uncomment this line to force italian language
if (fs.existsSync(tnpath)) {
loadedLanguage = JSON.parse(fs.readFileSync(tnpath), 'utf8');
}
}).catch(err => {
console.warn('Could not load locale, using English:', err);
});
}
}

Expand Down
3 changes: 0 additions & 3 deletions i18n/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@
"button-led-animation-full": "Animazione + conto alla rovescia",
"button-led-animation-cd": "Solo conto alla rovescia",
"button-led-animation-off": "Nessuna",
"label-led-type": "Tipo di LED",
"button-led-type-normal": "3 LED singoli",
"button-led-type-strip": "Striscia LED multicolore",
"label-usb-port": "Porta USB",
"label-direction": "Senso di marcia",
"label-reverse": "Inverti destra-sinistra",
Expand Down
19 changes: 7 additions & 12 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -494,18 +494,6 @@
</p>
</div>

<div class="field">
<label class="label tn" data-tn="label-led-type">LED type</label>
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="buttons has-addons">
<span id="js-led-type-0" class="button js-led-type tn" data-tn="button-led-type-normal" data-led-type="0">3 LEDs</span>
<span id="js-led-type-1" class="button js-led-type tn" data-tn="button-led-type-strip" data-led-type="1">LED strip</span>
</div>
</div>
</div>
</div>

<div class="field">
<label class="label tn" data-tn="label-led-animation">LED animation</label>
<div class="field is-grouped is-grouped-multiline">
Expand Down Expand Up @@ -681,6 +669,13 @@

</body>

<script>
// Load jQuery and Underscore before main.js loads using nodeRequire from preload
window.$ = window.jQuery = window.nodeRequire('jquery');
window._ = window.nodeRequire('underscore');
// Make require available as alias to nodeRequire for the modules
window.require = window.nodeRequire;
</script>
<script src="js/main.js"></script>

</html>
14 changes: 8 additions & 6 deletions js/chrono.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@ const init = (track, playerIds, cars) => {
};

// method called when a sensor receives a signal
const addLap = (lane) => {
// current time in milliseconds
const timestamp = new Date().getTime();
const addLap = (lane, timestamp) => {
// Use provided timestamp (captured at hardware level) or fallback to current time
if (!timestamp) {
timestamp = new Date().getTime();
}

console.log(`======= got signal for lane ${lane} at time ${timestamp}`);
console.log(`${timestamp} sensor triggered for lane ${lane}`);
// console.log(JSON.stringify(rCars, null, 2));

// find all cars that may have passes under this lane sensor
Expand All @@ -92,11 +94,11 @@ const addLap = (lane) => {

// false sensor read
if (rTempCar === null) {
console.log(`error: no valid car for signal on lane ${lane}`);
console.error(`${timestamp} no valid car found`);
return;
}
else {
console.log(`ok: valid car ${rTempCar.startLane}`);
console.log(`${timestamp} valid car found (start lane ${rTempCar.startLane})`);
}

// handle the correct car
Expand Down
Loading