typedef struct {
const char address[65];
int channel;
bool is_button;
} Control;
// ESP WT32-ETH01 datasheet:
// https://cdn.shopify.com/s/files/1/0698/0193/5164/files/WT32-ETH01_datasheet_V1.3_-_en.pdf?v=1717218833
// Extra info with potential corrections for above:
// https://community-assets.home-assistant.io/original/4X/1/4/2/1424be77c5e91735a9546fb2507250f137cf3cb1.jpeg
// Wiring info: https://github.com/someweisguy/esp_dmx?tab=readme-ov-file#wiring-an-rs-485-circuit
// Make sure RS-485 board is 3.3V data compatible. (The Maxim MAX485 is)
//////// Config ////////
#define DEBUG // Print debug lines to serial, comment out to disable
// #define EXTRA_DEBUG // Even more debug printing, currently loop timing
//// DMX
#define RX_PIN 5 // Labelled as RXD (May either be pin 35 or 5 on some versions of the board)
#define EN_PIN_RX 33 // Labelled as 485_EN
#define EN_PIN DMX_PIN_NO_CHANGE
// #define TX_PIN 17 // Labelled as TXD
#define TX_PIN DMX_PIN_NO_CHANGE
//// Network
#define HOSTNAME "dmx-to-osc"
// #define DHCP // Comment this line out to use below static IP address settings
#define LOCAL_IP "192.168.0.201"
#define GATEWAY "192.168.0.1"
#define SUBNET "0.0.0.0"
//// OSC
// Port defined in the program on the receiving server
#define OSC_OUT_PORT 8000
// Ip address of the receiving server
#define OSC_REMOTE_IP "192.168.0.200"
//// Define each slider/button to read here:
#define NUM_OSC_ADDRESSES 24 // Should equal the number of DMX channels mapped to OSC addresses
#define HIGHEST_DMX_CHANNEL 24 // Should equal or exceed the highest DMX channel number used below
// Each entry consists of: OSC Address, DMX Channel, Is button?
const Control controls[NUM_OSC_ADDRESSES] = {
{"/V3/console/EX/1/playbackFader/panel/0/col/0", 1, false},
{"/V3/console/EX/1/playbackFader/panel/0/col/1", 2, false},
{"/V3/console/EX/1/playbackFader/panel/0/col/2", 3, false},
{"/V3/console/EX/1/playbackFader/panel/0/col/3", 4, false},
{"/V3/console/EX/1/playbackFader/panel/0/col/4", 5, false},
{"/V3/console/EX/1/playbackFader/panel/1/col/0", 6, false},
{"/V3/console/EX/1/playbackFader/panel/1/col/1", 7, false},
{"/V3/console/EX/1/playbackFader/panel/1/col/2", 8, false},
{"/V3/console/EX/1/playbackFader/panel/1/col/3", 9, false},
{"/V3/console/EX/1/playbackFader/panel/1/col/4", 10, false},
{"/V3/console/EX/1/spbFader/id/0", 11, false},
{"/V3/console/EX/1/spbFader/id/1", 12, false},
{"/V3/console/EX/2/playbackFader/panel/0/col/0", 13, false},
{"/V3/console/EX/2/playbackFader/panel/0/col/1", 14, false},
{"/V3/console/EX/2/playbackFader/panel/0/col/2", 15, false},
{"/V3/console/EX/2/playbackFader/panel/0/col/3", 16, false},
{"/V3/console/EX/2/playbackFader/panel/0/col/4", 17, false},
{"/V3/console/EX/2/playbackFader/panel/1/col/0", 18, false},
{"/V3/console/EX/2/playbackFader/panel/1/col/1", 19, false},
{"/V3/console/EX/2/playbackFader/panel/1/col/2", 20, false},
{"/V3/console/EX/2/playbackFader/panel/1/col/3", 21, false},
{"/V3/console/EX/2/playbackFader/panel/1/col/4", 22, false},
{"/V3/console/EX/2/spbFader/id/0", 23, false},
{"/V3/console/EX/2/spbFader/id/1", 24, false},
};
// Examples of OSC Addresses for Vista:
// "/V3/console/EX/1/gmFader"
// "/V3/console/EX/1/spbFader/id/0"
// "/V3/console/EX/1/playbackFader/panel/0/col/0"
// "/V3/console/EX/1/playbackButton/panel/0/bank/0/row/2/col/0" // Set is_button to true for this
//////// End Config ////////
#ifndef ETH_PHY_TYPE
#define ETH_PHY_TYPE ETH_PHY_LAN8720
#define ETH_PHY_ADDR 1
#define ETH_PHY_MDC 23
#define ETH_PHY_MDIO 18
#define ETH_PHY_POWER 16
#define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN
#endif
#include <ETH.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>
#include <OSCBundle.h>
#include <esp_dmx.h>
#ifdef DEBUG
#define DEBUG_BEGIN(x) Serial.begin (x)
#define DEBUG_PRINT(x) Serial.print (x)
#define DEBUG_PRINTLN(x) Serial.println (x)
#else
#define DEBUG_BEGIN(x)
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif
WiFiUDP Udp;
volatile static bool eth_connected = false;
IPAddress out_IP; // remote IP of your computer
const unsigned int outPort = OSC_OUT_PORT; // remote port to receive OSC
const unsigned int localPort = 8888; // local port to listen for OSC packets (actually not used for sending)
OSCBundle osc_bundle;
const dmx_port_t dmx_num = DMX_NUM_1;
uint8_t prev_data[DMX_PACKET_SIZE];
unsigned long prev_time = 0;
// onEvent is called from a separate FreeRTOS task (thread)!
void onEvent(arduino_event_id_t event) {
switch (event) {
case ARDUINO_EVENT_ETH_START:
DEBUG_PRINTLN("ETH Started");
// The hostname must be set after the interface is started, but needs
// to be set before DHCP, so set it from the event handler thread.
ETH.setHostname(HOSTNAME);
break;
case ARDUINO_EVENT_ETH_CONNECTED:
DEBUG_PRINTLN("ETH Connected");
#ifndef DHCP
eth_connected = true; // Since there's no ARDUINO_EVENT_ETH_GOT_IP event with static IP
#endif
break;
case ARDUINO_EVENT_ETH_GOT_IP:
DEBUG_PRINTLN("ETH Got IP");
DEBUG_PRINTLN(ETH);
eth_connected = true;
break;
case ARDUINO_EVENT_ETH_LOST_IP:
DEBUG_PRINTLN("ETH Lost IP");
eth_connected = false;
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
DEBUG_PRINTLN("ETH Disconnected");
eth_connected = false;
break;
case ARDUINO_EVENT_ETH_STOP:
DEBUG_PRINTLN("ETH Stopped");
eth_connected = false;
break;
default: break;
}
}
void setup() {
DEBUG_BEGIN(115200);
//// Ethernet
Network.onEvent(onEvent);
ETH.begin();
#ifndef DHCP
ETH.config(LOCAL_IP, GATEWAY, SUBNET);
#endif
//// OSC
out_IP.fromString(OSC_REMOTE_IP);
Udp.begin(localPort);
//// DMX
dmx_config_t config = DMX_CONFIG_DEFAULT;
const int personality_count = 1;
dmx_personality_t personalities[] = {
{HIGHEST_DMX_CHANNEL, "Default Personality"}
};
dmx_driver_install(dmx_num, &config, personalities, personality_count);
dmx_set_pin(dmx_num, TX_PIN, RX_PIN, EN_PIN);
// Keep the DMX en pin low because we're only reading, and the library seems to behave weirdly in some way?
pinMode(EN_PIN_RX, OUTPUT);
digitalWrite(EN_PIN_RX, LOW);
}
void loop() {
// Print time since last loop
#ifdef EXTRA_DEBUG
unsigned long curr_time = millis();
DEBUG_PRINT("Time since last loop: ");
DEBUG_PRINTLN(curr_time - prev_time);
prev_time = curr_time;
#endif
dmx_packet_t dmx_packet;
uint8_t data[DMX_PACKET_SIZE];
// Blocking receive, 1250ms timeout
int size = dmx_receive(dmx_num, &dmx_packet, DMX_TIMEOUT_TICK);
if (size == 0) {
return;
}
if (dmx_packet.err != DMX_OK) {
DEBUG_PRINTLN("An error occurred receiving DMX!");
return;
}
dmx_read(DMX_NUM_1, data, size);
bool data_changed = false;
for (int i = 0; i < NUM_OSC_ADDRESSES; i++) {
int channel = controls[i].channel;
if (data[channel] != prev_data[channel]) {
prev_data[channel] = data[channel];
if (!data_changed) {
DEBUG_PRINTLN("Changed DMX Data:");
data_changed = true;
}
DEBUG_PRINT("{");
DEBUG_PRINT(channel);
DEBUG_PRINT(", ");
DEBUG_PRINT(data[channel]);
DEBUG_PRINT("}, ");
if (eth_connected) {
if (controls[i].is_button) {
osc_bundle.add(controls[i].address).add((bool)data[channel]);
} else {
float fader_value = data[channel] / 255.0f;
osc_bundle.add(controls[i].address).add(fader_value);
}
}
}
}
if (data_changed) DEBUG_PRINTLN("");
if (data_changed && eth_connected) {
Udp.beginPacket(out_IP, outPort);
osc_bundle.send(Udp);
Udp.endPacket();
}
osc_bundle.empty();
}