代码拉取完成,页面将自动刷新
package mockdata
import (
"encoding/json"
"fmt"
"gitee.com/captials-team/ubdframe/src/common/utils"
"github.com/Knetic/govaluate"
"github.com/spf13/cast"
"io/ioutil"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func NewMockDataRule(s string) MockDataRule {
var d MockDataRule
if s == "" {
return d
}
if err := json.Unmarshal([]byte(s), &d); err != nil {
panic(err)
}
if d.WhiteMap == nil {
d.WhiteMap = map[string]bool{}
}
for _, v := range d.WhiteList {
d.WhiteMap[v] = true
}
for k, v := range d.ComplexRules {
v.Index = int64(k) + 1
}
return d
}
func NewMockDataRuleFromFile(file string) MockDataRule {
var s = ``
if file != "" && utils.FilePathExist(file) {
tmp, err := ioutil.ReadFile(file)
if err == nil {
s = string(tmp)
}
}
if s == "" {
return MockDataRule{}
}
return NewMockDataRule(s)
}
// MockDataRule 模拟仿真数据规则配置
// 目前暂不支持并发下处理
type MockDataRule struct {
WhiteList []string `json:"whiteList"` //白名单,在白名单中的才可上传,空表示不限制
SampleRules map[string]*SampleRuleItem `json:"sample_rules"` //规则-随机取样本值
RandRules map[string]*RandRuleItem `json:"rand_rules"` //规则-取随机一个值
ComplexRules []*ComplexRuleItem `json:"complex_rules"` //复杂规则
WhiteMap map[string]bool `json:"-"`
}
type RuleCalcExtendParams map[string]interface{}
// MergeCalcExtendParams 合并计算扩展参数
func MergeCalcExtendParams(ps ...RuleCalcExtendParams) RuleCalcExtendParams {
p := make(map[string]interface{})
if len(ps) > 0 {
for _, tmp := range ps {
if tmp == nil {
continue
}
for k, v := range tmp {
p[k] = v
}
}
}
return p
}
// GetData 获取值
//
// key=对应字段名,value=默认值
// params,额外的计算参数
// 返回值: 对应值,是否需要使用
func (r MockDataRule) GetData(key string, value interface{}, params RuleCalcExtendParams) (interface{}, bool) {
//有白名单且不在里面就不处理
if r.WhiteMap != nil {
if _, exist := r.WhiteMap[key]; len(r.WhiteMap) > 0 && !exist {
return value, false
}
}
//样本规则
if r.SampleRules != nil {
if rule, exist := r.SampleRules[key]; exist {
return rule.GetData(key, value, params)
}
}
//随机规则
if r.RandRules != nil {
if rule, exist := r.RandRules[key]; exist {
return rule.GetData(key, value, params)
}
}
//复杂规则
for _, v := range r.ComplexRules {
result, ok := v.GetData(key, value, params)
if ok {
return result, ok
}
}
return value, false
}
// SampleRuleItem 样本随机规则
type SampleRuleItem []interface{}
func (r SampleRuleItem) GetData(key string, value interface{}, params RuleCalcExtendParams) (interface{}, bool) {
if len(r) == 0 {
return value, false
}
return r[utils.TrueScopeRand(0, int64(len(r)))], true
}
// RandRuleItem 随机取值规则
type RandRuleItem struct {
Min float64 `json:"min"`
Max float64 `json:"max"`
Step float64 `json:"step"`
}
func (r RandRuleItem) GetData(key string, value interface{}, params RuleCalcExtendParams) (interface{}, bool) {
if r.Max < r.Min {
return value, false
}
if r.Max == r.Min {
return r.Max, true
}
if r.Step == 0 {
r.Step = 1
}
l := (r.Max - r.Min) / r.Step
if int64(l) < 1 {
l = 1
}
return r.Min + float64(utils.TrueScopeRand(0, int64(l)))*r.Step, true
}
// ComplexRuleItem 复杂规则
type ComplexRuleItem struct {
Index int64 //序列值,从1开始,自动生成
Execs int64 //执行次数
Names []string `json:"names"` //命中名称
Condition string `json:"condition"` //执行条件
Expression string `json:"expression"` //计算公式
}
func (r *ComplexRuleItem) GetData(key string, value interface{}, params RuleCalcExtendParams) (interface{}, bool) {
if !r.inNames(key) {
return value, false
}
if !r.inCondition(key) {
return value, false
}
return r.calcExpression(key, value, params)
//return value, true
}
func (r *ComplexRuleItem) inNames(key string) bool {
for _, v := range r.Names {
if v == key {
return true
}
}
return false
}
func (r *ComplexRuleItem) inCondition(key string) bool {
if r.Condition == "" {
return true
}
expression, err := govaluate.NewEvaluableExpressionWithFunctions(r.Condition, ruleFunctions)
if err != nil {
return false
}
parameters := NewRuleExpressionParameters()
parameters["key"] = key
result, err := expression.Evaluate(parameters)
if err != nil {
return false
}
t, _ := result.(bool)
return t
}
// calcExpression 计算公式
//
// key:当前选择key
// value: 默认值
// params: 额外计算参数
func (r *ComplexRuleItem) calcExpression(key string, value interface{}, params RuleCalcExtendParams) (interface{}, bool) {
defer func() {
if err := recover(); err != nil {
fmt.Println(r.Expression, "err", err)
panic(err)
}
}()
defer func() {
r.Execs++
}()
if r.Expression == "" {
return value, false
}
dynamic := DynamicRuleFunctions(&MockRuntimeInfo{
Execs: r.Execs,
})
expression, err := govaluate.NewEvaluableExpressionWithFunctions(r.Expression, dynamic)
if err != nil {
fmt.Println("Expression=", r.Expression, "err", err)
return value, false
}
parameters := NewRuleExpressionParameters()
parameters["key"] = key
result, err := expression.Evaluate(MergeCalcExtendParams(parameters, params))
if err != nil {
fmt.Println(r.Expression, "err", err)
return value, false
}
return result, true
}
func NewRuleExpressionParameters() map[string]interface{} {
parameters := make(map[string]interface{}, 8)
parameters["timestamp"] = time.Now().Unix()
parameters["timestamp_mill"] = time.Now().UnixMilli() //毫秒级时间戳
parameters["timestamp_nano"] = time.Now().UnixNano() //纳秒级时间戳
parameters["year"] = time.Now().Year()
parameters["month"] = time.Now().Month()
parameters["day"] = time.Now().Day()
parameters["hour"] = time.Now().Hour()
parameters["minute"] = time.Now().Minute()
parameters["second"] = time.Now().Second()
return parameters
}
var ruleFunctions = map[string]govaluate.ExpressionFunction{
"Sample": _rfs.Sample,
"Timestamp": _rfs.Timestamp,
"Float64": _rfs.Float64,
"Int64": _rfs.Int64,
"Rand": _rfs.Rand,
"RandSeed": _rfs.RandSeed,
"KeyInt": _rfs.KeyInt64,
"KeyFloat": _rfs.KeyFloat64,
}
// MockRuntimeInfo 运行时信息
type MockRuntimeInfo struct {
Execs int64 //执行次数
}
func DynamicRuleFunctions(m *MockRuntimeInfo) map[string]govaluate.ExpressionFunction {
if m == nil {
return ruleFunctions
}
dynamicFunctions := map[string]govaluate.ExpressionFunction{}
for k, v := range ruleFunctions {
dynamicFunctions[k] = v
}
//从file自动获取value,根据当前执行次数
dynamicFunctions["FileValues"] = func(arguments ...interface{}) (interface{}, error) {
if len(arguments) == 1 {
arguments = append(arguments, m.Execs)
}
return _rfs.ValueFromFile(arguments[0], arguments[1])
}
dynamicFunctions["FileFloatValues"] = func(arguments ...interface{}) (interface{}, error) {
if len(arguments) == 1 {
arguments = append(arguments, m.Execs)
}
return _rfs.FloatValueFromFile(arguments[0], arguments[1])
}
dynamicFunctions["FileIntValues"] = func(arguments ...interface{}) (interface{}, error) {
if len(arguments) == 1 {
arguments = append(arguments, m.Execs)
}
return _rfs.IntValueFromFile(arguments[0], arguments[1])
}
dynamicFunctions["FileStrValues"] = func(arguments ...interface{}) (interface{}, error) {
if len(arguments) == 1 {
arguments = append(arguments, m.Execs)
}
return _rfs.ValueFromFile(arguments[0], arguments[1])
}
return dynamicFunctions
}
var _rfs RuleFunctions
type RuleFunctions struct {
}
func (rfs RuleFunctions) Sample(args ...interface{}) (interface{}, error) {
//随机取样本值
return args[utils.TrueScopeRand(0, int64(len(args)))], nil
}
// Timestamp 返回当前秒级时间戳,args子项含义一次为:offset(可不填)
func (rfs RuleFunctions) Timestamp(args ...interface{}) (interface{}, error) {
var offset int64
if len(args) > 0 {
offset = cast.ToInt64(args[0])
}
return int64(time.Now().Unix() + offset), nil
}
// Float64 转为float64
func (rfs RuleFunctions) Float64(args ...interface{}) (interface{}, error) {
return utils.Interface2Float64(args[0]), nil
}
// Int64 转为int64
func (rfs RuleFunctions) Int64(args ...interface{}) (interface{}, error) {
return utils.Interface2Int64(args[0]), nil
}
// Rand 完全随机值, args子项含义依次为: min,max,step
func (rfs RuleFunctions) Rand(args ...interface{}) (interface{}, error) {
//随机范围取值
if len(args) != 3 {
return nil, fmt.Errorf("rand params invalid")
}
min := args[0].(float64)
max := args[1].(float64)
step := args[2].(float64)
l := (max - min) / step
return min + step*float64(utils.TrueScopeRand(0, int64(l))), nil
}
// RandSeed 根据种子进行随机数, args子项含义依次为: min,max,step,seed
func (rfs RuleFunctions) RandSeed(args ...interface{}) (interface{}, error) {
//随机范围取值(指定种子)
if len(args) != 4 {
return nil, fmt.Errorf("rand params invalid")
}
min := args[0].(float64)
max := args[1].(float64)
step := args[2].(float64)
l := (max - min) / step
seed, _ := strconv.ParseFloat(fmt.Sprint(args[3]), 64)
r := rand.New(rand.NewSource(int64(seed)))
return min + step*float64(r.Int63n(int64(l)+1)), nil
}
func (rfs RuleFunctions) KeyInt64(args ...interface{}) (interface{}, error) {
//随机范围取值(指定种子)
if len(args) == 0 {
return nil, fmt.Errorf("key params invalid")
}
s := strconv.QuoteToASCII(fmt.Sprintf("%x", args[0]))
var number int64
for _, v := range s {
number += int64(v)
}
return number, nil
}
func (rfs RuleFunctions) KeyFloat64(args ...interface{}) (interface{}, error) {
//随机范围取值(指定种子)
if len(args) == 0 {
return nil, fmt.Errorf("key params invalid")
}
s := strconv.QuoteToASCII(fmt.Sprintf("%x", args[0]))
var number float64
for _, v := range s {
number += float64(v)
}
return number, nil
}
func (rfs RuleFunctions) FloatValueFromFile(args ...interface{}) (interface{}, error) {
value, err := rfs.ValueFromFile(args...)
if err != nil {
return int64(0), err
}
return utils.Interface2Float64(value), nil
}
func (rfs RuleFunctions) IntValueFromFile(args ...interface{}) (interface{}, error) {
value, err := rfs.ValueFromFile(args...)
if err != nil {
return int64(0), err
}
return utils.Interface2Int64(value), nil
}
func (rfs RuleFunctions) ValueFromFile(args ...interface{}) (interface{}, error) {
if len(args) == 0 {
return nil, fmt.Errorf("key params invalid")
}
file := args[0].(string)
index := time.Now().Unix()
if len(args) > 1 {
index = cast.ToInt64(args[1])
}
//fmt.Println(file)
bytes, err := os.ReadFile(file)
if err != nil {
return nil, err
}
s := string(bytes)
s = strings.TrimLeft(s, "\n")
s = strings.TrimRight(s, "\n")
rows := strings.Split(s, "\n")
if len(rows) == 0 {
return "", nil
}
value := rows[index%int64(len(rows))] //按index取对应值
return value, nil
}
func TestMockDataRule() MockDataRule {
return NewMockDataRule(`
{
"whiteList": [
],
"sample_rules": {
"RunState": ["中间","最上面"],
"ChargeState": ["充电","放电"],
"sample1": ["num1","num2","num3"],
"sample2": [],
"sample11.state": ["正常","告警","离线"]
},
"rand_rules": {
"RackCellsTemp": {"max": 30, "min": 29, "step": 0.1},
"sample3": {"max": 10, "min": 1, "step": 0.001}
},
"complex_rules":[
{"names":["sample4","sample5"],"condition":"","expression":"1"},
{"names":["sample6","sample5"],"condition":"","expression":"'1'"},
{"names":["sample7"],"condition":"second%2==0","expression":"'xxxx'"},
{"names":["sample8"],"condition":"","expression":"Rand(0,10,0.1)+timestamp"},
{"names":["sample9"],"condition":"","expression":"Rand(0,10,0.1)+Float64(Timestamp())"},
{"names":["sample10"],"condition":"","expression":"Rand(0,10,0.1)+Rand(0,10,0.1)"},
{"names":["sample11.time"],"condition":"","expression":"Rand(0,10,0.1)+Rand(0,10,0.1)"},
{"names":["sample11.temp"],"condition":"","expression":"Rand(0,10,0.1)+Rand(0,10,0.1)"},
{"names":["sample11.cell_temp"],"condition":"","expression":"Rand(0,10,0.1)+Rand(0,10,0.1)"},
{"names":["rack_rechargeable_cap"],"condition":"hour<=12","expression":"(12-hour)*40+20+second*1+Rand(1,10,1)"},
{"names":["rack_rechargeable_cap"],"condition":"hour>12","expression":"500-(hour-12)*40-second*1+Rand(1,10,1)"},
{"names":["rack_dischargeable_cap"],"condition":"hour<=12","expression":"500-(12-hour)*40-second*1+Rand(1,10,1)"},
{"names":["rack_dischargeable_cap"],"condition":"hour>12","expression":"(hour-12)*40+20+second*1+Rand(1,10,1)"},
{"names":["tdy_AC_discharged_cap"],"condition":"","expression":"hour>12?(hour-12)*RandSeed(0.8,0.9,0.01,day):0"},
{"names":["tdy_AC_charged_cap"],"condition":"","expression":"hour>12?0:(12-hour)*RandSeed(0.8,0.9,0.01,day)"},
{"names":["fwd_active_total_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)"},
{"names":["fwd_actives_sharp_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)*0.1"},
{"names":["fwd_active_peak_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)*0.3"},
{"names":["fwd_active_flat_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)*0.4"},
{"names":["fwd_active_valley_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)*0.2"},
{"names":["fwd_active_abyss_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)*0"},
{"names":["bw_reactive_total_cap"],"condition":"","expression":"RandSeed(500,900,0.01,day)"},
{"names":["bw_reactive_sharp_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)*0.1"},
{"names":["bw_reactive_peak_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)*0.3"},
{"names":["bw_reactive_flat_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)*0.4"},
{"names":["bw_reactive_valley_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)*0.2"},
{"names":["bw_active_abyss_cap"],"condition":"","expression":"RandSeed(500,800,0.01,day)*0"},
{"names":["tdy_AC_discharged_cap"],"condition":"","expression":"hour>12?(hour-12)**RandSeed(0.8,0.9,0.01,day):0"},
{"names":["tdy_AC_charged_cap"],"condition":"","expression":"hour>12?0:(12-hour)*RandSeed(0.8,0.9,0.01,day)"},
{"names":["total_AC_apparent_power"],"condition":"","expression":"timestamp-1717121200+RandSeed(10,100,1,hour)"},
{"names":["total_AC_power_factor"],"condition":"","expression":"timestamp-1717121200+RandSeed(20,120,1,hour)"},
{"names":["DC_power"],"condition":"","expression":"RandSeed(0.7,2,0.01,timestamp/101)"},
{"names":["sample12"],"condition":"","expression":"ValueFile('C:/Users/w00995/Desktop/projects/yunjian-iot/demo-mock/iot-service/src/data/jobs/data_rules/dps/pcs_DC_voltage.csv')"},
{"names":["sample13"],"condition":"","expression":"ValueFileAuto('C:/Users/w00995/Desktop/projects/yunjian-iot/demo-mock/iot-service/src/data/jobs/data_rules/dps/pcs_DC_voltage.csv')"}
]
}
`)
}
func NewMockDeviceTree(s string) MockDeviceTreeConfig {
var d MockDeviceTreeConfig
json.Unmarshal([]byte(s), &d)
d.devicesMap = map[string]*MockDeviceTreeNode{}
if d.ProductTypeCtr == nil {
d.ProductTypeCtr = map[string]MockDeviceCtrOption{}
}
d.Devices.FillEmpty()
d.Devices.Lookup(func(parent, node *MockDeviceTreeNode, i int) {
if tmp, exist := d.ProductTypeCtr[node.ProductType]; exist {
node.MockDeviceCtrOption = MergeMockDeviceCtrOption(node.MockDeviceCtrOption, tmp)
}
d.devicesMap[node.DeviceId] = node
})
return d
}
// MockDeviceTreeConfig 设备数配置
type MockDeviceTreeConfig struct {
Devices MockDeviceTree `json:"devices"`
ProductTypeCtr map[string]MockDeviceCtrOption `json:"product_type_ctr"` //产品类型对应控制内容
devicesMap map[string]*MockDeviceTreeNode //辅助生成的仿真数据节点,key={deviceId},value=节点信息和配置
}
func (c MockDeviceTreeConfig) GetDeviceNode(deviceId string) (*MockDeviceTreeNode, bool) {
tmp, exist := c.devicesMap[deviceId]
return tmp, exist
}
// MockDeviceTree 模拟仿真用设备树信息
type MockDeviceTree []*MockDeviceTreeNode
type MockDeviceTreeNode struct {
DeviceId string `json:"device_id"` //设备id
DeviceName string `json:"device_name"` //设备名称
ProductType string `json:"product_type"` //设备类型
MockDeviceCtrOption //控制可选项
Children MockDeviceTree `json:"children"` // 子设备
}
// FillEmpty 遍历树
func (tree MockDeviceTree) FillEmpty() {
for k, v := range tree {
if v.DeviceId == "" {
tree[k].DeviceId = "fill_s_" + utils.RandLetterFigureCode(8)
}
if v.DeviceName == "" {
tree[k].DeviceName = "fill_dev_" + v.ProductType + utils.RandLetterCode(6)
}
tree[k].Children.FillEmpty()
}
}
// Find 找节点
func (tree MockDeviceTree) Find(id string) *MockDeviceTreeNode {
var find *MockDeviceTreeNode
tree.Lookup(func(parent *MockDeviceTreeNode, node *MockDeviceTreeNode, i int) {
if find == nil && node.DeviceId == id {
find = node
}
})
return find
}
// Lookup 遍历树,f(parent,current,level)
func (tree MockDeviceTree) Lookup(f func(*MockDeviceTreeNode, *MockDeviceTreeNode, int)) {
var lookupNodes func(parent *MockDeviceTreeNode, list MockDeviceTree, level int)
lookupNodes = func(parent *MockDeviceTreeNode, list MockDeviceTree, level int) {
for _, v := range list {
f(parent, v, level)
lookupNodes(v, v.Children, level+1)
}
}
lookupNodes(nil, tree, 1)
}
type MockDeviceCtrOption struct {
ThingModel string `json:"thing_model"` //指定物模型,空则取全局配置
DataRule string `json:"data_rule"` //指定数据规则,空则取全局配置
ReportProperty bool `json:"report_property"` //是否上报属性
ReportEvent bool `json:"report_event"` //是否上报事件
ReportOnline bool `json:"report_online"` //是否上报在线状态
}
func MergeMockDeviceCtrOption(p1, p2 MockDeviceCtrOption) MockDeviceCtrOption {
if p1.ThingModel == "" {
p1.ThingModel = p2.ThingModel
}
if p1.DataRule == "" {
p1.DataRule = p2.DataRule
}
if p2.ReportProperty {
p1.ReportProperty = p2.ReportProperty
}
if p2.ReportEvent {
p1.ReportEvent = p2.ReportEvent
}
if p2.ReportOnline {
p1.ReportOnline = p2.ReportOnline
}
return p1
}
func TestMockDeviceTree() MockDeviceTreeConfig {
return NewMockDeviceTree(`
{
"devices": [
{
"device_id": "s_XM1SKU4WQ",
"device_name": "总变压器",
"product_type": "sys_transformer",
"children": [
{
"device_id": "s_I3DU1QU",
"device_name": "变压器节点-1",
"product_type": "sys_transformer",
"children": [
{
"device_id": "s_MX1NDI2IQ",
"device_name": "储能并网点-1",
"product_type": "sys_energy_point",
"children": [
{
"device_id": "s_AX32XI1Q",
"device_name": "堆-1",
"product_type": "sys_batterystack",
"children": [
{
"device_id": "s_UI2DKQ3U",
"device_name": "电表-1",
"product_type": "sys_meter"
},
{
"device_id": "s_XS2UQ903U",
"device_name": "空调-1",
"product_type": "sys_aircond"
},
{
"device_id": "s_OM3X1YQ",
"device_name": "推动环设备",
"product_type": "sys_node"
},
{
"device_id": "s_ZK2SI4DD",
"device_name": "直流汇聚点",
"product_type": "sys_node",
"children": [
{
"device_id": "s_LMS2IU3YQ",
"device_name": "簇-1",
"product_type": "sys_batterycluster",
"children": [
{
"device_id": "s_AX2KS4UW",
"device_name": "PCS",
"product_type": "sys_pcs"
},
{
"device_id": "s_SQ2IO3W",
"device_name": "BMS",
"product_type": "sys_bms"
},
{
"device_id": "s_CA2OS1X",
"device_name": "簇动环设备",
"product_type": "sys_node"
}
]
},
{
"device_id": "s_WI1QIS3O",
"device_name": "簇-2",
"product_type": "sys_batterycluster",
"children": [
{
"device_id": "s_PO2WS31S",
"device_name": "PCS",
"product_type": "sys_pcs"
},
{
"device_id": "s_QO1SWE2E",
"device_name": "BMS",
"product_type": "sys_bms"
},
{
"device_id": "s_ZM2SII4Q",
"device_name": "簇动环设备",
"product_type": "sys_node"
}
]
},
{
"device_id": "s_QY1OD33J",
"device_name": "簇-3",
"product_type": "sys_batterycluster",
"children": [
{
"device_id": "s_CJ1QIO89LA",
"device_name": "PCS",
"product_type": "sys_pcs"
},
{
"device_id": "s_OMI2UY3Q",
"device_name": "BMS",
"product_type": "sys_bms"
},
{
"device_id": "s_AW3UI1QO",
"device_name": "簇动环设备",
"product_type": "sys_node"
}
]
},
{
"device_id": "s_JX9US28KQ",
"device_name": "消防-1",
"product_type": "sys_fes",
"children": []
}
]
},
{
"device_id": "s_XM8LQ01P",
"device_name": "总PCS-1",
"product_type": "sys_pcs",
"children": []
},
{
"device_id": "s_M91JS1KW2",
"device_name": "总PCS-2",
"product_type": "sys_pcs",
"children": []
}
]
},
{
"device_id": "s_ER2IW1QQ1",
"device_name": "堆-2",
"product_type": "sys_batterystack",
"children": [
{
"device_id": "s_NSQ2OS9OW",
"device_name": "空调-1",
"product_type": "sys_aircond"
},
{
"device_id": "s_XU3S99OQ",
"device_name": "消防-1",
"product_type": "sys_fes",
"children": []
},
{
"device_id": "s_0NBS2YU1Q",
"device_name": "簇-1",
"product_type": "sys_batterycluster",
"children": [
{
"device_id": "s_2IDU2Q2XX",
"device_name": "BMS-1",
"product_type": "sys_bms"
}
]
},
{
"device_id": "s_HUW1UI27Q",
"device_name": "簇-2",
"product_type": "sys_batterycluster",
"children": [
{
"device_id": "s_O1MI8UU2S",
"device_name": "BMS-2",
"product_type": "sys_bms"
}
]
},
{
"device_id": "s_A2M0X9MK",
"device_name": "簇-3",
"product_type": "sys_batterycluster",
"children": [
{
"device_id": "s_SO1WO23Q",
"device_name": "BMS-3",
"product_type": "sys_bms"
}
]
}
]
}
]
},
{
"device_id": "s_UW1MQM2KS",
"device_name": "电表",
"product_type": "sys_meter"
}
]
},
{
"device_id": "s_PSI4SQ05",
"device_name": "变压器节点-2",
"product_type": "sys_transformer",
"children": [
{
"device_id": "s_UQ2KJ7WK",
"device_name": "储能并网点-2",
"product_type": "sys_energy_point"
}
]
},
{
"device_id": "s_YWD8IO8OQ",
"device_name": "变压器节点-3",
"product_type": "sys_transformer",
"children": [
{
"device_id": "s_YW2UQI31W",
"device_name": "储能并网点-3",
"product_type": "sys_energy_point"
}
]
},
{
"device_id": "s_SKD2IWQ1IO",
"device_name": "电表",
"product_type": "sys_meter"
}
]
}
],
"product_type_ctr": {
"sys_bms": {
"thing_model": "./data/jobs/thing_models/bms_model.json",
"data_rule": "./data/jobs/data_rules/bms_model.json"
},
"sys_transformer": {
"thing_model": "./data/jobs/thing_models/bms_model.json",
"data_rule": "./data/jobs/data_rules/bms_model.json"
}
}
}
`)
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。