plc-checkweigher 1.20.0 → 1.21.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.
@@ -588,19 +588,20 @@ smb-config)
588
588
 
589
589
  # ─────────────────────────────────────────────────────────────────────────────
590
590
  # FIX — auto-detect and repair common issues
591
- # Usage: fix [-wifi] [-health] [-programs] (no flags = run all)
591
+ # Usage: fix [-wifi] [-health] [-programs] [-errors] (no flags = run all)
592
592
  # ─────────────────────────────────────────────────────────────────────────────
593
593
  fix)
594
- FIX_WIFI=0; FIX_HEALTH=0; FIX_PROGRAMS=0
594
+ FIX_WIFI=0; FIX_HEALTH=0; FIX_PROGRAMS=0; FIX_ERRORS=0
595
595
  if [[ $# -eq 0 ]]; then
596
- FIX_WIFI=1; FIX_HEALTH=1; FIX_PROGRAMS=1
596
+ FIX_WIFI=1; FIX_HEALTH=1; FIX_PROGRAMS=1; FIX_ERRORS=1
597
597
  else
598
598
  for _flag in "$@"; do
599
599
  case "$_flag" in
600
600
  -wifi) FIX_WIFI=1 ;;
601
601
  -health) FIX_HEALTH=1 ;;
602
602
  -programs) FIX_PROGRAMS=1 ;;
603
- *) warn "Unknown flag: $_flag (valid: -wifi -health -programs)" ;;
603
+ -errors) FIX_ERRORS=1 ;;
604
+ *) warn "Unknown flag: $_flag (valid: -wifi -health -programs -errors)" ;;
604
605
  esac
605
606
  done
606
607
  fi
@@ -612,7 +613,8 @@ fix)
612
613
  [[ $FIX_WIFI -eq 1 ]] && _LOG_MODES="${_LOG_MODES:+${_LOG_MODES}-}wifi"
613
614
  [[ $FIX_HEALTH -eq 1 ]] && _LOG_MODES="${_LOG_MODES:+${_LOG_MODES}-}health"
614
615
  [[ $FIX_PROGRAMS -eq 1 ]] && _LOG_MODES="${_LOG_MODES:+${_LOG_MODES}-}programs"
615
- [[ "$_LOG_MODES" == "wifi-health-programs" ]] && _LOG_MODES="all"
616
+ [[ $FIX_ERRORS -eq 1 ]] && _LOG_MODES="${_LOG_MODES:+${_LOG_MODES}-}errors"
617
+ [[ "$_LOG_MODES" == "wifi-health-programs-errors" ]] && _LOG_MODES="all"
616
618
  LOG_FILE="${LOG_DIR}/fix_${_LOG_MODES}_$(date '+%Y%m%d_%H%M%S').log"
617
619
  flog() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }
618
620
  ffix_ok() { spin_ok "$*"; flog "FIXED : $*"; FIX_COUNT=$((FIX_COUNT+1)); }
@@ -621,7 +623,7 @@ fix)
621
623
  ffix_err() { spin_err "$*"; flog "ERROR : $*"; }
622
624
 
623
625
  FIX_COUNT=0
624
- flog "=== Run started | wifi=${FIX_WIFI} health=${FIX_HEALTH} programs=${FIX_PROGRAMS} ==="
626
+ flog "=== Run started | wifi=${FIX_WIFI} health=${FIX_HEALTH} programs=${FIX_PROGRAMS} errors=${FIX_ERRORS} ==="
625
627
 
626
628
  banner "Auto Fix"
627
629
  echo ""
@@ -878,6 +880,216 @@ SMBC
878
880
  echo ""
879
881
  fi
880
882
 
883
+ # ─────────────────────────────────────────────────────────────────────────
884
+ # ERRORS — journal scan, RT scheduling, permissions, PLC reach, config validity
885
+ # ─────────────────────────────────────────────────────────────────────────
886
+ if [[ $FIX_ERRORS -eq 1 ]]; then
887
+ echo -e " ${B}▸ Programmatic Errors${NC}"; hr; echo ""
888
+
889
+ # ── 1. Service restart / crash count ─────────────────────────────────
890
+ spin_start "Service stability"
891
+ _SVC_STABLE=1
892
+ for _svc in plc_watcher plc_web; do
893
+ _RC=$(systemctl show -p NRestarts --value "$_svc" 2>/dev/null || echo "0")
894
+ _RC="${_RC:-0}"
895
+ if [[ "$_RC" =~ ^[0-9]+$ && "$_RC" -gt 10 ]]; then
896
+ _spin_kill
897
+ ffix_warn "${_svc}: ${_RC} restarts — likely crash loop"
898
+ flog "WARN: ${_svc} restart count=${_RC}"
899
+ _SVC_STABLE=0
900
+ elif [[ "$_RC" =~ ^[0-9]+$ && "$_RC" -gt 3 ]]; then
901
+ _spin_kill
902
+ ffix_warn "${_svc}: ${_RC} restart(s) since last boot"
903
+ flog "WARN: ${_svc} restart count=${_RC}"
904
+ _SVC_STABLE=0
905
+ fi
906
+ done
907
+ [[ $_SVC_STABLE -eq 1 ]] && ffix_info "Both services stable (low restart count)"
908
+
909
+ # ── 2. Journal error scan — last 2 hours ─────────────────────────────
910
+ spin_start "Scanning journal for errors (last 2 h)"
911
+ _J_RAW=$(journalctl -u plc_watcher -u plc_web --since "2 hours ago" \
912
+ --no-pager -q 2>/dev/null \
913
+ | grep -iE \
914
+ '(Traceback|ImportError|ModuleNotFoundError|SyntaxError|PermissionError|FileNotFoundError|JSONDecodeError|OSError|ConnectionRefusedError|TimeoutError|CRITICAL|FATAL)' \
915
+ | grep -v '^-- Logs begin' \
916
+ | tail -30 || true)
917
+
918
+ if [[ -n "$_J_RAW" ]]; then
919
+ _spin_kill
920
+ warn "Errors found in recent journal:"
921
+ while IFS= read -r _line; do
922
+ # Trim to 110 chars for display
923
+ printf " ${R}│${NC} ${D}%.110s${NC}\n" "$_line"
924
+ flog "JOURNAL: $_line"
925
+ done <<< "$_J_RAW"
926
+ echo ""
927
+
928
+ # ── Auto-fix: missing Python module ──────────────────────────────
929
+ _MISSING_MOD=$(echo "$_J_RAW" \
930
+ | grep -oP "No module named ['\"]?\K[a-zA-Z0-9_]+" | head -1 || true)
931
+ if [[ -n "$_MISSING_MOD" ]]; then
932
+ spin_start "Auto-fix: installing missing module '${_MISSING_MOD}'"
933
+ if "${PYTHON}" -m pip install "${_MISSING_MOD}" -q 2>/dev/null; then
934
+ ffix_ok "pip installed: ${_MISSING_MOD}"
935
+ sudo systemctl restart plc_watcher plc_web 2>/dev/null || true
936
+ ffix_ok "Services restarted after module install"
937
+ else
938
+ ffix_warn "pip install failed for: ${_MISSING_MOD} — try manually"
939
+ fi
940
+ fi
941
+
942
+ # ── Auto-fix: corrupt delivery_queue.json ─────────────────────────
943
+ if echo "$_J_RAW" | grep -q "JSONDecodeError"; then
944
+ _QFILE="${DATA_DIR}/delivery_queue.json"
945
+ if [[ -f "$_QFILE" ]]; then
946
+ spin_start "Auto-fix: resetting corrupt delivery queue"
947
+ cp "$_QFILE" "${_QFILE}.broken.$(date +%s)" 2>/dev/null || true
948
+ echo "[]" > "$_QFILE"
949
+ ffix_ok "delivery_queue.json reset (broken copy saved)"
950
+ fi
951
+ fi
952
+
953
+ # ── Auto-fix: PermissionError on data/ files ──────────────────────
954
+ if echo "$_J_RAW" | grep -q "PermissionError"; then
955
+ spin_start "Auto-fix: correcting data/ permissions"
956
+ sudo chown pi:pi "${DATA_DIR}" 2>/dev/null || true
957
+ sudo chmod 755 "${DATA_DIR}" 2>/dev/null || true
958
+ sudo chown pi:pi "${DATA_DIR}"/* 2>/dev/null || true
959
+ sudo chmod 644 "${DATA_DIR}"/* 2>/dev/null || true
960
+ ffix_ok "data/ ownership set to pi:pi 755/644"
961
+ fi
962
+
963
+ # ── Auto-fix: FileNotFoundError on a data/ file ───────────────────
964
+ if echo "$_J_RAW" | grep -q "FileNotFoundError.*data"; then
965
+ spin_start "Auto-fix: recreating missing data files"
966
+ mkdir -p "${DATA_DIR}" 2>/dev/null || true
967
+ [[ -f "${DATA_DIR}/delivery_queue.json" ]] \
968
+ || echo "[]" > "${DATA_DIR}/delivery_queue.json"
969
+ [[ -f "${DATA_DIR}/delivery_sent.log" ]] \
970
+ || touch "${DATA_DIR}/delivery_sent.log"
971
+ chown pi:pi "${DATA_DIR}"/* 2>/dev/null || true
972
+ ffix_ok "Missing data files recreated"
973
+ fi
974
+
975
+ else
976
+ ffix_info "No Python exceptions in recent logs"
977
+ fi
978
+
979
+ # ── 3. RT scheduling verification ─────────────────────────────────────
980
+ spin_start "RT scheduling (plc_watcher)"
981
+ _WPD=$(systemctl show -p MainPID --value plc_watcher 2>/dev/null || echo "0")
982
+ _WPD="${_WPD//[^0-9]/}"
983
+ if [[ "${_WPD:-0}" -gt 0 ]]; then
984
+ _SCHED=$(chrt -p "$_WPD" 2>/dev/null || echo "")
985
+ if echo "$_SCHED" | grep -q "SCHED_FIFO"; then
986
+ _PRIO=$(echo "$_SCHED" | grep -oP 'priority: \K\d+' || echo "?")
987
+ _AFF=$(taskset -cp "$_WPD" 2>/dev/null | grep -oP 'list: \K.*' || echo "?")
988
+ ffix_info "SCHED_FIFO:${_PRIO} cpus:${_AFF} pid:${_WPD}"
989
+ flog "INFO: plc_watcher SCHED_FIFO:${_PRIO} cpu:${_AFF}"
990
+ else
991
+ ffix_warn "RT scheduling not active — reloading unit and restarting"
992
+ flog "WARN: plc_watcher not SCHED_FIFO: ${_SCHED}"
993
+ sudo systemctl daemon-reload 2>/dev/null || true
994
+ sudo systemctl restart plc_watcher 2>/dev/null && ffix_ok "Service restarted" || ffix_err "Restart failed"
995
+ fi
996
+ else
997
+ ffix_warn "plc_watcher not running — cannot check RT scheduling"
998
+ fi
999
+
1000
+ # ── 4. data/ directory writability ────────────────────────────────────
1001
+ spin_start "data/ directory"
1002
+ if [[ ! -d "${DATA_DIR}" ]]; then
1003
+ sudo mkdir -p "${DATA_DIR}" 2>/dev/null || true
1004
+ sudo chown pi:pi "${DATA_DIR}" && sudo chmod 755 "${DATA_DIR}"
1005
+ ffix_ok "Created missing data/"
1006
+ elif [[ ! -w "${DATA_DIR}" ]]; then
1007
+ sudo chown pi:pi "${DATA_DIR}" && sudo chmod 755 "${DATA_DIR}"
1008
+ ffix_ok "Fixed data/ permissions (was not writable by pi)"
1009
+ flog "FIXED: data/ was not writable — corrected to pi:pi 755"
1010
+ else
1011
+ ffix_info "data/ writable by pi"
1012
+ fi
1013
+
1014
+ # ── 5. smb_config.py syntax ───────────────────────────────────────────
1015
+ spin_start "smb_config.py syntax"
1016
+ if [[ -f "${SMB_CFG}" ]]; then
1017
+ _SMB_ERR=$("${PYTHON}" -c "import ast; ast.parse(open('${SMB_CFG}').read())" 2>&1 || true)
1018
+ if [[ -n "${_SMB_ERR}" ]]; then
1019
+ flog "ERROR: smb_config.py syntax: ${_SMB_ERR}"
1020
+ ffix_warn "smb_config.py has syntax errors — resetting to disabled"
1021
+ printf 'SMB_ENABLED = False\nSMB_HOST = ""\nSMB_SHARE = "Reports"\nSMB_USERNAME = ""\nSMB_PASSWORD = ""\nSMB_SUBDIR = ""\n' \
1022
+ > "${SMB_CFG}" 2>/dev/null || true
1023
+ ffix_ok "smb_config.py reset — run: plc_checkweigher smb-config"
1024
+ else
1025
+ _SMB_EN=$(grep "^SMB_ENABLED" "${SMB_CFG}" 2>/dev/null \
1026
+ | grep -qi "true" && echo "enabled" || echo "disabled")
1027
+ _SMB_H=$(smb_get "SMB_HOST")
1028
+ ffix_info "smb_config.py valid (SMB: ${_SMB_EN}${_SMB_H:+ → ${_SMB_H}})"
1029
+ fi
1030
+ else
1031
+ ffix_warn "smb_config.py missing — run: plc_checkweigher smb-config"
1032
+ flog "WARN: smb_config.py not found at ${SMB_CFG}"
1033
+ fi
1034
+
1035
+ # ── 6. Source file protection (root:root 644) ─────────────────────────
1036
+ spin_start "Source file protection"
1037
+ _WRONG=$(find "${INSTALL_DIR}" -maxdepth 1 -name "*.py" \
1038
+ ! -user root 2>/dev/null | wc -l || echo 0)
1039
+ if [[ "${_WRONG:-0}" -gt 0 ]]; then
1040
+ ffix_warn "${_WRONG} source .py file(s) not root-owned — locking"
1041
+ flog "WARN: ${_WRONG} source file(s) had wrong owner"
1042
+ sudo find "${INSTALL_DIR}" -maxdepth 1 -name "*.py" \
1043
+ -exec chown root:root {} \; -exec chmod 644 {} \; 2>/dev/null || true
1044
+ sudo find "${INSTALL_DIR}/web" -name "*.py" \
1045
+ -exec chown root:root {} \; -exec chmod 644 {} \; 2>/dev/null || true
1046
+ ffix_ok "Source files re-locked to root:root 644"
1047
+ else
1048
+ ffix_info "Source files protected (root:root 644)"
1049
+ fi
1050
+
1051
+ # ── 7. PLC TCP reachability ───────────────────────────────────────────
1052
+ spin_start "PLC TCP connection (192.168.3.250:1025)"
1053
+ if timeout 3 bash -c "echo >/dev/tcp/192.168.3.250/1025" 2>/dev/null; then
1054
+ ffix_info "PLC reachable at 192.168.3.250:1025"
1055
+ else
1056
+ ffix_warn "PLC not reachable at 192.168.3.250:1025"
1057
+ flog "WARN: PLC TCP connect failed (192.168.3.250:1025)"
1058
+ echo ""
1059
+ info "Possible causes:"
1060
+ info " • eth0 cable unplugged (check: ip link show eth0)"
1061
+ info " • PLC powered off / not ready"
1062
+ info " • SLMP not enabled in GX Works (Enable SLMP TCP port 1025)"
1063
+ info " • Wrong subnet on eth0 (should be 192.168.3.x)"
1064
+ echo ""
1065
+ fi
1066
+
1067
+ # ── 8. plc_live.json staleness ────────────────────────────────────────
1068
+ spin_start "Live state file (/tmp/plc_live.json)"
1069
+ if [[ -f "/tmp/plc_live.json" ]]; then
1070
+ _AGE=$(( $(date +%s) - $(stat -c %Y /tmp/plc_live.json 2>/dev/null || echo 0) ))
1071
+ if [[ "${_AGE:-0}" -gt 10 ]]; then
1072
+ ffix_warn "plc_live.json is ${_AGE}s old — watcher may be stuck or offline"
1073
+ flog "WARN: plc_live.json stale (${_AGE}s old)"
1074
+ else
1075
+ ffix_info "plc_live.json updated ${_AGE}s ago (OK)"
1076
+ fi
1077
+ else
1078
+ ffix_info "plc_live.json absent (normal — created when watcher connects)"
1079
+ fi
1080
+
1081
+ # ── 9. reader script path ─────────────────────────────────────────────
1082
+ spin_start "plc_reader.py present"
1083
+ if [[ ! -f "${INSTALL_DIR}/plc_reader.py" ]]; then
1084
+ ffix_err "plc_reader.py missing — watcher cannot launch it"
1085
+ flog "ERROR: plc_reader.py not found at ${INSTALL_DIR}/plc_reader.py"
1086
+ else
1087
+ ffix_info "plc_reader.py present and readable"
1088
+ fi
1089
+
1090
+ echo ""
1091
+ fi
1092
+
881
1093
  # ── Summary ───────────────────────────────────────────────────────────────
882
1094
  flog "=== Complete: ${FIX_COUNT} fix(es) applied ==="
883
1095
  hr
@@ -1158,6 +1370,7 @@ help|--help|-h)
1158
1370
  echo " fix -wifi Fix WiFi connectivity only"
1159
1371
  echo " fix -health Fix disk, memory, time, dirs"
1160
1372
  echo " fix -programs Fix services, packages, queue, unit files"
1373
+ echo " fix -errors Scan journal, verify RT/permissions/PLC/config"
1161
1374
  echo " logs Stream live logs (plc_watcher + plc_web)"
1162
1375
  echo " queue Show SMB pending queue and delivery ledger"
1163
1376
  echo ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plc-checkweigher",
3
- "version": "1.20.0",
3
+ "version": "1.21.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
  "scripts": {
6
6
  "postinstall": "node bin/cli.js"