mqtt+esp32公網(wǎng)控制PIn 2 led燈
PlatformIO 配置
platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =knolleary/PubSubClient @ ^2.8
build_flags =
-D LED_PIN=2 ; 板載LED(可改成你的實(shí)際引腳)
-D WIFI_SSID="你的WiFi"
-D WIFI_PASS="你的WiFi密碼"
-D MQTT_HOST="你的外網(wǎng)MQTT域名或IP"
-D MQTT_PORT=1883 ; 先用1883,跑通后可換8883
-D MQTT_USER="你的mqtt用戶名"
-D MQTT_PASS="你的mqtt密碼"
-D DEVICE_ID="esp32-led-01"
main.cpp
點(diǎn)擊查看代碼
#include <WiFi.h>
#include <PubSubClient.h>
// ====== 編譯期注入的配置(見 platformio.ini)======
#ifndef WIFI_SSID
#define WIFI_SSID "YOUR_WIFI"
#endif
#ifndef WIFI_PASS
#define WIFI_PASS "YOUR_PASS"
#endif
#ifndef MQTT_HOST
#define MQTT_HOST "broker.example.com"
#endif
#ifndef MQTT_PORT
#define MQTT_PORT 1883
#endif
#ifndef MQTT_USER
#define MQTT_USER ""
#endif
#ifndef MQTT_PASS
#define MQTT_PASS ""
#endif
#ifndef DEVICE_ID
#define DEVICE_ID "esp32-led-01"
#endif
#ifndef LED_PIN
#define LED_PIN 2
#endif
// ====== 主題約定 ======
String t_base = String("home/") + DEVICE_ID;
String t_cmd = t_base + "/cmd";
String t_state = t_base + "/state";
String t_online = t_base + "/online"; // LWT
String t_ip = t_base + "/ip";
String t_rssi = t_base + "/rssi";
WiFiClient wifiClient; // 先用非TLS,跑通后可改 WiFiClientSecure
PubSubClient mqttClient(wifiClient);
unsigned long lastReportMs = 0;
bool ledOn = false;
void setLed(bool on) {
ledOn = on;
digitalWrite(LED_PIN, on ? HIGH : LOW);
// 上報(bào)狀態(tài)(retained),方便手機(jī)端看到最新狀態(tài)
mqttClient.publish(t_state.c_str(), on ? "ON" : "OFF", true);
}
void ensureWiFi() {
if (WiFi.status() == WL_CONNECTED) return;
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting WiFi");
int tries = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (++tries > 60) { // 約30秒
Serial.println("\nWiFi connect timeout, retry...");
WiFi.disconnect(true);
delay(1000);
WiFi.begin(WIFI_SSID, WIFI_PASS);
tries = 0;
}
}
Serial.println("\nWiFi connected: " + WiFi.localIP().toString());
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String msg;
msg.reserve(length);
for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
Serial.printf("MQTT <- [%s] %s\n", topic, msg.c_str());
if (String(topic) == t_cmd) {
// 兼容純文本與JSON
String s = msg;
s.toUpperCase();
if (s == "ON") { setLed(true); return; }
if (s == "OFF") { setLed(false); return; }
// 簡(jiǎn)單JSON解析(不引lib,輕量處理)
if (msg.indexOf("\"on\"") != -1 || msg.indexOf("\"ON\"") != -1) { setLed(true); return; }
if (msg.indexOf("\"off\"") != -1 || msg.indexOf("\"OFF\"") != -1) { setLed(false); return; }
}
}
void ensureMQTT() {
if (mqttClient.connected()) return;
mqttClient.setServer(MQTT_HOST, MQTT_PORT);
mqttClient.setCallback(mqttCallback);
// LWT 設(shè)置:掉線時(shí) broker 自動(dòng)發(fā) "offline"
// 注意:必須 connect() 時(shí)一次性傳入
String clientId = String(DEVICE_ID) + "-" + String((uint32_t)ESP.getEfuseMac(), HEX);
Serial.printf("Connecting MQTT as %s ...\n", clientId.c_str());
if (mqttClient.connect(
clientId.c_str(),
MQTT_USER, MQTT_PASS,
t_online.c_str(), 1, true, "offline" // LWT: topic, qos, retained, payload
)) {
Serial.println("MQTT connected.");
// 上線標(biāo)記
mqttClient.publish(t_online.c_str(), "online", true);
// 基礎(chǔ)信息(retained)
mqttClient.publish(t_ip.c_str(), WiFi.localIP().toString().c_str(), true);
// 訂閱控制主題
mqttClient.subscribe(t_cmd.c_str());
// 立即上報(bào)一次狀態(tài)(retained)
setLed(ledOn);
} else {
Serial.printf("MQTT connect failed, state=%d\n", mqttClient.state());
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
setLed(false);
ensureWiFi();
ensureMQTT();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) ensureWiFi();
if (!mqttClient.connected()) ensureMQTT();
mqttClient.loop();
// 每10秒上報(bào)一次 RSSI,便于遠(yuǎn)程診斷
unsigned long now = millis();
if (now - lastReportMs > 10000 && mqttClient.connected()) {
lastReportMs = now;
long rssi = WiFi.RSSI();
char buf[16];
snprintf(buf, sizeof(buf), "%ld", rssi);
mqttClient.publish(t_rssi.c_str(), buf, false);
}
}
setup-mosquitto.sh
點(diǎn)擊查看代碼
#!/usr/bin/env bash
set -euo pipefail
# =========================
# Mosquitto quick installer
# - Creates MQTT user/password
# - Configures 1883 and 8883 (TLS)
# - Self-signed TLS by default, or Let's Encrypt with --letsencrypt
# - Enables and starts systemd service
# =========================
USER_NAME=""
USER_PASS=""
DOMAIN=""
EMAIL=""
MODE="selfsigned" # or letsencrypt
log() { echo -e "\033[1;32m[+] $*\033[0m"; }
warn(){ echo -e "\033[1;33m[!] $*\033[0m"; }
err() { echo -e "\033[1;31m[-] $*\033[0m" >&2; exit 1; }
usage() {
cat <<'EOF'
Usage:
setup-mosquitto.sh -u <user> -p <pass> -d <domain> [--selfsigned|--letsencrypt] [-m <email>]
Examples:
sudo ./setup-mosquitto.sh -u mqttuser -p mqttpass --selfsigned -d mqtt.example.com
sudo ./setup-mosquitto.sh -u mqttuser -p mqttpass --letsencrypt -d mqtt.example.com -m you@example.com
EOF
exit 1
}
# ---- Parse args ----
while [[ $# -gt 0 ]]; do
case "$1" in
-u) USER_NAME="${2:-}"; shift 2;;
-p) USER_PASS="${2:-}"; shift 2;;
-d) DOMAIN="${2:-}"; shift 2;;
-m) EMAIL="${2:-}"; shift 2;;
--selfsigned) MODE="selfsigned"; shift;;
--letsencrypt) MODE="letsencrypt"; shift;;
-h|--help) usage;;
*) err "Unknown arg: $1";;
esac
done
[[ -z "$USER_NAME" || -z "$USER_PASS" || -z "$DOMAIN" ]] && usage
if [[ "$MODE" == "letsencrypt" && -z "$EMAIL" ]]; then
err "Let's Encrypt mode requires -m <email>"
fi
# ---- OS detect ----
if command -v apt-get >/dev/null 2>&1; then
PKG_MGR="apt"
elif command -v dnf >/dev/null 2>&1; then
PKG_MGR="dnf"
elif command -v yum >/dev/null 2>&1; then
PKG_MGR="yum"
else
err "Unsupported OS: need apt, dnf or yum."
fi
log "Detected package manager: $PKG_MGR"
# ---- Install packages ----
log "Installing Mosquitto and tools..."
if [[ "$PKG_MGR" == "apt" ]]; then
apt-get update -y
DEBIAN_FRONTEND=noninteractive apt-get install -y mosquitto mosquitto-clients openssl
if [[ "$MODE" == "letsencrypt" ]]; then
apt-get install -y certbot
fi
else
# Enable EPEL on RHEL-like for mosquitto if needed
if ! rpm -q mosquitto >/dev/null 2>&1; then
if [[ "$PKG_MGR" != "apt" ]]; then
if ! rpm -q epel-release >/dev/null 2>&1; then
log "Installing EPEL repository..."
if [[ "$PKG_MGR" == "dnf" ]]; then
dnf install -y epel-release
else
yum install -y epel-release
fi
fi
fi
fi
if [[ "$PKG_MGR" == "dnf" ]]; then
dnf install -y mosquitto mosquitto-clients openssl
[[ "$MODE" == "letsencrypt" ]] && dnf install -y certbot
else
yum install -y mosquitto mosquitto-clients openssl
[[ "$MODE" == "letsencrypt" ]] && yum install -y certbot
fi
fi
systemctl enable mosquitto >/dev/null 2>&1 || true
# ---- Prepare directories ----
CONF_DIR="/etc/mosquitto"
CONF_D="${CONF_DIR}/conf.d"
CERT_DIR="${CONF_DIR}/certs"
PASS_FILE="${CONF_DIR}/passwd"
mkdir -p "$CONF_D" "$CERT_DIR"
# ---- Create MQTT user/password ----
log "Creating MQTT user ..."
# mosquitto_passwd will create file if not exist; -b for batch mode
mosquitto_passwd -b -c "$PASS_FILE" "$USER_NAME" "$USER_PASS"
chown mosquitto:mosquitto "$PASS_FILE"
chmod 640 "$PASS_FILE"
# ---- TLS certs ----
CERT_FILE="${CERT_DIR}/server.crt"
KEY_FILE="${CERT_DIR}/server.key"
CHAIN_FILE="" # for LE
if [[ "$MODE" == "selfsigned" ]]; then
log "Generating self-signed certificate for CN=${DOMAIN} ..."
openssl req -x509 -nodes -newkey rsa:2048 \
-keyout "$KEY_FILE" \
-out "$CERT_FILE" \
-subj "/C=XX/ST=State/L=City/O=Org/OU=IT/CN=${DOMAIN}" \
-days 825
CHAIN_FILE="$CERT_FILE" # not used but keep var non-empty
else
log "Obtaining Let's Encrypt certificate for ${DOMAIN} (standalone) ..."
# This will bind TCP/80 temporarily; ensure no web server occupies it.
systemctl stop mosquitto || true
certbot certonly --standalone --non-interactive --agree-tos \
-m "$EMAIL" -d "$DOMAIN"
CERT_FILE="/etc/letsencrypt/live/${DOMAIN}/fullchain.pem"
KEY_FILE="/etc/letsencrypt/live/${DOMAIN}/privkey.pem"
CHAIN_FILE="$CERT_FILE"
fi
chown -R mosquitto:mosquitto "$CERT_DIR" || true
chmod 600 "$KEY_FILE" || true
# ---- Write Mosquitto config ----
SECURE_CONF="${CONF_D}/secure.conf"
log "Writing Mosquitto config to ${SECURE_CONF} ..."
cat > "$SECURE_CONF" <<EOF
# ======= Generated by setup-mosquitto.sh =======
persistence true
persistence_location /var/lib/mosquitto/
log_timestamp true
log_type error
log_type warning
log_type notice
log_type information
# Global auth
allow_anonymous false
password_file ${PASS_FILE}
# Plain MQTT (LAN / testing). Comment out to disable.
listener 1883
protocol mqtt
# TLS listener for external access
listener 8883
protocol mqtt
certfile ${CERT_FILE}
keyfile ${KEY_FILE}
# Optional: tune keepalive/limits (uncomment if needed)
# max_inflight_messages 20
# max_queued_messages 1000
# autosave_interval 1800
EOF
# ---- Firewall rules ----
if command -v ufw >/dev/null 2>&1; then
if ufw status | grep -q "Status: active"; then
log "Opening ports 1883 and 8883 in ufw ..."
ufw allow 1883/tcp || true
ufw allow 8883/tcp || true
fi
elif command -v firewall-cmd >/dev/null 2>&1; then
if systemctl is-active --quiet firewalld; then
log "Opening ports 1883 and 8883 in firewalld ..."
firewall-cmd --add-port=1883/tcp --permanent || true
firewall-cmd --add-port=8883/tcp --permanent || true
firewall-cmd --reload || true
fi
else
warn "No ufw/firewalld detected; ensure 1883/8883 are open in your security group."
fi
# ---- Restart service ----
log "Restarting Mosquitto ..."
systemctl restart mosquitto
# ---- Show summary ----
echo
log "Done. Summary:"
echo " Broker (no TLS): mqtt://${DOMAIN}:1883"
echo " Broker (TLS): mqtts://${DOMAIN}:8883"
echo " User/Pass: ${USER_NAME} / (hidden)"
echo " Password file: ${PASS_FILE}"
echo " Config file: ${SECURE_CONF}"
if [[ "$MODE" == "selfsigned" ]]; then
echo " TLS (self-signed): ${CERT_FILE} / ${KEY_FILE}"
warn "Clients must trust the self-signed cert (or disable cert validation for testing)."
else
echo " TLS (LE): ${CERT_FILE} / ${KEY_FILE}"
warn "Certbot will NOT auto-reload mosquitto after renewal; consider a systemd timer hook."
fi
echo
log "Quick test (without TLS):"
echo " # Publish:"
echo " mosquitto_pub -h ${DOMAIN} -p 1883 -u ${USER_NAME} -P '<pass>' -t test -m hello"
echo " # Subscribe:"
echo " mosquitto_sub -h ${DOMAIN} -p 1883 -u ${USER_NAME} -P '<pass>' -t test -v"
echo
log "Quick test (with TLS):"
if [[ "$MODE" == "selfsigned" ]]; then
echo " mosquitto_sub -h ${DOMAIN} -p 8883 --capath /etc/ssl/certs -u ${USER_NAME} -P '<pass>' -t test -v --insecure"
echo " mosquitto_pub -h ${DOMAIN} -p 8883 --capath /etc/ssl/certs -u ${USER_NAME} -P '<pass>' -t test -m hello --insecure"
else
echo " mosquitto_sub -h ${DOMAIN} -p 8883 --capath /etc/ssl/certs -u ${USER_NAME} -P '<pass>' -t test -v"
echo " mosquitto_pub -h ${DOMAIN} -p 8883 --capath /etc/ssl/certs -u ${USER_NAME} -P '<pass>' -t test -m hello"
fi
# Optional hint for LE auto-reload
if [[ "$MODE" == "letsencrypt" ]]; then
echo
warn "Tip: add a certbot deploy hook to reload mosquitto after renewal:"
echo ' echo -e "#!/bin/sh\nsystemctl reload mosquitto" | tee /etc/letsencrypt/renewal-hooks/deploy/mosquitto-reload.sh'
echo ' chmod +x /etc/letsencrypt/renewal-hooks/deploy/mosquitto-reload.sh'
fi
chmod +x setup-mosquitto.sh
sudo ./setup-mosquitto.sh -u mqttuser -p mqttpass --selfsigned -d mqtt.example.com
# 或者用 Let’s Encrypt:
# sudo ./setup-mosquitto.sh -u mqttuser -p mqttpass --letsencrypt -d mqtt.example.com -m you@example.com
報(bào)錯(cuò)是:Duplicate persistence_location。
說明全局項(xiàng) persistence / persistence_location 在兩個(gè)文件里都寫了(主文件 mosquitto.conf 和我們寫的 conf.d/secure.conf),Mosquitto 不允許重復(fù)定義。
按下面修:
0x01) 去掉 secure.conf 里的全局持久化兩行
sudo sed -i 's/^\s*persistence\s\+.*/# &/' /etc/mosquitto/conf.d/secure.conf
sudo sed -i 's/^\s*persistence_location\s\+.*/# &/' /etc/mosquitto/conf.d/secure.conf
0x02)(可選保險(xiǎn))如果主配置里開了 port 1883,避免與 listener 1883 沖突
sudo sed -i 's/^\s*port\s\+1883/# port 1883/' /etc/mosquitto/mosquitto.conf
sudo sed -i 's/^\s*listener\s\+1883/# listener 1883/' /etc/mosquitto/mosquitto.conf
sudo sed -i 's/^\s*listener\s\+8883/# listener 8883/' /etc/mosquitto/mosquitto.conf
0x03) 重啟并看狀態(tài)
sudo systemctl restart mosquitto
sudo systemctl status mosquitto -l --no-pager
0x04) 快速驗(yàn)證
無 TLS
mosquitto_sub -h <你的域名或IP> -p 1883 -u <user> -P '<pass>' -t test -v &
mosquitto_pub -h <你的域名或IP> -p 1883 -u <user> -P '<pass>' -t test -m hello
電腦命令行測(cè)試(局域網(wǎng))
訂閱狀態(tài)
mosquitto_sub -h www.taotao01.fun -p 1883 -u mqttuser -P mqttpass -t home/esp32-led-01/state -v
控制 LED
# 開燈
mosquitto_pub -h www.taotao01.fun -p 1883 -u mqttuser -P mqttpass -t home/esp32-led-01/cmd -m ON
# 關(guān)燈
mosquitto_pub -h www.taotao01.fun -p 1883 -u mqttuser -P mqttpass -t home/esp32-led-01/cmd -m OFF
立刻停用 & 取消開機(jī)自啟
sudo systemctl stop mosquitto
sudo systemctl disable mosquitto
sudo systemctl status mosquitto --no-pager
關(guān)閉防火墻放行(按你系統(tǒng)其一執(zhí)行)
UFW:
sudo ufw delete allow 1883/tcp || true
sudo ufw delete allow 8883/tcp || true
firewalld:
sudo firewall-cmd --remove-port=1883/tcp --permanent || true
sudo firewall-cmd --remove-port=8883/tcp --permanent || true
sudo firewall-cmd --reload || true
停用配置(不刪,方便以后恢復(fù))
sudo mv /etc/mosquitto/conf.d/secure.conf /etc/mosquitto/conf.d/secure.conf.disabled
# 可選:清理賬號(hào)文件
# sudo rm -f /etc/mosquitto/passwd
檢查端口是否已關(guān)閉
sudo ss -ltnp | grep -E ':(1883|8883)' || echo "1883/8883 已關(guān)閉"
以后想再用
sudo mv /etc/mosquitto/conf.d/secure.conf.disabled /etc/mosquitto/conf.d/secure.conf
sudo systemctl enable mosquitto
sudo systemctl start mosquitto
想徹底卸載(可選)
sudo apt-get purge -y mosquitto mosquitto-clients
sudo rm -rf /etc/mosquitto /var/lib/mosquitto /var/log/mosquitto
# 如果之前用了 Let's Encrypt,還可:
# sudo certbot delete --cert-name www.taotao01.fun

浙公網(wǎng)安備 33010602011771號(hào)