Repository navigation
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 37 seconds Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (6)
📒 Files selected for processing (24)
WalkthroughBarRobot v3 replaces the legacy Flask application with a TypeScript HTTP service, C motion daemon, Unix-socket protocol, validated JSON state, job orchestration, static browser UI, systemd deployment, and CI coverage for application and motion builds. ChangesBarRobot v3 platform
Sequence Diagram(s)sequenceDiagram
participant Browser
participant HTTPServer
participant BarRobotService
participant MotionSocketClient
participant MotionDaemon
Browser->>HTTPServer: Submit drink job
HTTPServer->>BarRobotService: Create and queue plan
BarRobotService->>MotionSocketClient: Move and dispense
MotionSocketClient->>MotionDaemon: Unix-socket command
MotionDaemon-->>MotionSocketClient: Motion response
MotionSocketClient-->>BarRobotService: Typed result
BarRobotService-->>HTTPServer: Job status
HTTPServer-->>Browser: JSON response
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (5)
web/app.js (1)
203-247: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExtract shared default constants for
pressDurationMsandreleaseDurationMs.These values are hardcoded identically in both
addInventory(lines 212–213) andsaveInventory(lines 243–244). If defaults change, both must be updated in sync.♻️ Proposed refactor
+const DEFAULT_PRESS_DURATION_MS = 600; +const DEFAULT_RELEASE_DURATION_MS = 200; + function addInventory(mode) { state.inventory.push({ ... - pressDurationMs: 600, - releaseDurationMs: 200, + pressDurationMs: DEFAULT_PRESS_DURATION_MS, + releaseDurationMs: DEFAULT_RELEASE_DURATION_MS, }); ... } async function saveInventory() { ... return { ... - pressDurationMs: 600, - releaseDurationMs: 200, + pressDurationMs: DEFAULT_PRESS_DURATION_MS, + releaseDurationMs: DEFAULT_RELEASE_DURATION_MS, }; ... }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/app.js` around lines 203 - 247, Extract shared constants for the default press and release durations, then replace the duplicated 600 and 200 literals in both addInventory and saveInventory with those constants. Keep the existing values and behavior unchanged.src/adapters/cocktaildb-source.ts (1)
36-43: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider
Promise.allSettledfor resilient free-tier sync.
fetchFreefires 26 parallel requests to the CocktailDB free API. A single rate-limited response (HTTP 429) or transient failure causesPromise.allto reject, failing the entire sync. UsingPromise.allSettledand filtering fulfilled results would allow partial sync completion, letting the operator retry only the failed letters.♻️ Proposed refactor for resilient free-tier fetching
private async fetchFree(): Promise<CocktailDbDrink[]> { - const batches = await Promise.all( + const results = await Promise.allSettled( Array.from("abcdefghijklmnopqrstuvwxyz").map(async (letter) => fetchJson(`https://www.thecocktaildb.com/api/json/v1/1/search.php?f=${letter}`), ), ); - return batches.flatMap((batch) => batch.drinks ?? []); + return results + .filter((result): result is PromiseFulfilledResult<CocktailDbResponse> => + result.status === "fulfilled") + .flatMap((result) => result.value.drinks ?? []); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/adapters/cocktaildb-source.ts` around lines 36 - 43, Update fetchFree to use Promise.allSettled for the per-letter CocktailDB requests, then retain only fulfilled results before flattening their drinks arrays. Preserve the existing letter iteration and empty-drinks fallback while allowing individual rate-limit or transient failures to be skipped.motiond/src/server.c (1)
121-125: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winCall
motion_destroyandgpio_linux_destroyon shutdown.The
(void)motion; (void)gpio;suppressions indicate these are intentionally not cleaned up. Whilemotion_stop(called by the signal thread) sets safe outputs and the kernel releases GPIO on process exit,gpio_linux_destroyis documented to "restore safe output values and release the GPIO line request" andmotion_destroydestroys the mutex. Explicit cleanup ensures proper resource release and safe GPIO teardown.♻️ Proposed fix
pthread_join(signal_thread, NULL); unlink(config.socket_path); - (void)motion; - (void)gpio; + motion_destroy(motion); + gpio_linux_destroy(gpio); return EXIT_SUCCESS;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@motiond/src/server.c` around lines 121 - 125, Replace the unused-variable suppressions in the shutdown path after pthread_join with calls to motion_destroy for motion and gpio_linux_destroy for gpio, before unlinking the socket and returning. Preserve the existing shutdown ordering so the signal thread has finished before destroying these resources.motiond/tests/test_motion.c (1)
63-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider adding tests for disarm, reset, invalid slot, and BUSY rejection.
The current tests cover startup, arm, full revolution, tie-breaking, dispense, and stop — the most critical safety paths. Missing cases include:
motion_disarmoutput behavior,motion_resetfault recovery, invalid slot rejection (MOTION_INVALID), and concurrent command rejection (MOTION_BUSY). Adding these would strengthen confidence in the state machine's edge cases.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@motiond/tests/test_motion.c` around lines 63 - 128, Add focused tests alongside the existing motion tests for the missing state-machine cases: verify motion_disarm’s return value and GPIO output behavior, confirm motion_reset recovers from a latched fault, assert invalid slot commands return MOTION_INVALID without changing state, and exercise an in-progress command to ensure concurrent commands return MOTION_BUSY. Register each new test in main, reusing the existing fake_gpio and motion lifecycle patterns.src/adapters/json-state-repository.ts (1)
38-56: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueTemp file leak on write failure.
If
file.sync()orwriteFilethrows, thefinallyblock closes the handle but never unlinkstemporary, leaving orphaned.tmpfiles in the state directory. Over time a flaky disk could accumulate stale temp files. Consider cleaning up in acatch/finally.♻️ Optional cleanup on failure
private async writeAtomic(state: StateDocument): Promise<void> { const directory = dirname(this.path); const temporary = `${this.path}.${process.pid}.${Date.now()}.tmp`; await mkdir(directory, { recursive: true }); const file = await open(temporary, "wx", 0o600); try { await file.writeFile(`${JSON.stringify(state, null, 2)}\n`, "utf8"); await file.sync(); } catch (writeError) { await file.close().catch(() => undefined); await unlink(temporary).catch(() => undefined); throw writeError; } await file.close(); await rename(temporary, this.path); const directoryHandle = await open(directory, "r"); try { await directoryHandle.sync(); } finally { await directoryHandle.close(); } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/adapters/json-state-repository.ts` around lines 38 - 56, Update writeAtomic to remove the temporary file when writeFile or file.sync fails, while still closing the file handle and preserving the existing rename path on success. Use the existing temporary variable and ensure cleanup errors do not replace the original write failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@deploy/barrobot.service`:
- Line 16: Update the BARROBOT_HOST setting in the service configuration to bind
the actuator API only to loopback, preserving local touchscreen access without
exposing unauthenticated control endpoints on every network interface.
In `@motiond/src/server.c`:
- Around line 98-121: Ensure the accept loop’s non-EINTR error path also
terminates the signal thread before joining it: use pthread_kill with the
already-blocked SIGTERM signal to wake sigwait, then proceed to
pthread_join(signal_thread, NULL). Preserve the existing EINTR retry and normal
shutdown behavior.
In `@scripts/install.sh`:
- Around line 26-28: Update the service activation commands in
scripts/install.sh to separate enabling from restarting for
barrobot-motion.service and barrobot.service. Replace the --now behavior with
explicit systemctl enable followed by systemctl restart so newly installed
binaries are activated on both initial installation and upgrades.
In `@src/application/barrobot-service.ts`:
- Around line 221-270: Move the initial persist call in runJob into the try
block so activeJobId is always cleared by finally when persistence fails. Ensure
the failure-handling persist in runJob’s catch cannot throw an unhandled
rejection, and add rejection handling to pump so asynchronous job-processing
failures are contained. Preserve the existing job failure status updates and
cleanup behavior.
In `@src/http/server.ts`:
- Around line 39-61: Update the catch fallback in the /api/status handler to
derive queuedJobs and activeJob from the existing state snapshot, matching the
successful return branch. Preserve the offline motion response, but do not
replace snapshot-derived job information with zero or null when service.status()
fails.
In `@web/app.js`:
- Around line 169-184: Update the data-mode attribute interpolation in
renderInventory to pass item.mode through escapeAttribute, matching the escaping
used for other user-controlled attribute values while preserving the existing
mode selection behavior.
In `@web/index.html`:
- Around line 135-149: The deployed UI must not present motionSocket,
listenHost, and listenPort as immediately effective settings. Remove or disable
these inputs in web/index.html, or clearly mark them as requiring a systemd
service-unit change and service restart; preserve the maxDoseErrorPercent field.
---
Nitpick comments:
In `@motiond/src/server.c`:
- Around line 121-125: Replace the unused-variable suppressions in the shutdown
path after pthread_join with calls to motion_destroy for motion and
gpio_linux_destroy for gpio, before unlinking the socket and returning. Preserve
the existing shutdown ordering so the signal thread has finished before
destroying these resources.
In `@motiond/tests/test_motion.c`:
- Around line 63-128: Add focused tests alongside the existing motion tests for
the missing state-machine cases: verify motion_disarm’s return value and GPIO
output behavior, confirm motion_reset recovers from a latched fault, assert
invalid slot commands return MOTION_INVALID without changing state, and exercise
an in-progress command to ensure concurrent commands return MOTION_BUSY.
Register each new test in main, reusing the existing fake_gpio and motion
lifecycle patterns.
In `@src/adapters/cocktaildb-source.ts`:
- Around line 36-43: Update fetchFree to use Promise.allSettled for the
per-letter CocktailDB requests, then retain only fulfilled results before
flattening their drinks arrays. Preserve the existing letter iteration and
empty-drinks fallback while allowing individual rate-limit or transient failures
to be skipped.
In `@src/adapters/json-state-repository.ts`:
- Around line 38-56: Update writeAtomic to remove the temporary file when
writeFile or file.sync fails, while still closing the file handle and preserving
the existing rename path on success. Use the existing temporary variable and
ensure cleanup errors do not replace the original write failure.
In `@web/app.js`:
- Around line 203-247: Extract shared constants for the default press and
release durations, then replace the duplicated 600 and 200 literals in both
addInventory and saveInventory with those constants. Keep the existing values
and behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b179f12e-80c4-4bab-8888-939cd34029f9
⛔ Files ignored due to path filters (3)
classes.dotis excluded by!**/*.dotpackage-lock.jsonis excluded by!**/package-lock.jsonstatic/placeholder.pngis excluded by!**/*.png
📒 Files selected for processing (57)
.github/workflows/ci.yml.gitignore.prettierignore.prettierrc.jsonMakefileREADME.mdapp.pybottle_config.jsondeploy/barrobot-motion.servicedeploy/barrobot.servicedocs/design-v3.mdeslint.config.jshardware.pymotiond/Makefilemotiond/include/gpio_linux.hmotiond/include/motion.hmotiond/src/gpio_linux.cmotiond/src/motion.cmotiond/src/server.cmotiond/tests/test_motion.cpackage.jsonscripts/install.shsrc/adapters/cocktaildb-source.tssrc/adapters/json-state-repository.tssrc/adapters/motion-socket-client.tssrc/adapters/state-validation.tssrc/application/barrobot-service.tssrc/application/ports.tssrc/domain/errors.tssrc/domain/measure.tssrc/domain/model.tssrc/domain/normalize.tssrc/domain/planner.tssrc/domain/seed.tssrc/http/server.tssrc/main.tsstatic/style.csssystemd-barrobot.servicetemplates/base.htmltemplates/configure.htmltemplates/drink.htmltemplates/menu.htmltemplates/motor_controls.htmltemplates/suggestions.htmltemplates/suggestions2.htmltests/adapters.test.tstests/helpers.tstests/http.test.tstests/measure.test.tstests/planner.test.tstests/service.test.tstsconfig.jsontsconfig.test.jsonversion.txtweb/app.jsweb/index.htmlweb/styles.css
💤 Files with no reviewable changes (13)
- hardware.py
- templates/configure.html
- templates/suggestions.html
- static/style.css
- templates/suggestions2.html
- templates/menu.html
- templates/base.html
- bottle_config.json
- templates/drink.html
- systemd-barrobot.service
- version.txt
- templates/motor_controls.html
- app.py
| Environment=NODE_ENV=production | ||
| Environment=BARROBOT_STATE_PATH=/var/lib/barrobot/state.json | ||
| Environment=BARROBOT_MOTION_SOCKET=/run/barrobot/motion.sock | ||
| Environment=BARROBOT_HOST=0.0.0.0 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not expose the actuator API unauthenticated.
Line 16 binds the service to every interface, while the API permits arm, move, dispense-job, and stop actions without authentication. The origin hook is not network access control. Bind to loopback for the local touchscreen, or put remote access behind authenticated TLS and firewall rules.
Proposed safe default
-Environment=BARROBOT_HOST=0.0.0.0
+Environment=BARROBOT_HOST=127.0.0.1📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Environment=BARROBOT_HOST=0.0.0.0 | |
| Environment=BARROBOT_HOST=127.0.0.1 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@deploy/barrobot.service` at line 16, Update the BARROBOT_HOST setting in the
service configuration to bind the actuator API only to loopback, preserving
local touchscreen access without exposing unauthenticated control endpoints on
every network interface.
| for (;;) { | ||
| const int client_fd = accept4(server_fd, NULL, NULL, SOCK_CLOEXEC); | ||
| if (client_fd < 0) { | ||
| if (errno == EINTR) { | ||
| continue; | ||
| } | ||
| break; | ||
| } | ||
| struct client_context *context = malloc(sizeof(*context)); | ||
| if (context == NULL) { | ||
| close(client_fd); | ||
| continue; | ||
| } | ||
| context->client_fd = client_fd; | ||
| context->motion = motion; | ||
| pthread_t thread; | ||
| if (pthread_create(&thread, NULL, handle_client, context) != 0) { | ||
| close(client_fd); | ||
| free(context); | ||
| continue; | ||
| } | ||
| pthread_detach(thread); | ||
| } | ||
| pthread_join(signal_thread, NULL); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Potential deadlock if accept4 fails for a non-signal reason.
If accept4 returns an error other than EINTR that is not caused by the signal thread's shutdown (e.g., EMFILE from too many open file descriptors), the main thread breaks out of the loop and calls pthread_join(signal_thread, NULL). Since the signal thread is still blocked in sigwait, this join blocks indefinitely. Consider sending a signal to the signal thread or using pthread_cancel before the join.
🛡️ Proposed fix
}
- pthread_join(signal_thread, NULL);
+ pthread_kill(signal_thread, SIGTERM);
+ pthread_join(signal_thread, NULL);
unlink(config.socket_path);Since SIGTERM is already in the blocked signal set and sigwait will catch it, pthread_kill unblocks the signal thread cleanly.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (;;) { | |
| const int client_fd = accept4(server_fd, NULL, NULL, SOCK_CLOEXEC); | |
| if (client_fd < 0) { | |
| if (errno == EINTR) { | |
| continue; | |
| } | |
| break; | |
| } | |
| struct client_context *context = malloc(sizeof(*context)); | |
| if (context == NULL) { | |
| close(client_fd); | |
| continue; | |
| } | |
| context->client_fd = client_fd; | |
| context->motion = motion; | |
| pthread_t thread; | |
| if (pthread_create(&thread, NULL, handle_client, context) != 0) { | |
| close(client_fd); | |
| free(context); | |
| continue; | |
| } | |
| pthread_detach(thread); | |
| } | |
| pthread_join(signal_thread, NULL); | |
| for (;;) { | |
| const int client_fd = accept4(server_fd, NULL, NULL, SOCK_CLOEXEC); | |
| if (client_fd < 0) { | |
| if (errno == EINTR) { | |
| continue; | |
| } | |
| break; | |
| } | |
| struct client_context *context = malloc(sizeof(*context)); | |
| if (context == NULL) { | |
| close(client_fd); | |
| continue; | |
| } | |
| context->client_fd = client_fd; | |
| context->motion = motion; | |
| pthread_t thread; | |
| if (pthread_create(&thread, NULL, handle_client, context) != 0) { | |
| close(client_fd); | |
| free(context); | |
| continue; | |
| } | |
| pthread_detach(thread); | |
| } | |
| pthread_kill(signal_thread, SIGTERM); | |
| pthread_join(signal_thread, NULL); | |
| unlink(config.socket_path); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@motiond/src/server.c` around lines 98 - 121, Ensure the accept loop’s
non-EINTR error path also terminates the signal thread before joining it: use
pthread_kill with the already-blocked SIGTERM signal to wake sigwait, then
proceed to pthread_join(signal_thread, NULL). Preserve the existing EINTR retry
and normal shutdown behavior.
| systemctl daemon-reload | ||
| systemctl enable --now barrobot-motion.service barrobot.service | ||
| systemctl --no-pager --full status barrobot-motion.service barrobot.service |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Use restart instead of --now to pick up new binaries on upgrade.
systemctl enable --now starts services on first install but does not restart already-running services. On a re-run (upgrade), the new binary is on disk while the old process keeps running. Separating enable from restart ensures the new binary is activated on both first install and upgrade.
🔧 Proposed fix
systemctl daemon-reload
-systemctl enable --now barrobot-motion.service barrobot.service
+systemctl enable barrobot-motion.service barrobot.service
+systemctl restart barrobot-motion.service barrobot.service
systemctl --no-pager --full status barrobot-motion.service barrobot.service📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| systemctl daemon-reload | |
| systemctl enable --now barrobot-motion.service barrobot.service | |
| systemctl --no-pager --full status barrobot-motion.service barrobot.service | |
| systemctl daemon-reload | |
| systemctl enable barrobot-motion.service barrobot.service | |
| systemctl restart barrobot-motion.service barrobot.service | |
| systemctl --no-pager --full status barrobot-motion.service barrobot.service |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/install.sh` around lines 26 - 28, Update the service activation
commands in scripts/install.sh to separate enabling from restarting for
barrobot-motion.service and barrobot.service. Replace the --now behavior with
explicit systemctl enable followed by systemctl restart so newly installed
binaries are activated on both initial installation and upgrades.
| server.get("/api/status", async () => { | ||
| const state = service.snapshot(); | ||
| try { | ||
| return { | ||
| motion: await service.status(), | ||
| queuedJobs: state.jobs.filter((job) => job.status === "queued").length, | ||
| activeJob: | ||
| state.jobs.find((job) => job.status === "running" || job.status === "waiting_manual") ?? | ||
| null, | ||
| }; | ||
| } catch (error) { | ||
| return { | ||
| motion: { | ||
| state: "offline", | ||
| armed: false, | ||
| position: null, | ||
| realtime: false, | ||
| error: error instanceof Error ? error.message : "Motion service offline", | ||
| }, | ||
| queuedJobs: 0, | ||
| activeJob: null, | ||
| }; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Preserve job state when motion status is unavailable.
If service.status() fails, the fallback reports no queued or active job even when the snapshot already contains one. A temporary daemon outage can therefore hide a waiting-manual drink and its cancel/continue controls. Return the snapshot-derived queue and active job in both branches.
Proposed fix
server.get("/api/status", async () => {
const state = service.snapshot();
+ const queuedJobs = state.jobs.filter((job) => job.status === "queued").length;
+ const activeJob =
+ state.jobs.find((job) => job.status === "running" || job.status === "waiting_manual") ??
+ null;
try {
return {
motion: await service.status(),
- queuedJobs: state.jobs.filter((job) => job.status === "queued").length,
- activeJob:
- state.jobs.find((job) => job.status === "running" || job.status === "waiting_manual") ??
- null,
+ queuedJobs,
+ activeJob,
};
} catch (error) {
return {
motion: {
state: "offline",
armed: false,
position: null,
realtime: false,
error: error instanceof Error ? error.message : "Motion service offline",
},
- queuedJobs: 0,
- activeJob: null,
+ queuedJobs,
+ activeJob,
};
}
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| server.get("/api/status", async () => { | |
| const state = service.snapshot(); | |
| try { | |
| return { | |
| motion: await service.status(), | |
| queuedJobs: state.jobs.filter((job) => job.status === "queued").length, | |
| activeJob: | |
| state.jobs.find((job) => job.status === "running" || job.status === "waiting_manual") ?? | |
| null, | |
| }; | |
| } catch (error) { | |
| return { | |
| motion: { | |
| state: "offline", | |
| armed: false, | |
| position: null, | |
| realtime: false, | |
| error: error instanceof Error ? error.message : "Motion service offline", | |
| }, | |
| queuedJobs: 0, | |
| activeJob: null, | |
| }; | |
| } | |
| server.get("/api/status", async () => { | |
| const state = service.snapshot(); | |
| const queuedJobs = state.jobs.filter((job) => job.status === "queued").length; | |
| const activeJob = | |
| state.jobs.find((job) => job.status === "running" || job.status === "waiting_manual") ?? | |
| null; | |
| try { | |
| return { | |
| motion: await service.status(), | |
| queuedJobs, | |
| activeJob, | |
| }; | |
| } catch (error) { | |
| return { | |
| motion: { | |
| state: "offline", | |
| armed: false, | |
| position: null, | |
| realtime: false, | |
| error: error instanceof Error ? error.message : "Motion service offline", | |
| }, | |
| queuedJobs, | |
| activeJob, | |
| }; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/http/server.ts` around lines 39 - 61, Update the catch fallback in the
/api/status handler to derive queuedJobs and activeJob from the existing state
snapshot, matching the successful return branch. Preserve the offline motion
response, but do not replace snapshot-derived job information with zero or null
when service.status() fails.
| function renderInventory() { | ||
| elements["inventory-list"].innerHTML = state.inventory | ||
| .map( | ||
| (item) => ` | ||
| <article class="inventory-row" data-id="${escapeAttribute(item.id)}" data-mode="${item.mode}"> | ||
| <label>Name<input data-field="name" value="${escapeAttribute(item.name)}" /></label> | ||
| <label>Aliases<input data-field="aliases" value="${escapeAttribute(item.aliases.join(", "))}" /></label> | ||
| <label>Mode<select data-field="mode"> | ||
| <option value="bottle" ${item.mode === "bottle" ? "selected" : ""}>Bottle</option> | ||
| <option value="pantry" ${item.mode === "pantry" ? "selected" : ""}>Pantry</option> | ||
| </select></label> | ||
| <label>Slot<input data-field="slot" type="number" min="1" max="12" value="${item.slot === null ? "" : item.slot + 1}" ${item.mode === "pantry" ? "disabled" : ""} /></label> | ||
| <label>ml / press<input data-field="mlPerPress" type="number" min="0.1" step="0.1" value="${item.mlPerPress ?? ""}" ${item.mode === "pantry" ? "disabled" : ""} /></label> | ||
| <button class="button danger" data-remove="${escapeAttribute(item.id)}">Remove</button> | ||
| </article>`, | ||
| ) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Escape item.mode in the data-mode attribute.
Line 173 interpolates item.mode directly into an HTML attribute without escapeAttribute, unlike all other user-controlled values in this template. While the server validates mode to "bottle" or "pantry", defense-in-depth requires escaping it consistently.
🔒 Proposed fix
- <article class="inventory-row" data-id="${escapeAttribute(item.id)}" data-mode="${item.mode}">
+ <article class="inventory-row" data-id="${escapeAttribute(item.id)}" data-mode="${escapeAttribute(item.mode)}">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function renderInventory() { | |
| elements["inventory-list"].innerHTML = state.inventory | |
| .map( | |
| (item) => ` | |
| <article class="inventory-row" data-id="${escapeAttribute(item.id)}" data-mode="${item.mode}"> | |
| <label>Name<input data-field="name" value="${escapeAttribute(item.name)}" /></label> | |
| <label>Aliases<input data-field="aliases" value="${escapeAttribute(item.aliases.join(", "))}" /></label> | |
| <label>Mode<select data-field="mode"> | |
| <option value="bottle" ${item.mode === "bottle" ? "selected" : ""}>Bottle</option> | |
| <option value="pantry" ${item.mode === "pantry" ? "selected" : ""}>Pantry</option> | |
| </select></label> | |
| <label>Slot<input data-field="slot" type="number" min="1" max="12" value="${item.slot === null ? "" : item.slot + 1}" ${item.mode === "pantry" ? "disabled" : ""} /></label> | |
| <label>ml / press<input data-field="mlPerPress" type="number" min="0.1" step="0.1" value="${item.mlPerPress ?? ""}" ${item.mode === "pantry" ? "disabled" : ""} /></label> | |
| <button class="button danger" data-remove="${escapeAttribute(item.id)}">Remove</button> | |
| </article>`, | |
| ) | |
| function renderInventory() { | |
| elements["inventory-list"].innerHTML = state.inventory | |
| .map( | |
| (item) => ` | |
| <article class="inventory-row" data-id="${escapeAttribute(item.id)}" data-mode="${escapeAttribute(item.mode)}"> | |
| <label>Name<input data-field="name" value="${escapeAttribute(item.name)}" /></label> | |
| <label>Aliases<input data-field="aliases" value="${escapeAttribute(item.aliases.join(", "))}" /></label> | |
| <label>Mode<select data-field="mode"> | |
| <option value="bottle" ${item.mode === "bottle" ? "selected" : ""}>Bottle</option> | |
| <option value="pantry" ${item.mode === "pantry" ? "selected" : ""}>Pantry</option> | |
| </select></label> | |
| <label>Slot<input data-field="slot" type="number" min="1" max="12" value="${item.slot === null ? "" : item.slot + 1}" ${item.mode === "pantry" ? "disabled" : ""} /></label> | |
| <label>ml / press<input data-field="mlPerPress" type="number" min="0.1" step="0.1" value="${item.mlPerPress ?? ""}" ${item.mode === "pantry" ? "disabled" : ""} /></label> | |
| <button class="button danger" data-remove="${escapeAttribute(item.id)}">Remove</button> | |
| </article>`, | |
| ) |
🧰 Tools
🪛 ast-grep (0.44.1)
[warning] 169-184: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: elements["inventory-list"].innerHTML = state.inventory
.map(
(item) => <article class="inventory-row" data-id="${escapeAttribute(item.id)}" data-mode="${item.mode}"> <label>Name<input data-field="name" value="${escapeAttribute(item.name)}" /></label> <label>Aliases<input data-field="aliases" value="${escapeAttribute(item.aliases.join(", "))}" /></label> <label>Mode<select data-field="mode"> <option value="bottle" ${item.mode === "bottle" ? "selected" : ""}>Bottle</option> <option value="pantry" ${item.mode === "pantry" ? "selected" : ""}>Pantry</option> </select></label> <label>Slot<input data-field="slot" type="number" min="1" max="12" value="${item.slot === null ? "" : item.slot + 1}" ${item.mode === "pantry" ? "disabled" : ""} /></label> <label>ml / press<input data-field="mlPerPress" type="number" min="0.1" step="0.1" value="${item.mlPerPress ?? ""}" ${item.mode === "pantry" ? "disabled" : ""} /></label> <button class="button danger" data-remove="${escapeAttribute(item.id)}">Remove</button> </article>,
)
.join("")
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
(inner-outer-html)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/app.js` around lines 169 - 184, Update the data-mode attribute
interpolation in renderInventory to pass item.mode through escapeAttribute,
matching the escaping used for other user-controlled attribute values while
preserving the existing mode selection behavior.
| <label> | ||
| Motion socket | ||
| <input name="motionSocket" /> | ||
| </label> | ||
| <label> | ||
| Maximum dose error (%) | ||
| <input name="maxDoseErrorPercent" type="number" min="0" max="100" step="1" /> | ||
| </label> | ||
| <label> | ||
| Listen host | ||
| <input name="listenHost" /> | ||
| </label> | ||
| <label> | ||
| Listen port | ||
| <input name="listenPort" type="number" min="1" max="65535" /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not present deployment-overridden settings as effective.
These fields save successfully, but production always overrides motionSocket, listenHost, and listenPort through systemd environment variables. The socket value is therefore never used, and host/port are also only read at startup. Remove or disable these fields in the deployed UI, or explicitly show that they require a service-unit change/restart.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/index.html` around lines 135 - 149, The deployed UI must not present
motionSocket, listenHost, and listenPort as immediately effective settings.
Remove or disable these inputs in web/index.html, or clearly mark them as
requiring a systemd service-unit change and service restart; preserve the
maxDoseErrorPercent field.
Capture the four console tabs plus the running and manual-step job states against a demo 12-bottle cocktail profile, and document each in a new 'The UI' section. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com>
Greptile SummaryBarRobot v3 replaces the legacy Flask controller with a TypeScript application and isolated native motion daemon.
Confidence Score: 3/5This PR should not merge until concurrent stop and disarm requests are guaranteed to prevent the dispense loop from re-energizing the actuator. The independently handled motion commands leave a check-to-assert race in the core safety path, allowing the actuator to turn back on after STOP or DISARM has reported success. Files Needing Attention: motiond/src/motion.c and motiond/src/server.c
|
| Filename | Overview |
|---|---|
| motiond/src/motion.c | Implements motion safety and dispensing, but stop/disarm can race with actuator assertion during an active dispense. |
| motiond/src/server.c | Implements the concurrent Unix-socket command server and exposes the stop/dispense concurrency underlying the motion race. |
| src/application/barrobot-service.ts | Adds persisted FIFO job orchestration, cancellation, manual pauses, and restart failure handling. |
| src/http/server.ts | Adds the JSON API, same-origin mutation checks, static UI serving, and application-service routing. |
| src/domain/planner.ts | Adds calibrated immutable drink planning with automatic and manual ingredient steps. |
| motiond/src/gpio_linux.c | Adds exclusive Linux GPIO v2 line ownership and raw line operations for the motion controller. |
| scripts/install.sh | Builds and installs the application, native daemon, state directory, service account, and systemd units. |
Sequence Diagram
sequenceDiagram
participant App as TypeScript application
participant Server as Motion socket server
participant Motion as Motion controller
participant GPIO as Linux GPIO v2
App->>Server: DISPENSE
Server->>Motion: motion_dispense()
Motion->>GPIO: actuator and motion outputs
App->>Server: STOP (separate connection)
Server->>Motion: motion_stop()
Motion->>GPIO: safe outputs
Reviews (1): Last reviewed commit: "Add v3 UI screenshots to README (#6)" | Re-trigger Greptile
| if (atomic_load(&instance->stop_requested) || !atomic_load(&instance->armed)) { | ||
| result = fail_motion(instance, MOTION_STOPPED); | ||
| goto done; | ||
| } | ||
| if (set_logical_line(instance, MOTION_LINE_ACTUATOR, true) != 0 || |
There was a problem hiding this comment.
When STOP or DISARM runs after the dispense loop checks stop_requested and armed but before it asserts the actuator, the safety command clears the outputs and returns success before the dispense thread energizes the actuator again, causing it to remain active for up to the configured 60-second press duration.
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| ErrorProne | 1 medium 6 high |
| Security | 12 critical 11 high |
🟢 Metrics 717 complexity · 25 duplication
Metric Results Complexity 717 Duplication 25
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull Request Overview
While the architectural transition to Node.js and a native C11 daemon is sound and covers all primary acceptance criteria, the PR is currently not up to standards. A critical security risk was identified in the motion daemon's socket handling where unsafe string copying could lead to buffer overflows or non-terminated strings.
Furthermore, the implementation of external API fetching is likely to trigger rate limits, and there is a misalignment between the documented safety slowdown percentages and the actual multipliers used in the service logic. Core modules like motion.c and state-validation.ts exhibit very high complexity, which may impact future maintainability despite the presence of automated tests.
About this PR
- The PR has introduced 30 new quality issues and is currently flagged as not up to standards. Please review the specific findings in the file comments to address these regressions.
Test suggestions
- Found recommended test scenario: Verify exact 1,600 step revolution counts via fake GPIO backend
- Found recommended test scenario: Verify S-curve ramp starts without initial velocity jumps
- Found recommended test scenario: Verify shortest path logic with clockwise tie-break on 6-slot moves
- Found recommended test scenario: Verify arming is rejected until position is established
- Found recommended test scenario: Verify stop command latches fault and invalidates turret position
- Found recommended test scenario: Verify measure parsing for volume (ml) vs. manual terms (e.g., 'wedge')
- Found recommended test scenario: Verify recipe rejection when dose error exceeds configured tolerance
- Found recommended test scenario: Verify FIFO job execution correctly pauses for manual acknowledgement
- Found recommended test scenario: Verify cross-origin POST requests are rejected at the HTTP boundary
- Found recommended test scenario: Verify atomic state replacement and version validation during persistence
Low confidence findings
- Several core files, including
motiond/src/motion.c(complexity: 145) andsrc/adapters/state-validation.ts(complexity: 132), exhibit high cyclomatic complexity. While these implement critical logic, consider refactoring them into smaller, more testable units in a follow-up PR.
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| struct sockaddr_un address; | ||
| memset(&address, 0, sizeof(address)); | ||
| address.sun_family = AF_UNIX; | ||
| strncpy(address.sun_path, path, sizeof(address.sun_path) - 1); |
There was a problem hiding this comment.
🔴 HIGH RISK
The strncpy function is easily used incorrectly and can leave strings without null-termination. Consider using snprintf for safer string copying.
| strncpy(address.sun_path, path, sizeof(address.sun_path) - 1); | |
| snprintf(address.sun_path, sizeof(address.sun_path), "%s", path); |
|
|
||
| private async fetchFree(): Promise<CocktailDbDrink[]> { | ||
| const batches = await Promise.all( | ||
| Array.from("abcdefghijklmnopqrstuvwxyz").map(async (letter) => |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Suggestion: The application triggers up to 26 concurrent HTTP requests to CocktailDB. This is likely to be rate-limited by the service.
Try running the following prompt in your coding agent:
In
src/adapters/cocktaildb-source.ts, refactorfetchFreeto fetch the batches sequentially using a loop instead ofPromise.allto respect external API limits.
| return null; | ||
| } | ||
| const multiplier = UNIT_TO_ML[unit[1]]; | ||
| return multiplier === undefined ? null : Math.round(quantity * multiplier * 100) / 100; |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Nitpick: The 'multiplier' is guaranteed to be defined because the preceding regex match only allows units that exist in the UNIT_TO_ML map. The undefined check can be removed for cleaner logic.
| return multiplier === undefined ? null : Math.round(quantity * multiplier * 100) / 100; | |
| return Math.round(quantity * multiplier * 100) / 100; |
| } | ||
| for (let index = job.currentStep; index < job.plan.steps.length; index += 1) { | ||
| const step = job.plan.steps[index]; | ||
| if (!step) { |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Nitpick: This condition is unnecessary because 'step' is guaranteed to be defined for every iteration within the array bounds.
Try running the following prompt in your IDE agent:
Remove the redundant if (!step) check and the associated error throwing from src/application/barrobot-service.ts around line 277, as the loop bounds guarantee the element exists.
| reject(error); | ||
| }; | ||
| socket.setEncoding("utf8"); | ||
| socket.setTimeout(timeoutMs, () => fail(new MotionError("TIMEOUT", `${command} timed out`))); |
There was a problem hiding this comment.
⚪ LOW RISK
Suggestion: Avoid using a void expression in a shorthand return. Braces should be used to signal that the function is for side effects only.
| socket.setTimeout(timeoutMs, () => fail(new MotionError("TIMEOUT", `${command} timed out`))); | |
| socket.setTimeout(timeoutMs, () => { fail(new MotionError("TIMEOUT", `${command} timed out`)); }); |
| { x: 0, y: 0 }, | ||
| ); | ||
| const imbalance = Math.hypot(vector.x, vector.y) / total; | ||
| return imbalance >= 0.65 ? 150 : imbalance >= 0.4 ? 125 : 100; |
There was a problem hiding this comment.
⚪ LOW RISK
Suggestion: The timing multipliers (125, 150) result in a 20% and 33% speed reduction respectively (1/1.25 and 1/1.5). To achieve the 50% slowdown mentioned in the description, a timing multiplier of 200 would be required.
| } | ||
| pthread_join(signal_thread, NULL); | ||
| unlink(config.socket_path); | ||
| (void)motion; |
There was a problem hiding this comment.
⚪ LOW RISK
Nitpick: The motion and gpio objects are not explicitly destroyed on exit. Calling the destroyers ensures clean closure of file descriptors and state.
Try running the following prompt in your coding agent:
In
motiond/src/server.c, replace the(void)casts at the end ofmainwith calls tomotion_destroy(motion)andgpio_linux_destroy(gpio)after the signal thread has joined.
Codacy gates pull requests on new issues, and it decides what is new by path. Moving v1 into v1/ and bringing v2/ and v3/ onto main therefore presents both rewrites as brand-new code, so this PR inherits the whole backlog of the two open rewrite PRs at once: 62 issues from #2 plus 30 from #3, against a limit of zero. Four of those were real and are fixed in the preceding commit. The rest are analysers reaching wrong conclusions because they run with their own defaults rather than the configuration each version is built with: - Bandit reports B101 on every pytest assert and reads conftest.py's throwaway Flask signing key as a hardcoded password. - Codacy's ESLint has no access to v3's tsconfig, so it cannot see noUncheckedIndexedAccess and reports the undefined-guards that make v3 compile as conditions that are always false. - Opengrep flags a state repository whose path is configuration, and two innerHTML writes whose inputs are already escaped through the DOM's own textContent round-trip. - flawfinder flags every strncpy in motiond, all of which write memset-zeroed fixed buffers under an explicit sizeof - 1 bound. Each exclusion names one analyser and one path and carries its reason. No analyser is turned off repository-wide: v2's application code, 986 lines across eight files, stays under Bandit, and v3 keeps linting src/ in CI with typescript-eslint against the real tsconfig. Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com>
Codacy's dependency scanner reported these against package-lock.json. They are new since PR #3 was last analysed, not something the restructure introduced: @fastify/static 10.1.0 path traversal past route guards, and authorization bypass via non-canonical paths fastify 5.10.0 request spoofing through numeric trustProxy, and schema validation bypass on root primitives fast-uri 3.1.3, 4.1.0 six findings, mostly SSRF and host confusion from parsing inconsistencies find-my-way 9.6.0 HTTP/2 denial of service brace-expansion 5.0.7 denial of service through memory exhaustion Every fix was already inside the semver ranges package.json declares, so this is a lockfile refresh with no manifest change: fastify 5.12.3, @fastify/static 10.1.3, fast-uri 3.1.7 and 4.1.4, find-my-way 9.9.0, brace-expansion 5.0.9. npm audit is clean, npm ci reproduces the tree, and prettier, tsc, eslint, the build, and all 25 tests pass on it. Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com>
* Document BarRobot v2 rewrite design * Rewrite BarRobot around testable services * Add BarRobot behavioral tests and CI * Fix package installation in CI * Design BarRobot v3 greenfield architecture * Replace legacy app with BarRobot v3 * Add isolated GPIO motion service * Add v3 tests deployment and CI * Add product and S-curve motion profiles * Add product changeover and party controls * Slow lopsided automatic moves * Add v2 UI screenshots to README (#5) Capture the five Flask pages plus the pour status list against a demo 12-bottle configuration in safe mode, and document each in a new 'The UI' section. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com> * Add v3 UI screenshots to README (#6) Capture the four console tabs plus the running and manual-step job states against a demo 12-bottle cocktail profile, and document each in a new 'The UI' section. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com> * Move the original Flask app into v1/ Makes room for the v2 and v3 implementations to live alongside it instead of replacing it. Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com> * Index the three versions from the repository root Adds a root README explaining what v1, v2, and v3 are and which to use, merges the three .gitignore files, and splits CI into per-version workflows that only run when their own directory changes. Version READMEs now point at the shared LICENSE and hero image one level up. Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com> * Raise v2's Flask and requests floors past known CVEs Codacy's dependency scanner resolves the lower bound of each constraint, so ">=3.1" and ">=2.32" were reported as vulnerable: Flask 3.1 CVE-2025-47278 session signing fallback key CVE-2026-27205 session data cached improperly requests 2.32 CVE-2024-47081 .netrc credential leak via crafted URLs CVE-2026-25645 predictable temporary file creation Both fixes land within the ranges the project already allows, so the floors move to the first patched releases and the upper bounds stand. ruff and the 31 tests pass against Flask 3.1.3 and requests 2.33.1. Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com> * Configure Codacy for the three-version layout Codacy gates pull requests on new issues, and it decides what is new by path. Moving v1 into v1/ and bringing v2/ and v3/ onto main therefore presents both rewrites as brand-new code, so this PR inherits the whole backlog of the two open rewrite PRs at once: 62 issues from #2 plus 30 from #3, against a limit of zero. Four of those were real and are fixed in the preceding commit. The rest are analysers reaching wrong conclusions because they run with their own defaults rather than the configuration each version is built with: - Bandit reports B101 on every pytest assert and reads conftest.py's throwaway Flask signing key as a hardcoded password. - Codacy's ESLint has no access to v3's tsconfig, so it cannot see noUncheckedIndexedAccess and reports the undefined-guards that make v3 compile as conditions that are always false. - Opengrep flags a state repository whose path is configuration, and two innerHTML writes whose inputs are already escaped through the DOM's own textContent round-trip. - flawfinder flags every strncpy in motiond, all of which write memset-zeroed fixed buffers under an explicit sizeof - 1 bound. Each exclusion names one analyser and one path and carries its reason. No analyser is turned off repository-wide: v2's application code, 986 lines across eight files, stays under Bandit, and v3 keeps linting src/ in CI with typescript-eslint against the real tsconfig. Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com> * Update v3's lockfile past nineteen dependency CVEs Codacy's dependency scanner reported these against package-lock.json. They are new since PR #3 was last analysed, not something the restructure introduced: @fastify/static 10.1.0 path traversal past route guards, and authorization bypass via non-canonical paths fastify 5.10.0 request spoofing through numeric trustProxy, and schema validation bypass on root primitives fast-uri 3.1.3, 4.1.0 six findings, mostly SSRF and host confusion from parsing inconsistencies find-my-way 9.6.0 HTTP/2 denial of service brace-expansion 5.0.7 denial of service through memory exhaustion Every fix was already inside the semver ranges package.json declares, so this is a lockfile refresh with no manifest change: fastify 5.12.3, @fastify/static 10.1.3, fast-uri 3.1.7 and 4.1.4, find-my-way 9.9.0, brace-expansion 5.0.9. npm audit is clean, npm ci reproduces the tree, and prettier, tsc, eslint, the build, and all 25 tests pass on it. Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com> * Cover the remaining analysers that re-litigate v2 and v3 style The first pass cut Codacy's count on this PR from 92 to 45 and confirmed the exclusions land, but four analysers were named wrongly or not at all: - The strncpy findings in motiond come from Opengrep as well as flawfinder; only flawfinder was listed. - The 0.0001 objection in web/app.js is PMD's InnaccurateNumericLiteral, not ESLint, so excluding ESLint there had no effect. - Prospector and Pylint report twenty-two docstring and layout findings across v2 under a convention that contradicts the ruff configuration v2 actually enforces in CI. Each addition carries the same justification the existing entries do. Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cmc0619 <cmc0619@users.noreply.github.com>
Summary
Greenfield BarRobot v3 for the already-purchased Raspberry Pi, DM542T, NEMA-17, 12-slot turret, actuator, PSU, and existing GPIO wiring.
This PR is independent from v2 and targets
maindirectly. It intentionally removes the Flask/Python implementation and does not preserve old routes, configuration files, recipe scaling, or module APIs.Architecture
barrobot-motiondprocess that exclusively owns GPIO through Linux GPIO v2No additional controller board or other hardware is required.
Motion and safety
CLOCK_MONOTONICdeadlines prevent accumulated timer driftSCHED_FIFOstatus is reported honestlyDeployment
barrobot-motion.serviceandbarrobot.servicebarrobotaccount with only the existing Pigpiogroup and motion scheduling capability/opt/barrobot/var/lib/barrobot/state.jsonValidation
-Wall -Wextra -Werror -Wpedantic -Wconversion -WshadowSee
docs/design-v3.mdfor the complete design and safety contract.Summary by CodeRabbit
New Features
Documentation
Tests