first commit

This commit is contained in:
lihansani
2026-07-13 15:37:05 +08:00
commit 8b11aa2efc
299 changed files with 206339 additions and 0 deletions

View File

@@ -0,0 +1,367 @@
import struct
import socket
import time
import random
from random import uniform, randint
import math
import threading
from vdes_message_unpack import VdesMessageUnpack
SHIP_MMSIS = [random.randint(336295939, 336295969) for _ in range(10)]
SHIP_STATUS = {}
# 生成包含30艘船信息的列表MMSI、纬度、经度、航向
SHIP_LIST = [
(
336295939 + i, # MMSI号
round(random.uniform(30.0, 34.0), 6),
round(random.uniform(123.5, 126.0), 6),
random.randint(0, 359)
) for i in range(30)
]
# 初始化船只状态
for ship in SHIP_LIST:
mmsi, init_lat, init_lon, heading = ship
SHIP_STATUS[mmsi] = {
'current_lat': init_lat,
'current_lon': init_lon,
'heading': heading,
'speed': 280.24 #加速100倍用于演示
}
YANGTZE_ESTUARY_LAT = 31.23
YANGTZE_ESTUARY_LON = 123.87
RANGE_NM = 5 # 5海里范围
current_lat = YANGTZE_ESTUARY_LAT # 当前纬度
current_lon = YANGTZE_ESTUARY_LON # 当前经度
own_heading = 90 # 固定航向 (50度)
def generate_random_position():
"""生成船只位置(基于当前位置和航向计算)"""
global current_lat, current_lon, own_heading
# 速度为10.24节(来自GPS报文设置)
speed_knots = 300.24 #加速100倍用于演示
# 转换为公里/小时 (1节 = 1.852公里/小时)
speed_kmh = speed_knots * 1.852
# 转换为公里/秒 (发送间隔为1秒)
speed_kmps = speed_kmh / 3600
# 计算1秒内移动的距离(公里)
distance_km = speed_kmps * 1
# 将航向转换为弧度
heading_rad = math.radians(own_heading)
# 计算纬度和经度变化
# 1度纬度 ≈ 111公里
delta_lat = distance_km / 111.0 * math.cos(heading_rad)
# 1度经度 ≈ 111公里 * cos(纬度)
delta_lon = distance_km / (111.0 * math.cos(math.radians(current_lat))) * math.sin(heading_rad)
# 更新当前位置
current_lat += delta_lat
current_lon += delta_lon
# 转换为度分格式*10^7
longitude = int(current_lon * 10000000)
latitude = int(current_lat * 10000000)
return longitude, latitude
def generate_ship_position(mmsi):
"""根据船只MMSI生成当前位置"""
global SHIP_STATUS
ship = SHIP_STATUS[mmsi] #获取全局船只中的一个进行修改
speed_knots = ship['speed']
speed_kmh = speed_knots * 1.852
speed_kmps = speed_kmh / 3600
distance_km = speed_kmps * 1
# 航向转弧度
heading_rad = math.radians(ship['heading'])
# 计算经纬度变化
delta_lat = distance_km / 111.0 * math.cos(heading_rad)
delta_lon = distance_km / (111.0 * math.cos(math.radians(ship['current_lat']))) * math.sin(heading_rad)
# 更新船只状态
ship['current_lat'] += delta_lat
ship['current_lon'] += delta_lon
# 返回新位置
return ship['current_lon'], ship['current_lat']
def create_tcp_packet(packet_type, content):
"""创建符合VDES TCP协议的报文"""
# 报文帧头 (4字节)
header = b'####'
# 报文类型 (4字节大端)
type_field = struct.pack('>I', packet_type)
# 报文长度 (4字节大端内容长度)
length = len(content)
length_field = struct.pack('>I', length)
# 构建完整报文
packet = header + type_field + length_field + content
return packet
def create_gps_packet():
"""创建GPS解析数据报文(类型7)的内容"""
# 模拟GPS数据 (表11定义)
lon, lat = generate_random_position()
utc_time = 1680000000 # UTC时间 (8字节)
longitude = lon # 经度 (4字节东经120.5度)
latitude = lat # 纬度 (4字节北纬30.5度)
altitude = 50 # 海拔高度 (4字节50米)
used_sats = 10 # 定位使用的卫星数 (2字节)
gps_sats = 8 # GPS卫星数 (2字节)
beidou_sats = 7 # 北斗卫星数 (2字节)
gps_status = 1 # GPS状态 (1字节单点定位)
position_mode = 3 # 定位模式 (1字节3D定位)
hdop = 2550 # 水平精度因子 (4字节25.5米)
speed = 1024 # 运动速度 (4字节10.24节)
direction = own_heading # 运动方向 (4字节359度)
# 打包为二进制 (大端字节序)
content = struct.pack(
'>QiiiHHHBBIII', # 格式: Q(8),i(4),i(4),i(4),H(2),H(2),H(2),B(1),B(1),I(4),I(4),I(4)
utc_time,
longitude,
latitude,
altitude,
used_sats,
gps_sats,
beidou_sats,
gps_status,
position_mode,
hdop,
speed,
direction
)
return content
def create_ais_vdes_info_packet(demod_content, Demodulator_id):
"""创建解调信息报文(类型12)的内容"""
# 模拟解调信息数据
message_type = 12 # 消息类型 (1字节)
satellite_id = 1 # 卫星ID (1字节)
demodulator_id = Demodulator_id # 解调器编号 (1字节)
demod_slot = 1234 # 解调时隙 (2字节)
receive_delay = 5678 # 接收延迟 (2字节单位us)
snr = 25.75 # 信噪比 (2字节dB)
# 处理信噪比 (高8位整数部分低8位小数部分)
snr_int = int(snr)
snr_frac = int((snr - snr_int) * 100) # 转换为0-99的小数部分
snr_packed = (snr_int & 0xFF) << 8 | (snr_frac & 0xFF)
# 打包为二进制 (大端字节序)
content = struct.pack(
'>BBBHHH', # 格式: B(1),B(1),B(1),H(2),H(2),h(2)
message_type,
satellite_id,
demodulator_id,
demod_slot,
receive_delay,
snr_packed
)
return content + demod_content
def encode_ais_demod_fields(SHIP_MMSI, lat, lon, heading):
"""编码AIS解调信息字段"""
# 初始化二进制位列表
bits = []
# 示例数据
message_id = 1 # 消息ID (6 bits)
repeat_indicator = 0 # 转发指示符 (2 bits)
user_id = SHIP_MMSI # 用户ID/MMSI (30 bits)
navigation_status = 8 # 导航状态 (4 bits) - 航行中
rot = -128 # 旋转速率 (8 bits) - 无信息
sog = 102 # 地面航速 (10 bits) - 10.2节
position_accuracy = 1 # 位置准确度 (1 bit) - 高
longitude, latitude = lon, lat # 经纬度
cog = heading # 地面航线 (12 bits) - 90.0°
true_heading = heading # 实际航向 (9 bits) - 90°
timestamp = 30 # 时戳 (6 bits) - UTC秒
special_maneuver = 0 # 特定操纵指示符 (2 bits)
spare = 0 # 备用 (3 bits)
raim_flag = 0 # RAIM标志 (1 bit)
comm_state = 0 # 通信状态 (19 bits)
# 添加各个字段(按照协议顺序)
bits.append(format(message_id, '06b')) # 消息ID
bits.append(format(repeat_indicator, '02b')) # 转发指示符
bits.append(format(user_id, '030b')) # 用户ID
bits.append(format(navigation_status, '04b')) # 导航状态
# 旋转速率 (8位有符号整数)
rot_bits = rot & 0xFF
bits.append(format(rot_bits, '08b'))
# 地面航速 (10位)
bits.append(format(min(sog, 1023), '010b'))
# 位置准确度 (1位)
bits.append(str(position_accuracy))
# 经度 (28位)
# 转换为1/10,000分钟单位
longitude_min = int(longitude * 60 * 10000)
bits.append(format(longitude_min & 0x0FFFFFFF, '028b'))
# 纬度 (27位)
latitude_min = int(latitude * 60 * 10000)
bits.append(format(latitude_min & 0x07FFFFFF, '027b'))
# 地面航线 (12位)
bits.append(format(min(cog, 3600), '012b'))
# 实际航向 (9位)
bits.append(format(min(true_heading, 511), '09b'))
# 时戳 (6位)
bits.append(format(min(timestamp, 63), '06b'))
# 特定操纵指示符 (2位)
bits.append(format(special_maneuver, '02b'))
# 备用 (3位)
bits.append(format(spare, '03b'))
# RAIM标志 (1位)
bits.append(str(raim_flag))
# 通信状态 (19位)
bits.append(format(comm_state, '019b'))
# 合并所有位
bit_string = ''.join(bits)
# 验证总位数
total_bits = len(bit_string)
if total_bits != 168:
print(f"警告: 总位数应为168实际为{total_bits}")
# 将位字符串转换为字节
binary_int = int(bit_string, 2) # 将二进制字符串转换为整数
byte_length = (len(bit_string) + 7) // 8 # 计算需要的字节数
binary_data = binary_int.to_bytes(byte_length, 'big') # 转换为字节
return binary_data
def encode_vdes_demod_fields(Type, Fragment_num, MMSI, Payload):
"""编码VDES解调信息字段"""
source_id = 12345678 # 信源电台ID
satellite_id = 1 # 卫星ID
session_id = 100 # 会话ID
dest_id = MMSI # 目的地电台ID
fragment_num = Fragment_num # 片段编号
binary_payload = Payload # 载荷内容
# 计算载荷大小 (字段3到8的大小)
payload_size = 4 + 1 + 1 + 4 + 2 + len(binary_payload)
# 打包为二进制 (大端字节序)
content = struct.pack(
'>B H I B B I H', # 格式: B(1),H(2),I(4),B(1),B(1),I(4),H(2)
Type, # 类型 (1字节)
payload_size, # 载荷大小 (2字节)
source_id, # 信源电台ID (4字节)
satellite_id, # 卫星ID (1字节)
session_id, # 会话ID (1字节)
dest_id, # 目的地电台ID (4字节)
fragment_num # 片段编号 (2字节)
)
return content + binary_payload # 添加载荷内容
def print_binary(data):
"""打印二进制数据"""
print("十六进制表示:", data.hex())
print("二进制表示:", ' '.join(format(byte, '08b') for byte in data))
def send_data_continuously():
"""启动Socket服务端"""
host = '127.0.0.1' # 监听所有网络接口
port = 10
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(1)
print(f"Socket服务端已启动监听 {host}:{port}")
while True:
try:
conn, addr = s.accept()
print(f"客户端连接来自: {addr}")
unpacker = VdesMessageUnpack()
mmsi: int
fragment_num = 0
while True:
# 接收客户端数据
data = conn.recv(65536) # 接收客户端数据
if data:
print(f"收到VDES客户端消息: {data.hex()}")
try:
frame_type, message = unpacker.unpack_tcp_packet(data)
mmsi = unpacker.get_mmsi()
if frame_type == 0: # 起始片段
Type = 30
fragment_num += 1
elif frame_type == 1: # 延续片段
Type = 31
fragment_num += 1
elif frame_type == 2: # 结束片段
Type = 32
fragment_num += 1
vdes_demod_content = encode_vdes_demod_fields(Type, fragment_num, mmsi, message)
vdes_data = create_ais_vdes_info_packet(vdes_demod_content, 0) #0-3代表vdes解调器
vdes_packet = create_tcp_packet(12, vdes_data)
conn.sendall(vdes_packet)
time.sleep(0.5)
if not unpacker.get_unpack_statu():
fragment_num = 0 # 重置片段编号
print("解析消息完成")
except ValueError as e:
print(f"报文解析错误: {e}")
# 向客户端发送数据
# try:
# # 生成并发送AIS报文
# selected_ship = random.choice(SHIP_LIST)
# mmsi = selected_ship[0]
# lon, lat = generate_ship_position(mmsi)
# heading = SHIP_STATUS[mmsi]['heading']
# ais_demod_content = encode_ais_demod_fields(mmsi, lat, lon, heading)
# ais_data = create_ais_vdes_info_packet(ais_demod_content, 4) #4-5代表ais解调器
# ais_packet = create_tcp_packet(12, ais_data)
# conn.sendall(ais_packet)
# # 生成并发送GPS报文
# gps_content = create_gps_packet()
# gps_packet = create_tcp_packet(7, gps_content)
# print(f"发送GPS报文: {gps_packet.hex()}")
# conn.sendall(gps_packet)
# time.sleep(0.5)
# except (ConnectionResetError, BrokenPipeError):
# print("客户端断开连接")
# break
except KeyboardInterrupt:
print("\n服务端已停止")
break
except Exception as e:
print(f"服务端错误: {e}")
if __name__ == "__main__":
send_data_continuously()

