diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 61331646..9036dc91 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -25,7 +25,7 @@ Pull requests are the best way to propose changes to the codebase (we use [Githu
In short, when you submit code changes, your submissions are understood to be under the same [GPL-3.0 License](https://choosealicense.com/licenses/gpl-3.0/) that covers the project. Feel free to contact the maintainers if that's a concern.
## Report bugs using Github's [issues](https://github.com/AllYarnsAreBeautiful/ayab-desktop/issues)
-We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/AllYarnsAreBeautiful/ayab-desktop/issues/new); it's that easy!
+We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy!
## Write bug reports with detail, background, and sample code
diff --git a/README.md b/README.md
index 7f22f8a5..ea30a3a1 100644
--- a/README.md
+++ b/README.md
@@ -86,11 +86,19 @@ macOS
You can install Git using Homebrew:
```bash
brew install git
+ brew install esptool
```
You will also need the Xcode command line tools:
```bash
xcode-select --install
```
+
+get ayab-desktop for eKnitter from github:
+
+```bash
+ git clone -b eKnitter https://github.com/yekomS/ayab-desktop ayab-desktop-eknitter
+```
+
Install python from [the official universal2 installer](https://www.python.org/ftp/python/3.11.8/python-3.11.8-macos11.pkg). (Conda does not produce universal binaries)
If you encounter the pip `SSL:TLSV1_ALERT_PROTOCOL_VERSION` problem:
diff --git a/requirements.txt b/requirements.txt
index 6f1cbb7c..2c2b651a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,3 +6,4 @@ types-pyserial==3.5.0
types-mock
types-Pillow==10
mypy==1.9.0
+esptool==3.3.3
diff --git a/setup-environment.ps1 b/setup-environment.ps1
index 713dd06c..ac7cda5f 100755
--- a/setup-environment.ps1
+++ b/setup-environment.ps1
@@ -35,6 +35,9 @@ pyside6-rcc src/main/python/main/ayab/ayab_logo_rc.qrc -o src/main/python/main/a
pyside6-rcc src/main/python/main/ayab/engine/lowercase_e_rc.qrc -o src/main/python/main/ayab/engine/lowercase_e_rc.py
pyside6-rcc src/main/python/main/ayab/engine/lowercase_e_reversed_rc.qrc -o src/main/python/main/ayab/engine/lowercase_e_reversed_rc.py
+pyside6-rcc src/main/python/main/ayab/engine/eKnitter_rc.qrc -o src/main/python/main/ayab/engine/eKnitter_rc.py
+pyside6-rcc src/main/python/main/ayab/engine/eKnitter_reversed_rc.qrc -o src/main/python/main/ayab/engine/eKnitter_reversed_rc.py
+
# generate translation files
cd src/main/resources/base/ayab/translations/
perl ayab_trans.pl
diff --git a/src/main/NSIS/Installer.nsi b/src/main/NSIS/Installer.nsi
index d2d3f2dc..4cf6e4b1 100644
--- a/src/main/NSIS/Installer.nsi
+++ b/src/main/NSIS/Installer.nsi
@@ -16,7 +16,7 @@ Function .onInit
;Do not use InstallDir at all so we can detect empty $InstDir!
${If} $InstDir == "" ; /D not used
${If} $MultiUser.InstallMode == "AllUsers"
- StrCpy $InstDir "C:\%{app_name}-v%{version}"
+ StrCpy $InstDir "$%HOMEDRIVE%\%{app_name}-v%{version}"
${Else}
StrCpy $InstDir "$LOCALAPPDATA\%{app_name}"
${EndIf}
diff --git a/src/main/NSIS/ShellExecAsUser.dll b/src/main/NSIS/ShellExecAsUser.dll
index 01a8c85a..2bf1d0c3 100644
Binary files a/src/main/NSIS/ShellExecAsUser.dll and b/src/main/NSIS/ShellExecAsUser.dll differ
diff --git a/src/main/icons/Icon.ico b/src/main/icons/Icon.ico
index f6cfc6a1..d4de60be 100644
Binary files a/src/main/icons/Icon.ico and b/src/main/icons/Icon.ico differ
diff --git a/src/main/python/main/ayab/about.py b/src/main/python/main/ayab/about.py
index c7522a44..1273b1e8 100644
--- a/src/main/python/main/ayab/about.py
+++ b/src/main/python/main/ayab/about.py
@@ -38,7 +38,7 @@ def __init__(self, parent: GuiMain):
super().__init__()
self.__version = utils.package_version(parent.app_context)
self.__ui = Ui_AboutForm()
- self.__ui.setupUi(self)
+ self.__ui.setupUi(self) # type: ignore
self.__ui.title_label.setText(
QCoreApplication.translate("MainWindow", "All Yarns Are Beautiful")
+ " "
diff --git a/src/main/python/main/ayab/about_gui.ui b/src/main/python/main/ayab/about_gui.ui
index 1744d84f..dc434852 100644
--- a/src/main/python/main/ayab/about_gui.ui
+++ b/src/main/python/main/ayab/about_gui.ui
@@ -11,7 +11,7 @@
- About AYAB
+ About AYAB for eKnitter
false
@@ -41,7 +41,7 @@
-
- All Yarns Are Beautiful
+ All Yarns Are Beautiful for eKnitter
diff --git a/src/main/python/main/ayab/ayab.py b/src/main/python/main/ayab/ayab.py
index 9fd424ee..67e76233 100644
--- a/src/main/python/main/ayab/ayab.py
+++ b/src/main/python/main/ayab/ayab.py
@@ -17,6 +17,7 @@
# Copyright 2014 Sebastian Oliva, Christian Obersteiner,
# Andreas Müller, Christian Gerbrandt
# https://github.com/AllYarnsAreBeautiful/ayab-desktop
+# Copyright 2024 Marcus Hoose (eKnitter.com)
"""Provides a graphical interface for users to operate AYAB."""
from __future__ import annotations
@@ -25,6 +26,7 @@
from PySide6.QtWidgets import QMainWindow
from PySide6.QtCore import QCoreApplication
+from PySide6.QtCore import QEvent
from .main_gui import Ui_MainWindow
from .gui_fsm import gui_fsm
@@ -58,6 +60,11 @@ class GuiMain(QMainWindow):
components from `menu_gui.ui`.
"""
+ def closeEvent(self, event: QEvent) -> None:
+ print("close")
+ self.engine.close()
+ event.accept()
+
def __init__(self, app_context: AppContext):
super().__init__()
self.app_context = app_context
@@ -68,7 +75,7 @@ def __init__(self, app_context: AppContext):
# create UI
self.ui = Ui_MainWindow()
- self.ui.setupUi(self)
+ self.ui.setupUi(self) # type: ignore
# add modular components
self.menu = Menu(self)
@@ -131,6 +138,7 @@ def __set_prefs(self) -> None:
def __quit(self) -> None:
logging.debug("Quitting")
+ self.engine.close()
instance = QCoreApplication.instance()
if instance is not None:
instance.quit()
diff --git a/src/main/python/main/ayab/engine/communication_ip.py b/src/main/python/main/ayab/engine/communication_ip.py
new file mode 100644
index 00000000..87b353dc
--- /dev/null
+++ b/src/main/python/main/ayab/engine/communication_ip.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+# This file is part of AYAB.
+#
+# AYAB 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.
+#
+# AYAB 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 AYAB. If not, see .
+#
+# Copyright 2024 Marcus Hoose (eKnitter.com)
+"""Handles the IP communication protocol.
+
+This module handles IP communication, currently works in a synchronous way.
+"""
+
+from .communication import *
+from ..machine import *
+
+import socket
+
+import logging
+import pprint
+from time import sleep
+
+# Port for TCP
+remotePort = 12346
+
+class CommunicationIP(Communication):
+ def __init__(self) -> None:
+ logging.basicConfig(level=logging.DEBUG)
+ self.logger = logging.getLogger(type(self).__name__)
+ self.__tarAddressPort: tuple[str | None, int] = ("255.255.255.255", 12345)
+ self.__sockTCP: None | socket.socket = None
+ # socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
+ self.rx_msg_list = list()
+ self.version = 6
+
+ def __del__(self) -> None:
+ self.close_socket()
+
+ def is_open(self) -> bool:
+ if self.__sockTCP is not None:
+ return True
+ else:
+ return False
+
+ def open_serial(self, portname: str | None = None) -> bool:
+ print("open: " , portname)
+ return self.open_tcp(portname)
+
+ def close_serial(self) -> None:
+ return None
+
+ def open_tcp(self, pPortname: str | None = None) -> bool:
+ try:
+ self.__portname = pPortname
+ self.__tarAddressPort = (self.__portname, remotePort)
+ self.__sockTCP = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
+ self.__sockTCP.settimeout(10.0)
+ self.__sockTCP.connect(self.__tarAddressPort)
+ self.__sockTCP.settimeout(0.0)
+ self.__sockTCP.setblocking(False)
+ self.logger.info("Open TCP Socket successful")
+ return True
+ except:
+ self.logger.info("Open TCP Socket faild")
+ return False
+
+ def close_socket(self) -> None:
+ if self.__sockTCP is not None:
+ try:
+ self.__sockTCP.close()
+ del self.__sockTCP
+ self.logger.info("Closing TCP Socket successful.")
+ except:
+ self.logger.warning("Closing TCP Socket failed. (mem Leak?)")
+ self.__sockTCP = None
+
+ def send(self, data: bytearray) -> None:
+ if self.__sockTCP is not None:
+ try:
+ self.__sockTCP.send(bytes(data))
+ # self.logger.info("SEND b'"+data+"'")
+ sleep(0.5)
+ except Exception as e:
+ if hasattr(e, 'message'):
+ self.logger.exception(e.message)
+ else:
+ self.logger.exception("Connection...Error")
+ self.close_socket()
+
+ # NB this method must be the same for all API versions
+ def req_info(self) -> None:
+ self.send(bytearray([Token.reqInfo.value,self.version]))
+
+ def req_test_API6(self) -> None:
+ self.send(bytearray([Token.reqTest.value]))
+
+ def req_start_API6(self, start_needle: int, stop_needle: int,
+ continuous_reporting: bool, disable_hardware_beep: bool) -> None:
+ """Send a start message to the device."""
+ data = bytearray()
+ data.append(Token.reqStart.value)
+ data.append(start_needle)
+ data.append(stop_needle)
+ data.append(
+ 1 * continuous_reporting +
+ 2 * (not disable_hardware_beep))
+ hash = 0
+ hash = add_crc(hash, data)
+ data.append(hash)
+ self.send(data)
+
+ def req_init_API6(self, machine: Machine) -> None:
+ """Send a start message to the device."""
+ data = bytearray()
+ data.append(Token.reqInit.value)
+ data.append(machine.value)
+ hash = 0
+ hash = add_crc(hash, data)
+ data.append(hash)
+ self.send(data)
+
+ def cnf_line_API6(self, line_number: int , color: int, flags: int, line_data: bytes) -> None:
+ """Send a line of data via the serial port.
+
+ Send a line of data to the serial port. All arguments are mandatory.
+ The data sent here is parsed by the Arduino controller which sets the
+ knitting needles accordingly.
+
+ Args:
+ line_number (int): The line number to be sent.
+ color (int): The yarn color to be sent.
+ flags (int): The flags sent to the controller.
+ line_data (bytes): The bytearray to be sent to needles.
+ """
+ data = bytearray()
+ data.append(Token.cnfLine.value)
+ data.append(line_number)
+ data.append(color)
+ data.append(flags)
+ data.extend(line_data)
+ hash = 0
+ hash = add_crc(hash, data)
+ data.append(hash)
+ self.send(data)
+
+ def update_API6(self) -> tuple[bytes | None, Token, int]:
+ """Read data from serial and parse as SLIP packet."""
+ return self.parse_API6(self.read_API6())
+
+ def parse_API6(self, msg: bytes | None) -> tuple[bytes | None, Token, int]:
+ if msg is None:
+ return None, Token.none, 0
+ # else
+ for t in list(Token):
+ if msg[0] == t.value:
+ return msg, t, msg[1]
+ # fallthrough
+ self.logger.debug("unknown message: ") # drop crlf
+ pp = pprint.PrettyPrinter(indent=4)
+ pp.pprint(msg[1: -1].decode())
+ return msg, Token.unknown, 0
+
+ def read_API6(self) -> bytes | None:
+ """Read data from serial as SLIP packet."""
+ if self.__sockTCP is not None:
+ try:
+ data = self.__sockTCP.recv(1024)
+ except BlockingIOError:
+ data = bytes()
+ except Exception as e:
+ if hasattr(e, 'message'):
+ self.logger.exception(e.message)
+ else:
+ self.logger.exception("Connection...Error")
+ self.close_socket()
+ self.open_tcp(self.__portname)
+ data = bytes()
+
+ if len(data) > 0:
+ self.rx_msg_list.append(data)
+ if len(self.rx_msg_list) > 0:
+ return self.rx_msg_list.pop(0) # FIFO
+ # else
+ return None
+
+ def write_API6(self, cmd: bytes | bytearray) -> None:
+ # SLIP protocol, no CRC8
+ if self.__ser:
+ self.__ser.write(bytes(cmd))
+
+
diff --git a/src/main/python/main/ayab/engine/control.py b/src/main/python/main/ayab/engine/control.py
index 82d33760..d42cb026 100644
--- a/src/main/python/main/ayab/engine/control.py
+++ b/src/main/python/main/ayab/engine/control.py
@@ -17,6 +17,7 @@
# Copyright 2013-2020 Sebastian Oliva, Christian Obersteiner,
# Andreas Müller, Christian Gerbrandt
# https://github.com/AllYarnsAreBeautiful/ayab-desktop
+# Copyright 2024 Marcus Hoose (eKnitter.com)
from __future__ import annotations
import logging
@@ -177,7 +178,13 @@ def __log_cnfInfo(self, msg: bytes) -> None:
api = msg[1]
log = "API v" + str(api)
if api >= 5:
- log += ", FW v" + str(msg[2]) + "." + str(msg[3]) + "." + str(msg[4])
+ if len(msg) < 5:
+ if len(msg) < 4:
+ log += ", FW v?"
+ else:
+ log += ", FW v" + str(msg[2]) + "." + str(msg[3])
+ else:
+ log += ", FW v" + str(msg[2]) + "." + str(msg[3]) + "." + str(msg[4])
suffix = msg[5:21]
suffix_null_index = suffix.find(0)
suffix_str = suffix[: suffix_null_index + 1].decode()
diff --git a/src/main/python/main/ayab/engine/eKnitter-reversed.png b/src/main/python/main/ayab/engine/eKnitter-reversed.png
new file mode 100644
index 00000000..6c2b5376
Binary files /dev/null and b/src/main/python/main/ayab/engine/eKnitter-reversed.png differ
diff --git a/src/main/python/main/ayab/engine/eKnitter.png b/src/main/python/main/ayab/engine/eKnitter.png
new file mode 100644
index 00000000..6d6557ed
Binary files /dev/null and b/src/main/python/main/ayab/engine/eKnitter.png differ
diff --git a/src/main/python/main/ayab/engine/eKnitter_rc.qrc b/src/main/python/main/ayab/engine/eKnitter_rc.qrc
new file mode 100644
index 00000000..b86062d9
--- /dev/null
+++ b/src/main/python/main/ayab/engine/eKnitter_rc.qrc
@@ -0,0 +1,5 @@
+
+
+ eKnitter.png
+
+
\ No newline at end of file
diff --git a/src/main/python/main/ayab/engine/eKnitter_reversed_rc.qrc b/src/main/python/main/ayab/engine/eKnitter_reversed_rc.qrc
new file mode 100644
index 00000000..732873e3
--- /dev/null
+++ b/src/main/python/main/ayab/engine/eKnitter_reversed_rc.qrc
@@ -0,0 +1,5 @@
+
+
+ eKnitter-reversed.png
+
+
\ No newline at end of file
diff --git a/src/main/python/main/ayab/engine/engine.py b/src/main/python/main/ayab/engine/engine.py
index 34eec425..cb10dab4 100644
--- a/src/main/python/main/ayab/engine/engine.py
+++ b/src/main/python/main/ayab/engine/engine.py
@@ -17,6 +17,7 @@
# Copyright 2013-2020 Sebastian Oliva, Christian Obersteiner,
# Andreas Müller, Christian Gerbrandt
# https://github.com/AllYarnsAreBeautiful/ayab-desktop
+# Copyright 2024 Marcus Hoose (eKnitter.com)
from __future__ import annotations
import logging
@@ -33,6 +34,7 @@
from .status import Status, StatusTab
from .output import FeedbackHandler
from .dock_gui import Ui_Dock
+from .udp_monitor import UDPMonitor
from typing import TYPE_CHECKING, Literal, Optional, cast
from ..signal_sender import SignalSender
@@ -40,7 +42,7 @@
from ..ayab import GuiMain
from .control import Control
-
+udpMonitor = UDPMonitor()
class Engine(SignalSender, QDockWidget):
"""
Top-level class for the slave thread that communicates with the shield.
@@ -56,8 +58,10 @@ class Engine(SignalSender, QDockWidget):
def __init__(self, parent: GuiMain):
# set up UI
super().__init__(parent.signal_receiver)
+ logging.info("StartUDPMonitor")
+ udpMonitor.start()
self.ui = Ui_Dock()
- self.ui.setupUi(self)
+ self.ui.setupUi(self) # type: ignore
self.config: OptionsTab = OptionsTab(parent)
self.config.portname = self.__read_portname()
self.reload_settings()
@@ -69,6 +73,12 @@ def __init__(self, parent: GuiMain):
self.control = Control(parent, self)
self.__feedback = FeedbackHandler(parent)
self.__logger = logging.getLogger(type(self).__name__)
+ self.setWindowTitle("Machine: " + Machine(self.config.machine).name)
+
+ def close(self) -> bool:
+ logging.info("StopUDPMonitor")
+ udpMonitor.stop()
+ return True
def __del__(self) -> None:
self.control.stop()
@@ -110,6 +120,11 @@ def __activate_ui(self) -> None:
def __populate_ports(self, port_list: Optional[list[str]] = None) -> None:
combo_box = self.ui.serial_port_dropdown
utils.populate_ports(combo_box, port_list)
+ ip_list = udpMonitor.getIPlist()
+ print(ip_list)
+ for item in ip_list:
+ # TODO: should display the info of the device.
+ combo_box.addItem(item)
# Add Simulation item to indicate operation without machine
combo_box.addItem(QCoreApplication.translate("KnitEngine", "Simulation"))
@@ -174,7 +189,8 @@ def run(self, operation: Operation) -> None:
output = self.control.operate(operation)
if output != self.control.notification:
self.__feedback.handle(output)
- self.control.notification = output
+ if output.name != "CONNECTING_TO_MACHINE":
+ self.control.notification = output
if operation == Operation.KNIT:
self.__handle_status()
if self.__canceled or self.control.state == State.FINISHED:
diff --git a/src/main/python/main/ayab/engine/engine_fsm.py b/src/main/python/main/ayab/engine/engine_fsm.py
index be81c76d..2e61658d 100644
--- a/src/main/python/main/ayab/engine/engine_fsm.py
+++ b/src/main/python/main/ayab/engine/engine_fsm.py
@@ -17,6 +17,7 @@
# Copyright 2013-2020 Sebastian Oliva, Christian Obersteiner,
# Andreas Müller, Christian Gerbrandt
# https://github.com/AllYarnsAreBeautiful/ayab-desktop
+# Copyright 2024 Marcus Hoose (eKnitter.com)
from __future__ import annotations
@@ -27,6 +28,7 @@
from .communication import Communication, Token
from .communication_mock import CommunicationMock
+from .communication_ip import CommunicationIP
from .hw_test_communication_mock import HardwareTestCommunicationMock
from .output import Output
from typing import TYPE_CHECKING
@@ -88,6 +90,8 @@ def _API6_connect(control: Control, operation: Operation) -> Output:
control.com = CommunicationMock()
else:
control.com = HardwareTestCommunicationMock() # type: ignore
+ elif "." in control.portname:
+ control.com = CommunicationIP()
else:
control.com = Communication()
if not control.com.open_serial(control.portname):
diff --git a/src/main/python/main/ayab/engine/options.py b/src/main/python/main/ayab/engine/options.py
index 594ec4c7..331f39e2 100644
--- a/src/main/python/main/ayab/engine/options.py
+++ b/src/main/python/main/ayab/engine/options.py
@@ -30,8 +30,8 @@
from .options_gui import Ui_Options
from .mode import Mode
from ..machine import Machine
-from . import lowercase_e_rc
-from . import lowercase_e_reversed_rc
+from . import eKnitter_rc
+from . import eKnitter_reversed_rc
from typing import TYPE_CHECKING, Literal, TypedDict
if TYPE_CHECKING:
@@ -65,8 +65,8 @@ class OptionsTab(SignalSender, QWidget):
@date June 2020
"""
- lowercase_e_rc
- lowercase_e_reversed_rc
+ eKnitter_rc
+ eKnitter_reversed_rc
alignment: Alignment
auto_mirror: bool
continuous_reporting: bool
@@ -89,7 +89,7 @@ def __init__(self, parent: GuiMain):
# self.__reset()
def __setup_ui(self) -> None:
- self.ui.setupUi(self)
+ self.ui.setupUi(self) # type: ignore
# Combo boxes
Mode.add_items(self.ui.knitting_mode_box)
@@ -126,10 +126,10 @@ def update_needles(self) -> None:
def __auto_mirror_changed(self) -> None:
image_reversed = self.ui.auto_mirror_checkbox.isChecked()
if image_reversed:
- self.ui.auto_mirror_icon.setPixmap(QPixmap(":/garamond-lowercase-e.png"))
+ self.ui.auto_mirror_icon.setPixmap(QPixmap(":/eKnitter.png"))
else:
self.ui.auto_mirror_icon.setPixmap(
- QPixmap(":/garamond-lowercase-e-reversed.png")
+ QPixmap(":/eKnitter-reversed.png")
)
self.emit_image_reverser(image_reversed)
@@ -180,11 +180,11 @@ def refresh(self) -> None:
self.ui.alignment_combo_box.setCurrentIndex(self.alignment.value)
if self.auto_mirror:
self.ui.auto_mirror_checkbox.setCheckState(Qt.CheckState.Checked)
- self.ui.auto_mirror_icon.setPixmap(QPixmap(":/garamond-lowercase-e.png"))
+ self.ui.auto_mirror_icon.setPixmap(QPixmap(":/eKnitter.png"))
else:
self.ui.auto_mirror_checkbox.setCheckState(Qt.CheckState.Unchecked)
self.ui.auto_mirror_icon.setPixmap(
- QPixmap(":/garamond-lowercase-e-reversed.png")
+ QPixmap(":/eKnitter-reversed.png")
)
# self.ui.continuous_reporting_checkbox
self.emit_image_reverser(self.auto_mirror)
diff --git a/src/main/python/main/ayab/engine/options_gui.ui b/src/main/python/main/ayab/engine/options_gui.ui
index 50e3d723..a24bc07c 100644
--- a/src/main/python/main/ayab/engine/options_gui.ui
+++ b/src/main/python/main/ayab/engine/options_gui.ui
@@ -247,7 +247,7 @@
- 59
+ 200
64
diff --git a/src/main/python/main/ayab/engine/status.py b/src/main/python/main/ayab/engine/status.py
index c9919ab6..9b02644f 100644
--- a/src/main/python/main/ayab/engine/status.py
+++ b/src/main/python/main/ayab/engine/status.py
@@ -224,7 +224,7 @@ class StatusTab(Status, QWidget):
def __init__(self) -> None:
super().__init__()
self.ui = Ui_StatusTab()
- self.ui.setupUi(self)
+ self.ui.setupUi(self) # type: ignore
def refresh(self) -> None:
pass # TODO
diff --git a/src/main/python/main/ayab/engine/udp_monitor.py b/src/main/python/main/ayab/engine/udp_monitor.py
new file mode 100644
index 00000000..20ba211a
--- /dev/null
+++ b/src/main/python/main/ayab/engine/udp_monitor.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+# This file is part of AYAB.
+#
+# AYAB 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.
+#
+# AYAB 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 AYAB. If not, see .
+#
+# Copyright 2024 Marcus Hoose (eKnitter.com)
+
+import threading
+import socket
+from time import sleep
+
+# Port for UDP
+localPort = 12345
+broadcast = ("255.255.255.255", 12345)
+
+queueLock = threading.Lock()
+
+class UDPMonitor(threading.Thread):
+ def __init__(self) -> None:
+ threading.Thread.__init__(self)
+ self.exitFlag = False
+ self.__sockUDP: socket.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
+ self.__sockUDP.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+ self.__sockUDP.settimeout(0.1)
+ self.__sockUDP.setblocking(False)
+ # self.__hostname = socket.gethostname()
+ self.__ip_addresses = socket.gethostbyname_ex('localhost')
+ self.__ip_address = "0.0.0.0"
+ self.addresslist: list[socket._RetAddress] = list()
+
+ def __del__(self) -> None:
+ try:
+ self.stop()
+ except Exception:
+ return
+
+ def run(self) -> None:
+ print("Starting ")
+ self.__sockUDP.bind((self.__ip_address ,localPort))
+
+ Run = True
+ while Run:
+ try:
+ dataadress = self.__sockUDP.recvfrom(250)
+ print("Recive")
+ adress = dataadress[1][0]
+ if not(adress in self.__ip_addresses[2]) and not(adress in self.addresslist):
+ queueLock.acquire(True)
+ self.addresslist.append(adress)
+ queueLock.release()
+ except:
+ sleep(1)
+ queueLock.acquire(True)
+ Run = not self.exitFlag
+ queueLock.release()
+ # print("RUN = ",Run)
+
+ print("Exiting")
+ self.__sockUDP.close()
+ del self.__sockUDP
+ self.__sockUDP = socket.socket()
+ return
+
+ def stop(self) -> None:
+ print("Stoping")
+ queueLock.acquire(True)
+ self.exitFlag = True
+ queueLock.release()
+ sleep(1)
+
+ def getIPlist(self) -> list[str]:
+ print("GetList")
+ result: list[str] = list()
+ if queueLock.acquire(True,0.1):
+ result.extend(self.addresslist)
+ queueLock.release()
+ return result
diff --git a/src/main/python/main/ayab/firmware_flash.py b/src/main/python/main/ayab/firmware_flash.py
index dac4f0f5..d7ef024b 100644
--- a/src/main/python/main/ayab/firmware_flash.py
+++ b/src/main/python/main/ayab/firmware_flash.py
@@ -59,7 +59,7 @@ def __init__(self, parent: GuiMain):
self.__app_context = parent.app_context
self.ui = Ui_Firmware()
- self.ui.setupUi(self)
+ self.ui.setupUi(self) # type: ignore
self.ui.flash_firmware.setEnabled(False)
self.ui.flash_firmware.setDefault(True)
self.load_json()
@@ -155,9 +155,15 @@ def execute_flash_command(self) -> bool:
if firmware.get("version") == firmware_key:
firmware_name = firmware.get("file")
- command = self.generate_command(
- base_dir, os_name, controller_name, firmware_name
- )
+ if "eknitter" in controller_name:
+ command = self.generate_command_eknitter(
+ base_dir, os_name, controller_name, firmware_name
+ )
+ else:
+ command = self.generate_command(
+ base_dir, os_name, controller_name, firmware_name
+ )
+
if command is None:
return False
# else
@@ -176,6 +182,39 @@ def execute_flash_command(self) -> bool:
self.close()
return True
+ def generate_command_eknitter(
+ self, base_dir: str, os_name: str, controller_name: str, firmware_name: str
+ ) -> Optional[str]:
+ if os_name == "Windows":
+ exe_route = self.__app_context.get_resource("ayab/firmware/esp32/win64/esptool.exe")
+ exe_route = "\"" + exe_route + "\""
+ elif os_name == "Linux":
+ exe_route = self.__app_context.get_resource("ayab/firmware/esp32/linux-amd64/esptool")
+ elif os_name == "Darwin": # macOS
+ #exe_route = self.__parent_ui.app_context.get_resource("ayab/firmware/esp32/macos/esptool")
+ exe_route = "esptool.py"
+
+ binary_file = os.path.join(
+ self.__app_context.get_resource("ayab/firmware"), firmware_name
+ )
+
+ if "fw" in firmware_name:
+ exec_command = (
+ f"{exe_route} -port {self.port} --baud 921600 --no-stub write_flash"
+ + f" --flash_size 8MB 0x10000 {binary_file} --force"
+ )
+ elif "fs" in firmware_name:
+ exec_command = (
+ f"{exe_route} -port {self.port} --baud 921600 --no-stub write_flash"
+ + f" --flash_size 8MB 0x670000 {binary_file} --force"
+ )
+ else:
+ self.__logger.debug("error firmware or filesystem")
+ return None
+
+ self.__logger.debug(exec_command)
+ return exec_command
+
def generate_command(
self, base_dir: str, os_name: str, controller_name: str, firmware_name: str
) -> Optional[str]:
diff --git a/src/main/python/main/ayab/firmware_flash_gui.ui b/src/main/python/main/ayab/firmware_flash_gui.ui
index 2034ee37..d6787d69 100644
--- a/src/main/python/main/ayab/firmware_flash_gui.ui
+++ b/src/main/python/main/ayab/firmware_flash_gui.ui
@@ -11,7 +11,7 @@
- Firmware Flashing Utility
+ Update eKnitter
True
diff --git a/src/main/python/main/ayab/main_gui.ui b/src/main/python/main/ayab/main_gui.ui
index a8672e49..21e8042a 100644
--- a/src/main/python/main/ayab/main_gui.ui
+++ b/src/main/python/main/ayab/main_gui.ui
@@ -11,7 +11,7 @@
- All Yarns Are Beautiful
+ All Yarns Are Beautiful for eKnitter
diff --git a/src/main/python/main/ayab/menu.py b/src/main/python/main/ayab/menu.py
index ad2e5977..3f46351e 100644
--- a/src/main/python/main/ayab/menu.py
+++ b/src/main/python/main/ayab/menu.py
@@ -45,7 +45,7 @@ def __init__(self, parent: GuiMain):
self.setNativeMenuBar(False)
self.ui = Ui_MenuBar()
- self.ui.setupUi(self)
+ self.ui.setupUi(self) # type: ignore
self.setup()
def setup(self) -> None:
diff --git a/src/main/python/main/ayab/preferences.py b/src/main/python/main/ayab/preferences.py
index b0e02d65..c5fad52a 100644
--- a/src/main/python/main/ayab/preferences.py
+++ b/src/main/python/main/ayab/preferences.py
@@ -213,7 +213,7 @@ def __init__(self, parent: GuiMain):
# set up preferences dialog
self.__ui = Ui_Prefs()
- self.__ui.setupUi(self)
+ self.__ui.setupUi(self) # type: ignore
self.__form = QFormLayout(self.__ui.prefs_group)
# add form items
diff --git a/src/main/python/main/ayab/scene.py b/src/main/python/main/ayab/scene.py
index 6157a86c..1bb25773 100644
--- a/src/main/python/main/ayab/scene.py
+++ b/src/main/python/main/ayab/scene.py
@@ -89,12 +89,21 @@ def refresh(self) -> None:
machine_width = Machine(self.__prefs.value("machine")).width
# draw "machine"
+ rect_pen = QPen(QColor("black"))
+ rect_pen.setWidthF(0.5)
+
+ rect_height = 7
+
rect_orange = QGraphicsRectItem(
- -machine_width / 2 - 0.5, -5.5, machine_width / 2 + 0.5, 5
- )
- rect_orange.setBrush(QBrush(QColor("orange")))
- rect_green = QGraphicsRectItem(0, -5.5, machine_width / 2 + 0.5, 5)
- rect_green.setBrush(QBrush(QColor("green")))
+ -machine_width / 2 - 0.5, -0.5-rect_height,
+ machine_width / 2 + 0.5, rect_height
+ )
+ rect_orange.setBrush(QBrush(QColor("#C96939")))
+ rect_orange.setPen(rect_pen)
+
+ rect_green = QGraphicsRectItem(0, -0.5-rect_height, machine_width / 2 + 0.5, rect_height)
+ rect_green.setBrush(QBrush(QColor("#8A9A5B")))
+ rect_green.setPen(rect_pen)
qscene.addItem(rect_orange)
qscene.addItem(rect_green)
@@ -127,22 +136,22 @@ def refresh(self) -> None:
pattern.setPos(pos, 0)
# draw limiting lines (start/stop needle)
- qscene.addItem(
- QGraphicsRectItem(
+ rect_start = QGraphicsRectItem(
self.__start_needle - machine_width / 2 - 0.5,
-5.5,
0,
pixmap.height() + 5.5,
)
- )
- qscene.addItem(
- QGraphicsRectItem(
+ rect_start.setPen(rect_pen)
+ qscene.addItem(rect_start)
+ rect_stop = QGraphicsRectItem(
self.__stop_needle - machine_width / 2 + 1.5,
-5.5,
0,
pixmap.height() + 5.5,
)
- )
+ rect_stop.setPen(rect_pen)
+ qscene.addItem(rect_stop)
# Draw knitting progress
qscene.addItem(
diff --git a/src/main/python/main/ayab/thread.py b/src/main/python/main/ayab/thread.py
index df7b275d..9b951547 100644
--- a/src/main/python/main/ayab/thread.py
+++ b/src/main/python/main/ayab/thread.py
@@ -17,6 +17,7 @@
# Copyright 2014 Sebastian Oliva, Christian Obersteiner,
# Andreas Müller, Christian Gerbrandt
# https://github.com/AllYarnsAreBeautiful/ayab-desktop
+# Copyright 2024 Marcus Hoose (eKnitter.com)
from __future__ import annotations
from PySide6.QtCore import QThread
@@ -33,7 +34,10 @@ def __init__(self, function: Callable[..., Any], *args: Any, **kwargs: Any):
self.kwargs = kwargs
def __del__(self) -> None:
- self.wait()
+ try:
+ self.wait()
+ except Exception:
+ return
def run(self) -> None:
try:
diff --git a/src/main/python/main/ayab/transforms.py b/src/main/python/main/ayab/transforms.py
index ecb787c3..54f7c9ea 100644
--- a/src/main/python/main/ayab/transforms.py
+++ b/src/main/python/main/ayab/transforms.py
@@ -158,7 +158,7 @@ class MirrorDialog(QDialog):
def __init__(self, parent: Mirrors):
super().__init__() # FIXME set the parent widget as GuiMain
self.__ui = Ui_Mirrors()
- self.__ui.setupUi(self)
+ self.__ui.setupUi(self) # type: ignore
self.__ui.check0.toggled.connect(lambda: parent.toggled(0))
self.__ui.check1.toggled.connect(lambda: parent.toggled(1))
self.__ui.check2.toggled.connect(lambda: parent.toggled(2))
diff --git a/src/main/resources/base/ayab/firmware/eknitter/Place-eKnitter-Firmware-here.txt b/src/main/resources/base/ayab/firmware/eknitter/Place-eKnitter-Firmware-here.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/src/main/resources/base/ayab/firmware/esp32/linux-amd64/esptool b/src/main/resources/base/ayab/firmware/esp32/linux-amd64/esptool
new file mode 100644
index 00000000..b776a9d4
Binary files /dev/null and b/src/main/resources/base/ayab/firmware/esp32/linux-amd64/esptool differ
diff --git a/src/main/resources/base/ayab/firmware/esp32/macos/esptool b/src/main/resources/base/ayab/firmware/esp32/macos/esptool
new file mode 100644
index 00000000..721a7ac8
Binary files /dev/null and b/src/main/resources/base/ayab/firmware/esp32/macos/esptool differ
diff --git a/src/main/resources/base/ayab/firmware/esp32/win64/esptool.exe b/src/main/resources/base/ayab/firmware/esp32/win64/esptool.exe
new file mode 100644
index 00000000..5fbf1c6b
Binary files /dev/null and b/src/main/resources/base/ayab/firmware/esp32/win64/esptool.exe differ
diff --git a/src/main/resources/base/ayab/firmware/firmware.json b/src/main/resources/base/ayab/firmware/firmware.json
index 78de3a09..87403683 100644
--- a/src/main/resources/base/ayab/firmware/firmware.json
+++ b/src/main/resources/base/ayab/firmware/firmware.json
@@ -6,6 +6,18 @@
"version":"1.0.0",
"file":"ayab_monolithic_uno.hex"
}
+ ],
+ "eknitter":[
+ {
+ "url":"/",
+ "version":"Firmware",
+ "file":"eKnitter_fw.bin"
+ },
+ {
+ "url":"/",
+ "version":"Filesystem",
+ "file":"eKnitter_fs.bin"
+ }
]
}
}
diff --git a/windows-build/build.bat b/windows-build/build.bat
new file mode 100644
index 00000000..15463e69
--- /dev/null
+++ b/windows-build/build.bat
@@ -0,0 +1,7 @@
+set VERSION=%1
+
+python -m fbs freeze
+
+python -m fbs installer
+
+xcopy /Y .\target\AYABSetup.exe .\target\AYAB-eKnitter-v%VERSION%-Setup.exe
\ No newline at end of file