怎样写一个相似ROS的易用的android机器人框架(1)node
接下来,咱们一步步来实现这个几个目标android
参考项目源码git
相关代码实现位于 ai.easy.robot.framework
包内网络
首先,咱们须要开启一个常驻运行的 MasterService,在机器人运行的这个生命周期内保持活动,每一个节点(无论进程内仍是进程外)均可以链接此Service,向其发送消息,MasterService负责将这些消息转发给已经订阅了该消息主题的节点。多线程
咱们选用 Messenger:框架
MasterService 类在 onCreate()
以后开启一个线程运行消息循环socket
thread(start=true,name = "master"){ try { Looper.prepare() //TODO h = MyHandler(this, Looper.myLooper()) // messenger = Messenger(h) mtx.release() // logd("start loop") Looper.loop() logd("stop loop") }catch (e:Exception){ e.printStackTrace() } }
这里,MyHandler负责全部消息的接收,解析,转发,messenger则经过 onBind()
暴露给链接的外界节点ide
override fun onBind(intent: Intent?): IBinder = synchronized(this){ if(!this::messenger.isInitialized){ //mtx.wait() mtx.acquire() } messenger.binder }
MasterConnector 是负责普通节点和 MasterService 链接的中介,其connect()
函数经过bindService
方式获取 MasterService 的 messenger,从而能够想MasterService发送 Message函数
private val conn = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { logd("service disconnected") isConnected = false messenger = null } override fun onServiceConnected(name: ComponentName?, service: IBinder?) { logd("service connected") messenger = Messenger(service) isConnected = true onConnected?.invoke() } }
fun connect(servClass:Class<*>) { if (!isConnected && !isBounded) { val i = Intent() i.setClass(ctx, servClass) ctx.bindService(i, conn, Context.BIND_AUTO_CREATE) isBounded = true } }
每一个Message的 what
属性说明消息的做用,obj
属性指明节点名称或者消息主题,data
属性封装消息数据oop
what
的取值定义为
const val NODE_MGR_REG = 1 //节点注册指令消息 const val NODE_MGR_UN_REG = 2 //节点注销指令消息 const val NODE_MGR_LIST = 3 //查询节点列表指令消息 const val NODE_MGR_CLR = 4 //清除全部节点指令 const val NODE_MSG_PUB = 5 //节点发布消息 const val NODE_MSG_SUB = 6 //节点订阅消息 const val NODE_MSG_UN_SUB = 7 //取消订阅 const val NODE_MSG_ON_REG = 8 //注册成功事件 const val NODE_MSG_ON_NODE_CHANGED = 9 //节点列表发生变化事件 const val NODE_MSG = 16 //MasterService转发的消息
表示有外部节点要加入整个节点消息通信体系,该消息中有个 replyTo
属性,传入的是该节点接收 MasterService发送数据的Messenger对象,MaserService就是经过他再给目标节点发送消息的。这个Messenger对象须要放入nodeMap
中保存。
删除nodeMap
中存放的Messenger对象,释放其内存。
表示节点想接收某个主题的消息。将节点名称和订阅主题存入nodeSubMap
拥有消息主题和消息数据,经过nodeSubMap
查询订阅了该消息的节点的Messenger对象,再经过该Messenger对象发送该消息。实现消息转发。
查询已经注册的节点列表
强迫全部节点注销
BaseNode
是全部普通节点的基类,实现消息的收发
fun register() { val m = Message.obtain() m.what = MasterService.NODE_MGR_REG m.obj = name m.replyTo = receiver connector.messenger?.send(m) }
注册须要将自身的Messenger对象 receiver
传递给 MasterService,其中
private val receiver = Messenger( object : Handler() { override fun handleMessage(msg: Message?) { logd("msg>>") when (msg?.what) { MasterService.NODE_MSG -> onMessageReceive("${msg.obj}", msg?.data) MasterService.NODE_MSG_ON_REG -> onNodeRegisted() else -> { } } } })
receiver
接收到 MasterService 的消息后调用响应的回调函数
fun unregister()
fun subscribe(topic: String)
fun publish(topic: String, msg: Bundle)
全部节点的数据消息封装在Bundle对象中。
至此,android版的相似ROS通信框架就实现了
系统中只有一个进程运行MasterService 跟MasterService不在同一个进程的节点的消息收发不须要改变,只是其connect
方法作些改动
fun connect(masterPackageName:String,masterServiceName:String) { if (!isConnected && !isBounded) { val i = Intent() i.setComponent(ComponentName(masterPackageName,masterServiceName)) ctx.bindService(i, conn, Context.BIND_AUTO_CREATE) isBounded = true } }
多主板分布的节点须要经过网络进行通信了。考虑到这种应用场景很少。因此MasterService暂时还不支持这样的操做,所以还须要实现一个适配器,以便可以注册和管理网络上的节点。