微信小程序控制物联网LED灯:开关、闪烁、呼吸、流水功能实现与WiFi通信http协议详解

MENU

  • ESP32(服务端)
  • C++
  • 解析
  • 微信小程序(移动端)
  • Html
  • JavaScript

  • ESP32(服务端)

    C++

    #include <WiFi.h>
    #include <WebServer.h>
    #include <ArduinoJson.h>
    
    const char* ssid = "jifu";
    const char* pass = "2022xinchan!@#";
    const int ledPin = 5;
    bool isTwinkle = false;
    bool isFlowing = false;
    int pin_list[5] = { 13, 23, 14, 27, 4 };
    int size = sizeof(pin_list) / sizeof(pin_list[0]);
    bool isBreathing = false;
    
    WebServer server(8068);
    
    // 通用响应函数
    void sendResponse(int code, const char* status, const char* message, const String& response) {
      StaticJsonDocument<200> doc;
      doc["code"] = code;
      doc["status"] = status;
      doc["message"] = message;
      doc["response"] = response;
      String res;
      serializeJson(doc, res);
      server.send(code, "application/json", res);
    }
    
    // LED开关
    void ledSwitch() {
      String isSwitch = server.arg("isSwitch");
      if (isSwitch == "true") {
        digitalWrite(ledPin, HIGH);
        sendResponse(200, "success", "LED已打开", isSwitch);
      } else if (isSwitch == "false") {
        digitalWrite(ledPin, LOW);
        sendResponse(200, "success", "LED已关闭", isSwitch);
      } else {
        sendResponse(400, "error", "无效的开关状态", isSwitch);
      }
    }
    
    // LED闪烁
    void ledTwinkle() {
      String twinkle = server.arg("isTwinkle");
      if (twinkle == "true") {
        isTwinkle = true;
        sendResponse(200, "success", "LED开始闪烁", twinkle);
      } else if (twinkle == "false") {
        isTwinkle = false;
        sendResponse(200, "success", "LED停止闪烁", twinkle);
      } else {
        sendResponse(400, "error", "无效的闪烁状态", twinkle);
      }
    }
    
    // LED呼吸
    void ledBreathing() {
      String breathing = server.arg("isBreathing");
      if (breathing == "true") {
        isBreathing = true;
        sendResponse(200, "success", "LED开始呼吸", breathing);
      } else if (breathing == "false") {
        isBreathing = false;
        // 因为使用同一个引脚
        // 当启动过呼吸灯后
        // 同一引脚的其他功能无法使用
        // 所以在关闭呼吸灯时初始化一下引脚
        pinMode(ledPin, OUTPUT);
        sendResponse(200, "success", "LED停止呼吸", breathing);
      } else {
        sendResponse(400, "error", "无效的呼吸状态", breathing);
      }
    }
    
    // LED流水
    void ledFlowing() {
      String flowing = server.arg("isFlowing");
      if (flowing == "true") {
        isFlowing = true;
        sendResponse(200, "success", "LED开始流水", flowing);
      } else if (flowing == "false") {
        isFlowing = false;
        for (int i = 0; i < size; i++) digitalWrite(pin_list[i], LOW);
        sendResponse(200, "success", "LED停止流水", flowing);
      } else {
        isFlowing = false;
        for (int i = 0; i < size; i++) digitalWrite(pin_list[i], LOW);
        sendResponse(400, "error", "无效的流水状态", flowing);
      }
    }
    
    // 主函数
    void setup() {
      Serial.begin(9600);
      pinMode(ledPin, OUTPUT);
      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid, pass);
    
      while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print("Loading in progress...");
      }
    
      Serial.print("\nWiFiIP: ");
      Serial.println(WiFi.localIP());
    
      server.on("/api/led/switch", HTTP_GET, ledSwitch);
      server.on("/api/led/twinkle", HTTP_GET, ledTwinkle);
      server.on("/api/led/breathing", HTTP_GET, ledBreathing);
      server.on("/api/led/flowing", HTTP_GET, ledFlowing);
    
      for (int i = 0; i < size; i++) pinMode(pin_list[i], OUTPUT);
      server.begin();
    }
    
    // 循环函数
    void loop() {
      server.handleClient();
      if (isTwinkle) {
        digitalWrite(ledPin, HIGH);
        delay(1000);
        digitalWrite(ledPin, LOW);
        delay(1000);
      }
      if (isBreathing) {
        for (int i = 0; i < 256; i++) {
          analogWrite(ledPin, i);
          delay(10);
        }
        for (int i = 255; i >= 0; i--) {
          analogWrite(ledPin, i);
          delay(10);
        }
      }
      if (isFlowing) {
        for (int i = 0; i < size; i++) {
          digitalWrite(pin_list[i], HIGH);
          digitalWrite(pin_list[(i > 0) ? (i - 1) : (size - 1)], LOW);
          delay(250);
        }
      }
    }
    

    解析

    前言
    程序是一个通过WiFi使用Web接口控制ESP32上LED灯的应用。它使用WiFi连接网络,并通过WebServer库处理HTTP请求,从而根据请求执行各种LED操作(开关灯、闪烁灯、呼吸灯和流水灯)。


    包含的库

    #include <WiFi.h>
    #include <WebServer.h>
    #include <ArduinoJson.h>
    

    WiFi.h用于连接ESP32到WiFi网络。
    WebServer.h用于创建一个Web服务器来处理HTTP请求。
    ArduinoJson.h用于构建和解析JSON数据,方便API的响应格式化。


    全局变量

    const char* ssid = "jifu";
    const char* pass = "2022xinchan!@#";
    const int ledPin = 5;
    bool isTwinkle = false;
    bool isFlowing = false;
    int pin_list[5] = { 13, 23, 14, 27, 4 };
    int size = sizeof(pin_list) / sizeof(pin_list[0]);
    bool isBreathing = false;
    
    WebServer server(8068);
    

    ssid和passWiFi的SSID和密码,用于连接WiFi网络。
    ledPin控制LED灯的GPIO引脚,这里设置为5号引脚。
    isTwinkleisFlowingisBreathing分别标识LED是否正在执行闪烁、流水、呼吸灯效果。
    pin_list存储多个GPIO引脚,用于控制流水灯效果。
    server(8068)创建一个Web服务器,监听8086端口。


    通用响应函数

    void sendResponse(int code, const char* status, const char* message, const String& response) {
      StaticJsonDocument<200> doc;
      doc["code"] = code;
      doc["status"] = status;
      doc["message"] = message;
      doc["response"] = response;
      String res;
      serializeJson(doc, res);
      server.send(code, "application/json", res);
    }
    

    sendResponse用于发送HTTP响应,将请求结果打包成JSON格式,包含code、status、message和response字段,返回给客户端。


    LED开关控制

    void ledSwitch() {
      String isSwitch = server.arg("isSwitch");
      if (isSwitch == "true") {
        digitalWrite(ledPin, HIGH);
        sendResponse(200, "success", "LED已打开", isSwitch);
      } else if (isSwitch == "false") {
        digitalWrite(ledPin, LOW);
        sendResponse(200, "success", "LED已关闭", isSwitch);
      } else {
        sendResponse(400, "error", "无效的开关状态", isSwitch);
      }
    }
    

    根据isSwitch参数来控制LED的开关状态,true表示打开,false表示关闭。


    LED闪烁控制

    void ledTwinkle() {
      String twinkle = server.arg("isTwinkle");
      if (twinkle == "true") {
        isTwinkle = true;
        sendResponse(200, "success", "LED开始闪烁", twinkle);
      } else if (twinkle == "false") {
        isTwinkle = false;
        sendResponse(200, "success", "LED停止闪烁", twinkle);
      } else {
        sendResponse(400, "error", "无效的闪烁状态", twinkle);
      }
    }
    

    通过控制isTwinkle变量,来启动或停止LED的闪烁效果。


    LED呼吸灯控制

    void ledBreathing() {
      String breathing = server.arg("isBreathing");
      if (breathing == "true") {
        isBreathing = true;
        sendResponse(200, "success", "LED开始呼吸", breathing);
      } else if (breathing == "false") {
        isBreathing = false;
        pinMode(ledPin, OUTPUT);
        sendResponse(200, "success", "LED停止呼吸", breathing);
      } else {
        sendResponse(400, "error", "无效的呼吸状态", breathing);
      }
    }
    

    根据isBreathing参数控制LED的呼吸效果,呼吸灯效果通过循环改变LED亮度实现。


    LED流水灯控制

    void ledFlowing() {
      String flowing = server.arg("isFlowing");
      if (flowing == "true") {
        isFlowing = true;
        sendResponse(200, "success", "LED开始流水", flowing);
      } else if (flowing == "false") {
        isFlowing = false;
        for (int i = 0; i < size; i++) digitalWrite(pin_list[i], LOW);
        sendResponse(200, "success", "LED停止流水", flowing);
      } else {
        sendResponse(400, "error", "无效的流水状态", flowing);
      }
    }
    

    控制多个引脚上的LED依次亮灭,模拟流水灯效果。


    主函数setup()

    void setup() {
      Serial.begin(9600);
      pinMode(ledPin, OUTPUT);
      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid, pass);
    
      while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print("Loading in progress...");
      }
    
      Serial.print("\nWiFiIP: ");
      Serial.println(WiFi.localIP());
    
      server.on("/api/led/switch", HTTP_GET, ledSwitch);
      server.on("/api/led/twinkle", HTTP_GET, ledTwinkle);
      server.on("/api/led/breathing", HTTP_GET, ledBreathing);
      server.on("/api/led/flowing", HTTP_GET, ledFlowing);
    
      for (int i = 0; i < size; i++) pinMode(pin_list[i], OUTPUT);
      server.begin();
    }
    

    设置WiFi连接、初始化LED引脚和Web服务器,监听不同的API路径并关联对应的处理函数。


    主循环loop()

    void loop() {
      server.handleClient();
      if (isTwinkle) {
        digitalWrite(ledPin, HIGH);
        delay(1000);
        digitalWrite(ledPin, LOW);
        delay(1000);
      }
      if (isBreathing) {
        for (int i = 0; i < 256; i++) {
          analogWrite(ledPin, i);
          delay(10);
        }
        for (int i = 255; i >= 0; i--) {
          analogWrite(ledPin, i);
          delay(10);
        }
      }
      if (isFlowing) {
        for (int i = 0; i < size; i++) {
          digitalWrite(pin_list[i], HIGH);
          digitalWrite(pin_list[(i > 0) ? (i - 1) : (size - 1)], LOW);
          delay(250);
        }
      }
    }
    

    循环执行LED的控制逻辑,处理客户端的请求,并根据当前的状态变量决定是否执行闪烁、呼吸或流水效果。


    总结
    程序通过简单的HTTP接口,通过Web页面实现控制ESP32的LED开关灯、闪烁灯、呼吸灯和流水灯功能。


    微信小程序(移动端)

    Html

    代码

    <view>
      <view class="p_18 bs_bb bs_0_8_8_efefef radius_6 fs_28">
        <view class="fw_b">名称: {{info.title}}</view>
        <view class="color_909399">别名: {{info.aliasListStr}}</view>
      </view>
      <view class="mt_58 d_g gtc_2fr gg_38">
        <view class="w_100_ pt_28 pb_28 bs_bb ta_c bc_efefef radius_8 {{activa===item.id?'color_409eff':''}}" wx:for="{{apiList}}" wx:key="id" data-id="{{item.id}}" catchtap="handleLedAction">
          LED{{item.title}}灯已{{ledSwitch?'开启':'关闭'}}
        </view>
      </view>
    </view>
    

    解析
    前言
    代码段用于微信小程序的WXML模板,构建一个视图,用于显示LED灯的控制面板。包含一些动态数据绑定、条件渲染和事件处理。


    外层<view>标签
    最外层的<view>标签包含整个视图的结构布局,是微信小程序中用于显示布局的基础容器。它内嵌多个<view>子元素,每个子元素分别对应不同的功能展示。


    第一个<view>块显示信息

    <view class="p_18 bs_bb bs_0_8_8_efefef radius_6 fs_28">
      <view class="fw_b">名称: {{info.title}}</view>
      <view class="color_909399">别名: {{info.aliasListStr}}</view>
    </view>
    

    1、class="p_18 bs_bb bs_0_8_8_efefef radius_6 fs_28"用于<view>多个CSS样式类,控制外边距、边框样式、圆角半径和字体大小。
    1.1、p_18表示内边距为18单位。
    1.2、bs_bb表示边框样式为双线框。
    1.3、radius_6表示圆角半径为6单位。
    1.4、fs_28表示字体大小为28单位。
    2、{{info.title}}通过数据绑定显示info.title,这是一个动态数据,用于显示某个对象的名称。
    3、{{info.aliasListStr}}通过数据绑定显示info.aliasListStr,显示对象的别名。


    第二个<view>块控制LED面板

    <view class="mt_58 d_g gtc_2fr gg_38">
      <view class="w_100_ pt_28 pb_28 bs_bb ta_c bc_efefef radius_8 {{activa===item.id?'color_409eff':''}}" wx:for="{{apiList}}" wx:key="id" data-id="{{item.id}}" catchtap="handleLedAction">
        LED{{item.title}}灯已{{ledSwitch?'开启':'关闭'}}
      </view>
    </view>
    

    1、外层<view>样式,
    1.1、mt_58设置顶部外边距为58单位。
    1.2、d_g设置为网格布局(display: grid)。
    1.3、gtc_2fr设置网格布局的列定义为2个fr单位。
    1.4、gg_38网格单元格之间的间距为38单位。
    2、内层<view>,每个内层<view>是一个LED控制按钮。
    2.1、w_100_宽度设置为100%。
    2.2、pt_28 pb_28上下内边距为28单位。
    2.3、bs_bb边框样式为双线框。
    2.4、ta_c文本居中。
    2.5、bc_efefef背景颜色设置为浅灰色(#efefef)。
    2.6、radius_8圆角半径为8单位。
    2.7、{{activa===item.id?'color_409eff':''}}是一个条件样式绑定,检查activa是否等于当前项的id,如果是,则应用color_409eff(蓝色)样式;否则,不应用任何颜色样式。
    3、wx:for="{{apiList}}" wx:key="id"使用wx:for指令进行循环渲染,apiList是一个列表,表示多个LED控制选项,每个选项的id作为唯一键。
    4、data-id="{{item.id}}"为每个<view>元素设置一个自定义数据属性data-id,保存当前LED的id,以便在事件中使用。
    5、catchtap="handleLedAction"绑定点击事件catchtap,当用户点击该LED控制时,会调用handleLedAction函数。
    6、LED{{item.title}}灯已{{ledSwitch?'开启':'关闭'}}动态显示LED状态,{{item.title}}显示当前LED的名称,{{ledSwitch ? ‘开启’ : ‘关闭’}}根据ledSwitch变量的布尔值来显示“开启”或“关闭”状态。


    总结
    代码段是微信小程序中的WXML模板,显示一个LED灯控制面板。每个LED通过一个点击区域显示其当前状态(开启或关闭),并且点击该区域时触发相应的事件来切换LED的状态。


    JavaScript

    前言

    代码段是一个微信小程序的页面逻辑,主要功能是通过API接口控制LED灯的不同操作(开关灯、闪烁灯、呼吸灯和流水灯)。


    代码

    import {
      apiLedSwitch,
      apiLedTwinkle,
      apiBreathing,
      apiLedFlowing
    } from '../../api/led.js';
    
    const {
      showToast
    } = getApp();
    
    Page({
      /**
       * 页面的初始数据
       */
      data: {
        info: {},
        apiObj: {},
        apiList: [],
        activa: ''
      },
    
      async handleLedAction(event) {
        const that = this;
        const thatData = that.data;
        const apiObj = thatData.apiObj;
        const id = event.target.dataset.id;
        let activa = thatData.activa;
        let api = undefined;
        let text = undefined;
    
        if (activa === '') {
          activa = id;
        } else if (id === activa) {
          activa = '';
        } else if (activa !== '' && id !== activa) {
          return showToast('请先关闭其他LED');
        }
        api = apiObj[id];
        text = api.text;
    
        const {
          message
        } = await api.fn({
          [text]: activa ? true : false
        });
    
        that.setData({
            activa
          },
          () => showToast(message)
        );
      },
    
      runInit() {
        let that = this,
          thatData = thatData,
          apiObj = {
            ledSwitch: {
              title: '开关',
              text: 'isSwitch',
              fn: apiLedSwitch
            },
            ledFlicker: {
              title: '闪烁',
              text: 'isTwinkle',
              fn: apiLedTwinkle
            },
            ledBreathing: {
              title: '呼吸',
              text: 'isBreathing',
              fn: apiBreathing
            },
            ledFlowing: {
              title: '流水',
              text: 'isFlowing',
              fn: apiLedFlowing
            }
          },
          apiList = [];
    
        for (const key in apiObj) {
          if (Object.hasOwnProperty.call(apiObj, key)) {
            let title = apiObj[key].title;
    
            apiList.push({
              id: key,
              title
            });
          }
        }
        that.setData({
          apiObj,
          apiList
        });
      },
    
      /**
       * 生命周期函数--监听页面加载
       */
      onLoad(options) {
        this.runInit();
      },
    
      /**
       * 生命周期函数--监听页面显示
       */
      onShow() {
        const row = wx.getStorageSync('electronicComponent');
    
        row.aliasListStr = row.aliasList.slice(1);
        row.aliasListStr = row.aliasListStr.toString();
        row.aliasListStr = row.aliasListStr.replace(/,/g, "、");
        this.setData({
          info: row
        });
      }
    });
    

    导入依赖模块

    import {
      apiLedSwitch,
      apiLedTwinkle,
      apiBreathing,
      apiLedFlowing
    } from '../../api/led.js';
    
    const {
      showToast
    } = getApp();
    

    apiLedSwitchapiLedTwinkleapiBreathingapiLedFlowing从led.js文件中导入控制LED操作的API函数。
    showToast通过getApp()获取全局的showToast方法,用于展示提示信息。


    页面data数据

    data: {
      info: {},
      apiObj: {},
      apiList: [],
      activa: ''
    }
    

    info存储当前电子组件的信息。
    apiObj存储每个LED操作的API函数、文本标识以及对应的标题信息。
    apiList生成的LED操作列表,用于显示在页面上。
    activa标识当前激活的LED操作的ID,用于控制每次只能激活一个LED功能。


    handleLedAction方法

    async handleLedAction(event) {
      const that = this;
      const thatData = that.data;
      const apiObj = thatData.apiObj;
      const id = event.target.dataset.id;
      let activa = thatData.activa;
      let api = undefined;
      let text = undefined;
    
      if (activa === '') {
        activa = id;
      } else if (id === activa) {
        activa = '';
      } else if (activa !== '' && id !== activa) {
        return showToast('请先关闭其他LED');
      }
      api = apiObj[id];
      text = api.text;
    
      const { message } = await api.fn({ [text]: activa ? true : false });
    
      that.setData({
          activa
        },
        () => showToast(message)
      );
    }
    

    event通过事件对象event获取当前点击的LED按钮的id。
    activa当前激活的LED操作。如果没有激活,则将activa设置为点击的id;如果已经激活,再次点击同一个id,则取消激活;如果点击其他LED,则提示先关闭当前的LED。
    apiObj从apiObj中获取当前id对应的API函数和参数text,text用于动态生成请求的参数字段名。
    api.fn调用对应的API函数,传入动态生成的参数,如{ isSwitch: true }{ isSwitch: false },控制LED状态。
    messageAPI响应后,显示API返回的提示信息。
    setData更新activa状态,并在回调中使用showToast显示操作结果的提示。


    runInit方法

    runInit() {
      let that = this,
        thatData = thatData,
        apiObj = {
          ledSwitch: {
            title: '开关',
            text: 'isSwitch',
            fn: apiLedSwitch
          },
          ledFlicker: {
            title: '闪烁',
            text: 'isTwinkle',
            fn: apiLedTwinkle
          },
          ledBreathing: {
            title: '呼吸',
            text: 'isBreathing',
            fn: apiBreathing
          },
          ledFlowing: {
            title: '流水',
            text: 'isFlowing',
            fn: apiLedFlowing
          }
        },
        apiList = [];
    
      for (const key in apiObj) {
        if (Object.hasOwnProperty.call(apiObj, key)) {
          let title = apiObj[key].title;
          apiList.push({
            id: key,
            title
          });
        }
      }
      that.setData({
        apiObj,
        apiList
      });
    }
    

    apiObj是一个配置对象,包含每种LED操作的标题(如"开关"、"闪烁"等)、对应的API参数字段(如isSwitch、isTwinkle等)和API函数(如apiLedSwitch、apiLedTwinkle等)。
    apiList通过遍历apiObj生成LED操作列表,每个操作项包含id和title,用于显示在界面上。
    setData将生成的apiObj和apiList更新到页面的数据中。


    onLoad生命周期函数

    onLoad(options) {
      this.runInit();
    }
    

    页面加载时,调用runInit方法初始化API操作列表。


    onShow生命周期函数

    onShow() {
      const row = wx.getStorageSync('electronicComponent');
      
      row.aliasListStr = row.aliasList.slice(1);
      row.aliasListStr = row.aliasListStr.toString();
      row.aliasListStr = row.aliasListStr.replace(/,/g, "、");
      this.setData({
        info: row
      });
    }
    

    1、从本地存储中读取电子组件的信息(electronicComponent),并处理其aliasList字段。
    1.1、slice(1)移除第一个别名(可能是主名称)。
    1.2、toString()将别名列表转换为字符串。
    1.3、replace(/,/g, "、")将逗号替换为顿号。
    2、将处理后的信息设置到info数据字段,用于页面展示。

    作者:智码帮MJ682517

    物联沃分享整理
    物联沃-IOTWORD物联网 » 微信小程序控制物联网LED灯:开关、闪烁、呼吸、流水功能实现与WiFi通信http协议详解

    发表回复