Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ruanhaishen
Nodemcu Firmware
Commits
92eb21cd
Commit
92eb21cd
authored
Mar 17, 2016
by
jfollas
Browse files
Merge branch 'dev' into mqtt-connack
# Conflicts: # app/modules/mqtt.c
parents
354b9e96
09269a64
Changes
9
Hide whitespace changes
Inline
Side-by-side
app/modules/gpio.c
View file @
92eb21cd
...
...
@@ -12,6 +12,7 @@
#define PULLUP PLATFORM_GPIO_PULLUP
#define FLOAT PLATFORM_GPIO_FLOAT
#define OUTPUT PLATFORM_GPIO_OUTPUT
#define OPENDRAIN PLATFORM_GPIO_OPENDRAIN
#define INPUT PLATFORM_GPIO_INPUT
#define INTERRUPT PLATFORM_GPIO_INT
#define HIGH PLATFORM_GPIO_HIGH
...
...
@@ -93,7 +94,7 @@ static int lgpio_mode( lua_State* L )
unsigned
pullup
=
luaL_optinteger
(
L
,
3
,
FLOAT
);
luaL_argcheck
(
L
,
platform_gpio_exists
(
pin
)
&&
(
mode
!=
INTERRUPT
||
pin
>
0
),
1
,
"Invalid pin"
);
luaL_argcheck
(
L
,
mode
==
OUTPUT
||
mode
==
INPUT
luaL_argcheck
(
L
,
mode
==
OUTPUT
||
mode
==
OPENDRAIN
||
mode
==
INPUT
#ifdef GPIO_INTERRUPT_ENABLE
||
mode
==
INTERRUPT
#endif
...
...
@@ -203,12 +204,13 @@ static const LUA_REG_TYPE gpio_map[] = {
{
LSTRKEY
(
"trig"
),
LFUNCVAL
(
lgpio_trig
)
},
{
LSTRKEY
(
"INT"
),
LNUMVAL
(
INTERRUPT
)
},
#endif
{
LSTRKEY
(
"OUTPUT"
),
LNUMVAL
(
OUTPUT
)
},
{
LSTRKEY
(
"INPUT"
),
LNUMVAL
(
INPUT
)
},
{
LSTRKEY
(
"HIGH"
),
LNUMVAL
(
HIGH
)
},
{
LSTRKEY
(
"LOW"
),
LNUMVAL
(
LOW
)
},
{
LSTRKEY
(
"FLOAT"
),
LNUMVAL
(
FLOAT
)
},
{
LSTRKEY
(
"PULLUP"
),
LNUMVAL
(
PULLUP
)
},
{
LSTRKEY
(
"OUTPUT"
),
LNUMVAL
(
OUTPUT
)
},
{
LSTRKEY
(
"OPENDRAIN"
),
LNUMVAL
(
OPENDRAIN
)
},
{
LSTRKEY
(
"INPUT"
),
LNUMVAL
(
INPUT
)
},
{
LSTRKEY
(
"HIGH"
),
LNUMVAL
(
HIGH
)
},
{
LSTRKEY
(
"LOW"
),
LNUMVAL
(
LOW
)
},
{
LSTRKEY
(
"FLOAT"
),
LNUMVAL
(
FLOAT
)
},
{
LSTRKEY
(
"PULLUP"
),
LNUMVAL
(
PULLUP
)
},
{
LNILKEY
,
LNILVAL
}
};
...
...
app/modules/mqtt.c
View file @
92eb21cd
...
...
@@ -55,7 +55,6 @@ typedef struct mqtt_state_t
typedef
struct
lmqtt_userdata
{
lua_State
*
L
;
struct
espconn
*
pesp_conn
;
int
self_ref
;
int
cb_connect_ref
;
...
...
@@ -94,11 +93,13 @@ static void mqtt_socket_disconnected(void *arg) // tcp only
os_timer_disarm
(
&
mud
->
mqttTimer
);
lua_State
*
L
=
lua_getstate
();
if
(
mud
->
connected
){
// call back only called when socket is from connection to disconnection.
mud
->
connected
=
false
;
if
(
(
mud
->
L
!=
NULL
)
&&
(
mud
->
cb_disconnect_ref
!=
LUA_NOREF
)
&&
(
mud
->
self_ref
!=
LUA_NOREF
))
{
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_disconnect_ref
);
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata(client) to callback func in lua
if
((
mud
->
cb_disconnect_ref
!=
LUA_NOREF
)
&&
(
mud
->
self_ref
!=
LUA_NOREF
))
{
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_disconnect_ref
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata(client) to callback func in lua
call_back
=
true
;
}
}
...
...
@@ -123,18 +124,14 @@ static void mqtt_socket_disconnected(void *arg) // tcp only
mud
->
pesp_conn
=
NULL
;
}
if
(
mud
->
L
==
NULL
)
return
;
lua_gc
(
mud
->
L
,
LUA_GCSTOP
,
0
);
if
(
mud
->
self_ref
!=
LUA_NOREF
){
// TODO: should we unref the client and delete it?
luaL_unref
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
luaL_unref
(
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
mud
->
self_ref
=
LUA_NOREF
;
// unref this, and the mqtt.socket userdata will delete it self
}
lua_gc
(
mud
->
L
,
LUA_GCRESTART
,
0
);
}
if
(
(
mud
->
L
!=
NULL
)
&&
call_back
){
lua_call
(
mud
->
L
,
1
,
0
);
if
(
call_back
){
lua_call
(
L
,
1
,
0
);
}
NODE_DBG
(
"leave mqtt_socket_disconnected.
\n
"
);
...
...
@@ -180,38 +177,72 @@ static void deliver_publish(lmqtt_userdata * mud, uint8_t* message, int length)
return
;
if
(
mud
->
self_ref
==
LUA_NOREF
)
return
;
if
(
mud
->
L
==
NULL
)
return
;
lua_State
*
L
=
lua_getstate
();
if
(
event_data
.
topic
&&
(
event_data
.
topic_length
>
0
)){
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_message_ref
);
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata to callback func in lua
lua_pushlstring
(
mud
->
L
,
event_data
.
topic
,
event_data
.
topic_length
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_message_ref
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata to callback func in lua
lua_pushlstring
(
L
,
event_data
.
topic
,
event_data
.
topic_length
);
}
else
{
NODE_DBG
(
"get wrong packet.
\n
"
);
return
;
}
if
(
event_data
.
data
&&
(
event_data
.
data_length
>
0
)){
lua_pushlstring
(
mud
->
L
,
event_data
.
data
,
event_data
.
data_length
);
lua_call
(
mud
->
L
,
3
,
0
);
lua_pushlstring
(
L
,
event_data
.
data
,
event_data
.
data_length
);
lua_call
(
L
,
3
,
0
);
}
else
{
lua_call
(
mud
->
L
,
2
,
0
);
lua_call
(
L
,
2
,
0
);
}
NODE_DBG
(
"leave deliver_publish.
\n
"
);
}
static
void
mqtt_connack_fail
(
lmqtt_userdata
*
mud
,
int
reason_code
)
{
if
(
mud
->
cb_connect_fail_ref
==
LUA_NOREF
)
return
;
if
(
mud
->
self_ref
==
LUA_NOREF
)
return
;
if
(
mud
->
L
==
NULL
)
return
;
if
(
mud
->
cb_connect_fail_ref
==
LUA_NOREF
||
mud
->
self_ref
==
LUA_NOREF
)
{
return
;
}
lua_State
*
L
=
lua_getstate
();
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_connect_fail_ref
);
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata(client) to callback func in lua
lua_pushinteger
(
mud
->
L
,
reason_code
);
lua_call
(
mud
->
L
,
2
,
0
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_connect_fail_ref
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata(client) to callback func in lua
lua_pushinteger
(
L
,
reason_code
);
lua_call
(
L
,
2
,
0
);
}
static
sint8
mqtt_send_if_possible
(
struct
espconn
*
pesp_conn
)
{
if
(
pesp_conn
==
NULL
)
return
ESPCONN_OK
;
lmqtt_userdata
*
mud
=
(
lmqtt_userdata
*
)
pesp_conn
->
reverse
;
if
(
mud
==
NULL
)
return
ESPCONN_OK
;
sint8
espconn_status
=
ESPCONN_OK
;
// This indicates if we have sent something and are waiting for something to
// happen
if
(
mud
->
event_timeout
==
0
)
{
msg_queue_t
*
pending_msg
=
msg_peek
(
&
(
mud
->
mqtt_state
.
pending_msg_q
));
if
(
pending_msg
)
{
mud
->
event_timeout
=
MQTT_SEND_TIMEOUT
;
NODE_DBG
(
"Sent: %d
\n
"
,
pending_msg
->
msg
.
length
);
#ifdef CLIENT_SSL_ENABLE
if
(
mud
->
secure
)
{
espconn_status
=
espconn_secure_send
(
pesp_conn
,
pending_msg
->
msg
.
data
,
pending_msg
->
msg
.
length
);
}
else
#endif
{
espconn_status
=
espconn_send
(
pesp_conn
,
pending_msg
->
msg
.
data
,
pending_msg
->
msg
.
length
);
}
mud
->
keep_alive_tick
=
0
;
}
}
NODE_DBG
(
"send_if_poss, queue size: %d
\n
"
,
msg_size
(
&
(
mud
->
mqtt_state
.
pending_msg_q
)));
return
espconn_status
;
}
static
void
mqtt_socket_received
(
void
*
arg
,
char
*
pdata
,
unsigned
short
len
)
...
...
@@ -221,7 +252,6 @@ static void mqtt_socket_received(void *arg, char *pdata, unsigned short len)
uint8_t
msg_type
;
uint8_t
msg_qos
;
uint16_t
msg_id
;
msg_queue_t
*
node
=
NULL
;
int
length
=
(
int
)
len
;
// uint8_t in_buffer[MQTT_BUF_SIZE];
uint8_t
*
in_buffer
=
(
uint8_t
*
)
pdata
;
...
...
@@ -241,7 +271,8 @@ READPACKET:
uint8_t
temp_buffer
[
MQTT_BUF_SIZE
];
mqtt_msg_init
(
&
mud
->
mqtt_state
.
mqtt_connection
,
temp_buffer
,
MQTT_BUF_SIZE
);
mqtt_message_t
*
temp_msg
=
NULL
;
lua_State
*
L
=
lua_getstate
();
switch
(
mud
->
connState
){
case
MQTT_CONNECT_SENDING
:
case
MQTT_CONNECT_SENT
:
...
...
@@ -292,11 +323,9 @@ READPACKET:
break
;
if
(
mud
->
self_ref
==
LUA_NOREF
)
break
;
if
(
mud
->
L
==
NULL
)
break
;
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_connect_ref
);
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata(client) to callback func in lua
lua_call
(
mud
->
L
,
1
,
0
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_connect_ref
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata(client) to callback func in lua
lua_call
(
L
,
1
,
0
);
break
;
}
break
;
...
...
@@ -325,11 +354,9 @@ READPACKET:
break
;
if
(
mud
->
self_ref
==
LUA_NOREF
)
break
;
if
(
mud
->
L
==
NULL
)
break
;
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_suback_ref
);
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
lua_call
(
mud
->
L
,
1
,
0
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_suback_ref
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
lua_call
(
L
,
1
,
0
);
}
break
;
case
MQTT_MSG_TYPE_UNSUBACK
:
...
...
@@ -341,12 +368,12 @@ READPACKET:
case
MQTT_MSG_TYPE_PUBLISH
:
if
(
msg_qos
==
1
){
temp_msg
=
mqtt_msg_puback
(
&
mud
->
mqtt_state
.
mqtt_connection
,
msg_id
);
node
=
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
msg_id
,
MQTT_MSG_TYPE_PUBACK
,
(
int
)
mqtt_get_qos
(
temp_msg
->
data
)
);
}
else
if
(
msg_qos
==
2
){
temp_msg
=
mqtt_msg_pubrec
(
&
mud
->
mqtt_state
.
mqtt_connection
,
msg_id
);
node
=
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
msg_id
,
MQTT_MSG_TYPE_PUBREC
,
(
int
)
mqtt_get_qos
(
temp_msg
->
data
)
);
}
if
(
msg_qos
==
1
||
msg_qos
==
2
){
...
...
@@ -362,11 +389,9 @@ READPACKET:
break
;
if
(
mud
->
self_ref
==
LUA_NOREF
)
break
;
if
(
mud
->
L
==
NULL
)
break
;
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_puback_ref
);
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata to callback func in lua
lua_call
(
mud
->
L
,
1
,
0
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_puback_ref
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata to callback func in lua
lua_call
(
L
,
1
,
0
);
}
break
;
...
...
@@ -376,7 +401,7 @@ READPACKET:
// Note: actually, should not destroy the msg until PUBCOMP is received.
msg_destroy
(
msg_dequeue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
)));
temp_msg
=
mqtt_msg_pubrel
(
&
mud
->
mqtt_state
.
mqtt_connection
,
msg_id
);
node
=
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
msg_id
,
MQTT_MSG_TYPE_PUBREL
,
(
int
)
mqtt_get_qos
(
temp_msg
->
data
)
);
NODE_DBG
(
"MQTT: Response PUBREL
\r\n
"
);
}
...
...
@@ -385,7 +410,7 @@ READPACKET:
if
(
pending_msg
&&
pending_msg
->
msg_type
==
MQTT_MSG_TYPE_PUBREC
&&
pending_msg
->
msg_id
==
msg_id
){
msg_destroy
(
msg_dequeue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
)));
temp_msg
=
mqtt_msg_pubcomp
(
&
mud
->
mqtt_state
.
mqtt_connection
,
msg_id
);
node
=
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
msg_id
,
MQTT_MSG_TYPE_PUBCOMP
,
(
int
)
mqtt_get_qos
(
temp_msg
->
data
)
);
NODE_DBG
(
"MQTT: Response PUBCOMP
\r\n
"
);
}
...
...
@@ -398,16 +423,14 @@ READPACKET:
break
;
if
(
mud
->
self_ref
==
LUA_NOREF
)
break
;
if
(
mud
->
L
==
NULL
)
break
;
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_puback_ref
);
lua_rawgeti
(
mud
->
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata to callback func in lua
lua_call
(
mud
->
L
,
1
,
0
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
cb_puback_ref
);
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
mud
->
self_ref
);
// pass the userdata to callback func in lua
lua_call
(
L
,
1
,
0
);
}
break
;
case
MQTT_MSG_TYPE_PINGREQ
:
temp_msg
=
mqtt_msg_pingresp
(
&
mud
->
mqtt_state
.
mqtt_connection
);
node
=
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
msg_id
,
MQTT_MSG_TYPE_PINGRESP
,
(
int
)
mqtt_get_qos
(
temp_msg
->
data
)
);
NODE_DBG
(
"MQTT: Response PINGRESP
\r\n
"
);
break
;
...
...
@@ -436,21 +459,7 @@ READPACKET:
break
;
}
if
(
node
&&
(
1
==
msg_size
(
&
(
mud
->
mqtt_state
.
pending_msg_q
)))
&&
mud
->
event_timeout
==
0
){
mud
->
event_timeout
=
MQTT_SEND_TIMEOUT
;
NODE_DBG
(
"Sent: %d
\n
"
,
node
->
msg
.
length
);
#ifdef CLIENT_SSL_ENABLE
if
(
mud
->
secure
)
{
espconn_secure_send
(
pesp_conn
,
node
->
msg
.
data
,
node
->
msg
.
length
);
}
else
#endif
{
espconn_send
(
pesp_conn
,
node
->
msg
.
data
,
node
->
msg
.
length
);
}
}
NODE_DBG
(
"receive, queue size: %d
\n
"
,
msg_size
(
&
(
mud
->
mqtt_state
.
pending_msg_q
)));
mqtt_send_if_possible
(
pesp_conn
);
NODE_DBG
(
"leave mqtt_socket_received.
\n
"
);
return
;
}
...
...
@@ -498,22 +507,7 @@ static void mqtt_socket_sent(void *arg)
try_send
=
0
;
}
if
(
try_send
)
{
msg_queue_t
*
node
=
msg_peek
(
&
(
mud
->
mqtt_state
.
pending_msg_q
));
if
(
node
)
{
mud
->
event_timeout
=
MQTT_SEND_TIMEOUT
;
NODE_DBG
(
"Sent: %d
\n
"
,
node
->
msg
.
length
);
#ifdef CLIENT_SSL_ENABLE
if
(
mud
->
secure
)
{
(
void
)
espconn_secure_send
(
mud
->
pesp_conn
,
node
->
msg
.
data
,
node
->
msg
.
length
);
}
else
#endif
{
(
void
)
espconn_send
(
mud
->
pesp_conn
,
node
->
msg
.
data
,
node
->
msg
.
length
);
}
mud
->
keep_alive_tick
=
0
;
}
mqtt_send_if_possible
(
mud
->
pesp_conn
);
}
NODE_DBG
(
"sent2, queue size: %d
\n
"
,
msg_size
(
&
(
mud
->
mqtt_state
.
pending_msg_q
)));
NODE_DBG
(
"leave mqtt_socket_sent.
\n
"
);
...
...
@@ -623,44 +617,18 @@ void mqtt_socket_timer(void *arg)
}
else
if
(
mud
->
connState
==
MQTT_DATA
){
msg_queue_t
*
pending_msg
=
msg_peek
(
&
(
mud
->
mqtt_state
.
pending_msg_q
));
if
(
pending_msg
){
mud
->
event_timeout
=
MQTT_SEND_TIMEOUT
;
#ifdef CLIENT_SSL_ENABLE
if
(
mud
->
secure
)
{
espconn_secure_send
(
mud
->
pesp_conn
,
pending_msg
->
msg
.
data
,
pending_msg
->
msg
.
length
);
}
else
#endif
{
espconn_send
(
mud
->
pesp_conn
,
pending_msg
->
msg
.
data
,
pending_msg
->
msg
.
length
);
}
mud
->
keep_alive_tick
=
0
;
NODE_DBG
(
"id: %d - qos: %d, length: %d
\n
"
,
pending_msg
->
msg_id
,
pending_msg
->
publish_qos
,
pending_msg
->
msg
.
length
);
mqtt_send_if_possible
(
mud
->
pesp_conn
);
}
else
{
// no queued event.
mud
->
keep_alive_tick
++
;
if
(
mud
->
keep_alive_tick
>
mud
->
mqtt_state
.
connect_info
->
keepalive
){
mud
->
event_timeout
=
MQTT_SEND_TIMEOUT
;
uint8_t
temp_buffer
[
MQTT_BUF_SIZE
];
mqtt_msg_init
(
&
mud
->
mqtt_state
.
mqtt_connection
,
temp_buffer
,
MQTT_BUF_SIZE
);
NODE_DBG
(
"
\r\n
MQTT: Send keepalive packet
\r\n
"
);
mqtt_message_t
*
temp_msg
=
mqtt_msg_pingreq
(
&
mud
->
mqtt_state
.
mqtt_connection
);
msg_queue_t
*
node
=
msg_enqueue
(
&
(
mud
->
mqtt_state
.
pending_msg_q
),
temp_msg
,
0
,
MQTT_MSG_TYPE_PINGREQ
,
(
int
)
mqtt_get_qos
(
temp_msg
->
data
)
);
// only one message in queue, send immediately.
#ifdef CLIENT_SSL_ENABLE
if
(
mud
->
secure
)
{
espconn_secure_send
(
mud
->
pesp_conn
,
temp_msg
->
data
,
temp_msg
->
length
);
}
else
#endif
{
espconn_send
(
mud
->
pesp_conn
,
temp_msg
->
data
,
temp_msg
->
length
);
}
mud
->
keep_alive_tick
=
0
;
mqtt_send_if_possible
(
mud
->
pesp_conn
);
}
}
}
...
...
@@ -690,7 +658,6 @@ static int mqtt_socket_client( lua_State* L )
// create a object
mud
=
(
lmqtt_userdata
*
)
lua_newuserdata
(
L
,
sizeof
(
lmqtt_userdata
));
// pre-initialize it, in case of errors
mud
->
L
=
NULL
;
mud
->
self_ref
=
LUA_NOREF
;
mud
->
cb_connect_ref
=
LUA_NOREF
;
mud
->
cb_connect_fail_ref
=
LUA_NOREF
;
...
...
@@ -716,8 +683,6 @@ static int mqtt_socket_client( lua_State* L )
luaL_getmetatable
(
L
,
"mqtt.socket"
);
lua_setmetatable
(
L
,
-
2
);
mud
->
L
=
L
;
// L for mqtt module.
if
(
lua_isstring
(
L
,
stack
)
)
// deal with the clientid string
{
clientId
=
luaL_checklstring
(
L
,
stack
,
&
idl
);
...
...
@@ -1276,51 +1241,49 @@ static int mqtt_socket_subscribe( lua_State* L ) {
NODE_DBG
(
"subscribe table
\n
"
);
lua_pushnil
(
L
);
/* first key */
uint8_t
temp_buf
[
MQTT_BUF_SIZE
];
uint32_t
temp_pos
=
0
;
int
topic_count
=
0
;
uint8_t
overflow
=
0
;
while
(
lua_next
(
L
,
stack
)
!=
0
)
{
topic
=
luaL_checkstring
(
L
,
-
2
);
qos
=
luaL_checkinteger
(
L
,
-
1
);
temp_msg
=
mqtt_msg_subscribe
(
&
mud
->
mqtt_state
.
mqtt_connection
,
topic
,
qos
,
&
msg_id
);
if
(
topic_count
==
0
)
{
temp_msg
=
mqtt_msg_subscribe_init
(
&
mud
->
mqtt_state
.
mqtt_connection
,
&
msg_id
);
}
temp_msg
=
mqtt_msg_subscribe_topic
(
&
mud
->
mqtt_state
.
mqtt_connection
,
topic
,
qos
);
topic_count
++
;
NODE_DBG
(
"topic: %s - qos: %d, length: %d
\n
"
,
topic
,
qos
,
temp_msg
->
length
);
if
(
temp_pos
+
temp_msg
->
length
>
MQTT_BUF_SIZE
)
{
if
(
temp_msg
->
length
==
0
)
{
lua_pop
(
L
,
1
);
overflow
=
1
;
break
;
// too long message for the outbuffer.
}
c_memcpy
(
temp_buf
+
temp_pos
,
temp_msg
->
data
,
temp_msg
->
length
);
temp_pos
+=
temp_msg
->
length
;
lua_pop
(
L
,
1
);
}
if
(
temp_pos
==
0
){
luaL_error
(
L
,
"invalid data"
);
lua_pushboolean
(
L
,
0
);
return
1
;
if
(
topic_count
==
0
){
return
luaL_error
(
L
,
"no topics found"
);
}
if
(
overflow
!=
0
){
luaL_error
(
L
,
"buffer overflow, can't enqueue all subscriptions"
);
lua_pushboolean
(
L
,
0
);
return
1
;
return
luaL_error
(
L
,
"buffer overflow, can't enqueue all subscriptions"
);
}
temp_msg
=
mqtt_msg_subscribe_fini
(
&
mud
->
mqtt_state
.
mqtt_connection
);
if
(
temp_msg
->
length
==
0
)
{
return
luaL_error
(
L
,
"buffer overflow, can't enqueue all subscriptions"
);
}
c_memcpy
(
temp_buffer
,
temp_buf
,
temp_pos
);
temp_msg
->
data
=
temp_buffer
;
temp_msg
->
length
=
temp_pos
;
stack
++
;
}
else
{
NODE_DBG
(
"subscribe string
\n
"
);
topic
=
luaL_checklstring
(
L
,
stack
,
&
il
);
stack
++
;
if
(
topic
==
NULL
){
luaL_error
(
L
,
"need topic name"
);
lua_pushboolean
(
L
,
0
);
return
1
;
return
luaL_error
(
L
,
"need topic name"
);
}
qos
=
luaL_checkinteger
(
L
,
stack
);
temp_msg
=
mqtt_msg_subscribe
(
&
mud
->
mqtt_state
.
mqtt_connection
,
topic
,
qos
,
&
msg_id
);
...
...
@@ -1342,22 +1305,7 @@ static int mqtt_socket_subscribe( lua_State* L ) {
sint8
espconn_status
=
ESPCONN_IF
;
if
(
node
&&
(
1
==
msg_size
(
&
(
mud
->
mqtt_state
.
pending_msg_q
)))
&&
mud
->
event_timeout
==
0
){
mud
->
event_timeout
=
MQTT_SEND_TIMEOUT
;
NODE_DBG
(
"Sent: %d
\n
"
,
node
->
msg
.
length
);
#ifdef CLIENT_SSL_ENABLE
if
(
mud
->
secure
)
{
espconn_status
=
espconn_secure_send
(
mud
->
pesp_conn
,
node
->
msg
.
data
,
node
->
msg
.
length
);
}
else
#endif
{
espconn_status
=
espconn_send
(
mud
->
pesp_conn
,
node
->
msg
.
data
,
node
->
msg
.
length
);
}
mud
->
keep_alive_tick
=
0
;
}
espconn_status
=
mqtt_send_if_possible
(
mud
->
pesp_conn
);
if
(
!
node
||
espconn_status
!=
ESPCONN_OK
){
lua_pushboolean
(
L
,
0
);
...
...
@@ -1395,17 +1343,13 @@ static int mqtt_socket_publish( lua_State* L )
}
if
(
!
mud
->
connected
){
luaL_error
(
L
,
"not connected"
);
lua_pushboolean
(
L
,
0
);
return
1
;
return
luaL_error
(
L
,
"not connected"
);
}
const
char
*
topic
=
luaL_checklstring
(
L
,
stack
,
&
l
);
stack
++
;
if
(
topic
==
NULL
){
luaL_error
(
L
,
"need topic"
);
lua_pushboolean
(
L
,
0
);
return
1
;
return
luaL_error
(
L
,
"need topic"
);
}
const
char
*
payload
=
luaL_checklstring
(
L
,
stack
,
&
l
);
...
...
@@ -1434,22 +1378,7 @@ static int mqtt_socket_publish( lua_State* L )
sint8
espconn_status
=
ESPCONN_OK
;
if
(
node
&&
(
1
==
msg_size
(
&
(
mud
->
mqtt_state
.
pending_msg_q
)))
&&
mud
->
event_timeout
==
0
){
mud
->
event_timeout
=
MQTT_SEND_TIMEOUT
;
NODE_DBG
(
"Sent: %d
\n
"
,
node
->
msg
.
length
);
#ifdef CLIENT_SSL_ENABLE
if
(
mud
->
secure
)
{
espconn_status
=
espconn_secure_send
(
mud
->
pesp_conn
,
node
->
msg
.
data
,
node
->
msg
.
length
);
}
else
#endif
{
espconn_status
=
espconn_send
(
mud
->
pesp_conn
,
node
->
msg
.
data
,
node
->
msg
.
length
);
}
mud
->
keep_alive_tick
=
0
;
}
espconn_status
=
mqtt_send_if_possible
(
mud
->
pesp_conn
);
if
(
!
node
||
espconn_status
!=
ESPCONN_OK
){
lua_pushboolean
(
L
,
0
);
...
...
app/modules/wifi.c
View file @
92eb21cd
...
...
@@ -991,117 +991,65 @@ static int wifi_station_status( lua_State* L )
return
1
;
}
/**
* wifi.sta.eventMonStop()
* Description:
* Stop wifi station event monitor
* Syntax:
* wifi.sta.eventMonStop()
* wifi.sta.eventMonStop("unreg all")
* Parameters:
* "unreg all": unregister all previously registered functions
* Returns:
* Nothing.
*
* Example:
--stop wifi event monitor
wifi.sta.eventMonStop()
--stop wifi event monitor and unregister all callbacks
wifi.sta.eventMonStop("unreg all")
*/
static
void
wifi_station_event_mon_stop
(
lua_State
*
L
)
// wifi.sta.eventMonStop()
void
wifi_station_event_mon_stop
(
lua_State
*
L
)
{
os_timer_disarm
(
&
wifi_sta_status_timer
);
if
(
lua_isstring
(
L
,
1
))
{
if
(
c_strcmp
(
luaL_checkstring
(
L
,
1
),
"unreg all"
)
==
0
)
int
i
;
for
(
i
=
0
;
i
<
6
;
i
++
)
{
int
i
;
for
(
i
=
0
;
i
<
6
;
i
++
)
if
(
wifi_status_cb_ref
[
i
]
!=
LUA_NOREF
)
{
if
(
wifi_status_cb_ref
[
i
]
!=
LUA_NOREF
)
{
luaL_unref
(
L
,
LUA_REGISTRYINDEX
,
wifi_status_cb_ref
[
i
]);
wifi_status_cb_ref
[
i
]
=
LUA_NOREF
;
}
luaL_unref
(
L
,
LUA_REGISTRYINDEX
,
wifi_status_cb_ref
[
i
]);
wifi_status_cb_ref
[
i
]
=
LUA_NOREF
;
}
}
}
return
;
}
static
void
wifi_status_cb
(
int
arg
)
{
if
(
wifi_get_opmode
()
==
2
)
lua_State
*
L
=
lua_getstate
();
if
(
wifi_get_opmode
()
==
SOFTAP_MODE
)
{
os_timer_disarm
(
&
wifi_sta_status_timer
);
return
;
os_timer_disarm
(
&
wifi_sta_status_timer
);
return
;
}
int
wifi_status
=
wifi_station_get_connect_status
();
if
(
wifi_status
!=
prev_wifi_status
)
int
wifi_status
=
wifi_station_get_connect_status
();
if
(
wifi_status
!=
prev_wifi_status
)
{
if
(
wifi_status_cb_ref
[
wifi_status
]
!=
LUA_NOREF
)
{
lua_rawgeti
(
gL
,
LUA_REGISTRYINDEX
,
wifi_status_cb_ref
[
wifi_status
]);
lua_call
(
gL
,
0
,
0
);
}
if
(
wifi_status_cb_ref
[
wifi_status
]
!=
LUA_NOREF
)
{
lua_rawgeti
(
L
,
LUA_REGISTRYINDEX
,
wifi_status_cb_ref
[
wifi_status
]);
lua_pushnumber
(
L
,
prev_wifi_status
);
lua_call
(
L
,
1
,
0
);
}
}
prev_wifi_status
=
wifi_status
;
prev_wifi_status
=
wifi_status
;
}
/**
* wifi.sta.eventMonReg()
* Description:
* Register callback for wifi station status event
* Syntax:
* wifi.sta.eventMonReg(wifi_status, function)
* wifi.sta.eventMonReg(wifi.status, "unreg") //unregister callback
* Parameters:
* wifi_status: wifi status you would like to set callback for
* Valid wifi states:
* wifi.STA_IDLE
* wifi.STA_CONNECTING
* wifi.STA_WRONGPWD
* wifi.STA_APNOTFOUND
* wifi.STA_FAIL
* wifi.STA_GOTIP
* function: function to perform
* "unreg": unregister previously registered function
* Returns:
* Nothing.
*
* Example:
--register callback
wifi.sta.eventMonReg(0, function() print("STATION_IDLE") end)
wifi.sta.eventMonReg(1, function() print("STATION_CONNECTING") end)
wifi.sta.eventMonReg(2, function() print("STATION_WRONG_PASSWORD") end)
wifi.sta.eventMonReg(3, function() print("STATION_NO_AP_FOUND") end)
wifi.sta.eventMonReg(4, function() print("STATION_CONNECT_FAIL") end)
wifi.sta.eventMonReg(5, function() print("STATION_GOT_IP") end)
--unregister callback
wifi.sta.eventMonReg(0, "unreg")
*/
static
int
wifi_station_event_mon_reg
(
lua_State
*
L
)
// wifi.sta.eventMonReg()
int
wifi_station_event_mon_reg
(
lua_State
*
L
)
{
gL
=
L
;
uint8
id
=
luaL_checknumber
(
L
,
1
);
if
(
!
(
id
>=
0
&&
id
<=
5
))
uint8
id
=
(
uint8
)
luaL_checknumber
(
L
,
1
);
if
((
id
>
5
))
// verify user specified a valid wifi status
{
return
luaL_error
(
L
,
"valid wifi status:0-5"
);
return
luaL_error
(
L
,
"valid wifi status:0-5"
);
}
if
(
lua_type
(
L
,
2
)
==
LUA_TFUNCTION
||
lua_type
(
L
,
2
)
==
LUA_TLIGHTFUNCTION
)
if
(
lua_type
(
L
,
2
)
==
LUA_TFUNCTION
||
lua_type
(
L
,
2
)
==
LUA_TLIGHTFUNCTION
)
//check if 2nd item on stack is a function
{
lua_pushvalue
(
L
,
2
);
//
copy argument (func) to the
top of stack
lua_pushvalue
(
L
,
2
);
//
push function to
top of stack
if
(
wifi_status_cb_ref
[
id
]
!=
LUA_NOREF
)
{
luaL_unref
(
L
,
LUA_REGISTRYINDEX
,
wifi_status_cb_ref
[
id
]);
}
wifi_status_cb_ref
[
id
]
=
luaL_ref
(
L
,
LUA_REGISTRYINDEX
);
}
else
if
(
c_strcmp
(
luaL_checkstring
(
L
,
2
),
"unreg"
)
==
0
)
else
{
if
(
wifi_status_cb_ref
[
id
]
!=
LUA_NOREF
)
{
...
...
@@ -1113,25 +1061,7 @@ static int wifi_station_event_mon_reg(lua_State* L)
}
/**
* wifi.sta.eventMonStart()
* Description:
* Start wifi station event monitor
* Syntax:
* wifi.sta.eventMonStart()
* wifi.sta.eventMonStart(mS)
* Parameters:
* mS:interval between checks in milliseconds. defaults to 150 mS if not provided
* Returns:
* Nothing.
*
* Example:
--start wifi event monitor with default interval
wifi.sta.eventMonStart()
--start wifi event monitor with 100 mS interval
wifi.sta.eventMonStart(100)
*/
//wifi.sta.eventMonStart()
static
int
wifi_station_event_mon_start
(
lua_State
*
L
)
{
if
(
wifi_get_opmode
()
==
SOFTAP_MODE
)
...
...
app/mqtt/mqtt_msg.c
View file @
92eb21cd
...
...
@@ -402,14 +402,19 @@ mqtt_message_t* mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message
return
fini_message
(
connection
,
MQTT_MSG_TYPE_PUBCOMP
,
0
,
0
,
0
);
}
mqtt_message_t
*
mqtt_msg_subscribe
(
mqtt_connection_t
*
connection
,
const
char
*
topic
,
int
qos
,
uint16_t
*
message_id
)
mqtt_message_t
*
mqtt_msg_subscribe
_init
(
mqtt_connection_t
*
connection
,
uint16_t
*
message_id
)
{
init_message
(
connection
);
if
(
topic
==
NULL
||
topic
[
0
]
==
'\0'
)
if
(
(
*
message_id
=
append_message_id
(
connection
,
0
))
==
0
)
return
fail_message
(
connection
);
if
((
*
message_id
=
append_message_id
(
connection
,
0
))
==
0
)
return
&
connection
->
message
;
}
mqtt_message_t
*
mqtt_msg_subscribe_topic
(
mqtt_connection_t
*
connection
,
const
char
*
topic
,
int
qos
)
{
if
(
topic
==
NULL
||
topic
[
0
]
==
'\0'
)
return
fail_message
(
connection
);
if
(
append_string
(
connection
,
topic
,
c_strlen
(
topic
))
<
0
)
...
...
@@ -419,9 +424,29 @@ mqtt_message_t* mqtt_msg_subscribe(mqtt_connection_t* connection, const char* to
return
fail_message
(
connection
);
connection
->
buffer
[
connection
->
message
.
length
++
]
=
qos
;
return
&
connection
->
message
;
}
mqtt_message_t
*
mqtt_msg_subscribe_fini
(
mqtt_connection_t
*
connection
)
{
return
fini_message
(
connection
,
MQTT_MSG_TYPE_SUBSCRIBE
,
0
,
1
,
0
);
}
mqtt_message_t
*
mqtt_msg_subscribe
(
mqtt_connection_t
*
connection
,
const
char
*
topic
,
int
qos
,
uint16_t
*
message_id
)
{
mqtt_message_t
*
result
;
result
=
mqtt_msg_subscribe_init
(
connection
,
message_id
);
if
(
result
->
length
!=
0
)
{
result
=
mqtt_msg_subscribe_topic
(
connection
,
topic
,
qos
);
}
if
(
result
->
length
!=
0
)
{
result
=
mqtt_msg_subscribe_fini
(
connection
);
}
return
result
;
}
mqtt_message_t
*
mqtt_msg_unsubscribe
(
mqtt_connection_t
*
connection
,
const
char
*
topic
,
uint16_t
*
message_id
)
{
init_message
(
connection
);
...
...
app/mqtt/mqtt_msg.h
View file @
92eb21cd
...
...
@@ -136,6 +136,10 @@ mqtt_message_t* mqtt_msg_pingreq(mqtt_connection_t* connection);
mqtt_message_t
*
mqtt_msg_pingresp
(
mqtt_connection_t
*
connection
);
mqtt_message_t
*
mqtt_msg_disconnect
(
mqtt_connection_t
*
connection
);
mqtt_message_t
*
mqtt_msg_subscribe_init
(
mqtt_connection_t
*
connection
,
uint16_t
*
message_id
);
mqtt_message_t
*
mqtt_msg_subscribe_topic
(
mqtt_connection_t
*
connection
,
const
char
*
topic
,
int
qos
);
mqtt_message_t
*
mqtt_msg_subscribe_fini
(
mqtt_connection_t
*
connection
);
#ifdef __cplusplus
}
...
...
app/platform/platform.c
View file @
92eb21cd
...
...
@@ -60,7 +60,7 @@ uint8_t platform_key_led( uint8_t level){
/*
* Set GPIO mode to output. Optionally in RAM helper because interrupts are dsabled
*/
static
void
NO_INTR_CODE
set_gpio_no_interrupt
(
uint8
pin
)
{
static
void
NO_INTR_CODE
set_gpio_no_interrupt
(
uint8
pin
,
uint8_t
push_pull
)
{
unsigned
pnum
=
pin_num
[
pin
];
ETS_GPIO_INTR_DISABLE
();
#ifdef GPIO_INTERRUPT_ENABLE
...
...
@@ -71,9 +71,17 @@ static void NO_INTR_CODE set_gpio_no_interrupt(uint8 pin) {
gpio_pin_intr_state_set
(
GPIO_ID_PIN
(
pnum
),
GPIO_PIN_INTR_DISABLE
);
//clear interrupt status
GPIO_REG_WRITE
(
GPIO_STATUS_W1TC_ADDRESS
,
BIT
(
pnum
));
GPIO_REG_WRITE
(
GPIO_PIN_ADDR
(
GPIO_ID_PIN
(
pnum
)),
GPIO_REG_READ
(
GPIO_PIN_ADDR
(
GPIO_ID_PIN
(
pnum
)))
&
(
~
GPIO_PIN_PAD_DRIVER_SET
(
GPIO_PAD_DRIVER_ENABLE
)));
//disable open drain;
// configure push-pull vs open-drain
if
(
push_pull
)
{
GPIO_REG_WRITE
(
GPIO_PIN_ADDR
(
GPIO_ID_PIN
(
pnum
)),
GPIO_REG_READ
(
GPIO_PIN_ADDR
(
GPIO_ID_PIN
(
pnum
)))
&
(
~
GPIO_PIN_PAD_DRIVER_SET
(
GPIO_PAD_DRIVER_ENABLE
)));
//disable open drain;
}
else
{
GPIO_REG_WRITE
(
GPIO_PIN_ADDR
(
GPIO_ID_PIN
(
pnum
)),
GPIO_REG_READ
(
GPIO_PIN_ADDR
(
GPIO_ID_PIN
(
pnum
)))
|
GPIO_PIN_PAD_DRIVER_SET
(
GPIO_PAD_DRIVER_ENABLE
));
//enable open drain;
}
ETS_GPIO_INTR_ENABLE
();
}
...
...
@@ -124,7 +132,10 @@ int platform_gpio_mode( unsigned pin, unsigned mode, unsigned pull )
GPIO_DIS_OUTPUT
(
pin_num
[
pin
]);
/* run on */
case
PLATFORM_GPIO_OUTPUT
:
set_gpio_no_interrupt
(
pin
);
set_gpio_no_interrupt
(
pin
,
TRUE
);
break
;
case
PLATFORM_GPIO_OPENDRAIN
:
set_gpio_no_interrupt
(
pin
,
FALSE
);
break
;
#ifdef GPIO_INTERRUPT_ENABLE
...
...
app/platform/platform.h
View file @
92eb21cd
...
...
@@ -32,6 +32,7 @@ uint8_t platform_key_led( uint8_t level);
#define PLATFORM_GPIO_INT 2
#define PLATFORM_GPIO_OUTPUT 1
#define PLATFORM_GPIO_OPENDRAIN 3
#define PLATFORM_GPIO_INPUT 0
#define PLATFORM_GPIO_HIGH 1
...
...
docs/en/modules/gpio.md
View file @
92eb21cd
...
...
@@ -20,20 +20,20 @@ If not using a NodeMCU dev kit, please refer to the below GPIO pin maps for the
| 5 | GPIO14 | 12 | GPIO10 |
| 6 | GPIO12 | | |
** [*] D0(GPIO16) can only be used as gpio read/write. No
interrupt support. No
pwm/i2c/ow
support
. **
** [*] D0(GPIO16) can only be used as gpio read/write. No
support for open-drain/interrupt/
pwm/i2c/ow. **
## gpio.mode()
Initialize pin to GPIO mode, set the pin in/out direction, and optional internal pullup.
Initialize pin to GPIO mode, set the pin in/out direction, and optional internal
weak
pull
-
up.
#### Syntax
`gpio.mode(pin, mode [, pullup])`
#### Parameters
-
`pin`
pin to configure, IO index
-
`mode`
one of gpio.OUTPUT
or
gpio.INPUT, or gpio.INT(interrupt mode)
-
`pullup`
gpio.PULLUP
or gpio.FLOAT
; default is gpio.FLOAT
-
`mode`
one of gpio.OUTPUT
, gpio.OPENDRAIN,
gpio.INPUT, or gpio.INT
(interrupt mode)
-
`pullup`
gpio.PULLUP
enables the weak pull-up resistor
; default is gpio.FLOAT
#### Returns
`nil`
...
...
docs/en/modules/wifi.md
View file @
92eb21cd
...
...
@@ -326,19 +326,18 @@ none
Registers callbacks for WiFi station status events.
#### Syntax
-
`wifi.sta.eventMonReg(wifi_status, function([previous_state]))`
-
`wifi.sta.eventMonReg(wifi.status, "unreg")`
-
`wifi.sta.eventMonReg(wifi_status[, function([previous_state])])`
#### Parameters
-
`wifi_status`
WiFi status you would like to set callback for
, one of
:
-
`wifi_status`
WiFi status you would like to set
a
callback for:
-
`wifi.STA_IDLE`
-
`wifi.STA_CONNECTING`
-
`wifi.STA_WRONGPWD`
-
`wifi.STA_APNOTFOUND`
-
`wifi.STA_FAIL`
-
`wifi.STA_GOTIP`
-
`function`
function to perform when event occurs
-
`"unreg"`
unregister previously
register
ed
callback
-
`function`
callback
function to perform when event occurs
-
Note: leaving field blank un
register
s
callback
.
-
`previous_state`
previous wifi_state(0 - 5)
#### Returns
...
...
@@ -364,7 +363,7 @@ wifi.sta.eventMonReg(wifi.STA_CONNECTING, function(previous_State)
end
)
--unregister callback
wifi
.
sta
.
eventMonReg
(
wifi
.
STA_IDLE
,
"unreg"
)
wifi
.
sta
.
eventMonReg
(
wifi
.
STA_IDLE
)
```
#### See also
-
[
`wifi.sta.eventMonStart()`
](
#wifistaeventmonstart
)
...
...
@@ -395,15 +394,16 @@ wifi.sta.eventMonStart(100)
#### See also
-
[
`wifi.sta.eventMonReg()`
](
#wifistaeventmonreg
)
-
[
`wifi.sta.eventMonStop()`
](
#wifistaeventmonstop
)
-
## wifi.sta.eventMonStop()
Stops WiFi station event monitor.
#### Syntax
`wifi.sta.eventMonStop([
"
unreg
all
"
])`
`wifi.sta.eventMonStop([unreg
ister_
all])`
#### Parameters
`"unreg all"`
unregister all previously registered functions
-
`unregister_all`
enter 1 to unregister all previously registered functions.
-
Note: leave blank to leave callbacks registered
#### Returns
`nil`
...
...
@@ -414,7 +414,7 @@ Stops WiFi station event monitor.
wifi
.
sta
.
eventMonStop
()
--stop WiFi event monitor and unregister all callbacks
wifi
.
sta
.
eventMonStop
(
"unreg all"
)
wifi
.
sta
.
eventMonStop
(
1
)
```
#### See also
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment