Completes correct function for converting 2 bytes to Float16

This commit is contained in:
Patrick McDonagh
2018-01-08 18:26:08 -06:00
parent 3645e1a803
commit e6372acf34
5 changed files with 262 additions and 90 deletions

111
python-driver/lambda.js Normal file
View File

@@ -0,0 +1,111 @@
var assert = require('assert');
var topic = {"ChannelName":"t_amb","DeviceAddress":"c4:93:00:0a:8a:0d:01:99","DeviceType":"prostarsolar","GatewayAddress":"C493000A8A0D","TechName":"prostarsolar_[c4:93:00:0a:8a:0d:01:99]","TenantID":194};
var payload = {"Timestamp":1515422927,"Value":"0"};
var now = 1515422927;
var lambda = function(topic, payload, now){
var inpValue = parseInt(payload.Value);
var outValue;
var binaryInput = Array(16).join("0") + inpValue.toString(2);
var binRep = binaryInput.substring(binaryInput.length - 16);
var sign = 1;
var fraction;
if (parseInt(binRep[0]) === 1){
sign = -1;
}
var exponent = parseInt(binRep.substring(1, 6), 2);
if (exponent === 30.0){
fraction = parseInt("1".concat(binRep.substring(7, 17)), 2);
} else {
fraction = parseInt(binRep.substring(7, 17), 2);
}
if (exponent === 0){
outValue = sign * Math.pow(2,-14) * fraction / Math.pow(2.0, 10.0);
} else if (exponent === 0b11111){
if (fraction == 0){
outValue = sign * Infinity;
} else {
outValue = NaN;
}
} else {
var frac_part = 1.0 + fraction / Math.pow(2.0, 10.0);
outValue = sign * Math.pow(2 , exponent - 15) * frac_part;
}
return outValue;
}
var intToFloat16 = function(intToConvert){
var binRep = pad(intToConvert.toString(2), 16, "0");
var sign = 1;
var fraction;
if (parseInt(binRep[0]) === 1){
sign = -1;
}
var exponent = parseInt(binRep.substring(1, 6), 2);
if (exponent === 30.0){
fraction = parseInt("1".concat(binRep.substring(7, 17)), 2);
} else {
fraction = parseInt(binRep.substring(7, 17), 2);
}
// console.log("Sign: " + sign);
// console.log("exponent: " + exponent + " -- " + binRep.substring(1, 6));
// console.log("fraction: " + fraction);
if (exponent === 0){
return sign * (2 ** -14) * fraction / (2.0 ** 10.0);
} else if (exponent === 0b11111){
if (fraction == 0){
return sign * Infinity;
} else {
return NaN;
}
} else {
var frac_part = 1.0 + fraction / (2.0 ** 10.0);
return sign * (2 ** (exponent - 15)) * frac_part;
}
}
var pad = function(start, numChars, padWith){
var fullString = Array(numChars).join(padWith) + start;
return fullString.substring(fullString.length - 16);
}
var test_values = [
[0b0000000000000000, 0.],
[0b1000000000000000, -0.],
[0b0011110000000000, 1],
[0b0011110000000001, 1.0009765625],
[0b1011110000000001, -1.0009765625],
[0b1100000000000000, -2],
[0b0100000000000000, 2],
[0b0111101111111111, 65504.],
[0b1111101111111111, -65504.],
[0b0000010000000000, 6.10352e-5],
[0b0000001111111111, 6.09756e-5],
[0b0000000000000001, 5.96046e-8],
[0b0111110000000000, Infinity],
[0b1111110000000000, -Infinity],
[0b0011010101010101, 0.333251953125]
]
describe('intToFloat16', function(){
for (var i = 0; i < test_values.length; i++){
var testInput = test_values[i][0];
var expectedVal = test_values[i][1];
payload.Value = testInput;
// var testVal = intToFloat16(testInput);
var testVal = lambda(topic, payload, now);
it("should return " + test_values[i][1] + " for " + test_values[i][0].toString(2) + " : " + testVal, function(){
assert.equal(testVal, expectedVal);
})
}
})

