Cleaning specific room and zones with multi level map (roborock)

Yes the linked list on githum is mine.
My intention is indeed to add the newly found commands when I have some spare time & lust.
But where you have output for not yet listed commands feel free to add them :slight_smile: I only have the v1 device, so most of the newer commands are not availabe for mine.

I expect the recover_multi_map has as parameter a map id, or a array with map id e.g. [[mapid]]

Here is my longest list of available commands for the roborock vacuums.
To my knowledge the get_ you can always try without breaking anything.

{
“model”: “roborock.vacuum.s5e”,
“miot_items”: 0,
“miio_items”: 88,
“commands”: [
“set_clean_motor_mode”,
“set_camera_status”,
“set_clean_sequence”,
“set_homesec_password”,
“set_custom_mode”,
“set_water_box_custom_mode”,
“set_timer”,
“set_dnd_timer”,
“set_timezone”,
“set_app_timezone”,
“set_carpet_mode”,
“set_lab_status”,
“set_server_timer”,
“set_fds_endpoint”,
“set_led_status”,
“set_customize_clean_mode”,
“set_ignore_identify_area”,
“set_timer_dup”,
“set_map_name”,
“get_camera_status”,
“get_timer_summary”,
“get_timer_detail”,
“get_testid”,
“get_clean_sequence”,
“get_turn_server”,
“get_device_sdp”,
“get_device_ice”,
“get_multi_map”,
“get_prop”,
“get_status”,
“get_map”,
“get_map_v1”,
“get_map_v2”,
“get_custom_mode”,
“get_water_box_custom_mode”,
“get_clean_summary”,
“get_clean_record”,
“get_clean_record_map”,
“get_clean_record_map_v2”,
“get_consumable”,
“get_timer”,
“get_dnd_timer”,
“get_serial_number”,
“get_log_upload_status”,
“get_sound_progress”,
“get_current_sound”,
“get_timezone”,
“get_carpet_mode”,
“get_sound_volume”,
“get_fw_features”,
“get_fresh_map”,
“get_persist_map”,
“get_room_mapping”,
“get_recover_maps”,
“get_recover_map”,
“get_map_status”,
“get_segment_status”,
“get_server_timer”,
“get_network_info”,
“get_led_status”,
“get_customize_clean_mode”,
“get_multi_maps_list”
]

1 Like

Thanks to the updates of the binding, some rules are obsolete as the binding is doing the heavy lifting. The rules need the newest binding.

Please check earlier posts - there are many answers.

karaf

update org.openhab.binding.miio https://ci.openhab.org/job/openHAB-Addons/lastSuccessfulBuild/artifact/bundles/org.openhab.binding.miio/target/org.openhab.binding.miio-3.1.0-SNAPSHOT.jar

Items

Switch Rocky_VacuumOnOff  		// linked to :actions#vacuum
String Rocky_State 						// linked to :status#state
String Rocky_ExecuteCommand 				// linked to :actions#rpc
String Rocky_Room16					// linked to :info#room_mapping with profile JSONPATH; Profile Configuration: $.[0][1]

Group:Switch:OR(ON,OFF)	gSaugSwitches		"Group with one switch for each room"
Switch	HH_UG_GZ_Room1  (gSaugSwitches) 
Switch	HH_UG_KE_Room2  (gSaugSwitches)

And some Switches for the Zones
Switch	HH_EG_ZO_esstisch  (gSaugSwitches)
Switch	HH_EG_ZO_arbeitsflaeche  (gSaugSwitches)


String Rocky_Etage "The current level"
String Vacuum_Zone "The zone to clean"


String Rocky_Saugbefehle "Please clean..." 
/* in the UI, you have to configure Rocky_Saugbefehle with stateDescription & Command options
		EG_KU=Küche
		EG_EZ=Esszimmer
		EG_FL=Eingang
		EG_GT=Gästetoilette
		EG_BU=Büro
		SP_UT=Unterm Tisch
		EG_ZO_esstisch=unterm Tisch
		EG_ZO_arbeitsflaeche=Küchenzeile
*/

map

838001020681=EG
838001020689=UG
838001020686=E1
838001020694=E2
EG_BU=19
EG_FL=24
EG_ES=21
EG_KU=18
EG_GT=17
EG_SA=16
EG_BUFLGT=19,24,17
esstisch=-8.9,2.2,2.8,2.5,1
arbeitsflaeche=-4.2,1.3,4.2,1.3,1
grundreinigung=esstisch,arbeitsflaeche
=UNDEF

rules

// this is to en- or disable  debugging of a specific set of rules. 
val logName = "Rocky" // Name of the set of rules
var loggingInfo = true // if loggingInfo =true , this set of rules can be debugged without getting debug-messages from every rule.
var Timer Pause_Timer = null

// Function to calcilate from m to values the vacuum understands
// see https://community.openhab.org/t/xiaomi-roborock-zone-cleanup-rule/55138/2?u=peterk


val Functions$Function1<String, String> getZoneCoordinates =
[zone |
  var parameters = zone.split(',')
  // Docking point start position
  val double x = 25500.0;
  val double y = 25500.0;
  // Bottom (y) left (x)
  val double b = Double::parseDouble(parameters.get(0)) * 1000.0 + x
  val double l = Double::parseDouble(parameters.get(1)) * 1000.0 + y
  // Top (y) right (x)
  val double t = b + Double::parseDouble(parameters.get(2)) * 1000.0
  val double r = l + Double::parseDouble(parameters.get(3)) * 1000.0
  // Build zone coordinates (and number of times to scan)
  val coordinates = String::format("[%.0f,%.0f,%.0f,%.0f,%s]", b, l, t, r, parameters.get(4));
  coordinates
]

/////////////////////////////////////////////////////////////////////
// The rules to check for the floor each time when positioning is done
// The idea is from here https://community.openhab.org/t/xiaomi-roborock-room-clean-multi-level-map/108850/13
///////////////////////////////////////////////////////////////////

rule "Rocky_Room16 changed"
when
	Item Rocky_Room16 changed
then
	// update the current floor	
	Rocky_Etage.postUpdate(transform("MAP", "Rocky_Etage.map", newState.toString))
end

/////////////////////////////////////////////////////////////////////
// The rules to control the vacuum
///////////////////////////////////////////////////////////////////

rule "Vacuum should go to work"
when
	Member of gSaugSwitches  received command ON
then
	// For debugging: Set the name of this rule
	val ruleLogName = "Vacuum should go to work ( " + triggeringItem.name + ") "
	// This is how a debuglog looks like.... I also use this for documentation of the code
	if (loggingInfo == true ) logInfo(logName, ruleLogName + "Set the floor to UNDEF and start cleaning.")
	

		// Start cleaning - this will trigger positionin if neccesary. If positioning is not neccessary, the bindig will update Room16 anyway
	Rocky_VacuumOnOff.sendCommand(ON)
	
	Pause_Timer = createTimer( now.plusSeconds(5) ,[|
		// After leaving the dock (5 seconds)- ensure there is a change in the floor if there was already a change prior, this will do nothig bad
		Rocky_Room16.postUpdate("[]")
	 ])
	
end 

// When the "check the floor" is done
rule "Go to your room"
when
	Item Rocky_Etage changed 
then

	val ruleLogName = "Go to your room " 
	if (loggingInfo == true ) logInfo(logName, ruleLogName + "Triggered; Rocky_Etage.state = " + Rocky_Etage.state )
	
	if ( Rocky_Etage.state.toString == "UNDEF" ) {
		if (loggingInfo == true ) logInfo(logName, ruleLogName + "UNDEF - Nothing to do")
		return
	}
	
	
	// If there is a command to clean a room
	if (gSaugSwitches.state == ON) {
		
		// stop cleaning for new instructions
		if  ( (Rocky_ControlVacuum.state != "pause" ) &&  (Rocky_ControlVacuum.state != "dock" ) ) {
			Rocky_ControlVacuum.sendCommand("pause")
		}
		
		if (loggingInfo == true ) logInfo(logName, ruleLogName + "Check which switch was used latest")
		val WelcherSaugSwitch = gSaugSwitches.members.filter[i|i.state==ON].sortBy[lastUpdate].last

		if (loggingInfo == true ) logInfo(logName, ruleLogName + "found: " + WelcherSaugSwitch.name)
		
		// only do the command once
		gSaugSwitches.sendCommand(OFF)
		
		// get level and room from name of the switch
		val String SollEtage = WelcherSaugSwitch.name.toString.split('_').get(1) 
		val String SollRaum = WelcherSaugSwitch.name.toString.split('_').get(2)
		
		if (loggingInfo == true ) logInfo(logName, ruleLogName + "Floor: " + SollEtage + "; Room: " + SollRaum)


		//if vacuum is in correct floor. 
		if ( SollEtage == Rocky_Etage.state.toString ) {
			
			if (loggingInfo == true ) logInfo(logName, ruleLogName + "Etage OK")
			
			// if Zonecleaning was activated go to this rule and exit
			if ( SollRaum ==  "ZO" ) {
				val String SollZone = WelcherSaugSwitch.name.toString.split('_').get(3)
				if (loggingInfo == true ) logInfo(logName, ruleLogName + "Zonenreinigung von " + SollZone)
				
				Pause_Timer = createTimer( now.plusSeconds(3) ,[|
					if (loggingInfo == true ) logInfo(logName, ruleLogName + "Los gehts zu Zone " + SollZone)
					// Finally.... send to the Zone
					Vacuum_Zone.sendCommand(SollZone)
				 ])
			}  else { 
							
				if (loggingInfo == true ) logInfo(logName, ruleLogName + "Raumreinigung")
				// OK... Roomcleaning it is

				// get room ID from MAP
				val RaumNummer =  transform("MAP", "Rocky_Etage.map", SollEtage + "_" + SollRaum)		
				
				
				Pause_Timer = createTimer( now.plusSeconds(3) ,[|
					if (loggingInfo == true ) logInfo(logName, ruleLogName + "Go to your room " + RaumNummer)
					// Finally.... send the room
					Rocky_VacuumRoom.sendCommand(RaumNummer)
				 ])
			}
		} else {
			if (loggingInfo == true ) logInfo(logName, ruleLogName + "Falsche Etage. Ist= " + Rocky_Etage.state + ", Soll: " + SollEtage )
		} 
	} else {
		if (loggingInfo == true ) logInfo(logName, ruleLogName + "Thank you for the information of the current floor.")
	}
end

rule "Vacuum Zone control"
when
  Item Vacuum_Zone received command
then
	logInfo(logName, "Vacuum command: {}", receivedCommand.toString)

	// The final command send to the vacuum
	var String command = ""
	// Get zone coordinates
	var String zone = transform("MAP", "Rocky_Etage.map", receivedCommand.toString)
	// We still don't know if this is single or multiple zone
	var parameters = zone.split(',')
	try {
	  // Single zone only have numbers
	  Double::parseDouble(parameters.get(0))
	  command = getZoneCoordinates.apply(zone)
	} catch (Throwable t) {
	  // This is multi zone
	  parameters.forEach[string z|
		zone = transform("MAP", "Rocky_Etage.map.map", z)
		command = command + getZoneCoordinates.apply(zone) + ","
	  ]
	  // Remove last ','
	  command = command.substring(0, command.length - 1)
	}
	command = String::format("[%s]", command)
	logInfo(logName, "Clean {} zone coordinates {}", receivedCommand.toString, command)
	Rocky_ExecuteCommand.sendCommand('app_zoned_clean' + command)

end

// The sinlge switches are nice if you use e.g. Alexa. In the UI, it is nicer to have one string with command options
// This rlule will get the correct switch from the string command
rule "Saugbefehl generieren aus String"
when
	Item Rocky_Saugbefehle received command
then
	val ruleLogName = "Saugbefehl generieren aus String (" + receivedCommand + ") "
	if (loggingInfo == true ) logInfo(logName, ruleLogName + "suche HH_" + receivedCommand + "*")
	
	val SaugbefehlItem = gSaugSwitches.members.filter[s| s.name.startsWith("HH_" + receivedCommand)].head as SwitchItem
	
	if (loggingInfo == true ) logInfo(logName, ruleLogName + "Item gefunden: " + SaugbefehlItem.name)
	SaugbefehlItem.sendCommand(ON)
end
1 Like

Good evening, I have a Roborock s7 and would also like to try the great project. Unfortunately I can not get the miio binding updated to snapshot. I use openhab 3.0.1. Do I have to update openhab to snapshot as well?

No, I think with 3.0.1 you should be fine

Edit: if you have the Chanel

room_mapping

You are definitively ok.

No, there is no channel called raum_mapping here. I can only send the command “get_room_mapping” via actionCommand.

can’t you update binding in karaf?

If not, you can use an older version in this thread - but latest one is stable compared to the older ones.

Unfortunately, an update to snapshot does not work. I will test an older version first.