# SDK

---

### 安装（windows）

1. 获取孪生授权服务压缩包，并解压到本地文件夹，文件目录如下：
```text
  jre 运行环境
  tslauthor 授权服务
    logs 运行日志
    static 静态文件，包含模型相关的文件和配置信息
    application.yml 配置文件
    tslenrypt.exe 服务启动
```
2. 提供应用信息（应用名称、ip地址、mac地址、开始时间、结束时间），从特斯联获取授权license
3. 获取加密后的模型，放置 tslauthor/static/encry 文件夹中
4. 点击运行 tslenrypt.exe
5. 应用程序使用孪生SDK，连接孪生授权服务地址加载模型
6. 根据SDK弹出的授权页面（第一次或者license过期），输入license并保存
7. 结束


### 与 TacOS 建立连接

```ts
import Tacos from "@tslfe/dt-engine";

Tacos.connect({
  host: "",
  appid: "",
  token: ""
}).then((meta) => {
  // 建立连接后可获取 meta 实例对象
});
```

### 资源定位符格式说明

```
相对路径： ./
绝对路径： /
设备类型：device
空间类型：space
模式：mode
规则：regular
计划：scheduler
事件：event
状态：state
管道符： |

示例：
./yingli46/meeting01/[device=Light|Curtain]
./yingli46/[space=Office]
./yingli46/[mode=OnWork]
./yingli46/[regular=XXXX]
./yingli46/[scheduler=XXXX]
./yingli46/[event=XXXX]
./yingli46/[state=XXXX]

getDeivce('./yingli46/[device=Light|Curtain]')
or
// getDevice方法可以省略类型标识符: device
getDeivce('./yingli46/[Light|Curtain]')

getSpace('./yingli46/[Office|MeetingRoom]')
or
// getSpace方法可以省略类型标识符: space
getSpace('./yingli46/[Office|MeetingRoom]')

拓展：
// 获取英利46楼所有楼层的会议室
/yingli/yingli46/[space=MeetingRoom]
// 获取英利50楼所有楼层的会议室
/yingli/yingli50/[space=MeetingRoom]
// 获取英利所有楼层的会议室
/yingli/*/[space=MeetingRoom]

```

### 内置的全局特殊资源

```
环境: /env
时间属性：/env/[state=time]
其它属性：？
```

### 功能演示

```ts
import { Mode, Event } from '@tslfe/tacos-space'

// 进入指定的空间
await meta.enter('./yingli46')

//=====================1. 设备 分割线 =====================

// 获取当前空间下的所有窗帘
let curtainGroup = await meta.getDevice<"Curtain">('./[device=Curtain]');
------------------------------------------- 增加方位西向筛选控制窗帘
// 批量开窗帘
curtainGroup.open();
// 批量关窗帘
curtainGroup.close();
// 监听设备的事件
curtainGroup.addEventListener('event', (event)=>{
  // 设备离线
  // 设备异常
})
// 监听设备状态变化
let removeListener = curtainGroup.addEventListener('state', (event)=>{
  // 设备状态
})
// 移除设备状态监听器
removeListener();

//=====================2. 模式 分割线 =====================

// 获取当前空间下的所有办公区
let officeGroup = meta.getSpace<"Office">('./[type=Office]');
// 批量切换为上班模式
officeGroup.toggleMode(Mode.Office.OnWork);
// 批量切换为下班模式
officeGroup.toggleMode(Mode.Office.OffWork);

// 从办公区集合中查找指定的某一个办公区
let office = officeGroup.findById('officeId');
// 从当前空间中查找指定的办公区
let office = meta.getSpace('<officeId>');
// 从当前空间中根据资源定位符查询空间
let office = meta.getSpace('/yingli46/bangongqu01');

//=====================3. 计划 分割线 =====================

// 设置当前的日期和时间触发空间计划(见第 7 节测试)
tester.time.set('2022-09-11 09:00:00')

// 创建新的计划
meta.scheduler
  .create('scheduler name')
  .month(6,7,8,9)
  .week(1,2,3,4,5)
  .time(['09:00', '12:00'],['13:30','18:00'])
  .action({
    './[device=AirCondition]': {
      [AirCondition.Property.Status]: 1,
      [AirCondition.Property.Temperature]: 26,
    },
    './[device=Light]': {
      [Light.Property.Status]: 1
    }
  })
  .apply(['/yingli46/[space=Office]', '/yingli46/[space=MeetingRoom]']);

// 获取计划
let scheduler = meta.getSpace('/yingli46/[space=Office]').getScheduler('scheduler name');
// 当前计划应用到其它更多空间
scheduler.apply(['/yingli50/[space=Office]', '/yingli50/[space=MeetingRoom]']);
// 冻结计划
scheduler.disable();
// 启用计划
scheduler.enable();
// 删除计划
scheduler.remove();

//=====================4. 规则 分割线 =====================

// 修改空间属性触发空间规则(见第 7 节测试)
let space = meta.getSpace<"Office">('/yingli46/bangongqu01');
tester.space.set(space, {temperature: 40});
// 简化版本
tester.space.set('/yingli46/bangongqu01', {[Office.Property.Temperature]: 40});

// 创建规则
meta.regular
  .create('Temperature-Exceeded-Threshold-Summer')
  .if({
    and: {
      '/env/[state=time]': '>=09:00 && <=18:00',
      or: {
        './[state=temperature]': '>30',
        './[state=co2]': '>0.05'
      }
    }
  })
  .action({
    './[device=AirCondition]': {
      [AirCondition.Property.Status]: 1,
      [AirCondition.Property.FanFreq]: 1,
      [AirCondition.Property.Mode]: 2
    },
    './[mode=OnWork]': {},
  })
  .apply(['/yingli46/[space=Office]', '/yingli46/[space=MeetingRoom]']);

// 获取规则
let regular =meta.getSpace('/yingli46/[space=Office]').getRegular('regular name');
// 当前规则应用到其它更多空间
regular.apply(['/yingli50/[space=Office]', '/yingli50/[space=MeetingRoom]']);
// 冻结规则
regular.disable();
// 启用规则
regular.enable();
// 删除规则
regular.remove();

//=====================5. 指标 & 看板 分割线 ===============

let office = meta.getSpace('/yingli46/bangongqu01');
// 渲染空间的平均温度指标图表
let chart = office.chart(Chart.Temperature.Average, {
  positoin: [0, 0],
  size: [100, 100],
  horizontal: Board.Align.Left,
  vertical: Board.Align.Top
});
// 渲染
chart.render('el')
// 设置图表位置， eg: x=0, y=0
chart.position(0, 0);
// 重新设置水平、垂直方向都居中（处于屏幕中心位置）
chart.align({
  horizontal: Board.Align.Center,
  vertical: Board.Align.Center
});
// 设置图标大小,eg: 宽100 高150
chart.size(100, 150);
// 移除图表
chart.remove();

// 从当前空间中根据资源定位符查询空间
let office = meta.getSpace('/yingli46/bangongqu01'); --------------------------------------------------- dashboard 左右靠边对齐能否实现 ？
// 渲染办公区舒适度看板
let board = office.board(Board.Office.Comfortable, {
  positoin: [0, 0],
  size: [100, 100],
  horizontal: Board.Align.Left,
  vertical: Board.Align.Top
});
// 渲染
board.render('el');
// 重新设置看板位置， eg: x=0, y=0
board.position(0, 0);
// 重新设置水平、垂直方向都居中（处于屏幕中心位置）
board.align({
  horizontal: Board.Align.Center,
  vertical: Board.Align.Center
});
// 设置看板大小,eg: 宽100 高150
board.size(100, 150);
// 移除看板
board.remove();

//=====================6. 布局（三维） 分割线 =====================

// 获取灯模型
let lightModelGroup = meta.getModel('/yingli/46/device/Light');
// 批量修改灯的透明度为0.5
lightModelGroup.opacity = 0.5;
// 批量修改灯的颜色
lightModelGroup.color = '#ffffff';
// 批量修改灯的位置
lightModelGroup.position = new Vector(0,0,0);
// 批量修改灯的缩放
lightModelGroup.scale = new Vector(0,0,0);
// 旋转
lightModelGroup.rotation = new Vector(0,0,0);

// 从灯组中查找指定ID的灯
let lightModel = lightModelGroup.findById('lightId');
// 镜头飞到 lightModel位置
lightModel.foucs();
// 隐藏 lightModel
lightModel.hide();
// 显示 lightModel
lightModel.show();
// 显示 lightModel 的线框
lightModel.outline();
// 选中 lightModel
lightModel.select();

// 镜头飞到合适的位置观察这一组灯
meta.camera.setAutoRotate(true, 2, lightModelGroup);
// 停止观察
meta.camera.setAutoRotate(false);

// 镜头按照指定的物体顺序进行 travel ----------------------------------------------- 直接用 id 数组
meta.camera.travel([
  meta.getModel('<DoorId>'),
  meta.getModel('<lightId>'),
  meta.getModel('<windowId>'),
  meta.getModel('<windowId>'),
  meta.getModel('./[device=Curtain]')
], {loop: true, speed: Camera.Travel.Slow, mode: 'reverse' | 'loop'});
// 支持直接传ID
meta.camera.travel([
  '<DoorId>',
  '<lightId>',
  '<windowId>',
  '<windowId>'
], {loop: true, speed: Camera.Travel.Slow, mode: 'reverse' | 'loop'});
// 支持通过已经获取到的设备进行travel
let lightGroup = meta.getDevice<"Light">('./[Light]');
meta.camera.travel(lightGroup);
// 缩放
meta.camera.zoom(2)


// 图层过滤：只显示电动窗图层
meta.layer.filter('./[device=Widnow]');
// 清空所有图层
meta.layer.clear();
// 重置图层显示
meta.layer.reset();
// 监听视域发生变化
let closeViewChangeListener = meta.layer.onViewChange((list)=>{
  // 变化后的视域范围内的图层
})
// 关闭视域变化监听
closeViewChangeListener();

// 开启下雨情景
let raining = meta.environment.raining(Environment.Rain.Small);
// 停止下雨
meta.environment.clear(raining);

// 开启下雪情景
let snowing = meta.environment.snowing(Environment.Snowing.Small);
// 停止下雪
meta.environment.clear(snowing);

//=====================7. 测试 分割线 =====================

// 构造测试对象
let tester = meta.genTester(); ---------------------------------------------------- Test 未必来自 Meta
// 另一种实例化方式（需要在重新调动connect方法并设置参数）
let tester = await TacosTester.connect({host: '', token: '', appid: ''});

// 设置当前的日期和时间触发空间计划
tester.time.set('2022-09-11 90:00:00')

// 修改空间属性触发空间规则
let space = meta.getSpace('/yingli46/bangongqu01');
tester.space.set(space, {temperature: 40});
// 简化版本
tester.space.set('/yingli46/bangongqu01', {temperature: 40});

// 触发空间事件
let event = meta.getSpace('/yingli46/bangongqu01').getEvent('eventName')
tester.space.emit(event);
// 简化版本
tester.space.emit('/yingli46/bangongqu01/[event=FireAlarm]');

```



1. studio 标注(逃生路线、摄像头的配置、地板、墙)
2. ai