added http client support

pull/16/head
Martin Ger 2017-10-02 10:34:59 +02:00
rodzic 493d22e53f
commit 4ace989d53
12 zmienionych plików z 764 dodań i 25 usunięć

Wyświetl plik

@ -33,7 +33,7 @@ ESPPORT ?= /dev/ttyUSB0
TARGET = app
# which modules (subdirectories) of the project to include in compiling
MODULES = driver user mqtt ntp easygpio pwm
MODULES = driver user mqtt ntp easygpio pwm httpclient
#EXTRA_INCDIR = $(BUILD_AREA)/esp-open-sdk/esp-open-lwip/include include
EXTRA_INCDIR = include

Wyświetl plik

@ -80,7 +80,7 @@ By default the "remote" MQTT client is disabled. It can be enabled by setting th
- publish [local|remote] [topic] [data]: this publishes a topic (mainly for testing)
# Scripting
The esp_uMQTT_broker comes with a build-in scripting engine. A script enables the ESP not just to act as a passive broker but to react on events (publications and timing events), to send out its own items and handle local I/O. Details on syntax and semantics of the scripting language can be found here: https://github.com/martin-ger/esp_mqtt/blob/master/SCRIPTING.md . Examples of scripts are in the "scripts" directory
The esp_uMQTT_broker comes with a build-in scripting engine. A script enables the ESP not just to act as a passive broker but to react on events (publications and timing events), to send out its own items and handle local I/O. Details on syntax and semantics of the scripting language can be found here: https://github.com/martin-ger/esp_mqtt/blob/master/SCRIPTING.md . Examples of scripts are in the "scripts" directory.
The script specific CLI commands are:
@ -231,4 +231,5 @@ The *MqttConnectCallback* function does a similar check for the connection, but
- eadf for esp8266_easygpio (https://github.com/eadf/esp8266_easygpio )
- Stefan Brüns for ESP8266_new_pwm (https://github.com/StefanBruens/ESP8266_new_pwm )
- Ian Craggs for mqtt_topic
- Martin d'Allens for esphttpclient
- many others contributing to open software (for the ESP8266)

Wyświetl plik

@ -2,10 +2,12 @@
The scripting language of the esp_uMQTT_broker is stricly event based. It mainly consists of "on _event_ do _action_" clauses. An event can be:
- the reception of an MQTT item,
- the sucessful connection to an external MQTT broker,
- the sucessful connection to an external MQTT broker,
- the sucessful connect to the WiFi network
- an expiring timer,
- a predefined time-of-day,
- a GPIO interrupt, and
- a GPIO interrupt,
- an HTTP response, and
- the initialization of the system.
An action can be a sequence of:
@ -27,17 +29,20 @@ In general, scripts conform to the following BNF:
<statement> <statement>
<event> ::= init |
wificonnect |
mqttconnect |
timer <num> |
clock <timestamp> |
gpio_interrupt <num> (pullup|nopullup) |
topic (local|remote) <topic-id>
topic (local|remote) <topic-id> |
http_response
<action> ::= publish (local|remote) <topic-id> <expr> [retained] |
subscribe (local|remote) <topic-id> |
unsubscribe (local|remote) <topic-id> |
settimer <num> <expr> |
setvar ($[any ASCII]* | @<num>) = <expr> |
http_get <expr> |
gpio_pinmode <num> (input|output) [pullup] |
gpio_out <num> <expr> |
gpio_pwm <num> <num> |
@ -46,12 +51,13 @@ In general, scripts conform to the following BNF:
system <expr> |
<action> <action>
<expr> ::= <val> <op> <expr> | (<expr>) | not (<expr>)
<expr> ::= <val> | <val> <op> <expr> | (<expr>) | not (<expr>)
<op> := '=' | '>' | gte | str_ge | str_gte | '+' | '-' | '*' | '|' | div
<val> := <string> | <const> | #<hex-string> | $[any ASCII]* | @<num> |
gpio_in(<num>) | $this_item | $this_data | $this_gpio | $timestamp | $weekday
gpio_in(<num>) | $this_item | $this_data | $this_gpio |
$this_http_code | $this_http_body | $timestamp | $weekday
<string> := "[any ASCII]*" | [any ASCII]*
@ -77,6 +83,11 @@ init
```
This event happens once after restart of the script. All "config" parameters are applied, but typically WiFi is not yet up and no external nodes are connected. This is typically the clause where the initalization of variables and timers as well as subscriptions to topics on the local broker take place.
```
wificonnect
```
This event happens each time, the esp_uMQTT_broker (re-)connects as client to the WiFi and has received an IP address.
```
mqttconnect
```
@ -102,6 +113,11 @@ gpio_interrupt <num> (pullup|nopullup)
```
This event happens when the GPIO pin with the given number generates an interrupt. An interrupt happens on each state change, i.e. a 0-1-0 sequence will cause two events. Use the special variable _$this_gpio_ to access the actual state of the pin. This variable is only defined inside the "on topic" clause. The interrupt mechanism uses a 50ms delay for debouncing the input. This means this event is suitable for switches, not for high-frequency signals. The "pullup" or "nopullup" defines whether the input pin is free floating or internally pulled to high level.
```
http_response
```
This event happens when an HTTP-request has been sent with "http_get" and a response arrives. The actual body of the response can be accessed in the actions via the special variable _$this_http_body_, the HTTP return code via the special variable _$this_http_code_. These variables are only defined inside the "on http_response" clause.
# Action
```
publish (local|remote) <topic-id> <expr> [retained]
@ -128,6 +144,11 @@ Currently the interpreter is configured for a maximum of 10 variables, with a si
Flash variables can also be used for storing config parameters or handing them over from the CLI to a script. They can be set with the "set @[num] _value_" on the CLI and the written values can then be picked up by a script to read e.g. config parameters like DNS names, IPs, node IDs or username/password.
```
http_get <expr>
```
Sends an HTTP request to the URL given in the expression.
```
gpio_pinmode <num> (input|output) [pullup]
```
@ -146,7 +167,7 @@ Defines the GPIO pin num as PWM output and sets the PWM duty cycle to the given
```
system <expr>
```
Executes the given expression as if it has been issued on the CLI. Useful e.g. for cnditional "save", "lock" or "set" commands.
Executes the given expression as if it has been issued on the CLI. Useful e.g. for "save", "lock" or "reset" commands.
```
print <expr> |
@ -180,9 +201,13 @@ gpio_in(<num>)
Reads the current boolean input value of the given GPIO pin. This pin has to be defined as input before using the "gpio_pinmode" action.
```
$this_item | $this_data | $this_gpio | $timestamp | $weekday
$this_item | $this_data | $this_gpio | $timestamp | $weekday | $this_http_body | $this_http_code
```
Special variables: $this_topic and $this_data are only defined in "on topic" clauses and contain the current topic and its data. $this_gpio contains the state of the GPIO in an "on gpio_interrupt" clause and $timestamp contains the current time of day in "hh:mm:ss" format. If no NTP sync happened the time will be reported as "99:99:99". The variable "$weekday" returns the day of week as three letters ("Mon","Tue",...).
Special variables:
$this_topic and $this_data are only defined in "on topic" clauses and contain the current topic and its data.
$this_gpio contains the state of the GPIO in an "on gpio_interrupt" clause.
$timestamp contains the current time of day in "hh:mm:ss" format. If no NTP sync happened the time will be reported as "99:99:99". $weekday returns the day of week as three letters ("Mon","Tue",...).
$this_http_body and $this_http_code are only defined inside the "on http_response" clause and contain the body of an HTTP response and the HTTP return code.
# Operators
Operators are used to combine values and expressions.
@ -202,6 +227,9 @@ These operators are the arithmetical operations. CAUTION: arithmetical preceeden
```
This operator concatenates the left and the right operator as strings. Useful e.g. in "print" actions or when putting together MQTT topics.
# Comments
Comments start with a %' anywhere in a line and reach until the end of this line.
# Sample
Here is a demo of a script to give you an idea of the power of the scripting feature. This script controls a Sonoff switch module. It connects to a remote MQTT broker and in parallel offers locally its own. The device has a number stored in the variable $device_number. On both brokers it subscribes to a topic named '/martinshome/switch/($device_number)/command', where it receives commands, and it publishes the topic '/martinshome/switch/($device_number)/status' with the current state of the switch relay. It understands the commands 'on','off', 'toggle', and 'blink'. Blinking is realized via a timer event. Local status is stored in the two variables $relay_status and $blink (blinking on/off). The 'on gpio_interrupt' clause reacts on pressing the pushbutton of the Sonoff and simply toggles the switch (and stops blinking). The last two 'on clock' clauses implement a daily on and off period:

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -0,0 +1,525 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Martin d'Allens <martin.dallens@gmail.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
// FIXME: sprintf->snprintf everywhere.
#include "osapi.h"
#include "user_interface.h"
#include "espconn.h"
#include "mem.h"
#include "limits.h"
#include "httpclient.h"
// Debug output.
#if 0
#define PRINTF(...) os_printf(__VA_ARGS__)
#else
#define PRINTF(...)
#endif
// Internal state.
typedef struct {
char * path;
int port;
char * post_data;
char * headers;
char * hostname;
char * buffer;
int buffer_size;
bool secure;
http_callback user_callback;
} request_args;
static char * ICACHE_FLASH_ATTR esp_strdup(const char * str)
{
if (str == NULL) {
return NULL;
}
char * new_str = (char *)os_malloc(os_strlen(str) + 1); // 1 for null character
if (new_str == NULL) {
os_printf("esp_strdup: malloc error");
return NULL;
}
os_strcpy(new_str, str);
return new_str;
}
static int ICACHE_FLASH_ATTR
esp_isupper(char c)
{
return (c >= 'A' && c <= 'Z');
}
static int ICACHE_FLASH_ATTR
esp_isalpha(char c)
{
return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
}
static int ICACHE_FLASH_ATTR
esp_isspace(char c)
{
return (c == ' ' || c == '\t' || c == '\n' || c == '\12');
}
static int ICACHE_FLASH_ATTR
esp_isdigit(char c)
{
return (c >= '0' && c <= '9');
}
/*
* Convert a string to a long integer.
*
* Ignores `locale' stuff. Assumes that the upper and lower case
* alphabets and digits are each contiguous.
*/
static long ICACHE_FLASH_ATTR
esp_strtol(const char *nptr, char **endptr, int base)
{
const char *s = nptr;
unsigned long acc;
int c;
unsigned long cutoff;
int neg = 0, any, cutlim;
/*
* Skip white space and pick up leading +/- sign if any.
* If base is 0, allow 0x for hex and 0 for octal, else
* assume decimal; if base is already 16, allow 0x.
*/
do {
c = *s++;
} while (esp_isspace(c));
if (c == '-') {
neg = 1;
c = *s++;
} else if (c == '+')
c = *s++;
if ((base == 0 || base == 16) &&
c == '0' && (*s == 'x' || *s == 'X')) {
c = s[1];
s += 2;
base = 16;
} else if ((base == 0 || base == 2) &&
c == '0' && (*s == 'b' || *s == 'B')) {
c = s[1];
s += 2;
base = 2;
}
if (base == 0)
base = c == '0' ? 8 : 10;
/*
* Compute the cutoff value between legal numbers and illegal
* numbers. That is the largest legal value, divided by the
* base. An input number that is greater than this value, if
* followed by a legal input character, is too big. One that
* is equal to this value may be valid or not; the limit
* between valid and invalid numbers is then based on the last
* digit. For instance, if the range for longs is
* [-2147483648..2147483647] and the input base is 10,
* cutoff will be set to 214748364 and cutlim to either
* 7 (neg==0) or 8 (neg==1), meaning that if we have accumulated
* a value > 214748364, or equal but the next digit is > 7 (or 8),
* the number is too big, and we will return a range error.
*
* Set any if any `digits' consumed; make it negative to indicate
* overflow.
*/
cutoff = neg ? -(unsigned long)LONG_MIN : LONG_MAX;
cutlim = cutoff % (unsigned long)base;
cutoff /= (unsigned long)base;
for (acc = 0, any = 0;; c = *s++) {
if (esp_isdigit(c))
c -= '0';
else if (esp_isalpha(c))
c -= esp_isupper(c) ? 'A' - 10 : 'a' - 10;
else
break;
if (c >= base)
break;
if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim))
any = -1;
else {
any = 1;
acc *= base;
acc += c;
}
}
if (any < 0) {
acc = neg ? LONG_MIN : LONG_MAX;
// errno = ERANGE;
} else if (neg)
acc = -acc;
if (endptr != 0)
*endptr = (char *)(any ? s - 1 : nptr);
return (acc);
}
static int ICACHE_FLASH_ATTR chunked_decode(char * chunked, int size)
{
char *src = chunked;
char *end = chunked + size;
int i, dst = 0;
do
{
//[chunk-size]
i = esp_strtol(src, (char **) NULL, 16);
PRINTF("Chunk Size:%d\r\n", i);
if (i <= 0)
break;
//[chunk-size-end-ptr]
src = (char *)os_strstr(src, "\r\n") + 2;
//[chunk-data]
os_memmove(&chunked[dst], src, i);
src += i + 2; /* CRLF */
dst += i;
} while (src < end);
//
//footer CRLF
//
/* decoded size */
return dst;
}
static void ICACHE_FLASH_ATTR receive_callback(void * arg, char * buf, unsigned short len)
{
struct espconn * conn = (struct espconn *)arg;
request_args * req = (request_args *)conn->reverse;
if (req->buffer == NULL) {
return;
}
// Let's do the equivalent of a realloc().
const int new_size = req->buffer_size + len;
char * new_buffer;
if (new_size > BUFFER_SIZE_MAX || NULL == (new_buffer = (char *)os_malloc(new_size))) {
os_printf("Response too long (%d)\n", new_size);
req->buffer[0] = '\0'; // Discard the buffer to avoid using an incomplete response.
if (req->secure)
#ifdef HTTPCS
espconn_secure_disconnect(conn);
#else
os_printf("SSL not available\r\n");
#endif
else
espconn_disconnect(conn);
return; // The disconnect callback will be called.
}
os_memcpy(new_buffer, req->buffer, req->buffer_size);
os_memcpy(new_buffer + req->buffer_size - 1 /*overwrite the null character*/, buf, len); // Append new data.
new_buffer[new_size - 1] = '\0'; // Make sure there is an end of string.
os_free(req->buffer);
req->buffer = new_buffer;
req->buffer_size = new_size;
}
static void ICACHE_FLASH_ATTR sent_callback(void * arg)
{
struct espconn * conn = (struct espconn *)arg;
request_args * req = (request_args *)conn->reverse;
if (req->post_data == NULL) {
PRINTF("All sent\n");
}
else {
// The headers were sent, now send the contents.
PRINTF("Sending request body\n");
if (req->secure)
#ifdef HTTPCS
espconn_secure_sent(conn, (uint8_t *)req->post_data, strlen(req->post_data));
#else
os_printf("SSL not available\r\n");
#endif
else
espconn_sent(conn, (uint8_t *)req->post_data, strlen(req->post_data));
os_free(req->post_data);
req->post_data = NULL;
}
}
static void ICACHE_FLASH_ATTR connect_callback(void * arg)
{
PRINTF("Connected\n");
struct espconn * conn = (struct espconn *)arg;
request_args * req = (request_args *)conn->reverse;
espconn_regist_recvcb(conn, receive_callback);
espconn_regist_sentcb(conn, sent_callback);
const char * method = "GET";
char post_headers[32] = "";
if (req->post_data != NULL) { // If there is data this is a POST request.
method = "POST";
os_sprintf(post_headers, "Content-Length: %d\r\n", strlen(req->post_data));
}
char buf[69 + strlen(method) + strlen(req->path) + strlen(req->hostname) +
strlen(req->headers) + strlen(post_headers)];
int len = os_sprintf(buf,
"%s %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"Connection: close\r\n"
"User-Agent: ESP8266\r\n"
"%s"
"%s"
"\r\n",
method, req->path, req->hostname, req->port, req->headers, post_headers);
if (req->secure)
#ifdef HTTPCS
espconn_secure_sent(conn, (uint8_t *)buf, len);
#else
os_printf("SSL not available\r\n");
#endif
else
espconn_sent(conn, (uint8_t *)buf, len);
os_free(req->headers);
req->headers = NULL;
PRINTF("Sending request header\n");
}
static void ICACHE_FLASH_ATTR disconnect_callback(void * arg)
{
PRINTF("Disconnected\n");
struct espconn *conn = (struct espconn *)arg;
if(conn == NULL) {
return;
}
if(conn->reverse != NULL) {
request_args * req = (request_args *)conn->reverse;
int http_status = -1;
int body_size = 0;
char * body = "";
if (req->buffer == NULL) {
os_printf("Buffer shouldn't be NULL\n");
}
else if (req->buffer[0] != '\0') {
// FIXME: make sure this is not a partial response, using the Content-Length header.
const char * version10 = "HTTP/1.0 ";
const char * version11 = "HTTP/1.1 ";
if (os_strncmp(req->buffer, version10, strlen(version10)) != 0
&& os_strncmp(req->buffer, version11, strlen(version11)) != 0) {
os_printf("Invalid version in %s\n", req->buffer);
}
else {
http_status = atoi(req->buffer + strlen(version10));
/* find body and zero terminate headers */
body = (char *)os_strstr(req->buffer, "\r\n\r\n") + 2;
*body++ = '\0';
*body++ = '\0';
body_size = req->buffer_size - (body - req->buffer);
if(os_strstr(req->buffer, "Transfer-Encoding: chunked"))
{
body_size = chunked_decode(body, body_size);
body[body_size] = '\0';
}
}
}
if (req->user_callback != NULL) { // Callback is optional.
req->user_callback(body, http_status, req->buffer, body_size);
}
os_free(req->buffer);
os_free(req->hostname);
os_free(req->path);
os_free(req);
}
espconn_delete(conn);
if(conn->proto.tcp != NULL) {
os_free(conn->proto.tcp);
}
os_free(conn);
}
static void ICACHE_FLASH_ATTR error_callback(void *arg, sint8 errType)
{
PRINTF("Disconnected with error\n");
disconnect_callback(arg);
}
static void ICACHE_FLASH_ATTR dns_callback(const char * hostname, ip_addr_t * addr, void * arg)
{
request_args * req = (request_args *)arg;
if (addr == NULL) {
os_printf("DNS failed for %s\n", hostname);
if (req->user_callback != NULL) {
req->user_callback("", -1, "", 0);
}
os_free(req->buffer);
os_free(req->post_data);
os_free(req->headers);
os_free(req->path);
os_free(req->hostname);
os_free(req);
}
else {
PRINTF("DNS found %s " IPSTR "\n", hostname, IP2STR(addr));
struct espconn * conn = (struct espconn *)os_malloc(sizeof(struct espconn));
conn->type = ESPCONN_TCP;
conn->state = ESPCONN_NONE;
conn->proto.tcp = (esp_tcp *)os_malloc(sizeof(esp_tcp));
conn->proto.tcp->local_port = espconn_port();
conn->proto.tcp->remote_port = req->port;
conn->reverse = req;
os_memcpy(conn->proto.tcp->remote_ip, addr, 4);
espconn_regist_connectcb(conn, connect_callback);
espconn_regist_disconcb(conn, disconnect_callback);
espconn_regist_reconcb(conn, error_callback);
if (req->secure) {
#ifdef HTTPCS
espconn_secure_set_size(ESPCONN_CLIENT,5120); // set SSL buffer size
espconn_secure_connect(conn);
#else
os_printf("SSL not available\r\n");
#endif
} else {
espconn_connect(conn);
}
}
}
void ICACHE_FLASH_ATTR http_raw_request(const char * hostname, int port, bool secure, const char * path, const char * post_data, const char * headers, http_callback user_callback)
{
PRINTF("DNS request\n");
request_args * req = (request_args *)os_malloc(sizeof(request_args));
req->hostname = esp_strdup(hostname);
req->path = esp_strdup(path);
req->port = port;
req->secure = secure;
req->headers = esp_strdup(headers);
req->post_data = esp_strdup(post_data);
req->buffer_size = 1;
req->buffer = (char *)os_malloc(1);
req->buffer[0] = '\0'; // Empty string.
req->user_callback = user_callback;
ip_addr_t addr;
err_t error = espconn_gethostbyname((struct espconn *)req, // It seems we don't need a real espconn pointer here.
hostname, &addr, dns_callback);
if (error == ESPCONN_INPROGRESS) {
PRINTF("DNS pending\n");
}
else if (error == ESPCONN_OK) {
// Already in the local names table (or hostname was an IP address), execute the callback ourselves.
dns_callback(hostname, &addr, req);
}
else {
if (error == ESPCONN_ARG) {
os_printf("DNS arg error %s\n", hostname);
}
else {
os_printf("DNS error code %d\n", error);
}
dns_callback(hostname, NULL, req); // Handle all DNS errors the same way.
}
}
/*
* Parse an URL of the form http://host:port/path
* <host> can be a hostname or an IP address
* <port> is optional
*/
void ICACHE_FLASH_ATTR http_post(const char * url, const char * post_data, const char * headers, http_callback user_callback)
{
// FIXME: handle HTTP auth with http://user:pass@host/
// FIXME: get rid of the #anchor part if present.
char hostname[128] = "";
int port = 80;
bool secure = false;
bool is_http = os_strncmp(url, "http://", strlen("http://")) == 0;
bool is_https = os_strncmp(url, "https://", strlen("https://")) == 0;
if (is_http)
url += strlen("http://"); // Get rid of the protocol.
else if (is_https) {
port = 443;
secure = true;
url += strlen("https://"); // Get rid of the protocol.
} else {
os_printf("URL is not HTTP or HTTPS %s\n", url);
return;
}
char * path = os_strchr(url, '/');
if (path == NULL) {
path = os_strchr(url, '\0'); // Pointer to end of string.
}
char * colon = os_strchr(url, ':');
if (colon > path) {
colon = NULL; // Limit the search to characters before the path.
}
if (colon == NULL) { // The port is not present.
os_memcpy(hostname, url, path - url);
hostname[path - url] = '\0';
}
else {
port = atoi(colon + 1);
if (port == 0) {
os_printf("Port error %s\n", url);
return;
}
os_memcpy(hostname, url, colon - url);
hostname[colon - url] = '\0';
}
if (path[0] == '\0') { // Empty path is not allowed.
path = "/";
}
PRINTF("hostname=%s\n", hostname);
PRINTF("port=%d\n", port);
PRINTF("path=%s\n", path);
http_raw_request(hostname, port, secure, path, post_data, headers, user_callback);
}
void ICACHE_FLASH_ATTR http_get(const char * url, const char * headers, http_callback user_callback)
{
http_post(url, NULL, headers, user_callback);
}
void ICACHE_FLASH_ATTR http_callback_example(char * response_body, int http_status, char * response_headers, int body_size)
{
os_printf("http_status=%d\n", http_status);
if (http_status != HTTP_STATUS_GENERIC_ERROR) {
os_printf("strlen(headers)=%d\n", strlen(response_headers));
os_printf("body_size=%d\n", body_size);
os_printf("body=%s<EOF>\n", response_body); // FIXME: this does not handle binary data.
}
}

