arduino設備跑 ros service server 的波折記

arduino設備跑 ros service server 的波折記。

引——


參考前文https://blog.csdn.net/qq_38288618/article/details/104082877
創建工作空間、相關包、srv、makelist,catkinmake完成,調用
  rosrun rosserial_arduino make_libraries.py  生成arduino的 lib,複製相關lib到arduino庫,
寫個簡單的serviceserver示例,編譯下載成功。這一系列動作之後rosrun個節點來驗證通信。

波——


運行命令遇到錯誤

PS D:\project\ros\zzz_srv_ws> rosrun rosserial_python serial_node.py COM8
[INFO] [1581539992.670000]: ROS Serial Python Node
[INFO] [1581539992.686000]: zzzConnecting to COM8 at 57600 baud
[INFO] [1581539994.799000]: Requesting topics...
[INFO] [1581539994.840000]: Note: publish buffer size is 280 bytes
[ERROR] [1581539994.971000]: Creation of service server failed: rosserial_arduino
ROS path [0]=C:\opt\ros\melodic\x64\share\ros
ROS path [1]=C:\opt\ros\melodic\x64\share
ROS path [2]=D:/project/ros/zzz_srv_ws/src
[INFO] [1581539994.977000]: Setup publisher on chatter [std_msgs/String]
[INFO] [1581539994.987000]: Note: subscribe buffer size is 280 bytes
[ERROR] [1581539994.993000]: Creation of service server failed: rosserial_arduino
ROS path [0]=C:\opt\ros\melodic\x64\share\ros
ROS path [1]=C:\opt\ros\melodic\x64\share
ROS path [2]=D:/project/ros/zzz_srv_ws/src


順藤摸瓜,搜索 Creation of service server failed: 相關文件

SerialClient.py

def setupServiceServerPublisher(self, data):
        """ Register a new service server. """
        try:
            msg = TopicInfo()
            msg.deserialize(data)
            rospy.loginfo("zzz data=%s"%data)
            self.setPublishSize(msg.buffer_size)
            rospy.loginfo("zzz msg.buffer_size=%s"%msg.buffer_size)
            try:
                rospy.loginfo("zzz:msgname[type]= %s [%s]" % (msg.topic_name, msg.message_type) )
                srv = self.services[msg.topic_name]
                rospy.loginfo("try")
            except KeyError:
                srv = ServiceServer(msg, self)
                rospy.loginfo("key error")
                rospy.loginfo("Setup service server on %s [%s]" % (msg.topic_name, msg.message_type) )
                self.services[msg.topic_name] = srv
            if srv.mres._md5sum == msg.md5sum:
                self.callbacks[msg.topic_id] = srv.handlePacket
            else:
                raise Exception('Checksum does not match: ' + srv.mres._md5sum + ',' + msg.md5sum)
        except Exception as e:
            rospy.logerr("Creation of service server failed: %s", e)

print大法得到

[INFO] [1581576166.920000]: ROS Serial Python Node
[INFO] [1581576166.939000]: zzzConnecting to COM8 at 57600 baud
[INFO] [1581576169.046000]: Requesting topics...
[INFO] [1581576169.080000]: zzz data=   test_srv   rosserial_arduino/Test    0825d95fdfa2c8f4bbb4e9c74bccd3fd
[INFO] [1581576169.111000]: Note: publish buffer size is 280 bytes
[INFO] [1581576169.117000]: zzz msg.buffer_size=280
[INFO] [1581576169.121000]: zzz:msgname[type]= test_srv [rosserial_arduino/Test]
[ERROR] [1581576169.248000]: Creation of service server failed: rosserial_arduino
ROS path [0]=C:\opt\ros\melodic\x64\share\ros
ROS path [1]=C:\opt\ros\melodic\x64\share
ROS path [2]=D:/project/ros/zzz_srv_ws/src
[INFO] [1581576169.253000]: Setup publisher on chatter [std_msgs/String]


找不到問題,反覆調試,用arduino示例能跑,新生成的就不行,調到懷疑人生。。。。。
最後一氣之下把arduino的ros庫蓋了一把,發現沒毛病,竟然好了。!!!???!!!。。。。

