nao_diagnostic_updater.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # This script collects diagnostic information on a Nao robot and publishes
4 # the information as DiagnosticArray messages.
5 #
6 # This script was written and tested with NaoQI 1.10.52 and will probably
7 # fail on 1.12+ as some ALMemory keys and device paths have changed.
8 #
9 # You can run this script either on the robot or on a remote machine,
10 # however, CPU temperature and network status will only be available if the
11 # script runs directly on the robot.
12 #
13 
14 # Copyright 2011-2012 Stefan Osswald, University of Freiburg
15 #
16 # Redistribution and use in source and binary forms, with or without
17 # modification, are permitted provided that the following conditions are met:
18 #
19 # # Redistributions of source code must retain the above copyright
20 # notice, this list of conditions and the following disclaimer.
21 # # Redistributions in binary form must reproduce the above copyright
22 # notice, this list of conditions and the following disclaimer in the
23 # documentation and/or other materials provided with the distribution.
24 # # Neither the name of the University of Freiburg nor the names of its
25 # contributors may be used to endorse or promote products derived from
26 # this software without specific prior written permission.
27 #
28 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
29 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
32 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
33 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
34 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
35 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
36 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38 # POSSIBILITY OF SUCH DAMAGE.
39 #
40 
41 
42 import rospy
43 
44 import dbus
45 from dbus.exceptions import DBusException
46 
47 from naoqi_driver.naoqi_node import NaoqiNode
48 import os
49 
50 from diagnostic_msgs.msg import *
51 
53  def __init__(self):
54  NaoqiNode.__init__(self, 'nao_diagnostic_updater')
55 
56  # ROS initialization:
57  self.connectNaoQi()
58 
59  # Read parameters:
60  self.sleep = 1.0/rospy.get_param('~update_rate', 0.5) # update rate in Hz
61  self.warningThreshold = rospy.get_param('~warning_threshold', 68) # warning threshold for joint temperatures
62  self.errorThreshold = rospy.get_param('~error_threshold', 74) # error threshold for joint temperatures
63  # check if NAOqi runs on the robot by checking whether the OS has aldebaran in its name
64  self.runsOnRobot = "aldebaran" in os.uname()[2] # if temperature device files cannot be opened, this flag will be set to False later.
65 
66  # Lists with interesting ALMemory keys
67  self.jointNamesList = self.motionProxy.getJointNames('Body')
70  for i in range(0, len(self.jointNamesList)):
71  self.jointTempPathsList.append("Device/SubDeviceList/%s/Temperature/Sensor/Value" % self.jointNamesList[i])
72  self.jointStiffPathsList.append("Device/SubDeviceList/%s/Hardness/Actuator/Value" % self.jointNamesList[i])
73 
74  self.batteryNamesList = ["Percentage", "Status"]
75  self.batteryPathsList = ["Device/SubDeviceList/Battery/Charge/Sensor/Value",
76  "Device/SubDeviceList/Battery/Charge/Sensor/Status",
77  "Device/SubDeviceList/Battery/Current/Sensor/Value"]
78 
79  self.deviceInfoList = ["Device/DeviceList/ChestBoard/BodyId"]
80  deviceInfoData = self.memProxy.getListData(self.deviceInfoList)
81  if(len(deviceInfoData) > 1 and isinstance(deviceInfoData[1], list)):
82  deviceInfoData = deviceInfoData[1]
83  if(deviceInfoData[0] is None or deviceInfoData[0] == 0):
84  # No device info -> running in a simulation
85  self.isSimulator = True
86  if(self.pip.startswith("127.") or self.pip == "localhost"):
87  # Replace localhost with hostname of the machine
88  f = open("/etc/hostname")
89  self.hardwareID = "%s:%d"%(f.readline().rstrip(), self.pport)
90  f.close()
91  else:
92  self.hardwareID = "%s:%d"%(self.pip, self.pport)
93  else:
94  self.hardwareID = deviceInfoData[0].rstrip()
95  self.isSimulator = False
96 
97  # init. messages:
98  self.diagnosticPub = rospy.Publisher("diagnostics", DiagnosticArray)
99 
100  rospy.loginfo("nao_diagnostic_updater initialized")
101 
102  # (re-) connect to NaoQI:
103  def connectNaoQi(self):
104  """ Connects to NaoQi and gets proxies to ALMotion and ALMemory. """
105  rospy.loginfo("Connecting to NaoQi at %s:%d", self.pip, self.pport)
106  self.motionProxy = self.get_proxy("ALMotion")
107  self.memProxy = self.get_proxy("ALMemory")
108  if self.motionProxy is None or self.memProxy is None:
109  exit(1)
110 
111  def run(self):
112  """ Diagnostic thread code - collects and sends out diagnostic data. """
113  while self.is_looping():
114  try:
115  # Get data from robot
116  jointsTempData = self.memProxy.getListData(self.jointTempPathsList)
117  jointsStiffData = self.memProxy.getListData(self.jointStiffPathsList)
118  batteryData = self.memProxy.getListData(self.batteryPathsList)
119  if isinstance(jointsTempData[1], list):
120  # Some naoqi versions provide data in [0, [data1, data2, ...], timestamp] format,
121  # others just as [data1, data2, ...]
122  # --> get data list
123  jointsTempData = jointsTempData[1]
124  jointsStiffData = jointsStiffData[1]
125  batteryData = batteryData[1]
126  except RuntimeError,e:
127  print "Error accessing ALMemory, exiting...\n"
128  print e
129  rospy.signal_shutdown("No NaoQI available anymore")
130 
131  diagnosticArray = DiagnosticArray()
132 
133  # Process joint temperature and stiffness
134  for i in range(0, len(self.jointTempPathsList)):
135  status = DiagnosticStatus()
136  status.hardware_id = "%s#%s"%(self.hardwareID, self.jointNamesList[i])
137  status.name = "nao_joint: %s" % self.jointNamesList[i]
138  kv = KeyValue()
139  kv.key = "Temperature"
140  temperature = jointsTempData[i]
141  kv.value = str(temperature)
142  status.values.append(kv)
143  kv = KeyValue()
144  kv.key = "Stiffness"
145  if self.jointNamesList[i] == "RHipYawPitch":
146  # same joint as LHipYawPitch, does not provide data
147  kv.value = "None"
148  else:
149  kv.value = str(jointsStiffData[i])
150  status.values.append(kv)
151  if (type(temperature) != float and type(temperature) != int) or self.jointNamesList[i] == "RHipYawPitch":
152  status.level = DiagnosticStatus.OK
153  status.message = "No temperature sensor"
154  elif temperature < self.warningThreshold:
155  status.level = DiagnosticStatus.OK
156  status.message = "Normal: %3.1f C" % temperature
157  elif temperature < self.errorThreshold:
158  status.level = DiagnosticStatus.WARN
159  status.message = "Hot: %3.1f C" % temperature
160  else:
161  status.level = DiagnosticStatus.ERROR
162  status.message = "Too hot: %3.1f C" % temperature
163  diagnosticArray.status.append(status)
164 
165  # Process battery status flags
166  status = DiagnosticStatus()
167  status.hardware_id = "%s#%s"%(self.hardwareID, "battery")
168  status.name ="nao_power: Battery"
169  kv = KeyValue()
170  kv.key = "Percentage"
171  if batteryData[0] is None:
172  kv.value = "unknown"
173  else:
174  kv.value = str(batteryData[0] * 100)
175  status.values.append(kv)
176 
177  # Battery status: See http://www.aldebaran-robotics.com/documentation/naoqi/sensors/dcm/pref_file_architecture.html?highlight=discharge#charge-for-the-battery
178  # Note: SANYO batteries use different keys!
179  statuskeys = ["End off Discharge flag", "Near End Off Discharge flag", "Charge FET on", "Discharge FET on", "Accu learn flag", "Discharging flag", "Full Charge Flag", "Charge Flag", "Charge Temperature Alarm", "Over Charge Alarm", "Discharge Alarm", "Charge Over Current Alarm", "Discharge Over Current Alarm (14A)", "Discharge Over Current Alarm (6A)", "Discharge Temperature Alarm", "Power-Supply present" ]
180 
181  for j in range(0, 16):
182  kv = KeyValue()
183  kv.key = statuskeys[j]
184  if batteryData[1] is None:
185  kv.value = "unknown"
186  elif int(batteryData[1]) & 1<<j:
187  kv.value = "True"
188  else:
189  kv.value = "False"
190  status.values.append(kv)
191 
192  kv = KeyValue()
193  kv.key = "Status"
194  if batteryData[1] is None:
195  kv.value = "unknown"
196  else:
197  kv.value = bin(int(batteryData[1]))
198  status.values.append(kv)
199 
200  # Process battery charge level
201  if batteryData[0] is None:
202  status.level = DiagnosticStatus.OK
203  status.message = "Battery status unknown, assuming simulation"
204  elif int(batteryData[1]) & 1<<6:
205  status.level = DiagnosticStatus.OK
206  status.message = "Battery fully charged"
207  elif int(batteryData[1]) & 1<<7:
208  status.level = DiagnosticStatus.OK
209  status.message = "Charging (%3.1f%%)" % (batteryData[0] * 100)
210  elif batteryData[0] > 0.60:
211  status.level = DiagnosticStatus.OK
212  status.message = "Battery OK (%3.1f%% left)" % (batteryData[0] * 100)
213  elif batteryData[0] > 0.30:
214  status.level = DiagnosticStatus.WARN
215  status.message = "Battery discharging (%3.1f%% left)" % (batteryData[0] * 100)
216  else:
217  status.level = DiagnosticStatus.ERROR
218  status.message = "Battery almost empty (%3.1f%% left)" % (batteryData[0] * 100)
219  diagnosticArray.status.append(status)
220 
221  # Process battery current
222  status = DiagnosticStatus()
223  status.hardware_id = "%s#%s"%(self.hardwareID, "power")
224  status.name = "nao_power: Current"
225 
226  if batteryData[2] is None:
227  status.level = DiagnosticStatus.OK
228  status.message = "Total Current: unknown"
229  else:
230  kv = KeyValue()
231  kv.key = "Current"
232  kv.value = str(batteryData[2])
233  status.values.append(kv)
234  status.level = DiagnosticStatus.OK
235  if batteryData[2] > 0:
236  currentStatus = "charging"
237  else:
238  currentStatus = "discharging"
239  status.message = "Total Current: %3.2f Ampere (%s)" % (batteryData[2], currentStatus)
240  diagnosticArray.status.append(status)
241 
242  # Process CPU and head temperature
243  status = DiagnosticStatus()
244  status.hardware_id = "%s#%s"%(self.hardwareID, "cpu")
245  status.name = "nao_cpu: Head Temperature"
246  temp = self.temp_get()
247  kv = KeyValue()
248  kv.key = "CPU Temperature"
249  kv.value = str(temp['HeadSilicium'])
250  status.values.append(kv)
251  kv = KeyValue()
252  kv.key = "Board Temperature"
253  kv.value = str(temp['HeadBoard'])
254  status.values.append(kv)
255  if(temp['HeadSilicium'] == "invalid"):
256  status.level = DiagnosticStatus.OK
257  status.message = "unknown, assuming simulation"
258  else:
259  status.message = "%3.2f deg C" % (temp['HeadSilicium'])
260  if temp['HeadSilicium'] < 100:
261  status.level = DiagnosticStatus.OK
262  elif temp['HeadSilicium'] < 105:
263  status.level = DiagnosticStatus.WARN
264  else:
265  status.level = DiagnosticStatus.ERROR
266  diagnosticArray.status.append(status)
267 
268  # Process WIFI and LAN status
269  statusWifi = DiagnosticStatus()
270  statusWifi.hardware_id = "%s#%s"%(self.hardwareID, "wlan")
271  statusWifi.name = "nao_wlan: Status"
272 
273  statusLan = DiagnosticStatus()
274  statusLan.hardware_id = "%s#%s"%(self.hardwareID, "ethernet")
275  statusLan.name = "nao_ethernet: Status"
276 
277  if self.runsOnRobot:
278  statusLan.level = DiagnosticStatus.ERROR
279  statusLan.message = "offline"
280  statusWifi.level = DiagnosticStatus.ERROR
281  statusWifi.message = "offline"
282  systemBus = dbus.SystemBus()
283  try:
284  manager = dbus.Interface(systemBus.get_object("org.moblin.connman", "/"), "org.moblin.connman.Manager")
285  except DBusException as e:
286  self.runsOnRobot = False
287  if self.runsOnRobot:
288  properties = manager.GetProperties()
289  for property in properties["Services"]:
290  service = dbus.Interface(systemBus.get_object("org.moblin.connman", property), "org.moblin.connman.Service")
291  try:
292  props = service.GetProperties()
293  except DBusException as e:
294  continue # WLAN network probably disappeared meanwhile
295 
296  state = str(props.get("State", "None"))
297  if state == "idle":
298  pass # other network, not connected
299  else:
300  nettype = str(props.get("Type", "<unknown>"))
301  if(nettype == "wifi"):
302  status = statusWifi
303  else:
304  status = statusLan
305  kv = KeyValue()
306  kv.key = "Network"
307  kv.value = str(props.get("Name", "<unknown>"))
308  status.values.append(kv)
309  if nettype == "wifi":
310  strength = int(props.get("Strength", "<unknown>"))
311  kv = KeyValue()
312  kv.key = "Strength"
313  kv.value = "%d%%"%strength
314  status.values.append(kv)
315  else:
316  strength = None
317  kv = KeyValue()
318  kv.key = "Type"
319  kv.value = nettype
320  status.values.append(kv)
321  if strength is None:
322  status.message = state
323  else:
324  status.message = "%s (%d%%)"%(state, strength)
325 
326  if state in ["online", "ready"]:
327  status.level = DiagnosticStatus.OK
328  elif state in ["configuration", "association"]:
329  status.level = DiagnosticStatus.WARN
330  else: # can only be 'failure'
331  status.message = str("%s (%s)"%(state, props.get("Error")))
332  status.level = DiagnosticStatus.ERROR
333  else:
334  statusWifi.level = DiagnosticStatus.OK
335  statusWifi.message = "nao_diagnostic_updater not running on robot, cannot determine WLAN status"
336 
337  statusLan.level = DiagnosticStatus.OK
338  statusLan.message = "nao_diagnostic_updater not running on robot, cannot determine Ethernet status"
339 
340  diagnosticArray.status.append(statusWifi)
341  diagnosticArray.status.append(statusLan)
342 
343  # Publish all diagnostic messages
344  diagnosticArray.header.stamp = rospy.Time.now()
345  self.diagnosticPub.publish(diagnosticArray)
346  rospy.sleep(self.sleep)
347 
348  def temp_get(self):
349  """Read the CPU and head temperature from the system devices.
350 
351  Returns {'HeadSilicium': <temperature>, 'HeadBoard': <temperature>}
352 
353  Temperatures are returned as float values in degrees Celsius, or
354  as the string 'invalid' if the sensors are not accessible.
355  """
356  temp = {'HeadSilicium': 'invalid', 'HeadBoard': 'invalid'}
357  if(self.runsOnRobot):
358  try:
359  f = open("/sys/class/i2c-adapter/i2c-1/1-004c/temp2_input")
360  temp['HeadSilicium'] = float(f.readline()) / 1000.
361  f.close()
362  except IOError:
363  self.runsOnRobot = False
364  return temp
365  except:
366  temp['HeadSilicium'] = "invalid"
367  try:
368  f = open("/sys/class/i2c-adapter/i2c-1/1-004c/temp1_input")
369  temp['HeadBoard'] = float(f.readline()) / 1000.
370  f.close()
371  except IOError:
372  self.runsOnRobot = False
373  return temp
374  except:
375  temp['HeadBoard'] = "invalid"
376  return temp
377 
378 
379 if __name__ == '__main__':
380 
382  updater.start()
383 
384  rospy.spin()
385  exit(0)
def get_proxy(self, name, warn=True)


nao_apps
Author(s):
autogenerated on Mon Jun 10 2019 14:04:55