diff --git a/.editorconfig b/.editorconfig index eb8bd95..d4a2c44 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,21 +1,21 @@ -# http://editorconfig.org - -root = true - -[*] -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true -insert_final_newline = true -charset = utf-8 -end_of_line = lf - -[*.bat] -indent_style = tab -end_of_line = crlf - -[LICENSE] -insert_final_newline = false - -[Makefile] -indent_style = tab +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true +charset = utf-8 +end_of_line = lf + +[*.bat] +indent_style = tab +end_of_line = crlf + +[LICENSE] +insert_final_newline = false + +[Makefile] +indent_style = tab diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9f5fac5..a43c0f2 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,15 +1,15 @@ -* pyusbiss version: -* Python version: -* Operating System: - -### Description - -Describe what you were trying to get done. -Tell us what happened, what went wrong, and what you expected to happen. - -### What I Did - -``` -Paste the command(s) you ran and the output. -If there was a crash, please include the traceback here. -``` +* pyusbiss version: +* Python version: +* Operating System: + +### Description + +Describe what you were trying to get done. +Tell us what happened, what went wrong, and what you expected to happen. + +### What I Did + +``` +Paste the command(s) you ran and the output. +If there was a crash, please include the traceback here. +``` diff --git a/.gitignore b/.gitignore index f9aba89..e4a2b1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,59 +1,70 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +Scripts/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# GdH +.bat +.exe +.DLL +.ps1 + +# vsc +.vscode/ +.code-workspace diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..07bd59f --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,132 @@ +.. highlight:: shell + +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every little bit +helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/DancingQuanta/pyusbiss/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" and "help +wanted" is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +pyusbiss could always use more documentation, whether as part of the +official pyusbiss docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/DancingQuanta/pyusbiss/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `pyusbiss` for local development. + +1. Fork the `pyusbiss` repo on GitHub. +2. Clone your fork locally + +:: + + $ git clone git@github.com:your_name_here/pyusbiss.git + +3. Install your local copy into a virtualenv. Assuming you have + virtualenvwrapper installed, this is how you set up your fork for local + development + +:: + + $ mkvirtualenv pyusbiss + $ cd pyusbiss/ + $ python setup.py develop + +4. Create a branch for local development + +:: + + $ git checkout -b name-of-your-bugfix-or-feature + + Now you can make your changes locally. + +5. When you're done making changes, check that your changes pass flake8 and the + tests, including testing other Python versions with tox + +:: + + $ flake8 uabiss tests + $ python setup.py test + $ tox + + To get flake8 and tox, just pip install them into your virtualenv. + +6. Commit your changes and push your branch to GitHub + +:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +7. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 3.4, 3.5 and 3.6, and for PyPy. + +Deploying +--------- + +A reminder for the maintainers on how to deploy. +Make sure all your changes are committed (including an entry in HISTORY.rst). +Then run + +:: + +$ bumpversion patch # possible: major / minor / patch +$ git push +$ git push --tags + +Travis will then deploy to PyPI if tests pass. diff --git a/Devices/lcd_api.py b/Devices/lcd_api.py new file mode 100644 index 0000000..38b60da --- /dev/null +++ b/Devices/lcd_api.py @@ -0,0 +1,195 @@ +"""Provides an API for talking to HD44780 compatible character LCDs.""" + +import time + +class LcdApi: + """Implements the API for talking with HD44780 compatible character LCDs. + This class only knows what commands to send to the LCD, and not how to get + them to the LCD. + + It is expected that a derived class will implement the hal_xxx functions. + """ + + # The following constant names were lifted from the avrlib lcd.h + # header file, however, I changed the definitions from bit numbers + # to bit masks. + # + # HD44780 LCD controller command set + + LCD_CLR = 0x01 # DB0: clear display + LCD_HOME = 0x02 # DB1: return to home position + + LCD_ENTRY_MODE = 0x04 # DB2: set entry mode + LCD_ENTRY_INC = 0x02 # --DB1: increment + LCD_ENTRY_SHIFT = 0x01 # --DB0: shift + + LCD_ON_CTRL = 0x08 # DB3: turn lcd/cursor on + LCD_ON_DISPLAY = 0x04 # --DB2: turn display on + LCD_ON_CURSOR = 0x02 # --DB1: turn cursor on + LCD_ON_BLINK = 0x01 # --DB0: blinking cursor + + LCD_MOVE = 0x10 # DB4: move cursor/display + LCD_MOVE_DISP = 0x08 # --DB3: move display (0-> move cursor) + LCD_MOVE_RIGHT = 0x04 # --DB2: move right (0-> left) + + LCD_FUNCTION = 0x20 # DB5: function set + LCD_FUNCTION_8BIT = 0x10 # --DB4: set 8BIT mode (0->4BIT mode) + LCD_FUNCTION_2LINES = 0x08 # --DB3: two lines (0->one line) + LCD_FUNCTION_10DOTS = 0x04 # --DB2: 5x10 font (0->5x7 font) + LCD_FUNCTION_RESET = 0x30 # See "Initializing by Instruction" section + + LCD_CGRAM = 0x40 # DB6: set CG RAM address + LCD_DDRAM = 0x80 # DB7: set DD RAM address + + LCD_RS_CMD = 0 + LCD_RS_DATA = 1 + + LCD_RW_WRITE = 0 + LCD_RW_READ = 1 + + def __init__(self, num_lines, num_columns): + self.num_lines = num_lines + if self.num_lines > 4: + self.num_lines = 4 + self.num_columns = num_columns + if self.num_columns > 40: + self.num_columns = 40 + self.cursor_x = 0 + self.cursor_y = 0 + self.backlight = True + self.display_off() + self.backlight_on() + self.clear() + self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC) + self.hide_cursor() + self.display_on() + + def clear(self): + """Clears the LCD display and moves the cursor to the top left + corner. + """ + self.hal_write_command(self.LCD_CLR) + self.hal_write_command(self.LCD_HOME) + self.cursor_x = 0 + self.cursor_y = 0 + + def show_cursor(self): + """Causes the cursor to be made visible.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | + self.LCD_ON_CURSOR) + + def hide_cursor(self): + """Causes the cursor to be hidden.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY) + + def blink_cursor_on(self): + """Turns on the cursor, and makes it blink.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | + self.LCD_ON_CURSOR | self.LCD_ON_BLINK) + + def blink_cursor_off(self): + """Turns on the cursor, and makes it no blink (i.e. be solid).""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | + self.LCD_ON_CURSOR) + + def display_on(self): + """Turns on (i.e. unblanks) the LCD.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY) + + def display_off(self): + """Turns off (i.e. blanks) the LCD.""" + self.hal_write_command(self.LCD_ON_CTRL) + + def backlight_on(self): + """Turns the backlight on. + + This isn't really an LCD command, but some modules have backlight + controls, so this allows the hal to pass through the command. + """ + self.backlight = True + self.hal_backlight_on() + + def backlight_off(self): + """Turns the backlight off. + + This isn't really an LCD command, but some modules have backlight + controls, so this allows the hal to pass through the command. + """ + self.backlight = False + self.hal_backlight_off() + + def move_to(self, cursor_x, cursor_y): + """Moves the cursor position to the indicated position. The cursor + position is zero based (i.e. cursor_x == 0 indicates first column). + """ + self.cursor_x = cursor_x + self.cursor_y = cursor_y + addr = cursor_x & 0x3f + if cursor_y & 1: + addr += 0x40 # Lines 1 & 3 add 0x40 + if cursor_y & 2: + addr += 0x14 # Lines 2 & 3 add 0x14 + self.hal_write_command(self.LCD_DDRAM | addr) + + def putchar(self, char): + """Writes the indicated character to the LCD at the current cursor + position, and advances the cursor by one position. + """ + if char != '\n': + self.hal_write_data(ord(char)) + self.cursor_x += 1 + if self.cursor_x >= self.num_columns or char == '\n': + self.cursor_x = 0 + self.cursor_y += 1 + if self.cursor_y >= self.num_lines: + self.cursor_y = 0 + self.move_to(self.cursor_x, self.cursor_y) + + def putstr(self, string): + """Write the indicated string to the LCD at the current cursor + position and advances the cursor position appropriately. + """ + for char in string: + self.putchar(char) + + def custom_char(self, location, charmap): + """Write a character to one of the 8 CGRAM locations, available + as chr(0) through chr(7). + """ + location &= 0x7 + self.hal_write_command(self.LCD_CGRAM | (location << 3)) + time.sleep_us(40) + for i in range(8): + self.hal_write_data(charmap[i]) + time.sleep_us(40) + self.move_to(self.cursor_x, self.cursor_y) + + def hal_backlight_on(self): + """Allows the hal layer to turn the backlight on. + + If desired, a derived HAL class will implement this function. + """ + pass + + def hal_backlight_off(self): + """Allows the hal layer to turn the backlight off. + + If desired, a derived HAL class will implement this function. + """ + pass + + def hal_write_command(self, cmd): + """Write a command to the LCD. + + It is expected that a derived HAL class will implement this + function. + """ + raise NotImplementedError + + def hal_write_data(self, data): + """Write data to the LCD. + + It is expected that a derived HAL class will implement this + function. + """ + raise NotImplementedError diff --git a/Devices/mcp9808.py b/Devices/mcp9808.py new file mode 100644 index 0000000..04ee32c --- /dev/null +++ b/Devices/mcp9808.py @@ -0,0 +1,503 @@ +# Copyright (c) 2014 Miguel Ercolino +# Author: Miguel Ercolino +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# Adapted for use with the USBISS - I2C module by Geert de Haan (04-2019) + +import logging +import math +from usbiss import i2c as I2C + +# Default I2C address for device. +MCP9808_I2CADDR_DEFAULT = 0x18 + +# Register addresses. +MCP9808_REG_CONFIG = 0x01 +MCP9808_REG_UPPER_TEMP = 0x02 +MCP9808_REG_LOWER_TEMP = 0x03 +MCP9808_REG_CRIT_TEMP = 0x04 +MCP9808_REG_AMBIENT_TEMP = 0x05 +MCP9808_REG_MANUF_ID = 0x06 +MCP9808_REG_DEVICE_ID = 0x07 +MCP9808_REG_RESOLUTION = 0x08 + +# Configuration register values. +MCP9808_REG_CONFIG_SHUTDOWN = 0x0100 +MCP9808_REG_CONFIG_CRITLOCKED = 0x0080 +MCP9808_REG_CONFIG_WINLOCKED = 0x0040 +MCP9808_REG_CONFIG_INTCLR = 0x0020 +MCP9808_REG_CONFIG_ALERTSTAT = 0x0010 +MCP9808_REG_CONFIG_ALERTCTRL = 0x0008 +MCP9808_REG_CONFIG_ALERTSEL = 0x0004 +MCP9808_REG_CONFIG_ALERTPOL = 0x0002 +MCP9808_REG_CONFIG_ALERTMODE = 0x0001 + +class MCP9808(I2C.I2CDevice): + """Class to represent an Adafruit MCP9808 precision temperature measurement + board. + """ + + def __init__(self, i2c_channel, address=MCP9808_I2CADDR_DEFAULT): + """Initialize MCP9808 device on the specified I2C address and bus number. + Address defaults to 0x18 + """ + self._logger = logging.getLogger('MCP9808') + # if i2c is None: + # import Adafruit_GPIO.I2C as I2C + # self._i2c = I2C + # self._device = self._i2c.get_i2c_device(address, **kwargs) + self._i2c_channel = i2c_channel + self._i2c_addr = address + I2C.I2CDevice.__init__(self, self._i2c_channel, self._i2c_addr) + + + def begin(self): + """Start taking temperature measurements. Returns True if the device is + intialized, False otherwise. + """ + # Check manufacturer and device ID match expected values. + mid = self.readU16BE(MCP9808_REG_MANUF_ID) + did = self.readU16BE(MCP9808_REG_DEVICE_ID) + self._logger.debug('Read manufacturer ID: {0:#06X}'.format(mid)) + self._logger.debug('Read device ID: {0:#06X}'.format(did)) + return mid == 0x0054 and did == 0x0400 + + def getConfigReg(self): + """Returns the Config register value""" + # Read Config Register value + return self.readU16BE(MCP9808_REG_CONFIG) + + def clearConfigReg(self): + """Clear the Config Register value""" + self.write16(MCP9808_REG_CONFIG, 0x0000) + + def setTempHyst(self, thyst=0): + """Set the Temperature hysteresis, the valid values are 0, +1.5, +3.0, +6.0 Celsius Degrees + if the thyst is not a valid then it will not make any modification to the register, this function + returns a list with [a,b], a is 1 if thyst was valid and 0 if it was not, b is the new value of the + config register if it was successfull and if was not it returns an Error String""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Validate thyst + if thyst == 0: + t = 0x0000 + new_config = (config & 0xF9FF) | t + # self.write16(MCP9808_REG_CONFIG, self._i2c.reverseByteOrder(new_config)) + self.write16(MCP9808_REG_CONFIG, new_config, False) + self._logger.debug('Temperature Hysteresis set: {0:#06X}'.format(new_config)) + return [1, self.readU16BE(MCP9808_REG_CONFIG)] + elif thyst == 1.5: + t = 0x0200 + new_config = (config & 0xF9FF) | t + #self.write16(MCP9808_REG_CONFIG, self._i2c.reverseByteOrder(new_config)) + self.write16(MCP9808_REG_CONFIG, (new_config), False) + self._logger.debug('Temperature Hysteresis set: {0:#06X}'.format(new_config)) + return [1, self.readU16BE(MCP9808_REG_CONFIG)] + elif thyst == 3: + t = 0x0400 + new_config = (config & 0xF9FF) | t + #self.write16(MCP9808_REG_CONFIG, self._i2c.reverseByteOrder(new_config)) + self.write16(MCP9808_REG_CONFIG, (new_config), False) + self._logger.debug('Temperature Hysteresis set: {0:#06X}'.format(new_config)) + return [1, self.readU16BE(MCP9808_REG_CONFIG)] + elif thyst== 6: + t = 0x0600 + new_config = (config & 0xF9FF) | t + #self.write16(MCP9808_REG_CONFIG, self._i2c.reverseByteOrder(new_config)) + self.write16(MCP9808_REG_CONFIG, (new_config), False) + self._logger.debug('Temperature Hysteresis set: {0:#06X}'.format(new_config)) + return [1, self.readU16BE(MCP9808_REG_CONFIG)] + else: + return [0, 'Temperature Hysteresis is not valid, Valid Values are: 0, +1.5, +3, +6'] + self._logger.debug('Error setting the Temperature Hysteresis') + + def setShutdown(self): + """Set shutdown bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Set the shutdown bit + self.write16(MCP9808_REG_CONFIG, config | MCP9808_REG_CONFIG_SHUTDOWN, False) + self._logger.debug('The Shutdown set: {0:#06X}'.format(config | MCP9808_REG_CONFIG_SHUTDOWN)) + + def clearShutdown(self): + """Clear shutdown bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Clear the Shutdown bit + self.write16(MCP9808_REG_CONFIG, config & ~MCP9808_REG_CONFIG_SHUTDOWN, False) + self._logger.debug('The Shutdown Clear: {0:#06X}'.format(config | ~MCP9808_REG_CONFIG_SHUTDOWN)) + + def setCritLock(self): + """Set Critical lock bit on the config register, be careful once set it can only be cleared by an internal power reset""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Set the CritLock bit + new_config = config | MCP9808_REG_CONFIG_CRITLOCKED + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def setWinLock(self): + """Set Window lock bit on the config register, be careful once set it can only be cleared by an internal power reset""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Set the WinLock bit + new_config = config | MCP9808_REG_CONFIG_WINLOCKED + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def isLock(self): + """Check if the lock bits are set""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) & 0x00C0 + if config == 0x00C0 or config == 0x0040 or config==0x0080: + return True + else: + return False + + def setIntClr(self): + """Set Interrrupt Clear bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Set the Interrupt Clear bit + new_config = config | MCP9808_REG_CONFIG_INTCLR + self._logger.debug('Setting Interrupt Clear bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def clearIntClr(self): + """Clear Interrupt Clear bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Clear the Interrupt Clear bit + new_config = config & ~MCP9808_REG_CONFIG_INTCLR + self._logger.debug('Clearing Interrupt Clear bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def setAlertStat(self): + """Set Alert Status bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Set the Alert Status bit + new_config = config | MCP9808_REG_CONFIG_ALERTSTAT + self._logger.debug('Setting Alert Status bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def clearAlertStat(self): + """Clear Alert Status bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Clear the Alert Status bit + new_config = config & ~MCP9808_REG_CONFIG_ALERTSTAT + self._logger.debug('Clearing Alert Status bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def setAlertCtrl(self): + """Set Alert Control bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Set the Alert Control bit + new_config = config | MCP9808_REG_CONFIG_ALERTCTRL + self._logger.debug('Setting Alert Control bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def clearAlertCtrl(self): + """Clear Alert Control bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Clear the Alert Control bit + new_config = config & ~MCP9808_REG_CONFIG_ALERTCTRL + self._logger.debug('Clearing Alert Control bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def setAlertSel(self): + """Set Alert Select bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Set the Alert Select bit + new_config = config | MCP9808_REG_CONFIG_ALERTSEL + self._logger.debug('Setting Alert Select bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def clearAlertSel(self): + """Clear Alert Select bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Clear the Alert Select bit + new_config = config & ~MCP9808_REG_CONFIG_ALERTSEL + self._logger.debug('Clearing Alert Select bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def setAlertPol(self): + """Set Alert Polarity bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Set the Alert Polarity bit + new_config = config | MCP9808_REG_CONFIG_ALERTPOL + self._logger.debug('Setting Alert Polarity bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def clearAlertPol(self): + """Clear Alert Polarity bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Clear the Alert Polarity bit + new_config = config & ~MCP9808_REG_CONFIG_ALERTPOL + self._logger.debug('Clearing Alert Polarity bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def setAlertMode(self): + """Set Alert Mode bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Set the Alert Mode bit + new_config = config | MCP9808_REG_CONFIG_ALERTMODE + self._logger.debug('Setting Alert Mode bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def clearAlertMode(self): + """Clear Alert Mode bit on the config register""" + # Read the config Register + config = self.readU16BE(MCP9808_REG_CONFIG) + # Clear the Alert Mode bit + new_config = config & ~MCP9808_REG_CONFIG_ALERTMODE + self._logger.debug('Clearing Alert Mode bit: {0:#06X}'.format(new_config)) + if new_config < 0x00FF: + self.write16(MCP9808_REG_CONFIG, new_config << 8) + else: + self.write16(MCP9808_REG_CONFIG, new_config, False) + + def readTempC(self): + """Read sensor and return its value in degrees celsius.""" + # Read temperature register value. + t = self.readU16BE(MCP9808_REG_AMBIENT_TEMP) + self._logger.debug('Raw ambient temp register value: 0x{0:04X}'.format(t & 0xFFFF)) + # Scale and convert to signed value. + upperByte = t & 0x0F00 + lowerByte = t & 0x00FF + if t & 0x1000: + temp = (((upperByte >> 8) * 16) + (lowerByte / 16.0)) * (-1) + else: + temp = ((upperByte >> 8) * 16) + (lowerByte / 16.0) + return temp + + def getAlertOutput(self): + """This function will return the cause of the alert output trigger, it will return + the bits 13 14 and 15 of the TA Register mapped into an int""" + # Read temperature register value. + t = self.readU16BE(MCP9808_REG_AMBIENT_TEMP) + return (t & 0xE000) >> 13 + + def setResolution(self, res = 0.0625): + """Set the Sensor Resolution, the resolution could be set to 0.5, 0.25, 0.125 or 0.0625 this function + returns a list with [a,b], a is 1 if resolution is valid and 0 if it is not, b is the new value of the + config register if it was successfull and if was not it returns an Error String""" + # Validate res + if res == 0.5: + self.write8(MCP9808_REG_RESOLUTION, 0x00) + self._logger.debug('Resolution Set to: {0:#04X}'.format(0x00)) + return [1, self.readU16BE(MCP9808_REG_RESOLUTION)] + elif res == 0.25: + self.write8(MCP9808_REG_RESOLUTION, 0x01) + self._logger.debug('Resolution Set to: {0:#04X}'.format(0x01)) + return [1, self.readU16BE(MCP9808_REG_RESOLUTION)] + elif res == 0.125: + self.write8(MCP9808_REG_RESOLUTION, 0x02) + self._logger.debug('Resolution Set to: {0:#04X}'.format(0x02)) + return [1, self.readU16BE(MCP9808_REG_RESOLUTION)] + elif res == 0.0625: + self.write8(MCP9808_REG_RESOLUTION, 0x03) + self._logger.debug('Resolution Set to: {0:#04X}'.format(0x03)) + return [1, self.readU16BE(MCP9808_REG_RESOLUTION)] + else: + return [0, 'Sensor Resolution is not valid, Valid Values are: 0.5, 0.25, 0.125, +0.0625'] + self._logger.debug('Error with the resolution passed') + + def getResolution(self): + """Get Resolution Register, return a string with the resolution configured""" + # Read the Resolution Register + resolution = self.readU8(MCP9808_REG_RESOLUTION) + self._logger.debug('The Resolution is: {0:#06X}'.format(resolution)) + if resolution == 0x00: + return 'Resolution is set to 0.5 Degrees Celsius' + elif resolution == 0x01: + return 'Resolution is set to 0.25 Degrees Celsius' + elif resolution == 0x02: + return 'Resolution is set to 0.125 Degrees Celsius' + elif resolution == 0x03: + return 'Resolution is set to 0.0625 Degrees Celsius' + + def setUpperTemp(self, temp=0): + """Set the Temperature Upper Register with a resolution of 0.25 Degree Celsius, if the temperature passed + to the funcition is not in that resolution it will be rounded by defect to the nearest decimal resolution""" + #Check if temp is float + if isinstance(temp, int): + temp = float(temp) + #Divide Integer from decimal parts + t = math.modf(abs(temp)) + temp_int = int(t[1]) + temp_dec = t[0] + # Round decimal parts to 0.25 steps + if temp_dec >= 0 and temp_dec < 0.25: + temp_dec = 0x00 + elif temp_dec >= 0.25 and temp_dec < 0.5: + temp_dec = 0x01 + elif temp_dec >= 0.5 and temp_dec < 0.75: + temp_dec = 0x02 + elif temp_dec >= 0.75 and temp_dec < 1: + temp_dec = 0x03 + #Calculate the new temperature to write + new_temp = 0 + new_temp = ((temp_int << 4) | (temp_dec << 2)) & 0x0FFC + #Set Sign if it is negative + if temp < 0: + new_temp = new_temp | 0x1000 + # Write to Register + self._logger.debug('Raw temp set in Upper temp register: {0:#06X}'.format(new_temp)) + # self.write16(MCP9808_REG_UPPER_TEMP, self._i2c.reverseByteOrder(new_temp)) + self.write16(MCP9808_REG_UPPER_TEMP, (new_temp), False) + + def getUpperTemp(self, temp=0): + """Get the Temperature Upper Register""" + t = self.readU16BE(MCP9808_REG_UPPER_TEMP) + upperByte = t & 0x0F00 + lowerByte = t & 0x00FC + if t & 0x1000: + temp = (((upperByte >> 8) * 16) + (lowerByte / 16.0)) * (-1) + else: + temp = ((upperByte >> 8) * 16) + (lowerByte / 16.0) + return temp + + def setLowerTemp(self, temp=0): + """Set the Temperature Lower Register with a resolution of 0.25 Degree Celsius, if the temperature passed + to the funcition is not in that resolution it will be rounded by defect to the nearest decimal resolution""" + #Check if temp is float + if isinstance(temp, int): + temp = float(temp) + #Divide Integer from decimal parts + t = math.modf(abs(temp)) + temp_int = int(t[1]) + temp_dec = t[0] + # Round decimal parts to 0.25 steps + if temp_dec >= 0 and temp_dec < 0.25: + temp_dec = 0x00 + elif temp_dec >= 0.25 and temp_dec < 0.5: + temp_dec = 0x01 + elif temp_dec >= 0.5 and temp_dec < 0.75: + temp_dec = 0x02 + elif temp_dec >= 0.75 and temp_dec < 1: + temp_dec = 0x03 + #Calculate the new temperature to write + new_temp = 0 + new_temp = ((temp_int << 4) | (temp_dec << 2)) & 0x0FFC + #Set Sign if it is negative + if temp < 0: + new_temp = new_temp | 0x1000 + # Write to Register + self._logger.debug('Raw temp set in Lower temp register: {0:#06X}'.format(new_temp)) + # self.write16(MCP9808_REG_LOWER_TEMP, self._i2c.reverseByteOrder(new_temp)) + self.write16(MCP9808_REG_LOWER_TEMP, (new_temp), False) + + def getLowerTemp(self, temp=0): + """Get the Temperature Lower Register""" + t = self.readU16BE(MCP9808_REG_LOWER_TEMP) + upperByte = t & 0x0F00 + lowerByte = t & 0x00FC + if t & 0x1000: + temp = (((upperByte >> 8) * 16) + (lowerByte / 16.0)) * (-1) + else: + temp = ((upperByte >> 8) * 16) + (lowerByte / 16.0) + return temp + + def setCritTemp(self, temp=0): + """Set the Temperature Lower Register with a resolution of 0.25 Degree Celsius, if the temperature passed + to the funcition is not in that resolution it will be rounded by defect to the nearest decimal resolution""" + #Check if temp is float + if isinstance(temp, int): + temp = float(temp) + #Divide Integer from decimal parts + t = math.modf(abs(temp)) + temp_int = int(t[1]) + temp_dec = t[0] + # Round decimal parts to 0.25 steps + if temp_dec >= 0 and temp_dec < 0.25: + temp_dec = 0x00 + elif temp_dec >= 0.25 and temp_dec < 0.5: + temp_dec = 0x01 + elif temp_dec >= 0.5 and temp_dec < 0.75: + temp_dec = 0x02 + elif temp_dec >= 0.75 and temp_dec < 1: + temp_dec = 0x03 + #Calculate the new temperature to write + new_temp = 0 + new_temp = ((temp_int << 4) | (temp_dec << 2)) & 0x0FFC + #Set Sign if it is negative + if temp < 0: + new_temp = new_temp | 0x1000 + # Write to Register + self._logger.debug('Raw temp set in Critical temp register: {0:#06X}'.format(new_temp)) + # self.write16(MCP9808_REG_CRIT_TEMP, self._i2c.reverseByteOrder(new_temp)) + self.write16(MCP9808_REG_CRIT_TEMP, (new_temp), False) + + def getCritTemp(self, temp=0): + """Get the Temperature Lower Register""" + t = self.readU16BE(MCP9808_REG_CRIT_TEMP) + upperByte = t & 0x0F00 + lowerByte = t & 0x00FC + if t & 0x1000: + temp = (((upperByte >> 8) * 16) + (lowerByte / 16.0)) * (-1) + else: + temp = ((upperByte >> 8) * 16) + (lowerByte / 16.0) + return temp \ No newline at end of file diff --git a/Devices/usbiss_i2c_HD44780.py b/Devices/usbiss_i2c_HD44780.py new file mode 100644 index 0000000..9141b6c --- /dev/null +++ b/Devices/usbiss_i2c_HD44780.py @@ -0,0 +1,123 @@ +"""Implements a HD44780 character LCD connected via PCF8574 on I2C.""" +""" GdH - Using inheritance of I2CDevice) """ +""" Based on https://github.com/dhylands/python_lcd/blob/master/lcd/pyb_i2c_lcd.py """ + + +from Devices import lcd_api +from usbiss import i2c as I2C +# import I2CDevice as I2CDevice +import time + +# The PCF8574 has a jumper selectable address: 0x20 - 0x27 +DEFAULT_I2C_ADDR = 0x27 + +# Defines shifts or masks for the various LCD line attached to the PCF8574 + +MASK_RS = 0x01 +MASK_RW = 0x02 +MASK_E = 0x04 +SHIFT_BACKLIGHT = 3 +SHIFT_DATA = 4 + + +class I2cLcd(lcd_api.LcdApi, I2C.I2CDevice): + """Implements a HD44780 character LCD connected via PCF8574 on I2C.""" + + def __init__(self, i2c, i2c_addr, num_lines, num_columns): + self.i2c = i2c + self.i2c_addr = i2c_addr + # self.pcf8574 = I2CDevice.I2CDevice(self.i2c, self.i2c_addr) + I2C.I2CDevice.__init__(self, self.i2c, self.i2c_addr) + # pyb self.pcf8574.writeRaw8(0) + # self.pcf8574.writeRaw8(0) + self.writeRaw8(0) + time.sleep(20/1000.0) # Allow LCD time to powerup + # Send reset 3 times + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + time.sleep(5/1000.0) # need to time.sleep at least 4.1 msec + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + time.sleep(1/1000.0) + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + time.sleep(1/1000.0) + # Put LCD into 4 bit mode + self.hal_write_init_nibble(self.LCD_FUNCTION) + time.sleep(1/1000.0) + lcd_api.LcdApi.__init__(self, num_lines, num_columns) + cmd = self.LCD_FUNCTION + if num_lines > 1: + cmd |= self.LCD_FUNCTION_2LINES + self.hal_write_command(cmd) + + def hal_write_init_nibble(self, nibble): + """Writes an initialization nibble to the LCD. + + This particular function is only used during intiialization. + """ + byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA + # pyb self.i2c.send(byte | MASK_E, self.i2c_addr) + # self.pcf8574.writeRaw8(byte | MASK_E) + self.writeRaw8(byte | MASK_E) + # pyb self.i2c.send(byte, self.i2c_addr) + # self.pcf8574.writeRaw8(byte) + self.writeRaw8(byte) + + def hal_backlight_on(self): + """Allows the hal layer to turn the backlight on.""" + # pyb self.i2c.send(1 << SHIFT_BACKLIGHT, self.i2c_addr) + # self.pcf8574.writeRaw8(1 << SHIFT_BACKLIGHT) + self.writeRaw8(1 << SHIFT_BACKLIGHT) + + def hal_backlight_off(self): + """Allows the hal layer to turn the backlight off.""" + # pyb self.i2c.send(0, self.i2c_addr) + # self.pcf8574.writeRaw8(0) + self.writeRaw8(0) + + def hal_write_command(self, cmd): + """Writes a command to the LCD. + + Data is latched on the falling edge of E. + """ + byte = ((self.backlight << SHIFT_BACKLIGHT) | + (((cmd >> 4) & 0x0f) << SHIFT_DATA)) + # pyb self.i2c.send(byte | MASK_E, self.i2c_addr) + # self.pcf8574.writeRaw8(byte | MASK_E) + self.writeRaw8(byte | MASK_E) + # pyb self.i2c.send(byte, self.i2c_addr) + # self.pcf8574.writeRaw8(byte) + self.writeRaw8(byte) + + + byte = ((self.backlight << SHIFT_BACKLIGHT) | + ((cmd & 0x0f) << SHIFT_DATA)) + # pyb self.i2c.send(byte | MASK_E, self.i2c_addr, ) + # self.pcf8574.writeRaw8(byte | MASK_E) + self.writeRaw8(byte | MASK_E) + # pyb self.i2c.send(byte, self.i2c_addr) + # self.pcf8574.writeRaw8(byte) + self.writeRaw8(byte) + if cmd <= 3: + # The home and clear commands require a worst + # case time.sleep of 4.1 msec + time.sleep(5/1000.0) + + def hal_write_data(self, data): + """Write data to the LCD.""" + byte = (MASK_RS | + (self.backlight << SHIFT_BACKLIGHT) | + (((data >> 4) & 0x0f) << SHIFT_DATA)) + # pyb self.i2c.send(byte | MASK_E, self.i2c_addr) + # self.pcf8574.writeRaw8(byte | MASK_E) + self.writeRaw8(byte | MASK_E) + # pyb self.i2c.send(byte, self.i2c_addr) + # self.pcf8574.writeRaw8(byte) + self.writeRaw8(byte) + byte = (MASK_RS | + (self.backlight << SHIFT_BACKLIGHT) | + ((data & 0x0f) << SHIFT_DATA)) + # pyb self.i2c.send(byte | MASK_E, self.i2c_addr) + # self.pcf8574.writeRaw8(byte | MASK_E) + self.writeRaw8(byte | MASK_E) + # pyb self.i2c.send(byte, self.i2c_addr) + # self.pcf8574.writeRaw8(byte) + self.writeRaw8(byte) diff --git a/HISTORY.rst b/HISTORY.rst index 47dda0f..e11636e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -18,3 +18,25 @@ History * Changed SPI mode scheme to official SPI scheme rather than USB-ISS scheme. * Clarifies project mission in README.rst + +0.2.0 (2018-03-10) +------------------ + +* Refactored the codebase so that each protocol can be controlled by its own + class. This means a breaking change in the interface. +* SPI and USBISS support only in this release with updated usage. +* Added SPI tests. +* Updated README.rst with new interface. + +0.2.1 (2018-05-05) +------------------ + +* Refactored USB-ISS driver using property decorator +* Updated SPI driver to use updated USBISS driver +* Fully documented SPI driver +* Some fixes + +0.2.2 (2018-05-05) +------------------ + +* Tidied up README.md diff --git a/LICENSE b/LICENSE index 97c50a9..904f733 100644 --- a/LICENSE +++ b/LICENSE @@ -1,11 +1,11 @@ - -MIT License - -Copyright (c) 2016, 2018 Andrew Tolmie - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + +MIT License + +Copyright (c) 2016, 2018 Andrew Tolmie + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9806398 --- /dev/null +++ b/Makefile @@ -0,0 +1,90 @@ +.PHONY: clean clean-test clean-pyc clean-build docs help +.DEFAULT_GOAL := help + +define BROWSER_PYSCRIPT +import os, webbrowser, sys + +try: + from urllib import pathname2url +except: + from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT + +define PRINT_HELP_PYSCRIPT +import re, sys + +for line in sys.stdin: + match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) + if match: + target, help = match.groups() + print("%-20s %s" % (target, help)) +endef +export PRINT_HELP_PYSCRIPT + +BROWSER := python -c "$$BROWSER_PYSCRIPT" + +help: + @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) + +clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +clean-test: ## remove test and coverage artifacts + rm -fr .tox/ + rm -f .coverage + rm -fr htmlcov/ + +lint: ## check style with flake8 + flake8 usbiss tests + +test: ## run tests quickly with the default Python + python setup.py test + +test-all: ## run tests on every Python version with tox + tox + +coverage: ## check code coverage quickly with the default Python + coverage run --source usbiss setup.py test + coverage report -m + coverage html + $(BROWSER) htmlcov/index.html + +docs: ## generate Sphinx HTML documentation, including API docs + rm -f docs/pyusbiss.rst + rm -f docs/modules.rst + sphinx-apidoc -o docs/ usbiss + $(MAKE) -C docs clean + $(MAKE) -C docs html + $(BROWSER) docs/_build/html/index.html + +servedocs: docs ## compile the docs watching for changes + watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . + +release: clean dist ## package and upload a release + twine upload --repository pypi dist/* + +test-release: clean dist ## package and upload a release + twine upload --repository testpypi dist/* + +dist: clean ## builds source and wheel package + python setup.py sdist + python setup.py bdist_wheel + ls -l dist + +install: clean ## install the package to the active Python's site-packages + python setup.py install diff --git a/README.rst b/README.rst index 6fbc3e2..06a7f6c 100644 --- a/README.rst +++ b/README.rst @@ -26,46 +26,64 @@ Planned features **************** * Configure USB-ISS to different operating modes; I2C, SPI, I/O and serial + * For each mode, the API will mimic the popular APIs such as `py-spidev`_ for SPI by having same method and properties names. These names will be used in duck typing. This will ensure miminal adaption of applications wishing to use USB-ISS for prototyping and testing. + * Query status of USB-ISS * Send bytes to and read from components via USB-ISS -Current implementation -********************** +Current features -* The SPI mode is implemented with following methods and properties - * Methods - * ``xfer`` - send N bytes and read N bytes back. - * Properties - * ``mode`` - SPI modes. Please note that USB-ISS's SPI modes don't match up - with official SPI modes. Use official SPI mode numbers and the API will - configure the USB-ISS correctly. +* Functional USBISS driver +* Functional SPI driver * Other modes are not implemented. Installation ------------ -Install from setup.py +:: - python setup.py install + pip install pyusbiss Usage ----- +* USBISS only + +Connect to your USB-ISS and get information about your USB-ISS. + +:: + + from usbiss.usbiss import USBISS + + port = 'COM4' # Windows + port = '/dev/ttyACM0' # Linux + + cxn = USBISS(port) + print(repr(cxn))) + * SPI mode -Initiate with SPI mode and open a port +Initiate USB-ISS with SPI mode and open a port. + +:: - usb = usbiss.USBISS(port, 'spi', spi_mode=2, freq=500000) + from usbiss.spi import SPI - usb.open() + spi = SPI(port) - print(usb.get_iss_info()) + spi.mode = 1 + spi.max_speed_hz = 500000 + print(repr(spi._usbiss)) + + # SPI transaction + + response = spi.xfer([0x00, 0x00]) * I2C mode @@ -94,9 +112,11 @@ Credits The project was developed during a NERC's placement at University of Leeds. -This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. +This package was created with Cookiecutter_ and the +`audreyr/cookiecutter-pypackage`_ project template. -Inspired by `Waggle's alphasense.py`_, where most of USB-ISS functions was copied over. +Inspired by `Waggle's alphasense.py`_, where most of USB-ISS functions were +copied over. .. _`USB-ISS's webpage`: https://www.robot-electronics.co.uk/htm/usb_iss_tech.htm .. _pyserial: https://pypi.python.org/pypi/pyserial @@ -105,4 +125,3 @@ Inspired by `Waggle's alphasense.py`_, where most of USB-ISS functions was copie .. _Cookiecutter: https://github.com/audreyr/cookiecutter .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage .. _`Waggle's alphasense.py`: https://github.com/waggle-sensor/waggle - diff --git a/test/__init__.py b/examples/__init__.py similarity index 100% rename from test/__init__.py rename to examples/__init__.py diff --git a/examples/gpio/test_gpio.py b/examples/gpio/test_gpio.py new file mode 100644 index 0000000..1ec7701 --- /dev/null +++ b/examples/gpio/test_gpio.py @@ -0,0 +1,39 @@ +# Example GPIO based on Adafruit library for FT232H +# For the GPIO the connection to the USBISS is made outside the GPIO class thereby +# enabling using the GPIO class for use in I2C en Serial mode. +# GdH 23-3-2019 + +# Hardware : connect a LED to pins 1 and 2, connect Pin 3 to Pin 1 +import time +from usbiss import gpio +from usbiss import usbiss + +usbissdev = usbiss.USBISS('COM3') +io4=gpio.GPIO(usbissdev) + + + +io4.setup_pins({1:gpio.OUT, 2:gpio.OUT, 3:gpio.IN, 4: gpio.IN}, {1:gpio.HIGH, 2:gpio.HIGH}) +print('Press Ctrl-C to quit.') +while True: + # Set pin C0 to a high level so the LED turns on. + io4.output(1, gpio.HIGH) + # Sleep for 1 second. + time.sleep(1) + # Set pin C0 to a low level so the LED turns off. + io4.output(1, gpio.LOW) + # Sleep for 1 second. + time.sleep(1) + io4.output_pins({1:gpio.HIGH,2:gpio.HIGH}) + time.sleep(1) + io4.output_pins({1:gpio.LOW,2:gpio.LOW}) + # Read the input on pin D3 and print out if it's high or low. + level = io4.input(3) + if level == gpio.LOW: + print('Pin D3 is LOW!') + else: + print('Pin D3 is HIGH!') + # read the inputvalues of pins 3 and 4 and print the result + levels = io4.input_pins([3,4]) + print(levels) + \ No newline at end of file diff --git a/examples/i2c/example_i2c_MCP9808.py b/examples/i2c/example_i2c_MCP9808.py new file mode 100644 index 0000000..a219317 --- /dev/null +++ b/examples/i2c/example_i2c_MCP9808.py @@ -0,0 +1,76 @@ +# Based on https://github.com/mercolino/MCP9808 +# Adapted for use with th USBISS Device by Geert de Haan + +# import MCP9808.mcp9808 as MCP9808 +from usbiss import usbiss +from Devices import mcp9808 as MCP9808 +from usbiss import i2c as I2C +from usbiss import gpio + +import time + +port = 'COM3' +usbissdev = usbiss.USBISS(port) + +io2 = gpio.GPIO(usbissdev, gpio.I2C) +io2.setup(1, gpio.OUT) +io2.output(1, gpio.HIGH) +io2.setup(2, gpio.IN) + +i2c_chan = I2C.I2C(usbissdev, 'H', 100) + +sensor = MCP9808.MCP9808(i2c_chan, 48) + + +# Optionally you can override the address and/or bus number: +#sensor = MCP9808.MCP9808(address=0x20, busnum=2) + +# Initialize communication with the sensor. +sensor.begin() + +# Print the Config Register +sensor.clearConfigReg() +print('Config register: {0:#06X}'.format(sensor.getConfigReg())) + +# Print the Resolution Register +print (sensor.getResolution()) + +# You could change the resolution of the sensor +sensor.setResolution(0.25) + +# GdH - Debug self._i2c.reverseByteOrder(new_config)) ****** + +sensor.setTempHyst(0) + +# Set the Window and Critical temperature to use the alert output pin +sensor.setLowerTemp(25.0) +t = sensor.getLowerTemp() +sensor.setUpperTemp(30.00) +sensor.setCritTemp(33.0) + +# Enable the Alert pin, Remember that you should use a pullup resistor, for further information +# read the MCP9808 Datasheet +sensor.setAlertCtrl() + +print ("Window Temperature: %d - %d" %(sensor.getLowerTemp(), sensor.getUpperTemp())) +print ("Critical Temperature: %d" %(sensor.getCritTemp())) + +# Print the Config Register, Note how the config register change by setting the Alert Control pin +print ('Config register: {0:#06X}'.format(sensor.getConfigReg())) + +# Loop printing measurements every second. +print ('Press Ctrl-C to quit.') + + +while True: + # Check the MCP9808 Alert pin with an usbiss + # GPIO input pin + if io2.input(2) == gpio.LOW: + print("MCP9808 : Alert pin Low (Open collector) ") + # Read the 3 bits to know why the alert is set, read the datasheet to kno more + print ('Sensor Alert Output: {0:#05b}'.format(sensor.getAlertOutput())) + # Read the temperature + temp = sensor.readTempC() + print ('Temperature: {0:0.3F}*C'.format(temp)) + + time.sleep(1.0) diff --git a/examples/i2c/usbiss_i2c_lcd_test.py b/examples/i2c/usbiss_i2c_lcd_test.py new file mode 100644 index 0000000..55592f8 --- /dev/null +++ b/examples/i2c/usbiss_i2c_lcd_test.py @@ -0,0 +1,57 @@ +"""Implements a HD44780 character LCD connected via PCF8574 on I2C.""" +# Adapted from https://github.com/dhylands/python_lcd by Dave Hylands +# The following import was needed on my OpenMV board +import time + +from usbiss import usbiss +from usbiss import i2c as I2C +from Devices import usbiss_i2c_HD44780 as LCD + + + +Port = 'COM3' + +# The PCF8574 has a jumper selectable address: 0x20 - 0x27 +DEFAULT_I2C_ADDR = 78 + + +def test_main(): + """Test function for verifying basic functionality.""" + print("Running test_main") + usbissdev = usbiss.USBISS(Port) + i2c = I2C.I2C(usbissdev, 'H', 100) + lcd = LCD.I2cLcd(i2c, DEFAULT_I2C_ADDR, 2, 16) + lcd.putstr("USBISS Lcd_API\n Inheritance") + time.sleep(3000/1000.0) + lcd.clear() + count = 0 + while True: + lcd.move_to(0, 0) + # pyb lcd.putstr("%7d" % (millis() // 1000)) + lcd.putstr("%7d" % (count)) + # millis() not available in Python + time.sleep(1000/1000.0) + count += 1 + if count % 10 == 3: + print("Turning backlight off") + lcd.backlight_off() + if count % 10 == 4: + print("Turning backlight on") + lcd.backlight_on() + if count % 10 == 5: + print("Turning display off") + lcd.display_off() + if count % 10 == 6: + print("Turning display on") + lcd.display_on() + if count % 10 == 7: + print("Turning display & backlight off") + lcd.backlight_off() + lcd.display_off() + if count % 10 == 8: + print("Turning display & backlight on") + lcd.backlight_on() + lcd.display_on() + +if __name__ == "__main__": + test_main() diff --git a/pip-selfcheck.json b/pip-selfcheck.json new file mode 100644 index 0000000..545ffd8 --- /dev/null +++ b/pip-selfcheck.json @@ -0,0 +1 @@ +{"last_check":"2017-11-18T20:39:07Z","pypi_version":"9.0.1"} \ No newline at end of file diff --git a/pyusbiss.code-workspace b/pyusbiss.code-workspace new file mode 100644 index 0000000..1f15ef5 --- /dev/null +++ b/pyusbiss.code-workspace @@ -0,0 +1,10 @@ +{ + "folders": [ + { + "path": "usbiss" + } + ], + "settings": { + "python.pythonPath": "C:\\Users\\Geert\\AppData\\Local\\Programs\\Python\\Python36-32\\python.exe" + } +} \ No newline at end of file diff --git a/pyvenv.cfg b/pyvenv.cfg new file mode 100644 index 0000000..637fabf --- /dev/null +++ b/pyvenv.cfg @@ -0,0 +1,3 @@ +home = C:\Users\Geert\AppData\Local\Programs\Python\Python36-32 +include-system-site-packages = false +version = 3.6.0 diff --git a/requirements_dev.txt b/requirements_dev.txt index d1ce781..4b6584b 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,6 +5,7 @@ watchdog==0.8.3 flake8==3.5.0 tox==2.9.1 coverage==4.5.1 -Sphinx==1.7.0 +Sphinx==1.7.1 +twine diff --git a/setup.cfg b/setup.cfg index b0fc54a..4b89292 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.2 +current_version = 0.2.2 commit = True tag = True @@ -17,3 +17,6 @@ universal = 1 [flake8] exclude = docs +[metadata] +description-file = README.rst + diff --git a/setup.py b/setup.py index f0cf442..40161f5 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ history = history_file.read() requirements = ['pyserial'] -__version__ = '0.1.2' +__version__ = '0.2.2' setup( name='pyusbiss', diff --git a/tests/SerialClient.py b/tests/SerialClient.py new file mode 100644 index 0000000..3cd6ff7 --- /dev/null +++ b/tests/SerialClient.py @@ -0,0 +1,51 @@ +# GdH SerialClient 29-3-2019 +# Testprogram for the USBISS Serialdriver +# Used in conjunction with the unittest : test_serial_otherclient. +# Prerequisite is the availability of another COM port on the test machine. A standard FTDI based device can be used +# Connect the tx pin from USBISS serial pins to the rx pin of the other COM port and vica versa. +# Start the program in a separate DosBox +# ---------------------------------------------------------- + + +import serial +import time + +clientport = "COM6" +baud = 9600 + +class testSerial(object): + + def __init__(self): + self.serialPort = serial.Serial(port = clientport, baudrate=baud, + bytesize=8, timeout=5, stopbits=serial.STOPBITS_ONE) + + + + def test_readline(self): + + serialString = "" # Used to hold data coming over UART + while(True): + time.sleep(.5) + # Wait until there is data waiting in the serial buffer + if(self.serialPort.in_waiting > 0): + + # Read data out of the buffer until a carraige return / new line is found + serialString = self.serialPort.readline() + print(serialString) + return(serialString) + + def test_sendLine(self, line_to_send): + self.serialPort.write(line_to_send) + + + + + +if __name__ == '__main__': + sc = testSerial() + while(True): + # Receive line from the USBISS + line_received = sc.test_readline() + time.sleep(1) + # Send the line back to the USBISS + sc.test_sendLine(line_received) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_gpio.py b/tests/test_gpio.py new file mode 100644 index 0000000..b152b11 --- /dev/null +++ b/tests/test_gpio.py @@ -0,0 +1,81 @@ +#! /usr/bin/env python +# test_gpio - part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +""" +Geert de Haan / 23-3-2019 +Testing gpio module, part of pyusbiss + +** Hardware : ** + +USBISS +- Led on pin 1 (configured as output) +- Led on pin 2 (configured as output) +- pin 3 connected to pin 1 (configured as input) +- pin 4 connected to pin 2 (configured as input) +""" + + +import sys +import unittest +from usbiss import usbiss +from usbiss import gpio + + + +#USBISS parameter +Port = 'COM3' + + + +class GPIOtestCase(unittest.TestCase): + """ GPIO functions testcase """ + + def setUp(self): + self._usbissdev = usbiss.USBISS(Port) + self.io4 = gpio.GPIO(self._usbissdev) + self.io4.setup_pins({1:gpio.OUT, 2:gpio.OUT, 3:gpio.IN, 4: gpio.IN}, {1:gpio.LOW, 2:gpio.LOW}) + self.assertIsInstance(self.io4, gpio.GPIO) + + def tearDown(self): + self._usbissdev.close() + + def test1_loopback_pin1_pin3(self): + # set pin 1 HIGH, check result on pin 3 + # set pin 1 LOW, check result on pin 3 + pi = 3 + po = 1 + self.io4.output(po, gpio.HIGH) + pin3val = self.io4.input(pi) + self.assertEqual(pin3val, gpio.HIGH) + self.io4.output(po, gpio.LOW) + pin3val = self.io4.input(pi) + self.assertEqual(pin3val, gpio.LOW) + + def test2_loopback_pin2_pin4(self): + # set pin 2 HIGH, check result on pin 3 + # set pin 2 LOW, check result on pin 4 + pi = 4 + po = 2 + self.io4.output(po, gpio.HIGH) + pin4val = self.io4.input(pi) + self.assertEqual(pin4val, gpio.HIGH) + self.io4.output(po, gpio.LOW) + pin4val = self.io4.input(pi) + self.assertEqual(pin4val, gpio.LOW) + + def test3_loopback_multipin(self): + # set pin 1,2 HIGH, check result on pin 3,4 + # set pin 1,2 LOW, check result on pin 3,4 + self.io4.output_pins({1:gpio.HIGH, 2: gpio.HIGH}) + pinval = self.io4.input_pins([3,4]) + self.assertEqual(pinval, [gpio.HIGH, gpio.HIGH]) + self.io4.output_pins({1:gpio.LOW, 2: gpio.LOW}) + pinval = self.io4.input_pins([3,4]) + self.assertEqual(pinval, [gpio.LOW, gpio.LOW]) + + +if __name__ == '__main__': + sys.stdout.write(__doc__) + unittest.main() \ No newline at end of file diff --git a/tests/test_i2c.py b/tests/test_i2c.py new file mode 100644 index 0000000..71047bd --- /dev/null +++ b/tests/test_i2c.py @@ -0,0 +1,83 @@ +#! /usr/bin/env python +# test_i2c.py, part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Copyright (c) 2019 Geert de Haan +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +""" +Geert de Haan / 2-3-2019 +Testing the I2C module + +Hardware : for the scantest (test 14) the USBISS should be connected to a I2C bus with at least one device for a succesfull test +""" + +import sys +import unittest +from usbiss import usbiss +from usbiss import i2c + +Port = 'COM3' +# McpAddress = 160 +class I2ctestCase(unittest.TestCase): + """ I2C driver register functions testcase """ + + def _scan_devices(self): + + i2cchannel = i2c.I2C(self._usbissdev, 'H', 100) + return(i2cchannel.scan()) + + def setUp(self): + self._usbissdev = usbiss.USBISS(Port) + + def tearDown(self): + self._usbissdev.close() + + def test1_parameters_H(self): + self.assertIsNotNone(i2c.I2C(self._usbissdev, 'H', 100)) + + def test2_parameters_H(self): + self.assertIsNotNone(i2c.I2C(self._usbissdev, 'S', 100)) + + def test3_parameters_handshaking_invalid(self): + with self.assertRaises(ValueError): + i2c.I2C(self._usbissdev, 'P', 100) + + def test4_parameters_Hardware_100khz(self): + self.assertIsNotNone(i2c.I2C(self._usbissdev, 'H', 100)) + + def test5_parameters_Hardware_400khz(self): + self.assertIsNotNone(i2c.I2C(self._usbissdev, 'H', 400)) + + def test6_parameters_Hardware_1000khz(self): + self.assertIsNotNone(i2c.I2C(self._usbissdev, 'H', 1000)) + + def test7_parameters_Software_20khz(self): + self.assertIsNotNone(i2c.I2C(self._usbissdev, 'S', 20)) + + def test8_parameters_Software_50khz(self): + self.assertIsNotNone(i2c.I2C(self._usbissdev, 'S', 50)) + + def test9_parameters_Software_100khz(self): + self.assertIsNotNone(i2c.I2C(self._usbissdev, 'S', 100)) + + def test10_parameters_Software_400khz(self): + self.assertIsNotNone(i2c.I2C(self._usbissdev, 'S', 400)) + + def test11_parameters_Software_20khz(self): + self.assertIsNotNone(i2c.I2C(self._usbissdev, 'S', 20)) + + def test12_parameters_Hardware_invalidspeed(self): + with self.assertRaises(ValueError): + i2c.I2C(self._usbissdev, 'H', 50) + + def test13_parameters_Software_invalidspeed(self): + with self.assertRaises(ValueError): + i2c.I2C(self._usbissdev, 'S', 1000) + + def test14_scan_i2c_devices(self): + self.assertNotEqual(len(self._scan_devices()), 0, "I2C Scan : no devices found") + print(self._scan_devices()) + +if __name__ == '__main__': + sys.stdout.write(__doc__) + unittest.main() \ No newline at end of file diff --git a/tests/test_i2c_eprom(A2)_24lc256.py b/tests/test_i2c_eprom(A2)_24lc256.py new file mode 100644 index 0000000..29b0c9c --- /dev/null +++ b/tests/test_i2c_eprom(A2)_24lc256.py @@ -0,0 +1,70 @@ +#! /usr/bin/env python +# test_i2c_eprom_functions.py, part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +""" +Geert de Haan / 1-3-2019 +Testing the I2C module functions for reading and writing an EPROM + +Hardware : +24LC256 Eprom connected to the USBISS () (no pullup R's) +Address : 160 ((A0..A2) grounded) +""" + +import sys +import unittest +from usbiss import usbiss +from usbiss import i2c + +Port = 'COM3' +EpromAddress = 160 +class I2ctestCase(unittest.TestCase): + """ I2C driver EPROM functions testcase """ + + def setUp(self): + self._usbissdev = usbiss.USBISS(Port) + self.i2cchannel = i2c.I2C(self._usbissdev, 'H', 100) + print(self.i2cchannel._usbiss.__repr__) + self.eprom = i2c.I2CDevice(self.i2cchannel, EpromAddress) + + def tearDown(self): + self._usbissdev.close() + + def test1_devicepresent(self): + self.assertEqual(self.eprom.ping(), True, 'Device not found at i2c Address : %s' % EpromAddress) + + + def _writepattern(self): + pat1 = [0x0f] + pat2= [0xf0] + self.writebuf=[] + for high_Address in range(1): + for low_Address in range(255): + if low_Address % 2 == 1: + data = pat1 + else: + data = pat2 + self.eprom.writeMem(high_Address, low_Address, len(data), data) + self.writebuf.append(data) + + def _readpattern(self): + + self.readbuf=[] + for high_Address in range(1): + for low_Address in range(255): + data = self.eprom.readMem(high_Address, low_Address, 1) + self.readbuf.append(data) + + def test2_write_read(self): + """ + Write a pattern to Eprom, read it back and compare. + """ + self._writepattern() + self._readpattern() + self.assertListEqual(self.writebuf, self.readbuf) + + +if __name__ == '__main__': + sys.stdout.write(__doc__) + unittest.main() \ No newline at end of file diff --git a/tests/test_i2c_noregister(SGL)_PCF8574.py b/tests/test_i2c_noregister(SGL)_PCF8574.py new file mode 100644 index 0000000..5a04274 --- /dev/null +++ b/tests/test_i2c_noregister(SGL)_PCF8574.py @@ -0,0 +1,97 @@ +#! /usr/bin/env python +# test_i2c_noregister(SGL)_PCF8574.py, part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Copyright (c) 2019 Geert de Haan +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +""" +Geert de Haan / 9-3-2019 +Testing the I2C module : USBISS - I2C_SGL command - +Read/Write single byte for non-registered devices, such as the Philips PCF8574 I/O chip. + + +Hardware : Testboard met PCF8574, pin 7 connected to pin 0 for loopbacktest +""" +import time +import sys +import unittest +from usbiss import usbiss +from usbiss import i2c + +Port = 'COM3' +Address = 120 + +class I2CTestCaseSGL_noregister(unittest.TestCase): + """ I2C driver no register functions testcase """ + I2C_TEST = 0x58 + + def _SetPinOn(self, pin): + data = self.pcf8574.readRaw8() + data |= 1 << (pin) + data |= self.IODIR + self.pcf8574.writeRaw8(data) + + def _SetPinOff(self, pin): + data = self.pcf8574.readRaw8() + data &= ~(1 << (pin)) + data |= self.IODIR + self.pcf8574.writeRaw8(data) + + def _GetPin(self, pin): + data = data = self.pcf8574.readRaw8() + data &= (1 << (pin)) + data = data >> (pin) + return data + + def setUp(self): + self._usbissdev = usbiss.USBISS(Port) + self.i2cchannel = i2c.I2C(self._usbissdev, 'H', 100) + self.assertIsInstance(self.i2cchannel, i2c.I2C) + # print(self.i2cchannel._usbiss.__repr__) + self.pcf8574 = i2c.I2CDevice(self.i2cchannel, Address) + self.assertIsInstance(self.pcf8574, i2c.I2CDevice) + self.IODIR = 0x0F + + def tearDown(self): + self._usbissdev.close() + + def test1_devicepresent(self): + # time.sleep(1) + # resp = self.pcf8574.ping() + self.i2cchannel.write_data([self.I2C_TEST, Address]) + time.sleep(1) + resp = self.i2cchannel.read_data(1) + resp = self.i2cchannel.decode(resp) + if resp != [0]: + response = True + else: + response = False + self.assertEqual(response, True, 'Device not found at i2c Address : %s' % Address) + + + def test2_i2cdevice_loopback(self): + time.sleep(1) + OUTPUTPIN = 7 + INPUTPIN = 0 + ON = 1 + OFF = 0 + # Set OUTPUTPIN off and check te result + self._SetPinOff(OUTPUTPIN) + pinstat = self._GetPin(INPUTPIN) + self.assertEqual(pinstat, OFF) + # Set OUTPUTPIN off and check te result + self._SetPinOn(OUTPUTPIN) + pinstat = self._GetPin(INPUTPIN) + self.assertEqual(pinstat, ON) + # if a LED is connected to the OUTPUTPIN. + time.sleep(2) + # Set OUTPUTPIN off and check te result + self._SetPinOff(OUTPUTPIN) + pinstat = self._GetPin(INPUTPIN) + self.assertEqual(pinstat, OFF) + + + +if __name__ == '__main__': + sys.stdout.write(__doc__) + unittest.main() \ No newline at end of file diff --git a/tests/test_i2c_registers(A1)_mcp23008.py b/tests/test_i2c_registers(A1)_mcp23008.py new file mode 100644 index 0000000..c662a26 --- /dev/null +++ b/tests/test_i2c_registers(A1)_mcp23008.py @@ -0,0 +1,162 @@ +#! /usr/bin/env python +# test_i2c_registers_mcp23008.py, part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Copyright (c) 2019 Geert de Haan +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +""" +Geert de Haan / 1-3-2019 +Testing the I2C module functions for devices with registers. USBISS (I2C_AD1) + +Hardware : +MCP23008 IO Expander connected to the USBISS () (no pullup R's) +Address : 64 ((A0..A2) grounded) +Test 6 - Connect pin 7 (output) with pin 6 (input) for the loopbacktest +""" + +import sys +import time +import unittest +from usbiss import usbiss +from usbiss import i2c + + +# MCP23008 Registers + +IODIR = 0x00 # IO Direction +IPOL = 0x01 # Input polarity +GPINTEN = 0x02 # Interrupt on Change +DEFVAL = 0x03 # Default Compare (for interrupt) +INTCON = 0x04 # Interrupt Control +IOCON = 0x05 # Configuration register for MCP23008 +GPPU = 0x06 # Pull-up register configuration +INTF = 0x07 # Interrupt Flag Register +INTCAP = 0x08 # Interrupt Capture Register +GPIO = 0x09 # Port (GPIO) Register - Write - modifies Output latch (OLAT) +OLAT = 0x0A # Output latch register + +#USBISS parameter +Port = 'COM3' +# I2C parameters +Handshaking ='S' +Speed = 100 +# MCP23008 Address +McpAddress = 64 +class I2ctestCase(unittest.TestCase): + """ I2C driver register functions testcase """ + + def _write8read8(self, data): + """ + helper function for testing the 8 bit register functions + """ + writebyte = data + register = IODIR + self.mcp23008.write8(register, writebyte) + readbyte = self.mcp23008.readU8(register) + if readbyte == writebyte: + return True + else: + return False + + def _write16read16BE(self, data, little_endian): + """ + helper function for testing the 16 bit register functions + """ + write2bytes = data + register = IODIR + + # Write the 2 bytes Big Endian + self.mcp23008.write16(register, write2bytes, little_endian) + readbytes = self.mcp23008.readU16(register, little_endian) + if readbytes == write2bytes: + return True + else: + return False + + def _writeListreadList(self, data): + """ + helper function for testing the 16 writelist register functions + """ + writebytesarray = data + register = IODIR + + # Write the list of bytes to a register and read back the register content. + self.mcp23008.writeList(register, writebytesarray) + readbytesarray = self.mcp23008.readList(register, 3) + if readbytesarray == writebytesarray: + return True + else: + return False + + def _SetPinOn(self, pin): + data = self.mcp23008.readU8(GPIO) + data |= 1 << (pin) + self.mcp23008.write8(GPIO,data) + + def _SetPinOff(self, pin): + data = self.mcp23008.readU8(GPIO) + data &= ~(1 << (pin)) + self.mcp23008.write8(GPIO, data) + + def _GetPin(self, pin): + data = data = self.mcp23008.readU8(GPIO) + data &= (1 << (pin)) + data = data >> (pin) + return data + + def setUp(self): + self.usbissdev = usbiss.USBISS(Port) + self.i2cchannel = i2c.I2C(self.usbissdev, Handshaking, Speed) + print(self.i2cchannel._usbiss.__repr__) + self.mcp23008 = i2c.I2CDevice(self.i2cchannel, McpAddress) + + def tearDown(self): + self.usbissdev.close() + + def test1_i2device_ping(self): + self.assertEqual(self.mcp23008.ping(), True, 'Device not found at i2c Address : %s' % McpAddress) + + # test the write8 and read8 function by writing to a MCP23008 register + # and reading back the register and comparing the result. + def test2_i2device_write8(self): + self.assertTrue(self._write8read8(0b01010101)) + + # test the write16 and read16 functions with big - and little Endian + def test3_i2cdevice_write16_BE(self): + self.assertTrue(self._write16read16BE(0xFF00, False)) + + def test4_i2cdevice_write16_LE(self): + self.assertTrue(self._write16read16BE(0xFF00, True)) + + # test the writelist function by writing a list to the MCP23008 registers and reading it back + def test5_i2cdevice_writelist(self): + self.assertTrue(self._writeListreadList([0xFF, 0x00, 0xFF])) + + # test I2C module by writing to MCP23008 pin and reading back the result + # Connect pin 7 (output) with pin 6 (input) + def test6_i2cdevice_loopback(self): + OUTPUTPIN = 7 + INPUTPIN = 6 + ON = 1 + OFF = 0 + self.mcp23008.write8(IODIR, 0b01111111) + # Set OUTPUTPIN off and check te result + self._SetPinOff(OUTPUTPIN) + pinstat = self._GetPin(INPUTPIN) + self.assertEqual(pinstat, OFF) + # Set OUTPUTPIN off and check te result + self._SetPinOn(OUTPUTPIN) + pinstat = self._GetPin(INPUTPIN) + self.assertEqual(pinstat, ON) + # if a LED is connected to the OUTPUTPIN. + time.sleep(2) + # Set OUTPUTPIN off and check te result + self._SetPinOff(OUTPUTPIN) + pinstat = self._GetPin(INPUTPIN) + self.assertEqual(pinstat, OFF) + + + +if __name__ == '__main__': + sys.stdout.write(__doc__) + unittest.main() \ No newline at end of file diff --git a/tests/test_serial_loopback - GPIO.py b/tests/test_serial_loopback - GPIO.py new file mode 100644 index 0000000..0421127 --- /dev/null +++ b/tests/test_serial_loopback - GPIO.py @@ -0,0 +1,89 @@ +#! /usr/bin/env python +# test_serial_loop - GPIO.py, part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +""" +Geert de Haan / 5-5-2019 +Testing the serial module in loopback mode / combined with GPIO features of the USBISS + +Hardware : Connect the Rx (pin 1) and Tx (pin 2) of the USBISS + Connect the IO3 and IO4 for the GPIO loopback test. + +Due to the fact that the outgoing buffer is only 30 bytes long this is also the maximum string length +possible in loopback mode. +For tests longer than 30 chars see the test_serial_otherclient.py + +This test demonstrates that the usbiss can use both the serial and gpio at the same time. +""" + +import sys +import time +import unittest +from usbiss import usbiss +from usbiss import serial +from usbiss import gpio + + +Port = 'COM3' +Baudrate = 9600 + +class I2ctestCase(unittest.TestCase): + + + def setUp(self): + #initialize the serial port + _usbissdev = usbiss.USBISS(Port) + # Initialize the GPIO, only pins 3 and 4 are available if combined with serial mode + self.serport = serial.SERIAL(_usbissdev, Baudrate) + self.io2 = gpio.GPIO(_usbissdev, gpio.SERIAL) + self.io2.setup_pins({3:gpio.OUT, 4: gpio.IN}, {3:gpio.LOW}) + self.assertIsInstance(self.io2, gpio.GPIO) + + def tearDown(self): + self.serport.close() + #closed ook de _usbiss in de Serial driver + + def test1_loopback_readline(self): + """ + Test serial functionality by sending a string out and + reading it back with readline() + """ + send = 'Test1 - Loopbacktest\n' + self.serport.serial_write(send) + time.sleep(.5) # give USBISS time to send the string + receive = self.serport.readline() + self.assertEqual(receive +'\n', send) + + def test2_loopback_read_serial(self): + """ + Test basic functionality by using serial_read() en serial_write() + """ + send = 'Test2 - Loopbacktest\n' + self.serport.serial_write(send) + time.sleep(1) + # waiting = connection.in_waiting + waiting = self.serport.in_waiting + if waiting==0: + self.assertEqual('Error' , 'Nothing to receive') + else: + time.sleep(.1) + receive = self.serport.serial_read(waiting) + self.assertEqual(receive , send) + + def test3_loopback_gpio(self): + # set pin 3 HIGH, check result on pin 4 + # set pin 3 LOW, check result on pin 4 + pi = 4 + po = 3 + self.io2.output(po, gpio.HIGH) + pin4val = self.io2.input(pi) + self.assertEqual(pin4val, gpio.HIGH) + self.io2.output(po, gpio.LOW) + pin4val = self.io2.input(pi) + self.assertEqual(pin4val, gpio.LOW) + + +if __name__ == '__main__': + sys.stdout.write(__doc__) + unittest.main() \ No newline at end of file diff --git a/tests/test_serial_loopback.py b/tests/test_serial_loopback.py new file mode 100644 index 0000000..bdc7a16 --- /dev/null +++ b/tests/test_serial_loopback.py @@ -0,0 +1,65 @@ +#! /usr/bin/env python +# test_serial_loopback.py, part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +""" +Geert de Haan / 17-4-2019 +Testing the serial module in loopback mode + +Hardware : Connect the Rx (pin 2) and Tx (pin 3) of the USBISS +Due to the fact that the outgoing buffer is only 30 bytes long this is also the maximum string length +possible in loopback mode. +For tests longer than 30 chars see the test_serial_otherclient.py +""" + +import sys +import time +import unittest +from usbiss import usbiss +from usbiss import serial + +Port = 'COM3' +Baudrate = 9600 + +class I2ctestCase(unittest.TestCase): + + + def setUp(self): + self._usbissdev = usbiss.USBISS(Port) + self.serport = serial.SERIAL(self._usbissdev, Baudrate) + + def tearDown(self): + self._usbissdev.close() + + def test1_loopback_readline(self): + """ + Test serial functionality by sending a string out and + reading it back with readline() + """ + send = 'Test1 - Loopbacktest\n' + self.serport.serial_write(send) + time.sleep(.5) # give USBISS time to send the string + receive = self.serport.readline() + self.assertEqual(receive +'\n', send) + + def test2_loopback_read_serial(self): + """ + Test basic functionality by using serial_read() en serial_write() + """ + send = 'Test2 - Loopbacktest\n' + self.serport.serial_write(send) + time.sleep(1) + # waiting = connection.in_waiting + waiting = self.serport.in_waiting + if waiting==0: + self.assertEqual('Error' , 'Nothing to receive') + else: + time.sleep(.1) + receive = self.serport.serial_read(waiting) + self.assertEqual(receive , send) + + +if __name__ == '__main__': + sys.stdout.write(__doc__) + unittest.main() \ No newline at end of file diff --git a/tests/test_serial_otherclient - GPIO.py b/tests/test_serial_otherclient - GPIO.py new file mode 100644 index 0000000..6b78f2a --- /dev/null +++ b/tests/test_serial_otherclient - GPIO.py @@ -0,0 +1,84 @@ +#! /usr/bin/env python +# test_serial_otherclient - GPIO.py, part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Created by Geert de Haan +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +""" +Geert de Haan / 19-4-2019 +Testing the serial module with client serial program / combined with GPIO features of the USBISS + +Hardware : Connect the Rx (pin 2) and Tx (pin 3) with an external FTDI device +(usbiss.Rx --> FTDI.Tx, usbiss.Tx --> FTDI.Rx) and start the +tests\SerialClient.py program in a separate DosBox (python SerialClient.py) + +This test demonstrates that the usbiss can use both the serial and gpio at the same time. +""" + +import sys +import time +import unittest +from usbiss import usbiss +from usbiss import serial +from usbiss import gpio + +Port = 'COM3' +Baudrate = 9600 + +class I2ctestCase(unittest.TestCase): + + + def setUp(self): + self._usbissdev = usbiss.USBISS(Port) + #initialize the serial port + self.serport = serial.SERIAL(self._usbissdev, Baudrate) + # Initialize the GPIO, only pins 3 and 4 are available if combined with serial mode + self.io2 = gpio.GPIO(self._usbissdev, gpio.SERIAL) + # Configure Pin3 as Out, Pin4 as IN, set Pin3 as Low + self.io2.setup_pins({3:gpio.OUT, 4: gpio.IN}, {3:gpio.LOW}) + self.assertIsInstance(self.io2, gpio.GPIO) + + def tearDown(self): + self._usbissdev.close() + + def test1_loopback_readline(self): + #testing the + send = 'Test1 - Loopbacktest max 60 chars, the USBISS inputbuffer\n' + self.serport.serial_write(send) + time.sleep(.5) # give USBISS time to send the string + receive = self.serport.readline() + self.assertEqual(receive +'\n', send) + + def test2_loopback_read_serial(self): + send = 'Test2 - Loopbacktest with a longer string then 30 chars\n' + self.serport.serial_write(send) + time.sleep(.5) + #waiting for the data to come back. USBISS + n = 0 + waiting = 0 + while(n<5 and waiting == 0): + waiting = self.serport.in_waiting + n+=1 + time.sleep(.5) + if waiting==0: + self.assertEqual('Error' , 'Nothing to receive') + else: + time.sleep(.1) + receive = self.serport.serial_read(waiting) + self.assertEqual(receive , send) + + def test3_loopback_gpio(self): + # set pin 3 HIGH, check result on pin 4 + # set pin 3 LOW, check result on pin 4 + pi = 4 + po = 3 + self.io2.output(po, gpio.HIGH) + pin4val = self.io2.input(pi) + self.assertEqual(pin4val, gpio.HIGH) + self.io2.output(po, gpio.LOW) + pin4val = self.io2.input(pi) + self.assertEqual(pin4val, gpio.LOW) + +if __name__ == '__main__': + sys.stdout.write(__doc__) + unittest.main() \ No newline at end of file diff --git a/tests/test_serial_otherclient.py b/tests/test_serial_otherclient.py new file mode 100644 index 0000000..e980c61 --- /dev/null +++ b/tests/test_serial_otherclient.py @@ -0,0 +1,65 @@ +#! /usr/bin/env python +# test_serial_otherclient.py, part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Created by Geert de Haan +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +""" +Geert de Haan / 19-4-2019 +Testing the serial module with client serial program. + +Hardware : Connect the Rx (pin 2) and Tx (pin 3) with an external FTDI device +(usbiss.Rx --> FTDI.Tx, usbiss.Tx --> FTDI.Rx) and start the +tests\SerialClient.py program in a separate DosBox (python SerialClient.py) + +""" + +import sys +import time +import unittest +from usbiss import usbiss +from usbiss import serial + +Port = 'COM3' +Baudrate = 9600 + +class I2ctestCase(unittest.TestCase): + + + def setUp(self): + self._usbissdev = usbiss.USBISS(Port) + self.serport = serial.SERIAL(self._usbissdev, Baudrate) + + def tearDown(self): + self._usbissdev.close() + + def test1_loopback_readline(self): + #testing the + send = 'Test1 - Loopbacktest max 60 chars, the USBISS inputbuffer\n' + self.serport.serial_write(send) + time.sleep(.5) # give USBISS time to send the string + receive = self.serport.readline() + self.assertEqual(receive +'\n', send) + + def test2_loopback_read_serial(self): + send = 'Test2 - Loopbacktest with a longer string then 30 chars\n' + self.serport.serial_write(send) + time.sleep(.5) + #waiting for the data to come back. USBISS + n = 0 + waiting = 0 + while(n<5 and waiting == 0): + waiting = self.serport.in_waiting + n+=1 + time.sleep(.5) + if waiting==0: + self.assertEqual('Error' , 'Nothing to receive') + else: + time.sleep(.1) + receive = self.serport.serial_read(waiting) + self.assertEqual(receive , send) + + +if __name__ == '__main__': + sys.stdout.write(__doc__) + unittest.main() \ No newline at end of file diff --git a/test/test_spi.py b/tests/test_spi.py similarity index 76% rename from test/test_spi.py rename to tests/test_spi.py index 9915eee..e1e4e59 100644 --- a/test/test_spi.py +++ b/tests/test_spi.py @@ -15,7 +15,6 @@ """ import unittest -import time import sys from usbiss import spi @@ -36,14 +35,23 @@ def segments(data, size=16): class SpiTestCase(unittest.TestCase): """SPI driver test case""" - def setUp(self): self.cxn = spi.SPI(PORT) - def tearDown(self): self.cxn.close() + def test0_modes(self): + """ + Test SPI modes + """ + for i in range(3): + self.cxn.mode = i + self.assertEqual(self.cxn.mode, i, "expected a {} which was written before".format(i)) + lookup_table = [0, 2, 1, 3] + self.cxn._usbiss.get_iss_info() + usbiss_mode = self.cxn._usbiss.mode - self.cxn._usbiss.SPI_MODE + self.assertEqual(usbiss_mode, lookup_table[i], "expected a {} which was written before".format(lookup_table[i])) def test1_loopback(self): """ diff --git a/tests/test_usbiss.py b/tests/test_usbiss.py new file mode 100644 index 0000000..2938ebd --- /dev/null +++ b/tests/test_usbiss.py @@ -0,0 +1,98 @@ +#! /usr/bin/env python +# test_i2c_registers_mcp23008.py, part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Copyright (c) 2019 Geert de Haan +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +""" +Geert de Haan / 3-3-2019 +Testing usbiss.py module + +Hardware : + +USBISS +""" +#USBISS Commands +ISS_CMD = 0x5A +ISS_MODE = 0x02 + +#USBISS Operating modes +IO_MODE = 0x00 +IO_CHANGE = 0x10 # is not a real mode. mode stays IO_MODE +I2C_S_20KHZ = 0x20 +I2C_S_50KHZ = 0x30 +I2C_S_100KHZ = 0x40 +I2C_S_400KHZ = 0x50 +I2C_H_100KHZ = 0x60 +I2C_H_400KHZ = 0x70 +I2C_H_1000KHZ = 0x80 +SPI_MODE = 0x90 +SERIAL = 0x01 +# IO_MODE : Pinsettings +IO_TYPE = 0x04 +# Test incorrect mode +INCORRECT_MODE = 0xAA +# query for serial, firmware and mode +ISS_VERSION = 0x01 + +import sys +import unittest +from usbiss import usbiss + + + +#USBISS parameter +Port = 'COM3' + + + +class I2ctestCase(unittest.TestCase): + """ I2C driver register functions testcase """ + + def setUp(self): + self._usbiss = usbiss.USBISS(Port) + self.assertIsInstance(self._usbiss, usbiss.USBISS) + + def tearDown(self): + self._usbiss.close() + + def _get_usbiss_mode(self): + self._usbiss.write_data([ISS_CMD, ISS_VERSION]) + response = self._usbiss.read_data(3) + response = self._usbiss.decode(response) + mode = response[2] + return mode + + def test1_basicfunctionality(self): + self.assertIsInstance(self._usbiss, usbiss.USBISS) + + def test2_check_iomode(self): + self._usbiss.mode = [IO_MODE] + curmod = self._get_usbiss_mode() + self.assertEqual([IO_MODE], [curmod]) + self._usbiss.close() + + def test_operating_modes(self): + # IO_CHANGE not tested, mode unchanged, see USBISS doc + for opmode in [IO_MODE, I2C_S_20KHZ,I2C_S_50KHZ,I2C_S_100KHZ,I2C_S_400KHZ,I2C_H_100KHZ,I2C_H_400KHZ,I2C_H_1000KHZ,SPI_MODE,SERIAL]: + with self.subTest(opmode = [opmode]): + self._usbiss.mode = [opmode] + curmod = self._get_usbiss_mode() + self.assertEqual([opmode], [curmod]) + + def test_incorrect_operating_mode(self): + with self.assertRaises(usbiss.USBISSError): + self._usbiss.mode = [INCORRECT_MODE] + + + def test_latest_firmware(self): + latest_firmware = 0x08 + self._usbiss.write_data([ISS_CMD, ISS_VERSION]) + response = self._usbiss.read_data(3) + response = self._usbiss.decode(response) + firmware = response[1] + self.assertEqual(firmware, latest_firmware, 'Latest version = %i' % latest_firmware) + +if __name__ == '__main__': + sys.stdout.write(__doc__) + unittest.main() \ No newline at end of file diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..a5a8351 --- /dev/null +++ b/tox.ini @@ -0,0 +1,20 @@ +[tox] +envlist = py34, py35, py36, flake8 + +[travis] +python = + 3.6: py36 + 3.5: py35 + 3.4: py34 + +[testenv:flake8] +basepython = python +deps = flake8 +commands = flake8 usbiss + +[testenv] +setenv = + PYTHONPATH = {toxinidir} + +commands = python setup.py test + diff --git a/usbiss/__init__.py b/usbiss/__init__.py index ced8a93..0f21c43 100644 --- a/usbiss/__init__.py +++ b/usbiss/__init__.py @@ -2,7 +2,7 @@ # Copyright (c) 2016, 2018 Andrew Tolmie # Licensed under the MIT License. See LICENSE file in the project root for full license information. -__version__ = '0.1.2' +__version__ = '0.2.2' __title__ = 'pyusbiss' __description__ = 'USB-ISS Multifunction USB Communications Module USBISS device driver (pure Python)' __uri__ = 'http://github.com/DancingQuanta/pyusbiss' diff --git a/usbiss/gpio.py b/usbiss/gpio.py new file mode 100644 index 0000000..1b3763a --- /dev/null +++ b/usbiss/gpio.py @@ -0,0 +1,199 @@ +# gpio.py, part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Licensed under the MIT License. See LICENSE file in the project root for full license information. +# GdH - based on FT232H.py library from Adafruit for the FT232 (FTDI) + +"""GPIO support for USB-ISS""" + + + +# GPIO Connection modes +I2C = 1 +SERIAL = 2 +FULL = 3 +# Pin mode (IO_TYPE) + +OUT = 0b00 +OUTH = 0b01 +IN = 0b10 +ADC = 0b11 + +LOW = 0 +HIGH = 1 + + +class GPIO(object): + """GPIO operating mode of USBISS + as GPIO is also used in combination with I2C and Serial + this class does not open an connection to the USBISS itself, but is depending on a + masterclass to supply the connection + """ + + # USBISS GPIO commands + + IO_MODE = 0x00 + IO_CHANGE = 0x10 + IO_SETPINS_CMD = 0x63 + IO_GETPINS_CMD = 0x64 + IO_GETAD_CMD = 0x65 + + + + + def __init__(self, usbiss_con, con_mode=FULL): + # Default Configure USB-ISS as IO all pins as input to protect the + # external circuit and the USBISS from damage. + self.ControlRegister = 0b10101010 # All inputs + self.DataRegister = 0x00 + self.con_mode = con_mode + #if self.con_mode == FULL: + # self._usbiss = usbiss.USBISS(usbiss_con) + #else: + self._usbiss = usbiss_con + + self.configure() + + + def _check_pins_and_con_mode(self, pin): + # check if this pin is not reserverd for I2C or SERIAL operation + # in FULL mode all pins (1-4) can be changed + if self.con_mode == FULL: + if pin < 1 or pin > 4: + raise ValueError('GPIO - Pin must be between 1 and 4') + return + i2c_pins_reserved = [3,4] + serial_pins_reserved = [1,2] + if self.con_mode == I2C and pin in i2c_pins_reserved: + raise ValueError ('GPIO - pin %s is reserved for I2C operation' % str(pin)) + if self.con_mode == SERIAL and pin in serial_pins_reserved: + raise ValueError ('GPIO - pin %s is reserved for Serial operation' % str(pin)) + + + def configure(self): + """ + Configure GPIO controller + """ + self._usbiss.mode = [self.IO_CHANGE, self.ControlRegister] + + def _send_dataregister(self): + """ + Send the dataregister to the USBISS device + """ + self._usbiss.write_data([self.IO_SETPINS_CMD, self.DataRegister]) + resp = self._usbiss.read_data(1) + resp = self._usbiss.decode(resp) + if resp != [0xFF]: + raise RuntimeError('USB-ISS: GPIO output - Transmission Error') + + + def _setup_pin(self, pin, mode): + """ + Helper function to setup s GPIO pin. + Mode = IN, OUT, ADC for analog conversions + """ + if mode not in (IN, OUT, ADC): + raise ValueError('Mode must be GPIO.IN, GPIO.OUT or GPIO.ADC') + self._check_pins_and_con_mode(pin) # Check if this pin may be changed in this mode (I2C, SERIAL, FULL) + for i in range(0, 2): + if mode & 1 << i: + self.ControlRegister |= 1 << (pin -1) * 2 + i + else: + self.ControlRegister &= ~(1 << (pin - 1) *2 + i) + + + def setup(self, pin, mode): + + """ + Set the input or output mode for a specified pin. Mode should be + either OUT or IN or ADC. + """ + self._check_pins_and_con_mode(pin) # Check if this pin may be changed in this mode (I2C, SERIAL, FULL) + self._setup_pin(pin, mode) + self.configure() + + def setup_pins(self, pins, values={}): + """ + Ex : setup_pins({1:gpio.OUT, 2:gpio.OUT, 3:gpio.IN, 4: gpio.IN}, {1:gpio.HIGH, 2:gpio.HIGH}) + Setup multiple pins as inputs or outputs at once. Pins should be a + dict of pin name to pin mode (IN or OUT). Optional starting values of + pins can be provided in the values dict (with pin name to pin value). + """ + # pin setup + for pin, mode in pins.items(): + self._check_pins_and_con_mode(pin) # Check if this pin may be changed in this mode (I2C, SERIAL, FULL) + self._setup_pin(pin, mode) + self.configure() + # pin values + for pin, value in values.items(): + self._check_pins_and_con_mode(pin) # Check if this pin may be changed in this mode (I2C, SERIAL, FULL) + self._output_pin(pin, value) + self._send_dataregister() + + + def _output_pin(self, pin, level): + """ + Helper function to set a pin to a high or low level + """ + self._check_pins_and_con_mode(pin) + if level: + self.DataRegister |= 1 << (pin-1) + else: + self.DataRegister &= ~(1 << (pin - 1)) + + def output(self, pin, level): + """ + Set the specified pin the provided high/low value. Value should be + either HIGH/LOW or a boolean (true = high). + """ + self._check_pins_and_con_mode(pin) + self._output_pin(pin, level) + self._send_dataregister() + + def output_pins(self, pins): + """Set multiple pins high or low at once. Pins should be a dict of pin + name to pin value (HIGH/True for 1, LOW/False for 0). All provided pins + will be set to the given values. + """ + for pin, value in iter(pins.items()): + self._check_pins_and_con_mode(pin) + self._output_pin(pin, value) + self._send_dataregister() + + + def input(self, pin): + """ + Read the specified pin and return HIGH/true if the pin is pulled high, + or LOW/false if pulled low. + """ + self._check_pins_and_con_mode(pin) + self._usbiss.write_data([self.IO_GETPINS_CMD]) + self.DataRegister = int.from_bytes(self._usbiss.read_data(1), byteorder='little') + return(self.DataRegister & (1 << (pin -1)) !=0) + + def input_pins(self, pins): + + """ + Read multiple pins specified in the given list and return list of pin values + GPIO.HIGH/True if the pin is pulled high, or GPIO.LOW/False if pulled low. + """ + for pin in pins: + self._check_pins_and_con_mode(pin) + self._usbiss.write_data([self.IO_GETPINS_CMD]) + self.DataRegister = int.from_bytes(self._usbiss.read_data(1), byteorder='little') + # Adafruit - return [((_pins >> pin) & 0x0001) == 1 for pin in pins] + return [((self.DataRegister >> (pin-1))& 0x0001) for pin in pins] + + + def adc(self, pin, vcc): + """ + Read the analog voltage on the pin. vcc is the provided voltage to + the USBISS and is used as reference for the pin voltage. + """ + self._check_pins_and_con_mode(pin) + self._usbiss.write_data([self.IO_GETAD_CMD, pin]) + adcv = self._usbiss.read_data(2) + # DEBUG print('ADC 0 {} 1 {}'.format(adcv[0], adcv[1])) + return(vcc/(1024*(255*adcv[0]+adcv[1]))) + + + diff --git a/usbiss/i2c.py b/usbiss/i2c.py new file mode 100644 index 0000000..d816419 --- /dev/null +++ b/usbiss/i2c.py @@ -0,0 +1,296 @@ +# I2C.py - part of pyusbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Licensed under the MIT License. See LICENSE file in the project root for full license information. +# GdH - based on FT232H.py library from Adafruit for the FT232 (FTDI) + +"""I2C support for USB-ISS""" + + +import time + + +class I2C(object): + """I2C operating mode of USBISS""" + I2C_S_20KHZ = 0x20 + I2C_S_50KHZ = 0x30 + I2C_S_100KHZ = 0x40 + I2C_S_400KHZ = 0x50 + I2C_H_100KHZ = 0x60 + I2C_H_400KHZ = 0x70 + I2C_H_1000KHZ = 0x80 + IO_TYPE = 0x04 + I2C_TEST = 0x58 + + + + def __init__(self, usbissdev, handshaking, speed): + """ + + """ + if handshaking not in ('H', 'S'): + ValueError ('I2C - Handshaking is (H)ardware or (S)oftware/ not %s' % str(handshaking)) + + key = str(handshaking)+str(speed) + OperatingModes = {'H100':self.I2C_H_100KHZ, + 'H400':self.I2C_H_400KHZ, + 'H1000':self.I2C_H_1000KHZ, + 'S20':self.I2C_S_20KHZ, + 'S50':self.I2C_S_50KHZ, + 'S100':self.I2C_S_100KHZ, + 'S400':self.I2C_S_400KHZ + } + try: + setting = OperatingModes[key] + except KeyError: + raise ValueError('I2C - This combination of handshaking and speed is not supported : ' + str(handshaking) + ' '+ str(speed)) + + self._usbiss = usbissdev + self._usbiss.mode = [setting, self.IO_TYPE] + + + + def scan(self): + """ + Scan the bus for I2C devices. Returns a list of devices + max 127 devices per bus + A single byte is returned, zero if no device is detected or non-zero if the device was detected. + """ + response=[] + for devadr in range(255): # ToDo : Check range. + self.write_data([self.I2C_TEST, devadr]) + resp = self.read_data(1) + resp = self.decode(resp) + if resp != [0]: + response.append(devadr) + return response + + # GdH 5-1-2019 + def write_data(self, data): + self._usbiss.write_data(data) + + def read_data(self, size): + ret = self._usbiss.read_data(size) + return ret + + def decode(self,data): + dec = self._usbiss.decode(data) + return dec + + + + + # I2CDevice + # 30-12-2018 + # Class for individual I2CDevices using the I2C driver for the USBISS + + + + +class I2CDevice(object): + """ + """ + I2C_SGL = 0x53 # Read/Write single byte for non-registered devices, such as the Philips PCF8574 I/O chip. + I2C_AD0 = 0x54 # Read/Write multiple bytes for devices without internal address or where address does not require resetting. + I2C_AD1 = 0x55 # Read/Write 1 byte addressed devices (the majority of devices will use this one) + I2C_AD2 = 0x56 # Read/Write 2 byte addressed devices, eeproms from 32kbit (4kx8) and up. + I2C_DIRECT = 0x57 # Used to build your own custom I2C sequences. + I2C_TEST = 0x58 # Used to check for the existence of an I2C device on the bus. (V5 or later firmware only) + + def __init__(self, usbissdevice, addr): + + self._usbissi2c = usbissdevice + self._addr = addr # 8 bits Address ! + self._addr_read = addr + 1 + + def ping(self): + """ + ping the Device, returns True the device is available at the specified Address + """ + self._usbissi2c.write_data([self.I2C_TEST, self._addr]) + resp = self._usbissi2c.read_data(1) + resp = self._usbissi2c.decode(resp) + if resp != [0]: + response = True + else: + response = False + return response + + def readRaw8(self): + """ + Read single byte for non-registered devices, such as the Philips PCF8574 I/O chip. + """ + + self._usbissi2c.write_data([self.I2C_SGL, self._addr_read]) + # time.sleep(0.1) + resp = self._usbissi2c.read_data(1) # Reads 1 byte / 8 bits + resp = self._usbissi2c.decode(resp) + if len(resp) > 0: + return resp[0] + else: + return resp + + def writeRaw8(self, value): + """ + value : 1 byte to write + + Write single byte for non-registered devices, such as the Philips PCF8574 I/O chip. + """ + # Write is op het basisadres + value = value & 0xFF + self._usbissi2c.write_data([self.I2C_SGL, self._addr, value]) + resp = self._usbissi2c.read_data(1) + resp = self._usbissi2c.decode(resp) + if resp != [1]: + raise RuntimeError("I2CDevice - writeRaw8 - TransmissionError") + + def readU8(self, register): + """Read an unsigned byte from the specified register.""" + self._usbissi2c.write_data([self.I2C_AD1, self._addr_read, register, 1]) + resp = self._usbissi2c.read_data(1) # Reads 1 byte / 8 bits + resp = self._usbissi2c.decode(resp) + if len(resp) > 0: + return resp[0] + else: + return resp + + def readS8(self, register): + """Read a signed byte from the specified register.""" + result = self.readU8(register) + if result > 127: + result -= 256 + return result + + def write8(self, register, value): + """Write an 8-bit value to the specified register.""" + value = value & 0xFF + self._usbissi2c.write_data([self.I2C_AD1, self._addr, register, 1 , value]) + resp = self._usbissi2c.read_data(1) + resp = self._usbissi2c.decode(resp) + if resp != [1]: + raise RuntimeError("I2CDevice - write8 - TransmissionError") + + def readU16(self, register, little_endian=True): + """Read an unsigned 16-bit value from the specified register, with the + specified endianness (default little endian, or least significant byte + first).""" + self._usbissi2c.write_data([self.I2C_AD1, self._addr_read, register, 2]) + resp = self._usbissi2c.read_data(2) # Reads 1 byte / 8 bits + resp = self._usbissi2c.decode(resp) + if little_endian: + return (resp[-1] << 8) | resp[-2] + else: + return (resp[-2] << 8) | resp[-1] + + + def readS16(self, register, little_endian=True): + """Read a signed 16-bit value from the specified register, with the + specified endianness (default little endian, or least significant byte + first).""" + result = self.readU16(register, little_endian) + if result > 32767: + result -= 65536 + return result + + def write16(self, register, value, little_endian=True): + """Write a 16-bit value to the specified register.""" + value = value & 0xFFFF + value_low = value & 0xFF + value_high = (value >> 8) & 0xFF + if little_endian: + value = [value_low, value_high] + else: + value = [value_high, value_low] + + self._usbissi2c.write_data([self.I2C_AD1, self._addr, register, 2 ]+value) + resp = self._usbissi2c.read_data(1) + resp = self._usbissi2c.decode(resp) + if resp != [1]: + raise RuntimeError("I2CDevice - write16 - TransmissionError") + + def readList(self, register, length): + """Read a length number of bytes from the specified register. Results + will be returned as a bytearray.""" + self._usbissi2c.write_data([self.I2C_AD1, self._addr_read, register, length]) + resp = self._usbissi2c.read_data(length) + resp = self._usbissi2c.decode(resp) + if len(resp) > 0: + return resp + else: + return resp + + def writeList(self, register, data): + """Write bytes to the specified register.""" + # Data is a bytearray + length = len(data) + self._usbissi2c.write_data([self.I2C_AD1, self._addr, register, length] + data) + # ToDo check op data, concatenaten van de list tot een reeks getallen ? Testen !!! + resp = self._usbissi2c.read_data(1) + resp = self._usbissi2c.decode(resp) + if resp != [1]: + raise RuntimeError("I2CDevice - writeList - TransmissionError") + + + def readU16LE(self, register): + """Read an unsigned 16-bit value from the specified register, in little + endian byte order.""" + return self.readU16(register, little_endian=True) + + def readU16BE(self, register): + """Read an unsigned 16-bit value from the specified register, in big + endian byte order.""" + return self.readU16(register, little_endian=False) + + def readS16LE(self, register): + """Read a signed 16-bit value from the specified register, in little + endian byte order.""" + return self.readS16(register, little_endian=True) + + def readS16BE(self, register): + """Read a signed 16-bit value from the specified register, in big + endian byte order.""" + return self.readS16(register, little_endian=False) + + # These functions (writeMem, readMem) are not part of the Adafruit protocol library. Specific for the USBISS device + + + def readMem(self, AddressHighByte, AddressLowByte, length): + """ + AddressHighByte, AddressLowByte : Address of the memory location within the EPROM. + lenght : no of bytes to receive + + Read 2 byte addressed devices, eeproms from 32kbit (4kx8) and up. + The maximum number of data bytes requested should not exceed 64 so as not to overflow the USB-ISS's internal buffer. + """ + if length > 64: + raise ValueError("I2CDevice - readMem - max 64 bytes exceeded.") + + self._usbissi2c.write_data([self.I2C_AD2, self._addr_read, AddressHighByte, AddressLowByte, length]) + + resp = self._usbissi2c.read_data(length) + resp = self._usbissi2c.decode(resp) + return resp + + def writeMem(self, AddressHighByte, AddressLowByte, length, data): + """ + AddressHighByte, AddressLowByte : Address of the memory location within the EPROM. + lenght : length of the transmitted data + data : bytearray of data + + Write 2 byte addressed devices, eeproms from 32kbit (4kx8) and up.  + The maximum number of data bytes should not exceed 59 so as not to overflow the USB-ISS's 64 byte internal buffer. + """ + if length > 59: + raise ValueError("I2CDevice - writeMem - max 59 bytes exceeded.") + + self._usbissi2c.write_data([self.I2C_AD2, self._addr, AddressHighByte, AddressLowByte, length] + data) + + # Avoid transmission errors. + time.sleep(0.05) + + resp = self._usbissi2c.read_data(1) + resp = self._usbissi2c.decode(resp) + if resp != [1]: + raise RuntimeError("I2CDevice - writeList - TransmissionError - " + str(AddressHighByte) + '-' + str(AddressLowByte)) + + + diff --git a/usbiss/serial.py b/usbiss/serial.py new file mode 100644 index 0000000..f5d11d8 --- /dev/null +++ b/usbiss/serial.py @@ -0,0 +1,187 @@ +#! /usr/bin/env python +# Serial.py partt of usbiss +# Copyright (c) 2016, 2018 Andrew Tolmie +# Licensed under the MIT License. See LICENSE file in the project root for full license information. + +# Geert de Haan 10-3-2019 + +# Transmit : max 30 Bytes +# Receive : max 62 Bytes +# Process : 1 - send Transmit command ( optional with bytes to send) +# 2 - response : ACK or NACK - TxCount - RxCount if RxCount > 0 [RxData 1 .. RxData n ] +# loosely based on pyserial +# close() – This will close the serial port +# readline() – This will read a string from the serial port +# serial_read(size) – This will read n number of bytes from the serial port +# serial_write(data) – This will write the data passed to the function to the serial port +# in_waiting – This variable holds the number of bytes in the buffer +# out waiting - Number of bytes in the out buffer +# USBISS - FIFO - this means that every byte that is read is immediately removed from the +# USBISS buffer, this implies that even in_waiting and out_waiting can only be used once +# immediately followed by reading the available bytes in the buffer. +# This is solved by using an own readbuffer that holds all received character + + +"""Serial support for USB-ISS""" + +# from usbiss import usbiss +import time + +class SERIAL(object): + + + SERIAL_IO = 0x62 # response ACK (oxFF) or NACK (0x00) - Check on received characters | ACK | TxCount | RxCount + SERIAL = 0x01 + + def __init__(self, usbissdev, baudrate): + + divisor = { + 300 :[0x27, 0x0F], + 1200 :[0x09, 0xC3], + 2400 :[0x04, 0xE1], + 9600 :[0x01, 0x37], + 19200 :[0x00, 0x9B], + 38400 :[0x00, 0x4D], + 57600 :[0x00, 0x33], + 115200 :[0x00, 0x19], + 250000 :[0x00, 0x0B], + 1000000 :[0x00, 0x03] + } + try: + brhb = divisor[baudrate][0] + brlb = divisor[baudrate][1] + except KeyError: + raise ValueError('Serial - unknown baudrate; possible values : (300, 1200, 2400 .. 1000000') + + self._usbiss = usbissdev + self._usbiss.mode = [ self.SERIAL, brhb, brlb, 0b10101010] #Configure all I/O as input by default. + # ReadBuffer for all incoming characters from the usbiss. All read methodes use this buffer for actual reading the received chars. + self.buffer=[] + # Allow the device time to setup + time.sleep(1) + + def write(self, data): + # usbiss write function + self._usbiss.write_data(data) + + def read(self, size): + # usbiss read function + ret = self._usbiss.read_data(size) + return ret + + def close(self): + self._usbiss.close() + + def decode(self,data): + dec = self._usbiss.decode(data) + return dec + + def _GetResponseFrame(self): + """ + # Read the first three status bytes + # from the buffer + # Ack / Nack - rxcount - txcount + """ + self.write([self.SERIAL_IO]) + time.sleep(.1) + resp = self.read(3) + [ack, txcount, rxcount] = self.decode(resp) + if ack != 0xFF: + raise ValueError('Serial - GetResponseFrame - NACK received - Transmissionerror') + if rxcount > 0: + time.sleep(.01) + com = self._serial_read(rxcount) + #Add to buffer + self.buffer += com + return [ack, txcount, len(self.buffer)] + + def serial_write(self, data): + """ + serial_write + parameter : string of data to be send over + The transmitbuffer is 30 bytes long. if the data + is longer than 30 the string is send in multple + blocks. + """ + writebuffer = list(map(ord, data)) + while(True): + while(True): + # wait unit there are no more characters to send in the USBISS buffer + outw = self.out_waiting + if outw > 0: + time.sleep(0.5) + else: + break + if len(writebuffer) == 0: + return + # transmitbuffer of max 30 chars + transmitbuffer = writebuffer[:30] + writebuffer= writebuffer[30:] + self._usbiss.write_data([self.SERIAL_IO]+transmitbuffer) + time.sleep(1) + + + def _serial_read(self, size): + """ + _serial_read - bytes read are no longer available for consequtive + reads - (FIFO) - For internal use only ! + """ + self.write([self.SERIAL_IO]) + resp = self.read(size) + data = self.decode(resp) + return data + + def serial_read(self, size): + """ + serial_read - return and remove bytes from the readbuffer. + """ + line='' + # How many chars in the buffer + actualsize = len(self.buffer) + # maximal the avialable chars + if size > actualsize: + size = actualsize + linebuf = self.buffer[:size] + self.buffer = self.buffer[size:] + for c in linebuf: + line += chr(c) + return line + + def readline(self): + """ + readline - all data that is available is added to self.buffer. after each read from the + serial port self.buffer is checked for newlines. If found the function returns the string + up until the first newline. + Subsequently calling readline() will produce the next bufferd lines. + """ + while(True): + rxcount = self.in_waiting + if rxcount > 0: + for pos, i in enumerate(self.buffer): + # look for the \n + if i == 10: + line='' + linebuf = self.buffer[:pos] + self.buffer = self.buffer[pos+1:] + for c in linebuf: + line += chr(c) + return line + + + @property + def in_waiting(self): + """ + Get the number of bytes in the input buffer. + """ + [ack, txcount, rxcount] = self._GetResponseFrame() + return rxcount + + @property + def out_waiting(self): + """ + Get the number of bytes in the output buffer + """ + [ack, txcount, rxcount] = self._GetResponseFrame() + return txcount + + diff --git a/usbiss/spi.py b/usbiss/spi.py index 8fb1a68..9b0f7f8 100644 --- a/usbiss/spi.py +++ b/usbiss/spi.py @@ -5,19 +5,15 @@ """SPI support for USB-ISS""" from .usbiss import USBISS +from .usbiss import USBISSError class SPI(object): """SPI operating mode of USBISS """ - - SPI_MODE = 0x90 - SPI_CMD = 0x61 - - - def __init__(self, port, mode=0, max_speed_hz=3000000): - self._usbiss = USBISS(port) + def __init__(self, usbissdev, mode=0, max_speed_hz=3000000): + self._usbiss = usbissdev self.sck_divisor = 1 @@ -27,26 +23,26 @@ def __init__(self, port, mode=0, max_speed_hz=3000000): # Select frequency of USB-ISS's SPI operating mode self.max_speed_hz = max_speed_hz - def open(self): self._usbiss.open() - def close(self): self._usbiss.close() - def configure(self): """ - Configure SPI controller + Configure SPI controller with the SPI mode and operating frequency """ + # Convert standard SPI sheme to USBISS scheme + lookup_table = [0, 2, 1, 3] + mode = lookup_table[self._mode] + # Add signal for SPI switch - iss_mode = self.SPI_MODE + self._mode + iss_mode = self._usbiss.SPI_MODE + mode # Configure USB-ISS - self._usbiss.set_iss_mode([iss_mode, self.sck_divisor]) - + self._usbiss.mode = [iss_mode, self.sck_divisor] @property def mode(self): @@ -54,37 +50,39 @@ def mode(self): Property that gets / sets the SPI mode as two bit pattern of Clock Polarity and Phase [CPOL|CPHA]. - Emulates spidev.SpiDev.mode with USBISS.SPI.mode. + Standard SPI mode scheme ia used. USBISS SPI mode scheme have 1 and 2 + swapped compared with standard scheme. + + Emulates spidev.SpiDev.mode - Users must use standard SPI mode numbers. - USBISS.SPI.mode uses standard SPI mode numbers which do not match up - with USBISS number commands. - A lookup table will select the correct USBISS number command based on - chosen SPI mode. - Serves as a check on the value of the SPI mode which should be between - 0 and 3. + :getter: Gets SPI mode + :setter: Sets SPI mode + :type: int (byte 0xnn) """ return self._mode @mode.setter def mode(self, val): - try: - lookup_table = [0, 2, 1, 3] - self._mode = lookup_table[val] + if 0 <= val < 4: + self._mode = val self.configure() - except: - error = "The value of SPI mode, {}, is not between 0 and 3".format( - val + else: + error = ( + "The value of SPI mode, {}, is not between 0 and 3".format(val) ) raise ValueError(error) - @property def max_speed_hz(self): """ Property that gets / sets the maximum bus speed in Hz. - Emulates spidev.SpiDev.max_speed_hz with USBISS.SPI.max_speed_hz. + Emulates spidev.SpiDev.max_speed_hz. + + :getter: Gets the SPI operating frequency + :setter: Sets the SPI operating frequency + :type: int + """ return self._max_speed_hz @@ -94,62 +92,87 @@ def max_speed_hz(self, val): self.sck_divisor = self.iss_spi_divisor(val) self.configure() - def iss_spi_divisor(self, sck): - """Calculate a divisor from input SPI clock speed + """ + Calculate a USBISS SPI divisor value from the input SPI clock speed + + :param sck: SPI clock frequency + :type sck: int + :returns: ISS SCK divisor + :rtype: int """ _divisor = (6000000 / sck) - 1 divisor = int(_divisor) if divisor != _divisor: - raise ValueError('Non-integral SCK divisor.') + raise ValueError('Non-integer SCK divisor.') if not 1 <= divisor < 256: - error = "The value of sck_divisor, %s, is not between 0 and 255" % (divisor) + error = ( + "The value of sck_divisor, {}, " + "is not between 0 and 255".format(divisor) + ) raise ValueError(error) return divisor - def exchange(self, data): """ Perform SPI transaction. The first received byte is either ACK or NACK. - TODO: enforce rule that up to 63 bytes of data can be sent. - TODO: enforce rule that there is no gaps in data bytes (what define a gap?) + :TODO: enforce rule that up to 63 bytes of data can be sent. + :TODO: enforce rule that there is no gaps in data bytes (what define a gap?) + + :param data: List of bytes + :returns: List of bytes + :rtype: List of bytes """ - self._usbiss.write_data([self.SPI_CMD] + data) + self._usbiss.write_data([self._usbiss.SPI_CMD] + data) response = self._usbiss.read_data(1 + len(data)) if len(response) != 0: response = self._usbiss.decode(response) status = response.pop(0) if status == 0: - raise RuntimeError('USB-ISS: Transmission Error') + raise USBISSError('SPI Transmission Error') return response - else: - raise RuntimeError('USB-ISS: Transmission Error: No bytes received!') - + raise USBISSError('SPI Transmission Error: No bytes received!') def xfer(self, data): - return self.exchange(data) - + """ + Perform a SPI transection - def xfer2(self, data): + :param data: List of bytes + :returns: List of bytes + :rtype: List of bytes + """ return self.exchange(data) + def xfer2(self, data): + """ + Write bytes to SPI device. - def readbytes(self, len): + :param data: List of bytes + :returns: List of bytes + :rtype: List of bytes """ - Read len bytes from SPI device. + return self.exchange(data) + + def readbytes(self, readLen): """ - dummybytes = [0] * len - return self.exchange(dummybytes) + Read readLen bytes from SPI device. + :param readLen: Number of bytes + :returns: List of bytes + :rtype: List of bytes + """ + return self.exchange([0] * readLen) def writebytes(self, data): """ Write bytes to SPI device. + + :param data: List of bytes """ self.exchange(data) diff --git a/usbiss/usbiss.py b/usbiss/usbiss.py index 6e83542..460bbc6 100644 --- a/usbiss/usbiss.py +++ b/usbiss/usbiss.py @@ -5,7 +5,7 @@ """ Python interface to USB-ISS Multifunction USB Communications Module. The technical specification can be found here: - https://www.robot-electronics.co.uk/htm/usb_iss_tech.htm +https://www.robot-electronics.co.uk/htm/usb_iss_tech.htm Some of the code is derived from: https://github.com/waggle-sensor/waggle/ """ @@ -14,6 +14,10 @@ import serial +class USBISSError(IOError): + """Base class error for all USB-ISS devices""" + + class USBISS(object): """Base class for USB-ISS. The main purpose of the base class is to manage serial connection between @@ -28,17 +32,21 @@ class USBISS(object): library used to facilitate an connection. """ + # USBISS identification and configuration module = None firmware = None - iss_mode = None - cur_iss_mode = None + _mode = None serial = None + # USBISS command bytes ISS_CMD = 0X5A ISS_VERSION = 0x01 - ISS_MODE = 0x02 + ISS_SET_MODE = 0x02 ISS_SER_NUM = 0x03 + # SPI command bytes + SPI_MODE = 0x90 + SPI_CMD = 0x61 def __init__(self, port): @@ -55,33 +63,28 @@ def __init__(self, port): self.get_iss_info() self.get_iss_serial_no() - def open(self): """Open Serial port to USB-ISS """ self.serial.open() - def close(self): """Close Serial port to USB-ISS """ self.serial.close() - def write_data(self, data): """ Write to USB-ISS """ self.serial.write(bytearray(data)) - def read_data(self, size): """ Read from USB-ISS """ return self.serial.read(size) - def decode(self, data): decoded = [] for i in range(0, len(data)): @@ -89,24 +92,24 @@ def decode(self, data): decoded = decoded + [unpacked] return decoded - def get_iss_info(self): - """ Get information about the USB-ISS + """ + Get information about the USB-ISS + Querying will return three bytes; - - the module ID (7), - - firmware version (currently 2), - - the current operating mode. + - the module ID (7), + - firmware version (currently 2), + - the current operating mode. """ self.write_data([self.ISS_CMD, self.ISS_VERSION]) response = self.read_data(3) if len(response) == 3: response = self.decode(response) self.module = response[0] - self.firmware = hex(response[1]) - self.cur_iss_mode = hex(response[2]) + self.firmware = response[1] + self._mode = response[2] else: - raise RuntimeError("Could not get version details") - + raise USBISSError("Could not get version details") def get_iss_serial_no(self): """ Get serial number of USB-ISS module @@ -115,28 +118,42 @@ def get_iss_serial_no(self): # Return 8 bytes serial number self.iss_sn = self.read_data(8) + @property + def mode(self): + """ + The configuration byte of USB-ISS that controls the operating protocol + and its additional parameters. - def set_iss_mode(self, set_bytes): - """Set the operating protocol of the USB-ISS + :getter: A configuration byte + :setter: The configuration byte + :type: int (0xnn) """ - data = [self.ISS_CMD, self.ISS_MODE] + set_bytes + return self._mode + + @mode.setter + def mode(self, set_bytes): + """Set the operating protocol of the USB-ISS with additional + parameters for the protocol + """ + self._mode = set_bytes + data = [self.ISS_CMD, self.ISS_SET_MODE] + set_bytes self.write_data(data) response = self.read_data(2) if response[0] == 0: - if response[1] == 0x05: - raise RuntimeError('USB-ISS: Unknown Command') - elif response[1] == 0x06: - raise RuntimeError('USB-ISS: Internal Error 1') - elif response[1] == 0x07: - raise RuntimeError('USB-ISS: Internal Error 2') - else: - raise RuntimeError('USB-ISS: Undocumented Error') - + error_dict = { + 0x05: 'Unknown Command', + 0x06: 'Internal Error 1', + 0x07: 'Internal Error 2' + } + try: + raise USBISSError(error_dict[response[1]]) + except KeyError: + raise USBISSError('Undocumented Error') def __repr__(self): return ("The module ID is {}\n" "The firmware version is {}\n" "The current operating mode is {}\n" "The serial number is {}").format( - self.module, self.firmware, self.cur_iss_mode, self.iss_sn + self.module, self.firmware, self._mode, self.iss_sn )