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