1. 初次配置IPC
调用IPC的OpenAPI接口首先需要获取到IPC的IP地址,故第一步是把IPC接入网络,IPC连接网络的方式有有线连接和无线连接两种方式。
由于有线网络相对不容易受到环境干扰,稳定性和可靠性较高,故首选是有线方式接入网络。
有线接入:
有线接入只需要通过网线把IPC设备和集成SDK的Android设备接入到同一局域网即可,不需要其它设置。
无线接入:
无线配网方式相对要复杂点,步骤如下:
- 使用手机/PC的无线网卡扫描IPC的AP热点,一般AP热点的名称为SUNMI_XXXX,其中XXXX为MAC地址最后2个字节的16进制数字,MAC地址可以通过设备机身后背的标贴或者包装盒的标贴查到,AP热点本身是无加密的。
- 使用手机/PC的无线网卡连接IPC的AP热点,此时手机/PC就会获取到IPC分配的IP地址(按照设备发现描述的方法即可获取到),一般会是192.168.200.XXX,手机/PC的网关地址就是IPC的地址,一般会是192.168.200.1。
- 调用无线配置 API(见获取无线扫描AP列表(无需签名校验)的描述)获取IPC扫描到的AP热点。
- 调用无线配置 API(见设置无线参数(无需签名校验)的描述)设置IPC要连接的无线网络(例如无线路由器的SSID和密码),使得IPC能够从网关处获取到IP地址。
- 如果网络是可以正常上网的话,IPC取到IP地址后很快就会亮蓝灯,此时表明IPC可以正常连接Internet了。

SDK初始化&启动设备发现
private void initSdk() {
... ...
String localAddress = "192.168.1.100";
HttpsUtils.SSLParams sSlParams = getSSLParams();
IPCameraManager mIPCameraManager = IPCameraManager.getInstance();
mIPCameraManager.init(APP_ID, SECRET_KEY, LICENSE, sSlParams);
// 启动设备发现
mIPCameraManager.startDeviceTracker(localAddress);
... ...
}
private static HttpsUtils.SSLParams getSSLParams() {
try {
InputStream key = new FileInputStream(KEYSOTRE);
InputStream ca = new FileInputStream(TRUSTSTORE);
return HttpsUtils.getSslSocketFactory(key, KEYSOTRE_PASSWORD, ca);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
注册设备发现回调
mIPCameraManager.registerDeviceTrackListener(new DeviceTrackListener() {
@Override
public void onDeviceOnline(IPCameraInfo device) {
showToast(getApplicationContext(), "[ " + device.getDeviceid() + " ]上线");
}
@Override
public void onDeviceOffline(IPCameraInfo device) {
showToast(getApplicationContext(), "[ " + device.getDeviceid() + " ]离线");
}
});
扫描局域网内设备
mIPCameraManager.rescan();
扫描可连接的WIFI&设置连接WIFI
// 获取IPC扫描的AP热点
private void getWifiList() {
BasicConfig.getInstance().getApListWithoutAuth(sunmiDevice.getDeviceid(),
new RpcCallback‹RpcResponse‹ScanResultCollection››() {
@Override
public void onComplete(RpcResponse‹ScanResultCollection› 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().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("配置失败");
}
});
}
激活OpenAPI
// 由于激活需要与商米后台通信,因此在激活时确保IPC连接的网络可以连接外网。
DeviceManage.getInstance().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");
}
});
画面缩放&聚焦
// 缩放画面
public void setZoomFactor(int zoomFactor){
mBasicConfig.setZoom(mDevice.getDeviceid(), zoomFactor, new RpcCallback‹RpcResponse›() {
@Override
public void onComplete(RpcResponse result) {
if (result.code() == RpcErrorCode.SUCCESS) {
mLensConfig.zoom = zoomFactor;
}
}
});
}
// 缩放后画面会自动聚焦,如果聚焦不够清晰,手动微调。
public void focus(boolean forward) {
if (forward) {
mLensConfig.focus = (mLensConfig.focus+FOCUS_STEPPER)›mLensConfig.max_focus?mLensConfig.max_focus:(mLensConfig.focus+FOCUS_STEPPER);
} else {
mLensConfig.focus = (mLensConfig.focus-FOCUS_STEPPER)‹0?0:(mLensConfig.focus-FOCUS_STEPPER);
}
mBasicConfig.manualFocus(mDevice.getDeviceid(), mLensConfig.focus, new RpcCallback‹RpcResponse›() {
@Override
public void onComplete(RpcResponse result) {
if (result.code() == RpcErrorCode.SUCCESS) {
Log.i(TAG, "manual focus failed, error code: " + result.code());
}
}
});
}
设置拌线
// 设置拌线,拌线主要用于判定人流的方向,比如进入、离开、穿过等方向,需要进行人流统计的需要设置拌线。
public void line(int start_x, int start_y, int end_x, int end_y) {
PeopleFlowStat.getInstance().setDoorLine(mDevice.getDeviceid(), 0, start_x, start_y, end_x, end_y,
new RpcCallback‹RpcResponse›() {
@Override
public void onComplete(RpcResponse result) {
if (result.code() == RpcErrorCode.SUCCESS) {
stopLive();
mView.gotoHomePage();
}
}
});
}
2. 检查SD卡状态
IPC的录像、人脸识别和人流统计模块的功能都依赖于SD卡,因此需要在使用时确保SD卡插入并成功格式化。建议在发现设备上线后检查SD卡状态,如果获取状态失败则建议60s后再次获取(因为IPC重启后需要一定时间启动所有服务)。

检查SD卡状态&格式化
private void checkSdcardStatus() {
mBasicConfig.getMemoryCardStatus(mDevice.getDeviceid(),
new RPCCallback‹RPCResponse‹ExternalStorageBean››() {
@Override
public void onComplete(RPCResponse‹ExternalStorageBean› result) {
Log.d(TAG, "code: " + result.code() + ", sdcard status: " + result.data().status);
if (result.code() == 0 && result.data().status != 2) {
runOnUiThread(new Runnable() {
@Override
public void run() {
int message = R.string.ipc_prompt_format_sdcard;
switch (result.data().status) {
case 0:
message = R.string.ipc_prompt_format_sdcard_none;
break;
case 1:
message = R.string.ipc_prompt_format_sdcard;
break;
case 3:
message = R.string.ipc_prompt_format_sdcard_broken;
break;
}
CommonDialog sdcardDialog = new CommonDialog.Builder(context)
.setMessage(message)
.setCancelButton(R.string.ipc_cancel)
.setConfirmButton(R.string.ipc_format_sdcard, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mBasicConfig.formatMemoryCard(mDevice.getDeviceid(),
new RPCCallback‹RPCResponse›() {
@Override
public void onComplete(RPCResponse result) {
Log.d(TAG, "code: " + result.code());
}
});
}
})
.create();
sdcardDialog.show();
}
});
}
}
});
}
3. 直播&人脸识别
目前仅FM020支持人脸识别功能。

启动消息通道接收服务
启动此服务后才能接收到来自IPC的人脸识别、动态侦测等消息,启动此服务必须指定IP地址。
private void initSdk() {
... ...
mConnectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT ›= Build.VERSION_CODES.M) {
if (mConnectivityManager != null) {
mUsedNetwork = mConnectivityManager.getActiveNetwork();
if (mUsedNetwork != null) {
MessageCenter.startMessageReceiver(NetworkUtil.getIpAddress(this, mUsedNetwork), HTTP_SERVER_PORT);
}
// 为适配网络切换的情况,动态重启消息接收服务
mConnectivityManager.registerNetworkCallback(new NetworkRequest.Builder().build(),
new ConnectivityManager.NetworkCallback() {
@Override
public void onLost(@NonNull Network network) {
super.onLost(network);
Log.i(TAG, "onLost");
if (network.equals(mUsedNetwork)) {
MessageCenter.stopMessageReceiver();
mUsedNetwork = null;
Network activeNetwork = mConnectivityManager.getActiveNetwork();
if (activeNetwork != null) {
Log.i(TAG, "if: " + mConnectivityManager.getLinkProperties(activeNetwork).getInterfaceName());
mUsedNetwork = activeNetwork;
MessageCenter.startMessageReceiver(NetworkUtil.getIpAddress(getApplicationContext(), mUsedNetwork), HTTP_SERVER_PORT);
}
}
}
@Override
public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities);
Log.i(TAG, "onCapabilitiesChanged: " + networkCapabilities.toString());
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) && mUsedNetwork == null) {
mUsedNetwork = network;
MessageCenter.startMessageReceiver(NetworkUtil.getIpAddress(getApplicationContext(), mUsedNetwork), HTTP_SERVER_PORT);
}
}
});
}
}
... ...
}
注册人脸识别回调
private void registerCallback() {
mFaceDetectListener = new FaceDetectListener() {
@Override
public void onFaceDetect(FaceRecognitionEvent event) {
Bitmap bitmap = null;
try {
bitmap = downloadImage(event.pic_url);
} catch (IOException e) {
e.printStackTrace();
}
if(bitmap == null) {
return;
}
Person person = new Person();
person.setBitmap(bitmap);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
long tlong = Long.parseLong(event.report_time) * 1000;
Timestamp ts = new Timestamp(tlong);
person.setPersonDate(sdf.format(ts));
person.setPersonID(event.faceid);
person.setPersonName(event.group_name);
person.setSex(event.gender);
person.setPersonAgeRange(event.age_range);
personList.add(0, person);
if (personList.size() › personListRecyclerSize) {
personList.get(personListRecyclerSize).getBitmap().recycle();
personList.remove(personListRecyclerSize);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
personListAdapter.notifyDataSetChanged();
}
});
}
};
MessageCenter.registerFaceDetectListener(mFaceDetectListener);
}
订阅人脸识别消息
private void subscribeFaceRecognition() {
List‹String› events = new ArrayList‹›();
events.add("face_recog_event");
MessageCenter.getInstance().subscribeEvent(mDevice.getDeviceid(), events, new RpcCallback‹RpcResponse›() {
@Override
public void onComplete(RpcResponse result) {
Log.i(TAG, "subscribeEvent, code:" + result.code());
}
});
}
直播
// rtsp直播流的默认账号/密码是admin/admin
private void openMediaPlayer() {
new Thread(new Runnable() {
@Override
public void run() {
VideoStream.getInstance().getLiveStream(mDevice.getDeviceid(),
new RpcCallback‹RpcResponse‹LiveAddress››() {
@Override
public void onComplete(RpcResponse‹LiveAddress› result) {
if (result.code() == RpcErrorCode.SUCCESS) {
Log.i(TAG, "live url: " + result.data().fhd_live_url);
if (mPlayer == null) {
mPlayer = new SunmiPlayer(context);
mPlayer.setListener(new SunmiPlayerListener() {
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
iMediaPlayer.start();
}
});
mPlayer.setSurface(mVideoView.getHolder().getSurface());
String liveUrl = result.data().fhd_live_url.replaceFirst("^rtsp://", "rtsp://admin:admin@");
Log.i(TAG, "real url: " + liveUrl);
mPlayer.setUp(liveUrl);
}
} else {
Log.e(TAG, " IPC Error: " + result.code());
showToast(getApplicationContext(), "IPC Error: "+ result.code());
}
}
});
}
}).start();
}