pax_global_header00006660000000000000000000000064144321411650014513gustar00rootroot0000000000000052 comment=45ef1e06624d9abb78027d189ada4de3ec15e72d ArdaSeremet-progettihwsw-45ef1e0/000077500000000000000000000000001443214116500170665ustar00rootroot00000000000000ArdaSeremet-progettihwsw-45ef1e0/.gitignore000066400000000000000000000037621443214116500210660ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # 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/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ArdaSeremet-progettihwsw-45ef1e0/LICENSE000066400000000000000000000020371443214116500200750ustar00rootroot00000000000000Copyright (c) 2020 Arda Seremet 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.ArdaSeremet-progettihwsw-45ef1e0/ProgettiHWSW/000077500000000000000000000000001443214116500213745ustar00rootroot00000000000000ArdaSeremet-progettihwsw-45ef1e0/ProgettiHWSW/ProgettiHWSWAPI.py000066400000000000000000000105111443214116500246040ustar00rootroot00000000000000# Copyright (c) 2020 Arda Seremet from lxml import etree import base64 from .api import API from .input import Input from .relay import Relay from .analog import AnalogInput from .temperature import Temperature from .const import STATUS_XML_PATH import random class ProgettiHWSWAPI: """Class to communicate with ProgettiHWSW boards.""" def __init__(self, ip: str): """Initialize the API and return the corresponding object class.""" self.api = API(f"http://{ip}") self.ip = ip self.board_data = None def create_unique_id(self, number, io_type: str): """Generate an id based on IP address and a number.""" unencoded_ascii = ( f"{self.ip}_{io_type}_{number}_{random.random()}").encode('ascii') base64_bytes = base64.b64encode(unencoded_ascii) return base64_bytes.decode('ascii') async def check_board(self): """Check if this board is existing and valid.""" request = await self.api.request(STATUS_XML_PATH) if request is False: return False root = etree.XML(bytes(request, encoding='utf-8')) relay_tags = root.xpath("//*[starts-with(local-name(), 'led')]") input_tags = root.xpath("//*[starts-with(local-name(), 'btn')]") analog_tags = root.xpath("//*[starts-with(local-name(), 'pot')]") temp_tags = root.xpath("//*[starts-with(local-name(), 'temp')]") rfid_tags = root.xpath("//*[starts-with(local-name(), 'rfid')]") if (len(relay_tags) + len(input_tags) + len(analog_tags) + len(temp_tags) + len(rfid_tags)) <= 0: return False self.board_data = { "title": f"{len(relay_tags)}R Board", "relays": [i.tag[3:] for i in relay_tags], "inputs": [i.tag[3:] for i in input_tags], "analogs": [i.tag[3:] for i in analog_tags], "temps": [i.tag[4:] for i in temp_tags], "rfid": True if (len(rfid_tags) > 0) else False, } return self.board_data async def get_states_by_tag_prefix(self, tag: str, is_analog: bool = False): """Return all states with the XML tag prefix.""" request = await self.api.request(STATUS_XML_PATH) if request is False: return False root = etree.XML(bytes(request, encoding='utf-8')) tags = root.xpath(f"//*[starts-with(local-name(), '{tag}')]") if len(tags) <= 0: return False states = {} for i in tags: number = str(i.tag[len(tag):], 16) if is_analog: states[number] = i.text else: states[number] = ( True if i.text in ("up", "1", "on") else False ) return states async def get_rfid(self): """Return the RFID number of lastly read tag.""" request = await self.api.request(STATUS_XML_PATH) if request is False: return False root = etree.XML(bytes(request, encoding='utf-8')) tags = root.xpath(f"//*[starts-with(local-name(), 'rfid')]") if len(tags) <= 0: return False rfid_number = tags[0].text return rfid_number async def get_switches(self): """Return all switch states.""" return await self.get_states_by_tag_prefix("led") async def get_inputs(self): """Return all input states.""" return await self.get_states_by_tag_prefix("btn") async def get_pots(self): """Return all analog input states.""" return await self.get_states_by_tag_prefix("pot", True) async def get_temps(self): """Return all temperature states.""" return await self.get_states_by_tag_prefix("temp", True) def get_relay(self, relay_number: int, relay_mode: str = "bistable") -> Relay: """Return the Relay class.""" return Relay(self.api, relay_number, relay_mode) def get_input(self, input_number: int) -> Input: """Return the Input class.""" return Input(self.api, input_number) def get_pot(self, pot_number: int) -> AnalogInput: """Return the AnalogInput class.""" return AnalogInput(self.api, pot_number) def get_temp(self, temp_number: int) -> Temperature: """Return the Temperature class.""" return Temperature(self.api, temp_number) ArdaSeremet-progettihwsw-45ef1e0/ProgettiHWSW/__init__.py000066400000000000000000000000741443214116500235060ustar00rootroot00000000000000# Copyright (c) 2020 Arda Seremet ArdaSeremet-progettihwsw-45ef1e0/ProgettiHWSW/analog.py000066400000000000000000000020131443214116500232030ustar00rootroot00000000000000# Copyright (c) 2020 Arda Seremet from lxml import etree from .api import API from .const import STATUS_XML_PATH class AnalogInput: """Class that represents an analog input object.""" def __init__(self, api: API, pot_number: int): """Initialize AnalogInput class.""" self.pot_number = int(pot_number) self.api = api self._state = None @property def id(self) -> int: """Return the analog number.""" return self.pot_number @property def state(self): """Return the analog input value.""" return self._state async def update(self): """Update the input status.""" request = await self.api.request(STATUS_XML_PATH) if request is False: return False root = etree.XML(bytes(request, encoding='utf-8')) path = root.xpath(f"//pot{str(self.pot_number)}") if not len(path) > 0: return False self._state = path[0].text return True ArdaSeremet-progettihwsw-45ef1e0/ProgettiHWSW/api.py000066400000000000000000000014431443214116500225210ustar00rootroot00000000000000# Copyright (c) 2020 Arda Seremet import aiohttp import async_timeout from asyncio import TimeoutError class API: """Class to interact with the API of ProgettiHWSW boards.""" def __init__(self, ip: str): """Initialize the API.""" self.ip = ip async def request(self, path: str): try: with async_timeout.timeout(5): async with aiohttp.request("GET", f"{self.ip}/{path}") as resp: return await resp.text() except TimeoutError: return False async def execute(self, code: int): """Make requests with API codes for boards.""" try: return await self.request(f"index.htm?execute={code}") except Exception: return False ArdaSeremet-progettihwsw-45ef1e0/ProgettiHWSW/const.py000066400000000000000000000002551443214116500230760ustar00rootroot00000000000000# Copyright (c) 2020 Arda Seremet TURN_ON_BASE = 16 TURN_OFF_BASE = 116 TOGGLE_BASE = 0 TEMP_MONOSTABLE_BASE = 200 STATUS_XML_PATH = "status.xml" ArdaSeremet-progettihwsw-45ef1e0/ProgettiHWSW/input.py000066400000000000000000000020521443214116500231040ustar00rootroot00000000000000# Copyright (c) 2020 Arda Seremet from lxml import etree from .api import API from .const import STATUS_XML_PATH class Input: """Class that represents an input object.""" def __init__(self, api: API, input_number: int): """Initialize Input class.""" self.input_number = int(input_number) self.api = api self.state = None @property def id(self) -> int: """Return the input number.""" return self.input_number @property def is_on(self) -> bool: """Return if the input is on.""" return self.state async def update(self): """Update the input status.""" request = await self.api.request(STATUS_XML_PATH) if request is False: return False root = etree.XML(bytes(request, encoding='utf-8')) path = root.xpath(f"//btn{str(self.input_number)}") if not len(path) > 0: return False self.state = True if path[0].text in ("up", "1", "on") else False return True ArdaSeremet-progettihwsw-45ef1e0/ProgettiHWSW/relay.py000066400000000000000000000032321443214116500230620ustar00rootroot00000000000000# Copyright (c) 2020 Arda Seremet from lxml import etree from .api import API from .const import ( STATUS_XML_PATH, TEMP_MONOSTABLE_BASE, TOGGLE_BASE, TURN_OFF_BASE, TURN_ON_BASE, ) class Relay: """Class that represents a relay object.""" def __init__(self, api: API, relay_number: int, relay_mode: str): """Initialize Relay class.""" self.relay_number = int(relay_number) self.relay_mode = relay_mode self.api = api self.state = None @property def id(self) -> int: """Return the relay number.""" return self.relay_number @property def is_on(self) -> bool: """Return if the relay is on.""" return self.state async def toggle(self): """Toggle the relay.""" command = TOGGLE_BASE + self.relay_number await self.api.execute(command) async def control(self, state: bool): """Control the relay state.""" command = ( (TURN_ON_BASE if self.relay_mode == "bistable" else TEMP_MONOSTABLE_BASE) if state is True else TURN_OFF_BASE ) + self.relay_number await self.api.execute(command) async def update(self): """Update the relay status.""" request = await self.api.request(STATUS_XML_PATH) if request is False: return False root = etree.XML(bytes(request, encoding='utf-8')) path = root.xpath(f"//led{str(self.relay_number)}") if not len(path) > 0: return False self.state = True if path[0].text in ("up", "1", "on") else False return True ArdaSeremet-progettihwsw-45ef1e0/ProgettiHWSW/temperature.py000066400000000000000000000020171443214116500243030ustar00rootroot00000000000000# Copyright (c) 2020 Arda Seremet from lxml import etree from .api import API from .const import STATUS_XML_PATH class Temperature: """Class that represents a temperature sensor.""" def __init__(self, api: API, temp_number: int): """Initialize Temperature class.""" self.temp_number = int(temp_number) self.api = api self._state = None @property def id(self) -> int: """Return the input number.""" return self.temp_number @property def state(self): """Return the temperature value.""" return self._state async def update(self): """Update the input status.""" request = await self.api.request(STATUS_XML_PATH) if request is False: return False root = etree.XML(bytes(request, encoding = 'utf-8')) path = root.xpath(f"//temp{str(self.temp_number)}") if not len(path) > 0: return False self._state = path[0].text return True ArdaSeremet-progettihwsw-45ef1e0/README.md000066400000000000000000000002741443214116500203500ustar00rootroot00000000000000# ProgettiHWSW Python Controller This Python3 package makes it easier to control ProgettiHWSW relay boards. More detailed explanation will be added later. This project uses MIT license.ArdaSeremet-progettihwsw-45ef1e0/setup.py000066400000000000000000000022371443214116500206040ustar00rootroot00000000000000import os import sys import setuptools def read_file(file): with open(os.path.join(os.path.dirname(__file__), file)) as readable: return readable.read() README = read_file("README.md") setuptools.setup( name="ProgettiHWSW", version="0.1.3", long_description="\n\n".join([README]), long_description_content_type="text/markdown", description="Controls ProgettiHWSW relay boards.", url="http://github.com/ardaseremet/progettihwsw", download_url="http://github.com/ardaseremet/progettihwsw/tarball/0.1.3", author="Arda Seremet", author_email="ardaseremet@outlook.com", license="MIT", packages=setuptools.find_packages(), install_requires=["aiohttp", "lxml"], zip_safe=False, classifiers=[ "License :: OSI Approved :: MIT License", "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Intended Audience :: Telecommunications Industry", "Intended Audience :: Information Technology", "Intended Audience :: End Users/Desktop", "Intended Audience :: Legal Industry", ], )