# -*- coding: utf-8 -*-
#!/usr/bin/python3
#
# Simple implementation for iPanda I-P-XD-3000VA (AC FIRST) UPS.
#

import sys
import serial
import time

#global ser


def print_hex_list(data):
    print ('[{}]'.format(', '.join("0x%02x"%x for x in data)),end='')


def sadd(s,t):
    if (s):   r=s + "," + t
    else:     r=t
    return r


if __name__ == '__main__':  # noqa
    import argparse

    parser = argparse.ArgumentParser(
        description='iPanda UPS dump.',
        epilog="""\
NOTE: no check is done if the device is busy.

""")

    parser.add_argument(
        "--port",
        help="serial port name",
        default='/dev/ttyUSB2')

    parser.add_argument(
        "--bps",
        type=int,
        nargs='?',
        help='set baud rate, default: %(default)s',
        default=2400)

    parser.add_argument(
        '-q', '--quiet',
        action='store_true',
        help='suppress non error messages',
        default=False)

    parser.add_argument(
        '-c', '--compact',
        action='store_true',
        help='return Model;Status;Battery Chg;Utility;UPS Load;-;Batt.Run Time',
        default=False)

    parser.add_argument(
        '--csv',
        type=float,
        help='return lines with STATUS;LINEV;OUTPUTV;BCHARGE;LOADPCT;BATFACT;LINEFREQ, --csv 1 sets interval to 1s',
        default=0)

    parser.add_argument(
        '--debug',
        action='store_true',
        help='Development mode, prints Python internals on errors',
        default=False)

### Commands

    parser.add_argument(
        '--test',
        action='store_true',
        help='(T) Perform UPS self test for one minute. Send again to cancel',
        default=False)

    parser.add_argument(
        '--unplug',
        action='store_true',
        help='(u) Simulate mains failure, remove from grid. Send again to cancel',
        default=False)

    parser.add_argument(
        '--alarm',
        action='store_true',
        help='(A) Sound buzzer',
        default=False)

    parser.add_argument(
        '--shutdown',
        action='store_true',
        help='(k) Shut down after 2s',
        default=False)

    parser.add_argument(
        '--restart',
        action='store_true',
        help='(o) Restart UPS',
        default=False)

    parser.add_argument(
        '--batlow',
        action='store_true',
        help='(m) battery cut-off low voltage shutdown',
        default=False)

    parser.add_argument(
        '--calibrate',
        action='store_true',
        help='(d) battery calibration. Note: when low battery time, correction is automatically cancelled. Repeat the command cancel calibration',
        default=False)
