Power Meter

From i3Detroit
Revision as of 22:36, 31 May 2013 by Kc8nod (Talk | contribs)

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.

Contents

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 10.13.0.20. 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
    end



    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')

        true
    end


    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)
        end

        hsh
    end


    def inspect
        self.get_hash.inspect
    end


    def valid?
        @crc == self.calc_crc16
    end


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

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

    private

    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
    ]

end

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: