背景
阅读前预备知识:
学习安晓辉老师的qt for android 核心编程(了解怎么在Qt 里边c++ 调用java,java库怎么加进qt工程中)
现在做智能硬件设备,都是使用模块化的流行wifi芯片来接入云端,实现实时通讯和数据交互的工作.以前使用wifi芯片我们经常会遇到一个问题:在wifi设备初始化过程中,我们要手动从ap模式配置连接路由器,这个操作流程对于我们开发人员来说看起来很简单,当换路由器的时候,按复位键然后手动配置就好了.甚至现在我看到许多这样的产品比如网络摄像头,一些远程控制设备都是这个模式.甚至我在学校工作室的时候老师买了一套智能家居设备,我们要看着一本说明书按着流程来手动配置网络.也就是现在大部分智能设备还是存在这个问题(其实已经有解决方案了,也许开发商还没有留意到现在流行的智能网络配置功能,这个有没缺点漏洞我不知道,毕竟我也是新手,只用这个技术做过简单的智能家居系统).
wifi设备实现网络配置方式之间差异
比起传统的ap模式网页配置wif芯片网络或者串口配置网络,smartconfig是一种新型的智能网络配置功能,操作起来更智能方便,时物联网芯片产品的一大趋向。
区别:
ap模式下的网页形式配置
ap (所谓ap模式就是把wifi芯片启动成像路由器一样,可以让其他wifi终端连接到这个wifi芯片)模式下的网页形式是通过wifi终端手动搜索wifi芯片开的ap服务,然后连接该wifi芯片。接着在wifi终端打开浏览器,访问wifi芯片的地址(比如: http://192.168.0.1 ),由于wifi芯片本来支持ap模式,它已经是一个简单的网站服务,通过这网页去获取权限设置wifi芯片提供的设置项服务,比如重启连接到路由器等等。
串口配置
大部分wifi芯片都支持串口命令控制wifi芯片的所有功能。这时候就需要通过串口命令区控制wifi芯片了。
smartconfig
smartconfig 是一个面向软件开发者的智能网络配置功能。硬件开发商无需关系自己的wifi芯片怎么连接到路由器,也无需知道动态的路由怎么被切换。因为smartconfig时通过终端软件去让wifi芯片连接到路由器的。具体实现原理参考:智能硬件接入云平台解决方案
微信硬件平台
微信硬件平台里边有一个叫做airkiss 技术,下面的两个产品都支持这个技术,对于实现智能网络配置功能,它是一个相对完美的平台和方案.详细的学习请自行进平台官网看文档.
hi-fly系列wifi芯片(笔者的项目就是用这个芯片)
esp8266 wifi 芯片
hi-fly和esp系列芯片看起来大同小异,知识esp8266芯片实现智能网络配置比较麻烦,它里边只有实现源码,并没有封装成java库来使用,hi-fly产品就产商提供了一个java库,后边着重介绍怎么在qt里边使用这个库.
wifi芯片学习:
如果有官方评估版可以完全参考HF-LPT220wifi模块评估版使用指南学习搭建完整的调试环境,如果没有的话请参考HF-LPT220wifi模块用户手册这份文档做一个调试板子。
这个板子是自己做的,使用的时HF-LPT220芯片。电路设计按情况,留出串口和复位键,就可以当作官方评估版使用了。
前期硬件开发要了解wifi芯片的操作:
监听wifi芯片串口传来的数据
了解透传模式
了解串口数据传输和wifi串口控制命令。
软件实现smartconfig
就拿我的项目来作分析:
我们在创建qt android项目的时候要把hi-flying 提供的hiflying-iots-android-smartlink7.0.2.jar 拷贝到android/libs目录下
然后写给c++调用的java接口类:
SmartLinkManager.java
package com.tommego;
//hifly smart link libs
import com.hiflying.smartlink.ISmartLinker;
import com.hiflying.smartlink.OnSmartLinkListener;
import com.hiflying.smartlink.SmartLinkedModule;
import com.hiflying.smartlink.v3.SnifferSmartLinker;
import com.hiflying.smartlink.v7.MulticastSmartLinker;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
//屏幕像素密度
import android.util.DisplayMetrics;
public class SmartLinkManager extends org.qtproject.qt5.android.bindings.QtActivity implements OnSmartLinkListener{
private static SmartLinkManager m_instance;
//smart link
protected static ISmartLinker mSnifferSmartLinker;
private static boolean mIsConncting = false;
protected static Handler mViewHandler = new Handler();
// protected static ProgressDialog mWaitingDialog;
private static BroadcastReceiver mWifiChangedReceiver;
private static String mSSID;
public SmartLinkManager(){
m_instance = this;
//smart linker initialization
mSnifferSmartLinker = MulticastSmartLinker.getInstance();
}
public static void startSmartLink(String ssid,String pwd){
// TODO Auto-generated method stub
if(!mIsConncting){
//设置要配置的ssid 和pswd
try {
mViewHandler.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Toast.makeText(m_instance.getApplicationContext(), "正在后台配置wifi,请稍后",
Toast.LENGTH_LONG).show();
}
});
mSnifferSmartLinker.setOnSmartLinkListener(m_instance);
// showDialog();
//开始 smartLink
mSnifferSmartLinker.start(m_instance.getApplicationContext(), pwd.trim(),
ssid.trim());
mIsConncting = true;
// mWaitingDialog.show();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void stopSmartLink(){
mSnifferSmartLinker.stop();
mIsConncting = false;
// closeDialog();
}
public static String getSSID(){
WifiManager conMan = (WifiManager) m_instance.getSystemService(Context.WIFI_SERVICE);
return conMan.getConnectionInfo().getSSID();
}
@Override
public void onLinked(final SmartLinkedModule module) {
// TODO Auto-generated method stub
// Log.w(TAG, "onLinked");
mViewHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(m_instance.getApplicationContext(), "发现新wifi模块"+
"\n mac:"+module.getMac()+ "\n ip:"+module.getModuleIP(),
Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onCompleted() {
// Log.w(TAG, "onCompleted");
mViewHandler.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Toast.makeText(m_instance.getApplicationContext(), "智能配置完成!",
Toast.LENGTH_SHORT).show();
// mWaitingDialog.dismiss();
mIsConncting = false;
// closeDialog();
}
});
}
@Override
public void onTimeOut() {
// Log.w(TAG, "onTimeOut");
mViewHandler.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Toast.makeText(m_instance.getApplicationContext(), "配置超时!",
Toast.LENGTH_SHORT).show();
// mWaitingDialog.dismiss();
mIsConncting = false;
}
});
}
//获取屏幕像素密度
public static double getDentisy(){
DisplayMetrics metrics=new DisplayMetrics();
m_instance.getWindowManager().getDefaultDisplay().getMetrics(metrics);
return metrics.density;
}
// public static native void showDialog();
// public static native void closeDialog();
}
然后c++ 就可以调度这个库了:
wifidevicemanager.h
#ifndef WIFIDEVICEMANAGER_H
#define WIFIDEVICEMANAGER_H
#include <QObject>
#include <QUdpSocket>
#include <QHostAddress>
#include <QList>
#include <QAndroidJniObject>
#include <jni.h>
#include <QDebug>
#include <QByteArray>
#include <QTcpSocket>
struct WifiDevice{
QString ip,mac;
};
class WifiDeviceManager : public QObject
{
Q_OBJECT
public:
explicit WifiDeviceManager(QObject *parent = 0);
//device list
Q_PROPERTY(QString deviceList READ deviceList NOTIFY deviceListChanged)
QString deviceList();
Q_PROPERTY(QString revDatas READ revDatas NOTIFY revDatasChanged)
QString revDatas();
Q_PROPERTY(QString revTcpDatas READ revTcpDatas NOTIFY revTcpDatasChanged)
QString revTcpDatas();
//tcp socket properties
Q_PROPERTY(QString tcpState READ tcpState NOTIFY tcpStateChanged)
QString tcpState();
//tcp con
Q_INVOKABLE void connectTcp(QString ip);
Q_INVOKABLE void sendDatasTcp(QString data);
//smart config
Q_INVOKABLE void startSmartLink(QString SSID,QString pwd);
Q_PROPERTY(QString ssID READ ssID NOTIFY ssIDChanged)
QString ssID(){
return mssid;
}
//send udp datas
Q_INVOKABLE void searchDevice();
//send datas
Q_INVOKABLE void sendDatas(QString datas);
Q_INVOKABLE void sendDatas(QString datas,int port);
Q_INVOKABLE void setDAddress(QString ad){
this->dAddress=ad;
}
signals:
void deviceListChanged();
void ssIDChanged();
void revDatasChanged();
void tcpStateChanged();
void revTcpDatasChanged();
public slots:
void readDatas();
void readTcpDatas();
private:
QString myAddress;
QList<WifiDevice> mdeviceList;
QUdpSocket *socket;
QTcpSocket *tcpSocket;
int port;
QString mssid;
QString mrevDatas;
QString mRevTcpDatas;
QString dAddress;
QString m_tcpState;
};
#endif // WIFIDEVICEMANAGER_H
wifidevicemanager.cpp
#include "wifidevicemanager.h"
WifiDeviceManager::WifiDeviceManager(QObject *parent) : QObject(parent)
{
//init socket
port=48899;
socket=new QUdpSocket(this);
socket->bind(QHostAddress::Broadcast,port);
tcpSocket=new QTcpSocket();
tcpSocket->bind (8899);
connect(socket,SIGNAL(readyRead()),this,SLOT(readDatas()));
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readTcpDatas()));
this->m_tcpState="unConnected";
tcpStateChanged();
//init ssid
QAndroidJniObject str=QAndroidJniObject::callStaticObjectMethod("com/tommego/SmartLinkManager",
"getSSID",
"()Ljava/lang/String;");
this->mssid=str.toString();
ssIDChanged();
//init address
QHostAddress address;
myAddress=address.toString();
}
QString WifiDeviceManager::deviceList(){
return "";
}
void WifiDeviceManager::readDatas(){
socket->waitForReadyRead(100);
// qDebug()<<"&&&&&&&&&&&&&&&&&&&&&datas:"<<socket->readAll();
this->mrevDatas=socket->readAll();
while (socket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(socket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
// currentDatas="aa";
// mdataChanged();
socket->readDatagram(datagram.data(), datagram.size(),
&sender, &senderPort);
mrevDatas=datagram.data();
revDatasChanged();
// mdataChanged();
}
revDatasChanged();
}
void WifiDeviceManager::startSmartLink(QString SSID, QString pwd){
QAndroidJniObject cssid=QAndroidJniObject::fromString(SSID);
QAndroidJniObject cpwd=QAndroidJniObject::fromString(pwd);
QAndroidJniObject::callStaticMethod<void>("com/tommego/SmartLinkManager",
"startSmartLink",
"(Ljava/lang/String;Ljava/lang/String;)V",
cssid.object<jstring>(),
cpwd.object<jstring>());
}
void WifiDeviceManager::searchDevice(){
QByteArray cmd;
cmd.append("HF-A11ASSISTHREAD");
QString address="192.168.0.255";
QHostAddress ad(address);
socket->writeDatagram(cmd,ad,port);
// socket->writeDatagram(cmd,"192.168.1.255",port);
}
void WifiDeviceManager::sendDatas(QString datas){
QByteArray cmd;
cmd.append(datas);
QString address="192.168.0.255";
QHostAddress ad(address);
socket->writeDatagram(cmd,ad,port);
// socket->writeDatagram(cmd,"192.168.1.255",port);
}
QString WifiDeviceManager::revDatas(){
return this->mrevDatas;
}
void WifiDeviceManager::sendDatas(QString datas, int mport){
QByteArray cmd;
cmd.append(datas);
QString address="192.168.0.255";
QHostAddress ad(address);
socket->writeDatagram(cmd,ad,mport);
}
QString WifiDeviceManager::tcpState (){
return this->m_tcpState;
}
void WifiDeviceManager::connectTcp (QString ip){
if(tcpSocket->state()==QTcpSocket::ConnectedState)
return ;
QHostAddress address(ip);
tcpSocket->connectToHost(address,8899);
if(tcpSocket->state()==QTcpSocket::ConnectedState)
this->m_tcpState="connected";
else
this->m_tcpState="unConnected";
tcpStateChanged();
}
void WifiDeviceManager::sendDatasTcp (QString data){
if(tcpSocket->state()==QTcpSocket::ConnectedState){
QByteArray mdata;
mdata.append(data);
this->tcpSocket->write (mdata);
}
}
void WifiDeviceManager::readTcpDatas(){
tcpSocket->waitForReadyRead(500);
mRevTcpDatas=tcpSocket->readAll();
revTcpDatasChanged();
}
QString WifiDeviceManager::revTcpDatas(){
return this->mRevTcpDatas;
}
这里是调用smartlink库核心实现部分,在实现这个功能过程,遇到了很多java接口的坑.不过还好,整个源码实现完美在qt中跑起来了.这时候我们的app,就可以通过smartlink实现对为初始化的wifi设备进行一键配置网络了!(就算是有几十台wifi设备,都能直接在app中一键配置并且搜到wifi设备哈哈!)
功能截图: