diff --git a/software/monitor/monitor-python-qt/main_window.py b/software/monitor/monitor-python-qt/main_window.py
index 1090808..d8b2e81 100644
--- a/software/monitor/monitor-python-qt/main_window.py
+++ b/software/monitor/monitor-python-qt/main_window.py
@@ -13,35 +13,54 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
- MainWindow.resize(928, 629)
+ MainWindow.resize(973, 635)
+ MainWindow.setMinimumSize(QtCore.QSize(973, 635))
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
- self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.centralwidget)
+ self.widget = QtWidgets.QWidget(self.centralwidget)
+ self.widget.setGeometry(QtCore.QRect(14, 13, 947, 574))
+ self.widget.setObjectName("widget")
+ self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.widget)
+ self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.verticalLayout_4 = QtWidgets.QVBoxLayout()
self.verticalLayout_4.setObjectName("verticalLayout_4")
- self.checkBox_enableCamera = QtWidgets.QCheckBox(self.centralwidget)
+ self.checkBox_enableCamera = QtWidgets.QCheckBox(self.widget)
self.checkBox_enableCamera.setObjectName("checkBox_enableCamera")
self.verticalLayout_4.addWidget(self.checkBox_enableCamera)
- self.graphicsView_Image = QtWidgets.QGraphicsView(self.centralwidget)
- self.graphicsView_Image.setObjectName("graphicsView_Image")
- self.verticalLayout_4.addWidget(self.graphicsView_Image)
- self.pushButton_confirmArena = QtWidgets.QPushButton(self.centralwidget)
+ self.label_Image = QtWidgets.QLabel(self.widget)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.label_Image.sizePolicy().hasHeightForWidth())
+ self.label_Image.setSizePolicy(sizePolicy)
+ self.label_Image.setMinimumSize(QtCore.QSize(640, 480))
+ self.label_Image.setSizeIncrement(QtCore.QSize(1, 1))
+ self.label_Image.setFrameShape(QtWidgets.QFrame.Box)
+ self.label_Image.setText("")
+ self.label_Image.setObjectName("label_Image")
+ self.verticalLayout_4.addWidget(self.label_Image)
+ self.pushButton_confirmArena = QtWidgets.QPushButton(self.widget)
self.pushButton_confirmArena.setObjectName("pushButton_confirmArena")
self.verticalLayout_4.addWidget(self.pushButton_confirmArena)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
- self.checkBox_enableFPS = QtWidgets.QCheckBox(self.centralwidget)
+ self.checkBox_enableFPS = QtWidgets.QCheckBox(self.widget)
self.checkBox_enableFPS.setObjectName("checkBox_enableFPS")
self.horizontalLayout_3.addWidget(self.checkBox_enableFPS)
- self.checkBox_enablePosition = QtWidgets.QCheckBox(self.centralwidget)
+ self.checkBox_enablePosition = QtWidgets.QCheckBox(self.widget)
self.checkBox_enablePosition.setObjectName("checkBox_enablePosition")
self.horizontalLayout_3.addWidget(self.checkBox_enablePosition)
self.verticalLayout_4.addLayout(self.horizontalLayout_3)
self.horizontalLayout_6.addLayout(self.verticalLayout_4)
self.verticalLayout_6 = QtWidgets.QVBoxLayout()
self.verticalLayout_6.setObjectName("verticalLayout_6")
- self.groupBox_connection = QtWidgets.QGroupBox(self.centralwidget)
+ self.groupBox_activation = QtWidgets.QGroupBox(self.widget)
+ self.groupBox_activation.setAlignment(QtCore.Qt.AlignCenter)
+ self.groupBox_activation.setObjectName("groupBox_activation")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_activation)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.groupBox_connection = QtWidgets.QGroupBox(self.groupBox_activation)
self.groupBox_connection.setAlignment(QtCore.Qt.AlignCenter)
self.groupBox_connection.setFlat(False)
self.groupBox_connection.setCheckable(False)
@@ -102,12 +121,7 @@ class Ui_MainWindow(object):
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line.setObjectName("line")
self.verticalLayout.addWidget(self.line)
- self.verticalLayout_6.addWidget(self.groupBox_connection)
- self.groupBox_activation = QtWidgets.QGroupBox(self.centralwidget)
- self.groupBox_activation.setAlignment(QtCore.Qt.AlignCenter)
- self.groupBox_activation.setObjectName("groupBox_activation")
- self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_activation)
- self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.verticalLayout_2.addWidget(self.groupBox_connection)
self.checkBox_watchdog = QtWidgets.QCheckBox(self.groupBox_activation)
self.checkBox_watchdog.setObjectName("checkBox_watchdog")
self.verticalLayout_2.addWidget(self.checkBox_watchdog)
@@ -120,7 +134,7 @@ class Ui_MainWindow(object):
self.line_2.setObjectName("line_2")
self.verticalLayout_2.addWidget(self.line_2)
self.verticalLayout_6.addWidget(self.groupBox_activation)
- self.groupBox_mouvments = QtWidgets.QGroupBox(self.centralwidget)
+ self.groupBox_mouvments = QtWidgets.QGroupBox(self.widget)
self.groupBox_mouvments.setAlignment(QtCore.Qt.AlignCenter)
self.groupBox_mouvments.setObjectName("groupBox_mouvments")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_mouvments)
@@ -149,7 +163,7 @@ class Ui_MainWindow(object):
self.line_3.setObjectName("line_3")
self.verticalLayout_3.addWidget(self.line_3)
self.verticalLayout_6.addWidget(self.groupBox_mouvments)
- self.groupBox_AnswerandBattery = QtWidgets.QGroupBox(self.centralwidget)
+ self.groupBox_AnswerandBattery = QtWidgets.QGroupBox(self.widget)
self.groupBox_AnswerandBattery.setObjectName("groupBox_AnswerandBattery")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox_AnswerandBattery)
self.verticalLayout_5.setObjectName("verticalLayout_5")
@@ -171,7 +185,7 @@ class Ui_MainWindow(object):
self.horizontalLayout_6.addLayout(self.verticalLayout_6)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
- self.menubar.setGeometry(QtCore.QRect(0, 0, 928, 22))
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 973, 22))
self.menubar.setObjectName("menubar")
self.menu_Quitter = QtWidgets.QMenu(self.menubar)
self.menu_Quitter.setObjectName("menu_Quitter")
@@ -198,12 +212,12 @@ class Ui_MainWindow(object):
self.pushButton_confirmArena.setText(_translate("MainWindow", "Co&nfirme arena border..."))
self.checkBox_enableFPS.setText(_translate("MainWindow", "Enable &FPS"))
self.checkBox_enablePosition.setText(_translate("MainWindow", "Enable Pos&ition"))
+ self.groupBox_activation.setTitle(_translate("MainWindow", "Robot Activation"))
self.groupBox_connection.setTitle(_translate("MainWindow", "Connection"))
self.label.setText(_translate("MainWindow", "Address:"))
self.label_2.setText(_translate("MainWindow", "Port:"))
self.label_3.setText(_translate("MainWindow", "Status:"))
self.label_connectionStatus.setText(_translate("MainWindow", "Not connected"))
- self.groupBox_activation.setTitle(_translate("MainWindow", "Robot Activation"))
self.checkBox_watchdog.setText(_translate("MainWindow", "Start with &watchdog"))
self.pushButton_start.setText(_translate("MainWindow", "Start r&obot"))
self.groupBox_mouvments.setTitle(_translate("MainWindow", "Mouvments"))
diff --git a/software/monitor/monitor-python-qt/monitor-python.py b/software/monitor/monitor-python-qt/monitor-python.py
index b04facc..bf13e7e 100755
--- a/software/monitor/monitor-python-qt/monitor-python.py
+++ b/software/monitor/monitor-python-qt/monitor-python.py
@@ -1,27 +1,34 @@
#!/usr/bin/env python3
from email.message import Message
+from email.policy import Policy
import os
import time
import sys
+from tkinter import Image
-from PyQt5 import (QtCore, QtWidgets)
+from PyQt5 import (QtCore, QtWidgets, QtGui)
from main_window import Ui_MainWindow
from log_dialog import Ui_Dialog
from network import *
from globvar import GlobVar
+import base64
+
class Window(QtWidgets.QMainWindow, Ui_MainWindow):
_msg_dialog= None
_batteryTimer=None
+ _FPSTimer=None
+
+ fps=0
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
- self.DisableUIWidgets("Network")
+ #self.DisableUIWidgets("Network")
self.lineEdit_address.setText(GlobVar.address)
self.lineEdit_port.setText(str(GlobVar.port))
@@ -30,6 +37,8 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self._msg_dialog.ui= Ui_Dialog()
self._msg_dialog.ui.setupUi(self._msg_dialog)
+ self.fps=0
+
self.networkThread = Network()
self.connectSignalSlots()
@@ -41,6 +50,10 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self._batteryTimer = QtCore.QTimer()
self._batteryTimer.timeout.connect(self.OnBatteryTimeout)
+ # Create fps timer
+ self._FPSTimer = QtCore.QTimer()
+ self._FPSTimer.timeout.connect(self.OnFPSTimeout)
+
def connectSignalSlots(self):
# Buttons
self.pushButton_start.pressed.connect(self.OnButtonPress_Start)
@@ -78,7 +91,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
def EnableUIWidgets(self, area):
if area == "Network":
self.groupBox_activation.setDisabled(False)
- self.graphicsView_Image.setDisabled(False)
+ self.label_Image.setDisabled(False)
self.pushButton_confirmArena.setDisabled(False)
self.checkBox_enableCamera.setDisabled(False)
self.checkBox_enableFPS.setDisabled(False)
@@ -90,7 +103,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
def DisableUIWidgets(self, area):
if area == "Network":
self.groupBox_activation.setDisabled(True)
- self.graphicsView_Image.setDisabled(True)
+ self.label_Image.setDisabled(True)
self.pushButton_confirmArena.setDisabled(True)
self.checkBox_enableCamera.setDisabled(True)
self.checkBox_enableFPS.setDisabled(True)
@@ -131,16 +144,19 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
msg.warning(self,'Invalid answer', 'Server answer was not acknowledged. Maybe robot is still running', msg.Ok)
self.pushButton_start.setText("Start r&obot")
- self.DisableUIWidgets("Robot")
+ #self.DisableUIWidgets("Robot")
@QtCore.pyqtSlot()
def OnButtonPress_ConfirmArena(self):
+ self.networkThread.cameraAskArena()
msg= QtWidgets.QMessageBox
ret = msg.question(self, '', 'Arena boundaries are correctly detected ?',msg.Yes| msg.No)
if ret == msg.Yes:
+ self.networkThread.cameraConfirmArena()
print ("Answer is YES")
else:
+ self.networkThread.cameraInfirmArena()
print ("Answer is NO")
@QtCore.pyqtSlot()
@@ -165,21 +181,26 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
@QtCore.pyqtSlot(int)
def OnCheckBoxChanged_EnableCamera(self, state):
- msg= QtWidgets.QMessageBox
- msg.information(self, 'Feature not ready', 'Feature not yet available', msg.Ok)
- self.checkBox_enableCamera.setChecked(False)
+ if self.checkBox_enableCamera.isChecked():
+ self.networkThread.cameraOpen()
+ else:
+ self.networkThread.cameraClose()
@QtCore.pyqtSlot(int)
def OnCheckBoxChanged_EnableFPS(self, state):
- msg= QtWidgets.QMessageBox
- msg.information(self, 'Feature not ready', 'Feature not yet available', msg.Ok)
- self.checkBox_enableFPS.setChecked(False)
+ if state !=0:
+ self._FPSTimer.start(1000)
+ self.checkBox_enableFPS.setText("FPS (0)")
+ else:
+ self._FPSTimer.stop()
+ self.checkBox_enableFPS.setText("Enable FPS")
@QtCore.pyqtSlot(int)
def OnCheckBoxChanged_EnablePosition(self, state):
- msg= QtWidgets.QMessageBox
- msg.information(self, 'Feature not ready', 'Feature not yet available', msg.Ok)
- self.checkBox_enablePosition.setChecked(False)
+ if self.checkBox_enablePosition.isChecked():
+ self.networkThread.cameraGetPosition()
+ else:
+ self.networkThread.cameraStopPosition()
@QtCore.pyqtSlot(int)
def OnCheckBoxChanged_GetBattery(self, state):
@@ -229,13 +250,38 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self.checkBox_getBattery.setText ("Get battery (2 = full)")
else:
self.checkBox_getBattery.setText ("Get battery (invalid value)")
-
+ elif Network.CAMERA_IMAGE in s:
+ #print ("Image received")
+ #print ("Date received: " + s)
+
+ self.fps=self.fps+1
+ str_split = s.split(':')
+ image_jpg= base64.b64decode(str_split[1])
+ img = QtGui.QImage.fromData(image_jpg, "jpg")
+ im_pixmap = QtGui.QPixmap(QtGui.QPixmap.fromImage(img))
+
+ #print ("Image size: " + str(im_pixmap.width()) + "x" + str(im_pixmap.height()))
+
+ self.label_Image.setPixmap(im_pixmap)
+ self.label_Image.setScaledContents(True)
+ self.label_Image.setSizePolicy(QtWidgets.QSizePolicy.Ignored,QtWidgets.QSizePolicy.Ignored)
+ elif Network.CAMERA_POSITION in s:
+ print ("position received")
+ print (s)
+
# Callback for battery timeout
@QtCore.pyqtSlot()
def OnBatteryTimeout(self) -> None:
# Send a request for battery level. Answer will be done in OnReceptionEvent callback
self.networkThread.robotGetBattery()
+ # Callback for FPS timeout
+ @QtCore.pyqtSlot()
+ def OnFPSTimeout(self) -> None:
+ # Display current FPS
+ self.checkBox_enableFPS.setText("FPS (" + str(self.fps)+")")
+ self.fps=0
+
# Callback for connection/deconnection event from network manager
@QtCore.pyqtSlot(int)
def OnConnectionEvent(self, event) -> None:
@@ -251,7 +297,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self.label_connectionStatus.setText("Not connected")
self.pushButton_start.setText("Start r&obot")
- self.DisableUIWidgets("Network")
+ #self.DisableUIWidgets("Network")
# Callback for answer event from network manager
@QtCore.pyqtSlot(int)
diff --git a/software/monitor/monitor-python-qt/network.py b/software/monitor/monitor-python-qt/network.py
index 026a1e1..6fc8ee8 100644
--- a/software/monitor/monitor-python-qt/network.py
+++ b/software/monitor/monitor-python-qt/network.py
@@ -116,8 +116,8 @@ class Network(QtCore.QThread):
# 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)
+ 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))
@@ -126,7 +126,7 @@ class Network(QtCore.QThread):
# 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.sock.send(str.encode("\n"))
self.waitForAnswer=False
# Private method for receiving data from server.
@@ -147,6 +147,11 @@ class Network(QtCore.QThread):
bytes_recd = bytes_recd + len(chunk)
last_char=chunk[-1]
+ #print ("")
+ #print ("Data: ")
+ #print (b''.join(chunks))
+ #print ("")
+
self.__receiveHandler(b''.join(chunks).decode("utf-8"))
chunks = []
bytes_recd =0
@@ -315,6 +320,46 @@ class Network(QtCore.QThread):
def robotGetBattery(self) -> None:
ans = self.__sendCommand(self.ROBOT_GET_BATTERY,False)
+ # Send OpenCamera command to server
+ def cameraOpen(self) -> int:
+ ans = self.__sendCommand(self.CAMERA_OPEN, True)
+ decodedAns = self.__decodeAnswer(ans)
+ self.answerEvent.emit(decodedAns)
+ return decodedAns
+
+ # Send CloseCamera command to server
+ def cameraClose(self) -> int:
+ ans = self.__sendCommand(self.CAMERA_CLOSE, True)
+ decodedAns = self.__decodeAnswer(ans)
+ self.answerEvent.emit(decodedAns)
+ return decodedAns
+
+ # Send GetPosition command to server
+ def cameraGetPosition(self) -> int:
+ ans = self.__sendCommand(self.CAMERA_POSITION_COMPUTE, True)
+ decodedAns = self.__decodeAnswer(ans)
+ self.answerEvent.emit(decodedAns)
+ return decodedAns
+
+ # Send StopPosition command to server
+ def cameraStopPosition(self) -> int:
+ ans = self.__sendCommand(self.CAMERA_POSITION_STOP, True)
+ decodedAns = self.__decodeAnswer(ans)
+ self.answerEvent.emit(decodedAns)
+ return decodedAns
+
+ # Send AskArena command to server
+ def cameraAskArena(self) -> None:
+ ans = self.__sendCommand(self.CAMERA_ARENA_ASK,False)
+
+ # Send ConfirmArena command to server
+ def cameraConfirmArena(self) -> None:
+ ans = self.__sendCommand(self.CAMERA_ARENA_CONFIRM,False)
+
+ # Send InfirmArena command to server
+ def cameraInfirmArena(self) -> None:
+ ans = self.__sendCommand(self.CAMERA_ARENA_INFIRM,False)
+
# Function for decoding battery level
def batterylevelToStr(batlvl: int) -> str:
switcher = {
diff --git a/software/monitor/monitor-python-qt/ui/main_window.ui b/software/monitor/monitor-python-qt/ui/main_window.ui
index 623dedb..2a79080 100644
--- a/software/monitor/monitor-python-qt/ui/main_window.ui
+++ b/software/monitor/monitor-python-qt/ui/main_window.ui
@@ -6,313 +6,354 @@
0
0
- 928
- 629
+ 973
+ 635
+
+
+ 973
+ 635
+
+
Robot Monitor
-
- -
-
-
-
-
-
- Enable Ca&mera
-
-
-
- -
-
-
- -
-
-
- Co&nfirme arena border...
-
-
-
- -
-
-
-
-
-
- Enable &FPS
-
-
-
- -
-
-
- Enable Pos&ition
-
-
-
-
-
-
-
- -
-
-
-
-
-
- Connection
-
-
- Qt::AlignCenter
-
-
- false
-
-
- false
-
-
+
+
+
+ 14
+ 13
+ 947
+ 574
+
+
+
+
-
+
+
-
+
+
+ Enable Ca&mera
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 640
+ 480
+
+
+
+
+ 1
+ 1
+
+
+
+ QFrame::Box
+
+
+
+
+
+
+ -
+
+
+ Co&nfirme arena border...
+
+
+
+ -
+
-
-
-
-
-
-
-
- 0
- 0
-
-
-
- Address:
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 300
- 16777215
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Port:
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 300
- 16777215
-
-
-
-
-
-
- -
-
-
-
-
-
- Status:
-
-
-
- -
-
-
- Not connected
-
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
-
-
-
- -
-
-
- Robot Activation
-
-
- Qt::AlignCenter
-
-
-
-
-
+
- Start with &watchdog
+ Enable &FPS
-
-
+
- Start r&obot
-
-
-
- -
-
-
- Qt::Horizontal
+ Enable Pos&ition
-
-
- -
-
-
- Mouvments
-
-
- Qt::AlignCenter
-
-
-
-
-
-
-
-
-
- &Up
-
-
-
- -
-
-
- &Left
-
-
-
- -
-
-
- &Stop
-
-
-
- -
-
-
- &Right
-
-
-
- -
-
-
- &Down
-
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
-
-
-
- -
-
-
- Others
-
-
-
-
-
-
- Get &Battery
-
-
-
- -
-
-
-
-
-
- Last answer:
-
-
-
- -
-
-
- None
-
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
+
+
+
+ -
+
+
-
+
+
+ Robot Activation
+
+
+ Qt::AlignCenter
+
+
+
-
+
+
+ Connection
+
+
+ Qt::AlignCenter
+
+
+ false
+
+
+ false
+
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Address:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 16777215
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Port:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 16777215
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Status:
+
+
+
+ -
+
+
+ Not connected
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
+
+
+ -
+
+
+ Start with &watchdog
+
+
+
+ -
+
+
+ Start r&obot
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
+
+
+ -
+
+
+ Mouvments
+
+
+ Qt::AlignCenter
+
+
+
-
+
+
-
+
+
+ &Up
+
+
+
+ -
+
+
+ &Left
+
+
+
+ -
+
+
+ &Stop
+
+
+
+ -
+
+
+ &Right
+
+
+
+ -
+
+
+ &Down
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
+
+
+ -
+
+
+ Others
+
+
+
-
+
+
+ Get &Battery
+
+
+
+ -
+
+
-
+
+
+ Last answer:
+
+
+
+ -
+
+
+ None
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+