商米收银管家 TEST 隐藏

商米收银管家 TEST 隐藏商米收银管家 TEST 隐藏商米收银管家 TEST 隐藏

扫码头返回类型


Number Code Type CodeID Zebra CodeID Newland CodeID Fp/NL Notice   Example  
1 Code128 D j j Newland,Fp/NL:
AIM-128 — f
SETTING 128 — t
     
2 UCC·EAN128(GS1-128)   j u        
3 ISBT
128
D j  j  Zebra:
ISBT 128 Concatenated — D
     
4 EAN8 A d g        
5 EAN13 A d d        
6 UPC-E A c h        
7 UPC-E1 A            
8 UPC-A A c c   Instruction
9 Interleaved
2 of 5(ITF)
F e e Newland,:
ITF-6 —  e
ITF-14 — e 
Fp/NL:
ITF-6 —  r
ITF-14 — q
Head noun Paraphrase Scanner ID
10 Matrix
2 of 5
S v v   Nls NewLand (EM2096) ]N
11 Code39 B b b Zebra:
Trioptic Code 39 — M 
Zebra Zebra (4710) ]Z
12 Codabar C a a   Fp(Falcon) Falcon(BSM1825) ]FN
13 Code93 E i y   NL NewLand(EM1365) ]FN
14 GS1
DataBar(RSS)
R R R        
15 Composite-UCC T     Zebra:
MiroQRTCIF Linked Code 39(TLC 39) — T 
     
16 Composite-UPC              
17 Code11 H H z        
18 ISBN(Bookland
EAN)
L B B   Return: Scanner ID+Code ID  
19 Industrial
2 of 5
  D i        
20 Standard
2 of 5
  s s   Example:
scanner:Zebra
code content:123456
code type:code 128

return:]ZD123456

21 Discrete
2 of 5(DTF)
G            
22 Chinese
2 of 5
U            
23 Korea
3 of 5
V            
24 Plessey   p p Newland:
UK Plessey — p
     
25 MIS-Plessey J m m        
26 Composite
A/B
             
27 Composite
C
             
28 ISSN
EAN
X n n        
29 PDF417 X r          
30 QR
Code
P01 Q          
31 Aztec z            
32 DataMatrix P00 u          
33 HanXin P0H h          
34 MaxiCode P02            
35 AustralinPostal P08            
36 US
Postnet
P03            
37 US
Planet
P04            
38 Uk
Postal
P06            
39 Japan
Postal
P05            
40 Deutsche
12
  l l        
41 Deutsche
14
  w w        
42 Code32 B b b         
43 Netherlands
KIX Code
P08            
44 USPS
4CB/One Code/Intelligent Mail
P0A            
45 UPU
FICS Postal
P0B            
46 Signature
Capture
P0X            
47 Coupon
Code
N            

自定义客显程序开发说明


自定义客显程序开发说明


更新内容

2018-01-15
  • 去掉常用方法、关键类说明;
  • 更新Demo源码;
  • 更新双屏应用开发调试的方法;




一、简介

T1双屏机器有三种组合:主机、主机+7寸副屏、主机+14寸副屏。主副屏都是运行SUNMI OS定制系统,通过商米已封装好的接口实现通信。主屏主要用来运行业务APP,例如:收银系统。副屏主要面向顾客显示结算、广告内容。

双屏通信原理:

image

从下到上依次为:

  • Driver:该层是最底层的通信协议,开发者无需关注。

  • Service:该层是商米封装的一个通信服务,开发者也无需关注。

  • SDK:该层是T1自带的副显程序使用的通信接口,自定义的客显程序调用的是该层的接口。

  •  APP:指的是开发者自己的业务app和内置的副显程序。

开发者有两种方式实现副屏显示:

  • T1副屏系统内置了默认副屏显示APP,内置多个常用模板。开发者仅需通过商米的SDK向副屏发送正确格式的数据,即可实现副屏显示内容。
  • 开发自己副屏显示APP,需要自行处理数据的收发、内容显示等动作;

虽然商米封装的内置客显程序能满足绝大部分开发者的使用需求,但是考虑业务的多样性,商米秉持开放的原则,支持开发者自己定义副屏显示。

下文将对开发自定义双屏应用进行说明。

二、如何调试应用


由于主副屏是通过USB通信的,当主/副屏插入USB线时,主副屏的连接会断开,导致无法调试设备。商米提供的解决方案是:将开发电脑与将要调试的T1主机处于同一个局域网络环境下,通过网络对设备进行调试。

如何调试:

1、开启USB调试,及调试权限;

    操作说明:http://docs.sunmi.com/htmls/index.html?lang=zh##调试设备

2、通过网络对设备进行ADB调试;

    操作说明:http://docs.sunmi.com/htmls/调试说明.html 

三、如何开发自定义双屏应用


(强调:Sunmi OS是基于Android6.0定制,Android6.0+ 要求部分敏感权限需要动态申请)


1、初始化配置

以下将基于Demo讲解自定义双屏应用如何实现。

步骤1:

    下载DoubleAPP资源文件。(源码使用AndroidStudio编写)

步骤2:

    参照Demo源码,直接在Android Studio的app module下的build.gradle文件中声明以下代码。

compile 'com.sunmi:DS_Lib:1.0.9'  //商米提供的lib库,包含已封装好的接口
compile 'com.google.code.gson:gson:2.6.2'  //gson任意版本

步骤3:

    在清单文件AndroidMainfest.xml的节点下注册广播(开发者可参考Demo源码,自行实现广播类)。

"...广播类...">  //接收数据的广播

"com.sunmi.hcservice"/>
"com.sunmi.hcservice.status"/>

步骤4:

    在适当的位置初始化SDK代码,可以参考Demo源码。

private void initSdk() {
mDSKernel = DSKernel.newInstance();  
mDSKernel.init(this, mIConnectionCallback);  //绑定服务的回调
mDSKernel.addReceiveCallback(mIReceiveCallback);  //双屏通信接收数据回调
}

2、开发双屏应用

参考Demo源码,实现双屏数据交互。具体方法可以自行定义,此处不做示例。

当主屏与副屏的业务逻辑都已经实现了,需要将主屏与副屏的业务代码合在一个apk里(因为应用市场的安装逻辑是将一个应用分别安装到主副屏,故双屏应用只需提交一个APK)。

四、发布应用前的准备

自定义双屏应用已经开发好了,那接下来就要将应用发布至商米应用市场,分发出去。在此之前,需要再做一件重要的步骤,就是让应用市场能够识别该应用是双屏应用。

在应用的AndroidManifest.xml中添加一行标识代码,这样,该应用在应用市场上将显示为双屏应用,安装时,应用市场会将该应用分别安装在主屏和副屏。



"sunmi_dual" android:value="open"/>

调试说明

T1 调试说明

背景

  • 为什么需要通过网络调试?

    目前T1主屏与副屏通过USB连接通信,主屏作为主设备,副屏作为从设备,USB外设也是从设备。当PC通过USB线连接主屏(或副屏)进行调试时,PC作为主设备,主屏(或副屏)会作为从设备,此时主屏与副屏的通信会断开,外接USB设备的连接也会断开。因此,PC通过连USB线只能单独调试主屏或是副屏,并且会导致USB外设连接断开,也就是无法调试同时用到主副屏的应用,也无法调试使用到USB外设的应用。

  • 商米提供了解决以上问题的解决方案:

    PC通过有线或无线局域网连接T1设备,可以在不影响T1双屏通信及外设使用的情况下,对使用T1双屏或是使用USB外设的应用进行调试。

调试原理

å--å±-ADBè°-è¯-

说明:

  • T1的主副屏已经通过内置USB线连接,无需另外再通过USB线连接;
  • PC需要通过网络连接T1双屏机器(主屏);
  • 调试主屏:PC直接通过WiFi/有线网络调试主屏;
  • 调试副屏:PC先通过网路连接主屏,通过主屏桥接来调试副屏;

前置条件:

  1. WiFi/有线局域网,网络连接正常;
  2. PC与T1主屏在同一个局域网内;
  3. T1双屏系统版本均支持双屏ADB调试;(14主屏:V1.11.4及以上,14寸副屏:V1.8.3及以上)
  4. T1主屏开启USB调试;(T1主屏系统设置-开发者设置-USB调试 开启)
  5. T1双屏机器上不要外接USB调试线;(主屏/副屏外接USB调试线会导致双屏通讯断开,将只能调试其中一块屏幕)
  6. PC支持ADB调试环境;
  7. T1主副屏通讯正常;(打开主屏“副屏设置”,可查看副屏的系统版本信息)

操作步骤:

1.获取主屏IP地址

    在T1主屏系统设置-关于-状态信息查看主屏IP,记录IP地址。

    例如:

192.168.1.12

2.添加终端-主屏

    在PC上打开终端命令行工具,输入以下命令:

adb connect 192.168.1.12:5555

    说明:“192.168.1.12”要替换为记录的主屏IP地址,主屏对应的端口号是“5555”,不可修改。此时应该显示:Connected to 192.168.1.12:5555,表示已连接上主屏。

3.添加终端-副屏接步骤2,输入以下命令:

adb connect 192.168.1.12:5554

    说明:“192.168.1.12”要替换为记录的主屏IP地址,副屏对应的端口号是“5554”,不可修改。此时,应该会显示:Connected to 192.168.1.12:5554,表示已连接上副屏。
强调:必须先连接上主屏,才能连接副屏。


4.查询已添加的终端

    接步骤2或者步骤3,输入以下命令:

adb devices

    说明:此时,应该会显示1~2个终端设备。

5.调试指定终端

    接步骤3,调试主屏需输入以下命令:

adb -s 192.168.1.12:5555 shell ls

    说明:“192.168.1.12”要替换为主屏的IP地址,主屏对应的端口号是“5555”,不可修改。此时,应当显示主屏存储的目录。“shell ls”可以替换为其它的ADB命令。


    接步骤3,调试副屏需输入以下命令:

adb -s 192.168.1.12:5554 shell ls

    说明:“192.168.1.12”要替换为主屏的IP地址,副屏对应的端口号是“5554”,不可修改。此时,应当显示副屏存储的目录。“shell ls”可以替换为其它的ADB命令。

6.断开终端连接

    接步骤3,断开主屏副屏需输入以下命令:

adb disconnect 192.168.1.12:5555

    说明:“192.168.1.12”要替换为主屏的IP地址,主屏对应的端口号是“5555”,不可修改。此时,应当同时断开主屏和副屏的连接。
    强调:断开主屏连接时,会同时断开副屏连接。



示意图

添加终端:

WechatIMG53

调试指定终端-主屏:

WechatIMG39

调试指定终端-副屏:

WechatIMG40

断开终端连接-主屏和副屏:

截图 2017-07-13 12时15分48秒

USB设备

                                                           USB、串口设备使用说明文档



1. 简介:

外设接口 LAN 口,钱箱口, usb2.0 口,串口,耳机孔等通用通信接口。
本文主要介绍钱箱口,usb 口和串口的使用方式。

图片 2


2.接口说明


1)钱箱口:

钱箱口使用RJ12接口。
开发者可以通过向钱箱口发送数据来控制钱箱。

2)串口:
 串口使用 RJ11 接口。
开发者可以通过串口的数据发送来控制外设。 由于安全考虑,不支持串口节点遍历,但可以直接打开端口进行通信
(机器底座串口的节点路径/dev/ttyHSL1) 

通信文档事例:Jaynes 串口电子秤通信文档(不同电子秤通信协议需参考每家厂商提供的
文档)

3) USB 口:

 支持 USB2.0 协议的 USB 设备。

    

3.1 获取 USB 设备的 PID/VID:

方法1:java代码

