battery_profiling.lua
require("lib/strict")
local json = require("lib/dkjson")
local logger = require("modules/logger")
otii.clear()
local currlow = 300e-6
local timelow = 179.0
local currhigh = 4e-3
local timehigh = 1.0
local start_voltage = 3.0
local min_ocv_voltage = 2.0
local fourwire = false
local max_iterations = 20000
local cutoff_voltage = 0.6
local battery = { model = "Insert model here",
manufacturer = "Insert manufacture here",
voltage = start_voltage,
cutoffvoltage = min_ocv_voltage,
voltageunit = "V",
maxtemperature = 60,
worktemperature = 20,
mintemperature = -20,
temperatureunit = "°C",
size = "Insert size here",
sizeunit = "mm",
weight = 0,
weightunit = "g",
capacity = 0,
capacityunit = "mAh" }
local current_profile = {{
current = currhigh, duration = timehigh, }, {
current = currlow, duration = timelow, }}
local currdiff = currhigh - currlow
local recstart = os.date("%Y%m%d%H%M%S")
local recstop = {}
local table = {}
local actualocv = {}
local project = otii.get_active_project()
if not project then
project = otii.create_project()
assert(project, "cannot create project")
end
local devices = otii.get_devices("Arc")
assert(#devices > 0, "No available devices")
local arc = {}
local logs = {}
for i, _ in ipairs(devices) do
arc[i] = otii.open_device(devices[i].id)
assert(arc[i] ~= nil, "Error opening Arc device")
local logpath = string.format("%s/batteries/Log-%d-%s-%s.txt", otii.get_otii_dir(), i, devices[i].name, recstart)
logs[i] = io.open(logpath, "w")
assert(logs[i] ~= nil, "Cannot create file " .. logpath)
arc[i]:calibrate()
otii.msleep(1000)
arc[i]:set_power_regulation("current")
arc[i]:set_main_current(0)
arc[i]:set_max_current(5.0)
arc[i]:enable_4wire(fourwire)
arc[i]:enable_channel("mv", true)
arc[i]:enable_channel("mc", true)
arc[i]:set_battery_profile(current_profile)
arc[i]:enable_main(true)
otii.msleep(5)
local fourwiretext = string.format("%s: 4-wire mode is %s", devices[i].name, arc[i]:get_4wire())
otii.writeln(fourwiretext)
logs[i]:write(fourwiretext, "\n")
local profiletext = string.format("Discharge profile %.3f A for %.3f s, %.3f A for %.3f s", currhigh, timehigh, currlow, timelow)
logs[i]:write(profiletext, "\n")
table[i] = {}
end
function cleanup()
project:stop()
project:enable_main_power(false)
for i, _ in ipairs(devices) do
arc[i]:enable_battery_profiling(false)
arc[i]:set_main_current(0)
arc[i]:set_power_regulation("voltage")
arc[i]:close()
if recstop[i] == nil then recstop[i] = os.date("%Y%m%d%H%M%S") end
local curlowentry = { current = currlow * 1000, time = timelow }
local curhighentry = { current = currhigh * 1000, time = timehigh }
local dischargeprofile = { low = curlowentry, high = curhighentry }
local dischargetable = {current = 0, currentunit = "mA", dischargeprofile = {dischargeprofile}, table = table[i], starttime = recstart, stoptime = recstop[i]}
battery.capacity = math.floor(100*table[i][#table[i]].capacity+0.5)/100
local output = { battery = battery, dischargetables = {dischargetable} };
otii.writeln(json.encode(output, { indent = true }));
local battery_profile_path = string.format("%s/batteries/dischargeprofiles/Batt-profile-%s-%dmA-%d-%s-%s.json", otii.get_otii_dir(), battery.model, math.floor(currhigh*1e3), i, devices[i].name, recstart)
local file = io.open(battery_profile_path, "w")
assert(file ~= nil, "Cannot create file " .. battery_profile_path)
file:write(json.encode(output, {indent = true}))
file:close()
logs[i]:close()
end
project:save(string.format("%s/batteries/dischargeprofiles/%s-%dmA-%s.otii", otii.get_otii_dir(),battery.model, math.floor(currhigh*1e3), recstart))
end
otii.on_stop(cleanup)
otii.writeln("Otii Arc battery discharge profiling")
project:start()
for i, _ in ipairs(devices) do
actualocv[i] = arc[i]:get_value("mv")
local ocvtext = string.format("%s: Measured initial OCV=%.3f V", devices[i].name, actualocv[i])
otii.writeln(ocvtext)
logs[i]:write(ocvtext, "\n")
logs[i]:write("VoltLowLoad, VoltHighLoad, OCV, IntRes, mAh, CalcmAh\n")
arc[i]:enable_battery_profiling(true)
end
local iteration = {}
local laststepvalue = {}
local voltageok = {}
for i, _ in ipairs(devices) do
iteration[i] = -1
laststepvalue[i] = 0
voltageok[i] = 1
end
while true do
local alldone = 1
for i, _ in ipairs(devices) do
if voltageok[i] == 1 then
logs[i]:flush()
alldone = 0
local battery_data = arc[i]:wait_for_battery_data(600)
if battery_data ~= nil then
if battery_data.iteration ~= iteration[i] then
if battery_data.iteration >= max_iterations then
alldone = 1
break
end
iteration[i] = battery_data.iteration
otii.writeln(string.format("Arc %d Iteration %d", i, iteration[i] + 1))
end
otii.writeln("step" .. battery_data.step .. " " .. battery_data.voltage .. " " .. battery_data.discharge)
if battery_data.step == 0 then
laststepvalue[i] = battery_data.voltage
else
local voltdiff = battery_data.voltage - laststepvalue[i]
local intres = voltdiff / currdiff
local ocv = battery_data.voltage + intres * currlow
if ocv > actualocv[i] then
otii.writeln(string.format("%s: Clamping calculated OCV to initial measured OCV (%.3f V > %.3f V)", devices[i].name, ocv, actualocv[i]))
ocv = actualocv[i]
end
local mAh = battery_data.discharge * 1000 / 3600
local calcmAh = tonumber(string.format("%.3f", (iteration[i] + 1) * 1000 * (currhigh*timehigh + currlow*timelow) / 60 / 60))
otii.writeln(string.format("%s: OCV=%.3f V Res=%.3f Ohm (%.3f %.3f) %.3fmAh %.3f", devices[i].name, ocv, intres, battery_data.voltage, laststepvalue[i], mAh, calcmAh))
logs[i]:write(string.format("%.3f, %.3f, %.3f, %.3f, %.3f, %.3f\n", battery_data.voltage, laststepvalue[i], ocv, intres, mAh, calcmAh))
local ltable = table[i]
ltable[#ltable + 1] = {
voltage = tonumber(string.format("%.3f", ocv)),
resistance = tonumber(string.format("%.3f", intres)),
capacity = mAh
}
if ocv < min_ocv_voltage then
otii.writeln("OCV (" .. tostring(ocv) .. ") below min voltage (" .. tostring(min_ocv_voltage) .. "), stopping measurement")
voltageok[i] = 0
end
end
if battery_data.voltage < cutoff_voltage then
otii.writeln("Voltage (" .. tostring(battery_data.voltage) .. ") below cut-off voltage (" .. tostring(cutoff_voltage) .. "), stopping measurement")
voltageok[i] = 0
end
if voltageok[i] == 0 then
arc[i]:enable_battery_profiling(false)
arc[i]:set_main_current(0)
recstop[i] = os.date("%Y%m%d%H%M%S")
end
end
end
end
if alldone == 1 then
break
end
end
cleanup()