MySensors

MySensors 项目结合了 Arduino、ESP8266、Raspberry Pi、NRF24L01+ 和 RFM69 等设备,以构建经济实惠的传感器网络。此集成将在完成 展示 后自动将所有可用设备添加到 Home Assistant 中。也就是说,您无需在配置中添加任何内容即可添加设备。请前往开发者工具的 状态 部分查找已识别的设备。

配置

要将 MySensors integration 添加到您的 Home Assistant 实例中,请使用此 My 按钮:

手动配置步骤

如果上述 My 按钮不起作用,您也可以手动执行以下步骤:

配置取决于您使用的网关类型:

串行网关

如果您使用原始 Arduino 作为串行网关,则端口名称将为 ttyACM*。可以使用下面显示的命令确定确切的号码。

ls /dev/ttyACM*

除了串行设备外,您还需要输入波特率。

MQTT 网关

如果您使用 MQTT 网关,则需要输入输入和输出的主题前缀。这些需要与网关的设置进行互换。即,Home Assistant 的输入主题需要是网关的输出(发布)主题。

Note

MQTT 网关需要 MySensors 版本 2.0+ 并且仅支持 MQTT 客户端网关。

以太网网关

要使用以太网网关,您需要配置网关的 IP 地址和端口。

常规选项

所有网关都提供一些选项:

  • 持久化文件: Home Assistant 将在文件中存储检测到的节点。这意味着重启 Home Assistant 不需要重新发现所有节点。持久化文件选项允许设置文件的路径。如将选项留空,Home Assistant 将在配置目录中自动生成文件名。

  • 版本: 输入您用于网关的 MySensors 版本。

展示

通过以下步骤展示 MySensors 传感器或执行器:

输入您使用的 MySensors 版本

  1. 启动 Home Assistant 并配置 MySensors 集成
  2. 编写并上传您的 MySensors 草图到传感器。确保您:
    • 发送草图名称。
    • 展示传感器的 S_TYPE
    • 每个 V_TYPE 发送至少一个初始值。在 MySensors 版本 2.x 中,必须在循环函数中完成此操作。请参见下面的示例,以确保控制器已收到初始值。
  3. 启动传感器。
/*
 * 文档: https://www.mysensors.org
 * 支持论坛: https://forum.mysensors.org
 *
 * https://www.mysensors.org/build/relay
 */

#define MY_DEBUG
#define MY_RADIO_NRF24
#define MY_REPEATER_FEATURE
#define MY_NODE_ID 1
#include <SPI.h>
#include <MySensors.h>
#include <Bounce2.h>

#define RELAY_PIN  5
#define BUTTON_PIN  3
#define CHILD_ID 1
#define RELAY_ON 1
#define RELAY_OFF 0

Bounce debouncer = Bounce();
bool state = false;
bool initialValueSent = false;

MyMessage msg(CHILD_ID, V_STATUS);

void setup()
{
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  debouncer.attach(BUTTON_PIN);
  debouncer.interval(10);

  // 启动时确保继电器是关闭的
  digitalWrite(RELAY_PIN, RELAY_OFF);
  pinMode(RELAY_PIN, OUTPUT);
}

void presentation()  {
  sendSketchInfo("Relay+button", "1.0");
  present(CHILD_ID, S_BINARY);
}

void loop()
{
  if (!initialValueSent) {
    Serial.println("发送初始值");
    send(msg.set(state?RELAY_ON:RELAY_OFF));
    Serial.println("请求控制器的初始值");
    request(CHILD_ID, V_STATUS);
    wait(2000, C_SET, V_STATUS);
  }
  if (debouncer.update()) {
    if (debouncer.read()==LOW) {
      state = !state;
      // 发送新状态并请求确认
      send(msg.set(state?RELAY_ON:RELAY_OFF), true);
    }
  }
}

void receive(const MyMessage &message) {
  if (message.isAck()) {
     Serial.println("这是来自网关的确认");
  }

  if (message.type == V_STATUS) {
    if (!initialValueSent) {
      Serial.println("接收到来自控制器的初始值");
      initialValueSent = true;
    }
    // 更改继电器状态
    state = (bool)message.getInt();
    digitalWrite(RELAY_PIN, state?RELAY_ON:RELAY_OFF);
    send(msg.set(state?RELAY_ON:RELAY_OFF));
  }
}

SmartSleep

从 MySensors 设备向 Home Assistant 发送一个心跳 I_HEARTBEAT_RESPONSE,使用 MySensors 版本 2.0 - 2.1,将激活 Home Assistant 中的 SmartSleep 功能。这意味着消息会被缓冲,并且仅在接收到来自设备的心跳时才会发送状态更改。状态更改会被存储,以便仅将最后请求的状态更改发送到设备。其他类型的消息将在 FIFO 队列中排队。SmartSleep 对于等待命令的电池供电执行器非常有用。有关如何发送心跳和使设备进入睡眠状态的信息,请参见 MySensors 库 API。

