有贊零售智能硬件體系搭建歷程

前言

有贊零售 App 上線至今,爲了降低商家硬件遷移成本,同時提高商家硬件採購的選擇多樣性,陸陸續續對接了市面上 Top 20+ 的智能硬件,包括打印機、電子秤、掃碼槍、攝像頭、一體機等, 在硬件對接過程中團隊投入了大量的人力進行支持,受限於硬件架構不成體系、硬件類目劃分不清晰、通信協議多樣性、多端重複適配造輪子等因素,導致硬件線上問題較多,且投入的開發成本很高,也影響了商家的正常經營。爲了徹底解決這些問題,提高新設備對接效率,並確保硬件交互質量,有贊零售移動團隊對硬件體系做了幾次重構演進,目前一款新硬件的對接與適配成本已經控制在一到兩個工作日內,相較2019年人力投入降低了50%。同時通過不斷完善硬件 FAQ 文檔,協助商家與硬件支持同學快速定位解決問題,硬件開發同學直接處理的線上問題數量相較2019下半年環比下降55%,技術支持同學對接的硬件問題也環比下降了33%,提效比較明顯。

一、智能硬件矩陣

1.1 設備使用場景簡介

硬件類型 使用場景 對接設備
一體機 線下門店都會在收銀臺配置一款收銀機,方便商家與收銀員進行門店經營開單操作 商米、天波、聯迪、中科英泰等
打印機 訂單正向與逆向環節需要打印小票,比如購車小票、退貨小票等 365 、映美雲、佳博、思普瑞特、易聯雲等
副屏 開單支付與會員結算流程中,訂單信息對顧客足夠透明,可通過副屏將購物車、會員、支付相關信息投影到副屏上 商米 T2、T1、D2、聯迪等
客顯 除了副屏可以投影訂單數據之外,還提供客顯這種低成本的外接設備進行數據投影 中崎等
人臉識別 通過攝像頭採集顧客人臉信息,支持會員快速識別、快捷支付等 青蛙 Pro、蜻蜓、三方攝像頭等
NFC & Rfid 通過對接磁條、nfc、Rfid 等外接設備,滿足實體卡支付、Rfid 條碼商品(一般應用在服裝品類)等加購場景 cas、靈天智能等
電子秤 生鮮果蔬商家涉及到稱重環節,通過適配電子秤滿足稱重的經營場景 凱士、大華、歐陸達、S2 等
POS機 部分商家不採購收銀機,只需要使用 POS 進行訂單結算,且需要支持刷卡功能 WANGPOS、SUNMI P 系列等

1.2 硬件矩陣圖

1.3 體系搭建介紹

有贊零售對接的設備種類繁多,由於篇幅內容有限,接下來會着重講解打印機、 POS 、電子秤、副屏相關技術的設計細節。

二、硬件庫拆解重構

零售設備庫 sdk 早期設計類似於全家桶,聚合了打印機、電子秤、POS 機等所有設備,擴展性比較差,隨着新機器的適配接入,造成 sdk 頻繁升級,穩定性無法保證。前期只接入幾款設備勉強還能應付過來,隨着業務迭代發展,設備接入種類與數量越來越多,當前的設備庫架構設計顯得非常臃腫,維護與適配成本比較高,開發對接效率也非常低。

爲了徹底解決這些問題,組內經過多次討論與論證,全家桶的方式需要被徹底推翻改造,首先要做的就是對設備進行分類,將通用的設備放到單獨的 module 中進行維護,打成 aar 給業務方靈活調用,且需要下沉抽象出一些通用能力,降低新設備的接入成本,通過這次的架構設計迭代,新設備適配人力成本減少 2 倍以上,且硬件上線質量也得到了有效保證。

架構圖

新設備庫框架部分參考 Android 系統架構模型,分爲 OEM、Core 、 Base 、Library 四層,OEM 爲業務 Manager 層,業務方只需要感知 Manager 提供的 Api ,底層能力通過 Core、Base 支撐,同時 Library 層將硬件之間一些通用的三方sdk(比方說 WANGPOS SDK 既提供刷卡能力,又提供打印能力,聚合多個 OEM 的功能,可以共享)共享出來,供 OEM 層調用。

2.1 設備庫架構介紹

2.1.1 OEM 層

提供 PrinterManager 、 PosManager 、 WeightManager 、 XXXManager 等設備管理類,供業務方調用,且每個設備單獨打成 aar ,供業務方靈活依賴。

例如:

零售工程通過模塊化進行開發管理,所有硬件能力通過 module_device 向外提供能力,業務 module 通過調用模塊間定義的向外暴露接口(有贊零售模塊間通信方式詳細設計請參考這篇文章 Android -模塊化-面向接口編程)來訪問 module_device 提供的能力,同時 module_device 會依賴設備能力各自的 aar ,其他業務模塊只需要向設備模塊要能力,不需要關心設備模塊具體的實現,模塊之間責任劃分清晰。

調用示例圖:

2.1.2 Core 層

提供設備通用能力,包括設備模型、連接能力、緩存能力、設備狀態心跳檢測、異常處理、線程管理、讀寫能力等。

1)設備模型

零售對接瞭如此多的的設備,設備模型的抽象尤爲重要,包括設備連接類型、設備 id 、設備型號、設備狀態、設備標籤、是否需要緩存等,分類設備又可以基於設備模型進行接口擴展,比如 IPrinter 抽象出打印能力,IPos 抽象出刷卡、退款等能力,IWeight 抽象出稱重、置零、去皮等能力,設備實體各自實現 IPrinter 、IPos 、 IWeight 接口,實現接口提供的相應方法,通過面向接口編程,業務劃分與代碼管理清晰很多。

UML :

2)設備狀態心跳檢測

有贊零售收銀臺右上角“收銀中心”聚合了很多收銀通用能力,其中就包括了外接設備的狀態管理,該功能可以實時監測設備狀態,在快速定位線上問題過程中發揮了非常重要的作用,且也能協助商家對設備進行健康自檢。

  • 註冊心跳,開啓心跳檢測

    /**
     * 註冊監聽
     */
    fun registerCheckState(listener: IDeviceStateListener) {
        if (!deviceStateListeners.contains(listener)){
            deviceStateListeners.add(listener)
        }
        checkHeart()
    }
    
    
    /**
     * 檢查心跳
     */
    private fun checkHeart() {
        if (cacheDevices.isNullOrEmpty()) {
            return
        }
        if (!isCheckingHeart) {
            isCheckingHeart = true
            DeviceThreadManager.threadPoolProxy.getHeartExecutor().execute(HeartTask())
        }
    }
    
  • 心跳機制

    單線程內開啓 while 循環,每次心跳間隔 2 秒

    inner class HeartTask : Runnable {
            override fun run() {
                while (true) {
                    if (cacheDevices.isNullOrEmpty()) {
                        continue
                    }
                    for (entity in cacheDevices) {
                        val newState = entity.device.getState()
                        var countChange = false
                        if (deviceCount != cacheDevices.size){
                            countChange = true
                            deviceCount = cacheDevices.size
                        }
                        val shouldNotify = (entity.getState() != newState) || countChange
                        entity.setState(newState)
                        for (listener in deviceStateListeners){
                            if (listener is IDeviceStateAlwaysListener){
                                listener.onDeviceState(entity)
                            } else {
                                // 會與上一次心跳狀態進行比較,狀態不一樣時,纔會回調
                                if (shouldNotify){
                                    listener.onDeviceState(entity)
                                }
                            }
                        }
    
                    }
    
                    try {
                        Thread.sleep(STATE_UPDATE_SUSPEND)
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            }
        }
    }
    
3)讀寫能力

打印小票的前提是將 ESC / POS 協議字節數據輸入到打印機驅動中,這裏涉及到寫的場景。而在生鮮果蔬行業涉及到稱重場景中要用到電子秤,商品重量需要實時傳輸到收銀機,這個又涉及到讀的場景,底層抽象讀寫接口,業務方自己實現,這塊底層做的比較輕。

 /**
 * 讀接口
 */
interface IRead<T> {
    fun read(): T?
}

/**
 * 寫接口
 */
interface IWrite<T> {
    fun write(content: T?)
}

// 大華電子秤讀取商品重量,業務方自己實現
class DahuaWeight: IWeight, IRead<String?>{
      
 
    override fun read(): String? {
        return DahuaWeightSdk.getWeight()
    }
}
4)緩存能力

有贊零售 app 爲了滿足設備連接多樣性,支持同時連接多款設備,且針對每款設備提供手動斷開、連接能力(比方說餐飲行業,前臺與後廚都連接了打印機,退款小票只需要在前臺打印機打印的話,後廚的打印機可以手動點擊斷開),且我們需要確保商家退出 app 、app 覆蓋升級等場景,設備的狀態可以恢復,基於這種場景必須要支持本地緩存能力,下次 app 進入讀取本地緩存,繪製 UI 即可。

/**
 * 設備緩存管理
 * 緩存到本地文件
 */
class DeviceCacheManager {
    //添加設備
    fun addDevice(deviceInfo: DeviceInfo?) {
        if (addInner(deviceInfo)){//添加到內存
            memoryToCache()//刷到緩存
        }
    }
    //刪除設備
    fun removeDevice(deviceInfo: DeviceInfo?) {
        if (removeInner(deviceInfo)){//從內存中刪除
            memoryToCache()//刷到緩存
        }
    }
    //獲取設備列表
    fun getCacheDevices(tag: String): List<IDevice>? {
        if (cacheDevices.isNullOrEmpty()){
            cacheToMemory()
        }
 
        return getDevicesByTag(tag)
    }
}
5)線程管理

設備的狀態監測、IO 讀寫、耗時邏輯處理都涉及到線程切換,目前底層提供配置線程池統一管理,避免線程隨意創建,搶佔系統資源,拖累收銀機的性能(零售對接了很多低端設備,線程控制非常嚴格,且部分機型可能出現 p-thread 問題,線程創建數量超出一定數量後, app 將 crash )。

... ...
private val diskIOExecutor  = Executors.newSingleThreadExecutor(DeviceThreadFactory("diskIO"))
private val heartExecutor  = Executors.newSingleThreadExecutor(DeviceThreadFactory("heart"))
private val networkExecutor = Executors.newFixedThreadPool(3, DeviceThreadFactory("network"))
private val scheduleExecutor = ScheduledThreadPoolExecutor(5, DeviceThreadFactory("schedule"), ThreadPoolExecutor.AbortPolicy())
... ...

6)異常模型

硬件的異常管理在實際開發與交互提示流程中非常重要,比方說打印機是否缺紙了、電子秤是否斷開了等場景,通過交互提示能協助快速定位排查問題。

{
    // 設備名稱
    "deviceName":"sunmi",
    // 額外信息
    "extra":"",
    // 當前設備的連接狀態
    "state":0,
    "error":{
        // 打印機異常狀態碼
        "code":1,
        // 打印機異常信息詳情
        "message":"打印機缺紙/打印機離線/打印機斷開"
    }
}

2.1.3 Library 層

部分設備連接需要依賴硬件廠商提供的 sdk , 且不同分類的設備可能共享該 sdk , 這類的sdk可以放到 Library 進行管理,避免設備重複依賴。

庫簡介:

library名稱 功能介紹
woyou.aidlservice 商米打印與稱重 aidl 接口
sprtprintersdk 思普瑞特打印能力
paymentService 商米 P1 刷卡 sdk
cloudpossdk、wangpossdk WANGPOS 刷卡打印能力
2.1.4 Base 層

提供最基礎的能力,包括網絡請求、log 埋點等。

2.2 硬件庫實現細節

2.2.1 打印機

零售對接的打印設備非常多,包括藍牙、usb 、http 等,原有的設計中打印機與 pos 、電子秤功能聚合在一起,功能耦合嚴重,不同的硬件開發人員都會改動設備庫的代碼,導致 sdk 頻繁發版,違背開閉原則,設備庫穩定性也無法得到保證。需要將通用能力抽出來,包括連接能力、打印能力、協議封裝能力等,確保新的設備能夠快速接入。

解決方案

UML :

技術細節描述:

PrinterManager 暴露相應的 api 給業務方調用,DeviceCoreManager 提供 Core 通用能力(包含緩存能力、連接能力、線程切換能力等)並作爲 PrinterManager 的成員變量,所有的打印機實體繼承 AbsPrinter 基類(實現一些基本信息,以及相關方法做了默認實現),AbsPrinter 又實現 IPrinter 接口,IPrinter 繼而又繼承 IDevice 接口,同時部分打印機又可以打開錢箱,需要實現 IMoneyBox 接口。

IPrinter :

interface IPrinter : IDevice {
    ... ...
    /**
     * 設備紙張類型
     *
     * @return
     */
    fun getPagerType(): PagerType

    /**
     * 獲取設備協議
     *
     * @return
     */
    fun getProtocol(): Protocol


    /**
     * 打印內容
     *
     */
    fun print(content: ByteArray): PrinterResponse

    /**
     * 打印內容,附加一些信息
     *
     */
    fun print(content: ByteArray, extraInfo: String?): PrinterResponse

    /**
     * js DeviceName
     * @return
     */
    fun jsDeviceName(): String

    fun isSupportJSPrinter(): Boolean
    ... ...
}
2.2.2 POS 機

零售開發早期,開發了獨立的 POS 收銀臺,直接訪問第三方支付公司(通聯等)提供的刷卡接口,且針對 8583 協議(8583協議)進行自定義封裝,代碼複雜度與維護成本很高,在線上運行一段時間後,發現接口不太穩定,商家經常出現刷卡不成功問題。後期與 POS 廠商溝通後,直接對接了 POS 廠商提供的刷卡 sdk, 刷卡穩定性得到了提升,但是從設備庫設計來說還是要兼容自建收銀臺功能,目前還有部分商家在使用老的刷卡方式能力,不能貿然遷移。

零售 POS 對接現狀:

交易模塊、訂單模塊、儲值模塊、支付模塊都有使用過刷卡能力,但是各自調用的 sdk 不盡相同,包括 ecosy、zanpay、pos_pay_sdk 等,開發與維護成本很高

解決方案

UML :

技術細節描述:

PosManager 暴露相應的 api 給業務方調用,DeviceCoreManager 提供 Core 通用能力(連接能力、線程切換能力等)並作爲 PosManager 的成員變量,所有的 POS 機實體繼承 AbsCashier 基類(實現一些基本信息,以及相關方法做了默認實現),AbsCashier 又實現 IPos 接口,同時 IPos 繼承 IDevice 接口。AbsPrinter 會維護 PosChainTaskList 隊列,分別對應 POS 中籤到、收單、支付、上報流程。這些 Task 業務方需要注入並做接口實現,底層只會維護調用鏈路,不關心業務 Task 的執行內容。

IPos :

interface IPos : IDevice {

    /**
     * 刷卡支付
     *
     */
    fun payByPos(entity: PhoinexPosPayResult): Observable<PhoinexPosPayResult>

    /**
     * 取消支付
     *
     * @param orderNo
     * @param voucherNo
     */
    fun revoke(orderNo: String, voucherNo: String): Observable<Any>

    /**
     * 退款
     *
     * @param orderNo
     */
    fun refund(orderNo: String): Observable<Any>
}
2.2.3 電子秤

電子秤提供的能力比較簡單,IWeight 提供去皮、置零等能力,電子秤的讀取通過 CallableData (類似參考LiveData實現)進行 postValue 分發,同時 WeightManager 提供了設備基本的增刪改查能力。

解決方案

UML :

技術細節描述:

WeightManager 暴露相應的 api 給業務方調用,Weight 相對比較簡單,所有的電子秤都實現 IWeight 接口,IWeight 集成 IDevice 接口,同時 DeviceCoreManager 爲電子秤提供底層能力(讀寫能力、連接能力、緩存能力等)支持,電子秤部分是串口通信,需要實現 UsbReceiver 廣播監聽 usb 線的插拔狀態。

IWeight :

interface IWeight: IDevice {

    // 去皮
    fun doTare(): Pair<Boolean, String>

    // 置零
    fun fun doZero(): Pair<Boolean, String>

}

2.3 灰度上線方案

硬件重構相當於推倒重來,如此大的改動上線必須要穩,故此採用 AB Test 進行灰度,一部分商家繼續使用老 sdk ,一部分商家使用新 sdk ,新 sdk 進行數據異常埋點,當檢測到新的設備庫出現問題後,配置中心操作,使用新 sdk 的商家收銀機會立即回滾到老設備庫。

方案圖

灰度工具:AB-Test

三、打印機協議統一

移動團隊配合硬件支持同學根據商家需求適配對接了十幾款市面上口碑與穩定性較高的打印機設備,包括 365 、佳博、映美雲、思普瑞特、飛蛾等品牌,且技術上適配了 usb 、藍牙、 wifi 等多種連接方式,爲商家硬件選配提供了多樣性選擇。團隊對接打印機的過程中投入了大量的人力支持,也踩了不少坑,同時新設備的對接效率始終比較低,且穩定性不夠,商家經常反饋一些連接與打印問題,開發人員的自我成就不高,且對商家的經營場景造成了影響。在技術側特別是打印機協議適配涉及到多端參與( Android 、iOS 、前端等),重複造輪子的同時,也很難保證協議解析的穩定性與統一性,爲了降低多端打印協議適配成本,痛定思痛,技術上利用 js 作爲橋接層對打印協議進行統一解析預處理,業務方只需要根據一定格式(類似於 html )輸入打印內容,js 層會針對打印內容映射爲打印協議,且該方案支持跨平臺與動態化,目前零售所有的打印業務都是通過這種方式進行適配,穩定性得到了保障,且維護成本也被極大的降低,詳細技術方案請看這兩篇文章。(有贊零售小票打印跨平臺解決方案有贊零售跨平臺打印庫方案

架構圖

PC、Android、iOS 將打印內容輸入到 JsCore , JsCore 解析匹配打印數據,適配成特定的打印協議( ESC / POS 等),端獲取到打印協議後,將打印協議輸入給打印機,打印機讀取到協議數據後進行打印,且 JsCore 可通過後端配置中心進行動態下發,實時修復問題,無需重發版。

3.1 舉例:打印電子發票

3.1.1 小票模板編輯

每個小票都可在後臺配置小票模板,對小票的基本信息、商品信息、支付信息、買家信息、其他信息進行編輯,且編輯後之後可以實時預覽,小票模板編輯完成後,有贊零售 app 啓動後會拉取小票模板數據,存在本地,當下次觸發小票打印任務時,會將本地模板數據與打印數據進行結合,傳入到 JsCore 中,輸出打印協議,傳輸到打印機中進行打印。

  • 小票模板配置樣式

  • 小票模板預覽樣式

  • 小票模板配置源代碼

    <html><head></head>
    <body>
    <p style="font-size:24px;text-align:center;">{{shopName}}</p>
    <p style="font-size:24px;text-align:center;">電子發票自助開票</p><br>
    <p>訂單號:{{orderNo}}</p>
    <p>訂單時間:{{createTime}}</p>
    <p>店鋪名稱:{{shopName}}</p><br>
    <p>電子發票開票日期同申請電子發票的日期</p><br>
    <p>建議您在消費後{{timeScopeStr}}掃碼開具發票,超過建議時間後無法開票請聯繫商家,服務電話:{{shopPhone}}</p>
    <qrcode style="text-align:center;">{{invoiceUrl}}</qrcode>
    <p style="text-align:center;">微信掃碼開具電子發票</p>
    </body>
    </html>
    
3.1.2 小票打印內容:

小票進行打印時,實時從後端拉取打印內容

{"shopName":"有讚的店", "orderNo":"12345xxx", "createTime":"2020/07/01-11:00", "timeScopeStr":"12345xxx", "invoiceUrl":"http://xxxxxx"}
3.1.3 JsCore執行流程:

將小票打印內容與打印模板數據傳入到 JsCore 中,js 會將模板進行填充(打印模板中{{ key }}與打印內容的 value 映射匹配起來),jsCore 解析 html 樣式,翻譯成相應的打印協議( ESC / POS 、三方打印機自定義打印協議等)

3.1.4 JsCore封裝打印協議優勢:
  1. 多端打印協議解析邏輯統一,節省人員投入成本
  2. js 可動態下發,動態修復線上問題,無需發版
  3. jsCore 單端維護,開發與維護成本非常低

四、副屏佈局插件化改造

商家在使用有贊零售進行收銀過程中每天都會進行開單操作,開單完成後大部分顧客都想實時感知自己買了哪些商品、結算了多少錢、享受了多少優惠,爲了保證交易透明,零售開發了副屏功能,支持將購物車商品列表、會員信息、支付信息、營銷結算等信息實時投影到副屏上,同時支持閒時、忙時動態配置切換,商家可在pc後臺編輯廣告圖片與視頻資源,投放到有贊零售app副屏上,起到廣告宣傳作用。

副屏內容編輯後臺

副屏開發過程中也磕磕絆絆,踩過不少坑,比如副屏的連接穩定性、View 的佈局繪製性能、圖片內存佔用被打爆問題,且副屏的架構設計也經歷了幾次迭代,現在功能趨於穩定,業務方可以靈活定製自己的插件,註冊到副屏模塊中,模塊底層識別插件,按照一定的規則進行渲染展示。在保證高擴展性的同時,也降低了接入成本。

UML

SubMainManager 作爲副屏初始化入口,在 App Application 初始化的時候被調用,目前實現了 Sunmi7Manager(商米 7 寸,AIDL 通信)、Sunmi14Manager (商米 14 寸, AIDL 通信)、SunmiT2AclasManager (商米 T2 等設備,presentation 通信)等設備,
通過 SubEntity 對象與副屏進行通信,業務方可自定義 Plugin( Plugin 內自定義業務需要顯示的 View ),發送到相應設備的副屏 Manager ,副屏 Manager 最終會調用 SubTemplateManger 對 SubEntity 進行解析,將業務 Plugin (反射構造 Plugin 實體)提供的 LayoutId 解析成相應的 View ,添加到副屏上進行渲染投屏。

4.1 SubEntity

主機通過 SubEntity 與副屏進行通信,可定義具體的 action 、 jsonData (業務方自定義數據)、 leftPlugin (副屏左邊屏幕顯示的插件,內容爲 plugin 實體的 className )、 rightPlugin (副屏右邊屏幕顯示的插件,內容爲 plugin 實體的 className )等。

@Keep
public class SubEntity {
    ... ...
    @SerializedName("action")
    public int action;

    @SerializedName("title")
    public String title;

    @SerializedName("sub_setting")
    public String subSetting;

    @SerializedName("templateName")
    public String templateName;
    
    /**
     * 統一數據
     * (如果爲數據結構,建議請自行通過json解析與反解析,
     *  原則上只通過此字符串交流,如果要處理數據,請自行序列化,並且自行解析。
     *  本質上僅爲字符串,由使用方充分使用即可)
     */
    @SerializedName("jsonData")
    @Nullable public String jsonData;

    @SerializedName("leftPlugin")
    public String leftPlugin;

    @SerializedName("rightPlugin")
    public String rightPlugin;
    ... ...

}

4.2 SubPlugin

每個業務實現自己的業務插件,插件中包含 LayoutId, SubTemplateManager 會解析 Plugin 數據,將插件提供的 LayoutId Inflate 成 View 渲染在副屏上

副屏樣式實例:

public abstract class SubPlugin {

    private Context context;

    public SubPlugin(Context mContext) {
        context = mContext;
    }

    public abstract int getLayout();

    public abstract void createView(View view, Bundle bundle);

    public abstract void updateView(Bundle bundle);

    public abstract View getView();

    @Nullable
    public abstract View getView(LayoutInflater inflater, @Nullable ViewGroup container);
}

4.3 SubTemplateManager 解析 Plugin 過程

4.3.1 通過 Plugin ClassName 反射構造 Plugin 實體
@Nullable
    public static SubPlugin getPlugin(Context context, String className){
        if(TextUtils.isEmpty(className)){
            return null;
        }

        SubPlugin plugin = null;
        try {
            plugin = (SubPlugin) Class.forName(className)
                .getConstructor(Context.class).newInstance(context);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return plugin;
    }
4.3.2 解析 Plugin layoutId 字段 inflate 成 View 佈局,並將 View 渲染到副屏上
... ...
if (null != leftPlugin) {
    View left = LayoutInflater.from(mContext).inflate(
        leftPlugin.getLayout(), leftView, false);
    leftPlugin.createView(left, bundle);
    leftView.removeAllViews();
    leftView.addView(leftPlugin.getView());
}

if (null != rightPlugin) {
    View right = LayoutInflater.from(mContext).inflate(
        rightPlugin.getLayout(), rightView, false);
    rightPlugin.createView(right, bundle);
    rightView.removeAllViews();
    rightView.addView(rightPlugin.getView());
}
... ...

4.4 踩過的坑

4.4.1 副屏存儲空間有限,容易被充滿,導致副屏功能不可用

開啓 Timer 4 小時檢查一次副屏,當副屏可用存儲空間小於總空間的 30 %,主動清空副屏磁盤。

 mConsumer = new Consumer<Long>() {
            @Override
            public void accept(Long aLong) throws Exception {
                checkCache();
                checkMainStorage(mContext);
            }
        };

// 默認是4小時檢查一次緩存
int CHECK_CACHE_TIMER = 4;
mFlowable = Flowable.interval(CHECK_CACHE_TIMER, TimeUnit.HOURS)
        .subscribeOn(Schedulers.io())
        .doOnError(new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
                throwable.printStackTrace();
            }
        });
4.4.2 商米 T1 副屏調試困難,前期開發過程中採用打 log 方式調試,效率非常低

由於商米 T1 主副屏通過 usb 進行連接,當 pc 電腦插上 usb 後,pc 電腦將被認爲是主設備,而收銀機則成爲從設備,收銀機主副屏的連接將斷開。可以通過 adb connect 方式進行調試,adb connect 192.168.xx:5555 連接主屏, adb connect 192.168.xx:5554 連接副屏, ip地址爲收銀機的 ip 地址。

原理圖:

IoT

硬件問題排查過程非常痛苦,商家的網絡環境、設備連接狀況、外接設備類型這些關鍵信息總是無法及時收集,且商家反饋內容經過服務同學、技術支持等一層層上來後,信息容易失真,從而造成問題排查成本非常高。零售提供 IoT 解決方案,商家所有外接設備全部上雲,當商家設備出現問題,可以通過後臺數據及時拉取商家實時的設備狀態,協助快速排查問題。

  • 後臺:

    後臺可以採集設備的類型、名稱、型號、連接狀態等信息。

  • 客戶端對接IoT流程:

    設備 sdk 檢測到設備狀態變更後將設備狀態及時同步到 IoT 後臺,同時後臺可以對設備進行遠程解綁、刪除等操作。

提效數據統計
新硬件開發成本降低50%

隨着設備不斷重構優化迭代,新設備接入時間成本減少了50%,團隊開發提效不少

2019年新設備接入日常排期:

2020年新設備接入日常排期:

硬件線上問題數量降低33-55%

2019年下半年與2020年上半年開發同學處理問題總數環比下降55%,技術支持同學處理問題總數環比下降33%。

總結

硬件在零售業務發展中起到非常重要的作用,每天支撐商家數以萬計的小票打印、刷卡支付、人臉採集、稱重、副屏展示等各個流程,始終爲商家門店經營保駕護航。然而開發硬件的歷程也經歷坎坷、備受挫折,需要足夠的延遲滿足感。團隊一直秉承追求卓越,守護信任的原則,一次次的優化重構,爲每次硬件的完美交互做出了最大努力,且後續還會加油持續做的更好。

未來展望

  1. 打造與完善 IoT 平臺,將硬件解決方案推廣到全公司,供其他業務方靈活接入。
  2. 提供硬件對接開放接口,供第三方接入,比方說很多商家有自己的設備,零售沒有覆蓋到,商家可以對接開放接口完成設備的接入流程。
  3. 開發硬件自檢助手,幫助商家自己解決問題,節省開發排查問題的成本。
  4. 設備同一種分類內再做粒度細分(例如可單獨選擇幾款打印機進行依賴),提高業務對接靈活性。

有贊零售移動團隊 Slogan :

打造極致好用各方面業界最 NB 的移動端產品,對於每一行代碼,我們都追求卓越、不隨意、不湊合。

目前團隊 Android 與 iOS 崗位還有空缺,歡迎優秀的人才加入我們的團隊,一起搞事情。

內推郵箱地址:[email protected]

本文轉載自公衆號有贊coder(ID:youzan_coder)。

原文鏈接

https://mp.weixin.qq.com/s?__biz=MzAxOTY5MDMxNA==&mid=2455761122&idx=1&sn=72aa93251ed252057c8c83b27e370ad8&chksm=8c6876c7bb1fffd1d12f54ce46d8608edac5fbd9fc47c526391da89b76302731104c05305a15&scene=27#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章