3131# of Clearpath Robotics.
3232import os
3333
34+ from apt import Cache
35+
3436from clearpath_config .clearpath_config import ClearpathConfig
3537from clearpath_config .common .types .platform import Platform
3638from clearpath_config .common .utils .dictionary import merge_dict , replace_dict_items
39+ from clearpath_config .sensors .types .cameras import BaseCamera , IntelRealsense
40+ from clearpath_config .sensors .types .gps import BaseGPS
41+ from clearpath_config .sensors .types .imu import BaseIMU , PhidgetsSpatial
42+ from clearpath_config .sensors .types .lidars_2d import BaseLidar2D
43+ from clearpath_config .sensors .types .lidars_3d import BaseLidar3D
44+ from clearpath_config .sensors .types .sensor import BaseSensor
3745from clearpath_generator_common .common import Package , ParamFile
3846from clearpath_generator_common .param .writer import ParamWriter
47+ from clearpath_generator_common .ros import ROS_DISTRO
3948
4049
4150class PlatformParam ():
4251 CONTROL = 'control'
52+ DIAGNOSTIC_AGGREGATOR = 'diagnostic_aggregator'
53+ DIAGNOSTIC_UPDATER = 'diagnostic_updater'
4354 IMU_FILTER = 'imu_filter'
4455 LOCALIZATION = 'localization'
4556 TELEOP_INTERACTIVE_MARKERS = 'teleop_interactive_markers'
4657 TELEOP_JOY = 'teleop_joy'
4758 TWIST_MUX = 'twist_mux'
4859
60+ NOT_APPLICABLE = 'not_applicable'
61+
4962 PARAMETERS = [
5063 CONTROL ,
64+ DIAGNOSTIC_AGGREGATOR ,
65+ DIAGNOSTIC_UPDATER ,
5166 IMU_FILTER ,
5267 LOCALIZATION ,
5368 TELEOP_INTERACTIVE_MARKERS ,
@@ -57,6 +72,7 @@ class PlatformParam():
5772
5873 class BaseParam ():
5974 CLEARPATH_CONTROL = 'clearpath_control'
75+ CLEARPATH_DIAGNOSTICS = 'clearpath_diagnostics'
6076
6177 def __init__ (self ,
6278 parameter : str ,
@@ -169,6 +185,127 @@ def __init__(self,
169185 self .default_parameter_file_package = self .clearpath_control_package
170186 self .default_parameter_file_path = 'config'
171187
188+ class DiagnosticsAggregatorParam (BaseParam ):
189+ """Parameter file that decides the aggregation of the diagnostics data for display."""
190+
191+ def __init__ (self ,
192+ parameter : str ,
193+ clearpath_config : ClearpathConfig ,
194+ param_path : str ) -> None :
195+ super ().__init__ (parameter , clearpath_config , param_path )
196+ self .default_parameter_file_package = Package (self .CLEARPATH_DIAGNOSTICS )
197+ self .default_parameter_file_path = 'config'
198+
199+ class DiagnosticsUpdaterParam (BaseParam ):
200+ """Parameter file for Clearpath Diagnostics indicating which topics to monitor."""
201+
202+ DIAGNOSTIC_UPDATER_NODE = 'clearpath_diagnostic_updater'
203+
204+ def __init__ (self ,
205+ parameter : str ,
206+ clearpath_config : ClearpathConfig ,
207+ param_path : str ) -> None :
208+ super ().__init__ (parameter , clearpath_config , param_path )
209+ self .default_parameter_file_package = Package (self .CLEARPATH_DIAGNOSTICS )
210+ self .default_parameter_file_path = 'config'
211+ self .diag_dict = {}
212+
213+ def generate_parameters (self , use_sim_time : bool = False ) -> None :
214+ super ().generate_parameters (use_sim_time )
215+
216+ # Read the default parameter file
217+ self .default_param_file = ParamFile (
218+ name = self .default_parameter ,
219+ package = self .default_parameter_file_package ,
220+ path = self .default_parameter_file_path ,
221+ parameters = {}
222+ )
223+ self .default_param_file .read ()
224+
225+ # Initialize parameters with the default parameters
226+ self .param_file .parameters = self .default_param_file .parameters
227+
228+ # Update parameters based on the robot.yaml
229+ platform_model = self .clearpath_config .get_platform_model ()
230+ self .param_file .update ({self .DIAGNOSTIC_UPDATER_NODE : {
231+ 'serial_number' : self .clearpath_config .get_serial_number (),
232+ 'platform_model' : platform_model }})
233+
234+ if use_sim_time :
235+ latest_apt_firmware_version = 'simulated'
236+ installed_apt_firmware_version = 'simulated'
237+ elif platform_model == Platform .A200 :
238+ latest_apt_firmware_version = PlatformParam .NOT_APPLICABLE
239+ installed_apt_firmware_version = PlatformParam .NOT_APPLICABLE
240+ else :
241+ # Check latest firmware version available and save it in the config
242+ cache = Cache ()
243+ latest_apt_firmware_version = 'not_found'
244+ installed_apt_firmware_version = 'none'
245+ try :
246+ pkg = cache [f'ros-{ ROS_DISTRO } -clearpath-firmware' ]
247+ latest_apt_firmware_version = pkg .versions [0 ].version .split ('-' )[0 ]
248+ if (pkg .is_installed ):
249+ installed_apt_firmware_version = pkg .installed .version .split ('-' )[0 ]
250+ except KeyError :
251+ print (f'\033 [93mWarning: ros-{ ROS_DISTRO } -clearpath-firmware'
252+ ' package not found\033 [0m' )
253+
254+ self .param_file .update ({self .DIAGNOSTIC_UPDATER_NODE : {
255+ 'ros_distro' : ROS_DISTRO ,
256+ 'latest_apt_firmware_version' : latest_apt_firmware_version ,
257+ 'installed_apt_firmware_version' : installed_apt_firmware_version }})
258+
259+ # List all topics to be monitored from each launched sensor
260+ for sensor in self .clearpath_config .sensors .get_all_sensors ():
261+
262+ if not sensor .launch_enabled :
263+ continue
264+
265+ match sensor :
266+ case IntelRealsense ():
267+ if sensor .color_enabled :
268+ self .add_topic (sensor , sensor .TOPICS .COLOR_IMAGE )
269+ if sensor .depth_enabled :
270+ self .add_topic (sensor , sensor .TOPICS .DEPTH_IMAGE )
271+ if sensor .pointcloud_enabled :
272+ self .add_topic (sensor , sensor .TOPICS .POINTCLOUD )
273+
274+ case BaseCamera ():
275+ self .add_topic (sensor , sensor .TOPICS .COLOR_IMAGE )
276+
277+ case BaseLidar2D ():
278+ self .add_topic (sensor , sensor .TOPICS .SCAN )
279+
280+ case BaseLidar3D ():
281+ self .add_topic (sensor , sensor .TOPICS .SCAN )
282+ self .add_topic (sensor , sensor .TOPICS .POINTS )
283+
284+ case PhidgetsSpatial ():
285+ self .add_topic (sensor , sensor .TOPICS .RAW_DATA ),
286+ self .add_topic (sensor , sensor .TOPICS .MAG ),
287+
288+ case BaseIMU ():
289+ self .add_topic (sensor , sensor .TOPICS .DATA )
290+ self .add_topic (sensor , sensor .TOPICS .MAG )
291+
292+ case BaseGPS ():
293+ self .add_topic (sensor , sensor .TOPICS .FIX )
294+
295+ # Output the list of topics into the parameter file
296+ self .param_file .update ({self .DIAGNOSTIC_UPDATER_NODE : {'topics' : self .diag_dict }})
297+
298+ def add_topic (self , sensor : BaseSensor , topic_key : str ) -> None :
299+ """
300+ Add a sensor topic to the dictionary using the topic key string.
301+
302+ :param sensor: The sensor object from which the topic info will be gotten
303+ :param topic_key: The key used to identify the topic to be monitored
304+ """
305+ self .diag_dict [sensor .get_topic_name (topic_key , local = True )] = {
306+ 'type' : sensor .get_topic_type (topic_key ),
307+ 'rate' : float (sensor .get_topic_rate (topic_key ))}
308+
172309 class LocalizationParam (BaseParam ):
173310 EKF_NODE = 'ekf_node'
174311 imu_config = [False , False , False ,
@@ -247,6 +384,8 @@ def __init__(self,
247384
248385 PARAMETER = {
249386 IMU_FILTER : ImuFilterParam ,
387+ DIAGNOSTIC_AGGREGATOR : DiagnosticsAggregatorParam ,
388+ DIAGNOSTIC_UPDATER : DiagnosticsUpdaterParam ,
250389 LOCALIZATION : LocalizationParam ,
251390 TELEOP_JOY : TeleopJoyParam ,
252391 TWIST_MUX : TwistMuxParam ,
0 commit comments