客显开发说明

Sunmi T1mini/T2mini有顾客显示器,开发者可以参考客显开发文档来使用客显。

客显开发文档 | 客显DEMO

扫码头返回类型


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调试,及调试权限;

    操作说明

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

    操作说明

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

(强调: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主屏与副屏通过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秒

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 自定义副显程序开发

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

注:插入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"/>

 

钱箱驱动

关于商米设备操作钱箱

商米部分设备如T1可以连接外部钱箱,App可以通过以下两种方式打开钱箱。

一、通过AIDL文件中封装的方法打开钱箱。

二、通过指令打开钱箱。

一、通过AIDL文件中的方法打开钱箱

1.下载相关 资源文件 ,在项目中新建如下层级的package,将源文件中的AIDL文件放入package中。

1

2.操作钱箱的只有打开钱箱和获取钱箱打开次数这两个方法如下图所示,两个方法在IWoyouService.aidl文件中,开发者可以在自己的代码中调用。

2

二、通过ES/POS指令的方式

开发者可以通过两种方式向服务发送ES/POS指令

1.通过虚拟蓝牙与服务建立连接,发送ES/POS指令,可参照 打印机驱动 文档中的通过蓝牙连接调用发送指令打开钱箱,打开钱箱的ES/POS指令如下:

byte[] aa = new byte[5];
aa[0] = 0x10;
aa[1] = 0x14;
aa[2] = 0x00;
aa[3] = 0x00;
aa[4] = 0x00;

2.通过AIDL方法中封装的sendRAWData(bytes [] ,callback )方法发送指令,同样需要用到方式一中的资源文件,可参照 打印机驱动文档中的AIDL打印方式:

byte[] aa = new byte[5];

aa[0] = 0x10;
aa[1] = 0x14;
aa[2] = 0x00;
aa[3] = 0x00;
aa[4] = 0x00;

 try {
  woyouService.sendRAWData(aa, callback);
  } catch (RemoteException e1) {
     e1.printStackTrace();
   }

商米扫码

为什么要使用商米封装的扫码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); }

 @Override

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

super.onActivityResult(requestCode, resultCode, 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”));//这个是扫码的结果

}

}

}

第二种方式:

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; 

        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); 

    int result = scanner.scanImage(source); 

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

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

  }       

so更新记录

历史资源文件

版本更新内容
1.3.0 1.去掉libiconv.so,即解码库只有一个so文件libsunmiscan.so(包括32,64位平台)
2.解决内存泄漏问题
3.增加点阵打印QR码识读,开启方式scanner.setConfig(Symbol.QRCODE, Config.ENABLE_DPM,1);//允许识读点阵打印QR码,默认0:禁止
1.2.9 1.增加64位平台(arm64-v8a)解码库
2.去掉libscaninit.so,同时jar包去掉libscaninit.so库版本查询接口
3.jar包版本更新为1.1.1
4.修复若干由于增加64位解码库发现的兼容性问题
1.2.8 1.优化code39解析能力
2.针对银联扫码能力测试要求的部分修改,该版本已通过银联扫码能力测试和最佳实践测试
3. 增加ISBN10 ISBN13输出使能控制
1.2.6 1.增加解码结果字节模式输出接口
2.解决倾斜角度扫一维码偶现程序异常的问题
1.2.5 1. 增加解码结果字节模式输出接口
2.优化一维码定位算法和解码时间
1.2.4 1.解决码字符中有不可以显示字符导致的解码结果字符解析异常的问题
2.其他优化
1.2.2 1. 优化打开相册读取高分辨率的图片解码慢的问题
2.解决使用Android7.0后硬件管家的扫码成功后没有结果返回的问题,
1.2.1 1.新增支持的二维码DataMatrix和Aztec(默认关闭,需要设置开启识读)
2.支持识读缺损一个定位符的QR码的识读
3.解决因开启多码同时识读时的bug
1.1.0 1.一维码可360度识读,之前的版本只在接近水平垂直方向识读
2.QR码识别速度提升一倍
3.解决QR码混合编码(中英文等)译码出现的bug
4.解决PDF417测试出现的潜在内存异常风险(如除0,内存溢出等)
1.0.1 1.增加了二维码PDF417的解码算法(默认关闭,需要设置开启识读)
2.减少了部分一维码的误码率
3.对于比较长的一维码可以竖屏全屏读取
4.对于上下并排的多个一维码,优先读取中间的条码
5.一维码和QR码支持反色和镜像解码,PDF417支持镜像解码
6.增加了解码库版本查询功能
1.0.0基础版本, 支持识读二维码QR和10多种主流一维码

更多说明

商米扫码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

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调试,及调试权限;

    操作说明

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

    操作说明

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

(强调: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调用。

商米收银管家

商米收银管家产品介绍

商米收银管家为SaaS软件提供一站式的支付解决方案。可受理微信、支付宝、支付宝人脸、银联钱包等扫码或刷脸支付方式。商米收银管家已对接多条支付通道。商米收银管家已适配商米全部机型。SaaS软件可通过调用商米收银管家,完成支付。仅需一次对接开发,即可便捷使用多条支付通道。

以下为相关开发文档和SDK,请根据实际调试设备下载使用

1)商米非金融收银台及接口文档(支持T系列、D系列、S系列、X系列、K系列、H系列、V系列、M系列、L系列的机型)

2)商米P系列设备收银台及接口文档(支持P1-4G、P2lite和P2机型)

3)商米收银管家云对云接口(目前支持查询、退款、微信小程序公众号支付和支付回调操作)

支付业务接入对接人:赵琨 18101811986

商米收银管家-业务对接FAQ

商米收银管家-技术对接FAQ