蓝牙热敏打印机:php+ios+android

第一篇文章吧,少BB直接开始吧。

先直接上结果图:

image

先说需求吧,公司要求订单推送到商户端 APP,能够同步打印出小票。然后领导的要求能够适配市面上大多数蓝牙热敏打印机,然后就采购了一批打印机,什么得力,芯华,美达罗捷,佳博等等一共八牌子的,反正我都没听过,都是便宜货你懂的。

先说通常的思路吧,我在网上搜了一堆资料,多数的 Demo 和思路是,数据到客户端,然后在端完成数据的排版,然后通过蓝牙 socket 发送打印命令给打印机完成打印。这里就不再介绍关于客户端蓝牙链接和打印命令的东西了,有大神都整理的非常清楚。下面贴出来我觉的比较好的。

ios 端:https://www.jianshu.com/p/2d624044a27b

          https://www.jianshu.com/p/90cc08d11b5a

android 端:https://www.jianshu.com/p/c0b6d1a4823b

这里我发现 ios 端大神写的 Demo 有些适配的问题:\x1d\x21 对应的 ascii 吗是GS! 不是 对所有的打印机都适配的。多数的做法是用 \x1b\x21。这里涉及的计算机一些基础的字节知识:诸如16进制,2进制,8进制,高位地位,ascii 码以及之间的各种转换。就不去科普了。我也是再这次工作中从头学过的。

image

再说我们的思路吧:

上面的思路肯定是没问题的了,但是面临一个问题就是,如果产品的需求变更,要求的小票的样式更改,或者要求打各种样式,那问题就来了,需要对客户端进行再次开发升级发包。我们的思路是通过服务端生成好的打印命令传输给客户端,然后android 和 ios 只要忠实的执行写入命令就行了 以后产品如果想改小票样式 或再变态的需求。我只要在服务端更改就可以了,两个客户端不用动弹,产品的天马行空,各位程序员都懂的。

开始我是打算自己封装一个 php 打印类写了,做了开始,在 github 发现有第三方的。

github地址:https://github.com/mike42/escpos-php 这个非常全,各种打印,想怎么打怎么打。

我自己封装的 php:

// 字体大小
const JsonLi_FontSizeSmall = "\x00";
const JsonLi_FontSizeMiddle = "\x11";
const JsonLi_FontSizeBig = "\x22";

// 对齐方式
const JsonLi_Left = "\x00";
const JsonLi_Center = "\x01";
const JsonLi_Right = "\x02";

const JsonLi_WIDTH_PIXEL = 384;
const JsonLi_IMAGE_SIZE = 320;


class PrintDeviceUtil
{
    public $printWhiteString;


    function __construct() {
        $this->initPrintDevice();
    }

        /**
         * 初始化打印机
         */
        protected function initPrintDevice(){
//            $this->printWhiteString = "\x1b\x40\x1b\x32\x1b\x4d\x00";
            $this->printWhiteString = "\x1b\x40";

        }

        /**
         * 设置字体大小
         */
        protected function setTextFont($size) {
            $this->printWhiteString .= "\x1d" . "\x21" . $size;
        }

        /**
         * 设置对齐方式
         */
        protected function setAlignment($alignment) {
            $this->printWhiteString .= "\x1b" . "\x61" . $alignment;
        }

        /**
         * 换一行
         */
        protected function addNewLine() {
             $this->addMuiltNewLine(1);
        }

        /**
         * 换多行
         */
        protected function addMuiltNewLine($linenum) {
            for ($i = 0; $i < $linenum; $i++) {
                $this->printWhiteString .= "\x0A";
            }
        }

        /*
         * 添加分割线
         */
        protected  function addDividingLine ()
        {
            $this->setAlignment(JsonLi_Center);
            $this->setTextFont(JsonLi_FontSizeSmall);
            $this->printWhiteString .= "- - - - - - - - - - - - - - - -";
            $this->addNewLine();
        }

        /**
         * 设置文本
         */
        protected function setText($text) {
            $this->printWhiteString .= iconv("UTF-8","GB2312//IGNORE",$text);
        }

        /*
         * 获取偏移量
         */
        protected function getOffset($str) {
            return JsonLi_WIDTH_PIXEL - mb_strwidth($str);
        }

