1.前言

    这一篇,博主将教大家怎么去实现一个WiFi RGB彩灯。
    先来一个博主已经实现功能的图片,如下:

    当然,博主也拍了运行视频,请点击 传输门

1.1 知识储备

    本篇需要用到以下知识点:

  • 运用到ArduinoJson库,github传送门,请读者自行下载该库放到Arduino安装目录(这里直接使用,博主后面计划会详细讲解该库,敬请期待);
  • 运用到TCP Server服务,请参考 ESP8266开发之旅 网络篇⑦ TCP Server & TCP Client
  • 运用到STA模式,请参考 ESP8266开发之旅 网络篇④ Station——ESP8266WiFiSTA库的使用
  • 运用到一键配网功能,请参考 ESP8266开发之旅 网络篇⑧ SmartConfig——一键配网

1.2 设计原理

    这里的局域网,博主理解为手机、ESP8266均连接同一个路由wifi,然后8266是作为服务端,手机作为客户端,手机再通过wifi给8266发送数据,8266接收到数据再把数据分发给Arduino,然后Arduino解析协议数据以达到控制效果。
    设计图如下:

2.实验准备

  • ESP202 8266小黄板或者NodeMcu开发板或者其他8266模块;
  • Android 手机 + 博主App;
  • mega2560开发板(博主偷懒想用两个硬件串口,读者也可以用UNO);
  • RGB LED模块以及若干线;

3.实验步骤

    我们本应用需要分成两个部分的设计——Mega2560 Arduino端以及ESP8266端。需要分别往两块板子烧录代码以及连接电路。

3.1 8266端代码

    往8266烧入以下代码:

/**
* 日期:2019/02/18
* 功能:wifi lamp 8266端
*       加入SmartConfig功能
* 作者:单片机菜鸟
**/
#include <ESP8266WiFi.h>

#define MAX_SRV_CLIENTS 3   //最大同时联接数,即你想要接入的设备数量,8266tcpserver只能接入五个,哎
#define LED 2
#define DEBUG //是否开启debug功能

#ifdef DEBUG
#define DebugPrintln(message)    Serial.println(message)
#else
#define DebugPrintln(message)
#endif

#ifdef DEBUG
#define DebugPrint(message)    Serial.print(message)
#else
#define DebugPrint(message)
#endif

const unsigned long BAUD_RATE = 115200;// serial connection speed

WiFiServer server(8266);//你要的端口号,随意修改,范围0-65535
WiFiClient serverClients[MAX_SRV_CLIENTS];
int flag = HIGH;//默认当前灭灯

/**
* @Desc 初始化操作
*/
void setup() {
  Serial.begin(BAUD_RATE);
  pinMode(LED,OUTPUT);
  digitalWrite(LED, HIGH);

  if(!autoConfig()){
    smartConfig();
    DebugPrint("Connecting to WiFi");//写几句提示,哈哈
    while (WiFi.status() != WL_CONNECTED) {
    //这个函数是wifi连接状态,返回wifi链接状态
       delay(500);
       DebugPrint(".");
    }
  }

  delay(1000);
  digitalWrite(LED, LOW);
  DebugPrintln("IP address: ");
  DebugPrintln(WiFi.localIP());//WiFi.localIP()返回8266获得的ip地址
  server.begin();
  server.setNoDelay(true);  //加上后才正常些
  //使能软件看门狗的触发间隔
  ESP.wdtEnable(5000);
}

/**
* @Desc  主函数
*/
void loop() {
  uint8_t index;
  if (server.hasClient()){
        for (index = 0; index < MAX_SRV_CLIENTS; index++){
            if (!serverClients[index] || !serverClients[index].connected()){
                if (serverClients[index]) serverClients[index].stop();//未联接,就释放
                serverClients[index] = server.available();//分配新的
                continue;
            }
        }
        //8266tcpserver只能接入五个  超出的需要释放
        WiFiClient serverClient = server.available();
        if (serverClient){
          serverClient.stop();
        }
  }

  for (index = 0; index < MAX_SRV_CLIENTS; index++){
        if (serverClients[index] && serverClients[index].connected()){
            //处理客户端发过来的数据
            if (serverClients[index].available()){
                while (serverClients[index].available())
                    //把数据发送给mega
                    Serial.write(serverClients[index].read());
            }
        }
   }

   if(Serial.available()>0){
      char ch = Serial.read();
      //收到ardunio发过来的进入smartconfig模式的命令
      if(ch == '1'){
        smartConfig();
        delay(1000);
        digitalWrite(LED, LOW);
        DebugPrintln("IP address: ");
        DebugPrintln(WiFi.localIP());//WiFi.localIP()返回8266获得的ip地址
      }
   }

   //喂狗
   ESP.wdtFeed();
}

