import socket
import threading
import struct
from enum import IntEnum
from printer import perror
class METRIC(IntEnum):
MODEM_TEMP = 3 # celsius | int
CAMERA_TEMP = 5 # celsius | int
CPU_TEMP = 6 # celsius | int (Average temp of the high performance core cluster)
GPU_TEMP = 12 # celsius | int
BATTERY_TEMP = 42 # celsius | int
PHONE_BATTERY_PERCENT = 100 # [0-100] | int
POSITION = 101 # LATITUDE | float,
# LONGITUDE | float,
# ACCURACY | int
HEADING = 102 # degrees | int
SIGNAL_LEVEL = 103 # [0-5] | int
CAR_BATTERY_VOLTAGE = 104 # centiVolt | int
ELECTRONICS_BATTERY_VOLTAGE = 105 # centiVolt | int
# Metrics that are displayed on the video stream
STREAM_METRICS = [
METRIC.PHONE_BATTERY_PERCENT,
METRIC.HEADING,
METRIC.SIGNAL_LEVEL,
METRIC.CAR_BATTERY_VOLTAGE,
METRIC.ELECTRONICS_BATTERY_VOLTAGE
]
TEMP_METRICS = [
METRIC.MODEM_TEMP,
METRIC.CAMERA_TEMP,
METRIC.CPU_TEMP,
METRIC.GPU_TEMP,
METRIC.BATTERY_TEMP
]
class Server:
def __init__(self, telemetry_callback):
self.host = '0.0.0.0'
self.control_port = 8003
self.control_socket = None
self.control_client = None
self.telemetry_callback = telemetry_callback
self.running = False
def start_control_server(self):
try:
self.control_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.control_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.control_socket.bind((self.host, self.control_port))
self.control_socket.listen(1)
print(f"CONTROL SERVER thread listening on TCP {self.host}:{self.control_port}")
except Exception as e:
perror(f"Failed to start the control server: {e}")
# Control socket initialized, check forever for incoming connections to accept
while self.running:
try:
# Set a timeout to check the running flag periodically
self.control_socket.settimeout(1.0)
client, addr = self.control_socket.accept()
client.settimeout(1.0)
self.control_client = client
print(f"Control client connected from {addr}")
except socket.timeout:
continue # No one is connecting (expected, just loop again)
# Client succesfully connected, check forever for incoming messages to decode
while self.running:
try:
self._recv_telemetry()
except socket.timeout:
# The client simply is not sending anything (expected, just loop again)
continue
except Exception as e:
perror(f"Control client error: {e}")
break
# Connection broke - either gracefully or not
self.control_client.close()
self.control_client = None
print("Control client disconnected")
def _recv_telemetry(self):
# The first byte identifies the metric type
packet = self.control_client.recv(1)
if not packet:
raise ConnectionResetError("connection lost")
metric = METRIC(packet[0])
# Next, each metric encodes its values in 4-bytes numbers
# Every metric except for position uses one 4-byte integer
# Position uses three 4-byte floats
data = b''
expected_bytes = 4
if metric == METRIC.POSITION:
expected_bytes *= 3
while len(data) < expected_bytes:
packet = self.control_client.recv(expected_bytes - len(data))
if not packet:
raise ConnectionResetError("connection lost")
data += packet
if metric == METRIC.POSITION:
value = []
value.append(struct.unpack('>f', data[:4])[0]) # big-endian float
value.append(struct.unpack('>f', data[4:8])[0]) # big-endian float
value.append(struct.unpack('>i', data[8:])[0]) # big-endian int
else:
value = struct.unpack('>i', data)[0] # big-endian int
# Consume the decoded data
self.telemetry_callback(metric, value)
def send_command(self, command):
# There might be race conditions because self.control_client
# is used both here and by the listening deamon
if not self.running:
print("The server is not running")
return
if not self.control_client:
print("No control client connected")
return
try:
msglen = len(command)
totalsent = 0
while totalsent < msglen:
sent = self.control_client.send(command[totalsent:])
if sent == 0:
raise ConnectionResetError("connection lost")
totalsent = totalsent + sent
except Exception as e:
perror(f"Unable to send command: {e}")
self.control_client.close()
self.control_client = None
print("Control client disconnected")
def stop(self):
self.running = False
if self.control_client:
self.control_client.close()
self.control_client = None
if self.control_socket:
self.control_socket.close()
self.control_socket = None
print("CONTROL SERVER thread terminated")
def start(self):
self.running = True
threading.Thread(
target=self.start_control_server,
daemon=True
).start()
print("CONTROL SERVER thread launched")