bobjacobsen / python-openlcb

MIT License
2 stars 1 forks source link

example_node_implementation gives nodes in no particular order (Discuss ways to map IP address or hostname to LCC ID) #29

Closed Poikilos closed 7 months ago

Poikilos commented 7 months ago

@balazsracz Some type of auto-discovery is useful. The user often will not know the IP address of an LCC device. If I want to list nodes, I would like to be able to know which Node ID responded. I understand that I can know the Node ID by looking at the device, but I would like to know which one responded. For example, if my program connects 3 boosters, they will all list all boosters and the Command Station. The list is in no particular order, and the sourceID is the particular device not the one to which I connected. How can I know the ID of the IP address to which I connected? If you could update the example_node_implementation and show something like print("Detected farNodeID for {} is {}".format(host, detectedFarNodeID)) that would be very helpful as I can't readily see how to determine this.

Clarifications:

@jeff-tcs is helping with code that will utilize the Python module. For now we are trying to rework the examples and achieve the goals on the command line.

bobjacobsen commented 7 months ago

I can certainly add a printout of the NodeID of all the connected node(s) to example_node_implementation.py. PR to follow shortly.

Not sure what you mean about the host ID though. The example connects to a single, known IP address or host name. Do I understand correctly that you want to (try to) connect to every device on the subnet and see if that's an OpenLCB/LCC node? The usual approach is that the nodes (i.e. JMRI) connect to a hub; they won't respond to an attempt to connect to them. There's usually only one hub. Or is this how you want to locate the hub? It might be better to use mDNS to locate the hub's address.

bobjacobsen commented 7 months ago

See PR #29 for the changes to prompt for and print node IDs from attached nodes.

Poikilos commented 7 months ago

See PR https://github.com/bobjacobsen/PythonOlcbNode/issues/29 for the changes to prompt for and print node IDs from attached nodes.

I believe you mean PR #30.

I can certainly add a printout of the NodeID of all the connected node(s) to example_node_implementation.py. PR to follow shortly.

Not sure what you mean about the host ID though. The example connects to a single, known IP address or host name.

We want to get the ID of the hostname or IP, or vise versa (ask the LCC network for the IP address of a node), whichever is possible. Either way, we will then be able to connect an IP address to an ID within our software such as in a Python dictionary.

Do I understand correctly that you want to (try to) connect to every device on the subnet and see if that's an OpenLCB/LCC node?

Yes

The usual approach is that the nodes (i.e. JMRI) connect to a hub; they won't respond to an attempt to connect to them. There's usually only one hub.

Whether we use the packet above or use the code in the PR, all of the IDs come back, even if we connect to an IP address of a battery-powered throttle. Therefore, we still do not know the IP address if either the hub or the throttle, because both return the same list of IDs (In JMRI, the user has to know the IP address already, and we are trying to detect that)

Or is this how you want to locate the hub? It might be better to use mDNS to locate the hub's address.

Correct, searching individual IPs is not ideal, but whether we initiate a connection to one device or use multicast, we still want to know the IP address of the hub (such as a Command Station). The Command Station is not always the router, so mDNS doesn't seem to be the only thing we need. Correct? If the hub is not the ethernet/WiFi router, it isn't the DHCP server.

@joseph-rue says @balazsracz may be able to answer:

atanisoft commented 7 months ago

We want to get the ID of the hostname or IP, or vise versa (ask the LCC network for the IP address of a node), whichever is possible.

This would need to be exposed by the target node(s) in a uniform manner. It likely is not feasible today as there is no definition of where node(s) should expose this.

bobjacobsen commented 7 months ago

We want to get the ID of the hostname or IP, or vise versa (ask the LCC network for the IP address of a node), whichever is possible. Either way, we will then be able to connect an IP address to an ID within our software such as in a Python dictionary.

But why? What are you going to do with that information?

Once you have the connection to the hub, you can use LCC to talk to any attached device. Why do you also need their IP address?

Maybe we can think of another way of doing what you're trying to do.

Correct, searching individual IPs is not ideal, but whether we initiate a connection to one device or use multicast, we still want to know the IP address of the hub (such as a Command Station). The Command Station is not always the router, so mDNS doesn't seem to be the only thing we need. Correct? If the hub is not the ethernet/WiFi router, it isn't the DHCP server.

mDNS will give you what you need to connect to any LCC hub that's advertising itself. Both the CS-105 and the JMRI hub (and perhaps others) advertise themselves as the "openlcb-can" service with all the info you need to connect.

bobjacobsen commented 7 months ago

This will get you information on all the available hubs:

from zeroconf import ServiceBrowser, ServiceListener, Zeroconf

class MyListener(ServiceListener):

    def update_service(self, zc: Zeroconf, type_: str, name: str) -> None:
        print(f"Service {name} updated")

    def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:
        print(f"Service {name} removed")

    def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
        info = zc.get_service_info(type_, name)
        print(f"Service {name} added, service info: {info}")

zeroconf = Zeroconf()
listener = MyListener()
browser = ServiceBrowser(zeroconf, "_openlcb-can._tcp.local.", listener)
try:
    input("Press enter to exit...\n\n")
finally:
    zeroconf.close()

It uses the zeroconf PIP module. The code is a slightly-modified version of the example at https://github.com/python-zeroconf/python-zeroconf

atanisoft commented 7 months ago

@bobjacobsen normally we don't need to include the local. bit in mDNS searches. Is this coming from the zeroconf module?

bobjacobsen commented 7 months ago

@atanisoft I'm not at all an expert on this. I copied that example from the GitHub project's README, then changed it to read _openlcb-can._tcp.local. instead of the original _http._tcp.local.. Not sure why they have the .local in there.

Poikilos commented 7 months ago

Ok, thanks very much. I wasn't aware there was an advertised service that mDNS could find.

