gomyck-tools 1.3.1__py3-none-any.whl → 1.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ctools/__init__.py +0 -0
- ctools/aes_tools.py +35 -0
- ctools/api_result.py +55 -0
- ctools/application.py +386 -0
- ctools/b64.py +7 -0
- ctools/bashPath.py +13 -0
- ctools/bottle_web_base.py +169 -0
- ctools/bottle_webserver.py +143 -0
- ctools/bottle_websocket.py +75 -0
- ctools/browser_element_tools.py +314 -0
- ctools/call.py +71 -0
- ctools/cdebug.py +143 -0
- ctools/cftp.py +74 -0
- ctools/cjson.py +54 -0
- ctools/ckafka.py +159 -0
- ctools/compile_tools.py +18 -0
- ctools/console.py +55 -0
- ctools/coord_trans.py +127 -0
- ctools/credis.py +111 -0
- ctools/cron_lite.py +245 -0
- ctools/ctoken.py +34 -0
- ctools/cword.py +30 -0
- ctools/czip.py +130 -0
- ctools/database.py +185 -0
- ctools/date_utils.py +43 -0
- ctools/dict_wrapper.py +20 -0
- ctools/douglas_rarefy.py +136 -0
- ctools/download_tools.py +57 -0
- ctools/enums.py +4 -0
- ctools/ex.py +31 -0
- ctools/excelOpt.py +36 -0
- ctools/html_soup.py +35 -0
- ctools/http_utils.py +24 -0
- ctools/images_tools.py +27 -0
- ctools/imgDialog.py +44 -0
- ctools/metrics.py +131 -0
- ctools/mqtt_utils.py +289 -0
- ctools/obj.py +20 -0
- ctools/pacth.py +74 -0
- ctools/plan_area_tools.py +97 -0
- ctools/process_pool.py +36 -0
- ctools/pty_tools.py +72 -0
- ctools/resource_bundle_tools.py +121 -0
- ctools/rsa.py +70 -0
- ctools/screenshot_tools.py +127 -0
- ctools/sign.py +20 -0
- ctools/sm_tools.py +49 -0
- ctools/snow_id.py +76 -0
- ctools/str_diff.py +20 -0
- ctools/string_tools.py +85 -0
- ctools/sys_info.py +157 -0
- ctools/sys_log.py +89 -0
- ctools/thread_pool.py +35 -0
- ctools/upload_tools.py +40 -0
- ctools/win_canvas.py +83 -0
- ctools/win_control.py +106 -0
- ctools/word_fill.py +562 -0
- ctools/word_fill_entity.py +46 -0
- ctools/work_path.py +69 -0
- {gomyck_tools-1.3.1.dist-info → gomyck_tools-1.3.3.dist-info}/METADATA +4 -2
- gomyck_tools-1.3.3.dist-info/RECORD +64 -0
- {gomyck_tools-1.3.1.dist-info → gomyck_tools-1.3.3.dist-info}/WHEEL +1 -1
- gomyck_tools-1.3.3.dist-info/licenses/LICENSE +13 -0
- gomyck_tools-1.3.3.dist-info/top_level.txt +1 -0
- gomyck_tools-1.3.1.dist-info/RECORD +0 -4
- gomyck_tools-1.3.1.dist-info/top_level.txt +0 -1
ctools/metrics.py
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
import os
|
2
|
+
import threading
|
3
|
+
from enum import Enum
|
4
|
+
|
5
|
+
from prometheus_client import Counter, Gauge, Summary, Histogram, start_http_server
|
6
|
+
from ctools import call, cjson, sys_log, work_path
|
7
|
+
|
8
|
+
log = sys_log.flog
|
9
|
+
|
10
|
+
_metrics_port = 8889
|
11
|
+
_persistent_json = {}
|
12
|
+
_metrics_initial = {}
|
13
|
+
_lock = threading.Lock()
|
14
|
+
_metrics_persistent_path = os.path.join(work_path.get_user_work_path('metrics', True), 'persistent.json')
|
15
|
+
|
16
|
+
# 认证中间件
|
17
|
+
# @app.before_request
|
18
|
+
# def check_authentication():
|
19
|
+
# auth = request.authorization
|
20
|
+
# if not auth or auth.username != USERNAME or auth.password != PASSWORD:
|
21
|
+
# return Response(
|
22
|
+
# "Unauthorized", 401, {"WWW-Authenticate": 'Basic realm="Login Required"'}
|
23
|
+
# )
|
24
|
+
|
25
|
+
class MetricType(Enum):
|
26
|
+
COUNTER = 'counter'
|
27
|
+
GAUGE = 'gauge'
|
28
|
+
SUMMARY = 'summary'
|
29
|
+
HISTOGRAM = 'histogram'
|
30
|
+
|
31
|
+
class Metric:
|
32
|
+
def __init__(self, metric_type: MetricType, metric_key: str, metric_labels: [], persistent: bool = False, buckets: [] = None, reset: bool = False, desc: str = ""):
|
33
|
+
self.metric_type = metric_type
|
34
|
+
self.metric_key = metric_key
|
35
|
+
self.metric_labels = metric_labels
|
36
|
+
self.buckets = buckets
|
37
|
+
self.metric = None
|
38
|
+
self.persistent = persistent
|
39
|
+
self.reset = reset
|
40
|
+
if metric_type == MetricType.COUNTER:
|
41
|
+
self.metric = Counter(metric_key, desc, metric_labels)
|
42
|
+
elif metric_type == MetricType.GAUGE:
|
43
|
+
self.metric = Gauge(metric_key, desc, metric_labels)
|
44
|
+
elif metric_type == MetricType.SUMMARY:
|
45
|
+
self.metric = Summary(metric_key, desc, metric_labels)
|
46
|
+
elif metric_type == MetricType.HISTOGRAM:
|
47
|
+
if buckets is None: raise Exception('histogram buckets can not empty')
|
48
|
+
self.metric = Histogram(metric_key, desc, metric_labels, buckets=self.buckets)
|
49
|
+
else:
|
50
|
+
raise Exception('metric type not found')
|
51
|
+
_metrics_initial[metric_key] = self
|
52
|
+
|
53
|
+
@call.once
|
54
|
+
def init(reset_persistent: bool = False):
|
55
|
+
if os.path.exists(_metrics_persistent_path) and not reset_persistent:
|
56
|
+
with open(_metrics_persistent_path, 'r') as persistent_file:
|
57
|
+
global _persistent_json
|
58
|
+
try:
|
59
|
+
content = persistent_file.readline()
|
60
|
+
log.info("persistent初始化: %s" % content)
|
61
|
+
_persistent_json = cjson.loads(content)
|
62
|
+
except Exception:
|
63
|
+
log.error('persistent.json is not valid json!!!!!')
|
64
|
+
_persistent_json = {}
|
65
|
+
_init_all_metrics()
|
66
|
+
_persistent_json = _persistent_json or {}
|
67
|
+
for key, item in _persistent_json.items():
|
68
|
+
metrics_key = key.split("-")[0]
|
69
|
+
if '_labels' in key or metrics_key not in _metrics_initial: continue
|
70
|
+
opt(metrics_key, _persistent_json[key + '_labels'], _persistent_json[key])
|
71
|
+
persistent_metrics()
|
72
|
+
start_http_server(port=_metrics_port)
|
73
|
+
|
74
|
+
@call.schd(5, start_by_call=True)
|
75
|
+
def persistent_metrics():
|
76
|
+
if _persistent_json and not _lock.locked():
|
77
|
+
log.info('begin persistent metrics to file...')
|
78
|
+
with open(_metrics_persistent_path, 'w') as persistent_file:
|
79
|
+
persistent_file.write(cjson.dumps(_persistent_json))
|
80
|
+
persistent_file.flush()
|
81
|
+
|
82
|
+
def opt(metric_key: str, label_values: [], metric_value: int):
|
83
|
+
_lock.acquire(timeout=5)
|
84
|
+
try:
|
85
|
+
persistent_key = "%s-%s" % (metric_key, "_".join(map(str, label_values)))
|
86
|
+
metric_entity: Metric = _metrics_initial[metric_key]
|
87
|
+
if metric_entity.persistent:
|
88
|
+
if not metric_entity.reset and persistent_key in _persistent_json:
|
89
|
+
_persistent_json[persistent_key] += metric_value
|
90
|
+
else:
|
91
|
+
_persistent_json[persistent_key] = metric_value
|
92
|
+
_persistent_json[persistent_key + '_labels'] = label_values
|
93
|
+
|
94
|
+
if _persistent_json[persistent_key] < 0:
|
95
|
+
_persistent_json[persistent_key] = 0
|
96
|
+
metric_value = 0
|
97
|
+
|
98
|
+
if metric_entity.metric_type == MetricType.COUNTER or metric_entity.metric_type == MetricType.GAUGE:
|
99
|
+
if label_values is None or len(label_values) == 0:
|
100
|
+
if metric_entity.metric_type == MetricType.COUNTER and metric_entity.reset:
|
101
|
+
metric_entity.metric.labels('').reset()
|
102
|
+
if metric_entity.metric_type == MetricType.GAUGE and metric_entity.reset:
|
103
|
+
metric_entity.metric.labels('').set(0)
|
104
|
+
metric_entity.metric.labels('').inc(metric_value)
|
105
|
+
else:
|
106
|
+
if metric_entity.reset:
|
107
|
+
if metric_entity.metric_type == MetricType.COUNTER and metric_entity.reset:
|
108
|
+
metric_entity.metric.labels(*label_values).reset()
|
109
|
+
if metric_entity.metric_type == MetricType.GAUGE and metric_entity.reset:
|
110
|
+
metric_entity.metric.labels(*label_values).set(0)
|
111
|
+
metric_entity.metric.labels(*label_values).inc(metric_value)
|
112
|
+
else:
|
113
|
+
if label_values is None or len(label_values) == 0:
|
114
|
+
metric_entity.metric.observe(metric_value)
|
115
|
+
else:
|
116
|
+
metric_entity.metric.labels(*label_values).observe(metric_value)
|
117
|
+
except Exception as e:
|
118
|
+
log.error("添加指标信息异常: %s" % e)
|
119
|
+
_lock.release()
|
120
|
+
|
121
|
+
def _init_all_metrics():
|
122
|
+
Metric(MetricType.GAUGE, 'gomyck', ['g_label1', 'g_label2'], persistent=True, reset=True)
|
123
|
+
|
124
|
+
# if __name__ == '__main__':
|
125
|
+
# init()
|
126
|
+
# import random
|
127
|
+
# while True:
|
128
|
+
# opt('data_reported_time', ['123', '123'], random.randint(1, 10))
|
129
|
+
# opt('data_received_time', ['123', '123'], random.randint(1, 10))
|
130
|
+
# opt('data_insert_time', ['123', '123'], random.randint(1, 10))
|
131
|
+
# time.sleep(1)
|
ctools/mqtt_utils.py
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
import time
|
2
|
+
from enum import Enum
|
3
|
+
from typing import Dict
|
4
|
+
|
5
|
+
from ctools.dict_wrapper import DictWrapper as DictToObj
|
6
|
+
from paho.mqtt import client as mqtt
|
7
|
+
from paho.mqtt.enums import CallbackAPIVersion
|
8
|
+
|
9
|
+
from ctools import sys_log, cjson, string_tools, sys_info, date_utils, sm_tools, thread_pool
|
10
|
+
|
11
|
+
class MQTTEvent(Enum):
|
12
|
+
|
13
|
+
pass
|
14
|
+
|
15
|
+
'''
|
16
|
+
MQTT服务使用示例:
|
17
|
+
|
18
|
+
mqtt_utils = MQTTUtils("192.168.3.100")
|
19
|
+
mqtt_utils.connect(username="rpa", password="123")
|
20
|
+
|
21
|
+
# 订阅
|
22
|
+
mqtt_utils.subscribe("test/topic1", "test/topic2")
|
23
|
+
|
24
|
+
# 异步发送
|
25
|
+
msg = {"name": "测试消息"}
|
26
|
+
mqtt_utils.publish_async(msg, event=MQTTEvent.END_TASK)
|
27
|
+
|
28
|
+
# 同步发送
|
29
|
+
mqtt_resp = mqtt_utils.publish_sync(msg, event=MQTTEvent.UPLOAD_PROCESS, timeout=60)
|
30
|
+
status = mqtt_resp.status
|
31
|
+
body = mqtt_resp.body
|
32
|
+
print("%s, %s" % (status, body))
|
33
|
+
'''
|
34
|
+
|
35
|
+
log = sys_log.flog
|
36
|
+
|
37
|
+
NO_LOG_EVENT = [MQTTEvent.HEARTBEAT]
|
38
|
+
|
39
|
+
|
40
|
+
class MQTTRequest:
|
41
|
+
|
42
|
+
def __init__(self, **kwargs):
|
43
|
+
self.event: str = kwargs.get('event')
|
44
|
+
self.trace_id: str = kwargs.get('trace_id') if kwargs.get('trace_id') else string_tools.get_uuid()
|
45
|
+
self.client_id: str = kwargs.get('client_id')
|
46
|
+
self.time: str = kwargs.get('time') if kwargs.get('time') else date_utils.get_date_time()
|
47
|
+
self.body = kwargs.get('body')
|
48
|
+
|
49
|
+
def __str__(self):
|
50
|
+
res_str = ""
|
51
|
+
for k, v in self.__dict__.items():
|
52
|
+
res_str += "%s=%s," % (k, v)
|
53
|
+
return res_str[:-1]
|
54
|
+
|
55
|
+
|
56
|
+
class MQTTResponse:
|
57
|
+
|
58
|
+
def __init__(self, **kwargs):
|
59
|
+
self.event: str = kwargs.get('event')
|
60
|
+
self.trace_id: str = kwargs.get('trace_id')
|
61
|
+
self.status: int = kwargs.get('status')
|
62
|
+
self.time: str = kwargs.get('time') if kwargs.get('time') else date_utils.get_date_time()
|
63
|
+
self.msg: str = kwargs.get('msg')
|
64
|
+
self.body = kwargs.get('body')
|
65
|
+
|
66
|
+
def __str__(self):
|
67
|
+
res_str = ""
|
68
|
+
for k, v in self.__dict__.items():
|
69
|
+
res_str += "%s=%s," % (k, v)
|
70
|
+
return res_str[:-1]
|
71
|
+
|
72
|
+
|
73
|
+
class MQTTUtils:
|
74
|
+
def __init__(self, broker_address, port=1883, publish_topic=None, client_id=None, keepalive=60, clean_session=False,
|
75
|
+
userdata=None, protocol=mqtt.MQTTv311, message_handle=None, broker_encrypt_key=None):
|
76
|
+
self.publish_topic = publish_topic
|
77
|
+
self.broker_address = broker_address
|
78
|
+
self.port = port
|
79
|
+
self.client_id = client_id if client_id else sys_info.get_machine_code()
|
80
|
+
self.keepalive = keepalive
|
81
|
+
self.clean_session = clean_session
|
82
|
+
self.userdata = userdata
|
83
|
+
self.protocol = protocol
|
84
|
+
self.message_handle = message_handle
|
85
|
+
self.broker_encrypt_key = broker_encrypt_key
|
86
|
+
|
87
|
+
self.client = mqtt.Client(CallbackAPIVersion.VERSION2, client_id=self.client_id, clean_session=self.clean_session,
|
88
|
+
userdata=self.userdata, protocol=self.protocol)
|
89
|
+
self.client.on_connect = self._on_connect
|
90
|
+
self.client.on_message = self._on_message_thread
|
91
|
+
self.client.on_disconnect = self._on_disconnect
|
92
|
+
if self.publish_topic:
|
93
|
+
will_msg = {
|
94
|
+
"event": "offline",
|
95
|
+
"client_id": self.client_id,
|
96
|
+
"trace_id": string_tools.get_uuid(),
|
97
|
+
"time": date_utils.get_date_time(),
|
98
|
+
"body": {"type": "will_msg"}
|
99
|
+
}
|
100
|
+
self.client.will_set(self.publish_topic, cjson.dumps(will_msg), 2, False)
|
101
|
+
self.connected = False
|
102
|
+
self._publish_trace_id = []
|
103
|
+
self._received_message: Dict[str: MQTTResponse] = {}
|
104
|
+
|
105
|
+
def _on_connect(self, client, userdata, flags, rc, properties):
|
106
|
+
if rc == 0:
|
107
|
+
log.info("mqtt服务连接成功")
|
108
|
+
self.connected = True
|
109
|
+
else:
|
110
|
+
log.info("mqtt服务连接失败, broker地址: %s:%s, rc: %s" % (self.broker_address, self.port, rc))
|
111
|
+
|
112
|
+
def _on_message_thread(self, client, userdata, message):
|
113
|
+
thread_pool.submit(self._on_message, client=client, userdata=userdata, message=message)
|
114
|
+
|
115
|
+
def _on_message(self, client, userdata, message):
|
116
|
+
# try:
|
117
|
+
if message.topic != self.publish_topic and message.payload:
|
118
|
+
mqtt_msg = cjson.loads(message.payload.decode())
|
119
|
+
trace_id = mqtt_msg.get('trace_id')
|
120
|
+
if trace_id in self._publish_trace_id:
|
121
|
+
mqtt_resp = MQTTResponse(**mqtt_msg)
|
122
|
+
self._received_message[trace_id] = mqtt_resp
|
123
|
+
self._publish_trace_id.remove(trace_id)
|
124
|
+
else:
|
125
|
+
mqtt_req = MQTTRequest(**mqtt_msg)
|
126
|
+
|
127
|
+
try:
|
128
|
+
if mqtt_req.event not in NO_LOG_EVENT:
|
129
|
+
log.debug("异步接收mqtt消息: %s" % str(mqtt_req))
|
130
|
+
if mqtt_req.body:
|
131
|
+
try:
|
132
|
+
mqtt_req.body = self.decrypt_body(mqtt_req.body)
|
133
|
+
except Exception:
|
134
|
+
if mqtt_req.event != MQTTEvent.HAND_SHARK:
|
135
|
+
log.error('解密消息失败: {}'.format(mqtt_req))
|
136
|
+
return
|
137
|
+
if isinstance(mqtt_req.body, dict):
|
138
|
+
mqtt_req.body = DictToObj(mqtt_req.body)
|
139
|
+
except Exception as e:
|
140
|
+
log.info("异步接收mqtt消息异常: %s" % e)
|
141
|
+
self.message_handle(mqtt_req, message.topic)
|
142
|
+
# except Exception as e:
|
143
|
+
# log.error("接收mqtt消息异常: %s" % e)
|
144
|
+
|
145
|
+
def _on_disconnect(self, client, userdata, flags, rc, properties):
|
146
|
+
self.connected = False
|
147
|
+
log.info("mqtt服务已断开连接, rc: %s" % rc)
|
148
|
+
|
149
|
+
def encrypt_body(self, body):
|
150
|
+
if self.broker_encrypt_key:
|
151
|
+
try:
|
152
|
+
return sm_tools.encrypt_with_sm4(self.broker_encrypt_key, cjson.dumps(body))
|
153
|
+
except Exception:
|
154
|
+
return sm_tools.encrypt_with_sm4(self.broker_encrypt_key, body)
|
155
|
+
|
156
|
+
def decrypt_body(self, body):
|
157
|
+
if self.broker_encrypt_key:
|
158
|
+
try:
|
159
|
+
return cjson.loads(sm_tools.decrypt_with_sm4(self.broker_encrypt_key, body))
|
160
|
+
except Exception:
|
161
|
+
sm_tools.decrypt_with_sm4(self.broker_encrypt_key, body)
|
162
|
+
|
163
|
+
def connect(self, username=None, password=None, timeout=10):
|
164
|
+
if username and password:
|
165
|
+
self.client.username_pw_set(username, password)
|
166
|
+
self.client.connect_async(host=self.broker_address, port=self.port, keepalive=self.keepalive)
|
167
|
+
self.client.loop_start()
|
168
|
+
|
169
|
+
start_time = time.time()
|
170
|
+
while not self.connected and time.time() - start_time < timeout:
|
171
|
+
time.sleep(0.1)
|
172
|
+
|
173
|
+
if not self.connected:
|
174
|
+
log.info("mqtt服务连接超时, broker地址: %s:%s" % (self.broker_address, self.port))
|
175
|
+
|
176
|
+
def publish_resp(self, mqtt_resp: MQTTResponse, topic=None, encrypt=True, qos=2, retain=False):
|
177
|
+
"""
|
178
|
+
发送mqtt响应信息
|
179
|
+
:param mqtt_resp: 响应信息, 使用 mqtt_service.init_resp('ok', req)
|
180
|
+
:param topic: 主题, 默认用订阅的主题
|
181
|
+
:param encrypt: 是否加密
|
182
|
+
:param qos: 消息质量
|
183
|
+
:param retain: 是否保留
|
184
|
+
"""
|
185
|
+
topic = topic if topic else self.publish_topic
|
186
|
+
if self.connected:
|
187
|
+
if mqtt_resp:
|
188
|
+
try:
|
189
|
+
if encrypt and mqtt_resp.body:
|
190
|
+
mqtt_resp.body = self.encrypt_body(mqtt_resp.body)
|
191
|
+
mqtt_resp.client_id = self.client_id
|
192
|
+
mqtt_data = cjson.dumps(mqtt_resp)
|
193
|
+
self.client.publish(topic, mqtt_data, qos=qos, retain=retain)
|
194
|
+
log.debug("异步发送mqtt响应消息: topic: %s message: %s" % (topic, mqtt_data))
|
195
|
+
except Exception as e:
|
196
|
+
log.error('异步发送mqtt响应消息异常: %s' % e)
|
197
|
+
else:
|
198
|
+
log.info("异步发送mqtt响应-mqtt服务未连接, 无法发送消息")
|
199
|
+
|
200
|
+
def publish_async(self, message, event: MQTTEvent = None, encrypt=True, topic=None, qos=2, retain=False):
|
201
|
+
topic = topic if topic else self.publish_topic
|
202
|
+
if self.connected:
|
203
|
+
try:
|
204
|
+
if encrypt and message:
|
205
|
+
message = self.encrypt_body(message)
|
206
|
+
|
207
|
+
mqtt_req = MQTTRequest()
|
208
|
+
mqtt_req.client_id = self.client_id
|
209
|
+
mqtt_req.body = message
|
210
|
+
if event:
|
211
|
+
mqtt_req.event = event.value
|
212
|
+
mqtt_data = cjson.dumps(mqtt_req)
|
213
|
+
self.client.publish(topic, mqtt_data, qos=qos, retain=retain)
|
214
|
+
if event not in NO_LOG_EVENT:
|
215
|
+
log.debug("异步发送mqtt消息: topic: %s message: %s" % (topic, mqtt_data))
|
216
|
+
except Exception as e:
|
217
|
+
log.error('异步发送mqtt消息异常: %s' % e)
|
218
|
+
else:
|
219
|
+
log.info("异步发送mqtt消息-mqtt服务未连接, 无法发送消息")
|
220
|
+
|
221
|
+
def publish_sync(self, message, event: MQTTEvent, topic=None, encrypt=True,
|
222
|
+
qos=2, retain=False, timeout: int = 15, send_time: str = None) -> MQTTResponse:
|
223
|
+
if not event: raise Exception("事件不能为空")
|
224
|
+
mqtt_resp = MQTTResponse()
|
225
|
+
mqtt_resp.status = 500
|
226
|
+
mqtt_resp.msg = "服务端未响应, 请联系管理员!"
|
227
|
+
topic = topic if topic else self.publish_topic
|
228
|
+
if self.connected:
|
229
|
+
try:
|
230
|
+
if encrypt and message:
|
231
|
+
message = self.encrypt_body(message)
|
232
|
+
|
233
|
+
mqtt_req = MQTTRequest()
|
234
|
+
mqtt_req.source = "client"
|
235
|
+
mqtt_req.client_id = self.client_id
|
236
|
+
mqtt_req.body = message
|
237
|
+
mqtt_req.time = send_time if send_time else mqtt_req.time
|
238
|
+
mqtt_req.event = event.value
|
239
|
+
mqtt_data = cjson.dumps(mqtt_req)
|
240
|
+
self._publish_trace_id.append(mqtt_req.trace_id)
|
241
|
+
self.client.publish(topic, mqtt_data, qos=qos, retain=retain)
|
242
|
+
if event not in NO_LOG_EVENT:
|
243
|
+
log.debug("同步发送mqtt消息: topic: %s message: %s" % (topic, mqtt_data))
|
244
|
+
is_timeout = False
|
245
|
+
start_time = time.time()
|
246
|
+
while True:
|
247
|
+
if mqtt_req.trace_id in self._received_message:
|
248
|
+
mqtt_resp = self._received_message.pop(mqtt_req.trace_id)
|
249
|
+
if mqtt_resp.body and encrypt:
|
250
|
+
mqtt_resp.body = self.decrypt_body(mqtt_resp.body)
|
251
|
+
break
|
252
|
+
if (time.time() - start_time) >= timeout:
|
253
|
+
is_timeout = True
|
254
|
+
break
|
255
|
+
time.sleep(0.5)
|
256
|
+
if not is_timeout:
|
257
|
+
if event not in NO_LOG_EVENT:
|
258
|
+
log.debug("同步接收mqtt消息: topic: %s message: %s" % (topic, mqtt_resp))
|
259
|
+
else:
|
260
|
+
log.info("同步发送mqtt消息, 等待返回信息超时: %s" % mqtt_req)
|
261
|
+
except Exception as e:
|
262
|
+
log.error('同步发送mqtt消息异常: %s-%s' % (event.value, e))
|
263
|
+
else:
|
264
|
+
log.info("同步接收mqtt消息-mqtt服务未连接, 无法发送消息")
|
265
|
+
return mqtt_resp
|
266
|
+
|
267
|
+
def subscribe(self, *topic, qos=0):
|
268
|
+
if self.connected:
|
269
|
+
if topic and len(topic) > 0:
|
270
|
+
for t in topic:
|
271
|
+
self.client.subscribe(t, qos=qos)
|
272
|
+
log.info("已订阅mqtt主题[%s]" % t)
|
273
|
+
else:
|
274
|
+
log.info("mqtt服务未连接, 无法订阅主题")
|
275
|
+
|
276
|
+
def unsubscribe(self, *topic):
|
277
|
+
if self.connected:
|
278
|
+
if topic and len(topic) > 0:
|
279
|
+
for t in topic:
|
280
|
+
self.client.unsubscribe(t)
|
281
|
+
log.info("已解除订阅mqtt主题[%s]" % t)
|
282
|
+
else:
|
283
|
+
log.info("mqtt服务未连接, 无法解除订阅主题")
|
284
|
+
|
285
|
+
def disconnect(self):
|
286
|
+
if self.connected:
|
287
|
+
self.client.disconnect()
|
288
|
+
self.client.loop_stop()
|
289
|
+
log.info("已关闭mqtt服务连接")
|
ctools/obj.py
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
def isNull(param) -> bool:
|
3
|
+
if type(param) == str:
|
4
|
+
return param == ''
|
5
|
+
elif type(param) == list:
|
6
|
+
return len(param) == 0
|
7
|
+
elif type(param) == dict:
|
8
|
+
return len(param) == 0
|
9
|
+
elif type(param) == int:
|
10
|
+
return param == 0
|
11
|
+
elif type(param) == float:
|
12
|
+
return param == 0.0
|
13
|
+
elif type(param) == bool:
|
14
|
+
return param == False
|
15
|
+
else:
|
16
|
+
return param is None
|
17
|
+
|
18
|
+
def isNotNull(param) -> bool:
|
19
|
+
return not isNull(param)
|
20
|
+
|
ctools/pacth.py
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
|
4
|
+
from sqlalchemy.sql import text
|
5
|
+
|
6
|
+
from ctools import database
|
7
|
+
|
8
|
+
|
9
|
+
class Patch:
|
10
|
+
|
11
|
+
def __init__(self, oldVersion, newVersion, pythonPath, playwrightPath, driverPath) -> None:
|
12
|
+
super().__init__()
|
13
|
+
self.oldV = oldVersion[len('V'):].replace('.', '').replace('-snapshot', '')
|
14
|
+
self.currentV = newVersion[len('V'):].replace('.', '').replace('-snapshot', '')
|
15
|
+
self.snapshot = '-snapshot' in newVersion or '-snapshot' in oldVersion
|
16
|
+
self.pythonPath = pythonPath
|
17
|
+
self.playwrightPath = playwrightPath
|
18
|
+
self.driverPath = driverPath
|
19
|
+
self.deleteSDK, self.deleteHPL, self.deleteDriver = False, False, False
|
20
|
+
|
21
|
+
def apply_patch(self):
|
22
|
+
patch_methods = [method for method in dir(self) if callable(getattr(self, method)) and method.startswith('patch_V')]
|
23
|
+
patch_methods.sort(key=lambda x: int(x[len('patch_V'):]))
|
24
|
+
max_method_name = patch_methods[-1]
|
25
|
+
exec_max_method = False
|
26
|
+
for method_name in patch_methods:
|
27
|
+
slVersion = method_name[len('patch_V'):]
|
28
|
+
if int(self.currentV) > int(slVersion) >= int(self.oldV):
|
29
|
+
if max_method_name == method_name: exec_max_method = True
|
30
|
+
method = getattr(self, method_name)
|
31
|
+
print('start exec patch {}'.format(method_name))
|
32
|
+
method()
|
33
|
+
print('patch {} update success'.format(method_name))
|
34
|
+
if self.snapshot and not exec_max_method:
|
35
|
+
print('start exec snapshot patch {}'.format(max_method_name))
|
36
|
+
method = getattr(self, max_method_name)
|
37
|
+
method()
|
38
|
+
print('snapshot patch {} update success'.format(max_method_name))
|
39
|
+
|
40
|
+
def patch_V220(self):
|
41
|
+
pass
|
42
|
+
|
43
|
+
def run_sqls(self, sqls):
|
44
|
+
with database.get_session() as s:
|
45
|
+
for sql in sqls.split(";"):
|
46
|
+
try:
|
47
|
+
s.execute(text(sql.strip()))
|
48
|
+
s.commit()
|
49
|
+
except Exception as e:
|
50
|
+
print('结构升级错误, 请检查!!! {}'.format(e.__cause__))
|
51
|
+
|
52
|
+
def remove_sdk(self):
|
53
|
+
if self.deleteSDK: return
|
54
|
+
try:
|
55
|
+
self.deleteSDK = True
|
56
|
+
if os.path.exists(self.pythonPath): shutil.rmtree(self.pythonPath)
|
57
|
+
except Exception as e:
|
58
|
+
print('删除SDK错误: {}'.format(e))
|
59
|
+
|
60
|
+
def remove_hpl(self):
|
61
|
+
if self.deleteHPL: return
|
62
|
+
try:
|
63
|
+
self.deleteHPL = True
|
64
|
+
if os.path.exists(self.playwrightPath): shutil.rmtree(self.playwrightPath)
|
65
|
+
except Exception as e:
|
66
|
+
print('删除HPL错误: {}'.format(e))
|
67
|
+
|
68
|
+
def remove_driver(self):
|
69
|
+
if self.deleteDriver: return
|
70
|
+
try:
|
71
|
+
self.deleteDriver = True
|
72
|
+
if os.path.exists(self.driverPath): shutil.rmtree(self.driverPath)
|
73
|
+
except Exception as e:
|
74
|
+
print('删除Driver错误: {}'.format(e))
|
@@ -0,0 +1,97 @@
|
|
1
|
+
import tkinter as tk
|
2
|
+
|
3
|
+
"""
|
4
|
+
规划区域工具类, 按回车获取区域坐标
|
5
|
+
image_path=screenshot_tools.screenshot()
|
6
|
+
"""
|
7
|
+
|
8
|
+
|
9
|
+
class PlanAreaTools:
|
10
|
+
def __init__(self):
|
11
|
+
self.root = tk.Tk()
|
12
|
+
self.root.overrideredirect(True)
|
13
|
+
self.root.attributes("-alpha", 0.1)
|
14
|
+
self.root.attributes('-topmost', 'true')
|
15
|
+
self.root.geometry("{0}x{1}+0+0".format(self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
|
16
|
+
self.root.configure(bg="black")
|
17
|
+
|
18
|
+
self.canvas = tk.Canvas(self.root)
|
19
|
+
|
20
|
+
self.x, self.y = 0, 0
|
21
|
+
self.xstart, self.ystart = 0, 0
|
22
|
+
self.xend, self.yend = 0, 0
|
23
|
+
self.xcenter, self.ycenter = 0, 0
|
24
|
+
|
25
|
+
self.select_area = self.canvas.create_rectangle(0, 0, 0, 0, outline='red', width=0, dash=(4, 4))
|
26
|
+
self.center_offset = (0, 0)
|
27
|
+
|
28
|
+
self.pos_div = tk.Toplevel(self.root)
|
29
|
+
self.pos_div.overrideredirect(True)
|
30
|
+
self.pos_div.attributes('-topmost', 'true')
|
31
|
+
self.pos_div.configure(bg="grey")
|
32
|
+
self.pos_div.geometry("270x25")
|
33
|
+
|
34
|
+
self.pos_div_text = "POS: (%s, %s), Enter键-获取区域, Esc键-退出"
|
35
|
+
self.pos_label = tk.Label(self.pos_div, text=self.pos_div_text % (0, 0), bg="black", fg="white")
|
36
|
+
self.pos_label.pack(fill="both", expand=True)
|
37
|
+
|
38
|
+
self.root.bind('<Escape>', self.sys_out) # 键盘Esc键->退出
|
39
|
+
self.root.bind('<Motion>', self.print_pos)
|
40
|
+
self.root.bind("<Button-1>", self.mouse_left_down) # 鼠标左键点击->显示子窗口
|
41
|
+
self.root.bind("<B1-Motion>", self.mouse_left_move) # 鼠标左键移动->改变子窗口大小
|
42
|
+
self.root.bind("<ButtonRelease-1>", self.mouse_left_up) # 鼠标左键释放->记录最后光标的位置
|
43
|
+
self.root.bind("<Return>", self.save_area) # 回车键->获取区域信息
|
44
|
+
self.root.focus()
|
45
|
+
|
46
|
+
self.area = [0, 0, 0, 0]
|
47
|
+
|
48
|
+
def start(self):
|
49
|
+
self.root.mainloop()
|
50
|
+
|
51
|
+
def print_pos(self, event):
|
52
|
+
self.x, self.y = event.widget.winfo_pointerxy()
|
53
|
+
self.pos_label.config(text=self.pos_div_text % (self.x, self.y))
|
54
|
+
self.pos_div.geometry(f"+{self.x + 10}+{self.y + 10}")
|
55
|
+
|
56
|
+
def mouse_left_down(self, event):
|
57
|
+
self.x, self.y = event.x, event.y
|
58
|
+
self.xstart, self.ystart = event.x, event.y
|
59
|
+
self.canvas.configure(height=1)
|
60
|
+
self.canvas.configure(width=1)
|
61
|
+
self.canvas.config(highlightthickness=0)
|
62
|
+
self.canvas.place(x=event.x, y=event.y)
|
63
|
+
|
64
|
+
def mouse_left_move(self, event):
|
65
|
+
self.x, self.y = event.x, event.y
|
66
|
+
self.xcenter = self.xstart + int((self.x-self.xstart)/2)-2
|
67
|
+
self.ycenter = self.ystart + int((self.y-self.ystart)/2)-2
|
68
|
+
|
69
|
+
self.pos_label.config(text=self.pos_div_text % (self.x, self.y))
|
70
|
+
self.pos_div.geometry(f"+{self.x + 10}+{self.y + 10}")
|
71
|
+
|
72
|
+
self.canvas.configure(height=event.y - self.ystart)
|
73
|
+
self.canvas.configure(width=event.x - self.xstart)
|
74
|
+
self.canvas.coords(self.select_area, 0, 0, event.x - self.xstart, event.y - self.ystart)
|
75
|
+
|
76
|
+
def mouse_left_up(self, event):
|
77
|
+
self.xend, self.yend = event.x, event.y
|
78
|
+
|
79
|
+
def save_area(self, event):
|
80
|
+
try:
|
81
|
+
self.area = [self.xstart, self.ystart, self.xend, self.yend]
|
82
|
+
self.canvas.delete(self.select_area)
|
83
|
+
self.canvas.place_forget()
|
84
|
+
self.sys_out()
|
85
|
+
|
86
|
+
except Exception:
|
87
|
+
pass
|
88
|
+
|
89
|
+
def sys_out(self, event=None):
|
90
|
+
self.pos_div.destroy()
|
91
|
+
self.root.destroy()
|
92
|
+
|
93
|
+
|
94
|
+
def get_area():
|
95
|
+
pat = PlanAreaTools()
|
96
|
+
pat.start()
|
97
|
+
return tuple(pat.area)
|
ctools/process_pool.py
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: UTF-8 -*-
|
3
|
+
__author__ = 'haoyang'
|
4
|
+
__date__ = '2024/9/20 12:00'
|
5
|
+
import os
|
6
|
+
import time
|
7
|
+
from concurrent.futures import ProcessPoolExecutor
|
8
|
+
|
9
|
+
from ctools import call
|
10
|
+
|
11
|
+
_process_pool: ProcessPoolExecutor = None
|
12
|
+
|
13
|
+
@call.init
|
14
|
+
def init():
|
15
|
+
global _process_pool
|
16
|
+
max_workers = min(32, os.cpu_count()) # 最多 32 个
|
17
|
+
_process_pool = ProcessPoolExecutor(max_workers=max_workers)
|
18
|
+
|
19
|
+
def cb(f, callback):
|
20
|
+
exc = f.exception()
|
21
|
+
if exc:
|
22
|
+
if callback: callback(exc)
|
23
|
+
raise exc
|
24
|
+
else:
|
25
|
+
if callback: callback(f.result())
|
26
|
+
|
27
|
+
def submit(func, *args, callback=None, **kwargs):
|
28
|
+
if _process_pool is None: raise Exception('process pool is not init')
|
29
|
+
future = _process_pool.submit(func, *args, **kwargs)
|
30
|
+
future.add_done_callback(lambda f: cb(f, callback))
|
31
|
+
time.sleep(0.01)
|
32
|
+
return future
|
33
|
+
|
34
|
+
def shutdown(wait=True):
|
35
|
+
if _process_pool is None: raise Exception('process pool is not init')
|
36
|
+
_process_pool.shutdown(wait=wait)
|