View File

@@ -2,11 +2,10 @@
import threading
from device_base import deviceBase
from Channel import ModbusChannel
import persistence
from utilities import get_public_ip_address, int_to_float16
from utilities import get_public_ip_address
import prostarsolar_utils
import time
import minimalmodbus
import minimalmodbusM1
@@ -16,68 +15,10 @@ minimalmodbusM1.STOPBITS = 2
_ = None
def charge_state(inp_state):
"""Map function for charge state."""
states = {
0: "Start",
1: "Night Check",
2: "Disconnect",
3: "Night",
4: "Fault",
5: "Bulk",
6: "Absorption",
7: "Float",
8: "Equalize"
}
if inp_state in range(0, 9):
return states[inp_state]
else:
return inp_state
def array_faults(inp_array_faults):
"""Form a string for the array_faults."""
fault_string = ""
faults = {
0: "Overcurrent Phase 1",
1: "FETs Shorted",
2: "Software Bug",
3: "Battery HVD (High Voltage Disconnect)",
4: "Array HVD (High Voltage Disconnect)",
5: "EEPROM Setting Edit (reset required)",
6: "RTS Shorted",
7: "RTS was valid now disconnected",
8: "Local temp. sensor failed",
9: "Battery LVD (Low Voltage Disconect)",
10: "DIP Switch Changed (excl. DIP 8)",
11: "Processor Supply Fault"
}
bit_string = ("0" * 16 + "{0:b}".format(inp_array_faults))[-16:]
for i in range(0, 12):
if int(bit_string[i]) == 1:
fault_string += faults[i] + ", "
if fault_string:
return fault_string[:-2]
else:
return "None"
# GLOBAL VARIABLES
WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status
PLC_IP_ADDRESS = "192.168.1.10"
CHANNELS = [
ModbusChannel("adc_ia", 17, "REAL", 0.5, 3600, transformFn=int_to_float16),
ModbusChannel("adc_vbterm", 18, "REAL", 0.5, 3600, transformFn=int_to_float16),
ModbusChannel("adc_va", 19, "REAL", 0.5, 3600, transformFn=int_to_float16),
ModbusChannel("adc_vl", 20, "REAL", 0.5, 3600, transformFn=int_to_float16),
ModbusChannel("adc_il", 22, "REAL", 0.5, 3600, transformFn=int_to_float16),
ModbusChannel("t_amb", 28, "REAL", 2.0, 3600, transformFn=int_to_float16),
ModbusChannel("vb_min_daily", 65, "REAL", 2.0, 3600, transformFn=int_to_float16),
ModbusChannel("vb_max_daily", 66, "REAL", 2.0, 3600, transformFn=int_to_float16),
ModbusChannel('charge_state', 33, "STRING", 1, 3600, transformFn=charge_state),
ModbusChannel('array_fault', 34, "STRING", 1, 3600, transformFn=array_faults)
]
CHANNELS = prostarsolar_utils.CHANNELS # Load the channels from the utils program
# PERSISTENCE FILE
persist = persistence.load()
@@ -116,17 +57,8 @@ class start(threading.Thread, deviceBase):
public_ip_address = get_public_ip_address()
self.sendtodbDev(1, 'public_ip_address', public_ip_address, 0, 'prostarsolar')
# watchdog = self.prostarsolar_watchdog()
# self.sendtodbDev(1, 'watchdog', watchdog, 0, 'prostarsolar')
# watchdog_send_timestamp = time.time()
connected_to_485 = False
while connected_to_485 is False:
connected_to_485 = self.mcu.set485Baud(9600)
serial_485 = self.mcu.rs485
instrument_485 = minimalmodbusM1.Instrument(21, serial_485)
instrument_485.address = 21
instrument_485 = self.connect_to_485(prostarsolar_utils.BAUDRATE, prostarsolar_utils.NODEADDRESS)
send_loops = 0
watchdog_loops = 0
@@ -137,20 +69,14 @@ class start(threading.Thread, deviceBase):
for chan in CHANNELS:
try:
val = chan.read(instrument_485.read_register(chan.register_number, functioncode=4))
val = chan.read(instrument_485.read_registers(chan.register_number, chan.channel_size, functioncode=4))
if chan.check(val, self.forceSend):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'prostarsolar')
time.sleep(0.1)
except IOError as e:
print("prostarsolar IO Error: {}".format(e))
print("Attempting to reconnect to rs485 device")
connected_to_485 = False
while connected_to_485 is False:
connected_to_485 = self.mcu.set485Baud(9600)
serial_485 = self.mcu.rs485
instrument_485 = minimalmodbusM1.Instrument(21, serial_485)
instrument_485.address = 21
instrument_485 = self.connect_to_485(prostarsolar_utils.BAUDRATE, prostarsolar_utils.NODEADDRESS)
except Exception as e:
print("prostarsolar Non-IO Error: {}".format(e))
@@ -165,11 +91,6 @@ class start(threading.Thread, deviceBase):
watchdog_loops += 1
if (watchdog_loops >= watchdog_check_after):
# test_watchdog = self.prostarsolar_watchdog()
# if not test_watchdog == watchdog or (time.time() - watchdog_send_timestamp) > WATCHDOG_SEND_PERIOD:
# self.sendtodbDev(1, 'watchdog', test_watchdog, 0, 'prostarsolar')
# watchdog = test_watchdog
test_public_ip = get_public_ip_address()
if not test_public_ip == public_ip_address:
self.sendtodbDev(1, 'public_ip_address', test_public_ip, 0, 'prostarsolar')
@@ -177,6 +98,17 @@ class start(threading.Thread, deviceBase):
watchdog_loops = 0
time.sleep(10)
def connect_to_485(self, baudrate, nodeaddress):
"""Connect to the RS485 device."""
connected_to_485 = False
while connected_to_485 is False:
connected_to_485 = self.mcu.set485Baud(baudrate)
serial_485 = self.mcu.rs485
instrument_485 = minimalmodbusM1.Instrument(nodeaddress, serial_485)
instrument_485.address = nodeaddress
return instrument_485
def prostarsolar_sync(self, name, value):
"""Sync all data from the driver."""
self.forceSend = True

View File

@@ -0,0 +1,68 @@
"""Provide utilities for reading prostar solar panels."""
from utilities import int_to_float16
from Channel import ModbusChannel
def charge_state(inp_state):
"""Map function for charge state."""
states = {
0: "Start",
1: "Night Check",
2: "Disconnect",
3: "Night",
4: "Fault",
5: "Bulk",
6: "Absorption",
7: "Float",
8: "Equalize"
}
if inp_state in range(0, 9):
return states[inp_state]
else:
return inp_state
def array_faults(inp_array_faults):
"""Form a string for the array_faults."""
fault_string = ""
faults = {
0: "Overcurrent Phase 1",
1: "FETs Shorted",
2: "Software Bug",
3: "Battery HVD (High Voltage Disconnect)",
4: "Array HVD (High Voltage Disconnect)",
5: "EEPROM Setting Edit (reset required)",
6: "RTS Shorted",
7: "RTS was valid now disconnected",
8: "Local temp. sensor failed",
9: "Battery LVD (Low Voltage Disconect)",
10: "DIP Switch Changed (excl. DIP 8)",
11: "Processor Supply Fault"
}
bit_string = ("0" * 16 + "{0:b}".format(inp_array_faults))[-16:]
for i in range(0, 12):
if int(bit_string[i]) == 1:
fault_string += faults[i] + ", "
if fault_string:
return fault_string[:-2]
else:
return "None"
CHANNELS = [
ModbusChannel("adc_ia", 17, "REAL", 0.5, 3600, transformFn=int_to_float16),
ModbusChannel("adc_vbterm", 18, "REAL", 0.5, 3600, transformFn=int_to_float16),
ModbusChannel("adc_va", 19, "REAL", 0.5, 3600, transformFn=int_to_float16),
ModbusChannel("adc_vl", 20, "REAL", 0.5, 3600, transformFn=int_to_float16),
ModbusChannel("adc_il", 22, "REAL", 0.5, 3600, transformFn=int_to_float16),
ModbusChannel("t_amb", 28, "REAL", 2.0, 3600, transformFn=int_to_float16),
ModbusChannel("vb_min_daily", 65, "REAL", 2.0, 3600, transformFn=int_to_float16),
ModbusChannel("vb_max_daily", 66, "REAL", 2.0, 3600, transformFn=int_to_float16),
ModbusChannel('charge_state', 33, "STRING", 1, 3600, transformFn=charge_state),
ModbusChannel('array_fault', 34, "STRING", 1, 3600, transformFn=array_faults)
]
NODEADDRESS = 21 # Default node address
BAUDRATE = 9600 # Default Baud Rate

