CharlesGodwin
Posts: 45
Joined: Wed Aug 22, 2012 9:57 pm
Location: Ottawa Canada

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Feb 13, 2017 6:45 pm

RDK wrote:Guys.......I think I have it all working now. I have had access to the Magnum setup for the past three weeks and now I believe I have decoded both the Battery and Inverter data records that are being sent to the Magnum website via the Internet from the Mag Web unit.

I will be returning to France tomorrow and when I get back I will set down and document my findings. Are you still interested?...RDK
I too am interested.

User avatar
RDK
Posts: 182
Joined: Wed Aug 13, 2014 10:19 am
Location: Wyoming and France

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Feb 13, 2017 10:13 pm

Will.....I'm between here and there right now. However, I need to warn you that my application does not directly use the Magnum Inverter hardware or a RS485 device. My method is to place a Raspberry Pi based bridge between the Mag Web device and my internet router, and then to eavesdrop on the internet communications between the Mag Web and the Magnum website. Not sure this will be useful to you, but I'll post something once I get my feet and brain back on the ground in France....Rob

CharlesGodwin
Posts: 45
Joined: Wed Aug 22, 2012 9:57 pm
Location: Ottawa Canada

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Feb 13, 2017 10:16 pm

That interests me.

DMackG
Posts: 4
Joined: Tue Jun 16, 2015 7:29 pm

Re: Using a Pi to Log Data in a Magnum Energy System

Thu Feb 16, 2017 5:53 pm

Very Interested! :D

User avatar
RDK
Posts: 182
Joined: Wed Aug 13, 2014 10:19 am
Location: Wyoming and France

Re: Using a Pi to Log Data in a Magnum Energy System

Tue Feb 28, 2017 4:27 pm

Guys......OK, this is what I know and/or think I know.

Using the protocol document referenced earlier in this thread I have been able to find and decode the battery and inverter data which is displayed on the Magnum web site. That is the site which is receiving the data stream from the Mag Web unit.

The Mag Web unit sends data every 30 seconds. That transmission consists of at least two binary data streams wrapped in HTML/TCPIP wrappers. Using the TCPFLOW function I am able to capture these records and convert the binaries to strings of hexadecimal numbers. In that form they are records of 40 and 87 hex characters respectively.

Using the protocol document I have been able to extract the following relationships between the hex characters in those records and the data which is displayed on the Magnum website. Note indexing starts at 0.

The 40 character record contains the State of Charge and the battery data. This data record starts with this hex string: 003c3c in positions 0-2.
  • 1. State of Charge = 8 bit, hex character 24
    2. Battery DC volts = 16 bit, hex characters 25 and 26. Divide by 100
    3. Battery DC amps = 16 bit, signed hex characters 27 and 28. Divide by 10.
    Note working with signed hex numbers is tricky
    4. Minimum Battery Volts = 16 bit, hex characters 29 and 30. Divide by 100
    5. Maximum Battery Volts = 16 bit, hex characters 31 and 32. Divide by 100
    6. Battery Amp Hours = 16 bit, signed hex characters 33 and 34
The 87 character record caused me some grief as, somewhat randomly, this record was broken into two parts, whose lengths add to 87. Just a program trap issue!!

Anyway, the Inverter record will have "d4" and "7f" for hex characters 10 and 11, respectively. If not, ignore the record.

The data extraction follows, again note indexing starts at 0:
  • 7. Inverter Status = 8 bit, hex character 27
    8. Inverter Fail Code = 8 bit, hex character 28
    9. DC Volts = 16 bit, hex characters 29 and 30. Divide by 10
    10. DC Amps = 16 bit, hex characters 31 and 32
    11. AC Volts Out = 8 bit, hex character 33
    12. AC Volts In = 8 bit, hex character 34
    13. Inverter LED = 8 bit, hex character 35
    14. Charger LED = 8 bit, hex character 36
    15. Inverter Revision = 8 bit, hex character 37. Divide by 10
    16. Battery Temperature = 8 bit, hex character 38
    17. Transformer Temperature = 8 bit, hex character 39
    18. FET Temperature = 8 bit, hex character 40
    19. Stack Mode = 8 bit, hex character 41
    20. Inverter Mode = 8 bit, hex character 42
    21. AC Input Amps = 8 bit, hex character 43
    22. AC Output Amps = 8 bit, hex character 44
    23. AC Frequency = 16 bit, hex characters 45 and 46. Divide by 10
I know of two outstanding issues and they seem to involve the Amps when the system is being charged (generator and maybe solar?). I will investigate these the next time I'm at the cabin.

My Raspberry Pi setup is as follows:
  • 1. It can be most any favor of Raspberry Pi. I have used both a B+ and V2.
    2. The OS needs to be Jessie, or one that is compatible with TCPFLOW. Wheezy does not support TCPFLOW.
    3. The Pi is set up as a "bridge" using the on-board Ethernet as eth0 and an Ethernet dongle at eth1. As far as I know you can not set up a "bridge" using a WiFi instead of eth0.
    4. eth0 is connected to my router network and eth1 to the Mag Web unit.
In this way, the records pass through the Pi and proceed on the the Magnum server. But, the contents is recorded locally for inspection and processing.

Here is a tester Python program to collect and display the records coming from the Mag Web unit.

Note, you will need to specify the IP address for your unit in the "tcpflow = subprocess" line. Normally the Mag Web unit uses DHCP to get that address. I have not looked into setting it up for a static address.

Also note that the very first record is always messed up. I suspect it is a partial transmission??
====================================================================

#!/usr/bin/env python
import subprocess
import time
import sys
# binascii is a set of routines for processing binary data to HEX and Decimal
import binascii
nul_f = open('/dev/null', 'w')
print "Setting up PIPE and waiting for Magnum records\n"
# try loop for clean breakout with cntl-C
try:
# following code is for running the listener script inside of this pgm
tcpflow = subprocess.Popen(['/usr/bin/sudo /usr/local/bin/tcpflow -B -C -0 -i eth1 -o /mnt/usbdrive/TCPFLOW/ host 192.168.xx.yy'],
stdout=subprocess.PIPE, shell=True,
stderr=nul_f,)
i=0
# read data being piped from TCPFLOW script
while 1:
i=i+1
gline = tcpflow.stdout.readline()
strLenGline = str(len(gline))
print "\r\nRecord Length is " + strLenGline
# this line converts the binary record from the pipe into a Hex string
strHexGline = str(binascii.hexlify(gline))
print "\r\nMagnum Record is " + strHexGline

except KeyboardInterrupt:
print('\r\nDone. ' + str(i) + " Records Read")
# clean up pipe stuff
tcpflow.terminate()
tcpflow.kill()

====================================================

Have fun and let me know if you find errors or make additional discoveries.......RDK

trumps2000
Posts: 9
Joined: Wed Aug 29, 2012 1:05 am

Re: Using a Pi to Log Data in a Magnum Energy System

Tue Mar 21, 2017 4:12 am

Hi guys, A few months ago I setup my Magnum and luckily stumbled on this post. I fought with the original code a bit to get this to work for me as I don't have an AGS. Big props to the original poster getting this working! I rewrote MagPy.py a bit to make it more flexible and robust, so I thought one good turn deserves another.

My setup is a Pi2 with an RS485 hat using the GPIO UART. Using a splitter I have it attached to the MS4024PAE and ME-RTR.

The code isn't polished, and I've commented out the AGS reporting but left the communication intact. It determines what equipment you have and displays the revision up top. Then it only prints out info if the equipment is present, so it won't use the inverter voltage if you have the more accurate battery monitor. I also noticed one of the current readings in the documentation isn't input current, but "AC" equivalent current out of the battery to support the load. I've fixed this in the code.


This is the info I get out of the terminal:
~$ python MagPy.py -p /dev/ttyAMA0

MagPy Magnum Energy MagnaSine Data Protocol Decoder

Debug level : 0
serial port : /dev/ttyAMA0


Equip List
MS4024PAE(5.1)
ME-RTR(3.2)
BMK(1.0)

Live Data
Clock 23:57

Inverter Mode: Float Charging
Fault: No Faults

Output AC Volts: 117 Vac
Output AC Amps: 0 Amps
Output AC Watts: 0 Watts
Input AC Amps: 0 Amps
Line Frequency 60.1 Hz

Temperature Battery 57 F
Temperature Transformer 78 F
Temperature FETs 73 F

Fault Status: No Faults
Battery State of Charge: 99 %
Battery Volts DC: 27.08 Vdc
Amps DC: 0 Amps
Power DC: 0.0 Watts
Battery Max: 30.14 Vdc
Battery Min: 20.54 Vdc
Battery Amp Hour Net 0 AmpHr
Battery Amp Hour Trip 783 AmpHr
Battery Amp Hour Total 700 AmpHr

Code: Select all

