Загрузка данных


import socket
import json
import threading
import math

import cv2
import numpy as np
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import TwistStamped
from sensor_msgs.msg import LaserScan, Image
from nav_msgs.msg import Odometry
from cv_bridge import CvBridge

OPERATOR_IP = '192.168.1.100'
CMD_PORT = 5001
SCAN_PORT = 5002
VIDEO_PORT = 5000
SONAR_PORT = 5003
CAMERA_TOPIC = '/camera/image_raw'

SONAR_SIZE = 500
SONAR_RANGE = 3.0


def recv_exact(sock, n):
    buf = b''
    while len(buf) < n:
        chunk = sock.recv(n - len(buf))
        if not chunk:
            return None
        buf += chunk
    return buf


class CmdVelBridge(Node):

    def __init__(self):
        super().__init__('socket_cmd_bridge')
        self.pub = self.create_publisher(TwistStamped, '/cmd_vel', 10)
        self.create_subscription(LaserScan, '/scan', self.scan_cb, 10)
        self.min_distance = None
        self.last_scan = None

        self.bridge = CvBridge()
        self.last_frame = None
        self.create_subscription(Image, CAMERA_TOPIC, self.image_cb, 10)

        self.x = 0.0
        self.y = 0.0
        self.yaw = 0.0
        self.create_subscription(Odometry, '/odom', self.odom_cb, 10)

        self.sweep_angle = 0.0
        self.sweep_speed = 0.12

    def odom_cb(self, msg):
        self.x = msg.pose.pose.position.x
        self.y = msg.pose.pose.position.y
        q = msg.pose.pose.orientation
        siny = 2 * (q.w * q.z + q.x * q.y)
        cosy = 1 - 2 * (q.y * q.y + q.z * q.z)
        self.yaw = math.atan2(siny, cosy)

    def image_cb(self, msg):
        self.last_frame = self.bridge.imgmsg_to_cv2(msg, 'bgr8')

    def send(self, linear_x, angular_z):
        msg = TwistStamped()
        msg.header.stamp = self.get_clock().now().to_msg()
        msg.header.frame_id = 'base_link'
        msg.twist.linear.x = float(linear_x)
        msg.twist.angular.z = float(angular_z)
        self.pub.publish(msg)

    def stop(self):
        self.send(0.0, 0.0)

    def scan_cb(self, msg):
        valid = [r for r in msg.ranges if r > 0.0 and r < float('inf')]
        if valid:
            self.min_distance = min(valid)
        self.last_scan = msg

    def render_sonar(self):
        center = SONAR_SIZE // 2
        frame = np.zeros((SONAR_SIZE, SONAR_SIZE, 3), dtype=np.uint8)

        for ring in (1, 2, 3):
            radius = int(center * ring / 3)
            cv2.circle(frame, (center, center), radius, (0, 70, 0), 1)
        cv2.line(frame, (center, 0), (center, SONAR_SIZE), (0, 70, 0), 1)
        cv2.line(frame, (0, center), (SONAR_SIZE, center), (0, 70, 0), 1)

        if self.last_scan is not None:
            msg = self.last_scan
            angle = msg.angle_min
            for r in msg.ranges:
                if 0.0 < r < msg.range_max and r <= SONAR_RANGE:
                    px = int(center + r * math.cos(angle) * center / SONAR_RANGE)
                    py = int(center - r * math.sin(angle) * center / SONAR_RANGE)
                    if 0 <= px < SONAR_SIZE and 0 <= py < SONAR_SIZE:
                        cv2.circle(frame, (px, py), 2, (0, 255, 0), -1)
                angle += msg.angle_increment

        sweep_x = int(center + center * math.cos(self.sweep_angle))
        sweep_y = int(center - center * math.sin(self.sweep_angle))
        cv2.line(frame, (center, center), (sweep_x, sweep_y), (0, 255, 0), 2)

        heading_len = 35
        hx = center
        hy = center - heading_len
        cv2.arrowedLine(frame, (center, center), (hx, hy), (0, 165, 255), 3, tipLength=0.4)

        cv2.circle(frame, (center, center), 5, (0, 255, 0), -1)

        self.sweep_angle += self.sweep_speed
        if self.sweep_angle > 2 * math.pi:
            self.sweep_angle -= 2 * math.pi

        return frame

    def render_camera(self):
        if self.last_frame is None:
            return None
        frame = self.last_frame.copy()
        text = f"x={self.x:.2f}  y={self.y:.2f}  yaw={math.degrees(self.yaw):.0f} deg"
        cv2.putText(frame, text, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 3)
        cv2.putText(frame, text, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1)
        return frame


def scan_reply_loop(node):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((OPERATOR_IP, SCAN_PORT))
    print(f"подключено к оператору {OPERATOR_IP}:{SCAN_PORT} (лидар)")

    try:
        while True:
            request = sock.recv(16)
            if not request:
                break
            distance = node.min_distance if node.min_distance is not None else -1.0
            payload = json.dumps({"distance": distance}).encode('utf-8')
            sock.sendall(len(payload).to_bytes(4, 'big') + payload)
    except (ConnectionResetError, OSError) as e:
        print(f"канал лидара оборван: {e}")
    finally:
        sock.close()


def video_send_loop(node):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((OPERATOR_IP, VIDEO_PORT))
    print(f"подключено к оператору {OPERATOR_IP}:{VIDEO_PORT} (видео)")

    try:
        while True:
            frame = node.render_camera()
            if frame is None:
                continue
            ok, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 70])
            if not ok:
                continue
            data = buffer.tobytes()
            sock.sendall(len(data).to_bytes(4, 'big') + data)
    except (ConnectionResetError, BrokenPipeError, OSError) as e:
        print(f"канал видео оборван: {e}")
    finally:
        sock.close()


def sonar_send_loop(node):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((OPERATOR_IP, SONAR_PORT))
    print(f"подключено к оператору {OPERATOR_IP}:{SONAR_PORT} (сонар)")

    try:
        while True:
            frame = node.render_sonar()
            ok, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
            if not ok:
                continue
            data = buffer.tobytes()
            sock.sendall(len(data).to_bytes(4, 'big') + data)
    except (ConnectionResetError, BrokenPipeError, OSError) as e:
        print(f"канал сонара оборван: {e}")
    finally:
        sock.close()


def main():
    rclpy.init()
    node = CmdVelBridge()

    ros_thread = threading.Thread(target=rclpy.spin, args=(node,), daemon=True)
    ros_thread.start()

    scan_thread = threading.Thread(target=scan_reply_loop, args=(node,), daemon=True)
    scan_thread.start()

    video_thread = threading.Thread(target=video_send_loop, args=(node,), daemon=True)
    video_thread.start()

    map_thread = threading.Thread(target=sonar_send_loop, args=(node,), daemon=True)
    map_thread.start()

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((OPERATOR_IP, CMD_PORT))
    print(f"подключено к оператору {OPERATOR_IP}:{CMD_PORT}")

    try:
        while True:
            size_bytes = recv_exact(sock, 4)
            if size_bytes is None:
                break
            msg_len = int.from_bytes(size_bytes, 'big')

            payload = recv_exact(sock, msg_len)
            if payload is None:
                break

            command = json.loads(payload.decode('utf-8'))
            node.send(command.get("linear_x", 0.0), command.get("angular_z", 0.0))
            if "sweep_speed" in command:
                node.sweep_speed = command["sweep_speed"]
    except (ConnectionResetError, OSError) as e:
        print(f"соединение оборвано: {e}")
    except KeyboardInterrupt:
        pass
    finally:
        node.stop()
        sock.close()
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()