升级异常的固件恢复方法

1. 概述

本文指导用户通过线下手动升级的方式,恢复升级异常的固件。

2. 获取升级固件

请务必从商米售前获取对应型号摄像头的升级固件 ,否则出现升级异常概不负责。

目前商米有的IPC摄像头有两款,分别是

  1. AI识客摄像机,英文名Face Sense Camera,简称FS,目前的型号是FM020。
  2. 智能看店摄像机,英文名Store Sense Camera,简称SS,目前的型号是FM010。

3. 升级规则

  1. 不同机型之间系统固件不能互相升级,例如FS和SS之间不能升级,所以请务必根据自己的机型来获取对应的正式环境固件。
  2. 固件本身有自己的签名机制和升级校验方法,所以请务必从商米处获取固件,切勿随便从第三方获取固件。

4. 详细流程

4.1 拷贝固件到TF卡

  1. 从摄像头的TF卡槽中取出TF卡;
  2. 将TF卡插入windows电脑,执行格式化操作,文件系统设置为exfat,卷标设置为SUNMI-XXXX(其中XXXX为摄像头MAC地址最后四位,摄像头MAC地址见机身背面标贴)。 注:卷标SUNMI-XXXX中的-为英文半角符号,非中文全角符号;XXXX中的字母为大写。
  3. 进入TF卡目录,将从商米处 (详见2. 获取升级固件)获取到的升级固件解压后拷贝到根目录下,并把固件重命名为up.bin

4.2 升级固件

  1. 将带有固件的TF卡重新插入摄像头的TF卡槽中。
  2. 重新上电摄像头。
  3. 等待10秒左右,直到设备亮红灯,则说明正在固件升级 。
  4. 升级需要一些时间,再耐心等待1min左右,直到重新亮绿灯说明设备开始重启,当设备再次闪烁绿灯或者亮蓝灯说明设备已经重启完毕。

4.3 进行首配

  1. 重启后,长按设备上的Reset按键5秒以上进行恢复出厂设置。
  2. 恢复出厂重启后,使用正式环境的商米APP,按照《用户指南》手册中的软件配置指引完成摄像头的首次配置。
  3. 首配完成后,使用商米助手APP或登录正式环境的WEB服务网站https://store.sunmi.com 将设备固件更新到最新版本。
  4. 记得删除原来SD卡中的up.bin文件,避免不必要的意外升级。

操作日志

1 基本描述

SaaS合作方可以通过商米数字店铺开放平台获取到业务操作日志

2 接口规范

2.1 协议说明

对接的接口目前只开放HTTPS方式推送消息,所有的消息一律采用POST方式。

Content-Typeapplication/x-www-form-urlencoded
数据格式返回为JSON格式
字符编码UTF-8字符编码
签名算法MD5
签名规则参考2.2 签名规则

2.2 签名规则

参考《鉴权认证》文档。

2.3 公共参数

参数名必填类型说明
app_idstring唯一标识接入身份,联系商米数字店铺提供
randomstring随机字符串,由数字和字母组成,长度范围为6-10位
timestampint当前的unix timestamp,精度到秒级,10位数字
signstring签名信息,详见2.2

3 店铺设计规范

参考《商户店铺》文档。

4 操作日志接口

4.1 操作日志业务范围

操作功能操作内容操作行为
巡店抓拍配置(商户级业务)新建
修改(包含开启/关闭)
删除
巡店模板配置(商户级业务)新建
修改 (包含 开启/关闭 )
删除
巡店巡店计划配置(商户级业务) 新建
删除
  • 备注:使用商户级别业务需要先进行商户级别的商户店铺体系对接

4.2 接口列表

接口名称接口描述
/log/operation/company/getList获取操作日志列表

4.3 接口详情

4.3.1 获取商户级操作日志列表

接口描述:通过本接口调用,用户可以获取指定商户或者店铺下,指定时间范围内的操作日志列表

请求链接: /log/operation/company/getList

接口版本:v2.0

接口参数:

参数名称是否必须类型说明示例
sunmi_company_nostring商米数字店铺平台商户唯一编号 560279010307
start_timestring 查询开始时间, Unix时间戳,秒级别 1578969264
end_timestring 查询结束时间, Unix时间戳,秒级别 1579055640
page_num否 (默认1) int 当前页1
page_size 否  (默认10) int 当前页条目数量 10
  • 备注:查询时间范围为90天

请求示例:

"method": "POST",
"url": "https://store.uat.sunmi.com/openapi/operation/company/log/getList",
"headers": {
  "Content-Type": "application/x-www-form-urlencoded"
},
formData: {
  "sunmi_company_no": "560279010307",
  "app_id": "LMWWQVTW4QGCC",
  "timestamp": 1581383983,
  "random": "5dsf6698",
  "sign": "33C18A18282733A71F998BB5A5E4319D"
  "start_time": "1581333970",
  "end_time": "1581363970",
}

返回值:

{
	"data": {
		"total_count": 5,
		"operate_list": [{
			"operate_event": "patrol.plan.create",
			"operate_event_detail": "patrol.plan.create",
			"operate_source": "web",
			"operate_time": 1606707030,
			"operate_payload": {
				"params": {
					"period_type": 2,
					"plan_end_time": 1607011199,
					"plan_name": "操作日志巡店计划测试",
					"plan_start_time": 1606752000,
					"plan_task_type": 6,
					"week_day_list": []
				},
				"result": "success"
			}
		}, {
			"operate_event": "patrol.template.update",
			"operate_event_detail": "patrol.template.second_group.create",
			"operate_source": "web",
			"operate_time": 1606706053,
			"operate_payload": {
				"params": {
					"check_item_list": [{
						"full_score": 10,
						"name": "操作日志点检项测试"
					}],
					"first_group_id": 115,
					"second_group_name": "操作日志二级类测试",
					"template_id": 10
				},
				"result": "success"
			}
		}, {
			"operate_event": "patrol.template.update",
			"operate_event_detail": "patrol.template.second_group.delete",
			"operate_source": "web",
			"operate_time": 1606706031,
			"operate_payload": {
				"params": {
					"second_group_id": 120
				},
				"result": "success"
			}
		}, {
			"operate_event": "patrol.template.update",
			"operate_event_detail": "patrol.template.first_group.create",
			"operate_source": "web",
			"operate_time": 1606706024,
			"operate_payload": {
				"params": {
					"first_group_name": "操作日志大类测试",
					"template_id": 10
				},
				"result": "success"
			}
		}, {
			"operate_event": "patrol.template.update",
			"operate_event_detail": "patrol.template.second_group.create",
			"operate_source": "web",
			"operate_time": 1606706009,
			"operate_payload": {
				"params": {
					"check_item_list": [{
						"full_score": 10,
						"name": "测试1"
					}],
					"first_group_id": 32,
					"second_group_name": "操作日志测试",
					"template_id": 10
				},
				"result": "success"
			}
		}]
	},
	"code": 0,
	"msg": "succeed"
}
  • 备注,相关枚举值如有需要详见具体业务

返回参数说明:

参数说明
operate_list操作列表
operate_event操作事件
operate_event_detail操作详情
operate_source操作来源
operate_time操作时间
operate_payload操作内容
result操作结果

operate_event:

取值说明
patrol.cronjob.create巡店业务 创建定时任务
patrol.cronjob.update 巡店业务 更新定时任务(包括开启关闭)
patrol.cronjob.delete 巡店业务 删除定时任务
patrol.plan.create 巡店业务 创建巡店计划
patrol.plan.delete 巡店业务 删除巡店计划
patrol.template.create 巡店业务 创建考评模板
patrol.template.update 巡店业务 更新考评模板 (包括开启关闭)
patrol.template.delete 巡店业务 删除考评模板

operate_event_detail:

取值说明
patrol.cronjob.create创建定时任务
patrol.cronjob.update更新定时任务(包括开启关闭)
patrol.cronjob.delete删除定时任务
patrol.plan.create创建巡店计划
patrol.plan.delete删除巡店计划
patrol.template.create创建考评模板
patrol.template.update更新考评模板 (包括开启关闭)
patrol.template.first_group.create创建模板一级检查大类
patrol.template.first_group.update更新模板一级检查大类
patrol.template.first_group.delete删除模板一级检查大类
patrol.template.second_group.create创建模板二级检查类(包含点检项)
patrol.template.second_group.update更新模板二级检查类(包含点检项)
patrol.template.second_group.delete删除模板二级检查类(包含点检项)
patrol.template.delete删除考评模板

operate_source:

取值说明
app商米助手
websunmi store页面
mgt商米后端管理平台
openapiopenapi接口调用

错误码:

错误码说明
5041 商户组织参数错误
5000数据库错误

增值服务-巡店服务 接口

1 概述

为了方便Saas合作伙伴可以直接在其平台上使用商米提供的巡店服务业务数据,平台提供了巡店统计数据接口。Saas合作伙伴可以通过接口调用获取巡店业务的统计数据。

2 接口规范

2.1 协议说明

对接的openAPI接口目前只开放HTTPS方式,所有的消息一律采用POST方式。

注:消息体大小不得超过1M,超过1M的请求,直接拒绝!

Content-Typeapplication/x-www-form-urlencoded
数据格式返回为JSON格式
字符编码UTF-8字符编码
签名算法MD5
签名规则参考2.2 签名规则

2.2 签名规则

参考《鉴权认证》文档。

2.3 公共参数

参数名必填类型说明
app_idstring唯一标识接入身份,联系商米数字店铺提供
randomstring随机字符串,由数字和字母组成,长度范围为6-10位
timestampint当前的unix timestamp,精度到秒级,10位数字
signstring签名信息,详见2.2

3 巡店数据统计接口

3.1 接口描述

巡店数据统计接口用于Saas合作方获取巡店服务的各种数据统计。

3.2 接口列表

接口名称接口描述
/service/patrol/stat/groupByInspector获取巡检人巡查工作情况统计
/service/patrol/stat/groupByShop 获取门店下巡查工作数据统计
/service/patrol/stat/groupByTemplate获取模板使用情况数据统计
/service/patrol/stat/groupByCheckItem获取模板-检查项使用情况数据统计

3.3 接口详情

3.3.1 获取巡检人巡查工作数据统计

接口描述:通过本接口调用,可以获取到指定门店下基于巡检人统计的巡检人工作数据统计

接口链接:/service/patrol/stat/groupByInspector

接口版本:v2.0

接口参数:

参数名称是否必须类型说明示例
sunmi_shop_no string店铺编号560279010307
periodint统计周期1

period参数说明:

参数取值说明
1近7天数据统计
2近30天数据统计
3近90天数据统计
4近180天数据统计
5近365天数据统计

请求示例:

  "method": "POST",
  "url": "https://store.uat.sunmi.com/openapi/service/patrol/stat/groupByInspector",
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  formData: {
    "sunmi_shop_no": "560279010307",
    "app_id": "LMWWQVTW4QGCC",
    "timestamp": 1581383983,
    "random": "5dsf6698",
    "sign": "33C18A18282733A71F998BB5A5E4319D"
  }

返回值:

{
    "data": {
        "patrol_inspector_summary_list": [
            {
                "inspection_user_name": "小明",
                "plan_task_undone_count": 0,
                "plan_task_done_count": 7,
                "plan_task_doing_count": 0,
                "plan_task_total_count": 28,
                "check_total_count": 349,
                "check_done_count": 2,
                "total_time_duration": 295645,
                "task_normal_count": 7,
                "task_non_count": 1602,
                "task_total_count": 1609,
                "task_realtime_count": 699,
                "task_snapshot_count": 523,
                "task_evaluate_count": 40,
                "last_inspection_time": 1600867095
            },
            {
                "inspection_user_name": "xiaohong",
                "plan_task_undone_count": 0,
                "plan_task_done_count": 3,
                "plan_task_doing_count": 0,
                "plan_task_total_count": 12,
                "check_total_count": 9,
                "check_done_count": 4,
                "total_time_duration": 2101,
                "task_normal_count": 3,
                "task_non_count": 81,
                "task_total_count": 84,
                "task_realtime_count": 26,
                "task_snapshot_count": 42,
                "task_evaluate_count": 7,
                "last_inspection_time": 1599535469
            },
            {
                "inspection_user_name": "kunkun",
                "plan_task_undone_count": 0,
                "plan_task_done_count": 0,
                "plan_task_doing_count": 0,
                "plan_task_total_count": 0,
                "check_total_count": 0,
                "check_done_count": 0,
                "total_time_duration": 21,
                "task_normal_count": 0,
                "task_non_count": 2,
                "task_total_count": 2,
                "task_realtime_count": 0,
                "task_snapshot_count": 0,
                "task_evaluate_count": 2,
                "last_inspection_time": 1597661382
            },
            {
                "inspection_user_name": "小白",
                "plan_task_undone_count": 0,
                "plan_task_done_count": 0,
                "plan_task_doing_count": 0,
                "plan_task_total_count": 0,
                "check_total_count": 14,
                "check_done_count": 0,
                "total_time_duration": 13994,
                "task_normal_count": 0,
                "task_non_count": 48,
                "task_total_count": 48,
                "task_realtime_count": 0,
                "task_snapshot_count": 0,
                "task_evaluate_count": 48,
                "last_inspection_time": 1599542974
            }
        ]
    },
    "code": 0,
    "msg": "succeed"
}

返回参数说明:

参数名类型说明
inspection_user_namestring巡检人姓名
plan_task_undone_countint计划任务过期未执行数
plan_task_done_countint计划任务已完成数
plan_task_doing_count int计划任务待执行数
plan_task_total_count int计划任务总数
check_total_countint应审核问题总数
check_done_countint 审核完成数
total_time_durationint 巡检时长(秒)
task_normal_countint任务巡店次数
task_non_countint非任务巡店次数
task_total_countint巡店总次数
task_realtime_countint实时巡店次数
task_snapshot_countint抓拍巡店次数
task_evaluate_countint门店考评次数
last_inspection_timeint最后一次巡检时间(秒)

错误码:

错误码说明
5041 请求中未找到shop_id参数

3.3.2 获取门店下巡查工作数据统计

接口描述:通过本接口调用,可以获取到指定门店下基于门店统计的巡检工作数据统计

接口链接: /service/patrol/stat/groupByShop

接口版本:v2.0

接口参数:

参数名称是否必须类型说明示例
sunmi_shop_no string店铺编号列表560279010307
periodint统计周期1

period参数:

参数取值说明
1近7天数据统计
2近30天数据统计
3近90天数据统计
4近180天数据统计
5近365天数据统计

请求示例:

  "method": "POST",
  "url": "https://store.uat.sunmi.com/openapi/service/patrol/stat/groupByShop",
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  formData: {
    "sunmi_shop_no": "560279010307",
    "app_id": "LMWWQVTW4QGCC",
    "timestamp": 1581383983,
    "random": "5dsf6698",
    "sign": "33C18A18282733A71F998BB5A5E4319D"
  }

返回值:

{
    "data": {
        "patrol_shop_summary_list": [
            {
                "check_total_count": 0,
                "scoreless_total_count": 0,
                "avg_score": 5.58,
                "rectify_count": 438,
                "rectify_done_count": 398,
                "realtime_need_count": 393,
                "realtime_total_count": 777,
                "snapshot_problem_count": 286,
                "snapshot_total_count": 645,
                "evaluate_need_count": 45,
                "evaluate_total_count": 126
            }
        ]
    },
    "code": 0,
    "msg": "succeed"
}

返回参数说明:

参数名类型说明
check_total_countint考评检查项总数
scoreless_total_countint有问题检查项数
avg_scorefloat考评得分,百分制
rectify_countint应整改问题数
rectify_done_countint整改完成数
realtime_need_countint 实时巡店需整改数
realtime_total_countint 实时巡店总次数
snapshot_problem_countint抓拍巡检有问题数
snapshot_total_countint抓拍巡检总次数
evaluate_need_countint门店考评需整改数
evaluate_total_countint门店考评总次数

