Here's a detailed description of the bytes in the CUV / SLT protocol along with some sample code.  

Bit definitions:
An example teach telegram is (module ID is 0x FFEF0080):
0x A5 5A 0B 07 FC 08 08 80 FF EF 00 80 00

A "turn on" telegram from the same transmitter is:
0x A5 5A 0B 07 74 FF FF EB FF EF 00 80 00

A "turn off" telegram from the same transmitter is:
0x A5 5A 0B 07 00 FF FF EF FF EF 00 80 00

A status telegram with the input turned on is:
0x A5 5A 0B 07 74 FF FF FB FF EF 00 80 00

A status telegram with the input turned off is:
0x A5 5A 0B 07 74 FF FF FF FF EF 00 80 00

So BYTE3 is the time field, which indicates to the receiver how long until we should expect the next packet(telegram).  In the case of an OFF telegram from a CUV whose power is turning off, the field is 0x00, indicating that we should not expect to hear from that device again (until it's powered back on) and therefore we can zero out the timers for that device.  See the table below, along with code for decoding these values, but 0x74 corresponds to 10 seconds.  The protocol uses a companded time scale that allows representation of time values from a few milliseconds to a few days in one byte.

BYTE2 and BYTE1 are unused.  In addition to the LRN bit in BYTE0, the important bits (in non-Teach telegrams) are BYTE0 bit 4 (0x10) which indicates Action or Status packets.  A status telegram will have BYTE0.4 set to 1, and an Action packet will set BYTE0.4 to 0.  The other bit is the state (on/off) bit.  It resides in BYTE0.2 (0x04).  When 0, this bit indicates an ON state, when 1, it indicates an OFF state.

Next, a little background:
When the CUV (SLT) powers on, it sends an "action" packet (see bit description below).  If a receiver gets this message, it knows that a CUV has changed state, and the receiver should, therefore, take some action.  Any time an action packet is received with an ON message, the receiver should turn on it's output.  The ON "action" packet also contains a timing field.  This byte indicates the amount of time until the receiver should expect to hear another packet from the receiver.  The format of this byte is given below and allows the transmitter to decide how often it will send status packets.  If the receiver does not hear any packets from the receiver for 3 times this time, the receiver assumes that the transmitter is now turned off and we missed the OFF action packet, at which point the receiver behaves as if an OFF "action" packet had been received from that transmitter.  If an action packet is received with an OFF message, the output should turn off if there are no other CUVs turned on.

Periodically, after a CUV is powered on, it will transmit "status" packets.  The status packet also contains a time field, indicating how long until the next status packet will be transmitted.  Again, if we don't hear from the transmitter for 3x this long, we assume the transmitter is turned off.  If we receive an OFF "status" packet, but we didn't hear an OFF "action" packet, we assume that it was missed and we treat the first OFF "status" packet as an action packet.

There is another corner case handled at power-up of the receiver.  When the receiver is first powered up, it's possible that a CUV device was never powered off, and is still sending ON "status" packets.  In order to determine if this was the case, we run a timer, starting at 0 seconds at startup, and saturating at 0xFFFF seconds.  When we receive these status packets at startup, we look at the time field in the packet, multiply the time by 3, and see if that time is longer than the time since we started up.  If it's shorter, then we assume that we were powered up when the ON "action" packet would have been transmitted, and we act as if it's an "action" packet.  Otherwise, we treat it as a "status" packet.  We recommend passing events to the Lua Script when a device turns on or off (or we detect that we have missed an action packet and receive a status packet indicating a state change or a timeout occurs due to loss of status packets).  It isn't necessary but could be useful to pass an event when a status packet is received, but differentiate it from an action packet in some way.

Code to convert the time field to milliseconds:
uint32_t ExpTime2ms(uint8_t exptime)
unsigned char exponent, mantissa;
unsigned long ms;
    exponent = ( 3 * (exptime >> 5) );
    mantissa = exptime & 0x1F;
    if (0 == exptime) return 0;
    ms = ((unsigned long)mantissa) << exponent;
    return ms;

Code to convert the time field to seconds (approximately):
uint16_t ExpTimeTo1024ms(uint8_t exptime)
unsigned long ms;
    ms = ExpTime2ms(exptime);
    if(ms > (unsigned long)0x3FFF800 )        // It's a longer time than a uint can hold, we'll coerce it
        return 0;
    else if(ms < 1024)    // If it's shorter than we're marking time, we'll pretend it's one of our intervals
        if (0 == exptime) return 0;
        else return 1;
        return ((unsigned int)(ms >> 10));

Here's a table with some common time field values and the corresponding times:
#define COMPANDED_1_sec        (0x50)
#define COMPANDED_5_sec        (0x6A)
#define COMPANDED_15_sec    (0x7E)
#define COMPANDED_30_sec    (0x88)
#define COMPANDED_2_min        (0x9E)
#define COMPANDED_5_min        (0xA9)
#define COMPANDED_10_min    (0xB3)
#define COMPANDED_15_min    (0xBC)
#define COMPANDED_45_min    (0xCB)
#define COMPANDED_50_min    (0xCC)


Frequently Asked Questions (FAQ)

1.       The only bytes in the 4BS message that are valid are DB3 and DB0. DB2 and DB1 are not used in a single CUV device.


2.       DB3 represents an encoded time for the next status message.

The two MSB bits represent a power of 3 exponent used to scale the value captured in the lower 5 bits.

Exactly.  A few encoded example times are (the descriptions are rounded down, for example, the 30 second definition below represents 32768 milliseconds -- by rounding down we ensure there's a little extra time before the receiver timeout expires -- so we transmit every 30 seconds but tell the receiver that the timeout is 32.7 seconds).

#define COMPANDED_1_sec (0x50)

#define COMPANDED_5_sec (0x6A)

#define COMPANDED_7_sec (0x6E)

#define COMPANDED_15_sec(0x7E)

#define COMPANDED_30_sec(0x88)

#define COMPANDED_2_min (0x9E)

#define COMPANDED_5_min (0xA9)

#define COMPANDED_10_min(0xB3)

#define COMPANDED_15_min(0xBC)

#define COMPANDED_45_min(0xCB)

#define COMPANDED_50_min(0xCC)

If the receiver timer capability is exceeded (the CUV timer can only run for about 18 hours -- 65535 seconds) the timer is not used and the timeouts are ignored.

3.       There is a 3x window on the next status before actions are taken due to lost messages. (a single lost message is handled on the next status message received).

Yes, if the receiver doesn't get a packet for 3 times as long as the last timeout received, the receiver will generally time out.

4.       DB0 represents both the standard learn bit as well as Action(0)/Status(1) message type (in DB0.4) and On(1)/Off(0) state of the circuit (DB0.2).


5.       There is an Ad Hoc state machine associated with the CUV states to handle combining multiple CUV devices to control a “combine” state.

Yes, in particular, it handles the corner cases where a manual switch is used to turn off the load, but we subsequently receive a status "on" packet.  The state machine ensures that status packets will not override a user action.  CUV Action packets, however, are treated with the same priority as a user switch press. 

Questions on Action/Status messages:

1.       For multi-circuit CUV devices does the message encode each CUV state or just a logical combination of the states?

For a multi-channel CUV transmitter, a different ID is used for each of the four channels, and the same encoding method is used. 

Teach-In message:

DB3 is 0xFC

DB2 is 0x08

DB1 is 0x08

DB0 is 0x80 (EnOcean would indicate teach in message with the EnOcean EEP 2.1 information included)


Questions on Teach-In message:

1.       Do all CUV teach in messages have DB3 set to this value (0XFC) or does this value represent an encoding of the type of CUV device it is?

The 0xFC means it's manufacturer specific, and the function type is used to determine which specific function/profile is used (again, the list will vary across manufacturers). 

See attachment for additional LUA code that runs in ILLUMRA BACnet gateway p/n: E9x-GW2-Ei00-00