星纵网关和节点连接LinkWAN
目录
概述
本文主要介绍如何将星纵物联LoRaWAN®网关和终端节点连接到LinkWAN平台。目前平台只支持CN470频段,如下以星纵物联UG65网关和EM300传感器(LinkWAN版本)为例。
准备
- 星纵物联LoRaWAN®网关
- LoRaWAN®终端节点(LinkWAN版)
步骤
1. 网关配置
1.1 获取密钥文件
- 用户提供网关序列号给设备厂商,厂商使用网关密钥管理工具生成配置文件;
- 用户获取配置压缩文件:(该文件包含了GwEUI和PIN Code两个参数,该参数在平台添加网关时需要使用)。
1.2 网关启用LinkWAN
进入网关配置页面“Packet Forwarder>常规”,根据下图,配置LinkWAN连接,需要在“导入配置文件”位置导入上一步生成的配置文件。
2. LinkWAN平台配置
登录LinkWAN物联网络管理平台:https://signin.aliyun.com。(可自助申请登录账号)
- 登录控制台,选择“物联网络管理平台>网络管理>网关管理>添加网关”。
- 输入配置文件中的GwEUI和PIN Code参数及对应信息,如下图所示。
- “物联网络管理平台>网络管理>网关管理”,查看网关状态为在线。
- 点击查看,可以看到该网关的上下行数据,以及无效数据。(当无法收到节点数据时,可以查看无效数据。一般节点的JoinEUI或者DevEUI不合法等错误会打印在无效数据中。)
LinkWAN相关程序说明如下:
3. 传感器连接LinkWAN
3.1 创建凭证和密钥
登录物联网络管理平台,先创建凭证和申请密钥。凭证用于后续创建产品使用,密钥用于设备烧录和添加设备时使用。
- 进入“网络管理>入网开通>添加专用凭证”授权给自己。
- 进入“密钥管理>申请密钥>申请购买密钥”。注意:一个密钥配置文件1元。
3.2 新建产品
切换到物联网平台,先创建一个产品,如果没有凭证需要先创建凭证,并授权(选择授权给自己)。
3.3 添加设备
- 进入“设备管理>设备>添加设备”,输入节点的DevEUI和PIN Code,这两个参数在平台成功购买密钥后,可以从下载的密钥文件EXCEL表格中获取。
- 查看设备启用状态,刚添加完设备为未激活状态,设备成功上线后变为在线。
3.4 创建模型
- 进入物联网平台“设备管理>产品>产品详情”,选择对应产品,为设备创建物理模型。
- 添加自定义功能。
注意:标识符和数据类型应该遵循Decoder内的定义进行填写,否则会解析不成功。
A、EM300设备自定义功能参考如下:
B、UC1152设备自定义功能参考如下:
3.5 发布上线
3.6 设置数据解析
- 选择数据解析,将数据解析代码拷贝进去,具体代码见4.7(EM300)或4.8(UC1152)。
- 到设备物理模型下,可以查看设备的上报数据。
3.7 EM300数据解析代码如下
/**
* 将设备自定义topic数据转换为json格式数据, 设备上报数据到物联网平台时调用
* 入参:topic string 设备上报消息的topic
* 入参:rawData byte[]数组 不能为空
* 出参:jsonObj JSON对象 不能为空
*/
function array(length) {
return new Array(length).fill(null);
}
function fixFloat(float, fractionDigits) {
fractionDigits = fractionDigits || 1;
var value = typeof float === "string" ? parseFloat(float) : float;
return parseFloat(value.toFixed(fractionDigits));
}
function readUInt8(bytes) {
return (bytes & 0xFF);
}
function readInt8(bytes) {
var ref = readUInt8(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) {
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;
}
function BufferReader(buffer) {
this.buffer = buffer;
this.offset = 0;
}
BufferReader.prototype.getBuffer = function () {
return this.buffer;
}
BufferReader.prototype.byteAt = function (index) {
return this.buffer[index];
}
BufferReader.prototype.firstByte = function () {
return this.buffer[0];
}
BufferReader.prototype.lastByte = function () {
return this.buffer[this.buffer.length - 1];
}
BufferReader.prototype.hasNextByte = function () {
return this.buffer.length > this.offset;
}
BufferReader.prototype.moveOffset = function (plus) {
this.offset += plus;
}
BufferReader.prototype.setOffset = function (offset) {
this.offset = offset;
}
BufferReader.prototype.getOffset = function () {
return this.offset;
}
BufferReader.prototype.readBit = function (bits) {
var result = [];
var binary = this.readUInt8();
for (var idx = 0; idx < bits; idx++) {
result[idx] = (binary >> idx) & 1;
}
return result;
}
BufferReader.prototype.readUInt8 = function () {
var result = readUInt8(this.buffer.slice(this.offset, this.offset + 1));
this.offset += 1;
return result;
}
BufferReader.prototype.readInt16LE = function () {
var result = readInt16LE(this.buffer.slice(this.offset, this.offset + 2));
this.offset += 2;
return result;
}
BufferReader.prototype.readUInt16LE = function () {
var result = readUInt16LE(this.buffer.slice(this.offset, this.offset + 2));
this.offset += 2;
return result;
}
BufferReader.prototype.readInt32LE = function () {
var result = readInt32LE(this.buffer.slice(this.offset, this.offset + 4));
this.offset += 4;
return result;
}
BufferReader.prototype.readUInt32LE = function () {
var result = readUInt32LE(this.buffer.slice(this.offset, this.offset + 4));
this.offset += 4;
return result;
}
BufferReader.prototype.readFloatLE = function () {
var result = readFloatLE(this.buffer.slice(this.offset, this.offset + 4));
this.offset += 4;
return result;
}
BufferReader.prototype.readString = function (size) {
var result = this.buffer.slice(this.offset, this.offset + size);
this.offset += size;
return result.toString().replace(/\0/g, "");
}
BufferReader.prototype.readHex = function (length) {
var hex = this.buffer.slice(this.offset, this.offset + length).toString(16);
this.offset += length;
return hex;
}
BufferReader.prototype.readBuffer = function (length) {
var buffer = this.buffer.slice(this.offset, this.offset + length);
this.offset += length;
return buffer;
}
var DATA_TYPE = {
Int8: "Int8",
UInt8: "UInt8",
Int16: "Int16LE",
UInt16: "UInt16LE",
Int32LE: "Int32LE",
UInt32LE: "UInt32LE",
};
var IPSO_PARSER = {
0: defCommonChannelParser("digitalInput", DATA_TYPE.UInt8),
1: defCommonChannelParser("digitalOutput", DATA_TYPE.UInt8),
3: defCommonChannelParser("analogOutput", DATA_TYPE.Int16, undefined, function (value) { return fixFloat(value * 0.01, 2) }),
101: defCommonChannelParser("illumianceSensor", DATA_TYPE.UInt16, "lux"),
102: defCommonChannelParser("presenceSensor", DATA_TYPE.UInt8),
103: defCommonChannelParser("temperatureSensor", DATA_TYPE.Int16, "¡ãC", function (value) { return fixFloat(value * 0.1) }),
104: defCommonChannelParser("humiditySensor", DATA_TYPE.UInt8, "%", function (value) { return fixFloat(value * 0.5) }),
106: defCommonChannelParser("actuation", DATA_TYPE.UInt16, ""),
115: defCommonChannelParser("barometerPressure", DATA_TYPE.UInt16, "kPa", function (value) { return fixFloat(value * 0.1) }),
116: defCommonChannelParser("voltage", DATA_TYPE.UInt16, "V", function (value) { return fixFloat(value * 0.01, 2) }),
117: defCommonChannelParser("current", DATA_TYPE.UInt8, "%"),
119: defCommonChannelParser("depth", DATA_TYPE.UInt16, "cm", function (value) { return value }),
123: defCommonChannelParser("pressure", DATA_TYPE.UInt16, "", function (value) { return fixFloat(value * 0.1) }),
124: defCommonChannelParser("loudness", DATA_TYPE.UInt16, "dB", function (value) { return fixFloat(value * 0.1) }),
125: defCommonChannelParser("concentration", DATA_TYPE.UInt16, ""),
128: defCommonChannelParser("power", DATA_TYPE.UInt16, "W", function (value) { return fixFloat(value * 0.01, 2) }),
130: defCommonChannelParser("distance", DATA_TYPE.UInt16, "m", function (value) { return fixFloat(value / 1000, 3) }),
146: defCommonChannelParser("par", DATA_TYPE.UInt16, "¦Ìmol¡¤m?2¡¤s?1", function (value) { return fixFloat(value * 0.1) }),
200: defCommonChannelParser("counter", DATA_TYPE.UInt32LE),
};
// analog input
IPSO_PARSER[2] = function (channel) {
return function (reader, data) {
data[channel] = {
channel: channel,
type: "analogInput",
value: {
ccy: fixFloat(reader.readInt16LE() * 0.01, 2),
min: fixFloat(reader.readInt16LE() * 0.01, 2),
max: fixFloat(reader.readInt16LE() * 0.01, 2),
avg: fixFloat(reader.readInt16LE() * 0.01, 2),
},
};
}
};
var URSA_PARSER = {
1: defConfigDataParser("version", DATA_TYPE.UInt8, function (value) { return value.toString() }),
2: defConfigDataParser("interval", DATA_TYPE.UInt16),
3: defConfigDataParser("period", DATA_TYPE.UInt16),
4: defConfigDataParser("response", DATA_TYPE.UInt8),
5: parseLoRaChanMask,
6: skip(9, "alarmConfig"),
7: defConfigDataParser("debugLevel", DATA_TYPE.UInt8),
8: defSNParser(6),
9: parseVersion("hardware"),
10: parseVersion("firmware"),
11: defConfigDataParser("powerOn", DATA_TYPE.UInt8, function (value) { return value > 0 }),
12: defConfigDataParser("powerOff", DATA_TYPE.UInt8, function (value) { return value > 0 }),
13: parseAlarm,
14: parseChannels,
15: defConfigDataParser("class", DATA_TYPE.UInt8),
16: defConfigDataParser("reboot", DATA_TYPE.UInt8),
17: defConfigDataParser("timestamp", DATA_TYPE.UInt16),
18: parseText,
19: defConfigDataParser("intelligenceReport", DATA_TYPE.UInt8),
20: parseConversion,
21: parseCollectFailedChannelIds,
22: defSNParser(8),
};
function convertBufferToObject(buffer) {
var reader = new BufferReader(buffer);
var data = { config: {} };
while (reader.hasNextByte()) {
var channel = reader.readUInt8();
var type = reader.readUInt8();
var parser = getReadParser(channel, type);
if (parser) {
parser.parse(reader, data);
}
}
return data;
}
function getReadParser(channel, type) {
if (channel === 255) {
return { parse: URSA_PARSER[type] };
} else {
return { parse: IPSO_PARSER[type](channel) };
}
}
function defCommonChannelParser(type, dataType, unit, converter) {
return function (channel) {
return function (reader, data) {
var value = reader["read" + dataType]();
data[channel] = {
channel: channel,
type: type,
unit: unit,
value: (converter || function (value) { return value })(value),
};
}
};
}
function defConfigDataParser(property, dataType, converter) {
return function (reader, data) {
var value = reader["read" + dataType]();
if (!data.config) {
data.config = {};
}
data.config[property] = (converter || function (value) { return value })(value);
};
}
function skip(length, property) {
return function (reader, data) {
reader.moveOffset(length);
};
}
function defSNParser(length) {
return function (reader, data) {
data.config.sn = reader.readHex(length);
};
}
function parseLoRaChanMask(reader, data) {
var id = reader.readUInt8();
var value = reader.readUInt16LE();
data.config.loRaChanMask = { id: id, value: value };
}
function parseText(reader, data) {
var length = reader.readUInt8();
data.config.text = reader.readString(length);
}
function parseAlarm(reader, data) {
var MODE = ["DISABLE", "BELOW", "ABOVE", "WITHIN", "BELOW_OR_ABOVE"];
var idAndMode = reader.readUInt8();
var id = (idAndMode & 56) >>> 3; // & 0x00111000 >> 3
var mode = MODE[idAndMode & 7]; // & 0x00000111
var min = reader.readInt16LE() / 10;
var max = reader.readInt16LE() / 10;
var current = reader.readInt16LE() / 10;
var lessThan = min;
var greaterThan = max;
if (mode === "WITHIN") {
lessThan = max;
greaterThan = min;
} else if (mode === "BELOW_OR_ABOVE") {
mode = current < lessThan ? "BELOW" : "ABOVE";
}
data.config.alarm = { id: id, mode: mode, greaterThan: greaterThan, lessThan: lessThan, current: current };
}
function parseConversion(reader, data) {
var value = reader.readUInt8();
var index = (value & 240) >>> 4; // & 11110000 >> 4
var type = (value & 15) === 0 ? "mA" : "V"; // & 00001111
if (!data.config.conversion) {
data.config.conversion = [];
}
data.config.conversion.push({ index: index, type: type });
}
function parseVersion(property) {
return function (reader, data) {
var mainVer = reader.readUInt8();
var subVer = reader.readUInt8();
var realMainVer = mainVer % 16 + Math.floor(mainVer / 16) * 10;
var realSubVer = subVer % 16 === 0 && property === "hardware"
? Math.floor(subVer / 16)
: subVer % 16 + Math.floor(subVer / 16) * 10;
if (property === "hardware") {
data.config.hardware = "v" + realMainVer + "." + realSubVer;
} else if (property === "firmware") {
data.config.firmware = "v" + realMainVer + "." + realSubVer;
}
};
}
var CHANNEL_TYPE = ["REG_COIL", "REG_DISCRETE", "REG_INPUT", "REG_HOLD_INT16", "REG_HOLD_INT32",
"REG_HOLD_FLOAT", "REG_INPUT_INT32", "REG_INPUT_FLOAT",
"REG_INPUT_INT32_AB", "REG_INPUT_INT32_CD", "REG_HOLD_INT32_AB", "REG_HOLD_INT32_CD"];
function parseChannels(reader, data) {
var channelId = reader.readUInt8();
var ptype = reader.readUInt8();
var dataType = CHANNEL_TYPE[ptype & 7];
var dataLength = ptype >> 3;
var offset = reader.getOffset();
var raw = "";
var value = 0;
switch (dataType) {
case "REG_COIL":
case "REG_DISCRETE":
value = reader.readUInt8();
reader.setOffset(offset);
raw = reader.readHex(1);
break;
case "REG_INPUT":
case "REG_HOLD_INT16":
case "REG_INPUT_INT32_AB":
case "REG_INPUT_INT32_CD":
case "REG_HOLD_INT32_AB":
case "REG_HOLD_INT32_CD":
value = reader.readUInt16LE();
reader.setOffset(offset);
raw = reader.readHex(2);
break;
case "REG_HOLD_INT32":
case "REG_INPUT_INT32":
value = reader.readUInt32LE();
reader.setOffset(offset);
raw = reader.readHex(4);
break;
case "REG_HOLD_FLOAT":
case "REG_INPUT_FLOAT":
value = fixFloat(reader.readFloatLE(), 4);
reader.setOffset(offset);
raw = reader.readHex(4);
break;
}
if (!data.config.channelData) {
data.config.channelData = array(8);
}
data.config.channelData[channelId] = {
channelId: channelId,
dataType: dataType,
dataLength: dataLength,
value: value,
raw: raw,
};
}
function writeUInt8(bytes, value, index) {
bytes[index] = value;
}
function writeUInt16LE(bytes, value, index) {
var low = value & 0xFF;
var high = (value & 0xFF00) >> 8;
bytes[index] = low;
bytes[index + 1] = high;
}
function safeValue(value, defaultValue) {
if (value === null || value === undefined) {
return defaultValue;
}
value = Number(value);
if (value === NaN) {
return defaultValue;
}
return value;
}
function writeAlarm(alarmConfig) {
var MODE = { DISABLE: 0, BELOW: 1, ABOVE: 2, WITHIN: 3, BELOW_OR_ABOVE: 4 };
const bytes = new Uint8Array(11);
writeUInt8(bytes, 255, 0);
writeUInt8(bytes, 6, 1);
var idAndMode = ((alarmConfig.id || 0) << 3) + (MODE[alarmConfig.mode || "DISABLE"]);
writeUInt8(bytes, idAndMode, 2);
writeUInt8(bytes)
var upper = safeValue(alarmConfig.upper, 9999);
var lower = safeValue(alarmConfig.lower, 9999);
if ((alarmConfig.mode === "BELOW_OR_ABOVE" || alarmConfig.mode === "WITHIN") && upper > lower) {
// min和max都有值时min一定小于max
[upper, lower] = [lower, upper];
}
writeUInt16LE(bytes, upper, 3); // min
writeUInt16LE(bytes, lower, 5); // max
writeUInt16LE(bytes, 0, 7);
writeUInt16LE(bytes, 0, 9);
return bytes;
}
function parseCollectFailedChannelIds(reader, data) {
var ids = data.config.collectFailedChannelIds || [];
ids.push(reader.readUInt8());
data.config.collectFailedChannelIds = ids;
}
function Decoder(bytes, port) {
return convertBufferToObject(bytes);
}
function bufferToBytes(buffer) {
var bytes = [];
buffer.forEach(function (v) { bytes.push(v) });
return bytes;
}
function rawDataToProtocol(bytes) {
var data = convertBufferToObject(new Uint8Array(bytes));
var result = {};
// {"method":"thing.event.property.post", "id":"12345", "params":{"Temperature":1,"Humidity":2}, "version":"1.1"}
result.method = "thing.event.property.post";
result.version = "1.1";
result.id = "1";
result.params = {};
if (data[3]) {
result.params.temperature = data[3].value;
}
if (data[4]) {
result.params.humiditySensor = data[4].value;
}
if (data[6]) {
result.params.digitalInput = data[6].value;
}
if (data[5]) {
result.params.digitalInput1 = data[5].value;
}
if (data[1]) {
result.params.BatteryLevel = data[1].value;
}
return result;
}
function protocolToRawData(json) {
// {
// "method": "thing.service.SetTempHumiThreshold",
// "id": "12345",
// "version": "1.1",
// "params": {
// "MaxTemp": 50,
// "MinTemp": 8,
// "MaxHumi": 90,
// "MinHumi": 10
// }
// }
var header = [0x5D, 0x01, 0x00];
var params = json.params;
var lower = params.maxtemp;
var upper = params.mintemp;
var mode;
var isUpperExists = !!upper || upper === 0;
var isLowerExists = !!lower || lower === 0;
if (isUpperExists && !isLowerExists) {
mode = "BELOW";
} else if (!isUpperExists && isLowerExists) {
mode = "ABOVE";
} else if (!isUpperExists && !isLowerExists) {
return new Uint8Array([0x5d, 0x01, 0x00, 0xff, 0x06, 0x11, 0x14, 0x00, 0x32, 0x00, 0x3c, 0x00, 0x00, 0x00]);
}else if (lower <= upper) {
mode = "WITHIN";
} else if (lower > upper) {
mode = "BELOW_OR_ABOVE";
}
var payload = writeAlarm({id: 1, mode, upper, lower});
var arr = [];
payload.forEach(function (v, i) {
arr[i] = v;
})
return new Uint8Array(header.concat(arr));
}
function transformPayload() {
}
3.8 UC1152数据解析代码如下
/**
* 将设备自定义topic数据转换为json格式数据, 设备上报数据到物联网平台时调用
* 入参:topic string 设备上报消息的topic
* 入参:rawData byte[]数组 不能为空
* 出参:jsonObj JSON对象 不能为空
*/
function transformPayload(topic, rawData) {
var data = Decoder(rawData, 85);
var jsonObj = {};
jsonObj.method=topic;
jsonObj.version="1.0";
jsonObj.id="1";
jsonObj.params=data;
return jsonObj;
}
/**
* 将设备的自定义格式数据转换为Alink协议的数据,设备上报数据到物联网平台时调用
* 入参:rawData byte[]数组 不能为空
* 出参:jsonObj Alink JSON对象 不能为空
*/
function rawDataToProtocol(rawData) {
var data = Decoder(rawData, 85);
var jsonObj = {};
jsonObj.method="thing.event.property.post";
jsonObj.version="1.0";
jsonObj.id="1";
jsonObj.params=data;
return jsonObj;
}
/**
* 将Alink协议的数据转换为设备能识别的格式数据,物联网平台给设备下发数据时调用
* 入参:jsonObj Alink JSON对象 不能为空
* 出参:rawData byte[]数组 不能为空
*
*/
function protocolToRawData(jsonObj) {
var rawdata = [];
return rawdata;
}
function Decoder(bytes, port) {
var decoded = {};
for (i = 0; i < bytes.length;) {
var channel_id = bytes[i++];
var channel_type = bytes[i++];
// Digital Input 1
if (channel_id === 0x01 && channel_type !== 0xc8) {
decoded.din1 = bytes[i] === 0 ? "off" : "on";
i += 1;
}
// Digital Input 2
else if (channel_id === 0x02 && channel_type !== 0xc8) {
decoded.din2 = bytes[i] === 0 ? "off" : "on";
i += 1;
}
// Pulse Counter 1
else if (channel_id === 0x01 && channel_type === 0xc8) {
decoded.counter1 = readUInt32LE(bytes.slice(i, i + 4));
i += 4;
}
// Pulse Counter 1
else if (channel_id === 0x02 && channel_type === 0xc8) {
decoded.counter2 = readUInt32LE(bytes.slice(i, i + 4));
i += 4;
}
// Digital Output 1
else if (channel_id === 0x09) {
decoded.dout1 = bytes[i] === 0 ? "off" : "on";
i += 1;
}
// Digital Output 2
else if (channel_id === 0x0a) {
decoded.dout2 = bytes[i] === 0 ? "off" : "on";
i += 1;
}
// ADC 1
else if (channel_id === 0x11) {
decoded.adc1 = {};
decoded.adc1.cur = readInt16LE(bytes.slice(i, i + 2)) / 100;
decoded.adc1.min = readInt16LE(bytes.slice(i + 2, i + 4)) / 100;
decoded.adc1.max = readInt16LE(bytes.slice(i + 4, i + 6)) / 100;
decoded.adc1.avg = readInt16LE(bytes.slice(i + 6, i + 8)) / 100;
i += 8;
continue;
}
// ADC 2
else if (channel_id === 0x12) {
decoded.adc2 = {};
decoded.adc2.cur = readInt16LE(bytes.slice(i, i + 2)) / 100;
decoded.adc2.min = readInt16LE(bytes.slice(i + 2, i + 4)) / 100;
decoded.adc2.max = readInt16LE(bytes.slice(i + 4, i + 6)) / 100;
decoded.adc2.avg = readInt16LE(bytes.slice(i + 6, i + 8)) / 100;
i += 8;
continue;
}
// MODBUS
else if (channel_id === 0xFF && channel_type === 0x0E) {
var modbus_chn_id = bytes[i++];
var package_type = bytes[i++];
var data_type = package_type & 7;
var date_length = package_type >> 3;
var chn = 'chn' + modbus_chn_id;
switch (data_type) {
case 0:
decoded[chn] = bytes[i] ? "on" : "off";
i += 1;
break;
case 1:
decoded[chn] = bytes[i];
i += 1;
break;
case 2:
case 3:
decoded[chn] = readUInt16LE(bytes.slice(i, i + 2));
i += 2;
break;
case 4:
case 6:
decoded[chn] = readUInt32LE(bytes.slice(i, i + 4));
i += 4;
break;
case 5:
case 7:
decoded[chn] = readFloatLE(bytes.slice(i, i + 4));
i += 4;
break;
}
}
}
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;
}