在 MySensors 版本 2.2 中,串行 API 从使用 I_HEARTBEAT_RESPONSE 切换到使用 I_PRE_SLEEP_NOTIFICATIONI_POST_SLEEP_NOTIFICATION 来信号通知 SmartSleep。Home Assistant 已被升级以支持新的消息类型,当接收到 I_PRE_SLEEP_NOTIFICATION 类型的消息时,如使用 MySensors 版本 2.2.x 或更高版本时,将激活 SmartSleep。如果 Home Assistant 配置为使用 MySensors 版本 2.0 - 2.1,则保留旧的 SmartSleep 行为。

消息验证

从或向 MySensors 设备发送到 Home Assistant 的消息将根据 MySensors 串行 API 进行验证。如果消息未通过验证,将被丢弃,并不会转发到 Home Assistant。确保在编写 Arduino 草图时遵循您所用 MySensors 版本的串行 API。

日志应当警告您验证失败的消息,或缺少特定子类型所需的子值。如果某个平台接收到一个所需值类型,但其他所需值类型缺失,Home Assistant 将以警告级别记录子值的验证失败。

消息验证是在 Home Assistant 版本 0.52 中引入的。

调试日志

如果您遇到消息丢失或设备未添加到 Home Assistant 的情况,请为 mysensors 集成和 mysensors 包开启调试日志。这将帮助您了解发生了什么。如果您在我们的 GitHub 问题追踪器中报告有关 mysensors 集成的问题,请确保使用以下日志设置收集日志样本。

logger:
  default: info
  logs:
    homeassistant.components.mysensors: debug
    mysensors: debug

访问 MySensors 库 API 以获取更多信息。

二进制传感器

支持以下二进制传感器类型:

MySensors 版本 1.4 及更高版本

S_TYPE V_TYPE
S_DOOR V_TRIPPED
S_MOTION V_TRIPPED
S_SMOKE V_TRIPPED

MySensors 版本 1.5 及更高版本

S_TYPE V_TYPE
S_SPRINKLER V_TRIPPED
S_WATER_LEAK V_TRIPPED
S_SOUND V_TRIPPED
S_VIBRATION V_TRIPPED
S_MOISTURE V_TRIPPED

二进制传感器示例草图

/**
 * 文档: https://www.mysensors.org
 * 支持论坛: https://forum.mysensors.org
 *
 * https://www.mysensors.org/build/binary
 */


#include <MySensor.h>
#include <SPI.h>
#include <Bounce2.h>

#define SN "BinarySensor"
#define SV "1.0"
#define CHILD_ID 1
#define BUTTON_PIN 3  // Arduino 数字 I/O 引脚用于按钮/磁簧开关。

MySensor gw;
Bounce debouncer = Bounce();
MyMessage msg(CHILD_ID, V_TRIPPED);

void setup()
{
  gw.begin();
  gw.sendSketchInfo(SN, SV);
  // 设置按钮。
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  // 设置按钮后,设置去抖器。
  debouncer.attach(BUTTON_PIN);
  debouncer.interval(5);
  gw.present(CHILD_ID, S_DOOR);
  gw.send(msg.set(0));
}

void loop()
{
  if (debouncer.update()) {
    // 获取更新值。
    int value = debouncer.read();
    // 发送新值。
    gw.send(msg.set(value == LOW ? 1 : 0));
  }
}

气候

支持以下执行器类型:

MySensors 版本 1.5 及更高版本

S_TYPE V_TYPE
S_HVAC V_HVAC_FLOW_STATE*, V_HVAC_SETPOINT_HEAT, V_HVAC_SETPOINT_COOL, V_HVAC_SPEED, V_TEMP

V_HVAC_FLOW_STATE 被映射为 Home Assistant 中气候集成的状态,如下所示:

Home Assistant 状态 MySensors 状态
HVAC_MODE_COOL CoolOn
HVAC_MODE_HEAT HeatOn
HVAC_MODE_AUTO AutoChangeOver
HVAC_MODE_OFF Off

当前湿度、away_mode、aux_heat、swing_mode 不受支持。这将在后续版本中包含。

在制热模式中使用 V_HVAC_SETPOINT_HEAT 设置目标温度,在制冷模式中使用 V_HVAC_SETPOINT_COOL。在任何自动换季模式下,您可以使用 V_HVAC_SETPOINT_HEAT 和 V_HVAC_SETPOINT_COOL 设置设备的低值和高值。

您可以使用 V_HVAC_SPEED 控制 HVAC 中风扇的速度设置。

您可以使用 V_TEMP 将当前温度从节点发送到 Home Assistant。

MySensors 2.x 的气候示例草图

/*
 * 文档: https://www.mysensors.org
 * 支持论坛: https://forum.mysensors.org
 */

// 启用调试打印到串行监视器
#define MY_DEBUG
#define MY_RADIO_NRF24

#include <MySensors.h> // 测试版本 2.3.2

#define SENSOR_NAME "Heatpump Sensor"
#define SENSOR_VERSION "2.3"

#define CHILD_ID_HVAC 0

#define IR_PIN  3  // Arduino 引脚连接到 IR LED,使用 PWM


// 取消注释您的热泵型号
//#include <FujitsuHeatpumpIR.h>
//#include <PanasonicCKPHeatpumpIR.h>
//#include <PanasonicHeatpumpIR.h>
//#include <CarrierHeatpumpIR.h>
//#include <MideaHeatpumpIR.h>
//#include <MitsubishiHeatpumpIR.h>
//#include <SamsungHeatpumpIR.h> // 测试版本 1.0.15,见 http://librarymanager#HeatpumpIR by Toni Arte
//#include <SharpHeatpumpIR.h>
//#include <DaikinHeatpumpIR.h>

//一些全局变量用于保存发送到空调单元的数值状态
int POWER_STATE;
int TEMP_STATE;
int FAN_STATE;
int MODE_STATE;
int VDIR_STATE;
int HDIR_STATE;

// 一些全局变量用于保存发送到 Home Assistant 控制器的文本状态
String FAN_STATE_TXT;  // 可能值(“Min”,“Normal”,“Max”,“Auto”)
String MODE_STATE_TXT; // 可能值(“Off”,“HeatOn”,“CoolOn”或“AutoChangeOver”)


IRSenderPWM irSender(IR_PIN);

// 更改为您自己的热泵
//HeatpumpIR *heatpumpIR = new SamsungAQV12MSANHeatpumpIR();
/*
new PanasonicDKEHeatpumpIR()
new PanasonicJKEHeatpumpIR()
new PanasonicNKEHeatpumpIR()
new CarrierHeatpumpIR()
new MideaHeatpumpIR()
new FujitsuHeatpumpIR()
new MitsubishiFDHeatpumpIR()
new MitsubishiFEHeatpumpIR()
new SamsungAQVHeatpumpIR()
new SamsungFJMHeatpumpIR()
// new SamsungHeatpumpIR() 是一个受保护的通用方法,无法直接创建
new SharpHeatpumpIR()
new DaikinHeatpumpIR()
*/

MyMessage msgHVACSetPointC(CHILD_ID_HVAC, V_HVAC_SETPOINT_COOL);
MyMessage msgHVACSpeed(CHILD_ID_HVAC, V_HVAC_SPEED);
MyMessage msgHVACFlowState(CHILD_ID_HVAC, V_HVAC_FLOW_STATE);

bool initialValueSent = false;

void presentation() {
  // 向网关和控制器发送草图版本信息
  sendSketchInfo(SENSOR_NAME, SENSOR_VERSION);

  // 按照其 ID 和 S_TYPE 将所有传感器注册到网关(它们将作为子设备创建)
  present(CHILD_ID_HVAC, S_HVAC, "Thermostat");
}

void setup() {
}

void loop() {
  // 在此处放置您的主代码,以便重复运行:
  if (!initialValueSent) {
    FAN_STATE_TXT = "Auto"; // 默认风扇启动状态
    TEMP_STATE = 20; // 默认开始温度
    MODE_STATE_TXT = "Off"; // 默认模式状态

    send(msgHVACSetPointC.set(TEMP_STATE));
    send(msgHVACSpeed.set(FAN_STATE_TXT.c_str()));
    send(msgHVACFlowState.set(MODE_STATE_TXT.c_str()));

    initialValueSent = true;
  }
}

void receive(const MyMessage &message) {
  if (message.isAck()) {
     Serial.println("这是来自网关的确认");
     return;
  }

  Serial.print("接收到的消息: ");
  Serial.print(message.sensor);

  String recvData = message.data;
  recvData.trim();

  Serial.print(", 新状态: ");
  Serial.println(recvData);
  switch (message.type) {
    case V_HVAC_SPEED:
      Serial.println("V_HVAC_SPEED");

      if(recvData.equalsIgnoreCase("auto")) FAN_STATE = 0;
      else if(recvData.equalsIgnoreCase("min")) FAN_STATE = 1;
      else if(recvData.equalsIgnoreCase("normal")) FAN_STATE = 2;
      else if(recvData.equalsIgnoreCase("max")) FAN_STATE = 3;
      FAN_STATE_TXT = recvData;
    break;

    case V_HVAC_SETPOINT_COOL:
      Serial.println("V_HVAC_SETPOINT_COOL");
      TEMP_STATE = message.getFloat();
      Serial.println(TEMP_STATE);
    break;

    case V_HVAC_FLOW_STATE:
      Serial.println("V_HVAC_FLOW_STATE");
      if (recvData.equalsIgnoreCase("coolon")) {
        POWER_STATE = 1;
        MODE_STATE = MODE_COOL;
      }
      else if (recvData.equalsIgnoreCase("heaton")) {
        POWER_STATE = 1;
        MODE_STATE = MODE_HEAT;
      }
      else if (recvData.equalsIgnoreCase("autochangeover")) {
        POWER_STATE = 1;
        MODE_STATE = MODE_AUTO;
      }
      else if (recvData.equalsIgnoreCase("off")){
        POWER_STATE = 0;
      }
      MODE_STATE_TXT = recvData;
      break;
  }
  sendHeatpumpCommand();
  sendNewStateToGateway();
}

void sendNewStateToGateway() {
  Serial.println("状态更新发送到 HA:");
  Serial.println("*************************");
  Serial.println("模式 = " + MODE_STATE_TXT + "(" + (String)MODE_STATE + ")");
  Serial.println("风扇 = " + FAN_STATE_TXT + "(" + (String)FAN_STATE + ")");
  Serial.println("温度 = " + (String)TEMP_STATE);
  send(msgHVACFlowState.set(MODE_STATE_TXT.c_str()));
  send(msgHVACSpeed.set(FAN_STATE_TXT.c_str()));
  send(msgHVACSetPointC.set(TEMP_STATE));
}

void sendHeatpumpCommand() {
  Serial.println("热泵命令发送到空调:");
  Serial.println("********************************");
  Serial.println("电源 = " + (String)POWER_STATE);
  Serial.println("模式 = " + (String)MODE_STATE);
  Serial.println("风扇 = " + (String)FAN_STATE);
  Serial.println("温度 = " + (String)TEMP_STATE);

  heatpumpIR->send(irSender, POWER_STATE, MODE_STATE, FAN_STATE, TEMP_STATE, VDIR_AUTO, HDIR_AUTO);
}

遮罩

支持以下执行器类型:

MySensors 版本 1.4

S_TYPE V_TYPE
S_COVER V_UP, V_DOWN, V_STOP, [V_DIMMER 或 V_LIGHT]

MySensors 版本 1.5 及更高版本

S_TYPE V_TYPE
S_COVER V_UP, V_DOWN, V_STOP, [V_PERCENTAGE 或 V_STATUS]

以上所有 V_TYPES 都是必需的。如果您知道遮罩的确切位置,请使用 V_PERCENTAGE(或 V_DIMMER),如果您不知道,请使用 V_STATUS(或 V_LIGHT)。

遮罩示例草图

/*
 * 文档: https://www.mysensors.org
 * 支持论坛: https://forum.mysensors.org
 */

// 启用调试打印到串行监视器
#define MY_DEBUG
#define MY_RADIO_NRF24

#include <MySensors.h>

#define SN "Cover"
#define SV "1.1"

// 用于上下移动遮罩的执行器。
#define COVER_UP_ACTUATOR_PIN 2
#define COVER_DOWN_ACTUATOR_PIN 3
// 用于查找遮罩何时达到其上/下位置的传感器。
// 这些可以是简单的按钮或线性霍尔传感器。
#define COVER_UP_SENSOR_PIN 4
#define COVER_DOWN_SENSOR_PIN 5

#define CHILD_ID 0

// 遮罩状态的内部表示。
enum State {
  IDLE,
  UP, // 窗户遮盖。向上。
  DOWN, // 窗户遮盖。向下。
};

static int state = IDLE;
static int status = 0; // 0=遮罩在下,1=遮罩在上
static bool initial_state_sent = false;
MyMessage upMessage(CHILD_ID, V_UP);
MyMessage downMessage(CHILD_ID, V_DOWN);
MyMessage stopMessage(CHILD_ID, V_STOP);
MyMessage statusMessage(CHILD_ID, V_STATUS);

void sendState() {
  // 将当前状态和状态发送到网关。
  send(upMessage.set(state == UP));
  send(downMessage.set(state == DOWN));
  send(stopMessage.set(state == IDLE));
  send(statusMessage.set(status));
}

void setup() {
  pinMode(COVER_UP_SENSOR_PIN, INPUT);
  pinMode(COVER_DOWN_SENSOR_PIN, INPUT);
}

void presentation() {
  sendSketchInfo(SN, SV);

  present(CHILD_ID, S_COVER);
}

