device_daily_running_record.go 8.9 KB
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"`
}

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()
	if ok, _ := data.Valid(); !ok {
		return false
	}
	ts := t.Local().Unix()
	if ts > (now-DefaultCollectionTimeSpan) && ts < (now+DefaultCollectionTimeSpan) {
		deviceDailyRunningRecord.UpdatedAt = t
		return true
	}
	deviceDailyRunningRecord.UpdatedAt = time.Now()
	return true
}

func (deviceDailyRunningRecord *DeviceDailyRunningRecord) String() string {
	return fmt.Sprintf("记录ID:%v 工段:%v 设备:%v",
		deviceDailyRunningRecord.DeviceDailyRunningRecordId,
		deviceDailyRunningRecord.WorkStation.SectionName,
		deviceDailyRunningRecord.DeviceCode,
	)
}

// 设备运行记录信息
type DeviceRunningRecordInfo struct {
	// 设备类型
	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 | (1 << data.ComStatus)
	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)
}

// 重置运行时长
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
	*/
	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:
			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 {
			return 1 //故障
		}
		if val == 3 {
			return 2 //正常
		}
		if val == 2 || val == 0 {
			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"`
}

// 更新启动状态
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) 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,
	}
}