#!/bin/bash # install.sh — LARA-L6004D-01B-00 + Raspberry PLC v6 # Fecha: 2025-10 # Autor: Industrial Shields® # Usage: sudo bash install.sh set -Eeuo pipefail # =============================== # 0) Pre-checks # =============================== [[ ${EUID} -eq 0 ]] || { echo "[ERROR] Run as root (sudo)."; exit 1; } command -v apt-get >/dev/null || { echo "[ERROR] Debian/Ubuntu required (apt-get)."; exit 1; } # =============================== # 1) Ask for credentials / parameters # =============================== read -rp "APN (p.ej. 'internet'): " APN read -rp "ISP username (press enter if not applicable): " USERNAME read -rsp "ISP password (press enter if not applicable): " PASSWORD; echo if [[ -n "${USERNAME}" || -n "${PASSWORD}" ]]; then DEFAULT_AUTH="chap" else DEFAULT_AUTH="none" fi AUTH=${DEFAULT_AUTH} [[ -n "${APN}" ]] || { echo "[ERROR] APN cannot be empty"; exit 1; } # =============================== # 2) Install dependencies # =============================== export DEBIAN_FRONTEND=noninteractive apt-get update -y apt-get install -y --no-install-recommends \ libqmi-utils python3 python3-serial ppp \ jq iproute2 iputils-ping udev # =============================== # 3) Write common configuration # =============================== install -d -m 0755 /etc cat >/etc/lara.env </usr/local/bin/send-at-lara.py <<'PYEOF' #!/usr/bin/python3 # -*- coding: utf-8 -*- import os, sys, time, re, subprocess from pathlib import Path import serial APN = os.environ.get("APN", "") USER = os.environ.get("USERNAME", "") PASSWORD = os.environ.get("PASSWORD", "") AUTH = os.environ.get("AUTH", "none").lower() CID = int(os.environ.get("CID", "1")) MNOPROF = os.environ.get("MNOPROF", "90") RAT = os.environ.get("RAT", "3,2,0") USBCONF = os.environ.get("USBCONF", '104,"RMNET"') TTY_DEV = os.environ.get("TTY_DEV", "/dev/ttySC0") TARGET_BAUD= int(os.environ.get("TARGET_BAUD", "115200")) OK_PATTERNS = (b"\r\nOK\r\n", b"OK\r\n", b"\r\nOK\r\r\n") ERROR_PATTERNS = (b"\r\nERROR\r\n", b"ERROR\r\n") def open_serial(): ser = serial.Serial( port=TTY_DEV, baudrate=TARGET_BAUD, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=0.2, xonxoff=False, rtscts=False, dsrdtr=False, write_timeout=1.0, ) ser.reset_input_buffer(); ser.reset_output_buffer() return ser def printable(b: bytes) -> str: KEEP = {9,10,13}; out=[] for x in b: if x in KEEP or 0x20 <= x <= 0x7E: out.append(chr(x)) return ''.join(out) def send_and_capture(ser, cmd, timeout_s=3.0): wire = (cmd + "\r\n").encode("ascii", errors="ignore") print(f"--> {cmd}"); ser.write(wire); ser.flush() buf = bytearray(); deadline = time.time() + timeout_s while time.time() < deadline: rx = ser.read(256) if rx: buf.extend(rx) if any(p in buf for p in OK_PATTERNS) or any(p in buf for p in ERROR_PATTERNS): break else: time.sleep(0.05) s = printable(bytes(buf)) print("<-- {}".format(s if s else "")) return s def send_and_wait_ok(ser, cmd, timeout_s=3.0): s = send_and_capture(ser, cmd, timeout_s) b = s.encode("ascii", errors="ignore") return (any(p in b for p in OK_PATTERNS) and not any(p in b for p in ERROR_PATTERNS)) def send_with_retry(ser, cmd, timeout_s, attempts=3, retry_delay_s=1.0): for n in range(1, attempts+1): if send_and_wait_ok(ser, cmd, timeout_s): return True print(f"'{cmd}' ** retry: {n}/{attempts}"); time.sleep(retry_delay_s) print(f"[FAIL] '{cmd}' sin OK tras {attempts} intentos"); return False def wait_ready_after_cfun16(ser, max_wait_s=60): deadline = time.time()+max_wait_s; gap=1.0 while time.time()/usr/local/bin/setup-lara.sh <<'BASHEOF' #!/bin/bash set -Eeuo pipefail source /etc/lara.env APN=${APN:?APN missing} USERNAME=${USERNAME:-} PASSWORD=${PASSWORD:-} AUTH=${AUTH:-none} CID=${CID:-1} PORT=${QMI_PORT:-/dev/cdc-wdm0} IFACE=${WWAN_IFACE:-wwan0} TTY_DEV=${TTY_DEV:-/dev/ttySC0} TARGET_BAUD=${TARGET_BAUD:-115200} QMI_WAIT_SECS=${QMI_WAIT_SECS:-25} wait_for() { # $1: path or netif, $2: secs, $3: type(file|net) local t=${2:-20}; local what="$1"; local kind="${3:-file}" while (( t>0 )); do if [[ "$kind" == "file" && -e "$what" ]]; then return 0; fi if [[ "$kind" == "net" && -d "/sys/class/net/${what}" ]]; then return 0; fi sleep 1; ((t--)) done return 1 } if command -v stty >/dev/null 2>&1; then stty -F "$TTY_DEV" ${TARGET_BAUD} cs8 -parenb -cstopb -ixon -ixoff -crtscts raw -echo || true fi APN="$APN" USERNAME="$USERNAME" PASSWORD="$PASSWORD" AUTH="$AUTH" CID="$CID" \ TTY_DEV="$TTY_DEV" TARGET_BAUD="$TARGET_BAUD" \ /usr/bin/python3 /usr/local/bin/send-at-lara.py || true modprobe cdc_wdm || true modprobe qmi_wwan || true modprobe qcserial || true if [[ "${ADD_QMI_NEW_ID:-yes}" == "yes" ]]; then echo "1546 134a" > /sys/bus/usb/drivers/qmi_wwan/new_id 2>/dev/null || true fi if ! wait_for "$PORT" "$QMI_WAIT_SECS" file; then echo "[ERROR] No apareció $PORT en ${QMI_WAIT_SECS}s. Conecta USB al LARA y reintenta." exit 0 fi wait_for "$IFACE" "$QMI_WAIT_SECS" net || echo "[WARN] ${IFACE} no visible todavía." if [[ -d "/sys/class/net/${IFACE}" ]]; then ip link set "$IFACE" down || true if [[ -e "/sys/class/net/${IFACE}/qmi/raw_ip" ]]; then echo Y > "/sys/class/net/${IFACE}/qmi/raw_ip"; fi ip addr flush dev "$IFACE" || true ip link set "$IFACE" up || true else echo "[WARN] Interface ${IFACE} does not exist; continue with QMI anyways." fi if [[ "${DISABLE_MODEMMANAGER:-yes}" == "yes" ]]; then systemctl stop ModemManager.service 2>/dev/null || true fi pkill qmi-proxy 2>/dev/null || true if ! pgrep -x qmi-proxy >/dev/null 2>&1; then [[ -x /usr/lib/qmi-proxy ]] && /usr/lib/qmi-proxy >/dev/null 2>&1 & fi qmicli -p -d "$PORT" --dms-get-manufacturer || true WDS_ARGS="apn='${APN}',ip-type=4" case "${AUTH}" in chap) WDS_ARGS+=" ,username='${USERNAME}',password='${PASSWORD}',auth=chap" ;; pap) WDS_ARGS+=" ,username='${USERNAME}',password='${PASSWORD}',auth=pap" ;; *) : ;; esac qmicli -p -d "$PORT" --wds-start-network="$WDS_ARGS" --client-no-release-cid || true if [[ -d "/sys/class/net/${IFACE}" ]]; then if command -v dhclient >/dev/null; then dhclient -1 "$IFACE" || true elif command -v udhcpc >/dev/null; then udhcpc -i "$IFACE" -q -n || true fi if ! ip -4 addr show dev "$IFACE" | grep -q "inet "; then SETTINGS=$(qmicli -p -d "$PORT" --wds-get-current-settings 2>/dev/null || true) IP=$(printf "%s\n" "$SETTINGS" | sed -n 's/.*IPv4 address: \([0-9.]\+\).*/\1/p' | head -1) GW=$(printf "%s\n" "$SETTINGS" | sed -n 's/.*IPv4 gateway address: \([0-9.]\+\).*/\1/p' | head -1) DNS1=$(printf "%s\n" "$SETTINGS"| sed -n 's/.*IPv4 DNS \[1\]: \([0-9.]\+\).*/\1/p' | head -1) DNS2=$(printf "%s\n" "$SETTINGS"| sed -n 's/.*IPv4 DNS \[2\]: \([0-9.]\+\).*/\1/p' | head -1) MTU=$(printf "%s\n" "$SETTINGS" | sed -n 's/.*MTU: \([0-9]\+\).*/\1/p' | head -1) if [[ -n "${IP}" ]]; then ip addr flush dev "$IFACE" || true ip addr add "${IP}/32" dev "$IFACE" [[ -n "$MTU" ]] && ip link set dev "$IFACE" mtu "$MTU" || true fi if command -v resolvectl >/dev/null && [[ -n "${DNS1}" ]]; then resolvectl dns "$IFACE" $DNS1 ${DNS2:+$DNS2} resolvectl domain "$IFACE" "~." fi if [[ -z "${GW:-}" ]]; then SETTINGS=$(qmicli -p -d "$PORT" --wds-get-current-settings 2>/dev/null || true) GW=$(printf "%s\n" "$SETTINGS" | sed -n 's/.*IPv4 gateway address: \([0-9.]\+\).*/\1/p' | head -1) fi if [[ -n "${GW:-}" ]]; then ip route replace "${GW}/32" dev "$IFACE" scope link 2>/dev/null || true ip route replace default via "$GW" dev "$IFACE" metric 50 onlink ip route del default dev eth0 2>/dev/null \ || ip route change default dev eth0 metric 500 2>/dev/null || true fi fi printf "net.ipv4.conf.all.rp_filter=0\nnet.ipv4.conf.%s.rp_filter=0\n" "$IFACE" >/etc/sysctl.d/99-lara.conf sysctl -p /etc/sysctl.d/99-lara.conf || true ip addr show "$IFACE" || true ip route || true ping -c 3 -I "$IFACE" 8.8.8.8 || true else echo "[WARN] ${IFACE} does not exist; check USB cable and dmesg." fi BASHEOF chmod 755 /usr/local/bin/setup-lara.sh # =============================== # 6) Udev rule # =============================== cat >/etc/udev/rules.d/99-ublox-qmi.rules <<'RULES' ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="1546", RUN+="/bin/sh -c 'echo 1546 134a > /sys/bus/usb/drivers/qmi_wwan/new_id || true'" RULES udevadm control --reload || true # =============================== # 7) systemd unit # =============================== cat >/etc/systemd/system/lara-network.service <<'UNIT' [Unit] Description=LARA-L6004D bringup (UART AT + USB QMI) After=network-pre.target Wants=network-pre.target [Service] Type=oneshot EnvironmentFile=/etc/lara.env ExecStart=/usr/local/bin/setup-lara.sh RemainAfterExit=no StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target UNIT systemctl daemon-reload systemctl enable lara-network.service systemctl start lara-network.service echo -e "\n[OK] Installation completed. Settings in /etc/lara.env."