[RS232] add JSON parsing and hardware serial support to RS232 gateway (#1409)

* add JSON parse option to RS232 gateway

* add hardware UART support to RS232 gateway

* changed default RS232toMQTTmode to raw (instead of JSON)

* removed reference to Arduino Mega in RS232 gateway config

* update RS232 gateway doc
This commit is contained in:
Merijn authored and GitHub committed 2023-01-25 16:43:53 -06:00
1 parent c1afc02ffe
commit 6e3e8c3330
3 files changed
+266 -31

No files matched your search

+33
View File
@@ -1,5 +1,7 @@
# RS232 gateway
The RS232 gateway can be used to send and receive data from the serial connection to and from MQTT. Both softwareSerial as hardwareSerial are supported. HardwareSerial can be used for higher baud rates, but is limited to specific pins on most platforms.
## Sending an RS232 message
Simply publish the message you wish to transmit, minus the prefix and postfix. For example, to send the "Turn On" signal for a Mitsubishi XD221U projector, the code is simply '!' so you would use the command
@@ -8,7 +10,12 @@ Simply publish the message you wish to transmit, minus the prefix and postfix. F
It will automatically add the prefix and postfix you set in [config_RS232.h](https://github.com/1technophile/OpenMQTTGateway/blob/master/main/config_RS232.h).
## Receiving an RS232 message
Two modes are available for receiving RS232 messages.
### Single MQTT message mode (default)
To receive a message, subscribe to all with `mosquitto_sub -t +/# -v`
and perform an action that should get a response from the device. For example, If I were to send the "Turn On" signal from earlier, I would receive back
@@ -21,3 +28,29 @@ Because this projector echoes back a received command to acknowledge. Some devic
```json
home/OpenMQTTGateway/RStoMQTT {"value":"!:N"}
```
### JSON mode
This mode can be used if the received message on the serial link is JSON. The JSON keys are used as separate MQTT sub-topics. For nested JSON this will be repeated for sub-keys up to the specified nesting level.
For example:
input received at serial link:
```json
{temperature: {sens1: 22, sens2: 23}, humidity: {sens1: 80, sens2: 60}}
```
output in case of max nesting level 1:
```json
home/OpenMQTTGateway/RS232toMQTT/temperature "{sens1: 22, sens2: 23}"
home/OpenMQTTGateway/RS232toMQTT/humidity "{sens1: 80, sens2: 60}"
```
output in case of max nesting level 2 (or higher):
```json
home/OpenMQTTGateway/RS232toMQTT/temperature/sens1 22
home/OpenMQTTGateway/RS232toMQTT/temperature/sens2 23
home/OpenMQTTGateway/RS232toMQTT/humidity/sens1 80
home/OpenMQTTGateway/RS232toMQTT/humidity/sens2 60
```
+136 -14
View File
@@ -29,33 +29,89 @@
#ifdef ZgatewayRS232
# include <SoftwareSerial.h>
# ifndef RS232_UART // software serial mode
# include <SoftwareSerial.h>
SoftwareSerial RS232SoftSerial(RS232_RX_GPIO, RS232_TX_GPIO); // RX, TX
# endif
SoftwareSerial RS232Serial(RS232_RX_GPIO, RS232_TX_GPIO); // RX, TX
// use pointer to stream class for serial communication to make code
// compatible with both softwareSerial as hardwareSerial.
Stream* RS232Stream = NULL;
void setupRS232() {
//Initalize serial port
# ifdef RS232_UART // Hardware serial
# if RS232_UART == 0 // init UART0
Serial.end(); // stop if already initialized
# ifdef ESP32
Serial.begin(RS232Baud, SERIAL_8N1, RS232_RX_GPIO, RS232_TX_GPIO);
# else
Serial.begin(RS232Baud, SERIAL_8N1);
# endif
# if defined(ESP8266) && defined(RS232_UART0_SWAP)
Serial.swap(); // swap UART0 ports from (GPIO1,GPIO3) to (GPIO15,GPIO13)
# endif
RS232Stream = &Serial;
Log.notice(F("RS232 HW UART0" CR));
# elif RS232_UART == 1 // init UART1
Serial1.end(); // stop if already initialized
# ifdef ESP32
Serial1.begin(RS232Baud, SERIAL_8N1, RS232_RX_GPIO, RS232_TX_GPIO);
# else
Serial1.begin(RS232Baud, SERIAL_8N1);
# endif
RS232Stream = &Serial1;
Log.notice(F("RS232 HW UART1" CR));
# elif RS232_UART == 2 // init UART2
Serial2.end(); // stop if already initialized
# ifdef ESP32
Serial2.begin(RS232Baud, SERIAL_8N1, RS232_RX_GPIO, RS232_TX_GPIO);
# else
Serial2.begin(RS232Baud, SERIAL_8N1);
# endif
RS232Stream = &Serial2;
Log.notice(F("RS232 HW UART2" CR));
# elif RS232_UART == 3 // init UART3
Serial3.end(); // stop if already initialized
Serial3.begin(RS232Baud, SERIAL_8N1);
RS232Stream = &Serial3;
Log.notice(F("RS232 HW UART3" CR));
# endif
# else // Software serial
// define pin modes for RX, TX:
pinMode(RS232_RX_GPIO, INPUT);
pinMode(RS232_TX_GPIO, OUTPUT);
// set the data rate for the SoftwareSerial port
RS232Serial.begin(RS232Baud);
RS232SoftSerial.begin(RS232Baud);
RS232Stream = &RS232SoftSerial; // get stream of serial
Log.notice(F("RS232_RX_GPIO: %d " CR), RS232_RX_GPIO);
Log.notice(F("RS232_TX_GPIO: %d " CR), RS232_TX_GPIO);
Log.notice(F("RS232Baud: %d " CR), RS232Baud);
Log.trace(F("ZgatewayRS232 setup done " CR));
Log.notice(F("RS232_RX_GPIO: %d" CR), RS232_RX_GPIO);
Log.notice(F("RS232_TX_GPIO: %d" CR), RS232_TX_GPIO);
# endif
// Flush all bytes in the "link" serial port buffer
while (RS232Stream->available() > 0)
RS232Stream->read();
Log.notice(F("RS232Baud: %d" CR), RS232Baud);
Log.trace(F("ZgatewayRS232 setup done" CR));
}
# if RS232toMQTTmode == 0 // Convert received data to single MQTT topic
void RS232toMQTT() {
// Send all RS232 output (up to RS232InPost char revieved) as MQTT message
//This function is Blocking, but there should only ever be a few bytes, usually an ACK or a NACK.
if (RS232Serial.available()) {
if (RS232Stream->available()) {
Log.trace(F("RS232toMQTT" CR));
static char RS232data[MAX_INPUT];
static unsigned int input_pos = 0;
static char inChar;
do {
if (RS232Serial.available()) {
inChar = RS232Serial.read();
if (RS232Stream->available()) {
inChar = RS232Stream->read();
RS232data[input_pos] = inChar;
input_pos++;
Log.trace(F("Received %c" CR), inChar);
@@ -69,6 +125,72 @@ void RS232toMQTT() {
}
}
# elif RS232toMQTTmode == 1 // Convert recievee JSON data to multiple MQTT topics based (nested) keys
void RS232toMQTT() {
// Assumes valid JSON data at RS232 interface. Use (nested) keys to split JSON data in separate
// sub-MQTT-topics up to the defined nesting level.
if (RS232Stream->available()) {
Log.trace(F("RS232toMQTT" CR));
// Allocate the JSON document
StaticJsonDocument<RS232JSONDocSize> doc;
// Read the JSON document from the "link" serial port
DeserializationError err = deserializeJson(doc, *RS232Stream);
if (err == DeserializationError::Ok) {
// JSON received, send as MQTT topics
char topic[mqtt_topic_max_size + 1] = subjectRS232toMQTT;
sendMQTTfromNestedJson(doc.as<JsonVariant>(), topic, 0, RS232maxJSONlevel);
} else {
// Print error to serial log
Log.error(F("Error in RS232JSONtoMQTT, deserializeJson() returned %s"), err.c_str());
// Flush all bytes in the "link" serial port buffer
while (RS232Stream->available() > 0)
RS232Stream->read();
}
}
}
void sendMQTTfromNestedJson(JsonVariant obj, char* topic, int level, int maxLevel) {
// recursively step through JSON data and send MQTT messages
if (level < maxLevel && obj.is<JsonObject>()) {
int topicLength = strlen(topic);
// loop over fields
for (JsonPair pair : obj.as<JsonObject>()) {
// check if new key still fits in topic cstring
const char* key = pair.key().c_str();
Log.trace(F("level=%d, key='%s'" CR), level, pair.key().c_str());
if (topicLength + 2 + strlen(key) <= mqtt_topic_max_size) {
// add new level to existing topic cstring
topic[topicLength] = '/'; // add slash
topic[topicLength + 1] = '\0'; // terminate
strncat(topic + topicLength, key, mqtt_topic_max_size - topicLength - 2);
// step recursively into next level
sendMQTTfromNestedJson(pair.value(), topic, level + 1, maxLevel);
// restore topic
topic[topicLength] = '\0';
} else {
Log.error(F("Nested key '%s' at level %d does not fit within max topic length of %d, skipping"),
key, level, mqtt_topic_max_size);
}
}
} else {
// output value at current json level
char output[MAX_INPUT + 1];
serializeJson(obj, output, MAX_INPUT);
Log.notice(F("level=%d, topic=%s, value: %s\n"), level, topic, output);
// send MQTT message
pub(topic, &output[0]);
}
}
# endif
void MQTTtoRS232(char* topicOri, JsonObject& RS232data) {
Log.trace(F("json" CR));
if (cmpToMainTopic(topicOri, subjectMQTTtoRS232)) {
@@ -79,9 +201,9 @@ void MQTTtoRS232(char* topicOri, JsonObject& RS232data) {
Log.trace(F("Value set: %s" CR), data);
Log.trace(F("Prefix set: %s" CR), prefix);
Log.trace(F("Postfix set: %s" CR), postfix);
RS232Serial.print(prefix);
RS232Serial.print(data);
RS232Serial.print(postfix);
RS232Stream->print(prefix);
RS232Stream->print(data);
RS232Stream->print(postfix);
}
}
#endif
+97 -17
View File
@@ -30,27 +30,106 @@ extern void setupRS232();
extern void RS232toMQTT();
extern void MQTTtoRS232(char* topicOri, JsonObject& RS232data);
/*-------------------IR topics & parameters----------------------*/
//RS232 MQTT Subjects
#define subjectGTWRS232toMQTT "/RS232toMQTT"
#define subjectRS232toMQTT "/RS232toMQTT"
#define subjectMQTTtoRS232 "/commands/MQTTtoRS232"
#define subjectForwardMQTTtoRS232 "home/gateway2/commands/MQTTtoRS232"
/*-------------------RS232 topics & parameters----------------------*/
// Settings RS232 to MQTT topic
#define subjectRS232toMQTT "/RS232toMQTT"
// setting to specify mode used for sending MQTT messages:
// 0: RAW: all recieved input at RS232 interface is collected in single MQTT message
// defined by RS232toMQTTsubject
//
// 1: JSON: Assumes input at RS232 interface to be valid JSON. JSON keys are used to
// split the JSON data in separate MQTT sub-topics. This will be repeated for
// sub-keys up to the specified nesting level (RS232maxJSONlevel).
//
// EXAMPLE: "{temperature: {sens1: 22, sens2: 23}, humidity: {sens1: 80, sens2: 60}}"
// - with RS232maxJSONlevel=1:
// ./RS232toMQTT/temperature ==> "{sens1: 22, sens2: 23}"
// ./RS232toMQTT/humidity ==> "{sens1: 80, sens2: 60}"
//
// - with RS232maxJSONlevel=2 (or higher):
// ./RS232toMQTT/temperature/sens1 ==> 22
// ./RS232toMQTT/temperature/sens2 ==> 23
// ./RS232toMQTT/humidity/sens1 ==> 80
// ./RS232toMQTT/humidity/sens2 ==> 60
//
#ifndef RS232toMQTTmode
# define RS232toMQTTmode 0
#endif
// settings for RS232TopicMode 0 (RAW)
#define RS232InPost '\n' // Hacky way to get last character of postfix for incoming
#define MAX_INPUT 200 // how much serial data we expect
// settings for RS232TopicMode 1 (JSON)
#define RS232maxJSONlevel 2 // Max nested level in which JSON keys are converted to seperate sub-topics
#define RS232JSONDocSize 1024 // bytes to reserve for the JSON doc
// settings for MQTT to RS232
#define subjectMQTTtoRS232 "/commands/MQTTtoRS232"
#define RS232Pre "00" // The prefix for the RS232 message
#define RS232Post "\r" // The postfix for the RS232 message
//Setup for RS232
#define MAX_INPUT 50 // how much serial data we expect
#define RS232Baud 9600 // The serial connection Baud
#define RS232Pre "00" // The prefix for the RS232 message
#define RS232Post "\r" // The postfix for the RS232 message
#define RS232InPost '\r' // Hacky way to get last character of postfix for incoming
#ifndef RS232Baud
# define RS232Baud 9600 // The serial connection Baud
#endif
/*-------------------PIN DEFINITIONS----------------------*/
#ifndef RS232_UART
// set hardware serial UART to be used for device communication or
// use software emulaton if not defined
//
// VALUES:
// - not defined: use software emulation (pins set by RS232_RX_GPIO & RS232_TX_GPIO)
//
// - 0: use HW UART0 (serial), warning: default used for logging & usb
// ESP8266: (TX0 GPIO1, RX0 GPIO3)
// (TX0 GPIO15, RX0 GPIO13) with UART0 swap enabled
// ESP32: (TX0 by RS232_TX_GPIO, RX0 by RS232_RX_GPIO)
// ATmega2560: (TX0 pin1, RX0 pin0)
// Arduino Uno: (TX0 pin1, RX0 pin0)
//
// - 1: use HW UART1 (serial1)
// ESP8266: (TX1 GPIO2, RX none) only transmit available
// ESP32: (TX1 by RS232_TX_GPIO, RX1 by RS232_RX_GPIO)
// ATmega2560: (TX1 pin18, RX1 pin19)
// Arduino Uno: N/A
//
// - 2: use HW UART2 (serial2)
// ESP8266: N/A
// ESP32: (TX2 by RS232_TX_GPIO, RX2 by RS232_RX_GPIO)
// ATmega2560: (TX2 pin16, RX1 pin17)
// Arduino Uno: N/A
//
// - 3: use HW UART3 (serial3)
// ESP8266: N/A
// ESP32: N/A
// ATmega2560: (TX2 pin14, RX1 pin15)
// Arduino Uno: N/A
//
// defaults
# ifdef ESP32
# define RS232_UART 1 // use HW UART ESP32
# else
# undef RS232_UART // default use software serial
//# define RS232_UART 1 // define to use HW UART
# endif
#endif
#ifndef RS232_UART0_SWAP
// option for ESP8266 only to swap UART0 ports from (GPIO1,GPIO3) to (GPIO15,GPIO13)
# define RS232_UART0_SWAP
#endif
#ifndef RS232_RX_GPIO
# ifdef ESP8266
// define receive pin (for software serial or ESP32 with configurable HW UART)
# if defined(ESP8266) && !defined(RS232_UART)
# define RS232_RX_GPIO 4 //D2
# elif ESP32
# elif defined(ESP32)
# define RS232_RX_GPIO 26
# elif __AVR_ATmega2560__
# elif defined(__AVR_ATmega2560__) && !defined(RS232_UART)
# define RS232_RX_GPIO 2 // 2 = D2 on arduino mega
# else
# define RS232_RX_GPIO 0 // 0 = D2 on arduino UNO
@@ -58,15 +137,16 @@ extern void MQTTtoRS232(char* topicOri, JsonObject& RS232data);
#endif
#ifndef RS232_TX_GPIO
# ifdef ESP8266
// define transmit pin (for software serial and/or ESP32)
# if defined(ESP8266) && !defined(RS232_UART)
# define RS232_TX_GPIO 2 //D4
# elif ESP32
# define RS232_TX_GPIO 14
# elif __AVR_ATmega2560__
# elif defined(__AVR_ATmega2560__) && !defined(RS232_UART)
# define RS232_TX_GPIO 9
# else
# define RS232_TX_GPIO 9
# endif
#endif
#endif
#endif