-
Notifications
You must be signed in to change notification settings - Fork 1
/
ctrl_interface.py
1372 lines (1065 loc) · 63.7 KB
/
ctrl_interface.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
#########Just in case the comments are translated to Spanish: # -*- coding: utf-8 -*-
""" The following code implements an Adaptor Interface Node written in Python for Robot Operating System.
This module is meant to provide communication between any application able to communicate with this node
and any camera device drivers with the appropriate file configuration in the "translation.yaml" file.
"""
###############################################################################
## ***** ***** Initially needed constants ***** *****
###############################################################################
# Constants
globals()["PACKAGE_NAME"] = 'my_adaptor'
###############################################################################
###############################################################################
## ***** ***** CONFIGURATION PARAMETERS ***** *****
###############################################################################
## Keys
globals()["KEY_CONFIG_FILENAME"] = "property_config_file"
globals()["KEY_CONFIG_TEST_FILENAME"] = "property_test_file"
globals()["KEY_TOPIC_TIMEOUT"] = "topic_time_out"
globals()["KEY_SERVICE_TIMEOUT"] = "service_time_out"
globals()["KEY_GET_STRING_SERVICE"] = "get_string_service_name"
globals()["KEY_GET_INT_SERVICE"] = "get_int_service_name"
globals()["KEY_GET_FLOAT_SERVICE"] = "get_float_service_name"
globals()["KEY_GET_BOOL_SERVICE"] = "get_bool_service_name"
globals()["KEY_GET_TOPIC_LOCATION"] = "get_topic_location_service_name"
globals()["KEY_GET_IMAGE"] = "get_image_service_name"
globals()["KEY_GET_DISP_IMAGE"] = "get_disp_image_service_name"
globals()["KEY_SET_STRING_SERVICE"] = "set_string_service_name"
globals()["KEY_SET_INT_SERVICE"] = "set_int_service_name"
globals()["KEY_SET_FLOAT_SERVICE"] = "set_float_service_name"
globals()["KEY_SET_BOOL_SERVICE"] = "set_bool_service_name"
globals()["KEY_SET_TOPIC_LOCATION"] = "set_topic_location_service_name"
globals()["KEY_PUB_IMAGE"] = "pub_image_service_name"
globals()["KEY_PUB_DISP_IMAGE"] = "pub_disp_image_service_name"
## Defaults
globals()["DEFAULT_CONFIG_FILENAME"] = "propertyConfigFile.yaml"
globals()["DEFAULT_CONFIG_TEST_FILENAME"] = "propertyTestFile.yaml"
globals()["DEFAULT_TOPIC_TIMEOUT"] = 3
globals()["DEFAULT_SERVICE_TIMEOUT"] = 3
globals()["DEFAULT_GET_STRING_SERVICE"] = "get/StringProperty"
globals()["DEFAULT_GET_INT_SERVICE"] = "get/IntProperty"
globals()["DEFAULT_GET_FLOAT_SERVICE"] = "get/FloatProperty"
globals()["DEFAULT_GET_BOOL_SERVICE"] = "get/BoolProperty"
globals()["DEFAULT_GET_DISP_IMAGE"] = "get/DispImage"
globals()["DEFAULT_GET_IMAGE"] = "get/Image"
globals()["DEFAULT_GET_TOPIC_LOCATION"] = "get/TopicLocation"
globals()["DEFAULT_SET_STRING_SERVICE"] = "set/StrProperty"
globals()["DEFAULT_SET_INT_SERVICE"] = "set/IntProperty"
globals()["DEFAULT_SET_FLOAT_SERVICE"] = "set/FloatProperty"
globals()["DEFAULT_SET_BOOL_SERVICE"] = "set/BoolProperty"
globals()["DEFAULT_SET_TOPIC_LOCATION"] = "set/TopicLocation"
globals()["DEFAULT_PUB_IMAGE"] = "publishImages"
globals()["DEFAULT_PUB_DISP_IMAGE"] = "publishDispImages"
# Global values
globals()["propertyConfigFile"] = globals()["DEFAULT_CONFIG_FILENAME"]
globals()["propertyTestFile"] = globals()["DEFAULT_CONFIG_TEST_FILENAME"]
globals()["topicTimeOut"] = globals()["DEFAULT_TOPIC_TIMEOUT"]
globals()["serviceTimeOut"] = globals()["DEFAULT_SERVICE_TIMEOUT"]
globals()["getStrSrv"] = globals()["DEFAULT_GET_STRING_SERVICE"]
globals()["getIntSrv"] = globals()["DEFAULT_GET_INT_SERVICE"]
globals()["getFloatSrv"] = globals()["DEFAULT_GET_FLOAT_SERVICE"]
globals()["getBoolSrv"] = globals()["DEFAULT_GET_BOOL_SERVICE"]
globals()["getLocationSrv"] = globals()["DEFAULT_GET_TOPIC_LOCATION"]
globals()["getImgSrv"] = globals()["DEFAULT_GET_IMAGE"]
globals()["getDispImgSrv"] = globals()["DEFAULT_GET_DISP_IMAGE"]
globals()["setStrSrv"] = globals()["DEFAULT_SET_STRING_SERVICE"]
globals()["setIntSrv"] = globals()["DEFAULT_SET_INT_SERVICE"]
globals()["setFloatSrv"] = globals()["DEFAULT_SET_FLOAT_SERVICE"]
globals()["setBoolSrv"] = globals()["DEFAULT_SET_BOOL_SERVICE"]
globals()["setLocationSrv"] = globals()["DEFAULT_SET_TOPIC_LOCATION"]
globals()["pubImgSrv"] = globals()["DEFAULT_PUB_IMAGE"]
globals()["pubDispImgSrv"] = globals()["DEFAULT_PUB_DISP_IMAGE"]
###############################################################################
###############################################################################
## ***** ***** External modules to import ***** *****
###############################################################################
# **** Standard Python Utility Modules
#==============================================================================
import roslib; roslib.load_manifest(PACKAGE_NAME)
import rospy
import sys
# March19 - YAML library for configuration file.
import yaml
# April17 - Command line calls for launching nodes
import subprocess
import shlex
###############################################################################
# **** ROS-specific objects and definitions to import
#==============================================================================
#from dynamic_reconfigure.encoding import Config as dynReconfConfig
# January23 - Including services
from my_adaptor.srv import *
# January 24 - Including ALL the standard types
#(I think they'll all be necessary at some point)
from std_msgs.msg import *
# January31 - Dynamic Reconfigure
from dynamic_reconfigure.client import Client as DynamicReconfigureClient
from dynamic_reconfigure.server import Server as DynamicReconfigureServer
from my_adaptor.cfg import PropertiesConfig
# February14
from stereo_msgs.msg import *
from sensor_msgs.msg import *
###############################################################################
###############################################################################
## ***** ***** This file is divided in three classes: ***** *****
###############################################################################
# # # - Driver manager (communicate with layer below)
# # # - Interface node (communicate with layer above)
# # # - Property translator
###############################################################################
## ***** ***** ((Self agreement)): ***** *****
###############################################################################
# # # *** ( Not being followed anymore by the moment ) ***
# "None" values mean that some Error was already reported
# "False", negative or empty strings (depending on the place) mean that there was an error but still unknown
#Reasons:
#In case an error is not really taken into account; "None" will produce a second error that will be noticeable
#Some structures haven't got any "empty" element to return but in the lowest level is sometimes possible to just
#check if something is possible; read whatever the return value is (can be external) and answer whatever
#In the lowest level, it should be possible to ask to check something: "if isThatThingWorking():" and get True
#or False as the answer: but it's not an error that the answer is False, the error is finding that it shouldn't!
###############################################################################
## **** **** To avoid getting stuck in config updates: **** ****
###############################################################################
#avoidSelfReconf = 0
#avoidRemoteReconf = 0
# These vbles are incremented as many times as propagations to avoid. When below 1,
#then there are real callbacks:
# # avoidSelfReconf ++;
# # ** UNEXPECTED REAL CALLBACK **
# # ** real callback call avoided **
# # update_config(...)
# # ** second callback updates everything
#==============================================================================
###############################################################################
## ***** ***** Beginning of the executable code ***** *****
###############################################################################
# **** Assigning values to "constants" (used as literals):
#==============================================================================
## The names of the equivalent data types in python native types and
# ROS basic data types.
remoteType = {str: String, int: UInt16, float: Float64, bool: Bool}
## List of known kinds of properties
# The interface knows how to handle these properties and only these.
kindOfProperty = set(["dynParam", "parameter", "topic", "service", "virtual", "renaming"]) ## "publishedtopic" removed
##ppty = {"reference":0, "type":1, "kind":2}
PPTY_REF = 0# ppty[0]
PPTY_TYPE = 1# ppty[1]
PPTY_KIND = 2# ppty[2]
###############################################################################
###############################################################################
## ***** ***** img_interface_node Class ***** *****
###############################################################################
class img_interface_node: ## This is the LISTENER for the layer ABOVE
"""The 'img_interface_node' is the class which communicates to the layer above
according to this structure. It provides services and dynamic reconfigure server
and also publishes topics."""
###############################################
## ***** Static Data Members *****
###############################################
# **** Services provided to the above layer
#==================================================
listOf_services = {}
###############################################
## ***** Constructor Method *****
###############################################
def __init__(self, translator, driverMgr):
"""initialises img_interface_node instances by launching the dynamic reconfigure server
until it is called to launch services for listening. It doesn't receive or return
anything.
@param self - self object for the call.
@param translator - a reference to the translator object/instance to be used.
@param driverMgr - a reference to the object/instance of the driver manager to
be used (bottom layer)."""
self.translator = translator
self.driverMgr = driverMgr
self.avoidRemoteReconf = 0
try:
## Dynamic Reconfigure
self.avoidRemoteReconf = self.avoidRemoteReconf + 1
# Launch self DynamicReconfigure server
self.dynServer = DynamicReconfigureServer(PropertiesConfig, self.dynServerCallback)
# Launch request listener services
self.listenToRequests()
rospy.loginfo("Interface for requests created.")
except:
rospy.logerr("img_interface_node::Error: while trying to initialise dynamic parameter server.")
raise
return
###################################################
## ***** General Purpose Methods *****
###################################################
# **** Launch services for receiving requests
#==================================================
def listenToRequests(self):
"""Main services creator method in order to remain listening for requests.
It receives nothing and returns true just for future needs.
@param self - self object for the call.
@return True if it finishes. Otherwise it would just raise an exception
as there are no checks."""
self.listOf_services[getStrSrv] = rospy.Service(getStrSrv, stringValue, self.getStringProperty)
self.listOf_services[getIntSrv] = rospy.Service(getIntSrv, intValue, self.getIntProperty)
self.listOf_services[getFloatSrv] = rospy.Service(getFloatSrv, floatValue, self.getFloatProperty)
self.listOf_services[getBoolSrv] = rospy.Service(getBoolSrv, booleanValue, self.getBoolProperty)
##MICHI: 14Feb2012
self.listOf_services[getImgSrv] = rospy.Service(getImgSrv, normalImage, self.getImage)
self.listOf_services[getDispImgSrv] = rospy.Service(getDispImgSrv, disparityImage, self.getDispImage)
##MICHI: 13Mar2012
self.listOf_services[pubImgSrv] = rospy.Service(pubImgSrv, requestTopic, self.publishImages)
self.listOf_services[pubDispImgSrv] = rospy.Service(pubDispImgSrv, requestTopic, self.publishDispImages)
##MICHI: 16Apr2012
self.listOf_services[getLocationSrv] = rospy.Service(getLocationSrv, stringValue, self.getTopicLocation)
self.listOf_services[setLocationSrv] = rospy.Service(setLocationSrv, setString, self.setTopicLocation)
self.listOf_services[setStrSrv] = rospy.Service(setStrSrv, setString, self.setStrProperty)
self.listOf_services[setIntSrv] = rospy.Service(setIntSrv, setInteger, self.setIntProperty)
self.listOf_services[setFloatSrv] = rospy.Service(setFloatSrv, setFloat, self.setFloatProperty)
self.listOf_services[setBoolSrv] = rospy.Service(setBoolSrv, setBoolean, self.setBoolProperty)
rospy.loginfo("Ready to answer service requests.")
return True
###################################################
# **** Dynamic Reconfigure related methods
#==================================================
def dynServerCallback(self, dynConfiguration, levelCode):
"""Handler for the changes in the Dynamic Reconfigure server.
Must return a configuration in the same format as received.
@param self - self object for the call.
@param dynConfiguration - a dictionary of {property:value} with the changes to be done.
Precondition: the names of the properties are the LOCAL names.
@param levelCode - the code generated during the callback as a hint of which group of
properties where changed.
@return The dictionary with the final configuration (as the callback requires to)."""
if self.avoidRemoteReconf > 0:
rospy.loginfo("LOCAL configuration was changed by self (levelCode = %s)"%levelCode)
self.avoidRemoteReconf = self.avoidRemoteReconf - 1
elif levelCode != 0:
rospy.loginfo("LOCAL configuration changing.".rjust(80, '_'))
for elem in dynConfiguration:
if self.translator.canSet(elem):
success = self.setAnyProperty(elem, dynConfiguration[elem])
if success == False or success == None:
rospy.logerr("dynServerCallback::Error: Changing %s failed with value %s..."%(elem,dynConfiguration[elem]))
value = self.getAnyProperty(elem)
if value != None:
dynConfiguration[elem] = value
else:
rospy.logerr("dynServerCallback::Error: unable to read '" + elem + "' back.")
rospy.logdebug("...%s used"%(dynConfiguration[elem]))
return dynConfiguration
def updateSelfFromRemote(self, dynConfiguration):
"""It would be necessary to "translate" the local names into remote names if it is needed
to be used the "updateSelfParameters method.
@param self - self object for the call.
@param dynConfiguration - a dictionary of {property:value} with the changes to be done.
Precondition: the names of the properties are the REMOTE names.
@return The dictionary with the final configuration just in case there was any changes to it."""
## TODO: WATCH OUT: the PPTY_REF of the parameters could work as path/subParam/paramName
#or the absolute path or just "paramName"...
## From REMOTE to LOCAL names:
newConfig = {}
for elem, value in [(i, dynConfiguration[i]) for i in dynConfiguration]:
paramName = self.translator.reverseInterpret(elem)
if paramName != None and paramName != False and paramName != "":
newConfig[paramName] = value ## i.e.: { dictionary[parameter] = value }
else:
rospy.logdebug("updateSelfParameters::Ignoring unknown REMOTE name while updating self configuration: " + elem)
localConfig = self.updateSelfParameters(newConfig, avoidPropagation=True)
## From LOCAL to REMOTE names:
for elem, value in [(i, localConfig[i]) for i in localConfig]:
name = self.translator.interpret(elem)
if name != None and name != False and name != "":
dynConfiguration[name[PPTY_REF]] = value
else:
rospy.logdebug("updateSelfParameters::Ignoring unknown LOCAL name while updating self configuration: " + elem)
## Send the -already translated- configuration.
return dynConfiguration
def updateSelfParameters(self, newConfig, avoidPropagation=False):
"""This method is responsible for changing the node's own dynamic reconfigure parameters
from its own code ***but avoiding a chain of uncontrolled callbacks!!.
@param self - self object for the call.
@param newConfig - a dictionary of {property:value} with the changes to be done.
Precondition: the names of the properties are the LOCAL names.
@param avoidPropagation - boolean to indicate whether if not to prevent the changes to be
propagated downwards (in case that they already come from there).
@return The dictionary with the final configuration just in case there was any changes to it."""
if avoidPropagation == True:
self.avoidRemoteReconf = self.avoidRemoteReconf + 1
return self.dynServer.update_configuration(newConfig)
def getTopicLocation(self, getStrMsg):
"""Gets the location of an existing topic in the ROS environment.
@param self - self object for the call.
@param getStrMsg - a service structure including:
string topicName - the name of the topic which location is to be returned.
***************
string topicValue for storing the return value.
@return A string answering weather if the relocation was successful
or not and describing the possible error."""
return self.getStringProperty(getStrMsg)
def setTopicLocation(self, setStrMsg):
"""Sets a new location in the ROS environment for an existing topic.
@param self - self object for the call.
@param setStrMsg - a service structure including:
string topicName - the name of the topic to be relocated
string newValue - the new path or name of the topic after relocation
***************
string setAnswer for storing the return value
@return A string answering weather if the relocation was successful
or not and describing the possible error."""
rospy.loginfo (str("Received call to set/TopicLocation " + setStrMsg.topicName + " " + setStrMsg.newValue))
translation = self.translator.interpret(setStrMsg.topicName)
if translation != None and (translation[PPTY_KIND]=="topic"):
oldAddress = translation[PPTY_REF]
else:
oldAddress = setStrMsg.topicName
try:
rospy.loginfo (str("...relocating " + oldAddress + " as " + setStrMsg.newValue + "..."))
success = self.driverMgr.relocateTopic(oldAddress, setStrMsg.newValue)
## If it was a name; the value needs to be updated in the translator
if success == True and translation != None:
self.translator.updatePptyRef(setStrMsg.topicName, setStrMsg.newValue)
else:
print "Translator not updated"
except Exception as e:
rospy.logerr("Exception while relocating: %s"%(e))
return str("Exception while relocating: %s"%(e))
return "Success!"
###################################################
# **** Generic handlers for getting and setting parameters
#==================================================
def setAnyProperty(self, propertyName, newValue):
"""Generic setter in order to redirect to the type-specific setter method.
Received the LOCALLY KNOWN name of a property and sets the associated remote value.
@param self - self object for the call.
@param propertyName - the LOCAL name of the property to be set.
@param newValue - the new value to be set for the property.
@return True if success and False otherwise."""
propertyData = self.translator.interpret(propertyName)
if propertyData == None:
rospy.logdebug("setAnyProperty::called with an unknown property name.")
return None
#else:
if (propertyData[PPTY_TYPE].lower()).find("string") >= 0:
# if type(newValue) != str:
# print "Error: value '%s' seems to be %s instead of '%s' which is needed"%(newValue, type(newValue), propertyData[PPTY_TYPE])
# return None
valueType = str
elif (propertyData[PPTY_TYPE].lower()).find("int") >= 0:
valueType = int
elif (propertyData[PPTY_TYPE].lower()).find("double") >= 0 or (propertyData[PPTY_TYPE].lower()).find("float") >= 0:
valueType = float
elif (propertyData[PPTY_TYPE].lower()).find("bool") >= 0:
valueType = bool
else:
rospy.logerr("setAnyProperty::Error: non registered value type.")
return None
return self.setFixedTypeProperty(propertyName, newValue, valueType)
####################################
# Specific fixed type property setters
#===================================
## The next methods are the callbacks for the setter services
##all of them are supposed to return the resulting values to
##inform in case the set event failed.
## In case there's an error; the error message is sent no
##matter the returning type is
def setStrProperty(self, setterMessage):
return self.setFixedTypeProperty(setterMessage.topicName, setterMessage.newValue, str)
def setIntProperty(self, setterMessage):
return self.setFixedTypeProperty(setterMessage.topicName, setterMessage.newValue, int)
def setFloatProperty(self, setterMessage):
return self.setFixedTypeProperty(setterMessage.topicName, setterMessage.newValue, float)
def setBoolProperty(self, setterMessage):
return self.setFixedTypeProperty(setterMessage.topicName, setterMessage.newValue, bool)
def setFixedTypeProperty(self, localPropName, newValue, valueType):
"""Main property setter receiving the property name, the value to assign and its type
and returning the response from the "setRemoteValue" method from the low level driver.
Precondition: the localPropName must be a known local name and the change is supposed to
be remote. Not meant to be used for local changes in this layer.
@param self - self object for the call.
@param localPropName - the LOCAL name of the property to be set.
@param newValue - the new value to be set for the property.
@param valueType - the expected data type of the value to be set.
@return True if success and False otherwise."""
propertyData = self.translator.interpret(localPropName)
if propertyData == None:
rospy.logerr("setFixedTypeProperty::Error: trying to set not found property.")
return False
response = True
if propertyData[PPTY_KIND] == "dynParam":
remotePropName = propertyTranslator.get_basic_name(propertyData[PPTY_REF])
path = self.translator.getServerPath(remotePropName)
temp = self.driverMgr.setRemoteValue(remotePropName, newValue, path, self)
if temp==None:
rospy.logerr("setFixedTypeProperty::ERROR while setting remote property: " + remotePropName)
response = False
elif propertyData[PPTY_KIND] == "topic":
self.driverMgr.sendByTopic(rospy.get_namespace() + propertyData[PPTY_REF], newValue, remoteType[valueType])
else:
response = False
rospy.logerr("setFixedTypeProperty::Error: unable to set local property %s of kind: '%s'"%(localPropName, propertyData[PPTY_KIND]))
return response
# Getter and getter handlers
# Generic setter in order to redirect to the appropriate method
def getAnyProperty(self, propertyName):
"""Generic getter in order to get any property with any kind or type.
@param self - self object for the call.
@param propertyName - the LOCAL name of the property to be get.
@return the read value or None if there was some error."""
propertyData = self.translator.interpret(propertyName)
if propertyData == None:
return None
#else:
if propertyData[PPTY_TYPE].find("string") >= 0:
valueType = str
elif (propertyData[PPTY_TYPE].lower()).find("int") >= 0:
valueType = int
elif (propertyData[PPTY_TYPE].lower()).find("double") >= 0 or (propertyData[PPTY_TYPE].lower()).find("float") >= 0:
valueType = float
elif (propertyData[PPTY_TYPE].lower()).find("bool") >= 0:
valueType = bool
else:
rospy.logerr("getAnyProperty::Error: non registered value type")
return None
return self.getFixedTypeProperty(propertyName, valueType)
# Generic getter in order to redirect to the appropriate method
#Specific fixed type property getters
def getStringProperty(self, srvMsg):
return self.getFixedTypeProperty(srvMsg.topicName, str)
def getIntProperty(self, srvMsg):
return self.getFixedTypeProperty(srvMsg.topicName, int)
def getFloatProperty(self, srvMsg):
return self.getFixedTypeProperty(srvMsg.topicName, float)
def getBoolProperty(self, srvMsg):
return self.getFixedTypeProperty(srvMsg.topicName, bool)
def getImage(self, srvMsg):
"""Handler for getting images when called from a service.
@param self - self object for the call.
@param srvMsg - a service structure including:
int64 nImages - amount of images to retransmit.
string sourceTopic - original topic from which to get the images.
string responseTopic - target topic path through which to send them.
@return the images read from the source."""
respImages = []
while len(respImages) < srvMsg.nImages:
respImages.append(self.getFixedTypeProperty(srvMsg.topicName, None))
return respImages
def getDispImage(self, srvMsg):
"""Handler for getting disparity images when called from a service.
@param self - self object for the call.
@param srvMsg - a service structure including:
int64 nImages - amount of images to retransmit.
string sourceTopic - original topic from which to get the images.
string responseTopic - target topic path through which to send them.
@return the images read from the source."""
respImages = []
while len(respImages) < srvMsg.nImages:
respImages.append(self.getFixedTypeProperty(srvMsg.topicName, None))
for image in respImages:
self.driverMgr.sendByTopic("testImages", image, DisparityImage)
return respImages
##AMPLIATION: I could add normalImage.timestamp (time[] timestamp) if needed (including the changes in the "normalImage.srv"
## in that case I would need a normalImage variable and set both fields: images and timestamp
def getFixedTypeProperty(self, localPropName, valueType):
"""Main property getter receiving the property name and the value type
and returning the current value from the low level driver.
@param self - self object for the call.
@param localPropName - the LOCAL name of the property to be get.
@param valueType - the expected data type of the value to be get.
@return the read value or None if there was some error."""
propertyData = self.translator.interpret(localPropName)
if propertyData == None:
return None
#else:
if propertyData[PPTY_KIND] == "dynParam":
remotePropName = propertyTranslator.get_basic_name(propertyData[PPTY_REF])
response = self.driverMgr.getValue(remotePropName, self.translator.getServerPath(localPropName))
# elif propertyData[PPTY_KIND] == "publishedTopic":
# response = self.driverMgr.getTopic(propertyData[PPTY_REF], valueType)
elif propertyData[PPTY_KIND] == "parameter":
remotePropName = propertyTranslator.get_basic_name(propertyData[PPTY_REF])
location = rospy.search_param(remotePropName)
response = rospy.get_param(location)
elif propertyData[PPTY_KIND] == "topic":
if valueType == str:
response = str(propertyData[PPTY_REF])
else: ## Special case for reading disparity images
valueType2 = DisparityImage
if propertyData[PPTY_TYPE].find("Disparity") < 0:
valueType2 = Image
response = self.driverMgr.getTopic(propertyData[PPTY_REF], valueType2)
else:
rospy.logerr("getFixedTypeProperty::Error: unable to get property %s of kind %s"%(localPropName, propertyData[PPTY_TYPE]))
response = None
return response
# Method to request the complete list of properties
def get_property_list(self):
"""Auxiliary method for receiving the list of properties known by the translator.
@param self - self object for the call.
@return complete list of properties from the translator."""
return self.translator.get_property_list()
# Methods related to requesting topics to publish
def publishImages(self, srvMsg):
"""Specific purpose method meant to retransmit, when asked via-service, an image topic
through a topic path receiving the original path, the new path and the amount of messages.
@param self - self object for the call.
@param srvMsg - a service structure including:
int64 nImages - amount of images to retransmit.
string sourceTopic - original topic from which to get the images.
string responseTopic - target topic path through which to send them.
@return string message telling whether if it worked or not."""
topicPath = self.translator.get_topic_path(srvMsg.sourceTopic)
self.driverMgr.retransmitTopic(srvMsg.nImages, topicPath, srvMsg.responseTopic, Image)
"%s images sent from %s topic to %s."%(srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
return "%s images sent from %s topic to %s."%(srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
def publishDispImages(self, srvMsg):
"""Specific purpose method meant to retransmit, when asked via-service, a DISPARITY image topic
through a topic path receiving the original path, the new path and the amount of messages.
@param self - self object for the call.
@param srvMsg - a service structure including:
int64 nImages - amount of images to retransmit.
string sourceTopic - original topic from which to get the images.
string responseTopic - target topic path through which to send them.
@return string message telling whether if it worked or not."""
topicPath = self.translator.get_topic_path(srvMsg.sourceTopic)
self.driverMgr.retransmitTopic(srvMsg.nImages, topicPath, srvMsg.responseTopic, DisparityImage)
"%s images sent from %s topic to %s."%(srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
return "%s images sent from %s topic to %s."%(srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
###############################################################################
## ***** ***** propertyTranslator Class ***** *****
###############################################################################
class propertyTranslator(object):
"""This 'PropertyTranslator' class is the module to change driver names and paths into
the ones offered by the interface and the other way around. It stores two dictionaries (for
both translations) and several methods for translating."""
###############################################
## ***** Constructor Method *****
###############################################
def __init__(self,config_filename):
"""Initialises new instances of the propertyTranslator objects by
reading the new configuration and creating the dictionary and the
reverse translation dictionary.
@param self - self object for the call.
@param config_filename - the name AND PATH to the file from which to read
to read the configuration."""
try:
# Storing the filename as it's useful in case there is more than one instance
self.property_config_file = config_filename
# Loads the YAML Config in a pair of lists of names (drivers) and dictionaries (properties)
self.translations = self.readYAMLConfig(file_name = config_filename)
# A reverse dictionary is necessary to search for the original property names when needed
self.ReversePropDict = self.generateReverseDictionary()
rospy.loginfo("Property configuration loaded:")
except:
rospy.logerr("propertyTranslator::Error while reading property configuration from file.")
raise
return
###################################################
## ***** General Purpose Methods *****
###################################################
# **** Interpret and Reverse interpret
#==================================================
def updatePptyRef (self, propertyName, newPath):
"""Changes the REMOTE name or path to a property in the
translation dictionary.
@param self - self object for the call.
@param propertyName - the LOCAL name of a property.
@param newPath - the REMOTE new name to assign to the property.
@return The new path assigned if the property was found.
None otherwise."""
for dictIndex in xrange(len(self.translations[1])):
if propertyName in self.translations[1][dictIndex]:
self.translations[1][dictIndex][propertyName][PPTY_REF] = newPath
return newPath
return None
def interpret(self, propertyName):
"""Receives a generic name specified in the YAML config
Returns the associated values for the driver according to:
[property name, data type, kind of property].
@param self - self object for the call.
@param propertyName - the LOCAL name of a property.
@return The REMOTE name of the same property if found.
None otherwise"""
for dictionary in self.translations[1]:
if propertyName in dictionary:
return dictionary[propertyName]
rospy.logdebug("Property '%s' not found. Returning None type value.")
return None
def reverseInterpret(self, reverseProperty):
"""Reads from the reverse dictionary, the LOCAL name associated with
the received REMOTE name.
@param self - self object for the call.
@param reverseProperty - the REMOTE name of the property.
@return The LOCAL name of the same property if found. None otherwise"""
result = None
if reverseProperty in self.ReversePropDict:
result = self.ReversePropDict[reverseProperty]
rospy.logdebug("reverseInterpret::" + reverseProperty + " reversed as " + result)
else:
for completeName in [ str(path + "/" + reverseProperty) for path in self.translations[0]]:
if completeName in self.ReversePropDict:
result = self.ReversePropDict[completeName]
if result == None:
rospy.logdebug("reverseInterpret::Parameter '%s' not found in the property list."%(reverseProperty))
return result
###################################################
# **** Methods for loading property configuration
#==================================================
def readYAMLConfig(self, file_name):
"""This method receives a file_name in which to read the properties.
Returned Value MUST be a tuple of two lists with strings and
dictionaries respectively.
@param self - self object for the call.
@param file_name - the name AND PATH to the file from which to read
the configuration.
@return A tuple of two list. The first stores the path to each driver
and the second the associated dictionary in the same index."""
newDictionaries = ([],[])
yamlConfig = ""
cfgFile = file(file_name, 'r')
if cfgFile != None:
try:
yamlConfig = yaml.safe_load_all(cfgFile)
except yaml.YAMLError, exc:
if hasattr(exc, 'problem_mark'):
mark = exc.problem_mark
raise Exception("readYAMLConfig::exception: in YAML file '%s'::f%s,col%s." % (file_name, mark.line+1, mark.column+1))
else:
raise Exception("readYAMLConfig::exception: Unknown in YAML file '%s'." % (file_name))
else:
raise Exception("Unable to open YAML configuration file '%s'." % (file_name))
## Once the config is successfully loaded:
for page in yamlConfig:
if len(page) < 1:
rospy.logerr("readYAMLConfig::Error: WRONG CONFIG FORMAT!! Nothing found in this page. Exactly one entry expected.")
if len(page) > 1:
rospy.logerr("readYAMLConfig::Error: STRANGE CONFIG FORMAT!! More than one dictionary in a single page. Exactly one entry expected.")
# There is supposed to be only 1 dictionary but it
#can be useful for the future making it with a loop
for driverName in page:
## Store the properties in the second list...
newDictionaries[1].append(page[driverName])
##...and the driver path without the last '/'
if len(driverName) > 0 and driverName[-1] == '/':
newDictionaries[0].append(driverName[:-1])
else:
newDictionaries[0].append(driverName)
print " Translation Configuration read from YAML file:"
for driver, dictionary in zip(newDictionaries[0], newDictionaries[1]): ## enumerate() if index numbers needed
print "\r\n\r\nDictionary %s:"%driver
for elem in dictionary:
print elem, "\t:\t", dictionary[elem]
return newDictionaries
def cleanRenamings(self, driverManager):
"""Launches a multiplexor node that relocates a topic to a new address.
NOTE: Right now it checks if the topic exists... That may be avoidable if
the multiplexor can be working anyway for future topics.
The method is supposed to be launched from the outside when loading the
whole interface; the topics are suppossed to exist already.
@param self - self object for the call.
@param driverManager - the reference to the driverManager object to be
used for the relocations.
PRECONDITION: the driverManager must be already initialised
and the topics must be already published (at least with this solution)."""
dictionaryList = self.translations[1]
for dictionary in dictionaryList:
for renaming in [prop for prop in dictionary if dictionary[prop][PPTY_KIND]=="renaming"]:
rospy.loginfo("Automatically renaming from " + dictionary[renaming][PPTY_REF] + " to " + renaming)
translation = self.interpret(dictionary[renaming][PPTY_REF])
if translation != None and (translation[PPTY_KIND]=="topic"):
oldAddress = translation[PPTY_REF]
rospy.loginfo("...the " + dictionary[renaming][PPTY_REF] + " property is mapped as " + oldAddress)
else:
oldAddress = dictionary[renaming][PPTY_REF]
try:
rospy.loginfo("Remaping from " + oldAddress + " to " + renaming)
success = driverManager.relocateTopic(oldAddress, renaming)
## If it was a name; the value needs to be updated in the translator
if success == True and translation!= None and oldAddress == translation[PPTY_REF]:
self.translator.updatePptyRef(dictionary[renaming][PPTY_REF], renaming)
else
rospy.logerr("cleanRenamings::Error: Unable to remap topic.")
except Exception as e:
rospy.logerr("cleanRenamings::Exception: initial relocation failed: %s"%(e))
return
else: ## In case there was no exception
del dictionary[renaming]
return
def generateReverseDictionary(self):
"""Generates a dictionary for the translator using the driver-side name as
key and the name inside of the interface as value as the correlation is
needed in both directions.
@param self - self object for the call.
@return The reversed dictionary of properties indexed by the REMOTE names
both with and without the paths."""
reversePropDict = {}
rospy.loginfo("Storing the reverse dictionary")
for dictionary in self.translations[1]:
for elem in dictionary:
reversePropDict[dictionary[elem][PPTY_REF]] = elem
reversePropDict[self.get_basic_name(dictionary[elem][PPTY_REF])] = elem
return reversePropDict
def dynamicServers(self, checkDynamic=True):
"""Generates a list with the relative or absolute paths of the different
dynamic reconfigure servers according to the YAML property configuration file.
In the current version checkDynamic forces the method to ignore the path if
there are no dynamic parameters in its list.
@param self - self object for the call.
@param checkDynamic - decides weather if checking or not that there actually are
dynamic properties for each dynamic server.
@return The list of paths to the available servers."""
driverServers = []
for elem, index in [(elem, index) for elem, index in zip(self.translations[0], xrange(len(self.translations[0]))) if elem != "" and elem != "~"]:
# If check is off, any non-empty string different from '~' is added
if (checkDynamic == False):
driverServers.append(elem)
break
#else: # The existence of dynamic parameters is checked
for prop in [prop for prop in self.translations[1][index] if self.translations[1][index][prop][PPTY_KIND] == "dynParam"]:
driverServers.append(elem)
break
return driverServers
###################################################
# **** Properties Utility Methods
#==================================================
def prop_exists(self, propertyName):
"""Receives the name of a property and checks if it exists in the dictionary
(meaning the same as if it is known by the translator).
@param self - self object for the call.
@param propertyName - the local name of the property to be checked.
@return True if the property was found in the dictionary. False otherwise."""
# return (self.interpret(propertyName) != None) ## This line would look less efficient but more maintainable
for dictionary in self.translations[1]:
if propertyName in dictionary:
return True
return False;
def canSet(self, propertyName):
"""Receives the name of a property and decides whether if it can be Setted or not.
@param self - self object for the call.
@param propertyName - the local name of the property to be checked.
@return True if the property was found and it can be get. False otherwise."""
kindsToSet = set (["dynParam", "topic"])
prop_data = self.interpret(propertyName)
if prop_data != None:
return (prop_data[PPTY_KIND] in kindsToSet)
return False
def canGet(self, propertyName):
"""Receives the name of a property and decides weather if it can be Getted or not.
@param self - self object for the call.
@param propertyName - the local name of the property to be checked.
@return True if the property was found and it can be get. False otherwise."""
kindsToGet = set (["dynParam", "parameter", "topic"])
prop_data = self.interpret(propertyName)
if prop_data != None:
return (prop_data[PPTY_KIND] in kindsToGet)
return False
def getServerPath(self, propName):
"""Receives a property name.
If the property exists, the driver's path is returned
Otherwise, the response is None
@param propName - the known complete name of a property (which may
include a relative path to diferentiate from others).
@return the LAST 'part' of the name without the relative path."""
for dictIndex in xrange(len(self.translations[1])):
if propName in self.translations[1][dictIndex]:
return (self.translations[0][dictIndex])
return None
@staticmethod
def get_basic_name(propName):
"""Static method that receives a string with any name or path
and returns the basic name after the last slash. That is: