thermferm/rdconfig.c

Thu, 25 Apr 2024 14:26:47 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Thu, 25 Apr 2024 14:26:47 +0200
changeset 708
13555c27b592
parent 702
d77891f8915d
child 714
24749c296a50
permissions
-rw-r--r--

Version 0.9.19a6. Fixes after a short trial on the production controller. Fixed json for alternate beer termperature sensor. Fixed division by 1000 for the room temperature and humidity values. The dropdown list for devices shows the address instead of description in the list.

/*****************************************************************************
 * Copyright (C) 2014-2024
 *   
 * Michiel Broek <mbroek at mbse dot eu>
 *
 * This file is part of the mbsePi-apps
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * mbsePi-apps is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ThermFerm; see the file COPYING.  If not, write to the Free
 * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *****************************************************************************/

#include "rdconfig.h"
#include "thermferm.h"
#include "pid.h"
#include "futil.h"
#include "xutil.h"

int			debug = FALSE;
int			foreground = FALSE;
sys_config		Config;			/* System configuration		*/
extern pthread_mutex_t  mutexes[5];

#define MY_ENCODING "utf-8"

const char      UNITMODE[5][8]	= { "OFF", "NONE", "FRIDGE", "BEER", "PROFILE" };
const char	UNITSTAGE[4][12] = { "PRIMARY", "SECONDARY", "TERTIARY", "CARBONATION" };
const char	PROFSTATE[5][6]	= { "OFF", "PAUSE", "RUN", "DONE", "ABORT" };
const char	DEVTYPE[8][6] = { "NA", "W1", "GPIO", "RC433", "DHT", "I2C", "SPI", "SIM" };
const char	DEVPRESENT[4][6] = { "UNDEF", "NO", "YES", "ERROR" };
const char	DEVDIR[7][11] = { "UNDEF", "IN_BIN", "OUT_BIN", "IN_ANALOG", "OUT_ANALOG", "OUT_PWM", "INTERN" };
const char	PIDMODE[3][5] = { "NONE", "AUTO", "BOO" };



int parseSteps(xmlDocPtr doc, xmlNodePtr cur, prof_step **step);

void killconfig(void)
{
    units_list		*tmp2, *oldtmp2;
    prof_step		*step, *oldstep;
    devices_list	*device, *olddev;
#ifdef USE_SIMULATOR
    simulator_list	*simulator, *oldsim;
#endif

    if (Config.name)
	free(Config.name);
    Config.name = NULL;

    Config.server_port = 6554;
    Config.websocket_port = 8020;
    if (Config.temp_uuid)
	free(Config.temp_uuid);
    if (Config.hum_uuid)
	free(Config.hum_uuid);
    Config.temp_hum_idx = 0;
    Config.temp_uuid = Config.hum_uuid = NULL;
    Config.temp_value = 20000;
    Config.temp_state = Config.hum_state = 1;	// missing
    Config.hum_value = 50000;
    if (Config.mqtt_host)
	free(Config.mqtt_host);
    Config.mqtt_host = NULL;
    Config.mqtt_port = 1883;
    if (Config.mqtt_username)
	free(Config.mqtt_username);
    Config.mqtt_username = NULL;
    if (Config.mqtt_password)
	free(Config.mqtt_password);
    Config.mqtt_password = NULL;
    if (Config.uuid)
	free(Config.uuid);
    Config.uuid = NULL;
 
    for (tmp2 = Config.units; tmp2; tmp2 = oldtmp2) {
	oldtmp2 = tmp2->next;
	if (tmp2->uuid)
	    free(tmp2->uuid);
	if (tmp2->product_uuid)
	    free(tmp2->product_uuid);
	if (tmp2->product_code)
	    free(tmp2->product_code);
	if (tmp2->product_name)
	    free(tmp2->product_name);
	if (tmp2->air_address)
	    free(tmp2->air_address);
	if (tmp2->beer_address)
	    free(tmp2->beer_address);
	if (tmp2->beer_address2)
	    free(tmp2->beer_address2);
	if (tmp2->chiller_address)
	    free(tmp2->chiller_address);
	if (tmp2->heater_address)
	    free(tmp2->heater_address);
	if (tmp2->cooler_address)
	    free(tmp2->cooler_address);
	if (tmp2->fan_address)
	    free(tmp2->fan_address);
	if (tmp2->light_address)
	    free(tmp2->light_address);
	if (tmp2->door_address)
	    free(tmp2->door_address);
	if (tmp2->psu_address)
	    free(tmp2->psu_address);
	if (tmp2->profile_uuid)
	    free(tmp2->profile_uuid);
	if (tmp2->profile_name)
	    free(tmp2->profile_name);
	if (tmp2->profile_steps) {
	    for (step = tmp2->profile_steps; step; step = oldstep) {
		if (step->name)
		    free(step->name);
		oldstep = step->next;
		free(step);
	    }
	}
	if (tmp2->PID_cool)
	    free(tmp2->PID_cool);
	if (tmp2->PID_heat)
	    free(tmp2->PID_heat);
	free(tmp2);
    }
    Config.units = NULL;

    for (device = Config.devices; device; device = olddev) {
	olddev = device->next;
	if (device->uuid)
	    free(device->uuid);
	if (device->address)
	    free(device->address);
	if (device->description)
	    free(device->description);
	if (device->comment)
	    free(device->comment);
	free(device);
    }
    Config.devices = NULL;

#ifdef USE_SIMULATOR
    for (simulator = Config.simulators; simulator; simulator = oldsim) {
	oldsim = simulator->next;
	if (simulator->uuid)
	    free(simulator->uuid);
	if (simulator->name)
	    free(simulator->name);
	free(simulator);
    }
    Config.simulators = NULL;
#endif

    Config.lcd_cols = 16;
    Config.lcd_rows = 2;
}



int do_wrconfig(void);
int do_wrconfig(void)
{
    int			rc = 0;
    FILE		*fp;
    char		*mypath = NULL;
    xmlTextWriterPtr	writer;
    xmlBufferPtr	buf;
    units_list		*tmp3;
    prof_step		*tmp5;
    devices_list	*device;
#ifdef USE_SIMULATOR
    simulator_list	*simulator;
#endif

    /* 
     * Create a new XML buffer, to which the XML document will be written
     */
    if ((buf = xmlBufferCreate()) == NULL) {
	syslog(LOG_NOTICE, "wrconfig: error creating the xml buffer");
	return 1;
    }

    /* 
     * Create a new XmlWriter for memory, with no compression.
     */
    if ((writer = xmlNewTextWriterMemory(buf, 0)) == NULL) {
	syslog(LOG_NOTICE, "wrconfig: error creating the xml writer");
	return 1;
    }

    /*
     * Use indentation instead of one long line
     */
    if ((rc = xmlTextWriterSetIndent(writer, 2)) < 0) {
	syslog(LOG_NOTICE, "wrconfig: error setting Indent");
	return 1;
    }

    /* 
     * Start the document with the xml default for the version,
     * encoding UTF-8 and the default for the standalone declaration. 
     */
    xmlTextWriterStartDocument(writer, NULL, MY_ENCODING, NULL);

    /* 
     * Start an element named "THERMFERM". Since thist is the first
     * element, this will be the root element of the document.
     */
    xmlTextWriterStartElement(writer, BAD_CAST "THERMFERM");

    xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", Config.name);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "UUID", "%s", Config.uuid);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "SERVER_PORT", "%d", Config.server_port);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "WS_PORT", "%d", Config.websocket_port);

    xmlTextWriterWriteFormatElement(writer, BAD_CAST "TEMP_UUID", "%s", Config.temp_uuid);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "TEMP_STATE", "%d", Config.temp_state);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "TEMP_VALUE", "%d", Config.temp_value);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "HUM_UUID", "%s", Config.hum_uuid);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "HUM_STATE", "%d", Config.hum_state);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "HUM_VALUE", "%d", Config.hum_value);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "TEMP_HUM_IDX", "%d", Config.temp_hum_idx);

    xmlTextWriterWriteFormatElement(writer, BAD_CAST "NEXT_UNIT", "%d", Config.next_unit);

    xmlTextWriterWriteFormatElement(writer, BAD_CAST "MQTT_HOST", "%s", Config.mqtt_host);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "MQTT_PORT", "%d", Config.mqtt_port);
    if (Config.mqtt_username && Config.mqtt_password) {
    	xmlTextWriterWriteFormatElement(writer, BAD_CAST "MQTT_USER", "%s", Config.mqtt_username);
    	xmlTextWriterWriteFormatElement(writer, BAD_CAST "MQTT_PASS", "%s", Config.mqtt_password);
    }

    /* 
     * Start an element named "LCDS" as child of THERMFERM.
     */
    xmlTextWriterStartElement(writer, BAD_CAST "LCDS");
    /*
     * Start one LCD. It is possible to connect 7 LCD displays on the i2c bus.
     * However this program doesn't use more then one yet.
     */
    xmlTextWriterStartElement(writer, BAD_CAST "LCD");
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "ADDRESS", "0x%x", Config.lcd_address);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "COLUMNS", "%d", Config.lcd_cols);
    xmlTextWriterWriteFormatElement(writer, BAD_CAST "ROWS", "%d", Config.lcd_rows);
    xmlTextWriterEndElement(writer);	// close LCD
    xmlTextWriterEndElement(writer);	// close LCDS

    /*
     * Fermenter units
     */
    if (Config.units) {
	xmlTextWriterStartElement(writer, BAD_CAST "FERMENTERS");
	for (tmp3 = Config.units; tmp3; tmp3 = tmp3->next) {
	    xmlTextWriterStartElement(writer, BAD_CAST "FERMENTER");
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "UUID", "%s", tmp3->uuid);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "PRODUCT_UUID", "%s", tmp3->product_uuid);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "PRODUCT_CODE", "%s", tmp3->product_code);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "PRODUCT_NAME", "%s", tmp3->product_name);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "ALIAS", "%s", tmp3->alias);

	    if (tmp3->air_address) {
	    	xmlTextWriterWriteFormatElement(writer, BAD_CAST "AIR_ADDRESS", "%s", tmp3->air_address);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "AIR_STATE", "%d", tmp3->air_state);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "AIR_TEMPERATURE", "%d", tmp3->air_temperature);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "AIR_IDX", "%d", tmp3->air_idx);
	    }
	    if (tmp3->beer_address) {
	    	xmlTextWriterWriteFormatElement(writer, BAD_CAST "BEER_ADDRESS", "%s", tmp3->beer_address);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "BEER_STATE", "%d", tmp3->beer_state);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "BEER_TEMPERATURE", "%d", tmp3->beer_temperature);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "BEER_IDX", "%d", tmp3->beer_idx);
		if (tmp3->beer_address2) {
		    xmlTextWriterWriteFormatElement(writer, BAD_CAST "BEER_ADDRESS2", "%s", tmp3->beer_address2);
		}
	    }
	    if (tmp3->chiller_address) {
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "CHILLER_ADDRESS", "%s", tmp3->chiller_address);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "CHILLER_STATE", "%d", tmp3->chiller_state);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "CHILLER_TEMPERATURE", "%d", tmp3->chiller_temperature);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "CHILLER_IDX", "%d", tmp3->chiller_idx);
	    }
	    if (tmp3->heater_address) {
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "HEATER_ADDRESS", "%s", tmp3->heater_address);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "HEATER_STATE", "%d", tmp3->heater_state);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "HEATER_DELAY", "%d", tmp3->heater_delay);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "HEATER_USAGE", "%d", tmp3->heater_usage);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "HEATER_IDX", "%d", tmp3->heater_idx);
	    }
	    if (tmp3->cooler_address) {
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLER_ADDRESS", "%s", tmp3->cooler_address);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLER_STATE", "%d", tmp3->cooler_state);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLER_DELAY", "%d", tmp3->cooler_delay);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLER_USAGE", "%d", tmp3->cooler_usage);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLER_IDX", "%d", tmp3->cooler_idx);
	    }
	    if (tmp3->fan_address) {
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "FAN_ADDRESS", "%s", tmp3->fan_address);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "FAN_STATE", "%d", tmp3->fan_state);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "FAN_DELAY", "%d", tmp3->fan_delay);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "FAN_USAGE", "%d", tmp3->fan_usage);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "FAN_IDX", "%d", tmp3->fan_idx);
	    }
	    if (tmp3->light_address) {
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "LIGHT_ADDRESS", "%s", tmp3->light_address);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "LIGHT_STATE", "%d", tmp3->light_state);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "LIGHT_DELAY", "%d", tmp3->light_delay);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "LIGHT_USAGE", "%d", tmp3->light_usage);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "LIGHT_IDX", "%d", tmp3->light_idx);
	    }
	    if (tmp3->door_address) {
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "DOOR_ADDRESS", "%s", tmp3->door_address);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "DOOR_STATE", "%d", tmp3->door_state);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "DOOR_IDX", "%d", tmp3->door_idx);
	    }
	    if (tmp3->psu_address) {
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PSU_ADDRESS", "%s", tmp3->psu_address);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PSU_STATE", "%d", tmp3->psu_state);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PSU_IDX", "%d", tmp3->psu_idx);
	    }
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "MODE", "%s", UNITMODE[tmp3->mode] );
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "STAGE", "%s", UNITSTAGE[tmp3->stage] );
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "BEER_SET_LO", "%.1f", tmp3->beer_set_lo);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "BEER_SET_HI", "%.1f", tmp3->beer_set_hi);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "FRIDGE_SET_LO", "%.1f", tmp3->fridge_set_lo);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "FRIDGE_SET_HI", "%.1f", tmp3->fridge_set_hi);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "TEMP_SET_MIN", "%.1f", tmp3->temp_set_min);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "TEMP_SET_MAX", "%.1f", tmp3->temp_set_max);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "YEAST_LO", "%.1f", tmp3->yeast_lo);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "YEAST_HI", "%.1f", tmp3->yeast_hi);

	    if (tmp3->profile_uuid) {
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROFILE_UUID", "%s", tmp3->profile_uuid);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROFILE_NAME", "%s", tmp3->profile_name);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROFILE_INITTEMP_LO", "%.1f", tmp3->profile_inittemp_lo);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROFILE_INITTEMP_HI", "%.1f", tmp3->profile_inittemp_hi);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROFILE_FRIDGE_MODE", "%d", tmp3->profile_fridge_mode);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROFILE_DURATION", "%d", tmp3->profile_duration);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROFILE_TOTALSTEPS", "%d", tmp3->profile_totalsteps);
		if (tmp3->profile_steps) {
		    xmlTextWriterStartElement(writer, BAD_CAST "PROFILE_STEPS");
		    for (tmp5 = tmp3->profile_steps; tmp5; tmp5 = tmp5->next) {
			xmlTextWriterStartElement(writer, BAD_CAST "PROFILE_STEP");
			if (tmp5->name)
			     xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", tmp5->name);
			xmlTextWriterWriteFormatElement(writer, BAD_CAST "RESTTIME", "%d", tmp5->resttime);
			xmlTextWriterWriteFormatElement(writer, BAD_CAST "STEPTIME", "%d", tmp5->steptime);
			xmlTextWriterWriteFormatElement(writer, BAD_CAST "TARGET_LO", "%.1f", tmp5->target_lo);
			xmlTextWriterWriteFormatElement(writer, BAD_CAST "TARGET_HI", "%.1f", tmp5->target_hi);
			xmlTextWriterWriteFormatElement(writer, BAD_CAST "FRIDGE_MODE", "%d", tmp5->fridge_mode);
			xmlTextWriterEndElement(writer);    // close PROFILE_STEP
		    }
		    xmlTextWriterEndElement(writer);        // close PROFILE_STEPS
		}
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROF_STARTED", "%d", (unsigned int)tmp3->prof_started);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROF_PAUSED", "%d", (unsigned int)tmp3->prof_paused);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROF_STATE", "%s", PROFSTATE[tmp3->prof_state] );
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROF_PEAK_ABS", "%.3f", tmp3->prof_peak_abs);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROF_PEAK_REL", "%.3f", tmp3->prof_peak_rel);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PROF_PRIMARY_DONE", "%d", (unsigned int)tmp3->prof_primary_done);
	    }
	    if (tmp3->PID_cool) {
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_IMAX", "%.2f", tmp3->PID_cool->iMax);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_IGAIN", "%.3f", tmp3->PID_cool->iGain);
	        xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_PGAIN", "%.3f", tmp3->PID_cool->pGain);
	        xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_DGAIN", "%.3f", tmp3->PID_cool->dGain);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_IDLERANGE", "%.2f", tmp3->PID_cool->idleRange);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_INPUT", "%.2f", tmp3->PID_cool->Input);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_ERR", "%.2f", tmp3->PID_cool->Err);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_ISTATE", "%.2f", tmp3->PID_cool->iState);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_SETP", "%.2f", tmp3->PID_cool->SetP);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_OUTP", "%.2f", tmp3->PID_cool->OutP);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_MODE", "%s", PIDMODE[tmp3->PID_cool->Mode]);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDC_TYPE", "COOL");
	    }
	    if (tmp3->PID_heat) {
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_IMAX", "%.2f", tmp3->PID_heat->iMax);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_IGAIN", "%.3f", tmp3->PID_heat->iGain);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_PGAIN", "%.3f", tmp3->PID_heat->pGain);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_DGAIN", "%.3f", tmp3->PID_heat->dGain);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_IDLERANGE", "%.2f", tmp3->PID_heat->idleRange);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_INPUT", "%.2f", tmp3->PID_heat->Input);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_ERR", "%.2f", tmp3->PID_heat->Err);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_ISTATE", "%.2f", tmp3->PID_heat->iState);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_SETP", "%.2f", tmp3->PID_heat->SetP);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_OUTP", "%.2f", tmp3->PID_heat->OutP);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_MODE", "%s", PIDMODE[tmp3->PID_heat->Mode]);
		xmlTextWriterWriteFormatElement(writer, BAD_CAST "PIDH_TYPE", "HEAT");
	    }
	    xmlTextWriterEndElement(writer);	// close FERMENTER
	}
	xmlTextWriterEndElement(writer);	// close FERMENTERS
    }

    if (Config.devices) {
	xmlTextWriterStartElement(writer, BAD_CAST "DEVICES");
	pthread_mutex_lock(&mutexes[LOCK_DEVICES]);
	for (device = Config.devices; device; device = device->next) {
	    xmlTextWriterStartElement(writer, BAD_CAST "DEVICE");
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "UUID", "%s", device->uuid);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "TYPE", "%s", DEVTYPE[device->type]);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "DIRECTION", "%s", DEVDIR[device->direction]);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "VALUE", "%d", device->value);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "OFFSET", "%d", device->offset);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "PRESENT", "%s", DEVPRESENT[device->present]);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "ADDRESS", "%s", device->address);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "SUBDEVICE", "%d", device->subdevice);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "GPIOPIN", "%d", device->gpiopin);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "DESCRIPTION", "%s", device->description);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "INUSE", "%d", device->inuse);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "COMMENT", "%s", device->comment);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "TIMESTAMP", "%d", (int)device->timestamp);
	    xmlTextWriterEndElement(writer);	// close DEVICE
	}
	pthread_mutex_unlock(&mutexes[LOCK_DEVICES]);
	xmlTextWriterEndElement(writer);	// close DEVICES
    }

#ifdef USE_SIMULATOR
    if (Config.simulators) {
	xmlTextWriterStartElement(writer, BAD_CAST "SIMULATORS");
	for (simulator = Config.simulators; simulator; simulator = simulator->next) {
    	    xmlTextWriterStartElement(writer, BAD_CAST "SIMULATOR");
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "UUID", "%s", simulator->uuid);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", simulator->name);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "VOLUME_AIR", "%d", simulator->volume_air);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "VOLUME_BEER", "%d", simulator->volume_beer);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "ROOM_TEMPERATURE", "%.1f", simulator->room_temperature);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "ROOM_HUMIDITY", "%.1f", simulator->room_humidity);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "AIR_TEMPERATURE", "%f", simulator->air_temperature);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "BEER_TEMPERATURE", "%f", simulator->beer_temperature);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "CHILLER_TEMPERATURE", "%f", simulator->chiller_temperature);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLER_TEMP", "%f", simulator->cooler_temp);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLER_TIME", "%d", simulator->cooler_time);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLER_SIZE", "%.3f", simulator->cooler_size);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "HEATER_TEMP", "%f", simulator->heater_temp);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "HEATER_TIME", "%d", simulator->heater_time);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "HEATER_SIZE", "%.3f", simulator->heater_size);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "HEATER_STATE", "%d", simulator->heater_state);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLER_STATE", "%d", simulator->cooler_state);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "FRIGO_ISOLATION", "%.3f", simulator->frigo_isolation);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "S_YEAST_HEAT", "%f", simulator->s_yeast_heat);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "S_YEAST_STARTED", "%d", (int)simulator->s_yeast_started);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "S_COOL_TEMP", "%f", simulator->s_cool_temp);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "S_HEAT_TEMP", "%f", simulator->s_heat_temp);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "S_COOL_CHANGED", "%d", (int)simulator->s_cool_changed);
	    xmlTextWriterWriteFormatElement(writer, BAD_CAST "S_HEAT_CHANGED", "%d", (int)simulator->s_heat_changed);
    	    xmlTextWriterEndElement(writer);
	}
	xmlTextWriterEndElement(writer);
    }
#endif

    /*
     * All done, close any open elements
     */
    if ((rc = xmlTextWriterEndDocument(writer)) < 0) {
	syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterEndDocument");
	return 1;
    }
    xmlFreeTextWriter(writer);

    /*
     * Now write the XML configuration
     */
    if (getenv((char *)"USER") == NULL) {
	mypath = xstrcpy((char *)"/root");
    } else {
	mypath = xstrcpy(getenv((char *)"HOME"));
    }
    mypath = xstrcat(mypath, (char *)"/.thermferm/etc/");
    mkdirs(mypath, 0755);
    mypath = xstrcat(mypath, (char *)"thermferm.xml");

    if (debug)
	syslog(LOG_NOTICE, "Writing %s", mypath);

    if ((fp = fopen(mypath, "w")) == NULL) {
	syslog(LOG_NOTICE, "could not rewrite %s", mypath);
	free(mypath);
	return 1;
    }
    free(mypath);

    fprintf(fp, "%s", (const char *) buf->content);
    fclose(fp);
    xmlBufferFree(buf);

    return 0;
}



int wrconfig(void)
{
    int rc = do_wrconfig();
    syslog(LOG_NOTICE, "Rewritten configuration, rc=%d", rc);
    return rc;
}



/*
 * Parse one LCD display
 */
int parseLCD(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar	*key;
    int		ival;

    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COLUMNS"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
	        Config.lcd_cols = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ROWS"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		Config.lcd_rows = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ADDRESS"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%x", &ival) == 1)
		Config.lcd_address = ival;
	    xmlFree(key);
	}
	cur = cur->next;
    }

    return 0;
}



int parseLCDs(xmlDocPtr doc, xmlNodePtr cur)
{
    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"LCD"))) {
	    parseLCD(doc, cur);
	}
	cur = cur->next;
    }
    return 0;
}



/*
 * Parse a fermenter unit
 */
int parseUnit(xmlDocPtr doc, xmlNodePtr cur/* , int number */)
{
    xmlChar     *key;
    int         i, ival;
    float	val;
    units_list	*unit, *tmp;

    unit = (units_list *)malloc(sizeof(units_list));
    unit->next = NULL;
    unit->uuid = unit->product_uuid = unit->product_code = unit->product_name = unit->event_msg = \
		 unit->alias = unit->air_address = unit->beer_address = unit->beer_address2 = unit->chiller_address = \
		 unit->heater_address = unit->cooler_address = unit->fan_address = unit->door_address = \
		 unit->light_address = unit->psu_address = unit->profile_uuid = unit->profile_name  = NULL;
    unit->prof_peak_abs = unit->prof_peak_rel = 0.0;
    unit->air_temperature = unit->beer_temperature = unit->chiller_temperature = unit->beer_set_lo = unit->beer_set_hi = \
			    unit->fridge_set_lo = unit->fridge_set_hi = unit->profile_inittemp_lo = unit->profile_inittemp_hi = 20.0;
    unit->air_state = unit->beer_state = unit->chiller_state = DEVPRESENT_NO; // missing
    unit->heater_state = unit->cooler_state = unit->fan_state = unit->door_state = \
			 unit->light_state = unit->light_timer = unit->psu_state = unit->mode = unit->prof_state = unit->stage = 0;
    unit->air_idx = unit->beer_idx = unit->chiller_idx = unit->heater_idx = unit->cooler_idx = unit->fan_idx = \
		    unit->door_idx = unit->light_idx = unit->psu_idx = unit->profile_fridge_mode = \
		    unit->profile_duration = unit->profile_totalsteps = 0;
    unit->profile_steps = NULL;
    unit->heater_delay = unit->cooler_delay = unit->fan_delay = 20;	/* 5 minutes delay */
    unit->light_delay = 1;						/* 15 seconds delay	*/
    unit->heater_wait = unit->cooler_wait = unit->fan_wait = unit->light_wait = 0;
    unit->heater_usage = unit->cooler_usage = unit->fan_usage = unit->light_usage = 0;
    unit->temp_set_min = 1.0;
    unit->temp_set_max = 45.0;
    unit->yeast_lo = 12.0;
    unit->yeast_hi = 24.0;
    unit->prof_started = unit->prof_paused = unit->prof_primary_done = (time_t)0;
    unit->prof_percent = 0;
    unit->PID_cool = (pid_var *)malloc(sizeof(pid_var));
    unit->PID_heat = (pid_var *)malloc(sizeof(pid_var));
    InitPID(unit->PID_cool, PID_TYPE_COOL);
    InitPID(unit->PID_heat, PID_TYPE_HEAT);

    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"UUID"))) {
	    unit->uuid = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRODUCT_UUID"))) {
	    unit->product_uuid = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRODUCT_CODE"))) {
	    unit->product_code = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRODUCT_NAME"))) {
	    unit->product_name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ALIAS"))) {
	    unit->alias = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AIR_ADDRESS"))) {
	    unit->air_address = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AIR_STATE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->air_state = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AIR_IDX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->air_idx = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AIR_TEMPERATURE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->air_temperature = ival;
	    xmlFree(key);                           
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEER_ADDRESS2"))) {
            unit->beer_address2 = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
        } else if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEER_ADDRESS"))) {
	    unit->beer_address = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEER_STATE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->beer_state = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEER_IDX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->beer_idx = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEER_TEMPERATURE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->beer_temperature = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CHILLER_ADDRESS"))) {
	    unit->chiller_address = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CHILLER_STATE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->chiller_state = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CHILLER_IDX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->chiller_idx = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CHILLER_TEMPERATURE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->chiller_temperature = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HEATER_ADDRESS"))) {
	    unit->heater_address = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HEATER_DELAY"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->heater_delay = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HEATER_IDX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->heater_idx = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HEATER_USAGE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->heater_usage = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COOLER_ADDRESS"))) {
	    unit->cooler_address = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COOLER_DELAY"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->cooler_delay = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COOLER_IDX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->cooler_idx = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COOLER_USAGE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->cooler_usage = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FAN_ADDRESS"))) {
	    unit->fan_address = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FAN_DELAY"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->fan_delay = ival;
	    xmlFree(key);                           
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FAN_IDX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->fan_idx = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FAN_USAGE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->fan_usage = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"LIGHT_ADDRESS"))) {
	    unit->light_address = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"LIGHT_DELAY"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->light_delay = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"LIGHT_IDX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->light_idx = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"LIGHT_USAGE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->light_usage = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"DOOR_ADDRESS"))) {
	    unit->door_address = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"DOOR_IDX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->door_idx = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PSU_ADDRESS"))) {
	    unit->psu_address = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PSU_IDX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->psu_idx = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MODE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    for (i = 0; i < 5; i++) {
	    	if (! xmlStrcmp(key, (const xmlChar *)UNITMODE[i])) {
	   	    unit->mode = i;
		    break;
		}
	    }
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STAGE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    for (i = 0; i < 4; i++) {
		if (! xmlStrcmp(key, (const xmlChar *)UNITSTAGE[i])) {
		    unit->stage = i;
		    break;
		}
	    }
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEER_SET"))) {	// Remove in 2020.
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->beer_set_lo = unit->beer_set_hi = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEER_SET_LO"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->beer_set_lo = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEER_SET_HI"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->beer_set_hi = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FRIDGE_SET"))) {	// Remove in 2020
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->fridge_set_lo = unit->fridge_set_hi = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FRIDGE_SET_LO"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->fridge_set_lo = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FRIDGE_SET_HI"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->fridge_set_hi = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TEMP_SET_MIN"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->temp_set_min = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TEMP_SET_MAX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->temp_set_max = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"YEAST_LO"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->yeast_lo = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"YEAST_HI"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->yeast_hi = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_IMAX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_cool->iMax = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_IDLERANGE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_cool->idleRange = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_IGAIN"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_cool->iGain = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_PGAIN"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_cool->pGain = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_DGAIN"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_cool->dGain = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_INPUT"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_cool->Input = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_ERR"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_cool->Err = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_ISTATE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_cool->iState = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_SETP"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_cool->SetP = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_OUTP"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_cool->OutP = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDC_MODE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    for (i = 0; i < 3; i++) {
		if (! xmlStrcmp(key, (const xmlChar *)PIDMODE[i])) {
		    unit->PID_cool->Mode = i;
		    break;
	    	}
	    }
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_IMAX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_heat->iMax = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_IDLERANGE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_heat->idleRange = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_IGAIN"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_heat->iGain = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_PGAIN"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_heat->pGain = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_DGAIN"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_heat->dGain = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_INPUT"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_heat->Input = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_ERR"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_heat->Err = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_ISTATE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_heat->iState = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_SETP"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_heat->SetP = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_OUTP"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->PID_heat->OutP = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PIDH_MODE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    for (i = 0; i < 3; i++) {
		if (! xmlStrcmp(key, (const xmlChar *)PIDMODE[i])) {
		    unit->PID_heat->Mode = i;
		    break;
		}
	    }
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE_UUID"))) {
	    unit->profile_uuid = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE_NAME"))) {
	    unit->profile_name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE_INITTEMP_LO"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->profile_inittemp_lo = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE_INITTEMP_HI"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->profile_inittemp_hi = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE_FRIDGE_MODE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->profile_fridge_mode = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE_DURATION"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->profile_duration = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE_TOTALSTEPS"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
	    	unit->profile_totalsteps = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE_STEPS"))) {
	    parseSteps(doc, cur, &(unit)->profile_steps);
	}

	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROF_STARTED"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->prof_started = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROF_PAUSED"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->prof_paused = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROF_STATE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    for (i = 0; i < 5; i++) {
		if (! xmlStrcmp(key, (const xmlChar *)PROFSTATE[i])) {
		    unit->prof_state = i;
		    break;
		}
	    }
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROF_PEAK_ABS"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->prof_peak_abs = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROF_PEAK_REL"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		unit->prof_peak_rel = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROF_PRIMARY_DONE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		unit->prof_primary_done = ival;
	    xmlFree(key);
	}
	cur = cur->next;
    }

    if (Config.units == NULL) {
	Config.units = unit;
    } else {
	for (tmp = Config.units; tmp; tmp = tmp->next) {
	    if (tmp->next == NULL) {
		tmp->next = unit;
		break;
	    }
	}
    }

    return 0;
}



int parseFermenters(xmlDocPtr doc, xmlNodePtr cur)
{
    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	// Accept the wrong UNIT name as well as FERMENTER
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"UNIT")) || (!xmlStrcmp(cur->name, (const xmlChar *)"FERMENTER"))) {
            parseUnit(doc, cur);
        }
        cur = cur->next;
    }
    return 0;
}



int parseStep(xmlDocPtr doc, xmlNodePtr cur, prof_step **profstep)
{
    xmlChar     *key;
    int         ival;
    float       val;
    prof_step	*step, *tmp;

    step = (prof_step *)malloc(sizeof(prof_step));
    step->next = NULL;
    step->name = NULL;
    step->steptime = step->resttime = step->fridge_mode = 0;
    step->target_lo = step->target_hi = 20.0;

    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
	    step->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"RESTTIME"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		step->resttime = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STEPTIME"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		step->steptime = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TARGET_LO"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		step->target_lo = val;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TARGET_HI"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &val) == 1)
		step->target_hi = val;
	    xmlFree(key);   
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FRIDGE_MODE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		step->fridge_mode = ival;
	    xmlFree(key);
	}
	cur = cur->next;
    }

    if (*profstep == NULL) {
	*profstep = step;
    } else {
	for (tmp = *profstep; tmp; tmp = tmp->next) {
	    if (tmp->next == NULL) {
		tmp->next = step;
		break;
	    }
	}
    }
    return 0;
}



