Monitoring a Plant Using PubSub+, Raspberry Pi and Flutter

by Khajan in Circuits > Raspberry Pi

759 Views, 3 Favorites, 0 Comments

Monitoring a Plant Using PubSub+, Raspberry Pi and Flutter

IMG_20221016_172613068_HDR.jpg

I have a plant that I’ve been taking care of for a while, but I recently started to neglect it. As a techie, I decided the best way to make sure this plant gets the care it needs was to make a system that uses sensors to read things like the moisture of the soil and temperature around the plant, and lets me know when I need to water it.

As far as hardware goes, that’s a natural job for Raspberry Pi, and I decided to create an app using Flutter to notify me whenever I need to water the plant using data from the sensors. The Raspberry Pi will send the data to a database, the app will be able to read the data from there, and let me know when it notices a situation that needs my attention. I also wanted to make it possible to manually refresh the app, and had to figure out a way to actually do it.

Flutter is a widely used app-making framework to create cross-platform apps which can be run on multiple operating systems. Using Solace’s MQTT to subscribe and publish functionality, I can communicate with the Pi and the app using it as a broker to manage the messages.

In this article, I’ll explain how I used MQTT to communicate with the Raspberry Pi using a Flutter app, and I’ll present it in the form of a tutorial in case you want to do something similar yourself.

Supplies

To get started on the app side of things, you’ll need to install Flutter, and you can find a guide on how to do that here. You should also have a somewhat decent knowledge of Flutter to figure things out. I also assume that you have a Solace broker set up so that you can connect to it, and you can create your own one using Docker or set it up in the cloud using PubSub+ Cloud. I made a using it with Docker which you can find here.

For the Raspberry Pi, you’ll obviously need to have one along with an operating system for it to run. I’d recommend the official Raspberry Pi OS. I will be using Python to receive the data and Pi OS has that pre-installed.

Set Up the Flutter App

First off, start a Flutter project. To implement the MQTT usage, we need to run “pub mqtt_client” which will connect the app to the server. Information on this package can be found here.

Let’s create a file called MQTTManager.dart to contain connecting, disconnecting, subscribing, and publishing functions that will run in the app.


import 'package:mqtt_client/mqtt_client.dart';

import 'package:mqtt_client/mqtt_server_client.dart';


class MQTTManager {

  MqttServerClient? _client;

  final String _identifier;

  final String _host;

  final String _topic;


  MQTTManager({

    required String host,

    required String topic,

    required String identifier,

  })  : _identifier = identifier,

        _host = host,

        _topic = topic;


  void initializeMQTTClient() {

    _client = MqttServerClient(_host, _identifier);

    _client!.port = 1883;

    _client!.keepAlivePeriod = 20;

    _client!.onDisconnected = onDisconnected;

    _client!.secure = false;

    _client!.logging(on: true);


    _client!.onConnected = onConnected;

    _client!.onSubscribed = onSubscribed;


    final MqttConnectMessage connMess = MqttConnectMessage()

        .startClean() // Non persistent session for testing

        .withWillQos(MqttQos.atLeastOnce);

    print('Client connecting...');

    _client!.connectionMessage = connMess;

  }


  void connect() async {

    assert(_client != null);

    try {

      print('Starting to connect...');

      await _client!.connect();

    } on Exception catch (e) {

      print('Client exception - $e');

      disconnect();

    }

  }


  void disconnect() {

    print('Disconnected');

    _client!.disconnect();

  }


  void publish(String message) {

    final MqttClientPayloadBuilder builder = MqttClientPayloadBuilder();

    builder.addString(message);

    _client!.publishMessage(_topic, MqttQos.exactlyOnce, builder.payload!);

  }


  void onSubscribed(String topic) {

    print('Subscribed to topic $topic');

  }


  void onDisconnected() {

    print('Client disconnected');

  }


  void onConnected() {

    print('Connected to client');

    _client!.subscribe(_topic, MqttQos.atLeastOnce);

    _client!.updates!.listen((List<MqttReceivedMessage<MqttMessage?>>? c) {

      final MqttPublishMessage recMess = c![0].payload as MqttPublishMessage;


      final String pt =

          MqttPublishPayload.bytesToStringAsString(recMess.payload.message);

      print('Topic is <${c[0].topic}>, payload is <-- $pt -->');

      print('');

    });

    print('Connection was successful');

  }

}

Implement the MQTT Interface

To use the MQTT in the app, we must create a user interface to interact and send messages. You can use the functions however you want in a way that will fit your application correctly.

For demonstration, I created a simple example app which can let you connect to the MQTT broker and send messages to a topic by pressing a button. We’ll have to modify the MyHomePage class in the main.dart file.

Replace the MyHomePage class with this code:

class MyHomePage extends StatefulWidget {

  const MyHomePage({Key? key}) : super(key: key);


  @override

  State<MyHomePage> createState() => _MyHomePageState();

}


class _MyHomePageState extends State<MyHomePage> {

  late MQTTManager manager;


  void _configureAndConnect() {

    String osPrefix = 'Flutter_iOS';

    if (Platform.isAndroid) {

      osPrefix = 'Flutter_Android';

    }

    manager = MQTTManager(

      host: "127.0.0.1",

      topic: "app/test",

      identifier: osPrefix,

    );

    manager.initializeMQTTClient();

    manager.connect();

  }


  void _disconnect() {

    manager.disconnect();

  }


  void _publishMessage(String text) {

    String osPrefix = "mobile_client";

    final String message = osPrefix + ' says: ' + text;

    manager.publish(message);

  }


  @override

  void initState() {

    _configureAndConnect();

    super.initState();

  }


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: const Text(

          'Flutter Demo',

        ),

      ),

      body: Stack(

        children: [

          Column(

            children: [

                child: ElevatedButton(

                  style: ElevatedButton.styleFrom(

                    fixedSize: const Size(240, 50),

                  ),

                  onPressed: () {

                    try {

                      _publishMessage("Hi");

                    } on ConnectionException catch (e) {

                      print(e);

                      final snackBar = SnackBar(

                        content: const Text('Connecting...'),

                        backgroundColor: (Colors.black),

                        duration: const Duration(seconds: 1),

                        action: SnackBarAction(

                          label: 'Dismiss',

                          onPressed: () {},

                        ),

                      );

                      ScaffoldMessenger.of(context).showSnackBar(snackBar);

                    }

                  },

                  child: const Text(

                    "Refresh",

                    style: TextStyle(

                      fontFamily: 'Open Sans',

                      fontSize: 17.5,

                    ),

                  ),

                ),


),

            ],

          ),

        ],

      ),

    );

  }


  @override

  void deactivate() {

    _disconnect();

    super.deactivate();

  }

}

Raspberry Pi Setup

I have a Raspberry Pi that I want to receive data from for my use case. To implement this, you will need a script where the Pi is subscribed to MQTT and listening to the Solace PubSub+ server.

For my use case, I have a humidity/temperature sensor to record data around my plant. To replicate this, you must write the sensor according to your needs and install the Adafruit package by running this command in the terminal:

pip3 install adafruit-circuitpython-dht

Now, you’ll want to create a new Python file on the Pi which will hold the code for listening and responding whenever a refresh call is sent. In the text editor of your choice, edit the file to have this code in it:

import RPi.GPIO as GPIO

import adafruit_dht

import board


import random

import time

import os


from paho.mqtt import client as mqtt_client


broker = '192.168.1.73'

port = 1883

topic = "app/temp"

info_topic = "app/temp"


client_id = f"python-mqtt-{random.randint(0, 1000)}"


dht_device = adafruit_dht.DHT11(board.D27, use_pulseio=False)


def connect_mqtt():

    def on_connect(client, userdata, flags, rc):

        if rc == 0:

            print("Connected to MQTT Broker!")

        else:

            print("Failed to connect, return code %d\n", rc)


    client = mqtt_client.Client(client_id)

    client.on_connect = on_connect

    client.connect(broker, port)

    return client

   

def refreshCall(client):

    def on_message(client, msg):

        def tempHumidity(client):

            temperature = dht_device.temperature

            humidity = dht_device.humidity

            answer = f"The temperature is {temperature} celcius and the humidity is {humidity}%"

            result = client.publish(info_topic, answer)

            status = result[0]

            if status == 0:

                print(f"Sent `{msg}` to topic `{topic}`")

            else:

                print(f"Failed to send message to topic {topic}")

            time.sleep(2)

        receivedMsg = msg.payload.decode()

        if receivedMsg == "Refresh":

            tempHumidity(client)

        else:

            print(f"Received `{receivedMsg}` from `{msg.topic}` topic")


    client.subscribe(topic)

    client.on_message = on_message


def run():

    client = connect_mqtt()

    refreshCall(client)

    client.loop_forever()

if __name__ == '__main__':

    run()

Now you can have a Raspberry Pi that listens to MQTT commands sent from an app and execute commands from those sent commands! That’s great for my use case so that I can send a command to refresh from a Flutter app and use Solace’s MQTT platform to do something about that data like having it read temperature and humidity so that the app can see that refreshed data. If you’d like, there is also a Github repository linked here with the code for the app referenced in it.

Future Applications

Hopefully, this showed you how you can use MQTT to solve problems. The sky’s the limit for possibilities in what you can do, just like how I used my Raspberry Pi to receive data from an app, you can use this guide to communicate using Solace in an app or just using a Raspberry Pi, or both if you’d like! This allows for so many different use cases and scenarios in which you can communicate between devices. I hope you enjoyed it and found this guide helpful and interesting!