#!/usr/bin/env python

'''
MagPy - an application to interface with Magnum inverters
The application decodes the MagNet data protocol to display
data for all devices connected and active.

This program was initially inspired by Chris (aka user cpfl) Midnightsolar forum.

Created     13 Jun 2015
Modified    19 Jun 2105

@author: Paul Alting van Geusau

---------------------------------------------------------------------------------------------------------------------------
    Data capture from actual MagNet with MS4448PAE, RTR, AGS and BMK devices:
                                                                                                                    Bytes  Total   Packet Marker
    Inverter            40 00 01 F8 00 04 77 00 01 00 33 17 2E 22 73 01 00 01 02 58 00 FF                            22
    Remote_B+A0+A1      00 05 50 94 64 1E 16 08 00 D5 9B 84 07 19 17 26 14 00 73 00 A0 A1 02 35 FC 00 7D             27    = 49    pos[41] = 0xA0

    Inverter            40 00 01 F8 00 04 77 00 01 00 33 17 2E 22 73 01 00 01 02 58 00 FE                            22
    Remote_B+A1+A2      00 05 50 94 64 1E 16 08 00 D5 9B 84 07 19 20 20 90 3C 3C 78 A1 A2 01 00 00 00 00             27    = 49    pos[41] = 0xA1

    Inverter            40 00 01 F8 00 04 77 00 01 00 33 17 2E 22 73 01 00 01 02 58 00 FF                            22
    Remote_B+A2+RTR     00 05 50 94 64 1E 16 08 00 D5 9B 84 07 19 00 64 00 3C 0A 3C A2 91 01                         23    = 45    pos[41] = 0xA2

    Inverter            40 00 01 F8 00 04 77 00 01 00 33 17 2E 22 73 01 00 01 02 58 00 FF                            22
    Remote_B+A3         00 05 50 94 64 1E 16 08 00 D5 9B 84 07 19 50 28 00 20 0A 00 A3                               21    = 43    pos[41] = 0xA3

    Inverter            40 00 01 F8 00 04 77 00 01 00 33 17 2E 22 73 01 00 01 02 58 00 FE                            22
    Remote_B+A4         00 05 50 94 64 1E 16 08 00 D5 9B 84 07 19 3C 3C 00 00 00 00 A4                               21    = 43    pos[41] = 0xA4

    Inverter            40 00 01 F8 00 04 77 00 01 00 33 17 2E 22 73 01 00 01 02 58 00 FF                            22
    Remote_B+Z0         00 05 50 94 64 1E 16 08 00 D5 9B 84 07 19 17 26 00 00 00 00 00                               21    = 43    pos[41] = 0x00

    Inverter            40 00 01 F8 00 04 77 00 01 00 33 17 2E 22 73 01 00 01 02 58 00 FF                                                       22
    Remote_B+80+81 00 05 50 94 64 1E 16 08 00 D5 9B 84 07 19 17 26 00 00 28 00 80 81 61 13 AF FF D2 12 4C 18 BB FF E9 FF FF 00 5E 0A 01 39 = 61 pos[42] = 0x80 
---------------------------------------------------------------------------------------------------------------------------------------------
    Byte position       01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
    Buffer position     00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

'''
import serial
import time
import array
import argparse

class inverter_proto():
    """definition of inverter packet """
#    def __init__(self):
    status_descript = "NA"      #       status descriptive word:
    fault_descript = "NA"       #       fault descriptive word:
    model_descript = "NA"
    status_code = '0x00'        # 0     packet_buffer[0]
    fault_code = '0x00'         # 1     packet_buffer[1]
    volts_dc = 0.0              # 2     packet_buffer[2] HIGH BYTE
                                # 3     packet_buffer[3] LOW BYTE
    amps_dc = 0.0               # 4     packet_buffer[4] HIGH BYTE
                                # 5     packet_buffer[5] LOW BYTE
    volts_ac_out = 0            # 6     packet_buffer[6]
    volts_ac_in = 0             # 7     packet_buffer[7]
    LED_inverter = 0            # 8     packet_buffer[8]
    LED_charger = 0             # 9     packet_buffer[9]
    revision = 0.0              # 10    packet_buffer[10]
    temp_battery = 0            # 11    packet_buffer[11]
    temp_transformer = 0        # 12    packet_buffer[12]
    temp_FET = 0                # 13    packet_buffer[13]
    model_id = 0                # 14    packet_buffer[14]
    stack_mode = "NA"           # 15    packet_buffer[15]
    amps_ac_in = 0              # 16    packet_buffer[16]
    amps_ac_out = 0             # 17    packet_buffer[17]
    frequency_ac_out = 0.0      # 18    packet_buffer[18] HIGH BYTE
                                # 19    packet_buffer[19] LOW BYTE
#    ALWAYS 0                   # 20    packet_buffer[20]
#    ALWAYS 0xFF                # 21    packet_buffer[21]
#    END
#
#    inverter decode function:
    def decode(self, packet_buffer):
        global system_bus_volts

        self.status_code = hex(ord(packet_buffer[0]))
        if (self.status_code == '0x0') :
            self.status_descript = "Charger Standby"

        if (self.status_code == '0x1') :
            self.status_descript = "Equalizing"

        if (self.status_code == '0x2') :
            self.status_descript = "Float Charging"

        if (self.status_code == '0x4') :
            self.status_descript = "Absorb Charging"

        if (self.status_code == '0x8') :
            self.status_descript = "Bulk Charging"

        if (self.status_code == '0x9') :
            self.status_descript = "Battery Saver"

        if (self.status_code == '0x10') :
            self.status_descript = "Charge"

        if (self.status_code == '0x20') :
            self.status_descript = "Off"

        if (self.status_code == '0x40') :
            self.status_descript = "Inverting"

        if (self.status_code == '0x50') :
            self.status_descript = "Standby"

        if (self.status_code == '0x60') :
            self.status_descript = "Searching"

        self.fault_code = hex(ord(packet_buffer[1]))
        if (self.fault_code == '0x0') :
            self.fault_descript = "No Faults"

        if (self.fault_code == '0x1') :
            self.fault_descript = "Stuck Relay"
    
        if (self.fault_code == '0x2') :
            self.fault_descript = "DC Overload"
    
        if (self.fault_code == '0x3') :
            self.fault_descript = "AC Overload"
    
        if (self.fault_code == '0x4') :
            self.fault_descript = "Dead Battery"
    
        if (self.fault_code == '0x5') :
            self.fault_descript = "AC Backfeed"
    
        if (self.fault_code == '0x8') :
            self.fault_descript = "Low Battery Cutout"
    
        if (self.fault_code == '0x9') :
            self.fault_descript = "High Battery Cutout"
    
        if (self.fault_code == '0xA') :
            self.fault_descript = "High AC Output Volts"
    
        if (self.fault_code == '0x10') :
            self.fault_descript = "Bad FET Bridge"
    
        if (self.fault_code == '0x12') :
            self.fault_descript = "FETs Over Temperature"
    
        if (self.fault_code == '0x13') :
            self.fault_descript = "FETs Over Temperature Quick"
    
        if (self.fault_code == '0x14') :
            self.fault_descript = "Internal Fault #4"
    
        if (self.fault_code == '0x16') :
            self.fault_descript = "Stacker Mode Fault"
    
        if (self.fault_code == '0x18') :
            self.fault_descript = "Stacker Sync Clock Out of Phase"
    
        if (self.fault_code == '0x17') :
            self.fault_descript = "Stacker Sync Clock Lost"
    
        if (self.fault_code == '0x19') :
            self.fault_descript = "Stacker AC Phase Fault"
    
        if (self.fault_code == '0x20') :
            self.fault_descript = "Over temperature Shutdown"
    
        if (self.fault_code == '0x21') :
            self.fault_descript = "Transfer Relay Fault"
    
        if (self.fault_code == '0x80') :
            self.fault_descript = "Charger Fault"
    
        if (self.fault_code == '0x81') :
            self.fault_descript = "Battery Temperature High"
    
        if (self.fault_code == '0x90') :
            self.fault_descript = "Transformer Temperature Cutout Open"
    
        if (self.fault_code == '0x91') :
            self.fault_descript = "AC Breaker CB3 Tripped"
    
        self.volts_dc = ((ord(packet_buffer[2]) * 256) + ord(packet_buffer[3])) / 10.0
        self.amps_dc = ((ord(packet_buffer[4]) * 256) + ord(packet_buffer[5]))
        self.volts_ac_out = ord(packet_buffer[6])
        self.volts_ac_in = ord(packet_buffer[7])
        self.revision = ord(packet_buffer[10]) / 10.0
        self.temp_battery = ord(packet_buffer[11])
        self.temp_transformer = ord(packet_buffer[12])
        self.temp_FET = ord(packet_buffer[13])
        self.model_id = ord(packet_buffer[14])
        if (self.model_id < 53):
            system_bus_volts = 12
            
        if (self.model_id > 47):
            system_bus_volts = 24
            
        if (self.model_id > 107):
            system_bus_volts = 48

        if (self.model_id == 6) :
            self.model_descript = "MM612"
        elif (self.model_id == 7) :
            self.model_descript = "MM612-AE"
        elif (self.model_id == 8) :
            self.model_descript = "MM1212" 
        elif (self.model_id == 9) :
            self.model_descript = "MMS1012" 
        elif (self.model_id == 10) :
            self.model_descript = "MM1012E" 
        elif (self.model_id == 11) :
            self.model_descript = "MM1512"
        elif (self.model_id == 15) :
            self.model_descript = "ME1512"
        elif (self.model_id == 20) :
            self.model_descript = "ME2012"
        elif (self.model_id == 25) :
            self.model_descript = "ME2512"
        elif (self.model_id == 30) :
            self.model_descript = "ME3112"
        elif (self.model_id == 35) :
            self.model_descript = "MS2012"
        elif (self.model_id == 40) :
            self.model_descript = "MS2012E"
        elif (self.model_id == 45) :
            self.model_descript = "MS2812"
        elif (self.model_id == 47) :
            self.model_descript = "MS2712E"
        elif (self.model_id == 53) :
            self.model_descript = "MM1324E"
        elif (self.model_id == 54) :
            self.model_descript = "MM1524"
        elif (self.model_id == 55) :
            self.model_descript = "RD1824"
        elif (self.model_id == 59) :
            self.model_descript = "RD2624E"
        elif (self.model_id == 63) :
            self.model_descript = "RD2824"
        elif (self.model_id == 69) :
            self.model_descript = "RD4024E"
        elif (self.model_id == 74) :
            self.model_descript = "RD3924"
        elif (self.model_id == 90) :
            self.model_descript = "MS4124E"
        elif (self.model_id == 91) :
            self.model_descript = "MS2024"
        elif (self.model_id == 105) :
            self.model_descript = "MS4024"
        elif (self.model_id == 106) :
            self.model_descript = "MS4024AE"
        elif (self.model_id == 107) :
            self.model_descript = "MS4024PAE"
        elif (self.model_id == 111) :
            self.model_descript = "MS4448AE"
        elif (self.model_id == 112) :
            self.model_descript = "MS3748AEJ"
        elif (self.model_id == 115) :
            self.model_descript = "MS4448PAE"
        elif (self.model_id == 116) :
            self.model_descript = "MS3748PAEJ"
        else :
            self.model_descript = "UNKNOWN"
        
        self.stack_mode_code = hex(ord(packet_buffer[15]))
        if (self.stack_mode_code == '0x0') :
            self.stack_mode = "Standalone Unit"

        if (self.stack_mode_code == '0x1') :
            self.stack_mode = "Master in Parallel Stack"

        if (self.stack_mode_code == '0x2') :
            self.stack_mode = "Slave in Parallel Stack"

        if (self.stack_mode_code == '0x4') :
            self.stack_mode = "Master in Series Stack"

        if (self.stack_mode_code == '0x8') :
            self.stack_mode = "Slave in Series Stack"
            
        self.amps_ac_in = ord(packet_buffer[16])
        self.amps_ac_out = ord(packet_buffer[17])
        self.frequency_ac_out = ((ord(packet_buffer[18]) * 256) + ord(packet_buffer[19])) / 10.0

        
