commit 09d322378aab1468287ab7aebd8f3f7173d74bf3 Author: gitea Date: Thu Mar 20 18:13:49 2025 +0800 init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b40a6ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +venv/ +.idea/ +**/__pycache__/ +assets/files/examples/ \ No newline at end of file diff --git a/assets/files/projects/NB4h_R580_3G.zip b/assets/files/projects/NB4h_R580_3G.zip new file mode 100644 index 0000000..a7ee4ae Binary files /dev/null and b/assets/files/projects/NB4h_R580_3G.zip differ diff --git a/assets/files/projects/autotest.xml b/assets/files/projects/autotest.xml new file mode 100644 index 0000000..483b9af --- /dev/null +++ b/assets/files/projects/autotest.xml @@ -0,0 +1,842 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/files/projects/configs.xlsx b/assets/files/projects/configs.xlsx new file mode 100644 index 0000000..4520560 Binary files /dev/null and b/assets/files/projects/configs.xlsx differ diff --git a/assets/files/projects/fieldbus_device.json b/assets/files/projects/fieldbus_device.json new file mode 100644 index 0000000..e7de260 --- /dev/null +++ b/assets/files/projects/fieldbus_device.json @@ -0,0 +1,75 @@ +{ + "device_list": [ + { + "enable": true, + "endian": 1, + "extend_attr": { + "RTU": { + "serial_name": "" + }, + "TCP": { + "ip": "0.0.0.0", + "port": 502 + }, + "coils": { + "max_num": 16, + "start_addr": 0 + }, + "discrete_input": { + "max_num": 16, + "start_addr": 0 + }, + "hold_register": { + "max_num": 2000, + "start_addr": 40000 + }, + "input_register": { + "max_num": 2000, + "start_addr": 4000 + }, + "protol_type": "TCP", + "slaver_id": 1 + }, + "mode": "slaver", + "name": "autotest", + "type": "MODBUS" + } + ], + "support_types": [ + { + "device_type": "MODBUS", + "support_mode": { + "master": 10, + "slaver": 10 + } + }, + { + "device_type": "CCLINK", + "support_mode": { + "master": 0, + "slaver": 1 + } + }, + { + "device_type": "ETHERCAT", + "support_mode": { + "master": 0, + "slaver": 10 + } + }, + { + "device_type": "PROFINET", + "support_mode": { + "master": 0, + "slaver": 1 + } + }, + { + "device_type": "EtherNetIP", + "support_mode": { + "master": 0, + "slaver": 1 + } + } + ] +} diff --git a/assets/files/projects/registers.json b/assets/files/projects/registers.json new file mode 100644 index 0000000..2936126 --- /dev/null +++ b/assets/files/projects/registers.json @@ -0,0 +1,520 @@ +{ + "MODBUS": [ + { + "property": { + "device_name": "autotest", + "endian": 1 + }, + "regs": { + "rd": [ + { + "addr": 40000, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_clear_alarm", + "len": 1, + "name": "r_clear_alarm", + "retain": false, + "type": "bool" + }, + { + "addr": 40001, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_estop_reset", + "len": 1, + "name": "r_estop_reset", + "retain": false, + "type": "bool" + }, + { + "addr": 40002, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_estop_reset_clear_alarm", + "len": 1, + "name": "r_onekey_reset", + "retain": false, + "type": "bool" + }, + { + "addr": 40003, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_motor_off", + "len": 1, + "name": "r_motor_off", + "retain": false, + "type": "bool" + }, + { + "addr": 40004, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_motor_on", + "len": 1, + "name": "r_motor_on", + "retain": false, + "type": "bool" + }, + { + "addr": 40005, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_motoron_pptomain_start", + "len": 1, + "name": "r_onekey_start", + "retain": false, + "type": "bool" + }, + { + "addr": 40006, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_motoron_start", + "len": 1, + "name": "r_motoron_start", + "retain": false, + "type": "bool" + }, + { + "addr": 40007, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_pause_motoroff", + "len": 1, + "name": "r_pause_motoroff", + "retain": false, + "type": "bool" + }, + { + "addr": 40008, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_pptomain", + "len": 1, + "name": "r_pp2main", + "retain": false, + "type": "bool" + }, + { + "addr": 40009, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_program_start", + "len": 1, + "name": "r_prog_start", + "retain": false, + "type": "bool" + }, + { + "addr": 40010, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_program_stop", + "len": 1, + "name": "r_prog_stop", + "retain": false, + "type": "bool" + }, + { + "addr": 40011, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_reduced_mode", + "len": 1, + "name": "r_reduced_mode", + "retain": false, + "type": "bool" + }, + { + "addr": 40012, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_soft_estop", + "len": 1, + "name": "r_soft_estop", + "retain": false, + "type": "bool" + }, + { + "addr": 40013, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_switch_auto_motoron", + "len": 1, + "name": "r_auto_motoron", + "retain": false, + "type": "bool" + }, + { + "addr": 40014, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_switch_operation_auto", + "len": 1, + "name": "r_switch_auto", + "retain": false, + "type": "bool" + }, + { + "addr": 40015, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "ctrl_switch_operation_manu", + "len": 1, + "name": "r_switch_manual", + "retain": false, + "type": "bool" + }, + { + "addr": 40016, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "enable_safe_region01", + "len": 1, + "name": "r_safe_region01", + "retain": false, + "type": "bool" + }, + { + "addr": 40017, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "enable_safe_region02", + "len": 1, + "name": "r_safe_region02", + "retain": false, + "type": "bool" + }, + { + "addr": 40018, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "enable_safe_region03", + "len": 1, + "name": "r_safe_region03", + "retain": false, + "type": "bool" + }, + { + "addr": 40100, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "", + "len": 1, + "name": "act", + "retain": false, + "type": "bool" + } + ], + "rdwr": [ + { + "addr": 40500, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_alarm", + "len": 1, + "name": "w_alarm_state", + "retain": false, + "type": "bool" + }, + { + "addr": 40501, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_collision", + "len": 1, + "name": "w_clsn_alarm_stat", + "retain": false, + "type": "bool" + }, + { + "addr": 40502, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_collision_open", + "len": 1, + "name": "w_clsn_open_stat", + "retain": false, + "type": "bool" + }, + { + "addr": 40503, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_controller_is_running", + "len": 1, + "name": "w_controller_running", + "retain": false, + "type": "bool" + }, + { + "addr": 40504, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_encoder_low_battery", + "len": 1, + "name": "w_encoder_low", + "retain": false, + "type": "bool" + }, + { + "addr": 40505, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_estop", + "len": 1, + "name": "w_estop_stat", + "retain": false, + "type": "bool" + }, + { + "addr": 40506, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_motor", + "len": 1, + "name": "w_motor_stat", + "retain": false, + "type": "bool" + }, + { + "addr": 40507, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_operation_mode", + "len": 1, + "name": "w_operation_mode", + "retain": false, + "type": "bool" + }, + { + "addr": 40508, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_program", + "len": 1, + "name": "w_prog_stat", + "retain": false, + "type": "bool" + }, + { + "addr": 40509, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_program_not_run", + "len": 1, + "name": "w_prog_not_run", + "retain": false, + "type": "bool" + }, + { + "addr": 40510, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_program_reset", + "len": 1, + "name": "w_prog_reset", + "retain": false, + "type": "bool" + }, + { + "addr": 40511, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_reduced_mode", + "len": 1, + "name": "w_reduced_mode_stat", + "retain": false, + "type": "bool" + }, + { + "addr": 40512, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_robot_is_busy", + "len": 1, + "name": "w_robot_is_busy", + "retain": false, + "type": "bool" + }, + { + "addr": 40513, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_robot_moving", + "len": 1, + "name": "w_robot_moving", + "retain": false, + "type": "bool" + }, + { + "addr": 40514, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_safe_door", + "len": 1, + "name": "w_safe_door", + "retain": false, + "type": "bool" + }, + { + "addr": 40515, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_safe_region01", + "len": 1, + "name": "w_safe_region01", + "retain": false, + "type": "bool" + }, + { + "addr": 40516, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_safe_region02", + "len": 1, + "name": "w_safe_region02", + "retain": false, + "type": "bool" + }, + { + "addr": 40517, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_safe_region03", + "len": 1, + "name": "w_safe_region03", + "retain": false, + "type": "bool" + }, + { + "addr": 40518, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "sta_soft_estop", + "len": 1, + "name": "w_soft_estop_stat", + "retain": false, + "type": "bool" + }, + { + "addr": 40600, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "", + "len": 1, + "name": "ready_to_go", + "retain": false, + "type": "bool" + }, + { + "addr": 40601, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "", + "len": 1, + "name": "scenario_time", + "retain": false, + "type": "float" + }, + { + "addr": 40603, + "addr_1st": 0, + "addr_2nd": 0, + "bit_bias": 0, + "byte_bias": 0, + "function": "", + "len": 1, + "name": "capture_start", + "retain": false, + "type": "bool" + } + ] + } + } + ] +} diff --git a/assets/files/projects/registers.xml b/assets/files/projects/registers.xml new file mode 100644 index 0000000..7d71547 --- /dev/null +++ b/assets/files/projects/registers.xml @@ -0,0 +1,843 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/files/protocols/hmi/collision.get_params.json b/assets/files/protocols/hmi/collision.get_params.json new file mode 100644 index 0000000..62b0a45 --- /dev/null +++ b/assets/files/protocols/hmi/collision.get_params.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "safety", + "command": "collision.get_params" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/collision.set_params.json b/assets/files/protocols/hmi/collision.set_params.json new file mode 100644 index 0000000..813f7ca --- /dev/null +++ b/assets/files/protocols/hmi/collision.set_params.json @@ -0,0 +1,6 @@ +{ + "id": "xxxxxxxxxxx", + "module": "safety", + "command": "collision.set_params", + "data": null +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/collision.set_state.json b/assets/files/protocols/hmi/collision.set_state.json new file mode 100644 index 0000000..04bfbb5 --- /dev/null +++ b/assets/files/protocols/hmi/collision.set_state.json @@ -0,0 +1,7 @@ +{ + "module": "safety", + "command": "collision.set_state", + "data": { + "collision_state": false + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/controller.get_params.json b/assets/files/protocols/hmi/controller.get_params.json new file mode 100644 index 0000000..92dac53 --- /dev/null +++ b/assets/files/protocols/hmi/controller.get_params.json @@ -0,0 +1,5 @@ +{ + "id":"xxxxxxxxxxx", + "module":"system", + "command":"controller.get_params" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/controller.heart.json b/assets/files/protocols/hmi/controller.heart.json new file mode 100644 index 0000000..27300d6 --- /dev/null +++ b/assets/files/protocols/hmi/controller.heart.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "controller.heart" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/controller.reboot.json b/assets/files/protocols/hmi/controller.reboot.json new file mode 100644 index 0000000..a0ef6da --- /dev/null +++ b/assets/files/protocols/hmi/controller.reboot.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "controller.reboot", + "data": { + "arg": 6 + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/controller.set_params.json b/assets/files/protocols/hmi/controller.set_params.json new file mode 100644 index 0000000..3759298 --- /dev/null +++ b/assets/files/protocols/hmi/controller.set_params.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "controller.set_params", + "data": { + "time": "2020-02-28 15:28:30" + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/device.get_params.json b/assets/files/protocols/hmi/device.get_params.json new file mode 100644 index 0000000..62cd79d --- /dev/null +++ b/assets/files/protocols/hmi/device.get_params.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "device.get_params" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/diagnosis.get_params.json b/assets/files/protocols/hmi/diagnosis.get_params.json new file mode 100644 index 0000000..7e57457 --- /dev/null +++ b/assets/files/protocols/hmi/diagnosis.get_params.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "robot", + "command": "diagnosis.get_params", + "data": { + "version": "1.4.1" + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/diagnosis.open.json b/assets/files/protocols/hmi/diagnosis.open.json new file mode 100644 index 0000000..7fe21c2 --- /dev/null +++ b/assets/files/protocols/hmi/diagnosis.open.json @@ -0,0 +1,12 @@ +{ + "id": "xxxxxxxxxxx", + "module": "robot", + "command": "diagnosis.open", + "data": { + "open": false, + "display_open": false, + "overrun": false, + "turn_area": false, + "delay_motion": false + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/diagnosis.save.json b/assets/files/protocols/hmi/diagnosis.save.json new file mode 100644 index 0000000..6564248 --- /dev/null +++ b/assets/files/protocols/hmi/diagnosis.save.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "robot", + "command": "diagnosis.save", + "data": { + "save": true + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/diagnosis.set_params.json b/assets/files/protocols/hmi/diagnosis.set_params.json new file mode 100644 index 0000000..0bb13cc --- /dev/null +++ b/assets/files/protocols/hmi/diagnosis.set_params.json @@ -0,0 +1,10 @@ +{ + "id": "xxxxxxxxxxx", + "module": "robot", + "command": "diagnosis.set_params", + "data": { + "display_pdo_params": [], + "frequency": 50, + "version": "1.4.1" + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/drag.get_params.json b/assets/files/protocols/hmi/drag.get_params.json new file mode 100644 index 0000000..9c56d28 --- /dev/null +++ b/assets/files/protocols/hmi/drag.get_params.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "dynamic", + "command": "drag.get_params" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/drag.set_params.json b/assets/files/protocols/hmi/drag.set_params.json new file mode 100644 index 0000000..52b0b6f --- /dev/null +++ b/assets/files/protocols/hmi/drag.set_params.json @@ -0,0 +1,10 @@ +{ + "id": "xxxxxxxxxxx", + "module": "dynamic", + "command": "drag.set_params", + "data": { + "enable": true, + "space": 0, + "type": 0 + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/fieldbus_device.get_params.json b/assets/files/protocols/hmi/fieldbus_device.get_params.json new file mode 100644 index 0000000..6eca806 --- /dev/null +++ b/assets/files/protocols/hmi/fieldbus_device.get_params.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "fieldbus", + "command": "fieldbus_device.get_params" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/fieldbus_device.load_cfg.json b/assets/files/protocols/hmi/fieldbus_device.load_cfg.json new file mode 100644 index 0000000..79e1731 --- /dev/null +++ b/assets/files/protocols/hmi/fieldbus_device.load_cfg.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "fieldbus", + "command": "fieldbus_device.load_cfg", + "data": { + "file_name": "fieldbus_device.json" + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/fieldbus_device.set_params.json b/assets/files/protocols/hmi/fieldbus_device.set_params.json new file mode 100644 index 0000000..d6828f6 --- /dev/null +++ b/assets/files/protocols/hmi/fieldbus_device.set_params.json @@ -0,0 +1,9 @@ +{ + "id": "xxxxxxxxxxx", + "module": "fieldbus", + "command": "fieldbus_device.set_params", + "data": { + "device_name": "modbus_1", + "enable": true + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/io_device.load_cfg.json b/assets/files/protocols/hmi/io_device.load_cfg.json new file mode 100644 index 0000000..790f75c --- /dev/null +++ b/assets/files/protocols/hmi/io_device.load_cfg.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "io", + "command": "io_device.load_cfg" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/jog.get_params.json b/assets/files/protocols/hmi/jog.get_params.json new file mode 100644 index 0000000..db98a71 --- /dev/null +++ b/assets/files/protocols/hmi/jog.get_params.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "jog.get_params" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/jog.set_params.json b/assets/files/protocols/hmi/jog.set_params.json new file mode 100644 index 0000000..6c89d14 --- /dev/null +++ b/assets/files/protocols/hmi/jog.set_params.json @@ -0,0 +1,10 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "jog.set_params", + "data": { + "step": 1000, + "override": 0.2, + "space": 5 + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/jog.start.json b/assets/files/protocols/hmi/jog.start.json new file mode 100644 index 0000000..afe54cc --- /dev/null +++ b/assets/files/protocols/hmi/jog.start.json @@ -0,0 +1,10 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "jog.start", + "data": { + "index": 0, + "direction": true, + "is_ext": false + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/log_code.data.code_list.json b/assets/files/protocols/hmi/log_code.data.code_list.json new file mode 100644 index 0000000..c6f1ee2 --- /dev/null +++ b/assets/files/protocols/hmi/log_code.data.code_list.json @@ -0,0 +1,8 @@ +{ + "id": "log_code.data.code_list", + "s": { + "log_code.data": { + "code_list": [] + } + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/log_code.data.json b/assets/files/protocols/hmi/log_code.data.json new file mode 100644 index 0000000..021b678 --- /dev/null +++ b/assets/files/protocols/hmi/log_code.data.json @@ -0,0 +1,5 @@ +{ + "g": { + "log_code.data": "null" + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/modbus.get_params.json b/assets/files/protocols/hmi/modbus.get_params.json new file mode 100644 index 0000000..8bdde30 --- /dev/null +++ b/assets/files/protocols/hmi/modbus.get_params.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "fieldbus", + "command": "modbus.get_params" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/modbus.get_values.json b/assets/files/protocols/hmi/modbus.get_values.json new file mode 100644 index 0000000..d356362 --- /dev/null +++ b/assets/files/protocols/hmi/modbus.get_values.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "fieldbus", + "command": "modbus.get_values", + "data": { + "mode": "all" + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/modbus.load_cfg.json b/assets/files/protocols/hmi/modbus.load_cfg.json new file mode 100644 index 0000000..95d5e69 --- /dev/null +++ b/assets/files/protocols/hmi/modbus.load_cfg.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "fieldbus", + "command": "modbus.load_cfg", + "data": { + "file" : "registers.json" + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/modbus.set_params.json b/assets/files/protocols/hmi/modbus.set_params.json new file mode 100644 index 0000000..46a651e --- /dev/null +++ b/assets/files/protocols/hmi/modbus.set_params.json @@ -0,0 +1,12 @@ +{ + "id": "xxxxxxxxxxx", + "module": "fieldbus", + "command": "modbus.set_params", + "data": { + "enable_slave": true, + "ip": "192.168.0.160", + "port": 502, + "slave_id": 0, + "enable_master": false + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.get_joint_pos.json b/assets/files/protocols/hmi/move.get_joint_pos.json new file mode 100644 index 0000000..9754405 --- /dev/null +++ b/assets/files/protocols/hmi/move.get_joint_pos.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "move.get_joint_pos" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.get_monitor_cfg.json b/assets/files/protocols/hmi/move.get_monitor_cfg.json new file mode 100644 index 0000000..75fb033 --- /dev/null +++ b/assets/files/protocols/hmi/move.get_monitor_cfg.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "move.get_monitor_cfg" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.get_params.json b/assets/files/protocols/hmi/move.get_params.json new file mode 100644 index 0000000..bfe06e2 --- /dev/null +++ b/assets/files/protocols/hmi/move.get_params.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "move.get_params" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.get_pos.json b/assets/files/protocols/hmi/move.get_pos.json new file mode 100644 index 0000000..9124631 --- /dev/null +++ b/assets/files/protocols/hmi/move.get_pos.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "move.get_pos" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.get_quickstop_distance.json b/assets/files/protocols/hmi/move.get_quickstop_distance.json new file mode 100644 index 0000000..ac5f0b2 --- /dev/null +++ b/assets/files/protocols/hmi/move.get_quickstop_distance.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "move.get_quickstop_distance" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.get_quickturn_pos.json b/assets/files/protocols/hmi/move.get_quickturn_pos.json new file mode 100644 index 0000000..74ecae0 --- /dev/null +++ b/assets/files/protocols/hmi/move.get_quickturn_pos.json @@ -0,0 +1,5 @@ +{ + "id" : "xxxxxxxxx", + "module": "motion", + "command": "move.get_quickturn_pos" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.quick_turn.json b/assets/files/protocols/hmi/move.quick_turn.json new file mode 100644 index 0000000..3a72afc --- /dev/null +++ b/assets/files/protocols/hmi/move.quick_turn.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "move.quick_turn", + "data": { + "name":"home" + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.set_monitor_cfg.json b/assets/files/protocols/hmi/move.set_monitor_cfg.json new file mode 100644 index 0000000..68c3f35 --- /dev/null +++ b/assets/files/protocols/hmi/move.set_monitor_cfg.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "move.set_monitor_cfg", + "data": { + "ref_coordinate": 1 + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.set_params.json b/assets/files/protocols/hmi/move.set_params.json new file mode 100644 index 0000000..8788a6c --- /dev/null +++ b/assets/files/protocols/hmi/move.set_params.json @@ -0,0 +1,17 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "move.set_params", + "data": { + "MOTION": { + "JOINT_MAX_SPEED": [1.0,0.0,0.0,0.0,0.0,0.0], + "JOINT_MAX_ACC": [1.0,0.0,0.0,0.0,0.0,0.0], + "JOINT_MAX_JERK": [1.0,0.0,0.0], + "TCP_MAX_SPEED": 500, + "DEFAULT_ACC_PARAMS": [0.3,1.0], + "VEL_SMOOTH_FACTOR": 3.33, + "ACC_RAMPTIME_JOG": 0.01 + } + } + +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.set_quickstop_distance.json b/assets/files/protocols/hmi/move.set_quickstop_distance.json new file mode 100644 index 0000000..000d5f5 --- /dev/null +++ b/assets/files/protocols/hmi/move.set_quickstop_distance.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "move.set_quickstop_distance", + "data":{ + "distance": 2 + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.set_quickturn_pos.json b/assets/files/protocols/hmi/move.set_quickturn_pos.json new file mode 100644 index 0000000..164e442 --- /dev/null +++ b/assets/files/protocols/hmi/move.set_quickturn_pos.json @@ -0,0 +1,15 @@ +{ + "id" : "xxxxxxxxx", + "module": "motion", + "command": "move.set_quickturn_pos", + "data": { + "enable_home": false, + "enable_drag": false, + "enable_transport": false, + "joint_home": [0.0,0.0,0.0,0.0,0.0,0.0,0.0], + "joint_drag": [0.0,0.0,0.0,0.0,0.0,0.0,0.0], + "joint_transport": [0.0,0.0,0.0,0.0,0.0,0.0,0.0], + "end_posture": 0, + "home_error_range":[0.0,0.0,0.0,0.0,0.0,0.0,0.0] + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/move.stop.json b/assets/files/protocols/hmi/move.stop.json new file mode 100644 index 0000000..17648f0 --- /dev/null +++ b/assets/files/protocols/hmi/move.stop.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "motion", + "command": "move.stop", + "data":{ + "stoptype": 0 + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/overview.get_autoload.json b/assets/files/protocols/hmi/overview.get_autoload.json new file mode 100644 index 0000000..49e3b79 --- /dev/null +++ b/assets/files/protocols/hmi/overview.get_autoload.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "project", + "command": "overview.get_autoload" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/overview.get_cur_prj.json b/assets/files/protocols/hmi/overview.get_cur_prj.json new file mode 100644 index 0000000..8e50c5a --- /dev/null +++ b/assets/files/protocols/hmi/overview.get_cur_prj.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "project", + "command": "overview.get_cur_prj" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/overview.reload.json b/assets/files/protocols/hmi/overview.reload.json new file mode 100644 index 0000000..a2485a2 --- /dev/null +++ b/assets/files/protocols/hmi/overview.reload.json @@ -0,0 +1,9 @@ +{ + "id": "xxxxxxxxxxx", + "module": "project", + "command": "overview.reload", + "data": { + "prj_path": "", + "tasks": [] + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/overview.set_autoload.json b/assets/files/protocols/hmi/overview.set_autoload.json new file mode 100644 index 0000000..791d4fb --- /dev/null +++ b/assets/files/protocols/hmi/overview.set_autoload.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "project", + "command": "overview.set_autoload", + "data": { + "autoload_prj_path": "" + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/register.set_value.json b/assets/files/protocols/hmi/register.set_value.json new file mode 100644 index 0000000..6b64af3 --- /dev/null +++ b/assets/files/protocols/hmi/register.set_value.json @@ -0,0 +1,11 @@ +{ + "id": "xxxxxxxxxxx", + "module": "fieldbus", + "command": "register.set_value", + "data": { + "name": "", + "type": "bool", + "bias": 0, + "value": 0 + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/rl_task.pp_to_main.json b/assets/files/protocols/hmi/rl_task.pp_to_main.json new file mode 100644 index 0000000..3e2247f --- /dev/null +++ b/assets/files/protocols/hmi/rl_task.pp_to_main.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "project", + "command": "rl_task.pp_to_main", + "data": { + "tasks": [] + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/rl_task.run.json b/assets/files/protocols/hmi/rl_task.run.json new file mode 100644 index 0000000..52e89a1 --- /dev/null +++ b/assets/files/protocols/hmi/rl_task.run.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "project", + "command": "rl_task.run", + "data": { + "tasks": [] + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/rl_task.set_run_params.json b/assets/files/protocols/hmi/rl_task.set_run_params.json new file mode 100644 index 0000000..8490383 --- /dev/null +++ b/assets/files/protocols/hmi/rl_task.set_run_params.json @@ -0,0 +1,9 @@ +{ + "id": "xxxxxxxxxxx", + "module": "project", + "command": "rl_task.set_run_params", + "data": { + "loop_mode": true, + "override": 1.0 + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/rl_task.stop.json b/assets/files/protocols/hmi/rl_task.stop.json new file mode 100644 index 0000000..03b0f6b --- /dev/null +++ b/assets/files/protocols/hmi/rl_task.stop.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "project", + "command": "rl_task.stop", + "data": { + "tasks": [] + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/safety.safety_area.overall_enable.json b/assets/files/protocols/hmi/safety.safety_area.overall_enable.json new file mode 100644 index 0000000..517e64b --- /dev/null +++ b/assets/files/protocols/hmi/safety.safety_area.overall_enable.json @@ -0,0 +1,7 @@ +{ + "c": { + "safety.safety_area.overall_enable": { + "enable": true + } + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/safety.safety_area.safety_area_enable.json b/assets/files/protocols/hmi/safety.safety_area.safety_area_enable.json new file mode 100644 index 0000000..505fe50 --- /dev/null +++ b/assets/files/protocols/hmi/safety.safety_area.safety_area_enable.json @@ -0,0 +1,8 @@ +{ + "c": { + "safety.safety_area.safety_area_enable": { + "id": 0, + "enable": true + } + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/safety.safety_area.set_param.json b/assets/files/protocols/hmi/safety.safety_area.set_param.json new file mode 100644 index 0000000..28b65da --- /dev/null +++ b/assets/files/protocols/hmi/safety.safety_area.set_param.json @@ -0,0 +1,5 @@ +{ + "c": { + "safety.safety_area.set_param": null + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/safety.safety_area.signal_enable.json b/assets/files/protocols/hmi/safety.safety_area.signal_enable.json new file mode 100644 index 0000000..8a1fbac --- /dev/null +++ b/assets/files/protocols/hmi/safety.safety_area.signal_enable.json @@ -0,0 +1,7 @@ +{ + "c": { + "safety.safety_area.signal_enable": { + "signal": true + } + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/safety_area_data.json b/assets/files/protocols/hmi/safety_area_data.json new file mode 100644 index 0000000..e9b982a --- /dev/null +++ b/assets/files/protocols/hmi/safety_area_data.json @@ -0,0 +1,5 @@ +{ + "g": { + "safety_area_data": null + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/servo.clear_alarm.json b/assets/files/protocols/hmi/servo.clear_alarm.json new file mode 100644 index 0000000..bc3b7f8 --- /dev/null +++ b/assets/files/protocols/hmi/servo.clear_alarm.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "robot", + "command": "servo.clear_alarm" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/socket.get_params.json b/assets/files/protocols/hmi/socket.get_params.json new file mode 100644 index 0000000..2971e6a --- /dev/null +++ b/assets/files/protocols/hmi/socket.get_params.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "network", + "command": "socket.get_params" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/socket.set_params.json b/assets/files/protocols/hmi/socket.set_params.json new file mode 100644 index 0000000..9635cae --- /dev/null +++ b/assets/files/protocols/hmi/socket.set_params.json @@ -0,0 +1,17 @@ +{ + "id": "xxxxxxxxxxx", + "module": "network", + "command": "socket.set_params", + "data": { + "enable": true, + "ip": "", + "name": "c1", + "port": "8080", + "suffix": "\r", + "type": 1, + "reconnect_flag": false, + "auto_connect": true, + "disconnection_triggering_behavior": 0, + "disconnection_detection_time": 10 + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/soft_limit.get_params.json b/assets/files/protocols/hmi/soft_limit.get_params.json new file mode 100644 index 0000000..6f1f222 --- /dev/null +++ b/assets/files/protocols/hmi/soft_limit.get_params.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "safety", + "command": "soft_limit.get_params" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/soft_limit.set_params.json b/assets/files/protocols/hmi/soft_limit.set_params.json new file mode 100644 index 0000000..a0a3844 --- /dev/null +++ b/assets/files/protocols/hmi/soft_limit.set_params.json @@ -0,0 +1,12 @@ +{ + "id": "xxxxxxxxxxx", + "module": "safety", + "command": "soft_limit.set_params", + "data": { + "enable": true, + "upper": [0,0,0,0,0,0,0], + "lower": [0,0,0,0,0,0,0], + "reduced_upper": [0,0,0,0,0,0,0], + "reduced_lower": [0,0,0,0,0,0,0] + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/state.get_state.json b/assets/files/protocols/hmi/state.get_state.json new file mode 100644 index 0000000..1a2b981 --- /dev/null +++ b/assets/files/protocols/hmi/state.get_state.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "state.get_state" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/state.get_tp_mode.json b/assets/files/protocols/hmi/state.get_tp_mode.json new file mode 100644 index 0000000..5b59ed0 --- /dev/null +++ b/assets/files/protocols/hmi/state.get_tp_mode.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "state.get_tp_mode" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/state.set_tp_mode.json b/assets/files/protocols/hmi/state.set_tp_mode.json new file mode 100644 index 0000000..608dd02 --- /dev/null +++ b/assets/files/protocols/hmi/state.set_tp_mode.json @@ -0,0 +1,8 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "state.set_tp_mode", + "data": { + "tp_mode": "with" + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/state.switch_auto.json b/assets/files/protocols/hmi/state.switch_auto.json new file mode 100644 index 0000000..0c0a872 --- /dev/null +++ b/assets/files/protocols/hmi/state.switch_auto.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "state.switch_auto" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/state.switch_manual.json b/assets/files/protocols/hmi/state.switch_manual.json new file mode 100644 index 0000000..0451d04 --- /dev/null +++ b/assets/files/protocols/hmi/state.switch_manual.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "state.switch_manual" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/state.switch_motor_off.json b/assets/files/protocols/hmi/state.switch_motor_off.json new file mode 100644 index 0000000..0618931 --- /dev/null +++ b/assets/files/protocols/hmi/state.switch_motor_off.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "state.switch_motor_off" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/state.switch_motor_on.json b/assets/files/protocols/hmi/state.switch_motor_on.json new file mode 100644 index 0000000..0bbcc72 --- /dev/null +++ b/assets/files/protocols/hmi/state.switch_motor_on.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "system", + "command": "state.switch_motor_on" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/system_io.query_configuration.json b/assets/files/protocols/hmi/system_io.query_configuration.json new file mode 100644 index 0000000..27d16bd --- /dev/null +++ b/assets/files/protocols/hmi/system_io.query_configuration.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "io", + "command": "system_io.query_configuration" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/system_io.query_event_cfg.json b/assets/files/protocols/hmi/system_io.query_event_cfg.json new file mode 100644 index 0000000..5af3217 --- /dev/null +++ b/assets/files/protocols/hmi/system_io.query_event_cfg.json @@ -0,0 +1,5 @@ +{ + "id": "xxxxxxxxxxx", + "module": "io", + "command": "system_io.query_event_cfg" +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/system_io.update_configuration.json b/assets/files/protocols/hmi/system_io.update_configuration.json new file mode 100644 index 0000000..aaeb41f --- /dev/null +++ b/assets/files/protocols/hmi/system_io.update_configuration.json @@ -0,0 +1,9 @@ +{ + "id": "xxxxxxxxxxx", + "module": "io", + "command": "system_io.update_configuration", + "data": { + "input_system_io": {}, + "output_system_io": {} + } +} \ No newline at end of file diff --git a/assets/files/protocols/hmi/原理/协议原理.xlsx b/assets/files/protocols/hmi/原理/协议原理.xlsx new file mode 100644 index 0000000..7f1dcbb Binary files /dev/null and b/assets/files/protocols/hmi/原理/协议原理.xlsx differ diff --git a/assets/files/protocols/hmi/原理/解包示例.txt b/assets/files/protocols/hmi/原理/解包示例.txt new file mode 100644 index 0000000..17029a8 --- /dev/null +++ b/assets/files/protocols/hmi/原理/解包示例.txt @@ -0,0 +1,56 @@ +b'\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340\x01o429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17\n\t\t\t]\n\t\t}\n\t],\n\t"module" : "robot"\n}\x04\x00\x00\x00-h\x02\x00{\n\t"command" : "diagnosis.result",\n\t"data" : \n\t[\n\t\t{\n\t\t\t"channel" : 0,\n\t\t\t"name" : "hw_joint_vel_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0' +b'.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 1,\n\t\t\t"name" : "hw_joint_vel_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t6.7411265092630715e-06,\n\t\t\t\t0.00049929277011941824,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0027270103772138884,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t0.00012044146029883355,\n\t\t\t\t-0.0022272681986605192,\n\t\t\t\t-0.0027270103772138884,\n\t\t\t\t0.0020686270214758614,\n\t\t\t\t-0.0027270103772138884,\n\t\t\t\t0.0020686270214758614,\n\t\t\t\t1.3482253018526145e-06,\n\t\t\t\t-0.002697798\x04\x008290070812,\n\t\t\t\t0.00050648330506263212,\n\t\t\t\t-0.0,\n\t\t\t\t0.0020753681479851243,\n\t\t\t\t2.8762139772855775e-05,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t3.5952674716069715e-05,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t0.0001496530085056402,\n\t\t\t\t0.0001276319952420475,\n\t\t\t\t-0.0027252127434780845,\n\t\t\t\t6.7411265092630715e-06,\n\t\t\t\t-0.0026977988290070812,\n\t\t\t\t8.5387602450665581e-06,\n\t\t\t\t0.0020686270214758614,\n\t\t\t\t0.00206862702147' +b'58614,\n\t\t\t\t-0.0,\n\t\t\t\t7.1905349432139438e-06,\n\t\t\t\t1.3482253018526145e-06,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t2.8762139772855775e-05,\n\t\t\t\t-0.0,\n\t\t\t\t6.7411265092630715e-06,\n\t\t\t\t-0.0,\n\t\t\t\t0.00049929277011941824,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 2,\n\t\t\t"name" : "hw_joint_vel_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t1.1984224905356572e-06,\n\t\t\t\t0.00010705907582118537,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0024236097500266104,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0024240092241901226,\n\t\t\x04\x00\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t0.0018387795746452099,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t0.00010705907582118537,\n\t\t\t\t-0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 3,\n\t\t\t"name" : "hw_joint_vel_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t' +b'-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.00021840051467521813,\n\t\t\t\t-0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 4,\n\t\t\t"name" : "hw_joint_vel_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t-0.0,\n\t\t\t\t-0.0\x04\x00,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n' +b'\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 5,\n\t\t\t"name" : "hw_joint_vel_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-\x04\x000.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 0,\n\t\t\t"name" : "device_servo_trq_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t' +b'\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 1,\n\t\t\t"name" : "device_servo_trq_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0\x04\x00.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 2,\n\t\t\t"name" : "device_servo_trq_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.' +b'0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 3,\n\t\t\t"name" : "device_servo_trq_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\x04\x00\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 4,\n\t\t\t"name" : "device_servo_trq_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n' +b'\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 5,\n\t\t\t"name" : "device_servo_trq_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.\x04\x000,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 0,\n\t\t\t"name" : "device_safety_estop",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n' +b'\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 1,\n\t\t\t"name" : "device_safety_estop",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1\x04\x00.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 2,\n\t\t\t"name" : "device_safety_estop",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.' +b'0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 3,\n\t\t\t"name" : "device_safety_estop",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t\x04\x001.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0,\n\t\t\t\t1.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 4,\n\t\t\t"name" : "device_safety_estop",\n\t\t\t"value" : ' +b'\n\t\t\t[\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 5,\n\t\t\t"name" : "device_safety_estop",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5\x04\x00.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429' +b'065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.55163404\x01n29065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17\n\t\t\t]\n\t\t}\n\t],\n\t"module" : "robot"\n}\x04\x00\x00\x00-\x8f\x02\x00{\n\t"command"' + + + +> - 封包解包顺序:帧长度二字节/包长度四字节/协议二字节/预留二字节,\x04\x00:\x00\x00\tR:\x02:\x00 +> - 帧长度和包长度没有必然关系,单帧的时候是帧长度减去包长度等于6,包长度指的是所有内容的长度 +> - HMI内部每次发送1024个字节,进行分包,有效内容长度规则是:第一帧 frm_value-6(帧大小减去包头长度),第二帧(包含)及之后的帧,帧大小即是数据长度 + + +================================================================================================== +单包多帧示例 +\x04\x00\x00\x00-h\x02\x00 +frm_value = int.from_bytes(b"\x04\x00") = 1024 | 每次发送的帧的报文总长度,包括包头(如果有),但不包括帧头 +pkg_value = int.from_bytes(b"\x00\x00-h") = 11624 | 包长度是不包括所有的帧和包报文头的,单包单帧的情况也是一样的 +pkg_size = 6 字节 +有效数据长度 = 10240 + 402 + 1004 - 20 - 2(首个帧头) = 11624 +有效数据长度+包报文头 = 11624 + 6 = 11630 +有效数据长度+包报文头+帧报文头 = 11624 + 6 + 22 = 11652 +首帧长度 1018=frm_value-pkg_size=1024-6:如下是第一帧和第二帧的拼接 +b'{\n\t"command" : "diagnosis.result",\n\t"data" : \n\t[\n\t\t{\n\t\t\t"channel" : 0,\n\t\t\t"name" : "hw_joint_vel_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 1,\n\t\t\t"name" : "hw_joint_vel_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t6.7411265092630715e-06,\n\t\t\t\t0.00049929277011941824,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0027270103772138884,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t0.00012044146029883355,\n\t\t\t\t-0.0022272681986605192,\n\t\t\t\t-0.0027270103772138884,\n\t\t\t\t0.0020686270214758614,\n\t\t\t\t-0.0027270103772138884,\n\t\t\t\t0.0020686270214758614,\n\t\t\t\t1.3482253018526145e-06,\n\t\t\t\t-0.002697798' +单帧长度 1024:如下是第二帧和第三帧的拼接 +b'8290070812,\n\t\t\t\t0.00050648330506263212,\n\t\t\t\t-0.0,\n\t\t\t\t0.0020753681479851243,\n\t\t\t\t2.8762139772855775e-05,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t3.5952674716069715e-05,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t0.0001496530085056402,\n\t\t\t\t0.0001276319952420475,\n\t\t\t\t-0.0027252127434780845,\n\t\t\t\t6.7411265092630715e-06,\n\t\t\t\t-0.0026977988290070812,\n\t\t\t\t8.5387602450665581e-06,\n\t\t\t\t0.0020686270214758614,\n\t\t\t\t0.0020686270214758614,\n\t\t\t\t-0.0,\n\t\t\t\t7.1905349432139438e-06,\n\t\t\t\t1.3482253018526145e-06,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t2.8762139772855775e-05,\n\t\t\t\t-0.0,\n\t\t\t\t6.7411265092630715e-06,\n\t\t\t\t-0.0,\n\t\t\t\t0.00049929277011941824,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"channel" : 2,\n\t\t\t"name" : "hw_joint_vel_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t1.1984224905356572e-06,\n\t\t\t\t0.00010705907582118537,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0024236097500266104,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0,\n\t\t\t\t-0.0024240092241901226,\n\t\t' + +--------------------------------------------------------------------------------------------------- +第一帧有效 - 402 +b'{\n\t"command" : "diagnosis.result",\n\t"data" : \n\t[\n\t\t{\n\t\t\t"channel" : 0,\n\t\t\t"name" : "hw_joint_vel_feedback",\n\t\t\t"value" : \n\t\t\t[\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0.0,\n\t\t\t\t0' +--------------------------------------------------------------------------------------------------- +中间有 10 帧,共计 10240 个字节,也有 10 个 \x04\x00,共计 20 个字节 +--------------------------------------------------------------------------------------------------- +最后一帧有效 - 1004 +b'065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.55163404\x01n29065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17,\n\t\t\t\t-5.5516340429065717e-17\n\t\t\t]\n\t\t}\n\t],\n\t"module" : "robot"\n}' +--------------------------------------------------------------------------------------------------- + +================================================================================================== +单包单帧示例 +\x00\\\x00\x00\x00V\x02\x00 +frm_value = int.from_bytes(b"\x00\\") = 92 | 每次发送的帧的报文总长度,包括包头(如果有),但不包括帧头 +pkg_value = int.from_bytes(b"\x00\x00\x00V") = 86 | 包长度是不包括所有的帧和包报文头的,单包单帧的情况也是一样的 +pkg_size = 6 +有效数据长度 = 86 +有效数据长度+包报文头 = 86 + 6 = 92 +有效数据长度+包报文头+帧报文头 = 86 + 6 +2 = 94 +帧长度 86=frm_value-pkg_size=92-6 + +b'\x00\\\x00\x00\x00V\x02\x00{\n\t"data" : \n\t{\n\t\t"name" : "xCore"\n\t},\n\t"id" : "controller.heart-1719734550.9790015"\n}' diff --git a/assets/files/version/file_version_info.txt b/assets/files/version/file_version_info.txt new file mode 100644 index 0000000..aa27a6e --- /dev/null +++ b/assets/files/version/file_version_info.txt @@ -0,0 +1,43 @@ +# UTF-8 +# +# For more details about fixed file info 'ffi' see: +# http://msdn.microsoft.com/en-us/library/ms646997.aspx +VSVersionInfo( + ffi=FixedFileInfo( + # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) + # Set not needed items to zero 0. + filevers=(0, 3, 1, 6), + prodvers=(0, 3, 1, 6), + # Contains a bitmask that specifies the valid bits 'flags'r + mask=0x3f, + # Contains a bitmask that specifies the Boolean attributes of the file. + flags=0x0, + # The operating system for which this file was designed. + # 0x4 - NT and there is no need to change it. + OS=0x4, + # The general type of file. + # 0x1 - the file is an application. + fileType=0x1, + # The function of the file. + # 0x0 - the function is not defined for this fileType + subtype=0x0, + # Creation date and time stamp. + date=(0, 0) + ), + kids=[ + StringFileInfo( + [ + StringTable( + '040904b0', + [StringStruct('CompanyName', 'Rokae - https://www.rokae.com/'), + StringStruct('FileDescription', 'All in one automatic toolbox'), + StringStruct('FileVersion', '0.3.1.6 (2025-03-21)'), + StringStruct('InternalName', 'AIO.exe'), + StringStruct('LegalCopyright', '© 2024-2025 Manford Fan'), + StringStruct('OriginalFilename', 'AIO.exe'), + StringStruct('ProductName', 'AIO'), + StringStruct('ProductVersion', '0.3.1.6 (2025-03-21)')]) + ]), + VarFileInfo([VarStruct('Translation', [1033, 1200])]) + ] +) diff --git a/assets/files/version/release_change.md b/assets/files/version/release_change.md new file mode 100644 index 0000000..8d7e904 --- /dev/null +++ b/assets/files/version/release_change.md @@ -0,0 +1,490 @@ +v0.0.1(2024/05/18) +Draft + +v0.0.2(2024/05/20) +1. 功能模块化,为后面其他功能奠定一个基本的框架 +2. 使用了多线程提高效率 +3. 优化了准备工作中的细节 +4. 运行初始化时自动删除 raw_data_dir 中的 .xlsx 文件 +5. 优化了输出格式 +6. 使用 pyinstaller 库进行代码冻结并调试成功 + +v0.0.3(2024/05/21) +1. just_open函数打开失败的信息中,添加文件名 +2. 删除global变量,函数全部通过传参实现 +3. configuration.xlsx配置文件增加AXIS常量,表示那个轴,取值为 j1/j2/j3/j4/j5/j6/j7 +4. [bugfix] 增加get_threshold_step函数,用来获取在计算row_start时合适的阈值和步长,主要是解决了二轴最差工况下,最大速度是个尖端的问题: + a. load100_speed100_reachxxx 二轴 threshold = 50 step = 20 + b. 其他 threshold = 50 step = 100 + 如上是一个比较保守的设定,因为设定的step比较小,找到点之后要往后延200最好 +5. 在find_row_start_dp函数中新增一个参数data_file,方便后续调试 + +v0.0.4(2024/05/22) +1. 重新标定了get_threshold_step函数,让处理更加准确 +2. 新定义了now_doing_msg函数,实时输出处理信息 +3. 修改了find_row_start和find_row_start_dp函数,增加的部分相同,处理数据的时候,先判断是否是空值,或者是0,此时可以加快步进 +4. 修改了just_open函数,不在做重试 + +v0.0.5(2024/05/23) +1. 完善了函数注释 +2. 调整了阈值和步长 +3. 删除了just_open函数,以及对应的win32com库(Thank GOD!终于可以不用这个库了) +4. 重写了获取开始点位的代码,直接使用speed来判断,而不用角度,所以find_row_start_dp以及copy_data_to_excel_file函数也被一并删除 +5. 修改了配置文件configs.xlsx的初始参数顺序及结构,使程序通用性更强 +6. 将initialazation中的预定义变量赋值调整到try...except...之外,更方便排查问题 +7. 修改结束时间的格式,精确到秒 + +v0.0.6(2024/05/23) +1. 为了调整多功能框架,aio.py文件将会作为入口程序存在,不实现具体功能,功能的实现将由具体的功能脚本实现,aio.py只负责条件调用 +2. 新增了自动化处理电流数据(电机/过载)的功能 + +v0.1.0(2024/05/29) +1. 修改为customtkinter图形化界面 +2. 支持工业机器人制动数据处理(理论上支持,测试数据有问题,待验证) +3. 删除configs.xlsx配置表格,直接在界面配置,新增layout.xlsx文件,存储customtkinter布局 +4. 电流尚未支持 + +v0.1.1(2024/05/30) +1. 增加版本检测功能 +2. 修改了无效数据下的动作 +3. textbox只在开始和结束时改变状态,而不是每次写入都更改 +4. 调整了brake的结构 +5. 重新在write2textbox中添加exitcode参数,并补齐相关逻辑和修改brake中的调用方式 +6. 修复参数检查无效的情况 +7. 屏蔽电流相关的功能 + +v0.1.2(2024/06/01) +1. 增加iso数据处理功能 +2. 重新修改了README.md +3. 单独将rokae拉出来,作为一个独立的repo进行维护,与scripts分离 + +v0.1.3(2024/06/01) +1. 完成电流处理的基本功能 +2. 修复了一些已知bugs + +v0.1.4(2024/06/06) +1. AV/RR支持小数 +2. 可处理电机电流单轴以及多轴数据,可根据需要进行参数设定处理不同轴的数据 +3. 界面初始位置修改,以及删除所有entry的长度设定,因为设定无效 +4. 修改了layout.xlsx布局,增加了duration/trqH/STO字段,以及额外的RC行,整体扩展了区域 +5. 更改label/entry/optionmenu等控件的生成方式,使用循环实现,更加简洁和容易维护(暂未实现) +6. 支持工业/协作两条产品线的电机电流数据处理,包括单轴,场景,max/avg计算 + +v0.1.5(2024/06/12) +1. [aio.py] 主界面切换不同功能时保持placehold一致 +2. [brake.py] 由于制动采集模板和内容的更改,适配了新的数据,更新了算法 +3. [aio.py] 新增tabview组件,区分数据处理和自动化测试功能 +4. [aio.py] 重新调整界面配色 +5. [aio.py] 修改了write2textbox函数,定制化显示每一行的颜色,针对每一行可自定义输出内容颜色 +6. [brake.py/iso.py/current.py] 由于第 5 点的更改,同时修改了其他文件相关引用的部分 +7. [aio.py] 更改label/entry/optionmenu等控件的生成方式,使用循环实现,更加简洁和容易维护 +8. [aio.py] 修改customtkinter库中C:\Users\Administrator\AppData\Local\Programs\Python\Python312\Lib\site-packages\customtkinter\windows\widgets\ctk_tabview.py文件,参考https://github.com/TomSchimansky/CustomTkinter/issues/2296,实现修改tabview组件的字体大小 +9. [aio.py] 修改menu_main->menu_main_dp,menu_sub->menu_sub_dp,为后续其他tab功能按钮做扩展,是针对第三点做出的相应调整 +10. [layout.xlsx] 添加了各个功能的流程图 + +v0.1.5.1(2024/06/12) +1. [current.py] 修改cycle功能中,数据清理范围为70000行,并将threshold从2调整为5 +2. [current.py] 修改位置超限提示,使更清楚了解问题原因 +3. [current.py] 修改find_point函数中错误提示,增加定位信息 +4. [README.md] 精简打包命令 +5. [requirements.txt] 新增必要库配置文件 + +v0.1.5.2(2024/06/13) +1. [brake.py/aio.py]: 将sto修改为estop +2. [brake.py] 修改了速度计算逻辑,新版本的vel列数据遵循如下规则,av = vel * 180 / pi,根据av再计算speed +3. [brake.py] 将threshold修改为常量50 +4. [brake.py] 提高了输出提示语的明确性,删除了不必要的省略号 +5. [brake.py] 更正了之前的数据copy错误,重新优化了estop处是否达到指定百分比的判定逻辑 + +v0.1.5.3(2024/06/14) +1. [aio.py] 修改w_param为84,适配14寸电脑屏幕 +2. [brake.py] 将判定合规逻辑修改为角速度超过指定角速度的95% +3. [README.md] 稍作修改,包括打包方式,功能特性等 + +v0.1.6.0(2024/06/15) +1. [aio.py] 新增wavelogger处理界面 +2. [wavelogger.py] 新增精度数据处理模块 + +v0.1.6.1(2024/06/16) +1. [wavelogger.py] bugfix single_file_proc函数中,修改_start起始点的计算逻辑 +2. [wavelogger.py] bugfix find_point函数中,当判断条件为临界值 2.0 的时候,针对forward和backward两种情况,对row_target做与判断逻辑相同的处理,目的是避免形成死循环 + +v0.1.6.2(2024/06/16) +1. [current.py] 修改了max/avg相关功能中对于返回值的处理逻辑,并在输出框以行的形式打印出来 + +v0.1.6.3(2024/06/18) +1. [current.py] 适配电机电流中速度使用hw_joint_vel_feedback的数据,取消对device_servo_vel_feedback的支持,后续所有涉及到速度相关的数据均已前者为准,现已完成对单轴和场景的适配 + +> !!WARNING:目前版本的电机电流程序还支持DriverMaster采集的数据处理,等明确后,将不再支持,也即所有的电机电流数据(工业+协作),都是用诊断曲线来采集 + +v0.1.7.0(2024/06/19)-未发布 +1. [openapi.py] 初步搭建起框架,完成了新老协议的封包/解包/异步采集日志的操作(未充分测试,但基本无问题) +2. [openapi.py] 修改了封包的规则,使之更加明晰,封包操作没有实现分包功能,目前看实际场景用不到 + +v0.1.7.0(2024/06/21)-未发布 +1. [openapi.py] 定义 MAX_FRAME_SIZE 常量(1024),替换socket接收以及响应数据处理相关部分 +2. [openapi.py] 使用 int.to_bytes 和 int.from_bytes 替换 binascii 模块的功能 +3. [aio.py] 修改了Data Process中初始化的动作,使得初始化时的状态统一成程序刚启动时的样子 + +v0.1.7.0(2024/06/23)-未发布 +1. [aio.py] 增加了tabview的点击行为函数,每次点击tab都会初始化 +2. [aio.py] 增加了Automatic Test界面元素,包括如下,并完成了功能框架的搭建 + - 标签:文件/角速度/减速比 + - 按钮:急停及恢复 + - 输入框:文件路径/角速度/减速比 + - OptionMenu:负载 + - 进度条 +3. [openapi.py] 增加心跳检测函数,并开启线程执行;取消在该文件中生成实例 +4. [aio.py] 完成detect_network,并在main函数开启线程 +5. 将templates文件夹移动到assets内 + +v0.1.7.0(2024/06/24)-未发布 +1. [openapi.py] 建联部分做容错逻辑,并将读写文件做自适应处理 +2. [aio.py] 将读写文件做自适应处理,引入openapi模块并生成实例,做心跳检测,将socket超时时间修改为3s + +v0.1.7.0(2024/06/25)-未发布 +1. [aio.py] 取消了在本文件中开启openapi线程的做法,并修改如下: + - 通过包的方式导入其他模块 + - 使用current_path来规避文件路径问题 + - 声名了 self.hr 变量,用来接收openapi的实例化 + - 修改了对于segment button的错误调用 + - 设定progress bar的长度是10 + - 完善了segmented_button_callback函数 + - 在detect_network函数中增加heartbeat初始化 + - tabview_click函数中新增textbox清屏功能,以及实例化openapi,并做检测 +2. [openapi.py] 取消了初始化中无限循环检测,因为阻塞了aio主界面进程!!!socket也无法多次连接!!!浪费了好多时间!!!很生气!!!! + - 通过tabview切换来实现重新连接,并保留了异常处理部分 + - 将所有的 __xxxx 函数都替换成 xxxx 函数,去掉了 __ + - 使用current_path来规避文件路径问题 +3. [do_brake.py] 初步完成了机器状态收集的功能,还需要完善 + - 使用current_path来规避文件路径问题 + - 新增validate_resp函数,校验数据 + - 完善了调用接口 + +> **关于HMI接口** +> - 封包解包顺序:帧长度二字节/包长度四字节/协议二字节/预留二字节,\x04\x00:\x00\x00\tR:\x02:\x00 +> - 帧长度和包长度没有必然关系,单帧的时候是帧长度减去包长度等于6,包长度指的是所有内容的长度 +> - HMI内部每次发送1024个字节,进行分包,内容长度规则是:第一帧1024-6=1018(帧大小减去包头长度),第二帧(包含)及之后的帧,帧长度即是数据长度 + +v0.1.7.0(2024/06/26)-初步可用 +1. [aio.py] 在detect_network函数中需改查询时间间隔是1s,在tabview_click中增加textbox配置normal的语句 +2. [do_brake.py -> btn_functions.py] 新增执行相应函数,并在get_state函数中设置无示教器模式 +3. [openapi.py] 新增sock_conn函数,并做连接时的异常处理,新增类参数w2t +4. [aio.py] 修改customtkinter库中C:\Users\Administrator\AppData\Local\Programs\Python\Python312\Lib\site-packages\customtkinter\windows\widgets\ctk_tabview.py文件,参考https://github.com/TomSchimansky/CustomTkinter/issues/2296,实现修改tabview组件的字体大小,使用原生字体,同时将segmented button字体修改为原生,为了解决segmented button在禁用和启用时,屏幕抖动的问题,并将大小修改为16 +5. [aio.py] 修改了segmented_button_callback的实现逻辑,使代码更简洁 +6. [aio.py] 修改了在tabview_click函数中对于实例化HmiRequest的动作,使每次切换标签都会重新实例化,也就是每次都会重新连接,修复显示不正确的问题 +7. [openapi.py] 新增了socket关闭的函数,并增加msg_id为None的处理逻辑 +8. [btn_functions.py] 完善了状态获取的功能,新增告警获取以及功能切换的逻辑 +9. [aio.py] 修改了版本 +10. [current.py] max/avg功能结束之前会将结果数据追加写入源文件,avg算法更改为average+3×std +11. [wavelogger.py] 算法更改为 average+3×std + +v0.1.7.1.0(2024/06/29) +1. [APIs: aio.py] + - 对于automatic test删除了输入框,使用configs.xlsx配置文件作为参数输入 + - 完善initialization/param_check/func_start_callback函数中对于automatic test的处理 + - 将textbox组件一直设置为normal状态,不再频繁切换disabled + - 将所有的f_h文件对象修改为f_hb,并将connection_state修改为c_state + - 在detect_network函数中,实例化HmiRequest,并在无限循环中检测心跳是否正常,如异常,则销毁hr,重新生成 + - 取消在tabview切换时,检测心跳的逻辑,这样做无法保证实时性 +2. [APIs: openapi.py] + - 将sock_conn函数移出__init__,单独作为连接函数存在 + - 新增全局变量self.t_bool,控制所有的线程中无限循环的启停,也就是可以人为的退出线程 + - 移除close_sock函数 + - heartbeat函数中新增打印所有消息的代码,调试时打开,平常关闭 + - execution函数中,新增对overview.set_autoload和overview.reload的支持 + - execution函数中,对send动作增加异常处理逻辑 +3. [APIs: do_brake.py] + - 新增文件,处理制动测试流程,建立连接,导入project,pp2main,run,采集并处理曲线数据,本地修改RL程序,推送至控制器等 + - 目前完成: + - 文件合规性检查 + - 导入工程并设置为运行工程 +4. [APIs: current.py] 修改scenario/single电机电流最大长度为150s +5. 在本文件中更新关于制动自动化测试的相关内容 +6. [t_change_ui: aio.py/brake.py/current.py] 整体修改了操作界面,删除了大部分的配置输入框,改用 configs.xlsx 配置文件替代,并优化了max/avg功能中写入结果数据的方式 + +v0.1.7.1.1(2024/06/29) +1. [APIs: aio.py] + - 修改detect_network函数中sleep语句放到最后,重新生成HmiRequest实例中增加sleep(4),这个停顿时间一定是比openapi中heartbeat函数的sleep要长1s以上才能正常工作 + - 修改write2textbox函数,新增默认参数tab_name,只有当该值与当前tab一致时,函数才会有输出 + - 第二条改动影响到了automatic_test文件夹下所有的文件 +2. [APIs: openapi.py] + - 规定了所有的网络异常均由heartbeat函数来定义,其他异常不做中断处理 + - execution函数中合并了case条件 + - 增加了N多指令,多为诊断曲线和rl程序相关 + - 日志保留条数修改为20000 +3. [APIs: do_brake.py] + - 实现自动推送工程到xCore并自动运行 + - 初步实现了Modbus发送消息和检测状态 +4. [APIs: do_current.py] + - 将do_brake.py的内容完全拷贝到此文件,待修改 + +v0.1.7.2(2024/06/30) +1. 初步完成NB4h_R580_3BH7.zip工程的设计 +2. 重新研究了解包操作,重新实现了一版 +3. 修改openapi.pi中excution为execution函数 +4. 增加了解包原理性文档 + +v0.1.7.3(2024/07/01) +1. [APIs: openapi.py] + - 继续完善封包解包操作,并优化了所有调试信息,默认打开状态,直到bug数量明显减少 + - 修复了两个bug,删除了一个多余的break,另一个是补齐了self.broke的重置 +2. [APIs: do_current.py] 使用原工程的工程名进行move操作,语义更加明确 + +> 目前看openapi.py封包解包没有任何问题了,但是所有的调试信息都默认打开,以便可以第一时间保留现场 +> 打开诊断,跑了10多分钟,共计解包没有报错,应该是没有问题了 + +v0.1.7.4(2024/07/02) +1. [APIs: openapi.py] + - 增加了modbus的python实现 + - heartbeat函数修改发送间隔为1s + - 清除了绝大部分调试性输出,发现太多的这种输出也会导致心跳丢包...,不清楚这个原理是什么 + - 在get_response函数中的while self.pkg > 0循环中,删除了else语句,因为它永不会被执行到 + - 在get_response函数中,修复一个bug,在flag==0的else语句中,补齐了index==6的情况 +2. [APIs: do_current.py] + - 完成了六个轴的电机电流动作的执行,以及数据采集 + - 完成了对应的RL程序的编写 +3[APIs: aio.py] + - 引入modbus实例化,并以参数的形式,传递给相应的tabview + - 新增pre_warning函数,在做自动化测试之前,确保所有条件皆具备 + +v0.1.7.5(2024/07/03) +1. [APIs: aio.py] + - 增加触发急停和恢复急停功能逻辑 +2. [APIs: do_current.py] + - 重新调整运行顺序,增加数据处理的逻辑(惯量负载逻辑暂不实现,等待软件部解决了修改工程之后不生效的问题再考虑) +3. [APIs: btn_functions.py] + - 增加触发急停和恢复急停的modbus实现,仅适用于自动化测试 + +v0.1.7.6(2024/07/04) +1. [APIs: aio.py] + - Automatic Test逻辑中增加选择current时,需要选负载类型的逻辑 +2. [APIs: do_current.py] + - 单轴/场景电机电流的采集已完成 +3. [APIs: openapi.py] + - 增加了modbus读取浮点数的功能 + - 优化了get_from_id的逻辑 +4. [autotest.xml]: 新增了scenario_time只写寄存器 + +v0.1.8.0(2024/07/04) +1. [APIs: do_current.py]: 完成了堵转电流和惯量负载电机电流的采集和处理,至此,电机电流的自动化工作基本完成 + +v0.1.8.1(2024/07/05) +1. [APIs: do_brake.py]: 完成了制动性能测试框架的搭建,可以顺利执行完整的测试程序,但是未实现急停和数据处理 +2. [APIs: aio.py]: 修改了do_brake主函数的参数 +3. 增加工程文件target.zip + +v0.1.8.2(2024/07/08) +1. [APIs: do_brake.py]: 完成了制动性能测试逻辑,只不过制动信号传递生效延迟不可控,暂时pending +2. [APIs: do_current.py]: 修改曲线数据时序,主要是value data取反即可,解决了波形锯齿明细的问题 +3. [APIs: openapi.py]: modbus新增了触发急停信号的寄存器 stop0_signal,并重写了解除急停,socket新增了register.set_value协议 + +v0.1.9.0(2024/07/10) +1. 完成了制动性能的自动化采集 +2. 完善了modbus浮点数读写相关的功能 +3. 修改了target.zip工程,该工程目前适配电机电流和制动性能 + +v0.1.9.1(2024/07/12) +1. [APIs: do_brake.py] + - 修改正负方向拍急停的逻辑,基本原理为:运行之前发送正负方向信号pon给RL,RL根据信号以及速度正负号运作 + - 由于上述修改,正负方向急停准确率可达100% +2. [APIs: aio.py] + - 修改write2textbox的输出逻辑,实现更加灵活的自定义输出,同时修改相关部分 +3. [APIs: openapi.py] + - modbus类新增指示政府方向急停的信号pon,将modbus类入参中的tab_name删除,并修改tab_name的值为'openapi' + - socket类种修改tab_name的值为'openapi' + +v0.1.9.2(2024/07/13) +1. [APIs: do_brake.py] + - 修改ready_to_go信号的接收逻辑,适配大负载机型 +2. [APIs: do_current.py] + - 修改ready_to_go信号的接收逻辑,适配大负载机型 + - 调整单轴测试时间为35s,适配大负载机型,调整堵转电流持续时间15s,适当减少测试时间 + - 将act信号置为False的动作放在初始化,增加程序健壮性 + - 修改所有输出文件的命名,在扩展名之前加入时间戳 + - 删除多余的时序矫正语句——item['value'].reverse(),使输出的曲线为平滑的自然顺序 +3. [current: current.py] + - 在find_point函数种,当无法找到正确点位时,继续执行,而不是直接终止执行 + - max功能计算逻辑矫正,应该是取绝对值的最大值 + - 整体梳理了trq/trqh的传递路径,现已修正完毕 + - 减速比rr数据源修改为configs.xlsx +4. 在current工程main函数增加 VelSet 100语句 + +v0.1.9.3(2024/07/15) +1. [APIs: openapi.py] + - 修改modbus连接失败报错输出形式,使之只在automatic test页面显示 + - 将该文件移动至toplevel,为后面扩展做准备 + - 修改heartbeat文件路径,使后续打包的时候更方便 +2. [APIs: aio.py] + - 修改heartbeat文件路径,使后续打包的时候更方便 + - 修改write2textbox函数的打印逻辑,先判断网络相关 + +v0.1.9.4(2024/07/15) +1. [profile: aio.py]:完善durable text相关逻辑 +2. [profile: do_brake/do_current/btn_functions.py]:删除validate_resp函数,修改execution函数 +3. [profile: factory_test.py] + - 新增耐久/老化测试程序 + - 实现六轴折线图显示 +4. [profile: openapi.py]:多次合并遗留问题处理 +5. templates文件夹组织架构调整 + +v0.2.0.0(2024/07/17) +1. [profile: aio.py] + - 增加velocity相关逻辑 + - 修改负载信息为曲线信息 +2. [profile: factory_test.py] + - 增加velocity相关逻辑 +3. [profile: current.py] + - 修正减速比获取的规则 +4. [profile: openapi.py] + - HmiRequest模块:日志取消记录move.monitor相关 + - HmiRequest模块:增加了durable_lock变量,控制文件读写互斥 + +v0.2.0.1(2024/07/19) +1. [main: aio.py] + - 修改了x轴显示,使之为时间刻度 + - 修改pre_warning函数,增加了durable test的初始化 +2. [main: factory_test.py] + - 增加了数据计算错误的判断逻辑 + - 增加了历史数据保存的逻辑 + - 增加了文件读写互斥的逻辑 + - 修改功能为输出有效电流和最大电流,并将数据结构简化 + +v0.2.0.2(2024/07/26) +1. [main: current.py] + - 修正堵转电流无法正确写入结果文件的问题 +2. [main: do_brake.py] + - 初始速度采集等待时间设置为可通过configs.xlsx配置文件调整的 + - 初次速度采集停止逻辑修改为tasks.stop指令(未验证) + - 急停信号触发前,pending时间设置为固定值10s + - 实现正负方向速度采集逻辑 + - 工程名变更逻辑实现修改为通配符,方便后续根据机型保存文件 + - 增加超差后写诊断的逻辑,并可以通过configs.xlsx配置文件调整 + - 程序输出中增加时间戳,方便调试定位日志时间 +3. [main: do_current.py] + - 工程名变更逻辑实现修改为通配符,方便后续根据机型保存文件 +4. 为工程文件添加更详细的注释 +5. 补充了do_current/do_brake的流程图 +6. [main: openapi.py] + - 将modbus motor_on/off的实现方法改为高电平脉冲触发 +7. configs.xlsx配置表新增write_diagnosis/get_init_speed两个参数 + +v0.2.0.3(2024/07/27) +1. [APIs: do_brake.py]: 精简程序,解决 OOM 问题 +2. [APIs: do_current.py]: 精简程序,解决 OOM 问题 +3. [APIs: factory_test.py]: 精简程序,解决 OOM 问题 +4. [APIsL openapi.py] + - 心跳修改为 1 s,因为 OOM 问题的解决依赖于长久的打开曲线开关,此时对于 hr.c_msg 的定时清理是个挑战,将心跳缩短,有利于清理日志后,避免丢失心跳 + - 新增 diagnosis.save 命令,但是执行时,有问题,待解决 + +v0.2.0.4(2024/07/30) +1. [APIs: do_brake.py]: 修复制动数据处理过程中,只取曲线的最后 240 个数据 +2. [APIs: aio.py]: 判定版本处,删除 self.destroy(),因为该语句会导致异常发生 + - 心跳修改为 1s,因为 OOM 问题的解决依赖于长久的打开曲线开关,此时对于 hr.c_msg 的定时清理是个挑战,将心跳缩短,有利于清理日志后,避免丢失心跳 + - 新增 diagnosis.save 命令,但是执行时,有问题,待解决 + +v0.2.0.5(2024/07/31) +此版本改动较大,公共部分做了规整,放置到新建文件夹 commons 当中,并所有自定义模块引入 logging 模块,记录重要信息 +1. [t_change_ui: clibs.py] + - 调整代码组织结构,新增模块,将公共函数以及类合并入此 + - 将一些常量放入该模块 + - 引入logging/concurrent_log_handler模块,并作初始化操作,供其他模块使用,按50M切割,最多保留10份 + - prj_to_xcore函数设置工程名部分重写,修复了多个prj工程可能不能执行的问题,并优化输入密码的部分 +2. [t_change_ui: openapi.py] + - 完全重写了 get_from_id 函数,使更精准 + - 在 msg_storage 函数中,增加 logger,保留所有响应消息 + - 删除 heartbeat 函数中的日志保存功能部分 + - 心跳再次修改为 2s... +3. [t_change_ui: aio.py] + - 增加了日志初始化部分 + - detect_network 函数中修改重新实例化HR间隔为 4s,对应心跳 + - create_plot 函数中增加 close('all'),解决循环画图不销毁占用内存的问题 +4. [t_change_ui: do_brake.py] + - 使用一直打开曲线的方法规避解决了 OOM 的问题,同时修改数据处理方式,只取最后 12s + - 优化 ssh 输入密码的部分 +5. [t_change_ui: do_current.py] + - 保持电流,只取最后 15s + - 优化 ssh 输入密码的部分 +6. [t_change_ui: all the part]: 引入 commons 包,并定制了 logging 输出,后续持续优化 +7. [APIs: btn_functions.py]: 重写了告警输出函数,从日志中拿数据 +8. [APIs: aio.py]: 将日志框输出的内容,也保存至日志文件 +9. [APIs: do_brake.py] + - 修改获取初始速度的逻辑,只获取configs文件中配置的时间内的速度 + - 新增 configs 参数 single_brake,可针对特定条件做测试 +10. [APIs: all]: 添加了 logger.setLevel(INFO),只有添加这个,单个模块内才生效 + +v0.2.0.6(2024/08/09) +1. [t_change_ui: all files] + - 修改了 logger 的实现 + - 尤其是 clibs.py,使用日志字典,重写了日志记录的功能 + +v0.2.0.7(2024/08/16) +1. [t_change_ui: clibs.py]:修改了 hmi.log 的日志等级为 WARNING +2. [t_change_ui: openapi.py]:根据第一步的修改,将此模块日志记录等级调整至 warning +3. [current: current.py] + - README新增了整机自动化测试的前置条件,即滑块需要滑动到最右端 + - current修改了文件校验的逻辑 +4. [t_change_ui: aio.py] + - 修改变量命名,widgit -> widget + - 根据第 5 点变动,同步修改代码实现 + - 调整 UI 界面代码顺序,使之符合 layout.xlsx 描述 + - 将版本检查的部分单独封装成一个函数,在 detect_network 线程初始化时调用一次,并且程序启动也不会受到阻塞 +5. [t_change_ui: layout.xlsx]:修改了组件布局方式 + + +> 前两个修改点,修复的是网络提示的颜色不正确问题,因为日志将 textbox 中的内容也作为 debug 信息写入 hmi.log 了 + +v0.2.0.8(2024/08/20) +1. [t_change_ui: clibs.py] + - 从外部拷贝 icon.ico 文件到 templates 目录 + - 在 assets 目录新建 logs 目录,存放日志文件,并增加了相应的逻辑保证正常执行 +2. [t_change_ui: aio.py]:增加 App 窗口图标代码 +3. [t_change_ui: openapi.py]:将重复输出的网络错误提示,从 textbox 中转移到 debug.log 日志文件中 +4. [main: openapi.py]:新增 rl_task.set_run_params 指令支持,可设定速度滑块以及是否重复运行 +5. [main: do_brake/do_current/factory_test.py]:在初始化运动时增加 `clibs.execution('rl_task.set_run_params', hr, w2t, tab_name, loop_mode=True, override=1.0)` + +v0.2.0.9(2024/10/09) +1. [main: do_brake.py] 采集完成后,pending 3s,使速度完全将为 0 + +v0.2.1.0(2024/12/05) +1. [current: do_current.py] 增加了 hw_sensor_trq_feedback 曲线的采集 +2. [current: current.py] 增加了 hw_sensor_trq_feedback 曲线数据的处理,以及修改了之前数据处理的逻辑 +3. [current: clibs.py] 新增可手动修改连接 IP 地址的功能,存储在 assets/templates/ipaddr.txt 中,默认是 192.168.0.160 + +v0.2.1.1(2024/12/16) +1. [main: do_brake.py] 修改了 SSH 的固定 IP 为 clibs 中读取的内容,并删除了每次都 reload 工程的动作,改为只在修改 RL 工程时 reload 一次,旨在减少最近频繁出现的“无法获取overview.reload-xxxxxx”请求的响应,初步判断是 xCore 的问题,非 AIO 问题,已反馈待版本修复 +2. [main: wavelogger.py] 新增异常数据校验功能 + +v0.3.0.0(2025/01/09) +1. 重构了 AIO 工具: + - UI 界面 + - 电机电流数据处理功能 + - xCore 通讯协议实现 +2. 将之前的功能迁移到新代码 + +v0.3.1.0(2025/01/23) +1. 实现了从机型文件读取相关测试参数,并用于自动化测试或者数据处理 +2. 完整实现了所有功能的迁移,并自测验证通过 +3. 重新实现并优化了如下功能: + - 电机电流数据采集,优化代码,提高执行效率,并适配新的报告文件 + - 制动数据采集,比之前采集正确率提高 30%,基本可以做到 100% 的数据准确度 + - 耐久数据采集并记录,优化了执行以及数据展示 + - 基恩士数据采集处理,适配任意编码格式的文件处理 + +v0.3.1.2(2025/02/11) +1. 修改自动测试(制动/转矩/耐久)的read_ready_to_go信号等待时间为15s +2. 废弃write_diagnosis参数 +3. 优化do_brake中,触发超差写诊断等待操作流程 + +v0.3.1.3(2025/02/12) +1. 修改自动测试(制动)的打开关闭曲线逻辑,开始时打开,完整测试结束时关闭 + +v0.3.1.3(2025/02/13) +1. 修改自动测试(电机电流)的数据处理方法,从以前的Multiprocessing->threading,因为Intel CPU遇到Multiprocessing会重新打开一个AIO实例 + +v0.3.1.5(2025/03/10) +1. 因公司网络环境调整,修改服务器校验IP + +v0.3.1.6(2025/03/21) +1. 调整耐久(场景)测试工程,以及相应的数据采集逻辑,将采集规则由固定时间间隔,修改为固定运动动作周期 +> Tips:工程以及寄存器文件均有变动 \ No newline at end of file diff --git a/assets/files/version/requirements.txt b/assets/files/version/requirements.txt new file mode 100644 index 0000000..8660c7f --- /dev/null +++ b/assets/files/version/requirements.txt @@ -0,0 +1,11 @@ +chardet==5.2.0 +customtkinter==5.2.2 +matplotlib==3.10.0 +numpy==2.2.2 +openpyxl==3.1.5 +pandas==2.2.3 +paramiko==3.5.0 +pdfplumber==0.11.5 +Pillow==11.1.0 +pymodbus==3.8.3 +pyinstaller==6.12.0 diff --git a/assets/files/version/version b/assets/files/version/version new file mode 100644 index 0000000..245a459 --- /dev/null +++ b/assets/files/version/version @@ -0,0 +1 @@ +0.3.1.6@03/21/2025 \ No newline at end of file diff --git a/assets/media/icon.ico b/assets/media/icon.ico new file mode 100644 index 0000000..4c3b21a Binary files /dev/null and b/assets/media/icon.ico differ diff --git a/assets/media/updated.png b/assets/media/updated.png new file mode 100644 index 0000000..c1fe21f Binary files /dev/null and b/assets/media/updated.png differ diff --git a/assets/media/upgrade.png b/assets/media/upgrade.png new file mode 100644 index 0000000..8c88c01 Binary files /dev/null and b/assets/media/upgrade.png differ diff --git a/code/aio.py b/code/aio.py new file mode 100644 index 0000000..d585d18 --- /dev/null +++ b/code/aio.py @@ -0,0 +1,518 @@ +import json +import threading +import time + +import ui.login_window as login_window +import ui.reset_window as reset_window +import ui.main_window as main_window +from PySide6 import QtWidgets +from PySide6.QtCore import Qt, QThread, Signal, QObject +import sys +import re +import pymysql +import hashlib +import datetime +import common.clibs as clibs +import common.openapi as openapi +from PySide6.QtWidgets import QMessageBox +from PySide6.QtGui import QColor, QTextCursor, QTextCharFormat, QDoubleValidator +from analysis import brake, current, wavelogger, iso + + +class MultiWindows: + login_window = None + reset_window = None + main_window = None + + +class ConnDB(QObject): + completed = Signal(tuple) + + def __init__(self): + super().__init__() + + def do_conn(self, action): + conn, cursor = None, None + try: + conn = pymysql.connect(host='10.2.20.216', user='root', password='Rokae_123457', port=13306, charset='utf8', connect_timeout=clibs.INTERVAL*10) + cursor = conn.cursor() + except Exception: + ... + finally: + self.completed.emit((conn, cursor)) + + +class RunProg(QObject): + completed = Signal(tuple) + + def __init__(self): + super().__init__() + + def program(self, action): # 0: prog 1: idx + prog, idx, network = action + if idx in range(7): + run = prog.processing + elif idx == -1: + run = prog.net_conn + elif idx == -99: + run = prog + + try: + run() + self.completed.emit((True, prog, "", idx, network)) # 运行是否成功/返回值/报错信息/idx + except Exception as err: + self.completed.emit((False, None, err, idx, network)) # 运行是否成功/返回值/报错信息/idx + + +class ThreadIt(QObject): + completed = Signal(tuple) + + def __init__(self): + super().__init__() + + def run_program(self, action): + try: + res = action[0](*action[1]) + self.completed.emit((True, res, "")) # 运行是否成功/返回值/报错信息 + except Exception as err: + self.completed.emit((False, "", err)) + + +class LoginWindow(login_window.Ui_Form): + action = Signal(int) + + def __init__(self): + super(LoginWindow, self).__init__() + self.setupUi(self) + self.le_username.setFocus() + self.conn, self.cursor = None, None + if not clibs.status["mysql"]: + self.setup_DB() + + def get_user_infos(self, results): + self.conn, self.cursor = results + if self.conn is None and self.cursor is None: + QMessageBox.critical(self, "网络错误", "无法连接至服务器数据库,稍后再试......") + try: + MultiWindows.reset_window.close() + except Exception: + ... + finally: + self.close() + else: + self.cursor.execute("SET autocommit = 1;") + clibs.status["mysql"] = 1 + + def setup_DB(self): + self.t = QThread(self) + self.conn_db = ConnDB() + self.conn_db.moveToThread(self.t) + self.conn_db.completed.connect(self.get_user_infos) + self.action.connect(self.conn_db.do_conn) + self.t.start() + self.action.emit(1) + + def user_login(self): + username = self.le_username.text() + password = self.le_password.text() + md = hashlib.md5(password.encode()) + password = md.hexdigest() + + self.cursor.execute("use user_info;") + self.cursor.execute("select * from UserInfo;") + user_infos = self.cursor.fetchall() + for user_info in user_infos: + if user_info[0] == username and user_info[1] == password and user_info[2] == 0: + MultiWindows.main_window = MainWindow(self.conn, self.cursor, username) + MultiWindows.main_window.show() + self.close() + else: + t = datetime.datetime.now().strftime("%H:%M:%S") + self.label_hint.setText(f"[{t}] 用户名或密码错误,或用户已登录......") + self.label_hint.setStyleSheet("color: red;") + + def reset_password(self): + MultiWindows.reset_window = ResetWindow(self, self.conn, self.cursor) + MultiWindows.reset_window.show() + self.setVisible(False) + + +class ResetWindow(reset_window.Ui_Form): + def __init__(self, login_window, conn, cursor): + super(ResetWindow, self).__init__() + self.setupUi(self) + self.le_username.setFocus() + self.login_window = login_window + self.conn = conn + self.cursor = cursor + + def reset_password(self): + username = self.le_username.text() + old_password = self.le_old_password.text() + md = hashlib.md5(old_password.encode()) + password = md.hexdigest() + new_password_1 = self.le_new_password_1.text() + new_password_2 = self.le_new_password_2.text() + + self.cursor.execute("use user_info;") + self.cursor.execute("select * from UserInfo;") + user_infos = self.cursor.fetchall() + + for user_info in user_infos: + if user_info[0] == username and user_info[1] == password and user_info[2] == 0: + break + else: + t = datetime.datetime.now().strftime("%H:%M:%S") + self.label_hint.setText(f"[{t}] 用户名或密码错误,或用户已登录......") + self.label_hint.setStyleSheet("color: red;") + return + + if new_password_1 != new_password_2 or len(new_password_1) < 8: + t = datetime.datetime.now().strftime("%H:%M:%S") + self.label_hint.setText(f"[{t}] 两次输入的新密码不匹配,或长度小于8位......") + self.label_hint.setStyleSheet("color: red;") + else: + md = hashlib.md5(new_password_1.encode()) + password = md.hexdigest() + self.cursor.execute(f"UPDATE UserInfo SET password = '{password}' WHERE username = '{username}'") + self.close() + + def reset_cancel(self): + self.login_window.setVisible(True) + self.close() + + def closeEvent(self, event): + self.login_window.setVisible(True) + self.close() + + +class MainWindow(main_window.Ui_MainWindow): + action = Signal(tuple) + + def __init__(self, conn, cursor, username): + super(MainWindow, self).__init__() + self.setupUi(self) + self.conn = conn + self.cursor = cursor + self.username = username + self.predoes() + # self.t = threading.Thread(target=self.state_detection) + # self.t.daemon = True + # self.t.start() + + def predoes(self): + # ========================= db int ========================= + t = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + self.cursor.execute(f"UPDATE UserInfo SET online = 1 WHERE username = '{self.username}';") + self.cursor.execute(f"CREATE DATABASE IF NOT EXISTS {self.username};") + self.cursor.execute(f"use {self.username};") + self.cursor.execute(f"CREATE TABLE {t}_log (id INT AUTO_INCREMENT PRIMARY KEY, timestamp TIMESTAMP NOT NULL default CURRENT_TIMESTAMP, level ENUM('DEBUG', 'INFO', 'WARNING', 'ERROR'), module VARCHAR(255) NOT NULL, content TEXT);") + self.cursor.execute(f"INSERT INTO {t}_log (module, level, content) VALUES (%s, %s, %s)", ("aio", "info", "testing")) + self.cursor.execute("SHOW TABLES;") + tables = [x[0] for x in self.cursor.fetchall()] + if len(tables) > clibs.MAX_LOG_NUMBER: + for table in sorted(tables)[:-10]: + self.cursor.execute(f"DROP TABLE {table};") + + # ========================= clibs ========================= + clibs.cursor = self.cursor + clibs.tb_name = f"{t}_log" + + # ========================= clibs ========================= + # validator = QDoubleValidator(bottom=300, decimals=2) + # self.le_durable_interval.setValidator(validator) + + # ========================= styleSheet ========================= + tws = [self.tw_funcs, self.tw_docs] + for tw in tws: + tw.setStyleSheet(""" + QTabBar::tab:selected { + background: #0078D4; + color: white; + border-radius: 4px; + } + QTabBar::tab:!selected { + background: #F0F0F0; + color: #333; + } + QTabWidget::pane { + border: 1px solid #CCCCCC; + } + """) + + # ============================↓↓↓debug↓↓↓============================ + # print(f"self.cb_data_func.currentIndex() = {self.cb_data_func.currentIndex()}") + + def run_program_thread(self, prog, idx, prog_done, network): + self.tw_docs.setCurrentIndex(0) + # self.pte_output.clear() + if idx != -99: + prog.output.connect(self.w2t) + self.t = QThread(self) + self.run = RunProg() + self.run.moveToThread(self.t) + self.run.completed.connect(prog_done) + self.action.connect(self.run.program) + self.t.start() + self.action.emit((prog, idx, network)) + + def w2t(self, msg, color="black"): + self.pte_output.appendHtml(f"{msg}") + cursor = self.pte_output.textCursor() + cursor.movePosition(QTextCursor.End) + self.pte_output.setTextCursor(cursor) + self.pte_output.ensureCursorVisible() + self.update() + + def prog_start(self): + def prog_done(results): + flag, result, error, idx, network = results + clibs.running[idx] = 0 + # if flag is False: + # self.w2t(f"{clibs.functions[idx]}运行失败:{error}", "red") + # elif flag is True: + # ... + + if sum(clibs.running) > 0: + if sum(clibs.running) == 1: + QMessageBox.critical(self, "运行中", f"{clibs.functions[clibs.running.index(1)]}正在执行中,不可同时运行两个处理/测试程序!") + return + else: + self.w2t(f"clibs.running = {clibs.running}", "red") + self.w2t(f"clibs.functions = {clibs.functions}", "red") + QMessageBox.critical(self, "严重错误", "理论上不允许同时运行两个处理程序,需要检查!") + return + + if self.tw_funcs.currentIndex() == 0 and self.cb_data_func.currentIndex() == 0: + self.run_program_thread(brake.BrakeDataProcess(self.le_data_path.text()), 0, prog_done, None) + elif self.tw_funcs.currentIndex() == 0 and self.cb_data_func.currentIndex() == 1: + self.run_program_thread(current.CurrentDataProcess(self.le_data_path.text(), self.cb_data_current.currentText()), 1, prog_done, None) + elif self.tw_funcs.currentIndex() == 0 and self.cb_data_func.currentIndex() == 2: + self.run_program_thread(iso.IsoDataProcess(self.le_data_path.text()), 2, prog_done, None) + elif self.tw_funcs.currentIndex() == 0 and self.cb_data_func.currentIndex() == 3: + self.run_program_thread(wavelogger.WaveloggerDataProcess(self.le_data_path.text()), 3, prog_done, None) + elif self.tw_funcs.currentIndex() == 1 and self.cb_unit_func.currentIndex() == 0: + self.w2t(f"{clibs.functions[4]}功能待开发.....", "red") + elif self.tw_funcs.currentIndex() == 1 and self.cb_unit_func.currentIndex() == 1: + self.w2t(f"{clibs.functions[5]}功能待开发.....", "red") + elif self.tw_funcs.currentIndex() == 2: + self.w2t(f"{clibs.functions[6]}功能待开发.....", "red") + + def prog_stop(self): + QMessageBox.warning(self, "停止运行", "运行过程中不建议停止运行,可能会损坏文件,如果确实需要停止运行,可以直接关闭窗口!") + + def prog_reset(self): + self.pte_output.clear() + + def file_browser(self): + idx_dict = {0: self.le_data_path, 1: self.le_unit_path, 2: self.le_durable_path} + dir_path = QtWidgets.QFileDialog.getExistingDirectory() + tab_index = self.tw_funcs.currentIndex() + if dir_path: + idx_dict[tab_index].setText(dir_path) + + def curve_draw(self): + ... + + def durable_cb_change(self): + ... + + def pre_page(self): + ... + + def realtime_page(self): + ... + + def next_page(self): + ... + + def load_sql(self): + ... + + def search_keyword(self): + ... + + def prog_done_conn(self, results): + flag, result, error, idx, network = results + if flag is False: + self.w2t(f"{network.upper()}连接失败", "red") + elif flag is True: + clibs.status[network] = 1 + if network == "hmi": + self.btn_hmi_conn.setText("断开") + clibs.c_hr = result + elif network == "md": + self.btn_md_conn.setText("断开") + clibs.c_md = result + elif network == "ec": + self.btn_ec_conn.setText("断开") + clibs.c_ec = result + + def prog_done_disconn(self, results): + flag, result, error, idx, network = results + if flag is False: + self.w2t(f"{network.upper()}断开连接失败", "red") + elif flag is True: + clibs.status[network] = 0 + if network == "hmi": + self.btn_hmi_conn.setText("连接") + clibs.c_hr = result + elif network == "md": + self.btn_md_conn.setText("连接") + clibs.c_md = result + elif network == "ec": + self.btn_ec_conn.setText("连接") + clibs.c_ec = result + + def hmi_conn(self): + if self.btn_hmi_conn.text() == "连接": + clibs.ip_addr = self.le_hmi_ip.text().strip() + ip_pattern = re.compile(r"(([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])") + if not ip_pattern.fullmatch(clibs.ip_addr): + self.w2t(f"{clibs.ip_addr} 不是一个有效的 IP 地址", "red") + return + self.run_program_thread(openapi.HmiRequest(clibs.ip_addr, clibs.socket_port, clibs.xService_port), -1, self.prog_done_conn, "hmi") + elif self.btn_hmi_conn.text() == "断开": + self.run_program_thread(clibs.c_hr.close, -99, self.prog_done_disconn, "hmi") + + def md_conn(self): + if clibs.status["hmi"] == 0: + QMessageBox.warning(self, "告警", "打开Modbus连接之前,需要先打开HMI连接!") + return + + if self.btn_md_conn.text() == "连接": + clibs.modbus_port = self.le_md_port.text().strip() + self.run_program_thread(openapi.ModbusRequest(clibs.ip_addr, clibs.modbus_port), -1, self.prog_done_conn, "md") + elif self.btn_md_conn.text() == "断开": + self.run_program_thread(clibs.c_md.close, -99, self.prog_done_disconn, "md") + + def ec_conn(self): + if clibs.status["hmi"] == 0: + QMessageBox.warning(self, "告警", "打开外部通信连接之前,需要先打开HMI连接!") + return + + if self.btn_ec_conn.text() == "连接": + clibs.external_port = self.le_ec_port.text().strip() + self.run_program_thread(openapi.ExternalCommunication(clibs.ip_addr, clibs.external_port), -1, self.prog_done_conn, "ec") + elif self.btn_ec_conn.text() == "断开": + self.run_program_thread(clibs.c_ec.close, -99, self.prog_done_disconn, "ec") + + def hmi_page(self): + self.sw_network.setCurrentIndex(0) + + def md_page(self): + self.sw_network.setCurrentIndex(1) + + def ec_page(self): + self.sw_network.setCurrentIndex(2) + + def hmi_send(self): + def prog_done(results): + ... + + def hmi_send_thread(): + if clibs.status["hmi"] == 0: + QMessageBox.critical(self, "错误", "使用该功能之前,需要先打开HMI连接!") + return + + cmd = self.pte_hmi_send.toPlainText() + req = json.dumps(json.loads(cmd), separators=(",", ":")) + print(f"type of cmd = {type(cmd)}") + print(f"type of req = {type(req)}") + if "id" in req: # 老协议 + print(f"wrong req = {req}") + msg_id = json.loads(req)["id"] + clibs.c_hr.c.send(clibs.c_hr.package(req)) + print(f"msg_id ={msg_id}") + clibs.logger("INFO", "aio", f"hmi: [send] 老协议请求发送成功 {req}") + records = clibs.c_hr.get_from_id(msg_id, "done") + print(f"req = {req}") + print(f"records = {records}") + self.pte_him_recv.clear() + self.pte_him_recv.appendPlainText(records) + else: # 新协议 + clibs.c_hr.c_xs.send(clibs.c_hr.package_xs(json.loads(cmd))) + data = "" + time.sleep(clibs.INTERVAL/5) + _ = clibs.c_hr.c_xs.recv(1024) + while len(_) == 1024: + data += _ + _ = clibs.c_hr.c_xs.recv(1024) + + print(f"data = {data}") + self.pte_him_recv.clear() + self.pte_him_recv.appendPlainText(data.decode()) + self.run_program_thread(hmi_send_thread, -99, prog_done, None) + + def md_send(self): + ... + + def ec_send(self): + ... + + def hmi_cb_change(self): + cmd = self.cb_hmi_cmd.currentText() + self.pte_hmi_send.clear() + self.pte_him_recv.clear() + with open(f"{clibs.PREFIX}/files/protocols/hmi/{cmd}.json", mode="r", encoding="utf-8") as f_hmi: + t = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f") + hmi_dict = json.load(f_hmi) + if "id" in hmi_dict.keys(): + hmi_dict["id"] = f"{cmd}-{t}" + + self.pte_hmi_send.appendPlainText(json.dumps(hmi_dict, indent=2, separators=(",", ":"))) + + def md_cb_change(self): + ... + + def ec_cb_change(self): + ... + + def check_interval(self): + try: + interval = float(self.le_durable_interval.text()) + interval = 300 if interval < 300 else int(interval) + except Exception: + interval = 300 + self.le_durable_interval.setText(str(interval)) + + def state_detection(self): + while True: + time.sleep(clibs.INTERVAL) + if clibs.status["hmi"] == 0 and self.btn_hmi_conn.text() == "断开": + self.btn_hmi_conn.setText("连接") + elif clibs.status["hmi"] == 1 and self.btn_hmi_conn.text() == "连接": + self.btn_hmi_conn.setText("断开") + + def closeEvent(self, event): + idx = -1 if clibs.running.count(1) == 0 else clibs.running.index(1) + info_text = "当前无程序正在运行,可放心退出!" if idx == -1 else f"当前正在运行{clibs.functions[idx]},确认退出?" + reply = QMessageBox.question(self, "退出", info_text) + if reply == QMessageBox.Yes: + try: + self.cursor.execute(f"use user_info;") + self.cursor.execute(f"UPDATE UserInfo SET online = 0 WHERE username = '{self.username}'") + self.cursor.close() + self.conn.close() + finally: + clibs.lock.release() + + if clibs.status["md"] == 1: + self.run_program_thread(clibs.c_md.close, -99, self.prog_done_disconn, "md") + if clibs.status["ec"] == 1: + self.run_program_thread(clibs.c_ec.close, -99, self.prog_done_disconn, "ec") + if clibs.status["hmi"] == 1: + self.run_program_thread(clibs.c_hr.close, -99, self.prog_done_disconn, "hmi") + + self.close() + event.accept() + else: + event.ignore() + + +if __name__ == '__main__': + app = QtWidgets.QApplication(sys.argv) + window = LoginWindow() + window.show() + sys.exit(app.exec()) + diff --git a/code/analysis/brake.py b/code/analysis/brake.py new file mode 100644 index 0000000..a855df6 --- /dev/null +++ b/code/analysis/brake.py @@ -0,0 +1,218 @@ +import json +import os.path +import time +import pandas +from PySide6.QtCore import Signal, QThread +import openpyxl +import re +from common import clibs + + +class BrakeDataProcess(QThread): + output = Signal(str, str) + + def __init__(self, dir_path, /): + super().__init__() + self.dir_path = dir_path + self.idx = 0 + + def logger(self, level, module, content, color="black", error="", flag="both"): + clibs.logger(level, module, content, color, flag, signal=self.output) + if level.upper() == "ERROR": + raise Exception(f"{error} | {content}") + + def check_files(self, rawdata_dirs, result_files): + msg_wrong = "需要有四个文件和若干个数据文件夹,可参考如下确认:
" + msg_wrong += "- reach33/66/100_XXXXXXX.xlsx
- *.cfg
" + msg_wrong += "- reach33_load33_speed33
- reach33_load33_speed66
...
- reach100_load100_speed66
- reach100_load100_speed100
" + + if len(result_files) != 4 or len(rawdata_dirs) == 0: + self.logger("ERROR", "brake-check_files", msg_wrong, "red", "InitFileError") + + config_file, reach33_file, reach66_file, reach100_file = None, None, None, None + for result_file in result_files: + filename = result_file.split("/")[-1] + if re.match(".*\\.cfg", filename): + config_file = result_file + elif filename.startswith("reach33_") and filename.endswith(".xlsx"): + reach33_file = result_file + elif filename.startswith("reach66_") and filename.endswith(".xlsx"): + reach66_file = result_file + elif filename.startswith("reach100_") and filename.endswith(".xlsx"): + reach100_file = result_file + else: + if not (config_file and reach33_file and reach66_file and reach100_file): + self.logger("ERROR", "brake-check_files", msg_wrong, "red", "InitFileError") + + reach_s = ['reach33', 'reach66', 'reach100'] + load_s = ['load33', 'load66', 'load100'] + speed_s = ['speed33', 'speed66', 'speed100'] + prefix = [] + for rawdata_dir in rawdata_dirs: + components = rawdata_dir.split("/")[-1].split('_') # reach_load_speed + prefix.append(components[0]) + if components[0] not in reach_s or components[1] not in load_s or components[2] not in speed_s: + msg = f"报错信息:数据目录 {rawdata_dir} 命名不合规,请参考如下形式
" + msg += "命名规则:reachAA_loadBB_speedCC,AA/BB/CC 指的是臂展/负载/速度的比例
" + msg += "规则解释:reach66_load100_speed33,表示 66% 臂展,100% 负载以及 33% 速度情况下的测试结果文件夹
" + self.logger("ERROR", "brake-check_files", msg, "red", "WrongDataFolder") + + _, rawdata_files = clibs.traversal_files(rawdata_dir, self.output) + if len(rawdata_files) != 3: + msg = f"数据目录 {rawdata_dir} 下数据文件个数错误,每个数据目录下有且只能有三个以 .data 为后缀的数据文件" + self.logger("ERROR", "brake-check_files", msg, "red", "WrongDataFile") + + for rawdata_file in rawdata_files: + if not rawdata_file.endswith(".data"): + msg = f"数据文件 {rawdata_file} 后缀错误,每个数据目录下有且只能有三个以 .data 为后缀的数据文件" + self.logger("ERROR", "brake-check_files", msg, "red", "WrongDataFile") + + result_files = [] + for _ in [reach33_file, reach66_file, reach100_file]: + if _.split("/")[-1].split("_")[0] in set(prefix): + result_files.append(_) + + self.logger("INFO", "brake-check_files", "数据目录合规性检查结束,未发现问题......", "green") + return config_file, result_files + + def get_configs(self, config_file): + try: + with open(config_file, mode="r", encoding="utf-8") as f_config: + configs = json.load(f_config) + + p_dir = config_file.split('/')[-2] + if not re.match("^[jJ][123]$", p_dir): + self.logger("ERROR", "brake-get_configs-1", "被处理的根文件夹命名必须是 [Jj][123] 的格式", "red", "DirNameError") + + axis = int(p_dir[-1]) # 要处理的轴 + rrs = [abs(_) for _ in configs["TRANSMISSION"]["REDUCTION_RATIO_NUMERATOR"]] # 减速比,rr for reduction ratio + avs = configs["MOTION"]["JOINT_MAX_SPEED"] + rr = rrs[axis-1] + av = avs[axis-1] + return av, rr + except Exception as Err: + self.logger("ERROR", "brake-get_configs-2", f"无法打开 {config_file},或者使用了错误的机型配置文件,需检查
{Err}", "red", "OpenFileError") + + def now_doing_msg(self, docs, flag): + now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + file_type = 'file' if os.path.isfile(docs) else 'dir' + if flag == 'start' and file_type == 'dir': + self.logger("INFO", "brake-now_doing_msg", f"[{now}] 正在处理目录 {docs} 中的数据......") + elif flag == 'start' and file_type == 'file': + self.logger("INFO", "brake-now_doing_msg", f"[{now}] 正在处理文件 {docs} 中的数据......") + elif flag == 'done' and file_type == 'dir': + self.logger("INFO", "brake-now_doing_msg", f"[{now}] 目录 {docs} 数据文件已处理完毕") + elif flag == 'done' and file_type == 'file': + self.logger("INFO", "brake-now_doing_msg", f"[{now}] 文件 {docs} 数据已处理完毕") + + @staticmethod + def data2result(df, ws_result, row_start, row_end): + data = [] + for row in range(row_start, row_end): + data.append(df.iloc[row, 0]) + data.append(df.iloc[row, 1]) + data.append(df.iloc[row, 2]) + + i = 0 + row_max = 1000 if row_end - row_start < 1000 else row_end - row_start + 100 + for row in range(2, row_max): + try: + ws_result.cell(row=row, column=1).value = data[i] + ws_result.cell(row=row, column=2).value = data[i + 1] + ws_result.cell(row=row, column=3).value = data[i + 2] + i += 3 + except Exception: + ws_result.cell(row=row, column=1).value = None + ws_result.cell(row=row, column=2).value = None + ws_result.cell(row=row, column=3).value = None + + def get_row_range(self, data_file, df, conditions, av, rr): + row_start, row_end = 0, 0 + ratio = float(conditions[2].removeprefix('speed')) / 100 + av_max = av * ratio + threshold = 0.95 + + for row in range(df.index[-1] - 1, -1, -10): + if df.iloc[row, 2] != 0: + row_start = row - 20 if row - 20 > 0 else 0 # 急停前找 20 个点 + break + else: + self.logger("ERROR", "brake-get_row_range", f"数据文件 {data_file} 采集的数据中没有 ESTOP 为非 0 的情况,需要确认", "red", "StartNotFoundError") + + for row in range(row_start, df.index[-1] - 1, 10): + speed_row = df.iloc[row, 0] * clibs.RADIAN * rr * 60 / 360 + if abs(speed_row) < 1: + row_end = row + 100 if row + 100 <= df.index[-1] - 1 else df.index[-1] - 1 + break + else: + self.logger("ERROR", "brake-get_row_range", f"数据文件 {data_file} 最后的速度未降为零", "red", "SpeedNotZeroError") + + av_estop = abs(df.iloc[row_start - 20:row_start, 0].abs().mean() * clibs.RADIAN) + if abs(av_estop / av_max) < threshold: + filename = data_file.split("/")[-1] + msg = f"[av_estop: {av_estop:.2f} | shouldbe: {av_max:.2f}] 数据文件 {filename} 触发 ESTOP 时未采集到指定百分比的最大速度,需要检查" + self.logger("WARNING", "brake-get_row_range", msg, "#8A2BE2") + + return row_start, row_end + + @staticmethod + def get_shtname(conditions, count): + # 33%负载_33%速度_1 - reach/load/speed + load = conditions[1].removeprefix('load') + speed = conditions[2].removeprefix('speed') + result_sheet_name = f"{load}%负载_{speed}%速度_{count}" + + return result_sheet_name + + def single_file_process(self, data_file, wb, count, av, rr): + df = pandas.read_csv(data_file, sep='\t') + conditions = data_file.split("/")[-2].split("_") # reach/load/speed + shtname = self.get_shtname(conditions, count) + ws = wb[shtname] + + row_start, row_end = self.get_row_range(data_file, df, conditions, av, rr) + self.data2result(df, ws, row_start, row_end) + + def data_process(self, result_file, rawdata_dirs, av, rr): + filename = result_file.split("/")[-1] + self.logger("INFO", "brake-data_process", f"正在打开文件 {filename},这可能需要一些时间......", "blue") + try: + wb = openpyxl.load_workbook(result_file) + except Exception as Err: + self.logger("ERROR", "brake-data_process", f"{filename}文件打开失败,可能是文件已损坏,确认后重新执行!
{Err}", "red", "CannotOpenFile") + + prefix = filename.split('_')[0] + for rawdata_dir in rawdata_dirs: + if rawdata_dir.split("/")[-1].split('_')[0] == prefix: + self.now_doing_msg(rawdata_dir, 'start') + _, data_files = clibs.traversal_files(rawdata_dir, self.output) + for idx in range(3): + self.single_file_process(data_files[idx], wb, idx+1, av, rr) + # threads = [ + # threading.Thread(target=self.single_file_process, args=(data_files[0], wb, 1, av, rr)), + # threading.Thread(target=self.single_file_process, args=(data_files[1], wb, 2, av, rr)), + # threading.Thread(target=self.single_file_process, args=(data_files[2], wb, 3, av, rr)) + # ] + # [t.start() for t in threads] + # [t.join() for t in threads] + self.now_doing_msg(rawdata_dir, 'done') + + self.logger("INFO", "brake-data_process", f"正在保存文件 {filename},这可能需要一些时间......
", "blue") + wb.save(result_file) + wb.close() + + def processing(self): + time_start = time.time() + clibs.running[self.idx] = 1 + + rawdata_dirs, result_files = clibs.traversal_files(self.dir_path, self.output) + config_file, result_files = self.check_files(rawdata_dirs, result_files) + av, rr = self.get_configs(config_file) + + for result_file in result_files: + self.data_process(result_file, rawdata_dirs, av, rr) + + self.logger("INFO", "brake-processing", "-"*60 + "
全部处理完毕
", "purple") + time_total = time.time() - time_start + msg = f"处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s" + self.logger("INFO", "brake-processing", msg) diff --git a/code/analysis/current.py b/code/analysis/current.py new file mode 100644 index 0000000..533ec1e --- /dev/null +++ b/code/analysis/current.py @@ -0,0 +1,427 @@ +import json +import openpyxl +import pandas +import re +import csv +from PySide6.QtCore import Signal, QThread +import time +from common import clibs + + +class CurrentDataProcess(QThread): + output = Signal(str, str) + + def __init__(self, dir_path, proc, /): + super().__init__() + self.dir_path = dir_path + self.proc = proc + self.idx = 1 + + def logger(self, level, module, content, color="black", error="", flag="both"): + clibs.logger(level, module, content, color, flag, signal=self.output) + if level.upper() == "ERROR": + raise Exception(f"{error} | {content}") + + def initialization(self): + _, data_files = clibs.traversal_files(self.dir_path, self.output) + count, config_file = 0, None + for data_file in data_files: + filename = data_file.split("/")[-1] + if re.match(".*\\.cfg", filename): + config_file = data_file + count += 1 + elif filename == "T_电机电流.xlsx": + count += 1 + else: + if not re.match("^j[1-7].*\\.data$", filename): + msg = f"不合规 {data_file}
" + msg += "所有数据文件必须以 j[1-7]_ 开头,以 .data 结尾,比如j1_abcdef.data,请检查整改后重新运行" + self.logger("ERROR", "current-initialization", msg, "red", "FilenameIllegal") + + if count != 2: + msg = "需要有一个机型配置文件\"*.cfg\",以及一个数据处理文件\"T_电机电流.xlsx\"表格,请检查整改后重新运行" + self.logger("ERROR", "current-initialization", msg, "red", "FilenameIllegal") + + return data_files, config_file + + def current_max(self, data_files, rts): + self.logger("INFO", "current-current_max", f"正在处理最大转矩值逻辑......") + current = {1: [], 2: [], 3: [], 4: [], 5: [], 6: []} + for data_file in data_files: + if data_file.endswith(".data"): + df = pandas.read_csv(data_file, sep="\t") + else: + continue + + self.logger("INFO", "current-current_max", f"正在处理 {data_file} ...") + cols = len(df.columns) + axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j")) + rt = rts[axis-1] + self.logger("INFO", "current-current_max", f"最大列数为 {cols},{axis} 轴的额定转矩为 {rt}") + + col = df.columns.values[clibs.c_servo_trq-1] # 获取 "device_servo_trq_feedback" + c_max = df[col].abs().max() + + scale = 1000 + _ = abs(c_max/scale*rt) + current[axis].append(_) + self.logger("INFO", "current-current_max", f"{data_file}: {_:.2f}") + self.logger("INFO", "current-current_max", f"获取到的列名为 {col},最大转矩为 {_}") + + with open(data_file, "a+") as f_data: + csv_writer = csv.writer(f_data, delimiter="\t") + csv_writer.writerow([""] * (cols-1) + [_]) + + for axis, cur in current.items(): + if not cur: + continue + else: + _ = "" + for value in cur: + _ += f"{value:.4f} " + self.logger("INFO", "current-current_max", f"{axis}轴最大转矩数据:{_}") + + self.logger("INFO", "current-current_max", f"获取最大转矩值结束 current_max = {current}") + self.logger("INFO", "current-current_max", f"最大转矩数据处理完毕......") + return current + + def current_avg(self, data_files, rts): + self.logger("INFO", "current-current_avg", f"正在处理平均转矩值逻辑......") + current = {1: [], 2: [], 3: [], 4: [], 5: [], 6: []} + for data_file in data_files: + if data_file.endswith(".data"): + df = pandas.read_csv(data_file, sep="\t") + else: + continue + + self.logger("INFO", "current-current_avg", f"正在处理 {data_file} ...") + cols = len(df.columns) + axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j")) + rt = rts[axis-1] + self.logger("INFO", "current-current_avg", f"最大列数为 {cols},{axis} 轴的额定转矩为 {rt}") + + col = df.columns.values[clibs.c_servo_trq-1] + c_std = df[col].std() + c_avg = df[col].mean() + + scale = 1000 + _ = (abs(c_avg)+c_std*3)/scale*rt + current[axis].append(_) + self.logger("INFO", "current-current_avg", f"{data_file}: {_:.2f}") + self.logger("INFO", "current-current_avg", f"获取到的列名为 {col},平均转矩为 {_}") + + with open(data_file, "a+") as f_data: + csv_writer = csv.writer(f_data, delimiter="\t") + csv_writer.writerow([""] * (cols-1) + [_]) + + for axis, cur in current.items(): + if not cur: + continue + else: + _ = "" + for value in cur: + _ += f"{value:.4f} " + self.logger("INFO", "current-current_avg", f"{axis}轴平均转矩数据:{_}") + + self.logger("INFO", "current-current_avg", f"获取平均转矩值结束 current_avg = {current}", flag="cursor") + self.logger("INFO", "current-current_avg", f"平均转矩数据处理完毕......") + return current + + def current_cycle(self, data_files, rrs, rts, params): + result, hold, single, scenario, dur_time = None, [], [], [], 0 + for data_file in data_files: + filename = data_file.split("/")[-1] + if filename == "T_电机电流.xlsx": + result = data_file + elif re.match("j[1-7]_hold_.*\\.data", filename): + hold.append(data_file) + elif re.match("j[1-7]_s_.*\\.data", filename): + scenario.append(data_file) + dur_time = float(filename.split("_")[3]) + elif re.match("j[1-7]_.*\\.data", filename): + single.append(data_file) + + clibs.stop, filename = True, result.split("/")[-1] + self.logger("INFO", "current-current_cycle", f"正在打开文件 {filename},这可能需要一些时间......", "blue") + try: + wb = openpyxl.load_workbook(result) + except Exception as Err: + self.logger("ERROR", "current-current_cycle", f"{filename}文件打开失败,可能是文件已损坏,确认后重新执行!
{Err}", "red", "CannotOpenFile") + + ws = wb["统计"] + for idx in range(len(params)-1): + row = idx + 2 + for col in range(2, 8): + ws.cell(row=row, column=col).value = params[idx][col-2] + ws.cell(row=1, column=1).value = params[-1] + + if hold: + avg = self.current_avg(hold, rts) + for axis, cur_value in avg.items(): + sht_name = f"J{axis}" + wb[sht_name]["P4"].value = float(cur_value[0]) + + if dur_time == 0: + self.p_single(wb, single, rrs) + else: + self.p_scenario(wb, scenario, rrs, dur_time) + + self.logger("INFO", "current-current_cycle", f"正在保存文件 {filename},这可能需要一些时间......", "blue") + wb.save(result) + wb.close() + + def find_point(self, data_file, df, flag, row_s, row_e, threshold, step, end_point, skip_scale, axis, seq): + if flag == "lt": + while row_e > end_point: + speed_avg = df.iloc[row_s:row_e].abs().mean() + if speed_avg < threshold: + row_e -= step + row_s -= step + continue + else: + # one more time,如果连续两次 200 个点的平均值都大于 threshold,说明已经到了临界点了(其实也不一定,只不过相对遇到一次就判定临界点更安全一点点) + # 从实际数据看,这开逻辑很小概率能触发到 + speed_avg = df.iloc[row_s-end_point*skip_scale:row_e-end_point*skip_scale].abs().mean() + if speed_avg < threshold: + self.logger("WARNING", "current-find_point", f"【lt】{axis} 轴第 {seq} 次查找数据可能有异常,row_s = {row_s}, row_e = {row_e}!", "purple") + return row_s, row_e + else: + self.logger("ERROR", "current-find_point", f"{data_file} 数据有误,需要检查,无法找到第 {seq} 个有效点......", "red", "AnchorNotFound") + elif flag == "gt": + while row_e > end_point: + speed_avg = df.iloc[row_s:row_e].abs().mean() + # if axis == 1 and seq == 1: + # insert_logdb("DEBUG", "current", f"【gt】{axis} 轴,speed_avg = {speed_avg},row_s = {row_s}, row_e = {row_e}!") + if speed_avg > threshold: + row_e -= step + row_s -= step + continue + else: + # one more time,如果连续两次 200 个点的平均值都小于 threshold,说明已经到了临界点了(其实也不一定,只不过相对遇到一次就判定临界点更安全一点点) + # 从实际数据看,这开逻辑很小概率能触发到 + speed_avg = df.iloc[row_s-end_point*skip_scale:row_e-end_point*skip_scale].abs().mean() + if speed_avg > threshold: + self.logger("WARNING", "current-find_point", f"【gt】{axis} 轴第 {seq} 次查找数据可能有异常,row_s = {row_s}, row_e = {row_e}!", "purple") + return row_s, row_e + else: + self.logger("ERROR", "current-find_point", f"{data_file} 数据有误,需要检查,无法找到第 {seq} 个有效点......", "red", "AnchorNotFound") + + def get_row_number(self, threshold, flag, df, row_s, row_e, axis): + count_1, count_2 = 0, 0 + if flag == "start" or flag == "end": + for number in df.iloc[row_s:row_e].abs(): + count_2 += 1 + if number > threshold: + count_1 += 1 + if count_1 == 10: + return row_s + count_2 - 10 + else: + count_1 = 0 + elif flag == "middle": + for number in df.iloc[row_s:row_e].abs(): + count_2 += 1 + if number < threshold: # 唯一的区别 + count_1 += 1 + if count_1 == 10: + return row_s + count_2 - 10 + else: + count_1 = 0 + + places = {"start": "起点", "middle": "中间点", "end": "终点"} # 因为是终点数据,所以可能有异常 + self.logger("WARNING", "current-get_row_number", f"{axis} 轴获取{places[flag]}数据 {row_e} 可能有异常,需关注!", "purple") + return row_e + + def p_single(self, wb, single, rrs): + # 1. 先找到第一个速度为零的点,数据从后往前找,一开始就是零的情况不予考虑 + # 2. 记录第一个点的位置,继续向前查找第二个速度为零的点,同理,一开始为零的点不予考虑 + # 3. 记录第二个点的位置,并将其中的数据拷贝至对应位置 + for data_file in single: + axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j")) + sht_name = f"J{axis}" + ws = wb[sht_name] + pandas.set_option("display.precision", 2) + df_origin = pandas.read_csv(data_file, sep="\t") + rr = rrs[axis-1] + addition = 180 / 3.1415926 * 60 / 360 * rr + + col_names = list(df_origin.columns) + df = df_origin[col_names[clibs.c_joint_vel-1]].multiply(addition) + + step = 50 # 步进值 + end_point = 200 # 有效数值的数目 + threshold = 5 # 200个点的平均阈值线 + skip_scale = 2 + row_start, row_middle, row_end = 0, 0, 0 + row_e = df.index[-1] + row_s = row_e - end_point + speed_avg = df.iloc[row_s:row_e].abs().mean() + if speed_avg < threshold: + # 第一次过滤:消除速度为零的数据,找到速度即将大于零的上升临界点 + row_s, row_e = self.find_point(data_file, df, "lt", row_s, row_e, threshold, step, end_point, skip_scale, axis, "pre-1") + row_e -= end_point*skip_scale + row_s -= end_point*skip_scale + # 第二次过滤:消除速度大于零的数据,找到速度即将趋近于零的下降临界点 + row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, "pre-2") + row_e -= end_point*skip_scale + row_s -= end_point*skip_scale + # 第三次过滤:消除速度为零的数据,找到速度即将大于零的上升临界点 + row_s, row_e = self.find_point(data_file, df, "lt", row_s, row_e, threshold, step, end_point, skip_scale, axis, "pre-3") + row_e -= end_point*skip_scale + row_s -= end_point*skip_scale + # 正式第一次采集:消除速度大于零的数据,找到速度即将趋近于零的下降临界点 + row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 1) + row_end = self.get_row_number(threshold, "end", df, row_s, row_e, axis) + row_e -= end_point*skip_scale + row_s -= end_point*skip_scale + # 正式第二次采集:消除速度为零的数据,找到速度即将大于零的上升临界点 + row_s, row_e = self.find_point(data_file, df, "lt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 2) + row_middle = self.get_row_number(threshold, "middle", df, row_s, row_e, axis) + row_e -= end_point*skip_scale + row_s -= end_point*skip_scale + # 正式第三次采集:消除速度大于零的数据,找到速度即将趋近于零的下降临界点 + row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 3) + row_start = self.get_row_number(threshold, "start", df, row_s, row_e, axis) + elif speed_avg > threshold: + # 第一次过滤:消除速度大于零的数据,找到速度即将趋近于零的下降临界点 + row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, "pre-1") + row_e -= end_point*skip_scale + row_s -= end_point*skip_scale + # 第二次过滤:消除速度为零的数据,找到速度即将大于零的上升临界点 + row_s, row_e = self.find_point(data_file, df, "lt", row_s, row_e, threshold, step, end_point, skip_scale, axis, "pre-2") + row_e -= end_point*skip_scale + row_s -= end_point*skip_scale + # 第一次正式采集:消除速度大于零的数据,找到速度即将趋近于零的下降临界点 + row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 1) + row_end = self.get_row_number(threshold, "end", df, row_s, row_e, axis) + row_e -= end_point*skip_scale + row_s -= end_point*skip_scale + # 第二次正式采集:消除速度为零的数据,找到速度即将大于零的上升临界点 + row_s, row_e = self.find_point(data_file, df, "lt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 2) + row_middle = self.get_row_number(threshold, "middle", df, row_s, row_e, axis) + row_e -= end_point*skip_scale + row_s -= end_point*skip_scale + # 第三次正式采集:消除速度大于零的数据,找到速度即将趋近于零的下降临界点 + row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 3) + row_start = self.get_row_number(threshold, "start", df, row_s, row_e, axis) + + self.logger("INFO", "current", f"{axis} 轴起点:{row_start}") + self.logger("INFO", "current", f"{axis} 轴中间点:{row_middle}") + self.logger("INFO", "current", f"{axis} 轴终点:{row_end}") + self.logger("INFO", "current", f"{axis} 轴数据非零段点数:{row_middle-row_start+1}") + self.logger("INFO", "current", f"{axis} 轴数据为零段点数:{row_end-row_middle+1}") + if abs(row_end+row_start-2*row_middle) > 1000: + self.logger("WARNING", "current", f"{axis} 轴数据占空比异常!", "purple") + + data, first_c, second_c, third_c, fourth_c = [], clibs.c_joint_vel-1, clibs.c_servo_trq-1, clibs.c_sensor_trq-1, clibs.c_estimate_trans_trq-1 + for row in range(row_start, row_end+1): + data.append(df_origin.iloc[row, first_c]) + data.append(df_origin.iloc[row, second_c]) + data.append(df_origin.iloc[row, third_c]) + data.append(df_origin.iloc[row, fourth_c]) + + i = 0 + for row in ws.iter_rows(min_row=2, min_col=2, max_row=150000, max_col=5): + for cell in row: + try: + if i % 4 == 0: + ws.cell((i//4)+2, 1).value = float(((i//4)+1)/1000) + _ = f"{data[i]:.2f}" + cell.value = float(_) + i += 1 + except Exception: + if i % 4 == 0: + ws.cell((i//4)+2, 1).value = None + cell.value = None + i += 1 + + def p_scenario(self, wb, scenario, rrs, dur_time): + self.logger("INFO", "current", f"本次处理的是电机电流场景数据,场景运动周期为 {dur_time}s", "blue") + for data_file in scenario: + cycle = 0.001 + axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j")) + sht_name = f"J{axis}" + ws = wb[sht_name] + pandas.set_option("display.precision", 2) + df_origin = pandas.read_csv(data_file, sep="\t") + rr = rrs[axis-1] + addition = 180 / 3.1415926 * 60 / 360 * rr + + col_names = list(df_origin.columns) + df = df_origin[col_names[clibs.c_joint_vel-1]].multiply(addition) + + row_start = 3000 + row_end = row_start + int(dur_time/cycle) + if row_end > df.index[-1]: + self.logger("ERROR", "current-p_scenario", f"位置超限:{data_file} 共有 {df.index[-1]} 条数据,无法取到第 {row_end} 条数据,需要确认场景周期时间...", "blue", "DataOverLimit") + + data, first_c, second_c, third_c, fourth_c = [], clibs.c_joint_vel-1, clibs.c_servo_trq-1, clibs.c_sensor_trq-1, clibs.c_estimate_trans_trq-1 + for row in range(row_start, row_end+1): + data.append(df_origin.iloc[row, first_c]) + data.append(df_origin.iloc[row, second_c]) + data.append(df_origin.iloc[row, third_c]) + data.append(df_origin.iloc[row, fourth_c]) + + i = 0 + for row in ws.iter_rows(min_row=2, min_col=2, max_row=250000, max_col=5): + for cell in row: + try: + if i % 4 == 0: + ws.cell((i//4)+2, 1).value = float(((i//4)+1)/1000) + _ = f"{data[i]:.2f}" + cell.value = float(_) + i += 1 + except Exception: + cell.value = None + if i % 4 == 0: + ws.cell((i//4)+2, 1).value = None + i += 1 + + def get_configs(self, config_file): + try: + if re.match("^[NXEC]B.*", config_file.split("/")[-1]): + robot_type = "工业" + else: + robot_type = "协作" + + with open(config_file, mode="r", encoding="utf-8") as f_config: + configs = json.load(f_config) + + version = configs["VERSION"] + sc = [0.001, 0.001, 0.001, 0.001, 0.001, 0.001] # 采样周期,sc for sample cycle + r_rrs = configs["TRANSMISSION"]["REDUCTION_RATIO_NUMERATOR"] # 减速比,rr for reduction ratio + m_avs = configs["MOTION"]["JOINT_MAX_SPEED"] + m_stall_ts = configs["MOTOR"]["STALL_TORQUE"] # 电机堵转转矩 + m_rts = configs["MOTOR"]["RATED_TORQUE"] # 电机额定转矩rt for rated torque + m_max_ts = configs["MOTOR"]["PEAK_TORQUE"] # 电机峰值转矩 + m_r_rpms = configs["MOTOR"]["RATED_SPEED"] # 电机额定转速 + m_max_rpms = configs["MOTOR"]["MAX_SPEED"] # 电机最大转速 + r_max_sst = configs["TRANSMISSION"]["MAX_TORQUE_FOR_START_AND_STOP"] # 减速器最大启停转矩,sst for start and stop torque + r_max_t = configs["TRANSMISSION"]["MAX_PEAK_TORQUE"] # 减速器瞬时最大转矩 + r_avg_t = configs["TRANSMISSION"]["MAX_AVERAGE_TORQUE"] # 减速器平均负载转矩允许最大值 + + self.logger("INFO", "current", f"get_configs: 机型文件版本 {config_file}_{version}") + self.logger("INFO", "current", f"get_configs: 减速比 {r_rrs}") + self.logger("INFO", "current", f"get_configs: 额定转矩 {m_rts}") + self.logger("INFO", "current", f"get_configs: 最大角速度 {m_avs}") + return sc, r_rrs, m_avs, m_stall_ts, m_rts, m_max_ts, m_r_rpms, m_max_rpms, r_max_sst, r_max_t, r_avg_t, robot_type + except Exception as Err: + self.logger("ERROR", "current", f"get_config: 无法打开 {config_file},或获取配置文件参数错误 {Err}", "red", "OpenFileError") + + def processing(self): + time_start = time.time() + clibs.running[self.idx] = 1 + + data_files, config_file = self.initialization() + params = self.get_configs(config_file) + rts, rrs = params[4], params[1] + if self.proc == "最大值": + self.current_max(data_files, rts) + elif self.proc == "平均值": + self.current_avg(data_files, rts) + elif self.proc == "周期": + self.current_cycle(data_files, rrs, rts, params) + + self.logger("INFO", "current-processing", "-"*60 + "
全部处理完毕
", "purple") + time_total = time.time() - time_start + msg = f"数据处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s\n" + self.logger("INFO", "current-processing", msg) diff --git a/code/analysis/iso.py b/code/analysis/iso.py new file mode 100644 index 0000000..2aeacda --- /dev/null +++ b/code/analysis/iso.py @@ -0,0 +1,213 @@ +import pdfplumber +import openpyxl +import os +import time +from PySide6.QtCore import Signal, QThread +from common import clibs + + +class IsoDataProcess(QThread): + output = Signal(str, str) + + def __init__(self, dir_path, /): + super().__init__() + self.dir_path = dir_path + self.idx = 2 + + def logger(self, level, module, content, color="black", error="", flag="both"): + clibs.logger(level, module, content, color, flag, signal=self.output) + if level.upper() == "ERROR": + raise Exception(f"{error} | {content}") + + def p_iso(self, file, p_files, ws, tmpfile): + p_files.append(file) + + pdf = pdfplumber.open(file) + with open(tmpfile, mode="w", encoding="utf-8") as fb: + for page in pdf.pages: + fb.write(page.extract_text()) + with open(tmpfile, mode="r", encoding="utf-8") as fb: + lines = fb.readlines() + lines = [line for line in lines if not line.startswith("Page ")] + for line in lines: + if line.strip() == "Pose Accuracy and Repeatability": + index = lines.index(line) + ws.cell(row=3, column=7).value = float(lines[index+4].split()[1]) + ws.cell(row=4, column=7).value = float(lines[index+5].split()[1]) + ws.cell(row=5, column=7).value = float(lines[index+6].split()[1]) + ws.cell(row=6, column=7).value = float(lines[index+7].split()[1]) + ws.cell(row=7, column=7).value = float(lines[index+8].split()[1]) + + ws.cell(row=8, column=7).value = float(lines[index+4].split()[2]) + ws.cell(row=9, column=7).value = float(lines[index+5].split()[2]) + ws.cell(row=10, column=7).value = float(lines[index+6].split()[2]) + ws.cell(row=11, column=7).value = float(lines[index+7].split()[2]) + ws.cell(row=12, column=7).value = float(lines[index+8].split()[2]) + elif line.strip() == "Pose Accuracy Variation": + index = lines.index(line) + ws.cell(row=13, column=7).value = float(lines[index+4].split()[1]) + ws.cell(row=14, column=7).value = float(lines[index+5].split()[1]) + ws.cell(row=15, column=7).value = float(lines[index+6].split()[1]) + elif line.strip() == "Distance Accuracy": + index = lines.index(line) + ws.cell(row=16, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=17, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == "Stabilisation Time and Overshoot": + index = lines.index(line) + ws.cell(row=18, column=7).value = float(lines[index + 7].split()[3]) + ws.cell(row=19, column=7).value = float(lines[index + 7].split()[2]) + elif line.strip() == "Velocity Accuracy and Repeatability": + index = lines.index(line) + ws.cell(row=20, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=21, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=22, column=7).value = float(lines[index + 4].split()[3]) + elif line.strip()[:31] == "Path Accuracy and Repeatability": + index = lines.index(line) + ws.cell(row=29, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=30, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == "Corner Overshoot and Roundoff": + index = lines.index(line) + ws.cell(row=35, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=36, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == "Robot Weaving": + index = lines.index(line) + ws.cell(row=41, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=42, column=7).value = float(lines[index + 4].split()[3]) + ws.cell(row=43, column=7).value = float(lines[index + 4].split()[4]) + else: + pass + pdf.close() + + def p_iso_100(self, file, p_files, ws, tmpfile): + p_files.append(file) + + pdf = pdfplumber.open(file) + with open(tmpfile, mode="w", encoding="utf-8") as fb: + for page in pdf.pages: + fb.write(page.extract_text()) + with open(tmpfile, mode="r", encoding="utf-8") as fb: + lines = fb.readlines() + lines = [line for line in lines if not line.startswith("Page ")] + for line in lines: + if line.strip() == "Velocity Accuracy and Repeatability": + index = lines.index(line) + ws.cell(row=26, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=27, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=28, column=7).value = float(lines[index + 4].split()[3]) + elif line.strip()[:31] == "Path Accuracy and Repeatability": + index = lines.index(line) + ws.cell(row=33, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=34, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == "Corner Overshoot and Roundoff": + index = lines.index(line) + ws.cell(row=39, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=40, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == "Robot Weaving": + index = lines.index(line) + ws.cell(row=47, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=48, column=7).value = float(lines[index + 4].split()[3]) + ws.cell(row=49, column=7).value = float(lines[index + 4].split()[4]) + else: + pass + pdf.close() + + def p_iso_1000(self, file, p_files, ws, tmpfile): + p_files.append(file) + + pdf = pdfplumber.open(file) + with open(tmpfile, mode="w", encoding="utf-8") as fb: + for page in pdf.pages: + fb.write(page.extract_text()) + with open(tmpfile, mode="r", encoding="utf-8") as fb: + lines = fb.readlines() + lines = [line for line in lines if not line.startswith("Page ")] + for line in lines: + if line.strip() == "Velocity Accuracy and Repeatability": + index = lines.index(line) + ws.cell(row=23, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=24, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=25, column=7).value = float(lines[index + 4].split()[3]) + elif line.strip()[:31] == "Path Accuracy and Repeatability": + index = lines.index(line) + ws.cell(row=31, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=32, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == "Corner Overshoot and Roundoff": + index = lines.index(line) + ws.cell(row=37, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=38, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == "Robot Weaving": + index = lines.index(line) + ws.cell(row=44, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=45, column=7).value = float(lines[index + 4].split()[3]) + ws.cell(row=46, column=7).value = float(lines[index + 4].split()[4]) + else: + pass + pdf.close() + + def initialization(self): + dirs, files = clibs.traversal_files(self.dir_path, self.output) + if len(dirs) != 0: + self.logger("ERROR", "iso", f"init: 工作目录下不可以有文件夹!", "red", "InitFileError") + + for file in files: + file = file.lower() + if file.endswith("iso-results.xlsx"): + pass + elif file.endswith("iso-v1000.pdf"): + pass + elif file.endswith("iso-v100.pdf"): + pass + elif file.endswith("iso.pdf"): + pass + else: + self.logger("ERROR", "iso", f"init: 工作目录下只允许有如下四个文件,不区分大小写,pdf文件最少有一个!
1. iso-results.xlsx
2. ISO.pdf
3. ISO-V100.pdf
4. ISO-V1000.pdf", "red", "InitFileError") + + return files + + def processing(self): + time_start = time.time() + clibs.running[self.idx] = 1 + + files = self.initialization() + filename = f"{self.dir_path}/iso-results.xlsx" + tmpfile = f"{self.dir_path}/data.txt" + wb, ws = None, None + try: + wb = openpyxl.load_workbook(filename) + ws = wb.active + for i in range(3, 50): + ws.cell(row=i, column=7).value = None + except Exception as Err: + self.logger("ERROR", "iso", f"main: 无法打开文件 {filename}
{Err}", "red", "FileOpenError") + + p_files = [] + for file in files: + if file.split("/")[-1].lower() == "iso.pdf": + self.logger("INFO", "iso", f"正在处理{file}......") + self.p_iso(file, p_files, ws, tmpfile) + self.logger("INFO", "iso", f"文件{file}已处理完毕。") + + elif file.split("/")[-1].lower() == "iso-v100.pdf": + self.logger("INFO", "iso", f"正在处理{file}......") + self.p_iso_100(file, p_files, ws, tmpfile) + self.logger("INFO", "iso", f"文件{file}已处理完毕。") + + elif file.split("/")[-1].lower() == "iso-v1000.pdf": + self.logger("INFO", "iso", f"正在处理{file}......") + self.p_iso_1000(file, p_files, ws, tmpfile) + self.logger("INFO", "iso", f"文件{file}已处理完毕。") + + else: + pass + wb.save(filename) + wb.close() + + if len(p_files) == 0: + self.logger("ERROR", "iso", f"目录 {self.dir_path} 下没有需要处理的文件,需要确认......", "red", "FileNotFound") + else: + os.remove(tmpfile) + + self.logger("INFO", "current-processing", "-" * 60 + "
全部处理完毕
", "purple") + time_total = time.time() - time_start + msg = f"数据处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s\n" + self.logger("INFO", "current-processing", msg) diff --git a/code/analysis/wavelogger.py b/code/analysis/wavelogger.py new file mode 100644 index 0000000..1a3422f --- /dev/null +++ b/code/analysis/wavelogger.py @@ -0,0 +1,160 @@ +import pandas +import csv +import openpyxl +import chardet +import time +from PySide6.QtCore import Signal, QThread +from common import clibs + + +class WaveloggerDataProcess(QThread): + output = Signal(str, str) + + def __init__(self, dir_path, /): + super().__init__() + self.dir_path = dir_path + self.idx = 3 + + def logger(self, level, module, content, color="black", error="", flag="both"): + clibs.logger(level, module, content, color, flag, signal=self.output) + if level.upper() == "ERROR": + raise Exception(f"{error} | {content}") + + def find_point(self, bof, step, margin, threshold, pos, data_file, flag, df, row): + # bof: backward or forward + # pos: used for debug + # flag: greater than or lower than + row_target = None + row_origin = len(df) - margin + 1 + if flag == "gt": + while 0 < row < row_origin: + value = float(df.iloc[row, 2]) + if value > threshold: + row = row - step if bof == "backward" else row + step + continue + else: + row_target = row - step if bof == "backward" else row + step + break + else: + if bof == "backward": + self.logger("ERROR", "wavelogger-find_point", f"find_point-gt: [{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...", "red", "DataError") + elif bof == "forward": + row_target = row + margin # to end while loop in function `single_file_proc` + elif flag == "lt": + while 0 < row < row_origin: + value = float(df.iloc[row, 2]) + if value < threshold: + row = row - step if bof == "backward" else row + step + continue + else: + row_target = row - step if bof == "backward" else row + step + break + else: + if bof == "backward": + self.logger("ERROR", "wavelogger-find_point", f"find_point-lt: [{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...", "red", "DataError") + elif bof == "forward": + row_target = row + margin # to end while loop in function `single_file_proc` + return row_target + + def get_cycle_info(self, data_file, step, margin, threshold): + # end -> middle: low + # middle -> start: high + # 1. 从最后读取数据,无论是大于1还是小于1,都舍弃,找到相反的值的起始点 + # 2. 从起始点,继续往前寻找,找到与之数值相反的中间点 + # 3. 从中间点,继续往前寻找,找到与之数值相反的结束点,至此,得到了高低数值的时间区间以及一轮的周期时间 + with open(data_file, "rb") as f: + raw_data = f.read(1000) + result = chardet.detect(raw_data) + encoding = result['encoding'] + csv_reader = csv.reader(open(data_file, encoding=encoding)) + begin = int(next(csv_reader)[1]) + df = pandas.read_csv(data_file, sep=",", encoding=encoding, skip_blank_lines=False, header=begin - 1, on_bad_lines="skip") + row = len(df) - margin + if float(df.iloc[row, 2]) < threshold: + row = self.find_point("backward", step, margin, threshold, "a1", data_file, "lt", df, row) + + _row = self.find_point("backward", step, margin, threshold, "a2", data_file, "gt", df, row) + _row = self.find_point("backward", step, margin, threshold, "a3", data_file, "lt", df, _row) + row_end = self.find_point("backward", step, margin, threshold, "a4", data_file, "gt", df, _row) + row_middle = self.find_point("backward", step, margin, threshold, "a5", data_file, "lt", df, row_end) + row_start = self.find_point("backward", step, margin, threshold, "a6", data_file, "gt", df, row_middle) + # print(f"row_end = {row_end}") + # print(f"row_middle = {row_middle}") + # print(f"row_start = {row_start}") + return row_end-row_middle, row_middle-row_start, row_end-row_start, df + + def initialization(self): + _, data_files = clibs.traversal_files(self.dir_path, self.output) + + for data_file in data_files: + if not data_file.lower().endswith(".csv"): + self.logger("ERROR", "wavelogger-initialization", f"init: {data_file} 文件后缀错误,只允许 .csv 文件,需要确认!", "red", "FileTypeError") + + return data_files + + def preparation(self, data_file, step, margin, threshold, wb): + shtname = data_file.split("/")[-1].split(".")[0] + ws = wb.create_sheet(shtname) + low, high, cycle, df = self.get_cycle_info(data_file, step, margin, threshold) + + return ws, df, low, high, cycle + + def single_file_proc(self, ws, data_file, step, threshold, margin, data_length, df, cycle): + row, row_lt, row_gt, count, count_i, data = 1, 1, 1, 1, 1, {} + row_max = len(df) - margin + while row < row_max: + if count not in data.keys(): + data[count] = [] + + value = float(df.iloc[row, 2]) + if value < threshold: + row_lt = self.find_point("forward", step, margin, threshold, "c"+str(row), data_file, "lt", df, row) + start = int(row_gt + (row_lt - row_gt - data_length) / 2) + end = start + data_length + value = df.iloc[start:end, 2].astype(float).mean() + 3 * df.iloc[start:end, 2].astype(float).std() + if value > 1: + msg = f"\n" + self.logger("WARNING", "wavelogger-single_file_proc", f"{data_file} 文件第 {count} 轮 第 {count_i} 个数据可能有问题,需人工手动确认,确认有问题可删除,无问题则保留", "purple") + + data[count].append(value) + count_i += 1 + else: + row_gt = self.find_point("forward", step, margin, threshold, "c"+str(row), data_file, "gt", df, row) + if row_gt - row_lt > cycle * 2: + count += 1 + count_i = 1 + row = max(row_gt, row_lt) + for i in range(2, 10): + ws.cell(row=1, column=i).value = f"第{i-1}次测试" + ws.cell(row=i, column=1).value = f"第{i-1}次精度变化" + + for i in sorted(data.keys()): + row, column = 2, i + 1 + for value in data[i]: + ws.cell(row=row, column=column).value = float(value) + row += 1 + + def execution(self, data_files): + self.logger("INFO", "wavelogger-execution", "正在处理中......", "blue") + wb = openpyxl.Workbook() + step, margin, data_length, threshold = 5, 50, 50, 5 + for data_file in data_files: + ws, df, low, high, cycle = self.preparation(data_file, step, margin, threshold, wb) + self.single_file_proc(ws, data_file, step, threshold, margin, data_length, df, cycle) + + wd = "/".join(data_files[0].split("/")[:-1]) + filename = wd + "/result.xlsx" + wb.save(filename) + wb.close() + + def processing(self): + time_start = time.time() + clibs.running[self.idx] = 1 + + data_files = self.initialization() + self.execution(data_files) + + self.logger("INFO", "wavelogger-processing", "-" * 60 + "
全部处理完毕
", "purple") + time_total = time.time() - time_start + msg = f"数据处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s\n" + self.logger("INFO", "wavelogger-processing", msg) diff --git a/code/autotest/do_brake.py b/code/autotest/do_brake.py new file mode 100644 index 0000000..b30a5fb --- /dev/null +++ b/code/autotest/do_brake.py @@ -0,0 +1,374 @@ +import time +import os +import paramiko +import openpyxl +import pandas +import json +from PySide6.QtCore import Signal, QThread +from common import clibs + + +class DoBrakeTest(QThread): + output = Signal(str, str) + + def __init__(self, dir_path, tool, /): + super().__init__() + self.dir_path = dir_path + self.tool = tool + self.idx = 4 + + def logger(self, level, module, content, color="black", error="", flag="both"): + clibs.logger(level, module, content, color, flag, signal=self.output) + if level.upper() == "ERROR": + raise Exception(f"{error} | {content}") + + def initialization(self, data_dirs, data_files): + def check_files(): + msg = "初始路径下不允许有文件夹,初始路径下只能存在如下五个文件,且文件为关闭状态,确认后重新运行!
" + msg += "1. configs.xlsx
2. reach33/reach66/reach100_xxxx.xlsx
3. xxxx.zip" + if len(data_dirs) != 0 or len(data_files) != 5: + self.logger("ERROR", "do_brake-check_files", msg, "red", "InitFileError") + + config_file, reach33_file, reach66_file, reach100_file, prj_file, result_dirs = None, None, None, None, None, [] + for data_file in data_files: + filename = data_file.split("/")[-1] + if filename == "configs.xlsx": + config_file = data_file + elif filename.startswith("reach33_") and filename.endswith(".xlsx"): + reach33_file = data_file + elif filename.startswith("reach66_") and filename.endswith(".xlsx"): + reach66_file = data_file + elif filename.startswith("reach100_") and filename.endswith(".xlsx"): + reach100_file = data_file + elif filename.endswith(".zip"): + prj_file = data_file + else: + self.logger("ERROR", "do_brake-check_files", msg, "red", "InitFileError") + + if config_file and reach33_file and reach66_file and reach100_file and prj_file: + os.mkdir(f"{self.dir_path}/j1") + os.mkdir(f"{self.dir_path}/j2") + os.mkdir(f"{self.dir_path}/j3") + + load = f"load{self.tool.removeprefix('tool')}" + for reach in ["reach33", "reach66", "reach100"]: + for speed in ["speed33", "speed66", "speed100"]: + dir_name = "_".join([reach, load, speed]) + result_dirs.append(dir_name) + os.mkdir(f"{self.dir_path}/j1/{dir_name}") + os.mkdir(f"{self.dir_path}/j2/{dir_name}") + if reach == "reach100": + os.mkdir(f"{self.dir_path}/j3/{dir_name}") + + self.logger("INFO", "do_brake-check_files", "数据目录合规性检查结束,未发现问题......", "green") + return config_file, prj_file, result_dirs + else: + self.logger("ERROR", "do_brake-check_files", msg, "red", "InitFileError") + + def get_configs(): + robot_type = None + msg_id, state = clibs.c_hr.execution("controller.get_params") + records = clibs.c_hr.get_from_id(msg_id, state) + for record in records: + if "请求发送成功" not in record[0]: + robot_type = eval(record[0])["data"]["robot_type"] + server_file = f"/home/luoshi/bin/controller/robot_cfg/{robot_type}/{robot_type}.cfg" + local_file = self.dir_path + f"/{robot_type}.cfg" + clibs.c_pd.pull_file_from_server(server_file, local_file) + + try: + with open(local_file, mode="r", encoding="utf-8") as f_config: + configs = json.load(f_config) + except Exception as Err: + self.logger("ERROR", "do_brake-get_configs", f"无法打开 {local_file}
{Err}", "red", "OpenFileError") + + # 最大角速度,额定电流,减速比,额定转速 + version = configs["VERSION"] + avs = configs["MOTION"]["JOINT_MAX_SPEED"] + clibs.insert_logdb("INFO", "do_brake", f"get_configs: 机型文件版本 {robot_type}_{version}") + clibs.insert_logdb("INFO", "do_brake", f"get_configs: 各关节角速度 {avs}") + return avs + + _config_file, _prj_file, _result_dirs = check_files() + _avs = get_configs() + + return _config_file, _prj_file, _result_dirs, _avs + + def gen_result_file(self, axis, t_end, reach, load, speed, speed_max, rounds): + d_vel, d_trq, d_stop, threshold = [], [], [], 0.95 + + start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t_end-12)) + end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t_end)) + try: + clibs.lock.acquire(True) + clibs.cursor.execute(f"select content from logs where time between '{start_time}' and '{end_time}' and content like '%diagnosis.result%' order by id asc") + records = clibs.cursor.fetchall() + finally: + clibs.lock.release() + + for record in records: # 保留最后12s的数据 + data = eval(record[0])["data"] + for item in data: + d_item = reversed(item["value"]) + if item.get("channel", None) == axis-1 and item.get("name", None) == "hw_joint_vel_feedback": + d_vel.extend(d_item) + elif item.get("channel", None) == axis-1 and item.get("name", None) == "device_servo_trq_feedback": + d_trq.extend(d_item) + elif item.get("channel", None) == 0 and item.get("name", None) == "device_safety_estop": + d_stop.extend(d_item) + + idx = 0 + for idx in range(len(d_stop)-10, 0, -1): + if d_stop[idx] == 1: + break + + av_estop = abs(sum(d_vel[idx - 20:idx])/20 * clibs.RADIAN) + if av_estop / speed_max < threshold: + self.logger("WARNING", "do_brake-gen_result_file", f"[av_estop: {av_estop:.2f} | shouldbe: {speed_max:.2f}] 处理数据时,本次触发 ESTOP 时未采集到指定百分比的最大速度,即将重试!", "#8A2BE2") + clibs.count += 1 + if clibs.count < 3: + return "retry" + else: + clibs.count = 0 + self.logger("WARNING", "do_brake-gen_result_file",f"尝试三次后仍无法获取正确数据,本次数据无效,继续执行...", "red") + + df1 = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_vel}) + df2 = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq}) + df3 = pandas.DataFrame.from_dict({"device_safety_estop": d_stop}) + df = pandas.concat([df1, df2, df3], axis=1) + filename = f"{self.dir_path}/j{axis}/reach{reach}_load{load}_speed{speed}/reach{reach}_load{load}_speed{speed}_{rounds}.data" + df.to_csv(filename, sep="\t", index=False) + + @staticmethod + def change_curve_state(stat): + if not stat: + display_pdo_params = [] + else: + display_pdo_params = [{"name": name, "channel": chl} for name in ["hw_joint_vel_feedback", "device_servo_trq_feedback"] for chl in range(6)] + display_pdo_params.append({"name": "device_safety_estop", "channel": 0}) + clibs.c_hr.execution("diagnosis.open", open=stat, display_open=stat, overrun=True, turn_area=True, delay_motion=False) + clibs.c_hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params, frequency=50, version="1.4.1") + + def run_rl(self, config_file, prj_file, result_dirs, avs): + count, total, speed_target = 0, 63, 0 + prj_name = ".".join(prj_file.split("/")[-1].split(".")[:-1]) + wb = openpyxl.load_workbook(config_file, read_only=True) + ws = wb["Target"] + write_diagnosis = float(ws.cell(row=2, column=2).value) + get_init_speed = float(ws.cell(row=3, column=2).value) + single_brake = str(ws.cell(row=4, column=2).value) + pon = ws.cell(row=5, column=2).value + io_name = ws.cell(row=6, column=2).value.upper().strip() + wb.close() + msg = f"基本参数配置:write_diagnosis(废弃) = {write_diagnosis}, get_init_speed = {get_init_speed}, single_brake = {single_brake}, pon = {pon}" + self.logger("INFO", "do_brake-run_rl", msg) + + if pon == "positive": + clibs.c_md.write_pon(1) + elif pon == "negative": + clibs.c_md.write_pon(0) + else: + self.logger("ERROR", "do_brake-run_rl", "configs.xlsx 中 Target 页面 B5 单元格填写不正确,检查后重新运行...", "red", "DirectionError") + + self.change_curve_state(True) + for condition in result_dirs: + reach = condition.split("_")[0].removeprefix("reach") + load = condition.split("_")[1].removeprefix("load") + speed = condition.split("_")[2].removeprefix("speed") + + # for single condition test + single_axis = -1 + if single_brake != "0": + total = 3 + single_axis = int(single_brake.split("-")[0]) + if reach != single_brake.split("-")[1] or load != single_brake.split("-")[2] or speed != single_brake.split("-")[3]: + continue + + for axis in range(1, 4): + # for single condition test + if (single_axis != -1 and single_axis != axis) or (axis == 3 and reach != "100"): + continue + + clibs.c_md.write_axis(axis) + self.logger("INFO", "brake-processing", "-" * 90, "purple", flag="signal") + speed_max = 0 + for rounds in range(1, 4): + count += 1 + _ = 3 if count % 3 == 0 else count % 3 + this_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + prj_path = f"{prj_name}/_build/{prj_name}.prj" + msg = f"[{this_time} | {count}/{total}] 正在执行 {axis} 轴 {condition} 的第 {_} 次制动测试..." + self.logger("INFO", "do_brake-run_rl", msg) + + # 1. 触发软急停,并解除,目的是让可能正在运行着的机器停下来,切手动模式并下电 + clibs.c_md.r_soft_estop(0) + clibs.c_md.r_soft_estop(1) + clibs.c_ec.setdo_value(io_name, "true") + clibs.c_md.r_reset_estop() + clibs.c_md.r_clear_alarm() + clibs.c_md.write_act(0) + + while count % 3 == 1: + # 2. 修改要执行的场景 + rl_cmd = "" + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(hostname=clibs.ip_addr, port=clibs.ssh_port, username=clibs.username, password=clibs.password) + if pon == "positive": + rl_cmd = f"brake_E(j{axis}_{reach}_p, j{axis}_{reach}_n, p_speed, p_tool)" + elif pon == "negative": + rl_cmd = f"brake_E(j{axis}_{reach}_n, j{axis}_{reach}_p, p_speed, p_tool)" + rl_speed = f"VelSet {speed}" + rl_tool = f"tool p_tool = {self.tool}" + cmd = "cd /home/luoshi/bin/controller/; " + cmd += f'sudo sed -i "/brake_E/d" projects/{prj_name}/_build/brake/main.mod; ' + cmd += f'sudo sed -i "/DONOTDELETE/i {rl_cmd}" projects/{prj_name}/_build/brake/main.mod; ' + cmd += f'sudo sed -i "/VelSet/d" projects/{prj_name}/_build/brake/main.mod; ' + cmd += f'sudo sed -i "/MoveAbsJ/i {rl_speed}" projects/{prj_name}/_build/brake/main.mod; ' + cmd += f'sudo sed -i "/tool p_tool/d" projects/{prj_name}/_build/brake/main.mod; ' + cmd += f'sudo sed -i "/VelSet/i {rl_tool}" projects/{prj_name}/_build/brake/main.mod; ' + stdin, stdout, stderr = ssh.exec_command(cmd, get_pty=True) + stdin.write(clibs.password + "\n") + stdout.read().decode() # 需要read一下才能正常执行 + stderr.read().decode() + + # 3. reload工程后,pp2main,并且自动模式和上电,最后运行程序 + clibs.c_hr.execution("overview.reload", prj_path=prj_path, tasks=["brake"]) + clibs.c_hr.execution("rl_task.pp_to_main", tasks=["brake"]) + clibs.c_hr.execution("state.switch_auto") + clibs.c_hr.execution("state.switch_motor_on") + clibs.c_hr.execution("rl_task.set_run_params", loop_mode=True, override=1.0) + clibs.c_hr.execution("rl_task.run", tasks=["brake"]) + t_start = time.time() + while True: + if clibs.c_md.read_ready_to_go() == 1: + clibs.c_md.write_act(True) + break + else: + time.sleep(1) + if (time.time() - t_start) > 15: + self.logger("ERROR", "do_brake-run_rl", "15s 内未收到机器人的运行信号,需要确认 RL 程序编写正确并正常执行...", "red", "ReadySignalTimeoutError") + # 4. 找出最大速度,传递给RL程序,最后清除相关记录 + time.sleep(5) # 消除前 5s 的不稳定数据 + start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + time.sleep(get_init_speed) # 指定时间后获取实际【正|负】方向的最大速度,可通过configs.xlsx配置 + end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + clibs.c_hr.execution("rl_task.stop", tasks=["brake"]) + + # 找出最大速度 + @clibs.db_lock + def get_speed_max(): + _speed_max = 0 + clibs.cursor.execute(f"select content from logs where time between '{start_time}' and '{end_time}' and content like '%diagnosis.result%' order by id asc") + records = clibs.cursor.fetchall() + for record in records: + data = eval(record[0])["data"] + for item in data: + if item.get("channel", None) == axis-1 and item.get("name", None) == "hw_joint_vel_feedback": + _ = clibs.RADIAN * sum(item["value"]) / len(item["value"]) + if pon == "positive": + _speed_max = max(_, _speed_max) + elif pon == "negative": + _speed_max = min(_, _speed_max) + return _speed_max + + speed_max = abs(get_speed_max()) + speed_target = avs[axis-1] * float(speed) / 100 + clibs.insert_logdb("INFO", "do_brake", f"axis = {axis}, direction = {pon}, max speed = {speed_max}") + if speed_max < speed_target*0.95 or speed_max > speed_target*1.05: + self.logger("WARNING", "do_brake-run_rl", f"Axis: {axis}-{count} | 采集获取最大 Speed: {speed_max} | Shouldbe: {speed_target}", "indigo") + clibs.insert_logdb("WARNING", "do_brake", f"Axis: {axis}-{count} | 采集获取最大 Speed: {speed_max} | Shouldbe: {speed_target}") + clibs.c_md.write_speed_max(speed_max) + + if speed_max < 10: + clibs.c_md.r_clear_alarm() + self.logger("WARNING", "do_brake-run_rl", f"未获取到正确的速度,即将重新获取...", "red") + continue + else: + break + + while 1: + clibs.c_ec.setdo_value(io_name, "true") + clibs.c_md.r_reset_estop() + clibs.c_md.r_clear_alarm() + clibs.c_md.write_act(0) + # 5. 重新运行程序,发送继续运动信号,当速度达到最大值时,通过DO触发急停 + clibs.c_hr.execution("rl_task.pp_to_main", tasks=["brake"]) + clibs.c_hr.execution("state.switch_auto") + clibs.c_hr.execution("state.switch_motor_on") + t_start = time.time() + while 1: + clibs.c_md.r_clear_alarm() + clibs.c_hr.execution("rl_task.run", tasks=["brake"]) + time.sleep(1) + if clibs.c_md.w_program_state == 1: + break + else: + time.sleep(5) + if time.time() - t_start > 60: + self.logger("ERROR", "do_brake-run_rl","60s 内程序未能正常执行,需检查...", "red", "RlProgramStartTimeout") + + for i in range(16): + if clibs.c_md.read_ready_to_go() == 1: + clibs.c_md.write_act(1) + break + else: + time.sleep(1) + else: + self.logger("ERROR", "do_brake-run_rl", "16s 内未收到机器人的运行信号,需要确认 RL 程序配置正确并正常执行...", "red", "ReadySignalTimeoutError") + + def exec_brake(): + flag, start, data, record = True, time.time(), None, None + while flag: + time.sleep(0.05) + if time.time() - start > 20: + self.logger("ERROR", "do_brake-exec_brake", "20s 内未触发急停,需排查......", "red", "BrakeTimeoutError") + + try: + clibs.lock.acquire(True) + clibs.cursor.execute(f"select content from logs where content like '%diagnosis.result%' order by id desc limit 1") + record = clibs.cursor.fetchone() + data = eval(record[0])["data"] + finally: + clibs.lock.release() + + for item in data: + if item.get("channel", None) != axis-1 or item.get("name", None) != "hw_joint_vel_feedback": + continue + + speed_moment = clibs.RADIAN * sum(item["value"]) / len(item["value"]) + if abs(speed_moment) > speed_max - 2: + if (pon == "positive" and speed_moment > 0) or (pon == "negative" and speed_moment < 0): + clibs.c_ec.setdo_value(io_name, "false") + time.sleep(2) + flag = False + break + return time.time() + + time.sleep(11) # 排除从其他位姿到零点位姿,再到轴极限位姿的时间 + t_end = exec_brake() + # 6. 保留数据并处理输出 + ret = self.gen_result_file(axis, t_end, reach, load, speed, speed_max, rounds) + if ret != "retry": + clibs.count = 0 + break + + else: + time.sleep(50) # why? + self.change_curve_state(False) + msg = f"\n{self.tool.removeprefix('tool')}%负载的制动性能测试执行完毕,如需采集其他负载,须切换负载类型,并更换其他负载,重新执行" + self.logger("INFO", "do_brake-run_rl", msg, "green") + + def processing(self): + time_start = time.time() + clibs.running[self.idx] = 1 + + data_dirs, data_files = clibs.traversal_files(self.dir_path, self.output) + config_file, prj_file, result_dirs, avs = self.initialization(data_dirs, data_files) + clibs.c_pd.push_prj_to_server(prj_file) + self.run_rl(config_file, prj_file, result_dirs, avs) + + self.logger("INFO", "brake-processing", "-"*60 + "
全部处理完毕
", "purple") + time_total = time.time() - time_start + msg = f"处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s" + self.logger("INFO", "brake-processing", msg) diff --git a/code/autotest/do_current.py b/code/autotest/do_current.py new file mode 100644 index 0000000..0901d33 --- /dev/null +++ b/code/autotest/do_current.py @@ -0,0 +1,268 @@ +import os +import threading +import time +import paramiko +import pandas +from PySide6.QtCore import Signal, QThread +from common import clibs + + +class DoBrakeTest(QThread): + output = Signal(str, str) + + def __init__(self, dir_path, tool, /): + super().__init__() + self.dir_path = dir_path + self.tool = tool + self.idx = 5 + + def logger(self, level, module, content, color="black", error="", flag="both"): + clibs.logger(level, module, content, color, flag, signal=self.output) + if level.upper() == "ERROR": + raise Exception(f"{error} | {content}") + + def initialization(self, data_dirs, data_files): + def check_files(): + msg = "初始路径下不允许有文件夹,初始路径下只能存在如下两个文件,且文件为关闭状态,确认后重新运行!
" + msg += "1. T_电机电流.xlsx
2. xxxx.zip" + if len(data_dirs) != 0 or len(data_files) != 2: + self.logger("ERROR", "do_current-check_files", msg, "red", "InitFileError") + + prj_file, count = None, 0 + for data_file in data_files: + filename = data_file.split("/")[-1] + if filename == "T_电机电流.xlsx": + count += 1 + elif filename.endswith(".zip"): + count += 1 + prj_file = data_file + else: + self.logger("ERROR", "do_current-check_files", msg, "red", "InitFileError") + + if count != 2: + self.logger("ERROR", "do_current-check_files", msg, "red", "InitFileError") + + self.logger("INFO", "do_current-check_files", "数据目录合规性检查结束,未发现问题......", "green") + if self.tool == "tool100": + os.mkdir(f"{self.dir_path}/single") + os.mkdir(f"{self.dir_path}/s_1") + os.mkdir(f"{self.dir_path}/s_2") + os.mkdir(f"{self.dir_path}/s_3") + elif self.tool == "inertia": + os.mkdir(f"{self.dir_path}/inertia") + else: + self.logger("ERROR", "do_current-check_files", "负载选择错误,电机电流测试只能选择 tool100/inertia 规格!", "red", "LoadSelectError") + + return prj_file + + def get_configs(): + robot_type = None + msg_id, state = clibs.c_hr.execution("controller.get_params") + records = clibs.c_hr.get_from_id(msg_id, state) + for record in records: + if "请求发送成功" not in record[0]: + robot_type = eval(record[0])["data"]["robot_type"] + server_file = f"/home/luoshi/bin/controller/robot_cfg/{robot_type}/{robot_type}.cfg" + local_file = self.dir_path + f"/{robot_type}.cfg" + clibs.c_pd.pull_file_from_server(server_file, local_file) + + _prj_file = check_files() + get_configs() + + return _prj_file + + def single_axis_proc(self, records, number): + text = "single" if number < 6 else "hold" + number = number if number < 6 else number - 6 + d_vel, d_trq, d_sensor, d_trans = [], [], [], [] + for record in records: + data = eval(record[0])["data"] + for item in data: + d_item = reversed(item["value"]) + if item.get("channel", None) == number and item.get("name", None) == "hw_joint_vel_feedback": + d_vel.extend(d_item) + elif item.get("channel", None) == number and item.get("name", None) == "device_servo_trq_feedback": + d_trq.extend(d_item) + elif item.get("channel", None) == number and item.get("name", None) == "hw_sensor_trq_feedback": + d_sensor.extend(d_item) + elif item.get("channel", None) == number and item.get("name", None) == "hw_estimate_trans_trq_res": + d_trans.extend(d_item) + + df1 = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_vel}) + df2 = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq}) + df3 = pandas.DataFrame.from_dict({"hw_sensor_trq_feedback": d_sensor}) + df4 = pandas.DataFrame.from_dict({"hw_estimate_trans_trq_res": d_trans}) + df = pandas.concat([df1, df2, df3, df4], axis=1) + filename = f"{self.dir_path}/single/j{number + 1}_{text}_{time.time()}.data" + df.to_csv(filename, sep="\t", index=False) + + def scenario_proc(self, records, number, scenario_time): + d_vel, d_trq, d_sensor, d_trans = [[], [], [], [], [], []], [[], [], [], [], [], []], [[], [], [], [], [], []], [[], [], [], [], [], []] + for record in records: + data = eval(record[0])["data"] + for item in data: + d_item = reversed(item["value"]) + for axis in range(6): + if item.get("channel", None) == axis and item.get("name", None) == "hw_joint_vel_feedback": + d_vel[axis].extend(d_item) + elif item.get("channel", None) == axis and item.get("name", None) == "device_servo_trq_feedback": + d_trq[axis].extend(d_item) + elif item.get("channel", None) == axis and item.get("name", None) == "hw_sensor_trq_feedback": + d_sensor[axis].extend(d_item) + elif item.get("channel", None) == axis and item.get("name", None) == "hw_estimate_trans_trq_res": + d_trans[axis].extend(d_item) + + for axis in range(6): + df1 = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_vel[axis]}) + df2 = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq[axis]}) + df3 = pandas.DataFrame.from_dict({"hw_sensor_trq_feedback": d_sensor[axis]}) + df4 = pandas.DataFrame.from_dict({"hw_estimate_trans_trq_res": d_trans[axis]}) + df = pandas.concat([df1, df2, df3, df4], axis=1) + filename = f"{self.dir_path}/s_{number-11}/j{axis+1}_s_{number-11}_{scenario_time}_{time.time()}.data" + df.to_csv(filename, sep="\t", index=False) + + def gen_result_file(self, number, start_time, end_time, scenario_time): + def get_records(s_time, e_time): + clibs.cursor.execute(f"select content from logs where time between '{s_time}' and '{e_time}' and content like '%diagnosis.result%' order by id asc") + _ = clibs.cursor.fetchall() + return _ + + if number < 12: + records = get_records(start_time, end_time) + t = threading.Thread(target=self.single_axis_proc, args=(records, number)) + t.daemon = True + t.start() + elif number < 15: + records = get_records(start_time, end_time) + t = threading.Thread(target=self.scenario_proc, args=(records, number, scenario_time)) + t.daemon = True + t.start() + + @staticmethod + def change_curve_state(stat): + curves = ["hw_joint_vel_feedback", "device_servo_trq_feedback", "hw_sensor_trq_feedback", "hw_estimate_trans_trq_res"] + display_pdo_params = [] if not stat else [{"name": curve, "channel": chl} for curve in curves for chl in range(6)] + clibs.c_hr.execution("diagnosis.open", open=stat, display_open=stat, overrun=True, turn_area=True, delay_motion=False) + clibs.c_hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params, frequency=50, version="1.4.1") + + def run_rl(self, prj_file): + prj_name = ".".join(prj_file.split("/")[-1].split(".")[:-1]) + c_regular = [ + "scenario(0, j1_p, j1_n, p_speed, p_tool, i_tool)", + "scenario(0, j2_p, j2_n, p_speed, p_tool, i_tool)", + "scenario(0, j3_p, j3_n, p_speed, p_tool, i_tool)", + "scenario(0, j4_p, j4_n, p_speed, p_tool, i_tool)", + "scenario(0, j5_p, j5_n, p_speed, p_tool, i_tool)", + "scenario(0, j6_p, j6_n, p_speed, p_tool, i_tool)", + "scenario(4, j1_hold, j1_hold, p_speed, p_tool, i_tool)", + "scenario(4, j2_hold, j2_hold, p_speed, p_tool, i_tool)", + "scenario(4, j3_hold, j3_hold, p_speed, p_tool, i_tool)", + "scenario(4, j4_hold, j4_hold, p_speed, p_tool, i_tool)", + "scenario(4, j5_hold, j5_hold, p_speed, p_tool, i_tool)", + "scenario(4, j6_hold, j6_hold, p_speed, p_tool, i_tool)", + "scenario(1, j6_p, j6_n, p_speed, p_tool, i_tool)", + "scenario(2, j6_p, j6_n, p_speed, p_tool, i_tool)", + "scenario(3, j6_p, j6_n, p_speed, p_tool, i_tool)", + ] + c_inertia = [ + "scenario(5, j4_p_inertia, j4_n_inertia, p_speed, p_tool, i_tool)", + "scenario(5, j5_p_inertia, j5_n_inertia, p_speed, p_tool, i_tool)", + "scenario(5, j6_p_inertia, j6_n_inertia, p_speed, p_tool, i_tool)", + ] + disc_regular = ["一轴", "二轴", "三轴", "四轴", "五轴", "六轴", "一轴保持", "二轴保持", "三轴保持", "四轴保持", "五轴保持", "六轴保持", "场景一", "场景二", "场景三"] + disc_inertia = ["四轴惯量", "五轴惯量", "六轴惯量"] + conditions, disc = [], [] + if self.tool == "tool100": + conditions, disc = c_regular, disc_regular + elif self.tool == "inertia": + conditions, disc = c_inertia, disc_inertia + + # 打开诊断曲线,触发软急停,并解除,目的是让可能正在运行着的机器停下来 + clibs.c_md.r_soft_estop(0) + clibs.c_md.r_soft_estop(1) + clibs.c_md.r_clear_alarm() + + for condition in conditions: + number = conditions.index(condition) + self.logger("INFO", "do_current-run_rl", f"正在执行{disc[number]}测试......") + + # 1. 将act重置为False,并修改将要执行的场景 + clibs.c_md.write_act(False) + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(clibs.ip_addr, clibs.ssh_port, username=clibs.username, password=clibs.password) + cmd = "cd /home/luoshi/bin/controller/; " + cmd += f'sudo sed -i "/scenario/d" projects/{prj_name}/_build/current/main.mod; ' + cmd += f'sudo sed -i "/DONOTDELETE/i {condition}" projects/{prj_name}/_build/current/main.mod' + stdin, stdout, stderr = ssh.exec_command(cmd, get_pty=True) + stdin.write(clibs.password + "\n") + stdout.read().decode() # 需要read一下才能正常执行 + stderr.read().decode() + + # 2. reload工程后,pp2main,并且自动模式和上电 + prj_path = f"{prj_name}/_build/{prj_name}.prj" + clibs.c_hr.execution("overview.reload", prj_path=prj_path, tasks=["current"]) + clibs.c_hr.execution("rl_task.pp_to_main", tasks=["current"]) + clibs.c_hr.execution("state.switch_auto") + clibs.c_hr.execution("state.switch_motor_on") + + # 3. 开始运行程序 + clibs.c_hr.execution("rl_task.set_run_params", loop_mode=True, override=1.0) + clibs.c_hr.execution("rl_task.run", tasks=["current"]) + t_start = time.time() + while True: + if clibs.c_md.read_ready_to_go() == 1: + clibs.c_md.write_act(True) + break + else: + time.sleep(1) + if (time.time() - t_start) > 15: + self.logger("ERROR", "do_current-run_rl", "15s 内未收到机器人的运行信号,需要确认RL程序和工具通信是否正常执行...", "red", "ReadySignalTimeoutError") + + # 4. 执行采集 + time.sleep(10) # 消除前 10s 的不稳定数据 + self.change_curve_state(True) + start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + single_time, stall_time, scenario_time = 40, 10, 0 + if number < 6: # 单轴 + time.sleep(single_time) + elif number < 12: # 堵转 + time.sleep(stall_time) + else: # 场景 + t_start = time.time() + while True: + scenario_time = float(f"{float(clibs.c_md.read_scenario_time()):.2f}") + if float(scenario_time) != 0: + self.logger("INFO", "do_current-run_rl", f"场景{number - 11}的周期时间:{scenario_time}") + break + else: + time.sleep(1) + if (time.time()-t_start) > 180: + self.logger("ERROR", "do_current-run_rl", f"180s 内未收到场景{number - 11}的周期时间,需要确认RL程序和工具通信交互是否正常执行...", "red", "GetScenarioTimeError") + time.sleep(20) + + # 5.停止程序运行,保留数据并处理输出 + end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + clibs.c_hr.execution("rl_task.stop", tasks=["current"]) + time.sleep(2) # 确保数据都拿到 + self.change_curve_state(False) + self.gen_result_file(number, start_time, end_time, scenario_time) + else: + if self.tool == "tool100": + self.logger("INFO", "do_current-run_rl", "单轴和场景电机电流采集完毕,如需采集惯量负载,须切换负载类型,并更换惯量负载,重新执行", "green") + elif self.tool == "inertia": + self.logger("INFO", "do_current-run_rl", "惯量负载电机电流采集完毕,如需采集单轴/场景/保持电机电流,须切换负载类型,并更换偏置负载,重新执行", "green") + + def processing(self): + time_start = time.time() + clibs.running[self.idx] = 1 + + data_dirs, data_files = clibs.traversal_files(self.dir_path, self.output) + prj_file = self.initialization(data_dirs, data_files) + clibs.c_pd.push_prj_to_server(prj_file) + self.run_rl(prj_file) + + self.logger("INFO", "brake-processing", "-" * 60 + "
全部处理完毕
", "purple") + time_total = time.time() - time_start + msg = f"处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s" + self.logger("INFO", "brake-processing", msg) diff --git a/code/common/clibs.py b/code/common/clibs.py new file mode 100644 index 0000000..8a42f5b --- /dev/null +++ b/code/common/clibs.py @@ -0,0 +1,62 @@ +import os +import os.path +import threading + + +def traversal_files(dir_path, signal): + # 功能:以列表的形式分别返回指定路径下的文件和文件夹,不包含子目录 + # 参数:路径/信号/游标/功能编号 + # 返回值:路径下的文件夹列表 路径下的文件列表 + global cursor, tb_name + + if not os.path.exists(dir_path): + logger("ERROR", "clibs", f"数据文件夹{dir_path}不存在,请确认后重试......", "red", signal=signal) + else: + dirs, files = [], [] + for item in os.scandir(dir_path): + if item.is_dir(): + dirs.append(item.path.replace("\\", "/")) + elif item.is_file(): + files.append(item.path.replace("\\", "/")) + + return dirs, files + + +def db_lock(func): + def wrapper(*args, **kwargs): + try: + lock.acquire(True) + ret = func(*args, **kwargs) + finally: + lock.release() + return ret + + return wrapper + + +@db_lock +def logger(level, module, content, color="black", flag="both", signal=""): + global cursor, tb_name + if flag == "signal": + signal.emit(content, color) + elif flag == "cursor": + cursor.execute(f"INSERT INTO {tb_name} (level, module, content) VALUES (%s, %s, %s)", (level, module, content)) + elif flag == "both": + signal.emit(content, color) + cursor.execute(f"INSERT INTO {tb_name} (level, module, content) VALUES (%s, %s, %s)", (level, module, content)) + + +# PREFIX = "assets" # for pyinstaller packaging +PREFIX = "../assets" # for source code testing and debug +lock = threading.Lock() + +running = [0, 0, 0, 0, 0, 0, 0] # 制动数据/转矩数据/激光数据/精度数据/制动自动化/转矩自动化/耐久数据采集 +functions = ["制动数据处理", "转矩数据处理", "激光数据处理", "精度数据处理", "制动自动化测试", "转矩自动化测试", "耐久数据采集"] + +log_name = "" +ip_addr, ssh_port, socket_port, xService_port, external_port, modbus_port, upgrade_port = "", 22, 5050, 6666, 8080, 502, 4567 +username, password = "luoshi", "123456" +INTERVAL, RADIAN, MAX_FRAME_SIZE, MAX_LOG_NUMBER = 0.5, 57.3, 1024, 10 +c_md, c_hr, c_ec, c_pd, cursor, tb_name = None, None, None, None, None, "" +status = {"mysql": 0, "hmi": 0, "md": 0, "ec": 0} +c_joint_vel, c_servo_trq, c_sensor_trq, c_estimate_trans_trq, c_safety_estop = 1, 2, 3, 4, 3 # 各个指标所在列 diff --git a/code/common/openapi.py b/code/common/openapi.py new file mode 100644 index 0000000..24c3c16 --- /dev/null +++ b/code/common/openapi.py @@ -0,0 +1,2577 @@ +import json +import socket +from inspect import currentframe +import threading +import functools +from paramiko import SSHClient, AutoAddPolicy +from pymodbus.client.tcp import ModbusTcpClient +import selectors +import time +import os.path +from ctypes import * +import hashlib +import struct +from PySide6.QtCore import Signal, QThread +from common import clibs + + +class ModbusRequest(QThread): + output = Signal(str, str) + + def __init__(self, ip, port, /): + super().__init__() + self.ip = ip + self.port = port + + def net_conn(self): + RobotInit.modbus_init() + self.__c = ModbusTcpClient(host=self.ip, port=self.port) + if self.__c.connect(): + self.logger("DEBUG", "openapi", f"Modbus connection({clibs.ip_addr}:{clibs.modbus_port}) success...", "green") + else: + self.logger("ERROR", "openapi", f"Modbus connection({clibs.ip_addr}:{clibs.modbus_port}) failed...", "red", "MdConnFailed") + + def logger(self, level, module, content, color="black", error="", flag="both"): + clibs.logger(level, module, content, color, flag, signal=self.output) + if level.upper() == "ERROR": + raise Exception(f"{error} | {content}") + + def close(self): + if self.__c.connect(): + try: + self.__c.close() + except Exception as err: + self.logger("ERROR", "openapi", f"modbus: 关闭 Modbus 连接失败:{err}", "red", "MdCloseFailed") + + def __reg_high_pulse(self, addr: int) -> None: + self.__c.write_register(addr, 0) + time.sleep(clibs.INTERVAL) + self.__c.write_register(addr, 1) + time.sleep(clibs.INTERVAL+1) + self.__c.write_register(addr, 0) + + def r_clear_alarm(self): # OK + self.__reg_high_pulse(40000) + self.logger("INFO", "openapi", "modbus: 40000-010 执行清除告警信息") + + def r_reset_estop(self): # OK + self.__reg_high_pulse(40001) + self.logger("INFO", "openapi", "modbus: 40001-010 执行复位急停状态(非软急停)") + + def r_reset_estop_clear_alarm(self): # OK + self.__reg_high_pulse(40002) + self.logger("INFO", "openapi", "modbus: 40002-010 执行复位急停状态(非软急停),并清除告警信息") + + def r_motor_off(self): # OK + self.__reg_high_pulse(40003) + self.logger("INFO", "openapi", "modbus: 40003-010 执行机器人下电") + + def r_motor_on(self): # OK + self.__reg_high_pulse(40004) + self.logger("INFO", "openapi", "modbus: 40004-010 执行机器人上电") + + def r_motoron_pp2main_start(self): # OK + self.__reg_high_pulse(40005) + self.logger("INFO", "openapi", "modbus: 40005-010 执行机器人上电/pp2main/开始运行程序,需自动模式执行,若运行失败,可清除告警后再次尝试") + + def r_motoron_start(self): # OK + self.__reg_high_pulse(40006) + self.logger("INFO", "openapi", "modbus: 40006-010 执行机器人上电/开始运行程序,需自动模式执行,若运行失败,可清除告警、执行pp2main后再次尝试") + + def r_pulse_motoroff(self): # OK + self.__reg_high_pulse(40007) + self.logger("INFO", "openapi", "modbus: 40007-010 执行机器人停止,并下电,手动模式下可停止程序运行,但不能下电,若运行失败,可清除告警后再次尝试") + + def r_pp2main(self): # OK + self.__reg_high_pulse(40008) + self.logger("INFO", "openapi", "modbus: 40008-010 执行机器人 pp2main,需自动模式执行,若运行失败,可清除告警后再次尝试") + + def r_program_start(self): # OK + self.__reg_high_pulse(40009) + self.logger("INFO", "openapi", "modbus: 40009-010 执行机器人默认程序运行,需有 pp2main 前置操作,若运行失败,可清除告警后再次尝试") + + def r_program_stop(self): # OK + self.__reg_high_pulse(40010) + self.logger("INFO", "openapi", "modbus: 40010-010 执行机器人默认程序停止,需有 pp2main 前置操作,若运行失败,可清除告警后再次尝试") + + def r_reduced_mode(self, action: int): # OK + self.__c.write_register(40011, action) + actions = "进入" if action == 1 else "退出" + self.logger("INFO", "openapi", f"modbus: 40011-{action} 执行机器人{actions}缩减模式") + time.sleep(clibs.INTERVAL) + + def r_soft_estop(self, action: int): # OK + self.__c.write_register(40012, action) + actions = "解除" if action == 1 else "触发" + self.logger("INFO", "openapi", f"modbus: 40012-{action} 执行{actions}急停动作") + time.sleep(clibs.INTERVAL) + + def r_switch_auto_motoron(self): # OK + self.__reg_high_pulse(40013) + self.logger("INFO", "openapi", "modbus: 40013-010 执行切换为自动模式,并上电,初始状态 !!不能是!! 手动上电模式") + + def r_switch_auto(self): # OK + self.__reg_high_pulse(40014) + self.logger("INFO", "openapi", "modbus: 40014-010 执行切换为自动模式") + + def r_switch_manual(self): # OK + self.__reg_high_pulse(40015) + self.logger("INFO", "openapi", "modbus: 40015-010 执行切换为手动模式") + + def r_switch_safe_region01(self, action: bool): # OK | 上升沿打开,下降沿关闭 + if action: + self.__c.write_register(40016, False) + time.sleep(clibs.INTERVAL) + self.__c.write_register(40016, True) + else: + self.__c.write_register(40016, True) + time.sleep(clibs.INTERVAL) + self.__c.write_register(40016, False) + actions = "打开" if action else "关闭" + self.logger("INFO", "openapi", f"modbus: 40016-{action} 执行{actions}安全区 safe region 01") + time.sleep(clibs.INTERVAL) + + def r_switch_safe_region02(self, action: bool): # OK | 上升沿打开,下降沿关闭 + if action: + self.__c.write_register(40017, False) + time.sleep(clibs.INTERVAL) + self.__c.write_register(40017, True) + else: + self.__c.write_register(40017, True) + time.sleep(clibs.INTERVAL) + self.__c.write_register(40017, False) + actions = "打开" if action else "关闭" + self.logger("INFO", "openapi", f"modbus: 40017-{action} 执行{actions}安全区 safe region 02") + time.sleep(clibs.INTERVAL) + + def r_switch_safe_region03(self, action: bool): # OK | 上升沿打开,下降沿关闭 + if action: + self.__c.write_register(40018, False) + time.sleep(clibs.INTERVAL) + self.__c.write_register(40018, True) + else: + self.__c.write_register(40018, True) + time.sleep(clibs.INTERVAL) + self.__c.write_register(40018, False) + actions = "打开" if action else "关闭" + self.logger("INFO", "openapi", f"modbus: 40018-{action} 执行{actions}安全区 safe region 03") + time.sleep(clibs.INTERVAL) + + def write_act(self, number): + self.__c.write_register(40100, number) + self.logger("INFO", "openapi", f"modbus: 40100 将 {number} 写入") + + def write_probe(self, probe): + self.__c.write_register(40101, probe) + self.logger("INFO", "openapi", f"modbus: 40101 将 {probe} 写入") + + def write_pon(self, pon): + self.__c.write_register(40102, pon) + self.logger("INFO", "openapi", f"modbus: 40102 将 {pon} 写入") + + def write_axis(self, axis): + result = self.__c.convert_to_registers(int(axis), self.__c.DATATYPE.INT32, "little") + self.__c.write_registers(40103, result) + self.logger("INFO", "openapi", f"modbus: 40103 将 {axis} 写入") + + def write_speed_max(self, speed): + result = self.__c.convert_to_registers(float(speed), self.__c.DATATYPE.FLOAT32, "little") + self.__c.write_registers(40105, result) + self.logger("INFO", "openapi", f"modbus: 40105 将 {speed} 写入") + + def r_write_signals(self, addr: int, value): # OK | 40100 - 40109: signal_0 ~ signal_9 + if -1 < addr < 10 and addr.is_integer(): + self.__c.write_register(40100+addr, value) + self.logger("INFO", "openapi", f"modbus: {40100+addr}-{value} 将寄存器 signal_{addr} 赋值为 {value}") + else: + self.logger("INFO", "openapi", f"modbus: {40100+addr}-{value} 地址错误,无法赋值!") + + @property + def w_alarm_state(self): # OK + res = self.__c.read_holding_registers(40500, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40500 获取告警状态,结果为 {res} :--: 0 表示无告警,,1 表示有告警") + return res + + @property + def w_collision_alarm_state(self): # OK + res = self.__c.read_holding_registers(40501, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40501 获取碰撞告警状态,结果为 {res} :--: 0 表示未触发,1 表示已触发") + return res + + @property + def w_collision_open_state(self): # OK + res = self.__c.read_holding_registers(40502, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40502 获取碰撞检测开启状态,结果为 {res} :--: 0 表示关闭,1 表示开启") + return res + + @property + def w_controller_is_running(self): # OK + res = self.__c.read_holding_registers(40503, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40503 获取控制器运行状态,结果为 {res} :--: 0 表示运行异常,1 表示运行正常") + return res + + @property + def w_encoder_low_battery(self): # OK + res = self.__c.read_holding_registers(40504, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40504 获取编码器低电压状态,结果为 {res} :--: 0 表示非低电压,1 表示低电压 需关注") + return res + + @property + def w_estop_state(self): # OK + res = self.__c.read_holding_registers(40505, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40505 获取机器人急停状态(非软急停),结果为 {res} :--: 0 表示未触发,1 表示已触发") + return res + + @property + def w_motor_state(self): # OK + res = self.__c.read_holding_registers(40506, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40506 获取机器人上电状态,结果为 {res} :--: 0 表示未上电,1 表示已上电") + return res + + @property + def w_operation_mode(self): # OK + res = self.__c.read_holding_registers(40507, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40507 获取机器人操作模式,结果为 {res} :--: 0 表示手动模式,1 表示自动模式") + return res + + @property + def w_program_state(self): # OK + res = self.__c.read_holding_registers(40508, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40508 获取程序的运行状态,结果为 {res} :--: 0 表示未运行,1 表示正在运行") + return res + + @property + def w_program_not_run(self): # OK + res = self.__c.read_holding_registers(40509, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40509 判定程序为未运行状态,结果为 {res} :--: 0 表示正在运行,1 表示未运行") + return res + + @property + def w_program_reset(self): # OK + res = self.__c.read_holding_registers(40510, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40510 判定程序指针为 pp2main 状态,结果为 {res} :--: 0 表示指针不在 main 函数,1 表示指针在 main 函数") + return res + + @property + def w_reduce_mode_state(self): # OK + res = self.__c.read_holding_registers(40511, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40511 获取机器人缩减模式状态,结果为 {res} :--: 0 表示非缩减模式,1 表示缩减模式") + return res + + @property + def w_robot_is_busy(self): # OK + res = self.__c.read_holding_registers(40512, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40512 获取机器人是否处于 busy 状态,结果为 {res} :--: 0 表示未处于 busy 状态,1 表示处于 busy 状态") + return res + + @property + def w_robot_is_moving(self): # OK + res = self.__c.read_holding_registers(40513, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40513 获取机器人是否处于运动状态,结果为 {res} :--: 0 表示未运动,1 表示正在运动") + return res + + @property + def w_safe_door_state(self): # OK + res = self.__c.read_holding_registers(40514, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40514 获取机器人是否处于安全门打开状态,需自动模式下执行,结果为 {res} :--: 0 表示未触发安全门,1 表示已触发安全门") + return res + + @property + def w_safe_region01_trig_state(self): # OK + res = self.__c.read_holding_registers(40515, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40515 获取安全区域 safe region01 的触发状态,结果为 {res} :--: 0 表示未触发,1 表示已触发") + return res + + @property + def w_safe_region02_trig_state(self): # OK + res = self.__c.read_holding_registers(40516, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40516 获取安全区域 safe region02 的触发状态,结果为 {res} :--: 0 表示未触发,1 表示已触发") + return res + + @property + def w_safe_region03_trig_state(self): # OK + res = self.__c.read_holding_registers(40517, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40517 获取安全区域 safe region03 的触发状态,结果为 {res} :--: 0 表示未触发,1 表示已触发") + return res + + @property + def w_soft_estop_state(self): # OK + res = self.__c.read_holding_registers(40518, count=1).registers[0] + self.logger("INFO", "openapi", f"modbus: 40518 获取机器人软急停状态,结果为 {res} :--: 0 表示未触发软急停,1 表示已触发软急停") + return res + + def io_write_coils(self, addr, action): # OK | 名字叫写线圈,其实是写 modbus 的 discrete inputs(DI) + # e.g. io_write_coils(0, 1) + # e.g. io_write_coils(1, 1) + # e.g. io_write_coils(0, [1, 1, 1]) + self.__c.write_coils(addr, action) + self.logger("INFO", "openapi", f"modbus: 执行给 DI 地址 {addr} 赋值为 {action},可根据情况传递列表,实现一次性赋值多个") + time.sleep(clibs.INTERVAL) + + def io_read_coils(self): # OK | 读 modbus 的 16 个 discrete inputs(DI) + res = self.__c.read_coils(0, count=16).bits + self.logger("INFO", "openapi", f"modbus: 执行读取所有 DI 的结果为 {res}") + return res + + def io_read_discretes(self): # OK | 读 modbus 的 coil outputs(DO) + res = self.__c.read_discrete_inputs(0, count=16).bits + self.logger("INFO", "openapi", f"modbus: 执行读取所有 DO 的结果为 {res}") + return res + + def read_ready_to_go(self): + result = self.__c.read_holding_registers(40600, count=1) + return result.registers[0] + + def read_scenario_time(self): + results = self.__c.read_holding_registers(40601, count=2) + result = self.__c.convert_from_registers(registers=results.registers, data_type=self.__c.DATATYPE.FLOAT32, word_order="little") + return result + + def read_capture_start(self): + result = self.__c.read_holding_registers(40603, count=1) + return result.registers[0] + + +class HmiRequest(QThread): + output = Signal(str, str) + socket.setdefaulttimeout(clibs.INTERVAL * 3) + + def __init__(self, ip, port, port_xs, /): + super().__init__() + self.__ip = ip + self.__port = port + self.__port_xs = port_xs + self.__close_hmi = False + self.__is_connected = False + self.__index = 0 + self.__previous_data = b"" + self.__valid_data_length = 0 + self.__leftovers = 0 + self.__response = b"" + self.__response_xs = "" + self.__half_frm = b"" + self.__half_frm_flag = -1 + self.__half_pkg = b"" + self.__half_pkg_flag = False + self.__is_first_frame = True + self.__is_debug = True + + def net_conn(self): + self.__socket_conn() + self.__t_heartbeat = threading.Thread(target=self.__heartbeat) + self.__t_heartbeat.daemon = True + self.__t_heartbeat.start() + self.__t_unpackage = threading.Thread(target=self.__unpackage, args=(self.c,)) + self.__t_unpackage.daemon = True + self.__t_unpackage.start() + self.__t_unpackage_xs = threading.Thread(target=self.__unpackage_xs, args=(self.c_xs,)) + self.__t_unpackage_xs.daemon = True + self.__t_unpackage_xs.start() + + def logger(self, level, module, content, color="black", error="", flag="both"): + clibs.logger(level, module, content, color, flag, signal=self.output) + if level.upper() == "ERROR": + raise Exception(f"{error} | {content}") + + @property + def status(self): + return self.__is_connected + + def close(self): + if self.__is_connected: + try: + self.__is_connected = False + time.sleep(clibs.INTERVAL*2) + self.c.close() + self.c_xs.close() + time.sleep(clibs.INTERVAL*2) + clibs.status["hmi"] = 0 + except Exception as err: + self.logger("ERROR", "openapi", f"hmi: 关闭 Socket 连接失败 {err}", "red", "HmiCloseFailed") + + def __socket_conn(self): + self.close() + try: + self.c = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.c.connect((self.__ip, self.__port)) + self.c_xs = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.c_xs.connect((self.__ip, self.__port_xs)) + # 尝试连续三次发送心跳包,确认建联成功 + state = None + for i in range(3): + _, state = self.execution.__wrapped__(self, "controller.heart") + time.sleep(clibs.INTERVAL/5) + if state is not None: + self.__is_connected = True + self.logger("INFO", "openapi", "hmi: HMI connection success...", "green") + else: + self.logger("ERROR", "openapi", "hmi: HMI connection failed...", "red", "HmiConnFailed") + except Exception as err: + self.logger("ERROR", "openapi", f"hmi: HMI connection timeout...
err = {err}", "red", "HmiConnTimeout") + + def __heartbeat(self): + while self.__is_connected: + self.execution("controller.heart") + time.sleep(clibs.INTERVAL*2) + + @staticmethod + def package(cmd): + frm_value = (len(cmd) + 6).to_bytes(length=2, byteorder="big") + pkg_value = len(cmd).to_bytes(length=4, byteorder="big") + protocol = int(2).to_bytes(length=1, byteorder="big") + reserved = int(0).to_bytes(length=1, byteorder="big") + return frm_value + pkg_value + protocol + reserved + cmd.encode() + + def __unpackage(self, sock): + def to_read(conn, mask): + data = conn.recv(clibs.MAX_FRAME_SIZE) + if data: + # print(f"data = {data}") + # with open(f"{clibs.log_path}/logs.txt", mode="a", encoding="utf-8") as f_logs: + # f_logs.write(str(data) + "\n") + self.__get_response(data) + else: + sel.unregister(conn) + conn.close() + + try: + sel = selectors.DefaultSelector() + sel.register(sock, selectors.EVENT_READ, to_read) + while self.__is_connected: + events = sel.select() + for key, mask in events: + callback = key.data + callback(key.fileobj, mask) + except Exception as Err: + self.logger("INFO", "openapi", f"hmi: 老协议解包报错 {Err}") + + def __get_headers(self, index, data): + if index + 8 < len(data): + frm_value = int.from_bytes(data[index:index + 2], byteorder="big") + pkg_value = int.from_bytes(data[index + 2:index + 6], byteorder="big") + protocol = int.from_bytes(data[index + 6:index + 7], byteorder="big") + reserved = int.from_bytes(data[index + 7:index + 8], byteorder="big") + if reserved == 0 and protocol == 2: + return index + 8, frm_value, pkg_value + else: + # print("hr-get_headers: 解包数据有误,需要确认!") + # print(data) + self.logger("ERROR", "openapi", f"hmi: 解包数据有误,需要确认,最后一个数据包如下 {data}") + else: + self.__half_pkg = data[index:] + self.__half_pkg_flag = True + return -1, 0, 0 + + def __get_response(self, data): + frm_value, pkg_value, self.__index = 0, 0, 0 + + while self.__index < len(data): + if self.__is_first_frame: + if self.__half_pkg_flag: + len_one = len(self.__half_pkg) + len_another = 8 - len_one + headers = self.__half_pkg + data[:len_another] + self.__index = len_another + frm_value = int.from_bytes(headers[0:2], byteorder="big") + pkg_value = int.from_bytes(headers[2:6], byteorder="big") + self.__half_pkg_flag = False + else: + self.__index, frm_value, pkg_value = self.__get_headers(self.__index, data) + if self.__half_pkg_flag: + break + + if frm_value - pkg_value == 6: + if len(data[self.__index:]) >= pkg_value: + self.__response = data[self.__index:self.__index + pkg_value] + self.__index += pkg_value + self.logger("INFO", "openapi", str(json.loads(self.__response.decode()))) + self.__response = b"" + self.__leftovers = 0 + self.__is_first_frame = True + elif len(data[self.__index:]) < pkg_value: + self.__response = data[self.__index:] + self.__leftovers = pkg_value - len(data[self.__index:]) + self.__index += clibs.MAX_FRAME_SIZE # whatever + self.__is_first_frame = False + elif frm_value < pkg_value: + self.__valid_data_length = pkg_value + if len(data[self.__index:]) >= frm_value - 6: + self.__is_first_frame = False + self.__leftovers = 0 + self.__response = data[self.__index:self.__index + frm_value - 6] + self.__index += (frm_value - 6) + self.__valid_data_length -= (frm_value - 6) + if len(data[self.__index:]) == 2: + self.__half_frm = data[self.__index:] + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = 2 + elif len(data[self.__index:]) == 1: + self.__half_frm = data[self.__index:] + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = 1 + elif len(data[self.__index:]) == 0: + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = 0 + else: + self.__half_frm_flag = -1 + elif len(data[self.__index:]) < frm_value - 6: + self.__response = data[self.__index:] + self.__leftovers = frm_value - 6 - len(data[self.__index:]) + self.__valid_data_length -= len(data[self.__index:]) + self.__index += clibs.MAX_FRAME_SIZE + self.__is_first_frame = False + elif not self.__is_first_frame: # 不是首帧 + if self.__leftovers > 0 and self.__valid_data_length > 0: + if len(data) >= self.__leftovers: + self.__is_first_frame = False + self.__response += data[:self.__leftovers] + self.__index = self.__leftovers + self.__valid_data_length -= self.__leftovers + self.__leftovers = 0 + + if self.__valid_data_length == 0: + # with open(f"{clibs.log_path}/response.txt", mode="a", encoding="utf-8") as f_res: + # f_res.write(f"{json.loads(self.__response.decode())}" + "\n") + self.logger("INFO", "openapi", str(json.loads(self.__response.decode()))) + self.__response = b"" + self.__is_first_frame = True + continue # 此时应该重新 get_headers + + if len(data[self.__index:]) == 2: + self.__half_frm = data[self.__index:] + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = 2 + elif len(data[self.__index:]) == 1: + self.__half_frm = data[self.__index:] + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = 1 + elif len(data[self.__index:]) == 0: + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = 0 + else: + self.__half_frm_flag = -1 + + elif len(data) < self.__leftovers: + self.__response += data + self.__leftovers -= len(data) + self.__valid_data_length -= len(data) + self.__index += clibs.MAX_FRAME_SIZE + + elif self.__leftovers > 0 and self.__valid_data_length == 0: + if len(data) >= self.__leftovers: + self.__response += data[:self.__leftovers] + self.__index = self.__leftovers + self.__leftovers = 0 + self.logger("DEBUG", "openapi", str(json.loads(self.__response.decode()))) + self.__response = b"" + self.__is_first_frame = True + elif len(data) < self.__leftovers: + self.__response += data + self.__leftovers -= len(data) + self.__index += clibs.MAX_FRAME_SIZE + + elif self.__leftovers == 0 and self.__valid_data_length > 0: + # 1. len(data[self.__index:]) > 2 + # 2. len(data[self.__index:]) = 2 + # 3. len(data[self.__index:]) = 1 + # 4. len(data[self.__index:]) = 0 + if self.__half_frm_flag != -1: + if self.__half_frm_flag == 2: + frm_value = int.from_bytes(self.__half_frm) + if len(data) >= frm_value: + self.__response += data[:frm_value] + self.__leftovers = 0 + self.__valid_data_length -= len(data[:frm_value]) + self.__index = frm_value + elif len(data) < frm_value: + self.__response += data + self.__leftovers = frm_value - len(data) + self.__valid_data_length -= len(data) + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = -1 + elif self.__half_frm_flag == 1: + frm_value = int.from_bytes(self.__half_frm + data[0:1]) + if len(data[1:]) >= frm_value: + self.__response += data[1:frm_value+1] + self.__leftovers = 0 + self.__valid_data_length -= len(data[1:frm_value+1]) + self.__index = frm_value + 1 + elif len(data[1:]) < frm_value: + self.__response += data[1:] + self.__leftovers = frm_value - len(data[1:]) + self.__valid_data_length -= len(data[1:]) + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = -1 + elif self.__half_frm_flag == 0: + frm_value = int.from_bytes(data[0:2]) + if len(data[2:]) >= frm_value: + self.__response += data[2:frm_value+2] + self.__leftovers = 0 + self.__valid_data_length -= len(data[2:frm_value+2]) + self.__index = frm_value + 2 + elif len(data[2:]) < frm_value: + self.__response += data[2:] + self.__leftovers = frm_value - len(data[2:]) + self.__valid_data_length -= len(data[2:]) + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = -1 + + if self.__valid_data_length == 0: + self.logger("INFO", "openapi", str(json.loads(self.__response.decode()))) + self.__response = b"" + self.__is_first_frame = True + continue + + if len(data[self.__index:]) == 2: + self.__half_frm = data[self.__index:] + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = 2 + elif len(data[self.__index:]) == 1: + self.__half_frm = data[self.__index:] + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = 1 + elif len(data[self.__index:]) == 0: + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm_flag = 0 + else: + self.__half_frm_flag = -1 + + else: + frm_value = int.from_bytes(data[self.__index:self.__index + 2]) + self.__index += 2 + if len(data[self.__index:]) >= frm_value: + self.__leftovers = 0 + self.__response += data[self.__index:self.__index + frm_value] + self.__index += frm_value + self.__valid_data_length -= frm_value + if self.__valid_data_length == 0: + self.logger("INFO", "openapi", str(json.loads(self.__response.decode()))) + self.__response = b"" + self.__is_first_frame = True + continue + + if len(data[self.__index:]) == 2: + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm = data[self.__index:] + self.__half_frm_flag = 2 + elif len(data[self.__index:]) == 1: + self.__index += clibs.MAX_FRAME_SIZE + self.__half_frm = data[self.__index:] + self.__half_frm_flag = 1 + elif len(data[self.__index:]) == 0: + self.__half_frm_flag = 0 + else: + self.__half_frm_flag = -1 + + elif len(data[self.__index:]) < frm_value: + self.__response += data[self.__index:] + self.__leftovers = frm_value - len(data[self.__index:]) + self.__valid_data_length -= len(data[self.__index:]) + self.__index += clibs.MAX_FRAME_SIZE + else: + # DEBUG INFO + # if self.__is_debug: + # print(f"12 index = {self.__index}") + # print(f"12 frm_value = {frm_value}") + # print(f"12 leftovers = {self.__leftovers}") + # print(f"12 valid_data_length = {self.__valid_data_length}") + # print(f"12 is_first_frame = {self.__is_first_frame}") + # if self.__valid_data_length < 0 or self.__leftovers > 1024: + # print(f"data = {data}") + # raise Exception("DataError") + self.logger("ERROR", "openapi", "hmi: Will never be here", "red", "WillNeverBeHere") + + @staticmethod + def package_xs(cmd): + return "".join([json.dumps(cmd, separators=(",", ":")), "\r"]).encode() + + def __unpackage_xs(self, sock): + def to_read(conn, mask): + data = conn.recv(1024) # Should be ready + if data: + # print(data) + self.get_response_xs(data) + else: + sel.unregister(conn) + conn.close() + + try: + sel = selectors.DefaultSelector() + sel.register(sock, selectors.EVENT_READ, to_read) + + while self.__is_connected: + events = sel.select() + for key, mask in events: + callback = key.data + callback(key.fileobj, mask) + except Exception as err: + self.logger("INFO", "openapi", f"hmi: xService解包报错 {err}", "red") + + def get_response_xs(self, data): + char, response = "", self.__response_xs + for char in data.decode(): + if char != "\r": + response = "".join([response, char]) + else: + self.logger("INFO", "openapi", response) + self.response_xs = response + response = "" + else: + self.__response_xs = response + + def get_from_id(self, msg_id, state): + if state == "done_xs": + return + f_text = f"%{msg_id}%" + for _ in range(3): + time.sleep(clibs.INTERVAL * 4) + try: + clibs.lock.acquire(True) + clibs.cursor.execute(f"select content from {clibs.tb_name} where content like '{f_text}'") + records = clibs.cursor.fetchall() + finally: + clibs.lock.release() + if len(records) == 2: + return records + else: + self.close() + self.logger("ERROR", "openapi", f"hmi: 无法找到请求 {msg_id} 的响应", "red", "ResponseNotFound") + + @staticmethod + def validate_req(func): + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + msg_id, state = func(self, *args, **kwargs) + t = threading.Thread(target=self.get_from_id, args=(msg_id, state)) + t.daemon = True + t.start() + return msg_id, state + return wrapper + + @validate_req + def execution(self, command, p_flag=0, **kwargs): + req, state = None, None + try: + with open(f"{clibs.PREFIX}/files/protocols/hmi/{command}.json", encoding="utf-8", mode="r") as f_json: + req = json.load(f_json) + except Exception as err: + self.logger("ERROR", "openapi", f"hmi: 暂不支持 {command} 功能,或确认该功能存在... {err}", "red", "CommandNotSupport") + + if p_flag == 0: # for old protocols + match command: + case "state.set_tp_mode": + req["data"]["tp_mode"] = kwargs["tp_mode"] + case "overview.set_autoload": + req["data"]["autoload_prj_path"] = kwargs["autoload_prj_path"] + case "overview.reload": + req["data"]["prj_path"] = kwargs["prj_path"] + req["data"]["tasks"] = kwargs["tasks"] + case "rl_task.pp_to_main" | "rl_task.run" | "rl_task.stop": + req["data"]["tasks"] = kwargs["tasks"] + case "rl_task.set_run_params": + req["data"]["loop_mode"] = kwargs["loop_mode"] + req["data"]["override"] = kwargs["override"] + case "diagnosis.set_params": + req["data"]["display_pdo_params"] = kwargs["display_pdo_params"] + req["data"]["frequency"] = kwargs["frequency"] + req["data"]["version"] = kwargs["version"] + case "diagnosis.open": + req["data"]["open"] = kwargs["open"] + req["data"]["display_open"] = kwargs["display_open"] + req["data"]["overrun"] = kwargs["overrun"] + req["data"]["turn_area"] = kwargs["turn_area"] + req["data"]["delay_motion"] = kwargs["delay_motion"] + case "register.set_value": + req["data"]["name"] = kwargs["name"] + req["data"]["type"] = kwargs["type"] + req["data"]["bias"] = kwargs["bias"] + req["data"]["value"] = kwargs["value"] + case "diagnosis.save": + req["data"]["save"] = kwargs["save"] + case "socket.set_params": + req["data"] = kwargs["data"] + case "fieldbus_device.set_params": + req["data"]["device_name"] = kwargs["device_name"] + req["data"]["enable"] = kwargs["enable"] + case "soft_limit.set_params": + req["data"] = kwargs["data"] + case "move.set_quickturn_pos": + req["data"]["enable_home"] = kwargs["enable_home"] + req["data"]["enable_drag"] = kwargs["enable_drag"] + req["data"]["enable_transport"] = kwargs["enable_transport"] + req["data"]["end_posture"] = kwargs["end_posture"] + case "move.quick_turn": + req["data"]["name"] = kwargs["name"] + case "move.stop": + req["data"]["stoptype"] = kwargs["stoptype"] + case "jog.start": + req["data"]["index"] = kwargs["index"] + req["data"]["direction"] = kwargs["direction"] + req["data"]["is_ext"] = kwargs["is_ext"] + case "jog.set_params": + req["data"]["step"] = kwargs["step"] + req["data"]["override"] = kwargs["override"] + req["data"]["space"] = kwargs["space"] + case "diagnosis.get_params": + req["data"]["version"] = kwargs["version"] + case "system_io.update_configuration": + req["data"]["input_system_io"] = kwargs["input_system_io"] + req["data"]["output_system_io"] = kwargs["output_system_io"] + case "modbus.set_params": + req["data"]["enable_slave"] = kwargs["enable_slave"] + req["data"]["ip"] = kwargs["ip"] + req["data"]["port"] = kwargs["port"] + req["data"]["slave_id"] = kwargs["slave_id"] + req["data"]["enable_master"] = kwargs["enable_master"] + case "modbus.get_values": + req["data"]["mode"] = kwargs["mode"] + case "move.set_monitor_cfg": + req["data"]["ref_coordinate"] = kwargs["ref_coordinate"] + case "move.set_params": + req["data"]["MOTION"] = kwargs["data"] + case "move.set_quickstop_distance": + req["data"]["distance"] = kwargs["distance"] + case "collision.set_params": + req["data"] = kwargs["data"] + case "collision.set_state": + req["data"]["collision_state"] = kwargs["collision_state"] + case "controller.set_params": + req["data"]["time"] = kwargs["time"] + case "drag.set_params": + req["data"]["enable"] = kwargs["enable"] + req["data"]["space"] = kwargs["space"] + req["data"]["type"] = kwargs["type"] + case _: + pass + + req["id"] = f"{command}-{time.time()}" + cmd = json.dumps(req, separators=(",", ":")) + print(f"correct cmd = {cmd}") + try: + self.c.send(self.package(cmd)) + self.logger("INFO", "openapi", f"hmi: 老协议请求发送成功 {cmd}") + state = "done" + except Exception as err: + self.logger("ERROR", "openapi", f"hmi: 老协议请求发送失败 {cmd},报错信息 {err}", "red", "CommandSendFailed") + return req["id"], state + elif p_flag == 1: # for xService + match command: + case "safety.safety_area.signal_enable": + req["c"]["safety.safety_area.signal_enable"]["signal"] = kwargs["signal"] + case "safety.safety_area.overall_enable": + req["c"]["safety.safety_area.overall_enable"]["enable"] = kwargs["enable"] + case "log_code.data.code_list": + req["s"]["log_code.data"]["code_list"] = kwargs["code_list"] + case "safety.safety_area.safety_area_enable": + req["c"]["safety.safety_area.safety_area_enable"]["id"] = kwargs["id"] + req["c"]["safety.safety_area.safety_area_enable"]["enable"] = kwargs["enable"] + case "safety.safety_area.set_param": + req["c"]["safety.safety_area.set_param"] = kwargs["data"] + case _: + pass + + try: + self.c_xs.send(self.package_xs(req)) + self.logger("INFO", "openapi", f"hmi: xService请求发送成功 {req}") + state = "done" + except Exception as Err: + self.logger("ERROR", "openapi", f"hr: xService请求发送失败 {req} 报错信息 {Err}", "red", "CommandSendFailed") + return command, state + + # =================================== ↓↓↓ specific functions ↓↓↓ =================================== + + def switch_motor_state(self, state: str): # OK + """ + 切换上电/下电的状态 + :param state: on/off + :return: None + """ + match state: + case "on": + self.execution("state.switch_motor_on") + case "off": + self.execution("state.switch_motor_off") + case _: + self.logger("ERROR", "openapi", f"hmi: switch_motor_state 参数错误 {state}, 非法参数,只接受 on/off", "red", "ArgumentError") + + def switch_operation_mode(self, mode: str): # OK + """ + 切换自动/手动操作模式 + :param mode: auto/manual + :return: None + """ + match mode: + case "auto": + self.execution("state.switch_auto") + case "manual": + self.execution("state.switch_manual") + case _: + self.logger("ERROR", "openapi", f"hmi: switch_operation_mode 参数错误 {mode},非法参数,只接受 auto/manual", "red", "ArgumentError") + + def reload_project(self, prj_name: str, tasks: list): # OK + """ + 重新加载指定工程 + :param prj_name: 工程名,也即 zip 文件的名字 + :param tasks: 要加载的任务列表 + :return: None + """ + prj_path = f"{prj_name}/_build/{prj_name}.prj" + self.execution("overview.reload", prj_path=prj_path, tasks=tasks) + + def set_project_auto_reload(self, prj_name: str): # OK + """ + 将指定工程设置为开机自动加载,也即默认工程 + :param prj_name: 工程名,也即 zip 文件的名字 + :return: None + """ + autoload_prj_path = f"{prj_name}/_build/{prj_name}.prj" + self.execution("overview.set_autoload", autoload_prj_path=autoload_prj_path) + + def pp_to_main(self, tasks: list): # OK + """ + 将指定的任务列表的指针,指向 main 函数 + :param tasks: 任务列表 + :return: None + """ + self.execution("rl_task.pp_to_main", tasks=tasks) + + def program_start(self, tasks: list): # OK + """ + 开始执行程序任务,必须是自动模式下执行 + :param tasks: 任务列表 + :return: None + """ + self.execution("rl_task.run", tasks=tasks) + + def program_stop(self, tasks: list): # OK + """ + 停止执行程序任务 + :param tasks: 人物列表 + :return: None + """ + self.execution("rl_task.stop", tasks=tasks) + + def set_program_loop_speed(self, loop_mode: bool = True, override: float = 0.5): # OK + """ + :param loop_mode: True为循环模式,False为单次模式 + :param override: HMI 左下方的速度滑块,取值范围 [0, 1] + :return: None + """ + self.execution("rl_task.set_run_params", loop_mode=loop_mode, override=override) + + def clear_alarm(self): # OK + """ + 清除伺服告警 + :return: None + """ + self.execution("servo.clear_alarm") + + def reboot_robot(self): # OK + """ + 重启控制器 + :return: None + """ + self.execution("controller.reboot") + self.logger("INFO", "openapi", f"hmi: 控制器重启中,重连预计需要等待 100s 左右...") + ts = time.time() + time.sleep(30) + while True: + time.sleep(5) + te = time.time() + if te - ts > 180: + self.__silence = False + self.__sth_wrong("3min 内未能完成重新连接,需要查看后台控制器是否正常启动,或者 ip/port 是否正确") + break + for _ in range(3): + if not self.__is_connected: + break + time.sleep(2) + else: + self.logger("INFO", "openapi", "hr: HMI 重新连接成功...", "green") + break + + def reload_io(self): + """ + 触发控制器重新加载 IO 设备列表 + :return: None + """ + self.execution("io_device.load_cfg") + + @property + def get_quickturn_pos(self): # OK + """ + 获取机器人的home位姿、拖动位姿和发货位姿,轴关节角度,end_posture 取值如下: + 0 法兰平面与地面平行 + 1 工具坐标系X轴与地面垂直,正向朝下 + 2 工具坐标系X轴与地面垂直,正向朝上 + 3 工具坐标系Y轴与地面垂直,正向朝下 + 4 工具坐标系Y轴与地面垂直,正向朝上 + 5 工具坐标系Z轴与地面垂直,正向朝下 + 6 工具坐标系Z轴与地面垂直,正向朝上 + :return: as below + { + "enable_home": false, // 是否开启 home 点快速调整 + "enable_drag": false, // 是否开启拖动位姿点快速调整 + "enable_transport": false, // 是否开启发货位姿点快速调整 + "joint_home": [0.0,0.0,0.0,0.0,0.0,0.0,0.0], // home 位姿的关节角度 + "joint_drag": [0.0,0.0,0.0,0.0,0.0,0.0,0.0], // 拖动位姿的关节角度 + "joint_transport": [0.0,0.0,0.0,0.0,0.0,0.0,0.0], // 发货位姿的关节角度 + "end_posture":0, // 末端姿态的调整方式,取值 0-6 + "home_error_range":[0.0,0.0,0.0,0.0,0.0,0.0,0.0] // home点误差范围 + } + """ + return self.__get_data(currentframe().f_code.co_name, "move.get_quickturn_pos") + + def set_quickturn_pos(self, enable_home: bool = False, enable_drag: bool = False, enable_transport: bool = False, end_posture: int = 0): # OK + """ + 设置机器人的home位姿、拖动位姿、发货位姿,轴关节角度,Home点误差范围,详见上一个 get_quickturn_pos 功能实现 + :param enable_home: 是否开启 home 点快速调整 + :param enable_drag: 是否开启拖动位姿点快速调整 + :param enable_transport:是否开启发货位姿点快速调整 + :param end_posture: 末端姿态的调整方式,取值 0-6,详见 get_quickturn_pos 注释 + :return: None + """ + self.execution("move.set_quickturn_pos", enable_home=enable_home, enable_drag=enable_drag, enable_transport=enable_transport, end_posture=end_posture) + + def move2quickturn(self, name: str): # OK + """ + 运动到指定的快速调整位姿 + :param name: 指定快速调整的名称,home/drag/transport + :return: None + """ + self.execution("move.quick_turn", name=name) + + def stop_move(self, stoptype=0): # OK + """ + 停止运动 + TS_READY | TS_JOG | TS_LOADIDENTIFY | TS_DYNAMICIDENTIFY | TS_DRAG | TS_PROGRAM | TS_DEMO | TS_RCI | TS_DEBUG | TS_FRICTIONIDENTIFY + :param stoptype: 对应控制器的任务空间类型TaskSpace的枚举值,0-7 + :return: None + """ + self.execution("move.stop", stoptype=stoptype) + + @property + def get_jog_params(self): # OK + """ + 获取JOG的参数 + 世界坐标系 WORLD_COORDINATE 0 + 法兰盘坐标系 FLANGE_COORDINATE 1 + 基坐标系 BASE_COORDINATE 2 + 工具坐标系 TOOL_COORDINATE 3 + 工件坐标系 FRAME_COORDINATE 4 + 关节空间 JOINT_SPACE 5 + :return: + { + "step": 1000 [1000-连续] [10/1/0.1/0.001-点动] + "override": 0.2 速度比率 + "space": 5 JOG的空间 + } + """ + return self.__get_data(currentframe().f_code.co_name, "jog.get_params") + + def set_jog_params(self, step, override, space): # OK + """ + 设置JOG的参数,包含步长,空间,速度倍率 + :param step: [1000-连续] [10/1/0.1/0.001-点动] + :param override: 速度比率 + :param space: JOG的空间 + :return: None + """ + self.execution("jog.set_params", step=step, override=override, space=space) + + def start_jog(self, index: int, direction: bool = False, is_ext: bool = False): # OK + """ + 开始 JOG 运动 + :param index: 0-6,若选轴空间,则 0-6 对应 1-7 轴,若选笛卡尔空间,则 0-6 对应 xyzabc elb + :param direction: True 正方向,False 反方向 + :param is_ext: 是否是外部轴 jog + :return: None + """ + self.execution("jog.start", index=index, direction=direction, is_ext=is_ext) + + @property + def get_socket_params(self): # OK + """ + 获取socket参数 + :return: + { + "auto_connect": true, // True 开机启动,False 不开机启动 + "disconnection_detection_time": 10, // 链接断开检测周期(s) + "disconnection_triggering_behavior": 0, // 断开连接触发行为 0无动作 1暂停程序 2暂停并下电 + "enable": true, // True 开启或者 False 关闭 + "ip": "", // 仅限于客户端,用于指定服务端 IP;当作为服务端时,该参数设置为空字符串,否则会报错!!! + "name": "name", // 连接名称 + "port": "8080", // 连接端口 + "reconnect_flag": true, // True 自动重连,False 不自动重连 + "suffix": "\r", // 指定发送分隔符 + "type": 1 // 连接类型 0 client | 1 server + } + """ + return self.__get_data(currentframe().f_code.co_name, "socket.get_params") + + def set_socket_params(self, enable: bool, ip: str, port: str, suffix: str, type: int = 1, **kwargs): # OK + """ + 设置 socket 参数,一般作为服务器使用 + :param enable: True 开启或者 False 关闭 + :param ip: 连接ip + :param port: 连接端口 + :param suffix: 指定发送分隔符 + :param type: 0 client | 1 server + :return: None + """ + data = self.get_socket_params + keys = data.keys() + kwargs.update({"enable": enable, "ip": ip, "port": port, "suffix": suffix, "type": type}) + for _ in keys: + if _ in kwargs.keys(): + data[_] = kwargs[_] + self.execution("socket.set_params", data=data) + + @property + def get_diagnosis_params(self, version="1.4.1"): # OK + """ + 获取诊断功能开启状态,以及相应其他信息 + :param version: 指定诊断工具版本 + :return: + { + "delay_motion": false, // - + "display_open": false, // 诊断显示功能开启状态 + "ecat_diagnosis_state": false, // - + "overrun": false, // 是否开启实时线程超时监控上报 + "pdo_params": [...], // 指定版本支持的所有曲线信息 + "state": true, // 诊断功能的开启状态 + "turn_area": false // 转弯区是否上报 + } + """ + return self.__get_data(currentframe().f_code.co_name, "diagnosis.get_params", version=version) + + def set_diagnosis_params(self, display_pdo_params: list, frequency: int = 50, version: str = "1.4.1"): # OK + """ + 设置诊断功能显示参数 [{"name": "hw_joint_vel_feedback", "channel": 0}, ] + :param display_pdo_params: 指定要采集的曲线名称,具体可通过 get_diagnosis_params 函数功能获取所有目前已支持的曲线 + :param frequency: 采样频率,默认 50ms + :param version: xDiagnose的版本号 + :return: None + """ + self.execution("diagnosis.set_params", display_pdo_params=display_pdo_params, frequency=frequency, version=version) + + def open_diagnosis(self, open: bool, display_open: bool, overrun: bool = False, turn_area: bool = False, delay_motion: bool = False): # OK + """ + 打开或者关闭诊断曲线,并定义其他功能的开关(调试相关功能,比如是否开启线程超时监控和上报,转弯区以及运动延迟等) + :param open: 诊断功能,控制HMI->日志->诊断设置->私服诊断开关,一般设置成 True + :param display_open: 诊断显示功能,指的是在线诊断插件中的打开 switch 的状态,需要诊断数据的情况,设置成 True + :param overrun: 实时线程超时监控上报 + :param turn_area: 转弯区上报 + :param delay_motion: 延迟运动 + :return: None + """ + self.execution("diagnosis.open", open=open, display_open=display_open, overrun=overrun, turn_area=turn_area, delay_motion=delay_motion) + + def save_diagnosis(self, save: bool = True): # OK + """ + 保存诊断数据,也就是主动写诊断动作,HMI日志->诊断设置->保存诊断数据 + :param save: 保存数据开关 + :return: None + """ + self.execution("diagnosis.save", save=save) + + @property + def qurry_system_io_configuration(self): # OK + """ + 系统IO配置的查询协议,trigger 参数取值参照如下: + FLANKS 0, //边缘触发 + POS_FLANK 1, //上升沿 + NEG_FLANK 2, //下降沿 + HIGH_LEVEL 3, //高电平 + LOW_LEVEL 4 //低电平 + :return: + { + "input_system_io": { + "motor_on": { + "signal":"DI0_0", + "trigger":1 + }, + "motor_off": { + "signal":"DI0_0", + "trigger":2 + } + }, + "output_system_io": { + "sta_motor_on": { + "signal":"DO0_0" + }, + "sta_robot_running": { + "signal":"DO0_1" + } + } + } + """ + return self.__get_data(currentframe().f_code.co_name, "system_io.query_configuration") + + @property + def qurry_system_io_event_configuration(self): # OK + """ + 查询当前系统支持的系统IO事件列表,包括事件key、名称、支持的触发方式等配置 + :return: + { + "input_system_event": [ + { + "key": "ctrl_motor_on", + "name": "上电", + "trigger_types": [ + 1, + 2 + ] + }, + { + "key": "ctrl_motor_off", + "name": "下电", + "trigger_types": [ + 1, + 2 + ] + } + ], + "output_system_event": [ + { + "key": "sta_motor_on", + "name": "上下电状态" + }, + { + "key": "sta_program", + "name": "运行状态" + } + ], + "input_mutex_event": [ + { + "key0": "ctrl_motor_on", + "key1": "ctrl_motor_off" + }, + { + "key0": "ctrl_switch_auto", + "key1": "ctrl_switch_manu" + } + ] + } + """ + return self.__get_data(currentframe().f_code.co_name, "system_io.query_configuration") + + def update_system_io_configuration(self, i_funcs: list, o_funcs: list, i_signals: list, o_signals: list, trig_types: list): # OK + """ + 配置系统 IO + :param i_funcs: 输入,只写功能码列表 + :param o_funcs: 输出,只读功能码列表 + :param i_signals: DI 信号列表 + :param o_signals: DO 信号列表 + :param trig_types: 触发条件列表,可参考 qurry_system_io_configuration 中的触发条件 + :return: None + """ + input_system_io, output_system_io = {}, {} + for i_func in i_funcs: + _index = i_funcs.index(i_func) + input_system_io[i_func] = {"signal": i_signals[_index], "trigger": trig_types[_index]} + for o_func in o_funcs: + _index = o_funcs.index(o_func) + output_system_io[o_func] = {"signal": o_signals[_index]} + self.execution("system_io.update_configuration", input_system_io=input_system_io, output_system_io=output_system_io) + + @property + def get_fieldbus_device_params(self): # OK + """ + 获取的是 HMI通讯->总线设备列表的信息,以及开关状态 + :return: + { + "device_list": [ + { + "device_name": "modbus_1", + "enable": true + }, { + "device_name": "modbus_2", + "enable": false + } + ] + } + """ + return self.__get_data(currentframe().f_code.co_name, "fieldbus_device.get_params") + + def set_fieldbus_device_params(self, device_name: str, enable: bool): # OK + """ + 定义开关设备的协议,一次只能打开一个设备 + :param device_name: 设备列表中的名称 + :param enable: 是否开启,这里操作的是 HMI通信->IO设备里面的开关状态 + :return: None + """ + self.execution("fieldbus_device.set_params", device_name=device_name, enable=enable) + + def reload_fieldbus(self): # OK + """ + 触发控制器重新加载总线设备 + :return: None + """ + self.execution("fieldbus_device.load_cfg") + + @property + def get_modbus_params(self): # OK + """ + 获取modbus参数 + :return: + { + "connect_state": true, + "enable_master": false, + "enable_slave": false, + "ip": "192.168.0.160", + "is_convert": true, + "port": 502, + "slave_id": 1 + } + """ + return self.__get_data(currentframe().f_code.co_name, "modbus.get_params") + + def set_modbus_params(self, enable_slave: bool, ip: str, port: int, slave_id: int, enable_master: bool): # OK + """ + 设置modbus参数,相当于新建 + :param enable_slave: Modbus从站是否自动开启 + :param ip: ip 地址 + :param port: 端口 + :param slave_id: 从站 ID + :param enable_master: Modbus主站是否自动开启 + :return: + """ + self.execution("modbus.set_params", enable_slave=enable_slave, ip=ip, port=port, slave_id=slave_id, enable_master=enable_master) + + def reload_registers(self): # OK + """ + 触发控制器重新加载寄存器列表 + :return: None + """ + self.execution("modbus.load_cfg") + + def get_modbus_values(self, mode: str = "all"): # OK + """ + 用于获取 modbus 寄存器变量值的更新信息,展示在状态监控界面 + :param mode: all/change + :return: + """ + return self.__get_data(currentframe().f_code.co_name, "modbus.get_values", mode=mode) + + @property + def get_soft_limit_params(self): # OK + """ + 获取软限位参数 + :return: + { + "enable":true + "upper":[0,0,0,0,0,0,0], + "lower":[0,0,0,0,0,0,0], + "reduced_upper":[0,0,0,0,0,0,0], + "reduced_lower":[0,0,0,0,0,0,0] + } + """ + return self.__get_data(currentframe().f_code.co_name, "soft_limit.get_params") + + def set_soft_limit_params(self, **kwargs): # OK + """ + 设定软限位参数 enable: bool, upper: list, lower: list, reduced_upper: list, reduced_lower: list + :enable: 是否启用软限位 + :upper: 软限位上限 + :lower: 软限位下限 + :reduced_upper: 缩减模式软限位上限 + :reduced_lower: 缩减模式软限位下限 + :return: None + """ + data = self.get_soft_limit_params + keys = data.keys() + for _ in keys: + if _ in kwargs.keys(): + data[_] = kwargs[_] + self.execution("soft_limit.set_params", data=data) + + @property + def get_device_params(self): # OK + """ + 获取设备信息 + :return: + """ + return self.__get_data(currentframe().f_code.co_name, "device.get_params") + + @property + def get_cart_pos(self): # OK + """ + 获取机器人的当前位姿:包括轴关节角度,笛卡尔关节角度,四元数,欧拉角(臂角) + :return: + { + "joint":[0.0,0.0,0.0,0.0,0.0,0.0], + "position":[0.0,0.0,0.0,0.0,0.0,0.0], + "euler":[0.0,0.0,0.0], + "quaternion"[0.0,0.0,0.0,0.0], + "elb":0.0, // 可缺省 + "ext_joint":[0.0,0.0,0.0,0.0,0.0] + } + """ + return self.__get_data(currentframe().f_code.co_name, "move.get_pos") + + @property + def get_joint_pos(self): # OK + """ + 获取机器人的当前关节角度:包括内部轴和外部轴 + :return: + { + "inner_pos": [0,0,0,0,0,0,0], + "extern_pos": [0,0,0,0,0,0,0] + } + """ + return self.__get_data(currentframe().f_code.co_name, "move.get_joint_pos") + + @property + def get_monitor_cfg(self): # OK + """ + 获取机器人的监控配置参数,RefCoordinateType 类型数据,表示当前控制器位置监测的相对坐标系 + 基坐标系 REF_COORDINATE_BASE 0 + 世界坐标系 REF_COORDINATE_WORLD 1 + 工件坐标系 REF_COORDINATE_WOBJ 2 + :return: + { + "ref_coordinate": 0 + } + """ + return self.__get_data(currentframe().f_code.co_name, "move.get_monitor_cfg") + + def set_monitor_cfg(self, ref_coordinate): # OK + """ + 设置机器人的监控配置参数 + :ref_coordinate: RefCoordinateType类型数据,用来设置当前控制器位置监测的相对坐标系 + :return: None + """ + self.execution("move.set_monitor_cfg", ref_coordinate=ref_coordinate) + + @property + def get_move_params(self): # OK + """ + 获取机器人的运动参数:包括减速比、耦合比、最大速度、加速度、加加速度、acc ramp time、规划步长等信息 + :return: + { + "MOTION": { + "ACC_RAMPTIME_JOG": 0.5, + "ACC_RAMPTIME_STOP": 0.5, + "DEFAULT_ACC_PARAMS": [1.0, 0.5], + "JERK_LIMIT_CART": 0, + "JERK_LIMIT_JOINT": 0, + "JERK_LIMIT_ROT": 0, + "JOINT_MAX_ACC": [500, 500, 1000, 1000, 1000], + "JOINT_MAX_JERK": [2000, 2000, 2000, 4000, 4000, 4000], + "JOINT_MAX_SPEED": [120.0, 120.0, 180.0, 180.0, 180.0, 180.0], + "MAX_ACC_PARAMS": [1.0, 1], + "MIN_ACC_PARAMS": [0.3, 0.05], + "TCP_MAX_ACC": 5000, + "TCP_MAX_JERK": 10000, + "TCP_MAX_SPEED": 1000, + "TCP_ROTATE_MAX_ACC": 1800, + "TCP_ROTATE_MAX_JERK": 3600, + "TCP_ROTATE_MAX_SPEED": 180, + "VEL_SMOOTH_FACTOR": 1.0, + "VEL_SMOOTH_FACTOR_RANGE": [1.0, 10.0] + } + } + """ + return self.__get_data(currentframe().f_code.co_name, "move.get_params") + + def set_move_params(self, **kwargs): # OK + """ + 设置机器人的运动参数:轴最大速度、轴最大加加速度、速度、加加速度、加速度、加加速度、acc ramp time、规划步长等信息 + 可选参数:参考 get_move_params 函数返回值 MOTION 里面的选项 + :return: None + """ + data = self.get_move_params["MOTION"] + print(f"res = {data}") + keys = data.keys() + for _ in keys: + if _ in kwargs.keys(): + data[_] = kwargs[_] + self.execution("move.set_params", data=data) + + @property + def get_quick_stop_distance(self): # OK + """ + 获取机器人 search 指令最大停止距离 + :return: + { + "distance":2.0 + } + """ + return self.__get_data(currentframe().f_code.co_name, "move.get_quickstop_distance") + + def set_quick_stop_distance(self, distance: float): # OK + """ + 设置机器人 search 指令最大停止距离 + :param distance: 停止距离,单位 mm + :return: None + """ + self.execution("move.set_quickstop_distance", distance=distance) + + @property + def get_collision_params(self): # OK + """ + 获取碰撞检测相关参数 + :return: + { + "action": 1, // 触发行为:1-安全停止;2-触发暂停;3-柔顺停止 + "coeff": [100, 100, 100, 100, 100, 100], // 0-整机灵敏度百分比,1-单轴灵敏度百分比,2-单轴和整机灵敏度百分比 + "coeff_level": 0, // 灵敏度等级:0-低,1-中,2-高 + "compliance": 0, // 柔顺功能比例系数,[0-1] + "enable": true, // 功能使能开关 + "fallback_distance": 3, // 回退距离 + "mode": 0, // 力传感器系数,0 整机 1 单轴 + "percent": 100, // 0-200,整机灵敏度百分比 + "percent_axis": [100, 100, 100, 100, 100, 100], // 0-200,单轴灵敏度百分比 + "reduced_percent": 100, // 0-200,整机缩减模式灵敏度百分比 + "reduced_percent_axis": [100, 100, 100, 100, 100, 100] // 0-200,单轴缩减模式灵敏度百分比 + } + """ + return self.__get_data(currentframe().f_code.co_name, "collision.get_params") + + def set_collision_params(self, enable, mode, action, percent, **kwargs): # OK + """ + 设置碰撞检测相关参数 + :param enable: 功能使能开关 + :param mode: 力传感器系数,0 整机 1 单轴 + :param action: 触发行为:1-安全停止;2-触发暂停;3-柔顺停止 + :param percent: 0-200,整机灵敏度百分比 + :return: + """ + data = self.get_collision_params + keys = data.keys() + kwargs.update({"enable": enable, "mode": mode, "action": action, "percent": percent}) + for _ in keys: + if _ in kwargs.keys(): + data[_] = kwargs[_] + self.execution("collision.set_params", data=data) + + def set_collision_state(self, collision_state: bool): # NG + """ + 开启或者关闭虚拟墙碰撞检测,测试该函数功能无效!!! + :param collision_state: 碰撞检测的开关状态 + :return: None + """ + self.execution("collision.set_state", collision_state=collision_state) + + @property + def get_robot_state(self): # OK + """ + { + "rc_state":"normal", # "fatal" 、"error"、"block"、"normal" + "engine":"on", # "fatal" 、"error"、"GStop"、"EStop"、"on"、"off" + "servo_mode":"position", # "torque"、"position" + "operate": "auto", # "auto"、"manual" + "task_space": "program", # "jog"、"drag"、"ready"、"load_identify"、"demo"、"rci"、"dynamic_identify"、"program"、"debug" + "robot_action": "idle", # "idle"、"busy" + "safety_mode": "collision" # "normal"、"collision"、"collaboration" + } + """ + return self.__get_data(currentframe().f_code.co_name, "state.get_state") + + def set_controller_params(self, robot_time: str): # OK + """ + 设置控制器系统时间 + :param robot_time: 系统时间,"2020-02-28 15:28:30" + :return: None + """ + self.execution("controller.set_params", time=robot_time) + + @property + def get_robot_params(self): # OK + """ + "alias": "", + "auth_state": + "controller_type": "XBC_XMATE", + "controller_types": ["XBC_3", "XBC_5", "XBC_XMATE"], + "disk_serial_number": "2338020401535", + "mac_addr": "34:df:20:03:1b:45", + "model":, + "nic_list": ["enp1s0", "enp2s0"], + "occupied_addr": "192.168.2.123:49269", + "period": 0.002, + "period_types": [0.001, 0.002, 0.003, 0.004], + "robot_template": 10, + "robot_type": "XMC12-R1300-W7G3B1C", + "robot_types": ["XMC12-R1300-B7S3B0C", "XMC12-R1300-W7G3B1C", "XMC17_5-R1900-W7G3B1C", "XMC20-R1650-B7G3Z0C"], + "security_type": "ROKAE_RSC", + "security_types": ["ROKAE_MINI", "ROKAE_RSC"], + "time": "2024-09-13 12:36:38", + "version": "2.3.0.4" + """ + return self.__get_data(currentframe().f_code.co_name, "controller.get_params") + + def switch_tp_mode(self, mode: str): # OK + """ + 切换示教器模式 + :param mode: with/without + :return: None + """ + match mode: + case "with": + self.execution("state.set_tp_mode", tp_mode="with") + case "without": + self.execution("state.set_tp_mode", tp_mode="without") + case _: + self.logger("ERROR", "openapi", f"hmi: switch_tp_mode 参数错误{mode}, 非法参数,只接受 with/without", "red", "ArgumentError") + + @property + def get_tp_mode(self): # OK + """ + 获取示教器连接状态 + :return: + { + "tp_mode":"with" + } + """ + return self.__get_data(currentframe().f_code.co_name, "state.get_tp_mode") + + @property + def get_drag_params(self): # OK + """ + 获取拖动模式参数 + :return: + { + "enable": true, + "space": 0, + "type": 0 + } + """ + return self.__get_data(currentframe().f_code.co_name, "drag.get_params") + + def set_drag_params(self, enable: bool, space: int = 1, type: int = 2): # OK + """ + 设置拖动模式开关以及参数 + :param enable: 是否启用拖动 + :param space: 拖动空间 - 0 代表关节 1 代表笛卡尔 + :param type: 拖动类型 - 0 只平移 1 只旋转 2 自由拖动 + :return: None + """ + self.execution("drag.set_params", enable=enable, space=space, type=type) + + def set_safety_area_signal(self, signal: bool): # OK + """ + 设置安全区域信号控制使能开关 + :param signal: True 打开 False 关闭 + :return: None + """ + self.execution("safety.safety_area.signal_enable", protocol_flag=1, signal=signal) + + def set_safety_area_overall(self, enable: bool): # OK + """ + 设置安全区域整体控制使能开关 + :param enable: True 打开 False 关闭 + :return: None + """ + self.execution("safety.safety_area.overall_enable", protocol_flag=1, enable=enable) + + @property + def get_safety_area_params(self): # OK + """ + 获取安全区所有的配置信息 + :return: + "g": { + "safety_area_data": { + "overall_enable": true, + "safety_area_setting": [ + { + "box": { + "Lx": 100.0, + "Ly": 100.0, + "Lz": 100.0, + "direction": false, + "ori": { + "euler": { + "a": 179.9963851353547, + "b": -0.006653792532429416, + "c": 179.9934560302729 + }, + "quaternion": { + "q1": 0.0, + "q2": 0.0, + "q3": 0.0, + "q4": 0.0 + } + }, + "pos": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + "enable": false, + "id": 0, + "name": "region1", + "plane": { + "direction": true, + "point": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "vector": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + } + }, + "shape": 0, + "shared_bind_di": "", + "shared_bind_do": "", + "sphere": { + "ori": { + "euler": { + "a": 0.0, + "b": 0.0, + "c": 0.0 + }, + "quaternion": { + "q1": 0.0, + "q2": 0.0, + "q3": 0.0, + "q4": 0.0 + } + }, + "pos": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "radius": 0.0 + }, + "state": true, + "trigger": 0, + "type": 0, + "vertebral": { + "high": 0.0, + "ori": { + "euler": { + "a": 0.0, + "b": 0.0, + "c": 0.0 + }, + "quaternion": { + "q1": 0.0, + "q2": 0.0, + "q3": 0.0, + "q4": 0.0 + } + }, + "pos": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "radius": 0.0 + } + }, + ... // 剩余 9 个安全区域的配置信息 + ], + "signal_enable": true + } + } + } + """ + return self.__get_data(currentframe().f_code.co_name, "safety_area_data", flag=1) + + def set_safety_area_enable(self, id: int, enable: bool): # OK + """ + 设置每个安全区域的开关 + :param id: 安全区域开关,0-9 + :param enable: True 打开,False 关闭 + :return: None + """ + self.execution("safety.safety_area.safety_area_enable", protocol_flag=1, id=id, enable=enable) + + def set_safety_area_param(self, id: int, enable: bool, **kwargs): # OK + """ + 设定单独安全区的参数 + :param id: 安全区 id + :param enable: 是否开启 + :param kwargs: 其他参数,参考 get_safety_area_params 的返回值形式 + :return: None + """ + data = self.get_safety_area_params["g"]["safety_area_data"]["safety_area_setting"][id] + keys = data.keys() + kwargs.update({"id": id, "enable": enable}) + for _ in keys: + if _ in kwargs.keys(): + data[_] = kwargs[_] + self.execution("safety.safety_area.set_param", protocol_flag=1, data=data) + self.execution("safety.safety_area.safety_area_enable", protocol_flag=1, id=id, enable=enable) + + @property + def get_filtered_error_code(self): # OK + """ + 获取已设定的错误码过滤列表 + :return: + { + "g": { + "log_code.data": { + "code_list": [ + { + "id": 100, + "title": "DEMO\\u610f\\u5916\\u505c\\u6b62" + }, + { + "id": 10000, + "title": "HMI\\u8bf7\\u6c42\\u5305\\u89e3\\u6790\\u9519\\u8bef" + }, + { + "id": 10002, + "title": "\\u5feb\\u901f\\u8c03\\u6574\\u542f\\u52a8\\u5931\\u8d25" + } + ], + "version": 1 + } + } + } + """ + return self.__get_data(currentframe().f_code.co_name, "log_code.data", flag=1) + + def set_filtered_error_code(self, action: str, code_list: list): # OK + """ + 清空,增加,删除错误过滤码 + :param action: 支持 add/remove/clear,当为 clear 时,code_list 可为任意列表 + :param code_list: 需要添加/删除的过滤码列表 + :return: None + """ + origin_code_list = self.get_filtered_error_code["g"]["log_code.data"]["code_list"] + # [{"id": 10000, "title": "HMI请求包解析错误"}, {"id": 10002, "title": "快速调整启动失败"}] + if action == "clear": + code_list = [] + elif action == "add": + for error_code in code_list: + for item in origin_code_list: + if error_code == item["id"]: + break + else: + origin_code_list.append({"id": error_code, "title": ""}) + code_list = origin_code_list + elif action == "remove": + for error_code in code_list: + for item in origin_code_list: + if error_code == item["id"]: + origin_code_list.remove(item) + code_list = origin_code_list + + self.execution("log_code.data.code_list", protocol_flag=1, code_list=code_list) + + def __get_data(self, upper_func, command, flag=0, **kwargs): + msg_id, state = self.execution(command, protocol_flag=flag, **kwargs) + records = clibs.c_hr.get_from_id(msg_id, state) + for record in records: + if "请求发送成功" not in record[0]: + data = eval(record[0])["data"] + return data + # =================================== ↑↑↑ specific functions ↑↑↑ =================================== + + +class ExternalCommunication(QThread): + output = Signal(str, str) + + def __init__(self, ip, port, /): + super().__init__() + self.__c = None + self.ip = ip + self.port = int(port) + self.suffix = "\r" + self.exec_desc = " :--: 返回 true 表示执行成功,false 失败" + + def logger(self, level, module, content, color="black", error="", flag="both"): + clibs.logger(level, module, content, color, flag, signal=self.output) + if level.upper() == "ERROR": + raise Exception(f"{error} | {content}") + + def net_conn(self): + clibs.c_hr.set_socket_params(False, "0.0.0.0", self.port, "\r") + clibs.c_hr.set_socket_params(True, "0.0.0.0", self.port, "\r") + self.__c = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + self.__c.connect((self.ip, self.port)) + self.logger("INFO", "openapi", f"ec: 外部通信连接成功...", "green") + return self.__c + except Exception as err: + self.logger("ERROR", "openapi", f"ec: 外部通信连接失败... {err}", "red", "EcConnFailed") + + def close(self): + if clibs.status["ec"]: + try: + self.__c.close() + except Exception as err: + self.logger("ERROR", "openapi", f"ec: 关闭 EC 连接失败:{err}", "red", "EcCloseFailed") + + def s_string(self, directive): + order = "".join([directive, self.suffix]) + self.__c.send(order.encode()) + time.sleep(clibs.INTERVAL) + + def r_string(self, directive): + result, char = "", "" + while char != self.suffix: + try: + char = self.__c.recv(1).decode(encoding="unicode_escape") + except Exception as err: + self.logger("ERROR", "openapi", f"ec: 获取请求指令 {directive} 的返回数据超时,需确认指令发送格式以及内容正确!具体报错信息如下 {err}", "red", "RecvMsgFailed") + result = "".join([result, char]) + time.sleep(clibs.INTERVAL) + return result + + # =================================== ↓↓↓ specific functions ↓↓↓ =================================== + def motor_on(self): # OK + return self.__exec_cmd("motor_on", "电机上电", self.exec_desc) + + def motor_off(self): # OK + return self.__exec_cmd("motor_off", "电机下电", self.exec_desc) + + def pp_to_main(self): # OK + return self.__exec_cmd("pp_to_main", "程序指针到", self.exec_desc) + + def program_start(self): # OK + return self.__exec_cmd("start", "程序启动(可能需先清告警)", self.exec_desc) + + def program_stop(self): # OK + return self.__exec_cmd("stop", "程序停止", self.exec_desc) + + def clear_alarm(self): # OK + return self.__exec_cmd("clear_alarm", "清除伺服报警", self.exec_desc) + + def switch_operation_auto(self): # OK + return self.__exec_cmd("switch_mode:auto", "切换到自动模式", self.exec_desc) + + def switch_operation_manual(self): # OK + return self.__exec_cmd("switch_mode:manual", "切换到手动模式", self.exec_desc) + + def open_drag_mode(self): # OK + return self.__exec_cmd("open_drag", "打开拖动", self.exec_desc) + + def close_drag_mode(self): # OK + return self.__exec_cmd("close_drag", "关闭拖动", self.exec_desc) + + def get_program_list(self): # OK + return self.__exec_cmd("list_prog", "获取工程列表") + + def get_current_program(self): # OK + return self.__exec_cmd("current_prog", "当前工程") + + def load_program(self, program_name): # OK + return self.__exec_cmd(f"load_prog:{program_name}", "加载工程", self.exec_desc) + + def estop_reset(self): # OK | 复原外部 IO 急停和安全门急停,前提是硬件已复原 + return self.__exec_cmd("estop_reset", "急停复位", self.exec_desc) + + def estopreset_and_clearalarm(self): # OK | 外部 IO/安全门急停/安全碰撞告警都可以清除,前提是硬件已复原 + return self.__exec_cmd("estopreset_and_clearalarm", "急停复位并清除报警", self.exec_desc) + + def motoron_pp2main_start(self): # OK + return self.__exec_cmd("motoron_pptomain_start", "依次执行上电,程序指针到main,启动程序(可以是手动模式,必要时需清告警)", self.exec_desc) + + def motoron_start(self): # OK + return self.__exec_cmd("motoron_start", "依次执行上电,启动程序(可以是手动模式,必要时需清告警)", self.exec_desc) + + def pause_motoroff(self): # OK + return self.__exec_cmd("pause_motoroff", "暂停程序并下电", self.exec_desc) + + def set_program_speed(self, speed: int): # OK | 1-100 + return self.__exec_cmd(f"set_program_speed:{speed}", "设置程序运行速率(滑块)", self.exec_desc) + + def set_soft_estop(self, enable: str): # OK + return self.__exec_cmd(f"set_soft_estop:{enable}", "触发(true)/解除(false)机器人软急停", self.exec_desc) + + def switch_auto_motoron(self): # OK + return self.__exec_cmd("switch_auto_motoron", "切换自动模式并上电", self.exec_desc) + + def open_safe_region(self, number: int): # OK | 1-10 + return self.__exec_cmd(f"open_safe_region:{number}", f"打开第 {number} 个安全区域(1-10,信号控制开关需打开,不限操作模式)", self.exec_desc) + + def close_safe_region(self, number: int): # OK | 1-10 + return self.__exec_cmd(f"close_safe_region:{number}", f"关闭第 {number} 个安全区域(1-10,信号控制开关需打开,不限操作模式)", self.exec_desc) + + def open_reduced_mode(self): # OK + return self.__exec_cmd("open_reduced_mode", "开启缩减模式(不限操作模式)", self.exec_desc) + + def close_reduced_mode(self): # OK + return self.__exec_cmd("close_reduced_mode", "关闭缩减模式(不限操作模式)", self.exec_desc) + + def setdo_value(self, do_name: str, do_value: str): # OK | do_value 为 true/false + return self.__exec_cmd(f"setdo:{do_name},{do_value}", f"设置 {do_name} 的值为 {do_value} ", self.exec_desc) + + def modify_system_time(self, robot_time): # OK + return self.__exec_cmd(f"set_robot_time:{robot_time}", f"修改控制器和示教器的时间为 {robot_time} 的", self.exec_desc) + + # -------------------------------------------------------------------------------------------------- + @property + def motor_on_state(self): # OK + return self.__exec_cmd("motor_on_state", "获取上电状态", " :--: 返回 true 表示已上电,false 已下电") + + @property + def robot_running_state(self): # OK + return self.__exec_cmd("robot_running_state", "获取程序运行状态", " :--: 返回 true 表示正在运行,false 未运行") + + @property + def estop_state(self): # OK | 只表示外部急停,安全门触发不会返回 true,只要有急停标识 E 字母,就会返回 true + return self.__exec_cmd("estop_state", "急停状态", " :--: 返回 true 表示处于急停状态,false 非急停") + + @property + def operation_mode(self): # OK + return self.__exec_cmd("operating_mode", "获取工作模式", " :--: 返回 true 表示自动模式,false 手动模式") + + @property + def home_state(self): # OK | 需要设置一下 "HMI 设置->快速调整" + return self.__exec_cmd("home_state", "获取 HOME 输出状态", " :--: 返回 true 表示法兰中心处于 HOME 点,false 未处于 HOME 点") + + @property + def fault_state(self): # OK + return self.__exec_cmd("fault_state", "获取 故障状态", " :--: 返回 true 表示处于故障状态,false 非故障状态") + + @property + def collision_state(self): # OK | 但是触发后,无法清除? + return self.__exec_cmd("collision_state", "获取碰撞触发状态", " :--: 返回 true 表示碰撞已触发,false 未触发") + + @property + def task_state(self): # OK + return self.__exec_cmd("task_state", "获取机器人运行任务状态", " :--: 返回 program 表示任务正在运行,ready 未运行") + + @property + def get_cart_pos(self): # OK | cart_pos/cart_pos_name 都可以正常返回,区别在返回的前缀,可测试辨别 + return self.__exec_cmd("cart_pos", "获取笛卡尔位置") + + @property + def get_joint_pos(self): # OK | jnt_pos/jnt_pos_name 都可以正常返回,区别在返回的前缀,可测试辨别 + return self.__exec_cmd("jnt_pos", "获取轴位置") + + @property + def get_axis_vel(self): # OK | jnt_vel/jnt_vel_name 都可以正常返回,区别在返回的前缀,可测试辨别 + return self.__exec_cmd("jnt_vel", "获取轴速度") + + @property + def get_axis_trq(self): # OK | jnt_trq/jnt_trq_name 都可以正常返回,区别在返回的前缀,可测试辨别 + return self.__exec_cmd("jnt_trq", "获取轴力矩") + + @property + def reduced_mode_state(self): # OK + return self.__exec_cmd("reduced_mode_state", "获取缩减模式状态", " :--: 返回 true 表示缩减模式,false 非缩减模式") + + def get_io_state(self, io_list: str): # OK | DO0_0,DI1_3,DO2_5,不能有空格 + return self.__exec_cmd(f"io_state:{io_list}", "获取 IO 状态值") + + @property + def alarm_state(self): # OK + return self.__exec_cmd("alarm_state", "获取报警状态", " :--: 返回 true 表示当前有告警,false 没有告警") + + @property + def collision_alarm_state(self): # OK + return self.__exec_cmd("collision_alarm_state", "获取碰撞报警状态", " :--: 返回 true 表示有碰撞告警,false 没有碰撞告警") + + @property + def collision_open_state(self): # OK + return self.__exec_cmd("collision_open_state", "获取碰撞检测开启状态", " :--: 返回 true 表示已开启碰撞检测,false 未开启") + + @property + def controller_is_running(self): # OK + return self.__exec_cmd("controller_is_running", "判断控制器是否开机", " :--: 返回 true 表示控制器正在运行,false 未运行") + + @property + def encoder_low_battery_state(self): # OK + return self.__exec_cmd("encoder_low_battery_state", "编码器低电压报警状态", " :--: 返回 true 表示编码器处于低电压状态,false 电压正常") + + @property + def robot_error_code(self): # OK + return self.__exec_cmd("robot_error_code", "获取机器人错误码") + + @property + def rl_pause_state(self): # OK + # 0 -- 初始化状态,刚开机上电时 + # 1 -- RL 运行中 + # 2 -- HMI 暂停 + # 3 -- 系统 IO 暂停 + # 4 -- 寄存器功能码暂停 + # 5 -- 外部通讯暂停 + # 6 -- + # 7 -- Pause 指令暂停 + # 8 -- + # 9 -- + # 10 -- 外部 IO 急停 + # 11 -- 安全门急停 + # 12 -- 其他因素停止,比如碰撞检测 + return self.__exec_cmd("program_full", "获取 RL 的暂停状态", " :--: 返回值含义详见功能定义") + + @property + def program_reset_state(self): # OK + return self.__exec_cmd("program_reset_state", "获取程序复位状态", " :--: 返回 true 表示指针指向 main,false 未指向 main") + + @property + def program_speed_value(self): # OK | 速度滑块 + return self.__exec_cmd("program_speed", "获取程序运行速度") + + @property + def robot_is_busy(self): # OK | 触发条件为 pp2main/重载工程/推送到控制器,最好测试工程大一些,比较容易触发 + return self.__exec_cmd("robot_is_busy", "获取程序忙碌状态", " :--: 返回 1 表示控制器忙碌,0 非忙碌状态") + + @property + def robot_is_moving(self): # OK + return self.__exec_cmd("robot_is_moving", "获取程序运行状态", " :--: 返回 true 表示机器人正在运动,false 未运动") + + @property + def safe_door_state(self): # OK + return self.__exec_cmd("safe_door_state", "获取安全门状态", " :--: 返回 true 表示安全门已触发,false 未触发") + + @property + def soft_estop_state(self): # OK + return self.__exec_cmd("soft_estop_state", "获取软急停状态", " :--: 返回 true 表示软急停已触发,false 未触发") + + @property + def get_cart_vel(self): # OK + return self.__exec_cmd("cart_vel", "获取笛卡尔速度") + + @property + def get_tcp_pos(self): # OK + return self.__exec_cmd("tcp_pose", "获取 TCP 位姿") + + @property + def get_tcp_vel(self): # OK + return self.__exec_cmd("tcp_vel", "获取 TCP 速度") + + @property + def get_tcp_vel_mag(self): # OK + return self.__exec_cmd("tcp_vel_mag", "获取 TCP 合成线速度") + + @property + def ext_estop_state(self): # OK + return self.__exec_cmd("ext_estop_state", "获取外部轴急停状态", " :--: 返回 true 表示外部轴急停已触发,false 未触发") + + @property + def hand_estop_state(self): # OK + return self.__exec_cmd("hand_estop_state", "获取手持急停状态", " :--: 返回 true 表示手持急停已触发,false 未触发") + + @property + def collaboration_state(self): # OK + return self.__exec_cmd("collaboration_state", "获取协作模式状态(其实就是缩减模式)", " :--: 返回 true 表示协作模式,false 非协作模式") + + def __exec_cmd(self, directive, description, more_desc=""): + self.s_string(directive) + result = self.r_string(directive).strip() + self.logger("INFO", "openapi", f"ec: 执行{description}指令是 {directive},返回值为 {result}{more_desc}") + return result + + +class PreDos(object): + def __init__(self, ip, ssh_port, username, password): + self.__ssh = None + self.__sftp = None + self.ip = ip + self.ssh_port = ssh_port + self.username = username + self.password = password + + def __ssh2server(self): + try: + self.__ssh = SSHClient() + self.__ssh.set_missing_host_key_policy(AutoAddPolicy()) + self.__ssh.connect(hostname=self.ip, port=self.ssh_port, username=self.username, password=self.password) + self.__sftp = self.__ssh.open_sftp() + except Exception as err: + print(f"predos: SSH 无法连接到 {self.ip}:{self.ssh_port},需检查网络连通性或者登录信息是否正确 {err}") + raise Exception("SshConnFailed") + + def push_prj_to_server(self, prj_file): + # prj_file:本地工程完整路径 + self.__ssh2server() + prj_name = prj_file.split("/")[-1].split(".")[0] + self.__sftp.put(prj_file, f"/tmp/{prj_name}.zip") + cmd = f"cd /tmp; mkdir {prj_name}; unzip -d {prj_name} -q {prj_name}.zip; rm -rf /tmp/{prj_name}.zip; " + cmd += f"sudo rm -rf /home/luoshi/bin/controller/projects/{prj_name}; " + cmd += f"sudo mv /tmp/{prj_name}/ /home/luoshi/bin/controller/projects/" + stdin, stdout, stderr = self.__ssh.exec_command(cmd, get_pty=True) + stdin.write(self.password + "\n") + stdout.read().decode() # 需要read一下才能正常执行 + stderr.read().decode() + self.__ssh.close() + + def pull_prj_from_server(self, prj_name, local_prj_path): # NG | 可以拉取文件,但是导入之后,有问题 + # prj_name:要拉取的服务端工程名 + # local_prj_path:本地工程文件的完整路径 + self.__ssh2server() + cmd = f"cd /tmp/; sudo rm -rf {prj_name}*; sudo cp -rf /home/luoshi/bin/controller/projects/{prj_name} .; " + cmd += f"sudo zip -q -r {prj_name}.zip {prj_name}/; sudo rm -rf {prj_name}" + stdin, stdout, stderr = self.__ssh.exec_command(cmd, get_pty=True) + stdin.write(self.password + "\n") + print(stdout.read().decode()) # 需要read一下才能正常执行 + print(stderr.read().decode()) + + self.__sftp.get(f"/tmp/{prj_name}.zip", local_prj_path) + cmd = f"sudo rm -rf /tmp/{prj_name}.zip" + stdin, stdout, stderr = self.__ssh.exec_command(cmd, get_pty=True) + stdin.write(self.password + "\n") + print(stdout.read().decode()) # 需要read一下才能正常执行 + print(stderr.read().decode()) + + self.__ssh.close() + + def push_file_to_server(self, local_file, server_file): + # local_file:本地文件完整路径 + # server_file:服务端文件完整路径 + self.__ssh2server() + filename = local_file.split("\\")[-1].split("/")[-1] + self.__sftp.put(local_file, f"/tmp/{filename}") + cmd = f"sudo mv /tmp/{filename} {server_file}" + stdin, stdout, stderr = self.__ssh.exec_command(cmd, get_pty=True) + stdin.write(self.password + "\n") + stdout.read().decode() # 需要read一下才能正常执行 + stderr.read().decode() + self.__ssh.close() + + def pull_file_from_server(self, server_file, local_file): + # local_file:本地文件完整路径 + # server_file:服务端文件完整路径 + self.__ssh2server() + cmd = f"sudo cp {server_file} /tmp/" + stdin, stdout, stderr = self.__ssh.exec_command(cmd, get_pty=True) + stdin.write(self.password + "\n") + stdout.read().decode() # 需要read一下才能正常执行 + stderr.read().decode() + filename = server_file.split("/")[-1] + self.__sftp.get(f"/tmp/{filename}", f"{local_file}") + cmd = f"sudo rm -rf /tmp/{filename}" + stdin, stdout, stderr = self.__ssh.exec_command(cmd, get_pty=True) + stdin.write(self.password + "\n") + stdout.read().decode() # 需要read一下才能正常执行 + stderr.read().decode() + self.__ssh.close() + + +class RobotInit(object): + @staticmethod + def modbus_init(): + pd = PreDos(clibs.ip_addr, clibs.ssh_port, clibs.username, clibs.password) + # 推送配置文件 + print("init: 推送配置文件 fieldbus_device.json/registers.json/registers.xml 到控制器,并配置 IO 设备,设备号为 7...") + robot_params = clibs.c_hr.get_robot_params + robot_type = robot_params["robot_type"] + security_type = robot_params["security_type"] + controller_type = robot_params["controller_type"] + io_device_file = "_".join(["io_device", controller_type, security_type, "1"]) + + user_settings = "/home/luoshi/bin/controller/user_settings" + interactive_data = f"/home/luoshi/bin/controller/interactive_data/{robot_type}" + + config_files = [ + f"{clibs.PREFIX}/files/projects/fieldbus_device.json", + f"{clibs.PREFIX}/files/projects/registers.json", + f"{clibs.PREFIX}/files/projects/registers.xml" + ] + for config_file in config_files: + filename = config_file.split("/")[-1] + pd.push_file_to_server(config_file, f"{user_settings}/{filename}") + pd.push_file_to_server(config_file, f"{interactive_data}/{filename}") + + io_device_autotest = {"ai_num": 0, "ao_num": 0, "di_num": 16, "do_num": 16, "extend_attr": {"mode": "slaver", "name": "autotest", "type": "MODBUS"}, "id": 7, "name": "autotest", "type": 6} + + io_device_file_local = f"{io_device_file}" + io_device_file_local_tmp = f"{io_device_file}_tmp" + io_device_file_remote = f"{user_settings}/{io_device_file}" + pd.pull_file_from_server(io_device_file_remote, io_device_file_local) + with open(io_device_file_local, mode="r", encoding="utf-8") as f: + data = json.load(f) + for _ in data["device_list"]: + if _["extend_attr"].get("name", None) == "autotest": + break + else: + data["device_list"].append(io_device_autotest) + with open(io_device_file_local_tmp, mode="w", encoding="utf-8") as f_tmp: + json.dump(data, f_tmp, indent=4) + pd.push_file_to_server(io_device_file_local_tmp, f"{user_settings}/{io_device_file}") + pd.push_file_to_server(io_device_file_local_tmp, f"{interactive_data}/{io_device_file}") + + # os.remove(io_device_file_local) + # os.remove(io_device_file_local_tmp) + clibs.c_hr.reload_io() + clibs.c_hr.reload_registers() + clibs.c_hr.reload_fieldbus() + clibs.c_hr.set_fieldbus_device_params("autotest", True) + + def robot_init(self): + pd = PreDos(clibs.ip_addr, clibs.ssh_port, clibs.username, clibs.password) + # 推送配置文件 + print("init: 推送配置文件 fieldbus_device.json/registers.json/registers.xml 到控制器,并配置 IO 设备,设备号为 7...") + robot_params = clibs.c_hr.get_robot_params + robot_type = robot_params["robot_type"] + security_type = robot_params["security_type"] + controller_type = robot_params["controller_type"] + io_device_file = "_".join(["io_device", controller_type, security_type, "1"]) + + user_settings = "/home/luoshi/bin/controller/user_settings" + interactive_data = f"/home/luoshi/bin/controller/interactive_data/{robot_type}" + + config_files = [ + "..\\assets\\configs\\fieldbus_device.json", + "..\\assets\\configs\\registers.json", + "..\\assets\\configs\\registers.xml" + ] + for config_file in config_files: + filename = config_file.split("\\")[-1] + pd.push_file_to_server(config_file, f"{user_settings}/{filename}") + pd.push_file_to_server(config_file, f"{interactive_data}/{filename}") + + io_device_autotest = {"ai_num": 0, "ao_num": 0, "di_num": 16, "do_num": 16, "extend_attr": {"mode": "slaver", "name": "autotest", "type": "MODBUS"}, "id": 7, "name": "autotest", "type": 6} + io_device_file_local = f"..\\assets\\configs\\{io_device_file}" + io_device_file_local_tmp = f"..\\assets\\configs\\{io_device_file}_tmp" + io_device_file_remote = f"{user_settings}/{io_device_file}" + pd.pull_file_from_server(io_device_file_remote, io_device_file_local) + with open(io_device_file_local, mode="r", encoding="utf-8") as f: + data = json.load(f) + for _ in data["device_list"]: + if _["extend_attr"].get("name", None) == "autotest": + break + else: + data["device_list"].append(io_device_autotest) + with open(io_device_file_local_tmp, mode="w", encoding="utf-8") as f_tmp: + json.dump(data, f_tmp, indent=4) + pd.push_file_to_server(io_device_file_local_tmp, f"{user_settings}/{io_device_file}") + pd.push_file_to_server(io_device_file_local_tmp, f"{interactive_data}/{io_device_file}") + + clibs.c_hr.reload_io() + clibs.c_hr.reload_registers() + clibs.c_hr.reload_fieldbus() + clibs.c_hr.set_fieldbus_device_params("autotest", True) + + # 触发急停并恢复 + clibs.c_md.r_soft_estop(0) + clibs.c_md.r_soft_estop(1) + + # 断开示教器连接 + print("init: 断开示教器连接...") + clibs.c_hr.switch_tp_mode("without") + + # 清空 system IO 配置 + print("init: 清空所有的 System IO 功能配置...") + clibs.c_hr.update_system_io_configuration([], [], [], [], []) + + # 关闭缩减模式 + clibs.c_md.r_reduced_mode(0) + + # 打开软限位 + print("init: 打开软限位开关...") + clibs.c_hr.set_soft_limit_params(enable=True) + + # 关闭安全区域 + print("init: 正在关闭所有的安全区,并关闭总使能开关...") + clibs.c_hr.set_safety_area_overall(False) + clibs.c_hr.set_safety_area_signal(False) + for i in range(10): + clibs.c_hr.set_safety_area_enable(i, False) + + # 打开外部通信,并设置控制器时间同步 + print("init: 配置并打开外部通信,默认服务器,8080端口,后缀为 \"\\r\"...") + clibs.c_hr.set_socket_params(True, "8080", "\r", 1) + clibs.c_ec.modify_system_time(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))) + + # 关闭拖动 + if robot_type.upper()[:2] not in ["XB", "NB"]: + print("init: 关闭拖动模式...") + clibs.c_hr.set_drag_params(False, 1, 2) + + # 关闭碰撞检测 + print("init: 关闭碰撞检测...") + clibs.c_hr.set_collision_params(False, 0, 1, 100) + + # 清除所有过滤错误码 + print("init: 清除所有过滤错误码设定...") + clibs.c_hr.set_filtered_error_code("clear", []) + + # 回拖动位姿 + print("init: 正在回拖动位姿...") + clibs.c_hr.switch_operation_mode("manual") + clibs.c_hr.switch_motor_state("on") + clibs.c_hr.set_quickturn_pos(enable_drag=True) + clibs.c_hr.move2quickturn("drag") + while True: + if clibs.c_md.w_robot_is_moving: + time.sleep(1) + else: + break + clibs.c_hr.stop_move(1) + clibs.c_hr.switch_motor_state("off") + clibs.c_hr.close() + + # 清除所有告警 + clibs.c_md.r_clear_alarm() + + def fw_updater(self, local_file_path): + fw_size = os.path.getsize(local_file_path) + if fw_size > 10000000: + # get previous version of xCore + version_previous = clibs.hr.get_robot_params["version"] + + # var def + remote_file_path = "./upgrade/lircos.zip" + fw_content = bytearray() + package_data = bytearray() + package_data_with_head = bytearray() + + # socket connect + print(f"update firmware: 正在连接 {clibs.ip_addr}:{clibs.upgrade_port}...") + try: + tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + tcp_socket.connect((clibs.ip_addr, clibs.upgrade_port)) + tcp_socket.setblocking(True) + except Exception as Err: + print(f"update firmware: {Err} | 连接 {clibs.ip_addr}:{clibs.upgrade_port} 失败...") + raise Exception("UpgradeFailed") + + # get firmware content of binary format + print(f"update firmware: 正在读取 {local_file_path} 文件内容...") + with open(local_file_path, "rb") as f_fw: + fw_content += f_fw.read() + + # construct package data: http://confluence.i.rokae.com/pages/viewpage.action?pageId=15634148 + print("update firmware: 正在构造数据包,以及包头...") + # 1 protocol id + protocol_id = c_ushort(socket.htons(0)) # 2 bytes + package_data += protocol_id + + # 2 write type + write_type = c_ubyte(0) + package_data += bytes(write_type) + + # 3 md5 + md5_hash = hashlib.md5() + md5_hash.update(fw_content) + fw_md5 = md5_hash.hexdigest() + i = 0 + while i < len(fw_md5): + _ = (fw_md5[i:i + 2]) + package_data += bytes.fromhex(_) + i += 2 + + # 4 remote path len + remote_file_path_len = c_ushort(socket.htons(len(remote_file_path))) + package_data += remote_file_path_len + + # 5 remote path + package_data += remote_file_path.encode("ascii") + + # 6 file + package_data += fw_content + + # construct communication protocol: http://confluence.i.rokae.com/pages/viewpage.action?pageId=15634045 + # 1 get package data with header + package_size = c_uint(socket.htonl(len(package_data))) + package_type = c_ubyte(1) # 0-无协议 1-文件 2-json + package_reserve = c_ubyte(0) # 预留位 + + package_data_with_head += package_size + package_data_with_head += package_type + package_data_with_head += package_reserve + package_data_with_head += package_data + + # 2 send data to server + print("update firmware: 正在发送数据到 xCore,升级控制器无需重启,升级配置文件会自动软重启...") + start = 0 + len_of_package = len(package_data_with_head) + while start < len_of_package: + end = 10240 + start + if end > len_of_package: + end = len_of_package + sent = tcp_socket.send((package_data_with_head[start:end])) + time.sleep(0.01) + if sent == 0: + raise RuntimeError("socket connection broken") + else: + start += sent + + waited = 5 if fw_size > 10000000 else 25 + time.sleep(waited) + + if fw_size > 10000000: + # get current version of xCore + version_current = clibs.c_hr.get_robot_params["version"] + print(f"update firmware: 控制器升级成功:from {version_previous} to {version_current} :)") + else: + print(f"update firmware: 配置文件升级成功 :)") + + tcp_socket.close() + + +class UpgradeJsonCmd(object): + def __init__(self): + self.__c = None + self.__sock_conn() + + def __sock_conn(self): + # socket connect + print(f"正在连接 {clibs.ip_addr}:{clibs.upgrade_port}...") + try: + self.__c = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.__c.connect((clibs.ip_addr, clibs.upgrade_port)) + self.__c.setblocking(True) + self.__c.settimeout(3) + except Exception as Err: + print(f"upgrade: {Err} | 连接 {clibs.ip_addr}:{clibs.upgrade_port} 失败...") + raise Exception("UpgradePortConnFailed") + + @staticmethod + def __do_package(cmd): + package_size = struct.pack("!I", len(cmd)) + package_type = struct.pack("B", 2) + reserved_byte = struct.pack("B", 0) + return package_size + package_type + reserved_byte + cmd + + def __recv_result(self, cmd): + time.sleep(clibs.INTERVAL) + try: + res = self.__c.recv(10240).decode() + except Exception as err: + print(f"upgrade: 请求命令 {cmd.decode()} 的返回错误 {err}") + raise Exception("ResponseError") + + time.sleep(clibs.INTERVAL) + return res + + def __exec(self, command: dict): + try: + self.__c.recv(10240) + except Exception: + pass + cmd = json.dumps(command, separators=(",", ":")).encode("utf-8") + self.__c.sendall(self.__do_package(cmd)) + res = self.__recv_result(cmd) + print(f"upgrade: 请求命令 {cmd.decode()} 的返回信息:{res}") + + def erase_cfg(self): + # 一键抹除机器人配置(.rc_cfg)、交互数据配置(interactive_data),但保留用户日志 + # 场景:如果xCore版本升级跨度过大,配置文件可能不兼容导致无法启动,可以使用该功能抹除配置,重新生成配置。 + # 机器人参数、零点等会丢失! + self.__exec({"cmd": "erase"}) + + def clear_rubbish(self): + self.__exec({"cmd": "clearRubbish"}) + + def soft_reboot(self): + self.__exec({"cmd": "soft_reboot"}) + + def version_query(self): + self.__exec({"query": "version"}) + + def robot_reboot(self): + self.__exec({"cmd": "reboot"}) + + def reset_passwd(self): + # 不生效,有问题 + self.__exec({"cmd": "passwd"}) + + def backup_origin(self): + # xCore + # .rc_cfg / + # interactive_data / + # module / + # demo_project / + # robot_cfg / + # dev_eni.xml + # ecat_license + # libemllI8254x.so & libemllI8254x_v3.so + # set_network_parameters + self.__exec({"cmd": "backup_origin"}) + + def origin_recovery(self): + self.__exec({"cmd": "recover"}) diff --git a/code/durable/create_plot.py b/code/durable/create_plot.py new file mode 100644 index 0000000..3074b22 --- /dev/null +++ b/code/durable/create_plot.py @@ -0,0 +1,85 @@ +import os.path +import matplotlib.pyplot as plt +import pandas +from matplotlib.widgets import Slider +from PySide6.QtCore import Signal, QThread +from common import clibs + + +# class DrawCurves(QThread): +# output = Signal(str, str) +# +# def __init__(self, /): +# super().__init__() +# +# @staticmethod +# def logger(level, module, content, color="black", error="", flag="both", signal=output): +# clibs.logger(level, module, content, color, flag, signal) +# if level.upper() == "ERROR": +# raise Exception(error) +# +# def initialization(self): +# path, curves = None, None +# try: +# path = clibs.data_dd["path"] +# curves = clibs.data_dd["curves"] +# except Exception: +# clibs.w2t("程序未开始运行,暂无数据可以展示......\n", "red") +# return None, None +# +# for curve in curves: +# if not os.path.exists(f"{path}/{curve}.csv"): +# clibs.w2t(f"{curve}曲线数据暂未生成,稍后再试......\n", "orange") +# return None, None +# +# return path, curves +# +# +# def data_plot(path, curve): +# titles = {"hw_joint_vel_feedback": "各关节最大速度曲线", "device_servo_trq_feedback": "各关节平均有效转矩曲线"} +# ylabels = {"hw_joint_vel_feedback": "速度(rad/s)", "device_servo_trq_feedback": "转矩(Nm)"} +# +# fig, axes = plt.subplots(figsize=(10, 4.5), dpi=100) +# cols = [f"{curve}_{i}" for i in range(6)] +# cols.insert(0, "time") +# df = pandas.read_csv(f"{path}/{curve}.csv") +# plt.plot(df[cols[1]], label="一轴") +# plt.plot(df[cols[2]], label="二轴") +# plt.plot(df[cols[3]], label="三轴") +# plt.plot(df[cols[4]], label="四轴") +# plt.plot(df[cols[5]], label="五轴") +# plt.plot(df[cols[6]], label="六轴") +# axes.set_title(titles[curve]) +# axes.set_ylabel(ylabels[curve]) +# axes.legend(loc="upper right") +# +# slider_position = plt.axes((0.1, 0.01, 0.8, 0.05), facecolor="blue") # (left, bottom, width, height) +# scrollbar = Slider(slider_position, 'Time', 1, int(len(df)), valstep=1) +# +# def update(val): +# pos = scrollbar.val +# axes.set_xlim([pos, pos + 10]) +# fig.canvas.draw_idle() +# +# scrollbar.on_changed(update) +# fig.tight_layout(rect=(0, 0.02, 0.96, 1)) # tuple (left, bottom, right, top) +# +# +# def main(): +# path, curves = initialization() +# if not path or not curves: +# return +# +# for curve in curves: +# data_plot(path, curve) +# +# plt.show() +# +# +# plt.rcParams['font.sans-serif'] = ['SimHei'] +# plt.rcParams['axes.unicode_minus'] = False +# plt.rcParams['figure.dpi'] = 100 +# plt.rcParams['font.size'] = 14 +# plt.rcParams['lines.marker'] = 'o' +# plt.rcParams["figure.autolayout"] = True +# \ No newline at end of file diff --git a/code/durable/factory_test.py b/code/durable/factory_test.py new file mode 100644 index 0000000..ed146f8 --- /dev/null +++ b/code/durable/factory_test.py @@ -0,0 +1,239 @@ +import json +import threading +import time +import pandas +import numpy +import math +import csv +from PySide6.QtCore import Signal, QThread +from common import clibs + + +class DoBrakeTest(QThread): + output = Signal(str, str) + + def __init__(self, dir_path, interval, proc, /): + super().__init__() + self.dir_path = dir_path + self.interval = interval + self.proc = proc + self.idx = 6 + + def logger(self, level, module, content, color="black", error="", flag="both"): + clibs.logger(level, module, content, color, flag, signal=self.output) + if level.upper() == "ERROR": + raise Exception(f"{error} | {content}") + + def initialization(self, data_dirs, data_files): + def check_files(): + if len(curves) == 0: + self.logger("ERROR", "factory-check_files", "未查询到需要记录数据的曲线,至少选择一个!", "red", "CurveNameError") + + if len(data_dirs) != 0 or len(data_files) != 1: + self.logger("ERROR", "factory-check_files", "初始路径下不允许有文件夹,且初始路径下只能存在一个工程文件 —— *.zip,确认后重新运行!", "red", "InitFileError") + + if not data_files[0].endswith(".zip"): + self.logger("ERROR", "factory-check_files", f"{data_files[0]} 不是一个有效的工程文件,需确认!", "red", "ProjectFileError") + + return data_files[0], interval + + def get_configs(): + robot_type, records = None, None + msg_id, state = clibs.c_hr.execution("controller.get_params") + records = clibs.c_hr.get_from_id(msg_id, state) + for record in records: + if "请求发送成功" not in record[0]: + robot_type = eval(record[0])["data"]["robot_type"] + server_file = f"/home/luoshi/bin/controller/robot_cfg/{robot_type}/{robot_type}.cfg" + local_file = self.dir_path + f"/{robot_type}.cfg" + clibs.c_pd.pull_file_from_server(server_file, local_file) + + try: + with open(local_file, mode="r", encoding="utf-8") as f_config: + configs = json.load(f_config) + except Exception as Err: + self.logger("ERROR", "factory-get_configs", f"无法打开 {local_file}
{Err}", "red", "OpenFileError") + + # 最大角速度,额定电流,减速比,额定转速 + version = configs["VERSION"] + m_avs = configs["MOTION"]["JOINT_MAX_SPEED"] + m_rts = configs["MOTOR"]["RATED_TORQUE"] # 电机额定转矩rt for rated torque + m_tcs = [1, 1, 1, 1, 1, 1] # 电机转矩常数,tc for torque constant + m_rcs = [] + for i in range(len(m_tcs)): + m_rcs.append(m_rts[i] / m_tcs[i]) # 电机额定电流,rc for rated current + clibs.insert_logdb("INFO", "do_brake", f"get_configs: 机型文件版本 {robot_type}_{version}") + clibs.insert_logdb("INFO", "do_brake", f"get_configs: 各关节角速度 {m_avs}") + clibs.insert_logdb("INFO", "do_brake", f"get_configs: 各关节额定电流 {m_rcs}") + return m_avs, m_rcs + + prj_file, interval = check_files() + avs, rcs = get_configs() + params = { + "prj_file": prj_file, + "interval": interval, + "avs": avs, + "rcs": rcs, + } + self.logger("INFO", "factory-initialization", "数据目录合规性检查结束,未发现问题......", "green") + return params + + @staticmethod + def change_curve_state(curves, stat): + display_pdo_params = [{"name": name, "channel": chl} for name in curves for chl in range(6)] + clibs.c_hr.execution("diagnosis.open", open=stat, display_open=stat, overrun=True, turn_area=True, delay_motion=False) + clibs.c_hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params, frequency=50, version="1.4.1") + + def run_rl(self, params, curves): + prj_file, interval = params["prj_file"], params["interval"] + # 1. 关闭诊断曲线,触发软急停,并解除,目的是让可能正在运行着的机器停下来,切手动模式并下电 + self.change_curve_state(curves, False) + clibs.c_md.r_soft_estop(0) + clibs.c_md.r_soft_estop(1) + clibs.c_md.r_clear_alarm() + clibs.c_md.write_act(False) + time.sleep(1) # 让曲线彻底关闭 + + # 2. reload工程后,pp2main,并且自动模式和上电 + prj_name = ".".join(prj_file.split("/")[-1].split(".")[:-1]) + prj_path = f"{prj_name}/_build/{prj_name}.prj" + clibs.c_hr.execution("overview.reload", prj_path=prj_path, tasks=["factory"]) + clibs.c_hr.execution("rl_task.pp_to_main", tasks=["factory"]) + clibs.c_hr.execution("state.switch_auto") + clibs.c_hr.execution("state.switch_motor_on") + + # 3. 开始运行程序 + clibs.c_hr.execution("rl_task.set_run_params", loop_mode=True, override=1.0) + clibs.c_hr.execution("rl_task.run", tasks=["factory"]) + t_start = time.time() + while True: + if clibs.c_md.read_ready_to_go() == 1: + clibs.c_md.write_act(True) + break + else: + if (time.time() - t_start) > 15: + self.logger("ERROR", "factory-run_rl", "15s 内未收到机器人的运行信号,需要确认RL程序编写正确并正常执行...", "red", "ReadySignalTimeoutError") + else: + time.sleep(1) + + # 4. 获取初始数据,周期时间,首次的各轴平均电流值,打开诊断曲线,并执行采集 + time.sleep(10) # 等待 RL 程序中 scenario_time 初始化 + t_start = time.time() + while True: + scenario_time = float(f"{float(clibs.c_md.read_scenario_time()):.2f}") + if scenario_time != 0: + self.logger("INFO", "factory-run_rl", f"耐久工程的周期时间:{scenario_time}s | 单轮次执行时间:{scenario_time+interval}~{scenario_time*2+interval}") + break + else: + time.sleep(1) + if (time.time() - t_start) > 900: + self.logger("ERROR", "factory-run_rl", f"900s 内未收到耐久工程的周期时间,需要确认RL程序和工具通信交互是否正常执行...", "red", "GetScenarioTimeError") + + # 6. 准备数据保存文件 + for curve in curves: + with open(f"{self.dir_path}/{curve}.csv", mode="a+", newline="") as f_csv: + titles = [f"{curve}_{i}" for i in range(6)] + titles.insert(0, "time") + csv_writer = csv.writer(f_csv) + csv_writer.writerow(titles) + + # 7. 开始采集 + count = 0 + while clibs.running: + this_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + next_time_1 = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()+scenario_time+interval+1)) + next_time_2 = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()+scenario_time+interval+1+scenario_time)) + self.logger("INFO", "factory-run_rl", f"[{this_time}] 当前次数:{count:09d} | 预计下次数据更新时间:{next_time_1}~{next_time_2}", "#008B8B") + count += 1 + # 固定间隔,更新一次数据,打开曲线,获取周期内电流,关闭曲线 + time.sleep(interval) + while True: + capture_start = clibs.c_md.read_capture_start() + if capture_start == 1: + break + else: + time.sleep(0.1) + + self.change_curve_state(curves, True) + time.sleep(scenario_time) + end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()-scenario_time)) + self.change_curve_state(curves, False) + # 保留数据并处理输出 + self.gen_results(params, curves, start_time, end_time) + else: + self.change_curve_state(curves, False) + self.logger("INFO", "factory", "后台数据清零完成,现在可以重新运行其他程序。", "green") + + def gen_results(self, params, curves, start_time, end_time): + clibs.cursor.execute(f"select content from logs where time between '{start_time}' and '{end_time}' and content like '%diagnosis.result%' order by id asc") + records = clibs.cursor.fetchall() + self.data_proc(records, params, curves) + + def data_proc(self, records, params, curves): + for curve in curves: + if curve == "device_servo_trq_feedback": + # proc_device_servo_trq_feedback(records, params, w2t) + t = threading.Thread(target=self.proc_device_servo_trq_feedback, args=(records, params)) + t.daemon = True + t.start() + elif curve == "hw_joint_vel_feedback": + # proc_hw_joint_vel_feedback(records, params, w2t) + t = threading.Thread(target=self.proc_hw_joint_vel_feedback, args=(records, params)) + t.daemon = True + t.start() + + def proc_device_servo_trq_feedback(self, records, params): + d_trq, rcs, results = [[], [], [], [], [], []], params["rcs"], [time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))] + for record in records: + data = eval(record[0])["data"] + for item in data: + d_item = reversed(item["value"]) + for axis in range(6): + if item.get("channel", None) == axis and item.get("name", None) == "device_servo_trq_feedback": + d_trq[axis].extend(d_item) + + for axis in range(6): + df = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq[axis]}) + _ = math.sqrt(numpy.square(df[df.columns[0]].values * 1.27 / 1000).sum() / len(df)) + results.append(_) + + path = "/".join(params["prj_file"].split("/")[:-1]) + with open(f"{path}/device_servo_trq_feedback.csv", mode="a+", newline="") as f_csv: + csv_writer = csv.writer(f_csv) + csv_writer.writerow(results) + + def proc_hw_joint_vel_feedback(self, records, params): + d_trq, rcs, results = [[], [], [], [], [], []], params["rcs"], [time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))] + for record in records: + data = eval(record[0])["data"] + for item in data: + d_item = reversed(item["value"]) + for axis in range(6): + if item.get("channel", None) == axis and item.get("name", None) == "hw_joint_vel_feedback": + d_trq[axis].extend(d_item) + + for axis in range(6): + df = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_trq[axis]}) + _ = df.max().iloc[0] + results.append(_) + + path = "/".join(params["prj_file"].split("/")[:-1]) + with open(f"{path}/hw_joint_vel_feedback.csv", mode="a+", newline="") as f_csv: + csv_writer = csv.writer(f_csv) + csv_writer.writerow(results) + + def processing(self): + time_start = time.time() + clibs.running[self.idx] = 1 + + data_dirs, data_files = clibs.traversal_files(self.dir_path, self.output) + params = self.initialization(data_dirs, data_files) + prj_file = params["prj_file"] + clibs.c_pd.push_prj_to_server(prj_file) + self.run_rl(params) + + self.logger("INFO", "brake-processing", "-"*60 + "
全部处理完毕
", "purple") + time_total = time.time() - time_start + msg = f"处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s" + self.logger("INFO", "brake-processing", msg) \ No newline at end of file diff --git a/code/test.py b/code/test.py new file mode 100644 index 0000000..2265c7f --- /dev/null +++ b/code/test.py @@ -0,0 +1,113 @@ +import time + +# import common.openapi as openapi +# +# hr = openapi.HmiRequest("10.2.21.252", 5050, 6666) +# for _ in range(3): +# hr.execution("controller.heart") +# time.sleep(1) +# +# hr.close() + + + +import pymysql + +conn = pymysql.connect(host='10.2.20.216', user='root', password='Rokae_123457', port=13306, charset='utf8') +cursor = conn.cursor() +cursor.execute("SET autocommit = 1;") +cursor.execute("use fanmingfu;") +# cursor.execute("insert into 20250315153551_log (module, level, content) values (%s, %s, %s)", ("aioaaaaaa", "debug", "testing information")) +# logger("ERROR", "clibs", f"数据文件夹{dir_path}不存在,请确认后重试......\n", signal, "red", "PathNotExistError", idx) + +level = "ERROR" +module = "clibs" +content = "{'data': {'name': 'xCore'}, 'id': 'controller.heart-1742374255.8898985'}" +tb_name = "20250319162718_log" +cursor.execute(f"INSERT INTO {tb_name} (level, module, content) VALUES (%s, %s, %s)", (level, module, content)) + +# conn.commit() +# ============================================ +# def tttt(flag, signal, cursor, **data): +# if flag == "signal": +# print(f"data = {data['signals']}") +# elif flag == "cursor": +# print(f"data = {data['cursors']}") +# elif flag == "both": +# print(f"data = {data}") +# print(f"data = {data['signals']}") +# print(f"data = {data['cursors']}") +# +# +# tttt("both", 1, 1, signals=123, cursors=456) + +# ============================================ + +# import sys +# from time import sleep +# from PySide6.QtCore import * +# from PySide6.QtGui import * +# from PySide6.QtWidgets import * +# +# +# class MyWindow(QMainWindow): +# range_number = Signal(int) +# +# def __init__(self) -> None: +# super().__init__() +# self.setWindowTitle("QThread学习") +# self.resize(800, 600) +# self.setup_ui() +# self.setup_thread() +# +# def setup_ui(self): +# self.mylistwidget = QListWidget(self) +# self.mylistwidget.resize(500, 500) +# self.mylistwidget.move(20, 20) +# +# self.additem_button = QPushButton(self) +# self.additem_button.resize(150, 30) +# self.additem_button.setText("填充QListWidget") +# self.additem_button.move(530, 20) +# +# def setup_thread(self): +# self.thread1 = QThread(self) # 创建一个线程 +# self.range_thread = WorkThread() # 实例化线程类 +# self.range_thread.moveToThread(self.thread1) # 将类移动到线程中运行 +# # 线程数据传回信号,用add_item函数处理 +# self.range_thread.range_requested.connect(self.add_item) +# self.additem_button.clicked.connect(self.start_thread) +# self.range_number.connect(self.range_thread.range_proc) +# # self.additem_button.clicked.connect(self.range_thread.range_proc) # 连接到线程类的函数 +# +# def start_thread(self): +# self.thread1.start() +# range_number = 30 +# self.range_number.emit(range_number) # 发射信号让线程接收需要range多少 +# +# def add_item(self, requested_number): # 线程传回参数 +# text = f"第{requested_number}项————Item" +# item = QListWidgetItem() +# item.setIcon(QPixmap()) +# item.setText(text) +# self.mylistwidget.addItem(item) +# +# +# class WorkThread(QObject): +# range_requested = Signal(int) # 括号里是传出的参数的类型 +# +# def __init__(self): +# super().__init__() +# +# def range_proc(self, number): # number即为从主线程接收的参数 +# print(number) +# for i in range(number): +# self.range_requested.emit(i) # 发射信号 +# sleep(0.5) +# +# +# if __name__ == "__main__": +# app = QApplication(sys.argv) +# window = MyWindow() +# window.show() +# app.exec() \ No newline at end of file diff --git a/code/ui/login_window.py b/code/ui/login_window.py new file mode 100644 index 0000000..df2f7b4 --- /dev/null +++ b/code/ui/login_window.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'login.ui' +## +## Created by: Qt User Interface Compiler version 6.8.2 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QLineEdit, + QPushButton, QSizePolicy, QVBoxLayout, QWidget) + +class Ui_Form(QWidget): + def setupUi(self, Form): + if not Form.objectName(): + Form.setObjectName(u"Form") + Form.setWindowModality(Qt.WindowModality.WindowModal) + Form.resize(500, 270) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + Form.setMinimumSize(QSize(500, 270)) + Form.setMaximumSize(QSize(500, 270)) + font = QFont() + font.setFamilies([u"Consolas"]) + font.setPointSize(14) + Form.setFont(font) + icon = QIcon() + icon.addFile(u"../assets/media/icon.ico", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + Form.setWindowIcon(icon) + self.widget = QWidget(Form) + self.widget.setObjectName(u"widget") + self.widget.setGeometry(QRect(41, 41, 411, 211)) + self.verticalLayout = QVBoxLayout(self.widget) + self.verticalLayout.setSpacing(2) + self.verticalLayout.setObjectName(u"verticalLayout") + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_2 = QHBoxLayout() + self.horizontalLayout_2.setSpacing(2) + self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.label = QLabel(self.widget) + self.label.setObjectName(u"label") + self.label.setFont(font) + + self.horizontalLayout_2.addWidget(self.label) + + self.le_username = QLineEdit(self.widget) + self.le_username.setObjectName(u"le_username") + self.le_username.setFont(font) + + self.horizontalLayout_2.addWidget(self.le_username) + + + self.verticalLayout.addLayout(self.horizontalLayout_2) + + self.horizontalLayout_3 = QHBoxLayout() + self.horizontalLayout_3.setSpacing(2) + self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") + self.label_2 = QLabel(self.widget) + self.label_2.setObjectName(u"label_2") + self.label_2.setFont(font) + + self.horizontalLayout_3.addWidget(self.label_2) + + self.le_password = QLineEdit(self.widget) + self.le_password.setObjectName(u"le_password") + self.le_password.setFont(font) + self.le_password.setEchoMode(QLineEdit.EchoMode.Password) + + self.horizontalLayout_3.addWidget(self.le_password) + + + self.verticalLayout.addLayout(self.horizontalLayout_3) + + self.label_hint = QLabel(self.widget) + self.label_hint.setObjectName(u"label_hint") + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + sizePolicy1.setHorizontalStretch(0) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth(self.label_hint.sizePolicy().hasHeightForWidth()) + self.label_hint.setSizePolicy(sizePolicy1) + font1 = QFont() + font1.setFamilies([u"Consolas"]) + font1.setPointSize(12) + self.label_hint.setFont(font1) + + self.verticalLayout.addWidget(self.label_hint) + + self.horizontalLayout = QHBoxLayout() + self.horizontalLayout.setSpacing(2) + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.btn_login = QPushButton(self.widget) + self.btn_login.setObjectName(u"btn_login") + self.btn_login.setFont(font) + + self.horizontalLayout.addWidget(self.btn_login) + + self.btn_reset = QPushButton(self.widget) + self.btn_reset.setObjectName(u"btn_reset") + self.btn_reset.setFont(font) + + self.horizontalLayout.addWidget(self.btn_reset) + + + self.verticalLayout.addLayout(self.horizontalLayout) + + + self.retranslateUi(Form) + self.btn_login.clicked.connect(Form.user_login) + self.le_password.returnPressed.connect(Form.user_login) + self.le_username.returnPressed.connect(Form.user_login) + self.btn_reset.clicked.connect(Form.reset_password) + + QMetaObject.connectSlotsByName(Form) + # setupUi + + def retranslateUi(self, Form): + Form.setWindowTitle(QCoreApplication.translate("Form", u"\u767b\u5f55", None)) + self.label.setText(QCoreApplication.translate("Form", u"\u7528\u6237\u540d", None)) + self.label_2.setText(QCoreApplication.translate("Form", u"\u5bc6 \u7801", None)) + self.label_hint.setText("") + self.btn_login.setText(QCoreApplication.translate("Form", u"\u767b\u5f55", None)) + self.btn_reset.setText(QCoreApplication.translate("Form", u"\u91cd\u7f6e", None)) + # retranslateUi + diff --git a/code/ui/main_window.py b/code/ui/main_window.py new file mode 100644 index 0000000..9d6e659 --- /dev/null +++ b/code/ui/main_window.py @@ -0,0 +1,954 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'main.ui' +## +## Created by: Qt User Interface Compiler version 6.8.2 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QFormLayout, + QFrame, QHBoxLayout, QHeaderView, QLabel, + QLineEdit, QMainWindow, QPlainTextEdit, QPushButton, + QScrollArea, QSizePolicy, QSpacerItem, QStackedWidget, + QStatusBar, QTabWidget, QTreeWidget, QTreeWidgetItem, + QVBoxLayout, QWidget) + +class Ui_MainWindow(QMainWindow): + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + MainWindow.setEnabled(True) + MainWindow.resize(1002, 555) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) + MainWindow.setSizePolicy(sizePolicy) + MainWindow.setMinimumSize(QSize(1000, 550)) + font = QFont() + font.setFamilies([u"Consolas"]) + font.setPointSize(14) + MainWindow.setFont(font) + icon = QIcon() + icon.addFile(u"../assets/media/icon.ico", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + MainWindow.setWindowIcon(icon) + MainWindow.setStyleSheet(u"background-color: rgb(233, 233, 233);") + MainWindow.setDocumentMode(False) + self.centralwidget = QWidget(MainWindow) + self.centralwidget.setObjectName(u"centralwidget") + self.formLayout = QFormLayout(self.centralwidget) + self.formLayout.setObjectName(u"formLayout") + self.vl_1_left = QVBoxLayout() + self.vl_1_left.setObjectName(u"vl_1_left") + self.label = QLabel(self.centralwidget) + self.label.setObjectName(u"label") + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + sizePolicy1.setHorizontalStretch(0) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy1) + self.label.setMinimumSize(QSize(200, 100)) + self.label.setMaximumSize(QSize(240, 120)) + font1 = QFont() + font1.setFamilies([u"Segoe Print"]) + font1.setPointSize(24) + font1.setBold(True) + self.label.setFont(font1) + self.label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.label.setMargin(0) + + self.vl_1_left.addWidget(self.label, 0, Qt.AlignmentFlag.AlignHCenter|Qt.AlignmentFlag.AlignVCenter) + + self.btn_start = QPushButton(self.centralwidget) + self.btn_start.setObjectName(u"btn_start") + sizePolicy1.setHeightForWidth(self.btn_start.sizePolicy().hasHeightForWidth()) + self.btn_start.setSizePolicy(sizePolicy1) + self.btn_start.setMinimumSize(QSize(150, 36)) + self.btn_start.setMaximumSize(QSize(180, 45)) + font2 = QFont() + font2.setFamilies([u"Consolas"]) + font2.setPointSize(14) + font2.setBold(True) + self.btn_start.setFont(font2) + self.btn_start.setFlat(False) + + self.vl_1_left.addWidget(self.btn_start, 0, Qt.AlignmentFlag.AlignHCenter|Qt.AlignmentFlag.AlignVCenter) + + self.btn_stop = QPushButton(self.centralwidget) + self.btn_stop.setObjectName(u"btn_stop") + sizePolicy1.setHeightForWidth(self.btn_stop.sizePolicy().hasHeightForWidth()) + self.btn_stop.setSizePolicy(sizePolicy1) + self.btn_stop.setMinimumSize(QSize(150, 36)) + self.btn_stop.setMaximumSize(QSize(180, 45)) + self.btn_stop.setFont(font2) + self.btn_stop.setFlat(False) + + self.vl_1_left.addWidget(self.btn_stop, 0, Qt.AlignmentFlag.AlignHCenter|Qt.AlignmentFlag.AlignVCenter) + + self.btn_reset = QPushButton(self.centralwidget) + self.btn_reset.setObjectName(u"btn_reset") + sizePolicy1.setHeightForWidth(self.btn_reset.sizePolicy().hasHeightForWidth()) + self.btn_reset.setSizePolicy(sizePolicy1) + self.btn_reset.setMinimumSize(QSize(150, 36)) + self.btn_reset.setMaximumSize(QSize(180, 45)) + self.btn_reset.setFont(font2) + self.btn_reset.setFlat(False) + + self.vl_1_left.addWidget(self.btn_reset, 0, Qt.AlignmentFlag.AlignHCenter|Qt.AlignmentFlag.AlignVCenter) + + self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + + self.vl_1_left.addItem(self.verticalSpacer) + + self.vl_1_left.setStretch(0, 4) + self.vl_1_left.setStretch(1, 1) + self.vl_1_left.setStretch(2, 1) + self.vl_1_left.setStretch(3, 1) + self.vl_1_left.setStretch(4, 10) + + self.formLayout.setLayout(0, QFormLayout.LabelRole, self.vl_1_left) + + self.vl_1_right = QVBoxLayout() + self.vl_1_right.setObjectName(u"vl_1_right") + self.tw_funcs = QTabWidget(self.centralwidget) + self.tw_funcs.setObjectName(u"tw_funcs") + sizePolicy.setHeightForWidth(self.tw_funcs.sizePolicy().hasHeightForWidth()) + self.tw_funcs.setSizePolicy(sizePolicy) + self.tw_funcs.setMinimumSize(QSize(0, 0)) + font3 = QFont() + font3.setPointSize(14) + font3.setBold(True) + self.tw_funcs.setFont(font3) + self.tw_funcs.setElideMode(Qt.TextElideMode.ElideNone) + self.tw_funcs.setUsesScrollButtons(True) + self.tw_funcs.setDocumentMode(True) + self.tw_funcs.setTabsClosable(False) + self.tw_funcs.setTabBarAutoHide(False) + self.tab_data = QWidget() + self.tab_data.setObjectName(u"tab_data") + self.verticalLayout = QVBoxLayout(self.tab_data) + self.verticalLayout.setObjectName(u"verticalLayout") + self.horizontalLayout = QHBoxLayout() + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.cb_data_func = QComboBox(self.tab_data) + self.cb_data_func.addItem("") + self.cb_data_func.addItem("") + self.cb_data_func.addItem("") + self.cb_data_func.addItem("") + self.cb_data_func.setObjectName(u"cb_data_func") + self.cb_data_func.setMinimumSize(QSize(100, 0)) + font4 = QFont() + font4.setFamilies([u"Consolas"]) + font4.setPointSize(12) + self.cb_data_func.setFont(font4) + + self.horizontalLayout.addWidget(self.cb_data_func) + + self.cb_data_current = QComboBox(self.tab_data) + self.cb_data_current.addItem("") + self.cb_data_current.addItem("") + self.cb_data_current.addItem("") + self.cb_data_current.setObjectName(u"cb_data_current") + self.cb_data_current.setMinimumSize(QSize(100, 0)) + self.cb_data_current.setFont(font4) + + self.horizontalLayout.addWidget(self.cb_data_current) + + self.label_4 = QLabel(self.tab_data) + self.label_4.setObjectName(u"label_4") + self.label_4.setFont(font4) + self.label_4.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter) + + self.horizontalLayout.addWidget(self.label_4) + + self.le_data_path = QLineEdit(self.tab_data) + self.le_data_path.setObjectName(u"le_data_path") + self.le_data_path.setFont(font4) + self.le_data_path.setAlignment(Qt.AlignmentFlag.AlignLeading|Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter) + + self.horizontalLayout.addWidget(self.le_data_path) + + self.btn_data_open = QPushButton(self.tab_data) + self.btn_data_open.setObjectName(u"btn_data_open") + self.btn_data_open.setMaximumSize(QSize(30, 16777215)) + self.btn_data_open.setFont(font4) + + self.horizontalLayout.addWidget(self.btn_data_open) + + self.horizontalLayout.setStretch(0, 1) + self.horizontalLayout.setStretch(1, 1) + self.horizontalLayout.setStretch(2, 1) + self.horizontalLayout.setStretch(3, 10) + self.horizontalLayout.setStretch(4, 1) + + self.verticalLayout.addLayout(self.horizontalLayout) + + self.verticalSpacer_2 = QSpacerItem(20, 161, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + + self.verticalLayout.addItem(self.verticalSpacer_2) + + self.tw_funcs.addTab(self.tab_data, "") + self.tab_unit = QWidget() + self.tab_unit.setObjectName(u"tab_unit") + self.verticalLayout_2 = QVBoxLayout(self.tab_unit) + self.verticalLayout_2.setObjectName(u"verticalLayout_2") + self.hl_2_unit1 = QHBoxLayout() + self.hl_2_unit1.setObjectName(u"hl_2_unit1") + self.cb_unit_func = QComboBox(self.tab_unit) + self.cb_unit_func.addItem("") + self.cb_unit_func.addItem("") + self.cb_unit_func.setObjectName(u"cb_unit_func") + self.cb_unit_func.setMinimumSize(QSize(100, 0)) + self.cb_unit_func.setFont(font4) + + self.hl_2_unit1.addWidget(self.cb_unit_func) + + self.cb_unit_tool = QComboBox(self.tab_unit) + self.cb_unit_tool.addItem("") + self.cb_unit_tool.addItem("") + self.cb_unit_tool.addItem("") + self.cb_unit_tool.addItem("") + self.cb_unit_tool.setObjectName(u"cb_unit_tool") + self.cb_unit_tool.setMinimumSize(QSize(100, 0)) + self.cb_unit_tool.setFont(font4) + + self.hl_2_unit1.addWidget(self.cb_unit_tool) + + self.label_6 = QLabel(self.tab_unit) + self.label_6.setObjectName(u"label_6") + sizePolicy1.setHeightForWidth(self.label_6.sizePolicy().hasHeightForWidth()) + self.label_6.setSizePolicy(sizePolicy1) + self.label_6.setFont(font4) + self.label_6.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter) + + self.hl_2_unit1.addWidget(self.label_6) + + self.le_unit_path = QLineEdit(self.tab_unit) + self.le_unit_path.setObjectName(u"le_unit_path") + self.le_unit_path.setFont(font4) + + self.hl_2_unit1.addWidget(self.le_unit_path) + + self.btn_unit_open = QPushButton(self.tab_unit) + self.btn_unit_open.setObjectName(u"btn_unit_open") + self.btn_unit_open.setMaximumSize(QSize(30, 16777215)) + self.btn_unit_open.setFont(font4) + + self.hl_2_unit1.addWidget(self.btn_unit_open) + + self.hl_2_unit1.setStretch(0, 1) + self.hl_2_unit1.setStretch(1, 1) + self.hl_2_unit1.setStretch(2, 1) + self.hl_2_unit1.setStretch(3, 10) + self.hl_2_unit1.setStretch(4, 1) + + self.verticalLayout_2.addLayout(self.hl_2_unit1) + + self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + + self.verticalLayout_2.addItem(self.verticalSpacer_3) + + self.tw_funcs.addTab(self.tab_unit, "") + self.tab_durable = QWidget() + self.tab_durable.setObjectName(u"tab_durable") + self.horizontalLayout_11 = QHBoxLayout(self.tab_durable) + self.horizontalLayout_11.setObjectName(u"horizontalLayout_11") + self.horizontalLayout_10 = QHBoxLayout() + self.horizontalLayout_10.setObjectName(u"horizontalLayout_10") + self.verticalLayout_9 = QVBoxLayout() + self.verticalLayout_9.setObjectName(u"verticalLayout_9") + self.frame = QFrame(self.tab_durable) + self.frame.setObjectName(u"frame") + self.frame.setMinimumSize(QSize(200, 0)) + self.frame.setMaximumSize(QSize(300, 16777215)) + self.frame.setFrameShape(QFrame.Shape.StyledPanel) + self.frame.setFrameShadow(QFrame.Shadow.Raised) + self.verticalLayout_8 = QVBoxLayout(self.frame) + self.verticalLayout_8.setObjectName(u"verticalLayout_8") + self.verticalLayout_7 = QVBoxLayout() + self.verticalLayout_7.setObjectName(u"verticalLayout_7") + self.label_11 = QLabel(self.frame) + self.label_11.setObjectName(u"label_11") + sizePolicy1.setHeightForWidth(self.label_11.sizePolicy().hasHeightForWidth()) + self.label_11.setSizePolicy(sizePolicy1) + self.label_11.setFont(font2) + self.label_11.setAlignment(Qt.AlignmentFlag.AlignLeading|Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter) + + self.verticalLayout_7.addWidget(self.label_11) + + self.scrollArea = QScrollArea(self.frame) + self.scrollArea.setObjectName(u"scrollArea") + self.scrollArea.setWidgetResizable(True) + self.scrollAreaWidgetContents = QWidget() + self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents") + self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 211, 78)) + self.horizontalLayout_9 = QHBoxLayout(self.scrollAreaWidgetContents) + self.horizontalLayout_9.setObjectName(u"horizontalLayout_9") + self.verticalLayout_5 = QVBoxLayout() + self.verticalLayout_5.setObjectName(u"verticalLayout_5") + self.cb_1 = QCheckBox(self.scrollAreaWidgetContents) + self.cb_1.setObjectName(u"cb_1") + self.cb_1.setFont(font4) + + self.verticalLayout_5.addWidget(self.cb_1) + + self.cb_2 = QCheckBox(self.scrollAreaWidgetContents) + self.cb_2.setObjectName(u"cb_2") + self.cb_2.setFont(font4) + + self.verticalLayout_5.addWidget(self.cb_2) + + self.verticalSpacer_5 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + + self.verticalLayout_5.addItem(self.verticalSpacer_5) + + + self.horizontalLayout_9.addLayout(self.verticalLayout_5) + + self.scrollArea.setWidget(self.scrollAreaWidgetContents) + + self.verticalLayout_7.addWidget(self.scrollArea) + + + self.verticalLayout_8.addLayout(self.verticalLayout_7) + + + self.verticalLayout_9.addWidget(self.frame) + + + self.horizontalLayout_10.addLayout(self.verticalLayout_9) + + self.verticalLayout_6 = QVBoxLayout() + self.verticalLayout_6.setObjectName(u"verticalLayout_6") + self.horizontalLayout_6 = QHBoxLayout() + self.horizontalLayout_6.setObjectName(u"horizontalLayout_6") + self.label_8 = QLabel(self.tab_durable) + self.label_8.setObjectName(u"label_8") + sizePolicy1.setHeightForWidth(self.label_8.sizePolicy().hasHeightForWidth()) + self.label_8.setSizePolicy(sizePolicy1) + self.label_8.setFont(font4) + self.label_8.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.horizontalLayout_6.addWidget(self.label_8) + + self.le_durable_path = QLineEdit(self.tab_durable) + self.le_durable_path.setObjectName(u"le_durable_path") + self.le_durable_path.setFont(font4) + + self.horizontalLayout_6.addWidget(self.le_durable_path) + + self.btn_durable_open = QPushButton(self.tab_durable) + self.btn_durable_open.setObjectName(u"btn_durable_open") + self.btn_durable_open.setMaximumSize(QSize(30, 16777215)) + self.btn_durable_open.setFont(font4) + + self.horizontalLayout_6.addWidget(self.btn_durable_open) + + + self.verticalLayout_6.addLayout(self.horizontalLayout_6) + + self.horizontalLayout_7 = QHBoxLayout() + self.horizontalLayout_7.setObjectName(u"horizontalLayout_7") + self.label_9 = QLabel(self.tab_durable) + self.label_9.setObjectName(u"label_9") + sizePolicy1.setHeightForWidth(self.label_9.sizePolicy().hasHeightForWidth()) + self.label_9.setSizePolicy(sizePolicy1) + self.label_9.setFont(font4) + self.label_9.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.horizontalLayout_7.addWidget(self.label_9) + + self.le_durable_interval = QLineEdit(self.tab_durable) + self.le_durable_interval.setObjectName(u"le_durable_interval") + self.le_durable_interval.setFont(font4) + self.le_durable_interval.setInputMethodHints(Qt.InputMethodHint.ImhNone) + + self.horizontalLayout_7.addWidget(self.le_durable_interval) + + self.label_10 = QLabel(self.tab_durable) + self.label_10.setObjectName(u"label_10") + sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred) + sizePolicy2.setHorizontalStretch(0) + sizePolicy2.setVerticalStretch(0) + sizePolicy2.setHeightForWidth(self.label_10.sizePolicy().hasHeightForWidth()) + self.label_10.setSizePolicy(sizePolicy2) + self.label_10.setMinimumSize(QSize(30, 0)) + + self.horizontalLayout_7.addWidget(self.label_10) + + + self.verticalLayout_6.addLayout(self.horizontalLayout_7) + + self.horizontalLayout_8 = QHBoxLayout() + self.horizontalLayout_8.setObjectName(u"horizontalLayout_8") + self.cb_durable_total = QCheckBox(self.tab_durable) + self.cb_durable_total.setObjectName(u"cb_durable_total") + font5 = QFont() + font5.setFamilies([u"Consolas"]) + font5.setPointSize(12) + font5.setBold(True) + self.cb_durable_total.setFont(font5) + + self.horizontalLayout_8.addWidget(self.cb_durable_total) + + self.btn_draw = QPushButton(self.tab_durable) + self.btn_draw.setObjectName(u"btn_draw") + self.btn_draw.setFont(font5) + + self.horizontalLayout_8.addWidget(self.btn_draw) + + self.label_3 = QLabel(self.tab_durable) + self.label_3.setObjectName(u"label_3") + + self.horizontalLayout_8.addWidget(self.label_3) + + self.horizontalLayout_8.setStretch(0, 2) + self.horizontalLayout_8.setStretch(2, 8) + + self.verticalLayout_6.addLayout(self.horizontalLayout_8) + + self.verticalSpacer_4 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + + self.verticalLayout_6.addItem(self.verticalSpacer_4) + + + self.horizontalLayout_10.addLayout(self.verticalLayout_6) + + self.horizontalLayout_10.setStretch(0, 1) + self.horizontalLayout_10.setStretch(1, 2) + + self.horizontalLayout_11.addLayout(self.horizontalLayout_10) + + self.tw_funcs.addTab(self.tab_durable, "") + self.tab_network = QWidget() + self.tab_network.setObjectName(u"tab_network") + self.horizontalLayout_13 = QHBoxLayout(self.tab_network) + self.horizontalLayout_13.setObjectName(u"horizontalLayout_13") + self.horizontalLayout_12 = QHBoxLayout() + self.horizontalLayout_12.setObjectName(u"horizontalLayout_12") + self.sw_network = QStackedWidget(self.tab_network) + self.sw_network.setObjectName(u"sw_network") + self.page = QWidget() + self.page.setObjectName(u"page") + self.horizontalLayout_14 = QHBoxLayout(self.page) + self.horizontalLayout_14.setObjectName(u"horizontalLayout_14") + self.verticalLayout_10 = QVBoxLayout() + self.verticalLayout_10.setObjectName(u"verticalLayout_10") + self.horizontalLayout_3 = QHBoxLayout() + self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") + self.label_2 = QLabel(self.page) + self.label_2.setObjectName(u"label_2") + sizePolicy1.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) + self.label_2.setSizePolicy(sizePolicy1) + self.label_2.setMinimumSize(QSize(70, 0)) + self.label_2.setFont(font5) + self.label_2.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.horizontalLayout_3.addWidget(self.label_2) + + self.le_hmi_ip = QLineEdit(self.page) + self.le_hmi_ip.setObjectName(u"le_hmi_ip") + self.le_hmi_ip.setMinimumSize(QSize(150, 0)) + self.le_hmi_ip.setFont(font4) + + self.horizontalLayout_3.addWidget(self.le_hmi_ip) + + self.btn_hmi_conn = QPushButton(self.page) + self.btn_hmi_conn.setObjectName(u"btn_hmi_conn") + self.btn_hmi_conn.setFont(font5) + + self.horizontalLayout_3.addWidget(self.btn_hmi_conn) + + self.label_5 = QLabel(self.page) + self.label_5.setObjectName(u"label_5") + self.label_5.setFont(font4) + + self.horizontalLayout_3.addWidget(self.label_5) + + self.cb_hmi_cmd = QComboBox(self.page) + self.cb_hmi_cmd.addItem("") + self.cb_hmi_cmd.addItem("") + self.cb_hmi_cmd.addItem("") + self.cb_hmi_cmd.setObjectName(u"cb_hmi_cmd") + self.cb_hmi_cmd.setMinimumSize(QSize(240, 0)) + self.cb_hmi_cmd.setFont(font4) + + self.horizontalLayout_3.addWidget(self.cb_hmi_cmd) + + self.btn_hmi_send = QPushButton(self.page) + self.btn_hmi_send.setObjectName(u"btn_hmi_send") + self.btn_hmi_send.setFont(font5) + + self.horizontalLayout_3.addWidget(self.btn_hmi_send) + + self.horizontalLayout_3.setStretch(0, 1) + self.horizontalLayout_3.setStretch(1, 4) + self.horizontalLayout_3.setStretch(2, 1) + self.horizontalLayout_3.setStretch(3, 4) + self.horizontalLayout_3.setStretch(4, 8) + self.horizontalLayout_3.setStretch(5, 1) + + self.verticalLayout_10.addLayout(self.horizontalLayout_3) + + self.horizontalLayout_5 = QHBoxLayout() + self.horizontalLayout_5.setObjectName(u"horizontalLayout_5") + self.pte_hmi_send = QPlainTextEdit(self.page) + self.pte_hmi_send.setObjectName(u"pte_hmi_send") + + self.horizontalLayout_5.addWidget(self.pte_hmi_send) + + self.pte_him_recv = QPlainTextEdit(self.page) + self.pte_him_recv.setObjectName(u"pte_him_recv") + + self.horizontalLayout_5.addWidget(self.pte_him_recv) + + + self.verticalLayout_10.addLayout(self.horizontalLayout_5) + + + self.horizontalLayout_14.addLayout(self.verticalLayout_10) + + self.sw_network.addWidget(self.page) + self.page_2 = QWidget() + self.page_2.setObjectName(u"page_2") + self.horizontalLayout_17 = QHBoxLayout(self.page_2) + self.horizontalLayout_17.setObjectName(u"horizontalLayout_17") + self.verticalLayout_11 = QVBoxLayout() + self.verticalLayout_11.setObjectName(u"verticalLayout_11") + self.horizontalLayout_15 = QHBoxLayout() + self.horizontalLayout_15.setObjectName(u"horizontalLayout_15") + self.label_7 = QLabel(self.page_2) + self.label_7.setObjectName(u"label_7") + sizePolicy1.setHeightForWidth(self.label_7.sizePolicy().hasHeightForWidth()) + self.label_7.setSizePolicy(sizePolicy1) + self.label_7.setMinimumSize(QSize(70, 0)) + self.label_7.setFont(font5) + self.label_7.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.horizontalLayout_15.addWidget(self.label_7) + + self.le_md_port = QLineEdit(self.page_2) + self.le_md_port.setObjectName(u"le_md_port") + self.le_md_port.setMinimumSize(QSize(150, 0)) + self.le_md_port.setFont(font4) + + self.horizontalLayout_15.addWidget(self.le_md_port) + + self.btn_md_conn = QPushButton(self.page_2) + self.btn_md_conn.setObjectName(u"btn_md_conn") + self.btn_md_conn.setFont(font5) + + self.horizontalLayout_15.addWidget(self.btn_md_conn) + + self.label_12 = QLabel(self.page_2) + self.label_12.setObjectName(u"label_12") + self.label_12.setFont(font4) + + self.horizontalLayout_15.addWidget(self.label_12) + + self.cb_md_cmd = QComboBox(self.page_2) + self.cb_md_cmd.addItem("") + self.cb_md_cmd.addItem("") + self.cb_md_cmd.setObjectName(u"cb_md_cmd") + self.cb_md_cmd.setMinimumSize(QSize(240, 0)) + self.cb_md_cmd.setFont(font4) + + self.horizontalLayout_15.addWidget(self.cb_md_cmd) + + self.btn_md_send = QPushButton(self.page_2) + self.btn_md_send.setObjectName(u"btn_md_send") + self.btn_md_send.setFont(font5) + + self.horizontalLayout_15.addWidget(self.btn_md_send) + + self.horizontalLayout_15.setStretch(0, 1) + self.horizontalLayout_15.setStretch(1, 4) + self.horizontalLayout_15.setStretch(2, 1) + self.horizontalLayout_15.setStretch(3, 4) + self.horizontalLayout_15.setStretch(4, 8) + self.horizontalLayout_15.setStretch(5, 1) + + self.verticalLayout_11.addLayout(self.horizontalLayout_15) + + self.horizontalLayout_16 = QHBoxLayout() + self.horizontalLayout_16.setObjectName(u"horizontalLayout_16") + self.pte_md_send = QPlainTextEdit(self.page_2) + self.pte_md_send.setObjectName(u"pte_md_send") + + self.horizontalLayout_16.addWidget(self.pte_md_send) + + self.pte_md_recv = QPlainTextEdit(self.page_2) + self.pte_md_recv.setObjectName(u"pte_md_recv") + + self.horizontalLayout_16.addWidget(self.pte_md_recv) + + + self.verticalLayout_11.addLayout(self.horizontalLayout_16) + + + self.horizontalLayout_17.addLayout(self.verticalLayout_11) + + self.sw_network.addWidget(self.page_2) + self.page_3 = QWidget() + self.page_3.setObjectName(u"page_3") + self.horizontalLayout_26 = QHBoxLayout(self.page_3) + self.horizontalLayout_26.setObjectName(u"horizontalLayout_26") + self.verticalLayout_14 = QVBoxLayout() + self.verticalLayout_14.setObjectName(u"verticalLayout_14") + self.horizontalLayout_24 = QHBoxLayout() + self.horizontalLayout_24.setObjectName(u"horizontalLayout_24") + self.label_17 = QLabel(self.page_3) + self.label_17.setObjectName(u"label_17") + sizePolicy1.setHeightForWidth(self.label_17.sizePolicy().hasHeightForWidth()) + self.label_17.setSizePolicy(sizePolicy1) + self.label_17.setMinimumSize(QSize(70, 0)) + self.label_17.setFont(font5) + self.label_17.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.horizontalLayout_24.addWidget(self.label_17) + + self.le_ec_port = QLineEdit(self.page_3) + self.le_ec_port.setObjectName(u"le_ec_port") + self.le_ec_port.setMinimumSize(QSize(150, 0)) + self.le_ec_port.setFont(font4) + + self.horizontalLayout_24.addWidget(self.le_ec_port) + + self.btn_ec_conn = QPushButton(self.page_3) + self.btn_ec_conn.setObjectName(u"btn_ec_conn") + self.btn_ec_conn.setFont(font5) + + self.horizontalLayout_24.addWidget(self.btn_ec_conn) + + self.label_18 = QLabel(self.page_3) + self.label_18.setObjectName(u"label_18") + self.label_18.setFont(font4) + + self.horizontalLayout_24.addWidget(self.label_18) + + self.cb_ec_cmd = QComboBox(self.page_3) + self.cb_ec_cmd.addItem("") + self.cb_ec_cmd.addItem("") + self.cb_ec_cmd.setObjectName(u"cb_ec_cmd") + self.cb_ec_cmd.setMinimumSize(QSize(240, 0)) + self.cb_ec_cmd.setFont(font4) + + self.horizontalLayout_24.addWidget(self.cb_ec_cmd) + + self.btn_ec_send = QPushButton(self.page_3) + self.btn_ec_send.setObjectName(u"btn_ec_send") + self.btn_ec_send.setFont(font5) + + self.horizontalLayout_24.addWidget(self.btn_ec_send) + + self.horizontalLayout_24.setStretch(0, 1) + self.horizontalLayout_24.setStretch(1, 4) + self.horizontalLayout_24.setStretch(2, 1) + self.horizontalLayout_24.setStretch(3, 4) + self.horizontalLayout_24.setStretch(4, 8) + self.horizontalLayout_24.setStretch(5, 1) + + self.verticalLayout_14.addLayout(self.horizontalLayout_24) + + self.horizontalLayout_25 = QHBoxLayout() + self.horizontalLayout_25.setObjectName(u"horizontalLayout_25") + self.pte_ec_send = QPlainTextEdit(self.page_3) + self.pte_ec_send.setObjectName(u"pte_ec_send") + + self.horizontalLayout_25.addWidget(self.pte_ec_send) + + self.pte_ec_recv = QPlainTextEdit(self.page_3) + self.pte_ec_recv.setObjectName(u"pte_ec_recv") + + self.horizontalLayout_25.addWidget(self.pte_ec_recv) + + + self.verticalLayout_14.addLayout(self.horizontalLayout_25) + + + self.horizontalLayout_26.addLayout(self.verticalLayout_14) + + self.sw_network.addWidget(self.page_3) + + self.horizontalLayout_12.addWidget(self.sw_network) + + self.verticalLayout_4 = QVBoxLayout() + self.verticalLayout_4.setObjectName(u"verticalLayout_4") + self.pushButton = QPushButton(self.tab_network) + self.pushButton.setObjectName(u"pushButton") + self.pushButton.setFont(font5) + + self.verticalLayout_4.addWidget(self.pushButton) + + self.pushButton_2 = QPushButton(self.tab_network) + self.pushButton_2.setObjectName(u"pushButton_2") + self.pushButton_2.setFont(font5) + + self.verticalLayout_4.addWidget(self.pushButton_2) + + self.pushButton_3 = QPushButton(self.tab_network) + self.pushButton_3.setObjectName(u"pushButton_3") + self.pushButton_3.setFont(font5) + + self.verticalLayout_4.addWidget(self.pushButton_3) + + + self.horizontalLayout_12.addLayout(self.verticalLayout_4) + + self.horizontalLayout_12.setStretch(0, 11) + self.horizontalLayout_12.setStretch(1, 1) + + self.horizontalLayout_13.addLayout(self.horizontalLayout_12) + + self.tw_funcs.addTab(self.tab_network, "") + + self.vl_1_right.addWidget(self.tw_funcs) + + self.tw_docs = QTabWidget(self.centralwidget) + self.tw_docs.setObjectName(u"tw_docs") + self.tw_docs.setFont(font3) + self.tw_docs.setElideMode(Qt.TextElideMode.ElideNone) + self.tw_docs.setDocumentMode(True) + self.tab_output = QWidget() + self.tab_output.setObjectName(u"tab_output") + self.horizontalLayout_4 = QHBoxLayout(self.tab_output) + self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") + self.pte_output = QPlainTextEdit(self.tab_output) + self.pte_output.setObjectName(u"pte_output") + self.pte_output.setFont(font4) + self.pte_output.setLineWrapMode(QPlainTextEdit.LineWrapMode.WidgetWidth) + self.pte_output.setReadOnly(True) + + self.horizontalLayout_4.addWidget(self.pte_output) + + self.tw_docs.addTab(self.tab_output, "") + self.tab_log = QWidget() + self.tab_log.setObjectName(u"tab_log") + self.verticalLayout_3 = QVBoxLayout(self.tab_log) + self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.treew_log = QTreeWidget(self.tab_log) + self.treew_log.setObjectName(u"treew_log") + self.treew_log.setFont(font4) + self.treew_log.header().setVisible(True) + + self.verticalLayout_3.addWidget(self.treew_log) + + self.horizontalLayout_2 = QHBoxLayout() + self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.label_page = QLabel(self.tab_log) + self.label_page.setObjectName(u"label_page") + self.label_page.setMinimumSize(QSize(100, 0)) + font6 = QFont() + font6.setFamilies([u"Consolas"]) + font6.setPointSize(10) + font6.setBold(True) + self.label_page.setFont(font6) + self.label_page.setStyleSheet(u"background-color: rgb(222, 222, 222);") + self.label_page.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.horizontalLayout_2.addWidget(self.label_page) + + self.btn_docs_previous = QPushButton(self.tab_log) + self.btn_docs_previous.setObjectName(u"btn_docs_previous") + self.btn_docs_previous.setFont(font4) + + self.horizontalLayout_2.addWidget(self.btn_docs_previous) + + self.btn_docs_realtime = QPushButton(self.tab_log) + self.btn_docs_realtime.setObjectName(u"btn_docs_realtime") + self.btn_docs_realtime.setFont(font4) + + self.horizontalLayout_2.addWidget(self.btn_docs_realtime) + + self.btn_docs_next = QPushButton(self.tab_log) + self.btn_docs_next.setObjectName(u"btn_docs_next") + self.btn_docs_next.setFont(font4) + + self.horizontalLayout_2.addWidget(self.btn_docs_next) + + self.btn_docs_load = QPushButton(self.tab_log) + self.btn_docs_load.setObjectName(u"btn_docs_load") + self.btn_docs_load.setFont(font4) + + self.horizontalLayout_2.addWidget(self.btn_docs_load) + + self.btn_docs_search = QPushButton(self.tab_log) + self.btn_docs_search.setObjectName(u"btn_docs_search") + self.btn_docs_search.setFont(font4) + + self.horizontalLayout_2.addWidget(self.btn_docs_search) + + self.le_docs_search = QLineEdit(self.tab_log) + self.le_docs_search.setObjectName(u"le_docs_search") + self.le_docs_search.setFont(font4) + + self.horizontalLayout_2.addWidget(self.le_docs_search) + + self.horizontalLayout_2.setStretch(0, 1) + self.horizontalLayout_2.setStretch(1, 1) + self.horizontalLayout_2.setStretch(2, 1) + self.horizontalLayout_2.setStretch(3, 1) + self.horizontalLayout_2.setStretch(4, 1) + self.horizontalLayout_2.setStretch(5, 1) + self.horizontalLayout_2.setStretch(6, 10) + + self.verticalLayout_3.addLayout(self.horizontalLayout_2) + + self.tw_docs.addTab(self.tab_log, "") + + self.vl_1_right.addWidget(self.tw_docs) + + self.vl_1_right.setStretch(0, 1) + self.vl_1_right.setStretch(1, 3) + + self.formLayout.setLayout(0, QFormLayout.FieldRole, self.vl_1_right) + + MainWindow.setCentralWidget(self.centralwidget) + self.statusbar = QStatusBar(MainWindow) + self.statusbar.setObjectName(u"statusbar") + sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + sizePolicy3.setHorizontalStretch(0) + sizePolicy3.setVerticalStretch(0) + sizePolicy3.setHeightForWidth(self.statusbar.sizePolicy().hasHeightForWidth()) + self.statusbar.setSizePolicy(sizePolicy3) + self.statusbar.setMinimumSize(QSize(0, 27)) + self.statusbar.setStyleSheet(u"background-color: rgb(200, 200, 200);") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + self.btn_start.clicked.connect(MainWindow.prog_start) + self.btn_stop.clicked.connect(MainWindow.prog_stop) + self.btn_reset.clicked.connect(MainWindow.prog_reset) + self.btn_durable_open.clicked.connect(MainWindow.file_browser) + self.btn_draw.clicked.connect(MainWindow.curve_draw) + self.cb_durable_total.checkStateChanged.connect(MainWindow.durable_cb_change) + self.btn_unit_open.clicked.connect(MainWindow.file_browser) + self.btn_data_open.clicked.connect(MainWindow.file_browser) + self.btn_docs_previous.clicked.connect(MainWindow.pre_page) + self.btn_docs_realtime.clicked.connect(MainWindow.realtime_page) + self.btn_docs_next.clicked.connect(MainWindow.next_page) + self.btn_docs_load.clicked.connect(MainWindow.load_sql) + self.btn_docs_search.clicked.connect(MainWindow.search_keyword) + self.le_docs_search.returnPressed.connect(MainWindow.search_keyword) + self.cb_hmi_cmd.currentTextChanged.connect(MainWindow.hmi_cb_change) + self.btn_hmi_send.clicked.connect(MainWindow.hmi_send) + self.pushButton.clicked.connect(MainWindow.hmi_page) + self.pushButton_2.clicked.connect(MainWindow.md_page) + self.pushButton_3.clicked.connect(MainWindow.ec_page) + self.cb_md_cmd.currentTextChanged.connect(MainWindow.md_cb_change) + self.btn_md_send.clicked.connect(MainWindow.md_send) + self.btn_ec_send.clicked.connect(MainWindow.ec_send) + self.btn_hmi_conn.clicked.connect(MainWindow.hmi_conn) + self.btn_md_conn.clicked.connect(MainWindow.md_conn) + self.btn_ec_conn.clicked.connect(MainWindow.ec_conn) + self.le_durable_interval.editingFinished.connect(MainWindow.check_interval) + self.cb_ec_cmd.currentTextChanged.connect(MainWindow.ec_cb_change) + self.le_hmi_ip.returnPressed.connect(MainWindow.hmi_conn) + + self.tw_funcs.setCurrentIndex(0) + self.sw_network.setCurrentIndex(0) + self.tw_docs.setCurrentIndex(1) + + + QMetaObject.connectSlotsByName(MainWindow) + # setupUi + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Rokae AIO", None)) + self.label.setText(QCoreApplication.translate("MainWindow", u"Rokae AIO", None)) + self.btn_start.setText(QCoreApplication.translate("MainWindow", u"\u5f00\u59cb\u6267\u884c", None)) + self.btn_stop.setText(QCoreApplication.translate("MainWindow", u"\u505c\u6b62\u6267\u884c", None)) + self.btn_reset.setText(QCoreApplication.translate("MainWindow", u"\u72b6\u6001\u91cd\u7f6e", None)) + self.cb_data_func.setItemText(0, QCoreApplication.translate("MainWindow", u"\u5236\u52a8", None)) + self.cb_data_func.setItemText(1, QCoreApplication.translate("MainWindow", u"\u8f6c\u77e9", None)) + self.cb_data_func.setItemText(2, QCoreApplication.translate("MainWindow", u"\u6fc0\u5149", None)) + self.cb_data_func.setItemText(3, QCoreApplication.translate("MainWindow", u"\u7cbe\u5ea6", None)) + + self.cb_data_current.setItemText(0, QCoreApplication.translate("MainWindow", u"\u5468\u671f", None)) + self.cb_data_current.setItemText(1, QCoreApplication.translate("MainWindow", u"\u6700\u5927\u503c", None)) + self.cb_data_current.setItemText(2, QCoreApplication.translate("MainWindow", u"\u5e73\u5747\u503c", None)) + + self.label_4.setText(QCoreApplication.translate("MainWindow", u"\u8def\u5f84", None)) + self.btn_data_open.setText(QCoreApplication.translate("MainWindow", u"...", None)) + self.tw_funcs.setTabText(self.tw_funcs.indexOf(self.tab_data), QCoreApplication.translate("MainWindow", u"\u6570\u636e\u5904\u7406", None)) + self.cb_unit_func.setItemText(0, QCoreApplication.translate("MainWindow", u"\u5236\u52a8", None)) + self.cb_unit_func.setItemText(1, QCoreApplication.translate("MainWindow", u"\u8f6c\u77e9", None)) + + self.cb_unit_tool.setItemText(0, QCoreApplication.translate("MainWindow", u"tool33", None)) + self.cb_unit_tool.setItemText(1, QCoreApplication.translate("MainWindow", u"tool66", None)) + self.cb_unit_tool.setItemText(2, QCoreApplication.translate("MainWindow", u"tool100", None)) + self.cb_unit_tool.setItemText(3, QCoreApplication.translate("MainWindow", u"inertia", None)) + + self.label_6.setText(QCoreApplication.translate("MainWindow", u"\u8def\u5f84", None)) + self.btn_unit_open.setText(QCoreApplication.translate("MainWindow", u"...", None)) + self.tw_funcs.setTabText(self.tw_funcs.indexOf(self.tab_unit), QCoreApplication.translate("MainWindow", u"\u6574\u673a\u6d4b\u8bd5", None)) + self.label_11.setText(QCoreApplication.translate("MainWindow", u"\u9009\u62e9\u6307\u6807", None)) + self.cb_1.setText(QCoreApplication.translate("MainWindow", u"\u5468\u671f\u5185\u5e73\u5747\u8f6c\u77e9", None)) + self.cb_2.setText(QCoreApplication.translate("MainWindow", u"\u5468\u671f\u5185\u5e73\u5747\u8f6c\u77e9", None)) + self.label_8.setText(QCoreApplication.translate("MainWindow", u"\u8def\u5f84", None)) + self.btn_durable_open.setText(QCoreApplication.translate("MainWindow", u"...", None)) + self.label_9.setText(QCoreApplication.translate("MainWindow", u"\u95f4\u9694", None)) +#if QT_CONFIG(whatsthis) + self.le_durable_interval.setWhatsThis("") +#endif // QT_CONFIG(whatsthis) + self.le_durable_interval.setPlaceholderText(QCoreApplication.translate("MainWindow", u"\u6bcf\u6b21\u6570\u636e\u91c7\u96c6\u7684\u65f6\u95f4\u95f4\u9694\uff0c\u9ed8\u8ba4(\u6700\u5c0f)300s", None)) + self.label_10.setText("") + self.cb_durable_total.setText(QCoreApplication.translate("MainWindow", u"\u5168\u90e8\u6253\u5f00/\u5173\u95ed", None)) + self.btn_draw.setText(QCoreApplication.translate("MainWindow", u"\u7ed8\u56fe", None)) + self.label_3.setText("") + self.tw_funcs.setTabText(self.tw_funcs.indexOf(self.tab_durable), QCoreApplication.translate("MainWindow", u"\u8010\u4e45\u91c7\u96c6", None)) + self.label_2.setText(QCoreApplication.translate("MainWindow", u"HMI IP", None)) + self.le_hmi_ip.setText(QCoreApplication.translate("MainWindow", u"192.168.0.160", None)) + self.btn_hmi_conn.setText(QCoreApplication.translate("MainWindow", u"\u8fde\u63a5", None)) + self.label_5.setText("") + self.cb_hmi_cmd.setItemText(0, QCoreApplication.translate("MainWindow", u"controller.heart", None)) + self.cb_hmi_cmd.setItemText(1, QCoreApplication.translate("MainWindow", u"device.get_params", None)) + self.cb_hmi_cmd.setItemText(2, QCoreApplication.translate("MainWindow", u"safety_area_data", None)) + + self.btn_hmi_send.setText(QCoreApplication.translate("MainWindow", u"\u53d1\u9001", None)) + self.label_7.setText(QCoreApplication.translate("MainWindow", u"MD Port", None)) + self.le_md_port.setText(QCoreApplication.translate("MainWindow", u"502", None)) + self.btn_md_conn.setText(QCoreApplication.translate("MainWindow", u"\u8fde\u63a5", None)) + self.label_12.setText("") + self.cb_md_cmd.setItemText(0, QCoreApplication.translate("MainWindow", u"ctrl_motor_on", None)) + self.cb_md_cmd.setItemText(1, QCoreApplication.translate("MainWindow", u"ctrl_motor_off", None)) + + self.btn_md_send.setText(QCoreApplication.translate("MainWindow", u"\u53d1\u9001", None)) + self.label_17.setText(QCoreApplication.translate("MainWindow", u"EC Port", None)) + self.le_ec_port.setText(QCoreApplication.translate("MainWindow", u"8080", None)) + self.btn_ec_conn.setText(QCoreApplication.translate("MainWindow", u"\u8fde\u63a5", None)) + self.label_18.setText("") + self.cb_ec_cmd.setItemText(0, QCoreApplication.translate("MainWindow", u"motor_on_state", None)) + self.cb_ec_cmd.setItemText(1, QCoreApplication.translate("MainWindow", u"robot_running_state", None)) + + self.btn_ec_send.setText(QCoreApplication.translate("MainWindow", u"\u53d1\u9001", None)) + self.pushButton.setText(QCoreApplication.translate("MainWindow", u"HMI", None)) + self.pushButton_2.setText(QCoreApplication.translate("MainWindow", u"Modbus", None)) + self.pushButton_3.setText(QCoreApplication.translate("MainWindow", u"EC", None)) + self.tw_funcs.setTabText(self.tw_funcs.indexOf(self.tab_network), QCoreApplication.translate("MainWindow", u"\u7f51\u7edc\u8bbe\u7f6e", None)) + self.tw_docs.setTabText(self.tw_docs.indexOf(self.tab_output), QCoreApplication.translate("MainWindow", u"\u8f93\u51fa", None)) + ___qtreewidgetitem = self.treew_log.headerItem() + ___qtreewidgetitem.setText(4, QCoreApplication.translate("MainWindow", u"Content", None)); + ___qtreewidgetitem.setText(3, QCoreApplication.translate("MainWindow", u"Module", None)); + ___qtreewidgetitem.setText(2, QCoreApplication.translate("MainWindow", u"Level", None)); + ___qtreewidgetitem.setText(1, QCoreApplication.translate("MainWindow", u"Timestamp", None)); + ___qtreewidgetitem.setText(0, QCoreApplication.translate("MainWindow", u"ID", None)); + self.label_page.setText(QCoreApplication.translate("MainWindow", u"0/0", None)) + self.btn_docs_previous.setText(QCoreApplication.translate("MainWindow", u"\u4e0a\u4e00\u9875", None)) + self.btn_docs_realtime.setText(QCoreApplication.translate("MainWindow", u"\u5b9e\u65f6", None)) + self.btn_docs_next.setText(QCoreApplication.translate("MainWindow", u"\u4e0b\u4e00\u9875", None)) + self.btn_docs_load.setText(QCoreApplication.translate("MainWindow", u"\u52a0\u8f7d", None)) + self.btn_docs_search.setText(QCoreApplication.translate("MainWindow", u"\u67e5\u627e", None)) + self.le_docs_search.setPlaceholderText(QCoreApplication.translate("MainWindow", u"[id/level/module] \u67e5\u627e\u5185\u5bb9", None)) + self.tw_docs.setTabText(self.tw_docs.indexOf(self.tab_log), QCoreApplication.translate("MainWindow", u"\u65e5\u5fd7", None)) + # retranslateUi + diff --git a/code/ui/reset_window.py b/code/ui/reset_window.py new file mode 100644 index 0000000..ec6c7d6 --- /dev/null +++ b/code/ui/reset_window.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'reset.ui' +## +## Created by: Qt User Interface Compiler version 6.8.2 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QLineEdit, + QPushButton, QSizePolicy, QVBoxLayout, QWidget) + +class Ui_Form(QWidget): + def setupUi(self, Form): + if not Form.objectName(): + Form.setObjectName(u"Form") + Form.setWindowModality(Qt.WindowModality.WindowModal) + Form.resize(500, 270) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + Form.setMinimumSize(QSize(500, 270)) + Form.setMaximumSize(QSize(500, 270)) + font = QFont() + font.setFamilies([u"Consolas"]) + font.setPointSize(14) + Form.setFont(font) + icon = QIcon() + icon.addFile(u"../assets/media/icon.ico", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + Form.setWindowIcon(icon) + self.widget = QWidget(Form) + self.widget.setObjectName(u"widget") + self.widget.setGeometry(QRect(40, 27, 411, 211)) + self.verticalLayout = QVBoxLayout(self.widget) + self.verticalLayout.setObjectName(u"verticalLayout") + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_5 = QHBoxLayout() + self.horizontalLayout_5.setObjectName(u"horizontalLayout_5") + self.label = QLabel(self.widget) + self.label.setObjectName(u"label") + self.label.setFont(font) + + self.horizontalLayout_5.addWidget(self.label) + + self.le_username = QLineEdit(self.widget) + self.le_username.setObjectName(u"le_username") + self.le_username.setFont(font) + + self.horizontalLayout_5.addWidget(self.le_username) + + + self.verticalLayout.addLayout(self.horizontalLayout_5) + + self.horizontalLayout_4 = QHBoxLayout() + self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") + self.label_2 = QLabel(self.widget) + self.label_2.setObjectName(u"label_2") + self.label_2.setFont(font) + + self.horizontalLayout_4.addWidget(self.label_2) + + self.le_old_password = QLineEdit(self.widget) + self.le_old_password.setObjectName(u"le_old_password") + self.le_old_password.setFont(font) + self.le_old_password.setEchoMode(QLineEdit.EchoMode.Password) + + self.horizontalLayout_4.addWidget(self.le_old_password) + + + self.verticalLayout.addLayout(self.horizontalLayout_4) + + self.horizontalLayout_3 = QHBoxLayout() + self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") + self.label_3 = QLabel(self.widget) + self.label_3.setObjectName(u"label_3") + self.label_3.setFont(font) + + self.horizontalLayout_3.addWidget(self.label_3) + + self.le_new_password_1 = QLineEdit(self.widget) + self.le_new_password_1.setObjectName(u"le_new_password_1") + self.le_new_password_1.setFont(font) + self.le_new_password_1.setEchoMode(QLineEdit.EchoMode.Password) + + self.horizontalLayout_3.addWidget(self.le_new_password_1) + + + self.verticalLayout.addLayout(self.horizontalLayout_3) + + self.horizontalLayout_2 = QHBoxLayout() + self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.label_4 = QLabel(self.widget) + self.label_4.setObjectName(u"label_4") + self.label_4.setFont(font) + + self.horizontalLayout_2.addWidget(self.label_4) + + self.le_new_password_2 = QLineEdit(self.widget) + self.le_new_password_2.setObjectName(u"le_new_password_2") + self.le_new_password_2.setFont(font) + self.le_new_password_2.setEchoMode(QLineEdit.EchoMode.Password) + + self.horizontalLayout_2.addWidget(self.le_new_password_2) + + + self.verticalLayout.addLayout(self.horizontalLayout_2) + + self.label_hint = QLabel(self.widget) + self.label_hint.setObjectName(u"label_hint") + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + sizePolicy1.setHorizontalStretch(0) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth(self.label_hint.sizePolicy().hasHeightForWidth()) + self.label_hint.setSizePolicy(sizePolicy1) + font1 = QFont() + font1.setFamilies([u"Consolas"]) + font1.setPointSize(12) + self.label_hint.setFont(font1) + + self.verticalLayout.addWidget(self.label_hint) + + self.horizontalLayout = QHBoxLayout() + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.btn_reset = QPushButton(self.widget) + self.btn_reset.setObjectName(u"btn_reset") + self.btn_reset.setFont(font) + + self.horizontalLayout.addWidget(self.btn_reset) + + self.btn_cancel = QPushButton(self.widget) + self.btn_cancel.setObjectName(u"btn_cancel") + self.btn_cancel.setFont(font) + + self.horizontalLayout.addWidget(self.btn_cancel) + + + self.verticalLayout.addLayout(self.horizontalLayout) + + QWidget.setTabOrder(self.le_username, self.le_old_password) + QWidget.setTabOrder(self.le_old_password, self.le_new_password_1) + QWidget.setTabOrder(self.le_new_password_1, self.le_new_password_2) + + self.retranslateUi(Form) + self.btn_reset.clicked.connect(Form.reset_password) + self.btn_cancel.clicked.connect(Form.reset_cancel) + + QMetaObject.connectSlotsByName(Form) + # setupUi + + def retranslateUi(self, Form): + Form.setWindowTitle(QCoreApplication.translate("Form", u"\u91cd\u7f6e\u5bc6\u7801", None)) + self.label.setText(QCoreApplication.translate("Form", u"\u7528\u6237\u540d", None)) + self.label_2.setText(QCoreApplication.translate("Form", u"\u65e7\u5bc6\u7801", None)) + self.label_3.setText(QCoreApplication.translate("Form", u"\u65b0\u5bc6\u7801", None)) + self.label_4.setText(QCoreApplication.translate("Form", u"\u786e \u8ba4", None)) + self.label_hint.setText("") + self.btn_reset.setText(QCoreApplication.translate("Form", u"\u786e\u5b9a", None)) + self.btn_cancel.setText(QCoreApplication.translate("Form", u"\u53d6\u6d88", None)) + # retranslateUi + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..689079b --- /dev/null +++ b/readme.md @@ -0,0 +1,129 @@ +# 珞石测试部自动化工具 + +针对珞石工业协作六轴机器人的整机测试,该工具可以执行如下自动化操作,以减少人工处理时长,提高测试数据处理的效率和准确度: + +1. 制动数据,单轴数据处理 5min 以内 +2. 电机电流数据,全部轴数据处理 3min 以内 +3. ISO 激光数据整理,1min 以内 +4. wavelogger 波形处理,几乎不花费时间 +5. 制动自动化测试,40min 左右 +6. 电机电流自动化测试,15min 左右 +7. 耐久工程相关指标采集记录,可定制曲线并绘图 + +## 一、处理逻辑以及原理 + +该部分简要介绍了工具实现的核心逻辑原理,以及执行的必要条件。 + +### 1. 数据处理 + +#### A. 制动数据 + +- 原理:找到 `device_safety_estop` 曲线值由 1 -> 0 的位置,并预留 20 个数据余量截取 1000 个点 +- 必要条件: + - 根目录命名为 j1/2/3 + - 数据文件夹命名参照执行制动数据采集 + - 需要有三个结果文件,命名为 reach33/66/100_xxxxxxxx.xlsx + - 需要有一个机型文件,比如 NB4h-R580-3G.cfg,此文件会在执行制动测试的时候自动生成,注意保存 + +#### B. 电机电流 + +- 单轴原理:找到一个周期的起点和终点 +- 堵转原理:求平均均值 +- 场景原理:获取场景的执行周期时间,并做数据截取 +- 必要条件: + - 数据文件命名参照电机电流测试生成的格式 + - 需要有一个结果文件,命名为 T_电机电流.xlsx + - 需要有一个机型文件,比如 NB4h-R580-3G.cfg,此文件会在执行制动测试的时候自动生成,注意保存 + +#### C. 激光 + +- 原理:根据要获取的数据所在位置的特征,过滤提取 +- 必要条件: + - 数据文件命名必须参照如下: + - ISO-.pdf + - ISO-V100.pdf + - ISO-V1000.pdf + - 需要有一个结果文件,iso-results.xlsx + +#### D. 基恩士数据 + +- 原理:根据数据大小以及出现的周期规律,进行相应的处理,处理完成之后结果文件会自动生成 +- 必要条件:需要提前将 `.xdt` 波形数据转换成 `.csv` 文件,数据文件命名必须以 `.csv` 结尾 + +## 二、自动测试 + +### 1. 协议封包解包 + +详见 `assets/files/protocols/` + +### 2. 制动测试 + +- 原理:使用 xCore socket 协议获取诊断数据,包括速度,力矩等信息,结合 modbus 和外部通信执行急停等动作 +- 必要条件: + - RL 工程(brake任务),参考 `assets/files/projects/`,需要详细阅读对应的注释 + - 需要有一个配置文件,命名为 configs.xlsx + - 需要有三个结果文件,命名为 reach33/66/100_xxxxxxxx.xlsx + +### 3. 电机电流测试 + +- 原理:使用 xCore socket 协议获取诊断数据,包括速度,力矩等信息,结合 modbus 和外部通信执行急停等动作 +- 必要条件: + - RL 工程(current任务),参考 `assets/files/projects/`,需要详细阅读对应的注释 + - 需要有一个结果文件,命名为 T_电机电流.xlsx + +## 三、耐久测试数据采集 + +- 原理:根据设置的时间间隔,采集相应的场景周期指标数据 +- 必要条件: + - RL 工程(factory任务),参考 `assets/files/projects/`,需要详细阅读对应的注释 + +## 三、注意事项 + +> **!!仅内网使用!!** + +1. 仅适用于 xCore 2.3.0.7 及以上的版本 +2. 仅适配了六轴工业/协作机型,其他机型可能会存在使用上的问题,具体可以找fanmingfu@rokae.com确认 +3. 单轴电机电流数据处理,至少需要三个完整周期,使用本工具采集一般不会有问题,主要是针对手动采集命名的数据 +4. 执行制动测试/电机电流采集/耐久测试的时候,执行过程中停止,需要重新关闭软件再次打开,才能正常使用 +5. RL工程/寄存器文件/configs.xlsx文件已更新,需要使用新版,其他使用方法和之前工具一致 +6. 基恩士采集数据时,不同轮次(每5次测试)数据时间间隔最好大于 2 倍的周期时间,否则会出现采集的轮数不正确的情况,但数据是完整的 +7. 激光数据处理只支持英文版本的数据文件 +8. 耐久(场景)指标数据采集的工程运行周期需要小于 300s,否则会出现异常 + +> **需要使用 assets/files/projects/ 下的工程,寄存器文件以及配置文件等!!!** + +## 四、发版记录 + +详见 `assets/files/version/release_change.txt` 文件 + +## 五、其他 + +### 1. 打包命令 + +打包时,只需要修改 clibs.py 中的 PREFIX 即可,调试时再修改回来,第三方库依赖详见 `assets/files/version/requirements.txt` 文件 + +``` +pyinstaller --noconfirm --onedir --windowed --optimize 2 --contents-directory . --upx-dir "D:/Syncthing/common/A_Program/upx-4.2.4-win64/" --add-data "../.venv/Lib/site-packages/customtkinter;customtkinter/" --add-data "../assets:assets" --version-file ../assets/files/version/file_version_info.txt -i ../assets/media/icon.ico ../code/aio.py -p ../code/common/clibs.py -p ../code/commom/openapi.py -p ../code/data_process/brake.py -p ../code/data_process/iso.py -p ../code/data_process/current.py -p ../code/data_process/wavelogger.py -p ../code/automatic_test/do_current.py -p ../code/automatic_test/do_brake.py -p ../code/durable_docs/factory_test.py -p ../code/durable_docs/create_plot.py --exclude-module=scipy +``` + +### 2. tabview 组件字体修改 + +customtkinter的tabview组件不支持修改字体大小,解决方法可参考如下: + +- Method 1:可以参考 [Changing Font of a Tabview](https://github.com/TomSchimansky/CustomTkinter/issues/2296) 进行手动修改源码实现: + + + 运行 `pip show customtkinter`,获取到库的路径 + + 修改.../windows/widgets/ctk_tabview.py + + 增加 from .font.ctk_font import CTkFont + + 在大概 78 行的位置,增加 font=CTkFont(family="Consolas", size=18, weight='bold') + +- Method 2: + + 直接在源码中修改:`self.tabview_bottom._segmented_button.configure(font=ctk.CTkFont(family="Consolas", size=18, weight="bold"))` + +### 3. scroll frame 不支持修改高度和宽度 + +https://github.com/TomSchimansky/CustomTkinter/pull/1765/files + + +--- diff --git a/ui/login.ui b/ui/login.ui new file mode 100644 index 0000000..2f976bc --- /dev/null +++ b/ui/login.ui @@ -0,0 +1,249 @@ + + + Form + + + Qt::WindowModality::WindowModal + + + + 0 + 0 + 500 + 270 + + + + + 0 + 0 + + + + + 500 + 270 + + + + + 500 + 270 + + + + + Consolas + 14 + + + + 登录 + + + + ../assets/media/icon.ico../assets/media/icon.ico + + + + + 41 + 41 + 411 + 211 + + + + + 2 + + + + + 2 + + + + + + Consolas + 14 + + + + 用户名 + + + + + + + + Consolas + 14 + + + + + + + + + + 2 + + + + + + Consolas + 14 + + + + 密 码 + + + + + + + + Consolas + 14 + + + + QLineEdit::EchoMode::Password + + + + + + + + + + 0 + 0 + + + + + Consolas + 12 + + + + + + + + + + + 2 + + + + + + Consolas + 14 + + + + 登录 + + + + + + + + Consolas + 14 + + + + 重置 + + + + + + + + + + + + btn_login + clicked() + Form + user_login() + + + 85 + 130 + + + 34 + 112 + + + + + le_password + returnPressed() + Form + user_login() + + + 178 + 82 + + + 11 + 70 + + + + + le_username + returnPressed() + Form + user_login() + + + 169 + 42 + + + 10 + 33 + + + + + btn_reset + clicked() + Form + reset_password() + + + 311 + 138 + + + 367 + 113 + + + + + + user_login() + reset_password() + + diff --git a/ui/main.ui b/ui/main.ui new file mode 100644 index 0000000..53452a8 --- /dev/null +++ b/ui/main.ui @@ -0,0 +1,2004 @@ + + + MainWindow + + + true + + + + 0 + 0 + 1002 + 555 + + + + + 0 + 0 + + + + + 1000 + 550 + + + + + Consolas + 14 + + + + Rokae AIO + + + + ../assets/media/icon.ico../assets/media/icon.ico + + + background-color: rgb(233, 233, 233); + + + false + + + + + + + + + + 0 + 0 + + + + + 200 + 100 + + + + + 240 + 120 + + + + + Segoe Print + 24 + true + + + + Rokae AIO + + + Qt::AlignmentFlag::AlignCenter + + + 0 + + + + + + + + 0 + 0 + + + + + 150 + 36 + + + + + 180 + 45 + + + + + Consolas + 14 + true + + + + 开始执行 + + + false + + + + + + + + 0 + 0 + + + + + 150 + 36 + + + + + 180 + 45 + + + + + Consolas + 14 + true + + + + 停止执行 + + + false + + + + + + + + 0 + 0 + + + + + 150 + 36 + + + + + 180 + 45 + + + + + Consolas + 14 + true + + + + 状态重置 + + + false + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 14 + true + + + + 0 + + + Qt::TextElideMode::ElideNone + + + true + + + true + + + false + + + false + + + + 数据处理 + + + + + + + + + 100 + 0 + + + + + Consolas + 12 + + + + + 制动 + + + + + 转矩 + + + + + 激光 + + + + + 精度 + + + + + + + + + 100 + 0 + + + + + Consolas + 12 + + + + + 周期 + + + + + 最大值 + + + + + 平均值 + + + + + + + + + Consolas + 12 + + + + 路径 + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + + Consolas + 12 + + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + + + 30 + 16777215 + + + + + Consolas + 12 + + + + ... + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 161 + + + + + + + + + 整机测试 + + + + + + + + + 100 + 0 + + + + + Consolas + 12 + + + + + 制动 + + + + + 转矩 + + + + + + + + + 100 + 0 + + + + + Consolas + 12 + + + + + tool33 + + + + + tool66 + + + + + tool100 + + + + + inertia + + + + + + + + + 0 + 0 + + + + + Consolas + 12 + + + + 路径 + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + + Consolas + 12 + + + + + + + + + 30 + 16777215 + + + + + Consolas + 12 + + + + ... + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + 耐久采集 + + + + + + + + + + + 200 + 0 + + + + + 300 + 16777215 + + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + + + + + 0 + 0 + + + + + Consolas + 14 + true + + + + 选择指标 + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + + true + + + + + 0 + 0 + 211 + 78 + + + + + + + + + + Consolas + 12 + + + + 周期内平均转矩 + + + + + + + + Consolas + 12 + + + + 周期内平均转矩 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + Consolas + 12 + + + + 路径 + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + Consolas + 12 + + + + + + + + + 30 + 16777215 + + + + + Consolas + 12 + + + + ... + + + + + + + + + + + + 0 + 0 + + + + + Consolas + 12 + + + + 间隔 + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + Consolas + 12 + + + + + + + Qt::InputMethodHint::ImhNone + + + 每次数据采集的时间间隔,默认(最小)300s + + + + + + + + 0 + 0 + + + + + 30 + 0 + + + + + + + + + + + + + + + + Consolas + 12 + true + + + + 全部打开/关闭 + + + + + + + + Consolas + 12 + true + + + + 绘图 + + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 网络设置 + + + + + + + + 0 + + + + + + + + + + + + 0 + 0 + + + + + 70 + 0 + + + + + Consolas + 12 + true + + + + HMI IP + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 150 + 0 + + + + + Consolas + 12 + + + + 192.168.0.160 + + + + + + + + Consolas + 12 + true + + + + 连接 + + + + + + + + Consolas + 12 + + + + + + + + + + + + 240 + 0 + + + + + Consolas + 12 + + + + + controller.heart + + + + + device.get_params + + + + + safety_area_data + + + + + + + + + Consolas + 12 + true + + + + 发送 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 70 + 0 + + + + + Consolas + 12 + true + + + + MD Port + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 150 + 0 + + + + + Consolas + 12 + + + + 502 + + + + + + + + Consolas + 12 + true + + + + 连接 + + + + + + + + Consolas + 12 + + + + + + + + + + + + 240 + 0 + + + + + Consolas + 12 + + + + + ctrl_motor_on + + + + + ctrl_motor_off + + + + + + + + + Consolas + 12 + true + + + + 发送 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 70 + 0 + + + + + Consolas + 12 + true + + + + EC Port + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 150 + 0 + + + + + Consolas + 12 + + + + 8080 + + + + + + + + Consolas + 12 + true + + + + 连接 + + + + + + + + Consolas + 12 + + + + + + + + + + + + 240 + 0 + + + + + Consolas + 12 + + + + + motor_on_state + + + + + robot_running_state + + + + + + + + + Consolas + 12 + true + + + + 发送 + + + + + + + + + + + + + + + + + + + + + + + + + + + + Consolas + 12 + true + + + + HMI + + + + + + + + Consolas + 12 + true + + + + Modbus + + + + + + + + Consolas + 12 + true + + + + EC + + + + + + + + + + + + + + + + 14 + true + + + + 1 + + + Qt::TextElideMode::ElideNone + + + true + + + + 输出 + + + + + + + Consolas + 12 + + + + QPlainTextEdit::LineWrapMode::WidgetWidth + + + true + + + + + + + + 日志 + + + + + + + Consolas + 12 + + + + true + + + + ID + + + + + Timestamp + + + + + Level + + + + + Module + + + + + Content + + + + + + + + + + + 100 + 0 + + + + + Consolas + 10 + true + + + + background-color: rgb(222, 222, 222); + + + 0/0 + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + Consolas + 12 + + + + 上一页 + + + + + + + + Consolas + 12 + + + + 实时 + + + + + + + + Consolas + 12 + + + + 下一页 + + + + + + + + Consolas + 12 + + + + 加载 + + + + + + + + Consolas + 12 + + + + 查找 + + + + + + + + Consolas + 12 + + + + [id/level/module] 查找内容 + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 27 + + + + background-color: rgb(200, 200, 200); + + + + + + + btn_start + clicked() + MainWindow + prog_start() + + + 89 + 158 + + + 18 + 130 + + + + + btn_stop + clicked() + MainWindow + prog_stop() + + + 43 + 176 + + + 15 + 172 + + + + + btn_reset + clicked() + MainWindow + prog_reset() + + + 41 + 217 + + + 15 + 214 + + + + + btn_durable_open + clicked() + MainWindow + file_browser() + + + 964 + 69 + + + 990 + 97 + + + + + btn_draw + clicked() + MainWindow + curve_draw() + + + 692 + 136 + + + 701 + 179 + + + + + cb_durable_total + checkStateChanged(Qt::CheckState) + MainWindow + durable_cb_change() + + + 566 + 135 + + + 546 + 180 + + + + + btn_unit_open + clicked() + MainWindow + file_browser() + + + 981 + 78 + + + 974 + 181 + + + + + btn_data_open + clicked() + MainWindow + file_browser() + + + 964 + 73 + + + 941 + 180 + + + + + btn_docs_previous + clicked() + MainWindow + pre_page() + + + 408 + 507 + + + 307 + 517 + + + + + btn_docs_realtime + clicked() + MainWindow + realtime_page() + + + 489 + 507 + + + 435 + 520 + + + + + btn_docs_next + clicked() + MainWindow + next_page() + + + 570 + 507 + + + 520 + 519 + + + + + btn_docs_load + clicked() + MainWindow + load_sql() + + + 651 + 507 + + + 606 + 521 + + + + + btn_docs_search + clicked() + MainWindow + search_keyword() + + + 732 + 507 + + + 688 + 521 + + + + + le_docs_search + returnPressed() + MainWindow + search_keyword() + + + 838 + 505 + + + 932 + 525 + + + + + cb_hmi_cmd + currentTextChanged(QString) + MainWindow + hmi_cb_change() + + + 806 + 89 + + + 585 + 185 + + + + + btn_hmi_send + clicked() + MainWindow + hmi_send() + + + 887 + 89 + + + 789 + 185 + + + + + pushButton + clicked() + MainWindow + hmi_page() + + + 980 + 85 + + + 744 + 2 + + + + + pushButton_2 + clicked() + MainWindow + md_page() + + + 980 + 124 + + + 784 + 6 + + + + + pushButton_3 + clicked() + MainWindow + ec_page() + + + 980 + 163 + + + 969 + 9 + + + + + cb_md_cmd + currentTextChanged(QString) + MainWindow + md_cb_change() + + + 805 + 89 + + + 612 + 181 + + + + + btn_md_send + clicked() + MainWindow + md_send() + + + 887 + 89 + + + 795 + 181 + + + + + btn_ec_send + clicked() + MainWindow + ec_send() + + + 887 + 89 + + + 652 + 7 + + + + + btn_hmi_conn + clicked() + MainWindow + hmi_conn() + + + 545 + 89 + + + 372 + 186 + + + + + btn_md_conn + clicked() + MainWindow + md_conn() + + + 545 + 89 + + + 398 + 182 + + + + + btn_ec_conn + clicked() + MainWindow + ec_conn() + + + 545 + 89 + + + 412 + -1 + + + + + le_durable_interval + editingFinished() + MainWindow + check_interval() + + + 844 + 97 + + + 821 + -3 + + + + + cb_ec_cmd + currentTextChanged(QString) + MainWindow + ec_cb_change() + + + 805 + 89 + + + 540 + -2 + + + + + le_hmi_ip + returnPressed() + MainWindow + hmi_conn() + + + 420 + 79 + + + 216 + 130 + + + + + + prog_start() + prog_stop() + prog_reset() + file_browser() + curve_draw() + durable_cb_change() + pre_page() + realtime_page() + next_page() + load_sql() + search_keyword() + hmi_conn() + md_conn() + ec_conn() + hmi_page() + md_page() + ec_page() + hmi_send() + md_send() + ec_send() + hmi_cb_change() + md_cb_change() + ec_cb_change() + check_interval() + + diff --git a/ui/reset.ui b/ui/reset.ui new file mode 100644 index 0000000..2f34e7d --- /dev/null +++ b/ui/reset.ui @@ -0,0 +1,271 @@ + + + Form + + + Qt::WindowModality::WindowModal + + + + 0 + 0 + 500 + 270 + + + + + 0 + 0 + + + + + 500 + 270 + + + + + 500 + 270 + + + + + Consolas + 14 + + + + 重置密码 + + + + ../assets/media/icon.ico../assets/media/icon.ico + + + + + 40 + 27 + 411 + 211 + + + + + + + + + + Consolas + 14 + + + + 用户名 + + + + + + + + Consolas + 14 + + + + + + + + + + + + + Consolas + 14 + + + + 旧密码 + + + + + + + + Consolas + 14 + + + + QLineEdit::EchoMode::Password + + + + + + + + + + + + Consolas + 14 + + + + 新密码 + + + + + + + + Consolas + 14 + + + + QLineEdit::EchoMode::Password + + + + + + + + + + + + Consolas + 14 + + + + 确 认 + + + + + + + + Consolas + 14 + + + + QLineEdit::EchoMode::Password + + + + + + + + + + 0 + 0 + + + + + Consolas + 12 + + + + + + + + + + + + + + Consolas + 14 + + + + 确定 + + + + + + + + Consolas + 14 + + + + 取消 + + + + + + + + + + le_username + le_old_password + le_new_password_1 + le_new_password_2 + + + + + btn_reset + clicked() + Form + reset_password() + + + 85 + 175 + + + 22 + 149 + + + + + btn_cancel + clicked() + Form + reset_cancel() + + + 295 + 177 + + + 386 + 148 + + + + + + reset_password() + reset_cancel() + +