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