DIY掌上POS机,或许是最小的收银POS机了!

Posted 铁熊玩创客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DIY掌上POS机,或许是最小的收银POS机了!相关的知识,希望对你有一定的参考价值。

封面图片

本文作者: & 铁熊

在这个共享经济时代,万物皆可共享,这其中包括了共享单车、共享充电宝、共享雨伞等服务。所有这些共享经济产品的背后,都用到了扫码支付相关的技术。

作为个人 DIY 玩家,有没有办法在自己的作品上增加扫码支付相关的功能呢?比如通过扫码支付控制设备实现不同的功能呢。想法虽然美好,但现实却是残酷的,因为大多数支付接口并不针对个人开放,个人想要实现扫码支付效果,一般只能通过第三方服务实现。本教程中,我将介绍一种第三方服务:奇迹码支付(http://pay.qj61.cn/login),它是一个独立开发者个人即时到账收款平台,可以通过监测手机通知从而验证支付状态,验证支付成功之后,我们就可以 DIY 各种扫码付费项目啦。

本教程中,我给大家制作了一个掌上 POS 机为大家演示扫码收款功能,效果如下:

Arduino实现全网最迷你收米工具

在这个项目中,我们实现了类似 POS 机的效果,不仅可以设置收款金额,而且还可以选择收款方式,比如支付宝、微信、QQ等,用户扫码后就可以实现支付。

预期目标及功能

  • 触摸键盘功能;
  • 支付图标显示;
  • 支付方式选择;
  • 二维码生成;
  • 网络状态反馈;
  • 触摸震动反馈;

所用硬件

  • M5Core2 模块

    M5Core2 具有如下特点:

  • 基于 ESP32 开发,支持 WiFi,蓝牙;

  • 16M 闪存,8M PSRAM;

  • 内置扬声器,电源指示灯,震动马达,RTC,I2S 功放,电容式触摸屏,电源键,复位键;

  • TF 卡插槽(支持最大 16GB);

  • 内置锂电池,配备电源管理芯片;

  • 独立小板内置 6 轴 IMU,PDM 麦克风;

  • M-Bus 总线插座。

程序设计

下面开始详细讲解程序设计过程。

开发环境

我们使用 Arduino IDE 来编写本项目的程序,上传程序时开发板选择 M5Stack-Core2,编程过程中需要用到的软件及库,将会打包作为附件给大家下载,详见文末下载说明。

程序思路

为了实现项目的所有功能,我们先根据预期的目标绘制思维导图,再根据思维导图逐步实现自制 POS 结算终端机的功能。

下面我们将具体讨论自制结算终端的各个子功能是如何实现的。

触摸按键测试程序

我们想要使用触摸屏实现金额的输入以及支付方式的选择,离不开设计触摸按键。M5Core2 为我们提供了成熟的解决方案,我们能够轻易地绘制一个按键,并且设置指定的区域,触摸按键的使用示例如下:

#include <M5Core2.h>

ButtonColors on_clrs = {YELLOW, WHITE, WHITE};
ButtonColors off_clrs = {BLACK, WHITE, WHITE};
Button tl(0, 0, 0, 0, false , "Button", off_clrs, on_clrs, MC_DATUM);

void setup() {
  M5.begin();
  M5.Buttons.addHandler(eventDisplay, E_ALL - E_MOVE);
  doButtons();
}

void loop() {
  M5.update();
}

void doButtons() {
  uint8_t but_w = 100;
  uint8_t but_h = 60;
  tl.set(110, 90, but_w, but_h); // 设置按键的显示坐标以及长和宽
  M5.Buttons.draw();
}

void eventDisplay(Event& e) {
  Serial.printf("%-12s finger%d  %-18s (%3d, %3d) --> (%3d, %3d)   ",
                e.typeName(), e.finger, e.objName(), e.from.x, e.from.y,
                e.to.x, e.to.y);
  Serial.printf("( dir %d deg, dist %d, %d ms )\\n", e.direction(),
                e.distance(), e.duration);
}

其中:

  • ButtonColors on_clrs = {YELLOW, WHITE, WHITE} 定义了按键按下时的颜色以及按键框的颜色;

  • ButtonColors off_clrs = {BLACK, WHITE, WHITE} 定义了按键释放时的颜色以及按键框的颜色;

  • Button tl(0, 0, 0, 0, false , "Button", off_clrs, on_clrs, MC_DATUM) 定义了按键的显示文本以及文本显示方式;

  • doButtons() 函数绘制了按钮;

  • eventDisplay(Event& e) 函数用来侦测屏幕触摸事件。

如果你想要实现更多个性化设置,请参考 M5Core2.h 库文件进行设置。

触摸按键效果测试

上传上面的测试程序,打开串口监视器,点击 M5Core2 屏幕上的触摸按键可看到下图所示内容:

可以看到:

  • 当点击程序定义的触摸按键时,串口会返回按键的标签字符串 Button 以及按下的持续时间以及坐标区域;

  • 当点击未被程序设置的区域时,返回的字符串是 background;

  • 当点击触摸屏上的另外三个默认触摸按键时,返回 BtnA,BtnB 或 BtnC。

这里我们重点关注串口打印的 e.typeName() (触发类型)和 e.objName() (触发按键名),后面我们将重点利用这两个返回值,可以根据返回值区分我们按下的每一个按键。触发类型我们关注 E_RELEASE 这个返回值,该字符串代表了按键被释放,可以用来检测按键是否点击结束。

主界面 UI 设计

知道了如何利用程序定义一个触摸按钮之后,接下来我们来设计该主界面的 UI。根据前面所学的按钮绘制以及定义方法,绘制出所有数字输入按键、支付按键、清除按键以及确认按键,结合圆角矩形绘制函数 M5.Lcd.drawRoundRect()绘制出主界面,主界面设计如下:

图标显示

显示图标有两种方式,第一种方式是单色图标,第二种方式是彩色图标。单色图标可以直接使用 M5.Lcd.drawXBitmap() 函数绘制,彩色图标则可以使用 M5.Lcd.drawBitmap() 函数进行绘制。本项目中,单色图标有 WiFi 图标以及货币的符号 ¥。

对于单色图标,可以通过取模软件 Image2Lcd 取模。在 Image2Lcd 软件中,选择需要取模的图片,根据自己的屏幕类型,调整取模方式、取模大小、亮度,最后导出取模数据。设置如下:

对于货币符号 ¥ 我们使用 Mixly 软件中的取模工具获取字模,取模设置如下:

对于彩色图片,可以使用 ImageConverter 软件获取彩色图片取模数据,其界面如下,选择需要取模的图片并调整其大小,最后导出为 C 语言取模数据即可:

按键功能以及 UI 设计

现在根据前面的按键 UI 设计示例、以及图像显示函数设计出按键的处理程序,我们先定义一个输入字符串变量 Input_data 代表输入的字符串,当我们按下数字按键以及小数点时对输入的字符进行连接,按下删除按键 DEL 删除 Input_data 的最后一个字符,按下对应的支付方式时显示对应的图标,具体程序如下:

#include <M5Core2.h>

extern const unsigned short success_icon[0x125C0];
extern const unsigned short failure_icon[0xE100];
extern const unsigned char currency[0x78];
extern const unsigned short QQ_icon[0x3A2];
extern const unsigned short WX_icon[0x384];
extern const unsigned short ZFB_icon[0x384];
extern const unsigned char wifi[0x78];

String Input_data;

ButtonColors on_clrs = {YELLOW, WHITE, WHITE};
ButtonColors off_clrs = {BLACK, WHITE, WHITE};
Button tl(0, 0, 0, 0, false , "7", off_clrs, on_clrs, MC_DATUM);
Button t2(0, 0, 0, 0, false, "8", off_clrs, on_clrs, MC_DATUM);
Button t3(0, 0, 0, 0, false, "9", off_clrs, on_clrs, MC_DATUM);
Button t4(0, 0, 0, 0, false, "QQ", off_clrs, on_clrs, MC_DATUM);
Button t5(0, 0, 0, 0, false , "4", off_clrs, on_clrs, MC_DATUM);
Button t6(0, 0, 0, 0, false, "5", off_clrs, on_clrs, MC_DATUM);
Button t7(0, 0, 0, 0, false, "6", off_clrs, on_clrs, MC_DATUM);
Button t8(0, 0, 0, 0, false, "WX", off_clrs, on_clrs, MC_DATUM);
Button t9(0, 0, 0, 0, false , "1", off_clrs, on_clrs, MC_DATUM);
Button t10(0, 0, 0, 0, false, "2", off_clrs, on_clrs, MC_DATUM);
Button t11(0, 0, 0, 0, false, "3", off_clrs, on_clrs, MC_DATUM);
Button t12(0, 0, 0, 0, false, "ZFB", off_clrs, on_clrs, MC_DATUM);
Button t13(0, 0, 0, 0, false , "0", off_clrs, on_clrs, MC_DATUM);
Button t14(0, 0, 0, 0, false, ".", off_clrs, on_clrs, MC_DATUM);
Button t15(0, 0, 0, 0, false, "DEL", off_clrs, on_clrs, MC_DATUM);
Button t16(0, 0, 0, 0, false, "CON", off_clrs, on_clrs, MC_DATUM);

void setup() {
 M5.begin();
 M5.Lcd.fillScreen(BLACK); // 设置背景颜色
 M5.Buttons.addHandler(eventDisplay, E_ALL - E_MOVE); // 注册按键动作检测
 M5.Lcd.drawRoundRect(0, 0, 315, 43, 5, WHITE); // 绘制显示文本框
 M5.Lcd.drawXBitmap(10, 6, currency, 30, 30, WHITE); // 显示货币图标
 M5.Lcd.setTextColor(WHITE, BLACK); // 设置显示文本颜色
 M5.Lcd.setTextSize(2); // 设置显示字体大小
 M5.Lcd.drawBitmap(262, 6, 30, 30, WX_icon); // 显示默认微信图标
 doButtons(); // 设置按钮属性及绘制按钮
}

void loop() {
 M5.update();
}

void doButtons() {
 uint8_t but_w = 75;
 uint8_t but_h = 43;
 tl.set(0, 48, but_w, but_h);
 t2.set(80, 48, but_w, but_h);
 t3.set(160, 48, but_w, but_h);
 t4.set(240, 48, but_w, but_h);
 t5.set(0, 96, but_w, but_h);
 t6.set(80, 96, but_w, but_h);
 t7.set(160, 96, but_w, but_h);
 t8.set(240, 96, but_w, but_h);
 t9.set(0, 144, but_w, but_h);
 t10.set(80, 144, but_w, but_h);
 t11.set(160, 144, but_w, but_h);
 t12.set(240, 144, but_w, but_h);
 t13.set(0, 192, but_w, but_h);
 t14.set(80, 192, but_w, but_h);
 t15.set(160, 192, but_w, but_h);
 t16.set(240, 192, but_w, but_h);
 M5.Buttons.draw();
}

void eventDisplay(Event& e) {
 Serial.printf("%-12s finger%d  %-18s (%3d, %3d) --> (%3d, %3d)   ",
               e.typeName(), e.finger, e.objName(), e.from.x, e.from.y,
               e.to.x, e.to.y);
 Serial.printf("( dir %d deg, dist %d, %d ms )\\n", e.direction(),
               e.distance(), e.duration);
 if (String(e.objName()).equals(String("BtnA"))) {
   // 执行BtnA事件
   delay(50);
 } else {
   if (String(e.objName()).equals(String("BtnB"))) {
     // 执行BtnB事件
     delay(50);
   } else {
     if (String(e.objName()).equals(String("BtnC"))) {
       // 执行BtnC事件
       delay(50);
     } else {
       if (String(e.typeName()).equals(String("E_RELEASE"))) { // 检测是否释放屏幕
         if (!String(e.objName()).equals(String("background"))) { // 检测是否为已注册按键区域

           if (String(e.objName()).equals(String("QQ"))) { // 检测是否为按下QQ按键
             Serial.println("QQ");
             M5.Lcd.drawBitmap(262, 6, 30, 31, QQ_icon); // 显示QQ图标
             delay(50);
           } else {
             if (String(e.objName()).equals(String("WX"))) { // 检测是否为按下WX按键
               Serial.println("WX");
               M5.Lcd.drawBitmap(262, 6, 30, 30, WX_icon); // 显示微信图标
               delay(50);
             } else {
               if (String(e.objName()).equals(String("ZFB"))) { // 检测是否为按下ZFB按键
                 Serial.println("ZFB");
                 M5.Lcd.drawBitmap(262, 6, 30, 30, ZFB_icon); // 显示支付宝图标
                 delay(50);
               } else {
                 if (String(e.objName()).equals(String("CON"))) { // 检测是否为按下CON确认按键
                   Serial.println("CON");
                   if (!Input_data.equals(String("")) && Input_data.toFloat() > 0) { // 当输入不为空且数字大于0生成订单
                     Serial.println("Amount:" + Input_data);
                     // 生成订单
                   }
                   delay(50);
                 } else {
                   if (String(e.objName()).equals(String("DEL"))) { // 检测是否为按下DEL删除按键,按下删除末尾字符
                     Input_data = String(Input_data).substring(0, (String(Input_data).length() - 1)); // 删除输入字符串末尾字符
                     M5.Lcd.fillRoundRect(40, 0, 180, 43, 5, BLACK); // 清空显示字符串
                     M5.Lcd.drawRoundRect(0, 0, 315, 43, 5, WHITE); // 重新绘制文本显示框
                     M5.Lcd.setCursor(40, 33); // 设置文本显示坐标
                     M5.Lcd.printf(Input_data.c_str()); // 显示输入文本
                     delay(50);
                   } else {
                     if (String(Input_data).length() <= 7) { // 检测到数字按键及小数点
                       Input_data = String(Input_data) + String(e.objName()); 通过OSS,POS机数据也可以上机器学习挖掘数据价值

POS系统触屏故障校准处理

哗啦啦系统POS终端设备安装

Android 滑动过程中dy是如何计算的 pos1-pos2 ? pos2-pos1?

哗啦啦收银系统故障收集

移动POS机