[智能硬件][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));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章