mqtt+esp32公网控制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(可改成你的实际引脚)
  -D WIFI_SSID="你的WiFi"
  -D WIFI_PASS="你的WiFi密码"
  -D MQTT_HOST="你的外网MQTT域名或IP"
  -D MQTT_PORT=1883          ; 先用1883,跑通后可换8883
  -D MQTT_USER="你的mqtt用户名"
  -D MQTT_PASS="你的mqtt密码"
  -D DEVICE_ID="esp32-led-01"

main.cpp

点击查看代码
#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);
  // 上报状态(retained),方便手机端看到最新状态
  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; }

    // 简单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 设置:掉线时 broker 自动发 "offline"
  // 注意:必须 connect() 时一次性传入
  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.");
    // 上线标记
    mqttClient.publish(t_online.c_str(), "online", true);
    // 基础信息(retained)
    mqttClient.publish(t_ip.c_str(), WiFi.localIP().toString().c_str(), true);
    // 订阅控制主题
    mqttClient.subscribe(t_cmd.c_str());
    // 立即上报一次状态(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秒上报一次 RSSI,便于远程诊断
  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

点击查看代码
#!/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

报错是:Duplicate persistence_location。
说明全局项 persistence / persistence_location 在两个文件里都写了(主文件 mosquitto.conf 和我们写的 conf.d/secure.conf),Mosquitto 不允许重复定义。
按下面修:

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)(可选保险)如果主配置里开了 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) 重启并看状态

sudo systemctl restart mosquitto
sudo systemctl status mosquitto -l --no-pager

0x04) 快速验证

无 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

电脑命令行测试(局域网)

订阅状态

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
# 关灯
mosquitto_pub -h www.taotao01.fun -p 1883 -u mqttuser -P mqttpass -t home/esp32-led-01/cmd -m OFF

立刻停用 & 取消开机自启

sudo systemctl stop mosquitto
sudo systemctl disable mosquitto
sudo systemctl status mosquitto --no-pager

关闭防火墙放行(按你系统其一执行)

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

停用配置(不删,方便以后恢复)

sudo mv /etc/mosquitto/conf.d/secure.conf /etc/mosquitto/conf.d/secure.conf.disabled
# 可选:清理账号文件
# sudo rm -f /etc/mosquitto/passwd

检查端口是否已关闭

sudo ss -ltnp | grep -E ':(1883|8883)' || echo "1883/8883 已关闭"

以后想再用

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
posted @ 2025-08-12 13:50  huh&uh  阅读(23)  评论(0)    收藏  举报