diff --git a/sf_machine_connect/__manifest__.py b/sf_machine_connect/__manifest__.py index db7ae467..ac8aef64 100644 --- a/sf_machine_connect/__manifest__.py +++ b/sf_machine_connect/__manifest__.py @@ -30,6 +30,7 @@ 'views/machine_info_present.xml', 'views/delivery_record.xml', 'views/res_config_settings_views.xml', + 'views/maintenance_views.xml', ], 'assets': { diff --git a/sf_machine_connect/controllers/controllers.py b/sf_machine_connect/controllers/controllers.py index 2fe2e7a5..945b7a86 100644 --- a/sf_machine_connect/controllers/controllers.py +++ b/sf_machine_connect/controllers/controllers.py @@ -1,11 +1,45 @@ # -*- coding: utf-8 -*- +import re import ast import json import logging +from datetime import datetime from odoo import http from odoo.http import request +def convert_to_seconds(time_str): + # 修改正则表达式,使 H、M、S 部分可选 + + pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?" + match = re.match(pattern, time_str) + + if match: + # 提取各时间单位,如果某个单位缺失则默认设为0 + hours = int(match.group(1)) if match.group(1) else 0 + minutes = int(match.group(2)) if match.group(2) else 0 + seconds = int(match.group(3)) if match.group(3) else 0 + + # 计算总秒数 + total_seconds = hours * 3600 + minutes * 60 + seconds + if total_seconds == 0: + # return None + pattern = r"(?:(\d+)小时)?(?:(\d+)分钟)?(?:(\d+)秒)?" + match = re.match(pattern, time_str) + if match: + # 提取各时间单位,如果某个单位缺失则默认设为0 + hours = int(match.group(1)) if match.group(1) else 0 + minutes = int(match.group(2)) if match.group(2) else 0 + seconds = int(match.group(3)) if match.group(3) else 0 + + # 计算总秒数 + total_seconds = hours * 3600 + minutes * 60 + seconds + return total_seconds + else: + return None + return total_seconds + + class Sf_Dashboard_Connect(http.Controller): @http.route('/api/get_machine_datas/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False, @@ -18,6 +52,11 @@ class Sf_Dashboard_Connect(http.Controller): """ res = {'status': 1, 'message': '成功', 'data': []} logging.info('前端请求机床数据的参数为:%s' % kw) + + # 获取当前时间的时间戳 + current_timestamp = datetime.now().timestamp() + print(current_timestamp) + # tem_list = [ # "XT-GNJC-WZZX-X800-Y550-Z550-T24-A5-1", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-3", # "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-4", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-5", @@ -33,8 +72,21 @@ class Sf_Dashboard_Connect(http.Controller): machine_list = ast.literal_eval(kw['machine_list']) for item in machine_list: machine_data = equipment_obj.search([('code', '=', item)]) + + # 机床上线时间段 + first_online_duration = current_timestamp - int(machine_data.first_online_time.timestamp()) + + power_off_time = None + power_off_rate = None + if machine_data.machine_power_on_time: + power_off_time = first_online_duration - convert_to_seconds(machine_data.machine_power_on_time) + power_off_rate = round((power_off_time / first_online_duration), 3) + else: + power_off_time = False + power_off_rate = False if machine_data: res['data'].append({ + 'active': machine_data.status, 'id': machine_data.id, 'name': machine_data.name, 'brand': machine_data.type_id.name, @@ -92,6 +144,13 @@ class Sf_Dashboard_Connect(http.Controller): # 计算出来的数据 # 开动率:运行时间/通电时间 'run_rate': machine_data.run_rate, + # 关机时长:初次上线时间 - 通电时间 + 'power_off_time': power_off_time, + # 关机率:关机时长/初次上线时间 + 'power_off_rate': power_off_rate, + 'first_online_duration': first_online_duration, + # 停机时间:关机时间 - 运行时间 + # 停机时长:关机时间 - 初次上线时间 }) return json.JSONEncoder().encode(res) @@ -101,6 +160,95 @@ class Sf_Dashboard_Connect(http.Controller): res['message'] = '前端请求机床数据失败,原因:%s' % e return json.JSONEncoder().encode(res) + # @http.route('/api/logs/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False, + # cors="*") + # def logs_list(self, **kw): + # """ + # 拿到日志数据返回给大屏展示 + # :param kw: + # :return: + # """ + # res = {'status': 1, 'message': '成功', 'data': []} + # logging.info('前端请求日志数据的参数为:%s' % kw) + # + # try: + # # 获取请求的日志数据 + # logs_obj = request.env['maintenance.equipment.oee.log.detail'].sudo() + # # 获取请求的机床数据 + # machine_list = ast.literal_eval(kw['machine_list']) + # begin_time_str = kw['begin_time'].strip('"') + # end_time_str = kw['end_time'].strip('"') + # + # begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S') + # end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S') + # + # print('begin_time: %s' % begin_time) + # for item in machine_list: + # log_datas = logs_obj.search( + # [('equipment_code', '=', item), ('time', '>=', begin_time), ('time', '<=', end_time)]) + # print('log_datas: %s' % log_datas) + # for log_data in log_datas: + # res['data'].append({ + # 'equipment_code': log_data.equipment_code, + # 'time': log_data.time.strftime('%Y-%m-%d %H:%M:%S'), + # 'state': log_data.state + # + # }) + # + # return json.JSONEncoder().encode(res) + # + # except Exception as e: + # logging.info('前端请求日志数据失败,原因:%s' % e) + # res['status'] = -1 + # res['message'] = '前端请求日志数据失败,原因:%s' % e + # return json.JSONEncoder().encode(res) + + @http.route('/api/logs/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") + def logs_list(self, **kw): + """ + 拿到日志数据返回给大屏展示 + :param kw: + :return: + """ + res = {'status': 1, 'message': '成功', 'data': {}} + logging.info('前端请求日志数据的参数为:%s' % kw) + + try: + # 获取请求的日志数据 + logs_obj = request.env['maintenance.equipment.oee.log.detail'].sudo() + # 获取请求的机床数据 + machine_list = ast.literal_eval(kw['machine_list']) + begin_time_str = kw['begin_time'].strip('"') + end_time_str = kw['end_time'].strip('"') + + begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S') + end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S') + + print('begin_time: %s' % begin_time) + + for item in machine_list: + log_datas = logs_obj.search( + [('equipment_code', '=', item), ('time', '>=', begin_time), ('time', '<=', end_time)]) + print('log_datas: %s' % log_datas) + + # 将数据按照 equipment_code 进行分组 + if item not in res['data']: + res['data'][item] = [] + + for log_data in log_datas: + res['data'][item].append({ + 'time': log_data.time.strftime('%Y-%m-%d %H:%M:%S'), + 'state': log_data.state + }) + + return json.dumps(res) # 注意使用 json.dumps 而不是直接用 json.JSONEncoder().encode() + + except Exception as e: + logging.info('前端请求日志数据失败,原因:%s' % e) + res['status'] = -1 + res['message'] = '前端请求日志数据失败,原因:%s' % e + return json.dumps(res) + # 返回CNC机床列表 @http.route('/api/CNCList', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") diff --git a/sf_machine_connect/models/ftp_client.py b/sf_machine_connect/models/ftp_client.py index f2dcc826..1d56424f 100644 --- a/sf_machine_connect/models/ftp_client.py +++ b/sf_machine_connect/models/ftp_client.py @@ -121,6 +121,13 @@ class Machine_ftp(models.Model): """ _inherit = 'maintenance.equipment' + # 机床首次上线时间(默认取值2024年08月01日零点) + + def _get_default_online_time(self): + return datetime(2024, 1, 1, 0, 0, 0) + + first_online_time = fields.Datetime(string='首次上线时间', default=_get_default_online_time) + # workorder_ids = fields.One2many('mrp.workorder', 'machine_tool_id', string='工单') # # 机床配置项目 @@ -278,6 +285,26 @@ class Machine_ftp(models.Model): # # 开动率 run_rate = fields.Char('开动率', readonly=True) + # 同步CNC设备到oee + def sync_oee(self): + """ + 同步CNC设备到oee + :return: + """ + for record in self: + record.ensure_one() + cnc_oee_dict = { + 'equipment_id': record.id, + 'type_id': record.type_id.id, + 'machine_tool_picture': record.machine_tool_picture, + 'equipment_code': record.code, + 'function_type': record.function_type, + } + if self.env['maintenance.equipment.oee.logs'].search([('equipment_id', '=', record.id)]): + self.env['maintenance.equipment.oee.logs'].write(cnc_oee_dict) + else: + self.env['maintenance.equipment.oee.logs'].create(cnc_oee_dict) + class WorkCenterBarcode(models.Model): """ diff --git a/sf_machine_connect/views/machine_monitor.xml b/sf_machine_connect/views/machine_monitor.xml index c6964659..5a05778f 100644 --- a/sf_machine_connect/views/machine_monitor.xml +++ b/sf_machine_connect/views/machine_monitor.xml @@ -18,6 +18,7 @@ + diff --git a/sf_machine_connect/views/maintenance_views.xml b/sf_machine_connect/views/maintenance_views.xml new file mode 100644 index 00000000..82c5aff4 --- /dev/null +++ b/sf_machine_connect/views/maintenance_views.xml @@ -0,0 +1,17 @@ + + + + + sf.machine.hr.equipment.view.tree.inherit + maintenance.equipment + + + +
+
+
+
+
+ +
\ No newline at end of file diff --git a/sf_maintenance/models/sf_maintenance_oee.py b/sf_maintenance/models/sf_maintenance_oee.py index 41301410..66e0a855 100644 --- a/sf_maintenance/models/sf_maintenance_oee.py +++ b/sf_maintenance/models/sf_maintenance_oee.py @@ -41,29 +41,32 @@ class SfMaintenanceEquipmentOEELog(models.Model): _name = 'maintenance.equipment.oee.logs' _description = '设备运行日志' - equipment_id = fields.Many2one('maintenance.equipment', '机台号') - equipment_code = fields.Char('设备编码') + equipment_id = fields.Many2one('maintenance.equipment', '机台号', readonly='True') + equipment_code = fields.Char('设备编码', readonly='True') name = fields.Char('设备名称', readonly='True') + function_type = fields.Selection( + [("ZXJGZX", "钻铣加工中心"), ("CXJGZX", "车削加工中心"), ("FHJGZX", "复合加工中心")], + default="", string="功能类型") machine_tool_picture = fields.Binary('设备图片') - type_id = fields.Many2one('sf.machine_tool.type', '品牌型号') + type_id = fields.Many2one('sf.machine_tool.type', '品牌型号', reaonly='True') state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"), ("检修", "检修"), ("保养", "保养")], default="", string="实时状态") - online_time = fields.Char('开机时长') + online_time = fields.Char('开机时长', reaonly='True') - offline_time = fields.Char('关机时长') - offline_nums = fields.Integer('关机次数') + offline_time = fields.Char('关机时长', reaonly='True') + offline_nums = fields.Integer('关机次数', reaonly='True') # 待机时长 - idle_time = fields.Char('待机时长') + idle_time = fields.Char('待机时长', reaonly='True') # 待机率 - idle_rate = fields.Char('待机率') + idle_rate = fields.Char('待机率', reaonly='True') - work_time = fields.Char('加工时长') - work_rate = fields.Char('可用率') - fault_time = fields.Char('故障时长') - fault_rate = fields.Char('故障率') - fault_nums = fields.Integer('故障次数') + work_time = fields.Char('加工时长', reaonly='True') + work_rate = fields.Char('可用率', reaonly='True') + fault_time = fields.Char('故障时长', reaonly='True') + fault_rate = fields.Char('故障率', reaonly='True') + fault_nums = fields.Integer('故障次数', reaonly='True') detail_ids = fields.One2many('maintenance.equipment.oee.log.detail', 'log_id', string='日志详情') @@ -82,11 +85,13 @@ class SfMaintenanceEquipmentOEELogDetail(models.Model): _name = 'maintenance.equipment.oee.log.detail' _description = '设备运行日志详情' - sequence = fields.Integer('序号') + # sequence = fields.Integer('序号', related='id') time = fields.Datetime('时间') state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"), ("检修", "检修"), ("保养", "保养")], default="", string="事件/状态") production_id = fields.Many2one('mrp.production', '加工工单') log_id = fields.Many2one('maintenance.equipment.oee.logs', '日志') + # equipment_code = fields.Char('设备编码', related='log_id.equipment_code') + equipment_code = fields.Char('设备编码', readonly='True') diff --git a/sf_maintenance/views/maintenance_logs_views.xml b/sf_maintenance/views/maintenance_logs_views.xml index 0d172285..30f1c79c 100644 --- a/sf_maintenance/views/maintenance_logs_views.xml +++ b/sf_maintenance/views/maintenance_logs_views.xml @@ -159,6 +159,8 @@ + +
@@ -202,7 +204,7 @@ - + @@ -219,7 +221,7 @@ - + @@ -263,7 +265,7 @@ maintenance.equipment.oee.log.detail - + @@ -283,7 +285,7 @@ - +