refactor(config): 重构配置模块并优化应用依赖注入

- 将配置相关类移动到model模块
- 实现依赖注入容器管理各组件依赖关系
- 重构配置加载逻辑支持多层级键值查找
- 更新主应用入口支持命令行参数解析
- 统一日志输出格式替换原有打印语句
- 引入钻井实时数据模型简化数据处理
- 移除硬编码字段映射改用动态配置方式
- 优化数据库写入逻辑基于新的数据模型
This commit is contained in:
2026-03-12 10:41:26 +08:00
parent 6d13da4cc2
commit 6557479a2f
16 changed files with 783 additions and 589 deletions

View File

@@ -1,5 +1,6 @@
import argparse import argparse
import json import json
import logging
import os import os
import time import time
from datetime import datetime from datetime import datetime
@@ -7,57 +8,11 @@ from urllib.parse import urlparse
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
from config.config import build_mock_dependencies from config import build_mock_dependencies
from model import DrillingRealtimeData
DATA_KEYS = [ logger = logging.getLogger(__name__)
"ts",
"wellid",
"stknum",
"recid",
"seqid",
"actual_date",
"actual_time",
"actcod",
"deptbitm",
"deptbitv",
"deptmeas",
"deptvert",
"blkpos",
"ropa",
"hkla",
"hklx",
"woba",
"wobx",
"torqa",
"torqx",
"rpma",
"sppa",
"chkp",
"spm1",
"spm2",
"spm3",
"tvolact",
"tvolcact",
"mfop",
"mfoa",
"mfia",
"mdoa",
"mdia",
"mtoa",
"mtia",
"mcoa",
"mcia",
"stkc",
"lagstks",
"deptretm",
"gasa",
"space1",
"space2",
"space3",
"space4",
"space5",
]
def parse_broker(broker): def parse_broker(broker):
@@ -81,16 +36,7 @@ def ensure_dir(path):
def build_server_payload(equipment_code): def build_server_payload(equipment_code):
data = {key: 0 for key in DATA_KEYS} return DrillingRealtimeData.empty(wellid=equipment_code).to_payload(equipment_code)
data["ts"] = int(time.time() * 1000)
data["wellid"] = ""
return {
"meta": {
"equipment_code": equipment_code,
"equipment_sn": equipment_code,
},
"data": data,
}
def parse_payload(raw_payload): def parse_payload(raw_payload):
@@ -146,67 +92,52 @@ def run_mock_service(args, deps):
tdengine_writer = deps.tdengine_writer tdengine_writer = deps.tdengine_writer
scheme, host, port = parse_broker(mqtt_config.broker) scheme, host, port = parse_broker(mqtt_config.broker)
print("MQTT mock config:") logger.info("MQTT mock config broker=%s://%s:%s client_id=%s mode=%s", scheme, host, port, mqtt_config.mock_client_id, args.mode)
print(f" broker: {scheme}://{host}:{port}") logger.info("Topics ingest=%s forward=%s ack=%s", mqtt_config.pub_topic, mqtt_config.sub_topic, mqtt_config.ack_topic)
print(f" client-id: {mqtt_config.mock_client_id}") logger.info("Data file=%s device_code=%s tdengine_enabled=%s", deps.data_file, tms_config.device_code, tdengine_writer.enabled)
print(f" pub-topic (ingest): {mqtt_config.pub_topic}")
print(f" sub-topic (forward): {mqtt_config.sub_topic}")
print(f" ack-topic: {mqtt_config.ack_topic}")
print(f" data-file: {deps.data_file}")
print(f" device-code: {tms_config.device_code}")
print(f" tdengine enabled: {tdengine_writer.enabled}")
if tdengine_writer.enabled: if tdengine_writer.enabled:
print(f" tdengine host: {tdengine_config.base_url}") logger.info("TDengine host=%s database=%s stable=%s pool_size=%s", tdengine_config.base_url, tdengine_config.database, tdengine_config.stable, tdengine_config.pool_size)
print(f" tdengine database: {tdengine_config.database}")
print(f" tdengine stable: {tdengine_config.stable}")
print(f" tdengine pool-size: {tdengine_config.pool_size}")
print(f" mode: {args.mode}")
client = mqtt.Client(client_id=mqtt_config.mock_client_id, clean_session=True) client = mqtt.Client(client_id=mqtt_config.mock_client_id, clean_session=True)
if mqtt_config.username is not None: if mqtt_config.username is not None:
client.username_pw_set(mqtt_config.username, mqtt_config.password) client.username_pw_set(mqtt_config.username, mqtt_config.password)
if scheme in ("ssl", "tls", "mqtts"): if scheme in ("ssl", "tls", "mqtts"):
client.tls_set() client.tls_set()
def on_connect(c, userdata, flags, rc): def on_connect(c, userdata, flags, rc):
if rc == 0: if rc == 0:
print("Connected") logger.info("Connected")
else: else:
print(f"Connect failed rc={rc}") logger.error("Connect failed rc=%s", rc)
return return
if args.mode in ("listen", "both") and mqtt_config.pub_topic: if args.mode in ("listen", "both") and mqtt_config.pub_topic:
c.subscribe(mqtt_config.pub_topic) c.subscribe(mqtt_config.pub_topic)
print(f"Subscribed: {mqtt_config.pub_topic}") logger.info("Subscribed %s", mqtt_config.pub_topic)
def on_disconnect(c, userdata, rc): def on_disconnect(c, userdata, rc):
print(f"Disconnected callback rc={rc}") logger.info("Disconnected callback rc=%s", rc)
def on_message(c, userdata, msg): def on_message(c, userdata, msg):
payload = msg.payload.decode("utf-8", errors="replace") payload = msg.payload.decode("utf-8", errors="replace")
print(f"RX {msg.topic}: {payload}") logger.info("RX topic=%s bytes=%s", msg.topic, len(msg.payload))
if msg.topic != mqtt_config.pub_topic: if msg.topic != mqtt_config.pub_topic:
return return
if deps.data_file: if deps.data_file:
write_message(deps.data_file, msg.topic, payload) write_message(deps.data_file, msg.topic, payload)
print(f"Wrote to file: {deps.data_file}") logger.info("Wrote file %s", deps.data_file)
if tdengine_writer.enabled: if tdengine_writer.enabled:
try: try:
tdengine_writer.write_payload(parse_payload(payload)) tdengine_writer.write_payload(parse_payload(payload))
print("Wrote to TDengine") logger.info("Wrote TDengine")
except Exception as exc: except Exception:
print(f"Write TDengine failed: {exc}") logger.exception("Write TDengine failed")
if mqtt_config.sub_topic: if mqtt_config.sub_topic:
c.publish(mqtt_config.sub_topic, payload) c.publish(mqtt_config.sub_topic, payload)
print(f"Forwarded to {mqtt_config.sub_topic}") logger.info("Forwarded %s", mqtt_config.sub_topic)
if mqtt_config.ack_topic: if mqtt_config.ack_topic:
ack_payload = build_ack_payload(msg.topic, payload) ack_payload = build_ack_payload(msg.topic, payload)
c.publish(mqtt_config.ack_topic, json.dumps(ack_payload, ensure_ascii=True)) c.publish(mqtt_config.ack_topic, json.dumps(ack_payload, ensure_ascii=True))
print(f"TX {mqtt_config.ack_topic}: {ack_payload}") logger.info("TX ack %s", mqtt_config.ack_topic)
client.on_connect = on_connect client.on_connect = on_connect
client.on_disconnect = on_disconnect client.on_disconnect = on_disconnect
@@ -217,14 +148,14 @@ def run_mock_service(args, deps):
try: try:
if args.mode in ("publish", "both"): if args.mode in ("publish", "both"):
if not mqtt_config.pub_topic: if not mqtt_config.pub_topic:
print("pub-topic is empty; nothing to publish") logger.warning("pub-topic is empty; nothing to publish")
else: else:
seq = 0 seq = 0
while True: while True:
seq += 1 seq += 1
payload = build_server_payload(tms_config.device_code) payload = build_server_payload(tms_config.device_code)
client.publish(mqtt_config.pub_topic, json.dumps(payload, ensure_ascii=True)) client.publish(mqtt_config.pub_topic, json.dumps(payload, ensure_ascii=True))
print(f"TX {mqtt_config.pub_topic}: {payload}") logger.info("TX %s #%s", mqtt_config.pub_topic, seq)
if args.count and seq >= args.count: if args.count and seq >= args.count:
break break
time.sleep(args.interval) time.sleep(args.interval)
@@ -232,21 +163,25 @@ def run_mock_service(args, deps):
while True: while True:
time.sleep(1) time.sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:
pass logger.info("Mock interrupted")
finally: finally:
client.loop_stop() client.loop_stop()
client.disconnect() client.disconnect()
print("Disconnected") logger.info("Mock stopped")
def main(): def add_arguments(parser):
ap = argparse.ArgumentParser(description="MQTT mock service") parser.add_argument("--config", default="config.yaml", help="Path to config yaml")
ap.add_argument("--config", default="config.yaml", help="Path to config yaml") parser.add_argument("--mode", choices=["publish", "listen", "both"], default="listen")
ap.add_argument("--mode", choices=["publish", "listen", "both"], default="listen") parser.add_argument("--interval", type=float, default=2.0, help="Publish interval (seconds)")
ap.add_argument("--interval", type=float, default=2.0, help="Publish interval (seconds)") parser.add_argument("--count", type=int, default=0, help="Publish count (0 = forever)")
ap.add_argument("--count", type=int, default=0, help="Publish count (0 = forever)") parser.add_argument("--data-file", default="", help="Override data-file in config")
ap.add_argument("--data-file", default="", help="Override data-file in config")
args = ap.parse_args()
def main(argv=None):
parser = argparse.ArgumentParser(description="MQTT mock service")
add_arguments(parser)
args = parser.parse_args(argv)
deps = build_mock_dependencies(args.config, data_file_override=args.data_file) deps = build_mock_dependencies(args.config, data_file_override=args.data_file)
run_mock_service(args, deps) run_mock_service(args, deps)

View File

@@ -1,63 +1,17 @@
import argparse import argparse
import json import json
import logging
import random import random
import time import time
from datetime import datetime
from urllib.parse import urlparse from urllib.parse import urlparse
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
from config.config import build_sender_dependencies from config import build_sender_dependencies
from model import DrillingRealtimeData
DATA_KEYS = [ logger = logging.getLogger(__name__)
"ts",
"wellid",
"stknum",
"recid",
"seqid",
"actual_date",
"actual_time",
"actcod",
"deptbitm",
"deptbitv",
"deptmeas",
"deptvert",
"blkpos",
"ropa",
"hkla",
"hklx",
"woba",
"wobx",
"torqa",
"torqx",
"rpma",
"sppa",
"chkp",
"spm1",
"spm2",
"spm3",
"tvolact",
"tvolcact",
"mfop",
"mfoa",
"mfia",
"mdoa",
"mdia",
"mtoa",
"mtia",
"mcoa",
"mcia",
"stkc",
"lagstks",
"deptretm",
"gasa",
"space1",
"space2",
"space3",
"space4",
"space5",
]
def parse_broker(broker): def parse_broker(broker):
@@ -76,61 +30,61 @@ def rand_int(a, b):
return random.randint(a, b) return random.randint(a, b)
def rand_float(a, b, digits=2):
return round(random.uniform(a, b), digits)
def build_random_payload(equipment_code): def build_random_payload(equipment_code):
data = {key: 0 for key in DATA_KEYS} entity = DrillingRealtimeData.empty(wellid=equipment_code)
data["ts"] = int(time.time() * 1000) entity = DrillingRealtimeData(
data["wellid"] = random.choice(["", f"WELL-{rand_int(1, 9999):04d}"]) ts=entity.ts,
data["stknum"] = rand_int(0, 500) wellid=entity.wellid,
data["recid"] = rand_int(0, 100000) stknum=rand_int(0, 500),
data["seqid"] = rand_int(0, 100000) recid=rand_int(0, 100000),
data["actual_date"] = int(datetime.utcnow().strftime("%Y%m%d")) seqid=rand_int(0, 100000),
data["actual_time"] = int(datetime.utcnow().strftime("%H%M%S")) actual_date=entity.actual_date,
data["actcod"] = rand_int(0, 9) actual_time=entity.actual_time,
data["deptbitm"] = rand_int(0, 5000) actcod=rand_int(0, 9),
data["deptbitv"] = rand_int(0, 5000) deptbitm=rand_float(0, 5000),
data["deptmeas"] = rand_int(0, 5000) deptbitv=rand_float(0, 5000),
data["deptvert"] = rand_int(0, 5000) deptmeas=rand_float(0, 5000),
data["blkpos"] = rand_int(0, 100) deptvert=rand_float(0, 5000),
data["ropa"] = rand_int(0, 200) blkpos=rand_float(0, 100),
data["hkla"] = rand_int(0, 500) ropa=rand_float(0, 200),
data["hklx"] = rand_int(0, 500) hkla=rand_float(0, 500),
data["woba"] = rand_int(0, 200) hklx=rand_float(0, 500),
data["wobx"] = rand_int(0, 200) woba=rand_float(0, 200),
data["torqa"] = rand_int(0, 200) wobx=rand_float(0, 200),
data["torqx"] = rand_int(0, 200) torqa=rand_float(0, 200),
data["rpma"] = rand_int(0, 300) torqx=rand_float(0, 200),
data["sppa"] = rand_int(0, 5000) rpma=rand_int(0, 300),
data["chkp"] = rand_int(0, 5000) sppa=rand_float(0, 5000),
data["spm1"] = rand_int(0, 200) chkp=rand_float(0, 5000),
data["spm2"] = rand_int(0, 200) spm1=rand_int(0, 200),
data["spm3"] = rand_int(0, 200) spm2=rand_int(0, 200),
data["tvolact"] = rand_int(0, 20000) spm3=rand_int(0, 200),
data["tvolcact"] = rand_int(0, 20000) tvolact=rand_float(0, 20000),
data["mfop"] = rand_int(0, 1000) tvolcact=rand_float(0, 20000),
data["mfoa"] = rand_int(0, 1000) mfop=rand_int(0, 1000),
data["mfia"] = rand_int(0, 1000) mfoa=rand_float(0, 1000),
data["mdoa"] = rand_int(0, 1000) mfia=rand_float(0, 1000),
data["mdia"] = rand_int(0, 1000) mdoa=rand_float(0, 1000),
data["mtoa"] = rand_int(0, 1000) mdia=rand_float(0, 1000),
data["mtia"] = rand_int(0, 1000) mtoa=rand_float(0, 1000),
data["mcoa"] = rand_int(0, 1000) mtia=rand_float(0, 1000),
data["mcia"] = rand_int(0, 1000) mcoa=rand_float(0, 1000),
data["stkc"] = rand_int(0, 200) mcia=rand_float(0, 1000),
data["lagstks"] = rand_int(0, 200) stkc=rand_int(0, 200),
data["deptretm"] = rand_int(0, 5000) lagstks=rand_int(0, 200),
data["gasa"] = rand_int(0, 100) deptretm=rand_float(0, 5000),
data["space1"] = rand_int(0, 10) gasa=rand_float(0, 100),
data["space2"] = rand_int(0, 10) space1=rand_float(0, 10),
data["space3"] = rand_int(0, 10) space2=rand_float(0, 10),
data["space4"] = rand_int(0, 10) space3=rand_float(0, 10),
data["space5"] = rand_int(0, 10) space4=rand_float(0, 10),
return { space5=rand_float(0, 10),
"meta": { )
"equipment_code": equipment_code, return entity.to_payload(equipment_code)
"equipment_sn": equipment_code,
},
"data": data,
}
def run_sender(args, deps): def run_sender(args, deps):
@@ -138,21 +92,16 @@ def run_sender(args, deps):
tms_config = deps.config.tms tms_config = deps.config.tms
scheme, host, port = parse_broker(mqtt_config.broker) scheme, host, port = parse_broker(mqtt_config.broker)
print("MQTT sender config:") logger.info("MQTT sender config broker=%s://%s:%s client_id=%s pub_topic=%s interval=%ss", scheme, host, port, mqtt_config.sender_client_id, mqtt_config.pub_topic, args.interval)
print(f" broker: {scheme}://{host}:{port}")
print(f" client-id: {mqtt_config.sender_client_id}")
print(f" pub-topic: {mqtt_config.pub_topic}")
print(f" interval: {args.interval}s")
client = mqtt.Client(client_id=mqtt_config.sender_client_id, clean_session=True) client = mqtt.Client(client_id=mqtt_config.sender_client_id, clean_session=True)
if mqtt_config.username is not None: if mqtt_config.username is not None:
client.username_pw_set(mqtt_config.username, mqtt_config.password) client.username_pw_set(mqtt_config.username, mqtt_config.password)
if scheme in ("ssl", "tls", "mqtts"): if scheme in ("ssl", "tls", "mqtts"):
client.tls_set() client.tls_set()
def on_disconnect(c, userdata, rc): def on_disconnect(c, userdata, rc):
print(f"Disconnected callback rc={rc}") logger.info("Disconnected callback rc=%s", rc)
client.on_disconnect = on_disconnect client.on_disconnect = on_disconnect
client.connect(host, port, keepalive=tms_config.keepalive) client.connect(host, port, keepalive=tms_config.keepalive)
@@ -160,31 +109,35 @@ def run_sender(args, deps):
try: try:
if not mqtt_config.pub_topic: if not mqtt_config.pub_topic:
print("pub-topic is empty; nothing to publish") logger.warning("pub-topic is empty; nothing to publish")
return return
seq = 0 seq = 0
while True: while True:
seq += 1 seq += 1
payload = build_random_payload(tms_config.device_code) payload = build_random_payload(tms_config.device_code)
client.publish(mqtt_config.pub_topic, json.dumps(payload, ensure_ascii=True)) client.publish(mqtt_config.pub_topic, json.dumps(payload, ensure_ascii=True))
print(f"TX {mqtt_config.pub_topic}: {payload}") logger.info("TX %s #%s", mqtt_config.pub_topic, seq)
if args.count and seq >= args.count: if args.count and seq >= args.count:
break break
time.sleep(args.interval) time.sleep(args.interval)
except KeyboardInterrupt: except KeyboardInterrupt:
pass logger.info("Sender interrupted")
finally: finally:
client.loop_stop() client.loop_stop()
client.disconnect() client.disconnect()
print("Disconnected") logger.info("Sender stopped")
def main(): def add_arguments(parser):
ap = argparse.ArgumentParser(description="MQTT random data sender") parser.add_argument("--config", default="config.yaml", help="Path to config yaml")
ap.add_argument("--config", default="config.yaml", help="Path to config yaml") parser.add_argument("--interval", type=float, default=3.0, help="Publish interval (seconds)")
ap.add_argument("--interval", type=float, default=3.0, help="Publish interval (seconds)") parser.add_argument("--count", type=int, default=0, help="Publish count (0 = forever)")
ap.add_argument("--count", type=int, default=0, help="Publish count (0 = forever)")
args = ap.parse_args()
def main(argv=None):
parser = argparse.ArgumentParser(description="MQTT random data sender")
add_arguments(parser)
args = parser.parse_args(argv)
deps = build_sender_dependencies(args.config) deps = build_sender_dependencies(args.config)
run_sender(args, deps) run_sender(args, deps)

View File

@@ -1,12 +1,16 @@
import argparse import argparse
import json import json
import logging
import time import time
from datetime import datetime from datetime import datetime
from urllib.parse import urlparse from urllib.parse import urlparse
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
from config.config import build_subscriber_dependencies from config import build_subscriber_dependencies
logger = logging.getLogger(__name__)
def parse_broker(broker): def parse_broker(broker):
@@ -22,12 +26,12 @@ def parse_broker(broker):
def print_flat_fields(title, value): def print_flat_fields(title, value):
print(title) logger.info(title)
if isinstance(value, dict): if isinstance(value, dict):
for k, v in value.items(): for key, val in value.items():
print(f" {k}: {v}") logger.info(" %s: %s", key, val)
else: else:
print(f" {value}") logger.info(" %s", value)
def run_subscriber(args, deps): def run_subscriber(args, deps):
@@ -38,49 +42,36 @@ def run_subscriber(args, deps):
raise ValueError("No topic to subscribe. Set sub-topic or pub-topic, or pass --topic") raise ValueError("No topic to subscribe. Set sub-topic or pub-topic, or pass --topic")
scheme, host, port = parse_broker(mqtt_config.broker) scheme, host, port = parse_broker(mqtt_config.broker)
logger.info("MQTT subscriber config broker=%s://%s:%s client_id=%s topic=%s", scheme, host, port, mqtt_config.subscriber_client_id, topic)
print("MQTT subscriber config:")
print(f" broker: {scheme}://{host}:{port}")
print(f" client-id: {mqtt_config.subscriber_client_id}")
print(f" topic: {topic}")
client = mqtt.Client(client_id=mqtt_config.subscriber_client_id, clean_session=True) client = mqtt.Client(client_id=mqtt_config.subscriber_client_id, clean_session=True)
if mqtt_config.username is not None: if mqtt_config.username is not None:
client.username_pw_set(mqtt_config.username, mqtt_config.password) client.username_pw_set(mqtt_config.username, mqtt_config.password)
if scheme in ("ssl", "tls", "mqtts"): if scheme in ("ssl", "tls", "mqtts"):
client.tls_set() client.tls_set()
def on_connect(c, userdata, flags, rc): def on_connect(c, userdata, flags, rc):
if rc == 0: if rc == 0:
print("Connected") logger.info("Connected")
c.subscribe(topic) c.subscribe(topic)
print(f"Subscribed: {topic}") logger.info("Subscribed %s", topic)
else: else:
print(f"Connect failed rc={rc}") logger.error("Connect failed rc=%s", rc)
def on_disconnect(c, userdata, rc): def on_disconnect(c, userdata, rc):
print(f"Disconnected callback rc={rc}") logger.info("Disconnected callback rc=%s", rc)
def on_message(c, userdata, msg): def on_message(c, userdata, msg):
raw_payload = msg.payload.decode("utf-8", errors="replace") raw_payload = msg.payload.decode("utf-8", errors="replace")
print("=" * 80) logger.info("RX time=%s topic=%s qos=%s retain=%s bytes=%s", datetime.now().strftime('%Y-%m-%d %H:%M:%S'), msg.topic, msg.qos, msg.retain, len(msg.payload))
print(f"RX Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Topic: {msg.topic}")
print(f"QoS: {msg.qos}, Retain: {msg.retain}, Bytes: {len(msg.payload)}")
try: try:
obj = json.loads(raw_payload) obj = json.loads(raw_payload)
print("Raw JSON:") logger.info("Raw JSON: %s", json.dumps(obj, ensure_ascii=False))
print(json.dumps(obj, ensure_ascii=False, indent=2))
if isinstance(obj, dict): if isinstance(obj, dict):
print_flat_fields("meta:", obj.get("meta")) print_flat_fields("meta:", obj.get("meta"))
print_flat_fields("data:", obj.get("data")) print_flat_fields("data:", obj.get("data"))
extra_keys = [key for key in obj.keys() if key not in ("meta", "data")]
for key in extra_keys:
print_flat_fields(f"{key}:", obj.get(key))
except Exception: except Exception:
print("Raw payload:") logger.info("Raw payload: %s", raw_payload)
print(raw_payload)
client.on_connect = on_connect client.on_connect = on_connect
client.on_disconnect = on_disconnect client.on_disconnect = on_disconnect
@@ -92,18 +83,22 @@ def run_subscriber(args, deps):
while True: while True:
time.sleep(1) time.sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:
pass logger.info("Subscriber interrupted")
finally: finally:
client.loop_stop() client.loop_stop()
client.disconnect() client.disconnect()
print("Disconnected") logger.info("Subscriber stopped")
def main(): def add_arguments(parser):
ap = argparse.ArgumentParser(description="MQTT subscriber") parser.add_argument("--config", default="config.yaml", help="Path to config yaml")
ap.add_argument("--config", default="config.yaml", help="Path to config yaml") parser.add_argument("--topic", default="", help="Override topic to subscribe")
ap.add_argument("--topic", default="", help="Override topic to subscribe")
args = ap.parse_args()
def main(argv=None):
parser = argparse.ArgumentParser(description="MQTT subscriber")
add_arguments(parser)
args = parser.parse_args(argv)
deps = build_subscriber_dependencies(args.config) deps = build_subscriber_dependencies(args.config)
run_subscriber(args, deps) run_subscriber(args, deps)

