diff --git a/.gitignore b/.gitignore index 8859f1a..895dcfb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.pyc .vscode/ *.blend1 +software/pc/config.json diff --git a/software/pc/hexapod.py b/software/pc/hexapod.py new file mode 100644 index 0000000..c4e0d5f --- /dev/null +++ b/software/pc/hexapod.py @@ -0,0 +1,259 @@ +""" + Copyright (C) 2017 - PRESENT Zhengyu Peng, https://zpeng.me + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ---------- + + ` ` + -:. -#: + -//:. -###: + -////:. -#####: + -/:.://:. -###++##: + .. `://:- -###+. :##: + `:/+####+. :##: + .::::::::/+###. :##: + .////-----+##: `:###: + `-//:. :##: `:###/. + `-//:. :##:`:###/. + `-//:+######/. + `-/+####/. + `+##+. + :##: + :##: + :##: + :##: + :##: + .+: + +""" + +import sys +from PySide6 import QtWidgets, QtCore, QtGui +from PySide6.QtCore import Qt +from PySide6.QtCore import QThread, QFile +from PySide6.QtUiTools import QUiLoader + +import psutil +import socket + +from pathlib import Path +import json + +from tcpclient import TCPClient + +QtWidgets.QApplication.setAttribute( + QtCore.Qt.AA_EnableHighDpiScaling, True) # enable highdpi scaling +QtWidgets.QApplication.setAttribute( + QtCore.Qt.AA_UseHighDpiPixmaps, True) # use highdpi icons + + +class MyApp(QtWidgets.QMainWindow): + + def __init__(self): + super(MyApp, self).__init__() + + self.status_message = ['● Idle', '● Idle', + '● Idle', '● Idle', '● Idle', ''] + + config_file = Path('config.json') + + if config_file.exists(): + self.config = json.load(open('config.json', 'r')) + else: + self.config = dict() + json.dump(self.config, open('config.json', 'w+')) + + """Load UI""" + ui_file_name = "mainwindow.ui" + ui_file = QFile(ui_file_name) + loader = QUiLoader() + self.ui = loader.load(ui_file) + + ui_file.close() + self.init_ui() + + self.ui.comboBox_Interface.currentIndexChanged.connect( + self.on_interface_selection_change) + self.ui.button_Refresh.clicked.connect(self.on_refresh_button_clicked) + + self.ui.button_TcpClient.clicked.connect( + self.on_tcp_client_connect_button_clicked + ) + + self.ui.show() + + def save_config(self): + try: + json.dump(self.config, open('config.json', 'w+')) + except PermissionError as err: + pass + + def init_ui(self): + # Interface + self.update_network_interfaces() + + # TCP Client + # self.ui.textBrowser_TcpClientMessage.setEnabled(False) + # self.ui.lineEdit_TcpClientSend.setEnabled(False) + # self.ui.button_TcpClientSend.setEnabled(False) + + tcp_client_ip = self.config.get('TCP_Client_IP', '127.0.0.1') + tcp_client_port = self.config.get('TCP_Client_Port', '1234') + self.ui.lineEdit_TcpClientTargetIP.setText(tcp_client_ip) + self.ui.lineEdit_TcpClientTargetPort.setText(tcp_client_port) + + def update_network_interfaces(self): + self.net_if = psutil.net_if_addrs() + + interface_idx = self.config.get('Interface', 0) + self.ui.comboBox_Interface.clear() + + net_names = list(self.net_if.keys()) + net_if_stats = psutil.net_if_stats() + + for if_name in net_names: + if not net_if_stats[if_name].isup: + self.net_if.pop(if_name, None) + else: + self.ui.comboBox_Interface.addItem(if_name) + + if interface_idx >= self.ui.comboBox_Interface.count(): + self.ui.comboBox_Interface.setCurrentIndex(0) + else: + self.ui.comboBox_Interface.setCurrentIndex(interface_idx) + + current_interface = self.ui.comboBox_Interface.currentText() + self.config['Interface'] = self.ui.comboBox_Interface.currentIndex() + + for snicaddr in self.net_if[current_interface]: + if snicaddr.family == socket.AF_INET: + ipv4_add = snicaddr.address + break + else: + ipv4_add = '0.0.0.0' + + self.ui.label_LocalIP.setText(ipv4_add) + + self.save_config() + + def on_interface_selection_change(self): + current_interface = self.ui.comboBox_Interface.currentText() + + if current_interface in self.net_if: + for snicaddr in self.net_if[current_interface]: + if snicaddr.family == socket.AF_INET: + ipv4_add = snicaddr.address + break + else: + ipv4_add = '0.0.0.0' + else: + return + + self.ui.label_LocalIP.setText(ipv4_add) + self.config['Interface'] = self.ui.comboBox_Interface.currentIndex() + self.save_config() + + def on_refresh_button_clicked(self): + self.update_network_interfaces() + + # TCP Client + def on_tcp_client_connect_button_clicked(self): + if self.ui.button_TcpClient.text() == 'Connect': + self.ui.button_TcpClient.setEnabled(False) + self.ui.lineEdit_TcpClientTargetIP.setEnabled(False) + self.ui.lineEdit_TcpClientTargetPort.setEnabled(False) + + self.tcp_client_thread = QThread() + self.tcp_client = TCPClient( + self.ui.lineEdit_TcpClientTargetIP.text(), + int(self.ui.lineEdit_TcpClientTargetPort.text())) + + self.tcp_client_thread.started.connect(self.tcp_client.start) + self.tcp_client.status.connect(self.on_tcp_client_status_update) + self.tcp_client.message.connect(self.on_tcp_client_message_ready) + + self.tcp_client.moveToThread(self.tcp_client_thread) + + self.tcp_client_thread.start() + + self.config['TCP_Client_IP'] = self.ui.lineEdit_TcpClientTargetIP.text() + self.config['TCP_Client_Port'] = self.ui.lineEdit_TcpClientTargetPort.text() + self.save_config() + + elif self.ui.button_TcpClient.text() == 'Disconnect': + self.ui.button_TcpClient.setEnabled(False) + self.tcp_client.close() + + def on_tcp_client_status_update(self, status, addr): + if status == TCPClient.STOP: + self.tcp_client.status.disconnect() + self.tcp_client.message.disconnect() + + self.ui.button_TcpClient.setText('Connect') + self.tcp_client_thread.quit() + + self.ui.lineEdit_TcpClientTargetIP.setEnabled(True) + self.ui.lineEdit_TcpClientTargetPort.setEnabled(True) + + self.ui.textBrowser_TcpClientMessage.setEnabled(False) + self.ui.lineEdit_TcpClientSend.setEnabled(False) + self.ui.button_TcpClientSend.setEnabled(False) + self.status_message[0] = '● Idle' + if self.ui.tabWidget.currentIndex() == 0: + self.on_tab_changed(0) + + elif status == TCPClient.CONNECTED: + self.ui.button_TcpClient.setText('Disconnect') + + self.ui.textBrowser_TcpClientMessage.setEnabled(True) + self.ui.lineEdit_TcpClientSend.setEnabled(True) + self.ui.button_TcpClientSend.setEnabled(True) + self.status_message[0] = '● Connected to ' +\ + self.ui.label_LocalIP.text() +\ + ':'+self.ui.lineEdit_TcpClientTargetPort.text() + if self.ui.tabWidget.currentIndex() == 0: + self.on_tab_changed(0) + + self.ui.button_TcpClient.setEnabled(True) + + def on_tcp_client_message_ready(self, source, msg): + self.ui.textBrowser_TcpClientMessage.append( + '

----- ' + + source + + ' -----

') + self.ui.textBrowser_TcpClientMessage.append( + '

' + + msg + + '

') + + def on_tcp_client_message_send(self): + self.tcp_client.send(self.ui.lineEdit_TcpClientSend.text()) + self.ui.textBrowser_TcpClientMessage.append( + '

----- ' + + 'this' + + ' -----

') + self.ui.textBrowser_TcpClientMessage.append( + '

' + + self.ui.lineEdit_TcpClientSend.text() + + '

') + self.ui.lineEdit_TcpClientSend.clear() + + +if __name__ == "__main__": + QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts) + app = QtWidgets.QApplication(sys.argv) + window = MyApp() + + sys.exit(app.exec()) diff --git a/software/pc/mainwindow.ui b/software/pc/mainwindow.ui index 6e6ae16..65325c1 100644 --- a/software/pc/mainwindow.ui +++ b/software/pc/mainwindow.ui @@ -346,8 +346,6 @@ - - - + diff --git a/software/pc/tcpclient.py b/software/pc/tcpclient.py new file mode 100644 index 0000000..1bea888 --- /dev/null +++ b/software/pc/tcpclient.py @@ -0,0 +1,104 @@ +""" + Copyright (C) 2017 - 2021 Zhengyu Peng, https://zpeng.me + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ---------- + + ` ` + -:. -#: + -//:. -###: + -////:. -#####: + -/:.://:. -###++##: + .. `://:- -###+. :##: + `:/+####+. :##: + .::::::::/+###. :##: + .////-----+##: `:###: + `-//:. :##: `:###/. + `-//:. :##:`:###/. + `-//:+######/. + `-/+####/. + `+##+. + :##: + :##: + :##: + :##: + :##: + .+: + +""" + +from PySide6.QtCore import QObject, Signal, Slot +import socket + + +class TCPClient(QObject): + status = Signal(int, object) + message = Signal(object, object) + ERROR = -1 + LISTEN = 1 + CONNECTED = 2 + STOP = 3 + + SIG_NORMAL = 0 + SIG_STOP = 1 + SIG_DISCONNECT = 2 + + def __init__(self, ip, port): + QObject.__init__(self) + + self.ip = ip + self.port = port + self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.tcp_socket.settimeout(1) + + self.signal = self.SIG_NORMAL + + @Slot() + def start(self): + try: + self.tcp_socket.connect((self.ip, self.port)) + except OSError as err: + print(err) + # self.status.emit(self.STOP, '') + else: + # print('connected') + self.status.emit(self.CONNECTED, self.ip) + + while True: + if self.signal == self.SIG_NORMAL: + try: + + data = self.tcp_socket.recv(4096) + except socket.timeout as t_out: + pass + else: + if data: + self.message.emit( + self.ip+':'+str(self.port), + data.decode()) + else: + break + elif self.signal == self.SIG_DISCONNECT: + self.signal = self.SIG_NORMAL + self.tcp_socket.close() + break + finally: + self.status.emit(self.STOP, '') + + def send(self, msg): + self.tcp_socket.sendall(msg.encode()) + + def close(self): + self.signal = self.SIG_DISCONNECT