/**
* 自动连接20s 超过之后自动进入SmartConfig模式
*/
bool autoConfig(){
  WiFi.mode(WIFI_AP_STA);     //设置esp8266 工作模式
  WiFi.begin();
  delay(2000);//刚启动模块的话 延时稳定一下
  DebugPrintln("AutoConfiging ......");
  for(int index=0;index<20;index++){
    int wstatus = WiFi.status();
    if (wstatus == WL_CONNECTED){
      DebugPrintln("AutoConfig Success");
      DebugPrint("SSID:");
      DebugPrintln(WiFi.SSID().c_str());
      DebugPrint("PSW:");
      DebugPrintln(WiFi.psk().c_str());
      return true;
    }else{
      DebugPrint(".");
      delay(1000);
      flag = !flag;
      digitalWrite(LED, flag);
    }
  }
  DebugPrintln("AutoConfig Faild!");
  return false;
}

/**
* 开启SmartConfig功能
*/
void smartConfig()
{
  WiFi.mode(WIFI_STA);
  delay(2000);
  DebugPrintln("Wait for Smartconfig");
  // 等待配网
  WiFi.beginSmartConfig();
  while (1){
    DebugPrint(".");
    delay(500);
    flag = !flag;
    digitalWrite(LED, flag);

    if (WiFi.smartConfigDone()){
      //smartconfig配置完毕
      DebugPrintln("SmartConfig Success");
      DebugPrint("SSID:");
      DebugPrintln(WiFi.SSID().c_str());
      DebugPrint("PSW:");
      DebugPrintln(WiFi.psk().c_str());
      WiFi.mode(WIFI_AP_STA);     //设置esp8266 工作模式
      WiFi.setAutoConnect(true);  // 设置自动连接
      break;
    }
  }
}

    整个代码的流程是这样的:

  1. 8266上电启动后,进入自动连接模式autoConfig(根据上一次成功连接的SSID和密码),最多尝试20s,在尝试连接的过程中,LED灯会每隔1s闪烁一下,表示正在连接状态;如果连接成功,就直接配置8266服务器模式。
  2. 如果上面操作失败(可能连接的热点不存在了或者修改了密码),那么会自动进入一键配置模式SmartConfig等待手机一键配置,这个过程是不限制时间的,LED会每隔0.5s闪一下,表示处在SmartConfig状态,这时大家可以去手机端开始一键配置。

    这里代码功能其实就是把8266当做服务端,8266连接上路由wifi,然后监听连接进来的客户端(这里是手机)

  • app下载地址: wifilamp
    如果链接失效,请找博主要最新的apk。
  • app源码下载地址:github

读者安装好app之后,会看到如下的手机配置页面,请一步步设置:

    如果提示失败,一般都是因为你的8266模块并没有进入到SmartConfig模式,可以尝试重启一下。再者就是,smartconfig不保证配网成功率100%;

3.2 mega端代码

    烧写以下代码到mega2560板子,代码如下:

/**
* 日期:2019/02/18
* 功能:wifi lamp arduino端
* 作者:单片机菜鸟
**/
#include <SoftwareSerial.h>
#include <ArduinoJson.h>

const unsigned long BAUD_RATE = 115200;                   // serial connection speed
const size_t MAX_CONTENT_SIZE = 50;
const size_t t_bright=1,t_color=2,t_frequency=3,t_switch=4;

//#define UNO      //uncomment this line when you use it with UNO board
#define MEGA    //uncomment this line when you use it with MEGA board

#ifdef UNO
 SoftwareSerial mySerial(10,11);
#endif

#ifdef UNO
#define WifiSerial  Serial
#define MyDebugSerial mySerial
#endif