View File

@@ -1,119 +1,20 @@
import argparse import argparse
import logging
import random import random
import socket import socket
import time import time
from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from config.dependencies import build_wits_sender_dependencies from config import build_wits_sender_dependencies
from model import WITS_FIELD_MAPPING, WitsData
logger = logging.getLogger(__name__)
BEGIN_MARK = "&&\r\n" BEGIN_MARK = "&&\r\n"
END_MARK = "!!\r\n" END_MARK = "!!\r\n"
@dataclass(frozen=True)
class WitsData:
ts: int
wellid: str
stknum: int
recid: int
seqid: int
actual_date: float
actual_time: float
actual_ts: int
actcod: int
actod_label: str
deptbitm: float
deptbitv: float
deptmeas: float
deptvert: float
blkpos: float
ropa: float
hkla: float
hklx: float
woba: float
wobx: float
torqa: float
torqx: float
rpma: int
sppa: float
chkp: float
spm1: int
spm2: int
spm3: int
tvolact: float
tvolcact: float
mfop: int
mfoa: float
mfia: float
mdoa: float
mdia: float
mtoa: float
mtia: float
mcoa: float
mcia: float
stkc: int
lagstks: int
deptretm: float
gasa: float
space1: float
space2: float
space3: float
space4: float
space5: float
WITS_FIELD_MAPPING = [
(1, "wellid", "string"),
(2, "stknum", "int"),
(3, "recid", "int"),
(4, "seqid", "int"),
(5, "actual_date", "float"),
(6, "actual_time", "float"),
(7, "actcod", "int"),
(8, "deptbitm", "float"),
(9, "deptbitv", "float"),
(10, "deptmeas", "float"),
(11, "deptvert", "float"),
(12, "blkpos", "float"),
(13, "ropa", "float"),
(14, "hkla", "float"),
(15, "hklx", "float"),
(16, "woba", "float"),
(17, "wobx", "float"),
(18, "torqa", "float"),
(19, "torqx", "float"),
(20, "rpma", "int"),
(21, "sppa", "float"),
(22, "chkp", "float"),
(23, "spm1", "int"),
(24, "spm2", "int"),
(25, "spm3", "int"),
(26, "tvolact", "float"),
(27, "tvolcact", "float"),
(28, "mfop", "int"),
(29, "mfoa", "float"),
(30, "mfia", "float"),
(31, "mdoa", "float"),
(32, "mdia", "float"),
(33, "mtoa", "float"),
(34, "mtia", "float"),
(35, "mcoa", "float"),
(36, "mcia", "float"),
(37, "stkc", "int"),
(38, "lagstks", "int"),
(39, "deptretm", "float"),
(40, "gasa", "float"),
(41, "space1", "float"),
(42, "space2", "float"),
(43, "space3", "float"),
(44, "space4", "float"),
(45, "space5", "float"),
]
def rand_int(a, b): def rand_int(a, b):
return random.randint(a, b) return random.randint(a, b)
@@ -219,17 +120,10 @@ def run_wits_sender(args, deps):
host = args.host or wits_config.host host = args.host or wits_config.host
port = args.port or wits_config.port port = args.port or wits_config.port
timeout = args.timeout or wits_config.timeout timeout = args.timeout or wits_config.timeout
if not host or not port: if not host or not port:
raise ValueError("WITS target host/port is empty. Configure wits.host/wits.port or tms.server-ip/tms.server-port") raise ValueError("WITS target host/port is empty. Configure wits.host/wits.port or tms.server-ip/tms.server-port")
print("WITS sender config:") logger.info("WITS sender config host=%s port=%s timeout=%ss source_file=%s interval=%ss count=%s", host, port, timeout, source_file or "(generated)", args.interval, args.count or "forever")
print(f" host: {host}")
print(f" port: {port}")
print(f" timeout: {timeout}s")
print(f" source-file: {source_file or '(generated)'}")
print(f" interval: {args.interval}s")
print(f" count: {args.count or 'forever'}")
seq = 0 seq = 0
try: try:
@@ -240,29 +134,33 @@ def run_wits_sender(args, deps):
else: else:
packet = build_wits_packet(build_random_wits_data(device_code)) packet = build_wits_packet(build_random_wits_data(device_code))
send_packet(host, port, timeout, packet) send_packet(host, port, timeout, packet)
print(f"TX WITS #{seq} -> {host}:{port}") logger.info("TX WITS #%s -> %s:%s", seq, host, port)
print(packet) if logger.isEnabledFor(logging.DEBUG):
logger.debug("WITS packet:\n%s", packet)
if args.count and seq >= args.count: if args.count and seq >= args.count:
break break
time.sleep(args.interval) time.sleep(args.interval)
except KeyboardInterrupt: except KeyboardInterrupt:
pass logger.info("WITS sender interrupted")
def main(): def add_arguments(parser):
ap = argparse.ArgumentParser(description="WITS TCP sender") parser.add_argument("--config", default="config.yaml", help="Path to config yaml")
ap.add_argument("--config", default="config.yaml", help="Path to config yaml") parser.add_argument("--host", default="", help="Override target host")
ap.add_argument("--host", default="", help="Override target host") parser.add_argument("--port", type=int, default=0, help="Override target port")
ap.add_argument("--port", type=int, default=0, help="Override target port") parser.add_argument("--timeout", type=int, default=0, help="Override socket timeout")
ap.add_argument("--timeout", type=int, default=0, help="Override socket timeout") parser.add_argument("--source-file", default="", help="Send raw WITS packet from file")
ap.add_argument("--source-file", default="", help="Send raw WITS packet from file") parser.add_argument("--interval", type=float, default=3.0, help="Send interval in seconds")
ap.add_argument("--interval", type=float, default=3.0, help="Send interval in seconds") parser.add_argument("--count", type=int, default=1, help="Send count (0 = forever)")
ap.add_argument("--count", type=int, default=1, help="Send count (0 = forever)")
args = ap.parse_args()
def main(argv=None):
parser = argparse.ArgumentParser(description="WITS TCP sender")
add_arguments(parser)
args = parser.parse_args(argv)
deps = build_wits_sender_dependencies(args.config) deps = build_wits_sender_dependencies(args.config)
run_wits_sender(args, deps) run_wits_sender(args, deps)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -1,129 +1,24 @@
from dataclasses import dataclass from config.config import get_value, load
from config.dependencies import (
import yaml MockDependencies,
SenderDependencies,
SubscriberDependencies,
@dataclass(frozen=True) WitsSenderDependencies,
class MqttConfig: build_mock_dependencies,
broker: str build_sender_dependencies,
client_id: str build_subscriber_dependencies,
mock_client_id: str build_wits_sender_dependencies,
sender_client_id: str )
subscriber_client_id: str
username: str | None
password: str | None
pub_topic: str | None
sub_topic: str | None
ack_topic: str | None
data_file: str
@dataclass(frozen=True)
class TmsConfig:
device_code: str
equipment_sn: str
timeout: int
keepalive: int
server_ip: str | None
server_port: int | None
@dataclass(frozen=True)
class WitsConfig:
host: str
port: int
timeout: int
source_file: str
@dataclass(frozen=True)
class AppConfig:
mqtt: MqttConfig
tms: TmsConfig
wits: WitsConfig
raw: dict
def load_raw_config(path):
with open(path, "r", encoding="utf-8") as f:
return yaml.safe_load(f) or {}
def get_value(cfg, *paths, default=None):
for path in paths:
current = cfg
found = True
for key in path:
if not isinstance(current, dict) or key not in current:
found = False
break
current = current[key]
if found and current is not None:
return current
return default
def load_app_config(path):
raw = load_raw_config(path)
base_client_id = get_value(raw, ("mqtt", "client-id"), ("client-id",), default="mqtt")
device_code = get_value(
raw,
("tms", "device-code"),
("tms", "equipment-sn"),
("device-code",),
("equipment-sn",),
default="GJ-304-0088",
)
equipment_sn = get_value(
raw,
("tms", "equipment-sn"),
("tms", "device-code"),
("equipment-sn",),
("device-code",),
default=device_code,
)
return AppConfig(
mqtt=MqttConfig(
broker=get_value(raw, ("mqtt", "broker"), ("broker",), default=""),
client_id=base_client_id,
mock_client_id=get_value(raw, ("mqtt", "mock-client-id"), ("mock-client-id",), default=f"{base_client_id}-mock"),
sender_client_id=get_value(raw, ("mqtt", "sender-client-id"), ("sender-client-id",), default=f"{base_client_id}-sender"),
subscriber_client_id=get_value(raw, ("mqtt", "subscriber-client-id"), ("subscriber-client-id",), default=f"{base_client_id}-subscriber"),
username=get_value(raw, ("mqtt", "username"), ("username",)),
password=get_value(raw, ("mqtt", "password"), ("password",)),
pub_topic=get_value(raw, ("mqtt", "pub-topic"), ("pub-topic",)),
sub_topic=get_value(raw, ("mqtt", "sub-topic"), ("sub-topic",)),
ack_topic=get_value(raw, ("mqtt", "ack-topic"), ("ack-topic",)),
data_file=get_value(raw, ("mqtt", "data-file"), ("data-file",), default=""),
),
tms=TmsConfig(
device_code=device_code,
equipment_sn=equipment_sn,
timeout=int(get_value(raw, ("tms", "timeout"), ("timeout",), default=10)),
keepalive=int(get_value(raw, ("tms", "keepalive"), ("keepalive",), default=20)),
server_ip=get_value(raw, ("tms", "server-ip"), ("server-ip",)),
server_port=get_value(raw, ("tms", "server-port"), ("server-port",)),
),
wits=WitsConfig(
host=get_value(raw, ("wits", "host"), ("tms", "server-ip"), ("server-ip",), default=""),
port=int(get_value(raw, ("wits", "port"), ("tms", "server-port"), ("server-port",), default=0)),
timeout=int(get_value(raw, ("wits", "timeout"), ("tms", "timeout"), ("timeout",), default=10)),
source_file=get_value(raw, ("wits", "source-file"), default=""),
),
raw=raw,
)
load_config = load_app_config
__all__ = [ __all__ = [
"AppConfig", "MockDependencies",
"MqttConfig", "SenderDependencies",
"TmsConfig", "SubscriberDependencies",
"WitsConfig", "WitsSenderDependencies",
"build_mock_dependencies",
"build_sender_dependencies",
"build_subscriber_dependencies",
"build_wits_sender_dependencies",
"get_value", "get_value",
"load_app_config", "load",
"load_config",
"load_raw_config",
] ]

View File

@@ -1,18 +1,83 @@
import yaml import yaml
from config.model import * from model import AppConfig, MqttConfig, TdengineConfig, TmsConfig, WitsConfig
def load(path: str) -> "Config": def get_value(cfg, *paths, default=None):
for path in paths:
current = cfg
found = True
for key in path:
if not isinstance(current, dict) or key not in current:
found = False
break
current = current[key]
if found and current is not None:
return current
return default
def load(path: str) -> AppConfig:
with open(path, "r", encoding="utf-8") as f: with open(path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f) raw = yaml.safe_load(f) or {}
mqtt_cfg = MqttConfig(**data["mqtt"]) base_client_id = get_value(raw, ("mqtt", "client-id"), ("client-id",), default="mqtt")
tms_cfg = TmsConfig(**data["tms"]) device_code = get_value(
tdengine_cfg = TdengineConfig(**data["tdengine"]) raw,
("tms", "device-code"),
return Config( ("tms", "equipment-sn"),
mqtt=mqtt_cfg, ("device-code",),
tms=tms_cfg, ("equipment-sn",),
tdengine=tdengine_cfg default="GJ-304-0088",
)
equipment_sn = get_value(
raw,
("tms", "equipment-sn"),
("tms", "device-code"),
("equipment-sn",),
("device-code",),
default=device_code,
)
td_url = get_value(raw, ("tdengine", "url"), ("tdengine-url",), default="")
td_database = get_value(raw, ("tdengine", "database"), ("tdengine-database",), default="")
return AppConfig(
mqtt=MqttConfig(
broker=get_value(raw, ("mqtt", "broker"), ("broker",), default=""),
client_id=base_client_id,
mock_client_id=get_value(raw, ("mqtt", "mock-client-id"), ("mock-client-id",), default=f"{base_client_id}-mock"),
sender_client_id=get_value(raw, ("mqtt", "sender-client-id"), ("sender-client-id",), default=f"{base_client_id}-sender"),
subscriber_client_id=get_value(raw, ("mqtt", "subscriber-client-id"), ("subscriber-client-id",), default=f"{base_client_id}-subscriber"),
username=get_value(raw, ("mqtt", "username"), ("username",)),
password=get_value(raw, ("mqtt", "password"), ("password",)),
pub_topic=get_value(raw, ("mqtt", "pub-topic"), ("pub-topic",)),
sub_topic=get_value(raw, ("mqtt", "sub-topic"), ("sub-topic",)),
ack_topic=get_value(raw, ("mqtt", "ack-topic"), ("ack-topic",)),
data_file=get_value(raw, ("mqtt", "data-file"), ("data-file",), default=""),
),
tms=TmsConfig(
device_code=device_code,
equipment_sn=equipment_sn,
timeout=int(get_value(raw, ("tms", "timeout"), ("timeout",), default=10)),
keepalive=int(get_value(raw, ("tms", "keepalive"), ("keepalive",), default=20)),
server_ip=get_value(raw, ("tms", "server-ip"), ("server-ip",)),
server_port=get_value(raw, ("tms", "server-port"), ("server-port",)),
),
wits=WitsConfig(
host=get_value(raw, ("wits", "host"), ("tms", "server-ip"), ("server-ip",), default=""),
port=int(get_value(raw, ("wits", "port"), ("tms", "server-port"), ("server-port",), default=0)),
timeout=int(get_value(raw, ("wits", "timeout"), ("tms", "timeout"), ("timeout",), default=10)),
source_file=get_value(raw, ("wits", "source-file"), default=""),
),
tdengine=TdengineConfig(
url=td_url,
username=get_value(raw, ("tdengine", "username"), ("tdengine-username",), default=""),
password=get_value(raw, ("tdengine", "password"), ("tdengine-password",), default=""),
database=td_database,
stable=get_value(raw, ("tdengine", "stable"), ("tdengine-stable",), default="drilling_realtime_st"),
device_code=get_value(raw, ("tdengine", "device-code"), ("tdengine", "equipment-sn"), default=device_code),
pool_size=int(get_value(raw, ("tdengine", "pool-size"), ("tdengine-pool-size",), default=2)),
timeout=int(get_value(raw, ("tdengine", "timeout"), ("tdengine-timeout",), default=10)),
),
raw=raw,
) )

51
config/dependencies.py Normal file
View File

@@ -0,0 +1,51 @@
from dataclasses import dataclass
from config.config import load
from db import TDengineWriter, load_tdengine_config
from model import AppConfig
@dataclass(frozen=True)
class SenderDependencies:
config: AppConfig
@dataclass(frozen=True)
class SubscriberDependencies:
config: AppConfig
@dataclass(frozen=True)
class WitsSenderDependencies:
config: AppConfig
@dataclass(frozen=True)
class MockDependencies:
config: AppConfig
tdengine_config: object
tdengine_writer: object
data_file: str
def build_sender_dependencies(config_path):
return SenderDependencies(config=load(config_path))
def build_subscriber_dependencies(config_path):
return SubscriberDependencies(config=load(config_path))
def build_wits_sender_dependencies(config_path):
return WitsSenderDependencies(config=load(config_path))
def build_mock_dependencies(config_path, data_file_override=""):
app_config = load(config_path)
tdengine_config = load_tdengine_config(app_config, default_device_code=app_config.tms.device_code)
return MockDependencies(
config=app_config,
tdengine_config=tdengine_config,
tdengine_writer=TDengineWriter(tdengine_config),
data_file=data_file_override or app_config.mqtt.data_file,
)

View File

@@ -1,40 +1,12 @@
from dataclasses import dataclass from model import AppConfig, MqttConfig, TdengineConfig, TmsConfig, WitsConfig
Config = AppConfig
@dataclass __all__ = [
class MqttConfig: "Config",
broker: str "AppConfig",
client_id: str "MqttConfig",
mock_client_id: str "TdengineConfig",
sender_client_id: str "TmsConfig",
subscriber_client_id: str "WitsConfig",
username: str ]
password: str
pub_topic: str
@dataclass
class TmsConfig:
device_code: str
equipment_sn: str
timeout: int
keepalive: int
server_ip: str
server_port: int
@dataclass
class TdengineConfig:
url: str
username: str
password: str
database: str
stable: str
device_code: str
@dataclass
class Config:
mqtt: MqttConfig
tms: TmsConfig
tdengine: TdengineConfig

41
db/config.py Normal file
View File

@@ -0,0 +1,41 @@
from urllib.parse import urlparse
from model import TdengineConfig
def parse_taos_url(jdbc_url):
if not jdbc_url:
return "", ""
raw = str(jdbc_url).strip()
if raw.lower().startswith("jdbc:taos-rs://"):
raw = "http://" + raw[len("jdbc:TAOS-RS://") :]
elif "://" not in raw:
raw = "http://" + raw
parsed = urlparse(raw)
base_url = f"{parsed.scheme or 'http'}://{parsed.hostname or '127.0.0.1'}:{parsed.port or 6041}"
database = (parsed.path or "").strip("/")
return base_url, database
def load_tdengine_config(cfg_or_app, default_device_code="GJ-304-0088"):
config = getattr(cfg_or_app, "tdengine", None)
if isinstance(config, TdengineConfig):
url = config.url
_, db_from_url = parse_taos_url(url)
if config.database:
return config
return TdengineConfig(
url=config.url,
username=config.username,
password=config.password,
database=db_from_url,
stable=config.stable,
device_code=config.device_code or default_device_code,
pool_size=config.pool_size,
timeout=config.timeout,
)
_, db_from_url = parse_taos_url("")
return TdengineConfig(database=db_from_url, device_code=default_device_code)
TDengineConfig = TdengineConfig

