Files
mijia-go-api/types.go
T

300 lines
7.9 KiB
Go

package mijia
import (
"bytes"
"encoding/json"
"fmt"
"io"
"time"
)
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)
}