mirror of
https://github.com/yoboujon/dumber.git
synced 2025-06-08 22:00:49 +02:00
340 lines
11 KiB
Python
340 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import socket
|
|
import time
|
|
import sys
|
|
|
|
from PyQt5 import QtCore
|
|
from globvar import GlobVar
|
|
|
|
import ipaddress
|
|
import threading
|
|
|
|
# Network event from socket.
|
|
# Event are 'Connected to server' or 'disconnected from server'
|
|
class NetworkEvents():
|
|
EVENT_CONNECTED=1
|
|
EVENT_CONNECTION_LOST=0
|
|
UNKNOWN_EVENT=-1
|
|
|
|
class NetworkAnswers():
|
|
# Decoded answers
|
|
ACK = 1
|
|
NACK =2
|
|
COM_ERROR =3
|
|
TIMEOUT_ERROR=4
|
|
CMD_REJECTED=5
|
|
UNKNOWN=-1
|
|
|
|
# Network managment class
|
|
# Contain a thread for conenction to server and data reception
|
|
# Contains functions for sending message to server
|
|
# Contains functions for answer decoding
|
|
class Network(QtCore.QThread):
|
|
i=0
|
|
reconnectionDelay =0
|
|
waitForAnswer=False
|
|
receivedAnswer = ""
|
|
receiveFlag = None
|
|
|
|
receptionEvent = QtCore.pyqtSignal(str)
|
|
connectionEvent= QtCore.pyqtSignal(int)
|
|
answerEvent= QtCore.pyqtSignal(int)
|
|
logEvent = QtCore.pyqtSignal(str)
|
|
|
|
# List of possible answers from server
|
|
ANSWER_ACK = "AACK"
|
|
ANSWER_NACK = "ANAK"
|
|
ANSWER_COM_ERROR = "ACER"
|
|
ANSWER_TIMEOUT = "ATIM"
|
|
ANSWER_CMD_REJECTED = "ACRJ"
|
|
|
|
# List of possible messages from server
|
|
MESSAGE = "MSSG"
|
|
ROBOT_BATTERY_LEVEL = "RBLV"
|
|
ROBOT_CURRENT_STATE = "RCST"
|
|
|
|
# List of accepted command by server
|
|
CAMERA_OPEN = "COPN"
|
|
CAMERA_CLOSE = "CCLS"
|
|
CAMERA_IMAGE = "CIMG"
|
|
CAMERA_ARENA_ASK = "CASA"
|
|
CAMERA_ARENA_INFIRM = "CAIN"
|
|
CAMERA_ARENA_CONFIRM = "CACO"
|
|
CAMERA_POSITION_COMPUTE = "CPCO"
|
|
CAMERA_POSITION_STOP = "CPST"
|
|
CAMERA_POSITION = "CPOS"
|
|
ROBOT_COM_OPEN = "ROPN"
|
|
ROBOT_COM_CLOSE = "RCLS"
|
|
ROBOT_PING = "RPIN"
|
|
ROBOT_RESET = "RRST"
|
|
ROBOT_START_WITHOUT_WD = "RSOW"
|
|
ROBOT_START_WITH_WD = "RSWW"
|
|
ROBOT_RELOAD_WD = "RLDW"
|
|
ROBOT_MOVE = "RMOV"
|
|
ROBOT_TURN = "RTRN"
|
|
ROBOT_GO_FORWARD = "RGFW"
|
|
ROBOT_GO_BACKWARD = "RGBW"
|
|
ROBOT_GO_LEFT = "RGLF"
|
|
ROBOT_GO_RIGHT = "RGRI"
|
|
ROBOT_STOP = "RSTP"
|
|
ROBOT_POWEROFF = "RPOF"
|
|
ROBOT_GET_BATTERY = "RGBT"
|
|
ROBOT_GET_STATE = "RGST"
|
|
|
|
SEPARATOR_CHAR = ':'
|
|
|
|
def __init__(self):
|
|
super(Network, self).__init__()
|
|
self.i =0
|
|
self.receiveFlag = threading.Event()
|
|
|
|
def run(self):
|
|
print ("Network thread started" )
|
|
self.i =0
|
|
|
|
while True:
|
|
self.i = self.i+1
|
|
|
|
try:
|
|
self.__connect()
|
|
|
|
self.connectionEvent.emit(NetworkEvents.EVENT_CONNECTED)
|
|
self.__read()
|
|
|
|
except (RuntimeError, TimeoutError, socket.gaierror, ConnectionResetError, ConnectionRefusedError) as e:
|
|
print ("exception in network thread: " + str(e))
|
|
if e.__class__ == RuntimeError or \
|
|
e.__class__== ConnectionResetError or \
|
|
e.__class__ == ConnectionRefusedError:
|
|
self.connectionEvent.emit(NetworkEvents.EVENT_CONNECTION_LOST)
|
|
|
|
except Exception as e:
|
|
print ("unknown exception in network thread: " + str(e))
|
|
|
|
time.sleep(2.0)
|
|
|
|
# Private method for connecting to server
|
|
def __connect(self) -> None:
|
|
#self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
try:
|
|
self.sock.connect((GlobVar.address, GlobVar.port))
|
|
except ConnectionRefusedError:
|
|
raise RuntimeError("Unable to connect to " + GlobVar.address + ":" + str(GlobVar.port))
|
|
|
|
# In UDP, no way to know if server is running (connect is always successfull).
|
|
# So, send a single line feed to ping the server
|
|
self.sock.send(str.encode("\n"))
|
|
self.waitForAnswer=False
|
|
|
|
# Private method for receiving data from server.
|
|
# Data received are sent to callback __receiveHandler for decoding
|
|
def __read(self) -> None:
|
|
chunks = []
|
|
bytes_recd = 0
|
|
last_char=0
|
|
|
|
while True:
|
|
while last_char != 0x0A:
|
|
chunk = self.sock.recv(2048)
|
|
#print ("Chunk =" + str(chunk))
|
|
|
|
if chunk == b'':
|
|
raise RuntimeError("Connection lost with " + GlobVar.address + ":" + str(GlobVar.port))
|
|
chunks.append(chunk)
|
|
bytes_recd = bytes_recd + len(chunk)
|
|
last_char=chunk[-1]
|
|
|
|
self.__receiveHandler(b''.join(chunks).decode("utf-8"))
|
|
chunks = []
|
|
bytes_recd =0
|
|
last_char=0
|
|
|
|
# private callback for reception and decoding of data.
|
|
# If no answer is wait, send data to caller callback
|
|
def __receiveHandler(self, data: str) -> None:
|
|
# traitement a faire lors de la reception de donnée
|
|
self.logEvent.emit("< " + data)
|
|
|
|
if self.waitForAnswer:
|
|
self.receivedAnswer = data
|
|
self.waitForAnswer=False
|
|
self.receiveFlag.set()
|
|
else:
|
|
# si pas de donnée prevue en reception, alors envoie au callback
|
|
self.receptionEvent.emit(data)
|
|
|
|
def checkAddressIsValid(self, address: str) -> bool:
|
|
try:
|
|
if address == "localhost":
|
|
return True
|
|
|
|
ip=ipaddress.ip_address(address)
|
|
return True
|
|
|
|
except ValueError as e:
|
|
return False
|
|
|
|
# Private method for sending raw data to server
|
|
def __write(self, msg: str) -> None:
|
|
totalsent = 0
|
|
if msg[-1] != '\n':
|
|
msg = msg + '\n'
|
|
|
|
MSGLEN = len(msg)
|
|
msgbytes = bytes(msg,'UTF-8')
|
|
|
|
self.logEvent.emit ("> " + msg)
|
|
|
|
while totalsent < MSGLEN:
|
|
sent = self.sock.send(msgbytes[totalsent:])
|
|
if sent == 0:
|
|
GlobVar.connectedToPi=False
|
|
raise RuntimeError("Unable to send data to " + GlobVar.address + ":" + str(GlobVar.port))
|
|
totalsent = totalsent + sent
|
|
|
|
# Private method for sending command to server
|
|
def __sendCommand(self,cmd, waitans: bool) -> str:
|
|
if waitans == True:
|
|
self.waitForAnswer = True
|
|
else:
|
|
self.waitForAnswer = False
|
|
|
|
self.__write(cmd)
|
|
|
|
if waitans:
|
|
ans = self.ANSWER_TIMEOUT
|
|
try:
|
|
self.receiveFlag.wait(timeout=GlobVar.timeout) # Max waiting time = GlobVar.timeout
|
|
|
|
if self.receiveFlag.is_set():
|
|
ans=self.receivedAnswer
|
|
self.receivedAnswer=""
|
|
else:
|
|
ans=self.ANSWER_TIMEOUT
|
|
|
|
self.receiveFlag.clear()
|
|
except TimeoutError:
|
|
ans=self.ANSWER_TIMEOUT
|
|
except Exception as e:
|
|
GlobVar.exceptionmsg = str(e)
|
|
|
|
self.waitForAnswer=False
|
|
return ans
|
|
else:
|
|
return self.ANSWER_ACK
|
|
|
|
# Private method for decoding answer from server
|
|
def __decodeAnswer(self, ans: str) -> int:
|
|
if self.ANSWER_ACK in ans:
|
|
return NetworkAnswers.ACK
|
|
elif self.ANSWER_NACK in ans:
|
|
return NetworkAnswers.NACK
|
|
elif self.ANSWER_COM_ERROR in ans:
|
|
return NetworkAnswers.COM_ERROR
|
|
elif self.ANSWER_TIMEOUT in ans:
|
|
return NetworkAnswers.TIMEOUT_ERROR
|
|
elif self.ANSWER_CMD_REJECTED in ans:
|
|
return NetworkAnswers.CMD_REJECTED
|
|
else:
|
|
return NetworkAnswers.UNKNOWN
|
|
|
|
# Send OpenCom command to server
|
|
def robotOpenCom(self) -> int:
|
|
ans = self.__sendCommand(self.ROBOT_COM_OPEN, True)
|
|
decodedAns = self.__decodeAnswer(ans)
|
|
self.answerEvent.emit(decodedAns)
|
|
return decodedAns
|
|
|
|
# Send CloseCom command to server
|
|
def robotCloseCom(self) -> int:
|
|
ans = self.__sendCommand(self.ROBOT_COM_CLOSE, True)
|
|
decodedAns = self.__decodeAnswer(ans)
|
|
self.answerEvent.emit(decodedAns)
|
|
return decodedAns
|
|
|
|
# Send StartWithWatchdog command to server
|
|
def robotStartWithWatchdog(self) -> int:
|
|
ans = self.__sendCommand(self.ROBOT_START_WITH_WD, True)
|
|
decodedAns = self.__decodeAnswer(ans)
|
|
self.answerEvent.emit(decodedAns)
|
|
return decodedAns
|
|
|
|
# Send StartWithoutWatchdog command to server
|
|
def robotStartWithoutWatchdog(self) -> int:
|
|
ans = self.__sendCommand(self.ROBOT_START_WITHOUT_WD, True)
|
|
decodedAns = self.__decodeAnswer(ans)
|
|
self.answerEvent.emit(decodedAns)
|
|
return decodedAns
|
|
|
|
# Send Reset command to server
|
|
def robotReset(self) -> int:
|
|
ans = self.__sendCommand(self.ROBOT_RESET, True)
|
|
decodedAns = self.__decodeAnswer(ans)
|
|
self.answerEvent.emit(decodedAns)
|
|
return decodedAns
|
|
|
|
# Send Stop command to server
|
|
def robotStop(self) -> int:
|
|
ans = self.__sendCommand(self.ROBOT_STOP, True)
|
|
decodedAns = self.__decodeAnswer(ans)
|
|
self.answerEvent.emit(decodedAns)
|
|
return decodedAns
|
|
|
|
# Send GoLeft command to server
|
|
def robotGoLeft(self) -> int:
|
|
ans = self.__sendCommand(self.ROBOT_GO_LEFT, True)
|
|
decodedAns = self.__decodeAnswer(ans)
|
|
self.answerEvent.emit(decodedAns)
|
|
return decodedAns
|
|
|
|
# Send GoRight command to server
|
|
def robotGoRight(self) -> int:
|
|
ans = self.__sendCommand(self.ROBOT_GO_RIGHT, True)
|
|
decodedAns = self.__decodeAnswer(ans)
|
|
self.answerEvent.emit(decodedAns)
|
|
return decodedAns
|
|
|
|
# Send GoForward command to server
|
|
def robotGoForward(self) -> int:
|
|
ans = self.__sendCommand(self.ROBOT_GO_FORWARD, True)
|
|
decodedAns = self.__decodeAnswer(ans)
|
|
self.answerEvent.emit(decodedAns)
|
|
return decodedAns
|
|
|
|
# Send GoBackward command to server
|
|
def robotGoBackward(self) -> int:
|
|
ans = self.__sendCommand(self.ROBOT_GO_BACKWARD, True)
|
|
decodedAns = self.__decodeAnswer(ans)
|
|
self.answerEvent.emit(decodedAns)
|
|
return decodedAns
|
|
|
|
# Send GetBattery command to server
|
|
def robotGetBattery(self) -> None:
|
|
ans = self.__sendCommand(self.ROBOT_GET_BATTERY,False)
|
|
|
|
# Function for decoding battery level
|
|
def batterylevelToStr(batlvl: int) -> str:
|
|
switcher = {
|
|
2: "Full",
|
|
1: "Mid",
|
|
0: "Empty",
|
|
}
|
|
|
|
return switcher.get(batlvl, "Unknown")
|
|
|
|
# Function for display human readable answer
|
|
def answertoStr(ans: int) -> str:
|
|
switcher = {
|
|
Network.ACK: "Acknowledged",
|
|
Network.NACK: "Not acknowledged",
|
|
Network.TIMEOUT_ERROR: "No answer (timeout)",
|
|
Network.CMD_REJECTED: "Command rejected",
|
|
Network.COM_ERROR: "Communication error (invalid command)"
|
|
}
|
|
|
|
return switcher.get(ans, "Unknown answer")
|
|
|
|
|