分析
早些時候安裝了arduino的ros庫,這次只把相關的頭文件考過來了,有可能版本不一致,導致這個問題。
用新的lib把arduino的ros庫給覆蓋一下就ok了。我的疏忽,浪費了生命,大家引以爲戒吧

此時鬆了一口氣,serviceserver最終ok。輸出如下

[INFO] [1581784397.551000]: Note: publish buffer size is 280 bytes
[INFO] [1581784397.563000]: zzz msg.buffer_size=280
[INFO] [1581784397.570000]: zzz:msgname[type]= zzz_srv [serialhwinfo/hwinfo]
[INFO] [1581784397.622000]: key error
[INFO] [1581784397.625000]: Setup service server on zzz_srv [serialhwinfo/hwinfo]

折——


但、但、但、但發現通信還是問題:

 responded with an error: service cannot process request: service handler returned None

再深呼吸,調整下自己的位姿,依舊使用順藤摸瓜大法:


找到文件
C:\opt\ros\melodic\x64\lib\site-packages\rospy\impl
tcpros_service.py就是這個函數輸出,當response == None:時是這個錯誤

def convert_return_to_response(response, response_class):
    """
    Convert return value of function to response instance. The
    rules/precedence for this are:

    1. If the return type is the same as the response type, no conversion
    is done.

    2. If the return type is a dictionary, it is used as a keyword-style
    initialization for a new response instance.

    3. If the return type is *not* a list type, it is passed in as a single arg
    to a new response instance.

    4. If the return type is a list/tuple type, it is used as a args-style
    initialization for a new response instance.
    """

    # use this declared ROS type check instead of a direct instance
    # check, which allows us to play tricks with serialization and
    # deserialization
    if isinstance(response, genpy.Message) and response._type == response_class._type:
    #if isinstance(response, response_class):
        return response
    elif type(response) == dict:
        # kwds response
        try:
            return response_class(**response)
        except AttributeError as e:
            raise ServiceException("handler returned invalid value: %s"%str(e))
    elif response == None:
        raise ServiceException("service handler returned None")
    elif type(response) not in [list, tuple]:
        # single, non-list arg
        try:
            return response_class(response)
        except TypeError as e:
            raise ServiceException("handler returned invalid value: %s"%str(e))
    else:
        # user returned a list, which has some ambiguous cases. Our resolution is that
        # all list/tuples are converted to *args
        try:
            return response_class(*response)
        except TypeError as e:
            raise ServiceException("handler returned wrong number of values: %s"%str(e))

這裏調用了convert_return_to_response,讓我們來加些print
   

def _handle_request(self, transport, request):
        """
        Process a single incoming request.
        @param transport: transport instance
        @type  transport: L{TCPROSTransport}
        @param request: Message
        @type  request: genpy.Message
        """
        try:
            print("zzz 20200215")
            # convert return type to response Message instance
            print(self.response_class)
            print(self.handler)
            print(request)
            print("handler=",self.handler(request))
            response = convert_return_to_response(self.handler(request), self.response_class)
            self.seq += 1
            print("seq=",self.seq)
            print(transport.write_buff)
            # ok byte
            transport.write_buff.write(struct.pack('<B', 1))
            
            transport.send_message(response, self.seq)
        except ServiceException as e:
            rospy.core.rospydebug("handler raised ServiceException: %s"%(e))
            self._write_service_error(transport, "service cannot process request: %s"%e)
        except Exception as e:
            (exc_type, exc_value, tb) = sys.exc_info()
            self.error_handler(e, exc_type, exc_value, tb)
            self._write_service_error(transport, "error processing request: %s"%e)


底部註釋

If handler cannot process request, it may either return None,
        to indicate failure, or it may raise a rospy.ServiceException
        to send a specific error message to the client. Returning None
        is always considered a failure.
簡譯:
若handler無法處理請求,它可能返回None以表示失敗,
也可能是引起了rospy.ServiceException錯誤,來向客戶端發送特定的錯誤消息。返回None總被認爲是失敗的。。。。

對這個說明不是很滿意。

看看輸出捋捋吧

