@@ -6,20 +6,19 @@ import cStringIO
class Layout(object):
"""Represents a NILM database layout"""
@classmethod
def description(cls):
def description(self):
"""Return the PyTables description of this layout"""
desc = {}
for (n, (name, type)) in enumerate(cls .fields):
for (n, (name, type)) in enumerate(self .fields):
desc[name] = tables.Col.from_type(type, pos=n+1)
return tables.Description(desc)
@classmethod
def parse(cls, in_fields):
def parse(self, in_fields):
"""Given in_fields as text, return a list of values
converted to the correct types"""
# Consider overriding this in subclasses for speed?
out=[]
for (n, (name, type)) in enumerate(cls .fields):
for (n, (name, type)) in enumerate(self .fields):
if name == 'timestamp':
# special case: parse float, save as int
out.append(int(float(in_fields[n]) * 1e6))
@@ -29,9 +28,10 @@ class Layout(object):
out.append(max(0, min(65535, int(in_fields[n], 10))))
else:
raise TypeError("Can't parse type " + type)
return out
class PrepData(Layout):
expected_daily_rows = 120 * 8640 0
rate_hz = 12 0
fields = [ ( 'timestamp', 'int64' ),
( 'p1', 'float32' ),
( 'q1', 'float32'),
@@ -43,7 +43,7 @@ class PrepData(Layout):
( 'q7', 'float32') ]
class RawData(Layout):
expected_daily_rows = 8000 * 864 00
rate_hz = 80 00
fields = [ ( 'timestamp', 'int64'),
( 'va', 'uint16'),
( 'vb', 'uint16'),
@@ -53,16 +53,16 @@ class RawData(Layout):
( 'ic', 'uint16') ]
class RawNotchedData(Layout):
expected_daily_rows = 8000 * 864 00
rate_hz = 80 00
fields = RawData.fields + [ ( 'notch_ia', 'uint16' ),
( 'notch_ib', 'uint16' ),
( 'notch_ic', 'uint16' ) ]
# Build list of all layouts, so we can look them up by name
# Instantiate all layouts, indexed by their name
named = {}
for name, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj) and issubclass(obj, Layout):
named[name] = obj
named[name] = obj()
class Parser(object):
"""Object that parses and stores ASCII data for inclusion into the database"""
@@ -73,6 +73,10 @@ class Parser(object):
self.layout = named[layout]
self.data = []
self.nrows = 0
self.min_timestamp = None
self.max_timestamp = None
# Assume timestamp is always the first field, for now
self.ts_field = 0
def parse(self, textdata):
"""Parse the data, provided as lines of text, using the current
@@ -86,12 +90,27 @@ class Parser(object):
# In the future we might want to skip completely empty lines,
# or partial lines right before EOF?
try:
last_ts = None
for line in indata:
self.nrows += 1
# Parse and append
fields = line.partition('#')[0].split()
self.data.append(self.layout.parse(fields))
out = self.layout.parse(fields)
self.data.append(out)
# Verify timestamp
if self.ts_field is not None:
if last_ts is not None and out[self.ts_field] < last_ts:
raise ValueError("timestamp is not monotonically increasing")
last_ts = out[self.ts_field]
except (ValueError, TypeError, IndexError) as e:
raise TypeError("line " + self.nrows + ": " + e.message)
raise TypeError("line " + str(self.nrows) + ": " + e.message)
# Mark timestamp ranges
if len(self.data) and self.ts_field is not None:
self.min_timestamp = self.data[0][self.ts_field]
self.max_timestamp = self.data[-1][self.ts_field]
def fillrow(self, tablerow, rownum):
"""Fill a PyTables row object with the parsed data.