Ambient temperature, cabin temperature, the temperature of the gearbox, the percentage of wind speed pattern will change over time.
Fan abnormality occurs in the power generation process, the fault location and the time of failure occurrence. Collect information about the exception in favor of people anomaly analysis, and exception handling.
整理思路:
场景部分:
这里把 3D 当做背景嵌套在 2D 场景中。
这样在初始化图纸的时候,直接反序列化 2D 图纸即可。
事件部分:
2D 图纸中有很多的按钮,通过它们来控制 3D 中的一些动画。
实现思路是在反序列化图纸的时候把 2D、3D 的 模型和视图对象挂载到 window 上,这样在不同的场景中都可以获取到相应的数据模型。
环境部分:
风速、风向、变桨角度这些会在 2D、3D 中所表现,所以可以把他们放到数据池里面,方便管理。
具体代码实现:
场景搭建:上面说了,我们把 3D 当做背景嵌套在 2D 中,所以只需要序列化 2D 即可,里面需要进行背景判断的部分代码。
相关伪代码:
graphView.deserialize('displays/demo/风力发电机/风力发电机结构查看.json', function (json, dm, gv, datas) {
if (json.title) document.title = json.title if (json.a['json.background']) { var bgJSON = json.a['json.background'] if (bgJSON.indexOf('displays') === 0) { var bgGv = new ht.graph.GraphView() bgGv.deserialize(bgJSON) bgGv.addToDOM() graphView.addToDOM(bgGv.getView()) } else if (bgJSON.indexOf('scenes') === 0) { var bgG3d = new ht.graph3d.Graph3dView() bgG3d.deserialize(bgJSON) bgG3d.addToDOM() graphView.addToDOM(bgG3d.getView()) } graphView.handleScroll = function () { } }
})
模拟风速:每隔30s,随机产生一个值,当做风速值。
相关伪代码:
// 模拟风速
mockWindSpeed() {
return 8 + Math.random() * 12
}
数据统计:每隔30s,随机变换。
效果:
相关伪代码:
// 指针和扇叶旋转的角度 进行变化
var oldPointerValue = pitchSystem.a('pointer') || 0
// 风机扇叶和变桨系统的旋转角度
var newRotateAngular = (this.windSpeed - 8) * 7.5 * translateAngularRadian.radian var addPointerValue = newRotateAngular - oldPointerValue var oldWindSpeedClip = environmentalData.a('windSpeedClip') || 0 var newWindSpeedClip = (this.windSpeed - 8) / 12 var addWindSpeedClip = newWindSpeedClip - oldWindSpeedClip var anim = { duration: 1e3, easing: (v) => { return v * v }, action: (v) => { var windSpeed = Number(this.windSpeed.toFixed(2)) var Max = Number(MaxValue.toFixed(2)) var average = Number(Aver.toFixed(2)) var windSpeedClip = oldWindSpeedClip + (addWindSpeedClip * v) // 设置发电参数随机数据 generator.a({ windSpeed }) // 设置环境监测随机数据 environmentalData.a({ windSpeed, windSpeedClip }) // 设置统计参数随机数据 statisticalParam.a({ average, Max, windSpeed }) // 设置变航系统的指针角度 pitchSystem.a('pointer', oldPointerValue + (addPointerValue * v)) } }
ht.Default.startAnim(anim)
这里涉及到角度和弧度的转换。1° = Math.PI / 180°,1rad = 180° / Math.PI,因为场景中使用的是弧度制,所以需要把随机出的角度值转换成弧度。
这里解释下代码,先获取到当前的值。然后在加上 随机值 - 当前值。比如当前值为 16,随机出的数值有两种情况,1:比当前值大。2:比当前值小。
如果比当前值大的话,比如18,那么就是这样 16 + (18 - 16) * v ( easing 函数运算后的值)
如果比当前值小的话,比如13,那么就是这样 16 + (13 - 16) * v ( easing 函数运算后的值)
这样当随机的时候,就会从当前值平滑的改变到目标值。
数据统计:每隔30s,监测当前风机的故障信息。
效果:
这里使用了 table.json 文件,通过修改 ht.dataSource 属性添加实时信息。
相关伪代码:
var checkInternals = () => {
/**
* 故障信息
* 变桨系统 主轴 偏航系统 齿轮箱 油冷装置 发电机 风冷装置
*/
var FailureStatus = { // 正常状态 status1: new Map([ [0, ['舱内温度正常,变桨角度正常。', 'i10']], [1, ['舱内温度正常,主轴转速正常。', 'i9']], [2, ['偏航系统精确。', 'i8']], [3, ['齿轮箱温度正常。', 'i1']], [4, ['油冷装置温控表正常。', 'i3']], [5, ['发电机功率正常。', 'i5']], [6, ['风冷装置正常。', 'i2']] ]), // 异常状态 status2: new Map([ [0, ['变桨角度异常。', 'i10']], [1, ['主轴转速偏高。', 'i9']], [2, ['偏航系统出现偏移。', 'i8']], [3, ['齿轮箱温度偏高。', 'i1']], [4, ['油冷装置内积尘过多。', 'i3']], [5, ['发电机电流过大。', 'i5']], [6, ['风冷装置散热不足。', 'i2']] ]), } // 返回设备正常的状况 status 1: 正常 2: 不正常 var mockQquipmentFailure = (status) => { var { rangeRandom } = common var index = rangeRandom(7) // 返回随机出来的设备情况 return FailureStatus[`status${status}`].get(index) } var info = randomInfo[0] var targetTag = randomInfo[1] this.tableArr = table.a('ht.dataSource') var currentTimeFormat = DateUtil.formatHourTime(new Date()) // 默认是正常 如果找到故障关键字的话 赋值为 异常 var status = 'normal' var time = 0 this.tableArr.push({ status, info, time: currentTimeFormat }) }
我们需要两个 Map 数组方便进行取值操作。一个是正常信息数组,一个是异常信息数组。利用随机值当做一个索引,然后取到相对应的状态信息,添加到 table 中。
如果当前的 status 为 normal,说明是正常信息,否则为异常信息。异常信息的话就可以通过 table.json 的渲染回调函数 "drawCell": function(g, text, rect, option) { } 来修改它的颜色,使其高亮。
偏航系统:风机转动的过程中,随着风的位置的不同,通过偏航系统改变方向。
效果:
相关伪代码:
/**
* 随机偏航系统
* @param { * }
*/
randomYawSystem() {
var { dm } = this var { d2d } = window var { rangeRandom } = common var poll = () => { // 随机数 30 - 50 var random = 30 + rangeRandom(20) var cabin = dm.getDataByTag('cabin') // 将角度度换算成弧度 然后乘以随机数 实现随机风向 var randomDegrees = translateAngularRadian.radian * random var defaultDegress = translateAngularRadian.radian * 180 ht.Default.startAnim({ duration: 1e3, action: (v) => { var oldValue = cabin.getRotationY() var newValue = randomDegrees var addValue = newValue - oldValue cabin.setRotationY(oldValue + addValue * v) } }) }
}
上面讲到过角度弧度转换,这里先将随机出的角度转换成弧度,然后赋值,进行旋转。
变桨系统:风速的变化影响风机扇叶的角度。
效果:
相关伪代码:
var old3Value = whiteShell3Line.getRotationX()
var old4Value = whiteShell4Line.getRotationX()
var old5Value = whiteShell5Line.getRotationX()
// 指针和扇叶旋转的角度 进行变化
var oldPointerValue = pitchSystem.a('pointer') || 0
// 风机扇叶和变桨系统的旋转角度 var newRotateAngular = (this.windSpeed - 8) * 7.5 * translateAngularRadian.radian var addPointerValue = newRotateAngular - oldPointerValue whiteShell3Line.setRotationX(old3Value + ((newRotateAngular - oldPointerValue))) whiteShell4Line.setRotationX(old4Value + ((newRotateAngular - oldPointerValue))) whiteShell5Line.setRotationX(old5Value + ((newRotateAngular - oldPointerValue)))
先获取到每个扇叶当前的 X 轴旋转值,再获取到需要旋转的角度值,进行赋值。
风机启停:风机的启动和停止
相关伪代码:
var fanWireframe = d3d.getDataByTag('fanWireframe')
fanWireframe.setRotationMode('zxy')
this.allAnimManage = new Map([['fanRotate', null]])
var fanRotating = (easeType) => {
anim = ht.Default.startAnim({
duration: 5e3,
easing: (t) => easeIn(t), action: (v) => { fanWireframe.setRotationZ(fanWireframe.getRotationZ() + speed) // 风机轮毂旋转 if (fanWireframe.getRotationZ() <= -6.28) { fanWireframe.setRotationZ(0) } // 风机扇叶 uv 偏移 for (let i = 1; i < 17; i++) { var node = d3d.getDataByTag(`q${i}`) node.s('shape3d.uv.offset', fanOffsetData(v)[i - 1]) } // 暂停命令 if (isStop) { stopFanRotate(fanWireframe.getRotationZ()) anim.pause() anim = null } }, finishFunc: () => { fanRotating(false) } }) this.allAnimManage.set('fanRotate', anim) }
因为可以启动和停止,那么我们就可以通过控制 ht.Default.startAnim() 的返回对象的 resume 和 pause 来达到效果。
所以我把风机旋转的动画添加到了全局对象中,方便进行调用。
setRotationMode('zxy') 方法是设置三维旋转模式,顺序是 z -> x -> y,先进行z轴旋转,再进行x轴旋转,最后进行y轴旋转。设置目的是为了避免坐标轴受外部旋转的影响。
风向: 根据风的角度,判断当前是什么位置的风。
效果:
相关伪代码:
// 判断风向
var windDirection = (rotate) => {
let direction
switch (true) { case rotate === 0: direction = '南' break case rotate === 90: direction = '东' break case rotate === 180: direction = '北' break case rotate === 240: direction = '西' break case rotate > 0 && rotate < 90: direction = '东南' break case rotate > 90 && rotate < 180: direction = '东北' break case rotate > 180 && rotate < 270: direction = '西北' break case rotate > 270 && rotate < 360: direction = '西南' break default: direction = '没有找到风向' } return direction } // 判断是哪个方向 var angular = randomDegrees * translateAngularRadian.angular var direction = windDirection(angular)
将罗盘的指针角度放到 switch 进行判断,如果找到对应的风向就返回。
总结
风力发电是一个工业互联网的典型例子,我们可以通过对风机模型或者监测数据进行分析,可以减轻我们工作复杂程度,帮助我们快速了解发电内部结构及发电功能。
HT 能做的东西远远不止于此,这需要我们丰富的想象力以及自身过硬的技术。我希望可以通过这篇文章向大家传递一种能量,让大家更有兴趣、迸发更多新鲜的想法,去做更多好玩的东西。