Sensor
/** * Payload Encoder * * Copyright 2025 Milesight IoT * * @product UC511 / UC512 */ var RAW_VALUE = 0x00; /* eslint no-redeclare: "off" */ /* eslint-disable */ // Chirpstack v4 function encodeDownlink(input) { var encoded = milesightDeviceEncode(input.data); return { bytes: encoded }; } // Chirpstack v3 function Encode(fPort, obj) { return milesightDeviceEncode(obj); } // The Things Network function Encoder(obj, port) { return milesightDeviceEncode(obj); } /* eslint-enable */ function milesightDeviceEncode(payload) { var encoded = []; if ("reboot" in payload) { encoded = encoded.concat(reboot(payload.reboot)); } if ("report_status" in payload) { encoded = encoded.concat(reportStatus(payload.report_status)); } if ("sync_time" in payload) { encoded = encoded.concat(syncTime(payload.sync_time)); } if ("collection_interval" in payload) { encoded = encoded.concat(setCollectionInterval(payload.collection_interval)); } if ("report_interval" in payload) { encoded = encoded.concat(setReportInterval(payload.report_interval)); } if ("retransmit_enable" in payload) { encoded = encoded.concat(setRetransmitEnable(payload.retransmit_enable)); } if ("retransmit_interval" in payload) { encoded = encoded.concat(setRetransmitInterval(payload.retransmit_interval)); } if ("resend_interval" in payload) { encoded = encoded.concat(setResendInterval(payload.resend_interval)); } if ("time_zone" in payload) { encoded = encoded.concat(setTimeZone(payload.time_zone)); } if ("sync_time_type" in payload) { encoded = encoded.concat(setSyncTimeType(payload.sync_time_type)); } if ("d2d_key" in payload) { encoded = encoded.concat(setD2DKey(payload.d2d_key)); } if ("d2d_enable" in payload) { encoded = encoded.concat(setD2DEnable(payload.d2d_enable)); } if ("response_enable" in payload) { encoded = encoded.concat(setResponseEnable(payload.response_enable)); } if ("class_a_response_time" in payload) { encoded = encoded.concat(setClassAResponseTime(payload.class_a_response_time)); } if ("valve_1_pulse" in payload) { encoded = encoded.concat(setValvePulse1(payload.valve_1_pulse)); } if ("valve_2_pulse" in payload) { encoded = encoded.concat(setValvePulse2(payload.valve_2_pulse)); } if ("valve_1_task" in payload) { encoded = encoded.concat(setValveTask(1, payload.valve_1_task)); } if ("valve_2_task" in payload) { encoded = encoded.concat(setValveTask(2, payload.valve_2_task)); } if ("batch_read_rules" in payload) { encoded = encoded.concat(batchReadRules(payload.batch_read_rules)); } if ("batch_enable_rules" in payload) { encoded = encoded.concat(batchEnableRules(payload.batch_enable_rules)); } if ("batch_remove_rules" in payload) { encoded = encoded.concat(batchRemoveRules(payload.batch_remove_rules)); } if ("query_rule_config" in payload) { encoded = encoded.concat(queryRuleConfig(payload.query_rule_config)); } var rule_x_enable_map = { rule_1_enable: 1, rule_2_enable: 2, rule_3_enable: 3, rule_4_enable: 4, rule_5_enable: 5, rule_6_enable: 6, rule_7_enable: 7, rule_8_enable: 8, rule_9_enable: 9, rule_10_enable: 10, rule_11_enable: 11, rule_12_enable: 12, rule_13_enable: 13, rule_14_enable: 14, rule_15_enable: 15, rule_16_enable: 16 }; for (var key in rule_x_enable_map) { if (key in payload) { encoded = encoded.concat(enableRule(rule_x_enable_map[key], payload[key])); } } var rule_x_remove_map = { rule_1_remove: 1, rule_2_remove: 2, rule_3_remove: 3, rule_4_remove: 4, rule_5_remove: 5, rule_6_remove: 6, rule_7_remove: 7, rule_8_remove: 8, rule_9_remove: 9, rule_10_remove: 10, rule_11_remove: 11, rule_12_remove: 12, rule_13_remove: 13, rule_14_remove: 14, rule_15_remove: 15, rule_16_remove: 16 }; for (var key in rule_x_remove_map) { if (key in payload) { encoded = encoded.concat(removeRule(rule_x_remove_map[key], payload[key])); } } // hardware_version>=v2.0, firmware_version>=v2.1 if ("rule_config" in payload) { for (var i = 0; i < payload.rule_config.length; i++) { encoded = encoded.concat(setRuleConfig(payload.rule_config[i])); } } // hardware_version>=v4.0, firmware_version>=v1.1 if ("rules_config" in payload) { for (var i = 0; i < payload.rules_config.length; i++) { encoded = encoded.concat(setNewRuleConfig(payload.rules_config[i])); } } if ("clear_valve_1_pulse" in payload) { encoded = encoded.concat(clearValvePulse(1, payload.clear_valve_1_pulse)); } if ("clear_valve_2_pulse" in payload) { encoded = encoded.concat(clearValvePulse(2, payload.clear_valve_2_pulse)); } if ("pulse_filter_config" in payload) { encoded = encoded.concat(setPulseFilterConfig(payload.pulse_filter_config)); } if ("gpio_jitter_time" in payload) { encoded = encoded.concat(setGpioJitterTime(payload.gpio_jitter_time)); } if ("valve_power_supply_config" in payload) { encoded = encoded.concat(setValvePowerSupplyConfig(payload.valve_power_supply_config)); } if ("pressure_calibration_settings" in payload) { encoded = encoded.concat(setPressureCalibration(payload.pressure_calibration_settings)); } if ("history_enable" in payload) { encoded = encoded.concat(setHistoryEnable(payload.history_enable)); } if ("clear_history" in payload) { encoded = encoded.concat(clearHistory(payload.clear_history)); } if ("fetch_history" in payload) { encoded = encoded.concat(fetchHistory(payload.fetch_history)); } if ("stop_transmit" in payload) { encoded = encoded.concat(stopTransmit(payload.stop_transmit)); } return encoded; } /** * Reboot * @since hardware_version>=v2.0, firmware_version>=v2.2 * @param {number} reboot values:(0: no, 1: yes) * @example { "reboot": 1 } */ function reboot(reboot) { var yes_no_map = { 0: "no", 1: "yes" }; var reboot_values = getValues(yes_no_map); if (reboot_values.indexOf(reboot) === -1) { throw new Error("reboot must be one of " + reboot_values.join(", ")); } if (getValue(yes_no_map, reboot) === 0) { return []; } return [0xff, 0x10, 0xff]; } /** * report status * @since hardware_version>=v3.0, firmware_version>=v3.1 * @param {number} report_status values:(0: no, 1: yes) * @example { "report_status": 1 } */ function reportStatus(report_status) { var yes_no_map = { 0: "no", 1: "yes" }; var yes_no_values = getValues(yes_no_map); if (yes_no_values.indexOf(report_status) === -1) { throw new Error("report_status must be one of " + yes_no_values.join(", ")); } if (getValue(yes_no_map, report_status) === 0) { return []; } return [0xff, 0x28, 0xff]; } /** * Sync time * @since hardware_version>=v2.0, firmware_version>=v2.1 * @param {number} sync_time values: (0: no, 1: yes) * @example { "sync_time": 1 } */ function syncTime(sync_time) { var yes_no_map = { 0: "no", 1: "yes" }; var yes_no_values = getValues(yes_no_map); if (yes_no_values.indexOf(sync_time) === -1) { throw new Error("sync_time must be one of " + yes_no_values.join(", ")); } if (getValue(yes_no_map, sync_time) === 0) { return []; } return [0xff, 0x4a, 0x00]; } /** * Set collection interval * @param {number} collection_interval unit: second, range: [10, 64800] * @example { "collection_interval": 10 } */ function setCollectionInterval(collection_interval) { if (collection_interval < 10 || collection_interval > 64800) { throw new Error("collection_interval must be in the range of 10 to 64800"); } var buffer = new Buffer(4); buffer.writeUInt8(0xff); buffer.writeUInt8(0x02); buffer.writeUInt16LE(collection_interval); return buffer.toBytes(); } /** * Set report interval * @param {number} report_interval unit: second, range: [10, 64800] * @example { "report_interval": 10 } */ function setReportInterval(report_interval) { if (report_interval < 10 || report_interval > 64800) { throw new Error("report_interval must be in the range of 10 to 64800"); } var buffer = new Buffer(4); buffer.writeUInt8(0xff); buffer.writeUInt8(0x03); buffer.writeUInt16LE(report_interval); return buffer.toBytes(); } /** * retransmit enable * @since hardware_version>=v3.0, firmware_version>=v3.1 * @param {number} retransmit_enable values: (0: disable, 1: enable) * @example { "retransmit_enable": 1 } */ function setRetransmitEnable(retransmit_enable) { var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); if (enable_values.indexOf(retransmit_enable) === -1) { throw new Error("retransmit_enable must be one of " + enable_values.join(", ")); } var buffer = new Buffer(3); buffer.writeUInt8(0xff); buffer.writeUInt8(0x69); buffer.writeUInt8(getValue(enable_map, retransmit_enable)); return buffer.toBytes(); } /** * retransmit interval * @since hardware_version>=v3.0, firmware_version>=v3.1 * @param {number} retransmit_interval unit: second, range: [30, 1200] * @example { "retransmit_interval": 60 } */ function setRetransmitInterval(retransmit_interval) { if (retransmit_interval < 30 || retransmit_interval > 1200) { throw new Error("retransmit_interval must be in the range of 30 to 1200"); } var buffer = new Buffer(5); buffer.writeUInt8(0xff); buffer.writeUInt8(0x6a); buffer.writeUInt8(0x00); buffer.writeUInt16LE(retransmit_interval); return buffer.toBytes(); } /** * resend interval * @since hardware_version>=v3.0, firmware_version>=v3.1 * @param {number} resend_interval unit: second, range: [30, 1200] * @example { "resend_interval": 60 } */ function setResendInterval(resend_interval) { if (resend_interval < 30 || resend_interval > 1200) { throw new Error("resend_interval must be in the range of 30 to 1200"); } var buffer = new Buffer(5); buffer.writeUInt8(0xff); buffer.writeUInt8(0x6a); buffer.writeUInt8(0x01); buffer.writeUInt16LE(resend_interval); return buffer.toBytes(); } /** * set time zone * @param {number} time_zone unit: minute, UTC+8 -> 8 * 10 = 80 * @example { "time_zone": 80 } */ function setTimeZone(time_zone) { var timezone_map = { "-120": "UTC-12", "-110": "UTC-11", "-100": "UTC-10", "-95": "UTC-9:30", "-90": "UTC-9", "-80": "UTC-8", "-70": "UTC-7", "-60": "UTC-6", "-50": "UTC-5", "-40": "UTC-4", "-35": "UTC-3:30", "-30": "UTC-3", "-20": "UTC-2", "-10": "UTC-1", 0: "UTC", 10: "UTC+1", 20: "UTC+2", 30: "UTC+3", 35: "UTC+3:30", 40: "UTC+4", 45: "UTC+4:30", 50: "UTC+5", 55: "UTC+5:30", 57: "UTC+5:45", 60: "UTC+6", 65: "UTC+6:30", 70: "UTC+7", 80: "UTC+8", 90: "UTC+9", 95: "UTC+9:30", 100: "UTC+10", 105: "UTC+10:30", 110: "UTC+11", 120: "UTC+12", 127: "UTC+12:45", 130: "UTC+13", 140: "UTC+14" }; var timezone_values = getValues(timezone_map); if (timezone_values.indexOf(time_zone) === -1) { throw new Error("time_zone must be one of " + timezone_values.join(", ")); } var buffer = new Buffer(4); buffer.writeUInt8(0xff); buffer.writeUInt8(0x17); buffer.writeInt16LE(getValue(timezone_map, time_zone)); return buffer.toBytes(); } /** * Set sync time type * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {number} sync_time_type values: (1: v1.0.2, 2: v1.0.3, 3: v1.1.0) * @example { "sync_time_type": 2 } */ function setSyncTimeType(sync_time_type) { var sync_time_type_map = { 1: "v1.0.2", 2: "v1.0.3", 3: "v1.1.0" }; var sync_time_type_values = getValues(sync_time_type_map); if (sync_time_type_values.indexOf(sync_time_type) === -1) { throw new Error("sync_time_type must be one of " + sync_time_type_values.join(", ")); } var buffer = new Buffer(3); buffer.writeUInt8(0xff); buffer.writeUInt8(0x3b); buffer.writeUInt8(getValue(sync_time_type_map, sync_time_type)); return buffer.toBytes(); } /** * set d2d key * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {string} d2d_key * @example { "d2d_key": "0000000000000000" } */ function setD2DKey(d2d_key) { if (typeof d2d_key !== "string") { throw new Error("d2d_key must be a string"); } if (d2d_key.length !== 16) { throw new Error("d2d_key must be 16 characters"); } if (!/^[0-9a-fA-F]+$/.test(d2d_key)) { throw new Error("d2d_key must be hex string [0-9a-fA-F]"); } var data = hexStringToBytes(d2d_key); var buffer = new Buffer(10); buffer.writeUInt8(0xff); buffer.writeUInt8(0x35); buffer.writeBytes(data); return buffer.toBytes(); } /** * set d2d enable * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {number} d2d_enable values: (0: "disable", 1: "enable") * @example { "d2d_enable": 1 } */ function setD2DEnable(d2d_enable) { var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); if (enable_values.indexOf(d2d_enable) === -1) { throw new Error("d2d_enable must be one of " + enable_values.join(", ")); } var buffer = new Buffer(3); buffer.writeUInt8(0xff); buffer.writeUInt8(0x84); buffer.writeUInt8(getValue(enable_map, d2d_enable)); return buffer.toBytes(); } /** * set response enable * @since hardware_version>=v3.0, firmware_version>=v3.3 * @param {number} response_enable values: (0: disable, 1: enable) * @example { "response_enable": 1 } */ function setResponseEnable(response_enable) { var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); if (enable_values.indexOf(response_enable) === -1) { throw new Error("response_enable must be one of " + enable_values.join(", ")); } var buffer = new Buffer(3); buffer.writeUInt8(0xff); buffer.writeUInt8(0xf3); buffer.writeUInt8(getValue(enable_map, response_enable)); return buffer.toBytes(); } /** * set class a response time * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {number} class_a_response_time unit: s, range: [0, 64800] * @example { "class_a_response_time": 10 } */ function setClassAResponseTime(class_a_response_time) { if (class_a_response_time < 0 || class_a_response_time > 64800) { throw new Error("class_a_response_time must be in the range of 0 to 64800"); } var buffer = new Buffer(6); buffer.writeUInt8(0xff); buffer.writeUInt8(0x1e); buffer.writeUInt32LE(class_a_response_time); return buffer.toBytes(); } /** * set valve pulse 1 * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {number} valve_1_pulse * @example { "valve_1_pulse": 100 } */ function setValvePulse1(valve_1_pulse) { if (typeof valve_1_pulse !== "number") { throw new Error("valve_1_pulse must be a number"); } var buffer = new Buffer(7); buffer.writeUInt8(0xff); buffer.writeUInt8(0x92); buffer.writeUInt8(0x01); buffer.writeUInt32LE(valve_1_pulse); return buffer.toBytes(); } /** * set valve pulse 2 * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {number} valve_2_pulse unit: pulse * @example { "valve_2_pulse": 100 } */ function setValvePulse2(valve_2_pulse) { if (typeof valve_2_pulse !== "number") { throw new Error("valve_2_pulse must be a number"); } var buffer = new Buffer(7); buffer.writeUInt8(0xff); buffer.writeUInt8(0x92); buffer.writeUInt8(0x02); buffer.writeUInt32LE(valve_2_pulse); return buffer.toBytes(); } /** * set valve task * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {number} index * @param {object} valve_task * @param {number} valve_task.time_rule_enable values: (0: disable, 1: enable) * @param {number} valve_task.pulse_rule_enable values: (0: disable, 1: enable) * @param {number} valve_task.sequence_id values: (0: force execute, 1-255: sequence execute) * @param {number} valve_task.valve_status values: (0: close, 1: open) * @param {number} valve_task.duration * @param {number} valve_task.valve_pulse * @example { "valve_1_task": { "time_rule_enable": 1, "pulse_rule_enable": 1, "sequence_id": 0, "valve_status": 0, "duration": 100, "pulse": 100 } } */ function setValveTask(index, valve_task) { var time_rule_enable = valve_task.time_rule_enable; var pulse_rule_enable = valve_task.pulse_rule_enable; var sequence_id = valve_task.sequence_id; var valve_status = valve_task.valve_status; var duration = valve_task.duration; var valve_pulse = valve_task.valve_pulse; var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); var status_map = { 0: "close", 1: "open" }; var status_values = getValues(status_map); if (sequence_id === undefined) { sequence_id = 0x00; } if (enable_values.indexOf(time_rule_enable) === -1) { throw new Error("valve_" + index + "_task.time_rule_enable must be one of " + enable_values.join(", ")); } if (enable_values.indexOf(pulse_rule_enable) === -1) { throw new Error("valve_" + index + "_task.pulse_rule_enable must be one of " + enable_values.join(", ")); } if (status_values.indexOf(valve_status) === -1) { throw new Error("valve_" + index + "_task.valve_status must be one of " + status_values.join(", ")); } var time_rule_enable_value = getValue(enable_map, time_rule_enable); var pulse_rule_enable_value = getValue(enable_map, pulse_rule_enable); var valve_status_value = getValue(status_map, valve_status); var data = 0x00; data |= time_rule_enable_value << 7; data |= pulse_rule_enable_value << 6; data |= valve_status_value << 5; data |= (index - 1) << 0; var length = 4; if (time_rule_enable_value === 1) { length += 3; } if (pulse_rule_enable_value === 1) { length += 4; } var buffer = new Buffer(length); buffer.writeUInt8(0xff); buffer.writeUInt8(0x1d); buffer.writeUInt8(data); buffer.writeUInt8(sequence_id); if (time_rule_enable_value === 1) { buffer.writeUInt24LE(duration); } if (pulse_rule_enable_value === 1) { buffer.writeUInt32LE(valve_pulse); } return buffer.toBytes(); } /** * read rules * @since hardware_version>=v2.0, firmware_version>=v2.1 * @deprecated hardware_version>=v4.0, firmware_version>=v1.1 * @param {object} batch_read_rules * @param {number} batch_read_rules.rule_1 * @param {number} batch_read_rules.rule_2 * @param {number} batch_read_rules.rule_x * @param {number} batch_read_rules.rule_16 * @example { "batch_read_rules": { "rules_id": 1 } } */ function batchReadRules(batch_read_rules) { var enable_map = { 0: "no", 1: "yes" }; var enable_values = getValues(enable_map); var data = 0; var rule_bit_offset = { rule_1: 0, rule_2: 1, rule_3: 2, rule_4: 3, rule_5: 4, rule_6: 5, rule_7: 6, rule_8: 7, rule_9: 8, rule_10: 9, rule_11: 10, rule_12: 11, rule_13: 12, rule_14: 13, rule_15: 14, rule_16: 15 }; for (var key in rule_bit_offset) { if (key in batch_read_rules) { if (enable_values.indexOf(batch_read_rules[key]) === -1) { throw new Error("batch_read_rules." + key + " must be one of " + enable_values.join(", ")); } data |= getValue(enable_map, batch_read_rules[key]) << rule_bit_offset[key]; } } var buffer = new Buffer(5); buffer.writeUInt8(0xff); buffer.writeUInt8(0x4b); buffer.writeUInt8(0x00); // read rules buffer.writeUInt16LE(data); return buffer.toBytes(); } /** * batch enable rules * @since hardware_version>=v2.0, firmware_version>=v2.1 * @deprecated hardware_version>=v4.0, firmware_version>=v1.1 * @param {object} batch_enable_rules * @param {number} batch_enable_rules.rule_1 * @param {number} batch_enable_rules.rule_2 * @param {number} batch_enable_rules.rule_x * @param {number} batch_enable_rules.rule_16 * @example { "batch_enable_rules": { "rule_1": 1, "rule_2": 1, "rule_3": 1, "rule_4": 1 } } */ function batchEnableRules(batch_enable_rules) { var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); var data = 0; var rule_bit_offset = { rule_1: 0, rule_2: 1, rule_3: 2, rule_4: 3, rule_5: 4, rule_6: 5, rule_7: 6, rule_8: 7, rule_9: 8, rule_10: 9, rule_11: 10, rule_12: 11, rule_13: 12, rule_14: 13, rule_15: 14, rule_16: 15 }; for (var key in rule_bit_offset) { if (key in batch_enable_rules) { if (enable_values.indexOf(batch_enable_rules[key]) === -1) { throw new Error("batch_enable_rules." + key + " must be one of " + enable_values.join(", ")); } data |= getValue(enable_map, batch_enable_rules[key]) << rule_bit_offset[key]; } } var buffer = new Buffer(5); buffer.writeUInt8(0xff); buffer.writeUInt8(0x4b); buffer.writeUInt8(0x01); // enable rules buffer.writeUInt16LE(data); return buffer.toBytes(); } /** * batch remove rules * @since hardware_version>=v2.0, firmware_version>=v2.1 * @deprecated hardware_version>=v4.0, firmware_version>=v1.1 * @param {object} batch_remove_rules * @param {number} batch_remove_rules.rule_1 * @param {number} batch_remove_rules.rule_2 * @param {number} batch_remove_rules.rule_x * @param {number} batch_remove_rules.rule_16 * @example { "batch_remove_rules": { "rule_1": 1, "rule_2": 1, "rule_3": 1, "rule_4": 1 } } */ function batchRemoveRules(batch_remove_rules) { var yes_no_map = { 0: "no", 1: "yes" }; var yes_no_values = getValues(yes_no_map); var data = 0; var rule_bit_offset = { rule_1: 0, rule_2: 1, rule_3: 2, rule_4: 3, rule_5: 4, rule_6: 5, rule_7: 6, rule_8: 7, rule_9: 8, rule_10: 9, rule_11: 10, rule_12: 11, rule_13: 12, rule_14: 13, rule_15: 14, rule_16: 15 }; for (var key in rule_bit_offset) { if (key in batch_remove_rules) { if (yes_no_values.indexOf(batch_remove_rules[key]) === -1) { throw new Error("batch_remove_rules." + key + " must be one of " + yes_no_values.join(", ")); } data |= getValue(yes_no_map, batch_remove_rules[key]) << rule_bit_offset[key]; } } var buffer = new Buffer(5); buffer.writeUInt8(0xff); buffer.writeUInt8(0x4b); buffer.writeUInt8(0x02); // remove rules buffer.writeUInt16LE(data); return buffer.toBytes(); } /** * enable rule * @since hardware_version>=v2.0, firmware_version>=v2.1 * @deprecated hardware_version>=v4.0, firmware_version>=v1.1 * @param {number} rule_index range: [1, 16] * @param {number} enable values: (0: disable, 1: enable) * @example { "rule_1_enable": 1 } * @example { "rule_2_enable": 1 } */ function enableRule(rule_index, enable) { var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); if (enable_values.indexOf(enable) === -1) { throw new Error("rule_" + rule_index + "_enable must be one of " + enable_values.join(", ")); } var buffer = new Buffer(5); buffer.writeUInt8(0xff); buffer.writeUInt8(0x4b); buffer.writeUInt8(0x03); // enable single rule buffer.writeUInt8(rule_index); buffer.writeUInt8(getValue(enable_map, enable)); return buffer.toBytes(); } /** * remove rule * @since hardware_version>=v2.0, firmware_version>=v2.1 * @deprecated hardware_version>=v4.0, firmware_version>=v1.1 * @param {number} rule_index range: [1, 16] * @example { "remove_rule": 1 } */ function removeRule(rule_index) { var buffer = new Buffer(5); buffer.writeUInt8(0xff); buffer.writeUInt8(0x4b); buffer.writeUInt8(0x04); // remove single rule buffer.writeUInt8(rule_index); buffer.writeUInt8(0x00); return buffer.toBytes(); } /** * query rule config * @since hardware_version>=v2.0, firmware_version>=v2.1 * @deprecated hardware_version>=v4.0, firmware_version>=v1.1 * @param {object} query_rule_config * @param {number} query_rule_config.rule_1 values: (0: no, 1: yes) * @param {number} query_rule_config.rule_2 values: (0: no, 1: yes) * @param {number} query_rule_config.rule_x values: (0: no, 1: yes) * @param {number} query_rule_config.rule_16 values: (0: no, 1: yes) */ function queryRuleConfig(query_rule_config) { var yes_no_map = { 0: "no", 1: "yes" }; var yes_no_values = getValues(yes_no_map); var data = []; var rule_offset = { rule_1: 1, rule_2: 2, rule_3: 3, rule_4: 4, rule_5: 5, rule_6: 6, rule_7: 7, rule_8: 8, rule_9: 9, rule_10: 10, rule_11: 11, rule_12: 12, rule_13: 13, rule_14: 14, rule_15: 15, rule_16: 16 }; for (var key in rule_offset) { if (key in query_rule_config) { if (yes_no_values.indexOf(query_rule_config[key]) === -1) { throw new Error("query_rule_config." + key + " must be one of " + yes_no_values.join(", ")); } if (getValue(yes_no_map, query_rule_config[key]) === 0) { continue; } data = data.concat([0xff, 0x4c, rule_offset[key]]); } } return data; } /** * set rule config * @since hardware_version>=v2.0, firmware_version>=v2.1 * @deprecated hardware_version>=v4.0, firmware_version>=v1.1 * @param {object} rule_config * @param {number} rule_config.id * @param {number} rule_config.enable values: (0: disable, 1: enable) * @param {number} rule_config.valve_status values: (0: open, 1: close) * @param {number} rule_config.valve_1_enable values: (0: disable, 1: enable) * @param {number} rule_config.valve_2_enable values: (0: disable, 1: enable) * @param {object} rule_config.week_cycle * @param {number} rule_config.week_cycle.monday values: (0: disable, 1: enable) * @param {number} rule_config.week_cycle.tuesday values: (0: disable, 1: enable) * @param {number} rule_config.week_cycle.wednesday values: (0: disable, 1: enable) * @param {number} rule_config.week_cycle.thursday values: (0: disable, 1: enable) * @param {number} rule_config.week_cycle.friday values: (0: disable, 1: enable) * @param {number} rule_config.week_cycle.saturday values: (0: disable, 1: enable) * @param {number} rule_config.week_cycle.sunday values: (0: disable, 1: enable) * @param {number} rule_config.start_hour range: [0, 24] * @param {number} rule_config.start_min range: [0, 59] * @param {number} rule_config.end_hour range: [0, 24] * @param {number} rule_config.end_min range: [0, 59] * @param {number} rule_config.valve_pulse range: [0, 65535] * @example { "rule_config": { "id": 1, "enable": 1, "valve_status": 0, "valve_1_enable": 1, "valve_2_enable": 1, "week_cycle": { "monday": 1, "tuesday": 1, "wednesday": 1, "thursday": 1, "friday": 1, "saturday": 1, "sunday": 1 }, "start_hour": 10, "start_min": 0, "end_hour": 18, "end_min": 0, "valve_pulse": 100 } } */ function setRuleConfig(rule_config) { var id = rule_config.id; var enable = rule_config.enable; var valve_status = rule_config.valve_status; var valve_1_enable = rule_config.valve_1_enable; var valve_2_enable = rule_config.valve_2_enable; var week_cycle = rule_config.week_cycle; var start_hour = rule_config.start_hour; var start_min = rule_config.start_min; var end_hour = rule_config.end_hour; var end_min = rule_config.end_min; var valve_pulse = rule_config.valve_pulse; var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); var status_map = { 0: "open", 1: "close" }; var status_values = getValues(status_map); if (id < 1 || id > 16) { throw new Error("rules_config._item.id must be in the range of 1 to 16"); } if (enable_values.indexOf(enable) === -1) { throw new Error("rules_config._item.enable must be one of " + enable_values.join(", ")); } if (status_values.indexOf(valve_status) === -1) { throw new Error("rules_config._item.valve_status must be one of " + status_values.join(", ")); } if (enable_values.indexOf(valve_1_enable) === -1) { throw new Error("rules_config._item.valve_1_enable must be one of " + enable_values.join(", ")); } if (enable_values.indexOf(valve_2_enable) === -1) { throw new Error("rules_config._item.valve_2_enable must be one of " + enable_values.join(", ")); } if (start_hour < 0 || start_hour > 24) { throw new Error("rules_config._item.start_hour must be in the range of 0 to 24"); } if (start_min < 0 || start_min > 59) { throw new Error("rules_config._item.start_min must be in the range of 0 to 59"); } if (end_hour < 0 || end_hour > 24) { throw new Error("rules_config._item.end_hour must be in the range of 0 to 24"); } if (end_min < 0 || end_min > 59) { throw new Error("rules_config._item.end_min must be in the range of 0 to 59"); } var data = 0x00; data |= getValue(enable_map, enable) << 7; data |= getValue(status_map, valve_status) << 6; data |= getValue(enable_map, valve_2_enable) << 1; data |= getValue(enable_map, valve_1_enable) << 0; var week_cycle_value = 0x00; var week_offset = { monday: 0, tuesday: 1, wednesday: 2, thursday: 3, friday: 4, saturday: 5, sunday: 6 }; for (var key in week_offset) { if (key in week_cycle) { week_cycle_value |= getValue(enable_map, week_cycle[key]) << week_offset[key]; } } var buffer = new Buffer(11); buffer.writeUInt8(0xff); buffer.writeUInt8(0x4d); buffer.writeUInt8(id); buffer.writeUInt8(data); buffer.writeUInt8(week_cycle_value); buffer.writeUInt8(start_hour); buffer.writeUInt8(start_min); buffer.writeUInt8(end_hour); buffer.writeUInt8(end_min); buffer.writeUInt16LE(valve_pulse); return buffer.toBytes(); } /** * set rule config * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {object} rule_config * @param {number} rule_config.id range: [1, 16] * @param {number} rule_config.enable values: (0: disable, 1: enable) * @param {object} rule_config.condition * @param {object} rule_config.action */ function setNewRuleConfig(rule_config) { var id = rule_config.id; var enable = rule_config.enable; var condition = rule_config.condition; var action = rule_config.action; var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); if (enable_values.indexOf(enable) === -1) { throw new Error("rules_config._item.enable must be one of " + enable_values.join(", ")); } var buffer = new Buffer(30); buffer.writeUInt8(0xff); buffer.writeUInt8(0x55); buffer.writeUInt8(id); // set rule config buffer.writeUInt8(getValue(enable_map, enable)); buffer.writeBytes(encodedRuleCondition(condition)); buffer.writeBytes(encodedAction(action)); return buffer.toBytes(); } /** * rule config condition * @param {object} condition * @param {number} condition.type values: (0: none, 1: time, 2: d2d, 3: time_or_pulse_threshold, 4: pulse_threshold) * @param {number} condition.start_time unit: second * @param {number} condition.end_time unit: second * @param {number} condition.repeat_enable values: (0: disable, 1: enable) * @param {number} condition.repeat_mode values: (0: monthly, 1: daily, 2: weekly) * @param {number} condition.repeat_step * @param {object} condition.repeat_week * @param {number} condition.repeat_week.monday values: (0: disable, 1: enable) * @param {number} condition.repeat_week.tuesday values: (0: disable, 1: enable) * @param {number} condition.repeat_week.wednesday values: (0: disable, 1: enable) * @param {number} condition.repeat_week.thursday values: (0: disable, 1: enable) * @param {number} condition.repeat_week.friday values: (0: disable, 1: enable) * @param {number} condition.repeat_week.saturday values: (0: disable, 1: enable) * @param {number} condition.repeat_week.sunday values: (0: disable, 1: enable) * @param {number} condition.d2d_command * @param {number} condition.valve_index values: (1: valve_1, 2: valve_2) * @param {number} condition.duration unit: min * @param {number} condition.pulse_threshold */ function encodedRuleCondition(condition) { var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); var condition_type_map = { 0: "none", 1: "time", 2: "d2d", 3: "time_or_pulse_threshold", 4: "pulse_threshold" }; var condition_type_values = getValues(condition_type_map); var repeat_mode_map = { 0: "monthly", 1: "daily", 2: "weekly" }; var repeat_mode_values = getValues(repeat_mode_map); var weekday_bit_offset = { monday: 0, tuesday: 1, wednesday: 2, thursday: 3, friday: 4, saturday: 5, sunday: 6 }; if (condition_type_values.indexOf(condition.type) === -1) { throw new Error("rules_config._item.condition.type must be one of " + condition_type_values.join(", ")); } var buffer = new Buffer(13); var condition_type_value = getValue(condition_type_map, condition.type); buffer.writeUInt8(condition_type_value); switch (condition_type_value) { case 0x00: // none break; case 0x01: // time condition (start_time, end_time, repeat_enable, repeat_mode, [repeat_step], [repeat_week]) if (enable_values.indexOf(condition.repeat_enable) === -1) { throw new Error("rules_config._item.condition.repeat_enable must be one of " + enable_values.join(", ")); } if (repeat_mode_values.indexOf(condition.repeat_mode) === -1) { throw new Error("rules_config._item.condition.repeat_mode must be one of " + repeat_mode_values.join(", ")); } buffer.writeUInt32LE(condition.start_time); buffer.writeUInt32LE(condition.end_time); buffer.writeUInt8(getValue(enable_map, condition.repeat_enable)); var repeat_mode_value = getValue(repeat_mode_map, condition.repeat_mode); buffer.writeUInt8(repeat_mode_value); // repeat mode: monthly or daily if (repeat_mode_value === 0x00 || repeat_mode_value === 0x01) { buffer.writeUInt16LE(condition.repeat_step); } // repeat mode: weekly else if (repeat_mode_value === 0x02) { var weekday_value = 0; for (var key in weekday_bit_offset) { if (key in condition.repeat_week) { if (enable_values.indexOf(condition.repeat_week[key]) === -1) { throw new Error("rules_config._item.repeat_week." + key + " must be one of " + enable_values.join(", ")); } weekday_value |= getValue(enable_map, condition.repeat_week[key]) << weekday_bit_offset[key]; } } buffer.writeUInt16LE(weekday_value); } break; case 0x02: // d2d condition (d2d_command) buffer.writeD2DCommand(condition.d2d_command, "0000"); break; case 0x03: // time or pulse threshold condition (valve_index, duration, pulse_threshold) buffer.writeUInt8(condition.valve_index); buffer.writeUInt16LE(condition.duration); buffer.writeUInt32LE(condition.pulse_threshold); break; case 0x04: // pulse threshold condition (valve_index, pulse_threshold) buffer.writeUInt8(condition.valve_index); buffer.writeUInt32LE(condition.pulse_threshold); break; } return buffer.toBytes(); } /** * rule config action * @param {object} action * @param {number} action.type values: (0: none, 1: em_valve_control, 2: valve_control, 3: report) * @param {number} action.valve_index values: (1: valve_1, 2: valve_2) * @param {number} action.valve_opening * @param {number} action.time_enable values: (0: disable, 1: enable) * @param {number} action.duration unit: min * @param {number} action.pulse_enable values: (0: disable, 1: enable) * @param {number} action.pulse_threshold * @param {number} action.report_type values: (1: valve_1, 2: valve_2, 3: custom_message) * @param {string} action.report_content * @param {number} action.report_counts * @param {number} action.threshold_release_enable values: (0: disable, 1: enable) * @example { "rules_config": [ { "index": 1, "enable": 1, "condition": { "type": 0 }, "action": { "type": 1, "valve_index": 1, "valve_opening": 1, "time_enable": 1, "duration": 1, "pulse_enable": 1, "pulse_threshold": 1 } }]} */ function encodedAction(action) { var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); var action_type_map = { 0: "none", 1: "em_valve_control", 2: "valve_control", 3: "report" }; var action_type_values = getValues(action_type_map); var report_type_map = { 1: "valve_1", 2: "valve_2", 3: "custom_message" }; var report_type_values = getValues(report_type_map); var buffer = new Buffer(13); if (action_type_values.indexOf(action.type) === -1) { throw new Error("rules_config._item.action.type must be one of " + action_type_values.join(", ")); } var action_type_value = getValue(action_type_map, action.type); buffer.writeUInt8(action_type_value); switch (action_type_value) { case 0x00: // none break; case 0x01: // em valve control (interrupt current execution task) if (enable_values.indexOf(action.time_enable) === -1) { throw new Error("rules_config._item.action.time_enable must be one of " + enable_values.join(", ")); } if (enable_values.indexOf(action.pulse_enable) === -1) { throw new Error("rules_config._item.action.pulse_enable must be one of " + enable_values.join(", ")); } buffer.writeUInt8(action.valve_index); buffer.writeUInt8(action.valve_opening); buffer.writeUInt8(getValue(enable_map, action.time_enable)); buffer.writeUInt32LE(action.duration); buffer.writeUInt8(getValue(enable_map, action.pulse_enable)); buffer.writeUInt32LE(action.pulse_threshold); break; case 0x02: // general valve control if (enable_values.indexOf(action.time_enable) === -1) { throw new Error("rules_config._item.action.time_enable must be one of " + enable_values.join(", ")); } if (enable_values.indexOf(action.pulse_enable) === -1) { throw new Error("rules_config._item.action.pulse_enable must be one of " + enable_values.join(", ")); } buffer.writeUInt8(action.valve_index); buffer.writeUInt8(action.valve_opening); buffer.writeUInt8(getValue(enable_map, action.time_enable)); buffer.writeUInt32LE(action.duration); buffer.writeUInt8(getValue(enable_map, action.pulse_enable)); buffer.writeUInt32LE(action.pulse_threshold); break; case 0x03: // report if (report_type_values.indexOf(action.report_type) === -1) { throw new Error("rules_config._item.action.report_type must be one of " + report_type_values.join(", ")); } buffer.writeUInt8(getValue(report_type_map, action.report_type)); buffer.writeAscii(action.report_content, 8); break; } return buffer.toBytes(); } /** * clear valve pulse * @param {number} valve_index values: (1: valve 1, 2: valve 2) * @param {number} clear_valve_pulse values: (0: no, 1: yes) * @example { "clear_valve_1_pulse": 1 } * @example { "clear_valve_2_pulse": 1 } */ function clearValvePulse(valve_index, clear_valve_pulse) { var yes_no_map = { 0: "no", 1: "yes" }; var yes_no_values = getValues(yes_no_map); if (yes_no_values.indexOf(clear_valve_pulse) === -1) { throw new Error("clear_valve_pulse must be one of " + yes_no_values.join(", ")); } if (getValue(yes_no_map, clear_valve_pulse) === 0) { return []; } return [0xff, 0x4e, valve_index, 0x00]; } /** * set pulse filter config * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {object} pulse_filter_config * @param {number} pulse_filter_config.mode values: (1: hardware, 2: software) * @param {number} pulse_filter_config.time hardware: unit: us, software: unit: ms * @example { "pulse_filter_config": { "mode": 1, "time": 40 } } */ function setPulseFilterConfig(pulse_filter_config) { var mode = pulse_filter_config.mode; var time = pulse_filter_config.time; var mode_map = { 1: "hardware", 2: "software" }; var mode_values = getValues(mode_map); if (mode_values.indexOf(mode) === -1) { throw new Error("pulse_filter_config.mode must be one of " + mode_values.join(", ")); } var buffer = new Buffer(6); buffer.writeUInt8(0xff); buffer.writeUInt8(0x52); buffer.writeUInt8(0x00); buffer.writeUInt8(getValue(mode_map, mode)); buffer.writeUInt16LE(time); return buffer.toBytes(); } /** * set gpio jitter time * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {number} gpio_jitter_time unit: s * @example { "gpio_jitter_time": 40 } */ function setGpioJitterTime(gpio_jitter_time) { if (typeof gpio_jitter_time !== "number") { throw new Error("gpio_jitter_time must be a number"); } var buffer = new Buffer(3); buffer.writeUInt8(0xff); buffer.writeUInt8(0x46); buffer.writeUInt8(gpio_jitter_time); return buffer.toBytes(); } /** * set valve power supply config * @since hardware_version>=v2.0, firmware_version>=v2.3 * @param {object} valve_power_supply_config * @param {number} valve_power_supply_config.counts range: [1, 5] * @param {number} valve_power_supply_config.control_pulse_time unit: ms, range: [20, 1000] * @param {number} valve_power_supply_config.power_time unit: ms, range: [500, 10000] * @example { "valve_power_supply_config": { "counts": 1, "control_pulse_time": 100, "power_time": 1000 } } */ function setValvePowerSupplyConfig(valve_power_supply_config) { var counts = valve_power_supply_config.counts; var control_pulse_time = valve_power_supply_config.control_pulse_time; var power_time = valve_power_supply_config.power_time; if (counts < 1 || counts > 5) { throw new Error("valve_power_supply_config.counts must be in the range of 1 to 5"); } if (control_pulse_time < 20 || control_pulse_time > 1000) { throw new Error("valve_power_supply_config.control_pulse_time must be in the range of 20 to 1000"); } if (power_time < 500 || power_time > 10000) { throw new Error("valve_power_supply_config.power_time must be in the range of 500 to 10000"); } var buffer = new Buffer(7); buffer.writeUInt8(0xff); buffer.writeUInt8(0x4f); buffer.writeUInt8(counts); buffer.writeUInt16LE(control_pulse_time); buffer.writeUInt16LE(power_time); return buffer.toBytes(); } /** * set pressure calibration * @since hardware_version>=v4.0, firmware_version>=v1.1 * @param {object} pressure_calibration_settings * @param {number} pressure_calibration_settings.enable values: (0: disable, 1: enable) * @param {number} pressure_calibration_settings.calibration_value unit: kPa * @example { "pressure_calibration_settings": { "enable": 1, "calibration_value": 1 } } */ function setPressureCalibration(pressure_calibration_settings) { var enable = pressure_calibration_settings.enable; var calibration_value = pressure_calibration_settings.calibration_value; var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); if (enable_values.indexOf(enable) === -1) { throw new Error("pressure_calibration_settings.enable must be one of " + enable_values.join(", ")); } var buffer = new Buffer(5); buffer.writeUInt8(0xff); buffer.writeUInt8(0xab); buffer.writeUInt8(getValue(enable_map, enable)); buffer.writeInt16LE(calibration_value); return buffer.toBytes(); } /** * history enable * @since hardware_version>=v3.0, firmware_version>=v3.1 * @param {number} history_enable values: (0: disable, 1: enable) * @example { "history_enable": 1 } */ function setHistoryEnable(history_enable) { var enable_map = { 0: "disable", 1: "enable" }; var enable_values = getValues(enable_map); if (enable_values.indexOf(history_enable) === -1) { throw new Error("history_enable must be one of " + enable_values.join(", ")); } var buffer = new Buffer(3); buffer.writeUInt8(0xff); buffer.writeUInt8(0x68); buffer.writeUInt8(getValue(enable_map, history_enable)); return buffer.toBytes(); } /** * clear history * @since hardware_version>=v3.0, firmware_version>=v3.1 * @param {number} clear_history values:(0: no, 1: yes) * @example { "clear_history": 1 } */ function clearHistory(clear_history) { var yes_no_map = { 0: "no", 1: "yes" }; var yes_no_values = getValues(yes_no_map); if (yes_no_values.indexOf(clear_history) === -1) { throw new Error("clear_history must be one of " + yes_no_values.join(", ")); } if (getValue(yes_no_map, clear_history) === 0) { return []; } return [0xff, 0x27, 0xff]; } /** * fetch history * @param {object} fetch_history * @param {number} fetch_history.start_time * @param {number} fetch_history.end_time * @example { "fetch_history": { "start_time": 1609459200, "end_time": 1609545600 } } */ function fetchHistory(fetch_history) { var start_time = fetch_history.start_time; var end_time = fetch_history.end_time; if (typeof start_time !== "number") { throw new Error("start_time must be a number"); } if (end_time && typeof end_time !== "number") { throw new Error("end_time must be a number"); } if (end_time && start_time > end_time) { throw new Error("start_time must be less than end_time"); } var buffer; if (end_time === 0) { buffer = new Buffer(6); buffer.writeUInt8(0xfd); buffer.writeUInt8(0x6b); buffer.writeUInt32LE(start_time); } else { buffer = new Buffer(10); buffer.writeUInt8(0xfd); buffer.writeUInt8(0x6c); buffer.writeUInt32LE(start_time); buffer.writeUInt32LE(end_time); } return buffer.toBytes(); } /** * history stop transmit * @param {number} stop_transmit values: (0: no, 1: yes) * @example { "stop_transmit": 1 } */ function stopTransmit(stop_transmit) { var yes_no_map = { 0: "no", 1: "yes" }; var yes_no_values = getValues(yes_no_map); if (yes_no_values.indexOf(stop_transmit) === -1) { throw new Error("stop_transmit must be one of " + yes_no_values.join(", ")); } if (getValue(yes_no_map, stop_transmit) === 0) { return []; } return [0xfd, 0x6d, 0xff]; } function getValues(map) { var values = []; for (var key in map) { values.push(RAW_VALUE ? parseInt(key) : map[key]); } return values; } function getValue(map, value) { if (RAW_VALUE) return value; for (var key in map) { if (map[key] === value) { return parseInt(key); } } throw new Error("not match in " + JSON.stringify(map)); } function Buffer(size) { this.buffer = new Array(size); this.offset = 0; for (var i = 0; i < size; i++) { this.buffer[i] = 0; } } Buffer.prototype._write = function (value, byteLength, isLittleEndian) { var offset = 0; for (var index = 0; index < byteLength; index++) { offset = isLittleEndian ? index << 3 : (byteLength - 1 - index) << 3; this.buffer[this.offset + index] = (value >> offset) & 0xff; } }; Buffer.prototype.writeUInt8 = function (value) { this._write(value, 1, true); this.offset += 1; }; Buffer.prototype.writeInt8 = function (value) { this._write(value < 0 ? value + 0x100 : value, 1, true); this.offset += 1; }; Buffer.prototype.writeUInt16LE = function (value) { this._write(value, 2, true); this.offset += 2; }; Buffer.prototype.writeInt16LE = function (value) { this._write(value < 0 ? value + 0x10000 : value, 2, true); this.offset += 2; }; Buffer.prototype.writeUInt24LE = function (value) { this._write(value, 3, true); this.offset += 3; }; Buffer.prototype.writeInt24LE = function (value) { this._write(value < 0 ? value + 0x1000000 : value, 3, true); this.offset += 3; }; Buffer.prototype.writeUInt32LE = function (value) { this._write(value, 4, true); this.offset += 4; }; Buffer.prototype.writeInt32LE = function (value) { this._write(value < 0 ? value + 0x100000000 : value, 4, true); this.offset += 4; }; Buffer.prototype.writeBytes = function (bytes) { for (var i = 0; i < bytes.length; i++) { this.buffer[this.offset + i] = bytes[i]; } this.offset += bytes.length; }; Buffer.prototype.writeAscii = function (value, maxLength) { for (let i = 0; i < maxLength; i++) { if (i < value.length) { this.buffer[this.offset + i] = value.charCodeAt(i); } else { this.buffer[this.offset + i] = 0; } } this.offset += maxLength; }; Buffer.prototype.writeD2DCommand = function (value, defaultValue) { if (typeof value !== "string") { value = defaultValue; } if (value.length !== 4) { throw new Error("d2d_cmd length must be 4"); } this.buffer[this.offset] = parseInt(value.substr(2, 2), 16); this.buffer[this.offset + 1] = parseInt(value.substr(0, 2), 16); this.offset += 2; }; Buffer.prototype.toBytes = function () { return this.buffer; }; function hexStringToBytes(hex) { var bytes = []; for (var c = 0; c < hex.length; c += 2) { bytes.push(parseInt(hex.substr(c, 2), 16)); } return bytes; }
This codec is sourced from Milesight-IoT. All rights belong to Milesight-IoT.
This codec is licensed under the GNU General Public License v3 (GPL v3). Modifications, if any, are clearly marked. You are free to use, modify, and distribute the codec under the terms of GPL v3.