Files
2026-07-13 15:37:05 +08:00

230 lines
8.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import struct
import socket
import time
import random
SHIP_MMSIS = [random.randint(200000000, 299999999) for _ in range(10)]
YANGTZE_ESTUARY_LAT = 31.23
YANGTZE_ESTUARY_LON = 122.47
RANGE_NM = 5 # 5海里范围
def generate_random_position():
"""生成随机船只位置(基于长江口基准坐标)"""
# 1海里=1.852公里≈0.0166667度(纬度)
lat = YANGTZE_ESTUARY_LAT + (random.random() - 0.5) * RANGE_NM * 0.0166667
lon = YANGTZE_ESTUARY_LON + (random.random() - 0.5) * RANGE_NM * 0.0166667
# 转换为度分格式*10000000
longitude = int(lon * 10000000) # 东经为正数
latitude = int(lat * 10000000) # 北纬为正数
return longitude, latitude
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 = 359 # 运动方向 (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_info_packet(demod_content):
"""创建解调信息报文(类型12)的内容"""
# 模拟解调信息数据
message_type = 12 # 消息类型 (1字节)
satellite_id = 1 # 卫星ID (1字节)
demodulator_id = 0 # 解调器编号 (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_demod_fields():
"""编码解调信息字段并打印解码结果"""
# 初始化二进制位列表
bits = []
# 示例数据
message_id = 1 # 消息ID (6 bits)
repeat_indicator = 0 # 转发指示符 (2 bits)
user_id = random.choice(SHIP_MMSIS) # 用户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 = 124.5 + (random.random() - 0.5) * RANGE_NM * 0.0166667 # 经度 (28 bits) - 东经120.5°
latitude = 30.5 + (random.random() - 0.5) * RANGE_NM * 0.0166667 # 纬度 (27 bits) - 北纬30.5°
cog = 900 # 地面航线 (12 bits) - 90.0°
true_heading = 90 # 实际航向 (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 print_binary(data):
"""打印二进制数据"""
print("十六进制表示:", data.hex())
print("二进制表示:", ' '.join(format(byte, '08b') for byte in data))
def send_data_continuously():
"""持续发送AIS和GPS数据到指定端口"""
host = 'localhost'
port = 8082
# 无连接型
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
print(f"模拟发送数据到 {host}:{port} (UDP)")
while True:
try:
# 生成并发送AIS报文
demod_content = encode_demod_fields()
ais_data = create_ais_info_packet(demod_content)
ais_packet = create_tcp_packet(12, ais_data)
s.sendto(ais_packet, (host, port))
print("AIS报文:")
print_binary(ais_packet)
print("已发送AIS报文")
# 生成并发送GPS报文
gps_content = create_gps_packet()
gps_packet = create_tcp_packet(7, gps_content)
s.sendto(gps_packet, (host, port))
print("\nGPS报文:")
print_binary(gps_packet)
print("已发送GPS报文")
# 等待1秒
time.sleep(3)
except KeyboardInterrupt:
print("\n停止发送数据")
break
except Exception as e:
print(f"发送数据时出错: {e}")
break
if __name__ == "__main__":
send_data_continuously()