HPInc / HP-Digital-Microfluidics

HP Digital Microfluidics Software Platform and Libraries
MIT License
2 stars 0 forks source link

Handle instances with multiple local IP addresses #209

Open EvanKirshenbaum opened 5 months ago

EvanKirshenbaum commented 5 months ago

Oh, that's interesting! I'm not sure quite what to do in that situation. I guess I can add a command-line argument to allow you to specify your local IP address for situations like this. I'll have to poke around to see if I can detect the situation and give a warning.

Originally posted by @EvanKirshenbaum in https://github.com/HPInc/HP-Digital-Microfluidics/issues/201 [comment by @EvanKirshenbaum on Oct 07, 2022 at 11:49 AM PDT]

To support the Opentrons pipettor, we run a local HTTP server and tell the robot where to call back to. To obtain the IP address, we run

host_name = socket.gethostname()
ip = socket.gethostbyname(host_name)

When Mark Huber ran this, the robot was unable to connect to his machine. It turns out that the problem was that his machine had two IP addresses: 192.168.1.117 over Wi-Fi and 192.168.223.1 using a VMWare network adapter. Even though the virtual machine wasn't running, the above code was picking up that address, and the robot was unable to connect to it, since it wasn't on the same local network.

A simple fix would be to allow the user, when they realize that they are in this situation, to specify a local IP address on the command line, but this is unsatisfying, as it requires the user to know what their problem is. Better would be to somehow detect that there are multiple possibilities and either refuse to run if the user doesn't specify or at least print a warning that this might be a problem and pick one to use by default.

Note that even now, the IP address can be specified in the JSON config file. (The above code only runs if it isn't specified there.)

Migrated from internal repository. Originally created by @EvanKirshenbaum on Oct 07, 2022 at 11:50 AM PDT.
EvanKirshenbaum commented 5 months ago

This issue was referenced by the following commit before migration:

EvanKirshenbaum commented 5 months ago

Ah. It looks as though what I need is socket.getaddrinfo().

I should be able to work with that.

Migrated from internal repository. Originally created by @EvanKirshenbaum on Oct 07, 2022 at 12:09 PM PDT.
EvanKirshenbaum commented 5 months ago

Since it's me, there's now a general solution.

First, I added an erk.network module with the following:

def ip_addr_as_int(addr: str) -> int: ...
def int_to_ip_addr(ip: int) -> str: ...
def canonicalize_ip_addr(addr: str) -> str: ...
def local_ipv4_addrs(*, subnet: Optional[str] = None,
                     subnet_mask: Optional[str] = None) -> Sequence[str]: ...
def local_ipv4_addr(*, 
                    addr: Optional[str] = None,
                    subnet: Optional[str] = None,
                    subnet_mask: Optional[str] = None
                    ) -> Optional[str]: ...

local_ipv4_addrs() returns a (possibly empty) list of all local IP addresses (as canonicalized strings) or those that match the given subnet using the given subnet_mask. The subnet doesn't have to be a full quad. Trailing zeroes are inferred. If it isn't a full quad and the subnet_mask is None, a subnet_mask will be inferred with a 255 for every explicit place in subnet. (That means, for example, that a subnet of "192.168" will have a default subnet_mask of "255.255.0.0", while a subnet of "192.168.0" will have a default subnet_mask of "255.255.255.0".)

local_ipv4_addr() returns a single address (unless there are no addresses, in which case it returns None). If addr is given, it is returned, and a warning is logged if it isn't among the local addresses. If subnet is given and there's a single local address on that subnet (given the mask), it's returned. If there are multiple addresses on the subnet, a warning is logged, and the first matching address is returned. If there are no addresses on that subnet, a warning is logged and the first local address is returned. If neither addr nor subnet is provided, the first local address is returned and a warning is logged if there are more than one of them.

Next, I added --local-ip, local-subnet, and --subnet-mask command-line arguments to provide the values to pass to local_ipv4_addr() if the config file doesn't specify endpoint information. To support that, I added ip_addr_arg and ip_subnet_arg types to cmd_line.py. (Oh, yeah, I abstracted out command-line types to a separate module.) These actually are used by System, which now has a local_ipv4_addr property, so the same IP address will be handed out to any component that needs to run a listener.

Migrated from internal repository. Originally created by @EvanKirshenbaum on Oct 10, 2022 at 4:16 PM PDT.
EvanKirshenbaum commented 5 months ago

The code works on my machine, but it only has one IP address. I've asked [comment by @EvanKirshenbaum on Oct 10, 2022 at 4:43 PM PDT] Mark Huber to try it out on his machine, which has multiple and which led to our noticing the problem.

Migrated from internal repository. Originally created by @EvanKirshenbaum on Oct 10, 2022 at 4:49 PM PDT.
EvanKirshenbaum commented 5 months ago

it works, i took out the hardcoded ip and ran it with the local-ip argument, it worked. I did not see a warning though:

python cs.py joey --pipettor ot2 --ot-ip 192.168.1.113 --ot-config ../inputs/ot2.json --ot-reagents ../inputs/reagents.json --clock-speed=100ms --fragments 20 --shuttles=2 --cycles=2 --local-ip 192.168.1.117
Platform option 'devices.bilby.PlatformTask' requires 'pyglider' module, ignoring
   INFO|opentrons|Launching listener
======== Running on http://0.0.0.0:8087 ========
(Press CTRL+C to quit)
   INFO|opentrons|Listening for OT-2 on 192.168.1.117:8087
   INFO|opentrons|Temp protocol file is C:\Users\huberma\AppData\Local\Temp\protocol_pz7chrop.py
   INFO|opentrons|Create Protocol result: {'data': {'id': 'c5868978-6638-4fba-9e06-c4b3fdc6fb95', 'createdAt': '2022-10-13T21:27:54.836499+00:00', 'files': [{'name': 'protocol_pz7chrop.py', 'role': 'main'}], 'protocolType': 'python', 'metadata': {'description': 'A generic looping client to use the OT-2 as a peripheral for Joey', 'apiLevel': '2.11', 'protocolName': 'Joey Peripheral Protocol', 'author': 'Evan Kirshenbaum < evan.kirshenbaum@hp.com>'}, 'analyses': [], 'analysisSummaries': [{'id': '59aeb8e1-cc11-4332-9121-1548276683b7', 'status': 'pending'}]}}
   INFO|opentrons|Created protocol "c5868978-6638-4fba-9e06-c4b3fdc6fb95".
   INFO|opentrons|Created session "28ad82e4-44ab-4f1d-975f-37e8a10a9b38".
   INFO|opentrons|Started run.
   INFO|pipettor|Pipettor("OT-2") is not idle
   INFO|monitor|Setting tick to 100.0 ms
OT-2 [14:28:19]: Creating the robot. Callback endpoint is http://192.168.1.117:8087
OT-2 [14:28:51]: Board labware spec is {'name': 'biorad_96_wellplate_200ul_pcr', 'slot': 8}
OT-2 [14:28:51]: Board plate is Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:51]: Board drop size is 1.0
OT-2 [14:28:51]: Board wells is [E1 of Bio-Rad 96 Well Plate 200 µL PCR on 8, F1 of Bio-Rad 96 Well Plate 200 µL PCR on 8, G1 of Bio-Rad 96 Well Plate 200 µL PCR on 8, H1 of Bio-Rad 96 Well Plate 200 µL PCR on 8, E5 of Bio-Rad 96 Well Plate 200 µL PCR on 8, F5 of Bio-Rad 96 Well Plate 200 µL PCR on 8, G5 of Bio-Rad 96 Well Plate 200 µL PCR on 8, H5 of Bio-Rad 96 Well Plate 200 µL PCR on 8]
OT-2 [14:28:51]: Board extraction_ports is [E3 of Bio-Rad 96 Well Plate 200 µL PCR on 8, F3 of Bio-Rad 96 Well Plate 200 µL PCR on 8, G3 of Bio-Rad 96 Well Plate 200 µL PCR on 8]
OT-2 [14:28:51]: Board oil_reservoir is A3 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:51]: Done creating board
OT-2 [14:28:56]: Created robot and board
OT-2 [14:28:56]: W1: E1 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: W2: F1 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: W3: G1 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: W4: H1 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: W5: E5 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: W6: F5 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: W7: G5 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: W8: H5 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: E1: E3 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: E2: F3 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: E3: G3 of Bio-Rad 96 Well Plate 200 µL PCR on 8
OT-2 [14:28:56]: O1: A3 of Bio-Rad 96 Well Plate 200 µL PCR on 8

OT-2 [14:28:56]: Starting run
OT-2 [14:28:56]: http://192.168.1.117:8087
OT-2 [14:28:56]: Entering main loop
OT-2 [14:28:56]: Making ready call
OT-2 [14:28:56]: Calling ready: {}
OT-2 [14:28:56]: Back from ready: <Response [200]>
OT-2 [14:28:56]: -------------------
OT-2 [14:28:57]: supplying PM Primers: 10.0 µL @ W5
Migrated from internal repository. Originally created by Mark Huber on Oct 13, 2022 at 2:52 PM PDT.
EvanKirshenbaum commented 5 months ago

it works, i took out the hardcoded ip and ran it with the local-ip argument, it worked. I did not see a warning though:

You'll only get the warning if you don't give an argument or if you give a --local-subnet argument that isn't specific enough to narrow it down.

Could you try running it again with the following options?

Migrated from internal repository. Originally created by @EvanKirshenbaum on Oct 13, 2022 at 3:40 PM PDT.