错误码:

错误码说明
5041 请求中未找到shop_id参数

3.3.3 获取模板使用情况数据统计

接口描述: 通过本接口调用,可以获取到指定门店下基于门店统计的巡检工作数据统计

接口链接: /service/patrol/stat/groupByTemplate

接口版本:v2.0

接口参数:

参数名称是否必须类型说明示例
sunmi_shop_no string店铺编号列表560279010307
periodint统计周期1

period参数说明:

参数取值说明
1近7天数据统计
2近30天数据统计
3近90天数据统计
4近180天数据统计
5近365天数据统计

请求示例:

  "method": "POST",
  "url": "https://store.uat.sunmi.com/openapi/service/patrol/stat/groupByTemplate",
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  formData: {
    "sunmi_shop_no": "560279010307",
    "app_id": "LMWWQVTW4QGCC",
    "timestamp": 1581383983,
    "random": "5dsf6698",
    "sign": "33C18A18282733A71F998BB5A5E4319D"
  }

返回值:

{
    "data": {
        "patrol_template_summary_list": [
            {
                "template_id": "549755813846",
                "template_name": "你好世界",
                "total_score": 3925,
                "total_full_score": 5240,
                "total_count": 126
            }
        ]
    },
    "code": 0,
    "msg": "succeed"
}
参数名类型说明
template_idint模板id
template_namestring模板名称
total_scoreint门店下模板累计得分
total_full_scoreint 门店下模板累计总分
total_countint总共使用次数

错误码:

错误码说明
5041 请求中未找到shop_id参数

3.3.4 获取模板-检查项使用情况数据统计

接口描述: 通过本接口调用,可以获取到指定门店下基于模板统计的巡检工作数据统计

接口链接: /service/patrol/stat/groupByCheckItem

接口版本:v2.0

接口参数:

参数名称是否必须类型说明示例
sunmi_shop_no string店铺编号列表560279010307
periodint统计周期1

period参数说明:

参数取值说明
1近7天数据统计
2近30天数据统计
3近90天数据统计
4近180天数据统计
5近365天数据统计

请求示例:

  "method": "POST",
  "url": "https://store.uat.sunmi.com/openapi/service/patrol/stat/groupByCheckItem",
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  formData: {
    "sunmi_shop_no": "560279010307",
    "app_id": "LMWWQVTW4QGCC",
    "timestamp": 1581383983,
    "random": "5dsf6698",
    "sign": "33C18A18282733A71F998BB5A5E4319D"
  }

返回值:

{
    "data": {
        "patrol_check_item_summary_list": [
            {
                "template_id": "549755813846",
                "check_item_name": "1",
                "check_score_count": 9,
                "check_total_count": 12
            },
            {
                "template_id": "549755813846",
                "check_item_name": "2dada方式方法将老豆腐江东父老的肌肤豆腐煎豆腐",
                "check_score_count": 1,
                "check_total_count": 1
            },
            {
                "template_id": "549755813846",
                "check_item_name": "调料归类正确",
                "check_score_count": 3,
                "check_total_count": 5
            },
            {
                "template_id": "549755813846",
                "check_item_name": "食品归类正确",
                "check_score_count": 4,
                "check_total_count": 5
            },
            {
                "template_id": "549755813846",
                "check_item_name": "食品细菌含量未超标",
                "check_score_count": 3,
                "check_total_count": 5
            },
            {
                "template_id": "549755813846",
                "check_item_name": "食材未过期",
                "check_score_count": 5,
                "check_total_count": 5
            },
            {
                "template_id": "549755813846",
                "check_item_name": "食材清洗干净",
                "check_score_count": 4,
                "check_total_count": 5
            }
        ]
    },
    "code": 0,
    "msg": "succeed"
}

返回参数说明:

参数名类型说明
template_idint模板id
check_item_namestring检查项名称
check_score_countint检查得分次数
check_total_countint 检查次数

错误码:

错误码说明
5041 请求中未找到shop_id参数

云打印机CP事件及消息示例

1 事件列表

event取值说明
6001未经处理的文字小票上传消息
6002未经处理的图片小票上传消息
6003经过处理的小票消息

2 事件消息示例

2.1 未经处理的文字小票上传消息(event=6001)

{
  "id": 123,     // 系统生成的小票ID
  "sn": "sn",    // 打印设备SN
  "text": "abc"  // 文字小票内容
}

2.2 未经处理的图片小票上传消息 (event=6002)

{
  "id": 123,  // 系统生成的小票ID
  "sn": "sn", // 打印设备SN
  "base64_image": "avc"  // 图片小票的base64 encoded string
}

2.3 经过处理的小票消息 (event=6003)

{
  "id": 123,  // 系统生成的小票ID
  "sn": "sn",  // 打印设备SN
  "order_id": 456, // 小票上的订单ID
  "order_time": "2020-01-01", // 小票生成时间
  "total_payment": 10.11  // 小票金额
  "sales_detail_list": [{   //明细列表
    "amount": 10,          // 商品单价
    "item":   "商品",      // 商品名字
    "quantity": 1          // 商品数量
  },
  ...
  ]
}

IPC设备迁移

本章节详细介绍在Android平台如何快速方便添加IPC SDK

1、拓扑图

IPC设备和Android设备连接拓扑图如下:

1、Android设备有一套自有的用户管理模块,有唯一ID识别机制。Android设备在管理用户时,需要保留一张用户人脸图片

2、IPC设备使用人脸图片作为识别预输入。

3、Android设备激活IPC设备后,通过局域网把用户人脸图片以及与用户唯一关联的ID传递给IPC设备

4、IPC设备从人脸图片提取人脸特征值,并把特征值与用户ID保存到数据库。

5、IPC设备监控到用户进入,查询注册的数据库,返回用户唯一ID的IPC设备。

6、Android设备获取到唯一ID值后,在用户管理模块根据该唯一ID进行查询,显示用户信息

2、IPC激活

IPC设备使用时,如果未激活,需要先激活IPC设备。激活步骤如下:

2.1 SDK初始化

APP_ID:激活与API调用校验使用的账号。

SECRET_KEY:API调用所需的签名密钥。

LICENSE:激活API所需的激活码。

IPCameraManager mIPCameraManager = IPCameraManager.getInstance(context);
mIPCameraManager.init(APP_ID, SECRET_KEY, LICENSE);

2.2 配网

IPC设备激活时需要连接到互联网。IPC连接互联网时,可以通过无线(wifi)或有线(以太网口)连接到路由器。无线连接时需要做配网设置。

有线接入:

有线接入只需要通过网线把IPC设备和集成SDK的Android设备接入到同一局域网即可,不需要其它设置。

无线接入:

无线配网方式相对要复杂点,步骤如下:

  1. 使用手机/PC的无线网卡扫描IPC的AP热点,一般AP热点的名称为SUNMI_XXXX,其中XXXX为MAC地址最后2个字节的16进制数字,MAC地址可以通过设备机身后背的标贴或者包装盒的标贴查到,AP热点本身是无加密的。
  2. 使用手机/PC的无线网卡连接IPC的AP热点,此时手机/PC就会获取到IPC分配的IP地址(按照设备发现描述的方法即可获取到),一般会是192.168.200.XXX,手机/PC的网关地址就是IPC的地址,一般会是192.168.200.1。
  3. 调用无线配置 API(见获取无线扫描AP列表 (无需签名校验)的描述)获取IPC扫描到的AP热点。
  4. 调用无线配置 API(见设置无线参数(无需签名校验)的描述)设置IPC要连接的无线网络(例如无线路由器的SSID和密码),使得IPC能够从网关处获取到IP地址。
  5. 如果网络是可以正常上网的话,IPC取到IP地址后很快就会亮蓝灯,此时表明IPC可以正常连接Internet了。
// 获取IPC扫描的AP热点
private void getWifiList() {
    BasicConfig.getInstance(context).getApListWithoutAuth(sunmiDevice.getDeviceid(),
            new RPCCallback‹RPCResponse‹IpcApBean››() {
                @Override
                public void onComplete(RPCResponse‹IpcApBean› result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        wifiListGetSuccess(result.data());
                    } else {
                        Log.i(TAG, "getApListWithoutAuth failed, errcode: " + result.code());
                    }
                }
        });

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            if (wifiList.size() == 0) {
                setNoWifiVisible(View.VISIBLE);
            }
        }
    }, TIMEOUT_GET_WIFI);
}
// 设置IPC连接的AP热点
private void setIpcWifi(String ssid, String psw) {
    showLoadingDialog();
    BasicConfig.getInstance(context).setWifiConfWithoutAuth(sunmiDevice.getDeviceid(),
            ssid, psw, new RPCCallback‹RPCResponse›() {
                @Override
                public void onComplete(RPCResponse result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        hideLoadingDialog();
                        shortTip("配置成功,请等待设备联网,联网后指示灯会变成蓝色");
                        createWaitDialog();
                    } else {
                        hideLoadingDialog();
                        shortTip("配置失败");
                    }
                }

                @Override
                public void onError(Throwable t) {
                    hideLoadingDialog();
                    shortTip("配置失败");
                }
            });
}

2.3 激活

通过 2.2配网 后,IPC通过无线连接到路由或者直接有线连接路由。

由于使用IPC的其它API时需要签名校验,所以在使用API之前需要激活IPC。激活只需要在第一次使用IPC时进行,后续都不再需要激活。

调用active接口既可以激活IPC设备。 代码如下:

//激活ipc设备,并设置回调接口
DeviceManage.getInstance(context).activate(ipcList.get(postion).getDeviceid(),
    new RPCCallback‹RPCResponse›() {
        //IPC返回相关错误码。
        @Override
        public void onComplete(RPCResponse result) {
            if (result.code() == RPCErrorCode.SUCCESS || result.code() == RPCErrorCode.DEVICE_ACTIVATED) {
                Log.i(TAG, "activate ipc success");
            } else {
                Log.i(TAG, "activate ipc failed");
            }
        }
        //网络问题导致无法通信,会调用onError接口
        @Override
        public void onError(Throwable t) {
            Log.i(TAG, "activate ipc failed");
        }
    });

2.4 画面调整

由于IPC的人脸识别对于人脸图像质量有一定要求,因此在使用IPC前需要调整IPC的画面,以达到最好的体验效果。画面调整包括镜头的调焦、对焦。步骤如下:

2.4.1 RTSP 播放

首先需要通过预览画面查看IPC设备镜头是对焦,画面是否清晰。IPC设备是通过RTSP协议传递视频流,所以开发一款RTSP播放器才能看到视频流。

调用VideoStream.getInstance(context).getLiveStream接口可以获取到RTSP播放流的地址。代码流程如下:

    private void openMediaPlayer() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //获取播放流
                VideoStream.getInstance(context).getLiveStream(mDevice.getDeviceid(),
                        new RPCCallback‹RPCResponse‹RPCResponse.LiveAddressBean››() {
                            @Override
                            public void onComplete(RPCResponse‹RPCResponse.LiveAddressBean› result) {
                                if (result.code() == RPCErrorCode.SUCCESS) {
                                    Log.i(TAG, "live url: " + result.data().fhd_live_url);
                                    if (mPlayer == null) {
                                       //自定义的rtsp播放器
                                        mPlayer = new SunmiPlayer(context);
                                        mPlayer.setListener(new SunmiPlayerListener() {
                                            @Override
                                            public void onPrepared(IMediaPlayer iMediaPlayer) {
                                                iMediaPlayer.start();
                                            }
                                        });
                                        mPlayer.setSurface(mVideoView.getHolder().getSurface());
                                        //获取到ipc返回的播放流rtsp地址
                                        String liveUrl = result.data().fhd_live_url.replaceFirst("^rtsp://", "rtsp://admin:admin@");
                                        //自定义播放器开始播放rtsp视频流
                                        mPlayer.setUp(liveUrl);
                                    }
                                }
                            }
                });
            }
        }).start();
    }

2.4.2 调焦、对焦

private BasicConfig mBasicConfig;

public void init() {
    ...
    mBasicConfig = BasicConfig.getInstance(context);
    ...
}

// 调焦
private void func1(){
    ...
    mBasicConfig.setZoom(mDevice.getDeviceid(), zfBean.zoom, new RPCCallback‹RPCResponse›() {
        @Override
        public void onComplete(RPCResponse result) {
            Log.i(TAG, "setZoom, code:" + result.code());
        }
    });
    ...
}

// 自动对焦
private void func2() {
    ...
    mBasicConfig.autoFocus(mDevice.getDeviceid(), xRelative, yRelative, new RPCCallback‹RPCResponse›() {
        @Override
        public void  onComplete(RPCResponse result) {
            Log.d(TAG, "autoFocus, code:" + result.code());
            if (result.code() == RPCErrorCode.SUCCESS) {
                mBasicConfig.getZoomFocusConf(mDevice.getDeviceid(), new RPCCallback‹RPCResponse‹RPCResponse.ZoomFocusBean››() {
                    @Override
                    public void onComplete(RPCResponse‹RPCResponse.ZoomFocusBean› result) {
                        if (result.code() == RPCErrorCode.SUCCESS) {
                            zfBean = result.data();
                            mSbZoom.setProgress(zfBean.zoom);
                        }
                    }
                });
            }
        }
    });
    ...
}

// 自动对焦如果不够清晰,可以手动对焦进行微调
private void func3() {
    ...
    mBasicConfig.manualFocus(mDevice.getDeviceid(), focus, new RPCCallback‹RPCResponse›() {
        @Override
        public void onComplete(RPCResponse result) {
            if (result.code() == RPCErrorCode.SUCCESS) {
                zfBean.focus = focus;
            }
        }
    });
    ....
}

2.4.3 设置门线

设置门线主要是更精确地判断人流的方向(进门、出门、路过),提高人流统计的准确度。

调用接口PeopleFlowStats.getInstance(context).setDoorLine设置门线。代码如下:

private void func() {
    ...
    PeopleFlowStats.getInstance(context).setDoorLine(mDevice.getDeviceid(), 0, lineStart[0],
            lineStart[1], lineEnd[0], lineEnd[1], new RPCCallback‹RPCResponse›() {
                @Override
                public void onComplete(RPCResponse result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        stopPlay();
                        finish();
                        startActivity(new Intent(context, MainActivity.class));
                    }
                }
            });
    ...
}

3、注册人脸

IPC激活好以后,向设备注册人脸信息。

Android设备端注册人脸时,需要同步向IPC设备注册人脸需要传递用户人脸照片、以及唯一标识用户的ID。

通过接口IPCameraManager.getInstance(context).addFaceRecord向IPC注册人脸。代码如下:

/*
 *传递人脸照片路径: picPath,
 * 用户唯一ID: uid
 */
IPCameraManager.getInstance(context).addFaceRecord(picPath, uid, new RPCCallback‹RPCResponse›() {
                        //IPC设备调用成功后会调用onComplete
                        @Override
                        public void onComplete(RPCResponse result) {
                            Log.i(TAG, "addFaceRecord, code:" + result.code());
                            if (result.code() != 0){
                                showToast(context, "添加人脸失败 code: " + result.code());
                            } else {
                                showToast(context, "添加人脸成功");
                            }
                        }
                        //IPC设备离线时,调用会调用onError
                        @Override
                        public void onError(Throwable t) {
                            super.onError(t);
                            Log.i(TAG, "addFaceRecord, Exception:" + t.getMessage());
                            showToaste(context, "添加人脸失败 ");
                        }
                    });

4、删除人脸

Android设备端删除用户时,需要同步删除IPC设备的人脸信息。Andorid端设备需要传递用户唯一ID。

调用接口 IPCameraManager.getInstance(context).addFaceRecord 向IPC删除人脸。代码如下:

//传递用户唯一ID:userId
IPCameraManager.getInstance(context).deleteFaceRecord(userId,new RPCCallback‹RPCResponse‹RPCResponse.FaceDeleteSubResult››() {
                @Override
                public void onComplete(RPCResponse‹RPCResponse.FaceDeleteSubResult› result) {
                    Log.i(TAG, "deleteFaceRecord, code:" + result.code());
                    if (result.code() != 0){
                        showToast(context, "删除人脸失败 code: " + result.code());
                    } else {
                        showToast(context, "删除人脸成功");
                    }
                }

                @Override
                public void onError(Throwable t) {
                    super.onError(t);
                    Log.i(TAG, "addFaceRecord, Exception:" + t.getMessage());
                }

            });

5、 监听人脸识别消息

Android设备注册好人脸信息后,即可以监听IPC设备人脸识别信息。通过IPC返回的ID信息,查询数据库,显示用户详细信息。

调用IPCameraManager.getInstance(context).setFaceDetectListener即可监听人脸信息,代码如下:

IPCameraManager.getInstance(context).setFaceDetectListener(new FaceDetectListener() {
    @Override
    public void onFaceDetect(String userId) {
        String userName = null;
        if (userName == null) {
            showToast(getApplicationContext(), "未注册的用户进店");
        } else {
            showToast(getApplicationContext(), "用户[ " + userName + " ]进店");
        }
    }
});

设备端OpenAPI Postman测试示例

1.准备工作

本文介绍如何使用Postman在局域网内完成商米IPC设备的首配操作,并进行调试。首先请安装说明书接上电源并通过网线连上网络,确保IPC设备亮蓝灯。

进行以下操作前,还需要获取以下信息,其中除了IPC设备IP和设备SN外,其它信息请联系商米售前技术团队获取:

项目本文示例
app_id08BABCDABBB661234567
secret_keyABCDEFG3F2BB03917123
激活码abcd2986jnts8987hntl1234
OpenAPI证书及其密码openapi.p12
Postman测试集Sunmi_IPC_OpenAPI_Postman_Collection_202007001.json
设备IP192.168.103.198
设备SNC201000P00123

2.Postman安装

  1. 通过Postman官网下载最新版本软件:链接,本文使用的版本为v7.27.1。
  2. 按照安装向导进行安装

3.导入OpenAPI测试集合及配置

  1. 启动Postman
  2. 点击File -> Import -> Upload Files, 打开商米提供的Postman测试集文件,即”Sunmi_IPC_OpenAPI_Postman_Collection_202007001.json”,确认后点击”Import”。
  3. 点击File -> Settings -> Certificate,打开证书管理页面,点击”Add Certificate”,在Host项目填入设备IP如192.168.103.198,在下方打开商米提供的证书并填写对应的密码,确认无误后点击”Add”。
  • 4. 打开General页面,将”SSL certificate verificatio”选项置为`OFF`,并关闭页面。
  • 5. 返回Postman主页面,在侧边栏的Sunmi IPC OpenAPI测试集点击右键,选择”Edit”, 打开”Variables”页面,在”CURRENT VALUE”栏更新对应信息。

4.激活示例

  1. 激活步骤请参考商米开发者平台智能摄像机IPC的设备管理部分
  2. 激活操作采用激活码代替secret_key,请参照第3步修改本测试集的”Variables”页面的secret_key参数为商米提供的激活码
  3. 从侧边栏打开Sunmi IPC OpenAPI -> 基本配置 -> 0. 激活设备,在窗口右侧”Body”中,修改参数”sn”的值为测试设备SN,点击”Send”,成功激活返回”code”为”0″,如下图所示

5.OpenAPI示例测试

  1. 本测试集包含数个IPC设备基本功能的示例,方便前期部署的接口验证,更多接口请查阅商米开发者平台智能摄像机IPC相关页面。每个OpenAPI请求需要加上签名,只有正确签名的合法请求才会被IPC设备响应,具体签名规则请参考签名规则
  2. 本测试集已包含HTTPS请求签名,具体实现请见测试集属性的”Pre-request Scripts”部分,签名所需的请求参数在每个请求”Body”部分的”app_id”、”timestamp”、”random”、”sign”,请勿更改这两部分内容
  3. 正常成功请求如下图所示,错误码定义请参考错误码

OpenAPI开发指南

  1. 准备工作
  2. OpenAPI简介
  3. Android SDK简介
  4. Android SDK集成指南
  5. 开发联调
  6. 正式上线

1.准备工作

1.1 申请API对接所需的材料

步骤一:联系商米售前技术团队,申请对接API所需要的账号信息,需要向售前技术支持团队提供如下软件开发商信息

客户提供的信息说明
客户名称 软件商公司名称
对接摄像头型号 对接API的型号,如SS(Store Sense)、FS(Face Sense)
客户设备的SN 客户对接联调的所有设备的SN,需要商米云把这些SN加入到UAT环境
客户需求场景 描述大体的需求和使用场景

步骤二:售前技术团队根据客户的需求信息,返回软件商对接API所需的账号信息,包括

返回给客户的信息说明
SaaS代号 商米内部管理SaaS软件商信息的代号
app_id 激活设备端API所需的账号
secret_key 调用API所需的签名秘钥
设备激活码 激活设备端API所需的激活码,用于签名校对软件商是否有权调用
可激活的设备数量上限 UAT环境下的可激活设备的数量
openapi.bks 双向验证中客户端自己的证书以及私钥。
sunmica.bks 双向验证中用于校验服务端发送过来的的证书是否合法。
SDK开发包 用于对接的SDK,目前只支持安卓版本
UAT环境商米助手APP 用于在UAT环境下给设备做首配

1.2 设备刷机UAT环境固件

一般,软件商客户在开发对接API阶段,建议使用调试环境,即商米的UAT环境,避免对正式生产环境造成困扰或者数据污染。

因此,需要将摄像头的固件升级到开发测试环境(UAT环境),具体操作流程参考 升级开发环境固件

另外,刷机到UAT环境后,可以使用UAT环境商米助手进行首次配置,使摄像机工作起来,后续集成了Android SDK后可以在开发者自己的应用上对摄像头进行配置,而无需商米助手。

2.OpenAPI简介

摄像头API提供强大的功能,几乎包含了摄像头本身具有的所有功能,用户可以通过这些API开发自己的功能并集成到自己的应用上。

API提供两种使用方式,用户可以选择其中一种方式来完成自己的开发,简单介绍如下:

方式一:直接根据API接口实现原理和接口细节(详见API调用方式),自己开发客户端代码调用API。

方式二:推荐的方式,根据商米提供的平台SDK(当前只支持安卓SDK,详见设备端Android SDK),直接调用JAVA API来完成对摄像头API的调用,不需要过多关注API的接口细节,方便快捷,节省开发时间。

后续的集成指南介绍都以安卓SDK来展开。

3. Android SDK简介

3.1 相关文档

序号内容项获取方式说明
1SDK技术文档Android SDK技术文档包含了SDK的API说明与示例代码。
2SDK开发包由技术支持提供给SaaS客户。见章节3.2。
3SDK LicenseSaaS客户提供自身的客户信息进行申请,技术支持邮件发送给SaaS客户。License目前包含:app_id、secret_key、激活码

3.2 SDK开发包介绍

  • SDK开发包组成
序号文件/文件名说明
1IpcDemoDemo工程源码,可直接编译出Demo APP
2ipcsdk.aarOpenAPI库
3demo.apk编译好的Demo APP
  • Demo工程源码结构

4. Android SDK集成指南

详见设备端Android SDK集成指南

5. 开发联调

激活设备API后,可以正式调用摄像头所有开发的API完成自己的业务开发和调试,测试通过后即可准备上线。

6. 正式上线

由于开发联调是在UAT环境完成的,因此,客户确认业务测试没有问题后,给客户正式使用的时候需要上线到正式环境,即商米的 Release环境。

可以给设备升级到正式环境,见升级线上环境固件的操作指南。在正式环境下回归测试OK后,即可正式发布。

商米AI识客SDK开发指南

1.准备工作

1.1 申请API对接所需的材料