View File

@@ -0,0 +1,208 @@
import struct
import socket
import time
import random
import math
import threading
from vdes_message_unpack import VdesMessageUnpack
# ------------------------ 全局队列和锁 ------------------------
vdes_message_queue = []
message_queue_lock = threading.Lock()
# ------------------------ 船只初始化 ------------------------
SHIP_MMSIS = [random.randint(336295939, 336295969) for _ in range(10)]
SHIP_STATUS = {}
SHIP_LIST = [
(336295939 + i, round(random.uniform(30.0, 34.0), 6),
round(random.uniform(123.5, 126.0), 6), random.randint(0, 359))
for i in range(30)
]
for ship in SHIP_LIST:
mmsi, init_lat, init_lon, heading = ship
SHIP_STATUS[mmsi] = {
'current_lat': init_lat,
'current_lon': init_lon,
'heading': heading,
'speed': 280.24 # 加速100倍用于演示
}
# ------------------------ 当前坐标 ------------------------
YANGTZE_ESTUARY_LAT = 31.23
YANGTZE_ESTUARY_LON = 123.87
current_lat = YANGTZE_ESTUARY_LAT
current_lon = YANGTZE_ESTUARY_LON
own_heading = 90 # 固定航向
# ------------------------ 工具函数 ------------------------
def generate_random_position():
global current_lat, current_lon, own_heading
speed_knots = 300.24
speed_kmh = speed_knots * 1.852
speed_kmps = speed_kmh / 3600
distance_km = speed_kmps * 1
heading_rad = math.radians(own_heading)
delta_lat = distance_km / 111.0 * math.cos(heading_rad)
delta_lon = distance_km / (111.0 * math.cos(math.radians(current_lat))) * math.sin(heading_rad)
current_lat += delta_lat
current_lon += delta_lon
return int(current_lon * 10000000), int(current_lat * 10000000)
def generate_ship_position(mmsi):
ship = SHIP_STATUS[mmsi]
speed_kmh = ship['speed'] * 1.852
distance_km = speed_kmh / 3600
heading_rad = math.radians(ship['heading'])
delta_lat = distance_km / 111.0 * math.cos(heading_rad)
delta_lon = distance_km / (111.0 * math.cos(math.radians(ship['current_lat']))) * math.sin(heading_rad)
ship['current_lat'] += delta_lat
ship['current_lon'] += delta_lon
return ship['current_lon'], ship['current_lat']
def create_tcp_packet(packet_type, content):
header = b'####'
type_field = struct.pack('>I', packet_type)
length_field = struct.pack('>I', len(content))
return header + type_field + length_field + content
def create_gps_packet():
lon, lat = generate_random_position()
utc_time = int(time.time())
content = struct.pack('>QiiiHHHBBIII',
utc_time, lon, lat, 50, 10, 8, 7, 1, 3, 2550, 1024, own_heading)
return content
def create_ais_vdes_info_packet(demod_content, Demodulator_id):
snr = 25.75
snr_int = int(snr)
snr_frac = int((snr - snr_int) * 100)
snr_packed = (snr_int & 0xFF) << 8 | (snr_frac & 0xFF)
content = struct.pack('>BBBHHH', 12, 1, Demodulator_id, 1234, 5678, snr_packed)
return content + demod_content
def encode_ais_demod_fields(SHIP_MMSI, lat, lon, heading):
bits = []
bits.append(format(1, '06b'))
bits.append(format(0, '02b'))
bits.append(format(SHIP_MMSI, '030b'))
bits.append(format(8, '04b'))
bits.append(format(128 & 0xFF, '08b'))
bits.append(format(102, '010b'))
bits.append(str(1))
bits.append(format(int(lon * 60 * 10000) & 0x0FFFFFFF, '028b'))
bits.append(format(int(lat * 60 * 10000) & 0x07FFFFFF, '027b'))
bits.append(format(min(heading, 3600), '012b'))
bits.append(format(min(heading, 511), '09b'))
bits.append(format(30, '06b'))
bits.append(format(0, '02b'))
bits.append(format(0, '03b'))
bits.append(str(0))
bits.append(format(0, '019b'))
bit_string = ''.join(bits)
binary_int = int(bit_string, 2)
byte_length = (len(bit_string) + 7) // 8
return binary_int.to_bytes(byte_length, 'big')
def encode_vdes_demod_fields(Type, Fragment_num, MMSI, Payload):
source_id = 12345678
satellite_id = 1
session_id = 100
dest_id = MMSI
fragment_num = Fragment_num
binary_payload = Payload
payload_size = 4 + 1 + 1 + 4 + 2 + len(binary_payload)
content = struct.pack('>B H I B B I H',
Type, payload_size, source_id, satellite_id, session_id, dest_id, fragment_num)
return content + binary_payload
# ------------------------ 线程函数 ------------------------
def send_messages(conn):
try:
while True:
with message_queue_lock:
if vdes_message_queue:
packet = vdes_message_queue.pop(0)
else:
packet = None
if packet:
try:
conn.sendall(packet)
except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
print("发送失败,客户端断开")
break
else:
time.sleep(1)
# 生成并发送AIS报文
# selected_ship = random.choice(SHIP_LIST)
# mmsi = selected_ship[0]
# lon, lat = generate_ship_position(mmsi)
# heading = SHIP_STATUS[mmsi]['heading']
# ais_demod_content = encode_ais_demod_fields(mmsi, lat, lon, heading)
# ais_data = create_ais_vdes_info_packet(ais_demod_content, 4)
# ais_packet = create_tcp_packet(12, ais_data)
# #生成并发送GPS报文
# gps_content = create_gps_packet()
# gps_packet = create_tcp_packet(7, gps_content)
# try:
# conn.sendall(ais_packet)
# conn.sendall(gps_packet)
# except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
# print("发送失败,客户端断开")
# break
time.sleep(0.5)
finally:
print("发送线程退出")
def handle_client(conn, addr):
print(f"客户端连接: {addr}")
unpacker = VdesMessageUnpack()
fragment_num = 0
send_thread = threading.Thread(target=send_messages, args=(conn,))
send_thread.daemon = True
send_thread.start()
try:
while True:
try:
data = conn.recv(65536)
if not data:
print("客户端关闭连接")
break
# 解析报文并加入发送队列
frame_type, message = unpacker.unpack_tcp_packet(data)
mmsi = unpacker.get_mmsi()
Type = 30 if frame_type == 0 else 31 if frame_type == 1 else 32
vdes_demod_content = encode_vdes_demod_fields(Type, fragment_num, mmsi, message)
vdes_data = create_ais_vdes_info_packet(vdes_demod_content, 0)
vdes_packet = create_tcp_packet(12, vdes_data)
fragment_num += 1
with message_queue_lock:
vdes_message_queue.append(vdes_packet)
except (ConnectionResetError, ConnectionAbortedError):
print("客户端断开连接")
break
except Exception as e:
print(f"解析报文错误: {e}")
finally:
conn.close()
print("客户端连接关闭")
# ------------------------ 服务端启动 ------------------------
def send_data_continuously():
host = '127.0.0.1'
port = 10
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(5)
print(f"服务端启动 {host}:{port}")
try:
while True:
conn, addr = s.accept()
client_thread = threading.Thread(target=handle_client, args=(conn, addr))
client_thread.daemon = True
client_thread.start()
except KeyboardInterrupt:
print("服务端停止")
if __name__ == "__main__":
send_data_continuously()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,37 @@
import binascii
def image_to_binary(image_path):
"""将图片文件转换为二进制数据"""
with open(image_path, 'rb') as file:
return file.read()
def binary_to_image(binary_data, output_path):
"""将二进制数据保存为图片文件"""
with open(output_path, 'wb') as file:
file.write(binary_data)
def binary_to_hex(binary_data):
"""将二进制数据转换为十六进制字符串"""
return binascii.hexlify(binary_data).decode('utf-8')
def hex_to_binary(hex_string):
"""将十六进制字符串转换回二进制数据"""
return binascii.unhexlify(hex_string)
# 使用示例
if __name__ == "__main__":
# 读取图片并转换为二进制
binary_data = image_to_binary('./tcp_data/VDES报文传输的图片.jpg')
print(f"原始图片大小: {len(binary_data)} 字节")
# 转换为十六进制字符串
hex_data = binary_to_hex(binary_data)
print(f"十六进制表示长度: {len(hex_data)} 字符")
print(f"十六进制预览: {hex_data}...")
# 转换回二进制
restored_binary = hex_to_binary(hex_data)
# 保存回图片文件
# binary_to_image(restored_binary, 'output.jpg')
# print("图片已成功转换并保存为 output.jpg")

