#!/usr/bin/env python3 # # Simple PWM controller # Remote control tool # # Copyright (c) 2018-2021 Michael Buesch # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # from fractions import Fraction from libsimplepwm import * from libsimplepwm.util import * import argparse import pathlib import sys import time def parse_setpoints(string): def parseOne(v): try: v = v.strip() if v.casefold().startswith("0x".casefold()): # Raw 16 bit hex value. v = int(v, 16) if not 0 <= v <= 0xFFFF: raise SimplePWMError("Setpoint raw value " "out of range 0-0xFFFF.") return v if v.endswith("%"): # Percentage v = float(v[:-1]) if not 0.0 <= v <= 100.0: raise SimplePWMError("Setpoint percentage " "out of range 0%-100%.") return round(v * 0xFFFF / 100.0) if v.endswith("*"): # Degrees (0-360) v = float(v[:-1]) return round((v % 360.0) * 0xFFFF / 360.0) # Value 0-255 v = int(v) if 0 <= v <= 0xFF: return (v << 8) | v raise ValueError except ValueError as e: raise SimplePWMError("Setpoint value parse error.") s = string.split(",") if len(s) == 1: return [ parseOne(s[0]) for i in range(3) ] if len(s) != 3: raise SimplePWMError("Setpoints are not a comma separated triple.") return [ parseOne(v) for v in s ] def parse_pwmcorrindex(string): try: index = int(string) if not 0 <= index <= 3: raise SimplePWMError("PWM correction index is out of range.") indices = range(3) if index == 0 else (index-1,) return indices except ValueError as e: raise SimplePWMError("PWM correction parameter is invalid.") def parse_pwmcorr(string): s = string.split(":") if len(s) != 2: raise SimplePWMError("PWM correction parameter is invalid.") try: indices = parse_pwmcorrindex(s[0]) fraction = Fraction(float(s[1])).limit_denominator(0xFFFF) if not 0 <= fraction.numerator <= 0xFFFF: raise SimplePWMError("PWM correction factor is out of range.") return indices, fraction except (ValueError, ZeroDivisionError) as e: raise SimplePWMError("PWM correction parameter is invalid.") class ArgumentParserOrderedNamespace(argparse.Namespace): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) super().__setattr__("_setupDone", False) super().__setattr__("_orderedArgs", []) def __setattr__(self, name, value): sanitizedName = name.replace("_", "-") if (not self._setupDone and sanitizedName in (n for n, v in self._orderedArgs)): super().__setattr__("_setupDone", True) del self._orderedArgs[:] self._orderedArgs.append( (sanitizedName, value) ) super().__setattr__(name, value) @property def orderedArgs(self): return self._orderedArgs if self._setupDone else () def main(): s = None try: p = argparse.ArgumentParser(description="SimplePWM remote control") p.add_argument("-p", "--ping", action="store_true", help="Send a ping to the device and wait for pong reply.") p.add_argument("-b", "--get-battery", action="store_true", help="Get the battery voltage.") p.add_argument("-e", "--get-eeprom-enable", action="store_true", help="Get the state of the EEPROM.") p.add_argument("-E", "--eeprom-enable", type=int, default=None, metavar="BOOL", help="Enable/disable the EEPROM.") p.add_argument("-a", "--get-analog", action="store_true", help="Get the state of the analog inputs.") p.add_argument("-A", "--analog", type=int, default=None, metavar="BOOL", help="Enable/disable the analog inputs.") p.add_argument("-r", "--get-rgb", action="store_true", help="Get the current RGB setpoint values.") p.add_argument("-R", "--rgb", type=parse_setpoints, default=None, metavar="R,G,B", help="Set the RGB setpoint values.") p.add_argument("-s", "--get-hsl", action="store_true", help="Get the current HSL setpoint values.") p.add_argument("-S", "--hsl", type=parse_setpoints, default=None, metavar="H,S,L", help="Set the HSL setpoint values.") p.add_argument("-c", "--get-pwm-corr", type=parse_pwmcorrindex, metavar="INDEX", help="Get PWM lower range correction factor. " "Index is the PWM index (1-3) or 0 for all.") p.add_argument("-C", "--pwm-corr", type=parse_pwmcorr, default=None, metavar="INDEX:FACTOR", help="Set PWM lower range correction factor.") p.add_argument("-W", "--wait", type=float, metavar="SECONDS", help="Delay for a fractional number of seconds.") p.add_argument("-L", "--loop", action="store_true", help="Repeat the whole sequence.") p.add_argument("-d", "--debug-device", action="store_true", help="Read and dump debug messages from device.") p.add_argument("-D", "--debug", action="store_true", help="Enable remote side debugging.") p.add_argument("-F", "--write-flash", type=pathlib.Path, metavar="HEXFILE", help="Write an application flash hex.") p.add_argument("-P", "--write-eeprom", type=pathlib.Path, metavar="HEXFILE", help="Write an application EEPROM hex.") p.add_argument("port", nargs="?", type=pathlib.Path, default=pathlib.Path("/dev/ttyUSB0"), help="Serial port.") args = p.parse_args(namespace=ArgumentParserOrderedNamespace()) if args.debug: printDebug.enabled = True if args.debug_device: printDebugDevice.enabled = True s = SimplePWM(port=str(args.port), dumpDebugStream=args.debug_device, dumpDataStream=False, recoveryMode=(args.write_flash or args.write_eeprom)) repeat = True while repeat: for name, value in args.orderedArgs: if name == "write-flash": s.writeFlash(value) if name == "write-eeprom": s.writeEeprom(value) if name == "ping": if value: s.ping() if name == "get-battery": if value: meas, drop = s.getBatVoltage() print(f"Battery: " f"measured {meas/1000:.2f} V, " f"drop {drop/1000:.2f} V, " f"actual {(meas+drop)/1000:.2f} V") if name == "eeprom-enable": s.setEepromEn(value) if name == "get-eeprom-enable": if value: enabled = "enabled" if s.getEepromEn() else "disabled" print(f"EEPROM: {enabled}") if name == "analog": s.setAnalogEn(value) if name == "get-analog": if value: enabled = "enabled" if s.getAnalogEn() else "disabled" print(f"Analog inputs state: " f"{enabled}") if name == "rgb": s.setRGB(value) if name == "get-rgb": if value: rgb = s.getRGB() print(f"RGB setpoints: " f"{rgb[0]*100/0xFFFF:.1f}%, " f"{rgb[1]*100/0xFFFF:.1f}%, " f"{rgb[2]*100/0xFFFF:.1f}%") if name == "hsl": s.setHSL(value) if name == "get-hsl": if value: hsl = s.getHSL() print(f"HSL setpoints: " f"{hsl[0]*100/0xFFFF:.1f}%, " f"{hsl[1]*100/0xFFFF:.1f}%, " f"{hsl[2]*100/0xFFFF:.1f}%") if name == "pwm-corr": indices, fraction = value for i in indices: s.setPwmCorr(i, fraction) if name == "get-pwm-corr": indices = value for i in indices: fraction = s.getPwmCorr(i) print(f"PWM {i+1} " f"lower range correction factor: " f"{float(fraction)}") if name == "wait": time.sleep(value) if name == "loop": repeat = True break else: repeat = False if args.debug_device: s.dumpDebugStream() s.disconnect() except SimplePWMError as e: if s: s.disconnect() printError(e) return 1 except KeyboardInterrupt as e: if s: s.disconnect() printInfo("Interrupted.") return 1 return 0 if __name__ == "__main__": sys.exit(main()) # vim: ts=4 sw=4 expandtab