Browse Source

同步设备指令、场景历史等业务

liuxiulin 2 tuần trước cách đây
mục cha
commit
22d47c7338

+ 62 - 0
pkg/models/device_command.go

@@ -0,0 +1,62 @@
+package models
+
+import (
+	"errors"
+
+	"github.com/jinzhu/gorm"
+)
+
+// DeviceCommandDatas DeviceCommandData 切片类型
+type DeviceCommandDatas []DeviceCommandData
+
+// DeviceCommand 设备指令
+type DeviceCommand struct {
+	gorm.Model
+	RecordID     string             `gorm:"primary_key;column:record_id;size:32;index" json:"record_id"`
+	DeviceType   string             `gorm:"column:device_type;size:200" json:"device_type"`            // 设备类型
+	DeviceTypeId string             `gorm:"column:device_type_id;size:32;index" json:"device_type_id"` // 设备类型id
+	Name         string             `gorm:"column:name;size:200" json:"name"`                          // 名称
+	Command      string             `gorm:"column:command;size:200" json:"command"`                    // 指令
+	DataType     string             `gorm:"column:data_type;size:100" json:"data_type"`                // 数据类型
+	Data         DeviceCommandDatas `gorm:"foreignkey:CommandId" json:"data"`                          // 数据
+	CommandType  int                `gorm:"column:command_type" json:"command_type"`                   // 指令类型
+}
+
+// TableName 表名
+func (DeviceCommand) TableName() string {
+	return "device_command"
+}
+
+// Validate 验证
+func (a *DeviceCommand) Validate() error {
+	if a.Name == "" {
+		return errors.New("非法参数:[Name]")
+	}
+	if a.DeviceTypeId == "" {
+		return errors.New("非法参数:[DeviceTypeId]")
+	}
+	return nil
+}
+
+// DeviceCommandData DeviceCommandData对象
+type DeviceCommandData struct {
+	gorm.Model
+	RecordID    string `gorm:"primary_key;column:record_id;size:32;index" json:"record_id"`
+	CommandId   string `gorm:"column:command_id;size:32;index" json:"command_id"` // 指令id
+	CommandName string `gorm:"column:command_name;size:200" json:"command_name"`  // 指令名称
+	Name        string `gorm:"column:name;size:200" json:"name"`                  // 名称
+	Params      string `gorm:"column:params;type:text" json:"params"`             // 参数
+}
+
+// TableName 表名
+func (DeviceCommandData) TableName() string {
+	return "device_command_data"
+}
+
+// Validate 验证
+func (a *DeviceCommandData) Validate() error {
+	if a.CommandId == "" {
+		return errors.New("非法参数:[CommandId]")
+	}
+	return nil
+}

+ 63 - 0
pkg/models/device_status.go

@@ -0,0 +1,63 @@
+package models
+
+import (
+	"errors"
+
+	"github.com/jinzhu/gorm"
+)
+
+// DeviceStatusDatas DeviceStatusData 切片类型
+type DeviceStatusDatas []DeviceStatusData
+
+// DeviceStatusInfo DeviceStatus对象
+type DeviceStatus struct {
+	gorm.Model
+	RecordID       string            `gorm:"primary_key;column:record_id;size:32;index" json:"record_id"`
+	DeviceTypeId   string            `gorm:"column:device_type_id;size:32;index" json:"device_type_id"` // 设备类型id
+	DeviceTypeCode string            `gorm:"column:device_type_code;size:100" json:"device_type_code"`  // 设备类型code
+	Desc           string            `gorm:"column:desc;size:500" json:"desc"`                          // 描述
+	Name           string            `gorm:"column:name;size:200" json:"name"`                          // 状态字段名称
+	Label          string            `gorm:"column:label;size:200" json:"label"`                        // 状态字段标签
+	Type           string            `gorm:"column:type;size:100" json:"type"`                          // 状态字段类型
+	Unit           string            `gorm:"column:unit;size:50" json:"unit"`                           // 单位
+	FieldType      int               `gorm:"column:field_type" json:"field_type"`                       // 数据类型
+	Data           DeviceStatusDatas `gorm:"foreignkey:StatusID" json:"data"`                           // 状态数据
+}
+
+// TableName 表名
+func (DeviceStatus) TableName() string {
+	return "device_status"
+}
+
+// Validate 验证
+func (a *DeviceStatus) Validate() error {
+	if a.Name == "" {
+		return errors.New("非法参数:[Name]")
+	}
+	if a.DeviceTypeId == "" {
+		return errors.New("非法参数:[DeviceTypeId]")
+	}
+	return nil
+}
+
+// DeviceStatusData DeviceStatusData对象
+type DeviceStatusData struct {
+	gorm.Model
+	RecordID string `gorm:"primary_key;column:record_id;size:32;index" json:"record_id"`
+	StatusID string `gorm:"column:status_id;size:32;index" json:"status_id"` // 状态id
+	Name     string `gorm:"column:name;size:200" json:"name"`                // 名称
+	Value    string `gorm:"column:value;size:500" json:"value"`              // 值
+}
+
+// TableName 表名
+func (DeviceStatusData) TableName() string {
+	return "device_status_data"
+}
+
+// Validate 验证
+func (a *DeviceStatusData) Validate() error {
+	if a.StatusID == "" {
+		return errors.New("非法参数:[StatusID]")
+	}
+	return nil
+}

+ 6 - 15
pkg/models/scene_his.go

@@ -4,23 +4,14 @@ import (
 	"github.com/jinzhu/gorm"
 )
 
-// SceneHis device model
-// device is a product instance, which is managed by our platform
+// SceneHis scene-manager execution history
+// Records each time a scene is triggered, including which conditions were met and what actions were executed
 type SceneHis struct {
 	gorm.Model
-	RecordId    string `gorm:"column:record_id;size:32;index"`
-	SceneID     string `gorm:"column:scene_id;size:32;index"`
-	DeviceId    string `gorm:"column:device_id;size:20;index"`
-	SubDeviceId string `gorm:"column:sub_device_id;size:20;index"`
-	Cmd         string `gorm:"column:cmd;size:20;"`
-	Params      string `sql:"type:varchar(200);"`
-	Topic       string `sql:"type:varchar(200);"`
-	Payload     string `sql:"type:varchar(200);"`
-}
-
-type SceneHisQuery struct {
-	Device
-	ProductName string
+	RecordId    string `gorm:"column:record_id;size:32;index" json:"record_id"`
+	SceneID     string `gorm:"column:scene_id;size:32;index" json:"scene_id"`
+	ConditionId string `gorm:"column:condition_id;size:200;" json:"condition_id"`
+	ActionId    string `gorm:"column:action_id;size:200;" json:"action_id"`
 }
 
 // Validate 验证

+ 4 - 0
pkg/mysql/migrate.go