class remote_base_proto():
    """definition of remote packet """
#    def __init__(self):
    status_code = '0x00'        # 00    ord(packet_buffer[22])
    search_watts = 0.0          # 01    ord(packet_buffer[23])
    battery_size = 0.0          # 02    ord(packet_buffer[24])
    battery_type = 0.0          # 03    ord(packet_buffer[25])
    charger_amps = 0.0          # 04    ord(packet_buffer[26])
    shore_ac_amps = 0.0         # 05    ord(packet_buffer[27])
    revision = 0.0              # 06    ord(packet_buffer[28])
    parallel_threshold = 0      # 07    ord(packet_buffer[29]) LOW NIBBLE
    force_charge = '0x00'       # 07    ord(packet_buffer[29]) HIGH NIBBLE
    genstart_auto = '0x00'      # 08    ord(packet_buffer[30])
    battery_low_trip = 0.0      # 09    ord(packet_buffer[31])
    volts_ac_trip = 0           # 10    ord(packet_buffer[32])
    float_volts = 0.0           # 11    ord(packet_buffer[33])
    equalise_volts = 0.0        # 12    ord(packet_buffer[34])
    absorb_time = 0.0           # 13    ord(packet_buffer[35]
    absorb_volts = 14.4
    status_descript = "NA"
    battery_type_descript = "NA"
    force_charge_descript = "NA"
    genstart_auto_descript = "NA"
#    END
#
#    remote_base decode function:
    def decode(self, packet_buffer):
        global system_bus_volts
        if (system_bus_volts == 24):
            self.absorb_volts *= 2

        if (system_bus_volts == 48):
            self.absorb_volts *= 4

        self.status_code = ord(packet_buffer[22])
        if (self.status_code == '0x0') :
            self.status_descript = "Remote Command Clear"

        self.search_watts = ord(packet_buffer[23])
        self.battery_size = ord(packet_buffer[24]) * 10
        self.battery_type = ord(packet_buffer[25])
        if (self.battery_type == 2):
            self.battery_type_descript = "Gel"

        if (self.battery_type == 4):
            self.battery_type_descript = "Flooded"

        if (self.battery_type == 8):
            self.battery_type_descript = "AGM"

        if (self.battery_type == 10):
            self.battery_type_descript = "AGM2"

        if (self.battery_type > 100):
            self.battery_type_descript = "Custom"
            if (system_bus_volts == 12):
                self.absorb_volts = self.battery_type / 10.0
                
            if (system_bus_volts == 24):
                self.absorb_volts = self.battery_type * 2 / 10.0

            if (system_bus_volts == 48):
                self.absorb_volts = self.battery_type * 4 / 10.0

        self.charger_amps = ord(packet_buffer[26])  # % of charger capacity ?
        self.shore_ac_amps = ord(packet_buffer[27])
        self.revision = ord(packet_buffer[28]) / 10.0
        self.parallel_threshold = (ord(packet_buffer[29]) & 0x0f) * 10
        self.force_charge = hex(ord(packet_buffer[29]) & 0xf0)
        if (self.force_charge == '0x10'):
            self.force_charge_descript = "Disable Refloat"

        if (self.force_charge == '0x20'):
            self.force_charge_descript = "Force Silent"

        if (self.force_charge == '0x40'):
            self.force_charge_descript = "Force Float"

        if (self.force_charge == '0x80'):
            self.force_charge_descript = "Force Bulk"

        self.genstart_auto = ord(packet_buffer[30])
        if (self.genstart_auto == 0):
            self.genstart_auto_descript = "Off"

        if (self.genstart_auto == 1):
            self.genstart_auto_descript = "Enable"

        if (self.genstart_auto == 2):
            self.genstart_auto_descript = "Test"

        if (self.genstart_auto == 4):
            self.genstart_auto_descript = "Enable with Quiet Time"

        if (self.genstart_auto == 5):
            self.genstart_auto_descript = "On"

        if (system_bus_volts == 12):
            self.battery_low_trip = ord(packet_buffer[31]) / 10.0

        if (system_bus_volts == 24):
            self.battery_low_trip = ord(packet_buffer[31]) / 10.0

        if (system_bus_volts == 48):
            self.battery_low_trip = ord(packet_buffer[31]) * 2 / 10.0

        self.volts_ac_trip = ord(packet_buffer[32])
        if (self.volts_ac_trip == 110):
            self.volts_ac_trip = 60

        if (self.volts_ac_trip == 122):
            self.volts_ac_trip = 65

        if (self.volts_ac_trip == 135):
            self.volts_ac_trip = 70

        if (self.volts_ac_trip == 145):
            self.volts_ac_trip = 75

        if (self.volts_ac_trip == 155):
            self.volts_ac_trip = 80

        if (self.volts_ac_trip == 165):
            self.volts_ac_trip = 85

        if (self.volts_ac_trip == 175):
            self.volts_ac_trip = 90

        if (self.volts_ac_trip == 182):
            self.volts_ac_trip = 95

        if (self.volts_ac_trip == 190):
            self.volts_ac_trip = 90

        if (system_bus_volts == 12):
            self.float_volts = ord(packet_buffer[33]) / 10.0

        if (system_bus_volts == 24):
            self.float_volts = ord(packet_buffer[33]) * 2 / 10.0

        if (system_bus_volts == 48):
            self.float_volts = ord(packet_buffer[33]) * 4 / 10.0

        if (system_bus_volts == 12):    # value added to absorbtion volts to give equalise volts range 0 - 2 volts
            self.equalise_volts = self.absorb_volts + (ord(packet_buffer[34]) / 10.0)

        if (system_bus_volts == 24):    # value added to absorbtion volts to give equalise volts range 0 - 2 volts
            self.equalise_volts = self.absorb_volts + (ord(packet_buffer[34]) * 2 / 10.0)

        if (system_bus_volts == 48):    # value added to absorbtion volts to give equalise volts range 0 - 2 volts
            self.equalise_volts = self.absorb_volts + (ord(packet_buffer[34]) * 4 / 10.0)
            
        self.absorb_time = ord(packet_buffer[35]) / 10.0                 # as decimal hours, 2.5 is 2 hours 30 minutes


