Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
sfeakes committed Jun 21, 2024
1 parent 53f6e68 commit e93ad44
Show file tree
Hide file tree
Showing 13 changed files with 395 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ CFLAGS = $(GCCFLAGS) -I. -I./minIni $(DBG) $(LIBS) -D MG_DISABLE_MD5 -D MG_DISAB


# 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 $(sd_GPIO_C)
SRCS = sprinkler.c utils.c config.c net_services.c json_messages.c zone_ctrl.c sd_cron.c hassio.c mongoose.c minIni/minIni.c $(sd_GPIO_C)

# define the C object files
#
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ Specifically, make sure you configure your MQTT, Pool Equiptment Labels & Domoti

## Included scripts in extras directory.

[sprinklerRainProbability.sh](https://github.com/sfeakes/sprinklerd/blob/master/extras/sprinklerRainProbability.sh)

Script to check the chance of rain from forecast OpenWeather, WeaterBit, Climacell, WeatherAPI, if it's greater than a configured percentage then enable the 24h rainDelay on SprinklerD.
Simply edit the scrips with your values, and use cron to run it 30mins before your sprinkelrs are due to turn on each day. If the chance of rain is over your configured % it will enable SprinklerD's 24h rain delay, which will stop the sprinklers running that day, and the 24h delay will timeout before the sprinklers are due to run the next day. (or be enabeled again if the chance of rain is high).
You will need to edit this script for your API keys.

Example cron entry
`0 5 * * * ~/bin/sprinklerRainProbability.sh weatherapi`

<!--
[sprinklerDarkskys.sh](https://github.com/sfeakes/sprinklerd/blob/master/extras/sprinklerDarkskys.sh)
Script to check the chance of rain from darkskys forecast API, if it's greater than a configured percentage then enable the 24h rainDelay on SprinklerD.
Simply edit the scrips with your values, and use cron to run it 30mins before your sprinkelrs are due to turn on each day. If the chance of rain is over your configured % it will enable SprinklerD's 24h rain delay, which will stop the sprinklers running that day, and the 24h delay will timeout before the sprinklers are due to run the next day. (or be enabeled again if the chance of rain is high)
Expand All @@ -106,7 +116,7 @@ Script to pull daily rain total from Meteohub and send it to SprinklerD. Sprink
[sprinklerRainDelay.sh](https://github.com/sfeakes/sprinklerd/blob/master/extras/sprinklerRainDelay.sh)
Script to enable extended rain delays. i.e. I have my weatherstation triger this script when it detects rain. The script will cancel any running zones and enable a rain delay. You can use a number on command line parameter to enable long delays, (number represents number of days). So if my rainsensor logs over 5mm rain in any 24h period it will call this script with a 2 day delay, or 3 day delay if over 15mm of rain.

-->

# Configuration with home automation hubs

Expand Down
20 changes: 16 additions & 4 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,22 +232,34 @@ void readCfg(char *inifile)
ini_gets("SPRINKLERD", "MQTT_ADDRESS", NULL, _sdconfig_.mqtt_address, sizearray(_sdconfig_.mqtt_address), inifile);
ini_gets("SPRINKLERD", "MQTT_USER", NULL, _sdconfig_.mqtt_user, sizearray(_sdconfig_.mqtt_user), inifile);
ini_gets("SPRINKLERD", "MQTT_PASSWD", NULL, _sdconfig_.mqtt_passwd, sizearray(_sdconfig_.mqtt_passwd), inifile);

if ( ini_gets("SPRINKLERD", "MQT_TOPIC", NULL, _sdconfig_.mqtt_topic, sizearray(_sdconfig_.mqtt_topic), inifile) > 0 )
_sdconfig_.enableMQTTaq = true;
else
_sdconfig_.enableMQTTaq = false;

//if ( ini_gets("SPRINKLERD", "MQTT_HA_DIS_TOPIC", NULL, _sdconfig_.mqtt_ha_dis_topic, sizearray(_sdconfig_.mqtt_ha_dis_topic), inifile) > 0 )
if ( ini_gets("SPRINKLERD", "MQTT_DZ_PUB_TOPIC", NULL, _sdconfig_.mqtt_dz_pub_topic, sizearray(_sdconfig_.mqtt_dz_pub_topic), inifile) > 0 &&
ini_gets("SPRINKLERD", "MQTT_DZ_SUB_TOPIC", NULL, _sdconfig_.mqtt_dz_sub_topic, sizearray(_sdconfig_.mqtt_dz_sub_topic), inifile) > 0)
_sdconfig_.enableMQTTdz = true;
else
_sdconfig_.enableMQTTdz = false;

if (_sdconfig_.enableMQTTdz == true || _sdconfig_.enableMQTTaq == true) {
if ( ini_gets("SPRINKLERD", "MQTT_HA_DIS_TOPIC", NULL, _sdconfig_.mqtt_ha_dis_topic, sizearray(_sdconfig_.mqtt_ha_dis_topic), inifile) > 0 )
_sdconfig_.enableMQTTha = true;
else
_sdconfig_.enableMQTTha = false;


if (_sdconfig_.enableMQTTaq == true ) {
logMessage (LOG_DEBUG,"Config mqtt_topic '%s'\n",_sdconfig_.mqtt_topic);
logMessage (LOG_DEBUG,"Config mqtt_dz_pub_topic '%s'\n",_sdconfig_.mqtt_dz_pub_topic);
logMessage (LOG_DEBUG,"Config mqtt_dz_sub_topic '%s'\n",_sdconfig_.mqtt_dz_sub_topic);
if (_sdconfig_.enableMQTTdz == true) {
logMessage (LOG_DEBUG,"Config mqtt_dz_pub_topic '%s'\n",_sdconfig_.mqtt_dz_pub_topic);
logMessage (LOG_DEBUG,"Config mqtt_dz_sub_topic '%s'\n",_sdconfig_.mqtt_dz_sub_topic);
}
if (_sdconfig_.enableMQTTha == true) {
logMessage (LOG_DEBUG,"Config mqtt_ha_dis_topic '%s'\n",_sdconfig_.mqtt_ha_dis_topic);
}
} else {
logMessage (LOG_DEBUG,"Config mqtt 'disabeled'\n");
}
Expand Down
8 changes: 5 additions & 3 deletions config.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,13 @@ struct sprinklerdcfg {
char socket_port[6];
char name[20];
char docroot[512];
char mqtt_address[20];
char mqtt_address[128];
char mqtt_user[50];
char mqtt_passwd[50];
char mqtt_topic[50];
char mqtt_dz_sub_topic[50];
char mqtt_dz_pub_topic[50];
char mqtt_dz_sub_topic[128];
char mqtt_dz_pub_topic[128];
char mqtt_ha_dis_topic[128];
char mqtt_ID[MQTT_ID_LEN];
int dzidx_calendar;
int dzidx_24hdelay;
Expand All @@ -75,6 +76,7 @@ struct sprinklerdcfg {
int dzidx_rainsensor;
bool enableMQTTdz;
bool enableMQTTaq;
bool enableMQTTha;
int zones;
int inputs;
//int pincfgs;
Expand Down
138 changes: 138 additions & 0 deletions extras/sprinklerRainProbability.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/bin/bash
#
# you MUST change at least one of the following API keys, and the lat lng variables.
# openweatherAPI, weatherAPIkey, weatherbitAPIkey, climacellAPIkey
# lat = your latitude
# lon = your longitude
# probabilityOver = Enable rain delay if probability of rain today is grater than this number.
# Range is 0 to 100, so 50 is 50%
# sprinklerdEnableDelay = the URL to SprinklerD
#
# The below are NOT valid, you MUST change them to your information.

# API Keys from the following are supported
# openweathermap.org weatherapi.com weatherbit.io climacell.co

openweatherAPIkey='-----'
weatherbitAPIkey='-----'
climacellAPIkey='-----'
weatherAPIkey='-----'

lat='42.3601'
lon='-71.0589'

# 101 means don't set rain delay from this script, use the SprinklerD config (webUI) to decide if to set delay
probabilityOver=101

# If you are not running this from the same host as SprinklerD, then change localhost in the below.
sprinklerdHost="http://localhost:80"



########################################################################################################################
#
# Should not need to edit below this line.
#
########################################################################################################################


sprinklerdEnableDelay="$sprinklerdHost/?type=option&option=24hdelay&state=reset"
sprinklerdProbability="$sprinklerdHost/?type=sensor&sensor=chanceofrain&value="

openweatherURL="https://api.openweathermap.org/data/2.5/onecall?lat=$lat&lon=$lon&appid=$openweatherAPIkey&exclude=current,minutely,hourly,alerts"
weatherbitURL="https://api.weatherbit.io/v2.0/forecast/daily?key=$weatherbitAPIkey&lat=$lat&lon=$lon"
climacellURL="https://data.climacell.co/v4/timelines?location=$lat%2C$lon&fields=precipitationProbability&timesteps=1d&units=metric&apikey=$climacellAPIkey"
weatherAPIlURL="http://api.weatherapi.com/v1/forecast.json?key=$weatherAPIkey&q=$lat,$lon&days=1&aqi=no&alerts=no"

true=0
false=1

echoerr() { printf "%s\n" "$*" >&2; }
echomsg() { if [ -t 1 ]; then echo "$@" 1>&2; fi; }

function getURL() {
url=$1
jq=$2
factor=$3

JSON=$(curl -s "$url")

if [ $? -ne 0 ]; then
echoerr "Error reading URL, '$1' please check!"
echoerr "Maybe you didn't configure your API and location?"
echo 0;
return $false
else
probability=$(echo $JSON | jq "$jq" )
fi

probability=$(echo "$probability * $factor / 1" | bc )
echo $probability
return $true
}

# Test we have everything installed
command -v curl >/dev/null 2>&1 || { echoerr "curl is not installed. Aborting!"; exit 1; }
command -v jq >/dev/null 2>&1 || { echoerr "jq is not installed. Aborting!"; exit 1; }
command -v bc >/dev/null 2>&1 || { echoerr "bc not installed. Aborting!"; exit 1; }

pop=-1

if [ "$#" -lt 1 ]; then
echomsg "Pass at least one command line parameter (openweather, weatherbit, climacell, weatherapi)"
exit 1;
fi

for var in "$@"
do
case "$var" in
openweather)
if [ "$openweatherAPIkey" == "-----" ]; then echomsg "missing OpenWeather API key" && continue; fi
if probability=$(getURL "$openweatherURL" '.["daily"][0].pop' "100"); then
echomsg "OpenWeather probability of rain today is $probability%"
pop=$(echo "if($probability>$pop)print $probability else print $pop" | bc -l)
fi
;;
weatherbit)
if [ "$weatherbitAPIkey" == "-----" ]; then echomsg "missing WeatherBit API key" && continue; fi
if probability=$(getURL "$weatherbitURL" '.data[0].pop' "1"); then
echomsg "WeatherBit probability of rain today is $probability%"
pop=$(echo "if($probability>$pop)print $probability else print $pop" | bc -l)
fi
;;
climacell)
if [ "$climacellAPIkey" == "-----" ]; then echomsg "missing ClimaCell API key" && continue; fi
if probability=$(getURL "$climacellURL" '.data.timelines[0].intervals[0].values.precipitationProbability' "1"); then
echomsg "ClimaCell probability of rain today is $probability%"
pop=$(echo "if($probability>$pop)print $probability else print $pop" | bc -l)
fi
;;
weatherapi)
if [ "$weatherAPIkey" == "-----" ]; then echomsg "missing WeatherAPI API key" && continue; fi
if probability=$(getURL "$weatherAPIlURL" '.forecast.forecastday[0].day.daily_chance_of_rain' "1"); then
echomsg "WeatherAPI probability of rain today is $probability%"
pop=$(echo "if($probability>$pop)print $probability else print $pop" | bc -l)
fi
;;
esac
done

# Remove all new line, carriage return, tab characters
# from the string, to allow integer comparison
pop="${pop//[$'\t\r\n ']}"

if [ "$pop" -lt 0 ]; then
echoerr "Error reading Probability, please check!"
exit 1
fi

echomsg "Chance of rain $pop%"

curl -s "$sprinklerdProbability$pop" > /dev/null

if (( $(echo "$pop > $probabilityOver" | bc -l) )); then
echomsg "Enabeling rain delay"
curl -s "$sprinklerdEnableDelay" > /dev/null
fi

exit 0
152 changes: 152 additions & 0 deletions hassio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@


#include <stdio.h>
#include <stdlib.h>
#include <strings.h>


#include "mongoose.h"
#include "config.h"
#include "net_services.h"
#include "version.h"

#define HASS_DEVICE "\"identifiers\": " \
"[\"SprinklerD\"]," \
" \"sw_version\": \"" SD_VERSION "\"," \
" \"model\": \"Sprinkler Daemon\"," \
" \"name\": \"SprinklerD\"," \
" \"manufacturer\": \"SprinklerD\"," \
" \"suggested_area\": \"pool\""

#define HASS_AVAILABILITY "\"payload_available\" : \"1\"," \
"\"payload_not_available\" : \"0\"," \
"\"topic\": \"%s/" MQTT_LWM_TOPIC "\""

const char *HASSIO_TEXT_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"icon\": \"mdi:card-text\""
"}";

const char *HASSIO_SWITCH_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"switch\","
"\"unique_id\": \"%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"command_topic\": \"%s/%s/set\","
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"qos\": 1,"
"\"retain\": false"
"}";

const char *HASSIO_VALVE_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"valve\","
"\"device_class\": \"water\","
"\"unique_id\": \"%s\"," // sprinklerd_zone_1
"\"name\": \"Zone %d (%s)\"," // 1 island
"\"state_topic\": \"%s/zone%d\"," // 1
"\"command_topic\": \"%s/zone%d/set\"," // 1
"\"value_template\": \"{%% set values = { '0':'closed', '1':'open'} %%}{{ values[value] if value in values.keys() else 'closed' }}\","
"\"payload_open\": \"1\","
"\"payload_close\": \"0\","
"\"qos\": 1,"
"\"retain\": false"
"}";

void publish_mqtt_hassio_discover(struct mg_connection *nc)
{
char msg[2048];
char topic[256];
char id[128];
int i;


sprintf(id,"sprinklerd_status");
sprintf(topic, "%s/sensor/sprinklerd/%s/config", _sdconfig_.mqtt_ha_dis_topic, id);
sprintf(msg, HASSIO_TEXT_SENSOR_DISCOVER,
_sdconfig_.mqtt_topic,
id,
"Status",
_sdconfig_.mqtt_topic, "status" );
send_mqtt_msg(nc, topic, msg);

sprintf(id,"sprinklerd_active_zone");
sprintf(topic, "%s/sensor/sprinklerd/%s/config", _sdconfig_.mqtt_ha_dis_topic, id);
sprintf(msg, HASSIO_TEXT_SENSOR_DISCOVER,
_sdconfig_.mqtt_topic,
id,
"Active Zone",
_sdconfig_.mqtt_topic, "active" );
send_mqtt_msg(nc, topic, msg);



sprintf(id,"sprinklerd_calendar");
sprintf(topic, "%s/switch/sprinklerd/%s/config", _sdconfig_.mqtt_ha_dis_topic, id);
sprintf(msg, HASSIO_SWITCH_DISCOVER,
_sdconfig_.mqtt_topic,
id,
"Calendar Schedule",
_sdconfig_.mqtt_topic, "calendar",
_sdconfig_.mqtt_topic, "calendar" );
send_mqtt_msg(nc, topic, msg);

sprintf(id,"sprinklerd_24hdelay");
sprintf(topic, "%s/switch/sprinklerd/%s/config", _sdconfig_.mqtt_ha_dis_topic, id);
sprintf(msg, HASSIO_SWITCH_DISCOVER,
_sdconfig_.mqtt_topic,
id,
"24 Hour rain delay",
_sdconfig_.mqtt_topic, "24hdelay",
_sdconfig_.mqtt_topic, "24hdelay" );
send_mqtt_msg(nc, topic, msg);

sprintf(id,"sprinklerd_cycleallzones");
sprintf(topic, "%s/switch/sprinklerd/%s/config", _sdconfig_.mqtt_ha_dis_topic, id);
sprintf(msg, HASSIO_SWITCH_DISCOVER,
_sdconfig_.mqtt_topic,
id,
"Cycle All Zones",
_sdconfig_.mqtt_topic, "cycleallzones",
_sdconfig_.mqtt_topic, "cycleallzones" );
send_mqtt_msg(nc, topic, msg);




//for (i=(_sdconfig_.master_valve?0:1); i <= _sdconfig_.zones ; i++)
// Don't publish zome0/master valve to ha
for (i=1; i <= _sdconfig_.zones ; i++)
{
sprintf(id,"sprinklerd_zone_%d", _sdconfig_.zonecfg[i].zone);
sprintf(topic, "%s/valve/sprinklerd/%s/config", _sdconfig_.mqtt_ha_dis_topic, id);
sprintf(msg, HASSIO_VALVE_DISCOVER,
_sdconfig_.mqtt_topic,
id,
_sdconfig_.zonecfg[i].zone,
_sdconfig_.zonecfg[i].name,
_sdconfig_.mqtt_topic,_sdconfig_.zonecfg[i].zone,
_sdconfig_.mqtt_topic,_sdconfig_.zonecfg[i].zone
);

send_mqtt_msg(nc, topic, msg);
/*
length += sprintf(buffer+length, "{\"type\" : \"zone\", \"zone\": %d, \"name\": \"%s\", \"state\": \"%s\", \"duration\": %d, \"id\" : \"zone%d\" },",
_sdconfig_.zonecfg[i].zone,
_sdconfig_.zonecfg[i].name,
(digitalRead(_sdconfig_.zonecfg[i].pin)==_sdconfig_.zonecfg[i].on_state?"on":"off"),
_sdconfig_.zonecfg[i].default_runtime * 60,
_sdconfig_.zonecfg[i].zone);
*/
}

}
6 changes: 6 additions & 0 deletions hassio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef HASSIO_H_
#define HASSIO_H_

void publish_mqtt_hassio_discover(struct mg_connection *nc);

#endif
Loading

0 comments on commit e93ad44

Please sign in to comment.