Realizziamo una piccola GUI in Java per gestire Led RGB con Arduino

In questo articolo vedremo come realizzare un’interfaccia grafica in Java che ci permetta di comunicare con Arduino per gestire il colore dei led RGB collegati.
Per realizzare questo progetto ci occorre:

Per quanto riguarda le strip led, visto che esistono un grande varietà di modelli, vediamo nel dettaglio quelli più comuni e cerchiamo di capire le differenze e come funzionano.

Differenza tra WS2801, WS2811 e WS2812

Nel web si trovano principalmente questi tre modelli:

1. WS2801
2. WS2811/WS2812
3. WS2812B

Le sigle sopra citate si riferiscono a circuiti integrati usati per pilotare i LED, ma comunemente usate per identificare le strip led.
Questi  IC possono controllare fino a 3 led, e nel caso delle strip RGB permettono di gestire i led rossi,verdi e blu.
La strip WS2801 (Fig.1) è quella più conosciuta presenta l‘IC esternamente ai led e a differenza degli altri modelli utilizza 4 fili VCC,GND,DATA,CLK presentando la linea di clock separata.

Strip LED WS2801
Fig.1 – Strip LED WS2801

La WS2812 è una strip che contiene all’interno del led 5050 il chip WS2811 ed è pilotabile con solo 3 fili VCC,GND e DATA.
Come visibile in Fig.2 un led 5050 è package di 5mmx5mm con 3 led all’interno (rosso,verde e blu), inoltre si può notare che, nella strip WS2812, all’interno del led si vede il chip WS2811 assente invece nel classico led 5050.

Strip LED WS2812 (a sinistra) e LED 5050 (a destra)
Fig.2 – Strip LED WS2812 (a sinistra) e LED 5050 (a destra)

Giusto per confondere ulteriormente le idee 🙂 sul web potete trovare anche il modello WS2812B ma non preoccupatevi questo modello è molto simile a quello della versione precedente WS2812, infatti avremo sempre i 3 contatti per pilotare la strip, le differenze principali riguardano la struttura interna del led e come si vede in Fig.3 la presenza di soli 4 pin e non più 6.
Se volete approfondire la differenza tra questi due ultimi modelli vi rimando a questo sito http://rgb-123.com/ws2812b-vs-ws2811/.

WS2812B (in alto a sinistra), WS2812 (in basso a destra) e WS2812B (a destra)
Fig.3 – WS2812B (in alto a sinistra), WS2812 (in basso a destra) e WS2812B (a destra)

Collegare Arduino alla strip WS2812

Ora che abbiamo capito le differenze tra tutti questi modelli dobbiamo capire come farli funzionare o meglio come collegarli ad Arduino.
Come si vede in Fig.2 e Fig.3 sulla strip sono presenti delle frecce, queste indicano la direzione dei dati lungo la strip quindi per farla funzionare correttamente bisogna saldare i 3 file come indicato in Fig.4. L’alimentazione è di 5 V e il pin dei dati può essere collegato ad un qualsiasi pin digitale di Arduino, l’utilizzo della resistenza da 470 ohm è facoltativa.

Arduino e WS2812
Fig.4 – Arduino e WS2812

 Sketch Arduino

Prima di passare al codice java vediamo il codice da caricare sulla nostra scheda Arduino e cerchiamo di capire come funziona.

#include <Adafruit_NeoPixel.h>
#include <MoodLight.h>

#define PIXEL_PIN 10 // pin IO used for pilot the Neopixel led
#define PIXEL_COUNT 20 // number Neopixel led present in strip
int valueRead = 0; // variable used for read the serial data
byte data_in[4] {0,0,0,0}; // variable used for write data read of the serial 
int saturation = 255;  // use value between 0 - 255
int brightness = 255;  // use value between 0 - 255
int hue = 0; // use value between 0 - 359
 