#ifdef MEGA
#define WifiSerial Serial1
#define MyDebugSerial Serial
#endif  

//该条语句用于使能DEBUG输出信息,屏蔽掉就不会输出debug调试信息
#define DEBUG
//该条语句用于使能是共阴RGB  屏蔽掉就是共阳RGB
//#define COMMON_GND

#ifdef DEBUG
#define DBGLN(message)    MyDebugSerial.println(message)
#else
#define DBGLN(message)
#endif

#ifdef UNO
#define PIN_RED 3 //red 引脚
#define PIN_GREEN 5 //green 引脚
#define PIN_BLUE 6 //blue 引脚
#define PIN_ENABLE 9  //使能引脚 pwm控制亮度
#define PIN_KEY 7// 按键
#else
#define PIN_RED 2
#define PIN_GREEN 3
#define PIN_BLUE 4
#define PIN_ENABLE 5
#define PIN_KEY 6
#endif 

int red = 0,green = 0,blue = 0;
int type = 4;//当前模式 1亮度 2颜色 3呼吸 4开关
int frequency = 1;//频率
int switch_status = 1;//关闭 or 开启
int bright = 1;//亮度

char response[MAX_CONTENT_SIZE];
int fadeValue = 0;//当前亮度
bool isAdd = true;//是否是从暗到亮

// 定义记录按键当前状态的变量
int state_btn;
// 定义记录按键最近一次状态变化的变量,并初始化状态为LOW。
int lastButtonState = LOW;
// 定义记录最近一次抖动的时间变量,并初始化时间为0毫秒。
long lastDebounceTime = 0;
// 定义延迟抖动的时间变量
long debouncdDelay = 60;

/**
* @Desc 初始化操作
*/
void setup() {
  pinMode(PIN_RED, OUTPUT);
  pinMode(PIN_GREEN, OUTPUT);
  pinMode(PIN_BLUE, OUTPUT);
  pinMode(PIN_ENABLE, OUTPUT);
  pinMode(PIN_KEY,INPUT);

  WifiSerial.begin(BAUD_RATE);
  #ifdef DEBUG
    #ifdef UNO
      MyDebugSerial.begin(9600);//软串口9600稳定
    #else
      MyDebugSerial.begin(BAUD_RATE);
    #endif
  #endif
  DBGLN("Arduino Init End");
}

/**
* @Desc  主函数
*/
void loop() {

  if(WifiSerial.available()>0){
    clrEsp8266ResponseBuffer();
    int data_size = ReceiveMessage(response, sizeof(response));
    if(data_size>0){
      //开始解析数据
      parseData(response);
    }
  }

  if(type == t_frequency){
    //呼吸灯效果
    breatheRGB(frequency);
  }
  checkButton();
}

/**
* 读取串口缓冲区里面的数据
*/
int ReceiveMessage(char* content, size_t maxSize){
  //不用 readBytes 因为比较耗时
  size_t length = WifiSerial.readBytesUntil('}',content, maxSize);
  content[length] = '}';
  content[++length] = 0;
  DBGLN(content);
  return length;
}

/**
     * @Desc 解析json
     * 有三种
     * 1.亮度控制页面(0 暗 1正常 2亮)
     * {
     *     "t": 1,
     *     "bb": 2
     * }
     * 2.颜色控制页面
     * {
     *     "t": 2,
     *     "cr": 154,
     *     "cg": 147,
     *     "cb": 255
     * }
     * 3.呼吸灯控制页面(0 慢呼吸 1正常 2快)
     * {
     *    "t": 3,
     *    "gf": 1
     * }
     * 4.开关控制(0关闭 1开启)
     * {
     *    "t": 4,
     *    "ss": 1
     * }
     **/
