15浏览
查看: 15|回复: 0

[ESP8266/ESP32] ESP32-C5 FireBeetle 2 搭建离线版的课堂交互网页

[复制链接]
ESP32-C5  FireBeetle 2 搭建离线版的课堂交互网页
注:此文参考卓尔大佬的文章,谢谢!
1.1 产品简介
FireBeetle 2 ESP32-C5 IO套装包括两部分:Firebeetle 2 ESP32-C5 开发板和其专用的IO扩展底板。IO扩展板方便快速连接各种传感器外设,让Firebeetle 2 ESP32-C5开发板到手即用,无需焊接。
FireBeetle 2 ESP32-C5 是一款搭载乐鑫 ESP32-C5 模组的低功耗 IoT 开发板,面向智能家居和广泛物联网场景,集高性能计算、多协议支持与智能电源管理于一体,为各种部署需求提供高可靠性、高灵活性与长续航的解决方案。
支持 2.4 & 5 GHz 双频 Wi-Fi 6
ESP32-C5 是乐鑫首款支持2.4 G和5 G 双频Wi-Fi 6的芯片。相较于 2.4GHz 频段,5GHz 具备更高传输速率、更低延迟及更少干扰特性,可提供更稳定、更低延迟的无线连接。同时,Wi-Fi 6 技术通过 OFDMA 频分复用 和目标唤醒时间(TWT,Target Wake Time) 机制,显著提升网络容量并降低设备功耗,延长电池使用时间,让设备长久续航。
FireBeetle 2 ESP32-C5 凭借双频 Wi-Fi 6 支持,可满足智能家居、物联网等场景对高吞吐与低功耗的双重需求。
多协议融合,扩展无线连接性
开发板支持 Wi-Fi、Thread、BLE、Zigbee 协议,可构建 Matter Wi-Fi/Thread 终端设备,实现跨平台智能家居设备互联。结合外部 MCU,还可作为 Thread 边界路由器、Matter 网关 或 Zigbee 网桥,覆盖复杂场景需求。
高效电源管理,部署更灵活
FireBeetle 2 ESP32-C5提供了高效和易用的电源管理,为各种部署需求提供可靠、灵活的供电解决方案。
  • 多元供电方式:支持 Type-C、5V DC 及太阳能输入对锂电池充电,解决无电源场景(如屋顶、阳台)的部署难题。
  • 太阳能优化PMIC(电源管理集成电路):采用太阳能电源管理模块 5V@1A同款太阳能电源管理芯片,最大限度的利用输入电源的电流输出能力,可最大化不同光照下的发电效率。
  • 智能监测与节能:集成电池电量监测功能,支持低电量预警;提供一组可控 3.3V 电源输出,可切断外接传感器供电以进一步降低功耗。

搭配专用IO扩展底板,无需焊接
Firebeetle 2 ESP32-C5开发板推出专属的IO扩展板,其IO引脚全部引出,并且精心做了功能分区,方便直接快速连接各种传感器外设,真正让开发板做到了到手即用,无需焊接。
2.1 打开Arduino,配置环境
环境配置参考:
请点击下方链接查看添加板卡的详细步骤:
注意:仅esp32板卡环境 3.3.0-alpha1分支版本才支持ESP32-C5。
2.2 输入代码
#include <WiFi.h>
#include <WebServer.h>

// 手机热点配置(替换为您的热点SSID和密码)
const char* ssid = "jzai"; // 替换为手机热点的SSID
const char* password = "88998899"; // 替换为手机热点的密码

// 创建Web服务器对象,监听80端口(HTTP协议)
WebServer server(80);

