# How to decode Elvaco CMi4110 standard M-Bus payload?

**URL:** <https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747>\
**Category:** End Devices (Nodes)\
**Created:** [November 14, 2019, 10:25am UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747 "2019-11-14T10:25:17Z")\
**Posts on this page:** 10\
**Page:** 2

<div class="post-metadata">

**Author:** ![log\_win](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/log_win/32/38572_2.png) [@log\_win](https://www.thethingsnetwork.org/forum/u/log_win)\
**Post date:** [November 21, 2019, 6:27am UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747/21 "2019-11-21T06:27:01Z")

</div>

> [@arjanvanb](#):
>
> So, you’re right about how to get the actual values.

Yes i will post my endversion soon with explanation for other people!

> [@arjanvanb](#):
>
> I could not quickly find a pure JavaScript library for some generic M-Bus decoding. Reading a bit more on [the M-Bus/Meter-Bus protocol](http://www.m-bus.com/), it seems that generic decoding still needs a hardcoded decision tree for expected “VIF” values (whereas “DIF” values can be used to determine the length and type regardless the VIF, but it seems many possible DIF values are never used in this very device).
> 
> Even when limiting to this very device, it seems that small errors in the Elvaco documentation will require a lot of extra field testing. Like the length of `xxxxxx` does not always match the number of expected bytes: the excerpt of the documentation above claims Energy is always 6 bytes and BCD8, but nevertheless shows 7 bytes in, e.g., `0CFB00xxxxxxxx` . So, that should either read `0CFB00xxxxxx` which would then be BCD6, or Energy could be 8 bytes in some cases? Also `0C06xxxxxxxx` is listed twice in the same short excerpt, and Power is documented as “BCD8” which should _probably_ read “BCD6”. That’s already 3 errors while quickly browsing one page in the specification.
> 
> Finally, a generic decoder would need to include the unit in its output (like apparently Energy can be one of MWh, kWh, MJ or GJ) which then needs to be respected during further processing, or the decoder would need to convert it to a single unit.

I totally agree that it is not that great but we have to deal with. Maybe for future projects we should look for other sensors which can be more modified or do handel the payload in an other way. Nevertheless i really want to thank you for your patient and help!!! We got the thinks working! Get the payload via node red into an InfluxDB and visualisation with grafana 🙂 Thank your very much!!!

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [November 21, 2019, 1:28pm UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747/22 "2019-11-21T13:28:29Z")

</div>

For Node-RED you might be able to find some libraries to decode M-Bus for you, rather than using a TTN Payload Format to do the preprocessing. On the other hand, you obviously only need the decoding part, not any communication, so any full M-Bus library might be overkill. Also, just in case the device’s format has some errors, you might not be able to fix those. Still then, the JavaScript in NodeJS is a bit more advanced too, and it would keep all your code in one place.

As for the Energy with 0x0CFB00 and 0x0CFB01, I think that indeed adds another byte to the payload to keep 8 digits for the value, as otherwise its existence makes no sense: 6 digits with one or zero decimals would also fit in the other 0x0C06, 0C07, 0C0E and 0C0F formats. So, the “extended VIF” 0xFB seems to be needed for large values. Which also makes me wonder:

> [@log\_win](#):
>
> So i can compare the real value and the payload value plus the information of the manual 😉 and thats how i know to use the unit MWh oder kWh (in this case)

So, the unit is not set for some specific output using some configuration? I think it might not be safe to assume the unit and number of decimals never change. **If, over time, the values get to be too large to fit in the fixed number of digits, are you sure the device will not decrease its number of decimals, or select a larger unit, to prevent truncation of its readings?**

In case you didn’t do that yet, one can easily create a helper for the packed BCD:

```javascript
/**
 * Convert the array of bytes into an unsigned integer, assuming packed
 * binary-coded decimal (BCD) with an even number of nibbles, LSB.
 */
function bcdToUint(bytes) {
  return bytes.reduceRight(function(acc, byte) {
    return 100*acc + 10*(byte >> 4) + (byte & 0x0F);
  }, 0);
}

```

The above can be used with, e.g, `Volumen: bcdToUint(bytes.slice(9, 12)) / 100` (where index 12 is _not_ included in the slice of the array, so passes an array of length 3, not 4).

Next, such code is less prone to programming errors when used along with a running index: if some `var i` has value 9, then it can be incremented by 3 on the fly using [addition assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Addition_assignment), when used in `bcdToUint(bytes.slice(i, i += 3))` to indicate one wants to consume the next 3 bytes.

And though the documentation seems to suggest a specific order of the data, each part is also uniquely identified by its “VIF”, and its “DIF” specifies the BCD length. That allows for such something quite generic, also supporting the other formats, like:

```javascript
/**
 * Decoder for M-Bus style payload of the Elvaco CMi4110 sensor.
 * 
 * 2019-11-21: initial, PARTIAL, UNTESTED implementation
 * 
 * Notes:
 *
 * - In M-Bus the length of BCD values can be determined from the data,
 * but the value multiplier (the number of decimals) is part of the
 * specification (along with the unit), and not in the data itself.
 *
 * - The documentation seems to suggest the order of DIBs is fixed, but
 * this decoder does not rely on any order, using the VIF values.
 */

/**
 * Convert the array of bytes into an unsigned integer, assuming packed
 * binary-coded decimal (BCD) with an even number of nibbles, LSB.
 */
function bcdToUint(bytes) {
  return bytes.reduceRight(function(acc, byte) {
    return 100*acc + 10*(byte >> 4) + (byte & 0x0F);
  }, 0);
}

/**
 * Get a hexadecimal representation with leading zeroes for each byte.
 */
function hex(bytes, separator) {
  return bytes.map(function (b) {
    return ('0' + b.toString(16)).substr(-2);
  }).join(separator || '');
}

function Decoder(bytes, port) {
  var result = {};
  var i = 0;

  result.messageType = [
    'standard',
    'compact',
    // 0x02: JSON; not supported in this decoder 
    undefined,
    'scheduled daily redundant',
    'scheduled extended'
  ][bytes[i++]];

  if (!result.messageType) {
    return {
      error: 'unsupported message type',
      unparsed: hex(bytes)
    };
  }

  // Iterate the M-Bus Data Information Blocks, based on their Data
  // Information Field and Value Information Field
  while (i < bytes.length) {
    var dif = bytes[i++];
    var vif = bytes[i++];
  
    var difData = dif & 0x0F;
    var isAccumulated = (dif & 0x40) > 0;
  
    // For this device, actually only BCD4, BCD6 and BCD8 are expected
    var isBcd = (difData >= 0x09 && difData <= 0x0C) || difData === 0x0E;
    var bcdLen = isBcd ? difData & 0x07 : undefined;

    // VIF 0xFB is a special case for large values, handled below. For
    // other BCD values extract the unsigned integer and increase the
    // index here:
    var bcdValue = isBcd && vif !== 0xFB 
      ? bcdToUint(bytes.slice(i, i += bcdLen))
      : undefined;
  
    // We could switch on just VIF, but then unsupported DIF values
    // will go unnoticed
    var difVif = dif << 8 | vif;
    switch (difVif) {

      // Energy, or accumulated energy at 24:00
      case 0x0C06:
      case 0x0C07:
      case 0x4C06:
      case 0x4C07:
        result[isAccumulated ? 'accumulatedEnergy' : 'energy'] = {
          // 0x06 = 3 decimals, 07 = 2 decimals
          value: bcdValue / Math.pow(10, 0x09 - vif),
          unit: 'MWh'
        };
        break;

      case 0x0C0E:
      case 0x0C0F:
      case 0x4C0E:
      case 0x4C0F:
        result[isAccumulated ? 'accumulatedEnergy' : 'energy'] = {
          // 0x0E = 3 decimals, 0F = 2 decimals
          value: bcdValue / Math.pow(10, 0x11 - vif),
          unit: 'GJ'
        };
        break;

      case 0x0CFB:
      case 0x4CFB:
        // Extended VIF, providing an additional byte after VIF
        var vifExtension = bytes[i++];
        bcdValue = bcdToUint(bytes.slice(i, i += bcdLen));
        switch (vifExtension) {
          case 0x00:
          case 0x01:
            result[isAccumulated ? 'accumulatedEnergy' : 'energy'] = {
              // 0x00 = 1 decimal, 01 = no decimals
              value: bcdValue / Math.pow(10, 0x01 - vifExtension),
              unit: 'MWh'
            };
            break;

          case 0x08:
          case 0x09:
            result[isAccumulated ? 'accumulatedEnergy' : 'energy'] = {
              // 0x08 = 1 decimal, 09 = no decimals
              value: bcdValue / Math.pow(10, 0x09 - vifExtension),
              unit: 'GJ'
            };
            break;

          default:
            return {
              error: 'unexpected extended VIF',
              parsed: result,
              unparsed: hex(bytes.slice(i - 3))
            };
        }
        break;

      // Volume
      case 0x0C14:
      case 0x0C15:
      case 0x0C16:
        result.volume = {
          // 0x14 = 2 decimals, 15 = 1, 16 = no decimals
          value: bcdValue / Math.pow(10, 0x16 - vif),
          unit: 'm3'
        };
        break;

      // Power
      case 0x0B2B:
      case 0x0B2C:
      case 0x0B2D:
      case 0x0B2E:
        result.power = {
          // 0x2B = 3 decimals, 2C = 2, 2D = 1, 2E = no decimals
          value: bcdValue / Math.pow(10, 0x2E - vif),
          unit: 'kW'
        };
        break;

      // Flow
      case 0x0B3B:
      case 0x0B3C:
      case 0x0B3D:
      case 0x0B3E:
        result.flow = {
          // 0x3B = 3 decimals, 3C = 2, 3D = 1, 3E = no decimals
          value: bcdValue / Math.pow(10, 0x3E - vif),
          unit: 'm3/h'
        };
        break;

      // Forward temperature
      case 0x0A5A:
      case 0x0A5B:
        result.forwardTemperature = {
          // 0x5A = 1 decimal, 5B = no decimals
          value: bcdValue / Math.pow(10, 0x5B - vif),
          unit: 'C'
        };
        break;

      // Return temperature
      case 0x0A5E:
      case 0x0A5F:
        result.returnTemperature = {
          // 0x5E = 1 decimal, 5F = no decimals
          value: bcdValue / Math.pow(10, 0x5F - vif),
          unit: 'C'
        };
        break;

      // Meter ID (using BCD values)
      case 0x0C78:
        // This will remove leading zeroes in the JSON output
        result.meterId = bcdValue;
        break;

      // Meter date and time
      case 0x046D:
        // Not really implemented yet
        result.dateTime = hex(bytes.slice(i, i += 4));
        break;

      // Error and warning flags
      case 0x02FD:
        // Skip the 0x17 in 02FD17 (we should probably validate it)
        i++;
        result.errorFlags = hex(bytes.slice(i, i += 2));
        break;

      // Error message
      case 0x0E00:
        result.error = 'CMi4110 unable to communicate with UH50/UC50';
        break;

      default:
        return {
          error: 'unexpected format',
          parsed: result,
          unparsed: hex(bytes.slice(i - 2))
        };
    }
  }
  
  return result;
}

```

For the payload from your first post, this yields (sorted for readability):

```json
{
  "messageType": "standard",
  "energy": {
    "unit": "MWh",
    "value": 5.857
  },
  "volume": {
    "unit": "m3",
    "value": 239.22
  },
  "power": {
    "unit": "kW",
    "value": 15.7
  },
  "flow": {
    "unit": "m3/h",
    "value": 0.82
  },
  "forwardTemperature": {
    "unit": "C",
    "value": 60.6
  },
  "returnTemperature": {
    "unit": "C",
    "value": 44.1
  },
  "meterId": 70183899,
  "errorFlags": "0000"
}

```

Of course: untested… So, future readers: if you ever use this, compare it to the documentation, and please report back here with some real payload values? Thanks.

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [November 22, 2019, 4:55pm UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747/23 "2019-11-22T16:55:36Z")

</div>

> [@arjanvanb](#):
>
> are you sure the device will not decrease its number of decimals, or select a larger unit, to prevent truncation of its readings?

I got a reply from Elvaco:

> the module will use the same VIF code (unit) as is presented to it by the meter.

I don’t know if that means that the meter always uses the same VIF, or could change the VIF over time when totals get to be large values. But I guess whoever uses these meters knows.

---

<div class="post-metadata">

**Author:** ![log\_win](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/log_win/32/38572_2.png) [@log\_win](https://www.thethingsnetwork.org/forum/u/log_win)\
**Post date:** [November 25, 2019, 12:32pm UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747/24 "2019-11-25T12:32:08Z")

</div>

> [@arjanvanb](#):
>
> For the payload from your first post, this yields (sorted for readability):
> 
> ```auto
> {
> "messageType": "standard",
> "energy": {
> "unit": "MWh",
> "value": 5.857
> },
> "volume": {
> "unit": "m3",
> "value": 239.22
> },
> "power": {
> "unit": "kW",
> "value": 15.7
> },
> "flow": {
> "unit": "m3/h",
> "value": 0.82
> },
> "forwardTemperature": {
> "unit": "C",
> "value": 60.6
> },
> "returnTemperature": {
> "unit": "C",
> "value": 44.1
> },
> "meterId": 70183899,
> "errorFlags": "0000"
> }
> 
> ```
> 
> Of course: untested… So, future readers: if you ever use this, compare it to the documentation, and please report back here with some real payload values? Thanks.

It works. I do get

```
    {
  "energy": {
    "unit": "MWh",
    "value": 5.857
  },
  "errorFlags": "0000",
  "flow": {
    "unit": "m3/h",
    "value": 0.82
  },
  "forwardTemperature": {
    "unit": "C",
    "value": 60.6
  },
  "messageType": "standard",
  "meterId": 70183899,
  "power": {
    "unit": "kW",
    "value": 15.7
  },
  "returnTemperature": {
    "unit": "C",
    "value": 44.1
  },
  "volume": {
    "unit": "m3",
    "value": 239.22
  }
}

```

For my Payload

`00 0C 06 57 58 00 00 0C 14 22 39 02 00 0B 2D 57 01 00 0B 3B 20 08 00 0A 5A 06 06 0A 5E 41 04 0C 78 99 38 18 70 02 FD 17 00 00`

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [November 25, 2019, 12:34pm UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747/25 "2019-11-25T12:34:31Z")

</div>

Does the MWh for the energy match whatever is on the meter? (This is the `0x0C06` mentioned twice in the manual, with different units…)

```json
  "energy": {
    "unit": "MWh",
    "value": 5.857
  }

```

---

<div class="post-metadata">

**Author:** ![log\_win](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/log_win/32/38572_2.png) [@log\_win](https://www.thethingsnetwork.org/forum/u/log_win)\
**Post date:** [November 25, 2019, 12:39pm UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747/26 "2019-11-25T12:39:17Z")

</div>

Yes the meter does show MWh on its display and also in the payload

---

<div class="post-metadata">

**Author:** ![charuga](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/charuga/32/25844_2.png) [@charuga](https://www.thethingsnetwork.org/forum/u/charuga)\
**Post date:** [May 5, 2020, 8:28am UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747/27 "2020-05-05T08:28:58Z")

</div>

does anyone have an idea if it is possible to use crypto function in a payload decoder?  
I have a mbus message that is encoded with aes key … can aes key be decoded in ttn payload decoder?

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [May 5, 2020, 11:13am UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747/28 "2020-05-05T11:13:37Z")

</div>

> [@charuga](#):
>
> possible to use crypto function in a payload decoder?

There is no mechanism to `require` or `import` external libraries. But you can include any JavaScript code by using copy-paste.

However, you’ll need to administer the secrets, and you might not want to share those with TTN, and those might be different for each device in an application in TTN Console. So, it might be easier to do the decryption (and maybe all of the payload decoding) in your own application instead.

---

<div class="post-metadata">

**Author:** ![charuga](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/charuga/32/25844_2.png) [@charuga](https://www.thethingsnetwork.org/forum/u/charuga)\
**Post date:** [May 5, 2020, 12:07pm UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747/29 "2020-05-05T12:07:23Z")

</div>

thanks for your reply…  
the same is my aes key for all devices and security is not important to me in this application…

I would like it to be all in the payload decoder for my data to be written directly into influx integration…

it is somehow most elegant and safest solution for me

---

<div class="post-metadata">

**Author:** ![maikstolle](https://www.thethingsnetwork.org/forum/letter_avatar_proxy/v4/letter/m/3bc359/32.png) [@maikstolle](https://www.thethingsnetwork.org/forum/u/maikstolle)\
**Post date:** [October 18, 2023, 5:15am UTC](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747/30 "2023-10-18T05:15:19Z")

</div>

Hi folks,

i got a big problem with the decoder.

the meter sends following payload

```auto
00 0c0600000000 0c1475430300 0b2d330700 0b3b3019f0 0a5a5002 0a5e8205 0c78 28576468 02fd170000
S	MWh m³	power	flow	VL	RL ID	Meter	no Error

```

in flow the data is `0b3b3019f0 ` this result in a to big value. `0f 19 30`  
in normal operation the meter sens `0b3b804200` leading 2 zeros `00 42 80`  
it could be an error code for negativ flow.

How to handle this in the codec?

This also happens foe power readings.

regards Maik

[Previous page](https://www.thethingsnetwork.org/forum/t/how-to-decode-elvaco-cmi4110-standard-m-bus-payload/31747.md?page=1)