View File

@@ -1,5 +1,6 @@
import re import re
import time
from model import DrillingRealtimeData
DB_COLUMNS = [ DB_COLUMNS = [
@@ -50,7 +51,6 @@ DB_COLUMNS = [
"space5", "space5",
] ]
INT_COLUMNS = {"stknum", "recid", "seqid", "actcod", "rpma", "spm1", "spm2", "spm3", "mfop", "stkc", "lagstks"} INT_COLUMNS = {"stknum", "recid", "seqid", "actcod", "rpma", "spm1", "spm2", "spm3", "mfop", "stkc", "lagstks"}
@@ -67,20 +67,6 @@ def sql_quote(value):
return "'" + str(value).replace("'", "''") + "'" return "'" + str(value).replace("'", "''") + "'"
def to_int(value, default=0):
try:
return int(value)
except Exception:
return default
def to_float(value, default=0.0):
try:
return float(value)
except Exception:
return default
class DrillingRealtimeORM: class DrillingRealtimeORM:
def __init__(self, database, stable="drilling_realtime_st", default_device_code="GJ-304-0088"): def __init__(self, database, stable="drilling_realtime_st", default_device_code="GJ-304-0088"):
self.database = database self.database = database
@@ -90,30 +76,23 @@ class DrillingRealtimeORM:
def build_insert_sql(self, payload): def build_insert_sql(self, payload):
if not isinstance(payload, dict): if not isinstance(payload, dict):
raise ValueError("payload is not JSON object") raise ValueError("payload is not JSON object")
entity = DrillingRealtimeData.from_payload(payload)
meta = payload.get("meta") if isinstance(payload.get("meta"), dict) else {} meta = payload.get("meta") if isinstance(payload.get("meta"), dict) else {}
data = payload.get("data") if isinstance(payload.get("data"), dict) else {}
equipment_code = ( equipment_code = (
str(self.default_device_code).strip() str(self.default_device_code).strip()
or str(meta.get("equipment_code", "")).strip() or str(meta.get("equipment_code", "")).strip()
or str(meta.get("equipment_sn", "")).strip() or str(meta.get("equipment_sn", "")).strip()
or entity.wellid
or "GJ-304-0088" or "GJ-304-0088"
) )
table_name = sanitize_identifier( table_name = sanitize_identifier(f"drilling_realtime_{equipment_code}", "drilling_realtime_default")
f"drilling_realtime_{equipment_code}",
"drilling_realtime_default",
)
values = [] values = []
for col in DB_COLUMNS: for column in DB_COLUMNS:
if col == "ts": raw = getattr(entity, column)
raw = data.get("ts", data.get("record_time", int(time.time() * 1000))) if column in INT_COLUMNS or column == "ts":
values.append(str(to_int(raw, int(time.time() * 1000)))) values.append(str(int(raw)))
elif col in INT_COLUMNS:
values.append(str(to_int(data.get(col, 0), 0)))
else: else:
values.append(str(to_float(data.get(col, 0), 0.0))) values.append(str(float(raw)))
columns_sql = ", ".join([f"`{column}`" for column in DB_COLUMNS]) columns_sql = ", ".join([f"`{column}`" for column in DB_COLUMNS])
values_sql = ", ".join(values) values_sql = ", ".join(values)
return ( return (

53
main.py
View File

@@ -1,10 +1,55 @@
import argparse
import logging import logging
from app import mqtt_mock, mqtt_sender, mqtt_subscriber, wits_sender
def main():
logging.info("start app....")
def configure_logging(level_name):
level = getattr(logging, str(level_name).upper(), logging.INFO)
logging.basicConfig(
level=level,
format="%(asctime)s %(levelname)s %(name)s - %(message)s",
)
if __name__ == '__main__':
def build_parser():
parser = argparse.ArgumentParser(description="tdEngine mqtt/wits mock application")
parser.add_argument("--log-level", default="INFO", help="Logging level")
subparsers = parser.add_subparsers(dest="command", required=True)
mqtt_mock_parser = subparsers.add_parser("mqtt-mock", help="Run MQTT mock service")
mqtt_mock.add_arguments(mqtt_mock_parser)
mqtt_sender_parser = subparsers.add_parser("mqtt-sender", help="Run MQTT sender")
mqtt_sender.add_arguments(mqtt_sender_parser)
mqtt_subscriber_parser = subparsers.add_parser("mqtt-subscriber", help="Run MQTT subscriber")
mqtt_subscriber.add_arguments(mqtt_subscriber_parser)
wits_sender_parser = subparsers.add_parser("wits-sender", help="Run WITS sender")
wits_sender.add_arguments(wits_sender_parser)
return parser
def main(argv=None):
parser = build_parser()
args = parser.parse_args(argv)
configure_logging(args.log_level)
logging.getLogger(__name__).info("start app command=%s", args.command)
if args.command == "mqtt-mock":
deps = mqtt_mock.build_mock_dependencies(args.config, data_file_override=args.data_file)
mqtt_mock.run_mock_service(args, deps)
elif args.command == "mqtt-sender":
deps = mqtt_sender.build_sender_dependencies(args.config)
mqtt_sender.run_sender(args, deps)
elif args.command == "mqtt-subscriber":
deps = mqtt_subscriber.build_subscriber_dependencies(args.config)
mqtt_subscriber.run_subscriber(args, deps)
elif args.command == "wits-sender":
deps = wits_sender.build_wits_sender_dependencies(args.config)
wits_sender.run_wits_sender(args, deps)
if __name__ == "__main__":
main() main()

View File

@@ -0,0 +1,14 @@
from model.config import AppConfig, MqttConfig, TdengineConfig, TmsConfig, WitsConfig
from model.drilling import DrillingRealtimeData
from model.wits import WITS_FIELD_MAPPING, WitsData
__all__ = [
"AppConfig",
"DrillingRealtimeData",
"MqttConfig",
"TdengineConfig",
"TmsConfig",
"WITS_FIELD_MAPPING",
"WitsConfig",
"WitsData",
]

72
model/config.py Normal file
View File

@@ -0,0 +1,72 @@
from dataclasses import dataclass
from urllib.parse import urlparse
@dataclass(frozen=True)
class MqttConfig:
broker: str
client_id: str
mock_client_id: str
sender_client_id: str
subscriber_client_id: str
username: str | None
password: str | None
pub_topic: str | None
sub_topic: str | None
ack_topic: str | None
data_file: str
@dataclass(frozen=True)
class TmsConfig:
device_code: str
equipment_sn: str
timeout: int
keepalive: int
server_ip: str | None
server_port: int | None
@dataclass(frozen=True)
class WitsConfig:
host: str
port: int
timeout: int
source_file: str
@dataclass(frozen=True)
class TdengineConfig:
url: str = ""
username: str = ""
password: str = ""
database: str = ""
stable: str = "drilling_realtime_st"
device_code: str = "GJ-304-0088"
pool_size: int = 2
timeout: int = 10
@property
def base_url(self):
if not self.url:
return ""
raw = str(self.url).strip()
if raw.lower().startswith("jdbc:taos-rs://"):
raw = "http://" + raw[len("jdbc:TAOS-RS://") :]
elif "://" not in raw:
raw = "http://" + raw
parsed = urlparse(raw)
return f"{parsed.scheme or 'http'}://{parsed.hostname or '127.0.0.1'}:{parsed.port or 6041}"
@property
def enabled(self):
return bool(self.base_url and self.database and self.username)
@dataclass(frozen=True)
class AppConfig:
mqtt: MqttConfig
tms: TmsConfig
wits: WitsConfig
tdengine: TdengineConfig
raw: dict

176
model/drilling.py Normal file
View File

@@ -0,0 +1,176 @@
import time
from dataclasses import dataclass
from datetime import datetime
DATA_KEYS = [
"ts",
"wellid",
"stknum",
"recid",
"seqid",
"actual_date",
"actual_time",
"actcod",
"deptbitm",
"deptbitv",
"deptmeas",
"deptvert",
"blkpos",
"ropa",
"hkla",
"hklx",
"woba",
"wobx",
"torqa",
"torqx",
"rpma",
"sppa",
"chkp",
"spm1",
"spm2",
"spm3",
"tvolact",
"tvolcact",
"mfop",
"mfoa",
"mfia",
"mdoa",
"mdia",
"mtoa",
"mtia",
"mcoa",
"mcia",
"stkc",
"lagstks",
"deptretm",
"gasa",
"space1",
"space2",
"space3",
"space4",
"space5",
]
@dataclass(frozen=True)
class DrillingRealtimeData:
ts: int
wellid: str
stknum: int
recid: int
seqid: int
actual_date: float
actual_time: float
actcod: int
deptbitm: float
deptbitv: float
deptmeas: float
deptvert: float
blkpos: float
ropa: float
hkla: float
hklx: float
woba: float
wobx: float
torqa: float
torqx: float
rpma: int
sppa: float
chkp: float
spm1: int
spm2: int
spm3: int
tvolact: float
tvolcact: float
mfop: int
mfoa: float
mfia: float
mdoa: float
mdia: float
mtoa: float
mtia: float
mcoa: float
mcia: float
stkc: int
lagstks: int
deptretm: float
gasa: float
space1: float
space2: float
space3: float
space4: float
space5: float
@classmethod
def empty(cls, wellid=""):
now = datetime.utcnow()
return cls(
ts=int(time.time() * 1000),
wellid=wellid,
stknum=0,
recid=0,
seqid=0,
actual_date=float(now.strftime("%Y%m%d")),
actual_time=float(now.strftime("%H%M%S")),
actcod=0,
deptbitm=0.0,
deptbitv=0.0,
deptmeas=0.0,
deptvert=0.0,
blkpos=0.0,
ropa=0.0,
hkla=0.0,
hklx=0.0,
woba=0.0,
wobx=0.0,
torqa=0.0,
torqx=0.0,
rpma=0,
sppa=0.0,
chkp=0.0,
spm1=0,
spm2=0,
spm3=0,
tvolact=0.0,
tvolcact=0.0,
mfop=0,
mfoa=0.0,
mfia=0.0,
mdoa=0.0,
mdia=0.0,
mtoa=0.0,
mtia=0.0,
mcoa=0.0,
mcia=0.0,
stkc=0,
lagstks=0,
deptretm=0.0,
gasa=0.0,
space1=0.0,
space2=0.0,
space3=0.0,
space4=0.0,
space5=0.0,
)
@classmethod
def from_payload(cls, payload):
meta = payload.get("meta") if isinstance(payload, dict) and isinstance(payload.get("meta"), dict) else {}
data = payload.get("data") if isinstance(payload, dict) and isinstance(payload.get("data"), dict) else {}
values = {key: data.get(key, 0) for key in DATA_KEYS}
values["ts"] = int(data.get("ts", data.get("record_time", int(time.time() * 1000))))
values["wellid"] = data.get("wellid") or meta.get("equipment_code") or meta.get("equipment_sn") or ""
return cls(**values)
def to_payload(self, equipment_code):
return {
"meta": {
"equipment_code": equipment_code,
"equipment_sn": equipment_code,
},
"data": {
key: getattr(self, key)
for key in DATA_KEYS
},
}

102
model/wits.py Normal file
View File

@@ -0,0 +1,102 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class WitsData:
ts: int
wellid: str
stknum: int
recid: int
seqid: int
actual_date: float
actual_time: float
actual_ts: int
actcod: int
actod_label: str
deptbitm: float
deptbitv: float
deptmeas: float
deptvert: float
blkpos: float
ropa: float
hkla: float
hklx: float
woba: float
wobx: float
torqa: float
torqx: float
rpma: int
sppa: float
chkp: float
spm1: int
spm2: int
spm3: int
tvolact: float
tvolcact: float
mfop: int
mfoa: float
mfia: float
mdoa: float
mdia: float
mtoa: float
mtia: float
mcoa: float
mcia: float
stkc: int
lagstks: int
deptretm: float
gasa: float
space1: float
space2: float
space3: float
space4: float
space5: float
WITS_FIELD_MAPPING = [
(1, "wellid", "string"),
(2, "stknum", "int"),
(3, "recid", "int"),
(4, "seqid", "int"),
(5, "actual_date", "float"),
(6, "actual_time", "float"),
(7, "actcod", "int"),
(8, "deptbitm", "float"),
(9, "deptbitv", "float"),
(10, "deptmeas", "float"),
(11, "deptvert", "float"),
(12, "blkpos", "float"),
(13, "ropa", "float"),
(14, "hkla", "float"),
(15, "hklx", "float"),
(16, "woba", "float"),
(17, "wobx", "float"),
(18, "torqa", "float"),
(19, "torqx", "float"),
(20, "rpma", "int"),
(21, "sppa", "float"),
(22, "chkp", "float"),
(23, "spm1", "int"),
(24, "spm2", "int"),
(25, "spm3", "int"),
(26, "tvolact", "float"),
(27, "tvolcact", "float"),
(28, "mfop", "int"),
(29, "mfoa", "float"),
(30, "mfia", "float"),
(31, "mdoa", "float"),
(32, "mdia", "float"),
(33, "mtoa", "float"),
(34, "mtia", "float"),
(35, "mcoa", "float"),
(36, "mcia", "float"),
(37, "stkc", "int"),
(38, "lagstks", "int"),
(39, "deptretm", "float"),
(40, "gasa", "float"),
(41, "space1", "float"),
(42, "space2", "float"),
(43, "space3", "float"),
(44, "space4", "float"),
(45, "space5", "float"),
]

View File

@@ -5,6 +5,7 @@
### wits数据模拟 ### wits数据模拟
1. 读取config.yaml中的server-ip和端口将模拟的wits数据发送到这个地址。wits数据要尽可能的拟真。 1. 读取config.yaml中的server-ip和端口将模拟的wits数据发送到这个地址。wits数据要尽可能的拟真。
2. 发送间隔两秒一条。