oriceon / mammotion
Laravel client for the Mammotion Open API — control Luba / Yuka robot mowers: devices, live status, work parameters, commands and telemetry subscriptions.
Requires
- php: ^8.5
- illuminate/contracts: ^13.0
- illuminate/http: ^13.0
- illuminate/support: ^13.0
Requires (Dev)
- larastan/larastan: ^3.9
- orchestra/testbench: ^11.0
- pestphp/pest: ^5.0
- pestphp/pest-plugin-laravel: ^5.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^13.0
README
Laravel client for the Mammotion Open API — list your Luba / Yuka robot mowers, read what they are doing right now, send work commands and subscribe to live telemetry.
use Oriceon\Mammotion\Facades\Mammotion; $devices = Mammotion::devices(); $id = $devices[0]['id']; Mammotion::currentWork($id); // status + battery + decoded job settings Mammotion::start($id, 'Front yard'); Mammotion::pause($id); Mammotion::returnToDock($id);
Requirements
- PHP 8.5+
- Laravel 13+
- API credentials generated at https://developer.mammotion.com/credentials
Installation
composer require oriceon/mammotion
The service provider and the Mammotion alias are auto-discovered. Publish the config if you need to change it:
php artisan vendor:publish --tag=mammotion-config php artisan vendor:publish --tag=mammotion-translations
Configuration
Only the credentials are mandatory:
MAMMOTION_CLIENT_ID= MAMMOTION_CLIENT_SECRET=
Everything else has a sane default and can be overridden through config/mammotion.php or the environment:
| Key | Env | Default | Meaning |
|---|---|---|---|
auth_url |
MAMMOTION_AUTH_URL |
https://id.mammotion.com |
OAuth2 server that issues the access token |
api_url |
MAMMOTION_API_URL |
https://api-open.mammotion.com |
API server |
language |
MAMMOTION_LANGUAGE |
en-US |
Sent as Accept-Language |
timeout |
MAMMOTION_TIMEOUT |
15 |
HTTP timeout, in seconds |
token_leeway |
MAMMOTION_TOKEN_LEEWAY |
60 |
Safety margin shaved off expires_in when caching the token |
cache.store |
MAMMOTION_CACHE_STORE |
app default | Cache store holding the tokens |
cache.prefix |
MAMMOTION_CACHE_PREFIX |
mammotion |
Cache key prefix |
Authentication
Authentication is client_credentials and is handled for you: every call fetches the token if needed, caches it until shortly before it expires (as Mammotion asks integrators to do) and, on a 401, re-authenticates and retries the request exactly once. A refresh_token, when the server returns one, is preferred over a new client_credentials exchange.
You never need to call it explicitly, but it is available:
Mammotion::accessToken(); // cached token Mammotion::accessToken(true); // force a new one Mammotion::forgetToken(); // drop the cached tokens
Usage
Resolve the client however you prefer — facade, container, or a plain instance with explicit credentials:
use Oriceon\Mammotion\Facades\Mammotion; use Oriceon\Mammotion\MammotionClient; Mammotion::devices(); app(MammotionClient::class)->devices(); (new MammotionClient(clientId: '…', clientSecret: '…'))->devices();
Devices
Mammotion::devices(); // every mower bound to the account Mammotion::device('DEV-1'); // raw detail: status, battery, network, firmware Mammotion::status('DEV-1'); // MammotionDeviceStatusEnum|null Mammotion::isOnline('DEV-1'); // bool Mammotion::batteryLevel('DEV-1'); // int|null
What the mower is doing right now
$snapshot = Mammotion::currentWork('DEV-1');
[
'deviceId' => 'DEV-1',
'nickname' => 'Yuka',
'model' => 'LUBA 3 AWD',
'online' => true,
'status' => MammotionDeviceStatusEnum::WORKING,
'statusLabel' => 'Working',
'isWorking' => true,
'batteryLevel' => 72,
'isCharging' => false,
'job' => MammotionJobContentEnum::MOWING_AND_COLLECTING,
'jobLabel' => 'Mowing and collecting',
'settings' => ['channelMode' => 'Cross stripes', 'knifeHeight' => '45 mm', …],
'workParams' => [ /* raw response */ ],
'workParamsPending' => false,
'network' => ['usedNetwork' => '1', 'wifiRssi' => -46],
]
Two limitations belong to the Mammotion API, not to this package:
- REST does not expose which task or area is running, nor the progress percentage.
Mammotion::tasks($id)only lists the tasks defined in the app, without marking the active one. work-paramsis asynchronous despite its name. The firstGETmerely asks the mower to report; the immediate response is often just{"commandResult": true, "resultMessage": "Command has been sent", …}with every value at0.
The package never turns those zeros into settings. When it happens, settings stays empty and workParamsPending is true. To tell the two apart yourself:
$params = Mammotion::workParams('DEV-1'); Mammotion::workParamsArePopulated($params); // false for an empty acknowledgement // Ask a few times, in case the backend serves the mower's later report from cache: Mammotion::workParamsPolled('DEV-1', attempts: 4, delayMs: 2000);
Live progress, track and coordinates arrive exclusively through the SSE push channel — see Telemetry.
Commands
Mammotion::start('DEV-1', 'Front yard'); // runs a task created in the Mammotion app Mammotion::startImmediately('DEV-1'); // mow now, no plan Mammotion::pause('DEV-1'); Mammotion::resume('DEV-1'); Mammotion::stop('DEV-1'); // stops and cancels the task Mammotion::returnToDock('DEV-1'); Mammotion::cancelReturn('DEV-1'); // Or the generic form: Mammotion::action('DEV-1', MammotionActionEnum::START, 'Front yard');
Mammotion::taskNames('DEV-1') returns the names accepted by start().
Telemetry
Subscribing tells the Mammotion server to push updates over its SSE channel. A subscription expires after 10 minutes and has to be renewed; the API accepts at most 20 devices per request.
use Oriceon\Mammotion\Enums\MammotionSubscriptionPropertyEnum as Property; Mammotion::subscribeDevice('DEV-1'); // basic telemetry set Mammotion::subscribeDevice('DEV-1', [Property::WORK_PROGRESS, Property::COORDINATES]); Mammotion::subscribe([ 'DEV-1' => [Property::DEVICE_STATUS, Property::BATTERY_PERCENT], 'DEV-2' => Property::telemetry(), ]);
Enums
| Enum | Covers |
|---|---|
MammotionActionEnum |
commands accepted by POST /v1/mower/action |
MammotionDeviceStatusEnum |
mower states, mapped case-insensitively |
MammotionJobContentEnum |
operating mode (mowing / collecting / both) |
MammotionSubscriptionPropertyEnum |
telemetry property keys |
Every case exposes a translated label(). Translations ship for en and ro; publish them to add your own locale.
Error handling
Both transport failures (HTTP != 2xx) and application errors (code other than 0 / 200 in the response envelope) raise MammotionApiException:
use Oriceon\Mammotion\Exceptions\MammotionApiException; try { Mammotion::pause('DEV-1'); } catch (MammotionApiException $e) { $e->getMessage(); $e->apiCode; // application code, e.g. 4001 $e->httpStatus; // HTTP status $e->requestId; // Mammotion request id, useful for support }
A missing or empty credential pair raises MammotionConfigurationException at build time.
Testing
The package is covered by Pest, and every HTTP call goes through Laravel's HTTP client — so Http::fake() is all you need in your own tests:
composer install
composer test
License
MIT. See LICENSE.md.