From 0852a2c83e503734276a095c93640c42dee57584 Mon Sep 17 00:00:00 2001 From: Jaro Date: Mon, 26 Oct 2020 20:07:30 +0100 Subject: [PATCH] Inicializacny commit --- Makefile | 38 ++++++ bell-mqtt.c | 308 ++++++++++++++++++++++++++++++++++++++++++++++++ bell-rspro.init | 22 ++++ gpiosysfs.c | 85 +++++++++++++ gpiosysfs.h | 13 ++ 5 files changed, 466 insertions(+) create mode 100644 Makefile create mode 100644 bell-mqtt.c create mode 100644 bell-rspro.init create mode 100644 gpiosysfs.c create mode 100644 gpiosysfs.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..92a4e83 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +VERSION = 0.0.7 +# Global target; when 'make' is run without arguments, this is what it should do +all: bell-mqtt-recv + +# These variables hold the name of the compilation tool, the compilation flags and the link flags +# We make use of these variables in the package manifest +CC = gcc +CFLAGS = -Wall +LDFLAGS += -lmosquitto + +# This variable identifies all header files in the directory; we use it to create a dependency chain between the object files and the source files +# This approach will re-build your application whenever any header file changes. In a more complex application, such behavior is often undesirable +DEPS = $(wildcard *.h) + +# This variable holds all source files to consider for the build; we use a wildcard to pick all files +SRC = $(wildcard *.c) + +# This variable holds all object file names, constructed from the source file names using pattern substitution +OBJ = $(patsubst %.c, %.o, $(SRC)) + +# This rule builds individual object files, and depends on the corresponding C source files and the header files +%.o: %.c $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +# To build 'helloworld', we depend on the object files, and link them all into a single executable using the compilation tool +# We use automatic variables to specify the final executable name 'helloworld', using '$@' and the '$^' will hold the names of all the +# dependencies of this rule +bell-mqtt-recv: $(OBJ) + $(CC) -o $@ $^ $(LDFLAGS) + +# To clean build artifacts, we specify a 'clean' rule, and use PHONY to indicate that this rule never matches with a potential file in the directory +.PHONY: clean + +clean: + rm bell-mqtt-recv -f *.o + +pack: + tar cvzf ../bell-rspro-$(VERSION).tar.gz $(DEPS) $(SRC) Makefile bell-rspro.init diff --git a/bell-mqtt.c b/bell-mqtt.c new file mode 100644 index 0000000..7ee161f --- /dev/null +++ b/bell-mqtt.c @@ -0,0 +1,308 @@ +/**************************************************************/ +/* Multicast listener (server) */ +/* */ +/* Activation using: {program name} {Multicast IP} {port} */ +/* {program name} - This program name */ +/* {Multicast IP} - The IP address to listen to (Class D) */ +/* {port} - The port hnumber to listen on */ +/* {gpio} - gpio pin number for output */ +/* */ +/* */ +/* */ +/* */ +/* */ +/* */ +/* */ +/* */ +/* This is free software released under the GPL license. */ +/* See the GNU GPL for details. */ +/* */ +/* (c) Juan-Mariano de Goyeneche. 1998, 1999. */ +/**************************************************************/ + + +#include /* printf(), snprintf() */ +#include /* strtol(), exit() */ +#include +#include /* perror() */ +#include /* fork(), sleep() */ +#include /* uname() */ +#include /* memset() */ +#include /* openlog(), syslog() */ +#include "gpiosysfs.h" +#include //signal(3) +#include //umask(3) + + +#include +#include +#include +#include + +#include "mosquitto.h" + +#define DEBUG +#define MAXLEN 1024 + +#ifdef DEBUG +#define LOG(...) do { printf(__VA_ARGS__); } while (0) +#else +#define LOG(...) +#endif + +/* How many seconds the broker should wait between sending out + * keep-alive messages. */ +#define KEEPALIVE_SECONDS 60 + +/* Hostname and port for the MQTT broker. */ +char *mqtt_host; +int port; +char *topic; +char *pin; +int timeout; +bool debug=false; + +int daemonize(char* name, char* path, char* outfile, char* errfile, char* infile , char* pidfile) +{ + char filename[64]; + FILE *pf; + + if(!path) { path="/"; } + if(!name) { name="medaemon"; } + if(!infile) { infile="/dev/null"; } + if(!outfile) { outfile="/dev/null"; } + if(!errfile) { errfile="/dev/null"; } + if(!pidfile) { + snprintf(filename,63,"/var/run/%s.pid",name); + pidfile = filename; + } + + //printf("%s %s %s %s\n",name,path,outfile,infile); + pid_t child; + //fork, detach from process group leader + if( (child=fork())<0 ) { //failed fork + fprintf(stderr,"error: failed fork\n"); + exit(EXIT_FAILURE); + } + if (child>0) { //parent + exit(EXIT_SUCCESS); + } + if( setsid()<0 ) { //failed to become session leader + fprintf(stderr,"error: failed setsid\n"); + exit(EXIT_FAILURE); + } + + //catch/ignore signals + signal(SIGCHLD,SIG_IGN); + signal(SIGHUP,SIG_IGN); + + //fork second time + if ( (child=fork())<0) { //failed fork + fprintf(stderr,"error: failed fork\n"); + exit(EXIT_FAILURE); + } + if( child>0 ) { //parent + exit(EXIT_SUCCESS); + } + + //new file permissions + umask(0); + //change to path directory + chdir(path); + + // open pid file + pf = fopen(pidfile,"w+"); + if (pf == NULL) { + fprintf(stderr,"error: failed open pid file\n"); + exit(EXIT_FAILURE); + } else { + fprintf(pf,"%d",getpid()); + fclose(pf); + } + + //Close all open file descriptors + int fd; + for( fd=sysconf(_SC_OPEN_MAX); fd>0; --fd ) + { + close(fd); + } + + //reopen stdin, stdout, stderr + freopen(infile,"r",stdin); //fd=0 + freopen(outfile,"w+",stdout); //fd=1 + freopen(errfile,"w+",stderr); //fd=2 + + //open syslog + openlog(name,LOG_PID,LOG_DAEMON); + + syslog (LOG_DAEMON, "Program started by User %d", getuid ()); + return(0); +} + +void beep() +{ + gpioWrite(pin,"1"); + msleep(timeout); + gpioWrite(pin,"0"); + msleep(timeout); +} + +struct client_info { + struct mosquitto *m; + pid_t pid; + uint32_t tick_ct; +}; + +static void die(const char *msg); +static struct mosquitto *init(struct client_info *info); +static bool set_callbacks(struct mosquitto *m); +static bool connect(struct mosquitto *m); +static int run_loop(struct client_info *info); + +/* Fail with an error message. */ +static void die(const char *msg) { + fprintf(stderr, "%s", msg); + exit(1); +} + +/* Initialize a mosquitto client. */ +static struct mosquitto *init(struct client_info *info) { + void *udata = (void *)info; + size_t buf_sz = 32; + char buf[buf_sz]; + if (buf_sz < snprintf(buf, buf_sz, "bellrecv_%d", info->pid)) { + return NULL; /* snprintf buffer failure */ + } + /* Create a new mosquitto client, with the name "client_#{PID}". */ + LOG("Init\n"); + mosquitto_lib_init(); + struct mosquitto *m = mosquitto_new(buf, true, udata); + + return m; +} + +/* Callback for successful connection: add subscriptions. */ +static void on_connect(struct mosquitto *m, void *udata, int res) { + if (res == 0) { /* success */ + LOG("connecion successfull\n"); + + //struct client_info *info = (struct client_info *)udata; + mosquitto_subscribe(m, NULL, topic, 0); + } else { + die("connection refused\n"); + } +} + +/* A message was successfully published. */ +static void on_publish(struct mosquitto *m, void *udata, int m_id) { + LOG("-- published successfully\n"); +} + +static bool match(const char *topic, const char *key) { + return 0 == strncmp(topic, key, strlen(key)); +} + +/* Handle a message that just arrived via one of the subscriptions. */ +static void on_message(struct mosquitto *m, void *udata, + const struct mosquitto_message *msg) { + if (msg == NULL) { + LOG("-- message NULL\n"); + return; + } + LOG("-- got message @ %s: (%d, QoS %d, %s) '%s'\n", + msg->topic, msg->payloadlen, msg->qos, msg->retain ? "R" : "!r", + (char *)msg->payload); + + struct client_info *info = (struct client_info *)udata; + + syslog(LOG_DAEMON, "Bell message recived: '%s' -> '%s'", msg->topic, (char *)msg->payload); + if (match(msg->topic, topic)) { + if (0 == strncmp(msg->payload, "BELL:", 5)) { + LOG("%s %d\n",(char *)msg->payload, info->pid); + syslog (LOG_DAEMON, "Bell ringing by message: %s",(char *)msg->payload); + if (debug == false) beep(); + } else { + LOG("invalid message: '%s' excepted 'BELL:'\n",(char *)msg->payload); + } + } +} + +/* Successful subscription hook. */ +static void on_subscribe(struct mosquitto *m, void *udata, int mid, + int qos_count, const int *granted_qos) { + LOG("-- subscribed successfully on topic: '%s'\n",topic); + syslog (LOG_DAEMON, "Bell subscibed on topic %s" ,topic); +} + +/* Register the callbacks that the mosquitto connection will use. */ +static bool set_callbacks(struct mosquitto *m) { + mosquitto_connect_callback_set(m, on_connect); + mosquitto_publish_callback_set(m, on_publish); + mosquitto_subscribe_callback_set(m, on_subscribe); + mosquitto_message_callback_set(m, on_message); + return true; +} + +/* Connect to the network. */ +static bool connect(struct mosquitto *m) { + int res = mosquitto_connect(m, mqtt_host, port, KEEPALIVE_SECONDS); + return res == MOSQ_ERR_SUCCESS; +} + +/* Loop until it is explicitly halted or the network is lost, then clean up. */ +static int run_loop(struct client_info *info) { + int res = mosquitto_loop_forever(info->m, 1000, 1000 /* unused */); + + mosquitto_destroy(info->m); + (void)mosquitto_lib_cleanup(); + + closelog(); + if (res == MOSQ_ERR_SUCCESS) { + return 0; + } else { + return 1; + } +} + +int main(int argc, char* argv[]) +{ + int res; + + if (argc < 6) { + fprintf(stderr, "Usage: %s mqtt_host port topic gpio seconds [debug]\n", argv[0]); + exit(1); + } + + mqtt_host = argv[1]; + port = atoi(argv[2]); + topic = argv[3]; + pin = argv[4]; + timeout = atoi(argv[5])*1000; //To milis + + if (argc == 6) { + + if( (res=daemonize("bellring","/tmp",NULL,NULL,NULL,NULL)) != 0 ) { + fprintf(stderr,"error: daemonize failed\n"); + exit(EXIT_FAILURE); + } + } else { debug = true ;}; + + if (debug == false) gpioSetup(pin); + + pid_t pid = getpid(); + + struct client_info info; + memset(&info, 0, sizeof(info)); + info.pid = pid; + + struct mosquitto *m = init(&info); + if (m == NULL) { die("init() failure\n"); } + info.m = m; + + if (!set_callbacks(m)) { die("set_callbacks() failure\n"); } + + if (!connect(m)) { die("connect() failure\n"); } + + return run_loop(&info); +} + diff --git a/bell-rspro.init b/bell-rspro.init new file mode 100644 index 0000000..6af4097 --- /dev/null +++ b/bell-rspro.init @@ -0,0 +1,22 @@ +#!/bin/sh /etc/rc.common +# "new(er)" style init script +# Look at /lib/functions/service.sh on a running system for explanations of what other SERVICE_ +# options you can use, and when you might want them. + +START=80 +APP=bell-mqtt-recv +SERVICE_WRITE_PID=1 +SERVICE_DAEMONIZE=1 +HOST=172.16.1.254 +PORT=1883 +TOPIC=home/bell/ring +GPIO=7 +SECONDS=1 + +start() { + service_start /usr/sbin/$APP $HOST $PORT $TOPIC $GPIO $SECONDS +} + +stop() { + service_stop /usr/sbin/$APP +} diff --git a/gpiosysfs.c b/gpiosysfs.c new file mode 100644 index 0000000..1efb33b --- /dev/null +++ b/gpiosysfs.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include +#include /* printf(), snprintf() */ +#include /* strtol(), exit() */ +#include +#include +#include +#include "gpiosysfs.h" + +static void writeToFile(const char *absoluteFileName, const char *contents) { + int fd = open(absoluteFileName, O_WRONLY); + + if (-1 == fd) { + fprintf(stderr, "Couldn't open %s for writing!\n", absoluteFileName); + exit(1); + } + + int contentsLength = strlen(contents); + + if (write(fd, contents, contentsLength) != contentsLength) { + fprintf(stderr, "Failed to write entire value %s to %s!\n", contents, absoluteFileName); + close(fd); + exit(1); + } + + close(fd); +} + +void gpioSetup(const char *pin) { + // TODO only export if not already exported... + if (access("/sys/class/gpio/unexport",W_OK) == 0) + writeToFile("/sys/class/gpio/unexport", pin); + writeToFile("/sys/class/gpio/export", pin); + + char buf[33]; + struct stat statBuf; + int pinExported = -1; + + sprintf(buf, "/sys/class/gpio/gpio%s/direction", pin); + + // May have to briefly wait for OS to make symlink! + while (pinExported != 0) { + msleep(1000); + pinExported = stat(buf, &statBuf); + } + + writeToFile(buf, "out"); +} + +void gpioCleanup(const char *pin) { + writeToFile("/sys/class/gpio/unexport", pin); +} + +void gpioWrite(const char *pin, const char *val) { + char buf[29]; + sprintf(buf, "/sys/class/gpio/gpio%s/value", pin); + writeToFile(buf, val); +} + +/* msleep(): Sleep for the requested number of milliseconds. */ +int msleep(long msec) +{ + struct timespec ts; + int res; + + if (msec < 0) + { + errno = EINVAL; + return -1; + } + + ts.tv_sec = msec / 1000; + ts.tv_nsec = (msec % 1000) * 1000000; + + do { + res = nanosleep(&ts, &ts); + } while (res && errno == EINTR); + + return res; +} diff --git a/gpiosysfs.h b/gpiosysfs.h new file mode 100644 index 0000000..576525a --- /dev/null +++ b/gpiosysfs.h @@ -0,0 +1,13 @@ +/* gpiosysfs.h */ + +#include +#include +#include +#include +#include +#include + +void gpioSetup(const char *pin); +void gpioCleanup(const char *pin); +void gpioWrite(const char *pin, const char *val); +int msleep(long msec); \ No newline at end of file