# MClimate 16A Switch & Power Meter

## Codec

{% code title="mclimate-16a\_switch-codec.js" lineNumbers="true" expandable="true" %}

```js
function decodeUplink(input) {
  try {
    var bytes = input.bytes || [];
    var data = {};

    function u8(i) { return bytes[i] & 0xff; }
    function u16be(i) { return ((u8(i) << 8) | u8(i + 1)) >>> 0; }
    function u32be(i) { return (((u8(i) << 24) | (u8(i + 1) << 16) | (u8(i + 2) << 8) | u8(i + 3)) >>> 0); }

    function handleKeepalive(kaBytes, out) {
      // KA payload is 12 bytes: [type=1, temp, energy(4), power(2), volt(1), current(2), relay(1)]
      out.internalTemperature = kaBytes[1]; // as-is (uint8 in official doc)
      out.energy_Wh_raw = ((kaBytes[2] << 24) | (kaBytes[3] << 16) | (kaBytes[4] << 8) | kaBytes[5]) >>> 0; // Wh * 1000? see below
      out.energy_kWh = out.energy_Wh_raw / 1000; // per official example: raw/1000 => kWh

      out.power_W = ((kaBytes[6] << 8) | kaBytes[7]) >>> 0;
      out.acVoltage_V = kaBytes[8];
      out.acCurrent_mA = ((kaBytes[9] << 8) | kaBytes[10]) >>> 0;

      out.relayState = (kaBytes[11] === 0x01) ? 1 : 0;
      return out;
    }

    function handleResponse(respBytes, out) {
      // Convert to hex array strings (like official)
      var commands = respBytes.map(function (b) {
        return ("0" + (b & 0xff).toString(16)).slice(-2);
      });

      // The last 12 bytes are keepalive; response commands are before that
      // commands = commands.slice(0, -12);

      // Parse a stream of TLV-ish commands
      for (var i = 0; i < commands.length; i++) {
        var cmd = commands[i];
        var command_len = 0;

        switch (cmd) {
          case "04": // device versions: 04 HW SW
            command_len = 2;
            out.deviceVersions_hardware = parseInt(commands[i + 1], 16);
            out.deviceVersions_software = parseInt(commands[i + 2], 16);
            break;

          case "12": // getKeepAliveTime: 12 XX
            command_len = 1;
            out.keepAliveTime_min = parseInt(commands[i + 1], 16);
            break;

          case "19": // getJoinRetryPeriod: 19 XX (unit=5s steps => minutes = XX*5/60)
            command_len = 1;
            var cr = parseInt(commands[i + 1], 16);
            out.joinRetryPeriod_min = (cr * 5) / 60;
            break;

          case "1b": // getUplinkType: 1b XX
            command_len = 1;
            out.uplinkType = parseInt(commands[i + 1], 16);
            break;

          case "1d": // getWatchDogParams: 1d CC UC
            command_len = 2;
            var wdpC = commands[i + 1] === "00" ? 0 : parseInt(commands[i + 1], 16);
            var wdpUc = commands[i + 2] === "00" ? 0 : parseInt(commands[i + 2], 16);
            out.watchDogParams_confirmed = wdpC;
            out.watchDogParams_unconfirmed = wdpUc;
            break;

          case "1f": // getOverheatingThresholds: 1f TRIG REC
            command_len = 2;
            out.overheatingThreshold_trigger_C = parseInt(commands[i + 1], 16);
            out.overheatingThreshold_recovery_C = parseInt(commands[i + 2], 16);
            break;

          case "21": // getOvervoltageThresholds: 21 TRIG(2) REC(1?) (as in your code)
            command_len = 3;
            out.overvoltageThreshold_trigger_V = (parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16);
            out.overvoltageThreshold_recovery_V = parseInt(commands[i + 3], 16);
            break;

          case "23": // getOvercurrentThreshold: 23 XX
            command_len = 1;
            out.overcurrentThreshold_A = parseInt(commands[i + 1], 16);
            break;

          case "25": // getOverpowerThreshold: 25 XX XX
            command_len = 2;
            out.overpowerThreshold_W = (parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16);
            break;

          case "5a": // getAfterOverheatingProtectionRecovery
            command_len = 1;
            out.afterOverheatingProtectionRecovery = parseInt(commands[i + 1], 16);
            break;

          case "5c": // getLedIndicationMode
            command_len = 1;
            out.ledIndicationMode = parseInt(commands[i + 1], 16);
            break;

          case "5d": // manualChangeRelayState
            command_len = 1;
            out.manualChangeRelayState = (parseInt(commands[i + 1], 16) === 0x01) ? 1 : 0;
            break;

          case "5f": // getRelayRecoveryState
            command_len = 1;
            out.relayRecoveryState = parseInt(commands[i + 1], 16);
            break;

          case "60": // getOverheatingEvents: 60 EV TEMP
            command_len = 2;
            out.overheatingEvents_count = parseInt(commands[i + 1], 16);
            out.overheatingEvents_temperature_C = parseInt(commands[i + 2], 16);
            break;

          case "61": // getOvervoltageEvents: 61 EV VOLT(2)
            command_len = 3;
            out.overvoltageEvents_count = parseInt(commands[i + 1], 16);
            out.overvoltageEvents_voltage_V = (parseInt(commands[i + 2], 16) << 8) | parseInt(commands[i + 3], 16);
            break;

          case "62": // getOvercurrentEvents: 62 EV CURR(2)
            command_len = 3;
            out.overcurrentEvents_count = parseInt(commands[i + 1], 16);
            out.overcurrentEvents_current = (parseInt(commands[i + 2], 16) << 8) | parseInt(commands[i + 3], 16);
            break;

          case "63": // getOverpowerEvents: 63 EV POWER(2)
            command_len = 3;
            out.overpowerEvents_count = parseInt(commands[i + 1], 16);
            out.overpowerEvents_power_W = (parseInt(commands[i + 2], 16) << 8) | parseInt(commands[i + 3], 16);
            break;

          case "70": // getOverheatingRecoveryTime: 70 XX XX
            command_len = 2;
            out.overheatingRecoveryTime_s = (parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16);
            break;

          case "71": // getOvervoltageRecoveryTime: 71 XX XX
            command_len = 2;
            out.overvoltageRecoveryTime_s = (parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16);
            break;

          case "72": // getOvercurrentRecoveryTemp
            command_len = 1;
            out.overcurrentRecoveryTemp_C = parseInt(commands[i + 1], 16);
            break;

          case "73": // getOverpowerRecoveryTemp
            command_len = 1;
            out.overpowerRecoveryTemp_C = parseInt(commands[i + 1], 16);
            break;

          case "b1": // getRelayState response: b1 00/01
            command_len = 1;
            out.relayState = (parseInt(commands[i + 1], 16) === 0x01) ? 1 : 0;
            break;

          case "a0": // FUOTA address: a0 XX XX XX XX
            command_len = 4;
            var fuota_raw = commands[i + 1] + commands[i + 2] + commands[i + 3] + commands[i + 4];
            out.fuota_address_raw = fuota_raw;
            out.fuota_address = parseInt(fuota_raw, 16) >>> 0;
            break;

          default:
            // Unknown / not handled
            command_len = 0;
            break;
        }

        if (command_len > 0) {
          // skip over cmd + payload
          i += command_len;
        }
      }

      return out;
    }

     if (bytes.length < 1) return { data: {} };

    // Détection de la position du Keepalive (12 octets commençant par 0x01)
    var hasKeepaliveAtEnd = (bytes.length >= 12 && bytes[bytes.length - 12] === 0x01);
    var hasKeepaliveAtStart = (bytes.length >= 12 && bytes[0] === 0x01);

    if (hasKeepaliveAtEnd) {
      data = handleKeepalive(bytes.slice(-12), data);
      if (bytes.length > 12) {
        data = handleResponse(bytes.slice(0, -12), data);
      }
    } else if (hasKeepaliveAtStart) {
      data = handleKeepalive(bytes.slice(0, 12), data);
      if (bytes.length > 12) {
        data = handleResponse(bytes.slice(12), data);
      }
    } else if (bytes[0] !== 0x01) {
      data = handleResponse(bytes, data);
    } else {
      // Fallback if frame size less than 12 bytes
      data.raw = bytes;
    }

    return { data: data };
  } catch (e) {
    throw new Error("Unhandled data");
  }
}

function encodeDownlink(input) {
  var bytes = [];
  var key, i;

  for (key in input.data) {
    if (!input.data.hasOwnProperty(key)) continue;

    switch (key) {
      case "setKeepAlive":
        bytes.push(0x02);
        bytes.push(input.data.setKeepAlive);
        break;

      case "getKeepAliveTime":
        bytes.push(0x12);
        break;

      case "getDeviceVersions":
        bytes.push(0x04);
        break;

      case "setJoinRetryPeriod":
        // minutes -> steps of 5 seconds
        var periodToPass = Math.floor((input.data.setJoinRetryPeriod * 60) / 5);
        bytes.push(0x10);
        bytes.push(periodToPass);
        break;

      case "getJoinRetryPeriod":
        bytes.push(0x19);
        break;

      case "setUplinkType":
        bytes.push(0x11);
        bytes.push(input.data.setUplinkType);
        break;

      case "getUplinkType":
        bytes.push(0x1b);
        break;

      case "setWatchDogParams":
        bytes.push(0x1c);
        bytes.push(input.data.setWatchDogParams.confirmedUplinks);
        bytes.push(input.data.setWatchDogParams.unconfirmedUplinks);
        break;

      case "getWatchDogParams":
        bytes.push(0x1d);
        break;

      case "setOverheatingThresholds":
        bytes.push(0x1e);
        bytes.push(input.data.setOverheatingThresholds.trigger);
        bytes.push(input.data.setOverheatingThresholds.recovery);
        break;

      case "getOverheatingThresholds":
        bytes.push(0x1f);
        break;

      case "setOvervoltageThresholds":
        bytes.push(0x20);
        bytes.push(input.data.setOvervoltageThresholds.trigger);
        bytes.push(input.data.setOvervoltageThresholds.recovery);
        break;

      case "getOvervoltageThresholds":
        bytes.push(0x21);
        break;

      case "setOvercurrentThreshold":
        bytes.push(0x22);
        bytes.push(input.data.setOvercurrentThreshold);
        break;

      case "getOvercurrentThreshold":
        bytes.push(0x23);
        break;

      case "setOverpowerThreshold":
        bytes.push(0x24);
        bytes.push(input.data.setOverpowerThreshold);
        break;

      case "getOverpowerThreshold":
        bytes.push(0x25);
        break;

      case "setAfterOverheatingProtectionRecovery":
        bytes.push(0x59);
        bytes.push(input.data.setAfterOverheatingProtectionRecovery);
        break;

      case "getAfterOverheatingProtectionRecovery":
        bytes.push(0x5a);
        break;

      case "setLedIndicationMode":
        bytes.push(0x5b);
        bytes.push(input.data.setLedIndicationMode);
        break;

      case "getLedIndicationMode":
        bytes.push(0x5c);
        break;

      case "setRelayRecoveryState":
        bytes.push(0x5e);
        bytes.push(input.data.setRelayRecoveryState);
        break;

      case "getRelayRecoveryState":
        bytes.push(0x5f);
        break;

      case "setRelayState":
        bytes.push(0xc1);
        bytes.push(input.data.setRelayState);
        break;

      case "getRelayState":
        bytes.push(0xb1);
        break;

      case "getOverheatingEvents":
        bytes.push(0x60);
        break;

      case "getOvervoltageEvents":
        bytes.push(0x61);
        break;

      case "getOvercurrentEvents":
        bytes.push(0x62);
        break;

      case "getOverpowerEvents":
        bytes.push(0x63);
        break;

      case "getOverheatingRecoveryTime":
        bytes.push(0x70);
        break;

      case "getOvervoltageRecoveryTime":
        bytes.push(0x71);
        break;

      case "getOvercurrentRecoveryTemp":
        bytes.push(0x72);
        break;

      case "getOverpowerRecoveryTemp":
        bytes.push(0x73);
        break;

      case "sendCustomHexCommand":
        var hex = input.data.sendCustomHexCommand || "";
        for (i = 0; i < hex.length; i += 2) {
          bytes.push(parseInt(hex.substr(i, 2), 16));
        }
        break;

      default:
        break;
    }
  }

  return {
    bytes: bytes,
    fPort: 1,
    warnings: [],
    errors: []
  };
}

function decodeDownlink(input) {
  return {
    data: { bytes: input.bytes },
    warnings: [],
    errors: [],
  };
}
```

{% endcode %}

## Mapping

{% code title="mclimate-16a\_switch-mapping.json" lineNumbers="true" expandable="true" %}

```json
{
  "uplink": {
    "fields": [
      {
        "name": "internalTemperature",
        "description": "Internal temperature",
        "data_type": "NUMBER",
        "numeric_type": "UINT8",
        "access_mode": "R",
        "unit": "°C",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogInput", "bacnet_unit_type_id": 62 }
        }
      },
      {
        "name": "energy_Wh_raw",
        "description": "Accumulated energy raw (raw/1000 = kWh)",
        "data_type": "NUMBER",
        "numeric_type": "UINT32",
        "access_mode": "R",
        "unit": "raw",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogInput", "bacnet_unit_type_id": 95 }
        }
      },
      {
        "name": "energy_kWh",
        "description": "Accumulated energy (kWh)",
        "data_type": "NUMBER",
        "numeric_type": "FLOAT32",
        "access_mode": "R",
        "unit": "kWh",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogInput", "bacnet_unit_type_id": 95 }
        }
      },
      {
        "name": "power_W",
        "description": "Active power",
        "data_type": "NUMBER",
        "numeric_type": "UINT16",
        "access_mode": "R",
        "unit": "W",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogInput", "bacnet_unit_type_id": 47 }
        }
      },
      {
        "name": "acVoltage_V",
        "description": "AC voltage",
        "data_type": "NUMBER",
        "numeric_type": "UINT8",
        "access_mode": "R",
        "unit": "V",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogInput", "bacnet_unit_type_id": 5 }
        }
      },
      {
        "name": "acCurrent_mA",
        "description": "AC current",
        "data_type": "NUMBER",
        "numeric_type": "UINT16",
        "access_mode": "R",
        "unit": "mA",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogInput", "bacnet_unit_type_id": 3 }
        }
      },
      {
        "name": "relayState",
        "description": "Relay state (0=OFF, 1=ON)",
        "data_type": "NUMBER",
        "numeric_type": "UINT8",
        "access_mode": "R",
        "unit": "",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "binaryInput", "bacnet_unit_type_id": 95 }
        }
      },
      {
        "name": "deviceVersions_hardware",
        "description": "Hardware version",
        "data_type": "NUMBER",
        "numeric_type": "UINT8",
        "access_mode": "R",
        "unit": "",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogValue", "bacnet_unit_type_id": 95 }
        }
      },
      {
        "name": "deviceVersions_software",
        "description": "Software version",
        "data_type": "NUMBER",
        "numeric_type": "UINT8",
        "access_mode": "R",
        "unit": "",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogValue", "bacnet_unit_type_id": 95 }
        }
      },
      {
        "name": "keepAliveTime_min",
        "description": "Keep-alive time",
        "data_type": "NUMBER",
        "numeric_type": "UINT8",
        "access_mode": "R",
        "unit": "min",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogValue", "bacnet_unit_type_id": 72 }
        }
      },
      {
        "name": "joinRetryPeriod_min",
        "description": "Join retry period (minutes)",
        "data_type": "NUMBER",
        "numeric_type": "FLOAT32",
        "access_mode": "R",
        "unit": "min",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogValue", "bacnet_unit_type_id": 72 }
        }
      },
      {
        "name": "uplinkType",
        "description": "Uplink type",
        "data_type": "NUMBER",
        "numeric_type": "UINT8",
        "access_mode": "R",
        "unit": "",
        "protocol_specific": {
          "modbus": { "register_type": "input", "factor": 1 },
          "bacnet": { "object_type": "analogValue", "bacnet_unit_type_id": 95 }
        }
      }
    ]
  },

  "config": {
    "fields": [
      {
        "name": "setRelayState",
        "description": "Command: set relay state (0=OFF,1=ON)",
        "data_type": "NUMBER",
        "numeric_type": "UINT8",
        "access_mode": "R/W",
        "unit": "",
        "protocol_specific": {
          "modbus": { "register_type": "holding", "factor": 1 }
        }
      },
      {
        "name": "setKeepAlive",
        "description": "Command: set keep-alive (minutes, device-specific range)",
        "data_type": "NUMBER",
        "numeric_type": "UINT8",
        "access_mode": "R/W",
        "unit": "min",
        "protocol_specific": {
          "modbus": { "register_type": "holding", "factor": 1 }
        }
      },
      {
        "name": "setJoinRetryPeriod",
        "description": "Command: set join retry period (minutes)",
        "data_type": "NUMBER",
        "numeric_type": "UINT32",
        "access_mode": "R/W",
        "unit": "min",
        "protocol_specific": {
          "modbus": { "register_type": "holding", "factor": 1 }
        }
      },
      {
        "name": "setUplinkType",
        "description": "Command: set uplink type",
        "data_type": "NUMBER",
        "numeric_type": "UINT8",
        "access_mode": "R/W",
        "unit": "",
        "protocol_specific": {
          "modbus": { "register_type": "holding", "factor": 1 }
        }
      }
    ]
  },
  "lns_metadata":{
    "fields":[
      {
        "name":"rssi",
        "description":"Received RSSI",
        "data_type":"NUMBER",
        "numeric_type":"INT16",
        "access_mode":"R",
        "unit":"dBm",
        "protocol_specific":{
          "modbus":{
            "register_type":"input",
            "offset":1,
            "factor":1
          }
        }
      },
      {
        "name":"snr",
        "description":"Received SNR",
        "data_type":"NUMBER",
        "numeric_type":"INT16",
        "access_mode":"R",
        "unit":"dB",
        "protocol_specific":{
          "modbus":{
            "register_type":"input",
            "offset":2,
            "factor":1
          }
        }
      },
      {
        "name":"sf",
        "description":"Spreading Factor",
        "data_type":"NUMBER",
        "numeric_type":"UINT8",
        "access_mode":"R",
        "protocol_specific":{
          "modbus":{
            "register_type":"input",
            "offset":3,
            "factor":1
          }
        }
      },
      {
        "name":"minutesSinceLastRx",
        "description":"Minutes since last reception",
        "data_type":"NUMBER",
        "numeric_type":"UINT16",
        "access_mode":"R",
        "unit":"min",
        "protocol_specific":{
          "modbus":{
            "register_type":"input",
            "offset":4,
            "factor":1
          }
        }
      }
    ]
  }
}
```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://enless.gitbook.io/centre-aide/ressources/gateway-lorawan/decodeurs-capteurs-tiers/mclimate-16a-switch-and-power-meter.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
