Created
November 15, 2017 23:24
-
-
Save nrclark/e43ba5191eb3ac03c3c893c1964de973 to your computer and use it in GitHub Desktop.
Serial-port controller for the IP Power 9258
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It works very well!
I built a stand alone controller with PIC (PIC12F683).
https://github.com/7m4mon/ip9258_ctrl
Thank you!!