Power Meter

From i3Detroit
Jump to: navigation, search

The Power meter for our building measures the whole building, including B.Nektar. Installation of separate service for the two halves of the building is prohibitively expensive. Thus we have installed our own meter at the big disconnect right by the door to B. Nektar, at the southwest corner of the space.

The Meter

The OmniMeter from EKM Metering

RS-485 Interface

The meter can be read and configured via a RS-485 interface on top of the box. The serial protocol is documented here: http://documents.ekmmetering.com/Meter_Communication_Parsing_Submeter_v3.pdf

The port settings are unusual: 9600 baud 7 data bits and EVEN parity.

RS-485 <-> Ethernet

The meter's serial port is connected to the network with a Serial to ethernet adapter. The manual is here: File:RocketPortSoloManual.pdf

The IP address is It should also be recorded on the Network page of this wiki.

There is a web interface for serial port configuration, but please don't monkey with it. Changing the IP address is more tricky, see the manual above.

The adapter is software configurable for RS-232, RS-422 and RS-485. But be careful, the pinout is non-standard. Don't assume that you can re-use the cable that is attached to the adapter.

To connect to the meter's serial port, open a TCP socket on port 8000.

The Serial Protocol

The serial protocol is documented here: http://documents.ekmmetering.com/Meter_Communication_Parsing_Submeter_v3.pdf Further discussion can be found on the EKM forums here: http://forum.ekmmetering.com/viewtopic.php?f=4&t=5

Here's an example of how to talk to this thing with ruby:

class OmniMeter

    attr_reader :raw, :time, :address, :total_kWh, :t1_kWh, :t2_kWh, :t3_kWh,
                :voltage1, :voltage2, :voltage3,
                :current1, :current2, :current3,
                :power1, :power2, :power3, :total_power,
                :cos1, :cos2, :cos3,
                :t1_rev_kWh, :t2_rev_kWh, :t3_rev_kWh, :total_rev_kWh,
                :max_demand, :crc

    def initialize(connection)
        @connection = connection

    def update
        @connection.write "/?000010000863\r\n"
        @raw = @connection.read(255)

        @crc = @raw[-2..-1].unpack('n').first

        return false unless self.valid?

        @address       = @raw[4..15]
        @total_kWh     = @raw[16..23].to_f / 10
        @t1_kWh        = @raw[24..31].to_f / 10
        @t2_kWh        = @raw[32..39].to_f / 10
        @t3_kWh        = @raw[40..47].to_f / 10
        @t4_kWh        = @raw[48..57].to_f / 10
        @total_rev_kWh = @raw[58..63].to_f / 10
        @t1_rev_kWh    = @raw[64..71].to_f / 10
        @t2_rev_kWh    = @raw[72..79].to_f / 10
        @t3_rev_kWh    = @raw[80..87].to_f / 10
        @voltage1      = @raw[96..99].to_f / 10
        @voltage2      = @raw[100..103].to_f / 10
        @voltage3      = @raw[104..107].to_f / 10
        @current1      = @raw[108..112].to_f / 10
        @current2      = @raw[113..117].to_f / 10
        @current3      = @raw[118..122].to_f / 10
        @power1        = @raw[123..129].to_f
        @power2        = @raw[130..136].to_f
        @power3        = @raw[137..143].to_f
        @total_power   = @raw[144..150].to_f
        @cos1          = @raw[151..154]
        @cos2          = @raw[155..158]
        @cos3          = @raw[159..162]
        @max_demand    = @raw[163..170].to_f / 10

        date = @raw[172..177]
        time = @raw[180..185]
        @time = Time.strptime(date + time, '%y%m%d%H%M%S')


    def get_hash
        hsh = {}
        [:time, :address, :total_kWh, :t1_kWh, :t2_kWh, :t3_kWh,
         :voltage1, :voltage2, :voltage3,
         :current1, :current2, :current3,
         :power1, :power2, :power3, :total_power,
         :cos1, :cos2, :cos3,
         :t1_rev_kWh, :t2_rev_kWh, :t3_rev_kWh, :total_rev_kWh,
         :max_demand, :crc
        ].each do |symbol|
            hsh[symbol] = self.send(symbol)


    def inspect

    def valid?
        @crc == self.calc_crc16

    def calc_crc16
        crc = 0xFFFF
        @raw[1..-3].each_byte do |b|
            crc = ((crc >> 8) & 0xff) ^ CRC_LOOKUP[(crc ^ b) & 0xff]

        swapped = ((crc << 8) | (crc >> 8)) & 0xFFFF
        swapped & 0x7F7F


    CRC_LOOKUP = [
        0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
        0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
        0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
        0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
        0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
        0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
        0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
        0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
        0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
        0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
        0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
        0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
        0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
        0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
        0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
        0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
        0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
        0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
        0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
        0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
        0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
        0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
        0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
        0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
        0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
        0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
        0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
        0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
        0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
        0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
        0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
        0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040


Logging Script

The logging script runs on the skynet.i3detroit.local server in the space. It can be found at /home/ted/omnimeter/omnimeter_logger.rb and works like so:

  1. Script reads data from the meter
  2. Script parses data and writes a new document to the local CouchDB database at http://localhost:5984/power_meter
  3. The local CouchDB continually replicates itself to the cloud at: https://i3detroit.iriscouch.com:6984/power_meter
  4. Profit

The Database

The CouchDB database is world-readable at: https://i3detroit.iriscouch.com:6984/power_meter

Here are some example queries: