device_daily_running_record.go
9.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
package domain
import (
"fmt"
"gitlab.fjmaimaimai.com/allied-creation/allied-creation-manufacture/pkg/infrastructure/utils"
"time"
)
const DefaultTimeWindow = 1
const DefaultCollectionTimeSpan = 60 * 20
// 设备每日运行记录(汇总)
type DeviceDailyRunningRecord struct {
// 设备每日运行记录ID
DeviceDailyRunningRecordId int `json:"deviceDailyRunningRecordId"`
// 企业id
CompanyId int `json:"companyId"`
// 组织ID
OrgId int `json:"orgId"`
// 工作位置
WorkStation *WorkStation `json:"workStation"`
// 设备Id
DeviceId int `json:"deviceId"`
// 设备编号
DeviceCode string `json:"deviceCode"`
// 生产日期
ProductDate time.Time `json:"productDate"`
// 设备运行记录信息
DeviceRunningRecordInfo *DeviceRunningRecordInfo `json:"deviceRunningRecordInfo"`
// 创建时间
CreatedAt time.Time `json:"createdAt"`
// 更新时间
UpdatedAt time.Time `json:"updatedAt"`
// 删除时间
DeletedAt time.Time `json:"deletedAt"`
RedisKey string `json:"-"`
}
type DeviceDailyRunningRecordRepository interface {
Save(deviceDailyRunningRecord *DeviceDailyRunningRecord) (*DeviceDailyRunningRecord, error)
Remove(deviceDailyRunningRecord *DeviceDailyRunningRecord) (*DeviceDailyRunningRecord, error)
FindOne(queryOptions map[string]interface{}) (*DeviceDailyRunningRecord, error)
Find(queryOptions map[string]interface{}) (int64, []*DeviceDailyRunningRecord, error)
}
func (deviceDailyRunningRecord *DeviceDailyRunningRecord) Identify() interface{} {
if deviceDailyRunningRecord.DeviceDailyRunningRecordId == 0 {
return nil
}
return deviceDailyRunningRecord.DeviceDailyRunningRecordId
}
func (deviceDailyRunningRecord *DeviceDailyRunningRecord) Update(data map[string]interface{}) error {
return nil
}
func (deviceDailyRunningRecord *DeviceDailyRunningRecord) AddDeviceRunningData(data *DeviceRunningData) bool {
t := data.CollectionTime
deviceDailyRunningRecord.DeviceRunningRecordInfo.AddDeviceRunningData(t, data)
now := time.Now().Unix()
ts := t.Local().Unix()
deviceDailyRunningRecord.UpdatedAt = time.Now()
if ts > (now-DefaultCollectionTimeSpan) && ts < (now+DefaultCollectionTimeSpan) {
deviceDailyRunningRecord.UpdatedAt = t
}
if ok, _ := data.Valid(); !ok {
return false
}
return true
}
func (deviceDailyRunningRecord *DeviceDailyRunningRecord) String() string {
return fmt.Sprintf("记录ID:%v 工段:%v 设备:%v 数量:%v(%v)",
deviceDailyRunningRecord.DeviceDailyRunningRecordId,
deviceDailyRunningRecord.WorkStation.SectionName,
deviceDailyRunningRecord.DeviceCode,
deviceDailyRunningRecord.DeviceRunningRecordInfo.Count,
deviceDailyRunningRecord.DeviceRunningRecordInfo.ProductDate,
)
}
// 设备运行记录信息
type DeviceRunningRecordInfo struct {
// 日期
ProductDate string `json:"productDate"`
// 设备类型
DeviceType string `json:"deviceType"`
// 当前状态
// bit0: 运行、停止
// bit1: 正常、故障
CurrentStatus int `json:"currentStatus"`
// 设备OEE = tu * pu * qu
OEE float64 `json:"oee"`
// 时间利用率 TimeUtilization 运行时间 / (当前时间-当日零时)
TimeUtilization float64 `json:"tu"`
// 性能利用率 PerformanceUtilization
// 1. 当前设备实际产出数量/理论数量(理论数量=60*60*12/标准工时)
// 2. 没有数量100%
PerformanceUtilization float64 `json:"pu"`
// 合格率 QualificationUtilization 设备提交的二级品是kg 、 机器上报的是串
// 1.按工段的合格率
// 2.默认100%
QualificationUtilization float64 `json:"qu"`
// 运行时长 单位:分钟
UpTime float64 `json:"upTime"`
// 生成数量
Count int `json:"count"`
// 设备温度 单位:摄氏度 (前端温度、温度1)
Temp1 float64 `json:"temp"`
// 设备温度 单位:摄氏度 (后断温度、温度2)
Temp2 float64 `json:"temp1"`
// 时间点
//TimeLine []string `json:"timeLine"`
// 时间点对应的设备状态 按小时 1
TimeLineDeviceStatus map[string]*HourDeviceStatus `json:"timeLineDeviceStatus"`
// 批次数据
// 生产计划ID
ProductPlanId int `json:"productPlanId,omitempty"`
// 设备名称
DeviceName string `json:"deviceName"`
// 组织名称
OrgName string `json:"orgName"`
// 额外数据
// 单位数据 比如:1串/0.1kg weight = count * unitQuantity
UnitQuantity float64 `json:"unitQuantity"`
}
func NewDeviceRunningRecordInfo() *DeviceRunningRecordInfo {
return &DeviceRunningRecordInfo{
TimeLineDeviceStatus: make(map[string]*HourDeviceStatus),
}
}
func (d *DeviceRunningRecordInfo) AddDeviceRunningData(t time.Time, data *DeviceRunningData) {
if len(d.DeviceType) == 0 {
d.DeviceType = data.DeviceType
}
d.CurrentStatus = data.StartupStatus | (data.ComStatus << 1) | (data.Alarm << 2)
d.ResetUpTime()
d.Count += data.Count
//d.Temp = data.FrontTemp
d.Temp1 = data.Temp1
d.Temp2 = data.Temp2
d.AddTimeLineDeviceStatus(t, data)
//d.OEE (time.Now().Sub(utils.GetZeroTime(time.Now())).Minutes())
//d.TimeUtilization = utils.Round((d.UpTime*100)/(24*60), 2)
//d.PerformanceUtilization
//d.QualificationUtilization
}
// 添加新的设备状态
func (d *DeviceRunningRecordInfo) AddTimeLineDeviceStatus(t time.Time, data *DeviceRunningData) {
if t.IsZero() {
return
}
key := fmt.Sprintf("%v", t.Local().Hour())
//log.Logger.Debug(fmt.Sprintf("time:%v hour:%v", t, key))
var v *HourDeviceStatus
var ok bool
if v, ok = d.TimeLineDeviceStatus[key]; !ok {
v = NewHourDeviceStatus()
d.TimeLineDeviceStatus[key] = v
}
v.UpdateUp(t, data.StartupStatus)
v.UpdateCom(t, data.ComStatus)
v.UpdateAlarm(t, data.Alarm)
}
// 重置运行时长
func (d *DeviceRunningRecordInfo) ResetUpTime() float64 {
var upTime float64
for _, v := range d.TimeLineDeviceStatus {
t := v.CountTime(v.Up)
upTime += t.Minutes()
}
d.UpTime = upTime
return upTime
}
// 重置设备运行OEE
func (d *DeviceRunningRecordInfo) ResetOEE(pu, qu float64) float64 {
d.TimeUtilization = utils.Round((d.UpTime*100)/(24*60), 1)
d.PerformanceUtilization = pu
d.QualificationUtilization = qu
d.OEE = utils.Round((d.TimeUtilization*d.PerformanceUtilization*d.QualificationUtilization)/10000, 1)
return d.OEE
}
// 详细的小时设备数据
// 0:00 1
// 0:59 2
// 23:00 1
// 23:59 2
// [{"min":1,"time":"00:01","status":1}]
func (d *DeviceRunningRecordInfo) HourDeviceStatusDetail(endTime int) map[string]interface{} {
on := make([][]int, 0)
off := make([][]int, 0)
err := make([][]int, 0)
var begin, end int = 0, 0
/*
旧: 1.故障: 1 0 \ 0 0
2.正常: 1 1
3.停机:0 1
新: 只要未开机,就是待机中状态
1.故障: 1 0 0 / 1 0 1 / 1 1 1 / 1/5/7
2.正常: 1 1 0 3
3.停机:0 0 0 / 0 1 0 / 0 0 1 / 0 1 1 0/2/4/6
*/
var status = 1
// 添加数据
addToStatus := func(s []int, status int) {
switch status {
case 1:
err = append(err, s)
break
case 2:
on = append(on, s)
break
case 3:
if len(s) == 2 && s[1]-s[0] <= 5 {
last := len(on) - 1
if last >= 0 && len(on[last]) == 2 {
if on[last][1]+1 == s[0] { // [56,79] [80,80] => [56,80]
on[last][1] = s[1]
break
}
}
on = append(on, s)
break
}
off = append(off, s)
break
}
}
// 计算当前状态
computeStatus := func(up, com int, index int) int {
var val = 0
if up&index > 0 {
val |= 1
}
if com&index > 0 {
val |= 2
}
if val == 1 || val == 5 || val == 7 {
return 1 //故障
}
if val == 3 {
return 2 //正常
}
if val == 0 || val == 2 || val == 4 || val == 6 {
return 3 //停机
}
return 3 // 停机
}
for i := 0; i < 24; i++ {
var index = 1
var hds *HourDeviceStatus
var ok bool
if hds, ok = d.TimeLineDeviceStatus[fmt.Sprintf("%v", i)]; !ok {
hds = &HourDeviceStatus{Window: DefaultTimeWindow}
}
if end >= endTime {
break
}
if i == 0 {
status = computeStatus(hds.Up, hds.Com, index) // 状态初始化
}
if hds.Up == 0 && hds.Com == 0 && (end+60/hds.Window) < endTime {
end += 60 / hds.Window
continue
}
for j := 1; j < 60; j++ {
curStatus := computeStatus(hds.Up, hds.Com, index)
if curStatus == status {
end += 1
} else {
addToStatus([]int{begin, end}, status)
status = curStatus
begin = end + 1
end = begin
}
if end >= endTime {
break
}
index = index << 1
}
}
addToStatus([]int{begin, end}, status)
return map[string]interface{}{
"on": on,
"off": off,
"err": err,
}
}
// 单个小时内的设备状态
type HourDeviceStatus struct {
// 时间窗口 1-60 代表时间段范围
// 例如: w=1 则标识下面的状态按1分钟记录一次状态
// up 启动 bit0-bit59的位用来存启动状态 1:启动 0:关闭
// com通讯 bit0-bit59的位用来存通讯状态 1:正常 0:故障
// 如果 w=5 表示按5分钟记录一次状态 使用到 bit0-bit11
Window int `json:"w"`
// 启动
// bit0:1 代表
Up int `json:"up"`
// 通讯
Com int `json:"com"`
// 报警
Alarm int `json:"alarm"`
}
// 更新启动状态
func (d *HourDeviceStatus) UpdateUp(t time.Time, up int) {
m := t.Local().Minute()
bit := 1 << (m / d.Window)
if up&1 == 0 {
return
}
if d.Up&bit > 0 {
return
}
d.Up |= bit
return
}
// 更新通讯状态
func (d *HourDeviceStatus) UpdateCom(t time.Time, c int) {
m := t.Minute()
bit := 1 << (m / d.Window)
if c&1 == 0 {
return
}
if d.Com&bit > 0 {
return
}
d.Com |= bit
return
}
// 更新报警状态
func (d *HourDeviceStatus) UpdateAlarm(t time.Time, c int) {
m := t.Minute()
bit := 1 << (m / d.Window)
if c&1 == 0 {
return
}
if d.Alarm&bit > 0 {
return
}
d.Alarm |= bit
return
}
// 计算状态持续的时间
func (d *HourDeviceStatus) CountTime(v int) time.Duration {
l := 60 / d.Window
count := 0
index := 1
for i := 0; i < l; i++ {
if index > v {
break
}
if index&v > 0 {
count++
}
index <<= 1
}
return time.Duration(d.Window*count) * time.Minute
}
func NewHourDeviceStatus() *HourDeviceStatus {
return &HourDeviceStatus{
Window: DefaultTimeWindow,
Up: 0,
Com: 0,
Alarm: 0,
}
}