Skip to content

Commit

Permalink
Add test button to GUI
Browse files Browse the repository at this point in the history
  • Loading branch information
t0mpr1c3 committed Aug 6, 2020
1 parent 5488b0e commit b3ff08e
Show file tree
Hide file tree
Showing 18 changed files with 320 additions and 261 deletions.
25 changes: 21 additions & 4 deletions src/main/python/ayab/ayab.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
from .progressbar import ProgressBar
from .about import About
from .knitprogress import KnitProgress
from .engine import KnitEngine
from .engine import Engine
from .engine.state import Operation
from .engine.options import Alignment
from .machine import Machine

Expand Down Expand Up @@ -73,11 +74,12 @@ def __init__(self, app_context):
self.about = About(self)
self.scene = Scene(self)
self.knitprog = KnitProgress(self)
self.engine = KnitEngine(self)
self.engine = Engine(self)
self.progbar = ProgressBar(self)
self.flash = FirmwareFlash(self)
self.audio = AudioPlayer(self)
self.engine_thread = GenericThread(self.engine.knit)
self.knit_thread = GenericThread(self.engine.run, Operation.KNIT)
self.test_thread = GenericThread(self.engine.run, Operation.TEST)

# show UI
self.showMaximized()
Expand Down Expand Up @@ -124,7 +126,7 @@ def start_knitting(self):
self.ui.filename_lineedit.setEnabled(False)
self.ui.load_file_button.setEnabled(False)
# start thread for knit engine
self.engine_thread.start()
self.knit_thread.start()

def finish_knitting(self, beep: bool):
"""(Re-)enable UI elements after knitting finishes."""
Expand All @@ -134,6 +136,21 @@ def finish_knitting(self, beep: bool):
if beep:
self.audio.play("finish")

def start_testing(self):
"""Start the testing process."""
# disable UI elements at start of testing
self.menu.depopulate()
self.ui.filename_lineedit.setEnabled(False)
self.ui.load_file_button.setEnabled(False)
# start thread for test engine
self.test_thread.start()

def finish_testing(self, beep: bool):
"""(Re-)enable UI elements after testing finishes."""
self.menu.repopulate()
self.ui.filename_lineedit.setEnabled(True)
self.ui.load_file_button.setEnabled(True)

def set_image_dimensions(self):
"""Set dimensions of image."""
width, height = self.scene.ayabimage.image.size
Expand Down
2 changes: 1 addition & 1 deletion src/main/python/ayab/engine/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from . import engine

# This adds the engine to the upper namespace of the module.
KnitEngine = engine.KnitEngine
Engine = engine.Engine
21 changes: 10 additions & 11 deletions src/main/python/ayab/engine/communication.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import pprint


class MessageToken(Enum):
class Token(Enum):
unknown = -2
none = -1
reqInfo = 0x03
Expand All @@ -47,7 +47,7 @@ class MessageToken(Enum):
slipFrameEnd = 0xc0


class AyabCommunication(object):
class Communication(object):
"""Class Handling the serial communication protocol."""
def __init__(self, serial=None):
"""Create an AyabCommunication object,
Expand Down Expand Up @@ -98,20 +98,19 @@ def close_serial(self):
# NB this method must be the same for all API versions
def req_info(self):
"""Send a request for information to the device."""
data = self.__driver.send(bytes([MessageToken.reqInfo.value]))
data = self.__driver.send(bytes([Token.reqInfo.value]))
self.__ser.write(data)

def req_test_API6(self, machine_val):
"""Send a request for testing to the device."""
data = self.__driver.send(
bytes([MessageToken.reqTest.value, machine_val]))
data = self.__driver.send(bytes([Token.reqTest.value, machine_val]))
self.__ser.write(data)

def req_start_API6(self, machine_val, start_needle, stop_needle,
continuous_reporting):
"""Send a start message to the device."""
data = bytearray()
data.append(MessageToken.reqStart.value)
data.append(Token.reqStart.value)
data.append(machine_val)
data.append(start_needle)
data.append(stop_needle)
Expand All @@ -137,7 +136,7 @@ def cnf_line_API6(self, line_number, color, flags, line_data):
"""
data = bytearray()
data.append(MessageToken.cnfLine.value)
data.append(Token.cnfLine.value)
data.append(line_number)
data.append(color)
data.append(flags)
Expand All @@ -158,21 +157,21 @@ def update_API6(self):
if len(self.__rx_msg_list) > 0:
return self.parse_update_API6(self.__rx_msg_list.pop(0))

return None, MessageToken.none, 0
return None, Token.none, 0

def parse_update_API6(self, msg):
if msg is None:
return None, MessageToken.none, 0
return None, Token.none, 0

for t in list(MessageToken):
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)
return msg, MessageToken.unknown, 0
return msg, Token.unknown, 0


# CRC algorithm after Maxim/Dallas
Expand Down
22 changes: 10 additions & 12 deletions src/main/python/ayab/engine/communication_mockup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# Copyright 2013 Christian Obersteiner, Andreas Müller, Christian Gerbrandt
# https://github.com/AllYarnsAreBeautiful/ayab-desktop
"""
Mockup Class of AYABCommunication for Test/Simulation purposes
Mockup Class of Communication for Test/Simulation purposes
"""

import logging
Expand All @@ -26,10 +26,10 @@
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMessageBox

from .communication import AyabCommunication, MessageToken
from .communication import Communication, Token


class AyabCommunicationMockup(AyabCommunication):
class CommunicationMockup(Communication):
"""Class Handling the serial communication protocol."""
def __init__(self, delay=True, step=False) -> None:
logging.basicConfig(level=logging.DEBUG)
Expand Down Expand Up @@ -60,30 +60,28 @@ def open_serial(self, portname=None) -> bool:
return True

def req_info(self) -> None:
cnfInfo = bytearray([MessageToken.cnfInfo.value, 0x5, 0xFF, 0xFF])
cnfInfo = bytearray([Token.cnfInfo.value, 0x5, 0xFF, 0xFF])
self.__rx_msg_list.append(cnfInfo)
indState = bytearray([
MessageToken.indState.value, 0x1, 0xFF, 0xFF, 0xFF, 0xFF, 0x1, 0x7F
])
indState = bytearray(
[Token.indState.value, 0x1, 0xFF, 0xFF, 0xFF, 0xFF, 0x1, 0x7F])
self.__rx_msg_list.append(indState)

def req_test_API6(self, machine_val) -> None:
cnfTest = bytearray([MessageToken.cnfTest.value, 0x1])
cnfTest = bytearray([Token.cnfTest.value, 0x1])
self.__rx_msg_list.append(cnfTest)

def req_start_API6(self, machine_val, start_needle, stop_needle,
continuous_reporting) -> None:
self.__is_started = True
cnfStart = bytearray([MessageToken.cnfStart.value, 0x1])
cnfStart = bytearray([Token.cnfStart.value, 0x1])
self.__rx_msg_list.append(cnfStart)

def cnf_line_API6(self, line_number, color, flags, line_data) -> bool:
return True

def update_API6(self) -> tuple:
if self.__is_open and self.__is_started:
reqLine = bytearray(
[MessageToken.reqLine.value, self.__line_count])
reqLine = bytearray([Token.reqLine.value, self.__line_count])
self.__line_count += 1
self.__line_count %= 256
self.__rx_msg_list.append(reqLine)
Expand All @@ -105,4 +103,4 @@ def update_API6(self) -> tuple:
if len(self.__rx_msg_list) > 0:
return self.parse_update_API6(self.__rx_msg_list.pop(0))

return None, MessageToken.none, 0
return None, Token.none, 0
34 changes: 17 additions & 17 deletions src/main/python/ayab/engine/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@

from PyQt5.QtCore import QCoreApplication

from .communication import AyabCommunication, MessageToken
from .communication_mockup import AyabCommunicationMockup
from .communication import Communication, Token
from .communication_mockup import CommunicationMockup
from .options import Alignment
from .mode import KnitMode, KnitModeFunc
from .state import KnitState, KnitStateMachine
from .output import KnitOutput
from .mode import Mode, ModeFunc
from .state import State, StateMachine
from .output import Output
from ayab.machine import Machine


class KnitControl(object):
class Control(object):
"""
Class governing information flow with the shield.
"""
Expand All @@ -48,7 +48,7 @@ def __init__(self, parent):

def start(self, machine):
self.machine_width = machine.width
self.state = KnitState.SETUP
self.state = State.SETUP

def stop(self):
try:
Expand All @@ -69,17 +69,17 @@ def func_selector(self):
return False
# else
func_name = self.mode.knit_func(self.num_colors)
if not hasattr(KnitModeFunc, func_name):
if not hasattr(ModeFunc, func_name):
self.logger.error(
"Unrecognized value returned from KnitMode.knit_func()")
"Unrecognized value returned from Mode.knit_func()")
return False
# else
self.mode_func = getattr(KnitModeFunc, func_name)
self.mode_func = getattr(ModeFunc, func_name)
return True

def reset_status(self):
self.status.reset()
if self.mode == KnitMode.SINGLEBED:
if self.mode == Mode.SINGLEBED:
self.status.alt_color = self.pattern.palette[1]
self.status.color_symbol = "" # "A/B"
else:
Expand All @@ -88,9 +88,9 @@ def reset_status(self):