class remote_A0_proto():
    """definition of remote A0 packet """
#    def __init__(self):
    remote_hours = 0.0          # 14    ord(packet_buffer[36])
    remote_min = 0.0            # 15    ord(packet_buffer[37])
    ags_run_time = 0.0          # 16    ord(packet_buffer[38]
    ags_start_temp = 0.0        # 17    ord(packet_buffer[39]
    ags_start_volts_dc = 0.0    # 18    ord(packet_buffer[40]
    ags_quite_hours = 0         # 19    ord(packet_buffer[41]
#    FOOTER 0xA0                # 20    ord(packet_buffer[42]
#    END
#
#    remoteA0_agsA1 decode function:
    def decode(self, packet_buffer):
        global system_bus_volts

        self.remote_hours = ord(packet_buffer[36])       # duplicate from BMK class
        self.remote_min = ord(packet_buffer[37])         # duplicate from BMK class
        self.ags_run_time = ord(packet_buffer[38]) / 10.0
        self.ags_start_temp = ord(packet_buffer[39])
        if (system_bus_volts == 12):
            self.ags_start_volts_dc = ord(packet_buffer[40]) / 10.0

        if (system_bus_volts == 24):
            self.ags_start_volts_dc = ord(packet_buffer[40]) * 2 / 10.0

        if (system_bus_volts == 48):
            self.ags_start_volts_dc = ord(packet_buffer[40]) * 4 / 10.0

        self.ags_quite_hours = ord(packet_buffer[41])
        if (self.ags_quite_hours == 0):
            self.ags_quiet_hours_descrip = "Off"

        if (self.ags_quite_hours == 1):
            self.ags_quiet_hours_descrip = "21h - 07h"

        if (self.ags_quite_hours == 2):
            self.ags_quiet_hours_descrip = "21h - 09h"

        if (self.ags_quite_hours == 3):
            self.ags_quiet_hours_descrip = "21h - 09h"

        if (self.ags_quite_hours == 4):
            self.ags_quiet_hours_descrip = "22h - 08h"

        if (self.ags_quite_hours == 5):
            self.ags_quiet_hours_descrip = "23h - 08h"


class AGS1_proto():
    """definition of remote A0 packet """
#    HEADER 0xA1                # 21    ord(packet_buffer[43]
    ags_status = 0              # 22    ord(packet_buffer[44]
    ags_revision = 0.0          # 23    ord(packet_buffer[45]
    ags_temperature = 0.0       # 24    ord(packet_buffer[46]
    ags_gen_runtime = 0.0       # 25    ord(packet_buffer[47]
    ags_volts_dc = 0.0          # 26    ord(packet_buffer[48]
    ags_quiet_hours_descrip = "NA"
    ags_status_descript = "NA"
#    END
#
#    remoteA0_agsA1 decode function:
    def decode(self, packet_buffer):
        global system_bus_volts

        self.ags_status = ord(packet_buffer[44])
        if (self.ags_status == 0):
            self.ags_status_descript = "Non Valid"

        if (self.ags_status == 1):
            self.ags_status_descript = "Off"

        if (self.ags_status == 2):
            self.ags_status_descript = "Ready"

        if (self.ags_status == 3):
            self.ags_status_descript = "Manual Run"

        if (self.ags_status == 4):
            self.ags_status_descript = "Inverter in Charge Mode"

        if (self.ags_status == 5):
            self.ags_status_descript = "In Quiet Time"

        if (self.ags_status == 6):
            self.ags_status_descript = "Start in Test Mode"

        if (self.ags_status == 7):
            self.ags_status_descript = "Start on Temperature"

        if (self.ags_status == 8):
            self.ags_status_descript = "Start on Voltage"

        if (self.ags_status == 9):
            self.ags_status_descript = "Fault Start on Test"

        if (self.ags_status == 10):
            self.ags_status_descript = "Fault Start on Temperature"

        if (self.ags_status == 11):
            self.ags_status_descript = "Fault Start on Voltage"

        if (self.ags_status == 12):
            self.ags_status_descript = "Start Time of Day"

        if (self.ags_status == 13):
            self.ags_status_descript = "Start State of Charge"

        if (self.ags_status == 14):
            self.ags_status_descript = "Start Exercise"

        if (self.ags_status == 15):
            self.ags_status_descript = "Fault Start Time of Day"

        if (self.ags_status == 16):
            self.ags_status_descript = "Fault Start State of Charge"

        if (self.ags_status == 17):
            self.ags_status_descript = "Fault Start Exercise"

        if (self.ags_status == 18):
            self.ags_status_descript = "Start on Amps"

        if (self.ags_status == 19):
            self.ags_status_descript = "Start on Topoff"

        if (self.ags_status == 20):
            self.ags_status_descript = "Non Valid"

        if (self.ags_status == 21):
            self.ags_status_descript = "Fault Start on Amps"

        if (self.ags_status == 22):
            self.ags_status_descript = "Fault Start on Topoff"

        if (self.ags_status == 23):
            self.ags_status_descript = "Non Valid"

        if (self.ags_status == 24):
            self.ags_status_descript = "Fault Maximum Run"

        if (self.ags_status == 25):
            self.ags_status_descript = "Gen Run Fault"

        if (self.ags_status == 26):
            self.ags_status_descript = "Generator in Warm Up"

        if (self.ags_status == 27):
            self.ags_status_descript = "Generator in Cool Down"

        self.ags_revision = ord(packet_buffer[45]) / 10.0
        self.ags_temperature = ord(packet_buffer[46])
        self.ags_run_time = ord(packet_buffer[47]) / 10.0

        if (system_bus_volts == 12):
            self.ags_volts_dc = ord(packet_buffer[48]) / 10.0

        if (system_bus_volts == 24):
            self.ags_volts_dc = ord(packet_buffer[48]) * 2 / 10.0

        if (system_bus_volts == 48):
            self.ags_volts_dc = ord(packet_buffer[48]) * 4 / 10.0



class remote_A1_proto():
    """definition of remote A1 packet """
#    def __init__(self):
    ags_start_time = 0.0        # 14    ord(packet_buffer[36]
    ags_stop_time = 0.0         # 15    ord(packet_buffer[37]
    ags_volts_dc_stop = 0.0     # 16    ord(packet_buffer[38]
    ags_start_delay = 0.0       # 17    ord(packet_buffer[39]
    ags_stop_delay = 0.0        # 18    ord(packet_buffer[40]
    ags_max_run_time = 0.0      # 19    ord(packet_buffer[41]
#    FOOTER 0xA1                # 20    ord(packet_buffer[42]#
#    remoteA1_agsA2 decode function:
    def decode(self, packet_buffer):
        global system_bus_volts

        self.ags_start_time = ord(packet_buffer[36]) * 0.25
        self.ags_stop_time = ord(packet_buffer[37]) * 0.25

        if (ord(packet_buffer[36]) == ord(packet_buffer[37])):
            self.ags_start_stop_enable = "Disabled"
        else:
            self.ags_start_stop_enable = "Enabled"

        if (system_bus_volts == 12):
            self.ags_volts_dc_stop = ord(packet_buffer[38]) / 10.0

        if (system_bus_volts == 24):
            self.ags_volts_dc_stop = ord(packet_buffer[38]) * 2 / 10.0

        if (system_bus_volts == 48):
            self.ags_volts_dc_stop = ord(packet_buffer[38]) * 4 / 10.0

        if (ord(packet_buffer[39]) & 0x80):                                  # check for minutes selection as MSB:
            self.ags_start_delay = (ord(packet_buffer[39]) & 0x7f) * 60      # strip MSB and store as seconds
        else:
            self.ags_start_delay = ord(packet_buffer[39])                    # store seconds

        if (ord(packet_buffer[40]) & 0x80):                                  # check for minutes selection as MSB:
            self.ags_stop_delay = (ord(packet_buffer[40]) & 0x7f) * 60       # strip MSB and store as seconds
        else:
            self.ags_stop_delay = ord(packet_buffer[40])                     # store seconds

        self.ags_max_run_time = ord(packet_buffer[41]) / 10.0



