#!/usr/bin/env python """ Small coil on/off tester for the BE rig. Defaults to the PFC relay (coil 12, "PFC_relay_enable") on the relay board, but any coil address works via -a. Usage: python relay_test.py -p COM13 --on # close the relay python relay_test.py -p COM13 --off # open it python relay_test.py -p COM13 --read # read the coil back python relay_test.py -p COM13 --toggle 5 -n 3 # 3 on/off cycles, 5s apart python relay_test.py -p COM4 -a 0 --on # RCU charging cmd instead """ import argparse import sys import time import minimalmodbus SLAVE_ID = 0x10 BAUDRATE = 9600 SERIAL_TIMEOUT = 1.0 RELAY_CONTROL_CMD_ADDRESS = 12 # coil - PFC_relay_enable, 1 = on def connect(port, slave_id): inst = minimalmodbus.Instrument(port=port, slaveaddress=slave_id, mode='rtu') inst.serial.baudrate = BAUDRATE inst.serial.timeout = SERIAL_TIMEOUT inst.close_port_after_each_call = False return inst def read_coil(inst, address): """Read the coil back with function code 1. Not every board supports it.""" try: return inst.read_bit(address, functioncode=1) except Exception as exc: return "unreadable (%s)" % exc def write_coil(inst, address, value): inst.write_bit(address, 1 if value else 0, functioncode=5) def main(): parser = argparse.ArgumentParser(description="Toggle a Modbus coil.") parser.add_argument("-p", "--port", required=True, help="COM port (e.g. COM13)") parser.add_argument("-a", "--address", type=int, default=RELAY_CONTROL_CMD_ADDRESS, help="Coil address (default: %(default)s, the PFC " "relay)") parser.add_argument("-s", "--slave", type=lambda v: int(v, 0), default=SLAVE_ID, help="Slave id (default: 16)") action = parser.add_mutually_exclusive_group(required=True) action.add_argument("--on", action="store_true", help="Write 1") action.add_argument("--off", action="store_true", help="Write 0") action.add_argument("--read", action="store_true", help="Read the coil") action.add_argument("--toggle", type=float, metavar="SECONDS", help="Alternate on/off, holding each state this long") parser.add_argument("-n", "--cycles", type=int, default=1, help="Number of on/off cycles for --toggle " "(default: %(default)s)") args = parser.parse_args() try: inst = connect(args.port, args.slave) except Exception as exc: print("Connect failed on %s: %s" % (args.port, exc)) return 1 print("%s slave %d coil %d" % (args.port, args.slave, args.address)) try: if args.read: print(" read = %s" % read_coil(inst, args.address)) elif args.on or args.off: value = bool(args.on) write_coil(inst, args.address, value) print(" wrote = %d" % value) print(" read = %s" % read_coil(inst, args.address)) else: for cycle in range(1, args.cycles + 1): for value in (1, 0): write_coil(inst, args.address, value) print(" cycle %d: wrote %d, read %s" % (cycle, value, read_coil(inst, args.address))) time.sleep(args.toggle) except KeyboardInterrupt: print("Interrupted.") except Exception as exc: print(" FAILED: %s" % exc) return 1 finally: inst.serial.close() return 0 if __name__ == "__main__": sys.exit(main())