在 HarmonyOS 3.0 和 OpenHarmony 3.2 的支持下,TCP-socket 通信 API 已经稳定可控,今天我们做一个控制应用来控制小车。
效果演示:
设计思路
运行环境:HarmonyOS 3.0,OpenHarmony 3.2
①按键说明
如下:
转向控制:左右滑动摇杆,实现转向,上下滑动摇杆,实现速度控制
动力控制:上下滑动摇杆,实现前进后退
本机 IP 地址展示
对端 IP 地址输入
链接,断开按键,主动进行 TCP 连接请求与断开
②控制指令
本遥控器以状态指令为驱动,每触发一种状态仅发送一次指令,对端在未接收到新指令的情况下一直保持当前指令状态。
前进状态:“1”
后退状态:“2”
左转状态:“3”
右转状态:“4”
停止状态:“0”
页面设计
在摇杆的拖动设计中,主要运用 ontouchmove,ontouchend,ontouchstart 实现。 通过手指坐标来确定摇杆组件的 top 和 left 值,通过设定方向阈值来判断是否开始发送指令,通过打印回调数据来设置参数。
hml:
{{local_ip}}
CSS:
.container { display: flex; flex-direction: column; justify-content: center; align-items: center; left: 0px; top: 0px; width: 100%; height: 100%; } .title { font-size: 40px; text-align: center; width: 100%; height: 40%; margin: 10px; } .yaogan{ position: absolute; top: 100px; left: 50px; width: 200px; height: 200px; background-image: url("./common/RadialJoy_Area.png"); background-size: 100%; background-repeat: no-repeat; z-index: -1; } .controller{ width: 100px; height: 100px; top: 150px; left: 100px; background-image: url("./common/RadialJoy_Area.png"); background-size: 100%; background-repeat: no-repeat; position: absolute; z-index: 1; } .forward{ position: absolute; left: 550px; width: 100px; height: 100px; background-size: 100%; z-index: -1; } .ip_input{ font-size: 18px; left: 30px; width: 200px; height: 50px; margin-top: 25px; background-color: #ff2c7a87; /* background-imagenone*/ /* background-size: 100%;*/ /* background-repeat: no-repeat;*/ } .btn{ width: 100px; height:30px; left: 30px; margin-top: 5px; background-color: #ff93f0fd; /* background-imagenone*/ /* background-size: 150%;*/ /* background-repeat: no-repeat;*/ } .ip_local{ font-size: 20px; width: 200px; height: 50px; left:30px; color: #ff3850ef; margin-top: 20px; background-image: url("./common/images/bg2.png"); background-size: 100%; background-repeat: no-repeat; }
业务逻辑
①参数调试
我们前面为摇杆组件设置了 ontouch 事件,那么如何设计 Top 或者 left 值来判断什么时候可以开始发送指令呢?
摇杆既不可太过灵敏也不可以太过迟钝,我们可以通过打印触摸事件返回的参数来进行调参。
export default { touchstartfunc(msg) { console.info(`on touch start, point is: ${msg.touches[0].globalX}`); console.info(`on touch start, point is: ${msg.touches[0].globalY}`); console.info(`on touch start, data is: ${msg.target.dataSet.a}`); } }②触摸控制 根据前文提到的状态控制机制,我们应该在 ontouchmove 中进行判断,当上滑到某一阈值的时候开始发送前进指令,当松手时即 ontouchend 时我们应该立即发送停止指令。 即滑动中判断并发送指令,停止则立马发送停止信息。具体的阈值参数根据个人考虑进行调试设置。
import prompt from '@ohos.prompt'; import wifi from '@ohos.wifi'; import socket from '@ohos.net.socket'; import display from '@ohos.display'; var promise ; export default { data: { title: "", x:150, y:100, forward_x:150, msg:"forward", forward_image:"Button_normal", TGA:"YZJ", command:"1", local_ip:"", remote_ip:"", speed_mode:1, speed:10, tcp:socket.constructTCPSocketInstance(), pre_cmd:'0', cur_cmd:'0' }, onInit() { this.title = this.$t('strings.world'); this.getIpAddress(); this.creatScoket(); }, send_cmd(cmd){ if(cmd!=this.cur_cmd){ this.cur_cmd=cmd; this.sendMessage(cmd); } }, onMoveStart(e){ console.info("开始移动"+JSON.stringify(e)); }, toSpeed_mode(){ if(this.speed_mode==0) this.speed_mode=1; else if(this.speed_mode==1) this.speed_mode=0; }, onMove(e){ //圆心是(100,250) if(this.speed_mode==0){ console.info(JSON.stringify(e)) let nx=e.touches[0].globalY-50; this.x=nx; } else if(this.speed_mode==1){ console.info(JSON.stringify(e)) let ny=e.touches[0].globalX-50; this.y=ny; if(ny>=110){ this.msg="trun_right" console.info("YZJ:正在向右转") this.command="4"; this.send_cmd(this.command); } else if(ny<=90){ this.msg="trun_left" console.info("YZJ:正在向做左转") this.command="3"; this.send_cmd(this.command); } } }, onMoveEnd(){ this.x=150; this.y=100; this.msg="stop" this.command='0'; this.send_cmd(this.command); }, onForwardstart(e){ this.forward_image="Button_active"; this.forward_x=e.touches[0].globalY-50 }, onForward(e){ if( e.touches[0].globalY-50<=140){ console.info("正在前进") this.msg="forward" this.command="1" this.send_cmd(this.command); if(e.touches[0].globalY-50<100){ this.forward_x=100 } else this.forward_x=e.touches[0].globalY-50 } else if(e.touches[0].globalY-50>165){ console.info("正在后退") this.msg="backoff" this.command="2" this.send_cmd(this.command); if(e.touches[0].globalY-50>200){ this.forward_x=200 } else this.forward_x=e.touches[0].globalY-50 } }, onForwardend(){ this.forward_x=150; console.info("停止前进") this.msg="stop" this.forward_image="Button_normal" this.command="0" this.send_cmd(this.command); }, //创建udpSocket 默认端口10006 creatScoket: async function(){ this.tcp.bind({address: this.local_ip, port: 8888,family: 1}, err => { if (err) { console.log('YZJ---bind fail'); return; } console.log('YZJ---bind success'); }) //监听收到的信息 打印到屏幕上 this.tcp.on('message', value => { let buffer = value.message; let dataView = new DataView(buffer); let str = ""; for (let i = 0;i < dataView.byteLength; ++i) { str += String.fromCharCode(dataView.getUint8(i)) } this.title =str; }); }, sendMessage: async function(cmd){ //发送信息 // let promise1 = this.tcp.connect({ address: {address: this.remote_ip, port: 10006, family: 1} , timeout: 6000}); let promise2 = this.tcp.send({ data:cmd }); promise2.then(() => { console.log('YZJ---send success'); }).catch(err => { console.log('YZJ---send fail'); }); }, onConnect: async function(){ promise = this.tcp.connect({ address: {address: "192.168.1.1", port: 8888, family: 1} , timeout: 6000}); promise.then(() => { prompt.showToast({ message: "连接成功!" }) console.log('YZJ---connect success'); this.tcp.setExtraOptions({ keepAlive: true, OOBInline: true, TCPNoDelay: true, socketLinger: { on:true, linger:10 }, receiveBufferSize: 1000, sendBufferSize: 1000, reuseAddress: true, socketTimeout: 3000, },err => { if (err) { console.log('YZJ---setExtraOptions fail'); return; } console.log('YZJ---setExtraOptions success'); }); }).catch(err => { console.log('YZJ---connect fail'); prompt.showToast({ message: "连接失败!" }) }); }, onDisconnect(){ this.tcp.close() prompt.showToast({ message: "断开链接!" }) }, onDestroy(){ this.tcp.close() prompt.showToast({ message: "断开链接!" }) }, //获取本机ip地址 getIpAddress(){ let ip=wifi.getIpInfo().ipAddress; this.local_ip = (ip >> 24 & 0xFF)+"."+ ((ip >> 16) & 0xFF)+"."+((ip >> 8) & 0xFF)+"."+(ip & 0xFF); }, get_remote_ip(e){ this.remote_ip=e.value } }③TCP 通过输入框获取对端 IP 地址,点击链接按键时触发 connect 方法请求连接,连接成功弹出对话框"连接成功"。 展示本机 IP 地址。 应用或者页面销毁时应关闭连接,否则会占据对端该端口,导致下次连接失败。 根据状态驱动指令控制,由于 ontouchmove 是一直在触发的,也就是判断是一直在进行的,当我们保持摇杆前进状态的时候,注意要判断指令状态是否更新了?如果指令未变,那么就不再发送指令。只有指令变化的时候才会发送一次指令。 只有连接成功后,才能够发送信息。
tcp 设置参数:
|参数|描述| |-|-| |keepAlive|是否保持连接。默认为false。| |OOBInline|是否为OOB内联。默认为false。| |TCPNoDelay|TCPSocket连接是否无时延。默认为false。| |receiveBufferSize|接收缓冲区大小(单位:Byte)| |sendBufferSize|发送缓冲区大小(单位:Byte)| |reuseAddress|是否重用地址。默认为false| |socketTimeout|套接字超时时间,单位毫秒(ms)|建议开启 HarmonyOS 工程,开发完毕后可同步安装到 OpenHarmony 设备,反之则会变得麻烦一些。
④申请权限
"reqPermissions": [ { "name": "ohos.permission.GET_WIFI_INFO" }, { "name": "ohos.permission.GET_NETWORK_INFO" }, { "name": "ohos.permission.INTERNET" }, { "name": "ohos.permission.SET_NETWORK_INFO" }, { "name": "ohos.permission.ACCELEROMETER" } ]
结语
本次分享的应用需要南北向开发配合食用,同时需要 HarmonyOS 3.0 设备或者 OpenHarmony 3.2 设备。 HarmonyOS 2.0 设备可考虑采用 JS/JAVA 混合开发,JAVA 侧实现 Socket 通信,可参考我往期博客。 下一期,我将会分享如何配置 HarmonyOS 3.0 设备的碰一碰拉起应用配置。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !