142 | | |
143 | | }}} |
144 | | |
145 | | |
146 | | Lets have a closer look to this example. First, we import some python objects: |
| 140 | MainThread::Logger_.__init__(): start new logger 'timer-1.2.3' |
| 141 | MainThread::Scheduler started |
| 142 | |
| 143 | GAD Datapoint Functional block DPTID Flags Priority |
| 144 | ----------------------------------------------------------------------------------------------------------------------------- |
| 145 | 1 Lights |
| 146 | ├── 1 Lights commands |
| 147 | │ ├── 1 Light 'test' (cmd) cmd timer 1.001 CWT low |
| 148 | ├── 2 Lights States |
| 149 | │ ├── 1 Ligh 'test' (state) state timer 1.001 CWUI low |
| 150 | ├── 3 Lights delays |
| 151 | │ ├── 1 Light 'test' (delay) delay timer 7.005 CWU low |
| 152 | |
| 153 | Functional block Datapoint DPTID GAD Flags Priority |
| 154 | ------------------------------------------------------------------------------------------------------------------------ |
| 155 | timer delay 7.005 1/3/1 CWU low |
| 156 | timer state 1.001 1/2/1 CWUI low |
| 157 | timer cmd 1.001 1/1/1 CWT low |
| 158 | |
| 159 | Jobstore default: |
| 160 | TimerFB.updateTimer (trigger: interval[0:00:01], next run at: 2013-08-14 20:21:46.943959) |
| 161 | |
| 162 | MainThread::Stack running |
| 163 | }}} |
| 164 | |
| 165 | Here, your device waits for bus events! |
| 166 | |
| 167 | Lets deep inside this example. First, we import some python objects: |
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 | | |
176 | | The {{{ETS}}} object is a tool which works more or less like the real ETS application (see below). |
177 | | |
178 | | The {{{Scheduler}}} is a helper implementating a powerfull scheduler (see below). |
179 | | The {{{Notifier}}} works the same way the {{{Scheduler}}} does, but does not provide the same features (see below). |
180 | | |
181 | | The main part is to create our custom Functional Block; this is done by subclassing the '''!FunctionBlock''' base class, and adding a few attributes/methods: |
| 215 | '''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, like in a real installation. Keep this in mind ; if you don't see telegrams you are expecting, the problem may be there.''' |
| 216 | |
| 217 | The {{{ETS}}} object is a tool which works more or less like the real ETS application (see below). It needs one mandatory parameter: the {{{Stack}}} object. The {{{gadMap}}} param is optionnal, and is a simple dictionnary which maps group addresses to names/description. '''pKNyX''' does not need this to work, but it can be easier to debug things. Note that it is not really used now, but it will be in the future. |
| 218 | |
| 219 | The {{{Scheduler}}} is a helper implementating a powerfull scheduler, based on [http://pythonhosted.org/APScheduler APScheduler] (see below). |
| 220 | The {{{Notifier}}} works the same way the {{{Scheduler}}} does, but provides bus notifications rather than time notifications (see below). |
| 221 | |
| 222 | Now, it is time to write the central part of our device: a custom Functional Block; this is done by subclassing the '''!FunctionBlock''' base class, and adding a few attributes/methods: |
232 | | This method as a '''decorator'''. If you don't know what is a decorator, have a look at this [excellent documentation. |
233 | | |
234 | | |
235 | | Note 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 | | |
237 | | In this method, we get the temperature and humidity values (not explained here), and give these values to the respective Datapoints. |
238 | | |
239 | | Then, we register our new Funtional Block (this will automatically instanciate it - and do other things): |
240 | | |
241 | | {{{ |
242 | | #!python |
243 | | ets.register(WeatherTemperatureBlock, name="weather_temperature", desc="A simple weather block example") |
244 | | }}} |
245 | | |
246 | | and use the {{{ETS}}} object to weave (bind, link...) our Datapoints (their matching Group Objects, in fact) to Group Addresses: |
247 | | |
248 | | {{{ |
249 | | #!python |
250 | | ets.weave(fb="weatherTempBlock", dp="temperature", gad="1/1/1") |
251 | | ets.weave(fb="weatherTempBlock", dp="humidity", gad="1/1/2") |
252 | | }}} |
253 | | |
254 | | And finally, we launch the framework main loop: |
255 | | |
256 | | {{{ |
257 | | #!python |
258 | | stack.mainLoop() |
259 | | }}} |
260 | | |
261 | | (this call is blocking). |
262 | | |
263 | | 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 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 | | |
265 | | That'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. |
| 277 | The role of this method is to start/stop the timer depending of the state. |
| 278 | |
| 279 | This method as a '''decorator'''. '''pKNyX''' does not use decorators as they usually are made for; here, the decorator registers the decorated method in the {{{Notifier}}}, which will call this method as soon as the ''state'' Datapoint changes because of a bus activity. |
| 280 | |
| 281 | Note how we retreive Datapoint value: our functional block has a {{{dp}}} dictionnary, which contains all our datapoints; the keys are the names we used to describe them in the {{{DP_xxx}}} dictionnary. To get the value of the Datapoint, just use the {{{.value}}} property. |
| 282 | |
| 283 | Lets have a look at the timer treatement: |
| 284 | |
| 285 | {{{ |
| 286 | #!python |
| 287 | @schedule.every(seconds=1) |
| 288 | def updateTimer(self): |
| 289 | """ Method called every second. |
| 290 | """ |
| 291 | if self._timer: |
| 292 | self._timer -= 1 |
| 293 | if not self._timer: |
| 294 | logger.info("%s: timer expired; switch off" % self._name) |
| 295 | self.dp["cmd"].value = "Off" |
| 296 | }}} |
| 297 | |
| 298 | Note how this method is periodically called, using the {{{schedule.every()}}} method as python decorator, as for the {{{Notifier}}}. This decorator will automatically register our method and call it every second. |
| 299 | |
| 300 | There, we just decrement the timer (if not null), and check if the delay expired. If it is the case, we change the ''cmd'' Datapoint value by assigning a new value to the {{{.value}}} property. Doing this, '''pKNyX''' will automatically notify the bus of this change, according to the flags of the corresponding {{{GO_xxx}}} object! If things do not work as expected, check that you didn't omit the {{{.value}}} property; if it is the caase, you will simply overwrite the Datapoint itself with the value you want to assign to it! |
| 301 | |
| 302 | The last method just manages the ''delay'' Datapoint: |
| 303 | |
| 304 | {{{ |
| 305 | #!python |
| 306 | @notify.datapoint(dp="delay", condition="change") |
| 307 | def delayChanged(self, event): |
| 308 | """ Method called when the 'delay' datapoint changes |
| 309 | """ |
| 310 | logger.debug("TimerFB.delayChanged(): event=%s" % repr(event)) |
| 311 | |
| 312 | # If the timer is running, we reset it to the new delay |
| 313 | if self._timer: |
| 314 | delay = self.dp["delay"].value |
| 315 | Logger().info("%s: delay changed; restart timer" % self._name) |
| 316 | self._timer = delay |
| 317 | }}} |
| 318 | |
| 319 | I think you got the point ;o) |
| 320 | |
| 321 | Ok, we need to write a few more things to get our device working. First, we need to register the functional bloc: |
| 322 | |
| 323 | {{{ |
| 324 | #!python |
| 325 | def main(): |
| 326 | |
| 327 | # Register functional block |
| 328 | ets.register(TimerFB, name="timer", desc="") |
| 329 | }}} |
| 330 | |
| 331 | and use the {{{ETS}}} object to weave (bind, link...) our Datapoints (their matching Group Objects, in fact) to our group addresses: |
| 332 | |
| 333 | {{{ |
| 334 | #!python |
| 335 | # Weave datapoints |
| 336 | ets.weave(fb="timer", dp="cmd", gad="1/1/1") |
| 337 | ets.weave(fb="timer", dp="state", gad="1/2/1") |
| 338 | ets.weave(fb="timer", dp="delay", gad="1/3/1") |
| 339 | }}} |
| 340 | |
| 341 | We can print a summary of our mapping: |
| 342 | |
| 343 | {{{ |
| 344 | #!python |
| 345 | print |
| 346 | ets.printGroat("gad") |
| 347 | print |
| 348 | ets.printGroat("go") |
| 349 | print |
| 350 | schedule.printJobs() |
| 351 | print |
| 352 | }}} |
| 353 | |
| 354 | And finally, launch the framework main loop: |
| 355 | |
| 356 | {{{ |
| 357 | #!python |
| 358 | # Run the stack main loop (blocking call) |
| 359 | stack.mainLoop() |
| 360 | }}} |
| 361 | |
| 362 | 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 they are weaved to. |
| 363 | |
| 364 | That's it for now. This is the first working dev. release; their are many additional things to do, and final implementation may change, according to feedback/suggestions I will get. But the core is there. Again, the goal of the framework is to provide very high level tools to build complete and powerfull applications and KNX extensions. Unlink '''linknx''', you have a little more to do that simply write a configuration file. But you will find that it is not that complicated ('''pKNyX''' handles all nasty stuff for you), and may even simplify things for complex tasks (linknx rules are not that simple). |