Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added functionality to receive Events from Openhab and added methods to create and delete openhab items #22

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4c06593
initial commit for added functionality to receive ItemEvents from Ope…
galexey Dec 5, 2020
969f525
receiving events works.
galexey Dec 8, 2020
1ac15fb
ItemFactory to create new items
galexey Dec 10, 2020
ac20567
added testcases for creation of all itemtypes
galexey Dec 13, 2020
deae6a0
added testcases
galexey Dec 13, 2020
92864c1
cleaning code (PEPs)
galexey Dec 14, 2020
7336dd3
Merge remote-tracking branch 'upstream/master'
galexey Dec 14, 2020
50f7504
merged with upstream master
galexey Dec 14, 2020
47c1a9e
updated samples in readme
galexey Dec 14, 2020
10e6973
fixed bug in event-parsing and testcases
galexey Dec 14, 2020
d442ec6
major refactor to better reflect incoming states/statechange and comm…
galexey Dec 20, 2020
628a9af
changed Server Side Events client to aiohttp_sse_client as suggested …
galexey Dec 30, 2020
0b1e91e
added http_buffersize to avoid "line too long" Exception
galexey Dec 30, 2020
77da438
added audiosinks
galexey Jan 3, 2021
70154e7
fix for _is_my_own_echo and Eventtypes
galexey Jan 20, 2021
7ac50f1
introduced a historylist of sent itemvalues for better echo checking
galexey Jan 28, 2021
64f1799
changed behaviour for not yet implemented OH types
galexey Feb 1, 2021
7ddd650
introduced slotted sending of changes
galexey Feb 2, 2021
3f1ff0c
changed item Factory and item creation methods for slotted sending of…
galexey Feb 3, 2021
55477f3
openHAB3
galexey Feb 13, 2021
2072ede
added widgets
galexey Mar 9, 2021
9f3a21f
added widgets
galexey Mar 9, 2021
c3a331b
decoupled receiving of events from processing of events through an ad…
galexey Mar 10, 2021
614b347
changes widget behaviour
galexey Mar 10, 2021
7ab6e9c
logging Q len
galexey Mar 14, 2021
3b7cfe7
token store
galexey Mar 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
added testcases
finished documentation
  • Loading branch information
galexey committed Dec 13, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit deae6a0d1b6001351a92959d75d74397770419b1
35 changes: 33 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
@@ -24,7 +24,9 @@ A number of features are implemented but not all, this is work in progress.
currently you can
- retrieve current state of items
- send updates and commands to items
- receive commands, updates and changes from openhab
- receive commands, updates and changes triggered by openhab
- create new items and groups
- delete items and groups


Requirements
@@ -105,13 +107,42 @@ Example usage of the library:
if event.source == openhab.events.EventSourceOpenhab:
log.info("this change came from openhab")
# install listener for evetns
testroom1_LampOnOff.addEventListener(types=openhab.events.ItemCommandEventType, listener=onLight_switchCommand, onlyIfEventsourceIsOpenhab=False)
testroom1_LampOnOff.addEventListener(listeningTypes=openhab.events.ItemCommandEventType, listener=onLight_switchCommand, onlyIfEventsourceIsOpenhab=False)
# switch you will receive update also for your changes in the code. (see
testroom1_LampOnOff.off()

#Events stop to be delivered
testroom1_LampOnOff=None


#create or delete items:
# first instantiate a Factory:
itemFactory = openhab.items.ItemFactory(openhab)
#create the item
testDimmer = itemFactory.createOrUpdateItem(name="the_testDimmer", type=openhab.items.DimmerItem)
#use item
testDimmer.state=95



# you can set change many item attributes:
nameprefix="testcase_1_"
itemname = "{}CreateItemTest".format(nameprefix)
itemQuantityType = "Angle"
itemtype = "Number"
itemtype = openhab.items.NumberItem

labeltext = "this is a test azimuth:"
itemlabel = "[{labeltext}%.1f °]".format(labeltext=labeltext)
itemcategory = "{}TestCategory".format(nameprefix)
itemtags: List[str] = ["{}testtag1".format(nameprefix), "{}testtag2".format(nameprefix)]
itemgroupNames: List[str] = ["{}testgroup1".format(nameprefix), "{}testgroup2".format(nameprefix)]
grouptype = "{}testgrouptype".format(nameprefix)
functionname = "{}testfunctionname".format(nameprefix)
functionparams: List[str] = ["{}testfunctionnameParam1".format(nameprefix), "{}testfunctionnameParam2".format(nameprefix), "{}testfunctionnameParam3".format(nameprefix)]

testazimuth=itemFactory.createOrUpdateItem(name=itemname, type=itemtype, quantityType=itemQuantityType, label=itemlabel, category=itemcategory, tags=itemtags, groupNames=itemgroupNames, grouptype=grouptype, functionname=functionname, functionparams=functionparams)

Note on NULL and UNDEF
----------------------

27 changes: 22 additions & 5 deletions openhab/client.py
Original file line number Diff line number Diff line change
@@ -61,8 +61,8 @@ def __init__(self, base_url: str,
http_auth (AuthBase, optional): An alternative to username/password pair, is to
specify a custom http authentication object of type :class:`requests.auth.AuthBase`.
timeout (float, optional): An optional timeout for REST transactions
autoUpdate (bool, optional): Register for Openhab Item Events to actively get informed about changes.
maxEchoToOpenHAB_ms (int, optional): interpret Events from openHAB with same statevalue as we have coming within maxEchoToOpenhabMS millisends since our update/command as echos of our update//command
autoUpdate (bool, optional): True: receive Openhab Item Events to actively get informed about changes.
maxEchoToOpenHAB_ms (int, optional): interpret Events from openHAB which hold a state-value equal to items current state-value which are coming in within maxEchoToOpenhabMS millisends since our update/command as echos of our own update//command
Returns:
OpenHAB: openHAB class instance.
"""
@@ -172,21 +172,33 @@ def _parseEvent(self, eventData:typing.Dict)->None:
log.debug("received command for '{itemname}'[{datatype}]:{newValue}".format(itemname=itemname, datatype=remoteDatatype, newValueRaw=newValue))

self._parseItem(event)
self.informEventListeners(event)
self._informEventListeners(event)
else:
log.info("received unknown Event-type in Openhab Event stream: {}".format(eventData))

def informEventListeners(self,event:openhab.events.ItemEvent):
def _informEventListeners(self, event:openhab.events.ItemEvent):
"""internal method to send itemevents to listeners.
Args:
event:openhab.events.ItemEvent to be sent to listeners
"""
for aListener in self.eventListeners:
try:
aListener(event)
except Exception as e:
self.logger.error("error executing Eventlistener for event:{}.".format(event.itemname),e)

def addEventListener(self, listener:typing.Callable[[openhab.events.ItemEvent],None]):
"""method to register a callback function to get informed about all Item-Events received from openhab.
Args:
listener:typing.Callable[[openhab.events.ItemEvent] a method with one parameter of type openhab.events.ItemEvent which will be called for every event
"""
self.eventListeners.append(listener)

def removeEventListener(self, listener:typing.Optional[typing.Callable[[openhab.events.ItemEvent],None]]=None):
"""method to unregister a callback function to stop getting informed about all Item-Events received from openhab.
Args:
listener:typing.Callable[[openhab.events.ItemEvent] the method to be removed.
"""
if listener is None:
self.eventListeners.clear()
elif listener in self.eventListeners:
@@ -198,7 +210,7 @@ def removeEventListener(self, listener:typing.Optional[typing.Callable[[openhab.


def _sseDaemonThread(self):
"""internal method to receice events from openhab.
"""internal method to receive events from openhab.
This method blocks and therefore should be started as separate thread.
"""
self.logger.info("starting Openhab - Event Deamon")
@@ -224,10 +236,15 @@ def _sseDaemonThread(self):


def get_registered_items(self)->weakref.WeakValueDictionary:
"""get a Dict of weak references to registered items.
Args:
an Item object
"""
return self.registered_items

def register_item(self, item: openhab.items.Item)->None:
"""method to register an instantiated item. registered items can receive commands an updated from openhab.
Usually you don´t need to register as Items register themself.
Args:
an Item object
"""
4 changes: 4 additions & 0 deletions openhab/events.py
Original file line number Diff line number Diff line change
@@ -37,12 +37,14 @@

@dataclass
class ItemEvent(object):
"""The base class for all ItemEvents"""
type = ItemEventType
itemname: str
source: EventSource

@dataclass
class ItemStateEvent(ItemEvent):
"""a Event representing a state event on a Item"""
type = ItemStateEventType
remoteDatatype: str
newValue: typing.Any
@@ -53,6 +55,7 @@ class ItemStateEvent(ItemEvent):

@dataclass
class ItemCommandEvent(ItemEvent):
"""a Event representing a command event on a Item"""
type = ItemCommandEventType
remoteDatatype: str
newValue: typing.Any
@@ -62,6 +65,7 @@ class ItemCommandEvent(ItemEvent):

@dataclass
class ItemStateChangedEvent(ItemStateEvent):
"""a Event representing a state change event on a Item"""
type = ItemStateChangedEventType
oldRemoteDatatype: str
oldValueRaw: str
64 changes: 51 additions & 13 deletions openhab/items.py
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@
__license__ = 'AGPLv3+'

class ItemFactory:
"""A factory to get an Item from Openhab or create new items in openHAB"""
"""A factory to get an Item from Openhab, create new or delete existing items in openHAB"""

def __init__(self,openhabClient:openhab.client.OpenHAB):
"""Constructor.
@@ -283,13 +283,8 @@ def state(self,fetchFromOpenhab=False) -> typing.Any:

@state.setter
def state(self, value: typing.Any):
oldstate= self._state
self.update(value)
# if oldstate==self._state:
# event=openhab.events.ItemStateEvent( name=self.name,source=openhab.events.EventSourceInternal, remoteDatatype=self.type_,newValueRaw=self._state, asUpdate=False)
# else:
# event = openhab.events.ItemStateChangedEvent(name=self.name,source=openhab.events.EventSourceInternal, remoteDatatype=self.type_, newValueRaw=self._state, oldRemoteDatatype=self.type_,oldValueRaw=oldstate, asUpdate=False)
# self._processEvent(event)


@property
def members(self):
@@ -341,6 +336,7 @@ def _rest_format(self, value: str) -> typing.Union[str, bytes]:
return _value

def _isMyOwnChange(self, event):
"""find out if the incoming event is actually just a echo of my previous command or change"""
now = datetime.now()
self.logger.debug("_isMyOwnChange:event.source:{}, event.type{}, self._state:{}, event.newValue:{},self.lastCommandSent:{}, self.lastUpdateSent:{} , now:{}".format(event.source,event.type,self._state,event.newValue ,self.lastCommandSent,self.lastUpdateSent,now))
if event.source == openhab.events.EventSourceOpenhab:
@@ -367,9 +363,11 @@ def _processExternalEvent(self, event:openhab.events.ItemEvent):
event.newValue=newValue
event.unitOfMeasure=uom
if event.type==openhab.events.ItemStateChangedEventType:
oldValue,ouom=self._parse_rest(event.oldValueRaw)
event.oldValue=oldValue
event.oldUnitOfMeasure=ouom
try:
oldValue,ouom=self._parse_rest(event.oldValueRaw)
except:
event.oldValue=None
event.oldUnitOfMeasure=None
isMyOwnChange=self._isMyOwnChange(event)
self.logger.info("external event:{}".format(event))
if not isMyOwnChange:
@@ -397,7 +395,16 @@ def _processInternalEvent(self,event:openhab.events.ItemEvent):


class EventListener(object):
"""EventListener Objects hold data about a registered event listener"""
def __init__(self,listeningTypes:typing.Set[openhab.events.EventType],listener:typing.Callable[[openhab.events.ItemEvent],None],onlyIfEventsourceIsOpenhab,alsoGetMyEchosFromOpenHAB):
"""Constructor of an EventListener Object
Args:
listeningTypes (openhab.events.EventType or set of openhab.events.EventType): the eventTypes the listener is interested in.
onlyIfEventsourceIsOpenhab (bool): the listener only wants events that are coming from openhab.
alsoGetMyEchosFromOpenHAB (bool): the listener also wants to receive events coming from openhab that originally were triggered by commands or changes by our item itself.


"""
allTypes = {openhab.events.ItemStateEvent.type, openhab.events.ItemCommandEvent.type, openhab.events.ItemStateChangedEvent.type}
if listeningTypes is None:
self.listeningTypes = allTypes
@@ -413,6 +420,11 @@ def __init__(self,listeningTypes:typing.Set[openhab.events.EventType],listener:t
self.alsoGetMyEchosFromOpenHAB=alsoGetMyEchosFromOpenHAB

def addTypes(self,listeningTypes:typing.Set[openhab.events.EventType]):
"""add aditional listening types
Args:
listeningTypes (openhab.events.EventType or set of openhab.events.EventType): the additional eventTypes the listener is interested in.

"""
if listeningTypes is None: return
elif not hasattr(listeningTypes, '__iter__'):
self.listeningTypes.add(listeningTypes)
@@ -422,6 +434,11 @@ def addTypes(self,listeningTypes:typing.Set[openhab.events.EventType]):
self.listeningTypes.update(listeningTypes)

def removeTypes(self,listeningTypes:typing.Set[openhab.events.EventType]):
"""remove listening types
Args:
listeningTypes (openhab.events.EventType or set of openhab.events.EventType): the eventTypes the listener is not interested in anymore

"""
if listeningTypes is None:
self.listeningTypes.clear()
elif not hasattr(listeningTypes, '__iter__'):
@@ -436,21 +453,40 @@ def removeTypes(self,listeningTypes:typing.Set[openhab.events.EventType]):



def addEventListener(self,types:typing.List[openhab.events.EventType],listener:typing.Callable[[openhab.events.ItemEvent],None],onlyIfEventsourceIsOpenhab=True,alsoGetMyEchosFromOpenHAB=False):
def addEventListener(self,listeningTypes:typing.Set[openhab.events.EventType],listener:typing.Callable[[openhab.items.Item,openhab.events.ItemEvent],None],onlyIfEventsourceIsOpenhab=True,alsoGetMyEchosFromOpenHAB=False):
"""add a Listener interested in changes of items happening in openhab
Args:
Args:
listeningTypes (openhab.events.EventType or set of openhab.events.EventType): the eventTypes the listener is interested in.
listener (Callable[[openhab.items.Item,openhab.events.ItemEvent],None]: a method with 2 parameters:
item (openhab.items.Item): the item that received a command, change or update
event (openhab.events.ItemEvent): the item Event holding the actual change
onlyIfEventsourceIsOpenhab (bool): the listener only wants events that are coming from openhab.
alsoGetMyEchosFromOpenHAB (bool): the listener also wants to receive events coming from openhab that originally were triggered by commands or changes by our item itself.


"""

if listener in self.eventListeners:
eventListener= self.eventListeners[listener]
eventListener.addTypes(types)
eventListener.addTypes(listeningTypes)
eventListener.onlyIfEventsourceIsOpenhab=onlyIfEventsourceIsOpenhab
else:
eventListener=Item.EventListener(listeningTypes=types,listener=listener,onlyIfEventsourceIsOpenhab=onlyIfEventsourceIsOpenhab,alsoGetMyEchosFromOpenHAB=alsoGetMyEchosFromOpenHAB)
eventListener=Item.EventListener(listeningTypes=listeningTypes,listener=listener,onlyIfEventsourceIsOpenhab=onlyIfEventsourceIsOpenhab,alsoGetMyEchosFromOpenHAB=alsoGetMyEchosFromOpenHAB)
self.eventListeners[listener]=eventListener

def removeAllEventListener(self):
self.eventListeners=[]

def removeEventListener(self,types:typing.List[openhab.events.EventType],listener:typing.Callable[[openhab.events.ItemEvent],None]):
"""removes a previously registered Listener interested in changes of items happening in openhab
Args:
Args:
listeningTypes (openhab.events.EventType or set of openhab.events.EventType): the eventTypes the listener is interested in.
listener: the previously registered listener method.


"""
if listener in self.eventListeners:
eventListener = self.eventListeners[listener]
eventListener.removeTypes(types)
@@ -692,6 +728,8 @@ def _parse_rest(self, value: str) -> float:

#logging.getLogger().debug("original value:{}, myvalue:{}, my UoM:{}".format(m,value,unitOfMeasure))
return (float(value),unitOfMeasure)
else:
return value
except Exception as e:
self.logger.error("error in parsing new value '{}' for '{}'".format(value,self.name),e)

Loading