package mijia import ( "bytes" "encoding/json" "fmt" "io" "math" "time" ) const ( // PropertyResultCodeMissing classifies a missing GetMany result locally and is never returned by upstream. PropertyResultCodeMissing int = math.MinInt32 // PropertyResultCodeDuplicate classifies duplicate GetMany results locally and is never returned by upstream. PropertyResultCodeDuplicate int = math.MinInt32 + 1 ) type Home struct { ID string `json:"id"` Name string `json:"name"` UID int64 `json:"uid"` RoomList json.RawMessage `json:"roomlist,omitempty"` Extra map[string]json.RawMessage `json:"-"` } func (home *Home) UnmarshalJSON(payload []byte) error { type homeFields struct { ID json.RawMessage `json:"id"` Name string `json:"name"` UID int64 `json:"uid"` RoomList json.RawMessage `json:"roomlist"` } var fields homeFields if err := json.Unmarshal(payload, &fields); err != nil { return err } id, err := decodeStringOrNumber(fields.ID) if err != nil { return fmt.Errorf("decode home id: %w", err) } home.ID = id home.Name = fields.Name home.UID = fields.UID home.RoomList = fields.RoomList home.Extra, err = decodeExtraFields(payload, "id", "name", "uid", "roomlist") if err != nil { return err } return nil } func (home Home) MarshalJSON() ([]byte, error) { type homeFields Home return encodeJSONWithExtra(homeFields(home), home.Extra, "id", "name", "uid", "roomlist") } type Device struct { DID string `json:"did"` Name string `json:"name"` Model string `json:"model"` UID int64 `json:"uid"` Owner bool `json:"owner"` HomeID string `json:"home_id,omitempty"` Extra map[string]json.RawMessage `json:"-"` properties map[string]PropertySpec actions map[string]ActionSpec client *Client delay time.Duration } func (device *Device) UnmarshalJSON(payload []byte) error { type deviceFields Device var fields deviceFields if err := json.Unmarshal(payload, &fields); err != nil { return err } *device = Device(fields) extra, err := decodeExtraFields(payload, "did", "name", "model", "uid", "owner", "home_id") if err != nil { return err } device.Extra = extra return nil } func (device Device) MarshalJSON() ([]byte, error) { type deviceFields Device return encodeJSONWithExtra(deviceFields(device), device.Extra, "did", "name", "model", "uid", "owner", "home_id") } type Scene struct { SceneID string `json:"scene_id"` Name string `json:"name"` HomeID string `json:"home_id,omitempty"` Extra map[string]json.RawMessage `json:"-"` } func (scene *Scene) UnmarshalJSON(payload []byte) error { type sceneFields Scene var fields sceneFields if err := json.Unmarshal(payload, &fields); err != nil { return err } *scene = Scene(fields) extra, err := decodeExtraFields(payload, "scene_id", "name", "home_id") if err != nil { return err } scene.Extra = extra return nil } func (scene Scene) MarshalJSON() ([]byte, error) { type sceneFields Scene return encodeJSONWithExtra(sceneFields(scene), scene.Extra, "scene_id", "name", "home_id") } type Consumable struct { DID string `json:"did"` Name string `json:"name"` Details json.RawMessage `json:"details"` HomeID string `json:"home_id,omitempty"` Extra map[string]json.RawMessage `json:"-"` } func (consumable *Consumable) UnmarshalJSON(payload []byte) error { type consumableFields Consumable var fields consumableFields if err := json.Unmarshal(payload, &fields); err != nil { return err } *consumable = Consumable(fields) extra, err := decodeExtraFields(payload, "did", "name", "details", "home_id") if err != nil { return err } consumable.Extra = extra return nil } func (consumable Consumable) MarshalJSON() ([]byte, error) { type consumableFields Consumable return encodeJSONWithExtra(consumableFields(consumable), consumable.Extra, "did", "name", "details", "home_id") } type PropertyRequest struct { DID string `json:"did"` SIID int `json:"siid"` PIID int `json:"piid"` } type PropertySetRequest struct { DID string `json:"did"` SIID int `json:"siid"` PIID int `json:"piid"` Value any `json:"value"` } type PropertyResult struct { DID string `json:"did"` SIID int `json:"siid"` PIID int `json:"piid"` Value any `json:"value,omitempty"` Code int `json:"code"` UpdateTime int64 `json:"updateTime,omitempty"` Message string `json:"message,omitempty"` } type DevicePropertyResult struct { Name string Value any Code int } type ActionRequest struct { DID string `json:"did"` SIID int `json:"siid"` AIID int `json:"aiid"` Value any `json:"value,omitempty"` Extra map[string]any `json:"-"` } // MarshalJSON 先序列化固定字段,再合并 Extra;Extra 与保留键 // (did, siid, aiid, value) 冲突时返回错误。 func (request ActionRequest) MarshalJSON() ([]byte, error) { type actionRequestFields ActionRequest payload, err := json.Marshal(actionRequestFields(request)) if err != nil { return nil, err } if len(request.Extra) == 0 { return payload, nil } var combined map[string]json.RawMessage if err := json.Unmarshal(payload, &combined); err != nil { return nil, err } for key, value := range request.Extra { if actionRequestReservedKey(key) { return nil, fmt.Errorf("无效的参数: %s. 请勿使用保留键 (did, siid, aiid, value)", key) } encoded, err := json.Marshal(value) if err != nil { return nil, fmt.Errorf("编码扩展参数 %s: %w", key, err) } combined[key] = encoded } return json.Marshal(combined) } func actionRequestReservedKey(key string) bool { switch key { case "did", "siid", "aiid", "value": return true default: return false } } type ActionResult struct { DID string `json:"did"` SIID int `json:"siid"` AIID int `json:"aiid"` Code int `json:"code"` Out json.RawMessage `json:"out,omitempty"` Message string `json:"message,omitempty"` } type StatisticsRequest struct { DID string `json:"did"` Key string `json:"key"` DataType string `json:"data_type"` Limit int `json:"limit"` TimeStart int64 `json:"time_start"` TimeEnd int64 `json:"time_end"` } func decodeStringOrNumber(raw json.RawMessage) (string, error) { if len(raw) == 0 || bytes.Equal(raw, []byte("null")) { return "", nil } var value string if err := json.Unmarshal(raw, &value); err == nil { return value, nil } decoder := json.NewDecoder(bytes.NewReader(raw)) decoder.UseNumber() var number json.Number if err := decoder.Decode(&number); err != nil { return "", err } return number.String(), nil } func decodeJSON(payload []byte, target any) error { decoder := json.NewDecoder(bytes.NewReader(payload)) decoder.UseNumber() if err := decoder.Decode(target); err != nil { return err } if err := decoder.Decode(&struct{}{}); err != io.EOF { if err == nil { return fmt.Errorf("unexpected trailing JSON value") } return err } return nil } func decodeExtraFields(payload []byte, knownFields ...string) (map[string]json.RawMessage, error) { var extra map[string]json.RawMessage if err := json.Unmarshal(payload, &extra); err != nil { return nil, err } for _, field := range knownFields { delete(extra, field) } if len(extra) == 0 { return nil, nil } return extra, nil } func encodeJSONWithExtra(fields any, extra map[string]json.RawMessage, knownFields ...string) ([]byte, error) { payload, err := json.Marshal(fields) if err != nil { return nil, err } if len(extra) == 0 { return payload, nil } var combined map[string]json.RawMessage if err := json.Unmarshal(payload, &combined); err != nil { return nil, err } reserved := make(map[string]struct{}, len(knownFields)) for _, field := range knownFields { reserved[field] = struct{}{} } for field, value := range extra { if _, known := reserved[field]; !known { combined[field] = value } } return json.Marshal(combined) }