thermferm/one-wire.c

Sun, 05 May 2024 17:24:54 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Sun, 05 May 2024 17:24:54 +0200
changeset 730
6eba006ed8f5
parent 728
da038d0bed04
permissions
-rw-r--r--

Much faster shutdown of the websocket service.

/**
 * @brief One-wire devices
 *
 * Copyright (C) 2024
 *
 * 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 MBSE BBS; see the file COPYING.  If not, write to the Free
 * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

extern int		debug;

#include "thermferm.h"
#include "statetbl.h"
#include "one-wire.h"
#include "devices.h"
#include "delay.h"
#include "websocket.h"
#include "futil.h"
#include "xutil.h"

#define	W1_TEMP_RESOLUTION	12


extern sys_config	Config;
extern pthread_mutex_t	mutexes[5];
extern const char	DEVPRESENT[4][6];


int			my_one_wire_state = 0;
int			my_one_wire_shutdown = 0;
w1_list			*w1_devices = NULL;


static int one_wire(void);


/*
 * Return json data for one device.
 */
char *one_wire_json(w1_list *dev_w1)
{
    char        *payload;
    char        vbuf[64];

    payload = xstrcpy((char *)"{\"address\":\"");
    payload = xstrcat(payload, dev_w1->address);
    payload = xstrcat(payload, (char *)"\",\"family\":\"");
    payload = xstrcat(payload, dev_w1->family);
    payload = xstrcat(payload, (char *)"\",\"present\":\"");
    payload = xstrcat(payload, (char *)DEVPRESENT[dev_w1->present]);
    payload = xstrcat(payload, (char *)"\",\"value\":");
    snprintf(vbuf, 63, "%d", dev_w1->value);
    payload = xstrcat(payload, vbuf);
    payload = xstrcat(payload, (char *)",\"resolution\":");
    snprintf(vbuf, 63, "%d", dev_w1->resolution);
    payload = xstrcat(payload, vbuf);
    payload = xstrcat(payload, (char *)",\"timestamp\":");
    snprintf(vbuf, 63, "%ld", (long)dev_w1->timestamp);
    payload = xstrcat(payload, vbuf);
    payload = xstrcat(payload, (char *)"}");

    return payload;
}


void one_wire_ws(void)
{
    bool	comma = false;
    char	*payload = NULL, *payloadu = NULL;
    w1_list	*dev_w1;

    payload = xstrcpy((char *)"{\"type\":\"onewire\",\"metric\":[");
    for (dev_w1 = w1_devices; dev_w1; dev_w1 = dev_w1->next) {
        if (comma)
            payload = xstrcat(payload, (char *)",");
        payloadu = one_wire_json(dev_w1);
        payload = xstrcat(payload, payloadu);
        comma = true;
        free(payloadu);
        payloadu = NULL;
    }
    payload = xstrcat(payload, (char *)"]}");
    ws_broadcast(payload);
    free(payload);
    payload = NULL;
}



void *my_one_wire_loop(void *threadid)
{
    pid_t	pid = gettid();

    my_one_wire_state = 1;
    syslog(LOG_NOTICE, "Thread my_one_wire_loop started, pid=%d", pid);

    /*
     * Run the state machine
     */
    one_wire();

    /*
     * Remove the dynamic tables.
     */

    syslog(LOG_NOTICE, "Thread my_one_wire_loop stopped");
    my_one_wire_state = 0;
    return 0;
}




SM_DECL(one_wire,(char *)"one-wire")
SM_STATES
    ScanNew,
    ScanDel,
    Read2413,
    ReadTemp,
    Missing,
    Websocket
SM_NAMES
    (char *)"ScanNew",
    (char *)"ScanDel",
    (char *)"Read2413",
    (char *)"ReadTemp",
    (char *)"Missing",
    (char *)"Websocket"
SM_EDECL

    int			found, i, rc, value, conv_time;
    FILE		*fp;
    devices_list	*device;
    w1_list		*dev_w1, *n_w1, *cur_w1 = NULL;
    char		buffer[25], w1type[10], *devfile = NULL;
    uint8_t		state, output, newval;
    bool		changed;

SM_START(ScanNew)

SM_STATE(ScanNew)

    if (my_one_wire_shutdown) {
	SM_SUCCESS;
    }

    changed = false;
    /*
     * Scan for current one-wire devices.
     */
    fp = fopen("/sys/devices/w1_bus_master1/w1_master_slaves", "r");
    if (fp == NULL) {
	syslog(LOG_NOTICE, "No w1_bus_master: %s", strerror(errno));
	SM_ERROR;
    }
    while ((fgets(buffer, 25, fp))) {
	buffer[strlen(buffer)-1] = '\0';
	strncpy(w1type, buffer, 2);
	w1type[2] = '\0';

	/*
	 * Check if device is known and already detected.
	 */
	if ((strcmp(w1type, (char *)"3a") == 0) || (strcmp(w1type, (char *)"10") == 0) ||
	    (strcmp(w1type, (char *)"22") == 0) || (strcmp(w1type, (char *)"28") == 0) ||
	    (strcmp(w1type, (char *)"3b") == 0) || (strcmp(w1type, (char *)"42") == 0)) {
	    found = FALSE;
	    for (dev_w1 = w1_devices; dev_w1; dev_w1 = dev_w1->next) {
		if (strcmp(dev_w1->address, buffer) == 0) {
		    found = TRUE;
		    if (dev_w1->present != DEVPRESENT_YES) {
			syslog(LOG_NOTICE, "One-wire device %s is back", buffer);
			pthread_mutex_lock(&mutexes[LOCK_ONE_WIRE]);
			dev_w1->present = DEVPRESENT_YES;
			dev_w1->resolution = 0;			/* Might be wrong, so reset */
			dev_w1->timestamp = time(NULL);
			pthread_mutex_unlock(&mutexes[LOCK_ONE_WIRE]);
			changed = true;
			device_present(dev_w1->address, DEVPRESENT_YES);
		    }
		    break;
		}
	    }
	    if (found == FALSE) {
		syslog(LOG_NOTICE, "One-wire device %s add new", buffer);
		n_w1 = (w1_list *)malloc(sizeof(w1_list));
		n_w1->next = NULL;
		n_w1->address = xstrcpy(buffer);
		strncpy(n_w1->family, buffer, 2);
		n_w1->family[2] = '\0';
		n_w1->present = DEVPRESENT_YES;
		n_w1->value = (strcmp(w1type, (char *)"3a") == 0) ? 3:-1;
		n_w1->resolution = 0;
		n_w1->timestamp = time(NULL);
		changed = true;

		pthread_mutex_lock(&mutexes[LOCK_ONE_WIRE]);
		if (w1_devices == NULL) {
		    w1_devices = n_w1;
		    cur_w1 = w1_devices;	/* Point to first device	*/
		} else {
		    for (dev_w1 = w1_devices; dev_w1; dev_w1 = dev_w1->next) {
			if (dev_w1->next == NULL) {
			    dev_w1->next = n_w1;
			    break;
			}
		    }
		}
		pthread_mutex_unlock(&mutexes[LOCK_ONE_WIRE]);
	    }
	} else if (strcmp(w1type, (char *)"00")) {
	    syslog(LOG_NOTICE, "One-wire device %ld %s unknown", (long)strlen(buffer), buffer);
	}
    }
    fclose(fp);
    SM_PROCEED(ScanDel);

SM_STATE(ScanDel)

    if (my_one_wire_shutdown) {
	SM_SUCCESS;
    }

    /*
     * Scan from the linked list if all devices are still present.
     */
    for (dev_w1 = w1_devices; dev_w1; dev_w1 = dev_w1->next) {
	devfile = xstrcpy((char *)"/sys/bus/w1/devices/");
	devfile = xstrcat(devfile, dev_w1->address);
	devfile = xstrcat(devfile, (char *)"/uevent");
	if (file_exist(devfile, R_OK) && (dev_w1->present == DEVPRESENT_YES)) {
	    /*
	     * Gone missing
	     */
	    syslog(LOG_NOTICE, "One-wire device %s is missing", dev_w1->address);
	    pthread_mutex_lock(&mutexes[LOCK_ONE_WIRE]);
	    dev_w1->present = DEVPRESENT_NO;
	    dev_w1->timestamp = time(NULL);
	    pthread_mutex_unlock(&mutexes[LOCK_ONE_WIRE]);
	    changed = true;
	    device_present(dev_w1->address, DEVPRESENT_NO);
	}
	free(devfile);
	devfile = NULL;
    }

    mDelay(40);
    SM_PROCEED(Read2413);

SM_STATE(Read2413)

    if (my_one_wire_shutdown) {
	SM_SUCCESS;
    }

    for (dev_w1 = w1_devices; dev_w1; dev_w1 = dev_w1->next) {
	if (strcmp(dev_w1->family, "3a") == 0) {
	    for (i = 0; i < 2; i++) {
		for (device = Config.devices; device; device = device->next) {
		    if ((strcmp(dev_w1->address, device->address) == 0) && (device->subdevice == i) && (device->direction == DEVDIR_IN_BIN)) {
			/*
			 * First make sure that this device is configured as input
			 * to drive the output high. Fix if programmed as output.
			 */
			if ((rc = read_w1(device->address, (char *)"state")) >= 0) {
			    state = (unsigned int)rc;
			    output = ((state & 0x02) >> 1) + ((state & 0x08) >> 2);		/* Both latch states	*/
			    if ((i == 0) && ((state & 0x02) == 0)) {				/* Fix A side		*/
				syslog(LOG_NOTICE, "One-wire device %s-%d out %02x -> %02x", dev_w1->address, i, output, output | 0x01);
				output |= 0x01;
				write_w1(device->address, (char *)"output", output);
				mDelay(10);
				if ((rc = read_w1(device->address, (char *)"state")) >= 0)	/* Read PIO again	*/
				    state = (unsigned int)rc;
			    }
			    if ((i == 1) && ((state & 0x08) == 0)) {				/* Fix B side		*/
				syslog(LOG_NOTICE, "One-wire device %s-%d out %02x -> %02x", dev_w1->address, i, output, output | 0x02);
				output |= 0x02;
				write_w1(device->address, (char *)"output", output);
				mDelay(10);
				if ((rc = read_w1(device->address, (char *)"state")) >= 0)      /* Read PIO again       */
				    state = (unsigned int)rc;
			    }

			    newval = ((state & 0x04) >> 1) + (state & 0x01);
			    if (newval != dev_w1->value) {
			    	syslog(LOG_NOTICE, "One-wire device %s-%d in %02x value %d => %d", dev_w1->address, i, state, dev_w1->value, newval);
			    	dev_w1->value = newval;
				dev_w1->timestamp = time(NULL);
				changed = true;
			    }

//			    pthread_mutex_lock(&mutexes[LOCK_DEVICES]);
			    /*
			     * Read PIOA or PIOB pin state bits
			     */
			    if (device->subdevice == 0)
				device->value = (state & 0x01) ? 0 : 1;
			    else if (device->subdevice == 1)
				device->value = (state & 0x04) ? 0 : 1;
			    device->timestamp = time(NULL);
//			    pthread_mutex_unlock(&mutexes[LOCK_DEVICES]);
			}
			mDelay(20);
		    } else if ((strcmp(dev_w1->address, device->address) == 0) && (device->subdevice == i) && (device->direction == DEVDIR_OUT_BIN)) {
			/*
			 * Sync output state
			 */
			if ((rc = read_w1(device->address, (char *)"state")) >= 0) {
                            state = (unsigned int)rc;
			    newval = output = (state & 0x01) + ((state & 0x04) >> 1);

			    if (device->subdevice == 0) {
				newval = (newval & 0xfe);
				newval |= (device->value) ? 0x00 : 0x01;
			    } else if (device->subdevice == 1) {
				newval = (newval & 0xfd);
				newval |= (device->value) ? 0x00 : 0x02;
			    }

			    if (output != newval) {
				if ((write_w1(dev_w1->address, (char *)"output", newval)) == 0) {
				    syslog(LOG_NOTICE, "One-wire device %s-%d out %02x -> %02x", dev_w1->address, i, output, newval);
				    dev_w1->value = newval;
				    dev_w1->timestamp = time(NULL);
				    changed = true;
				}
			    }
			}
		    }
		} /* for (device = Config.devices; ... */
	    }
	}
    }

    mDelay(40);
    SM_PROCEED(ReadTemp);

SM_STATE(ReadTemp)

    if (my_one_wire_shutdown) {
	SM_SUCCESS;
    }

    /*
     * cur_w1 points to the next not handled device.
     */
    if (cur_w1 != NULL) {

	if (((strcmp(cur_w1->family, (char *)"10") == 0) || (strcmp(cur_w1->family, (char *)"22") == 0) || (strcmp(cur_w1->family, (char *)"28") == 0) ||
	     (strcmp(cur_w1->family, (char *)"3b") == 0) || (strcmp(cur_w1->family, (char *)"42") == 0)) && (cur_w1->present == DEVPRESENT_YES)) {

	    /*
	     * Check and correct resolution. Only do this for new sensors
	     * or known sensors that are plugged in again.
	     */
	    if (cur_w1->resolution != W1_TEMP_RESOLUTION) {
	    	devfile = xstrcpy((char *)"/sys/bus/w1/devices/");
	    	devfile = xstrcat(devfile, cur_w1->address);
	    	devfile = xstrcat(devfile, (char *)"/resolution");
	    	if ((fp = fopen(devfile, "r+"))) {
		    if ((fgets(buffer, 25, fp))) {
			sscanf(buffer, "%d", &value);
			/* If device is removed, value is negative (errno?) */
		     	if ((value > 0) && (value != W1_TEMP_RESOLUTION)) {
			    syslog(LOG_NOTICE, "One-wire device %s set resolution from %d to %d", cur_w1->address, value, W1_TEMP_RESOLUTION);
			    fseek(fp, 0L, SEEK_SET);
			    sprintf(buffer, "%d", W1_TEMP_RESOLUTION);
			    fputs(buffer, fp);
		     	}
			cur_w1->resolution = W1_TEMP_RESOLUTION;
		    }
		    fclose(fp);
	    	} else {
		    syslog(LOG_NOTICE, "One-wire device %s open: %s", cur_w1->address, strerror(errno));
	    	}
	    	free(devfile);
	    }

	    /*
	     * Set conversion wait time. Skip this??
	     */
	    conv_time = 760;
	    devfile = xstrcpy((char *)"/sys/bus/w1/devices/");
	    devfile = xstrcat(devfile, cur_w1->address);
	    devfile = xstrcat(devfile, (char *)"/conv_time");
	    if ((fp = fopen(devfile, "r"))) {
	    	if ((fgets(buffer, 25, fp))) {
		    sscanf(buffer, "%d", &conv_time);
	    	}
	    	fclose(fp);
	    }
	    free(devfile);

	    /*
	     * Read temperature for one sensor.
	     * Bulk read does not work for unknown reason.
	     * It may be because there are also DS2413 devices.
	     */
	    devfile = xstrcpy((char *)"/sys/bus/w1/devices/");
	    devfile = xstrcat(devfile, cur_w1->address);
	    devfile = xstrcat(devfile, (char *)"/temperature");
	    if ((fp = fopen(devfile, "r"))) {
	    	mDelay(conv_time);
	    	if ((fgets(buffer, 25, fp))) {
		    sscanf(buffer, "%d", &value);
		    if (cur_w1->value != value) {
		    	cur_w1->timestamp = time(NULL);
		    	changed = true;
		    	if (debug)
		    	    syslog(LOG_NOTICE, "One-wire device %s temperature read %d => %d", cur_w1->address, cur_w1->value, value);
		    }
		    cur_w1->value = value;		/* devices.c will pick this up */
	    	} else {
		    syslog(LOG_NOTICE, "One-wire device %s temperature read error", cur_w1->address);
	    	}
	    	fclose(fp);
	    } else {
		syslog(LOG_NOTICE, "One-wire device %s open: %s", cur_w1->address, strerror(errno));
	    }
	    free(devfile);
	    devfile = NULL;
	} /* if temperature sensor and present */

	for (;;) {
	    if (cur_w1->next != NULL) {
	    	cur_w1 = cur_w1->next;
	    } else {
	    	cur_w1 = w1_devices;
	    }
	    if ((strcmp(cur_w1->family, (char *)"10") == 0) || (strcmp(cur_w1->family, (char *)"22") == 0) || (strcmp(cur_w1->family, (char *)"28") == 0) ||
		(strcmp(cur_w1->family, (char *)"3b") == 0) || (strcmp(cur_w1->family, (char *)"42") == 0))
		break;
	}

    } else {
    	mDelay(750);
    }

    SM_PROCEED(Missing);

SM_STATE(Missing)

    if (my_one_wire_shutdown) {
	SM_SUCCESS;
    }

    SM_PROCEED(Websocket);

SM_STATE(Websocket)

    for (i = 0; i < 10; i++) {
	if (my_one_wire_shutdown) {
	    SM_SUCCESS;
	}

	mDelay(100);
	if (changed && i == 5)
	    one_wire_ws();
    }
    SM_PROCEED(ScanNew);

SM_END
SM_RETURN

mercurial