步骤一:联系商米售前技术团队,申请对接API所需要的账号信息,需要向售前技术支持团队提供如下软件开发商信息

客户提供的信息说明
客户名称软件商公司名称
对接摄像头型号对接API的型号,如SS(Store Sense)、FS(Face Sense)
收银设备硬件指纹获取到SDK后,通过SDK获取硬件指纹信息,用于申请Licence

步骤二:售前技术团队根据客户的需求信息,返回软件商对接API所需的账号信息,包括:

返回给客户的信息说明
SaaS代号商米内部管理SaaS软件商信息的代号
设备激活码激活设备端所需的激活码,用于签名校对软件商是否有权调用
SDK开发包用于人脸识别以及AI识客设备开发的SDK,目前只支持安卓版本

SDK开发包:

序号文件/文件名说明
1FaceDemoDemo工程源码
2face-release.aar商米人脸识别SDK
3libipcsdk-release.aarIPC联动版sdk
4asset 商米 人脸识别资源文件

2、申请Licence

登陆开发者网站,提供 以下信息申请Licence

提供的信息说明
收银设备硬件指纹 通过SDK API获取设备硬件信息
人脸库人脸库的限制大小

商米开发者网站会生成一个Licence文件。SaaS开发者获取到Licence后即使用SDK。

3、添加SDK到工程

3.1 导入SDK

右键点击app,选择New->Module->Import .JAR/.AAR Package

点击Finish即导入aar模块

3.2 权限申明

在AndroidManifest.xml文件里添加如下权限:

	‹uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/›
	‹uses-permission android:name="android.permission.INTERNET" /›
	‹uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /›
	‹uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /›
	‹uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /›
	‹uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /›
	‹uses-permission android:name="android.permission.CAMERA" /›

3.3 导入asset资源

导入提供的assets资源

说明:资源文件需要APP启动的时候复制到APP的运行目录,以方便SDK后续初始化。

3.4 导入licence文件

获取到licence_valid.txt文件,可以放到assets资源目录,也可以放到sdcard目录。

1)放到assets资源目录,则需要把licence_valid.txt文件从资源文件复制到app的工作目录。

2)如果放到sdcard目录,则需要app申请sdcard读取权限。

无论哪一种情况,最后使用的时候,需要传递licence_valid.txt文件的路径,以激活人脸识别SDK。详细见SDK初始化代码

4、DEMO说明

4.1 工程目录结构

4.1.1 app Module

app Module是DEMO的入口。其中包含了各个模块的入口函数调用。

4.1.2 commonlib

commonlib Module包含了整个DEMO通用的接口。其中包含摄像头管理,工具类。如下图:

4.1.2.1 FaceCameraManager

FaceCameraManager统一管理设备自带摄像头和USB摄像头。设备自带摄像头的流程是使用Android系统提供的摄像头通用方法。USB摄像头则使用UVC库提供的操作摄像头的方法。FaceCameraManager对上层应用提供统一的方法: startPreview和 stopPreview。

4.1.2.2 NormalCameraManager

NormalCameraManager使用Android系统提供的摄像头方法,实现startPreview和stopPreview。并在内部实现回调接口用于返回每一帧摄像头获得的数据。

4.1.2.3 UVCCameraManager

UVCCameraManager使用第三方库UVClib提供的方法用于操作USB摄像头,实现startPreview和stopPreview。 并在内部实现回调接口用于返回每一帧摄像头获得的数据 。

4.1.2.4 ByteUtils

ByteUtils提供Byte操作的一些通用方法。

4.1.2.5 DBHelper

DBHelper提供创建数据的方法。

4.1.2.6 ImageUtils

ImageUtils提供图片操作的一些方法。

4.1.3 libuvccamera

libuvccamera Module采用第三方库开源库libuvccamera

4.1.4 facedemo

facedemo Module是DEMO实现功能的主要模块。其包含:会员注册、会员管理、会员查询、IPC配置等功能。

5、SDK初始化

详细代码见 商米AI识客SDK示例代码-> SDK初始化

6、获取摄像头数据

详细代码见 商米AI识客SDK示例代码->获取摄像头信息

7、获取摄像头预览数据

详细代码见 商米AI识客SDK示例代码->获取摄像头预览信息

8、会员注册

详细代码见 商米AI识客SDK示例代码-> 会员注册

9、会员实时查询

会员实时查询,需要使用到,实时获取摄像头预览数据以及人脸特征提取两个功能

详细代码见 商米AI识客SDK示例代码->获取摄像头预览信息

详细代码见 商米AI识客SDK示例代码->人脸特征提取

10、配置摄像机

见IPC配置页面

示例参考

  1. SDK初始化
  2. 设备发现
  3. 首次配网并激活
  4. 画面调整
  5. 设置门线
  6. 监听人脸识别消息

本文描述的示例代码都可在Demo工程源码中找到,故详细代码可阅读SDK开发包介绍中提到的Demo工程源码。

1. SDK初始化

APP_ID:激活与API调用校验使用的账号。

SECRET_KEY:API调用所需的签名密钥。

LICENSE:激活API所需的激活码。

IPCameraManager mIPCameraManager = IPCameraManager.getInstance(context);
mIPCameraManager.init(APP_ID, SECRET_KEY, LICENSE);

2.设备发现

当集成安卓SDK的机器和摄像头设备在同一个局域网内的时候,可以通过如下示例方法扫描到所有的摄像头设备。

设备发现listener返回的IPCameraInfo包含了IPC的SN、IP、MAC等基本信息。

mIPCameraManager.registerListener(new IPCameraListener() {
    @Override
    public void onDeviceOnline(IPCameraInfo device) {
        showToast(getApplicationContext(), "[ " + device.getDeviceid() + " ]上线");
    }
    @Override
    public void onDeviceOffline(IPCameraInfo device) {
        showToast(getApplicationContext(), "[ " + device.getDeviceid() + " ]离线");
    }
});

3.首次配网并激活

调用IPC前需要先获取到IPC的IP地址,故第一步是把IPC接入网络,IPC连接网络的方式有有线连接和无线连接两种方式。

由于有线网络相对不容易受到环境干扰,稳定性和可靠性较高,故首选是有线方式接入网络。

有线接入:
有线接入只需要通过网线把IPC设备和集成SDK的Android设备接入到同一局域网即可,不需要其它设置。

无线接入:

无线配网方式相对要复杂点,步骤如下:

  1. 使用手机/PC的无线网卡扫描IPC的AP热点,一般AP热点的名称为SUNMI_XXXX,其中XXXX为MAC地址最后2个字节的16进制数字,MAC地址可以通过设备机身后背的标贴或者包装盒的标贴查到,AP热点本身是无加密的。
  2. 使用手机/PC的无线网卡连接IPC的AP热点,此时手机/PC就会获取到IPC分配的IP地址(按照设备发现描述的方法即可获取到),一般会是192.168.200.XXX,手机/PC的网关地址就是IPC的地址,一般会是192.168.200.1。
  3. 调用无线配置 API(见获取无线扫描AP列表 (无需签名校验)的描述)获取IPC扫描到的AP热点。
  4. 调用无线配置 API(见设置无线参数(无需签名校验)的描述)设置IPC要连接的无线网络(例如无线路由器的SSID和密码),使得IPC能够从网关处获取到IP地址。
  5. 如果网络是可以正常上网的话,IPC取到IP地址后很快就会亮蓝灯,此时表明IPC可以正常连接Internet了。
