Skip to content

Projects ​

A project is one complete description of one boat. The Brain can hold as many as fit on disk, but only one of them is ever running: the one that was last deployed. Everything else is a draft, a spare, an older version, or a template.

That separation is the point. An installer can build next season's configuration next to the one the boat is running on, compare them, and switch over in a single act — or not switch over at all.

Identity: the short name ​

Every project has a short name, which is its id, its filename and the segment in every URL that touches it. It is restricted to ASCII letters, digits and -; anything else — a space, a dot, an underscore, an accent, a / — does not match the route at all and comes back 404. Every handler then re-checks the same rule before touching the filesystem, so loosening one route could never turn an id into a path. A name of 64 characters or more is rejected as well.

The short name is not the display name. A project also carries metadata.name, which is free text and is what the interface shows.

sh
curl -X PUT http://127.0.0.1:12000/project/bali-58-hull-042 \
     -H 'content-type: application/json' \
     -d '{"name":"Bali 58 — hull 042"}'

The project list ​

sh
curl -s http://127.0.0.1:12000/project

One entry per file, from two directories:

SourcetypeWritable
/etc/muxen/configuration/*.jsonprojectyes
/usr/share/muxen-config/templates/*.jsontemplateno

Each entry carries id, name, ctm, mtm, version, icon and havePassword. Missing metadata comes back as an empty string rather than being omitted, so the shape is stable. A file that is not valid JSON is skipped silently — it will not appear in the list at all.

Templates ​

Anything dropped into /usr/share/muxen-config/templates/ is offered alongside the real projects. The directory sits on a read-only filesystem, and the service runs under ProtectSystem=strict, so a template can be read and copied but never modified — which is what makes it safe to ship a factory configuration and hand the boat a copy of it.

The fallback is per-request, not a mode: any project-scoped endpoint that cannot find /etc/muxen/configuration/<id>.json looks for /usr/share/muxen-config/templates/<id>.json before answering 404. So GET /project/<template-id> works, and so does GET /project/<template-id>/device.

Writes are the exception that proves the rule. They read the template, apply the change in memory and then try to write back to the path they read from — which fails on the read-only filesystem, and the request answers 500. Duplicate a template before editing it.

Duplicating, and commissioning ​

PUT /project/<source>/duplicate/<new-id> copies a project or a template into a new short name. While copying it:

  • sets metadata.id to the new short name,
  • records the source in metadata.fromTemplate,
  • gives the copy a new metadata.uid, and records the source's in metadata.duplicateFrom (left out when the source has none, as the templates do),
  • stamps a fresh metadata.ctm,
  • sets metadata.name from the request body's name, if given.

The new short name must be free. If configuration/<new-id>.json already exists the request answers 409 and changes nothing: a duplicate never overwrites a project (nor its password). Delete it first, or replace it with PUT /project/<new-id>, which checks its password. A template of that name does not count; the copy in /etc shadows it.

sh
curl -X PUT http://127.0.0.1:12000/project/template-bali-58/duplicate/hull-042 \
     -H 'content-type: application/json' \
     -d '{"name":"Hull 042"}'

The interesting part is the optional commissioning object in the body, described below.

Renaming ​

PUT /project/<id>/rename/<new-id> moves a project to another short name. It is the same project afterwards: metadata.uid, ctm and fromTemplate are kept, and no duplicateFrom is added. The body may carry a new name, as for a duplicate.

  • Only a project in /etc can be renamed; a /usr template answers 404.
  • The source's password is enforced (401), and moves with the project.
  • The new short name must be free, as for a duplicate (409).
  • The new file is written before the old one is removed, so a failure (500) leaves the project under its old name.

Daemons that have the route list rename in the features of GET /info. Against an older daemon a client can only duplicate and then delete, which gives the project a new uid and a duplicateFrom.

sh
curl -X PUT http://127.0.0.1:12000/project/hull-042/rename/hull-042-b \
     -H 'content-type: application/json' \
     -d '{"name":"Hull 042 B"}'

What commissioning does ​

A template describes every option the model can be built with. A hull is built with some of them. The template's commissioning section is a list of option groups, each with an id, a name and a list of rules naming what belongs to that option. The duplicate request answers the groups:

sh
curl -X PUT http://127.0.0.1:12000/project/template-bali-58/duplicate/hull-042 \
     -H 'content-type: application/json' \
     -d '{
           "name": "Hull 042",
           "commissioning": {
             "watermaker": false,
             "aft-cabin": true,
             "solar": true
           }
         }'

Every group is evaluated. A group whose id is absent from the object, or present and false, counts as not wanted, and its rules take effect:

Rule typeEffect when the group is not wanted
muxingdelete the switching rule named by target
sensordelete the sensor named by target
viewdelete the screen page named by target
devicedelete the device whose id is target
device-parameternothing — this type applies parameters when the group is wanted

device-parameter is the one rule that adds rather than removes: its target is an array of {deviceId, name, value} records that are written onto the devices when the group is selected.

A rule may also carry "reverse": true. On device-parameter it inverts the condition cleanly: the parameters are applied when the group is not selected. On the four removing types the rule as written is "delete the target unless the group is selected and the rule is not reverse", which means a reverse removing rule deletes its target whichever way the group was answered. No template shipped with MUXEN uses reverse; treat it as usable only on device-parameter.

Deleting a switching rule cascades: the device parameters that rule generated are removed from the devices, and any commissioning rule pointing at it is removed too. A commissioning group left with no rules is removed entirely.

Replacing one — a PUT on a name that exists — does not cascade into commissioning. The old config is taken off the devices and the new one applied, but a muxing rule points at its target by name only, and a replace keeps the name, so every rule and group naming it stays as it was, whatever the new body changes.

After commissioning ​

Once the answers are applied, the new project no longer needs the catalogue it came from, so the daemon:

  • stores the answers in metadata.withCommissioning, so what was chosen stays on the record,
  • deletes the commissioning section from the new project,
  • deletes the FeatureCommissioning setting.

The result is a plain project for that hull. Commissioning is a one-time act at duplication; there is no "re-run commissioning" on a project that has already been through it.

A malformed commissioning object — a group without id, name or rules, or a rule with an unknown type — is refused with 400 and nothing is written.

Editing commissioning on a template ​

The commissioning section itself is editable through the API, which is how a template is authored:

sh
# create or replace a group
curl -X PUT http://127.0.0.1:12000/project/<id>/commissioning/watermaker \
     -H 'content-type: application/json' \
     -d '{"name":"Watermaker","rules":[]}'

# append one rule to it
curl -X PUT http://127.0.0.1:12000/project/<id>/commissioning/watermaker/rules \
     -H 'content-type: application/json' \
     -d '{"type":"device","target":1280}'

# remove rule number 0
curl -X DELETE http://127.0.0.1:12000/project/<id>/commissioning/watermaker/rules/0

Rules are addressed by their index in the group's array, so an index is only valid until the next change. Removing the last rule of a group removes the group as well.

Passwords ​

A project is protected by adding a setting called password carrying a hash field. The hash is the SHA-256 of the password, lowercase hex.

sh
HASH=$(printf %s 's3cret' | sha256sum | cut -d' ' -f1)

curl -X PUT http://127.0.0.1:12000/project/hull-042/settings/password \
     -H 'content-type: application/json' \
     -d "{\"value\":\"\",\"hash\":\"$HASH\"}"

From then on every request touching that project must present it:

sh
curl -H "Authorization: Bearer $HASH" http://127.0.0.1:12000/project/hull-042

What the daemon checks is narrow and worth knowing exactly:

  • It splits the Authorization header on the first space and compares the second field to the stored hash. The scheme word is not checked; Bearer is the convention.
  • A project with no password setting is open. Removing the setting removes the protection.
  • The three operations that replace or remove the whole file — PUT /project/<id>, PUT /project/<id>/restore, DELETE /project/<id> — are gated on the password of the file already on disk, so the protection cannot be deleted away by overwriting it.
  • Creating a project that does not exist yet needs no credential: there is no stored password to check.
  • That last check consults only /etc/muxen/configuration/<id>.json, never the template fallback, so a protected template does not block creating a new project that happens to share its name.

Reading a project ​

Two endpoints, and the difference matters.

EndpointBody
GET /project/<id>the project stripped: media[].data, and views[].elements, .conditionGroups, .image removed
GET /project/<id>/backupthe project whole, byte for byte what is on disk

The stripped form is what an interface wants: a boat configuration with embedded images is measured in megabytes, and the heavy parts are fetched on demand. The whole form is what an export wants.

Individual sections have their own endpoints — /device, /sensor, /muxing, /settings, /view, /media, /energy, /synapse, /button-mapping, /commissioning — listed in full in Reference.

Backup and restore ​

"Backup" here means export one project, and it is a plain HTTP round trip:

sh
# export
curl -s http://127.0.0.1:12000/project/hull-042/backup > hull-042.json

# import, under any short name
curl -X PUT http://127.0.0.1:12000/project/hull-042-restored/restore \
     -H 'content-type: application/json' \
     --data-binary @hull-042.json

Restore writes the request body to /etc/muxen/configuration/<id>.json and answers 201. Two consequences:

  • The body is not validated against any schema. Whatever JSON is posted becomes the project, including one that no longer parses as a configuration. Restore from a file this daemon produced.
  • metadata.id inside the body is not rewritten to match the URL. Restoring hull-042.json under a different short name leaves the old id in the metadata. Duplicate instead if you want a clean copy.

This is separate from the daily deploy.json backups, which are described in Deployment.

Deleting ​

sh
curl -X DELETE http://127.0.0.1:12000/project/hull-042

200 when the file was removed, 404 when there was nothing to remove, 401 when the project is password-protected and no credential was given.

Deleting a project does not touch /etc/muxen/deploy.json. A boat running a deleted project keeps running it: the deployed copy is independent of the source. That is a safety property, not an oversight — but it does mean the running configuration can outlive the project it came from, with no way back to an editable form except through the backups.

Changing many things at once ​

Every write is read-modify-write on a whole JSON file. Applying twenty changes as twenty requests parses and rewrites the project twenty times, and leaves nineteen intermediate states on disk. The transaction endpoint exists for that:

sh
curl -X POST http://127.0.0.1:12000/project/hull-042/transaction \
     -H 'content-type: application/json' \
     -d '[
           {"url":"/project/hull-042/device/64","method":"PUT"},
           {"url":"/project/hull-042/device/64/parameter/Name",
            "method":"PUT","data":{"value":"Saloon lights"}}
         ]'

The steps run in order against one in-memory copy, and the file is written once at the end. Details, including its failure behaviour and one sharp edge, are in Reference.

Integration of multiplexed solutions
MUXEN and the MUXEN logo are trademarks of MUXEN SAS.