diff --git a/README.md b/README.md index 8ef9fe8..fbb2732 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,17 @@ PushDeer是一个可以自行架设的无APP推送服务。 当前状态:API、iOS、Android和Mac第一版已完成,快应用正在开发🚧 +**🔥 PushDeer支持推送消息到智能设备了** + +👉[点此查看如何将消息推送到成本35元左右的自制设备上](other_devices/README.md) + +|||| +|-|-|-| +|[👨🏻‍🏫 教程](other_devices/README.md)|[⌨️ 源码](other_devices/deeresp/)|[📼 演示视频,可以听到提示音♪](https://weibo.com/1088413295/LfJtvDx6K?type=comment)| + +![](other_devices/image/deeresp.gif) + + [🐙🐱 GitHub仓库](https://github.com/easychen/pushdeer) [🔮 中国大陆镜像仓库@Gitee](https://gitee.com/easychen/pushdeer) |登入|设备|Key|消息|设置| diff --git a/other_devices/README.md b/other_devices/README.md index ae93227..39c74a0 100644 --- a/other_devices/README.md +++ b/other_devices/README.md @@ -1,11 +1,82 @@ -# 使用MQTT接受PushDeer推送的消息 +> PushDeer可以将消息推送到各种支持MQTT协议的智能设备。 + +本文将以 `NodeMCU` 1.0开发板和 1.44寸的 Arduino Black TFT屏幕为例,讲解如何组建一个成本35元人民币左右的硬件设备,并通过PushDeer将消息推送给它。 + +最终效果如下图: + +![](image/deeresp.gif) + +[📼 点此查看视频版本,可以听到提示音♪](https://weibo.com/1088413295/LfJtvDx6K?type=comment) + +PS:如果你有硬件量产的经验并有兴趣参与,可以在[微博](https://weibo.com/easy)私信或者评论@Easy。 + +## 硬件的购买 + +![](image/2022-02-17-00-43-58.png) + +1. 开发板:只要是兼容NodeMCU1.0规范的就行,内存需要4M(32Mbits),更大更好(就能放中文字库了)我选的是CH340接口 +1. 屏幕:[1.44寸黑色TFT](http://www.lcdwiki.com/zh/1.44inch_SPI_Arduino_Module_Black_SKU:MAR1442),其他兼容ST7735驱动的屏幕也可以,但连线可能就不同了,需要自己配置屏幕库 +1. 蜂鸣器(可选):来消息时播放提示音 + +前边两个加上USB线,[淘宝33.30](https://item.taobao.com/item.htm?spm=a1z09.2.0.0.2c042e8dlNdY3E&id=531755241333&_u=gog6id51b2),无源蜂鸣器:[淘宝3.3](https://detail.tmall.com/item.htm?id=41251333522&spm=a1z09.2.0.0.2c042e8dlNdY3E&_u=gog6id9820&skuId=4323951807546)。不认识店家,就随便搜了就买了。 + +## 硬件的连接 + +首先把屏幕和开发板连起来,按下图操作: + +![](image/2022-02-16-21-48-45.png) + +然后再把蜂鸣器和开发板连起来 + +|蜂鸣器|开发板| +|-|-| +|GND|G| +|VCC|3V| +|IO|D8| + +如果你的连线不同,那么程序中的PIN值可能需要随之调整。 + +## 将开发板和电脑连起来 + +### 串口驱动安装 + +用USB线将开发板和电脑连起来,但这时候它们之间还不能通信,因为开发板用串口信号,电脑用USB信号,需要进行转换。 + +一般NodeMCU开发板上有自带转换芯片,比如CH3XX或者CPXXX,这里以CH340为例。去它们官网下载[最新的驱动](http://www.wch.cn/products/ch340.html)安装后,两者就能通信了。 + +![](image/2022-02-16-21-59-51.png) + +## 配置开发环境 + +我们使用 `arduino` 开发环境进行开发,在此之前需要[下载并安装它的IDE](https://www.arduino.cc/en/software)。 + +![](image/2022-02-16-22-01-05.png) + +由于我们使用的8266并没有内置到 `ardunio IDE` 中,我们还需要进行一下配置,在设置界面填上附加开发板管理器网址:`https://arduino.esp8266.com/stable/package_esp8266com_index.json` + +![](image/2022-02-16-22-02-36.png) + +然后选择 `工具`→`开发板`→`ESP8266 Boards`→`NodeMCU1.0` + +![](image/2022-02-16-22-04-25.png) + +再调整下开发板使用的串口: + +![](image/2022-02-16-22-07-07.png) + +要确认哪一个串口是开发板的很简单,你拔掉它就不见了… + +这些准备工作做好以后,我们就可以将代码烧录到设备上了。但在这之前,我们需要把MQTT服务器架设起来,之后我们才能把账号等信息烧录进去。 + + +## 使用MQTT接受PushDeer推送的消息 PushDeer自架版支持通过MQTT协议向兼容的设备(以下简称设备)发送信息,其主要工作原理是: 1. 自架版docker-compose文件中预置了MQTT服务器,手动开启后,设备可以通过配置的端口连接到服务器 1. 设备通过订阅主题实时获得消息,文字类型(text/markdown)消息主题为:`{{pushkey}}_text`, 图片类型的主题为`{{pushkey}}_bg_url` -## 开启MQTT服务 +### 开启MQTT服务 修改根目录下的 `docker-compose.self-hosted.yml`: @@ -67,7 +138,7 @@ PushDeer自架版支持通过MQTT协议向兼容的设备(以下简称设备 docker-compose -f docker-compose.self-hosted.yml up --build -d ``` -## 连接参数实例 +### 连接参数实例 这里以上边的设置为例,详细说明MQTT连接中用到的值,假设你的`Pushkey` 为 `PDU01234`,那么: @@ -89,4 +160,115 @@ docker-compose -f docker-compose.self-hosted.yml up --build -d 1. text/markdown类型的消息会通过`PDU01234_text`发送 1. image类型的消息会通过`PDU01234_bg_url`发送 +## 烧录程序到设备 +回到我们的设备这边来。首先用 `arduino IDE` 打开 `deeresp/deeresp.ino`,修改最上边的几行: + +```cpp +#define WIFI_SSID "wifi名称" +#define WIFI_PASSWORD "wifi密码" +#define MQTT_IP "pushdeer公网IP" +#define MQTT_USER "MQTT用户名" +#define MQTT_PASSWORD "MQTT密码" +#define MQTT_TOPIC "PushDeer pushkey" // 这里填PushDeer的Key +#define MQTT_PORT 1883 +``` + +这里的信息我们现在都有了,把它们替换掉,然后点击上传图标(向右的箭头),就会编译并烧录程序到设备上了。不过别急,有两个问题需要处理。 + +![](image/2022-02-16-23-32-30.png) + +### 添加缺少的库 + +如果你在编译的过程中遇到了错误,那么多半是因为缺少库导致的。可以直接在IDE中搜索添加。以 `TJpg_Decoder` 库为例,点开`项目`→`加载库`→`管理库`。 + +![](image/2022-02-16-23-35-41.png) + +在弹出的`库管理器`窗口里输入库的名字进行搜索,选择对应库并点击`安装`即可。 + +![](image/2022-02-16-23-38-05.png) + +### 屏幕适配 + +我们使用了 `TFT_eSPI` 这个库来控制屏幕,它可以适配非常多的屏幕,但我们并没有在代码中告诉它我们用的屏幕是哪一款。这是因为,它要靠修改源代码目录的配置文件来实现的。 + +在首选项中找到`项目文件夹`位置。 +![](image/2022-02-16-23-42-25.png) + +打开该目录,然后找到 `User_Setup.h`文件。 + +![](image/2022-02-16-23-44-07.png) + +将以下行之前的注释去掉: + +```cpp +#define ST7735_DRIVER +#define TFT_RGB_ORDER TFT_BGR +#define TFT_WIDTH 128 +#define TFT_HEIGHT 128 +#define ST7735_GREENTAB3 +#define TFT_CS PIN_D1 // Chip select control pin D8 +#define TFT_DC PIN_D3 // Data Command control pin +#define TFT_RST PIN_D2 // Reset pin (could connect to NodeMCU RST, see next line) +#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH +#define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters +#define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters +#define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm +#define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-. +#define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-. +//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT +#define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts +#define SMOOTH_FONT +#define SPI_FREQUENCY 27000000 +#define SPI_READ_FREQUENCY 20000000 +#define SPI_TOUCH_FREQUENCY 2500000 +``` + +然后再打开同目录下的 `TFT_eSPI.h`,找到 `User_select.h`,打开注释: + +```cpp +#include +// #include +``` + +这些操作完成后再编译就会发现屏幕正常显示了。如果显示不正常(比如图片颜色、大小不对等),这有可能是因为你用的屏幕硬件规格和我们这里的不同,可以参照注释尝试修改那些配置项。 + +## 通过 PushDeer 推送信息到设备 + +当程序烧录完成,设备会初始化并自动连接服务器。如果没有初始化,可以按开发板上的reset进行重置。如果在烧录过程中串口无法连接开发板,也请按reset。 + +连接完成后可以看到「Waiting for message...」的提示。这时候我们就可以进行推送了。 + +![](image/2022-02-17-00-00-43.png) + +推送直接使用 PushDeer 的API。 + +`POST /message/push` + +|参数|说明|备注| +|-|-|-| +|pushkey|PushKey| +|text|推送消息内容| +|type|格式,选填|文本=text,markdown,图片=image,默认为markdown| + +> type 为 image 时,text 中为要发送图片的URL。 +> type 为 text 时,且 text 中包含 `♪` 字符时,蜂鸣器会发声 + +## 独立架设服务端 + +如果你希望为智能设备架设单一的服务端,可以单独使用 `pushdeeresp` 镜像: + +```bash +docker run -e API_KEY=9LKo3 -e MQTT_PORT=1883 -e MQTT_USER=easy -e MQTT_PASSWORD=y0urp@ss - MQTT_BASE_TOPIC=default -p 1883:1883 -p 80:80 +``` + +启动后,支持`MQTT`和`HTTP`两种方式发送消息。 + +其中 `HTTP` 方式如下:访问 `IP/send` 即可发送消息,参数为: + +|参数|说明|备注| +|-|-|-| +|key|API_key|用于限制权限访问| +|content|推送消息内容| +|type|格式,选填|文本=text,图片=bg_url,默认为text| +|topic|推送到主题,选填|会根据type推送到`${topic}_text`或`${topic}_bg_url`通道| diff --git a/other_devices/deeresp/deeresp.ino b/other_devices/deeresp/deeresp.ino new file mode 100644 index 0000000..059fcb2 --- /dev/null +++ b/other_devices/deeresp/deeresp.ino @@ -0,0 +1,162 @@ +#define WIFI_SSID "wifi名称" +#define WIFI_PASSWORD "wifi密码" +#define MQTT_IP "pushdeer公网IP" +#define MQTT_USER "MQTT用户名" +#define MQTT_PASSWORD "MQTT密码" +#define MQTT_TOPIC "PushDeer pushkey" // 这里填PushDeer的Key +#define MQTT_PORT 1883 + + +// ====== 以下不用修改 =============== +#define MQTT_CLIENT_NAME "DeerEsp" +#define DOWNLOADED_IMG "/download.jpg" +#define BEEP_PIN D8 + +#include + +EspMQTTClient mclient( + WIFI_SSID, + WIFI_PASSWORD, + MQTT_IP, + MQTT_USER, + MQTT_PASSWORD, + MQTT_CLIENT_NAME, + MQTT_PORT +); + +#include "SPI.h" +#include +TFT_eSPI tft = TFT_eSPI(); + +#ifdef ESP8266 + #include +#else + #include "SPIFFS.h" // Required for ESP32 only + #include +#endif + +#include + +void setup() { + Serial.begin(115200); + mclient.enableDebuggingMessages(); + + tft.begin(); + tft.fillScreen(TFT_BLACK); + tft.setTextColor(0xFFFF,0x0000);tft.setCursor(0, 0, 1);tft.setTextSize(2);tft.println("Init ..."); + Serial.println("tft init"); + + if (!SPIFFS.begin()) { + Serial.println("SPIFFS initialisation failed!"); + while (1) yield(); // Stay here twiddling thumbs waiting + } + Serial.println("SPIFFS init"); + + TJpgDec.setJpgScale(2); + TJpgDec.setSwapBytes(true); + TJpgDec.setCallback(tft_output); + + Serial.println("TJpgDec init"); + +} + +void onConnectionEstablished() +{ + Serial.println("connected"); + tft.setTextColor(0xFFFF,0x0000);tft.setCursor(0, 0, 1);tft.println("Waiting for message ..."); + + mclient.subscribe(String(MQTT_TOPIC)+"_text", [] (const String &payload) + { + Serial.println(payload); + + if (SPIFFS.exists(DOWNLOADED_IMG) == true) TJpgDec.drawFsJpg(0, 0, DOWNLOADED_IMG); + else tft.fillScreen( TFT_BLACK ); + + if(payload.indexOf("♪") >= 0) tone(BEEP_PIN, 1000, 100); + + if( payload.length() > 80 ) tft.setTextSize(1); + else tft.setTextSize(2); + + tft.setTextColor(0xFFFF,0x0000);tft.setCursor(0, 0, 1);tft.println(payload); + }); + + mclient.subscribe(String(MQTT_TOPIC)+"_bg_url", [] (const String &payload) + { + Serial.println(payload); + bool ret = file_put_contents(payload, DOWNLOADED_IMG); + if (SPIFFS.exists(DOWNLOADED_IMG) == true) { + TJpgDec.drawFsJpg(0, 0, DOWNLOADED_IMG); + } + }); +} + +void loop() { + mclient.loop(); +} + +bool file_put_contents(String url, String filename) { + + Serial.println("Downloading " + filename + " from " + url); + + // Check WiFi connection + if (WiFi.status() == WL_CONNECTED) { + + Serial.print("[HTTP] begin...\n"); + + WiFiClient client; + HTTPClient http; + http.begin(client, url); + + + Serial.print("[HTTP] GET...\n"); + int httpCode = http.GET(); + if (httpCode > 0) { + fs::File f = SPIFFS.open(filename, "w+"); + if (!f) { + Serial.println("file open failed"); + return 0; + } + Serial.printf("[HTTP] GET... code: %d\n", httpCode); + if (httpCode == HTTP_CODE_OK) { + + int total = http.getSize(); + int len = total; + + uint8_t buff[128] = { 0 }; + WiFiClient * stream = http.getStreamPtr(); + + while (http.connected() && (len > 0 || len == -1)) { + size_t size = stream->available(); + + if (size) { + int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); + + f.write(buff, c); + + if (len > 0) { + len -= c; + } + } + yield(); + } + Serial.println(); + Serial.print("[HTTP] connection closed or file end.\n"); + } + f.close(); + } + else { + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + Serial.print(httpCode); + + } + http.end(); + } + return 1; +} + +bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap) +{ + if ( y >= tft.height() ) return 0; + tft.pushImage(x, y, w, h, bitmap); + return 1; +} diff --git a/other_devices/image/2022-02-16-21-48-45.png b/other_devices/image/2022-02-16-21-48-45.png new file mode 100644 index 0000000..e9e0b00 Binary files /dev/null and b/other_devices/image/2022-02-16-21-48-45.png differ diff --git a/other_devices/image/2022-02-16-21-59-51.png b/other_devices/image/2022-02-16-21-59-51.png new file mode 100644 index 0000000..7c6f98e Binary files /dev/null and b/other_devices/image/2022-02-16-21-59-51.png differ diff --git a/other_devices/image/2022-02-16-22-01-05.png b/other_devices/image/2022-02-16-22-01-05.png new file mode 100644 index 0000000..5399f58 Binary files /dev/null and b/other_devices/image/2022-02-16-22-01-05.png differ diff --git a/other_devices/image/2022-02-16-22-02-36.png b/other_devices/image/2022-02-16-22-02-36.png new file mode 100644 index 0000000..804b281 Binary files /dev/null and b/other_devices/image/2022-02-16-22-02-36.png differ diff --git a/other_devices/image/2022-02-16-22-04-25.png b/other_devices/image/2022-02-16-22-04-25.png new file mode 100644 index 0000000..e7c4ad9 Binary files /dev/null and b/other_devices/image/2022-02-16-22-04-25.png differ diff --git a/other_devices/image/2022-02-16-22-07-07.png b/other_devices/image/2022-02-16-22-07-07.png new file mode 100644 index 0000000..384529a Binary files /dev/null and b/other_devices/image/2022-02-16-22-07-07.png differ diff --git a/other_devices/image/2022-02-16-23-32-30.png b/other_devices/image/2022-02-16-23-32-30.png new file mode 100644 index 0000000..981ff67 Binary files /dev/null and b/other_devices/image/2022-02-16-23-32-30.png differ diff --git a/other_devices/image/2022-02-16-23-35-41.png b/other_devices/image/2022-02-16-23-35-41.png new file mode 100644 index 0000000..7fb31e1 Binary files /dev/null and b/other_devices/image/2022-02-16-23-35-41.png differ diff --git a/other_devices/image/2022-02-16-23-38-05.png b/other_devices/image/2022-02-16-23-38-05.png new file mode 100644 index 0000000..ddc5877 Binary files /dev/null and b/other_devices/image/2022-02-16-23-38-05.png differ diff --git a/other_devices/image/2022-02-16-23-42-25.png b/other_devices/image/2022-02-16-23-42-25.png new file mode 100644 index 0000000..67019bb Binary files /dev/null and b/other_devices/image/2022-02-16-23-42-25.png differ diff --git a/other_devices/image/2022-02-16-23-44-07.png b/other_devices/image/2022-02-16-23-44-07.png new file mode 100644 index 0000000..aa867b7 Binary files /dev/null and b/other_devices/image/2022-02-16-23-44-07.png differ diff --git a/other_devices/image/2022-02-17-00-00-43.png b/other_devices/image/2022-02-17-00-00-43.png new file mode 100644 index 0000000..4d7e1a4 Binary files /dev/null and b/other_devices/image/2022-02-17-00-00-43.png differ diff --git a/other_devices/image/2022-02-17-00-43-58.png b/other_devices/image/2022-02-17-00-43-58.png new file mode 100644 index 0000000..a7c4b76 Binary files /dev/null and b/other_devices/image/2022-02-17-00-43-58.png differ diff --git a/other_devices/image/deeresp.gif b/other_devices/image/deeresp.gif new file mode 100644 index 0000000..dc869f6 Binary files /dev/null and b/other_devices/image/deeresp.gif differ