第一步:请求 CODE

移动应用授权登录

  1. 登陆前 GET https://open-api.flyme.cn/oauth/authorize?response_type=code&scope=uc_basic_info&client_id=LABG5jq09SHTWfjOSKr5&redirect_uri=http://www.meizu.com 返回 Set-Cookie: OSESSIONID,JSESSIONID 并 302 跳转登陆页面

  2. 登录页面 GET https://open-api.flyme.cn/login.do?display=login&redirect_url=https://open-api.flyme.cn/oauth/authorize?response_type=code&redirect_uri=http://www.meizu.com&scope=uc_basic_info&client_id=LABG5jq09SHTWfjOSKr5 返回 Set-Cookie: loginSessionId

  3. 点击登录后,POST 登录表单内容到 https://open-api.flyme.cn/oauth/login.do

参数说明
redirect_url跳转认证服务器链接
password通过 excutePP (password, cryKK)函数对于 password 的每个字符,它将其与 cryKK 进行异或运算,再 URL 编码后返回。
cryKK随机生成,范围 1~1000 整数
ExcutePP 加密算法还原成 python 代码如下
def executePP(password, cryKK):
    # 初始化一个空列表用于存储编码后的字符
    encoded = []
    
    # 遍历密码字符串中的每个字符
    for character in password:
        # 对字符进行 XOR 操作
        encoded_char = chr(cryKK ^ ord(character))
        
        # 如果编码后的字符是字母,则直接添加到列表中
        if encoded_char.isalpha():
            encoded.append(encoded_char)
        else:
            # 如果不是字母,则转换为十六进制表示,并添加到列表中
            encoded.append(f'%{ord(encoded_char):02x}')
    
    # 将列表中的所有元素连接成一个字符串并返回
    return ''.join(encoded)

# 示例用法
password = "password"
cryKK = 40
encoded = executePP(password, cryKK)
print(encoded)  # 输出: XI%5b%5b%5fGZL
参数说明
OSESSIONID登录后获得,跳转访问认证服务器时使用需要
JSESSIONID标识会话,具体用处还不清楚
loginSessionId值与 OSESSIONID 相同

返回示例

请求内容Request
POST https://open-api.flyme.cn/oauth/login.do HTTP/1.1
Host: open-api.flyme.cn
Connection: keep-alive
Content-Length: 751
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
Origin: https://open-api.flyme.cn
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Linux; Android 12; DCO-AL00 Build/HUAWEIDCO-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/99.0.4844.88 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
X-Requested-With: com.meizu.smarthome
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://open-api.flyme.cn/login.do?display=login&redirect_url=https%3A%2F%2Fopen-api.flyme.cn%2Foauth%2Fauthorize%3Fresponse_type%3Dcode%26redirect_uri%3Dhttp%253A%252F%252Fwww.meizu.com%26scope%3Duc_basic_info%26client_id%3DLABG5jq09SHTWfjOSKr5
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: OSESSIONID=c8ddbb8d-cb64-4753-a487-dfc6ac80d8ae; JSESSIONID=node01udgqa8i7do6a40v8ocs6yp9l981586.node0; loginSessionId=c8ddbb8d-cb64-4753-a487-dfc6ac80d8ae; _sh_t=1693920919392; _mco=7930cd6f39357e3cdf61d0b8f36d3cbb; _kbcc=12; _mcc=7


redirect_url=https%3A%2F%2Fopen-api.flyme.cn%2Foauth%2Fauthorize%3Fresponse_type%3Dcode%26redirect_uri%3Dhttp%253A%252F%252Fwww.meizu.com%26scope%3Duc_basic_info%26client_id%3DLABG5jq09SHTWfjOSKr5&encoder_redirect_url=https%253A%252F%252Fopen-api.flyme.cn%252Foauth%252Fauthorize%253Fresponse_type%253Dcode%2526redirect_uri%253Dhttp%253A%252F%252Fwww.meizu.com%2526scope%253Duc_basic_info%2526client_id%253DLABG5jq09SHTWfjOSKr5&account=13888888888&password=XI%5b%5b%5fGZL&geetest_challenge=7e4114a5af8239e4d0baa32c4a393eb7&geetest_validate=943a2a68930df575daf228a8e1451f3c&geetest_seccode=943a2a68930df575daf228a8e1451f3c%7Cjordan&acceptFlyme=on&appuri=https%3A%2F%2Fmember.meizu.com%2Findex.jsp&useruri=&service=&cryKK=40&sid=

--------------------------------------------------------

响应内容 Response
HTTP/1.1 302
Server: nginx
Date: Tue, 05 Sep 2023 13:36:06 GMT
Content-Length: 0
Connection: keep-alive
Location: https://open-api.flyme.cn/oauth/authorize?response_type=code&redirect_uri=http%3A%2F%2Fwww.meizu.com&scope=uc_basic_info&client_id=LABG5jq09SHTWfjOSKr5

跳转认证服务器

手机端登录后跳转认证服务器链接 https://open-api.flyme.cn/oauth/authorize?response_type=code&redirect_uri=http://www.meizu.com&scope=uc_basic_info&client_id=LABG5jq09SHTWfjOSKr5 ,然后通过 302 重定向到回调网站 http://www.meizu.com ,并且带上授权临时票据 code,code 一次性使用后失效。
参数是否必须说明
response_type返回说明,返回用于换取 access_token 的 code
redirect_uri授权回调链接http://www.meizu.com
scope应用授权作用域,此处填uc_basic_info
client_id应用唯一标识,Lipro 的 RELEASE_CLIENT_ID 为LABG5jq09SHTWfjOSKr5

返回示例

请求内容Request
GET https://open-api.flyme.cn/oauth/authorize?response_type=code&redirect_uri=http%3A%2F%2Fwww.meizu.com&scope=uc_basic_info&client_id=LABG5jq09SHTWfjOSKr5 HTTP/1.1
Host: open-api.flyme.cn
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 12; DCO-AL00 Build/HUAWEIDCO-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/99.0.4844.88 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
X-Requested-With: com.meizu.smarthome
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://open-api.flyme.cn/login.do?display=login&redirect_url=https%3A%2F%2Fopen-api.flyme.cn%2Foauth%2Fauthorize%3Fresponse_type%3Dcode%26redirect_uri%3Dhttp%253A%252F%252Fwww.meizu.com%26scope%3Duc_basic_info%26client_id%3DLABG5jq09SHTWfjOSKr5
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: OSESSIONID=2b1f3486-b117-4327-aca4-52055abab956; JSESSIONID=node0u9s0dfzjsrbsith5kkie1agy1016057.node0; loginSessionId=2b1f3486-b117-4327-aca4-52055abab956; _sh_t=1693999010108; _mco=7930cd6f39357e3cdf61d0b8f36d3cbb; _kbcc=29; _mcc=7

--------------------------------------------------------

响应内容Response
HTTP/1.1 302
Server: nginx
Date: Wed, 06 Sep 2023 11:22:04 GMT
Content-Length: 0
Connection: keep-alive
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Cache-Control: no-store
Content-Language: zh-CN
Location: http://www.meizu.com?code=sz_ce947538b1bd4bad868db478320f9567

第二步:通过 code 获取 access_token

接口说明

获取第一步的 code 后,请求以下链接获取 access_token

请求方法

@FormUrlEncoded
@POST("https://api-lipro.meizu.com/v1/user/token/get.do")
Observable<SmartHomeResponse<GetTokenBean>> getTokenByCode(@Field("sign") String str, @Field("optFrom") String str2, @Field("optAt") long j, @Field("vn") String str3, @Field("vc") int i, @Field("model") String str4, @Field("code") String str5);

GetTokenByCode

public static Observable<SmartHomeResponse<GetTokenBean>> getTokenByCode(String str) {
    Application app = getApp();
    String phoneId = DeviceUtil.getPhoneId();
    return getApiService().getTokenByCode(sign(phoneId), phoneId, System.currentTimeMillis(), AppUtil.getMyVersionName(app), AppUtil.getMyVersionCode(app), DeviceUtil.getFlymeModel(), str);
}

//sign=sign(phoneId),算法代码如下
private static String sign(String str) {
    return Md5Util.getSign(str, "*Hilbert$@q9g");
}

//phoneId=DeviceUtil.getPhoneId(),通过获取设备信息的工具类获得
//access_token=getTokenBean.access_token
//refresh_token=getTokenBean.refresh_token
//expires_in=getTokenBean.expires_in

DeviceUtil. GetPhoneId

public class DeviceUtil {
    private static final String KEY_PHONE_ID = "key_phone_id";
    private static final String TAG = "SM_DeviceUtil";
    private static String sPhoneId = null;
    private static final String sUnAuthorizedOAID = "00000000000000000000000000000000";


    public static String getPhoneId() {
        try {
            if (TextUtils.isEmpty(sPhoneId)) {
                String decodeString = KvUtil.decodeString(KEY_PHONE_ID);
                sPhoneId = decodeString;
                if (TextUtils.isEmpty(decodeString)) {
                    sPhoneId = generatePhoneId();
                }
            }
            LogUtil.i(TAG, "get PhoneId : " + sPhoneId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sPhoneId;
    }
	//···省略
}

参数说明

参数是否必须说明
signsign(phoneId),既Md5Util.getSign(phoneId, "*Hilbert$@q9g")返函获得
optFrom唯一标识,phoneId
optAt时间戳 ms
vnapp 版本名称,AppUtil.getMyVersionName(app) 通过 AppUtil 类函数获得
vcapp 版本号,AppUtil.getMyVersionCode(app)通过 AppUtil 类函数获得
model手机型号,DeviceUtil.getFlymeModel()获取设备信息的工具类 DeviceUtil 函数获得
code填写第一步获取的 code 参数

返回说明

正确的返回:
参数说明
access_token接口调用凭证
refresh_token用户刷新 access_token
xpires_inaccess_token 接口调用凭证超时时间,单位(秒)
scope用户授权的作用域,使用逗号(,)分隔
openid授权用户唯一标识

返回示例

请求内容Request
POST https://api-lipro.meizu.com/v1/user/token/get.do HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 170
Host: api-lipro.meizu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.14.9


sign=9d35cdb0ffb98c6bc3e62a191c54a80d&optFrom=12cffe3b-7a62-798b-30fc-3d43d585ae22&optAt=1693920967301&vn=2.3.0&vc=2003000&model=&code=sz_e1f76335bcfa41268f635f26c175bbf7
--------------------------------------------------------

响应内容Response
HTTP/1.1 200
Server: nginx
Date: Tue, 05 Sep 2023 13:36:07 GMT
Content-Type: application/json
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 315

{"code":200,"errorCode":null,"message":"","redirect":"","value":"{\"access_token\":\"eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoiYWNjZXNzX3Rva2VuWFhYIn0\",\"token_type\":\"bearer\",\"refresh_token\":\"eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoicmVmcmVzaF90b2tlblhYIn0\",\"expires_in\":7199,\"scope\":\"uc_basic_info\",\"open_id\":\"X_evTHdKyfVr1-vyS0EHjSOojNfh7Ap9Mf7-DAUw1XA\"}"}

错误返回样例

{
    "code": 1201,
    "errorCode": "AUTH_GET_TOKEN_ERROR",
    "message": "org.apache.http.client.HttpResponseException: Bad Request, {\"error\":\"invalid_grant\",\"error_description\":\"Invalid authorization code: sz_e1f76335bcfa41268f635f26c175bbf7\"}",
    "redirect": "",
    "value": ""
}

第三步:刷新 access_token 有效期

接口说明

Access_token 是调用授权关系接口的调用凭证,由于 access_token 有效期(7199 秒)较短,当 access_token 超时后,可以使用 refresh_token 进行刷新,access_token 刷新结果有两种:

若 access_token 已超时,那么进行 refresh_token 会获取一个新的 access_token 和 refresh_token,新的超时时间。

Refresh_token 拥有较长的有效期,当 refresh_token 失效的后,需要用户重新授权。

请求方法

@FormUrlEncoded
@POST("https://api-lipro.meizu.com/v1/user/token/refresh.do")
Observable<SmartHomeResponse<GetTokenBean>> getTokenByRefreshToken(@Field("sign") String str, @Field("optFrom") String str2, @Field("optAt") long j, @Field("vn") String str3, @Field("vc") int i, @Field("model") String str4, @Field("refreshToken") String str5);

GetTokenByRefreshToken

public static Observable<SmartHomeResponse<GetTokenBean>> getTokenByRefreshToken(String str) {
    Application app = getApp();
    String phoneId = DeviceUtil.getPhoneId();
    return getApiService().getTokenByRefreshToken(sign(phoneId), phoneId, System.currentTimeMillis(), AppUtil.getMyVersionName(app), AppUtil.getMyVersionCode(app), DeviceUtil.getFlymeModel(), str);
}

参数说明

参数是否必须说明
signsign(phoneId)
optFrom唯一标识,phoneId
optAt时间戳 ms
vnapp 版本名称,AppUtil.getMyVersionName(app) 通过 AppUtil 类函数获得
vcapp 版本号,AppUtil.getMyVersionCode(app)通过 AppUtil 类函数获得
model手机型号,DeviceUtil.getFlymeModel()获取设备信息的工具类 DeviceUtil 函数获得
refreshToken填写通过第二步 access_token 获取到的 refresh_token 参数

返回说明

正确的返回:
参数说明
access_token接口调用凭证
refresh_token用户刷新 access_token
xpires_inaccess_token 接口调用凭证超时时间,单位(秒)
scope用户授权的作用域,使用逗号(,)分隔
openid授权用户唯一标识

返回示例

请求内容Request
POST https://api-lipro.meizu.com/v1/user/token/refresh.do HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 266
Host: api-lipro.meizu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.14.9


sign=9d35cdb0ffb98c6bc3e62a191c54a80d&optFrom=12cffe3b-7a62-798b-30fc-3d43d585ae22&optAt=1693911004652&vn=2.3.0&vc=2003000&model=&refreshToken=eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoicmVmcmVzaF90b2tlblhYIn0
--------------------------------------------------------

响应内容Response
HTTP/1.1 200
Server: nginx
Date: Tue, 05 Sep 2023 10:50:04 GMT
Content-Type: application/json
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 315

{"code":200,"errorCode":null,"message":"","redirect":"","value":"{\"access_token\":\"eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoiYWNjZXNzX3Rva2VuWFhYIn0\",\"token_type\":\"bearer\",\"refresh_token\":\"eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoicmVmcmVzaF90b2tlblhYIn0\",\"expires_in\":7199,\"scope\":\"uc_basic_info\",\"open_id\":\"X_evTHdKyfVr1-vyS0EHjSOojNfh7Ap9Mf7-DAUw1XA\"}"}

错误返回样例

{
    "code": 1202,
    "errorCode": "AUTH_REFRESH_TOKEN_ERROR",
    "message": "org.apache.http.client.HttpResponseException: Server Error, {\"error\":\"server_error\",\"error_description\":null}",
    "redirect": "",
    "value": ""
}

第四步:获取用户设备索引

接口说明

若已获得相关设备信息(如"serial": "mesh_group_49666"),可直接进入第七步操作设备

请求方法

@FormUrlEncoded
@Headers({"Cache-Control: no-cache"})
@POST("https://api-lipro.meizu.com/v1/user/index.do")
Observable<SmartHomeResponse<AllDevicesBean>> fetchAllDevices(@Field("accessToken") String str, @Field("sign") String str2, @Field("optFrom") String str3, @Field("optAt") long j, @Field("vn") String str4, @Field("vc") int i, @Field("offset") int i2, @Field("limit") int i3);
public static Observable<SmartHomeResponse<AllDevicesBean>> fetchAllDevices(String str) {
    Application app = getApp();
    String phoneId = DeviceUtil.getPhoneId();
    return getApiService().fetchAllDevices(str, sign(phoneId), phoneId, System.currentTimeMillis(), AppUtil.getMyVersionName(app), AppUtil.getMyVersionCode(app), 0, 200);
}

返回示例

请求内容Request
POST https://api-lipro.meizu.com/v1/user/index.do HTTP/1.1
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
Content-Length: 277
Host: api-lipro.meizu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.14.9


accessToken=eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoiYWNjZXNzX3Rva2VuWFhYIn0&sign=9d35cdb0ffb98c6bc3e62a191c54a80d&optFrom=12cffe3b-7a62-798b-30fc-3d43d585ae22&optAt=1693881745199&vn=2.3.0&vc=2003000&offset=0&limit=200
--------------------------------------------------------

响应内容Response
HTTP/1.1 200
Server: nginx
Date: Tue, 05 Sep 2023 02:42:25 GMT
Content-Type: application/json
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 367

{"code":200,"errorCode":null,"message":"","redirect":"","value":{"devices":[{"deviceId":26666,"deviceName":"客厅智能灯带","group":true,"iotName":"21P3","irRemote":false,"lastModifyAt":1683097218000,"lastModifyBy":"482e84ff8d41de7b319d6bdf245e178c","model":null,"productId":11,"roomId":33331,"roomName":"客厅","serial":"mesh_group_49888","type":6,"userId":null},{"deviceId":25900,"deviceName":"主卧智能灯带","group":true,"iotName":"21P3","irRemote":false,"lastModifyAt":1682154529000,"lastModifyBy":"286bf2352b59af58","model":null,"productId":11,"roomId":33333,"roomName":"主卧","serial":"mesh_group_49666","type":6,"userId":null}],"order":{"device":",26666,25900,","room":",12121,33331,33333,"}}}

第五步:获取用户房间信息

@FormUrlEncoded
@Headers({"Cache-Control: no-cache"})
@POST("https://api-lipro.meizu.com/v1/user/room/list.do")
Observable<SmartHomeResponse<AllRoomsBean>> fetchAllRooms(@Field("accessToken") String str, @Field("sign") String str2, @Field("optFrom") String str3, @Field("optAt") long j, @Field("vn") String str4, @Field("vc") int i);
public static Observable<SmartHomeResponse<AllRoomsBean>> fetchAllRooms(String str) {
    Application app = getApp();
    String phoneId = DeviceUtil.getPhoneId();
    return getApiService().fetchAllRooms(str, sign(phoneId), phoneId, System.currentTimeMillis(), AppUtil.getMyVersionName(app), AppUtil.getMyVersionCode(app));
}
请求内容Request
POST https://api-lipro.meizu.com/v1/user/room/list.do HTTP/1.1
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
Content-Length: 258
Host: api-lipro.meizu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.14.9


accessToken=eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoiYWNjZXNzX3Rva2VuWFhYIn0&sign=9d35cdb0ffb98c6bc3e62a191c54a80d&optFrom=12cffe3b-7a62-798b-30fc-3d43d585ae22&optAt=1693881745199&vn=2.3.0&vc=2003000
--------------------------------------------------------

响应内容Response
HTTP/1.1 200
Server: nginx
Date: Tue, 05 Sep 2023 02:42:25 GMT
Content-Type: application/json
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 289

{"code":200,"errorCode":null,"message":"","redirect":"","value":{"order":{"device":",26666,25900,","room":",12121,33331,33333,"},"rooms":[{"def":0,"lastModifyAt":1682150606000,"lastModifyBy":"286bf2352b59af58","name":"主卧","orders":",25900,","roomId":33333},{"def":0,"lastModifyAt":1683097202000,"lastModifyBy":"482e84ff8d41de7b319d6bdf245e178c","name":"客厅","orders":",26666,","roomId":33331},{"def":1,"lastModifyAt":1683097202000,"lastModifyBy":"482e84ff8d41de7b319d6bdf245e178c","name":"未分配","orders":",","roomId":12121}]}}

第六步:获取 iot 设备状态信息

接口说明

通过 access-token 接口调用凭证获取设备状态信息,携带参数IOT_MERCHANT_CODEtimestampsignRequestBody去调用 API,其中 sign=MD 5 (access-token+timestamp+IOT_MERCHANT_CODE+RequestBody+IOT_SIGN_KEY)

请求方法

@Headers({"Cache-Control: no-cache"})
@POST("https://api-mlink.meizu.com/app/oauth/api/v2/user/query/devices/group/state.do")
Observable<IotResponse<List<IotMeshStatusBean>>> queryIotMeshGroupStatus(@Header("i-access-token") String str, @Header("merchant-code") String str2, @Header("nonce") long j, @Header("sign") String str3, @Body RequestBody requestBody);

QueryIotMeshGroupStatus

public class NetRequest {
    private static final String IOT_MERCHANT_CODE = "LP0002";
    private static final String IOT_SIGN_KEY = "19ff9eb20f818bc45ab216d0d67f";
    //···省略
    public static Observable<IotResponse<List<IotMeshStatusBean>>> iotQueryMeshGroupStatus(String str, List<String> list) {
        long currentTimeMillis = System.currentTimeMillis();
        String format = String.format(Locale.US, "{\"groupIdList\":[%s]}", ArrayUtil.toString(list, "\"", "\"", MzContactsContract.MzGroups.GROUP_SPLIT_MARK_EXTRA));
        return getApiService().queryIotMeshGroupStatus(str, IOT_MERCHANT_CODE, currentTimeMillis, iotSign(str, IOT_MERCHANT_CODE, currentTimeMillis, format), RequestBody.create(MediaType.parse(TrackerConstants.POST_CONTENT_TYPE), format));
    }
	//···省略
}

参数说明

参数是否必须说明
i-access-tokenaccess-token,str
merchant-codeIOT_MERCHANT_CODE 值为LP0002
nonce时间戳timestamp,单位 (毫秒)
sign通过 iotSign 函数返回的 MD5,Md5Util.GetSign (access-token+timestamp+IOT_MERCHANT_CODE+RequestBody+IOT_SIGN_KEY)
RequestBodyrequestBody,format

Iotsign

private static String iotSign(String str, String str2, long j, String str3) {
    String trim = str3 != null ? str3.trim() : "";
    return Md5Util.getSign(str + j + str2 + trim, IOT_SIGN_KEY);
}

//str + j + str2 + trim=`eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoiYWNjZXNzX3Rva2VuWFhYIn0`+`1693883408434`+`LP0002`+`{"groupIdList":["mesh_group_49888","mesh_group_49666"]}`

//IOT_SIGN_KEY=`19ff9eb20f818bc45ab216d0d67f`

Md5Util. GetSign

package com.meizu.smarthome.util;

import android.annotation.SuppressLint;
import android.util.Log;
import java.security.MessageDigest;

@SuppressLint({"LogNotTimber"})
/* loaded from: classes2.dex */
public class Md5Util {
    private static final String TAG = "SM_EncryptUtil";
    private static final char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    public static String encodeHex(byte[] bArr) {
        if (bArr == null || bArr.length <= 0) {
            return null;
        }
        int length = bArr.length;
        char[] cArr = new char[length << 1];
        int i = 0;
        for (int i2 = 0; i2 < length; i2++) {
            int i3 = i + 1;
            char[] cArr2 = hexDigits;
            cArr[i] = cArr2[(bArr[i2] & 240) >>> 4];
            i = i3 + 1;
            cArr[i3] = cArr2[bArr[i2] & 15];
        }
        return new String(cArr);
    }

    public static String getSign(String str, String str2) {
        return md5Hex((str + str2).getBytes());
    }

    public static String md5Hex(byte[] bArr) {
        try {
            return encodeHex(MessageDigest.getInstance("MD5").digest(bArr));
        } catch (Exception e) {
            Log.e(TAG, "", e);
            return null;
        }
    }
}

返回说明

正确的返回:
{
    "code":"0000",
    "message":"success",
    "data":[
        {
            "groupId":"mesh_group_49888",
            "devices":Array[4],
            "properties":Array[15],
            "code":"0000",
            "message":"success",
            "hasBindGateway":true,
            "gatewayDeviceId":null
        },
        {
            "groupId":"mesh_group_49666",
            "devices":[
                {
                    "deviceId":"03ab5ccccccccccc",
                    "properties":[
                        {
                            "key":"address",
                            "value":"7"
                        },
                        {
                            "key":"meshType",
                            "value":"1"
                        },
                        {
                            "key":"groupRoot",
                            "value":"false"
                        },
                        {
                            "key":"gateway",
                            "value":"true"
                        },
                        {
                            "key":"bleMac",
                            "value":"5C:CD:CD:CD:CD:D8"
                        }
                    ],
                    "status":null,
                    "bindCloudType":null,
                    "code":"0000",
                    "message":"success"
                }
            ],
            "properties":[
                {
                    "key":"net_type",
                    "value":"-1"
                },
                {
                    "key":"wifi_rssi",
                    "value":"-80"
                },
                {
                    "key":"deviceName",
                    "value":"21P3"
                },
                {
                    "key":"gearList",
                    "value":"[{\"temperature\":0,\"brightness\":50},{\"temperature\":50,\"brightness\":50},{\"temperature\":100,\"brightness\":50}]"
                },
                {
                    "key":"powerState",
                    "value":"0"
                },
                {
                    "key":"connectState",
                    "value":"0"
                },
                {
                    "key":"version",
                    "value":"7.3.9"
                },
                {
                    "key":"switchGearOnReboot",
                    "value":"1"
                },
                {
                    "key":"mac",
                    "value":"5c:cd:cd:cd:cd:d7"
                },
                {
                    "key":"fadeState",
                    "value":"1"
                },
                {
                    "key":"temperature",
                    "value":"50"
                },
                {
                    "key":"latestSyncTimestamp",
                    "value":"1691640345986"
                },
                {
                    "key":"rcList",
                    "value":"[{\"address\":\"5ccdcdcdcd68\",\"keyindex\":1,\"name\":\"主卧无线控制器\",\"selfIndex\":0,\"timestamp\":\"1683097773619\",\"version\":\"3.1.0\"}]"
                },
                {
                    "key":"brightness",
                    "value":"11"
                },
                {
                    "key":"saveJson",
                    "value":"0"
                }
            ],
            "code":"0000",
            "message":"success",
            "hasBindGateway":true,
            "gatewayDeviceId":null
        }
    ],
    "success":true
}

返回示例

请求内容Request
POST https://api-mlink.meizu.com/app/oauth/api/v2/user/query/devices/group/state.do HTTP/1.1
Cache-Control: no-cache
i-access-token: eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoiYWNjZXNzX3Rva2VuWFhYIn0
merchant-code: LP0002
nonce: 1693911005137
sign: c70fb00e48c81df0c40f3fc6578fc870
Content-Type: application/json; charset=utf-8
Content-Length: 55
Host: api-mlink.meizu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.14.9


{"groupIdList":["mesh_group_49888","mesh_group_49666"]}
--------------------------------------------------------

响应内容Response
HTTP/1.1 200
Server: nginx
Date: Tue, 05 Sep 2023 10:50:05 GMT
Content-Type: application/json;charset=utf-8
Connection: keep-alive
Set-Cookie: JSESSIONID=node02kbh5b8jl1lc10t4p4090ctaj23811.node0; Path=/
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Mon, 04-Sep-2023 10:50:05 GMT
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Mon, 04-Sep-2023 10:50:05 GMT
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 3620

{"code":"0000","message":"success","data":[{"groupId":"mesh_group_49888","devices":[{"deviceId":"03ab5cccccccccc1","properties":[{"key":"schedule","value":""},{"key":"extensions","value":"{}"},{"key":"address","value":"8"},{"key":"meshType","value":"1"},{"key":"groupRoot","value":"false"},{"key":"gateway","value":"false"},{"key":"bleMac","value":"5C:CD:CD:CD:CD:31"}],"status":null,"bindCloudType":null,"code":"0000","message":"success"},{"deviceId":"03ab5cccccccccc7","properties":[{"key":"schedule","value":""},{"key":"extensions","value":"{}"},{"key":"address","value":"9"},{"key":"meshType","value":"1"},{"key":"groupRoot","value":"false"},{"key":"gateway","value":"false"},{"key":"bleMac","value":"5C:CD:CD:CD:CD:77"}],"status":null,"bindCloudType":null,"code":"0000","message":"success"},{"deviceId":"03ab5ccccccccccd","properties":[{"key":"schedule","value":""},{"key":"extensions","value":"{}"},{"key":"address","value":"10"},{"key":"meshType","value":"1"},{"key":"groupRoot","value":"false"},{"key":"gateway","value":"false"},{"key":"bleMac","value":"5C:CD:CD:CD:CD:4D"}],"status":null,"bindCloudType":null,"code":"0000","message":"success"},{"deviceId":"03ab5cccccccccb1","properties":[{"key":"extensions","value":"{}"},{"key":"address","value":"11"},{"key":"meshType","value":"1"},{"key":"groupRoot","value":"true"},{"key":"gateway","value":"true"},{"key":"bleMac","value":"5C:CD:CD:CD:CD:B1"}],"status":null,"bindCloudType":null,"code":"0000","message":"success"}],"properties":[{"key":"net_type","value":"-1"},{"key":"wifi_rssi","value":"-39"},{"key":"deviceName","value":"21P3"},{"key":"gearList","value":"[{\"temperature\":0,\"brightness\":50},{\"temperature\":50,\"brightness\":50},{\"temperature\":100,\"brightness\":50}]"},{"key":"powerState","value":"0"},{"key":"connectState","value":"1"},{"key":"version","value":"7.3.9"},{"key":"switchGearOnReboot","value":"1"},{"key":"mac","value":"5c:CD:CD:CD:CD:b0"},{"key":"fadeState","value":"1"},{"key":"temperature","value":"50"},{"key":"latestSyncTimestamp","value":"1693888845190"},{"key":"rcList","value":"[{\"address\":\"5ccdcdcdcdf1\",\"keyindex\":1,\"name\":\"客厅智能控制器\",\"selfIndex\":0,\"timestamp\":\"1689429124762\",\"version\":\"3.1.0\"}]"},{"key":"brightness","value":"21"},{"key":"saveJson","value":"0"}],"code":"0000","message":"success","hasBindGateway":true,"gatewayDeviceId":null},{"groupId":"mesh_group_49666","devices":[{"deviceId":"03ab5ccccccccccc","properties":[{"key":"address","value":"7"},{"key":"meshType","value":"1"},{"key":"groupRoot","value":"false"},{"key":"gateway","value":"true"},{"key":"bleMac","value":"5C:CD:CD:CD:CD:D8"}],"status":null,"bindCloudType":null,"code":"0000","message":"success"}],"properties":[{"key":"net_type","value":"-1"},{"key":"wifi_rssi","value":"-80"},{"key":"deviceName","value":"21P3"},{"key":"gearList","value":"[{\"temperature\":0,\"brightness\":50},{\"temperature\":50,\"brightness\":50},{\"temperature\":100,\"brightness\":50}]"},{"key":"powerState","value":"0"},{"key":"connectState","value":"0"},{"key":"version","value":"7.3.9"},{"key":"switchGearOnReboot","value":"1"},{"key":"mac","value":"5c:cd:cd:cd:cd:d7"},{"key":"fadeState","value":"1"},{"key":"temperature","value":"50"},{"key":"latestSyncTimestamp","value":"1691640345986"},{"key":"rcList","value":"[{\"address\":\"5ccdcdcdcd68\",\"keyindex\":1,\"name\":\"主卧无线控制器\",\"selfIndex\":0,\"timestamp\":\"1683097773619\",\"version\":\"3.1.0\"}]"},{"key":"brightness","value":"11"},{"key":"saveJson","value":"0"}],"code":"0000","message":"success","hasBindGateway":true,"gatewayDeviceId":null}],"success":true}

错误返回样例

{
    "error": "unauthorized_client",
    "error_description": "请使用授权的access_token"
}

第七步:发送设备操作信息

接口说明

请求方法

@Headers({"Cache-Control: no-cache"})
@POST("https://api-mlink.meizu.com/app/oauth/api/v2/user/control/device/group/command.do")
Observable<IotCommandResult> iotSendCommandToMeshGroup(@Header("i-access-token") String str, @Header("merchant-code") String str2, @Header("nonce") long j, @Header("sign") String str3, @Body RequestBody requestBody);

IotSendCommandToMeshGroup

public static Observable<IotCommandResult> sendCommandToMeshGroup(String str, String str2, String str3, String str4, String str5, String str6) {
    String format;
    long currentTimeMillis = System.currentTimeMillis();
    String iotDeviceType = getIotDeviceType(str4);
    if (str6 != null && str6.length() > 0) {
        format = String.format(Locale.US, "{\"command\": \"%s\", \"deviceId\": \"%s\", \"deviceType\": \"%s\", \"groupId\": \"%s\", \"properties\": %s}", str2, str3, iotDeviceType, str5, str6);
    } else {
        format = String.format(Locale.US, "{\"command\": \"%s\", \"deviceId\": \"%s\", \"deviceType\": \"%s\", \"groupId\": \"%s\"}", str2, str3, iotDeviceType, str5);
    }
    return getApiService().iotSendCommandToMeshGroup(str, IOT_MERCHANT_CODE, currentTimeMillis, iotSign(str, IOT_MERCHANT_CODE, currentTimeMillis, format), RequestBody.create(MediaType.parse(TrackerConstants.POST_CONTENT_TYPE), format));
}

参数说明

参数是否必须说明
i-access-tokenaccess-token,str
merchant-codeIOT_MERCHANT_CODE 值为LP0002
nonce时间戳 ms
sign通过 iotSign 函数返回的 MD5
RequestBodyrequestBody,format
RequestBod 的参数说明
参数是否必须说明
straccess-token
str2command 值:POWER_ONPOWER_OFF
str3deviceId,
iotDeviceTypegetIotDeviceType (str4)返回值
str5groupId,
str6properties 值

返回说明

正确的返回:
{
    "code":"0000",
    "message":"success",
    "data":{
        "msgSn":"354337351310262356",
        "pushSuccess":true,
        "pushTimestamp":1693911007835
    },
    "success":true
}

返回示例

请求内容Request
POST https://api-mlink.meizu.com/app/oauth/api/v2/user/control/device/group/command.do HTTP/1.1
Cache-Control: no-cache
i-access-token: eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoiYWNjZXNzX3Rva2VuWFhYIn0
merchant-code: LP0002
nonce: 1693911007862
sign: 5c864b4c43bcf9ac1411e3cae1443f85
Content-Type: application/json; charset=utf-8
Content-Length: 112
Host: api-mlink.meizu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.14.9


{"command": "POWER_ON", "deviceId": "mesh_group_49888", "deviceType": "ff000001", "groupId": "mesh_group_49888"}
--------------------------------------------------------

响应内容Response
HTTP/1.1 200
Server: nginx
Date: Tue, 05 Sep 2023 10:50:07 GMT
Content-Type: application/json;charset=utf-8
Connection: keep-alive
Set-Cookie: JSESSIONID=node0k5mmglau1l3fjy32amjsxzl423812.node0; Path=/
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Mon, 04-Sep-2023 10:50:07 GMT
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Mon, 04-Sep-2023 10:50:07 GMT
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 137

{"code":"0000","message":"success","data":{"msgSn":"354337351310262356","pushSuccess":true,"pushTimestamp":1693911007835},"success":true}

错误返回样例

{
    "error": "unauthorized_client",
    "error_description": "请使用授权的access_token"
}

获取全部设备配置表(其他)

接口说明

获取 lipro 全部设备配置

请求方法

@FormUrlEncoded
@POST("https://api-lipro.meizu.com/v1/iot/product/index/list.do")
Observable<SmartHomeResponse<List<DeviceConfigBean>>> getDeviceConfigs(@Field("sign") String str, @Field("optFrom") String str2, @Field("optAt") long j, @Field("vn") String str3, @Field("vc") int i);

GetDeviceConfigs

public static Observable<SmartHomeResponse<List<DeviceConfigBean>>> getDeviceConfigs() {
    Application app = getApp();
    String phoneId = DeviceUtil.getPhoneId();
    return getApiService().getDeviceConfigs(sign(phoneId), phoneId, System.currentTimeMillis(), AppUtil.getMyVersionName(app), AppUtil.getMyVersionCode(app));
}

请求内容Request
POST https://api-lipro.meizu.com/v1/iot/product/index/list.do HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 122
Host: api-lipro.meizu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.14.9


sign=&optFrom=12cffe3b-7a62-798b-30fc-3d43d585ae22&optAt=1693994071436&vn=2.3.0&vc=2003000
--------------------------------------------------------

响应内容Response
HTTP/1.1 200
Server: nginx
Date: Wed, 06 Sep 2023 09:54:31 GMT
Content-Type: application/json
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 3550

{"code":200,"errorCode":null,"message":"","redirect":"","value":[{"bannerUrl":"http://lipro.mzres.com/res/6c08cf2d40f328634af2b893189dd483.png","category":"照明","defaultGears":"0,50,100","iconUrl":"http://lipro.mzres.com/res/434f75364d30920f43c785c6ea58821a.png","id":1,"iotName":"20X1","iotType":"1","macRule":"(5CCDCDC[DEF][0-F][0-F][0-F][0-F])|(5CCD7C5E[0-F][0-F][0-F][0-F])|(0CDC7E1[2-4][0-F][0-F][0-F][0-F])|(5CCD7C574[0-F][0-F][0-F])|(5CCDCDCD1[0-F][0-F][0-F])|(5CCDCD714[0-F][0-F][0-F])|(5CCDCDCDD[0-F][0-F][0-F])|(5CCDCD713[0-F][0-F][0-F])|(5CCDCDCD9[0-F][0-F][0-F])|(5CCDCD72[0-F][0-F][0-F][0-F])|(5CCDCD73[0-F][0-F][0-F][0-F])|(EC4D4D9998[3-5][0-F])","maxBrightness":100,"maxTemperature":4000,"minBrightness":1,"minTemperature":3000,"minVersion":"","name":"智能吸顶灯","pairUrl":"http://lipro.mzres.com/res/17af8fc7de105e8818ded9f7b39ae064.png","skuId":"","text":"1.  开灯状态下\\n2. 使用墙壁开关,关灯再开灯,重复 6 次\\n3. 设备出现呼吸效果\\n4. 已恢复到出厂设置","type":1,"typeName":"智能吸顶灯"},{······}]}

获取用户使用 lipro 的天数(其他)

接口说明

请求方法

@FormUrlEncoded
@Headers({"Cache-Control: no-cache"})
@POST("https://api-lipro.meizu.com/v2/user/info/days/get.do")
Observable<SmartHomeResponse<UserUsageInfoBean>> queryUseLiproDays(@Field("accessToken") String str, @Field("sign") String str2, @Field("optFrom") String str3, @Field("optAt") long j, @Field("vn") String str4, @Field("vc") int i);

QueryUseLiproDays

public static Observable<SmartHomeResponse<UserUsageInfoBean>> queryUseLiproDays(String str) {
    Application app = getApp();
    String phoneId = DeviceUtil.getPhoneId();
    return getApiService().queryUseLiproDays(str, sign(phoneId), phoneId, System.currentTimeMillis(), AppUtil.getMyVersionName(app), AppUtil.getMyVersionCode(app));
}
```bash
请求内容Request
POST https://api-lipro.meizu.com/v2/user/info/days/get.do HTTP/1.1
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
Content-Length: 258
Host: api-lipro.meizu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.14.9


accessToken=eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoiYWNjZXNzX3Rva2VuWFhYIn0&sign=9d35cdb0ffb98c6bc3e62a191c54a80d&optFrom=12cffe3b-7a62-798b-30fc-3d43d585ae22&optAt=1693920967848&vn=2.3.0&vc=2003000
--------------------------------------------------------

响应内容Response
HTTP/1.1 200
Server: nginx
Date: Tue, 05 Sep 2023 13:36:07 GMT
Content-Type: application/json
Content-Length: 85
Connection: keep-alive

{"code":200,"errorCode":null,"message":"","redirect":"","value":{"useLiproDays":137}}

获取用户信息(其他)

接口说明

请求方法

@FormUrlEncoded
@Headers({"Cache-Control: no-cache"})
@POST("https://api-lipro.meizu.com/v1/user/auth.do")
Observable<SmartHomeResponse<SmartHomeAuthBean>> authOnSmartHomeService(@Field("accessToken") String str, @Field("sign") String str2, @Field("optFrom") String str3, @Field("optAt") long j, @Field("vn") String str4, @Field("vc") int i);

AuthOnSmartHomeService

public static Observable<SmartHomeResponse<SmartHomeAuthBean>> authOnSmartHomeService(String str) {
    Application app = getApp();
    String phoneId = DeviceUtil.getPhoneId();
    return getApiService().authOnSmartHomeService(str, sign(phoneId), phoneId, System.currentTimeMillis(), AppUtil.getMyVersionName(app), AppUtil.getMyVersionCode(app));
}
请求内容Request
POST https://api-lipro.meizu.com/v1/user/auth.do HTTP/1.1
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
Content-Length: 258
Host: api-lipro.meizu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.14.9


accessToken=eyJhIjoiNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjYiLCJzIjoic3oiLCJ1IjoiMTk5OTk5OTkiLCJ2IjoxLCJpIjoiYWNjZXNzX3Rva2VuWFhYIn0&sign=9d35cdb0ffb98c6bc3e62a191c54a80d&optFrom=12cffe3b-7a62-798b-30fc-3d43d585ae22&optAt=1693920967605&vn=2.3.0&vc=2003000
--------------------------------------------------------

响应内容Response
HTTP/1.1 200
Server: nginx
Date: Tue, 05 Sep 2023 13:36:07 GMT
Content-Type: application/json
Content-Length: 125
Connection: keep-alive

{"code":200,"errorCode":null,"message":"","redirect":"","value":{"uid":19461698,"source":1,"status":1,"regAt":1671866844000}}