原文地址:http://blog.starrtc.com/?p=94

1 创建工程
IDE:Android Studio 3.1;
File>New>New Project>输入项目名>next>Target Android Devices 复选框勾选 Phone and Tablet 和 Android Things>next… finish;
创建成功后项目会包括mobile和things两个module,mobile是手机端程序,things是小车上的程序。
things部分编译出来的是apk只能运行在Android Things系统上,后面我们会在树莓派上运行这module。

2 导入StarRTC SDK
SDK下载:https://docs.starrtc.com/download/
开发文档:https://docs.starrtc.com/docs/android-1.html
按照开发文档所述,分别在mobile和things两个module下导入StarRTC的sdk。

3 开始码代码,小车端程序(things module)
这个项目的代码大部分都是从StarRTC官网demo源码中拷贝过来的,并做了些简单的修改。
3.1 小车开机后原地待命
小车要先登录StarRTC的服务,后边才能接收到手机端的启动指令。

//设置小车的ID,后边要通过这个ID来唤醒小车
MLOC.userId = "car0001";
//设置适合树莓派的默认参数值
XHClient.getInstance().setDefaultConfig(true,true,0,0,1,false,false,false,false,XHConstants.XHCropTypeEnum.STAR_VIDEO_CROP_CONFIG_BIG_NOCROP_SMALL_NONE);
XHClient.getInstance().setCustomEncoderConfig(640,480,640,480,15,500,45);
//初始化SDK,添加登录和消息状态监听
XHClient.getInstance().initSDK(this, new XHSDKConfig(MLOC.agentId),MLOC.userId);
XHClient.getInstance().getChatManager().addListener(new XHChatManagerListener());
XHClient.getInstance().getLoginManager().addListener(new XHLoginManagerListener());
checkNetAvailable();

这里有个问题要注意一下,本来这段初始化代码最后,就要进行登录操作了,但树莓派开机时,会第一时间自动运行小车的程序,这样就可能遇到程序在进行初始化时,网络未连接或设备时间未同步的问题。这两个问题都会导致登录失败。所以登录之前先检查一下网络是否已经可用,时间是否已经同步。这里只是判断了时间中的year部分是否包含”201″。

    private Timer checkNetTimer = new Timer();
private void checkNetAvailable(){
checkNetTimer.schedule(new TimerTask() {
@Override
public void run() {
Runtime runtime = Runtime.getRuntime();
Process pingProcess = null;
try {
String nowDate = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new java.util.Date());
//时间是否已经同步
if(nowDate.contains("201")){
checkNetTimer.cancel();
InterfaceUrls.demoLogin(MLOC.userId);
}
} catch (Exception e) {
e.printStackTrace();
}
}
},3000,3000);
}

上边代码中InterfaceUrls.demoLogin(MLOC.userId)是向服务器获取登录证明AuthKey的,拿到证明之后就是真正的登录了。这里我通过自定义的事件来专递各种消息和参数。
登录SDK成功后,小车将处于待命状态,等待操控者的启动指令。这里使用的启动指令是“IotCarStart”这个字符串。

@Override
public void dispatchEvent(String aEventID, boolean success, Object eventObj) {
switch (aEventID){
case AEvent.AEVENT_LOGIN:
if(success){
MLOC.d("", (String) eventObj);
//登录SDK
XHClient.getInstance().getLoginManager().login(MLOC.authKey, new IXHCallback() {
@Override
public void success(Object data) {
isLogin = true;
} @Override
public void failed(final String errMsg) {
MLOC.d("",errMsg);
runOnUiThread(new Runnable() {
@Override
public void run() {
MLOC.showMsg(SplashActivity.this,errMsg);
}
});
}
});
}else{
MLOC.d("", (String) eventObj);
}
break;
case AEvent.AEVENT_C2C_REV_MSG:
XHIMMessage message = (XHIMMessage) eventObj;
String command = message.contentData;
switch (command){
case "IotCarStart":
startCar(message.fromId);
break;
}
break;
}
} private void startCar(String fromId){
removeListener();
Intent intent = new Intent(SplashActivity.this,SampleLiveActivity.class);
intent.putExtra("driverId",fromId);
startActivity(intent);
}

