Python library for working with the PD Buddy Sink Serial Console Configuration Interface
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

__init__.py 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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 __str__(self):
  140. """Print the SinkStatus in the manner of the configuration shell"""
  141. s = ""
  142. if self.status is not None:
  143. s += "status: "
  144. if self.status is SinkStatus.EMPTY:
  145. s += "empty"
  146. elif self.status is SinkStatus.VALID:
  147. s += "valid"
  148. elif self.status is SinkStatus.INVALID:
  149. s += "invalid"
  150. s += "\n"
  151. if self.flags is not None:
  152. s += "flags: "
  153. if self.flags is SinkFlags.NONE:
  154. s += "(none)"
  155. else:
  156. if self.flags & SinkFlags.GIVEBACK:
  157. s += "GiveBack"
  158. s += "\n"
  159. if self.v is not None:
  160. s += "v: {:.2f} V\n".format(self.v / 1000)
  161. if self.i is not None:
  162. s += "i: {:.2f} A\n".format(self.i / 1000)
  163. # Return all but the last character of s to remove the trailing newline
  164. return s[:-1]
  165. def __eq__(self, other):
  166. if isinstance(other, self.__class__):
  167. if other.status is not self.status:
  168. return False
  169. if other.flags is not self.flags:
  170. return False
  171. if other.v != self.v:
  172. return False
  173. if other.i != self.i:
  174. return False
  175. return True
  176. return NotImplemented
  177. def __ne__(self, other):
  178. if isinstance(other, self.__class__):
  179. return not self.__eq__(other)
  180. return NotImplemented
  181. def __hash__(self):
  182. return hash(tuple(sorted(self.__dict__.items())))
  183. @classmethod
  184. def from_text(cls, text):
  185. """Creates a SinkConfig from text returned by Sink.send_command
  186. Returns a new SinkConfig object.
  187. Raises IndexError if the configuration reads "Invalid index".
  188. """
  189. # Assume the parameters will all be None
  190. status = None
  191. flags = None
  192. v = None
  193. i = None
  194. # Iterate over all lines of text
  195. for line in text:
  196. # If the configuration said invalid index, raise an IndexError
  197. if line.startswith(b"Invalid index"):
  198. raise IndexError("configuration index out of range")
  199. # If there is no configuration, return an empty SinkConfig
  200. elif line.startswith(b"No configuration"):
  201. return cls()
  202. # If this line is the status field
  203. elif line.startswith(b"status: "):
  204. line = line.split()[1:]
  205. if line[0] == b"empty":
  206. status = SinkStatus.EMPTY
  207. elif line[0] == b"valid":
  208. status = SinkStatus.VALID
  209. elif line[0] == b"invalid":
  210. status = SinkStatus.INVALID
  211. # If this line is the flags field
  212. elif line.startswith(b"flags: "):
  213. line = line.split()[1:]
  214. flags = SinkFlags.NONE
  215. for word in line:
  216. if word == b"(none)":
  217. # If there are no flags set, stop looking
  218. break
  219. elif word == b"GiveBack":
  220. flags |= SinkFlags.GIVEBACK
  221. # If this line is the v field
  222. elif line.startswith(b"v: "):
  223. word = line.split()[1]
  224. v = round(1000*float(word))
  225. # If this line is the i field
  226. elif line.startswith(b"i: "):
  227. word = line.split()[1]
  228. i = round(1000*float(word))
  229. # Create a new SinkConfig object with the values we just read
  230. return cls(status=status, flags=flags, v=v, i=i)
  231. class SinkStatus(Enum):
  232. """Status field of a PD Buddy Sink configuration object"""
  233. EMPTY = 1
  234. VALID = 2
  235. INVALID = 3
  236. class SinkFlags(Flag):
  237. """Flags field of a PD Buddy Sink configuration object"""
  238. NONE = 0
  239. GIVEBACK = auto()