def check_serial_API6(self):
msg, token, param = self.com.update_API6()
if token == MessageToken.cnfInfo:
if token == Token.cnfInfo:
self.__log_cnfInfo(msg)
elif token == MessageToken.indState:
elif token == Token.indState:
self.status.parse_device_state_API6(param, msg)
return token, param

Expand Down Expand Up @@ -154,7 +154,7 @@ def __update_status(self, line_number, color, bits):
self.status.line_number = line_number
if self.inf_repeat:
self.status.repeats = self.pattern_repeats
if self.mode != KnitMode.SINGLEBED:
if self.mode != Mode.SINGLEBED:
self.status.color_symbol = self.COLOR_SYMBOLS[color]
self.status.color = self.pattern.palette[color]
if self.FLANKING_NEEDLES:
Expand Down Expand Up @@ -190,8 +190,8 @@ def select_needles_API6(self, color, row_index, blank_line):
def operate(self, pattern, options, operation, API_version=6):
"""Finite State Machine governing serial communication"""
method = "_API" + str(API_version) + "_" + self.state.name.lower()
if not hasattr(KnitStateMachine, method):
return KnitOutput.NONE
dispatch = getattr(KnitStateMachine, method)
if not hasattr(StateMachine, method):
return Output.NONE
dispatch = getattr(StateMachine, method)
result = dispatch(self, pattern, options, operation)
return result
52 changes: 33 additions & 19 deletions src/main/python/ayab/engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@

from ayab import utils
from ayab.observable import Observable
from .control import KnitControl
from .state import KnitOperation
from .control import Control
from .state import Operation
from .pattern import Pattern
from .options import OptionsTab, Alignment, NeedleColor
from .status import Status, StatusTab
from .mode import KnitMode
from .output import KnitOutput, KnitFeedbackHandler
from .mode import Mode
from .output import Output, FeedbackHandler
from .dock_gui import Ui_DockWidget


class KnitEngine(Observable, QDockWidget):
class Engine(Observable, QDockWidget):
"""
Top-level class for the slave thread that communicates with the shield.
Expand All @@ -52,9 +52,10 @@ def __init__(self, parent):
self.status = StatusTab()
self.setup_ui()
parent.ui.dock_container_layout.addWidget(self)
self.__control = KnitControl(self)
self.__control = Control(self)
self.__logger = logging.getLogger(type(self).__name__)
self.__feedback = KnitFeedbackHandler(parent)
self.__feedback = FeedbackHandler(parent)
self.__pattern = None

def __del__(self):
self.__control.stop()
Expand Down Expand Up @@ -149,9 +150,6 @@ def knit_config(self, image):
# if self.config.continuous_reporting:
# self.__status_tab.activate()

# start knitting controller
self.__control.start(self.config.machine)

# send signal to start knitting
self.emit_knitting_starter()

Expand All @@ -161,25 +159,42 @@ def validate(self):
# else
return self.config.validate()

def knit(self):
def run(self, operation):
self.__canceled = False

# start knitting controller
self.__control.start(self.config.machine)

while True:
# continue knitting
# continue operating
# typically each step involves some communication with the shield

# FIXME pattern and config are only used by KnitControl.knit()
# in the KnitState.SETUP step and do not need to be sent otherwise.
# FIXME pattern and config are only used by Control.operate()
# in the State.SETUP step and do not need to be sent otherwise.
result = self.__control.operate(self.__pattern, self.config,
KnitOperation.KNIT)
operation)
self.__feedback.handle(result)
self.__status_handler()
if self.__canceled or result is KnitOutput.FINISHED:
if operation == Operation.KNIT:
self.__status_handler()
if self.__canceled or result is Output.FINISHED:
break

self.__logger.info("Finished knitting.")
self.__control.stop()

if operation == Operation.KNIT:
if self.__canceled:
self.emit_notification("Knitting canceled.")
self.__logger.info("Knitting canceled.")
else:
self.__logger.info("Finished knitting.")
else:
# TODO: provide translations for these messages
if self.__canceled:
self.emit_notification("Testing canceled.")
self.__logger.info("Testing canceled.")
else:
self.__logger.info("Finished testing.")

# small delay to finish printing to knit progress window
# before "finish.wav" sound plays
sleep(1)
Expand All @@ -205,5 +220,4 @@ def __status_handler(self):
data.repeats, data.color_symbol)

def cancel(self):
self.emit_notification("Knitting canceled.")
self.__canceled = True
Loading

0 comments on commit b3ff08e

Please sign in to comment.