// create the object strip of type Adafruit_NeoPixel
// Parameter 1 = number of pixels in strip
// Parameter 2 = Arduino pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ400);

// create MoodLight object
MoodLight ml = MoodLight();

void setup() {
  Serial.begin(9600);
  // Initialize all pixels to 'off
  strip.begin();
  strip.show();
}

void loop() { 
  // save data in data_in
  if (Serial.available() > 3){
      for (int i = 0; i<4; i++) {
      valueRead = Serial.read();
      data_in[i] = valueRead;
      }
    }
  // choose the animation
  if (data_in[0] == 'A')  
      single_color();
  else if (data_in[0] == 'B') {
      hue = (data_in[2]*256+data_in[1]);
      rainbow_light(hue);
  }
}

// animation signle color
void single_color(){
  for (int i = 0; i < PIXEL_COUNT ; i++)
    // strip.setPixelColor(n, red, green, blue);
    // The first argument — n in this example — is the pixel number along the strip, starting from 0 closest to the Arduino. 
    // If you have a strip of 30 pixels, they’re numbered 0 through 29.
    // The next three arguments are the pixel color, expressed as red, green and blue brightness levels, 
    // where 0 is dimmest (off) and 255 is maximum brightness.
    strip.setPixelColor(i,(int)data_in[1],(int)data_in[2],(int)data_in[3]);
  strip.show();
}

// animation rainbow_light
void rainbow_light(int hue){
  // set the Moodlight values 
  ml.setHSB(hue, saturation , brightness);
  for (int i = 0; i < PIXEL_COUNT ; i++) 
    strip.setPixelColor(i,ml.getRed(),ml.getGreen(),ml.getBlue());
  strip.show();
}

Nelle prime due righe vengono importate le due librerie necessarie per il corretto funzionamento del codice, in particolare la prima Adafruit_NeoPixel.h è utilizzata per pilotare i led mentre la seconda MoodLight.h per realizzare l’effetto arcobaleno.

#include Adafruit_NeoPixel.h;
#include MoodLight.h;

Dalla riga 4 alla 10 sono state definite le variabili usate nel codice, in particolare:

 #define PIXEL_PIN 10
 #define PIXEL_COUNT 20
 int valueRead = 0;
 byte data_in[4] {0,0,0,0};
 int saturation = 255;
 int brightness = 255;
 int hue = 0;
  • #define PIXEL_PIN 10: Indica il pin della scheda Arduino collegato alla linea dati della strip led.
  • #define PIXEL_COUNT 20: Indica il numero totale di led presenti sulla strip, necessario per creare successivamente l’oggetto Adafruit_NeoPixel.
  • int valueRead: Una variabile di tipo intero usato per leggere i dati dalla seriale.
  • byte data_in[4]: Un array di tipo byte dove salvare i quattro byte inviati dall’applicazione java.
  • int saturation: Una variabile di tipo intero usata per definire il valore di saturazione dei led, necessaria per creare l’oggetto Moodlight. Il valore varia tra 0 e 255.
  • int brightness: Una variabile di tipo intero usata per definire il valore di luminosità dei led, necessaria per creare l’oggetto Moodlight. Il valore varia tra 0 e 255.
  • int hue: Una variabile di tipo intero usata per definire il valore del colore dei led, necessaria per creare l’oggetto Moodlight. Il valore varia tra 0 e 359.

Nelle righe 12 e 13 vengono creati due oggetti: Adafruit_NeoPixel e MoodLight.

Adafruit_NeoPixel = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN,NEO_GRB + NEO_KHZ400);
MoodLight ml = MoodLight();

Il primo campo del costruttore Adafruit_NeoPixel rappresenta il numero di pixel presenti sulla strip, il secondo il pin digitale di Arduino usato, mentre l’ultimo parametro indica il tipo di strip Neopixel usata. Per maggiori informazioni https://learn.adafruit.com/adafruit-neopixel-uberguide/arduino-library.

