首次提交完整的python工程代码

This commit is contained in:
2026-06-15 20:38:42 +08:00
commit bca485d748
159 changed files with 248076 additions and 0 deletions

0
core/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

520
core/a2b_forward.py Normal file
View File

@@ -0,0 +1,520 @@
"""
2026/4/5
TMY
A2B转发器,重构A→B 组帧与发送
"""
# import socket
# from utils.logger import logger
# from utils.hex_helper import hex_dump
# from processor.telem_config import TELEM_LAYOUT, REVERSE_CALC
# from core.bit_packer import BitPacker
# from config.settings import SATELLITE_ID
# # 帧定义
# SYNC_HEADER = b'\xEB\x90\xEB\x90'
# TAIL = b'\x4C\x3A'
# # 方向与类型
# DIR_TO_SAT = 0x00000001
# TYPE_TELEM = 0x00111111
# class A2BForward:
# def __init__(self, local_port, b_ip, b_udp_port):
# self.local_port = local_port
# self.b_ip = b_ip
# self.b_udp_port = b_udp_port
# self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# self.sock_b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# def build_b_frame(self, data_id, values):
# # 1) 获取布局与长度
# layout = TELEM_LAYOUT[data_id]
# data_len_bytes = layout['length']
# items = layout['items']
# # 2) 比特打包
# packer = BitPacker(data_len_bytes)
# for idx, v in enumerate(values):
# if idx >= len(items):
# break
# item = items[idx]
# d_int = REVERSE_CALC(v, item)
# packer.set_bits(item['byte'], item['bit'], item['bits'], d_int)
# data_field = packer.get_bytes()
# # 3) 组帧
# frame = bytearray()
# frame += SYNC_HEADER
# frame += len(data_field).to_bytes(4, 'big') # 数据长度
# frame += DIR_TO_SAT.to_bytes(4, 'big') # 方向
# frame += data_id.to_bytes(4, 'big') # 数据代号
# frame += TYPE_TELEM.to_bytes(4, 'big') # 类型:遥测
# frame += SATELLITE_ID.to_bytes(4, 'big') # 卫星编号
# frame += data_field
# frame += TAIL
# return bytes(frame)
# def loop(self):
# self.sock_a.bind(('0.0.0.0', self.local_port))
# logger.info(f"A监听UDP: {self.local_port}")
# while True:
# data, _ = self.sock_a.recvfrom(4096)
# if len(data) < 24:
# continue
# # 简单解析A帧取 data_id + 浮点列表
# data_id = int.from_bytes(data[8:12], 'big')
# float_cnt = (len(data) - 16) // 8
# values = []
# for i in range(float_cnt):
# f = float.from_bytes(data[16 + i*8 : 24 + i*8], 'big')
# values.append(f)
# # 组B帧并发送
# b_frame = self.build_b_frame(data_id, values)
# self.sock_b.sendto(b_frame, (self.b_ip, self.b_udp_port))
# hex_dump(b_frame, f"A→B UDP {self.b_ip}:{self.b_udp_port}")
"""
2026/4/19
Tan Mingyan
重写A->B 4中模型的遥测量发送并保存至全局变量。
"""
import socket
from utils.logger import logger
from utils.hex_helper import hex_dump
from processor.telem_config import TELEM_LAYOUT, REVERSE_CALC
from core.bit_packer import BitPacker
#from config.settings import SATELLITE_ID
import struct # 👈 新增,用于解析浮点数
from core.global_params import get_param, set_param # 顶部导入
from core.global_params import TELEM_PARAMS
from protocol.a_frame import AFrame
# from processor.telem_config import TABLE_MAP, TELEM_COUNT
from processor.telm_calc import calc_telem_reverse
from protocol.b_frame import build_b_frame
#from global_vars import *
SYNC_HEADER = b'\xEB\x90\xEB\x90'
TAIL = b'\x4C\x3A'
DIR_TO_SAT = 0x00000001
TYPE_TELEM = 0x00111111
from config_loader import cfg
SATELLITE_ID = cfg.get_int("SATELLITE", "SATELLITE_ID")#2026/4/7 TMY
class A2BForward:
g_vbat_check_cnt =[] #静态变量
g_vbat_check_cnt_B =[] #静态变量
ck_ab_flag = True #True #测控AB通道有效标志
ck_c_flag = True #True #测控C通道有效标志
def __init__(self, local_port, b_ip, b_udp_port):
self.local_port = local_port
self.b_ip = b_ip
self.b_udp_port = b_udp_port
self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock_b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.g_vbat_check_cnt = [0,0,0,0] #静态变量
self.g_vbat_check_cnt_B = [0,0,0,0] #静态变量
self.gufangA_power = 0
self.gufangB_power = 0
self.kuopinA_5V = 0
self.kuopinB_5V = 0
#上一次接收到的值
self.gufangA_power_former = 0
self.gufangB_power_former = 0
self.kuopinA_5V_former = 0
self.kuopinB_5V_former = 0
self.yitiji_5v_former = 0
self.yitiji_5v = 0
self.ck_ab_flag = True #测控AB通道有效标志
self.ck_a_flag = True #测控A通道有效标志
self.ck_b_flag = True #测控B通道有效标志
self.ck_c_flag = True #测控C通道有效标志
self.receive_A_frame_count = 0
self.A_battery_v_direct = 0
self.B_battery_v_direct = 0
def build_b_frame(self, data_id,satelllite_id, values):
#print(data_id)
layout = TELEM_LAYOUT[data_id]
data_len = layout['length']
items = layout['items']
packer = BitPacker(data_len)
for idx, y in enumerate(values):
if idx >= len(items):
break
it = items[idx]
d = REVERSE_CALC(y, it)##反推的值,然后存入全局变量?
if self.receive_A_frame_count % 10 == 0:#每100帧才打印1次A软件输入参数避免日志过大
#print(f"输入y={y}, 配置it={it}, 反推d={d}")
logger.info((f"输入y={y}, 配置it={it}, 反推d={d}"))
self.receive_A_frame_count = 0
# ====================== 【核心:存入全局变量】 ======================2026/4/18
if 'param_num' in it:
set_param(it['param_num'], d) # 按代号存入全局
#存入一些原值为全局变量 2026/4/30
if it['param_num'] == 'M001CV024':
self.gufangA_power = y
if it['param_num'] == 'M001CV029':
self.gufangB_power = y
if it['param_num'] == 'M001CV002':
self.kuopinA_5V = y
if it['param_num'] == 'M001CV013':
self.kuopinB_5V = y
if it['param_num'] == 'M085CV004':
self.yitiji_5v = y
if it['param_num'] == 'M010YV012':
self.A_battery_v_direct = y
if it['param_num'] == 'M010YV014':
self.B_battery_v_direct = y
#写入A->B帧
packer.set_bits(it['byte'], it['bit'], it['bits'], d)
data_field = packer.get_bytes()
# 组帧
frame = bytearray()
frame += SYNC_HEADER
frame += len(data_field).to_bytes(4, 'big')
frame += DIR_TO_SAT.to_bytes(4, 'big')
frame += data_id.to_bytes(4, 'big')
frame += TYPE_TELEM.to_bytes(4, 'big')
#frame += SATELLITE_ID.to_bytes(4, 'big')
frame += satelllite_id.to_bytes(4, 'big')#2026/4/7 TMY
frame += data_field
frame += TAIL
self.receive_A_frame_count += 1
return bytes(frame)
def check_battery_safe_mode(self):#判断整星安全模式
# 蓄电池组A
# ====================== 1. 计算三路电池电压 ======================
Vbat1 = get_param('M002YV020',0) *0.01837765 - 0.00943934 # 整组电池1A组蓄电池组电压
#Vbat2 = get_param('M010YV012',0) * 16.56008 -0.01009 # 整组电池2M010YV012A蓄电池组电压-直
#Vbat2 = self.A_battery_v_direct #2026/5/9 暂时来源为0所以屏蔽
Vbat2 = Vbat1 #2026/5/9
Vbat3 = (get_param('M004YV002',0) + get_param('M004YV004',0) +
get_param('M004YV006',0) + get_param('M004YV008',0) +
get_param('M004YV010',0) + get_param('M004YV012',0) +
get_param('M004YV014',0) + get_param('M004YV016',0) + get_param('M004YV018',0))* 0.00244140625 # 单体总和
Vth1 = 30 #
Vth2 = 28.998 # g_M050YV00229.9982026/5/12 不改为29.9.
Vth3 = 24.999 # g_M050YV003
Vth4 = 3.2983398 #M050YV004 A单体过放电VCOD1阈值-软
# # ====================== 2. 单次判断:是否低于阈值 ======================
# fail1 = 1 if (Vbat1 < Vth1) else 0
# fail2 = 1 if (Vbat2 < Vth2) else 0
# fail3 = 1 if (Vbat3 < Vth3) else 0
fail11 = 1 if (Vbat1 < Vth1) else 0
fail21 = 1 if (Vbat2 < Vth1) else 0
fail31 = 1 if (Vbat3 < Vth1) else 0
fail12 = 1 if (Vbat1 < Vth2) else 0
fail22 = 1 if (Vbat2 < Vth2) else 0
fail32 = 1 if (Vbat3 < Vth2) else 0
fail13 = 1 if (Vbat1 < Vth3) else 0
fail23 = 1 if (Vbat2 < Vth3) else 0
fail33 = 1 if (Vbat3 < Vth3) else 0
fail4 = 1 if(Vbat3 < Vth4 * 9) else 0
# # ====================== 3. 连续3次判断 ======================
# total_fail = fail1 + fail2 + fail3
# if total_fail >= 2:
# g_vbat_check_cnt += 1
# g_vbat_fail_flag = [fail1, fail2, fail3]
# else:
# g_vbat_check_cnt = 0
# g_vbat_fail_flag = [0, 0, 0]
total_fail1 = fail11 + fail21 + fail31#低于VBOD1的数量
total_fail2 = fail12 + fail22 + fail32#低于VBOD2的数量
total_fail3 = fail13 + fail23 + fail33#低于VBOD3的数量
if total_fail1 >= 2:
self.g_vbat_check_cnt[0] += 1
else:
self.g_vbat_check_cnt[0] = 0
if total_fail2 >= 2:
self.g_vbat_check_cnt[1] += 1
else:
self.g_vbat_check_cnt[1] = 0
if total_fail3 >= 2:
self.g_vbat_check_cnt[2] += 1
else:
self.g_vbat_check_cnt[2] = 0
if fail4 >0:
self.g_vbat_check_cnt[3] += 1
else:
self.g_vbat_check_cnt[3] = 0
# 超限3次锁定报警
# if g_vbat_check_cnt >= 3:
# g_M050YS055 = g_vbat_fail_flag[0]
# g_M050YS056 = g_vbat_fail_flag[1]
# g_M050YS057 = g_vbat_fail_flag[2]
# else:
# g_M050YS055 = 0
# g_M050YS056 = 0
# g_M050YS057 = 0
## 分别产生过放电报警信号VBOD1~VBOD3M050YS055改为1M050YS056改为1M050YS057改为1否则都发别更改为0
if self.g_vbat_check_cnt[0]>=3 :
g_M050YS055 = 1
set_param('M050YS055', 1) # 按代号存入全局
else:
g_M050YS055 = 0
set_param('M050YS055', 0) # 按代号存入全局
if self.g_vbat_check_cnt[1]>=3 :
g_M050YS056 = 1
set_param('M050YS056', 1) # 按代号存入全局
else:
g_M050YS056 = 1
set_param('M050YS056', 0) # 按代号存入全局
if self.g_vbat_check_cnt[2]>=3 :
g_M050YS057 = 1
set_param('M050YS057', 1) # 按代号存入全局
else:
g_M050YS057 = 1
set_param('M050YS057', 0) # 按代号存入全局
if self.g_vbat_check_cnt[3]>=3 :
g_M050YS058 = 1
set_param('M050YS058', 1) # 按代号存入全局
else:
g_M050YS058 = 1
set_param('M050YS058', 0) # 按代号存入全局
# ====================== 4. 整星安全模式触发条件 ======================
## 当蓄电池组电压M002YV020小于VBOD2M050YV002初始阈值为29V产生“整组过放电2”报警信号即把M050YS056改为1此时卫星进入整星安全模式修改M050YG073供电模式为1否则修改为0
if Vbat2 < Vth2: # 小于阈值2(默认29V)
g_M050YS056 = 1
g_M050YG073 = 1 # 进入安全模式
set_param('M050YS056', 1) # 按代号存入全局
set_param('M050YG073', 1) # 按代号存入全局
set_param('M014ZG027', 9) # 按代号存入全局M014ZG027 整星进入安全模式原因
else:
g_M050YS056 = 0
g_M050YG073 = 0 # 退出安全模式
set_param('M050YS056', 0) # 按代号存入全局
set_param('M050YG073', 0) # 按代号存入全局
set_param('M014ZG027', 0) # 按代号存入全局M014ZG027 整星进入安全模式原因
##蓄电池组B的逻辑和A完全一样
# ====================== 1. 计算三路电池电压 ======================
Vbat1_B = get_param('M002YV054',0) * 0.01856155 -0.01615284 # 整组电池1
#Vbat2_B = get_param('M010YV014',0) * 16.473805 - 0.009011 # 整组电池2
#Vbat2_B =self.B_battery_v_direct # 整组电池2
Vbat2_B = Vbat1_B
Vbat3_B = (get_param('M004YV022',0) + get_param('M004YV024',0) +
get_param('M004YV026',0) + get_param('M004YV028',0) +
get_param('M004YV030',0) + get_param('M004YV032',0) +
get_param('M004YV034',0) + get_param('M004YV036',0) + get_param('M004YV038',0))* 0.00244140625 # 单体总和
Vth1_B = 30 # M050YV021
Vth2_B = 29 # M050YV022
Vth3_B = 25 # M050YV023
Vth4_B= 3.2983398 #M050YV024 B单体过放电VCOD1阈值-软
# # ====================== 2. 单次判断:是否低于阈值 ======================
# fail1 = 1 if (Vbat1 < Vth1) else 0
# fail2 = 1 if (Vbat2 < Vth2) else 0
# fail3 = 1 if (Vbat3 < Vth3) else 0
fail11_B = 1 if (Vbat1_B < Vth1_B) else 0
fail21_B = 1 if (Vbat2_B < Vth1_B) else 0
fail31_B = 1 if (Vbat3_B < Vth1_B) else 0
fail12_B = 1 if (Vbat1_B < Vth2_B) else 0
fail22_B = 1 if (Vbat2_B < Vth2_B) else 0
fail32_B = 1 if (Vbat3_B < Vth2_B) else 0
fail13_B = 1 if (Vbat1_B < Vth3_B) else 0
fail23_B = 1 if (Vbat2_B < Vth3_B) else 0
fail33_B = 1 if (Vbat3_B < Vth3_B) else 0
fail4_B = 1 if(Vbat3_B < Vth4_B * 9) else 0
# # ====================== 3. 连续3次判断 ======================
# total_fail = fail1 + fail2 + fail3
# if total_fail >= 2:
# g_vbat_check_cnt += 1
# g_vbat_fail_flag = [fail1, fail2, fail3]
# else:
# g_vbat_check_cnt = 0
# g_vbat_fail_flag = [0, 0, 0]
total_fail1_B = fail11_B + fail21_B + fail31_B#低于VBOD1的数量
total_fail2_B = fail12_B + fail22_B + fail32_B#低于VBOD2的数量
total_fail3_B = fail13_B + fail23_B + fail33_B#低于VBOD3的数量
if total_fail1_B >= 2:
self.g_vbat_check_cnt_B[0] += 1
else:
self.g_vbat_check_cnt_B[0]=0
if total_fail2_B >= 2:
self.g_vbat_check_cnt_B[1] += 1
else:
self.g_vbat_check_cnt_B[1]=0
if total_fail3_B >= 2:
self.g_vbat_check_cnt_B[2] += 1
else:
self.g_vbat_check_cnt_B[2]=0
if fail4_B >= 1:
self.g_vbat_check_cnt_B[3] += 1
else:
self.g_vbat_check_cnt_B[3]=0
# 超限3次锁定报警
# if g_vbat_check_cnt >= 3:
# g_M050YS055 = g_vbat_fail_flag[0]
# g_M050YS056 = g_vbat_fail_flag[1]
# g_M050YS057 = g_vbat_fail_flag[2]
# else:
# g_M050YS055 = 0
# g_M050YS056 = 0
# g_M050YS057 = 0
## 分别产生过放电报警信号VBOD1~VBOD3M050YS055改为1M050YS056改为1M050YS057改为1否则都发别更改为0
if self.g_vbat_check_cnt_B[0]>=3 :
g_M050YS067 = 1
set_param('M050YS067', 1) # 按代号存入全局
else:
g_M050YS067 = 0
set_param('M050YS067', 0) # 按代号存入全局
if self.g_vbat_check_cnt_B[1]>=3 :
g_M050YS068 = 1
set_param('M050YS068', 1) # 按代号存入全局
else:
g_M050YS068 = 1
set_param('M050YS068', 0) # 按代号存入全局
if self.g_vbat_check_cnt_B[2]>=3 :
g_M050YS069 = 1
set_param('M050YS069', 1) # 按代号存入全局
else:
g_M050YS069 = 1
set_param('M050YS069', 0) # 按代号存入全局
if self.g_vbat_check_cnt_B[3]>=3 :
g_M050YS070 = 1
set_param('M050YS070', 1) # 按代号存入全局
else:
g_M050YS070 = 1
set_param('M050YS070', 0) # 按代号存入全局
# ====================== 4. 整星安全模式触发条件 ======================
## 当蓄电池组电压M002YV020小于VBOD2M050YV002初始阈值为29V产生“整组过放电2”报警信号即把M050YS056改为1此时卫星进入整星安全模式修改M050YG073供电模式为1否则修改为0
if Vbat2_B < Vth2_B: # 小于阈值2(默认29V)
g_M050YS068 = 1
g_M050YG073 = 1 # 进入安全模式
set_param('M050YS068', 1) # 按代号存入全局
set_param('M050YG073', 1) # 按代号存入全局
else:
g_M050YS068 = 0
#g_M050YG073 = 0 # 退出安全模式
set_param('M050YS068', 0) # 按代号存入全局
#set_param('M050YG073', 0) # 按代号存入全局
def change_ck_mode(self):
# gufangA_power = get_param('M001CV024',0) *1
# kuopinA_5V = get_param('M001CV002',0)*1
# gufangB_power = get_param('M001CV029',0) *1
# kuopinB_5V = get_param('M001CV013',0)*1
print(f"self.gufangA_power_former={self.gufangA_power_former},self.gufangA_power={self.gufangA_power}")
#测控ab通道正常发遥测监测判断M001CV024固放A功率从10变为0或M001CV002扩频A+5V从5变为0M001CV029固放B功率从10变为0或M001CV013扩频B+5V从5变为0则停掉测控AB遥测转发地面向星务发送切C通道遥测指令后星务模型播发C通道遥测转发到地面解析
if ((self.gufangA_power_former > 9 and self.gufangA_power < 0.1)or(self.kuopinA_5V_former>4.9 and self.kuopinA_5V<0.1)) and ((self.gufangB_power_former > 9 and self.gufangB_power < 0.1)or(self.kuopinB_5V_former>4.9 and self.kuopinB_5V<0.1)) :
self.ck_ab_flag = False ## 暂时and改成or!!!已经改回来。这样的判断导致AB不同时失效时无法触发异常处理。
A2BForward.ck_ab_flag = False
if((self.gufangA_power_former > 9 and self.gufangA_power < 0.1)or(self.kuopinA_5V_former>4.9 and self.kuopinA_5V<0.1)):
self.ck_a_flag = False #测控A异常标志
if((self.gufangB_power_former > 9 and self.gufangB_power < 0.1)or(self.kuopinB_5V_former>4.9 and self.kuopinB_5V<0.1)):
self.ck_b_flag = False #测控B异常标志
if(self.ck_a_flag == False and self.ck_b_flag == False):
self.ck_ab_flag = False ## 测控A,B异常标志
A2BForward.ck_ab_flag = False
self.gufangA_power_former = self.gufangA_power #上一次接收到的值
self.gufangB_power_former = self.gufangB_power
self.kuopinA_5V_former = self.kuopinA_5V
self.kuopinB_5V_former = self.kuopinB_5V
#监测判断M085CV004一体机+5V电源-直从5变为0则停掉C通道遥测转发
if((self.yitiji_5v_former>4.8)and(self.yitiji_5v)<0.1):
self.ck_c_flag = False
A2BForward.ck_c_flag = False
if(self.yitiji_5v>4.8):
self.ck_c_flag = True
A2BForward.ck_c_flag = True
self.yitiji_5v_former = self.yitiji_5v
def loop(self):
self.sock_a.bind(('0.0.0.0', self.local_port))
logger.info(f"监听A UDP: {self.local_port}")
while True:
data, _ = self.sock_a.recvfrom(8192)
if len(data) < 24:
continue
s = data.hex(" ").upper()
logger.info(f"A同元的帧 {s}")
#data_id = int.from_bytes(data[8:12], 'big')#错误
data_id = int.from_bytes(data[12:16], 'big')
satelllite_id = int.from_bytes(data[20:24], 'big')
#float_cnt = (len(data) - 16) // 8
float_cnt = int.from_bytes(data[4:8], 'big')//8 #最新协议规定是double数据的数量不是字节数 2026/4/19 TMY 同元做法就是字节数 2026/4/20
print(f"{float_cnt} 个8B浮点数")
values = []
for i in range(float_cnt):
# f = float.from_bytes(data[16+i*8 : 24+i*8], 'big')
# values.append(f)
start = 24 + i * 8 #start = 16 + i * 8
end = start + 8
if end > len(data):#判断读取是否越界
break
# 👈 修复点:使用 struct 解析 8 字节大端 double
f = struct.unpack('>d', data[start:end])[0]
values.append(f)
# # 逆运算反推原始值
# frame = AFrame(data)
# data_id = frame.data_id
# if data_id not in TABLE_MAP:
# continue
# processed = []
# table = TABLE_MAP[data_id]
# for idx, v in enumerate(values):
# if idx >= len(table):
# processed.append(v)
# continue
# name, method, param = table[idx]
# pv = calc_telem_reverse(v, method, param)
# print(f"[{name}] 输入Y: {v:.6f},反推D: {pv:.2f}")
# processed.append(pv)
#b_frame = build_b_frame(data_id, processed)#2026/4/20 TMY
b_frame = self.build_b_frame(data_id,satelllite_id, values)
# self.sock_b.sendto(b_frame, (self.b_ip, self.b_udp_port)) #B软件暂时无法解析这个数据先不发 2026/5/7
# hex_dump(b_frame, f"A→B UDP {self.b_ip}:{self.b_udp_port}")
##实现更新安全模式的方法
self.check_battery_safe_mode()
## AB测控帧头检测
self.change_ck_mode()

