diff --git a/docs/source/Plugin/P054.rst b/docs/source/Plugin/P054.rst index ad5de6617e..e1cf1f9061 100644 --- a/docs/source/Plugin/P054.rst +++ b/docs/source/Plugin/P054.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p05x.repl +.. include:: ../Plugin/_plugin_substitutions_p05x.repl .. _P054_page: |P054_typename| @@ -21,15 +21,59 @@ Maintainer: |P054_maintainer| Used libraries: |P054_usedlibraries| -Supported hardware ------------------- - |P054_usedby| -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +Description +----------- + +For controlling DMX512 equipment, this plugin can be used for sending commands to a DMX bus. As an ESP only has a 5V compatible serial interface, usually a RS485 or DMX converter is needed to connect to the DMX bus. + +This plugin can only send out commands to the DMX bus, no receiving or recording features are available. + +Configuration +------------- + +.. image:: P054_DeviceConfiguration.png + +* **Name** In the Name field a unique name should be entered. + +* **Enabled** When unchecked the plugin is not enabled. + +Actuator +^^^^^^^^ + +See: :ref:`SerialHelper_page` + +.. warning:: The selected serial port **must** use on-ESP GPIO pins, as the DMX protocol has some specific coding that is not supported by regular serial chips, and is implemented by directly interacting with the ESP TX pin. + +Example selection list (taken from an ESP32-C6 configuration): + +.. image:: P054_SerialPortOptions.png -.. .. include:: P054_commands.repl +* *HW Serial0*: First serial port, usually also in use for the serial log, so should best be avoided. + +* *HW Serial1*: Secondary serial port. Selected as the default, and required to be used on ESP8266. + +* *HW Serial2*: (Not available on an ESP32-C6, but is available on some other ESP32 models) Can be used. + +* *SW Serial*: Can be tried, and used if working as intended, but should better be avoided because of the high serial speed (250000 baud). + +* *USB HWCDC*: Can not be used. + +* *USB CDC*: Can not be used. + +* *I2C Serial*: Can not be used. + +Device Settings +^^^^^^^^^^^^^^^ + +* **Channels**: The number of DMX channels to be used. Determines the size of a memory buffer. Keep as small as possible, especially on ESP8266, where available memory can be limited. + + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P054_commands.repl .. Events .. ~~~~~~ @@ -42,6 +86,8 @@ Change log .. versionchanged:: 2.0 ... + |added| 2024-09-29: Add selection for Serial port and GPIO on ESP32. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P054_DeviceConfiguration.png b/docs/source/Plugin/P054_DeviceConfiguration.png new file mode 100644 index 0000000000..05154a12d5 Binary files /dev/null and b/docs/source/Plugin/P054_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P054_SerialPortOptions.png b/docs/source/Plugin/P054_SerialPortOptions.png new file mode 100644 index 0000000000..c981b596fd Binary files /dev/null and b/docs/source/Plugin/P054_SerialPortOptions.png differ diff --git a/docs/source/Plugin/P054_commands.repl b/docs/source/Plugin/P054_commands.repl new file mode 100644 index 0000000000..baf5120ba1 --- /dev/null +++ b/docs/source/Plugin/P054_commands.repl @@ -0,0 +1,39 @@ +.. csv-table:: + :header: "Command Syntax", "Extra information" + :widths: 20, 30 + + " + | ``dmx,log`` + "," + | List the current content of the send-buffer to the log. + " + " + | ``dmx,test`` + "," + | Fills the send-buffer with random data and sends that to the connected devices. + " + " + | ``dmx,on`` + "," + | Fills the send-buffer all \'255\' values and sends that to the connected devices to turn everthing on. + " + " + | ``dmx,off`` + "," + | Fills the send-buffer all \'0\' values and sends that to the connected devices to turn everthing off. + " + " + | ``dmx,=[,[,=]]`` + + | ``channel``: Channel number from 1 .. **Channels** set in the configuration. + + | ``value``: Numeric value between 0 and 255 (inclusive) to set for the channel. + "," + | Fills the send-buffer given value(s) for the channel and sends that to the connected devices. + + | If no channel is used with a value, it is set for the next channel. The channel is initially set to 1. + + | Multiple values, optionally with a channel prefixed, can be provided and will be processed until no more data is found. + + | Also, other subcommands (not including ``dmx``) can be used, f.e. setting up some channel data and adding a ``,log`` at the end to list the current buffer content to the log output. + " diff --git a/docs/source/Plugin/_plugin_substitutions_p05x.repl b/docs/source/Plugin/_plugin_substitutions_p05x.repl index 5baecc6bbc..22dee3255d 100644 --- a/docs/source/Plugin/_plugin_substitutions_p05x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p05x.repl @@ -53,7 +53,7 @@ .. |P054_name| replace:: :cyan:`DMX512 TX` .. |P054_type| replace:: :cyan:`Communication` .. |P054_typename| replace:: :cyan:`Communication - DMX512 TX` -.. |P054_porttype| replace:: `.` +.. |P054_porttype| replace:: `Serial` .. |P054_status| replace:: :yellow:`COLLECTION` .. |P054_github| replace:: P054_DMX512.ino .. _P054_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P054_DMX512.ino diff --git a/src/_P054_DMX512.ino b/src/_P054_DMX512.ino index a7f34ee18c..09d4483c96 100644 --- a/src/_P054_DMX512.ino +++ b/src/_P054_DMX512.ino @@ -5,6 +5,13 @@ // ######################################## Plugin 054: DMX512 TX ######################################## // ####################################################################################################### +/** Changelog: + * 2024-09-21 tonhuisman: Use Direct-GPIO for interacting with GPIO pins for speed & timing accuracy + * Use ESPEasySerial for more flexible serial configuration + * 2024-09-20 tonhuisman: Reformat source, allow GPIO selection on ESP32 by _not_ resetting to GPIO 2 when not ESP8266 + * 2024-09-20 Start changelog + */ + // ESPEasy Plugin to control DMX-512 Devices (DMX 512/1990; DIN 56930-2) like Dimmer-Packs, LED-Bars, Moving-Heads, Event-Lighting // written by Jochen Krapf (jk@nerd2nerd.org) @@ -28,9 +35,9 @@ // Examples: // DMX,123" Set channel 1 to value 123 -// DMX,123,22,33,44" Set channel 1 to value 123, channel 2 to 22, channel 3 to33, channel 4 to 44 +// DMX,123,22,33,44" Set channel 1 to value 123, channel 2 to 22, channel 3 to 33, channel 4 to 44 // DMX,5=123" Set channel 5 to value 123 -// DMX,5=123,22,33,44" Set channel 5 to value 123, channel 6 to 22, channel 7 to33, channel 8 to 44 +// DMX,5=123,22,33,44" Set channel 5 to value 123, channel 6 to 22, channel 7 to 33, channel 8 to 44 // DMX,5=123,8=44" Set channel 5 to value 123, channel 8 to 44 // DMX,OFF" Pitch Black @@ -52,13 +59,16 @@ // #include <*.h> //no lib needed +# include +# include # define PLUGIN_054 # define PLUGIN_ID_054 54 # define PLUGIN_NAME_054 "Communication - DMX512 TX" -uint8_t *Plugin_054_DMXBuffer = 0; -int16_t Plugin_054_DMXSize = 32; +uint8_t *Plugin_054_DMXBuffer = 0; +int16_t Plugin_054_DMXSize = 32; +ESPeasySerial *Plugin_054_Serial = nullptr; static inline void PLUGIN_054_Limit(int16_t& value, int16_t min, int16_t max) { @@ -79,12 +89,11 @@ boolean Plugin_054(uint8_t function, struct EventStruct *event, String& string) { case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_054; - Device[deviceCount].Type = DEVICE_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; - Device[deviceCount].ValueCount = 0; - Device[deviceCount].setPin1Direction(gpio_direction::gpio_output); + Device[++deviceCount].Number = PLUGIN_ID_054; + Device[deviceCount].Type = DEVICE_TYPE_SERIAL; + Device[deviceCount].Ports = 0; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; + Device[deviceCount].ValueCount = 0; break; } @@ -94,21 +103,73 @@ boolean Plugin_054(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_SET_DEFAULTS: + { + # ifdef ESP8266 + CONFIG_PORT = static_cast(ESPEasySerialPort::serial1); // Serial1 port + CONFIG_PIN1 = -1; // RX pin + CONFIG_PIN2 = 2; // TX pin + # endif // ifdef ESP8266 + # ifdef ESP32 + CONFIG_PORT = static_cast(ESPEasySerialPort::serial1); // Serial1 port + int rxPin = 0; + int txPin = 0; + const ESPEasySerialPort port = static_cast(CONFIG_PORT); + + ESPeasySerialType::getSerialTypePins(port, rxPin, txPin); + CONFIG_PIN1 = rxPin; + CONFIG_PIN2 = txPin; + # endif // ifdef ESP32 + + PCONFIG(0) = Plugin_054_DMXSize; // Holds default value + break; + } + + case PLUGIN_WEBFORM_PRE_SERIAL_PARAMS: + { + if ((-1 == CONFIG_PIN2) && (2 == CONFIG_PIN1)) { + CONFIG_PIN2 = CONFIG_PIN1; + CONFIG_PIN1 = -1; + } + break; + } + + case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS: + { + addFormNote(F("An on-chip ESP Serial port" + #if USES_USBCDC + " (not USB CDC)" + #endif // if USES_USBCDC + #if USES_HWCDC + " (not USB HWCDC)" + #endif // if USES_HWCDC + " must be selected!")); + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + serialHelper_getGpioNames(event, true); + break; + } + case PLUGIN_WEBFORM_LOAD: { - CONFIG_PIN1 = 2; - PCONFIG(0) = Plugin_054_DMXSize; - addFormNote(F("Only GPIO-2 (D4) can be used as TX1!")); - addFormNumericBox(F("Channels"), F("channels"), Plugin_054_DMXSize, 1, 512); + addFormNumericBox(F("Channels"), F("channels"), PCONFIG(0), 1, 512); success = true; break; } case PLUGIN_WEBFORM_SAVE: { - CONFIG_PIN1 = 2; + # ifdef ESP8266 + + if (static_cast(ESPEasySerialPort::serial1) == CONFIG_PORT) { + CONFIG_PIN2 = 2; + } + # endif // ifdef ESP8266 - if (Settings.Pin_status_led == 2) { // Status LED assigned to TX1? + if (Settings.Pin_status_led == CONFIG_PIN2) { // Status LED assigned to TX1? Settings.Pin_status_led = -1; } Plugin_054_DMXSize = getFormItemInt(F("channels")); @@ -120,7 +181,6 @@ boolean Plugin_054(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - CONFIG_PIN1 = 2; // TX1 fix to GPIO2 (D4) == onboard LED Plugin_054_DMXSize = PCONFIG(0); if (Plugin_054_DMXBuffer) { @@ -132,7 +192,32 @@ boolean Plugin_054(uint8_t function, struct EventStruct *event, String& string) memset(Plugin_054_DMXBuffer, 0, Plugin_054_DMXSize); } - success = Plugin_054_DMXBuffer != nullptr; + if ((-1 == CONFIG_PIN2) && (2 == CONFIG_PIN1)) { // Convert previous GPIO settings + CONFIG_PIN2 = CONFIG_PIN1; + CONFIG_PIN1 = -1; + } + int rxPin = CONFIG_PIN1; + int txPin = CONFIG_PIN2; + const ESPEasySerialPort port = static_cast(CONFIG_PORT); + + if ((rxPin < 0) && (txPin < 0)) { + ESPeasySerialType::getSerialTypePins(port, rxPin, txPin); + CONFIG_PIN1 = rxPin; + CONFIG_PIN2 = txPin; + } + delete Plugin_054_Serial; + Plugin_054_Serial = new (std::nothrow) ESPeasySerial(port, rxPin, txPin); + + if (nullptr != Plugin_054_Serial) { + # ifdef ESP8266 + Plugin_054_Serial->begin(250000, (SerialConfig)SERIAL_8N2); + # endif // ifdef ESP8266 + # ifdef ESP32 + Plugin_054_Serial->begin(250000, SERIAL_8N2); + # endif // ifdef ESP32 + } + + success = Plugin_054_DMXBuffer != nullptr && Plugin_054_Serial != nullptr && validGpio(CONFIG_PIN2); break; } @@ -140,18 +225,18 @@ boolean Plugin_054(uint8_t function, struct EventStruct *event, String& string) { if (Plugin_054_DMXBuffer) { delete[] Plugin_054_DMXBuffer; + Plugin_054_DMXBuffer = nullptr; } + delete Plugin_054_Serial; + Plugin_054_Serial = nullptr; break; } case PLUGIN_WRITE: { - String lowerString = string; - lowerString.toLowerCase(); - String command = parseString(lowerString, 1); + const String command = parseString(string, 1); - if (equals(command, F("dmx"))) - { + if (equals(command, F("dmx"))) { String param; String paramKey; String paramVal; @@ -159,90 +244,66 @@ boolean Plugin_054(uint8_t function, struct EventStruct *event, String& string) int16_t channel = 1; int16_t value = 0; - // FIXME TD-er: Same code in _P057 - lowerString.replace(F(" "), " "); - lowerString.replace(F(" ="), "="); - lowerString.replace(F("= "), "="); - - param = parseString(lowerString, paramIdx++); - - if (param.length()) - { - while (param.length()) - { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG_MORE, param); - # endif // ifndef BUILD_NO_DEBUG - - if (equals(param, F("log"))) - { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("DMX : "); - - for (int16_t i = 0; i < Plugin_054_DMXSize; i++) - { - log += Plugin_054_DMXBuffer[i]; - log += F(", "); - } - addLogMove(LOG_LEVEL_INFO, log); - } - success = true; - } + param = parseString(string, paramIdx++); - else if (equals(param, F("test"))) - { - for (int16_t i = 0; i < Plugin_054_DMXSize; i++) { - // Plugin_054_DMXBuffer[i] = i+1; - Plugin_054_DMXBuffer[i] = rand() & 255; - } - success = true; - } + while (param.length()) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG_MORE, param); + # endif // ifndef BUILD_NO_DEBUG - else if (equals(param, F("on"))) - { - memset(Plugin_054_DMXBuffer, 255, Plugin_054_DMXSize); - success = true; + if (equals(param, F("log"))) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("DMX : "); + + for (int16_t i = 0; i < Plugin_054_DMXSize; ++i) { + log += concat(Plugin_054_DMXBuffer[i], F(", ")); + } + addLogMove(LOG_LEVEL_INFO, log); } + success = true; + } - else if (equals(param, F("off"))) - { - memset(Plugin_054_DMXBuffer, 0, Plugin_054_DMXSize); - success = true; + else if (equals(param, F("test"))) { + for (int16_t i = 0; i < Plugin_054_DMXSize; ++i) { + Plugin_054_DMXBuffer[i] = rand() & 255; } + success = true; + } - else - { - int16_t index = param.indexOf('='); + else if (equals(param, F("on"))) { + memset(Plugin_054_DMXBuffer, 255, Plugin_054_DMXSize); + success = true; + } - if (index > 0) // syntax: "=" - { - paramKey = param.substring(0, index); - paramVal = param.substring(index + 1); - channel = paramKey.toInt(); - } - else // syntax: "" - { - paramVal = param; - } + else if (equals(param, F("off"))) { + memset(Plugin_054_DMXBuffer, 0, Plugin_054_DMXSize); + success = true; + } - value = paramVal.toInt(); - PLUGIN_054_Limit(value, 0, 255); + else { + int16_t index = param.indexOf('='); - if ((channel > 0) && (channel <= Plugin_054_DMXSize)) { - Plugin_054_DMXBuffer[channel - 1] = value; - } - channel++; + if (index > 0) { // syntax: "=" + paramKey = param.substring(0, index); + paramVal = param.substring(index + 1); + channel = paramKey.toInt(); + } + else { // syntax: "" + paramVal = param; } - param = parseString(lowerString, paramIdx++); + value = paramVal.toInt(); + PLUGIN_054_Limit(value, 0, 255); + + if ((channel > 0) && (channel <= Plugin_054_DMXSize)) { + Plugin_054_DMXBuffer[channel - 1] = value; + } + channel++; + success = true; } - } - else - { - // ??? no params - } - success = true; + param = parseString(string, paramIdx++); + } } break; @@ -250,25 +311,30 @@ boolean Plugin_054(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_TEN_PER_SECOND: { - if (Plugin_054_DMXBuffer) - { - int16_t sendPin = 2; // TX1 fix to GPIO2 (D4) == onboard LED + if (Plugin_054_DMXBuffer && Plugin_054_Serial) { + int16_t sendPin = CONFIG_PIN2; // ESP8266: TX1 fixed to GPIO2 (D4) == onboard LED // empty serial from prev. transmit - Serial1.flush(); + Plugin_054_Serial->flush(); // send break - Serial1.end(); - pinMode(sendPin, OUTPUT); - digitalWrite(sendPin, LOW); + Plugin_054_Serial->end(); + + // DIRECT_PINMODE_OUTPUT(sendPin); // This shouldn't be needed + DIRECT_pinWrite(sendPin, LOW); delayMicroseconds(120); // 88µs ... inf - digitalWrite(sendPin, HIGH); + DIRECT_pinWrite(sendPin, HIGH); delayMicroseconds(12); // 8µs ... 1s // send DMX data - Serial1.begin(250000, SERIAL_8N2); - Serial1.write(0); // start uint8_t - Serial1.write(Plugin_054_DMXBuffer, Plugin_054_DMXSize); + # ifdef ESP8266 + Plugin_054_Serial->begin(250000, (SerialConfig)SERIAL_8N2); + # endif // ifdef ESP8266 + # ifdef ESP32 + Plugin_054_Serial->begin(250000, SERIAL_8N2); + # endif // ifdef ESP32 + Plugin_054_Serial->write((uint8_t)0); // start uint8_t + Plugin_054_Serial->write(Plugin_054_DMXBuffer, Plugin_054_DMXSize); } break; }