Skip to content
Snippets Groups Projects
Select Git revision
  • d5a9aafd0317b8a8af42bc527bd61af566f6778e
  • master default
  • main protected
3 results

heater.cpp

Blame
  • user avatar
    Carl Philipp Klemm authored
    d5a9aafd
    History
    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;
    }