mt-sdk is the toolkit for building plugins for Maus-Tec Electronics devices,
like the Edge-o-Matic 3000. Plugins are written in MTP, a small language made
specifically for this job, and this SDK gives you everything you need to
write, test, and package them.
With mt-sdk you can:
- Scaffold a new plugin project in seconds
- Validate your plugin against the device's API before you ever touch hardware
- Simulate events against your plugin and see exactly what it does
- Write and run automated tests for your plugin's logic
- Build your
.mtpsource down to theplugin.jsonthe device actually loads
You'll need Node.js 20 or later. Install the CLI globally:
npm install -g @maustec/mt-sdkOr add it to a project as a dev dependency for building your own tooling:
npm install --save-dev @maustec/mt-sdkWe also publish a VS Code extension that adds syntax
highlighting and live error checking for .mtp files. Install "Maus-Tec Plugin
Syntax" from the Marketplace once you have a plugin project open.
Scaffold a new plugin:
mt-sdk init my-pluginThis asks you a few questions (plugin type, target platform, and so on) and generates a ready-to-edit project. If you'd rather skip the prompts, pass flags directly:
mt-sdk init my-plugin --type plugin --plugin-type feature --platforms eom3kOnce you have a plugin.mtp file, you can validate it:
mt-sdk validate plugin.mtpSimulate an event to see what your plugin would actually do:
mt-sdk simulate plugin.mtp --event speed_change --arg 128Run any tests you've written for it:
mt-sdk testAnd build your .mtp source into the JSON the device loads (specifying the filename is optional):
mt-sdk build plugin.mtpMTP plugins describe how a device should react to events, like a Bluetooth connection or a speed change from the app. A plugin declares some metadata, optional configuration and BLE matching rules, and then a set of event handlers that run your logic.
Here's a real plugin, the Lovense BLE driver, shortened slightly:
defplugin LovenseDriver do
@display_name "Lovense Driver"
@version "1.0.0"
@sdk_version "~> 1.1.0"
@type "ble_driver"
@description "BLE driver for Lovense single vibration motor toys"
@author "Maus-Tec Electronics"
@license "MIT"
@platforms ["eom3k >= 2.0.2"]
@permissions ["ble:write"]
match do
ble_name_prefix "LVS-"
end
config do
# Vibration Max Level
int max_level = 20, min: 0, max: 5
end
globals do
int state = 0
end
# Maps a 0-255 speed value to a 0-max_level motor command value.
fn map_speed = (int arg) -> round(arg * config.max_level / 255)
on :connect do
log "lovense: connected"
ble_write "Battery;"
end
on :speed_change with speed do
speed |> map_speed() |> to_string() |> concat("Vibrate:", $_, ";") |> ble_write()
end
end
A few things worth pointing out:
- This language feels like Elixir. If your IDE does not support the Maus-Tec Plugin syntax extensions, you can render *.mtp files as Elixir for some sort of syntax highlighting.
- Every plugin starts with
defplugin Name do ... endand a block of@metadataattributes at the top. match do ... endtells the device which hardware this plugin applies to. For BLE drivers, that's usually able_name_prefix. Other match types will be developed in the future.config do ... enddeclares settings your plugin exposes to the user, with a type, a default value, and optional constraints likeminandmax. You read them back later withconfig.name. Config names are taken from the comment immediately preceeding the config definition.globals do ... enddeclares state that persists between events, for exampleint state = 0. You read and write globals with a$prefix, like$state.fndefines a short, single-expression function. Since a single expression includes pipe chains,fndefs are useful for aliasing pipe chain transformations.def ... do ... endfunction blocks define a longer function which is made of multiple expressions, and can also include local variable scope. Note that variables must be declared first ("hoisted", if you will).on :event_name do ... endhandles an event. If the event carries a value, name it withwith, likeon :speed_change with speed do ... end.- The
|>pipe operator passes the result of one call into the next as its final argument, and$_refers to that piped-in value if you need it in another position, explicitly. This keeps chains like "convert to string, then wrap in a command, then send over BLE" readable top to bottom. The underlying runtime executes actions as a series of steps, so the pipe operator helps write efficient plugins that mirror this mental model.
Plugins are tested with a companion .test.mtp file. Tests mock out the host
functions your plugin calls (like ble_write or log), emit events, and
assert on the resulting globals or calls:
deftest for LovenseDriver do
mock ble_write = (string data) -> 0
describe "speed_change" do
test "sends a vibrate command scaled to max_level" do
emit :speed_change with 255
expect ble_write called with "Vibrate:20;"
end
end
end
Run mt-sdk test from your project directory to execute all .test.mtp
files.
The eom-plugins repository contains real, in-use plugins for the Edge-o-Matic 3000, including BLE drivers and feature plugins, each with their own tests. It's the best place to see the language used in practice and to find a starting point close to what you're building.
MIT