gma3
gma3 copied to clipboard
Arduino library to control GrandMA3 lighting consoles using OSC
Arduino OSC library for GrandMA3 consoles v3
An object orientated library for Arduino to control GrandMA3 consoles with OSC over Ethernet UDP/TCP. The goal of the library is to have a smart toolbox to create your own hardware which covers your needing by endless combinations of hardware elements.
Changes for v3
- global / local page() numbering
- command() function for sending raw commands
- fetch(), value() and jitter() functions for fader handling
- buttons with callback for e.g. paging
Changes for v2
- TCP support
- UDP parser for echo replays to get usefull informations, e.g. fader value ...
- independent from the CNNMAT OSC library
- change of naming conventions
- change license to CC BY-NC-SA 4.0
Installation
- Download from Releases
- Follow the instruction on the Arduino website https://www.arduino.cc/en/Guide/Libraries You can import the .zip file from the IDE with Sketch / Include Library / Add .ZIP Library...
- For PlatformIO Unzip and move the folder to the lib folder of your project.
Boards
You can use any Arduino compatible board which gives you the possibility of an ethernet port. Some boards like Teensy or STM32 Nucleo-F767ZI have an Ethernet port build in, others need an external ethernet port based e.g. on Wiznet W5500 boards like USR-ES1 or Arduino EthernetShield 2. WLAN boards like ESP32 should work but are not tested and there is no guarantee for a stable connection. There are also some Arduino based SPS controllers on the market which are ideal for rough enviroment using 24V.
- Controllino https://www.controllino.com
- Industrial Shields https://www.industrialshields.com
Ethernet Usage
The in the Arduino board example used Ethernet library only supports the Wiznet 5500 chip, used on Ethernet Shield 2 or the popular USR-ES1 module which you can buy for a small pice at aliexpress.com
Following libraries must downloaded for use with Ethernet
!!! Beware, the Ethernet libraries have different init procedures !!!
WIZNet w5500 boards like Ethernet Shield 2
- an Arduino compatible Ethernet library like Ethernet3 https://github.com/sstaub/Ethernet3
- optional for Teensy MAC address https://github.com/sstaub/TeensyID
Teensy 4.1 with buildin Ethernet
- https://github.com/vjmuzik/NativeEthernet
- https://github.com/vjmuzik/FNET
STM32duino (https://github.com/stm32duino)
- https://github.com/stm32duino/STM32Ethernet
- https://github.com/stm32duino/LwIP
Hardware
The library support hardware elements like encoders, faders, buttons with some helper functions. The library allows you to use hardware elements as an object and with the use of the helper functions, code becomes much easier to write and read and to understand.
-
Buttons You can use every momentary push button on the market, e.g. MX Keys which are also used by MA Lighting, the keys are available with different push characters and have therefore different color markers. One pin must connect to a Digital Pin Dn the other to ground.
! A 100nF capitor is recommanded between the button pins ! -
Faders Recommanded are linear faders with 10k Ohm from Bourns or ALPS which are available in different lengths and qualities.
Beware that ARM boards like STM32-Nucleo use 3.3V, classic AVR boards like Arduino UNO use 5V. The leveler must connect to the Analog Pin An. The other pins must connect to ground and 3.3V or 5V.
! A 10nF capitor is recommanded between leveler and ground ! -
Rotary Encoders You can use encoders from ALPS or equivalent. The middle pin of the encoders must connect to ground, the both other pins A/B must connect to Digital Pins Dn.
! Two 100nF capitors are recommanded between the button pin A/B and ground !
Additional Advices for Analog Pins
The most problems comes from bad grounding and cables that are to long, on PCB's the shielding design is very important.
-
Arduino UNO, MEGA with WIZnet5500 Use AREF Pin instead +5V to the top (single pin) of the fader (100%). Use GND next to AREF and connect to the center button pin (2 pins, the outer pin is normaly for the leveler) of the fader (0%)
-
STM32-Nucleo use IOREF Pin instead +3.3V to the top (single pin) of the fader (100%). GND to the center button pin (2 pins, the outer pin is normaly for the leveler) of the fader (0%).
-
TEENSY 3.x with WIZnet5500 +3.3V to the top (single pin) of the fader (100%) use ANALOG GND instead the normal GND to the center button pin (2 pins, the outer pin is normaly for the leveler) of the fader (0%).
Test configuartion with Teensy41
Usage
You can find general information about OSC on http://opensoundcontrol.org/ Please refer to the GrandMA3 manual for more information about using OSC on GrandMA3.
Here an example OSC setup in the GrandMA3 software
For use with PlatformIO https://platformio.org, as a recommanded IDE with MS VSCode, there is an extra start example folder called gma3_Arduino_PIO, you must add the needed libraries manually.
If you have wishes for other functions or classes enter the discussion forum. If you find bugs make an issue, nobody is perfect.
Ethernet configuration and initialization
The Ethernet functionality is now independent from the hardware port (e.g. WIFI or other Ethernet hardware than WizNet W5500) and libraries. Behind the scenes it uses the virtual Arduino UDP class.
Before using Ethernet there a some things that must be done. It can be different between the diverse libraries.
- Include the necessary #defines e.g.
- Arduino or Teensy 3.x with WIZnet5500
#include "Ethernet3.h"
#include "EthernetUdp3.h"
- STM32-Nucleo (e.g. Nucleo-F767ZI)
#include <LwIP.h>
#include <STM32Ethernet.h>
#include <EthernetUdp.h>
- Teensy 4.1 with FNET network stack
#include <TeensyID.h>
#include <NativeEthernet.h>
#include <NativeEthernetUdp.h>
- You need to define IP addresses, ports and sockets
- mac - You need a unique MAC address, for Teensy 3.x / 4.1 you can use the TeensyID library on this GitHub site, for STM32-Nucleo there is a build in MAC address
- localIP - You need a static IP address for your Arduino in the subnet range of network system
- subnet - A subnet range is necessary
- localPort - This is the destinitaion port of your Arduino
- gma3IP - This is the GrandMA3 console IP address
- gma3Port - This is the destination port of the GrandMA3 console
// configuration example, must done before setup()
uint8_t mac[] = {0x90, 0xA2, 0xDA, 0x10, 0x14, 0x48}; // MAC Address only needed for WIZNet5500 chips
// for Teensy this can empty: uint8_t mac[6];
#define GMA3_UDP_PORT 8000 // UDP Port configured in gma3
#define GMA3_TCP_PORT 9000 // TCP Port configured in gma3
IPAddress localIP(10, 101, 1, 201); // IP address of the microcontroller board
IPAddress subnet(255, 255, 0, 0); // subnet range
uint16_t localUdpPort = GMA3_UDP_PORT;
IPAddress gma3IP(10, 101, 1, 100); // IP address of the gma3 console
uint16_t gma3UdpPort = GMA3_UDP_PORT;
uint16_t gma3TcpPort = GMA3_TCP_PORT;
- You need an UDP/TCP socket, must done before setup().
EthernetUDP udp;
EthernetClient tcp;
- In the beginning of setup() you must start network services.
// for Teensy call: teensyMAC(mac);
Ethernet.begin(mac, localIP, subnet);
// for STM32 use: Ethernet.begin(localIP, subnet);
interfaceGMA3(gma3IP);
interfaceUDP(udp, gma3UdpPort);
interfaceTCP(tcp, gma3TcpPort);
Example
A simple example for use with an Arduino UNO with EthernetShield 2
#include <Ethernet3.h>
#include <EthernetUdp3.h>
#include "gma3.h"
#define BTN_KEY 2
#define ENC_1_A 3
#define ENC_1_B 4
#define BTN_CMD 5
#define FADER A0
// network data
uint8_t mac[] = {0x90, 0xA2, 0xDA, 0x10, 0x14, 0x48};
#define GMA3_UDP_PORT 8000 // UDP Port configured in gma3
#define GMA3_TCP_PORT 9000 // UDP Port configured in gma3
IPAddress localIP(10, 101, 1, 201); // IP address of the microcontroller board
IPAddress subnet(255, 255, 0, 0); // subnet range
IPAddress gma3IP(10, 101, 1, 100); // IP address of the gma3 console
uint16_t gma3UdpPort = GMA3_UDP_PORT;
uint16_t gma3TcpPort = GMA3_TCP_PORT;
EthernetUDP udp;
EthernetClient tcp;
// hardware definitions
Fader fader201(FADER, 201);
Key key201(BTN_KEY, 201, TCP); // using TCP
ExecutorKnob enc301(ENC_1_A, ENC_1_B, 301);
CmdButton macro1(BTN_CMD, "GO+ Macro 1");
void setup() {
Serial.begin(9600);
Ethernet.begin(mac, localIP, subnet);
interfaceGMA3(gma3IP);
interfaceUDP(udp, gma3UdpPort);
interfaceTCP(tcp, gma3TcpPort);
}
void loop() {
key201.update();
fader201.update();
enc301.update();
macro1.update();
}
Examples folders
There are some basic examples for for different board types using the Arduino IDE. Also a new example for the Selction class with an Up / Down button for page change and fetching.
gma3_Arduino_PIO
Is a project folder for use with PlatformIO and includes an example code for an Arduino MEGA as a starting point. It also include an extra button for a GO command to QLab.
RAM usage adjustment
Because using strictly stack allocation of OSC strings, you need to adjust the allocation size in the gma3.h file.
// OSC settings
#define OSC_PATTERN_SIZE 64 // length depends on naming conventions
#define OSC_STRING_SIZE 64 // length depends on maximum command length
#define OSC_MESSAGE_SIZE 128 // this should OSC_PATTERN_SIZE + OSC_STRING_SIZE
Transport modes
- UDPOSC standard mode using UDP protocol
- TCP for OSC 1.0 supported by GrandMA3 using TCP
- TCPSLIP for OSC 1.1 e.g. for QLab TCP with SLIP encoding
GrandMA3 naming conventions
This has changed from v1.x to avoid performance problems. Naming of prefix and others are done in the gma3.h file. The standard prefix is gma3 now, when changing the prefix you should also change the PREFIX_PATTERN, this is needed for echo replay. The naming must the same as in the GrandMA3 software
// GMA3 naming conventions
#define PREFIX "gma3"
#define PAGE "Page"
#define FADER "Fader"
#define EXECUTOR_KNOB "Encoder"
#define KEY "Key"
Setup functions
Interfaces
The Interfaces are needed for initialize the connection to the GrandMA3 console and external receivers.
Following settings must done in setup()
:
- IP address of the GrandMA3 console
- name of the UDP class member
- name of the TCP class member
- IP Address of the GrandMA3 console
- OSC Port, set in the GrandMA3 console, standard for UDP is port 8000, for TCP 9000
- IP addresses and ports for external receivers using
oscButton()
function
interfaceGMA3()
void interfaceGMA3(IPAddress gma3IP)
Set the IP address of the GrandMA3 software
interfaceUDP()
void interfaceUDP(UDP &udp, uint16_t port = 8000)
Set the UDP socket and port of the GrandMA3 software, standard UDP port is 8000
interfaceTCP()
void interfaceTCP(UDP &tcp, uint16_t port = 9000)
Set the TCP socket and port of the GrandMA3 software, standard TCP port is 9000
interfaceExternUDP()
void interfaceExternUDP(UDP &udp, uint16_t port)
Set the UDP socket and port external receivers accessing via oscButton()
function
interfaceExternTCP()
void interfaceExternTCP(UDP &tcp, uint16_t port)
Set the TCP socket and port external receivers accessing via oscButton()
function
Examples
interfaceGMA3(gma3IP);
interfaceUDP(udp, gma3UdpPort);
interfaceTCP(tcp, gma3TcpPort);
interfaceExternUDP(udpQLab, qlabIP, qlabPort);
interfaceExternTCP(tcpQLab, qlabIP, qlabPort);
Page Number
You can change the Page number now global or laocal.
Global Page Number
For changing the global page number use
void page(uint16_t page)
Standard is Page 1
Example
page(2); // set glabal page to 2
Local Page Number
Refer to the classes documentation
Functions
command()
void command(const char command[], protocol_t protocol = UDPOSC);
Send a command message
Example
command("Page+");
Classes
Key
With this class you can create Key objects which can be triggered with a button.
Constructor
Key(uint8_t pin, uint16_t key, protocol_t protocol = UDPOSC);
-
pin
are the connection Pin for the button hardware -
key
is the key number of the executors, refer to the GrandMA3 manual -
protocol
is the transport protocol you want to use, it can UDPOSC and TCP
Example, this should done before the setup()
Key key201(2, 201, TCP);
// executor button 201, using pin 2, transport with TCP
Destructor
~Key();
Use for destructing a Key object
Page
void page(uint16_t pageLocal = 0);
-
pageLocal
is the page number of the executors, refer to the GrandMA3 manual
Set a local page number which overrides the global.
Example
key201.page(2); // set local page 2
key201.page(); // reset to global page
Update
To get the actual button state you must call inside the loop()
function
void update();
Example, this must happen in the loop()
function
key201.update();
Fader
This class allows you to control a fader containing with a hardware (slide) potentiometer as an executor fader.
Timing constants
#define FADER_UPDATE_RATE_MS 1 // update rate, must low at possible for fetching
#define FADER_THRESHOLD 4 // Jitter threshold of the faders
Constructor
Fader(uint8_t analogPin, uint16_t fader, protocol_t protocol = UDPOSC);
-
analogPin
are the connection Analog Pin for the fader leveler -
fader
is the fader number of the executors, refer to the GrandMA3 manual -
protocol
is the transport the protocol you want to use, it can UDPOSC and TCP, UDPOSC is recommanded
Example, this should done before the setup()
Fader fader201(A0, 201);
// leveler is Analog Pin A0, executor number of the fader is 201, using UDPOSC as standard
Destructor
~Fader();
Use for destructing a Fader object
Page
void page(uint16_t pageLocal = 0);
-
pageLocal
is the page number of the executors, refer to the GrandMA3 manual
Set a local page number which overrides the global.
Example
fader201.page(2); // set local page 2
fader201.page(); // reset to global page
Value
int32_t value();
Return the value (0...100) of the fader.
Example
int32_t value = fader201.value();
Fetch
void fetch(uint16_t value);
-
value
unlock value
Lock the sending of OSC fader data until the value defined in fetch() is reached.
This functionality is intended for page changing. So you need to fetch the fader before you can use it.
Example
fader201.fetch(0); // set fetch value to 0
Lock
bool lock();
void lock(bool state);
Get or set the state of the fetch function, can used for indication of the fader state or force a new state.
-
true
locked fader -
false
unlocked fader
Example
bool state = fader201.lock(); // get the lock state
fader201.lock(false); // set the lock state
Jitter
void jitter(uint8_t delta);
-
delta
+/- value range
This functionality is a helper function for fetching.
e.g. if fetch(20)
and jitter(2)
the unlock value expand to a range from 18 ... 22
Example
fader201.jitter(2); // set fetch range to +/- 2
Update
To get the actual button state you must call inside the loop()
function
void update();
Example, this must happen in the loop()
function
fader201.update();
ExecutorKnob
The ExecutorKnob class creates an encoder object which allows to control the executor knobs:
ExecutorKnob(uint8_t pinA, uint8_t pinB, uint16_t executorKnob, protocol_t protocol = UDPOSC, uint8_t direction = FORWARD);
-
pinA
and pinB are the connection Pins for the encoder hardware -
executorKnob
is the number of the executor knob, refer to the GrandMA3 manual -
protocol
is the transport protocol you want to use, it can UDPOSC and TCP -
direction
is used for changing the direction of the encoder to clockwise if pinA and pinB are swapped. The directions are FORWARD (standard) or REVERSE
Example, this should done before the setup()
ExecutorKnob enc301(3, 4, 301, UDPOSC, REVERSE);
// the encoder pins are 3/4, the number of the executorKnob is 301, send with UDP, encoder pins are swapped (REVERSE)
Destructor
~ExecutorKnob();
Use for destructing a Fader object
Page
void page(uint16_t pageLocal = 0);
-
pageLocal
is the page number of the executors, refer to the GrandMA3 manual
Set a local page number which overrides the global.
Example
enc301.page(2); // set local page 2
enc301.page(); // reset to global page
Update
To get the actual encoder state you must call inside the loop()
function
void update();
Example, this must happen in the loop()
function
enc301.update();
CmdButton
With this class you can create Keya button which allows to send commands to the console.
CmdButton(uint8_t pin, const char command[], protocol_t protocol = UDPOSC);
-
pin
are the connection Pin for the button hardware -
command
is a command string which should send to the console, refer also to the GrandMA3 manual -
protocol
is the transport protocol you want to use, it can UDPOSC and TCP
Example, this should done before the setup()
CmdButton macro1(A2, "GO+ Macro 1");
// button on pin A2, fires Macro 1
Destructor
~CmdButton();
Use for destructing a Fader object
Update
To get the actual button state you must call inside the loop()
function
void update();
Example, this must happen in the loop()
function
macro1.update();
OscButton
With this class you can create generic buttons which allows you to control other OSC compatible software in the network like QLab. The class initializer is overloaded to allow sending different OSC data types: Interger 32 bit, Float, Strings or no data.
! When using interger or float, 0 or 0.0 is explicit send when releasing the button !
Constructor
OscButton(uint8_t pin, const char pattern[], int32_t integer32, protocol_t protocol = UDPOSC);
OscButton(uint8_t pin, const char pattern[], float float32, protocol_t protocol = UDPOSC);
OscButton(uint8_t pin, const char pattern[], const char message[], protocol_t protocol = UDPOSC);
OscButton(uint8_t pin, const char pattern[], protocol_t protocol = UDPOSC);
-
pin
the connection Pin for the button hardware -
pattern
the OSC address pattern -
integer32
optional Integer data to send, you must cast this data e.g.(int32_t)1
-
float32
optional Float data to send, you must cast this data e.g.1.0f
-
message
optional String to send -
protocol
is the transport protocol you want to use, it can UDPOSC, TCP and TCPSLIP
Example for Ethernet UDP using a button on Pin 0, this should done before the setup()
#define QLAB_GO_PIN 0
IPAddress qlabIP(10, 101, 1, 101); // IP of QLab
uint16_t qlabPort = 53000; // QLab receive port
OscButton qlabGo(QLAB_GO_PIN , "/go", qlabIP, qlabPort);
Destructor
~OscButton();
Use for destructing a Fader object
Update
To get the actual button state you must call inside the loop()
function
void update();
Example, this must happen in the loop()
function
qlabGo.update();
Button
This is a simple class to create a button which triggers a callback function.
There is following order in the program code needed before setup()
:
- Initialization of the hardware classes
- Declaration (and optional Initialization) of the callback functions
- Initialization of the Button classes
Constructor
Button(uint8_t pin, cbptr callback);
-
pin
are the connection Pin for the button hardware -
callback
pointer to the callback function for the button
Example, this should done before the setup()
#define PAGES 4
int pageNumber = 1; // start page
void upButton() { // callback function the Up button
if (pageNumber >= PAGES) pageNumber = 1; // wrap around
else pageNumber++;
char cmd[12] = "Page ";
strcat(cmd, itoa(pageNumber));
command(cmd); // send command
page(pageNumber); // change global page
Serial.print("Page ");
Serial.println(pageNumber);
}
void downButton() { // callback function the Up button
if (pageNumber <= 1) pageNumber = PAGES;
else pageNumber--;
char cmd[12] = "Page ";
strcat(cmd, itoa(pageNumber));
command(cmd); // send command
page(pageNumber); // change global page
Serial.print("Page ");
Serial.println(pageNumber);
}
Button pageUp(2, upButton);
Button pageDown(3, downButton);
Update
To get the actual button state you must call inside the loop()
function
void update();
Example, this must happen in the loop()
function
pageUp.update();
pageDown.update();
Send OSC message manually
Two steps are needed for send OSC messages manually:
- Create an OSC message using
void oscMessage(const char pattern[], int32_t int32, protocol_t protocol = UDPOSC);
void oscMessage(const char pattern[], float float32, protocol_t protocol = UDPOSC);
void oscMessage(const char pattern[], const char string[], protocol_t protocol = UDPOSC);
void oscMessage(const char pattern[], protocol_t protocol = UDPOSC);
-
const char pattern[]
is the OSC pattern -
int32_t int32
for interger data -
float float32
for float date -
const char string[]
for strings - it is also possible to send no argument, e.g. for QLab Go cmd
-
protocol_t protocol
is the protocol type UDPOSC, TCP or TCPSLIP
- Send an OSC Message, depending on the choosen interface. This functions can called inside setup() or loop() after init the interface.
void sendUDP();
void sendExternUDP();
void sendTCP();
void sendExternTCP();
Receive UDP data with receiveUdp()
You can receive the data send by the GrandMA software. Only UDP receive is supported.
With the receiveUdp()
function you get the data which are automatic parsed to seperate the OSC data.
bool receiveUDP(); // returns true if there is data present
The OSC data consists of the OSC pattern and max. 3 arguments, 1 String, up to two Integers and one Float argument To get the OSC data inside you can use following functions:
patternOSC() // returns the pattern string
stringOSC() // returns the string argument which the command
int1OSC() // returns the 1. integer argument
int2OSC() // returns the 2. integer argument if available
floatOSC() // returns the float argument if available
Example
This must happen in the loop()
function
if (receiveUDP()) {
Serial.print("OSC Pattern: ");
Serial.print(patternOSC());
Serial.print(" String: ");
Serial.print(stringOSC());
Serial.print(" Integer 1: ");
Serial.print(int1OSC());
Serial.print(" Integer 2: ");
Serial.print(int2OSC());
Serial.print(" Float: ");
Serial.println(floatOSC());
}
Exapmle Outputs are
OSC Pattern: /gma3/13.13.1.5.2 String: Go+ Integer 1: 1 Integer 2: 0 Float: 0.00
OSC Pattern: /gma3/13.13.1.5.2 String: Go+ Integer 1: 0 Integer 2: 0 Float: 0.00
OSC Pattern: /gma3/13.13.1.5.2 String: FaderMaster Integer 1: 1 Integer 2: 0 Float: 100.00
The OSC pattern data is very cryptic because of the representation of the internal structure which there is no real docuentation.