###


    group = parser.add_argument_group('serial port')

    group.add_argument(
        "--bytesize",
        choices=[5, 6, 7, 8],
        type=int,
        help="set bytesize, one of {5 6 7 8}, default: 8",
        default=8)

    group.add_argument(
        "--parity",
        choices=['N', 'E', 'O', 'S', 'M'],
        type=lambda c: c.upper(),
        help="set parity, one of {N E O S M}, default: N",
        default='N')

    group.add_argument(
        "--stopbits",
        choices=[1, 1.5, 2],
        type=float,
        help="set stopbits, one of {1 1.5 2}, default: 1",
        default=1)

    group.add_argument(
        '--rtscts',
        action='store_true',
        help='enable RTS/CTS flow control (default off)',
        default=False)

    group.add_argument(
        '--xonxoff',
        action='store_true',
        help='enable software flow control (default off)',
        default=False)

    group.add_argument(
        '--rts',
        type=int,
        help='set initial RTS line state (possible values: 0, 1)',
        default=None)

    group.add_argument(
        '--dtr',
        type=int,
        help='set initial DTR line state (possible values: 0, 1)',
        default=None)

    args = parser.parse_args()

    # connect to serial port
    ser = serial.serial_for_url(args.port, do_not_open=True)
    ser.baudrate = args.bps
    ser.bytesize = args.bytesize
    ser.parity = args.parity
    ser.stopbits = args.stopbits
    ser.rtscts = args.rtscts
    ser.xonxoff = args.xonxoff
    ser.timeout = 1

    if args.rts is not None:
        ser.rts = args.rts

    if args.dtr is not None:
        ser.dtr = args.dtr

    if args.debug:
        sys.stderr.write(
            '--- iPanda UPS on {p.name}  {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'
            ''.format(p=ser))

    try:
        ser.open()
    except serial.SerialException as e:
        sys.stderr.write('Could not open serial port {}: {}\n'.format(ser.name, e))
        sys.exit(1)

    if (args.csv==0):
          # I Read mfg id
          # SIN3000wGERMANY Z2111D0002
          # :I-P-XD-3000VA (AC FIRST)
          # DC 48V TO AC 230V 50HZ
          # 2000W continuous power inverter
          # 4000W watt peak power
          #
          ser.write(b"I")
          r=ser.read_until('\n',26)
          sModel=r.decode("utf-8")

    if args.test:
      print("Starting Self test")
      ser.write(b"T")
    else:
      if args.unplug:
        print("Unplugging")
        ser.write(b"U")
      else:
        if args.alarm:
          print("Alarm")
          ser.write(b"A")
        else:
          if args.shutdown:
            print("Shutdown in 2s")
            ser.write(b"K") # not tested
          else:
            if args.restart:
              print("Restarting")
              ser.write(b"O")
            else:
              if args.batlow:
                print("Starting bat cut-off shutdown")
                ser.write(b"M") # not tested
              else:
                if args.calibrate:
                  print("Starting calibration")
                  ser.write(b"D")
    bvf=0.946
    lvf=0.64 #0.65
    ovf=0.636 #0.65
    timestamp=0
    try:
        while True:
             # Y Get status
             # First character definition  b7b6b5b4b3b2b1b0
             #       b6¡ªBattery low
             #     b5-- Buzzer cut
             #     b4-- Short-circuit fault
             #     b3¡ªUPS Self-test
             #     b2-- High temperature
             #     b1¡ªUPS Shutdown
             #     b0¡ªoverload
             # Second; Input voltage   Calculate factor :0.65 (Or self-adjusting)
             # Third; Output voltage   Calculate factor :0.65 (Or self-adjusting)
             # Fourth; Percentage of battery capacity
             # Fifth; Percentage of load
             # Sixth; Battery voltage Calculation factors are adjusted according to different models
             # Seventh; Frequency
             # Eighth; HEX(0D)

             ser.write(b"Y")                    # SIN3000wGERMANY Z2111D0002
             r=ser.read_until('\x0D',8)

             s=""
             if (r[7] == 0x0d):
                  if (r[0]==0):
                    s="ONLINE"
                  else:
                    if (r[0] & 0x80 != 0): s = sadd(s,"INVERTER_MODE")
                    if (r[0] & 0x40 != 0): s = sadd(s,"BATLOW")
                    if (r[0] & 0x20 != 0): s = sadd(s,"BUZZER")
                    if (r[0] & 0x10 != 0): s = sadd(s,"SHORT_FAULT")
                    if (r[0] & 0x8  != 0): s = sadd(s,"SELF_TEST")
                    if (r[0] & 0x4  != 0): s = sadd(s,"HIGH_TEMP")
                    if (r[0] & 0x2  != 0): s = sadd(s,"SHUTDOWN")
                    if (r[0] & 0x1  != 0): s = sadd(s,"OVERLOAD")

                  if args.compact:
                        print("I-P-XD-3000VA;" + sModel + ";" + s + ";" + str(r[3]) + " %;" + '{0:.1f}'.format(r[2]/ovf) + " VAC;" + str(r[4]) + " %;-;" + '{0:.1f}'.format(r[5]*bvf) + " V" )
                  else:
                        if (args.csv>0):
         #                      STATUS;LINEV;OUTPUTV;BCHARGE;LOADPCT;BATFACT;LINEFREQ
                              print('{0:.2f}'.format(timestamp) + ";" + s + ";" + '{0:.1f}'.format(r[1]/lvf)  + ";" + '{0:.1f}'.format(r[2]/ovf) + ";"  + str(r[3]) + ";"  + str(r[4]) + ";" + '{0:.1f}'.format(r[5]*bvf) + ";" + str(r[6]) )
                        else:
                              print("MODEL: "+ sModel)
                              print("STATUS: " + s + " ",end='')
                              print_hex_list(r)
                              print()

                              print("LINEV  : " + '{0:.1f}'.format(r[1]/lvf))
                              print("OUTPUTV: " + '{0:.1f}'.format(r[2]/ovf))
                              print("BCHARGE: " +str (r[3]))
                              print("LOADPCT: " +str (r[4]))
                              print("BATVOLT: " +'{0:.1f}'.format(r[5]*bvf))
                              print("LINEFREQ: " +str (r[6]))
             else:
                  sys.stderr.write('Error reading status string\n')
                  print_hex_list(r)

             if (args.csv==0): break
             if (args.csv>0):
                try:
                   time.sleep(args.csv)
                   timestamp += args.csv
                except KeyboardInterrupt:
                   break

    except KeyboardInterrupt:
       pass

    if args.debug:
        sys.stderr.write('\n--- exit ---\n')
#    serial_worker.stop()
    ser.close()
    