first commit 1

master
sfeakes 2018-06-16 16:58:31 -05:00
parent 4a05b5ffa4
commit 51313f986f
13 changed files with 1814 additions and 0 deletions

61
Makefile Executable file
View File

@ -0,0 +1,61 @@
# define the C compiler to use
CC = gcc
LIBS := -lpthread -lwiringPi -lwiringPiDev -lm
#LIBS := -lpthread -lwebsockets
# debug of not
#$DBG = -g
$DBG =
# define any compile-time flags
#CFLAGS = -Wall -lpthread -lwiringPi -lwiringPiDev -lm -I. -I./minIni
CFLAGS = -Wall -I. -I./minIni $(DBG) $(LIBS) -D MG_DISABLE_MD5 -D MG_DISABLE_HTTP_DIGEST_AUTH -D MG_DISABLE_MD5 -D MG_DISABLE_JSON_RPC
#CFLAGS = -Wall -g -I./wiringPI
# define the C source files
SRCS = sprinkler.c utils.c config.c net_services.c json_messages.c zone_ctrl.c sd_cron.c mongoose.c minIni/minIni.c
# define the C object files
#
# This uses Suffix Replacement within a macro:
# $(name:string1=string2)
# For each word in 'name' replace 'string1' with 'string2'
# Below we are replacing the suffix .c of all words in the macro SRCS
# with the .o suffix
#
OBJS = $(SRCS:.c=.o)
# define the executable file
MAIN = ./release/sprinklerd
#
# The following part of the makefile is generic; it can be used to
# build any executable just by changing the definitions above and by
# deleting dependencies appended to the file from 'make depend'
#
.PHONY: depend clean
all: $(MAIN)
@echo: $(MAIN) have been compiled
$(MAIN): $(OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $(MAIN) $(OBJS) $(LFLAGS) $(LIBS)
# this is a suffix replacement rule for building .o's from .c's
# it uses automatic variables $<: the name of the prerequisite of
# the rule(a .c file) and $@: the name of the target of the rule (a .o file)
# (see the gnu make manual section about automatic variables)
.c.o:
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean:
$(RM) *.o *~ $(MAIN) $(MAIN_U)
depend: $(SRCS)
makedepend $(INCLUDES) $^
install: $(MAIN)
./release/install.sh

1
README.md Normal file
View File

@ -0,0 +1 @@
# sprinklerd

31
minIni/minGlue.h Normal file
View File

@ -0,0 +1,31 @@
/* Glue functions for the minIni library, based on the C/C++ stdio library
*
* Or better said: this file contains macros that maps the function interface
* used by minIni to the standard C/C++ file I/O functions.
*
* By CompuPhase, 2008-2014
* This "glue file" is in the public domain. It is distributed without
* warranties or conditions of any kind, either express or implied.
*/
/* map required file I/O types and functions to the standard C library */
#include <stdio.h>
#define INI_FILETYPE FILE*
#define ini_openread(filename,file) ((*(file) = fopen((filename),"rb")) != NULL)
#define ini_openwrite(filename,file) ((*(file) = fopen((filename),"wb")) != NULL)
#define ini_openrewrite(filename,file) ((*(file) = fopen((filename),"r+b")) != NULL)
#define ini_close(file) (fclose(*(file)) == 0)
#define ini_read(buffer,size,file) (fgets((buffer),(size),*(file)) != NULL)
#define ini_write(buffer,file) (fputs((buffer),*(file)) >= 0)
#define ini_rename(source,dest) (rename((source), (dest)) == 0)
#define ini_remove(filename) (remove(filename) == 0)
#define INI_FILEPOS long int
#define ini_tell(file,pos) (*(pos) = ftell(*(file)))
#define ini_seek(file,pos) (fseek(*(file), *(pos), SEEK_SET) == 0)
/* for floating-point support, define additional types and functions */
#define INI_REAL float
#define ini_ftoa(string,value) sprintf((string),"%f",(value))
#define ini_atof(string) (INI_REAL)strtod((string),NULL)

878
minIni/minIni.c Normal file
View File

@ -0,0 +1,878 @@
/* minIni - Multi-Platform INI file parser, suitable for embedded systems
*
* These routines are in part based on the article "Multiplatform .INI Files"
* by Joseph J. Graf in the March 1994 issue of Dr. Dobb's Journal.
*
* Copyright (c) CompuPhase, 2008-2017
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Version: $Id: minIni.c 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $
*/
#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY
# if !defined UNICODE /* for Windows */
# define UNICODE
# endif
# if !defined _UNICODE /* for C library */
# define _UNICODE
# endif
#endif
#define MININI_IMPLEMENTATION
#include "minIni.h"
#if defined NDEBUG
#define assert(e)
#else
#include <assert.h>
#endif
#if !defined __T || defined INI_ANSIONLY
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#define TCHAR char
#define __T(s) s
#define _tcscat strcat
#define _tcschr strchr
#define _tcscmp strcmp
#define _tcscpy strcpy
#define _tcsicmp stricmp
#define _tcslen strlen
#define _tcsncmp strncmp
#define _tcsnicmp strnicmp
#define _tcsrchr strrchr
#define _tcstol strtol
#define _tcstod strtod
#define _totupper toupper
#define _stprintf sprintf
#define _tfgets fgets
#define _tfputs fputs
#define _tfopen fopen
#define _tremove remove
#define _trename rename
#endif
#if defined __linux || defined __linux__
#define __LINUX__
#elif defined FREEBSD && !defined __FreeBSD__
#define __FreeBSD__
#elif defined(_MSC_VER)
#pragma warning(disable: 4996) /* for Microsoft Visual C/C++ */
#endif
#if !defined strnicmp && !defined PORTABLE_STRNICMP
#if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__
#define strnicmp strncasecmp
#endif
#endif
#if !defined _totupper
#define _totupper toupper
#endif
#if !defined INI_LINETERM
#if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__
#define INI_LINETERM __T("\n")
#else
#define INI_LINETERM __T("\r\n")
#endif
#endif
#if !defined INI_FILETYPE
#error Missing definition for INI_FILETYPE.
#endif
#if !defined sizearray
#define sizearray(a) (sizeof(a) / sizeof((a)[0]))
#endif
enum quote_option {
QUOTE_NONE,
QUOTE_ENQUOTE,
QUOTE_DEQUOTE,
};
#if defined PORTABLE_STRNICMP
int strnicmp(const TCHAR *s1, const TCHAR *s2, size_t n)
{
while (n-- != 0 && (*s1 || *s2)) {
register int c1, c2;
c1 = *s1++;
if ('a' <= c1 && c1 <= 'z')
c1 += ('A' - 'a');
c2 = *s2++;
if ('a' <= c2 && c2 <= 'z')
c2 += ('A' - 'a');
if (c1 != c2)
return c1 - c2;
} /* while */
return 0;
}
#endif /* PORTABLE_STRNICMP */
static TCHAR *skipleading(const TCHAR *str)
{
assert(str != NULL);
while ('\0' < *str && *str <= ' ')
str++;
return (TCHAR *)str;
}
static TCHAR *skiptrailing(const TCHAR *str, const TCHAR *base)
{
assert(str != NULL);
assert(base != NULL);
while (str > base && '\0' < *(str-1) && *(str-1) <= ' ')
str--;
return (TCHAR *)str;
}
static TCHAR *striptrailing(TCHAR *str)
{
TCHAR *ptr = skiptrailing(_tcschr(str, '\0'), str);
assert(ptr != NULL);
*ptr = '\0';
return str;
}
static TCHAR *ini_strncpy(TCHAR *dest, const TCHAR *source, size_t maxlen, enum quote_option option)
{
size_t d, s;
assert(maxlen>0);
assert(source != NULL && dest != NULL);
assert((dest < source || (dest == source && option != QUOTE_ENQUOTE)) || dest > source + strlen(source));
if (option == QUOTE_ENQUOTE && maxlen < 3)
option = QUOTE_NONE; /* cannot store two quotes and a terminating zero in less than 3 characters */
switch (option) {
case QUOTE_NONE:
for (d = 0; d < maxlen - 1 && source[d] != '\0'; d++)
dest[d] = source[d];
assert(d < maxlen);
dest[d] = '\0';
break;
case QUOTE_ENQUOTE:
d = 0;
dest[d++] = '"';
for (s = 0; source[s] != '\0' && d < maxlen - 2; s++, d++) {
if (source[s] == '"') {
if (d >= maxlen - 3)
break; /* no space to store the escape character plus the one that follows it */
dest[d++] = '\\';
} /* if */
dest[d] = source[s];
} /* for */
dest[d++] = '"';
dest[d] = '\0';
break;
case QUOTE_DEQUOTE:
for (d = s = 0; source[s] != '\0' && d < maxlen - 1; s++, d++) {
if ((source[s] == '"' || source[s] == '\\') && source[s + 1] == '"')
s++;
dest[d] = source[s];
} /* for */
dest[d] = '\0';
break;
default:
assert(0);
} /* switch */
return dest;
}
static TCHAR *cleanstring(TCHAR *string, enum quote_option *quotes)
{
int isstring;
TCHAR *ep;
assert(string != NULL);
assert(quotes != NULL);
/* Remove a trailing comment */
isstring = 0;
for (ep = string; *ep != '\0' && ((*ep != ';' && *ep != '#') || isstring); ep++) {
if (*ep == '"') {
if (*(ep + 1) == '"')
ep++; /* skip "" (both quotes) */
else
isstring = !isstring; /* single quote, toggle isstring */
} else if (*ep == '\\' && *(ep + 1) == '"') {
ep++; /* skip \" (both quotes */
} /* if */
} /* for */
assert(ep != NULL && (*ep == '\0' || *ep == ';' || *ep == '#'));
*ep = '\0'; /* terminate at a comment */
striptrailing(string);
/* Remove double quotes surrounding a value */
*quotes = QUOTE_NONE;
if (*string == '"' && (ep = _tcschr(string, '\0')) != NULL && *(ep - 1) == '"') {
string++;
*--ep = '\0';
*quotes = QUOTE_DEQUOTE; /* this is a string, so remove escaped characters */
} /* if */
return string;
}
static int getkeystring(INI_FILETYPE *fp, const TCHAR *Section, const TCHAR *Key,
int idxSection, int idxKey, TCHAR *Buffer, int BufferSize,
INI_FILEPOS *mark)
{
TCHAR *sp, *ep;
int len, idx;
enum quote_option quotes;
TCHAR LocalBuffer[INI_BUFFERSIZE];
assert(fp != NULL);
/* Move through file 1 line at a time until a section is matched or EOF. If
* parameter Section is NULL, only look at keys above the first section. If
* idxSection is postive, copy the relevant section name.
*/
len = (Section != NULL) ? (int)_tcslen(Section) : 0;
if (len > 0 || idxSection >= 0) {
assert(idxSection >= 0 || Section != NULL);
idx = -1;
do {
if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp))
return 0;
sp = skipleading(LocalBuffer);
ep = _tcsrchr(sp, ']');
} while (*sp != '[' || ep == NULL ||
(((int)(ep-sp-1) != len || Section == NULL || _tcsnicmp(sp+1,Section,len) != 0) && ++idx != idxSection));
if (idxSection >= 0) {
if (idx == idxSection) {
assert(ep != NULL);
assert(*ep == ']');
*ep = '\0';
ini_strncpy(Buffer, sp + 1, BufferSize, QUOTE_NONE);
return 1;
} /* if */
return 0; /* no more section found */
} /* if */
} /* if */
/* Now that the section has been found, find the entry.
* Stop searching upon leaving the section's area.
*/
assert(Key != NULL || idxKey >= 0);
len = (Key != NULL) ? (int)_tcslen(Key) : 0;
idx = -1;
do {
if (mark != NULL)
ini_tell(fp, mark); /* optionally keep the mark to the start of the line */
if (!ini_read(LocalBuffer,INI_BUFFERSIZE,fp) || *(sp = skipleading(LocalBuffer)) == '[')
return 0;
sp = skipleading(LocalBuffer);
ep = _tcschr(sp, '='); /* Parse out the equal sign */
if (ep == NULL)
ep = _tcschr(sp, ':');
} while (*sp == ';' || *sp == '#' || ep == NULL
|| ((len == 0 || (int)(skiptrailing(ep,sp)-sp) != len || _tcsnicmp(sp,Key,len) != 0) && ++idx != idxKey));
if (idxKey >= 0) {
if (idx == idxKey) {
assert(ep != NULL);
assert(*ep == '=' || *ep == ':');
*ep = '\0';
striptrailing(sp);
ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE);
return 1;
} /* if */
return 0; /* no more key found (in this section) */
} /* if */
/* Copy up to BufferSize chars to buffer */
assert(ep != NULL);
assert(*ep == '=' || *ep == ':');
sp = skipleading(ep + 1);
sp = cleanstring(sp, &quotes); /* Remove a trailing comment */
ini_strncpy(Buffer, sp, BufferSize, quotes);
return 1;
}
/** ini_gets()
* \param Section the name of the section to search for
* \param Key the name of the entry to find the value of
* \param DefValue default string in the event of a failed read
* \param Buffer a pointer to the buffer to copy into
* \param BufferSize the maximum number of characters to copy
* \param Filename the name and full path of the .ini file to read from
*
* \return the number of characters copied into the supplied buffer
*/
int ini_gets(const TCHAR *Section, const TCHAR *Key, const TCHAR *DefValue,
TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
{
INI_FILETYPE fp;
int ok = 0;
if (Buffer == NULL || BufferSize <= 0 || Key == NULL)
return 0;
if (ini_openread(Filename, &fp)) {
ok = getkeystring(&fp, Section, Key, -1, -1, Buffer, BufferSize, NULL);
(void)ini_close(&fp);
} /* if */
if (!ok) {
ini_strncpy(Buffer, (DefValue != NULL) ? DefValue : __T(""), BufferSize, QUOTE_NONE);
//Buffer = DefValue;
}
return (int)_tcslen(Buffer);
}
/** ini_getl()
* \param Section the name of the section to search for
* \param Key the name of the entry to find the value of
* \param DefValue the default value in the event of a failed read
* \param Filename the name of the .ini file to read from
*
* \return the value located at Key
*/
long ini_getl(const TCHAR *Section, const TCHAR *Key, long DefValue, const TCHAR *Filename)
{
TCHAR LocalBuffer[64];
int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
return (len == 0) ? DefValue
: ((len >= 2 && _totupper((int)LocalBuffer[1]) == 'X') ? _tcstol(LocalBuffer, NULL, 16)
: _tcstol(LocalBuffer, NULL, 10));
}
#if defined INI_REAL
/** ini_getf()
* \param Section the name of the section to search for
* \param Key the name of the entry to find the value of
* \param DefValue the default value in the event of a failed read
* \param Filename the name of the .ini file to read from
*
* \return the value located at Key
*/
INI_REAL ini_getf(const TCHAR *Section, const TCHAR *Key, INI_REAL DefValue, const TCHAR *Filename)
{
TCHAR LocalBuffer[64];
int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
return (len == 0) ? DefValue : ini_atof(LocalBuffer);
}
#endif
/** ini_getbool()
* \param Section the name of the section to search for
* \param Key the name of the entry to find the value of
* \param DefValue default value in the event of a failed read; it should
* zero (0) or one (1).
* \param Filename the name and full path of the .ini file to read from
*
* A true boolean is found if one of the following is matched:
* - A string starting with 'y' or 'Y'
* - A string starting with 't' or 'T'
* - A string starting with '1'
*
* A false boolean is found if one of the following is matched:
* - A string starting with 'n' or 'N'
* - A string starting with 'f' or 'F'
* - A string starting with '0'
*
* \return the true/false flag as interpreted at Key
*/
int ini_getbool(const TCHAR *Section, const TCHAR *Key, int DefValue, const TCHAR *Filename)
{
TCHAR LocalBuffer[2] = __T("");
int ret;
ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
LocalBuffer[0] = (TCHAR)_totupper((int)LocalBuffer[0]);
if (LocalBuffer[0] == 'Y' || LocalBuffer[0] == '1' || LocalBuffer[0] == 'T')
ret = 1;
else if (LocalBuffer[0] == 'N' || LocalBuffer[0] == '0' || LocalBuffer[0] == 'F')
ret = 0;
else
ret = DefValue;
return(ret);
}
/** ini_getsection()
* \param idx the zero-based sequence number of the section to return
* \param Buffer a pointer to the buffer to copy into
* \param BufferSize the maximum number of characters to copy
* \param Filename the name and full path of the .ini file to read from
*
* \return the number of characters copied into the supplied buffer
*/
int ini_getsection(int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
{
INI_FILETYPE fp;
int ok = 0;
if (Buffer == NULL || BufferSize <= 0 || idx < 0)
return 0;
if (ini_openread(Filename, &fp)) {
ok = getkeystring(&fp, NULL, NULL, idx, -1, Buffer, BufferSize, NULL);
(void)ini_close(&fp);
} /* if */
if (!ok)
*Buffer = '\0';
return (int)_tcslen(Buffer);
}
/** ini_getkey()
* \param Section the name of the section to browse through, or NULL to
* browse through the keys outside any section
* \param idx the zero-based sequence number of the key to return
* \param Buffer a pointer to the buffer to copy into
* \param BufferSize the maximum number of characters to copy
* \param Filename the name and full path of the .ini file to read from
*
* \return the number of characters copied into the supplied buffer
*/
int ini_getkey(const TCHAR *Section, int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
{
INI_FILETYPE fp;
int ok = 0;
if (Buffer == NULL || BufferSize <= 0 || idx < 0)
return 0;
if (ini_openread(Filename, &fp)) {
ok = getkeystring(&fp, Section, NULL, -1, idx, Buffer, BufferSize, NULL);
(void)ini_close(&fp);
} /* if */
if (!ok)
*Buffer = '\0';
return (int)_tcslen(Buffer);
}
#if !defined INI_NOBROWSE
/** ini_browse()
* \param Callback a pointer to a function that will be called for every
* setting in the INI file.
* \param UserData arbitrary data, which the function passes on the the
* \c Callback function
* \param Filename the name and full path of the .ini file to read from
*
* \return 1 on success, 0 on failure (INI file not found)
*
* \note The \c Callback function must return 1 to continue
* browsing through the INI file, or 0 to stop. Even when the
* callback stops the browsing, this function will return 1
* (for success).
*/
int ini_browse(INI_CALLBACK Callback, void *UserData, const TCHAR *Filename)
{
TCHAR LocalBuffer[INI_BUFFERSIZE];
int lenSec, lenKey;
enum quote_option quotes;
INI_FILETYPE fp;
if (Callback == NULL)
return 0;
if (!ini_openread(Filename, &fp))
return 0;
LocalBuffer[0] = '\0'; /* copy an empty section in the buffer */
lenSec = (int)_tcslen(LocalBuffer) + 1;
for ( ;; ) {
TCHAR *sp, *ep;
if (!ini_read(LocalBuffer + lenSec, INI_BUFFERSIZE - lenSec, &fp))
break;
sp = skipleading(LocalBuffer + lenSec);
/* ignore empty strings and comments */
if (*sp == '\0' || *sp == ';' || *sp == '#')
continue;
/* see whether we reached a new section */
ep = _tcsrchr(sp, ']');
if (*sp == '[' && ep != NULL) {
*ep = '\0';
ini_strncpy(LocalBuffer, sp + 1, INI_BUFFERSIZE, QUOTE_NONE);
lenSec = (int)_tcslen(LocalBuffer) + 1;
continue;
} /* if */
/* not a new section, test for a key/value pair */
ep = _tcschr(sp, '='); /* test for the equal sign or colon */
if (ep == NULL)
ep = _tcschr(sp, ':');
if (ep == NULL)
continue; /* invalid line, ignore */
*ep++ = '\0'; /* split the key from the value */
striptrailing(sp);
ini_strncpy(LocalBuffer + lenSec, sp, INI_BUFFERSIZE - lenSec, QUOTE_NONE);
lenKey = (int)_tcslen(LocalBuffer + lenSec) + 1;
/* clean up the value */
sp = skipleading(ep);
sp = cleanstring(sp, &quotes); /* Remove a trailing comment */
ini_strncpy(LocalBuffer + lenSec + lenKey, sp, INI_BUFFERSIZE - lenSec - lenKey, quotes);
/* call the callback */
if (!Callback(LocalBuffer, LocalBuffer + lenSec, LocalBuffer + lenSec + lenKey, UserData))
break;
} /* for */
(void)ini_close(&fp);
return 1;
}
#endif /* INI_NOBROWSE */
#if ! defined INI_READONLY
static void ini_tempname(TCHAR *dest, const TCHAR *source, int maxlength)
{
TCHAR *p;
ini_strncpy(dest, source, maxlength, QUOTE_NONE);
p = _tcsrchr(dest, '\0');
assert(p != NULL);
*(p - 1) = '~';
}
static enum quote_option check_enquote(const TCHAR *Value)
{
const TCHAR *p;
/* run through the value, if it has trailing spaces, or '"', ';' or '#'
* characters, enquote it
*/
assert(Value != NULL);
for (p = Value; *p != '\0' && *p != '"' && *p != ';' && *p != '#'; p++)
/* nothing */;
return (*p != '\0' || (p > Value && *(p - 1) == ' ')) ? QUOTE_ENQUOTE : QUOTE_NONE;
}
static void writesection(TCHAR *LocalBuffer, const TCHAR *Section, INI_FILETYPE *fp)
{
if (Section != NULL && _tcslen(Section) > 0) {
TCHAR *p;
LocalBuffer[0] = '[';
ini_strncpy(LocalBuffer + 1, Section, INI_BUFFERSIZE - 4, QUOTE_NONE); /* -1 for '[', -1 for ']', -2 for '\r\n' */
p = _tcsrchr(LocalBuffer, '\0');
assert(p != NULL);
*p++ = ']';
_tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */
if (fp != NULL)
(void)ini_write(LocalBuffer, fp);
} /* if */
}
static void writekey(TCHAR *LocalBuffer, const TCHAR *Key, const TCHAR *Value, INI_FILETYPE *fp)
{
TCHAR *p;
enum quote_option option = check_enquote(Value);
ini_strncpy(LocalBuffer, Key, INI_BUFFERSIZE - 3, QUOTE_NONE); /* -1 for '=', -2 for '\r\n' */
p = _tcsrchr(LocalBuffer, '\0');
assert(p != NULL);
*p++ = '=';
ini_strncpy(p, Value, INI_BUFFERSIZE - (p - LocalBuffer) - 2, option); /* -2 for '\r\n' */
p = _tcsrchr(LocalBuffer, '\0');
assert(p != NULL);
_tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */
if (fp != NULL)
(void)ini_write(LocalBuffer, fp);
}
static int cache_accum(const TCHAR *string, int *size, int max)
{
int len = (int)_tcslen(string);
if (*size + len >= max)
return 0;
*size += len;
return 1;
}
static int cache_flush(TCHAR *buffer, int *size,
INI_FILETYPE *rfp, INI_FILETYPE *wfp, INI_FILEPOS *mark)
{
int terminator_len = (int)_tcslen(INI_LINETERM);
int pos = 0;
(void)ini_seek(rfp, mark);
assert(buffer != NULL);
buffer[0] = '\0';
assert(size != NULL);
assert(*size <= INI_BUFFERSIZE);
while (pos < *size) {
(void)ini_read(buffer + pos, INI_BUFFERSIZE - pos, rfp);
while (pos < *size && buffer[pos] != '\0')
pos++; /* cannot use _tcslen() because buffer may not be zero-terminated */
} /* while */
if (buffer[0] != '\0') {
assert(pos > 0 && pos <= INI_BUFFERSIZE);
if (pos == INI_BUFFERSIZE)
pos--;
buffer[pos] = '\0'; /* force zero-termination (may be left unterminated in the above while loop) */
(void)ini_write(buffer, wfp);
}
ini_tell(rfp, mark); /* update mark */
*size = 0;
/* return whether the buffer ended with a line termination */
return (pos > terminator_len) && (_tcscmp(buffer + pos - terminator_len, INI_LINETERM) == 0);
}
static int close_rename(INI_FILETYPE *rfp, INI_FILETYPE *wfp, const TCHAR *filename, TCHAR *buffer)
{
(void)ini_close(rfp);
(void)ini_close(wfp);
(void)ini_remove(filename);
(void)ini_tempname(buffer, filename, INI_BUFFERSIZE);
(void)ini_rename(buffer, filename);
return 1;
}
/** ini_puts()
* \param Section the name of the section to write the string in
* \param Key the name of the entry to write, or NULL to erase all keys in the section
* \param Value a pointer to the buffer the string, or NULL to erase the key
* \param Filename the name and full path of the .ini file to write to
*
* \return 1 if successful, otherwise 0
*/
int ini_puts(const TCHAR *Section, const TCHAR *Key, const TCHAR *Value, const TCHAR *Filename)
{
INI_FILETYPE rfp;
INI_FILETYPE wfp;
INI_FILEPOS mark;
INI_FILEPOS head, tail;
TCHAR *sp, *ep;
TCHAR LocalBuffer[INI_BUFFERSIZE];
int len, match, flag, cachelen;
assert(Filename != NULL);
if (!ini_openread(Filename, &rfp)) {
/* If the .ini file doesn't exist, make a new file */
if (Key != NULL && Value != NULL) {
if (!ini_openwrite(Filename, &wfp))
return 0;
writesection(LocalBuffer, Section, &wfp);
writekey(LocalBuffer, Key, Value, &wfp);
(void)ini_close(&wfp);
} /* if */
return 1;
} /* if */
/* If parameters Key and Value are valid (so this is not an "erase" request)
* and the setting already exists, there are two short-cuts to avoid rewriting
* the INI file.
*/
if (Key != NULL && Value != NULL) {
ini_tell(&rfp, &mark);
match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), &head);
if (match) {
/* if the current setting is identical to the one to write, there is
* nothing to do.
*/
if (_tcscmp(LocalBuffer,Value) == 0) {
(void)ini_close(&rfp);
return 1;
} /* if */
/* if the new setting has the same length as the current setting, and the
* glue file permits file read/write access, we can modify in place.
*/
#if defined ini_openrewrite
/* we already have the start of the (raw) line, get the end too */
ini_tell(&rfp, &tail);
/* create new buffer (without writing it to file) */
writekey(LocalBuffer, Key, Value, NULL);
if (_tcslen(LocalBuffer) == (size_t)(tail - head)) {
/* length matches, close the file & re-open for read/write, then
* write at the correct position
*/
(void)ini_close(&rfp);
if (!ini_openrewrite(Filename, &wfp))
return 0;
(void)ini_seek(&wfp, &head);
(void)ini_write(LocalBuffer, &wfp);
(void)ini_close(&wfp);
return 1;
} /* if */
#endif
} /* if */
/* key not found, or different value & length -> proceed (but rewind the
* input file first)
*/
(void)ini_seek(&rfp, &mark);
} /* if */
/* Get a temporary file name to copy to. Use the existing name, but with
* the last character set to a '~'.
*/
ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE);
if (!ini_openwrite(LocalBuffer, &wfp)) {
(void)ini_close(&rfp);
return 0;
} /* if */
(void)ini_tell(&rfp, &mark);
cachelen = 0;
/* Move through the file one line at a time until a section is
* matched or until EOF. Copy to temp file as it is read.
*/
len = (Section != NULL) ? (int)_tcslen(Section) : 0;
if (len > 0) {
do {
if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
/* Failed to find section, so add one to the end */
flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
if (Key!=NULL && Value!=NULL) {
if (!flag)
(void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */
writesection(LocalBuffer, Section, &wfp);
writekey(LocalBuffer, Key, Value, &wfp);
} /* if */
return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
} /* if */
/* Copy the line from source to dest, but not if this is the section that
* we are looking for and this section must be removed
*/
sp = skipleading(LocalBuffer);
ep = _tcsrchr(sp, ']');
match = (*sp == '[' && ep != NULL && (int)(ep-sp-1) == len && _tcsnicmp(sp + 1,Section,len) == 0);
if (!match || Key != NULL) {
if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
(void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
} /* if */
} /* if */
} while (!match);
} /* if */
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
/* when deleting a section, the section head that was just found has not been
* copied to the output file, but because this line was not "accumulated" in
* the cache, the position in the input file was reset to the point just
* before the section; this must now be skipped (again)
*/
if (Key == NULL) {
(void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
(void)ini_tell(&rfp, &mark);
} /* if */
/* Now that the section has been found, find the entry. Stop searching
* upon leaving the section's area. Copy the file as it is read
* and create an entry if one is not found.
*/
len = (Key != NULL) ? (int)_tcslen(Key) : 0;
for( ;; ) {
if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
/* EOF without an entry so make one */
flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
if (Key!=NULL && Value!=NULL) {
if (!flag)
(void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */
writekey(LocalBuffer, Key, Value, &wfp);
} /* if */
return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
} /* if */
sp = skipleading(LocalBuffer);
ep = _tcschr(sp, '='); /* Parse out the equal sign */
if (ep == NULL)
ep = _tcschr(sp, ':');
match = (ep != NULL && len > 0 && (int)(skiptrailing(ep,sp)-sp) == len && _tcsnicmp(sp,Key,len) == 0);
if ((Key != NULL && match) || *sp == '[')
break; /* found the key, or found a new section */
/* copy other keys in the section */
if (Key == NULL) {
(void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */
} else {
if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
(void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
} /* if */
} /* if */
} /* for */
/* the key was found, or we just dropped on the next section (meaning that it
* wasn't found); in both cases we need to write the key, but in the latter
* case, we also need to write the line starting the new section after writing
* the key
*/
flag = (*sp == '[');
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
if (Key != NULL && Value != NULL)
writekey(LocalBuffer, Key, Value, &wfp);
/* cache_flush() reset the "read pointer" to the start of the line with the
* previous key or the new section; read it again (because writekey() destroyed
* the buffer)
*/
(void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
if (flag) {
/* the new section heading needs to be copied to the output file */
cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
} else {
/* forget the old key line */
(void)ini_tell(&rfp, &mark);
} /* if */
/* Copy the rest of the INI file */
while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
(void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
} /* if */
} /* while */
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
}
/* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" book. */
#define ABS(v) ((v) < 0 ? -(v) : (v))
static void strreverse(TCHAR *str)
{
int i, j;
for (i = 0, j = (int)_tcslen(str) - 1; i < j; i++, j--) {
TCHAR t = str[i];
str[i] = str[j];
str[j] = t;
} /* for */
}
static void long2str(long value, TCHAR *str)
{
int i = 0;
long sign = value;
/* generate digits in reverse order */
do {
int n = (int)(value % 10); /* get next lowest digit */
str[i++] = (TCHAR)(ABS(n) + '0'); /* handle case of negative digit */
} while (value /= 10); /* delete the lowest digit */
if (sign < 0)
str[i++] = '-';
str[i] = '\0';
strreverse(str);
}
/** ini_putl()
* \param Section the name of the section to write the value in
* \param Key the name of the entry to write
* \param Value the value to write
* \param Filename the name and full path of the .ini file to write to
*
* \return 1 if successful, otherwise 0
*/
int ini_putl(const TCHAR *Section, const TCHAR *Key, long Value, const TCHAR *Filename)
{
TCHAR LocalBuffer[32];
long2str(Value, LocalBuffer);
return ini_puts(Section, Key, LocalBuffer, Filename);
}
#if defined INI_REAL
/** ini_putf()
* \param Section the name of the section to write the value in
* \param Key the name of the entry to write
* \param Value the value to write
* \param Filename the name and full path of the .ini file to write to
*
* \return 1 if successful, otherwise 0
*/
int ini_putf(const TCHAR *Section, const TCHAR *Key, INI_REAL Value, const TCHAR *Filename)
{
TCHAR LocalBuffer[64];
ini_ftoa(LocalBuffer, Value);
return ini_puts(Section, Key, LocalBuffer, Filename);
}
#endif /* INI_REAL */
#endif /* !INI_READONLY */

152
minIni/minIni.h Normal file
View File

@ -0,0 +1,152 @@
/* minIni - Multi-Platform INI file parser, suitable for embedded systems
*
* Copyright (c) CompuPhase, 2008-2017
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Version: $Id: minIni.h 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $
*/
#ifndef MININI_H
#define MININI_H
#include "minGlue.h"
#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY
#include <tchar.h>
#define mTCHAR TCHAR
#else
/* force TCHAR to be "char", but only for minIni */
#define mTCHAR char
#endif
#if !defined INI_BUFFERSIZE
#define INI_BUFFERSIZE 512
#endif
#if defined __cplusplus
extern "C" {
#endif
int ini_getbool(const mTCHAR *Section, const mTCHAR *Key, int DefValue, const mTCHAR *Filename);
long ini_getl(const mTCHAR *Section, const mTCHAR *Key, long DefValue, const mTCHAR *Filename);
int ini_gets(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *DefValue, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename);
int ini_getsection(int idx, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename);
int ini_getkey(const mTCHAR *Section, int idx, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename);
#if defined INI_REAL
INI_REAL ini_getf(const mTCHAR *Section, const mTCHAR *Key, INI_REAL DefValue, const mTCHAR *Filename);
#endif
#if !defined INI_READONLY
int ini_putl(const mTCHAR *Section, const mTCHAR *Key, long Value, const mTCHAR *Filename);
int ini_puts(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, const mTCHAR *Filename);
#if defined INI_REAL
int ini_putf(const mTCHAR *Section, const mTCHAR *Key, INI_REAL Value, const mTCHAR *Filename);
#endif
#endif /* INI_READONLY */
#if !defined INI_NOBROWSE
typedef int (*INI_CALLBACK)(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData);
int ini_browse(INI_CALLBACK Callback, void *UserData, const mTCHAR *Filename);
#endif /* INI_NOBROWSE */
#if defined __cplusplus
}
#endif
#if defined __cplusplus
#if defined __WXWINDOWS__
#include "wxMinIni.h"
#else
#include <string>
/* The C++ class in minIni.h was contributed by Steven Van Ingelgem. */
class minIni
{
public:
minIni(const std::string& filename) : iniFilename(filename)
{ }
bool getbool(const std::string& Section, const std::string& Key, bool DefValue=false) const
{ return ini_getbool(Section.c_str(), Key.c_str(), int(DefValue), iniFilename.c_str()) != 0; }
long getl(const std::string& Section, const std::string& Key, long DefValue=0) const
{ return ini_getl(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); }
int geti(const std::string& Section, const std::string& Key, int DefValue=0) const
{ return static_cast<int>(this->getl(Section, Key, long(DefValue))); }
std::string gets(const std::string& Section, const std::string& Key, const std::string& DefValue="") const
{
char buffer[INI_BUFFERSIZE];
ini_gets(Section.c_str(), Key.c_str(), DefValue.c_str(), buffer, INI_BUFFERSIZE, iniFilename.c_str());
return buffer;
}
std::string getsection(int idx) const
{
char buffer[INI_BUFFERSIZE];
ini_getsection(idx, buffer, INI_BUFFERSIZE, iniFilename.c_str());
return buffer;
}
std::string getkey(const std::string& Section, int idx) const
{
char buffer[INI_BUFFERSIZE];
ini_getkey(Section.c_str(), idx, buffer, INI_BUFFERSIZE, iniFilename.c_str());
return buffer;
}
#if defined INI_REAL
INI_REAL getf(const std::string& Section, const std::string& Key, INI_REAL DefValue=0) const
{ return ini_getf(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); }
#endif
#if ! defined INI_READONLY
bool put(const std::string& Section, const std::string& Key, long Value) const
{ return ini_putl(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; }
bool put(const std::string& Section, const std::string& Key, int Value) const
{ return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; }
bool put(const std::string& Section, const std::string& Key, bool Value) const
{ return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; }
bool put(const std::string& Section, const std::string& Key, const std::string& Value) const
{ return ini_puts(Section.c_str(), Key.c_str(), Value.c_str(), iniFilename.c_str()) != 0; }
bool put(const std::string& Section, const std::string& Key, const char* Value) const
{ return ini_puts(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; }
#if defined INI_REAL
bool put(const std::string& Section, const std::string& Key, INI_REAL Value) const
{ return ini_putf(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; }
#endif
bool del(const std::string& Section, const std::string& Key) const
{ return ini_puts(Section.c_str(), Key.c_str(), 0, iniFilename.c_str()) != 0; }
bool del(const std::string& Section) const
{ return ini_puts(Section.c_str(), 0, 0, iniFilename.c_str()) != 0; }
#endif
private:
std::string iniFilename;
};
#endif /* __WXWINDOWS__ */
#endif /* __cplusplus */
#endif /* MININI_H */

97
release/install.sh Executable file
View File

@ -0,0 +1,97 @@
#!/bin/bash
#
# ROOT=/nas/data/Development/Raspberry/gpiocrtl/test-install
#
BUILD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SERVICE="sprinklerd"
BIN="sprinklerd"
CFG="sprinklerd.conf"
SRV="sprinklerd.service"
DEF="sprinklerd"
MDNS="sprinklerd.service"
BINLocation="/usr/local/bin"
CFGLocation="/etc"
SRVLocation="/etc/systemd/system"
DEFLocation="/etc/default"
WEBLocation="/var/www/sprinklerd/"
MDNSLocation="/etc/avahi/services/"
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
if [[ $(mount | grep " / " | grep "(ro,") ]]; then
echo "Root filesystem is readonly, can't install"
exit 1
fi
# Check cron.d options
if [ ! -d "/etc/cron.d" ]; then
echo "The version of Cron may not support chron.d, if so the calendar will not work"
echo "Please check before starting"
else
if [ -f "/etc/default/cron" ]; then
CD=$(cat /etc/default/cron | grep -v ^# | grep "\-l")
if [ -z "$CD" ]; then
echo "Please enabled cron.d support, if not the calendar will not work"
echo "Edit /etc/default/cron and look for the -l option"
fi
else
echo "Please make sure the version if Cron supports chron.d, if not the calendar will not work"
fi
fi
# Exit if we can't find systemctl
command -v systemctl >/dev/null 2>&1 || { echo "This script needs systemd's systemctl manager, Please check path or install manually" >&2; exit 1; }
# stop service, hide any error, as the service may not be installed yet
systemctl stop $SERVICE > /dev/null 2>&1
SERVICE_EXISTS=$(echo $?)
# copy files to locations, but only copy cfg if it doesn;t already exist
cp $BUILD/$BIN $BINLocation/$BIN
cp $BUILD/$SRV $SRVLocation/$SRV
if [ -f $CFGLocation/$CFG ]; then
echo "Config exists, did not copy new config, you may need to edit existing! $CFGLocation/$CFG"
else
cp $BUILD/$CFG $CFGLocation/$CFG
fi
if [ -f $DEFLocation/$DEF ]; then
echo "Defaults exists, did not copy new defaults to $DEFLocation/$DEF"
else
cp $BUILD/$DEF.defaults $DEFLocation/$DEF
fi
if [ -f $MDNSLocation/$MDNS ]; then
echo "Avahi/mDNS defaults exists, did not copy new defaults to $MDNSLocation/$MDNS"
else
if [ -d "$MDNSLocation" ]; then
cp $BUILD/$MDNS.avahi $MDNSLocation/$MDNS
else
echo "Avahi/mDNS may not be installed, not copying $MDNSLocation/$MDNS"
fi
fi
if [ ! -d "$WEBLocation" ]; then
mkdir -p $WEBLocation
fi
cp -r $BUILD/../web/* $WEBLocation
systemctl enable $SERVICE
systemctl daemon-reload
if [ $SERVICE_EXISTS -eq 0 ]; then
echo "Starting daemon $SERVICE"
systemctl start $SERVICE
fi

BIN
release/sprinklerd Executable file

Binary file not shown.

106
release/sprinklerd.conf Normal file
View File

@ -0,0 +1,106 @@
[SPRINKLERD]
PORT=80
NAME=Feakes Sprinkler
#DOCUMENTROOT = /var/www/gpioctrld/
DOCUMENTROOT = /nas/data/Development/Raspberry/sprinklerd/web/
#DEBUG2LOGFILE = yes
CACHE = /var/cache/sprinklerd.cache
# The log level. [DEBUG, INFO, NOTICE, WARNING, ERROR]
#log_level = DEBUG
log_level = INFO
# mqtt stuff
mqtt_address = trident:1883
#mqtt_user = someusername
#mqtt_passwd = somepassword
mqtt_topic = sprinklerd
#mqtt_dz_pub_topic = domoticz/in
#mqtt_dz_sub_topic = domoticz/out
# LOW 0
# HIGH 1
# PUD_OFF 0
# PUD_DOWN 1
# PUD_UP 2
#NAME = name or texT
#GPIO_PIN = GPIO Pin # This is WiringPi Pin#, not Raspberry Pi board pin#.
#GPIO_PULL_UPDN = setup pull up pull down resistor NONE|PUD_OFF|PUD_DOWN|PUD_UP
#GPIO_ON_STATE = State GPIO reads when relay for zone is on. HIGH or LOW 1 or 0
#DOMOTICZ_IDX = Domoticz IDX 0 or remove entry if you don;t use Domoticz
#MASTER_VALVE = YES=1 NO=0 // turn on with any zone
# Don't use ZONE:0 for anything other than master valve, if you do't have a master valve simply delete it and start from ZONE:1
[ZONE]
[ZONE:0]
NAME=Master Valve
MASTER_VALVE=1
GPIO_PIN=0
GPIO_PULL_UPDN=1
GPIO_ON_STATE=0
DOMOTICZ_IDX=999
[ZONE:1]
NAME=Island
DEFAULT_RUNTIME=10
GPIO_PIN=1
GPIO_PULL_UPDN=1
GPIO_ON_STATE=0
DOMOTICZ_IDX=998
[ZONE:2]
NAME=Driveway
DEFAULT_RUNTIME=10
GPIO_PIN=2
GPIO_PULL_UPDN=1
GPIO_ON_STATE=0
DOMOTICZ_IDX=997
[ZONE:3]
NAME=Diningroom Flowerbeds
DEFAULT_RUNTIME=10
GPIO_PIN=3
GPIO_PULL_UPDN=1
GPIO_ON_STATE=0
DOMOTICZ_IDX=997
[ZONE:4]
NAME=Front Flowerbeds
DEFAULT_RUNTIME=10
GPIO_PIN=4
GPIO_PULL_UPDN=1
GPIO_ON_STATE=0
DOMOTICZ_IDX=997
[ZONE:5]
NAME=Backgarden Left
DEFAULT_RUNTIME=10
GPIO_PIN=5
GPIO_PULL_UPDN=1
GPIO_ON_STATE=0
DOMOTICZ_IDX=997
[ZONE:6]
NAME=Backgarden Right
DEFAULT_RUNTIME=10
GPIO_PIN=6
GPIO_PULL_UPDN=1
GPIO_ON_STATE=0
DOMOTICZ_IDX=997
[ZONE:7]
NAME=Garage Flowerbeds
DEFAULT_RUNTIME=10
GPIO_PIN=21
GPIO_PULL_UPDN=1
GPIO_ON_STATE=0
DOMOTICZ_IDX=997
[ZONE:8]
NAME=Golfcart path
DEFAULT_RUNTIME=10
GPIO_PIN=22
GPIO_PULL_UPDN=1
GPIO_ON_STATE=0
DOMOTICZ_IDX=997

View File

@ -0,0 +1,5 @@
# Defaults / Configuration options for aqualinkd
#
# -c config file
OPTS = -c /etc/sprinklerd.conf

21
release/sprinklerd.service Executable file
View File

@ -0,0 +1,21 @@
[Unit]
Description=Aqualink RS daemon
ConditionPathExists=/etc/sprinklerd.conf
After=network.target multi-user.target
Requires=network.target
[Service]
Type=forking
#RemainAfterExit=no
#StartLimitInterval=0
PIDFile=/run/sprinklerd.pid
EnvironmentFile=/etc/default/sprinklerd
ExecStart=/usr/local/bin/sprinklerd $OPTS
ExecReload=/bin/kill -HUP $MAINPID
#Restart=on-failure
KillMode=process
RestartSec=2
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,9 @@
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
<name replace-wildcards="yes">%h HTTP</name>
<service>
<type>_http._tcp</type>
<port>80</port>
</service>
</service-group>

5
sprinkler.cron Normal file
View File

@ -0,0 +1,5 @@
00 08 * * 1 /usr/bin/curl 'localhost?type=cron&zone=1&runtime=12'
12 08 * * 1 /usr/bin/curl 'localhost?type=cron&zone=2&runtime=10'
22 08 * * 1 /usr/bin/curl 'localhost?type=cron&zone=3&runtime=8'
00 08 * * 2 /usr/bin/curl 'localhost?type=cron&zone=1&runtime=8'
00 22 * * 3 /usr/bin/curl 'localhost?type=cron&zone=2&runtime=10'

448
web/index.html Normal file
View File

@ -0,0 +1,448 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=windows-1252'>
<title>Sprinkler</title>
<meta name='viewport' content='width=device-width'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name='apple-mobile-web-app-status-bar-style' content='black'>
<style>
:root {
--diameter: 30px;
}
html {
}
body {
font-family: 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-weight: 300;
background-color: #f1f1f1;
color: #000000;
margin: 0 !important;
padding: 0 !important;
}
table,
th,
td {
border-collapse: collapse;
padding: 10px;
text-align: left;
border-bottom: 1px solid rgb(189, 189, 189);
width: 100%;
}
th {
width: 100vw;
padding: 15px;
text-align: center;
background-color: #f1f1f1;
}
tr {
background-color: white;
color: black;
}
.button {
background-color: white;
border-radius: 15px;
-webkit-border-radius: 15px;
-moz-border-radius: 15px;
height: 30px
}
.opaque {
top: 0px;
left: 0px;
background-color: black;
filter: alpha(opacity=20);
opacity: 0.2;
}
.show {
top: 0px;
left: 0px;
}
.message {
position: fixed;
top: 0px;
left: 0px;
height: 100%;
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
}
.message-text {
background-color: black;
color: white;
height: auto;
padding: 15px;
font-size: 20px;
text-align: center;
position: relative;
border-radius: var(--diameter);
}
.option_button {
position: fixed;
top: 2px;
left: 2px;
z-index: 900;
}
.options {
top: 0px;
left: 0px;
z-index: 800;
display: none;
}
.input-text {
font-size: 16px;
}
.input-ios {
font-size: 16px;
}
.small-note {
font-size: 12px;
}
.btn-toggle {
position: absolute;
margin-left: -9999px;
visibility: hidden;
}
.btn-toggle+label {
display: block;
position: relative;
cursor: pointer;
outline: none;
user-select: none;
}
input.btn-toggle-round+label {
padding: 2px;
width: calc(2 * var(--diameter));
height: var(--diameter);
background-color: #dddddd;
border-radius: var(--diameter);
}
input.btn-toggle-round+label:before,
input.btn-toggle-round+label:after {
display: block;
position: absolute;
top: 1px;
left: 1px;
bottom: 1px;
content: '';
}
input.btn-toggle-round+label:before {
right: 1px;
background-color: #f1f1f1;
border-radius: var(--diameter);
transition: background 0.4s;
}
input.btn-toggle-round+label:after {
width: var(--diameter);
background-color: #ffffff;
border-radius: 100%;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
transition: margin 0.4s;
}
input.btn-toggle-round:checked+label:before {
background-color: rgb(76, 217, 100);
}
input.btn-toggle-round:checked+label:after {
margin-left: var(--diameter);
}
</style>
<script type='text/javascript'>
var _poller;
var _showingConfig=false;
var _width;
function setSizeSpecifics() {
_width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
/*
Future change element sizes here if needed
*/
}
function addHeadIcon(rel) {
var image = 'data:image/png;base64,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';
var link = document.createElement('link');
link.rel = rel;
link.type= 'image/png';
link.href = image;
document.head.appendChild(link);
}
function headIcons() {
addHeadIcon('shortcut icon');
addHeadIcon('apple-touch-icon');
addHeadIcon('apple-touch-startup-image');
}
function hidemessage() {
if (_showingConfig == false) {
document.getElementById('main').className = 'show';
} else {
document.getElementById('options').className = 'show';
}
document.getElementById('message').style.display = 'none';
}
function showmessage() {
if (_showingConfig == false) {
document.getElementById('main').className = 'opaque';
} else {
document.getElementById('options').className = 'opaque';
}
document.getElementById('message').style.display = 'flex';
}
function switchOptionsPane() {
if (_showingConfig == true) {
document.getElementById('options').style.display = 'none';
document.getElementById('main').style.display = 'flex';
_showingConfig = false;
} else {
document.getElementById('options').style.display = 'flex';
document.getElementById('main').style.display = 'none';
cleanupCalendar();
_showingConfig = true;
}
}
function calendarhtmlrow(title, daynum, zones) {
html = '</tr><tr><td>'+title+'</td> \
<td><input type="time" id="d'+daynum+'-starttime" class="input-ios" onchange="update(this);"></td>';
for (i=1; i<= zones; i++) {
html = html+'<td><input type="number" id="d'+daynum+'z'+i+'-runtime" class="input-ios" onchange="update(this);" pattern="[0-9]*" maxlength="2" inputmode="numeric" min="0" max="30" step="1"></td>';
}
html = html+'</tr>';
return html;
}
function setzonehtml(zones) {
html = '';
for (i=1; i<= zones; i++) {
html = html+'<tr> \
<td>Zone&nbsp;'+i+'</td> \
<td><label id="z'+i+'-name" class="small-note"></label></td> \
<td>Runtime</td> \
<td><input type="number" id="z'+i+'-runtime" onchange="update(this);" class="input-ios" pattern="[0-9]*" maxlength="2" inputmode="numeric" min="1" max="30" step="1"></td> \
<td> \
<div class="switch"> \
<input id="btn-toggle-z'+i+'" name="z'+i+'" class="btn-toggle btn-toggle-round" type="checkbox" onclick="update(this);"> \
<label for="btn-toggle-z'+i+'"></label> \
</div> \
</td> \
</tr>';
}
rnz = document.getElementById('main');
rnz.innerHTML = rnz.innerHTML.replace("<!-- AUTO HTML -->", html);
html = '<tr><th colspan="'+(zones+2)+'"> Configuration </th></tr> \
<tr> \
<td>&nbsp;</td> \
<td>Start Time</td>';
for (i=1; i<= zones; i++) {
html = html+'<td>Z'+i+'</td>';
}
html = html+calendarhtmlrow("Mon", 1, zones);
html = html+calendarhtmlrow("Tue", 2, zones);
html = html+calendarhtmlrow("Wed", 3, zones);
html = html+calendarhtmlrow("Thu", 4, zones);
html = html+calendarhtmlrow("Fri", 5, zones);
html = html+calendarhtmlrow("Sat", 6, zones);
html = html+calendarhtmlrow("Sun", 0, zones);
rnz = document.getElementById('options');
rnz.innerHTML = rnz.innerHTML.replace("<!-- AUTO HTML -->", html);
}
function cleanupCalendar() {
for (i = 0; i < 7; i++) {
st = document.getElementById('d'+i+'-starttime')
if (st != null) {
if (st.value != ''){show=true}else{show=false}
for (j=1; null != (rt = document.getElementById('d'+i+'z'+j+'-runtime'));j++) {
if (show) {
rt.classList.remove("opaque");
if (rt.value == '')
rt.value = document.getElementById('z'+j+'-runtime').value;
} else {
rt.classList.add("opaque");
}
}
}
}
}
// Need to come back and make the better.
// if under 375 make sure only 20 character with a space after 10.
function resize(string) {
if (_width <= 375)
return string;
else
return "("+string.replace(" ", "&nbsp;")+")";
}
function update(type) {
var http = new XMLHttpRequest();
if (http) {
http.onreadystatechange = function () {
if (http.readyState === 4) {
if (http.status == 200 && http.status < 300) {
if (type == 'firstload') {
setzonehtml(JSON.parse(http.responseText)['zones']);
hidemessage();
}
var data = JSON.parse(http.responseText);
for (var obj in data) {
if (obj == 'title') {
document.title = data[obj];
document.getElementById('title').innerHTML = data[obj];
} else if (obj == '24hdelay-offtime') {
if (data[obj] > 0) {
dt = new Date(data[obj]);
var options = { weekday: "short", hour: "2-digit", minute: "2-digit"};
document.getElementById('24hdelay-offtime').innerHTML = '(until '+dt.toLocaleTimeString("en-us", options)+')';
} else {
document.getElementById('24hdelay-offtime').innerHTML = '';
}
} else if ( (field = document.getElementById(obj)) != null){
if (obj.substr(-5,5) == '-name')
field.innerHTML = resize(data[obj]);
else
field.value = data[obj];
} else {
if ( (btn = document.getElementById('btn-toggle-' + obj)) != null){
if (data[obj] == 'on')
btn.checked = true;
else
btn.checked = false;
} else if ( (btn = document.getElementById('btn-' + obj)) != null){
btn.selectedIndex = data[obj];
}
}
}
}
else if(http.status >= 400 || http.status == 0) {
document.getElementById('message-text').innerHTML = 'Error connecting to server';
}
}
}
};
if (type == null ) {
http.open('GET', location.href + '?type=read', true);
http.send(null);
} else if (type == 'firstload') {
http.open('GET', location.href + '?type=firstload', true);
http.send(null);
} else if (type != null) {
clearTimeout(_poller);
// compare xx-starttime or xxx-runtime or xxxx-runtime
if (type.id.substr(-4,4) == 'time') {
// d1z10-runtime is a calendar config, z11-runtime is a zone default runtime
if (type.id.substr(0,1) == 'd') {
http.open('GET', location.href + '?type=calcfg&day='+type.id.substr(1,1)+'&zone='+type.id.substr(3,type.id.lastIndexOf("-")-3)+'&time='+type.value);
http.send(null);
cleanupCalendar();
} else if (type.id.substr(0,1) == 'z') {
http.open('GET', location.href + '?type=zrtcfg&zone='+type.id.substr(1,type.id.lastIndexOf("-")-1)+'&time='+type.value);
http.send(null);
cleanupCalendar();
}
} else if (type.id.substr(0,11) == 'btn-toggle-'){
// If there is a 'xx-runtime' id then it's a zone to start, else it's a cfg button
if ( (rto = document.getElementById(type.name + '-runtime')) != null){
http.open('GET', location.href + '?type=zone&zone=' + type.name.substring(1) + '&state=' + ((type.checked == true) ? 'on' : 'off') + '&runtime=' + rto.value, true);
} else {
http.open('GET', location.href + '?type=option&option=' + type.name + '&state=' + ((type.checked == true) ? 'on' : 'off'), true);
}
http.send(null);
}
}
_poller = setTimeout(update, 5000);
}
</script>
</head>
<body onload='setSizeSpecifics(); headIcons(); update("firstload");'>
<div id='message' class='message' onmousedown='hidemessage()'>
<div id='message-text' class='message-text'>
<b>Loading....</b>
</div>
</div>
<div id='option_button' class='option_button' onmousedown='switchOptionsPane()'>
<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAeUExURQAAADMzMjU1LjMzMjQ0NDMzMzAwMDMzMTIyMjMzMnuMncMAAAAJdFJOUwCAIOFAbRC/ouTmS5QAAACRSURBVDjL3ZTBDsMwCEMJJLT+/x8eblVtOzRhU9V288lCT4pMAJFNZj33lAK67z4DXakKVN1zTm5CQpPIjJRmAWz8tAEBlnGY8g3Ybfgb2NOvgcr+21pca0ajZ4AWiiGRRtM4VjR6Rurs0xe25/jUXkIMW2kq49P4rVIvm76dIn8p/OcWogwVkLTcIY3PbTnuAUNsEZOTrdGvAAAAAElFTkSuQmCC'>
</div>
<div id='main' class='opaque'>
<table>
<tbody>
<tr>
<th colspan='5'><label id='title'>Sprinkler</label></th>
</tr>
<tr>
<td colspan='4'>
Run on calendar schedule
</td>
<td>
<div class='switch'>
<input id='btn-toggle-system' name='system' class='btn-toggle btn-toggle-round' type='checkbox' onclick='update(this);'>
<label for='btn-toggle-system'></label>
</div>
</td>
</tr>
<tr>
<td colspan='4'>
24h Rain Delay&nbsp;<label id='24hdelay-offtime' class='small-note'></label>
</td>
<td>
<div class='switch'>
<input id='btn-toggle-24hdelay' name='24hdelay' class='btn-toggle btn-toggle-round' type='checkbox' onclick='update(this);'>
<label for='btn-toggle-24hdelay'></label>
</div>
</td>
</tr>
<tr>
<th colspan='5'>
<font size='-1'>Run now option</font>
</th>
</tr>
<tr>
<td colspan='4'>Cycle all Zones</td>
<td>
<div class='switch'>
<input id='btn-toggle-allz' name='allz' class='btn-toggle btn-toggle-round' type='checkbox' onclick='update(this);'>
<label for='btn-toggle-allz'></label>
</div>
</td>
</tr>
<!-- AUTO HTML -->
</tbody>
</table>
</div>
<div id='options' class='options'>
<table>
<tbody>
<!-- AUTO HTML -->
</tbody>
</table>
</div>
</body>
</html>