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.
 
 
 
 

179 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():
  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()
  43. printf("Stream path: %s\n", args.path)
  44. printf(" Data rate: %s Hz\n", repr(args.rate))
  45. return args
  46. def main(args = None):
  47. if args is None:
  48. args = parse_args()
  49. client = nilmdb.client.Client(args.url)
  50. # Local copies to save dictionary lookups
  51. live = args.live
  52. # data_ts is the timestamp that we'll use for the current line
  53. data_ts_base = 0
  54. data_ts_inc = 0
  55. data_ts_rate = args.rate
  56. # clock_ts is the imprecise "real" timestamp (from the filename,
  57. # comments, or or system clock)
  58. clock_ts = None
  59. def print_clock_updated():
  60. printf("Clock time updated to %s\n", timestamp_to_human(clock_ts))
  61. if data_ts_base != 0:
  62. diff = data_ts - clock_ts
  63. if diff >= 0:
  64. printf(" (data timestamp ahead by %.6f sec)\n",
  65. timestamp_to_seconds(diff))
  66. else:
  67. printf(" (data timestamp behind by %.6f sec)\n",
  68. timestamp_to_seconds(-diff))
  69. with client.stream_insert_context(args.path) as stream:
  70. for f in args.infile:
  71. filename = f.name
  72. printf("Processing %s\n", filename)
  73. # If the filename ends in .gz, open it with gzcat instead.
  74. if filename.endswith(".gz"):
  75. p = subprocess.Popen(["gzip", "-dc"],
  76. stdin = f, stdout = subprocess.PIPE)
  77. f = p.stdout
  78. # Try to get a real timestamp from the filename
  79. try:
  80. # Subtract 1 hour because files are created at the end
  81. # of the hour. Hopefully, we'll be able to use
  82. # internal comments and this value won't matter anyway.
  83. clock_ts = parse_time(filename) - seconds_to_timestamp(3600)
  84. print_clock_updated()
  85. except ValueError:
  86. pass
  87. truncated_lines = 0
  88. # Read each line
  89. for line in f:
  90. data_ts = data_ts_base + rate_to_period(data_ts_rate,
  91. data_ts_inc)
  92. # If no content other than the newline, skip it
  93. if len(line) <= 1:
  94. continue
  95. # If line starts with a comment, look for a timestamp
  96. if line[0] == '#':
  97. try:
  98. clock_ts = parse_time(line[1:])
  99. print_clock_updated()
  100. except ValueError:
  101. pass
  102. continue
  103. # If inserting live, use clock timestamp
  104. if live:
  105. clock_ts = time_now()
  106. # If we have a real timestamp, compare it to the data
  107. # timestamp, and make sure things match up.
  108. if clock_ts is not None:
  109. if (data_ts - seconds_to_timestamp(10)) > clock_ts:
  110. # Accumulated line timestamps are in the future.
  111. # If we were to set data_ts=clock_ts, we'd create
  112. # an overlap, so we have to just bail out here.
  113. err = sprintf("Data is coming in too fast: data time "
  114. "is %s but clock time is only %s",
  115. timestamp_to_human(data_ts),
  116. timestamp_to_human(clock_ts))
  117. raise ParseError(filename, err)
  118. if (data_ts + seconds_to_timestamp(10)) < clock_ts:
  119. # Accumulated line timetamps are in the past. We
  120. # can just skip some time and leave a gap in the
  121. # data.
  122. if data_ts_base != 0:
  123. printf("Skipping data timestamp forward from "
  124. "%s to %s to match clock time\n",
  125. timestamp_to_human(data_ts),
  126. timestamp_to_human(clock_ts))
  127. stream.finalize()
  128. data_ts_base = data_ts = clock_ts
  129. data_ts_inc = 0
  130. # Don't use this clock time anymore until we update it
  131. clock_ts = None
  132. if data_ts_base == 0:
  133. raise ParseError(filename, "No idea what timestamp to use")
  134. # This line is legit, so increment timestamp
  135. data_ts_inc += 1
  136. # Once in a while a line might be truncated, if we're at
  137. # the end of a file. Ignore it, but if we ignore too many,
  138. # bail out.
  139. if line[-1] != '\n':
  140. truncated_lines += 1
  141. if truncated_lines > 3:
  142. raise ParseError(filename, "too many short lines")
  143. printf("Ignoring short line in %s\n", filename)
  144. continue
  145. # Insert it
  146. stream.insert("%d %s" % (data_ts, line))
  147. print "Done"
  148. if __name__ == "__main__":
  149. main()