void loop() {
  if (!initial_state_sent) {
    sendState();
    initial_state_sent = true;
  }

  if (state == IDLE) {
    digitalWrite(COVER_UP_ACTUATOR_PIN, LOW);
    digitalWrite(COVER_DOWN_ACTUATOR_PIN, LOW);
  }

  if (state == UP && digitalRead(COVER_UP_SENSOR_PIN) == HIGH) {
    Serial.println("遮罩向上。");
    // 更新状态并状态;发送到网关。
    status = 1;
    state = IDLE;
    sendState();
    // 执行器将在下一个 loop() 迭代中被禁用。
  }

  if (state == DOWN && digitalRead(COVER_DOWN_SENSOR_PIN) == HIGH) {
    Serial.println("遮罩向下。");
    // 更新状态并状态;发送到网关。
    status = 0;
    state = IDLE;
    sendState();
    // 执行器将在下一个 loop() 迭代中被禁用。
  }
}

void receive(const MyMessage &message) {
  if (message.type == V_UP) {
    // 设置状态为遮罩向上并发送回网关。
    state = UP;
    sendState();
    Serial.println("正在移动遮罩向上。");

    // 激活执行器,直到传感器在 loop() 中返回 HIGH。
    digitalWrite(COVER_UP_ACTUATOR_PIN, HIGH);
  }

  if (message.type == V_DOWN) {
    // 设置状态为遮罩向下并发送到网关。
    state = DOWN;
    sendState();
    Serial.println("正在移动遮罩向下。");
    // 激活执行器,直到传感器在 loop() 中返回 HIGH。
    digitalWrite(COVER_DOWN_ACTUATOR_PIN, HIGH);
  }

  if (message.type == V_STOP) {
    // 设置状态为闲置并发送回网关。
    state = IDLE;
    sendState();
    Serial.println("停止遮罩。");

    // 执行器将在 loop() 中关闭。
  }
}

基于电机运行时间进行位置测量的遮罩示例草图

此草图理想用于星形拓扑布线。您可以使用一个 Arduino Mega 板和一些继电器运行多达 12 个遮罩。您只需为一个遮罩设置一行参数。不过,您也可以在基于 Arduino Nano 或 ESP8266 板的单个遮罩上使用它。

查看 GitHub 上的代码。

设备追踪器

支持以下传感器类型:

MySensors 版本 2.0 及更高版本

S_TYPE V_TYPE
S_GPS V_POSITION

MySensors 2.x 的设备追踪器示例草图

/**
 * 文档: https://www.mysensors.org
 * 支持论坛: https://forum.mysensors.org
 *
 * https://www.mysensors.org/build/gps
 */

// 启用调试打印到串行监视器
#define MY_DEBUG
#define MY_RADIO_NRF24

#include <MySensors.h>

#define SN "GPS Sensor"
#define SV "1.0"

// GPS 位置发送间隔(以毫秒为单位)
#define GPS_SEND_INTERVAL 30000
// 用于 GPS 传感器的儿童 ID
#define CHILD_ID_GPS 1

MyMessage msg(CHILD_ID_GPS, V_POSITION);

// 上次 GPS 位置发送到控制器的时间
unsigned long lastGPSSent = -31000;

// 一些缓存
char latBuf[11];
char lngBuf[11];
char altBuf[6];
char payload[30];

// 虚拟值。真实 GPS 设备的实现尚未完成。
float gpsLocationLat = 40.741895;
float gpsLocationLng = -73.989308;
float gpsAltitudeMeters = 12.0;

void setup() {

}

void presentation() {
  sendSketchInfo(SN, SV);
  present(CHILD_ID_GPS, S_GPS);
}

void loop()
{
  unsigned long currentTime = millis();

  // 评估是否是发送新位置的时间
  bool timeToSend = currentTime - lastGPSSent > GPS_SEND_INTERVAL;

  if (timeToSend) {
    // 发送当前 GPS 位置
    // 构建要发送的位置和高度字符串
    dtostrf(gpsLocationLat, 1, 6, latBuf);
    dtostrf(gpsLocationLng, 1, 6, lngBuf);
    dtostrf(gpsAltitudeMeters, 1, 0, altBuf);
    sprintf(payload, "%s,%s,%s", latBuf, lngBuf, altBuf);

    Serial.print(F("位置: "));
    Serial.println(payload);

    send(msg.set(payload));
    lastGPSSent = currentTime;
  }
}

灯光

支持以下执行器类型:

MySensors 版本 1.4

S_TYPE V_TYPE
S_DIMMER V_DIMMER*, V_LIGHT*

MySensors 版本 1.5 及更高版本

S_TYPE V_TYPE
S_DIMMER [V_DIMMER* 或 V_PERCENTAGE*], [V_LIGHT* 或 V_STATUS*]
S_RGB_LIGHT V_RGB*, [V_LIGHT* 或 V_STATUS*], [V_DIMMER 或 V_PERCENTAGE]
S_RGBW_LIGHT V_RGBW*, [V_LIGHT* 或 V_STATUS*], [V_DIMMER 或 V_PERCENTAGE]

