22 from subprocess
import Popen, PIPE
27 from diagnostic_msgs.msg
import DiagnosticArray, DiagnosticStatus, KeyValue
31 rospy.init_node(
"ntp_monitor")
34 stat = DiagnosticStatus()
35 stat.level = DiagnosticStatus.WARN
37 stat.message =
'No Data'
40 self.
msg = DiagnosticArray()
41 self.
msg.header.stamp = rospy.get_rostime()
42 self.
msg.status = [stat]
46 self.
diag_pub = rospy.Publisher(
"/diagnostics", DiagnosticArray, queue_size=1)
51 stat = DiagnosticStatus()
52 stat.level = DiagnosticStatus.WARN
54 stat.message =
'No Data'
60 p = Popen([
"ntpdate",
"-q", host], stdout=PIPE, stdin=PIPE, stderr=PIPE)
62 stdout, stderr = p.communicate()
64 stdout = stdout.decode()
65 stderr = stderr.decode()
66 except (UnicodeDecodeError, AttributeError):
70 st.level = DiagnosticStatus.ERROR
71 st.message =
'ntpdate Error'
72 st.values = [ KeyValue(key =
'ntpdate Error', value = stderr),
73 KeyValue(key =
'Output', value = stdout) ]
76 measured_offset = float(re.search(
"offset (.*),", stdout).group(1))*1000000
78 st.level = DiagnosticStatus.OK
80 st.values = [ KeyValue(
"NTP Server" , self.
ntp_server),
81 KeyValue(
"Offset (us)", str(measured_offset)),
82 KeyValue(
"Offset tolerance (us)", str(off)),
83 KeyValue(
"Offset tolerance (us) for Error", str(self.
error_offset)) ]
85 if (abs(measured_offset) > off):
86 st.level = DiagnosticStatus.WARN
87 st.message =
"NTP Offset Too High"
89 st.level = DiagnosticStatus.ERROR
90 st.message =
"NTP Offset Too High"
92 except Exception
as e:
93 stat.level = DiagnosticStatus.ERROR
94 stat.message =
'ntpdate Exception'
95 stat.values = [ KeyValue(key =
'Exception', value = str(e)), KeyValue(key =
'Traceback', value = str(traceback.format_exc())) ]
97 self.
msg = DiagnosticArray()
98 self.
msg.header.stamp = rospy.get_rostime()
99 self.
msg.status = [stat]
106 parser = optparse.OptionParser(usage=
"usage: ntp_monitor ntp-hostname []")
107 parser.add_option(
"--offset", dest=
"offset",
108 action=
"store", default=500,
109 help=
"Offset from NTP host", metavar=
"OFFSET")
110 parser.add_option(
"--error-offset", dest=
"error_offset",
111 action=
"store", default=5000000,
112 help=
"Offset from NTP host. Above this is error", metavar=
"OFFSET")
113 parser.add_option(
"--diag-hostname", dest=
"diag_hostname",
114 help=
"Computer name in diagnostics output (ex: 'c1')",
115 metavar=
"DIAG_HOSTNAME",
116 action=
"store", default=
None)
117 options, args = parser.parse_args(rospy.myargv())
120 parser.error(
"Invalid arguments. Must have HOSTNAME [args]. %s" % args)
121 print(
'Invalid arguments.')
125 offset = int(options.offset)
126 error_offset = int(options.error_offset)
128 parser.error(
"Offsets must be numbers")
129 print(
'Offsets must be numbers')
134 hostname = socket.gethostname()
138 self.
offset = rospy.get_param(
'~offset', offset)
142 if __name__ ==
"__main__":