../
来传纸条吧
==========

2022-08-30

之前有两个分别叫喵喵机和咕咕机的热敏打印机产品,里面最有趣的功能就是传纸条。可
是喵喵机的传纸条功能不知道为什么下线了,产品主打变成了学生错题打印机。不过这个
功能也不难实现,所以这里就用Telegram机器人和POS打印机自己做一个。

纸条

## 申请Telegram机器人

首先打开BotFather,用/newbot命令申请创建机器人,如图:

0002

输入完名字和ID之后会得到一个令牌(token),需要存下来。

## 选购POS打印机

市面上的POS打印机以热敏打印机为主。但是热敏打印机有两个缺点:

- 热敏纸上的涂层是一种低毒材料
- 热敏纸过几周之后就会褪色

但是好处是便宜:

0003

此外还有针式的小票打印机,只需要普通纸带,也不会褪色,常用于发票打印。可价格却
也高出不少:

0004

具体选哪种就见仁见智了。这些打印机都是通过USB口和计算机连接,在软件层面看都是一
样的。

## 测试打印机

这里我选了在Linux操作系统下使用打印机。因为Linux比较适合嵌入式系统,可以用树莓
派这样的低功耗单板电脑。此外,Linux内核里面是自带USB打印机驱动的,比Windows还要
方便。用USB线把打印机连接到电脑上之后,用dmesg命令可以查看设备信息:

    $ sudo dmesg |grep -i printer
    [ 2.249791] usb 2-2.1: Product: USB PRINTER
    [ 2.249792] usb 2-2.1: Manufacturer: Printer
    [ 3.826349] usblp 2-2.1:1.0: usblp0: USB Bidirectional printer 
        dev 4 \ if 0 alt 0 proto 2 vid 0x0483 pid 0x070B

如上述命令所示,新增了`usblp0`的打印机设备。此时`/dev/usb`目录下也会出现一个字
符设备:

    $ ls -l /dev/usb/
    total 0
    crw-rw---- 1 root root 180, 0 Aug 29 05:16 lp0

然后把这个设备的所有者改成成自己:

    sudo chown $USER:$USER /dev/usb/lp0

向这个字符设备中输入文本就可以开始打印了:

    echo "The quick brown fox jumps over the lazy dog." > /dev/usb/lp0

如果需要输入中文的话要留意,现在国内的热敏打印机大部分都是只支持GBK字符集的,需
要做编码转换:

    echo "我能吞下玻璃而不伤身体。" | iconv -i utf-8 -t gbk > /dev/usb/lp0

操作系统重启之后设备的所有者会恢复成root,需要修改udev配置:

    echo -e KERNEL=="lp0", \ SUBSYSTEM=="usbmisc", \ ACTION=="add", \ OWNER="$USER", \ GROUP="$USER" \ | sudo tee -a /etc/udev/rules.d/99-perm.rules

## 编写Telegram机器人

写Telegram机器人的话还是Python糊起来最容易!

首先下载Telegram机器人库:

    sudo pip3 install python-telegram-bot

本来讲道理应该用`virtualenv`之类的工具,不过我这里图省事直接全局安装了。

然后是代码:

    # 这里需要改成之前申请的令牌
    TOKEN='xxxxxx'

    import logging
    from time import localtime, strftime
    from telegram import ForceReply, Update
    from telegram.ext import Updater, CommandHandler, MessageHandler, Filters

    logging.basicConfig(
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        level=logging.INFO
    )

    def printer_output(content):
        # 需要以GBK编码打开设备文件
        with open('/dev/usb/lp0', 'w', encoding='gbk') as fp:
            fp.write(content)

    def start(update, context):
        user = update.effective_user
        update.message.reply_text('来传纸条吧!')

    def print_msg(update, context):
        if update.message.text is None or update.effective_user is None:
            update.message.reply_text('错误: 不支持的消息类型')
            return
        user = update.effective_user
        name = user.first_name
        if user.last_name is not None:
            name = name + ' ' + user.last_name
        # 忽略名字里面的非GBK字符
        # 或者可以遍历一下字符串,把非GBK字符变成口口口
        name = name.encode("gbk", errors='ignore').decode("gbk")
        if user.username is not None:
            name = name + ' @' + user.username
        content = '-------------------------\n'
        content = content + 'from: ' + name + '\n'
        content = content + 'date: ' + strftime("%Y-%m-%d %H:%M:%S", localtime()) + '\n\n'
        content = content + update.message.text
        content = content + '\n-------------------------'
        content = content + '\n\n\n\n'
        try:
            printer_output(content)
        except Exception as e:
            update.message.reply_text('纸条传送失败: ' + str(e))
            return
        update.message.reply_text('已送达')

    if __name__ == '__main__':
        updater = Updater(TOKEN)
        dispatcher = updater.dispatcher
        start_handler = CommandHandler('start', start)
        dispatcher.add_handler(start_handler)
        dispatcher.add_handler(MessageHandler(~Filters.command, print_msg))

        updater.start_polling()
        updater.idle()

因为国内的网络问题,机器人没有办法直接连上Telegram的API服务器,所以这里要用代理,
最方便的是`proxychains-ng`:

    # RedHat系:
    $ sudo yum install proxychains-ng

    # Debian系
    $ sudo apt-get install proxychains-ng

然后修改配置文件`/etc/proxychains.conf`,在ProxyList这一节加上你的代理地址和端
口,例如socks5一般是用localhost的1080端口:

    [ProxyList]
    # add proxy here ...
    # meanwile
    # defaults set to "tor"
    socks5  127.0.0.1 1080

最后运行机器人:

    proxychains -q python3 bot.py

顺利的话,一个可以传纸条的打印机应该就已经可以工作起来了。

## 总结和进阶

到这里,这个传纸条的功能还很有限,只能打印GBK编码的字符文本,如果需要更复杂功能,
可以看一下python-escpos。市场上几乎所有的POS打印机都是用 ESCPOS协议和计算机交互
的。利用ESCPOS协议中的图片打印功能,可以打印条形码、二维码、图片,都是可以实现
的。

对于UTF-8编码的文本,如果包含韩文、emoji等不属于GBK字符集的字符,也可以用图片打
印的功能来间接实现:自己先在电脑上渲染成图片,然后再打印。



--------------------------------------------------------------------
Email: i (at) mistivia (dot) com