├── tests
├── test_communication.py
├── test_exceptions.py
├── mushroom.png
├── test_knitlib_cli.py
├── test_src_knitlib_knitting_job.py
├── test_knitpat_module.py
├── test_ayab_image.py
├── test_knitting_plugin.py
└── test_ayab_communication.py
├── docs
├── authors.rst
├── changelog.rst
├── contributing.rst
├── readme.rst
├── requirements.txt
├── reference
│ ├── index.rst
│ ├── knitlib_plugin_api.rst
│ ├── knitpat.rst
│ └── knitlib.rst
├── installation.rst
├── spelling_wordlist.txt
├── index.rst
├── usage.rst
├── writing_plugins.rst
└── conf.py
├── requirements.txt
├── src
└── knitlib
│ ├── plugins
│ ├── ayab_plugin
│ │ ├── mushroom.png
│ │ ├── __init__.py
│ │ ├── ayab_communication.py
│ │ ├── ayab_image.py
│ │ └── ayab_control.py
│ ├── pdd_plugin
│ │ ├── splitfile2track.py
│ │ ├── __init__.py
│ │ ├── insertpattern.py
│ │ ├── brother.py
│ │ └── PDDemulate.py
│ ├── __init__.py
│ ├── dummy_plugin.py
│ └── knitting_plugin.py
│ ├── knitpat
│ ├── validate.py
│ ├── knitting_pattern_schema.json
│ └── __init__.py
│ ├── __init__.py
│ ├── machine_handler.py
│ ├── knitting_job.py
│ ├── communication.py
│ ├── __main__.py
│ └── exceptions.py
├── CHANGELOG.rst
├── .coveragerc
├── AUTHORS.rst
├── MANIFEST.in
├── ci
├── templates
│ ├── .travis.yml
│ ├── appveyor.yml
│ └── tox.ini
├── appveyor-with-compiler.cmd
├── bootstrap.py
└── appveyor-bootstrap.ps1
├── .gitignore
├── .travis.yml
├── setup.cfg
├── tox.ini
├── appveyor.yml
├── CONTRIBUTING.rst
├── setup.py
├── README.rst
└── LICENSE
/tests/test_communication.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/test_exceptions.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/authors.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../AUTHORS.rst
2 |
--------------------------------------------------------------------------------
/docs/changelog.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../CHANGELOG.rst
2 |
--------------------------------------------------------------------------------
/docs/contributing.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../CONTRIBUTING.rst
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | --index-url https://pypi.python.org/simple/
2 |
3 | -e .
--------------------------------------------------------------------------------
/tests/mushroom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/knitlib/HEAD/tests/mushroom.png
--------------------------------------------------------------------------------
/docs/readme.rst:
--------------------------------------------------------------------------------
1 | ########
2 | Overview
3 | ########
4 |
5 | .. include:: ../README.rst
6 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx
2 | sphinxcontrib-napoleon
3 | sphinx-py3doc-enhanced-theme
4 | -e .
5 |
--------------------------------------------------------------------------------
/docs/reference/index.rst:
--------------------------------------------------------------------------------
1 | Reference
2 | =========
3 |
4 | .. toctree::
5 | :glob:
6 |
7 | knit*
8 |
--------------------------------------------------------------------------------
/docs/installation.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Installation
3 | ============
4 |
5 | At the command line::
6 |
7 | pip install knitlib
8 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/ayab_plugin/mushroom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/knitlib/HEAD/src/knitlib/plugins/ayab_plugin/mushroom.png
--------------------------------------------------------------------------------
/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 | =========
2 | Changelog
3 | =========
4 |
5 | 0.0.1 (2015-05-15
6 | -----------------------------------------
7 |
8 | * First release on PyPI.
9 |
--------------------------------------------------------------------------------
/docs/spelling_wordlist.txt:
--------------------------------------------------------------------------------
1 | builtin
2 | builtins
3 | classmethod
4 | staticmethod
5 | classmethods
6 | staticmethods
7 | args
8 | kwargs
9 | callstack
10 | Changelog
11 | Indices
12 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [paths]
2 | source = src
3 |
4 | [run]
5 | branch = True
6 | source = src
7 | parallel = true
8 |
9 | [report]
10 | show_missing = true
11 | precision = 2
12 | omit = *migrations*
13 |
--------------------------------------------------------------------------------
/docs/reference/knitlib_plugin_api.rst:
--------------------------------------------------------------------------------
1 | Knitlib Plugin API
2 | =============================
3 |
4 |
5 | Base Knitting Plugin
6 | --------------------
7 |
8 | .. automodule:: knitlib.plugins.knitting_plugin
9 | :members:
10 |
--------------------------------------------------------------------------------
/AUTHORS.rst:
--------------------------------------------------------------------------------
1 |
2 | Authors
3 | =======
4 |
5 | * Sebastian Oliva - http://sebastianoliva.com
6 | * Shiluka Dharmasena - http://shiluka.blogspot.com/
7 |
8 | AYAB Authors
9 | ------------
10 |
11 | * Christian Obersteiner
12 | * Andreas Müller
13 |
14 | PDD Authors
15 | -----------
16 |
17 | * Steve Conklin
--------------------------------------------------------------------------------
/docs/reference/knitpat.rst:
--------------------------------------------------------------------------------
1 | Knitpat
2 | =============================
3 |
4 | Knitpat is an standarized format for knitting machine files. It is based in JSON, validated via a JSON Schema and
5 | by each tool according to it's supported features.
6 |
7 |
8 | .. automodule:: knitlib.knitpat
9 | :members:
10 |
--------------------------------------------------------------------------------
/src/knitlib/knitpat/validate.py:
--------------------------------------------------------------------------------
1 | import jsonschema
2 | import json
3 |
4 |
5 | def validate():
6 | validate_file = file("knitting_pattern_sample.json", "rb")
7 | schema_file = file("knitting_pattern_schema.json", "rb")
8 | data = json.load(validate_file)
9 | schema = json.load(schema_file)
10 | jsonschema.validate(data, schema)
11 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to knitlib's documentation!
2 | ======================================
3 |
4 | Contents:
5 |
6 | .. toctree::
7 | :maxdepth: 2
8 |
9 | readme
10 | installation
11 | usage
12 | reference/index
13 | contributing
14 | authors
15 | changelog
16 |
17 | Indices and tables
18 | ==================
19 |
20 | * :ref:`genindex`
21 | * :ref:`modindex`
22 | * :ref:`search`
23 |
24 |
--------------------------------------------------------------------------------
/docs/reference/knitlib.rst:
--------------------------------------------------------------------------------
1 | Knitlib Overview
2 | =============================
3 |
4 |
5 | Knitlib modules
6 | ---------------
7 |
8 | .. automodule:: knitlib
9 | :members:
10 |
11 | Knitlib machine handler
12 | -----------------------
13 |
14 | .. automodule:: knitlib.machine_handler
15 | :members:
16 |
17 | Knitlib Knitting Jobs
18 | ---------------------
19 |
20 | .. automodule:: knitlib.knitting_job
21 | :members:
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | graft docs
2 | graft examples
3 | graft src
4 | graft ci
5 | graft tests
6 |
7 | include *.komodoproject
8 | include .bumpversion.cfg
9 | include .coveragerc
10 | include .isort.cfg
11 | include .pylintrc
12 |
13 | include AUTHORS.rst
14 | include CHANGELOG.rst
15 | include CONTRIBUTING.rst
16 | include LICENSE
17 | include README.rst
18 |
19 | include tox.ini .travis.yml appveyor.yml
20 | include requirements.txt
21 |
22 | include *.json
23 |
24 | global-exclude *.py[cod] __pycache__ *.so
25 |
--------------------------------------------------------------------------------
/ci/templates/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python: 2.7
3 | sudo: false
4 | env:
5 | global:
6 | LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so
7 | matrix:
8 | - TOXENV=check
9 | {% for env, config in tox_environments|dictsort %}
10 | - TOXENV={{ env }}{% if config.cover %},extension-coveralls,coveralls{% endif %}
11 |
12 | {% endfor %}
13 | before_install:
14 | - python --version
15 | - virtualenv --version
16 | - pip --version
17 | - uname -a
18 | - lsb_release -a
19 | install:
20 | - pip install tox
21 | script:
22 | - tox -v
23 | notifications:
24 | email:
25 | on_success: never
26 | on_failure: always
27 |
28 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/pdd_plugin/splitfile2track.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | if len(sys.argv) != 2:
7 | print 'Usage: %s file.dat' % sys.argv[0]
8 | print 'Splits a 2K file.dat file into two 1K track files track0.dat and track1.dat'
9 | sys.exit()
10 |
11 | infile = open(sys.argv[1], 'rb')
12 |
13 | track0file = open("track0.dat", 'wb')
14 | track1file = open("track1.dat", 'wb')
15 |
16 | t0dat = infile.read(1024)
17 | t1dat = infile.read(1024)
18 |
19 | track0file.write(t0dat)
20 | track0file.close()
21 |
22 | track1file.write(t1dat)
23 | track1file.close()
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 |
3 | # C extensions
4 | *.so
5 |
6 | # Compiled python modules
7 | *.pyc
8 |
9 | # Packages
10 | *.egg
11 | *.egg-info
12 | dist
13 | build
14 | eggs
15 | parts
16 | bin
17 | var
18 | sdist
19 | develop-eggs
20 | .installed.cfg
21 | lib
22 | lib64
23 | venv*/
24 | pyvenv*/
25 |
26 | # Installer logs
27 | pip-log.txt
28 |
29 | # Unit test / coverage reports
30 | .coverage
31 | .tox
32 | .coverage.*
33 | nosetests.xml
34 | htmlcov
35 |
36 | # Translations
37 | *.mo
38 |
39 | # Mr Developer
40 | .mr.developer.cfg
41 | .project
42 | .pydevproject
43 | .idea
44 | *.iml
45 | *.komodoproject
46 |
47 | # Complexity
48 | output/*.html
49 | output/*/index.html
50 |
51 | # Sphinx
52 | docs/_build
53 |
54 | .DS_Store
55 | *~
56 | .*.sw[po]
57 | .build
58 | .ve
59 | .bootstrap
60 | *.bak
61 | .pythoscope/
62 |
--------------------------------------------------------------------------------
/tests/test_knitlib_cli.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from click.testing import CliRunner
3 | import mock
4 | from knitlib.__main__ import main
5 |
6 |
7 | def test_main_init_with_no_args():
8 | runner = CliRunner()
9 | result = runner.invoke(main, [])
10 |
11 | assert result.output is not '()\n'
12 | assert result.exit_code is 0 # It should fail due to invalid configuration,
13 |
14 |
15 | @mock.patch("knitlib.__main__.logging.error")
16 | def test_invalid_plugin_fail(mock_logging):
17 | runner = CliRunner()
18 | result = runner.invoke(main, ["--plugin_name", "INVALID_PLUGIN"])
19 | # assert mock_logging has been called
20 | assert result.output is not '()\n'
21 | #assert result.exit_code is not 0
22 |
23 |
24 | @mock.patch('knitlib.plugins.dummy_plugin.logging.debug')
25 | def test_dummy_knit(mock_logging):
26 | runner = CliRunner()
27 | result = runner.invoke(main, ["--plugin_name", "dummy"])
28 | # assert mock_logging.called
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/knitlib/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of Knitlib.
3 | #
4 | # Knitlib is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # Knitlib is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with Knitlib. If not, see .
16 | #
17 | # Copyright 2015 Sebastian Oliva
18 |
19 | __version__ = "0.0.1"
20 |
21 | import machine_handler
22 | machine_handler = machine_handler
23 |
24 | import knitpat
25 | knitpat = knitpat
26 |
--------------------------------------------------------------------------------
/docs/usage.rst:
--------------------------------------------------------------------------------
1 | =====
2 | Usage
3 | =====
4 |
5 | To use knitlib in a project::
6 |
7 | import knitlib
8 |
9 | # Search for a plugin via it's public name.
10 | Plugin = knitlib.machine_handler.get_machine_plugin_by_id(plugin_name)
11 | # Initalize the plugin instance
12 | machine_instance = Plugin()
13 | machine_instance.configure(conf={'options': 'go here'})
14 | # Knit will block execution. If you need async execution check the callbacks configuration.
15 | machine_instance.knit()
16 | machine_instance.finish()
17 |
18 | Each machine has it's own configuration options, however the base functions are standardized in knitpat.
19 | Check the Knitpat section for more info.
20 |
21 | Knitlib API allows for setting callbacks for certain actions that the user needs to interact with the underlying
22 | hardware. This callbacks system includes messaging, messages with blocking operations (move knob, set dial, feed yarn,
23 | etc) and progress. Default callbacks are included for CLI operation, and projects should provide their own
24 | implementations for the environment in use (knitweb, desktop UIs, etc).
25 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/ayab_plugin/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of Knitlib. It is based on AYAB
3 | #
4 | # Knitlib is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # Knitlib is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with Knitlib. If not, see .
16 | #
17 | # Copyright 2015 Sebastian Oliva, Christian Obersteiner, Andreas Müller
18 |
19 | __author__ = 'tian'
20 |
21 | import ayab_control
22 |
23 | # This adds ayab_control.AyabControl to the upper namespace of the module.
24 | AyabPluginControl = ayab_control.AyabPluginControl
25 |
--------------------------------------------------------------------------------
/docs/writing_plugins.rst:
--------------------------------------------------------------------------------
1 | ===============
2 | Writing Plugins
3 | ===============
4 |
5 | One of the main objectives of knitlib is to be a common platform for knitting machine software and control. Knitlib is
6 | designed with a modular approach, that allows programmers to easily create plugins without worrying about Control, UI
7 | elements, file and port abstractions, knitting pattern specification and format, etc.
8 |
9 | Fundamental Abstractions: A Finite State Machine for Knitting Machines
10 | ======================================================================
11 |
12 | Center to all abstractions in Knitlib is the Knitting Finite State Machine, defined at knitlib/plugins/knitting_plugin.py
13 | BaseKnittingPlugin is a simple plugin base that offers several commodities for the handling of the knitting flow.
14 | Function such as onconfigure, onknit and onfinish are used to setup the plugin and to operate it.
15 |
16 |
17 | Documentation improvements
18 | ==========================
19 |
20 | knitlib could always use more documentation, whether as part of the
21 | official knitlib docs, in docstrings, or even on the web in blog posts,
22 | articles, and such.
23 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python: 2.7
3 | sudo: false
4 | env:
5 | global:
6 | LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so
7 | matrix:
8 | - TOXENV=check
9 | # - TOXENV=2.6,extension-coveralls,coveralls
10 | # - TOXENV=2.6-nocover
11 | - TOXENV=2.7,extension-coveralls,coveralls
12 | - TOXENV=2.7-nocover
13 | # - TOXENV=3.3,extension-coveralls,coveralls
14 | # - TOXENV=3.3-nocover
15 | # - TOXENV=3.4,extension-coveralls,coveralls
16 | # - TOXENV=3.4-nocover
17 | #- TOXENV=pypy,extension-coveralls,coveralls
18 | #- TOXENV=pypy-nocover
19 | before_install:
20 | - python --version
21 | - virtualenv --version
22 | - pip --version
23 | - uname -a
24 | - lsb_release -a
25 | install:
26 | - pip install tox flake8
27 | script:
28 | - tox -v
29 | notifications:
30 | email:
31 | on_success: never
32 | on_failure: always
33 | webhooks:
34 | urls:
35 | - https://webhooks.gitter.im/e/5d1581aa60471c78e1da
36 | on_success: change # options: [always|never|change] default: always
37 | on_failure: always # options: [always|never|change] default: always
38 | on_start: false # default: false
39 |
40 |
--------------------------------------------------------------------------------
/tests/test_src_knitlib_knitting_job.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 |
4 | class TestKnittingJob(unittest.TestCase):
5 | def test___init__(self):
6 | # knitting_job = KnittingJob(plugin_class, port, knitpat_file)
7 | assert True # TODO: implement your test here
8 |
9 | def test_configure_job(self):
10 | # knitting_job = KnittingJob(plugin_class, port, knitpat_file)
11 | # self.assertEqual(expected, knitting_job.configure_job())
12 | assert True # TODO: implement your test here
13 |
14 | def test_get_plugin_name(self):
15 | # knitting_job = KnittingJob(plugin_class, port, knitpat_file)
16 | # self.assertEqual(expected, knitting_job.get_plugin_name())
17 | assert True # TODO: implement your test here
18 |
19 | def test_knit_job(self):
20 | # knitting_job = KnittingJob(plugin_class, port, knitpat_file)
21 | # self.assertEqual(expected, knitting_job.knit_job())
22 | assert True # TODO: implement your test here
23 |
24 | def test_start_job(self):
25 | # knitting_job = KnittingJob(plugin_class, port, knitpat_file)
26 | # self.assertEqual(expected, knitting_job.start_job())
27 | assert True # TODO: implement your test here
28 |
29 |
30 | if __name__ == '__main__':
31 | unittest.main()
32 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | import os
5 |
6 |
7 | extensions = [
8 | 'sphinx.ext.autodoc',
9 | 'sphinx.ext.autosummary',
10 | 'sphinx.ext.todo',
11 | 'sphinx.ext.coverage',
12 | 'sphinx.ext.ifconfig',
13 | 'sphinx.ext.viewcode',
14 | 'sphinx.ext.napoleon'
15 | ]
16 | if os.getenv('SPELLCHECK'):
17 | extensions += 'sphinxcontrib.spelling',
18 | spelling_show_suggestions = True
19 | spelling_lang = 'en_US'
20 |
21 | source_suffix = '.rst'
22 | master_doc = 'index'
23 | project = 'knitlib'
24 | year = '2015'
25 | author = 'Sebastian Oliva'
26 | copyright = '{0}, {1}'.format(year, author)
27 | version = release = '0.0.1'
28 |
29 | #import sphinx_py3doc_enhanced_theme
30 | #html_theme = "sphinx_py3doc_enhanced_theme"
31 | #html_theme_path = [sphinx_py3doc_enhanced_theme.get_html_theme_path()]
32 |
33 | pygments_style = 'trac'
34 | templates_path = ['.']
35 | html_use_smartypants = True
36 | html_last_updated_fmt = '%b %d, %Y'
37 | html_split_index = True
38 | html_sidebars = {
39 | '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'],
40 | }
41 | html_short_title = '%s-%s' % (project, version)
42 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
43 | if on_rtd:
44 | html_theme_options = {
45 | 'githuburl': 'https://github.com/fashiontec/knitlib/'
46 | }
47 |
--------------------------------------------------------------------------------
/src/knitlib/knitpat/knitting_pattern_schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema":"http://json-schema.org/draft-04/schema#",
3 | "title":"KnittingPattern",
4 | "description":"A knitting pattern specification. Defines a file name and knitting specs.",
5 | "type":"object",
6 | "properties":{
7 | "file_url":{
8 | "description":"The name of the file that holds the pattern.",
9 | "type":"string"
10 | },
11 | "pattern_name":{
12 | "description":"Name of the pattern",
13 | "type":"string"
14 | },
15 | "dimensions":{
16 | "type":"object",
17 | "properties":{
18 | "image_length":{
19 | "type":"integer"
20 | },
21 | "image_width":{
22 | "type":"integer"
23 | },
24 | "physical_length":{
25 | "type":"number"
26 | },
27 | "physical_width":{
28 | "type":"number"
29 | },
30 | "units":{
31 | "type":"string"
32 | },
33 | "yarn_strength":{
34 | "type":"object",
35 | "properties":{
36 | "units":{
37 | "type":"string"
38 | },
39 | "value":{
40 | "type":"number"
41 | }
42 | }
43 | }
44 | }
45 | },
46 | "colors":{
47 | "type":"integer"
48 | }
49 | },
50 | "required":[
51 | "file_url",
52 | "colors"
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of Knitlib.
3 | #
4 | # Knitlib is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # Knitlib is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with Knitlib. If not, see .
16 | #
17 | # Copyright 2015 Sebastian Oliva
18 |
19 | from enum import Enum
20 |
21 | from dummy_plugin import DummyKnittingPlugin
22 | from ayab_plugin import AyabPluginControl
23 | from pdd_plugin import PDDEmulationKnittingPlugin
24 |
25 |
26 | PluginType = Enum('PluginType', 'serial network other')
27 | '''PluginTypes holds an enumeration of the type of machine plugins available for use.'''
28 |
29 | active_plugins = {
30 | PluginType.other: {DummyKnittingPlugin.__PLUGIN_NAME__: DummyKnittingPlugin},
31 | PluginType.serial: {AyabPluginControl.__PLUGIN_NAME__: AyabPluginControl,
32 | PDDEmulationKnittingPlugin.__PLUGIN_NAME__: PDDEmulationKnittingPlugin,
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/knitlib/machine_handler.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import plugins
3 | import serial.tools.list_ports
4 |
5 | """Handles allocation of machines to plugins and resources.
6 |
7 | Machine Plugins are defined as subclasses of BaseKnittingPlugin. Each Machine
8 | plugin can be matched to a port or interface. Each port can only handle one
9 | machine at once.
10 | """
11 |
12 |
13 | def get_available_ports():
14 | """Returns a list tuples of available serial ports."""
15 | # TODO: add other kinds of ports listing.
16 | return list(serial.tools.list_ports.comports())
17 |
18 |
19 | def get_machines_by_type(machine_type):
20 | """Returns a list of the available plugins for a given PluginType or empty array if none found."""
21 | return plugins.active_plugins.get(machine_type, {})
22 |
23 |
24 | def get_active_machine_plugins_names():
25 | """Returns a list of tuples of the available plugins and type."""
26 | active_plugins = []
27 | for plugin_type, plugins_by_type in plugins.active_plugins.items():
28 | for plugin_name, plugin_class in plugins_by_type.items():
29 | active_plugins.append(plugin_name)
30 | return active_plugins
31 |
32 |
33 | def get_machine_plugin_by_id(machine_id, if_not_found=None):
34 | """Returns a machine plugin given the machine_id class name."""
35 | for k, v in plugins.active_plugins.items():
36 | if machine_id in v:
37 | return v[machine_id]
38 | return if_not_found
39 |
40 |
41 | def get_machine_types():
42 | """Returns the PluginType Enum."""
43 | return plugins.PluginType
44 |
--------------------------------------------------------------------------------
/src/knitlib/knitpat/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'tian'
2 |
3 | import jsonschema
4 | import logging
5 | import json
6 | import pkg_resources
7 |
8 |
9 | __SCHEMA_DATA = pkg_resources.resource_string(__name__, "knitting_pattern_schema.json")
10 | # file("./knitting_pattern_schema.json", "rb")
11 | __SCHEMA_DICT = json.loads(__SCHEMA_DATA)
12 |
13 |
14 | def validate_dict(loaded_json_data):
15 | """Checks if a dict is a valid Knitpat format.
16 |
17 | Validates if a dict, loaded from a json file, is valid according to the Knitpat scheme.
18 |
19 | Returns:
20 | True if valid, False if not.
21 | """
22 | try:
23 | jsonschema.validate(loaded_json_data, __SCHEMA_DICT)
24 | return True
25 | except Exception as e:
26 | logging.error(e)
27 | return False
28 |
29 |
30 | def parse_ustring(string_data):
31 | """Parses a string into a dict and validates it."""
32 | loaded_json_data = json.loads(string_data)
33 | if validate_dict(loaded_json_data):
34 | return loaded_json_data
35 | else:
36 | raise ValueError("Invalid data string")
37 |
38 |
39 | def parse_dict_from_cli(cli_dict):
40 | """Parses from CLI dict into a valid Knitpat."""
41 | # TODO add parsing for size and measurement.
42 | parsed_dict = {}
43 | for k, v in cli_dict.items():
44 | if k in ["colors"]:
45 | parsed_dict[k] = int(v)
46 | else:
47 | parsed_dict[k] = v
48 | if validate_dict(parsed_dict):
49 | return parsed_dict
50 | else:
51 | return dict()
52 |
--------------------------------------------------------------------------------
/ci/templates/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{branch}-{build}'
2 | build: off
3 | environment:
4 | global:
5 | WITH_COMPILER: "cmd /E:ON /V:ON /C .\\ci\\appveyor-with-compiler.cmd"
6 | matrix:
7 | - TOXENV: check
8 | PYTHON_HOME: "C:\\Python27"
9 | PYTHON_VERSION: "2.7"
10 | PYTHON_ARCH: "32"
11 | {% for env, config in tox_environments|dictsort %}{% if env.startswith('2.7') or env.startswith('3.4') or env.startswith('3.3') %}
12 | - TOXENV: "{{ env }}"
13 | TOXPYTHON: "C:\\Python{{ env[:3].replace('.', '') }}\\python.exe"
14 | WINDOWS_SDK_VERSION: "v7.{{ '1' if env[0] == '3' else '0' }}"
15 | PYTHON_HOME: "C:\\Python{{ env[:3].replace('.', '') }}"
16 | PYTHON_VERSION: "{{ env[:3] }}"
17 | PYTHON_ARCH: "32"
18 | - TOXENV: "{{ env }}"
19 | TOXPYTHON: "C:\\Python{{ env[:3].replace('.', '') }}-x64\\python.exe"
20 | WINDOWS_SDK_VERSION: "v7.{{ '1' if env[0] == '3' else '0' }}"
21 | PYTHON_HOME: "C:\\Python{{ env[:3].replace('.', '') }}-x64"
22 | PYTHON_VERSION: "{{ env[:3] }}"
23 | PYTHON_ARCH: "64"
24 | {% endif %}{% endfor %}
25 | init:
26 | - "ECHO %TOXENV%"
27 | - ps: "ls C:\\Python*"
28 | install:
29 | - "powershell ci\\appveyor-bootstrap.ps1"
30 | test_script:
31 | - "%PYTHON_HOME%\\Scripts\\tox --version"
32 | - "%PYTHON_HOME%\\Scripts\\virtualenv --version"
33 | - "%PYTHON_HOME%\\Scripts\\pip --version"
34 | - "%WITH_COMPILER% %PYTHON_HOME%\\Scripts\\tox"
35 | after_test:
36 | - "IF \"%TOXENV:~-8,8%\" == \"-nocover\" %WITH_COMPILER% %TOXPYTHON% setup.py bdist_wheel"
37 | artifacts:
38 | - path: dist\*
39 |
40 |
--------------------------------------------------------------------------------
/ci/appveyor-with-compiler.cmd:
--------------------------------------------------------------------------------
1 | :: To build extensions for 64 bit Python 3, we need to configure environment
2 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of:
3 | :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1)
4 | ::
5 | :: To build extensions for 64 bit Python 2, we need to configure environment
6 | :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of:
7 | :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0)
8 | ::
9 | :: 32 bit builds do not require specific environment configurations.
10 | ::
11 | :: Note: this script needs to be run with the /E:ON and /V:ON flags for the
12 | :: cmd interpreter, at least for (SDK v7.0)
13 | ::
14 | :: More details at:
15 | :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows
16 | :: http://stackoverflow.com/a/13751649/163740
17 | ::
18 | :: Author: Olivier Grisel
19 | :: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
20 | @ECHO OFF
21 |
22 | SET COMMAND_TO_RUN=%*
23 | SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows
24 |
25 | IF "%PYTHON_ARCH%"=="64" (
26 | ECHO SDK: %WINDOWS_SDK_VERSION% ARCH: %PYTHON_ARCH%
27 | SET DISTUTILS_USE_SDK=1
28 | SET MSSdk=1
29 | "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION%
30 | "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release
31 | ECHO Executing: %COMMAND_TO_RUN%
32 | call %COMMAND_TO_RUN% || EXIT 1
33 | ) ELSE (
34 | ECHO SDK: %WINDOWS_SDK_VERSION% ARCH: %PYTHON_ARCH%
35 | ECHO Executing: %COMMAND_TO_RUN%
36 | call %COMMAND_TO_RUN% || EXIT 1
37 | )
38 |
--------------------------------------------------------------------------------
/tests/test_knitpat_module.py:
--------------------------------------------------------------------------------
1 | __author__ = 'tian'
2 |
3 | import json
4 | import logging
5 | from click.testing import CliRunner
6 |
7 | from knitlib import __main__ as main
8 | from knitlib.knitpat import validate_dict
9 | from knitlib.knitpat import parse_ustring
10 |
11 |
12 | def test_validate_simple_data_knitpat_method():
13 | test_sample = ("{\n"
14 | " \"file_url\":\"mypat.png\",\n"
15 | " \"name\":\"a small sweater\",\n"
16 | " \"colors\": 2}")
17 | sample_dict = json.loads(test_sample)
18 | validation = validate_dict(sample_dict)
19 | assert validation is True
20 |
21 |
22 | def test_validate_invalid_data_knitpat_method():
23 | test_sample = ("{\n"
24 | " \"file_location\":\"mypat.png\",\n"
25 | " \"colors\": 2}")
26 | sample_dict = json.loads(test_sample)
27 | validation = validate_dict(sample_dict)
28 | assert validation is False
29 |
30 |
31 | def test_parse():
32 | test_sample = ("{\n"
33 | " \"file_url\":\"mypat.png\",\n"
34 | " \"name\":\"a small sweater\",\n"
35 | " \"colors\": 2}")
36 | test_dict = {u"file_url": u"mypat.png",
37 | u"name": u"a small sweater",
38 | u"colors": 2}
39 | parsed_sample = parse_ustring(test_sample)
40 | assert test_dict == parsed_sample
41 |
42 |
43 | def test_cli_parse_simple_pattern():
44 | runner = CliRunner()
45 |
46 | result = runner.invoke(main, ["--plugin_name", "dummy",
47 | "--config", "file_url", "mypat.png",
48 | "--config", "pattern_name", "a small sweater",
49 | "--config", "colors", "2"
50 | ])
51 | print result.exception
52 | assert result.exit_code is 0 # Should be 0 as it would mean successful execution.
53 | # TODO: assert that parse_dict_from_cli is properly working
54 |
55 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/dummy_plugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of Knitlib.
3 | #
4 | # Knitlib is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # Knitlib is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with Knitlib. If not, see .
16 | #
17 | # Copyright 2015 Sebastian Oliva
18 |
19 | import logging
20 | import time
21 | import knitting_plugin
22 |
23 |
24 | class DummyKnittingPlugin(knitting_plugin.BaseKnittingPlugin):
25 | """Implements a sample knitting plugin that allows for simple operation emulation."""
26 |
27 | __PLUGIN_NAME__ = u"dummy"
28 |
29 | def __init__(self):
30 | super(DummyKnittingPlugin, self).__init__()
31 |
32 | base_log_string = u"{} has been called on dummy knitting plugin."
33 |
34 | def onknit(self, e):
35 | logging.debug(DummyKnittingPlugin.base_log_string.format("onknit"))
36 | # In order to simulate blocking we make it sleep.
37 | total = 5
38 | for i in range(total):
39 | time.sleep(1)
40 | self.interactive_callbacks["progress"](i / float(total), i, total)
41 | self.finish()
42 |
43 | def onfinish(self, e):
44 | logging.debug(DummyKnittingPlugin.base_log_string.format("onfinish"))
45 |
46 | def onconfigure(self, e):
47 | logging.debug(DummyKnittingPlugin.base_log_string.format("onconfigure"))
48 |
49 | def set_port(self, *args, **kwargs):
50 | pass
51 |
52 | @staticmethod
53 | def supported_config_features():
54 | return {"$schema": "http://json-schema.org/schema#", "type": "object"}
55 |
--------------------------------------------------------------------------------
/src/knitlib/knitting_job.py:
--------------------------------------------------------------------------------
1 | __author__ = 'tian'
2 |
3 | from knitlib.plugins.knitting_plugin import BaseKnittingPlugin
4 | import uuid
5 |
6 |
7 | class KnittingJob(object):
8 | """A Knitting job is composed of a Machine Plugin at a certain state, a port and a knitpat file."""
9 |
10 | def __init__(self, plugin_class, port, callbacks_dict=None, knitpat_dict=None):
11 | assert issubclass(plugin_class, BaseKnittingPlugin)
12 | self.id = uuid.uuid4()
13 | self.__plugin_class = plugin_class
14 | self.__plugin = None
15 | self.__callback_dict = callbacks_dict
16 | self.__port = port
17 | self.__knitpat_dict = knitpat_dict
18 |
19 | def get_plugin_name(self):
20 | return self.__plugin.__PLUGIN_NAME__
21 |
22 | def get_plugin_instance(self):
23 | return self.__plugin
24 |
25 | def get_status(self):
26 | return self.__plugin.current
27 |
28 | def get_job_public_dict(self):
29 | plugin_state = self.__plugin.current if self.__plugin else "none"
30 | return {
31 | "id": str(self.id),
32 | "plugin_type": self.__plugin_class.__PLUGIN_NAME__,
33 | "port": str(self.__port),
34 | "state": plugin_state,
35 | "knitpat_file": self.__knitpat_dict
36 | }
37 |
38 | def init_job(self):
39 | self.__plugin = self.__plugin_class()
40 | assert self.__plugin.current == "activated"
41 | self.__plugin.set_port(self.__port)
42 | if self.__callback_dict is not None:
43 | self.__plugin.register_interactive_callbacks(self.__callback_dict)
44 |
45 | def configure_job(self, knitpat_dict=None):
46 | if knitpat_dict is None:
47 | knitpat_dict = self.__knitpat_dict
48 | self.__plugin.configure(conf=knitpat_dict)
49 |
50 | def knit_job(self):
51 | """Blocking knitting job operation.
52 |
53 | Be sure to call knit_job in an async context, or as a greenlet spawn.
54 | """
55 | # TODO: Add exception handling.
56 | self.__plugin.knit()
57 |
--------------------------------------------------------------------------------
/tests/test_ayab_image.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of Knitlib. It is based on AYAB.
3 | #
4 | # Knitlib is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # Knitlib is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with Knitlib. If not, see .
16 | #
17 | # Copyright 2015 Shiluka Dharmasena, Sebastian Oliva
18 |
19 | import pytest
20 | import unittest
21 | import os
22 | from knitlib.plugins.ayab_plugin.ayab_image import ayabImage
23 | from PIL import Image
24 |
25 | class TestImage(unittest.TestCase):
26 |
27 | def setUp(self):
28 | self.script_dir = os.path.dirname(os.path.abspath(__file__))
29 | self.filename_text = u"mushroom.png"
30 | self.conf = {}
31 | self.conf["filename"] = self.filename_text
32 | self.pil_image = Image.open(os.path.join(self.script_dir, self.conf["filename"]))
33 | self.ayab_image = ayabImage(self.pil_image, 2)
34 |
35 | def test_knitStartNeedle(self):
36 | assert self.ayab_image.knitStartNeedle() == 0
37 |
38 | def test_knitStopNeedle(self):
39 | assert self.ayab_image.knitStopNeedle() == 199
40 |
41 | def test_imgPosition(self):
42 | assert self.ayab_image.imgPosition() == 'center'
43 |
44 | def test_startLine(self):
45 | assert self.ayab_image.startLine() == 0
46 |
47 | def test_numColors(self):
48 | assert self.ayab_image.numColors() == 2
49 |
50 | def test_setStartLine(self):
51 | self.startLine = 0
52 | self.ayab_image.setStartLine(self.startLine)
53 | assert self.ayab_image.startLine() == 0
54 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [aliases]
2 | release = register clean --all sdist
3 |
4 | [flake8]
5 | max-line-length = 140
6 | exclude = tests/*,*/migrations/*,*/south_migrations/*
7 |
8 | [bumpversion]
9 | current_version = 0.0.1
10 | files = setup.py docs/conf.py src/knitlib/__init__.py
11 | commit = True
12 | tag = True
13 |
14 | [pytest]
15 | norecursedirs =
16 | .git
17 | .tox
18 | dist
19 | build
20 | south_migrations
21 | migrations
22 | python_files =
23 | test_*.py
24 | *_test.py
25 | tests.py
26 | addopts =
27 | -rxEfs
28 | --strict
29 | --ignore=docs/conf.py
30 | --ignore=setup.py
31 | --ignore=ci
32 | --doctest-modules
33 | --doctest-glob=\*.rst
34 | --tb=short
35 |
36 | [isort]
37 | force_single_line=True
38 | line_length=120
39 | known_first_party=knitlib
40 | default_section=THIRDPARTY
41 | forced_separate=test_knitlib
42 |
43 | [matrix]
44 | # This is the configuration for the `./bootstrap.py` script.
45 | # It generates `.travis.yml`, `tox.ini` and `appveyor.yml`.
46 | #
47 | # Syntax: [alias:] value [!variable[glob]] [&variable[glob]]
48 | #
49 | # alias:
50 | # - is used to generate the tox environment
51 | # - it's optional
52 | # - if not present the alias will be computed from the `value`
53 | # value:
54 | # - a value of "-" means empty
55 | # !variable[glob]:
56 | # - exclude the combination of the current `value` with
57 | # any value matching the `glob` in `variable`
58 | # - can use as many you want
59 | # &variable[glob]:
60 | # - only include the combination of the current `value`
61 | # when there's a value matching `glob` in `variable`
62 | # - can use as many you want
63 |
64 | python_versions =
65 | #2.6
66 | 2.7
67 | #3.3
68 | #3.4
69 | #pypy
70 |
71 | dependencies =
72 | fysom
73 | pyserial
74 | # 1.4: Django==1.4.16 !python_versions[3.*]
75 | # 1.5: Django==1.5.11
76 | # 1.6: Django==1.6.8
77 | # 1.7: Django==1.7.1 !python_versions[2.6]
78 | # Deps commented above are provided as examples. That's what you would use in a Django project.
79 |
80 |
81 | coverage_flags =
82 | : true
83 | nocover: false
84 |
85 | environment_variables =
86 | -
87 |
--------------------------------------------------------------------------------
/src/knitlib/communication.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of AYAB.
3 | #
4 | # AYAB is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # AYAB is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with AYAB. If not, see .
16 | #
17 | # Copyright 2015 Shiluka Dharmasena, Sebastian Oliva, Christian Obersteiner, Andreas Müller
18 |
19 | import time
20 | import serial
21 | import logging
22 |
23 |
24 | class Communication(object):
25 | """Class Handling the serial communication protocol."""
26 |
27 | def __init__(self, serial=None):
28 | """Creates an Communication object, with an optional serial-like object."""
29 | logging.basicConfig(level=logging.DEBUG)
30 | self.__logger = logging.getLogger(__name__)
31 | self.__ser = serial
32 |
33 | def __del__(self):
34 | """Handles on delete behavior closing serial port object."""
35 | self.close_serial()
36 |
37 | def open_serial(self, pPortname=None, baudrate=None):
38 | """Opens serial port communication with a portName and baudrate."""
39 | if not self.__ser:
40 | self.__portname = pPortname
41 | self.__baudrate = baudrate
42 | try:
43 | self.__ser = serial.Serial(self.__portname, self.__baudrate)
44 | time.sleep(1)
45 | except:
46 | self.__logger.error("could not open serial port " + self.__portname + "with specific baudrate " + self.__baudrate)
47 | raise CommunicationException()
48 | return True
49 |
50 | def close_serial(self):
51 | """Closes serial port."""
52 | try:
53 | self.__ser.close()
54 | del(self.__ser)
55 | self.__ser = None
56 | except:
57 | raise CommunicationException()
58 |
59 |
60 | class CommunicationException(Exception):
61 | pass
62 |
--------------------------------------------------------------------------------
/ci/bootstrap.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from __future__ import absolute_import, print_function, unicode_literals
4 |
5 | import os
6 | import sys
7 | from os.path import exists
8 | from os.path import join
9 | from os.path import dirname
10 | from os.path import abspath
11 |
12 |
13 | if __name__ == "__main__":
14 | base_path = dirname(dirname(abspath(__file__)))
15 | print("Project path: {0}".format(base_path))
16 | env_path = join(base_path, ".tox", "bootstrap")
17 | if sys.platform == "win32":
18 | bin_path = join(env_path, "Scripts")
19 | else:
20 | bin_path = join(env_path, "bin")
21 | if not exists(env_path):
22 | import subprocess
23 | print("Making bootstrap env in: {0} ...".format(env_path))
24 | try:
25 | subprocess.check_call(["virtualenv", env_path])
26 | except Exception:
27 | subprocess.check_call([sys.executable, "-m", "virtualenv", env_path])
28 | print("Installing `jinja2` and `matrix` into bootstrap environment ...")
29 | subprocess.check_call([join(bin_path, "pip"), "install", "jinja2", "matrix"])
30 | activate = join(bin_path, "activate_this.py")
31 | exec(compile(open(activate, "rb").read(), activate, "exec"), dict(__file__=activate))
32 |
33 | import jinja2
34 | import matrix
35 |
36 | jinja = jinja2.Environment(
37 | loader=jinja2.FileSystemLoader(join(base_path, "ci", "templates")),
38 | trim_blocks=True,
39 | lstrip_blocks=True,
40 | keep_trailing_newline=True
41 | )
42 | tox_environments = {}
43 | for (alias, conf) in matrix.from_file(join(base_path, "setup.cfg")).items():
44 | python = conf["python_versions"]
45 | deps = conf["dependencies"]
46 | if "coverage_flags" in conf:
47 | cover = {"false": False, "true": True}[conf["coverage_flags"].lower()]
48 | if "environment_variables" in conf:
49 | env_vars = conf["environment_variables"]
50 |
51 | tox_environments[alias] = {
52 | "python": "python" + python if "py" not in python else python,
53 | "deps": deps.split(),
54 | }
55 | if "coverage_flags" in conf:
56 | tox_environments[alias].update(cover=cover)
57 | if "environment_variables" in conf:
58 | tox_environments[alias].update(env_vars=env_vars.split())
59 |
60 | for name in os.listdir(join("ci", "templates")):
61 | with open(join(base_path, name), "w") as fh:
62 | fh.write(jinja.get_template(name).render(tox_environments=tox_environments))
63 | print("Wrote {}".format(name))
64 | print("DONE.")
65 |
--------------------------------------------------------------------------------
/src/knitlib/__main__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of Knitlib.
3 | #
4 | # Knitlib is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # Knitlib is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with Knitlib. If not, see .
16 | #
17 | # Copyright 2015 Sebastian Oliva
18 |
19 | import sys
20 | import click
21 | import knitlib
22 | import logging
23 | import knitpat
24 | import knitting_job
25 | # Why does this file exist, and why __main__?
26 | # For more info, read:
27 | # - https://www.python.org/dev/peps/pep-0338/
28 | # - https://docs.python.org/2/using/cmdline.html#cmdoption-m
29 | # - https://docs.python.org/3/using/cmdline.html#cmdoption-m
30 |
31 | name = "knitlib"
32 |
33 |
34 | @click.command()
35 | @click.option('--plugin_name', default="dummy", # pPluginType prompt='Name of the Machine Plugin you want.',
36 | help='The name of the Machine Plugin you want.')
37 | @click.option('--config', multiple=True, nargs=2, type=click.Tuple([unicode, unicode]), help="Arguments tuples for the "
38 | "selected plugin")
39 | @click.option('--port', type=unicode, help="Serial Port")
40 | def main(plugin_name, config, port):
41 | logging.basicConfig(level=logging.DEBUG)
42 | logging.debug("Config got as object {0}".format(config))
43 | logging.debug("Port set as {}".format(port))
44 | config_dict = dict(config)
45 | logging.debug(config_dict)
46 | knitpat_dict = knitpat.parse_dict_from_cli(config_dict)
47 | logging.debug("Knitpat object parsed as object {0}".format(knitpat_dict))
48 | # Getting the selected plugin from ID.
49 | plugin = knitlib.machine_handler.get_machine_plugin_by_id(plugin_name)
50 | logging.debug("Plugin Selected: {0}".format(plugin))
51 | if plugin is None:
52 | print("The plugin selected is not available. Available plugins are: {}".
53 | format(knitlib.machine_handler.get_active_machine_plugins_names()))
54 | return -2
55 | knitting_job_instance = knitting_job.KnittingJob(plugin, port)
56 | knitting_job_instance.init_job()
57 | knitting_job_instance.configure_job(knitpat_dict)
58 | knitting_job_instance.knit_job()
59 | return 0
60 |
61 | if __name__ == "__main__":
62 | sys.exit(main())
63 |
--------------------------------------------------------------------------------
/ci/templates/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist =
3 | clean,
4 | check,
5 | {% for env in tox_environments|sort %}
6 | {{ env }},
7 | {% endfor %}
8 | report,
9 | docs
10 |
11 | [testenv]
12 | setenv =
13 | PYTHONPATH={toxinidir}/tests
14 | PYTHONUNBUFFERED=yes
15 | deps =
16 | pytest
17 | pytest-capturelog
18 | commands =
19 | python setup.py clean --all build_ext --force --inplace
20 | {posargs:py.test -vv --ignore=src}
21 |
22 | [testenv:spell]
23 | setenv =
24 | SPELLCHECK = 1
25 | commands =
26 | sphinx-build -b spelling docs dist/docs
27 | usedevelop = true
28 | deps =
29 | -r{toxinidir}/docs/requirements.txt
30 | sphinxcontrib-spelling
31 | pyenchant
32 |
33 | [testenv:docs]
34 | whitelist_externals =
35 | rm
36 | commands =
37 | sphinx-build {posargs:-E} -b html docs dist/docs
38 | sphinx-build -b linkcheck docs dist/docs
39 | usedevelop = true
40 | deps =
41 | -r{toxinidir}/docs/requirements.txt
42 |
43 | [testenv:configure]
44 | deps =
45 | jinja2
46 | matrix
47 | usedevelop = true
48 | commands =
49 | python bootstrap.py
50 |
51 | [testenv:check]
52 | basepython = python3.4
53 | deps =
54 | docutils
55 | check-manifest
56 | flake8
57 | readme
58 | pygments
59 | usedevelop = true
60 | commands =
61 | python setup.py check --strict --metadata --restructuredtext
62 | check-manifest {toxinidir}
63 | flake8 src
64 |
65 | [testenv:coveralls]
66 | deps =
67 | coveralls
68 | usedevelop = true
69 | commands =
70 | coverage combine
71 | coverage report
72 | coveralls --merge=extension-coveralls.json
73 |
74 | [testenv:extension-coveralls]
75 | deps =
76 | cpp-coveralls
77 | usedevelop = true
78 | commands =
79 | coveralls --build-root=. --include=src --dump=extension-coveralls.json
80 |
81 | [testenv:report]
82 | basepython = python3.4
83 | commands =
84 | coverage combine
85 | coverage report
86 | usedevelop = true
87 | deps = coverage
88 |
89 | [testenv:clean]
90 | commands = coverage erase
91 | usedevelop = true
92 | deps = coverage
93 |
94 | {% for env, config in tox_environments|dictsort %}
95 | [testenv:{{ env }}]
96 | basepython = {{ config.python }}
97 | {% if config.cover or config.env_vars %}
98 | setenv =
99 | {[testenv]setenv}
100 | {% endif %}
101 | {% for var in config.env_vars %}
102 | {{ var }}
103 | {% endfor %}
104 | {% if config.cover %}
105 | WITH_COVERAGE=yes
106 | CFLAGS=-coverage
107 | usedevelop = true
108 | commands =
109 | python setup.py clean --all build_ext --force --inplace
110 | {posargs:py.test --cov=src --cov-report=term-missing -vv}
111 | {% endif %}
112 | {% if config.cover or config.deps %}
113 | deps =
114 | {[testenv]deps}
115 | {% endif %}
116 | {% if config.cover %}
117 | pytest-cov{% endif %}
118 | {% for dep in config.deps %}
119 | {{ dep }}
120 | {% endfor %}
121 |
122 | {% endfor %}
123 |
124 |
125 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | ; a generative tox configuration, see: https://testrun.org/tox/latest/config.html#generative-envlist
2 |
3 | [tox]
4 | envlist =
5 | clean,
6 | check,
7 | 2.7,
8 | ; {2.6,2.7,3.3,3.4,pypy},
9 | 2.7-nocover,
10 | ; {2.6,2.7,3.3,3.4,pypy}-nocover,
11 | report,
12 | docs
13 |
14 | [flake8]
15 | ignore = E111
16 | max-line-length = 160
17 |
18 | [testenv]
19 | basepython =
20 | {2.7,docs}: python2.7
21 | {clean,check,report,extension-coveralls,coveralls}: python2.7
22 | setenv =
23 | PYTHONPATH={toxinidir}/tests
24 | PYTHONUNBUFFERED=yes
25 | deps =
26 | pytest
27 | pytest-capturelog
28 | pytest-cov
29 | mock
30 | commands =
31 | python setup.py clean --all build_ext --force --inplace
32 | {posargs:py.test --cov=src --cov-report=term-missing -vv}
33 | usedevelop = true
34 |
35 | [testenv:spell]
36 | setenv =
37 | SPELLCHECK=1
38 | commands =
39 | sphinx-build -b spelling docs dist/docs
40 | usedevelop = true
41 | deps =
42 | -r{toxinidir}/docs/requirements.txt
43 | sphinxcontrib-spelling
44 | pyenchant
45 |
46 | [testenv:docs]
47 | whitelist_externals =
48 | rm
49 | commands =
50 | sphinx-build {posargs:-E} -b html docs dist/docs
51 | sphinx-build -b linkcheck docs dist/docs
52 | usedevelop = true
53 | deps =
54 | -r{toxinidir}/docs/requirements.txt
55 |
56 | [testenv:check]
57 | basepython = python2.7
58 | deps =
59 | docutils
60 | check-manifest
61 | flake8
62 | readme
63 | pygments
64 | usedevelop = true
65 | commands =
66 | python setup.py check --strict --metadata --restructuredtext
67 | check-manifest {toxinidir}
68 | flake8 src
69 |
70 | [testenv:coveralls]
71 | deps =
72 | coveralls
73 | usedevelop = true
74 | commands =
75 | coverage combine
76 | coverage report
77 | coveralls
78 |
79 | [testenv:extension-coveralls]
80 | deps =
81 | cpp-coveralls
82 | usedevelop = true
83 | commands =
84 | coveralls --build-root=. --include=src --dump=extension-coveralls.json
85 | [testenv:report]
86 | basepython = python2.7
87 | commands =
88 | coverage combine
89 | coverage report
90 | usedevelop = true
91 | deps = coverage
92 |
93 | [testenv:clean]
94 | commands = coverage erase
95 | usedevelop = true
96 | deps = coverage
97 |
98 |
99 | [testenv:2.7-nocover]
100 | commands =
101 | python setup.py clean --all build_ext --force
102 | {posargs:py.test -vv --ignore=src}
103 | usedevelop = false
104 |
105 | [testenv:3.3-nocover]
106 | commands =
107 | python setup.py clean --all build_ext --force
108 | {posargs:py.test -vv --ignore=src}
109 | usedevelop = false
110 |
111 | [testenv:3.4-nocover]
112 | commands =
113 | python setup.py clean --all build_ext --force
114 | {posargs:py.test -vv --ignore=src}
115 | usedevelop = false
116 |
117 | [testenv:pypy-nocover]
118 | commands =
119 | python setup.py clean --all build_ext --force
120 | {posargs:py.test -vv --ignore=src}
121 | usedevelop = false
122 |
123 |
--------------------------------------------------------------------------------
/tests/test_knitting_plugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of Knitlib.
3 | #
4 | # Knitlib is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # Knitlib is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with Knitlib. If not, see .
16 | #
17 | # Copyright 2015 Sebastian Oliva
18 | import pytest
19 |
20 | import knitlib
21 | from knitlib import machine_handler
22 | from knitlib.plugins import knitting_plugin
23 | from knitlib.plugins import dummy_plugin
24 |
25 |
26 | def test_methods_exception():
27 | """Tests that abstract methods from BaseKnittingPlugin throw exceptions.
28 |
29 | All methods from BaseKnittingPlugin should fail. Methods from this class
30 | are not implemented, as this is a base, abstract class."""
31 |
32 | knit_machine = knitting_plugin.BaseKnittingPlugin()
33 | with pytest.raises(NotImplementedError):
34 | knit_machine.configure()
35 | with pytest.raises(NotImplementedError):
36 | knit_machine.knit()
37 | with pytest.raises(NotImplementedError):
38 | knit_machine.finish()
39 |
40 | def test_dummy_plugin():
41 | """Tests that dummy plugin flows as expected in ideal conditions."""
42 | knit_machine = dummy_plugin.DummyKnittingPlugin()
43 | knit_machine.configure(None)
44 | knit_machine.knit()
45 | knit_machine.finish()
46 |
47 | def test_machine_handler_get_machines():
48 | for machine_type in list(machine_handler.get_machine_types()):
49 | assert machine_handler.get_machines_by_type(machine_type) == knitlib.plugins.active_plugins.get(machine_type, {})
50 |
51 |
52 | def test_machine_handler_get_machine_by_id():
53 | machine = knitlib.machine_handler.get_machine_plugin_by_id(dummy_plugin.DummyKnittingPlugin.__PLUGIN_NAME__)
54 | assert machine.__name__ == "DummyKnittingPlugin"
55 |
56 | def test_dummy_machine():
57 |
58 | mach_type = knitlib.machine_handler.get_machine_types().other
59 | other_type_dict = knitlib.machine_handler.get_machines_by_type(mach_type)
60 | assert type(other_type_dict) is dict
61 | dummy_type = other_type_dict["dummy"]()
62 |
63 | dummy_type.configure(None)
64 | dummy_type.knit()
65 | dummy_type.finish()
66 |
67 |
68 | def test_ayab_plugin():
69 | pass
70 | # machine = knitlib.machine_handler.get_machine_plugin_by_id("AYAB")()
71 |
72 | #dummy_type.configure(None)
73 | #dummy_type.knit() # https://bitbucket.org/chris007de/ayab-apparat/wiki/english/Hardware#!nomachine-development-mode
74 | #if dummy_type.isstate("finished"):
75 | # dummy_type.finish()+
76 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/pdd_plugin/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'tian'
2 |
3 | import os
4 | import shutil
5 | # import fs
6 | import sys
7 | import logging
8 | import tempfile
9 | import PDDemulate
10 | from PDDemulate import PDDemulator
11 | from knitlib.plugins.knitting_plugin import BaseKnittingPlugin
12 | import knitlib.exceptions
13 |
14 |
15 | class PDDEmulationKnittingPlugin(BaseKnittingPlugin):
16 |
17 | __PLUGIN_NAME__ = u"PDD"
18 |
19 | def __init__(self, callbacks_dict=None, interactive_callbacks=None):
20 | super(PDDEmulationKnittingPlugin, self).__init__(callbacks_dict, interactive_callbacks)
21 | logging.debug("Loaded PDD emulator version: {}".format(PDDemulate.version))
22 | self.__port = ""
23 | self.__conf = None
24 | self.__main_temp_dir = tempfile.gettempdir()
25 | self.__folder = os.path.join(self.__main_temp_dir, "PDD_tmp_dir")
26 | self.__emu = None
27 | # TODO: init emulator
28 |
29 | def register_interactive_callbacks(self, callbacks=None):
30 | super(PDDEmulationKnittingPlugin, self).register_interactive_callbacks(callbacks)
31 |
32 | def set_port(self, portname):
33 | self.__port = portname
34 |
35 | def onconfigure(self, e):
36 | if hasattr(e, "conf") and e.conf is not None:
37 | self.__conf = e.conf
38 | else:
39 | raise knitlib.exceptions.InputException("Conf dict is missing")
40 |
41 | conf = self.__conf
42 | if self.__port is "":
43 | self.__port = conf.get("port", "/dev/ttyUSB0")
44 |
45 | try:
46 | os.mkdir(self.__folder)
47 | except OSError:
48 | # Exception thrown meaning the folder already exists
49 | logging.warning("PDD temp folder already exists")
50 |
51 | # Copying the image file to temp folder.
52 | try:
53 | shutil.copy(conf.get("file_url"), self.__folder)
54 | except IOError:
55 | logging.error("Error when copying file url")
56 |
57 | self.__emu = PDDemulator(self.__folder)
58 |
59 | def onknit(self, e):
60 | self.__emu.open(cport=self.__port)
61 | logging.info("PDD Emulator Ready")
62 | self.__emu.handleRequests()
63 |
64 | def validate_configuration(self, conf):
65 | # TODO validate formally
66 | return True
67 |
68 | def onfinish(self, e):
69 | # TODO: remove and cleanup dir at self.__folder
70 | self.__emu.close()
71 |
72 | @staticmethod
73 | def supported_config_features():
74 | return {"$schema": "http://json-schema.org/schema#", "type": "object"}
75 |
76 |
77 | if __name__ == "__main__":
78 | if len(sys.argv) < 3:
79 | print 'Usage: %s basedir serialdevice' % sys.argv[0]
80 | sys.exit()
81 |
82 | print 'Preparing . . . Please Wait'
83 | emu = PDDemulator(sys.argv[1])
84 |
85 | # TODO: fix usb port hardcoding
86 | emu.open(cport=sys.argv[2])
87 |
88 | print 'Emulator Ready!'
89 | try:
90 | while 1:
91 | emu.handleRequests()
92 | except (KeyboardInterrupt):
93 | pass
94 |
95 | emu.close()
96 |
--------------------------------------------------------------------------------
/ci/appveyor-bootstrap.ps1:
--------------------------------------------------------------------------------
1 | # Source: https://github.com/pypa/python-packaging-user-guide/blob/master/source/code/install.ps1
2 | # Sample script to install Python and pip under Windows
3 | # Authors: Olivier Grisel and Kyle Kastner
4 | # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
5 |
6 | $BASE_URL = "https://www.python.org/ftp/python/"
7 | $GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
8 | $GET_PIP_PATH = "C:\get-pip.py"
9 |
10 |
11 | function DownloadPython ($python_version, $platform_suffix) {
12 | $webclient = New-Object System.Net.WebClient
13 | $filename = "python-" + $python_version + $platform_suffix + ".msi"
14 | $url = $BASE_URL + $python_version + "/" + $filename
15 |
16 | $basedir = $pwd.Path + "\"
17 | $filepath = $basedir + $filename
18 | if (Test-Path $filename) {
19 | Write-Host "Reusing" $filepath
20 | return $filepath
21 | }
22 |
23 | # Download and retry up to 5 times in case of network transient errors.
24 | Write-Host "Downloading" $filename "from" $url
25 | $retry_attempts = 3
26 | for($i=0; $i -lt $retry_attempts; $i++){
27 | try {
28 | $webclient.DownloadFile($url, $filepath)
29 | break
30 | }
31 | Catch [Exception]{
32 | Start-Sleep 1
33 | }
34 | }
35 | Write-Host "File saved at" $filepath
36 | return $filepath
37 | }
38 |
39 |
40 | function InstallPython ($python_version, $architecture, $python_home) {
41 | Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
42 | if (Test-Path $python_home) {
43 | Write-Host $python_home "already exists, skipping."
44 | return $false
45 | }
46 | if ($architecture -eq "32") {
47 | $platform_suffix = ""
48 | } else {
49 | $platform_suffix = ".amd64"
50 | }
51 | $filepath = DownloadPython $python_version $platform_suffix
52 | Write-Host "Installing" $filepath "to" $python_home
53 | $args = "/qn /i $filepath TARGETDIR=$python_home"
54 | Write-Host "msiexec.exe" $args
55 | Start-Process -FilePath "msiexec.exe" -ArgumentList $args -Wait -Passthru
56 | Write-Host "Python $python_version ($architecture) installation complete"
57 | return $true
58 | }
59 |
60 |
61 | function InstallPip ($python_home) {
62 | $pip_path = $python_home + "/Scripts/pip.exe"
63 | $python_path = $python_home + "/python.exe"
64 | if (-not(Test-Path $pip_path)) {
65 | Write-Host "Installing pip..."
66 | $webclient = New-Object System.Net.WebClient
67 | $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH)
68 | Write-Host "Executing:" $python_path $GET_PIP_PATH
69 | Start-Process -FilePath "$python_path" -ArgumentList "$GET_PIP_PATH" -Wait -Passthru
70 | } else {
71 | Write-Host "pip already installed."
72 | }
73 | }
74 |
75 | function InstallPackage ($python_home, $pkg) {
76 | $pip_path = $python_home + "/Scripts/pip.exe"
77 | & $pip_path install $pkg
78 | }
79 |
80 | function main () {
81 | InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON_HOME
82 | InstallPip $env:PYTHON_HOME
83 | InstallPackage $env:PYTHON_HOME setuptools
84 | InstallPackage $env:PYTHON_HOME wheel
85 | InstallPackage $env:PYTHON_HOME tox
86 | }
87 |
88 | main
89 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{branch}-{build}'
2 | build: off
3 | environment:
4 | global:
5 | WITH_COMPILER: "cmd /E:ON /V:ON /C .\\ci\\appveyor-with-compiler.cmd"
6 | matrix:
7 | - TOXENV: check
8 | PYTHON_HOME: "C:\\Python27"
9 | PYTHON_VERSION: "2.7"
10 | PYTHON_ARCH: "32"
11 | - TOXENV: "2.7"
12 | TOXPYTHON: "C:\\Python27\\python.exe"
13 | WINDOWS_SDK_VERSION: "v7.0"
14 | PYTHON_HOME: "C:\\Python27"
15 | PYTHON_VERSION: "2.7"
16 | PYTHON_ARCH: "32"
17 | - TOXENV: "2.7"
18 | TOXPYTHON: "C:\\Python27-x64\\python.exe"
19 | WINDOWS_SDK_VERSION: "v7.0"
20 | PYTHON_HOME: "C:\\Python27-x64"
21 | PYTHON_VERSION: "2.7"
22 | PYTHON_ARCH: "64"
23 | - TOXENV: "2.7-nocover"
24 | TOXPYTHON: "C:\\Python27\\python.exe"
25 | WINDOWS_SDK_VERSION: "v7.0"
26 | PYTHON_HOME: "C:\\Python27"
27 | PYTHON_VERSION: "2.7"
28 | PYTHON_ARCH: "32"
29 | - TOXENV: "2.7-nocover"
30 | TOXPYTHON: "C:\\Python27-x64\\python.exe"
31 | WINDOWS_SDK_VERSION: "v7.0"
32 | PYTHON_HOME: "C:\\Python27-x64"
33 | PYTHON_VERSION: "2.7"
34 | PYTHON_ARCH: "64"
35 | - TOXENV: "3.3"
36 | TOXPYTHON: "C:\\Python33\\python.exe"
37 | WINDOWS_SDK_VERSION: "v7.1"
38 | PYTHON_HOME: "C:\\Python33"
39 | PYTHON_VERSION: "3.3"
40 | PYTHON_ARCH: "32"
41 | - TOXENV: "3.3"
42 | TOXPYTHON: "C:\\Python33-x64\\python.exe"
43 | WINDOWS_SDK_VERSION: "v7.1"
44 | PYTHON_HOME: "C:\\Python33-x64"
45 | PYTHON_VERSION: "3.3"
46 | PYTHON_ARCH: "64"
47 | - TOXENV: "3.3-nocover"
48 | TOXPYTHON: "C:\\Python33\\python.exe"
49 | WINDOWS_SDK_VERSION: "v7.1"
50 | PYTHON_HOME: "C:\\Python33"
51 | PYTHON_VERSION: "3.3"
52 | PYTHON_ARCH: "32"
53 | - TOXENV: "3.3-nocover"
54 | TOXPYTHON: "C:\\Python33-x64\\python.exe"
55 | WINDOWS_SDK_VERSION: "v7.1"
56 | PYTHON_HOME: "C:\\Python33-x64"
57 | PYTHON_VERSION: "3.3"
58 | PYTHON_ARCH: "64"
59 | - TOXENV: "3.4"
60 | TOXPYTHON: "C:\\Python34\\python.exe"
61 | WINDOWS_SDK_VERSION: "v7.1"
62 | PYTHON_HOME: "C:\\Python34"
63 | PYTHON_VERSION: "3.4"
64 | PYTHON_ARCH: "32"
65 | - TOXENV: "3.4"
66 | TOXPYTHON: "C:\\Python34-x64\\python.exe"
67 | WINDOWS_SDK_VERSION: "v7.1"
68 | PYTHON_HOME: "C:\\Python34-x64"
69 | PYTHON_VERSION: "3.4"
70 | PYTHON_ARCH: "64"
71 | - TOXENV: "3.4-nocover"
72 | TOXPYTHON: "C:\\Python34\\python.exe"
73 | WINDOWS_SDK_VERSION: "v7.1"
74 | PYTHON_HOME: "C:\\Python34"
75 | PYTHON_VERSION: "3.4"
76 | PYTHON_ARCH: "32"
77 | - TOXENV: "3.4-nocover"
78 | TOXPYTHON: "C:\\Python34-x64\\python.exe"
79 | WINDOWS_SDK_VERSION: "v7.1"
80 | PYTHON_HOME: "C:\\Python34-x64"
81 | PYTHON_VERSION: "3.4"
82 | PYTHON_ARCH: "64"
83 | init:
84 | - "ECHO %TOXENV%"
85 | - ps: "ls C:\\Python*"
86 | install:
87 | - "powershell ci\\appveyor-bootstrap.ps1"
88 | test_script:
89 | - "%PYTHON_HOME%\\Scripts\\tox --version"
90 | - "%PYTHON_HOME%\\Scripts\\virtualenv --version"
91 | - "%PYTHON_HOME%\\Scripts\\pip --version"
92 | - "%WITH_COMPILER% %PYTHON_HOME%\\Scripts\\tox"
93 | after_test:
94 | - "IF \"%TOXENV:~-8,8%\" == \"-nocover\" %WITH_COMPILER% %TOXPYTHON% setup.py bdist_wheel"
95 | artifacts:
96 | - path: dist\*
97 |
--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Contributing
3 | ============
4 |
5 | Contributions are welcome, and they are greatly appreciated! Every
6 | little bit helps, and credit will always be given.
7 |
8 | Bug reports
9 | ===========
10 |
11 | When `reporting a bug `_ please include:
12 |
13 | * Your operating system name and version.
14 | * Any details about your local setup that might be helpful in troubleshooting.
15 | * Detailed steps to reproduce the bug.
16 |
17 | Documentation improvements
18 | ==========================
19 |
20 | knitlib could always use more documentation, whether as part of the
21 | official knitlib docs, in docstrings, or even on the web in blog posts,
22 | articles, and such.
23 |
24 | Feature requests, bug reports, and feedback
25 | ===========================================
26 |
27 | The best way to send feedback is to file an issue at https://github.com/fashiontec/knitlib/issues.
28 |
29 | If you are proposing a feature:
30 |
31 | * Explain in detail how it would work.
32 | * Keep the scope as narrow as possible, to make it easier to implement.
33 | * Remember that this is a volunteer-driven project, and that contributions are welcome :)
34 |
35 | Development
36 | ===========
37 |
38 | To set up `knitlib` for local development:
39 |
40 | 1. `Fork knitlib on GitHub `_.
41 | 2. Clone your fork locally::
42 |
43 | git clone git@github.com:your_name_here/knitlib.git
44 |
45 | 3. Create a branch for local development::
46 |
47 | git checkout -b name-of-your-bugfix-or-feature
48 |
49 | Now you can make your changes locally.
50 |
51 | 4. When you're done making changes, run all the checks, doc builder and spell checker with `tox `_ one command::
52 |
53 | tox
54 |
55 | 5. Commit your changes and push your branch to GitHub::
56 |
57 | git add .
58 | git commit -m "Your detailed description of your changes."
59 | git push origin name-of-your-bugfix-or-feature
60 |
61 | 6. Submit a pull request through the GitHub website.
62 | 7. Assign some one to review the code, somebody who is familiar with the architecture. Or some of the other students involved.
63 | 8. Handle the code changes required by the reviewer, discuss alternative ways etc.
64 | 9. Clean up, reduce commits for the entire stuff done via the PR (Squashing Commits - http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html)
65 | 11. Ensure it runs tests.
66 | 10. Once the PR (Pull Request) is green (reviewer says OK or LGTM), merge the PR and delete the branch.
67 |
68 | Pull Request Guidelines
69 | -----------------------
70 |
71 | If you need some code review or feedback while you're developing the code just make the pull request.
72 |
73 | For merging, you should:
74 |
75 | 1. Include passing tests (run ``tox``) [1]_.
76 | 2. Update documentation when there's new API, functionality etc.
77 | 3. Add a note to ``CHANGELOG.rst`` about the changes.
78 | 4. Add yourself to ``AUTHORS.rst``.
79 |
80 | .. [1] If you don't have all the necessary python versions available locally you can rely on Travis - it will
81 | `run the tests `_ for each change you add in the pull request.
82 |
83 | It will be slower though ...
84 |
85 | Tips
86 | ----
87 |
88 | * To run a subset of tests::
89 |
90 | tox -e envname -- py.test -k test_myfeature
91 |
92 | * To run all the test environments in *parallel* (you need to ``pip install detox``)::
93 |
94 | detox
95 |
96 | * Please check the `Google Python coding guidelines `_, it has good advices in order to write properly structured code.
97 |
--------------------------------------------------------------------------------
/tests/test_ayab_communication.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of AYAB.
3 | #
4 | # AYAB is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # AYAB is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with AYAB. If not, see .
16 | #
17 | # Copyright 2015 Shiluka Dharmasena, Sebastian Oliva, Christian Obersteiner, Andreas Müller
18 | # https://bitbucket.org/chris007de/ayab-apparat/
19 |
20 | import pytest
21 | import serial
22 | import unittest
23 | from knitlib.exceptions import CommunicationException
24 | from knitlib.plugins.ayab_plugin.ayab_communication import AyabCommunication
25 | from mock import patch
26 |
27 | class TestCommunication(unittest.TestCase):
28 |
29 | def setUp(self):
30 | self.dummy_serial = serial.serial_for_url("loop://logging=debug")
31 | self.comm_dummy = AyabCommunication(self.dummy_serial)
32 |
33 | def test_close_serial(self):
34 | """Test on closing serial port communication."""
35 | before = self.dummy_serial.isOpen()
36 | assert before
37 | self.comm_dummy.close_serial()
38 | after = self.dummy_serial.isOpen()
39 | assert after == False
40 |
41 | def test_open_serial(self):
42 | """Test on opening serial port communication with a baudrate 115200."""
43 | with patch.object(serial,'Serial') as mock_method:
44 | mock_method.return_value = object()
45 | self.ayabCom = AyabCommunication()
46 | openStatus = self.ayabCom.open_serial('dummyPortname')
47 | assert openStatus
48 | mock_method.assert_called_once_with('dummyPortname',115200)
49 |
50 | with patch.object(serial,'Serial') as mock_method:
51 | with pytest.raises(CommunicationException) as excinfo:
52 | mock_method.side_effect = serial.SerialException()
53 | self.ayabCom = AyabCommunication()
54 | openStatus = self.ayabCom.open_serial('dummyPortname')
55 | mock_method.assert_called_once_with('dummyPortname',115200)
56 |
57 | def test_req_start(self):
58 | """Test on sending a start message to the controller."""
59 | start_val, end_val = 0, 10
60 | self.comm_dummy.req_start(start_val, end_val)
61 | byte_array = bytearray([0x01, start_val, end_val, 0x0a, 0x0d])
62 | bytes_read = self.dummy_serial.read(len(byte_array))
63 | self.assertEqual(bytes_read, byte_array)
64 |
65 | def test_req_info(self):
66 | """Test on Sending a request for information to controller."""
67 | self.comm_dummy.req_info()
68 | byte_array = bytearray([0x03, 0x0a, 0x0d])
69 | bytes_read = self.dummy_serial.read(len(byte_array))
70 | assert bytes_read == byte_array
71 |
72 | def test_cnf_line(self):
73 | """Test on sending a line of data via the serial port."""
74 | lineNumber = 13
75 | lineData = chr(0xAB)
76 | flags = 0x12
77 | crc8 = 0x57
78 | self.comm_dummy.cnf_line(lineNumber, lineData, flags, crc8)
79 | byte_array = bytearray([0x42, lineNumber, lineData, flags, crc8, 0x0a, 0x0d])
80 | bytes_read = self.dummy_serial.read(len(byte_array))
81 | assert bytes_read == byte_array
82 |
83 | def test_read_line(self):
84 | """Test on reading a line from serial communication."""
85 | byte_start_val = 0x01
86 | byte_end_val = 0x12
87 | byte_array = bytearray([0xC1, byte_start_val, byte_end_val, 0x0a, 0x0d])
88 | bytes_wrote = self.dummy_serial.write(byte_array)
89 | bytes_read = self.comm_dummy.read_line()
90 | assert bytes_read == byte_array
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- encoding: utf-8 -*-
3 | from __future__ import absolute_import, print_function
4 |
5 | import io
6 | import os
7 | import re
8 | from glob import glob
9 | from os.path import basename
10 | from os.path import dirname
11 | from os.path import join
12 | from os.path import relpath
13 | from os.path import splitext
14 |
15 | from setuptools import find_packages
16 | from setuptools import setup
17 | from setuptools.command.build_ext import build_ext
18 | from distutils.core import Extension
19 | from distutils.errors import CCompilerError
20 | from distutils.errors import CompileError
21 | from distutils.errors import DistutilsExecError
22 | from distutils.errors import DistutilsPlatformError
23 |
24 | def read(*names, **kwargs):
25 | return io.open(
26 | join(dirname(__file__), *names),
27 | encoding=kwargs.get('encoding', 'utf8')
28 | ).read()
29 |
30 |
31 | class optional_build_ext(build_ext):
32 | '''Allow the building of C extensions to fail.'''
33 | def run(self):
34 | try:
35 | build_ext.run(self)
36 | except DistutilsPlatformError as e:
37 | self._unavailable(e)
38 | self.extensions = [] # avoid copying missing files (it would fail).
39 |
40 | def build_extension(self, ext):
41 | try:
42 | build_ext.build_extension(self, ext)
43 | except (CCompilerError, CompileError, DistutilsExecError) as e:
44 | self._unavailable(e)
45 | self.extensions = [] # avoid copying missing files (it would fail).
46 |
47 | def _unavailable(self, e):
48 | print('*' * 80)
49 | print('''WARNING:
50 |
51 | An optional code optimization (C extension) could not be compiled.
52 |
53 | Optimizations for this package will not be available!
54 | ''')
55 |
56 | print('CAUSE:')
57 | print('')
58 | print(' ' + repr(e))
59 | print('*' * 80)
60 |
61 |
62 | setup(
63 | name='knitlib',
64 | version='0.0.1',
65 | license='GPLv3',
66 | description='A library designed to support varied knitting machines.',
67 | long_description='%s\n%s' % (read('README.rst'), re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst'))),
68 | author='Sebastian Oliva , Shiluka Dharmasena',
69 | author_email='code@sebastianoliva.com , shiluka@gmail.com',
70 | url='https://github.com/fashiontec/knitlib',
71 | packages=find_packages('src'),
72 | package_dir={'': 'src'},
73 | py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')],
74 | include_package_data=True,
75 | zip_safe=False,
76 | classifiers=[
77 | # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers
78 | 'Development Status :: 5 - Production/Stable',
79 | 'Intended Audience :: Developers',
80 | 'License :: OSI Approved :: BSD License',
81 | 'Operating System :: Unix',
82 | 'Operating System :: POSIX',
83 | 'Operating System :: Microsoft :: Windows',
84 | 'Programming Language :: Python',
85 | 'Programming Language :: Python :: 2.6',
86 | 'Programming Language :: Python :: 2.7',
87 | 'Programming Language :: Python :: 3',
88 | 'Programming Language :: Python :: 3.3',
89 | 'Programming Language :: Python :: 3.4',
90 | 'Programming Language :: Python :: Implementation :: CPython',
91 | 'Programming Language :: Python :: Implementation :: PyPy',
92 | 'Topic :: Utilities',
93 | ],
94 | keywords=[
95 | # eg: 'keyword1', 'keyword2', 'keyword3',
96 | ],
97 | install_requires=[
98 | 'click',
99 | 'fysom',
100 | 'pyserial',
101 | 'enum34',
102 | 'Pillow',
103 | 'jsonschema',
104 | 'fs'
105 | ],
106 | extras_require={
107 | # eg: 'rst': ['docutils>=0.11'],
108 | },
109 | entry_points={
110 | 'console_scripts': [
111 | 'knitlib = knitlib.__main__:main',
112 | ]
113 | },
114 | cmdclass={'build_ext': optional_build_ext},
115 | ext_modules=[
116 | Extension(
117 | splitext(relpath(path, 'src').replace(os.sep, '.'))[0],
118 | sources=[path],
119 | include_dirs=[dirname(path)]
120 | )
121 | for root, _, _ in os.walk('src')
122 | for path in glob(join(root, '*.c'))
123 | ]
124 | )
125 |
--------------------------------------------------------------------------------
/src/knitlib/exceptions.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of Knitlib.
3 | #
4 | # Knitlib is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # Knitlib is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with Knitlib. If not, see .
16 | #
17 | # Copyright 2015 Sebastian Oliva, Shiluka Dharmasena
18 |
19 |
20 | class KnittingError(Exception):
21 | """Base class for exceptions in this module."""
22 | def __init__(self, expression="", message=""):
23 | self.expression = expression
24 | self.message = message
25 |
26 |
27 | class InputException(KnittingError):
28 | """Exception raised for errors in the input.
29 | Attributes:
30 | expression -- input expression in which
31 | the error occurred
32 | message -- explanation of the error
33 | """
34 | pass
35 |
36 |
37 | class CommunicationException(KnittingError):
38 | """Exception raised for errors in the communication.
39 | Attributes:
40 | expression -- communication expression in which
41 | the error occurred
42 | message -- explanation of the error
43 | """
44 | pass
45 |
46 |
47 | class PatternNotFoundException(KnittingError):
48 | """Exception raised for errors in the pattern.
49 | Attributes:
50 | expression -- pattern expression in which
51 | the error occurred
52 | message -- explanation of the error
53 | """
54 | pass
55 |
56 |
57 | class InsertPatternException(KnittingError):
58 | """Exception raised for errors in the insert of the pattern.
59 | Attributes:
60 | expression -- insert pattern expression in which
61 | the error occurred
62 | message -- explanation of the error
63 | """
64 | pass
65 |
66 |
67 | class SendDataException(KnittingError):
68 | """Exception raised for errors in sending data to the serial port.
69 | Attributes:
70 | expression -- send data expression in which
71 | the error occurred
72 | message -- explanation of the error
73 | """
74 | pass
75 |
76 |
77 | class OpenSerialException(KnittingError):
78 | """Exception raised for errors in opening the serial port.
79 | Attributes:
80 | expression -- open serial port expression in which
81 | the error occurred
82 | message -- explanation of the error
83 | """
84 | pass
85 |
86 |
87 | class CloseSerialException(KnittingError):
88 | """Exception raised for errors in closing the serial port.
89 | Attributes:
90 | expression -- close serial port expression in which
91 | the error occurred
92 | message -- explanation of the error
93 | """
94 | pass
95 |
96 |
97 | class ReadLineException(KnittingError):
98 | """Exception raised for errors in reading lines.
99 | Attributes:
100 | expression -- send data expression in which
101 | the error occurred
102 | message -- explanation of the error
103 | """
104 | pass
105 |
106 |
107 | class WriteLineException(KnittingError):
108 | """Exception raised for errors in writing lines.
109 | Attributes:
110 | expression -- write lines expression in which
111 | the error occurred
112 | message -- explanation of the error
113 | """
114 | pass
115 |
116 |
117 | class ReadByteException(KnittingError):
118 | """Exception raised for errors in reading bytes.
119 | Attributes:
120 | expression -- read data as bytes expression in which
121 | the error occurred
122 | message -- explanation of the error
123 | """
124 | pass
125 |
126 |
127 | class WriteByteException(KnittingError):
128 | """Exception raised for errors in writing bytes.
129 | Attributes:
130 | expression -- write data as byte expression in which
131 | the error occurred
132 | message -- explanation of the error
133 | """
134 | pass
135 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/ayab_plugin/ayab_communication.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of AYAB.
3 | #
4 | # AYAB is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # AYAB is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with AYAB. If not, see .
16 | #
17 | # Copyright 2013 Christian Obersteiner, Andreas Müller
18 | # https://bitbucket.org/chris007de/ayab-apparat/
19 |
20 | """Handles the serial communication protocol.
21 |
22 | This module handles serial communication, currently works in a synchronous way.
23 | AyabCommunication uses an internal PySerial.Serial object to connect to the device.
24 | The initializer can also be overriden with a dummy serial object.
25 | """
26 |
27 | import time
28 | import serial
29 |
30 | import logging
31 |
32 | from knitlib.exceptions import CommunicationException
33 |
34 |
35 | class AyabCommunication(object):
36 | """Class Handling the serial communication protocol."""
37 |
38 | def __init__(self, serial=None):
39 | """Creates an AyabCommunication object, with an optional serial-like object."""
40 | logging.basicConfig(level=logging.DEBUG)
41 | self.__logger = logging.getLogger(__name__)
42 | self.__ser = serial
43 |
44 | def __del__(self):
45 | """Handles on delete behaviour closing serial port object."""
46 | self.close_serial()
47 |
48 | def open_serial(self, serial_port_name=None, serial_port_speed=115200):
49 | """Opens serial port communication with a portName."""
50 | if not self.__ser:
51 | self.__portname = serial_port_name
52 | try:
53 | self.__ser = serial.Serial(self.__portname, serial_port_speed)
54 | time.sleep(1)
55 | except:
56 | self.__logger.error("could not open serial port " + self.__portname)
57 | raise CommunicationException()
58 | return True
59 | else:
60 | self.__logger.error("could not open serial port ")
61 | raise CommunicationException()
62 |
63 | def close_serial(self):
64 | """Closes serial port."""
65 | try:
66 | self.__ser.close()
67 | del(self.__ser)
68 | self.__ser = None
69 | except:
70 | # TODO: add message for closing serial failure.
71 | raise CommunicationException()
72 |
73 | def read_line(self):
74 | """Reads a line from serial communication."""
75 | line = bytes()
76 | if self.__ser:
77 | while self.__ser.inWaiting() > 0:
78 | line += self.__ser.read(1)
79 | return line
80 |
81 | def write_line(self, line):
82 | """Writes a line """
83 | if self.__ser:
84 | while line.length > 0:
85 | self.__ser.write(line)
86 | return line
87 |
88 | def write_byte(self, byte):
89 | """Writes a byte"""
90 | if self.__ser:
91 | while byte.length > 0:
92 | self.__ser.write(byte)
93 | return byte
94 |
95 | def req_start(self, startNeedle, stopNeedle):
96 | """Sends a start message to the controller."""
97 |
98 | msg = chr(0x01) # msg id
99 | msg += chr(int(startNeedle))
100 | msg += chr(int(stopNeedle))
101 | # print "< reqStart"
102 | self.__ser.write(msg + '\n\r')
103 |
104 | def req_info(self):
105 | """Sends a request for information to controller."""
106 | # print "< reqInfo"
107 | self.__ser.write(chr(0x03) + '\n\r')
108 |
109 | def cnf_line(self, lineNumber, lineData, flags, crc8):
110 | """Sends a line of data via the serial port.
111 |
112 | Sends a line of data to the serial port, all arguments are mandatory.
113 | The data sent here is parsed by the Arduino controller which sets the
114 | knitting needles accordingly.
115 |
116 | Args:
117 | lineNumber (int): The line number to be sent.
118 | lineData (bytes): The bytearray to be sent to needles.
119 | flags (bytes): The flags sent to the controller.
120 | crc8 (bytes, optional): The CRC-8 checksum for transmission.
121 |
122 | """
123 |
124 | msg = chr(0x42) # msg id
125 | msg += chr(lineNumber) # line number
126 | msg += lineData # line data
127 | msg += chr(flags) # flags
128 | msg += chr(crc8) # crc8
129 | # print "< cnfLine"
130 | # print lineData
131 | self.__ser.write(msg + '\n\r')
132 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 |
2 | ===============================
3 | knitlib
4 | ===============================
5 |
6 | .. | |docs| |travis| |appveyor| |coveralls| |landscape| |scrutinizer|
7 | .. | |version| |downloads| |wheel| |supported-versions| |supported-implementations|
8 |
9 | | |travis| |docs| |downloads| |scrutinizer|
10 |
11 |
12 | .. image:: https://badge.waffle.io/fashiontec/knitlib.png?label=ready&title=Ready
13 | :target: https://waffle.io/fashiontec/knitlib
14 | :alt: 'Stories in Ready'
15 |
16 | .. |docs| image:: https://readthedocs.org/projects/knitlib/badge/?style=flat
17 | :target: https://readthedocs.org/projects/knitlib
18 | :alt: Documentation Status
19 |
20 | .. |travis| image:: http://img.shields.io/travis/fashiontec/knitlib/master.png?style=flat
21 | :alt: Travis-CI Build Status
22 | :target: https://travis-ci.org/fashiontec/knitlib
23 |
24 | .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/tian2992/knitlib?branch=master
25 | :alt: AppVeyor Build Status
26 | :target: https://ci.appveyor.com/project/tian2992/knitlib
27 |
28 | .. |coveralls| image:: http://img.shields.io/coveralls/tian2992/knitlib/master.png?style=flat
29 | :alt: Coverage Status
30 | :target: https://coveralls.io/r/tian2992/knitlib
31 |
32 | .. |landscape| image:: https://landscape.io/github/tian2992/knitlib/master/landscape.svg?style=flat
33 | :target: https://landscape.io/github/tian2992/knitlib/master
34 | :alt: Code Quality Status
35 |
36 | .. |version| image:: http://img.shields.io/pypi/v/knitlib.png?style=flat
37 | :alt: PyPI Package latest release
38 | :target: https://pypi.python.org/pypi/knitlib
39 |
40 | .. |downloads| image:: http://img.shields.io/pypi/dm/knitlib.png?style=flat
41 | :alt: PyPI Package monthly downloads
42 | :target: https://pypi.python.org/pypi/knitlib
43 |
44 | .. |wheel| image:: https://pypip.in/wheel/knitlib/badge.png?style=flat
45 | :alt: PyPI Wheel
46 | :target: https://pypi.python.org/pypi/knitlib
47 |
48 | .. |supported-versions| image:: https://pypip.in/py_versions/knitlib/badge.png?style=flat
49 | :alt: Supported versions
50 | :target: https://pypi.python.org/pypi/knitlib
51 |
52 | .. |supported-implementations| image:: https://pypip.in/implementation/knitlib/badge.png?style=flat
53 | :alt: Supported imlementations
54 | :target: https://pypi.python.org/pypi/knitlib
55 |
56 | .. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/tian2992/knitlib/master.png?style=flat
57 | :alt: Scrutinizer Status
58 | :target: https://scrutinizer-ci.com/g/tian2992/knitlib/
59 |
60 |
61 | ==================
62 | 1. What is Knitlib
63 | ==================
64 | Knitlib is a library designed to support the operation of varied knitting machines, mechanisms, and hacks. Knitlib is based on projects like AYAB, PDD, and KnitterStream to control knitting machines. Knitlib features a plugin system for knitting machines and implements an API to control machines' operation, knitting jobs and knitting patterns. The software is based on Python. There also is a Web API. Among the primary tasks is to develop plugins based on this solution to add support for more machines.
65 |
66 | 1.1 Idea behind Knitlib
67 | -----------------------
68 |
69 | 1.2 Technical Background of Knitlib
70 | -----------------------------------
71 |
72 | Knitlib is implemented as a Python Library and API. Each machine is supported via a Plugin, allowing for extensibility. Each of the plugins is based on a simple Finite State Machine, with states from machine initialization to operation and knitting process. Among the aplications using the Knitlib API is Knitlib-server, implementing a Webserver and REST / WebSocket endpoints. Clients can also implement message callbacks, errors, notifications and blocking messages in order to provide a good user experience.
73 |
74 | ==========================
75 | 2.Development Installation
76 | ==========================
77 |
78 | pip install -r requirements.txt
79 | pip install knitlib
80 |
81 | ========
82 | 3. Usage
83 | ========
84 |
85 | ==============
86 | 4. Development
87 | ==============
88 |
89 | To run the all tests run::
90 |
91 | tox
92 |
93 | =============
94 | 5. References
95 | =============
96 |
97 | ================
98 | 6. Documentation
99 | ================
100 |
101 | .. https://knitlib.readthedocs.org/
102 |
103 | ===============
104 | 7. Contributing
105 | ===============
106 |
107 | 7.1. Bug reports
108 | ----------------
109 |
110 | Bugs can be reported via the Github issues tracker at https://github.com/fashiontec/knitlib/issues
111 |
112 | 7.2 Documentation improvements
113 | ------------------------------
114 |
115 | 7.3 Feature requests, Issues, and Feedback
116 | -----------------------------------------------
117 | Issues, feature requests and feedback can be reported via the Github issues tracker at https://github.com/fashiontec/knitlib/issues
118 |
119 |
120 |
121 | 7.4 Pull Request Guidelines
122 | ---------------------------
123 |
124 |
125 |
126 | =============================
127 | 8. Applications using Knitlib
128 | =============================
129 |
130 | ==========
131 | 9. License
132 | ==========
133 |
134 | Free software: LGPLv3+ license
135 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/knitting_plugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of Knitlib.
3 | #
4 | # Knitlib is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # Knitlib is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with Knitlib. If not, see .
16 | #
17 | # Copyright 2015 Sebastian Oliva
18 |
19 |
20 | import abc
21 | import logging
22 | from fysom import Fysom
23 |
24 |
25 | class BaseKnittingPlugin(Fysom):
26 | """A generic plugin implementing a state machine for knitting.
27 |
28 | Subclasses inherit the basic State Machine defined in __init__.
29 | """
30 |
31 | __NOT_IMPLEMENTED_ERROR = "Classes that inherit from KnittingPlugin should implement {0}"
32 |
33 | @abc.abstractmethod
34 | def onknit(self, e):
35 | """Callback when state machine executes knit().
36 |
37 | Starts the knitting process, this is the only function call that can block indefinitely, as it is called from an instance
38 | of an individual Thread, allowing for processes that require timing and/or blocking behaviour.
39 | """
40 | raise NotImplementedError(
41 | self.__NOT_IMPLEMENTED_ERROR.format("onknit. It is used for the main 'knitting loop'."))
42 |
43 | @abc.abstractmethod
44 | def onfinish(self, e):
45 | """Callback when state machine executes finish().
46 |
47 | When finish() gets called, the plugin is expected to be able to restore it's state back when configure() gets called.
48 | Finish should trigger a Process Completed notification so the user can operate accordingly.
49 | """
50 | raise NotImplementedError(
51 | self.__NOT_IMPLEMENTED_ERROR.format("onfinish. It is a callback that is called when knitting is over."))
52 |
53 | @abc.abstractmethod
54 | def onconfigure(self, e):
55 | """Callback when state machine executes configure(conf={})
56 |
57 | This state gets called to configure the plugin for knitting. It can either
58 | be called when first configuring the plugin, when an error happened and a
59 | reset is necessary.
60 |
61 | Args:
62 | e: An event object holding a conf dict.
63 | """
64 | raise NotImplementedError(self.__NOT_IMPLEMENTED_ERROR.format(
65 | "onconfigure. It is used to configure the knitting plugin before starting."))
66 |
67 | @abc.abstractmethod
68 | def validate_configuration(self, conf):
69 | raise NotImplementedError(self.__NOT_IMPLEMENTED_ERROR.format(
70 | "validate_configuration must be defined. It verifies configurations are valid."))
71 |
72 | @abc.abstractmethod
73 | def set_port(self, portname):
74 | """Sets a port name before configuration method."""
75 | raise NotImplementedError(self.__NOT_IMPLEMENTED_ERROR.format(
76 | "set_port must be defined."))
77 |
78 | def register_interactive_callbacks(self, callbacks=None):
79 | """Serves to register a dict of callbacks that require interaction by the User,
80 |
81 | Interactive callbacks serve to block operation until a human acts on them. Interactive callbacks can include
82 | physical operations (set needles, move knob, flip switch), decisions (yes/no or cancel), or simply human
83 | acknowledgement.
84 |
85 | Args:
86 | callbacks: keys can be info, warning, progress, error.
87 |
88 | """
89 | if callbacks is None:
90 | callbacks = {}
91 | self.interactive_callbacks = callbacks
92 |
93 | @staticmethod
94 | def __cli_emit_message(message, level="info"):
95 | # TODO: use appropriate logging level for message.
96 | logging.info(message)
97 |
98 | @staticmethod
99 | def __cli_blocking_action(message, level="info"):
100 | """Capturing raw_input to block CLI action."""
101 | # TODO: use appropriate logging level for message.
102 | logging.info(message)
103 | raw_input()
104 |
105 | @staticmethod
106 | def __cli_log_progress(percent, done, total):
107 | """Logs progress percentage and lines of current job."""
108 | logging.info("Knitting at {}% . {} out of {}.".format(percent, done, total))
109 |
110 | @staticmethod
111 | def supported_config_features():
112 | """Returns a JSON schema dict with the available features for the given machine plugin."""
113 | raise NotImplementedError(BaseKnittingPlugin.__NOT_IMPLEMENTED_ERROR.format(
114 | "supported_config_features must be defined. It returns a JSON Schema with available configuration options."))
115 |
116 | def __init__(self, callbacks_dict=None, interactive_callbacks=None):
117 | """Interactive callbacks handle Plugin-Frontend interaction hooks."""
118 | self.interactive_callbacks = {}
119 |
120 | # Are we running on CLI or knitlib web?
121 | # If no callbacks are registered, we set a CLI set as default.
122 | if interactive_callbacks is None:
123 | self.register_interactive_callbacks({
124 | "blocking_user_action": BaseKnittingPlugin.__cli_blocking_action,
125 | "message": BaseKnittingPlugin.__cli_emit_message,
126 | "progress": BaseKnittingPlugin.__cli_log_progress
127 | })
128 | else:
129 | self.register_interactive_callbacks(interactive_callbacks)
130 |
131 | # Fysom allows to set hooks before changing states, we set them here.
132 | if callbacks_dict is None:
133 | callbacks_dict = {
134 | 'onknit': self.onknit,
135 | 'onconfigure': self.onconfigure,
136 | 'onfinish': self.onfinish,
137 | }
138 | Fysom.__init__(self, {
139 | 'initial': 'activated',
140 | 'events': [ # TODO: add more states for handling error management.
141 | {'name': 'configure', 'src': 'activated', 'dst': 'configured'},
142 | {'name': 'configure', 'src': 'configured', 'dst': 'configured'},
143 | {'name': 'configure', 'src': 'finished', 'dst': 'configured'},
144 | {'name': 'configure', 'src': 'error', 'dst': 'configured'},
145 | {'name': 'knit', 'src': 'configured', 'dst': 'knitting'},
146 | {'name': 'finish', 'src': 'knitting', 'dst': 'finished'},
147 | {'name': 'finish', 'src': 'finished', 'dst': 'finished'},
148 | {'name': 'fail', 'src': 'knitting', 'dst': 'error'}],
149 | 'callbacks': callbacks_dict
150 | })
151 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/pdd_plugin/insertpattern.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2009 Steve Conklin
4 | # steve at conklinhouse dot com
5 | #
6 | # This program is free software; you can redistribute it and/or
7 | # modify it under the terms of the GNU General Public License
8 | # as published by the Free Software Foundation; either version 2
9 | # of the License, or (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program; if not, write to the Free Software
18 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 |
20 | import sys
21 | import brother
22 | from PIL import Image
23 | # import array
24 |
25 | # import convenience functions from brother module
26 | # from brother import roundeven, roundfour, roundeight, nibblesPerRow, bytesPerPattern, bytesForMemo, methodWithPointers
27 | from brother import roundfour, bytesForMemo, methodWithPointers
28 |
29 | TheImage = None
30 |
31 | version = '1.0'
32 |
33 |
34 | class PatternInserter:
35 | def __init__(self):
36 | self.printInfoCallback = self.printInfo
37 | self.printErrorCallback = self.printError
38 | self.printPatternCallback = self.printPattern
39 |
40 | def printInfo(self, printMsg):
41 | print printMsg
42 |
43 | def printError(self, printMsg):
44 | print printMsg
45 |
46 | def printPattern(self, printMsg):
47 | sys.stdout.write(printMsg)
48 |
49 | def insertPattern(self, oldbrotherfile, pattnum, imgfile, newbrotherfile):
50 |
51 | bf = brother.brotherFile(oldbrotherfile)
52 |
53 | pats = bf.getPatterns()
54 |
55 | # ok got a bank, now lets figure out how big this thing we want to insert is
56 | TheImage = Image.open(imgfile)
57 | TheImage.load()
58 |
59 | im_size = TheImage.size
60 | width = im_size[0]
61 | self.printInfoCallback("width:" + str(width))
62 | height = im_size[1]
63 | self.printInfoCallback("height:" + str(height))
64 |
65 | # find the program entry
66 | thePattern = None
67 |
68 | for pat in pats:
69 | if (int(pat["number"]) == int(pattnum)):
70 | # print "found it!"
71 | thePattern = pat
72 | if (thePattern is None):
73 | raise PatternNotFoundException(pattnum)
74 |
75 | if (height != thePattern["rows"] or width != thePattern["stitches"]):
76 | raise InserterException("Pattern is the wrong size, the BMP is ", height, "x", width, "and the pattern is ",
77 | thePattern["rows"], "x", thePattern["stitches"])
78 |
79 | # debugging stuff here
80 | x = 0
81 | y = 0
82 |
83 | x = width - 1
84 | for y in xrange(height):
85 | for x in xrange(width):
86 | value = TheImage.getpixel((x, y))
87 | if value:
88 | self.printPattern('* ')
89 | else:
90 | self.printPattern(' ')
91 | print " "
92 |
93 | # debugging stuff done
94 |
95 | # now to make the actual, yknow memo+pattern data
96 |
97 | # the memo seems to be always blank. i have no idea really
98 | memoentry = []
99 | for i in range(bytesForMemo(height)):
100 | memoentry.append(0x0)
101 |
102 | # now for actual real live pattern data!
103 | pattmemnibs = []
104 | for r in range(height):
105 | row = [] # we'll chunk in bits and then put em into nibbles
106 | for s in range(width):
107 | x = s if methodWithPointers else width - s - 1
108 | value = TheImage.getpixel((x, height - r - 1))
109 | isBlack = (value == 0) if methodWithPointers else (value != 0)
110 | if (isBlack):
111 | row.append(1)
112 | else:
113 | row.append(0)
114 | # print row
115 | # turn it into nibz
116 | for s in range(roundfour(width) / 4):
117 | n = 0
118 | for nibs in range(4):
119 | # print "row size = ", len(row), "index = ",s*4+nibs
120 |
121 | if (len(row) == (s * 4 + nibs)):
122 | break # padding!
123 |
124 | if (row[s * 4 + nibs]):
125 | n |= 1 << nibs
126 | pattmemnibs.append(n)
127 | # print hex(n),
128 |
129 | if (len(pattmemnibs) % 2):
130 | # odd nibbles, buffer to a byte
131 | pattmemnibs.append(0x0)
132 |
133 | # print len(pattmemnibs), "nibbles of data"
134 |
135 | # turn into bytes
136 | pattmem = []
137 | for i in range(len(pattmemnibs) / 2):
138 | pattmem.append(pattmemnibs[i * 2] | (pattmemnibs[i * 2 + 1] << 4))
139 |
140 | # print map(hex, pattmem)
141 | # whew.
142 |
143 | # now to insert this data into the file
144 | # now we have to figure out the -end- of the last pattern is
145 | endaddr = 0x6df
146 |
147 | beginaddr = thePattern["pattend"]
148 | endaddr = beginaddr + bytesForMemo(height) + len(pattmem)
149 | self.printInfoCallback("beginning will be at " + str(hex(beginaddr)) + ", end at " + str(hex(endaddr)))
150 |
151 | # Note - It's note certain that in all cases this collision test is needed. What's happening
152 | # when you write below this address (as the pattern grows downward in memory) in that you begin
153 | # to overwrite the pattern index data that starts at low memory. Since you overwrite the info
154 | # for highest memory numbers first, you may be able to get away with it as long as you don't
155 | # attempt to use higher memories.
156 | # Steve
157 |
158 | if beginaddr <= 0x2B8:
159 | self.printErrorCallback(
160 | "Sorry, this will collide with the pattern entry data since %s is <= 0x2B8!" % hex(beginaddr))
161 | # exit
162 |
163 | # write the memo and pattern entry from the -end- to the -beginning- (up!)
164 | for i in range(len(memoentry)):
165 | bf.setIndexedByte(endaddr, 0)
166 | endaddr -= 1
167 |
168 | for i in range(len(pattmem)):
169 | bf.setIndexedByte(endaddr, pattmem[i])
170 | endaddr -= 1
171 |
172 | # push the data to a file
173 | outfile = open(newbrotherfile, 'wb')
174 |
175 | d = bf.getFullData()
176 | outfile.write(d)
177 | outfile.close()
178 |
179 |
180 | class InserterException(Exception):
181 | def getMessage(self):
182 | msg = ''
183 | for arg in self.args:
184 | if msg != '':
185 | msg += ' '
186 | msg += str(arg)
187 |
188 | return msg
189 |
190 |
191 | class PatternNotFoundException(InserterException):
192 | def __init__(self, patternNumber):
193 | self.patternNumber = patternNumber
194 |
195 |
196 | if __name__ == "__main__":
197 | if len(sys.argv) < 5:
198 | print 'Usage: %s oldbrotherfile pattern# image.bmp newbrotherfile' % sys.argv[0]
199 | sys.exit()
200 | inserter = PatternInserter()
201 | argv = sys.argv
202 | try:
203 | inserter.insertPattern(argv[1], argv[2], argv[3], argv[4])
204 | except PatternNotFoundException as e:
205 | print 'ERROR: Pattern %d not found' % e.patternNumber
206 | sys.exit(1)
207 | except InserterException as e:
208 | print 'ERROR: ', e.getMessage()
209 | sys.exit(1)
210 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/ayab_plugin/ayab_image.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of AYAB.
3 | #
4 | # AYAB is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # AYAB is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with AYAB. If not, see .
16 | #
17 | # Copyright 2013 Christian Obersteiner, Andreas Müller
18 | # https://bitbucket.org/chris007de/ayab-apparat/
19 |
20 | from PIL import Image
21 |
22 |
23 | class ayabImage(object):
24 | def __init__(self, pil_image, pNumColors=2):
25 | self.__numColors = pNumColors
26 |
27 | self.__imgPosition = 'center'
28 | self.__imgStartNeedle = '0'
29 | self.__imgStopNeedle = '0'
30 |
31 | self.__knitStartNeedle = 0
32 | self.__knitStopNeedle = 199
33 |
34 | self.__startLine = 0
35 |
36 | self.__image = pil_image
37 |
38 | self.__image = self.__image.convert('L') # convert to 1 byte depth
39 | self.__updateImageData()
40 |
41 | def imageIntern(self):
42 | return self.__imageIntern
43 |
44 | def imageExpanded(self):
45 | return self.__imageExpanded
46 |
47 | def imgWidth(self):
48 | return self.__imgWidth
49 |
50 | def imgHeight(self):
51 | return self.__imgHeight
52 |
53 | def knitStartNeedle(self):
54 | return self.__knitStartNeedle
55 |
56 | def knitStopNeedle(self):
57 | return self.__knitStopNeedle
58 |
59 | def imgStartNeedle(self):
60 | return self.__imgStartNeedle
61 |
62 | def imgStopNeedle(self):
63 | return self.__imgStopNeedle
64 |
65 | def imgPosition(self):
66 | return self.__imgPosition
67 |
68 | def startLine(self):
69 | return self.__startLine
70 |
71 | def numColors(self):
72 | return self.__numColors
73 |
74 | def __updateImageData(self):
75 | self.__imgWidth = self.__image.size[0]
76 | self.__imgHeight = self.__image.size[1]
77 |
78 | self.__convertImgToIntern()
79 | self.__calcImgStartStopNeedles()
80 |
81 | def __convertImgToIntern(self):
82 | """Converts an image to AYAB's internal representation, a big 2D array."""
83 | num_colors = self.__numColors
84 | clr_range = float(256) / num_colors
85 |
86 | imgWidth = self.__imgWidth
87 | imgHeight = self.__imgHeight
88 |
89 | self.__imageIntern = [[0 for i in range(imgWidth)] for j in range(imgHeight)]
90 | self.__imageColors = [[0 for i in range(num_colors)] for j in range(imgHeight)]
91 | self.__imageExpanded = [[0 for i in range(imgWidth)] for j in range(num_colors * imgHeight)]
92 |
93 | # Distill image to x colors
94 | for row in range(0, imgHeight):
95 | for col in range(0, imgWidth):
96 | pxl = self.__image.getpixel((col, row))
97 |
98 | for color in range(0, num_colors):
99 | lowerBound = int(color * clr_range)
100 | upperBound = int((color + 1) * clr_range)
101 | if pxl >= lowerBound and pxl < upperBound:
102 | # color map
103 | self.__imageIntern[row][col] = color
104 | # amount of bits per color per line
105 | self.__imageColors[row][color] += 1
106 | # colors separated per line
107 | self.__imageExpanded[(num_colors * row) + color][col] = 1
108 |
109 | # print(self.__imageIntern)
110 | # print(self.__imageColors)
111 | # print(self.__imageExpanded)
112 |
113 | def __calcImgStartStopNeedles(self):
114 | if self.__imgPosition == 'center':
115 | needleWidth = (self.__knitStopNeedle - self.__knitStartNeedle) + 1
116 | self.__imgStartNeedle = (self.__knitStartNeedle + needleWidth / 2) - self.__imgWidth / 2
117 | self.__imgStopNeedle = self.__imgStartNeedle + self.__imgWidth - 1
118 |
119 | elif self.__imgPosition == 'left':
120 | self.__imgStartNeedle = self.__knitStartNeedle
121 | self.__imgStopNeedle = self.__imgStartNeedle + self.__imgWidth
122 |
123 | elif self.__imgPosition == 'right':
124 | self.__imgStopNeedle = self.__knitStopNeedle
125 | self.__imgStartNeedle = self.__imgStopNeedle - self.__imgWidth
126 |
127 | elif int(self.__imgPosition) > 0 and int(self.__imgPosition) < 200:
128 | self.__imgStartNeedle = int(self.__imgPosition)
129 | self.__imgStopNeedle = self.__imgStartNeedle + self.__imgWidth
130 |
131 | else:
132 | return False
133 | return True
134 |
135 | def setNumColors(self, pNumColors):
136 | """
137 | sets the number of colors the be used for knitting
138 | """
139 | if pNumColors > 1 and pNumColors < 7:
140 | self.__numColors = pNumColors
141 | self.__updateImageData()
142 | return
143 |
144 | def invertImage(self):
145 | """
146 | invert the pixels of the image
147 | """
148 | for y in range(0, self.__image.size[1]):
149 | for x in range(0, self.__image.size[0]):
150 | pxl = self.__image.getpixel((x, y))
151 | self.__image.putpixel((x, y), 255 - pxl)
152 | self.__updateImageData()
153 | return
154 |
155 | def rotateImage(self):
156 | """
157 | rotate the image 90 degrees clockwise
158 | """
159 | self.__image = self.__image.rotate(-90)
160 |
161 | self.__updateImageData()
162 | return
163 |
164 | def resizeImage(self, pNewWidth):
165 | """
166 | resize the image to a given width, keeping the aspect ratio
167 | """
168 | wpercent = (pNewWidth / float(self.__image.size[0]))
169 | hsize = int((float(self.__image.size[1]) * float(wpercent)))
170 | self.__image = self.__image.resize((pNewWidth, hsize), Image.ANTIALIAS)
171 |
172 | self.__updateImageData()
173 | return
174 |
175 | def setKnitNeedles(self, pKnitStart, pKnitStop):
176 | """
177 | set the start and stop needle
178 | """
179 | if (pKnitStart < pKnitStop) \
180 | and pKnitStart >= 0 \
181 | and pKnitStop < 200:
182 | self.__knitStartNeedle = pKnitStart
183 | self.__knitStopNeedle = pKnitStop
184 |
185 | self.__updateImageData()
186 | return
187 |
188 | def setImagePosition(self, pImgPosition):
189 | """
190 | set the position of the pattern
191 | """
192 | ok = False
193 | if pImgPosition == 'left' \
194 | or pImgPosition == 'center' \
195 | or pImgPosition == 'right':
196 | ok = True
197 | elif (int(pImgPosition) >= 0 and int(pImgPosition) < 200):
198 | ok = True
199 |
200 | if ok:
201 | self.__imgPosition = pImgPosition
202 | self.__updateImageData()
203 | return
204 |
205 | def setStartLine(self, pStartLine):
206 | """
207 | set the line where to start knitting
208 | """
209 | # Check if StartLine is in valid range (picture height)
210 | if pStartLine >= 0 \
211 | and pStartLine < self.__image.size[1]:
212 | self.__startLine = pStartLine
213 | return
214 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/pdd_plugin/brother.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2009 Steve Conklin
4 | # steve at conklinhouse dot com
5 | #
6 | # This program is free software; you can redistribute it and/or
7 | # modify it under the terms of the GNU General Public License
8 | # as published by the Free Software Foundation; either version 2
9 | # of the License, or (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program; if not, write to the Free Software
18 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 |
20 | # import sys
21 | # import array
22 | # import os
23 | # import os.path
24 | # import string
25 | # import ctypes
26 | from array import array
27 |
28 | __version__ = '1.0'
29 |
30 | methodWithPointers = False
31 | # uncomment this to use new, more precise method of finding patterns in the dat file,
32 | # based on kh940 format documentation from https://github.com/stg/knittington/blob/master/doc/kh940_format.txt
33 | # (should work with all kh930 and kh940 models)
34 | methodWithPointers = True
35 | # Some file location constants
36 | initPatternOffset = 0x06DF # programmed patterns start here, grow down
37 | currentPatternAddr = 0x07EA # stored in MSN and following byte
38 | currentRowAddr = 0x06FF
39 | nextRowAddr = 0x072F
40 | currentRowNumberAddr = 0x0702
41 | carriageStatusAddr = 0x070F
42 | selectAddr = 0x07EA
43 |
44 |
45 | # various unknowns which are probably something we care about
46 | unknownList = {'0700': 0x0700, '0701': 0x0701,
47 | '0704': 0x0704, '0705': 0x0705, '0706': 0x0706, '0707': 0x0707,
48 | '0708': 0x0708, '0709': 0x0709, '070A': 0x070A, '070B': 0x070B,
49 | '070C': 0x070C, '070D': 0x070D, '070E': 0x070E, '0710': 0x0710,
50 | '0711': 0x0711, '0712': 0x0712, '0713': 0x0713, '0714': 0x0714,
51 | '0715': 0x0715}
52 |
53 |
54 | def nibbles(achar):
55 | # print '0x%02X' % ord(achar)
56 | msn = (ord(achar) & 0xF0) >> 4
57 | lsn = ord(achar) & 0x0F
58 | return msn, lsn
59 |
60 |
61 | def hto(hundreds, tens, ones):
62 | return (100 * hundreds) + (10 * tens) + ones
63 |
64 |
65 | def roundeven(val):
66 | return (val + (val % 2))
67 |
68 |
69 | def roundeight(val):
70 | if val % 8:
71 | return val + (8 - (val % 8))
72 | else:
73 | return val
74 |
75 |
76 | def roundfour(val):
77 | if val % 4:
78 | return val + (4 - (val % 4))
79 | else:
80 | return val
81 |
82 |
83 | def nibblesPerRow(stitches):
84 | # there are four stitches per nibble
85 | # each row is nibble aligned
86 | return (roundfour(stitches) / 4)
87 |
88 |
89 | def bytesPerPattern(stitches, rows):
90 | nibbs = rows * nibblesPerRow(stitches)
91 | bytes = roundeven(nibbs) / 2
92 | return bytes
93 |
94 |
95 | def bytesForMemo(rows):
96 | bytes = roundeven(rows) / 2
97 | return bytes
98 |
99 |
100 | def bytesPerPatternAndMemo(stitches, rows):
101 | patbytes = bytesPerPattern(stitches, rows)
102 | memobytes = bytesForMemo(rows)
103 | return patbytes + memobytes
104 |
105 |
106 | class brotherFile(object):
107 | def __init__(self, fn):
108 | self.dfn = None
109 | self.verbose = False
110 | try:
111 | try:
112 | self.df = open(fn, 'rb+') # YOU MUST HAVE BINARY FORMAT!!!
113 | except IOError:
114 | # for now, read only
115 | raise
116 | # self.df = open(fn, 'w')
117 | except:
118 | print 'Unable to open brother file <%s>' % fn
119 | raise
120 | try:
121 | if methodWithPointers:
122 | self.data = self.df.read(-1)
123 | else:
124 | self.data = self.df.read(2048)
125 | self.df.close()
126 | if len(self.data) == 0:
127 | raise Exception()
128 | except:
129 | if methodWithPointers:
130 | print 'Unable to read 2048 bytes from file <%s>' % fn
131 | else:
132 | print 'Unable to read data from file <%s>' % fn
133 | raise
134 | self.dfn = fn
135 | return
136 |
137 | def __del__(self):
138 | return
139 |
140 | def getIndexedByte(self, index):
141 | return ord(self.data[index])
142 |
143 | def setIndexedByte(self, index, b):
144 | # python strings are mutable so we
145 | # will convert the string to a char array, poke
146 | # and convert back
147 | dataarray = array('c')
148 | dataarray.fromstring(self.data)
149 |
150 | if self.verbose:
151 | print "* writing ", hex(b), "to", hex(index)
152 | # print dataarray
153 |
154 | # this is the actual edit
155 | dataarray[index] = chr(b)
156 |
157 | # save the new string. sure its not very memory-efficient
158 | # but who cares?
159 | self.data = dataarray.tostring()
160 |
161 | # handy for debugging
162 | def getFullData(self):
163 | return self.data
164 |
165 | def getIndexedNibble(self, offset, nibble):
166 | # nibbles is zero based
167 | bytes = nibble / 2
168 | m, l = nibbles(self.data[offset - bytes])
169 | if nibble % 2:
170 | return m
171 | else:
172 | return l
173 |
174 | def getRowData(self, pattOffset, stitches, rownumber):
175 | row = array('B')
176 | nibspr = nibblesPerRow(stitches)
177 | startnib = nibspr * rownumber
178 | endnib = startnib + nibspr
179 |
180 | for i in range(startnib, endnib, 1):
181 | nib = self.getIndexedNibble(pattOffset, i)
182 | row.append(nib & 0x01)
183 | stitches = stitches - 1
184 | if stitches:
185 | row.append((nib & 0x02) >> 1)
186 | stitches = stitches - 1
187 | if stitches:
188 | row.append((nib & 0x04) >> 2)
189 | stitches = stitches - 1
190 | if stitches:
191 | row.append((nib & 0x08) >> 3)
192 | stitches = stitches - 1
193 | return row
194 |
195 | def getPatterns(self, patternNumber=None):
196 | """
197 | Get a list of custom patterns stored in the file, or
198 | information for a single pattern.
199 | Pattern information is stored at the beginning
200 | of the file, with seven bytes per pattern and
201 | 99 possible patterns, numbered 901-999.
202 | Returns: A list of tuples:
203 | patternNumber
204 | stitches
205 | rows
206 | patternOffset
207 | memoOffset
208 | """
209 | patlist = []
210 | idx = 0
211 | pptr = initPatternOffset
212 | for pi in range(1, 100):
213 | flag = ord(self.data[idx])
214 | if self.verbose:
215 | print 'Entry %d, flag is 0x%02X' % (pi, flag)
216 | idx = idx + 1
217 | unknown = ord(self.data[idx])
218 | idx = idx + 1
219 | rh, rt = nibbles(self.data[idx])
220 | idx = idx + 1
221 | ro, sh = nibbles(self.data[idx])
222 | idx = idx + 1
223 | st, so = nibbles(self.data[idx])
224 | idx = idx + 1
225 | unk, ph = nibbles(self.data[idx])
226 | idx = idx + 1
227 | pt, po = nibbles(self.data[idx])
228 | idx = idx + 1
229 | rows = hto(rh, rt, ro)
230 | stitches = hto(sh, st, so)
231 | patno = hto(ph, pt, po)
232 | # we have this entry
233 | if self.verbose:
234 | print ' Pattern %3d: %3d Rows, %3d Stitches - ' % (patno, rows, stitches)
235 | if flag != 0:
236 | # valid entry
237 | if methodWithPointers:
238 | pptr = len(self.data) - 1 - ((flag << 8) + unknown)
239 | memoff = pptr
240 | if self.verbose:
241 | print "Memo #", patno, "offset ", memoff
242 | patoff = pptr - bytesForMemo(rows)
243 | if self.verbose:
244 | print "Pattern #", patno, "offset ", patoff
245 | pptr = pptr - bytesPerPatternAndMemo(stitches, rows)
246 | if self.verbose:
247 | print "Ending offset ", hex(pptr)
248 | if patternNumber:
249 | if patternNumber == patno:
250 | patlist.append(
251 | {'number': patno, 'stitches': stitches, 'rows': rows, 'memo': memoff, 'pattern': patoff,
252 | 'pattend': pptr})
253 | else:
254 | patlist.append(
255 | {'number': patno, 'stitches': stitches, 'rows': rows, 'memo': memoff, 'pattern': patoff,
256 | 'pattend': pptr})
257 | else:
258 | break
259 | return patlist
260 |
261 | def getMemo(self):
262 | """
263 | Return an array containing the memo
264 | information for the pattern currently in memory
265 | """
266 | patt = self.patternNumber()
267 | if patt > 900:
268 | return self.getPatternMemo(patt)
269 | else:
270 | pass
271 | # rows = 0 # TODO XXXXXXXXX
272 | return [0]
273 |
274 | def patternNumber(self):
275 | sn, pnh = nibbles(self.data[currentPatternAddr])
276 | pnt, pno = nibbles(self.data[currentPatternAddr + 1])
277 | pattern = hto(pnh, pnt, pno)
278 | return (pattern)
279 |
280 | def getPatternMemo(self, patternNumber):
281 | """
282 | Return an array containing the memo
283 | information for a custom pattern. The array
284 | is the same length as the number of rows
285 | in the pattern.
286 | """
287 | list = self.getPatterns(patternNumber)
288 | if len(list) == 0:
289 | return None
290 | memos = array('B')
291 | memoOff = list[0]['memo']
292 | rows = list[0]['rows']
293 | memlen = roundeven(rows) / 2
294 | # memo is padded to en even byte
295 | for i in range(memoOff, memoOff - memlen, -1):
296 | msn, lsn = nibbles(self.data[i])
297 | memos.append(lsn)
298 | rows = rows - 1
299 | if (rows):
300 | memos.append(msn)
301 | rows = rows - 1
302 | return memos
303 |
304 | def getPattern(self, patternNumber):
305 | """
306 | Return an array containing the pattern
307 | information for a pattern.
308 | """
309 | list = self.getPatterns(patternNumber)
310 | if len(list) == 0:
311 | return None
312 | pattern = []
313 |
314 | patoff = list[0]['pattern']
315 | rows = list[0]['rows']
316 | stitches = list[0]['stitches']
317 |
318 | # print 'patoff = 0x%04X' % patoff
319 | # print 'rows = ', rows
320 | # print 'stitches = ', stitches
321 | for i in range(0, rows):
322 | arow = self.getRowData(patoff, stitches, i)
323 | # print arow
324 | pattern.append(arow)
325 | return pattern
326 |
327 | def displayPattern(self, patternNumber):
328 | """
329 | Display a user pattern stored in file saved
330 | from the brother knitting machine. Patterns
331 | in memory are stored with the beginning of the
332 | pattern at the highest memory address.
333 | """
334 |
335 | return
336 |
337 | def rowNumber(self):
338 | sn, rnh = nibbles(self.data[currentRowNumberAddr])
339 | rnt, rno = nibbles(self.data[currentRowNumberAddr + 1])
340 | rowno = hto(rnh, rnt, rno)
341 | return (rowno)
342 |
343 | def nextRow(self):
344 | return self.getRowData(nextRowAddr, 200, 0)
345 |
346 | def selectorValue(self):
347 | return ord(self.data[selectAddr])
348 |
349 | def carriageStatus(self):
350 | return ord(self.data[carriageStatusAddr])
351 |
352 | def motifData(self):
353 | motiflist = []
354 | addr = 0x07FB
355 | for i in range(6):
356 | mph, mpt = nibbles(self.data[addr])
357 | if mph & 8:
358 | mph = mph - 8
359 | side = 'right'
360 | else:
361 | side = 'left'
362 | mpo, foo = nibbles(self.data[addr + 1])
363 | mch, mct = nibbles(self.data[addr + 2])
364 | mco, bar = nibbles(self.data[addr + 3])
365 | pos = hto(mph, mpt, mpo)
366 | cnt = hto(mch, mct, mco)
367 | motiflist.append({'position': pos, 'copies': cnt, 'side': side})
368 | addr = addr - 3
369 | return motiflist
370 |
371 | def patternPosition(self):
372 | addr = 0x07FE
373 | foo, ph = nibbles(self.data[addr])
374 | if ph & 8:
375 | ph = ph - 8
376 | side = 'right'
377 | else:
378 | side = 'left'
379 | pt, po = nibbles(self.data[addr + 1])
380 | pos = hto(ph, pt, po)
381 |
382 | return {'position': pos, 'side': side}
383 |
384 | # these are hardcoded for now
385 | def unknownOne(self):
386 | info = array('B')
387 | for i in range(0x06E0, 0x06E5):
388 | info.append(ord(self.data[i]))
389 | return info
390 |
391 | def unknownMemoRange(self):
392 | info = array('B')
393 | for i in range(0x0731, 0x0787):
394 | info.append(ord(self.data[i]))
395 | return info
396 |
397 | def unknownEndRange(self):
398 | info = array('B')
399 | for i in range(0x07D0, 0x07E9):
400 | info.append(ord(self.data[i]))
401 | return info
402 |
403 | def unknownAddrs(self):
404 | return unknownList.items()
405 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/ayab_plugin/ayab_control.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of AYAB.
3 | #
4 | # AYAB is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # AYAB is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with AYAB. If not, see .
16 | #
17 | # Copyright 2013, 2014 Sebastian Oliva, Christian Obersteiner, Andreas Müller
18 | # https://bitbucket.org/chris007de/ayab-apparat/
19 |
20 | from ayab_communication import AyabCommunication
21 | import ayab_image
22 | import time
23 | import logging
24 | import os
25 | from PIL import Image
26 | from knitlib.plugins.knitting_plugin import BaseKnittingPlugin
27 | from knitlib import knitpat
28 |
29 |
30 | class AyabPluginControl(BaseKnittingPlugin):
31 |
32 | __PLUGIN_NAME__ = "AYAB"
33 |
34 | def __init__(self):
35 | super(AyabPluginControl, self).__init__()
36 | # KnittingPlugin.__init__(self)
37 |
38 | # From AYAB's ayab_control
39 | self.__API_VERSION = 0x03
40 | self.__ayabCom = AyabCommunication()
41 | self.conf = {}
42 | self.__portname = ""
43 |
44 | self.__formerRequest = 0
45 | self.__lineBlock = 0
46 |
47 | def __del__(self):
48 | self.__close_serial()
49 |
50 | def onknit(self, e):
51 | logging.debug("called onknit on AyabPluginControl")
52 | self.__knitImage(self.__image, self.conf)
53 | self.finish()
54 |
55 | def onconfigure(self, e):
56 | logging.debug("called onconfigure on AYAB Knitting Plugin")
57 | logging.debug("With args: {}".format(e.args))
58 | logging.debug("With conf kwarg: {}".format(e.conf))
59 |
60 | # Start to knit with the bottom first
61 | # pil_image = self.pil_image.rotate(180)
62 |
63 | # conf = e.event.conf
64 | # self.conf = e.event.conf
65 | if hasattr(e, "conf") and e.conf is not None:
66 | if knitpat.validate_dict(e.conf):
67 | conf = e.conf
68 | else:
69 | conf = self.generate_test_configuration()
70 |
71 | else:
72 | conf = self.generate_test_configuration()
73 |
74 | self.conf = conf
75 | logging.debug("Working with final conf: {}".format(e.conf))
76 |
77 | script_dir = os.path.dirname(os.path.abspath(__file__)) # Temp fix for testing Image opening
78 | pil_image = Image.open(os.path.join(script_dir, conf["file_url"]))
79 |
80 | try:
81 | self.__image = ayab_image.ayabImage(pil_image, self.conf["colors"])
82 | except:
83 | self.__notify_user("You need to set an image.", "error")
84 | return
85 |
86 | if conf.get("start_needle") and conf.get("stop_needle"):
87 | self.__image.setKnitNeedles(conf.get("start_needle"), conf.get("stop_needle"))
88 | if conf.get("alignment"):
89 | self.__image.setImagePosition(conf.get("alignment"))
90 | if conf.get("start_line"):
91 | self.__image.setStartLine(conf.get("start_line"))
92 |
93 | # Do Knit.
94 |
95 | def onfinish(self, e):
96 | logging.info("Finished Knitting.")
97 | self.__close_serial()
98 | # self.__parent_ui.resetUI()
99 | # self.__parent_ui.emit(QtCore.SIGNAL('updateProgress(int,int,int)'), 0, 0, 0)
100 |
101 | @staticmethod
102 | def supported_config_features():
103 | return {
104 | "$schema": "http://json-schema.org/schema#",
105 | "type": "object",
106 | "properties": {
107 | "start_needle": {"type": "integer"},
108 | "stop_needle": {"type": "integer"},
109 | "start_line": {"type": "integer"},
110 | "machine_type": {"type": "string",
111 | "enum": ["single", "double"]},
112 | "alignment": {"type": "string",
113 | "enum": ["left", "center", "right"]},
114 | "inf_repeat": {"type": "integer"},
115 | "port": {"type": "string"}
116 | }
117 | }
118 |
119 | def cancel(self):
120 | self._knitImage = False
121 | # self.finish()
122 |
123 | def __close_serial(self):
124 | try:
125 | self.__ayabCom.close_serial()
126 | logging.debug("Closing Serial port successful.")
127 | except:
128 | logging.debug("Closing Serial port failed. Was it ever open?")
129 |
130 | def onerror(self, e):
131 | # TODO add message info from event
132 | logging.error("Error while Knitting.")
133 | self.__close_serial()
134 |
135 | def validate_configuration(self, conf):
136 | if conf.get("start_needle") and conf.get("stop_needle"):
137 | if conf.get("start_needle") > conf.get("stop_needle"):
138 | self.__notify_user("Invalid needle start and end.", "warning")
139 | return False
140 | if conf.get("start_line") > self.__image.imgHeight():
141 | self.__notify_user("Start Line is larger than the image.")
142 | return False
143 |
144 | # if conf.get("portname") == '':
145 | # self.__notify_user("Please choose a valid port.")
146 | # return False
147 | return True
148 |
149 | def __wait_for_user_action(self, message="", message_type="info"):
150 | """Waits for the user to react, blocking it."""
151 | self.interactive_callbacks["blocking_user_action"](message, message_type)
152 | # logging.info(message)
153 | # time.sleep(3)
154 | # raw_input()
155 | # pass
156 | # self.__parent_ui.emit(QtCore.SIGNAL('display_blocking_pop_up_signal(QString, QString)'), message, message_type)
157 |
158 | def __notify_user(self, message="", message_type="info"):
159 | """Sends the a notification without blocking."""
160 | self.interactive_callbacks["message"](message, message_type)
161 | # logging.info(message)
162 | # pass
163 | # self.__parent_ui.emit(QtCore.SIGNAL('display_pop_up_signal(QString, QString)'), message, message_type)
164 |
165 | def __emit_progress(self, percent, done, total):
166 | """Shows the current job progress."""
167 | self.interactive_callbacks["progress"](percent, done, total)
168 | # logging.info("Knitting at {}% . {} out of {}.".format(percent, done, total))
169 | # pass
170 | # self.__parent_ui.emit(QtCore.SIGNAL('updateProgress(int,int,int)'), int(percent), int(done), int(total))
171 |
172 | def generate_test_configuration(self):
173 | """Creates a configuration dict from the ui elements.
174 |
175 | Returns:
176 | dict: A dict with configuration.
177 |
178 | """
179 |
180 | conf = {}
181 | conf[u"colors"] = 2
182 | conf[u"start_line"] = 0
183 |
184 | start_needle_color = stop_needle_color = u"orange" # or green.
185 | start_needle_value = stop_needle_value = 0
186 |
187 | def set_value_by_color(conf_dict, needle_position, needle_color, start_needle_value):
188 | if needle_color == u"orange":
189 | conf_dict[needle_position] = 100 - start_needle_value
190 | elif needle_color == u"green":
191 | conf_dict[needle_position] = 99 + start_needle_value
192 | else:
193 | conf_dict[needle_position] = start_needle_value
194 | return conf_dict
195 |
196 | conf = set_value_by_color(conf, u"start_needle", start_needle_color, start_needle_value)
197 | conf = set_value_by_color(conf, u"stop_needle", stop_needle_color, stop_needle_value)
198 |
199 | conf["alignment"] = "center"
200 | conf["inf_repeat"] = 0
201 | conf["machine_type"] = "single"
202 |
203 | # serial_port = u"/dev/ttyACM0"
204 | # conf["portname"] = serial_port # Should be related to self.getSerialPorts()[0][0]
205 | self.set_port()
206 | # getting file location from textbox
207 | filename_text = u"mushroom.png"
208 | conf["file_url"] = filename_text
209 | logging.debug(conf)
210 | # TODO: Add more config options.
211 | return conf
212 |
213 | def set_port(self, portname=u"/dev/ttyACM0"):
214 | self.__portname = portname
215 |
216 | # From ayab_control
217 | #####################################
218 |
219 | def __setBit(self, int_type, offset):
220 | mask = 1 << offset
221 | return (int_type | mask)
222 |
223 | def __setPixel(self, bytearray, pixel):
224 | numByte = int(pixel / 8)
225 | bytearray[numByte] = self.__setBit(
226 | int(bytearray[numByte]), pixel - (8 * numByte))
227 | return bytearray
228 |
229 | def __checkSerial(self):
230 | time.sleep(1) # TODO if problems in communication, tweak here
231 |
232 | line = self.__ayabCom.read_line()
233 |
234 | if line != '':
235 | msgId = ord(line[0])
236 | if msgId == 0xC1: # cnfStart
237 | # print "> cnfStart: " + str(ord(line[1]))
238 | return ("cnfStart", ord(line[1]))
239 |
240 | elif msgId == 0xC3: # cnfInfo
241 | # print "> cnfInfo: Version=" + str(ord(line[1]))
242 | logging.debug("Detected device with API v" + str(ord(line[1])))
243 | return ("cnfInfo", ord(line[1]))
244 |
245 | elif msgId == 0x82: # reqLine
246 | # print "> reqLine: " + str(ord(line[1]))
247 | return ("reqLine", ord(line[1]))
248 |
249 | else:
250 | self.__printError("unknown message: " + line[:]) # drop crlf
251 | return ("unknown", 0)
252 | return ("none", 0)
253 |
254 | def __cnfLine(self, lineNumber):
255 | imgHeight = self.__image.imgHeight()
256 | color = 0
257 | indexToSend = 0
258 | sendBlankLine = False
259 | lastLine = 0x00
260 |
261 | # TODO optimize performance
262 | # initialize bytearray to 0x00
263 | bytes = bytearray(25)
264 | for x in range(0, 25):
265 | bytes[x] = 0x00
266 |
267 | if lineNumber < 256:
268 | # TODO some better algorithm for block wrapping
269 | # if the last requested line number was 255, wrap to next block of
270 | # lines
271 | if self.__formerRequest == 255 and lineNumber == 0:
272 | self.__lineBlock += 1
273 | # store requested line number for next request
274 | self.__formerRequest = lineNumber
275 | reqestedLine = lineNumber
276 |
277 | # adjust lineNumber with current block
278 | lineNumber = lineNumber + (self.__lineBlock * 256)
279 |
280 | # when knitting infinitely, keep the requested lineNumber in its limits
281 | if self.__infRepeat:
282 | lineNumber = lineNumber % imgHeight
283 |
284 | #########################
285 | # decide which line to send according to machine type and amount of colors
286 | # singlebed, 2 color
287 | if self.__machineType == 'single' and self.__numColors == 2:
288 |
289 | # color is always 0 in singlebed,
290 | # because both colors are knitted at once
291 | color = 0
292 |
293 | # calculate imgRow
294 | imgRow = lineNumber + self.__startLine
295 |
296 | # 0 1 2 3 4 .. (imgRow)
297 | # | | | | |
298 | # 0 1 2 3 4 5 6 7 8 .. (imageExpanded)
299 | indexToSend = imgRow * 2
300 |
301 | # Check if the last line of the image was requested
302 | if imgRow == imgHeight - 1:
303 | lastLine = 0x01
304 |
305 | # doublebed, 2 color
306 | elif self.__machineType == 'double' \
307 | and self.__numColors == 2:
308 |
309 | # calculate imgRow
310 | imgRow = int(lineNumber / 2) + self.__startLine
311 |
312 | # 0 0 1 1 2 2 3 3 4 4 .. (imgRow)
313 | # 0 1 2 3 4 5 6 7 8 9 .. (lineNumber)
314 | # | | X | | X | |
315 | # 0 1 3 2 4 5 7 6 8 9 .. (imageExpanded)
316 | lenImgExpanded = len(self.__image.imageExpanded())
317 | indexToSend = self.__startLine * 2
318 |
319 | # TODO more beautiful algo
320 | if lineNumber % 4 == 1 or lineNumber % 4 == 2:
321 | color = 1
322 | else:
323 | color = 0
324 |
325 | if (lineNumber - 2) % 4 == 0:
326 | indexToSend += lineNumber + 1
327 |
328 | elif (lineNumber - 2) % 4 == 1:
329 | indexToSend += lineNumber - 1
330 | if (imgRow == imgHeight - 1) \
331 | and (indexToSend == lenImgExpanded - 2):
332 | lastLine = 0x01
333 | else:
334 | indexToSend += lineNumber
335 | if (imgRow == imgHeight - 1) \
336 | and (indexToSend == lenImgExpanded - 1):
337 | lastLine = 0x01
338 |
339 | # doublebed, multicolor
340 | elif self.__machineType == 'double' \
341 | and self.__numColors > 2:
342 |
343 | # calculate imgRow
344 | imgRow = int(
345 | lineNumber / (self.__numColors * 2)) + self.__startLine
346 |
347 | if (lineNumber % 2) == 0:
348 | color = (lineNumber / 2) % self.__numColors
349 | indexToSend = (imgRow * self.__numColors) + color
350 | logging.debug("COLOR" + str(color))
351 | else:
352 | sendBlankLine = True
353 |
354 | lenImgExpanded = len(self.__image.imageExpanded())
355 |
356 | # TODO Check assignment
357 | if imgRow == imgHeight - 1 \
358 | and (indexToSend == lenImgExpanded - 1):
359 | lastLine = 0x01
360 | #########################
361 |
362 | # assign pixeldata
363 | imgStartNeedle = self.__image.imgStartNeedle()
364 | if imgStartNeedle < 0:
365 | imgStartNeedle = 0
366 |
367 | imgStopNeedle = self.__image.imgStopNeedle()
368 | if imgStopNeedle > 199:
369 | imgStopNeedle = 199
370 |
371 | # set the bitarray
372 | if color == 0 \
373 | and self.__machineType == 'double':
374 | for col in range(0, 200):
375 | if col < imgStartNeedle \
376 | or col > imgStopNeedle:
377 | bytes = self.__setPixel(bytes, col)
378 |
379 | for col in range(0, self.__image.imgWidth()):
380 | pxl = (self.__image.imageExpanded())[indexToSend][col]
381 | # take the image offset into account
382 | if pxl is True and sendBlankLine is False:
383 | bytes = self.__setPixel(
384 | bytes, col + self.__image.imgStartNeedle())
385 |
386 | # TODO implement CRC8
387 | crc8 = 0x00
388 |
389 | # send line to machine
390 | if self.__infRepeat:
391 | self.__ayabCom.cnf_line(reqestedLine, bytes, 0, crc8)
392 | else:
393 | self.__ayabCom.cnf_line(reqestedLine, bytes, lastLine, crc8)
394 |
395 | # screen output
396 | msg = str((self.__image.imageExpanded())[indexToSend])
397 | msg += ' Image Row: ' + str(imgRow)
398 | msg += ' (indexToSend: ' + str(indexToSend)
399 | msg += ', reqLine: ' + str(reqestedLine)
400 | msg += ', lineNumber: ' + str(lineNumber)
401 | msg += ', lineBlock:' + str(self.__lineBlock) + ')'
402 | logging.debug(msg)
403 | # sending line progress to gui
404 | progress_int = 100 * float(imgRow) / self.__image.imgHeight()
405 | self.__emit_progress(progress_int, imgRow, imgHeight)
406 |
407 | else:
408 | logging.error("requested lineNumber out of range")
409 |
410 | if lastLine:
411 | if self.__infRepeat:
412 | self.__lineBlock = 0
413 | return 0 # keep knitting
414 | else:
415 | return 1 # image finished
416 | else:
417 | return 0 # keep knitting
418 |
419 | def __knitImage(self, pImage, pOptions):
420 | self.__formerRequest = 0
421 | self.__image = pImage
422 | self.__startLine = pImage.startLine()
423 |
424 | self.__numColors = pOptions.get("colors", 2)
425 | self.__machineType = pOptions.get("machine_type", "single")
426 | self.__infRepeat = pOptions.get("inf_repeat", False)
427 |
428 | API_VERSION = self.__API_VERSION
429 | curState = 's_init'
430 | oldState = 'none'
431 |
432 | if not self.__ayabCom.open_serial(self.__portname):
433 | logging.error("Could not open serial port")
434 | return
435 |
436 | self._knitImage = True
437 | logging.debug("Ready to start knitting process.")
438 | while self._knitImage:
439 | # TODO catch keyboard interrupts to abort knitting
440 | # TODO: port to state machine or similar.
441 | rcvMsg, rcvParam = self.__checkSerial()
442 | if curState == 's_init':
443 | if oldState != curState:
444 | self.__ayabCom.req_info()
445 |
446 | if rcvMsg == 'cnfInfo':
447 | if rcvParam == API_VERSION:
448 | curState = 's_start'
449 | self.__wait_for_user_action(
450 | "Please init machine. (Set the carriage to mode KC-I or KC-II and move the carriage over the left turn mark).")
451 | else:
452 | self.__notify_user("Wrong API.")
453 | logging.error("wrong API version: " + str(rcvParam)
454 | + (" (expected: )") + str(API_VERSION))
455 | return
456 |
457 | if curState == 's_start':
458 | if oldState != curState:
459 | self.__ayabCom.req_start(self.__image.knitStartNeedle(),
460 | self.__image.knitStopNeedle())
461 |
462 | if rcvMsg == 'cnfStart':
463 | if rcvParam == 1:
464 | curState = 's_operate'
465 | self.__wait_for_user_action("Ready to Operate")
466 | else:
467 | self.__wait_for_user_action("Device not ready, configure and try again.")
468 | logging.error("device not ready")
469 | return
470 |
471 | if curState == 's_operate':
472 | if rcvMsg == 'reqLine':
473 | imageFinished = self.__cnfLine(rcvParam)
474 | if imageFinished:
475 | curState = 's_finished'
476 |
477 | if curState == 's_finished':
478 | self.__wait_for_user_action(
479 | "Image transmission finished. Please knit until you hear the double beep sound.")
480 | return
481 |
482 | oldState = curState
483 |
484 | return
485 |
--------------------------------------------------------------------------------
/src/knitlib/plugins/pdd_plugin/PDDemulate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2009 Steve Conklin
4 | # steve at conklinhouse dot com
5 | #
6 | # This program is free software; you can redistribute it and/or
7 | # modify it under the terms of the GNU General Public License
8 | # as published by the Free Software Foundation; either version 2
9 | # of the License, or (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program; if not, write to the Free Software
18 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 |
20 |
21 | # This software emulates the external floppy disk drive used
22 | # by the Brother Electroknit KH-930E computerized knitting machine.
23 | # It may work for other models, but has only been tested with the
24 | # Brother KH-930E
25 | #
26 | # This emulates the disk drive and stores the saved data from
27 | # the knitting machine on the linux file system. It does not
28 | # read or write floppy disks.
29 | #
30 | # The disk drive used by the brother knitting machine is the same
31 | # as a Tandy PDD1 drive. This software does not support the entire
32 | # command API of the PDD1, only what is required for the knitting
33 | # machine.
34 | #
35 |
36 | #
37 | # Notes about data storage:
38 | #
39 | # The external floppy disk is formatted with 80 sectors of 1024
40 | # bytes each. These sectors are numbered (internally) from 0-79.
41 | # When starting this emulator, a base directory is specified.
42 | # In this directory the emulator creates 80 files, one for each
43 | # sector. These are kept sync'd with the emulator's internal
44 | # storage of the same sectors. For each sector, there are two
45 | # files, nn.dat, and nn.id, where 00 <= nn <= 79.
46 | #
47 | # The knitting machine uses two sectors for each saved set of
48 | # information, which are referred to in the knitting machine
49 | # manual as 'tracks' (which they were on the floppy disk). Each
50 | # pair of even/odd numbered sectors is a track. Tracks are
51 | # numbered 1-40. The knitting machine always writes sectors
52 | # in even/odd pairs, and when the odd sector is written, both
53 | # sectors are concatenated to a file named fileqq.dat, where
54 | # qq is the sector number.
55 | #
56 |
57 | # The Knitting machine does not parse the returned hex ascii values
58 | # unless they are ALL UPPER CASE. Lower case characters a-f appear
59 | # to parse az zeros.
60 |
61 | # You will need the (very nice) pySerial module, found here:
62 | # http://pyserial.wiki.sourceforge.net/pySerial
63 |
64 | import sys
65 | import os
66 | import os.path
67 | import string
68 | from array import array
69 | import serial
70 | import logging
71 |
72 | version = '2.5'
73 |
74 | #
75 | # Note that this code makes a fundamental assumption which
76 | # is only true for the disk format used by the brother knitting
77 | # machine, which is that there is only one logical sector (LS) per
78 | # physical sector (PS). The PS size is fixed at 1280 bytes, and
79 | # the brother uses a LS size of 1024 bytes, so only one can fit.
80 | #
81 |
82 |
83 | class DiskSector():
84 | def __init__(self, fn):
85 | self.sectorSz = 1024
86 | self.idSz = 12
87 | self.data = ''
88 | self.id = ''
89 | # self.id = array('c')
90 |
91 | dfn = fn + ".dat"
92 | idfn = fn + ".id"
93 |
94 | try:
95 | try:
96 | self.df = open(dfn, 'r+')
97 | except IOError:
98 | self.df = open(dfn, 'w')
99 |
100 | try:
101 | self.idf = open(idfn, 'r+')
102 | except IOError:
103 | self.idf = open(idfn, 'w')
104 |
105 | dfs = os.path.getsize(dfn)
106 | idfs = os.path.getsize(idfn)
107 |
108 | except:
109 | logging.error('Unable to open files using base name <%s>' % fn)
110 | raise
111 |
112 | try:
113 | if dfs == 0:
114 | # New or empty file
115 | self.data = ''.join([chr(0) for num in xrange(self.sectorSz)])
116 | self.writeDFile()
117 | elif dfs == self.sectorSz:
118 | # Existing file
119 | self.data = self.df.read(self.sectorSz)
120 | else:
121 | logging.error('Found a data file <%s> with the wrong size' % dfn)
122 | raise IOError
123 | except:
124 | print 'Unable to handle data file <%s>' % fn
125 | raise
126 |
127 | try:
128 | if idfs == 0:
129 | # New or empty file
130 | self.id = ''.join([chr(0) for num in xrange(self.idSz)])
131 | self.writeIdFile()
132 | elif idfs == self.idSz:
133 | # Existing file
134 | self.id = self.idf.read(self.idSz)
135 | else:
136 | logging.error('Found an ID file <%s> with the wrong size, is %d should be %d' % (idfn, idfs, self.idSz))
137 | raise IOError
138 | except:
139 | logging.error('Unable to handle id file <%s>' % fn)
140 | raise
141 |
142 | return
143 |
144 | def __del__(self):
145 | return
146 |
147 | def format(self):
148 | self.data = ''.join([chr(0) for num in xrange(self.sectorSz)])
149 | self.writeDFile()
150 | self.id = ''.join([chr(0) for num in xrange(self.idSz)])
151 | self.writeIdFile()
152 |
153 | def writeDFile(self):
154 | self.df.seek(0)
155 | self.df.write(self.data)
156 | self.df.flush()
157 | return
158 |
159 | def writeIdFile(self):
160 | self.idf.seek(0)
161 | self.idf.write(self.id)
162 | self.idf.flush()
163 | return
164 |
165 | def read(self, length):
166 | if length != self.sectorSz:
167 | logging.error('Error, read of %d bytes when expecting %d' % (length, self.sectorSz))
168 | raise IOError
169 | return self.data
170 |
171 | def write(self, indata):
172 | if len(indata) != self.sectorSz:
173 | logging.error('Error, write of %d bytes when expecting %d' % (len(indata), self.sectorSz))
174 | raise IOError
175 | self.data = indata
176 | self.writeDFile()
177 | return
178 |
179 | def getSectorId(self):
180 | return self.id
181 |
182 | def setSectorId(self, newid):
183 | if len(newid) != self.idSz:
184 | logging.error('Error, bad id length of %d bytes when expecting %d' % (len(newid), self.id))
185 | raise IOError
186 | self.id = newid
187 | self.writeIdFile()
188 | logging.info('Wrote New ID: {}'.format(self.dumpId()))
189 | return
190 |
191 | def dumpId(self):
192 | id_l = []
193 | for i in self.id:
194 | id_l.append('%02X ' % ord(i))
195 | return " ".join(id_l)
196 |
197 |
198 | class Disk():
199 | """
200 | Fields:
201 | self.lastDatFilePath : string
202 | """
203 |
204 | def __init__(self, basename):
205 | self.numSectors = 80
206 | self.Sectors = []
207 | self.filespath = ""
208 | ""
209 | self.lastDatFilePath = None
210 | # Set up disk Files and internal buffers
211 |
212 | # if absolute path, just accept it
213 | if os.path.isabs(basename):
214 | dirpath = basename
215 | else:
216 | dirpath = os.path.abspath(basename)
217 |
218 | if os.path.exists(dirpath):
219 | if not os.access(dirpath, os.R_OK | os.W_OK):
220 | logging.error('Directory <%s> exists but cannot be accessed, check permissions' % dirpath)
221 | raise IOError
222 | elif not os.path.isdir(dirpath):
223 | logging.error('Specified path <%s> exists but is not a directory' % dirpath)
224 | raise IOError
225 | else:
226 | try:
227 | os.mkdir(dirpath)
228 | except:
229 | logging.error('Unable to create directory <%s>' % dirpath)
230 | raise IOError
231 |
232 | self.filespath = dirpath
233 | # we have a directory now - set up disk sectors
234 | for i in range(self.numSectors):
235 | fname = os.path.join(dirpath, '%02d' % i)
236 | ds = DiskSector(fname)
237 | self.Sectors.append(ds)
238 | return
239 |
240 | def __del__(self):
241 | return
242 |
243 | def format(self):
244 | for i in range(self.numSectors):
245 | self.Sectors[i].format()
246 | return
247 |
248 | def findSectorID(self, psn, id):
249 | for i in range(psn, self.numSectors):
250 | sid = self.Sectors[i].getSectorId()
251 | if id == sid:
252 | return '00' + '%02X' % i + '0000'
253 | return '40000000'
254 |
255 | def getSectorID(self, psn):
256 | return self.Sectors[psn].getSectorId()
257 |
258 | def setSectorID(self, psn, id):
259 | self.Sectors[psn].setSectorId(id)
260 | return
261 |
262 | def writeSector(self, psn, lsn, indata):
263 | self.Sectors[psn].write(indata)
264 | if psn % 2:
265 | filenum = ((psn - 1) / 2) + 1
266 | filename = 'file-%02d.dat' % filenum
267 | # we wrote an odd sector, so create the
268 | # associated file
269 | fn1 = os.path.join(self.filespath, '%02d.dat' % (psn - 1))
270 | fn2 = os.path.join(self.filespath, '%02d.dat' % psn)
271 | outfn = os.path.join(self.filespath, filename)
272 | # TODO: change this to a pure Python binary merge.
273 | cmd = 'cat %s %s > %s' % (fn1, fn2, outfn)
274 | os.system(cmd)
275 | self.lastDatFilePath = outfn
276 | return
277 |
278 | def readSector(self, psn, lsn):
279 | return self.Sectors[psn].read(1024)
280 |
281 |
282 | class PDDemulator():
283 | def __init__(self, basename):
284 | self.listeners = [] # list of PDDEmulatorListener
285 | self.verbose = True
286 | self.noserial = False
287 | self.ser = None
288 | self.disk = Disk(basename)
289 | self.FDCmode = False
290 | # bytes per logical sector
291 | self.bpls = 1024
292 | self.formatLength = {'0': 64, '1': 80, '2': 128, '3': 256, '4': 512, '5': 1024, '6': 1280}
293 | return
294 |
295 | def __del__(self):
296 | return
297 |
298 | def open(self, cport='/dev/ttyUSB0'):
299 | if self.noserial is False:
300 | self.ser = serial.Serial(port=cport, baudrate=9600, parity='N', stopbits=1, timeout=1, xonxoff=0, rtscts=0,
301 | dsrdtr=0)
302 | # self.ser.setRTS(True)
303 | if self.ser is None:
304 | logging.error('Unable to open serial device %s' % cport)
305 | raise IOError
306 | return
307 |
308 | def close(self):
309 | if self.noserial is not False:
310 | if self.ser:
311 | self.ser.close()
312 | return
313 |
314 | def dumpchars(self):
315 | num = 1
316 | while 1:
317 | inc = self.ser.read()
318 | if len(inc) != 0:
319 | logging.info('flushed 0x%02X (%d)' % (ord(inc), num))
320 | num += 1
321 | else:
322 | break
323 | return
324 |
325 | def readsomechars(self, num):
326 | sch = self.ser.read(num)
327 | return sch
328 |
329 | def readchar(self):
330 | inc = ''
331 | while len(inc) == 0:
332 | inc = self.ser.read()
333 | return inc
334 |
335 | def writebytes(self, bytes):
336 | self.ser.write(bytes)
337 | return
338 |
339 | def readFDDRequest(self):
340 | inbuf = []
341 | # read through a carriage return
342 | # parameters are seperated by commas
343 | while 1:
344 | inc = self.readchar()
345 | if inc == '\r':
346 | break
347 | elif inc == ' ':
348 | continue
349 | else:
350 | inbuf.append(inc)
351 |
352 | all = string.join(inbuf, '')
353 | rv = all.split(',')
354 | return rv
355 |
356 | def getPsnLsn(self, info):
357 | psn = 0
358 | lsn = 1
359 | if len(info) >= 1 and info[0] != '':
360 | val = int(info[0])
361 | if psn <= 79:
362 | psn = val
363 | if len(info) > 1 and info[1] != '':
364 | val = int(info[0])
365 | return psn, lsn
366 |
367 | def readOpmodeRequest(self, req):
368 | buff = array('b')
369 | sum = req
370 | reqlen = ord(self.readchar())
371 | buff.append(reqlen)
372 | sum = sum + reqlen
373 |
374 | for x in range(reqlen, 0, -1):
375 | rb = ord(self.readchar())
376 | buff.append(rb)
377 | sum = sum + rb
378 |
379 | # calculate ckecksum
380 | sum = sum % 0x100
381 | sum = sum ^ 0xFF
382 |
383 | cksum = ord(self.readchar())
384 |
385 | if cksum == sum:
386 | return buff
387 | else:
388 | logging.warning('Checksum mismatch!!')
389 | return None
390 |
391 | def handleRequests(self):
392 | synced = False
393 | while True:
394 | self.handleRequest()
395 | if synced:
396 | break
397 | # never returns
398 | return
399 |
400 | def handleRequest(self, blocking=True):
401 | if not blocking:
402 | if self.ser.inWaiting() == 0:
403 | return
404 | inc = self.readchar()
405 | if self.FDCmode:
406 | self.handleFDCmodeRequest(inc)
407 | else:
408 | # in OpMode, look for ZZ
409 | # inc = self.readchar()
410 | if inc != 'Z':
411 | return
412 | inc = self.readchar()
413 | if inc == 'Z':
414 | self.handleOpModeRequest()
415 |
416 | def handleOpModeRequest(self):
417 | req = ord(self.ser.read())
418 | logging.debug('Request: 0X%02X' % req)
419 | if req == 0x08:
420 | # Change to FDD emulation mode (no data returned)
421 | inbuf = self.readOpmodeRequest(req)
422 | if inbuf is not None:
423 | # Change Modes, leave any incoming serial data in buffer
424 | self.FDCmode = True
425 | else:
426 | logging.warning('Invalid OpMode request code 0X%02X received' % req)
427 | return
428 |
429 | def handleFDCmodeRequest(self, cmd):
430 | # Commands may be followed by an optional space
431 | # PSN (physical sector) range 0-79
432 | # LSN (logical sector) range 0-(number of logical sectors in a physical sector)
433 | # LSN defaults to 1 if not supplied
434 | #
435 | # Result code information (verbatim from the Tandy reference):
436 | #
437 | # After the drive receives a command in FDC-emulation mode, it transmits
438 | # 8 byte characters which represent 4 bytes of status code in hexadecimal.
439 | #
440 | # * The first and second bytes contain the error status. A value of '00'
441 | # indicates that no error occurred
442 | #
443 | # * The third and fourth bytes usually contain the number of the physical
444 | # sector where data is kept in the buffer
445 | #
446 | # For the D, F, and S commands, the contents of these bytes are different.
447 | # See the command descriptions in these cases.
448 | #
449 | # * The fifth-eighth bytes usual show the logical sector length of the data
450 | # kept in the RAM buffer, except the third and fourth digits are 'FF'
451 | #
452 | # In the case of an S, C, or M command -- or an F command that ends in
453 | # an error -- the bytes contain '0000'
454 | #
455 |
456 | if cmd == '\r':
457 | return
458 |
459 | if cmd == 'Z':
460 | # Hmmm, looks like we got the start of an Opmode Request
461 | inc = self.readchar()
462 | if inc == 'Z':
463 | # definitely!
464 | logging.info('Detected Opmode Request in FDC Mode, switching to OpMode')
465 | self.FDCmode = False
466 | self.handleOpModeRequest()
467 |
468 | elif cmd == 'M':
469 | # apparently not used by brother knitting machine
470 | logging.info('FDC Change Modes')
471 | raise
472 | # following parameter - 0=FDC, 1=Operating
473 |
474 | elif cmd == 'D':
475 | # apparently not used by brother knitting machine
476 | logging.info('FDC Check Device')
477 | raise
478 | # Sends result in third and fourth bytes of result code
479 | # See doc - return zero for disk installed and not swapped
480 |
481 | elif cmd == 'F' or cmd == 'G':
482 | # rint 'FDC Format',
483 | info = self.readFDDRequest()
484 |
485 | if len(info) != 1:
486 | logging.warning('wrong number of params (%d) received, assuming 1024 bytes per sector' % len(info))
487 | bps = 1024
488 | else:
489 | try:
490 | bps = self.formatLength[info[0]]
491 | except KeyError:
492 | logging.error('Invalid code %c for format, assuming 1024 bytes per sector' % info[0])
493 | bps = 1024
494 | # we assume 1024 because that's what the brother machine uses
495 | if self.bpls != bps:
496 | logging.error('Bad news, differing sector sizes')
497 | self.bpls = bps
498 |
499 | self.disk.format()
500 |
501 | # But this is probably more correct
502 | self.writebytes('00000000')
503 |
504 | # After a format, we always start out with OPMode again
505 | self.FDCmode = False
506 |
507 | elif cmd == 'A':
508 | # Followed by physical sector number (0-79), defaults to 0
509 | # returns ID data, not sector data
510 | info = self.readFDDRequest()
511 | psn, lsn = self.getPsnLsn(info)
512 | logging.debug('FDC Read ID Section %d' % psn)
513 |
514 | try:
515 | id = self.disk.getSectorID(psn)
516 | except:
517 | logging.error('Error getting Sector ID %d, quitting' % psn)
518 | self.writebytes('80000000')
519 | raise
520 |
521 | self.writebytes('00' + '%02X' % psn + '0000')
522 |
523 | # see whether to send data
524 | go = self.readchar()
525 | if go == '\r':
526 | self.writebytes(id)
527 |
528 | elif cmd == 'R':
529 | # Followed by Physical Sector Number PSN and Logical Sector Number LSN
530 | info = self.readFDDRequest()
531 | psn, lsn = self.getPsnLsn(info)
532 | logging.info('FDC Read one Logical Sector %d' % psn)
533 |
534 | try:
535 | sd = self.disk.readSector(psn, lsn)
536 | except:
537 | logging.error('Failed to read Sector %d, quitting' % psn)
538 | self.writebytes('80000000')
539 | raise
540 |
541 | self.writebytes('00' + '%02X' % psn + '0000')
542 |
543 | # see whether to send data
544 | go = self.readchar()
545 | if go == '\r':
546 | self.writebytes(sd)
547 |
548 | elif cmd == 'S':
549 | # We receive (optionally) PSN, (optionally) LSN
550 | # This is not documented well at all in the manual
551 | # What is expected is that all sectors will be searched
552 | # and the sector number of the first matching sector
553 | # will be returned. The brother machine always sends
554 | # PSN = 0, so it is unknown whether searching should
555 | # start at Sector 0 or at the PSN sector
556 | info = self.readFDDRequest()
557 | psn, lsn = self.getPsnLsn(info)
558 | logging.debug('FDC Search ID Section %d' % psn)
559 |
560 | # Now we must send status (success)
561 | self.writebytes('00' + '%02X' % psn + '0000')
562 |
563 | # self.writebytes('00000000')
564 |
565 | # we receive 12 bytes here
566 | # compare with the specified sector (formatted is apparently zeros)
567 | id = self.readsomechars(12)
568 | logging.debug('checking ID for sector %d' % psn)
569 |
570 | try:
571 | status = self.disk.findSectorID(psn, id)
572 | except:
573 | logging.error("Fail at finding sector ID")
574 | status = '30000000'
575 | raise
576 |
577 | print 'returning %s' % status
578 | # guessing - doc is unclear, but says that S always ends in 0000
579 | # MATCH 00000000
580 | # MATCH 02000000
581 | # infinite retries 10000000
582 | # infinite retries 20000000
583 | # blinking error 30000000
584 | # blinking error 40000000
585 | # infinite retries 50000000
586 | # infinite retries 60000000
587 | # infinite retries 70000000
588 | # infinite retries 80000000
589 |
590 | self.writebytes(status)
591 |
592 | # Stay in FDC mode
593 |
594 | elif cmd == 'B' or cmd == 'C':
595 | # Followed by PSN 0-79, defaults to 0
596 | # When received, send result status, if not error, wait
597 | # for data to be written, then after write, send status again
598 | info = self.readFDDRequest()
599 | psn, lsn = self.getPsnLsn(info)
600 | logging.debug('FDC Write ID section %d' % psn)
601 |
602 | self.writebytes('00' + '%02X' % psn + '0000')
603 |
604 | id = self.readsomechars(12)
605 |
606 | try:
607 | self.disk.setSectorID(psn, id)
608 | except:
609 | logging.error('Failed to write ID for sector %d, quitting' % psn)
610 | self.writebytes('80000000')
611 | raise
612 |
613 | self.writebytes('00' + '%02X' % psn + '0000')
614 |
615 | elif cmd == 'W' or cmd == 'X':
616 | info = self.readFDDRequest()
617 | psn, lsn = self.getPsnLsn(info)
618 | logging.debug('FDC Write logical sector %d' % psn)
619 |
620 | # Now we must send status (success)
621 | self.writebytes('00' + '%02X' % psn + '0000')
622 |
623 | indata = self.readsomechars(1024)
624 | try:
625 | self.disk.writeSector(psn, lsn, indata)
626 | for l in self.listeners:
627 | l.dataReceived(self.disk.lastDatFilePath)
628 | logging.info('Saved data in dat file: ', self.disk.lastDatFilePath)
629 | except:
630 | logging.error('Failed to write data for sector %d, quitting' % psn)
631 | self.writebytes('80000000')
632 | raise
633 |
634 | self.writebytes('00' + '%02X' % psn + '0000')
635 |
636 | else:
637 | print 'Unknown FDC command <0x02%X> received' % ord(cmd)
638 |
639 | # return to Operational Mode
640 | return
641 |
642 |
643 | class PDDEmulatorListener:
644 | def dataReceived(self, fullFilePath):
645 | pass
646 |
647 | # meat and potatos here
648 |
649 | if __name__ == "__main__":
650 | if len(sys.argv) < 3:
651 | print '%s version %s' % (sys.argv[0], version)
652 | print 'Usage: %s basedir serialdevice' % sys.argv[0]
653 | sys.exit()
654 |
655 | print 'Preparing . . . Please Wait'
656 | emu = PDDemulator(sys.argv[1])
657 |
658 | # TODO: fix usb port hardcoding
659 | emu.open(cport=sys.argv[2])
660 |
661 | print 'Emulator Ready!'
662 | try:
663 | while 1:
664 | emu.handleRequests()
665 | except (KeyboardInterrupt):
666 | pass
667 |
668 | emu.close()
669 |
--------------------------------------------------------------------------------