{"id":289,"date":"2026-06-28T02:48:28","date_gmt":"2026-06-28T01:48:28","guid":{"rendered":"https:\/\/myjetsonnano.perseus314.com\/?p=289"},"modified":"2026-06-28T02:48:28","modified_gmt":"2026-06-28T01:48:28","slug":"wifi-remote-control-mp3-player","status":"publish","type":"post","link":"https:\/\/myjetsonnano.perseus314.com\/index.php\/2026\/06\/28\/wifi-remote-control-mp3-player\/","title":{"rendered":"WiFi Remote Control MP3 Player"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">The controller (WIO Terminal)<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;rpcWiFi.h>\n#include &lt;WiFiUdp.h>\n#include &lt;TFT_eSPI.h>\n\n#include \"config.h\" \/\/ Pulls in WIFI_SSID, WIFI_PASSWORD, and UDP_PORT\n\n\/\/ UDP Configuration\nWiFiUDP udp;\n\n\/\/ TFT Screen\nTFT_eSPI tft = TFT_eSPI();\n\n\/\/ UI State Variables\nint selectedTrack = 1;\nconst int maxTracks = 10; \n\nvoid setup() {\n  Serial.begin(115200);\n  tft.begin();\n  tft.setRotation(3);\n  tft.fillScreen(TFT_BLACK);\n  tft.setTextSize(2);\n  tft.setTextColor(TFT_GREEN);\n  tft.drawString(\"Connecting to Wi-Fi...\", 10, 10);\n\n  \/\/ Configure Joystick &amp; Top Buttons as inputs (Fixed naming to WIO_5S)\n  pinMode(WIO_5S_UP, INPUT_PULLUP);\n  pinMode(WIO_5S_DOWN, INPUT_PULLUP);\n  pinMode(WIO_KEY_A, INPUT_PULLUP); \/\/ Right-most top button\n  pinMode(WIO_KEY_B, INPUT_PULLUP); \/\/ Middle top button\n\n  \/\/ Updated to use uppercase names from config.h\n  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n  while (WiFi.status() != WL_CONNECTED) {\n    delay(500);\n    Serial.print(\".\");\n  }\n\n  udp.begin(UDP_PORT); \/\/ Updated to match config.h\n  updateScreen();\n}\n\nvoid loop() {\n  \/\/ Joystick UP: Increment Track Number\n  if (digitalRead(WIO_5S_UP) == LOW) {\n    if (selectedTrack &lt; maxTracks) {\n      selectedTrack++;\n      updateScreen();\n      delay(200); \/\/ Debounce\n    }\n  }\n\n  \/\/ Joystick DOWN: Decrement Track Number\n  if (digitalRead(WIO_5S_DOWN) == LOW) {\n    if (selectedTrack > 1) {\n      selectedTrack--;\n      updateScreen();\n      delay(200);\n    }\n  }\n\n  \/\/ Button A (Right Top Button): SEND PLAY COMMAND\n  if (digitalRead(WIO_KEY_A) == LOW) {\n    sendUDPCommand(\"PLAY:\" + String(selectedTrack));\n    tft.setTextColor(TFT_RED);\n    tft.drawString(\"SENT: PLAY \" + String(selectedTrack), 10, 150);\n    delay(400);\n    updateScreen();\n  }\n\n  \/\/ Button B (Middle Top Button): SEND STOP COMMAND\n  if (digitalRead(WIO_KEY_B) == LOW) {\n    sendUDPCommand(\"STOP\");\n    tft.setTextColor(TFT_YELLOW);\n    tft.drawString(\"SENT: STOP      \", 10, 150);\n    delay(400);\n    updateScreen();\n  }\n}\n\nvoid updateScreen() {\n  tft.fillScreen(TFT_BLACK);\n  tft.setTextColor(TFT_GREEN);\n  tft.drawString(\"HALLOWEEN AUDIO MATRIX\", 10, 10);\n  \n  \/\/ Network Debug Info\n  tft.setTextSize(1);\n  tft.setTextColor(TFT_YELLOW);\n  tft.drawString(\"SSID: \" + String(WiFi.SSID()), 10, 35);\n  tft.drawString(\"IP:   \" + WiFi.localIP().toString(), 10, 48);\n  tft.drawString(\"-----------------------------------\", 10, 60);\n  \n  tft.setTextSize(2);\n  tft.setTextColor(TFT_WHITE);\n  tft.drawString(\"Selected Track: \" + String(selectedTrack), 10, 80);\n  \n  tft.setTextSize(1);\n  tft.drawString(\"Press Top Right Button (A) to PLAY\", 10, 120);\n  tft.drawString(\"Press Top Middle Button (B) to STOP\", 10, 135);\n  tft.setTextSize(2);\n}\n\nvoid sendUDPCommand(String msg) {\n  \/\/ Pass direct IPAddress object for subnet broadcast\n  udp.beginPacket(IPAddress(255, 255, 255, 255), UDP_PORT);\n  udp.print(msg);\n  udp.endPacket();\n  Serial.println(\"Broadcasted: \" + msg);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">The MP3 Player (M5Stack AtomS3U plus Grove MP3 v4.0)<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>#include \"..\/config.h\"\n\n#include &lt;Adafruit_NeoPixel.h>\n#include &lt;WiFi.h>\n#include &lt;WiFiUdp.h>\n\n\/\/ --- Hardware Pins ---\n#define LED_PIN         35\n#define NUM_LEDS        1\n\/\/ AtomS3U Grove port UART pins for the MP3 player\n#define GROVE_RX_PIN    1\n#define GROVE_TX_PIN    2\n\nAdafruit_NeoPixel rgb(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);\nWiFiUDP udp;\n\n\/\/ --- Status Color Functions ---\nvoid setStatusRed()    { rgb.setPixelColor(0, rgb.Color(255, 0, 0));   rgb.show(); } \/\/ Booted\nvoid setStatusYellow() { rgb.setPixelColor(0, rgb.Color(255, 255, 0)); rgb.show(); } \/\/ WiFi Connected\/Waiting\nvoid setStatusBlue()   { rgb.setPixelColor(0, rgb.Color(0, 0, 255));   rgb.show(); } \/\/ Raw Packet Received\nvoid setStatusGreen()  { rgb.setPixelColor(0, rgb.Color(0, 255, 0));   rgb.show(); } \/\/ Playing Song\nvoid setStatusPurple() { rgb.setPixelColor(0, rgb.Color(128, 0, 128)); rgb.show(); } \/\/ MP3 Error \/ Silence\n\nvoid initGroveMP3() {\n  delay(500);\n  Serial1.print(\"AT+VOL=10\\r\\n\");      \n  delay(100);\n  checkMP3Response();\n  \n  \/\/ Set the specific repeat mode that worked yesterday\n  Serial1.print(\"AT+REPEATMODE=4\\r\\n\");  \n  delay(100);\n  checkMP3Response();\n}\n\nvoid setup() {\n  \/\/ Initialize USB Serial communication\n  Serial.begin(115200); \n  \n  \/\/ Initialize status LED immediately so we can track boot stages\n  rgb.begin();\n  rgb.setBrightness(70);\n  setStatusRed(); \/\/ Solid Red = Booting up \/ Not connected\n  \n  \/\/ 1. Turn on UART pins for MP3\n  Serial1.begin(115200, SERIAL_8N1, GROVE_RX_PIN, GROVE_TX_PIN);\n  \n  \/\/ 2. Let the power settle, then initialize the MP3 player\n  delay(600); \n  initGroveMP3();\n  delay(600); \/\/ Extra spacing delay to prevent concurrent Wi-Fi \/ UART noise spikes\n\n  Serial.println(\"\\n--- Initiating Wi-Fi Connection ---\");\n\n  bool connected = false;\n  int retryAttempts = 0;\n\n  while (!connected) {\n    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n    retryAttempts = 0;\n\n    \/\/ Try connecting up to 10 times (5 seconds total) before giving up and resetting\n    while (WiFi.status() != WL_CONNECTED &amp;&amp; retryAttempts &lt; 10) {\n      delay(500);\n      Serial.print(\".\");\n      retryAttempts++;\n    }\n\n    if (WiFi.status() == WL_CONNECTED) {\n      connected = true;\n    } else {\n      \/\/ If it failed, flash Red to signal a retry, disconnect, and loop back\n      Serial.println(\"\\nConnection timed out. Resetting Wi-Fi radio and retrying...\");\n      WiFi.disconnect(true); \n      delay(1000); \n    }\n  }\n  \n  \/\/ Connection successful!\n  Serial.println(\"\\nWi-Fi Connected Successfully!\");\n  Serial.print(\"AtomS3U IP Address: \");\n  Serial.println(WiFi.localIP());\n\n  setStatusYellow(); \/\/ Turn Yellow = Safely connected and waiting for Wio Packets\n  udp.begin(UDP_PORT);\n}\n\nvoid loop() {\n  int packetSize = udp.parsePacket();\n  \n  if (packetSize) {\n    setStatusBlue(); \/\/ Blue flash = Network packet hit\n    \n    char packetBuffer&#91;255];\n    int len = udp.read(packetBuffer, 255);\n    if (len > 0) packetBuffer&#91;len] = 0;\n\n    String command = String(packetBuffer);\n    Serial.print(\"Received UDP: &#91;\"); Serial.print(command); Serial.println(\"]\");\n\n    if (command == \"STOP\") {\n      Serial1.print(\"AT+PLAY=0\\r\\n\"); \/\/ Explicit CRLF\n      Serial.println(\"Sent to MP3: STOP\");\n      checkMP3Response();\n      delay(300);\n      setStatusYellow(); \n    }\n    else if (command.startsWith(\"PLAY:\")) {\n      String trackStr = command.substring(5); \n      int trackNumber = trackStr.toInt(); \n      \n      playTrack(trackNumber); \n    }\n  }\n}\n\nvoid playTrack(uint8_t trackNumber) {\n  \/\/ Matches yesterday's exact working syntax: AT+PLAY=sd0,1\n  String command = \"AT+PLAY=sd0,\" + String(trackNumber) + \"\\r\\n\";\n  Serial1.print(command); \n  \n  Serial.print(\"Sent to MP3: \");\n  Serial.print(command);\n\n  delay(200); \n  bool success = checkMP3Response();\n  \n  if (success) {\n    setStatusGreen(); \n  } else {\n    setStatusPurple(); \n  }\n}\n\n\/\/ Reads UART response from the MP3 player\nbool checkMP3Response() {\n  String response = \"\";\n  unsigned long startWait = millis();\n  \n  \/\/ Wait up to 100ms for a response\n  while (millis() - startWait &lt; 100) {\n    while (Serial1.available()) {\n      char c = Serial1.read();\n      response += c;\n    }\n  }\n  \n  if (response.length() > 0) {\n    Serial.print(\"MP3 Chip Response: &#91;\");\n    Serial.print(response);\n    Serial.println(\"]\");\n    \n    \/\/ Most AT firmware returns \"OK\" on success. If it returns \"ERROR\", fail.\n    if (response.indexOf(\"ERROR\") >= 0) {\n      return false;\n    }\n    return true; \/\/ Received a valid response (likely \"OK\")\n  }\n  \n  Serial.println(\"MP3 Chip Response: SILENCE (No UART data returned)\");\n  return false; \/\/ Hardware silence\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The controller (WIO Terminal) The MP3 Player (M5Stack AtomS3U plus Grove MP3 v4.0)<\/p>\n","protected":false},"author":1,"featured_media":253,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-289","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorised"],"_links":{"self":[{"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/posts\/289","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/comments?post=289"}],"version-history":[{"count":1,"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/posts\/289\/revisions"}],"predecessor-version":[{"id":290,"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/posts\/289\/revisions\/290"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/media\/253"}],"wp:attachment":[{"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/media?parent=289"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/categories?post=289"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/myjetsonnano.perseus314.com\/index.php\/wp-json\/wp\/v2\/tags?post=289"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}