Implementing the File Transfer

The basic idea is an automatic transfer of image files from one computer (in this case a Raspi) to another computer. Of course you could do the file transfer just as well with FTP or HTTP, but if you already have MQTT in use, you save the setup and implementation of the FTP and HTTP servers. By the way, the implementation allows the transfer of arbitrary files, not only image files.

Commuication Principle

The example uses two Python classes: an ImageSender and an ImageReceiver. The ImageSender reads all files with the extension "jpg" from a directory and then sends first the name of the file (as string) and then the content base64 encoded.

The topics "ImageReceiver/start" and "ImageReceiver/stop" are used to enable the ImageReceiver to actively start and stop the file transfer.

File Transfer Implementation

For the transfer of binary files it is always advantageous to use a transfer format that is not dependent on any code pages (UTF-8 / 16 / ..). A good method is for instance base64, Base64 is a method for encoding 8-bit binary data (e.g. executable programs, ZIP files, or images) into a string consisting only of readable, codepage-independent ASCII characters.

Source code for reading and sending a file as base64 bytes:

file = open(" ... your pathname ... ", 'rb')
filecontent = file.read()
# encode base64
base64_bytes = base64.b64encode(filecontent)
result = self.client.publish("ImageSender/content", base64_bytes, 0)

Source code for receiving, decoding and saving a file:

file = open(" ... your pathname ... ", "wb")
content = str(message.payload.decode('utf-8'))
image = base64.b64decode(content)
file.write(image)
file.close()    

Complete Source Code

import base64
import os
import sys
import time

import paho.mqtt.client as mqtt

# ImageSender
# A ImageSender waits for a start message , retrieves all images in a file and send all
# files with names to an ImageReceiver

class ImageSender:
    def __init__(self):
        try:
            self.client = mqtt.Client("ImageSender")  # create new instance
            self.client.connect("192.168.2.110")  # connect to your broker
            self.client.subscribe("ImageReceiver/start")
            self.client.subscribe("ImageReceiver/stop")
            # start loop für  MQTT client
            self.client.loop_start()  # start the loop

            # this is the directory containing the images
            self.dir = "/home/shares/pi/imagesender/pictures"
            # callback for incoming MQTT Messages
            self.client.on_message = self.messageArrived

            # run until a stop message arriived
            self.keeponrunning = True
            self.run()

        except Exception:
            print("Connection to Broker failed ... shutting down.")
            sys.exit()

    # run as long as there is no stop message from the imageReceiver
    def run(self):
        while(self.keeponrunning):
            time.sleep(1)
        # disconnect from broker when application stops
        self.client.disconnect()

    # checks for incoming messages
    def messageArrived(self, client, userdata, message):
        # start ot stop?
        if message.topic == "ImageReceiver/start":
            self.sendImages()
        if message.topic == "ImageReceiver/stop":
            print("Received stop message, image service will stop ...")
            self.keeponrunning = False

    def sendImages(self):
        # iterate directory and send all image files (jpg)  in the directory
        filelist = []
        # iterating over all files which end with "jpg"
        for file in os.listdir(self.dir):
            if file.endswith('jpg'):
                filelist.append(file)
            else:
                continue
        print(filelist)

        for name in filelist:
            # send file name prior to file content
            self.client.publish("ImageSender/filename", name)
            # open the file for binary read
            file = open("./pictures/" + name, 'rb')
            filecontent = file.read()
            # encode base64
            base64_bytes = base64.b64encode(filecontent)
            result = self.client.publish("ImageSender/content", base64_bytes, 0)
            msg_status = result[0]
            if msg_status == 0:
                print("file ", name, "sent ...")
            else:
                print("Failed to send file", name)

# start sender
sender = ImageSender()
import base64
import sys
import time

import paho.mqtt.client as mqtt

# ImageReceiver
# A ImageReceiver sends a start message to an ImageSender, waits for the files
# and saves them to a local directory

class ImageReceiver:
    def __init__(self):
        try:
            self.client = mqtt.Client("ImageReceiver")  # create new instance
            self.client.connect("192.168.2.110")  # connect to your broker
            self.client.subscribe("ImageSender/filename")
            self.client.subscribe("ImageSender/content")
            # start loop für  MQTT client
            self.client.loop_start()  # start the loop

            # this is the directory containing the images
            self.imagedir = "./pictures"
            # callback for incoming MQTT Messages
            self.client.on_message = self.imageDataArrived

            # send an empty start Message as soon as the ImageReceiver object was created
            self.client.publish("ImageReceiver/start", "")

            # press ENTER to stop receiver -> send stop message to sender
            input("Press ENTER to stop image receiver...")
            print("Sending stop message to sender ...")
            self.client.publish("ImageReceiver/stop", "")
            time.sleep(1)
            self.client.disconnect()

        except Exception:
            print("Connection to Broker failed ... shutting down.")
            sys.exit()


    def imageDataArrived(self, client, userdata, message):
        data = message.payload.decode("utf-8")
        # filename or file content?
        if message.topic == "ImageSender/filename":
            self.nextFileName = data

        if message.topic == "ImageSender/content":
            # open file for write access
            file = open(self.imagedir + "/" + self.nextFileName, "wb")
            content = str(message.payload.decode('utf-8'))
            image = base64.b64decode(content)
            file.write(image)
            file.close()
            self.nextFileName = ""

sender = ImageReceiver()