// HTML内容(主题,保持与原代码一致)
const char* htmlContent = R"rawliteral(
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>打地鼠小游戏</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Arial Rounded MT Bold', 'Arial', sans-serif;
        }
        
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
            padding: 20px;
        }
        
        .game-container {
            width: 100%;
            max-width: 600px;
            background-color: rgba(255, 255, 255, 0.9);
            border-radius: 20px;
            padding: 25px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            text-align: center;
        }
        
        h1 {
            color: #333;
            margin-bottom: 20px;
            font-size: 2.5rem;
            text-shadow: 2px 2px 0 #ffcc00;
        }
        
        .game-info {
            display: flex;
            justify-content: space-between;
            margin-bottom: 20px;
            font-size: 1.2rem;
            font-weight: bold;
        }
        
        .score, .timer {
            background-color: #ffcc00;
            padding: 10px 20px;
            border-radius: 10px;
            box-shadow: 0 4px 0 #e6b800;
        }
        
        .game-board {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 15px;
            margin-bottom: 25px;
        }
        
        .hole {
            width: 100%;
            aspect-ratio: 1;
            background-color: #8B4513;
            border-radius: 50%;
            position: relative;
            overflow: hidden;
            box-shadow: inset 0 10px 0 rgba(0, 0, 0, 0.3);
            cursor: pointer;
        }
        
        .mole {
            width: 80%;
            height: 80%;
            background-color: #8B4513;
            border-radius: 50%;
            position: absolute;
            bottom: -80%;
            left: 10%;
            transition: bottom 0.3s;
            cursor: pointer;
        }
        
        .mole::before {
            content: '';
            position: absolute;
            width: 40%;
            height: 40%;
            background-color: #333;
            border-radius: 50%;
            top: 20%;
            left: 30%;
        }
        
        .mole::after {
            content: '';
            position: absolute;
            width: 60%;
            height: 30%;
            background-color: #ff6666;
            border-radius: 50%;
            bottom: 10%;
            left: 20%;
        }
        
        .mole.up {
            bottom: 10%;
        }
        
        .controls {
            margin-top: 20px;
        }
        
        button {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 12px 30px;
            font-size: 1.2rem;
            border-radius: 10px;
            cursor: pointer;
            transition: all 0.3s;
            box-shadow: 0 4px 0 #3d8b40;
        }
        
        button:hover {
            background-color: #45a049;
            transform: translateY(2px);
            box-shadow: 0 2px 0 #3d8b40;
        }
        
        button:active {
            transform: translateY(4px);
            box-shadow: 0 0 0 #3d8b40;
        }
        
        .game-over {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.8);
            justify-content: center;
            align-items: center;
            z-index: 10;
        }
        
        .game-over-content {
            background-color: white;
            padding: 30px;
            border-radius: 15px;
            text-align: center;
            max-width: 400px;
            width: 90%;
        }
        
        .game-over h2 {
            color: #ff3333;
            margin-bottom: 20px;
            font-size: 2rem;
        }
        
        .final-score {
            font-size: 1.5rem;
            margin-bottom: 20px;
        }
        
        @media (max-width: 500px) {
            .game-board {
                grid-template-columns: repeat(2, 1fr);
            }
            
            h1 {
                font-size: 2rem;
            }
        }
    </style>
