The light meter uses a binary protocol over a serial connection. Therefore, talking to it manually through a terminal program is not fun.
The protocol documentation and implementation started from 3 pages of chinglish documentation that PCE was kind enough to send to me (Thanks a lot for that!). As it turns out, the documentation is sketchy and partially incorrect, so quite a bit of reverse engineering went into this, too.
I'd provide the original documentation here but don't feel like getting sued for copyright violations. So if you want it ask nicely and PCE or Extech may send it to you, too.
Caution: This represents my current knowledge – or what I believe I know. No warranty, so don't complain if it wrecks your car, explodes your house or harms a kitten.
Upon connection to the computer the instrument identifies as a CP2102 USB to
UART bridge (device ID 10c4:ea60).
Serial communication parameters are 9600bps8N1. Or more verbosely:
| parameter | value |
|---|---|
| baudrate | 9600 |
| byte size | 8 |
| parity | None |
| stop bits | 1 |
| timeout | None |
| xon/xoff | False |
| rts/cts | False |
All commands are preceded by sending the two magic bytes:
0x87 0x83
After that, send a single code byte to run the desired command. For the most part, the commands directly correspond to key presses on the instrument (see manual for details):
| Code | Key | Description |
|---|---|---|
| 0xfe | UNITS key | Toggle units (lux/fc) |
| 0xfd | LIGHT/LOAD key | Toggle backlight |
| 0x7f | RANGE/APO | Toggle measurement ranges |
| 0xfb | REC/SETUP | Save reading to memory |
| 0xbf | MAX/MIN/UP | Toggle min/max/continuous mode |
| 0xf7 | PEAK/LEFT | Toggle peak min/max mode |
| 0xdf | REL/RIGHT | Toggle rel mode |
| 0xef | HOLD/DOWN | Toggle hold mode |
| 0xdb * | LIGHT/LOAD (hold) | Toggle view mode for saved data |
| 0xdc | REC/SETUP (hold) | Start/Stop data logging |
| 0xda | PEAK/LEFT (hold) | Switch to previous display mode |
| 0xde * | REL/RIGHT (hold) | Switch to next display mode |
| 0xf3 | POWER | Power off |
Codes marked with an asterisk differ from the manufacturer's documentation.
Commands not described in manufacturer's protocol documentation but found to do something by trial and error:
| Code | Key | Description |
|---|---|---|
| 0xfa | REC+UNITS | Enter/exit setup |
| 0xf9 | APO on? | |
| 0x7b | APO on? | |
| 0x7c | APO off? | |
| 0xee | memCL shows but memory is not cleared |
The APO commands do toggle the apo logo on the display but I am not at all confident that they actually change apo mode.
0xee seemed to be the command for clearing logger memory but despite showing
'memCL' on screen logger memory is unaffected.
Commands that request data from the instrument cannot be triggered by button presses:
| Code | Description |
|---|---|
| 0x11 | Read the current measurement |
| 0x12 | Read manually stored data registers (1-99) |
| 0x13 | Read logger data |
| 0x14 | Does not exist although mentioned in original docs |
After receiving one of these commands, the instrument returns a binary blob
that requires decoding. The structure of these blobs is described in the next
section. The command 0x14 appears in the original documentation but seems to
do nothing. The data described for this command is actually part of the data
structure returned by 0x13 so is most likely an error in the original docs.
Data blobs always start with a 2 byte magic number that indicates the type of blob. The actual data follows immediately after that.
Most numerical data is encoded as binary coded decimal (BCD), i.e. bytes are interpreted as two separate 4 bit nibbles which encode decimal digits (0-9).
Command: 0x11
Returns a data record of 18 bytes:
| Byte | Size | Content | Type | Comment |
|---|---|---|---|---|
| 0 | 2 | 0xaadd | – | magic number |
| 2 | 1 | 0x00 | – | reserved |
| 3 | 1 | year | BCD | date: year, 2 digits |
| 4 | 1 | weekday | BCD | date: weekday [1, 7] |
| 5 | 1 | month | BCD | date: month |
| 6 | 1 | day | BCD | date: day |
| 7 | 1 | hour | BCD | time: hours |
| 8 | 1 | minute | BCD | time: minutes |
| 9 | 1 | second | BCD | time: seconds |
| 10 | 1 | valH | Uchar | value: higher 2 digits |
| 11 | 1 | valL | Uchar | value: lower 2 digits |
| 12 | 1 | rawvalH | Uchar | raw value: higher 2 digits |
| 13 | 1 | rawvalL | Uchar | raw value: lower 2 digits |
| 14 | 1 | stat0 | bin | Status byte 0 |
| 15 | 1 | stat1 | bin | Status byte 1 |
| 16 | 1 | mem_no | bin | number of saved data records |
| 17 | 1 | read_no | bin | manual storage cursor position |
In normal mode, value and rawvalue are identical. In rel mode however,
rawvalue contains the absolute reading (that would be measured without rel
mode) and value is the relative reading as displayed on the screen.
read_no indicates where the cursor for viewing saved data is positioned. I.e.
the memory register which you will see on the display when switching to saved
data viewing mode by pressing and holding load.
Command: 0x12
The instrument has 99 storage registers so we expect 99 data records of 13 bytes, each.
Total blob length = 99x13 + 2 = 1289bytes.
However, the instrument normally returns quite a few extra bytes. The extra
bytes are all 0x00.
| Byte | Size | Content | Type | Comment |
|---|---|---|---|---|
| 0 | 2 | 0xbb88 | – | magic number |
| 2 | 13 | data record 1 | ||
| 15 | 13 | data record 2 | ||
| 28 | 13 | data record 3 | ||
| ... | ... | |||
| . | 13 | data record n (n<100) | ||
| . | x | 0x00 | trailing zero bytes |
Data record format:
| Byte | Size | Content | Type | Comment |
|---|---|---|---|---|
| 0 | 1 | 0x00 | – | reserved |
| 1 | 1 | year | BCD | date: year, 2 digits |
| 2 | 1 | weekday | BCD | date: weekday [1,7] |
| 3 | 1 | month | BCD | date: month |
| 4 | 1 | day | BCD | date: day |
| 5 | 1 | hour | BCD | time: hour |
| 6 | 1 | minute | BCD | time: minute |
| 7 | 1 | second | BCD | time: second |
| 8 | 1 | pos | Uchar | storage position [1,99] |
| 9 | 1 | valH | Uchar | value: higher 2 digits |
| 10 | 1 | valL | Uchar | value: lower 2 digits |
| 11 | 1 | stat0 | bin | Status byte 0 |
| 12 | 1 | stat1 | bin | Status byte 1 |
The data value uses a variety of BCD on byte level. valH and valL are not BCD
encoded, themselves.
value = Stat0_sign * (100 * valH + valL) * Frange
with:
| Range | Frange |
|---|---|
| 40 | 0.01 |
| 400 | 0.1 |
| 4k | 1.0 |
| 40k | 10 |
| 400k | 100 |
and Stat0_sign is the sign from the Stat0 byte.
The instrument does return all storage positions – even the ones that are
unused. Those have a value of 0x00 for the pos field and are ignored by this
program.
Command: 0x13
Returns a nested data structure comprising a header followed by one or more logging group records that contain logging data records.
5 bytes
| Byte | Size | Content | Type | Comment |
|---|---|---|---|---|
| 0 | 2 | 0xaacc | – | magic number |
| 2 | 1 | nogroups | Uchar | No. of logging groups |
| 3 | 2 | bufsize | UInt16 | Size of logging buffer [bytes] |
Followed by nogroups logging records.
Header of 13 bytes:
| Byte | Size | Content | Type | Comment |
|---|---|---|---|---|
| 0 | 2 | 0xaa56 | – | magic number |
| 2 | 1 | groupno | BCD | number of this group |
| 3 | 1 | sampling | BCD | sampling interval [s] |
| 4 | 1 | 0x00 | – | reserved |
| 5 | 1 | 0x00 | – | reserved |
| 6 | 1 | year | BCD | year |
| 7 | 1 | weekday | BCD | weekday [1,7] |
| 8 | 1 | month | BCD | month |
| 9 | 1 | day | BCD | day |
| 10 | 1 | hour | BCD | hour |
| 11 | 1 | minute | BCD | minute |
| 12 | 1 | second | BCD | second |
Followed by an unknown number of data records. So we need to read until we hit the next magic number for a logging group or EOF.
This seems like a really bad idea, as we may run into the magic number by chance when a value is measured that happens to match the magic number. However, this is actually safe:
The largest value valH and valL can take is 99 because they represent the
upper and lower 2 decimal digits of a number, respectively. The largest
possible value is 9999 = 0x6363 in this representation. So the magic number
0xaa56 is not a valid measurement value and cannot be encountered.
| Byte | Size | Content | Type | Comment |
|---|---|---|---|---|
| 0 | 1 | valH | Uchar | value: higher 2 digits |
| 1 | 1 | valL | Uchar | value: lower 2 digits |
| 2 | 1 | Stat0 | bin | Stat0 byte |
See above (manually stored data) for interpretation of valH and valL.
As no Stat1 byte is present, there is no sign value. It is unclear how
negative readings (in rel mode) should be handled. In fact, the instrument
simply disregards rel mode when logging and just stores absolute values.
Some data records contain status bytes that indicate the settings associated with a measurement as bit fields.
| Bits | Meaning | Values |
|---|---|---|
| 7 | APO | 0: on, 1: off |
| 6 | Hold | 0: cont, 1: hold |
| 5,4,3 | Mode | 000:normal, 010:Pmin, 011:Pmax, 100:max, 101:min, 110:rel |
| 2 | units | 0:lux, 1:fc |
| 1,0 | range | 00:level0, 01:level1, 10:level2 ,11:level3 |
| range level | lux | fc |
|---|---|---|
| level0 | 400k | 40k |
| level1 | 400 | 40 |
| level2 | 4k | 400 |
| level3 | 40k | 4k |
This status byte indicates the display mode the instrument was in.
| Bits | Meaning | Values |
|---|---|---|
| 7,6 | reserved | |
| 5 | power | 0:ok, 1:low |
| 4 | sign | 0:+, 1:- |
| 3,2 | view | 00:time, 01:day, 10:sampling-interval, 11:year |
| 1,0 | memstat | 00: None, 01:store, 10:recall, 11: logging |
The sign must be multiplied with value, as the value data is always unsigned.
memstat indicates if the instrument is in store, recall or logging
mode. store is the mode you enter by saving a value by pressing REC,
recall is the mode in which you view saved datae (long-pressing the load
button) and logging indicates that the instrument actively logs data at the
moment.