I’d like to share a JavaScript-based integration of Mammotion Luba robotic mowers for openHAB. This implementation is based on the Python library PyMammotion and has been converted to JavaScript for direct integration into openHAB’s scripting engine.
Important Notes
This integration is currently untested
I don’t personally own a Mammotion Luba mower yet, so this implementation is purely theoretical based on the API documented in the PyMammotion project. I’m sharing this code hoping that:
- Someone with a Luba mower can test and improve it
- Anyone who has already tried integrating Luba mowers with openHAB can share their experience
- It can serve as a starting point for a working integration
Features
The integration includes:
- Authentication with Mammotion cloud API
- Status monitoring (position, battery, work statistics)
- Control functions (start, stop, park)
- Zone-based mowing
- Regular status updates
- Command handling via openHAB Items
The Code
// Mammotion Luba Mower Integration for openHAB
// Based on https://github.com/mikey0000/PyMammotion
// Author: Community Contribution
// Version: 0.1 (BETA)
const { rules, items, time, actions, triggers } = require('openhab');
// Configuration for Mammotion Luba
const LUBA_CONFIG = {
email: "your-email@example.com",
password: "your-password",
device_id: "your-device-id",
api_base_url: "https://api.mammotion.com"
};
// Token management
let accessToken = null;
let tokenExpiry = 0;
// Authentication and token management
async function getAuthToken() {
// Check if token is still valid
if (accessToken && Date.now() < tokenExpiry) {
return accessToken;
}
try {
const authResponse = await actions.HTTP.sendHttpPostRequest(
`${LUBA_CONFIG.api_base_url}/v1/user/login`,
"application/json",
JSON.stringify({
email: LUBA_CONFIG.email,
password: LUBA_CONFIG.password
}),
{}
);
const authData = JSON.parse(authResponse);
if (authData.code === 0 && authData.data && authData.data.access_token) {
accessToken = authData.data.access_token;
// Token expiry (typically 24 hours)
tokenExpiry = Date.now() + (23 * 60 * 60 * 1000);
console.log("Luba: Authentication successful");
return accessToken;
} else {
console.error(`Luba: Authentication error: ${JSON.stringify(authData)}`);
return null;
}
} catch (error) {
console.error(`Luba: Authentication error: ${error}`);
return null;
}
}
// Mower functions
async function getMowerStatus() {
const token = await getAuthToken();
if (!token) return null;
try {
const statusResponse = await actions.HTTP.sendHttpGetRequest(
`${LUBA_CONFIG.api_base_url}/v1/device/info?device_id=${LUBA_CONFIG.device_id}`,
"application/json",
{ "Authorization": `Bearer ${token}` }
);
const statusData = JSON.parse(statusResponse);
if (statusData.code === 0 && statusData.data) {
return statusData.data;
} else {
console.error(`Luba: Status retrieval failed: ${JSON.stringify(statusData)}`);
return null;
}
} catch (error) {
console.error(`Luba: Error retrieving status: ${error}`);
return null;
}
}
async function sendMowerCommand(command, params = {}) {
const token = await getAuthToken();
if (!token) return false;
const commandData = {
device_id: LUBA_CONFIG.device_id,
command: command,
...params
};
try {
const response = await actions.HTTP.sendHttpPostRequest(
`${LUBA_CONFIG.api_base_url}/v1/device/command`,
"application/json",
JSON.stringify(commandData),
{ "Authorization": `Bearer ${token}` }
);
const result = JSON.parse(response);
if (result.code === 0) {
console.log(`Luba: Command '${command}' successfully sent`);
return true;
} else {
console.error(`Luba: Command '${command}' failed: ${JSON.stringify(result)}`);
return false;
}
} catch (error) {
console.error(`Luba: Error sending command '${command}': ${error}`);
return false;
}
}
// Specific commands
async function startMower() {
return await sendMowerCommand("start_mowing");
}
async function stopMower() {
return await sendMowerCommand("stop");
}
async function parkMower() {
return await sendMowerCommand("park");
}
async function setZone(zoneId) {
return await sendMowerCommand("set_zone", { zone_id: zoneId });
}
async function startZone(zoneId) {
return await sendMowerCommand("start_zone", { zone_id: zoneId });
}
async function getBatteryStatus() {
const status = await getMowerStatus();
return status ? status.battery_level : null;
}
async function getErrorLogs() {
const token = await getAuthToken();
if (!token) return null;
try {
const logsResponse = await actions.HTTP.sendHttpGetRequest(
`${LUBA_CONFIG.api_base_url}/v1/device/logs?device_id=${LUBA_CONFIG.device_id}`,
"application/json",
{ "Authorization": `Bearer ${token}` }
);
const logsData = JSON.parse(logsResponse);
if (logsData.code === 0 && logsData.data) {
return logsData.data;
} else {
console.error(`Luba: Error log retrieval failed: ${JSON.stringify(logsData)}`);
return null;
}
} catch (error) {
console.error(`Luba: Error retrieving error logs: ${error}`);
return null;
}
}
// Status conversion functions
function mapMowerStatus(statusCode) {
const statusMap = {
0: "INACTIVE",
1: "MOWING",
2: "PAUSED",
3: "RETURNING",
4: "CHARGING",
5: "ERROR",
6: "FIRMWARE_UPDATE"
};
return statusMap[statusCode] || "UNKNOWN";
}
function mapBatteryStatus(batteryLevel) {
if (batteryLevel === null || batteryLevel === undefined) return "UNKNOWN";
if (batteryLevel <= 15) return "LOW";
if (batteryLevel <= 50) return "MEDIUM";
return "HIGH";
}
// OpenHAB Item updates
async function updateOpenHABItems() {
const status = await getMowerStatus();
if (!status) {
console.error("Luba: Could not retrieve status for item update");
return;
}
// Map status to openHAB Items
try {
items.getItem("Luba_Status").postUpdate(mapMowerStatus(status.status));
items.getItem("Luba_BatteryLevel").postUpdate(status.battery_level);
items.getItem("Luba_ErrorCode").postUpdate(status.error_code || 0);
items.getItem("Luba_CurrentZone").postUpdate(status.current_zone || "");
// Operating time and other statistics
if (status.statistics) {
items.getItem("Luba_TotalWorkingHours").postUpdate(status.statistics.total_working_hours || 0);
items.getItem("Luba_TotalDistance").postUpdate(status.statistics.total_distance || 0);
}
// Current position
if (status.position) {
items.getItem("Luba_Position").postUpdate(`${status.position.latitude},${status.position.longitude}`);
}
console.log("Luba: Items successfully updated");
} catch (error) {
console.error(`Luba: Error updating items: ${error}`);
}
}
// Rule for automatic updates
rules.JSRule({
name: "Luba_Status_Updates",
description: "Updates the status of the Luba mower every 5 minutes",
triggers: [triggers.GenericCronTrigger("0 */5 * * * ?")],
execute: async (event) => {
console.log("Luba: Performing scheduled status update");
await updateOpenHABItems();
}
});
// Rule for command processing
rules.JSRule({
name: "Luba_Command_Handler",
description: "Processes commands for the Luba mower",
triggers: [
triggers.ItemCommandTrigger("Luba_Command")
],
execute: async (event) => {
const command = event.receivedCommand.toString();
let result = false;
console.log(`Luba: Processing command: ${command}`);
switch (command) {
case "START":
result = await startMower();
break;
case "STOP":
result = await stopMower();
break;
case "PARK":
result = await parkMower();
break;
case "REFRESH":
await updateOpenHABItems();
result = true;
break;
default:
// Check for zone command (e.g. "ZONE_1")
if (command.startsWith("ZONE_")) {
const zoneId = command.split("_")[1];
result = await startZone(zoneId);
} else {
console.warn(`Luba: Unknown command: ${command}`);
items.getItem("Luba_CommandResult").postUpdate("UNKNOWN_COMMAND");
return;
}
}
items.getItem("Luba_CommandResult").postUpdate(result ? "SUCCESS" : "FAILED");
// Update status after successful command execution
if (result) {
// Short delay for status update
setTimeout(async () => {
await updateOpenHABItems();
}, 5000);
}
}
});
Required openHAB Items
// Luba Mower Items
String Luba_Command "Mower Command"
String Luba_CommandResult "Command Result [%s]"
String Luba_Status "Mower Status [%s]" <lawnmower>
Number Luba_BatteryLevel "Battery [%d %%]" <battery>
Number Luba_ErrorCode "Error Code [%d]" <error>
String Luba_CurrentZone "Current Zone [%s]" <garden>
Number Luba_TotalWorkingHours "Operating Hours [%.1f h]" <time>
Number Luba_TotalDistance "Distance Traveled [%.1f km]" <motion>
String Luba_Position "Position [%s]" <map>
Usage Instructions
- Create the items in your openHAB items configuration
- Save the JavaScript code to
/etc/openhab/automation/js/luba.js
- Update the LUBA_CONFIG object with your credentials and device ID
- Create a basic sitemap for controlling the mower
Request for Feedback
Has anyone already tried integrating Mammotion Luba mowers with openHAB? I’d appreciate any feedback, especially from actual Luba owners who could test this code. As mentioned, this is an untested implementation based on the PyMammotion project, and I’d be very interested to know if it works or what adjustments might be needed.
If you have experience with these mowers or have suggestions for improving the integration, please share your insights!