flash-fluidnc.sh 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. #!/bin/bash
  2. #
  3. # Flash FluidNC firmware onto a connected ESP32 board
  4. #
  5. # Usage: bash flash-fluidnc.sh
  6. #
  7. # Interactive script that:
  8. # 1. Lets user choose FluidNC version (v3.9.5 for all tables, v3.8.3 for Mini Dune Weaver)
  9. # 2. Lets user select a serial port
  10. # 3. Downloads, extracts, and flashes the firmware
  11. # 4. Confirms success by checking esptool output
  12. #
  13. set -e
  14. # Warn if running as root/sudo — venv and pip won't work correctly
  15. if [[ $EUID -eq 0 ]]; then
  16. echo -e "\033[1;33mWarning:\033[0m Running as root is not recommended."
  17. echo "If you need serial port access, add your user to the dialout group instead:"
  18. echo " sudo usermod -a -G dialout \$USER"
  19. echo "Then log out and back in, and run: bash flash-fluidnc.sh"
  20. echo ""
  21. read -p "Continue anyway? [y/N]: " root_confirm
  22. if [[ ! "$root_confirm" =~ ^[Yy] ]]; then
  23. exit 1
  24. fi
  25. fi
  26. # Colors
  27. RED='\033[0;31m'
  28. GREEN='\033[0;32m'
  29. YELLOW='\033[1;33m'
  30. BLUE='\033[0;34m'
  31. CYAN='\033[0;36m'
  32. BOLD='\033[1m'
  33. NC='\033[0m'
  34. # Versions
  35. VERSION_ALL="3.9.5"
  36. VERSION_MINI="3.8.3"
  37. GITHUB_BASE="https://github.com/bdring/FluidNC/releases/download"
  38. cleanup() {
  39. if [[ -n "${WORK_DIR:-}" && -d "$WORK_DIR" ]]; then
  40. echo -e "\n${BLUE}Cleaning up temporary files...${NC}"
  41. rm -rf "$WORK_DIR"
  42. fi
  43. }
  44. trap cleanup EXIT
  45. # ─── Step 1: Choose version ────────────────────────────────────────────────
  46. echo -e "${BOLD}${CYAN}"
  47. echo " ╔═══════════════════════════════════════╗"
  48. echo " ║ FluidNC Firmware Flasher ║"
  49. echo " ║ for Dune Weaver ║"
  50. echo " ╚═══════════════════════════════════════╝"
  51. echo -e "${NC}"
  52. echo -e "${BOLD}Select FluidNC version:${NC}"
  53. echo ""
  54. echo -e " ${GREEN}1)${NC} v${VERSION_ALL} - All tables (Dune Weaver, Pro, Gold, Mini Pro)"
  55. echo -e " ${GREEN}2)${NC} v${VERSION_MINI} - Mini Dune Weaver only"
  56. echo ""
  57. read -p "Enter choice [1/2]: " version_choice
  58. case "$version_choice" in
  59. 1)
  60. VERSION="$VERSION_ALL"
  61. echo -e "\nSelected: ${GREEN}FluidNC v${VERSION}${NC}"
  62. ;;
  63. 2)
  64. VERSION="$VERSION_MINI"
  65. echo -e "\nSelected: ${GREEN}FluidNC v${VERSION}${NC}"
  66. ;;
  67. *)
  68. echo -e "${RED}Invalid choice. Exiting.${NC}"
  69. exit 1
  70. ;;
  71. esac
  72. # ─── Step 2: Detect and select serial port ─────────────────────────────────
  73. echo ""
  74. echo -e "${BOLD}Detecting serial ports...${NC}"
  75. # Collect available serial ports (Linux + macOS patterns)
  76. PORTS=()
  77. for port in /dev/ttyUSB* /dev/ttyACM* /dev/cu.usbserial* /dev/cu.wchusbserial* /dev/cu.SLAB_USBtoUART*; do
  78. if [[ -e "$port" ]]; then
  79. PORTS+=("$port")
  80. fi
  81. done
  82. if [[ ${#PORTS[@]} -eq 0 ]]; then
  83. echo -e "${RED}No serial ports found!${NC}"
  84. echo ""
  85. echo "Make sure your ESP32 board is connected via USB."
  86. echo "On Linux, you may need to add your user to the 'dialout' group:"
  87. echo " sudo usermod -a -G dialout \$USER"
  88. exit 1
  89. fi
  90. if [[ ${#PORTS[@]} -eq 1 ]]; then
  91. PORT="${PORTS[0]}"
  92. echo -e "Found port: ${GREEN}${PORT}${NC}"
  93. read -p "Use this port? [Y/n]: " confirm
  94. if [[ "$confirm" =~ ^[Nn] ]]; then
  95. echo -e "${RED}Exiting.${NC}"
  96. exit 1
  97. fi
  98. else
  99. echo -e "Found ${GREEN}${#PORTS[@]}${NC} serial ports:"
  100. echo ""
  101. for i in "${!PORTS[@]}"; do
  102. echo -e " ${GREEN}$((i + 1)))${NC} ${PORTS[$i]}"
  103. done
  104. echo ""
  105. read -p "Select port [1-${#PORTS[@]}]: " port_choice
  106. if [[ "$port_choice" -ge 1 && "$port_choice" -le ${#PORTS[@]} ]] 2>/dev/null; then
  107. PORT="${PORTS[$((port_choice - 1))]}"
  108. else
  109. echo -e "${RED}Invalid choice. Exiting.${NC}"
  110. exit 1
  111. fi
  112. fi
  113. echo -e "Using port: ${GREEN}${PORT}${NC}"
  114. # ─── Step 3: Check for esptool ─────────────────────────────────────────────
  115. # Find a working pip/venv to install esptool (avoids PEP 668 externally-managed errors)
  116. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  117. VENV_DIR=""
  118. if [[ -f "$SCRIPT_DIR/.venv/bin/activate" ]]; then
  119. VENV_DIR="$SCRIPT_DIR/.venv"
  120. elif [[ -f "$HOME/dune-weaver/.venv/bin/activate" ]]; then
  121. VENV_DIR="$HOME/dune-weaver/.venv"
  122. fi
  123. if [[ -n "$VENV_DIR" ]]; then
  124. source "$VENV_DIR/bin/activate"
  125. fi
  126. if ! command -v esptool.py &> /dev/null && ! command -v esptool &> /dev/null; then
  127. echo ""
  128. echo -e "${YELLOW}esptool not found. Installing...${NC}"
  129. # Try the venv pip directly first, then fall back to creating a temp venv
  130. if [[ -n "$VENV_DIR" && -x "$VENV_DIR/bin/pip" ]]; then
  131. "$VENV_DIR/bin/pip" install esptool
  132. elif python3 -m venv /tmp/esptool-venv 2>/dev/null; then
  133. echo -e "${YELLOW}Creating temporary venv for esptool...${NC}"
  134. source /tmp/esptool-venv/bin/activate
  135. pip install esptool
  136. else
  137. # Last resort: override PEP 668 restriction
  138. pip install --break-system-packages esptool 2>/dev/null || pip install esptool
  139. fi
  140. fi
  141. # Determine the esptool command name
  142. if command -v esptool.py &> /dev/null; then
  143. ESPTOOL="esptool.py"
  144. elif command -v esptool &> /dev/null; then
  145. ESPTOOL="esptool"
  146. else
  147. echo -e "${RED}Failed to install esptool. Please install manually: pip install esptool${NC}"
  148. exit 1
  149. fi
  150. # ─── Step 4: Download and extract ──────────────────────────────────────────
  151. ZIP_NAME="fluidnc-v${VERSION}-posix.zip"
  152. DOWNLOAD_URL="${GITHUB_BASE}/v${VERSION}/${ZIP_NAME}"
  153. EXTRACT_DIR="fluidnc-v${VERSION}-posix"
  154. WORK_DIR=$(mktemp -d)
  155. echo ""
  156. echo -e "${BLUE}Downloading FluidNC v${VERSION}...${NC}"
  157. if ! curl -L --fail --progress-bar "$DOWNLOAD_URL" -o "$WORK_DIR/$ZIP_NAME"; then
  158. echo -e "${RED}Download failed!${NC}"
  159. echo "URL: $DOWNLOAD_URL"
  160. exit 1
  161. fi
  162. echo -e "${BLUE}Extracting...${NC}"
  163. unzip -q "$WORK_DIR/$ZIP_NAME" -d "$WORK_DIR"
  164. if [[ ! -d "$WORK_DIR/$EXTRACT_DIR" ]]; then
  165. echo -e "${RED}Expected directory $EXTRACT_DIR not found after extraction.${NC}"
  166. exit 1
  167. fi
  168. FLASH_DIR="$WORK_DIR/$EXTRACT_DIR"
  169. # ─── Step 5: Flash firmware ────────────────────────────────────────────────
  170. echo ""
  171. echo -e "${BOLD}${CYAN}══════════════════════════════════════════${NC}"
  172. echo -e "${BOLD} Flashing FluidNC v${VERSION} to ${PORT}${NC}"
  173. echo -e "${BOLD}${CYAN}══════════════════════════════════════════${NC}"
  174. echo ""
  175. echo -e "${YELLOW}Do NOT disconnect the board during flashing!${NC}"
  176. echo ""
  177. FLASH_SUCCESS=true
  178. # --- 5a: Flash WiFi firmware ---
  179. echo -e "${BLUE}[1/2] Flashing WiFi firmware...${NC}"
  180. echo ""
  181. WIFI_OUTPUT=$($ESPTOOL --chip esp32 --port "$PORT" --baud 460800 \
  182. --before default_reset --after hard_reset \
  183. write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect \
  184. 0x1000 "$FLASH_DIR/wifi/bootloader.bin" \
  185. 0x8000 "$FLASH_DIR/wifi/partitions.bin" \
  186. 0xe000 "$FLASH_DIR/common/boot_app0.bin" \
  187. 0x10000 "$FLASH_DIR/wifi/firmware.bin" 2>&1) || true
  188. echo "$WIFI_OUTPUT"
  189. echo ""
  190. if echo "$WIFI_OUTPUT" | grep -q "Hard resetting via RTS pin"; then
  191. echo -e "${GREEN}Firmware flash successful!${NC}"
  192. else
  193. echo -e "${RED}Firmware flash may have failed!${NC}"
  194. FLASH_SUCCESS=false
  195. fi
  196. # --- 5b: Flash filesystem (LittleFS) ---
  197. echo ""
  198. echo -e "${BLUE}[2/2] Flashing filesystem (LittleFS)...${NC}"
  199. echo ""
  200. FS_OUTPUT=$($ESPTOOL --chip esp32 --port "$PORT" --baud 460800 \
  201. --before default_reset --after hard_reset \
  202. write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect \
  203. 0x3d0000 "$FLASH_DIR/wifi/littlefs.bin" 2>&1) || true
  204. echo "$FS_OUTPUT"
  205. echo ""
  206. if echo "$FS_OUTPUT" | grep -q "Hard resetting via RTS pin"; then
  207. echo -e "${GREEN}Filesystem flash successful!${NC}"
  208. else
  209. echo -e "${RED}Filesystem flash may have failed!${NC}"
  210. FLASH_SUCCESS=false
  211. fi
  212. # ─── Step 6: Summary ──────────────────────────────────────────────────────
  213. echo ""
  214. echo -e "${BOLD}${CYAN}══════════════════════════════════════════${NC}"
  215. if [[ "$FLASH_SUCCESS" == true ]]; then
  216. echo -e "${GREEN} FluidNC v${VERSION} flashed successfully!${NC}"
  217. echo -e "${BOLD}${CYAN}══════════════════════════════════════════${NC}"
  218. echo ""
  219. echo "The board has been reset and should now be running FluidNC."
  220. echo "Connect to the 'FluidNC' WiFi network to access the web UI."
  221. else
  222. echo -e "${RED} Flashing completed with errors!${NC}"
  223. echo -e "${BOLD}${CYAN}══════════════════════════════════════════${NC}"
  224. echo ""
  225. echo "Review the output above for error details."
  226. echo "Common fixes:"
  227. echo " - Hold the BOOT button on the ESP32 while flashing"
  228. echo " - Try a different USB cable (data cable, not charge-only)"
  229. echo " - Check port permissions: sudo chmod 666 $PORT"
  230. echo " - Try a lower baud rate by editing this script (change 460800 to 115200)"
  231. exit 1
  232. fi