64bit double float to byteArray ?

Hi there,

I've started developing a widget to remote control via BLE the Insta360 One R action camera.

https://apps.garmin.com/us-US/apps/bb53f4bb-6c8c-4369-bca9-84cc09b25526

I've already implement basic remote control functionality and now I'm in the phase of sending GPS Telemetry to the camera.

However the camera only wants 64bit double float GPS coordinates , speed, altitude, etc, values sent in HEX format that I handle through byteArrays, but the encodeNumber method only supports 32bit encoding.

Is there a way around this ?

Thanks for your help.

Greg

  • Can’t you concatenate encodeNumber(n >> 32) with encodeNumber(n & 0xffffffff)?

    (After zero padding them, obviously.)

  • Nope, standard IEEE float representation is a bit complex with 3 parts and although 32bit and 63 bit float encoding is similar, they cannot be shifted or padded easily from one to another

  • Thanks. The insta360 BLE protocol does indeed use standard IEEE float representation, however I was trying to avoid burning a whole in the watch CPU,  computing 3 or 4 float values  each second ;)

  • So finally I've wiped up this function, not very elegant, but does the job for what I need.

    	function toDouble(single) {
    		// convert a 32bit float to 64-bit, unsigned, in a bitArray. 
    		var double = new [0]b;
    		var d1 = new [4]b;
    		var d2 = new [4]b;
    		var t32 = new [4]b;
    		var tb = 0l;
    		var tb1 = 0l;
    		var tb2 = 0l;
    		var te = 0l;
    		var tm = 0l;
    		var bias = 0l;
    		// Need to convert to float 64bit, but not directly supported by Connect IQ.
    		// convert back and forth from 32bit float to 32bit INT to have a "bitwise" representation
    		t32.encodeNumber(single,Lang.NUMBER_FORMAT_FLOAT,null);
    		tb = t32.decodeNumber(NUMBER_FORMAT_UINT32, null);
    		// get the exponent part (ignoring sign)
    		te = tb & 0xff800000;
    		te >>= 23;
    		// new 64bit based bias
    		bias = te - 127 + 1023;
    		bias <<= 52;
    		// get the mantisse part and shift it 
    		tm = tb & 0x007fffff;
    		tm <<= 29;
    		// add the new bias to the mantis
    		tb = bias + tm;
    		// cut the new value in 2 32bit chunks
    		tb1 = tb;
    		tb1 >>= 32;
    		d1.encodeNumber(tb1, NUMBER_FORMAT_UINT32, null);
    		tb1 <<=32;
    		tb2 = tb - tb1;
    		d2.encodeNumber(tb2, NUMBER_FORMAT_UINT32, null);
    		// build the final bitArray, little endian
    		double.addAll(d2);
    		double.addAll(d1);
    		return double;
    	}
    

  • Looks good! 

    If you want to improve efficiency, you may not need to calculate everything every second, but use what you had before.  For example, if speed hasn't changed, just use what you had before, or for GPS, only if it's moved more than a tiny amount (you are standing still, but may change a few feet due to GPS drift).  Elevation might not change more than a foot or 2 if you are flat and level ground, etc.

    But the biggest battery drain could be BLE itself, and not the math, in which case maybe try every 5 seconds instead of every second. (or make it configurable)

  • Thanks for the suggestion. In any case my code needs lots of refinement ;)

  • I must have had a slow day yesterday!

    I went a different route based on Jim's link:

    ETA: I HAD A PROBLEM WITH NEGATIVE VALUES! I've edited the above to fix it.

    ETA2: I ALSO HAD A PROBLEM WITH FRACTIONAL VALUES! Edited again.

        function flTypeToBytes(val,exponentNumBits,storeBits) {
        	// https://www.wikihow.com/Convert-a-Number-from-Decimal-to-IEEE-754-Floating-Point-Representation
        	var bias = (1 << (exponentNumBits-1)) - 1, ONE_LONG = 1l;
        	var sign = val < 0;
        	val = val.abs(); // Remove problems with negative values
        	var decimal = val.toLong(), rest = val - decimal;
        	var output = decimal.toLong(), significance = 0, exponent = 0, bitCount = 0;
        	if(decimal > 0) {
        		significance = -1;
    	    	while(decimal > 0) {
    	    		bitCount++;
    	    		decimal >>= 1;
    	    		significance++;
    	    	}
    	    	exponent = significance;
        	}
        	var maxDepth = storeBits - exponentNumBits - 1 - exponent;
    		while(rest > 0 && maxDepth > bitCount) {
    			rest *= 2;
    			var n = rest.toNumber();
    			output <<= 1;
    			output |= n;
    			rest -= n;
    			if(exponent == 0) {
    				if(n == 0) {	significance--;					}
    				else {			exponent = significance - 1;	}
    			} 
    			if(exponent != 0) {	bitCount++;						}
    		}
    		// Remove the leading 1 as that's implicit from the exponent
    		output -= ONE_LONG << (bitCount-1);
    		output <<= storeBits - bitCount - exponentNumBits;
    		output |= (bias + exponent).toLong() << (storeBits - 1 - exponentNumBits);
    		var bitsPerByte = 8, numBytes = storeBits / bitsPerByte;
    		var a = new [numBytes];
    		for(var i=0;i<numBytes;i++) {
    			var shift = (storeBits - bitsPerByte) - i * bitsPerByte;
    			var byte = (output >> shift) & 0xff; 
    			a[i] = byte.toNumber();
    		}
    		// Make sure we respect negatives
    		if(sign) {			a[0] += 128;		}
    		return a;
        }
        function doubleToBytes2(double) {
        	var arr = flTypeToBytes(double,11,64);
        	System.println(double+" double becomes "+arr);
        	return arr;
    	}    
    	function floatToBytes2(float) {
        	var arr = flTypeToBytes(float,8,32);
        	System.println(float+" float becomes "+arr);
        	return arr;
    	}

     (Won't let me edit now, for some reason, but the max depth line should be "var maxDepth = storeBits - exponentNumBits;")

  • And somewhat unnecessarily, going the other way:

    function flBytesToType(bytes,exponentNumBits,storeBits,input) {
        	// 0. Set some constant values
        	var bias = (1 << (exponentNumBits-1)) - 1, numBytes = bytes.size();
        	var ZERO_LONG = 0l, ONE_LONG = 1l, TWO_LONG = 2l, ONE_DOUBLE = 1.0d, TWO_DOUBLE = 2.0d; // Prevent human error with wrong data type meaning shifts out of legal range...
        	// 1. Rebuild number
        	var lastByte = numBytes - 1, bitsPerByte = 8, byte = numBytes;
        	var asLong = ZERO_LONG;
        	while(byte > 0) {
        		byte--;
        		asLong |= bytes[byte].toLong() << ((lastByte - byte) * bitsPerByte);
        	}    	
        	// 2. Extract exponent and sign and mask
        	var signBit = ONE_LONG << (storeBits - 1);
        	var signMultiplier = asLong & signBit == signBit ? -1 : 1;
        	var exponent = ZERO_LONG, offset = storeBits - 1;
        	var i = 0, readPos, writePos;
        	while(i < exponentNumBits) {
        		readPos = offset - i - 1;
        		writePos = exponentNumBits - i - 1;
        		exponent |= (asLong & (ONE_LONG << readPos)) >> (readPos - writePos); 
        		i++;
        	}
        	exponent -= bias;
        	// 3. Rebuild value
    		var value = ONE_DOUBLE, toAdd = ONE_DOUBLE / 2;
    		var readBit = ONE_LONG << (storeBits - exponentNumBits - 2);
    		while(i < storeBits) {
    			if((asLong & readBit) > 0) {	value += toAdd;			}
    			readBit >>= 1;
    			toAdd /= 2;
    			i++;
    		}
    		return signMultiplier * value * Math.pow(TWO_DOUBLE,exponent);    	    	
        }
        function doubleFromBytes(bytes) {
        	var input = 0d;
        	return flBytesToType(bytes,11,64,input).toDouble();
        }
        function floatFromBytes(bytes) {
        	var input = 0f;
        	return flBytesToType(bytes,8,32,input).toFloat();
        }

  • Thanks! You helped me a lot! Very good solution.