@@ -50,6 +50,10 @@ func MigrateDatabase(dbhost, dbport, dbname, dbuser, dbpass string) error {
 		&models.SubDevice{},
 		&models.Ota{},
 		&models.SceneHis{},
+		&models.DeviceCommand{},
+		&models.DeviceCommandData{},
+		&models.DeviceStatus{},
+		&models.DeviceStatusData{},
 	).Error
 	if err != nil {
 		fmt.Printf("%s", err.Error())

+ 46 - 0
pkg/rpcs/device_config.go

@@ -0,0 +1,46 @@
+package rpcs
+
+import (
+	"sparrow/pkg/models"
+)
+
+// ArgsDeviceCommandQuery 设备指令查询参数
+type ArgsDeviceCommandQuery struct {
+	DeviceTypeId string
+	Pi           int
+	Ps           int
+	Name         string
+}
+
+// ReplyDeviceCommandList 设备指令列表响应
+type ReplyDeviceCommandList struct {
+	Total int
+	List  []models.DeviceCommand
+}
+
+// ArgsDeviceStatusQuery 设备状态查询参数
+type ArgsDeviceStatusQuery struct {
+	DeviceTypeId string
+	Pi           int
+	Ps           int
+	Name         string
+}
+
+// ReplyDeviceStatusList 设备状态列表响应
+type ReplyDeviceStatusList struct {
+	Total int
+	List  []models.DeviceStatus
+}
+
+// ArgsSceneHisQuery 场景执行历史查询参数
+type ArgsSceneHisQuery struct {
+	SceneId string
+	Pi      int
+	Ps      int
+}
+
+// ReplySceneHisList 场景执行历史列表响应
+type ReplySceneHisList struct {
+	Total int
+	List  []models.SceneHis
+}

+ 45 - 0
services/apiprovider/actions.go

@@ -8,6 +8,7 @@ import (
 	"github.com/opentracing/opentracing-go/ext"
 	"sparrow/pkg/productconfig"
 	"sparrow/pkg/rpcs"
+	"strings"
 
 	"github.com/opentracing/opentracing-go"
 
@@ -140,6 +141,50 @@ func GetDeviceCurrentStatus(device *models.Device, config *productconfig.Product
 	return
 }
 
+func GetDeviceStatusByFields(device *models.Device, config *productconfig.ProductConfig,
+	urlparams martini.Params, req *http.Request, r render.Render) {
+	server.Log.Printf("ACTION GetDeviceCurrentStatus, identifier:: %v", device.DeviceIdentifier)
+
+	fieldsParam := req.URL.Query().Get("fields")
+	if fieldsParam == "" {
+		r.JSON(http.StatusOK, renderError(ErrWrongQueryFormat, errBadRequestString))
+		return
+	}
+
+	// 解析字段列表
+	requestFields := strings.Split(fieldsParam, ",")
+
+	statusargs := rpcs.ArgsGetStatus{
+		Id: device.DeviceIdentifier,
+	}
+	statusreply := rpcs.ReplyGetStatus{}
+
+	err := server.RPCCallByName(context.Background(), rpcs.ControllerName, "Controller.GetStatus", statusargs, &statusreply)
+	if err != nil {
+		server.Log.Errorf("get device status error: %v", err)
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+
+	status, err := config.StatusToMap(statusreply.Status)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, err))
+		return
+	}
+
+	j := gjson.New("")
+	for _, v := range requestFields {
+		_ = j.Set(v, status[v])
+	}
+
+	result := DeviceStatusFieldResponse{
+		Data: *j,
+	}
+
+	r.JSON(http.StatusOK, result)
+	return
+}
+
 // GetDeviceLatestStatus get device latest status
 func GetDeviceLatestStatus() {
 

+ 147 - 0
services/apiprovider/device_config_actions.go

@@ -0,0 +1,147 @@
+package main
+
+import (
+	"github.com/go-martini/martini"
+	"github.com/martini-contrib/render"
+	"net/http"
+	"sparrow/pkg/rpcs"
+	"sparrow/pkg/server"
+	"strconv"
+)
+
+// GetDeviceCommands 查询设备指令列表(通过设备类型id)
+func GetDeviceCommands(req *http.Request, r render.Render) {
+	deviceTypeId := req.URL.Query().Get("device_type_id")
+	if deviceTypeId == "" {
+		r.JSON(http.StatusOK, renderError(ErrWrongQueryFormat, errBadRequestString))
+		return
+	}
+	pi, _ := strconv.Atoi(req.URL.Query().Get("pi"))
+	ps, _ := strconv.Atoi(req.URL.Query().Get("ps"))
+	if pi <= 0 {
+		pi = 1
+	}
+	if ps <= 0 {
+		ps = 20
+	}
+	name := req.URL.Query().Get("name")
+
+	args := &rpcs.ArgsDeviceCommandQuery{
+		DeviceTypeId: deviceTypeId,
+		Pi:           pi,
+		Ps:           ps,
+		Name:         name,
+	}
+	reply := &rpcs.ReplyDeviceCommandList{}
+	err := server.RPCCallByName(nil, rpcs.RegistryServerName, "Registry.GetDeviceCommands", args, reply)
+	if err != nil {
+		server.Log.Errorf("GetDeviceCommands error: %v", err)
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+	r.JSON(http.StatusOK, done(map[string]interface{}{
+		"list":  reply.List,
+		"total": reply.Total,
+	}))
+}
+
+// GetDeviceStatusList 查询设备状态列表(通过设备类型id)
+func GetDeviceStatusList(req *http.Request, r render.Render) {
+	deviceTypeId := req.URL.Query().Get("device_type_id")
+	if deviceTypeId == "" {
+		r.JSON(http.StatusOK, renderError(ErrWrongQueryFormat, errBadRequestString))
+		return
+	}
+	pi, _ := strconv.Atoi(req.URL.Query().Get("pi"))
+	ps, _ := strconv.Atoi(req.URL.Query().Get("ps"))
+	if pi <= 0 {
+		pi = 1
+	}
+	if ps <= 0 {
+		ps = 20
+	}
+	name := req.URL.Query().Get("name")
+
+	args := &rpcs.ArgsDeviceStatusQuery{
+		DeviceTypeId: deviceTypeId,
+		Pi:           pi,
+		Ps:           ps,
+		Name:         name,
+	}
+	reply := &rpcs.ReplyDeviceStatusList{}
+	err := server.RPCCallByName(nil, rpcs.RegistryServerName, "Registry.GetDeviceStatusList", args, reply)
+	if err != nil {
+		server.Log.Errorf("GetDeviceStatusList error: %v", err)
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+	r.JSON(http.StatusOK, done(map[string]interface{}{
+		"list":  reply.List,
+		"total": reply.Total,
+	}))
+}
+
+// GetSceneHis 分页查询场景执行历史
+func GetSceneHis(req *http.Request, r render.Render) {
+	pi, _ := strconv.Atoi(req.URL.Query().Get("pi"))
+	ps, _ := strconv.Atoi(req.URL.Query().Get("ps"))
+	if pi <= 0 {
+		pi = 1
+	}
+	if ps <= 0 {
+		ps = 20
+	}
+	sceneId := req.URL.Query().Get("scene_id")
+
+	args := &rpcs.ArgsSceneHisQuery{
+		SceneId: sceneId,
+		Pi:      pi,
+		Ps:      ps,
+	}
+	reply := &rpcs.ReplySceneHisList{}
+	err := server.RPCCallByName(nil, rpcs.RegistryServerName, "Registry.GetSceneHis", args, reply)
+	if err != nil {
+		server.Log.Errorf("GetSceneHis error: %v", err)
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+	r.JSON(http.StatusOK, done(map[string]interface{}{
+		"list":  reply.List,
+		"total": reply.Total,
+	}))
+}
+
+// GetSceneHisBySceneId 按场景ID分页查询执行历史
+func GetSceneHisBySceneId(params martini.Params, req *http.Request, r render.Render) {
+	sceneId := params["scene_id"]
+	if sceneId == "" {
+		r.JSON(http.StatusOK, renderError(ErrWrongQueryFormat, errBadRequestString))
+		return
+	}
+
+	pi, _ := strconv.Atoi(req.URL.Query().Get("pi"))
+	ps, _ := strconv.Atoi(req.URL.Query().Get("ps"))
+	if pi <= 0 {
+		pi = 1
+	}
+	if ps <= 0 {
+		ps = 20
+	}
+
+	args := &rpcs.ArgsSceneHisQuery{
+		SceneId: sceneId,
+		Pi:      pi,
+		Ps:      ps,
+	}
+	reply := &rpcs.ReplySceneHisList{}
+	err := server.RPCCallByName(nil, rpcs.RegistryServerName, "Registry.GetSceneHisBySceneId", args, reply)
+	if err != nil {
+		server.Log.Errorf("GetSceneHisBySceneId error: %v", err)
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+	r.JSON(http.StatusOK, done(map[string]interface{}{
+		"list":  reply.List,
+		"total": reply.Total,
+	}))
+}

+ 7 - 0
services/apiprovider/response.go

@@ -1,5 +1,7 @@
 package main
 
+import "github.com/gogf/gf/encoding/gjson"
+
 // Common response fields
 type Common struct {
 	Code    int         `json:"code"`
@@ -30,3 +32,8 @@ type AppAuthDataResponse struct {
 	AccessToken string `json:"access_token"`
 	ExpireAt    int64  `json:"expire_at"`
 }
+
+type DeviceStatusFieldResponse struct {
+	Common
+	Data gjson.Json `json:"data"`
+}

+ 18 - 3
services/apiprovider/router.go

@@ -32,7 +32,7 @@ func ValidateTokenMiddleware(w http.ResponseWriter, r *http.Request, c martini.C
 func route(m *martini.ClassicMartini) {
 	m.Group("/application/v1", func(r martini.Router) {
 		// find a device by key
-		r.Get("/device/info", GetDeviceInfoByKey)
+		r.Get("/device/info", ApplicationAuthOnDeviceIdentifer, GetDeviceInfoByKey)
 
 		// find a device by identifier
 		r.Get("/devices/:identifier/info", ApplicationAuthOnDeviceIdentifer, GetDeviceInfoByIdentifier)
@@ -42,6 +42,11 @@ func route(m *martini.ClassicMartini) {
 			ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
 			GetDeviceCurrentStatus)
 
+		// get device status by specific fields
+		r.Get("/devices/:identifier/status/fields",
+			ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
+			GetDeviceStatusByFields)
+
 		// get devie latest status
 		r.Get("/devices/:identifier/status/latest",
 			ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
@@ -63,7 +68,7 @@ func route(m *martini.ClassicMartini) {
 		r.Post("/devices/:identifier/rules",
 			ApplicationAuthOnDeviceIdentifer, CheckDeviceIdentifier,
 			AddRule)
-		r.Get("/devices/check_net_config", CheckDeviceNetConfig)
+		r.Get("/devices/check_net_config", ApplicationAuthOnDeviceIdentifer, CheckDeviceNetConfig)
 
 		r.Get("/devices/online", CheckDeviceIsOnline)
 
@@ -71,7 +76,17 @@ func route(m *martini.ClassicMartini) {
 
 		r.Post("/task_lifecycle", SubmitTaskLifecycle)
 
-		r.Post("/submit_scene", SubmitSceneAction)
+		r.Post("/submit_scene", ApplicationAuthOnDeviceIdentifer, SubmitSceneAction)
+
+		// 设备指令查询
+		r.Get("/device_commands", ApplicationAuthOnDeviceIdentifer, GetDeviceCommands)
+
+		// 设备状态查询
+		r.Get("/device_status_list", ApplicationAuthOnDeviceIdentifer, GetDeviceStatusList)
+
+		// 场景执行历史查询
+		r.Get("/scene_his", ApplicationAuthOnDeviceIdentifer, GetSceneHis)
+		r.Get("/scene_his/:scene_id", ApplicationAuthOnDeviceIdentifer, GetSceneHisBySceneId)
 	})
 	m.Group("/application/v2", func(r martini.Router) {
 		// send a command to device

+ 101 - 0
services/knowoapi/controllers/device_command.go

@@ -0,0 +1,101 @@
+package controllers
+
+import (
+	"sparrow/pkg/models"
+	"sparrow/services/knowoapi/services"
+
+	"github.com/kataras/iris/v12"
+)
+
+// DeviceCommandController 设备指令API
+type DeviceCommandController struct {
+	Ctx     iris.Context
+	Service services.DeviceCommandService
+	Token   Token
+}
+
+// Post 创建设备指令
+// POST /device_command
+func (a *DeviceCommandController) Post() {
+	item := new(models.DeviceCommand)
+	if err := parseBody(a.Ctx, item); err != nil {
+		badRequest(a.Ctx, err)
+		return
+	}
+	err := a.Service.Create(item)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, item)
+}
+
+// Delete 删除设备指令
+// DELETE /device_command
+func (a *DeviceCommandController) Delete() {
+	item := new(models.DeviceCommand)
+	if err := parseBody(a.Ctx, item); err != nil {
+		badRequest(a.Ctx, err)
+		return
+	}
+	err := a.Service.Delete(item)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, "删除成功")
+}
+
+// Put 更新设备指令
+// PUT /device_command
+func (a *DeviceCommandController) Put() {
+	item := new(models.DeviceCommand)
+	if err := parseBody(a.Ctx, item); err != nil {
+		badRequest(a.Ctx, err)
+		return
+	}
+	p, err := a.Service.Update(item)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, p)
+}
+
+// Get 根据设备类型id获取指令列表
+// GET /device_command?device_type_id=xxx&pi=1&ps=10&name=xxx
+func (a *DeviceCommandController) Get() {
+	pi, err := a.Ctx.URLParamInt("pi")
+	if err != nil {
+		badRequest(a.Ctx, err)
+		return
+	}
+	ps, err := a.Ctx.URLParamInt("ps")
+	if err != nil {
+		badRequest(a.Ctx, err)
+		return
+	}
+	deviceTypeId := a.Ctx.URLParam("device_type_id")
+	name := a.Ctx.URLParam("name")
+
+	ds, total, err := a.Service.GetListByDeviceTypeId(deviceTypeId, pi, ps, name)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, map[string]interface{}{
+		"list":  ds,
+		"total": total,
+	})
+}
+
+// GetBy 根据id查询设备指令
+// GET /device_command/{id}
+func (a *DeviceCommandController) GetBy(id string) {
+	p, err := a.Service.QueryOne(id)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, p)
+}

+ 101 - 0
services/knowoapi/controllers/device_status.go

@@ -0,0 +1,101 @@
+package controllers
+
+import (
+	"sparrow/pkg/models"
+	"sparrow/services/knowoapi/services"
+
+	"github.com/kataras/iris/v12"
+)
+
+// DeviceStatusController 设备状态API
+type DeviceStatusController struct {
+	Ctx     iris.Context
+	Service services.DeviceStatusService
+	Token   Token
+}
+
+// Post 创建设备状态
+// POST /device_status
+func (a *DeviceStatusController) Post() {
+	item := new(models.DeviceStatus)
+	if err := parseBody(a.Ctx, item); err != nil {
+		badRequest(a.Ctx, err)
+		return
+	}
+	err := a.Service.Create(item)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, item)
+}
+
+// Delete 删除设备状态
+// DELETE /device_status
+func (a *DeviceStatusController) Delete() {
+	item := new(models.DeviceStatus)
+	if err := parseBody(a.Ctx, item); err != nil {
+		badRequest(a.Ctx, err)
+		return
+	}
+	err := a.Service.Delete(item)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, "删除成功")
+}
+
+// Put 更新设备状态
+// PUT /device_status
+func (a *DeviceStatusController) Put() {
+	item := new(models.DeviceStatus)
+	if err := parseBody(a.Ctx, item); err != nil {
+		badRequest(a.Ctx, err)
+		return
+	}
+	p, err := a.Service.Update(item)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, p)
+}
+
+// Get 根据设备类型id获取状态列表
+// GET /device_status?device_type_id=xxx&pi=1&ps=10&name=xxx
+func (a *DeviceStatusController) Get() {
+	pi, err := a.Ctx.URLParamInt("pi")
+	if err != nil {
+		badRequest(a.Ctx, err)
+		return
+	}
+	ps, err := a.Ctx.URLParamInt("ps")
+	if err != nil {
+		badRequest(a.Ctx, err)
+		return
+	}
+	deviceTypeId := a.Ctx.URLParam("device_type_id")
+	name := a.Ctx.URLParam("name")
+
+	ds, total, err := a.Service.GetListByDeviceTypeId(deviceTypeId, pi, ps, name)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, map[string]interface{}{
+		"list":  ds,
+		"total": total,
+	})
+}
+
+// GetBy 根据id查询设备状态
+// GET /device_status/{id}
+func (a *DeviceStatusController) GetBy(id string) {
+	p, err := a.Service.QueryOne(id)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, p)
+}

+ 32 - 13
services/knowoapi/controllers/scene_his.go

@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"errors"
 	"github.com/kataras/iris/v12"
 	"sparrow/pkg/models"
 	"sparrow/services/knowoapi/services"
@@ -13,8 +14,8 @@ type SceneHisController struct {
 	Token   Token
 }
 
-// Post post
-// POST /SceneHis
+// Post 创建场景执行历史记录
+// POST /scene_his
 func (a *SceneHisController) Post() {
 	ptl := new(models.SceneHis)
 	if err := parseBody(a.Ctx, ptl); err != nil {
@@ -30,14 +31,14 @@ func (a *SceneHisController) Post() {
 }
 
 // Delete delete
-// DELETE /user/SceneHis
+// DELETE /scene_his
 func (a *SceneHisController) Delete() {
-	SceneHis := new(models.SceneHis)
-	if err := parseBody(a.Ctx, SceneHis); err != nil {
+	sceneHis := new(models.SceneHis)
+	if err := parseBody(a.Ctx, sceneHis); err != nil {
 		badRequest(a.Ctx, err)
 		return
 	}
-	if err := a.Service.Delete(SceneHis); err != nil {
+	if err := a.Service.Delete(sceneHis); err != nil {
 		responseError(a.Ctx, ErrDatabase, err.Error())
 		return
 	}
@@ -45,7 +46,7 @@ func (a *SceneHisController) Delete() {
 }
 
 // Put 更新
-// PUT /user/SceneHis
+// PUT /scene_his
 func (a *SceneHisController) Put() {
 	params := new(models.SceneHis)
 	if err := parseBody(a.Ctx, params); err != nil {
@@ -59,8 +60,8 @@ func (a *SceneHisController) Put() {
 	done(a.Ctx, "已保存")
 }
 
-// Get  SceneHis
-// GET /user/SceneHis?pi=&ps=&name=&version
+// Get 分页查询场景执行历史
+// GET /scene_his?pi=&ps=&scene_id=
 func (a *SceneHisController) Get() {
 	pi, err := a.Ctx.URLParamInt("pi")
 	if err != nil {
@@ -72,16 +73,34 @@ func (a *SceneHisController) Get() {
 		badRequest(a.Ctx, err)
 		return
 	}
-	deviceId := a.Ctx.URLParam("device_id")
+	sceneId := a.Ctx.URLParam("scene_id")
 
-	datas, tSceneHisl, err := a.Service.Query(pi, ps, deviceId)
+	datas, total, err := a.Service.Query(pi, ps, sceneId)
 	if err != nil {
 		responseError(a.Ctx, ErrDatabase, err.Error())
 		return
 	}
 
 	done(a.Ctx, map[string]interface{}{
-		"list":       datas,
-		"tSceneHisl": tSceneHisl,
+		"list":  datas,
+		"total": total,
+	})
+}
+
+// GetBySceneId 根据场景ID获取历史记录列表
+// GET /scene_his/{scene_id:string}
+func (a *SceneHisController) GetBySceneId(sceneId string) {
+	if sceneId == "" {
+		badRequest(a.Ctx, errors.New("scene_id不能为空"))
+		return
+	}
+	datas, err := a.Service.GetBySceneId(sceneId)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, map[string]interface{}{
+		"list":  datas,
+		"total": len(datas),
 	})
 }

+ 51 - 0
services/knowoapi/controllers/scene_history.go

@@ -0,0 +1,51 @@
+package controllers
+
+import (
+	"github.com/gogf/gf/util/guid"
+	"github.com/kataras/iris/v12"
+	"sparrow/pkg/models"
+	"sparrow/services/knowoapi/services"
+)
+
+// SceneHistoryRequest scene-service saveHis 请求体
+type SceneHistoryRequest struct {
+	SceneId     string `json:"scene_id"`
+	ConditionId string `json:"condition_id"`
+	ActionId    string `json:"action_id"`
+}
+
+// SceneHistoryHandler 场景历史内部接口处理器
+// 供 scene-service 通过 HTTP POST 调用,无需JWT认证
+type SceneHistoryHandler struct {
+	Ctx     iris.Context
+	Service services.SceneHisService
+}
+
+// Post 保存场景执行历史
+// POST /iot/v1/scene_history
+func (a *SceneHistoryHandler) Post() {
+	req := new(SceneHistoryRequest)
+	if err := a.Ctx.ReadJSON(req); err != nil {
+		fail(a.Ctx, iris.StatusBadRequest, "请求参数错误:%s", err.Error())
+		return
+	}
+
+	if req.SceneId == "" {
+		fail(a.Ctx, iris.StatusBadRequest, "scene_id不能为空")
+		return
+	}
+
+	sceneHis := &models.SceneHis{
+		RecordId:    guid.S(),
+		SceneID:     req.SceneId,
+		ConditionId: req.ConditionId,
+		ActionId:    req.ActionId,
+	}
+
+	err := a.Service.Create(sceneHis)
+	if err != nil {
+		responseError(a.Ctx, ErrDatabase, err.Error())
+		return
+	}
+	done(a.Ctx, sceneHis)
+}

+ 19 - 15
services/knowoapi/model/all.go

@@ -4,21 +4,23 @@ import "github.com/jinzhu/gorm"
 
 // All 导出
 type All struct {
-	Product     *Product
-	Vendor      *Vendor
-	User        *User
-	Application *Application
-	Protocal    *Protocal
-	Sensor      *Sensor
-	Alert       *Alert
-	Device      *Device
-	SubDevice   *SubDevice
-	Role        *Role
-	Relation    *Relation
-	RuleChain   *RuleChain
-	RuleNode    *RuleNode
-	Ota         *Ota
-	SceneHis    *SceneHis
+	Product       *Product
+	Vendor        *Vendor
+	User          *User
+	Application   *Application
+	Protocal      *Protocal
+	Sensor        *Sensor
+	Alert         *Alert
+	Device        *Device
+	SubDevice     *SubDevice
+	Role          *Role
+	Relation      *Relation
+	RuleChain     *RuleChain
+	RuleNode      *RuleNode
+	Ota           *Ota
+	SceneHis      *SceneHis
+	DeviceCommand *DeviceCommandModel
+	DeviceStatus  *DeviceStatusModel
 }
 
 // Init 初始化所有model
@@ -38,5 +40,7 @@ func (a *All) Init(db *gorm.DB) *All {
 	a.RuleNode = new(RuleNode).Init(db)
 	a.Ota = new(Ota).Init(db)
 	a.SceneHis = new(SceneHis).Init(db)
+	a.DeviceCommand = new(DeviceCommandModel).Init(db)
+	a.DeviceStatus = new(DeviceStatusModel).Init(db)
 	return a
 }

+ 86 - 0
services/knowoapi/model/device_command.go

@@ -0,0 +1,86 @@
+package model
+
+import (
+	"fmt"
+	"sparrow/pkg/models"
+
+	"github.com/jinzhu/gorm"
+)
+
+// DeviceCommandModel ``
+type DeviceCommandModel struct {
+	db *gorm.DB
+}
+
+// Init 初始化
+func (a *DeviceCommandModel) Init(_db *gorm.DB) *DeviceCommandModel {
+	a.db = _db
+	return a
+}
+
+// Create 添加
+func (a *DeviceCommandModel) Create(item *models.DeviceCommand) error {
+	cache := getCache()
+	key := fmt.Sprintf("DeviceCommand:%s", item.RecordID)
+	err := a.db.Save(item).Error
+	if err == nil {
+		cache.Set(key, item)
+	}
+	return err
+}
+
+// Delete 删除
+func (a *DeviceCommandModel) Delete(item *models.DeviceCommand) error {
+	cache := getCache()
+	key := fmt.Sprintf("DeviceCommand:%s", item.RecordID)
+	if _, ok := cache.Get(key); ok {
+		cache.Delete(key)
+	}
+	// 删除关联的 data
+	a.db.Where("command_id = ?", item.RecordID).Delete(&models.DeviceCommandData{})
+	return a.db.Delete(item).Error
+}
+
+// Update 更新
+func (a *DeviceCommandModel) Update(item *models.DeviceCommand) (data models.DeviceCommand, err error) {
+	// 先删除旧的 data
+	a.db.Where("command_id = ?", item.RecordID).Delete(&models.DeviceCommandData{})
+	// 重新保存
+	err = a.db.Save(item).Error
+	if err == nil {
+		cache := getCache()
+		key := fmt.Sprintf("DeviceCommand:%s", item.RecordID)
+		if _, ok := cache.Get(key); ok {
+			cache.Delete(key)
+		}
+		cache.Set(key, item)
+	}
+	return
+}
+
+// GetListByDeviceTypeId 根据设备类型id获取指令列表
+func (a *DeviceCommandModel) GetListByDeviceTypeId(deviceTypeId string, pi, ps int, name string) (datas []models.DeviceCommand, total int, err error) {
+	tx := a.db.Where("device_type_id = ?", deviceTypeId)
+	if name != "" {
+		tx = tx.Where("name like ?", "%"+name+"%")
+	}
+	err = tx.Preload("Data").Limit(ps).Offset((pi - 1) * ps).Find(&datas).Error
+	tx.Model(&models.DeviceCommand{}).Where("device_type_id = ?", deviceTypeId).Count(&total)
+	return
+}
+
+// QueryOne 获取单个内容
+func (a *DeviceCommandModel) QueryOne(recordID string) (data models.DeviceCommand, err error) {
+	cache := getCache()
+	key := fmt.Sprintf("DeviceCommand:%s", recordID)
+	if v, ok := cache.Get(key); ok {
+		_d := v.(*models.DeviceCommand)
+		data = *_d
+	} else {
+		err = a.db.Preload("Data").Where("record_id = ?", recordID).First(&data).Error
+		if err == nil {
+			cache.Set(key, &data)
+		}
+	}
+	return
+}

+ 86 - 0
services/knowoapi/model/device_status.go

@@ -0,0 +1,86 @@
+package model
+
+import (
+	"fmt"
+	"sparrow/pkg/models"
+
+	"github.com/jinzhu/gorm"
+)
+
+// DeviceStatusModel ``
+type DeviceStatusModel struct {
+	db *gorm.DB
+}
+
+// Init 初始化
+func (a *DeviceStatusModel) Init(_db *gorm.DB) *DeviceStatusModel {
+	a.db = _db
+	return a
+}
+
+// Create 添加
+func (a *DeviceStatusModel) Create(item *models.DeviceStatus) error {
+	cache := getCache()
+	key := fmt.Sprintf("DeviceStatus:%s", item.RecordID)
+	err := a.db.Save(item).Error
+	if err == nil {
+		cache.Set(key, item)
+	}
+	return err
+}
+
+// Delete 删除
+func (a *DeviceStatusModel) Delete(item *models.DeviceStatus) error {
+	cache := getCache()
+	key := fmt.Sprintf("DeviceStatus:%s", item.RecordID)
+	if _, ok := cache.Get(key); ok {
+		cache.Delete(key)
+	}
+	// 删除关联的 data
+	a.db.Where("status_id = ?", item.RecordID).Delete(&models.DeviceStatusData{})
+	return a.db.Delete(item).Error
+}
+
+// Update 更新
+func (a *DeviceStatusModel) Update(item *models.DeviceStatus) (data models.DeviceStatus, err error) {
+	// 先删除旧的 data
+	a.db.Where("status_id = ?", item.RecordID).Delete(&models.DeviceStatusData{})
+	// 重新保存
+	err = a.db.Save(item).Error
+	if err == nil {
+		cache := getCache()
+		key := fmt.Sprintf("DeviceStatus:%s", item.RecordID)
+		if _, ok := cache.Get(key); ok {
+			cache.Delete(key)
+		}
+		cache.Set(key, item)
+	}
+	return
+}
+
+// GetListByDeviceTypeId 根据设备类型id获取状态列表
+func (a *DeviceStatusModel) GetListByDeviceTypeId(deviceTypeId string, pi, ps int, name string) (datas []models.DeviceStatus, total int, err error) {
+	tx := a.db.Where("device_type_id = ?", deviceTypeId)
+	if name != "" {
+		tx = tx.Where("name like ?", "%"+name+"%")
+	}
+	err = tx.Preload("Data").Limit(ps).Offset((pi - 1) * ps).Find(&datas).Error
+	tx.Model(&models.DeviceStatus{}).Where("device_type_id = ?", deviceTypeId).Count(&total)
+	return
+}
+
+// QueryOne 获取单个内容
+func (a *DeviceStatusModel) QueryOne(recordID string) (data models.DeviceStatus, err error) {
+	cache := getCache()
+	key := fmt.Sprintf("DeviceStatus:%s", recordID)
+	if v, ok := cache.Get(key); ok {
+		_d := v.(*models.DeviceStatus)
+		data = *_d
+	} else {
+		err = a.db.Preload("Data").Where("record_id = ?", recordID).First(&data).Error
+		if err == nil {
+			cache.Set(key, &data)
+		}
+	}
+	return
+}

+ 25 - 19
services/knowoapi/model/scene_his.go

@@ -18,27 +18,34 @@ func (a *SceneHis) Init(db *gorm.DB) *SceneHis {
 	return a
 }
 
-// Query query all roles
-func (a *SceneHis) Query(pi, ps int, deviceId string) (datas []models.SceneHis, tSceneHisl int, err error) {
+// Query 分页查询场景执行历史
+// sceneId: 按场景ID筛选
+func (a *SceneHis) Query(pi, ps int, sceneId string) (datas []models.SceneHis, total int, err error) {
 	tx := a.db.Where("1=1")
-	if deviceId != "" {
-		tx = tx.Where("version like ?", "%"+deviceId+"%")
+	if sceneId != "" {
+		tx = tx.Where("scene_id = ?", sceneId)
 	}
-
+	tx = tx.Order("id desc")
 	err = tx.Limit(ps).Offset((pi - 1) * ps).Find(&datas).Error
-	err = tx.Model(&models.SceneHis{}).Count(&tSceneHisl).Error
+	err = tx.Model(&models.SceneHis{}).Count(&total).Error
 	return
 }
 
-// Get 获取数据内容
-func (a *SceneHis) Get(vendorId string, recordId string) (data models.SceneHis, err error) {
+// GetBySceneId 根据场景ID获取历史记录列表
+func (a *SceneHis) GetBySceneId(sceneId string) (datas []models.SceneHis, err error) {
+	err = a.db.Where("scene_id = ?", sceneId).Order("id desc").Find(&datas).Error
+	return
+}
+
+// Get 获取单条数据内容
+func (a *SceneHis) Get(recordId string) (data models.SceneHis, err error) {
 	cache := getCache()
 	key := fmt.Sprintf("SceneHisId:%s", recordId)
 	if v, ok := cache.Get(key); ok {
 		_d := v.(*models.SceneHis)
 		data = *_d
 	} else {
-		err = a.db.Where("vendor_id = ? and record_id = ?", vendorId, recordId).First(&data).Error
+		err = a.db.Where("record_id = ?", recordId).First(&data).Error
 		if err == nil {
 			cache.Set(key, &data)
 		}
@@ -46,29 +53,28 @@ func (a *SceneHis) Get(vendorId string, recordId string) (data models.SceneHis,
 	return
 }
 
-// Create 创建
-func (a *SceneHis) Create(SceneHis *models.SceneHis) error {
-	return a.db.Save(SceneHis).Error
-
+// Create 创建场景执行历史记录
+func (a *SceneHis) Create(sceneHis *models.SceneHis) error {
+	return a.db.Save(sceneHis).Error
 }
 
 // Delete delete
-func (a *SceneHis) Delete(SceneHis *models.SceneHis) error {
+func (a *SceneHis) Delete(sceneHis *models.SceneHis) error {
 	cache := getCache()
-	key := fmt.Sprintf("SceneHis:%d", SceneHis.ID)
+	key := fmt.Sprintf("SceneHis:%d", sceneHis.ID)
 	if _, ok := cache.Get(key); ok {
 		cache.Delete(key)
 	}
-	return a.db.Delete(SceneHis).Error
+	return a.db.Delete(sceneHis).Error
 }
 
 // Update update
-func (a *SceneHis) Update(SceneHis *models.SceneHis) (data models.SceneHis, err error) {
+func (a *SceneHis) Update(sceneHis *models.SceneHis) (data models.SceneHis, err error) {
 	cache := getCache()
-	key := fmt.Sprintf("SceneHis:%d", SceneHis.ID)
+	key := fmt.Sprintf("SceneHis:%d", sceneHis.ID)
 	if _, ok := cache.Get(key); ok {
 		cache.Delete(key)
 	}
-	err = a.db.Model(&data).Update(SceneHis).Error
+	err = a.db.Model(&data).Update(sceneHis).Error
 	return
 }

+ 14 - 0
services/knowoapi/router.go

@@ -55,6 +55,8 @@ func registerRouters(srv *iris.Application, models *model.All, gen *generator.Ke
 	fileService := services.NewFileService()
 	otaService := services.NewOtaService(models)
 	sceneHisService := services.NewSceneHisService(models)
+	deviceCommandService := services.NewDeviceCommandService(models)
+	deviceStatusService := services.NewDeviceStatusService(models)
 	v1router := srv.Party("/api/v1")
 
 	// 登陆,注册
@@ -107,4 +109,16 @@ func registerRouters(srv *iris.Application, models *model.All, gen *generator.Ke
 
 	sceneHisAPI := mvc.New(userRouter).Party("/scene_his")
 	sceneHisAPI.Register(sceneHisService).Handle(new(controllers.SceneHisController))
+
+	// scene-service 内部调用接口(无需JWT认证)
+	sceneHistoryAPI := mvc.New(srv.Party("/iot/v1")).Party("/scene_history")
+	sceneHistoryAPI.Register(sceneHisService).Handle(new(controllers.SceneHistoryHandler))
+
+	// deviceCommand api
+	deviceCommandAPI := mvc.New(userRouter.Party("/device_command"))
+	deviceCommandAPI.Register(deviceCommandService).Handle(new(controllers.DeviceCommandController))
+
+	// deviceStatus api
+	deviceStatusAPI := mvc.New(userRouter.Party("/device_status"))
+	deviceStatusAPI.Register(deviceStatusService).Handle(new(controllers.DeviceStatusController))
 }

+ 60 - 0
services/knowoapi/services/device_command.go

@@ -0,0 +1,60 @@
+package services
+
+import (
+	"github.com/gogf/gf/util/guid"
+	"sparrow/pkg/models"
+	"sparrow/services/knowoapi/model"
+)
+
+// DeviceCommandService 业务接口
+type DeviceCommandService interface {
+	Create(*models.DeviceCommand) error
+	Delete(*models.DeviceCommand) error
+	Update(*models.DeviceCommand) (models.DeviceCommand, error)
+	GetListByDeviceTypeId(deviceTypeId string, pi, ps int, name string) ([]models.DeviceCommand, int, error)
+	QueryOne(recordID string) (models.DeviceCommand, error)
+}
+
+type deviceCommandService struct {
+	model *model.All
+}
+
+// NewDeviceCommandService new device command manager
+func NewDeviceCommandService(m *model.All) DeviceCommandService {
+	return &deviceCommandService{
+		model: m,
+	}
+}
+
+func (a *deviceCommandService) Create(item *models.DeviceCommand) error {
+	item.RecordID = guid.S()
+	// 为子数据生成 record_id
+	for i := range item.Data {
+		item.Data[i].RecordID = guid.S()
+		item.Data[i].CommandId = item.RecordID
+	}
+	return a.model.DeviceCommand.Create(item)
+}
+
+func (a *deviceCommandService) Delete(item *models.DeviceCommand) error {
+	return a.model.DeviceCommand.Delete(item)
+}
+
+func (a *deviceCommandService) Update(item *models.DeviceCommand) (models.DeviceCommand, error) {
+	// 为子数据生成 record_id
+	for i := range item.Data {
+		if item.Data[i].RecordID == "" {
+			item.Data[i].RecordID = guid.S()
+		}
+		item.Data[i].CommandId = item.RecordID
+	}
+	return a.model.DeviceCommand.Update(item)
+}
+
+func (a *deviceCommandService) GetListByDeviceTypeId(deviceTypeId string, pi, ps int, name string) ([]models.DeviceCommand, int, error) {
+	return a.model.DeviceCommand.GetListByDeviceTypeId(deviceTypeId, pi, ps, name)
+}
+
+func (a *deviceCommandService) QueryOne(recordID string) (models.DeviceCommand, error) {
+	return a.model.DeviceCommand.QueryOne(recordID)
+}

+ 60 - 0
services/knowoapi/services/device_status.go

@@ -0,0 +1,60 @@
+package services
+
+import (
+	"github.com/gogf/gf/util/guid"
+	"sparrow/pkg/models"
+	"sparrow/services/knowoapi/model"
+)
+
+// DeviceStatusService 业务接口
+type DeviceStatusService interface {
+	Create(*models.DeviceStatus) error
+	Delete(*models.DeviceStatus) error
+	Update(*models.DeviceStatus) (models.DeviceStatus, error)
+	GetListByDeviceTypeId(deviceTypeId string, pi, ps int, name string) ([]models.DeviceStatus, int, error)
+	QueryOne(recordID string) (models.DeviceStatus, error)
+}
+
+type deviceStatusService struct {
+	model *model.All
+}
+
+// NewDeviceStatusService new device status manager
+func NewDeviceStatusService(m *model.All) DeviceStatusService {
+	return &deviceStatusService{
+		model: m,
+	}
+}
+
+func (a *deviceStatusService) Create(item *models.DeviceStatus) error {
+	item.RecordID = guid.S()
+	// 为子数据生成 record_id
+	for i := range item.Data {
+		item.Data[i].RecordID = guid.S()
+		item.Data[i].StatusID = item.RecordID
+	}
+	return a.model.DeviceStatus.Create(item)
+}
+
+func (a *deviceStatusService) Delete(item *models.DeviceStatus) error {
+	return a.model.DeviceStatus.Delete(item)
+}
+
+func (a *deviceStatusService) Update(item *models.DeviceStatus) (models.DeviceStatus, error) {
+	// 为子数据生成 record_id
+	for i := range item.Data {
+		if item.Data[i].RecordID == "" {
+			item.Data[i].RecordID = guid.S()
+		}
+		item.Data[i].StatusID = item.RecordID
+	}
+	return a.model.DeviceStatus.Update(item)
+}
+
+func (a *deviceStatusService) GetListByDeviceTypeId(deviceTypeId string, pi, ps int, name string) ([]models.DeviceStatus, int, error) {
+	return a.model.DeviceStatus.GetListByDeviceTypeId(deviceTypeId, pi, ps, name)
+}
+
+func (a *deviceStatusService) QueryOne(recordID string) (models.DeviceStatus, error) {
+	return a.model.DeviceStatus.QueryOne(recordID)
+}

+ 17 - 12
services/knowoapi/services/scene_his.go

@@ -11,7 +11,8 @@ type SceneHisService interface {
 	Delete(*models.SceneHis) error
 	Update(*models.SceneHis) error
 	Query(int, int, string) ([]models.SceneHis, int, error)
-	Get(string, string) (models.SceneHis, error)
+	GetBySceneId(string) ([]models.SceneHis, error)
+	Get(string) (models.SceneHis, error)
 }
 
 type sceneHisService struct {
@@ -25,24 +26,28 @@ func NewSceneHisService(models *model.All) SceneHisService {
 	}
 }
 
-func (a sceneHisService) Query(pi, ps int, deviceId string) ([]models.SceneHis, int, error) {
-	return a.models.SceneHis.Query(pi, ps, deviceId)
+func (a sceneHisService) Query(pi, ps int, sceneId string) ([]models.SceneHis, int, error) {
+	return a.models.SceneHis.Query(pi, ps, sceneId)
 }
 
-func (a sceneHisService) Get(vendorId, recordId string) (models.SceneHis, error) {
-	return a.models.SceneHis.Get(vendorId, recordId)
+func (a sceneHisService) GetBySceneId(sceneId string) ([]models.SceneHis, error) {
+	return a.models.SceneHis.GetBySceneId(sceneId)
 }
 
-func (a sceneHisService) Create(SceneHis *models.SceneHis) error {
-	SceneHis.RecordId = guid.S()
-	return a.models.SceneHis.Create(SceneHis)
+func (a sceneHisService) Get(recordId string) (models.SceneHis, error) {
+	return a.models.SceneHis.Get(recordId)
 }
 
-func (a sceneHisService) Delete(SceneHis *models.SceneHis) error {
-	return a.models.SceneHis.Delete(SceneHis)
+func (a sceneHisService) Create(sceneHis *models.SceneHis) error {
+	sceneHis.RecordId = guid.S()
+	return a.models.SceneHis.Create(sceneHis)
 }
 
-func (a sceneHisService) Update(SceneHis *models.SceneHis) error {
-	_, err := a.models.SceneHis.Update(SceneHis)
+func (a sceneHisService) Delete(sceneHis *models.SceneHis) error {
+	return a.models.SceneHis.Delete(sceneHis)
+}
+
+func (a sceneHisService) Update(sceneHis *models.SceneHis) error {
+	_, err := a.models.SceneHis.Update(sceneHis)
 	return err
 }

+ 130 - 0
services/registry/device_config.go

@@ -0,0 +1,130 @@
+package main
+
+import (
+	"sparrow/pkg/models"
+	"sparrow/pkg/rpcs"
+)
+
+// GetDeviceCommands 查询设备指令列表(通过设备类型id)
+func (r *Registry) GetDeviceCommands(args *rpcs.ArgsDeviceCommandQuery, reply *rpcs.ReplyDeviceCommandList) error {
+	db, err := getDB()
+	if err != nil {
+		return err
+	}
+
+	tx := db.Where("device_type_id = ?", args.DeviceTypeId)
+	if args.Name != "" {
+		tx = tx.Where("name like ?", "%"+args.Name+"%")
+	}
+
+	var datas []models.DeviceCommand
+	err = tx.Preload("Data").Limit(args.Ps).Offset((args.Pi - 1) * args.Ps).Find(&datas).Error
+	if err != nil {
+		return err
+	}
+	var total int
+	db.Model(&models.DeviceCommand{}).Where("device_type_id = ?", args.DeviceTypeId).Count(&total)
+
+	reply.List = datas
+	reply.Total = total
+	return nil
+}
+
+// GetDeviceCommandById 根据ID查询设备指令
+func (r *Registry) GetDeviceCommandById(recordId string, reply *models.DeviceCommand) error {
+	db, err := getDB()
+	if err != nil {
+		return err
+	}
+
+	err = db.Preload("Data").Where("record_id = ?", recordId).First(reply).Error
+	return err
+}
+
+// GetDeviceStatusList 查询设备状态列表(通过设备类型id)
+func (r *Registry) GetDeviceStatusList(args *rpcs.ArgsDeviceStatusQuery, reply *rpcs.ReplyDeviceStatusList) error {
+	db, err := getDB()
+	if err != nil {
+		return err
+	}
+
+	tx := db.Where("device_type_id = ?", args.DeviceTypeId)
+	if args.Name != "" {
+		tx = tx.Where("name like ?", "%"+args.Name+"%")
+	}
+
+	var datas []models.DeviceStatus
+	err = tx.Preload("Data").Limit(args.Ps).Offset((args.Pi - 1) * args.Ps).Find(&datas).Error
+	if err != nil {
+		return err
+	}
+	var total int
+	db.Model(&models.DeviceStatus{}).Where("device_type_id = ?", args.DeviceTypeId).Count(&total)
+
+	reply.List = datas
+	reply.Total = total
+	return nil
+}
+
+// GetDeviceStatusById 根据ID查询设备状态
+func (r *Registry) GetDeviceStatusById(recordId string, reply *models.DeviceStatus) error {
+	db, err := getDB()
+	if err != nil {
+		return err
+	}
+
+	err = db.Preload("Data").Where("record_id = ?", recordId).First(reply).Error
+	return err
+}
+
+// GetSceneHis 分页查询场景执行历史
+func (r *Registry) GetSceneHis(args *rpcs.ArgsSceneHisQuery, reply *rpcs.ReplySceneHisList) error {
+	db, err := getDB()
+	if err != nil {
+		return err
+	}
+
+	tx := db.Order("id desc")
+	if args.SceneId != "" {
+		tx = tx.Where("scene_id = ?", args.SceneId)
+	}
+
+	var datas []models.SceneHis
+	err = tx.Limit(args.Ps).Offset((args.Pi - 1) * args.Ps).Find(&datas).Error
+	if err != nil {
+		return err
+	}
+
+	countTx := db.Model(&models.SceneHis{})
+	if args.SceneId != "" {
+		countTx = countTx.Where("scene_id = ?", args.SceneId)
+	}
+	var total int
+	countTx.Count(&total)
+
+	reply.List = datas
+	reply.Total = total
+	return nil
+}
+
+// GetSceneHisBySceneId 按场景ID分页查询执行历史
+func (r *Registry) GetSceneHisBySceneId(args *rpcs.ArgsSceneHisQuery, reply *rpcs.ReplySceneHisList) error {
+	db, err := getDB()
+	if err != nil {
+		return err
+	}
+
+	var datas []models.SceneHis
+	err = db.Where("scene_id = ?", args.SceneId).Order("id desc").
+		Limit(args.Ps).Offset((args.Pi - 1) * args.Ps).Find(&datas).Error
+	if err != nil {
+		return err
+	}
+
+	var total int
+	db.Model(&models.SceneHis{}).Where("scene_id = ?", args.SceneId).Count(&total)
+
+	reply.List = datas
+	reply.Total = total
+	return nil
+}

+ 33 - 25
services/scene-service/internal/service/manager/executer.go

@@ -4,6 +4,8 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"github.com/gogf/gf/util/guid"
+	"sparrow/pkg/models"
 	"sparrow/pkg/rpcs"
 	"sparrow/pkg/server"
 	"sparrow/pkg/utils"
@@ -12,6 +14,7 @@ import (
 )
 
 type Action struct {
+	ActionId         string                `json:"action_id"`         // 动作ID
 	DeviceID         string                `json:"device_id"`         // 设备ID
 	SubDeviceId      string                `json:"sub_device_id"`     // 实体子设备Id,如果需要
 	ActionExecutor   string                `json:"action_executor"`   // 动作对象类型
@@ -115,12 +118,39 @@ func getAccessRPCHost(deviceid string) (string, error) {
 }
 
 func (a *TaskExecutor) saveHis(id string, conditionId []string) error {
+	// 收集所有动作ID
+	var actionIds []string
+	for _, action := range a.Actions {
+		if action.ActionId != "" {
+			actionIds = append(actionIds, action.ActionId)
+		}
+	}
 
+	conditionIdStr := strings.Join(conditionId, ",")
+	actionIdStr := strings.Join(actionIds, ",")
+
+	// 1. 通过 RPC 调用 Registry 同步到本地数据库
+	sceneHis := models.SceneHis{
+		RecordId:    guid.S(),
+		SceneID:     id,
+		ConditionId: conditionIdStr,
+		ActionId:    actionIdStr,
+	}
+	rpcReply := rpcs.ReplyEmptyResult{}
+	rpcErr := server.RPCCallByName(nil, rpcs.RegistryServerName, "Registry.CreateSceneHis", &sceneHis, &rpcReply)
+	if rpcErr != nil {
+		server.Log.Errorf("RPC保存场景执行历史失败: sceneId=%s, error=%s", id, rpcErr.Error())
+	}
+
+	// 2. 通过 HTTP POST 通知 knowoapi
 	url := "http://127.0.0.1:8199/iot/v1/scene_history"
 	body := make(map[string]interface{})
 	body["scene_id"] = id
-	if len(conditionId) > 0 {
-		body["condition_id"] = strings.Join(conditionId, ",")
+	if conditionIdStr != "" {
+		body["condition_id"] = conditionIdStr
+	}
+	if actionIdStr != "" {
+		body["action_id"] = actionIdStr
 	}
 	w := new(bytes.Buffer)
 	if err := json.NewEncoder(w).Encode(body); err != nil {
@@ -131,32 +161,10 @@ func (a *TaskExecutor) saveHis(id string, conditionId []string) error {
 		server.Log.Error(err)
 		return err
 	}
-
 	req.Header.Add("Content-Type", "application/json")
 	_, err = a.client.Do(req)
 	if err != nil {
-		server.Log.Errorf("请求出错%s", err.Error())
-
+		server.Log.Errorf("HTTP保存场景执行历史失败: sceneId=%s, error=%s", id, err.Error())
 	}
-
-	//_, err := a.client.Post(url, "application/json", gjson.New(body))
-	//if err != nil {
-	//	server.Log.Errorf("sync his error:%s", err.Error())
-	//}
 	return err
-	//args := models.SceneHis{
-	//	RecordId:    guid.S(),
-	//	SceneID:     id,
-	//	DeviceId:    action.DeviceID,
-	//	SubDeviceId: action.SubDeviceId,
-	//}
-	//if action.ExecutorProperty.FunctionCode != "" {
-	//	args.Cmd = action.ExecutorProperty.FunctionCode
-	//	args.Params = gjson.New(action.ExecutorProperty.FunctionValue).MustToJsonString()
-	//}
-	//if action.PlcPubMessage != nil {
-	//	args.Topic = action.PlcPubMessage.Topic
-	//	args.Payload = string(action.PlcPubMessage.Payload)
-	//}
-	//return server.RPCCallByName(nil, rpcs.RegistryServerName, "Registry.CreateSceneHis", args, &rpcs.ReplyEmptyResult{})
 }

+ 1 - 1
services/scene-service/internal/service/manager/weather.go

@@ -145,7 +145,7 @@ func (w *WeatherSceneService) Stop(id string) error {
 
 // checkWeatherCondition 检查天气
 func (w *WeatherSceneService) checkWeatherCondition(config WeatherSceneConfig) (CheckResult, error) {
-	results := make([]bool, len(config.Conditions))
+	results := make([]bool, 0, len(config.Conditions))
 	var checkResult CheckResult
 	var err error
 	for _, condition := range config.Conditions {