bool parseData(char* content) {
//    -- 根据我们需要解析的数据来计算JSON缓冲区最佳大小
//   如果你使用StaticJsonBuffer时才需要
//    const size_t BUFFER_SIZE = 1024;
//   在堆栈上分配一个临时内存池
//    StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;
//    -- 如果堆栈的内存池太大,使用 DynamicJsonBuffer jsonBuffer 代替
  DynamicJsonBuffer jsonBuffer;

  JsonObject& root = jsonBuffer.parseObject(content);

  if (!root.success()) {
    Serial.println("JSON parsing failed!");
    return false;
  }

  type = root["t"];
  switch(type){
    case t_bright:
         bright = root["bb"];
         brightRGB(bright);
         break;
    case t_color:
         red = root["cr"];
         green = root["cg"];
         blue = root["cb"];
         colorRGB(red,green,blue);
         break;
    case t_frequency:
         frequency = root["gf"];
         break;
    case t_switch:
         switch_status = root["ss"];
         bool enable = switch_status == 1;
         switchRGB(enable);
         break;
  }
  return true;
}

/**
* 控制灯亮度
*/
void brightRGB(int bright){
  int level = bright%3;
  int bright_level;
  switch(level){
    case 0://暗  50
      bright_level = 50;
      break;
    case 1://正常 100
      bright_level = 100;
      break;
    case 2://亮  200
      bright_level = 200;
      break;
  }
    #ifdef COMMON_GND
     //共地
     analogWrite(PIN_ENABLE,bright_level);
    #else
     analogWrite(PIN_ENABLE,255-bright_level);
    #endif
}

/**
* 控制RGB颜色
*/
void colorRGB(int red, int green, int blue){
  #ifdef COMMON_GND
     analogWrite(PIN_RED,constrain(red,0,255));
     analogWrite(PIN_GREEN,constrain(green,0,255));
     analogWrite(PIN_BLUE,constrain(blue,0,255));
  #else
     analogWrite(PIN_RED,constrain(255-red,0,255));
     analogWrite(PIN_GREEN,constrain(255-green,0,255));
     analogWrite(PIN_BLUE,constrain(255-blue,0,255));
  #endif
}

/**
* 控制亮灭
*/
void switchRGB(bool enable){
  if(enable){
    //打开
    #ifdef COMMON_GND
     //共地
     analogWrite(PIN_ENABLE,255);
    #else
     analogWrite(PIN_ENABLE,0);
    #endif
  }else{
    //关闭
    #ifdef COMMON_GND
     //共地
     analogWrite(PIN_ENABLE,0);
    #else
     analogWrite(PIN_ENABLE,255);
    #endif
  }
}

/**
* 呼吸灯
*/
void breatheRGB(int frequency){
  int level = frequency%3;
  int f_level;
  switch(level){
    case 0://慢  50
      f_level = 3;
      break;
    case 1://正常 100
      f_level = 10;
      break;
    case 2://快  200
      f_level = 20;
      break;
  }
  if(isAdd){
    //递增方向
     fadeValue +=f_level;
     if(fadeValue>=255){
       fadeValue = 255;
       isAdd =false;
     }
  }else{
    //递减方向
    fadeValue -=f_level;
     if(fadeValue<=0){
       fadeValue = 0;
       isAdd =true;
     }
  }
  analogWrite(PIN_ENABLE,fadeValue);
  delay(20);
}

/**
* 检查按键功能
*/
void checkButton(){
  int buttonState = digitalRead(PIN_KEY);//读取当前按键状态
  if(buttonState != lastButtonState){
     //如果按键发生了变化  则重新设置最近一次抖动的时间
     //方法millis()可以获取当前时间,单位统一为毫秒。
     lastDebounceTime = millis();
  }

  // 判断按键按下状态时间间隔是否大于延迟抖动的时间长度。
  if(millis()-lastDebounceTime>debouncdDelay){
    // 判断当前的按键状态是否和之前有所变化
    if(buttonState != state_btn){
       // 如果发生了变化,
       // 则更新按键状态变量。
       state_btn = buttonState;
       if(state_btn == HIGH){
        //再次确认是否真的按下了按键
         DBGLN("smartconfig");
         WifiSerial.write('1');
       }
    }
  }
  // 更新按键最近一次状态变化的变量
  lastButtonState = buttonState;
}

void clrEsp8266ResponseBuffer(void){
    memset(response, 0, MAX_CONTENT_SIZE);      //清空
}

代码解释:
    这里我们用到了一个按键,按下按键就给8266发个命令进入SmartConfig模式(Arduino和8266通过串口1通信)。

