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.
 
 
 
 

178 lines
7.0 KiB

  1. #!/usr/bin/python
  2. import nilmdb.client
  3. from nilmdb.utils.printf import *
  4. from nilmdb.utils.time import (parse_time, timestamp_to_human,
  5. timestamp_to_seconds, seconds_to_timestamp,
  6. rate_to_period, now as time_now)
  7. import nilmtools
  8. import time
  9. import sys
  10. import re
  11. import argparse
  12. import subprocess
  13. class ParseError(Exception):
  14. def __init__(self, filename, error):
  15. msg = filename + ": " + error
  16. super(ParseError, self).__init__(msg)
  17. def parse_args(argv = None):
  18. parser = argparse.ArgumentParser(
  19. formatter_class = argparse.RawDescriptionHelpFormatter,
  20. version = nilmtools.__version__,
  21. description = """\
  22. Insert data from ethstream, either live (using the system time as a
  23. reference) or prerecorded (using comments in the file as a reference).
  24. The data is assumed to have been recorded at the specified rate.
  25. Small discrepencies between the accumulated timestamps and the
  26. reference time are ignored; larger discrepencies cause gaps to be
  27. created in the stream. Overlapping data returns an error.
  28. """)
  29. parser.add_argument("-u", "--url", action="store",
  30. default="http://localhost:12380/",
  31. help="NilmDB server URL (default: %(default)s)")
  32. parser.add_argument("-r", "--rate", action="store", default=8000,
  33. type=float,
  34. help="Data rate in Hz (default: %(default)s)")
  35. parser.add_argument("-l", "--live", action="store_true",
  36. help="Live capture; use system time to verify rate")
  37. parser.add_argument("path", action="store",
  38. help="Path of stream, e.g. /foo/bar")
  39. parser.add_argument("infile", type=argparse.FileType('r'), nargs='*',
  40. default=[sys.stdin],
  41. help="Input files (default: stdin)")
  42. args = parser.parse_args(argv or sys.argv)
  43. printf("Stream path: %s\n", args.path)
  44. printf(" Data rate: %s Hz\n", repr(args.rate))
  45. return args
  46. def main(argv = None):
  47. args = parse_args(argv)
  48. client = nilmdb.client.Client(args.url)
  49. # Local copies to save dictionary lookups
  50. live = args.live
  51. # data_ts is the timestamp that we'll use for the current line
  52. data_ts_base = 0
  53. data_ts_inc = 0
  54. data_ts_rate = args.rate
  55. # clock_ts is the imprecise "real" timestamp (from the filename,
  56. # comments, or or system clock)
  57. clock_ts = None
  58. def print_clock_updated():
  59. printf("Clock time updated to %s\n", timestamp_to_human(clock_ts))
  60. if data_ts_base != 0:
  61. diff = data_ts - clock_ts
  62. if diff >= 0:
  63. printf(" (data timestamp ahead by %.6f sec)\n",
  64. timestamp_to_seconds(diff))
  65. else:
  66. printf(" (data timestamp behind by %.6f sec)\n",
  67. timestamp_to_seconds(-diff))
  68. with client.stream_insert_context(args.path) as stream:
  69. for f in args.infile:
  70. filename = f.name
  71. printf("Processing %s\n", filename)
  72. # If the filename ends in .gz, open it with gzcat instead.
  73. if filename.endswith(".gz"):
  74. p = subprocess.Popen(["gzip", "-dc"],
  75. stdin = f, stdout = subprocess.PIPE)
  76. f = p.stdout
  77. # Try to get a real timestamp from the filename
  78. try:
  79. # Subtract 1 hour because files are created at the end
  80. # of the hour. Hopefully, we'll be able to use
  81. # internal comments and this value won't matter anyway.
  82. clock_ts = parse_time(filename) - seconds_to_timestamp(3600)
  83. print_clock_updated()
  84. except ValueError:
  85. pass
  86. truncated_lines = 0
  87. # Read each line
  88. for line in f:
  89. data_ts = data_ts_base + rate_to_period(data_ts_rate,
  90. data_ts_inc)
  91. # If no content other than the newline, skip it
  92. if len(line) <= 1:
  93. continue
  94. # If line starts with a comment, look for a timestamp
  95. if line[0] == '#':
  96. try:
  97. clock_ts = parse_time(line[1:])
  98. print_clock_updated()
  99. except ValueError:
  100. pass
  101. continue
  102. # If inserting live, use clock timestamp
  103. if live:
  104. clock_ts = time_now()
  105. # If we have a real timestamp, compare it to the data
  106. # timestamp, and make sure things match up.
  107. if clock_ts is not None:
  108. if (data_ts - seconds_to_timestamp(10)) > clock_ts:
  109. # Accumulated line timestamps are in the future.
  110. # If we were to set data_ts=clock_ts, we'd create
  111. # an overlap, so we have to just bail out here.
  112. err = sprintf("Data is coming in too fast: data time "
  113. "is %s but clock time is only %s",
  114. timestamp_to_human(data_ts),
  115. timestamp_to_human(clock_ts))
  116. raise ParseError(filename, err)
  117. if (data_ts + seconds_to_timestamp(10)) < clock_ts:
  118. # Accumulated line timetamps are in the past. We
  119. # can just skip some time and leave a gap in the
  120. # data.
  121. if data_ts_base != 0:
  122. printf("Skipping data timestamp forward from "
  123. "%s to %s to match clock time\n",
  124. timestamp_to_human(data_ts),
  125. timestamp_to_human(clock_ts))
  126. stream.finalize()
  127. data_ts_base = data_ts = clock_ts
  128. data_ts_inc = 0
  129. # Don't use this clock time anymore until we update it
  130. clock_ts = None
  131. if data_ts_base == 0:
  132. raise ParseError(filename, "No idea what timestamp to use")
  133. # This line is legit, so increment timestamp
  134. data_ts_inc += 1
  135. # Once in a while a line might be truncated, if we're at
  136. # the end of a file. Ignore it, but if we ignore too many,
  137. # bail out.
  138. if line[-1] != '\n':
  139. truncated_lines += 1
  140. if truncated_lines > 3:
  141. raise ParseError(filename, "too many short lines")
  142. printf("Ignoring short line in %s\n", filename)
  143. continue
  144. # Insert it
  145. stream.insert("%d %s" % (data_ts, line))
  146. print "Done"
  147. if __name__ == "__main__":
  148. main()