Changes between Version 39 and Version 40 of Proposal


Ignore:
Timestamp:
Jul 8, 2013, 1:20:58 PM (11 years ago)
Author:
Frédéric
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Proposal

    v39 v40  
    2626
    2727'''pKNyX''' tries to follow as much as possible the KNX specifications in its implementation. This may not leads to the best architecture in term of efficiency, but I think it is the best way to have something easy to understand, maintain and improve. This can also  serve as a summary of all informations I can grab arround these specs.
    28 == Virtual Device ==
    29 
    30 This will be the central feature of '''pKNyX''', allowing user to create virtual devices which mimics real KNX devices. They will follow the same architecture, like having '''Datapoints''' (aka as Communication Objects), which have to be linked to '''Group Address''' to communicate.
    31 
    32 Here is a very simple example of what I have in mind: creating a virtual weather station, which uses informations from a non-KNX real weather-station, or from a web site:
    33 
    34 {{{
    35 #!python
    36 from pknyx.api import Device, Stack, ETS
    37 
    38 stack = Stack()
    39 ets = ETS(stack)
    40 
    41 # Weather station class definition
    42 class WeatherStation(Device):
    43 
    44     # Datapoints (= Group Objects) definition
    45     DP_01 = dict(name="temperature", dptId="9.001", flags="CRT", priority="low", initValue=0.)
    46     DP_02 = dict(name="humidity", dptId="9.007", flags="CRT", priority="low", initValue=0.)
    47     DP_03 = dict(name="wind_speed", dptId="9.005", flags="CRT", priority="low", initValue=0.)
    48     DP_04 = dict(name="wind_alarm", dptId="1.005", flags="CRT", priority="urgent", initValue="No alarm")
    49     DP_05 = dict(name="wind_speed_limit", dptId="9.005", flags="CWTU", priority="low", initValue=15.)
    50     DP_06 = dict(name="wind_alarm_enable", dptId="1.003", flags="CWTU", priority="low", initValue="Disable")
    51 
    52 # Instanciation of the weather station device object
    53 station = WeatherStation(name="weather_station", desc="My simple weather station example", address="1.2.3")
    54 
    55 # Linking weather station Datapoints to Group Address
    56 ets.link(dev=station, dp="temperature", gad="1/1/1")
    57 ets.link(dev=station, dp="humidity", gad="1/1/2")
    58 ets.link(dev=station, dp="wind_speed", gad="1/1/3")
    59 ets.link(dev=station, dp="wind_alarm", gad="1/1/4")
    60 ets.link(dev=station, dp="wind_speed_limit", gad="1/1/5")
    61 ets.link(dev=station, dp="wind_alarm_enable", gad="1/1/6")
    62 }}}
    63 
    64 That's it! As you can see, concepts used here are not new... This device can be then used from any other real device of your installation, through GADs {{{1/1/1}}} to {{{1/1/6}}}. All you have to do, is to link the Communication Objects of your real devices to these GADs, using the '''ETS''' application. For example, the DP Nr 4 will send the {{{"Alarm"}}} value over the bus when the wind speed will reach the value stored in DP Nr 5, but only if the value of the DP Nr 6 as been set to {{{"Enable"}}}. By linking this alram object to the alarm entry of you sun blinds, you can save them from destruction!
    65 
    66 Ok, but how the device knows the current wind speed? Well, it does not... yet! Let's see how it can be done:
    67 
    68 {{{
    69 #!python
    70 from pknyx.api import Device, Stack, ETS
    71 
    72 stack = Stack()
    73 ets = ETS(stack)
    74 
    75 # Weather station class definition
    76 class WeatherStation(Device):
    77 
    78     # Datapoints (= Group Objects) definition
    79     DP_01 = dict(name="temperature", dptId="9.001", flags="CRT", priority="low", initValue=0.)
    80     DP_02 = dict(name="humidity", dptId="9.007", flags="CRT", priority="low", initValue=0.)
    81     DP_03 = dict(name="wind_speed", dptId="9.005", flags="CRT", priority="low", initValue=0.)
    82     DP_04 = dict(name="wind_alarm", dptId="1.005", flags="CRT", priority="urgent", initValue="No alarm")
    83     DP_05 = dict(name="wind_speed_limit", dptId="9.005", flags="CWTU", priority="low", initValue=15.)
    84     DP_06 = dict(name="wind_alarm_enable", dptId="1.003", flags="CWTU", priority="low", initValue="Disable")
    85 
    86     @Device.schedule.every(minute=5)
    87     def checkWindSpeed(self):
    88 
    89         # How we retreive the speed is out of the scope of this proposal
    90         # speed = xxx
    91 
    92         # Now, write the new speed value to the Datapoint
    93         self.dp["wind_speed"].value = speed
    94 
    95         # Check alarm speed
    96         if self.dp["wind_alarm_enable"].value == "Enable":
    97             if speed >= self.dp["wind_speed_limit"].value:
    98                 self.dp["wind_alarm"].value = "Alarm"
    99             elif speed < self.dp["wind_speed_limit"].value - 5.:
    100                 self.dp["wind_alarm"].value = "No alarm"
    101 
    102 # Instanciation of the weather station device object
    103 station = WeatherStation(name="weather_station", desc="My simple weather station example", address="1.2.3")
    104 
    105 # Linking weather station Datapoints to Group Address
    106 ets.link(dev=station, dp="temperature", gad="1/1/1")
    107 ets.link(dev=station, dp="humidity", gad="1/1/2")
    108 ets.link(dev=station, dp="wind_speed", gad="1/1/3")
    109 ets.link(dev=station, dp="wind_alarm", gad="1/1/4")
    110 ets.link(dev=station, dp="wind_speed_limit", gad="1/1/5")
    111 ets.link(dev=station, dp="wind_alarm_enable", gad="1/1/6")
    112 }}}
    113 
    114 All we do, here, is adding a method periodically called by the framework (every 5 minutes in the above example). In this method, we retreive the wind speed (not explained here), and give the value to the Datapoint Nr 3. If the value has changed from the previous call, the Datapoint will automagically transmit it to the bus (according to the flags, of course).
    115 
    116 We also check if the speed has reached the speed limit; if so, we change the corresponding Datapoint value, which wil, in turn, send the value on the bus if needed (a special value of the flag, like in '''linknx''', can force the value to be sent, even if it does not change).
    117 
    118 A last thing: is will be possible to compute and print the '''map table''':
    119 
    120 {{{
    121 #!python
    122 
    123 print ets.computeMapTable()
    124 
    125 {'byDP': {'humidity (station)': ['1/1/2'],
    126           'temperature (station)': ['1/1/1'],
    127           'wind_alarm (station)': ['1/1/4'],
    128           'wind_alarm_enable (station)': ['1/1/6'],
    129           'wind_speed (station)': ['1/1/3'],
    130           'wind_speed_limit (station)': ['1/1/5']},
    131  'byGAD': {'1/1/1': ['temperature (station)'],
    132            '1/1/2': ['humidity (station)'],
    133            '1/1/3': ['wind_speed (station)'],
    134            '1/1/4': ['wind_alarm (station)'],
    135            '1/1/5': ['wind_speed_limit (station)'],
    136            '1/1/6': ['wind_alarm_enable (station)']}}
    137 }}}
     28== Functional Blocks ==
     29
     30This will be the central feature of '''pKNyX''', allowing user to create virtual devices which mimics real KNX devices. The Device itself will be implemented as the process level.
     31
     32Here is a very simple example of what I have in mind: creating a virtual minimalistic weather station, which uses informations from a non-KNX real weather-station, or from a web site. This station only implements temperature/humidity.
     33
     34{{{
     35#!python
     36from pknyx.api import FunctionalBlock, Stack, ETS
     37
     38
     39class WeatherTemperatureBlock(FunctionalBlock):
     40
     41    DP_01 = dict(name="temperature", access="output", dptId="9.001", default=19.)
     42    DP_02 = dict(name="humidity", access="output", dptId="9.007", default=50.)
     43
     44    GO_01 = dict(dp="temperature", flags="CRT", priority="low")
     45    GO_02 = dict(dp="humidity", flags="CRT", priority="low")
     46
     47
     48weatherTempBlock = WeatherTemperatureBlock(name="weather_tempertature", desc="A simple weather block example")
     49
     50stack = Stack(individualAddress="1.2.3")
     51ets = ETS(stack)
     52
     53ets.weave(fb=weatherTempBlock, dp="temperature", gad="1/1/1")
     54ets.weave(fb=weatherTempBlock, dp="humidity", gad="1/1/2")
     55
     56stack.run()
     57}}}
     58
     59That's it! As you can see, concepts used here are simple... This Functional Block can be then used from any other real device of your installation, through Groups Addresses {{{1/1/1}}} and {{{1/1/2}}}. 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.
     60
     61Lets have a closer look to this example. First, we import some python objects:
     62
     63{{{
     64#!python
     65from pknyx.api import FunctionalBlock, Stack, ETS
     66}}}
     67
     68These objects are classes.
     69
     70Then, we create a custom Functional Block; this is done by subclassing the '''!FunctionBlock''' base class, and adding a few attributes:
     71
     72{{{
     73#!python
     74class WeatherTemperatureBlock(FunctionalBlock):
     75
     76    DP_01 = dict(name="temperature", access="output", dptId="9.001", default=19.)
     77    DP_02 = dict(name="humidity", access="output", dptId="9.007", default=50.)
     78
     79    GO_01 = dict(dp="temperature", flags="CRT", priority="low")
     80    GO_02 = dict(dp="humidity", flags="CRT", priority="low")
     81}}}
     82
     83The {{{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. They are both defined as python dictionnary; they will be automatically instanciated for you by the framework. The named used here does not matter, as long as they start with {{{DP_}}} for Datapoints, and {{{GO_}}} for Group Objects.
     84
     85Then, we just instanciate our new Funtional Block:
     86
     87{{{
     88#!python
     89weatherTempBlock = WeatherTemperatureBlock(name="weather_tempertature", desc="A simple weather block example")
     90}}}
     91
     92We also need to instanciante some global high level objects:
     93
     94{{{
     95#!python
     96stack = Stack(individualAddress="1.2.3")
     97ets = ETS(stack)
     98}}}
     99
     100The {{{Stack}}} object is used to communicate over the bus (real bus, of course, but also on virtual bus). We can give it an Individual Address, to mimic real devices behaviour.
     101The {{{ETS}}} object is a tool which works like the reall ETS application.
     102
     103The {{{Stack}}} object is used to weave (bind, link...) our Group Objects to Group Addresses:
     104
     105{{{
     106#!python
     107ets.weave(fb=weatherTempBlock, dp="temperature", gad="1/1/1")
     108ets.weave(fb=weatherTempBlock, dp="humidity", gad="1/1/2")
     109}}}
     110
     111And finally:
     112
     113{{{
     114#!python
     115stack.serve()
     116}}}
     117
     118This call is blocking. We 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 their are weaved to ("1/1/1" and "1/1/2"). According to the flags, they will transmit their internal value on a Read request, or if this value changes.
     119
     120Ok, but our Functional Block is not really usefull yet, as it does not refresh its Datapoints! Let's see how it can be done:
     121
     122{{{
     123#!python
     124from pknyx.api import FunctionalBlock, Stack, ETS
     125from pknyx.api import Scheduler
     126
     127
     128schedule = Scheduler()
     129
     130
     131class WeatherTemperatureBlock(FunctionalBlock):
     132
     133    DP_01 = dict(name="temperature", access="output", dptId="9.001", default=19.)
     134    DP_02 = dict(name="humidity", access="output", dptId="9.007", default=50.)
     135
     136    GO_01 = dict(dp="temperature", flags="CRT", priority="low")
     137    GO_02 = dict(dp="humidity", flags="CRT", priority="low")
     138
     139    schedule.every(minute=5)
     140    def updateTemperaturHumidity(self, event):
     141
     142        # How we retreive the temperature/humidity is out of the scope of this proposal
     143        # temperature = xxx
     144        # humidity = xxx
     145        self.dp["temperature"] = temperature
     146        self.dp["humidity"] = humidity
     147
     148weatherTempBlock = WeatherTemperatureBlock(name="weather_tempertature", desc="A simple weather block example")
     149
     150stack = Stack(individualAddress="1.2.3")
     151ets = ETS(stack)
     152
     153ets.weave(fb=weatherTempBlock, dp="temperature", gad="1/1/1")
     154ets.weave(fb=weatherTempBlock, dp="humidity", gad="1/1/2")
     155
     156stack.run()
     157}}}
     158
     159All we do, here, is adding a method periodically called by the framework (every 5 minutes in the above example). In this method, we retreive the temperature and humidity values (not explained here), and give the value to the respective Datapoints. If the value has changed from the previous call, the Datapoint will automagically transmit it to the bus (according to the flags, of course).
    138160
    139161That'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.
    140 
    141162== Simple rule ==
    142163