Ursalink (Milesight) UC1122 Decoder - Help needed

I have the following ‘standard’ decoder that works in TTN but trying to get it to work in Tago’s payload parser does not convert the payload into the buckets for dout1 etc.

I know almost zero about javascript and JSON - the end goal is to create a connector for this device into my dashboard.

Can anybody offer any assistance? Thanks.

//UC1122 Decoder
function Decoder(bytes, port) {
var decoded = {};

for (i = 0; i < bytes.length;) {
    // GPIO INPUT 1
    if (bytes[i] == 0x01) {
        decoded.din1 = bytes[i + 2] === 0 ? "0" : "1";
        i += 3;
        continue;
    }

    // GPIO INPUT 2
    if (bytes[i] == 0x02) {
        decoded.din2 = bytes[i + 2] === 0 ? "0" : "1";
        i += 3;
        continue;
    }

    // GPIO OUTPUT 1
    if (bytes[i] == 0x09) {
        decoded.dout1 = bytes[i + 2] === 0 ? "0" : "1";
        i += 3;
        continue;
    }

    // GPIO OUTPUT 2
    if (bytes[i] == 0x0a) {
        decoded.dout2 = bytes[i + 2] === 0 ? "0" : "1";
        i += 3;
        continue;
    }

    // ADC OUTPUT 1
    if (bytes[i] == 0x11) {
        decoded.ain1_cur = readInt16LE(bytes.slice(i + 2, i + 4)) / 100;
        decoded.ain1_min = readInt16LE(bytes.slice(i + 4, i + 6)) / 100;
        decoded.ain1_max = readInt16LE(bytes.slice(i + 6, i + 8)) / 100;
        decoded.ain1_avg = readInt16LE(bytes.slice(i + 8, i + 10)) / 100;
        i += 10;
        continue;
    }

    // ADC OUTPUT 2
    if (bytes[i] == 0x12) {
        decoded.ain2_cur = readInt16LE(bytes.slice(i + 2, i + 4)) / 100;
        decoded.ain2_min = readInt16LE(bytes.slice(i + 4, i + 6)) / 100;
        decoded.ain2_max = readInt16LE(bytes.slice(i + 6, i + 8)) / 100;
        decoded.ain2_avg = readInt16LE(bytes.slice(i + 8, i + 10)) / 100;
        i += 10;
        continue;
    }

}

return decoded;

}

/* ******************************************

  • bytes to number
    ********************************************/
    function readUInt8LE(bytes) {
    return (bytes & 0xFF);
    }

function readInt8LE(bytes) {
var ref = readUInt8LE(bytes);
return (ref > 0x7F) ? ref - 0x100 : ref;
}

function readUInt16LE(bytes) {
var value = (bytes[1] << 8) + bytes[0];
return (value & 0xFFFF);
}

function readInt16LE(bytes) {
var ref = readUInt16LE(bytes);
return (ref > 0x7FFF) ? ref - 0x10000 : ref;
}

function readUInt32LE(bytes) {
var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0];
return (value & 0xFFFFFFFF);
}

function readInt32LE(bytes) {
var ref = readUInt32LE(bytes);
return (ref > 0x7FFFFFFF) ? ref - 0x100000000 : ref;
}

function readFloatLE(bytes) {
// JavaScript bitwise operators yield a 32 bits integer, not a float.
// Assume LSB (least significant byte first).
var bits = bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0];
var sign = (bits >>> 31 === 0) ? 1.0 : -1.0;
var e = bits >>> 23 & 0xff;
var m = (e === 0) ? (bits & 0x7fffff) << 1 : (bits & 0x7fffff) | 0x800000;
var f = sign * m * Math.pow(2, e - 150);
return f;
}

Hi @kdamji,

Tago uses a specific format for the data, all the data need to be a object with variable and value. e.g { variable: “din1”, value: “0” }, so you need an array of these objects as the result, also, in your parser you need to get the input, which comes in a payload variable and run the decoder on it.

To make sure you can know which data were parsed together, you also need a serie in each of the objects, you can use the current time to do that.

Here is the code:

/* ******************************************
  bytes to number
  *******************************************
*/
function readUInt8LE(bytes) {
  return bytes & 0xff;
}
function readInt8LE(bytes) {
  const ref = readUInt8LE(bytes);
  return ref > 0x7f ? ref - 0x100 : ref;
}

function readUInt16LE(bytes) {
  const value = (bytes[1] << 8) + bytes[0];
  return value & 0xffff;
}

function readInt16LE(bytes) {
  const ref = readUInt16LE(bytes);
  return ref > 0x7fff ? ref - 0x10000 : ref;
}

function readUInt32LE(bytes) {
  const value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0];
  return value & 0xffffffff;
}

function readInt32LE(bytes) {
  const ref = readUInt32LE(bytes);
  return ref > 0x7fffffff ? ref - 0x100000000 : ref;
}

function readFloatLE(bytes) {
  // JavaScript bitwise operators yield a 32 bits integer, not a float.
  // Assume LSB (least significant byte first).
  const bits = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
  const sign = bits >>> 31 === 0 ? 1.0 : -1.0;
  const e = (bits >>> 23) & 0xff;
  const m = e === 0 ? (bits & 0x7fffff) << 1 : (bits & 0x7fffff) | 0x800000;
  const f = sign * m * 2 ** (e - 150);
  return f;
}

// UC1122 Decoder
function decoder(bytes) {
  const decoded = [];

  for (let i = 0; i < bytes.length; ) {
    // GPIO INPUT 1
    if (bytes[i] === 0x01) {
      decoded.push({ variable: "din1", value: bytes[i + 2] === 0 ? "0" : "1" });
      i += 3;
      continue;
    }

    // GPIO INPUT 2
    else if (bytes[i] === 0x02) {
      decoded.push({ variable: "din2", value: bytes[i + 2] === 0 ? "0" : "1" });
      i += 3;
      continue;
    }

    // GPIO OUTPUT 1
    else if (bytes[i] === 0x09) {
      decoded.push({ variable: "dout1", value: bytes[i + 2] === 0 ? "0" : "1" });
      i += 3;
      continue;
    }

    // GPIO OUTPUT 2
    else if (bytes[i] === 0x0a) {
      decoded.push({ variable: "dout2", value: bytes[i + 2] === 0 ? "0" : "1" });
      i += 3;
      continue;
    }

    // ADC OUTPUT 1
    else if (bytes[i] === 0x11) {
      decoded.push({ variable: "ain1_cur", value: readInt16LE(bytes.slice(i + 2, i + 4)) / 100 });
      decoded.push({ variable: "ain1_min", value: readInt16LE(bytes.slice(i + 4, i + 6)) / 100 });
      decoded.push({ variable: "ain1_max", value: readInt16LE(bytes.slice(i + 6, i + 8)) / 100 });
      decoded.push({ variable: "ain1_avg", value: readInt16LE(bytes.slice(i + 8, i + 10)) / 100 });
      i += 10;
      continue;
    }

    // ADC OUTPUT 2
    else if (bytes[i] === 0x12) {
      decoded.push({ variable: "ain2_cur", value: readInt16LE(bytes.slice(i + 2, i + 4)) / 100 });
      decoded.push({ variable: "ain2_min", value: readInt16LE(bytes.slice(i + 4, i + 6)) / 100 });
      decoded.push({ variable: "ain2_max", value: readInt16LE(bytes.slice(i + 6, i + 8)) / 100 });
      decoded.push({ variable: "ain2_avg", value: readInt16LE(bytes.slice(i + 8, i + 10)) / 100 });
      i += 10;
      continue;
    } else {
      return [{ variable: "parser_error", value: "that was not supposed to happen" }];
    }
  }
  return decoded;
}

const data = payload.find((x) => x.variable === "payload" || x.variable === "payload_raw" || x.variable === "data");

if (data) {
  const buffer = Buffer.from(data.value, "hex");
  const serie = new Date().getTime();
  payload = payload.concat(decoder(buffer));
  payload = payload.map((x) => ({ ...x, serie }));
}

note that i added an extra else in the decoder, because if for any reason your parser doesn’t get into any of the other if statements, it would loop forever!

Hope it help you.

1 Like

@danielg

Thank you so much for your input here - this works perfectly. You sir, are awesome!