try {
//获得外接USB输入设备的信息
Process p=Runtime.getRuntime().exec("cat /proc/bus/input/devices");
BufferedReader in = new BufferedReader(new
InputStreamReader(p.getInputStream()));
String line = null;
while((line = in.readLine())!= null){
}
String deviceInfo = line.trim();
//对获取的每行的设备信息进行过滤,获得自己想要的。
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
*deviceinfo中包含每个设备的pid和vid* 

方法2:adb命令 
adb shell cat /proc/bus/input/devices

图片 1
3.2 通用 USB 外设
  • HID设备
默认支持 HID 协议设备(鼠标键盘扫码枪等),可即插即用。 
HID 扫码枪

HID扫码枪可即插即用:连接商米设备,在商米设备上打开一个可编辑框并获取焦点,扫码。此时编辑框应该被输入条码或者二维码内容。从代码获取扫码内容可参考USB 外设通信谷歌开发者文档

  • U盘

支持的U盘格式:
 FAT32:可读可写;NTFS:可读不可写 ;exFAT:不支持

  • 摄像头

商米支持 USB UVC 摄像头 (如LogitechC170)

  • 商米读写器
侧边栏可接入商米读卡器(刷卡槽接口)

66D031F8-2666-49ED-8E7A-A6C88572BEE7

商米读卡器包含两部分,一部分为磁条卡刷卡,磁条卡刷卡可参考商米读卡器开发包和读卡器demo。另一部分为nfc,nfc为android原生接口,可参考android nfc开发文档

商米读卡器开发包
商米读卡器demo
商米读卡器demo源码
  • 第三方通用读写器

商米目前已经支持四款第三方usb通用读写器,开发者可参考商米提供的demo做读卡写卡操作。

  1. 上海亿矽智能科技有限公司UM002读卡器
  2. 深圳市德卡科技有限公司T10读写器(需支持android版本,电源充电版)
  3. 香港龙杰智能卡有限公司ACR1281U-C1读写器
  4. 香港龙杰智能卡有限公司ACR1281U-K1读写器

开发文档2.0

通用读写器demo

通用读写器demo源码

T1双屏通信接口文档

关键类说明


包 sunmi.ds

DSKernel类:SDK核心类,该类暴露了向副屏发送数据、初始化SDK的函数
  • newInstance(): DSKernel


说明:实例化DSKernel类的静态函数。

  • getDSDPackageName(): String


说明:获取副屏接收数据的app包名的静态函数。 
返回值:packageName,副屏负责接收数据的App包名

  • init(Context contetx, IConnectionCallback stateCallback, String vicePackageName): void


说明:初始化SDK。 

参数: 

contetx:android上下文对象。
 stateCallback:与副屏的连接状态回调。 
vicePackageName:副屏接收数据的app包名,传null或空字符串则默认接收数据的app为Sunmi的副屏默认App。

  • init(Context contetx, IConnectionCallback stateCallback): void


说明:初始化SDK。

 参数: 

contetx:android上下文对象。 
stateCallback:与副屏的连接状态回调。

  • isConnected(): boolean


说明:判断双屏通讯连接是否畅通 
返回值:isConn true为畅通,false为断开。

  • sendData(DataPacket pack): void


说明:发送数据。

 参数:

 pack:DataPacket类型的对象,封装了要发送的数据。

  • sendCMD(String recePackageName, String cmd, long fileId, ISendCallback callback): void


说明:发送CMD命令包,可指定要使用的缓存文件id。 
参数:
 recePackName:接收端包名。 

cmd:命令。
fileId:要使用的缓存文件id,如果没有则传0。 

callback:发送结果回调。


  • sendCMD(DataPacket dataPacket): void


说明:发送CMD命令包,可指定要使用的缓存文件id。 

参数:
dataPacket:CMD类型数据包。


  • sendQuery(DataPacket mPack, QueryCallback callback): void


说明:发送数据,向副屏发送查询数据包时使用。

 举例:在用户协议层,主屏向副屏发送一个表示查询副屏亮度的Query数据包,副屏App收到后获取亮度再调用
sendResult(long queryId)函数向主屏发回一个携带亮度的结果数据包,此Result数据包的queryId必须与Query
数据包的taskId一致,主屏才能识别到是之前的查询结果。

 注意:使用QueryCallback接收结果数据包时,通过addReceiveCallback()注册的回调实例将不会被调用。 

参数:
pack:DataPacket类型的对象,封装了要发送的数据。
callback 查询结果回调


  • sendQuery(String recePackageName, String queryStr, ISendCallback sendCallback, QueryCallback callback): void

说明:发送数据,向副屏发送查询数据包时使用。
举例:在用户协议层,主屏向副屏发送一个表示查询副屏亮度的Query数据包,副屏App收到后获取亮度再调用
sendResult(long queryId)函数向主屏发回一个携带亮度的结果数据包,此Result数据包的queryId必须与Query
数据包的taskId一致,主屏才能识别到是之前的查询结果。 

注意:使用QueryCallback接收结果数据包时,通过addReceiveCallback()注册的回调实例将不会被调用。 

参数:
recePackageName:接收端包名。
queryStr:要携带的字符串数据
sendCallback:发送结果回调
callback:查询结果回调


  • sendResult(String recePackageName, String resultStr, long queryId, ISendCallback sendCallback): void


说明:发送Result数据包。
参数:
recePackName:接收端包名。
resultStr:查询结果。
queryId:Query数据包的taskId。
sendCallback:发送结果回调。


  • sendFile(String recePackName, String filePath, ISendCallback callback): long


说明:发送文件。

 参数:
recePackName:副屏接收的app package name。
filePath:文件路径。
callback:发送结果回调。 

返回值:taskId,维护此任务Id直至收到发送成功的回调时可以向副屏发送指令对文件做自定义操作,比如:显示图片、打开文件等操作。 

注意:文件传输到副屏以后会被缓存起来并且与返回的taskId形成映射关系,若想长期复用该文件则应将taskId持久化维护起来。

  • sendFile(String recePackName, String msg, String filePath, ISendCallback callback): long


说明:发送一个文件+String类型的数据
参数:
recePackName:副屏接收的app package name。
msg:String类型的数据,例如:一个json格式的字符串、一段文字信息等…
filePath:文件路径。
callback:发送结果回调。
返回值:taskId,维护此任务Id直至收到发送成功的回调时可以向副屏发送指令对文件做自定义操作,比如:显示图片、打开文件等操作。
注意:文件传输到副屏以后会被缓存起来并且与返回的taskId形成映射关系,若想长期复用该文件则应将taskId持久化维护起来。


  • sendFiles(String recePackName, String msg, List files, ISendFilesCallback callback): long


  • 说明:发送多个文件+String类型的数据
    参数:
    recePackName:副屏接收的app package name。
    msg:String类型的数据,例如:一个json格式的字符串、一段文字信息等…
    files:文件路径集合。
    callback:多文件发送结果回调。
    返回值:taskId,维护此任务Id直至收到发送成功的回调时可以向副屏发送指令对文件做自定义操作,比如:显示图片、打开文件等操作。
    注意:文件传输到副屏以后会被缓存起来并且与返回的taskId形成映射关系,若想长期复用该文件则应将taskId持久化维护起来。


  • checkFileExist(long fileId, final ICheckFileCallback callback): void


说明:检查fileId对应的文件在副屏是否存在 
参数:
fileId:文件Id。
callback:检查结果回调

  • addConnCallback(IConnectionCallback callback): void

说明:注册一个监听连接的回调,可注册多个。
 参数:
stateCallback:与副屏的连接状态回调。


  • removeConnCallback(IConnectionCallback callback): void

说明:移除监听连接的回调。 
参数:
stateCallback:与副屏的连接状态回调。


  • checkConnection(): void

说明:检测与副屏的连接状态,有结果会回调注册的IConnectionCallback。

  • addReceiveCallback(IReceiveCallback receiveCallback): void

说明:注册数据接收回调,可注册多个。
 参数:
receiveCallback:接收端用于接收发送端数据的回调接口。


  • removeReceiveCallback(IReceiveCallback receiveCallback): void

说明:移除数据接收回调。 
参数:
receiveCallback:要注销的回调实例。

FilesManager类:持久化维护缓存接收到的文件,以发送文件任务的taskId为key缓存DSFile、DSFiles。
  • getFile(Long taskId): DSFile说明:根据任务ID获取文件。
    参数:
    taskId:文件对应的任务ID。
  • getFiles(Long taskId): DSFiles说明:根据任务ID获取多个文件。
    参数:
    taskId:文件对应的任务ID。
    —>

包 sunmi.ds.callback

IReceiveCallback类:接收数据时的回调接口。
  • onReceiveData(DSData data): void说明:接收到数据时的回调。
    参数:
    data:接收到的数据
  • onReceiveFile(DSFile file): void说明:接收到单个文件时的回调。
    参数:
    file:接收到的文件
  • onReceiveFiles(DSFiles files): void说明:接收到>=1个文件+一段String类型数据时的回调。
    参数:
    files:接收到的文件+String数据
  • onReceiveCMD(DSData cmd): void说明:接收到CMD类型数据时的回调。
    参数:
    cmd:接收到的CMD类型的数据
QueryCallback类:接收到查询结果数据包时的回调。
  • onReceiveData(DSData data): void说明:接收到结果数据包时的回调。
    参数:
    data:接收到的数据
IConnectionCallback类:双屏通讯连接状态的回调接口,所有回调函数都运行在子线程。
  • onDisConnect(): void说明:连接断开时的回调。
  • onConnected(ConnState state): void说明:连接状态更新时回调。连接状态分为3种:1.与本地service链接正常、2.与副屏service连接正常(处于这种状态下就可以向副屏发送数据了)、3.与副屏APP连接正常。
    参数:
    state:连接状态
ICheckFileCallback类:检查文件结果回调接口。
  • onCheckFail(): void说明:检查失败(通讯失败)。
  • onResult(boolean exist): boolean说明:检查失败(通讯失败)。
    参数:
    exist:true表示存在,false表示不存在。
IConnectionCallback类:双屏通讯连接状态的回调接口,所有回调函数都运行在子线程。
  • onDisConnect(): void说明:连接断开时的回调。
  • onConnected(ConnState state): void说明:连接状态更新时回调。连接状态分为3种:1.与本地service链接正常、2.与副屏service连接正常(处于这种状态下就可以向副屏发送数据了)、3.与副屏APP连接正常。
    参数:
    state:连接状态
ISendCallback类:发送String数据或单个文件的回调接口,所有回调函数都运行在子线程。
  • onSendSuccess(long taskId): void说明:发送成功时的回调。
    taskId:任务Id,对应发送的数据或文件
  • onSendFail(int errorId, String errorInfo): void说明:发送失败时的回调。
    参数:
    errorId:错误识别码。
    errorInfo:错误描述。
  • onSendProcess(long totle, long sended): void说明:发送进度的回调。
    参数:
    totle:数据的总大小,单位:byte。
    sended:已发送的数据大小,单位:byte
ISendFilesCallback类:发送>=1个文件+String数据的回调接口,所有回调函数都运行在子线程。
  • onAllSendSuccess(long fileId): void说明:多个文件都发送成功时的回调。
    fileId:任务Id也是缓存在副屏文件对应的fileId。
  • onSendSuccess(String path, long taskId): void说明:某一个文件发送成功时的回调。
    taskId:任务Id,发送多个文件都属于同一任务所以taskId都相同。
  • onSendFail(int errorId, String errorInfo): void说明:发送String数据失败时的回调,String数据发送失败后不会继续发送文件。
    参数:
    errorId:错误识别码。
    errorInfo:错误描述。
  • onSendFileFaile(String path, int errorId, String errorInfo): void说明:发送某一个文件失败时的回调。
    参数:
    path:发送失败对应的文件路径。
    errorId:错误识别码。
    errorInfo:错误描述。
  • onSendProcess(String path, long totle, long sended): void说明:发送某个文件的进度回调。
    参数:
    path:对应的文件路径。
    totle:数据的总大小,单位:byte。
    sended:已发送的数据大小,单位:byte
    —>

<---

包 sunmi.ds.data

DataPacket类:封装了发送数据和发送回调,调用DSKernel类的sendData(DataPacket pack)函数时需要的参数实体类。
DataPacket.Builder类:DataPacket的builder类。
  • Builder(DSData.DataType dataType)说明:构造函数。
    参数:
    dataType:要build的数据类型。
  • recPackName(String recPackName): Builder说明:指定接收数据的app包名。
    参数:
    recPackName:接收数据的app包名。
    返回值:Builder实例
  • data(String data): Builder说明:指定要发送的数据。
    参数:
    data:要发送的String数据。
    返回值:Builder实例
  • taskId(long taskId): Builder说明:指定任务Id,不指定则自动生成。
    参数:
    taskId:任务Id。
    返回值:Builder实例
  • fileId(long fileId): Builder说明:指定缓存文件Id。
    参数:
    fileId:缓存文件Id。
    返回值:Builder实例
  • queryId(long queryId): Builder说明:指定Query数据包的Id。
    参数:
    fileId:Query数据包的Id。
    返回值:Builder实例
  • isReport(boolean isReport): Builder说明:指定是否需要结果回调。
    参数:
    isReport:是否需要结果回调。
    返回值:Builder实例
  • addCallback(ISendCallback callback): Builder说明:设置发送结果的回调实例。
    参数:
    callback:发送结果的回调实例。
    返回值:Builder实例
  • build(): DataPacket说明:build一个DataPacket实例。
    返回值:DataPacket实例
DSData类:双屏通讯的数据封装。
  • sender: String说明:发送端app包名。
  • taskId: long说明:任务Id。
  • fileId: long说明:缓存文件Id。
  • queryId: long说明:Query数据包的任务Id。
  • dataType: DataType说明:数据类型。
  • data: String说明:要发送的数据。
DSData.DataType枚举类:数据类型描述封装。
  • DATA: DataType说明:表示数据。
  • FILE: DataType说明:表示文件。
  • CMD: DataType说明:表示命令。
  • typeCode: int说明:类型code。
DSFile类:接收端接收的文件类。
  • sender: String说明:发送端app包名。
  • path: String说明:接收到的文件路径。
  • taskId: long说明:任务Id。
DSFiles类:接收端接收的多文件类。
  • filesDescribe: FilesDescribe说明:封装了接收到的String数据与文件数量。
  • sender: String说明:发送端app包名。
  • files: List说明:接收到的文件路径集合。
  • taskId: long说明:任务Id。
FilesDescribe类:封装了接收到的String数据与文件数量。
  • msg: String说明:接收到的String数据。
  • fileCount: int说明:文件数量。

T1 自定义副显程序开发

                    

                                                             T1 自定义副显程序开发 


【提示:在开发之前,请认真阅读文档中的内容,如有疑问可以联系商米技术支持咨询。】

注:插入usb调试线,会导致主副屏断开。

关于T1双屏


商米T1有两种双屏配置:


1. 主屏14寸,副屏7寸。

注:7寸副屏仅支持浏览,不支持触控,不支持安装app。用户如需要修改副屏界面,请参考内置副显程序对接文档

Ouk69hAZ0gIkhS99

2. 主屏14寸,副屏14寸,副屏可触摸。

2

详细的硬件说明,请查看官网介绍,设备主副屏运行的都是SUNMI OS系统,相互通过商米封装好的接口实现通信。

注:不要把sunmi的有关代码混淆,混淆可能造成组件无法工作

#sunmi

-keep class com.luedongtech.kr3000.sunmi.** {*;}

-keep class sunmi.ds.**{*;}

-keep class com.sunmi.**{*;}

-keep class sunmi.sc.**{*;}

-keep class com.google.gson.**{*;}

通常开发者的业务app运行在主屏,同时需要副屏显示清单信息、宣传图片,视频等内容,有时还需要实现简单的交互操作。开发者有两种方式使用T1副屏显示内容:


    1. 使用T1副屏系统内置的显示程序。只要主屏app遵照商米的规范,向副显程序发特定格式的数据就可让副显程序显示内容(开发成本低,推荐)。

   

    2. 开发者自己写副屏显示app(开发成本高,自由度高)。


虽然商米封装的副显程序能满足绝大部分的开发者对于副屏显示内容的需求,但是业务是多样的,商米始终秉持开放原则,支持开发者自己写副显程序。下面将对自己写副显程序做详细的说明。

  • 副显app的分发

我们规定,只要在您的主屏业务App的AndroidManifest.xml中添加一行配置(文档最后讲),业务App在通过应用市场安装到主屏的同时,也会在副屏上安装,也就是说,您的主屏App同时也是副屏App,这里需要您在代码中进行首页显示内容的控制 。

进入正题


自定义副显程序开发的核心问题是主副屏的通信,实现了主副屏通信后,您可以把副屏当成是普通的平板电脑开发相关的副屏app。这里有必要先了解一下双屏的通信方式,我们通过一张图片向您直观展示T1双屏的通信协议。



prototype1

T1的通信协议,从下到上依次包括:


    1. Driver层,该层是最底层的通信协议,开发者无需关注。

    2. Service层,该层是商米封装的一个通信服务,开发者也无需关注。

    3. SDK层,该层是T1自带的副显程序使用的通信接口,自定义的副显程序调用的也是这层的接口

    4. APP层,指的是开发者自己的业务app和内置的副显程序。


  • 如果使用的是T1内置副显程序,数据通信方式如上图所示,开发者的主屏业务App只要调用商米的SDK按照商米规定好的数据格式向副显发数据,剩下的交给系统处理即可。

  • 如果您自己开发副显程序,就要自己处理数据的收发,内容显示等动作,如果您没有强烈的自定义需求,我们建议您使用T1自带的副显程序显示内容,可以节约很多的人力,物力成本。


开始开发

我们给开发者准备了一个双屏通信的Demo,您可以下载副显程序开发资源,参照Demo中的代码实现您自己的副显app,请按照下面的流程部署该Demo:


    1. 将资源文件中的HCService.apk安装到模拟主副屏通信的两台设备上,资源中包含两个HCService的apk,请先安装release版本,如果安装失败请尝试安装unsigned版本的apk。


    2. 主副屏上分别运行这个程序,在第一台设备上输入自己的IP地址,勾选”作为主屏”,点击确定。


    3. 在另一台设备上输入主屏机器的IP地址,点击确定(不要勾选‘作为主屏’),可以看到屏幕中弹出toast提示“与主/副屏连接”,就代表主副屏能通信了,如果没有弹出提示,请在设置中的应用程序管理中找到HCService程序 (进入设置->应用->点击右上角的更多->点击显示系统进程,才能看到HCService),强制停止主副屏上的HCService服务,再重复上面的步骤试试。


    4. 将MyDSD项目导入到AndroidStudio中,如果报错了请检查您的编译环境的版本是否兼容demo,可以自己做相关的调整。注:我们不建议您使用eclipse开发副显程序,目前没有提供相关的eclipse资


    5. 这里请修改AndroidManifest.xml中的启动页面为MainScreenActivity,然后将项目部署到主屏设备中,运行。


    6. 再修改启动页面为ViceScreenActivity,然后将项目部署到副屏设备中,运行。


    7.在主屏上点击发送文字的按钮,查看副屏显示内容的变更,然后可以发送图片给副屏查看显示情况。


在这个Demo中包含了主副屏相互发送字符数据的方法,发送单个文件的方法和发送多个文件的方法。以下我们将基于Demo讲解主副屏通信的开发流程:

1. 在build.gradle中添加以下依赖:

dependencies {
    compile files('libs/commons-codec-1.9.jar')
    provided files('libs/framework.jar')
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.sunmi:DS_Lib:latest.release'
    compile 'com.google.code.gson:gson:2.6.2'
}



需要添加的权限:(强调:Sunmi OS是基于Android6.0定制,Android6.0+ 要求部分敏感权限需要动态申请)


还有一个广播接收器,注意,这个是固定的写法,不要做修改哦。

"sunmi.ds.MsgReceiver">
    
        "com.sunmi.hcservice" />
        "com.sunmi.hcservice.status" />
    


2. 初始化,您可以在Application中初始化SDK的代码,也可以在任何您想要的地方初始化:

private void initSDK() {
    mDSKernel = DSKernel.newInstance();
    mDSKernel.init(this, mConnCallback);
}


以上DSKernel类是SDK中的核心类,几乎所有双屏通信相关的方法都在这个类中。在初始化后需要添加两个监听类:

//添加主副屏连接状态的监听
mDSKernel.addConnCallback(new IConnectionCallback(){
                        @Override
                        public void onConnected(ConnState arg0) {
                        //连接上了回调
                        }

                        @Override
                        public void onDisConnect() {
                        //断开连接的回调
                        }
                        
                });

//添加接收到数据的回调                
mDSKernel.addReceiveCallback(new IReceiveCallback(){

                        @Override
                        public void onReceiveCMD(DSData arg0) {
                          //收到命令      
                        }

                        @Override
                        public void onReceiveData(DSData arg0) {
                           //收到数据
                        }

                        @Override
                        public void onReceiveFile(DSFile arg0) {
                           //收到单个文件
                        }

                        @Override
                        public void onReceiveFiles(DSFiles arg0) {
                           //收到多个文件
                        }
             
                        
                });   



以上我们完成了初始化,通信当然会分信息的发送和接收了,主副屏可以相互发送接收数据。可以看到在MyDSD里面,MainScreenActivity中包含很多发数据的方法,在ViewScreenActiivty中包含很多接收数据的方法。

所有的方法可以在DSKernel类中查看到,详情可以查看 T1双屏通信接口文档 ,以下我们将讲解部分接口的使用:

1.发送字符数据,副屏接收显示

DataPacket packet = UPacketFactory.buildShowText(DSKernel.getDSDPackageName(), "成功接收字符数据!",null);
mDSKernel.sendData(packet);//参数DataPacket是双屏通信数据打包类,请参照Demo生成该数据


接收的方法是在IReceiveCallback接口的onReceiveData(DSData data)中:

@Override
public void onReceiveData(DSData data) {
    Log.d(TAG, "获取到数据:" + data.data);//将接收到的数据打印出来
}


2.发送指令的方法:

String json = UPacketFactory.createJson(DataModel.QRCODE, "");
//三个参数:1.接收指令的副屏app的包名;2.要发送的指令,这里可以参照Demo;3.发送结果回调
mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json.toString(), l,null);


接收指令的方法:

@Override
public void onReceiveCMD(DSData cmd) {
     Log.d(TAG, "获取到数据:" + cmd.data);//将接收到的数据打印出来
}


指令的使用场景举例:图片视频等的显示需要先将文件发送到副屏,副屏会返回一个文件的id给主屏,然后向副屏发送指令显示指定id的图片或视频。

3.发送单个文件的方法:

//该方法有三个参数:1.接收端app的包名;2.文件在本地存储中的路径;3.发送结果回调
mDSKernel.sendFile(PACKAGE_OF_VICE, Environment.getExternalStorageDirectory().toString()+"/"+videoInAsserts,
      new ISendCallback() {
         public void onSendSuccess(long l) {
            Log.i("sunmi", "这里表示发送成功" + l);
            showVideo(l);
         }

      
         public void onSendFail(int i, String s) {
            //tv_show_id.setText("fail:" + s);
            Log.i("sunmi", "---:" + s);
         }

         public void onSendProcess(long l, long l1) {
            //发送进度的方法
         }
      });


接收单个文件的方法,IReceiveCallback接口的onReceiveFile(DSFile file) 中通过调用FilesManager.saveFile(DSFile file)将文件保存在本地:

@Override
public void onReceiveFile(DSFile file) {
   Log.i(TAG,"接收到的单个文件的路径:"+file.path);
   Log.i(TAG,"接收到的单个文件的taskId:"+file.taskId);
    mFilesManager.saveFile(file);
}

4.发送多个文件的方法

mDSKernel.sendFiles(PACKAGE_OF_VICE, json.toString(), paths, new ISendFilesCallback(){
   @Override
   public void onAllSendSuccess(long l) {
     //所有文件发送成功
   }
   @Override
   public void onSendFaile(int arg0, String arg1) {}
   @Override
   public void onSendFileFaile(String arg0, int arg1, String arg2) {}
   @Override
   public void onSendProcess(String arg0, long arg1, long arg2) {}
   @Override
   public void onSendSuccess(String arg0, long arg1) {
   }
   
});


多个文件的接收方法:

@Override
public void onReceiveFiles(DSFiles dsFiles) {
            mFilesManager.saveFiles(dsFiles);
        }


5.启动指定应用的方法


参照Demo生成数据,向副屏发指令,该代码需要DSD程序的支持,可以在下载的资源文件中找到该apk,安装在副屏。

DataPacket dsPacket   = UPacketFactory.buildOpenApp(PACKAGE_OF_VICE, null);
//PACKAGE_OF_VICE为要启动的副屏指定应用的包名,如:com.sunmi.mydsd
mDSKernel.sendCMD(dsPacket);


以上介绍了一些基本接口的使用,更多的接口请参看DSKernel中的代码注释。


应用的发布

主副屏的应用都开发好了,当然是通过商米应用市场将该应用大规模分发出去了,上面我们讲了,只要在应用的AndroidManifest.xml中添加一行配置,该应用通过应用市场在主屏安装的时候,只要双屏通信畅通,就会在副屏上同时安装该应用(必须通过应用市场安装才能生效)。主屏和副屏程序是写在一个应用里的,所以需要您在入口类中添加相关的判断逻辑,这就是运行Demo时您需要修改启动页的原因,具体判断主副屏的代码可以参考Demo。




"sunmi_dual" android:value="open"/>

商米扫码

商米扫码


为什么要使用商米封装的扫码SDK

商米提供了适配自己设备的扫码SDK,相对于目前使用的开源方案,商米的扫码SDK有以下5个优势


  1. 识别率高,经过大量模拟真实场景的测试,商米扫码SDK相对普遍使用的基于ZXing开源项目的扫码方案在扫码识别率上平均提高了74%。
  2. 比ZXing方案在一维码扫码速度上快了100%以上。
  3. 使用方式更简单,5行代码就能在自己的项目中添加扫码功能。
  4. 支持扫描多达15种码,后续还将添加更多的码种。
  5. 与商米的设备完美适配,软硬件结合可以保证功能的高效稳定。

怎么使用商米的扫码SDK

开发者有两种方式使用商米的扫码SDK

  1. 开发者的应用调用SUNMIUI系统集成的扫码模块完成扫码,获取返回值,该方法简单易用。
  2. 自己写相机界面,调用商米的封装的扫码SDK完成图片的解析,该方式相对复杂,但提供了更高的自由度。

第一种使用方式:

为了降低开发难度,商米在最新的SUNMI OS(V1固件版本187,M1固件版本37)系统中内置了一个扫码的模块,开发者在项目需要调用扫码的地方通过startActivityForResult()调用商米的扫码模块,然后在onActivityResult()方法中接受扫码结果返回值。

/**

	* 外部应用在自己的业务代码需要启动扫码的地方使用下面的方式创建Intent,

	* 然后使用startActivityForResult()调用起商米的扫码模块;

	*/

	Intent intent = new Intent("com.summi.scan");

	intent.setPackage("com.sunmi.sunmiqrcodescanner");

	        

	/**

	* 使用该方式也可以调用扫码模块

	*Intent intent = new Intent("com.summi.scan");

	*intent.setClassName("com.sunmi.sunmiqrcodescanner", 

	"com.sunmi.sunmiqrcodescanner.activity.ScanActivity");

	*/

	/**

	//扫码模块有一些功能选项,开发者可以通过传递参数控制这些参数,

	//所有参数都有一个默认值,开发者只要在需要的时候添加这些配置就可以。

	intent.putExtra("CURRENT_PPI", 0X0003);//当前分辨率 

	//M1和V1的最佳是800*480,PPI_1920_1080 = 0X0001;PPI_1280_720 = 

	//0X0002;PPI_BEST = 0X0003;

	intent.putExtra("PLAY_SOUND", true);// 扫描完成声音提示  默认true

	intent.putExtra("PLAY_VIBRATE", false);

	//扫描完成震动,默认false,目前M1硬件支持震动可用该配置,V1不支持

	intent.putExtra("IDENTIFY_INVERSE_QR_CODE", true);// 识别反色二维码,默认true

	intent.putExtra("IDENTIFY_MORE_CODE", false);// 识别画面中多个二维码,默认false        

	intent.putExtra("IS_SHOW_SETTING", true);// 是否显示右上角设置按钮,默认true

	intent.putExtra("IS_SHOW_ALBUM", true);// 是否显示从相册选择图片按钮,默认true

	*/
	startActivityForResult(intent, START_SCAN);           

在onActivityResult方法中接收返回的扫码结果参数,参考如下代码:



	@Override

	protected void onActivityResult(int requestCode, int resultCode, Intent data) {

	                if (requestCode == 1 && data != null) {

	                        Bundle bundle = data.getExtras();

	    ArrayList> result = (ArrayList>) bundle

	                                        .getSerializable("data");

	                        

	                        Iterator> it = result.iterator();

	                        while (it.hasNext()) {

	                                HashMap hashMap = it.next();

	                                

	                                Log.i("sunmi", hashMap.get("TYPE"));//这个是扫码的类型

	                                Log.i("sunmi", hashMap.get("VALUE"));//这个是扫码的结果

	                                

	                        }

	                }

	                super.onActivityResult(requestCode, resultCode, data);

	        }

        


第二种方式:

1. 在项目的libs目录中按以下层级添加libiconv.so,libscaninit.so,libsunmiscan.so和sunmiscan.jar四个库文件。

project


2. 在处理业务的代码中引入头文件和解码库,可以参照DEMO。

        import com.sunmi.scan.Config;

	import com.sunmi.scan.Image; 

	import com.sunmi.scan.ImageScanner; 

	import com.sunmi.scan.Symbol;

	import com.sunmi.scan.SymbolSet; 


3. 初始化和配置。

        private ImageScanner scanner;//声明扫描器 

	scanner = new ImageScanner();//创建扫描器

	scanner.setConfig(0, Config.X_DENSITY, 2);//行扫描间隔

	scanner.setConfig(0, Config.Y_DENSITY, 2);//列扫描间隔

	scanner.setConfig(0, Config.ENABLE_MULTILESYMS, 0);

	//是否开启同一幅图一次解多个条码,0表示只解一个,1为多个

	scanner.setConfig(0, Config.ENABLE_INVERSE, 0);//是否解反色的条码
        scanner.setConfig( Symbol.QRCODE,Config.ENABLE, 1);//允许识读QR码,默认1:允许

        scanner.setConfig( Symbol.PDF417,Config.ENABLE, 1);//允许识读PDF417码,默认0:禁止

        scanner.setConfig(Symbol.DataMatrix, Config.ENABLE, 1);//允许识读DataMatrix码,默认0:禁止

        scanner.setConfig(Symbol.AZTEC, Config.ENABLE, 1);//允许识读AZTEC码,默认0:禁止


4.传入图像数据和解码,以下的代码可以写在PreviewCallback.onPreviewFrame(byte[] data, Camera camera)方法中。

  /**
    *创建解码图像,width, height 分别为摄像头预览分辨率的宽度和高度,一般来说,分辨率越高图
    *像越清晰,但解码速度越慢。由于解码算法需要处理的是原始灰度数据,而预览图像的默认格式为 
    *YCbCr_420_SP,需要转换格式才能处理, 参数"Y800"表示待转换的图像格式。
    */
    Image source = new Image(width, height, "Y800");

    /**
    *设置扫描区域范围,为了较好的识读较长的一维码,扫描框的宽度不应过小,由于预览的图像为横屏, 
    *注意扫描区域需要转换为竖屏对应的位置 
    */
    Rect cropRect = finder_view.getScanImageRect(size.height, size.width);
    //finder_view为DEMO中自定义的扫码区域控件。
    source.setCrop(cropRect.top,cropRect.left,cropRect.height(),cropRect.width());

    /*填充图像数据,data 为摄像头原始数据*/
    source.setData(data); 

    /*解码,返回值为 0 代表失败,>0 表示成功*/
    int result = scanner.scanImage(source); 



5.获取解码结果和条码类型。

if (result != 0) {
         
      SymbolSet syms = scanner.getResults();
          for (Symbol sym : syms) {   
            Log.i("sunmi", "码型:"+sym.getSymbolName());//条码类型,如“EAN-8”
            Log.i("sunmi","结果:"+sym.getResult())//获取解码结果字符串,这里就是要获取的结果
            
            }

  }       

更多说明

商米扫码SDK目前支持的扫码类型包括如下:

  • 一维码:

    EAN-8, EAN-13, UPC-A, UPC-E, Codabar, Code39, Code93, Code128, ISBN10, ISBN13, DataBar, DataBar Expanded, Interleaved 2 of 5

  • 二维码: QR Code , PDF417,DataMatrix,AZTEC

商米打印机驱动

                                                                     

                                                              商米打印机驱动

 

简介

商米的V1、P1、T1内置了热敏打印机,允许App直接打印热敏小票出来。商米产品的内置打印机一共有2种规格:

  • 80mm宽,带切刀。兼容58mm,H10、T1搭载了这种打印机
  • 58mm宽,不带切刀。V1、V2搭载了这种打印机

App如何调用内置热敏打印机

App开发者可以使用3种方式调用内置热敏打印机:

 

1.通过蓝牙连接打印机
2.通过AIDL连接打印机
3.通过JS桥调用打印机

 

下面以V1设备为例,介绍两种调用方式,该方案也适用于P1、T1设备。

一、通过蓝牙连接调用

在V1的蓝牙设备列表中可以看到一个已经配对,且永远存在的蓝牙设备“ InnerPrinter”,这是由操作系统虚拟出来的打印机设备,实际并不存在,但它的通信方式与外置的蓝牙打印机几乎完全相同,是用来方便已经熟悉ESC/POS指令的开发者适配V1,这样他们就无需修改代码、快速迁移打印代码到V1了。

因此,如果开发者已经熟悉ESC/POS指令,甚至拥有的App已经针对ESC/POS指令完成了开发,或期望打印方法保持通用性,不要高度依赖商米设备的特征,则建议使用蓝牙连接方式。

 

如果还不熟悉ESC/POS指令,可以 点击这里 下载《ESC/POS指令说明》了解V1支持的指令集。

 

通过连接InnerPrinter虚拟打印机来完成打印的原理是:

 

1.与该蓝牙设备建立连接
2.将指令和文本内容拼接转码为Bytes
3.发送给InnerPrinter
4.底层打印服务驱动打印设备完成打印

 

关于如何连接“InnerPrinter”,开发者可以在网上搜索蓝牙连接的相关基础教程,为方便阅读,以下简要地说明了蓝牙设备连接的开发代码,便于快速上手。

 

以下效果为蓝牙打印Demo App打印的效果,点击这里下载Demo Apk及源码

1

其中包含5个关键操作:

 

1.在App的项目中添加蓝牙权限声明

  
    
    
    

 

2.获取蓝牙设备BluetoothAdapter

  BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null && !mBluetoothAdapter.isEnabled()) {
       Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  
       startActivityForResult(intent, REQUEST_ENABLE_BT);
    }

 

3.获取虚拟蓝牙设备

  String innerprinter_address = "00:11:22:33:44:55";
    BluetoothDevice innerprinter_device = null;
    Set devices = mBluetoothAdapter.getBondedDevices();
    for(BluetoothDevice device : devices){
        if(device.getAddress().equals(innerprinter_address)){
              innerprinter_device = device;
         }
     }

 

4.获取蓝牙套接字 BluetoothSocket

 UUID PRINTER_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
   BluetoothSocket mSocket = 
   innerprinter_device.createRfcommSocketToServiceRecord(PRINTER_UUID);

 

5 .将已经拼接完的打印指令Bytes,通过sendData(byte [] bytes)方法发送给InnerPrinter

  OutputStream mOut = mSocket.getOutputStream();
   private void sendData(byte[] bytes) {
          if (mOut != null) {
              try {
                  mOut.write(bytes, 0, bytes.length);
                  mOut.flush();
              } catch (IOException e) {
                  Log.e("TAG", e.getMessage());
              } finally {
                 try {
                      mOut.close();
                  } catch (IOException e) { }
              }
          }
    }

 

第5个关键操作中的byte[] bytes参数为待打印的小票内容,是通过“GBK”编码将文字转为Byte与指令Byte拼接在一起的内容。

 

58mm宽打印机的每行含384个像素点,每个中文字符默认宽度为24像素,每个英文字符默认宽度为12像素,所以一行最多可以打印出16个中文字符或32个英文字符,每行打印后会自动换行,当然,你也可以通过打印“ ”字符强制换行。

 

每个行的默认高度是30像素,你可以通过指令设置倍高倍宽来控制字的大小,通过对齐指令来控制每行的文字位置。

 

请参考《ESC/POS指令说明》了解更多的控制指令细节。

 

注意:可能会出现多个应用同时发起打印请求的问题,我们建议您如果通过虚拟蓝牙打印,打印数据最好一次性发送。

二、通过AIDL调用打印机

 

背景

小票打印机的发明者是EPSON,ESC/POS指令就是由EPSON设计、定义的,在早在Android之前就已经出现,在互联网普及之前就已经广泛应用于餐饮、零售行业,至今有15年以上的历史了。

 

因此指令中的数据处理方式比较底层,一些经历过传统软件开发的开发者可能在这些指令集上有自己封装和积累,但对于新兴的互联网开发者来说,无论是打印文字、图片、调整格式都会显得非常繁琐,需要投入一定的成本周期来适应。

 

因此,在V1的开发者还可以通过使用Android标准的AIDL连接内置打印服务,调用打印机。

 

AIDL是Android Interface Definition language的缩写,它是一种Android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口。关于AIDL的使用方法,请开发者查看网上相关的文档查看相关说明。

 

当然开发者也可以暂时跳过AIDL的原理,通过下文简短的说明及示例代码,快速了解调用方式。

 

教程

首先需要下载AIDL文件及Demo,压缩文档中含有2部分内容:


1.Demo是我们准备的打印示例工程源码,以下效果为通过Demo打印的小票,T1。
2.AIDL文件是所有打印服务暴露的接口方法清单,需要整合到自己App源码项目中。

 

以下为Demo App中具体的调用关键操作解读:

 

1.在自己项目中添加资源文件中附带的AIDL文件:
(1)在src下建立woyou.aidlservice.jiuiv5包

      (2)加入IWoyouService.aidl、ICallback.aidl2个文件。

 

2.在控制打印的代码类中实现ServiceConnection。

  private ServiceConnection connService = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
                woyouService = null;
            }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
                woyouService = IWoyouService.Stub.asInterface(service);
                setButtonEnable(true);
           }
        };  

 

3.调用ApplicationContext.bindService(),并在ServiceConnection实现中进行传递。

 

注意:bindservice是非阻塞调用,意味着调用完成后并没有立即绑定成功,必须以serviceConnected为准。

 Intent intent=new Intent(); 
 intent.setPackage("woyou.aidlservice.jiuiv5"); 
 intent.setAction("woyou.aidlservice.jiuiv5.IWoyouService"); 
 startService(intent); bindService(intent, connService, 
 Context.BIND_AUTO_CREATE);

 

4.在ServiceConnection.onServiceConnected()实现中,你会接收一个IBinder实例(被调用的Service). 调用 IWoyouService.Stub.asInterface(service)将参数转换为IWoyouService类型。

 

5.现在就可以调用IWoyouService接口中定义的各种方法进行打印了。这一步你总要检测到RemoteException异常,该异常在连接断开时被抛出,这里需要做处理。它只会被远程方法抛出。接口方法中ICallback可以传递null。断开连接时调用接口实例中的 ApplicationContext.unbindService() 。

 

IWoyouService中封装了打印很多种数据的方法和控制格式的方法如:

 woyouService.printText("this is a text",callback); 
 woyouService.printTextWithFont("this is a text with font","",36,callback); 
 woyouService.setFontSize(24,callback); 
 woyouservice.printColumnsText("print a line of a table!",12,0,callback);

 

该接口同时也包含打印ESC/POS拼装的数据的方法:

 woyouService.sendRAWData(bytes,callback);

 

注意:可能会出现多个应用同时发起打印请求的问题,您如果通过AIDL打印,我们建议您使用事物打印。

三、通过HTML中的JS调用打印机

通过JS调用打印机本质上是通过JS桥在HTML中操作android原生的代码实现打印,通过蓝牙打印或者AIDL的方式实现打印。我们给开发者准备了一个 Demo,在Demo中,通过点击HTML中的一个按钮,调用在java中写好的一个方法,让后通过AIDL的方式调用打印机打印一个自检小票。

以下为具体的调用流程:

 

1.在HTML中定义android中要使用的方法。

  document.getElementsByTagName('a')[0].addEventListener('click', function(){
          
          var a = "wellcome to sunmi";
                  javascript:lee.funAndroid(a);   
          return false;
      }, false);      

 

2.初始化WebView.

    WebView mWebView = (WebView) findViewById(R.id.wv_view);
    // 设置编码
    mWebView.getSettings().setDefaultTextEncodingName("utf-8");
    // 支持js
    mWebView.getSettings().setJavaScriptEnabled(true);
    mWebView.setWebChromeClient(new WebChromeClient());

 

初始化打印服务

Intent intent = new Intent();
intent.setPackage("woyou.aidlservice.jiuiv5");
intent.setAction("woyou.aidlservice.jiuiv5.IWoyouService");
startService(intent);//Start printer service
bindService(intent, connService, Context.BIND_AUTO_CREATE);

 

3.给WebView添加监听,在onPageFinished 回调方法中调用WebView.addJavascriptInterface(new JsObject(), ‘lee’);在JsObject类中通过 @JavascriptInterface定义在HTML中指定要操作的方法,在方法中通过调用AIDL打印方式打印一张小票。

 

mWebView.setWebViewClient(new WebViewClientDemo());//添加一个页面相应监听类

class WebViewClientDemo extends WebViewClient {

     @Override
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
        // 当打开新链接时,使用当前的 WebView,不会使用系统其他浏览器
         view.loadUrl(url);
         return true;
       }

      @Override
      public void onPageFinished(WebView view, String url) {
      super.onPageFinished(view, url);
         /**
          * 注册JavascriptInterface,其中"lee"的名字随便取,如果你用"lee",那么在html中只要用  lee.方法名()
          * 即可调用MyJavascriptInterface里的同名方法,参数也要一致
          */
           mWebView.addJavascriptInterface(new JsObject(), "lee");
                }

        }
        
        
        class JsObject {

                @JavascriptInterface
                public void funAndroid(final String i) {
                        Toast.makeText(getApplicationContext(), "calling the local method funAndroid by JS. " + i,
                        Toast.LENGTH_SHORT).show();
                                
                        try {
                                woyouService.printerSelfChecking(callback);//Using AIDL to print something. 
                        } catch (RemoteException e) {
                                e.printStackTrace();
                        }
                }
        }

 

4.加载HTML文件,在点击HTML中的按钮的时候就会打印一张小票了

// 载入包含js的html
mWebView.loadData("", "text/html", null);
mWebView.loadUrl("file:///android_asset/test.html");//这里是您业务html所在的网页

 

关于通过jar包调用打印机

 

由于我们打印服务的升级,这个方式的调用在打印服务版本2.0.0以后都不再兼容,如果您已经使用了该方式进行打印 ,我们建议通过获取打印机版本号判断是否大于或等于’2.0.0’来兼容不同版本的打印服务,您可能会用到如下类似的代码:

    ... //绑定打印服务成功后得到一个IWoyouService类型的变量woyouService
    String version=woyouService.getServiceVersion(); //获取到打印服务的版本号
    if(version.compareTo("2.0.0")>=0){  //判断是否是新的打印服务
      ... //AIDL的方式调用打印服务
    }else{
      ... //依旧是通过jar包来调用打印服务
    }

T1副屏内置副显程序对接

T1 接入副屏客显程序说明

更新内容

2017-12-28 (适用版本:主屏(T1-host): V1.14.22、7寸副屏(sub7): V1.11.3、14寸副屏(sub14): V1.9.11)

  • 新增14寸副屏支持轮播视频;
  • 新增14寸副屏支持视频续播;
  • 新增7寸副屏支持幻灯片;
  • 新增7寸副屏支持单个视频;
  • 新增7寸副屏支持视频轮播;
  • 新增7寸副屏支持视频续播;
  • 新增判断副屏尺寸的方法;
  • 新增管理副屏缓存文件的方法;
  • DS_Lib库更新至1.0.16;

2018-01-15

  • 更新下载资源;

                                                                                                                                                                                                                                       

一、简介

T1双屏机器有三种组合:主机、主机+7寸副屏、主机+14寸

初始化配置

副屏。主副屏都是运行SUNMI OS定制系统,通过商米已封装好的接口实现通信。

主屏主要用来运行业务APP,例如:收银系统。副屏主要面向顾客显示结算、广告内容。开发者有两种方式实现副屏显示:

  • T1副屏系统内置了默认副屏显示APP,内置多个常用模板。开发者仅需参照文中模板的实现代码,即可实现副屏内容显示;
  • 开发自己副屏显示APP,需要自行处理数据的收发、内容显示等动作;

如果您没有强烈的自定义需求,商米建议您使用默认副屏显示APP,可以节省很多的研发成本。

本文将对如何接入内置副屏客显程序进行说明。

二、如何调试应用

由于主副屏是通过USB通信的,当主/副屏插入USB线时,主副屏的连接会断开,导致无法调试设备。

商米提供的解决方案是:将开发电脑与将要调试的T1主机处于同一个局域网络环境下,通过网络对设备进行调试。

如何调试:

1、开启USB调试,及调试权限;

    操作说明:http://docs.sunmi.com/htmls/index.html?lang=zh##调试设备

2、通过网络对设备进行ADB调试;

    操作说明:http://docs.sunmi.com/htmls/调试说明.html

三、如何接入副屏客显程序

(强调:Sunmi OS是基于Android6.0定制,Android6.0+ 要求部分敏感权限需要动态申请)

商米默认副屏APP提供了多个显示模板,开发者仅需要在自己的主屏业务APP实现控制副屏输出的代码,向默认副屏APP发送正确格式的数据即可实现副屏显示。

1、初始化配置

对接7寸或14寸默认副屏APP的初始化流程一样。

步骤1:

    下载DemoApp资源文件。

步骤2:

    参照DemoApp源码,直接在Android Studio的app module下的build.gradle文件中声明以下代码:

 dependencies {
    compile 'com.sunmi:DS_Lib:1.0.15'   //商米提供的lib库,包含已封装好的接口
    compile 'compile 'com.alibaba:fastjson:1.1.67.android''  //fastjson任意版本
}

步骤3:

    在清单文件AndroidMainfest.xml的节点下配置以下声明:


  ....
  "sunmi.ds.MsgReceiver">  //接收数据的广播
     
         "com.sunmi.hcservice">
         "com.sunmi.hcservice.status">
      
  

步骤4:

    在适当的位置初始化SDK代码,可以参考DSC_Demo中的实现代码。

DSKernel mDSKernel =DSKernel.newInstance();
mDSKernel.init(context, mConnCallback);    //绑定服务的回调
mDSKernel.addReceiveCallback(mReceiveCallback);  //双屏通信接收数据回调

接下来可以参考DemoApp源码,实现套用默认副屏APP内置模板的内容显示。

混淆ds_lib:

-keep interface sunmi.ds.**{ *; }

-keep class sunmi.ds.**{ *; }

除此之外,还需要混淆greendao以及fastjson,具体混淆规则以greendao和fastjson官方提供为准

2、副屏客显程序内置模板

7寸与14寸副屏支持的模板对比:

 模板 7寸  14寸 
 文字  √  √
 文字+二维码  √  √
 单张图片  √  √
 幻灯片  √  √
 单个视频  √  √
 视频轮播  √  √
 清单  ×  √
 清单+单张图片  ×  √
 清单+幻灯片  ×  √
 清单+单个视频   ×  √
 清单+视频轮播  ×  √

2.1、两行文字

png

实现代码:

JSONObject json = new JSONObject();
json.put("title", title);//title为上面一行的标题内容
json.put("content", content);//content为下面一行的内容
String jsonStr = json.toString();
//构建DataPacket类
DataPacket packet = UPacketFactory.buildShowText(DSKernel.getDSDPackageName(), jsonStr, callback);//第一个参数是接收数据的副显应用的包名,这里参照Demo就可以,第二个参数是要显示的内容字符串,第三个参数为结果回调。

mDSKernel.sendData(packet);//调用sendData方法发送文本

2.2、二维码+文字

2

实现代码:

JSONObject json = new JSONObject();
try {
    json.put("title", "微信支付");
    json.put("content", "10.00");
} catch (JSONException e) {
    e.printStackTrace();
}

mDSKernel.sendFile(DSKernel.getDSDPackageName(), json.toString(), Environment.getExternalStorageDirectory().getPath() + "/qrcode.png", new ISendCallback() {
    @Override
    public void onSendSuccess(long l) {
        //显示图片
        try {
            JSONObject json = new JSONObject();
            json.put("dataModel", "QRCODE");
            json.put("data", "default");
            mDSKernel.sendCMD(SF.DSD_PACKNAME, json.toString(), l, null);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onSendFail(int i, String s) {      
    }

    @Override
    public void onSendProcess(long l, long l1) {
    }
});

2.3、单张图片

3

实现代码:

mDSKernel.sendFile(DSKernel.getDSDPackageName(), Environment.getExternalStorageDirectory().getPath() + "/img_01.png", new ISendCallback() {

        @Override
        public void onSendSuccess(long taskId) {
            showPicture(taskId);
        }

        @Override
        public void onSendFail(int errorId, String errorInfo) {
        }

        @Override
        public void onSendProcess(long totle, long sended) {
        }
    });
}

/**
 * 展示单张图片
 *
 * @param taskId
 */

private void showPicture(long taskId) {
    //显示图片
    try {
        JSONObject json = new JSONObject();
        json.put("dataModel", "SHOW_IMG_WELCOME");
        json.put("data", "default");
        mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json.toString(), taskId, null);
    } catch (JSONException e) {
        e.printStackTrace();
    }
}

2.4、幻灯片

4

实现代码:

JSONObject json = new JSONObject();
json.put("rotation_time",5000); //幻灯片的切换时间,用毫秒计算,如果不传默认是10000毫秒
List pathList = new ArrayList<>();
pathList.add("/sdcard/img1.png");
pathList.add("/sdcard/img2.png");
...
mDSKernel.sendFiles(DSKernel.getDSDPackageName(), json.toString(), 
pathList, new ISendFilesCallback() {
    public void onAllSendSuccess(long fileId) {
        show(fileId);
    }
    public void onSendSuccess(final String s,final long l) {}
    public void onSendFaile(int errorId, String errorInfo) {}
    public void onSendFileFaile(String path, int errorId, String errorInfo){}
    public void onSendProcess(String path, long total, long sended) {}
});

private void show(long fileId) {
    String json = UPacketFactory.createJson(DataModel.IMAGES,"");
    mDSKernel.sendCMD(DSKernel.getDSDPackageName(),json,fileId,null);
}

2.5、单个视频

5

实现代码:

mDSKernel.sendFile(DSKernel.getDSDPackageName(), Environment.getExternalStorageDirectory().getPath() + "/video_01.mp4", new sunmi.ds.callback.ISendCallback() {
        @Override
        public void onSendSuccess(long l) {         
            playvideo(l);         
        }

        @Override
        public void onSendFail(int i, String s) {
            Log.d("highsixty", "发送单个文件视频文件失败 ------------>" + s);          
        }

        @Override
        public void onSendProcess(final long l, final long l1) {
        }
    });
}

private void playvideo(long taskID) {  
    String json = UPacketFactory.createJson(DataModel.VIDEO, "true"); //"true"视频续播;false视频从头播放
    mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json, taskID, null);
}

2.6、视频轮播

6

实现代码:

/**
 * 发送多视频
 */

private void sendVideos() {
    //请对文件是否存在做判断
    List files = new ArrayList<>();
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_01.mp4");
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_02.mp4");
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_03.mp4");
    mDSKernel.sendFiles(DSKernel.getDSDPackageName(), "", files, new ISendFilesCallback() {

        @Override
        public void onAllSendSuccess(long fileid) {
            playvideos(fileid);
        }

        @Override
        public void onSendSuccess(String path, long taskId) {           
        }

        @Override
        public void onSendFaile(int errorId, String errorInfo) {           
        }

        @Override
        public void onSendFileFaile(String path, int errorId, String errorInfo) {            
        }

        @Override
        public void onSendProcess(String path, long totle, long sended) {
        }
    });
}

private void playvideos(long taskID) {
    Log.d(TAG, "playvideos: ------------>" + taskID);
    String json = UPacketFactory.createJson(DataModel.VIDEOS, "true"); //第二个参数true:视频续播 false:视频从头播放
    mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json, taskID, null);
}

2.7、清单

7

实现代码:

{
        "title": "三米奶茶店欢迎您", 
        "head": {
            "param1": "序列号",
            "param2": "商品名",
            "param3": "单价""param4": "数量""param5": "小结"
        },
        "list": [
            {
              "param1": "1",
              "param2": "华夫饼",
              "param3": "10.00""param4": "1""param5":"10.00"
            },
            {
              "param1": "1",
              "param2": "吞拿鱼华夫饼",
              "param3": "12.00""param4": "1""param5":"12.00"
            }
            ... ...//这里是相同格式的数据
        ],
        "KVPList": [
            {
                "name": "收款",
                "value": "¥40.00"
            },
            {
                "name": "优惠",
                "value": "¥3.00"
            },
           {
                "name": "找零",
                "value": "¥3.00"
            },
           {
                "name": "实收",
                "value": "¥37.00"
            }
        ]
}

/**
*JSON数据格式说明:
*1:title字段为标题
*2:head字段是表头字段
*3:list是商品列表,表头和表的字段数量要一致 
*4:KVPList为结算键值对列表
*/

/**
*规则:
*当显示图文混合时:head的params赋值个数最小1个最大4个;list中每个元素的params赋值个数最*小1个最大4个;KVPList的size
*最小1个最大4个。
*/ 

/**
*receiverPackageName 接收数据的副屏显示app的包名DataType.DATA 
*DataType.DATA 
*DataModel.TEXT 
*jsonStr 显示的数据内容,具体格式参见下面的框
*callback 回调
*/
DataPacket pack = buildPack(receiverPackageName, DataType.DATA, DataModel.TEXT, jsonStr, callback);
mDSKernel.sendData(pack);

2.8、清单+单张图片

8

实现代码:

 {
        "title": "三米奶茶店欢迎您", 
        "head": {
            "param1": "序列号",
            "param2": "商品名",
            "param3": "单价""param4": "数量""param5": "小结"
        },
        "list": [
            {
              "param1": "1",
              "param2": "华夫饼",
              "param3": "10.00""param4": "1""param5":"10.00"
            },
            {
              "param1": "1",
              "param2": "吞拿鱼华夫饼",
              "param3": "12.00""param4": "1""param5":"12.00"
            }
            ... ...//这里是相同格式的数据
        ],
        "KVPList": [
            {
                "name": "收款",
                "value": "¥40.00"
            },
            {
                "name": "优惠",
                "value": "¥3.00"
            },
           {
                "name": "找零",
                "value": "¥3.00"
            },
           {
                "name": "实收",
                "value": "¥37.00"
            }
        ]
}

/**
*JSON数据格式说明:
*1:title字段为标题
*2:head字段是表头字段
*3:list是商品列表,表头和表的字段数量要一致 
*4:KVPList为结算键值对列表
*/

/**
*规则:
*当显示图文混合时:head的params赋值个数最小1个最大4个;list中每个元素的params赋值个数最*小1个最大4个;KVPList的size
*最小1个最大4个。
*/ 

String filePath = "xxx/img.png";//显示的图片路径
mDSKernel.sendFile(DSKernel.getDSDPackageName(), filePath, new ISendCallback() {
    public void onSendSuccess(long fileId) {
        show(fileId, jsonStr);//图片发送成功,显示文字内容
    } 
    public void onSendFail(int errorId, String errorInfo) {}
    public void onSendProcess(long total, long sended) {}
});

void show(long fileId, String jsonStr){
    jsonStr = UPacketFactory.createJson(DataModel.SHOW_IMG_LIST, jsonStr);
    //第一个参数DataModel.SHOW_IMG_LIST为显示布局模式,jsonStr为要显示的内容字符
    mDSKernel.sendCMD(DSKernel.getDSDPackageName(), jsonStr, fileId,null);
}

2.9、清单+幻灯片

8

实现代码:

{
        "title": "三米奶茶店欢迎您", 
        "head": {
            "param1": "序列号",
            "param2": "商品名",
            "param3": "单价""param4": "数量""param5": "小结"
        },
        "list": [
            {
              "param1": "1",
              "param2": "华夫饼",
              "param3": "10.00""param4": "1""param5":"10.00"
            },
            {
              "param1": "1",
              "param2": "吞拿鱼华夫饼",
              "param3": "12.00""param4": "1""param5":"12.00"
            }
            ... ...//这里是相同格式的数据
        ],
        "KVPList": [
            {
                "name": "收款",
                "value": "¥40.00"
            },
            {
                "name": "优惠",
                "value": "¥3.00"
            },
           {
                "name": "找零",
                "value": "¥3.00"
            },
           {
                "name": "实收",
                "value": "¥37.00"
            }
        ]
}

/**
*JSON数据格式说明:
*1:title字段为标题
*2:head字段是表头字段
*3:list是商品列表,表头和表的字段数量要一致 
*4:KVPList为结算键值对列表
*/

/**
*规则:
*当显示图文混合时:head的params赋值个数最小1个最大4个;list中每个元素的params赋值个数最*小1个最大4个;KVPList的size
*最小1个最大4个。
*/ 

List paths = new ArrayList<>();
paths.add(Environment.getExternalStorageDirectory().getPath() + "/sunmi1.png");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/sunmi2.png");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/sunmi3.png");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/sunmi4.png");

mDSKernel.sendFiles(DSKernel.getDSDPackageName(), "", paths, new ISendFilesCallback() {
    @Override
    public void onAllSendSuccess(long fileId) {               
      sendImgsListCMD(fileId,json);                           
    }

    @Override
    public void onSendSuccess(String path, long taskId) {}
    @Override
    public void onSendFaile(int errorId, String errorInfo) {}
    @Override
    public void onSendFileFaile(String path, int errorId, String errorInfo) {}
    @Override
    public void onSendProcess(String path, long totle, long sended) {}
    });
    
    void sendImgsListCMD(long fileId, String jsonStr){   
      jsonStr= UPacketFactory.createJson(DataModel.SHOW_IMGS_LIST, json);
        mDSKernel.sendCMD(DSKernel.getDSDPackageName(), jsonStr, fileId,null);
    }

2.10、清单+单个视频

10

实现代码:

mDSKernel.sendFile(DSKernel.getDSDPackageName(), Environment.getExternalStorageDirectory().getPath() + "/video_01.mp4", new sunmi.ds.callback.ISendCallback() {
    @Override
    public void onSendSuccess(long l) {     
        playVideoMenu(l);      
    }

    @Override
    public void onSendFail(int i, String s) {
        Log.d("highsixty", "发送单个文件视频和清单文件失败 ------------>" + s);      
    }

    @Override
    public void onSendProcess(final long l, final long l1) {
    }
});

private void playVideoMenu(long fileId) {
    try {
        JSONObject data = new JSONObject();
        data.put("title", "商米奶茶店收银");
        JSONObject head = new JSONObject();
        head.put("param1", "序号");
        head.put("param2", "商品名");
        head.put("param3", "单价");
        data.put("head", head);
        data.put("flag", "true");    //设置为true,则视频将接着之前的进度播放。设置为false,则视频将从头播放。
        JSONArray list = new JSONArray();
        for (int i = 1; i < 11; i++) {
            JSONObject listItem = new JSONObject();
            listItem.put("param1", "" + i);
            listItem.put("param2", products.get(i - 1));
            listItem.put("param3", prices.get(i - 1));
            list.put(listItem);
        }

        data.put("list", list);
        JSONArray KVPList = new JSONArray();
        JSONObject KVPListOne = new JSONObject();
        KVPListOne.put("name", "总计 ");
        KVPListOne.put("value", "132.00");
        JSONObject KVPListTwo = new JSONObject();
        KVPListTwo.put("name", "优惠 ");
        KVPListTwo.put("value", "12.00");
        JSONObject KVPListThree = new JSONObject();
        KVPListThree.put("name", "数量 ");
        KVPListThree.put("value", "10");
        JSONObject KVPListFour = new JSONObject();
        KVPListFour.put("name", "应收 ");
        KVPListFour.put("value", "120.00");
        KVPList.put(0, KVPListOne);
        KVPList.put(1, KVPListTwo);
        KVPList.put(2, KVPListThree);
        KVPList.put(3, KVPListFour);
        data.put("KVPList", KVPList);
        Log.d("HHHH", "onClick: ---------->" + data.toString());
        String json = UPacketFactory.createJson(DataModel.SHOW_VIDEO_LIST, data.toString());
        mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json, fileId, null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2.11、清单+视频轮播

10

实现代码:

/**
 * 左视频右清单文件
 */
private void sendMenuVideos() {
    Log.d(TAG, "sendMenuVideos: ------------->");
    List files = new ArrayList<>();
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_01.mp4");
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_02.mp4");
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_03.mp4");

    mDSKernel.sendFiles(DSKernel.getDSDPackageName(), "", files, new ISendFilesCallback() {

        @Override
        public void onAllSendSuccess(long fileid) {          
            playMenuVideos(fileid);
        }

        @Override
        public void onSendSuccess(String path, long taskId) {            
        }

        @Override
        public void onSendFaile(int errorId, String errorInfo) {           
        }

        @Override
        public void onSendFileFaile(String path, int errorId, String errorInfo) {           
        }

        @Override
        public void onSendProcess(String path, long totle, long sended) {
        }
    });
}

private void playMenuVideos(long taskID) {    
    try {
        JSONObject data = new JSONObject();
        data.put("title", "商米奶茶店收银");
        JSONObject head = new JSONObject();
        head.put("param1", "序号");
        head.put("param2", "商品名");
        head.put("param3", "单价");
        data.put("head", head);
        data.put("flag", "true"); //“true"视频续播;false 从头播放
        JSONArray list = new JSONArray();
        for (int i = 1; i < 11; i++) {
            JSONObject listItem = new JSONObject();
            listItem.put("param1", "" + i);
            listItem.put("param2", products.get(i - 1));
            listItem.put("param3", prices.get(i - 1));
            list.put(listItem);
        }
        data.put("list", list);
        JSONArray KVPList = new JSONArray();
        JSONObject KVPListOne = new JSONObject();
        KVPListOne.put("name", "总计 ");
        KVPListOne.put("value", "132.00");
        JSONObject KVPListTwo = new JSONObject();
        KVPListTwo.put("name", "优惠 ");
        KVPListTwo.put("value", "12.00");
        JSONObject KVPListThree = new JSONObject();
        KVPListThree.put("name", "数量 ");
        KVPListThree.put("value", "10");
        JSONObject KVPListFour = new JSONObject();
        KVPListFour.put("name", "应收 ");
        KVPListFour.put("value", "120.00");
        KVPList.put(0, KVPListOne);
        KVPList.put(1, KVPListTwo);
        KVPList.put(2, KVPListThree);
        KVPList.put(3, KVPListFour);
        data.put("KVPList", KVPList);
        Log.d("HHHH", "onClick: ---------->" + data.toString());
        String json = UPacketFactory.createJson(DataModel.MENUVIDEOS, data.toString());
        mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json, taskID, null);
    } catch (JSONException e) {
        e.printStackTrace();
    }
}

3、判断副屏的尺寸

主屏业务应用可通过获取副屏Model值判断副屏尺寸,实现根据副屏尺寸输出相应的显示内容的业务逻辑。

3.1、从主屏获取副屏Model值

实现代码:

String subModel = Settings.Global.getString(getContentResolver(), "sunmi_sub_model")

//返回值:“t1sub14”对应14寸副屏,“t1sub7”对应7寸副屏。返回其它值表示获取失败。

3.2、从副屏获取副屏Model值

实现代码:

//从副屏获取副屏model
case R.id.btn_get_sub_model:
JSONObject jsonObject2 = new JSONObject();
try {
  jsonObject2.put("dataModel", "GET_MODEL");
  jsonObject2.put("data", "");
} catch (JSONException e) {
  e.printStackTrace();
}

DataPacket p2 = new DataPacket.Builder(DSData.DataType.CMD).recPackName(SF.SUNMI_DSD_PACKNAME).data(jsonObject2.toString())
.addCallback(new ISendCallback() {
  @Override
  public void onSendSuccess(long taskId) {
  }    
  
  @Override                         
  public void onSendFail(int errorId, String errorInfo) {           
  }     
                       
  @Override
  public void onSendProcess(long totle, long sended) {              
  }
}).build();

mDSKernel.sendQuery(p2, new QueryCallback() {                 
  @Override                 
  public void onReceiveData(final DSData data) {                      Log.d("highsixty", "onReceiveData: ------------>" + data.data); runOnUiThread(new Runnable() {                         
      @Override                         
      public void run() {                             
        Toast.makeText(MainActivity.this, "从副屏获取副屏model-->" + data.data, Toast.LENGTH_LONG).show();                         
      }
    });
  }
});             
break;
//返回值:“t1sub14”对应14寸副屏,“t1sub7”对应7寸副屏。返回其它值表示获取失败。

4、清除副屏缓存

4.1、查询副屏指定缓存目录大小

实现代码:

JSONObject jsonObject = new JSONObject();
try {
    jsonObject.put("dataModel", "GETVICECACHEFILESIZE");
    jsonObject.put("data", Environment.getExternalStorageDirectory().getAbsolutePath() + "/HCService/" + getPackageName().replace(".", "_"));
} catch (JSONException e) {
    e.printStackTrace();
}

DataPacket packet = new DataPacket.Builder(DSData.DataType.CMD).recPackName(SF.SUNMI_DSD_PACKNAME).data(jsonObject.toString())
        .addCallback(null).build();
mDSKernel.sendQuery(packet, new QueryCallback() {
    @Override
    public void onReceiveData(final DSData data) {
        Log.d("highsixty", "onReceiveData: ------------>" + data.data);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, "副屏缓存文件大小字节数为" + data.data, Toast.LENGTH_LONG).show();
            }
        });
    }
});

4.2、清除副屏指定缓存目录

实现代码:

DataPacket packet2 = UPacketFactory.remove_folders(DSKernel.getDSDPackageName(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/HCService/" + getPackageName().replace(".", "_"), new ISendCallback() {
    @Override
    public void onSendSuccess(long taskId) {
        showToast("清除缓存文件成功");
    }

    @Override
    public void onSendFail(int errorId, String errorInfo) {
        showToast("清除缓存文件失败");
    }

    @Override
    public void onSendProcess(long totle, long sended) {
    }
});

mDSKernel.sendCMD(packet2);

4.3、查询指定缓存文件是否存在

实现代码:

//说明:该方法检查fileId对应的文件在副屏是否存在,
第一个参数为之前发送文件时副屏返回的文件id,该id是所有的文件唯一的,第二个参数为结果回调
mDSKernel.checkFileExist(fileId, new ICheckFileCallback(){
  @Override
  public void onCheckFail() {}
 
  @Override
  public void onResult(boolean arg0) {
     //返回值为true是存在
     //返回值false是不存在  
      }                                    
});

4.4、清除副屏指定缓存文件

实现代码:

mDSKernel.deleteFileExist(FileID, new ICheckFileCallback() {
    @Override
    public void onCheckFail() {
        Log.d(TAG, "onCheckFail: ----------->");
    }

    @Override
    public void onResult(boolean exist) {
        Log.d(TAG, "onResult: ---------->" + exist);
    }
});

5、图片/视频显示规则

图片/视频的缩放规则:

  • 图片/视频默认居中等比例缩放,至少有两条边与显示容器边界接触;

图片/视频的建议分辨率:

  • 7寸:
  • 全屏显示图片:1024*600
  • 全屏显示视频:1024*600
  • 14寸:
  • 全屏显示图片/视频:1920*1080
  • 图片/视频+清单:1186*1080

建议文件大小:

  • 图片:≤1MB/张
  • 视频:≤5MB/个

建议幻灯片文件数量:

  • 图片:≤10张

建议轮播视频文件数量:

  • 视频:≤10个

注:因文件传送至副屏需要时间,显示文件时可能会有延时。故尽量在空闲时先将图片和视频文件缓存至副屏,当需要在副屏显示时,根据文件ID调用。