        /*
         * 设置偏移量
         */
        protected function setOffset($str) {
            $offset = $this->getOffset($str);
            $remainder = $offset % 256; // 低位
            $consult = $offset >> 8; // 高位
            $offsetBytes = "\x1B\x24" . chr($remainder) . chr($consult);
            $this->printWhiteString .= $offsetBytes;
        }




    public function testPrint(){
        $this->setAlignment(JsonLi_Center);
        $this->setTextFont(JsonLi_FontSizeBig);
        $this->setText("泛客云商");
        $this->addNewLine();

        $this->addDividingLine();

        $this->setAlignment(JsonLi_Left);
        $this->setTextFont(JsonLi_FontSizeSmall);
        $this->setText("时间:");


        $value = "2018-01-01";
        $this->setOffset($value);
        $this->setText($value);
        $this->addNewLine();


        $this->setAlignment(JsonLi_Left);
        $this->setTextFont(JsonLi_FontSizeSmall);
        $this->setText("订单号:");
        $this->setAlignment(JsonLi_Right);
        $this->setTextFont(JsonLi_FontSizeSmall);
        $this->setText("XXXXXXXXXXX");
        $this->addNewLine();

        $this->setAlignment(JsonLi_Left);
        $this->setTextFont(JsonLi_FontSizeSmall);
        $this->setText("付款人:");
        $this->setAlignment(JsonLi_Right);
        $this->setTextFont(JsonLi_FontSizeSmall);
        $this->setText("XXXXXXXXXXX");
        $this->addNewLine();


        $this->addDividingLine();


        $this->addMuiltNewLine(3);





//        $result = $this->printWhiteString . iconv("UTF-8","GB2312//IGNORE","泛客云商") ;
//        $printWhiteString = $printWhiteString . "测试测试";

        //return $this->printWhiteString;
        return base64_encode($this->printWhiteString);

//        return strlen("我爱上海");

//        $hex = '';
//        for ($i = 0; $i < strlen($this->printWhiteString); $i++) {
//            $hex .= sprintf("%02x", ord($this->printWhiteString[$i]));
//        }
//
//        $hex .= PHP_EOL;
//
//        return $hex;

//        return $this->printWhiteString;
    }





}


$p = new PrintDeviceUtil;
echo $p->testPrint();

使用第三方封装的:

<?php


require __DIR__ . '/vendor/autoload.php';

use Mike42\Escpos\PrintConnectors\FilePrintConnector;
use Mike42\Escpos\Printer;
use Mike42\Escpos\EscposImage;

class BTPrinter extends Mike42\Escpos\Printer
{
    const kPageWidth = 32;

    protected $textWidth;
    protected $bufferFile;

    public function __construct()
    {
        //创建临时文件
        $this->bufferFile = tempnam('/tmp', 'Printer');
        $this->textWidth = static::kPageWidth;

        //连接临时文件作为打印缓存
        $connector = new FilePrintConnector($this->bufferFile);
        parent::__construct($connector);

        //程序结束后删除临时文件
        register_shutdown_function(function () {
            unlink($this->bufferFile);
        });
    }


    /**
     * @return int
     */
    public function getTextWidth()
    {
        return $this->textWidth;
    }

    public function initialize()
    {
        parent::initialize();
        $this->textWidth = static::kPageWidth;
    }


    public function setTextSize($widthMultiplier = 1, $heightMultiplier = 0)
    {
        if ($heightMultiplier === 0) {
            $heightMultiplier = $widthMultiplier;
        }

        $this->textWidth = floor(static::kPageWidth / $widthMultiplier);
        parent::setTextSize($widthMultiplier, $heightMultiplier);
    }

    public function text($str = "", $newline = false)
    {
        parent::text($str);
        if ($newline) {
            $this->newline();
        }
    }

    public function textChinese($str = "", $newline = false)
    {
        parent::textChinese($str);
        if ($newline) {
            $this->newline();
        }
    }

    public function textLabel($label, $text)
    {
        $space = $this->textWidth - mb_strwidth($label);
        $this->textChinese($label);
        if (mb_strwidth($text) > $space) {
            $this->newline();
        } else {
            $text = mb_str_pad($text, $space, ' ', STR_PAD_LEFT);
        }
        $this->textChinese($text, true);
    }

    public function writeRaw($text)
    {
        $this->connector->write($text);
    }

    public function separator($sep = '-', $text = '')
    {
        $this->textChinese(mb_str_pad($text, $this->textWidth, $sep, STR_PAD_BOTH), true);
    }

    public function newline()
    {
        $this->text("\n");
    }

    public function flush()
    {
        $this->close();
        return file_get_contents($this->bufferFile);
    }

    public function flushBase64()
    {
        return base64_encode($this->flush());
    }

    public function flushHex()
    {
        return bin2hex($this->flush());
    }


}

if (!function_exists('mb_sprintf')) {
    function mb_sprintf($format)
    {
        $argv = func_get_args() ;
        array_shift($argv) ;
        return mb_vsprintf($format, $argv) ;
    }
}

if (!function_exists('mb_vsprintf')) {
    /**
     * Works with all encodings in format and arguments.
     * Supported: Sign, padding, alignment, width and precision.
     * Not supported: Argument swapping.
     */
    function mb_vsprintf($format, $argv, $encoding=null)
    {
        if (is_null($encoding))
            $encoding = mb_internal_encoding();

        // Use UTF-8 in the format so we can use the u flag in preg_split
        $format = mb_convert_encoding($format, 'UTF-8', $encoding);

        $newformat = ""; // build a new format in UTF-8
        $newargv = array(); // unhandled args in unchanged encoding

        while ($format !== "") {

            // Split the format in two parts: $pre and $post by the first %-directive
            // We get also the matched groups
            list ($pre, $sign, $filler, $align, $size, $precision, $type, $post) =
                preg_split("!\%(\+?)('.|[0 ]|)(-?)([1-9][0-9]*|)(\.[1-9][0-9]*|)([%a-zA-Z])!u",
                    $format, 2, PREG_SPLIT_DELIM_CAPTURE) ;

            $newformat .= mb_convert_encoding($pre, $encoding, 'UTF-8');

            if ($type == '') {
                // didn't match. do nothing. this is the last iteration.
            }
            elseif ($type == '%') {
                // an escaped %
                $newformat .= '%%';
            }
            elseif ($type == 's') {
                $arg = array_shift($argv);
                $arg = mb_convert_encoding($arg, 'UTF-8', $encoding);
                $padding_pre = '';
                $padding_post = '';

                // truncate $arg
                if ($precision !== '') {
                    $precision = intval(substr($precision,1));
                    if ($precision > 0 && mb_strwidth($arg, $encoding) > $precision)
                        $arg = mb_substr($precision,0,$precision,$encoding);
                }

                // define padding
                if ($size > 0) {
                    $arglen = mb_strwidth($arg, $encoding);
                    if ($arglen < $size) {
                        if($filler==='')
                            $filler = ' ';
                        if ($align == '-')
                            $padding_post = str_repeat($filler, $size - $arglen);
                        else
                            $padding_pre = str_repeat($filler, $size - $arglen);
                    }
                }

                // escape % and pass it forward
                $newformat .= $padding_pre . str_replace('%', '%%', $arg) . $padding_post;
            }
            else {
                // another type, pass forward
                $newformat .= "%$sign$filler$align$size$precision$type";
                $newargv[] = array_shift($argv);
            }
            $format = strval($post);
        }
        // Convert new format back from UTF-8 to the original encoding
        $newformat = mb_convert_encoding($newformat, $encoding, 'UTF-8');
        return vsprintf($newformat, $newargv);
    }
}


if (!function_exists('mb_str_pad')) {
    function mb_str_pad($str, $pad_length, $pad_string, $pad_type = STR_PAD_RIGHT, $encoding = null)
    {
        if ($encoding === null) {
            $encoding = mb_internal_encoding();
        }

        $w = mb_strwidth($str, $encoding);
        $len = strlen($str);
        $pad_length += $len - $w;
        return str_pad($str, $pad_length, $pad_string, $pad_type);
    }
}


/**
 * 服务端以下代码放到 Ctroller 里面。
 * 初始化BTPrinter得到$printer对象,排版填充数据后调用$printer->flushBase64()方法得到 base64数据,api返回给客户端即可。
 * (注(服务端不用关心这句):$printer->flush 返回的是byte 数据,客户端经过Base64.decode即是该数据,通过bluesocket 发送打印机执行打印)
 * json_li 2018-1-10 12:01
 */

