T h last step to complete the binding is to implement the new enOcean profile on the Raspberry Pi board.
I already build the EOLink library in round n.2. Now I'm going to add a new profile and making some changes the eoGateway class in order to handle incoming commands
Commands format
The enOcean profile D2-01 has variable length telegrams (D2 identifies telegrams as VLD -variable length data- telegrams).
To set an actuator status, the radio packet is the following
| DB_2 | DB_1 | DB_0 | |||||||||||||||||||||
DB_2.Bit 7 <-- 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Bit ofs 0--> 23 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
|
| CMD | Dim value | I/O Channel |
| Output value |
where
CMD is the command identifier (0x01 in this case)
Dim value defines which action needs to be taken on the actuator. In my case, it's 0x00 (Switch to new output value)
I/O Channel is identifies the actuator to operate. The value range from 0 (0x00) to 29 (0x1D). There are two special values: 0x1E, which means "operate on all the channels supported by the device" and 0x1F, which means "operate on input channel (for mains supply)".
Output value is the value to set the actuator to. The profile supports also dimming, so output value can 0 to set the output value to 0% or OFF or any value from 1 to 100 to set the output value from 1% to 100% or ON.
Another command I need to implement is the "Actuator status query". The telegram in this case is made by two bytes
| DB_1 | DB_0 | ||||||||||||||
DB_1.Bit 7 <-- 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Bit ofs 0--> 15 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|
| CMD | Dim value | I/O Channel |
where
CMD is the command identifier (0x03 in this case)
I/O Channel identifies the actuator to query
The last command to implement is the "Actuator status response", which is sent when an "Actuator status query" command is received or when the status of a channel has changed. The telegram in this case is a little more complex
| DB_2 | DB_1 | DB_0 | ||||||||||||||||||||||
DB_2.Bit 7 <-- 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
Bit ofs 0--> 23 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | |
| PF | PFD |
|
| CMD | OC | EL | I/O Channel | LC | Output value | |||||||||||||||
where
PF is the "Power failure" flag. In my case it is 0 since power failure is not supported
PFD is the "Power failure detection" flag . In my case it is 0, since power failure is not supported
CMD is the command identifier (0x04 in this case)
OC is the "Over current switch off" flag. it is 0 since ovecurrent switch is not supported
EL is the error level. Possible values are
00: hardware OK
01: hardware warning
10: hardware failure
11: not supported
I/O Channel identifies the actuator the telegram refers to
LC is the "Local control" flag
Output value is the new I/O channel value
Implement the new enOcean profile
To implement the new profile, I created a new class derived from base class eoProfile.
class eoD2EEProfile: public eoEEProfile
{
public:
/**
* Constructor.
* @param size Default size of the expected message.
*/
eoD2EEProfile(uint16_t size);
virtual ~eoD2EEProfile();
virtual eoReturn GetValue(CHANNEL_TYPE type, float &value);
virtual eoReturn GetValue(CHANNEL_TYPE type, float &value, uint8_t index);
virtual eoReturn GetValue(CHANNEL_TYPE type, uint8_t &value, uint8_t index);
virtual eoReturn SetValue(CHANNEL_TYPE type, float value);
virtual eoReturn SetValue(CHANNEL_TYPE type, uint8_t value, uint8_t index);
virtual eoReturn SetValue(CHANNEL_TYPE type, float value, uint8_t index);
virtual eoReturn CreateTeachIN(eoMessage &m);
virtual eoReturn Create(eoMessage &m);
virtual eoReturn Parse(const eoMessage &msg);
/**
* Checks if the message is a VLD data Telegram as in [EEP]
* @param m Message to check
* @return true=yes; false=no
*/
bool IsVLDData(const eoMessage &m);
private:
uint8_t command;
uint32_t destinationID;
uint8_t channel;
};
The most interesting methods in this class are the Parse and Create methods. The first one, parses an incoming radio packet and extract commands and/or status updates. The second one, create a new radio packet to send out a command, a status update or a status query. Before digging into this two methods, let's see the format of the enOcean radio packet.
eoReturn eoD2EEProfile::Create(eoMessage &m)
{
if (command == CMD_ACTUATOR_SET)
{
tel.RORG = rorg;
tel.SetDataLength(3);
tel.destinationID = destinationID;
tel.sourceID = 0;
// bit 0..3: Not used
// bit 4..7: cmd
tel.data[0] = command;
// bit 8..10: dim value -> 0x00 = switch to new output value
// bit 11..15: IO channel
tel.data[1] = 0x00 | (channel & 0x1F);
// bit 16: not used
// but 17..23: output value
tel.data[2] = msg.data[2];
return EO_OK;
}
if (command == CMD_ACTUATOR_QUERY)
{
tel.RORG = rorg;
tel.SetDataLength(2);
tel.destinationID = destinationID;
tel.sourceID = 0;
// bit 0..3: Not used
// bit 4..7: cmd
tel.data[0] = command;
// bit 8..10: not used
// bit 11..15: IO channel
tel.data[1] = 0x00 | (channel & 0x1F);
return EO_OK;
}
if (command == CMD_ACTUATOR_RESPONSE)
{
tel.RORG = rorg;
tel.SetDataLength(3);
tel.destinationID = destinationID;
tel.sourceID = 0;
// bit 0: power failure (0 -> not supported)
// bit 1: power failure detected (0)
// bit 2..3: not used
// bit 4..7: cmd
tel.data[0] = command;
// bit 8: over current switch off (0 -> not supported)
// bit 9..10: error level (0 -> hardware OK)
// bit 11..15: IO channel
tel.data[1] = 0x00 | (channel & 0x1F);
// bit 16: local control (0 -> not supported)
// but 17..23: output value
tel.data[2] = msg.data[2];
return EO_OK;
}
return NOT_SUPPORTED;
}
Changes to the eoGateway class
I changed the oeGateway class to recognize incoming commands.
First of all, I added the code to rea dthe enOcean ID from the transceiver
eoReturn eoGateway::Open(const char *port)
{
eoReturn result = stream->Open(port);
if (result != EO_OK)
return result;
eoSerialCommand cmd(&gateway);
//Read Sw and HW version of the device
CO_RD_VERSION_RESPONSE version;
result = cmd.ReadVersion(version);
if (result == EO_OK)
{
printf("%s %i.%i.%i.%i, ID:0x%08X on %s\n",
version.appDescription,
version.appVersion[0], version.appVersion[1],
version.appVersion[2], version.appVersion[3],
version.chipID,
port);
chipID = version.chipID;
}
else
{
//Failed to read the version of the device
printf("Failed to retrieve USB300 version\n");
}
return result;
}
Obviously I need the chip ID in order to filter the packets that are sent to the device.
Then I added a member variable to store the profile the gateway complies to.
Finally, I changed the Receive methods to handle packets sent to the gateway
uint16_t eoGateway::Receive()
{
flags = 0;
// Process the packet from the queue or receive one from UART
if (!packet_queue.empty())
{
eoPacket *p = packet_queue.front();
p->copyTo(packet);
delete p;
packet_queue.pop_front();
}
else
{
errorCode = (uint8_t)stream->Receive(&packet);
if (errorCode != EO_OK)
return flags;
}
flags = RECV_PACKET;
// check packet consistency
errorCode = (uint8_t)eoConverter::packetToRadio(packet, telegram);
if (errorCode == EO_OK)
flags |= RECV_TELEGRAM;
else
return flags;
// get device information using the source ID in the telegram
device = deviceManager->Get(telegram.sourceID);
if (LearnMode)
errorCode = (uint8_t)learnHandler(telegram);
else
errorCode = (uint8_t)normalHandler(telegram);
if (device != NULL)
{
// if the source device is learned, try to parse message
if (flags & RECV_MESSAGE)
{
if (device->GetProfile()->Parse(message) == EO_OK)
{
flags |= RECV_PROFILE;
}
}
}
else
{
// check if the telegram is directed to this device
if (gateway.chipID == telegram.destinationID)
{
// telegram is for this device: try to parse
if ((profile != NULL) & (profile->Parse(message) == E_OK))
flags |= RECV_COMMAND;
}
}
return flags;
}
The RECV_COMMAND flag is checked by the caller of the eoGateway::Receive method to check for incoming commands.
Sending commands
To send commands without breaking up the interface implemented by the eoProfile class, I decided to set the command to perform and the I/O channel of interest by invoking the SetValue method with E_COMMAND for the channelType parameter and 0 or 1 for the index parameter to set respectively the command and the I/O channel. So the code to send an actuator status change is the following
eoProfile *sendProf = eoProfileFactory::CreateProfile(0xD2,0x01,0x00);
eoMessage mytel = eoMessage(4);
// set command to perform (Actuator status change)
sendProf->SetValue(E_COMMAND, (uint8_t)0x04, 0);
// set I/O channel
sendProf->SetValue(E_COMMAND, (uint8_t)aswitch, 1);
// set new value
sendProf->SetValue(E_IO_CHANNEL, (uint8_t)status, aswitch);
sendProf->Create(mytel);
enocean_gateway.Send(mytel);
Building and deploying
Build the EOLink library as I wrote in my second post and copy the libEOLink.a file (located in the "EOLink\ReleaseLib" folder) to the "/usr/lib" folder of your Raspberry Pi board. We are now re ready to test whether the two-way communication between OpenHAB and Raspberry works!