Python library for working with the PD Buddy Sink Serial Console Configuration Interface
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

__init__.py 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. """Python bindings for PD Buddy Sink configuration"""
  2. from enum import *
  3. import serial
  4. import serial.tools.list_ports
  5. class Sink:
  6. """Interface for configuring a PD Buddy Sink"""
  7. vid = 0x1209
  8. pid = 0x0001
  9. def __init__(self, sp):
  10. """Open the serial port to communicate with the PD Buddy Sink
  11. Parameters:
  12. sp: A serial.tools.list_ports.ListPortInfo object
  13. """
  14. self.port = serial.Serial(sp.device, baudrate=115200)
  15. def send_command(self, cmd):
  16. """Send a command to the PD Buddy Sink, returning the result
  17. Parameters:
  18. cmd: A string containing the text to send to the Sink
  19. Returns:
  20. A list of zero or more bytes objects, each being one line printed
  21. as a response to the command.
  22. """
  23. # Send the command
  24. self.port.write(bytes(cmd, "utf-8") + b"\r\n")
  25. self.port.flush()
  26. # Read the result
  27. answer = b""
  28. while not answer.endswith(b"PDBS) "):
  29. answer += self.port.read(1)
  30. answer = answer.split(b"\r\n")
  31. # Remove the echoed command and prompt
  32. answer = answer[1:-1]
  33. return answer
  34. def help(self):
  35. """Returns the help text from the PD Buddy Sink"""
  36. return self.send_command("help")
  37. def license(self):
  38. """Returns the license text from the PD Buddy Sink"""
  39. return self.send_command("license")
  40. def erase(self):
  41. """Synchronously erases all stored configuration from flash"""
  42. self.send_command("erase")
  43. def write(self):
  44. """Synchronously writes the contents of the configuration buffer to flash"""
  45. self.send_command("write")
  46. def load(self):
  47. """Loads the current configuration from flash into the buffer
  48. Raises KeyError if there is no configuration in flash.
  49. """
  50. text = self.send_command("load")
  51. if len(text) > 0 and text[0].startswith(b"No configuration"):
  52. raise KeyError("no configuration")
  53. def get_cfg(self, index=None):
  54. """Reads configuration from flash
  55. Parameters:
  56. index: Optional index of configuration object in flash to read
  57. Returns:
  58. A SinkConfig object
  59. """
  60. if index is None:
  61. cfg = self.send_command("get_cfg")
  62. else:
  63. cfg = self.send_command("get_cfg {}".format(index))
  64. return SinkConfig.from_text(cfg)
  65. def get_tmpcfg(self):
  66. """Reads the contents of the configuration buffer
  67. Returns:
  68. A SinkConfig object
  69. """
  70. cfg = self.send_command("get_tmpcfg")
  71. return SinkConfig.from_text(cfg)
  72. def clear_flags(self):
  73. """Clears all the flags in the configuration buffer"""
  74. self.send_command("clear_flags")
  75. def toggle_giveback(self):
  76. """Toggles the GiveBack flag in the configuration buffer"""
  77. self.send_command("toggle_giveback")
  78. def set_v(self, mv):
  79. """Sets the voltage of the configuration buffer, in millivolts"""
  80. self.send_command("set_v {}".format(mv))
  81. def set_i(self, ma):
  82. """Sets the current of the configuration buffer, in milliamperes"""
  83. self.send_command("set_i {}".format(ma))
  84. def identify(self):
  85. """Blinks the LED quickly"""
  86. self.send_command("identify")
  87. def set_tmpcfg(self, sc):
  88. """Writes a SinkConfig object to the device's configuration buffer
  89. Note: the value of the status field is ignored; it will always be
  90. SinkStatus.VALID.
  91. """
  92. # Set flags
  93. self.clear_flags()
  94. if sc.flags & SinkFlags.GIVEBACK:
  95. self.toggle_giveback()
  96. # Set voltage
  97. self.set_v(sc.v)
  98. # Set current
  99. self.set_i(sc.i)
  100. @classmethod
  101. def get_devices(cls):
  102. """Get an iterable of PD Buddy Sink devices
  103. Returns an iterable of serial.tools.list_ports.ListPortInfo objects.
  104. """
  105. return serial.tools.list_ports.grep("{:04X}:{:04X}".format(cls.vid,
  106. cls.pid))
  107. class SinkConfig:
  108. """Python representation of a PD Buddy Sink configuration object"""
  109. def __init__(self, status=None, flags=None, v=None, i=None):
  110. """Create a SinkConfig object
  111. Parameters:
  112. status: A SinkStatus value
  113. flags: Zero or more SinkFlags values
  114. v: Voltage in millivolts
  115. i: Current in milliamperes
  116. """
  117. self.status = status
  118. self.flags = flags
  119. self.v = v
  120. self.i = i
  121. def __repr__(self):
  122. s = type(self).__name__ + "("
  123. if self.status is not None:
  124. s += "status={}".format(self.status)
  125. if self.flags is not None:
  126. if not s.endswith("("):
  127. s += ", "
  128. s += "flags={}".format(self.flags)
  129. if self.v is not None:
  130. if not s.endswith("("):
  131. s += ", "
  132. s += "v={}".format(self.v)
  133. if self.i is not None:
  134. if not s.endswith("("):
  135. s += ", "
  136. s += "i={}".format(self.i)
  137. s += ")"
  138. return s
  139. def __eq__(self, other):
  140. if isinstance(other, self.__class__):
  141. if other.status is not self.status:
  142. return False
  143. if other.flags is not self.flags:
  144. return False
  145. if other.v != self.v:
  146. return False
  147. if other.i != self.i:
  148. return False
  149. return True
  150. return NotImplemented
  151. def __ne__(self, other):
  152. if isinstance(other, self.__class__):
  153. return not self.__eq__(other)
  154. return NotImplemented
  155. def __hash__(self):
  156. return hash(tuple(sorted(self.__dict__.items())))
  157. @classmethod
  158. def from_text(cls, text):
  159. """Creates a SinkConfig from text returned by Sink.send_command
  160. Returns a new SinkConfig object.
  161. Raises IndexError if the configuration reads "Invalid index".
  162. """
  163. # Assume the parameters will all be None
  164. status = None
  165. flags = None
  166. v = None
  167. i = None
  168. # Iterate over all lines of text
  169. for line in text:
  170. # If the configuration said invalid index, raise an IndexError
  171. if line.startswith(b"Invalid index"):
  172. raise IndexError("configuration index out of range")
  173. # If there is no configuration, return an empty SinkConfig
  174. elif line.startswith(b"No configuration"):
  175. return cls()
  176. # If this line is the status field
  177. elif line.startswith(b"status: "):
  178. line = line.split()[1:]
  179. if line[0] == b"empty":
  180. status = SinkStatus.EMPTY
  181. elif line[0] == b"valid":
  182. status = SinkStatus.VALID
  183. elif line[0] == b"invalid":
  184. status = SinkStatus.INVALID
  185. # If this line is the flags field
  186. elif line.startswith(b"flags: "):
  187. line = line.split()[1:]
  188. flags = SinkFlags.NONE
  189. for word in line:
  190. if word == b"(none)":
  191. # If there are no flags set, stop looking
  192. break
  193. elif word == b"GiveBack":
  194. flags |= SinkFlags.GIVEBACK
  195. # If this line is the v field
  196. elif line.startswith(b"v: "):
  197. word = line.split()[1]
  198. v = round(1000*float(word))
  199. # If this line is the i field
  200. elif line.startswith(b"i: "):
  201. word = line.split()[1]
  202. i = round(1000*float(word))
  203. # Create a new SinkConfig object with the values we just read
  204. return cls(status=status, flags=flags, v=v, i=i)
  205. class SinkStatus(Enum):
  206. """Status field of a PD Buddy Sink configuration object"""
  207. EMPTY = 1
  208. VALID = 2
  209. INVALID = 3
  210. class SinkFlags(Flag):
  211. """Flags field of a PD Buddy Sink configuration object"""
  212. NONE = 0
  213. GIVEBACK = auto()