Close
0%
0%

Old Roomba, new tricks

Add mapping and MQTT-interfacing with ESP8266 and an IMU

Public Chat
Similar projects worth following
Using an ESP8266 module, control my Roomba 632 that I bought on Ibood some years ago.
It will clean my house, won't look like something I hacked together and is a perfect platform for experimentation.

Goals:
V - Add control with MQTT in Home Assistant for scheduled cleaning;
V - Play Macgyver theme song;
V - MQTT-autodiscover;
V - Read sensorstream data;
V - Add IMU;
V - Get vectordata from wheel encoder readings for location;
V - Get vectordata from IMU-readings for location;
V - Mapping;
o - Integrate IMU and wheel encoder readings to improve accuracy;
o - Correct vectordata with closed loop;
o - Controll and SLAM?
V - Having fun ;-)

Progress:

Ok. This project will take a while... A lot of stuff coming together on this one. 

I found out that the Roomba I've had for some years has a serial port and an open interface.

It's basically a iRobot Create 2, but this one can also clean my floor:

https://edu.irobot.com/what-we-offer/create-robot 

The Roomba is a 632 (600 series) without any scheduling or mapping capabilities... for now.

The interface allows me to not only send basic commands, but also to get full sensor readings. On everything from motors, bump and cliff sensors etc. 

As always, the plan is pretty simple. Add microcontroller, some soldering, some typing, done!

For starters, I want to be able to let Home Assistant send a CLEAN command via MQTT on a schedule. [DONE]

In the long run I want to add a IMU (MPU6050) and integrate this with the sensordata (odometry) from the Roomba to get mapping data. Tracks like you get with a handheld GPS. [DONE]

Then maybe do some loop-closing geodesy magic and take full control of the driving and cleaning.

Arduino sketch 2025-05-03_Roomba_EmptyBin.zip

Sketch. With undock / empty bin routine

Zip Archive - 7.12 kB - 05/03/2025 at 20:30

Download

Roomba632_2025-05-03.zip

Roomba library. Added undocking. Reset on timeout

Zip Archive - 12.61 kB - 05/03/2025 at 20:25

Download

2025-04-23_libraries.zip

Needed Arduino libraries

Zip Archive - 502.61 kB - 04/23/2025 at 20:38

Download

Roomba_MQTT_daemon.py

Python service/daemon to run on a Pi for logging and mapping (and to do loop closures and error correction in the future)

text/x-python - 9.78 kB - 09/02/2022 at 09:53

Download

Roomba_MQTT.service

service file for systemctl to run the python script as a service

service - 297.00 bytes - 09/02/2022 at 09:53

Download

View all 14 files

  • 1 × iRobot Roomba 632
  • 1 × ESP8266
  • 1 × IPEX antenna WiFi antenna for the ESP8266: https://hackerstore.nl/Artikel/1026
  • 1 × Buck step down DC/DC converter 3.3V
  • 1 × MPU6050

  • Unlimited memory!*

    Simon Jansen05/04/2025 at 14:26 0 comments

    Songs and driving scripts are now stored in and read from PROGMEM. This means we can use large scripts (for calibration or as consistent cleaning routes) without running out of memory.

    //Starwars' Imperial March
    const uint8_t imperialMarch[] PROGMEM = {
      N_A4,  32,  N_A4,  32,  N_A4,  32,  N_F4,  23,  N_C5,  10,  N_A4,  32,  N_F4,  23,  N_C5,  10,  N_A4,  42,  0   ,  32,  N_E5,  32,  N_E5,  32,  N_E5,  32,  N_F5,  23, N_C5,  10,  N_G4S, 32,
      N_F4,  23,  N_C5,  10,  N_A4,  42,  0   ,  32,  N_A5,  32,  N_A4,  19,  N_A4,  10,  N_A5,  32,  N_G5S, 21,  N_G5,  11,  N_F5S,  8,  N_F5,   8,  N_F5S, 16, 0    , 21,  N_A4S, 16,  N_D5S, 32,
      N_D5,  21,  N_C5S, 11,  N_C5,   8,  N_B4,   8,  N_C5,  16,  0    , 23,  N_F4,  16,  N_G4S, 32,  N_F4,  23,  N_A4,   8,  N_C5,  32,  N_A4,  24,  N_C5,   8,  N_E5,  42, 0    , 32,  N_A5,  32,
      N_A4,  19,  N_A4,  10,  N_A5,  32,  N_G5S, 21,  N_G5,  11,  N_F4S,  8,  N_F5,   8,  N_F4S, 16,  0    , 21,  N_A4S, 16,  N_D5S, 32,  N_D5,  21,  N_C5S, 11,  N_C5,   8, N_B4,   8,  N_C5,  16,  
      0   ,  23,  N_F4,  16,  N_G4S, 32,  N_F4,  24,  N_C5,   8,  N_A4,  32,  N_F4,  24,  N_C5,   8,  N_A4,  42,  0     ,42
    };
    //macGyver theme song!
    const uint8_t macGyver[] PROGMEM = {
      N_B3, 16, N_E4, 16, N_A4, 16, N_B4, 16, N_A4, 16, N_B3, 16, N_E4, 16, N_B3, 16, 0   , 16, N_E4, 16,  N_A4, 16, N_B4, 16,  N_A4, 16,  N_E4, 16,  N_B3, 16,  N_E4, 16,
      0   , 16, N_E4, 16, N_A4, 16, N_B4, 16, N_A4, 16, N_B3, 16, N_E4, 16, N_B3, 32, 0   , 16, N_A4, 16,  N_D5, 16, N_C5, 16,  N_D5, 16,  N_C5, 16,  N_B4, 16,  N_A4, 16,
      N_B4, 48, N_A4, 76, 0   , 4,  N_A4, 48, N_G4, 76, 0   , 4,  N_B4, 14, 0   , 2,  N_B4, 48, N_A4, 76,  0   , 4,  N_A4, 48,  N_G4, 32,  N_A4, 72,  0   , 8,   N_C5, 12,
      0   , 2,  N_C5, 12, 0   , 2,  N_C5, 12, 0   , 2,  N_C5, 12, 0   , 2,  N_C5, 12, 0   , 2,  N_C5, 12,  0   , 2,  N_B4, 64,  N_F4S, 16, N_A4, 32,  N_G4, 80,  N_C5, 14,
      0, 2,     N_C5, 32, N_B4, 32, N_C5, 16, N_B4, 16, N_A4, 16, N_G4, 16, N_E5, 32, N_A4, 64, N_C5, 14,  0   , 2,  N_C5, 80,  N_F4S, 16, N_A4, 32,  N_G4, 76,  N_C5, 14,
         0,  2, N_C5, 32, N_B4, 32, N_C5, 16, N_B4, 16, N_G4, 16, N_E5, 32, N_A4, 64, N_B4, 64, N_C5, 16,  N_B4, 16, N_A4, 16,  N_C5, 32,  N_B4, 16,  N_A4, 16,  N_D5, 32,
      N_C5, 16, N_B4, 16, N_D5, 32, N_C5, 16, N_B4, 16, N_E5, 32, N_D5, 16, N_E5, 16, N_F5S,32, N_B4, 32,  N_G5S, 48,N_F5S, 32, N_F5, 32,  N_B4, 32,  N_G5, 16,  N_E5, 16,
      N_B4, 16, N_F5S, 16,N_D5, 16, N_A4, 16, N_E5, 16, N_C5, 16, N_G4, 16, N_D5, 16, N_B4, 16, N_G4, 16,  N_C5, 16, N_E4, 16,  N_B4, 16,  N_D4, 16,  N_C5, 16,  N_B4, 16,
      N_A4, 16, N_G4, 16, N_A4S, 32,N_A4, 32, N_G5, 16, N_G4, 16, N_D5, 16, N_G4, 16, N_D5S, 16,N_D4S, 16, N_A4S, 16,N_A4, 16,  N_G4, 16,  N_G3, 16,  N_D4, 16,  N_G3, 16,
      N_D4S, 16,N_G3, 16, N_A3S, 16,N_A3, 16, N_G3, 14, 0    , 2, N_G3, 14, 0   , 2,  N_G3, 14,0     , 2,  N_G3, 14, 0   , 2,   N_G3, 14,  0   , 2,   N_G3, 14,  0   , 2,
      N_G3, 14, 0   , 2
    };
    const uint8_t Square[] PROGMEM = {
      RMDriveF, 20, //50cm
      RMTurnL, 64,  //quarter turn L
      RMDriveF, 20, //50cm
      RMTurnL, 64,  //quarter turn L
      RMDriveF, 20, //50cm
      RMTurnL, 64,  //quarter turn L
      RMDriveF, 20, //50cm
      RMTurnL, 64,  //quarter turn L
      RMDriveF, 20, //50cm
      RMTurnR, 64,  //quarter turn R
      RMDriveF, 20, //50cm
      RMTurnR, 64,  //quarter turn R
      RMDriveF, 20, //50cm
      RMTurnR, 64,  //quarter turn R
      RMDriveF, 20, //50cm
      RMTurnR, 64,  //quarter turn R
      RMDriveF, 20, //50cm
      RMHalt        //Stop
    };
    
    const uint8_t Empty[] PROGMEM = {
      //RMDriveRawBL, 50, 50, 32, //Velocity: reverse 150, Arc: Left 500mm, Time 32*128ms=4sec
      RMDriveR, 40,
      RMTurnL, 64,
      RMDriveF, 16,
      RMTurnL, 128,
      RMHalt
    };
    
    const uint8_t CallibrationRun[] PROGMEM = {
      //RMDriveRawBL, 50, 50, 32, //Velocity: reverse 150, Arc: Left 500mm, Time 32*128ms=4sec
      RMDriveR, 40,
      RMTurnL, 128,
      RMTurnL, 128,
      RMTurnR, 128,
      RMTurnR, 128,
      RMDriveF, 16,
      RMHalt
    };

    With snippets..

    const uint8_t *p_songPointer;
    int p_songNumberOfBytes;
    const uint8_t *p_scriptPointer;
    unsigned long p_undockedTimer;
    
    void playScript(const uint8_t *script);
    void playMusic(const uint8_t *song, size_t songNumberOfBytes);
    
    void Roomba632::playScript(const uint8_t *script){
    	//memcpy(script,script,sizeof(script));
    	p_scriptPointer = script;
    	p_startScriptFlag = true;
    }
    
    void Roomba632::playMusic(const uint8_t *song, size_t songNumberOfBytes){
    ...
    Read more »

  • Aye, aye, captain! Leaving dock!

    Simon Jansen04/29/2025 at 07:19 0 comments

    Call it "side projects" or "rabit holes", but we're here now and we're doing it.

    The goal is to have a button in Home Assistant for when I need to empty the bin. The button will let the Roomba come from under the bed and present itself. That way I can empty the bin or do some other work on it. When I'm done I'll press the "dock" button and send it on it's way. 

    I begin with adding a button for MQTT-autodiscover, availability handling of the button and scripting of a path to drive:

    The first problem I ran into: It won't run my scripts when docked. This is apparently by design:

    void Roomba632::handler(){
        //state machine for high level states
        switch (roombaState){
            case roombaHome: //Charging
                //Check flags in appropriate order of prirority
                if(p_cleanFlag){
                    SendCleanCommand();
                }
                else if(p_playMusicFlag){
                    p_playMusicFlag=false;
                    //SendPlayMusicCommand();
                }    
                if (dataState < chargingMessageWaitingForHeader){
                    roombaState = roombaIdle;
                    EventStateChange();
                }
                break;

     It doesn't "listen" to the scriptflag when docked. So a simple fix there. Just insert:

                else if(p_startScriptFlag){
                    roombaState = roombaPlayingScript;
                    EventStateChange();
                }
    

    Now it will undock, but will run only a part of the script. What happens is: 

    • It will drive away from the base;
    • Charging message will be lost;
    • After CHARGINGMESSAGETIMEOUT (3sec) the datastream is started;
    • Serial.write(OCStart): this puts the Roomba in "Passive" mode;
    • Driving needs "Safe" or "Full" OI-mode so driving stops;

    Nothing we can't handle. The OCStart command should only be given if the current OI-mode is Off or Passive (roomba.OImode < Safe);

    Mode command should only be given if current OI-mode < OI-mode to set. 

    This way, starting the datastream (or any other action) should not interfere with current operations. 

    -- update 03-05-2025--

    Library is now doing all of the things as mentioned above. One of the problems I ran into was the time needed between commands. Updated Roomba632 lib and latest sketch are uploaded to files.

    So now I really have my button in Home Assistant to get the Roomba from under the bed to present it's dustbin. What will I do with all the time I saved..

  • Code update

    Simon Jansen04/23/2025 at 20:37 0 comments

    I have made a few changes to the codebase. 

    • If there is no response from the Roomba (no data stream and no charging message) the data stream is started 5 times. If that has no effect, the Roomba is reset with an OC:7. This is repeated a few times. If that also does not get a response, a call-back function is triggered in the main Arduino sketch. This is a device trigger event that can get a response in Home Assistant with an automation. Although I was not able to test this properly, I can see the right commands being sent over serial (from the mock-up ESP running the same software connected to a PC);
    • There are more call-back functions. A "started_cleaning event" is added;
    • Latest libraries for I2C / MPU6050 are used;
    • Extra's library with main-loop counter is changed. The counter now is an unsigned long to prevent rollover;
    • The done cleaning event is also called in more states. For instance, when in "docking" state. This means that we can give the "docking" command while it's cleaning and the mapping will finish;
    • The ESP exposes more information for troubleshooting to Home Assistant. For instance: The amount of free memory and heap fragmentation. This should give early signs of memory leaks;
    • Newer version of ArduinoJSON library is used. The amount of allocated memory is better matched now;
    • Roomba-state on startup is now "docked" and availability states are set;
    • ESP8266 is running on 160MHz now. This seems to give the needed headroom for a stable WiFi connection;
    • Full MPU data is now available for the posing algorithm in a stable manner;

    Newer software will be added to the files section here.

    I'm ready for more!

    It's clearly not done yet. One of the weird things I can see is the amount of "main loops per second" will sometimes be doubled with some sketch-uploads. If I re-upload the same sketch the next day, the number will drop again. I was hoping it had something to do with the way I count the number of main loops, but I can't find the problem. There is no rollover. 

    Seems to be solved by removing the memory snapshot from the main loop. Running at ~25k loops/sec when docked now and ~10k when cleaning with full logging, pose calculation and magic.

    It has plenty of spare processing at the moment, so I will ignore it for now. Any ideas? 

    Next up, scripting?

  • Are we having fun yet?!

    Simon Jansen04/16/2025 at 10:03 0 comments

    It's been a while. But we're back!

    The roomba has been doing it's scheduled job for quite a while. And I must say, I'm impressed with the stability of the whole setup. 

    The battery needed replacing after two years. The roomba also would quit outputting serial data a few times and needed a reset. But it would run beautifully for months at a time! 

    In the meantime the house got some changes and additional occupants. The complete (digital) infrastructure was also changed,. The mapping service now runs on a different / separate Raspberry Pi. Home Assistant had a complete reinstall and runs on HAOS. 

    The roomba has been in storage for almost a year during construction works. But when I turned it back on the MQTT-autodiscovery worked like a charm!

    It has been cleaning the bedroom now. 

    I'm running into a few stability now and usability issues which I want to address:

    • Unexplained crashes. The ESP would need a hard reset;
    • WiFi dropouts (connected to the crashes?);
    • Watchdogtimer does not trigger and reset the ESP; 
    • If "done cleaning" event is not received, mapping algorithm on Pi also won't stop;
    • State changes and availability messages are not ideal; 
    • It needs a "come out from under the bed so I can change your dustbin" routine;
    • MQTT-autodiscover should be device-based instead of component based;

    So stay tuned! We're back!

  • Logs and maps (part 5)

    Simon Jansen09/06/2022 at 17:33 0 comments

    Quick update on the update

    Writing live map image to buffer instead of file. Reduces load quite a bit. 

    img_buf = io.BytesIO()
    plt.savefig(img_buf, dpi=50, format='png')
    img_buf.seek(0)
    poseLogImageString = img_buf.read()
    poseLogImageByteArray = bytes(poseLogImageString)
    client.publish(MQTT_Config.HA_name + "/camera/" + DeviceName + "_" + DeviceID + "/map",poseLogImageByteArray,0,False)
    img_buf.close()

    It's still not fast enough for a real live feed though. Other small changes I made are: using "zorder" to make sure the scatter plot with dots is on top. Changing the size of the dots with s=120 and setting the resolution for both live-map and post-process-map with "dpi".

    Updated python script is uploaded.

    I guess, now it's time to do the deep dive into the accuracy of the positional data...

  • Logs and maps (part 5)

    Simon Jansen09/03/2022 at 10:22 0 comments

    Quick update: better live mapping:

    I noticed the live mapping would slow down and fail. This was because of the way I used the scatter plot function. I added a new layer with just one dot for each value. 

    Now I clear the plot and map out the complete path it took with a red dot on its last position. This works much better.

    Updated python script is uploaded to files.

  • Logs and maps (part 4)

    Simon Jansen09/02/2022 at 10:01 0 comments

    Live mapping and better maps!

    I added a live mapping feature. While cleaning, every 10 position readings are added to a scatter plot. The image of the plot is then published by MQTT.

    I tried omitting saving to file and using an io-buffer instead, but I can't get this to work. This means it's not fast enough to send an updated plot every 500ms (the update-rate of positional data). I have it set now to post every 10 datapoints. So an updated plot every 5 secs.

    plt.scatter(poseJson['X'],poseJson['Y'], color='r')
                
    #-every 10 seconds records?
    liveFeedCounter=liveFeedCounter+1
    if liveFeedCounter == 10:
        liveFeedCounter = 0
        #img_buf = io.BytesIO()
        #plt.savefig(img_buf, format='png')
        #poseLogImageString = img_buf.read()
                
        plt.savefig("live.png",format='png')
        poseLogImageFile = open("live.png", "rb")
        poseLogImageString = poseLogImageFile.read()
        poseLogImageByteArray = bytes(poseLogImageString)
        client.publish(MQTT_Config.HA_name + "/camera/" + DeviceName + "_" + DeviceID + "/map",poseLogImageByteArray,0,False)
        poseLogImageFile.close()
        #img_buf.close()

    Also, the map created after a clean is much better. It's an overlay of a 2D histogram to show where the roomba has spent most of it's time. Then the positional points as scattered dots to indicate speed. And a line to get a sense of the completed track. This is all presented with equal axis to not distort the map and with a grid to get a sense of scale.

    The plot should also be cleared in between cleaning cycles with plt.close()

    plotData = np.array(plotData)
    x,y = plotData.T
    plt.close()
    plt.hist2d(x,y, bins=25, cmin=1, norm=col.LogNorm())
    #plt.plot(x,y,linestyle='--')
    plt.scatter(x,y,marker='.',color='r')
    plt.plot(x,y,linestyle='-',color='b')
    #OutlineX = []
    #OutlineX = []
    #plt.plot(OutlineX,OutlineY)
    plt.axis('equal')
    plt.grid(True)
    #plt.xlim([-2000, 8000])
    #plt.ylim([-5000, 5000])          
    plt.savefig(poseLogFilename + ".png", format='png')
    plt.close()
    plt.axis('equal')
    plt.grid(True)
    # - publish map
    poseLogImageFile = open(poseLogFilename+".png", "rb")
    poseLogImageString = poseLogImageFile.read()
    poseLogImageByteArray = bytes(poseLogImageString)
    client.publish(MQTT_Config.HA_name + "/camera/" + DeviceName + "_" + DeviceID + "/map",poseLogImageByteArray,0,False)

    Full python script is uploaded to files.

  • Logs and maps (part 3)

    Simon Jansen08/30/2022 at 18:01 0 comments

    Aaaand, it's done!

    Using numpy and matplotlib. It's not pretty, but it works! Actually I wanted to use a heatmap. But this will work for now.

    Full python code:

    #!/usr/bin/env python
    # (c) 2022-08-30 S.E.Jansen.
    
    # MQTT-layer for Roomba logging
        # Listen for the start and stop of cleaning event
        # Log raw and positional data to file
        # Loop closing and error correction on positional data
        # Render map from positional data as a imagefile
        # Post imagefile to camera entity for Home Assistant
    
    import time
    import datetime
    import MQTT_Config
    import json
    import paho.mqtt.client as paho
    import csv
    import matplotlib.pyplot as plt
    import numpy as np
    
    #Roomba device info for Home Assistant autoconfig
    DeviceName = 'Roomba632'
    DeviceID = '01'
    DeviceManufacturer = 'iRobot'
    DeviceModel = '632'
    
    poseLogFilename = 'poseDummy'
    poseLogFile = open(poseLogFilename + ".csv", 'w')
    poseLogWriter = csv.writer(poseLogFile)
    rawLogFilename = 'rawDummy'
    rawLogFile = open(rawLogFilename + ".csv", 'w')
    rawLogWriter = csv.writer(rawLogFile)
    
    #MQTT callback functions
    def on_message(client, userdata, message):
        data = message.payload
        global poseLogFilename
        global poseLogFile
        global poseLogWriter
        global rawLogFilename
        global rawLogFile
        global rawLogWriter
    
        if message.topic == MQTT_Config.HA_name + "/button/" + DeviceName + "_" + DeviceID + "/set":
            command=data.decode("utf-8")
            # start logging
            if  command == "Clean":
                # open logfile
                print("Started cleaning cycle")
                poseLogFile.close()
                poseLogFilename = "./Roomba/Logs/" + datetime.datetime.now().isoformat('_', 'seconds') + "_pose"
                poseLogFile = open(poseLogFilename + ".csv", 'w')
                poseLogWriter = csv.writer(poseLogFile)
                rawLogFile.close()
                rawLogFilename = "./Roomba/Logs/" + datetime.datetime.now().isoformat('_', 'seconds')+ "_raw"
                rawLogFile = open(rawLogFilename + ".csv", 'w')
                rawLogWriter = csv.writer(rawLogFile)
        if message.topic == MQTT_Config.HA_name + "/device_automation/" + DeviceName + "_" + DeviceID + "/event_DoneCleaning":
            event=data.decode("utf-8")
            # start logging
            if event == "done cleaning":
                # close log
                print("Cleaning cycle is done. Save logfile and start postprocess")
                poseLogFile.close()
                poseLogFile = open('poseDummy.csv', 'w')
                poseLogWriter = csv.writer(poseLogFile)
                rawLogFile.close()
                rawLogFile = open('rawDummy.csv', 'w')
                rawLogWriter = csv.writer(rawLogFile)
                # plot image from CSV file.
                with open(poseLogFilename + ".csv", 'r') as plotDataFile:
                    plotData = list(csv.reader(plotDataFile, delimiter=",",quoting=csv.QUOTE_NONNUMERIC))
                plotData = np.array(plotData)
                x,y = plotData.T
                plt.scatter(x,y)
                #plt.imshow(plotData, cmap='hot', interpolation='nearest')
                plt.savefig(poseLogFilename + ".png", format='png')
                # - publish map
                poseLogImageFile = open(poseLogFilename+".png", "rb")
                poseLogImageString = poseLogImageFile.read()
                poseLogImageByteArray = bytes(poseLogImageString)
                client.publish(MQTT_Config.HA_name + "/camera/" + DeviceName + "_" + DeviceID + "/map",poseLogImageByteArray,0,False)
        if message.topic == MQTT_Config.HA_name + "/device/roomba/raw":
            rawJson=json.loads(data.decode("utf-8"))
            # start logging
            #print(rawJson['mEL'])            
            #print(rawJson['mER'])            
            #print(rawJson['rTh'])            
            #print(rawJson['Xacc'])            
            #print(rawJson['Yacc'])            
            #print(rawJson['Yaw'])
            if not rawLogFile.closed:
                rawLogData = [rawJson['mEL'],rawJson['mER'],rawJson['rTh'],rawJson['Xacc'],rawJson['Yacc'],rawJson['Yaw']]
                rawLogWriter.writerow(rawLogData)
            else:
                print("Raw logfile was closed")
        if message.topic == MQTT_Config.HA_name + "/device/roomba/pose":
            poseJson=json.loads(data.decode("utf-8"))
            # start logging
            #print(poseJson['X'])            
            #print(poseJson['Y'])
            #print(poseJson['rTh']) 
            if not poseLogFile.closed:
                #poseLogData = [poseJson['X'],poseJson['Y'],poseJson['rTh']]
                poseLogData = [poseJson['X'],poseJson['Y']]
                poseLogWriter.writerow(poseLogData)
            else:
                print("Pose logfile was closed")
    #call back function for MQTT connection
    def on_connect(client, userdata, flags, rc):
        if rc==0:
     client.connected_flag=...
    Read more »

  • Logs and maps (part 2)

    Simon Jansen08/29/2022 at 18:59 0 comments

    Captains log, stardate...

    Nice! logging is done. I'll get csv-logfiles on every clean cycle. 

    poseLogFilename = 'poseDummy'
    poseLogFile = open(poseLogFilename + ".csv", 'w')
    poseLogWriter = csv.writer(poseLogFile)
    rawLogFilename = 'rawDummy'
    rawLogFile = open(rawLogFilename + ".csv", 'w')
    rawLogWriter = csv.writer(rawLogFile)
    
    #MQTT callback functions
    def on_message(client, userdata, message):
        data = message.payload
        global poseLogFilename
        global poseLogFile
        global poseLogWriter
        global rawLogFilename
        global rawLogFile
        global rawLogWriter
    
        if message.topic == MQTT_Config.HA_name + "/button/" + DeviceName + "_" + DeviceID + "/set":
            command=data.decode("utf-8")
            # start logging
            if  command == "Clean":
                # open logfile
                print("Started cleaning cycle")
                poseLogFile.close()
                poseLogFilename = "./Roomba/Logs/" + datetime.datetime.now().isoformat('_', 'seconds') + "_pose"
                poseLogFile = open(poseLogFilename + ".csv", 'w')
                poseLogWriter = csv.writer(poseLogFile)
                rawLogFile.close()
                rawLogFilename = "./Roomba/Logs/" + datetime.datetime.now().isoformat('_', 'seconds')+ "_raw"
                rawLogFile = open(rawLogFilename + ".csv", 'w')
                rawLogWriter = csv.writer(rawLogFile)
        if message.topic == MQTT_Config.HA_name + "/device_automation/" + DeviceName + "_" + DeviceID + "/event_DoneCleaning":
            event=data.decode("utf-8")
            # start logging
            if event == "done cleaning":
                # close log
                print("Cleaning cycle is done. Save logfile and start postprocess")
                poseLogFile.close()
                poseLogFile = open('poseDummy.csv', 'w')
                poseLogWriter = csv.writer(poseLogFile)
                rawLogFile.close()
                rawLogFile = open('rawDummy.csv', 'w')
                rawLogWriter = csv.writer(rawLogFile)
                # plot image from CSV file.
    
        if message.topic == MQTT_Config.HA_name + "/device/roomba/raw":
            rawJson=json.loads(data.decode("utf-8"))
            # start logging
            #print(rawJson['mEL'])            
            #print(rawJson['mER'])            
            #print(rawJson['rTh'])            
            #print(rawJson['Xacc'])            
            #print(rawJson['Yacc'])            
            #print(rawJson['Yaw'])
            if not rawLogFile.closed:
                rawLogData = [rawJson['mEL'],rawJson['mER'],rawJson['rTh'],rawJson['Xacc'],rawJson['Yacc'],rawJson['Yaw']]
                rawLogWriter.writerow(rawLogData)
            else:
                print("Raw logfile was closed")
        if message.topic == MQTT_Config.HA_name + "/device/roomba/pose":
            poseJson=json.loads(data.decode("utf-8"))
            # start logging
            #print(poseJson['X'])            
            #print(poseJson['Y'])
            #print(poseJson['rTh']) 
            if not poseLogFile.closed:
                poseLogData = [poseJson['X'],poseJson['Y'],poseJson['rTh']]
                poseLogWriter.writerow(poseLogData)
            else:
                print("Pose logfile was closed")

    Only thing left now is to plot the pose-log to an image-file and post that with MQTT....

    Anyone who knows how??

  • Logs and maps (part 1)

    Simon Jansen08/28/2022 at 19:43 0 comments

    Always fun when your wild ideas seem quite possible

    I'm doing some experimenting with python on the Pi to log positioning info. I edited the sketch to output a stream of calculated pose data to construct a map with. And a stream with raw positional sensordata to do postprocessing, analysis and hopefully improve the accuracy of the system. 

    The python daemon will listen to these streams and make separate log-files. These files are opened/created and closed/saved on the start and end of a cleaning cycle. 

    After a cleaning cycle is done, the pose-log will be used to create a map-image. This image can be sent with MQTT and will display in Home Assistant. This seems to work brilliantly when testing with a dummy image:

    I'm very curious if someone has a better way of getting something like a map in Home Assistant.

    The MQTT autodiscovery and setup for the map and topics to subscribe to:

    #Roomba device info for Home Assistant autoconfig
    DeviceName = 'Roomba632'
    DeviceID = '01'
    DeviceManufacturer = 'iRobot'
    DeviceModel = '632'
    
    # MQTT autoconfig
    device = {}
    device['identifiers'] = DeviceName + "_" + DeviceID
    device['name'] = DeviceName + "_" + DeviceID
    device['manufacturer'] = DeviceManufacturer
    #device['sw_version'] = ""
    device['model'] = DeviceModel
    # - Camera entity for maps
    data = {}
    data['name'] = "Map of last clean"
    data['topic'] = MQTT_Config.HA_name + "/camera/" + DeviceName + "_" + DeviceID + "/map"
    #data['availability_topic'] = MQTT_Config.HA_name + "/camera/" + DeviceName + "_" + DeviceID + "/available"
    #data['payload_available'] = 'yes'
    #data['payload_available'] = 'no'
    data['unique_id'] = DeviceName + "_" + DeviceID + "_Map"
    data['icon'] = "mdi:map-outline"
    data['device'] = device
    client.publish(MQTT_Config.HA_name + "/camera/" + DeviceName + "_" + DeviceID + "/config",json.dumps(data),0,True)
        
    # MQTT subscribe to command topics to receive commands
    # - Start clean cycle:
    client.subscribe(MQTT_Config.HA_name + "/device_automation/" + DeviceName + "_" + DeviceID + "/event_DoneCleaning",0)
    # - Event for ending of clean cylce:
    client.subscribe(MQTT_Config.HA_name + "/button/" + DeviceName + "_" + DeviceID + "/set",0)
    # - Positional data calculated on roomba:
    client.subscribe(MQTT_Config.HA_name + "/device/roomba/pose",0)
    # - Raw sensordata for postprocess calculation of position
    client.subscribe(MQTT_Config.HA_name + "/device/roomba/raw",0)

    I'm able to listen to the pose and raw topics and can also deconstruct the JSON message. It shouldn't be that hard to log to file and plot something of a map... to be continued

View all 32 project logs

Enjoy this project?

Share

Discussions

Simon Jansen wrote 04/30/2025 at 07:52 point

Just curious, anyone out there using these instructions and/or library? Are you using the Roomba part or the Home Assistant part? Did some of the info here solve any problem or did it create new ones?

  Are you sure? yes | no

Simon Jansen wrote 08/15/2022 at 18:43 point

I'm not using anything proprietary though. Just the OPEN API which iRobot has mostly/fully documented and encourages tinkering. One of the reasons why I went for an iRobot device in the first place.

It's a shame you can't document and share your progress any further. I'm sure never to buy anything from Shark in your support :)

  Are you sure? yes | no

Simon Jansen wrote 08/15/2022 at 18:38 point

Thanks for the support Jon and Holy Sh*t for your project dude! Not cool!

  Are you sure? yes | no

Jon Steel wrote 08/15/2022 at 16:40 point

Great Work! You have a nice approach that will hopefully not get your toes stepped on, unlike what has happened with my project.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates