#include "ArduinoJson-v7.4.1.h"
#define DEBUG
#ifdef DEBUG
#define DEBUG_PRINT(...) Serial.printf(__VA_ARGS__); Serial.flush()
#else
#define DEBUG_PRINT(...)
#endif
#include <vector>
class DeviceInterface{
#define INTERFACE_STATE "state"
#define INTERFACE_PROGRESS "progress"
#define INTERFACE_EXPOSED "exposed"
#define INTERFACE_CONFIGZ "config"
#define INTERFACE_OFFLINE "offline"
#define INTERFACE_GROUP "group"
#define INTERFACE_USENETWORK "network"
public:
String interfaceName = "noname";
String interfaceState="disconnected";
uint8_t progress = 0;
bool exposed = true;
bool offline = false;
bool network = true; // by default an interface uses the network
String group = "undef"; // used by the strip to define which deviceinterface they can connect
DeviceInterface(bool);
~DeviceInterface();
String serializeState(){
JsonDocument doc;
doc[interfaceName][INTERFACE_STATE] = interfaceState;
doc[interfaceName][INTERFACE_PROGRESS] = progress;
doc.shrinkToFit();
String output;
serializeJson(doc, output);
//{"MixerOffline":{"state":"disconnected","progress":0}}
DEBUG_PRINT("\nserializeState: %s",output.c_str());
return output;
}
String serializeConfig(bool mainNode = true){
// by default we serialize the full content (main node)
// but if an interface uses another one, it will only partially serialize
// means we don't include somes attributes
JsonDocument doc;
//JsonObject config = doc[interfaceName].createNestedObject(/*INTERFACE_CONFIG*/);
JsonObject config;
if (exposed)
config = doc.createNestedObject(interfaceName);
else config = doc.to<JsonObject>();
if (mainNode == true){
// that's the attribute we do not serialize in case of partial serialization
config[INTERFACE_EXPOSED] = exposed;
config[INTERFACE_USENETWORK] = network;
config[INTERFACE_OFFLINE] = offline;
config[INTERFACE_GROUP] = group;
}
derivedConfig(config);
doc.shrinkToFit();
String output;
serializeJson(doc, output);
//{"MixerOffline":{"config":{FILLED BY DERIVED CLASS}}}
DEBUG_PRINT("\nserializeConfig: %s",output.c_str());
return output;
}
bool restoreFromString(const char* config){
JsonDocument doc;
DeserializationError error = deserializeJson(doc, config);
if (error) {
Serial.printf("\n%s restoreFromString %s",interfaceName.c_str(),error.c_str());
return false;
}
JsonObject configJson = doc.as<JsonObject>();
//by default a used interface (which means it had a partial serialization)
// is not exposed and is online, this can be overwritten by the deserialization
// of a full interface
//offline = false;
//exposed = false;
restore(configJson);
doc.shrinkToFit();
#ifdef DEBUG
String output;
serializeJson(doc, output);
//{"MixerOffline":{"config":{FILLED BY DERIVED CLASS}}}
DEBUG_PRINT("\nrestoreFromString: %s",output.c_str());
#endif
return true;
}
virtual void serialize(JsonDocument& interfaceDoc){
//start with the config
deserializeJson(interfaceDoc, serializeConfig());
JsonDocument interfaceState;
// use another document to grab the state
deserializeJson(interfaceState, serializeState());
// fuzion the two, we have to do this on object as otherwise, key content would be overwritteb
// on extrait le nom de l'interface (qui est la clef commune aux deux documents)
const char* interfaceName = interfaceDoc.as<JsonObject>().begin()->key().c_str();
JsonObject state = interfaceState[interfaceName];
JsonObject fullDoc = interfaceDoc[interfaceName];
for (JsonPair kv : state) {
fullDoc[kv.key()] = kv.value(); // Ajoute ou écrase les clés dans name1
}
}
virtual void restore(const JsonObject& config) = 0;
virtual void derivedConfig(JsonObject& config) = 0;
};
class InterfaceRepository{
#define INTERFACES_KEY "interfaces"
public:
std::vector<DeviceInterface*> interfaces;
String serialize(){
JsonDocument doc;
JsonArray interfacesArray = doc[INTERFACES_KEY].to<JsonArray>();
for (DeviceInterface* interface : interfaces) {
// serializing is used to save the interfaces config in the config file or to send them to the web app to be modified
// hence, we only do that for interface which are "persistant"
if (interface->exposed == true){
JsonDocument interfaceDoc;
interface->serialize(interfaceDoc);
interfaceDoc.shrinkToFit();
interfacesArray.add(interfaceDoc);
}
}
doc.shrinkToFit();
String output;
serializeJson(doc, output);
DEBUG_PRINT("\nserializeRepo: %s\n",output.c_str());
return output;
}
};
InterfaceRepository interfaceRepo;
DeviceInterface::DeviceInterface(bool _configurable = true):exposed(_configurable){
interfaceRepo.interfaces.push_back(this);
}
DeviceInterface::~DeviceInterface(){
interfaceRepo.interfaces.erase(std::remove(interfaceRepo.interfaces.begin(), interfaceRepo.interfaces.end(), this), interfaceRepo.interfaces.end());
}
class OSCInterface: public DeviceInterface{
#define GROUP_INTERFACE_OSC "OSC"
public:
OSCInterface(bool _configurable = false):DeviceInterface(_configurable){
group = GROUP_INTERFACE_OSC;
}
};
#define NETWORKOSC_INTERFACE "Network(OSC)"
class NetworkOSC: public OSCInterface{
#define CONFIG_OSCIP "oscIP"
#define CONFIG_OSCPORT "oscPort"
public:
String OSC_IPaddress;
int OSC_port;
NetworkOSC(String _OSC_IPaddress,int _OSC_port,bool _configurable=false):OSC_IPaddress(_OSC_IPaddress),OSC_port(_OSC_port), OSCInterface(_configurable){
interfaceName = NETWORKOSC_INTERFACE;
}
virtual void derivedConfig(JsonObject& config){
//{"oscIP":"5.6.7.8","oscPort":3000}
config[CONFIG_OSCIP] = OSC_IPaddress;
config[CONFIG_OSCPORT] = OSC_port;
}
virtual void restore(const JsonObject& config){
OSC_IPaddress = config[CONFIG_OSCIP].as<String>();
OSC_port = config[CONFIG_OSCPORT];
}
};
class DMXInterface: public DeviceInterface{
#define GROUP_INTERFACE_DMX "DMX"
public:
DMXInterface(bool _configurable):DeviceInterface(_configurable){
group = GROUP_INTERFACE_DMX;
}
};
#define ARTNETDMX_INTERFACE "Artnet"
class ArtnetDMXInterface: public DMXInterface{
#define CONFIG_ARTNET "voidArtnetparam"
public:
String voidArtnetParam = "artnet123";
ArtnetDMXInterface(bool _configurable):DMXInterface(_configurable){
interfaceName = ARTNETDMX_INTERFACE;
}
virtual void derivedConfig(JsonObject& config){
config[CONFIG_ARTNET] = voidArtnetParam; // fill any parameters that are required by sysex
}
virtual void restore(const JsonObject& config){
voidArtnetParam = config[CONFIG_ARTNET].as<String>();
}
};
#define THREEEPINDMX_INTERFACE "3PinDMX"
class ThreePinDMXInterface: public DMXInterface{
#define CONFIG_3PINDMX "void3PinDMXparam"
public:
String void3pinDMXParam = "3pin123";
ThreePinDMXInterface(bool _configurable):DMXInterface(_configurable){
interfaceName = THREEEPINDMX_INTERFACE;
network = false; // no network dependency
}
virtual void derivedConfig(JsonObject& config){
config[CONFIG_3PINDMX] = void3pinDMXParam; // fill any parameters that are required by sysex
}
virtual void restore(const JsonObject& config){
void3pinDMXParam = config[CONFIG_3PINDMX].as<String>();
}
};
class MIDIInterface: public DeviceInterface{
#define GROUP_INTERFACE_MIDI "MIDI"
public:
virtual void sendSysex() = 0;
MIDIInterface(bool _configurable):DeviceInterface(_configurable){
group = GROUP_INTERFACE_MIDI;
}
};
#define BLEMIDI_INTERFACE "BLEMIDI"
class BLEMIDIInterface: public MIDIInterface{
#define CONFIG_BLE "voidBLEparam"
public:
String voidBLEparam = "abc";
BLEMIDIInterface(bool _configurable):MIDIInterface(_configurable){
interfaceName = BLEMIDI_INTERFACE;
network = false; // no network dependency
}
virtual void derivedConfig(JsonObject& config){
config[CONFIG_BLE] = voidBLEparam; // fill any parameters that are required by sysex
}
virtual void restore(const JsonObject& config){
voidBLEparam = config[CONFIG_BLE].as<String>();
}
virtual void sendSysex(){}
};
#define USBMIDI_INTERFACE "USBMIDI"
class USBMIDIInterface: public MIDIInterface{
#define CONFIG_USB "voidUSBparam"
public:
String voidUSBparam = "toto";
USBMIDIInterface(bool _configurable):MIDIInterface(_configurable){
interfaceName = USBMIDI_INTERFACE;
network = false; // no network dependency
}
virtual void sendSysex(){}
virtual void derivedConfig(JsonObject& config){
config[CONFIG_USB] = voidUSBparam; // fill any parameters that are required by sysex
}
virtual void restore(const JsonObject& config){
voidUSBparam = config[CONFIG_USB].as<String>();
}
};
#define APPLEMIDI_INTERFACE "AppleMIDI"
class AppleMIDIInterface: public MIDIInterface{
#define CONFIG_APPLE "voidAppleMidiParam"
public:
String voidAppleMidiParam = "titi";
AppleMIDIInterface(bool _configurable):MIDIInterface(_configurable){
interfaceName = APPLEMIDI_INTERFACE;
}
virtual void sendSysex(){}
virtual void derivedConfig(JsonObject& config){
config[CONFIG_APPLE] = voidAppleMidiParam; // fill any parameters that are required by sysex
}
virtual void restore(const JsonObject& config){
voidAppleMidiParam = config[CONFIG_APPLE].as<String>();
}
};
class MIDIOSC: public OSCInterface{
#define CONFIG_SYSEX "voidSysexParam"
public:
MIDIInterface* midiTransport = nullptr;
String voidSysexParam = "xyz";
MIDIOSC(String midiInterface, bool _configurable){
instantiate(midiInterface,_configurable);
};
MIDIOSC(JsonObject& config){
//JsonObject uses = config[INTERFACE_USES].to<JsonObject>();
String midiInterfaceName ;//= uses.begin()->key().c_str();
serializeJson(config, midiInterfaceName);
//DEBUG_PRINT("\nZOGZOG: %s",midiInterfaceName.c_str());
bool _configurable = config[INTERFACE_EXPOSED];
instantiate(midiInterfaceName,_configurable);
restore(config);
}
~MIDIOSC(){
delete midiTransport;
}
void instantiate(String midiInterface, bool _configurable){
interfaceName = "MIDI-"+midiInterface;
if (midiInterface == APPLEMIDI_INTERFACE) midiTransport = new AppleMIDIInterface(_configurable);
else if (midiInterface == BLEMIDI_INTERFACE) midiTransport = new BLEMIDIInterface(_configurable);
else midiTransport = new USBMIDIInterface(_configurable);
}
virtual void derivedConfig(JsonObject& config){
//{"sysex":""}}
config[CONFIG_SYSEX] = voidSysexParam; // fill any parameters that are required by sysex
// we retrieve the config of the OSCInterface instance which is specific
String usesConf = midiTransport->serializeConfig(false); // serializing a subnode
JsonDocument midiConf;
// convert it back to an object that can be attached to the parent config
DeserializationError error = deserializeJson(midiConf, usesConf);
if (error) {
DEBUG_PRINT("\n%s serialize %s",interfaceName.c_str(),error.c_str());
return;
}
midiConf.shrinkToFit();
// "uses":{"USBMIDIInterface":{"voidUSBparam":""}}
// "uses":{"BLEMIDI":{"config":{"voidBLEparam":""}}
// "uses":{"AppleMIDI":{"config":{"voidAppleMidiParam":""}}
// partial serialization means we embbed the "uses" into the core config when it comes to unexposed interface
// in the first case we create a key "uses", in the second we just copy the elements ne by one
//if (exposed) config[INTERFACE_USES] = midiConf;
//else
{
// copie manuelle
for (JsonPair kv : midiConf.as<JsonObject>()) config[kv.key()] = kv.value();
}
}
virtual void restore(const JsonObject& config){
voidSysexParam = config[CONFIG_SYSEX].as<String>();
/*JsonObject midiConfig = config[INTERFACE_USES]
midiTransport->restore(midiConfig);*/
midiTransport->restore(config);
}
};
#define GROUP_INTERFACE_MIXER "Mixer"
class MixerInterface: public DeviceInterface{
#define CONFIG_MIXERNAME "mixerName"
#define CONFIG_MIXERMODEL "model"
public:
String mixerName, mixerModel;
MixerInterface(String _name, String _model):mixerName(_name), mixerModel(_model){
group = GROUP_INTERFACE_MIXER;
}
// by design, we only allow one connection to a mixer, hence we have to record which instance of
// derived class has establishedso all the other one can self turn them to "offline" when update state is called
static MixerInterface* currentConnectionOwner;
virtual void derivedConfig(JsonObject& config){
//{"mixerName":"software","model":"virtual"}
config[CONFIG_MIXERNAME] = mixerName;
config[CONFIG_MIXERMODEL] = mixerModel;
}
virtual void restore(const JsonObject& config){
//mixerName = config[CONFIG_MIXERNAME].as<String>(); // since mixerName is dynamically retrieved from mixer, we should not consider it
mixerModel = config[CONFIG_MIXERMODEL].as<String>();
}
};
class OSCMixerInterface : public MixerInterface{
public:
OSCInterface* osc;
OSCMixerInterface(String _name, String _model, OSCInterface*_osc):osc(_osc),MixerInterface(_name,_model){
}
virtual void derivedConfig(JsonObject& config){
MixerInterface::derivedConfig(config);
//{"mixerName":"software","model":"virtual"}
// we retrieve the config of the OSCInterface instance which is specific
String usesConf = osc->serializeConfig(false); // false : we partially serialize
JsonDocument oscConf;
// convert it back to an object that can be attached to the parent config
DeserializationError error = deserializeJson(oscConf, usesConf);
if (error) {
DEBUG_PRINT("\n%s serialize %s",interfaceName.c_str(),error.c_str());
return;
}
oscConf.shrinkToFit();
//config[INTERFACE_USES] = oscConf;
for (JsonPair kv : oscConf.as<JsonObject>()) config[kv.key()] = kv.value();
/*
"uses":{"NetworkOSC":{"config":{"oscIP":"1.2.3.4-restored","oscPort":10029}
"uses":{"MIDIOSC":{"config":{"voidSysexParam":"null"}
*/
}
virtual void restore(const JsonObject& config){
osc->restore(config);
MixerInterface::restore(config);
}
};
class MixingStation: public OSCMixerInterface{
#define MS_CONFIG_AUTOCONNECT "autoConnect"
#define MS_CONFIG_AUTOCONNECT_MIXERIP "mixerIP"
#define MS_CONFIG_AUTOCONNECT_MIXERID "mixerID"
#define MS_CONFIG_AUTOCONNECT_APIPORT "restAPIport"
public:
uint8_t autoConnectMixerID = 1;
int RESTAPI_port = 8080;
String mixerIPaddress = "1.1.1.1";
bool autoConnect = false;
MixingStation(OSCInterface* osc):OSCMixerInterface("undef","virtual",osc){
interfaceName = "MixingStation";
}
virtual void derivedConfig(JsonObject& config){
OSCMixerInterface::derivedConfig(config);
// uses NetworkOSC, so outcome will be: {"mixerName":"undef","model":"virtual","oscIP":"5.6.7.8","oscPort":3000}
config[MS_CONFIG_AUTOCONNECT_MIXERID] = autoConnectMixerID ;
config[MS_CONFIG_AUTOCONNECT_APIPORT] = RESTAPI_port;
config[MS_CONFIG_AUTOCONNECT] = autoConnect;
config[MS_CONFIG_AUTOCONNECT_MIXERIP] = mixerIPaddress;
//{"MixingStation":{"config":{"mixerName":"undef","model":"virtual","oscIP":"5.6.7.8","oscPort":3000,"mixerID":1,"restAPIport":8080,"autoConnect":false,"mixerIP":"1.1.1.1"}}}
}
virtual void restore(const JsonObject& config){
autoConnectMixerID = config[MS_CONFIG_AUTOCONNECT_MIXERID];
RESTAPI_port = config[MS_CONFIG_AUTOCONNECT_APIPORT];
autoConnect = config[MS_CONFIG_AUTOCONNECT];
mixerIPaddress = config[MS_CONFIG_AUTOCONNECT_MIXERIP].as<String>();
OSCMixerInterface::restore(config);
}
};
class BehringerXMR: public OSCMixerInterface{
#define BEHRINGER_CONFIG_DISCOVERY "discovery"
public:
bool discovery = false;
BehringerXMR(OSCInterface* osc):OSCMixerInterface("X32Emul","X32",osc){
interfaceName = "X+Mseries-"+osc->interfaceName;
}
virtual void derivedConfig(JsonObject& config){
// uses NetworkOSC, so outcome will be: {"mixerName":"undef","model":"virtual","oscIP":"5.6.7.8","oscPort":3000}
OSCMixerInterface::derivedConfig(config);
config[BEHRINGER_CONFIG_DISCOVERY] = discovery;
}
virtual void restore(const JsonObject& config){
discovery = config[BEHRINGER_CONFIG_DISCOVERY];
OSCMixerInterface::restore(config);
}
};
enum{
ID_MS,
ID_BEHRINGER_MIDI_USB,
ID_BEHRINGER_MIDI_BLE,
ID_BEHRINGER_MIDI_APPLE,
ID_BEHRINGER_NET,
ID_OSCNET,
ID_APPLEMIDI,
ID_USBMIDI,
ID_BLEMIDI,
ID_ARTNETDMX,
ID_3PINDMX,
ID_MAX
};
class MixerServer{
public:
OSCInterface* osc = nullptr;
MixerInterface* mixerInterface = nullptr;
void fake_refresh(uint8_t iFaceCount){
/*if (mixerInterface != nullptr) delete mixerInterface;
if (osc != nullptr) delete osc;*/
switch(iFaceCount){
case ID_BEHRINGER_MIDI_APPLE:
osc = new MIDIOSC(APPLEMIDI_INTERFACE, false); // false = not configurable
mixerInterface = new BehringerXMR(osc);
mixerInterface->serializeConfig();
break;
case ID_BEHRINGER_MIDI_USB:
osc = new MIDIOSC(USBMIDI_INTERFACE, false); // false = not configurable
mixerInterface = new BehringerXMR(osc);
mixerInterface->serializeConfig();
break;
case ID_BEHRINGER_MIDI_BLE:
osc = new MIDIOSC(BLEMIDI_INTERFACE, false); // false = not configurable
mixerInterface = new BehringerXMR(osc);
mixerInterface->serializeConfig();
break;
case ID_BEHRINGER_NET:
osc = new NetworkOSC("1.2.3.4",10024,false);
mixerInterface = new BehringerXMR(osc);
mixerInterface->serializeConfig();
break;
case ID_MS:
osc = new NetworkOSC("5.6.7.8",3000,false);
mixerInterface = new MixingStation(osc);
mixerInterface->serializeConfig();
break;
case ID_OSCNET:
new NetworkOSC("99.98.97.96",10024,true); // true = configurable
break;
case ID_APPLEMIDI:
new AppleMIDIInterface(true);
break;
case ID_USBMIDI:
new USBMIDIInterface(true);
break;
case ID_BLEMIDI:
new BLEMIDIInterface(true);
break;
case ID_ARTNETDMX:
new ArtnetDMXInterface(true);
break;
case ID_3PINDMX:
new ThreePinDMXInterface(true);
break;
default:
Serial.printf("\nUndefined interface");
break;
}
if (mixerInterface != nullptr) mixerInterface->serializeState();
Serial.printf("\n");
}
};
MixerServer mixerServer;
void setup() {
Serial.begin(115200);
for (uint8_t i=0;i<ID_MAX;i++){
mixerServer.fake_refresh(i);
}
/*DEBUG_PRINT("\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
JsonDocument doc;
String interfaceString = "{\"MIDIOSC\":{\"voidSysexParam\":\"null\",\"uses\":{\"Apple MIDI\":{\"voidAppleMidiParam\":\"null\"}}}}";
DeserializationError error = deserializeJson(doc, interfaceString);
if (error) {
DEBUG_PRINT("\ninterfaceString %s",error.c_str());
return;
}
JsonObject interfaceDef = doc.as<JsonObject>();
MIDIOSC sysex = MIDIOSC(interfaceDef);
DEBUG_PRINT("\nSYSEXOS(): ",sysex.serializeConfig().c_str());*/
DEBUG_PRINT("\n--------------------------------------------------");
DEBUG_PRINT("\n<%s>",interfaceRepo.serialize().c_str());
}
void loop() {
}
Loading
esp32-s3-devkitc-1
esp32-s3-devkitc-1