You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

101 lines
3.5 KiB

  1. import tables
  2. import time
  3. import sys
  4. import inspect
  5. import cStringIO
  6. class Layout(object):
  7. """Represents a NILM database layout"""
  8. @classmethod
  9. def description(cls):
  10. """Return the PyTables description of this layout"""
  11. desc = {}
  12. for (n, (name, type)) in enumerate(cls.fields):
  13. desc[name] = tables.Col.from_type(type, pos=n+1)
  14. return tables.Description(desc)
  15. @classmethod
  16. def parse(cls, in_fields):
  17. """Given in_fields as text, return a list of values
  18. converted to the correct types"""
  19. out=[]
  20. for (n, (name, type)) in enumerate(cls.fields):
  21. if name == 'timestamp':
  22. # special case: parse float, save as int
  23. out.append(int(float(in_fields[n]) * 1e6))
  24. elif type == 'float32':
  25. out.append(float(in_fields[n]))
  26. elif type == 'uint16':
  27. out.append(max(0, min(65535, int(in_fields[n], 10))))
  28. else:
  29. raise TypeError("Can't parse type " + type)
  30. class PrepData(Layout):
  31. expected_daily_rows = 120 * 86400
  32. fields = [ ( 'timestamp', 'int64' ),
  33. ( 'p1', 'float32' ),
  34. ( 'q1', 'float32'),
  35. ( 'p3', 'float32'),
  36. ( 'q3', 'float32'),
  37. ( 'p5', 'float32'),
  38. ( 'q5', 'float32'),
  39. ( 'p7', 'float32'),
  40. ( 'q7', 'float32') ]
  41. class RawData(Layout):
  42. expected_daily_rows = 8000 * 86400
  43. fields = [ ( 'timestamp', 'int64'),
  44. ( 'va', 'uint16'),
  45. ( 'vb', 'uint16'),
  46. ( 'vc', 'uint16'),
  47. ( 'ia', 'uint16'),
  48. ( 'ib', 'uint16'),
  49. ( 'ic', 'uint16') ]
  50. class RawNotchedData(Layout):
  51. expected_daily_rows = 8000 * 86400
  52. fields = RawData.fields + [ ( 'notch_ia', 'uint16' ),
  53. ( 'notch_ib', 'uint16' ),
  54. ( 'notch_ic', 'uint16' ) ]
  55. # Build list of all layouts, so we can look them up by name
  56. named = {}
  57. for name, obj in inspect.getmembers(sys.modules[__name__]):
  58. if inspect.isclass(obj) and issubclass(obj, Layout):
  59. named[name] = obj
  60. class Parser(object):
  61. """Object that parses and stores ASCII data for inclusion into the database"""
  62. def __init__(self, layout):
  63. if layout not in named:
  64. raise TypeError("unknown layout")
  65. self.layout = named[layout]
  66. self.data = []
  67. self.nrows = 0
  68. def parse(self, textdata):
  69. """Parse the data, provided as lines of text, using the current
  70. layout, into an internal data structure."""
  71. # This currently takes about 0.1 seconds for 1 megabyte of prep data,
  72. # 85 klines/sec. Could clearly be optimized a lot...
  73. indata = cStringIO.StringIO(textdata)
  74. self.nrows = 0
  75. # Assume any parsing error is a real error.
  76. # In the future we might want to skip completely empty lines,
  77. # or partial lines right before EOF?
  78. try:
  79. for line in indata:
  80. self.nrows += 1
  81. fields = line.partition('#')[0].split()
  82. self.data.append(self.layout.parse(fields))
  83. except (ValueError, TypeError, IndexError) as e:
  84. raise TypeError("line " + self.nrows + ": " + e.message)
  85. def fillrow(self, tablerow, rownum):
  86. """Fill a PyTables row object with the parsed data.
  87. The row must match the parser's layout"""
  88. for (n, (name, type)) in enumerate(self.layout.fields):
  89. tablerow[name] = self.data[rownum][n]