</head>
<body>
    <div class="game-container">
        <h1>打地鼠小游戏</h1>
        
        <div class="game-info">
            <div class="score">得分: <span id="score">0</span></div>
            <div class="timer">时间: <span id="time">30</span>秒</div>
        </div>
        
        <div class="game-board" id="gameBoard">
            <!-- 地鼠洞将通过JavaScript生成 -->
        </div>
        
        <div class="controls">
            <button id="startBtn">开始游戏</button>
        </div>
    </div>
   
    <div class="game-over" id="gameOver">
        <div class="game-over-content">
            <h2>游戏结束!</h2>
            <p class="final-score">你的得分: <span id="finalScore">0</span></p>
            <button id="restartBtn">再玩一次</button>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            // 游戏元素
            const gameBoard = document.getElementById('gameBoard');
            const scoreDisplay = document.getElementById('score');
            const timeDisplay = document.getElementById('time');
            const startBtn = document.getElementById('startBtn');
            const gameOverScreen = document.getElementById('gameOver');
            const finalScoreDisplay = document.getElementById('finalScore');
            const restartBtn = document.getElementById('restartBtn');
            
            // 游戏变量
            let score = 0;
            let timeLeft = 30;
            let gameActive = false;
            let timer;
            let moleTimer;
            
            // 创建地鼠洞
            for (let i = 0; i < 9; i++) {
                const hole = document.createElement('div');
                hole.className = 'hole';
               
                const mole = document.createElement('div');
                mole.className = 'mole';
                mole.dataset.id = i;
               
                hole.appendChild(mole);
                gameBoard.appendChild(hole);
               
                // 添加点击事件
                mole.addEventListener('click', hitMole);
            }
            
            const moles = document.querySelectorAll('.mole');
            
            // 开始游戏
            startBtn.addEventListener('click', startGame);
            
            // 重新开始游戏
            restartBtn.addEventListener('click', function() {
                gameOverScreen.style.display = 'none';
                startGame();
            });
            
            function startGame() {
                // 重置游戏状态
                score = 0;
                timeLeft = 30;
                gameActive = true;
                scoreDisplay.textContent = score;
                timeDisplay.textContent = timeLeft;
                startBtn.disabled = true;
               
                // 清除所有活动的地鼠
                moles.forEach(mole => {
                    mole.classList.remove('up');
                });
               
                // 开始计时
                timer = setInterval(function() {
                    timeLeft--;
                    timeDisplay.textContent = timeLeft;
                    
                    if (timeLeft <= 0) {
                        endGame();
                    }
                }, 1000);
               
                // 开始地鼠出现
                showRandomMole();
            }
            
            function showRandomMole() {
                if (!gameActive) return;
               
                // 随机选择一个地鼠
                const randomIndex = Math.floor(Math.random() * moles.length);
                const mole = moles[randomIndex];
               
                // 显示地鼠
                mole.classList.add('up');
               
                // 设置地鼠消失的时间(0.5-2秒之间随机)
                const hideTime = Math.random() * 1500 + 500;
               
                setTimeout(() => {
                    mole.classList.remove('up');
                    
                    // 如果游戏还在进行中,继续显示下一个地鼠
                    if (gameActive) {
                        showRandomMole();
                    }
                }, hideTime);
            }
            
            function hitMole(e) {
                if (!gameActive) return;
               
                // 确保点击的是地鼠而不是洞
                if (e.target.classList.contains('up')) {
                    // 增加分数
                    score++;
                    scoreDisplay.textContent = score;
                    
                    // 播放点击效果
                    e.target.style.backgroundColor = '#ff3333';
                    setTimeout(() => {
                        e.target.style.backgroundColor = '#8B4513';
                    }, 200);
                    
                    // 立即隐藏被点击的地鼠
                    e.target.classList.remove('up');
                }
            }
            
            function endGame() {
                gameActive = false;
                clearInterval(timer);
                startBtn.disabled = false;
               
                // 显示游戏结束画面
                finalScoreDisplay.textContent = score;
                gameOverScreen.style.display = 'flex';
            }
        });
    </script>
</body>
</html>
)rawliteral";

void handleRoot() {
  // 发送HTTP响应,状态码200,内容类型为text/html
  server.send(200, "text/html", htmlContent);
}

void setup() {
  // 初始化串口,用于调试
  Serial.begin(115200);
  Serial.println("Starting ESP32-C5 Web Server...");

  // 设置ESP32为STA模式,连接手机热点
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi: ");
  Serial.println(ssid);

  // 等待连接,最多尝试20秒
  int timeout = 20;
  while (WiFi.status() != WL_CONNECTED && timeout > 0) {
    delay(1000);
    Serial.print(".");
    timeout--;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nWiFi Connected");
    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP()); // 打印STA模式的IP地址
  } else {
    Serial.println("\nFailed to connect to WiFi. Check SSID/password or signal strength.");
    return; // 连接失败,停止后续操作
  }

  // 设置Web服务器路由
  server.on("/", handleRoot);
  server.onNotFound([]() {
    server.send(404, "text/plain", "404: Page Not Found");
  });

  // 启动服务器
  server.begin();
  Serial.println("HTTP Server Started");
}

void loop() {
  // 处理客户端HTTP请求
  server.handleClient();
  delay(10); // 短暂延时以确保稳定性
}
3.1 查看效果
无论是电脑还是手机只要接入同一个热点网络,那么它就可以通过查看串口监视中的IP进行连接游玩。
4 接下来的方向
我觉得可以搭建一个离线的小型服务器,这样学生的数据可以收集起来,方便老师统计。
比如学生的答题,只要学生提交数据,老师这边就可以得到学生反馈。
这有点类似学习平板的同步反馈。
或者做个本地游戏。多人竞技游戏,支持2-4个玩家同时游戏,每个玩家连接自己的设备,实时显示玩家排名。
这应该也是一个不错的方法。



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail