Use MQTT over the XBee Cellular Modem with a PC

To use this MQTT library over an XBee Cellular Modem, you need a basic proxy that transfers a payload received via the MQTT client’s socket to the serial or COM port that the XBee Cellular Modem is active on, as well as the reverse; transfer of a payload received on the XBee Cellular Modem’s serial or COM port to the socket of the MQTT client. This is simplest with the XBee Cellular Modem in Transparent mode, as it does not require code to parse or create API frames, and not using API frames means there is no need for them to be queued for processing.

  1. To put the XBee Cellular Modem in Transparent mode, set AP to 0.
  2. Set DL to the IP address of the broker you want to use.
  3. Set DE to the port to use, the default is 1883 (0x75B). This sets the XBee Cellular Modem to communicate directly with the broker, and can be performed in XCTU as described in Example: MQTT connect.
  4. You can make the proxy with a dual-threaded Python script, a simple version follows:
import threading
import serial
import socket


def setup():
    """
    This function sets up the variables needed, including the serial port,
    and it's speed/port settings, listening socket, and localhost adddress.
    """
    global clisock, cliaddr, svrsock, ser
    # Change this to the COM port your XBee Cellular module is using.  On
    # Linux, this will be /dev/ttyUSB#
    comport = 'COM44'
    # This is the default serial communication speed of the XBee Cellular
    # module
    comspeed = 115200
    buffer_size = 4096  # Default receive size in bytes
    debug_on = 0  # Enables printing of debug messages
    toval = None  # Timeout value for serial port below
    # Serial port object for XBCell modem
    ser = serial.Serial(comport,comspeed,timeout=toval)
    # Listening socket (accepts incoming connection)
    svrsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # Allow address reuse on socket (eliminates some restart errors)
    svrsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    clisock = None
    cliaddr = None  # These are first defined before thread creation
    addrtuple = ('127.0.0.1', 17300)  # Address tuple for localhost
    # Binds server socket to localhost (allows client program connection)
    svrsock.bind(addrtuple)
    svrsock.listen(1)  # Allow (1) connection


def ComReaderThread():
    """
    This thread listens on the defined serial port object ('ser') for data
    from the modem, and upon receipt, sends it out to the client over the
    client socket ('clisock').
    """
    global clisock
    while (1):
        resp = ser.read()  ## Read any available data from serial port
        print("Received {} bytes from modem.".format(len(resp)))

        clisock.sendall(resp)  # Send RXd data out on client socket
        print("Sent {} byte payload out socket to client.".format(len(resp)))


def SockReaderThread():
    """
    This thread listens to the MQTT client's socket and upon receiving a
    payload, it sends this data out on the defined serial port ('ser') to the
    modem for transmission.
    """

    global clisock
    while (1):
        data = clisock.recv(4096)  # RX data from client socket
        # If the RECV call returns 0 bytes, the socket has closed
        if (len(data) == 0):
            print("ERROR - socket has closed.  Exiting socket reader thread.")
            return 1  # Exit the thread to avoid a loop of 0-byte receptions
        else:
            print("Received {} bytes from client via socket.".format(len(data)))
            print("Sending payload to modem...")
            bytes_wr = ser.write(data)  # Write payload to modem via UART/serial
            print("Wrote {} bytes to modem".format(bytes_wr))

def main():
    setup()  # Setup the serial port and socket
    global clisock, svrsock
    if (not clisock):  # Accept a connection on 'svrsock' to open 'clisock'
        print("Awaiting ACCEPT on server sock...")
        (clisock,cliaddr) = svrsock.accept()  # Accept an incoming connection
        print("Connection accepted on socket")
    # Make thread for ComReader
    comthread = threading.Thread(target=ComReaderThread)
    comthread.start()  # Start the thread
    # Make thread for SockReader
    sockthread = threading.Thread(target=SockReaderThread)
    sockthread.start()  # Start the thread

main()

Note This script is a general TCP-UART proxy, and can be used for other applications or scripts that use the TCP protocol. Its functionality is not limited to MQTT.

Note You can easily copy and paste code from the online version of this guide. Use caution with the PDF version, as it may not maintain essential indentations.

This proxy script waits for an incoming connection on localhost (127.0.0.1), on port 17300. After accepting a connection, and creating a socket for that connection (clisock), it creates two threads, one that reads the serial or COM port that the XBee Cellular Modem is connected to, and one that reads the socket (clisock), that the MQTT client is connected to.

With:

the proxy acts as an intermediary between the MQTT client and the XBee Cellular Modem, allowing the MQTT client to use the data connection provided by the device.

Think of the proxy script as a translator between the MQTT client and the XBee Cellular Modem. The following figure shows the basic operation.

The thread that reads the serial port forwards any data received onward to the client socket, and the thread reading the client socket forwards any data received onward to the serial port. This is represented in the figure above.

The proxy script needs to be running before running an MQTT publish or subscribe script.

  1. With the proxy script running, run the subscribe example from Example: receive messages (subscribe) with MQTT, but change the connect line from client.connect("m2m.eclipse.org", 1883, 60) to client.connect("127.0.0.1", port=17300, keepalive=20). This connects the MQTT client to the proxy script, which in turn connects to a broker via the XBee Cellular Modem’s internet connection.
  2. Run the publish example from Example: send messages (publish) with MQTT in a third Python instance (while the publish script is running you will have three Python scripts running at the same time).

The publish script runs over your computer’s normal Internet connection, and does not use the XBee Cellular Modem. You are able to see your published message appear in the subscribe script’s output once it is received from the broker via the XBee Cellular Modem. If you watch the output of the proxy script during this process you can see the receptions and transmissions taking place.

The proxy script must be running before you run the subscribe and publish scripts. If you stop the subscribe script, the socket closes, and the proxy script shows an error. If you try to start the proxy script after starting the subscribe script, you may also see a socket error. To avoid these errors, it is best to start the scripts in the correct order: proxy, then subscribe, then publish.