FastBond2阶段2-基于220V市电的物联网控制器
通过ESP32C3制作一个220V继电器模块,实现接入家庭物联网中枢home assistant并控制家用电器
标签
嵌入式系统
开发板
接口
PCB设计
FastBond第二季
饺子sama
更新2023-08-31
宁波大学
447
  • 项目介绍+设计思路

网上常见的智能家居相关项目都是在低电压下工作,而对于市电控制的资料相对较少。因此在这次项目中我打算好好研究一下继电器的设计与使用,毕竟关乎安全。计划通过ESP32C3制作一个220V继电器模块,实现接入家庭物联网中枢home assistant并控制家用电器。

整个项目一共由三个部分构成,上位机由Linux开发板组成,在上面搭建docker环境,并运行homeassistant容器,作为整个家庭物联网中心。

第二部分是MQTT通信。目前homeassistant支持的通信协议非常多,这次选择MQTT,可以实现更大的兼容性,方便在不同平台上进行移植。

第三部分是控制节点。也就是我们的ESP32-C3继电器。这次项目代码使用Arduino进行编写,Arduino的库非常庞大,可以很方便的快速实现功能。

 

  • 原理图解释

项目所有的PCB相关文件,都是使用KiCAD进行的绘制。这个原理图基本属于一个ESP32-C3最小系统,仅包含了能让系统运作的最基本部分。除了上面已经讲过的供电部分之外,需要注意的就是要额外添加两个按钮,一个是boot引脚,另一个是RESET引脚,来帮助我们进行固件的烧录。不添加这两个按钮的话,烧录会变得十分麻烦。还有一点就是,记得每一部分电路的供电处都要添加旁路电容,以确保电路工作稳定。

Fk9F7rs3Wei_sGgQMNpmJozdYgi0

 

接下来我们看看继电器部分的设计:

FsgqocbKe331yAuyhEpqUMNPSO5Z

继电器部分用了一块单独的PCB,与ESP32-C3部分分开。因为市电220V电压比较高,距离过近可能会出现爬电,造成烧坏设备及触电的风险。在这里我是用一个NPN三极管来控制继电器的开合,并加入一个发光二极管来作为继电器工作状态的指示灯。在这个设计中,最关键的元件是D1这个二极管,由于继电器控制部分的内部是一个线圈,所以它在通断时会产生较高的尖峰电压,我们需要通过续流二极管来消除这个尖峰,否则电压一旦过大,很容易击穿我们接在下面的三极管。

 

  • 方案中可能用到的规定厂商元器件介绍

乐鑫公司的ESP32-C3是一个低功耗的MCU,内置了WIFI和BLE,可以很方便的搭建物联网应用。最关键的是,价格十分低廉,各位创客同学可以尽情尝试。

方案中使用ESP32-S3-MINI模组,使用模组可以大大简化外围电路的设计,同时手工焊接模组难度也比焊接芯片要小,在该项目中是理想的选择。

 

  • PCB绘制打板介绍及遇到的问题和解决方法

根据原理图画好的PCB如下:

FvYN27kFttBBGV6-GmRRs5tEnci4Fl3KHlHtDuSD_TBsHaWMBACAgXSy

新鲜出炉的PCB是这样的:

FmfhRybPVzl4ubg2HaowMj23mF9IFnBUInsUo0DNsPuz_zmrjJZvJhBx

可以看到由于是喷锡处理的焊盘,焊盘表面并不平整。这对于ESP32-C3模块的焊接增加了一定的难度,因为模块焊盘全部都在下面,并没有额外露出的部分,焊接必须得一次成型,没有烙铁修补的机会。我焊接的方法是先给焊盘上锡,尽量均匀让锡面等高,然后再把模块放上去,热风吹焊。

  • 关键代码及说明

上位机中dockers环境的部署,Home Assistant和Mosquito服务器的部署由于和本次项目相关性较小,网上也有很多资料,在这里就不再赘述了。如果未来有机会我会单独开贴讲解(又是一个大坑)。在这个项目中我们主要来看一下MQTT客户端的代码:

项目中用到的库并不多,只有这四个。

#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

首先是连接网络,连接MQTT服务器,并配置好回调函数。这一部分只是一系列的定义及配置,比较简单:

void wifi_loop()
{
  if (WiFi.status() != WL_CONNECTED)
  {
    WiFi.begin(ssid.c_str(), password.c_str());
    while (WiFi.status() != WL_CONNECTED)
    {
      yield();
    }
  }
}

// MQTT client connect
boolean mqtt_loop()
{
  if (WiFi.status() != WL_CONNECTED)
  {
    return false;
  }
  if (!client.connected())
  {
    // init the MQTT setup
    client.setServer(MQTT_SERVER_IP, MQTT_SERVER_PORT);
    client.setCallback(mqtt_callback);
    // Attempt to connect
    const String availability_topic = String(MQTT_USER) + "/" + String(MQTT_CLIENT_ID) + "/availability";
    if (client.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASSWORD, availability_topic.c_str(), 1, true, "offline", true))
    {
      boolean isOK = client.publish(availability_topic.c_str(), "online", true);
      discover_0();
      subscribe_0();
    }
    else
    {
      delay(1000);
      return false;
    }
  }
  client.loop(); // update message fromm MQTT
  return true;
}

为了实现可以让homeassistant可以自动识别我们的终端设备,首先需要publish相应的配置信息给服务器:

void discover_0()
{
  // setup
  pinMode(DEVICE_0_PIN, OUTPUT);
  digitalWrite(DEVICE_0_PIN, LOW);

  const String components = DEVICE_0_COMPONENT;
  const String name = DEVICE_0_NAME;
  const String config_topic = String(MQTT_USER) + "/" + components + "/" + String(MQTT_CLIENT_ID) + "_" + name + "/config";
  const String availability_topic = String(MQTT_USER) + "/" + String(MQTT_CLIENT_ID) + "/availability";
  const String command_topic = String(MQTT_USER) + "/" + components + "/" + String(MQTT_CLIENT_ID) + "_" + name + "/set";
  const String state_topic = String(MQTT_USER) + "/" + components + "/" + String(MQTT_CLIENT_ID) + "_" + name + "/state";
  StaticJsonDocument<512> doc;

  doc["name"] = String(MQTT_CLIENT_ID) + "_" + name;
  doc["unique_id"] = String(MQTT_CLIENT_ID) + "_" + name;
  doc["command_topic"] = command_topic;
  doc["state_topic"] = state_topic;
  doc["availability_topic"] = availability_topic;
  doc["schema"] = "json";
  doc["optimistic"] = false;
  doc["retain"] = false;
  doc["qos"] = 0;
  doc["brightness"] = true;

  unsigned char buffer[512];
  unsigned int n = serializeJson(doc, buffer);
  client.setBufferSize(512);
  boolean isOK = client.publish(config_topic.c_str(), buffer, n, false);
}

之后就是定义MQTT的订阅及发布功能,并将对应功能放在CALLBACK中,这样当ESP32-C3接收到服务器的信息后,就会执行相应操作,并返回执行结果。

void subscribe_0()
{
  const String components = DEVICE_0_COMPONENT;
  const String name = DEVICE_0_NAME;
  const String command_topic = String(MQTT_USER) + "/" + components + "/" + String(MQTT_CLIENT_ID) + "_" + name + "/set";
  client.subscribe(command_topic.c_str(), 0);
}

void publish_0()
{
  const String components = DEVICE_0_COMPONENT;
  const String name = DEVICE_0_NAME;
  const String state_topic = String(MQTT_USER) + "/" + components + "/" + String(MQTT_CLIENT_ID) + "_" + name + "/state";
  StaticJsonDocument<128> doc;

  if (DEVICE_0_TGT_VALUE)
  {
    doc["state"] = DEVICE_ON;
    doc["brightness"] = DEVICE_0_SET_VALUE;
  }
  else
  {
    doc["state"] = DEVICE_OFF;
  }

  unsigned char buffer[128];
  unsigned int n = serializeJson(doc, buffer);
  boolean isOK = client.publish(state_topic.c_str(), buffer, n, false);
}

void process_0(const String topic_p, const String payload_p)
{
  const String components = DEVICE_0_COMPONENT;
  const String name = DEVICE_0_NAME;
  const String command_topic = String(MQTT_USER) + "/" + components + "/" + String(MQTT_CLIENT_ID) + "_" + name + "/set";
  if (command_topic == topic_p)
  {
    StaticJsonDocument<128> doc;
    deserializeJson(doc, payload_p);
    if (doc["state"] == DEVICE_OFF)
    {
      DEVICE_0_TGT_VALUE = 0;
    }
    else
    {
      if (!DEVICE_0_SET_VALUE)
      {
        DEVICE_0_TGT_VALUE = 255;
        DEVICE_0_SET_VALUE = DEVICE_0_TGT_VALUE;
      }
      else if (doc.containsKey("brightness"))
      {
        DEVICE_0_TGT_VALUE = doc["brightness"];
        DEVICE_0_SET_VALUE = DEVICE_0_TGT_VALUE;
      }
      else
      {
        DEVICE_0_TGT_VALUE = DEVICE_0_SET_VALUE;
      }
    }
    publish_0();
  }
}

void execute_0()
{
  analogWrite(DEVICE_0_PIN, DEVICE_0_TGT_VALUE);
}

void mqtt_callback(char *p_topic, byte *p_payload, unsigned int p_length)
{
  // concat the payload into a string
  const String topic = p_topic;
  p_payload[p_length] = '\0'; // Null terminator used to terminate the char array
  const String payload = (char *)p_payload;
  process_0(topic, payload);
}

完整代码在附件中,大家可以自行下载尝试。

  • 功能展示及说明

焊接好的成品如下:

FgVQFpb_5271H5tl2TDvN9d1QD4k

可以看到上面部分元件没有焊接。因为这些元件设计时是为了符合设计规范,但实际使用时并不需要。上图中的电阻位置和PCB文件中的略有差异,是因为我不小心在打板的时候搞错了启动引脚,因此实物通过飞线解决问题。上传的PCB已经修复了这个问题,可以直接使用。

下面是独立的继电器模块:

FnTqOjT5A5Sm73yYZsrpOxvmReNM

背面在所有的高压引脚处都增加了爬电槽,防止在潮湿环境中出现爬电短路。同时高压区还增加了警示标志:

FjOvC7GmGD2uOGIg7vxb0TjHrmH4

两个部分合体后,是长这个样子。高压区与控制区隔离,确保安全:

FjsJuHLYJDsZdiC61FTgKa81__J1

找个螺口灯座,再找根电源插座线,接到继电器里,整个成品就完成了。这里由于我只是做个示范,接线处并没有做绝缘处理。实际上这样操作非常危险,220V短路跟放鞭炮一样,能把一大片都炸的焦黑,大家可千万不要模仿。

FvqTYB-ZTuUW1-zfVggDnH6fMEEH

 

 

  • 对本大赛的心得体会

这次比赛让我有机会(动力)可以自己尝试一下从头设计一个日常生活中可能会有用的小电器,非常有意义,希望活动越办越好。

附件下载
C3继电器点灯.7z
团队介绍
个人
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号