水星 Mercury MIPC251C-4 网络摄像头 ONVIF 与 PTZ 云台控制

概况

最近在 什么值得买 上发现一款水星的网络摄像头, 除了支持云台/夜视功能之外, 还标明支持 onvif 协议. 所以想着买来接入到 HomeAssistat 作为监控使用.
可到手之后发现事情并没有那么简单, 记录如下.

接入 HomeAssistant

按照 HA 的文档 ONVIF Camera 接入无非就是配置文件里写两行配置的事, 但无论是使用默认配置还是尝试后台页面里的几个端口都是添加失败.
最后使用 DSM 的 Surveillance Station 找到了正确的端口, 事后发现其实使用 iSpy 也可以找到正确的端口, 端口是2020.

HA 配置如下:

1
2
3
4
5
6
7
8
ffmpeg:
camera:
- platform: onvif
host: 192.168.2.2
port: 2020
username: admin
password: password

需要注意的是使用 onvif camera 需要配置 ffmpeg, 另外可能根据环境不同需要 pip install onvif-zeep-async

ONVIF 与 RTSP 参数

onvif
http://192.168.2.x:2020/onvif/device_service
rtsp 1920x1080
rtsp://admin:pass@192.168.2.x:554/stream1
rtsp 640x480
rtsp://admin:pass@192.168.2.x:554/stream2

PTZ / 云台控制

接入 HA 和 DSM Surveillance Station 之后发现云台不能控制, 看来说的支持 ONVIF 只是可以录制.
剩下的大部分记录如何控制云台.

抓包

Charles 抓包的结果是失败, 应用没有走设置的代理.

反编译 Android App

在尝试了 Charles http 抓包无果之后试着反编译 水星安防App.
然后发现大段 com.tplink.ipc 的代码, 看来水星真的是 TP 的弟弟.
应用开了混淆, 挺好…

  • 添加本地局域网设备 Fragment
    com.tplink.ipc.ui.device.add.DeviceAddByDeviceDetailInputFragment
  • 登录
    com.tplink.ipc.ui.device.add.DeviceAddByDeviceDetailInputFragment#x
    com.tplink.ipc.core.IPCAppContext#devReqAddDevice(java.lang.String, int, java.lang.String, java.lang.String, int, int)
    而 devReqAddDevice 执行的 native 方法, 真正的实现在 libIPCAppContextJNI.so
    看来网络请求是在 JNI 里做的, Charles 抓不到包. 反编译 so 库吃力不讨好, 换方向.

WireShark iOS 抓包

rvictl 可以将 iOS 设备虚拟成一个网络端口, 然后就可以 WireShark 抓包. 好处是这样可以减少一些局域网的干扰.
使用 rvictl 还可以抓 3G/4G 的包, 可以用来抓一些只能在 4G环境下才能复现的问题的包. 同样, 对于一些不支持设置代理的设备, 也可以通过把 iPhone 设置为热点来抓包.
rvictl -s 0000xxxx-00xxxxxxxxxxxxxx
抓包发现 App登录 控制云台的几个步骤的网络请求如下: (1,2两步是根据网页端登录分析出来的)

  1. 获取 RSA 公钥和 nonce
  2. 使用公钥加密密码, 登录, 获取 stok
  3. 发送控制指令

登录

获取 stok

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 获取 RSA 公钥
---->
POST / HTTP/1.1
Host: 192.168.2.2:80
User-Agent: MERCURY_APP
Content-Type: application/json; charset=UTF-8
{"method":"do","login":{}}
<----
{ "error_code": -40401,
"data": {
"code": -60502,
"encrypt_type": ["1", "2"],
"key": "MIGfMA0GCSqGSIb3DQ省略200个字符uJ7N8wIDAQAB",
"nonce": "4vqqXXXX"
}
}
# 登录 获取 stok
---->
POST /
{ "method":"do",
"login": {
"username":"admin",
"encrypt_type":"2",
"password":"ERcuPXuaiwNQcrOX8woIgUAN省略150%2fIHaA%3d"
}
}
<----
{ "error_code": 0,
"stok": "7exxxxxxxxxxxxxxxxxxxxxxxxxxxx59",
"user_group": "root"
}

控制

使用获取的 stok post 指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 获取信息
POST /stok=7exxxxxxxxxxxxxxxxxxxxxxxxxxxx59/ds
{"method":"get","device_info":{"name":["basic_info"]}}
# 获取信息2
POST /stok=7exxxxxxxxxxxxxxxxxxxxxxxxxxxx59/ds
{"method":"get","function":{"name":["module_spec"]}}
# 获取预设位置
POST /stok=7e82c0908c8b141c8e9bb8353b54e259/ds
{"method":"get","preset":{"name":["preset"]}}
# 控制云台转到预设位置
POST /stok=7exxxxxxxxxxxxxxxxxxxxxxxxxxxx59/ds
{"method":"do","preset":{"goto_preset":{"id":"1"}}}
# 云台水平/垂直移动
POST /stok=7exxxxxxxxxxxxxxxxxxxxxxxxxxxx59/ds
{"method":"do","motor":{"move":{"x_coord":"10","y_coord":"0"}}}
# 云台步进 direction 0 / 90
POST /stok=7exxxxxxxxxxxxxxxxxxxxxxxxxxxx59/ds
{"method":"do","motor":{"movestep":{"direction":"0"}}}
# 云台停止
POST /stok=7exxxxxxxxxxxxxxxxxxxxxxxxxxxx59/ds
{"method":"do","motor":{"stop":"null"}}
# 增加预置点
POST /stok=7exxxxxxxxxxxxxxxxxxxxxxxxxxxx59/ds
{"method":"do","preset":{"set_preset":{"name":"name","save_ptz":"1"}}}
# 镜头遮蔽
POST /stok=7exxxxxxxxxxxxxxxxxxxxxxxxxxxx59/ds
{"method":"set","lens_mask":{"lens_mask_info":{"enabled":"on"}}}

小结

目前看来想要控制云台/摄像头的关键是获取 stok, 可以通过抓包可以获得, 但是通过观察当设备重启或着过一段时间(?)就就会失效.

获取 stok

这款摄像头虽然有 web 端但是不支持在 web 端控制云台, 所以开始的抓包没有考虑 web 端. 对 web 端分析发现登录验证使用的是同样的逻辑.
步骤如下

  1. 获取 RSA 公钥 和 nonce (公钥和 nonce 每次都会变化)
  2. 使用 tp-link 的通用加密方式将 密码 password 加密 为 tpPassword
  3. tpPassword 追加 :nonce 为 tpPassword:nonce
  4. tpPassword:nonce 使用 公钥加密为 rsaPassword
  5. rsaPassword 作为密码发送给摄像头验证

对于步骤 2 3 记录如下

tp-link 加密密码为 tpPassword


搜索 RDpbLfCPsJZ7fiv 可以找到这种加密的各种实现.

RSA 加密 tpPassword 为 rsaPassword


其中 a = a.concat(":", $.authRltObj.nonce) 即为追加 nonce
c.setPublicKey($.authRltObj.key) 设置公钥
后面的 sendAjaxReq 就是发送请求

控制脚本

根据上面的分析写的脚本
https://github.com/likaci/mercury-ipc-control

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
usage
need to install packages rsa and requests
pip install requests
pip install rsa
python mipcc.py admin password url data
python mipcc.py admin password http://192.168.2.89:80 '{"method":"do","preset":{"goto_preset": {"id": "1"}}}'
data example:
PTZ to preset position {"method":"do","preset":{"goto_preset": {"id": "1"}}}
PTZ by coord {"method":"do","motor":{"move":{"x_coord":"10","y_coord":"0"}}}
PTZ horizontal by step {"method":"do","motor":{"movestep":{"direction":"0"}}}
PTZ vertical by step {"method":"do","motor":{"movestep":{"direction":"90"}}}
stop PTZ {"method":"do","motor":{"stop":"null"}}
add PTZ preset position {"method":"do","preset":{"set_preset":{"name":"name","save_ptz":"1"}}}
lens mask {"method":"set","lens_mask":{"lens_mask_info":{"enabled":"on"}}}

MacBook Pro 2016 充电功率

前两天服役一年多的洋垃圾 Dell TB15 挂掉了, 表现是只能充电, 提示 usb 设备耗电过多被禁用.

查了几个拓展坞都在价格承受范围之外(流下了穷人的眼泪), 最后决定再买个电源配合之前买的华为 MateDock 用.
参考了充电头网 Macbook的好搭档 60~65W USB PD充电器推荐, 最后选择了网易智造的65W充电器.

测试结果如下:

充电器(标称功率) 充电线 系统详情功率 显示充电器详情
原装 (87W) 原装线 86W Y
原装 (87W) 微软 Dock 线 60W Y
网易智造 (65W) 原装线 65W N
网易智造 (65W) 微软 Dock 线 60W N
Dell TB15 (180W电源) - 58W N
原装 (87W) 原装线 -> 华为 MateDock 51W N

##结论

  • Dell TB15 供电 58W 使用一年多没有过供电不足的情况, 58W 在中等压力下其实已经够用.
  • 网易智造65W 充电器 + 微软 Dock 线 够用/实惠.
  • 华为 MateDock 外接显示器长时间使用温度 60+℃.
  • 充电功率与 充电器 和 充电线 都有关系.

电信阿里鱼卡 UC 免流原理

上个月和电信客服扯皮两周多终于把手机套餐改成了阿里鱼卡套餐。
从 59RMB (500MB+100分钟通话) 的高端套餐改成了 19RMB(1GB+100分钟+日租+阿里文娱应用免流) 低端套餐。
实在要吐槽一下电信改套餐的规定。改套餐明明一个电话或者App戳戳就能搞定事情,偏偏让你回归属地营业厅跑一趟。虽然最后手持身份证拍照异地受理了但体验实在是不好。

鱼卡UC浏览器免流大体实现是这样的

抓包

Wireshark抓包
注意这段 Proxy-Authorization: 1|15120022766*******|com.ucweb.iphone.lowversion|ca5fe4b11712e10ba745c2817*******
关于 Proxy-Authorization 见 https://developer.mozilla.org
即UC浏览器的请求都经过代理服务器。
对比几次请求可以发现 1|UID|UA|Key 这几段猜测规律

  • 1 不变
  • UID 标示用户ID
  • UA 浏览器UA
  • Key 由网站Host计算,应该是md5

反编译

反编译 Android 客户端,基本验证各字段的意义
UC浏览器拼接Proxy-Authorization

而 bcX 和 bcW 的获取过程断点获得调用栈如下

在浏览器激活免流的时候会获取相关字段(不清楚是否会定时更新)
请求 https://freeflow.uc.cn/freeflow/generatePhoneToken 获取一段密文,解密之后解析赋值给 bcX bcW
当开始网络请求时由对应 UID|Key|Host 计算 md5

获取Host

计算MD5

拼接Proxy-Authorization

密文解密之后的结构体

上面需要的 UID UA KEY Server Port都在里面

所以

SSL Pinning 还是必须的。

Android Method Trace 生成与解析

Android Studio 3.0 中的 Profiler 工具相比 2.0 版本有了很大的进步,这几天翻了下相关的源码记录如下.

生成 trace 文件

官方的文档里对于生成 trace 文件主要有两种

code

第一种是在代码中添加

1
2
3
4
5
// start tracing to "/sdcard/calc.trace"
Debug.startMethodTracing("calc");
// ...
// stop tracing
Debug.stopMethodTracing();

生成的trace文件

ddmlib

第二种是使用 ddms / Android Monitor 手动生成, ddms 的核心是一个叫 ddmlib 的库, 在 Android SDK 里 tools/lib/ddmlib-*.jar 可以找到.
网上关于 ddmlib 的文章不多, 可以参考一下. 隐藏Boss——ddmlib使用入门通过 ddmlib 使用 adb,构建框架基础库

命令行工具

翻着源码掉了 N 次坑写了一个命令行调用的 jar AndroidMethodTraceRunner
命令行中调用 $ java -jar mtr.jar -p com.your.package -o output.trace -t 10 可以 trace 10 秒钟, 运行需要 java8.
主要的两个坑:
AndroidDebugBridge.init(true); false 的话不能获取到 client
必须关闭 ddms 和 Studio 的 monitor / profiler, 否则会抢占 client 造成连接失败.

对于 ddmlib 有几个大体的概念
bridge - adb
device - 设备
client - 应用

主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//bridge
//**注意**, 生成 trace 需要连接 client, init(false) 会断开 client
AndroidDebugBridge.init(true);
AndroidDebugBridge bridge = AndroidDebugBridge.createBridge();
//device
IDevice[] devices = bridge.getDevices();
IDevice device = devices[0];
//client
Client client = device.getClient(packageName);
//start stop
client.startSamplingProfiler(10, TimeUnit.MILLISECONDS);
client.stopSamplingProfiler();

Android Studio

但是 Android Studio 3.0 中并没有使用 ddmlib
大体调用栈如下:

1
2
3
4
5
6
7
8
9
10
11
./idea/profilers-ui/src/com/android/tools/profilers/cpu/CpuProfilerStageView.java: myStage.startCapturing();
startCapturing
./tools/adt/idea/profilers/src/com/android/tools/profilers/cpu/CpuProfilerStage.java
com.android.tools.profilers.cpu.CpuProfilerStage#startCapturing
./tools/base/profiler/native/perfd/cpu/cpu_service.cc
grpc::Status CpuServiceImpl::StartProfilingApp
./tools/base/profiler/native/perfd/cpu/simpleperf_manager.cc
bool SimplePerfManager::StartProfiling

解析/读取 trace

GUI

ddms 或者 Android Studio 2.0 / Intellij IDEA 可以直接打开 trace 文件, 但是都没有 Android Studio 3.0 的 profiler 直观好用.

android-studio-3.0-profile)

android-studio-traceview

monitor

dmtracedump

todo

命令行工具

todo