MQTT
Post controller integration via MQTT
Connection parameters
| Parameter | Value |
|---|---|
| Broker | Mosquitto 2.x |
| Publish topic | wash/telemetry/{type} or wash/telemetry/# |
| DLQ | wash/dlq |
| QoS | 1 (recommended) |
| Port | 1883 (default on all host interfaces) |
| Address | <CRM-server-IP>:1883 from local network |
| User (post) | settings.mqttLogin / settings.mqttPassword from post card in CRM |
| User (CRM) | system — password in Settings → MQTT (CRM); on first launch also MQTT_PASSWORD in .env |
Inside Docker: mosquitto:1883 (CRM connects as system).
From local network, post: mqtt://<mqttLogin>:<mqttPassword>@192.168.1.10:1883 (CRM server IP).
Port 1883 is published automatically with ./scripts/start.sh. Controllers on the same LAN can connect without additional overlay files.
To accept MQTT localhost only (not from LAN), in .env:
MQTT_BIND=127.0.0.1
Message format
{
"washSerial": "WASH-001",
"postSerial": "POST-001",
"messageType": "mode",
"payload": {
"mode": "wash",
"modeName": "Мойка",
"modeNumber": 1,
"freePause": 30,
"paidPause": 15,
"modeTime": 120
},
"timestamp": "2024-06-22T12:00:00Z"
}
Top-level fields
| Field | Description |
|---|---|
washSerial |
Car wash serial number (optional, for logs) |
postSerial |
Required — post serial number from CRM |
messageType |
Message type (see below) |
payload |
Type-dependent data |
timestamp |
ISO 8601 |
Message types (messageType)
| Type | Purpose | Example topic |
|---|---|---|
mode |
Current post operating mode | wash/telemetry/mode |
state |
General state | wash/telemetry/state |
card |
Card operations | wash/telemetry/card |
statistics |
Usage statistics | wash/telemetry/statistics |
finance |
Financial data | wash/telemetry/finance |
equipment |
Equipment state | wash/telemetry/equipment |
event |
Events and alerts | wash/telemetry/event |
settings |
Post settings change | wash/telemetry/settings |
message-processor subscribes to:
wash/telemetry/#— legacy envelope (integrations, tests)+/+/#— WASH-PRO native protocol:state/*,set/*(ETH module and panel)
The MQTT log in CRM stores every inbound message with original JSON (one row per publication).
Viewing the log in Dashboard (/mqtt)
| Level | Element | Description |
|---|---|---|
| API | Load more (100 rows) above table | Next telemetry page from server (100 rows each) |
| Table | DataTable footer | Per page: 20 / 40 / 60 / 80 / 100 · Back / Next · Load more among loaded rows |
See also Dashboard — table pagination.
WASH-PRO native protocol (ETH / panel)
Controllers publish flat JSON to topic:
{dt_pref}/{serial_number}/state/{suffix}
Example: washpro/WP-001/state/process
| Suffix | Source | CRM messageType |
|---|---|---|
process |
Post state (~1 s) | state |
totals |
Finance counters (~30 s) | finance (2 records: before/after collection) |
usages |
Usage counters (~30 s) | statistics (6 records: regular/service/unlimited × before/after collection) |
credit |
Credit (panel) | event (eventType: credit) |
card |
NFC / collection (panel) | card |
Post identification: second topic segment (serial_number) is passed to CRM as-is, without transformation — it must exactly match the post serialNumber field in CRM.
Monetary fields (balance, tcash, acash, summ, etc.) — in rubles (integer or fractional, no kopeck conversion).
state/process
{
"balance": 120,
"discount": 0,
"pause": 30,
"inactiv": 0,
"light": 0,
"card": 0,
"type": 0,
"number": 2
}
Mapping: pause → freePause, number → modeNumber, card/type/inactiv/light → equipmentState.
card field in state/process: 0 — card not active; otherwise — NFC card number (synced with CRM record along with balance and discount).
state/totals
{
"tcash": 45600,
"tnoncash": 78900,
"tdiscount": 12300,
"acash": 2300,
"anoncash": 4500,
"adiscount": 890
}
a*→finance/before_collection(current period)t*→finance/after_collection(cumulative totals)
state/usages
{
"tclients": 1234,
"tservices": 890,
"tunlims": 45,
"aclients": 56,
"aservices": 12,
"aunlims": 3
}
Values are minutes of usage by card category:
| Field | Period | CRM category |
|---|---|---|
aclients |
before collection | regular (discount clients) |
aservices |
before collection | service (service maintenance) |
aunlims |
before collection | unlimited (VIP) |
tclients |
after collection | regular |
tservices |
after collection | service |
tunlims |
after collection | unlimited |
Stored in CRM as usageTime (seconds = minutes × 60) and clientCount (minutes from panel).
state/credit (panel)
{ "type": 1, "summ": 100 }
type: 0 — cash, 1 — cashless, 2 — discount.
state/card (panel)
{ "type": 0, "number": 10, "card": 1234567890 }
type: 0 — discount, 1 — service, 2 — VIP, 3 — collection.
Post control from CRM (set/*)
CRM sends commands and prices to topics:
{dt_pref}/{serial_number}/set/prices
{dt_pref}/{serial_number}/set/command
Prefix dt_pref matches panel NVS (default washpro). Serial number — as in CRM (posts.serialNumber).
Web UI requests go through message-processor (HTTP → MQTT). Outbound publications also appear in the MQTT log.
Delivery settings (Settings → MQTT)
| Field | Default | Description |
|---|---|---|
outboundRetentionHours |
168 (7 days) | Retention of outbound commands/prices in CRM outbox |
requireDeliveryConfirmation |
false |
Enable acknowledgment protocol (set/ack) |
redeliverOnNoAck |
false |
Republish if ack not received (only when confirmation enabled) |
redeliverIntervalSec |
30 | Pause between retries |
redeliverMaxAttempts |
5 | Max attempts (including first) |
With requireDeliveryConfirmation=false (default) behavior is as before: CRM publishes set/*, for prices the device may echo on set/prices.
With requireDeliveryConfirmation=true CRM adds message_id (UUID) to payload, saves record in /api/crm/mqtt-outbox, and waits for response on topic:
{dt_pref}/{serial_number}/set/ack
ETH acknowledgment format:
{
"kind": "prices",
"status": "ok",
"message_id": "550e8400-e29b-41d4-a716-446655440000"
}
Error:
{
"kind": "command",
"status": "error",
"message_id": "550e8400-e29b-41d4-a716-446655440000",
"cmd": 3,
"error_message": "INVALID_AMOUNT"
}
kind |
Source topic |
|---|---|
prices |
set/prices |
command |
set/command |
Inbound MQTT log (/api/crm/telemetry) still stores all messages; log cleanup period is set in archive settings (archive.retentionDays), separate from outbox.
set/prices
Keys — mode codes (0–9), values — price in rubles:
{
"0": 50,
"1": 80,
"2": 120
}
On device response to set/prices, CRM may sync posts.settings.modePrices.
set/command
{ "cmd": 1 }
cmd |
Action |
|---|---|
| 1 | Soft reset |
| 2 | Hard reset |
| 3 | Credit balance (extra field summ — amount in rubles) |
| 4 | Fault mode |
| 5 | Bay maintenance |
| 6 | VIP mode |
| 7 | Collection mode |
Credit example:
{ "cmd": 3, "summ": 100 }
From CRM web UI
On the post page (Posts → ⚙ → Device settings or /posts/{id}#device-settings):
- Mode prices — grid from work mode reference (codes
0–9). "Save prices" writes values toposts.settings.modePricesand, if checkbox enabled, publishes MQTTset/prices. - Commands — dropdown (resets, credit balance, service modes). For credit, specify amount. Confirm → MQTT
set/command. - MQTT prefix (
dt_pref) — must match panel setting (defaultwashpro).
Requires create or update permission (Administrator / Operator roles).
HTTP API (via Dashboard)
Internal HTTP server message-processor (port 3022) proxied by nginx:
| Method | Path (via Dashboard) | Purpose |
|---|---|---|
POST |
/api/crm/post-device/posts/{serial}/prices |
Save prices and/or send to post |
POST |
/api/crm/post-device/posts/{serial}/command |
Send command to post |
POST |
/api/crm/post-device/mqtt/sync-users |
Apply post accounts in Mosquitto |
Authorization: Authorization: Bearer <JWT> header (same token as Dashboard).
POST …/prices
Request body:
{
"prices": { "0": 50, "1": 80, "2": 120 },
"mqttPrefix": "washpro",
"sendToDevice": true,
"persist": true
}
| Field | Default | Description |
|---|---|---|
prices |
— | Object "mode code → price in rubles" |
mqttPrefix |
washpro or MQTT_DEVICE_PREFIX |
Topic prefix |
sendToDevice |
true |
Publish to MQTT |
persist |
true |
Save to posts.settings |
Response: { "success": true, "data": { "topic": "washpro/SN123/set/prices", "prices": {...}, "mqttPrefix": "washpro" } }
POST …/command
{
"command": "credit_balance",
"amount": 100,
"mqttPrefix": "washpro"
}
command values: soft_reset, hard_reset, credit_balance, fault_mode, service_mode, vip_mode, collection_mode.
Outbound publications logged in /api/crm/telemetry with payload.direction: "outbound".
mosquitto_pub examples (commands and prices)
Substitute PASSWORD, server IP, and post serial from CRM:
# Mode prices
mosquitto_pub -h localhost -p 1883 -u system -P 'PASSWORD' -q 1 \
-t 'washpro/SN123/set/prices' \
-m '{"0":50,"1":80,"2":120,"3":40}'
# Soft reset
mosquitto_pub -h localhost -p 1883 -u system -P 'PASSWORD' -q 1 \
-t 'washpro/SN123/set/command' \
-m '{"cmd":1}'
# Credit 100 ₽
mosquitto_pub -h localhost -p 1883 -u system -P 'PASSWORD' -q 1 \
-t 'washpro/SN123/set/command' \
-m '{"cmd":3,"summ":100}'
# Collection mode
mosquitto_pub -h localhost -p 1883 -u system -P 'PASSWORD' -q 1 \
-t 'washpro/SN123/set/command' \
-m '{"cmd":7}'
curl examples (via Dashboard)
TOKEN="..." # JWT from browser after login
curl -s -X POST http://localhost/api/crm/post-device/posts/SN123/prices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"prices":{"0":50,"1":80},"mqttPrefix":"washpro"}'
curl -s -X POST http://localhost/api/crm/post-device/posts/SN123/command \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"command":"soft_reset","mqttPrefix":"washpro"}'
Price sync from device
If the panel publishes a response to set/prices, message-processor updates posts.settings.modePrices in CRM automatically.
ETH configuration for CRM broker
In panel NVS (get_settings.remote):
| Field | Value for CRM |
|---|---|
rm_en |
1 |
rm_addr |
CRM server IP |
rm_port |
1883 |
rm_login / rm_pass |
Login and password from post card in CRM (settings.mqttLogin / settings.mqttPassword) |
dt_pref |
e.g. washpro |
Legacy envelope (wash/telemetry)
For third-party integrations, JSON wrapper (see Message format) in topic wash/telemetry/{type} is supported.
Post identification
message-processor finds post by serialNumber in /api/crm/posts:
- Native protocol — serial from topic (
{dt_pref}/{serial}/state/...), 1:1 - Legacy —
postSerialfield in JSON
Error handling
- Successful message — processed and written to CRM
- Processing error — message published to
wash/dlq, notification on overflow - On broker unavailability processor reconnects every 5 seconds
Publication example (Node.js)
import mqtt from 'mqtt';
const client = mqtt.connect('mqtt://system:PASSWORD@192.168.1.10:1883');
client.on('connect', () => {
const msg = {
postSerial: 'POST-001',
messageType: 'mode',
payload: { mode: 'idle', modeName: 'Ожидание', modeNumber: 0 },
timestamp: new Date().toISOString(),
};
client.publish('wash/telemetry/mode', JSON.stringify(msg), { qos: 1 }, () => {
client.end();
});
});
Publication example (mosquitto_pub)
# From CRM server itself
mosquitto_pub -h localhost -p 1883 -u system -P 'PASSWORD' \
-t 'wash/telemetry/mode' -q 1 \
-m '{"postSerial":"POST-001","messageType":"mode","payload":{"mode":"idle"},"timestamp":"2024-06-22T12:00:00Z"}'
# From another device on local network
mosquitto_pub -h 192.168.1.10 -p 1883 -u system -P 'PASSWORD' \
-t 'wash/telemetry/mode' -q 1 \
-m '{"postSerial":"POST-001","messageType":"mode","payload":{"mode":"idle"},"timestamp":"2024-06-22T12:00:00Z"}'
Port change
MQTT_EXTERNAL_PORT=1883
After change: docker compose up -d mosquitto.
Security
Port 1883 is available to devices on the local network. In mosquitto.conf, per_listener_settings true is required: otherwise allow_anonymous true on healthcheck port 1884 disables auth on 1883.
- Post panel connects with login/password from post card (
settings.mqttLogin/settings.mqttPassword). - Mosquitto passwd allows only system (CRM) and post accounts. Anonymous access on 1883 is forbidden.
- Post isolation: ACL allows each post to publish and read only topics with its own serial number (
washpro/{serial}/#). Serial spoofing in payload does not affect CRM — serial from topic is used. systemhas full access to all topics (CRM and debugging).- Logins
system,superadmin, andwashcannot be assigned to posts — they are reserved. - After password or serial change in CRM, click "Sync MQTT" in wizard or save post (sync runs automatically).
Set a strong MQTT_PASSWORD in .env and restrict access with firewall if needed.