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

Initial contribution of the KNX 2.0 (refactored) binding #1438

Closed
wants to merge 3 commits into from

Conversation

kgoderis
Copy link
Contributor

This is PR that contains a refactoring of the KNX 2.0 binding. In short, it will allow a DSL syntax similar as follows:

Bridge knx:ip:ip1 [ ipAddress="192.168.0.10", portNumber=3671, localIp="192.168.0.200", ipConnectionType="TUNNEL", readingPause=50, responseTimeOut=1000, readRetriesLimit=3, autoReconnectPeriod=1,localSourceAddr="0.0.0" ] 
{
    Thing generic someGenericActor [ address="1.1.132"] {
        Channels:
            Type switch : someSwitch [ switchGA="1/5/0", statusGA="1/5/1", read=true, interval=15, autoupdate=false]
            Type energy : someEnergyReading [ energyGA="1/5/4", read=true, interval=15]
            Type dimmer : someDimmer [ increaseDecreaseGA="1/2/71", positionGA="1/2/72", positionStatusGA="1/2/74", read=true, interval=15, autoupdate=false]
        }

}

I have made some design decisions as well, but these are up for discussion:

  • no channel groups, as it is either channel groups or channels. I choose channels for more flexibility
  • no channels on the bridge Thing itself, everything has to go through the Generic Thing. This reduces complexity and increases readability of the code
  • no more Discovery service for Group Addresses, only for Individual Addresses, which then get mapped into Generic Things
  • no support for [ GroupAddresses="1/5/0, 1/5/1"] configuration constructs for the channels, but just one GA per configuration parameter, just to keep things simpel
  • all complexity wrt channels is rather captured in the KNXChannelSelector class. It makes the binding relatively clean, but means there is a limitation on what you can do. For example, processing of States (e.g. type conversion,...) is possible, but it is not possible to use the historical state of a Channel, since we are working through an Enum and not POJOs.

The rest you will have to discover in the code itself. Enjoy ;-)

Signed-off-by: Karel Goderis karel.goderis@me.com

}

link = new KNXNetworkLinkIP(ipConnectionType, localEndPoint,
new InetSocketAddress(ip, ((BigDecimal) getConfig().get(PORT_NUMBER)).intValue()), false,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: use default port number, config field is not required, this will crate null pointer exception if user forgets to set it. As default multitasking address is set, one can assume the default port alia set as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to traverse the code again and insert some NPE checks left and right


protected Logger logger = LoggerFactory.getLogger(KNXBridgeBaseThingHandler.class);

public IndividualAddressDiscoveryService(KNXBridgeBaseThingHandler bridgeHandler) throws IllegalArgumentException {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: for Knx traffic sensitive environments it could be usable to have config parameter on bridge to enable/disable scanning

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. device discovery part, and certainly the part for reading out device parameters, is quite experimental. Sometimes it works fine, but it times out a lot on a busy KNX bus

@udo1toni
Copy link
Contributor

no support for[ GroupAddresses="1/5/0, 1/5/1"] configuration constructs for the channels, but just one GA per configuration parameter, just to keep things simple

Please keep in mind, that knx does work this way, there are some installations that need more than one GA per channel, for example: An actuator does not provide it's state but does react to more than one GA, in this case, you will have to configure the second GA in each switch to always get the correct state.

@kaikreuzer
Copy link
Member

@kgoderis I agree with @udo1toni, this would be a constraint in comparison to the KNX1 binding, where we can add any number of "listeningGAs" (by adding them with "+").

I think it should be fairly easy to support this in your code as well, because you could simply declare the statusGA config parameters to be multiple="true" in the config description, which then will allow to provide an array of strings (GAs).

@kgoderis
Copy link
Contributor Author

kgoderis commented Nov 24, 2016

@kaikreuzer @udo1toni Thru enough, indeed, you can configure more that one listening GA on an actor. But then, from the perspective of ESH/OH the question is if this holds true as well? IMO in the Thing definition you want to listen to the actual state of Actor (by the same KNX specs/constraints, the Actors can only send to a single Group Address), so, you would only have to listen to a single GA. Since we ruled out the strategy to build virtual actors in ESH/OH, are there use cases whereby a Thing would listen to multiple GA's (e.g. thus sent by different Actors, or, originating from different Communication Objects on the same Actor)? I could imagine something like a Switch that is ON when at least one switch actor of a group of actors (or channels on an actor if a multi-channel device) is switched on, but apart from that.... Maybe the OH1 KNX binding is too long in the past for me, I do not recall having multiple GA's in an item definition in own installation.

Just to be clear on this, as it might be utterly confusing, the objective here is not to replicate an KNX Actor in ESH/OH, but rather provide a way/method/interface to operate them, right?

That being said, there is no issue to implement multiple="true" of course. Do you rather see this limited to the listening GA's, or also to the "action" GA's, and thus enable things like? [pls neglect exact syntax here]

Type switch : someSwitch [ switchGA="1/5/0, 1/4/0", statusGA="1/5/1, 1/4/1", read=true, interval=15, autoupdate=false]

so that a ON command on the someSwitch channel of the Thing, will switch on for example two different switch actors on the bus, and that in reverse a status sent back by any of the two actors, could change the state of the Item linked to someSwitch? To further complicate things, I presume that in such a case, it is not expected that the channel acts as a bridge between the GA's? (e.g. a 1.001 DPT received on 1/5/1 will set the state of the linked Item to ON, but will also send out a new 1.001 DPT to both 1/5/0 and 1/4/0)

@kgoderis
Copy link
Contributor Author

@kaikreuzer the usage of multiple=true implies that one has to work with

      <options>
        <option value="String">String</option>
      </options>

constructs, no? I understand how that can be done from a UI perspective, e.g user making the selection from a list, but how is that dealt with in the DSL?

@kaikreuzer
Copy link
Member

No, "multiple" does not require any options, it can be any kind of entries.
I am not fully sure if arrays are already supported by the DSL, I actually fear that they aren't.
@SJKA, am I right? If so, any good suggestion how we could best add that to the Thing grammar?

@kgoderis
Copy link
Contributor Author

@SJKA, am I right? If so, any good suggestion how we could best add that to the Thing grammar?

I checked as well, and they are not at the time being. Once that is in, I will add support for them

@sjsf
Copy link

sjsf commented Nov 25, 2016

am I right? If so, any good suggestion how we could best add that to the Thing grammar?

Yes, you are. I will think about it.

Once that is in, I will add support for them

@kgoderis I'll let you know. But I guess you will notice the PR yourself anyway.

@kaikreuzer kaikreuzer added the new binding If someone has started to work on a binding. For a new binding PR. label Dec 1, 2016
Copy link

@sjsf sjsf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @kgoderis, I just had a look at the ThingHandler (not the bridge handlers so far) and already left a few comments for you. I will continue the review as soon as possible.

<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="false" name="org.openhab.binding.knx.internal.factory.KNXHandlerFactory">

<implementation class="org.openhab.binding.knx.internal.factory.KNXHandlerFactory"/>
<reference bind="setItemRegistry" cardinality="1..1" interface="org.eclipse.smarthome.core.items.ItemRegistry" name="ItemRegistry" policy="static" unbind="unsetItemRegistry"/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bindings should not need access to the item registry. And as a matter of fact you are not using it 👍
Just remove the reference here.

THING_TYPE_IP_BRIDGE, THING_TYPE_SERIAL_BRIDGE);

private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
protected ItemRegistry itemRegistry;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the itemRegistry here is dead code (which is good, see my comment above...)

private Logger logger = LoggerFactory.getLogger(IPBridgeThingHandler.class);

// the ip connection type for connecting to the KNX bus. Could be either TUNNEL or ROUTING
private static int ipConnectionType;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this static?

private static int ipConnectionType;

// the ip address to use for connecting to the KNX bus
private static String ip;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this static?

private static String ip;

// the group address used within the KNX bus
private static String localsource;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this static?

link = new KNXNetworkLinkFT12(serialPort, new TPSettings());

} catch (NoClassDefFoundError e) {
throw new KNXException(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add e as the second argument (cause)

*/
public class SerialBridgeThingHandler extends KNXBridgeBaseThingHandler {

// List of all Configuration parameters
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

registering "dumb" instances just to get the dependencies injected looks like a pretty awkward hack to me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How else should this be done with Declarative Services? in KNXHandlerFactory? and what if then new KNXTypeMappers would be registered....?

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

registering "dumb" instances just to get the dependencies injected looks like a pretty awkward hack to me

private LinkedBlockingQueue<RetryDatapoint> readDatapoints = new LinkedBlockingQueue<RetryDatapoint>();
protected ConcurrentHashMap<IndividualAddress, Destination> destinations = new ConcurrentHashMap<IndividualAddress, Destination>();

protected ItemChannelLinkRegistry itemChannelLinkRegistry;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you shouldn't need to depend in it in a binding. Also, I cannot see any usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, for the KNX binding you might. This was discussed several times in the past, whereby one has to decide what the binding will do wrt "bridging" values between Items.

}

@Override
public boolean equals(Object o) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this implementation of equal is not null safe. And this class is lacking a hashcode() method. Which is especially a problem as it is used within a set!


if (pollingJob == null || pollingJob.isCancelled()) {
logger.trace("'{}' will be polled every {} ms", getThing().getUID(), pollingInterval);
pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, Math.round(pollingInterval / 4),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pollingInterval / 4 already results in an int, so rounding is useless


@Override
public boolean equals(Object o) {
return (o instanceof RetryDatapoint)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, not null-safe and hashCode() is missing!

PS: let eclipse generate it... ;-)

@kgoderis
Copy link
Contributor Author

kgoderis commented Dec 5, 2016

@SJKA You professional coders are way too professional ;-) ;-) ;-)
Ok, give me some time to process the comments

@kgoderis kgoderis force-pushed the knx-refactor branch 2 times, most recently from 8ef3876 to 365d033 Compare December 10, 2016 15:23
@kgoderis
Copy link
Contributor Author

@SJKA Processed the bulk, some stuff we need to further discuss

@kgoderis
Copy link
Contributor Author

Quick poll : who is using ETS3, ETS4 or ETS5?

I am looking into adding the possibility to OH2 to automatically read-out the knxproj ETS files and create Things from that. The XSD of the ETS project files have evolved over time, I would like to find out to what extent the various versions of ETS have to be supported

@uspringis
Copy link

uspringis commented Dec 24, 2016 via email

@sjsf
Copy link

sjsf commented Dec 24, 2016

ETS 5

@kaikreuzer
Copy link
Member

ETS 3

@NorbertHD
Copy link

ETS 5

@aschamberger
Copy link

@sjsf
Copy link

sjsf commented Dec 24, 2016

no, please don't - it's GPL code. So unless the author agrees to contribute a version of that code under the EPL or the like, it's really not a good source to copy from...

@nibblerrick
Copy link

ETS 4. In my opinion KNX could be a bit more succeful if ETS wasn't so expensive, even the upgrade to ETS 5 is relatively pricey :-/

@teichsta
Copy link
Member

ETS 4

@curlyel
Copy link

curlyel commented Dec 25, 2016

ETS5

btw.: Thanks for the effort you've put into KNX2 binding @kgoderis. I'm looking forward to see it merged...

@ccellar
Copy link

ccellar commented Dec 26, 2016

ETS 4

@kgoderis
Copy link
Contributor Author

I did not know that https://github.com/tuxedo0801/KnxProjParser was out there, but then, I have not intention whatsoever to use or even look at the code. Well, I looked at it and do not like parts of it anyways ;-)

I probed knx.org to get the official XSD files but got no answer from them (anyone got them?). Since my own ETS5-driven installed base is rather large, I derived an XSD from the final XML files, and I hope they are rather complete. The XSD will serve as input for xjc (Java XML Binding Compiler). If someone wants to contribute a XSD for ETS4, pls feel free to do so (http://www.freeformatter.com/xsd-generator.html)
My objective is to have strict/clean code that can withstand any future evolution of the XSD, but also for once and for all take away the massive burden of configuring '000 of GA in OH2.

@lewie
Copy link

lewie commented Dec 27, 2016

ETS5

@yphyph01
Copy link

yphyph01 commented Dec 28, 2016

ETS4
For the XSD, I've got those description documents, but not the XSD files :

  • ETS4 XML Project Format Description
  • ETS5 XML Project Format Description

Let me know if that might help you, and if so, how could I send them you?
Regards

@sjsf
Copy link

sjsf commented May 19, 2017

I have been thinking over the last days about the troubles you have with the handleUpdate() method. Let me summarize in my words what I understood so far:

As a matter of fact, the KNX binding is the only binding that we have in openHAB which implements this method at all. All others don't need it. And that's for a good reason: This method only makes sense for use-cases where the state is not "owned" somewhere in any real device (e.g. a Hue lamp knows whether it is ON or OFF), but where the state resides on openHAB side (may it the item itself or openHAB acting as a proxy to another device outside in the KNX bus).

So, for ChannelTypes representing a KNX switch/dimmer/rollershutter actor it does not make sense to do anything in handleUpdate() at all. If we send a command to the KNX bus on one GA, the actor will reply with its state on another GA, and that answer will be used in the handler to update the channel's state. No cycle, all good.

The interesting part will be if e.g. a KNX wall-button should be included in openHAB. It sends "commands" via on GA, but also needs to know via another GA the current state of the GA in order to reflect this with its LED and send the correct ON/OFF command if pushed. A good example would be a Hue lamp which should be switched ON and OFF from a KNX wall-button. However, such a device only sends commands, it does not receive any. There is no handleCommand() for such a "read-only" device, it just need to be updated with whatever state the item has in handleUpdate(). And such a channel of course has to have a different ChannelType than e.g. a switch actor.

So I'm wondering: if we would define the ChannelTypes right, wouldn't this whole problem suddenly vanish? And in fact there correctly already is a "statusswitch" type. So we'd only need to use this information to decide whether the communication is meant to go one way or the other (in the sense of handleCommand() or handleUpdate()).

Or am I missing anything?

@kgoderis
Copy link
Contributor Author

kgoderis commented May 20, 2017

@SJKA Maybe the best thing to explain the problem is with an example. Assume I want to control a Shutter with a wall-mounted control panel. The shutter is defined as follows:

[This comes from the knxproj parsing]

knx:generic:ip1:1_2_122:3_3_11 (Type Contact, DPT 1.009, false/true/false/false, 'Bedroom Alexander - Shutter - Blinds Stop')
knx:generic:ip1:1_2_122:3_3_14 (Type Number, DPT 5.001, true/false/true/false, 'Bedroom Alexander - Shutter - Status Current Position')
knx:generic:ip1:1_2_122:3_3_13 (Type Number, DPT 5.001, false/true/false/false, 'Bedroom Alexander - Shutter - Absolute Position')
knx:generic:ip1:1_2_122:3_3_10 (Type Rollershutter, DPT 1.008, false/true/false/false, 'Bedroom Alexander - Shutter - Control')

In the runtime these channels are linked to an Item BedroomAlexanderShutterControl as follows:

openhab> smarthome:links | grep BedroomAlexanderShutterControl
BedroomAlexanderShutterControl -> knx:generic:ip1:1_2_122:3_3_13
BedroomAlexanderShutterControl -> knx:generic:ip1:1_2_122:3_3_14
BedroomAlexanderShutterControl -> knx:generic:ip1:1_2_122:3_3_11
BedroomAlexanderShutterControl -> knx:generic:ip1:1_2_122:3_3_10

The Item is defined as follows:

Rollershutter BedroomAlexanderShutterControl "Bedroom Alexander - Shutter - Control" <rollershutter> (gFirstFloor,gShutterBlinds,gControl,gBedroomAlexander) { channel="knx:generic:ip1:1_2_122:3_3_10", channel="knx:generic:ip1:1_2_122:3_3_11", channel="knx:generic:ip1:1_2_122:3_3_13", channel="knx:generic:ip1:1_2_122:3_3_14" }

I now control the shutter, e.g. I open it, via the wall mounted control panel, and what follows is a bunch of KNX Telegrams on the bus to Group Address 3/3/14 (e.g. the shutter is reporting the actual position)

In the logs you will thus have:

2017-05-20 08:22:55.860 [TRACE] [nx.handler.KNXBridgeBaseThingHandler] - Received a Group Write telegram from '1.2.122' for destination '3/3/14'
2017-05-20 08:23:09.590 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Thing knx:generic:ip1:1_2_122 received a Group Write telegram from '1.2.122' for destination '3/3/14'
2017-05-20 08:23:09.590 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Thing knx:generic:ip1:1_2_122 processes a Group Write telegram for destination '3/3/14' for channel 'knx:generic:ip1:1_2_122:3_3_14'

2017-05-20 08:23:09.683 [ItemStateChangedEvent ] - BedroomAlexanderShutterControl changed from 100 to 0

So, the binding is receiving the telegram on the appropriate Channel, and will process it. It does this by updating the BedroomAlexanderShutterControl Item.

In doing so, the Runtime will now issue a handleUpdate for all the other Channels linked to that Item. The other Channels, see above, include for example GA 3/3/13 to steer the shutter to a desired/defined position. We do not want that to happen, because the shutter is actually moving down, so, we definitely do not want the handleUpdate() to happen for those other Channels linked to the BedroomAlexanderShutterControl Item

I solve this by tracking the itemChannelLinkRegistry, and thus can do the following:

2017-05-20 08:23:09.591 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Adding channel 'knx:generic:ip1:1_2_122:3_3_10' of Item 'BedroomAlexanderShutterControl' the list of blocked channels
2017-05-20 08:23:09.591 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Adding channel 'knx:generic:ip1:1_2_122:3_3_13' of Item 'BedroomAlexanderShutterControl' the list of blocked channels
2017-05-20 08:23:09.591 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Adding channel 'knx:generic:ip1:1_2_122:3_3_11' of Item 'BedroomAlexanderShutterControl' the list of blocked channels
2017-05-20 08:23:09.683 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Handling a State (0) update for Channel 3_3_10
2017-05-20 08:23:09.683 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Removing channel 'knx:generic:ip1:1_2_122:3_3_10' from the list of blocked channels
2017-05-20 08:23:09.683 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Handling a State (0) update for Channel 3_3_13
2017-05-20 08:23:09.683 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Removing channel 'knx:generic:ip1:1_2_122:3_3_13' from the list of blocked channels
2017-05-20 08:23:09.683 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Handling a State (0) update for Channel 3_3_11
2017-05-20 08:23:09.683 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Removing channel 'knx:generic:ip1:1_2_122:3_3_11' from the list of blocked channels

What are the solutions at hand?

  1. Group GAs together in functional blocks, e.g. ThingHandler with a specific functionality, like a ShutterKNXThingHandler, that can effectively "interpret" that is has GA's for moving up, reporting status, etc, and thus control the flow of Updates. The first version of the binding was built like this, but voted down. I had to stick to the "GA" as building block, and it was up to the user to bind them together in the 'user land' space (e.g. Rule engine). Note that this solution goes against the automatic knxproj parsing approach

  2. The "Auto-bridge binding" (see link elsewhere). With that binding I could block the "bridging" of Updates at the Item level definition. It was equally voted down

  3. Track KNX Telegrams over time, but that failed big time because of timing issues and so forth (That was all the "Log" code you removed in your review of the PR)

  4. track as I do today, with the itemChannelLinkRegistry.

  5. .... ?

To me the binding has to have a handleUpdate(). I see the KNX bus not as a "real" bus, bus rather as a kind of shared memory between actors. they all use a memory address, i.e. GA, to write and read from that memory. So, you not only have to send commands, but also have to deal with other actors writing to that memory, and listen for changes on GA's you are interested in. What you describe (actor sending a command, and a status update coming via an another GA) is not necessarily true: it all depends on the actual configuration of the actor, there is no obligation whatsoever to respond with a status update. Also, status updates are sent without a previous command send by someone else, it is just a device reporting a state at given interval or conditions. So, handleCommand is for stuff coming from the ESH runtime, handleUpdate is for stuff coming from the bus, but unfortunately also from the ThingManager doing the bridging between Channels linked to an Item

@kgoderis
Copy link
Contributor Author

@SJKA Just to follow up on this, consider the example you give with the HUE lamp. Well, in a scenario whereby the ThingManager would be the disc jokey of handleUpdate() and handleCommands(), using solution 2. (the Auto-bridge binding) you would instruct the ThingHandler to allow "inter"-binding bridging of updates, but disallow "intra"-binding updates. That way the HUE sourced updates can be passed on to the KNX-binding linked Channels, but updates between KNX-linked Channels would not be allowed.

@kgoderis
Copy link
Contributor Author

kgoderis commented May 20, 2017

However, such a device only sends commands, it does not receive any. There is no handleCommand() for such a "read-only" device, it just need to be updated with whatever state the item has in handleUpdate().

Ah... well, there is no distinction between commands and updates on the KNX bus.

It boils down to his question : "where do you want to place the functional KNX logic in the runtime"?
(functional logic, as in "a wall button", a roller shutter,...)

  1. In Rules? The user has to define individual Items (each linked to a single KNX-bound Channel), representing each some functionality (a switch, a switch status,...) and binds them together with Rules to "emulate" a roller shutter
  2. In Items? No really possible as such, but here you can control "bridging" (see above, with an AutoBridge binding, or by using the itemChannelLinkRegistry in the Handler). You put the Channels together in an Item, but you control how the updates are passed on between them
  3. In Channels (thus the binding itself)? Very much possible, that means end up with a RollerShutterKNXThing and alike. And the "GAThing" to represent "orphan" GA's that are not intertwined with other GA's that together make up an RollerShutter or alike

And such a channel of course has to have a different ChannelType than e.g. a switch actor.

Why would that be? In the current implementation, a GA is a GA, they are all the same type. Am I right if you in fact suggest to define different ChannelTypes based on the actual configuration/characteristics of the GA, whether that is defined in the DSL or coming from the knxproj parsing? IMHO that will be messy, because part of that is configurable in ETS. For example, you can easily change the Read/Write/Transmit/Update flags of a GA in an actor.

@sjsf
Copy link

sjsf commented May 22, 2017

Ah... well, there is no distinction between commands and updates on the KNX bus.

I see the KNX bus not as a "real" bus, bus rather as a kind of shared memory between actors. they all use a memory address, i.e. GA, to write and read from that memory. So, you not only have to send commands, but also have to deal with other actors writing to that memory, and listen for changes on GA's you are interested in.

Yes, I'm very well aware of how it works. Still, the whole problem arises only because you are trying to model the way how KNX works into ESH directly in a generic fashion. As I said, I do not understand the need for it and I also don't think it is the right approach.

Group GAs together in functional blocks, e.g. ThingHandler with a specific functionality, like a ShutterKNXThingHandler, that can effectively "interpret" that is has GA's for moving up, reporting status, etc, and thus control the flow of Updates.

No, that was exactly my point: When you are using such a functional abstraction, there is no need for parsing or guessing. You already know by the functional type if the state/logic is kept on KNX side, no need for handleUpdate() or the other way round for state/logic kept elsewhere. You can tell from the ChannelType.

To me the binding has to have a handleUpdate().

You didn't read my comment closely. I agree with you, it needs to have it. But it does not make sense to to anything in there all of the time. It's actually rather an exceptional case where this really is needed.

The first version of the binding was built like this, but voted down.

Really? Where? AFAIR, the suggestion was about moving the semantic level from ThingTypes down to ChannelTypes (see https://github.com/openhab/openhab2-addons/pull/21#issuecomment-255045806), not to drop the functional level completely!

And btw. that's what you still have here in generic.xml and what you also decribed as the main purpose of this PR. So why not use this information?

I had to stick to the "GA" as building block and it was up to the user to bind them together in the 'user land' space (e.g. Rule engine).

I really can't find it. Who suggested that?

Note that this solution goes against the automatic knxproj parsing approach

Yes. As you said yourself, the ETS file format is very limited wrt figuring out the functional abstraction. And I agree, this sucks.

However, as much as I like the idea - and I have to admit that you got pretty far with it despite of its limitations - I'd rather sacrifice the knxproj parsing than implementing a special mechanism in the framework which allows the KNX binding to guess a little better but still it's only a guess.

What you describe (actor sending a command, and a status update coming via an another GA) is not necessarily true: it all depends on the actual configuration of the actor, there is no obligation whatsoever to respond with a status update.

If I configure my actor to not have a status GA, then my wall-buttons won't light up. Same with openHAB: If I don't have a status GA, then I can't use the current value from the bus. What is your point? No doubt, you will always be able to abuse the system to a limit that it does not interact well with its environment anymore. But do you have a common use-case example where this is required?

Signed-off-by: Karel Goderis <karel.goderis@me.com>
@lewie
Copy link

lewie commented May 22, 2017

@SJKA,

If we send a command to the KNX bus on one GA, the actor will reply with its state on another GA, and that answer will be used in the handler to update the channel's state. No cycle, all good.

That is not in general! Older (switch-)actors in general and some modern simple actors use Main-GA as Status-GA. Then the Main-GA then is set to write AND read.

The interesting part will be if e.g. a KNX wall-button should be included in openHAB. It sends "commands" via on GA, but also needs to know via another GA the current state of the GA in order to reflect this with its LED and send the correct ON/OFF command if pushed. A good example would be a Hue lamp which should be switched ON and OFF from a KNX wall-button. However, such a device only sends commands, it does not receive any. There is no handleCommand() for such a "read-only" device, it just need to be updated with whatever state the item has in handleUpdate(). And such a channel of course has to have a different ChannelType than e.g. a switch actor.

KNX wall-button can switch its LED by its Main-GA as Status-GA too, it does not necessarily need two GAs.

If I configure my actor to not have a status GA, then my wall-buttons won't light up. Same with openHAB: If I don't have a status GA, then I can't use the current value from the bus. What is your point? No doubt, you will always be able to abuse the system to a limit that it does not interact well with its environment anymore. But do you have a common use-case example where this is required?

No, this is not abuse the system to a limit. As described above, many Actors need/must not handle status-GA!
So in KNX1 simplest way to send and receive is knx="1/1/10" Main-GA is status-GA too. Of course, this must match the KNX configuration.

Some times it can help to have a look back, where most parts work very well in KNX1.
Which you are currently discussing, (mostly) is quite well represented in KNX1:

Switch:

knx="1/1/10"
knx="1.001:1/1/10"
knx="<1/1/10"
knx="<(5)1/1/10"
knx="<1/1/10+0/1/13+0/1/14+0/1/15"
knx="<(10)1/1/10+0/1/13+0/1/14+0/1/15"
knx="1/1/10+<0/1/13+0/1/14+0/1/15"
knx="1/1/10+<(60)0/1/13+0/1/14+0/1/15"

RollershutterItem or Dimmer:

knx="4/2/10"
knx="4/2/10, 4/2/11"
knx="4/2/10, 4/2/11, 4/2/12"
knx="1.008:4/2/10, 5.001:4/2/11"
knx="<4/2/10+0/2/10, 5.001:4/2/11+0/2/11"
knx="<(60)4/2/10+0/2/10, 5.001:4/2/11+0/2/11"

General:

knx="
[<[(<autoRefresh>)]][<dptId>:]<mainGA>[[+[<[(<autoRefresh>)]]<listeningGA>]+[<[(<autoRefresh>)]]<listeningGA>..], 
[<[(<autoRefresh>)]][<dptId>:]<mainGA>[[+[<[(<autoRefresh>)]]<listeningGA>]+[<[(<autoRefresh>)]]<listeningGA>..]
"

The underlying logic, should be easily transferred into Things / Channels?

Just as a stimulus! ;-)

@lewie
Copy link

lewie commented May 22, 2017

@kgoderis, @SJKA,

Does the current Source (knx-refactor) work with current Nightly Build 2.1.0?

I have compiled KNX2 Binding source from knx-refactor branch with current openhab:master well.

It connects and no Error or Warning is shown. It does not discover. It finds knxproj, but does not parse.

I can send from KNX-Bus but it ends at:

.KNXBridgeBaseThingHandler] - Received a Group Write Request telegram from '10.1.2' for destination '1/1/11'