class AGS2_proto():
    """definition of remote A1 packet """
#    def __init__(self):
#    HEADER 0xA2                # 21    ord(packet_buffer[43]
    ags_days_last_gen_run = 0   # 22    ord(packet_buffer[44]
#    ALWAYS 0                   # 23    ord(packet_buffer[45]
#    ALWAYS 0                   # 24    ord(packet_buffer[46]
#    ALWAYS 0                   # 25    ord(packet_buffer[47]
#    ALWAYS 0                   # 26    ord(packet_buffer[48]
    ags_start_stop_enable = "NA"
#    END
#
#    remoteA1_agsA2 decode function:
    def decode(self, packet_buffer):
        global system_bus_volts
        self.ags_days_last_gen_run = ord(packet_buffer[44])


class remote_A2_proto():
    """definition of remote A2 packet """
#    def __init__(self):
    ags_soc_start = 0           # 14    ord(packet_buffer[36]
    ags_soc_stop = 0            # 15    ord(packet_buffer[37]
    ags_amps_start = 0          # 16    ord(packet_buffer[38]
    ags_amps_start_delay = 0    # 17    ord(packet_buffer[39]
    ags_amps_stop = 0           # 18    ord(packet_buffer[40]
    ags_amps_stop_delay = 0     # 19    ord(packet_buffer[41]
#    FOOTER 0xA2                # 20    ord(packet_buffer[42]
#    END
#
#    remote_A2 decode function:
    def decode(self, packet_buffer):
        global system_bus_volts

        self.ags_soc_start = ord(packet_buffer[36])
        self.ags_soc_stop = ord(packet_buffer[37])
        self.ags_amps_start = ord(packet_buffer[38])

        if (ord(packet_buffer[39]) & 0x80):                                          # check for minutes selection as MSB:
            self.ags_amps_start_delay = (ord(packet_buffer[39]) & 0x7f) * 60         # strip MSB and store as seconds
        else:
            self.ags_amps_start_delay = ord(packet_buffer[39])                       # store seconds

        self.ags_amps_stop = ord(packet_buffer[40])

        if (ord(packet_buffer[41]) & 0x80):                                          # check for minutes selection as MSB:
            self.ags_amps_stop_delay = (ord(packet_buffer[41]) & 0x7f) * 60          # strip MSB and store as seconds
        else:
            self.ags_amps_stop_delay = ord(packet_buffer[41])                        # store seconds

class RTR_proto():
    """definition of remote A2 packet """
#    def __init__(self):
#    HEADER 0x91                # 21    ord(packet_buffer[43]
    rtr_revision = 0            # 22    ord(packet_buffer[44]
#    END
#
#    remoteA2_RTR decode function:
    def decode(self, packet_buffer):

        self.rtr_revision = ord(packet_buffer[44]) / 10.0

class remote_A3_proto():
    """definition of remote A3 packet """
#    def __init__(self):
    ags_quite_time_start = 0.0  # 14    ord(packet_buffer[36]
    ags_quite_time_stop = 0.0   # 15    ord(packet_buffer[37]
    ags_exercise_days = 0       # 16    ord(packet_buffer[38]
    ags_exercise_start_time = 0.0 # 17    ord(packet_buffer[39]
    ags_exercise_run_time = 0   # 18    ord(packet_buffer[40]
    ags_top_off = 0             # 19    ord(packet_buffer[41]
#    FOOTER 0xA3                # 20    ord(packet_buffer[42]
#    END
#
#    remoteA3 decode function:
    def decode(self, packet_buffer):
        global system_bus_volts
        self.ags_quite_time_start = ord(packet_buffer[36]) * 0.25
        self.ags_quite_time_stop = ord(packet_buffer[37]) * 0.25
        self.ags_exercise_days = ord(packet_buffer[38])
        self.ags_exercise_start_time = ord(packet_buffer[39]) * 0.25
        self.ags_exercise_run_time = ord(packet_buffer[40]) / 10
        self.ags_top_off = ord(packet_buffer[41])


class remote_A4_proto():
    """definition of remote A4 packet """
#    def __init__(self):
    ags_warm_up_time = 0        # 14    ord(packet_buffer[36])
    ags_cool_down_time = 0      # 15    ord(packet_buffer[37]
#     ALWAYS 0                  # 16    ord(packet_buffer[38]
#     ALWAYS 0                  # 17    ord(packet_buffer[39]
#     ALWAYS 0                  # 18    ord(packet_buffer[40]
#     ALWAYS 0                  # 19    ord(packet_buffer[41]
#    FOOTER 0xA4                # 20    ord(packet_buffer[42]
#    END
#
#    remoteA4 decode function:
    def decode(self, packet_buffer):
        global system_bus_volts
        
        if (ord(packet_buffer[36]) & 0x80):                                          # check for minutes selection as MSB:
            self.ags_warm_up_time = (ord(packet_buffer[36]) & 0x7f) * 60             # strip MSB and store as seconds
        else:
            self.ags_warm_up_time = ord(packet_buffer[36])                           # store seconds

        if (ord(packet_buffer[37]) & 0x80):                                          # check for minutes selection as MSB:
            self.ags_cool_down_time = (ord(packet_buffer[37]) & 0x7f) * 60           # strip MSB and store as seconds
        else:
            self.ags_cool_down_time = ord(packet_buffer[37])                         # store seconds


class remote_Z0_proto():
    """definition of remote Z0 packet """
#    def __init__(self):
    remote_hours = 0            # 14    ord(packet_buffer[36])
    remote_min = 0              # 15    ord(packet_buffer[37])
#     ALWAYS 0                  # 16    ord(packet_buffer[38])
#     ALWAYS 0                  # 17    ord(packet_buffer[39])
#     ALWAYS 0                  # 18    ord(packet_buffer[40])
#     ALWAYS 0                  # 19    ord(packet_buffer[41])
#     ALWAYS 0                  # 20    ord(packet_buffer[42])
#    END
#
#    remote_Z0 decode function:
    def decode(self, packet_buffer):
        global system_bus_volts
#        nothing to do here, move along:


class remote_BMK_proto():
    """definition of remote BMK packet """
#    def __init__(self):
    remote_hours = ""           # 14    ord(packet_buffer[36]
    remote_min = ""             # 15    ord(packet_buffer[37]
    bmk_battery_efficiency = 0  # 16    ord(packet_buffer[38]
    bmk_resets = 0              # 17    ord(packet_buffer[39]
    bmk_battery_size = 0        # 18    ord(packet_buffer[40]
#    ALWAYS 0                   # 19    ord(packet_buffer[41]
#    FOOTER 0x80                # 20    ord(packet_buffer[42]
#    END
#
#    remote_BMK decode function:
    def decode(self, packet_buffer):
        global system_bus_volts

        self.remote_hours = packet_buffer[36]
        self.remote_min = packet_buffer[37]
        self.bmk_battery_efficiency = ord(packet_buffer[38])
        self.bmk_resets = ord(packet_buffer[39])                     # XXX needs decoding XXX
        self.bmk_battery_size = ord(packet_buffer[40]) * 10


class BMK_proto():
    """definition of remote BMK packet """
#    def __init__(self):
#    HEADER 0x81                # 21    ord(packet_buffer[43]
    bmk_soc = 0                 # 22    ord(packet_buffer[44]
    bmk_volts_dc = 0.0          # 23    ord(packet_buffer[45] HIGH BYTE
                                # 24    ord(packet_buffer[46] LOW  BYTE
    bmk_amps_dc = 0             # 25    ord(packet_buffer[47] HIGH BYTE
                                # 26    ord(packet_buffer[48] LOW  BYTE 
    bmk_volts_min = 0.0         # 27    ord(packet_buffer[49] HIGH BYTE
                                # 28    ord(packet_buffer[50] LOW  BYTE
    bmk_volts_max = 0.0         # 29    ord(packet_buffer[51] HIGH BYTE
                                # 30    ord(packet_buffer[52] LOW  BYTE
    bmk_amp_hour_net = 0.0      # 31    ord(packet_buffer[53] HIGH BYTE
                                # 32    ord(packet_buffer[54] LOW  BYTE
    bmk_amp_hour_trip = 0       # 33    ord(packet_buffer[55] HIGH BYTE
                                # 34    ord(packet_buffer[56] LOW  BYTE
    bmk_amp_hour_total = 0      # 35    ord(packet_buffer[57] HIGH BYTE
                                # 36    ord(packet_buffer[58] LOW  BYTE
    bmk_revision = 0.0          # 37    ord(packet_buffer[59]
    bmk_fault = 0               # 38    ord(packet_buffer[60]
    bmk_fault_descrip = "NA"