134
core/a2b_forward_old.py Normal file
View File

@@ -0,0 +1,134 @@
"""
##A→B 转发主逻辑UDP 监听 5000 → TCP 发 B2026/3/29 2026/4/3停用
import socket
import threading
from utils.logger import logger
from utils.hex_helper import hex_dump
from protocol.a_frame import AFrame
from protocol.b_frame import build_b_frame
from processor.telem_config import TABLE_MAP, TELEM_COUNT
from processor.telm_calc import calc_telem, calc_telem_reverse
class A2BForward:
def __init__(self, local_port, tcp_ip, tcp_port):
self.local_port = local_port
self.tcp_ip = tcp_ip
self.tcp_port = tcp_port
self.sock_tcp = None
def connect_b(self):
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.tcp_ip, self.tcp_port))
self.sock_tcp = s
logger.info("✅ 已连接B软件")
return
except Exception as e:
logger.error("连接B失败重连中")
threading.Event().wait(2)
def loop(self):
sock_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_udp.bind(("0.0.0.0", self.local_port))
logger.info(f"✅ 监听A软件 UDP:{self.local_port}")
self.connect_b()
while True:
data, addr = sock_udp.recvfrom(4096)
frame = AFrame(data)
if not frame.valid:
continue
data_id = frame.data_id
if data_id not in TABLE_MAP:
continue
expect_cnt = TELEM_COUNT[data_id]
values = frame.values[:expect_cnt]
table = TABLE_MAP[data_id]
# 计算所有遥测
#ref_cache = {}
processed = []
for idx, v in enumerate(values):
if idx >= len(table):
processed.append(v)
continue
name, method, param = table[idx]
#pv = calc_telem(v, method, param, ref_cache)
# 处理前
#print(f"[{name}] 输入Y: {v:.6f}")
pv = calc_telem_reverse(v, method, param)#改为逆运算
print(f"[{name}] 输入Y: {v:.6f},反推D: {pv:.2f}")
processed.append(pv)
# 组B帧并发送
b_frame = build_b_frame(data_id, processed)
try:
self.sock_tcp.send(b_frame)
except:
self.connect_b()
self.sock_tcp.send(b_frame)
hex_dump(b_frame, "已发往B软件")
"""
# 修改 A→B 发送逻辑(核心) 2026/4/3
import socket
import threading
from utils.logger import logger
from utils.hex_helper import hex_dump
from protocol.a_frame import AFrame
from protocol.b_frame import build_b_frame
from processor.telem_config import TABLE_MAP, TELEM_COUNT
from processor.telm_calc import calc_telem_reverse
class A2BForward:
def __init__(self, local_port, b_ip, b_udp_port):
self.local_port = local_port # 5000
self.b_ip = b_ip # 10.20.97.166
self.b_udp_port = b_udp_port # B 新UDP端口
# 监听 A 的 UDP
self.sock_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 发给 B 的 UDP新链路
self.sock_to_b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def loop(self):
self.sock_udp.bind(("0.0.0.0", self.local_port))
logger.info(f"✅ 监听 A UDP: {self.local_port}")
while True:
data, addr = self.sock_udp.recvfrom(4096)
frame = AFrame(data)
if not frame.valid:
continue
data_id = frame.data_id
if data_id not in TABLE_MAP:
continue
expect_cnt = TELEM_COUNT[data_id]
values = frame.values[:expect_cnt]
table = TABLE_MAP[data_id]
# 逆运算反推原始值
processed = []
for idx, v in enumerate(values):
if idx >= len(table):
processed.append(v)
continue
name, method, param = table[idx]
pv = calc_telem_reverse(v, method, param)
print(f"[{name}] 输入Y: {v:.6f},反推D: {pv:.2f}")
processed.append(pv)
# 组帧
b_frame = build_b_frame(data_id, processed)
# ===================== 发给 BUDP 发送 =====================
self.sock_to_b.sendto(b_frame, (self.b_ip, self.b_udp_port))
hex_dump(b_frame, f"✅ A→B 已通过 UDP 发往 B: {self.b_ip}:{self.b_udp_port}")

868
core/b2c_update_rules.py Normal file
View File

@@ -0,0 +1,868 @@
"""
2026/4/18
Tan Mingyan
B软件数字卫星遥测帧-> C软件遥测数据更新规则
"""
# 格式:
# 包代号: [
# (参数代号, 起始字节, 起始比特, 比特数),
# ...
# ]
"""
B2C_UPDATE_RULES = {
1: [
("M001CT001", 0, 0, 8),
("M001CV002", 1, 0, 8),
("M001CV003", 2, 0, 8),
("M001CV004", 3, 0, 8),
("M001CS005", 4, 0, 1),
("M001CS006", 5, 0, 1),
("M001CS007", 6, 0, 1),
("M001CS008", 7, 0, 1),
("M001CS009", 8, 0, 1),
("M001CS010", 9, 0, 1),
("M001CS011", 10, 0, 1),
("M001CT012", 11, 0, 8),
("M001CV013", 12, 0, 8),
("M001CV014", 13, 0, 8),
("M001CV015", 14, 0, 8),
("M001CS016", 15, 0, 1),
("M001CS017", 16, 0, 1),
("M001CS018", 17, 0, 1),
("M001CS019", 18, 0, 1),
("M001CS020", 19, 0, 1),
("M001CS021", 20, 0, 1),
("M001CS022", 21, 0, 1),
("M001CV023", 22, 0, 8),
("M001CV024", 23, 0, 8),
("M001CT025", 24, 0, 8),
("M001CS026", 25, 0, 1),
("M001CI027", 26, 0, 8),
("M001CV028", 27, 0, 8),
("M001CV029", 28, 0, 8),
("M001CT030", 29, 0, 8),
("M001CS031", 30, 0, 1),
("M001CI032", 31, 0, 8),
("M001CT033", 32, 0, 8),
("M001CS034", 33, 0, 1),
("M001CS035", 34, 0, 1),
],
2: [
("M002YI002", 0, 4, 12),
("M002YI004", 2, 4, 12),
("M002YI006", 4, 4, 12),
("M002YI008", 6, 4, 12),
("M002YI010", 8, 4, 12),
("M002YI012", 10, 4, 12),
("M002YI014", 12, 4, 12),
("M002YI016", 14, 4, 12),
("M002YV018", 16, 4, 12),
("M002YV020", 18, 4, 12),
("M002YV022", 20, 4, 12),
("M002YV024", 22, 4, 12),
("M002YS025", 24, 0, 8),
("M002YS026", 25, 0, 8),
("M002YS027", 26, 0, 8),
("M002YS028", 27, 0, 8),
("M002YS029", 28, 0, 8),
("M002YS030", 29, 0, 8),
("M002YS031", 30, 0, 8),
("M002YS032", 31, 0, 8),
("M002YS033", 32, 0, 8),
("M002YS034", 33, 0, 8),
("M002YS035", 34, 0, 8),
("M002YS036", 35, 0, 8),
("M002YI040", 38, 4, 12),
("M002YI042", 40, 4, 12),
("M002YI044", 42, 4, 12),
("M002YI046", 44, 4, 12),
("M002YI048", 46, 4, 12),
("M002YI050", 48, 4, 12),
("M002YI052", 50, 4, 12),
("M002YV054", 52, 4, 12),
("M002YV056", 54, 4, 12),
],
3: [
("M003YI002", 0, 4, 12),
("M003YI004", 2, 4, 12),
("M003YG027", 24, 0, 4),
("M003YG028", 24, 4, 4),
("M003YG029", 25, 0, 4),
("M003YG030", 25, 4, 4),
("M003YI034", 28, 4, 12),
("M003YI036", 30, 4, 12),
],
4: [
("M004YV002", 0, 4, 12),
("M004YV004", 2, 4, 12),
("M004YV006", 4, 4, 12),
("M004YV008", 6, 4, 12),
("M004YV010", 8, 4, 12),
("M004YV012", 10, 4, 12),
("M004YV014", 12, 4, 12),
("M004YV016", 14, 4, 12),
("M004YV018", 16, 4, 12),
("M004YV022", 20, 4, 12),
("M004YV024", 22, 4, 12),
("M004YV026", 24, 4, 12),
("M004YV028", 26, 4, 12),
("M004YV030", 28, 4, 12),
("M004YV032", 30, 4, 12),
("M004YV034", 32, 4, 12),
("M004YV036", 34, 4, 12),
("M004YV038", 36, 4, 12),
],
19: [
("M019XV005", 8, 0, 16),
],
18: [
("M018XV017", 32, 0, 16),
("M018XV019", 36, 0, 16),
],
57: [
("M057JS005", 3, 1, 1),
("M057JS006", 3, 2, 1),
("M057JS007", 3, 3, 1),
("M057JS008", 3, 4, 1),
("M057JS009", 3, 5, 1),
("M057JS010", 3, 6, 1),
("M057JS012", 4, 1, 1),
("M057JS013", 4, 2, 1),
("M057JS014", 4, 3, 3),
("M057JS015", 4, 6, 3),
("M057JS017", 5, 2, 1),
("M057JS018", 5, 3, 1),
("M057JG023", 10, 0, 16),
("M057JG024", 12, 0, 16),
#("M057JG039", 42, 0, 32),
("M057JG040", 46, 0, 32),
],
79: [
("M079CG032", 16, 2, 2),
("M079CS033", 16, 4, 1),
("M079CS034", 16, 5, 1),
],
85: [
("M085TV003", 3, 0, 16),
("M085CV004", 5, 0, 16),
],
87: [
("M087CS063", 47, 3, 1),
("M087CS064", 47, 4, 1),
("M087CS065", 47, 5, 1),
("M087CS066", 47, 6, 1),
("M087CS067", 47, 7, 1),
("M087CS068", 48, 0, 1),
("M087CS069", 48, 1, 1),
("M087CS078", 49, 2, 1),
("M087CS079", 49, 3, 1),
],
}
"""
"""
# 最新B->C遥测替换规则 2026-04-19
# 已完成所有偏移量计算、多包号拆分、统一字节偏移
B2C_UPDATE_RULES = {
# 包1
1: [
("M001CT001", 132, 0, 8),
("M001CV002", 133, 0, 8),
("M001CV003", 134, 0, 8),
("M001CV004", 135, 0, 8),
("M001CS005", 136, 0, 1),
("M001CS006", 137, 0, 1),
("M001CS007", 138, 0, 1),
("M001CS008", 139, 0, 1),
("M001CS009", 140, 0, 1),
("M001CS010", 141, 0, 1),
("M001CS011", 142, 0, 1),
("M001CT012", 143, 0, 8),
("M001CV013", 144, 0, 8),
("M001CV014", 145, 0, 8),
("M001CV015", 146, 0, 8),
("M001CS016", 147, 0, 1),
("M001CS017", 148, 0, 1),
("M001CS018", 149, 0, 1),
("M001CS019", 150, 0, 1),
("M001CS020", 151, 0, 1),
("M001CS021", 152, 0, 1),
("M001CS022", 153, 0, 1),
("M001CV023", 154, 0, 8),
("M001CV024", 155, 0, 8),
("M001CT025", 156, 0, 8),
("M001CS026", 157, 0, 1),
("M001CI027", 158, 0, 8),
("M001CV028", 159, 0, 8),
("M001CV029", 160, 0, 8),
("M001CT030", 161, 0, 8),
("M001CS031", 162, 0, 1),
("M001CI032", 163, 0, 8),
("M001CT033", 164, 0, 8),
("M001CS034", 165, 0, 1),
("M001CS035", 166, 0, 1),
],
# 包2
2: [
("M002YI002", 132, 4, 12),
("M002YI004", 134, 4, 12),
("M002YI006", 136, 4, 12),
("M002YI008", 138, 4, 12),
("M002YI010", 140, 4, 12),
("M002YI012", 142, 4, 12),
("M002YI014", 144, 4, 12),
("M002YI016", 146, 4, 12),
("M002YV018", 148, 4, 12),
("M002YV020", 150, 4, 12),
("M002YV022", 152, 4, 12),
("M002YV024", 154, 4, 12),
("M002YS025", 156, 0, 8),
("M002YS026", 157, 0, 8),
("M002YS027", 158, 0, 8),
("M002YS028", 159, 0, 8),
("M002YS029", 160, 0, 8),
("M002YS030", 161, 0, 8),
("M002YS031", 162, 0, 8),
("M002YS032", 163, 0, 8),
("M002YS033", 164, 0, 8),
("M002YS034", 165, 0, 8),
("M002YS035", 166, 0, 8),
("M002YS036", 167, 0, 8),
("M002YI040", 170, 4, 12),
("M002YI042", 172, 4, 12),
("M002YI044", 174, 4, 12),
("M002YI046", 176, 4, 12),
("M002YI048", 178, 4, 12),
("M002YI050", 180, 4, 12),
("M002YI052", 182, 4, 12),
("M002YV054", 184, 4, 12),
("M002YV056", 186, 4, 12),
],
# 包3
3: [
("M003YI002", 132, 4, 12),
("M003YI004", 134, 4, 12),
("M003YG027", 156, 0, 4),
("M003YG028", 156, 4, 4),
("M003YG029", 157, 0, 4),
("M003YG030", 157, 4, 4),
("M003YI034", 160, 4, 12),
("M003YI036", 162, 4, 12),
],
# 包4
4: [
("M004YV002", 132, 4, 12),
("M004YV004", 134, 4, 12),
("M004YV006", 136, 4, 12),
("M004YV008", 138, 4, 12),
("M004YV010", 140, 4, 12),
("M004YV012", 142, 4, 12),
("M004YV014", 144, 4, 12),
("M004YV016", 146, 4, 12),
("M004YV018", 148, 4, 12),
("M004YV022", 152, 4, 12),
("M004YV024", 154, 4, 12),
("M004YV026", 156, 4, 12),
("M004YV028", 158, 4, 12),
("M004YV030", 160, 4, 12),
("M004YV032", 162, 4, 12),
("M004YV034", 164, 4, 12),
("M004YV036", 166, 4, 12),
("M004YV038", 168, 4, 12),
],
# 包18
18: [
("M018XV017", 164, 0, 16),
("M018XV019", 168, 0, 16),
],
# 包19
19: [
("M019XV005", 140, 0, 16),
],
# 包57
57: [
("M057JS005", 135, 1, 1),
("M057JS006", 135, 2, 1),
("M057JS007", 135, 3, 1),
("M057JS008", 135, 4, 1),
("M057JS009", 135, 5, 1),
("M057JS010", 135, 6, 1),
("M057JS012", 136, 1, 1),
("M057JS013", 136, 2, 1),
("M057JS014", 136, 3, 3),
("M057JS015", 136, 6, 3),
("M057JS017", 137, 2, 1),
("M057JS018", 137, 3, 1),
("M057JG023", 142, 0, 16),
("M057JG024", 144, 0, 16),
("M057JG039", 174, 0, 32),
("M057JG040", 178, 0, 32),
],
# 包79
79: [
("M079CG032", 148, 2, 2),
("M079CS033", 148, 4, 1),
("M079CS034", 148, 5, 1),
],
# 包85
85: [
("M085TV003", 135, 0, 16),
("M085CV004", 137, 0, 16),
],
# 包87
87: [
("M087CS063", 179, 3, 1),
("M087CS064", 179, 4, 1),
("M087CS065", 179, 5, 1),
("M087CS066", 179, 6, 1),
("M087CS067", 179, 7, 1),
("M087CS068", 180, 0, 1),
("M087CS069", 180, 1, 1),
("M087CS078", 181, 2, 1),
("M087CS079", 181, 3, 1),
],
# ========== 新增第二批独立包号 ==========
# 包151
151: [
("M001CT001", 0, 0, 8),
("M001CV002", 1, 0, 8),
("M001CV003", 2, 0, 8),
("M001CV004", 3, 0, 8),
("M001CS005", 4, 0, 1),
("M001CS006", 5, 0, 1),
("M001CS007", 6, 0, 1),
("M001CS008", 7, 0, 1),
("M001CS009", 8, 0, 1),
("M001CS010", 9, 0, 1),
("M001CS011", 10, 0, 1),
("M001CT012", 11, 0, 8),
("M001CV013", 12, 0, 8),
("M001CV014", 13, 0, 8),
("M001CV015", 14, 0, 8),
("M001CS016", 15, 0, 1),
("M001CS017", 16, 0, 1),
("M001CS018", 17, 0, 1),
("M001CS019", 18, 0, 1),
("M001CS020", 19, 0, 1),
("M001CS021", 20, 0, 1),
("M001CS022", 21, 0, 1),
("M001CV023", 22, 0, 8),
("M001CV024", 23, 0, 8),
("M001CT025", 24, 0, 8),
("M001CS026", 25, 0, 1),
("M001CI027", 26, 0, 8),
("M001CV028", 27, 0, 8),
("M001CV029", 28, 0, 8),
("M001CT030", 29, 0, 8),
("M001CS031", 30, 0, 1),
("M001CI032", 31, 0, 8),
("M001CT033", 32, 0, 8),
("M001CS034", 33, 0, 1),
("M001CS035", 34, 0, 1),
],
# 包126
126: [
("M079CG032", 16, 2, 2),
("M079CS033", 16, 4, 1),
("M079CS034", 16, 5, 1),
("M002YI002", 66, 4, 12),
("M002YI004", 68, 4, 12),
("M002YI006", 70, 4, 12),
("M002YI008", 72, 4, 12),
("M002YI010", 74, 4, 12),
("M002YI012", 76, 4, 12),
("M002YI014", 78, 4, 12),
("M002YI016", 80, 4, 12),
("M002YV018", 82, 4, 12),
("M002YV020", 84, 4, 12),
("M002YV022", 86, 4, 12),
("M002YV024", 88, 4, 12),
("M002YS025", 90, 0, 8),
("M002YS026", 91, 0, 8),
("M002YS027", 92, 0, 8),
("M002YS028", 93, 0, 8),
("M002YS029", 94, 0, 8),
("M002YS030", 95, 0, 8),
("M002YS031", 96, 0, 8),
("M002YS032", 97, 0, 8),
("M002YS033", 98, 0, 8),
("M002YS034", 99, 0, 8),
("M002YS035", 100, 0, 8),
("M002YS036", 101, 0, 8),
("M002YI040", 104, 4, 12),
("M002YI042", 106, 4, 12),
("M002YI044", 108, 4, 12),
("M002YI046", 110, 4, 12),
("M002YI048", 112, 4, 12),
("M002YI050", 114, 4, 12),
("M002YI052", 116, 4, 12),
("M002YV054", 118, 4, 12),
("M002YV056", 120, 4, 12),
],
# 包127
127: [
("M085TV003", 3, 0, 16),
("M085CV004", 5, 0, 16),
("M087CS063", 113, 3, 1),
("M087CS064", 113, 4, 1),
("M087CS065", 113, 5, 1),
("M087CS066", 113, 6, 1),
("M087CS067", 113, 7, 1),
("M087CS068", 114, 0, 1),
("M087CS069", 114, 1, 1),
("M087CS078", 115, 2, 1),
("M087CS079", 115, 3, 1),
],
# 包128
128: [
("M087CS063", 113, 3, 1),
("M087CS064", 113, 4, 1),
("M087CS065", 113, 5, 1),
("M087CS066", 113, 6, 1),
("M087CS067", 113, 7, 1),
("M087CS068", 114, 0, 1),
("M087CS069", 114, 1, 1),
("M087CS078", 115, 2, 1),
("M087CS079", 115, 3, 1),
],
# 包129
129: [
("M087CS063", 113, 3, 1),
("M087CS064", 113, 4, 1),
("M087CS065", 113, 5, 1),
("M087CS066", 113, 6, 1),
("M087CS067", 113, 7, 1),
("M087CS068", 114, 0, 1),
("M087CS069", 114, 1, 1),
("M087CS078", 115, 2, 1),
("M087CS079", 115, 3, 1),
],
# 包143
143: [
("M057JS005", 135, 1, 1),
("M057JS006", 135, 2, 1),
("M057JS007", 135, 3, 1),
("M057JS008", 135, 4, 1),
("M057JS009", 135, 5, 1),
("M057JS010", 135, 6, 1),
("M057JS012", 136, 1, 1),
("M057JS013", 136, 2, 1),
("M057JS014", 136, 3, 3),
("M057JS015", 136, 6, 3),
("M057JS017", 137, 2, 1),
("M057JS018", 137, 3, 1),
("M057JG023", 142, 0, 16),
("M057JG024", 144, 0, 16),
("M057JG039", 174, 0, 32),
("M057JG040", 178, 0, 32),
],
# 包147
147: [
("M003YI002", 0, 4, 12),
("M003YI004", 2, 4, 12),
("M003YG027", 24, 0, 4),
("M003YG028", 24, 4, 4),
("M003YG029", 25, 0, 4),
("M003YG030", 25, 4, 4),
("M003YI034", 28, 4, 12),
("M003YI036", 30, 4, 12),
("M004YV002", 66, 4, 12),
("M004YV004", 68, 4, 12),
("M004YV006", 70, 4, 12),
("M004YV008", 72, 4, 12),
("M004YV010", 74, 4, 12),
("M004YV012", 76, 4, 12),
("M004YV014", 78, 4, 12),
("M004YV016", 80, 4, 12),
("M004YV018", 82, 4, 12),
("M004YV022", 86, 4, 12),
("M004YV024", 88, 4, 12),
("M004YV026", 90, 4, 12),
("M004YV028", 92, 4, 12),
("M004YV030", 94, 4, 12),
("M004YV032", 96, 4, 12),
("M004YV034", 98, 4, 12),
("M004YV036", 100, 4, 12),
("M004YV038", 102, 4, 12),
],
# 包149
149: [
("M019XV005", 132, 0, 16),
("M018XV017", 32, 0, 16),
("M018XV019", 36, 0, 16),
]
}
"""
# 2026-04-19 最新版 B2C 遥测更新规则
# 完全依据本次最新表格、所有偏移量预计算、多包拆分完成
B2C_UPDATE_RULES = {
# ==================== 第一组:大包号(1/2/3/4/18/19/57/79/85/87) 偏移+132 ====================
1: [
("M001CT001", 132, 0, 8),
("M001CV002", 133, 0, 8),
("M001CV003", 134, 0, 8),
("M001CV004", 135, 0, 8),
("M001CS005", 136, 0, 1),
("M001CS006", 137, 0, 1),
("M001CS007", 138, 0, 1),
("M001CS008", 139, 0, 1),
("M001CS009", 140, 0, 1),
("M001CS010", 141, 0, 1),
("M001CS011", 142, 0, 1),
("M001CT012", 143, 0, 8),
("M001CV013", 144, 0, 8),
("M001CV014", 145, 0, 8),
("M001CV015", 146, 0, 8),
("M001CS016", 147, 0, 1),
("M001CS017", 148, 0, 1),
("M001CS018", 149, 0, 1),
("M001CS019", 150, 0, 1),
("M001CS020", 151, 0, 1),
("M001CS021", 152, 0, 1),
("M001CS022", 153, 0, 1),
("M001CV023", 154, 0, 8),
("M001CV024", 155, 0, 8),
("M001CT025", 156, 0, 8),
("M001CS026", 157, 0, 1),
("M001CI027", 158, 0, 8),
("M001CV028", 159, 0, 8),
("M001CV029", 160, 0, 8),
("M001CT030", 161, 0, 8),
("M001CS031", 162, 0, 1),
("M001CI032", 163, 0, 8),
("M001CT033", 164, 0, 8),
("M001CS034", 165, 0, 1),
("M001CS035", 166, 0, 1),
],
2: [
("M002YI002", 132, 4, 12),
("M002YI004", 134, 4, 12),
("M002YI006", 136, 4, 12),
("M002YI008", 138, 4, 12),
("M002YI010", 140, 4, 12),
("M002YI012", 142, 4, 12),
("M002YI014", 144, 4, 12),
("M002YI016", 146, 4, 12),
("M002YV018", 148, 4, 12),
("M002YV020", 150, 4, 12),
("M002YV022", 152, 4, 12),
("M002YV024", 154, 4, 12),
("M002YS025", 156, 0, 8),
("M002YS026", 157, 0, 8),
("M002YS027", 158, 0, 8),
("M002YS028", 159, 0, 8),
("M002YS029", 160, 0, 8),
("M002YS030", 161, 0, 8),
("M002YS031", 162, 0, 8),
("M002YS032", 163, 0, 8),
("M002YS033", 164, 0, 8),
("M002YS034", 165, 0, 8),
("M002YS035", 166, 0, 8),
("M002YS036", 167, 0, 8),
("M002YI040", 170, 4, 12),
("M002YI042", 172, 4, 12),
("M002YI044", 174, 4, 12),
("M002YI046", 176, 4, 12),
("M002YI048", 178, 4, 12),
("M002YI050", 180, 4, 12),
("M002YI052", 182, 4, 12),
("M002YV054", 184, 4, 12),
("M002YV056", 186, 4, 12),
],
3: [
("M003YI002", 132, 4, 12),
("M003YI004", 134, 4, 12),
("M003YG027", 156, 0, 4),
("M003YG028", 156, 4, 4),
("M003YG029", 157, 0, 4),
("M003YG030", 157, 4, 4),
("M003YI034", 160, 4, 12),
("M003YI036", 162, 4, 12),
# ========== 【新增】包3 M003YV038 ==========
("M003YV038", 164, 4, 12),
],
4: [
("M004YV002", 132, 4, 12),
("M004YV004", 134, 4, 12),
("M004YV006", 136, 4, 12),
("M004YV008", 138, 4, 12),
("M004YV010", 140, 4, 12),
("M004YV012", 142, 4, 12),
("M004YV014", 144, 4, 12),
("M004YV016", 146, 4, 12),
("M004YV018", 148, 4, 12),
("M004YV022", 152, 4, 12),
("M004YV024", 154, 4, 12),
("M004YV026", 156, 4, 12),
("M004YV028", 158, 4, 12),
("M004YV030", 160, 4, 12),
("M004YV032", 162, 4, 12),
("M004YV034", 164, 4, 12),
("M004YV036", 166, 4, 12),
("M004YV038", 168, 4, 12),
],
# ========== 【新增】包10M010 系列 ==========
10: [
("M010YV006", 136, 4, 12),
("M010YV012", 142, 4, 12),
("M010YV014", 144, 4, 12),
],
14: [
("M014ZG027", 184, 0, 8),#2026/5/8 计算机包9
],
18: [
("M018XV017", 164, 0, 16),
("M018XV019", 168, 0, 16),
],
19: [
("M019XV005", 140, 0, 16),
],
##2026/4/26新增50包
50: [
# ("M050YV001", 132, 0, 16),
# ("M050YV002", 134, 0, 16),
# ("M050YV003", 136, 0, 16),#阈值先不动
("M050YS055", 171, 0, 1),
("M050YS056", 171, 1, 1),
("M050YS057", 171, 2, 1),
("M050YS058", 171, 3, 1),#2026/5/8
("M050YS067", 186, 0, 2),#2026/5/8
("M050YS068", 186, 1, 2),#2026/5/8
("M050YS069", 186, 2, 2),#2026/5/8
("M050YS070", 186, 3, 2),#2026/5/8
("M050YG073", 186, 6, 2),
],
57: [
("M057JS005", 135, 1, 1),
("M057JS006", 135, 2, 1),
("M057JS007", 135, 3, 1),
("M057JS008", 135, 4, 1),
("M057JS009", 135, 5, 1),
("M057JS010", 135, 6, 1),
# ("M057JS012", 136, 1, 1),
# ("M057JS013", 136, 2, 1),
("M057JS014", 136, 3, 3),
("M057JS015", 136, 6, 3),
("M057JS017", 137, 2, 1),
("M057JS018", 137, 3, 1),
#("M057JG023", 142, 0, 16),
#("M057JG024", 144, 0, 16),
#("M057JG039", 174, 0, 32),
#("M057JG040", 178, 0, 32),
],
79: [
("M079CG032", 148, 2, 2),
("M079CS033", 148, 4, 1),
("M079CS034", 148, 5, 1),
],
85: [
("M085TV003", 135, 0, 16),
("M085CV004", 137, 0, 16),
],
87: [
("M087CS063", 179, 3, 1),
("M087CS064", 179, 4, 1),
("M087CS065", 179, 5, 1),
("M087CS066", 179, 6, 1),
("M087CS067", 179, 7, 1),
("M087CS068", 180, 0, 1),
("M087CS069", 180, 1, 1),
("M087CS078", 181, 2, 1),
("M087CS079", 181, 3, 1),
],
# ==================== 第二组:专项包号(151/126/127/128/129/143/147/149) 新偏移 ====================
151: [
("M001CT001", 4, 0, 8),
("M001CV002", 5, 0, 8),
("M001CV003", 6, 0, 8),
("M001CV004", 7, 0, 8),
("M001CS005", 8, 0, 1),
("M001CS006", 9, 0, 1),
("M001CS007", 10, 0, 1),
("M001CS008", 11, 0, 1),
("M001CS009", 12, 0, 1),
("M001CS010", 13, 0, 1),
("M001CS011", 14, 0, 1),
("M001CT012", 15, 0, 8),
("M001CV013", 16, 0, 8),
("M001CV014", 17, 0, 8),
("M001CV015", 18, 0, 8),
("M001CS016", 19, 0, 1),
("M001CS017", 20, 0, 1),
("M001CS018", 21, 0, 1),
("M001CS019", 22, 0, 1),
("M001CS020", 23, 0, 1),
("M001CS021", 24, 0, 1),
("M001CS022", 25, 0, 1),
("M001CV023", 26, 0, 8),
("M001CV024", 27, 0, 8),
("M001CT025", 28, 0, 8),
("M001CS026", 29, 0, 1),
("M001CI027", 30, 0, 8),
("M001CV028", 31, 0, 8),
("M001CV029", 32, 0, 8),
("M001CT030", 33, 0, 8),
("M001CS031", 34, 0, 1),
("M001CI032", 35, 0, 8),
("M001CT033", 36, 0, 8),
("M001CS034", 37, 0, 1),
("M001CS035", 38, 0, 1),
],
125: [
("M014ZG027", 184, 0, 8),#2026/5/8 计算机包9
],
126: [
("M079CG032", 20, 2, 2),
("M079CS033", 20, 4, 1),
("M079CS034", 20, 5, 1),
("M002YI002", 68, 4, 12),
("M002YI004", 70, 4, 12),
("M002YI006", 72, 4, 12),
("M002YI008", 74, 4, 12),
("M002YI010", 76, 4, 12),
("M002YI012", 78, 4, 12),
("M002YI014", 80, 4, 12),
("M002YI016", 82, 4, 12),
("M002YV018", 84, 4, 12),
("M002YV020", 86, 4, 12),
("M002YV022", 88, 4, 12),
("M002YV024", 90, 4, 12),
("M002YS025", 92, 0, 8),
("M002YS026", 93, 0, 8),
("M002YS027", 94, 0, 8),
("M002YS028", 95, 0, 8),
("M002YS029", 96, 0, 8),
("M002YS030", 97, 0, 8),
("M002YS031", 98, 0, 8),
("M002YS032", 99, 0, 8),
("M002YS033", 100, 0, 8),
("M002YS034", 101, 0, 8),
("M002YS035", 102, 0, 8),
("M002YS036", 101, 0, 8),
("M002YI040", 106, 4, 12),
("M002YI042", 108, 4, 12),
("M002YI044", 110, 4, 12),
("M002YI046", 112, 4, 12),
("M002YI048", 114, 4, 12),
("M002YI050", 116, 4, 12),
("M002YI052", 118, 4, 12),
("M002YV054", 120, 4, 12),
("M002YV056", 122, 4, 12),
],
127: [
("M085TV003", 7, 0, 16),
("M085CV004", 9, 0, 16),
("M087CS063", 115, 3, 1),
("M087CS064", 115, 4, 1),
("M087CS065", 115, 5, 1),
("M087CS066", 115, 6, 1),
("M087CS067", 115, 7, 1),
("M087CS068", 116, 0, 1),
("M087CS069", 116, 1, 1),
("M087CS078", 117, 2, 1),
("M087CS079", 117, 3, 1),
],
128: [
("M087CS063", 115, 3, 1),
("M087CS064", 115, 4, 1),
("M087CS065", 115, 5, 1),
("M087CS066", 115, 6, 1),
("M087CS067", 115, 7, 1),
("M087CS068", 116, 0, 1),
("M087CS069", 116, 1, 1),
("M087CS078", 117, 2, 1),
("M087CS079", 117, 3, 1),
],
129: [
("M087CS063", 115, 3, 1),
("M087CS064", 115, 4, 1),
("M087CS065", 115, 5, 1),
("M087CS066", 115, 6, 1),
("M087CS067", 115, 7, 1),
("M087CS068", 116, 0, 1),
("M087CS069", 116, 1, 1),
("M087CS078", 117, 2, 1),
("M087CS079", 117, 3, 1),
],
143: [
("M057JS005", 135, 1, 1),
("M057JS006", 135, 2, 1),
("M057JS007", 135, 3, 1),
("M057JS008", 135, 4, 1),
("M057JS009", 135, 5, 1),
("M057JS010", 135, 6, 1),
# ("M057JS012", 136, 1, 1),
# ("M057JS013", 136, 2, 1),
("M057JS014", 136, 3, 3),
("M057JS015", 136, 6, 3),
("M057JS017", 137, 2, 1),
("M057JS018", 137, 3, 1),
#("M057JG023", 142, 0, 16),
#("M057JG024", 144, 0, 16),
#("M057JG039", 174, 0, 32),
#("M057JG040", 178, 0, 32),
],
147: [
("M003YI002", 4, 4, 12),
("M003YI004", 6, 4, 12),
("M003YG027", 28, 0, 4),
("M003YG028", 28, 4, 4),
("M003YG029", 29, 0, 4),
("M003YG030", 29, 4, 4),
("M003YI034", 32, 4, 12),
("M003YI036", 34, 4, 12),
# ========== 【新增】包147 M003YV038 ==========
("M003YV038", 36, 4, 12),
("M004YV002", 68, 4, 12),
("M004YV004", 70, 4, 12),
("M004YV006", 72, 4, 12),
("M004YV008", 74, 4, 12),
("M004YV010", 76, 4, 12),
("M004YV012", 78, 4, 12),
("M004YV014", 80, 4, 12),
("M004YV016", 82, 4, 12),
("M004YV018", 84, 4, 12),
("M004YV022", 88, 4, 12),
("M004YV024", 90, 4, 12),
("M004YV026", 92, 4, 12),
("M004YV028", 94, 4, 12),
("M004YV030", 96, 4, 12),
("M004YV032", 98, 4, 12),
("M004YV034", 100, 4, 12),
("M004YV036", 102, 4, 12),
("M004YV038", 104, 4, 12),
## 2026/4/26
# ("M050YV001", 132, 0, 16),
# ("M050YV002", 134, 0, 16),
# ("M050YV003", 136, 0, 16),#阈值先不修改
("M050YS055", 171, 0, 1),
("M050YS056", 171, 1, 1),
("M050YS057", 171, 2, 1),
("M050YS058", 171, 3, 1),#2026/5/8
("M050YS067", 186, 0, 2),#2026/5/8
("M050YS068", 186, 1, 2),#2026/5/8
("M050YS069", 186, 2, 2),#2026/5/8
("M050YS070", 186, 3, 2),#2026/5/8
("M050YG073", 186, 6, 2),
],
149: [
("M019XV005", 76, 0, 16),
("M018XV017", 36, 0, 16),
("M018XV019", 40, 0, 16),
],
# ========== 【新增】包162M010 系列 ==========
162: [
("M010YV006", 8, 4, 12),
("M010YV012", 14, 4, 12),
("M010YV014", 16, 4, 12),
],
}

36
core/b_gis_client.py Normal file
View File

@@ -0,0 +1,36 @@
import socket
import time
from utils.logger import logger
class BGisClient:
def __init__(self, b_ip, b_port):
self.b_ip = b_ip
self.b_port = b_port
self.sock = None
self.connected = False
def connect(self):
while True:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(3)
sock.connect((self.b_ip, self.b_port))
self.sock = sock
self.connected = True
logger.info(f"✅ B 地测端口连接成功:{self.b_ip}:{self.b_port}")
return
except Exception as e:
logger.error(f"❌ B 地测端口连接失败,重试:{e}")
time.sleep(1)
def send(self, data):
if not self.connected:
self.connect()
return
try:
self.sock.sendall(data)
except Exception as e:
logger.error(f"❌ B 地测发送失败:{e}")
self.connected = False
self.sock = None

31
core/bit_packer.py Normal file
View File

@@ -0,0 +1,31 @@
"""
2026/4/5
TMY
比特位打包器
"""
class BitPacker:
def __init__(self, total_bytes):
self.buf = bytearray(total_bytes)
def set_bits(self, start_byte, start_bit, bit_len, value):
"""
start_byte: 起始字节
start_bit: 起始比特位0~7
bit_len: 比特长度
value: 无符号整数值
"""
value = int(value) & ((1 << bit_len) - 1)
bit_pos = start_byte * 8 + start_bit
for i in range(bit_len):
bit = (value >> (bit_len - 1 - i)) & 1
byte_idx = bit_pos // 8
bit_idx = 7 - (bit_pos % 8)
if bit:
self.buf[byte_idx] |= (1 << bit_idx)
else:
self.buf[byte_idx] &= ~(1 << bit_idx)
bit_pos += 1
def get_bytes(self):
return bytes(self.buf)

34
core/bit_tools.py Normal file
View File

@@ -0,0 +1,34 @@
"""
2026/4/18
Tan Mingyan
新建 比特 / 字节 操作工具(通用、精准)
"""
def set_bits(data: bytearray, byte_offset: int, bit_offset: int, bit_length: int, value: int):
"""
对字节数组指定位置写入指定位数的值
:param data: 原始字节数组(会被修改)
:param byte_offset: 起始字节从0开始用户数据域起始为0
:param bit_offset: 起始比特0~7
:param bit_length: 占多少比特
:param value: 要写入的值
"""
if byte_offset >= len(data):
return
# 生成掩码
max_val = (1 << bit_length) - 1
value = value & max_val
pos = byte_offset * 8 + bit_offset
for i in range(bit_length):
bit = (value >> (bit_length - 1 - i)) & 1
byte_idx = pos // 8
bit_idx = pos % 8
if byte_idx >= len(data):
break
# 清位 + 置位
data[byte_idx] &= ~(1 << (7 - bit_idx))
data[byte_idx] |= (bit << (7 - bit_idx))
pos += 1

198
core/f_to_a_forward.py Normal file
View File

@@ -0,0 +1,198 @@
"""
2026/4/11
TMY
新建 F→A 转发核心
"""
# import socket
# import struct
# from utils.logger import logger
# from config_loader import cfg
# # A 帧固定头
# SYNC_HEADER = b'\xEB\x90\xEB\x90'
# TAIL = b'\x4C\x3A'
# # 固定配置
# DIR = 0x01111111
# DATA_ID = 0x00110000
# TYPE = 0x00444444
# SAT_ID = 0x000000ED
# DATA_LEN = 0x00000004 # 固定4个double
# class FToAForward:
# def __init__(self, local_port, a_ip, a_port):
# self.local_port = local_port
# self.a_ip = a_ip
# self.a_port = a_port
# # 监听 F UDP
# self.sock_f = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# self.sock_f.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# self.sock_f.bind(('0.0.0.0', self.local_port))
# # 发往 A UDP
# self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# logger.info(f"✅ F→A 转发启动:监听 F UDP {self.local_port} → A {a_ip}:{a_port}")
# def build_a_frame(self, shadow, sbx, sby, sbz):
# """组 A 软件协议帧"""
# frame = bytearray()
# frame += SYNC_HEADER
# frame += DATA_LEN.to_bytes(4, 'big')
# frame += DIR.to_bytes(4, 'big')
# frame += DATA_ID.to_bytes(4, 'big')
# frame += TYPE.to_bytes(4, 'big')
# frame += SAT_ID.to_bytes(4, 'big')
# # 4个 double 大端序
# frame += struct.pack('>d', shadow)
# frame += struct.pack('>d', sbx)
# frame += struct.pack('>d', sby)
# frame += struct.pack('>d', sbz)
# frame += TAIL
# logger.info(f"F->A 帧内容: {frame}")
# return bytes(frame)
# def loop(self):
# while True:
# data, addr = self.sock_f.recvfrom(4096)
# # 1. 只处理长度 2792
# if len(data) != 2792:
# continue
# # 2. 去头48去尾8 → 2736
# payload = data[48 : -8]
# if len(payload) != 2736:
# continue
# try:
# # 3. 小端解析所有 double
# doubles = []
# for i in range(0, 2736, 8):
# d = struct.unpack('<d', payload[i:i+8])[0]
# doubles.append(d)
# # 4. 提取从0开始所以第30个 → 索引29
# shadow = doubles[29] # 第30个
# sbx = doubles[318] # 319
# sby = doubles[319] # 320
# sbz = doubles[320] # 321
# logger.info(f"shadow={shadow}, sbx={sbx}, sby={sby}, sbz={sbz}")
# # shadow=0 → 全部置0
# # if shadow == 0:
# # sbx = 0.0
# # sby = 0.0
# # sbz = 0.0
# # 5. 组帧并发送
# a_frame = self.build_a_frame(shadow, sbx, sby, sbz)
# self.sock_a.sendto(a_frame, (self.a_ip, self.a_port))
# except Exception as e:
# logger.error(f"F→A 解析失败: {e}")
import socket
import struct
import time
from utils.hex_helper import hex_dump
from utils.logger import logger
# A 帧固定头
SYNC_HEADER = b'\xEB\x90\xEB\x90'
TAIL = b'\x4C\x3A'
# 固定配置
DIR = 0x01111111
DATA_ID = 0x00110000
TYPE = 0x00444444
SAT_ID = 0x000000ED
DATA_LEN = 0x00000004 # 4个double
class FToAForward:
def __init__(self, f_ip, f_port, a_ip, a_port):
self.f_ip = f_ip
self.f_port = f_port
self.a_ip = a_ip
self.a_port = a_port
self.sock_f = None
self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
logger.info(f"✅ F→A 转发启动TCP客户端连接 {f_ip}:{f_port} → A {a_ip}:{a_port}")
def connect_f(self):
while True:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
sock.connect((self.f_ip, self.f_port))
self.sock_f = sock
logger.info(f"✅ 成功连接 F TCP Server{self.f_ip}:{self.f_port}")
return
except Exception as e:
logger.error(f"❌ 连接 F 失败,重试:{e}")
time.sleep(1)
def build_a_frame(self, shadow, sbx, sby, sbz):
frame = bytearray()
frame += SYNC_HEADER
frame += DATA_LEN.to_bytes(4, 'big')
frame += DIR.to_bytes(4, 'big')
frame += DATA_ID.to_bytes(4, 'big')
frame += TYPE.to_bytes(4, 'big')
frame += SAT_ID.to_bytes(4, 'big')
frame += struct.pack('>d', shadow)
frame += struct.pack('>d', sbx)
frame += struct.pack('>d', sby)
frame += struct.pack('>d', sbz)
frame += TAIL
#logger.info(f"F->A 帧内容: {bytes(frame)}")
hex_dump(frame, "发送 F->A遥测帧")
return bytes(frame)
def loop(self):
while True:
if self.sock_f is None:
self.connect_f()
try:
data = self.sock_f.recv(4096)
if not data:
logger.error("❌ F 断开连接,重连...")
self.sock_f = None
time.sleep(1)
continue
if len(data) != 2792:
continue
logger.info("收到F软件发送的长度2792数据...")
payload = data[48:-8]
if len(payload) != 2736:
continue
doubles = []
for i in range(0, 2736, 8):
d = struct.unpack('<d', payload[i:i+8])[0]
doubles.append(d)
shadow = doubles[29]
sbx = doubles[318]
sby = doubles[319]
sbz = doubles[320]
# if shadow == 0:
# sbx = 0.0
# sby = 0.0
# sbz = 0.0
logger.info(f"shadow={shadow}, sbx={sbx}, sby={sby}, sbz={sbz}")
a_frame = self.build_a_frame( sbx, sby, sbz, shadow)
self.sock_a.sendto(a_frame, (self.a_ip, self.a_port))
except Exception as e:
logger.error(f"❌ F 接收异常:{e}")
self.sock_f = None
time.sleep(1)

435
core/forward_engine.py Normal file
View File

@@ -0,0 +1,435 @@
import socket
import threading
import time
from utils.logger import logger
from utils.hex_helper import hex_dump
from core.tcp_forward_client import TcpForwardClient # 新增
from core.global_params import get_param
from core.b2c_update_rules import B2C_UPDATE_RULES
from core.bit_tools import set_bits
class UdpForward:
def __init__(self, local_port, remote_ip, remote_port, converter):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind(("0.0.0.0", local_port))
self.remote = (remote_ip, remote_port)
self.converter = converter
def loop(self):
logger.info(f"UDP监听 :{self.sock.getsockname()[1]}{self.remote}")
while True:
data, addr = self.sock.recvfrom(4096)
hex_dump(data, f"UDP接收 {addr}")
out = self.converter.convert(data)
if out:
self.sock.sendto(out, self.remote)
"""#2026/3/31 C软件使用UDP协议后停用
class Tcp2UdpForward:
'''
原有功能B → 格式转换 → D
新增功能B → 无条件同时转发 → C
实现 1 发 2
'''
# def __init__(self, remote_tcp_ip, remote_tcp_port, udp_ip, udp_port, converter):
# self.tcp_ip = remote_tcp_ip
# self.tcp_port = remote_tcp_port
# self.udp_ip = udp_ip
# self.udp_port = udp_port
# self.converter = converter
# self.sock = None
def __init__(self, remote_tcp_ip, remote_tcp_port,
udp_ip, udp_port, converter,
forward_c_ip=None, forward_c_port=None): # 新增 C 地址
self.remote_tcp_ip = remote_tcp_ip
self.remote_tcp_port = remote_tcp_port
self.udp_ip = udp_ip
self.udp_port = udp_port
self.converter = converter
# ===================== 新增转发C =====================
self.forward_c = None
if forward_c_ip and forward_c_port:
self.forward_c = TcpForwardClient(forward_c_ip, forward_c_port)
self.udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# def connect(self):
# while True:
# try:
# self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# self.sock.connect((self.tcp_ip, self.tcp_port))
# logger.info(f"TCP连接B成功 {self.tcp_ip}:{self.tcp_port}")
# return
# except:
# import time
# time.sleep(1)
# def loop(self):
# self.connect()
# udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# while True:
# data = self.sock.recv(4096)
# if not data:
# self.connect()
# continue
# hex_dump(data, "TCP接收B")
# out = self.converter.convert(data)
# if out:
# udp.sendto(out, (self.udp_ip, self.udp_port))
def connect_b(self):
while True:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((self.remote_tcp_ip, self.remote_tcp_port))
logger.info(f"✅ 连接 B 软件成功: {self.remote_tcp_ip}:{self.remote_tcp_port}")
return sock
except Exception as e:
logger.error(f"❌ 连接 B 失败,重连中: {e}")
threading.Event().wait(2)
def loop(self):
# 如果需要转发C先连接C
if self.forward_c:
self.forward_c.connect()
while True:
sock_tcp = self.connect_b()
try:
while True:
data = sock_tcp.recv(4096)
if not data:
break
hex_dump(data, "收到 B 软件遥测帧")
# ===================== 核心1 分 2 =====================
# 1) 原有逻辑:转换后发给 D
d_data = self.converter.convert(data)
if d_data:
self.udp_sock.sendto(d_data, (self.udp_ip, self.udp_port))
# 2) 新增逻辑:无条件原样发给 C
if self.forward_c:
self.forward_c.send(data)
except Exception as e:
logger.error(f"B 连接异常: {e}")
finally:
sock_tcp.close()
"""
"""
class Tcp2UdpForward:#2026/4/2 B软件TC,TM双端后停用
'''
B → D格式转换 UDP
B → C原样 UDP 转发)
1发2 同时输出
'''
def __init__(self, remote_tcp_ip, remote_tcp_port,
udp_ip, udp_port, converter,
forward_c_ip=None, forward_c_port=None):
self.remote_tcp_ip = remote_tcp_ip
self.remote_tcp_port = remote_tcp_port
self.udp_ip = udp_ip
self.udp_port = udp_port
self.converter = converter
# D 端 UDP
self.udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# ===================== C 端 UDP 转发(新版) =====================
self.forward_c_ip = forward_c_ip
self.forward_c_port = forward_c_port
self.udp_c_sock = None
if self.forward_c_ip and self.forward_c_port:
self.udp_c_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def connect_b(self):
while True:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((self.remote_tcp_ip, self.remote_tcp_port))
logger.info(f"✅ 连接 B 软件成功: {self.remote_tcp_ip}:{self.remote_tcp_port}")
return sock
except Exception as e:
logger.error(f"❌ 连接 B 失败,重连中...")
threading.Event().wait(2)
def loop(self):
while True:
sock_tcp = self.connect_b()
try:
while True:
data = sock_tcp.recv(4096)
if not data:
break
hex_dump(data, "✅ 收到 B 软件遥测帧")
# ========== 1. 原有逻辑:转换后发给 D ==========
d_data = self.converter.convert(data)
if d_data:
self.udp_sock.sendto(d_data, (self.udp_ip, self.udp_port))
# ========== 2. 新增逻辑UDP 原样发给 C ==========
if self.udp_c_sock:
self.udp_c_sock.sendto(data, (self.forward_c_ip, self.forward_c_port))
except Exception as e:
logger.error(f"⚠️ B 连接异常: {e}")
finally:
sock_tcp.close()
"""
import socket
import threading
from utils.logger import logger
from utils.hex_helper import hex_dump
from core.a2b_forward import A2BForward
cmd_K6466 = "EB9001073500EB90B571A50911C00100E505F4ED4500000000000000000000000000000000000000000000000000000000DDAAAAAAAA88880C1B0C02024477A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5CCCCCCCCCCCCCCCCB2EBA5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5DF0B91945716"
cmd_K6466_byte = bytes.fromhex(cmd_K6466)
class Tcp2UdpForward:
"""
功能:
1. B 遥测(3078 TCP Server) → M 客户端接收
2. 转换格式 → D(UDP)
3. 无条件原样 → C(UDP)
"""
def __init__(self, b_ip, b_telem_port, udp_d_ip, udp_d_port, converter, c_ip, c_port, g_ip, g_port, g_cmd_ip, g_cmd_port, e_ip, e_port):
self.b_ip = b_ip
self.b_telem_port = b_telem_port # 3078
self.d_ip = udp_d_ip
self.d_port = udp_d_port
self.c_ip = c_ip
self.c_port = c_port
self.g_ip = g_ip
self.g_port = g_port
self.g_cmd_ip = g_cmd_ip
self.g_cmd_port = g_cmd_port
self.converter = converter
self.e_ip = e_ip
self.e_port = e_port
# UDP 发送 socket
self.udp_d = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.udp_c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.udp_g = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.udp_g_cmd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.udp_e = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock_b = None #2026/4/16修复休眠断开
self.connected = False #2026/4/16修复休眠断开
self.not_received_count = 0 #没有收到遥测帧次数
# def connect_b_telem(self):#2026/4/16弃用不能保证断联后重新连接
# """连接 B 遥测 TCP Server 3078"""
# while True:
# try:
# sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# logger.info(f"✅ B ip,remote——port{self.b_ip,self.b_telem_port}")
# sock.connect((self.b_ip, self.b_telem_port))
# logger.info(f"✅ 已连接 B 遥测端口 3078")
# return sock
# except Exception as e:
# logger.error("❌ 连接 B 3078 失败,重试中...")
# threading.Event().wait(2)
def connect_b_telem(self):#2026/4/16启用修复休眠断开重连
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
ret = s.connect_ex((self.b_ip, self.b_telem_port))
if ret == 0:
self.sock_b = s
self.b_connected = True
logger.info(f"✅ 连接 B 遥测端口成功: {self.b_ip}:{self.b_telem_port}")
return
except:
pass
logger.error(f"❌ B 遥测端口连接失败,重试...")
time.sleep(1)
"""
def connect_b_telem(self):#2026/4/16启用修复休眠断开重连
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((self.b_ip, self.b_telem_port))
self.sock_f = s
logger.info(f"✅ 连接 B 遥测端口成功: {self.b_ip}:{self.b_telem_port}")
return
except Exception as e:
logger.error(f"❌ B 遥测 连接失败,重试...")
time.sleep(1)
"""
"""
def loop(self):#2026/4/16停用不能修复休眠断开重连
while True:
sock_b = self.connect_b_telem()
try:
while True:
data = sock_b.recv(4096)
if not data:
break
hex_dump(data, "📥 收到 B 遥测帧")
# 1. 转换后发 D
d_data = self.converter.convert(data)
if d_data:
self.udp_d.sendto(d_data, (self.d_ip, self.d_port))
# 2. 原样发 C
self.udp_c.sendto(data, (self.c_ip, self.c_port))
except Exception as e:
logger.error(f"⚠️ B 遥测链路异常: {e}")
finally:
sock_b.close()
"""
"""
def loop(self):#2026/4/16启动修复休眠断开重连
while True:
if self.sock_b is None:
self.connect_b_telem()
try:
data = self.sock_b.recv(4096)
if not data:
break
hex_dump(data, "📥 收到 B 遥测帧")
# 1. 转换后发 D
d_data = self.converter.convert(data)
if d_data:
self.udp_d.sendto(d_data, (self.d_ip, self.d_port))
# 2. 原样发 C
self.udp_c.sendto(data, (self.c_ip, self.c_port))
except:
self.sock_b = None
logger.error(f"⚠️ B 遥测链路异常: ")
time.sleep(1)
"""
def update_telem_frame(self, frame):#2026/4/19
"""根据全局变量修改遥测帧(你新需求的核心)"""
if len(frame) < 84:
return frame
# 包代号在 B[83]从0开始
pkt_id = frame[83]
if pkt_id not in B2C_UPDATE_RULES:
return frame
# 用户数据域从 B[120] 开始
if len(frame) <= 120:
return frame
user_data = bytearray(frame[120:])
rules = B2C_UPDATE_RULES[pkt_id]
for (code, byte_off, bit_off, bit_len) in rules:
val = get_param(code, 0)
set_bits(user_data, byte_off, bit_off, bit_len, val)
# 替换回原帧
new_frame = bytearray(frame)
new_frame[120:] = user_data
# logger.info(f"更新后的B遥测帧{new_frame}")
hex_dump(new_frame, " 更新 B 遥测帧")
return bytes(new_frame)
def loop(self):#2026/4/16新加不能修复休眠断开重连
logger.info(f"✅ B→D/C 遥测转发启动")
self.connect_b_telem()
self.k6466_send_count = 0
while True:
if not self.b_connected:
self.connect_b_telem()
time.sleep(0.5)
continue
#sock_b = self.connect_b_telem() #启动2026/4/16
try:
data = self.sock_b.recv(4096)
# 截取前324个字节 2026/4/20
data = data[:324]
if not data:
'''
self.not_received_count += 1#注释2026/4/16
time.sleep(0.2)
'''
continue
else:
self.not_received_count = 0
'''
if self.not_received_count > 100:
logger.error("❌ 没有收到B 遥测,可能断开,重连中...")#注释2026/4/16
self.b_connected = False
self.sock_b.close()
time.sleep(1)
continue
'''
hex_dump(data, "📥 收到 B 遥测帧")
# 1. 转换后发 D
d_data = self.converter.convert(data)
if d_data:
self.udp_d.sendto(d_data, (self.d_ip, self.d_port))
# 2. 原样发 C删除
#self.udp_c.sendto(data, (self.c_ip, self.c_port))
print(f"A2BForward.ck_ab_flag={A2BForward.ck_ab_flag}")
#2.修改部分遥测量发C 2026/4/18
data = self.update_telem_frame(data)
if A2BForward.ck_ab_flag != False:#c测控AB通道正常的话向C软件发送遥测
self.udp_c.sendto(data, (self.c_ip, self.c_port))
# 2026/4/30 发给C软件的data同样给G软件发一份
self.udp_g.sendto(data, (self.g_ip, self.g_port))
if A2BForward.ck_c_flag == True:
self.k6466_send_count = 0
if A2BForward.ck_c_flag == False and self.k6466_send_count<6:#如果测控C通道异常给G软件发送遥控指令。
self.udp_g.sendto(cmd_K6466_byte, (self.g_cmd_ip, self.g_cmd_port))
time.sleep(0.1)
self.udp_e.sendto(cmd_K6466_byte, (self.e_ip, self.e_port))
self.k6466_send_count += 1
time.sleep(0.1)
cmd_K6466_byte_hex= cmd_K6466_byte.hex(" ").upper()
logger.info(f"给G软件-BD项目数据中继发送指令K6466出站数据{cmd_K6466_byte_hex}IP{self.g_cmd_ip}PORT{self.g_cmd_port}")
logger.info(f"给E软件-专项软件中继发送指令K6466出站数据{cmd_K6466_byte_hex}IP{self.e_ip}PORT{self.e_port}")
# self.udp_g.sendto(cmd_K6466_byte, (self.g_cmd_ip, self.g_cmd_port))
# self.k6466_send_count += 1
# time.sleep(0.1)
# cmd_K6466_byte_hex= cmd_K6466_byte.hex(" ").upper()
# logger.info(f"给G软件-BD项目数据中继发送指令K6466出站数据{cmd_K6466_byte_hex}IP{self.g_cmd_ip}PORT{self.g_cmd_port}")
except Exception as e:
logger.error(f"❌ B→D/C 接收异常: {e}")
self.b_connected = False #可以先不使用
time.sleep(0.1)

20
core/global_params.py Normal file
View File

@@ -0,0 +1,20 @@
# 全局遥测参数存储器
# 结构:{ "参数代号": 值 }
TELEM_PARAMS = {}
# 分类顺序(你要求的顺序)
# 1. 测控
# 2. 星间链路终端
# 3. SADA
# 4. 能源
def set_param(param_code, value):
"""设置参数:代号 + 值"""
TELEM_PARAMS[param_code] = value
def get_param(param_code, default=0):
"""获取参数值"""
return TELEM_PARAMS.get(param_code, default)
def clear_params():
TELEM_PARAMS.clear()

31
core/global_vars.py Normal file
View File

@@ -0,0 +1,31 @@
# ======================================================
# 全局变量:整星安全模式 / 电池电压监测 / 报警状态
# ======================================================
# ---------------------- A 软件遥测全局 ----------------------
g_M002YV020 = 0.0 # Vbat1 整组电池电压
g_M010YV012 = 0.0 # Vbat2 整组电池电压
g_M004YV002 = 0.0
g_M004YV004 = 0.0
g_M004YV006 = 0.0
g_M004YV008 = 0.0
g_M004YV010 = 0.0
g_M004YV012 = 0.0
g_M004YV014 = 0.0
g_M004YV016 = 0.0
g_M004YV018 = 0.0
# ---------------------- B 软件阈值全局 ----------------------
g_M050YV001 = 30.0 # 下限阈值1 默认29V
g_M050YV002 = 29.0 # 下限阈值2 默认29V
g_M050YV003 = 25.0 # 下限阈值3 默认29V
# ---------------------- 报警输出全局 ----------------------
g_M050YS055 = 0 # VBOD1 过放电1
g_M050YS056 = 0 # VBOD2 过放电2 / 整星安全模式触发
g_M050YS057 = 0 # VBOD3 过放电3
g_M050YG073 = 0 # 供电模式 0=正常 1=安全模式
# ---------------------- 连续3次判断计数器 ----------------------
g_vbat_check_cnt = 0
g_vbat_fail_flag = [False, False, False]

287
core/link_manager.py Normal file
View File

