跟着思兼学习Klipper(19) 通知系统:蜂鸣器、语音播报与手机消息通知
前言
原创文章,转载引用请务必注明链接,水平有限,如有疏漏,欢迎指正交流。
本文目的在于交流,其中原创内容禁止一切非授权商业行为。
文章如有更新请访问 DFRobot 社区 或者 cnblogs 博客园。
欢迎对 Klipper 固件感兴趣,以及对改版 CNC 加工的 Voron 三叉戟、v0、v2.4 感兴趣的朋友加群交流(QQ Group:490111638)
今天总结下 Klipper 的通知系统,介绍三个,一是使用蜂鸣器,二是使用上位机上的扬声器,三是手机信息通知。常用的调用包括开始打印、结束打印、断料检测等。
1、蜂鸣器播放提示音
【屏幕上的蜂鸣器】
蜂鸣器一般分为两种:有源和无源。主要区别是前者通电后固定频率发出声音,后者频率可变,因此可以演奏旋律。一般 LCD 显示屏上的蜂鸣器都是无源的。
无源蜂鸣器是没有正负之分的,类似于喇叭,只要在两个腿上加载不同的频率的电信号就可以实现发声,根据不同的频率所发出的声音也是不一样的。 有源蜂鸣器是有正负之分的,只需要在两个腿上加上电压信号就会发声,发出的声音音调单一、频率固定。 有源蜂鸣器比无源蜂鸣器内部多了振荡结构,所以有源蜂鸣器在价格上稍微贵一点。Source:sohu.com
Gcode 中有一个命令 M300,用于播放提示音,但是 Klipper 默认没有实装,所以我们可以自己写一个宏,以下宏来源记不清了,如有知道的欢迎补充。
######################################################################
# Beeper | 蜂鸣器设置
######################################################################
# M300 : 播放提示音,支持常见的无源蜂鸣器。使用方法:
# M300 [P<ms>] [S<Hz>]
# P 为持续时间, S 为频率.
# 需要注意的是频率和音高并不是完美对应.
## 定义蜂鸣器控制引脚,一般使用 LCD 上的 EXP1_1 板载蜂鸣器,也可以使用外置蜂鸣器接到 GPIO 引脚。
[output_pin BEEPER_pin]
pin: EXP1_1
# 无源蜂鸣器(piezo beeper)需要 PWM 信号控制,有源(DC buzzer)不需要
pwm: True
value: 0
# Silent at power on, set to 1 if active low.
shutdown_value: 0
# Disable at emergency shutdown (no PWM would be available anyway).
cycle_time: 0.001
# Default PWM frequency : 0.001 = 1ms will give a tone of 1kHz
# Although not pitch perfect.
## 定义 M300 宏,实现 Gcode 功能
[gcode_macro M300]
description: 播放提示音
gcode:
# Use a default 1kHz tone if S is omitted.
{% set S = params.S|default(1000)|int %}
# Use a 10ms duration is P is omitted.
{% set P = params.P|default(100)|int %}
SET_PIN PIN=BEEPER_pin VALUE=0.5 CYCLE_TIME={ 1.0/S if S > 0 else 1 }
G4 P{P}
SET_PIN PIN=BEEPER_pin VALUE=0
######################################################################
# 开始、结束打印时调用 M300 播放提示音,示例来自 CHEP
######################################################################
[gcode_macro START_PRINT]
gcode:
# chirp to indicate starting to print
M300 S1000 P500
# Use absolute coordinates
G90
# Reset the G-Code Z offset (adjust Z offset if needed)
SET_GCODE_OFFSET Z=0.0
# Home the printer
G28
# G1 X10 Y50 Z0.2 F3000
# 避开夹子
G1 Y60 F3000
G1 X10 Z0.2 F3000
G92 E0
G1 Y130 E10 F225
G92 E0
[gcode_macro END_PRINT]
gcode:
# Turn off bed, extruder, and fan
M140 S0
M104 S0
M106 S0
# Move nozzle away from print while retracting
G91
G1 X-2 Y-2 E-3 F300
# Raise nozzle by 10mm
G1 Z10 F3000
G90
G28 X Y
# Disable steppers
M84
# 播放结束提示音
M300 S440 P200
M300 S660 P250
M300 S880 P300
以上除了最后的调用示例,其他的不需要修改(必要时修改蜂鸣器控制引脚)。一般我习惯放在 display.cfg
里面进行调用。
当然,你也可以生成并播放复杂的旋律,或者打印翻车的时候播放 120
的提示音
2、扬声器语音播报
之前在群里讨论过语音播报,什么重新上料后 哥哥不行了,已经插到底了
、开始打印 啊~~ 要开始了吗
、打印结束 哥哥我还要,再来一次吧
,云云。这次借着 KlipperPad 上的俩喇叭,来试验一下。当然你也可以使用外置扬声器(HDMI、3.5mm),记得选择正确的音频输出设备,具体可以参考 Archwiki。使用扬声器播报主要包括以下几点:
2.1 语音生成
这里可以使用在线免费服务:TTSMaker,测试了众多服务之后,这个最简单,也可以使用 Microsoft、阿里、讯飞等的 API。
2.2 命令行播放音频文件
首先要选择合适的终端命令行音频文件播放程序,KlipperPad 预装了 VLC 多媒体播放软件,可以直接调用其命令行版本 cvlc
,对于其他系统,可以考虑 mpg123
,软件依赖少。
测试后发现有三种情况:
- 桌面环境下的 shell
- SSH 登录的 shell,直接调用播放有延迟,可以指定
DISPLAY=:0
来解决,序号根据实际情况填写,一般是 0 。
- Klipper 调用的 shell
2.3 创建并调用语音播报宏
这里借助 shell_cmd 组件,注意它带来了极大的便利,方便与系统交互,但是由于会使能调用 shell 脚本程序,带来可能的安全性隐患,不建议暴露到公网使用。
## 创建播放语音的 shell 脚本
mkdir ~/kbox_scripts
mkdir ~/printer_data/config/misc
cat << '_EOF_' > /home/pi/kbox_scripts/voice_notify.sh
!/bin/bash
case $1 in
0) sound="print_start"
;;
1) sound="print_end"
;;
2) sound="runout"
;;
*) echo 'You do not input a valid number between 0 to 3'
;;
esac
【回复后可见】
_EOF_
## 创建 Klipper 宏脚本
cat << _EOF_ > /home/pi/printer_data/config/misc/voice_notify.cfg
[respond]
[gcode_shell_command voice_notify]
command: bash /home/pi/kbox_scripts/voice_notify.sh
timeout: 5.
verbose: False
[gcode_macro VOICE_NOTIFY]
description: 语音播报功能, 后期将采用林志玲语音,调用命令:voice_notify INFO=0 | by 思兼
gcode:
{% if params.INFO is not defined %}
RESPOND MSG='请正确填写语音编号: 0-打印开始, 1-打印结束, 2-检测到断料'
{% else %}
RUN_SHELL_COMMAND CMD=voice_notify PARAMS={params.INFO}
{% endif %}
_EOF_
测试及使用方法:
- 目前 KlipperPad 内置支持
打印开始
、打印结束
、检测到断料
三个语音,位于 ~/Music
文件夹内,分别对应数字 0-1-2 ,后续可根据需求增加。
- 启用语音播报: 在
printer.cfg
文件中增加一句:[include misc/voice_notify.cfg]
即可
- 调用方法: 以打印开始为例,INFO后面的数字对应语音,在对应的功能处添加一行 Gcode:
voice_notify INFO=0
。也可以手动在网页控制台中输入上述指令进行测试。
3、手机消息通知
触发特定事件后给手机发送通知的功能在生活中用处很多,比如流行的 IFTTT(If This Then That) 服务。我常用的国内免费且使用方便的通知服务包括:
如果想在 Klipper 系统中使用的话,可以看看 Fly3D 的关于微信通知的视频。不过对于普通用户,可以看看 Moonraker 内置的 apprise 服务,目前支持飞书、钉钉、微信、短信、邮箱等等,部分支持多媒体文件,详细支持列表可以参考 moonraker官方文档。
3.1 选择合适的通知服务
对我而言,理想的通知服务最好有以下特征:
- 无需注册,使用简单
- 软件功能简洁,不要动辄几百兆的臃肿软件
- 支持多媒体文件
- 免费使用或者可以自行搭建服务
如果与现有日常软件结合,比较理想的就是飞书、钉钉、微信等。但是多少都无法满足所有条件。搜索、研究、试用一番,ntfy 是一个比较易用的服务。
3.2 安装测试 ntfy
此服务拥有 Android、iOS 客户端,还支持网页端,非常方便,可以从 此处 下载。
安装完毕后,我们先打开 网页端 和 Android 客户端,首先需要订阅/添加一个主题,并发送一条测试信息,是不是很简单?无需注册,免费使用。当然,此主题目前没有身份验证,所有互联网用户订阅了此主题都可以收到信息,所以我们要进一步设置。
3.3 moonraker 配置 ntfy
我们参考文档,创建一个 ntfy 实例即可使用。
# moonraker.conf
[notifier ntfy]
url: ntfy://{topics}
# The url for your notifier. This URL accepts Jinja2 templates, so you can use [secrets] if you want.
events: *
# The events this notifier should trigger to. '*' means all events.
# You can use multiple events, comma separated.
# Valid events:
# started
# complete
# error
# cancelled
# paused
# resumed
body: "打印机状态已变为: {event_name}"
# The body of the notification. This option accepts Jinja2 templates.
# You can use {event_name} to print the current event trigger name. And {event_args} for
# the arguments that came with it. When using the notify functionality in a macro context, you can
# use {event_message} to print out your message.
title: Klipper 提醒,love from china
# The optional title of the notification. Just as the body, this option accepts Jinja2 templates.
attach: http://[ip]/webcam/?action=snapshot
# An optional attachment. Can be an url of a webcam for example. Note: this isn't available for all
# notification services. You can check if it's supported on the Apprise Wiki. Be aware that links in
# your internal network can only be viewed within your network.
3.4 自定义事件调用 ntfy
上述支持 moonraker 内置事件,如果想自定义事件,则包括自定义提示器和 Klipper 调用宏。
## moonraker.conf
[notifier runout]
url: ntfy://klipper_test
events: gcode
# 事件消息接收自 Klippy
body: {event_message}
attach: http://[ip]/webcam/?action=snapshot
## printer.cfg
[gcode_macro NOTIFY_FILAMENT_RUNOUT]
gcode:
{action_call_remote_method("notify",
name="runout",
message="Filament Runout!")}
理想很丰满,现实很骨感,摄像头画面并没有如期出现,并抛给你一个错误。
3.5 慢慢排障路
moonraker.log 报错如下,提示附件位置无效:[moonraker.py:add_warning()] - Attachment of notifier 'runout' is not valid. The location of the attachment is not accessible.
结果是上传了一个文件,拓展名为 bin 文件,无法作为 jpg 打开。
目前用到的 ntfy 调用方式从上到下为 moonraker —— apprise —— ntfy,循序排查之。
apprise 调试信息
以如下命令调用 apprise,信息如下:
python3 -m pip install apprise
/home/pi/.local/bin/apprise -vv -t "测试标题" -b "测试内容" "ntfy://klipper_test/?attach=/home/pi/shot.jpg"
# WARNING - Failed to send ntfy notification to topic 'klipper_test': invalid request: attachment URL is invalid, error=40013.
/home/pi/.local/bin/apprise -vv -t "测试标题" -b "测试内容" 'ntfy://klipper_test/?attach=http://192.168.0.204/webcam/?action=snapshot&filename=smoke.jpg'
# 此命令正确显示图像,且更改名称,但是仅仅是 url 链接,导致仅内网可访问,并未上传到 ntfy 服务器。记得是这样,注意参数调用的双引号。
# ntfy 支持参数 https://github.com/caronc/apprise/wiki/Notify_ntfy#parameter-breakdown
/home/pi/.local/bin/apprise -vv -t "测试标题" -b "测试内容" 'ntfy://klipper_test' --attach=http://192.168.0.204/webcam/?action=snapshot
# 此命令可以上传文件,但是变成 bin 文件,无法作为图像打开
显然上述无论是上传成为无法打开的 bin 文件,还是没有上传只能局域网显示的 URL 链接,都不是我们想要的结果。
小结:
/?attach 仅支持url,不会上传。shot.jpg 或者 snapshot 结尾的 url 都可以
--attach 支持本地文件和url,会上传文件,但是图片会bin,文本正常
ntfy curl 调试
我们再试试 ntfy 官方提供的上传方法:
curl \
-T /home/pi/shot.jpg \
-H "Filename: flower.jpg" \
ntfy.sh/klipper_test
curl \
-X POST \
-H "Attach: http://192.168.0.240/webcam/?action=snapshot" \
ntfy.sh/klipper_test
结果前者正常上传、显示。其中后者并不上传,仅作引用。
查看 ntfy apprise 源码
-
ntfy 的调用方法可以查看 test_plugin_ntfy.py 和 NotifyNtfy.py 两个文件。
-
ntfy 官方的 上传本地文件 Python 示例
requests.put("https://ntfy.sh/flowers",
data=open("flower.jpg", 'rb'),
headers={ "Filename": "flower.jpg" })
-
apprise 的上传文件 --attach
示例(NotifyNtfy.py)
r = requests.post(
notify_url,
params=params if params else None,
data=dumps(data) if data else None,
headers=headers,
files=files,
auth=auth,
verify=self.verify_certificate,
timeout=self.request_timeout,
)
分析如下:
-
都使用 requests 库
-
一个使用 put,一个使用 post
-
后者使用 json 传输数据
-
前者使用 data 传输图片,后者使用 files
最终怀疑问题出在 4 上可能性大,写一个测试程序:
# python3
import requests
url = 'https://ntfy.sh/klipper_test'
files = {'file': open('/home/pi/shot.jpg', 'rb')}
data = open(attach.path, 'rb'),
headers={ "Filename": "flower.jpg" }
r = requests.post(url, files=files, headers=headers)
r = requests.put(url, data=data, headers=headers)
确认上传后 jpg 图像变成 bin 的元凶就是 files
。
查看 requests 文档,关于 files 部分:
# Requests 使得上传多部分编码文件变得很简单:
files = {'file': open('report.xls', 'rb')}
# 你可以显式地设置文件名,文件类型和请求头:
files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
指定为 image/jpeg
仍然无效。临时解决方案:
find ~ -name 'NotifyNtfy.py'
/home/pi/.local/lib/python3.9/site-packages/apprise/plugins/NotifyNtfy.py
/home/pi/moonraker-env/lib/python3.9/site-packages/apprise/plugins/NotifyNtfy.py
r = requests.post(
notify_url,
params=params if params else None,
#data=dumps(data) if data else None,
data=open(attach.path, 'rb'),
headers=headers,
#files=files,
auth=auth,
verify=self.verify_certificate,
timeout=self.request_timeout,
)
/home/pi/.local/bin/apprise -vv -t "测试标题" -b "测试内容" 'ntfy://klipper_test' --attach=/home/pi/shot.jpg --attach=http://192.168.0.142:8080/shot.jpg
测试成功上传、显示。上传多个文件时,会显示多条信息。但是 /?atatch=
参数从此报错,此情景暂时不用,后续完善。
3.6 杂项
-
中间有一种思路怀疑摄像头快照地址需要是 jpg
结尾,于是试了 ip cam
,无效。
-
手动抓取快照地址保存为 jpg
,或者用 moonraker-timelapse 插件,不行。
-
ntfy 支持自行部署,官方服务器不支持身份验证,所以所有人都可以订阅你的频道查看你的信息,除非使用自建服务器。
By default, the ntfy server is open for everyone, meaning everyone can read and write to any topic (this is how ntfy.sh is configured). To restrict access to your own server, you can optionally configure authentication and authorization.
3.7 其他功能(待续)