Air Quality Sensor (PMS5003)

I would like to connect several PMS5003 units to OpenHAB via serial. The devices measures PM1.0, PM2.5 and PM10 dust concentration in the air. The data is spit out of the serial port at 9600 baud with 32 byte frame that starts with 0X42 0C4d. The data I care about is broken up in high eight bits and low eight bits. So as an example:

Start Character 1	0x42(fixed bit)
Start Character 2	0x4d(fixed bit)
Frame Length 16-byte	Frame Length = 2*9+2 (data+check bit)
Data 1, 16-byte	concentration of PM1.0, ug/m3
Data 2, 16-byte	concentration of PM2.5, ug/m3
Data 3, 16-byte	concentration of PM10.0, ug/m3
Data 4, 16-byte	Internal test data
Data 5, 16-byte	Internal test data
Data 6, 16-byte	Internal test data
Data 7, 16-byte	the number of particulate of diameter above 0.3um in 0.1 liters of air
Data 8, 16-byte	the number of particulate of diameter above 0.5um in 0.1 liters of air
Data 9, 16-byte	the number of particulate of diameter above 1.0um in 0.1 liters of air
Data 10, 16-byte	the number of particulate of diameter above 2.5um in 0.1 liters of air
Data 11, 16-byte	the number of particulate of diameter above 5.0um in 0.1 liters of air
Data 12, 16-byte	the number of particulate of diameter above 10.0um in 0.1 liters of air
Data 13, 16-byte	Internal test data
Check Bit for Data Sum, 16-byte	

Is it possible with OpenHAB to make an item for Data 1, Data 2, and Data 3, each made up of a the two 8 bits from the serial stream such as:

42 4D 00 1C 00 3E 00 54 00 60 00 2A 00 3E 00 50 22 23 09 A4 01 4A 00 28 00 06 00 0567 00 04 2C

I personally haven’t done much with Serial devices on OH, but maybe these links would help. My thought would be to use a regex in the serial binding config to extract the values you are after…


https://github.com/avaldebe/AQmon/blob/master/Documents/PMS5003_LOGOELE.pdf

The serial binding receives the data as a String. And given this is a pretty specific binary encoded message I think your best bet will be to create one Item to receive the messages from your devices and a rule to pick out the values and apply them to your Items.

So I created an item:

String Air_Quality {serial="/dev/ttyUSB0@9600"}

And I see it in the logs every second as I should:

2016-04-04 20:11:07.444 [INFO ] [runtime.busevents ] - Air_Quality state updated to BM �u q�

I am now trying to get that string into the HEX char because that is what the device is sending.

logInfo("Testing", "Air Quality: " + Air_Quality.state.toString.getBytes)

That gives me:

2016-04-04 20:11:08.147 [INFO ] [g.openhab.model.script.Testing] - Air Quality: [B@54bf348a

I expected to see:

Air Quality: 42 4D 00 1C 00 3E 00 54 00 60 00 2A 00 3E 00 50 22 23 09 A4 01 4A 00 28 00 06 00 0567 00 04 2C

P.S. I also tried ,BASE64 on the item.

Regarding the item definition, did you use BASE64 and tried using the base64 DECODER against that item value, within a rule, as mentioned here:

Base64 can be decoded in the rules by importing javax.xml.bind.DatatypeConverter and then decoding the value like this:

DatatypeConverter::parseBase64Binary(ITEM.state.toString)

For encoding use the printBase64Binary method of the DatatypeConverter. This is optional and new in version 1.8.

The Base 64 item looks better (no more special char), but still can’t decode to binary.

logInfo(“Testing”, "Air Quality: " + DatatypeConverter::parseBase64Binary(Air_Quality.state.toString)

Gives me:

2016-04-05 08:19:16.335 [INFO ] [runtime.busevents             ] - Air_Quality state updated to Qk0AHAABAAEAAwABAAEAAwR6AO+/vQAQAAIAAgAAcQACSA==

2016-04-05 08:19:16.336 [INFO ] [g.openhab.model.script.Testing] - Air Quality: [B@2a4a201

I also tried:

    var String air = new DatatypeConverter::parseBase64Binary(Air_Quality.state.toString)
    loginfo("testing", "Air Quality: " + air)

And got:

2016-04-05 08:30:35.294 [INFO ] [runtime.busevents             ] - Air_Quality state updated to Qk0AHAABAAEAAQABAAEAAQTvv70AfAALAAAAAAAAcQACPA==

2016-04-05 08:30:35.405 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Testing': The name 'parseBase64Binary(<XMemberFeatureCallImplCustom>)' cannot be resolved to an item or type.

getBytes returns an array of byte primitives so what you are seeing when you print it is the memory address of the start of that array. You probably want something along the lines of:

val StringBuilder bts = new StringBuilder
bts.append("Air Quality:")
Air_Quality.state.toString.getBytes.forEach[b | bts.append(Integer.toHexString(b)]

logInfo("Testing", bts.toString)

Neither Java nor the Rules DSL makes dealing with raw binary data obvious nor easy.

Usual disclaimer, I just typed in the above, there may be typos.

2016-04-05 10:16:01.631 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule ‘Testing’: The name ‘Integer’ cannot be resolved to an item or type.

Actually, this may be what I want, how do I access that array and pull two 1 byte values and then combine them in a 16 bit number? As an example if byte 1 is 42 and byte 2 is 4d I have valid data. I then need to update item1 with 5th and 6th bytes and item 2 with 7th and 8th bytes and so on. The first byte is the high eight bits and the 2nd byte is the low eight bits.

Doh! I knew there would be a typo.

Integer::toHexString(b)

That prints out the data, but it does not look correct:

2016-04-05 10:26:12.629 [INFO ] [runtime.busevents             ] - Air_Quality state updated to Qk0AHAABAAEAAQABAAEAAQPvv70AeQAIAAAAAAAAcQAC77+9

2016-04-05 10:26:12.633 [INFO ] [g.openhab.model.script.Testing] - Air Quality:516b30414841414241414541415141424141454141515076763730416551414941414141414141416351414337372b39da

2016-04-05 10:26:13.468 [INFO ] [runtime.busevents             ] - Air_Quality state updated to Qk0AHAABAAEAAQABAAEAAQQRAHQADAAAAAAAAHEAAe+/vQ==

2016-04-05 10:26:13.473 [INFO ] [g.openhab.model.script.Testing] - Air Quality:516b304148414142414145414151414241414541415151524148514144414141414141414148454141652b2f76513d3dda

It (according to what I see in a serial console) should start with 424d

Ah, I got it, we are not doing the base 64 decode.

I think you need to use ByteBuffer to do this. I think you need to import java.io.* to get to ByteBuffer but don’t remember.

ByteBuffer bb = ByteBuffer::wrap(Air_Quality.state.toString.getBytes)

In Java and therefore by extension in the Rules DSL a 16 bit number is a short so to get the first two bytes as a number you would use:

val short first = bb.getShort

NOTE: ByteBuffer consumes the bytes as you read them off so the next time you call, for example, getShort it will return bytes three and four.

If this doesn’t handle the byte ordering you can change it with a call to :

bb.order(ByteOrder.LITTLE_ENDIAN)

but if I remember correctly what you described is actually Big endian.

val ByteBuffer bb = ByteBuffer::wrap(Air_Quality.state.toString.getBytes)
bb.order(ByteOrder.LITTLE_ENDIAN)
val short first = bb.getShort
logInfo("Testing", "1st Bytes: " + first)
val short second = bb.getShort
logInfo("Testing", "2nd Bytes: " + second)

It does not like the ByteOrder:

2016-04-05 13:04:58.726 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Testing': The name 'ByteOrder' cannot be resolved to an item or type.

If I take that out I get my two vars, but they don’t look right (I am not 100% tho). I also want to run DatatypeConverter::parseBase64Binary on it. How can I do that as part of this code?

Grrr. You need import java.nio.* for ByteOrder. I thought it was in java.io.

Import javax.xml.bind.* to get the object. Then just call:

DatatypeConverter::parseBase64Binary(Air_Quality.state.toString)

But unless you know that the incoming data is indeed Base-64 encoded (from your description and example I would say the answer is “no”) it is not going to do much for you. “�u q�” is not a Base-64 encoded string. There are no special characters in Base-64, only "0-1, a-z, A-Z, and ‘+’ and “/” are valid characters in a Base-64 encoded string.

Ya, I figured that part out, I have import java.nio.*, but its still giving me:

2016-04-05 14:14:53.534 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Testing': The name 'ByteOrder' cannot be resolved to an item or type.

Code so far:

DatatypeConverter::parseBase64Binary(Air_Quality.state.toString)
val ByteBuffer bb = ByteBuffer::wrap(Air_Quality.state.toString.getBytes)
bb.order(ByteOrder.LITTLE_ENDIAN)

val short first = bb.getShort
logInfo("Testing", "1st Bytes: " + first)
val short second = bb.getShort
logInfo("Testing", "2nd Bytes: " + second)
val short third = bb.getShort
logInfo("Testing", "3rd Bytes: " + third)
val short forth = bb.getShort
logInfo("Testing", "4th Bytes: " + forth)
val short fith = bb.getShort

P.S. Your right on Base64, I am changing the item tho to BASE64 so I don’t get junk in my log.

Try ByteOrder::LITTLE_ENDIAN or java.nio.ByteOrder::LITTLE_ENDIAN. Reference to something that is static needs to use “::” instead of “.”.

Though I looked it up and “first byte is the high eight bits and second byte the low eight bits” is big endian which is the default so you probably don’t need it anyway.

NOTE: your first value should be 0x424d = 16973 so your first long when it works should read “1st Bytes: 16973” , unless I did the math wrong.

Oh, your math is correct!!!

2016-04-05 14:41:57.524 [INFO ] [runtime.busevents ] - Air_Quality state updated to Qk0AHAACAAMABAACAAMABAVbAO+/vQATAAMAAgABcQACbA==

2016-04-05 14:41:57.525 [INFO ] [g.openhab.model.script.Testing] - 1st Bytes: 16973
2016-04-05 14:41:57.525 [INFO ] [g.openhab.model.script.Testing] - 2nd Bytes: 28
2016-04-05 14:41:57.525 [INFO ] [g.openhab.model.script.Testing] - 3rd Bytes: 2
2016-04-05 14:41:57.525 [INFO ] [g.openhab.model.script.Testing] - 4th Bytes: 3
2016-04-05 14:41:57.525 [INFO ] [g.openhab.model.script.Testing] - 5th Bytes: 4
2016-04-05 14:41:57.525 [INFO ] [g.openhab.model.script.Testing] - 6th Bytes: 2
2016-04-05 14:41:57.526 [INFO ] [g.openhab.model.script.Testing] - 7th Bytes: 3
2016-04-05 14:41:57.526 [INFO ] [g.openhab.model.script.Testing] - 8th Bytes: 4
2016-04-05 14:41:57.526 [INFO ] [g.openhab.model.script.Testing] - 9th Bytes: 1371
2016-04-05 14:41:57.526 [INFO ] [g.openhab.model.script.Testing] - 10th Bytes: 239
2016-04-05 14:41:57.526 [INFO ] [g.openhab.model.script.Testing] - 11th Bytes: -16451
2016-04-05 14:41:57.526 [INFO ] [g.openhab.model.script.Testing] - 12th Bytes: 19
2016-04-05 14:41:57.526 [INFO ] [g.openhab.model.script.Testing] - 13th Bytes: 3
2016-04-05 14:41:57.526 [INFO ] [g.openhab.model.script.Testing] - 14th Bytes: 2
2016-04-05 14:41:57.526 [INFO ] [g.openhab.model.script.Testing] - 15th Bytes: 1
2016-04-05 14:41:57.526 [INFO ] [g.openhab.model.script.Testing] - 16th Bytes: 28928

1 Like

How do I make the var an item? I tried:

postUpdate(Outside_PM1, bb.getShort)

And got a potUpdate?

2016-04-05 16:57:44.861 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Testing': The name 'potUpdate(<XFeatureCallImplCustom>,<XMemberFeatureCallImplCustom>)' cannot be resolved to an item or type.

Then tried to just use pm1 var:

            val short pm1 = bb.getShort
            logInfo("Testing", "PM 1.0 : " + pm1)
            potUpdate(Outside_PM1, pm1)

And got the same error.