专栏介绍
- 能够实现数行代码即可在Arduino中接入告警功能,包括微信、QQ、飞书、钉钉等多个告警方式。
订阅用户须知
- 订阅后私聊博主获取源码(搜索QQ:
2421818708
),专栏上遇到的问题尽可咨询。🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝
钉钉告警
1、背景
在我们做Arduino项目(特别是环境监测、数据报警等等)开发学习的时候,总会考虑要是这些信息能够通知到我的微信、钉钉、飞书等等常用app的时候就好了。
现在,这些不再是高深想法,跟着鸟哥来学习,可以实现快速简单接入,并且提供对应案例代码以方便直接运行感受实验效果。
关于告警通知,鸟哥会循序渐进,计划五部曲,分别为:
- 腾讯科技微信告警
- 腾讯科技QQ告警
- 字节跳动飞书告警
- 阿里巴巴钉钉告警
- 整合以上四者到NodeJs服务器,业务控制用服务代码实现
关于微信告警,我们已经在上一期详细讲解过。那么接下来,让我们接入第二篇 —— 钉钉告警。
2、钉钉告警
阿里作为当前国内巨头,旗下也有不少好的产品,比如钉钉,我们当然希望能结合智能硬件一起使用。这就不得不说钉钉开放平台的一些能力 ——机器人消息推送(一般也叫作推送消息提醒)。
在钉钉,机器人是独立存在的一个应用类型,可以开箱即用,也可以进行二次开发,无需和微应用或者群等场景进行强制绑定。
对于开发者而言,钉钉机器人是全局唯一的应用,即无论是在单聊中还是群聊中,都可以用来推送微应用的通知和用来对用户进行对话式服务,其对应的机器人ID都可以是唯一的。这意味着开发者既可以选择仅创建一个机器人,而后将其放在各个应用场景下使用,也可以创建多个机器人,然后分别部署再不同场景下。
而我们这里重点关注自定义机器人的接入。
官方文档 https://open.dingtalk.com/document/robots/custom-robot-access
接下来,请根据我的步骤来配置环境。
2.1 自定义机器人配置使用步骤
前提条件:假设你是有一个钉钉账号。
2.1.1 步骤一:获取自定义机器人Webhook(其实就是一个请求地址)
- 选择需要添加机器人的群聊,然后依次单击
群设置 > 智能群助手
。
- 在机器人管理页面
选择自定义机器人
,输入机器人名字并选择要发送消息的群,同时可以为机器人设置机器人头像。完成必要的安全设置
,勾选我已阅读并同意《自定义机器人服务及免责条款》,然后单击完成。
- 这里我们可以自定义关键词(
发送文本时需要带上这个关键词,否则不给通过
)- 也可以自定义固定IP地址,只有该IP地址发送过来的信息才会处理。
- 获取到一个webhook链接(这个链接就是我们的请求地址),复制出机器人的Webhook地址,可用于向这个群发送消息,点击完成。
https://oapi.dingtalk.com/robot/send?
access_token=XXXXXX
最重要的就是AccessToken
。这里每个人都不一样的
2.1.2 步骤二:使用自定义机器人
获取到Webhook地址后,用户可以向这个地址发起HTTP POST 请求
,即可实现给该钉钉群发送消息。
也就是说我们只需要按照规定的数据格式要求发起一个HTTP消息请求即可,这又是后面博主封装sdk的目的。
当前自定义机器人支持文本 (text)
、链接 (link)、markdown(markdown)、ActionCard、FeedCard消息类型。我们这里只关注文本类型(发送HTTP POST请求时构造这个JSON数据结构
)。
{
"at": {
"atMobiles":[
"180xxxxxx"
],
"atUserIds":[
"user123"
],
"isAtAll": false
},
"text": {
"content":"我就是我, @XXX 是不一样的烟火"
},
"msgtype":"text"
}
注意事项:
- 每个机器人
每分钟最多发送20条
(对我们环境监控场景来说足够)。消息发送太频繁会严重影响群成员的使用体验,大量发消息的场景 (譬如系统监控报警) 可以将这些信息进行整合,通过markdown消息以摘要的形式发送到群里。
2.1.3 步骤三:了解常见问题(HTTP请求错误码)
- 当出现以下错误时,表示消息校验未通过,请查看机器人的安全设置。
// 消息内容中不包含任何关键词
{
"errcode":310000,
"errmsg":"keywords not in content"
}
// timestamp 无效
{
"errcode":310000,
"errmsg":"invalid timestamp"
}
// 签名不匹配
{
"errcode":310000,
"errmsg":"sign not match"
}
// IP地址不在白名单
{
"errcode":310000,
"errmsg":"ip X.X.X.X not in whitelist"
}
接下来,我们就可以封装我们的sdk —— DingTalkMessageIoTSDK
2.2 DingTalkMessageIoTSDK实现
- 第一步,构造消息体,仅仅文本类型
- 第二步,发送消息
2.2.1 构造消息体,这里仅仅文本类型
官方文档案例模板:
{
"at": {
"atMobiles":[
"180xxxxxx"
],
"atUserIds":[
"user123"
],
"isAtAll": false
},
"text": {
"content":"我就是我, @XXX 是不一样的烟火"
},
"msgtype":"text"
}
所以我们的sdk对应代码:
#define MESSAGE_BODY_FORMAT "{\"at\":{\"isAtAll\":true},\"msgtype\":\"text\",\"text\":{\"content\":\"%s\"}}"
2.2.2 发送消息
以上面消息体为参数。发起一个post请求
请求方式:POST(HTTPS)
请求地址:https://oapi.dingtalk.com/robot/send?access_token=ACCESS_TOKEN
,其中token是填我们自己的
sdk对应代码:
/**
* 发送文本到钉钉群
* @param payload 文本内容
*/
void DingTalkMessageIoTSDK::sendText(const char *payload)
{
Serial.print(F("[HTTPS] begin...\n"));
char jsonBuf[500];
sprintf(jsonBuf, MESSAGE_BODY_FORMAT, payload);
Serial.println(jsonBuf);
Serial.println(DINGTALK_API_SEND_MSG);
bool sendMessageOK = false;
if(https.begin(*client, DINGTALK_API_SEND_MSG)) { // HTTPS
Serial.print(F("[DINGTALK] sendMessage...\n"));
// start connection and send HTTP header
https.addHeader("Content-Type", "application/json");
int httpCode = https.POST(jsonBuf);
// httpCode will be negative on error
if(httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[DINGTALK] POST... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = https.getString();
Serial.println(payload);
sendMessageOK = true;
} else {
Serial.printf("[DINGTALK] POST... failed, error: %s\n", https.errorToString(httpCode).c_str());
sendMessageOK = false;
}
https.end();
}
}else {
Serial.print(F("[HTTPS] Unable to connect\n"));
}
if (sendMessageOK) {
Serial.print(F("[DINGTALK] sendMessage Ok...\n"));
} else {
Serial.print(F("[DINGTALK] sendMessage error...\n"));
}
Serial.print(F("[HTTPS] end...\n"));
}
接下来,我们来测试完整的sdk代码。
3、测试SDK效果
3.1 测试代码
/**
* 时间:2022-05-03
* 描述:
* 测试钉钉提醒
*/
#include <ESP8266WiFi.h>
#include <stdlib.h>
#include <Ticker.h>
#include "DingTalkMessageIoTSDK.h"
/******************* 常量声明 **********************/
#define SSID "TP-LINK_5344" // 填入自己的WiFi账号
#define PASSWORD "xxxxx" // 填入自己的WiFi密码
//---------------- 钉钉自定义机器人相关配置信息 ------------------//
#define ACCESS_TOKEN "xxxxx" // token,从机器人链接得到,替换为自己的
#define INTERVAL 10000 // 读取时间间隔,默认10s
//---------------- 钉钉自定义机器人相关配置信息 ------------------//
/******************* 函数声明 **********************/
void initWifiConnect(void); // 初始化Wifi连接
void doWiFiConnectTick(void); // 检测WiFi连接状态
void initDingTalkIotSDK(void); // 初始化钉钉IOT SDK
void sendDataToDingTalk(void); // 发送文本到钉钉
/******************* 函数声明 **********************/
/******************* 变量定义 **********************/
Ticker delayTimer; // 表示定时模块,用来做一个定时器
unsigned long previousMillis = 0; // 记录上次读取的时间戳
/******************* 变量定义 **********************/
/**
* 初始化
*/
void setup() {
delay(2000); // 延时2秒,用于等待系统上电稳定
Serial.begin(115200); // 初始化串口,波特率 115200
Serial.println(""); // 串口默认先换行显示
Serial.println("测试钉钉消息提醒~"); // 串口打印信息表示项目启动~
initWifiConnect(); // 初始化Wifi连接
initDingTalkIotSDK(); // 初始化企业微信信息
ESP.wdtEnable(5000); // 启用看门狗
}
void loop() {
ESP.wdtFeed();// 定时喂狗
unsigned long currentMillis = millis(); // 获取当前时间戳
doWiFiConnectTick(); // wifi连接状态检测以及重连
if(currentMillis - previousMillis >= INTERVAL) // 每隔一段时间 interval为时间间隔
{
previousMillis = currentMillis; // 记录当前时间戳
sendDataToDingTalk();
}
delay(2000); // 延时2秒
}
/**
* 检测WiFi连接状态
*/
void doWiFiConnectTick(void)
{
static uint32_t lastWiFiCheckTick = 0; // 记录最近一次检测WiFi连接状态的时间点
static uint32_t disConnectCount = 0; // 记录WiFi断开连接的次数
if(WiFi.status() == WL_CONNECTED) // 当前WiFi处于连接状态
{
disConnectCount = 0;// 重置WiFi断开连接为0
return;
}
if(millis() - lastWiFiCheckTick > 1000) // 检测间隔大于1秒
{
lastWiFiCheckTick = millis(); // 记录时间点
Serial.println("WiFi disConnect!"); // 串口输出.表示设备已经断开连接
disConnectCount++; // WiFi断开连接的次数累计加1
if(disConnectCount>=40) // 断开连接累计次数达到40次,表示可能wifi连接异常
{
delayRestart(1); //一直连接不上就重启ESP系统
}
}
}
/**
* 延时t秒后重启ESP
* @param t t秒
*/
void delayRestart(float t)
{
Serial.print("Restart after ");
Serial.print(t);
Serial.println("s");
// 开启一个定时器,定时时间为t秒
delayTimer.attach(t, []() {
Serial.println("\r\nRestart now!");
ESP.restart();// 重启ESP模块
});
}
/**
* 初始化Wifi连接
*/
void initWifiConnect(void)
{
Serial.printf("Connecting to WiFi:%s\n",SSID);// 串口打印当前WiFi热点的名字
WiFi.disconnect(); // 默认断开之前的连接,回归初始化非连接状态
WiFi.mode(WIFI_STA); // 设置ESP工作模式为Station模式
WiFi.begin(SSID, PASSWORD); // 连接到WiFi
int cnt = 0; // 记录重试次数
while (WiFi.status() != WL_CONNECTED) // 当还没有连接上WiFi热点时
{
delay(1000); // 延时等待1秒
cnt++; // 累计次数+1
Serial.print("."); // 串口输出.表示设备正在连接WiFi热点
if(cnt>=40) // 超过40秒还没有连接上网络
{
delayRestart(1); //一直连接不上就重启ESP系统
}
}
Serial.println(WiFi.localIP()); // 打印当前IP地址
}
/**
* 初始化钉钉IOT SDK
*/
void initDingTalkIotSDK(void)
{
// 初始化 iot sdk
DingTalkMessageIoTSDK::begin(ACCESS_TOKEN);
}
/**
* 发送数据
*/
void sendDataToDingTalk(void)
{
DingTalkMessageIoTSDK::sendText("告警 这里是8266 收到请回复!");
}
这里的效果是每隔一段时间给微信发送一条消息。
告警 这里是8266 收到请回复!
这里接入sdk非常简单,只需要调用初始化方法之后,可以在适当时机调用一下sendText方法即可完美。
3.2 测试结果
3.2.1 串口打印结果
3.2.2 打开钉钉,看消息
自此,我们就可以愉快接入钉钉消息提醒,接入鸟哥写的钉钉提醒sdk,调用两行代码即可快速接入。