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 8.0KB

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