带星号(*)的 V_TYPES 表示应在草图启动时发送的 V_TYPES。对于 S_DIMMER,发送 V_DIMMER/V_PERCENTAGE 和 V_LIGHT/V_STATUS 消息。对于 S_RGB_LIGHT,发送 V_RGB 和 V_LIGHT/V_STATUS 消息,V_DIMMER/V_PERCENTAGE 消息可选。相同的原则适用于 S_RGBW_LIGHT 和 V_RGBW。

草图应该以相同类型确认来自控制器的命令。如果命令引发关闭状态更改(包括 V_PERCENTAGE,V_RGB 或 V_RGBW 消息值为零),则应该仅发送 V_STATUS 为零的消息。请参见下面的草图示例。

MySensors 2.x 的灯光示例草图

/*
 * 示例可调光灯
 * 代码来自 https://github.com/mysensors/MySensors/tree/master/examples/DimmableLight
 *
 * 文档: https://www.mysensors.org
 * 支持论坛: https://forum.mysensors.org
 *
 */

// 启用调试打印
#define MY_DEBUG
#define MY_RADIO_NRF24

#include <MySensors.h>

#define CHILD_ID_LIGHT 1

#define LIGHT_OFF 0
#define LIGHT_ON 1

#define SN "Dimmable Light"
#define SV "1.0"

int16_t last_state = LIGHT_ON;
int16_t last_dim = 100;

MyMessage light_msg( CHILD_ID_LIGHT, V_STATUS );
MyMessage dimmer_msg( CHILD_ID_LIGHT, V_PERCENTAGE );

void setup()
{
  update_light();
  Serial.println( "节点准备接收消息..." );
}

void loop()
{
  // 在 MySensors2.x 中,第一条消息必须来自 loop() 内部
  static bool first_message_sent = false;
  if ( first_message_sent == false ) {
    Serial.println( "发送初始状态..." );
    send_dimmer_message();
    send_status_message();
    first_message_sent = true;
  }
}

void presentation()
{
  // 将草图版本信息发送到网关
  sendSketchInfo( SN, SV );
  present( CHILD_ID_LIGHT, S_DIMMER );
}

void receive(const MyMessage &message)
{
  // 接收到 V_STATUS 命令时,在关闭状态与最后接收到的调光值间切换灯光
  if ( message.type == V_STATUS ) {
    Serial.println( "接收到 V_STATUS 命令..." );

    int lstate = message.getInt();
    if (( lstate < 0 ) || ( lstate > 1 )) {
      Serial.println( "V_STATUS 数据无效(应为 0/1)" );
      return;
    }
    last_state = lstate;

    // 如果最后调光状态为零,则将调光器设置为 100
    if (( last_state == LIGHT_ON ) && ( last_dim == 0 )) {
      last_dim=100;
    }

    // 更新控制器状态
    send_status_message();

  } else if ( message.type == V_PERCENTAGE ) {
    Serial.println( "接收到 V_PERCENTAGE 命令..." );
    int dim_value = constrain( message.getInt(), 0, 100 );
    if ( dim_value == 0 ) {
      last_state = LIGHT_OFF;

      // 更新控制器的调光值和状态
      send_dimmer_message();
      send_status_message();
    } else {
      last_state = LIGHT_ON;
      last_dim = dim_value;

      // 更新控制器的调光值
      send_dimmer_message();
    }

  } else {
    Serial.println( "接收到的命令无效..." );
    return;
  }

  // 在这里设置实际的灯光状态/级别
  update_light();
}

void update_light()
{
  // 对于此示例,仅将灯光状态打印到控制台。
  if ( last_state == LIGHT_OFF ) {
    Serial.println( "灯光状态:关闭" );
  } else {
    Serial.print( "灯光状态:开启,级别: " );
    Serial.println( last_dim );
  }
}

void send_dimmer_message()
{
  send( dimmer_msg.set( last_dim ) );
}

void send_status_message()
{
  if ( last_state == LIGHT_OFF ) {
    send( light_msg.set( (int16_t)0) );
  } else {
    send( light_msg.set( (int16_t)1) );
  }
}

遥控器

支持以下类型组合:

MySensors 版本 1.4 和更高版本

S_TYPE V_TYPE
S_IR V_IR_SEND, V_LIGHT

MySensors 版本 1.5 和更高版本

S_TYPE V_TYPE
S_IR V_IR_SEND, V_STATUS

V_LIGHT 或 V_STATUS 是报告遥控器开 / 关状态所必需的。根据库版本使用 V_LIGHT 或 V_STATUS。

IR 收发器示例草图

/*
 * 文档: https://www.mysensors.org
 * 支持论坛: https://forum.mysensors.org
 *
 * https://www.mysensors.org/build/ir
 */

#include <MySensor.h>
#include <SPI.h>
#include <IRLib.h>