#    END
#
#    remote_BMK decode function:
    def decode(self, packet_buffer):
        global system_bus_volts

        self.bmk_battery_soc = ord(packet_buffer[44])
        self.bmk_volts_dc = ((ord(packet_buffer[45]) * 256) + ord(packet_buffer[46])) / 100.0
#        self.bmk_amps_dc = (65535 - ((ord(packet_buffer[47]) * 256) + ord(packet_buffer[48]))) / 10.0
        self.bmk_amps_dc = (ord(packet_buffer[47]) * 256) + ord(packet_buffer[48])
        if (self.bmk_amps_dc > 32768):
            self.bmk_amps_dc = (65535 - self.bmk_amps_dc)
        self.bmk_amps_dc = self.bmk_amps_dc / 10

        self.bmk_volts_min = ((ord(packet_buffer[49]) * 256) + ord(packet_buffer[50])) / 100.0
        self.bmk_volts_max = ((ord(packet_buffer[51]) * 256) + ord(packet_buffer[52])) / 100.0

        self.bmk_amp_hour_net = (ord(packet_buffer[53]) * 256) + ord(packet_buffer[54])
        if (self.bmk_amp_hour_net > 32768):
            self.bmk_amp_hour_net = 65535 - self.bmk_amp_hour_net
            
        self.bmk_amp_hour_trip = ((ord(packet_buffer[55]) * 256) + ord(packet_buffer[56])) / 10
        self.bmk_amp_hour_total = ((ord(packet_buffer[57]) * 256) + ord(packet_buffer[58])) * 100

        self.bmk_revision = ord(packet_buffer[59]) / 10.0
        self.bmk_fault = ord(packet_buffer[60])
        if (self.bmk_fault == 0):
            self.bmk_fault_descrip = "Reserved"

        if (self.bmk_fault == 1):
            self.bmk_fault_descrip = "No Faults"    # documentation says 'Normal', what ever that is:

        if (self.bmk_fault == 2):
            self.bmk_fault_descrip = "Fault Start"



# main application function:
def main():
    inverter = inverter_proto()
    remote_base = remote_base_proto()
    remote_A0 = remote_A0_proto()
    remote_A1 = remote_A1_proto()
    remote_A2 = remote_A2_proto()
    remote_A3 = remote_A3_proto()
    remote_A4 = remote_A4_proto()
    remote_Z0 = remote_Z0_proto()
    remote_BMK = remote_BMK_proto()
    BMK = BMK_proto()    
    RTR = RTR_proto()
    AGS1 = AGS1_proto()
    AGS2 = AGS2_proto()
    global serial_port          # access to the global serial port argument:

#    open commuication port:
    def openPort(serial_port):
        try:
            mag_port = serial.Serial(port = serial_port,
#            mag_port = serial.Serial(port = '/dev/ttyUSB0',
#            mag_port = serial.Serial(port = '/dev/tty.usbmodem621',
                            baudrate = 19200,
                            bytesize = 8,
                            timeout = None)
            
            return(mag_port)
        except:
            print('Error: Failed to open commuications port, exiting')
            exit()

#    main application loop:
    def mainLoop():
        serial_byte     = 0
        datacount       = 0
        sync_locked     = 0
        serial_buffer   = array.array('i')

#        call to open the communications port and assign handle on success:
        mag_port = openPort(serial_port)
        
        mag_port.read(100)
        mag_port.flush()
      
        sync_locked = 0
        bytes_waiting = 0

        while (sync_locked < 0.03):
          sync_check_start = time.time()

          try:
            serial_byte = ord(mag_port.read(1))
            #print(format(serial_byte, '02x')),

          except:
            print
            print("Error: Failed to read communications Port, exiting")
            mag_port.close()
            exit()
        
          sync_check_stop = time.time()
          sync_locked = sync_check_stop - sync_check_start
            
        if (sync_locked > 0.03):

          if debug_level > 0:
            print
            print("sync")
          
            for i in range(0,10):
              print(str(i) + " "),

            for i in range(10,60):#bytes_waiting -1):
              print(i),
            print

          for p in range(0,8):

            time.sleep(0.07)
         
            bytes_waiting = mag_port.inWaiting()
       
            serial_buffer = chr(serial_byte) + mag_port.read(bytes_waiting)

            if debug_level > 0:
              print("(" + str(bytes_waiting+1) + ")"),

              for i in range(0,bytes_waiting+1):
                print(format(ord(serial_buffer[i]), '02x')),

              print

            if (ord(serial_buffer[42]) == 0xa0):
              if debug_level > 0:
                print("Remote A0 Packet ")
              remote_A0.decode(serial_buffer)

            elif (ord(serial_buffer[42]) == 0xa1):
              if debug_level > 0:
                print("Remote A1 Packet")
              remote_A1.decode(serial_buffer)

            elif (ord(serial_buffer[42]) == 0xa2):
              if debug_level > 0:
                print("Remote A2 Packet")
              remote_A2.decode(serial_buffer)

            elif (ord(serial_buffer[42]) == 0xa3):
              if debug_level > 0:
                print("Remote A3 Packet")
              remote_A3.decode(serial_buffer)
  
            elif (ord(serial_buffer[42]) == 0xa4):
              if debug_level > 0:
                print("Remote A4 Packet")
              remote_A4.decode(serial_buffer)

            #elif (ord(serial_buffer[42]) == 0x11):
                
            elif (ord(serial_buffer[42]) == 0x00):
              if debug_level > 0:
                print("Remote Z0 Packet")
              remote_Z0.decode(serial_buffer)

            elif (ord(serial_buffer[42]) == 0x80):         
              if debug_level > 0:
                print("Remote BMK Packet")
              remote_BMK.decode(serial_buffer)
              
            #decode if an accesory responded 
            if(bytes_waiting+1 > 43):

              if debug_level > 0:
                print ("Decoding Accessory"),

              if (ord(serial_buffer[43]) == 0x81):
                if debug_level > 0:
                  print("BMK Packet")
                BMK.decode(serial_buffer)

              elif (ord(serial_buffer[43]) == 0x91):
                if debug_level > 0:
                  print("RTR Packet")
                RTR.decode(serial_buffer)
              elif (ord(serial_buffer[43]) == 0xA1):
                if debug_level > 0:
                  print("AGS1 Packet")
                AGS1.decode(serial_buffer)              
              elif (ord(serial_buffer[43]) == 0xA2):
                if debug_level > 0:
                  print("AGS2 Packet")
                AGS1.decode(serial_buffer)

            if p < 7:
              serial_byte = ord(mag_port.read(1))

        remote_base.decode(serial_buffer)
        inverter.decode(serial_buffer)

        report_results()        # better print out the results:
        mag_port.close()        # close the com port and finish:

