[RF] Proposal fix for issue #2139 + Extra documentation (#2157)

* Refactor announceDeviceTrigger function to improve parameter handling and documentation for optional device information
Fix null pointer references in RFtoMQTTdiscovery and update logging levels for RF signal handling
Refactor RF to MQTT discovery functions for improved clarity and parameter handling

* Update documentation for RF auto discovery and MQTT integration, correcting terminology and enhancing clarity

* Refactor enableRFReceive function to accept parameters for frequency and GPIOs, enhancing flexibility
This commit is contained in:
Alessandro Staniscia
2025-03-07 23:47:33 +01:00
committed by GitHub
parent e6443143bb
commit e683fbbd18
13 changed files with 490 additions and 191 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -5,7 +5,7 @@ Home Assistant provide the [MQTT integration](https://www.home-assistant.io/inte
Once this integration on home assistant is configured with the same MQTT broker, it is possible to create devices manually or through the autodiscovery function.
## Auto discovery
## Automatic integration with Auto discovery
From Home Assistant site
@@ -42,7 +42,7 @@ On first and subsequent startups, auto discovery will start. If you want to prev
Some devices may require a button push or motion/contact event to trigger a message and generate the auto discovery.
:::
## RTL_433 auto discovery specificity
### RTL_433 auto discovery specificity
Even if the RTL_433 gateway will create automatically the devices and entities, you may lose the link to them when you change the batteries. This is proper to the RF devices. In this case new device and entities will be created. You may bypass this by creating entities through manual configuration that filter following the device model and other parameters and don't take into account the id.
Example:
@@ -74,16 +74,119 @@ mqtt:
Alternatively the rssi signal could be used also.
## MQTT Device Trigger and RF
### RF (RCSwitch based gateway) Auto discovery specificity
With OpenMQTTGateway [configured to receive RF signals](../setitup/rf.html) messages are transmitted accordingly.
With OpenMQTTGateway [configured to receive RF signals](../setitup/rf.html) the messages are transmitted as indicated by [RCSwitch based gateway](../use/rf.html#rcswitch-based-gateway), so it is possible to receive a pulse every time the sensor discover a signal.
As indicated in the [RCSwitch based gateway](../use/rf.html#rcswitch-based-gateway), it is possible to receive a pulse each time the sensor detects a signal. With auto-discovery enabled, you can configure two types of entities in Home Assistant to handle RF signals:
With autodiscovery enabled, HomeAssistant will discover a [MQTT Device Trigger](https://www.home-assistant.io/integrations/device_trigger.mqtt/) identified by the value field given in the mqtt argument.
## Manual integration examples
From @123, @finity, @denniz03, @jrockstad, @anarchking, @dkluivingh
1. An [MQTT Device Trigger](https://www.home-assistant.io/integrations/device_trigger.mqtt/) which allows actions to be triggered when a specific RF signal is received.
2. An [MQTT Sensor](https://www.home-assistant.io/integrations/sensor.mqtt/)
which stores the received RF data as a sensor value.
Below are some examples of both configurations.
#### Example A - RF as MQTT Device Trigger
If the following parameters are enabled in OpenMQTTGateway:
- ZgatewayRF="RF"
- valueAsATopic=true
- HADiscovery=true
- RF_on_HAS_as_DeviceTrigger=true
- RF_on_HAS_as_MQTTSensor=false
Then, each time an RF signal is received by OpenMQTTGateway, two messages are sent to the MQTT broker:
1. A device trigger announcement (used by Home Assistant to recognize the trigger).
2. The RF signal data on the corresponding MQTT topic.
```
...
N: [ OMG->MQTT ] topic: homeassistant/device_automation/246F287AF0C4/15524904/config msg: {"automation_type":"trigger","platform ":"device_automation","type":"Received","subtype":"RF-15524904","device":{"configuration_url":"http://192.168.2.150/","connections":[["mac","246F287AF0C4"]],"identifiers":["246F287AF0C4"],"mf":"OMG_community","mdl":"[\"WebUI\",\"RF\"]","name":"OMG_DEVELOPMENT","sw":"1.8.0-o"},"value_template":"{{trigger.value.raw}}","topic":"home/OMG_DEVELOPMENT/433toMQTT/15524904"}
T: Dequeue JSON
N: [ OMG->MQTT ] topic: home/OMG_DEVELOPMENT/433toMQTT/15524904 msg: {"value":15524904,"protocol":1,"length":24,"delay":368,"tre_state":"-","binary":"111011001110010000101000","raw":"11427,1042,407,1039,417,1027,430,302,1148,1019,438,1015,440,295,1152,301,1153,1014,442,1012,441,1008,445,291,1160,293,1158,1011,444,292,1156,296,1157,296,1157,298,1154,1011,442,295,1153,1013,443,293,1157,295,1154,300"}
...
```
These messages allow Home Assistant to recognize and integrate the RF signal as a device trigger.
![MQTTExplorer Show Topic](../img/HASS-RF-DeviceTrigger.png)
*Example of a detected RF device trigger in Home Assistant: Green shows the real message, Yellow shows the device trigger configuration*
Once this process is completed, Home Assistant detects a new trigger associated with the OpenMQTTGateway device and linked to the received RF signal value.
This means that Home Assistant can now react to the received RF signal as an automation trigger.
Example of a detected RF trigger in Home Assistant:
![MQTTExplorer Show Topic](../img/HASS-RF-Trigger-example.png)
*Example of a detected RF trigger in Home Assistant*
#### Example B - RF As MQTT Sensor
If the following parameters are enabled in OpenMQTTGateway:
- ZgatewayRF="RF"
- HADiscovery=true
- RF_on_HAS_as_DeviceTrigger=false
- RF_on_HAS_as_MQTTSensor=true
During any discovery round, a SYS message is sent, setting the MQTT Sensor as a sensor of the OpenMQTTGateway device.
```
...
N: [ OMG->MQTT ] topic: homeassistant/sensor/246F287AF0C4-gatewayRF/config msg: {"stat_t":"home/OMG_DEVELOPMENT/433toMQTT/#","avty_t":"home/OMG_DEVELOPMENT/LWT","name":"gatewayRF","uniq_id":"246F287AF0C4-gatewayRF","val_tpl":"{{ value_json.value | is_defined }}","pl_avail":"online","pl_not_avail":"offline","device":{"ids":["246F287AF0C4"],"name":"OMG_DEVELOPMENT","mdl":"[\"WebUI\",\"RF\"]","mf":"OMG_community","cu":"http://192.168.2.150/","sw":"1.8.0-o"}}
T: Dequeue JSON
```
Then, each time an RF signal is received by OpenMQTTGateway, the content of messages are sent to the MQTT broker on classic topic:
```
T: Dequeue JSON
N: [ OMG->MQTT ] topic: home/OMG_DEVELOPMENT/433toMQTT/15524904 msg: {"value":15524904,"protocol":1,"length":24,"delay":368,"tre_state":"-","binary":"111011001110010000101000","raw":"11404,1045,414,1034,424,1024,430,305,1146,1022,431,1019,436,299,1150,304,1145,1021,432,1017,438,1013,441,295,1156,299,1149,1018,436,300,1149,304,1146,307,1143,310,1141,1025,430,306,1143,1021,432,305,1145,310,1140,309"}
```
These messages enable Home Assistant to recognize and integrate the RF signal as a sensor.
![MQTTExplorer Show Topic](../img/HASS-RF-MQTTSensor.png)
*Example of a detected RF sensor in Home Assistant: Yellow the configuration of sensor, Green the real message*
Once this process is completed, Home Assistant detects a device with the new sensor and linked to the received RF signal value.
This means that Home Assistant can now react to the received RF signal as an sensor event.
Example of a detected RF trigger in Home Assistant:
![MQTTExplorer Show Topic](../img/HASS-RF-Sensor-example.png)
*Example of a detected RF sensor in Home Assistant: Yellow the real message*
## Manual integration
If you prefer not to use MQTT auto-discovery, you can manually configure Home Assistant to manage MQTT topics. This approach gives you full control over the structure and behavior of your MQTT entities. Below are some examples of how to define your devices, sensors, and switches manually using YAML configuration.
From @123, @finity, @denniz03, @jrockstad, @anarchking, @dkluivingh, @Odyno
### Pir Sensor
This is an example of how to configure a PIR (Passive Infrared) sensor in Home Assistant using MQTT.
![Pir Sensor](../img/digoo-pir-DG-HOSA.png)
The configuration below sets up a binary sensor that detects motion and sends the state to Home Assistant.
```yaml
mqtt:
binary_sensor:
- unique_id: pir.15484294
name: "Pir.Mansarda"
device_class: motion
state_topic: "home/+/433toMQTT/15484294"
value_template: "{{ value_json.value }}"
payload_on: "15484294"
off_delay: 3
```
### Door sensor
```yaml
mqtt:
binary_sensor:

View File

@@ -73,3 +73,5 @@ This Developer Certificate Of Origin (DCO) was adopted on June 7, 2021.
The text of this license is available under the [Creative Commons Attribution-ShareAlike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/). It is based on the Linux [Developer Certificate Of Origin](http://elinux.org/Developer_Certificate_Of_Origin).
To accept the DCO it is required to put a x between [ ] on `[ ] I accept the DCO` in the PR template when submitting it. The [ ] is an opt-in box, so you have to manually accept it.

View File

@@ -105,82 +105,114 @@ Delta applied to RSSI floor noise level to determine start and end of signal, de
## RCSwitch based gateway
### Receiving data from RF signal
The [RCSwitch](https://github.com/1technophile/rc-switch.git) library is an Arduino library that facilitates communication with 433MHz RF modules. It is designed to be lightweight and easy to use, making it suitable for basic RF communication tasks. The library supports both sending and receiving of RF signals, allowing for integration with various RF devices such as remote controls, sensors, and switches.
Subscribe to all the messages with mosquitto or open your MQTT client software:
To enable this module during compilation, add the flag `'-DZgatewayRF="RF"'` or uncomment the line `#define ZgatewayRF "RF"` in the `User_config.h` file.
` sudo mosquitto_sub -t +/# -v`
Generate your RF signals by pressing a remote button or other and you should see :
Other useful definitions can be:
`home/OpenMQTTGateway/433toMQTT {"value":1315156,"protocol":1,"length":24,"delay":317}`
* `#define RF_DISABLE_TRANSMIT` To disable the transmit functions and free up the pin for other uses, add (or uncomment) this line to the `config_rf.h` file.
* `#define repeatRFwMQTT true` To enable repeating the RF signal received by the gateway, set this parameter to true in the `config_rf.h` file.
* `#define RF_on_HAS_as_DeviceTrigger` To send a Home Assistant Device Trigger Message for each RF signal received, add (or uncomment) this line in the `config_rf.h` file. Note that this action also depends on the `ZmqttDiscovery` settings.
* `#define RF_on_HAS_as_MQTTSensor` To send a Home Assistant MQTT sensor message for each RF signal received, add (or uncomment) this line in the `config_rf.h` file. Note that this action also depends on the `ZmqttDiscovery` settings.
### Disabling Transmit function to safe a PIN
To disable transmit functions to allow the use of another pin, add the following to the config_rf.h file :
`#define RF_DISABLE_TRANSMIT`
### Handling RF signal over MQTT
In this chapter, the process of receiving and sending RF signals using MQTT will be explored. By subscribing to MQTT topics and publishing messages, RF devices can be seamlessly integrated with an MQTT broker.
### Send data by MQTT to convert it on RF signal
#### Receiving Data from RF Signal
`mosquitto_pub -t "home/OpenMQTTGateway/commands/MQTTto433" -m '{"value":1315156}'`
To receive data from an RF signal, follow these steps:
This command will send by RF the code 1315156 and use the default parameters (protocol 1, delay 350)
1. Subscribe to all messages using mosquitto or open your MQTT client software:
```sh
sudo mosquitto_sub -t +/# -v
```
Arduino IDE serial data received when publishing data by MQTT
2. Generate your RF signals by pressing a remote button or other RF device. You should see output similar to:
```json
home/OpenMQTTGateway/433toMQTT {"value":1315156,"protocol":1,"length":24,"delay":317}
```
#### Send data by MQTT to convert it to an RF signal
To send an RF signal using MQTT, you can publish a message to the topic `home/OpenMQTTGateway/commands/MQTTto433` with the desired value. For example:
```sh
mosquitto_pub -t "home/OpenMQTTGateway/commands/MQTTto433" -m '{"value":1315156}'
```
This command will send the RF code `1315156` using the default parameters (protocol 1, delay 350).
When publishing data via MQTT, the Arduino IDE serial monitor will display the received value and the corresponding MQTT topic:
![Serial data](../img/OpenMQTTGateway_serial1.jpg)
We see that the Arduino receive the value 1315156 on the MQTT subject "MQTTto433" and send the data by RF
In this example, the Arduino receives the value `1315156` on the MQTT topic `MQTTto433` and transmits the data via RF.
Arduino IDE serial data received when receiving data by 433Mhz
Similarly, when receiving data via 433MHz, the Arduino IDE serial monitor will show the received data:
![Serial data 2](../img/OpenMQTTGateway_serial2.jpg)
### Send data by MQTT with advanced RF parameters
RF sending support three advanced parameters: bits length, RF protocol and RF pulselength
RF sending supports three advanced parameters: bits length, RF protocol, and RF pulse length.
-if you want to use a bits number different than 24 put inside your topic "length":24 for example
-if you want to use a different RCswitch protocol put inside your payload the protocol number 2, "protocol":2.
-if you want to use a pulselength 315 put inside your topic "delay":315
- To use a different bits number, include `"length":24` in your payload.
- To use a different RCSwitch protocol, include `"protocol":2` in your payload.
- To use a different pulse length, include `"delay":315` in your payload.
Example:
`mosquitto_pub -t "home/OpenMQTTGateway/commands/MQTTto433" -m '{"value":1315156,"protocol":2,"length":24,"delay":315}'`
will make RCSwitch use the protocol 2 with a pulselength of 315ms and a bits number of 24 with a power of 5
### Repeat the RF signal OpenMQTTGateway receive
So as to repeat the RF signal received by the gateway once set the following parameter to true in config_RF.h
```sh
mosquitto_pub -t "home/OpenMQTTGateway/commands/MQTTto433" -m '{"value":1315156,"protocol":2,"length":24,"delay":315}'
```
`#define repeatRFwMQTT true`
This command will make RCSwitch use protocol 2 with a pulse length of 315ms and a bits number of 24.
### Repeat the RF signal several times
You can add a "repeat" key/value to the MQTTto433 JSON message to override the default number of repeats.
#### Repeat the RF signal several time
You can add a "repeat" key/value to the MQTTto433 JSON message to override the default number of repeats. Ensure that `repeatRFwMQTT` is enabled at compile time.
Example:
`home/OpenMQTTGateway/commands/MQTTto433 {"value":1315156,"protocol":1,"length":24,"delay":317, "repeat":10}`
```sh
mosquitto_pub -t "home/OpenMQTTGateway/commands/MQTTto433" -m '{"value":1315156,"protocol":1,"length":24,"delay":317,"repeat":10}'
```
### Set Transmit and Receive Frequency and Transmit Power of CC1101 Transceiver
#### Set Transmit and Receive Frequency and Transmit Power of CC1101 Transceiver
Default transmit frequency of the CC1101 module is 433.92 Mhz, and this can be can changed by including the frequency in the transmit message. Parameter is `mhz` and valid values are 300-348 Mhz, 387-464Mhz and 779-928Mhz. Actual frequency support will depend on your CC1101 board.
The default transmit frequency of the CC1101 module is 433.92 MHz. This can be changed by including the desired frequency in the transmit message. The parameter is `frequency`, and valid values are 300-348 MHz, 387-464 MHz, and 779-928 MHz. Actual frequency support will depend on your CC1101 board.
`home/OpenMQTTGateway/commands/MQTTto433 {"value":1150,"protocol":6,"length":12,"delay":450,"repeat":8,"frequency":303.732}`
Example:
```sh
mosquitto_pub -t "home/OpenMQTTGateway/commands/MQTTto433" -m '{"value":1150,"protocol":6,"length":12,"delay":450,"repeat":8,"frequency":303.732}'
```
Default receive frequency of the CC1101 module is 433.92 Mhz, and this can be can changed by sending a message with the frequency. Parameter is `frequency` and valid values are 300-348 Mhz, 387-464Mhz and 779-928Mhz. Actual frequency support will depend on your CC1101 board
The default receive frequency of the CC1101 module is also 433.92 MHz. This can be changed by sending a message with the desired frequency. The parameter is `frequency`, and valid values are 300-348 MHz, 387-464 MHz, and 779-928 MHz. Actual frequency support will depend on your CC1101 board.
`home/OpenMQTTGateway/commands/MQTTtoRF/config {"frequency":433.92}`
Example:
```sh
mosquitto_pub -t "home/OpenMQTTGateway/commands/MQTTtoRF/config" -m '{"frequency":433.92}'
```
Messages received will include the frequency, and when transmitting on a different frequency the module return to the receive frequency afterwards. ie transmit messages on 303.732 Mhz then receive messages on 433.92 Mhz
Messages received will include the frequency, and when transmitting on a different frequency, the module will return to the receive frequency afterward. For example, transmit messages on 303.732 MHz and then receive messages on 433.92 MHz.
`{"value":4534142,"protocol":6,"length":26,"delay":356,"frequency":315.026}`
Example received message:
```json
{"value":4534142,"protocol":6,"length":26,"delay":356,"frequency":315.026}
```
You can adjust the tx-power in db for a transmission. Parameter is `cc1101_pa` and valid values in decibel are (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max!
That can be done to reduce range and therefore disturbances with other nearby devices.
If you want to send a transmission with a power of 5 db than use the message
You can also adjust the transmit power (tx-power) in decibels (dB) for a transmission. The parameter is `cc1101_pa`, and valid values in decibels are -30, -20, -15, -10, -6, 0, 5, 7, 10, 11, and 12. The default is the maximum value. Adjusting the transmit power can help reduce range and minimize disturbances with other nearby devices.
Example:
```sh
mosquitto_pub -t "home/OpenMQTTGateway/commands/MQTTto433" -m '{"value":1315156,"protocol":2,"length":24,"delay":315,"cc1101_pa":5}'
```
`{"value":1315156,"protocol":2,"length":24,"delay":315, "cc1101_pa":5}`
## Pilight gateway

View File

@@ -144,7 +144,7 @@ void enableActiveReceiver() {
# ifdef ZgatewayRF
case ACTIVE_RF:
initCC1101();
enableRFReceive();
enableRFReceive(RFConfig.frequency, RF_RECEIVER_GPIO, RF_EMITTER_GPIO);
currentReceiver = ACTIVE_RF;
break;
# endif

View File

@@ -28,6 +28,8 @@
#include "User_config.h"
#ifdef ZgatewayRF
# include <ArduinoJson.h>
# include <ArduinoLog.h>
# ifdef ZradioCC1101
# include <ELECHOUSE_CC1101_SRC_DRV.h>
@@ -37,7 +39,22 @@
RCSwitch mySwitch = RCSwitch();
//SOME CONVERSION function from https://github.com/sui77/rc-switch/tree/master/examples/ReceiveDemo_Advanced
/**
* @brief Converts a binary string to a tristate string.
*
* This function takes a binary string as input and converts it to a tristate string.
* The tristate string is composed of '0', '1', and 'F' characters, where:
* - '0' represents "00" in the binary string
* - '1' represents "11" in the binary string
* - 'F' represents "01" in the binary string
*
* If the input binary string contains any other combination, the function returns "-".
*
* @note CONVERSION function from https://github.com/sui77/rc-switch/tree/master/examples/ReceiveDemo_Advanced
*
* @param bin The input binary string.
* @return A pointer to the tristate string.
*/
static const char* bin2tristate(const char* bin) {
static char returnValue[50];
int pos = 0;
@@ -59,6 +76,22 @@ static const char* bin2tristate(const char* bin) {
return returnValue;
}
/**
* @brief Converts a decimal number to a binary string with zero fill.
*
* This function takes an unsigned long decimal number and converts it to a binary string
* representation, ensuring that the resulting string is zero-padded to the specified bit length.
*
* @param Dec The decimal number to be converted.
* @param bitLength The length of the resulting binary string, including leading zeros.
* @return A pointer to a static character array containing the binary string representation.
*
* @note The returned string is stored in a static buffer, so it will be overwritten by subsequent
* calls to this function. The buffer size is fixed at 64 characters.
*
* @note CONVERSION function from https://github.com/sui77/rc-switch/tree/master/examples/ReceiveDemo_Advanced
*
*/
static char* dec2binWzerofill(unsigned long Dec, unsigned int bitLength) {
static char bin[64];
unsigned int i = 0;
@@ -80,41 +113,67 @@ static char* dec2binWzerofill(unsigned long Dec, unsigned int bitLength) {
return bin;
}
# if defined(ZmqttDiscovery) && !defined(RF_DISABLE_TRANSMIT) && defined(RFmqttDiscovery)
void RFtoMQTTdiscovery(uint64_t MQTTvalue) {
//on the fly switch creation from received RF values
# if defined(ZmqttDiscovery) && defined(RF_on_HAS_as_DeviceTrigger)
/**
* @brief Announces RF signal data to Home Assistant via MQTT for device trigger configuration.
*
* This function creates and publishes a Home Assistant configuration message
* for an RF signal received by the gateway. It constructs the necessary
* MQTT topic and payload to announce the RF signal as a device trigger
* in Home Assistant, allowing it to be used as an automation trigger.
*
* @param MQTTvalue The RF signal value to be published to MQTT.
*/
void announceGatewayTriggerTypeToHASS(uint64_t MQTTvalue) {
char val[11];
sprintf(val, "%lu", MQTTvalue);
Log.trace(F("RF Entity Discovered, create HA Discovery CFG" CR));
char* switchRF[2] = {val, "RF"};
Log.trace(F("CreateDiscoverySwitch: %s" CR), switchRF[1]);
String iSignal = String(val);
# if valueAsATopic
String discovery_topic = String(subjectRFtoMQTT) + "/" + String(switchRF[0]);
String discovery_topic = String(subjectRFtoMQTT) + "/" + iSignal;
# else
String discovery_topic = String(subjectRFtoMQTT);
# endif
String theUniqueId = getUniqueId(String(switchRF[0]), "-" + String(switchRF[1]));
String subType = String(switchRF[0]);
announceDeviceTrigger(
false,
(char*)discovery_topic.c_str(),
"received",
(char*)subType.c_str(),
(char*)theUniqueId.c_str(),
"", "", "", "");
Log.trace(F("[RF] Entity Discovered, create HA Discovery CFG" CR));
announceGatewayTrigger(
discovery_topic.c_str(), // topic
"Received", // type
String("RF-" + iSignal).c_str(), // subtype
iSignal.c_str(), //signal id
"{{trigger.value.raw}}" // value template
);
}
# endif
/**
* @brief Processes received RF signals and converts them to JSON format for further handling.
*
* This function checks if an RF signal is available, extracts relevant data from the signal,
* and stores it in a JSON object. It also handles duplicate signal detection and optionally
* publishes the signal data for MQTT discovery and repetition.
*
* @note This function is designed to work with both ESP32 and ESP8266 platforms.
*
* @details The function performs the following steps:
* - Checks if an RF signal is available.
* - Logs the reception of the RF signal.
* - Extracts the value, protocol, length, delay, tristate, and binary representation of the signal.
* - For ESP32 and ESP8266, extracts the raw data of the signal.
* - If the ZradioCC1101 is defined, includes the frequency in the JSON object.
* - Resets the availability status of the RF signal.
* - Checks for duplicate signals and processes the signal if it is not a duplicate.
* - Optionally publishes the signal data for MQTT discovery and repetition.
*
* @param None
* @return void
*/
void RFtoX() {
if (mySwitch.available()) {
StaticJsonDocument<JSON_MSG_BUFFER> RFdataBuffer;
JsonObject RFdata = RFdataBuffer.to<JsonObject>();
Log.trace(F("Rcv. RF" CR));
# ifdef ESP32
Log.trace(F("RF Task running on core :%d" CR), xPortGetCoreID());
Log.trace(F("[RF] Rcv. RF - Task running on core :%d" CR), xPortGetCoreID());
# else
Log.trace(F("[RF] Rcv. RF" CR));
# endif
uint64_t MQTTvalue = mySwitch.getReceivedValue();
int length = mySwitch.getReceivedBitlength();
@@ -137,36 +196,62 @@ void RFtoX() {
}
RFdata["raw"] = rawDump;
# endif
# ifdef ZradioCC1101 // set Receive off and Transmitt on
RFdata["frequency"] = RFConfig.frequency;
# endif
mySwitch.resetAvailable();
if (!isAduplicateSignal(MQTTvalue) && MQTTvalue != 0) { // conditions to avoid duplications of RF -->MQTT
# if defined(ZmqttDiscovery) && !defined(RF_DISABLE_TRANSMIT) && defined(RFmqttDiscovery) //component creation for HA
# if defined(ZmqttDiscovery) && defined(RF_on_HAS_as_DeviceTrigger)
if (SYSConfig.discovery)
RFtoMQTTdiscovery(MQTTvalue);
announceGatewayTriggerTypeToHASS(MQTTvalue);
# endif
RFdata["origin"] = subjectRFtoMQTT;
enqueueJsonObject(RFdata);
// Casting "receivedSignal[o].value" to (unsigned long) because ArduinoLog doesn't support uint64_t for ESP's
Log.trace(F("Store val: %u" CR), (unsigned long)MQTTvalue);
Log.trace(F("[RF] Store val: %u" CR), (unsigned long)MQTTvalue);
storeSignalValue(MQTTvalue);
if (repeatRFwMQTT) {
Log.trace(F("Pub RF for rpt" CR));
Log.trace(F("[RF] Pub RF for rpt" CR));
RFdata["origin"] = subjectMQTTtoRF;
enqueueJsonObject(RFdata);
}
} else {
Log.trace(F("[RF] RF signal received but already managed" CR));
}
}
// else {
// No RF signal received
// }
}
# if simpleReceiving
# if simpleReceiving // FALSE MEAN you don't want to use old way reception analysis
/**
* @brief Transmits RF signals based on the provided MQTT topic and data.
*
* This function processes the MQTT topic and data to determine the RF protocol,
* pulse length, and number of bits to use for transmission. It then transmits
* the RF signal using the specified parameters. If no specific parameters are
* provided, default values are used.
*
* @param topicOri The original MQTT topic string.
* @param datacallback The data to be transmitted, provided as a string.
*
* The function performs the following steps:
* 1. Disables the current RF receiver and enables the transmitter if ZradioCC1101 is defined.
* 2. Converts the data string to a 64-bit unsigned integer.
* 3. Analyzes the topic string to extract RF protocol, pulse length, and bit count.
* 4. Transmits the RF signal using the extracted or default parameters.
* 5. Publishes an acknowledgment to the GTWRF topic.
* 6. Re-enables the RF receiver and disables the transmitter if ZradioCC1101 is defined.
*/
void XtoRF(const char* topicOri, const char* datacallback) {
# ifdef ZradioCC1101 // set Receive off and Transmitt on
disableCurrentReceiver();
ELECHOUSE_cc1101.SetTx(RFConfig.frequency);
Log.notice(F("Transmit frequency: %F" CR), RFConfig.frequency);
Log.notice(F("[RF] Transmit frequency: %F" CR), RFConfig.frequency);
# endif
mySwitch.disableReceive();
mySwitch.enableTransmit(RF_EMITTER_GPIO);
@@ -196,22 +281,20 @@ void XtoRF(const char* topicOri, const char* datacallback) {
}
if ((cmpToMainTopic(topicOri, subjectMQTTtoRF)) && (valuePRT == 0) && (valuePLSL == 0) && (valueBITS == 0)) {
Log.trace(F("MQTTtoRF dflt" CR));
Log.trace(F("[RF] MQTTtoRF dflt" CR));
mySwitch.setProtocol(1, 350);
mySwitch.send(data, 24);
// Acknowledgement to the GTWRF topic
pub(subjectGTWRFtoMQTT, datacallback);
} else if ((valuePRT != 0) || (valuePLSL != 0) || (valueBITS != 0)) {
Log.trace(F("MQTTtoRF usr par." CR));
Log.trace(F("[RF] MQTTtoRF usr par." CR));
if (valuePRT == 0)
valuePRT = 1;
if (valuePLSL == 0)
valuePLSL = 350;
if (valueBITS == 0)
valueBITS = 24;
Log.notice(F("RF Protocol:%d" CR), valuePRT);
Log.notice(F("RF Pulse Lgth: %d" CR), valuePLSL);
Log.notice(F("Bits nb: %d" CR), valueBITS);
Log.notice(F("[RF] Protocol: %d, Pulse Lgth: %d, Bits nb: %d" CR), valuePRT, valuePLSL, valueBITS);
mySwitch.setProtocol(valuePRT, valuePLSL);
mySwitch.send(data, valueBITS);
// Acknowledgement to the GTWRF topic
@@ -225,34 +308,53 @@ void XtoRF(const char* topicOri, const char* datacallback) {
}
# endif
# if jsonReceiving
void XtoRF(const char* topicOri, JsonObject& RFdata) { // json object decoding
# if jsonReceiving // FALSE MEAN you don't want to use Json reception analysis
/**
* @brief Handles the conversion of MQTT messages to RF signals.
*
* This function decodes a JSON object received via MQTT and transmits the corresponding RF signal.
* It supports different RF protocols and configurations.
*
* @param topicOri The original MQTT topic.
* @param RFdata The JSON object containing RF data to be transmitted.
*
* The JSON object should contain the following fields:
* - "value": The RF signal value to be transmitted (required).
* - "protocol": The RF protocol to be used (optional, default is 1).
* - "delay": The pulse length in microseconds (optional, default is 350).
* - "length": The number of bits in the RF signal (optional, default is 24).
* - "repeat": The number of times the RF signal should be repeated (optional, default is RF_EMITTER_REPEAT).
* - "txpower": The transmission power for CC1101 (optional, default is RF_CC1101_TXPOWER).
* - "frequency": The transmission frequency for CC1101 (optional, default is RFConfig.frequency).
*
* The function logs the transmission details and acknowledges the sending by publishing the value to an acknowledgement topic.
* It also restores the default repeat transmit value after sending the signal.
*/
void XtoRF(const char* topicOri, JsonObject& RFdata) {
if (cmpToMainTopic(topicOri, subjectMQTTtoRF)) {
Log.trace(F("MQTTtoRF json" CR));
Log.trace(F("[RF] MQTTtoRF json" CR));
uint64_t data = RFdata["value"];
if (data != 0) {
int valuePRT = RFdata["protocol"] | 1;
int valuePLSL = RFdata["delay"] | 350;
int valueBITS = RFdata["length"] | 24;
int valueRPT = RFdata["repeat"] | RF_EMITTER_REPEAT;
Log.notice(F("RF Protocol:%d" CR), valuePRT);
Log.notice(F("RF Pulse Lgth: %d" CR), valuePLSL);
Log.notice(F("Bits nb: %d" CR), valueBITS);
Log.notice(F("[RF] Protocol:%d, Pulse Lgth: %d, Bits nb: %d" CR), valuePRT, valuePLSL, valueBITS);
disableCurrentReceiver();
# ifdef ZradioCC1101
initCC1101();
int txPower = RFdata["txpower"] | RF_CC1101_TXPOWER;
ELECHOUSE_cc1101.setPA((int)txPower);
Log.notice(F("CC1101 TX Power: %d" CR), txPower);
Log.notice(F("[RF] CC1101 TX Power: %d" CR), txPower);
float txFrequency = RFdata["frequency"] | RFConfig.frequency;
ELECHOUSE_cc1101.SetTx(txFrequency);
Log.notice(F("Transmit frequency: %F" CR), txFrequency);
Log.notice(F("[RF] Transmit frequency: %F" CR), txFrequency);
# endif
mySwitch.enableTransmit(RF_EMITTER_GPIO);
mySwitch.setRepeatTransmit(valueRPT);
mySwitch.setProtocol(valuePRT, valuePLSL);
mySwitch.send(data, valueBITS);
Log.notice(F("MQTTtoRF OK" CR));
Log.notice(F("[RF] MQTTtoRF OK" CR));
// we acknowledge the sending by publishing the value to an acknowledgement topic, for the moment even if it is a signal repetition we acknowledge also
RFdata["origin"] = subjectGTWRFtoMQTT;
enqueueJsonObject(RFdata);
@@ -264,28 +366,50 @@ void XtoRF(const char* topicOri, JsonObject& RFdata) { // json object decoding
}
# endif
int receiveInterupt = -1;
/**
* @brief Disables the RF receiver.
*
* This function disables the RF receiver by calling the disableReceive method
* on the mySwitch object. It also logs a trace message indicating that the RF
* receiver has been disabled, along with the GPIO pin number used for the RF
* receiver.
*
* @note THIS SEEMS LIKE A DEAD CODE. THE FUNCTION IS NOT CALLED ANYWHERE.
*/
void disableRFReceive() {
Log.trace(F("disableRFReceive %d" CR), receiveInterupt);
Log.trace(F("[RF] disable RFReceive %d" CR), RF_RECEIVER_GPIO);
mySwitch.disableReceive();
}
void enableRFReceive() {
Log.notice(F("Enable RF Receiver: %FMhz" CR), RFConfig.frequency);
//RF init parameters
Log.notice(F("RF_EMITTER_GPIO: %d " CR), RF_EMITTER_GPIO);
Log.notice(F("RF_RECEIVER_GPIO: %d " CR), RF_RECEIVER_GPIO);
/**
* @brief Enables the RF receiver and optionally the RF transmitter.
*
* This function initializes the RF receiver on the specified GPIO pin and, if not disabled,
* initializes the RF transmitter on the specified GPIO pin. It also sets the RF frequency
* and logs the configuration details.
*
* @param rfFrequency The frequency for the RF communication in MHz. Default is RFConfig.frequency.
* @param rfReceiverGPIO The GPIO pin number for the RF receiver. Default is RF_RECEIVER_GPIO.
* @param rfEmitterGPIO The GPIO pin number for the RF transmitter. Default is RF_EMITTER_GPIO.
*
* @note If RF_DISABLE_TRANSMIT is defined, the RF transmitter will be disabled.
*/
void enableRFReceive(
float rfFrequency = RFConfig.frequency,
int rfReceiverGPIO = RF_RECEIVER_GPIO,
int rfEmitterGPIO = RF_EMITTER_GPIO) {
Log.notice(F("[RF] Enable RF Receiver: %fMhz, RF_EMITTER_GPIO: %d, RF_RECEIVER_GPIO: %d" CR), rfFrequency, rfEmitterGPIO, rfReceiverGPIO);
# ifdef RF_DISABLE_TRANSMIT
mySwitch.disableTransmit();
# else
mySwitch.enableTransmit(RF_EMITTER_GPIO);
mySwitch.enableTransmit(rfEmitterGPIO);
# endif
receiveInterupt = RF_RECEIVER_GPIO;
mySwitch.setRepeatTransmit(RF_EMITTER_REPEAT);
mySwitch.enableReceive(receiveInterupt);
Log.trace(F("ZgatewayRF command topic: %s%s%s" CR), mqtt_topic, gateway_name, subjectMQTTtoRF);
Log.trace(F("ZgatewayRF setup done" CR));
mySwitch.setRepeatTransmit(rfEmitterGPIO);
mySwitch.enableReceive(rfReceiverGPIO);
Log.trace(F("[RF] Setup command topic: %s%s%s\n Setup done" CR), (const char*)mqtt_topic, (const char*)gateway_name, (const char*)subjectMQTTtoRF);
}
#endif

View File

@@ -23,9 +23,14 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ArduinoJson.h>
#include <ArduinoLog.h>
#include "User_config.h"
#ifdef ZmqttDiscovery
# include "config_mqttDiscovery.h"
String getMacAddress() {
uint8_t baseMac[6];
@@ -89,106 +94,145 @@ void createDiscoveryFromList(const char* mac,
# endif
/**
* @brief Create a message for Discovery Device Trigger. For HA @see https://www.home-assistant.io/integrations/device_trigger.mqtt/
* @param use_gateway_info Boolean where true mean use the OMG information as Device Information
* @param topic The Topic where the trigger will publish the content
* @brief Announce that the Gateway have the ability to raise Trigger.
* This function provide the configuration of the MQTT Device trigger ( @see https://www.home-assistant.io/integrations/device_trigger.mqtt/ ).
* All messages published by this function will be interpreted as configuration messages of Gateway Triggers.
* Instead, all messages published on the "triggerTopic" will be interpreted as Gateway trigger.
*
* @param triggerTopic Mandatory - The MQTT topic subscribed to receive trigger events.
* @param type The type of the trigger, e.g. button_short_press. Entries supported by the HA Frontend: button_short_press, button_short_release, button_long_press, button_long_release, button_double_press, button_triple_press, button_quadruple_press, button_quintuple_press. If set to an unsupported value, will render as subtype type, e.g. button_1 spammed with type set to spammed and subtype set to button_1
* @param subtype The subtype of the trigger, e.g. button_1. Entries supported by the HA frontend: turn_on, turn_off, button_1, button_2, button_3, button_4, button_5, button_6. If set to an unsupported value, will render as subtype type, e.g. left_button pressed with type set to button_short_press and subtype set to left_button
* @param unique_id Valid only if gateway entry is false, The IDs that uniquely identify the device. For example a serial number.
* @param device_name Valid only if gateway entry is false, The name of the device.
* @param device_manufacturer Valid only if gateway entry is false, The manufacturer of the device.
* @param device_model Valid only if gateway entry is false, The model of the device.
* @param device_id Valid only if gateway entry is false, The connection of the device to the outside world
* @param object_id The object_id of the trigger.
* @param value_template The template to render the value of the trigger. The template can use the variables trigger.id, trigger.type, trigger.subtype, trigger.payload, trigger.payload_json, trigger.topic, trigger.timestamp, trigger.value, trigger.value_json. The template can be a string or a JSON object. If the template is a JSON object, it must be a valid JSON object. If the template is a string, it will be rendered as a string. If the template is a JSON object, it will be rendered as a JSON object.
*/
void announceDeviceTrigger(bool use_gateway_info, char* topic, char* type, char* subtype, char* unique_id, char* device_name, char* device_manufacturer, char* device_model, char* device_id) {
void announceGatewayTrigger(const char* triggerTopic,
const char* type,
const char* subtype,
const char* object_id,
const char* value_template) {
//Create The Json
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject sensor = jsonBuffer.to<JsonObject>();
// SET Default Configuration
sensor["automation_type"] = "trigger"; // The type of automation, must be trigger.
/**
* The type of automation, must be trigger.
* @see https://www.home-assistant.io/integrations/device_trigger.mqtt/#automation_type
*/
sensor["automation_type"] = "trigger";
//SET TYPE
/**
* Must be device_automation. Only allowed and required in MQTT auto discovery device messages.
* @see https://www.home-assistant.io/integrations/device_trigger.mqtt/#platform
* @see https://www.home-assistant.io/integrations/mqtt/#device-discovery-payload
*/
sensor["platform "] = "device_automation";
// The MQTT topic subscribed to receive trigger events.
if (triggerTopic && triggerTopic[0]) {
char state_topic[mqtt_topic_max_size];
strcpy(state_topic, mqtt_topic);
strcat(state_topic, gateway_name);
strcat(state_topic, triggerTopic);
/**
* "info_topic" is not a standard field, for the message is required the filed "topic", but this filed is reserved and it is used to know where to publish the topic.
* If we want to send on the message the topic information is usefull to use this "info_topic" that will be not delete by the send function but converted to "topic"
*/
sensor["info_topic"] = state_topic;
} else {
Log.error(F("[RF] Error: topic is mandatory for device trigger Discovery" CR));
return;
}
/**
* The type of the trigger, e.g. button_short_press.
* Entries supported by the HA frontend: button_short_press, button_short_release, button_long_press, button_long_release, button_double_press, button_triple_press, button_quadruple_press, button_quintuple_press.
* If set to an unsupported value, will render as subtype type, e.g. button_1 spammed with type set to spammed and subtype set to button_1
*/
if (type && type[0] != 0) {
sensor["type"] = type;
} else {
sensor["type"] = "button_short_press";
}
//SET SUBTYPE
/**
* The subtype of the trigger, e.g. turn_on.
* Entries supported by the frontend: turn_on, turn_off, button_1, button_2, button_3, button_4, button_5, button_6.
* If set to an unsupported value, will render as subtype type, e.g. left_button pressed with type set to button_short_press and subtype set to left_button
*/
if (subtype && subtype[0] != 0) {
sensor["subtype"] = subtype;
} else {
sensor["subtype"] = "turn_on";
}
/* Set The topic */
if (topic && topic[0]) {
char state_topic[mqtt_topic_max_size];
// ------------------ START DEVICE DECLARATION --------------------------------------------------
// TODO: This section, like the almost identical one in createDiscovery, should be placed in a
// separate function and managed specifically to avoid errors in representing the device
// in the HASS world.
// -------------------------------------------------------------------------------------------------
strcpy(state_topic, mqtt_topic);
strcat(state_topic, gateway_name);
strcat(state_topic, topic);
sensor["info_topic"] = state_topic;
}
/* Set The Devices */
// Information about the device: this device trigger is a part of to tie it into the HA device registry.
StaticJsonDocument<JSON_MSG_BUFFER> jsonDeviceBuffer;
JsonObject device = jsonDeviceBuffer.to<JsonObject>();
JsonArray identifiers = device.createNestedArray("identifiers");
if (use_gateway_info) {
device["name"] = gateway_name;
// A link to the webpage that can manage the configuration of this device.
if (ethConnected) {
# ifdef ESP32_ETHERNET
device["configuration_url"] = String("http://") + String(ETH.localIP().toString()) + String("/"); //configuration_url
# endif
} else {
device["configuration_url"] = String("http://") + String(WiFi.localIP().toString()) + String("/"); //configuration_url
}
/*
* A list of connections of the device to the outside world as a list of tuples [connection_type, connection_identifier].
* For example the MAC address of a network interface: "connections": [["mac", "02:5b:26:a8:dc:12"]].
*/
JsonArray connections = device.createNestedArray("connections");
JsonArray connection_mac = connections.createNestedArray();
connection_mac.add("mac");
connection_mac.add(getMacAddress());
// A list of IDs that uniquely identify the device. For example a serial number.
String unique_id = String(getMacAddress());
JsonArray identifiers = device.createNestedArray("identifiers");
identifiers.add(unique_id);
// The manufacturer of the device.
device["mf"] = GATEWAY_MANUFACTURER;
// The model of the device.
# ifndef GATEWAY_MODEL
String model = "";
serializeJson(modules, model);
device["model"] = model;
String model = "";
serializeJson(modules, model);
device["mdl"] = model;
# else
device["model"] = GATEWAY_MODEL;
device["mdl"] = GATEWAY_MODEL;
# endif
device["manufacturer"] = GATEWAY_MANUFACTURER;
device["sw_version"] = OMG_VERSION;
identifiers.add(getMacAddress());
// The name of the device.
device["name"] = String(gateway_name);
device["sw"] = OMG_VERSION;
// ------------------ END DEVICE DECLARATION ------------------ //
} else {
char deviceid[13];
memcpy(deviceid, &unique_id[0], 12);
deviceid[12] = '\0';
identifiers.add(deviceid);
/*Set Connection */
if (device_id && device_id[0] != 0) {
JsonArray connections = device.createNestedArray("connections");
JsonArray connection_mac = connections.createNestedArray();
connection_mac.add("mac");
connection_mac.add(device_id);
}
//Set manufacturer
if (device_manufacturer && device_manufacturer[0]) {
device["manufacturer"] = device_manufacturer;
}
//Set name
if (device_name && device_name[0]) {
device["name"] = device_name;
}
// set The Model
if (device_model && device_model[0]) {
device["model"] = device_model;
}
device["via_device"] = gateway_name; //device name of the board
}
sensor["device"] = device; //device representing the board
/* Publish on the topic */
String topic_to_publish = String(discovery_prefix) + "/device_automation/" + String(Gateway_Short_Name) + "/" + String(unique_id) + "/config";
Log.trace(F("Announce Device Trigger %s" CR), topic_to_publish.c_str());
if (value_template && value_template[0]) {
sensor["value_template"] = String(value_template);
}
/* Publish on the topic
The discovery topic needs to be: <discovery_prefix>/device_automation/[<node_id>/]<object_id>/config.
Note that only one trigger may be defined per unique discovery topic.
Also note that the combination of type and subtype should be unique for a device.
*/
String topic_to_publish = String(discovery_prefix) + "/device_automation/" + String(unique_id) + "/" + object_id + "/config";
Log.trace(F("Announce Gatewy Trigger %s" CR), topic_to_publish.c_str());
sensor["topic"] = topic_to_publish;
sensor["retain"] = true;
enqueueJsonObject(sensor);
@@ -1061,7 +1105,8 @@ void pubMqttDiscovery() {
}
# endif
# ifdef ZgatewayRF
// in addition to the MQTT Device Discovery
# if defined(ZgatewayRF) && defined(RF_on_HAS_as_MQTTSensor)
// Sensor to display RF received value
Log.trace(F("gatewayRFDiscovery" CR));
char* gatewayRF[8] = {"sensor", "gatewayRF", "", "", jsonVal, "", "", ""};

View File

@@ -35,7 +35,7 @@ extern void RFtoX();
extern void XtoRF(const char* topicOri, const char* datacallback);
extern void XtoRF(const char* topicOri, JsonObject& RFdata);
extern void disableRFReceive();
extern void enableRFReceive();
extern void enableRFReceive(float rfFrequency, int rfReceiverGPIO, int rfEmitterGPIO);
#endif
#ifdef ZgatewayRF2
extern void RF2toX();
@@ -159,7 +159,8 @@ const char parameters[51][4][24] = {
#define RF_EMITTER_REPEAT 20
#define RF2_EMITTER_REPEAT 2 // Actual repeats is 2^R, where R is the here configured amount
//#define RF_DISABLE_TRANSMIT //Uncomment this line to disable RF transmissions. (RF Receive will work as normal.)
#define RFmqttDiscovery true //uncomment this line so as to create a discovery switch for each RF signal received
//#define RF_on_HAS_as_DeviceTrigger //uncomment this line so as to create a Home Assistant device trigger for each RF signal received
#define RF_on_HAS_as_MQTTSensor //uncomment this line so as to create a Home Assistant MQTT sensor for each RF signal received
/*-------------------RF2 topics & parameters----------------------*/
//433Mhz newremoteswitch MQTT Subjects and keys

View File

@@ -92,25 +92,17 @@ extern void createDiscovery(const char* sensor_type,
/**
* @brief Create a message for Discovery Device Trigger. For HA @see https://www.home-assistant.io/integrations/device_trigger.mqtt/
* @param use_gateway_info Boolean where true mean use the OMG information as Device Information
* @param topic The Topic where the trigger will publish the content
* @param topic Mandatory - The MQTT topic subscribed to receive trigger events.
* @param type The type of the trigger, e.g. button_short_press. Entries supported by the HA Frontend: button_short_press, button_short_release, button_long_press, button_long_release, button_double_press, button_triple_press, button_quadruple_press, button_quintuple_press. If set to an unsupported value, will render as subtype type, e.g. button_1 spammed with type set to spammed and subtype set to button_1
* @param subtype The subtype of the trigger, e.g. button_1. Entries supported by the HA frontend: turn_on, turn_off, button_1, button_2, button_3, button_4, button_5, button_6. If set to an unsupported value, will render as subtype type, e.g. left_button pressed with type set to button_short_press and subtype set to left_button
* @param unique_id Valid only if gateway entry is false, The IDs that uniquely identify the device. For example a serial number.
* @param device_name Valid only if gateway entry is false, The name of the device.
* @param device_manufacturer Valid only if gateway entry is false, The manufacturer of the device.
* @param device_model Valid only if gateway entry is false, The model of the device.
* @param device_mac Valid only if gateway entry is false, The connection of the device to the outside world
* @param object_id The object_id of the trigger.
* @param value_template The template to render the value of the trigger. The template can use the variables trigger.id, trigger.type, trigger.subtype, trigger.payload, trigger.payload_json, trigger.topic, trigger.timestamp, trigger.value, trigger.value_json. The template can be a string or a JSON object. If the template is a JSON object, it must be a valid JSON object. If the template is a string, it will be rendered as a string. If the template is a JSON object, it will be rendered as a JSON object.
*/
void announceDeviceTrigger(bool use_gateway_info,
char* topic,
char* type,
char* subtype,
char* unique_id,
char* device_name,
char* device_manufacturer,
char* device_model,
char* device_mac);
void announceGatewayTrigger(const char* topic,
const char* type,
const char* subtype,
const char* object_id,
const char* value_template);
#ifdef discovery_Topic //Deprecated - use discovery_Prefix instead
# pragma message("compiler directive discovery_Topic is deprecated, use discovery_Prefix instead")