Back to home

Help & Guides

Everything you need to install, set up and get started with CampMatic.

  1. Connect the data link (RJ12)

    The CampMatic box connects to the Truma LinBUS with an RJ12 cable. There are three ways to do this — pick the one that fits your vehicle.

    Route 1 — Tap it at the CP Plus (recommended):
    The easiest route: you never have to get to the Truma heater itself — the entire connection there is dropped, opening the cover included. On the back of the Truma CP Plus panel, unplug the RJ12 cable sitting there and plug it into the input of the RJ12 splitter. One splitter output goes back into the CP Plus using that same cable, the second output runs to the CampMatic box via the additional RJ12 cable. CP Plus and CampMatic then sit in parallel on the same LinBUS without interfering with each other. The splitter and the extra cable are available with your order as the RJ12 splitter set.

    Route 2 — Directly at the Truma heater:
    If you cannot reach the back of the CP Plus, or there is not enough room behind it for the splitter and plugs: remove the cover above the Truma heater (marked with an arrow and the word "open") and insert the CampMatic RJ12 plug into the right-hand free socket of the Truma heater. No splitter needed for this route.Route 3 — Replacing an existing Truma iNet Box:
    If your vehicle already has a Truma iNet Box and the CampMatic is to take its place: unplug the RJ12 cable from the iNet Box and remove the box. You are then left with two cable ends, each carrying a male RJ12 plug — your existing cable and the cable of the CampMatic box. The two cannot be plugged into each other directly; an RJ12 coupler (socket to socket) goes in between and the link is done. The coupler is available as an accessory with your order — routes 1 and 2 do not need it.



    Route 1 is shown step by step in the video — plug in the splitter, cable back into the CP Plus, second output to the box.

  2. Connect 12V power supply

    The CampMatic box only needs a 12V power supply — from any source. A connection to the CP Plus is not required. You have two options:

    Option A — Via 12V cable (red/black):
    Connect the included red/black cable to any 12V power source in your camper. This way the box works completely independently of the CP Plus.

    Option B — Conveniently tap power at the CP Plus (JST-PH plug):
    If you prefer to tap the power directly at the CP Plus: on the back of the Truma CP Plus control panel, disconnect the red 12V power plug. Insert it into the female JST-PH connector of the CampMatic box, then plug the male JST-PH connector into the control panel. This only uses the 12V supply voltage — no data connection.

  3. Perform reset

    Wait 3 minutes for the CampMatic box to start up. Then in the Truma control panel menu, navigate to Reset → PR SET and start the reset.

  4. Verify CampMatic detection

    After the reset, check the Index menu for a new (third) entry. It must show T23.700 — then the CampMatic box is detected and ready. If not, perform the reset again.

  1. CampMatic Box

    This is the CampMatic box — compact, pre-wired and ready to install.

  2. Locate the bench seat

    The bench seat in the VW Grand California, under which the service compartment is located. This is where the CampMatic box will be installed.

  3. Remove seat cushion

    The seat cushion is attached with hook-and-loop tape. Simply lift it off — no tools needed.

  4. Unscrew wooden panel

    After removing the cushion, the screws of the wooden panel become visible. Remove all screws and lift the panel.

  5. Unscrew control panel (CP Plus)

    The Truma CP Plus control panel is secured with 3 screws. Remove all screws and carefully flip the panel.

  6. Connect the data link (RJ12)

    The CampMatic box connects to the Truma LinBUS with an RJ12 cable. There are three ways to do this — pick the one that fits your vehicle.

    Route 1 — Tap it at the CP Plus (recommended):
    The easiest route: you never have to get to the Truma heater itself — the entire connection there is dropped, opening the cover included. On the back of the Truma CP Plus panel, unplug the RJ12 cable sitting there and plug it into the input of the RJ12 splitter. One splitter output goes back into the CP Plus using that same cable, the second output runs to the CampMatic box via the additional RJ12 cable. CP Plus and CampMatic then sit in parallel on the same LinBUS without interfering with each other. The splitter and the extra cable are available with your order as the RJ12 splitter set.

    Route 2 — Directly at the Truma heater:
    If you cannot reach the back of the CP Plus, or there is not enough room behind it for the splitter and plugs: remove the cover above the Truma heater (marked with an arrow and the word "open") and insert the CampMatic RJ12 plug into the right-hand free socket of the Truma heater. No splitter needed for this route.Route 3 — Replacing an existing Truma iNet Box:
    If your vehicle already has a Truma iNet Box and the CampMatic is to take its place: unplug the RJ12 cable from the iNet Box and remove the box. You are then left with two cable ends, each carrying a male RJ12 plug — your existing cable and the cable of the CampMatic box. The two cannot be plugged into each other directly; an RJ12 coupler (socket to socket) goes in between and the link is done. The coupler is available as an accessory with your order — routes 1 and 2 do not need it.



    Route 1 is shown step by step in the video — plug in the splitter, cable back into the CP Plus, second output to the box.

  7. Connect CampMatic box

    On the back of the control panel, disconnect the red 12V power plug. Insert it into the female JST-PH connector of the CampMatic box. Then plug the male JST-PH connector of the box into the Truma control panel. The CampMatic box is essentially connected in-line with the existing power supply.

  8. Reattach control panel

    Reattach the control panel with the 3 screws. Then wait 3 minutes for the CampMatic box to start up.

  9. Perform reset

    In the Truma control panel menu, navigate to Reset → PR SET and start the reset.

  10. Verify CampMatic detection

    After the reset, check the Index menu for a new (third) entry. It must show T23.700 — then the CampMatic box is detected and ready. If not, perform the reset again.

  1. Power on CampMatic Box & wait

    Connect the CampMatic box to power and wait approx. 3 minutes until the device has fully booted and the access point is ready.

  2. Connect to CampMatic Access Point

    Connect your smartphone or laptop to the CampMatic WiFi network. The password is on the sticker on the CampMatic box or in the CampMatic app on the status page.

  3. Open configuration page

    Once your device is connected to the CampMatic network, open http://192.168.4.1/ in your browser. You will be asked to log in — username: admin, password: found on the sticker on the box or in the CampMatic app on the status page.

    Important: If after login you see the normal control panel instead of the WiFi selection, open http://192.168.4.1/captiveportal directly — this will show the WiFi selection.

  4. Connect to WiFi network

    Select your WiFi network and enter the password. The CampMatic box will connect and display the local Truma control panel once connected successfully.

  5. Change WiFi later

    If you want to change the WiFi network later: The CampMatic box automatically opens its own access point when the configured network is unavailable. Reconnect to the CampMatic network and open http://192.168.4.1/captiveportal to configure a new WiFi network.

  1. Prepare SIM card

    Before the 4G CampMatic box can be used, a SIM card must be inserted.

    Important: The SIM card must not have a PIN lock. Otherwise the box cannot register on the mobile network. How do I disable the PIN lock?

  2. Open the box while powered off

    Disconnect the CampMatic box from power before opening. Open the case carefully: the GSM antenna is mounted on the lid and connected to the board by a cable. Lift the lid slowly and without pulling on the antenna cable.

  3. Open the SIM slot

    The SIM card holder is located on the board. Slide the metal lock in the direction of the arrow (see image) to unlock the holder, then carefully flip it up.

  4. Insert SIM & close the box

    Insert the SIM card with the contacts facing down (watch the beveled corner). Close the holder and slide the lock against the arrow direction until it clicks. Carefully reattach the lid — do not pinch the antenna cable — and screw the case shut.

  5. Power on CampMatic Box & wait

    Reconnect the box to power and wait approx. 3 minutes until the device has booted up and registered on the mobile network.

  6. Connect to CampMatic Access Point

    On 4G devices the access point is always active — no WiFi configuration is required. Connect your smartphone or laptop to the CampMatic WiFi network. The password is on the sticker on the CampMatic box or in the CampMatic app on the status page.

  7. Open local control panel

    Open http://192.168.4.1/ in your browser. Log in with username admin and the password from the sticker or CampMatic app. The Truma control panel is shown locally — the 4G box talks to the CampMatic cloud directly over cellular, independent of any local WiFi.

  1. Open registration page

    Go to campmatic.de/app/register.php and fill out the registration form. The PIN for registration is on the sticker on your CampMatic box.

  2. Verify email address

    After registration, a confirmation email will be sent to the email address you provided. Click the link in the email to activate your account. Check your spam folder if needed.

  3. Log in and start heating

    After verification, log in at campmatic.de/app and start controlling your heater.

Your CampMatic device publishes its sensors and controls over MQTT. So Home Assistant can find all entities via auto-discovery, you need an MQTT connection to the CampMatic broker — either through a local Mosquitto bridge (recommended, resilient to WAN drops and works for multiple devices) or directly from Home Assistant.

The tabs at a glance: Bridge and Direct are the two ways to connect HA to the broker — you need one of them. Truma holds ready-made Lovelace cards for heating, hot water and energy mix. Gas bottle, Battery and Climate cover the BLE add-on sensors (Mopeka, BM2, Ruuvi): complete YAML examples including the conversions these sensors require — fill level from ultrasonic time of flight, state of charge from resting voltage, dew point from temperature and humidity.

📦 All of it is also available as a ready-made download: campmatic-homeassistant.zip

  1. Get credentials from the Status page

    Log in at campmatic.de/app and open the Status page. The "MQTT Bridge (Home Assistant)" card shows: host (always campmatic.de), port (8883, TLS), username (e.g. campmatic_003), password (eye icon reveals), and topic prefix (e.g. campmatic/003). These values replace every <placeholder> below. The angle brackets go away tooremote_username <mqtt_user> becomes remote_username campmatic_003, not <campmatic_003>.

    One value is not on the Status page: the internal device name <node>. The bridge's discovery line needs it, and it follows from the device number:
    • device 001–051grand-cali-<NNN>, e.g. grand-cali-003
    • device 052 and up → campmatic-<NNN>, e.g. campmatic-054
    If unsure, subscribe to homeassistant/# in MQTT Explorer: the config topics are named homeassistant/<type>/<node>/<entity>/config.

  2. Start Mosquitto

    There are two ways — every step below shows both.

    A · Home Assistant OS / Supervised (add-on): Settings → Add-ons → Add-on Store → install Mosquitto broker (official) and start it. That completes this step — the code below is only for way B.

    B · Docker / Compose (e.g. Synology, Pi, NUC): start your own Mosquitto with the file below.

    Important: Neither this docker-compose.yml nor the mosquitto.conf from step 4 belong in Home Assistant's configuration.yaml. They are files of the MQTT broker — Home Assistant never reads them.

    # Weg B (Docker/Compose) — beim Add-on nicht nötig
    # docker-compose.yml
    services:
      mqtt:
        image: eclipse-mosquitto:latest
        container_name: mqtt5
        restart: unless-stopped
        ports:
          - "1883:1883"
        volumes:
          - ./config:/mosquitto/config
          - ./data:/mosquitto/data
          - ./log:/mosquitto/log
  3. Create local password

    Create a HA-side user in the local broker. This password has nothing to do with the CampMatic password — it only protects the local link between HA and Mosquitto.

    A · Add-on: In the Mosquitto broker add-on → Configuration tab, add the user under logins:, save and restart the add-on. No terminal needed.
    B · Docker: Run the command below once.

    # Weg A (Add-on) — Konfiguration des Mosquitto-Add-ons
    logins:
      - username: homeassistant
        password: <lokales_passwort>
    # Weg B (Docker)
    docker exec -it mqtt5 mosquitto_passwd -c /mosquitto/config/pwfile homeassistant
    # enter a HA-side password
  4. Configure the bridge

    Replace every <placeholder> with the value from the Status page. bridge_capath /etc/ssl/certs uses the system CA store — no CA file to manage, hostname verification stays on.

    There are two topic lines and both are needed:
    topic # both 0 <prefix>/ <prefix>/ carries the readings in and your commands back out.
    topic homeassistant/+/<node>/# in 0 carries the auto-discovery messages. Per MQTT convention they live under homeassistant/…, i.e. outside your prefix — without this line Home Assistant creates no entities at all, even though every value shows up in MQTT Explorer. The + matches the entity type (sensor, climate, …) and in means: from the CampMatic broker to you only. These messages are retained on the broker, so they normally arrive the moment the bridge connects. If nothing arrives despite the line being correct, briefly disconnect the box from power: on its next connection it re-announces all its entities.

    A · Add-on: In the add-on → Configuration, set customize to active: true. Then create the folder /share/mosquitto and put a file campmatic.conf there containing only the connection block — no listener, password_file or persistence, the add-on already provides those. Then restart the add-on.

    Watch out — the classic trap: /share sits next to the config directory (/homeassistant or /config), not inside it. A folder /homeassistant/share/mosquitto will not be found. Three ways to create it correctly:
    Terminal & SSH (quickest): mkdir -p /share/mosquitto
    File editor: use the folder icon to navigate up until homeassistant and share sit side by side, then enter share
    Samba: open the share network share (not config)
    B · Docker: Create config/mosquitto.conf next to the password file, then docker restart mqtt5.

    # Weg A (Add-on) — 1. Konfiguration des Mosquitto-Add-ons
    customize:
      active: true
      folder: mosquitto
    # Weg A (Add-on) — 2. Datei /share/mosquitto/campmatic.conf
    connection campmatic-<NNN>
    address campmatic.de:8883
    bridge_capath /etc/ssl/certs
    remote_username <mqtt_user>
    remote_password <mqtt_pass>
    # Messwerte rein, Schaltbefehle raus
    topic # both 0 <prefix>/ <prefix>/
    # Auto-Discovery — ohne diese Zeile bleiben die Entitaeten aus
    topic homeassistant/+/<node>/# in 0
    # Weg B (Docker) — config/mosquitto.conf
    # Local listener — only HA talks to this
    allow_anonymous false
    listener 1883
    password_file /mosquitto/config/pwfile
    
    persistence true
    persistence_file mosquitto.db
    persistence_location /mosquitto/data/
    
    # --- Bridge to CampMatic ---
    connection campmatic-<NNN>
    address campmatic.de:8883
    bridge_capath /etc/ssl/certs
    remote_username <mqtt_user>
    remote_password <mqtt_pass>
    # Messwerte rein, Schaltbefehle raus
    topic # both 0 <prefix>/ <prefix>/
    # Auto-Discovery — ohne diese Zeile bleiben die Entitaeten aus
    topic homeassistant/+/<node>/# in 0
  5. Verify the bridge

    The logs should show "Connecting bridge" with no TLS errors — add-on: in the Log tab, Docker: with the command below.

    First things first: is the line there at all? It appears in the startup log after the Opening … listen socket lines and before mosquitto version 2.x running, carrying the name of your connection block: Connecting bridge campmatic-003 (campmatic.de:8883). Depending on the build it may read Connecting bridge (step 1) … — so just search the log for Connecting bridge. If the line is missing, the bridge file was never read — searching the log for error messages is then pointless, because Mosquitto knows nothing about the bridge. With the add-on it is almost always one of three things: customize: is not set to active: true (the folder alone is not enough), the file sits in /homeassistant/share/mosquitto instead of /share/mosquitto, or its name does not end in .conf (some editors silently append .txt).

    Don't be alarmed: the lines Protocol error from ::1: First packet not CONNECT and Client ::1 disconnected: protocol error appear on every start of the add-on. That is an internal port check and not an error — it is never the cause of a problem.

    If "Connecting bridge" is there but the connection fails: on Connection refused re-copy username/password from the Status page. On certificate verify failed the system CA store is missing — update the add-on or image. If the add-on reports Unable to open include_dir '/share/mosquitto' and stops, the folder from step 4 is missing — usually created inside the config directory (/homeassistant/share/mosquitto) instead of next to it. If the log shows the username in angle brackets, the <> were left in place.

    # Weg B (Docker)
    docker logs --tail 20 mqtt5 | grep -iE 'bridge|tls|connect'
  6. Connect Home Assistant to the local broker

    In HA: Settings → Devices & Services → Add Integration → MQTT. Broker: core-mosquitto for the add-on, or the container hostname/IP for Docker (e.g. mqtt5). Port: 1883. Username/password: the local HA user from step 3. SSL off. After saving, CampMatic entities appear automatically via MQTT discovery.

    Values arrive but no entities? Then the second topic line from step 4 is missing. To check, subscribe to homeassistant/# in MQTT Explorer on your local broker (not on campmatic.de): if that branch stays empty while values keep arriving under your prefix, that is exactly the cause. Add the line, restart Mosquitto, and the entities show up within seconds. Otherwise check under MQTT → Configure that "Enable discovery" is on and the discovery prefix is homeassistant.

  7. Multiple devices through one bridge

    Add one more connection block per device in mosquitto.conf — all run through the same local broker, HA stays connected with a single MQTT integration. Every block needs both topic lines. Mind that for devices up to 051 the name in the discovery line is grand-cali-…, not campmatic-… (see step 1).

    connection campmatic-003
    address campmatic.de:8883
    bridge_capath /etc/ssl/certs
    remote_username campmatic_003
    remote_password <pw_003>
    topic # both 0 campmatic/003/ campmatic/003/
    topic homeassistant/+/grand-cali-003/# in 0
    
    connection campmatic-004
    address campmatic.de:8883
    bridge_capath /etc/ssl/certs
    remote_username campmatic_004
    remote_password <pw_004>
    topic # both 0 campmatic/004/ campmatic/004/
    topic homeassistant/+/grand-cali-004/# in 0
  1. Get credentials from the Status page

    Log in at campmatic.de/app and open the Status page. The "MQTT Bridge (Home Assistant)" card shows: host (always campmatic.de), port (8883, TLS), username (e.g. campmatic_003), password (eye icon reveals), and topic prefix (e.g. campmatic/003). These values replace every <placeholder> below. The angle brackets go away tooremote_username <mqtt_user> becomes remote_username campmatic_003, not <campmatic_003>.

    One value is not on the Status page: the internal device name <node>. The bridge's discovery line needs it, and it follows from the device number:
    • device 001–051grand-cali-<NNN>, e.g. grand-cali-003
    • device 052 and up → campmatic-<NNN>, e.g. campmatic-054
    If unsure, subscribe to homeassistant/# in MQTT Explorer: the config topics are named homeassistant/<type>/<node>/<entity>/config.

  2. Set up the MQTT integration in Home Assistant

    Settings → Devices & Services → Add Integration → MQTT. Broker: campmatic.de, port: 8883, username/password from the Status page, SSL/TLS on, certificate validation "Auto" (system trust store). No CA file upload — the broker uses a public TLS certificate that HA trusts out of the box.

  3. When to prefer the bridge over direct?

    Direct is fine for one single device on a HA instance with stable internet. For multiple CampMatic devices or frequent WAN dropouts, the local bridge (see the "Bridge" tab) is the only practical option — HA entities stay available even when the cloud link is briefly down.

  1. Check auto-discovery

    After successful MQTT setup the entities appear automatically.

    The YAML examples below use <entity> as a placeholder. Replace it with the name part Home Assistant gave your device — it depends on when the device was built:
    • device 001–044grand_california_<NNN>, e.g. climate.grand_california_003_truma_heizung
    • device 045 and up → campmatic_<NNN>, e.g. climate.campmatic_054_truma_heizung
    To look it up, open Developer tools → States and filter for truma: take the whole name part before _truma. In the download package personalize.sh does this for you.

    Entities that already show campmatic_<NNN> in the examples (gas bottle, battery, Ruuvi) stay as they are — those names come from the package itself.

    A finished Lovelace dashboard can look like this — climate card on top, stats chips in the middle, 24-hour graph below, plus water control, energy mix and a status board:

  2. Room heater — climate card with chips and 24 h graph

    The polished variant: control card on top, chip row of live status values, 24-hour temperature trend at the bottom. Requires the HACS cards Mushroom, stack-in-card and mini-graph-card. Replace <NNN> with your device number.

    type: custom:stack-in-card
    mode: vertical
    keep:
      background: false
      border_radius: true
    cards:
      - type: custom:mushroom-climate-card
        entity: climate.<entity>_truma_heizung
        name: Raumheizung
        show_temperature_control: true
        hvac_modes:
          - "off"
          - heat
        collapsible_controls: false
        fill_container: true
    
      - type: custom:mushroom-chips-card
        alignment: center
        chips:
          - type: template
            icon: mdi:fire
            content: "{{ states('sensor.<entity>_truma_heizmodus') }}"
            icon_color: >-
              {% if is_state('binary_sensor.<entity>_truma_heizung_aktiv','on') %}
              orange{% else %}disabled{% endif %}
          - type: template
            icon: mdi:thermometer-check
            content: "{{ states('sensor.<entity>_truma_soll_raumtemperatur') | round(0) }} °C"
            icon_color: amber
          - type: template
            icon: mdi:lightning-bolt
            content: "{{ states('sensor.<entity>_truma_elektrische_leistung') }} W"
            icon_color: >-
              {% if is_state('binary_sensor.<entity>_truma_elektro_aktiv','on') %}
              yellow{% else %}disabled{% endif %}
          - type: template
            icon: mdi:fuel
            content: "{{ states('sensor.<entity>_truma_energiemix_status') }}"
            icon_color: >-
              {% if is_state('binary_sensor.<entity>_truma_diesel_aktiv','on') %}
              deep-orange{% else %}disabled{% endif %}
          - type: template
            icon: mdi:check-network
            content: ""
            content_info: none
            icon_color: >-
              {% if is_state('binary_sensor.<entity>_truma_cp_plus_alive','on') %}
              green{% else %}red{% endif %}
    
      - type: custom:mini-graph-card
        name: Temperaturverlauf (24 h)
        icon: mdi:thermometer-lines
        hours_to_show: 24
        points_per_hour: 4
        line_width: 3
        smoothing: true
        show:
          icon: true
          name: true
          labels: true
          extrema: true
          average: true
          fill: fade
          legend: true
          points: false
        entities:
          - entity: sensor.<entity>_truma_raumtemperatur
            name: Raum
            color: "#FF7A00"
          - entity: sensor.<entity>_truma_soll_raumtemperatur
            name: Soll
            color: "#FFC107"
            show_state: true
            y_axis: secondary
  3. Water heater — 3-button selector (Off / 40 °C / 60 °C)

    The Truma water heater only supports off / 40 / 60. Three chips replace the slider and highlight the active value:

    type: custom:mushroom-chips-card
    alignment: center
    chips:
      - type: template
        icon: mdi:water-off
        content: Aus
        icon_color: >-
          {% if state_attr('climate.<entity>_truma_wasser',
            'temperature') | float(99) == 0 %}red{% else %}disabled{% endif %}
        tap_action:
          action: call-service
          service: climate.set_temperature
          target:
            entity_id: climate.<entity>_truma_wasser
          data:
            temperature: 0
      - type: template
        icon: mdi:thermometer-low
        content: 40 °C
        icon_color: >-
          {% if state_attr('climate.<entity>_truma_wasser',
            'temperature') | float(0) == 40 %}orange{% else %}disabled{% endif %}
        tap_action:
          action: call-service
          service: climate.set_temperature
          target:
            entity_id: climate.<entity>_truma_wasser
          data:
            temperature: 40
      - type: template
        icon: mdi:thermometer-high
        content: 60 °C
        icon_color: >-
          {% if state_attr('climate.<entity>_truma_wasser',
            'temperature') | float(0) == 60 %}red{% else %}disabled{% endif %}
        tap_action:
          action: call-service
          service: climate.set_temperature
          target:
            entity_id: climate.<entity>_truma_wasser
          data:
            temperature: 60
  4. Energy mix selector

    A compact card for the Gas / Electric / Mix selector:

    type: custom:mushroom-entity-card
    entity: select.<entity>_truma_energiemix
    name: Energiemix
    icon: mdi:fuel
    icon_color: deep-orange
    fill_container: true
  5. Hiding entities

    Hide just in one dashboard: omit the card. Hide everywhere in HA: Settings → Devices & Services → MQTT → device → entity → gear icon → "Disable entity". The entity is not deleted on the device side, only hidden in HA.

  1. Why these three sensors need manual setup

    Truma and CampMatic entities are announced to Home Assistant via auto-discovery — once the MQTT connection is up, they are simply there. That includes two battery-monitor values: "Bordspannung" and "Bordbatterie Ladezustand" — but only from the BM2 with the lowest slot number. For everything else in the BLE sensor set — Mopeka, Ruuvi and the BM2 slot topics — this is not the case: the box publishes their values on dedicated raw topics campmatic/<NNN>/<integration>/<device>_<slot>/<field> without a discovery config. The reason: some of these sensors only send raw bytes that have to be converted first — in the CampMatic app that happens server-side, so calibration and new bottle types work without a firmware update.

    In Home Assistant you build both once: the MQTT sensors for the raw values and the template sensors holding the math. After that it just runs. Every formula in this section is exactly the one the CampMatic app uses — app and Home Assistant therefore show the same number.

    Three things that otherwise cost time:
    • The raw topics are not retained. After a HA restart the entities stay unknown until the next BLE packet arrives (Mopeka/Ruuvi ≤ 60 s, BM2 ≤ 30 s). That is expected.
    • That is why every sensor gets expire_after: 2700 (45 min): HA then shows a removed or dead sensor as unavailable instead of the last value forever.
    • The entity_id is built from device name + sensor name, lower-cased and with umlauts stripped: "Gasflasche Füllstand" becomes sensor.gasflasche_fullstand, "Messqualität" becomes ..._messqualitat. After the first restart check Developer tools → States and adjust the templates if needed.

    Rather not retype all this? Every YAML block in this section is available as a ready-made download — package file, automations, a complete dashboard view, a Mosquitto bridge template and a script that replaces the placeholders interactively: campmatic-homeassistant.zip (about 20 kB). The steps here explain what happens inside those files — the package just saves the typing.

  2. Find the slot number and tank type

    The CampMatic app stores the pairing in a retained MQTT topic. From it you read the slot number (it appears in the topic name as bottle_<S>) and the paired tank_type. Note: re-pairing can change the slot — update the topics in your YAML if it does.

    # Über die lokale Bridge (Tab "Bridge"):
    mosquitto_sub -h localhost -u homeassistant -P <lokales_pw> \
      -v -t 'campmatic/<NNN>/mopeka/config' -W 5
    
    # Oder direkt gegen den CampMatic-Broker:
    mosquitto_sub -h campmatic.de -p 8883 --capath /etc/ssl/certs \
      -u <mqtt_user> -P <mqtt_pass> \
      -v -t 'campmatic/<NNN>/mopeka/config' -W 5
    
    # Ausgabe (retained, kommt sofort):
    # campmatic/<NNN>/mopeka/config [{"slot":1,"mac":"AA:BB:CC:00:11:22","tank_type":"EUROPE_6KG","medium":"lpg"}]
    #                                       ^^^^^^^^                      ^^^^^^^^^^^^^^^^^^^^^^
    #                                       Slot -> bottle_1              Tanktyp -> full_mm unten
  3. Create the raw sensors

    The Mopeka Pro Check measures the ultrasonic time of flight to the liquid surface and sends raw bytes only: raw_tof (time of flight in µs), raw_t (temperature byte 0–127), battery_raw (battery byte), quality (echo quality 0–3) and rssi. These five go into configuration.yaml as MQTT sensors — marked as diagnostic, since they are useless without conversion.

    # configuration.yaml
    # <NNN> = Gerätenummer, <S> = Mopeka-Slot aus dem Schritt davor.
    mqtt:
      sensor:
        - name: "Gasflasche Roh-Laufzeit"
          unique_id: campmatic_<NNN>_gas_raw_tof
          state_topic: "campmatic/<NNN>/mopeka/bottle_<S>/raw_tof"
          state_class: measurement
          icon: mdi:radar
          entity_category: diagnostic
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          payload_available: "online"
          payload_not_available: "offline"
          # YAML-Anker: einmal definieren, in allen weiteren Sensoren nur
          # noch "device: *campmatic_dev" schreiben. Alle Entitäten landen
          # dadurch unter EINEM Gerät in Home Assistant.
          device: &campmatic_dev
            identifiers: ["campmatic_<NNN>"]
            name: "CampMatic <NNN>"
            manufacturer: "CampMatic"
    
        - name: "Gasflasche Roh-Temperaturbyte"
          unique_id: campmatic_<NNN>_gas_raw_t
          state_topic: "campmatic/<NNN>/mopeka/bottle_<S>/raw_t"
          state_class: measurement
          icon: mdi:thermometer
          entity_category: diagnostic
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          device: *campmatic_dev
    
        - name: "Gasflasche Roh-Batteriebyte"
          unique_id: campmatic_<NNN>_gas_raw_batt
          state_topic: "campmatic/<NNN>/mopeka/bottle_<S>/battery_raw"
          state_class: measurement
          icon: mdi:battery-unknown
          entity_category: diagnostic
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          device: *campmatic_dev
    
        - name: "Gasflasche Messqualität"
          unique_id: campmatic_<NNN>_gas_quality
          state_topic: "campmatic/<NNN>/mopeka/bottle_<S>/quality"
          state_class: measurement
          icon: mdi:signal-cellular-2
          entity_category: diagnostic
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          device: *campmatic_dev
    
        - name: "Gasflasche Signalstärke"
          unique_id: campmatic_<NNN>_gas_rssi
          state_topic: "campmatic/<NNN>/mopeka/bottle_<S>/rssi"
          unit_of_measurement: "dBm"
          device_class: signal_strength
          state_class: measurement
          entity_category: diagnostic
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          device: *campmatic_dev
  4. Compute distance, level, temperature and battery

    Now the actual math. The speed of sound in LPG depends on temperature, hence a second-order polynomial over the raw temperature byte — the coefficients come from the Mopeka Pro Developers Integration Guide v7. Two thresholds turn the distance into a percentage. The battery percentage is the formula used by the official Mopeka app.

    Worth knowing: Mopeka reports the 80 % safety fill mark as 100 % — so a freshly filled bottle correctly shows 100 %, not 80 %. And the level breathes: the same bottle reads a few percent differently at 30 °C than at 5 °C. That is physics, not a sensor fault.

    # configuration.yaml — abgeleitete Werte
    #
    # distance_mm = raw_tof * (c0 + c1*raw_t + c2*raw_t^2)
    #   LPG (Propan/Butan):  c = [ 0.573045, -0.002822, -5.35e-6  ]
    #   Wasser (Pro Check H2O): c = [ 0.600592,  0.003124, -1.368e-5 ]
    # raw_t ist das ROHE Temperaturbyte 0..127, NICHT der °C-Wert.
    template:
      - sensor:
          - name: "Gasflasche Distanz"
            unique_id: campmatic_<NNN>_gas_distanz
            unit_of_measurement: "mm"
            state_class: measurement
            icon: mdi:arrow-expand-vertical
            availability: >-
              {{ has_value('sensor.campmatic_<NNN>_gasflasche_roh_laufzeit')
                 and has_value('sensor.campmatic_<NNN>_gasflasche_roh_temperaturbyte') }}
            state: >-
              {% set tof = states('sensor.campmatic_<NNN>_gasflasche_roh_laufzeit') | float(0) %}
              {% set t   = states('sensor.campmatic_<NNN>_gasflasche_roh_temperaturbyte') | float(0) %}
              {{ (tof * (0.573045 - 0.002822 * t - 5.35e-6 * t * t)) | round(1) }}
    
          # Prozent = (Distanz - empty_mm) / (full_mm - empty_mm) * 100, geklemmt.
          # empty_mm ist bei allen Presets 38 (Restabstand Puck <-> Bodenplatte).
          # full_mm je Tanktyp -> Tabelle im nächsten Schritt.
          - name: "Gasflasche Füllstand"
            unique_id: campmatic_<NNN>_gas_fuellstand
            unit_of_measurement: "%"
            state_class: measurement
            icon: mdi:propane-tank
            availability: "{{ has_value('sensor.gasflasche_distanz') }}"
            state: >-
              {% set mm       = states('sensor.gasflasche_distanz') | float(0) %}
              {% set empty_mm = 38 %}
              {% set full_mm  = 333 %}
              {{ [[ (mm - empty_mm) / (full_mm - empty_mm) * 100, 0 ] | max, 100 ] | min | round(1) }}
    
          # Das Rohbyte ist um 40 versetzt, damit Minusgrade in ein
          # vorzeichenloses Byte passen.
          - name: "Gasflasche Temperatur"
            unique_id: campmatic_<NNN>_gas_temperatur
            unit_of_measurement: "°C"
            device_class: temperature
            state_class: measurement
            availability: "{{ has_value('sensor.campmatic_<NNN>_gasflasche_roh_temperaturbyte') }}"
            state: >-
              {{ (states('sensor.campmatic_<NNN>_gasflasche_roh_temperaturbyte') | float(0)) - 40 }}
    
          # Zellspannung = Byte / 32;  Prozent = (V - 2.2) / 0.65 * 100, geklemmt.
          # Gleiche Formel wie die offizielle Mopeka-App.
          - name: "Gasflasche Sensorbatterie"
            unique_id: campmatic_<NNN>_gas_sensorbatterie
            unit_of_measurement: "%"
            device_class: battery
            state_class: measurement
            availability: "{{ has_value('sensor.campmatic_<NNN>_gasflasche_roh_batteriebyte') }}"
            state: >-
              {% set v = (states('sensor.campmatic_<NNN>_gasflasche_roh_batteriebyte') | float(0)) / 32.0 %}
              {{ [[ (v - 2.2) / 0.65 * 100, 0 ] | max, 100 ] | min | round(0) }}
            attributes:
              spannung_v: >-
                {{ ((states('sensor.campmatic_<NNN>_gasflasche_roh_batteriebyte') | float(0)) / 32.0) | round(2) }}
  5. Adjust the bottle type and fine-calibrate

    The level template contains full_mm: 333 — that is the 6 kg steel bottle. For other bottles just swap that number. empty_mm: 38 always stays; it is the residual gap between sensor puck and base plate that even an empty bottle still measures.

    # full_mm je Tanktyp — nur diese eine Zahl im Füllstand-Template ändern.
    # empty_mm ist überall 38.
    #
    #   EUROPE_6KG        6 kg Stahl                full_mm: 333
    #   EUROPE_11KG      11 kg Stahl                full_mm: 358
    #   EUROPE_14KG      14 kg Stahl                full_mm: 408
    #   EUROPE_6KG_ALU    6 kg Alu                  full_mm: 300
    #   EUROPE_11KG_ALU  11 kg Alu (TRAVELmate)     full_mm: 460
    #   CAMPINGGAZ_907   CampingGaz 907             full_mm: 130
    #   20LB_V           US 20 lb                   full_mm: 248
    #   30LB_V           US 30 lb                   full_mm: 381
    #   40LB_V           US 40 lb                   full_mm: 508
    #
    # Feinkalibrierung: Flasche wiegen, Tara vom Flaschenhals abziehen ->
    # tatsächliche kg Gas. Weicht der angezeigte Prozentwert ab, full_mm so
    # lange in 5-mm-Schritten korrigieren, bis es passt.
  6. Lovelace card

    A gauge for the level, tiles for fill height, temperature, sensor battery and measurement quality, plus a 7-day trend below. The long window is deliberate: a gas bottle empties over days, a 24 h graph would be a flat line. The trend needs the HACS card mini-graph-card, everything else is built in.

    type: grid
    cards:
      - type: heading
        heading: Gasflasche
        icon: mdi:propane-tank
    
      - type: gauge
        entity: sensor.gasflasche_fullstand
        name: Füllstand
        min: 0
        max: 100
        needle: true
        severity:
          red: 0
          yellow: 20
          green: 40
        grid_options:
          columns: 6
    
      - type: tile
        entity: sensor.gasflasche_distanz
        name: Füllhöhe
        icon: mdi:arrow-expand-vertical
        grid_options:
          columns: 6
    
      - type: tile
        entity: sensor.gasflasche_temperatur
        name: Temperatur
        grid_options:
          columns: 6
    
      - type: tile
        entity: sensor.gasflasche_sensorbatterie
        name: Sensorbatterie
        grid_options:
          columns: 6
    
      - type: tile
        entity: sensor.campmatic_<NNN>_gasflasche_messqualitat
        name: Messqualität
        icon: mdi:signal-cellular-2
        grid_options:
          columns: 6
    
      - type: custom:mini-graph-card
        name: Füllstand (7 Tage)
        icon: mdi:propane-tank
        hours_to_show: 168
        points_per_hour: 1
        line_width: 3
        smoothing: true
        show:
          icon: true
          name: true
          labels: true
          extrema: true
          average: true
          fill: fade
          legend: false
          points: false
        entities:
          - entity: sensor.gasflasche_fullstand
            name: Füllstand
            color: "#FF7A00"
  7. Automation: warn before the gas runs out

    Two details that separate a useful automation from an annoying one: the hold time (for), so the level's temperature swing does not trigger a false alarm, and the quality check — at quality < 2 the echo is too weak, so it is better to stay quiet.

    # automations.yaml
    - alias: "Gasflasche wird knapp"
      mode: single
      triggers:
        # "for" ist wichtig: der Füllstand atmet mit der Flaschentemperatur.
        # Ohne Halte-Zeit meldet die Automation an einem sonnigen Nachmittag.
        - trigger: numeric_state
          entity_id: sensor.gasflasche_fullstand
          below: 20
          for: "00:30:00"
      conditions:
        # Messqualität 0..3. Unter 2 ist das Echo zu schwach — dann lieber
        # gar nicht melden als falsch melden.
        - condition: numeric_state
          entity_id: sensor.campmatic_<NNN>_gasflasche_messqualitat
          above: 1
      actions:
        - action: notify.persistent_notification
          data:
            title: "Gas wird knapp"
            message: >-
              Nur noch {{ states('sensor.gasflasche_fullstand') }} %
              ({{ states('sensor.gasflasche_distanz') }} mm Füllhöhe,
              {{ states('sensor.gasflasche_temperatur') }} °C an der Flasche).
  8. Bonus: is the bottle tilted?

    The Mopeka Pro has a tilt sensor. If the vehicle stands at an angle, the ultrasonic pulse misses the liquid surface and the level becomes unreliable — with this sensor you see the reason instead of guessing. The firmware publishes both axes from version 1.11 on.

    # 1) Roh-Topics ergänzen (Firmware ab 1.11)
    mqtt:
      sensor:
        - name: "Gasflasche Neigung X"
          unique_id: campmatic_<NNN>_gas_accel_x
          state_topic: "campmatic/<NNN>/mopeka/bottle_<S>/accel_x"
          unit_of_measurement: "G"
          entity_category: diagnostic
          expire_after: 2700
          device: *campmatic_dev
        - name: "Gasflasche Neigung Y"
          unique_id: campmatic_<NNN>_gas_accel_y
          state_topic: "campmatic/<NNN>/mopeka/bottle_<S>/accel_y"
          unit_of_measurement: "G"
          entity_category: diagnostic
          expire_after: 2700
          device: *campmatic_dev
    
    # 2) Gesamtneigung: theta = asin(sqrt(x^2 + y^2)), umgerechnet in Grad.
    #    Der Beschleunigungssensor sättigt bauartbedingt bei ±0,125 G
    #    (~7,2°) — mehr als "7,2" zeigt er nie an, das heißt dann schlicht
    #    "deutlich schief".
    template:
      - sensor:
          - name: "Gasflasche Neigung"
            unique_id: campmatic_<NNN>_gas_neigung
            unit_of_measurement: "°"
            icon: mdi:angle-acute
            availability: >-
              {{ has_value('sensor.campmatic_<NNN>_gasflasche_neigung_x')
                 and has_value('sensor.campmatic_<NNN>_gasflasche_neigung_y') }}
            state: >-
              {% set gx = states('sensor.campmatic_<NNN>_gasflasche_neigung_x') | float(0) %}
              {% set gy = states('sensor.campmatic_<NNN>_gasflasche_neigung_y') | float(0) %}
              {% set m  = [ sqrt(gx*gx + gy*gy), 1 ] | min %}
              {{ (asin(m) * 57.2958) | round(1) }}
  1. Why these three sensors need manual setup

    Truma and CampMatic entities are announced to Home Assistant via auto-discovery — once the MQTT connection is up, they are simply there. That includes two battery-monitor values: "Bordspannung" and "Bordbatterie Ladezustand" — but only from the BM2 with the lowest slot number. For everything else in the BLE sensor set — Mopeka, Ruuvi and the BM2 slot topics — this is not the case: the box publishes their values on dedicated raw topics campmatic/<NNN>/<integration>/<device>_<slot>/<field> without a discovery config. The reason: some of these sensors only send raw bytes that have to be converted first — in the CampMatic app that happens server-side, so calibration and new bottle types work without a firmware update.

    In Home Assistant you build both once: the MQTT sensors for the raw values and the template sensors holding the math. After that it just runs. Every formula in this section is exactly the one the CampMatic app uses — app and Home Assistant therefore show the same number.

    Three things that otherwise cost time:
    • The raw topics are not retained. After a HA restart the entities stay unknown until the next BLE packet arrives (Mopeka/Ruuvi ≤ 60 s, BM2 ≤ 30 s). That is expected.
    • That is why every sensor gets expire_after: 2700 (45 min): HA then shows a removed or dead sensor as unavailable instead of the last value forever.
    • The entity_id is built from device name + sensor name, lower-cased and with umlauts stripped: "Gasflasche Füllstand" becomes sensor.gasflasche_fullstand, "Messqualität" becomes ..._messqualitat. After the first restart check Developer tools → States and adjust the templates if needed.

    Rather not retype all this? Every YAML block in this section is available as a ready-made download — package file, automations, a complete dashboard view, a Mosquitto bridge template and a script that replaces the placeholders interactively: campmatic-homeassistant.zip (about 20 kB). The steps here explain what happens inside those files — the package just saves the typing.

  2. Find the slot number

    As with Mopeka the pairing lives in a retained topic. The slot appears in the topic name as monitor_<S>. Multiple monitors (e.g. leisure and starter battery) each get their own slot.

    mosquitto_sub -h localhost -u homeassistant -P <lokales_pw> \
      -v -t 'campmatic/<NNN>/bm2/config' -W 5
    
    # campmatic/<NNN>/bm2/config [{"slot":2,"mac":"AA:BB:CC:00:33:44"}]
    #                                    ^^^^^^^^  -> monitor_2
  3. Create the MQTT sensors

    The BM2 already delivers finished values — voltage in volts, charge as state of charge in percent, plus rssi. So no conversion is needed here, only the sensor definition.

    Do not confuse them: the box additionally mirrors one monitor onto the auto-discovered entities "Bordspannung" and "Bordbatterie Ladezustand" — but only the one with the lowest slot number. With two battery monitors these slot topics are the only unambiguous route.

    # configuration.yaml
    # <NNN> = Gerätenummer, <S> = BM2-Slot aus dem Schritt davor.
    # Steht der Anker &campmatic_dev schon weiter oben in der Datei, hier
    # nur noch "device: *campmatic_dev" schreiben.
    mqtt:
      sensor:
        - name: "Aufbaubatterie Spannung"
          unique_id: campmatic_<NNN>_bm2_voltage
          state_topic: "campmatic/<NNN>/bm2/monitor_<S>/voltage"
          unit_of_measurement: "V"
          device_class: voltage
          state_class: measurement
          suggested_display_precision: 2
          icon: mdi:car-battery
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          payload_available: "online"
          payload_not_available: "offline"
          device: &campmatic_dev
            identifiers: ["campmatic_<NNN>"]
            name: "CampMatic <NNN>"
            manufacturer: "CampMatic"
    
        - name: "Aufbaubatterie Ladezustand"
          unique_id: campmatic_<NNN>_bm2_charge
          state_topic: "campmatic/<NNN>/bm2/monitor_<S>/charge"
          unit_of_measurement: "%"
          device_class: battery
          state_class: measurement
          suggested_display_precision: 0
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          device: *campmatic_dev
    
        - name: "Aufbaubatterie Signalstärke"
          unique_id: campmatic_<NNN>_bm2_rssi
          state_topic: "campmatic/<NNN>/bm2/monitor_<S>/rssi"
          unit_of_measurement: "dBm"
          device_class: signal_strength
          state_class: measurement
          entity_category: diagnostic
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          device: *campmatic_dev
  4. Compute the state of charge honestly

    The BM2's charge value is an estimate derived from voltage — and it is calibrated for lead-acid batteries. On a LiFePO4 the BM2 therefore reads almost always 100 %: a lithium battery sits at 13.0–13.3 V across nearly its entire range, which the BM2 reads as "full". That is not a defect, it is the wrong curve.

    The template sensor below interpolates over a real curve instead, and deliberately makes itself unavailable while charging. Because voltage only says something about charge at rest: under load it sags, on a charger it sits too high. The value becomes trustworthy after roughly two hours without significant load.

    # configuration.yaml
    template:
      - binary_sensor:
          # Ab ~13,2 V arbeitet ein Lader (Landstrom, Lichtmaschine, Solar).
          # Die Spannung sagt dann nichts mehr über den Ladezustand aus.
          - name: "Aufbaubatterie lädt"
            unique_id: campmatic_<NNN>_bm2_laedt
            device_class: battery_charging
            state: >-
              {{ states('sensor.campmatic_<NNN>_aufbaubatterie_spannung') | float(0) >= 13.2 }}
    
      - sensor:
          - name: "Aufbaubatterie Ladezustand (Ruhespannung)"
            unique_id: campmatic_<NNN>_bm2_soc_ruhe
            unit_of_measurement: "%"
            device_class: battery
            state_class: measurement
            icon: mdi:battery-heart-variant
            # Nur im Ruhezustand aussagekräftig: unter Last bricht die Spannung
            # ein, beim Laden liegt sie zu hoch.
            availability: >-
              {{ has_value('sensor.campmatic_<NNN>_aufbaubatterie_spannung')
                 and is_state('binary_sensor.aufbaubatterie_ladt', 'off') }}
            state: >-
              {% set v = states('sensor.campmatic_<NNN>_aufbaubatterie_spannung') | float(0) %}
              {# Blei / AGM / Gel, 12 V, ca. 20 °C, mind. 2 h ohne Last #}
              {% set tbl = [[11.80,0],[12.00,10],[12.10,20],[12.20,30],[12.30,40],
                            [12.40,50],[12.50,65],[12.60,80],[12.70,90],[12.80,100]] %}
              {# LiFePO4 stattdessen:                                              #}
              {# [[12.00,0],[12.80,10],[12.90,20],[13.00,30],[13.10,50],           #}
              {#  [13.15,60],[13.20,80],[13.30,90],[13.40,100]]                    #}
              {% set ns = namespace(p = 100 if v >= tbl[-1][0] else 0) %}
              {% for i in range(tbl | length - 1) %}
                {% if v >= tbl[i][0] and v < tbl[i+1][0] %}
                  {% set ns.p = tbl[i][1] + (v - tbl[i][0]) / (tbl[i+1][0] - tbl[i][0])
                                * (tbl[i+1][1] - tbl[i][1]) %}
                {% endif %}
              {% endfor %}
              {{ ns.p | round(0) }}
  5. Lovelace card

    Two gauges plus a 24-hour trend with two axes — voltage on the left, state of charge on the right. In the trend you can spot charging phases as plateaus and overnight consumption as a gentle slope. The colour thresholds of the voltage gauge (red from 10 V, yellow from 12 V, green from 12.8 V) suit lead/AGM; for LiFePO4 use 12.8 / 13.0 / 13.2 V.

    type: grid
    cards:
      - type: heading
        heading: Bordbatterie
        icon: mdi:car-battery
    
      - type: gauge
        entity: sensor.campmatic_<NNN>_aufbaubatterie_ladezustand
        name: Ladezustand
        min: 0
        max: 100
        needle: true
        severity:
          red: 0
          yellow: 40
          green: 60
        grid_options:
          columns: 6
    
      - type: gauge
        entity: sensor.campmatic_<NNN>_aufbaubatterie_spannung
        name: Spannung
        min: 10
        max: 15
        needle: true
        severity:
          red: 10
          yellow: 12
          green: 12.8
        grid_options:
          columns: 6
    
      - type: custom:mini-graph-card
        name: Bordspannung (24 h)
        icon: mdi:car-battery
        hours_to_show: 24
        points_per_hour: 4
        line_width: 3
        smoothing: true
        show:
          icon: true
          name: true
          labels: true
          extrema: true
          average: true
          fill: fade
          legend: true
          points: false
        entities:
          - entity: sensor.campmatic_<NNN>_aufbaubatterie_spannung
            name: Spannung
            color: "#4CAF50"
          - entity: sensor.campmatic_<NNN>_aufbaubatterie_ladezustand
            name: Ladung
            color: "#2196F3"
            y_axis: secondary
            show_state: true
  6. Automations: deep discharge and end of charging

    The first automation protects the battery: below 12.0 V (lead) sulfation starts, and every hour spent there permanently costs capacity. The second reports when charging current disappears — handy to notice that shore power at the campsite failed before the fridge drains the battery.

    # automations.yaml
    - alias: "Aufbaubatterie kritisch"
      mode: single
      triggers:
        # Blei/AGM: 12,0 V unter Ruhe = praktisch leer, ab hier beginnt die
        # Sulfatierung. LiFePO4: stattdessen 12,8 V eintragen.
        - trigger: numeric_state
          entity_id: sensor.campmatic_<NNN>_aufbaubatterie_spannung
          below: 12.0
          for: "00:15:00"
      conditions:
        - condition: state
          entity_id: binary_sensor.aufbaubatterie_ladt
          state: "off"
      actions:
        - action: notify.persistent_notification
          data:
            title: "Bordbatterie kritisch"
            message: >-
              {{ states('sensor.campmatic_<NNN>_aufbaubatterie_spannung') }} V —
              bitte laden. Verbraucher abschalten.
    
    - alias: "Landstrom weg — Batterie entlädt"
      mode: single
      triggers:
        - trigger: state
          entity_id: binary_sensor.aufbaubatterie_ladt
          from: "on"
          to: "off"
          for: "00:10:00"
      actions:
        - action: notify.persistent_notification
          data:
            title: "Kein Ladestrom mehr"
            message: >-
              Ladung beendet bei
              {{ states('sensor.campmatic_<NNN>_aufbaubatterie_spannung') }} V.
  1. Why these three sensors need manual setup

    Truma and CampMatic entities are announced to Home Assistant via auto-discovery — once the MQTT connection is up, they are simply there. That includes two battery-monitor values: "Bordspannung" and "Bordbatterie Ladezustand" — but only from the BM2 with the lowest slot number. For everything else in the BLE sensor set — Mopeka, Ruuvi and the BM2 slot topics — this is not the case: the box publishes their values on dedicated raw topics campmatic/<NNN>/<integration>/<device>_<slot>/<field> without a discovery config. The reason: some of these sensors only send raw bytes that have to be converted first — in the CampMatic app that happens server-side, so calibration and new bottle types work without a firmware update.

    In Home Assistant you build both once: the MQTT sensors for the raw values and the template sensors holding the math. After that it just runs. Every formula in this section is exactly the one the CampMatic app uses — app and Home Assistant therefore show the same number.

    Three things that otherwise cost time:
    • The raw topics are not retained. After a HA restart the entities stay unknown until the next BLE packet arrives (Mopeka/Ruuvi ≤ 60 s, BM2 ≤ 30 s). That is expected.
    • That is why every sensor gets expire_after: 2700 (45 min): HA then shows a removed or dead sensor as unavailable instead of the last value forever.
    • The entity_id is built from device name + sensor name, lower-cased and with umlauts stripped: "Gasflasche Füllstand" becomes sensor.gasflasche_fullstand, "Messqualität" becomes ..._messqualitat. After the first restart check Developer tools → States and adjust the templates if needed.

    Rather not retype all this? Every YAML block in this section is available as a ready-made download — package file, automations, a complete dashboard view, a Mosquitto bridge template and a script that replaces the placeholders interactively: campmatic-homeassistant.zip (about 20 kB). The steps here explain what happens inside those files — the package just saves the typing.

  2. Find the slot number

    Again the pairing is in a retained topic; the slot appears as tag_<S> in the topic name. Multiple tags — e.g. one in the living area, one in the fridge, one outside — each get their own slot.

    mosquitto_sub -h localhost -u homeassistant -P <lokales_pw> \
      -v -t 'campmatic/<NNN>/ruuvi/config' -W 5
    
    # campmatic/<NNN>/ruuvi/config [{"slot":3,"mac":"AA:BB:CC:00:55:66"}]
    #                                      ^^^^^^^^  -> tag_3
  3. Create the MQTT sensors

    The RuuviTag broadcasts in RAWv2 format (data format 5) and delivers ready-converted values: temperature, humidity, air pressure, battery voltage and signal strength. Plus a movement counter that increments whenever the tag is moved — you can build a door contact or a "vehicle moved" alert from it without any extra hardware.

    # configuration.yaml
    # <NNN> = Gerätenummer, <S> = Ruuvi-Slot aus dem Schritt davor.
    mqtt:
      sensor:
        - name: "Ruuvi Temperatur"
          unique_id: campmatic_<NNN>_ruuvi_temp
          state_topic: "campmatic/<NNN>/ruuvi/tag_<S>/temp_c"
          unit_of_measurement: "°C"
          device_class: temperature
          state_class: measurement
          suggested_display_precision: 1
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          payload_available: "online"
          payload_not_available: "offline"
          device: &campmatic_dev
            identifiers: ["campmatic_<NNN>"]
            name: "CampMatic <NNN>"
            manufacturer: "CampMatic"
    
        - name: "Ruuvi Luftfeuchtigkeit"
          unique_id: campmatic_<NNN>_ruuvi_hum
          state_topic: "campmatic/<NNN>/ruuvi/tag_<S>/humidity"
          unit_of_measurement: "%"
          device_class: humidity
          state_class: measurement
          suggested_display_precision: 1
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          device: *campmatic_dev
    
        - name: "Ruuvi Luftdruck"
          unique_id: campmatic_<NNN>_ruuvi_press
          state_topic: "campmatic/<NNN>/ruuvi/tag_<S>/pressure_hpa"
          unit_of_measurement: "hPa"
          device_class: pressure
          state_class: measurement
          suggested_display_precision: 1
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          device: *campmatic_dev
    
        - name: "Ruuvi Batteriespannung"
          unique_id: campmatic_<NNN>_ruuvi_batt
          state_topic: "campmatic/<NNN>/ruuvi/tag_<S>/battery_v"
          unit_of_measurement: "V"
          device_class: voltage
          state_class: measurement
          suggested_display_precision: 3
          entity_category: diagnostic
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          device: *campmatic_dev
    
        - name: "Ruuvi Signalstärke"
          unique_id: campmatic_<NNN>_ruuvi_rssi
          state_topic: "campmatic/<NNN>/ruuvi/tag_<S>/rssi"
          unit_of_measurement: "dBm"
          device_class: signal_strength
          state_class: measurement
          entity_category: diagnostic
          expire_after: 2700
          availability_topic: "campmatic/<NNN>/status"
          device: *campmatic_dev
    
        # Bewegungszähler des Tags — zählt hoch, sobald der Tag bewegt wird.
        # Als Trigger brauchbar ("Schranktür auf", "Fahrzeug bewegt").
        - name: "Ruuvi Bewegungszähler"
          unique_id: campmatic_<NNN>_ruuvi_moves
          state_topic: "campmatic/<NNN>/ruuvi/tag_<S>/moves"
          state_class: total_increasing
          icon: mdi:motion-sensor
          entity_category: diagnostic
          expire_after: 2700
          device: *campmatic_dev
  4. Dew point, absolute humidity and true air pressure

    Three calculations that turn three readings into the numbers you actually want:

    Dew point — the temperature at which air releases its water. If any surface inside the vehicle drops below it (window pane, sheet metal, pop-top roof), condensation forms there. The gap between room temperature and dew point is therefore the most honest measure of mould risk.

    Absolute humidity in g/m³ — relative humidity alone tells you nothing about ventilating, because it jumps with temperature: 80 % at 5 °C outside is drier than 55 % at 22 °C inside. Only comparing the absolute values answers whether airing actually carries the moisture out.

    Sea-level air pressure — the tag measures absolute pressure where it sits. At 1000 m altitude that is around 120 hPa below what the weather report says. Without the conversion every alpine pass looks like a hurricane.

    # configuration.yaml
    template:
      - sensor:
          # --- Taupunkt (Magnus-Formel) ---
          # gamma = ln(RH/100) + (17.62*T) / (243.12+T)
          # Td    = 243.12*gamma / (17.62-gamma)
          # Unterschreitet eine Fläche im Fahrzeug (Fenster, Blech, Aufstelldach)
          # den Taupunkt, schlägt sich dort Wasser nieder.
          - name: "Innenklima Taupunkt"
            unique_id: campmatic_<NNN>_ruuvi_taupunkt
            unit_of_measurement: "°C"
            device_class: temperature
            state_class: measurement
            availability: >-
              {{ has_value('sensor.campmatic_<NNN>_ruuvi_temperatur')
                 and has_value('sensor.campmatic_<NNN>_ruuvi_luftfeuchtigkeit') }}
            state: >-
              {% set t  = states('sensor.campmatic_<NNN>_ruuvi_temperatur') | float(0) %}
              {% set rh = [ states('sensor.campmatic_<NNN>_ruuvi_luftfeuchtigkeit') | float(1), 1 ] | max %}
              {% set g  = log(rh / 100) + (17.62 * t) / (243.12 + t) %}
              {{ (243.12 * g / (17.62 - g)) | round(1) }}
    
          # --- Absolute Luftfeuchte in g/m³ ---
          # AH = 216.7 * (RH/100 * 6.112 * e^(17.62*T/(243.12+T)) / (273.15+T))
          # Das ist die Zahl, auf die es beim Lüften ankommt: relative Feuchte
          # allein sagt nichts, weil sie mit der Temperatur springt.
          - name: "Innenklima absolute Feuchte"
            unique_id: campmatic_<NNN>_ruuvi_abs_feuchte
            unit_of_measurement: "g/m³"
            state_class: measurement
            icon: mdi:water-percent
            availability: >-
              {{ has_value('sensor.campmatic_<NNN>_ruuvi_temperatur')
                 and has_value('sensor.campmatic_<NNN>_ruuvi_luftfeuchtigkeit') }}
            state: >-
              {% set t  = states('sensor.campmatic_<NNN>_ruuvi_temperatur') | float(0) %}
              {% set rh = states('sensor.campmatic_<NNN>_ruuvi_luftfeuchtigkeit') | float(0) %}
              {{ (2.1674 * rh * 6.112 * e ** (17.62 * t / (243.12 + t)) / (273.15 + t)) | round(2) }}
    
          # --- Luftdruck auf Meereshöhe ---
          # Der Tag misst den ABSOLUTEN Druck. Wetterberichte nennen den auf
          # Meereshöhe reduzierten — auf 1000 m Höhe sind das ~120 hPa
          # Unterschied. Ohne diese Umrechnung wirkt jeder Bergstellplatz wie
          # ein Orkantief.  <HOEHE_M> = Höhe des Stellplatzes in Metern.
          - name: "Luftdruck (Meereshöhe)"
            unique_id: campmatic_<NNN>_ruuvi_press_msl
            unit_of_measurement: "hPa"
            device_class: pressure
            state_class: measurement
            availability: "{{ has_value('sensor.campmatic_<NNN>_ruuvi_luftdruck') }}"
            state: >-
              {% set p = states('sensor.campmatic_<NNN>_ruuvi_luftdruck') | float(0) %}
              {% set t = states('sensor.campmatic_<NNN>_ruuvi_temperatur') | float(15) %}
              {% set h = <HOEHE_M> %}
              {{ (p * (1 - (0.0065 * h) / (t + 0.0065 * h + 273.15)) ** -5.257) | round(1) }}
    
      - binary_sensor:
          # --- Lüften bringt jetzt etwas ---
          # Nur wenn die Außenluft absolut trockener ist als die Innenluft,
          # trocknet Lüften das Fahrzeug. Zweiter Ruuvi draußen = <A> ;
          # alternativ die Werte einer weather.*-Entität einsetzen.
          - name: "Lüften empfohlen"
            unique_id: campmatic_<NNN>_lueften
            icon: mdi:window-open-variant
            state: >-
              {% set ti  = states('sensor.campmatic_<NNN>_ruuvi_temperatur') | float(0) %}
              {% set hi  = states('sensor.campmatic_<NNN>_ruuvi_luftfeuchtigkeit') | float(0) %}
              {% set ta  = states('sensor.campmatic_<NNN>_ruuvi_temperatur_<A>') | float(0) %}
              {% set ha  = states('sensor.campmatic_<NNN>_ruuvi_luftfeuchtigkeit_<A>') | float(0) %}
              {% set ai  = 2.1674 * hi * 6.112 * e ** (17.62 * ti / (243.12 + ti)) / (273.15 + ti) %}
              {% set aa  = 2.1674 * ha * 6.112 * e ** (17.62 * ta / (243.12 + ta)) / (273.15 + ta) %}
              {{ hi > 60 and (ai - aa) > 1.0 }}
  5. Lovelace card

    Temperature and humidity as gauges with warning bands (yellow from 65 % humidity, red from 80 % — above that a vehicle gets damp quickly), air pressure, tag battery and dew point as tiles, plus the 24-hour trend with two axes below.

    type: grid
    cards:
      - type: heading
        heading: Ruuvi Innenklima
        icon: mdi:home-thermometer
    
      - type: gauge
        entity: sensor.campmatic_<NNN>_ruuvi_temperatur
        name: Temperatur
        min: 0
        max: 35
        needle: true
        severity:
          green: 0
          yellow: 24
          red: 28
        grid_options:
          columns: 6
    
      - type: gauge
        entity: sensor.campmatic_<NNN>_ruuvi_luftfeuchtigkeit
        name: Luftfeuchtigkeit
        min: 0
        max: 100
        needle: true
        severity:
          green: 0
          yellow: 65
          red: 80
        grid_options:
          columns: 6
    
      - type: tile
        entity: sensor.campmatic_<NNN>_ruuvi_luftdruck
        name: Luftdruck
        icon: mdi:gauge
        grid_options:
          columns: 6
    
      - type: tile
        entity: sensor.campmatic_<NNN>_ruuvi_batteriespannung
        name: Tag-Batterie
        icon: mdi:battery-outline
        grid_options:
          columns: 6
    
      - type: tile
        entity: sensor.innenklima_taupunkt
        name: Taupunkt
        icon: mdi:water-thermometer
        grid_options:
          columns: 6
    
      - type: custom:mini-graph-card
        name: Innenklima (24 h)
        icon: mdi:home-thermometer
        hours_to_show: 24
        points_per_hour: 4
        line_width: 3
        smoothing: true
        show:
          icon: true
          name: true
          labels: true
          extrema: true
          average: true
          fill: fade
          legend: true
          points: false
        entities:
          - entity: sensor.campmatic_<NNN>_ruuvi_temperatur
            name: Temperatur
            color: "#FF7A00"
          - entity: sensor.campmatic_<NNN>_ruuvi_luftfeuchtigkeit
            name: Feuchte
            color: "#2196F3"
            y_axis: secondary
            show_state: true
  6. Automations: frost, condensation, battery

    The first automation is the most useful one in winter storage: if the vehicle drops below 3 °C it sets the Truma to frost-protection level itself — the link between Ruuvi and heater that turns two sensors into a system. The second warns before condensation forms. The third reports the dead coin cell in time: the tag runs on a CR2477, below 2.5 V it is due. Note that in frost the voltage temporarily sags without the cell being empty — hence the long 6-hour hold time.

    # automations.yaml
    - alias: "Frostgefahr im Fahrzeug"
      mode: single
      triggers:
        - trigger: numeric_state
          entity_id: sensor.campmatic_<NNN>_ruuvi_temperatur
          below: 3
          for: "00:10:00"
      actions:
        # Beispiel: Truma automatisch auf Frostschutz-Niveau setzen.
        - action: climate.set_temperature
          target:
            entity_id: climate.<entity>_truma_heizung
          data:
            temperature: 8
            hvac_mode: heat
        - action: notify.persistent_notification
          data:
            title: "Frostgefahr"
            message: >-
              {{ states('sensor.campmatic_<NNN>_ruuvi_temperatur') }} °C im
              Fahrzeug — Heizung auf 8 °C gesetzt.
    
    - alias: "Kondensat-Warnung"
      mode: single
      triggers:
        # Taupunkt nah an der Raumtemperatur = die kalten Flächen sind schon
        # drunter. 2 K Abstand ist eine brauchbare Vorwarnstufe.
        - trigger: template
          value_template: >-
            {{ (states('sensor.campmatic_<NNN>_ruuvi_temperatur') | float(99)
                - states('sensor.innenklima_taupunkt') | float(0)) < 2 }}
          for: "00:20:00"
      actions:
        - action: notify.persistent_notification
          data:
            title: "Feuchtigkeit im Fahrzeug"
            message: >-
              Taupunkt {{ states('sensor.innenklima_taupunkt') }} °C bei
              {{ states('sensor.campmatic_<NNN>_ruuvi_temperatur') }} °C
              Raumtemperatur — lüften oder heizen.
    
    - alias: "Ruuvi Tag-Batterie leer"
      mode: single
      triggers:
        - trigger: numeric_state
          entity_id: sensor.campmatic_<NNN>_ruuvi_batteriespannung
          below: 2.5
          for: "06:00:00"
      actions:
        - action: notify.persistent_notification
          data:
            title: "Ruuvi Tag: Batterie wechseln"
            message: >-
              Nur noch {{ states('sensor.campmatic_<NNN>_ruuvi_batteriespannung') }} V
              (CR2477).
  1. Set up the sensor with its manufacturer app

    CampMatic supports additional Bluetooth sensors: Mopeka gas level sensors (e.g. Mopeka Pro Check), BM2 battery monitors for the onboard/leisure battery and Ruuvi environment sensors (temperature, humidity, air pressure).

    After purchase, first set up the device with the manufacturer's app on your smartphone (e.g. the Mopeka, BM2 or Ruuvi app). This activates and calibrates the sensor over Bluetooth. You only need to do this once — afterwards the device is detected by CampMatic permanently.

  2. Add the device in CampMatic

    In CampMatic open Settings → Devices and choose Add device. Select the matching device type (Mopeka gas sensor, BM2 battery monitor or Ruuvi environment sensor). You can add multiple sensors — e.g. a main and a reserve gas bottle, several batteries, or multiple environment sensors.

  3. Automatic detection & dashboard display

    CampMatic then finds the sensor automatically when in range and shows it as its own tile on the dashboard — with live values such as gas level and bottle temperature, battery voltage and state of charge (SoC), or temperature, humidity and air pressure.

  4. And in Home Assistant?

    In the CampMatic app you are done — fill level, state of charge and climate values are computed server-side and show up in the dashboard on their own.

    If you want the sensors in Home Assistant as well, some manual work is required: the three BLE integrations publish on dedicated raw topics without auto-discovery. The Home Assistant Integration section has a tab for each — Gas bottle (Mopeka), Battery (BM2) and Climate (Ruuvi) — with complete YAML examples, the conversion formulas, ready-made Lovelace cards and automations.

  1. The Truma does not detect the CampMatic box (no T23.700 entry)

    After connecting, wait about 3 minutes until the box has fully started. Then run Reset → PR SET in the Truma control panel menu and check the Index menu for a new (third) entry showing T23.700. If it does not appear: check that the RJ12 connection is seated properly and repeat the reset. If you wired it through the splitter at the CP Plus, both splitter outputs must sit tight and the original cable must be back in the CP Plus; if you connected directly at the heater, the plug belongs in the right-hand free socket (see Installation).

  2. No WiFi selection appears after logging in

    If the normal control interface appears after logging in to the configuration page instead of the WiFi selection, open http://192.168.4.1/captiveportal directly — the WiFi selection is there.

  3. Box is offline / WiFi network has changed

    As soon as the configured WiFi is unreachable, the box automatically opens its own access point (SSID and password are on the box sticker and on the app status page under "Fallback WiFi"). Connect your phone to it and open http://192.168.4.1 — you can control the heater locally without internet there and store a new WiFi via /captiveportal. An offline notification is triggered once the box has not sent data for more than 5 minutes (once per offline phase).

  4. The 4G box does not connect to the cellular network

    Most common cause: the SIM card still has PIN protection enabled. Disable the SIM PIN in a phone first (iOS and Android instructions in Setup, 4G Box tab). Also check that the SIM is inserted correctly and has credit or an active data plan.

  5. Registration or password email does not arrive

    Check your spam folder first. You can get a new app password via "Forgot your password?" on the login page — the link in the email is valid for 30 minutes. The credentials for the box's local configuration page (user admin) are on the box sticker and on the app status page.

  6. Setting up the Telegram bot and its commands

    In the app: Settings → Telegram → "Link Telegram", then tap "Open Telegram" and press "Start" in the @camp_matic_bot bot (the link is valid for 10 minutes — the connection is detected automatically). Commands: /status (temperatures, heating status — including gas bottles, onboard battery and climate sensors if paired), /control (button panel for heating, water, energy mix, fan), /set 22 (room temperature 0–30 °C, /set 0 = off), /help and /unlink. Multiple Telegram users per device and multiple devices per chat are supported.

  7. Configuring notifications

    Expand the "Notifications" card on the dashboard. Email and Telegram can be enabled separately for each event: box offline, target temperature reached, heating/hot water switched on or off, room temperature below/above a freely selectable threshold (e.g. frost protection at 5 °C). Paired sensors get their own rows: gas level (Mopeka), onboard voltage (BM2) and sensor temperature (Ruuvi). Note: temperature alerts only work while the heater is switched on. Use "Test Email" / "Test Telegram" to verify delivery right away.

  8. Heating schedules and timers

    Open "Scheduler" at the top of the dashboard. You can schedule room heating and hot water (on/off with target temperature), energy mix and fan mode — daily, on specific weekdays or once, to the minute. Schedules run server-side in your time zone (configurable in Settings) — the box itself stores nothing. If the device is offline at the scheduled time, the schedule is skipped and you are notified. One-time schedules delete themselves after execution; the history is on the status page.

  9. Do I have to get to the Truma heater itself?

    In the vast majority of cases, no. With the RJ12 splitter the connection at the heater is dropped entirely — you only ever work at the back of the Truma CP Plus: unplug the existing RJ12 cable, put it into the splitter, run one output back to the CP Plus and the other to the CampMatic box. The cover above the heater stays shut. Only if you cannot reach the back of the CP Plus, or there is not enough room there, do you need the socket on the heater itself (see Installation, route 2).

  10. Do I need the Truma CP Plus?

    No. The CampMatic box only needs a 12V power supply — from any source in your camper. The JST-PH connection at the CP Plus is merely a convenient way to tap power; no data connection to the CP Plus is required.

  11. Home Assistant bridge does not connect

    Search the Mosquitto logs for bridge, tls and connect messages. Look first for the line Connecting bridge … in the startup log: if it is missing, the bridge file was never read (with the add-on, customize: active: true is usually missing, or the file sits in /homeassistant/share/mosquitto instead of /share/mosquitto). On "Connection refused": re-copy username and password from the status page. On "certificate verify failed": the container's CA store is missing — update the image or mount the host CA store.

    Not an error, even though it looks like one: Protocol error from ::1: First packet not CONNECT appears on every start of the Mosquitto add-on and is an internal port check. It is never the cause of a problem.

    The full steps are in the Home Assistant Integration card under "Verify the bridge".

Have questions or need help? We're happy to help.