#define SN "IR Sensor"
#define SV "1.0"
#define CHILD_ID 1

MySensor gw;

char code[10] = "abcd01234";
char oldCode[10] = "abcd01234";
MyMessage msgCodeRec(CHILD_ID, V_IR_RECEIVE);
MyMessage msgCode(CHILD_ID, V_IR_SEND);
MyMessage msgSendCode(CHILD_ID, V_LIGHT);

void setup()
{
  gw.begin(incomingMessage);
  gw.sendSketchInfo(SN, SV);
  gw.present(CHILD_ID, S_IR);
  // 发送初始值。
  gw.send(msgCodeRec.set(code));
  gw.send(msgCode.set(code));
  gw.send(msgSendCode.set(0));
}

void loop()
{
  gw.process();
  // IR 接收器未实现,仅在代码更改时虚拟报告
  if (String(code) != String(oldCode)) {
    Serial.print("接收到代码 ");
    Serial.println(code);
    gw.send(msgCodeRec.set(code));
    strcpy(oldCode, code);
  }
}

void incomingMessage(const MyMessage &message) {
  if (message.type==V_LIGHT) {
    // IR 发送器未实现,仅打印虚拟信息。
    if (message.getBool()) {
      Serial.print("发送代码 ");
      Serial.println(code);
    }
    gw.send(msgSendCode.set(message.getBool() ? 1 : 0));
    // 始终关闭设备
    gw.wait(100);
    gw.send(msgSendCode.set(0));
  }
  if (message.type == V_IR_SEND) {
    // 从传入消息中检索 IR 代码值。
    String codestring = message.getString();
    codestring.toCharArray(code, sizeof(code));
    Serial.print("更改代码为 ");
    Serial.println(code);
    gw.send(msgCode.set(code));
  }
}

传感器

支持以下传感器类型:

MySensors 版本 1.4 及更高版本

S_TYPE V_TYPE
S_TEMP V_TEMP
S_HUM V_HUM
S_BARO V_PRESSURE, V_FORECAST
S_WIND V_WIND, V_GUST, V_DIRECTION
S_RAIN V_RAIN, V_RAINRATE
S_UV V_UV
S_WEIGHT V_WEIGHT, V_IMPEDANCE
S_POWER V_WATT, V_KWH
S_DISTANCE V_DISTANCE
S_LIGHT_LEVEL V_LIGHT_LEVEL
S_IR V_IR_RECEIVE
S_WATER V_FLOW, V_VOLUME
S_AIR_QUALITY V_DUST_LEVEL
S_CUSTOM V_VAR1, V_VAR2, V_VAR3, V_VAR4, V_VAR5
S_DUST V_DUST_LEVEL
S_SCENE_CONTROLLER V_SCENE_ON, V_SCENE_OFF

MySensors 版本 1.5 及更高版本

S_TYPE V_TYPE
S_COLOR_SENSOR V_RGB
S_MULTIMETER V_VOLTAGE, V_CURRENT, V_IMPEDANCE
S_SOUND V_LEVEL
S_VIBRATION V_LEVEL
S_MOISTURE V_LEVEL
S_LIGHT_LEVEL V_LEVEL
S_AIR_QUALITY V_LEVEL (替代 V_DUST_LEVEL)
S_DUST V_LEVEL (替代 V_DUST_LEVEL)

MySensors 版本 2.0 及更高版本

S_TYPE V_TYPE
S_INFO V_TEXT
S_GAS V_FLOW, V_VOLUME
S_GPS V_POSITION
S_IR V_IR_RECORD
S_WATER_QUALITY V_TEMP, V_PH, V_ORP, V_EC

自定义测量单位

某些传感器值类型并不特定于某种传感器类型。这些没有 Home Assistant 中的默认测量单位。例如,V_LEVEL 类型可以用于不同的传感器类型,如灰尘、声音、振动等。

通过使用 V_UNIT_PREFIX,可以为任何传感器设置自定义单位。发送 V_UNIT_PREFIX 的字符串值将优先于为定义的传感器使用任何其他测量单位。V_UNIT_PREFIX 不能作为独立的传感器值类型使用。还必须从上述表中发送受支持的值类型和值。V_UNIT_PREFIX 从 MySensors 版本 1.5 开始可用。

MySensors 2.x 的传感器示例草图

/**
 * 文档: https://www.mysensors.org
 * 支持论坛: https://forum.mysensors.org
 *
 * https://www.mysensors.org/build/light
 */

// 启用调试打印到串行监视器
#define MY_DEBUG
#define MY_RADIO_NRF24

#include <BH1750.h>
#include <Wire.h>
#include <MySensors.h>

#define SN "LightLuxSensor"
#define SV "1.0"
#define CHILD_ID 1
unsigned long SLEEP_TIME = 30000; // 读取之间的睡眠时间(以毫秒为单位)

