Plain Text DCE Messages
From LinuxMCE wiki
Normally DCE messages are exchanged in binary format. If you are using C++, the DCE library will handle all the work for you. The binary format was designed to allow maximum performance in the router, so it can pass through large numbers of messages. Therefore the binary structure is optimized for use with C++. If you want to make a device in another language, you can also connect to the DCE router and send messages as plain text. Here's a quick tutorial explaining the concept:
Setup Test Devices
This sample shows how to write a device that intercepts messages from security sensors, and sends commands to lights. In LinuxMCE admin,
- go to Wizard devices security, and add a motion sensor.
- go to Wizard devices lights, and add a light.
Make note of the device ID for both by clicking the "ADV" button. The device ID is shown above the description. We will refer to those device IDs as [sensor] and [light].
Setup Controller Device
In LinuxMCE web admin,
- from the green menu bar choose Advanced -> Configuration -> devices.
- Choose a PC from the tree, such as "Core".
- Click 'Create Child Device', then 'Pick Device Template'.
- Choose the category 'Logic Handlers' and add the model "Generic #1". This category is reserved for devices that users create to add particular logic. Make note of the device ID, we will refer to it as [logic].
Do a Quick Reload Router before proceeding so the router is aware of these new devices.
- If you click "view" next to the device template, you will see that "implements DCE" is checked. This means a piece of software runs for these devices. The software should register with the DCE router to receive commands and fire events. Note the command line. You can put a binary file in the directory /usr/pluto/bin with that name, and it will be run automatically on the computer in the "controlled via" pulldown when that computer boots up, and passed the command line argument -d [device id] -r [router ip]. Your program should use those parameters to connect to the router as that device. We added a few generic device templates in the logic handler category so users who want to write some generic devices can do so without creating new templates. We do not include any binary files for these generic templates, they're just placeholders. So untill you add a binary file with that name you will see a warning at boot up that the file does not exist. It is harmless.
Playing With The Devices
Open 4 consoles or SSH sessions.
- In the first we will follow the DCE router log by running: tail -f /var/log/pluto/DCERouter.log
- In the second and third, you will create 2 telnet sessions to connect to the DCERouter running on the core so you can see how the messages work.
- Each device connects to the DCERouter twice.
- One connection is used for incoming messages, normally receiving commands.
- The second connection is used for outgoing messages, normally firing events.
- Two socket connections are used because if a device receives an incoming message that will require a response, such as a command with output parameters, that device may want to be able to send messages and to get back responses before it responds to its message.
- In both consoles run: telnet [ip of core] 3450
- Both telnet sessions should say they connected to the server. In console number two type: COMMAND [logic]
- Remember [logic] is the device ID from step number two.
- Go back to console number one, and you should see DCE router logged: Device ID [logic]'s Command Handler registered. This means your device is ready to receive commands.
- In the fourth console session send your device a bogus command of type 999, ID 9999 from device ID 1234 to confirm it is working like this:
- /usr/pluto/bin/MessageSend [core ip] 1234 [logic] 999 9999. It doesn't matter to the router that the 'From Device', 1234, doesn't exist.
- In your second console window you should've seen the plain text MESSAGE 63, meaning you are receiving an incoming message of 63 bytes, followed by the message as binary data.
- In the third console window you'll connect your event handler which you can use to send messages to the router.
- Note that you can connect as many events handlers, that is outgoing sockets, as you want.
- However you can only connect one command socket for incoming messages.
- If you were to open another telnet session and connect again with the same device ID, the prior connection would automatically be dropped.
- So in this third console window, enter EVENT [logic]
- The router should give you back an OK followed by your Device's IP address and Mac Address.
- Tell the router you want messages in plain text, rather than binary by entering: PLAIN_TEXT
- Go back to the fourth console and send yourself that same message again. This time when you switch back to your command handler on the second console, you should see the message arrived in plain text, and the MESSAGE is now MESSAGET, for plain text. The format of the message is identical to the command line arguments you would pass using the MessageSend utility.
- Type /usr/pluto/bin/MessageSend --help for details. Note messages can be embedded within messages, they will be separated with a single "&" parameter.
Register Message Interceptor
Now let's register a message interceptor telling the router you want to receive all events from any device within the category "security sensor". See Message Interceptors. Normally message interceptors are handled by the C++ DCE library automatically. Most interceptors are registered by plug-ins, which share DCERouter's memory space, and therefore get copies of the actual message and can do processing on it. Since you have created an external device without using DCE's C++ libraries, you will instead send the router a message indicating what type of messages you want to intercept.
- You do this by sending a message of type 8 (MESSAGETYPE_REGISTER_INTERCEPTOR) to DCERouter.
- DCERouter always responds to the virtual device ID -1000 (DEVICEID_DCEROUTER).
- The 'from device' in the message must be your device ID. This is the device that will receive copies of messages that meet the criteria.
- The ID of the message is unimportant--we will use zero.
- The parameters of the message represent the criteria: PARM_FROM=1, PARM_TO=2, PARM_TEMPLATE=3, PARM_CATEGORY=4, PARM_MESSAGE_TYPE=5, PARM_MESSAGE_ID=6.
- So our message has two parameters,
- one for the type, since we only once messages of type 2 MESSAGETYPE_EVENT,
- and one for the category since we only want events from devices within category 84 (Security Device).
- The Message, as we would send it with MessageSend (don't do this yet), would look like this: /usr/pluto/bin/MessageSend [core ip] [logic] -1000 8 0 5 2 4 84
- You actually could use MessageSend to send that message.
- The router would still register the interceptor and forward matching messages to the device ID [logic]. But since you already have on outgoing event connection on console three, we can send the message there.
- Note to the length of the message starting with the from device.
- So if this device was 54625, the message would be 24 bytes long: 54625 -1000 8 0 5 2 4 84
- In console 3 enter this, where 24 is the length of the message, which may vary if your device ID is not 5 digits: MESSAGET 24
- Like always you hit enter to terminate the plain text command.
- Now the router knows you are sending a message, will wait to receive 24 bytes, and process the message when it does.
- Enter: [logic] -1000 8 0 5 2 4 84
- You should see an "OK" and a log entry on console 1 showing the interceptor.
- If you mess up, enter a bunch of characters so DCERouter finishes waiting for that message to come in. Then hit enter. You can always enter TIME and you should get back the time, or enter garbage, like xxxxxx and get back an ERROR.
- Now let's test this.
- We will fire an event from your sensor "sensor tripped" (id 9) with the tripped parameter (id 25) set to 1.
- Events should be sent to the virtual device -1001 (DEVICEID_EVENTMANAGER).
- Entering this in console 4: /usr/pluto/bin/MessageSend [core ip] [sensor] -1001 2 9 25 1
- Note that if you already added some actions for this sensor on the Wizard, devices, security settings page, and if you set the house mode accordingly, such as "armed", security plug-in will also intercept the message and treat this like a security breach.
- It will pop up in pads on all the orbiters
- start archiving video
- and after 30 seconds attempt to notify you.
- So leave your security system disarmed at the moment.
- Back in console 2, where you have your incoming command connection,
- you should have received a message of type 9 (MESSAGETYPE_MESSAGE_INTERCEPTED),
- and the first embedded message will be the event the security sensor fired, like this: 0 [logic] 9 0 "&" [sensor] -1001 2 9 25 "1"
- Now let's also tell DCE router you want to be notified whenever lights are turned on or off.
- So we will add two more message interceptors, both with the type 1 (MESSAGETYPE_COMMAND), category 73 (Lighting Device), and with the ID 192 (command on), 193 (command off).
- Send this in your event connection on console 3: MESSAGET 63 (ENTER) [logic] -1000 8 0 5 1 4 73 6 192 & [logic] -1000 8 0 5 1 4 73 6 193
- Now send the lights on and off command.
- You can do this with MessageSend, or with the SendCommand option in LinuxMCE admin, or create a lighting scenario that turns them on or off, or use of floor plans on the orbiter. In all cases you will get the intercepted message the same.
Note On Simple Cases
If you only want to be able to send commands to LinuxMCE devices, you can just make an events connection with the special device ID -1003 (DEVICEID_MESSAGESEND). In other words send EVENT -1003 when you open your event connection. This is a special device ID the DCERouter ignores allowing external utilities to connect and send messages without actually having a unique device ID that exists in the database. When the MessageSend utility connects to the router to send a message, it uses this device ID.
Of course, if you only you want to write an external script that sends messages, you can just use the MessageSend utility. However by registering a command handler to receive incoming messages, then you will be able to implement commands, and register message interceptors.
The messages are entirely freeform. In other words, you can send a device message of a type and an ID that does not exist, and DCERouter will process it just fine forwarding it to do what ever destination device. Message type 1 and 2 are reserved for commands and events, and when DCERouter sees messages of those types, it will look up the description of the command and event, which ultimately comes from the database, and log that.
If you need to send or receive binary message parameters, they will come through uuencoded. MessageSend can do uuencode/decode for you. Type MessageSend --help for details.
If your device dies, and restarts and re-registers message interceptors, DCERouter will have multiple message interceptors for your device, and will therefore send your device multiple copies of intercepted messages. DCERouter's list of message interceptors is reset on when DCERouter is reloaded. You can tell DCERouter to purge any message interceptors for your device by sending it a message type 13 (MESSAGETYPE_PURGE_INTERCEPTORS), or by calling void Command_Impl::PurgeInterceptors();
DCERouter sends 'PING' string on device's event receiving socket. Device should response with PONG on same socket - otherwise DCERouter will close command socket...
System Commands & Response
It seems that response is required on all system commands (type 7 - System Command) received from DCERouter. Device should response with OK on same socket - otherwise DCERouter will send Close command to your device (same as in Reload sequence)...
I guess for minimal implementation, you should just response with OK on all commands and exit application if SYSCOMMAND_RELOAD is received.
Brief enumeration of system commands :
# SYSCOMMAND_QUIT=0, SYSCOMMAND_RELOAD=1, SYSCOMMAND_START_MODULE=2, SYSCOMMAND_STOP_MODULE=3, # SYSCOMMAND_SEGFAULT=4, SYSCOMMAND_DEADLOCK=5, SYSCOMMAND_RELOAD_FORCED=6, SYSCOMMAND_SHOW_SOCKETS=7, # SYSCOMMAND_DEVICE_UP=8, SYSCOMMAND_DEVICE_DOWN=9, SYSCOMMAND_ROTATE=10, SYSCOMMAND_RELOAD_LOGLEVEL=11, # SYSCOMMAND_SPAWN_NEW_CHILDREN=12, SYSCOMMAND_RETRIEVE_INSTALLATION_ID=13
DCERouter Reload behaviour
When DCERouter reloads, it sends command 'reload' of type 7 and id of 1 to all clients. They should take proper actions too. One way is to exit application and let Core restart them. Another is probably to reconnect after some time delay...
Some additional enum information can be found in the binary message definition