package mijia import ( "context" "encoding/json" "fmt" "strconv" "time" ) const ( checkNewMessagesURI = "/v2/message/v2/check_new_msg" getHomesURI = "/v2/homeroom/gethome_merged" getDevicesURI = "/home/home_device_list" getSharedDevicesURI = "/v2/home/device_list_page" getScenesURI = "/appgateway/miot/appsceneservice/AppSceneService/GetSimpleSceneList" getConsumablesURI = "/v2/home/standard_consumable_items" getPropertiesURI = "/miotspec/prop/get" setPropertiesURI = "/miotspec/prop/set" runActionURI = "/miotspec/action" ) var getHomesData = map[string]any{ "fg": true, "fetch_share": true, "fetch_share_dev": true, "fetch_cariot": true, "limit": 300, "app_ver": 7, "plat_form": 0, } func (client *Client) CheckNewMessages(ctx context.Context, beginAt time.Time) (json.RawMessage, error) { result, err := client.request(ctx, checkNewMessagesURI, map[string]int64{"begin_at": beginAt.Unix()}, true) if err != nil { return nil, fmt.Errorf("check new messages: %w", err) } return result, nil } func (client *Client) GetHomes(ctx context.Context) ([]Home, error) { result, err := client.request(ctx, getHomesURI, getHomesData, true) if err != nil { return nil, fmt.Errorf("get homes: %w", err) } var response struct { Homes []Home `json:"homelist"` } if err := decodeJSON(result, &response); err != nil { return nil, fmt.Errorf("decode %s response: %w", getHomesURI, err) } return response.Homes, nil } func (client *Client) GetDevices(ctx context.Context, homeID string) ([]Device, error) { homes, err := client.GetHomes(ctx) if err != nil { return nil, err } if homeID != "" { owner, err := homeOwner(homes, homeID) if err != nil { return nil, err } return client.getDevices(ctx, homeID, owner) } var devices []Device for _, home := range homes { homeDevices, err := client.getDevices(ctx, home.ID, home.UID) devices = append(devices, homeDevices...) if err != nil { return devices, fmt.Errorf("get devices for home %s: %w", home.ID, err) } } return devices, nil } func (client *Client) getDevices(ctx context.Context, homeID string, owner int64) ([]Device, error) { numericHomeID, err := strconv.ParseInt(homeID, 10, 64) if err != nil { return nil, fmt.Errorf("home id %q must be numeric: %w", homeID, err) } var devices []Device startDID := "" seenCursors := map[string]struct{}{startDID: {}} for { data := map[string]any{ "home_owner": owner, "home_id": numericHomeID, "limit": 200, "start_did": startDID, "get_split_device": true, "support_smart_home": true, "get_cariot_device": true, "get_third_device": true, } result, err := client.request(ctx, getDevicesURI, data, true) if err != nil { return nil, fmt.Errorf("request devices for home %s: %w", homeID, err) } var page struct { Devices []Device `json:"device_info"` MaxDID string `json:"max_did"` HasMore bool `json:"has_more"` } if err := decodeJSON(result, &page); err != nil { return devices, fmt.Errorf("decode %s response for home %s: %w", getDevicesURI, homeID, err) } for index := range page.Devices { page.Devices[index].HomeID = homeID } devices = append(devices, page.Devices...) if !page.HasMore { return devices, nil } if page.MaxDID == "" { return devices, fmt.Errorf("paginate %s response for home %s: has_more is true but max_did is empty", getDevicesURI, homeID) } if _, seen := seenCursors[page.MaxDID]; seen { return devices, fmt.Errorf("paginate %s response for home %s: max_did %q repeats or forms a cursor cycle", getDevicesURI, homeID, page.MaxDID) } seenCursors[page.MaxDID] = struct{}{} startDID = page.MaxDID } } func (client *Client) GetSharedDevices(ctx context.Context) ([]Device, error) { data := map[string]any{ "ssid": "", "bssid": "02:00:00:00:00:00", "getVirtualModel": true, "getHuamiDevices": 1, "get_split_device": true, "support_smart_home": true, "get_cariot_device": true, "get_third_device": true, "get_phone_device": true, "get_miwear_device": true, } result, err := client.request(ctx, getSharedDevicesURI, data, true) if err != nil { return nil, fmt.Errorf("get shared devices: %w", err) } var response struct { Devices []Device `json:"list"` } if err := decodeJSON(result, &response); err != nil { return nil, fmt.Errorf("decode %s response: %w", getSharedDevicesURI, err) } devices := make([]Device, 0, len(response.Devices)) for _, device := range response.Devices { if device.Owner { device.HomeID = "shared" devices = append(devices, device) } } return devices, nil } func (client *Client) GetScenes(ctx context.Context, homeID string) ([]Scene, error) { homes, err := client.GetHomes(ctx) if err != nil { return nil, err } if homeID != "" { owner, err := homeOwner(homes, homeID) if err != nil { return nil, err } return client.getScenes(ctx, homeID, owner) } var scenes []Scene for _, home := range homes { homeScenes, err := client.getScenes(ctx, home.ID, home.UID) if err != nil { return nil, fmt.Errorf("get scenes for home %s: %w", home.ID, err) } scenes = append(scenes, homeScenes...) } return scenes, nil } func (client *Client) getScenes(ctx context.Context, homeID string, owner int64) ([]Scene, error) { data := map[string]any{"app_version": 12, "get_type": 2, "home_id": homeID, "owner_uid": owner} result, err := client.request(ctx, getScenesURI, data, true) if err != nil { return nil, fmt.Errorf("request scenes for home %s: %w", homeID, err) } var response struct { Scenes []Scene `json:"manual_scene_info_list"` } if err := decodeJSON(result, &response); err != nil { return nil, fmt.Errorf("decode %s response for home %s: %w", getScenesURI, homeID, err) } for index := range response.Scenes { response.Scenes[index].HomeID = homeID } return response.Scenes, nil } func (client *Client) RunScene(ctx context.Context, sceneID, homeID string) (json.RawMessage, error) { homes, err := client.GetHomes(ctx) if err != nil { return nil, err } owner, err := homeOwner(homes, homeID) if err != nil { return nil, err } data := map[string]any{"scene_id": sceneID, "scene_type": 2, "phone_id": "null", "home_id": homeID, "owner_uid": owner} result, err := client.request(ctx, "/appgateway/miot/appsceneservice/AppSceneService/NewRunScene", data, true) if err != nil { return nil, fmt.Errorf("run scene %s in home %s: %w", sceneID, homeID, err) } return result, nil } func (client *Client) GetConsumables(ctx context.Context, homeID string) ([]Consumable, error) { homes, err := client.GetHomes(ctx) if err != nil { return nil, err } if homeID != "" { owner, err := homeOwner(homes, homeID) if err != nil { return nil, err } return client.getConsumables(ctx, homeID, owner) } var consumables []Consumable for _, home := range homes { homeConsumables, err := client.getConsumables(ctx, home.ID, home.UID) if err != nil { return nil, fmt.Errorf("get consumables for home %s: %w", home.ID, err) } consumables = append(consumables, homeConsumables...) } return consumables, nil } func (client *Client) getConsumables(ctx context.Context, homeID string, owner int64) ([]Consumable, error) { numericHomeID, err := strconv.ParseInt(homeID, 10, 64) if err != nil { return nil, fmt.Errorf("home id %q must be numeric: %w", homeID, err) } data := map[string]any{"home_id": numericHomeID, "owner_id": owner, "filter_ignore": true} result, err := client.request(ctx, getConsumablesURI, data, true) if err != nil { return nil, fmt.Errorf("request consumables for home %s: %w", homeID, err) } var response struct { Items []struct { Consumables []Consumable `json:"consumes_data"` } `json:"items"` } if err := decodeJSON(result, &response); err != nil { return nil, fmt.Errorf("decode %s response for home %s: %w", getConsumablesURI, homeID, err) } if len(response.Items) == 0 { return nil, nil } consumables := response.Items[0].Consumables for index := range consumables { consumables[index].HomeID = homeID var details []json.RawMessage if json.Unmarshal(consumables[index].Details, &details) == nil && len(details) == 1 { consumables[index].Details = details[0] } } return consumables, nil } func (client *Client) GetProperties(ctx context.Context, requests []PropertyRequest) ([]PropertyResult, error) { if len(requests) == 0 { return []PropertyResult{}, nil } result, err := client.request(ctx, getPropertiesURI, map[string]any{"params": requests, "datasource": 1}, true) if err != nil { return nil, fmt.Errorf("get properties: %w", err) } var properties []PropertyResult if err := decodeJSON(result, &properties); err != nil { return nil, fmt.Errorf("decode %s response: %w", getPropertiesURI, err) } return properties, nil } func (client *Client) SetProperties(ctx context.Context, requests []PropertySetRequest) ([]PropertyResult, error) { if len(requests) == 0 { return []PropertyResult{}, nil } result, err := client.request(ctx, setPropertiesURI, map[string]any{"params": requests}, true) if err != nil { return nil, fmt.Errorf("set properties: %w", err) } var properties []PropertyResult if err := decodeJSON(result, &properties); err != nil { return nil, fmt.Errorf("decode %s response: %w", setPropertiesURI, err) } for index := range properties { properties[index].Message = resultMessage(properties[index].Code, properties[index].Message) } return properties, nil } func (client *Client) RunActions(ctx context.Context, requests []ActionRequest) ([]ActionResult, error) { results := make([]ActionResult, 0, len(requests)) for index, action := range requests { result, err := client.request(ctx, runActionURI, map[string]any{"params": action}, true) if err != nil { return results, fmt.Errorf("run action %d: %w", index, err) } var actionResult ActionResult if err := decodeJSON(result, &actionResult); err != nil { return results, fmt.Errorf("decode %s response for action %d: %w", runActionURI, index, err) } actionResult.Message = resultMessage(actionResult.Code, actionResult.Message) results = append(results, actionResult) } return results, nil } func (client *Client) GetStatistics(ctx context.Context, requests []StatisticsRequest) ([]json.RawMessage, error) { results := make([]json.RawMessage, 0, len(requests)) for index, statisticsRequest := range requests { result, err := client.request(ctx, "/v2/user/statistics", statisticsRequest, true) if err != nil { return results, fmt.Errorf("get statistics %d: %w", index, err) } results = append(results, result) } return results, nil } func homeOwner(homes []Home, homeID string) (int64, error) { for _, home := range homes { if home.ID == homeID { return home.UID, nil } } return 0, &APIError{Code: -1, Message: fmt.Sprintf("未找到 home_id=%s 的家庭信息", homeID)} } func resultMessage(code int, message string) string { if code == 0 || code == 1 { return "成功" } if knownMessage, ok := errorCodeMessages[code]; ok { return knownMessage } if message != "" { return message } return ErrorMessage(code) }