int parseSteps(xmlDocPtr doc, xmlNodePtr cur, prof_step **step)
{
    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE_STEP"))) {
	    parseStep(doc, cur, step);
	}
	cur = cur->next;
    }
    return 0;
}



int parseDevice(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar             *key;
    devices_list	*device, *tmp;
    int                 i, ival;

    device = (devices_list *)malloc(sizeof(devices_list));
    device->next = NULL;
    device->uuid = device->address = device->description = device->comment = NULL;
    device->type = device->direction = device->present = device->subdevice = device->inuse = device->offset = 0;
    device->gpiopin = -1;
    device->timestamp = (time_t)0;

    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"UUID"))) {
	    device->uuid = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    for (i = 0; i < 8; i++) {
		if (! xmlStrcmp(key, (const xmlChar *)DEVTYPE[i])) {
		    device->type = i;
		    break;
		}
	    }
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"DIRECTION"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    for (i = 0; i < 7; i++) {
		if (! xmlStrcmp(key, (const xmlChar *)DEVDIR[i])) {
		    device->direction = i;
		    break;
		}
	    }
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VALUE"))) {
		key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
		if (sscanf((const char *)key, "%d", &ival) == 1)
			device->value = ival;
		xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"OFFSET"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		device->offset = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRESENT"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    for (i = 0; i < 4; i++) {
		if (! xmlStrcmp(key, (const xmlChar *)DEVPRESENT[i])) {
		    device->present = i;
		    break;
		}
	    }
	    xmlFree(key);                           
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ADDRESS"))) {
	    device->address = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SUBDEVICE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		device->subdevice = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"GPIOPIN"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		device->gpiopin = ival;
	    xmlFree(key);                                       
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"DESCRIPTION"))) {
	    device->description = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"INUSE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		device->inuse = ival;
	    xmlFree(key);                                       
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COMMENT"))) {
	    device->comment = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TIMESTAMP"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		device->timestamp = (time_t)ival;
	    xmlFree(key);                                   
	}

	cur = cur->next;
    }

    if (Config.devices == NULL) {
	Config.devices = device;
    } else {
	for (tmp = Config.devices; tmp; tmp = tmp->next) {
	    if (tmp->next == NULL) {
		tmp->next = device;
		break;
	    }
	}
    }

    return 0;
}



int parseDevices(xmlDocPtr doc, xmlNodePtr cur)
{
    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"DEVICE"))) {
	    parseDevice(doc, cur);
	}
	cur = cur->next;
    }
    return 0;
}



#ifdef USE_SIMULATOR
int parseSimulator(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar             *key;
    simulator_list      *simulator, *tmp;
    int                 ival;
    float		fval;

    simulator = (simulator_list *)malloc(sizeof(simulator_list));
    simulator->next = NULL;
    simulator->uuid = simulator->name = NULL;
    simulator->volume_air = simulator->volume_beer = 0;
    simulator->room_temperature = simulator->air_temperature = simulator->beer_temperature = simulator->s_cool_temp = simulator->s_heat_temp = 20.0;
    simulator->chiller_temperature = 1.5;
    simulator->room_humidity = 49.2;
    simulator->cooler_temp = simulator->cooler_size = simulator->heater_temp = simulator->heater_size = simulator->frigo_isolation = 0.0;
    simulator->cooler_time = simulator->heater_time = simulator->cooler_state = simulator->heater_state = 0;
    simulator->s_yeast_started = simulator->s_cool_changed = simulator->s_heat_changed = (time_t)0;
    simulator->s_yeast_heat = simulator->s_cool_temp = simulator->s_heat_temp = 0.0;

    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"UUID"))) {
	    simulator->uuid = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
	    simulator->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VOLUME_AIR"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		simulator->volume_air = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VOLUME_BEER"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		simulator->volume_beer = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ROOM_TEMPERATURE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->room_temperature = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ROOM_GUMIDITY"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->room_humidity= fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AIR_TEMPERATURE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->air_temperature = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEER_TEMPERATURE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->beer_temperature = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CHILLER_TEMPERATURE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->chiller_temperature = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COOLER_TEMP"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->cooler_temp = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COOLER_TIME"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		simulator->cooler_time = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COOLER_SIZE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->cooler_size = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HEATER_TEMP"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->heater_temp = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HEATER_TIME"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		simulator->heater_time = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HEATER_SIZE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->heater_size = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HEATER_STATE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		simulator->heater_state = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COOLER_STATE"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		simulator->cooler_state = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FRIGO_ISOLATION"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->frigo_isolation = fval;
	    xmlFree(key);
	}

	if ((!xmlStrcmp(cur->name, (const xmlChar *)"S_YEAST_HEAT"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->s_yeast_heat = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"S_YEAST_STARTED"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		simulator->s_yeast_started = (time_t)ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"S_COOL_TEMP"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->s_cool_temp = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"S_HEAT_TEMP"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%f", &fval) == 1)
		simulator->s_heat_temp = fval;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"S_COOL_CHANGED"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		simulator->s_cool_changed = (time_t)ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"S_HEAT_CHANGED"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		simulator->s_heat_changed = (time_t)ival;
	    xmlFree(key);
	}

	cur = cur->next;
    }

    if (Config.simulators == NULL) {
	Config.simulators = simulator;
    } else {
	for (tmp = Config.simulators; tmp; tmp = tmp->next) {
	    if (tmp->next == NULL) {
		tmp->next = simulator;
		break;
	    }
	}
    }

    return 0;
}



int parseSimulators(xmlDocPtr doc, xmlNodePtr cur)
{
    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SIMULATOR"))) {
	    parseSimulator(doc, cur);
	}
	cur = cur->next;
    }
    return 0;
}
#endif



int rdconfig(void) 
{
    int		rc = 0, ival;
    char	*mypath;
    xmlDocPtr	doc;
    xmlNodePtr	cur;
    xmlChar	*key;

    killconfig();
    syslog(LOG_NOTICE, "HOME='%s' USER='%s' LOGNAME='%s'", MBSE_SS(getenv((char *)"HOME")), MBSE_SS(getenv((char *)"USER")), MBSE_SS(getenv((char *)"LOGNAME")));

    /*
     * Search config file
     */
    if (getenv((char *)"USER") == NULL) {
	mypath = xstrcpy((char *)"/root");
    } else {
    	mypath = xstrcpy(getenv((char *)"HOME"));
    }
    mypath = xstrcat(mypath, (char *)"/.thermferm/etc/");
    mkdirs(mypath, 0755);
    mypath = xstrcat(mypath, (char *)"thermferm.xml");
    if ((doc = xmlParseFile(mypath)) == NULL) {
	/*
	 * No config file, create a fresh one
	 */
	syslog(LOG_NOTICE, "rdconfig: %s not found, creating", mypath);
	wrconfig();

	if ((doc = xmlParseFile(mypath)) == NULL) {
	    syslog(LOG_NOTICE, "rdconfig: could not create %s", mypath);
	    free(mypath);
	    return 1;
	}
    }
    syslog(LOG_NOTICE, "rdconfig: using %s", mypath);

    Config.mqtt_host = xstrcpy((char *)"localhost");
    Config.mqtt_port = 1883;
    Config.next_unit = 1;
    Config.websocket_port = 8020;

    if ((cur = xmlDocGetRootElement(doc)) == NULL) {
	syslog(LOG_NOTICE, "XML file %s empty.", mypath);
	xmlFreeDoc(doc);
	return 1;
    }
    if (xmlStrcmp(cur->name, (const xmlChar*)"THERMFERM")) {
	syslog(LOG_NOTICE, "XML file %s is not a valid configuration file.", mypath);
	xmlFreeDoc(doc);
	return 1;
    }

    /*
     * Parse configuration
     */
    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
	    Config.name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SERVER_PORT")) || (!xmlStrcmp(cur->name, (const xmlChar *)"LISTEN_PORT"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		Config.server_port = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TEMP_UUID")) || (!xmlStrcmp(cur->name, (const xmlChar *)"TEMP_ADDRESS"))) {
	    Config.temp_uuid = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HUM_UUID")) || (!xmlStrcmp(cur->name, (const xmlChar *)"HUM_ADDRESS"))) {
	    Config.hum_uuid = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TEMP_HUM_IDX"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		Config.temp_hum_idx = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NEXT_UNIT"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		Config.next_unit = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MQTT_HOST"))) {
	    if (Config.mqtt_host)
		free(Config.mqtt_host);
	    Config.mqtt_host = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MQTT_PORT"))) {
	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	    if (sscanf((const char *)key, "%d", &ival) == 1)
		Config.mqtt_port = ival;
	    xmlFree(key);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MQTT_USER"))) {
	    Config.mqtt_username = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MQTT_PASS"))) {
	    Config.mqtt_password = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"UUID"))) {
	    Config.uuid = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"WS_PORT"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            if (sscanf((const char *)key, "%d", &ival) == 1)
                Config.websocket_port = ival;
            xmlFree(key);
        }
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"LCDS"))) {
	    parseLCDs(doc, cur);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FERMENTERS"))) {
	    parseFermenters(doc, cur);
	}
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"DEVICES"))) {
	    parseDevices(doc, cur);
	}
#ifdef USE_SIMULATOR
	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SIMULATORS"))) {
	    parseSimulators(doc, cur);
	}
#endif
	cur = cur->next;
    }
    xmlFreeDoc(doc);

    free(mypath);
    mypath = NULL;

    /*
     * If the system uuid is not set, do it now.
     */
    if (Config.uuid && ! strcmp((char *)"(null)", Config.uuid)) {
	free(Config.uuid);
	Config.uuid = NULL;
    }
    if (Config.uuid == NULL) {
	uuid_t		uu;

	Config.uuid = malloc(37);
	uuid_generate(uu);
	uuid_unparse(uu, Config.uuid);
    }

    return rc;
}


mercurial