diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..341e6ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,148 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +**/dist/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +tmp/ +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 + +# 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 +.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/ + +# intellij +.idea/ + +**/.env + +# Visual Studio Code +.vscode/ +.history + +# for vscode dev container +local_home/ +.devcontainer + + +# Editor Related +.idea/* +*.iml + +# Profiler +fil-result diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e5e5307 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: + python -m pytest -s -v --doctest-modules diff --git a/ambientweather.py b/ambientweather.py deleted file mode 100644 index 03eb41d..0000000 --- a/ambientweather.py +++ /dev/null @@ -1,57 +0,0 @@ -import requests -from lxml import html -from datetime import datetime - - -TITLE = 'LiveData' # HTML live data page title - - -class SensorData(object): - """ - time - temp - humidity - abs_press - rel_press - battery ('Normal') - """ - - def parse(self, live_data_html): - """ - Extract sensor's data from html (LiveData.html from your ObserverIP) - Returns touple with (sensor1, sensor2 -> SensorData) - """ - - tree = html.fromstring(live_data_html) - title = tree.xpath('//title/text()') - if title[0] != TITLE: - raise Exception('Wrong html page. Good one have to have title {}'.format(TITLE)) - - inSensor = SensorData() - time_str = tree.xpath('//input[@name="CurrTime"]/@value')[0] - inSensor.time = datetime.strptime(time_str, '%H:%M %m/%d/%Y') - inSensor.temp = float(tree.xpath('//input[@name="inTemp"]/@value')[0]) - inSensor.humidity = float(tree.xpath('//input[@name="inHumi"]/@value')[0]) - inSensor.abs_press = float(tree.xpath('//input[@name="AbsPress"]/@value')[0]) - inSensor.rel_press = float(tree.xpath('//input[@name="RelPress"]/@value')[0]) - inSensor.battery = tree.xpath('//input[@name="inBattSta"]/@value')[0] - - outSensor = SensorData() - outSensor.time = inSensor.time - outSensor.temp = float(tree.xpath('//input[@name="outTemp"]/@value')[0]) - outSensor.humidity = float(tree.xpath('//input[@name="outHumi"]/@value')[0]) - outSensor.abs_press = inSensor.abs_press - outSensor.rel_press = inSensor.rel_press - outSensor.battery = tree.xpath('//input[@name="outBattSta2"]/@value')[0] - - return inSensor, outSensor - - def get(self, url): - """ - Load ObserverIP live data page from the URL and parse it - - Returns touple with (sensor1, sensor2 -> SensorData) - """ - - page = requests.get(url).content - return self.parse(page) diff --git a/ambientweather_test.py b/ambientweather_test.py deleted file mode 100644 index 07fb824..0000000 --- a/ambientweather_test.py +++ /dev/null @@ -1,42 +0,0 @@ -from ambientweather import SensorData -from datetime import datetime - - -def test(): - with open('LiveData.html', 'r') as f: - page = f.read() - - sensor_data = SensorData() - inSensor, outSensor = sensor_data.parse(page) - - print(f'Time: {inSensor.time}\n') - print('''Indoor\n{delimiter}\nTemperature: {temp}\nHumidity: {humidity} -Absolute preassure: {abs_press}\nRelative preassure: {rel_press}\nBattery status: {battery}\n'''.format( - delimiter='='*20, - temp=inSensor.temp, - humidity=inSensor.humidity, - abs_press=inSensor.abs_press, - rel_press=inSensor.rel_press, - battery=inSensor.battery - )) - print('''Outdoor\n{delimiter}\nTemperature: {temp}\nHumidity: {humidity} -Battery status: {battery}\n'''.format( - delimiter='='*20, - temp=inSensor.temp, - humidity=inSensor.humidity, - battery=inSensor.battery - )) - - assert inSensor.time == datetime.strptime('2017/10/06 13:55:00', '%Y/%m/%d %H:%M:%S') - assert inSensor.temp == 21.9 - assert inSensor.humidity == 50.0 - assert inSensor.abs_press == 744.29 - assert inSensor.rel_press == 728.31 - assert inSensor.battery == 'Normal' - assert outSensor.temp == 10.1 - assert outSensor.humidity == 71.0 - assert outSensor.battery == 'Normal' - - - - diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..9398455 --- /dev/null +++ b/conftest.py @@ -0,0 +1,13 @@ +""" +Tests run from the project root folder. +But the python code expects to run inside server folder. + +So for tests we add server folder to sys.path. + +This file is loaded first by py.test therefore we change sys.path for all other python files. +""" +import os.path +import sys + + +sys.path.append(os.path.join(os.path.dirname(__file__), 'src')) diff --git a/src/ambientweather.py b/src/ambientweather.py new file mode 100644 index 0000000..b42e9c1 --- /dev/null +++ b/src/ambientweather.py @@ -0,0 +1,57 @@ +import requests +from lxml import html +from datetime import datetime + + +TITLE = 'LiveData' # HTML live data page title + + +class SensorData(object): + """ + time + temp + humidity + abs_press + rel_press + battery ('Normal') + """ + + def parse(self, live_data_html): + """ + Extract sensor's data from html (LiveData.html from your ObserverIP) + Returns touple with (sensor1, sensor2 -> SensorData) + """ + + tree = html.fromstring(live_data_html) + title = tree.xpath('//title/text()') + if title[0] != TITLE: + raise ValueError(f'Wrong html page. Good one have to have title {TITLE}') + + in_sensor = SensorData() + time_str = tree.xpath('//input[@name="CurrTime"]/@value')[0] + in_sensor.time = datetime.strptime(time_str, '%H:%M %m/%d/%Y') + in_sensor.temp = float(tree.xpath('//input[@name="inTemp"]/@value')[0]) + in_sensor.humidity = float(tree.xpath('//input[@name="inHumi"]/@value')[0]) + in_sensor.abs_press = float(tree.xpath('//input[@name="AbsPress"]/@value')[0]) + in_sensor.rel_press = float(tree.xpath('//input[@name="RelPress"]/@value')[0]) + in_sensor.battery = tree.xpath('//input[@name="inBattSta"]/@value')[0] + + out_sensor = SensorData() + out_sensor.time = in_sensor.time + out_sensor.temp = float(tree.xpath('//input[@name="outTemp"]/@value')[0]) + out_sensor.humidity = float(tree.xpath('//input[@name="outHumi"]/@value')[0]) + out_sensor.abs_press = in_sensor.abs_press + out_sensor.rel_press = in_sensor.rel_press + out_sensor.battery = tree.xpath('//input[@name="outBattSta2"]/@value')[0] + + return in_sensor, out_sensor + + def get(self, url): + """ + Load ObserverIP live data page from the URL and parse it + + Returns touple with (sensor1, sensor2 -> SensorData) + """ + + page = requests.get(url).content + return self.parse(page) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..4a23385 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,24 @@ +from pathlib import Path +import pytest + + +def _get_repo_root_dir() -> str: + """ + :return: path to the project folder. + `tests/` should be in the same folder and this file should be in the root of `tests/`. + """ + return str(Path(__file__).parent.parent) + + +ROOT_DIR = _get_repo_root_dir() +RESOURCES = Path(f"{ROOT_DIR}/tests/resources") + +@pytest.fixture( + scope="function", + params=[ + "LiveData.html", + ], +) +def ambientweather_data(request) -> str: + with (RESOURCES / request.param).open("r", encoding="utf8") as data: + return data.read() diff --git a/LiveData.html b/tests/resources/LiveData.html similarity index 100% rename from LiveData.html rename to tests/resources/LiveData.html diff --git a/tests/test_ambientweather_data.py b/tests/test_ambientweather_data.py new file mode 100644 index 0000000..7b51aa7 --- /dev/null +++ b/tests/test_ambientweather_data.py @@ -0,0 +1,39 @@ +from ambientweather import SensorData +from datetime import datetime + + +def test_ambientweather_data(ambientweather_data: str): + sensor_data = SensorData() + in_sensor, out_sensor = sensor_data.parse(ambientweather_data) + + print(f'Time: {in_sensor.time}\n') + print('''Indoor\n{delimiter}\nTemperature: {temp}\nHumidity: {humidity} +Absolute preassure: {abs_press}\nRelative preassure: {rel_press}\nBattery status: {battery}\n'''.format( + delimiter='='*20, + temp=in_sensor.temp, + humidity=in_sensor.humidity, + abs_press=in_sensor.abs_press, + rel_press=in_sensor.rel_press, + battery=in_sensor.battery + )) + print('''Outdoor\n{delimiter}\nTemperature: {temp}\nHumidity: {humidity} +Battery status: {battery}\n'''.format( + delimiter='='*20, + temp=in_sensor.temp, + humidity=in_sensor.humidity, + battery=in_sensor.battery + )) + + assert in_sensor.time == datetime.strptime('2017/10/06 13:55:00', '%Y/%m/%d %H:%M:%S') + assert in_sensor.temp == 21.9 + assert in_sensor.humidity == 50.0 + assert in_sensor.abs_press == 744.29 + assert in_sensor.rel_press == 728.31 + assert in_sensor.battery == 'Normal' + assert out_sensor.temp == 10.1 + assert out_sensor.humidity == 71.0 + assert out_sensor.battery == 'Normal' + + + +