Skip to content

Commit

Permalink
bumped version to v0.9.9
Browse files Browse the repository at this point in the history
  • Loading branch information
caronc committed Aug 7, 2023
1 parent fd31434 commit a144903
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 104 deletions.
82 changes: 82 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -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 }}
30 changes: 0 additions & 30 deletions .travis.yml

This file was deleted.

22 changes: 1 addition & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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/)<br/>
[![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/)

Expand Down
1 change: 0 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,3 @@ addopts = --verbose -ra
python_files = tests/test_*.py
filterwarnings =
once::Warning
strict = true
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
34 changes: 9 additions & 25 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tox]
envlist = py38,py39,py310,coverage-report

envlist = py311,coverage-report
skipsdist = true

[testenv]
# Prevent random setuptools/pip breakages like
Expand All @@ -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
2 changes: 1 addition & 1 deletion ultrasync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <lead2gold@gmail.com>'
Expand Down
8 changes: 4 additions & 4 deletions ultrasync/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.')
Expand Down Expand Up @@ -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 = {}
Expand Down
48 changes: 27 additions & 21 deletions ultrasync/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -619,29 +620,28 @@ 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 = {
'sess': self.session_id,
'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))
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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

Expand All @@ -2166,7 +2171,7 @@ def set_output_control(self, output, state):
payload.update({
'onum': output,
'ostate': state
})
})

# Send our response
response = self.__get(
Expand All @@ -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:
Expand All @@ -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):
Expand Down

0 comments on commit a144903

Please sign in to comment.