Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
21f0d9c
typo
t0mpr1c3 Jun 10, 2020
eb22202
Update knitting_plugin.py
t0mpr1c3 Jun 19, 2020
ab69af7
Create CONTRIBUTING.md (fixes #425)
Mar 26, 2022
98b4980
add IP connection handling
yekomS Jun 29, 2022
e7209d3
add try block in cnf_line()
D1v42 Oct 9, 2022
26b411e
fix crash on connection lost
D1v42 Dec 17, 2022
c79c8b7
Update issue templates
dl1com Jun 29, 2023
0ab3cf0
Update bug_report.md
dl1com Jun 29, 2023
a4b4925
update Copyright for Marcus, remove start cmd
yekomS Jul 9, 2023
3421c34
Merge branch 'AllYarnsAreBeautiful:master' into ComViaIp
yekomS Jul 9, 2023
2dfdb4c
Merge pull request #1 from D1v42/ComViaIp
yekomS Jul 9, 2023
9d92dfd
Update udp_thread.py
yekomS Sep 17, 2023
0ddad61
Update ayab_control.py
yekomS Sep 17, 2023
27c4adb
Update ayab_control.py
yekomS Sep 17, 2023
2395d30
Update udp_thread.py
yekomS Sep 17, 2023
9c197eb
Update README.md
yekomS Sep 19, 2023
441d3cd
Update README.md
yekomS Sep 19, 2023
791b142
Update README.md
yekomS Sep 19, 2023
03566c0
Update README.md
yekomS Sep 19, 2023
2a3ff95
Update README.md
yekomS Sep 26, 2023
2ee3738
Update README.md
yekomS Oct 1, 2023
e6def79
Update README.md
yekomS Oct 1, 2023
a73f58c
Update README.md
yekomS Oct 1, 2023
fcc7202
add hotfix regarding ayab-desktop crash
yekomS Oct 7, 2023
d9fee94
Merge remote-tracking branch 'origin/master' into ComViaIp
yekomS Oct 28, 2023
01f8e91
Merge branch 'ComViaIp' into eKnitter
yekomS Oct 28, 2023
e80ec4b
add eKnitter icon and win build.bat
yekomS Oct 28, 2023
9348c83
init ayab line block after new start
yekomS Oct 28, 2023
73a27cf
update build.bat
yekomS Oct 28, 2023
a76c279
update windows build script, dll etc.
yekomS Oct 29, 2023
1aa6794
prepare Release 95.2
yekomS Nov 1, 2023
b6da180
flash eknitter via USB fw and fs
yekomS Nov 1, 2023
20bff05
prepare 95.2-Preview
yekomS Nov 19, 2023
5b6a7f1
prepare R95.2: flash eknitter via USB, change logo etc
yekomS Feb 25, 2024
9662cf2
Merge branch 'eKnitter' into eknitter-dev
yekomS Feb 25, 2024
a092ada
update for macos
yekomS Feb 25, 2024
81dc059
Merge branch 'eknitter-dev' into eKnitter
yekomS Feb 25, 2024
44344d6
Revert "Merge branch 'eknitter-dev' into eKnitter"
yekomS Feb 25, 2024
13a1416
update for macos
yekomS Feb 25, 2024
9458342
Update firmware_flash.py for macos
yekomS Feb 25, 2024
1dd8685
update Readme for macOs, update flash for macOs
Apr 9, 2024
fb59d12
Merge branch 'eKnitter_dev' into eKnitter
yekomS Apr 9, 2024
c488c86
add how to update to macos description
yekomS Apr 28, 2024
39f1d90
add eknitter ip handling
yekomS Sep 22, 2024
0a4b204
add eknitter ip handling
yekomS Sep 22, 2024
d1f1935
Merge branch 'AllYarnsAreBeautiful:main' into eknitter-ayab-desktop-main
yekomS Mar 23, 2025
7f2bba4
Merge branch 'eknitter-ayab-desktop-main' of https://github.com/yekom…
yekomS Mar 29, 2025
b90bc31
merge 0.95.0
D1v42 Mar 29, 2025
0a63bfe
Merge remote-tracking branch 'origin/eKnitter' into add-firmware-ekni…
D1v42 Mar 29, 2025
5148bc5
fix merge bug
D1v42 Mar 29, 2025
894c1e8
add firmware info and cmd
D1v42 Mar 29, 2025
9d2bd6b
fix mypy errors
D1v42 Mar 29, 2025
ec5636b
add eKnitter icon
D1v42 Mar 30, 2025
1a8ee29
Merge branch 'AllYarnsAreBeautiful:main' into eknitter-ayab-desktop-main
yekomS Apr 13, 2025
c190c10
Merge remote-tracking branch 'origin/add-firmware-eknitter' into ekni…
yekomS Apr 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the empty link in the issue reporting instructions.

The link for "opening a new issue" is currently empty, which breaks the functionality of the document and has been flagged by static analysis.

-We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy!
+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!
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy!
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!
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

28-28: No empty links
null

(MD042, no-empty-links)


## Write bug reports with detail, background, and sample code

Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ types-pyserial==3.5.0
types-mock
types-Pillow==10
mypy==1.9.0
esptool==3.3.3
3 changes: 3 additions & 0 deletions setup-environment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/main/NSIS/Installer.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Check environment variable syntax in NSIS script

The current syntax $%HOMEDRIVE%\%{app_name}-v%{version} mixes two different ways of accessing environment variables in NSIS. NSIS typically uses either $HOMEDRIVE or %HOMEDRIVE% syntax, but not a combination of both.

Consider using one of these alternatives:

-          StrCpy $InstDir "$%HOMEDRIVE%\%{app_name}-v%{version}"
+          StrCpy $InstDir "$HOMEDRIVE\%{app_name}-v%{version}"

Or:

-          StrCpy $InstDir "$%HOMEDRIVE%\%{app_name}-v%{version}"
+          StrCpy $InstDir "%HOMEDRIVE%\%{app_name}-v%{version}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
StrCpy $InstDir "$%HOMEDRIVE%\%{app_name}-v%{version}"
StrCpy $InstDir "$HOMEDRIVE\%{app_name}-v%{version}"

${Else}
StrCpy $InstDir "$LOCALAPPDATA\%{app_name}"
${EndIf}
Expand Down
Binary file modified src/main/NSIS/ShellExecAsUser.dll
Binary file not shown.
Binary file modified src/main/icons/Icon.ico
Binary file not shown.
2 changes: 1 addition & 1 deletion src/main/python/main/ayab/about.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
+ " "
Expand Down
4 changes: 2 additions & 2 deletions src/main/python/main/ayab/about_gui.ui
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>About AYAB</string>
<string>About AYAB for eKnitter</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
Expand Down Expand Up @@ -41,7 +41,7 @@
<item>
<widget class="QLabel" name="title_label">
<property name="text">
<string>All Yarns Are Beautiful</string>
<string>All Yarns Are Beautiful for eKnitter</string>
</property>
</widget>
</item>
Expand Down
10 changes: 9 additions & 1 deletion src/main/python/main/ayab/ayab.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -131,6 +138,7 @@ def __set_prefs(self) -> None:

def __quit(self) -> None:
logging.debug("Quitting")
self.engine.close()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

🛠️ Refactor suggestion

Based on the search results, I can now generate the final response:

Add error handling to engine.close() and avoid duplicate cleanup

The verification reveals two locations where engine.close() is called:

  • In closeEvent() handler (line 142)
  • In __quit() method (the line under review)

This creates a potential issue where:

  • The cleanup is attempted twice
  • Neither location has error handling
  • The closeEvent handler is the Qt standard way to handle cleanup

Recommendations:

  • Remove the redundant engine.close() from __quit()
  • Add error handling to closeEvent()
  • Let Qt's event system handle the cleanup sequence
def closeEvent(self, event):
    print("close")
-    self.engine.close()
+    try:
+        self.engine.close()
+    except Exception as e:
+        logging.error(f"Error during cleanup: {e}")
    event.accept()

def __quit(self) -> None:
    logging.debug("Quitting")
-    self.engine.close()  # Remove duplicate cleanup
    instance = QCoreApplication.instance()
    if instance is not None:
        instance.quit()
    sys.exit()
🔗 Analysis chain

Verify cleanup sequence and add error handling

The cleanup sequence in __quit might need improvement:

  1. Add error handling around engine.close()
  2. Consider potential race conditions with closeEvent
  3. Ensure cleanup completes before exit

Let's verify the cleanup sequence:

Consider this implementation:

    def __quit(self) -> None:
        logging.debug("Quitting")
-        self.engine.close()
+        try:
+            self.engine.close()
+        except Exception as e:
+            logging.error(f"Error during cleanup: {e}")
+
         instance = QCoreApplication.instance()
         if instance is not None:
             instance.quit()
-        sys.exit()
+        # Allow event loop one last iteration to complete cleanup
+        QCoreApplication.processEvents()
+        sys.exit(0)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other cleanup handlers that might conflict
rg -A 5 "def (close|cleanup|shutdown|dispose|exit|quit)"

# Search for engine.close() usage
ast-grep --pattern 'engine.close()'

Length of output: 3821

instance = QCoreApplication.instance()
if instance is not None:
instance.quit()
Expand Down
200 changes: 200 additions & 0 deletions src/main/python/main/ayab/engine/communication_ip.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
#
# 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 *
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid wildcard imports for clarity and maintainability.

Using from .communication import * can lead to namespace pollution and makes it harder to track dependencies. It's better to import only the necessary entities explicitly.

Apply this diff to replace the wildcard import with explicit imports:

-from .communication import *
+from .communication import Communication, Token, Machine

This change ensures that Communication, Token, and Machine are explicitly imported and helps static analysis tools function correctly.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff

23-23: from .communication import * used; unable to detect undefined names

(F403)

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))


9 changes: 8 additions & 1 deletion src/main/python/main/ayab/engine/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/main/python/main/ayab/engine/eKnitter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/main/python/main/ayab/engine/eKnitter_rc.qrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>eKnitter.png</file>
</qresource>
</RCC>
5 changes: 5 additions & 0 deletions src/main/python/main/ayab/engine/eKnitter_reversed_rc.qrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>eKnitter-reversed.png</file>
</qresource>
</RCC>
Loading