But why? What are you going to do with that information?

Once you have the connection to the hub, you can use LCC to talk to any attached device. Why do you also need their IP address?

The only other time I would want an IP address is for a "fast" update of a throttle bypassing LCC (using a special TCP port for updating). A workaround for achieving auto-discovery could be our current methods: having the throttle in AP mode, or instructing the user to turn other devices off.

I realize I can route general traffic through the hub such as configuration. I could potentially route firmware updates of throttles through a hub using the LCC pending standard for Firmware Update and that seems sufficient, but further proposals are welcome.

balazsracz commented 7 months ago

Hi,

Thanks for looping me into this discussion. There is no way in the existing OpenLCB protocol and specifically the TCS implementation that would allow tying the IP address to a given OPen LCB node ID. There is also no way to prevent a hub from forwarding packets, as such it is not possible to identify using the current standard, which of the nodes visible on the bus is the hub specifically. Sometimes the hub is not a node at all.

Generally Bob is right -- there is typically only one hub. However, both the TCS CS and the TCS throttles are always listening to port 12021, and if you make a TCP connection to this port, it will accept the connection and will act as a hub; meaning it will forward GridConnect packets to its existing OpenLCB connection. This means that it is possible to talk to a specific LCC node on the network for the purposes of firmware upgrades in a highly suboptimal way, going through multiple, unnecessary, and slow hubs. The TCS CS and throttle are pretty slow today (i.e., with all currently published firmware versions) as a hub.

There are a couple of pragmatic alternative approaches that can be used with the current firmware of TCS devices.

mDNS is the canonical one. Here is a network with a CS-105, a UWT-100 and a UWT-50 on it:

$ avahi-browse -a -r
+ enx047bcb60460b IPv4 tcs_cs_090099dd0242
_openlcb-can._tcp    local
+ enx047bcb60460b IPv4 tcs-throttle-0188                             Web
Site             local
+ enx047bcb60460b IPv4 tcs-throttle-01d2                             Web
Site             local
+ enx047bcb60460b IPv4 tcs-cs-0242                                   Web
Site             local
= enx047bcb60460b IPv4 tcs_cs_090099dd0242
_openlcb-can._tcp    local
   hostname = [tcs-cs-0242.local]
   address = [192.168.137.242]
   port = [12021]
   txt = ["OLCB"]
= enx047bcb60460b IPv4 tcs-throttle-0188                             Web
Site             local
   hostname = [tcs-throttle-0188.local]
   address = [192.168.137.188]
   port = [80]
   txt = ["srcvers=1D90645" "path=/"]
= enx047bcb60460b IPv4 tcs-throttle-01d2                             Web
Site             local
   hostname = [tcs-throttle-01d2.local]
   address = [192.168.137.190]
   port = [80]
   txt = ["srcvers=1D90645" "path=/"]
= enx047bcb60460b IPv4 tcs-cs-0242                                   Web
Site             local
   hostname = [tcs-cs-0242.local]
   address = [192.168.137.242]
   port = [80]
   txt = ["srcvers=1D90645" "path=/"]

The IP addresses can be clearly identified. The OpenLCB Node ID is not clearly stated; the last two bytes are visible. For example, "tcs-cs-0242" and "tcs-throttle-01d2" means that the OpenLCB node ID ends with 02.42 and 01.d2, respectively. It is always in hex.

Another option for getting to the same string is to download the web page /index.html. Near the top there will be <title>tcs-throttle-01d2</title> which contains the same string.

The command station was the only one advertising the service _openlcb-can._tcp in the above example. That advertisement contains the entire openlcb node ID in hex. The other advertisements were for web sites, meaning _http._tcp. The web site advertisements are present in firmware upgrade mode as well. Downloading /index.html and looking for will work too. The Cs-105 will not advertise the openlcb hub (tcs_cs_090099dd0242._openlcb-can._tcp) when it is booted in firmware update mode. It will still have the port 12021 open for connections though, and it will still route traffic between anybody who connects. This means that in a very common scenario (CS used in AP mode, some throttles connect directly) you would still find multiple OpenLCB nodes in the identification when you are querying directly through the CS's hub in AP mode; asking the user to turn off other devices generally makes sense.</p> <p>There is no way to differentiate between a UWT-50 and a UWT-100 based on the information presented above. You need to ask for the OpenLCB node ID using the standard protocol to do that.</p> <p>hth, Balazs</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/Poikilos"><img src="https://avatars.githubusercontent.com/u/7557867?v=4" />Poikilos</a> commented <strong> 7 months ago</strong> </div> <div class="markdown-body"> <p>@balazsracz Thank you, that is very useful. We and other vendors who wish to have similar features (getting to the IP address of the device for our reason or other reasons) can put some model-specific info that could be tied to their range (their organization's range, and their internal sub-range assigned to a model), and put the remaining (serialized) part of the address in there too. With this info I can use mDNS alone to get which device I want, for the use case of using non-LCC updating over the binary port.</p> <p>With this, the issue is resolved, at least for our devices and others who voluntarily make similar service names.</p> <p>For other devices, there seems to be no issue remaining, since we can route regular LCC operations such as configuration through a hub without bandwidth concerns.</p> <p>@bobjacobsen The example code in PR #30 is still helpful and can be used for whatever the user developer to do when they discover an ID.</p> </div> </div> <div class="page-bar-simple"> </div> <div class="footer"> <ul class="body"> <li>© <script> document.write(new Date().getFullYear()) </script> Githubissues.</li> <li>Githubissues is a development platform for aggregating issues.</li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script> <script src="/githubissues/assets/js.js"></script> <script src="/githubissues/assets/markdown.js"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/highlight.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/languages/go.min.js"></script> <script> hljs.highlightAll(); </script> </body> </html>