NOTA: Neopixel è la marca della strip led WS2812 realizzata da Adafruit.
L’oggetto MoodLight invece non ha parametri ed è usato per creare l’effetto arcobaleno.

Nel setup() la prima riga apre la porta seriale e setta il data rate a 9600 bps, mentre le righe successive inizializzano tutti i pixel allo stato off.

void setup() {
  Serial.begin(9600);
  // Initialize all pixels to 'off
  strip.begin();
  strip.show();
}

All’interno del loop() possiamo distinguere due blocchi il primo serve per leggere i dati da seriale, nel nostro caso ogni volta che sono disponibile più di 3 byte questi vengono letti e con un ciclo for salvati nell’array data_in.

if (Serial.available() > 3){
    for (int i = 0; i < 4; i++) {
    valueRead = Serial.read();
    data_in[i] = valueRead;
    }
}

Il secondo blocco è usato invece per la scelta dell’animazione dei led, nello specifico si è scelto di pilotare i led singolarmente se il primo byte salvato nell’array data_in è uguale al carattere ‘A’ mentre con l’effetto arcobaleno in caso sia uguale a ‘B’.

Da notare che nel caso dell’effetto arcobaleno il dato inviato dall’applicazione java è un numero compreso tra 0 e 359 per questo sono usati 2 byte, e quindi per ricostruire il dato e salvarlo nella variabile intera hue si usa la seguente riga di codice data_in[2]*256+data_in[1], quando vedremo il codice java sarà tutto più chiaro.

if (data_in[0] == 'A') 
 single_color(); 
else if (data_in[0] == 'B') { 
 hue = (data_in[2]*256+data_in[1]); 
 rainbow_light(hue); 
}

Ora vediamo nel dettaglio come sono realizzate le due funzione richiamate nel loop().
La prima single_color() setta attraverso la for tutti i led della strip utilizzando il metodo setPixelColor() che riceve in ingresso 4 parametri, il primo indica il numero del pixel da accendere e come per gli array l’indice parte da 0 per cui in una strip da 20 led il primo led sarà lo 0 mentre l’ultimo il 19, i restanti 3 parametri indicano la luminosità dei colori rispettivamente rosso, verde e blu.
Con strip.show() si rendono effettivi i settaggi dei colori verso la strip led.

void single_color(){ 
 for (int i = 0; i < PIXEL_COUNT ; i++) 
 strip.setPixelColor(i,(int)data_in[1],(int)data_in[2],(int)data_in[3]); 
 strip.show(); 
}

La seconda funzione rainbow_light(int hue) è utilizzata per creare l’effetto arcobaleno, questo grazie ad un parametro in ingresso che identifica il colore.

ml.setHSB(hue, saturation , brightness) permette di settare gli attributi colore, saturazione e luminosità dell’oggetto ml.
Anche in questo caso il for è utilizzato per settare il colore definito dalla variabile hue su tutti i led della strip.
Per ottenere i colori dall’oggetto ml si invocano i metodi getRed(), getGreen() e getBlue().

void rainbow_light(int hue){ 
 ml.setHSB(hue, saturation , brightness); 
 for (int i = 0; i < PIXEL_COUNT ; i++) 
 strip.setPixelColor(i,ml.getRed(),ml.getGreen(),ml.getBlue()); 
 strip.show(); 
}

GUI Java

Siamo quasi alla fine, ora ci manca solamente di realizzare l’interfaccia grafica in java che ci permetta di modificare il colore dei led collegati ad Arduino.
Come prima cosa apriamo Eclipse e dopo aver creato un nuovo progetto Java “ArduinoRGB” inseriamo un file .java scegliendolo attraverso il wizard: WindowBuilder->Swing Designer->Application Window e definiamo la classe ArduinoRGB.
Ora abbiamo la struttura iniziale della finestra e la possibilità di modificare il codice attraverso l’interfaccia grafica offerta da questo plug-in spostandoci nella scheda Design.
In Fig.5 possiamo vedere quello che andremo a realizzare, quindi iniziamo!

GUI Java
Fig.5 – GUI Java

Come prima cosa iniziamo ad inserire tutti gli elementi che ci occorrono all’interno della finestra e posizioniamoli come in Fig.5, oppure date spazio alla vostra fantasia e definite la vostra GUI.
Come per una ricetta quello che ci serve sono:

  • 4x JSlider
  • 6x JLabel
  • 1x JCheckBox
  • 2x JButton
  • 1x Choice

Prima di passare al codice facciamoci aiutare da questo plug-in per definire alcune impostazioni iniziali di questi elementi.
Iniziamo con le Label una volta selezionate nella finestra Properties andiamo a cambiare il campo text con il nome che vedete in Fig.5, scegliamo il colore in foreground, il font e per ultimo nella casella Variable diamo un nome significativo alle varie Label per esempio per la scritta “RED” mettiamo lblRed, questo vale per tutti gli elementi che inseriamo in quanto quando si passa al codice è più semplice riconoscerli.
Dopo aver finito con le Label passiamo agli Slider e sempre nella finestra Properties nel campo maximum scriviamo 255 per gli slider Red, Green e Blu mentre 359 per lo slider Rainbow. Come valore iniziale value mettiamo 0.
Per i Button le impostazioni da modificare sono solamente il campo text, per il Checkbox il campo label mentre il Choice è già pronto così.
Dimenticavo per i seguenti elementi: Button “Reset LED”, i 4 Slider e il Checkbox “Rainbow” bisogna deselezionare il campo enable (false) in quanto ad avvio applicazione non devono essere abilitati fintanto che  non si stabilisce una connessione.

Focus codice Java

Ora che abbiamo definito la nostra GUI possiamo passare al codice vero e proprio, visto che però è molto lungo mi soffermerò principalmente nei punti inseriti da me e non sul codice auto-generato dal plug-in WindowBuilder.

La prima cosa che dobbiamo fare è inserire la libreria jSCC, utilizzata per la comunicazione seriale, per fare questo dobbiamo fare click con il tasto destro sul progetto “ArduinoRGB” selezionare Properties->Java Build Path->Libraries->Add JARs e caricare il file .jar contenente la libreria scaricata in precedenza. Per poter usare questa libreria dobbiamo importarla nel file .java con la seguente riga di codice.

import jssc.*;

Inseriamo nella classe ArduinoRGB i seguenti attributi in modo tale da essere visibili in tutti i suoi metodi.

SerialPort serialPort; 
String[] listPort; 
boolean init = false;
  • SerialPort serialPort: Un oggetto SerialPort descrive l’interfaccia di basso livello di una porta seriale definendo le funzionalità minime richieste.
  • String[] listPort: Un array di stringhe contenente i nomi delle porte seriali disponibili.
  • boolean init: Un boolean, inizializzato a false, usato per capire quando si è instaurata la connessione.

Iniziamo a vedere alcuni metodi della classe ArduinoRGB, il primo è serialInitialize() questo metodo permette di aprire la porta seriale, precedentemente scelta, e di settare i seguenti parametri della porta: baud, n° bit di dati, n° bit di stop e la parità della porta il tutto all’interno di un blocco try-catch per gestire eventuali eccezioni.

public void serialInitialize() { 
    try { 
        // open serial port 
        System.out.println("Port opened: " + serialPort.openPort()); 
        // set the serial port parameters 
        System.out.println("Params setted: " + serialPort.setParams(9600, 8, 1, 0)); 
        Thread.sleep(3000); 
        init = true; 
    } catch (SerialPortException | InterruptedException ex){ 
        System.out.println(ex); 
      } 
}

