Skip to main content

Use zeroconf to automatically connect devices via TCP on a LAN

Project description

zeroconnect

PyPI - Version PyPI - Python Version

Use zeroconf to automatically connect devices via TCP on a LAN. I can hardly believe this doesn't exist already, but after searching for an hour, in despair I resign myself to write my own, and patch the glaring hole in existence.


Table of Contents

Installation

pip install zeroconnect

Usage

One or more servers, and one or more clients, run connected to the same LAN. (Wifi or ethernet.)

Most basic

Service:

from zeroconnect import ZeroConnect

def rxMessageConnection(messageSock, nodeId, serviceId):
    print(f"got message connection from {nodeId}")
    data = messageSock.recvMsg()
    print(data)
    messageSock.sendMsg(b"Hello from server")

ZeroConnect().advertise(rxMessageConnection, "YOUR_SERVICE_ID_HERE")

Client:

from zeroconnect import ZeroConnect
messageSock = ZeroConnect().connectToFirst("YOUR_SERVICE_ID_HERE")
messageSock.sendMsg(b"Hello from client")
data = messageSock.recvMsg()
print(data)

Less basic

Service:

from zeroconnect import ZeroConnect

SERVICE_ID = "YOUR_SERVICE_ID_HERE"
zc = ZeroConnect("NODE_ID")

def rxMessageConnection(messageSock, nodeId, serviceId):
    print(f"got message connection from {nodeId}")
    # If you also want to spontaneously send messages, pass the socket to e.g. another thread.
    while True:
        data = messageSock.recvMsg()
        print(data)
        if data == b'enable jimjabber':
            print(f"ENABLE JIMJABBER")
        elif data == b'save msg:':
            toSave = messageSock.recvMsg()
            print(f"SAVE MESSAGE {toSave}")
        elif data == b'marco':
            messageSock.sendMsg(b'polo')
            print(f"PING PONGED")
        elif data == None:
            print(f"Connection closed from {nodeId}")
            messageSock.close()
            return
        else:
            print(f"Unhandled message: {data}")
        # Use messageSock.sock for e.g. sock.getsockname()
        # I recommend messageSock.close() after you're done with it - but it'll get closed on zc.close(), at least

zc.advertise(rxMessageConnection, SERVICE_ID) # Implicit mode=SocketMode.Messages

try:
    input("Press enter to exit...\n\n")
finally:
    zc.close()

Client:

from zeroconnect import ZeroConnect, SocketMode

SERVICE_ID = "YOUR_SERVICE_ID_HERE"
zc = ZeroConnect("NODE_ID") # Technically the nodeId is optional; it'll assign you a random UUID

ads = zc.scan(SERVICE_ID, time=5)
# OR: ads = zc.scan(SERVICE_ID, NODE_ID)
# An `Ad` contains a `serviceId` and `nodeId` etc.; see `Ad` for details
messageSock = zc.connect(ads[0], mode=SocketMode.Messages) # Send and receive messages; the default mode
# OR: messageSock = zc.connectToFirst(SERVICE_ID)
# OR: messageSock = zc.connectToFirst(nodeId=NODE_ID)
# OR: messageSock = zc.connectToFirst(SERVICE_ID, NODE_ID, timeout=10)

messageSock.sendMsg(b"enable jimjabber")
messageSock.sendMsg(b"save msg:")
messageSock.sendMsg(b"i love you")
messageSock.sendMsg(b"marco")
print(f"rx: {messageSock.recvMsg()}")

# ...

zc.close()

You can also get raw sockets rather than MessageSockets, if you prefer:

Server:

from zeroconnect import ZeroConnect, SocketMode

SERVICE_ID = "YOUR_SERVICE_ID_HERE"
zc = ZeroConnect("NODE_ID")

def rxRawConnection(sock, nodeId, serviceId):
    print(f"got raw connection from {nodeId}")
    data = sock.recv(1024)
    print(data)
    sock.sendall(b"Hello from server\n")
    # sock is a plain socket; use accordingly

zc.advertise(rxRawConnection, SERVICE_ID, mode=SocketMode.Raw)

try:
    input("Press enter to exit...\n\n")
finally:
    zc.close()

Client:

from zeroconnect import ZeroConnect, SocketMode

SERVICE_ID = "YOUR_SERVICE_ID_HERE"
zc = ZeroConnect("NODE_ID") # Technically the nodeId is optional; it'll assign you a random UUID

ads = zc.scan(SERVICE_ID, time=5)
# OR: ads = zc.scan(SERVICE_ID, NODE_ID)
# An `Ad` contains a `serviceId` and `nodeId` etc.; see `Ad` for details
sock = zc.connect(ads[0], mode=SocketMode.Raw) # Get the raw streams
# OR: sock = zc.connectToFirst(SERVICE_ID, mode=SocketMode.Raw)
# OR: sock = zc.connectToFirst(nodeId=NODE_ID, mode=SocketMode.Raw)
# OR: sock = zc.connectToFirst(SERVICE_ID, NODE_ID, mode=SocketMode.Raw, timeout=10)

sock.sendall(b"Hello from client\n")
data = sock.recv(1024)
print(f"rx: {data}")

# ...

zc.close()

There's a few other functions you might find useful. Look at the source code. Here; I'll paste the declaration of all the public ZeroConnect methods here.

    def __init__(self, localId=None):
    def advertise(self, callback, serviceId, port=0, host="0.0.0.0", mode=SocketMode.Messages):
    def scan(self, serviceId=None, nodeId=None, time=30):
    def scanGen(self, serviceId=None, nodeId=None, time=30):
    def connectToFirst(self, serviceId=None, nodeId=None, localServiceId="", mode=SocketMode.Messages, timeout=30):
    def connect(self, ad, localServiceId="", mode=SocketMode.Messages):
    def broadcast(self, message, serviceId=None, nodeId=None):
    def getConnections(self):
    def close(self):

Tips

Be careful not to have two nodes recv from each other at the same time, or they'll deadlock. However, you CAN have them send at the same time (at least according to my tests).

ZeroConnect is intended to be manipulated via its methods, but it probably won't immediately explode if you read the data in the fields.

Note that some computers/networks block zeroconf, or external connection attempts, etc.

Calling broadcast will automatically clean up dead connections.

If you close your socket immediately after sending a message, the data may not finish sending. Not my fault; blame socket.

broadcast uses MessageSockets, so if you're using a raw socket, be aware the message will be prefixed with a header, currently an 8 byte unsigned long representing the length of the subsequent message. See MessageSocket.

See logging.py to see logging settings, or do like so:

from zeroconnect.logging import *
setLogLevel(-1) # 4+ for everything current, -1 for nothing except uncaught exceptions
# It also contains some presets; ERROR/WARN/INFO/VERBOSE/DEBUG atm.
# Also, you can move all the logging to stderr with `setLogType(2)`.

Bonus!

Also includes zcat, like ncat/nc/netcat. Use as follows:

RX:

python -m zeroconnect.zcat -l SERVICE_ID [NODE_ID] > FILE

TX:

cat FILE | python -m zeroconnect.zcat SERVICE_ID [NODE_ID]

License

zeroconnect is distributed under the terms of the MIT license.

TODO

ssl lower timeouts? connect to all, forever? connection callback maybe some automated tests? .advertiseSingle to get one connection? for quick stuff?

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zeroconnect-1.0.1.tar.gz (13.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zeroconnect-1.0.1-py3-none-any.whl (15.2 kB view details)

Uploaded Python 3

File details

Details for the file zeroconnect-1.0.1.tar.gz.

File metadata

  • Download URL: zeroconnect-1.0.1.tar.gz
  • Upload date:
  • Size: 13.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.7.9

File hashes

Hashes for zeroconnect-1.0.1.tar.gz
Algorithm Hash digest
SHA256 0a9883785a6451c69fad8c808c5302be1d15fba8cf4a4c2ccfd0a4e911eedee3
MD5 39679c6820aeb90876d6ff8ce43abb5c
BLAKE2b-256 1015c2ef16fc6b2baec7c2886d17faae850cc536cf382795aa3da2c03afe10f0

See more details on using hashes here.

File details

Details for the file zeroconnect-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: zeroconnect-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 15.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.7.9

File hashes

Hashes for zeroconnect-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6420208976139c11be0e1bd594206418e74d9d62de6d67b5f4e1ce3a7c4a4a51
MD5 6e9680a29f3cbbe8be002c1ebb932f71
BLAKE2b-256 9fc2663d79d5c656018abb0a3549c619590740e347777835e1f0e70ac01b3183

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page