ESP8266-NodeMCU网络服务器(一)- 数字时钟制作

效果

在这里插入图片描述

所需环境

ESP8266-NodeMCU 开发板(以下简称NodeMCU

ESP8266

Arduino IDE 代码编写工具

无线(热点)2.4G频段

实现步骤

原理

NodeMCU作为无线终端接入局域网内的无线信号,并配合ESP8266库提供的服务器功能使得NodeMCU最终作为一个服务器,将html、js文件上传到该服务器(NodeMCU的闪存文件系统),然后通过浏览器访问该服务器的文件即可

HTML、JS代码

index.html主页

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="main.css">
    <title>时钟</title>
</head>
<body>
    <canvas id="canvas" width="" height=""></canvas>
    <script src="digit.js"></script>
    <script src="countdown.js"></script>
</body>
</html>

digit.js数字

digit =
    [
        [
            [0,0,1,1,1,0,0],
            [0,1,1,0,1,1,0],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,0,1,1,0],
            [0,0,1,1,1,0,0]
        ],//0
        [
            [0,0,0,1,1,0,0],
            [0,1,1,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [1,1,1,1,1,1,1]
        ],//1
        [
            [0,1,1,1,1,1,0],
            [1,1,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,0,0],
            [0,0,1,1,0,0,0],
            [0,1,1,0,0,0,0],
            [1,1,0,0,0,0,0],
            [1,1,0,0,0,1,1],
            [1,1,1,1,1,1,1]
        ],//2
        [
            [1,1,1,1,1,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,0,0],
            [0,0,1,1,1,0,0],
            [0,0,0,0,1,1,0],
            [0,0,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,1,1,0]
        ],//3
        [
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,1,0],
            [0,0,1,1,1,1,0],
            [0,1,1,0,1,1,0],
            [1,1,0,0,1,1,0],
            [1,1,1,1,1,1,1],
            [0,0,0,0,1,1,0],
            [0,0,0,0,1,1,0],
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,1,1]
        ],//4
        [
            [1,1,1,1,1,1,1],
            [1,1,0,0,0,0,0],
            [1,1,0,0,0,0,0],
            [1,1,1,1,1,1,0],
            [0,0,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,1,1,0]
        ],//5
        [
            [0,0,0,0,1,1,0],
            [0,0,1,1,0,0,0],
            [0,1,1,0,0,0,0],
            [1,1,0,0,0,0,0],
            [1,1,0,1,1,1,0],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,1,1,0]
        ],//6
        [
            [1,1,1,1,1,1,1],
            [1,1,0,0,0,1,1],
            [0,0,0,0,1,1,0],
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,1,1,0,0,0],
            [0,0,1,1,0,0,0],
            [0,0,1,1,0,0,0],
            [0,0,1,1,0,0,0]
        ],//7
        [
            [0,1,1,1,1,1,0],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,1,1,0],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,1,1,0]
        ],//8
        [
            [0,1,1,1,1,1,0],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,0,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,0,0],
            [0,1,1,0,0,0,0]
        ],//9
        [
            [0,0,0,0],
            [0,0,0,0],
            [0,1,1,0],
            [0,1,1,0],
            [0,0,0,0],
            [0,0,0,0],
            [0,1,1,0],
            [0,1,1,0],
            [0,0,0,0],
            [0,0,0,0]
        ]//:
    ];

countdown.js动画

var WINDOW_WIDTH = 1024;
var WINDOW_HEIGHT = 768;
var RADIUS = 8;
var MARGIN_TOP = 60;
var MARGIN_LEFT = 30;


/*const endTime = new Date(2020, 7, 22, 10, 45, 46);*/

/*var endTime = new Date();
endTime.setTime(endTime.getTime() + 3600 * 1000);*/

var curShowTimeSeconds = 0;

var balls = [];
const colors = ["#33b5e5","#0099CC","#aa66cc","#9933CC","#99CC00","#669900","#FFBB33","#FF8800","#FF4444","#CC0000"]


window.onload = function(){

	 /*WINDOW_WIDTH = document.body.clientWidth;
	 WINDOW_HEIGHT = document.body.clientHeight;*/
	WINDOW_WIDTH = document.documentElement.clientWidth;
	WINDOW_HEIGHT = document.documentElement.clientHeight;
	 RADIUS = Math.round(WINDOW_WIDTH * 4 / 5 / 108) - 1;
	 MARGIN_TOP = Math.round(WINDOW_HEIGHT / 5);
	 MARGIN_LEFT = Math.round(WINDOW_WIDTH / 10);

	var canvas = document.getElementById("canvas");
	var context = canvas.getContext("2d");
	
	canvas.width = WINDOW_WIDTH;
	canvas.height = WINDOW_HEIGHT;
	
	curShowTimeSeconds = getCurrentShowTimeSeconds();
	//render(context);
	//动画函数  每1000毫秒刷新 1000/50 = 20 帧
	setInterval(
		function(){
			render(context);
			update();
		}, 50);
	
}

function getCurrentShowTimeSeconds(){
	var curTime = new Date();
	var ret = curTime.getHours() * 3600 + curTime.getMinutes() * 60 + curTime.getSeconds();
	return ret;

	/*var curTime = new Date();
	//getTime() 当前时间距1970年1月1日0时0分0秒的毫秒数
	var ret = endTime.getTime() - curTime.getTime();
	ret = Math.round(ret / 1000); //秒数
	return ret >= 0 ? ret : 0;*/
}

/*刷新*/
function update(){
	/*下一秒*/
	var nextShowTimeSeconds = getCurrentShowTimeSeconds();
	var nextHours = parseInt(nextShowTimeSeconds / 3600);
	var nextMinutes = parseInt((nextShowTimeSeconds - nextHours*3600) / 60);
	var nextSeconds = nextShowTimeSeconds % 60;

	/*当前时间*/
	var curHours = parseInt(curShowTimeSeconds / 3600);
	var curMinutes = parseInt((curShowTimeSeconds - curHours*3600) / 60);
	var curSeconds = curShowTimeSeconds % 60;
	
	if(nextSeconds != curSeconds){
		if(parseInt(curHours/10) != parseInt(nextHours/10)){
			addBalls(MARGIN_LEFT + 0, MARGIN_TOP, parseInt(curHours/10));
		}
		if(parseInt(curHours%10) != parseInt(nextHours%10)){
			addBalls(MARGIN_LEFT + 15*(RADIUS + 1),MARGIN_TOP, parseInt(curHours/10));
		}
		if(parseInt(curMinutes/10) != parseInt(nextMinutes/10)){
			addBalls(MARGIN_LEFT + 39*(RADIUS + 1),MARGIN_TOP, parseInt(curMinutes/10));
		}
		if(parseInt(curMinutes%10) != parseInt(nextMinutes%10)){
			addBalls(MARGIN_LEFT + 54*(RADIUS + 1), MARGIN_TOP, parseInt(curMinutes %10));
		}
		if(parseInt(curSeconds/10) != parseInt(nextSeconds/10)){
			addBalls(MARGIN_LEFT + 78*(RADIUS + 1), MARGIN_TOP, parseInt(curSeconds/10));
		}
		if(parseInt(curSeconds%10) != parseInt(nextSeconds%10)){
			addBalls(MARGIN_LEFT + 93*(RADIUS + 1), MARGIN_TOP, parseInt(nextSeconds%10));
		}

		curShowTimeSeconds = nextShowTimeSeconds;
	}
	updateBalls();
}

function updateBalls(){

	for( var i = 0 ; i < balls.length ; i ++ ){

		balls[i].x += balls[i].vx;
		balls[i].y += balls[i].vy;
		balls[i].vy += balls[i].g;

		if( balls[i].y >= WINDOW_HEIGHT-RADIUS ){
			balls[i].y = WINDOW_HEIGHT-RADIUS;
			balls[i].vy = - balls[i].vy*0.75;
		}
	}

	/*删除数组中的小球*/
	var cnt = 0
	for( var i = 0 ; i < balls.length ; i ++ )
		if( balls[i].x + RADIUS > 0 && balls[i].x -RADIUS < WINDOW_WIDTH )
			balls[cnt++] = balls[i]

	while( balls.length > cnt ){
		balls.pop();
	}

	Math.min(300, cnt)//取两个数最小值
	while( balls.length > Math.min(300, cnt) ){
		balls.pop();
	}
}

function addBalls( x , y , num ){

	for( var i = 0  ; i < digit[num].length ; i ++ )
		for( var j = 0  ; j < digit[num][i].length ; j ++ )
			if( digit[num][i][j] == 1 ){
				var aBall = {
					x:x+j*2*(RADIUS+1)+(RADIUS+1),
					y:y+i*2*(RADIUS+1)+(RADIUS+1),
					g:1.5+Math.random(),
					vx:Math.pow( -1 , Math.ceil( Math.random()*1000 ) ) * 4,
					vy:-5,
					color: colors[ Math.floor( Math.random()*colors.length ) ]
				}

				balls.push( aBall )
			}
}

/*绘制*/
function render(cxt){
	//对矩形内的动画进行刷新
	cxt.clearRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
	
	var hours = parseInt(curShowTimeSeconds / 3600);
	var minutes = parseInt((curShowTimeSeconds - hours*3600) / 60);
	var seconds = curShowTimeSeconds % 60;
	//1
	renderDigit(MARGIN_LEFT,MARGIN_TOP, parseInt(hours / 10), cxt);
	//2
	renderDigit(MARGIN_LEFT + 15*(RADIUS + 1),MARGIN_TOP, parseInt(hours % 10), cxt);
	//:
	renderDigit(MARGIN_LEFT + 30*(RADIUS + 1),MARGIN_TOP, 10, cxt);
	//3
	renderDigit(MARGIN_LEFT + 39*(RADIUS + 1),MARGIN_TOP, parseInt(minutes / 10), cxt);
	//4
	renderDigit(MARGIN_LEFT + 54*(RADIUS + 1),MARGIN_TOP, parseInt(minutes % 10), cxt);
	//:
	renderDigit(MARGIN_LEFT + 69*(RADIUS + 1),MARGIN_TOP, 10, cxt);
	//5
	renderDigit(MARGIN_LEFT + 78*(RADIUS + 1),MARGIN_TOP, parseInt(seconds / 10), cxt);
	//6
	renderDigit(MARGIN_LEFT + 93*(RADIUS + 1),MARGIN_TOP, parseInt(seconds % 10), cxt);

	for( var i = 0 ; i < balls.length ; i ++ ){
		cxt.fillStyle=balls[i].color;

		cxt.beginPath();
		cxt.arc( balls[i].x , balls[i].y , RADIUS , 0 , 2*Math.PI , true );
		cxt.closePath();

		cxt.fill();
	}
}

//画出小球
function renderDigit(x, y, num, cxt){
	cxt.fillStyle = "rgb(0, 102, 153)";
	
	for(var i = 0; i < digit[num].length; i ++){
		for(var j = 0; j < digit[num][i].length; j++){
			if(digit[num][i][j] == 1){
				cxt.beginPath();
				cxt.arc(x + j*2*(RADIUS+1)+(RADIUS+1), y+i*2*(RADIUS+1)+(RADIUS+1), RADIUS, 0, 2*Math.PI);
				cxt.closePath();
				cxt.fill();
			}
		}
	}
}

上传文件到闪存系统

将以上三个文件放在主程序同目录下的data文件中,然后使用Arduino IDE的插件将data文件上传到NodeMCU的闪存系统中
在这里插入图片描述
插件下载地址:https://github.com/esp8266/arduino-esp8266fs-plugin/releases
在这里插入图片描述

将下载后的文件解压并移动到Arduino IDE安装目录下的tools文件夹中,重启 Arduino IDE

在这里插入图片描述

注意选择闪存大小,本次上传的文件几十KB,所以选择1MB就够了。点击工具下的 ESP8266 Sketh Data Upload,留意控制台输出看是否上传成功