@@ -0,0 +1,287 @@
# import threading
# from core.forward_engine import UdpForward, Tcp2UdpForward
# from config.settings import LOCAL_PORT, REMOTE
# # 导入转换器
# from converter.b_to_d import B2DConverter
# from converter.a_to_b import A2BConverter
# from converter.b_to_e import B2EConverter
# from converter.a_to_c import A2CConverter
# class LinkManager:
# def __init__(self):
# self.links = []
# def register_all(self):
# # B→D
# self.links.append(
# Tcp2UdpForward(
# remote_tcp_ip=REMOTE["B"]["ip"],
# remote_tcp_port=REMOTE["B"]["port"],
# udp_ip=REMOTE["D"]["ip"],
# udp_port=REMOTE["D"]["port"],
# converter=B2DConverter()
# )
# )
# # A→B
# self.links.append(
# UdpForward(
# local_port=LOCAL_PORT["A_B"],
# remote_ip=REMOTE["B"]["ip"],
# remote_port=REMOTE["B"]["port"],
# converter=A2BConverter()
# )
# )
# # A→C
# self.links.append(
# UdpForward(LOCAL_PORT["A_C"], REMOTE["C"]["ip"], REMOTE["C"]["port"], A2CConverter())
# )
# # B→E
# self.links.append(
# UdpForward(LOCAL_PORT["B_E"], REMOTE["E"]["ip"], REMOTE["E"]["port"], B2EConverter())
# )
# def start(self):
# for link in self.links:
# threading.Thread(target=link.loop, daemon=True).start()
import threading
from core.forward_engine import UdpForward, Tcp2UdpForward
from config.settings import LOCAL_PORT, REMOTE
from config_loader import cfg
from converter.b_to_d import B2DConverter
from converter.a_to_b import A2BConverter
from converter.b_to_e import B2EConverter
from converter.a_to_c import A2CConverter
# ==================== 新增导入 ====================
from converter.c_to_b import C2BConverter
from converter.c_to_a import C2AConverter
from core.single_to_dual import SingleToDualForward
from core.tcp_single_to_dual import TcpSingleToDualForward
#from core.a_to_b_forward import A2BForward
from core.a2b_forward import A2BForward
#from core.udp_c_remote import UdpRemoteC
from core.udp_c_remote import UdpCRemote
from core.f_to_a_forward import FToAForward # 新增导入
class LinkManager:
def __init__(self):
self.links = []
def register_all(self):
# # B→D
# self.links.append(
# Tcp2UdpForward(
# remote_tcp_ip=REMOTE["B"]["ip"],
# remote_tcp_port=REMOTE["B"]["port"],
# udp_ip=REMOTE["D"]["ip"],
# udp_port=REMOTE["D"]["port"],
# converter=B2DConverter()
# )
# )
# B → D转换 + B → C无条件转发2026/4/2 B软件换双端TC,TM后停用
# self.links.append(Tcp2UdpForward(
# remote_tcp_ip=REMOTE["B"]["ip"], remote_tcp_port=REMOTE["B"]["port"],
# udp_ip=REMOTE["D"]["ip"], udp_port=REMOTE["D"]["port"],
# converter=B2DConverter(),
# # 👇 新增:同时转发给 C
# forward_c_ip=REMOTE["C"]["ip"],
# forward_c_port=REMOTE["C"]["port"]
# ))
# ========== 1. B 遥测 3078 → D + C ==========2026/4/2更新 B软件换双端TC,TM
# self.links.append(Tcp2UdpForward(
# b_ip=REMOTE["B"]["ip"],
# b_telem_port=REMOTE["B"]["port_telem"], # 3078
# udp_d_ip=REMOTE["D"]["ip"],
# udp_d_port=REMOTE["D"]["port"],
# converter=B2DConverter(),
# c_ip=REMOTE["C"]["ip"],
# c_port=REMOTE["C"]["port"]
# ))
self.links.append(Tcp2UdpForward(
# 2026/4/7更新 使用config.ini配置文件
b_ip=cfg.get("REMOTE", "B_IP"),
b_telem_port=cfg.get_int("REMOTE", "B_TELEM_PORT"), # 3078
udp_d_ip=cfg.get("REMOTE", "D_IP"),
udp_d_port=cfg.get_int("REMOTE", "D_PORT"),
converter=B2DConverter(),
c_ip=cfg.get("REMOTE", "C_IP"),
c_port=cfg.get_int("REMOTE", "C_PORT"),
#2026/4/30新增G软件-数字卫星C通道遥测遥控转发
g_ip = cfg.get("REMOTE", "B2G_IP"),
g_port = cfg.get_int("REMOTE", "B2G_PORT"),
g_cmd_ip = cfg.get("REMOTE", "G_CMD_IP"),
g_cmd_port = cfg.get_int("REMOTE", "G_CMD_PORT"),
#2026/5/11 新增E软件遥控转发
e_ip = cfg.get("REMOTE", "E_IP"),
e_port = cfg.get_int("REMOTE", "E_PORT"),
))
# A→B 2026/4/2停用
# self.links.append(
# UdpForward(
# local_port=LOCAL_PORT["A_B"],
# remote_ip=REMOTE["B"]["ip"],
# remote_port=REMOTE["B"]["port_remote"], ### 暂时是遥控接口,以后要改
# converter=A2BConverter()
# )
# )
# A→C
self.links.append(
#UdpForward(LOCAL_PORT["A_C"], REMOTE["C"]["ip"], REMOTE["C"]["port"], A2CConverter())
UdpForward(cfg.get_int("LOCAL","A_C"), cfg.get("REMOTE","C_IP"), cfg.get_int("REMOTE","C_PORT"), A2CConverter())#2026/4/7更新 使用config.ini配置文件
)
# B→E
self.links.append(
#UdpForward(LOCAL_PORT["B_E"], REMOTE["E"]["ip"], REMOTE["E"]["port"], B2EConverter())
UdpForward(cfg.get_int("LOCAL","B_E"),cfg.get("REMOTE","E_IP"), cfg.get_int("REMOTE","E_PORT"), B2EConverter())#2026/4/7更新 使用config.ini配置文件
)
# # ==================== 新增两条链路 ====================
# # C → B 透传
# self.links.append(
# UdpForward(
# local_port=LOCAL_PORT["C_B"],
# remote_ip=REMOTE["B"]["ip"],
# remote_port=REMOTE["B"]["port"],
# converter=C2BConverter()
# )
# )
# # C → A 转发
# self.links.append(
# UdpForward(
# local_port=LOCAL_PORT["C_A"],
# remote_ip=REMOTE["A"]["ip"],
# remote_port=REMOTE["A"]["port"],
# converter=C2AConverter()
# )
# )
# ===================== 核心修改 =====================
# # C 单端口 → 同时发给 B 和 A
# self.links.append(SingleToDualForward(
# local_port=LOCAL_PORT["C"],
# # 发给 B
# tcp_target_ip=REMOTE["B"]["ip"],
# tcp_target_port=REMOTE["B"]["port"],
# converter_tcp=C2BConverter(),
# # 发给 A
# udp_target_ip=REMOTE["A"]["ip"],
# udp_target_port=REMOTE["A"]["port"],
# converter_udp=C2AConverter()
# ))
# ===================== 【C 改为 TCP】 2026/3/31停用=====================
# self.links.append(TcpSingleToDualForward(
# local_port=LOCAL_PORT["C"],
# # 转发 B
# tcp_target_ip=REMOTE["B"]["ip"],
# tcp_target_port=REMOTE["B"]["port"],
# converter_tcp=C2BConverter(),
# # 转发 A
# udp_target_ip=REMOTE["A"]["ip"],
# udp_target_port=REMOTE["A"]["port"],
# converter_udp=C2AConverter()
# ))
# # 在 register_all 中添加:
# self.links.append(
# A2BForward(
# local_port=LOCAL_PORT["A_LISTEN"],
# tcp_target_ip=REMOTE["B"]["ip"],
# tcp_target_port=REMOTE["B"]["port"]
# )
# )
'''
# 在 register_all 末尾加A-B
self.links.append(
A2BForward(
#local_port=7100,
local_port=LOCAL_PORT["A_B"], #2026/4/2改
tcp_ip=REMOTE["B"]["ip"],
#tcp_port=REMOTE["B"]["port"]
tcp_port=REMOTE["B"]["port_remote"] ##2026/4/2暂时改为遥控接口以后要改
)
)
'''
# A-B 2026/4/3
self.links.append(
A2BForward(
#local_port=LOCAL_PORT["A_B"], #REMOTE["A"]["port"], #2026/4/3改
local_port=cfg.get_int("LOCAL", "A_B"),
#b_ip=REMOTE["B"]["ip"],
b_ip=cfg.get("REMOTE", "B_IP"),#2026/4/7
#b_udp_port=REMOTE["B"]["port_udp"] # B UDP 端口
b_udp_port=cfg.get_int("REMOTE", "B_UDP_PORT")#2026/4/7
)
)
# ===================== C(UDP)遥控 → A + B =====================2026/4/2停用 B软件换双端TC,TM
# self.links.append(
# UdpRemoteC(
# local_port=6004, # C 发指令的 UDP 目标端口
# a_ip=REMOTE["A"]["ip"],
# a_port=REMOTE["A"]["port"],
# b_ip=REMOTE["B"]["ip"],
# b_port=REMOTE["B"]["port"],
# converter=C2AConverter() # 你原有遥控指令转换器
# )
# )
# ========== 2. C UDP 遥控 → A + B 3028 ========== 2026/4/2更新 B软件换双端TC,TM
self.links.append(
#UdpRemoteC(
UdpCRemote(
# local_udp_port=6004,
# a_ip=REMOTE["A"]["ip"],
# a_port=REMOTE["A"]["port"],
# b_ip=REMOTE["B"]["ip"],
# b_remote_port=REMOTE["B"]["port_remote"], # 3028
#local_udp_port=cfg.get_int("LOCAL", "C_AB"),#2026/4/7,
local_port=cfg.get_int("LOCAL", "C_AB"),#2026/4/7,
a_ip=cfg.get("REMOTE", "A_IP"),
a_port=cfg.get_int("REMOTE", "A_PORT"),
b_ip=cfg.get("REMOTE", "B_IP"),
b_remote_port=cfg.get_int("REMOTE", "B_REMOTE_PORT"), # 3028
b_gis_port = cfg.get_int("REMOTE","B_DICE_PORT"), # 2026/4/11新增
converter=C2AConverter(),
#2026/4//30新增E专项G测控C通道软件转发
e_ip=cfg.get("REMOTE", "E_IP"),
e_port=cfg.get_int("REMOTE", "E_PORT"),
g_ip=cfg.get("REMOTE", "G_CMD_IP"),
g_port=cfg.get_int("REMOTE", "G_CMD_PORT")
))
# ========== F → A 转发 新增 ==========2026/4/11废弃
# self.links.append(
# FToAForward(
# local_port=cfg.get_int("LOCAL", "UDP_F_LISTEN_PORT"),
# a_ip=cfg.get("REMOTE", "A_IP"),
# a_port=cfg.get_int("REMOTE", "A_PORT")
# )
# )
self.links.append(#7786
FToAForward(
f_ip=cfg.get("REMOTE", "F_IP"),
f_port=cfg.get_int("REMOTE", "F_PORT"),
a_ip=cfg.get("REMOTE", "A_IP"),
a_port=cfg.get_int("REMOTE", "A_FROM_B_PORT")
)
)
def start(self):
for link in self.links:
threading.Thread(target=link.loop, daemon=True).start()

64
core/single_to_dual.py Normal file
View File

@@ -0,0 +1,64 @@
import socket
import threading
from utils.logger import logger
from utils.hex_helper import hex_dump
class SingleToDualForward:
"""
单端口接收,同时发给两个目标:
1) 转发给 TCP 目标B
2) 同时转发给 UDP 目标A条件转发
"""
def __init__(
self,
local_port,
tcp_target_ip, tcp_target_port, converter_tcp,
udp_target_ip, udp_target_port, converter_udp
):
self.local_port = local_port
self.tcp_ip = tcp_target_ip
self.tcp_port = tcp_target_port
self.converter_tcp = converter_tcp # C→B
self.udp_ip = udp_target_ip
self.udp_port = udp_target_port
self.converter_udp = converter_udp # C→A
self.tcp_sock = None
self.udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def connect_tcp(self):
while True:
try:
self.tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_sock.connect((self.tcp_ip, self.tcp_port))
logger.info(f"✅ TCP 连接 B 成功 {self.tcp_ip}:{self.tcp_port}")
return
except Exception as e:
logger.error(f"❌ TCP 连接失败,重试: {e}")
threading.Event().wait(2)
def loop(self):
# 绑定单端口接收 C
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", self.local_port))
logger.info(f"✅ C 单端口监听启动: 0.0.0.0:{self.local_port}")
self.connect_tcp()
while True:
data, addr = sock.recvfrom(4096)
hex_dump(data, f"收到 C 指令 {addr}")
# ========== 1. 转发给 B无条件 ==========
try:
b_data = self.converter_tcp.convert(data)
if b_data:
self.tcp_sock.send(b_data)
except:
self.connect_tcp()
# ========== 2. 转发给 A条件判断 ==========
a_data = self.converter_udp.convert(data)
if a_data:
self.udp_sock.sendto(a_data, (self.udp_ip, self.udp_port))

View File

@@ -0,0 +1,45 @@
"""
2026/3/31
Tan Mingyan
2026/3/31/, 软件C使用UDP协议不再需要这个类
"""
import socket
import threading
from utils.hex_helper import hex_dump
from utils.logger import logger
class TcpForwardClient:
"""
专门用于:无条件原样转发 TCP 数据(发给 C 软件)
"""
def __init__(self, target_ip, target_port):
self.ip = target_ip
self.port = target_port
self.sock = None
self.lock = threading.Lock()
def connect(self):
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.ip, self.port))
with self.lock:
self.sock = s
logger.info(f"✅ C软件转发连接成功: {self.ip}:{self.port}")
return
except Exception as e:
logger.error(f"❌ C软件转发连接失败重连中: {e}")
threading.Event().wait(2)
def send(self, data):
"""原样转发,不修改任何数据"""
try:
with self.lock:
if self.sock:
self.sock.send(data)
#logger.info(f"✅ C软件转发成功: {self.ip}:{self.port}")
hex_dump(data,"B-C透明转发遥测帧")
return True
except:
self.connect()
return False

