kopia lustrzana https://github.com/martin-ger/esp_mqtt
added http client support
rodzic
493d22e53f
commit
4ace989d53
2
Makefile
2
Makefile
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
44
SCRIPTING.md
44
SCRIPTING.md
|
@ -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.
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
119
user/lang.c
119
user/lang.c
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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);
|
||||
|
|
Ładowanie…
Reference in New Issue