Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@nrclark
Created November 15, 2017 23:24
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nrclark/e43ba5191eb3ac03c3c893c1964de973 to your computer and use it in GitHub Desktop.
Save nrclark/e43ba5191eb3ac03c3c893c1964de973 to your computer and use it in GitHub Desktop.
Serial-port controller for the IP Power 9258
#!/usr/bin/env python3
""" Command-line utility for interfacing with an IP Power 9258 using the
serial debug interface. """
import time
import sys
import re
import argparse
import ast
import serial
class Controller(object):
""" Class for controlling an IP Power 9258 over a serial port (using the
pyserial library). """
def __init__(self, port):
""" Opens the target serial port, configures it to control an IP Power
9258, and places the conencted 9258 into debug mode. """
self.device = serial.Serial(port=port, baudrate=19200, timeout=0)
self.device.read(8192)
self.delay = 0.06
self.enter_debug()
def close(self):
""" Closes the serial port. """
self.device.close()
def type(self, string):
""" Sends a string of data one character at a time with an instance-
specific intra-character delay. """
for char in string:
self.device.write(char.encode('ascii'))
time.sleep(self.delay)
def dump(self):
""" Retrieves all data waiting in the serial port's recieve buffer. """
data = ""
chunk = None
while chunk != "":
time.sleep(0.05)
chunk = self.device.read(512).decode('ascii')
data += chunk
return data.strip()
def send_command(self, cmd):
""" Sends a command, waits for the response, and returns it. """
self.type("0\\A%s\\Z" % cmd)
return self.dump()
def enter_debug(self):
""" Enters debug mode on the IP Power 9258. """
result = self.send_command("DEBUG9258")
if re.findall("DEBUG ON", result) == []:
raise IOError("Couldn't enter debug mode")
def leave_debug(self):
""" Exits debug mode on the IP Power 9258. """
print(self.send_command("DEBUGOFF"))
def set_binary(self, value):
""" Sets channels according to a binary mask. 0x00 is "all off", 0x01
is "port 1 only", and 0x0F is "all on". """
next_val = 15 - (value & 0x0F)
self.send_command("P060%01X" % next_val)
def show(self):
""" Returns the existing binary value of the control register. 0x00 is
"all off", 0x01 is "port 1 only", and 0x0F is "all on". """
result = self.send_command("p06")
result = re.findall("p6[:][0-9A-Fa-f]{2,2}", result)[0]
result = result.split(":")[1].strip()
result = 15 - int(result, 16)
return result
def enable_all(self):
""" Enables all power channels. """
self.set_binary(0xF)
def disable_all(self):
""" Disables all power channels. """
self.set_binary(0x0)
def enable_channel(self, channel):
""" Enables one channel without changing the others. """
current = self.show()
new = current | (1 << (channel - 1))
self.set_binary(new)
def disable_channel(self, channel):
""" Disables one channel without changing the others. """
current = self.show()
new = current & (15 - (1 << (channel - 1)))
self.set_binary(new)
def create_parser():
""" Creates an argparse parser for the command-line application. """
description = "Control an IP Power 9258 power-switch using the serial port."
parser = argparse.ArgumentParser(description=description)
parser.add_argument('-d', '--device', dest='device', default='/dev/ttyUSB0',
help="Target serial port")
parser.add_argument('command', metavar="COMMAND", help="Command to run. " +
"Possible commands are 'on', 'off', 'set' and 'get'.")
parser.add_argument('parameter', metavar="PARAM", help="Parameter for " +
"target command. For 'on' and 'off', this should be " +
"'all' or a channel number. For 'set', this should " +
"be a numerical value (bit 0 = channel 1, bit 3 = " +
"channel 4).", nargs="?")
return parser
def run_parser(parser):
""" Runs the parser created by _create_parser(), sanitizes the result,
and returns it. """
result = parser.parse_args()
result.command = result.command.strip().lower()
if result.parameter is None:
result.parameter = ""
else:
result.parameter = result.parameter.strip().lower()
if result.command not in ['on', 'off', 'set', 'get']:
errmsg = "%s: unknown command [%s]" % (parser.prog, result.command)
sys.stderr.write(errmsg + "\n")
sys.exit(1)
if result.command in ['on', 'off']:
if result.parameter != "all":
try:
result.parameter = ast.literal_eval(result.parameter)
assert result.parameter == int(result.parameter)
result.parameter = int(result.parameter)
assert result.parameter > 0
assert result.parameter < 5
except (SyntaxError, ValueError, AssertionError):
errmsg = "%s: channel should be 1-4 or 'all'" % parser.prog
sys.stderr.write(errmsg + "\n")
sys.exit(1)
elif result.command == "set":
try:
result.parameter = ast.literal_eval(result.parameter)
assert result.parameter == int(result.parameter)
result.parameter = int(result.parameter)
assert result.parameter >= 0
assert result.parameter < 16
except (SyntaxError, ValueError, AssertionError):
errmsg = "%s: value should be 0-15" % parser.prog
sys.stderr.write(errmsg + "\n")
sys.exit(1)
return result
def main():
""" Main routine for CLI application. """
parser = create_parser()
args = run_parser(parser)
controller = Controller(args.device)
if args.command == 'on':
if args.parameter == 'all':
controller.enable_all()
else:
controller.enable_channel(args.parameter)
elif args.command == 'off':
if args.parameter == 'all':
controller.disable_all()
else:
controller.disable_channel(args.parameter)
elif args.command == 'set':
controller.set_binary(args.parameter)
elif args.command == 'get':
print(controller.show())
controller.close()
return
if __name__ == "__main__":
main()
@7m4mon
Copy link

7m4mon commented Jul 15, 2022

It works very well!
I built a stand alone controller with PIC (PIC12F683).
https://github.com/7m4mon/ip9258_ctrl
Thank you!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment