diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5abb1a7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+/dist
+/build
+/ihcsdk.egg-info
+/build.bat
+/upload.bat
+/nppBackup
+/.venv
+__pycache__/
+.parameters
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..d92004e
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,15 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python: Example file",
+ "type": "python",
+ "request": "launch",
+ "program": "example.py",
+ "console": "integratedTerminal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..aa4e935
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "python.pythonPath": "g:\\Dev\\python\\ihcsdk\\.venv\\Scripts\\python.exe"
+}
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..9d9fe57
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include readme.md
+include license.txt
+include ihcsdk/certs/*.crt
\ No newline at end of file
diff --git a/build.bat b/build.bat
index c536db3..98e3d49 100644
--- a/build.bat
+++ b/build.bat
@@ -1,2 +1,4 @@
rm -R dist
-python setup.py sdist
\ No newline at end of file
+rm -R build
+rm -R ihcsdk.egg-info
+python setup.py sdist bdist_wheel
\ No newline at end of file
diff --git a/Examples/ihctest.py b/example.py
similarity index 57%
rename from Examples/ihctest.py
rename to example.py
index 182df2e..082fc47 100644
--- a/Examples/ihctest.py
+++ b/example.py
@@ -1,43 +1,45 @@
"""
Test example showing how to use the ihcsdk to connect to the ihc controller
+To run the example create a file '.parameters' in this folder and add:
+ihcurl username password resourceid
+The resourceid is an ihc resource id of any boolean resource in you controller.
+The resource will be toggled when the test starts, and after this you can set it
+using '1' and '2'. 'q' to quit
"""
-from sys import argv
from datetime import datetime
-from ihcsdk.ihccontroller import IHCController
-# from ihcsdk.ihccurlconnection import IHCCurlConnection
-t = datetime.now()
+from ihcsdk.ihccontroller import IHCController
-def on_ihc_change(ihcid, value):
- """Callback when ihc resource changes"""
- print("Resource change " + str(ihcid) + "->" + str(value) +
- " time: " + gettime())
+def main():
+ """Do the test"""
+ starttime = datetime.now()
-def gettime():
- dif = datetime.now() - t
- return str(dif)
+ def on_ihc_change(ihcid, value):
+ """Callback when ihc resource changes"""
+ print("Resource change " + str(ihcid) + "->" + str(value) +
+ " time: " + gettime())
+ def gettime():
+ dif = datetime.now() - starttime
+ return str(dif)
-def main():
- """Do the test"""
- global t
- if len(argv) != 5:
- print("Syntax: ihctest ihcurl username password resourceid")
+ cmdline = open(".parameters", "rt").read()
+ args = cmdline.split(' ')
+ if len(args) != 4:
+ print("The '.parameters' file should contain: ihcurl username password resourceid")
exit()
- url = argv[1]
- resid = int(argv[4])
- ihc = IHCController(url, argv[2], argv[3])
-# Un-comment the line below to use pycurl connection
-# ihc.client.connection = IHCCurlConnection( url)
+ url = args[0]
+ resid = int(args[3])
+ ihc = IHCController(url, args[1], args[2])
if not ihc.authenticate():
print("Authenticate failed")
exit()
print("Authenticate succeeded\r\n")
- # read project
+ # read the ihc project
project = ihc.get_project()
if project is False:
print("Failed to read project")
@@ -63,11 +65,11 @@ def main():
while True:
i = input()
if i == "1":
- t = datetime.now()
+ starttime = datetime.now()
ihc.set_runtime_value_bool(resid, False)
continue
if i == "2":
- t = datetime.now()
+ starttime = datetime.now()
ihc.set_runtime_value_bool(resid, True)
continue
if i == "q":
diff --git a/ihc.crt b/ihcsdk/certs/ihc.crt
similarity index 100%
rename from ihc.crt
rename to ihcsdk/certs/ihc.crt
diff --git a/ihcsdk/certs/ihc3.crt b/ihcsdk/certs/ihc3.crt
new file mode 100644
index 0000000..985333a
--- /dev/null
+++ b/ihcsdk/certs/ihc3.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDyzCCArOgAwIBAgIEapjIFzANBgkqhkiG9w0BAQsFADCBlTELMAkGA1UEBhMC
+RlIxDzANBgNVBAgTBkZyYW5jZTEeMBwGA1UEBxMVOTI1MDAgUnVlaWwgTWFsbWFp
+c29uMRswGQYDVQQKExJTY2huZWlkZXIgRWxlY3RyaWMxGzAZBgNVBAsTEkdsb2Jh
+bCBFbmdpbmVlcmluZzEbMBkGA1UEAxMSU2NobmVpZGVyIEVsZWN0cmljMB4XDTE2
+MDMwMjExNTExMFoXDTI2MDEwOTExNTExMFowgZUxCzAJBgNVBAYTAkZSMQ8wDQYD
+VQQIEwZGcmFuY2UxHjAcBgNVBAcTFTkyNTAwIFJ1ZWlsIE1hbG1haXNvbjEbMBkG
+A1UEChMSU2NobmVpZGVyIEVsZWN0cmljMRswGQYDVQQLExJHbG9iYWwgRW5naW5l
+ZXJpbmcxGzAZBgNVBAMTElNjaG5laWRlciBFbGVjdHJpYzCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKBnGTVIWpV4yld9hFpkRFn0rT/jFMT9JUbMRvBx
+La0nZuOPAQh4xCpM6p+upjF91x+SRM+pGt1oMxptxmhK3fXeghinC1qF3EB3e7wE
+QUc/35ckVbDR7K/rlqRW/qSjB1mNSX8YWzxCTEbBhYJiQwSxVOjAZ1zyvNUq8Msq
+3yi19aER1Rn3Vpdma216ogXWHJe2hVgtnoM+l85vE8dd72wcVdya5qrMOI67gvnh
+DjoSbVeP5kUFgt9gU0cNC6HsSw0ayvxYlTb3hlzdO211zCJc1yADFzIb5eNa8LPp
+9NCTrG5qAhda+0GY2pJk+XqrpH6VryKLZIvV7CCa2VASlO0CAwEAAaMhMB8wHQYD
+VR0OBBYEFI2fPYY01dOInhlJEhJl3mMavE27MA0GCSqGSIb3DQEBCwUAA4IBAQB3
+BdnEsqTv/f44b2cKsztQ9+3tq4RfO0FRtcpXGr5uNIRVcTLqaT1TrqEMOmB9v+9j
+99hVrm4T0gkchW51jYKNuwTP+4zYk+mx6BVuSWysmBg6meB3J8I7x5q/oWjylHOT
+LznoHPhB4kwA/TCh9DfOdcklhYMVesWm3XllYhY8Vy5JaT2g4AWE2PHZP6e/fPHc
+Vm4867n8P58ko+2uHb+noFBkNC/3BsSiGkMQZ+GSgkVxQAzvdj8vcndJEe/uvCh+
+/OI9DdwFVF00zdP1CTaNI4YvDEfCHjRDyVNY1ygJ/cvNeK/Y1ohYKgGfrsunCZsJ
+Jit2KlT6WgvhepiYCrWP
+-----END CERTIFICATE-----
diff --git a/ihcsdk/ihcclient.py b/ihcsdk/ihcclient.py
index 3b9bf50..2ce1806 100644
--- a/ihcsdk/ihcclient.py
+++ b/ihcsdk/ihcclient.py
@@ -5,6 +5,7 @@
import zlib
import base64
from ihcsdk.ihcconnection import IHCConnection
+from ihcsdk.ihcsslconnection import IHCSSLConnection
IHCSTATE_READY = "text.ctrl.state.ready"
@@ -22,7 +23,10 @@ def __init__(self, url: str):
self.url = url
self.username = ""
self.password = ""
- self.connection = IHCConnection(url)
+ if url.startswith( "https://"):
+ self.connection = IHCSSLConnection(url)
+ else:
+ self.connection = IHCConnection(url)
def authenticate(self, username: str, password: str) -> bool:
"""Do an Authentricate request and save the cookie returned to be used
diff --git a/ihcsdk/ihcconnection.py b/ihcsdk/ihcconnection.py
index 604f007..7016b2d 100644
--- a/ihcsdk/ihcconnection.py
+++ b/ihcsdk/ihcconnection.py
@@ -6,7 +6,7 @@
class IHCConnection(object):
- """description of class"""
+ """Implements a http connection to the controller"""
soapenvelope = """
{body}"""
def __init__(self, url: str):
- """Initialize the IIHCSoapClient with a url for the controller"""
+ """Initialize the IHCConnection with a url for the controller"""
self.url = url
self.cookies = ""
self.verify = False
@@ -23,6 +23,9 @@ def __init__(self, url: str):
self.last_response = None
self.session = requests.Session()
+ def cert_verify(self):
+ return None
+
def soap_action(self, service, action, payloadbody):
"""Do a soap request."""
payload = self.soapenvelope.format(body=payloadbody).encode('utf-8')
@@ -36,6 +39,7 @@ def soap_action(self, service, action, payloadbody):
response = self.session.post(url=self.url + service,
headers=headers,
data=payload,
+ verify=self.cert_verify(),
cookies=self.cookies)
except requests.exceptions.RequestException as exp:
self.last_exception = exp
diff --git a/ihcsdk/ihccontroller.py b/ihcsdk/ihccontroller.py
index e7ddcb3..05a9847 100644
--- a/ihcsdk/ihccontroller.py
+++ b/ihcsdk/ihccontroller.py
@@ -34,8 +34,7 @@ def authenticate(self) -> bool:
if not self.client.authenticate(self._username, self._password):
return False
if self._ihcevents:
- self.client.enable_runtime_notifications(
- self._ihcevents.keys())
+ self.client.enable_runtime_notifications(self._ihcevents.keys())
return True
def disconnect(self):
@@ -112,8 +111,7 @@ def _notify_fn(self):
with IHCController._mutex:
# Are there are any new ids to be added?
if self._newnotifyids:
- self.client.enable_runtime_notifications(
- self._newnotifyids)
+ self.client.enable_runtime_notifications(self._newnotifyids)
self._newnotifyids = []
changes = self.client.wait_for_resource_value_changes()
diff --git a/ihcsdk/ihccurlconnection.py b/ihcsdk/ihccurlconnection.py
deleted file mode 100644
index 5c0c705..0000000
--- a/ihcsdk/ihccurlconnection.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""Implements soap request using the pycurl module"""
-# pylint: disable=too-few-public-methods
-import xml.etree.ElementTree
-from io import BytesIO
-from re import match
-import pycurl
-from ihcsdk.ihcconnection import IHCConnection
-
-
-class IHCCurlConnection(IHCConnection):
- """description of class"""
-
- cookies = ""
-
- @staticmethod
- def _write_header(header: str):
- cookiekmatch = match("^set-cookie: (.*)$", header.decode('utf-8'))
- if cookiekmatch:
- IHCCurlConnection.cookies = cookiekmatch.group(1)
-
- def soap_action(self, service, action, payloadbody):
- """Do a soap request """
- payload = self.soapenvelope.format(body=payloadbody).encode('utf-8')
- headers = ['SOAPAction: ' + action,
- 'Content-Type: application/soap+xml; charset=UTF-8',
- 'Content-Length: ' + str(len(payload))]
-
- try:
- curl = pycurl.Curl()
- curl.setopt(pycurl.SSL_CIPHER_LIST, "AES256-SHA")
- curl.setopt(pycurl.SSLVERSION, pycurl.SSLVERSION_TLSv1_0)
- # self.curl.setopt(pycurl.CAINFO,'ihc.crt')
- curl.setopt(pycurl.SSL_VERIFYPEER, 0)
- curl.setopt(pycurl.SSL_VERIFYHOST, 0)
- curl.setopt(pycurl.POST, 1)
- curl.setopt(pycurl.HEADERFUNCTION, IHCCurlConnection._write_header)
-
- curl.setopt(pycurl.HTTPHEADER, headers)
- inbuffer = BytesIO(payload)
- curl.setopt(pycurl.READDATA, inbuffer)
- buffer = BytesIO()
- curl.setopt(pycurl.WRITEDATA, buffer)
- curl.setopt(pycurl.URL, self.url + service)
- curl.setopt(pycurl.COOKIE, IHCCurlConnection.cookies)
- # curl.setopt(pycurl.VERBOSE,1)
- curl.perform()
- body = buffer.getvalue().decode('utf-8')
- code = curl.getinfo(pycurl.HTTP_CODE)
- curl.close()
- except Exception as exp:
- return False
- if code != 200:
- return False
- try:
- xdoc = xml.etree.ElementTree.fromstring(body)
- if xdoc is None:
- return False
- except xml.etree.ElementTree.ParseError:
- return False
- return xdoc
diff --git a/ihcsdk/ihcsslconnection.py b/ihcsdk/ihcsslconnection.py
index 2a7e3dd..4f9d5e3 100644
--- a/ihcsdk/ihcsslconnection.py
+++ b/ihcsdk/ihcsslconnection.py
@@ -1,65 +1,46 @@
"""Implements soap reqeust using the "requests" module"""
# pylint: disable=too-few-public-methods
-import ssl
-import xml.etree.ElementTree
+import os
import requests
-from requests.packages.urllib3.util.ssl_ import create_urllib3_context
+
+from cryptography.x509 import load_pem_x509_certificate
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes
from requests.adapters import HTTPAdapter
+
from ihcsdk.ihcconnection import IHCConnection
class IHCSSLConnection(IHCConnection):
- """description of class"""
+ """Implements a https connection to the controller"""
def __init__(self, url: str):
- """Initialize the IIHCSoapClient with a url for the controller"""
+ """Initialize the IHCSSLConnection with a url for the controller"""
super(IHCSSLConnection, self).__init__(url)
- self.session = requests.Session()
- self.session.mount('https://', TLSv1Adapter())
+ self.cert_file = os.path.dirname( __file__) + "/certs/ihc3.crt"
+ self.session.mount('https://', CertAdapter( self.get_fingerprint_from_cert()))
- def soap_action(self, service, action, payloadbody):
- """Do a soap request"""
- payload = self.soapenvelope.format(body=payloadbody).encode('utf-8')
- headers = {"Host": self.url,
- "Content-Type": "text/xml; charset=UTF-8",
- "Cache-Control": "no-cache",
- "Content-Length": str(len(payload)),
- "SOAPAction": action}
- try:
- response = self.session.post(
- url=self.url + service, headers=headers, data=payload)
- except Exception as exp:
- return False
- if response.status_code != 200:
- return False
- try:
- xdoc = xml.etree.ElementTree.fromstring(response.text)
- if xdoc is None:
- return False
- except xml.etree.ElementTree.ParseError:
- return False
- return xdoc
+ def get_fingerprint_from_cert( self):
+ """Get the fingerprint from the certificate"""
+ pem = open( self.cert_file, "rb").read()
+ cert = load_pem_x509_certificate(pem, default_backend())
+ f = cert.fingerprint( hashes.SHA1())
+ return ''.join('{:02x}'.format(x) for x in f)
+ def cert_verify(self):
+ return self.cert_file
-class TLSv1Adapter(HTTPAdapter):
- """Force TLSv1"""
- CIPHERS = ('AES256-SHA')
+class CertAdapter(requests.adapters.HTTPAdapter):
+ """A adapter for a specific certificate"""
- def init_poolmanager(self, connections, maxsize,
- block=requests.adapters.DEFAULT_POOLBLOCK,
- **pool_kwargs):
- """Initialize poolmanager with cipher and Tlsv1"""
- context = create_urllib3_context(ciphers=self.CIPHERS,
- ssl_version=ssl.PROTOCOL_TLSv1)
- pool_kwargs['ssl_context'] = context
- return super(TLSv1Adapter, self).init_poolmanager(connections, maxsize,
- block, **pool_kwargs)
+ def __init__(self, fingerprint, **kwargs):
+ """Constructor. Store the fingerprint for use when creating the poolmanager."""
+ self.fingerprint = fingerprint
+ super(CertAdapter, self).__init__(**kwargs)
- def proxy_manager_for(self, proxy, **proxy_kwargs):
- """Ensure cipher and Tlsv1"""
- context = create_urllib3_context(ciphers=self.CIPHERS,
- ssl_version=ssl.PROTOCOL_TLSv1)
- proxy_kwargs['ssl_context'] = context
- return super(TLSv1Adapter, self).proxy_manager_for(proxy,
- **proxy_kwargs)
+ def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
+ """Create a custom poolmanager"""
+ pool_kwargs['assert_fingerprint'] = self.fingerprint
+ return super(CertAdapter, self).init_poolmanager(connections, maxsize,
+ block, **pool_kwargs)
diff --git a/ihctest.pyproj b/ihctest.pyproj
deleted file mode 100644
index f907461..0000000
--- a/ihctest.pyproj
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
- Debug
- 2.0
- {9bf98da8-003a-40b7-8437-734f6982b724}
-
- Examples\ihctest.py
-
-
- .
- .
- {888888a0-9f3d-457c-b088-3a5042f75d52}
- Standard Python launcher
- Global|PythonCore|3.7
- http://192.168.1.3 admin flipperi 2362206
- False
- False
-
-
-
-
- 10.0
-
-
-
-
- Code
-
-
-
-
-
-
-
- Code
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ihctest.sln b/ihctest.sln
deleted file mode 100644
index 9857b77..0000000
--- a/ihctest.sln
+++ /dev/null
@@ -1,23 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26730.10
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "ihctest", "ihctest.pyproj", "{9BF98DA8-003A-40B7-8437-734F6982B724}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {9BF98DA8-003A-40B7-8437-734F6982B724}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9BF98DA8-003A-40B7-8437-734F6982B724}.Release|Any CPU.ActiveCfg = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {6B3D9D0F-995A-4D3F-B2A4-206CBF944418}
- EndGlobalSection
-EndGlobal
diff --git a/readme.md b/readme.md
index 3b7c0e1..cc5f974 100644
--- a/readme.md
+++ b/readme.md
@@ -14,7 +14,7 @@ The library implements:
## Examples
-See the examples folder.
+See the example.py file.
For more infomation about this library look at
@@ -22,6 +22,7 @@ http://www.dingus.dk/ihc-soap-client-python/
# Note
+Version 2.7 Add https support for version 3 of the controller. Switched from VS to vscode
Version 2.x.x has changed the naming to make pylint happy, so if you are upgrading from
version 1.x.x you will have to make changes to your code.
@@ -30,16 +31,16 @@ to have the libray included in PyPi. The library files are now in the ihcsdk sub
# License
-IHCSoapClient is free software: you can redistribute it and/or modify
+ihcsdk is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
-IHCSoapClient is distributed in the hope that it will be useful,
+ihcsdk is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
-along with ArduinoIHC. If not, see .
+along with ihcsdk. If not, see .
diff --git a/settings.json b/settings.json
new file mode 100644
index 0000000..fdbd82a
--- /dev/null
+++ b/settings.json
@@ -0,0 +1,3 @@
+{
+ "python.linting.flake8Enabled": true
+}
diff --git a/setup.py b/setup.py
index 31123ca..ae27fdd 100644
--- a/setup.py
+++ b/setup.py
@@ -5,12 +5,17 @@
setup(
name='ihcsdk',
- version='2.6.0',
+ version='2.7.0',
description='IHC Python SDK',
long_description=("SDK for connection to the LK IHC Controller. "
"Made for interfacing to home assistant"),
author='Jens Nielsen',
url='https://github.com/dingusdk/PythonIhcSdk',
packages=['ihcsdk'],
+ install_requires=[
+ 'requests',
+ 'cryptography',
+ ],
license='GPL-3.0',
+ include_package_data=True,
)