3.2 小车收到开机指令
当手机端通过IM,向小车发送一条内容为“IotCarStart”的一对一消息时,小车就会启动,并且记录操控者的ID,直到本次遥控结束,小车将忽略其他人发来的一切消息。
小车启动时,要创建一个互动直播的房间,并开始直播。开始直播后,通过一对一消息将直播间的id发给手机端。手机端通过直播间id进入直播间,并申请连麦。小车收到连麦申请后判断是否是操控者发来的,是操控者将允许连麦,否则拒绝连麦。

     private void createLive(){
//创建直播
final XHLiveItem liveItem = new XHLiveItem();
liveItem.setLiveType(XHConstants.XHLiveType.XHLiveTypeGlobalPublic);
liveItem.setLiveName(MLOC.userId);
//创建直播间
liveManager.createLive(liveItem, new IXHCallback() {
@Override
public void success(Object data) {
//创建成功
MLOC.d("XHLiveManager","createLive success "+data);
liveId = (String) data;
MLOC.saveSharedData(SampleLiveActivity.this,MLOC.userId+"_iotCarId", liveId);
InterfaceUrls.demoReportLive(liveId,liveItem.getLiveName(),MLOC.userId);
starLive();
}
@Override
public void failed(final String errMsg) {
//创建失败
MLOC.d("XHLiveManager","createLive failed "+errMsg);
removeListener();
finish();
}
});
} private void starLive(){
//开始直播
liveManager.startLive(liveId, new IXHCallback() {
@Override
public void success(Object data) {
//成功
MLOC.d("XHLiveManager","startLive success "+data);
//给操控者发送直播间ID
XHClient.getInstance().getChatManager().sendOnlineMessage(liveId, driverId, null);
}
@Override
public void failed(final String errMsg) {
//失败
MLOC.d("XHLiveManager","startLive failed "+errMsg);
MLOC.saveSharedData(SampleLiveActivity.this,MLOC.userId+"_iotCarId", "");
stop();
}
});
}
@Override
public void dispatchEvent(String aEventID, boolean success, final Object eventObj) {
MLOC.d("XHLiveManager","dispatchEvent "+aEventID + eventObj);
switch (aEventID){
case AEvent.AEVENT_C2C_REV_MSG:
XHIMMessage message = (XHIMMessage) eventObj;
if(message.fromId.equals(driverId)){
String command = message.contentData;
if(command.equals("IotCarStart")){
XHClient.getInstance().getChatManager().sendOnlineMessage(liveId, driverId, null);
}
}
break;
case AEvent.AEVENT_LIVE_ADD_UPLOADER:
//连麦者加入,因为小车不需要播放,所以设置为不接收视频
StarRtcCore.getInstance().setNullVideo();
break;
case AEvent.AEVENT_LIVE_REMOVE_UPLOADER:
//操控者退出,本次操控结束
driverId = "";
stop();
break;
case AEvent.AEVENT_LIVE_APPLY_LINK:
//收到连麦申请
if(driverId.equals((String) eventObj)){
// 操控者申请,自动同意上麦
liveManager.agreeApplyToBroadcaster(driverId);
}else{
// 拒绝其他人上麦
liveManager.refuseApplyToBroadcaster((String) eventObj);
}
break;
case AEvent.AEVENT_LIVE_ERROR:
removeListener();
finish();
MLOC.d("VideoLiveActivity","AEVENT_LIVE_ERROR "+eventObj);
break;
case AEvent.AEVENT_LIVE_REV_REALTIME_DATA:
// 收到实时流数据,操控车的指令
if(success){
try {
JSONObject jsonObject = (JSONObject) eventObj;
final byte[] tData = (byte[]) jsonObject.get("data");
runOnUiThread(new Runnable() {
@Override
public void run() {
//给小车下达指令
GpioManager.getInstance().controlCar(tData);
}
});
} catch (JSONException e) {
e.printStackTrace();
}
}
break;
}
}

4 通过树莓派GPIO和PWM控制小车运动
4.1 GPIO控制驱动电机
我购买的直流电机驱动板可以直接插在树莓派上,并将树莓的引脚再次暴露出来,用起来比较方便。
根据驱动板使用说明,将电机线接到驱动板上,然后通过GPIO设置相应引脚的高低电平就能控制电机的启动停止和正转反转。
使用GPIO要记得先申请相关权限
GPIO使用前记得重置,使用后记得销毁,GPIO口一旦被占用,后来者将无法使用。

    //初始化小车需要的GPIO口
public void initCarGpio(){
manager = PeripheralManager.getInstance();
try {
mGpioLeftRun = manager.openGpio(GpioNameLeftRun);
resetGpio(mGpioLeftRun);
mGpioLeftDirection = manager.openGpio(GpioNameLeftDirection);
resetGpio(mGpioLeftDirection);
mGpioRightRun = manager.openGpio(GpioNameRightRun);
resetGpio(mGpioRightRun);
mGpioRightDirection = manager.openGpio(GpioNameRightDirection);
resetGpio(mGpioRightDirection);
} catch (IOException e) {
MLOC.d("IOTCAR","initCarGpio IOException"+e.getMessage());
e.printStackTrace();
}
MLOC.d("IOTCAR","initCarGpio");
} //关闭车
public void stopCarGpio(){
destoryGpio(mGpioLeftRun);
destoryGpio(mGpioLeftDirection);
destoryGpio(mGpioRightRun);
destoryGpio(mGpioRightDirection); MLOC.d("IOTCAR","stopCarGpio");
}
//重置GPIO
private void resetGpio(Gpio gpio){
try {
if(gpio!=null) {
gpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);//设置为输出,默认低电平
gpio.setActiveType(Gpio.ACTIVE_HIGH);//设置高电平为活跃的
gpio.setValue(false);//设置成低电平
}
} catch (IOException e) {
e.printStackTrace();
}
} //销毁GPIO
private void destoryGpio(Gpio gpio){
try {
if(gpio!=null){
gpio.close();
gpio = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}

在这吐糟一下树莓派和电机驱动板,树莓派每次通电开机时,控制电机使能的两个GPIO口默认输出的都是高电平,导致小车每次开机时会不受控制的一直往前跑。直到开始运行程序,GPIO口被重置成低电平才会停下来…

4.2 PWM控制云台舵机旋转
摄像头云台的控制需要使用PWM,Android Things在树莓派上提供了两个可以生成PWM波的引脚,正好可以将云台的两个舵机接入到树莓派的相应PWM引脚上,一个控制左右转动,一个控制上上下转动。

这里需要说一下自己碰到的坑,刚开始不知道Android Things提供了PWM的API,所以自己写了一个SoftPWM。但是PWM的周期是20ms,控制舵机从0到180度转动所需的高电平宽度是0.5ms-1.5ms,然而java的计时器最小单位就是ms,所以根本无法满足舵机调节角度的精度需求。幸亏很快发现了系统提供的API。之后又遇到个坑,只要小车打开视频,云台就无法控制。后来定位到问题是,播放声音时speaker也是通过PWM控制发声的,也就是说PWM被系统拿去播放音频了,所以云台无法控制。解决这个问题只需要再加一个PWM发生器即可,我选择了另一条路,禁用了小车的音频输出。

继续说舵机和PWM,PWM是通过设置频率和占空比来产生不通方波的,理论上舵机的0~180度对应的高电平宽是0.5ms~1.5ms,也就是0度的占空比 = 0.5ms/20ms = 2.5%,可能因为理论值和实际值有偏差,经过我的测试,设置占空比为3.27%时,也就是高电平宽w = 3.27%*20ms = 0.654ms时,舵机的角度为0度。后边又测出了180度时的占空比值,最终计算出角度每增加1度,占空比增加约0.0463。有了0度的基础值和单步值,后边设置角度时就比较方便了。
比如 90度时PWM的占空比=3.27+90*0.0463。

PWM最好持续输出,时断时续的容易造成舵机无规则摆动,停止遥控时一定要记得停止你使用的PWM,不然舵机一直工作,影响舵机使用寿命和电池续航。

    public void initCarPwm(){
MLOC.d("IOTCAR_PWM","initCarPwm");
manager = PeripheralManager.getInstance();
try {
if(mPwmCameraH!=null) {
mPwmCameraH.setEnabled(false);
mPwmCameraH.close();
mPwmCameraH = null;
}
running.set(true);
mPwmCameraH = manager.openPwm(GpioNameHRotate);
mPwmCameraH.setPwmDutyCycle(beginValue+90*stepLenght);//设置占空比
mPwmCameraH.setPwmFrequencyHz(50);//设置频率
mPwmCameraH.setEnabled(true); //开始生成PWM if(mPwmCameraV!=null) {
mPwmCameraV.setEnabled(false);
mPwmCameraV.close();
mPwmCameraV = null;
}
mPwmCameraV = manager.openPwm(GpioNameVRotate);
mPwmCameraV.setPwmDutyCycle(beginValue+40*stepLenght);
mPwmCameraV.setPwmFrequencyHz(50);
mPwmCameraV.setEnabled(true);
} catch (IOException e) {
e.printStackTrace();
}
} public void changePwm(){
try {
if(mPwmCameraV!=null&&lastCameraV!=camearV.get()){
mPwmCameraV.setEnabled(false);//先停掉之前的PWM
mPwmCameraV.setPwmDutyCycle(beginValue+camearV.get()*stepLenght);//重新设置占空比
mPwmCameraV.setEnabled(true);//开始生成PWM
lastCameraV = camearV.get();
}
if(mPwmCameraH!=null&&lastCameraH!=camearH.get()){
mPwmCameraH.setEnabled(false);
mPwmCameraH.setPwmDutyCycle(beginValue+camearH.get()*stepLenght);
mPwmCameraH.setEnabled(true);
lastCameraH = camearH.get();
}
} catch (IOException e) {
e.printStackTrace();
}
} public void stopPwm(){
running.set(false);
if(mPwmCameraH!=null){
try {
mPwmCameraH.setEnabled(false);
mPwmCameraH.close();
mPwmCameraH = null;
} catch (IOException e) {
e.printStackTrace();
}
}
if(mPwmCameraV!=null){
try {
mPwmCameraV.setEnabled(false);
mPwmCameraV.close();
mPwmCameraV = null;
} catch (IOException e) {
e.printStackTrace();
}
}
MLOC.d("IOTCAR_PWM","stopPwm");
}

到这里小车端的程序介绍完了,后边再说说手机端。

同行的认可是远行最大的动力,欢迎转载本博客文章,转载请注明出处,十分感谢。

StarRTC , AndroidThings , 树莓派小车,公网环境,视频遥控(一)准备工作
StarRTC , AndroidThings , 树莓派小车,公网环境,视频遥控(二)小车端
StarRTC , AndroidThings , 树莓派小车,公网环境,视频遥控(三)手机端
源码下载地址

StarRTC , AndroidThings , 树莓派小车,公网环境,视频遥控(二)小车端的更多相关文章

  1. StarRTC , AndroidThings , 树莓派小车,公网环境,视频遥控(三)手机端

    原文地址:http://blog.starrtc.com/?p=111 这篇来介绍一下整个项目的手机端部分.在上一篇里我们已经将sdk导入到项目中了,下边直接用即可. 1 登录StarRTC的服务跟小 ...

  2. StarRTC , AndroidThings , 树莓派小车,公网环境,视频遥控(一)准备工作

    原文地址:http://blog.starrtc.com/?p=48 啥也不说,先来个视频看看效果 视频播放器     00:00   00:54     概述为了体现StarRTC的实时音视频传输能 ...

  3. 【树莓派+.NET MF打造视频监控智能车】遥控篇

    树莓派是最近比较火热的开源硬件,其设备只有信用卡大小,运行着Linux系统,专为学生编程教育而设计.我十多年的技术路线基本以学习微软的技术为主,中间也曾试图学习过linux,但是相对陡峭的学习曲线,只 ...

  4. 【树莓派+.NET MF打造视频监控智能车】控制篇(树莓派)

    对已经具备一定Linux基础的人来说,树莓派学习起来应该非常简单自然.在他们眼中,树莓派就是一个简易版的,卡通版的Linux而已.但是对我这样一个早已习惯微软技术生态系统的人或者初学者来说,要实现一个 ...

  5. 【树莓派+.NET MF打造视频监控智能车】控制篇(.NET MF)

    在上一篇<遥控篇>文章中,我们介绍了Sony PS2手柄信号的采集和编程,通过简单的封装,以事件的方式向我们提供按键信息.本篇文章主要介绍.NET Micro Framework系统接受到 ...

  6. 个人云主机 博客上线 家用宽带-树莓派2-certbot-docker 运行环境简介

    https://blog.cuidp.top/ 博客主页,目前刚建好,没什么数据 服务是 https://github.com/TryGhost/Ghost 现插件是https://github.co ...

  7. SLAM+语音机器人DIY系列:(五)树莓派3开发环境搭建——1.安装系统ubuntu_mate_16.04

    摘要 通过前面一系列的铺垫,相信大家对整个miiboo机器人的DIY有了一个清晰整体的认识.接下来就正式进入机器人大脑(嵌入式主板:树莓派3)的开发.本章将从树莓派3的开发环境搭建入手,为后续ros开 ...

  8. 树莓派搭建python环境服务器

    树莓派搭建python环境服务器 服务器结构大致为:django+uwsgi+nginx+python+sqlite 配置python环境 系统本身自带了python2.7和python3.5.在这里 ...

  9. QT树莓派交叉编译开发环境搭建

    QT树莓派交叉编译开发环境搭建 - JerryZone <Cross-compiling Qt Embedded 5.5 for Raspberry Pi 2> <Qt for Em ...

随机推荐

  1. iOS开发 - 用AFNetworking实现https单向验证,双向验证

    https相关 自苹果宣布2017年1月1日开始强制使用https以来,htpps慢慢成为大家讨论的对象之一,不是说此前https没有出现,只是这一决策让得开发者始料未及,博主在15年的时候就做过ht ...

  2. 通过源码了解ASP.NET MVC 几种Filter的执行过程 在Winform中菜单动态添加“最近使用文件”

    通过源码了解ASP.NET MVC 几种Filter的执行过程   一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神 ...

  3. DataTime? 的 GetValueOrDefault() 方法

    DataTime? 转换为 DataTime类型 就可以调用 ToString()  自定义格式 @item.CreateDate.GetValueOrDefault().ToString(" ...

  4. selenium 并发执行测试用例

    转帖: 要想多线程并发的运行WebDriver,必须同时满足2个条件,首先你的测试程序是多线程,其次需要用到Selenium Server(selenium-server-standalone-XXX ...

  5. IIS6.0中布署MVC站点

    http://www.cnblogs.com/anny-1980/articles/2616657.html

  6. 关于Linux动态库的加载路径

    问题 按如下步骤在Ubuntu上编译安装Google Protocol Buffers $ ./configure $ make $ make check $ sudo make install 运行 ...

  7. [Jobdu] 题目1516:调整数组顺序使奇数位于偶数前面

    题目描述: 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变. 输入: 每个输 ...

  8. 并行开发系列 Plinq等

    http://www.cnblogs.com/huangxincheng/archive/2012/04/03/2430638.html

  9. chrome浏览器的scrollTop问题

    使用zepto里面的scrollTop()方法是没用哒~~~ chrome浏览器: document.body.scrollTop = 一个数值 其它浏览器: document.documentEle ...

  10. vim环境配置

    ctags ctags对浏览代码非常的方便,可以在函数.变量之间跳来跳去等等. 下载 下载路径:http://ctags.sourceforge.net/ 我已经下载过了,路径:/home1/fuju ...