Il metodo serialWrite(int[] rgbValue) permette di inviare sulla seriale 4 byte, secondo una determinata struttura, utilizzati da Arduino per accendere il colore del led scelto dalla GUI.
Il primo byte data[0] contiene il carattere ‘A’ indica l’animazione singleLed() presente nel codice Arduino, i restanti 3 byte prendono i valori (0-255) passati in ingresso dall’array rgbValue che definisce rispettivamente i colori rosso, verde e blu.
Una volta salvato i valori nell’array data questi vengono inviati con la seguente riga di codice serialPort.writeBytes(data).

public void serialWrite(int[] rgbValue) { 
 try { 
     byte[] data = new byte[4]; 
     // data[0] = 'A' set animation singleLed 
     data[0] = (byte)'A'; 
     // insert the value of the slider red in data[0] 
     data[1] = (byte)rgbValue[0]; 
     // insert the value of the slider green in data[1] 
     data[2] = (byte)rgbValue[1]; 
     // insert the value of the slider blue in data[2] 
     data[3] = (byte)rgbValue[2]; 
     System.out.println("\"Send RGB: " + rgbValue[0] + "," + rgbValue[1] + "," + rgbValue[2] + " " + serialPort.writeBytes(data)); 
     //System.out.println("Port closed: " + serialPort.closePort()); 
 } catch (SerialPortException ex){ 
     System.out.println(ex); 
   } 
}

Di seguito possiamo vedere l’overloading del metodo serialWrite che questa volta accetta un intero rainbowValue il cui valore si riferisce al colore dell’arcobaleno da dare in ingresso alla funzione rainbow_light(hue) presente nel codice Arduino.
Da notare che data[0] questa volta è uguale ‘B’ per avvisare Arduino di usare l’effetto arcobaleno, inoltre  per inviare rainbowValue valore che varia tra 0 a 359 un solo byte non basta per cui se ne usano 2 e sul secondo si prende il valore di ingresso shiftato a destra di 8 bit, data[3] invece non è usata quindi è impostata 0.

public void serialWrite(int rainbowValue) { 
 try { 
     byte[] data = new byte[4]; 
     // data[0] = 'B' set animation rainbow 
     data[0] = (byte)'B'; 
     // insert rainbowValue in data[0] 
     data[1] = (byte)rainbowValue; 
     // insert rainbowValue shift right in data[1] 
     data[2] = (byte)(rainbowValue >> 8); 
     // data[3] = 0 is value not used 
     data[3] = (byte)0; 
     System.out.println("\"Send color: " + rainbowValue + " " + serialPort.writeBytes(data)); 
     //System.out.println("Port closed: " + serialPort.closePort()); 
 } catch (SerialPortException ex){ 
     System.out.println(ex); 
   } 
}

L’ultimo metodo che andiamo a vedere è initialize() usato per inizializzare il contenuto della finestra, ossia tutti gli elementi che abbiamo definito all’inizio nell’area Design.
Prima di tutto creiamo un array che conterrà la tripletta di valore per il led rosso, verde e blu da inviare per seriale.

// create array rgb used to send the colors 
int[] rgb = new int[3];

Ora inseriamo i vari eventi per gestire gli slider rosso, verde, blu e rainbow. Come si vede nel codice qui sotto, scegliendo dalla sezione Design, cliccando sull’evento change dell’elemento slider viene creato il metodo   stateChanged(ChangeEvent e) all’interno del quale si può inserire cosa vogliamo accada al verificarsi del movimento dello slider.

Nel nostro caso quando si verifica un cambiamento del valore dello slider si controlla prima se la connessione con la porta seriale è attiva (init == true) e in caso positivo si salva nell’array rgb[2] il valore dello slider e lo si invia tramite serialWrite(rgb). Un codice simile è usato per gestire lo stesso evento per gli altri slider.

// send the rgb value to Arduino when there is a change state of sliderBlue 
sliderBlue.addChangeListener(new ChangeListener() { 
    public void stateChanged(ChangeEvent e) { 
        if (init) { 
        rgb[2] = sliderBlue.getValue(); 
        serialWrite(rgb); 
        //System.out.println("RGB : " + rgb[0] + " " + rgb[1] + " " +rgb[2]); 
        } 
    } 
});

Il metodo itemStateChanged(ItemEvent e) è utilizzato per gestire l’evento riguardante il cambiamento di stato del CheckBox “Rainbow“, quando si verifica tale evento si controlla lo stato del checkbox se è selezionato si attiva lo slider rainbow, si disattivano gli altri 3 (rosso, verde e blu) settando i loro valori a 0 ed infine si invia il valore del colore sulla seriale con serialWrite(sliderRainbow.getValue()).
In caso sia deselezionato si disattiva lo slider rainbow e si attivano gli altri inviando la tripletta dei colori rgb sulla seriale.

public void itemStateChanged(ItemEvent e) {
// if checkbox is on enable sliderRainbow and unable the signle color slider
// and send serialWrite(sliderRainbow.getValue())
	if (checkboxRainbow.getState()) {
		sliderRainbow.setEnabled(true);
		serialWrite(sliderRainbow.getValue());
		sliderRed.setEnabled(false);
		sliderGreen.setEnabled(false);
		sliderBlue.setEnabled(false);
		sliderRed.setValue(0);
		sliderGreen.setValue(0);
		sliderBlue.setValue(0);
		// if checkbox is off enable the signle color slider and unable sliderRainbow 
		// and send serialWrite(rgb)
	} else {
		sliderRainbow.setEnabled(false);
		sliderRainbow.setValue(0);
		rgb[0] = 0;
		rgb[1] = 0;
		rgb[2] = 0;
		serialWrite(rgb);
		sliderRed.setEnabled(true);
		sliderGreen.setEnabled(true);
		sliderBlue.setEnabled(true);
	}
}

Più avanti nel codice troviamo l’evento per il button “Reset LED“, se premuto reimposta i valori degli slider a 0 in base allo stato del checkBox “Rainbow“.

public void mouseClicked(MouseEvent e) {
	if (checkboxRainbow.getState()) {
		sliderRainbow.setValue(0);
		serialWrite(0);
	} else {
		sliderRed.setValue(0);
		sliderGreen.setValue(0);
		sliderBlue.setValue(0);
		rgb[0] = 0;
		rgb[1] = 0;
		rgb[2] = 0;
		serialWrite(rgb);
	}
}

L’ultimo tasto della nostra GUI rimane il button “Update” che ci permette di aggiornare la lista delle porte seriali disponibili all’interno dello Choice.

Ogni volta che si preme viene aggiornato l’array listPort attraverso SerialPortList.getPortNames(), quindi si rimuovono tutti i vecchi elementi all’interno dello choice e con una for si aggiornano quelli nuovi.

public void mouseClicked(MouseEvent arg0) {
	listPort = SerialPortList.getPortNames();
	choice.removeAll();
	for (String list: listPort)
		choice.add(list);
}

Infine la parte forse più importante e quella che viene eseguita per prima 🙂 è la seguente, ossia l’inizializzazione della porta e l’abilitazione degli slider (rosso, verde e blu).

Infatti quando si sceglie una porta dalla lista viene aperta la porta e settati i parametri (serialInitialize()) e abilitati i vari slider, checkbox e il button ResetLed.

public void itemStateChanged(ItemEvent arg0) {
	serialPort = new SerialPort(choice.getSelectedItem());
	serialInitialize();
	if (init) {
		sliderRed.setEnabled(true);
		sliderGreen.setEnabled(true);
		sliderBlue.setEnabled(true);
		checkboxRainbow.setEnabled(true);	
		btnResetLed.setEnabled(true);
	}
}

Siamo arrivati alla fine di questo lungo articolo spero che sia stato tutto chiaro, in caso di problemi o dubbi potete lasciare un commento.
Il codice del progetto lo potete scaricare cliccando sull’icona qui sotto.

Download
Download