Skip to content

Commit

Permalink
Merge branch 'dev' into patch-5
Browse files Browse the repository at this point in the history
  • Loading branch information
nielstron committed Jan 5, 2019
2 parents 0465879 + 9f2c151 commit 19a60a3
Show file tree
Hide file tree
Showing 28 changed files with 704 additions and 62 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Pipfile.lock
### PyCharm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
.idea

# User-specific stuff
.idea/**/workspace.xml
Expand Down
4 changes: 0 additions & 4 deletions .idea/misc.xml

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

13 changes: 0 additions & 13 deletions .idea/pyblnet.iml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/vcs.xml

This file was deleted.

24 changes: 24 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
language: python

python:
# - "2.7"
- "3.4"
- "3.5"
- "3.6"
- "3.7"
dist: xenial
sudo: true

matrix:
fast_finish: true

install:
- pip install coverage
- pip install coveralls

script:
- coverage run setup.py test

after_success:
- coverage report
- coveralls
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# PyBLNET - a very basic python BL-NET bridge
[![Build Status](https://travis-ci.com/nielstron/pyblnet.svg?branch=master)](https://travis-ci.com/nielstron/pyblnet)
[![Coverage Status](https://coveralls.io/repos/github/nielstron/pyblnet/badge.svg?branch=master)](https://coveralls.io/github/nielstron/pyblnet?branch=master)

A package that connects to the BL-NET that is connected itself to a UVR1611 device by Technische Alternative.
It is able to read digital and analog values as well as to set switches to ON/OFF/AUTO

Expand Down Expand Up @@ -27,4 +30,4 @@ blnet = BLNETDirect(ip)
print(blnet.get_latest())
# Still inofficial because unexplicably failing often
print(blnet._get_data(1))
```
```
35 changes: 32 additions & 3 deletions pyblnet/blnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"""
Created on 13.08.2018
General high-level BLNET interface.
Abstracts from actual type of connection to the BLNET and provides as much functionality
as possible.
@author: Niels
"""
from pyblnet.blnet_web import BLNETWeb
Expand All @@ -13,8 +18,19 @@

class BLNET(object):
"""
General high-level BLNET class, using just
what is available and more precise
General high-level BLNET interface.
Abstracts from actual type of connection to the BLNET and provides as much functionality
as possible.
Attributes:
address ip address of the BLNET
web_port port of the HTTP interface
password password for authenticating on the HTTP interface
ta_port port of the PC-BLNET interface
timeout timeout for requests
max_retries maximum number of connection retries before aborting
use_web boolean about whether to make use of the HTTP interface
use_ta boolean about whether to make use of the (buggy) PC-BLNET interface
"""

def __init__(self,
Expand Down Expand Up @@ -42,7 +58,7 @@ def __init__(self,
self.blnet_web = None
self.blnet_direct = None
if use_web:
self.blnet_web = BLNETWeb(address, password, timeout)
self.blnet_web = BLNETWeb("{}:{}".format(address, web_port), password, timeout)
if use_ta:
# The address might not have a resulting hostname
# especially not if not prefixed with http://
Expand All @@ -53,6 +69,7 @@ def __init__(self,
def fetch(self, node=None):
"""
Fetch all available data about selected node
(defaults to active node on the device)
"""
data = {
'analog': {},
Expand Down Expand Up @@ -84,12 +101,24 @@ def fetch(self, node=None):
return data

def turn_on(self, digital_id, can_node=None):
"""
Turn switch with given id on given node on
Return: no error during set operation
"""
return self._turn(digital_id, 'EIN', can_node)

def turn_off(self, digital_id, can_node=None):
"""
Turn switch with given id on given node off
Return: no error during set operation
"""
return self._turn(digital_id, 'AUS', can_node)

def turn_auto(self, digital_id, can_node=None):
"""
Turn switch with given id on given node to "AUTO"/ give control over to UVR
Return: no error during set operation
"""
return self._turn(digital_id, 'AUTO', can_node)

def _turn(self, digital_id, value, can_node=None):
Expand Down
9 changes: 7 additions & 2 deletions pyblnet/blnet_conn.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class BLNETDirect(object):
"""
A class for establishing a direct connection to the BLNET (rather than
scraping the web interface)
It uses this protocol and is still very buggy
www.haus-terra.at/heizung/download/Schnittstelle/Schnittstelle_PC_Bootloader.pdf
"""

def __init__(self, address, port=40000, reset=False):
Expand Down Expand Up @@ -117,11 +120,13 @@ def get_count(self):
else:
raise ConnectionError('Could not retreive count')

def _get_data(self, max_count=math.inf):
def _get_data(self, max_count=None):
data = []
try:
count = self._start_read()
for _ in range(0, min(count, max_count)):
if isinstance(max_count, int):
count = min(max_count, count)
for _ in range(0, count):
data.append(self._fetch_data())
self._end_read(True)
return data
Expand Down
70 changes: 49 additions & 21 deletions pyblnet/blnet_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"""
Created on 26.09.2017
A module for connecting with, collecting data from and controlling the BLNet
via it's HTTP-interface
@author: Nielstron
"""

Expand All @@ -11,9 +14,10 @@
import html
import re
from builtins import int
import pickle


def test_blnet(ip, timeout=5):
def test_blnet(ip, timeout=5, id=0):
"""
Tests whether an BLNET answers under given ip
Attributes:
Expand All @@ -38,10 +42,12 @@ def test_blnet(ip, timeout=5):

class BLNETWeb(object):
"""
Interface to communicate with the UVR1611 over his web surface (BL-Net)
Interface for connecting with, collecting data from and controlling the BLNet
via it's HTTP-interface
Attributes:
ip The ip/domain of the UVR1611/BL-Net to connect to
ip the ip/domain of the BL-Net to connect to
password the password to log into the web interface provided
timeout timeout for http requests
"""
ip = ""
_def_password = "0128" # default password is 0128
Expand All @@ -61,8 +67,6 @@ def __init__(self, ip, password=_def_password, timeout=5):
raise ValueError(
'No BLNET found under given address: {}'.format(ip))
self.ip = ip
if password is None:
password = self._def_password
self.password = password
self._timeout = timeout

Expand All @@ -71,6 +75,8 @@ def logged_in(self):
Determines whether the object is still connected to the BLNET
/ Logged into the web interface
"""
if self.password is None:
return True
# check if a request to a restricted page returns a cookie if
# we have sent one (if our cookie is the current one this
# would be the case)
Expand Down Expand Up @@ -98,7 +104,8 @@ def log_in(self):
Return: Login successful
"""
if self.logged_in(): return True
if self.logged_in():
return True
payload = {
'blu': 1, # log in as experte
'blp': self.password,
Expand All @@ -113,10 +120,10 @@ def log_in(self):
timeout=self._timeout)
except requests.exceptions.RequestException:
return False
self.current_taid = r.headers.get('Set-Cookie')
# try two times to log in
i = 0
while i < 2:
self.current_taid = r.headers.get('Set-Cookie')
i += 1
if self.logged_in():
return True
Expand All @@ -128,6 +135,8 @@ def log_out(self):
Return: successful log out
"""
if self.password is None:
return True
try:
requests.get(
self.ip + "/main.html?blL=1",
Expand All @@ -142,7 +151,7 @@ def set_node(self, node):
Selects the node at which the UVR of interest lies
future requests will be sent at this particular UVR
Return: Successful node change
Return: Still logged in (indicating successful node change)
"""
# ensure to be logged in
if not self.log_in():
Expand All @@ -157,17 +166,17 @@ def set_node(self, node):
except requests.exceptions.RequestException:
return False
# return whether we we're still logged in => setting went well
return r.headers.get('Set-Cookie') is not None
return self.password is None or r.headers.get('Set-Cookie') is not None

def read_analog_values(self):
"""
Reads all analog values (temperatures, speeds) from the web interface
and returns list of quadruples of id, name, value, unit of measurement
"""
# ensure to be logged in
if not self.log_in(): return None
if not self.log_in():
return None

if not self.logged_in(): self.log_in()
try:
r = requests.get(
self.ip + "/580500.htm",
Expand Down Expand Up @@ -211,9 +220,9 @@ def read_digital_values(self):
(EIN/AUS)
"""
# ensure to be logged in
if not self.log_in(): return None
if not self.log_in():
return None

if not self.logged_in(): self.log_in()
try:
r = requests.get(
self.ip + "/580600.htm",
Expand Down Expand Up @@ -258,24 +267,43 @@ def set_digital_value(self, digital_id, value):
Attributes:
id id of the device whichs state should be changed
value value to change the state to
Return: successful set
Return: still logged in (indicating successful set)
"""

digital_id = int(digital_id)
# throw error for wrong id's
if digital_id < 1:
raise ValueError('Device id can\'t be smaller than 1')
raise ValueError('Device id can\'t be smaller than 1, was {}'.format(digital_id))
if digital_id > 15:
raise ValueError('Device id can\'t be larger than 15, was {}'.format(digital_id))
# ensure to be logged in
if not self.log_in():
return False

# transform input value to 'EIN' or 'AUS'
if value == 'AUTO':
value = '3' # 3 means auto
elif value != 'AUS' and value:
value = '2' # 2 means turn on
if isinstance(value, str):
if value.lower() == 'AUTO'.lower() or value == '3':
value = '3' # 3 means auto
elif value.lower() == 'EIN'.lower() or value == '2' or value.lower() == 'on'.lower():
value = '2' # 2 means turn on
elif value.lower() == 'AUS'.lower() or value == '1' or value.lower() == 'off'.lower():
value = '1' # 1 means turn off
else:
raise ValueError("Illegal input string {}".format(value))
elif isinstance(value, int) and not isinstance(value, bool):
if value is 3 or value is 2 or value is 1:
value = str(value)
elif value is 0:
value = '1'
else:
raise ValueError("Illegal input integer {}".format(value))
else:
value = '1' # 1 means turn off
# value can be interpreted as a true value
if value:
value = '2' # 2 means turn on
else:
value = '1' # 1 means turn off
assert(value in ['1', '2', '3'])

# convert id to hexvalue so that 10 etc become A...
hex_repr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F']
Expand All @@ -293,4 +321,4 @@ def set_digital_value(self, digital_id, value):
return False

# return whether we we're still logged in => setting went well
return r.headers.get('Set-Cookie') is not None
return self.password is None or r.headers.get('Set-Cookie') is not None
Empty file added pyblnet/tests/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions pyblnet/tests/test_structure/.error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=windows-1252">
<title>BL-Net Zugang verweigert</title></head>
<body bgcolor="#EEEEEE"><font size="5">Sie sind nicht befugt auf diese Seite zuzugreifen!!!<br>
<script type="text/javascript">
if(document.URL!=window.top.location.href){ window.top.document.getElementById('outd').style.display='none';window.top.document.getElementById('ind').style.display='block';window.top.document.getElementById('logu').style.display='none';}
</script>
</font></body>
</html>
Binary file added pyblnet/tests/test_structure/580500.htm
Binary file not shown.
Binary file added pyblnet/tests/test_structure/580600.htm
Binary file not shown.
Empty file.
Loading

0 comments on commit 19a60a3

Please sign in to comment.