Changes between Version 1 and Version 2 of Tutorial


Ignore:
Timestamp:
Aug 14, 2013, 8:15:29 PM (11 years ago)
Author:
Frédéric
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Tutorial

    v1 v2  
    11[[PageOutline(2-5, Table of Contents, floated)]]
    22
     3'''Draft'''
     4
    35= Tutorial =
     6
     7== Architecture ==
     8
     9== Vocabulary ==
     10
     11TODO
     12
     13== Functional Blocks ==
     14
     15This is the central feature of '''pKNyX''', allowing user to create virtual devices which mimics real KNX devices. The Device itself is implemented as a process.
     16
     17Here is a very simple example which implements a timer. This timer can monitor the state of a light, and switch it off automatically after a delay. The code is taken from the {{{examples/3_timer}}} directory of ''pKNyX''' sources.
     18
     19{{{
     20#!python
     21from pknyx.api import Logger
     22from pknyx.api import FunctionalBlock, Stack, ETS
     23from pknyx.api import Scheduler, Notifier
     24
     25NAME = "timer"
     26IND_ADDR = "1.2.3"
     27
     28# ETS group address map
     29GAD_MAP = {"1": dict(name="lights", desc="Lights"),
     30           "1/1": dict(name="cmds", desc="Commands"),
     31           "1/1/1": dict(name="light_test_cmd", desc="Light 'test' (cmd)"),
     32           "1/2": dict(name="states", desc="States"),
     33           "1/2/1": dict(name="light_test_state", desc="Ligh 'test' (state)"),
     34           "1/3": dict(name="delay", desc="Delays"),
     35           "1/3/1": dict(name="light_test_delay", desc="Light 'test' (delay)"),
     36          }
     37
     38
     39logger = Logger("%s-%s" % (NAME, IND_ADDR))
     40
     41stack = Stack(individualAddress=IND_ADDR)
     42ets = ETS(stack=stack, gadMap=GAD_MAP))
     43
     44schedule = Scheduler()
     45notify = Notifier()
     46
     47
     48class TimerFB(FunctionalBlock):
     49    """ Timer functional block
     50    """
     51
     52    # Datapoints definition
     53    DP_01 = dict(name="cmd", access="output", dptId="1.001", default="Off")
     54    DP_02 = dict(name="state", access="input", dptId="1.001", default="Off")
     55    DP_03 = dict(name="delay", access="input", dptId="7.005", default=10)
     56
     57    GO_01 = dict(dp="cmd", flags="CWT", priority="low")
     58    GO_02 = dict(dp="state", flags="CWUI", priority="low")
     59    GO_03 = dict(dp="delay", flags="CWU", priority="low")
     60
     61    DESC = "Timer"
     62
     63    def _init(self):
     64        """ Additionnal init of the timer
     65        """
     66        self._timer = 0
     67
     68    @schedule.every(seconds=1)
     69    def updateTimer(self):
     70        """ Method called every second.
     71        """
     72        #logger.trace("TimerFB.updateTimer()")
     73
     74        if self._timer:
     75            self._timer -= 1
     76            if not self._timer:
     77                logger.info("%s: timer expired; switch off" % self._name)
     78                self.dp["cmd"].value = "Off"
     79
     80    @notify.datapoint(dp="state", condition="change")
     81    def stateChanged(self, event):
     82        """ Method called when the 'state' datapoint changes
     83        """
     84        logger.debug("TimerFB.stateChanged(): event=%s" % repr(event))
     85
     86        if event['newValue'] == "On":
     87            delay = self.dp["delay"].value
     88            Logger().info("%s: start timer for %ds" % (self._name, delay))
     89            self._timer = delay
     90        elif event['newValue'] == "Off":
     91            if self._timer:
     92                Logger().info("%s: switched off detected; cancel timer" % self._name)
     93                self._timer = 0
     94
     95    @notify.datapoint(dp="delay", condition="change")
     96    def delayChanged(self, event):
     97        """ Method called when the 'delay' datapoint changes
     98        """
     99        logger.debug("TimerFB.delayChanged(): event=%s" % repr(event))
     100
     101        # If the timer is running, we reset it to the new delay
     102        if self._timer:
     103            delay = self.dp["delay"].value
     104            Logger().info("%s: delay changed; restart timer" % self._name)
     105            self._timer = delay
     106
     107
     108def main():
     109
     110    # Register functional block
     111    ets.register(TimerFB, name="timer", desc="")
     112
     113    # Weave datapoints
     114    ets.weave(fb="timer", dp="cmd", gad="1/1/1")
     115    ets.weave(fb="timer", dp="state", gad="1/2/1")
     116    ets.weave(fb="timer", dp="delay", gad="1/3/1")
     117
     118    print
     119    ets.printGroat("gad")
     120    print
     121    ets.printGroat("go")
     122    print
     123    schedule.printJobs()
     124    print
     125
     126    # Run the stack main loop (blocking call)
     127    stack.mainLoop()
     128
     129
     130if __name__ == "__main__":
     131    try:
     132        main()
     133    except:
     134        logger.exception("3_main")
     135}}}
     136
     137That's it! As you can see, concepts used here are simple... This Functional Block can now be used from any other real device of your installation, through Groups Addresses {{{1/1/1}}}, {{{1/3/1}}} and {{{1/3/1}}}. All you have to do, is to weave (link, bind...) the Group Objects of your real devices to these Groups Addresses, using the real '''ETS''' application. Sure, you can change the GAD to match your installation.
     138
     139Ok, now, lets start this device (invoke the python interpreter as for any other python script):
     140
     141{{{
     142
     143}}}
     144
     145
     146Lets have a closer look to this example. First, we import some python objects:
     147
     148{{{
     149#!python
     150from pknyx.api import Logger
     151from pknyx.api import FunctionalBlock, Stack, ETS
     152from pknyx.api import Scheduler, Notifier
     153}}}
     154
     155These objects are all classes.
     156
     157We then instanciante some high level objects and helpers:
     158
     159{{{
     160#!python
     161logger = Logger("%s-%s" % (NAME, IND_ADDR))
     162
     163stack = Stack(individualAddress=IND_ADDR)
     164ets = ETS(stack=stack, gadMap=GAD_MAP))
     165
     166schedule = Scheduler()
     167notify = Notifier()
     168}}}
     169
     170The {{{Logger}}} object is a global logger service, which allow you to output all informations you want, at different levels. '''pKNyX''' make a big usage of that logger.
     171
     172The {{{Stack}}} object is used to communicate over the bus (real bus, of course, but also virtual bus). We can give it an Individual Address, to mimic real devices behaviour. This address will be used as Source Address when communicating over the bus. The address {{{0/0/0}}} is used by default.
     173
     174'''Important: to avoid internal loops, a device drops all telegrams sent by itself. So, if you want 2 virtal devices to communicate, you must ensure that they don't use the same source address.'''
     175
     176The {{{ETS}}} object is a tool which works more or less like the real ETS application (see below).
     177
     178The {{{Scheduler}}} is a helper implementating a powerfull scheduler (see below).
     179The {{{Notifier}}} works the same way the {{{Scheduler}}} does, but does not provide the same features (see below).
     180
     181The main part is to create our custom Functional Block; this is done by subclassing the '''!FunctionBlock''' base class, and adding a few attributes/methods:
     182
     183{{{
     184#!python
     185class TimerFB(FunctionalBlock):
     186    """ Timer functional block
     187    """
     188
     189    # Datapoints definition
     190    DP_01 = dict(name="cmd", access="output", dptId="1.001", default="Off")
     191    DP_02 = dict(name="state", access="input", dptId="1.001", default="Off")
     192    DP_03 = dict(name="delay", access="input", dptId="7.005", default=10)
     193
     194    GO_01 = dict(dp="cmd", flags="CWT", priority="low")
     195    GO_02 = dict(dp="state", flags="CWUI", priority="low")
     196    GO_03 = dict(dp="delay", flags="CWU", priority="low")
     197
     198    DESC = "Timer"
     199
     200    def _init(self):
     201        """ Additionnal init of the timer
     202        """
     203        self._timer = 0
     204}}}
     205
     206The {{{DP_xx}}} class attributes are the Datapoints of our Functional Block. The {{{GO_xx}}} class attributes are the Group Objects mapping the Datapoints to the bus through multicast service (Group Address). They are both defined as python dictionnary; they will be automatically instanciated for us by the framework. The named used here do not matter, as long as they start with {{{DP_}}} for Datapoints, and {{{GO_}}} for Group Objects.
     207
     208There are a few parameters in the dicts; some are obvious, some will need more explanations. But this is out of the scope of this tutorial.
     209
     210The {{{_init()}}} method is called when the functional block is instanciated; it can be used to init some needed global vars, or make initial tasks. Here, we just create the {{{_timer}}} var, and set it to 0.
     211
     212Ok, it's time to dig in the active part of our functional block. Let's start with the {{{stateChanged()}}} method:
     213
     214{{{
     215#!python
     216    @notify.datapoint(dp="state", condition="change")
     217    def stateChanged(self, event):
     218        """ Method called when the 'state' datapoint changes
     219        """
     220        logger.debug("TimerFB.stateChanged(): event=%s" % repr(event))
     221
     222        if event['newValue'] == "On":
     223            delay = self.dp["delay"].value
     224            Logger().info("%s: start timer for %ds" % (self._name, delay))
     225            self._timer = delay
     226        elif event['newValue'] == "Off":
     227            if self._timer:
     228                Logger().info("%s: switched off detected; cancel timer" % self._name)
     229                self._timer = 0
     230}}}
     231
     232This method as a '''decorator'''. If you don't know what is a decorator, have a look at this [excellent documentation.
     233
     234
     235Note how this method is periodically called, using the {{{schedule.every()}}} method as python decorator. This decorator will automatically register our method and call it every 5 minutes.
     236
     237In this method, we get the temperature and humidity values (not explained here), and give these values to the respective Datapoints.
     238
     239Then, we register our new Funtional Block (this will automatically instanciate it - and do other things):
     240
     241{{{
     242#!python
     243ets.register(WeatherTemperatureBlock, name="weather_temperature", desc="A simple weather block example")
     244}}}
     245
     246and use the {{{ETS}}} object to weave (bind, link...) our Datapoints (their matching Group Objects, in fact) to Group Addresses:
     247
     248{{{
     249#!python
     250ets.weave(fb="weatherTempBlock", dp="temperature", gad="1/1/1")
     251ets.weave(fb="weatherTempBlock", dp="humidity", gad="1/1/2")
     252}}}
     253
     254And finally, we launch the framework main loop:
     255
     256{{{
     257#!python
     258stack.mainLoop()
     259}}}
     260
     261(this call is blocking).
     262
     263We now have a process (Device) running, listening to the bus. The Datapoints, through their respective Group Objects, will react to requests on the Group Addresses they are weaved to ("1/1/1" and "1/1/2"). According to the flags, they will transmit their internal value on Read requests or if their value internally change (updated). Yes, you don't have to manage this: '''pKNyX''' does it for you! This is the all point of a framework, isn't it?
     264
     265That's it for now. This is only a draft version; final implementation may change, according to feedback/suggestions I will get. But the core is all there. Again, the goal of the framework is to provide very high level tools to build complete and powerfull applications and KNX extensions.