通过 Wi-Fi 玩火(ESP8266、NeoPixels 和 Android 应用)
组件和用品
| × | 1 | ||||
| × | 3 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
![]() |
| × | 1 |
必要的工具和机器
![]() |
| |||
![]() |
|
应用和在线服务
![]() |
|
关于这个项目
您可以在 上阅读本教程和其他精彩教程 ElectroPeak 的官方网站
用 Wi-Fi 无线控制创造酷炫的火焰模拟效果。一个具有漂亮界面的移动应用程序(适用于 Android 智能手机)已准备好安装,与您的创作一起玩!我们还将使用 Arduino 和 ESP8266 来控制火焰。在本项目结束时,您将了解到:
- NeoPixels 的工作原理。
- 如何通过 Wi-Fi 对 ESP8266 进行编程和控制变量
- 如何使用 NeoPixels 创建炫酷的火焰效果
NeoPixel 简介
可单独寻址的 LED 或通常称为 NeoPixels 已经存在一段时间了,您可能知道它们,但是,如果您不知道,它们就像普通的 RGB LED,但顾名思义,它们中的每一个的颜色都可以单独寻址,允许制作无限酷的图案和动画。对于 WS2812b,您只需要 3 根电线,2 根用于电源,1 根用于数据。这意味着您只需要一个免费的 Arduino 引脚即可控制大量 LED!
在这个项目中,我们将使用这些智能 LED 来创造火焰效果。为了控制 LED,我们将使用很棒的 FastLED 库。我们将使用 Mark Kriegsman 编写的库的 Fire2012 草图示例。我们使用 6 条 LED,每条 LED 有 30 个 LED(总共 180 个 LED),我们将这些 LED 粘在一根 PVC 管上,然后将它们放置在玻璃圆柱体中(这些玻璃圆柱体通常用作花瓶)。我们必须漫射 LED 的光,使它们看起来连续,为此我们使用了描图纸,让光通过并漫射。

施工
首先得到一个合适的玻璃圆柱体,我们的圆柱体长60cm,直径12cm。
如果你能找到磨砂玻璃圆柱体会很好,但如果它是透明玻璃,你可以使用描图纸覆盖圆柱体表面(内表面或外表面),描图纸可以很好地漫射光线并产生良好的效果。
得到一个玻璃圆柱体后,测量其内部长度,然后切割 PVC 管,使其适合圆柱体内部。我们的玻璃圆柱体高度为 60 厘米(不包括底座,内部长度为 59 厘米),因此我们将 PVC 管切割为 59 厘米。你会在这个管子上贴上LED灯条,直径4cm的管子就完美了。
接下来我们必须将我们的 LED 灯条切成 6 个相等的部分,这里我们使用 60LEDs/m 密度的灯条(如果需要,您可以使用更高的密度以获得更好的效果)我们使用六个 50 厘米的长度,这意味着我们需要 3 米。将 PVC 管周围的六个长度等距间隔开,并将条带粘在管子上。它应该是这样的。





将 LED 灯条连接在一起,您可以根据下图直接将电线焊接到灯条上,也可以先将排针焊接到灯条上,然后使用面包板线将它们连接起来。

当所有 LED 灯条连接完成后,您必须将管子放在圆柱体内。要使圆柱体内部的管道居中,您可以使用泡沫切割一个圆,该圆的外径等于玻璃圆柱体的内径,内径等于 PVC 管的外径。为管道的每一侧准备两个。将这些部件连接到末端,然后将管道轻轻放入气缸内。
代码
我们使用 Arduino IDE 进行编码并上传到 ESP8266。如果您想在 SPIFFS 上上传控制器软件文件,您必须使用带有 3MB SPIFFS 的 ESP8266 的开发板。 SPIFFS 是“Serial Peripheral Interface Flash File System”的缩写,您可以将控制器文件上传到此内存,以便从该位置提供文件。通过这样做,您可以打开您的浏览器(在您的手机或笔记本电脑上)并转到您的 ESP 地址(默认为 192.168.4.1),您将在浏览器中获得控制器界面,而无需安装应用程序,如果您拥有 iPhone 或 iPad,这是您唯一的选择。
将以下草图上传到您的 ESP 板上。我们需要 FastLED 库,所以如果您还没有,请先将它添加到您的 Arduino IDE(您可以在此处下载)。火灾模拟代码是 Mark Kriegsman 的 fire2012 草图,您可以在示例中找到。该示例适用于一条 LED,但在这里我们修改了代码以使用可变数量的 LED。灯条/灯珠数量越多,效果越好。
示例文件中清楚地描述了火灾模拟的逻辑。如果你想知道它是如何工作的,请阅读示例的源代码。
#include #include #include "FastLED.h"#include "EEPROM.h"#include "FS.h" //SPIFFS 需要#define DATA_PIN 5 #define LED_TYPE WS2811#define COLOR_ORDER GRB#define COLOR_ORDER GRB#define NUM_LEDS 30#define NUM_STRIPS 6#define CHIPSET WS2812B//保存数据到EEPROM的地址以保持火灾模拟状态#define cs0Adr 0#define cs1Adr 3#define cs2Adr 3Adr#define cs2Adr 6Adr 9#define BriAdr 15#define FpsAdr 16#define SparkingAdr 17#define CoolingAdr 18#define EEPROMCheckAdr 20 //如果这个值为250,我们假设我们之前已经保存到EEPROM并从那个CRGB LEDs[NUM_STRIPS * NUM_LEDS];String inData加载数据;uint8_t FPS =100; //FRAMES_PER_SECONDuint8_t SPARKING =150;uint8_t COOLING =90; uint8_t 亮度 =100;uint8_t csRGB[4][3] ={{0, 0, 0}, {255, 0, 0}, {255, 127, 0}, {255, 255, 255}};unsigned long以前的米利斯 =0;布尔变化 =假; //如果为真我们去保存到EEprom.unsigned long changeMillis =0; //更改将在未应用更改后 1 分钟保存以避免 EEPROM 磨损。bool initSetup =true;CRGBPalette16 gPal;ESP8266WebServer server(80); //网络服务器对象。将侦听端口 80(HTTP 的默认值)void setup(){ EEPROM.begin(200); cWiFi(); setupFastLED();加载配置(); gPal =CRGBPalette16( CRGB(csRGB[0][0],csRGB[0][1],csRGB[0][2]), CRGB(csRGB[1][0],csRGB[1][1],csRGB [1][2]), CRGB(csRGB[2][0],csRGB[2][1],csRGB[2][2]),CRGB(csRGB[3][0],csRGB[3][ 1],csRGB[3][2]));}inline void setupFastLED(){ delay(1000); // 理智延迟 FastLED.addLeds(leds, NUM_STRIPS * NUM_LEDS).setCorrection(TypicalLEDStrip); FastLED.setBrightness(BRIGHTNESS);}void loop(){ server.handleClient(); //处理传入请求 if (change) { if (millis() - changeMillis> 60000) { change =false; saveToEEPROM(); } } 火(); FastLED.show(); FastLED.delay(1000 / FPS);}void Fire2012WithPalette(int stripNo){ static byte heat[NUM_STRIPS][NUM_LEDS]; // 步骤 1. 稍微冷却每个单元格 for( int i =0; i =2; k--) { heat[stripNo][k] =(heat[stripNo] [k - 1] + heat[stripNo][k - 2] + heat[stripNo][k - 2] ) / 3; } // 步骤 3. 在底部附近随机点燃新的“火花” if( random8() =period * 1000) { // 保存上次使 LED 闪烁的时间 previousMillis =currentMillis;返回真; } else { 返回假; }}void EEPROMupdate(byte address, byte value){ if (EEPROM.read(address) !=value) { EEPROM.write(address, value); EEPROM.commit(); } return;}void saveToEEPROM(){ EEPROMupdate(BriAdr, BRIGHTNESS); EEPROM更新(FpsAdr,FPS); EEPROM更新(SparkingAdr,SPARKING); EEPROM更新(CoolingAdr, COOLING); for (uint8_t i =0; i <4; i++) { for (uint8_t j =0; j <3; j++) { EEPROMupdate((i * 3 + j), csRGB[i][j]); } }}void handleCS0Change(){ csRGB[0][0] =str2int(server.arg("R")); csRGB[0][1] =str2int(server.arg("G")); csRGB[0][2] =str2int(server.arg("B")); gPal =CRGBPalette16( CRGB(csRGB[0][0],csRGB[0][1],csRGB[0][2]), CRGB(csRGB[1][0],csRGB[1][1],csRGB [1][2]), CRGB(csRGB[2][0],csRGB[2][1],csRGB[2][2]),CRGB(csRGB[3][0],csRGB[3][ 1],csRGB[3][2])); changeMillis =毫秒(); change =true;}void handleCS1Change(){ csRGB[1][0] =str2int(server.arg("R")); csRGB[1][1] =str2int(server.arg("G")); csRGB[1][2] =str2int(server.arg("B")); gPal =CRGBPalette16( CRGB(csRGB[0][0],csRGB[0][1],csRGB[0][2]), CRGB(csRGB[1][0],csRGB[1][1],csRGB [1][2]), CRGB(csRGB[2][0],csRGB[2][1],csRGB[2][2]),CRGB(csRGB[3][0],csRGB[3][ 1],csRGB[3][2])); changeMillis =毫秒(); change =true;}void handleCS2Change(){ csRGB[2][0] =str2int(server.arg("R")); csRGB[2][1] =str2int(server.arg("G")); csRGB[2][2] =str2int(server.arg("B")); gPal =CRGBPalette16( CRGB(csRGB[0][0],csRGB[0][1],csRGB[0][2]), CRGB(csRGB[1][0],csRGB[1][1],csRGB [1][2]), CRGB(csRGB[2][0],csRGB[2][1],csRGB[2][2]),CRGB(csRGB[3][0],csRGB[3][ 1],csRGB[3][2])); changeMillis =毫秒(); change =true;}void handleCS3Change(){ csRGB[3][0] =str2int(server.arg("R")); csRGB[3][1] =str2int(server.arg("G")); csRGB[3][2] =str2int(server.arg("B")); gPal =CRGBPalette16( CRGB(csRGB[0][0],csRGB[0][1],csRGB[0][2]), CRGB(csRGB[1][0],csRGB[1][1],csRGB [1][2]), CRGB(csRGB[2][0],csRGB[2][1],csRGB[2][2]),CRGB(csRGB[3][0],csRGB[3][ 1],csRGB[3][2])); changeMillis =毫秒(); change =true;}void handleConf(){ if (server.arg("brightness") !="") { BRIGHTNESS =str2int(server.arg("brightness")); FastLED.setBrightness(BRIGHTNESS); changeMillis =毫秒();改变 =真; } if (server.arg("fps") !="") { FPS =str2int(server.arg("fps")); changeMillis =毫秒();改变 =真; } if (server.arg("sparking") !="") { SPARKING =str2int(server.arg("sparking")); changeMillis =毫秒();改变 =真; } if (server.arg("cooling") !="") { COOLING =str2int(server.arg("cooling")); changeMillis =毫秒();改变 =真; } server.sendHeader("连接", "关闭"); server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "text/plain", ""); //返回HTTP响应}void loadConfig(){ if (EEPROM.read(EEPROMCheckAdr) ==250) { BRIGHTNESS =EEPROM.read(BriAdr); SPARKING =EEPROM.read(SparkingAdr);冷却 =EEPROM.read(CoolingAdr); FPS =EEPROM.read(FpsAdr);如果(FPS ==0)FPS =100; for (uint8_t i =0; i <4; i++) { for (uint8_t j =0; j <3; j++) { csRGB[i][j] =EEPROM.read(i * 3 + j); } } }else{ EEPROMupdate(BriAdr,BRIGHTNESS); EEPROM更新(FpsAdr,FPS); EEPROM更新(CoolingAdr,COOLING); EEPROM更新(SparkingAdr,SPARKING); for (uint8_t i =0; i <4; i++) { for (uint8_t j =0; j <3; j++) { EEPROMupdate((i*3+j), csRGB[i][j]); } } EEPROMupdate(EEPROMCheckAdr, 250); }}void cWiFi(){ WiFi.softAP("ElectroPeak's Flame", ""); //如果需要,请在此处设置密码,即 WiFi.softAP("ElectroPeak's Flame", "12345678"); IPAddress myIP =WiFi.softAPIP(); server.on("/cs0", handleCS0Change); server.on("/cs1", handleCS1Change); server.on("/cs2", handleCS2Change); server.on("/cs3", handleCS3Change); server.on("/conf", handleConf); server.serveStatic("/", SPIFFS, "/", "max-age=86400"); server.begin(); //启动服务器}
要控制火的“外观和感觉”,可以使用两个变量:SPARKING 和 COOLING,您可以在上传到 SPIFFS 的控制器软件或您可以下载的 android 应用程序中动态控制它们。您也可以在这里控制 FPS。

火的颜色由调色板控制,该调色板也可通过控制器软件进行更改(通过 4 个色标)。只需单击/点击代表色标的每个色环即可设置颜色,设置颜色命中关闭后关闭对话框并查看更改。

如何上传到 SPIFFS?
要使用 Arduino IDE 首先将文件上传到 SPIFFS 内存,您需要在草图的文件夹中创建一个名为“data”的文件夹,并将所有要上传的文件放在该文件夹中。此处上传的文件包含草图和此文件夹。
接下来,您需要适用于 Arduino 的 Arduino ESP8266 文件系统上传器插件。按照其 Github 页面上的说明安装插件。安装后你会发现ESP8266 Sketch Data Upload 在工具下 菜单。将您的 ESP 置于编程模式并单击它。耐心等待,让文件上传,这可能需要一点时间。注意:将“上传速度”设置为 921600 以使其更快。

它是如何工作的?
上传到 ESP8266 板上的草图在其上创建了一个网络服务器,该服务器响应从应用程序发送的请求。该应用程序只是向服务器 (ESP8266) 发送 GET 请求。用于创建调色板的颜色数据在 get 请求中作为参数发送,其他参数如 Sparking 和 Cooling 参数也是如此。
比如设置亮度,app发送如下请求
http://192.168.4.1/conf?brightness=224
草图中有此请求的处理程序,当收到此请求时,会设置亮度。查看代码以了解更多信息。
安卓应用
Android 应用程序是使用 Phonegap 创建的。它是一种允许您使用网络技术(HTML、CSS、Javascript)创建跨平台移动应用程序的技术。您可以访问此页面获取源代码
您可以在 上阅读本教程和其他精彩教程 ElectroPeak 的官方网站
代码
- 火焰效果代码
- 草图和数据文件夹
火焰效果代码Arduino
#include #include #include "FastLED.h"#include "EEPROM.h"#include "FS.h" //需要用于 SPIFFS#define DATA_PIN 5#define LED_TYPE WS2811#define COLOR_ORDER GRB#define NUM_LEDS 30# define NUM_STRIPS 6#define CHIPSET WS2812B//将数据保存到 EEPROM 的地址以保存模拟火灾的状态#define cs0Adr 0#define cs1Adr 3#define cs2Adr 6#define cs3Adr 9#define BriAdr 15#define FpsAdr 16#define SparkingAdr 17 #define CoolingAdr 18#define EEPROMCheckAdr 20 //如果这个值是250,我们假设我们之前已经保存到EEPROM并从那个CRGB LED加载数据[NUM_STRIPS * NUM_LEDS];String inData;uint8_t FPS =100; //FRAMES_PER_SECONDuint8_t SPARKING =150;uint8_t COOLING =90; uint8_t 亮度 =100;uint8_t csRGB[4][3] ={{0, 0, 0}, {255, 0, 0}, {255, 127, 0}, {255, 255, 255}};unsigned long以前的米利斯 =0;布尔变化 =假; //如果为真我们去保存到EEprom.unsigned long changeMillis =0; //更改将在未应用更改后 1 分钟保存以避免 EEPROM 磨损。bool initSetup =true;CRGBPalette16 gPal;ESP8266WebServer server(80); //网络服务器对象。将侦听端口 80(HTTP 的默认值)void setup(){ EEPROM.begin(200); cWiFi(); setupFastLED();加载配置(); gPal =CRGBPalette16( CRGB(csRGB[0][0],csRGB[0][1],csRGB[0][2]), CRGB(csRGB[1][0],csRGB[1][1],csRGB [1][2]), CRGB(csRGB[2][0],csRGB[2][1],csRGB[2][2]),CRGB(csRGB[3][0],csRGB[3][ 1],csRGB[3][2])); }inline void setupFastLED(){ delay(1000); // 理智延迟 FastLED.addLeds(leds, NUM_STRIPS * NUM_LEDS).setCorrection(TypicalLEDStrip); FastLED.setBrightness(BRIGHTNESS);}void loop(){ server.handleClient(); //处理传入请求 if (change) { if (millis() - changeMillis> 60000) { change =false; saveToEEPROM(); } } 火(); FastLED.show(); FastLED.delay(1000 / FPS);}void Fire2012WithPalette(int stripNo){ static byte heat[NUM_STRIPS][NUM_LEDS]; // 步骤 1. 稍微冷却每个单元格 for( int i =0; i =2; k--) { heat[stripNo][k] =(heat[stripNo] [k - 1] + heat[stripNo][k - 2] + heat[stripNo][k - 2] ) / 3; } // 步骤 3. 在底部附近随机点燃新的“火花” if( random8() =period * 1000) { // 保存上次使 LED 闪烁的时间 previousMillis =currentMillis;返回真; } else { 返回假; }}void EEPROMupdate(byte address, byte value){ if (EEPROM.read(address) !=value) { EEPROM.write(address, value); EEPROM.commit(); } return;}void saveToEEPROM(){ EEPROMupdate(BriAdr, BRIGHTNESS); EEPROM更新(FpsAdr,FPS); EEPROM更新(SparkingAdr,SPARKING); EEPROM更新(CoolingAdr, COOLING); for (uint8_t i =0; i <4; i++) { for (uint8_t j =0; j <3; j++) { EEPROMupdate((i * 3 + j), csRGB[i][j]); } }}void handleCS0Change(){ csRGB[0][0] =str2int(server.arg("R")); csRGB[0][1] =str2int(server.arg("G")); csRGB[0][2] =str2int(server.arg("B")); gPal =CRGBPalette16( CRGB(csRGB[0][0],csRGB[0][1],csRGB[0][2]), CRGB(csRGB[1][0],csRGB[1][1],csRGB [1][2]), CRGB(csRGB[2][0],csRGB[2][1],csRGB[2][2]),CRGB(csRGB[3][0],csRGB[3][ 1],csRGB[3][2])); changeMillis =毫秒(); change =true;}void handleCS1Change(){ csRGB[1][0] =str2int(server.arg("R")); csRGB[1][1] =str2int(server.arg("G")); csRGB[1][2] =str2int(server.arg("B")); gPal =CRGBPalette16( CRGB(csRGB[0][0],csRGB[0][1],csRGB[0][2]), CRGB(csRGB[1][0],csRGB[1][1],csRGB [1][2]), CRGB(csRGB[2][0],csRGB[2][1],csRGB[2][2]),CRGB(csRGB[3][0],csRGB[3][ 1],csRGB[3][2])); changeMillis =毫秒(); change =true;}void handleCS2Change(){ csRGB[2][0] =str2int(server.arg("R")); csRGB[2][1] =str2int(server.arg("G")); csRGB[2][2] =str2int(server.arg("B")); gPal =CRGBPalette16( CRGB(csRGB[0][0],csRGB[0][1],csRGB[0][2]), CRGB(csRGB[1][0],csRGB[1][1],csRGB [1][2]), CRGB(csRGB[2][0],csRGB[2][1],csRGB[2][2]),CRGB(csRGB[3][0],csRGB[3][ 1],csRGB[3][2])); changeMillis =毫秒(); change =true;}void handleCS3Change(){ csRGB[3][0] =str2int(server.arg("R")); csRGB[3][1] =str2int(server.arg("G")); csRGB[3][2] =str2int(server.arg("B")); gPal =CRGBPalette16( CRGB(csRGB[0][0],csRGB[0][1],csRGB[0][2]), CRGB(csRGB[1][0],csRGB[1][1],csRGB [1][2]), CRGB(csRGB[2][0],csRGB[2][1],csRGB[2][2]),CRGB(csRGB[3][0],csRGB[3][ 1],csRGB[3][2])); changeMillis =毫秒(); change =true;}void handleConf(){ if (server.arg("brightness") !="") { BRIGHTNESS =str2int(server.arg("brightness")); FastLED.setBrightness(BRIGHTNESS); changeMillis =毫秒();改变 =真; } if (server.arg("fps") !="") { FPS =str2int(server.arg("fps")); changeMillis =毫秒();改变 =真; } if (server.arg("sparking") !="") { SPARKING =str2int(server.arg("sparking")); changeMillis =毫秒();改变 =真; } if (server.arg("cooling") !="") { COOLING =str2int(server.arg("cooling")); changeMillis =毫秒();改变 =真; } server.sendHeader("连接", "关闭"); server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "text/plain", ""); //返回HTTP响应}void loadConfig(){ if (EEPROM.read(EEPROMCheckAdr) ==250) { BRIGHTNESS =EEPROM.read(BriAdr); SPARKING =EEPROM.read(SparkingAdr);冷却 =EEPROM.read(CoolingAdr); FPS =EEPROM.read(FpsAdr);如果(FPS ==0)FPS =100; for (uint8_t i =0; i <4; i++) { for (uint8_t j =0; j <3; j++) { csRGB[i][j] =EEPROM.read(i * 3 + j); } } }else{ EEPROMupdate(BriAdr,BRIGHTNESS); EEPROM更新(FpsAdr,FPS); EEPROM更新(CoolingAdr,COOLING); EEPROM更新(SparkingAdr,SPARKING); for (uint8_t i =0; i <4; i++) { for (uint8_t j =0; j <3; j++) { EEPROMupdate((i*3+j), csRGB[i][j]); } } EEPROMupdate(EEPROMCheckAdr, 250); }}void cWiFi(){ WiFi.softAP("ElectroPeak's Flame", ""); //如果需要,请在此处设置密码,即 WiFi.softAP("ElectroPeak's Flame", "12345678"); IPAddress myIP =WiFi.softAPIP(); server.on("/cs0", handleCS0Change); server.on("/cs1", handleCS1Change); server.on("/cs2", handleCS2Change); server.on("/cs3", handleCS3Change); server.on("/conf", handleConf); server.serveStatic("/", SPIFFS, "/", "max-age=86400"); server.begin(); //启动服务器}
草图和数据文件夹Arduino
此 zip 文件包含草图文件和数据文件夹(SPIFFS 上传)无预览(仅下载)。
定制零件和外壳
eps_flame_android_u5Zy5Bksvp.apk制造工艺
- 用物联网扑灭野火
- 用三星 SAMIIO、Arduino UNO 和 Raspberry Pi 在几分钟内制作一个火灾探测器
- 使用 Raspberry Pi 的简单 DIY 婴儿哭闹检测器
- 火灾危害与预防
- 物联网消防
- 企业应用程序设计:iOS 在安全性方面与 Android 相比如何?
- Python String strip() 函数与示例
- Axiom Equipment Group 与其他赞助商一起向 Oxbow 消防局捐赠超过 11,000 美元
- 创建一个由 Android 应用控制的人数统计
- 带有 Arduino 或 ESP8266 的电容式指纹传感器
- 使用 Arduino 和 Android 设备控制 Roomba 机器人
- 带安卓设备的便携式温度计