Select Git revision
heater.cpp 11.11 KiB
#include <coincellhell/coincellhell.h>
#include <coincellhell/usbcommands.h>
#include <cstdint>
#include <ctime>
#include <memory>
#include <thread>
#include "heaters.h"
#include "startupfailure.h"
Heaters::Heaters(const std::vector<uint16_t> serials,
std::function<void(int device, uint16_t serial, int code)> deviceFailCbIn,
std::function<void(int heater, int device)> heaterFailCbIn, const std::set<uint16_t>& maskedSerials):
deviceFailCb(deviceFailCbIn), heaterFailCb(heaterFailCbIn)
{
Log(Log::INFO)<<"inializeing "<<serials.size()<<" coincellhell devices with "<<serials.size()*HEATERS_PER_DEVICE<<" heaters";
devices.resize(serials.size());
if(mock)
return;
for(size_t i = 0; i < serials.size(); ++i)
{
devices[i].mutex = std::make_unique<std::mutex>();
int ret = coincellhell_connect(&devices[i].device, serials[i]);
if(maskedSerials.contains(serials[i]))
{
if(ret != 0)
Log(Log::WARN)<<"Could not connect to masked heater device with the serial "<<serials[i];
devices[i].bad = true;
continue;
}
if(ret != 0)
{
throw startup_failure("Unable to connect to coincellhell with serial " + std::to_string(serials[i]));
}
Log(Log::INFO)<<"coincellhell device "<<i<<" with serial "<<serials[i]<<" reporting";
devices[i].serial = serials[i];
struct heater_state states[HEATERS_PER_DEVICE];
for(ssize_t heater = 0; heater < HEATERS_PER_DEVICE; ++heater)
{
ret = coincellhell_get_state(&devices[i].device, heater, &states[heater]);
if(ret != 0)
throw startup_failure("Unable to read state from coincellhell with serial " + std::to_string(serials[i]));
if(states[heater].fault)
{
throw startup_failure("Coincellhell with serial " + std::to_string(serials[i]) +
" reports its heater " + std::to_string(heater) + " is in a fault conditon: " +
coincellhell_string_for_fault(states[i].faultType));
}
ret = coincellhell_set_temperature(&devices[i].device, heater, 0);
if(ret != 0)
throw startup_failure("Unable to set temperature to coincellhell with serial " + std::to_string(serials[i]));
}
}
}
void Heaters::deviceError(int device, int reason)
{
devices[device].bad = reason != ERR_RECOVERED;
deviceFailCb(device, devices[device].serial, reason);
if(devices[device].bad)
recoverDevice(device);
}
bool Heaters::deviceBad(int device)
{
return devices[device].bad;
}
bool Heaters::heaterBad(int heater)
{
return devices[heater/HEATERS_PER_DEVICE].badHeater[heater];
}
void Heaters::recoverDevice(int device)
{
devices[device].mutex->lock();
coincellhell_disconnect(&devices[device].device);
int ret = coincellhell_connect(&devices[device].device, devices[device].serial);
if(ret == 0)
{
coincellhell_reset_bus(&devices[device].device);
coincellhell_disconnect(&devices[device].device);
sleep(10);
ret = coincellhell_connect(&devices[device].device, devices[device].serial);
if(watchdogs)
coincellhell_enable_watchdog(&devices[device].device);
}
devices[device].mutex->unlock();
if(ret == 0)
deviceError(device, ERR_RECOVERED);
}
void Heaters::reconnectDevices()
{
for(size_t i = 0; i < devices.size(); ++i)
{
struct device& device = devices[i];
if(device.bad)
{
device.mutex->lock();
coincellhell_disconnect(&device.device);
int ret = coincellhell_connect(&device.device, device.serial);
device.mutex->unlock();
if(ret == 0)
deviceError(i, ERR_RECOVERED);
}
}
}
bool Heaters::setMaxCurrent(uint8_t current)
{
bool ret = true;
for(int i = 0; i < count(); ++i)
{
std::pair<int, int> addr = getAddress(i);
struct device& device = devices[addr.first];
if(device.bad)
continue;
device.mutex->lock();
int retch = coincellhell_set_max_current(&device.device, addr.second, current);
device.mutex->unlock();
if(retch != 0)
{
ret = false;
deviceError(i, ERR_CONNECTION);
}
}
return ret;
}
bool Heaters::allReady()
{
if(mock)
return true;
bool ready = false;
int timeout = 1800;
Log(Log::INFO)<<"Wating for heaters to become ready";
while(!ready && --timeout > 0)
{
ready = true;
for(size_t i = 0; i < devices.size(); ++i)
{
if(deviceBad(i))
continue;
bool deviceReady = true;
for(size_t heater = 0; heater < HEATERS_PER_DEVICE; ++heater)
{
struct heater_state state;
devices[i].mutex->lock();
int ret = coincellhell_get_state(&devices[i].device, heater, &state);
devices[i].mutex->unlock();
if(ret != 0)
{
deviceError(i, ERR_CONNECTION);
continue;
}
if(state.enabled && !state.ready)
{
deviceReady = false;
break;
}
}
if(!deviceReady)
{
ready = false;
break;
}
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return ready;
}
bool Heaters::setHeaterEnabled(int heater, bool enabled)
{
if(mock)
return true;
int device = getAddress(heater).first;
int heaterId = getAddress(heater).second;
if(device >= devices.size() || devices[device].bad)
return false;
devices[device].mutex->lock();
int ret = coincellhell_set_enabled(&devices[device].device, heaterId, enabled);
devices[device].mutex->unlock();
if(ret != 0)
{
deviceError(device, ERR_CONNECTION);
if(deviceBad(device))
return false;
else
return setHeaterEnabled(heater, enabled);
}
return true;
}
bool Heaters::wait(int timeout)
{
if(mock)
return true;
time_t startTime = time(NULL);
for(size_t i = 0; i < devices.size(); ++i)
{
if(deviceBad(i))
continue;
while(true)
{
time_t currentTime = time(NULL);
if(currentTime - startTime > timeout && timeout > 0)
return false;
bool ready;
devices[i].mutex->lock();
int ret = coincellhell_check_ready(&devices[i].device, &ready);
devices[i].mutex->unlock();
if(ret != 0)
deviceError(i, ERR_CONNECTION);
if(ready || deviceBad(i))
break;
}
}
return true;
}
std::vector<bool> Heaters::getBadDevices()
{
std::vector<bool> badDevices(devices.size());
for(size_t i = 0; i < devices.size(); ++i)
badDevices[i] = devices[i].bad;
return badDevices;
}
bool Heaters::setSetpoint(int heater, float temperature)
{
int device = getAddress(heater).first;
int heaterId = getAddress(heater).second;
if(devices.size() <= device)
return false;
if(deviceBad(device))
return false;
devices[device].mutex->lock();
int ret = coincellhell_set_temperature(&devices[device].device, heaterId, temperature);
devices[device].mutex->unlock();
if(ret != 0)
{
deviceError(device, ERR_CONNECTION);
if(deviceBad(device))
{
return false;
}
else
{
return setSetpoint(heater, temperature);
}
}
return true;
}
bool Heaters::getSetpoint(int heater, float& temperature)
{
int device = getAddress(heater).first;
int heaterId = getAddress(heater).second;
if(devices.size() <= device)
return false;
if(deviceBad(device))
return false;
devices[device].mutex->lock();
int ret = coincellhell_get_temperature_setpoint(&devices[device].device, heaterId, &temperature);
devices[device].mutex->unlock();
if(ret != 0)
{
deviceError(device, ERR_CONNECTION);
if(deviceBad(device))
{
return false;
}
else
{
return getSetpoint(heater, temperature);
}
}
return true;
}
bool Heaters::getHeaterState(int heater, struct heater_state& state)
{
int device = getAddress(heater).first;
int heaterId = getAddress(heater).second;
if(devices.size() <= device)
return false;
if(deviceBad(device))
return false;
devices[device].mutex->lock();
int ret = coincellhell_get_state(&devices[device].device, heaterId, &state);
devices[device].mutex->unlock();
if(ret != 0)
{
deviceError(device, ERR_CONNECTION);
if(deviceBad(device))
{
return false;
}
else
{
return getHeaterState(heater, state);
}
}
return true;
}
bool Heaters::getHeaterEnabled(int heater)
{
struct heater_state state;
bool ret = getHeaterState(heater, state);
if(!ret)
return false;
else
return state.enabled;
}
bool Heaters::getTemperature(int heater, float& temperature)
{
int device = getAddress(heater).first;
int heaterId = getAddress(heater).second;
if(devices.size() <= device)
return false;
if(deviceBad(device))
return false;
devices[device].mutex->lock();
int ret = coincellhell_get_temperature(&devices[device].device, heaterId, TEMP_LOCATION_BOTH, &temperature);
devices[device].mutex->unlock();
if(ret != 0)
{
deviceError(device, ERR_CONNECTION);
if(deviceBad(device))
{
return false;
}
else
{
return getTemperature(heater, temperature);
}
}
return true;
}
bool Heaters::setRamp(int heater, time_t endTime, float temperature)
{
return false;
}
void Heaters::log(Log::Level level)
{
if(mock)
{
Log(level)<<"Nothing to log as we are running in mock mode";
return;
}
for(size_t i = 0; i < devices.size(); ++i)
{
bool bad = deviceBad(i);
struct heater_state states[HEATERS_PER_DEVICE];
if(!bad)
{
for(ssize_t heater = 0; heater < HEATERS_PER_DEVICE; ++heater)
{
devices[i].mutex->lock();
int ret = coincellhell_get_state(&devices[i].device, heater, &states[heater]);
devices[i].mutex->unlock();
if(ret != 0)
{
deviceError(i, ERR_CONNECTION);
if(deviceBad(i))
{
bad = true;
break;
}
else
{
heater = -1;
continue;
}
}
}
}
Log(level)<<"Coincellhell device "<<i<<": ";
Log(level)<<"\tbad: "<<(bad ? "True" : "False");
if(!bad)
{
for(size_t heater = 0; heater < HEATERS_PER_DEVICE; ++heater)
{
Log(level)<<"\theater: "<<i<<" cmd: "<<static_cast<int>(states[heater].dacCommand)
<<" sp: "<<states[heater].setpoint;
if(states[heater].fault && !devices[i].badHeater[heater])
{
devices[i].badHeater[heater] = true;
heaterFailCb(heater, i);
}
}
}
}
}
void Heaters::safetyCheck()
{
if(mock)
return;
for(size_t i = 0; i < devices.size(); ++i)
{
if(deviceBad(i))
continue;
for(ssize_t heater = 0; heater < HEATERS_PER_DEVICE; ++heater)
{
float frontTemperature;
float sideTemperature;
devices[i].mutex->lock();
int ret = coincellhell_get_temperature(&devices[i].device, heater, TEMP_LOCATION_FRONT, &frontTemperature);
devices[i].mutex->unlock();
if(ret != 0)
deviceError(i, ERR_CONNECTION);
if(deviceBad(i))
{
break;
}
else
{
continue;
--heater;
}
devices[i].mutex->lock();
ret = coincellhell_get_temperature(&devices[i].device, heater, TEMP_LOCATION_FRONT, &sideTemperature);
devices[i].mutex->unlock();
if(ret != 0)
deviceError(i, ERR_CONNECTION);
if(deviceBad(i))
{
break;
}
else
{
continue;
--heater;
}
if(frontTemperature > PANIC_TEMPERATURE || sideTemperature > PANIC_TEMPERATURE)
{
devices[i].mutex->lock();
coincellhell_set_enabled(&devices[i].device, heater, false);
devices[i].mutex->unlock();
deviceError(i, ERR_OVERTEMP);
}
}
}
}
std::pair<int, int> Heaters::getAddress(int heater) const
{
int device = heater / HEATERS_PER_DEVICE;
int heaterId = heater % HEATERS_PER_DEVICE;
return {device, heaterId};
}
void Heaters::enableWatchdogs()
{
watchdogs = true;
for(struct device& device : devices)
{
device.mutex->lock();
coincellhell_enable_watchdog(&device.device);
device.mutex->unlock();
}
}
int Heaters::count()
{
return devices.size()*HEATERS_PER_DEVICE;
}