From a14490393683b0dca861fe0b59a1a9cfe2f182a0 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 7 Aug 2023 11:36:04 -0400 Subject: [PATCH] bumped version to v0.9.9 --- .github/workflows/tests.yml | 82 +++++++++++++++++++++++++++++++++++++ .travis.yml | 30 -------------- README.md | 22 +--------- setup.cfg | 1 - setup.py | 2 +- tox.ini | 34 ++++----------- ultrasync/__init__.py | 2 +- ultrasync/cli.py | 8 ++-- ultrasync/main.py | 48 ++++++++++++---------- 9 files changed, 125 insertions(+), 104 deletions(-) create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..f2e7cec --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,82 @@ +name: Tests + +on: + + # On which repository actions to trigger the build. + push: + branches: [ master ] + pull_request: + branches: [ master ] + + # Allow job to be triggered manually. + workflow_dispatch: + +# Cancel in-progress jobs when pushing to the same branch. +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +jobs: + + tests: + + runs-on: ${{ matrix.os }} + strategy: + + # Run all jobs to completion (false), or cancel + # all jobs once the first one fails (true). + fail-fast: true + + # Define a minimal test matrix, it will be + # expanded using subsequent `include` items. + matrix: + os: ["ubuntu-latest"] + python-version: ["3.10"] + bare: [false] + + defaults: + run: + shell: bash + + env: + OS: ${{ matrix.os }} + PYTHON: ${{ matrix.python-version }} + BARE: ${{ matrix.bare }} + + name: Python ${{ matrix.python-version }} on ${{ matrix.os }} ${{ matrix.bare && '(bare)' || '' }} + steps: + + - name: Acquire sources + uses: actions/checkout@v3 + + - name: Install prerequisites (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + + - name: Install project dependencies (Baseline) + run: | + pip install -r requirements.txt -r dev-requirements.txt + + # For saving resources, code style checking is + # only invoked within the `bare` environment. + - name: Check code style + if: matrix.bare == true + run: | + flake8 ultrasync --count --show-source --statistics + + - name: Run tests + run: | + coverage run -m pytest ultrasync + + - name: Process coverage data + run: | + coverage xml + coverage report + + - name: Upload coverage data + uses: codecov/codecov-action@v3 + with: + files: ./coverage.xml + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index def5706..0000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: python - -dist: focal - -matrix: - include: - - python: "3.8" - env: TOXENV=py38 - - python: "3.9" - env: TOXENV=py39 - - python: "3.10" - env: TOXENV=py310 - -install: - - pip install . - - pip install codecov - - pip install -r dev-requirements.txt - - pip install -r requirements.txt - - -# run tests -script: - - tox - -after_success: - - tox -e coverage-report - - codecov - -notifications: - email: false diff --git a/README.md b/README.md index 60abf55..296ebcf 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,6 @@ # NX-595E Output Control Fork This fork is designated to implementing the "Output Control" section of the NX-595E. The main objective is to enable communication with the outputs and ensure its proper implementation. -## How does output control work? -Output Control is handled through the `output.htm` file within the web app. From my understanding, Output Controls has two key value pairs that identify each output: -``` - { - 'name': "Garage Auto Door", - 'state': "0", - } -``` -In this example, the `name` is the name of the output and the `state` is whether the output is "on" or "off", 0 being "off" and 1 being "on" - -To activate the Output Control switch, a post request is made to `/user/output.cgi` with the following parameters: -``` - { - 'sess': self.session_id, - 'onum': 1, - 'ostate': 1 - } -``` -In this example, the `sess` is referred to the session ID of the current login, `onum` is the index of the output (`'onum': 1` refers to the first output), and `ostate` which refers to the state in which you want to set the output (`'ostate': 1` means on and `'ostate': 0` means off). - # NX-595E UltraSync Hub Compatible with both NX-595E [Hills](https://www.hills.com.au/) ComNav, xGen, xGen8 (such as [NXG-8-Z-BO](https://firesecurityproducts.com/en/product/intrusion/NXG_8_Z_BO/82651)), [Interlogix](https://www.interlogix.com/), and [ZeroWire](https://www.interlogix.com/intrusion/product/ultrasync-selfcontained-hub) UltraSync solutions. @@ -30,7 +10,7 @@ Compatible with both NX-595E [Hills](https://www.hills.com.au/) ComNav, xGen, xG [![Paypal](https://img.shields.io/badge/paypal-donate-green.svg)](https://paypal.me/lead2gold?locale.x=en_US) [![Follow](https://img.shields.io/twitter/follow/l2gnux)](https://twitter.com/l2gnux/)
[![Python](https://img.shields.io/pypi/pyversions/ultrasync.svg?style=flat-square)](https://pypi.org/project/ultrasync/) -[![Build Status](https://travis-ci.org/caronc/ultrasync.svg?branch=master)](https://travis-ci.org/caronc/ultrasync) +[![Build Status](https://github.com/caronc/ultrasync/actions/workflows/tests.yml/badge.svg)](https://github.com/caronc/ultrasync/actions/workflows/tests.yml) [![CodeCov Status](https://codecov.io/github/caronc/ultrasync/branch/master/graph/badge.svg)](https://codecov.io/github/caronc/ultrasync) [![Downloads](http://pepy.tech/badge/ultrasync)](https://pypi.org/project/ultrasync/) diff --git a/setup.cfg b/setup.cfg index 383cc9c..5aef9cd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,4 +20,3 @@ addopts = --verbose -ra python_files = tests/test_*.py filterwarnings = once::Warning -strict = true diff --git a/setup.py b/setup.py index 30f7258..3020d3b 100755 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ setup( name='ultrasync', - version='0.9.8', + version='0.9.9', description='Wrapper to XGen/XGen8/Hills/Interlogix NX-595E/UltraSync ' 'ZeroWire', license='MIT', diff --git a/tox.ini b/tox.ini index 1d1f60f..ef077f5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] -envlist = py38,py39,py310,coverage-report - +envlist = py311,coverage-report +skipsdist = true [testenv] # Prevent random setuptools/pip breakages like @@ -11,36 +11,20 @@ deps= -r{toxinidir}/requirements.txt -r{toxinidir}/dev-requirements.txt commands = - coverage run --parallel -m pytest {posargs} - flake8 . --count --show-source --statistics - -[testenv:py38] -deps= - -r{toxinidir}/requirements.txt - -r{toxinidir}/dev-requirements.txt -commands = - coverage run --parallel -m pytest {posargs} - flake8 . --count --show-source --statistics - -[testenv:py39] -deps= - -r{toxinidir}/requirements.txt - -r{toxinidir}/dev-requirements.txt -commands = - coverage run --parallel -m pytest {posargs} - flake8 . --count --show-source --statistics + coverage run --parallel -m pytest {posargs} ultrasync + flake8 ultrasync --count --show-source --statistics -[testenv:py310] +[testenv:py311] deps= -r{toxinidir}/requirements.txt -r{toxinidir}/dev-requirements.txt commands = - coverage run --parallel -m pytest {posargs} - flake8 . --count --show-source --statistics + coverage run --parallel -m pytest {posargs} ultrasync + flake8 ultrasync --count --show-source --statistics [testenv:coverage-report] deps = coverage skip_install = true commands= - coverage combine - coverage report + coverage combine ultrasync + coverage report ultrasync diff --git a/ultrasync/__init__.py b/ultrasync/__init__.py index d80531f..8cd3ede 100644 --- a/ultrasync/__init__.py +++ b/ultrasync/__init__.py @@ -24,7 +24,7 @@ # THE SOFTWARE. __title__ = 'ultrasync' -__version__ = '0.9.8' +__version__ = '0.9.9' __author__ = 'Chris Caron' __license__ = 'MIT' __copywrite__ = 'Copyright (C) 2023 Chris Caron ' diff --git a/ultrasync/cli.py b/ultrasync/cli.py index 1659957..dc59034 100644 --- a/ultrasync/cli.py +++ b/ultrasync/cli.py @@ -106,7 +106,8 @@ def print_version_msg(): help='Specify the Zone you wish to target with a --bypass ' 'action.') @click.option('--output', type=int, metavar='OUTPUT', - help='Specify the Output you wish to control with a --switch action.') + help='Specify the Output you wish to control with a ' + '--switch action.') @click.option('--switch', type=int, metavar='STATE', help='Set to 1 to turn on an output, set to 0 to turn it off.') @@ -206,16 +207,15 @@ def main(config, debug_dump, full_debug_dump, scene, bypass, details, watch, if not usync.set_zone_bypass(zone=zone, state=bypass): sys.exit(1) actioned = True - + if output is not None and switch is not None: - if switch not in [0,1]: + if switch not in (0, 1): logger.error('Switch state should be either 0 or 1') sys.exit(1) if not usync.set_output_control(output=output, state=switch): sys.exit(1) actioned = True - if watch: area_delta = {} zone_delta = {} diff --git a/ultrasync/main.py b/ultrasync/main.py index cdc6894..48d6287 100644 --- a/ultrasync/main.py +++ b/ultrasync/main.py @@ -221,7 +221,8 @@ def login(self): self.release, )) - if not self._areas(response=response) or not self._zones() or not self.output_control(): + if not self._areas(response=response) \ + or not self._zones() or not self.output_control(): # No match and/or bad login logger.error('Failed to authenticate to {}'.format(self.host)) return False @@ -602,15 +603,15 @@ def set_zone_bypass(self, zone, state=False): } if self.vendor in (NX595EVendor.ZEROWIRE, NX595EVendor.XGEN8): - payload.update({ + payload.update({ 'cmd': 5, 'opt': int(state), 'zone': zone - 1, }) - - # Send our response - response = self.__get( - '/user/zonefunction.cgi', payload=payload) + + # Send our response + response = self.__get( + '/user/zonefunction.cgi', payload=payload) elif self.vendor in (NX595EVendor.COMNAV): # Call comnav_process_zones to update can_bypass attribute @@ -619,8 +620,8 @@ def set_zone_bypass(self, zone, state=False): # Get the current can_bypass state of the zone can_bypass = self.zones[zone - 1]['can_bypass'] - # If the current can_bypass state does not match the desired bypass state, - # toggle the bypass state + # If the current can_bypass state does not match the desired + # bypass state, toggle the bypass state if can_bypass == state: # Start our payload off with our session identifier payload = { @@ -628,20 +629,19 @@ def set_zone_bypass(self, zone, state=False): 'comm': 82, 'data0': zone - 1, } - else: + else: payload = {} # Send our response response = self.__get( '/user/zonefunction.cgi', payload=payload) - + else: # self.vendor is NX595EVendor.{ZEROWIRE, XGEN} logger.error( 'Bypass not implemented for vendor {}'.format(self.vendor)) return False - if not response: logger.info( 'Failed to set bypass={} for zone {}'.format(state, zone)) @@ -2107,7 +2107,7 @@ def output_control(self): if not self.session_id and not self.login(): return False - + logger.info('Retrieving initial Output Control information.') # Perform our Query @@ -2117,12 +2117,16 @@ def output_control(self): if self.vendor is NX595EVendor.COMNAV: # Regex to capture output names and states - name_pattern = re.compile(r'var oname(\d) = decodeURIComponent\(decode_utf8\("([^"]*)"\)\);') + name_pattern = re.compile( + r'var oname(\d) = decodeURIComponent' + r'\(decode_utf8\("([^"]*)"\)\);') state_pattern = re.compile(r'var ostate(\d) = "(\d)";') # Extract names and states - names = {int(m.group(1)): unquote(m.group(2)) for m in name_pattern.finditer(response)} - states = {int(m.group(1)): m.group(2) for m in state_pattern.finditer(response)} + names = {int(m.group(1)): unquote(m.group(2)) + for m in name_pattern.finditer(response)} + states = {int(m.group(1)): m.group(2) + for m in state_pattern.finditer(response)} # Store our outputs: for i in range(1, max(len(names), len(states)) + 1): @@ -2135,9 +2139,10 @@ def output_control(self): # Otherwise: else: logger.error( - 'Output Control not implemented for vendor {}'.format(self.vendor)) + 'Output Control not implemented for vendor {}'.format( + self.vendor)) return False - + return True def set_output_control(self, output, state): @@ -2152,7 +2157,7 @@ def set_output_control(self, output, state): logger.error( '{} is not valid output'.format(output)) return False - + # A boolean for tracking any errors has_error = False @@ -2166,7 +2171,7 @@ def set_output_control(self, output, state): payload.update({ 'onum': output, 'ostate': state - }) + }) # Send our response response = self.__get( @@ -2176,7 +2181,8 @@ def set_output_control(self, output, state): # Otherwise: else: logger.error( - 'Output Control not implemented for vendor {}'.format(self.vendor)) + 'Output Control not implemented for vendor {}'.format( + self.vendor)) return False if not response: @@ -2186,7 +2192,7 @@ def set_output_control(self, output, state): logger.info( 'Set state={} for output {} successfully'.format(state, output)) - + return not has_error def _sequence(self):