Controlling an Arduino Ring Using a Go Web Server

Hasan AlkhatibHasan Alkhatib
6 min read

Sometimes, the best ideas come from playing around with simple projects. I recently set up an Arduino Nano RP2040 Connect to control an RGB LED ring via a web server written in Go. It turned out to be a fun and educational dive into IoT and web programming.

In this blog, I’ll walk you through the setup, share the code, and reflect on what I learned.


What We'll Build!

Control an RGB LED Ring using an Arduino Nano RP2040 Connect by fetching color values from a Go-based web server.

Componenets:

  • Arduino Nano RP2040 Connect (with built-in WiFi).

  • NeoPixel/WS2812 LED Ring.

  • A simple web server in Go.

How it works:

  1. The Arduino connects to WiFi and polls the web server every 3 seconds for RGB values.

  2. The web server responds with JSON data containing the current RGB values.

  3. The Arduino updates the LED ring to match the received colors.

Arduino Code Breakdown

Let’s dive into the Arduino code:

  1. WiFi Connection: The WiFiNINA library handles WiFi connectivity. Replace ssid and password with your network credentials.
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";
  1. HTTP Communication
httpClient.get("/rgb");
  1. LED Control: The Adafruit_NeoPixel library manages the LED ring. Each color value (Red, Green, Blue) is applied to all LEDs.
for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, strip.Color(red, green, blue));
}
strip.show();
  1. JSON Parsing
bool parseRGB(String json, int &red, int &green, int &blue) {
    int redIndex = json.indexOf("\"red\":") + 6;
    int greenIndex = json.indexOf("\"green\":") + 8;
    int blueIndex = json.indexOf("\"blue\":") + 7;
    ...
}

Full Code

/*
 * Setup:
 * - RGB LED Ring (WS2812/NeoPixel compatible) connected to D2
 * - Arduino Nano RP2040 Connect with built-in WiFi

 * - Ensure the web server is running at the specified IP and port.
 */

#include <ArduinoHttpClient.h>
#include <WiFiNINA.h>
#include <Wire.h>
#include <Adafruit_NeoPixel.h>

// WiFi credentials
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";

// Server details (ipconfig getifaddr en0)
const char* serverAddress = "web_server_ip";
const int port = web_server_port;

WiFiClient wifiClient;
HttpClient httpClient = HttpClient(wifiClient, serverAddress, port);

// LED setup
#define LED_PIN 25
#define LED_COUNT 24

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

// Function prototypes
void connectToWiFi();
void fetchAndSetRGB();
bool parseRGB(String json, int &red, int &green, int &blue);

void setup() {
  Serial.begin(9600);

  strip.begin();
  strip.show();
  strip.setBrightness(50);

  connectToWiFi();
}

void loop() {
  fetchAndSetRGB();
  delay(3000); // Fetch every 3 seconds
}

void connectToWiFi() {
  Serial.print("Connecting to WiFi...");
  while (WiFi.begin(ssid, password) != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("Connected!");
}

void fetchAndSetRGB() {
  Serial.println("Fetching RGB values...");
  httpClient.get("/rgb");
  int statusCode = httpClient.responseStatusCode();
  if (statusCode != 200) {
    Serial.print("Failed to fetch RGB values. Status code: ");
    Serial.println(statusCode);
    return;
  }

  String response = httpClient.responseBody();
  Serial.print("Response: ");
  Serial.println(response);

  int red, green, blue;
  if (parseRGB(response, red, green, blue)) {
    for(int i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, strip.Color(red, green, blue));
    }
    strip.show();
    Serial.println("RGB values set successfully.");
    Serial.print("Red: ");
    Serial.print(red);
    Serial.print(", Green: ");
    Serial.print(green);
    Serial.print(", Blue: ");
    Serial.println(blue);
  } else {
    Serial.println("Failed to parse RGB values.");
  }
}