View File

@@ -0,0 +1,395 @@
import struct
import socket
import time
import random
from random import uniform, randint
import math
import threading
from vdes_message_unpack import VdesMessageUnpack
vdes_message_queue = []
message_queue_lock = threading.Lock()
SHIP_MMSIS = [random.randint(336295939, 336295969) for _ in range(10)]
SHIP_STATUS = {}
# 生成包含30艘船信息的列表MMSI、纬度、经度、航向
SHIP_LIST = [
(
336295939 + i, # MMSI号
round(random.uniform(30.0, 34.0), 6),
round(random.uniform(123.5, 126.0), 6),
random.randint(0, 359)
) for i in range(30)
]
# 初始化船只状态
for ship in SHIP_LIST:
mmsi, init_lat, init_lon, heading = ship
SHIP_STATUS[mmsi] = {
'current_lat': init_lat,
'current_lon': init_lon,
'heading': heading,
'speed': 280.24 #加速100倍用于演示
}
YANGTZE_ESTUARY_LAT = 31.23
YANGTZE_ESTUARY_LON = 123.87
RANGE_NM = 5 # 5海里范围
current_lat = YANGTZE_ESTUARY_LAT # 当前纬度
current_lon = YANGTZE_ESTUARY_LON # 当前经度
own_heading = 90 # 固定航向 (50度)
def generate_random_position():
"""生成船只位置(基于当前位置和航向计算)"""
global current_lat, current_lon, own_heading
# 速度为10.24节(来自GPS报文设置)
speed_knots = 300.24 #加速100倍用于演示
# 转换为公里/小时 (1节 = 1.852公里/小时)
speed_kmh = speed_knots * 1.852
# 转换为公里/秒 (发送间隔为1秒)
speed_kmps = speed_kmh / 3600
# 计算1秒内移动的距离(公里)
distance_km = speed_kmps * 1
# 将航向转换为弧度
heading_rad = math.radians(own_heading)
# 计算纬度和经度变化
# 1度纬度 ≈ 111公里
delta_lat = distance_km / 111.0 * math.cos(heading_rad)
# 1度经度 ≈ 111公里 * cos(纬度)
delta_lon = distance_km / (111.0 * math.cos(math.radians(current_lat))) * math.sin(heading_rad)
# 更新当前位置
current_lat += delta_lat
current_lon += delta_lon
# 转换为度分格式*10^7
longitude = int(current_lon * 10000000)
latitude = int(current_lat * 10000000)
return longitude, latitude
def generate_ship_position(mmsi):
"""根据船只MMSI生成当前位置"""
global SHIP_STATUS
ship = SHIP_STATUS[mmsi] #获取全局船只中的一个进行修改
speed_knots = ship['speed']
speed_kmh = speed_knots * 1.852
speed_kmps = speed_kmh / 3600
distance_km = speed_kmps * 1
# 航向转弧度
heading_rad = math.radians(ship['heading'])
# 计算经纬度变化
delta_lat = distance_km / 111.0 * math.cos(heading_rad)
delta_lon = distance_km / (111.0 * math.cos(math.radians(ship['current_lat']))) * math.sin(heading_rad)
# 更新船只状态
ship['current_lat'] += delta_lat
ship['current_lon'] += delta_lon
# 返回新位置
return ship['current_lon'], ship['current_lat']
def create_tcp_packet(packet_type, content):
"""创建符合VDES TCP协议的报文"""
# 报文帧头 (4字节)
header = b'####'
# 报文类型 (4字节大端)
type_field = struct.pack('>I', packet_type)
# 报文长度 (4字节大端内容长度)
length = len(content)
length_field = struct.pack('>I', length)
# 构建完整报文
packet = header + type_field + length_field + content
return packet
def create_gps_packet():
"""创建GPS解析数据报文(类型7)的内容"""
# 模拟GPS数据 (表11定义)
lon, lat = generate_random_position()
utc_time = 1680000000 # UTC时间 (8字节)
longitude = lon # 经度 (4字节东经120.5度)
latitude = lat # 纬度 (4字节北纬30.5度)
altitude = 50 # 海拔高度 (4字节50米)
used_sats = 10 # 定位使用的卫星数 (2字节)
gps_sats = 8 # GPS卫星数 (2字节)
beidou_sats = 7 # 北斗卫星数 (2字节)
gps_status = 1 # GPS状态 (1字节单点定位)
position_mode = 3 # 定位模式 (1字节3D定位)
hdop = 2550 # 水平精度因子 (4字节25.5米)
speed = 1024 # 运动速度 (4字节10.24节)
direction = own_heading # 运动方向 (4字节359度)
# 打包为二进制 (大端字节序)
content = struct.pack(
'>QiiiHHHBBIII', # 格式: Q(8),i(4),i(4),i(4),H(2),H(2),H(2),B(1),B(1),I(4),I(4),I(4)
utc_time,
longitude,
latitude,
altitude,
used_sats,
gps_sats,
beidou_sats,
gps_status,
position_mode,
hdop,
speed,
direction
)
return content
def create_ais_vdes_info_packet(demod_content, Demodulator_id):
"""创建解调信息报文(类型12)的内容"""
# 模拟解调信息数据
message_type = 12 # 消息类型 (1字节)
satellite_id = 1 # 卫星ID (1字节)
demodulator_id = Demodulator_id # 解调器编号 (1字节)
demod_slot = 1234 # 解调时隙 (2字节)
receive_delay = 5678 # 接收延迟 (2字节单位us)
snr = 25.75 # 信噪比 (2字节dB)
# 处理信噪比 (高8位整数部分低8位小数部分)
snr_int = int(snr)
snr_frac = int((snr - snr_int) * 100) # 转换为0-99的小数部分
snr_packed = (snr_int & 0xFF) << 8 | (snr_frac & 0xFF)
# 打包为二进制 (大端字节序)
content = struct.pack(
'>BBBHHH', # 格式: B(1),B(1),B(1),H(2),H(2),h(2)
message_type,
satellite_id,
demodulator_id,
demod_slot,
receive_delay,
snr_packed
)
return content + demod_content
def encode_ais_demod_fields(SHIP_MMSI, lat, lon, heading):
"""编码AIS解调信息字段"""
# 初始化二进制位列表
bits = []
# 示例数据
message_id = 1 # 消息ID (6 bits)
repeat_indicator = 0 # 转发指示符 (2 bits)
user_id = SHIP_MMSI # 用户ID/MMSI (30 bits)
navigation_status = 8 # 导航状态 (4 bits) - 航行中
rot = -128 # 旋转速率 (8 bits) - 无信息
sog = 102 # 地面航速 (10 bits) - 10.2节
position_accuracy = 1 # 位置准确度 (1 bit) - 高
longitude, latitude = lon, lat # 经纬度
cog = heading # 地面航线 (12 bits) - 90.0°
true_heading = heading # 实际航向 (9 bits) - 90°
timestamp = 30 # 时戳 (6 bits) - UTC秒
special_maneuver = 0 # 特定操纵指示符 (2 bits)
spare = 0 # 备用 (3 bits)
raim_flag = 0 # RAIM标志 (1 bit)
comm_state = 0 # 通信状态 (19 bits)
# 添加各个字段(按照协议顺序)
bits.append(format(message_id, '06b')) # 消息ID
bits.append(format(repeat_indicator, '02b')) # 转发指示符
bits.append(format(user_id, '030b')) # 用户ID
bits.append(format(navigation_status, '04b')) # 导航状态
# 旋转速率 (8位有符号整数)
rot_bits = rot & 0xFF
bits.append(format(rot_bits, '08b'))
# 地面航速 (10位)
bits.append(format(min(sog, 1023), '010b'))
# 位置准确度 (1位)
bits.append(str(position_accuracy))
# 经度 (28位)
# 转换为1/10,000分钟单位
longitude_min = int(longitude * 60 * 10000)
bits.append(format(longitude_min & 0x0FFFFFFF, '028b'))
# 纬度 (27位)
latitude_min = int(latitude * 60 * 10000)
bits.append(format(latitude_min & 0x07FFFFFF, '027b'))
# 地面航线 (12位)
bits.append(format(min(cog, 3600), '012b'))
# 实际航向 (9位)
bits.append(format(min(true_heading, 511), '09b'))
# 时戳 (6位)
bits.append(format(min(timestamp, 63), '06b'))
# 特定操纵指示符 (2位)
bits.append(format(special_maneuver, '02b'))
# 备用 (3位)
bits.append(format(spare, '03b'))
# RAIM标志 (1位)
bits.append(str(raim_flag))
# 通信状态 (19位)
bits.append(format(comm_state, '019b'))
# 合并所有位
bit_string = ''.join(bits)
# 验证总位数
total_bits = len(bit_string)
if total_bits != 168:
print(f"警告: 总位数应为168实际为{total_bits}")
# 将位字符串转换为字节
binary_int = int(bit_string, 2) # 将二进制字符串转换为整数
byte_length = (len(bit_string) + 7) // 8 # 计算需要的字节数
binary_data = binary_int.to_bytes(byte_length, 'big') # 转换为字节
return binary_data
def encode_vdes_demod_fields(Type, Fragment_num, MMSI, Payload):
"""编码VDES解调信息字段"""
source_id = 12345678 # 信源电台ID
satellite_id = 1 # 卫星ID
session_id = 100 # 会话ID
dest_id = MMSI # 目的地电台ID
fragment_num = Fragment_num # 片段编号
binary_payload = Payload # 载荷内容
# 计算载荷大小 (字段3到8的大小)
payload_size = 4 + 1 + 1 + 4 + 2 + len(binary_payload)
# 打包为二进制 (大端字节序)
content = struct.pack(
'>B H I B B I H', # 格式: B(1),H(2),I(4),B(1),B(1),I(4),H(2)
Type, # 类型 (1字节)
payload_size, # 载荷大小 (2字节)
source_id, # 信源电台ID (4字节)
satellite_id, # 卫星ID (1字节)
session_id, # 会话ID (1字节)
dest_id, # 目的地电台ID (4字节)
fragment_num # 片段编号 (2字节)
)
return content + binary_payload # 添加载荷内容
def print_binary(data):
"""打印二进制数据"""
print("十六进制表示:", data.hex())
print("二进制表示:", ' '.join(format(byte, '08b') for byte in data))
def handle_client(conn, addr):
"""处理客户端连接的线程函数"""
print(f"客户端连接来自: {addr}")
unpacker = VdesMessageUnpack()
mmsi: int
fragment_num = 0
# 创建发送线程
send_thread = threading.Thread(target=send_messages, args=(conn,))
send_thread.daemon = True
send_thread.start()
# 接收消息
while True:
data = conn.recv(65536)
if data:
# print(f"收到VDES客户端消息: {data.hex()}")
try:
frame_type, message = unpacker.unpack_tcp_packet(data)
mmsi = unpacker.get_mmsi()
if frame_type == 0: # 起始片段
Type = 30
elif frame_type == 1: # 延续片段
Type = 31
elif frame_type == 2: # 结束片段
Type = 32
vdes_demod_content = encode_vdes_demod_fields(Type, fragment_num, mmsi, message)
vdes_data = create_ais_vdes_info_packet(vdes_demod_content, 0)
vdes_packet = create_tcp_packet(12, vdes_data)
fragment_num += 1
# 将消息添加到全局队列
# with message_queue_lock:
vdes_message_queue.append(vdes_packet)
if not unpacker.get_unpack_statu():
fragment_num = 0
# print("解析消息完成")
except ValueError as e:
print(f"报文解析错误: {e}")
def send_messages(conn):
"""持续发送消息的线程函数"""
try:
while True:
# 检查并发送队列中的VDES消息
# with message_queue_lock:
# print(11111111111111111111111)
# print(vdes_message_queue)
if vdes_message_queue:
print(vdes_message_queue)
packet = vdes_message_queue.pop(0)
conn.sendall(packet)
print(f"发送VDES报文: {packet.hex()}")
time.sleep(0.5) # 模拟发送间隔
# 生成并发送AIS报文
# selected_ship = random.choice(SHIP_LIST)
# mmsi = selected_ship[0]
# lon, lat = generate_ship_position(mmsi)
# heading = SHIP_STATUS[mmsi]['heading']
# ais_demod_content = encode_ais_demod_fields(mmsi, lat, lon, heading)
# ais_data = create_ais_vdes_info_packet(ais_demod_content, 4)
# ais_packet = create_tcp_packet(12, ais_data)
# print(f"发送AIS报文: {ais_packet.hex()}")
# conn.sendall(ais_packet)
# # 生成并发送GPS报文
# gps_content = create_gps_packet()
# gps_packet = create_tcp_packet(7, gps_content)
# conn.sendall(gps_packet)
# time.sleep(2)
except (ConnectionResetError, BrokenPipeError):
print("客户端断开连接")
def send_data_continuously():
"""启动Socket服务端"""
host = '127.0.0.1'
port = 10
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(1)
print(f"Socket服务端已启动监听 {host}:{port}")
try:
while True:
conn, addr = s.accept()
client_thread = threading.Thread(target=handle_client, args=(conn, addr))
client_thread.daemon = True
client_thread.start()
except KeyboardInterrupt:
print("\n服务端已停止")
except Exception as e:
print(f"服务端错误: {e}")
if __name__ == "__main__":
send_data_continuously()

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
ffd8ffe000104a46494600010101006000600000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc000110800a9006403012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00d9a70a4a5ab10b4514b4c04a2968a0414b494b4005145140051451400514514011d028a5a431452d252d300a28a5a004a5a28a04145145001451450014514500474b4da75218a29452514c05a28a280168a4a5a04145251400b45251400b4514500474b4da5a918b4b482969805578aed5e77888c61b00fad58ac6b8631ddc8aa3ab66a27271348454ae6d5364916342edd05650bfb88f2a76b7a64722a296ea6b8e49181d062a1d78f41fb277d4d2b4b969a47570011c8c7a559cd64e94ccd3ca5ba81835a99ab84aeb526a46d2b21d9a334ccd381abb903a8a4a280194b494b4862d14945021d9ac7b828b772c8cc781c8f615af9ac6bf3e5dd3e1796c7cd9fd2b2adb1ad2dc820b88ae8b98490508ce47d698f7915bddac063662d8e476cff9cd0a8b69217440aa47cf8144686491a560371e54903207f315cda7637b17ad0aaddb614862307deb47359b6392d9c0c2f43577757452f84c2a6e4b9a729a881a78ad4cc933452514c068a5a4a280168cd2678e6a8dcdd338291b6d1ebeb532928a1c62d935cdf4506467738ec2b3a67dcbb9b9cf26a9e0b499c0fbc735336ec64638ed5cb2a8e474460a2446523e42bbd471d706a68a32fc05da9e99cd42593bf0d5760f9941cd45cb64f061176ad5807355236c1dbe9deaca9ae8a6f439e6b5255a916a35a916b6440f14500514086519a4cd266802bdf4a55020eadd6a9b11e59f6a748e649724e41e94d603cb3f4ae59cb999d11564545db9c004e4e4d5819f2fe65c7155119966e067d2acfef366777cc7f84d648d0acee9b812738e738ab50ce0a8e31e95525882827057fa9a580ee60074c5306692b67a5584aad18c0039fc6aca574c3639e4f5265a956a35152ad6a886385149453110d35cfc8df4a334c91808d89e80527b0d6e67c58d9f29c81de9b2b050707f4a588aa26380291981fc4d7133a8a864c4a8323e71eb4e78dda5016e063d076aab78a162f306708c0e3dbbd4d082c11a1451e841cd26343ee731821e4c8a86d22334f8c95451d4773516ad24c258d593258700558d321bbda19f6853fc34ec4dcd48942f03a0ab518aad1f5ab51d75c56873b7a93a8a78a6ad3c55922d1451408a99a8e6c985c0ebb4e29d9a4a4ca463f980af3d7de9158123355af898ae64518e0f1c5575bb72a598602f06b8ec75742fce1248d909192b8a874e00c5f32166538250f5a8639f7b7c913b67bd5ad2f89664da5307760d2481e826a6187d9e7c7dc621bdb35a5013e583ed54f50656b570392a412055ab6915e05c1eab4fb10c9e3ab51d558b8e2aca1aeb4739616a4151ad3c715421d452668a76028668cd333466a4b28cfa7a5d8f31dcab30ce47155d7c3ea7949b073dc66b5e1008191d00a970015c7ad64a29ea573332e2d2678d36f9b13f3d791fd0d247a75ea5eb4cde4942bb70ac73fa815a1f6c41f7a3917fde4228fb7dbffcf551f534724439e4574b6b904e62ce4ff7978fd6abdbe9f7d0b3b1119193b403d07e55ab1cf1cbfeadc37d29c6440705803e99a5ece21ceca71c72a201244e48eea0d384cea7989c7e9fcc55bdea7a30fce9c18ff78d68924ac43d48239dd8f1131ff3f4ab01c8197c20f7346f6f5348483d429fa8aab8b944fb5443fe5a29fa5146d8fbc719ff00808a28b8b94a5bb039350b5ec4bfc59e714973fea4d631ff00529f53fceb1a9371d8de1052dce92191190329041a716e57ebfd2a8e9fff001e8956c755fad5c5dd5ccdab3b108697276dd46dec53ff00af4e0f73d8c27fe047fc2b166ff8fa6ff7cff3ad28bee0a181760326f25e345e3ef29cd47385f358b5a16cff00cb450327f2e69d6df78fd2a76ea298140883f8a19c7bfcf406b61ff2f1220f761fd6af8a51d45003243fb8e25d9c0c3d43fbdfe1b943f55ffebd2ea3ff001e137d3fad62d97facfc68046d6eb8fefc67f1ff00eb5150af4a290ec7ffd9

View File

@@ -0,0 +1 @@
ffd8ffe000104a46494600010101006000600000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc000110800a9006403012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00d9a70a4a5ab10b4514b4c04a2968a0414b494b4005145140051451400514514011d028a5a431452d252d300a28a5a004a5a28a04145145001451450014514500474b4da75218a29452514c05a28a280168a4a5a04145251400b45251400b4514500474b4da5a918b4b482969805578aed5e77888c61b00fad58ac6b8631ddc8aa3ab66a27271348454ae6d5364916342edd05650bfb88f2a76b7a64722a296ea6b8e49181d062a1d78f41fb277d4d2b4b969a47570011c8c7a559cd64e94ccd3ca5ba81835a99ab84aeb526a46d2b21d9a334ccd381abb903a8a4a280194b494b4862d14945021d9ac7b828b772c8cc781c8f615af9ac6bf3e5dd3e1796c7cd9fd2b2adb1ad2dc820b88ae8b98490508ce47d698f7915bddac063662d8e476cff9cd0a8b69217440aa47cf8144686491a560371e54903207f315cda7637b17ad0aaddb614862307deb47359b6392d9c0c2f43577757452f84c2a6e4b9a729a881a78ad4cc933452514c068a5a4a280168cd2678e6a8dcdd338291b6d1ebeb532928a1c62d935cdf4506467738ec281f0f00efffffff9fffffff8ff0000f7b3a67dcbb9b9cf26a9e0b499c0fbc735336ec64638ed5cb2a8e474460a2446523e42bbd471d706a68a32fc05da9e99cd42593bf0d5760f9941cd45cb64f061176ad5807355236c1dbe9deaca9ae8a6f439e6b5255a916a35a916b6440f14500514086519a4cd266802bdf4a55020eadd6a9b11e59f6a748e649724e41e94d603cb3f4ae59cb999d11564545db9c004e4e4d5819f2fe65c7155119966e067d2acfef366777cc7f84d648d0acee9b812738e738ab50ce0a8e31e95525882827057fa9a580ee60074c5306692b67a5584aad18c0039fc6aca574c3639e4f5265a956a35152ad6a886385149453110d35cfc8df4a334c91808d89e80527b0d6e67c58d9f29c81de9b2b050707f4a588aa26380291981fc4d7133a8a864c4a8323e71eb4e78dda5016e063d076aab78a162f306708c0e3dbbd4d082c11a1451e841cd26343ee731821e4c8a86d22334f8c95451d4773516ad24c258d593258700558d321bbda19f6853fc34ec4dcd48942f03a0ab518aad1f5ab51d75c56873b7a93a8a78a6ad3c55922d1451408a99a8e6c985c0ebb4e29d9a4a4ca463f980af3d7de9158123355af898ae64518e0f1c5575bb72a598602f06b8ec75742fce1248d909192b8a874e00c5f32166538250f5a8639f7b7c913b67bd5ad2f89664da5307760d2481e826a6187d9e7c7dc621bdb35a5013e583ed54f50656b570392a412055ab6915e05c1eab4fb10c9e3ab51d558b8e2aca1aeb4739616a4151ad3c715421d452668a76028668cd333466a4b28cfa7a5d8f31dcab30ce47155d7c3ea7949b073dc66b5e1008191d00a970015c7ad64a29ea573332e2d2678d36f9b13f3d791fd0d247a75ea5eb4cde4942bb70ac73fa815a1f6c41f7a3917fde4228fb7dbffcf551f534724439e4574b6b904e62ce4ff7978fd6abdbe9f7d0b3b1119193b403d07e55ab1cf1cbfeadc37d29c6440705803e99a5ece21ceca71c72a201244e48eea0d384cea7989c7e9fcc55bdea7a30fce9c18ff78d68924ac43d48239dd8f1131ff3f4ab01c8197c20f7346f6f5348483d429fa8aab8b944fb5443fe5a29fa5146d8fbc719ff00808a28b8b94a5bb039350b5ec4bfc59e714973fea4d631ff00529f53fceb1a9371d8de1052dce92191190329041a716e57ebfd2a8e9fff001e8956c755fad5c5dd5ccdab3b108697276dd46dec53ff00af4e0f73d8c27fe047fc2b166ff8fa6ff7cff3ad28bee0a181760326f25e345e3ef29cd47385f358b5a16cff00cb450327f2e69d6df78fd2a76ea298140883f8a19c7bfcf406b61ff2f1220f761fd6af8a51d45003243fb8e25d9c0c3d43fbdfe1b943f55ffebd2ea3ff001e137d3fad62d97facfc68046d6eb8fefc67f1ff00eb5150af4a290ec7ffd9000000000000000000000000000000000000000000000000001ae74609fffffff9fffffff8ff0000f7

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
with open("TCP_VDES报文消息.bin", "rb") as f:
data = f.read()
print(data.hex())
# 帧起始标志
delimiter = b"####"
# 按标志切分
frames = data.split(delimiter)
# 第一个切分出来的可能是空的(因为文件就是以 #### 开头)
for i, frame in enumerate(frames):
if not frame:
continue
# 打印时把起始标志加回去
ais_packet = delimiter + frame
print(f"Frame {i}:")
print((delimiter + frame).hex())
print("-" * 60)

View File

@@ -0,0 +1,14 @@
# 将十六进制字符串转换为二进制数据并保存为image.jpg
hex_str = "ffd8ffe000104a46494600010101006000600000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc000110800a9006403012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131440613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00d9a70a4a5ab10b4514b4c04a2968a0414b494b4005145140051451400514514011d028a5a431452d252d300a28a5a004a5a28a04145145001451450014514500474b4da75218a29452514c05a28a280168a4a5a04145251400b45251400b4514500474b4da5a918b4b482969805578aed5e77888c61b00fad58ac6b8631ddc8aa3ab66a27271348454ae6d5364916342edd05650bfb88f2a76b7a64722a296ea6b8e49181d062a1d78f41fb277d4d2b4b969a47570011c8c7a559cd64e94ccd3ca5ba81835a99ab84aeb526a46d2b21d9a334ccd381abb903a8a4a280194b494b4862d14945021d9ac7b828b772c8cc781c8f615af9ac6bf3e5dd3e1796c7cd9fd2b2adb1ad2dc820b88ae8b98490508ce47d698f7915bddac063662d8e476cff9cd0a8b69217440aa47cf8144686491a560371e54903207f315cda7637b17ad0aaddb614862307deb47359b6392d9c0c2f43577757452f84c2a6e4b9a729a881a78ad4cc933452514c068a5a4a280168cd2678e6a8dcdd338291b6d1ebeb532928a1c62d935cdf4506467738ec281f0f00efffffff9fffffff8ff0000f7b3a67dcbb9b9cf26a9e0b499c0fbc735336ec64638ed5cb2a8e474460a2446523e42bbd471d706a68a32fc05da9e99cd42593bf0d5760f9941cd45cb64f061176ad5807355236c1dbe9deaca9ae8a6f439e6b5255a916a35a916b6440f14500514086519a4cd266802bdf4a55020eadd6a9b11e59f6a748e649724e41e94d603cb3f4ae59cb999d11564545db9c004e4e4d5819f2fe65c7155119966e067d2acfef366777cc7f84d648d0acee9b812738e738ab50ce0a8e31e95525882827057fa9a580ee60074c5306692b67a5584aad18c0039fc6aca574c3639e4f5265a956a35152ad6a886385149453110d35cfc8df4a334c9388d89e80527b0d6e67c58d9f29c81de9b2b050707f4a588aa26380291981fc4d7133a8a864c4a8323e71eb4e78dda5016e063d076aab78a162f306708c0e3dbbd4d082c11a1451e841cd26343ee731821e4c8a86d22334f8c95451d4773516ad24c258d593258700558d321bbda19f6853fc34ec4dcd48942f03a0ab518aad1f5ab51d75c56873b7a93a8a78a6ad3c55922d1451408a99a8e6c985c0ebb4e29d9a4a4ca463f980af3d7de9158123355af898ae64518e0f1c5575bb72a598602f06b8ec75742fce1248d909192b8a874e00c5f32166538250f5a8639f7b7c913b67bd5ad2f89664da5307760d2481e826a6187d9e7c7dc621bdb35a5013e583ed54f50656b570392a412055ab6915e05c1eab4fb10c9e3ab51d558b8e2aca1aeb4739616a4151ad3c715421d452668a76028668cd333466a4b28cfa7a5d8f31dcab30ce47155d7c3ea7949b073dc66b5e1008191d00a970015c7ad64a29ea573332e2d2678d36f9b13f3d791fd0d247a75ea5eb4cde4942bb70ac73fa815a1f6c41f7a3917fde4228fb7dbffcf551f534724439e4574b6b904e62ce4ff7978fd6abdbe9f7d0b3b1119193b403d07e55ab1cf1cbfeadc37d29c6440705803e99a5ece21ceca71c72a201244e48eea0d384cea7989c7e9fcc55bdeb8cee18fad28707a30fce9a492b12f5210e40cbe107b9a3ed517fcf45fce9f93ea69327d4d3b8ac27da63fef8a29323fbc28a2e3b14ae3fd49ac63fea53ea7f9d6c5c7fa9358c7fd4a7d4ff3ac6b6e6d4763474fff008f44ab27aafd6aae9fff001e89568f55fad5c3e14672f899872ffc7d37fbe7f9d6947f705674dff1f4dfef9fe75a51fdc14c192c3d4fd2a76ea2a087a9fa54edd45310a29475a4a51d45003251ba323d4562c16d2c2fb8a923dab6e9b81e94014d5db1f71a8ab581e9450027e1452d1401ffd9"
# 移除任何空格或换行(确保字符串是连续的)
hex_str = hex_str.strip()
# 将十六进制字符串转换为字节
image_data = bytes.fromhex(hex_str)
# 保存为JPEG文件
with open('image.jpg', 'wb') as f:
f.write(image_data)
print("图片已保存为 image.jpg")

