Kona Home Sensor configuration downlink help

I’m pretty new to LoRa… Sorry if this is a dumb question.

I have my chirpstack and gateway setup with no obvious issues.

I’ve paired a Kojna Home Sensor (PIR version “D”), OTAA.

I wanted to fetch the configuration as I am NOT getting temperature/humidity/etc hourly like I think I should be. But no matter what I put in as the downlink it returns either nothing, or registers I didn’t ask for.

Not sure where I’m going wrong here. For example, I wanted to get the temperature reporting setting. The manual says to write “0x 22” on fport 100. So I used cryptii.com to convert a bazillion things (hex bytes to base64 text, text to base64 text in a ton of formats) and send it, but none of them have worked thus far. I see the downlinks getting sent, and an ack… but no uplink with the config report.

  • Do I need anything special for an encoder? Right now it is just the empty frame/stub of a function.
  • Anyone that has a Kona Home Sensor tried this and/or could provide an example of a config read that worked for them?

Thanks in advance.

Jason

Example.

Doing a downlink of this payload (“0x 20”) on port 100:
MHggMjA=

Yields this port 100 response:

  • decode_data_hex:“0x20,0x00,0x00,0x0e,0x10,0x30,0x0b,0xb8,0x32,0x04”

But looking at the downlink message the payload was actually:
image

Not sure why the payload looks different/was changed versus the base64 encoded string I provided? (EDIT: Answered in next post.)

And in terms of the uplink response, I asked for register 20, sure, but it also gave me 0x30 and 0x32 for some reason.

Along the same lines, doing this to try and get register 21:
MHggMjE=

returns nothin but a txack… No uplink data. And that payload is also different thhan the base64 string I provided:

  • bytes:“CsxXGc4=”

Color me confused. :slight_smile:

Did you see this helptext on the live lorawan frames tab? Note the encrypted :slight_smile:

image

2 Likes

Yes, of course. That should have been obvious to me! I was so focused on the string already being ENCODED I didn’t think about that is was also ENCRYPTED when sent. Thank you.

Good. That mystery is solved.

Now I just have to figure out the right downlink payload format to get what I need out of the device.

In the manual it says the format is as shown below, but I haven’t quite gotten it right I guess as while I have gotten a few registers (as mentioned above), I’ve never been able to get this example to return anything other than the ack.
image

Or maybe attacking this at another angle… There is a TTN custom encoder for this device published by the manufacturer. However, the function format and return format look different than the Application server wants.

Any advice on how to use this encoder with Chirpstack?

It is a bit confusing for the novice as the skeleton function shows 3 parameters, but the on-screen text only says 2. And this manufacturer encoder returns both bytes and port. I have yet to find a working example of an Encoder for chirpstack anywhere in my searches either. :confused:

Skeleton function (3 parameter):

function Encode(fPort, obj, variables) {

On-Screen text (2 parameters):

Encoder from manufacturer:

function Encoder(data) {
    var ret = [];
    var port = 100;

    check_encode("device_eui",
        function (value) {
        },
        function () {
            ret = ret.concat(ret, [0x00])
        }
    );
    check_encode("app_eui",
        function (value) {
        },
        function () {
            ret = ret.concat(ret, [0x01])
        }
    );
    check_encode("app_key",
        function (value) {
        },
        function () {
            ret = ret.concat(ret, [0x02])
        }
    );
    check_encode("device_address",
        function (value) {
        },
        function () {
            ret = ret.concat(ret, [0x03])
        }
    );
    check_encode("network_session_key",
        function (value) {
        },
        function () {
            ret = ret.concat(ret, [0x04])
        }
    );
    check_encode("app_session_key",
        function (value) {
        },
        function () {
            ret = ret.concat(ret, [0x05])
        }
    );
    check_encode("lorawan_join_mode",
        function (value) {
            var converted = [0x10 | 0x80,
                ((value & 0x1) << 7), 0x00];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x10])
        }
    );
    check_encode("loramac_opts",
        function (value) {
            var converted = [0x11 | 0x80, 0x00,
                ((value.loramac_confirm_mode & 0x1) << 0) |
                ((value.loramac_sync_word & 0x1) << 1) |
                ((value.loramac_duty_cycle & 0x1) << 2) |
                ((value.loramac_adr & 0x1) << 3)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x11])
        }
    );
    check_encode("loramac_dr_tx",
        function (value) {
            var converted = [0x12 | 0x80,
                ((value.dr_number & 0xf) << 0),
                ((value.tx_power_number & 0xf) << 0)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x12])
        }
    );
    check_encode("loramac_rx2",
        function (value) {
            var converted = [0x13 | 0x80,
                (value.frequency >> 24) & 0xff, (value.frequency >> 16) & 0xff, (value.frequency >> 8) & 0xff, value.frequency & 0xff,
                value.dr_number & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x13])
        }
    );
    check_encode("seconds_per_core_tick",
        function (value) {
            var converted = [0x20 | 0x80,
                (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x20])
        }
    );
    check_encode("tick_per_battery",
        function (value) {
            var converted = [0x21 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x21])
        }
    );
    check_encode("tick_per_ambient_temperature",
        function (value) {
            var converted = [0x22 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x22])
        }
    );
    check_encode("tick_per_relative_humidity",
        function (value) {
            var converted = [0x23 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x23])
        }
    );
    check_encode("tick_per_reed_switch",
        function (value) {
            var converted = [0x24 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x24])
        }
    );
    check_encode("tick_per_light",
        function (value) {
            var converted = [0x25 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x25])
        }
    );
    check_encode("tick_per_accelerometer",
        function (value) {
            var converted = [0x26 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x26])
        }
    );
    check_encode("tick_per_mcu_temperature",
        function (value) {
            var converted = [0x27 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x27])
        }
    );
    check_encode("tick_per_pir",
        function (value) {
            var converted = [0x28 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x28])
        }
    );
    check_encode("tick_per_external_connector",
        function (value) {
            var converted = [0x29 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x29])
        }
    );
    check_encode("reed_mode",
        function (value) {
            var converted = [0x2A | 0x80,
                ((value.rising_edge_enabled & 0x1) << 0) |
                ((value.falling_edge_enabled & 0x1) << 1)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x2A])
        }
    );
    check_encode("reed_switch_count_threshold",
        function (value) {
            var converted = [0x2B | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x2B])
        }
    );
    check_encode("reed_tx",
        function (value) {
            var converted = [0x2C | 0x80,
                ((value.report_state_enabled & 0x1) << 0) |
                ((value.report_count_enabled & 0x1) << 1)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x2C])
        }
    );
    check_encode("external_connector",
        function (value) {
            var converted = [0x2D | 0x80,
                ((value.rising_edge_enabled & 0x1) << 0) |
                ((value.falling_edge_enabled & 0x1) << 1) |
                ((value.mode & 0x1) << 7)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x2D])
        }
    );
    check_encode("external_connector_count_threshold",
        function (value) {
            var converted = [0x2E | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x2E])
        }
    );
    check_encode("external_connector_tx",
        function (value) {
            var converted = [0x2F | 0x80,
                ((value.report_state_enabled & 0x1) << 0) |
                ((value.report_count_enabled & 0x1) << 1)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x2F])
        }
    );
    check_encode("impact_event_threshold",
        function (value) {
            var converted = [0x30 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x30])
        }
    );
    check_encode("acceleration_event_threshold",
        function (value) {
            var converted = [0x31 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x31])
        }
    );
    check_encode("accelerometer_tx",
        function (value) {
            var converted = [0x32 | 0x80,
                ((value.report_alarm_enabled & 0x1) << 0) |
                ((value.report_magnitude_enabled & 0x1) << 4) |
                ((value.report_vector_enabled & 0x1) << 5)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x32])
        }
    );
    check_encode("acceleration_impact_grace_period",
        function (value) {
            var converted = [0x33 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x33])
        }
    );
    check_encode("accelerometer",
        function (value) {
            var converted = [0x34 | 0x80,
                ((value.impact_threshold_enabled & 0x1) << 0) |
                ((value.acceleration_threshold_enabled & 0x1) << 1) |
                ((value.xaxis_enabled & 0x1) << 4) |
                ((value.yaxis_enabled & 0x1) << 5) |
                ((value.zaxis_enabled & 0x1) << 6) |
                ((value.poweron & 0x1) << 7)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x34])
        }
    );
    check_encode("sensitivity",
        function (value) {
            var converted = [0x35 | 0x80,
                ((value.accelerometer_sample_rate & 0x7) << 0) |
                ((value.accelerometer_measurement_range & 0x3) << 4)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x35])
        }
    );
    check_encode("impact_alarm_grace_period",
        function (value) {
            var converted = [0x36 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x36])
        }
    );
    check_encode("impact_alarm_threshold_count",
        function (value) {
            var converted = [0x37 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x37])
        }
    );
    check_encode("impact_alarm_threshold_period",
        function (value) {
            var converted = [0x38 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x38])
        }
    );
    check_encode("temperature_relative_humidity_sample_period_idle",
        function (value) {
            var converted = [0x39 | 0x80,
                (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x39])
        }
    );
    check_encode("temperature_relative_humidity_sample_period_active",
        function (value) {
            var converted = [0x3A | 0x80,
                (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x3A])
        }
    );
    check_encode("ambient_temperature_threshold",
        function (value) {
            var converted = [0x3B | 0x80,
                value.high & 0xff,
                value.low & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x3B])
        }
    );
    check_encode("ambient_temperature_threshold_enabled",
        function (value) {
            var converted = [0x3C | 0x80,
                value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x3C])
        }
    );
    check_encode("relative_humidity_threshold",
        function (value) {
            var converted = [0x3D | 0x80,
                value.high & 0xff,
                value.low & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x3D])
        }
    );
    check_encode("relative_humidity_threshold_enabled",
        function (value) {
            var converted = [0x3E | 0x80,
                value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x3E])
        }
    );
    check_encode("mcu_temperature_sample_period_idle",
        function (value) {
            var converted = [0x40 | 0x80,
                (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x40])
        }
    );
    check_encode("mcu_temperature_sample_period_active",
        function (value) {
            var converted = [0x41 | 0x80,
                (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x41])
        }
    );
    check_encode("mcu_temperature_threshold",
        function (value) {
            var converted = [0x42 | 0x80,
                value.high & 0xff,
                value.low & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x42])
        }
    );
    check_encode("mcu_temperature_threshold_enabled",
        function (value) {
            var converted = [0x43 | 0x80,
                value & 0x1];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x43])
        }
    );
    check_encode("analog_sample_period_idle",
        function (value) {
            var converted = [0x44 | 0x80,
                (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x44])
        }
    );
    check_encode("analog_sample_period_active",
        function (value) {
            var converted = [0x45 | 0x80,
                (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x45])
        }
    );
    check_encode("analog_input_threshold",
        function (value) {
            var converted = [0x46 | 0x80,
                (value.high >> 16) & 0xff,
                (value.high) & 0xff,
                (value.low >> 16) & 0xff,
                (value.low) & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x46])
        }
    );
    check_encode("light_sample_period",
        function (value) {
            var converted = [0x47 | 0x80,
                (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x47])
        }
    );
    check_encode("light",
        function (value) {
            var converted = [0x48 | 0x80,
                (value.threshold & 0x3f) |
                (value.threshold_enabled & 0x1) << 7];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x48])
        }
    );
    check_encode("light_tx",
        function (value) {
            var converted = [0x49 | 0x80,
                (value.state_reported & 0x1) |
                (value.intensity_reported & 0x1) << 1];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x49])
        }
    );
    check_encode("analog_input_threshold_enabled",
        function (value) {
            var converted = [0x4A | 0x80,
                value.analog_input_threshold_enabled & 0x1];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x4A])
        }
    );
    check_encode("pir_grace_period",
        function (value) {
            var converted = [0x50 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x50])
        }
    );
    check_encode("pir_threshold",
        function (value) {
            var converted = [0x51 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x51])
        }
    );
    check_encode("pir_threshold_period",
        function (value) {
            var converted = [0x52 | 0x80,
                (value >> 8) & 0xff, value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x52])
        }
    );
    check_encode("pir_mode",
        function (value) {
            var converted = [0x53 | 0x80,
                ((value.motion_count_reported & 0x1) << 0) |
                ((value.motion_state_reported & 0x1) << 1) |
                ((value.event_transmission_enabled & 0x1) << 6) |
                ((value.transducer_enabled & 0x1) << 7)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x53])
        }
    );
    check_encode("moisture_sample_period",
        function (value) {
            var converted = [0x5A | 0x80,
                value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x5A])
        }
    );
    check_encode("moisture_threshold",
        function (value) {
            var converted = [0x5B | 0x80,
                value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x5B])
        }
    );
    check_encode("moisture_sensing_enabled",
        function (value) {
            var converted = [0x5C | 0x80,
                value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x5C])
        }
    );
    check_encode("moisture_caliberation_dry",
        function (value) {
            var converted = [0x5D | 0x80,
                value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x5D])
        }
    );
    check_encode("write_to_flash",
        function (value) {
            var converted = [0x70 | 0x80,
                ((value.app_configuration & 0x1) << 5) |
                ((value.lora_configuration & 0x1) << 6),
                ((value.restart_sensor & 0x1) << 0)];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x70])
        }
    );
    check_encode("firmware_version",
        function (value) {
        },
        function () {
            ret = ret.concat(ret, [0x71])
        }
    );
    check_encode("configuration_factory_reset",
        function (value) {
            var converted = [0x72 | 0x80,
                value & 0xff];
            ret = ret.concat(converted);
        },
        function () {
            ret = ret.concat([0x72])
        }
    );
    check_encode("payload",
        function (value) {
            var converted = base64ToArray(value);
            ret = ret.concat(converted);
        },
        function () {
        }
    );
    function check_encode(prop_name, do_write, do_read) {
        if (data.hasOwnProperty(prop_name)) {
            var obj = data[prop_name];
            if (obj.hasOwnProperty("access")) {
                var access_value = obj.access;
                if (access_value == "write") {
                    do_write(obj.value);
                } else if (access_value == "read") {
                    do_read();
                }
            } else if (obj.hasOwnProperty("value")) {
                do_write(obj.value);
            }
        }
    }

    function atob(input) {
        var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
        var str = String(input).replace(/[=]+$/, ''); // #31: ExtendScript bad parse of /=
        if (str.length % 4 === 1) {
            throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
        }
        for (
            var bc = 0, bs, buffer, idx = 0, output = '';
            buffer = str.charAt(idx++);
            ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
        ) {
            buffer = chars.indexOf(buffer);
        }
        return output;
    }

    function base64ToArray(base64) {
        var binary_string = atob(base64);
        var len = binary_string.length;
        var result = [];
        for (var i = 0; i < len; i++) {
            result.push(binary_string.charCodeAt(i));
        }
        return result;
    }

    return {
        'bytes': ret,
        'port': port
    };

}

My initial thought:

  1. Change manufacturer encoder to only return the bytes, and not the port (as the text says it must return an array of bytes - doesn’t mention anything about returning the port (?).
return {
        'bytes': ret
    };
  1. Make wrapper to call the manufacturer Encoder function:
function Encode(fPort, obj) {
  return Encoder(obj); 
}

I got two more Kona sensors in the mail today.

These two seem to work as expected with the downlinks. So maybe there is something up with the other device. I’ll troubleshoot more when I have time.

So I guess disregard this thread… Doing base64 encoded hex values for the downlink works fine on the other devices.

1 Like