Telegram Motion Detection Bot – Part 2: Adding buttons

Introduction

In this article I will show you how to improve the telegram motion detection bot that we created on the previous tutorial: we will add activation / deactivation buttons!

If you missed the previous tutorial on the which we created a simple and no-frills Telegram motion detection bot you can find it here.

Important!

If you already read the previous tutorial, in order to continue you have to re-download my TelegramBotUtilities library, because I had to fix a bug with the InlineKeyboardMarkup under sent files.
You can download it from here.

Why activation buttons into our Telegram Motion Detection Bot?

In this part, as I said in the introduction, we will add activation buttons to our bot.
I saw that this is an important feature if we want to have our application running anytime on our pc / raspberry pi without having to manually start or stop it.
This way we could start and stop the telegram motion detection bot by only pressing a button from our telegram application!
In order to make a clean graphical interface, and to exploit at its better the telegram framework, I thought that we can put our deactivation button under each photo that we receive from the motion detection bot.
Similarly, we can put the activation button under the message that says that the motion detector bot has been deactivated.

Let’s start

First of all, we need to add the deactivation button under the photos: we have to create a one-button InlineKeyboardMarkup, so we need to declare a 1-by-1 InlineKeyboardButtons matrix and call the constructor on the only element of the matrix.

InlineKeyboardButton btns[][] = new InlineKeyboardButton[1][1];
btns[0][0] = new InlineKeyboardButton("Deactivate", null, "deactivate", null);
activatedKM = new InlineKeyboardMarkup(btns);

Like I said before, in this little snippet of code we created a 1-by-1 buttons matrix, then we created the button setting the text to “Deactivate” and the Telegram callback_data to “deactivate”.
This way, when we click on the button, we automatically send an update to the bot, which contains the message containing the InlineKeyboardMarkup and also a data field: on that field we will find the callback data, that in our case is set to “deactivate”.
After creating the button, we instantiate an InlineKeyboardMarkup passing it the btns matrix.

You can see the structure of a callback update into this image:
Telegram motion detection bot: Callback structure

Now, anytime the bot sends us a photo, it has to send also the keyboard that we just created.
In order to achieve that, we have to add the keyboard markup to the sendPhoto method.

bot.sendPhoto(recipientId, temp, "Motion detected", false, -1, activatedKM);

Changing the state

Now that we can send the deactivation update to the telegram motion bot we have to define the bot’s statuses.

Here it is the Finite State Automata for our bot including activation/deactivation.
TelegramBotUtilities: Finite State Automata for the motion detection bot

We can implement the activation/deactivation manager using a Thread.
The thread has to check for incoming queries and if there is an incoming activation/deactivation message it has to start/stop the telegram motion detection bot.
Also, we have to send a message when to the user the bot changes its state.

Obviously, we have to create another inline keyboard markup in order to allow re-activation under the deactivation message.
So, we can create the keyboard in the same way as we did before.

InlineKeyboardButton aBtns[][] = new InlineKeyboardButton[1][1];
aBtns[0][0] = new InlineKeyboardButton("Activate", null, "activate", null);
InlineKeyboardMarkup deactivatedKM = new InlineKeyboardMarkup(aBtns);

Avoiding repeated messages

According to the FSM, we need to activate the bot only if it is deactivated and vice versa.
To achieve that we have to create a boolean flag that tells if the current state is running or not running.

So, the condition that allows us to deactivate the bot is the following.

if (isRunning && updates[0].getCallbackQuery() != null && updates[0].getCallbackQuery().getData().compareTo("deactivate") == 0) {
    // Manage deactivation
}

On the other hand, the activation condition is the following one.

if (!isRunning && updates[0].getCallbackQuery() != null && updates[0].getCallbackQuery().getData().compareTo("activate") == 0) {
    // Manage activation
}

Full code of the working Telegram motion detector bot with activation

That said, here you can find the whole code for our telegram bot.

package MotionDetection;

import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamMotionDetector;
import com.github.sarxos.webcam.WebcamMotionEvent;
import com.github.sarxos.webcam.WebcamMotionListener;
import org.altervista.leocus.telegrambotutilities.*;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;

public class MotionDetector implements WebcamMotionListener {
    private TelegramBot bot = new TelegramBot(""); // Replace it with your api key
    private Webcam webcam;
    private int interval = 250;
    private String recipientId = ""; // Replace it with your id
    private int flagSetterDelay = 250;
    private boolean isRunning;
    private WebcamMotionDetector detector;
    InlineKeyboardMarkup activatedKM;

    Thread flagSetterThread = new Thread(new Runnable() {
        @Override
        public void run() {
            int offset = 0;
            isRunning = true;

            InlineKeyboardButton aBtns[][] = new InlineKeyboardButton[1][1];
            aBtns[0][0] = new InlineKeyboardButton("Activate", null, "activate", null);
            InlineKeyboardMarkup deactivatedKM = new InlineKeyboardMarkup(aBtns);

            while (true) {
                try {
                    Update[] updates = bot.getUpdates(offset, 1, -1);
                    if (updates.length > 0) {
                        if (isRunning && updates[0].getCallbackQuery() != null && updates[0].getCallbackQuery().getData().compareTo("deactivate") == 0) {
                            detector.stop();
                            bot.sendMessage(recipientId, "Deactivated", null, false,
                                    false, -1, deactivatedKM);
                            isRunning = false;
                        } else {
                            if (!isRunning && updates[0].getCallbackQuery() != null && updates[0].getCallbackQuery().getData().compareTo("activate") == 0) {
                                startDetector(webcam);
                                bot.sendMessage(recipientId, "Activated", null, false,
                                        false, -1, activatedKM);
                                isRunning = true;
                            }
                        }
                        offset = updates[0].getId() + 1;
                    }
                    Thread.sleep(flagSetterDelay);
                } catch (EmptyUpdatesException | GettingUpdatesException | IOException | InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    public MotionDetector() {
        InlineKeyboardButton btns[][] = new InlineKeyboardButton[1][1];
        btns[0][0] = new InlineKeyboardButton("Deactivate", null, "deactivate", null);
        activatedKM = new InlineKeyboardMarkup(btns);
        webcam = Webcam.getDefault();
        startDetector(webcam);
        flagSetterThread.start();
    }

    private void startDetector(Webcam webcam) {
        detector = new WebcamMotionDetector(webcam);
        detector.setInterval(this.interval);
        detector.addMotionListener(this);
        detector.start();
    }
    @Override
    public void motionDetected(WebcamMotionEvent webcamMotionEvent) {
        try {
            // Create a temp file
            File temp = File.createTempFile("temp", ".jpg");
            // Write the image onto the file
            if(isRunning) {
                ImageIO.write(webcam.getImage(), "jpeg", temp);
            // Send the photo
                bot.sendPhoto(recipientId, temp, "Motion detected", false, -1, activatedKM);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        new MotionDetector();
        System.in.read(); // In order to keep the application alive
    }
}

Subscribe to the telegram group in order to get updates for next episodes and for asking your questions: Link to the group

Previous Entries Motion detection bot that sends images via Telegram in only 45 lines of code in java! - Part 1

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.