zzz 20200215
<class 'serialhwinfo.srv._hwinfo.hwinfoResponse'>
<bound method ServiceServer.callback of <rosserial_python.SerialClient.ServiceServer instance at 0x000000000417ACC8>>
input: "name"
('handler=', None)

發現convert_return_to_response的參數self.handler(request)的確爲None
response = convert_return_to_response(self.handler(request), self.response_class)

根據print (handler)輸出的ServiceServer.callback ,可以找到文件

C:\opt\ros\melodic\x64\lib\site-packages\rosserial_python
SerialClient.py

def callback(self, req):
        """ Forward request to serial device. """
        print("zzz20200215--handler--callback-----------------------------")
        data_buffer = StringIO.StringIO()
        req.serialize(data_buffer)
        print(self.id, data_buffer.getvalue())
        self.response = None
        print(self.parent.send)
        if self.parent.send(self.id, data_buffer.getvalue()) >= 0:
            print("send ok,wait....")
            while self.response is None:
                #print("isnone!!!!!!!!!!!!!")
                pass
            #print(self.response)
        return self.response

    def handlePacket(self, data):
        """ Forward response to ROS network. """
        r = self.mres()
        r.deserialize(data)
        #print("------------------------",r)
        self.response = r

 

zzz 20200215
<class 'serialhwinfo.srv._hwinfo.hwinfoResponse'>
<bound method ServiceServer.callback of <rosserial_python.SerialClient.ServiceServer instance at 0x000000000474BD48>>
input: "name"
zzz20200215--handler--callback-----------------------------
(100, '\x04\x00\x00\x00name')
<bound method SerialClient.send of <rosserial_python.SerialClient.SerialClient object at 0x00000000038AA0F0>>
('handler=', None)


這個條件沒有過呀

        if self.parent.send(self.id, data_buffer.getvalue()) >= 0:
            print("send ok,wait....")

parent.send 中parent是個什麼鬼?原來是個SerialClient
<bound method SerialClient.send of <rosserial_python.SerialClient.SerialClient object at 0x00000000038AA0F0>>

接着找到

    def send(self, topic, msg):
        """
        Queues data to be written to the serial port.
        """
        print("===================")
        self.write_queue.put((topic, msg))

print大法有=====輸出,證明是在這裏了。

但是send並沒有返回值????也就是說返回None,這麼神奇的事情????看見_send有返回值,難道是版本升級導致????好吧,
if self.parent.send(self.id, data_buffer.getvalue()) >= 0:這句情何以堪?沒有返回做比較?

我:沒有槍頭你還捅?
ros:你怎麼知道沒有槍頭就捅不死人?
反正我現在是想錘電腦了。。。


來吧,給你配個槍頭先,加個返回值


    def send(self, topic, msg):
        """
        Queues data to be written to the serial port.
        """
        print("===================")
        self.write_queue.put((topic, msg))
        return 1

 


輸出

zzz 20200215
<class 'serialhwinfo.srv._hwinfo.hwinfoResponse'>
<bound method ServiceServer.callback of <rosserial_python.SerialClient.ServiceServer instance at 0x00000000041EBDC8>>
input: "name"
zzz20200215--handler--callback-----------------------------
(100, '\x04\x00\x00\x00name')
<bound method SerialClient.send of <rosserial_python.SerialClient.SerialClient object at 0x00000000033AA0F0>>
===================
send ok,wait....
('handler=', output: "SCREEN1602-PCF8574")
zzz20200215--handler--callback-----------------------------
(100, '\x04\x00\x00\x00name')
<bound method SerialClient.send of <rosserial_python.SerialClient.SerialClient object at 0x00000000033AA0F0>>
===================
send ok,wait....
('seq=', 10)
<cStringIO.StringO object at 0x0000000004216B20>

。。。
。。。

一切正常。
。。。
。。。

此時

內心波濤之涌轉爲止水之靜。

順便給官方提個建議

https://github.com/ros-drivers/rosserial/pulls

 Update SerialClient.py #465


記——

外面陽光明媚,春光大好,只有疫情讓人憋屈,但對於猿們來說,程序搗鼓通了,隔着窗也能體會到春風拂面的那種感覺!

此文旨在遇坑做個記號!有用給個贊!謝謝!
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章