Wyświetl plik

@ -0,0 +1,53 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Martin d'Allens <martin.dallens@gmail.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
#ifndef HTTPCLIENT_H
#define HTTPCLIENT_H
//#include <espmissingincludes.h> // This can remove some warnings depending on your project setup. It is safe to remove this line.
#define HTTP_STATUS_GENERIC_ERROR -1 // In case of TCP or DNS error the callback is called with this status.
#define BUFFER_SIZE_MAX 5000 // Size of http responses that will cause an error.
/*
* "full_response" is a string containing all response headers and the response body.
* "response_body and "http_status" are extracted from "full_response" for convenience.
*
* A successful request corresponds to an HTTP status code of 200 (OK).
* More info at http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
*/
typedef void (* http_callback)(char * response_body, int http_status, char * response_headers, int body_size);
/*
* Download a web page from its URL.
* Try:
* http_get("http://wtfismyip.com/text", http_callback_example);
*/
void ICACHE_FLASH_ATTR http_get(const char * url, const char * headers, http_callback user_callback);
/*
* Post data to a web form.
* The data should be encoded as application/x-www-form-urlencoded.
* Try:
* http_post("http://httpbin.org/post", "first_word=hello&second_word=world", http_callback_example);
*/
void ICACHE_FLASH_ATTR http_post(const char * url, const char * post_data, const char * headers, http_callback user_callback);
/*
* Call this function to skip URL parsing if the arguments are already in separate variables.
*/
void ICACHE_FLASH_ATTR http_raw_request(const char * hostname, int port, bool secure, const char * path, const char * post_data, const char * headers, http_callback user_callback);
/*
* Output on the UART.
*/
void ICACHE_FLASH_ATTR http_callback_example(char * response_body, int http_status, char * response_headers, int body_size);
#endif

Wyświetl plik

@ -0,0 +1,16 @@
% Config params, overwrite any previous settings from the commandline
% Nothing here
% Now the events, checked whenever something happens
on wificonnect
do
println "get http://wtfismyip.com/text"
http_get "http://wtfismyip.com/text"
on http_response
do
println "return code: " | $this_http_code
println $this_http_body

Wyświetl plik

@ -56,16 +56,24 @@ char **my_token;
int max_token;
bool script_enabled = false;
bool in_topic_statement;
bool in_gpio_statement;
Interpreter_Status interpreter_status;
char *interpreter_topic;
char *interpreter_data;
int interpreter_data_len;
int interpreter_timer;
char *interpreter_timestamp;
int ts_counter;
#ifdef GPIO
bool in_gpio_statement;
int interpreter_gpio;
int interpreter_gpioval;
int ts_counter;
#endif
#ifdef HTTPC
bool in_http_statement;
int interpreter_http_status;
void interpreter_http_reply(char *response_body, int http_status, char *response_headers, int body_size);
#endif
static os_timer_t timers[MAX_TIMERS];
var_entry_t vars[MAX_VARS];
@ -164,7 +172,7 @@ void ICACHE_FLASH_ATTR inttimer_func(void *arg){
}
// Interrupt handler - this function will be executed on any edge of a GPIO
LOCAL void gpio_intr_handler(void *arg)
LOCAL void gpio_intr_handler(void *arg)
{
gpio_entry_t *my_gpio_entry = (gpio_entry_t *)arg;
@ -442,7 +450,13 @@ int ICACHE_FLASH_ATTR parse_statement(int next_token) {
while ((next_token = syn_chk ? next_token : search_token(next_token, "on")) < max_token) {
in_topic_statement = in_gpio_statement = false;
in_topic_statement = false;
#ifdef GPIO
in_gpio_statement = false;
#endif
#ifdef HTTPC
in_http_statement = false;
#endif
if (is_token(next_token, "on")) {
lang_debug("statement on\r\n");
@ -489,7 +503,16 @@ int ICACHE_FLASH_ATTR parse_event(int next_token, bool * happend) {
*happend = (interpreter_status == MQTT_CLIENT_CONNECT);
if (*happend)
lang_log("on init\r\n");
lang_log("on mqttconnect\r\n");
return next_token + 1;
}
if (is_token(next_token, "wificonnect")) {
lang_debug("event wificonnect\r\n");
*happend = (interpreter_status == WIFI_CONNECT);
if (*happend)
lang_log("on wificonnect\r\n");
return next_token + 1;
}
@ -587,8 +610,18 @@ int ICACHE_FLASH_ATTR parse_event(int next_token, bool * happend) {
lang_log("on clock %s\r\n", my_token[next_token + 1]);
return next_token + 2;
}
#ifdef HTTPC
if (is_token(next_token, "http_response")) {
lang_debug("event http_response\r\n");
in_http_statement = true;
return syntax_error(next_token, "'init', 'mqttconnect', 'topic', 'gpio_interrupt', 'clock', or 'timer' expected");
*happend = (interpreter_status == HTTP_RESPONSE);
if (*happend)
lang_log("on http_response\r\n");
return next_token + 1;
}
#endif
return syntax_error(next_token, "'init', 'mqttconnect', 'topic', 'gpio_interrupt', 'clock', 'http_response', or 'timer' expected");
}
int ICACHE_FLASH_ATTR parse_action(int next_token, bool doit) {
@ -879,6 +912,21 @@ int ICACHE_FLASH_ATTR parse_action(int next_token, bool doit) {
}
}
}
#ifdef HTTPC
else if (is_token(next_token, "http_get")) {
len_check(2);
char *url_data;
int url_len;
Value_Type url_type;
if ((next_token = parse_expression(next_token + 1, &url_data, &url_len, &url_type, doit)) == -1)
return -1;
if (doit) {
http_get(url_data, "", interpreter_http_reply);
}
}
#endif
#ifdef GPIO
else if (is_token(next_token, "gpio_pinmode")) {
len_check(2);
@ -1182,6 +1230,32 @@ int ICACHE_FLASH_ATTR parse_value(int next_token, char **data, int *data_len, Va
return next_token + 1;
}
#endif
#ifdef HTTPC
else if (is_token(next_token, "$this_http_body")) {
lang_debug("val $this_http_body\r\n");
if (!in_http_statement)
return syntax_error(next_token, "undefined $this_http_body");
*data = interpreter_data;
*data_len = interpreter_data_len;
*data_type = STRING_T;
return next_token + 1;
}
else if (is_token(next_token, "$this_http_code")) {
static char codebuf[4];
lang_debug("val $this_http_code\r\n");
if (!in_http_statement)
return syntax_error(next_token, "undefined $this_http_code");
os_sprintf(codebuf, "%3d", interpreter_http_status);
*data = codebuf;
*data_len = os_strlen(codebuf);
*data_type = STRING_T;
return next_token + 1;
}
#endif
#ifdef NTP
else if (is_token(next_token, "$timestamp")) {
lang_debug("val $timestamp\r\n");
@ -1330,11 +1404,23 @@ int ICACHE_FLASH_ATTR interpreter_init() {
return ret_val;
}
int ICACHE_FLASH_ATTR interpreter_reconnect(void) {
int ICACHE_FLASH_ATTR interpreter_wifi_connect(void) {
if (!script_enabled)
return -1;
lang_debug("interpreter_init_reconnect\r\n");
lang_debug("interpreter_wifi_connect\r\n");
interpreter_status = WIFI_CONNECT;
interpreter_topic = interpreter_data = "";
interpreter_data_len = 0;
return parse_statement(0);
}
int ICACHE_FLASH_ATTR interpreter_mqtt_connect(void) {
if (!script_enabled)
return -1;
lang_debug("interpreter_mqtt_connect\r\n");
interpreter_status = MQTT_CLIENT_CONNECT;
interpreter_topic = interpreter_data = "";
@ -1360,3 +1446,20 @@ int ICACHE_FLASH_ATTR interpreter_topic_received(const char *topic, const char *
return parse_statement(0);
}
#ifdef HTTPC
void ICACHE_FLASH_ATTR interpreter_http_reply(char *response_body, int http_status, char *response_headers, int body_size) {
if (!script_enabled)
return;
lang_debug("interpreter_http_reply\r\n");
interpreter_status = HTTP_RESPONSE;
interpreter_topic = response_headers;
interpreter_http_status = http_status;
interpreter_data = response_body;
interpreter_data_len = body_size;
parse_statement(0);
}
#endif

Wyświetl plik

@ -4,7 +4,7 @@
#include "mqtt_server.h"
typedef enum {SYNTAX_CHECK, CONFIG, INIT, MQTT_CLIENT_CONNECT, TOPIC_LOCAL, TOPIC_REMOTE, TIMER, GPIO_INT, CLOCK} Interpreter_Status;
typedef enum {SYNTAX_CHECK, CONFIG, INIT, MQTT_CLIENT_CONNECT, WIFI_CONNECT, TOPIC_LOCAL, TOPIC_REMOTE, TIMER, GPIO_INT, CLOCK, HTTP_RESPONSE} Interpreter_Status;
typedef enum {STRING_T, DATA_T} Value_Type;
typedef struct _var_entry_t {
@ -40,7 +40,8 @@ extern bool script_enabled;
int interpreter_syntax_check();
int interpreter_config();
int interpreter_init();
int interpreter_reconnect(void);
int interpreter_mqtt_connect(void);
int interpreter_wifi_connect(void);
int interpreter_topic_received(const char *topic, const char *data, int data_len, bool local);
void init_timestamps(uint8_t *curr_time);

Wyświetl plik

@ -13,10 +13,11 @@
// Here the MQTT stuff
//
//
// Define this if you want to have it work as a MQTT client
#define MQTT_CLIENT 1
// Define this if you need SSL for the *MQTT client*
// Define MQTT_SSL_ENABLE if you need SSL for the *MQTT client*
//
#define MQTT_CLIENT 1
//#define MQTT_SSL_ENABLE 1
#define MQTT_BUF_SIZE 1024
@ -59,6 +60,13 @@
//
#define NTP 1
//
// Define this if you want to have HTTP client support.
// Define HTTPCS if you want to have additional HTTPS support.
//
#define HTTPC 1
//#define HTTPCS 1
//
// Define this if you want to have mDNS support in scripts.
//

Wyświetl plik

@ -106,7 +106,7 @@ static void ICACHE_FLASH_ATTR mqttConnectedCb(uint32_t * args) {
MQTT_Client *client = (MQTT_Client *) args;
mqtt_connected = true;
#ifdef SCRIPTED
interpreter_reconnect();
interpreter_mqtt_connect();
#endif
os_printf("MQTT client connected\r\n");
}
@ -1260,7 +1260,7 @@ static void ICACHE_FLASH_ATTR user_procTask(os_event_t * events) {
switch (events->sig) {
case SIG_START_SERVER:
// Anything else to do here, when the repeater has received its IP?
// Anything else to do here, when the broker has received its IP?
break;
#ifdef SCRIPTED
case SIG_TOPIC_RECEIVED:
@ -1366,6 +1366,10 @@ void wifi_handle_event_cb(System_Event_t * evt) {
my_ip = evt->event_info.got_ip.ip;
connected = true;
#ifdef SCRIPTED
interpreter_wifi_connect();
#endif
#ifdef MQTT_CLIENT
if (mqtt_enabled)
MQTT_Connect(&mqttClient);