View File

@@ -0,0 +1,77 @@
import struct
class VdesMessageUnpack:
"""VDES消息解包工具类"""
def __init__(self):
self.message_frames = []
self.current_message = b''
self.is_receiving = False
self.is_unpacked = False #是否解析到结束帧
self.MMSI: int
def unpack_tcp_packet(self, packet_data):
"""解包TCP协议报文"""
# 检查帧头
if packet_data[:4] != b'####':
raise ValueError("无效的报文帧头")
# 解析报文类型和长度
packet_type = struct.unpack('>I', packet_data[4:8])[0]
packet_length = struct.unpack('>I', packet_data[8:12])[0]
packet_content = packet_data[12:]
# 处理不同类型的帧
if packet_type == 10: # 消息帧
# print(f"消息帧内容: {packet_content.hex()}")
frame_type, message = self._process_message_frame(packet_content)
else:
print(f"忽略未知报文类型: {packet_type}")
return None, None
return frame_type, message
def _process_message_frame(self, content):
"""处理消息帧内容"""
# 解析帧类型(1字节)
frame_type = struct.unpack('>B', content[:1])[0]
# print(f"帧类型: {frame_type}")
if frame_type == 0: # 起始帧
self.is_receiving = True
self.current_message = b''
# 解析目标MMSI和优先级
target_mmsi = struct.unpack('>I', content[1:5])[0]
priority = struct.unpack('>B', content[5:6])[0]
self.MMSI = target_mmsi
# print(f"开始接收消息目标MMSI: {target_mmsi}, 优先级: {priority}")
elif frame_type == 1: # 中间帧
if self.is_receiving:
self.current_message = content[1:]
# print(f"接收中间帧,{self.current_message}")
elif frame_type == 2: # 结束帧
if self.is_receiving:
self.current_message = b''
# self.message_frames.append(self.current_message)
self.is_receiving = False
# print(f"消息接收完成,总长度: {len(self.current_message)}字节")
else:
print(f"忽略未知帧类型: {frame_type}")
return frame_type, self.current_message
def get_unpack_statu(self):
"""获取解包状态"""
return self.is_receiving
def get_message_frames(self):
"""获取解包后的消息帧列表"""
return self.message_frames
def get_mmsi(self):
"""获取解包后的消息帧列表"""
return self.MMSI

View File

@@ -0,0 +1,2 @@
127.0.0.1
10

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,239 @@
import struct
import socket
import time
import random
import math
import threading
from vdes_message_unpack import VdesMessageUnpack
# ------------------------ 全局队列和锁 ------------------------
vdes_message_queue = []
message_queue_lock = threading.Lock()
# ------------------------ 船只初始化 ------------------------
SHIP_MMSIS = [random.randint(336295939, 336295969) for _ in range(10)]
SHIP_STATUS = {}
SHIP_LIST = [
(336295939 + i, round(random.uniform(30.0, 34.0), 6),
round(random.uniform(123.5, 126.0), 6), random.randint(0, 359))
for i in range(30)
]
for ship in SHIP_LIST:
mmsi, init_lat, init_lon, heading = ship
SHIP_STATUS[mmsi] = {
'current_lat': init_lat,
'current_lon': init_lon,
'heading': heading,
'speed': 280.24 # 加速100倍用于演示
}
# ------------------------ 当前坐标 ------------------------
YANGTZE_ESTUARY_LAT = 31.23
YANGTZE_ESTUARY_LON = 123.87
current_lat = YANGTZE_ESTUARY_LAT
current_lon = YANGTZE_ESTUARY_LON
own_heading = 90 # 固定航向
# ------------------------ 工具函数 ------------------------
def generate_random_position():
global current_lat, current_lon, own_heading
speed_knots = 300.24
speed_kmh = speed_knots * 1.852
speed_kmps = speed_kmh / 3600
distance_km = speed_kmps * 1
heading_rad = math.radians(own_heading)
delta_lat = distance_km / 111.0 * math.cos(heading_rad)
delta_lon = distance_km / (111.0 * math.cos(math.radians(current_lat))) * math.sin(heading_rad)
current_lat += delta_lat
current_lon += delta_lon
return int(current_lon * 10000000), int(current_lat * 10000000)
def generate_ship_position(mmsi):
ship = SHIP_STATUS[mmsi]
speed_kmh = ship['speed'] * 1.852
distance_km = speed_kmh / 3600
heading_rad = math.radians(ship['heading'])
delta_lat = distance_km / 111.0 * math.cos(heading_rad)
delta_lon = distance_km / (111.0 * math.cos(math.radians(ship['current_lat']))) * math.sin(heading_rad)
ship['current_lat'] += delta_lat
ship['current_lon'] += delta_lon
return ship['current_lon'], ship['current_lat']
def create_tcp_packet(packet_type, content):
header = b'####'
type_field = struct.pack('>I', packet_type)
length_field = struct.pack('>I', len(content))
return header + type_field + length_field + content
def create_gps_packet():
lon, lat = generate_random_position()
utc_time = int(time.time())
content = struct.pack('>QiiiHHHBBIII',
utc_time, lon, lat, 50, 10, 8, 7, 1, 3, 2550, 1024, own_heading)
return content
def create_ais_vdes_info_packet(demod_content, Demodulator_id):
snr = 25.75
snr_int = int(snr)
snr_frac = int((snr - snr_int) * 100)
snr_packed = (snr_int & 0xFF) << 8 | (snr_frac & 0xFF)
content = struct.pack('>BBBHHH', 12, 1, Demodulator_id, 1234, 5678, snr_packed)
return content + demod_content
def encode_ais_demod_fields(SHIP_MMSI, lat, lon, heading):
bits = []
bits.append(format(1, '06b'))
bits.append(format(0, '02b'))
bits.append(format(SHIP_MMSI, '030b'))
bits.append(format(8, '04b'))
bits.append(format(128 & 0xFF, '08b'))
bits.append(format(102, '010b'))
bits.append(str(1))
bits.append(format(int(lon * 60 * 10000) & 0x0FFFFFFF, '028b'))
bits.append(format(int(lat * 60 * 10000) & 0x07FFFFFF, '027b'))
bits.append(format(min(heading, 3600), '012b'))
bits.append(format(min(heading, 511), '09b'))
bits.append(format(30, '06b'))
bits.append(format(0, '02b'))
bits.append(format(0, '03b'))
bits.append(str(0))
bits.append(format(0, '019b'))
bit_string = ''.join(bits)
binary_int = int(bit_string, 2)
byte_length = (len(bit_string) + 7) // 8
return binary_int.to_bytes(byte_length, 'big')
def encode_vdes_demod_fields(Type, Fragment_num, MMSI, Payload):
source_id = 12345678
satellite_id = 1
session_id = 100
dest_id = MMSI
fragment_num = Fragment_num
binary_payload = Payload
payload_size = 4 + 1 + 1 + 4 + 2 + len(binary_payload)
content = struct.pack('>B H I B B I H',
Type, payload_size, source_id, satellite_id, session_id, dest_id, fragment_num)
return content + binary_payload
# ------------------------ 线程函数 ------------------------
def send_messages(conn):
global frame_index
try:
while True:
with message_queue_lock:
if vdes_message_queue:
packet = vdes_message_queue.pop(0)
else:
packet = None
if packet:
try:
conn.sendall(packet)
except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
print("发送失败,客户端断开")
break
else:
if ais_frames:
# print(ais_frames, type(ais_frames))
frame = ais_frames[frame_index]
try:
conn.sendall(frame)
print(f"发送报文帧[{frame_index}]:", frame.hex())
except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
print("发送失败,客户端断开")
break
# 轮流取下一帧
frame_index = (frame_index + 1) % len(ais_frames)
time.sleep(5)
# pass
# 生成并发送AIS报文
# selected_ship = random.choice(SHIP_LIST)
# mmsi = selected_ship[0]
# lon, lat = generate_ship_position(mmsi)
# heading = SHIP_STATUS[mmsi]['heading']
# ais_demod_content = encode_ais_demod_fields(mmsi, lat, lon, heading)
# ais_data = create_ais_vdes_info_packet(ais_demod_content, 4)
# ais_packet = create_tcp_packet(12, ais_data)
# #生成并发送GPS报文
# gps_content = create_gps_packet()
# gps_packet = create_tcp_packet(7, gps_content)
# try:
# conn.sendall(ais_packet)
# conn.sendall(gps_packet)
# except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
# print("发送失败,客户端断开")
# break
time.sleep(0.5)
finally:
print("发送线程退出")
def handle_client(conn, addr):
print(f"客户端连接: {addr}")
unpacker = VdesMessageUnpack()
fragment_num = 0
send_thread = threading.Thread(target=send_messages, args=(conn,))
send_thread.daemon = True
send_thread.start()
try:
while True:
try:
data = conn.recv(65536)
if not data:
print("客户端关闭连接")
break
# 解析报文并加入发送队列
frame_type, message = unpacker.unpack_tcp_packet(data)
mmsi = unpacker.get_mmsi()
Type = 30 if frame_type == 0 else 31 if frame_type == 1 else 32
vdes_demod_content = encode_vdes_demod_fields(Type, fragment_num, mmsi, message)
vdes_data = create_ais_vdes_info_packet(vdes_demod_content, 0)
vdes_packet = create_tcp_packet(12, vdes_data)
fragment_num += 1
with message_queue_lock:
vdes_message_queue.append(vdes_packet)
except (ConnectionResetError, ConnectionAbortedError):
print("客户端断开连接")
break
except Exception as e:
print(f"解析报文错误: {e}")
finally:
conn.close()
print("客户端连接关闭")
# ------------------------ 服务端启动 ------------------------
def send_data_continuously():
host = 'localhost'
port = 10
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(5)
print(f"服务端启动 {host}:{port}")
try:
while True:
conn, addr = s.accept()
client_thread = threading.Thread(target=handle_client, args=(conn, addr))
client_thread.daemon = True
client_thread.start()
except KeyboardInterrupt:
print("服务端停止")
def load_frames(filepath):
with open(filepath, "rb") as f:
data = f.read()
delimiter = b"####" # 帧起始标记
frames = data.split(delimiter)
# 加回起始符,过滤掉空的
frames = [delimiter + frame for frame in frames if frame]
return frames
if __name__ == "__main__":
# 全局变量:预加载所有帧
ais_frames = load_frames("./tcp_data/TCP_GNSS消息.bin")
frame_index = 0 # 当前取到第几帧
send_data_continuously()