加上读者已经配置成功Smartconfig了,那么我们连接好电路之后就可以进行控制操作了。请看app控制页面:

    接下来,如果有使能Debug的话,应该会打印以下信息:

注意

  • 最后下载到板子的时候,最好把调试信息去掉,会影响到解析速度。注释掉 #define DEBUG
  • 在上面的app源码中,博主已经将smartconfig的代码抽取成一个android module,懂android开发的同学可以自行取来用。

4.实验总结

博主简单介绍如何基于之前讲的基础知识来做一个简单项目,希望大家巩固认识。

ESP8266开发之旅 应用篇① 局域网应用 ——炫酷RGB彩灯的更多相关文章

  1. ESP8266开发之旅 网络篇⑯ 无线更新——OTA固件更新

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  2. ESP8266开发之旅 进阶篇② 闲聊Arduino IDE For ESP8266烧录配置

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  3. ESP8266开发之旅 基础篇① 走进ESP8266的世界

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  4. ESP8266开发之旅 基础篇② 如何安装ESP8266的Arduino开发环境

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  5. ESP8266开发之旅 基础篇③ ESP8266与Arduino的开发说明

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  6. ESP8266开发之旅 基础篇④ ESP8266与EEPROM

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  7. ESP8266开发之旅 基础篇⑥ Ticker——ESP8266定时库

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  8. ESP8266开发之旅 网络篇⑦ TCP Server & TCP Client

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  9. ESP8266开发之旅 网络篇⑧ SmartConfig——一键配网

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

随机推荐

  1. .netCore+Vue 搭建的简捷开发框架 (3)-- Services层实现

    继续交作业: 上一篇作业中我们实现了 Repository仓储层的应用.并为我们的框架引入了EFCore 详见: .netCore+Vue 搭建的简捷开发框架 (2)--仓储层实现和EFCore 的使 ...

  2. 网站启动,报编译错误:类型“ASP.global_asax”同时存在两个文件夹的问题

    CS0433: The type 'ASP.global_asax' exists in both 'c:\Windows\Microsoft.NET\Framework64\v4.0.30319\T ...

  3. 了解Java线程优先级,更要知道对应操作系统的优先级,不然会踩坑

    Java 多线程系列第 6 篇. 这篇我们来看看 Java 线程的优先级. Java 线程优先级 Thread 类中,使用如下属性来代表优先级. private int priority; 我们可以通 ...

  4. CF #579 (Div. 3) D1.Remove the Substring (easy version)

    D1.Remove the Substring (easy version) time limit per test2 seconds memory limit per test256 megabyt ...

  5. Disruptor—核心概念及体验

    本文基于最新的3.4.2的版本文档进行翻译,翻译自: https://github.com/LMAX-Exchange/disruptor/wiki/Introduction https://gith ...

  6. JS多线程WebWorker

    JS多线程WebWorker 一,介绍与需求 1.1,介绍 Web Worker可以为JavaScript创建多线程,且Web Worker 是运行在后台的 JavaScript,独立于其他脚本,不会 ...

  7. 虚拟现实中的Motion Sickness晕动症问题 - VIMS

    虚拟现实(VR)中的晕动症 - VIMS 在玩VR的时候,很多玩家都遇到过发晕恶心等症状,这就是晕动症(Motion Sickness,以下或简称MS).MS并不是VR特有的问题.我们在坐船.坐车.坐 ...

  8. php一行代码获取本周一,本周日,上周一,上周日,本月一日,本月最后一日,上月一日,上月最后一日日期

    <?php //本周一 echo date('Y-m-d', (time() - ((date('w') == 0 ? 7 : date('w')) - 1) * 24 * 3600)); // ...

  9. $(document).height 与$(window).height的区别

    $(document).scrollTop() 获取垂直滚动的距离 (即当前滚动的地方的窗口顶端到整个页面顶端的距离)$(document).scrollLeft() 这是获取水平滚动条的距离 要获取 ...

  10. 教你制作挂件头像 | 小程序七十二变之 canvas 绘制国旗头像

    昨天朋友圈被「请给我一面国旗@微信官方」刷屏,虽然知道是假的,但是从另一个角度来看,弄清楚如何实现更有趣. 1.canvas 这就不得不提到小程序中的 API canvas,H5 中也是有 canva ...