1.1制作材料:
硬件系统主要由单片机主控模块、电源模块、电机驱动模块、WiFi通信模块和无线视频监控模块组成。
1.2主控模块
主控模块采用STM32F103为主控制器,STM32F103属于中低端的32位ARM微控制器,该系列芯片是意法半导体(ST)公司出品,其内核是Cortex-M3。该系列芯片按片内Flash的大小可分为三大类:小容量(16K和32K)、中容量(64K和128K)、大容量(256K、384K和512K)。芯片集成定时器,CAN,ADC,SPI,I2C,USB,UART,等多种功能。STM32F103可使用keilC语言编译,支持STLink-SWD在线调试,主要用于收集信息、处理数据、协调系统中的每个功能模块预计要完成的任务。(图3)
图3单片机
1.3 WiFi模块ESP8266
WIFI通信模块作为STM32和云平台通信的中介,两端都通过WIFI模块进行数据交互,该模块选用ESP8266芯片,其特点就是如果断开连接,再次连接,模块会连接到最近一次连接过的热点。数据将通过ESP8266存放到云平台,并且stm32也能通过ESP8266获取云平台的数据。以下表格1是ESP8266与STM32的接线图。WiFi模块ESP8266如图4所示。
1.4L298N电机驱动模块
电机驱动原理:小车左轮右轮上分别配有两个电机,左轮电机A与右轮电机B的正转、反转和停止决定着小车的运动模式。而STM32单片机4根电机控制信号线连接着L298N的IN1~IN4,另外两根PWM调速信号线连着ENA和ENB。输入信号线IN1和IN2控制电机A的运动,直流电机A接OUT1和OUT2。同理,IN3和IN4合起来控制了电机B的运动,如图5电机模块L298N所示。
图5电机模块L298N
电机调速原理:L298N上还有ENA和ENB两个信号输入端口,这两个端口的作用是控制信号的使能,低电平有效。由于L298N有控制使能的信号线,则可以通过控制ENA和ENB的信号来进行PWM调速。原理是开关管在一个周期T的时间内导通的时间为t,那么电机两端的平均电压U=V*t()T=aV。其中,a=t/T(占空比),V是电源电压。电动机的转速与电动机两端的电压成正比,而电动机两端的电压与控制波形的占空比成正比,因此电动机的转动速度与PWM信号的占空比成比例,信号的占空比越大电动机转动得越快。在硬件电路上将STM32单片机的PA0~PA3端口分别接到L298N的IN1~IN4上,通过改变PA0~PA3口的高低电平控制小车的行驶方向,通过调节信号线ENA和ENB上的PWM占空比来控制小车运动速度。
1.5电源模块DC-DC
DC-DC是用开关电源的思想实现的。DC-DC有降压和升压两种,这里叫降压。DC-DC内部有振荡器和斩波器模块。输出端有电容器,对中间的脉冲波形进行微积分,输出5V的直流波形。该降压的过程相对于恒压模块,更大幅度地避免了降压模块上的电力消耗,内部振荡部通过控制其占空比,输出恒定。例如在本次设计中使用输入为12V电源的降压模块,其中输出11V引脚给电机供电,输出3.3V引脚给单片机供电。下面图6所示为电源模块DC-DC。
图6电源模块DC-DC
2.1小车制作部分
2.1.1主控制器
主控制器STM32F103C8T6以ARM32位CortexM3作为CPU,其开发环境为常用的ARM开发环境,本系统采用KeilμVision5作为开发工具。ARM公司在2015年发布的集成开发环境RealViewMDK集成了最新版本的KeilμVision5,其编译器、调试工具实现与ARM器件的最完美匹配,用C语言完成编程。
2.1.2控制小车的软件设计
电机的GPIO口的初始化,通过调用库函数RCC_APB2PeriphClockCmd[1],使能RCC_APB2Periph_GPIOA/RCC_APB2Periph_GPIOB端口时钟,而由于需要重映射,所以使能AFIO时钟RCC_APB2Periph_AFIO,之后通过复用推挽输出GPIO_Mode_Out_PP来将GPIO进行输出,输出到L298N的IN1~IN4,这里有两个电机,所以初始化了8个引脚,分别为PB引脚的GPIO_Pin_11,GPIO_Pin_12,和PA引脚的GPIO_Pin_3~GPIO_Pin_8。之后通过GPIO_SetBits拉高使能和GPIO_ResetBits拉低使能来控制电机的正反转动。
TIM3定时器的初始化,通过调用RCC_APB1PeriphClockCmd使能RCC_APB1Periph_TIM3,用于设定定时器3中断服务程序。
TIM1定时器的初始化,通过调用RCC_APB1PeriphClockCmd使能RCC_APB2Periph_TIM1,开启了TIM1的时钟之后,设置ARR和PSC两个寄存器的值来控制输出PWM的周期。之后输出比较通道1到4的初始化,之后,就可以控制TIM1的CH1到CH4输出PWM了,这里我使用的是PA6和PA7来进行左轮的使能,PB0和PB1进行右轮的使能。通过调整输出PWM波的频率还可以对轮子的转速进行调速。
关于小车的前进后退,可以这样实现:前进:左轮和右轮以同样的速率向前转动,小车向正前方运动;后退:左轮和右轮以同样的速率向后转动,小车向正后方运动。右转:当左轮速率大于右轮的速率时,小车右转;左转:当右轮速率大于左轮的速率时,小车左转。原地打转:当两轮速率相同,方向相反时,小车原地打转。转弯半径与差值的关系:小车转弯半径由左右电机数值差决定,差值越小,半径越大;差值越大,半径越小。以下图7为MCU控制小车的流程图。
代码如下(示例):
//单片机头文件
#include "stm32f10x.h"
//网络设备
#include "esp8266.h"
//协议文件
#include "onenet.h"
#include "mqttkit.h"
//硬件驱动
#include "usart.h"
//C库
#include
#include
#define PROID "111111" //产品ID
#define AUTH_INFO "111111" //鉴权信息
#define DEVID "1111111" //设备ID
extern unsigned char esp8266_buf[128];
//==========================================================
// 函数名称: OneNet_DevLink
//
// 函数功能: 与onenet创建连接
//
// 入口参数: 无
//
// 返回参数: 1-成功 0-失败
//
// 说明: 与onenet平台建立连接
//==========================================================
_Bool OneNet_DevLink(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
unsigned char *dataPtr;
_Bool status = 1;
printf("OneNet_DevLink\r\nPROID: %s, AUIF: %s, DEVID:%s\r\n", PROID, AUTH_INFO, DEVID);
if(MQTT_PacketConnect(PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0)
{
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //上传平台
dataPtr = ESP8266_GetIPD(250); //等待平台响应
if(dataPtr != NULL)
{
if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
{
switch(MQTT_UnPacketConnectAck(dataPtr))
{
case 0:printf("Tips: 连接成功\r\n");status = 0;break;
case 1:printf("WARN: 连接失败:协议错误\r\n");break;
case 2:printf("WARN: 连接失败:非法的clientid\r\n");break;
case 3:printf("WARN: 连接失败:服务器失败\r\n");break;
case 4:printf("WARN: 连接失败:用户名或密码错误\r\n");break;
case 5:printf("WARN: 连接失败:非法链接(比如token非法)\r\n");break;
default:printf("ERR: 连接失败:未知错误\r\n");break;
}
}
}
MQTT_DeleteBuffer(&mqttPacket); //删包
}
else
printf("WARN: MQTT_PacketConnect Failed\r\n");
return status;
}
u8 velue0 ;
u8 velue1 ;
unsigned char OneNet_FillBuf(char *buf)
{
char text[32];
memset(text, 0, sizeof(text));
//strcpy(buf, ",;");
strcpy(buf, ",;");
memset(text, 0, sizeof(text));
sprintf(text, "temperature,%d;", velue0);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "humidity,%d;", velue1);
strcat(buf, text);
//printf("buf: %s \r\n", buf);
return strlen(buf);
}
//==========================================================
// 函数名称: OneNet_SendData
//
// 函数功能: 上传数据到平台
//
// 入口参数: type:发送数据的格式
//
// 返回参数: 无
//
// 说明:
//==========================================================
void OneNet_SendData(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
char buf[128];
short body_len = 0, i = 0;
// printf("Tips: OneNet_SendData-MQTT\r\n");
memset(buf, 0, sizeof(buf));
body_len = OneNet_FillBuf(buf); //获取当前需要发送的数据流的总长度
if(body_len)
{
if(MQTT_PacketSaveData(DEVID, body_len, NULL, 5, &mqttPacket) == 0) //封包
{
for(; i < body_len; i++)
mqttPacket._data[mqttPacket._len++] = buf[i];
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //上传数据到平台
//printf(" buf[%d]:%c \r\n",i, buf[i]);
//printf("Send %d Bytes\r\n", mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket); //删包
}
else
printf("WARN: EDP_NewBuffer Failed\r\n");
}
}
//==========================================================
// 函数名称: OneNet_RevPro
//
// 函数功能: 平台返回数据检测
//
// 入口参数: dataPtr:平台返回的数据
//
// 返回参数: 无
//
// 说明:
//==========================================================
void OneNet_RevPro(unsigned char *cmd)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
char *req_payload = NULL;
char *cmdid_topic = NULL;
unsigned short req_len = 0;
unsigned char type = 0;
short result = 0;
char *dataPtr = NULL;
char numBuf[10];
int num = 0;
type = MQTT_UnPacketRecv(cmd);
switch(type)
{
case MQTT_PKT_CMD: //命令下发
result = MQTT_UnPacketCmd(cmd, &cmdid_topic, &req_payload, &req_len); //解出topic和消息体
if(result == 0)
{
printf("cmdid: %s, req: %s, req_len: %d\r\n", cmdid_topic, req_payload, req_len);
if(MQTT_PacketCmdResp(cmdid_topic, req_payload, &mqttPacket) == 0) //命令回复组包
{
printf("Tips: Send CmdResp\r\n");
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //回复命令
MQTT_DeleteBuffer(&mqttPacket); //删包
}
}
break;
case MQTT_PKT_PUBACK: //发送Publish消息,平台回复的Ack
if(MQTT_UnPacketPublishAck(cmd) == 0)
// printf("Tips: MQTT Publish Send OK\r\n");
break;
default:
result = -1;
break;
}
ESP8266_Clear(); //清空缓存
// if(result == -1)
// return;
dataPtr = strchr(req_payload, ':'); //搜索':'
if(dataPtr != NULL && result != -1) //如果找到了
{
dataPtr++;
while(*dataPtr >= '0' && *dataPtr <= '9') //判断是否是下发的命令控制数据
{
numBuf[num++] = *dataPtr++;
}
numBuf[num] = 0;
num = atoi((const char *)numBuf); //转为数值形式
if(strstr((char *)req_payload, "onoff")) //搜索"onoff"
{
printf("onoff = %d", num);
velue0 = num;
}
else if(strstr((char *)req_payload, "switch")) //搜索"switch"
{
printf("switch = %d", num);
velue1 = num;
}
}
if(type == MQTT_PKT_CMD || type == MQTT_PKT_PUBLISH)
{
MQTT_FreeBuffer(cmdid_topic);
MQTT_FreeBuffer(req_payload);
}
}
//定义一个数据交换函数
void Shujujiaohuan(unsigned char wendu,unsigned char shidu)
{
velue0=wendu;
velue1=shidu;
}
2.1.3ONENet云平台软件设计
OneNET[3]是中国移动推出的物联网开放平台,该平台屏蔽了复杂的技术细节,提供多种协议类型,支持多种智能硬件的接入和大数据服务。用户按照OneNET云平台的规范接入平台,上传数据,实现数据传输与存储管理功能,同时平台还支持MQTT、EDP、HTTP等接入协议。用户在官网注册账号后即可进入云平台创建项目。数据上传完成后,用户可以在网页和手机APP端查看数据和对应的变化曲线,也可以下发控制指令,控制智能设备的运行。本系统采用OneNET云平台提供的网页应用控件、命令下发控件等实现了数据接收和远程指令下发等应用。通过网页端和手机APP端的同步应用,可快速方便地构建远程智能监控系统。系统运行时,智能小车在远程指令的控制下,实现前进、后退、转弯。
我们通过云平台产品的多协议接入中的MQTT协议创建一个产品,还需要创建一个设备,之后取出里面的产品ID,设备ID,还有鉴权信息,用于上传数据、查看数据、下发命令。
在keil5中通过RCC_APB2PeriphClockCmd库使能RCC_APB2Periph_GPIOB端口时钟,对ESP8266的使能引脚和复位引脚初始化,之后通过AT指令如:AT+CWMODE=1、AT+CWDHCP=1等等进行模式设置,连接手机热点,开启单连接,连接TCP服务器。
之后通过MQTT协议与云平台创建连接,如果8266成功连接到onenet云平台会打印Tips:连接成功的信息提示语,如果连接失败会打印出连接失败的原因。
连接成功后通过OneNet_SendData和OneNet_RevPro函数来进行发送和接收数据,在接收数据的方法中使用字符串匹配函数来进行在云端下发命令。
2.2APP制作部分
本设计釆用Android Studio作为系统开发软件。Android Studio提供了用于开发和调试的集成Android开发工具,功能比Eclipse更强大。虽然安卓手机或者平板具有很多种尺寸的屏幕与分辨率,但开发人员应用集成Android开发工具都能够比较轻松的调整每个分辨率设备上的应用程序.开发人员能够一边写和调试程序并能够看到这个程序在不同屏幕里的外观。以下图8 为Android Studio开发界面。
图8 Android Studio 开发界面
2.2.1视频监控
本次开发使用声网(Agora)[2]的视频通话和信令的SDK进行二次开发。集成了Agora视频SDK并调用API。
创建项目后我们将Agora视频SDK集成到项目中,添加mavenCentral依赖,之后添加网络和设备权限(INTERNET、CAMERA、RECORD_AUDIO、MODIFY_AUDIO_SETTINGS、ACCESS_WIFI_STATE、ACCESS_NETWORK_STATE、BLUETOOTH)
之后我们创建界面布局,在用户界面中,需要设置两个帧布局(FrameLayout),分别展示本地视图和远端视图。
处理Android系统逻辑,导入必要的Android类,如Manifest、FrameLayout等等。添加必要权限的授权逻辑,启动应用程序时,检查是否已在app中授予了实现视频通话所需的权限。如果未授权,使用内置的Android功能申请权限;如果已授权,则返回true。
实现视频通话逻辑,应用开启时,需要依次创建RtcEngine实例,开启视频模块,让本地用户加入频道,将本地视图与处于较低图层的帧布局(FrameLayout)绑定。当其他用户加入频道时,应用捕捉到远端用户加入频道的事件,并将远端视图与处于较高图层的帧布局(FrameLayout)绑定。首先导入必要的Agora类(io.agora.rtc.RtcEngine;io.agora.rtc.video.VideoCanvas;io.agora.rtc.IRtcEngineEventHandler;)
创建变量用以创建并加入视频通话频道。如AppID、频道名称。通过IRtcEngineEventHandler函数监听频道内的远端用户,获取用户的uid信息。从onUserJoined回调获取uid后,调用setupRemoteVideo,设置远端视频视图。由于视频默认禁用,需要调用enableVideo开始视频流,调用CreateRendererView创建一个SurfaceView对象,并将其作为FrameLayout的子对象,将SurfaceView对象传入Agora,以渲染本地视频。
当远端用户加入频道时,通过setupRemoteVideo函数更新远端用户界面。以下图9为视频监控驱动流程图。
图9视频监控驱动流程图
2.2.2APP控制端
Onenet云平台提供开放的API接口,用户可以通过HTTP/HTTPS调用,进行设备管理,数据查询,设备命令交互等操作,在API的基础上,根据自己的个性化需求搭建上层应用。
Android端控制小车,主要是给OneNet服务器发送一个post请求。通过调用平台API-http://api.heclouds.com/devices/868917927来下发命令,下发的格式为onoff:%d(自己设定),当命令下发成功,平台会作出回应。而单片机那边通过订阅的方式接收到APP下发的指令,之后则会执行相应的程序来驱动小车的前进后退调速等。
APP的按钮通过单击监听事件setOnTouchListener来完成,当按钮按下发送驱动小车命令,当按钮松开执行小车停止命令,按钮颜色的变换,通过调用setImageResource方法进行更换图片来完成。布局界面通过RelativeLayout布局嵌套LinearLayout布局来完成,保证按钮在不同的安卓设备端不会乱跑,也保证了美观。以下图10为OneNet数据收流程图。
图10OneNet数据收流程图
package com.example.mobile_monitoring;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import android.view.MotionEvent;
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;
public class MainActivity extends AppCompatActivity {
private ImageButton btn_Lock_advance,btn_Lockback,btn_Lockleft,btn_Lockright;
private TextView show_DeviceName,show_online,show_DeviceID,show_CreateTime,show_Monitor;
private String mip = "183.230.40.39";//服务器地址
private String apikey = "111111111111111111111=";//
private String device_id = "11111111111111";//设备id
private int a=0,b=1,port = 6002;//
private static final String TAG = MainActivity.class.getSimpleName();
// 创建 SurfaceView 对象。
private FrameLayout mLocalContainer;
private SurfaceView mLocalView;
private VideoCanvas mLocalVideo;
private VideoCanvas mRemoteVideo;
// 创建一个 SurfaceView 对象。
private RelativeLayout mRemoteContainer;
private SurfaceView mRemoteView;
private boolean mCallEnd;
private boolean mMuted;
private ImageView mCallBtn;
private ImageView mMuteBtn;
private ImageView mSwitchCameraBtn;
// Java
private static final int PERMISSION_REQ_ID = 22;
// App 运行时确认麦克风和摄像头设备的使用权限。
private static final String[] REQUESTED_PERMISSIONS = {
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(getSupportActionBar() !=null){ getSupportActionBar().hide(); }//去掉标题栏
setContentView(R.layout.activity_main);
//setTitle("智能锁V1.0");//标题
initUI();
// 获取权限后,初始化 RtcEngine,并加入频道。
if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) {
initEngineAndJoinChannel();
}
show_online = findViewById(R.id.txtNetwork);//设备联网情况
/* show_DeviceName = findViewById(R.id.txtDeviceName);//设备名称
*//* show_DeviceID = findViewById(R.id.txtDeviceID);//设备ID
show_CreateTime = findViewById(R.id.txtCreateTime);//设备创建时间*//*
show_Monitor = findViewById(R.id.txtMonitor);//监控信息*/
btn_Lock_advance = findViewById(R.id.btnLock_advance);//前指令
btn_Lockback = findViewById(R.id.btnLock_back);//后
btn_Lockleft = findViewById(R.id.btnLock_left);//左
btn_Lockright = findViewById(R.id.btnLock_right);//右
//ButtonFunction();//按键功能
new Handler().postDelayed(task,2000);
btn_Lock_advance.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
a=1;
Control(a);
btn_Lock_advance.setImageResource(R.drawable.ic_btn_forward_on);
//btn_Lock_advance.setBackgroundColor(Color.#EAEAEA);
}else if(event.getAction() == MotionEvent.ACTION_UP){
a=5;
Control(a);
btn_Lock_advance.setImageResource(R.drawable.ic_btn_forward_off);
}
return false;
}
});
btn_Lockback.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
a=2;
Control(a);
btn_Lockback.setImageResource(R.drawable.ic_btn_backward_on);
}else if(event.getAction() == MotionEvent.ACTION_UP){
a=5;
Control(a);
btn_Lockback.setImageResource(R.drawable.ic_btn_backward_off);
}
return false;
}
});
btn_Lockleft.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
a=4;
Control(a);
btn_Lockleft.setImageResource(R.drawable.ic_btn_left_on);
}else if(event.getAction() == MotionEvent.ACTION_UP){
a=5;
Control(a);
btn_Lockleft.setImageResource(R.drawable.ic_btn_left_off);
}
return false;
}
});
btn_Lockright.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
a=3;
Control(a);
btn_Lockright.setImageResource(R.drawable.ic_btn_right_on);
}else if(event.getAction() == MotionEvent.ACTION_UP){
a=5;
Control(a);
btn_Lockright.setImageResource(R.drawable.ic_btn_right_off);
}
return false;
}
});
}
private Handler handler = new Handler();
private Runnable task = new Runnable() {
@Override
public void run() {
handler.postDelayed(this,3000);
new Thread(new Runnable() {
@Override
public void run() {
try{
OkHttpClient client = new OkHttpClient();
String url1 = String.format("http://api.heclouds.com/devices/%s",device_id);
Request request = new Request.Builder()
.url(url1)
.addHeader("api-key", apikey)
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
Log.w("test", responseData);
//json提取数据
JSONObject jsonObject = new JSONObject(responseData);
JSONObject jsonObjectData = jsonObject.getJSONObject("data");
//Log.e("e", String.valueOf(jsonObjectData));
final String device_title = jsonObjectData.getString("title");//提取设备名称
final String device_id = jsonObjectData.getString("id");//提取设备id
final String create_time = jsonObjectData.getString("create_time");//提取设备创建时间
final String device_online = jsonObjectData.getString("online");//提取设备是否在线
//设备监控信息
OkHttpClient client2 = new OkHttpClient();
String url2 = String.format("http://api.heclouds.com/devices/%s/datastreams/test2",device_id);
Request request2 = new Request.Builder()
.url(url2)
.addHeader("api-key", apikey)
.build();
Response response2 = client2.newCall(request2).execute();
String responseData2 = response2.body().string();
//json提取数据
JSONObject jsonObject2 = new JSONObject(responseData2);
JSONObject jsonObjectData2 = jsonObject2.getJSONObject("data");
final String current_value = jsonObjectData2.getString("current_value");//提取最新数据
final String update_at = jsonObjectData2.getString("update_at");//提取最新数据的时间
handler.post(new Runnable() {
@Override
public void run() {
//show_DeviceName.setText(device_title);
// show_DeviceID.setText("设备ID:"+device_id);
// show_CreateTime.setText("创建时间:"+create_time);
if(device_online.equals("true")) {
show_online.setText("在 线");
show_online.setTextColor(Color.GREEN);
}
if(device_online.equals("false")){
show_online.setText("离 线");
show_online.setTextColor(Color.RED);
}
}
});
} catch (Exception e) {
e.printStackTrace();
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "请检查手机是否连接网络", Toast.LENGTH_SHORT).show(); //显示提示
}
});
}
}
}).start();
}
};
//获取数据
public void GetData(){
new Thread(new Runnable() {
@Override
public void run() {
try{
OkHttpClient client = new OkHttpClient();
String url = String.format("http://api.heclouds.com/devices/%s/datastreams/test1",device_id);
Request request = new Request.Builder()
.url(url)
.addHeader("api-key", apikey)
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
Log.w("test", responseData);
//json提取数据
JSONObject jsonObject = new JSONObject(responseData);
JSONObject jsonObjectData = jsonObject.getJSONObject("data");
Log.e("e", String.valueOf(jsonObjectData));
final String current_value = jsonObjectData.getString("current_value");//提取最新数据
final String update_at = jsonObjectData.getString("update_at");//提取最新数据的时间
}catch (IOException | JSONException e) {
e.printStackTrace();
}
}
}).start();
}
//下发命令
public void Control(final int val) {
new Thread(new Runnable() {
@Override
public void run() {
try{
OkHttpClient client=new OkHttpClient();
String url = String.format("http://api.heclouds.com/cmds?device_id=%s",device_id);
String body = String.format("onoff:%d", val);
RequestBody body1=RequestBody.create(MediaType.parse("application/json; charset=utf-8"), body);
Request request = new Request.Builder()
.url(url)
.headers(new Headers.Builder().add("api-key", apikey).build())//表头
.post(body1)
.build();
Call call=client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {//请求失败
Log.e("e", "post请求失败");
}
@Override
public void onResponse(Call call, Response response) throws IOException {//请求成功
}
});
//平台回应
Response response = client.newCall(request).execute();
String jsonStr = response.body().string();
Log.e(" onenet回应数据",jsonStr);
JSONObject jsonObject = new JSONObject(jsonStr);
String jsonObjectRes = jsonObject.getString("errno");
final String jsonObjectError = jsonObject.getString("error");
Log.e("a", jsonObjectRes);
if (jsonObjectRes == "10"){
handler.post(new Runnable() {
@Override
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("请求错误");
builder.setMessage("设备不在线");
//builder.setIcon(R.mipmap.ic_launcher);//图标显示
builder.setCancelable(true);
builder.show();
// AlertDialog dialog=builder.create();
// dialog.show();
}
});
}else if (jsonObjectRes == "0"){
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "远程控制成功", Toast.LENGTH_SHORT).show(); //显示提示
}
});
}else {
handler.post(new Runnable() {
@Override
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("请求错误");
builder.setMessage(jsonObjectError);
builder.setCancelable(true);
// builder.show();
}
});
}
} catch (Exception e) {
// toastStr = "连接失败,请检测手机或设备的网络状态";
e.printStackTrace();
}
}
}).start();
}
/* @Override
protected void onDestroy(){
super.onDestroy();
handler.removeCallbacks(task);
}*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event){
if(keyCode == KeyEvent.KEYCODE_BACK){
exitBy2Click();
}
return false;
}
private static Boolean isExit = false;
private void exitBy2Click() {
Timer tExit = null;
if(!isExit){
isExit = true;
Toast.makeText(this,"再按一次退出程序",Toast.LENGTH_SHORT).show();
tExit = new Timer();
tExit.schedule(new TimerTask() {
@Override
public void run() {
isExit = false;
}
},2000);
}else {
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
}
//shengwang
private void initUI() {
mLocalContainer = findViewById(R.id.local_video_view_container);
mRemoteContainer = findViewById(R.id.remote_video_view_container);
mCallBtn = findViewById(R.id.btn_call);
mMuteBtn = findViewById(R.id.btn_mute);
mSwitchCameraBtn = findViewById(R.id.btn_switch_camera);
// Sample logs are optional.
}
private void initEngineAndJoinChannel() {
initializeEngine();
setupLocalVideo();
joinChannel();
}
private boolean checkSelfPermission(String permission, int requestCode) {
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode);
return false;
}
return true;
}
// Java
private RtcEngine mRtcEngine;
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
@Override
// 注册 onJoinChannelSuccess 回调。
// 本地用户成功加入频道时,会触发该回调。
public void onJoinChannelSuccess(String channel, final int uid, int elapsed) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("agora","Join channel success, uid: " + (uid & 0xFFFFFFFFL));
}
});
}
@Override
// 注册 onUserJoined 回调。
// 远端用户成功加入频道时,会触发该回调。
// 可以在该回调中调用 setupRemoteVideo 方法设置远端视图。
public void onUserJoined(final int uid, int elapsed) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("agora","Remote user joined, uid: " + (uid & 0xFFFFFFFFL));
setupRemoteVideo(uid);
}
});
}
private void onRemoteUserLeft(int uid) {
if (mRemoteVideo != null && mRemoteVideo.uid == uid) {
removeFromParent(mRemoteVideo);
// Destroys remote view
mRemoteVideo = null;
}
}
@Override
// 注册 onUserOffline 回调。
// 远端用户离开频道或掉线时,会触发该回调。
public void onUserOffline(final int uid, int reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("agora","User offline, uid: " + (uid & 0xFFFFFFFFL));
onRemoteUserLeft(uid);
}
});
}
};
// 初始化 RtcEngine 对象。
private void initializeEngine() {
try {
mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler);
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
}
}
// Java
private void setupLocalVideo() {
// 启用视频模块。
mRtcEngine.enableVideo();
mLocalView = RtcEngine.CreateRendererView(getBaseContext());
mLocalView.setZOrderMediaOverlay(true);
mLocalContainer.addView(mLocalView);
// 设置本地视图。
VideoCanvas localVideoCanvas = new VideoCanvas(mLocalView, VideoCanvas.RENDER_MODE_HIDDEN, 0);
mRtcEngine.setupLocalVideo(localVideoCanvas);
}
// Java
private void joinChannel() {
String token = getString(R.string.agora_access_token);
if (TextUtils.isEmpty(token) || TextUtils.equals(token, "#YOUR ACCESS TOKEN#")) {
token = null; // default, no token
}
// 调用 joinChannel 方法 加入频道。
mRtcEngine.joinChannel(token, "demoChannel1", "Extra Optional Data", 0);
}
// Java
private void setupRemoteVideo(int uid) {
mRemoteView = RtcEngine.CreateRendererView(getBaseContext());
mRemoteContainer.addView(mRemoteView);
// 设置远端视图。
mRtcEngine.setupRemoteVideo(new VideoCanvas(mRemoteView, VideoCanvas.RENDER_MODE_HIDDEN, uid));
}
// Java停止发送音频流
public void onLocalAudioMuteClicked(View view) {
mMuted = !mMuted;
mRtcEngine.muteLocalAudioStream(mMuted);
mRtcEngine.muteLocalAudioStream(mMuted);
int res = mMuted ? R.drawable.btn_mute : R.drawable.btn_unmute;
mMuteBtn.setImageResource(res);
}
// Java
public void onSwitchCameraClicked(View view) {
mRtcEngine.switchCamera();
}
public void onCallClicked(View view) {
if (mCallEnd) {
startCall();
mCallEnd = false;
mCallBtn.setImageResource(R.drawable.btn_endcall);
} else {
endCall();
mCallEnd = true;
mCallBtn.setImageResource(R.drawable.btn_startcall);
}
showButtons(!mCallEnd);
}
private void startCall() {
setupLocalVideo();
joinChannel();
}
private void endCall() {
removeFromParent(mLocalVideo);
mLocalVideo = null;
removeFromParent(mRemoteVideo);
mRemoteVideo = null;
leaveChannel();
}
private void showButtons(boolean show) {
int visibility = show ? View.VISIBLE : View.GONE;
mMuteBtn.setVisibility(visibility);
mSwitchCameraBtn.setVisibility(visibility);
}
private ViewGroup removeFromParent(VideoCanvas canvas) {
if (canvas != null) {
ViewParent parent = canvas.view.getParent();
if (parent != null) {
ViewGroup group = (ViewGroup) parent;
group.removeView(canvas.view);
return group;
}
}
return null;
}
// Java
@Override
protected void onDestroy() {
super.onDestroy();
if (!mCallEnd) {
leaveChannel();
}
RtcEngine.destroy();
handler.removeCallbacks(task);
}
private void leaveChannel() {
// 离开当前频道。
mRtcEngine.leaveChannel();
}
}
git代码
https://github.com/lianghuajunone/Mobile_monitoring
设计实物图
本项目设计了一个以STM32为核心的智能视频小车,通过Android Studio开发实现手机端监控且控制设备移动。通过测试,基本实现了用手机通过WIFI控制小车前进、后退、左转、右转等转向运动,可以实时视频监控,整体效果达到预期,小车成本低,稳定性好,在人们的生产、生活中具有非常好的应用前景。虽然设计出的移动监控系统达到了预期的设计要求,但是系统本身还是有一些缺陷。随着自动化和智能化的普及,工业生产对于智能小车的要求也越来越高。本文的视频监控系统只是智能化系统的一个分支,设计的系统硬件比较简单并且只能实现一些简单的功能,但为以后更加智能化地设计打下了一个基础。