BH1750 lightSensor;
MyMessage msg(CHILD_ID, V_LEVEL);
MyMessage msgPrefix(CHILD_ID, V_UNIT_PREFIX);  // 自定义单位消息。
uint16_t lastlux = 0;
bool initialValueSent = false;

void setup()
{
  sendSketchInfo(SN, SV);
  present(CHILD_ID, S_LIGHT_LEVEL);
  lightSensor.begin();
}

void loop()
{
  if (!initialValueSent) {
    Serial.println("发送初始值");
    send(msgPrefix.set("custom_lux"));  // 设置自定义单位。
    send(msg.set(lastlux));
    Serial.println("请求控制器的初始值");
    request(CHILD_ID, V_LEVEL);
    wait(2000, C_SET, V_LEVEL);
  }
  uint16_t lux = lightSensor.readLightLevel();  // 获取 Lux 值
  if (lux != lastlux) {
      send(msg.set(lux));
      lastlux = lux;
  }

  sleep(SLEEP_TIME);
}

void receive(const MyMessage &message) {
  if (message.type == V_LEVEL) {
    if (!initialValueSent) {
      Serial.println("接收到来自控制器的初始值");
      initialValueSent = true;
    }
  }
}

开关

支持以下执行器类型:

MySensors 版本 1.4 及更高版本

S_TYPE V_TYPE
S_DOOR V_ARMED
S_MOTION V_ARMED
S_SMOKE V_ARMED
S_LIGHT V_LIGHT
S_LOCK V_LOCK_STATUS

MySensors 版本 1.5 及更高版本

S_TYPE V_TYPE
S_LIGHT V_STATUS
S_BINARY [V_STATUS 或 V_LIGHT]
S_SPRINKLER V_STATUS
S_WATER_LEAK V_ARMED
S_SOUND V_ARMED
S_VIBRATION V_ARMED
S_MOISTURE V_ARMED

MySensors 版本 2.0 及更高版本

S_TYPE V_TYPE
S_WATER_QUALITY V_STATUS

以上每个 S_TYPE 的所有 V_TYPES 都是必需的,以便激活该平台的执行器。在需要此 V_TYPE 的情况下,根据库版本使用 V_LIGHT 或 V_STATUS。

开关示例草图

/*
 * 文档: https://www.mysensors.org
 * 支持论坛: https://forum.mysensors.org
 *
 * https://www.mysensors.org/build/relay
 */

#include <MySensor.h>
#include <SPI.h>

#define SN "Relay"
#define SV "1.0"
#define CHILD_ID 1
#define RELAY_PIN 3

MySensor gw;
MyMessage msgRelay(CHILD_ID, V_STATUS);

void setup()
{
  gw.begin(incomingMessage);
  gw.sendSketchInfo(SN, SV);
  // 初始化数字引脚为输出。
  pinMode(RELAY_PIN, OUTPUT);
  gw.present(CHILD_ID, S_BINARY);
  gw.send(msgRelay.set(0));
}

void loop()
{
  gw.process();
}

void incomingMessage(const MyMessage &message)
{
  if (message.type == V_STATUS) {
     // 更改继电器状态。
     digitalWrite(RELAY_PIN, message.getBool() ? 1 : 0);
     gw.send(msgRelay.set(message.getBool() ? 1 : 0));
  }
}

文本

支持以下传感器类型:

MySensors 版本 2.0 及更高版本

S_TYPE V_TYPE
S_INFO V_TEXT

文本示例草图

/*
 * 文档: https://www.mysensors.org
 * 支持论坛: https://forum.mysensors.org
 */

// 启用调试打印到串行监视器
#define MY_DEBUG
#define MY_RADIO_NRF24

#include <MySensors.h>
#include <SPI.h>

#define SN "TextSensor"
#define SV "1.0"
#define CHILD_ID 1

MyMessage textMsg(CHILD_ID, V_TEXT);
bool initialValueSent = false;

void setup(void) {
}

void presentation() {
  sendSketchInfo(SN, SV);
  present(CHILD_ID, S_INFO, "TextSensor1");
}

void loop() {
  if (!initialValueSent) {
    Serial.println("发送初始值");
    // 发送初始值。
    send(textMsg.set("-"));
    Serial.println("请求控制器的初始值");
    request(CHILD_ID, V_TEXT);
    wait(2000, C_SET, V_TEXT);
  }
}

void receive(const MyMessage &message) {
  if (message.type == V_TEXT) {
    if (!initialValueSent) {
      Serial.println("接收到来自控制器的初始值");
      initialValueSent = true;
    }
    // 虚拟打印
    Serial.print("消息: ");
    Serial.print(message.sensor);
    Serial.print(", 消息: ");
    Serial.println(message.getString());
    // 发送消息到控制器
    send(textMsg.set(message.getString()));
  }
}