thermferm/one-wire.c

Thu, 02 May 2024 15:49:16 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Thu, 02 May 2024 15:49:16 +0200
changeset 716
5c30c8ef83a8
parent 715
f5d85af156ab
child 724
01e3936f62d4
permissions
-rw-r--r--

Version 0.9.19b3. The simulator thread can be paused to be able to add and delete simulators. Added simulated door and PSU status. Devices can now fully use multiple simulators. Better rounding of simulated temperature values. The server SIMULATOR DEL and ADD commands pause the simulator when the linked list is manipulated. Fixed SIGSEGV when a simulator is added. Added socket SO_REUSEADDR again to the server socket.

/**
 * @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 *)",\"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)
{
    my_one_wire_state = 1;
    syslog(LOG_NOTICE, "Thread my_one_wire_loop started");

    /*
     * 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->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->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;
    }

    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; ... */
	    }
	}
    }

    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)) {
	    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);
		     }
		}
		fclose(fp);
		free(devfile);

		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);

		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;
			    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