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

316 lines
11 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
from random import uniform, randint
import math
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_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(SHIP_MMSI, lat, lon, heading):
"""编码解调信息字段并打印解码结果"""
# 初始化二进制位列表
bits = []
print(SHIP_MMSI, lat, lon, heading)
# 示例数据
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 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}")
while True:
# 接收客户端数据
data = conn.recv(4096) # 接收客户端数据
if data:
print(f"收到VDES客户端消息: {data.hex()}")
# 向客户端发送数据
try:
# 生成并发送AIS报文
selected_ship = random.choice(SHIP_LIST)
mmsi = selected_ship[0]
lon, lat = generate_ship_position(mmsi)
heading = SHIP_STATUS[mmsi]['heading']
demod_content = encode_demod_fields(mmsi, lat, lon, heading)
ais_data = create_ais_info_packet(demod_content)
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)
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()