I've created a ruby gem that provides an interface to EKM Omnimeter Pulse v.4 meters that are connected to a EKM iSerial TCP/IP Ethernet to RS-485 Serial Converter. Still very early stage, so use at your own risk. Need to write more specs, format some of the response values better, and figure out the correct parsing for a few of the attributes. Hoping this board can help me in that respect.
Source and example usage can be found on the git repo page:
https://github.com/jwtd/ekm-omnimeter
Feedback and bugs can be logged here:
https://github.com/jwtd/ekm-omnimeter/issues
Jordan
Ruby Gem to Read EKM Omnimeter Pulse v.4
Ruby Gem to Read EKM Omnimeter Pulse v.4
Last edited by jwtd on Thu Apr 03, 2014 8:29 pm, edited 2 times in total.
Re: Ruby Gem to Read EKM Omnimeter Pulse v.4
HaHa! This is neat
Thanks jwtd for posting to our forum and to github.
I see that you mention an issue with the time your meter is reporting, do you know that you can set the meter time? We normally would do this with the EKM Dash software.
Also I see that you have a xively connection. Will you let us know when you get that working?
It looks like your formatting is good from here. Just be aware that if m.kwh_data_decimal_places # 1 it would mean that all kWh values have 1 decimal place.
How you have parsed Max Demand looks like it may be high: 226400
Hey, and thanks for the "awesome hardware" shout out.
Thanks jwtd for posting to our forum and to github.
I see that you mention an issue with the time your meter is reporting, do you know that you can set the meter time? We normally would do this with the EKM Dash software.
Also I see that you have a xively connection. Will you let us know when you get that working?
It looks like your formatting is good from here. Just be aware that if m.kwh_data_decimal_places # 1 it would mean that all kWh values have 1 decimal place.
How you have parsed Max Demand looks like it may be high: 226400
Hey, and thanks for the "awesome hardware" shout out.
Re: Ruby Gem to Read EKM Omnimeter Pulse v.4
You're welcome.
Regarding the date, I did set the date via the EKM Dash Software first. Is there a place where you set daylight savings time or timezone? If not, is the time supposed to be UTC?
As for the precision of the kwh readings, I created a method to cast all the kwh attributes to the precision returned by kwh_data_decimal_places. That method is here...
https://github.com/jwtd/ekm-omnimeter/b ... er.rb#L175
...and the list of attributes that get cast to that precision is here...
https://github.com/jwtd/ekm-omnimeter/b ... er.rb#L238
Thanks for pointing out the incorrect precision on Max Demand. Just fixed it.
I haven't figured out how to map the bytes for meter_type or meter_firmware to human readable values yet. I'm getting back a carriage return and a dollar sign. Here is where I'm parsing them out...
https://github.com/jwtd/ekm-omnimeter/b ... er.rb#L290
From the request_a spec, I was able to pull "pulse_input_hilo", but I can't figure out how that maps to pulse_input_1, pulse_input_2, or pulse_input_3 values that I see in the EKM Dash. Same goes for "direction_of_current". I can't figure out how to get current_direction_L1, current_direction_L2, current_direction_L3 from it. And same goes for "outputs_onoff". I can't figure out how to get output_1 or output_2. Do those come from other calls? If so, could you point me to the correct line in the spec? Thanks!
Regarding the date, I did set the date via the EKM Dash Software first. Is there a place where you set daylight savings time or timezone? If not, is the time supposed to be UTC?
As for the precision of the kwh readings, I created a method to cast all the kwh attributes to the precision returned by kwh_data_decimal_places. That method is here...
https://github.com/jwtd/ekm-omnimeter/b ... er.rb#L175
...and the list of attributes that get cast to that precision is here...
https://github.com/jwtd/ekm-omnimeter/b ... er.rb#L238
Thanks for pointing out the incorrect precision on Max Demand. Just fixed it.
I haven't figured out how to map the bytes for meter_type or meter_firmware to human readable values yet. I'm getting back a carriage return and a dollar sign. Here is where I'm parsing them out...
https://github.com/jwtd/ekm-omnimeter/b ... er.rb#L290
From the request_a spec, I was able to pull "pulse_input_hilo", but I can't figure out how that maps to pulse_input_1, pulse_input_2, or pulse_input_3 values that I see in the EKM Dash. Same goes for "direction_of_current". I can't figure out how to get current_direction_L1, current_direction_L2, current_direction_L3 from it. And same goes for "outputs_onoff". I can't figure out how to get output_1 or output_2. Do those come from other calls? If so, could you point me to the correct line in the spec? Thanks!
Re: Ruby Gem to Read EKM Omnimeter Pulse v.4
I found the value maps for pulse input state, current direction, output state, demand period time, and auto reset max demand in the parsing reference PDF. The only two maps I haven't found yet are the Firmware and Meter Type lookup maps.
Re: Ruby Gem to Read EKM Omnimeter Pulse v.4
Hello jwtd,
Here is the PDF you are referring to (for others that read this), the values you are referring to are in green lettering: http://documents.ekmmetering.com/Omnime ... arsing.pdf
Here is the entire list of communicating meters, some of these are discontinued.
Model number bytes:
10 11 15IDS-N
10 12 25IDS-N
10 13 23EDS-N
10 14 15EDS-N
10 15 25EDS-N
10 16 23EDS-N
10 18 5A25EDS-N
10 20 25EDSP-N
***
OmniMeter I v.3 10 17 fw 13 or 14
OmniMeter II UL v.3 10 22 fw 14
OmniMeter Pulse v.4 10 24 fw 15 or 16
Thanks,
Here is the PDF you are referring to (for others that read this), the values you are referring to are in green lettering: http://documents.ekmmetering.com/Omnime ... arsing.pdf
Here is the entire list of communicating meters, some of these are discontinued.
Model number bytes:
10 11 15IDS-N
10 12 25IDS-N
10 13 23EDS-N
10 14 15EDS-N
10 15 25EDS-N
10 16 23EDS-N
10 18 5A25EDS-N
10 20 25EDSP-N
***
OmniMeter I v.3 10 17 fw 13 or 14
OmniMeter II UL v.3 10 22 fw 14
OmniMeter Pulse v.4 10 24 fw 15 or 16
Yes, the Dash was screwy how it interpreted time. We have fixed this now in our latest development version and the new way will be included with the next release of the Dash.Regarding the date, I did set the date via the EKM Dash Software first. Is there a place where you set daylight savings time or timezone? If not, is the time supposed to be UTC?
Thanks,
Re: Ruby Gem to Read EKM Omnimeter Pulse v.4
Thanks for getting back to me on the models.
In the spec, it says the meter_type us 2 Bytes and the firmware version is 1 Byte. Let me make sure I understand how to interpret the table you provided.
If model number bytes are a "10" and "11", the model is a "15IDS-N". Is that correct? So for the communicating meters, a "10" and "24", the model is a "OmniMeter Pulse v.4", correct?
And to confirm, the firmware byte is just the 13, 14, 15, or 16, correct?
Thanks again,
Jordan
In the spec, it says the meter_type us 2 Bytes and the firmware version is 1 Byte. Let me make sure I understand how to interpret the table you provided.
If model number bytes are a "10" and "11", the model is a "15IDS-N". Is that correct? So for the communicating meters, a "10" and "24", the model is a "OmniMeter Pulse v.4", correct?
And to confirm, the firmware byte is just the 13, 14, 15, or 16, correct?
Thanks again,
Jordan
Re: Ruby Gem to Read EKM Omnimeter Pulse v.4
No need to reply, I got it working.
the_bytes.unpack('H*') gave me the correct combination of values.
"\x10$".unpack('H*') -> ["1024"]
"\x15".unpack('H*') -> ["15"]
Thanks!
the_bytes.unpack('H*') gave me the correct combination of values.
"\x10$".unpack('H*') -> ["1024"]
"\x15".unpack('H*') -> ["15"]
Thanks!
Re: Ruby Gem to Read EKM Omnimeter Pulse v.4
I'm having trouble getting the CRC check to match. I tried to implement the algorithm described here http://forum.ekmmetering.com/viewtopic.php?f=4&t=5#p557
My implementation is here...
https://github.com/jwtd/ekm-omnimeter/b ... r/crc16.rb
Response from snippet (2nd byte through third from last byte, inclusive):
\x10$\x1500030000023400311088001820740000000000000000000047930000279700000000000000001250124700000022800288000000002848000360800000000006456 100 100C000002264001100000031000040000800000000000000000000000000000000000000000000000000000000001404080300101801!\r\n\x03
Expected CRC Value: "x\x7F" -> "787f"
Response snippet using ruby's s.bytes method
[16, 36, 21, 48, 48, 48, 51, 48, 48, 48, 48, 48, 50, 51, 52, 48, 48, 51, 49, 49, 48, 56, 56, 48, 48, 49, 56, 50, 48, 55, 52, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 52, 55, 57, 51, 48, 48, 48, 48, 50, 55, 57, 55, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 50, 53, 48, 49, 50, 52, 55, 48, 48, 48, 48, 48, 48, 50, 50, 56, 48, 48, 50, 56, 56, 48, 48, 48, 48, 48, 48, 48, 48, 50, 56, 52, 56, 48, 48, 48, 51, 54, 48, 56, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 54, 52, 53, 54, 32, 49, 48, 48, 32, 49, 48, 48, 67, 48, 48, 48, 48, 48, 50, 50, 54, 52, 48, 48, 49, 49, 48, 48, 48, 48, 48, 48, 51, 49, 48, 48, 48, 48, 52, 48, 48, 48, 48, 56, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 52, 48, 52, 48, 56, 48, 51, 48, 48, 49, 48, 49, 56, 48, 49, 33, 13, 10, 3]
Running these bytes through the normal CRC algo, I get 38708
I'm using a little indian processor, so I didn't reverse the bytes.
Masking the CRC per the thread, I get 13335, which does not match the last two bytes.
One thing I noticed in that thread I linked to above is that the bytes from the sample response look more like this...
Response snippet by unpacking string as hex bytes s.split().map{ |i| i.unpack('H*')[0]}
["10", "24", "15", "30", "30", "30", "33", "30", "30", "30", "30", "30", "32", "33", "34", "30", "30", "33", "31", "31", "30", "38", "38", "30", "30", "31", "38", "32", "30", "37", "34", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "34", "37", "39", "33", "30", "30", "30", "30", "32", "37", "39", "37", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "31", "32", "35", "30", "31", "32", "34", "37", "30", "30", "30", "30", "30", "30", "32", "32", "38", "30", "30", "32", "38", "38", "30", "30", "30", "30", "30", "30", "30", "30", "32", "38", "34", "38", "30", "30", "30", "33", "36", "30", "38", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "36", "34", "35", "36", "20", "31", "30", "30", "20", "31", "30", "30", "43", "30", "30", "30", "30", "30", "32", "32", "36", "34", "30", "30", "31", "31", "30", "30", "30", "30", "30", "30", "33", "31", "30", "30", "30", "30", "34", "30", "30", "30", "30", "38", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "31", "34", "30", "34", "30", "38", "30", "33", "30", "30", "31", "30", "31", "38", "30", "31", "21", "0d", "0a", "03"]
I'm thinking maybe I have the byte unpacking incorrect???
My implementation is here...
https://github.com/jwtd/ekm-omnimeter/b ... r/crc16.rb
Response from snippet (2nd byte through third from last byte, inclusive):
\x10$\x1500030000023400311088001820740000000000000000000047930000279700000000000000001250124700000022800288000000002848000360800000000006456 100 100C000002264001100000031000040000800000000000000000000000000000000000000000000000000000000001404080300101801!\r\n\x03
Expected CRC Value: "x\x7F" -> "787f"
Response snippet using ruby's s.bytes method
[16, 36, 21, 48, 48, 48, 51, 48, 48, 48, 48, 48, 50, 51, 52, 48, 48, 51, 49, 49, 48, 56, 56, 48, 48, 49, 56, 50, 48, 55, 52, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 52, 55, 57, 51, 48, 48, 48, 48, 50, 55, 57, 55, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 50, 53, 48, 49, 50, 52, 55, 48, 48, 48, 48, 48, 48, 50, 50, 56, 48, 48, 50, 56, 56, 48, 48, 48, 48, 48, 48, 48, 48, 50, 56, 52, 56, 48, 48, 48, 51, 54, 48, 56, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 54, 52, 53, 54, 32, 49, 48, 48, 32, 49, 48, 48, 67, 48, 48, 48, 48, 48, 50, 50, 54, 52, 48, 48, 49, 49, 48, 48, 48, 48, 48, 48, 51, 49, 48, 48, 48, 48, 52, 48, 48, 48, 48, 56, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 52, 48, 52, 48, 56, 48, 51, 48, 48, 49, 48, 49, 56, 48, 49, 33, 13, 10, 3]
Running these bytes through the normal CRC algo, I get 38708
I'm using a little indian processor, so I didn't reverse the bytes.
Masking the CRC per the thread, I get 13335, which does not match the last two bytes.
One thing I noticed in that thread I linked to above is that the bytes from the sample response look more like this...
Response snippet by unpacking string as hex bytes s.split().map{ |i| i.unpack('H*')[0]}
["10", "24", "15", "30", "30", "30", "33", "30", "30", "30", "30", "30", "32", "33", "34", "30", "30", "33", "31", "31", "30", "38", "38", "30", "30", "31", "38", "32", "30", "37", "34", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "34", "37", "39", "33", "30", "30", "30", "30", "32", "37", "39", "37", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "31", "32", "35", "30", "31", "32", "34", "37", "30", "30", "30", "30", "30", "30", "32", "32", "38", "30", "30", "32", "38", "38", "30", "30", "30", "30", "30", "30", "30", "30", "32", "38", "34", "38", "30", "30", "30", "33", "36", "30", "38", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "36", "34", "35", "36", "20", "31", "30", "30", "20", "31", "30", "30", "43", "30", "30", "30", "30", "30", "32", "32", "36", "34", "30", "30", "31", "31", "30", "30", "30", "30", "30", "30", "33", "31", "30", "30", "30", "30", "34", "30", "30", "30", "30", "38", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "31", "34", "30", "34", "30", "38", "30", "33", "30", "30", "31", "30", "31", "38", "30", "31", "21", "0d", "0a", "03"]
I'm thinking maybe I have the byte unpacking incorrect???
Re: Ruby Gem to Read EKM Omnimeter Pulse v.4
Hello Jordan,
Yes, the first thing I would look at is how you are getting bytes that start with a 4, a 5, or a 6.
Our meter responds with 99% of the Hex bytes are "3x". So 30,31,32, etc. 30 in Hex equals 0 in ASCII, 31 in Hex equals 1 in ASCII, etc.
Are you sure that you are using the correct serial settings? 7 data bits, even parity, 1 stop bit, and no hardware flow control.
Hope this helps.
Yes, the first thing I would look at is how you are getting bytes that start with a 4, a 5, or a 6.
Our meter responds with 99% of the Hex bytes are "3x". So 30,31,32, etc. 30 in Hex equals 0 in ASCII, 31 in Hex equals 1 in ASCII, etc.
Are you sure that you are using the correct serial settings? 7 data bits, even parity, 1 stop bit, and no hardware flow control.
This looks like an ASCII response.\x10$\x1500030000023400311088001820740000000000000000000047930000279700000000000000001250124700000022800288000000002848000360800000000006456 100 100C000002264001100000031000040000800000000000000000000000000000000000000000000000000000000001404080300101801!\r\n\x03
Hope this helps.
Re: Ruby Gem to Read EKM Omnimeter Pulse v.4
I have the meter hooked up as a remote meter using an iSerial converter. I'm getting the data back using Ruby's native TCPSocket lib, which gives me generic read and write methods. The code for the call is here https://github.com/jwtd/ekm-omnimeter/b ... er.rb#L388. I assumed that since the response from the meter is parsing out to all the correct values, the read/write calls were correct. I logged into my iSerial web dashboard and reviewed the UART Control screen. Values are as follows:
Mode: RS485
Baudrate: 9600
Character Bits: 7
Parity Type: even
Stop Bit: 1
Hardware Flow Control: none
Uart Memory: 0M, 0k, 0Byte
Uart FIFO Overflow count: 0times
No delimiters
So the response from the iSerial device arrives as that ASCII string. If I unpack it to Hex like so...
s.split().map{ |i| i.unpack('H*')[0]}
I get an array of string which seem to hold the correct hex byte values. Problem is, they aren't bytes, they're strings. This is probably just a ruby type conversion puzel I have to work out.
["10", "24", "15", "30", "30", "30", "33", "30", "30", "30", "30", "30", "32", "33", "34", "30", "30", "33", "31", "31", "30", "38", "38", "30", "30", "31", "38", "32", "30", "37", "34", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "34", "37", "39", "33", "30", "30", "30", "30", "32", "37", "39", "37", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "31", "32", "35", "30", "31", "32", "34", "37", "30", "30", "30", "30", "30", "30", "32", "32", "38", "30", "30", "32", "38", "38", "30", "30", "30", "30", "30", "30", "30", "30", "32", "38", "34", "38", "30", "30", "30", "33", "36", "30", "38", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "36", "34", "35", "36", "20", "31", "30", "30", "20", "31", "30", "30", "43", "30", "30", "30", "30", "30", "32", "32", "36", "34", "30", "30", "31", "31", "30", "30", "30", "30", "30", "30", "33", "31", "30", "30", "30", "30", "34", "30", "30", "30", "30", "38", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "31", "34", "30", "34", "30", "38", "30", "33", "30", "30", "31", "30", "31", "38", "30", "31", "21", "0d", "0a", "03"]
Mode: RS485
Baudrate: 9600
Character Bits: 7
Parity Type: even
Stop Bit: 1
Hardware Flow Control: none
Uart Memory: 0M, 0k, 0Byte
Uart FIFO Overflow count: 0times
No delimiters
So the response from the iSerial device arrives as that ASCII string. If I unpack it to Hex like so...
s.split().map{ |i| i.unpack('H*')[0]}
I get an array of string which seem to hold the correct hex byte values. Problem is, they aren't bytes, they're strings. This is probably just a ruby type conversion puzel I have to work out.
["10", "24", "15", "30", "30", "30", "33", "30", "30", "30", "30", "30", "32", "33", "34", "30", "30", "33", "31", "31", "30", "38", "38", "30", "30", "31", "38", "32", "30", "37", "34", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "34", "37", "39", "33", "30", "30", "30", "30", "32", "37", "39", "37", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "31", "32", "35", "30", "31", "32", "34", "37", "30", "30", "30", "30", "30", "30", "32", "32", "38", "30", "30", "32", "38", "38", "30", "30", "30", "30", "30", "30", "30", "30", "32", "38", "34", "38", "30", "30", "30", "33", "36", "30", "38", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "36", "34", "35", "36", "20", "31", "30", "30", "20", "31", "30", "30", "43", "30", "30", "30", "30", "30", "32", "32", "36", "34", "30", "30", "31", "31", "30", "30", "30", "30", "30", "30", "33", "31", "30", "30", "30", "30", "34", "30", "30", "30", "30", "38", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "30", "31", "34", "30", "34", "30", "38", "30", "33", "30", "30", "31", "30", "31", "38", "30", "31", "21", "0d", "0a", "03"]