View File

@@ -0,0 +1,48 @@
from utilities import int_to_float16
import struct
from math import copysign, frexp, isinf, isnan, trunc
NEGATIVE_INFINITY = b'\x00\xfc'
POSITIVE_INFINITY = b'\x00\x7c'
POSITIVE_ZERO = b'\x00\x00'
NEGATIVE_ZERO = b'\x00\x80'
# exp=2**5-1 and significand non-zero
EXAMPLE_NAN = struct.pack('<H', (0b11111 << 10) | 1)
test_values = [
(0b0000000000000000, 0.),
(0b1000000000000000, -0.),
(0b0011110000000000, 1),
(0b0011110000000001, 1.0009765625),
(0b1011110000000001, -1.0009765625),
(0b1100000000000000, -2),
(0b0100000000000000, 2),
(0b0111101111111111, 65504.),
(0b1111101111111111, -65504.),
(0b0000010000000000, 6.10352e-5),
(0b0000001111111111, 6.09756e-5), # subnormal
(0b0000000000000001, 5.96046e-8), # subnormal
(0b0111110000000000, float('infinity')),
(0b1111110000000000, float('-infinity')),
(0b0011010101010101, 0.333251953125)
]
def pad(string_to_pad, char_to_pad_with, num_chars):
"""Pad a string with characters."""
padded = char_to_pad_with * num_chars + string_to_pad
return padded[-16:]
def split_into_parts(inp_str):
"""Split the binary string."""
return inp_str[0:1] + " " + inp_str[1:6] + " " + inp_str[7:15]
for t in test_values:
expected = t[1]
actual = int_to_float16(t[0])
matches = expected == actual
inp = split_into_parts(pad("{0:b}".format(t[0]), "0", 16))
print("{}: {} == {} for int_to_float16({})".format(matches, actual, expected, inp))
print("----")

View File

@@ -14,13 +14,25 @@ def get_public_ip_address():
def int_to_float16(int_to_convert):
"""Convert integer into float16 representation."""
bin_rep = ('0' * 16 + '{0:b}'.format(int_to_convert))[-16:]
sign = 1
sign = 1.0
if int(bin_rep[0]) == 1:
sign = -1
exponent = int(bin_rep[1:6], 2)
fraction = int(bin_rep[7:17], 2)
sign = -1.0
exponent = float(int(bin_rep[1:6], 2))
if exponent == 30:
fraction = float(int("1" + bin_rep[7:17], 2))
else:
fraction = float(int(bin_rep[7:17], 2))
return sign * 2 ** (exponent - 15) * float("1.{}".format(fraction))
if exponent == float(0b00000):
return sign * 2 ** -14 * fraction / (2.0 ** 10.0)
elif exponent == float(0b11111):
if fraction == 0:
return sign * float("inf")
else:
return float("NaN")
else:
frac_part = 1.0 + fraction / (2.0 ** 10.0)
return sign * (2 ** (exponent - 15)) * frac_part
def degf_to_degc(temp_f):
@@ -32,6 +44,7 @@ def degc_to_degf(temp_c):
"""Convert deg C to deg F."""
return temp_c * 1.8 + 32.0
def reverse_map(value, map_):
"""Perform the opposite of mapping to an object."""
for x in map_: