feat: add Go API library
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
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 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)
|
||||
}
|
||||
Reference in New Issue
Block a user