View File

@@ -0,0 +1,83 @@
import socket
import threading
from utils.logger import logger
from utils.hex_helper import hex_dump
class TcpSingleToDualForward:
"""
C 软件TCP 客户端
M 软件TCP 服务端(单端口监听)
收到数据后:
1) 转发给 BTCP
2) 条件转发给 AUDP
"""
def __init__(
self,
local_port,
tcp_target_ip, tcp_target_port, converter_tcp,
udp_target_ip, udp_target_port, converter_udp
):
self.local_port = local_port
self.tcp_ip = tcp_target_ip
self.tcp_port = tcp_target_port
self.converter_tcp = converter_tcp # C→B
self.udp_ip = udp_target_ip
self.udp_port = udp_target_port
self.converter_udp = converter_udp # C→A
self.tcp_sock = None # 发给 B 的连接
self.udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def connect_tcp_b(self):
while True:
try:
self.tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_sock.connect((self.tcp_ip, self.tcp_port))
logger.info(f"✅ 已连接 B 软件 {self.tcp_ip}:{self.tcp_port}")
return
except Exception as e:
logger.error(f"❌ 连接 B 失败,重连中: {e}")
threading.Event().wait(2)
def handle_client(self, client_socket):
self.connect_tcp_b()
while True:
try:
data = client_socket.recv(4096)
if not data:
logger.warning("⚠️ C 软件断开连接")
break
hex_dump(data, "收到 C 软件指令")
# ========== 1. 转发 B ==========
try:
b_data = self.converter_tcp.convert(data)
if b_data:
self.tcp_sock.send(b_data)
except:
self.connect_tcp_b()
# ========== 2. 转发 A ==========
a_data = self.converter_udp.convert(data)
if a_data:
self.udp_sock.sendto(a_data, (self.udp_ip, self.udp_port))
except Exception as e:
logger.error(f"客户端异常: {e}")
break
client_socket.close()
def loop(self):
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(("0.0.0.0", self.local_port))
server_sock.listen(5)
logger.info(f"✅ TCP 监听 C 软件: 0.0.0.0:{self.local_port}")
while True:
client_sock, addr = server_sock.accept()
logger.info(f"✅ C 软件已连接: {addr}")
threading.Thread(target=self.handle_client, args=(client_sock,), daemon=True).start()

369
core/udp_c_remote.py Normal file
View File

@@ -0,0 +1,369 @@
# import socket
# import threading
# from utils.logger import logger
# from utils.hex_helper import hex_dump
# class UdpRemoteC:#2026/4/2停用因为B软件用双端口TCP遥控端口3028
# """
# C 软件UDP发遥控指令 → M 接收 → 转发 A(UDP) + B(TCP)
# """
# def __init__(self, local_port, a_ip, a_port, b_ip, b_port, converter):
# self.local_port = local_port
# self.a_ip = a_ip
# self.a_port = a_port
# self.b_ip = b_ip
# self.b_port = b_port
# self.converter = converter
# # AUDP
# self.udp_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# # BTCP长连接
# self.tcp_b = None
# self.lock = threading.Lock()
# def connect_b(self):
# """B TCP 重连"""
# while True:
# try:
# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# s.connect((self.b_ip, self.b_port))
# with self.lock:
# self.tcp_b = s
# logger.info(f"✅ 遥控-B TCP 连接成功: {self.b_ip}:{self.b_port}")
# return
# except Exception as e:
# logger.error("❌ 遥控-B 连接失败,重连中...")
# threading.Event().wait(2)
# def loop(self):
# # 监听 C UDP
# sock_c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# #sock_c.bind(("0.0.0.0", self.local_port))
# sock_c.bind(("127.0.0.1", self.local_port))
# logger.info(f"✅ 监听 C 遥控 UDP: {self.local_port}")
# # 连接 B
# self.connect_b()
# while True:
# data, addr = sock_c.recvfrom(4096)
# if not data:
# continue
# hex_dump(data, "📥 收到 C 遥控指令")
# # 1. 转换格式(你原有 C→A 转换器)
# a_frame = self.converter.convert(data)
# if not a_frame:
# a_frame = data
# # 2. 转发 AUDP
# self.udp_a.sendto(a_frame, (self.a_ip, self.a_port))
# logger.info("📤 已发往 A")
# # 3. 转发 BTCP
# try:
# with self.lock:
# if self.tcp_b:
# self.tcp_b.send(data)
# logger.info("📤 已发往 B")
# except:
# self.connect_b()
# try:
# with self.lock:
# self.tcp_b.send(data)
# except:
# pass
# import socket
# import threading
# from utils.logger import logger
# from utils.hex_helper import hex_dump
# from core.b_gis_client import BGisClient # 新增
# class UdpRemoteC: #2026/4/11停用
# """
# C(UDP 6004) 遥控指令 → M → 转发 A(UDP) + B(3028 TCP Server)
# """
# def __init__(self, local_udp_port, a_ip, a_port, b_ip, b_remote_port,b_gis_port, converter):
# self.local_port = local_udp_port # 6004
# self.a_ip = a_ip
# self.a_port = a_port
# self.b_ip = b_ip
# self.b_remote_port = b_remote_port # 3028
# self.converter = converter
# self.b_gis_port = b_gis_port # 新增
# self.udp_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# self.tcp_b = None
# self.lock = threading.Lock()
# # 新增 B 地测客户端2026/4/11
# self.b_gis = BGisClient(self.b_ip, self.b_gis_port)
# def connect_b_remote(self):
# """连接 B 遥控 TCP Server 3028"""
# while True:
# try:
# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# logger.info(f"B 遥控端口{self.b_ip}, {self.b_remote_port}")
# s.connect((self.b_ip, self.b_remote_port))
# with self.lock:
# self.tcp_b = s
# logger.info(f"✅ 已连接 B 遥控端口 3028")
# return
# except Exception as e:
# logger.error("❌ 连接 B 3028 失败,重试中...")
# threading.Event().wait(2)
# def loop(self):
# # 绑定 UDP 监听 C
# sock_c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# sock_c.bind(("0.0.0.0", self.local_port))
# logger.info(f"✅ UDP 监听 C 遥控端口: {self.local_port}")
# # 连接 B 遥控
# self.connect_b_remote()
# while True:
# data, addr = sock_c.recvfrom(4096)
# if not data:
# continue
# hex_dump(data, "📥 收到 C 遥控指令")
# # 转发 A
# a_frame = self.converter.convert(data)
# if a_frame:
# self.udp_a.sendto(a_frame, (self.a_ip, self.a_port))
# logger.info("C遥控指令已发往 B")
# # 转发 B 3028
# try:
# with self.lock:
# self.tcp_b.send(data)
# self.b_gis.send(data) # 新增:无条件转发给 B 地测端口
# except:
# self.connect_b_remote()
# try:
# with self.lock:
# self.tcp_b.send(data)
# except:
# pass
# import socket
# import threading
# import time
# from core.b_gis_client import BGisClient
# from utils.logger import logger
# from config_loader import cfg
# class UdpCRemote:
# def __init__(self, local_port, a_ip, a_port, b_ip, b_remote_port, b_gis_port, converter):
# self.local_port = local_port
# self.a_ip = a_ip
# self.a_port = a_port
# self.b_ip = b_ip
# self.b_remote_port = b_remote_port
# self.b_gis_port = b_gis_port # 新增
# self.converter = converter
# # 初始化B地测客户端
# self.b_gis = BGisClient(self.b_ip, self.b_gis_port)
# self.sock_c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# self.sock_c.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# self.sock_b = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# self.b_connected = False
# def connect_b(self):
# while True:
# try:
# self.sock_b.connect((self.b_ip, self.b_remote_port))
# self.b_connected = True
# logger.info(f"✅ 连接 B 遥控端口成功:{self.b_ip}:{self.b_remote_port}")
# return
# except Exception as e:
# logger.error(f"❌ 连接 B 遥控端口失败,重试:{e}")
# time.sleep(1)
# def loop(self):
# try:
# self.sock_c.bind(("0.0.0.0", self.local_port))
# logger.info(f"✅ C 遥控监听UDP {self.local_port}")
# except Exception as e:
# logger.error(f"❌ C 监听失败:{e}")
# return
# self.connect_b()
# while True:
# data, addr = self.sock_c.recvfrom(8192)
# if not data:
# continue
# try:
# forward_data = self.converter.convert(data)
# logger.info(f"📤 C→B 指令转发:{forward_data.hex()}")
# # 发送给原有 B 端口
# if self.b_connected:
# self.sock_b.send(forward_data)
# # ===================== 新增:无条件转发给 B 地测 4028 =====================
# self.b_gis.send(forward_data)
# # 发送给 A
# self.sock_a.sendto(forward_data, (self.a_ip, self.a_port))
# except Exception as e:
# logger.error(f"❌ C 指令转发异常:{e}")
import socket
import time
from utils.logger import logger
special_cmd_list={#分别是K6794 专项星间实时遥测发送使能-271K6794 专项星间实时遥测发送使能-271K6795专项星间实时遥测发送禁止-271K6465入站-271K6466 出站-271
b"EB9001073500EB90B571A50911C00100E505F4ED4500000000000000000000000000000000000000000000000000000000DDAAAAAAAA88880C1B0C02023366A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5CCCCCCCCCCCCCCCC7FCEA5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5DF0B91225716",
b"EB9001073500EB90B571A50911C00100E505F4ED4500000000000000000000000000000000000000000000000000000000DDAAAAAAAA88880C1B0C02024477A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5CCCCCCCCCCCCCCCCB2EBA5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5DF0B91945716",
b"EB9001073500EB90B571A50911C00100E505F4ED4500000000000000000000000000000000000000000000000000000000DDAAAAAAAA88880C1B0C020233AAA5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5CCCCCCCCCCCCCCCCF26EA5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5DF0B91795716",
b"EB9001073500EB90B571A50911C00100E505F4ED4500000000000000000000000000000000000000000000000000000000DDAAAAAAAA88880C1B0C020233BBA5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5CCCCCCCCCCCCCCCCECB6A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5DF0B91CC5716"
}
class UdpCRemote:
def __init__(self, local_port, a_ip, a_port, b_ip, b_remote_port, b_gis_port, converter, e_ip, e_port, g_ip, g_port):
self.local_port = local_port
self.a_ip = a_ip
self.a_port = a_port
self.b_ip = b_ip
self.b_remote_port = b_remote_port
self.b_gis_port = b_gis_port # 地测端口 4028
##2026/4/30 部分特殊指令转发
self.e_ip = e_ip
self.e_port = e_port
self.g_ip = g_ip
self.g_port = g_port
self.converter = converter
# C 指令监听UDP
self.sock_c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock_c.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 发给 AUDP
self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 发给 B 原遥控端口 TCP3028
self.sock_b = None
self.b_connected = False
# 发给 B 地测端口 TCP4028—— 这里是你要的新增!
self.sock_b_gis = None
self.b_gis_connected = False
#发给E专项
self.udp_e = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#发给G
self.udp_g = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 连接 B 原遥控端口 3028
def connect_b_remote(self):
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3)
s.connect((self.b_ip, self.b_remote_port))
self.sock_b = s
self.b_connected = True
logger.info(f"✅ 连接 B 遥控端口成功:{self.b_remote_port}")
return
except Exception as e:
logger.error(f"❌ B 遥控端口连接失败,重试:{e}")
time.sleep(1)
# 连接 B 地测端口 4028 —— 完整实现!
def connect_b_gis(self):
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3)
s.connect((self.b_ip, self.b_gis_port))
self.sock_b_gis = s
self.b_gis_connected = True
logger.info(f"✅ 连接 B 地测端口成功:{self.b_gis_port}")
return
except Exception as e:
logger.error(f"❌ B 地测端口连接失败,重试:{e}")
time.sleep(1)
def loop(self):
try:
self.sock_c.bind(("0.0.0.0", self.local_port))
logger.info(f"✅ C 指令监听:{self.local_port}")
except Exception as e:
logger.error(f"❌ C 监听失败:{e}")
return
# 启动时同时连接两个端口
self.connect_b_remote()
time.sleep(0.01)
self.connect_b_gis()
while True:
data, addr = self.sock_c.recvfrom(8192)
if not data:
continue
try:
# 转换指令
forward_data = self.converter.convert(data)
#logger.info(f"📤 转发C指令{forward_data.hex()}")
# 1. 转发 AUDP
if forward_data is not None:
self.sock_a.sendto(forward_data, (self.a_ip, self.a_port))
# 2. 转发 B 遥控端口TCP
if self.b_connected:
try:
self.sock_b.sendall(data)
except:
self.b_connected = False
# 3. 转发 B 地测端口TCP—— 无条件转发!
if self.b_gis_connected:
try:
self.sock_b_gis.sendall(data)
logger.info(f"📤 转发C指令到B地测端口{data.hex()}")
except:
self.b_gis_connected = False
## 判断指令是否是K6465K6466K6794K6795,是就转发给D和G软件
# 转换为小写的十六进制字节串进行比较
data_hex_lower = data.hex().lower().encode()
# 判断是否在列表中(列表中的命令也预先转为小写)
special_cmd_lower = {cmd.lower() for cmd in special_cmd_list}
if data_hex_lower in special_cmd_lower:
#print("匹配成功")
#if data in special_cmd_list:
# 转发E专项软件UDP
self.udp_e.sendto(data, (self.e_ip, self.e_port))
#转发G软件测控C通道UDP
self.udp_g.sendto(data, (self.g_ip, self.g_port))
logger.info(f"📤 转发C指令到E专项软件端口{self.e_port},G软件BD项目数据中继端口{self.g_port}, 指令:{data.hex()}")
# 自动重连
if not self.b_connected:
self.connect_b_remote()
if not self.b_gis_connected:
self.connect_b_gis()
except Exception as e:
logger.error(f"❌ 指令转发异常:{e}")