diff --git a/examples/RDMIntoDMXOut.ino b/examples/RDMIntoDMXOut/RDMIntoDMXOut.ino similarity index 100% rename from examples/RDMIntoDMXOut.ino rename to examples/RDMIntoDMXOut/RDMIntoDMXOut.ino diff --git a/examples/RDMSerialRecv/RDMSerialRecv.ino b/examples/RDMSerialRecv/RDMSerialRecv.ino index 8c8f142..2086180 100644 --- a/examples/RDMSerialRecv/RDMSerialRecv.ino +++ b/examples/RDMSerialRecv/RDMSerialRecv.ino @@ -31,6 +31,9 @@ // 25.05.2017 Stefan Krupop: Add support for sensors // 21.08.2018 improvements and typo by Peter Newman // 31.10.2018 Remove unnecessary #include by Graham Hanson +// 04.06.2023 Tim Nijssen: Add support for manufacturer-specific parameters +// 05.06.2023 Tim Nijssen: integrate sensors example +// 05.06.2023 Tim Nijssen: Add example for manufacturer-specific parameters // - - - - - #include @@ -81,7 +84,7 @@ void setup () { #endif // initialize the Serial interface to be used as an RDM Device Node. - // There are several constants that have to be passed to the library so it can reposonse to the + // There are several constants that have to be passed to the library so it can respond to the // corresponding commands for itself. DMXSerial2.init(&rdmInit, processCommand); diff --git a/examples/RDMSerialRecvParameter/RDMSerialRecvParameter.ino b/examples/RDMSerialRecvParameter/RDMSerialRecvParameter.ino new file mode 100644 index 0000000..7fd3b28 --- /dev/null +++ b/examples/RDMSerialRecvParameter/RDMSerialRecvParameter.ino @@ -0,0 +1,269 @@ +// - - - - - +// DmxSerial2 - A hardware supported interface to DMX and RDM. +// RDMSerialRecv.ino: Sample RDM application. +// +// Copyright (c) 2011-2013 by Matthias Hertel, http://www.mathertel.de +// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx +// +// This Arduino project is a sample application for the DMXSerial2 library that shows +// how a 3 channel receiving RDM client can be implemented. +// The 3 channels are used for PWM Output: +// address (startAddress) + 0 (red) -> PWM Port 9 +// address (startAddress) + 1 (green) -> PWM Port 6 +// address (startAddress) + 2 (blue) -> PWM Port 5 +// +// This sample shows how Device specific RDM Commands are handled in the processCommand function. +// The following RDM commands are implemented here: +// E120_LAMP_HOURS +// E120_DEVICE_HOURS +// +// More documentation and samples are available at http://www.mathertel.de/Arduino +// 06.12.2012 created from DMXSerialRecv sample. +// 09.12.2012 added first RDM response. +// 22.01.2013 first published version to support RDM +// 03.03.2013 Using DMXSerial2 as a library +// 15.05.2013 Arduino Leonard and Arduino MEGA compatibility +// 15.12.2013 ADD: output information on a LEONARDO board by using the #define SERIAL_DEBUG definition +// If you have to save pgm space you can delete the inner lines of this "#if" blocks +// 24.01.2014 Peter Newman/Sean Sill: Get device specific PIDs returning properly in supportedParameters +// 24.01.2014 Peter Newman: Make the device specific PIDs compliant with the OLA RDM Tests. Add device model ID option +// 12.04.2015 change of using datatype boolean to bool8. +// 25.05.2017 Stefan Krupop: Add support for sensors +// 21.08.2018 improvements and typo by Peter Newman +// 31.10.2018 Remove unnecessary #include by Graham Hanson +// 04.06.2023 Tim Nijssen: Add support for manufacturer-specific parameters +// 05.06.2023 Tim Nijssen: integrate sensors example +// 05.06.2023 Tim Nijssen: Add example for manufacturer-specific parameters +// - - - - - + +#include + +// uncomment this line for enabling information on a LEONARD board. +// #define SERIAL_DEBUG + +// Constants for demo program + +const int RedPin = 9; // PWM output pin for Red Light. +const int GreenPin = 6; // PWM output pin for Green Light. +const int BluePin = 5; // PWM output pin for Blue Light. + +// manufacturer-specific PIDs should be in the range 0x8000-0xFFDF +#define PARAM_A_PID 0x8000 +#define PARAM_B_PID 0x8001 + +// color: #203050 * 2 +#define RedDefaultLevel 0x20 * 2 +#define GreenDefaultLevel 0x30 * 2 +#define BlueDefaultLevel 0x50 * 2 + +// define the RGB output color +void rgb(byte r, byte g, byte b) { + analogWrite(RedPin, r); + analogWrite(GreenPin, g); + analogWrite(BluePin, b); +} // rgb() + +// see DMXSerial2.h for the definition of the fields of this structure +const uint16_t my_pids[] = { E120_DEVICE_HOURS, E120_LAMP_HOURS }; +const RDMPARAMETER my_parameters[] = { + { PARAM_A_PID, 1, E120_DS_UNSIGNED_BYTE, E120_UNITS_NONE, E120_PREFIX_NONE, 0, 255, 128, true, true, "paramA" }, + { PARAM_B_PID, 2, E120_DS_UNSIGNED_BYTE, E120_UNITS_NONE, E120_PREFIX_NONE, 0, 255, 128, true, true, "paramB" } +}; +struct RDMINIT rdmInit = { + "mathertel.de", // Manufacturer Label + 1, // Device Model ID + "Arduino RDM Device", // Device Model Label + 3, // footprint + (sizeof(my_pids) / sizeof(uint16_t)), my_pids, + 0, NULL, + (sizeof(my_parameters) / sizeof(RDMPARAMETER)), my_parameters +}; + +byte parameterA = 128; +byte parameterB[2] = { 0, 255 }; + +void setup() { + +#if defined(SERIAL_DEBUG) + // The Serial port can be used on Arduino Leonard Boards for debugging purpose + // because it is not mapped to the real serial port of the ATmega32U4 chip but to the USB port. + // Don't use that on Arduino Uno, 2009,... boards based on ATmega328 or ATmega168 chips. + Serial.begin(9600); + while (!Serial) + ; + Serial.println("starting..."); +#endif + + // initialize the Serial interface to be used as an RDM Device Node. + // There are several constants that have to be passed to the library so it can respond to the + // corresponding commands for itself. + DMXSerial2.init(&rdmInit, processCommand, getParameters, setParameters); + + uint16_t start = DMXSerial2.getStartAddress(); +#if defined(SERIAL_DEBUG) + Serial.print("Listening on DMX address #"); + Serial.println(start); +#endif + + // set default values to dark red + // this color will be shown when no signal is present for the first 5 seconds. + DMXSerial2.write(start + 0, 30); + DMXSerial2.write(start + 1, 0); + DMXSerial2.write(start + 2, 0); + + // enable pwm outputs + pinMode(RedPin, OUTPUT); // sets the digital pin as output + pinMode(GreenPin, OUTPUT); + pinMode(BluePin, OUTPUT); + +#if defined(SERIAL_DEBUG) + // output the current DeviceID + + DEVICEID thisDevice; + DMXSerial2.getDeviceID(thisDevice); + + Serial.print("This Device is: "); + if (thisDevice[0] < 0x10) Serial.print('0'); + Serial.print(thisDevice[0], HEX); + if (thisDevice[1] < 0x10) Serial.print('0'); + Serial.print(thisDevice[1], HEX); + Serial.print(":"); + if (thisDevice[2] < 0x10) Serial.print('0'); + Serial.print(thisDevice[2], HEX); + if (thisDevice[3] < 0x10) Serial.print('0'); + Serial.print(thisDevice[3], HEX); + if (thisDevice[4] < 0x10) Serial.print('0'); + Serial.print(thisDevice[4], HEX); + if (thisDevice[5] < 0x10) Serial.print('0'); + Serial.print(thisDevice[5], HEX); + Serial.println(); +#endif + +} // setup() + + +void loop() { + // Calculate how long no data backet was received + unsigned long lastPacket = DMXSerial2.noDataSince(); + + if (DMXSerial2.isIdentifyMode()) { + // RDM command for identification was sent. + // Blink the device. + unsigned long now = millis(); + if (now % 1000 < 500) { + rgb(200, 200, 200); + } else { + rgb(0, 0, 0); + } // if + + } else if (lastPacket < 30000) { + // read recent DMX values and set pwm levels + analogWrite(RedPin, DMXSerial2.readRelative(0)); + analogWrite(GreenPin, DMXSerial2.readRelative(1)); + analogWrite(BluePin, DMXSerial2.readRelative(2)); + + } else { +#if defined(SERIAL_DEBUG) + Serial.println("no signal since 30 secs."); +#endif + // Show default color, when no data was received since 30 seconds or more. + analogWrite(RedPin, RedDefaultLevel); + analogWrite(GreenPin, GreenDefaultLevel); + analogWrite(BluePin, BlueDefaultLevel); + } // if + + // check for unhandled RDM commands + DMXSerial2.tick(); +} // loop() + + +// This function was registered to the DMXSerial2 library in the initRDM call. +// Here device specific RDM Commands are implemented. +bool8 processCommand(struct RDMDATA *rdm, uint16_t *nackReason) { + byte CmdClass = rdm->CmdClass; // command class + uint16_t Parameter = rdm->Parameter; // parameter ID + bool8 handled = false; + + // This is a sample of how to return some device specific data + if (Parameter == SWAPINT(E120_DEVICE_HOURS)) { // 0x0400 + if (CmdClass == E120_GET_COMMAND) { + if (rdm->DataLength > 0) { + // Unexpected data + *nackReason = E120_NR_FORMAT_ERROR; + } else if (rdm->SubDev != 0) { + // No sub-devices supported + *nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE; + } else { + rdm->DataLength = 4; + rdm->Data[0] = 0; + rdm->Data[1] = 0; + rdm->Data[2] = 2; + rdm->Data[3] = 0; + handled = true; + } + } else if (CmdClass == E120_SET_COMMAND) { + // This device doesn't support set + *nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS; + } + + } else if (Parameter == SWAPINT(E120_LAMP_HOURS)) { // 0x0401 + if (CmdClass == E120_GET_COMMAND) { + if (rdm->DataLength > 0) { + // Unexpected data + *nackReason = E120_NR_FORMAT_ERROR; + } else if (rdm->SubDev != 0) { + // No sub-devices supported + *nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE; + } else { + + unsigned long seconds = millis() / 1000; + + rdm->DataLength = 4; + rdm->Data[0] = (seconds & 0xff000000) >> 24; + rdm->Data[1] = (seconds & 0x00ff0000) >> 16; + rdm->Data[2] = (seconds & 0x0000ff00) >> 8; + rdm->Data[3] = seconds & 0x000000ff; + handled = true; + } + } else if (CmdClass == E120_SET_COMMAND) { + // This device doesn't support set + *nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS; + } + } // if + + return handled; +} // processCommand + +// This function was registered to the DMXSerial2 library in the initRDM call. +// Here retrieval of manufacturer-specific parameter values is implemented. +bool8 getParameters(uint16_t pid, uint16_t parameterIndex, int8_t *value) { + if (pid == PARAM_A_PID) { + value[0] = parameterA; + return true; + } else if (pid == PARAM_B_PID) { + value[0] = parameterB[0]; + value[1] = parameterB[1]; + return true; + } else { + return false; + } +} // getParameters + +// This function was registered to the DMXSerial2 library in the initRDM call. +// Here setting of manufacturer-specific parameter values is implemented. +bool8 setParameters(uint16_t pid, uint16_t parameterIndex, int8_t *value) { + // do something with the value here, eg store it in eeprom + if (pid == PARAM_A_PID) { + parameterA = value[0]; + return true; + } else if (pid == PARAM_B_PID) { + parameterB[0] = value[0]; + parameterB[1] = value[1]; + return true; + } else { + return false; + } + return true; +} // setParameters + +// End. diff --git a/examples/RDMSerialRecvSensor/RDMSerialRecvSensor.ino b/examples/RDMSerialRecvSensor/RDMSerialRecvSensor.ino new file mode 100644 index 0000000..f6910bc --- /dev/null +++ b/examples/RDMSerialRecvSensor/RDMSerialRecvSensor.ino @@ -0,0 +1,288 @@ +// - - - - - +// DmxSerial2 - A hardware supported interface to DMX and RDM. +// RDMSerialRecv.ino: Sample RDM application. +// +// Copyright (c) 2011-2013 by Matthias Hertel, http://www.mathertel.de +// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx +// +// This Arduino project is a sample application for the DMXSerial2 library that shows +// how a 3 channel receiving RDM client can be implemented. +// The 3 channels are used for PWM Output: +// address (startAddress) + 0 (red) -> PWM Port 9 +// address (startAddress) + 1 (green) -> PWM Port 6 +// address (startAddress) + 2 (blue) -> PWM Port 5 +// +// This sample shows how Device specific RDM Commands are handled in the processCommand function. +// The following RDM commands are implemented here: +// E120_LAMP_HOURS +// E120_DEVICE_HOURS +// +// More documentation and samples are available at http://www.mathertel.de/Arduino +// 06.12.2012 created from DMXSerialRecv sample. +// 09.12.2012 added first RDM response. +// 22.01.2013 first published version to support RDM +// 03.03.2013 Using DMXSerial2 as a library +// 15.05.2013 Arduino Leonard and Arduino MEGA compatibility +// 15.12.2013 ADD: output information on a LEONARDO board by using the #define SERIAL_DEBUG definition +// If you have to save pgm space you can delete the inner lines of this "#if" blocks +// 24.01.2014 Peter Newman/Sean Sill: Get device specific PIDs returning properly in supportedParameters +// 24.01.2014 Peter Newman: Make the device specific PIDs compliant with the OLA RDM Tests. Add device model ID option +// 12.04.2015 change of using datatype boolean to bool8. +// 25.05.2017 Stefan Krupop: Add support for sensors +// 15.08.2018 Stefan Krupop: Add example for sensors +// 21.08.2018 improvements and typo by Peter Newman +// 31.10.2018 Remove unnecessary #include by Graham Hanson +// 04.06.2023 Tim Nijssen: Add support for manufacturer-specific parameters +// 05.06.2023 Tim Nijssen: integrate sensors example +// 05.06.2023 Tim Nijssen: Add example for manufacturer-specific parameters +// - - - - - + +#include + +// uncomment this line for enabling information on a LEONARD board. +// #define SERIAL_DEBUG + +// Constants for demo program + +const int RedPin = 9; // PWM output pin for Red Light. +const int GreenPin = 6; // PWM output pin for Green Light. +const int BluePin = 5; // PWM output pin for Blue Light. + +// color: #203050 * 2 +#define RedDefaultLevel 0x20 * 2 +#define GreenDefaultLevel 0x30 * 2 +#define BlueDefaultLevel 0x50 * 2 + +// define the RGB output color +void rgb(byte r, byte g, byte b) +{ + analogWrite(RedPin, r); + analogWrite(GreenPin, g); + analogWrite(BluePin, b); +} // rgb() + +// see DMXSerial2.h for the definition of the fields of this structure +const uint16_t my_pids[] = {E120_DEVICE_HOURS, E120_LAMP_HOURS}; +const RDMSENSOR my_sensors[] = { + {E120_SENS_TEMPERATURE, E120_UNITS_CENTIGRADE, E120_PREFIX_NONE, 0, 80, 10, 30, true, false, "Temperature"} +}; +struct RDMINIT rdmInit = { + "mathertel.de", // Manufacturer Label + 1, // Device Model ID + "Arduino RDM Device", // Device Model Label + 3, // footprint + (sizeof(my_pids)/sizeof(uint16_t)), my_pids, + (sizeof(my_sensors)/sizeof(RDMSENSOR)), my_sensors + //0, NULL +}; + +unsigned long previousMillis = 0; +double temperature = 0.0; +int16_t _lowestValue = 100; +int16_t _highestValue = 0; + +void setup () { + +#if defined(SERIAL_DEBUG) + // The Serial port can be used on Arduino Leonard Boards for debugging purpose + // because it is not mapped to the real serial port of the ATmega32U4 chip but to the USB port. + // Don't use that on Arduino Uno, 2009,... boards based on ATmega328 or ATmega168 chips. + Serial.begin(9600); + while (!Serial) ; + Serial.println("starting..."); +#endif + + // initialize the Serial interface to be used as an RDM Device Node. + // There are several constants that have to be passed to the library so it can respond to the + // corresponding commands for itself. + DMXSerial2.init(&rdmInit, processCommand, getSensorValue); + + uint16_t start = DMXSerial2.getStartAddress(); +#if defined(SERIAL_DEBUG) + Serial.print("Listening on DMX address #"); Serial.println(start); +#endif + + // set default values to dark red + // this color will be shown when no signal is present for the first 5 seconds. + DMXSerial2.write(start + 0, 30); + DMXSerial2.write(start + 1, 0); + DMXSerial2.write(start + 2, 0); + + // enable pwm outputs + pinMode(RedPin, OUTPUT); // sets the digital pin as output + pinMode(GreenPin, OUTPUT); + pinMode(BluePin, OUTPUT); + +#if defined(SERIAL_DEBUG) + // output the current DeviceID + + DEVICEID thisDevice; + DMXSerial2.getDeviceID(thisDevice); + + Serial.print("This Device is: "); + if (thisDevice[0] < 0x10) Serial.print('0'); Serial.print(thisDevice[0], HEX); + if (thisDevice[1] < 0x10) Serial.print('0'); Serial.print(thisDevice[1], HEX); + Serial.print(":"); + if (thisDevice[2] < 0x10) Serial.print('0'); Serial.print(thisDevice[2], HEX); + if (thisDevice[3] < 0x10) Serial.print('0'); Serial.print(thisDevice[3], HEX); + if (thisDevice[4] < 0x10) Serial.print('0'); Serial.print(thisDevice[4], HEX); + if (thisDevice[5] < 0x10) Serial.print('0'); Serial.print(thisDevice[5], HEX); + Serial.println(); +#endif + +} // setup() + + +void loop() { + // Calculate how long no data backet was received + unsigned long lastPacket = DMXSerial2.noDataSince(); + + if (DMXSerial2.isIdentifyMode()) { + // RDM command for identification was sent. + // Blink the device. + unsigned long now = millis(); + if (now % 1000 < 500) { + rgb(200, 200, 200); + } else { + rgb(0, 0, 0); + } // if + + } else if (lastPacket < 30000) { + // read recent DMX values and set pwm levels + analogWrite(RedPin, DMXSerial2.readRelative(0)); + analogWrite(GreenPin, DMXSerial2.readRelative(1)); + analogWrite(BluePin, DMXSerial2.readRelative(2)); + + } else { +#if defined(SERIAL_DEBUG) + Serial.println("no signal since 30 secs."); +#endif + // Show default color, when no data was received since 30 seconds or more. + analogWrite(RedPin, RedDefaultLevel); + analogWrite(GreenPin, GreenDefaultLevel); + analogWrite(BluePin, BlueDefaultLevel); + } // if + + if (millis() - previousMillis >= 1000) { + previousMillis = millis(); + temperature = getTemp(); + if (!isnan(temperature)) { + if (temperature < _lowestValue) { + _lowestValue = temperature; + } + if (temperature > _highestValue) { + _highestValue = temperature; + } + } + } + + // check for unhandled RDM commands + DMXSerial2.tick(); +} // loop() + + +// This function was registered to the DMXSerial2 library in the initRDM call. +// Here device specific RDM Commands are implemented. +bool8 processCommand(struct RDMDATA *rdm, uint16_t *nackReason) +{ + byte CmdClass = rdm->CmdClass; // command class + uint16_t Parameter = rdm->Parameter; // parameter ID + bool8 handled = false; + +// This is a sample of how to return some device specific data + if (Parameter == SWAPINT(E120_DEVICE_HOURS)) { // 0x0400 + if (CmdClass == E120_GET_COMMAND) { + if (rdm->DataLength > 0) { + // Unexpected data + *nackReason = E120_NR_FORMAT_ERROR; + } else if (rdm->SubDev != 0) { + // No sub-devices supported + *nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE; + } else { + rdm->DataLength = 4; + rdm->Data[0] = 0; + rdm->Data[1] = 0; + rdm->Data[2] = 2; + rdm->Data[3] = 0; + handled = true; + } + } else if (CmdClass == E120_SET_COMMAND) { + // This device doesn't support set + *nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS; + } + + } else if (Parameter == SWAPINT(E120_LAMP_HOURS)) { // 0x0401 + if (CmdClass == E120_GET_COMMAND) { + if (rdm->DataLength > 0) { + // Unexpected data + *nackReason = E120_NR_FORMAT_ERROR; + } else if (rdm->SubDev != 0) { + // No sub-devices supported + *nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE; + } else { + rdm->DataLength = 4; + rdm->Data[0] = 0; + rdm->Data[1] = 0; + rdm->Data[2] = 0; + rdm->Data[3] = 1; + handled = true; + } + } else if (CmdClass == E120_SET_COMMAND) { + // This device doesn't support set + *nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS; + } + } // if + + return handled; +} // processCommand + +// This function was registered to the DMXSerial2 library in the initRDM call. +// Here retrieval of sensor values is implemented. +bool8 getSensorValue(uint8_t sensorNr, int16_t *value, int16_t *lowestValue, int16_t *highestValue, int16_t *recordedValue) { + if (sensorNr == 0) { + if (isnan(temperature)) { + return false; + } + *value = temperature; + *lowestValue = _lowestValue; + *highestValue = _highestValue; + return true; + } + return false; +} // getSensorValue + +// Read the ATmega328 internal temperature sensor +// https://theorycircuit.com/arduino-internal-temperature-sensor/ +double getTemp(void) +{ + unsigned int wADC; + double t; + + // The internal temperature has to be used + // with the internal reference of 1.1V. + // Channel 8 can not be selected with + // the analogRead function yet. + + // Set the internal reference and mux. + ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3)); + ADCSRA |= _BV(ADEN); // enable the ADC + + delay(20); // wait for voltages to become stable. + + ADCSRA |= _BV(ADSC); // Start the ADC + + // Detect end-of-conversion + while (bit_is_set(ADCSRA,ADSC)); + + // Reading register "ADCW" takes care of how to read ADCL and ADCH. + wADC = ADCW; + + // The offset of 324.31 could be wrong. It is just an indication. + t = (wADC - 324.31 ) / 1.22; + + // The returned temperature is in degrees Celsius. + return (t); +} // getTemp + +// End. + diff --git a/src/DMXSerial2.cpp b/src/DMXSerial2.cpp index af42e5d..8392f5a 100644 --- a/src/DMXSerial2.cpp +++ b/src/DMXSerial2.cpp @@ -327,7 +327,15 @@ int random255(); // Initialize or reinitialize the DMX RDM mode. // The other values are stored for later use with the specific commands. -void DMXSerialClass2::init(struct RDMINIT *initData, RDMCallbackFunction func, RDMGetSensorValue sensorFunc, uint8_t modePin, uint8_t modeIn, uint8_t modeOut) +void DMXSerialClass2::init(struct RDMINIT *initData, + RDMCallbackFunction func, + RDMGetSensorValue sensorFunc, + RDMGetParameterValue getParamFunc, + RDMSetParameterValue setParamFunc, + uint8_t modePin, + uint8_t modeIn, + uint8_t modeOut +) { // This structure is defined for mapping the values in the EEPROM struct EEPROMVALUES eeprom; @@ -336,6 +344,8 @@ void DMXSerialClass2::init(struct RDMINIT *initData, RDMCallbackFunction func, R _initData = initData; _rdmFunc = func; _sensorFunc = sensorFunc; + _getParamFunc = getParamFunc; + _setParamFunc = setParamFunc; _dmxModePin = modePin; _dmxModeIn = modeIn; @@ -446,6 +456,13 @@ void DMXSerialClass2::attachSensorCallback(RDMGetSensorValue newFunction) _sensorFunc = newFunction; } // attachSensorCallback +// Register self implemented functions to get/set parameter values +void DMXSerialClass2::attachParameterCallback(RDMGetParameterValue newGetFunction, RDMSetParameterValue newSetFunction) +{ + _getParamFunc = newGetFunction; + _setParamFunc = newSetFunction; +} // attachParameterCallback + // some functions to hide the internal variables from being changed unsigned long DMXSerialClass2::noDataSince() { @@ -605,14 +622,15 @@ void DMXSerialClass2::term(void) void DMXSerialClass2::_processRDMMessage(byte CmdClass, uint16_t Parameter, bool8 handled, bool8 doRespond) { uint16_t nackReason = E120_NR_UNKNOWN_PID; + bool8 updateEEPRom = false; // call the device specific method if ((! handled) && (_rdmFunc)) { handled = _rdmFunc(&_rdm.packet, &nackReason); } // if - // if not already handled the command: handle it using this implementation - if (! handled ) { + // if this command has not already been handled, handle it using this implementation + if (! handled) { if (Parameter == SWAPINT(E120_IDENTIFY_DEVICE)) { // 0x1000 if (CmdClass == E120_SET_COMMAND) { // 0x30 @@ -717,12 +735,21 @@ void DMXSerialClass2::_processRDMMessage(byte CmdClass, uint16_t Parameter, bool // Oversized data nackReason = E120_NR_FORMAT_ERROR; } else { - memcpy(deviceLabel, _rdm.packet.Data, _rdm.packet.DataLength); - deviceLabel[min(_rdm.packet.DataLength, DMXSERIAL_MAX_RDM_STRING_LENGTH)] = '\0'; - _rdm.packet.DataLength = 0; - // persist in EEPROM - _saveEEPRom(); - handled = true; + bool8 validAsciiStr = true; + for (int i = 0; i<_rdm.packet.DataLength; i++) { + validAsciiStr = validAsciiStr && isAscii(_rdm.packet.Data[i]); + } + if (!validAsciiStr) { + // Not a valid ASCII string + nackReason = E120_NR_FORMAT_ERROR; + } else { + memcpy(deviceLabel, _rdm.packet.Data, _rdm.packet.DataLength); + deviceLabel[min(_rdm.packet.DataLength, DMXSERIAL_MAX_RDM_STRING_LENGTH)] = '\0'; + _rdm.packet.DataLength = 0; + // persist in EEPROM + updateEEPRom = true; + handled = true; + } } } else if (CmdClass == E120_GET_COMMAND) { if (_rdm.packet.DataLength > 0) { @@ -772,7 +799,7 @@ void DMXSerialClass2::_processRDMMessage(byte CmdClass, uint16_t Parameter, bool _startAddress = newStartAddress; _rdm.packet.DataLength = 0; // persist in EEPROM - _saveEEPRom(); + updateEEPRom = true; handled = true; } } @@ -808,7 +835,7 @@ void DMXSerialClass2::_processRDMMessage(byte CmdClass, uint16_t Parameter, bool // E120_DEVICE_INFO // E120_DMX_START_ADDRESS // E120_SOFTWARE_VERSION_LABEL - _rdm.packet.DataLength = 2 * (3 + _initData->additionalCommandsLength); + _rdm.packet.DataLength = 2 * (3 + _initData->additionalCommandsLength + _initData->parametersLength); WRITEINT(_rdm.packet.Data , E120_MANUFACTURER_LABEL); WRITEINT(_rdm.packet.Data+ 2, E120_DEVICE_MODEL_DESCRIPTION); WRITEINT(_rdm.packet.Data+ 4, E120_DEVICE_LABEL); @@ -820,7 +847,12 @@ void DMXSerialClass2::_processRDMMessage(byte CmdClass, uint16_t Parameter, bool WRITEINT(_rdm.packet.Data+ 8, E120_SENSOR_VALUE); } for (uint16_t n = 0; n < _initData->additionalCommandsLength; n++) { - WRITEINT(_rdm.packet.Data+offset+n+n, _initData->additionalCommands[n]); + WRITEINT(_rdm.packet.Data+offset, _initData->additionalCommands[n]); + offset += 2; + } + for (uint16_t n = 0; n < _initData->parametersLength; n++) { + WRITEINT(_rdm.packet.Data+offset, _initData->parameters[n].pid); + offset += 2; } handled = true; } @@ -829,7 +861,131 @@ void DMXSerialClass2::_processRDMMessage(byte CmdClass, uint16_t Parameter, bool nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS; } -// ADD: PARAMETER_DESCRIPTION + } else if (Parameter == SWAPINT(E120_PARAMETER_DESCRIPTION) && _initData->parametersLength > 0) { // 0x0051 + if (CmdClass == E120_GET_COMMAND) { + if (_rdm.packet.DataLength != 2) { + // Unexpected data + nackReason = E120_NR_FORMAT_ERROR; + } else if (_rdm.packet.SubDev != 0) { + // No sub-devices supported + nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE; + } else { + uint16_t requestedPid = READINT(_rdm.packet.Data); + uint16_t parameterIndex; + bool8 validPid = false; + for (uint16_t n = 0; n < _initData->parametersLength; n++) { + if (requestedPid == _initData->parameters[n].pid) { + parameterIndex = n; + validPid = true; + break; + } + } + if (!validPid) { + // Out of range manufacturer specific PID + nackReason = E120_NR_DATA_OUT_OF_RANGE; + } else { + _rdm.packet.DataLength = 20 + strnlen(_initData->parameters[parameterIndex].description, DMXSERIAL_MAX_RDM_STRING_LENGTH); + WRITEINT(_rdm.packet.Data, _initData->parameters[parameterIndex].pid); + _rdm.packet.Data[2] = _initData->parameters[parameterIndex].length; + _rdm.packet.Data[3] = _initData->parameters[parameterIndex].type; + if (_initData->parameters[parameterIndex].getSupported && !_initData->parameters[parameterIndex].setSupported) { + _rdm.packet.Data[4] = E120_CC_GET; + } else if (!_initData->parameters[parameterIndex].getSupported && _initData->parameters[parameterIndex].setSupported) { + _rdm.packet.Data[4] = E120_CC_SET; + } else if (_initData->parameters[parameterIndex].getSupported && _initData->parameters[parameterIndex].setSupported) { + _rdm.packet.Data[4] = E120_CC_GET_SET; + } + _rdm.packet.Data[5] = 0x00; + _rdm.packet.Data[6] = _initData->parameters[parameterIndex].unit; + _rdm.packet.Data[7] = _initData->parameters[parameterIndex].prefix; + WRITEINT32(_rdm.packet.Data + 8, _initData->parameters[parameterIndex].rangeMin); + WRITEINT32(_rdm.packet.Data + 12, _initData->parameters[parameterIndex].rangeMax); + WRITEINT32(_rdm.packet.Data + 16, _initData->parameters[parameterIndex].defaultValue); + memcpy(_rdm.packet.Data + 20, _initData->parameters[parameterIndex].description, _rdm.packet.DataLength - 20); + handled = true; + /////////////////////////////////////////// + } + } + } else if (CmdClass == E120_SET_COMMAND) { + // Unexpected set + nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS; + } + + } else if (Parameter == SWAPINT(E120_SENSOR_VALUE) && _initData->sensorsLength > 0) { // 0x0201 + if (CmdClass == E120_GET_COMMAND) { + if (_rdm.packet.DataLength != 1) { + // Unexpected data + nackReason = E120_NR_FORMAT_ERROR; + } else if (_rdm.packet.SubDev != 0) { + // No sub-devices supported + nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE; + } else { + uint8_t sensorNr = _rdm.packet.Data[0]; + if (sensorNr >= _initData->sensorsLength || sensorNr == 0xFF) { + // Out of range sensor + nackReason = E120_NR_DATA_OUT_OF_RANGE; + } else { + int16_t sensorValue = 0; + int16_t lowestValue = 0; + int16_t highestValue = 0; + int16_t recordedValue = 0; + bool8 res = false; + if (_sensorFunc) { + res = _sensorFunc(sensorNr, &sensorValue, &lowestValue, &highestValue, &recordedValue); + } + if (res) { + _rdm.packet.DataLength = 9; + _rdm.packet.Data[0] = sensorNr; + WRITEINT(_rdm.packet.Data + 1, sensorValue); + WRITEINT(_rdm.packet.Data + 3, (_initData->sensors[sensorNr].lowHighSupported ? lowestValue : 0)); + WRITEINT(_rdm.packet.Data + 5, (_initData->sensors[sensorNr].lowHighSupported ? highestValue : 0)); + WRITEINT(_rdm.packet.Data + 7, (_initData->sensors[sensorNr].recordedSupported ? recordedValue : 0)); + handled = true; + } else { + nackReason = E120_NR_HARDWARE_FAULT; + } + } + } + } else if (CmdClass == E120_SET_COMMAND) { + // Set on a sensor is used to reset stats. + if (_rdm.packet.DataLength != 1) { + // Unexpected data + nackReason = E120_NR_FORMAT_ERROR; + } else if (_rdm.packet.SubDev != 0) { + // No sub-devices supported + nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE; + } else { + uint8_t sensorNr = _rdm.packet.Data[0]; + if (sensorNr >= _initData->sensorsLength && sensorNr != 0xFF) { + // Out of range sensor + nackReason = E120_NR_DATA_OUT_OF_RANGE; + } else { + for (int i = 0; i<_initData->sensorsLength; i++) { + if (i == sensorNr || sensorNr == 0xFF) { + int16_t sensorValue = 0; + int16_t lowestValue = 0; + int16_t highestValue = 0; + int16_t recordedValue = 0; + bool8 res = false; + if (_sensorFunc) { + res = _sensorFunc(i, &sensorValue, &lowestValue, &highestValue, &recordedValue); + } + if (res) { + _rdm.packet.DataLength = 9; + _rdm.packet.Data[0] = i; + WRITEINT(_rdm.packet.Data + 1, sensorValue); + WRITEINT(_rdm.packet.Data + 3, (_initData->sensors[i].lowHighSupported ? sensorValue : 0)); + WRITEINT(_rdm.packet.Data + 5, (_initData->sensors[i].lowHighSupported ? sensorValue : 0)); + WRITEINT(_rdm.packet.Data + 7, (_initData->sensors[i].recordedSupported ? sensorValue : 0)); + handled = true; + } else { + nackReason = E120_NR_HARDWARE_FAULT; + } + } // if + } // for + } // else + } // else + } // else if } else if (Parameter == SWAPINT(E120_SENSOR_DEFINITION) && _initData->sensorsLength > 0) { // 0x0200 if (CmdClass == E120_GET_COMMAND) { @@ -863,6 +1019,7 @@ void DMXSerialClass2::_processRDMMessage(byte CmdClass, uint16_t Parameter, bool // Unexpected set nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS; } + } else if (Parameter == SWAPINT(E120_SENSOR_VALUE) && _initData->sensorsLength > 0) { // 0x0201 if (CmdClass == E120_GET_COMMAND) { if (_rdm.packet.DataLength != 1) { @@ -907,11 +1064,68 @@ void DMXSerialClass2::_processRDMMessage(byte CmdClass, uint16_t Parameter, bool } else { handled = false; + for (uint16_t n = 0; n < _initData->parametersLength; n++) { + if (Parameter == SWAPINT(_initData->parameters[n].pid)) { + if (CmdClass == E120_GET_COMMAND) { + if (!_initData->parameters[n].getSupported) { + // get not supported + nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS; + } else if (_rdm.packet.DataLength > 0) { + // Unexpected data + nackReason = E120_NR_FORMAT_ERROR; + } else if (_rdm.packet.SubDev != 0) { + // No sub-devices supported + nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE; + } else { + uint8_t value[_initData->parameters[n].length]; + bool8 res = false; + if (_getParamFunc) { + res = _getParamFunc(_initData->parameters[n].pid, n, value); + } + if (res) { + _rdm.packet.DataLength = _initData->parameters[n].length; + memcpy(_rdm.packet.Data,value,_initData->parameters[n].length); + handled = true; + } else { + nackReason = E120_NR_HARDWARE_FAULT; + } + } + + } else if (CmdClass == E120_SET_COMMAND) { + if (!_initData->parameters[n].setSupported) { + // set not supported + nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS; + } else if (_rdm.packet.DataLength != _initData->parameters[n].length) { + // Unexpected data + nackReason = E120_NR_FORMAT_ERROR; + } else if (_rdm.packet.SubDev != 0) { + // No sub-devices supported + nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE; + } else { + uint8_t value[_initData->parameters[n].length]; + memcpy(value,_rdm.packet.Data,_initData->parameters[n].length); + bool8 res = false; + if (_setParamFunc) { + res = _setParamFunc(_initData->parameters[n].pid, n, value); + } + if (res) { + _rdm.packet.DataLength = 0; + handled = true; + } else { + nackReason = E120_NR_HARDWARE_FAULT; + } + } + } // if + break; + } + } // for } // if } // if if (doRespond) respondMessage(handled, nackReason); + if (updateEEPRom) + _saveEEPRom(); } // _processRDMMessage @@ -1096,7 +1310,7 @@ void respondMessage(bool8 isHandled, uint16_t nackReason) if (isHandled) { _rdm.packet.ResponseType = E120_RESPONSE_TYPE_ACK; // 0x00 } else { - _rdm.packet.ResponseType = E120_RESPONSE_TYPE_NACK_REASON; // 0x00 + _rdm.packet.ResponseType = E120_RESPONSE_TYPE_NACK_REASON; // 0x02 _rdm.packet.DataLength = 2; _rdm.packet.Data[0] = (nackReason >> 8) & 0xFF; _rdm.packet.Data[1] = nackReason & 0xFF; diff --git a/src/DMXSerial2.h b/src/DMXSerial2.h index 16713bd..cf14c06 100644 --- a/src/DMXSerial2.h +++ b/src/DMXSerial2.h @@ -117,6 +117,9 @@ struct RDMDATA { /// write a 16 bit number to a data buffer location #define WRITEINT(p, d) (p)[0] = (d&0xFF00)>>8; (p)[1] = (d&0x00FF); +/// write a 32 bit number to a data buffer location +#define WRITEINT32(p, d) (p)[0] = (d&0xFF000000)>>24; (p)[1] = (d&0x00FF0000)>>16; (p)[2] = (d&0x0000FF00)>>8; (p)[3] = (d&0x000000FF); + // ----- Callback function types ----- @@ -133,12 +136,22 @@ extern "C" { * @brief Callback function for RDM sensors. */ typedef bool8 (*RDMGetSensorValue)(uint8_t sensorNr, int16_t *value, int16_t *lowestValue, int16_t *highestValue, int16_t *recordedValue); + + /** + * @brief Callback function for getting RDM parameters. + */ + typedef bool8 (*RDMGetParameterValue)(uint16_t pid, uint16_t parameterIndex, int8_t *value); + + /** + * @brief Callback function for setting RDM parameters. + */ + typedef bool8 (*RDMSetParameterValue)(uint16_t pid, uint16_t parameterIndex, int8_t *value); } // ----- Library Class ----- // These types are used to pass all the data into the initRDM function. -// The library needs this data to reposonse at the corresponding commands for itself. +// The library needs this data to respond to the corresponding commands for itself. struct RDMPERSONALITY { uint16_t footprint; @@ -158,6 +171,20 @@ struct RDMSENSOR { char *description; }; // struct RDMSENSOR +struct RDMPARAMETER { + uint16_t pid; + uint8_t length; + uint8_t type; + uint8_t unit; + uint8_t prefix; + int32_t rangeMin; + int32_t rangeMax; + int32_t defaultValue; + bool8 getSupported; + bool8 setSupported; + char *description; +}; // struct RDMPARAMETER + struct RDMINIT { const char *manufacturerLabel; // const uint16_t deviceModelId; // @@ -169,6 +196,8 @@ struct RDMINIT { const uint16_t *additionalCommands; const uint8_t sensorsLength; const RDMSENSOR *sensors; + const uint8_t parametersLength; + const RDMPARAMETER *parameters; }; // struct RDMINIT @@ -180,12 +209,12 @@ class DMXSerialClass2 * @brief Initialize for RDM mode. * @param [in] initData Startup parameters. * @param [in] func Callback function for answering on device specific features. - * @param [in] modePin The pin used to switch the communication direction. This parameter is optiona and defaults to 2. - * @param [in] modeIn The level for inbound communication. This parameter is optiona and defaults to 0 = LOW. - * @param [in] modeOut The level for outbound communication. This parameter is optiona and defaults to 1 = HIGH. + * @param [in] modePin The pin used to switch the communication direction. This parameter is optional and defaults to 2. + * @param [in] modeIn The level for inbound communication. This parameter is optional and defaults to 0 = LOW. + * @param [in] modeOut The level for outbound communication. This parameter is optional and defaults to 1 = HIGH. */ void init (struct RDMINIT *initData, RDMCallbackFunction func, uint8_t modePin = 2, uint8_t modeIn = 0, uint8_t modeOut = 1) { - init(initData, func, NULL, modePin, modeIn, modeOut); + init(initData, func, NULL, NULL, NULL, modePin, modeIn, modeOut); } /** @@ -193,11 +222,40 @@ class DMXSerialClass2 * @param [in] initData Startup parameters. * @param [in] func Callback function for answering on device specific features. * @param [in] sensorFunc Callback function for retrieving a sensor value. - * @param [in] modePin The pin used to switch the communication direction. This parameter is optiona and defaults to 2. - * @param [in] modeIn The level for inbound communication. This parameter is optiona and defaults to 0 = LOW. - * @param [in] modeOut The level for outbound communication. This parameter is optiona and defaults to 1 = HIGH. + * @param [in] modePin The pin used to switch the communication direction. This parameter is optional and defaults to 2. + * @param [in] modeIn The level for inbound communication. This parameter is optional and defaults to 0 = LOW. + * @param [in] modeOut The level for outbound communication. This parameter is optional and defaults to 1 = HIGH. + */ + void init (struct RDMINIT *initData, RDMCallbackFunction func, RDMGetSensorValue sensorFunc, uint8_t modePin = 2, uint8_t modeIn = 0, uint8_t modeOut = 1) { + init(initData, func, sensorFunc, NULL, NULL, modePin, modeIn, modeOut); + } + + /** + * @brief Initialize for RDM mode with parameters. + * @param [in] initData Startup parameters. + * @param [in] func Callback function for answering on device specific features. + * @param [in] getParamFunc Callback function for retrieving a parameter value. + * @param [in] setParamFunc Callback function for setting a parameter value. + * @param [in] modePin The pin used to switch the communication direction. This parameter is optional and defaults to 2. + * @param [in] modeIn The level for inbound communication. This parameter is optional and defaults to 0 = LOW. + * @param [in] modeOut The level for outbound communication. This parameter is optional and defaults to 1 = HIGH. + */ + void init (struct RDMINIT *initData, RDMCallbackFunction func, RDMGetParameterValue getParamFunc, RDMSetParameterValue setParamFunc, uint8_t modePin = 2, uint8_t modeIn = 0, uint8_t modeOut = 1) { + init(initData, func, NULL, getParamFunc, setParamFunc, modePin, modeIn, modeOut); + } + + /** + * @brief Initialize for RDM mode with sensor and parameter. + * @param [in] initData Startup parameters. + * @param [in] func Callback function for answering on device specific features. + * @param [in] sensorFunc Callback function for retrieving a sensor value. + * @param [in] getParamFunc Callback function for retrieving a parameter value. + * @param [in] setParamFunc Callback function for setting a parameter value. + * @param [in] modePin The pin used to switch the communication direction. This parameter is optional and defaults to 2. + * @param [in] modeIn The level for inbound communication. This parameter is optional and defaults to 0 = LOW. + * @param [in] modeOut The level for outbound communication. This parameter is optional and defaults to 1 = HIGH. */ - void init (struct RDMINIT *initData, RDMCallbackFunction func, RDMGetSensorValue sensorFunc, uint8_t modePin = 2, uint8_t modeIn = 0, uint8_t modeOut = 1); + void init (struct RDMINIT *initData, RDMCallbackFunction func, RDMGetSensorValue sensorFunc, RDMGetParameterValue getParamFunc, RDMSetParameterValue setParamFunc, uint8_t modePin = 2, uint8_t modeIn = 0, uint8_t modeOut = 1); /** * @brief Read the current value of a channel. @@ -240,12 +298,15 @@ class DMXSerialClass2 /// Return the current DMX footprint, that is the number of DMX addresses used by the device. uint16_t getFootprint(); - /// Register a device-specific implemented function for RDM callbacks + /// Register a manufacturer-specific implemented function for RDM callbacks void attachRDMCallback (RDMCallbackFunction newFunction); - /// Register a device-specific implemented function for getting sensor values + /// Register a manufacturer-specific implemented function for getting sensor values void attachSensorCallback (RDMGetSensorValue newFunction); + /// Register a manufacturer-specific implemented function for getting/setting parameter values + void attachParameterCallback(RDMGetParameterValue newGetFunction, RDMSetParameterValue newSetFunction); + /// check for unprocessed RDM Command. void tick(void); @@ -274,6 +335,12 @@ class DMXSerialClass2 /// callback function to get sensor value RDMGetSensorValue _sensorFunc; + /// callback function to get parameter value + RDMGetParameterValue _getParamFunc; + + /// callback function to set parameter value + RDMSetParameterValue _setParamFunc; + /// remember the given manufacturer label and device model strings during init struct RDMINIT *_initData; diff --git a/src/rdm.h b/src/rdm.h index 3f6be75..67ed0a0 100644 --- a/src/rdm.h +++ b/src/rdm.h @@ -308,7 +308,7 @@ /* Table A-6: Product Detail Defines */ /********************************************************/ -#define E120_PRODUCT_DETAIL_NOT DECLARED 0x0000 +#define E120_PRODUCT_DETAIL_NOT_DECLARED 0x0000 /* Generally applied to fixtures */ #define E120_PRODUCT_DETAIL_ARC 0x0001