#    application report function:
    def report_results():
        print("")
        print("Equip List")
        print("  " + inverter.model_descript + "({0})".format(inverter.revision))
        if (RTR.rtr_revision != 0):
          print("  ME-RTR({0})".format(RTR.rtr_revision))
        else:
          print("  ME-ARC({0})".format(remote_base.revision))
        if (BMK.bmk_revision != 0):
          print("  BMK({0})".format(BMK.bmk_revision))
        if (AGS1.ags_revision != 0):
          print("  AGS({0})".format(remote_A0_agsA1.ags_revision))
      

        print("\nLive Data")
        print("\tClock                     {:02}:{:02}".format(remote_A0.remote_hours, remote_A0.remote_min))
        print("")
        print("\tInverter Mode:            " + inverter.status_descript)
        print("\tFault:                    " + inverter.fault_descript)
        print("")
        print("\tOutput AC Volts:          {0} Vac".format(inverter.volts_ac_out))
        print("\tOutput AC Amps:           {0} Amps".format(inverter.amps_ac_in-inverter.amps_ac_out))
        print("\tOutput AC Watts:          {0} Watts".format(100*int(inverter.volts_ac_out*(inverter.amps_ac_in-inverter.amps_ac_out)/100.0+0.5)))
        #print("")
        #print("\tInput AC Volts:  {0} Vac".format(inverter.volts_ac_in))
        print("\tInput AC Amps:            {0} Amps".format(inverter.amps_ac_in))
        #print("\tInput AC Power:   {0} kW".format(inverter.volts_ac_in*inverter.amps_ac_in/1000.0))
        print("\tLine Frequency            {0} Hz".format(inverter.frequency_ac_out))
        print("")
        print("\tTemperature Battery       {0} F".format(int(1.8 * inverter.temp_battery + 32), 0))
        print("\tTemperature Transformer   {0} F".format(int(1.8 * inverter.temp_transformer + 32), 0))
        print("\tTemperature FETs          {0} F".format(int(1.8 * inverter.temp_FET + 32), 0))
        print("")
        if (BMK.bmk_revision != 0):
          print("\tFault Status:             " + BMK.bmk_fault_descrip)
          print("\tBattery State of Charge:  {0} %".format(BMK.bmk_battery_soc))
          print("\tBattery Volts DC:         {0} Vdc".format(BMK.bmk_volts_dc))
          print("\tAmps DC:                  {0} Amps".format(BMK.bmk_amps_dc))
          print("\tPower DC:                 {0} Watts".format(BMK.bmk_volts_dc*BMK.bmk_amps_dc))
          print("\tBattery Max:              {0} Vdc".format(BMK.bmk_volts_max))
          print("\tBattery Min:              {0} Vdc".format(BMK.bmk_volts_min))
          print("\tBattery Amp Hour Net      {0} AmpHr".format(BMK.bmk_amp_hour_net))
          print("\tBattery Amp Hour Trip     {0} AmpHr".format(BMK.bmk_amp_hour_trip))
          print("\tBattery Amp Hour Total    {0} AmpHr".format(BMK.bmk_amp_hour_total))
        else:
          print("\tInverter Input Volts:     {0} Vdc".format(inverter.volts_dc))
          print("\tInverter Input Amps:      {0} Amps".format(inverter.amps_dc))
          print("\tInverter Input Power:     {0} Watts".format(inverter.volts_dc*inverter.amps_dc))
        print("")
        
        #print("")
        #print("\tGenerator Run Time        {0} Hrs".format(remote_A0_agsA1.ags_run_time))
        #print("\tGenerator Last Run        {0} Days".format(remote_A1_agsA2.ags_days_last_gen_run))

        #print("")
        #print("AGS - ver({0})".format(remote_A0_agsA1.ags_revision))
        #print("\tStatus:                   " + remote_A0_agsA1.ags_status_descript)
        #print("\tGen Start Mode:           " + remote_base.genstart_auto_descript)
        #print("\tQuiet Hours:              " + remote_A0_agsA1.ags_quiet_hours_descrip)
        #print("\t\tQuiet Time Start  {0} Hrs".format(remote_A3.ags_quite_time_start))
        #print("\t\tQuiet Time Stop   {0} Hrs".format(remote_A3.ags_quite_time_stop))
        #print("")
        #print("\tAuto Start                " + remote_A1_agsA2.ags_start_stop_enable)
        #print("\t\tStart Time        {0} Hrs".format(remote_A1_agsA2.ags_start_time))
        #print("\t\tStop Time         {0} Hrs".format(remote_A1_agsA2.ags_stop_time))
        #print("")
        #print("\tStart Delay               {0} Sec".format(remote_A1_agsA2.ags_start_delay))
        #print("\tStop Delay                {0} Sec".format(remote_A1_agsA2.ags_stop_delay))
        #print("\tWarm Up Time              {0} Sec".format(remote_A4.ags_warm_up_time))
        #print("\tCool Down Time            {0} Sec".format(remote_A4.ags_cool_down_time))
        #print("\tSOC Start                 {0} %".format(remote_A2_RTR.ags_soc_start))
        #print("\tSOC Stop                  {0} %".format(remote_A2_RTR.ags_soc_stop))
        #print("\tAmps Start                {0} Amps".format(remote_A2_RTR.ags_amps_start))
        #print("\tAmps Stop                 {0} Amps".format(remote_A2_RTR.ags_amps_stop))
        #print("\t\tAmps Start Delay  {0} Sec".format(remote_A2_RTR.ags_amps_start_delay))
        #print("\t\tAmps Stop Delay   {0} Sec".format(remote_A2_RTR.ags_amps_stop_delay))
        #print("")
        #print("\tMaximum Run Time          {0} Hrs".format(remote_A1_agsA2.ags_max_run_time))
        #print("\tTop Off Time              {0} Min".format(remote_A3.ags_top_off))
        #print("\tExercise Day Period       {0} Days".format(remote_A3.ags_exercise_days))
        #print("\tExercise Start Time       {0} Hrs".format(remote_A3.ags_exercise_start_time))
        #print("\tExercise Run Time         {0} Hrs".format(remote_A3.ags_exercise_run_time))
        
        #print("\tStart Temperature         {0} F".format(remote_A0_agsA1.ags_start_temp))
        #print("\tStart Volts               {0} Vdc".format(remote_A0_agsA1.ags_start_volts_dc))
        #print("\tStop Volts                {0} Vdc".format(remote_A1_agsA2.ags_volts_dc_stop))

        #print("")
        #print(inverter.model_descript + " - ver({0})".format(inverter.revision))
        #print("\tSystem Bus Voltage:      {0} Vdc".format(system_bus_volts))
        #print("\tInverter Stack Mode:      " + inverter.stack_mode)
        #print("\tSearch Watts:             {0} Watts".format(remote_base.search_watts))
        #print("\tCharger Amps:             {0} %".format(remote_base.charger_amps))
        #print("\tShore AC Amps:            {0} Amps".format(remote_base.shore_ac_amps))
        #print("\tParallel Threshold:       {0} %".format(remote_base.parallel_threshold))
#        print("\tForce Charge:             {0}".format(remote_base.force_charge))
#        print("\tForce Charge:             " + remote_base.force_charge_descript)
        #print("\tAC Volts Trip:            {0} Vac".format(remote_base.volts_ac_trip))

        #print("")
        #print("Battery Settings")
        #print("\tBattery Size, remote:     {0} AmpHr".format(remote_base.battery_size))
        #print("\tBattery Size, BMK:        {0} AmpHr".format(BMK.bmk_battery_size))
        #print("\tBattery Type:             " + remote_base.battery_type_descript)
        #print("\tBattery Efficiency:       {0} %".format(BMK.bmk_battery_efficiency))
        #print("\tFloat Volts:              {0} Vdc".format(remote_base.float_volts))
        #print("\tAbsorb Volts:             {0} Vdc".format(remote_base.absorb_volts))
        #print("\tEqualise Volts:           {0} Vdc".format(remote_base.equalise_volts))
        #print("\tBattery Low Trip:         {0} Vdc".format(remote_base.battery_low_trip))

    mainLoop()

#application entry point:
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--port", default = "/dev/ttyUSB0", help="commuications port descriptor, e.g /dev/ttyUSB0 or COM1")
    parser.add_argument("-d", "--debug", default = 0, type=int, choices=[0, 1, 2], help="debug data")
    args = parser.parse_args()
    
    serial_port = args.port
    debug_level = args.debug 

    print("MagPy Magnum Energy MagnaSine Data Protocol Decoder\n")
    print("Debug level : {0}".format(debug_level))
    print("serial port : " + serial_port + "\n")
    
    system_bus_volts = 0    # set as global variable:
    main()                  # call the main function:

jimmiejoe
Posts: 1
Joined: Mon Jan 08, 2018 3:19 am

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Jan 08, 2018 4:04 am

@CharlesGodwin is the code still available. I have the same setup minus the AGS. I have been looking for a way to log the Inverter, BMS, and ARC, plus my morning star controller.

I am also interesting in powering the Pi from the battery as you have.

This is for a mobile off-grid setup.

CharlesGodwin
Posts: 45
Joined: Wed Aug 22, 2012 9:57 pm
Location: Ottawa Canada

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Jan 08, 2018 11:45 am

The short answer is probably. I'm willing to provide it but will need to drag it out of my archives. If you don't hear from me in 10 days, remind me.

trumps2000
Posts: 9
Joined: Wed Aug 29, 2012 1:05 am

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Jan 08, 2018 3:34 pm

Charles, Are you still using this setup? Just curious how its holding up.

CharlesGodwin
Posts: 45
Joined: Wed Aug 22, 2012 9:57 pm
Location: Ottawa Canada

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Jan 08, 2018 3:53 pm

It's working fine. I just haven't made changes to it in a few years.

My major problem is that I am setup in a cold and snowy situation and I don't visit for 6 months of the year. Because of snow falling on the panels I got low battery conditions because the drain on the system, although small, was persistent.

Willeert
Posts: 7
Joined: Tue Jun 14, 2016 2:06 pm

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Jan 08, 2018 4:06 pm

Hi Charles,

I would like a copy of the code also if you are going to find it. I have been collecting Python code for the Pi which extracts data from the Magnum Energy system. My goal is to convert it to C/C++ that I will use on an Arduino. Unfortunately my computer hiccupped and I lost some data so I am attemting to get my code examples again.

One thing I am very uncertain about is how to connect the RS485 device to read the Magnum data. Magnum suggests making the device emulate a piece of wire but I am struggling with how to implement this concept. I am not that experienced with coding etc so any help with the circuit would be much appreciated.

Thanks

Will

trumps2000
Posts: 9
Joined: Wed Aug 29, 2012 1:05 am

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Jan 08, 2018 4:09 pm

How much does your system draw? I was a little surprised to find mine draws 0.4A with everything. In standby that is.

CharlesGodwin
Posts: 45
Joined: Wed Aug 22, 2012 9:57 pm
Location: Ottawa Canada

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Jan 08, 2018 6:02 pm

