plc-checkweigher 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,582 @@
1
+ #!/usr/bin/env bash
2
+ # plc_checkweigher — CLI for the PLC Check-Weigher system
3
+ # Installed to /usr/local/bin/ by setup.sh
4
+ #
5
+ # Commands:
6
+ # status Full system diagnostic
7
+ # logs Live journalctl stream
8
+ # restart / start / stop Service control
9
+ # push-test Push latest PDF to SMB target
10
+ # queue Show SMB delivery queue + ledger
11
+ # wifi Scan and switch WiFi network (prompts SMB IP update)
12
+ # hotspot on/off/status/scan WiFi hotspot (AP mode) — direct PC connection
13
+ # display on/off/status Enable/disable the display (LightDM)
14
+ # smb-config Interactively update SMB delivery target
15
+
16
+ set -euo pipefail
17
+
18
+ INSTALL_DIR="/home/pi/plc_checkweigher"
19
+ PYTHON="/home/pi/plc_env/bin/python3"
20
+ SMB_CFG="${INSTALL_DIR}/smb_config.py"
21
+
22
+ # ── Colours ───────────────────────────────────────────────────────────────────
23
+ B='\033[1;34m'; G='\033[0;32m'; R='\033[1;31m'; Y='\033[1;33m'
24
+ C='\033[0;36m'; D='\033[2m'; W='\033[1m'; NC='\033[0m'
25
+
26
+ ok() { echo -e " ${G}✓${NC} $*"; }
27
+ err() { echo -e " ${R}✗${NC} $*" >&2; }
28
+ warn() { echo -e " ${Y}!${NC} $*"; }
29
+ info() { echo -e " ${C}i${NC} $*"; }
30
+ hr() { echo -e " ${D}$(printf '─%.0s' {1..56})${NC}"; }
31
+ banner() { echo -e "\n${B} ▸ $*${NC}"; }
32
+
33
+ need_sudo() {
34
+ if [[ $EUID -ne 0 ]]; then
35
+ if ! sudo -n true 2>/dev/null; then
36
+ echo -e " ${Y}sudo password required${NC}"
37
+ fi
38
+ fi
39
+ }
40
+
41
+ # ── Read current SMB config ───────────────────────────────────────────────────
42
+ smb_get() {
43
+ local key="$1"
44
+ grep "^${key}" "${SMB_CFG}" 2>/dev/null \
45
+ | head -1 | sed 's/.*= *"//;s/".*//'
46
+ }
47
+
48
+ smb_set() {
49
+ local key="$1" val="$2"
50
+ if grep -q "^${key}" "${SMB_CFG}" 2>/dev/null; then
51
+ sed -i "s|^${key}.*|${key} = \"${val}\"|" "${SMB_CFG}"
52
+ else
53
+ echo "${key} = \"${val}\"" >> "${SMB_CFG}"
54
+ fi
55
+ }
56
+
57
+ # ── SMB config update prompt (reused by wifi + smb-config) ───────────────────
58
+ prompt_smb_config() {
59
+ local context="${1:-}"
60
+ echo ""
61
+ banner "Update SMB Delivery Config"
62
+ [[ -n "$context" ]] && info "$context"
63
+ echo ""
64
+
65
+ CUR_HOST=$(smb_get "SMB_HOST")
66
+ CUR_SHARE=$(smb_get "SMB_SHARE")
67
+ CUR_USER=$(smb_get "SMB_USERNAME")
68
+ CUR_PASS=$(smb_get "SMB_PASSWORD")
69
+ CUR_ENABLED=$(grep "^SMB_ENABLED" "${SMB_CFG}" 2>/dev/null | grep -o "True\|False" || echo "True")
70
+
71
+ hr
72
+ printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Host IP" "${CUR_HOST:-(not set)}"
73
+ printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Share name" "${CUR_SHARE:-(not set)}"
74
+ printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Username" "${CUR_USER:-(not set)}"
75
+ printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Password" "${CUR_PASS:-(not set)}"
76
+ hr
77
+ echo ""
78
+
79
+ read -r -p " Host IP [${CUR_HOST}]: " NEW_HOST </dev/tty
80
+ NEW_HOST="${NEW_HOST:-$CUR_HOST}"
81
+
82
+ read -r -p " Share name [${CUR_SHARE:-Reports}]: " NEW_SHARE </dev/tty
83
+ NEW_SHARE="${NEW_SHARE:-${CUR_SHARE:-Reports}}"
84
+
85
+ read -r -p " Username [${CUR_USER:-plcreport}]: " NEW_USER </dev/tty
86
+ NEW_USER="${NEW_USER:-${CUR_USER:-plcreport}}"
87
+
88
+ read -r -s -p " Password (leave blank to keep current): " NEW_PASS </dev/tty
89
+ echo ""
90
+ NEW_PASS="${NEW_PASS:-$CUR_PASS}"
91
+
92
+ # Write updated config
93
+ cat > "${SMB_CFG}" << EOF
94
+ SMB_ENABLED = True
95
+ SMB_HOST = "${NEW_HOST}"
96
+ SMB_SHARE = "${NEW_SHARE}"
97
+ SMB_USERNAME = "${NEW_USER}"
98
+ SMB_PASSWORD = "${NEW_PASS}"
99
+ SMB_SUBDIR = ""
100
+ EOF
101
+
102
+ echo ""
103
+ ok "smb_config.py updated"
104
+ info "Host: ${NEW_HOST} | Share: //${NEW_HOST}/${NEW_SHARE} | User: ${NEW_USER}"
105
+
106
+ # Quick connectivity test
107
+ echo ""
108
+ echo -n " Testing connection ..."
109
+ if ping -c 2 -W 2 "${NEW_HOST}" &>/dev/null; then
110
+ echo ""
111
+ ok "${NEW_HOST} reachable"
112
+ if smbclient "//${NEW_HOST}/${NEW_SHARE}" -U "${NEW_USER}%${NEW_PASS}" -c "ls" &>/dev/null 2>&1; then
113
+ ok "SMB auth OK — PDF delivery is ready"
114
+ else
115
+ warn "SMB auth failed — verify share name and credentials on the PC"
116
+ fi
117
+ else
118
+ echo ""
119
+ warn "${NEW_HOST} not reachable right now — config saved, will retry at runtime"
120
+ fi
121
+ echo ""
122
+ }
123
+
124
+ # ─────────────────────────────────────────────────────────────────────────────
125
+ CMD="${1:-help}"
126
+ shift || true
127
+
128
+ case "$CMD" in
129
+
130
+ # ── System diagnostic ─────────────────────────────────────────────────────────
131
+ status|check|diag)
132
+ exec "${PYTHON}" "${INSTALL_DIR}/debugger.py" "$@"
133
+ ;;
134
+
135
+ # ── Live logs ─────────────────────────────────────────────────────────────────
136
+ logs)
137
+ exec journalctl -u plc_watcher -u plc_web -f --no-pager
138
+ ;;
139
+
140
+ # ── Service control ───────────────────────────────────────────────────────────
141
+ restart)
142
+ need_sudo
143
+ banner "Restarting services"
144
+ sudo systemctl restart plc_watcher plc_web
145
+ sleep 2
146
+ systemctl status plc_watcher plc_web --no-pager | grep -E 'Active|Main PID'
147
+ ;;
148
+ start)
149
+ need_sudo
150
+ banner "Starting services"
151
+ sudo systemctl start plc_watcher plc_web
152
+ ok "plc_watcher and plc_web started"
153
+ ;;
154
+ stop)
155
+ need_sudo
156
+ banner "Stopping services"
157
+ sudo systemctl stop plc_watcher plc_web
158
+ warn "Services stopped — they will restart automatically on next boot"
159
+ warn "To disable auto-start: sudo systemctl disable plc_watcher plc_web"
160
+ ;;
161
+
162
+ # ── SMB queue ─────────────────────────────────────────────────────────────────
163
+ queue)
164
+ banner "SMB Delivery Queue"
165
+ echo ""
166
+ QUEUE="${INSTALL_DIR}/delivery_queue.json"
167
+ LEDGER="${INSTALL_DIR}/delivery_sent.log"
168
+ if [[ -f "$QUEUE" ]]; then
169
+ COUNT=$(python3 -c "import json; d=json.load(open('$QUEUE')); print(len(d))" 2>/dev/null || echo 0)
170
+ if [[ "$COUNT" -eq 0 ]]; then
171
+ ok "Queue empty — all reports delivered"
172
+ else
173
+ warn "${COUNT} file(s) pending delivery:"
174
+ python3 -m json.tool "$QUEUE" 2>/dev/null
175
+ fi
176
+ else
177
+ ok "Queue empty"
178
+ fi
179
+ echo ""
180
+ if [[ -f "$LEDGER" ]]; then
181
+ SENT=$(wc -l < "$LEDGER")
182
+ ok "Delivery ledger: ${SENT} file(s) sent"
183
+ cat "$LEDGER" | sed 's/^/ /'
184
+ else
185
+ info "No deliveries recorded yet"
186
+ fi
187
+ echo ""
188
+ ;;
189
+
190
+ # ── Push test ─────────────────────────────────────────────────────────────────
191
+ push-test)
192
+ banner "SMB Push Test"
193
+ echo ""
194
+ LATEST=$(ls -t /home/pi/reports/*.pdf 2>/dev/null | head -1 || true)
195
+ if [[ -z "$LATEST" ]]; then
196
+ info "No PDFs found — generating one from PLC..."
197
+ "${PYTHON}" "${INSTALL_DIR}/plc_report.py"
198
+ LATEST=$(ls -t /home/pi/reports/*.pdf 2>/dev/null | head -1 || true)
199
+ fi
200
+ [[ -z "$LATEST" ]] && { err "Could not generate PDF — is PLC connected?"; exit 1; }
201
+ info "File: $(basename $LATEST)"
202
+ echo ""
203
+ "${PYTHON}" -c "
204
+ import sys, os
205
+ sys.path.insert(0, '${INSTALL_DIR}')
206
+ from pdf_push import _push_smb, _already_sent, _record_sent
207
+ fname = os.path.basename('$LATEST')
208
+ if _already_sent(fname):
209
+ print(f' Already delivered: {fname}')
210
+ print(' Delete the ledger entry to re-test:')
211
+ print(f' sed -i \"/{fname}/d\" ${INSTALL_DIR}/delivery_sent.log')
212
+ sys.exit(0)
213
+ ok = _push_smb('$LATEST')
214
+ if ok:
215
+ _record_sent(fname)
216
+ sys.exit(0 if ok else 1)
217
+ "
218
+ ;;
219
+
220
+ # ─────────────────────────────────────────────────────────────────────────────
221
+ # WIFI — scan + switch network
222
+ # ─────────────────────────────────────────────────────────────────────────────
223
+ wifi)
224
+ SUBCMD="${1:-scan}"
225
+ shift || true
226
+
227
+ case "$SUBCMD" in
228
+ scan|"")
229
+ banner "WiFi Networks"
230
+ echo ""
231
+ info "Scanning ..."
232
+ nmcli dev wifi rescan ifname wlan0 2>/dev/null || true
233
+ sleep 2
234
+
235
+ mapfile -t RAW < <(
236
+ nmcli -t -f SSID,SIGNAL,SECURITY,IN-USE dev wifi list ifname wlan0 2>/dev/null \
237
+ | awk -F: '$1!=""' \
238
+ | sort -t: -k2 -rn \
239
+ | awk -F: '!seen[$1]++'
240
+ )
241
+
242
+ [[ ${#RAW[@]} -eq 0 ]] && { warn "No networks found."; exit 0; }
243
+
244
+ echo ""
245
+ hr
246
+ printf " ${B}%-4s %-28s %-8s %-10s %s${NC}\n" "#" "SSID" "Signal" "Security" ""
247
+ hr
248
+ declare -a SSIDS
249
+ for i in "${!RAW[@]}"; do
250
+ IFS=':' read -r SSID SIGNAL SECURITY INUSE <<< "${RAW[$i]}"
251
+ SSIDS[$i]="$SSID"
252
+ SIG="${SIGNAL:-0}"
253
+ if [[ $SIG -ge 80 ]]; then BAR="${G}▂▄▆█${NC}"
254
+ elif [[ $SIG -ge 60 ]]; then BAR="${G}▂▄▆ ${NC}"
255
+ elif [[ $SIG -ge 40 ]]; then BAR="${Y}▂▄ ${NC}"
256
+ else BAR="${R}▂ ${NC}"; fi
257
+ ACTIVE=""
258
+ [[ "$INUSE" == "*" ]] && ACTIVE="${G} ← connected${NC}"
259
+ printf " %-4s %-28s %b %3s%% %-10s%b\n" \
260
+ "$((i+1)))" "$SSID" "$BAR" "$SIG" "${SECURITY:---}" "$ACTIVE"
261
+ done
262
+ hr
263
+ printf " %-4s %s\n" "0)" "Cancel"
264
+ echo ""
265
+
266
+ while true; do
267
+ read -r -p " Choose network [1-${#RAW[@]}] or 0 to cancel: " CHOICE </dev/tty
268
+ [[ "$CHOICE" =~ ^[0-9]+$ ]] && \
269
+ [[ "$CHOICE" -ge 0 && "$CHOICE" -le "${#RAW[@]}" ]] && break
270
+ echo -e " ${R}Enter a number 0–${#RAW[@]}${NC}"
271
+ done
272
+ [[ "$CHOICE" -eq 0 ]] && exit 0
273
+
274
+ SEL="${SSIDS[$((CHOICE-1))]}"
275
+ echo ""
276
+ read -r -s -p " Password for '${SEL}' (blank if open): " WIFI_PASS </dev/tty
277
+ echo ""
278
+ echo ""
279
+
280
+ need_sudo
281
+ # Remove existing connection if any, then connect
282
+ sudo nmcli connection delete "$SEL" 2>/dev/null || true
283
+ if [[ -n "$WIFI_PASS" ]]; then
284
+ sudo nmcli connection add type wifi ifname wlan0 con-name "$SEL" \
285
+ ssid "$SEL" wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$WIFI_PASS" \
286
+ connection.autoconnect yes connection.autoconnect-priority 200 \
287
+ 2>/dev/null
288
+ else
289
+ sudo nmcli connection add type wifi ifname wlan0 con-name "$SEL" \
290
+ ssid "$SEL" connection.autoconnect yes \
291
+ connection.autoconnect-priority 200 2>/dev/null
292
+ fi
293
+
294
+ echo -n " Connecting to '$SEL' ..."
295
+ if sudo nmcli connection up "$SEL" 2>/dev/null; then
296
+ echo ""
297
+ sleep 2
298
+ NEW_IP=$(ip -4 addr show wlan0 2>/dev/null | grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "")
299
+ ok "Connected to '${SEL}'"
300
+ [[ -n "$NEW_IP" ]] && ok "New IP: ${NEW_IP} → web UI at http://${NEW_IP}:8080"
301
+
302
+ # Prompt to update SMB host IP
303
+ echo ""
304
+ echo -e " ${Y}Network changed — SMB delivery target may need updating.${NC}"
305
+ read -r -p " Update SMB host IP? [Y/n]: " CONFIRM </dev/tty
306
+ CONFIRM="${CONFIRM:-Y}"
307
+ if [[ "${CONFIRM^^}" == "Y" ]]; then
308
+ prompt_smb_config "Network changed to '${SEL}' (Pi IP: ${NEW_IP:-unknown})"
309
+ fi
310
+ else
311
+ echo ""
312
+ warn "Could not connect — check password and try again"
313
+ fi
314
+ ;;
315
+
316
+ *)
317
+ echo "Usage: plc_checkweigher wifi [scan]"
318
+ ;;
319
+ esac
320
+ ;;
321
+
322
+ # ─────────────────────────────────────────────────────────────────────────────
323
+ # HOTSPOT — AP mode for direct PC connection + SMB push
324
+ # ─────────────────────────────────────────────────────────────────────────────
325
+ hotspot)
326
+ SUBCMD="${1:-status}"
327
+ shift || true
328
+ HOTSPOT_SSID="${HOTSPOT_SSID:-PLC-Reports}"
329
+ HOTSPOT_PASS="${HOTSPOT_PASS:-plcreport}"
330
+ HOTSPOT_CON="plc-hotspot"
331
+
332
+ case "$SUBCMD" in
333
+ on)
334
+ banner "Enable Hotspot"
335
+ need_sudo
336
+ echo ""
337
+
338
+ # Allow custom SSID/pass as args
339
+ [[ -n "${1:-}" ]] && HOTSPOT_SSID="$1"
340
+ [[ -n "${2:-}" ]] && HOTSPOT_PASS="$2"
341
+
342
+ # Bring down existing hotspot connection if any
343
+ sudo nmcli connection delete "$HOTSPOT_CON" 2>/dev/null || true
344
+
345
+ info "Creating hotspot '${HOTSPOT_SSID}' on wlan0 ..."
346
+ sudo nmcli device wifi hotspot \
347
+ ifname wlan0 \
348
+ con-name "$HOTSPOT_CON" \
349
+ ssid "$HOTSPOT_SSID" \
350
+ password "$HOTSPOT_PASS" 2>&1 | grep -v "^$" || true
351
+
352
+ sleep 2
353
+
354
+ # Get Pi's hotspot IP (usually 10.42.0.1)
355
+ HOTSPOT_IP=$(ip -4 addr show wlan0 2>/dev/null \
356
+ | grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "10.42.0.1")
357
+
358
+ echo ""
359
+ ok "Hotspot active"
360
+ echo ""
361
+ echo -e " ${W}┌────────────────────────────────────────────────┐${NC}"
362
+ echo -e " ${W}│ Connect your PC to this WiFi: │${NC}"
363
+ echo -e " ${W}│ │${NC}"
364
+ printf " ${W}│ %-14s ${G}%-30s${W} │${NC}\n" "SSID:" "$HOTSPOT_SSID"
365
+ printf " ${W}│ %-14s ${G}%-30s${W} │${NC}\n" "Password:" "$HOTSPOT_PASS"
366
+ printf " ${W}│ %-14s ${C}%-30s${W} │${NC}\n" "Pi IP:" "$HOTSPOT_IP"
367
+ echo -e " ${W}└────────────────────────────────────────────────┘${NC}"
368
+ echo ""
369
+ info "After PC connects, run: plc_checkweigher hotspot scan"
370
+ info " — detects PC's IP and updates SMB config automatically"
371
+ echo ""
372
+ info "Web UI still accessible at: http://${HOTSPOT_IP}:8080"
373
+ echo ""
374
+ ;;
375
+
376
+ off)
377
+ banner "Disable Hotspot"
378
+ need_sudo
379
+ sudo nmcli connection delete "$HOTSPOT_CON" 2>/dev/null \
380
+ && ok "Hotspot stopped" \
381
+ || warn "No active hotspot found"
382
+ echo ""
383
+ info "To reconnect to WiFi: plc_checkweigher wifi"
384
+ echo ""
385
+ ;;
386
+
387
+ status)
388
+ banner "Hotspot Status"
389
+ echo ""
390
+ if nmcli connection show --active 2>/dev/null | grep -q "$HOTSPOT_CON"; then
391
+ HOTSPOT_IP=$(ip -4 addr show wlan0 2>/dev/null \
392
+ | grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "?")
393
+ ok "Hotspot is ON"
394
+ info "SSID: ${HOTSPOT_SSID} | Pi IP: ${HOTSPOT_IP}"
395
+ info "Run: plc_checkweigher hotspot scan — to find connected PCs"
396
+ else
397
+ info "Hotspot is OFF"
398
+ info "Run: plc_checkweigher hotspot on — to start"
399
+ fi
400
+ echo ""
401
+ ;;
402
+
403
+ scan)
404
+ # Find devices connected to the hotspot subnet and offer to set as SMB target
405
+ banner "Scanning for connected devices"
406
+ echo ""
407
+
408
+ HOTSPOT_IP=$(ip -4 addr show wlan0 2>/dev/null \
409
+ | grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "")
410
+
411
+ if [[ -z "$HOTSPOT_IP" ]]; then
412
+ err "Hotspot not active — run: plc_checkweigher hotspot on"
413
+ exit 1
414
+ fi
415
+
416
+ SUBNET="${HOTSPOT_IP%.*}.0/24"
417
+ info "Scanning ${SUBNET} ..."
418
+
419
+ # Refresh ARP table by pinging the subnet
420
+ for i in $(seq 1 254); do
421
+ ping -c 1 -W 1 "${HOTSPOT_IP%.*}.${i}" &>/dev/null &
422
+ done
423
+ wait
424
+ sleep 1
425
+
426
+ # Collect clients (exclude Pi itself)
427
+ mapfile -t CLIENTS < <(
428
+ ip neigh show 2>/dev/null \
429
+ | grep "REACHABLE\|STALE\|DELAY" \
430
+ | grep "${HOTSPOT_IP%.*}\." \
431
+ | grep -v "^${HOTSPOT_IP}" \
432
+ | awk '{print $1}' \
433
+ | sort -t. -k4 -n
434
+ )
435
+
436
+ if [[ ${#CLIENTS[@]} -eq 0 ]]; then
437
+ warn "No devices found yet — make sure the PC is connected to '${HOTSPOT_SSID}'"
438
+ info "Try again in a few seconds: plc_checkweigher hotspot scan"
439
+ echo ""
440
+ exit 0
441
+ fi
442
+
443
+ echo ""
444
+ hr
445
+ printf " ${B}%-4s %-18s %s${NC}\n" "#" "IP Address" "Hostname"
446
+ hr
447
+ declare -a CLIENT_IPS
448
+ for i in "${!CLIENTS[@]}"; do
449
+ IP="${CLIENTS[$i]}"
450
+ CLIENT_IPS[$i]="$IP"
451
+ HOST=$(host "$IP" 2>/dev/null | grep "domain name pointer" | awk '{print $NF}' | sed 's/\.$//' || echo "")
452
+ printf " %-4s %-18s %s\n" "$((i+1)))" "$IP" "${HOST:-(unknown)}"
453
+ done
454
+ hr
455
+ printf " %-4s %s\n" "0)" "Cancel"
456
+ echo ""
457
+
458
+ while true; do
459
+ read -r -p " Set device as SMB target [1-${#CLIENTS[@]}] or 0 to cancel: " CHOICE </dev/tty
460
+ [[ "$CHOICE" =~ ^[0-9]+$ ]] && \
461
+ [[ "$CHOICE" -ge 0 && "$CHOICE" -le "${#CLIENTS[@]}" ]] && break
462
+ done
463
+ [[ "$CHOICE" -eq 0 ]] && exit 0
464
+
465
+ TARGET_IP="${CLIENT_IPS[$((CHOICE-1))]}"
466
+ echo ""
467
+ info "Selected: ${TARGET_IP}"
468
+ prompt_smb_config "Setting SMB target to hotspot client ${TARGET_IP}"
469
+ ;;
470
+
471
+ *)
472
+ echo "Usage: plc_checkweigher hotspot [on|off|status|scan]"
473
+ echo " on [SSID] [PASSWORD] — start hotspot (default: PLC-Reports / plcreport)"
474
+ echo " off — stop hotspot"
475
+ echo " status — show hotspot state"
476
+ echo " scan — find connected PCs and set as SMB target"
477
+ ;;
478
+ esac
479
+ ;;
480
+
481
+ # ─────────────────────────────────────────────────────────────────────────────
482
+ # DISPLAY — enable / disable LightDM
483
+ # ─────────────────────────────────────────────────────────────────────────────
484
+ display)
485
+ SUBCMD="${1:-status}"
486
+ shift || true
487
+
488
+ case "$SUBCMD" in
489
+ on)
490
+ banner "Enable Display"
491
+ need_sudo
492
+ sudo systemctl start lightdm 2>/dev/null \
493
+ && ok "Display enabled — LightDM started" \
494
+ || warn "LightDM failed to start (may not be installed)"
495
+ sudo systemctl enable lightdm 2>/dev/null && ok "Auto-start enabled" || true
496
+ echo ""
497
+ ;;
498
+ off)
499
+ banner "Disable Display"
500
+ need_sudo
501
+ sudo systemctl stop lightdm 2>/dev/null \
502
+ && ok "Display disabled — LightDM stopped" \
503
+ || warn "LightDM was not running"
504
+ read -r -p " Disable auto-start on boot too? [y/N]: " DIS </dev/tty
505
+ DIS="${DIS:-N}"
506
+ if [[ "${DIS^^}" == "Y" ]]; then
507
+ sudo systemctl disable lightdm 2>/dev/null && ok "Auto-start disabled" || true
508
+ fi
509
+ echo ""
510
+ ;;
511
+ status)
512
+ banner "Display Status"
513
+ echo ""
514
+ ACTIVE=$(systemctl is-active lightdm 2>/dev/null || echo "inactive")
515
+ ENABLED=$(systemctl is-enabled lightdm 2>/dev/null || echo "disabled")
516
+ if [[ "$ACTIVE" == "active" ]]; then
517
+ ok "LightDM: RUNNING (auto-start: ${ENABLED})"
518
+ else
519
+ info "LightDM: ${ACTIVE} (auto-start: ${ENABLED})"
520
+ fi
521
+ # HDMI / display connected
522
+ if command -v tvservice &>/dev/null; then
523
+ HDMI=$(tvservice -s 2>/dev/null | head -1)
524
+ info "HDMI: ${HDMI}"
525
+ fi
526
+ echo ""
527
+ ;;
528
+ *)
529
+ echo "Usage: plc_checkweigher display [on|off|status]"
530
+ ;;
531
+ esac
532
+ ;;
533
+
534
+ # ─────────────────────────────────────────────────────────────────────────────
535
+ # SMB CONFIG — interactive update of delivery target
536
+ # ─────────────────────────────────────────────────────────────────────────────
537
+ smb-config)
538
+ prompt_smb_config
539
+ ;;
540
+
541
+ # ─────────────────────────────────────────────────────────────────────────────
542
+ # HELP
543
+ # ─────────────────────────────────────────────────────────────────────────────
544
+ help|--help|-h)
545
+ echo ""
546
+ echo -e "${B} plc_checkweigher${NC} — PLC Check-Weigher system CLI"
547
+ echo ""
548
+ echo -e " ${W}Diagnostics${NC}"
549
+ echo " status Full system diagnostic — all checks + fix hints"
550
+ echo " logs Stream live logs (plc_watcher + plc_web)"
551
+ echo " queue Show SMB pending queue and delivery ledger"
552
+ echo ""
553
+ echo -e " ${W}Services${NC}"
554
+ echo " start Start plc_watcher and plc_web"
555
+ echo " stop Stop both services"
556
+ echo " restart Restart both services"
557
+ echo ""
558
+ echo -e " ${W}Network${NC}"
559
+ echo " wifi Scan and switch WiFi network"
560
+ echo " hotspot on [SSID] [PASS] Start WiFi hotspot (direct PC connection)"
561
+ echo " hotspot off Stop hotspot"
562
+ echo " hotspot status Show hotspot state"
563
+ echo " hotspot scan Detect connected PCs → set as SMB target"
564
+ echo ""
565
+ echo -e " ${W}Configuration${NC}"
566
+ echo " smb-config Interactively update SMB delivery target"
567
+ echo " push-test Push latest PDF to SMB target now"
568
+ echo ""
569
+ echo -e " ${W}Display${NC}"
570
+ echo " display on Enable display (start LightDM)"
571
+ echo " display off Disable display (stop LightDM)"
572
+ echo " display status Show display state"
573
+ echo ""
574
+ ;;
575
+
576
+ *)
577
+ err "Unknown command: $CMD"
578
+ echo " Run: plc_checkweigher help"
579
+ exit 1
580
+ ;;
581
+
582
+ esac
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plc-checkweigher",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "One-command installer for the PLC Check-Weigher system on Raspberry Pi (PREEMPT_RT kernel, Python stack, WiFi, SMB, systemd RT services)",
5
5
  "bin": {
6
6
  "plc-checkweigher": "bin/cli.js"
package/setup.sh CHANGED
@@ -136,6 +136,21 @@ setup_dirs() {
136
136
  ok "${REPORTS_DIR}"
137
137
  }
138
138
 
139
+ # ── CLI tool — install plc_checkweigher command ───────────────────────────────
140
+ install_cli() {
141
+ step "CLI tool ..."
142
+ CLI_SRC="${INSTALL_DIR}/bin/plc_checkweigher"
143
+ CLI_DEST="/usr/local/bin/plc_checkweigher"
144
+ if [[ -f "${CLI_SRC}" ]]; then
145
+ chmod +x "${CLI_SRC}"
146
+ cp "${CLI_SRC}" "${CLI_DEST}"
147
+ ok "plc_checkweigher → ${CLI_DEST}"
148
+ ok "Run: plc_checkweigher status (full system diagnostic)"
149
+ else
150
+ warn "bin/plc_checkweigher not found — skipping CLI install"
151
+ fi
152
+ }
153
+
139
154
  # ── 5. WiFi — scan → pick → password ─────────────────────────────────────────
140
155
  setup_wifi() {
141
156
  step "WiFi Setup"
@@ -601,10 +616,11 @@ main() {
601
616
  setup_repo # 2
602
617
  setup_venv # 3
603
618
  setup_dirs # 4
604
- setup_wifi # 5 — interactive WiFi picker
605
- setup_smb # 6 — interactive SMB config → smb_config.py
606
- setup_network_online # 7
607
- install_services # 8
619
+ install_cli # 5 — plc_checkweigher status command
620
+ setup_wifi # 6 — interactive WiFi picker
621
+ setup_smb # 7 — interactive SMB config → smb_config.py
622
+ setup_network_online # 8
623
+ install_services # 9
608
624
  setup_boot_logo # 9 — Plymouth: logo + "SAI SAMARTH ENGG"
609
625
  setup_display # 10 — LightDM priority, CPU isolation, utmpx, GPU
610
626
  install_rt_kernel # 11 — LAST, so only one reboot needed