Fultter APP控制 WOKWI ESP32 RFID+LED (使用flutter_windows_3.19.1-stable Visual studio 1.108.2 )
1) Android studio 環境設定
2) Visual studio 1.108.2版本 環境設定+ 開發 Flutter APP程式
3) 安裝 Flutter flutter_windows_3.19.1-stable 解壓縮後儲存 於 C:\src\flutter 目錄
WOKWI ESP32 程式
#include <SPI.h>
#include <MFRC522.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// --- 硬體腳位 (您的指定) ---
#define SS_PIN 5
#define RST_PIN 22
#define LED_PIN 2
#define I2C_SDA 17
#define I2C_SCL 16
// --- 設定區 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// MQTT 設定 (與您的 Python Tkinter 程式對接)
const char* mqtt_server = "mqttgo.io";
const char* TOPIC_RFID_UID = "alex9ufo/rfid/UID";
const char* TOPIC_LED_CONTROL = "alex9ufo/led/control";
const char* TOPIC_LED_STATUS = "alex9ufo/led/status";
// 硬體物件
LiquidCrystal_I2C lcd(0x27, 16, 2);
MFRC522 mfrc522(SS_PIN, RST_PIN);
WiFiClient espClient;
PubSubClient mqttClient(espClient);
// --- FreeRTOS 隊列 ---
QueueHandle_t rfidQueue;
struct RfidMsg { char uid[20]; };
// 全域變數
bool isFlashing = false;
// --- MQTT 接收處理 (來自 Python 控制台) ---
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String message = "";
for (int i = 0; i < length; i++) message += (char)payload[i];
Serial.printf("\n[MQTT CMD]: %s\n", message.c_str());
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("MQTT Command:");
lcd.setCursor(0, 1);
isFlashing = false;
if (message == "on") {
digitalWrite(LED_PIN, HIGH);
lcd.print("LED: ON");
mqttClient.publish(TOPIC_LED_STATUS, "ON");
}
else if (message == "off") {
digitalWrite(LED_PIN, LOW);
lcd.print("LED: OFF");
mqttClient.publish(TOPIC_LED_STATUS, "OFF");
}
else if (message == "flash") {
isFlashing = true;
lcd.print("MODE: FLASHING");
mqttClient.publish(TOPIC_LED_STATUS, "FLASHING");
}
else if (message == "timer") {
digitalWrite(LED_PIN, HIGH);
lcd.print("TIMER: 5 SEC");
mqttClient.publish(TOPIC_LED_STATUS, "TIMER_START");
vTaskDelay(5000 / portTICK_PERIOD_MS); // 在 Task 中使用 vTaskDelay 不會卡死整個系統
digitalWrite(LED_PIN, LOW);
mqttClient.publish(TOPIC_LED_STATUS, "OFF");
}
}
// --- Core 0: 負責 WiFi 與 MQTT 通訊 ---
void mqttTask(void *pvParameters) {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { vTaskDelay(500 / portTICK_PERIOD_MS); }
mqttClient.setServer(mqtt_server, 1883);
mqttClient.setCallback(mqttCallback);
RfidMsg rMsg;
while (true) {
// 維護 MQTT 連線
if (!mqttClient.connected()) {
Serial.print("Connecting to MQTT...");
if (mqttClient.connect("ESP32_RFID_Gate_NoTG")) {
Serial.println("Connected");
mqttClient.subscribe(TOPIC_LED_CONTROL);
} else {
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
mqttClient.loop();
// 接收來自 Core 1 的 RFID 訊息並發送到 MQTT
if (xQueueReceive(rfidQueue, &rMsg, 0) == pdPASS) {
mqttClient.publish(TOPIC_RFID_UID, rMsg.uid);
Serial.printf("Sent UID to Python: %s\n", rMsg.uid);
}
// 處理閃爍邏輯
if (isFlashing) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
vTaskDelay(300 / portTICK_PERIOD_MS);
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
// --- Core 1: 專門負責 RFID 掃描 (不處理網路) ---
void rfidTask(void *pvParameters) {
SPI.begin();
mfrc522.PCD_Init();
RfidMsg rMsg;
while (true) {
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
String uidStr = "";
for (byte i = 0; i < mfrc522.uid.size; i++) {
uidStr += (mfrc522.uid.uidByte[i] < 0x10 ? "0" : "");
uidStr += String(mfrc522.uid.uidByte[i], HEX);
}
uidStr.toUpperCase();
// 更新 LCD 顯示
lcd.clear();
lcd.setCursor(0, 0); lcd.print("RFID Detected!");
lcd.setCursor(0, 1); lcd.print("ID: " + uidStr);
// 將卡號打包送入隊列,交給 Core 0 發送
uidStr.toCharArray(rMsg.uid, 20);
xQueueSend(rfidQueue, &rMsg, portMAX_DELAY);
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
}
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
// 初始化 I2C LCD
Wire.begin(I2C_SDA, I2C_SCL);
lcd.init();
lcd.backlight();
lcd.print("MQTT Connecting...");
// 建立隊列
rfidQueue = xQueueCreate(10, sizeof(RfidMsg));
if (rfidQueue != NULL) {
// 建立雙核心任務
xTaskCreatePinnedToCore(mqttTask, "MQTT_Task", 8192, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(rfidTask, "RFID_Task", 4096, NULL, 1, NULL, 1);
}
}
void loop() {
// FreeRTOS 架構下 loop 不需執行內容
vTaskDelay(portMAX_DELAY);
}
VS Code pubspec.yaml
name: rfid
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: '>=3.3.0 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
mqtt_client: ^10.2.0 # Or latest version
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
VS Code AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:label="rfid"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility?hl=en and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
VS Code main.dart
import 'package:flutter/material.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
void main() => runApp(const MaterialApp(home: RFIDControlApp()));
class RFIDControlApp extends StatefulWidget {
const RFIDControlApp({super.key});
@override
State<RFIDControlApp> createState() => _RFIDControlAppState();
}
class _RFIDControlAppState extends State<RFIDControlApp> {
final String server = "mqttgo.io";
final String topicUID = "alex9ufo/rfid/UID";
final String topicControl = "alex9ufo/led/control";
final String topicStatus = "alex9ufo/led/status";
late MqttServerClient client;
String lastUID = "No Card Scanned";
String ledStatus = "Unknown";
bool isConnected = false;
@override
void initState() {
super.initState();
_setupMqtt();
}
Future<void> _setupMqtt() async {
client = MqttServerClient(server, 'flutter_client_${DateTime.now().millisecondsSinceEpoch}');
client.port = 1883;
client.keepAlivePeriod = 20;
client.onDisconnected = () => setState(() => isConnected = false);
try {
await client.connect();
setState(() => isConnected = true);
// Subscribe to UID and LED Status
client.subscribe(topicUID, MqttQos.atMostOnce);
client.subscribe(topicStatus, MqttQos.atMostOnce);
client.updates!.listen((List<MqttReceivedMessage<MqttMessage>> c) {
final MqttPublishMessage recMess = c[0].payload as MqttPublishMessage;
final String pt = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
setState(() {
if (c[0].topic == topicUID) {
lastUID = pt;
} else if (c[0].topic == topicStatus) {
ledStatus = pt;
}
});
});
} catch (e) {
print('Connection failed: $e');
client.disconnect();
}
}
void _sendCommand(String command) {
if (!isConnected) return;
final builder = MqttClientPayloadBuilder();
builder.addString(command);
client.publishMessage(topicControl, MqttQos.atLeastOnce, builder.payload!);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ESP32 RFID & LED Control"),
backgroundColor: isConnected ? Colors.green : Colors.red,
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
// RFID Display Card
Card(
elevation: 4,
child: ListTile(
leading: const Icon(Icons.credit_card, size: 40),
title: const Text("Last Scanned UID"),
subtitle: Text(lastUID, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.blue)),
),
),
const SizedBox(height: 20),
// LED Status Display
Text("Current LED Status: $ledStatus", style: const TextStyle(fontSize: 18)),
const Divider(height: 40),
// Control Buttons
Wrap(
spacing: 20,
runSpacing: 20,
children: [
_buildControlBtn("ON", Colors.green, () => _sendCommand("on")),
_buildControlBtn("OFF", Colors.red, () => _sendCommand("off")),
_buildControlBtn("FLASH", Colors.orange, () => _sendCommand("flash")),
_buildControlBtn("TIMER (5s)", Colors.purple, () => _sendCommand("timer")),
],
),
],
),
),
);
}
Widget _buildControlBtn(String label, Color color, VoidCallback onPressed) {
return ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: color, minimumSize: const Size(120, 50)),
onPressed: isConnected ? onPressed : null,
child: Text(label, style: const TextStyle(color: Colors.white)),
);
}
}
VS Code android/gradle/wrapper/gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
VS Code android/settings.gradle
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("${flutterSdkPath}/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
// 1. 將這裡的版本改為 8.2.1 或更高
id "com.android.application" version "8.2.1" apply false
// 2. 如果有 kotlin,建議也升級到 1.9.10 以上
id "org.jetbrains.kotlin.android" version "1.9.10" apply false
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
}
VS Code android/app/build.gradle
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
android {
namespace "com.example.rfid"
compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.rfid"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
PowerShell
# 1. 清除 Flutter 緩存
flutter clean
# 2. 重新取得套件
flutter pub get
# 3. 重新編譯
flutter build apk --release --split-per-abi


沒有留言:
張貼留言