Nemeus NIS-UL – Ultrasonic sensor

Don’t know why it says that on the Thingpark site, looks like it’s registered as Class A and Class C according to the Nemus website.

Certified LoRaWan 1.0.2 class A & C and SigFox networks

Hi Sir,
We added the application and registered the device in the ttn console with no luck in the process, it would be much appreciated if we could get more advice from you :slight_smile: .


@habibraed Only Nemeus themselves can configure the AppEUI and AppKey on the device. They are quite helpful, but you might have to send your device back.

Steps during ordering:

  1. Set up application, get the AppEUI
  2. Ask Nemeus for a configuration file which they will then generate with the DevEUI(s) from each device
  3. You register each device in the Application,
  4. Take the the generated AppKey and put it into the configuration file
  5. Send the file back to them which they will use to program the device keys before shipping to you

It’s awkward, and on one occasion a device was shipped without ever asking my AppKeys. Nemeus are expecting to send out large batches so don’t normally customise each device. I’m not sure what the default AppEUI is, or even how there could be one. Also take the opportunity to request the fields you need in the payload - battery voltage etc.

I do think there ought to be a way to configure them outside the factory to avoid the preconfiguration. Once running, though, they are reliable and good on battery despite never moving off SF12 on TTN.


1 Like

Hi everyone,

Has anyone created a decoder for the Nemeus NIS-UL ultrasonic distance sensor? We have it up and running, just need to decode the payload! (We’re new to this, and need a bit of help with understanding how to decode this).

This is their format - - I can’t get my head around it.

Any help is greatly appreciated!

Did you understand the formatting of the example packets that are given in the documentation you linked? As in if you got a packet of 8B02009700981f19 do you understand how you’d get the four readings of 151, 152, 31, and 25 at least from that? The decoder will have to account for the fact that the length of the packet could be variable if you’re not sending the same number and set of values each time.

The following can be used to decode the data. I don’t own this device, so it has only been tested with the provided examples, and some more examples I created myself:

function Decoder(bytes, port) {
  // See
  // Test with:
  //   8B 02 0097 0098 1f 19: 151, 152cm; 31, 25°C
  //   8B 02 0097 0098 fe 03: 151, 152cm; -2, 3°C
  //   AF 03 0097 0098 0090 0fb9 0e91 0fff fe 03 00 0a: 151, 152, 144cm; 4025, 3729, 4095mV, -2, 3, 0°C
  //   82 00a0: 160cm
  //   88 1e: 30°C
  var usonic_dist = [], voltage = [], internal_temp = [];
  var i = 0, m = 0;

  var mask = bytes[i++];

  // If bit 0 set: 1 byte indicating the number of measurements, else 1
  var nb_meas = mask & 1<<0 ? bytes[i++] : 1;

  // If bit 1 set: distances in centimeters, each 2 bytes unsigned MSB, 1 to 300cm
  if (mask & 1<<1) {
    for (m = 0; m < nb_meas; m++) {
      usonic_dist.push(bytes[i++]<<8 | bytes[i++]);

  // If bit 2 set: voltages in millivolts, each 2 bytes unsigned MSB
  if (mask & 1<<2) {
    for (m = 0; m < nb_meas; m++) {
      voltage.push((bytes[i++]<<8 | bytes[i++]) / 1000);

  // If bit 3 set: internal temperatures, each 1 byte signed integer [-128..+127]
  if (mask & 1<<3) {
    for (m = 0; m < nb_meas; m++) {
      // Sign-extend to 32 bits to support negative values, by shifting 24 bits
      // (too far) to the left, followed by a sign-propagating right shift:

  // Bit 4: reserved

  // If bit 5 set: cause for the measurement, else "periodic measurement"
  var cause = mask & 1<<5 ? bytes[i++] : 1;

  // We should have consumed all bytes, if not then just return the payload as hex
  if (i !== bytes.length) {
    return {
      'error': 'failed to parse payload',
      'payload': {
         return ('0' + b.toString(16).toUpperCase()).substr(-2);
       }).join(' ')

  // continued below! 

…which in the very same Decoder can then be formatted any way you like:

  // Format the results
  var result = {};
  // Show all 8 bits, ensuring leading zeroes
  result.mask = '0b' + ('00000000' + mask.toString(2)).substr(-8);

  result.numberOfMeasurements = nb_meas;
  result.distances = usonic_dist;
  result.voltages = voltage;
  result.temperatures = internal_temp;

  // Same data, grouped by measurement
  result.measurements = [];
  for (m = 0; m < nb_meas; m++) {
      distance: usonic_dist[m],
      voltage: voltage[m],
      temperature: internal_temp[m]

  result.cause = cause;
  result.causeMask = '0b' + ('00000000' + cause.toString(2)).substr(-8);
  result.causes = [];
  if (cause & 1<<0) {
  if (cause & 1<<1) {
    result.causes.push('usonic_dist > high threshold');
  if (cause & 1<<2) {
    result.causes.push('usonic_dist < high threshold - high hysteresis');
  if (cause & 1<<3) {
    result.causes.push('usonic_dist < low threshold');
  if (cause & 1<<4) {
    result.causes.push('usonic_dist > low threshold + low hysteresis');
  if (cause & 1<<5) {
    result.causes.push('forced manually');

  return result;

…which for AF 03 0097 0098 0090 0fb9 0e91 0fff fe 03 00 0a would return something like:

  "mask": "0b10101111",
  "numberOfMeasurements": 3,
  "distances": [
  "voltages": [
  "temperatures": [
  "measurements": [
      "distance": 151,
      "voltage": 4.025,
      "temperature": -2
      "distance": 152,
      "voltage": 3.729,
      "temperature": 3
      "distance": 144,
      "voltage": 4.095,
      "temperature": 0
  "cause": 10,
  "causeMask": "0b00001010",
  "causes": [
    "usonic_dist > high threshold",
    "usonic_dist < low threshold"

(Above, I changed the order for readability, to match the order of the bytes and the handling in the Decoder; you’ll get the keys sorted by name instead. And of course, much of the data in the result is redundant.)

If you know of a way to add this the Nemeus wiki, then please do! If you have true data, then please let us know as well, for proper testing.


Looks great, Arjan, thank you.

The image below is some of Flood Network’s sensors producing data and using your decoder. We already decode in our application, but it’s very useful for debugging on TTN. I included a device which was woken from standby too to check wakeup codes. (Note that the sensor with an erroneous voltage measurement is using the old pre-Nov 2017 packet format which has now changed.)


This would be immensely helpful on the wiki, but I believe the wiki is only editable by Nemeus staff. I will send this on to them.

Now if someone can fix the fact they’re stuck at SF12, but only on TTN then that’d be fantastic.


1 Like

I guess you know how to tell if ADR is enabled, and if TTN is trying to command the nodes to change their settings? (If ADR is enabled and the device is at SF12, then I think TTN will initiate a downlink when it cannot piggyback on a confirmation requested by the node, or a downlink scheduled by the application, and even if the node did not request an ADR downlink.)

Are they using the same LoRaWAN port number for their different formats?

Hi Arjan, this works a treat! Thank you so much for taking the time to look into this. I’ll also pass this on to Nemeus to see if they can update their wiki to be more user friendly.

I will test new downlink formats to get different measurements etc.


1 Like

I can’t see anything regarding this in their documentation anywhere.

Hi Arjan,

I can see that ADRACKReq is set in the uplink packets, but I can’t see what TTN does with them. It just seems to ignore them. An explicit downlink message doesn’t cause any change either. I’ve seen ADR work on other devices (and ADR works for this device on other networks), but the one theory I have is that TTN will not do ADR with any devices which start off at SF12. I have shown this with my own mdot-based devices.

Yes, port 8 seems to be used by all of them.

Jut to be sure: in LoRaWAN 1.0.x, that should only be set every 64 packets (and only if no downlink was received in the meantime). But for each uplink, the ADR bit should be set as well, so TTN knows it needs to keep track of the link quality.

So: is the ADR bit indeed set for every uplink?

Using an online LoRaWAN decoder you can easily check:

  • if ADR is set for every uplink (like if it has FCtrl = 0x80 = 0b10000000, hence has bit 7 for ADR set)
  • and if ADRACKReq is set for every 64th uplink (like if it has FCtrl = 0xC0 = 0b11000000, hence has bit 6 for ADRACKReq set)
  • and if any downlink is an ADR response (like for me, after an ADRACKReq uplink, TTN would generate a downlink right away, but and ADR downlink could also be postponed for another 32 uplinks if TTN feels like that).

If the uplinks look fine: any chance the uplinks are received by multiple gateways, and TTN tells another gateway to send the ADR downlink, which is then somehow lost?

Note that at some point TTN will give up to not waste more downlinks; such is then shown as “too many failed ADR requests” in the “trace” part of an expanded downlink in the gateway’s Traffic page in TTN Console:

:warning: Well, actually the above screenshot still resulted in a downlink, which I linked to in the 3rd bullet above… I did not investigate; see Error: too many failed ADR requests.

Maybe it even gives up if it has tried to send ADR commands before your node even has set ADRACKReq, which I think can happen when the node is using SF12 despite a good link quality?

Hello there,

I am unable to get the sensor to register to the TTN application, although i can see the frames in the gateway traffic, it was working fine, but it stopped suddenly, i tried deleting the app and creating it again with no luck, any advice ?


are you using new TTN generated keys , a new application (with a different name) ? or re using the same keys as before …

I am using a new application with a different name but i am using a specific DEV EUI, APP EUI and a APP KEY provided by Nemeus.

if you scroll back a bit I read it’s not the first time you encounter this.
how did you solve it a few months back ?

A DevAddr as assigned by TTN will always start with 0x26 or 0x27 , and for TTN the next bytes indicate other details such as the region; see , a manufacturer cannot assign those addresses.

You would have to make sure the AppEUI and AppKey are exactly as they were before since there’s no way to change them on the device. I expect the AppKey will be different each time it’s generated, but there seems to be an option to override it and paste in the old AppKey for that device.

Also make sure the sensor is properly reset to trigger a join. Setting the device to standby mode and back doesn’t trigger a join if it thinks it’s already joined. Using the magnet for 20 seconds will hardware reset it, triggering a join.

tnx… I don’t own a device like that but I’ve read that you allready advised him about this in the past.

Thanks sir, I have thought about resetting it by holding the magnet for 20 sec, but won’t this reset the configuration also?, and unfortunately, these sensors were shipped configured and i don’t think we know how to reconfigure them again.

At that time, i think it was by re creating the app. but i am not sure

1 Like