#include #include #include #include #include //#include #include #ifdef USE_WIRINGPI #include #else #include "GPIO_Pi.h" #endif #include "mongoose.h" #include "utils.h" #include "net_services.h" #include "config.h" #include "json_messages.h" #include "zone_ctrl.h" #include "sd_cron.h" #include "hassio.h" #define EVENT_SIZE ( sizeof (struct inotify_event) ) #define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) typedef enum {mqttstarting, mqttrunning, mqttstopped, mqttdisabled} mqttstatus; static mqttstatus _mqtt_status = mqttstopped; struct mg_connection *_mqtt_connection; static time_t _mqtt_connected_time; void start_mqtt(struct mg_mgr *mgr); static int is_websocket(const struct mg_connection *nc) { return nc->flags & MG_F_IS_WEBSOCKET; } static int is_mqtt(const struct mg_connection *nc) { return nc->flags & MG_F_USER_1; } static void set_mqtt(struct mg_connection *nc) { nc->flags |= MG_F_USER_1; } // Find the first network interface with valid MAC and put mac address into buffer upto length bool mac(char *buf, int len) { struct ifreq s; int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); struct if_nameindex *if_nidxs, *intf; if_nidxs = if_nameindex(); if (if_nidxs != NULL) { for (intf = if_nidxs; intf->if_index != 0 || intf->if_name != NULL; intf++) { strcpy(s.ifr_name, intf->if_name); if (0 == ioctl(fd, SIOCGIFHWADDR, &s)) { int i; if ( s.ifr_addr.sa_data[0] == 0 && s.ifr_addr.sa_data[1] == 0 && s.ifr_addr.sa_data[2] == 0 && s.ifr_addr.sa_data[3] == 0 && s.ifr_addr.sa_data[4] == 0 && s.ifr_addr.sa_data[5] == 0 ) { continue; } for (i = 0; i < 6 && i * 2 < len; ++i) { sprintf(&buf[i * 2], "%02x", (unsigned char)s.ifr_addr.sa_data[i]); } return true; } } } return false; } // Return true if added or changed value, false if same. bool update_dz_cache(int dzidx, int value) { int i; for(i=0; i < _sdconfig_.zones + NON_ZONE_DZIDS; i++) { if (_sdconfig_.dz_cache[i].idx == dzidx) { if (_sdconfig_.dz_cache[i].status == value) { //logMessage(LOG_DEBUG, "No change dzcache idx=%d %d\n", dzidx, value); return false; } else { _sdconfig_.dz_cache[i].status = value; //logMessage(LOG_DEBUG, "Updated dzcache idx=%d %d\n", dzidx, value); return true; } } } // if we got here, there is no cache value, so create one. for(i=0; i < _sdconfig_.zones + NON_ZONE_DZIDS; i++) { //logMessage(LOG_DEBUG, "dzcache index %d idx=%d %d\n",i, _sdconfig_.dz_cache[i].idx, _sdconfig_.dz_cache[i].status); if (_sdconfig_.dz_cache[i].idx == 0) { _sdconfig_.dz_cache[i].idx = dzidx; _sdconfig_.dz_cache[i].status = value; //logMessage(LOG_DEBUG, "Added dzcache idx=%d %d\n", dzidx, value); return true; } } logMessage(LOG_ERR, "didn't find or create dzcache value idx=%d %d\n", dzidx, value); return true; } bool check_dz_cache(int idx, int value) { int i; for(i=0; i < _sdconfig_.zones + NON_ZONE_DZIDS; i++) { if (_sdconfig_.dz_cache[i].idx == idx) { if (_sdconfig_.dz_cache[i].status == value) return true; else return false; } } return false; } /* void urldecode(char *dst, const char *src) { char a, b; while (*src) { if ((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b))) { if (a >= 'a') a -= 'a' - 'A'; if (a >= 'A') a -= ('A' - 10); else a -= '0'; if (b >= 'a') b -= 'a' - 'A'; if (b >= 'A') b -= ('A' - 10); else b -= '0'; *dst++ = 16 * a + b; src += 3; } else if (*src == '+') { *dst++ = ' '; src++; } else { *dst++ = *src++; } } *dst++ = '\0'; } */ // Need to update network interface. char *generate_mqtt_id(char *buf, int len) { extern char *__progname; // glibc populates this int i; strncpy(buf, basename(__progname), len); i = strlen(buf); if (i < len) { buf[i++] = '_'; // If we can't get MAC to pad mqtt id then use PID if (!mac(&buf[i], len - i - 1)) { sprintf(&buf[i], "%.*d", (len-i-2), getpid()); } } buf[len-1] = '\0'; return buf; } void send_mqtt_msg(struct mg_connection *nc, char *toppic, char *message) { static uint16_t msg_id = 0; if (msg_id >= 65535){msg_id=1;}else{msg_id++;} //mg_mqtt_publish(nc, toppic, msg_id, MG_MQTT_QOS(0), message, strlen(message)); mg_mqtt_publish(nc, toppic, msg_id, MG_MQTT_RETAIN | MG_MQTT_QOS(1), message, strlen(message)); logMessage(LOG_DEBUG, "MQTT: Published id=%d: %s %s\n", msg_id, toppic, message); } void publish_zone_mqtt(struct mg_connection *nc, struct GPIOcfg *gpiopin) { static char mqtt_topic[250]; static char mqtt_msg[50]; if (_sdconfig_.enableMQTTaq == true) { // sprintf(mqtt_topic, "%s/%s", _sdconfig_.mqtt_topic, gpiopin->name); sprintf(mqtt_topic, "%s/zone%d", _sdconfig_.mqtt_topic, gpiopin->zone); sprintf(mqtt_msg, "%s", (digitalRead(gpiopin->pin) == gpiopin->on_state ? MQTT_ON : MQTT_OFF)); send_mqtt_msg(nc, mqtt_topic, mqtt_msg); sprintf(mqtt_topic, "%s/zone%d/duration", _sdconfig_.mqtt_topic, gpiopin->zone); sprintf(mqtt_msg, "%d", gpiopin->default_runtime * 60); send_mqtt_msg(nc, mqtt_topic, mqtt_msg); sprintf(mqtt_topic, "%s/zone%d/remainingduration", _sdconfig_.mqtt_topic, gpiopin->zone); if (_sdconfig_.currentZone.zone == gpiopin->zone && _sdconfig_.currentZone.type != zcNONE) { sprintf(mqtt_msg, "%d", _sdconfig_.currentZone.timeleft * 60); } else { sprintf(mqtt_msg, "%d", 0); } send_mqtt_msg(nc, mqtt_topic, mqtt_msg); } if (gpiopin->dz_idx > 0 && _sdconfig_.enableMQTTdz == true) { if ( update_dz_cache(gpiopin->dz_idx,(digitalRead(gpiopin->pin) == gpiopin->on_state ? DZ_ON : DZ_OFF) ) ) { build_dz_mqtt_status_JSON(mqtt_msg, 50, gpiopin->dz_idx, (digitalRead(gpiopin->pin) == gpiopin->on_state ? DZ_ON : DZ_OFF), TEMP_UNKNOWN); send_mqtt_msg(nc, _sdconfig_.mqtt_dz_pub_topic, mqtt_msg); } } } int sprinklerdstatus(char *status, int length) { // Status in this order // Zone active and run time // 24h delay and end time // calendar schedule // off if (_sdconfig_.currentZone.type != zcNONE) { sprintf(status,"Running: Zone %d - time left %02d:%02d",_sdconfig_.currentZone.zone, _sdconfig_.currentZone.timeleft / 60, _sdconfig_.currentZone.timeleft % 60 ); return 1; } else if (_sdconfig_.delay24h == true) { struct tm * timeinfo = localtime (&_sdconfig_.delay24h_time); strftime (status,length,"24h delay: end time %a %I:%M%p",timeinfo); return 3; } else if (_sdconfig_.calendar == true) { sprintf(status,"Calendar schedule"); return 2; } else { sprintf(status,"OFF"); return 0; } return 0; } void broadcast_zonestate(struct mg_connection *nc, struct GPIOcfg *gpiopin) { //logMessage(LOG_DEBUG, "broadcast_gpiopinstate()\n"); //static int mqtt_count=0; struct mg_connection *c; check_net_services(nc->mgr); for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) { if (is_websocket(c)) { //ws_send(c, data); logMessage(LOG_ERR, "Hit part of code that's not complete - broadcast_gpiopinstate() - websocket\n"); } else if (is_mqtt(c)) { publish_zone_mqtt(c, gpiopin); } } return; } void broadcast_sprinklerdactivestate(struct mg_connection *nc) { //static int mqtt_count=0; //int i; struct mg_connection *c; static char mqtt_topic[250]; static char mqtt_msg[50]; for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) { if (is_mqtt(c)) { if (_sdconfig_.currentZone.type==zcNONE) { sprintf(mqtt_topic, "%s/active", _sdconfig_.mqtt_topic); send_mqtt_msg(c, mqtt_topic, " "); sprintf(mqtt_topic, "%s/cycleallzones/remainingduration", _sdconfig_.mqtt_topic); send_mqtt_msg(c, mqtt_topic, "0"); } else { sprintf(mqtt_topic, "%s/active", _sdconfig_.mqtt_topic); sprintf(mqtt_msg, "Zone %d", _sdconfig_.currentZone.zone ); send_mqtt_msg(c, mqtt_topic, mqtt_msg); //} //sprintf(mqtt_topic, "%s/remainingduration", _sdconfig_.mqtt_topic); //sprintf(mqtt_msg, "%d", _sdconfig_.currentZone.timeleft ); //send_mqtt_msg(c, mqtt_topic, mqtt_msg); sprintf(mqtt_topic, "%s/status", _sdconfig_.mqtt_topic); sprinklerdstatus(mqtt_msg, 50); send_mqtt_msg(c, mqtt_topic, mqtt_msg); // Send info specific to zone sprintf(mqtt_topic, "%s/zone%d/remainingduration", _sdconfig_.mqtt_topic, _sdconfig_.currentZone.zone); sprintf(mqtt_msg, "%d", _sdconfig_.currentZone.timeleft ); send_mqtt_msg(c, mqtt_topic, mqtt_msg); } if (_sdconfig_.currentZone.type == zcALL) { int i, total=_sdconfig_.currentZone.timeleft; for (i=_sdconfig_.currentZone.zone+1; i <= _sdconfig_.zones; i++) { total += (_sdconfig_.zonecfg[i].default_runtime * 60); } sprintf(mqtt_topic, "%s/cycleallzones/remainingduration", _sdconfig_.mqtt_topic); sprintf(mqtt_msg, "%d", total ); send_mqtt_msg(c, mqtt_topic, mqtt_msg); sprintf(mqtt_topic, "%s/zone0/remainingduration", _sdconfig_.mqtt_topic); //sprintf(mqtt_msg, "%d", _sdconfig_.currentZone.timeleft ); sprintf(mqtt_msg, "%d", total ); send_mqtt_msg(c, mqtt_topic, mqtt_msg); } else if (_sdconfig_.currentZone.type != zcNONE) { sprintf(mqtt_topic, "%s/zone0/remainingduration", _sdconfig_.mqtt_topic); sprintf(mqtt_msg, "%d", _sdconfig_.currentZone.timeleft ); send_mqtt_msg(c, mqtt_topic, mqtt_msg); } if (_sdconfig_.currentZone.type != zcALL) { } } } clearEventZones; } int getTodayString(char *buffer, int size) { time_t rawtime; struct tm * timeinfo; time (&rawtime); timeinfo = localtime (&rawtime); return strftime (buffer,size,"%a %h %d.",timeinfo); } void broadcast_sprinklerdstate(struct mg_connection *nc) { //static int mqtt_count=0; int i; struct mg_connection *c; static char mqtt_topic[250]; static char mqtt_msg[50]; static char last_state_msg[50]; static int publishedTodayRainChance = -1; static float publishedTodayRainTotal = -1; for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) { // Start from 0 since we publish master valve (just a temp measure) for (i=(_sdconfig_.master_valve?0:1); i <= _sdconfig_.zones ; i++) { if (is_websocket(c)) { //ws_send(c, data); logMessage(LOG_ERR, "Hit part of code that's not complete - broadcast_gpiopinstate() - websocket\n"); } else if (is_mqtt(c)) { publish_zone_mqtt(c, &_sdconfig_.zonecfg[i]); } } if (is_mqtt(c)) { if (_sdconfig_.enableMQTTaq == true) { sprintf(mqtt_topic, "%s/calendar", _sdconfig_.mqtt_topic); sprintf(mqtt_msg, "%s", (_sdconfig_.calendar?MQTT_ON:MQTT_OFF) ); send_mqtt_msg(c, mqtt_topic, mqtt_msg); sprintf(mqtt_topic, "%s/24hdelay", _sdconfig_.mqtt_topic); sprintf(mqtt_msg, "%s", (_sdconfig_.delay24h?MQTT_ON:MQTT_OFF) ); send_mqtt_msg(c, mqtt_topic, mqtt_msg); sprintf(mqtt_topic, "%s/cycleallzones", _sdconfig_.mqtt_topic); sprintf(mqtt_msg, "%s", (_sdconfig_.currentZone.type==zcALL?MQTT_ON:MQTT_OFF) ); send_mqtt_msg(c, mqtt_topic, mqtt_msg); sprintf(mqtt_topic, "%s/status", _sdconfig_.mqtt_topic); sprinklerdstatus(mqtt_msg, 50); send_mqtt_msg(c, mqtt_topic, mqtt_msg); if (publishedTodayRainChance != _sdconfig_.todayRainChance || isEventRainProbability) { sprintf(mqtt_topic, "%s/chanceofrain", _sdconfig_.mqtt_topic); sprintf(mqtt_msg, "%d",_sdconfig_.todayRainChance); send_mqtt_msg(c, mqtt_topic, mqtt_msg); publishedTodayRainChance = _sdconfig_.todayRainChance; getTodayString(mqtt_msg, 50); sprintf(mqtt_topic, "%s/chanceofrain/day", _sdconfig_.mqtt_topic); send_mqtt_msg(c, mqtt_topic, mqtt_msg); clearEventRainProbability; } if (publishedTodayRainTotal != _sdconfig_.todayRainTotal || isEventRainTotal) { sprintf(mqtt_topic, "%s/raintotal", _sdconfig_.mqtt_topic); sprintf(mqtt_msg, "%.2f",_sdconfig_.todayRainTotal); send_mqtt_msg(c, mqtt_topic, mqtt_msg); publishedTodayRainTotal = _sdconfig_.todayRainTotal; getTodayString(mqtt_msg, 50); sprintf(mqtt_topic, "%s/raintotal/day", _sdconfig_.mqtt_topic); send_mqtt_msg(c, mqtt_topic, mqtt_msg); clearEventRainTotal; } } if (_sdconfig_.enableMQTTdz == true) { if (_sdconfig_.dzidx_calendar > 0 && update_dz_cache(_sdconfig_.dzidx_calendar,(_sdconfig_.calendar==true ? DZ_ON : DZ_OFF) )) { build_dz_mqtt_status_JSON(mqtt_msg, 50, _sdconfig_.dzidx_calendar, (_sdconfig_.calendar==true ? DZ_ON : DZ_OFF), TEMP_UNKNOWN); send_mqtt_msg(c, _sdconfig_.mqtt_dz_pub_topic, mqtt_msg); } if (_sdconfig_.dzidx_24hdelay > 0 && update_dz_cache(_sdconfig_.dzidx_24hdelay,(_sdconfig_.delay24h==true ? DZ_ON : DZ_OFF) )) { build_dz_mqtt_status_JSON(mqtt_msg, 50, _sdconfig_.dzidx_24hdelay, (_sdconfig_.delay24h==true ? DZ_ON : DZ_OFF), TEMP_UNKNOWN); send_mqtt_msg(c, _sdconfig_.mqtt_dz_pub_topic, mqtt_msg); } if (_sdconfig_.dzidx_allzones > 0 && update_dz_cache(_sdconfig_.dzidx_allzones,(_sdconfig_.currentZone.type==zcALL ? DZ_ON : DZ_OFF) )) { build_dz_mqtt_status_JSON(mqtt_msg, 50, _sdconfig_.dzidx_allzones, (_sdconfig_.currentZone.type==zcALL ? DZ_ON : DZ_OFF), TEMP_UNKNOWN); send_mqtt_msg(c, _sdconfig_.mqtt_dz_pub_topic, mqtt_msg); } if (_sdconfig_.dzidx_status > 0) { int value =sprinklerdstatus(mqtt_msg, 50); if (strcmp(last_state_msg, mqtt_msg) != 0) { build_dz_status_message_JSON(mqtt_topic, 250, _sdconfig_.dzidx_status, value, mqtt_msg); send_mqtt_msg(c, _sdconfig_.mqtt_dz_pub_topic, mqtt_topic); strcpy(last_state_msg, mqtt_msg); } } } } } clearEventStatus; return; } int is_value_ON(char *buf) { if (strncasecmp(buf, "on", 3) == 0 || strncmp(buf, "1", 1) == 0) return true; else if (strncasecmp(buf, "off", 3) == 0 || strncmp(buf, "0", 1) == 0) return false; return -1; } bool is_value_flip(char *buf) { if (strncasecmp(buf, "flip", 4) == 0 || strncasecmp(buf, "toggle", 6) == 0 ) return true; else return false; } //int serve_web_request(struct mg_connection *nc, struct http_message *http_msg, char *buffer, int size, bool *changedOption) { int serve_web_request(struct mg_connection *nc, struct http_message *http_msg, char *buffer, int size) { static int buflen = 50; char buf[buflen]; //int action = -1; //int pin = -1; //int status = -1; int length = 0; //int i; int zone; int runtime; memset(&buffer[0], 0, size); /* Need to action the following /?type=firstload // Include cal schedule /?type=read // no need to include cal schedule // Cfg options /?type=option&option=24hdelay&state=off // turn off 24h delay /?type=option&option=calendar&state=off // turn off calendar // Calendar /?type=calcfg&day=3&zone=&time=07:00 // Use default water zone times /?type=calcfg&day=2&zone=1&time=7 // Change water zone time /?type=calcfg&day=3&zone=&time= // Delete day schedule // Run options /?type=option&option=allz&state=on // Run all zones default times /?type=zone&zone=2&state=on&runtime=3 // Run zone 2 for 3 mins (ignore 24h delay & calendar settings) /?type=zrtcfg&zone=2&time=10 // change zone 2 default runtime to 10 /?type=cron&zone=1&runtime=12' // Run zone 1 for 12 mins (calendar & 24hdelay settings overide this request) */ mg_get_http_var(&http_msg->query_string, "type", buf, buflen); logMessage (LOG_DEBUG, "Request type %s\n",buf); if (strcmp(buf, "json") == 0) { length = build_advanced_sprinkler_JSON(buffer, size); } else if (strcmp(buf, "homebridge") == 0) { length = build_homebridge_sprinkler_JSON(buffer, size); } else if (strcmp(buf, "firstload") == 0) { logMessage(LOG_DEBUG, "WEB REQUEST Firstload %s\n",buf); length = build_sprinkler_cal_JSON(buffer, size); } else if (strcmp(buf, "read") == 0) { logMessage(LOG_DEBUG, "WEB REQUEST read %s\n",buf); length = build_sprinkler_JSON(buffer, size); } else if (strcmp(buf, "option") == 0) { //logMessage(LOG_DEBUG, "WEB REQUEST option %s\n",buf); mg_get_http_var(&http_msg->query_string, "option", buf, buflen); if (strncasecmp(buf, "calendar", 6) == 0 ) { mg_get_http_var(&http_msg->query_string, "state", buf, buflen); logMessage(LOG_NOTICE, "WEB request to turn Calendar %s\n",buf); logMessage(LOG_NOTICE, "Request | %.*s\n",http_msg->query_string.len,http_msg->query_string.p); enable_calendar(is_value_ON(buf)); length = build_sprinkler_JSON(buffer, size); } else if (strncasecmp(buf, "24hdelay", 8) == 0 ) { mg_get_http_var(&http_msg->query_string, "state", buf, buflen); int val = is_value_ON(buf); if (val == true || val == false) { enable_delay24h(val); } else if (strncasecmp(buf, "reset", 5) == 0) { mg_get_http_var(&http_msg->query_string, "time", buf, buflen); reset_delay24h_time(atoi(buf)); } length = build_sprinkler_JSON(buffer, size); } else if (strncasecmp(buf, "allz", 4) == 0 ) { mg_get_http_var(&http_msg->query_string, "state", buf, buflen); zc_zone(zcALL, 0, (is_value_ON(buf)?zcON:zcOFF), 0); length = build_sprinkler_JSON(buffer, size); } else { logMessage(LOG_WARNING, "Bad request unknown option\n"); length = sprintf(buffer, "{ \"error\": \"Bad request unknown option\" }"); } //*changedOption = true; setEventStatus; //broadcast_sprinklerdstate(nc); } else if (strncasecmp(buf, "config", 6) == 0) { mg_get_http_var(&http_msg->query_string, "option", buf, buflen); if (strncasecmp(buf, "raindelaychance", 15) == 0 ) { mg_get_http_var(&http_msg->query_string, "value", buf, buflen); _sdconfig_.precipChanceDelay = atoi(buf); } else if (strncasecmp(buf, "raindelaytotal1", 15) == 0) { mg_get_http_var(&http_msg->query_string, "value", buf, buflen); _sdconfig_.precipInchDelay1day = atof(buf); } else if (strncasecmp(buf, "raindelaytotal2", 15) == 0) { mg_get_http_var(&http_msg->query_string, "value", buf, buflen); _sdconfig_.precipInchDelay2day = atof(buf); } length = build_sprinkler_JSON(buffer, size); } else if (strncasecmp(buf, "sensor", 6) == 0) { mg_get_http_var(&http_msg->query_string, "sensor", buf, buflen); if (strncasecmp(buf, "chanceofrain", 13) == 0) { mg_get_http_var(&http_msg->query_string, "value", buf, buflen); logMessage(LOG_NOTICE, "API: Chance of rain %d%%\n",atoi(buf)); setTodayChanceOfRain(atoi(buf)); } else if (strncasecmp(buf, "raintotal", 9) == 0) { mg_get_http_var(&http_msg->query_string, "value", buf, buflen); logMessage(LOG_NOTICE, "API: Rain total %.2f\n",atof(buf)); setTodayRainTotal(atof(buf)); } length = build_sprinkler_JSON(buffer, size); } else if (strcmp(buf, "zone") == 0 || strcmp(buf, "cron") == 0) { //logMessage(LOG_DEBUG, "WEB REQUEST zone %s\n",buf); zcRunType type = zcSINGLE; if (strcmp(buf, "cron") == 0) type = zcCRON; mg_get_http_var(&http_msg->query_string, "zone", buf, buflen); zone = atoi(buf); mg_get_http_var(&http_msg->query_string, "runtime", buf, buflen); runtime = atoi(buf); mg_get_http_var(&http_msg->query_string, "state", buf, buflen); //if ( (strncasecmp(buf, "off", 3) == 0 || strncmp(buf, "0", 1) == 0) && zone <= _sdconfig_.zones) { if (is_value_ON(buf) == true && zone <= _sdconfig_.zones) { logMessage(LOG_NOTICE, "API: Turn zone %d on for %d mins\n",zone,runtime); zc_zone(type, zone, zcON, runtime); length = build_sprinkler_JSON(buffer, size); //} else if ( (strncasecmp(buf, "on", 2) == 0 || strncmp(buf, "1", 1) == 0) && zone <= _sdconfig_.zones) { } else if ( is_value_ON(buf) == false && zone <= _sdconfig_.zones) { logMessage(LOG_NOTICE, "API: Turn zone %d off\n",zone); zc_zone(type, zone, zcOFF, runtime); length = build_sprinkler_JSON(buffer, size); } else if ( is_value_flip(buf) == true && zone <= _sdconfig_.zones) { logMessage(LOG_NOTICE, "API: Turn zone %d %s\n",zone, !zc_state(zone)==zcON?"ON":"OFF"); zc_zone(type, zone, !zc_state(zone), runtime); } else { if (zone > _sdconfig_.zones) { logMessage(LOG_WARNING, "Bad request unknown zone %d\n",zone); length = sprintf(buffer, "{ \"error\": \"Bad request unknown zone %d\"}", zone); } else { logMessage(LOG_WARNING, "Bad request on zone %d, unknown state %s\n",zone, buf); length = sprintf(buffer, "{ \"error\": \"Bad request on zone %d, unknown state %s\"}", zone, buf); } setEventZones; } } else if (strcmp(buf, "zrtcfg") == 0) { //logMessage(LOG_DEBUG, "WEB REQUEST cfg %s\n",buf); mg_get_http_var(&http_msg->query_string, "zone", buf, buflen); zone = atoi(buf); mg_get_http_var(&http_msg->query_string, "time", buf, buflen); runtime = atoi(buf); if (zone > 0 && zone <= _sdconfig_.zones && runtime > -1) { _sdconfig_.zonecfg[zone].default_runtime = runtime; logMessage(LOG_DEBUG, "changed default runtime on zone %d, to %d\n",zone, runtime); length = build_sprinkler_JSON(buffer, size); zc_update_runtime(zone); //*changedOption = true; setEventZones; } else length += sprintf(buffer, "{ \"error\": \"bad request zone %d runtime %d\"}",zone,runtime); } else if (strcmp(buf, "calcfg") == 0) { mg_get_http_var(&http_msg->query_string, "day", buf, buflen); int day = atoi(buf); mg_get_http_var(&http_msg->query_string, "zone", buf, buflen); zone = atoi(buf); mg_get_http_var(&http_msg->query_string, "time", buf, buflen); runtime = atoi(buf); if (day >= 0 && day <= 6 && zone > 0 && time > 0) { // zone runtime if (zone <= _sdconfig_.zones) _sdconfig_.cron[day].zruntimes[zone-1] = runtime; else length = sprintf(buffer, "{ \"error\": \"bad zone calendar config request\"}"); } else if (day >= 0 && day <= 6 && strlen(buf) > 0) { // add day to schedule _sdconfig_.cron[day].hour = str2int(buf, 2); _sdconfig_.cron[day].minute = str2int(&buf[3], 2);; } else if (day >= 0 && day <= 6) { // remove day from schedule _sdconfig_.cron[day].hour = -1; _sdconfig_.cron[day].minute = -1; } else { length = sprintf(buffer, "{ \"error\": \"bad calendar config request\"}"); } time(&_sdconfig_.cron_update); length = build_sprinkler_cal_JSON(buffer, size); } else { length += sprintf(buffer, "{ \"error\": \"unknown request\"}"); } buffer[length] = '\0'; //logMessage (LOG_DEBUG, "Web Return %d = '%.*s'\n",length, length, buffer); return strlen(buffer); } #define CT_TEXT "Content-Type: text/plain" #define CT_JSON "Content-Type: application/json" void action_web_request(struct mg_connection *nc, struct http_message *http_msg){ int bufsize = 1024 + (200 * _sdconfig_.zones); char buf[bufsize]; char *c_type; int size = 0; //bool changedOption = false; logMessage(LOG_DEBUG, "action_web_request %.*s %.*s\n",http_msg->uri.len, http_msg->uri.p, http_msg->query_string.len, http_msg->query_string.p); if (http_msg->query_string.len > 0) { //size = serve_web_request(nc, http_msg, buf, sizeof(buf)); //size = serve_web_request(nc, http_msg, buf, bufsize, &changedOption); size = serve_web_request(nc, http_msg, buf, bufsize); c_type = CT_JSON; if (size <= 0) { size = sprintf(buf, "{ \"error\": \"unknown request\"}"); } mg_send_head(nc, 200, size, c_type); mg_send(nc, buf, size); //logMessage (LOG_DEBUG, "Web Return %d = '%.*s'\n",size, size, buf); //if (changedOption) // _sdconfig_.eventToUpdateHappened = true; return; } struct mg_serve_http_opts opts; memset(&opts, 0, sizeof(opts)); // Reset all options to defaults opts.document_root = _sdconfig_.docroot; // Serve files from the current directory // logMessage (LOG_DEBUG, "Doc root=%s\n",opts.document_root); mg_serve_http(nc, http_msg, opts); } /* void action_websocket_request(struct mg_connection *nc, struct websocket_message *wm){ logMessage(LOG_INFO, "action_websocket_request\n"); } */ void action_domoticz_mqtt_message(struct mg_connection *nc, struct mg_mqtt_message *msg) { int idx = -1; int nvalue = -1; int i; char svalue[DZ_SVALUE_LEN]; if (parseJSONmqttrequest(msg->payload.p, msg->payload.len, &idx, &nvalue, svalue, "\"svalue2\"")) { if ( idx <= 0 || check_dz_cache(idx, nvalue)) return; if (idx == _sdconfig_.dzidx_calendar) { //_sdconfig_.system=(nvalue==DZ_ON?true:false); logMessage(LOG_NOTICE, "Domoticz: topic %.*s %.*s\n",msg->topic.len, msg->topic.p, msg->payload.len, msg->payload.p); logMessage(LOG_NOTICE, "Domoticz request to turn Calendar %s\n",nvalue==DZ_ON?"On":"Off"); enable_calendar(nvalue==DZ_ON?true:false); //_sdconfig_.eventToUpdateHappened = true; logMessage(LOG_INFO, "Domoticz MQTT request to turn %s calendar",(nvalue==DZ_ON?"ON":"OFF")); } else if (idx == _sdconfig_.dzidx_24hdelay) { enable_delay24h((nvalue==DZ_ON?true:false)); logMessage(LOG_INFO, "Domoticz MQTT request to turn %s 24hDelay",(nvalue==DZ_ON?"ON":"OFF")); } else if (idx == _sdconfig_.dzidx_allzones) { zc_zone(zcALL, 0, (nvalue==DZ_ON?zcON:zcOFF), 0); logMessage(LOG_INFO, "Domoticz MQTT request to turn %s cycle all zones",(nvalue==DZ_ON?"ON":"OFF")); } else if (idx == _sdconfig_.dzidx_rainsensor) { logMessage(LOG_INFO, "Domoticz MQTT rain sensor update, total %s",svalue); setTodayRainTotal(atof(svalue)); } else { for (i=1; i <= _sdconfig_.zones ; i++) { if ( _sdconfig_.zonecfg[i].dz_idx == idx ) { logMessage(LOG_INFO, "Domoticz MQT id %d matched Zone %d %s\n",idx, _sdconfig_.zonecfg[i].zone, _sdconfig_.zonecfg[i].name); if ( nvalue == DZ_ON || nvalue == DZ_OFF) { zc_zone(zcSINGLE, _sdconfig_.zonecfg[i].zone, (nvalue==DZ_ON?zcON:zcOFF), 0); logMessage(LOG_DEBUG, "Domoticz MQT: Turn zone %d %s\n",_sdconfig_.zonecfg[i].zone, nvalue==DZ_ON?"ON":"OFF"); } else { logMessage(LOG_WARNING, "Domoticz MQT id %d, unknown state to set %d\n",idx,nvalue); } } } } } } void action_mqtt_message(struct mg_connection *nc, struct mg_mqtt_message *msg){ //logMessage(LOG_INFO, "action_mqtt_message\n"); int i; //char *pt1 = (char *)&msg->topic.p[strlen(_sdconfig_.mqtt_topic)+1]; char *pt2 = NULL; char *pt3 = NULL; char *pt4 = NULL; for (i=strlen(_sdconfig_.mqtt_topic); i < msg->topic.len; i++) { if ( msg->topic.p[i] == '/' ) { if (pt2 == NULL) { pt2 = (char *)&msg->topic.p[++i]; } else if (pt3 == NULL) { pt3 = (char *)&msg->topic.p[++i]; } else if (pt4 == NULL) { pt4 = (char *)&msg->topic.p[++i]; break; } } } /* Need to action the following 1 = on //sprinkler/zone1/set on //sprinkler/zone1/duration/set on //sprinkler/zone/all/set on //sprinkler/24hdelay/set on //sprinkler/system/set on */ // All topics are on or off / 1 or 0 zcState status = zcOFF; if ( strncasecmp(msg->payload.p, "on", 2) == 0 || strncmp(msg->payload.p, MQTT_ON, 1) == 0) { status = zcON; } logMessage(LOG_DEBUG, "MQTT: topic %.*s %.*s\n",msg->topic.len, msg->topic.p, msg->payload.len, msg->payload.p); //logMessage(LOG_DEBUG, "MQTT: pt2 %s\n", pt2); //logMessage(LOG_DEBUG, "MQTT: pt2 %.*s == %s %c\n", 4, pt2, strncmp(pt2, "zone", 4) == 0?"YES":"NO", pt2[4]); /* if (pt2 != NULL && pt3 != NULL && pt4 != NULL && strncmp(pt2, "zone", 4) == 0 && strncmp(pt4, "set", 3) == 0 ) { int zone = atoi(pt3); if (zone > 0 && zone <= _sdconfig_.zones) { zc_zone(zcSINGLE, zone, status, 0); logMessage(LOG_DEBUG, "MQTT: Turn zone %d %s\n",zone, status==zcON?"ON":"OFF"); } else { logMessage(LOG_WARNING, "MQTT: unknown zone %d\n",zone); } } else*/ //setTodayRainTotal(atof(svalue)); if (pt2 != NULL && pt3 != NULL && pt4 != NULL && strncmp(pt2, "zone", 4) == 0 && strncmp(pt3, "duration", 8) == 0 && strncmp(pt4, "set", 3) == 0 ) { int zone = atoi(&pt2[4]); if (zone > 0 && zone <= _sdconfig_.zones) { int v = str2int(msg->payload.p, msg->payload.len); _sdconfig_.zonecfg[zone].default_runtime = v / 60; //_sdconfig_.eventToUpdateHappened = true; //setEventZones; zc_update_runtime(zone); logMessage(LOG_DEBUG, "MQTT: Default runtime zone %d is %d\n",zone,_sdconfig_.zonecfg[zone].default_runtime); } else { logMessage(LOG_DEBUG, "MQTT: BAD Default runtime zone %d is %d\n",zone,_sdconfig_.zonecfg[zone].default_runtime); } } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "24hdelay", 8) == 0 && strncmp(pt3, "set", 3) == 0 ) { enable_delay24h(status==zcON?true:false); logMessage(LOG_NOTICE, "MQTT: Enable 24 hour delay %s\n",status==zcON?"YES":"NO"); } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "calendar", 6) == 0 && strncmp(pt3, "set", 3) == 0 ) { //_sdconfig_.system=status==zcON?true:false; logMessage(LOG_DEBUG, "MQTT request to turn Calendar %s\n",status==zcON?"On":"Off"); enable_calendar(status==zcON?true:false); logMessage(LOG_NOTICE, "MQTT: Turning calendar %s\n",status==zcON?"ON":"OFF"); } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "cycleallzones", 13) == 0 && strncmp(pt3, "set", 3) == 0 ) { zc_zone(zcALL, 0, status, 0); logMessage(LOG_NOTICE, "MQTT: Cycle all zones %s\n",status==zcON?"ON":"OFF"); } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "raintotal", 9) == 0 && strncmp(pt3, "set", 3) == 0 ) { float v = str2float(msg->payload.p, msg->payload.len); logMessage(LOG_NOTICE, "MQTT: Rain total %.2f\n",v); setTodayRainTotal(v); } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "chanceofrain", 12) == 0 && strncmp(pt3, "set", 3) == 0 ) { int v = str2int(msg->payload.p, msg->payload.len); logMessage(LOG_NOTICE, "MQTT: Chance of rain %d%%\n",v); setTodayChanceOfRain(v); } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "zone", 4) == 0 && strncmp(pt3, "set", 3) == 0 ) { int zone = atoi(&pt2[4]); if (zone > 0 && zone <= _sdconfig_.zones) { logMessage(LOG_NOTICE, "MQTT: Turn zone %d %s\n",zone, status==zcON?"ON":"OFF"); zc_zone(zcSINGLE, zone, status, 0); } else if (zone == 0 && status == zcOFF && _sdconfig_.currentZone.type != zcNONE) { // request to turn off master will turn off any running zone logMessage(LOG_DEBUG, "MQTT: Request master valve off, turned zone %d off\n",_sdconfig_.currentZone.zone); zc_zone(zcSINGLE, _sdconfig_.currentZone.zone , zcOFF, 0); } else if (zone == 0 && status == zcON) { // Can't turn on master valve, just re-send current stats. //_sdconfig_.eventToUpdateHappened = true; setEventZones; setEventStatus; } else { logMessage(LOG_WARNING, "MQTT: unknown zone %d\n",zone); } } else { // No set. nothing for us to do. logMessage(LOG_DEBUG, "MQTT: Unknown topic %.*s %.*s\n",msg->topic.len, msg->topic.p, msg->payload.len, msg->payload.p); return; } } static void ev_handler(struct mg_connection *nc, int ev, void *p) { struct mg_mqtt_message *mqtt_msg; struct http_message *http_msg; static bool hapublished = false; char aq_topic[30]; //struct websocket_message *ws_msg; //static double last_control_time; // struct mg_mqtt_message *msg = (struct mg_mqtt_message *)p; (void)nc; // if (ev != MG_EV_POLL) logMessage(LOG_DEBUG, "MQTT handler got event %d\n", ev); switch (ev) { case MG_EV_CONNECT: { set_mqtt(nc); //if (_sdconfig_.mqtt_topic != NULL || _sdconfig_.mqtt_dz_sub_topic != NULL) { struct mg_send_mqtt_handshake_opts opts; memset(&opts, 0, sizeof(opts)); opts.user_name = _sdconfig_.mqtt_user; opts.password = _sdconfig_.mqtt_passwd; opts.keep_alive = 5; // opts.flags = 0x00; // opts.flags |= MG_MQTT_WILL_RETAIN; opts.flags |= MG_MQTT_CLEAN_SESSION; // NFS Need to readup on this sprintf(aq_topic, "%s/%s", _sdconfig_.mqtt_topic,MQTT_LWM_TOPIC); opts.will_topic = aq_topic; opts.will_message = MQTT_OFF; mg_set_protocol_mqtt(nc); mg_send_mqtt_handshake_opt(nc, _sdconfig_.mqtt_ID, opts); logMessage(LOG_INFO, "Connected to mqtt %s with id of: %s\n", _sdconfig_.mqtt_address, _sdconfig_.mqtt_ID); _mqtt_status = mqttrunning; _mqtt_connection = nc; //} //last_control_time = mg_time(); } break; case MG_EV_MQTT_CONNACK: mqtt_msg = (struct mg_mqtt_message *)p; if (mqtt_msg->connack_ret_code != MG_EV_MQTT_CONNACK_ACCEPTED) { logMessage(LOG_WARNING, "Got mqtt connection error: %d\n", mqtt_msg->connack_ret_code); _mqtt_status = mqttstopped; _mqtt_connection = NULL; break; } time(&_mqtt_connected_time); char gp_topic[30]; struct mg_mqtt_topic_expression topics[2]; int qos = 0; // can't be bothered with ack, so set to 0 snprintf(gp_topic, 29, "%s/#", _sdconfig_.mqtt_topic); if (_sdconfig_.enableMQTTaq == true && _sdconfig_.enableMQTTdz == true) { topics[0].topic = gp_topic; topics[0].qos = qos; topics[1].topic = _sdconfig_.mqtt_dz_sub_topic; topics[1].qos = qos; mg_mqtt_subscribe(nc, topics, 2, 42); logMessage(LOG_INFO, "MQTT: Subscribing to '%s'\n", gp_topic); logMessage(LOG_INFO, "MQTT: Subscribing to '%s'\n", _sdconfig_.mqtt_dz_sub_topic); } else if (_sdconfig_.enableMQTTaq == true) { topics[0].topic = gp_topic; topics[0].qos = qos; mg_mqtt_subscribe(nc, topics, 1, 42); logMessage(LOG_INFO, "MQTT: Subscribing to '%s'\n", gp_topic); } else if (_sdconfig_.enableMQTTdz == true) { topics[0].topic = _sdconfig_.mqtt_dz_sub_topic; topics[0].qos = qos; mg_mqtt_subscribe(nc, topics, 1, 42); logMessage(LOG_INFO, "MQTT: Subscribing to '%s'\n", _sdconfig_.mqtt_dz_sub_topic); } if (_sdconfig_.enableMQTTha && hapublished == false) { logMessage (LOG_DEBUG, "MQTT: Publishing to Home Assistant\n"); publish_mqtt_hassio_discover(nc); hapublished = true; } broadcast_sprinklerdstate(nc); break; case MG_EV_MQTT_PUBACK: mqtt_msg = (struct mg_mqtt_message *)p; logMessage(LOG_DEBUG, "MQTT: Message publishing acknowledged (msg_id: %d)\n", mqtt_msg->message_id); break; case MG_EV_MQTT_SUBACK: logMessage(LOG_DEBUG, "MQTT: Subscription(s) acknowledged\n"); sprintf(aq_topic, "%s/%s", _sdconfig_.mqtt_topic,MQTT_LWM_TOPIC); send_mqtt_msg(nc, aq_topic ,MQTT_ON); break; case MG_EV_WEBSOCKET_HANDSHAKE_DONE: // nc->user_data = WS; logMessage(LOG_DEBUG, "++ Websocket joined\n"); break; case MG_EV_CLOSE: if (is_websocket(nc)) { logMessage(LOG_DEBUG, "-- Websocket left\n"); } else if (is_mqtt(nc)) { logMessage(LOG_WARNING, "MQTT Connection closed\n"); _mqtt_status = mqttstopped; _mqtt_connection = NULL; // start_mqtt(nc->mgr); } break; case MG_EV_HTTP_REQUEST: http_msg = (struct http_message *)p; action_web_request(nc, http_msg); break; /* case MG_EV_WEBSOCKET_FRAME: ws_msg = (struct websocket_message *)p; action_websocket_request(nc, ws_msg); break; */ case MG_EV_MQTT_PUBLISH: mqtt_msg = (struct mg_mqtt_message *)p; // logMessage(LOG_DEBUG, "MQTT: (msg_id: %d) topic %.*s\n",mqtt_msg->message_id, mqtt_msg->topic.len, mqtt_msg->topic.p); { // BS since mongoose doesn;t seem to adhear to CLEAN_SESSION. Simply ignore messages for the first session. time_t now; time(&now); // logMessage(LOG_DEBUG, "MQTT: time diff = %ld\n", now - _mqtt_connected_time); if ((now - _mqtt_connected_time) < 1) { logMessage(LOG_DEBUG, "MQTT: received (msg_id: %d), looks like cached message, ignoring\n", mqtt_msg->message_id); break; } } if (mqtt_msg->message_id != 0) { logMessage(LOG_DEBUG, "MQTT: received (msg_id: %d), looks like my own message, ignoring\n", mqtt_msg->message_id); break; } // action_mqtt_message(nc, mqtt_msg); if (_sdconfig_.enableMQTTaq && strncmp(mqtt_msg->topic.p, _sdconfig_.mqtt_topic, strlen(_sdconfig_.mqtt_topic)) == 0) { action_mqtt_message(nc, mqtt_msg); } if (_sdconfig_.enableMQTTdz && strncmp(mqtt_msg->topic.p, _sdconfig_.mqtt_dz_sub_topic, strlen(_sdconfig_.mqtt_dz_sub_topic)) == 0) { action_domoticz_mqtt_message(nc, mqtt_msg); } break; } } bool check_net_services(struct mg_mgr *mgr) { if (_mqtt_status == mqttstopped) logMessage (LOG_NOTICE, "MQTT client stopped\n"); if (_mqtt_status == mqttstopped && _mqtt_status != mqttdisabled) { start_mqtt(mgr); return false; } return true; } void start_mqtt(struct mg_mgr *mgr) { if (_sdconfig_.enableMQTTaq == false) { //if( _sdconfig_.enableMQTTaq == false && _sdconfig_.enableMQTTdz == false ) { logMessage (LOG_NOTICE, "MQTT client is disabled, not stating\n"); _mqtt_status = mqttdisabled; return; } //generate_mqtt_id(_sdconfig_.mqtt_ID, sizeof(_sdconfig_.mqtt_ID)-1); if (strlen(_sdconfig_.mqtt_ID) < 1) { //logMessage (LOG_DEBUG, "MQTTaq %d | MQTTdz %d\n", _sdconfig_.enableMQTTaq, _sdconfig_.enableMQTTdz); generate_mqtt_id(_sdconfig_.mqtt_ID, MQTT_ID_LEN-1); logMessage (LOG_DEBUG, "MQTTaq %d | MQTTdz %d | MQTTha %d\n", _sdconfig_.enableMQTTaq, _sdconfig_.enableMQTTdz, _sdconfig_.enableMQTTha); } logMessage (LOG_NOTICE, "Starting MQTT client to %s\n", _sdconfig_.mqtt_address); if (mg_connect(mgr, _sdconfig_.mqtt_address, ev_handler) == NULL) { logMessage (LOG_ERR, "Failed to create MQTT listener to %s\n", _sdconfig_.mqtt_address); } else { /* NSF NEED TO PASS GPIO STATE HERE int i; for (i=0; i < TOTAL_BUTTONS; i++) { _last_mqtt_aqualinkdata.aqualinkleds[i].state = LED_S_UNKNOWN; } */ _mqtt_status = mqttstarting; } } bool start_net_services(struct mg_mgr *mgr) { //static struct mg_serve_http_opts s_http_server_opts; struct mg_connection *nc; mg_mgr_init(mgr, NULL); logMessage (LOG_NOTICE, "Starting web server on port %s\n", _sdconfig_.socket_port); nc = mg_bind(mgr, _sdconfig_.socket_port, ev_handler); if (nc == NULL) { logMessage (LOG_ERR, "Failed to create listener\n"); return false; } // Set up HTTP server parameters mg_set_protocol_http_websocket(nc); // Start MQTT start_mqtt(mgr); return true; }