bool parseRGB(String json, int &red, int &green, int &blue) {
  int redIndex = json.indexOf("\"red\":") + 6;
  int greenIndex = json.indexOf("\"green\":") + 8;
  int blueIndex = json.indexOf("\"blue\":") + 7;

  if (redIndex == -1 || greenIndex == -1 || blueIndex == -1) return false;

  red = json.substring(redIndex, json.indexOf(',', redIndex)).toInt();
  green = json.substring(greenIndex, json.indexOf(',', greenIndex)).toInt();
  blue = json.substring(blueIndex, json.indexOf('}', blueIndex)).toInt();

  return true;
}

Go Web Server

On the server side, we have a lightweight Go application to serve the RGB values.

Key Features

  1. Random Initial Colors: The server starts with a random Palestinian flag color (red, green, or white).

  2. 2. Concurrency Handling: A sync.Mutex ensures thread-safe access to the RGB struct.

  3. Dual Control: You can update the RGB values either through a CLI or programmatically via HTTP GET requests.

Full Code

// Usage:
// 1. Run the program: go run main.go
// 2. Web server starts on port 8080. Get RGB values with a GET request to http://localhost:8080/rgb.
// 3. Set new RGB values in the CLI using format R,G,B (e.g., 255,0,0 for red). Type 'exit' to quit.

// PS. get the server IP by running ipconfig getifaddr en0
// then access the server from another device using the IP and port 8080
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "math/rand"
    "net/http"
    "os"
    "strings"
    "sync"
    "time"
)

type RGB struct {
    Red   int `json:"red"`
    Green int `json:"green"`
    Blue  int `json:"blue"`
}

var (
    rgb RGB
    mu  sync.Mutex // To handle concurrent access
    rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
)

func main() {
    // Set default RGB values to randomly pick one of the Palestinian flag colors, since, why not?
    switch rnd.Intn(3) {
    case 0:
        rgb = RGB{Red: 0, Green: 100, Blue: 0} // Dark Green
    case 1:
        rgb = RGB{Red: 139, Green: 0, Blue: 0} // Dark Red
    case 2:
        rgb = RGB{Red: 255, Green: 255, Blue: 255} // White
    }
    fmt.Println("Setting the Palestinian flag colors, since, why not?")

    // Start the web server in a separate goroutine
    go func() {
        http.HandleFunc("/rgb", handleRGB)
        fmt.Println("Starting server on port 8080...")
        log.Fatal(http.ListenAndServe(":8080", nil))
    }()

    // CLI loop for setting RGB values
    reader := os.Stdin
    for {
        fmt.Print("Enter RGB values (format: R,G,B) or type 'exit' to quit: ")
        var input string
        fmt.Fscanln(reader, &input)
        input = strings.TrimSpace(input)

        if strings.ToLower(input) == "exit" {
            fmt.Println("Exiting...")
            break
        }

        if setRGB(input) {
            fmt.Printf("RGB updated to: %d, %d, %d\n", rgb.Red, rgb.Green, rgb.Blue)
        } else {
            fmt.Println("Invalid input. Use format R,G,B with values between 0 and 255.")
        }
    }
}

// Handle HTTP GET requests to /rgb
func handleRGB(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
        return
    }

    mu.Lock()
    defer mu.Unlock()

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(rgb)
}

// Set the RGB values based on CLI input
func setRGB(input string) bool {
    parts := strings.Split(input, ",")
    if len(parts) != 3 {
        return false
    }

    var r, g, b int
    _, err := fmt.Sscanf(input, "%d,%d,%d", &r, &g, &b)
    if err != nil || r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 {
        return false
    }

    mu.Lock()
    defer mu.Unlock()
    rgb = RGB{Red: r, Green: g, Blue: b}
    return true
}

Putting it All Together

  1. Run the Web Server: Compile and run the Go program on your machine:
go run main.go
  1. Connect the Arduino: Upload the code to your Arduino Nano RP2040 and monitor the serial output to ensure it connects to WiFi.

  2. Test the System: Open the RGB endpoint in your browser:

http://<your_server_ip>:8080/rgb

This project combines IoT and web programming in a fun way. Got questions or feedback? Drop a comment below or email me at hasan@alkhatib.tech

0
Subscribe to my newsletter

Read articles from Hasan Alkhatib directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hasan Alkhatib
Hasan Alkhatib

Father of two. DevOps Engineer. Interested in Cloud Development, Scalability, Java, CICD, and Seeking/Sharing Knowledge