calimero-project / calimero-core

Core library for KNX network access and management
Other
132 stars 65 forks source link

How to do a PropertyRead (or even MemWrite)? (not necessarily knc spec conform) #22

Closed tuxedo0801 closed 9 years ago

tuxedo0801 commented 9 years ago

Hi there,

I'm trying to extend an existing KNX Library for arduino microcontroller to be able to read/write properties and read/write memory on the arduino, using KNX and Calimero.

One thing upfront: Is must not necessarily be knx-spec-conform. The important point is: it must just work somehow ;-)

So, what I'm trying to do is the following:

Using calimero with a KNX IP Router to read property of arduino knx device with individual address 1.1.251.

public static void main(String[] args) throws KNXException, InterruptedException, UnknownHostException {
        InetAddress hostadr = InetAddress.getByName("224.0.23.12");
        int port = 3671;

        // setup knx connection
        KNXNetworkLinkIP netlink = new KNXNetworkLinkIP(KNXNetworkLinkIP.ROUTING, null, new InetSocketAddress(hostadr, port), false, new TPSettings(false));
        netlink.getKNXMedium().setDeviceAddress(new IndividualAddress("1.1.250"));

        ManagementProceduresImpl mp = new ManagementProceduresImpl(netlink);
        ManagementClientImpl mci = new ManagementClientImpl(netlink);

        IndividualAddress device = new IndividualAddress("1.1.251");

        byte[] readProperty = mci.readProperty(mci.createDestination(device, true), 0 /* obj-index */, 56 /* prop-id */, 1 /* start */, 1 /* elements*/);

        System.out.println("prop: "+Integer.toHexString(readProperty[0]));

    }

I already setup the arduino so that it responds with a propertyread-answer. But calimero does not recognise it. So i stepped through the calimero code to get an idea what is missing from arduino code and stumbled on something I don't understand:

bildschirmfoto - 15 06 2015 - 22 36 49

While the main-thread is busy with sending the T_CONNECT_REQ_PDU (which, according to calimero code does not need to be ack'ed due to routing mode), the connect from calimero 1.1.250 to arduino 1.1.251 is received on calimero on "Link notifier" thread, which does send's a "disconnect" to the source, means: Calimero sends a disconnect three times to itself ...???!

bildschirmfoto - 15 06 2015 - 22 40 01

... while arduino is answering the propertyread-request.

So my questions are:

1) Why is calimero doing a disconnect? In source (2.2.1-beta) TransportLayerImpl I found at line 478:

                // don't allow (client side)
                if (d.getState() == Destination.DISCONNECTED)
                    sendDisconnect(sender);

What does this mean? Dont allow client side? Is this a kind of bug?

2) Is calimero able to do property read/write as well as mem read/write in such a way (without too much code-overhead)? 3) If question nr.2 can be answered with "yes": How should a really simple code-snippet look like? Keep in mind: Is must not be knx-spec-conform. It's sufficient if the arduino can be programmed with a special-calimero-powered-tool. 4) If question nr.2 can be answered with "yes": Do you already see something missing from arduino side? any ACK message? Something else? Or do you see something else that hinders calimero from getting the response properly? It's really hard for me to get through all the documents and find the correct workflow... So I appreciate any hint on what else is missing.

br, Alex

tuxedo0801 commented 9 years ago

I further analyzed the situation:

The problem with "don't allow (client side)" in TransportLayerImpl:

Now I understand what this means: Calimero can of course act as a server (--> receive connection requests and handle them) and as client (send connection request). What I don't understand: Why is there no check for the target-address? Why does calimero handle its connection request? Would it not make sense to filter for the target-address: If calimero uses 1.1.250 and wants to connect to 1.1.251... then it should not react on it's own connect-telegram (if sender == me then ignore) ?!!

bmalinowsky commented 9 years ago

IIRC, the cause of this is the multicast behaviour of the local interface. In KNXnetIPRouting, see the call to init in the constructor. Currently multicast loopback is requested (true). You can check it with usesMulticastLoopback(). Disabling the loopback should provide the expected behaviour, leaving it disabled is a better default anyway.

tuxedo0801 commented 9 years ago

So for now I subclass KNXnetIPRouting and disable multicastloopback by myself and you will change the default behavior with a code-change?

tuxedo0801 commented 9 years ago

Subclassing of KNXnetIPRouting is not a problem. But I usually use KNXNetworkLinkIP and parametrize it and then internally KNXnetIPRouting is choosen.

If I have to use my own KNXnetIPRouting-class, how to I use it right?

[update] Disabling loopbackmode via reflection works :-)

        InetAddress hostadr = InetAddress.getByName("224.0.23.12");
        int port = 3671;

        // setup knx connection
        KNXNetworkLinkIP netlink = new KNXNetworkLinkIP(KNXNetworkLinkIP.ROUTING, null, new InetSocketAddress(hostadr, port), false, new TPSettings(false));

        try {
            Field connField = KNXNetworkLinkIP.class.getDeclaredField("conn");
            connField.setAccessible(true);

            KNXnetIPConnection conn = (KNXnetIPConnection) connField.get(netlink);

            if (conn instanceof KNXnetIPRouting) {
                KNXnetIPRouting knxnetiprouting = (KNXnetIPRouting) conn;
                Field socketField = KNXnetIPRouting.class.getSuperclass().getDeclaredField("socket");
                socketField.setAccessible(true);
                MulticastSocket socket = (MulticastSocket) socketField.get(knxnetiprouting);
                socket.setLoopbackMode(true);

                System.out.println("loopback enabled: "+knxnetiprouting.usesMulticastLoopback());
            }

        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException | SocketException ex) {
            ex.printStackTrace();
        }
        netlink.getKNXMedium().setDeviceAddress(new IndividualAddress("1.1.250"));
        IndividualAddress remote = new IndividualAddress("1.1.14");

        RemotePropertyServiceAdapter rpsa = new RemotePropertyServiceAdapter(netlink, remote, null, true);
        PropertyClient pc = new PropertyClient(rpsa);

        byte[] property = pc.getProperty(0 /* obj-index */, 56 /* prop-id */, 1 /* start */, 1 /* elements*/);

        System.out.println(Arrays.toString(property));

I see the output "loopback enabled: false" and busmonitor does no longer show wron disconnects.

reflectionworks

But it would be better to have a clean way to solve this. Either change default-behavior, or make it possible to set the flag at init (or at least after init with a setter or so).

From my current understanding subclassing KNXnetIPRouting would also need to subclass/copy KNXNetworkLinkIP. Or is there a way around (without reflection), without wrinting lots of code?

bmalinowsky commented 9 years ago

Good to hear it works! I was guessing based on the behaviour you observed, but didn't know for sure ;) A "clean" way (not clean from a networking perspective, but high-level/user-space perspective) is currently only done in the server part (calimero-server), similar in eibd. It involves datagram filter lists if loopback on the local outgoing interface is requested.

I actually don't remember why I set the loopback as is, without filtering (except the dumb justification that I needed it back then :/ ). In general, my prioritized way to go for least code change would be: a) if you compile everything from source, change the init argument b) subclass routing if you don't need network links c) extend the KNX network link for routing (not much change, but requires a copy) d) stay with your solution for now e) I have long-waiting commits for the embd8 branch which use an abstract network link, which allows that kind of stuff. Because initialization is an issue as you showed, I will back-port those to master.

For the stable (2.2.1) of the current beta, I will simply disable the loopback, which should work without problems for most things.

tuxedo0801 commented 9 years ago

Thanks for the feedback. Solution e) would be the best. b+c would be too much I guess, so i went for extending KNXNetworkLInkIP and adding my reflection-solution to override the default after init.

So part one of my problem is solved: unwanted disconnects. Part two of my problem (Calimero does not recognize the property-read-answer from arduino) might still exist. I have to check when I'm back home. Maybe the unwanted disconnects "disturbed" the receiveing of the answer (state change of destination or so). I will check and report back about the new situation.

tuxedo0801 commented 9 years ago

Last night I was able to properly answer propertyread-requests send from calimero with my arduino code. It was just missing a Transport-Data-Ack-PDU after the propread-request ...

Now it seems to work fine.

Is there an estimation when 2.2.1 stable will be available?

calimero-project commented 9 years ago

v2.2.1 stable is available. Multicast loopback on client-side is disabled by default.