pax_global_header00006660000000000000000000000064140354074170014517gustar00rootroot0000000000000052 comment=1135ecc5f8a711ab3907c1e5e1d9dbdeadb5830d dgomes-pykmtronic-1135ecc/000077500000000000000000000000001403540741700156005ustar00rootroot00000000000000dgomes-pykmtronic-1135ecc/.github/000077500000000000000000000000001403540741700171405ustar00rootroot00000000000000dgomes-pykmtronic-1135ecc/.github/workflows/000077500000000000000000000000001403540741700211755ustar00rootroot00000000000000dgomes-pykmtronic-1135ecc/.github/workflows/python-publish.yml000066400000000000000000000015251403540741700247100ustar00rootroot00000000000000# This workflows will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries name: Upload Python Package on: release: types: [created] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.8' - name: Install dependencies run: | python -m pip install --upgrade pip pip install setuptools wheel twine - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | python setup.py sdist twine upload dist/* dgomes-pykmtronic-1135ecc/.gitignore000066400000000000000000000001121403540741700175620ustar00rootroot00000000000000*.code-workspace *.egg-info *.pyc /dist /.vscode /lixo.py /teste.py /venv dgomes-pykmtronic-1135ecc/CHANGES.md000066400000000000000000000002071403540741700171710ustar00rootroot00000000000000# 0.3.0 Serialize HTTP requests by spacing 100ms # 0.2.0 Adds toggle() method to use relays.cgi?relay=NUMBER # 0.1.0 Initial Release dgomes-pykmtronic-1135ecc/README.md000066400000000000000000000005061403540741700170600ustar00rootroot00000000000000# pykmtronic [![PyPI version](https://badge.fury.io/py/pykmtronic.svg)](https://badge.fury.io/py/pykmtronic) This library provides a Python3 interface to [KM-Tronic web relays](https://info.kmtronic.com/lan-relays.html). # Tested equipments: - https://info.kmtronic.com/manuals/user_manuals/W2CR_WEB_TWO_CHANNEL_RELAY.pdf dgomes-pykmtronic-1135ecc/example.py000066400000000000000000000020641403540741700176070ustar00rootroot00000000000000"""Example usage of pykmtronic.""" import asyncio import aiohttp import time import logging from pykmtronic.auth import Auth from pykmtronic.hub import KMTronicHubAPI logging.basicConfig(level=logging.DEBUG) async def main(): async with aiohttp.ClientSession() as session: auth = Auth(session, "http://192.168.1.160", "admin", "admin") api = KMTronicHubAPI(auth) relays = await api.async_get_relays() r = relays[0] print("is relay1 ON?", r.is_energised) await r.energise() time.sleep(2) print("is relay1 ON?", r.is_energised) await r.de_energise() time.sleep(2) await api.async_update_relays() print("is relay1 ON?", r.is_energised) time.sleep(2) await r.toggle() time.sleep(2) await api.async_update_relays() print("is relay1 ON?", r.is_energised) time.sleep(2) await r.toggle() time.sleep(2) await api.async_update_relays() print("is relay1 ON?", r.is_energised) asyncio.run(main()) dgomes-pykmtronic-1135ecc/pykmtronic/000077500000000000000000000000001403540741700177775ustar00rootroot00000000000000dgomes-pykmtronic-1135ecc/pykmtronic/__init__.py000066400000000000000000000000551403540741700221100ustar00rootroot00000000000000from .consts import * __version__ = "0.2.1" dgomes-pykmtronic-1135ecc/pykmtronic/auth.py000066400000000000000000000013201403540741700213060ustar00rootroot00000000000000import asyncio from aiohttp import ClientSession, ClientResponse, BasicAuth class Auth: """Class to make authenticated requests.""" def __init__(self, websession: ClientSession, host: str, user: str, password: str): """Initialize the auth.""" self.websession = websession self.host = host self._auth = BasicAuth(user, password) self.lock = asyncio.Lock() async def request(self, path: str) -> ClientResponse: """Make a request.""" async with self.lock: return await self.websession.request( "GET", f"{self.host}/{path}", auth=self._auth, ) await asyncio.sleep(0.1) dgomes-pykmtronic-1135ecc/pykmtronic/consts.py000066400000000000000000000000401403540741700216540ustar00rootroot00000000000000STATUS_ENDPOINT = "/status.xml" dgomes-pykmtronic-1135ecc/pykmtronic/hub.py000066400000000000000000000032351403540741700211320ustar00rootroot00000000000000"""Representation of a KMtronic Device.""" from typing import List from lxml import etree import logging from .auth import Auth from .relay import Relay logger = logging.getLogger(__name__) class KMTronicHubAPI: """Class to communicate with the KMTronic Web based API.""" def __init__(self, auth: Auth): """Initialize the API and store the auth so we can make requests.""" self.auth = auth self.host = auth.host self.relays = [] @property def name(self) -> str: """Return the name of the kmtronic device, usually an IP.""" return self.host async def async_get_status(self) -> List[tuple]: """Return status on relays.""" resp = await self.auth.request("status.xml") resp.raise_for_status() response_xml = etree.fromstring(await resp.text()) response = response_xml.xpath("/response/*") status = {} for relay in response[1:]: relay_num = int(relay.tag[5:]) relay_status = relay.text == "1" status[relay_num] = relay_status return status async def async_get_relays(self) -> List[Relay]: """Create relay representations from status information.""" status = await self.async_get_status() self.relays = [ Relay(number, status, self.auth) for number, status in status.items() ] return self.relays async def async_update_relays(self): """Update the status of each relay.""" status = await self.async_get_status() logger.debug("Status of relays: %s", status) for relay in self.relays: relay.is_on = status[relay.id] dgomes-pykmtronic-1135ecc/pykmtronic/relay.py000066400000000000000000000033241403540741700214670ustar00rootroot00000000000000"""Representation of a single relay.""" from pykmtronic.auth import Auth import logging logger = logging.getLogger(__name__) class Relay: """Relay Object.""" def __init__(self, number: int, status: bool, auth: Auth): """Relay constructor.""" self._relay = number self.auth = auth self.is_energised = status # True means energised/ON @property def id(self) -> int: """Id of the relay, position of the relay in the device.""" return self._relay @property def is_energised(self): """Wether the relay is energised (ON/True) or not (OFF/False).""" return self._is_energised @is_energised.setter def is_energised(self, b): """Set state variable _is_energised.""" logger.debug( f"Relay{self._relay} is now {'Energised' if b else 'De-energised'}" ) self._is_energised = b async def energise(self): """Energises the relay connecting COM to NO.""" logger.debug(f"Sending ... FF{self._relay:02}01") resp = await self.auth.request(f"FF{self._relay:02}01") resp.raise_for_status() self.is_energised = True async def de_energise(self): """De-energises the relay connecting COM to NO.""" logger.debug(f"Sending ... FF{self._relay:02}00") resp = await self.auth.request(f"FF{self._relay:02}00") resp.raise_for_status() self.is_energised = False async def toggle(self): """Toggle the relay.""" logger.debug(f"Calling relays.cgi?relay={self._relay}") resp = await self.auth.request(f"relays.cgi?relay={self._relay}") resp.raise_for_status() self.is_energised = not self.is_energised dgomes-pykmtronic-1135ecc/requirements.txt000066400000000000000000000000331403540741700210600ustar00rootroot00000000000000aiohttp==3.6.2 lxml==4.5.2 dgomes-pykmtronic-1135ecc/setup.py000066400000000000000000000016531403540741700173170ustar00rootroot00000000000000from setuptools import setup, find_packages import pykmtronic long_description = open('README.md').read() setup( name='pykmtronic', version=pykmtronic.__version__, license='MIT License', url='https://github.com/dgomes/pykmtronic', author='Diogo Gomes', author_email='diogogomes@gmail.com', description='Python library to interface with KM Tronic Web Relays', long_description=long_description, long_description_content_type='text/markdown', packages=['pykmtronic'], zip_safe=True, platforms='any', install_requires=[ 'aiohttp', 'lxml', ], classifiers=[ 'License :: OSI Approved :: MIT License', 'Intended Audience :: Developers', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Libraries :: Python Modules' ] )