PageRenderTime 33ms CodeModel.GetById 42ms RepoModel.GetById 1ms app.codeStats 0ms

/python/PidStore.py

https://code.google.com/
Python | 1024 lines | 910 code | 57 blank | 57 comment | 57 complexity | ed34ea9504370df575916c4f2c6e8b28 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0
  1. # This program is free software; you can redistribute it and/or modify
  2. # it under the terms of the GNU General Public License as published by
  3. # the Free Software Foundation; either version 2 of the License, or
  4. # (at your option) any later version.
  5. #
  6. # This program is distributed in the hope that it will be useful,
  7. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. # GNU Library General Public License for more details.
  10. #
  11. # You should have received a copy of the GNU General Public License
  12. # along with this program; if not, write to the Free Software
  13. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  14. #
  15. # PidStore.py
  16. # Copyright (C) 2010 Simon Newton
  17. # Holds all the information about RDM PIDs
  18. """The PID Store."""
  19. __author__ = 'nomis52@gmail.com (Simon Newton)'
  20. import math
  21. import struct
  22. import sys
  23. from google.protobuf import text_format
  24. from ola import PidStoreLocation
  25. from ola import Pids_pb2
  26. # Various sub device enums
  27. ROOT_DEVICE = 0
  28. MAX_VALID_SUB_DEVICE = 0x0200;
  29. ALL_SUB_DEVICES = 0xffff
  30. # The two types of commands classes
  31. RDM_GET, RDM_SET = range(2)
  32. class Error(Exception):
  33. """Base error class."""
  34. class InvalidPidFormat(Error):
  35. "Indicates the PID data file was invalid."""
  36. class PidStructureException(Error):
  37. """Raised if the PID structure isn't vaild."""
  38. class ArgsValidationError(Error):
  39. """Raised if the arguments don't match the expected frame format."""
  40. class UnpackException(Error):
  41. """Raised if we can't unpack the data corectly."""
  42. class Pid(object):
  43. """A class that describes everything about a PID."""
  44. def __init__(self, name, value,
  45. get_request = None,
  46. get_response = None,
  47. set_request = None,
  48. set_response = None,
  49. get_validators = [],
  50. set_validators = []):
  51. """Create a new PID.
  52. Args:
  53. name: the human readable name
  54. value: the 2 byte PID value
  55. get_request: A Group object, or None if GET isn't supported
  56. get_response:
  57. set_request: A Group object, or None if SET isn't supported
  58. set_response:
  59. get_validators:
  60. set_validators:
  61. """
  62. self._name = name
  63. self._value = value
  64. self._requests = {
  65. RDM_GET: get_request,
  66. RDM_SET: set_request,
  67. }
  68. self._responses = {
  69. RDM_GET: get_response,
  70. RDM_SET: set_response,
  71. }
  72. self._validators = {
  73. RDM_GET: get_validators,
  74. RDM_SET: set_validators,
  75. }
  76. @property
  77. def name(self):
  78. return self._name
  79. @property
  80. def value(self):
  81. return self._value
  82. def RequestSupported(self, command_class):
  83. """Check if this PID allows a command class."""
  84. return self._requests.get(command_class) is not None
  85. def ValidateAddressing(self, args, command_class):
  86. """Run the validators."""
  87. validators = self._validators.get(command_class)
  88. if validators is None:
  89. return false
  90. args['pid'] = self
  91. for validator in validators:
  92. if not validator(args):
  93. return False
  94. return True
  95. def __cmp__(self, other):
  96. return cmp(self._value, other._value)
  97. def __str__(self):
  98. return '%s (0x%04hx)' % (self.name, self.value)
  99. def __hash__(self):
  100. return self._value
  101. def Pack(self, args, command_class):
  102. """Pack args
  103. Args:
  104. args: A list of arguments of the right types.
  105. command_class: RDM_GET or RDM_SET
  106. Returns:
  107. Binary data which can be used as the Param Data.
  108. """
  109. group = self._requests.get(command_class)
  110. blob, args_used = group.Pack(args)
  111. return blob
  112. def Unpack(self, data, command_class):
  113. """Unpack a message.
  114. Args:
  115. data: The raw data
  116. command_class: RDM_GET or RDM_SET
  117. """
  118. group = self._responses.get(command_class)
  119. if group is None:
  120. raise UnpackException('Response contained data: %s' % data)
  121. output = group.Unpack(data)[0]
  122. return output
  123. def GetRequestDescription(self, command_class):
  124. """Get a help string that describes the format of the request.
  125. Args:
  126. command_class: RDM_GET or RDM_SET
  127. Returns:
  128. A help string.
  129. """
  130. group = self._requests.get(command_class)
  131. return group.GetDescription()
  132. # The following classes are used to describe RDM messages
  133. class Atom(object):
  134. """The basic field in an RDM message."""
  135. def __init__(self, name):
  136. self._name = name
  137. @property
  138. def name(self):
  139. return self._name
  140. def CheckForSingleArg(self, args):
  141. if len(args) < 1:
  142. raise ArgsValidationError('Missing argument for %s' % self.name)
  143. def __str__(self):
  144. return '%s, %s' % (self.__class__, self._name)
  145. def __repr__(self):
  146. return '%s, %s' % (self.__class__, self._name)
  147. def GetDescription(self, indent=0):
  148. return str(self)
  149. class FixedSizeAtom(Atom):
  150. def __init__(self, name, format_char):
  151. super(FixedSizeAtom, self).__init__(name)
  152. self._char = format_char
  153. @property
  154. def size(self):
  155. return struct.calcsize(self._FormatString())
  156. def FixedSize(self):
  157. """Returns true if the size of this atom doesn't vary."""
  158. return True
  159. def Unpack(self, data):
  160. format_string = self._FormatString()
  161. try:
  162. values = struct.unpack(format_string, data)
  163. except struct.error:
  164. raise UnpackException(e)
  165. return values[0]
  166. def Pack(self, args):
  167. format_string = self._FormatString()
  168. try:
  169. data = struct.pack(format_string, args[0])
  170. except struct.error, e:
  171. raise ArgsValidationError("Can't pack data: %s" % e)
  172. return data, 1
  173. def _FormatString(self):
  174. return '!%c' % self._char
  175. class Bool(FixedSizeAtom):
  176. BOOL_MAP = {
  177. 'true': 1,
  178. 'false': 0,
  179. }
  180. def __init__(self, name):
  181. # once we have 2.6 use ? here
  182. super(Bool, self).__init__(name, 'B')
  183. def Pack(self, args):
  184. self.CheckForSingleArg(args)
  185. arg = args[0]
  186. if isinstance(arg, str):
  187. arg = args[0].lower()
  188. if arg not in self.BOOL_MAP:
  189. raise ArgsValidationError('Argument should be true or false')
  190. arg = self.BOOL_MAP[arg]
  191. return super(Bool, self).Pack([arg])
  192. def Unpack(self, value):
  193. return bool(super(Bool, self).Unpack(value))
  194. def GetDescription(self, indent=0):
  195. indent = ' ' * indent
  196. return '%s%s: <true|false>' % (indent, self.name)
  197. class Range(object):
  198. """A range of allowed int values."""
  199. def __init__(self, min, max):
  200. self.min = min
  201. self.max = max
  202. def Matches(self, value):
  203. return value >= self.min and value <= self.max
  204. def __str__(self):
  205. if self.min == self.max:
  206. return '%d' % self.min
  207. else:
  208. return '[%d, %d]' % (self.min, self.max)
  209. class IntAtom(FixedSizeAtom):
  210. def __init__(self, name, char, max_value, **kwargs):
  211. super(IntAtom, self).__init__(name, char)
  212. # About Labels & Ranges:
  213. # If neither labels nor ranges are specified, the valid values is the range of
  214. # the data type.
  215. # If labels are specified, and ranges aren't, the valid values are the labels
  216. # If ranges are specified, the valid values are those which fall into the range
  217. # (inclusive).
  218. # If both are specified, the enum values must fall into the specified ranges.
  219. # ranges limit the allowed values for a field
  220. self._ranges = kwargs.get('ranges', [])[:]
  221. self._multiplier = kwargs.get('multiplier', 0)
  222. # labels provide a user friendly way of referring to data values
  223. self._labels = {}
  224. for value, label in kwargs.get('labels', []):
  225. self._labels[label.lower()] = value
  226. if not kwargs.get('ranges', []):
  227. # Add the labels to the list of allowed values
  228. self._ranges.append(Range(value, value))
  229. if not self._ranges:
  230. self._ranges.append(Range(0, max_value))
  231. def Pack(self, args):
  232. self.CheckForSingleArg(args)
  233. arg = args[0]
  234. if isinstance(arg, str):
  235. arg = arg.lower()
  236. value = self._labels.get(arg)
  237. # not a labeled value
  238. if value is None and self._multiplier >= 0:
  239. try:
  240. value = int(args[0])
  241. except ValueError, e:
  242. raise ArgsValidationError(e)
  243. multiplier = 10 ** self._multiplier
  244. if value % multiplier:
  245. raise ArgsValidationError('Conversion will lose data: %d -> %d' %
  246. (value, (value / multiplier * multiplier)))
  247. value = value / multiplier
  248. elif value is None:
  249. try:
  250. value = float(args[0])
  251. except ValueError, e:
  252. raise ArgsValidationError(e)
  253. scaled_value = value * 10 ** abs(self._multiplier)
  254. fraction, int_value = math.modf(scaled_value)
  255. if fraction:
  256. raise ArgsValidationError(
  257. 'Conversion will lose data: %s -> %s' %
  258. (value, int_value * (10.0 ** self._multiplier)))
  259. value = int(int_value)
  260. for range in self._ranges:
  261. if range.Matches(value):
  262. break
  263. else:
  264. raise ArgsValidationError('Param %d out of range, must be one of %s' %
  265. (value, self._GetAllowedRanges()))
  266. return super(IntAtom, self).Pack([value])
  267. def Unpack(self, data):
  268. return self._AccountForMultiplier(super(IntAtom, self).Unpack(data))
  269. def GetDescription(self, indent=0):
  270. indent = ' ' * indent
  271. increment = ''
  272. if self._multiplier:
  273. increment = ', increment %s' % (10 ** self._multiplier)
  274. return ('%s%s: <%s> %s' % (indent, self.name, self._GetAllowedRanges(),
  275. increment))
  276. def _GetAllowedRanges(self):
  277. values = self._labels.keys()
  278. for range in self._ranges:
  279. if range.min == range.max:
  280. values.append(str(self._AccountForMultiplier(range.min)))
  281. else:
  282. values.append('[%s, %s]' %
  283. (self._AccountForMultiplier(range.min),
  284. self._AccountForMultiplier(range.max)))
  285. return ('%s' % ', '.join(values))
  286. def _AccountForMultiplier(self, value):
  287. return value * (10 ** self._multiplier)
  288. class Int8(IntAtom):
  289. """A single signed byte field."""
  290. def __init__(self, name, **kwargs):
  291. super(Int8, self).__init__(name, 'b', 0xff, **kwargs)
  292. class UInt8(IntAtom):
  293. """A single unsigned byte field."""
  294. def __init__(self, name, **kwargs):
  295. super(UInt8, self).__init__(name, 'B', 0xff, **kwargs)
  296. class Int16(IntAtom):
  297. """A two-byte signed field."""
  298. def __init__(self, name, **kwargs):
  299. super(Int16, self).__init__(name, 'h', 0xffff, **kwargs)
  300. class UInt16(IntAtom):
  301. """A two-byte unsigned field."""
  302. def __init__(self, name, **kwargs):
  303. super(UInt16, self).__init__(name, 'H', 0xffff, **kwargs)
  304. class Int32(IntAtom):
  305. """A four-byte signed field."""
  306. def __init__(self, name, **kwargs):
  307. super(Int32, self).__init__(name, 'i', 0xffffffff, **kwargs)
  308. class UInt32(IntAtom):
  309. """A four-byte unsigned field."""
  310. def __init__(self, name, **kwargs):
  311. super(UInt32, self).__init__(name, 'I', 0xffffffff, **kwargs)
  312. class IPV4(IntAtom):
  313. """A four-byte IPV4 address."""
  314. def __init__(self, name, **kwargs):
  315. super(IPV4, self).__init__(name, 'I', 0xffffffff, **kwargs)
  316. class String(Atom):
  317. """A string field."""
  318. def __init__(self, name, **kwargs):
  319. super(String, self).__init__(name)
  320. self._min = kwargs.get('min_size', 0)
  321. self._max = kwargs.get('max_size', 32)
  322. @property
  323. def min(self):
  324. return self._min
  325. @property
  326. def max(self):
  327. return self._max
  328. @property
  329. def size(self):
  330. # only valid if FixedSize() == True
  331. return self.min
  332. def FixedSize(self):
  333. return self.min == self.max
  334. def Pack(self, args):
  335. self.CheckForSingleArg(args)
  336. arg = args[0]
  337. arg_size = len(arg)
  338. if self.max is not None and arg_size > self.max:
  339. raise ArgsValidationError('%s can be at most %d,' %
  340. (self.name, self.max))
  341. if self.min is not None and arg_size < self.min:
  342. raise ArgsValidationError('%s must be more than %d,' %
  343. (self.name, self.min))
  344. try:
  345. data = struct.unpack('%ds' % arg_size, arg)
  346. except struct.error, e:
  347. raise ArgsValidationError("Can't pack data: %s" % e)
  348. return data[0], 1
  349. def Unpack(self, data):
  350. data_size = len(data)
  351. if self.min and data_size < self.min:
  352. raise UnpackException('%s too short, required %d, got %d' %
  353. (self.name, self.min, data_size))
  354. if self.max and data_size > self.max:
  355. raise UnpackException('%s too long, required %d, got %d' %
  356. (self.name, self.max, data_size))
  357. try:
  358. value = struct.unpack('%ds' % data_size, data)
  359. except struct.error, e:
  360. raise UnpackException(e)
  361. return value[0].rstrip('\x00')
  362. def GetDescription(self, indent=0):
  363. indent = ' ' * indent
  364. return ('%s%s: <string, [%d, %d] bytes>' %
  365. (indent, self.name, self.min, self.max))
  366. def __str__(self):
  367. return 'String(%s, min=%s, max=%s)' % (self.name, self.min, self.max)
  368. class Group(Atom):
  369. """A repeated group of atoms."""
  370. def __init__(self, name, atoms, **kwargs):
  371. """Create a group of atoms.
  372. Args:
  373. name: The name of the group
  374. atoms: The list of atoms the group contains
  375. Raises:
  376. PidStructureException: if the structure of this group is invalid.
  377. """
  378. super(Group, self).__init__(name)
  379. self._atoms = atoms
  380. self._min = kwargs.get('min_size')
  381. self._max = kwargs.get('max_size')
  382. # None for variable sized groups
  383. self._group_size = self._VerifyStructure()
  384. @property
  385. def min(self):
  386. return self._min
  387. @property
  388. def max(self):
  389. return self._max
  390. def _VerifyStructure(self):
  391. """Verify that we can pack & unpack this group.
  392. We need to make sure we have enough known information to pack & unpack a
  393. group. We don't support repeated groups of variable length data, nor
  394. nested, repeated groups.
  395. For now we support the following cases:
  396. - Fixed size group. This is easy to unpack
  397. - Groups of variable size. We enforce two conditions for these, i) the
  398. variable sized field MUST be the last one ii) Only a single occurance
  399. is allowed. This means you can't do things like:
  400. [(string, int)] # variable sized types must be last
  401. [(int, string)] # assuming string is variable sized
  402. [(int, [(bool,)]] # no way to tell where the group barriers are
  403. Returns:
  404. The number of bytes this group uses, or None if it's variable sized
  405. """
  406. variable_sized_atoms = []
  407. group_size = 0
  408. for atom in self._atoms:
  409. if atom.FixedSize():
  410. group_size += atom.size
  411. else:
  412. variable_sized_atoms.append(atom)
  413. if len(variable_sized_atoms) > 1:
  414. raise PidStore('More than one variable size field in %s: %s' %
  415. (self.name, variable_sized_atoms))
  416. if not variable_sized_atoms:
  417. # The group is of a fixed size, this means we don't care how many times
  418. # it's repeated.
  419. return group_size
  420. # for now we only support the case where the variable sized field is the
  421. # last one
  422. if variable_sized_atoms[0] != self._atoms[-1]:
  423. raise PidStructureException(
  424. 'The variable sized field %s must be the last one' %
  425. variable_sized_atoms[0].name)
  426. # It's impossible to unpack groups of variable length data without more
  427. # information.
  428. if self.min != 1 and self.max != 1:
  429. raise PidStructureException("Repeated groups can't contain variable length data")
  430. return None
  431. def FixedSize(self):
  432. """This is true if we know the exact size of the group and min == max.
  433. Obviously this is unlikely.
  434. """
  435. can_determine_size = True
  436. for atom in self._atoms:
  437. if not atom.FixedSize():
  438. can_determine_size = False
  439. break
  440. return (can_determine_size and self._min is not None and
  441. self._min == self._max)
  442. @property
  443. def size(self):
  444. # only valid if FixedSize() == True
  445. return self.min
  446. def Pack(self, args):
  447. """Pack the args into binary data.
  448. Args:
  449. args: A list of string.
  450. Returns:
  451. binary data
  452. """
  453. if self._group_size is None:
  454. # variable length data, work out the fixed length portion first
  455. data = []
  456. arg_offset = 0
  457. for atom in self._atoms[0:-1]:
  458. chunk, args_consumed = atom.Pack(args[arg_offset:])
  459. data.append(chunk)
  460. arg_offset += args_consumed
  461. # what remains is for the variable length section
  462. chunk, args_used = self._atoms[-1].Pack(args[arg_offset:])
  463. arg_offset += args_used
  464. data.append(chunk)
  465. if arg_offset < len(args):
  466. raise ArgsValidationError('Too many arguments, expected %d, got %d' %
  467. (arg_offset, len(args)))
  468. return ''.join(data), arg_offset
  469. elif self._group_size == 0:
  470. return '', 0
  471. else:
  472. # this could be groups of fields, but we don't support that yet
  473. data = []
  474. arg_offset = 0
  475. for atom in self._atoms:
  476. chunk, args_consumed = atom.Pack(args[arg_offset:])
  477. data.append(chunk)
  478. arg_offset += args_consumed
  479. if arg_offset < len(args):
  480. raise ArgsValidationError('Too many arguments, expected %d, got %d' %
  481. (arg_offset, len(args)))
  482. return ''.join(data), arg_offset
  483. def Unpack(self, data):
  484. """Unpack binary data.
  485. Args:
  486. data: The binary data
  487. Returns:
  488. A list of dicts.
  489. """
  490. # we've already performed checks in _VerifyStructure so we can rely on
  491. # self._group_size
  492. data_size = len(data)
  493. if self._group_size is None:
  494. total_size = 0
  495. for atom in self._atoms[0:-1]:
  496. total_size += atom.size
  497. if data_size < total_size:
  498. raise UnpackException('Response too small, required %d, only got %d' %
  499. (total_size, data_size))
  500. output, used = self._UnpackFixedLength(self._atoms[0:-1], data)
  501. # what remains is for the variable length section
  502. variable_sized_atom = self._atoms[-1]
  503. data = data[used:]
  504. output[variable_sized_atom.name] = variable_sized_atom.Unpack(data)
  505. return [output]
  506. elif self._group_size == 0:
  507. if data_size > 0:
  508. raise UnpackException('Expected 0 bytes but got %d' % data_size)
  509. return [{}]
  510. else:
  511. # groups of fixed length data
  512. if data_size % self._group_size:
  513. raise UnpackException(
  514. 'Data size issue for %s, data size %d, group size %d' %
  515. (self.name, data_size, self._group_size))
  516. group_count = data_size / self._group_size
  517. if self.max is not None and group_count > self.max:
  518. raise UnpackException(
  519. 'Too many repeated group_count for %s, limit is %d, found %d' %
  520. (self.name, self.max, group_count))
  521. if self.max is not None and group_count < self.min:
  522. raise UnpackException(
  523. 'Too few repeated group_count for %s, limit is %d, found %d' %
  524. (self.name, self.min, group_count))
  525. offset = 0
  526. groups = []
  527. while offset + self._group_size <= data_size:
  528. group = self._UnpackFixedLength(
  529. self._atoms,
  530. data[offset:offset + self._group_size])[0]
  531. groups.append(group)
  532. offset += self._group_size
  533. return groups
  534. def GetDescription(self, indent=0):
  535. names = []
  536. output = []
  537. for atom in self._atoms:
  538. names.append('<%s>' % atom.name)
  539. output.append(atom.GetDescription(indent=2))
  540. return ' '.join(names), '\n'.join(output)
  541. def _UnpackFixedLength(self, atoms, data):
  542. """Unpack a list of atoms of a known, fixed size.
  543. Args:
  544. atoms: A list of atoms, must all have FixedSize() == True.
  545. data: The binary data.
  546. Returns:
  547. A tuple in the form (output_dict, data_consumed)
  548. """
  549. output = {}
  550. offset = 0
  551. for atom in atoms:
  552. size = atom.size
  553. output[atom.name] = atom.Unpack(data[offset:offset + size])
  554. offset += size
  555. return output, offset
  556. def __str__(self):
  557. return ('Group: atoms: %s, [%s, %s]' %
  558. (str(self._atoms), self.min, self.max))
  559. # These are validators which can be applied before a request is sent
  560. def RootDeviceValidator(args):
  561. """Ensure the sub device is the root device."""
  562. if args.get('sub_device') != ROOT_DEVICE:
  563. print >> sys.stderr, (
  564. "Can't send GET %s to non root sub devices" % args['pid'].name)
  565. return False
  566. return True
  567. def SubDeviceValidator(args):
  568. """Ensure the sub device is in the range 0 - 512 or 0xffff."""
  569. sub_device = args.get('sub_device')
  570. if (sub_device is None or
  571. (sub_device > MAX_VALID_SUB_DEVICE and sub_device != ALL_SUB_DEVICES)):
  572. print >> sys.stderr, (
  573. "%s isn't a valid sub device" % sub_device)
  574. return False
  575. return True
  576. def NonBroadcastSubDeviceValiator(args):
  577. """Ensure the sub device is in the range 0 - 512."""
  578. sub_device = args.get('sub_device')
  579. if (sub_device is None or sub_device > MAX_VALID_SUB_DEVICE):
  580. print >> sys.stderr, (
  581. "Sub device %s needs to be between 0 and 512" % sub_device)
  582. return False
  583. return True
  584. def SpecificSubDeviceValidator(args):
  585. """Ensure the sub device is in the range 1 - 512."""
  586. sub_device = args.get('sub_device')
  587. if (sub_device is None or sub_device == ROOT_DEVICE or
  588. sub_device > MAX_VALID_SUB_DEVICE):
  589. print >> sys.stderr, (
  590. "Sub device %s needs to be between 1 and 512" % sub_device)
  591. return False
  592. return True
  593. class PidStore(object):
  594. """The class which holds information about all the PIDs."""
  595. def __init__(self):
  596. self._pid_store = Pids_pb2.PidStore()
  597. self._pids = {}
  598. self._name_to_pid = {}
  599. self._manufacturer_pids = {}
  600. self._manufacturer_names_to_pids = {}
  601. self._manufacturer_id_to_name = {}
  602. def Load(self, file, validate = True):
  603. """Load a PidStore from a file.
  604. Args:
  605. file: The path to the pid store file
  606. validate: When True, enable strict checking.
  607. """
  608. pid_file = open(file, 'r')
  609. lines = pid_file.readlines()
  610. pid_file.close()
  611. self._pid_store.Clear()
  612. try:
  613. text_format.Merge('\n'.join(lines), self._pid_store)
  614. except text_format.ParseError, e:
  615. raise InvalidPidFormat(str(e))
  616. for pid_pb in self._pid_store.pid:
  617. if validate:
  618. if pid_pb.value > 0x8000 and pid_pb.value < 0xffe0:
  619. raise InvalidPidFormat('%0x04hx between 0x8000 and 0xffdf in %s' %
  620. (pid_pb.value, file))
  621. if pid_pb.value in self._pids:
  622. raise InvalidPidFormat('0x%04hx listed more than once in %s' %
  623. (pid_pb.value, file))
  624. if pid_pb.name in self._name_to_pid:
  625. raise InvalidPidFormat('%s listed more than once in %s' %
  626. (pid_pb.name, file))
  627. pid = self._PidProtoToObject(pid_pb)
  628. self._pids[pid.value] = pid
  629. self._name_to_pid[pid.name] = pid
  630. for manufacturer in self._pid_store.manufacturer:
  631. pid_dict = self._manufacturer_pids.setdefault(
  632. manufacturer.manufacturer_id,
  633. {})
  634. name_dict = self._manufacturer_names_to_pids.setdefault(
  635. manufacturer.manufacturer_id,
  636. {})
  637. self._manufacturer_id_to_name[manufacturer.manufacturer_id] = (
  638. manufacturer.manufacturer_name)
  639. for pid_pb in manufacturer.pid:
  640. if validate:
  641. if pid_pb.value < 0x8000 or pid_pb.value > 0xffdf:
  642. raise InvalidPidFormat(
  643. 'Manufacturer pid 0x%04hx not between 0x8000 and 0xffdf' %
  644. pid_pb.value)
  645. if pid_pb.value in pid_dict:
  646. raise InvalidPidFormat(
  647. '0x%04hx listed more than once for 0x%04hx in %s' % (
  648. pid_pb.value, manufacturer.manufacturer_id, file))
  649. if pid_pb.name in name_dict:
  650. raise InvalidPidFormat(
  651. '%s listed more than once for %s in %s' % (
  652. pid_pb.name, manufacturer, file))
  653. pid = self._PidProtoToObject(pid_pb)
  654. pid_dict[pid.value] = pid
  655. name_dict[pid.name] = pid
  656. # we no longer need the protobuf representation
  657. self._pid_store.Clear()
  658. def Pids(self):
  659. """Returns a list of all PIDs. Manufacturer PIDs aren't included.
  660. Returns:
  661. A list of Pid objects.
  662. """
  663. return self._pids.values()
  664. def ManufacturerPids(self, esta_id):
  665. """Return a list of all Manufacturer PIDs for a given esta_id.
  666. Args:
  667. esta_id: The 2-byte esta / manufacturer ID.
  668. Returns:
  669. A list of Pid objects.
  670. """
  671. return self._manufacturer_pids.get(esta_id, {}).values()
  672. def GetPid(self, pid_value, esta_id=None):
  673. """Look up a PIDs by the 2-byte PID value.
  674. Args:
  675. pid_value: The 2-byte PID value, e.g. 0x8000
  676. esta_id: The 2-byte esta / manufacturer ID.
  677. Returns:
  678. A Pid object, or None if no PID was found.
  679. """
  680. pid = self._pids.get(pid_value, None)
  681. if not pid:
  682. pid = self._manufacturer_pids.get(esta_id, {}).get(
  683. pid_value, None)
  684. return pid
  685. def GetName(self, pid_name, esta_id=None):
  686. """Look up a PIDs by name.
  687. Args:
  688. pid_name: The name of the PID, e.g. 'DEVICE_INFO'
  689. esta_id: The 2-byte esta / manufacturer ID.
  690. Returns:
  691. A Pid object, or None if no PID was found.
  692. """
  693. pid = self._name_to_pid.get(pid_name)
  694. if not pid:
  695. pid = self._manufacturer_names_to_pids.get(esta_id, {}).get(
  696. pid_name, None)
  697. return pid
  698. def NameToValue(self, pid_name, esta_id=None):
  699. """A helper method to convert a PID name to a PID value
  700. Args:
  701. pid_name: The name of the PID, e.g. 'DEVICE_INFO'
  702. esta_id: The 2-byte esta / manufacturer ID.
  703. Returns:
  704. The value for this PID, or None if it wasn't found.
  705. """
  706. pid = self.GetName(pid_name)
  707. if pid:
  708. return pid.value
  709. return pid
  710. def _PidProtoToObject(self, pid_pb):
  711. """Convert the protobuf representation of a PID to a PID object.
  712. Args:
  713. pid_pb: The protobuf version of the pid
  714. Returns:
  715. A PIDStore.PID object.
  716. """
  717. def BuildList(field_name):
  718. if not pid_pb.HasField(field_name):
  719. return None
  720. try:
  721. group = self._FrameFormatToGroup(getattr(pid_pb, field_name))
  722. except PidStructureException, e:
  723. raise PidStructureException(
  724. "The structure for the %s in %s isn't valid: %s" %
  725. (field_name, pid_pb.name, e))
  726. return group
  727. get_request = BuildList('get_request')
  728. get_response = BuildList('get_response')
  729. set_request = BuildList('set_request')
  730. set_response = BuildList('set_response')
  731. get_validators = []
  732. if pid_pb.HasField('get_sub_device_range'):
  733. get_validators.append(self._SubDeviceRangeToValidator(
  734. pid_pb.get_sub_device_range))
  735. set_validators = []
  736. if pid_pb.HasField('set_sub_device_range'):
  737. set_validators.append(self._SubDeviceRangeToValidator(
  738. pid_pb.set_sub_device_range))
  739. return Pid(pid_pb.name,
  740. pid_pb.value,
  741. get_request,
  742. get_response,
  743. set_request,
  744. set_response,
  745. get_validators,
  746. set_validators)
  747. def _FrameFormatToGroup(self, frame_format):
  748. """Convert a frame format to a group."""
  749. atoms = []
  750. for field in frame_format.field:
  751. atoms.append(self._FieldToAtom(field))
  752. return Group('', atoms, min_size=1, max_size=1)
  753. def _FieldToAtom(self, field):
  754. """Convert a PID proto field message into an atom."""
  755. args = {'labels': [],
  756. 'ranges': [],
  757. }
  758. if field.HasField('max_size'):
  759. args['max_size'] = field.max_size
  760. if field.HasField('min_size'):
  761. args['min_size'] = field.min_size
  762. if field.HasField('multiplier'):
  763. args['multiplier'] = field.multiplier
  764. for label in field.label:
  765. args['labels'].append((label.value, label.label))
  766. for allowed_value in field.range:
  767. args['ranges'].append(Range(allowed_value.min, allowed_value.max))
  768. if field.type == Pids_pb2.BOOL:
  769. return Bool(field.name)
  770. elif field.type == Pids_pb2.INT8:
  771. return Int8(field.name, **args);
  772. elif field.type == Pids_pb2.UINT8:
  773. return UInt8(field.name, **args);
  774. elif field.type == Pids_pb2.INT16:
  775. return Int16(field.name, **args);
  776. elif field.type == Pids_pb2.UINT16:
  777. return UInt16(field.name, **args);
  778. elif field.type == Pids_pb2.INT32:
  779. return Int32(field.name, **args);
  780. elif field.type == Pids_pb2.UINT32:
  781. return UInt32(field.name, **args);
  782. elif field.type == Pids_pb2.IPV4:
  783. return IPV4(field.name, **args);
  784. elif field.type == Pids_pb2.GROUP:
  785. if not field.field:
  786. raise InvalidPidFormat('Missing child fields for %s' % field.name)
  787. atoms = []
  788. for child_field in field.field:
  789. atoms.append(self._FieldToAtom(child_field))
  790. return Group(field.name, atoms, **args)
  791. elif field.type == Pids_pb2.STRING:
  792. return String(field.name, **args)
  793. def _SubDeviceRangeToValidator(self, range):
  794. """Convert a sub device range to a validator."""
  795. if range == Pids_pb2.ROOT_DEVICE:
  796. return RootDeviceValidator
  797. elif range == Pids_pb2.ROOT_OR_ALL_SUBDEVICE:
  798. return SubDeviceValidator
  799. elif range == Pids_pb2.ROOT_OR_SUBDEVICE:
  800. return NonBroadcastSubDeviceValiator
  801. elif range == Pids_pb2.ONLY_SUBDEVICES:
  802. return SpecificSubDeviceValidator
  803. _pid_store = None
  804. def GetStore(location = None):
  805. """Get the instance of the PIDStore.
  806. Args:
  807. location: The location to load the store from. If not specified it uses the
  808. location defined in PidStoreLocation.py
  809. Returns:
  810. An instance of PidStore.
  811. """
  812. global _pid_store
  813. if not _pid_store:
  814. _pid_store = PidStore()
  815. if not location:
  816. location = PidStoreLocation.location
  817. _pid_store.Load(location)
  818. return _pid_store