I can switch Channel over Paper UI - Control, but nothing is sent to KNX-Bus:

18:57:07.641 [INFO ] [smarthome.event.ItemCommandEvent    ] - Item 'GenericKNXActor_Switch' received command OFF
18:57:07.642 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Handling a Command (OFF)  for Channel someSwitch
18:57:07.642 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Command to Channel someSwitch null Switch true/null : OFF -> OFF
18:57:07.642 [TRACE] [g.knx.handler.KNXGenericThingHandler] - Handling a State (OFF) update for Channel someSwitch
18:57:07.642 [TRACE] [g.knx.handler.KNXGenericThingHandler] - State to Channel someSwitch null Switch true/null : OFF -> OFF
18:57:07.643 [INFO ] [marthome.event.ItemStateChangedEvent] - GenericKNXActor_Switch changed from ON to OFF

In my despair, I tried to compile the branch directly, with very old Sources with hole knx-refactor branch (This branch is 3 commits ahead, 573 commits behind openhab:master. ). As expected, this does not work.

How do you get a working Binding at the moment.

@kgoderis, it would help, if you can copy/update your knx-refactor branch to openhab:master. knx-refactor branch is no longer compatible with current KNX2 source (Java 8)!

@kgoderis
Copy link
Contributor Author

No, that was exactly my point: When you are using such a functional abstraction, there is no need for parsing or guessing. You already know by the functional type if the state/logic is kept on KNX side, no need for handleUpdate() or the other way round for state/logic kept elsewhere. You can tell from the ChannelType.