// 获取IPC扫描的AP热点
private void getWifiList() {
    BasicConfig.getInstance(context).getApListWithoutAuth(sunmiDevice.getDeviceid(),
            new RPCCallback‹RPCResponse‹IpcApBean››() {
                @Override
                public void onComplete(RPCResponse‹IpcApBean› result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        wifiListGetSuccess(result.data());
                    } else {
                        Log.i(TAG, "getApListWithoutAuth failed, errcode: " + result.code());
                    }
                }
        });

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            if (wifiList.size() == 0) {
                setNoWifiVisible(View.VISIBLE);
            }
        }
    }, TIMEOUT_GET_WIFI);
}
// 设置IPC连接的AP热点
private void setIpcWifi(String ssid, String psw) {
    showLoadingDialog();
    BasicConfig.getInstance(context).setWifiConfWithoutAuth(sunmiDevice.getDeviceid(),
            ssid, psw, new RPCCallback‹RPCResponse›() {
                @Override
                public void onComplete(RPCResponse result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        hideLoadingDialog();
                        shortTip("配置成功,请等待设备联网,联网后指示灯会变成蓝色");
                        createWaitDialog();
                    } else {
                        hideLoadingDialog();
                        shortTip("配置失败");
                    }
                }

                @Override
                public void onError(Throwable t) {
                    hideLoadingDialog();
                    shortTip("配置失败");
                }
            });
}

激活IPC

配网成功后,使用IPC的其它API时需要签名校验,所以在使用API之前需要激活IPC。激活只需要在第一次使用IPC时进行,后续都不再需要激活。

DeviceManage.getInstance(context).activate(ipcList.get(postion).getDeviceid(),
    new RPCCallback‹RPCResponse›() {
        @Override
        public void onComplete(RPCResponse result) {
            if (result.code() == RPCErrorCode.SUCCESS || result.code() == RPCErrorCode.DEVICE_ACTIVATED) {
                Log.i(TAG, "activate ipc success");
            } else {
                Log.i(TAG, "activate ipc failed");
            }
        }

        @Override
        public void onError(Throwable t) {
            Log.i(TAG, "activate ipc failed");
        }
    });

4. 画面调整

由于IPC的人脸识别对于人脸图像质量有一定要求,因此在使用IPC前需要调整IPC的画面,以达到最好的体验效果。画面调整包括镜头的调焦、对焦。

private BasicConfig mBasicConfig;

public void init() {
    ...
    mBasicConfig = BasicConfig.getInstance(context);
    ...
}

// 调焦
private void func1(){
    ...
    mBasicConfig.setZoom(mDevice.getDeviceid(), zfBean.zoom, new RPCCallback‹RPCResponse›() {
        @Override
        public void onComplete(RPCResponse result) {
            Log.i(TAG, "setZoom, code:" + result.code());
        }
    });
    ...
}

// 自动对焦
private void func2() {
    ...
    mBasicConfig.autoFocus(mDevice.getDeviceid(), xRelative, yRelative, new RPCCallback‹RPCResponse›() {
        @Override
        public void  onComplete(RPCResponse result) {
            Log.d(TAG, "autoFocus, code:" + result.code());
            if (result.code() == RPCErrorCode.SUCCESS) {
                mBasicConfig.getZoomFocusConf(mDevice.getDeviceid(), new RPCCallback‹RPCResponse‹RPCResponse.ZoomFocusBean››() {
                    @Override
                    public void onComplete(RPCResponse‹RPCResponse.ZoomFocusBean› result) {
                        if (result.code() == RPCErrorCode.SUCCESS) {
                            zfBean = result.data();
                            mSbZoom.setProgress(zfBean.zoom);
                        }
                    }
                });
            }
        }
    });
    ...
}

// 自动对焦如果不够清晰,可以手动对焦进行微调
private void func3() {
    ...
    mBasicConfig.manualFocus(mDevice.getDeviceid(), focus, new RPCCallback‹RPCResponse›() {
        @Override
        public void onComplete(RPCResponse result) {
            if (result.code() == RPCErrorCode.SUCCESS) {
                zfBean.focus = focus;
            }
        }
    });
    ....
}

5. 设置门线

设置门线主要是更精确地判断人流的方向(进门、出门、路过),提高人流统计的准确度。

private void func() {
    ...
    PeopleFlowStats.getInstance(context).setDoorLine(mDevice.getDeviceid(), 0, lineStart[0],
            lineStart[1], lineEnd[0], lineEnd[1], new RPCCallback‹RPCResponse›() {
                @Override
                public void onComplete(RPCResponse result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        stopPlay();
                        finish();
                        startActivity(new Intent(context, MainActivity.class));
                    }
                }
            });
    ...
}

6. 监听人脸识别消息

mIPCameraManager.setFaceDetectListener(new FaceDetectListener() {
    @Override
    public void onFaceDetect(String userId) {
        String userName = null;
        if (userName == null) {
            showToast(getApplicationContext(), "未注册的用户进店");
        } else {
            showToast(getApplicationContext(), "用户[ " + userName + " ]进店");
        }
    }
});

商米AI识客SDK代码示例

  1. SDK 初始化
  2. 获取摄像头预览信息
  3. 获取摄像头拍照数据
  4. 获取特征值
  5. 人脸特征值比较
  6. 人脸会员库添加
  7. 删除特征记录
  8. 会员注册
  9. IPC 回调接口

1. SDK初始化

public boolean initFaceSdkInstance(Context context, String licencePath) {
         //初始化配置文件
        int ret = SunmiFaceSDK.init(confPath);
        if (licencePath == null) {
            Log.d(TAG, "Please input licence");
            return false;
        }
        File f = new File(licencePath);
        if (!f.exists()) {
            Log.d(TAG, "licence is not exist");
            return false;
        }
        String licence = readToString(licencePath);
        //验证Licence
        ret = SunmiFaceSDK.verifyLicence(context, licence);
        if (ret != 0) {
            Log.d(TAG, "Licence is not OK ErrorCode " + ret);
            return false;
        }
        //设置配置参数
        SunmiFaceConfigParam param = new SunmiFaceConfigParam();
        SunmiFaceSDK.getConfig(param);
        param.setDistance_threshold_(1.3f);
        param.setYaw_threshold_(50.0f);
        param.setPitch_threshold_(50.0f);
        param.setRoll_threshold_(50.0f);
        param.setMin_face_size(60);
        param.setImage_quality_threshold_(10);
        param.setMin_luminance_(10);
        param.setMax_luminance_(180);
        param.setLiveness_on_(false);
        param.setThread_num_(1);
        //设置配置参数
        ret = SunmiFaceSDK.setConfig(param);

        if (ret != 0) {
            Log.d(TAG, "param is not init");
            return false;
        }
        return true;
    }

public void init(Context context) {
      //设置licence_valid.txt为sd卡路径,也可以换成app运行工作目录。
      String licencePath = Environment.getExternalStorageDirectory() + File.separator + "licence_valid.txt";
      initFaceSdkInstance(context, licence_path);
     // 调用IPCameraManager静态类getInstance初始化IPCManger对象
     mIPCManager = IPCameraManager.getInstance(context);
}

2. 获取摄像头预览信息

//摄像头数据回调接口	
CameraDataCallback cameraDataCallback = new CameraDataCallback() {
    @Override
    public void onImageDataArrival(Bitmap bitmap, int width, int height) {
        if (mBackgroundHandler != null) {
            if (mClickAction == 1 && !faceDetectState.get()) {
                mBackgroundHandler.post(new MotionDetector(bitmap, width, height));
            } else {
                bitmap.recycle();
            }
        }

    }
};
//开启摄像头预览
FaceCameraManager.getInstance().startPreview(getApplicationContext(), this, FaceCameraManager.getInstance().getCurCamera(), cameraView, PREVIEW_WIDTH, PREVIEW_HEIGHT, cameraDataCallback);

3. 获取摄像头拍照数据

{
//根据摄像头是USB还是系统摄像头,调用不同的方法,获取bitmap数据
if (FaceCameraManager.getInstance().getCurCamera() == FaceCameraManager.CAMERA_USB)
    bitmap = mUVCCameraView.captureStillImage();
else
    bitmap = mTextureView.getBitmap();

}

4. 获取特征值

