rfxcom / node-rfxcom

Node.js client for talking to an RFXtrx433 device
MIT License
62 stars 45 forks source link

Upgrading from rfxsend.py: how to send raw commands? #99

Closed av01d closed 3 years ago

av01d commented 3 years ago

I am writing my own home automation software in nodejs. I've got an RFXcom on a Raspberry Pi. Until now I used the rfxcmd python scripts for sending and receiving commands: received sensor data is forwarded to nodejs via MQTT. Works fine, but this node-rfxcmd library seems to fit my needs even better. I've been playing around, and I can successfully receive data from my temperature, humidity and motion sensors. Great! Next step is sending commands to the few 433MHz devices I have. Until now, I used the rfxsend.py script for that. Here are some examples:

rfxsend.py -r 09130000155451015000 // Turn light on rfxsend.py -r 09130000155454015200 // Turn light off

rfxsend.py -r 0C1A000F010234010100000000 // Somfy blinds up rfxsend.py -r 0C1A000E010234010000000000 // Somfy blinds down

These raw commands were found with the RFXmngr program (Windows). How can I send these raw commands in node-rfxcmd? I've been playing with this:

var rfxcom = require('rfxcom');
var rfxtrx = new rfxcom.RfxCom("/dev/ttyUSB-RFX433", {debug: true});
var transmitter = new rfxcom.Transmitter(rfxtrx, null);
rfxtrx.initialise(function () {
   console.log("Device initialised");
   transmitter.sendRaw(???, ???, [0x09, 0x13, 0x00, 0x00, 0x15, 0x45, 0x51, 0x01, 0x50, 0x00]);
});

I don't know what the first 2 arguments to sendRaw should be?

I am hopeful somebody can & will help me. Thanks in advance!

maxwellhadley commented 3 years ago

Welcome to node-rfxcom!

I am not familiar with rfxsend.py, but it looks like it's providing a fairly low-level API to the RFX transceiver.

In node-rfxcom, the typical way to send command messages is by using one of the specialised subclasses of Transmitter: there is one for each supported type of RFX transmit packet (which is most of them). To take your first example, that has a packet type (i.e. the second byte) of 0x13, which is a lighting4 packet type. The first byte, 0x09, is the length of the data packet. The remaining bytes are the subtype (0) and the command sequence number (0), used by the RFX itself, followed by the actual data to be transmitted.

If you look in file lib/index.js, you will find at line 75 a list of the transmitter types, and further down from line 145 on the definition of each packet, listed in numerical order of packet type. Thus at lines 160-163 we see that the Transmitter subclass for a lighting4 packet, type 0x13, is called Lighting4, and has only one subtype, called PT2262. The Lighting4 class provides a single public API method, sendData() This takes three arguments: data, as a 3-byte array or a string, an optional pulseWidth a 16-bit number provided as a two-byte array or a string, and an optional callback. Note that the packet length, type, subtype, and sequence number are all provided automatically.

To send your two example commands, you should create a Lighting4 object, passing the subtype PT2262 to the constructor. Then you can call its sendData() method with your data:

var rfxcom = require('rfxcom');
var rfxtrx = new rfxcom.RfxCom("/dev/ttyUSB-RFX433", {debug: true});
var myLightController = new Lighting4(rfxtrx, rfxcom.lighting4.PT2262);
rfxtrx.initialise(function () {
   console.log("Device initialised");
   myLightController.sendData([0x15, 0x54, 0x51], [0x01, 0x50]); // On
   myLightController.sendData("0x155454"); // Off
});

The second call omits the pulseWidth parameter: in this case, the default value of [0x01, 0x5E] is used - which should be close enough.

Similarly, there is the Rfy subclass for sending commands to Somfy and similar blind motors that use the RFY protocol. There is an example showing how to use it in the README.md file. Your blind motor has address 0x10234 and unit code 1. You pass commands to the transmitter using command-specific methods, like rfy.up("0x10234/1") or rfy.down(["0x10234", "1"]). Note the two different ways the motor address can be supplied. One slight oddity is the command number 0 in your second example is actually the "stop" command, according to the protocol documentation.

The file DeviceCommands.md gives details, for each Transmitter subclass, of the available subtypes, and the public API methods. There are some examples of their use in the README.md file.

Finally, each Transmitter subclass calls Transmitter.sendRaw() to implement the business end of its API. The first parameter is the packet type (number), while the second is the subtype. Take a look at the source code of one of the subclasses to see how they are used!

If you have any other questions, please ask and I will try to answer them.

av01d commented 3 years ago

Wow, thank you so much for this extensive reply. Got most of my devices working fine now. Great. In my bedroom, I've got Dooya roller blinds that I cannot get to work yet. These are the raw commands:

up: "09 19 06 11 01 02 03 11 00 00",
down: "09 19 06 11 01 02 03 11 01 00",
stop: "09 19 06 11 01 02 03 11 02 00",
intermediate: "09 19 06 11 01 02 03 11 04 00"

This is my test code:

var ctrl = new rfxcom.Blinds1(rfxtrx, rfxcom.blinds1.BLINDS_T6);
var id = "0x010203/11";
ctrl.close(id);

I'm pretty sure that blinds1 is the correct subclass and BLINDS_T6 is the right type. But the blinds don't move... so I presume the id I'm using is incorrect. Can you help me find the right one?

Then some additional questions:

maxwellhadley commented 3 years ago

I think the problem is BLINDS_T6 uses the top 4 bits of the 8th byte as part of the id, not the unit code. So in your example the id ought to be "0x0102031/1".

Also, the RfxCom object emits an event for each type of packet received, so if you handle the blinds1 event, you will get an object populated with the correctly-decoded id, status information, and the command name & number, each time a packet is received. Unfortunately, for this packet type different subtypes use the same command number for different commands, so the name isn't always correct. The intermediate command is not one of those included - I will look at adding it, to both transmit & receive.

Plain Javascript doesn't actually support privacy, so you can always access any method of an object - feel free to call sendRaw() from your own code if you want!

But...

...if you want to implement home automation using NodeJS, can I recommend you take a look at Node-RED? It's a graphical, flow-based programming environment originally developed by a couple of guys at IBM, and now an Eclipse Foundation project. I have contributed a graphical node, node-red-contrib-rfxcom, that wraps node-rfxcom and exposes almost all of its functionality in the Node-RED environment.

maxwellhadley commented 3 years ago

The AsyncConfig message comes from a bug in my code - it refers to a transmitter subclass for packet type 0x61 that I don't support (yet), because it requires a later model of the hardware than I have here. You may see a similar warning for AsyncData (0x62) as well. I've created issue #100 to address this

av01d commented 3 years ago

I think the problem is BLINDS_T6 uses the top 4 bits of the 8th byte as part of the id, not the unit code. So in your example the id ought to be "0x0102031/1".

Works, thank you so much!

Also, the RfxCom object emits an event for each type of packet received, so if you handle the blinds1 event, you will get an object populated with the correctly-decoded id, status information, and the command name & number, each time a packet is received. Unfortunately, for this packet type different subtypes use the same command number for different commands, so the name isn't always correct. The intermediate command is not one of those included - I will look at adding it, to both transmit & receive.

Great. I've given it a go myself, successfully: in lib/defines.js, I added this:

BLINDS_INTERMEDIATE: 0x04,

in lib/blinds.js, I added this function:

intermediate(deviceId, callback) {
    return this._sendCommand(deviceId, defines.BLINDS_INTERMEDIATE, callback);
};

Maybe you need/want to add some extra checks (for devices other then BLINDS_T6), but this works for me.

Plain Javascript doesn't actually support privacy, so you can always access any method of an object - feel free to call sendRaw() from your own code if you want!

Might, might not.... not sure as everything now works the way you intended it!

But...

...if you want to implement home automation using NodeJS, can I recommend you take a look at Node-RED? It's a graphical, flow-based programming environment originally developed by a couple of guys at IBM, and now an Eclipse Foundation project. I have contributed a graphical node, node-red-contrib-rfxcom, that wraps node-rfxcom and exposes almost all of its functionality in the Node-RED environment.

Thanks!

maxwellhadley commented 3 years ago

Three other blinds subtypes support an intermediate position, of which one actually has three different positions. The API needs to cover them all, and of course I will have to write test cases for each. Deep joy...

av01d commented 3 years ago

Three other blinds subtypes support an intermediate position, of which one actually has three different positions. The API needs to cover them all, and of course I will have to write test cases for each. Deep joy...

Good luck with that .... ;-)

av01d commented 3 years ago

I decided to send raw commands; here is how I do that (for future reference):

const rfxcom = require('rfxcom');
const rfxtrx = new rfxcom.RfxCom('/dev/ttyUSB-RFX433');
const transmitter = new rfxcom.Transmitter(rfxtrx, null);

const command = '09 13 00 00 15 54 51 01 50 00' // raw 'on' command
const bits = command.split(' ').map(function(c) {
   return parseInt('0x'+c);
});

rfxtrx.queueMessage(transmitter, bits, 0, function() {
   // callback code here
});
maxwellhadley commented 3 years ago

Just published version 2.4.0 which adds the Blinds1.intemediatePosition() method. It takes an optional position parameter (integer in the range 1 to 3) but which is ignored by all subtypes except BLINDS_T9. If missing, it defaults to 2.

av01d commented 3 years ago

Works brilliantly. Thank you very much for all you efforts. My RFXcom is now powered by node-rfxcom alone, no more cmdline python scripts. Me = happy ;-)