在这里插入图片描述
在这里插入图片描述

上传程序

代码参考自太极创客:http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/iot-c/spiffs/spiffs-web-server/

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266WebServer.h>
#include <FS.h>  

ESP8266WiFiMulti wifiMulti;     // 建立ESP8266WiFiMulti对象

ESP8266WebServer esp8266_server(80);    // 建立网络服务器对象,该对象用于响应HTTP请求。监听端口(80)

void setup() {
  Serial.begin(9600);          // 启动串口通讯
  Serial.println("");
  
  // 无线名称 密码
  wifiMulti.addAP("面向对象编程_2G", "12345678");
  Serial.println("Connecting ...");
  
  int i = 0;  
  while (wifiMulti.run() != WL_CONNECTED) { // 尝试进行wifi连接。
    delay(1000);
    Serial.print(i++); Serial.print(' ');
  }
  
  // WiFi连接成功后将通过串口监视器输出连接成功信息 
  Serial.println('\n');
  Serial.print("Connected to ");
  Serial.println(WiFi.SSID());              // 通过串口监视器输出连接的WiFi名称
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP());           // 通过串口监视器输出ESP8266-NodeMCU的IP

  if(SPIFFS.begin()){                       // 启动闪存文件系统
    Serial.println("SPIFFS Started.");
  } else {
    Serial.println("SPIFFS Failed to Start.");
  }
  
  esp8266_server.onNotFound(handleUserRequet);      // 告知系统如何处理用户请求

  esp8266_server.begin();                           // 启动网站服务
  Serial.println("HTTP server started");
}

void loop(void) {
  esp8266_server.handleClient();                    // 处理用户请求
}

// 处理用户浏览器的HTTP访问
void handleUserRequet() {         
     
  // 获取用户请求网址信息
  String webAddress = esp8266_server.uri();
  
  // 通过handleFileRead函数处处理用户访问
  bool fileReadOK = handleFileRead(webAddress);

  // 如果在SPIFFS无法找到用户访问的资源,则回复404 (Not Found)
  if (!fileReadOK){                                                 
    esp8266_server.send(404, "text/plain", "404 Not Found"); 
  }
}

bool handleFileRead(String path) {            //处理浏览器HTTP访问

  if (path.endsWith("/")) {                   // 如果访问地址以"/"为结尾
    path = "/index.html";                     // 则将访问地址修改为/index.html便于SPIFFS访问
  } 
  
  String contentType = getContentType(path);  // 获取文件类型
  
  if (SPIFFS.exists(path)) {                     // 如果访问的文件可以在SPIFFS中找到
    File file = SPIFFS.open(path, "r");          // 则尝试打开该文件
    esp8266_server.streamFile(file, contentType);// 并且将该文件返回给浏览器
    file.close();                                // 并且关闭文件
    return true;                                 // 返回true
  }
  return false;                                  // 如果文件未找到,则返回false
}

// 获取文件类型
String getContentType(String filename){
  if(filename.endsWith(".htm")) return "text/html";
  else if(filename.endsWith(".html")) return "text/html";
  else if(filename.endsWith(".css")) return "text/css";
  else if(filename.endsWith(".js")) return "application/javascript";
  else if(filename.endsWith(".png")) return "image/png";
  else if(filename.endsWith(".gif")) return "image/gif";
  else if(filename.endsWith(".jpg")) return "image/jpeg";
  else if(filename.endsWith(".ico")) return "image/x-icon";
  else if(filename.endsWith(".xml")) return "text/xml";
  else if(filename.endsWith(".pdf")) return "application/x-pdf";
  else if(filename.endsWith(".zip")) return "application/x-zip";
  else if(filename.endsWith(".gz")) return "application/x-gzip";
  return "text/plain";
}

上传成功后打开串口,查看无线局域网给NodeMCU分配的IP地址,如果看不到输出需要按一下哎NodeMCU的RST键进行复位

在这里插入图片描述

通过浏览器访问该IP地址即可看到效果

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那个小白猿

讨杯可乐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值