Completes correct function for converting 2 bytes to Float16
This commit is contained in:
111
python-driver/lambda.js
Normal file
111
python-driver/lambda.js
Normal 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);
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
|
||||
68
python-driver/prostarsolar_utils.py
Normal file
68
python-driver/prostarsolar_utils.py
Normal 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
|
||||
48
python-driver/test_utilities.py
Normal file
48
python-driver/test_utilities.py
Normal 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("----")
|
||||
@@ -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_:
|
||||
|
||||
Reference in New Issue
Block a user