说明:通过bitmap获取特征值

    public ArrayList‹SunmiFaceFeature› getFeatures(Bitmap bitmap, int maxFace) {

        int ret = 0;
        //bitmap的RGB数据转化成BGR数据
        byte[] srcData = ImageUtils.getPixelsBGR(bitmap);
        //通过BGR数据构造SunmifaceImage对象
        SunmiFaceImage image = new SunmiFaceImage(srcData, bitmap.getHeight(), bitmap.getWidth(), maxFace);
        SunmiFaceImageFeatures features = new SunmiFaceImageFeatures();
        //从SunmifaceImage数据中提取人脸特征
        ret = SunmiFaceSDK.getImageFeatures(image, features);
        //返回人脸特征数组
        SunmiFaceFeature feature_ary = features.getFeatures_();
        ArrayList‹SunmiFaceFeature› arrayList = new ArrayList‹›();
        if (features.getFeatures_count_() == 0) {
            SunmiFaceSDK.releaseImageFeatures(features);
            return arrayList;
        }

        for (int i = 0; i ‹ features.getFeatures_count_(); i++) {
            SunmiFaceFeature sunmiFaceFeature = SunmiFaceLib.SunmiFaceFeatureArray_getitem(feature_ary, i);
            arrayList.add(sunmiFaceFeature);
        }
        SunmiFaceSDK.releaseImageFeatures(features);
        return arrayList;
        // return getFeature(srcData, bitmap.getWidth(),bitmap.getHeight(), 1);
    }

5. 人脸特征值比较

    public SunmiFaceCompareResult compareFeature1V1(float similar, float[] feature1, float[] feature2) {
        //构造SunmiFaceFeature对象
        SunmiFaceFeature feat1 = new SunmiFaceFeature();
        SunmiFaceFeature feat2 = new SunmiFaceFeature();
        feat1.setFeature_(feature1);
        feat2.setFeature_(feature2);
        SunmiFaceCompareResult result = new SunmiFaceCompareResult();
        //调用compare1v1进行比较
        int ret = SunmiFaceSDK.compare1v1(feat1, feat2, result);
        return result;
    }

6. 人脸会员库添加

说明: 添加人脸时,同时要向IPC注册人脸信息。向IPC传递信息包括:人脸会员库(GVIP),人脸照片、人脸会员唯一识别ID。

{
        //使用faceFeature构建record
        SunmiFaceDBRecord record =     SunmiFaceSDK.faceFeature2FaceDBRecord(faceFeature);
        record.setId_(user.getUserId());
        record.setName_(user.getUserName());
        //把record添加到人脸库
        int ret = SunmiFaceSDK.addDBRecord(record);
        //把人脸照片传递给IPC,向IPC添加人脸记录   
         if (mIPCManager != null) {
                    mIPCManager.addFaceRecord("GVIP", picPath, uid, new RPCCallback‹RPCResponse›() {
                        //IPC人脸添加记录完成后回调接口
                        @Override
                        public void onComplete(RPCResponse result) {
                            Log.i(TAG, "addFaceRecord, code:" + result.code());
                        }
                        //IPC人脸添加记录失败时回调接口
                        @Override
                        public void onAbort(int httpStatus) {
                            Log.i(TAG, "addFaceRecord, httpStatus:" + httpStatus);
                        }
                        //IPC人脸添加记录异常时回调接口
                        @Override
                        public void onException(Throwable t) {
                            Log.i(TAG, "addFaceRecord, Exception:" + t.getMessage());
                        }
                    });
                }

}

7. 删除特征记录

说明: 删除会员信息时,需要把删除的人脸会员信息同步到IPC。向IPC传递用户的唯一识别ID

{
//删除人脸库记录
int result = SunmiFaceSDK.deleteDBRecord(user.getImageName());
//同步删除IPC人脸记录
if (mIPCManager != null) {
    mIPCManager.deleteFaceRecord("GVIP", userId, new RPCCallback‹RPCResponse‹RPCResponse.FaceDeleteSubResult››() {
         //IPC人脸删除记录完成后回调接口
        @Override
        public void onComplete(RPCResponse‹RPCResponse.FaceDeleteSubResult› result) {
            Log.i(TAG, "deleteFaceRecord, code:" + result.code());
        }
    });
}
}

8 会员注册

/*
 *向系统注册会员信息
 *@groupname, 会员组
 *@userNanem, 会员名
 *@picPath,   会员照片存放路劲
 *@userInfo,  会员详情
 *@faceFeature, 会员照片提取的特征值
 */
public boolean registerUserIntoDBmanager(String groupName, String userName, String picPath, String userInfo, SunmiFaceFeature faceFeature) {
        boolean isSuccess = false;
        Group group = new Group();
        group.setGroupId(groupName);
        //创建会员
        User user = new User();
        user.setGroupId(groupName);
        //final String uid = UUID.randomUUID().toString();
        String uid = String.valueOf(System.currentTimeMillis());
        user.setUserId(uid);
        user.setUserName(userName);
        user.setFeature(faceFeature.getFeature_());
        //创建人脸库记录信息
        SunmiFaceDBRecord record = SunmiFaceSDK.faceFeature2FaceDBRecord(faceFeature);
        record.setId_(user.getUserId());
        record.setName_(user.getUserName());
        //像人脸库添加记录
        int ret = SunmiFaceSDK.addDBRecord(record);
        if (ret != 0) {
            Log.d(TAG, "addDBRecord failed " + SunmiFaceSDK.getErrorString(ret));
            return false;
        }
        //在会员数据库中记录,特征值对应的图片在人脸数据中的ID
        user.setImageName(record.getImg_id_());
        if (userInfo != null) {
            user.setUserInfo(userInfo);
        }
        // 添加用户信息到数据库
        boolean importUserSuccess = FaceManager.getInstance().userAdd(user);
        if (importUserSuccess) {
            // 如果添加到数据库成功,则添加用户组信息到数据库
            // 如果当前图片组名和上一张图片组名相同,则不添加数据库到组表
            if (FaceManager.getInstance().groupAdd(group)) {
                isSuccess = true;
                if (mIPCManager != null) {
                    Log.i(TAG, "picPath: " + picPath);
                    Log.i(TAG, "uid: " + uid);
                    //把会员照片同步发送到IPC,同步会员信息到IPC
                    mIPCManager.addFaceRecord("GVIP", picPath, uid, new RPCCallback‹RPCResponse›() {
                        @Override
                        public void onComplete(RPCResponse result) {
                            Log.i(TAG, "addFaceRecord, code:" + result.code());
                        }

                        @Override
                        public void onAbort(int httpStatus) {
                            Log.i(TAG, "addFaceRecord, httpStatus:" + httpStatus);
                        }

                        @Override
                        public void onException(Throwable t) {
                            Log.i(TAG, "addFaceRecord, Exception:" + t.getMessage());
                        }
                    });
                }
            } else {
                isSuccess = false;
            }

        } else {
            isSuccess = false;
        }
        return isSuccess;
    }

9. IPC 回调接口

说明:用户获取IPC的状态、返回信息等。

{
                    //获取IPC信息
                    IPCameraManager manager = IPCameraManager.getInstance(getApplicationContext());
                    //IPC初始化
                    manager.init("test", "123456", "123456");
                    //注册IPC回调
                    manager.registerListener(new IPCameraListener() {
                        //IPC设备在线时回调接口
                        @Override
                        public void onDeviceOnline(IPCameraInfo device) {
                            showToast("[ " + device.getDeviceid() + " ]上线");
                        }
                        //IPC设备离线时回调接口
                        @Override
                        public void onDeviceOffline(IPCameraInfo device) {
                            showToast("[ " + device.getDeviceid() + " ]离线");
                        }
                        //IPC设备获取到进店信息后回调接口
                        @Override
                        public void onFaceDetect(String userId) {
                            String userName = null;
                            List‹User› users = DBManager.getInstance().queryUserByUserId(userId);
                            if (users != null && users.size() › 0) {
                                userName = users.get(0).getUserName();
                            }
                            if (userName == null) {
                                showToast("未注册的用户进店");
                            } else {
                                User user = users.get(0);
                                UserEvent userEvent = new UserEvent();
                                userEvent.setUserId(user.getUserId());
                                userEvent.setEnterTime(System.currentTimeMillis());
                                userEvent.setDesc("Enter Store");
                                DBManager.getInstance().addUserEvent(userEvent);
                                showToast("用户[ " + userName + " ]进店");
                            }
                        }
                    }
}