Can't recall off hand but it's more than 0.4A

I have the Magnum Energy system with BMK, Remote, AGS all drawing a little. Turning off power to the inverter helps. Then I have the Pi and then one big drain is my Mobile broadband hub I use so the Pi can FTP records daily. I have added a control board to manage up time and shut stuff down and restart hourly but even that has a small draw.

I've added a small (7A) solar panel mounted perpendicularly so no snow will stick to it and that helps but it is a shaded by trees for part of the day as there is a very low horizon. I'm about 47 degrees north.

trumps2000
Posts: 9
Joined: Wed Aug 29, 2012 1:05 am

Re: Using a Pi to Log Data in a Magnum Energy System

Wed Jan 10, 2018 1:01 am

Cool, sounds like an interesting setup.

trumps2000
Posts: 9
Joined: Wed Aug 29, 2012 1:05 am

Re: Using a Pi to Log Data in a Magnum Energy System

Wed Jan 10, 2018 1:18 am

Willeert, JimmieJoe, have you tried the code I modified? I'm just curious how it works for others.

Fyi, I was able to setup a real-time web server and a plug n play adapter that works with the raspberry pi zero W. I can make the oshpark files public. Let me know if your interested.

Willeert
Posts: 7
Joined: Tue Jun 14, 2016 2:06 pm

Re: Using a Pi to Log Data in a Magnum Energy System

Thu Jan 11, 2018 4:23 pm

I have reveiwed this entire topic. I see that the topic already descibed how the circuit is configured in order to aquire the data, so my question did not make sense to ask. I have a multi inverter Magnum system with a router and remote ARC50 which is plugged into the router. I am going to install the splitter at the router and then plug the ARC50 and the Arduino into the splitter. This should get me the data stream. It should also let me emulate the ARC50 if I ever get the program to the point of writing to the router instead of just reading the data.

Will

trumps2000
Posts: 9
Joined: Wed Aug 29, 2012 1:05 am

Re: Using a Pi to Log Data in a Magnum Energy System

Thu Jan 11, 2018 10:00 pm

I don't have a remote arc, but when I sniffed using an unused port, I didn't get the bmk packets. With an Arc, attached to the router do u see the bmk packets?

Also do u have an ags attached?

Willeert
Posts: 7
Joined: Tue Jun 14, 2016 2:06 pm

Re: Using a Pi to Log Data in a Magnum Energy System

Thu Jan 11, 2018 10:11 pm

I do not have an AGS. My ARC sees the BMK information. I do not have any configuring of the code done to either capture packets or decipher them. I am just getting started however I expect to be very slow as I am have to learn Python and then attempt to apply it to Arduino C and get the system working in that configuration. The first thing I have to determine is how to connect the 4 wires to my RS485 shield. I don't know if I should connect the VCC wire or not........

Will

trumps2000
Posts: 9
Joined: Wed Aug 29, 2012 1:05 am

Re: Using a Pi to Log Data in a Magnum Energy System

Thu Jan 11, 2018 11:12 pm

Just fyi the original code would fail if an AGS wasn't present. Not sure if Charles updated that? I modified the code it so it didn't matter what was connected.

As far as connections go, you should only need to attach the 2 data lines and ground.

hamsterball1
Posts: 12
Joined: Thu May 24, 2018 3:21 am

Re: Using a Pi to Log Data in a Magnum Energy System

Thu May 24, 2018 6:24 pm

@CharlesGodwin, that is some impressive work done there and thank you for sharing the details!
I essentially joined this forum to ask you (or anyone else on here who knows/is willing to help) this question.
I have a rural off-grid situation (solar panels/diesel generator with magnum inverter and batteries) very similar to yours - no internet, lots of winter. Difference is I can access the system daily. We are a small non-profit organization that works with kids to connect more to nature and develop life skills.

Anyways, I have talked to Magnum and they told me that the guy they contracted to create the non-online software for data logging is taking a very long time and there is no telling when it will be done. Which led me to RPi and this post.

I am tech-savvy but am not a programmer by any means (and am also new to RPi), so I don't fully understand how to actually implement your code and system. I was wondering if you would be willing to guide me how to do this more specifically, or even set up another system like this for us. I would compensate you for your time, of course, if you wish. I am also open to anyone else on this forum who may be willing to help. Thanks (and I hope I didn't break any rules by asking this)

CharlesGodwin
Posts: 45
Joined: Wed Aug 22, 2012 9:57 pm
Location: Ottawa Canada

Re: Using a Pi to Log Data in a Magnum Energy System

Sun Jun 17, 2018 7:02 pm

I must apologize.

I thought I had replied to you but can find no record of my reply.

I will gladly help with your project. I have recently trimmed down the code to make it simpler to use.

I just noticed private messages has been turned off so this will out in the open.

What do you want to log or access about your system?

hamsterball1
Posts: 12
Joined: Thu May 24, 2018 3:21 am

Re: Using a Pi to Log Data in a Magnum Energy System

Sun Jul 01, 2018 8:57 pm

CharlesGodwin wrote:
Sun Jun 17, 2018 7:02 pm
I must apologize.

I thought I had replied to you but can find no record of my reply.

I will gladly help with your project. I have recently trimmed down the code to make it simpler to use.

I just noticed private messages has been turned off so this will out in the open.

What do you want to log or access about your system?
Hi Charles,
Thanks for writing here and sorry for my delay, I had a busy end of school year time with our programs over here. We can also do this by email if you prefer; I can provide my email address.

Essentially we need to log the data for two main reasons: one, to understand what is going on with the system in order to optimize and understand costs and ROI's (the latter of which is unlikely due to being offgrid, but one can try patiently), and two, to have the data that can be used to size future additions or replacement components accurately (eg wind turbine, more panels, or other tech, or when the batteries will need to be replaced), especially as the programs expand and the power consumption grows/changes.
I am no expert on this so I may not be using the right terms or not thinking about this the right way, but I would like to track and log over the course of a whole day, week, month, and year:
-the arc/trend of solar power production
-generator turn on date and time, off time, duration of run
-generator power production
-indication of whether the gen start was auto or manual
-power consumption
-indication of when inverter switches to "float" and time of day when the SOC reaches 100% and how long it stays there
-battery SOC
-battery temp
-total in and out amp hours from batteries
-Bonus: if there is a way to automatically calculate the fuel consumption and/or fuel remaining in tank based on the gen run time, that would be great, as well. I have the formula from the genset manufacturer for the fuel consumption, so I guess it is just a matter of a little program that makes a calculation?

I am not sure if that is more or less than what the MagWeb Monitoring kit does. It tracks the following according to their website:
Inverter/Charger:
-Status
-Program settings
-Faults
-DC volts, DC amps
-Invert, charge LEDs
-Tech menus
Battery Monitor status
Auto Gen Start (AGS) status

I may have said the same in other words in my list.

Is it possible to do what I described?

I recently received the Rapsberry Pi 3 B+ in the mail today and installed Rasbian, so I am ready to start! Do I need a RaspiComm expansion board? Thanks so much for your time!

CharlesGodwin
Posts: 45
Joined: Wed Aug 22, 2012 9:57 pm
Location: Ottawa Canada

Re: Using a Pi to Log Data in a Magnum Energy System

Wed Jul 04, 2018 3:46 pm

This seems practical. Write me at deleted I will delete this email address once I get yours and send you a valid address.
Last edited by CharlesGodwin on Fri Jul 06, 2018 11:43 am, edited 1 time in total.

hamsterball1
Posts: 12
Joined: Thu May 24, 2018 3:21 am

Re: Using a Pi to Log Data in a Magnum Energy System

Fri Jul 06, 2018 4:15 am

Hi Charles,
Thanks, will do.

EdwardHomestead
Posts: 2
Joined: Sun Jul 29, 2018 10:46 pm

Re: Using a Pi to Log Data in a Magnum Energy System

Mon Jul 30, 2018 4:09 pm

Hey guys!!!

Very interested in this topic, and have been in contact with a few "Sr. blahblablah" at Magnum.... but have always been given the run-around.

I have used pi's for some time, for many different little projects here and there, and have never had a need to join the forum.

Boom... House fire, we lost it all.

That being said, we had our solar setup replaced which is now a Magnum setup.

MS4448PAE....master,slave,
PT-100 charge controller
BMK
Magweb.

I thought I would be able to hack something together to have a local server for the magweb data... checked out how the mag web sends data over the lantronix device... but havn't spent the time writing how to handle the messages.

I would LOVE to get in touch with some of you who have this working, how can we go about that?

Lee

PS: I am located in Northern Ontario, probably more northern than your [Charles] remote cabin!

Return to “Automation, sensing and robotics”

Who is online

Users browsing this forum: No registered users and 6 guests