Maybe we are talking sideways here, but how would you deal with Updates sent by an Actor on the KNX bus? I am not sure I understand what you try to explain here

Really? Where? AFAIR, the suggestion was about moving the semantic level from ThingTypes down to ChannelTypes (see #21 (comment)), not to drop the functional level completely!

https://github.com/openhab/openhab2-addons/pull/21#issuecomment-257531370

Btw, as is stands today, it is still at the Channel level except that there is a 1-GA-1-Channel idiom because of the knxproj parsing.

I really can't find it. Who suggested that?

I think It was something discussed with Kai over email, but that is a long, very long time ago.

And btw. that's what you still have here in generic.xml and what you also decribed as the main purpose of this PR. So why not use this information?

Because I wanted to go for the automatic parsing route

I'd rather sacrifice the knxproj parsing than implementing a special mechanism in the framework which allows the KNX binding to guess a little better but still it's only a guess.

This is a big no-no for me. I can see the benefits of this in a small installation, but with now >130 actors and close to 3000 GA's in production at my home, this is impossible. Keeping the KNX binding configuration in sync with what is in ETS5 is a big nightmare.

In reverse, I see a lot of benefits of being able to read-out knxproj files (no more configuration copy-over mistakes for one), and therefore the fact you have to let the binding use the itemChannelLinkRegistry (because that's all there is to it) is a minor thing.

Yes it is guessing to some extend, but one can overcome some of the errors through a definition via the DSL

If I configure my actor to not have a status GA, then my wall-buttons won't light up.

I am afraid this does not hold true 100%. Many actors, not only wall-buttons, do not have a loop-back status GA mechanism.

What other solutions are there?

@kgoderis
Copy link
Contributor Author

Does the current Source (knx-refactor) work with current Nightly Build 2.1.0?

I have no idea - I tested the current source in Eclipse and it ran perfectly.

@kgoderis, it would help, if you can copy/update your knx-refactor branch to openhab:master. knx-refactor branch is no longer compatible with current KNX2 source (Java 8)!

Not sure I understood this. I though the complete openhab:master went Java 8?

@kgoderis
Copy link
Contributor Author

@SJKA I grabbed an older configuration file of mine, based on the generic.xml syntax, and in that one I have about 400 "multi-GA" Channels, and about 1200 single-GA channels. Even as you can solve the "circular Telegram" problem by grouping GA's in Channels, still, in the real world, there are bunch of "orphan" Channels that will be linked to Items as well, and thus you can not except the problem to go away automagically. Unless it is accepted that users misconfigure the whole binding, of course.

@kgoderis
Copy link
Contributor Author

@SJKA Simon, after some thinking I came to the conclusion that we suffer badly from a lack of context with respect to configuring KNX in ESH/OH, and there is nothing we can do about it. It is not in the ETS configuration files, the only place it basically resides is in the mind of the user. Getting that out of that mind will require manual coding/configuration, it can not be done automatically. That configuration can be done at the level of .items or .things (hence the discussion were are currently in). For me the logical place to do that is the .things context, as you suggest and already implemented, but it will still enable faulty configurations.

However, I think we still can have good use of the information stored in the ETS configuration files. We could think of a kind of KNXConfigurationProvider API that can basically provide information on Individual Addresses and Group Addresses. For GA's, it can provide the DPT, and the R/W/T/U flags. This configuration is now explicitly set through the ChannelType, and provided by means of the DSL configuration and KNXChannelSelectorProxy class.

In fact, the big issue in configuring KNX in ESH comes from the need to back and forward to ETS to get specific kind of information, like DPT, flags, descriptions and so forth. Secondly, there is currently no easy way to multiply/copy&paste configurations in OH, e.g. configuring similar rooms in the house. Lastly, we what we have at hand, we do not solve the inherent problem of related GA's that are not of the same ItemType or can not be linked. For example, you can have a presence sensor that reports back a Start of Motion, Stop of Motion and Status. This are in fact 3 separate Channels, you can not put them together in a logical block, e.g. 1 Channel with 3 defined GAs. For example, switches with energy read-outs: the switch is a Channel of given Type, but the energy part is another Type, yet, ideally one would like to have these GA's in a logical block. (The only solution is at the Thing level in this case, but that is not the route we want to go). This will lead to a massive amount of 1-GA-1-Channel channels, and thus a lot of administration.

@lewie
Copy link

lewie commented May 22, 2017

@kgoderis

I have no idea - I tested the current source in Eclipse and it ran perfectly.

I'm right you have imported KNX2 Project from your knx-refactor to eclipse. In your eclipse, there resists the current openhab:master? Then you can run in eclipse, this works in mine too.

To get a knx2.jar and for using code-analysis, I have copied knx2 Project manually into openhab:master Branch folders an ran mvn clean install in KNX2 folder. This compiles perfect, but the jar does not run perfect in a openHAB 2.1.0 karaf nightly Installation (no IDE). I'm wondering why it runs in Eclipse but not karaf. hmmm...

Not sure I understood this. I though the complete openhab:master went Java 8?

Your current Brunch knx-refactor is not compatible to java 8 , because it is too old:
This branch is 3 commits ahead, 574 commits behind openhab:master.

The only Source which is java 8 compatible in Banch knx-refactor is the source of KNX2. There I told you: https://github.com/openhab/openhab2-addons/pull/1438#issuecomment-302376240

As I can see you only testing in IDE. So it os on me, I will try to find the reason why in karaf it is not working...

@lewie
Copy link

lewie commented May 22, 2017

In fact, the big issue in configuring KNX in ESH comes from the need to back and forward to ETS to get specific kind of information, like DPT, flags, descriptions and so forth. Secondly, there is currently no easy way to multiply/copy&paste configurations in OH, e.g. configuring similar rooms in the house. Lastly, we what we have at hand, we do not solve the inherent problem of related GA's that are not of the same ItemType or can not be linked. For example, you can have a presence sensor that reports back a Start of Motion, Stop of Motion and Status. This are in fact 3 separate Channels, you can not put them together in a logical block, e.g. 1 Channel with 3 defined GAs.

Why 3 Channels?
From KNX-Bus we get the changing "Motion Status" and if we want to write to "Start/Stop of Motion" is sent.

    Thing generic presence{
        Channels:
            Type sensor : room1 [ mainGA="Start/Stop of Motion", listeningGAs=["Motion Status"] ]
            Type sensor : room2 [ mainGA="1/2/3", listeningGAs=["4/5/6"] ]
            ...
     }

For example, switches with energy read-outs: the switch is a Channel of given Type, but the energy part is another Type, yet, ideally one would like to have these GA's in a logical block. (The only solution is at the Thing level in this case, but that is not the route we want to go). This will lead to a massive amount of 1-GA-1-Channel channels, and thus a lot of administration.

Why not shorthand?
The connection is clearly and there is less redundant text.

    Thing generic energyswitch1{
        Channels:
            Type switch : a [ mainGA="1/1/1", listeningGAs=["1/1/2"] ] : energyGAs=["1/1/3"]
            : blockGAs=["1/1/4"] 
     }

same as:

    Thing generic energyswitch1{
        Channels:
            Type switch : a [ mainGA="1/1/1", listeningGAs=["1/1/2"] ] 
            Type energy: a [energyGAs=["1/1/3"] ]
            Type block: a [blockGAs=["1/1/4"] ]
     }

What about Simons (same Kai) Examples:
I do not understand why we need 1-GA-1-Channel? upDownGA, percentGA and moveStopGA are different dataTypes and different GAs?!
We can only put GAs in a channel, which also have a direct relationship to each other - which can be visualized/operated together. A switch Main-GA and its energy-GA can not.

Bridge knx:ip:1 [ ipAddress="192.168.0.123" ] {

    Thing generic actuator1 {
        Channels:
            Type switch : a [ mainGA="1/2/3", listeningGAs=["4/5/6", "7/8/9"] ]
            Type switch : b [ mainGA="2/2/2", listeningGAs=["4/0/0", "7/2/3"] ]
            Type switch : c [ mainGA="3/3/3", listeningGAs=["5/0/0", "7/1/5"] ]
            ...
            ]
        }

    Thing generic actuator2 {
        Channels:
            Type rollershutter : a [ upDownGA="1/2/3", percentGA="4/5/6", moveStopGA="7/8/9" ]
            Type rollershutter : b [ upDownGA="0/2/3", percentGA="1/5/6", moveStopGA="5/8/9" ]
            Type rollershutter : c [ upDownGA="2/2/3", percentGA="2/5/6", moveStopGA="6/8/9" ]
            ...
            ]
        }
}

@kgoderis
Copy link
Contributor Author

Not sure I understood this. I though the complete openhab:master went Java 8?
Your current Brunch knx-refactor is not compatible to java 8 , because it is too old:
This branch is 3 commits ahead, 574 commits behind openhab:master.

The only Source which is java 8 compatible in Banch knx-refactor is the source of KNX2. There I told you: #1438 (comment)

As I can see you only testing in IDE. So it os on me, I will try to find the reason why in karaf it is not working...

Ok in that sense. What I have to do is rebase it against the HEAD of the master, but then we will potentially loose the discussions/comments here above. I did that with another binding this week, and Jenkins screwed up after the forced push.

@kgoderis
Copy link
Contributor Author

kgoderis commented May 23, 2017

sensor that reports back a Start of Motion, Stop of Motion and Status. This are in fact 3 separate Channels, you can not put them together in a logical block, e.g. 1 Channel with 3 defined GAs.
Why 3 Channels?
From KNX-Bus we get the changing "Motion Status" and if we want to write to "Start/Stop of Motion" is sent.

Because a "motion start" is not the same kind of information as a "status". It was just an example, not all GA's of an actor lead to the same kind of information, even if their Type happens to be same. Alternative example: A roller shutter can have GA's for the position, but also boolean indicators to indicate if it is in the uppermost position for example

@kgoderis
Copy link
Contributor Author

Why not shorthand?
The connection is clearly and there is less redundant text.

Thing generic energyswitch1{
    Channels:
        Type switch : a [ mainGA="1/1/1", listeningGAs=["1/1/2"] ] : energyGAs=["1/1/3"]
        : blockGAs=["1/1/4"] 
 }

What Type will be used to update the Channel? a Switch (the switch) or a Number (the energy read out in Wh)? AFAIK a Channel is only associated with a single Type.

@kgoderis
Copy link
Contributor Author

I do not understand why we need 1-GA-1-Channel? upDownGA, percentGA and moveStopGA are different dataTypes and different GAs?!

Maybe I was not clear. Even as you can group together GA's as you describe, next to that you will still have a large amount of GA's that cannot be coupled together, and thus, these ones are mapped on a single Channel so to speak

@lewie
Copy link

lewie commented May 23, 2017

Ok in that sense. What I have to do is rebase it against the HEAD of the master, but then we will potentially loose the discussions/comments here above. I did that with another binding this week, and Jenkins screwed up after the forced push.

OH NO!!!!! 👎

EDIT: There is a way you add a new Branch maybe "knx-refactor-java8" and push from local the whole openhab:master including current org.openhab.binding.knx folder from knx-refactor. So I did first Time with my changes for generic mapper and code-analysis. Did it with eclipse directly from current openhab:master to my github fork: Team->Remote->Push... there you can Push to another repository and what branch.

@lewie
Copy link

lewie commented May 23, 2017

What Type will be used to update the Channel? a Switch (the switch) or a Number (the energy read out in Wh)? AFAIK a Channel is only associated with a single Type.

OK, thats right! We are back at:

    Thing generic energyswitch1{
        Channels:
            Type switch : a [ mainGA="1/1/1", listeningGAs=["1/1/2"] ] 
            Type energy: a [energyGAs=["1/1/3"] ]
            Type block: a [blockGAs=["1/1/4"] ]
     }

To show the togetherness, what about this:

    Thing generic energyswitch1{
        Channels:
            Type multi: a [
                switch [mainGA="1/1/1", listeningGAs=["1/1/2"]],
                energy [energyGAs=["1/1/3"]], 
                block [blockGAs=["1/1/4"]]
             ]
            Type switch : b [ mainGA="2/1/1", listeningGAs=["2/1/2"] ] 
            Type switch : c [ mainGA="3/1/1", listeningGAs=["3/1/2"] ] 
     }

@kgoderis
Copy link
Contributor Author

Type multi: a [
switch [mainGA="1/1/1", listeningGAs=["1/1/2"]],
energy [energyGAs=["1/1/3"]],
block [blockGAs=["1/1/4"]]
]

IMO the problem is still the same. What kind of Types will you push on this Channel? What ItemType will this channel be linked to?

@lewie
Copy link

lewie commented May 23, 2017

IMO the problem is still the same. What kind of Types will you push on this Channel? What ItemType will this channel be linked to?

Naive as I am, I would make three channels out of it:
a-switch -> Switch
a-energy -> Number
a-block -> Switch

It's about naming, right. We need unique names like a,b,c.

    Thing generic energyswitch1{
        Channels:
            Type switch : a [ mainGA="1/1/1", listeningGAs=["1/1/2"] ] 
            Type energy: b [energyGAs=["1/1/3"] ]
            Type block: c [blockGAs=["1/1/4"] ]
     }

I can live with it!

@kgoderis
Copy link
Contributor Author

@kgoderis kgoderis closed this May 23, 2017
@wborn wborn added the knx label Feb 25, 2019
@kgoderis kgoderis deleted the knx-refactor branch October 29, 2020 16:37
markus7017 pushed a commit to markus7017/openhab-addons that referenced this pull request Aug 12, 2023
* Bulk edit linter errors.

Signed-off-by: Jerome Luckenbach <github@luckenba.ch>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new binding If someone has started to work on a binding. For a new binding PR.
Projects
None yet
Development

Successfully merging this pull request may close these issues.