[智能硬件][WIFISocket]基于ESP8266的智能插座开发(3)_WIFISocketLite(UDP)

在本文中,将使用UDP广播的方式实现APP远程控制智能插座。

基本原理

Android 手机和智能插座连接到家庭路由器中。APP通过UDP广播的方式向ESP8266发送控制命令。ESP8266接收到控制命令后,执行相应的操作并返回结果。

通信帧格式

采用UDP的方式,不需要考虑TCP中存在的粘包的问题。为了简单起见,采用JSON格式传输数据。

控制命令ID:

const int MESSAGE_GETSWITCHSTATE_REQUEST = 1000;
const int MESSAGE_GETSWITCHSTATE_RESPONSE = 1001;
const int MESSAGE_SETSWITCHSTATE_REQUEST = 1002;
const int MESSAGE_SETSWITCHSTATE_RESPONSE = 1003;

返回结果:

const int STATUS_OK = 200;
const int STATUS_ERR = 304;

通信过程示例:

获取开关状态:

//APP:
{"Cmd":1000}
//ESP8266:
{"Cmd":1001,"Status":200,"SwitchState":false}

控制开关状态:

//APP:
{"Cmd":1002,"SwitchState":true}
//ESP8266:
{"Cmd":1003,"Status":200}

实现过程

源代码目前已开源:https://git.coding.net/zimengyu1992/WIFISocketProject.git

设备端

1)采用WiFiUdp类创建了UDP服务器。
2)采用ArduinoJson库来解析JSON命令包。
核心源码如下:

#ifndef __WIFISOCKETUDPSERVER_H__
#define __WIFISOCKETUDPSERVER_H__
#include <Arduino.h>
#include <WiFiUdp.h>
#include <ESP8266WiFi.h>


class WIFISocketUdpServerClass
{
private:
    const int UDP_SERVER_PORT = 10000;
    const int STATUS_OK = 200;
    const int STATUS_ERR = 304;
    const int MESSAGE_GETSWITCHSTATE_REQUEST = 1000;
    const int MESSAGE_GETSWITCHSTATE_RESPONSE = 1001;
    const int MESSAGE_SETSWITCHSTATE_REQUEST = 1002;
    const int MESSAGE_SETSWITCHSTATE_RESPONSE = 1003;
    WiFiUDP mUdp;
public:
    void begin();
    void loop();
};
extern WIFISocketUdpServerClass WIFISocketUdpServer;
#endif

#include <ArduinoJson.h>
#include "WIFISocketSwitch.h"
#include "WIFISocketUdpServer.h"
WIFISocketUdpServerClass WIFISocketUdpServer;

void WIFISocketUdpServerClass::begin()
{
    mUdp.begin(UDP_SERVER_PORT);
}

void WIFISocketUdpServerClass::loop()
{
    const int PACKET_MAXSIZE = 128;
    uint8_t packet[PACKET_MAXSIZE];
    memset(packet, 0, sizeof(char)*PACKET_MAXSIZE);
    if (!mUdp.parsePacket())
    {
        return;
    }
    mUdp.read(packet, PACKET_MAXSIZE);
    Serial.println((const char *)packet);
    DynamicJsonBuffer  jsonBuffer(PACKET_MAXSIZE);
    JsonObject& root = jsonBuffer.parseObject(packet);
    if (!root.success())
    {
        return;
    }
    if (!root.containsKey("Cmd"))
    {
        return;
    }
    int cmd = (int)root["Cmd"];
    if (cmd == MESSAGE_GETSWITCHSTATE_REQUEST)
    {
        jsonBuffer.clear();
        JsonObject& Root = jsonBuffer.createObject();
        Root["Cmd"] = MESSAGE_GETSWITCHSTATE_RESPONSE;
        Root["Status"] = STATUS_OK;
        Root["SwitchState"] = WIFISocketSwitch.getState();
        memset(packet, 0, PACKET_MAXSIZE);
        size_t length = Root.printTo((char *)packet, PACKET_MAXSIZE);
        mUdp.beginPacket(mUdp.remoteIP(), mUdp.remotePort());
        mUdp.write(packet, length);
        mUdp.endPacket();
    }
    else if (cmd == MESSAGE_SETSWITCHSTATE_REQUEST)
    {
        boolean Successed = false;
        if (root.containsKey("SwitchState"))
        {
            boolean SwitchState = (boolean)root["SwitchState"];
            WIFISocketSwitch.Switch(SwitchState);
            Successed = true;
        }
        jsonBuffer.clear();
        JsonObject& Root = jsonBuffer.createObject();
        Root["Cmd"] = MESSAGE_SETSWITCHSTATE_RESPONSE;
        Root["Status"] = Successed ? STATUS_OK : STATUS_ERR;
        memset(packet, 0, PACKET_MAXSIZE);
        size_t length = Root.printTo((char *)packet, PACKET_MAXSIZE);
        mUdp.beginPacket(mUdp.remoteIP(), mUdp.remotePort());
        mUdp.write(packet, length);
        mUdp.endPacket();
    }
}

