--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/brewco/devices.c Wed Nov 25 22:49:35 2015 +0100 @@ -0,0 +1,795 @@ +/***************************************************************************** + * Copyright (C) 2014..2015 + * + * 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 "brewco.h" +#include "devices.h" +#include "xutil.h" + + +extern int debug; +extern sys_config Config; +extern int my_shutdown; + +#ifdef USE_SIMULATOR + +/*extern*/ int SIM_hlt_temp = 0; +/*extern*/ int SIM_mlt_temp = 0; + +#endif + + + +/* + * Read one byte from a 1-wire device like a DS2413 + */ +int read_w1(char *address, char *file) +{ + char *addr = NULL; + int fn = -1, rc = -1, retries = 5; + uint8_t val; + + addr = xstrcpy((char *)"/sys/bus/w1/devices/"); + addr = xstrcat(addr, address); + addr = xstrcat(addr, (char *)"/"); + addr = xstrcat(addr, file); + + if ((fn = open(addr, O_RDONLY)) >= 0) { + + if ((lseek(fn, 0L, SEEK_SET)) == 0) { + + while (retries--) { + if ((read(fn, &val, 1)) == 1) { + rc = (int)val; + goto leave; + } + } + syslog(LOG_NOTICE, "read_w1() read %s fatal: %s", addr, strerror(errno)); + + } else { + syslog(LOG_NOTICE, "read_w1() lseek %s: %s", addr, strerror(errno)); + } + + } else { + syslog(LOG_NOTICE, "read_w1() open %s: %s", addr, strerror(errno)); + } + +leave: + if (fn != -1) { + if ((close(fn)) == -1) { + syslog(LOG_NOTICE, "read_w1() close %s: %s", addr, strerror(errno)); + } + } + + free(addr); + return rc; +} + + + +/* + * Write a byte to a 1-wire device like a DS2413 + */ +int write_w1(char *address, char *file, uint8_t val) +{ + char *addr = NULL; + int fn = -1, rc = -1, retries = 5; + + addr = xstrcpy((char *)"/sys/bus/w1/devices/"); + addr = xstrcat(addr, address); + addr = xstrcat(addr, (char *)"/"); + addr = xstrcat(addr, file); + + if ((fn = open(addr, O_WRONLY)) >= 0) { + + if ((lseek(fn, 0L, SEEK_SET)) == 0) { + + while (retries--) { + if ((write(fn, &val, 1)) == 1) { + rc = 0; + goto leave; + } + } + syslog(LOG_NOTICE, "write_w1() write %s fatal: %s", addr, strerror(errno)); + + } else { + syslog(LOG_NOTICE, "write_w1() lseek %s: %s", addr, strerror(errno)); + } + + } else { + syslog(LOG_NOTICE, "write_w1() open %s: %s", addr, strerror(errno)); + } + +leave: + if (fn != -1) { + if ((close(fn)) == -1) { + syslog(LOG_NOTICE, "write_w1() close %s: %s", addr, strerror(errno)); + } + } + + free(addr); + return rc; +} + + + +int device_out(char *uuid, int value) +{ + devices_list *device; + time_t now, my_timestamp; + int rc, my_value, test_value; +#ifdef HAVE_WIRINGPI_H + int i; + char buf[40]; +#endif + + if (uuid == NULL) + return 0; + + now = time(NULL); + +#ifdef HAVE_WIRINGPI_H + piLock(LOCK_DEVICES); +#endif + + for (device = Config.devices; device; device = device->next) { + if (! strcmp(uuid, device->uuid)) { + /* + * Execute command if different then the old value. But also + * every 2 minutes because commands can have temporary + * disconnects, or have radio problems. + */ + my_timestamp = device->timestamp; + my_value = device->value; + + if ((device->type == DEVTYPE_W1) && (device->direction == DEVDIR_OUT_BIN)) { + test_value = (value == 0) ? 0 : 1; + } else { + test_value = value; + } + + if ((test_value != my_value) || (((int)now - (int)my_timestamp) >= 120)) { + +#ifdef HAVE_WIRINGPI_H + rc = 0; + if ((device->type == DEVTYPE_RC433) && (device->gpiopin != -1) && (device->present == DEVPRESENT_YES)) { + snprintf(buf, 39, "%s,%d", device->address, value ? 1:0); + for (i = 0; i < strlen(buf); i++) + if (buf[i] == '-') + buf[i] = ','; + piUnlock(LOCK_DEVICES); + enableTransmit(device->gpiopin); + rc = toggleSwitch(buf); + disableTransmit(); + piLock(LOCK_DEVICES); + syslog(LOG_NOTICE, "RC433 command %s rc=%d", buf, rc); + if (debug) + fprintf(stdout, "RC433 command %s rc=%d\n", buf, rc); + device->value = value; + device->timestamp = time(NULL); + piUnlock(LOCK_DEVICES); + return rc; + } + + if ((device->type == DEVTYPE_GPIO) && (device->gpiopin != -1) && (device->present == DEVPRESENT_YES)) { + + } +#endif + if ((device->type == DEVTYPE_W1) && (device->direction == DEVDIR_OUT_BIN) && (device->present == DEVPRESENT_YES)) { + if (strncmp(device->address, (char *)"3a", 2) == 0) { + /* + * DS2413. First read state so that we can preserve the state of + * the "other" PIO channel. To make things a bit more complicated + * the bits in the state register differ from the output register. + */ + uint8_t state, output; + + if ((rc = read_w1(device->address, (char *)"state")) >= 0) { + state = (unsigned int)rc; + output = (state & 0x01) + ((state & 0x04) >> 1); + + if (device->subdevice == 0) { + output = (output & 0xfe); + output |= (value == 0) ? 0x01 : 0x00; + } else if (device->subdevice == 1) { + output = (output & 0xfd); + output |= (value == 0) ? 0x02 : 0x00; + } else { + output = 0xff; + } + + if ((write_w1(device->address, (char *)"output", output)) == 0) { + syslog(LOG_NOTICE, "DS2413 PIO%c value=%d (%s)", (device->subdevice == 0) ? 'A' : 'B', (value == 0) ? 0 : 1, device->comment); + if (debug) + fprintf(stdout, "DS2413 PIO%c value=%d (%s)\n", (device->subdevice == 0) ? 'A' : 'B', (value == 0) ? 0 : 1, device->comment); + device->value = (value == 0) ? 0 : 1; + device->timestamp = time(NULL); + } + } + } + } + +#ifdef USE_SIMULATOR + if ((device->type == DEVTYPE_SIM) && (device->direction == DEVDIR_OUT_BIN) && (device->present == DEVPRESENT_YES)) { + if ((strcmp((char *)"SimHLTheater", device->address) == 0) || (strcmp((char *)"SimMLTheater", device->address) == 0)) { + if (value != device->value) { + syslog(LOG_NOTICE, "SIM %s value=%d", device->address, value); + if (debug) + fprintf(stdout, "SIM %s value=%d\n", device->address, value); + } + device->value = value; + if (strcmp((char *)"SimHLTheater", device->address) == 0) + SIM_hlt_temp = value; + if (strcmp((char *)"SimMLTheater", device->address) == 0) + SIM_mlt_temp = value; + } + } +#endif + } else { +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + return 0; + } + } + } +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + + return 0; +} + + +/* + * Returns DEVPRESENT_NO if failed. + * Returns DEVPRESENT_YES if success, value contains new value. + */ +int device_in(char *uuid, int *value) +{ + devices_list *device; + int tmp, present; + + if (uuid == NULL) + return 0; + +#ifdef HAVE_WIRINGPI_H + piLock(LOCK_DEVICES); +#endif + + for (device = Config.devices; device; device = device->next) { + if (! strcmp(uuid, device->uuid)) { + present = device->present; + if (present == DEVPRESENT_YES) { + tmp = device->value + device->offset; + } else { + tmp = 0; + } +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + *value = tmp; + return present; + } + } + +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + + return DEVPRESENT_NO; +} + + + + +/* + * Auto detect hotplugged or known to be present devices + */ +int devices_detect(void) +{ + struct dirent *de; + DIR *fd; + devices_list *device, *ndev; + int found, subdevices, ival, i, rc = 0; + char buf[40]; + uuid_t uu; +#ifdef HAVE_WIRINGPI_H + int pin; +#endif + + /* + * Scan for 1-wire devices + */ + if ((fd = opendir((char *)"/sys/bus/w1/devices"))) { + while ((de = readdir(fd))) { + if (de->d_name[0] != '.') { + found = FALSE; + for (device = Config.devices; device; device = device->next) { + if (strcmp(device->address,de->d_name) == 0) { + found = TRUE; + break; + } + } + + if (found == FALSE) { + strncpy(buf, de->d_name, 2); + buf[2] = '\0'; + sscanf(buf, "%02x", &ival); + syslog(LOG_NOTICE, "Scan 1-wire %02x %d", ival, ival); + subdevices = 1; + if (strcmp(buf, (char *)"29") == 0) + subdevices = 8; + if (strcmp(buf, (char *)"3a") == 0) + subdevices = 2; + for (i = 0; i < subdevices; i++) { + ndev = (devices_list *)malloc(sizeof(devices_list)); + ndev->next = NULL; + ndev->version = 1; + ndev->uuid = malloc(37); + uuid_generate(uu); + uuid_unparse(uu, ndev->uuid); + ndev->type = DEVTYPE_W1; + ndev->direction = DEVDIR_UNDEF; + if (strcmp(buf, (char *)"10") == 0) { + ndev->direction = DEVDIR_IN_ANALOG; + ndev->description = xstrcpy((char *)"DS18S20 Digital thermometer"); + } else if (strcmp(buf, (char *)"22") == 0) { + ndev->direction = DEVDIR_IN_ANALOG; + ndev->description = xstrcpy((char *)"DS1820 Digital thermometer"); + } else if (strcmp(buf, (char *)"28") == 0) { + ndev->direction = DEVDIR_IN_ANALOG; + ndev->description = xstrcpy((char *)"DS18B20 Digital thermometer"); + } else if (strcmp(buf, (char *)"3a") == 0) { + ndev->description = xstrcpy((char *)"DS2413 Dual channel addressable switch"); + ndev->direction = DEVDIR_IN_BIN; + } else if (strcmp(buf, (char *)"3b") == 0) { + ndev->direction = DEVDIR_IN_ANALOG; + ndev->description = xstrcpy((char *)"DS1825 Digital thermometer"); + } else if (strcmp(buf, (char *)"42") == 0) { + ndev->direction = DEVDIR_IN_ANALOG; + ndev->description = xstrcpy((char *)"DS28EA00 Digital thermometer"); + } else if (strcmp(buf, (char *)"w1") == 0) { + ndev->description = xstrcpy((char *)"Master System device"); + } else { + ndev->description = xstrcpy((char *)"Unknown device family "); + ndev->description = xstrcat(ndev->description, buf); + } + ndev->value = ndev->offset = ndev->inuse = 0; + ndev->present = DEVPRESENT_YES; + ndev->address = xstrcpy(de->d_name); + ndev->subdevice = i; + ndev->gpiopin = -1; + ndev->comment = xstrcpy((char *)"Auto detected device"); + ndev->timestamp = time(NULL); + + if (Config.devices == NULL) { + Config.devices = ndev; + } else { + for (device = Config.devices; device; device = device->next) { + if (device->next == NULL) { + device->next = ndev; + break; + } + } + } + rc++; + } + } + } + } + closedir(fd); + } + +#ifdef HAVE_WIRINGPI_H + if (piBoardRev() == 2) { + /* + * Support rev B and newer boards only + */ + found = FALSE; + for (device = Config.devices; device; device = device->next) { + if (device->type == DEVTYPE_GPIO) { + found = TRUE; + break; + } + } + + if (found == FALSE) { + /* + * There were no GPIO devices found. + */ + subdevices = 12; + pin = 0; + for (i = 0; i < subdevices; i++) { + if (i == 8) + pin = 17; + + ndev = (devices_list *)malloc(sizeof(devices_list)); + ndev->next = NULL; + ndev->version = 1; + ndev->uuid = malloc(37); + uuid_generate(uu); + uuid_unparse(uu, ndev->uuid); + ndev->type = DEVTYPE_GPIO; + ndev->value = digitalRead(pin); + ndev->offset = 0; + ndev->present = DEVPRESENT_YES; + ndev->address = xstrcpy((char *)"GPIO"); + snprintf(buf, 39, "Raspberry GPIO %d", i); + ndev->description = xstrcpy(buf); + ndev->subdevice = i; + ndev->gpiopin = pin; + ndev->timestamp = time(NULL); + if (i == PANEL_RETURN) { + ndev->direction = DEVDIR_IN_BIN; + ndev->inuse = 1; + ndev->comment = xstrcpy((char *)"Frontpanel Return"); + } else if (i == PANEL_ENTER) { + ndev->direction = DEVDIR_IN_BIN; + ndev->inuse = 1; + ndev->comment = xstrcpy((char *)"Frontpanel Enter key"); + } else if (i == PANEL_DOWN) { + ndev->direction = DEVDIR_IN_BIN; + ndev->inuse = 1; + ndev->comment = xstrcpy((char *)"Frontpanel Down key"); + } else if (i == PANEL_UP) { + ndev->direction = DEVDIR_IN_BIN; + ndev->inuse = 1; + ndev->comment = xstrcpy((char *)"Frontpanel Up key"); + } else if (i == 7) { + ndev->direction = DEVDIR_INTERN; + ndev->inuse = 1; + ndev->comment = xstrcpy((char *)"1-Wire bus"); + } else { + ndev->direction = DEVDIR_IN_BIN; + ndev->inuse = 0; + ndev->comment = xstrcpy((char *)"Raspberry GPIO"); + } + pin++; + + if (Config.devices == NULL) { + Config.devices = ndev; + } else { + for (device = Config.devices; device; device = device->next) { + if (device->next == NULL) { + device->next = ndev; + break; + } + } + } + rc++; + } + } + } +#endif + +#ifdef USE_SIMULATOR + found = FALSE; + for (device = Config.devices; device; device = device->next) { + if (device->type == DEVTYPE_SIM) { + found = TRUE; + break; + } + } + + if (found == FALSE) { + subdevices = 5; + for (i = 0; i < subdevices; i++) { + ndev = (devices_list *)malloc(sizeof(devices_list)); + ndev->next = NULL; + ndev->version = 1; + ndev->uuid = malloc(37); + uuid_generate(uu); + uuid_unparse(uu, ndev->uuid); + ndev->type = DEVTYPE_SIM; + ndev->value = ndev->offset = 0; + ndev->present = DEVPRESENT_YES; + ndev->subdevice = i; + ndev->gpiopin = -1; + ndev->comment = xstrcpy((char *)"Auto detected device"); + ndev->timestamp = time(NULL); + ndev->inuse = 0; + switch (i) { + case 0: ndev->direction = DEVDIR_IN_ANALOG; + ndev->address = xstrcpy((char *)"SimRoomtemp"); + ndev->description = xstrcpy((char *)"Simulated room temperature"); + break; + case 1: ndev->direction = DEVDIR_IN_ANALOG; + ndev->address = xstrcpy((char *)"SimHLTtemp"); + ndev->description = xstrcpy((char *)"Simulated HLT temperature"); + break; + case 2: ndev->direction = DEVDIR_IN_ANALOG; + ndev->address = xstrcpy((char *)"SimMLTtemp"); + ndev->description = xstrcpy((char *)"Simulated MLT temperature"); + break; + case 3: ndev->direction = DEVDIR_OUT_ANALOG; + ndev->address = xstrcpy((char *)"SimHLTheater"); + ndev->description = xstrcpy((char *)"Simulated HLT heater"); + break; + case 4: ndev->direction = DEVDIR_OUT_ANALOG; + ndev->address = xstrcpy((char *)"SimMLTheater"); + ndev->description = xstrcpy((char *)"Simulated MLT heater"); + break; + } + + if (Config.devices == NULL) { + Config.devices = ndev; + } else { + for (device = Config.devices; device; device = device->next) { + if (device->next == NULL) { + device->next = ndev; + break; + } + } + } + rc++; + } + } +#endif + + return rc; +} + + + +#ifdef HAVE_WIRINGPI_H +PI_THREAD (my_devices_loop) +#else +void *my_devices_loop(void *threadid) +#endif +{ + devices_list *device; +#ifdef USE_SIMULATOR + simulator_list *simulator; +#endif + char *addr = NULL, line[60], *p = NULL; + FILE *fp; + int temp, rc; +#ifdef HAVE_WIRINGPI_H + time_t now; +#endif + + syslog(LOG_NOTICE, "Thread my_devices_loop started"); + +#ifdef HAVE_WIRINGPI_H + if ((rc = piHiPri(10))) + syslog(LOG_NOTICE, "my_devices_loop: piHiPri(10) rc=%d", rc); +#endif + + /* + * Loop forever until the external shutdown variable is set. + */ + for (;;) { + + /* + * Process all devices. + */ + for (device = Config.devices; device; device = device->next) { + + if (my_shutdown) + break; + + switch (device->type) { + case DEVTYPE_W1: + /* + * Only tested with DS18B20 but from the kernel source this + * should work with all 1-wire thermometer sensors. + */ + if ((strncmp(device->address, (char *)"10", 2) == 0) || + (strncmp(device->address, (char *)"22", 2) == 0) || + (strncmp(device->address, (char *)"28", 2) == 0) || + (strncmp(device->address, (char *)"3b", 2) == 0) || + (strncmp(device->address, (char *)"42", 2) == 0)) { + addr = xstrcpy((char *)"/sys/bus/w1/devices/"); + addr = xstrcat(addr, device->address); + addr = xstrcat(addr, (char *)"/w1_slave"); + if ((fp = fopen(addr, "r"))) { + if (device->present != DEVPRESENT_YES) { + syslog(LOG_NOTICE, "sensor %s is back", device->address); +#ifdef HAVE_WIRINGPI_H + piLock(LOCK_DEVICES); +#endif + device->present = DEVPRESENT_YES; +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + } + /* + * The output looks like: + * 72 01 4b 46 7f ff 0e 10 57 : crc=57 YES + * 72 01 4b 46 7f ff 0e 10 57 t=23125 + */ + fgets(line, 50, fp); + line[strlen(line)-1] = '\0'; + if ((line[36] == 'Y') && (line[37] == 'E')) { + /* CRC is Ok, continue */ + fgets(line, 50, fp); + line[strlen(line)-1] = '\0'; + strtok(line, (char *)"="); + p = strtok(NULL, (char *)"="); + rc = sscanf(p, "%d", &temp); +#ifdef HAVE_WIRINGPI_H + piLock(LOCK_DEVICES); +#endif + if ((rc == 1) && (device->value != temp)) { + device->value = temp; + device->timestamp = time(NULL); + } +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + } else { + syslog(LOG_NOTICE, "sensor %s CRC error", device->address); +#ifdef HAVE_WIRINGPI_H + piLock(LOCK_DEVICES); +#endif + device->present = DEVPRESENT_ERROR; +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + } + fclose(fp); + } else { + if (device->present != DEVPRESENT_NO) { + syslog(LOG_NOTICE, "sensor %s is missing", device->address); +#ifdef HAVE_WIRINGPI_H + piLock(LOCK_DEVICES); +#endif + device->present = DEVPRESENT_NO; +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + } + } + free(addr); + addr = NULL; + } /* if temperature sensor */ + /* + * DS2413 Dual channel addressable switch + */ + if (strncmp(device->address, (char *)"3a", 2) == 0) { + addr = xstrcpy((char *)"/sys/bus/w1/devices/"); + addr = xstrcat(addr, device->address); + addr = xstrcat(addr, (char *)"/state"); + + if ((access(addr, R_OK)) == 0) { + if (device->present != DEVPRESENT_YES) { + syslog(LOG_NOTICE, "DS2413 %s is back", device->address); +#ifdef HAVE_WIRINGPI_H + piLock(LOCK_DEVICES); +#endif + device->present = DEVPRESENT_YES; +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + } + /* + * First make sure that if this device is configured as input + * to drive the output high. + */ + if (device->direction == DEVDIR_IN_BIN) { + uint8_t state, 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 (device->subdevice == 0) { + output = (output & 0xfe); + output |= 0x01; + } else if (device->subdevice == 1) { + output = (output & 0xfd); + output |= 0x02; + } else { + output = 0xff; + } + write_w1(device->address, (char *)"output", output); + } + } + if ((rc = read_w1(device->address, (char *)"state")) >= 0) { +#ifdef HAVE_WIRINGPI_H + piLock(LOCK_DEVICES); +#endif + /* + * Read PIOA or PIOB pin state bits + */ + if (device->subdevice == 0) + device->value = (rc & 0x01) ? 0 : 1; + else if (device->subdevice == 1) + device->value = (rc & 0x04) ? 0 : 1; + device->timestamp = time(NULL); +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + } + } else { + if (device->present != DEVPRESENT_NO) { + syslog(LOG_NOTICE, "DS2413 %s is missing", device->address); +#ifdef HAVE_WIRINGPI_H + piLock(LOCK_DEVICES); +#endif + device->present = DEVPRESENT_NO; +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + } + } + free(addr); + addr = NULL; + } + + break; + +#ifdef HAVE_WIRINGPI_H + case DEVTYPE_GPIO: + if (device->direction == DEVDIR_IN_BIN) { + piLock(LOCK_DEVICES); + device->value = digitalRead(device->gpiopin); + device->offset = 0; + device->timestamp = time(NULL); + piUnlock(LOCK_DEVICES); + } + break; + +#endif +#ifdef USE_SIMULATOR + case DEVTYPE_SIM: +#ifdef HAVE_WIRINGPI_H + piLock(LOCK_DEVICES); +#endif + if (Config.simulators) { + simulator = Config.simulators; + if (device->subdevice == 0) { + device->value = (int)(simulator->room_temperature * 1000); + device->timestamp = time(NULL); + } else if (device->subdevice == 1) { + device->value = (int)(simulator->hlt_temperature * 1000); + device->timestamp = time(NULL); + } else if (device->subdevice == 2) { + device->value = (int)(simulator->mlt_temperature * 1000); + device->timestamp = time(NULL); + } + } +#ifdef HAVE_WIRINGPI_H + piUnlock(LOCK_DEVICES); +#endif + break; +#endif + default: + break; + } + + /* + * Delay a bit after procesing a device. + */ + usleep(10000); + } + /* + * Delay a bit after all devices + */ + usleep(100000); + } + + syslog(LOG_NOTICE, "Thread my_devices_loop stopped"); + return 0; +} + + +