/*
 * 附录:下面的代码如下打印样式:
 * &----------泛客云商订单----------.
&窗边小豆豆的奶茶铺子.
!&【外卖】.
!&--------------------------------.
&下单时间:.&    2018-01-10 12:00:06.
&订单编号:.&         BU20171829UUKL.
&付款人:.&                    cfgxy.
&--------------------------------.
&商品            数量        单价.
&苹果            2          10.02.
&香蕉            3           9.03.
&商品名称比较长比较长比较长比较长长比较长长比较长长比较长长比较长长长长长长长长长.
&                1          10.00.
&--------------------------------.
&名称1:.&                    value1.
&--------------------------------.
&名称2:.&                    value2.
&--------------------------------.
&名称3:.&                    value3.
&--------------------------------.
!&地址:合肥市蜀山区怀宁路888号天睿大厦万家热线菜鸟级工程师json_li测试打印比较长的地址会怎样 so?fucking you!.
!&------------订单结束------------.
 */

$printer = new BTPrinter();
$printer->initialize();//打印机初始化

//Section: 表头
$printer->separator('-', '商品订单');

//Section: 店铺名
$line = '窗边小豆豆的奶茶铺子';
$printer->setJustification(Printer::JUSTIFY_CENTER);
$printer->textChinese($line, true);

//大字
$printer->setTextSize(2);

//Section: 外卖 / 自提
$line = '【外卖】';
$printer->textChinese($line, true);

//恢复默认
$printer->setTextSize();
$printer->setJustification();

$printer->separator('-');

//Section: 下单时间
$printer->textLabel('下单时间:', date('Y-m-d H:i:s'));

//Section: 订单编号
$printer->textLabel('订单编号:', "BU20171829UUKL");

//Section: 付款人
$printer->textLabel('付款人:', " cfgxy");

$printer->separator('-');


//Section: 表头
$printer->textChinese(mb_sprintf("%-16s%-6s%10s", "商品", "数量", "单价"), true);

//Section: 商品列表
$list = [
    [
        'name'      => '苹果',
        'amount'    => 2,
        'price'     => number_format(10.023, 2)
    ],
    [
        'name'      => '香蕉',
        'amount'    => 3,
        'price'     => number_format(9.026, 2)
    ],
    [
        'name'      => '商品名称比较长比较长比较长比较长长比较长长比较长长比较长长比较长长长长长长长长长',
        'amount'    => 1,
        'price'     => number_format(10, 2)
    ]
];

foreach ($list as $item) {
    if (mb_strwidth($item['name']) > 16) {
        $printer->textChinese($item['name'], true);
        $printer->textChinese(mb_sprintf("%-16s%-6d%10s", "", $item['amount'], $item['price']), true);
    } else {
        $printer->textChinese(mb_sprintf("%-16s%-6d%10s", $item['name'], $item['amount'], $item['price']), true);
    }
}

$printer->separator('-');

//Section: 两列
$printer->textLabel('名称1:', "value1");
$printer->separator('-');

//Section: 两列
$printer->textLabel('名称2:', "value2");
$printer->separator('-');

//Section: 两列
$printer->textLabel('名称3:', "value3");
$printer->separator('-');

//Section: 地址
$printer->setTextSize(2);//大字
$line = '地址:合肥市菜鸟级工程师Jason测试打印比较长的地址会怎样长长长长长长长长长长长长长长长长长长长长长长长长 so?fucking you!';
$printer->setJustification(Printer::JUSTIFY_CENTER);
$printer->textChinese($line, true);
//恢复默认
$printer->setTextSize();
$printer->setJustification();

//Section: 表尾
$printer->separator('-', '订单结束');


$printer->feed(2);
// 切纸(多数大打印机不支持)
$printer->cut();

echo $printer->flushBase64();

ios 端我是抄上面那位大神的,稍作修改而已,他写的非常全。这里不赘述了。

还要板砖, 先写这里,方法和思路就这样。有问题的可以多沟通。

哦,最后分享一个今天发现一个很全的 linux 命令的网站:

http://man.linuxde.net

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • WebSocket-Swift Starscream的使用 WebSocket 是 HTML5 一种新的协议。它实...
    香橙柚子阅读 23,798评论 8 183
  • 曾经的我,因为太多不太正向的事,一直怨愤难平,直到一个朋友告诉我: 你所憎恨的,就是活在你心中的阴影,因为憎恶根本...
    舒一桐阅读 184评论 3 3
  • YS17017周鹏 【连续2天打卡】 A、今日完成情况 @信念三篇 3遍 完成100% @绕口令5篇 完成120...
    周鹏YS17017阅读 153评论 4 1