APP端源码

1)获取手机连接WIFI的状态以及本机的IP地址;通过本机IP推断广播IP。
如本机IP为192.168.1.120,则广播地址为192.168.1.255
2)采用DatagramSocket类发送&接收数据包;为了不阻塞UI线程,创建了UDP收发的独立线程。
3)采用Message&Handler的方式,进行UI线程通信;
核心源码如下:

package site.webhome.wifisocketliteapp;
public class StatusCode {
    public static final int MESSAGE_ARRIVE = 10000;
    public static final int MESSAGE_TIMEOUT = 10001;
    public static final int STATUS_OK = 200;
    public static final int STATUS_ERR = 304;
    public static final int MESSAGE_GETSWITCHSTATE_REQUEST = 1000;
    public static final int MESSAGE_GETSWITCHSTATE_RESPONSE = 1001;
    public static final int MESSAGE_SETSWITCHSTATE_REQUEST = 1002;
    public static final int MESSAGE_SETSWITCHSTATE_RESPONSE = 1003;
}
package site.webhome.wifisocketliteapp;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
public class WiFiUtility {
    public static String getIPAddress(Context context) {
        WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        //检查Wifi状态
        if (!wm.isWifiEnabled()) {
            return null;
        }
        WifiInfo wi = wm.getConnectionInfo();
        //获取32位整型IP地址
        int Ip = wi.getIpAddress();
        //把整型地址转换成“*.*.*.*”地址
        return IpToString(Ip);
    }
    public static String getBroadCastIPAddress(Context context) {
        WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        if (!wm.isWifiEnabled()) {
            return null;
        }
        WifiInfo wi = wm.getConnectionInfo();
        int Ip = wi.getIpAddress();
        Ip = Ip | (0xFF << 24);
        return IpToString(Ip);
    }
    private static String IpToString(int Ip) {
        return (Ip & 0xFF) + "." +
                ((Ip >> 8) & 0xFF) + "." +
                ((Ip >> 16) & 0xFF) + "." +
                (Ip >> 24 & 0xFF);
    }
}
package site.webhome.wifisocketliteapp;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.security.MessageDigest;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
public class UdpConnection extends Thread {
    private final static String mTAG = UdpConnection.class.getSimpleName();
    private Handler mHandler = null;
    private String mDestIP = null;
    private int mDestPort = 10000;
    private DatagramSocket mSocket = null;
    private Semaphore mSemaphore = null;
    private BlockingQueue<String> mQueue = null;
    public UdpConnection(Handler handler, String destIP, int destPort) throws SocketException {
        mHandler = handler;
        mDestIP = destIP;
        mDestPort = destPort;
        mQueue = new LinkedBlockingQueue<String>();
        mSemaphore = new Semaphore(0);
        mSocket = new DatagramSocket();
        mSocket.setSoTimeout(3000);
        this.start();
    }
    public void run() {
        while (true) {
            try {
                mSemaphore.acquire();
                if (!mQueue.isEmpty()) {
                    String sendMessage = mQueue.poll();
                    udpSend(sendMessage);
                    String receiveMessage = udpReceive();
                    if (mHandler != null) {
                        Message responseMessage = new Message();
                        responseMessage.what = StatusCode.MESSAGE_ARRIVE;
                        Bundle data = new Bundle();
                        data.putString("MESSAGE", receiveMessage);
                        responseMessage.setData(data);
                        mHandler.sendMessage(responseMessage);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                Message responseMessage = new Message();
                responseMessage.what = StatusCode.MESSAGE_TIMEOUT;
                mHandler.sendMessage(responseMessage);
            }
        }
    }
    private void udpSend(String Message) throws IOException {
        InetAddress IPAddress = InetAddress.getByName(mDestIP);
        byte[] sendBuffer = Message.getBytes();
        DatagramPacket packetSend = new DatagramPacket(sendBuffer, sendBuffer.length, IPAddress, mDestPort);
        mSocket.send(packetSend);
    }
    private String udpReceive() throws IOException {
        final int PACKET_MAXSIZE = 128;
        DatagramPacket packetReceive = new DatagramPacket(new byte[PACKET_MAXSIZE], PACKET_MAXSIZE);
        mSocket.receive(packetReceive);
        String Message = new String(packetReceive.getData(), 0, packetReceive.getLength());
        return Message;
    }
    public void send(String Message) throws InterruptedException {
        mQueue.put(Message);
        mSemaphore.release();
    }
}
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.JsonReader;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
public class MainActivity extends AppCompatActivity {
    private Button switchButton = null;
    private boolean switchState = false;
    private boolean setSwitchState = false;
    private boolean switchStateInited = false;
    private boolean requesting = false;
    private UdpConnection udpConnection = null;
    public Handler MainHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == StatusCode.MESSAGE_ARRIVE) {
                try {
                    String MessageText = msg.getData().getString("MESSAGE");
                    JSONObject Json = new JSONObject(MessageText);
                    int Status = Json.getInt("Status");
                    if (Status == StatusCode.STATUS_ERR) {
                        Toast.makeText(getApplicationContext(), "参数错误!", Toast.LENGTH_SHORT).show();
                    } else {
                        int Cmd = Json.getInt("Cmd");
                        if (Cmd == StatusCode.MESSAGE_GETSWITCHSTATE_RESPONSE) {
                            switchStateInited = true;
                            switchState = Json.getBoolean("SwitchState");
                            ChangeBackground(switchState);
                        }
                        if (Cmd == StatusCode.MESSAGE_SETSWITCHSTATE_RESPONSE) {
                            switchState = setSwitchState;
                            ChangeBackground(switchState);
                            Toast.makeText(getApplicationContext(), "操作成功!", Toast.LENGTH_SHORT).show();
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
            if (msg.what == StatusCode.MESSAGE_TIMEOUT) {
                Toast.makeText(getApplicationContext(), "网络超时!", Toast.LENGTH_SHORT).show();
            }
            requesting = false;
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        switchButton = (Button) findViewById(R.id.switch_button_device);
        switchButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SwitchButtonOnClick();
            }
        });
        ChangeBackground(switchState);
        InitStatus();
    }
    private boolean CheckInWifiState() {
        return (WiFiUtility.getIPAddress(this) != null);
    }
    private void InitStatus() {
        if (!CheckInWifiState()) {
            Toast.makeText(getApplicationContext(), "请先连接WIFI!", Toast.LENGTH_SHORT).show();
        } else {
            try {
                if (udpConnection == null) {
                    udpConnection = new UdpConnection(MainHandler, WiFiUtility.getBroadCastIPAddress(this), 10000);
                }
                SendGetSwitchState();
            } catch (Exception e) {
                Toast.makeText(getApplicationContext(), "网络超时!", Toast.LENGTH_SHORT).show();
            }
        }
    }
    private void SwitchButtonOnClick() {
        if (requesting) {
            Toast.makeText(getApplicationContext(), "操作未完成!", Toast.LENGTH_SHORT).show();
            return;
        }
        if (!CheckInWifiState()) {
            Toast.makeText(getApplicationContext(), "请先连接WIFI!", Toast.LENGTH_SHORT).show();
            switchStateInited = false;
            if (udpConnection != null) {
                if (udpConnection.isAlive()) {
                    udpConnection.interrupt();
                }
                udpConnection = null;
            }
            return;
        }
        if (!switchStateInited) {
            Toast.makeText(getApplicationContext(), "正在初始化!", Toast.LENGTH_SHORT).show();
            InitStatus();
            return;
        }
        try {
            setSwitchState = !switchState;
            SendSetSwitchState(setSwitchState);
        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), "网络超时!", Toast.LENGTH_SHORT).show();
        }
    }
    private void SendGetSwitchState() throws JSONException, InterruptedException {
        JSONObject json = new JSONObject();
        json.put("Cmd", StatusCode.MESSAGE_GETSWITCHSTATE_REQUEST);
        udpConnection.send(json.toString());
    }
    private void SendSetSwitchState(boolean state) throws JSONException, InterruptedException {
        JSONObject json = new JSONObject();
        json.put("Cmd", StatusCode.MESSAGE_SETSWITCHSTATE_REQUEST);
        json.put("SwitchState", state);
        udpConnection.send(json.toString());
    }
    private void ChangeBackground(boolean switchState) {
        int statusBarRes = R.color.colorBluePrimaryDark;
        int backgroundRes = R.drawable.switch_on_bg;
        if (!switchState) {
            backgroundRes = R.drawable.switch_off_bg;
            statusBarRes = R.color.colorBlackPrimaryDark;
        }
        RelativeLayout relativeLayout = (RelativeLayout) findViewById(R.id.layout_main_device);
        relativeLayout.setBackgroundResource(backgroundRes);
        Window window = this.getWindow();
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        window.setStatusBarColor(this.getResources().getColor(statusBarRes));
    }
}

猜你喜欢

转载自blog.csdn.net/zimengyu2020/article/details/79246517