Unverified Commit 67027c0d authored by Marcel Stör's avatar Marcel Stör Committed by GitHub
Browse files

Merge pull request #2340 from nodemcu/dev

2.2 master snap
parents 5073c199 18f33f5f
...@@ -340,6 +340,9 @@ if not mytimer:stop() then print("timer not stopped, not registered?") end ...@@ -340,6 +340,9 @@ if not mytimer:stop() then print("timer not stopped, not registered?") end
Suspend an armed timer. Suspend an armed timer.
!!! attention
This is disabled by default. Modify `ENABLE_TIMER_SUSPEND` in `app/include/user_config.h` to enable it.
* Timers can be suspended at any time after they are armed. * Timers can be suspended at any time after they are armed.
* If a timer is rearmed with `tmr.start` or `tmr.alarm` any matching suspended timers will be discarded. * If a timer is rearmed with `tmr.start` or `tmr.alarm` any matching suspended timers will be discarded.
...@@ -371,11 +374,13 @@ tmr.suspend(mytimer) ...@@ -371,11 +374,13 @@ tmr.suspend(mytimer)
Suspend all currently armed timers. Suspend all currently armed timers.
!!! attention
This is disabled by default. Modify `ENABLE_TIMER_SUSPEND` in `app/include/user_config.h` to enable it.
!!! Warning !!! Warning
This function suspends ALL active timers, including any active timers started by the NodeMCU subsystem or other modules. this may cause parts of your program to stop functioning properly. This function suspends ALL active timers, including any active timers started by the NodeMCU subsystem or other modules. this may cause parts of your program to stop functioning properly.
USE THIS FUNCTION AT YOUR OWN RISK! USE THIS FUNCTION AT YOUR OWN RISK!
#### Syntax #### Syntax
`tmr.suspend_all()` `tmr.suspend_all()`
......
...@@ -62,7 +62,7 @@ ws = websocket.createClient() ...@@ -62,7 +62,7 @@ ws = websocket.createClient()
ws:close() ws:close()
ws:close() -- nothing will happen ws:close() -- nothing will happen
ws = nil -- fully dispose the client as lua will now gc it ws = nil -- fully dispose the client as Lua will now gc it
``` ```
...@@ -137,7 +137,7 @@ ws:on("receive", function(_, msg, opcode) ...@@ -137,7 +137,7 @@ ws:on("receive", function(_, msg, opcode)
end) end)
ws:on("close", function(_, status) ws:on("close", function(_, status)
print('connection closed', status) print('connection closed', status)
ws = nil -- required to lua gc the websocket client ws = nil -- required to Lua gc the websocket client
end) end)
ws:connect('ws://echo.websocket.org') ws:connect('ws://echo.websocket.org')
......
...@@ -6,14 +6,44 @@ ...@@ -6,14 +6,44 @@
!!! important !!! important
The WiFi subsystem is maintained by background tasks that must run periodically. Any function or task that takes longer than 15ms (milliseconds) may cause the WiFi subsystem to crash. To avoid these potential crashes, it is advised that the WiFi subsystem be suspended with [wifi.suspend()](#wifisuspend) prior to the execution of any tasks or functions that exceed this 15ms guideline. The WiFi subsystem is maintained by background tasks that must run periodically. Any function or task that takes longer than 15ms (milliseconds) may cause the WiFi subsystem to crash. To avoid these potential crashes, it is advised that the WiFi subsystem be suspended with [wifi.suspend()](#wifisuspend) prior to the execution of any tasks or functions that exceed this 15ms guideline.
### WiFi modes
Courtesy: content for this chapter is borrowed/inspired by the [Arduino ESP8266 WiFi documentation](https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/readme.html).
Devices that connect to WiFi network are called stations (STA). Connection to Wi-Fi is provided by an access point (AP), that acts as a hub for one or more stations. The access point on the other end is connected to a wired network. An access point is usually integrated with a router to provide access from Wi-Fi network to the internet. Each access point is recognized by a SSID (**S**ervice **S**et **ID**entifier), that essentially is the name of network you select when connecting a device (station) to the WiFi.
Each ESP8266 module can operate as a station, so we can connect it to the WiFi network. It can also operate as a soft access point (soft-AP), to establish its own WiFi network. Therefore, we can connect other stations to such modules. Third, ESP8266 is also able to operate both in station and soft access point mode *at the same time*. This offers the possibility of building e.g. [mesh networks](https://en.wikipedia.org/wiki/Mesh_networking).
#### Station
Station (STA) mode is used to get the ESP8266 connected to a WiFi network established by an access point.
![ESP8266 operating in station mode](../../img/WiFi-station-mode.png)
#### Soft Access Point
An access point (AP) is a device that provides access to Wi-Fi network to other devices (stations) and connects them further to a wired network. ESP8266 can provide similar functionality except it does not have interface to a wired network. Such mode of operation is called soft access point (soft-AP). The maximum number of stations connected to the soft-AP is five.
![ESP8266 operating in Soft Access Point mode](../../img/WiFi-softap-mode.png)
The soft-AP mode is often used and an intermediate step before connecting ESP to a WiFi in a station mode. This is when SSID and password to such network is not known upfront. The module first boots in soft-AP mode, so we can connect to it using a laptop or a mobile phone. Then we are able to provide credentials to the target network. Once done ESP is switched to the station mode and can connect to the target WiFi.
Such functionality is provided by the [NodeMCU enduser setup module](../modules/enduser-setup.md).
#### Station + Soft Access Point
Another handy application of soft-AP mode is to set up [mesh networks](https://en.wikipedia.org/wiki/Mesh_networking). ESP can operate in both soft-AP and Station mode so it can act as a node of a mesh network.
![ESP8266 operating in station AP mode](../../img/WiFi-stationap-mode.png)
### Function reference
The NodeMCU WiFi control is spread across several tables: The NodeMCU WiFi control is spread across several tables:
- `wifi` for overall WiFi configuration - [`wifi`](#wifigetchannel) for overall WiFi configuration
- [`wifi.sta`](#wifista-module) for station mode functions - [`wifi.sta`](#wifista-module) for station mode functions
- [`wifi.ap`](#wifiap-module) for wireless access point (WAP or simply AP) functions - [`wifi.ap`](#wifiap-module) for wireless access point (WAP or simply AP) functions
- [`wifi.ap.dhcp`](#wifiapdhcp-module) for DHCP server control - [`wifi.ap.dhcp`](#wifiapdhcp-module) for DHCP server control
- [`wifi.eventmon`](#wifieventmon-module) for wifi event monitor - [`wifi.eventmon`](#wifieventmon-module) for wifi event monitor
- [`wifi.monitor`](wifi_monitor.md#wifimonitor-module) for wifi monitor mode
## wifi.getchannel() ## wifi.getchannel()
...@@ -28,6 +58,36 @@ Gets the current WiFi channel. ...@@ -28,6 +58,36 @@ Gets the current WiFi channel.
#### Returns #### Returns
current WiFi channel current WiFi channel
## wifi.getcountry()
Get the current country info.
#### Syntax
`wifi.getcountry()`
#### Parameters
`nil`
#### Returns
- `country_info` this table contains the current country info configuration
- `country` Country code, 2 character string.
- `start_ch` Starting channel.
- `end_ch` Ending channel.
- `policy` The policy parameter determines which country info configuration to use, country info given to station by AP or local configuration.
- `0` Country policy is auto, NodeMCU will use the country info provided by AP that the station is connected to.
- `1` Country policy is manual, NodeMCU will use locally configured country info.
#### Example
```lua
for k, v in pairs(wifi.getcountry()) do
print(k, v)
end
```
#### See also
[`wifi.setcountry()`](#wifisetcountry)
## wifi.getdefaultmode() ## wifi.getdefaultmode()
Gets default WiFi operation mode. Gets default WiFi operation mode.
...@@ -102,6 +162,9 @@ Configures whether or not WiFi automatically goes to sleep in NULL_MODE. Enabled ...@@ -102,6 +162,9 @@ Configures whether or not WiFi automatically goes to sleep in NULL_MODE. Enabled
Wake up WiFi from suspended state or cancel pending wifi suspension. Wake up WiFi from suspended state or cancel pending wifi suspension.
!!! attention
This is disabled by default. Modify `PMSLEEP_ENABLE` in `app/include/user_config.h` to enable it.
!!! note !!! note
Wifi resume occurs asynchronously, this means that the resume request will only be processed when control of the processor is passed back to the SDK (after MyResumeFunction() has completed). The resume callback also executes asynchronously and will only execute after wifi has resumed normal operation. Wifi resume occurs asynchronously, this means that the resume request will only be processed when control of the processor is passed back to the SDK (after MyResumeFunction() has completed). The resume callback also executes asynchronously and will only execute after wifi has resumed normal operation.
...@@ -132,6 +195,48 @@ wifi.resume(function() print("WiFi resume") end) ...@@ -132,6 +195,48 @@ wifi.resume(function() print("WiFi resume") end)
- [`node.sleep()`](node.md#nodesleep) - [`node.sleep()`](node.md#nodesleep)
- [`node.dsleep()`](node.md#nodedsleep) - [`node.dsleep()`](node.md#nodedsleep)
## wifi.setcountry()
Set the current country info.
#### Syntax
`wifi.setcountry(country_info)`
#### Parameters
- `country_info` This table contains the country info configuration. (If a blank table is passed to this function, default values will be configured.)
- `country` Country code, 2 character string containing the country code (a list of country codes can be found [here](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)). (Default:"CN")
- `start_ch` Starting channel (range:1-14). (Default:1)
- `end_ch` Ending channel, must not be less than starting channel (range:1-14). (Default:13)
- `policy` The policy parameter determines which country info configuration to use, country info given to station by AP or local configuration. (default:`wifi.COUNTRY_AUTO`)
- `wifi.COUNTRY_AUTO` Country policy is auto, NodeMCU will use the country info provided by AP that the station is connected to.
- while in stationAP mode, beacon/probe respose will reflect the country info of the AP that the station is connected to.
- `wifi.COUNTRY_MANUAL` Country policy is manual, NodeMCU will use locally configured country info.
#### Returns
`true` If configuration was sucessful.
#### Example
```lua
do
country_info={}
country_info.country="US"
country_info.start_ch=1
country_info.end_ch=13
country_info.policy=wifi.COUNTRY_AUTO;
wifi.setcountry(country_info)
end
--compact version
wifi.setcountry({country="US", start_ch=1, end_ch=13, policy=wifi.COUNTRY_AUTO})
--Set defaults
wifi.setcountry({})
```
#### See also
[`wifi.getcountry()`](#wifigetcountry)
## wifi.setmode() ## wifi.setmode()
Configures the WiFi mode to use. NodeMCU can run in one of four WiFi modes: Configures the WiFi mode to use. NodeMCU can run in one of four WiFi modes:
...@@ -208,6 +313,25 @@ physical mode after setup ...@@ -208,6 +313,25 @@ physical mode after setup
#### See also #### See also
[`wifi.getphymode()`](#wifigetphymode) [`wifi.getphymode()`](#wifigetphymode)
## wifi.setmaxtxpower()
Sets WiFi maximum TX power. This setting is not persisted across power cycles, and the Espressif SDK documentation does not specify if the setting persists after deep sleep. The default value used is read from byte 34 of the ESP8266 init data, and its value is hence defined by the manufacturer.
The default value, 82, corresponds to maximum TX power. Lowering this setting could reduce power consumption on battery backed devices.
#### Syntax
`wifi.setmaxtxpower(max_tpw)`
#### Parameters
`max_tpw` maximum value of RF Tx Power, unit: 0.25 dBm, range [0, 82].
#### Returns
`nil`
### See also
[`flash SDK init data`](../flash.md#sdk-init-data)
## wifi.startsmart() ## wifi.startsmart()
Starts to auto configuration, if success set up SSID and password automatically. Starts to auto configuration, if success set up SSID and password automatically.
...@@ -263,6 +387,9 @@ none ...@@ -263,6 +387,9 @@ none
## wifi.suspend() ## wifi.suspend()
Suspend Wifi to reduce current consumption. Suspend Wifi to reduce current consumption.
!!! attention
This is disabled by default. Modify `PMSLEEP_ENABLE` in `app/include/user_config.h` to enable it.
!!! note !!! note
Wifi suspension occurs asynchronously, this means that the suspend request will only be processed when control of the processor is passed back to the SDK (after MySuspendFunction() has completed). The suspend callback also executes asynchronously and will only execute after wifi has been successfully been suspended. Wifi suspension occurs asynchronously, this means that the suspend request will only be processed when control of the processor is passed back to the SDK (after MySuspendFunction() has completed). The suspend callback also executes asynchronously and will only execute after wifi has been successfully been suspended.
...@@ -423,7 +550,7 @@ Sets the WiFi station configuration. ...@@ -423,7 +550,7 @@ Sets the WiFi station configuration.
- Items returned in table : - Items returned in table :
- `SSID`: SSID of access point. (format: string) - `SSID`: SSID of access point. (format: string)
- `BSSID`: BSSID of access point. (format: string) - `BSSID`: BSSID of access point. (format: string)
- `REASON`: See [wifi.eventmon.reason](#wifieventmonreason) below. (format: number) - `reason`: See [wifi.eventmon.reason](#wifieventmonreason) below. (format: number)
- `authmode_change_cb`: Callback to execute when the access point has changed authorization mode. (Optional) - `authmode_change_cb`: Callback to execute when the access point has changed authorization mode. (Optional)
- Items returned in table : - Items returned in table :
- `old_auth_mode`: Old wifi authorization mode. (format: number) - `old_auth_mode`: Old wifi authorization mode. (format: number)
...@@ -447,6 +574,7 @@ Sets the WiFi station configuration. ...@@ -447,6 +574,7 @@ Sets the WiFi station configuration.
station_cfg={} station_cfg={}
station_cfg.ssid="NODE-AABBCC" station_cfg.ssid="NODE-AABBCC"
station_cfg.pwd="password" station_cfg.pwd="password"
station_cfg.save=false
wifi.sta.config(station_cfg) wifi.sta.config(station_cfg)
--connect to Access Point (DO save config to flash) --connect to Access Point (DO save config to flash)
...@@ -456,14 +584,14 @@ station_cfg.pwd="password" ...@@ -456,14 +584,14 @@ station_cfg.pwd="password"
station_cfg.save=true station_cfg.save=true
wifi.sta.config(station_cfg) wifi.sta.config(station_cfg)
--connect to Access Point with specific MAC address --connect to Access Point with specific MAC address (DO save config to flash)
station_cfg={} station_cfg={}
station_cfg.ssid="NODE-AABBCC" station_cfg.ssid="NODE-AABBCC"
station_cfg.pwd="password" station_cfg.pwd="password"
station_cfg.bssid="AA:BB:CC:DD:EE:FF" station_cfg.bssid="AA:BB:CC:DD:EE:FF"
wifi.sta.config(station_cfg) wifi.sta.config(station_cfg)
--configure station but don't connect to Access point --configure station but don't connect to Access point (DO save config to flash)
station_cfg={} station_cfg={}
station_cfg.ssid="NODE-AABBCC" station_cfg.ssid="NODE-AABBCC"
station_cfg.pwd="password" station_cfg.pwd="password"
...@@ -514,7 +642,7 @@ Disconnects from AP in station mode. ...@@ -514,7 +642,7 @@ Disconnects from AP in station mode.
- Items returned in table : - Items returned in table :
- `SSID`: SSID of access point. (format: string) - `SSID`: SSID of access point. (format: string)
- `BSSID`: BSSID of access point. (format: string) - `BSSID`: BSSID of access point. (format: string)
- `REASON`: See [wifi.eventmon.reason](#wifieventmonreason) below. (format: number) - `reason`: See [wifi.eventmon.reason](#wifieventmonreason) below. (format: number)
#### Returns #### Returns
`nil` `nil`
...@@ -937,7 +1065,9 @@ Sets station hostname. ...@@ -937,7 +1065,9 @@ Sets station hostname.
`hostname` must only contain letters, numbers and hyphens('-') and be 32 characters or less with first and last character being alphanumeric `hostname` must only contain letters, numbers and hyphens('-') and be 32 characters or less with first and last character being alphanumeric
#### Returns #### Returns
`nil` - `true` Success
- `false` Failure
#### Example #### Example
```lua ```lua
...@@ -1583,3 +1713,4 @@ Table containing disconnect reasons. ...@@ -1583,3 +1713,4 @@ Table containing disconnect reasons.
|wifi.eventmon.reason.AUTH_FAIL | 202 | |wifi.eventmon.reason.AUTH_FAIL | 202 |
|wifi.eventmon.reason.ASSOC_FAIL | 203 | |wifi.eventmon.reason.ASSOC_FAIL | 203 |
|wifi.eventmon.reason.HANDSHAKE_TIMEOUT | 204 | |wifi.eventmon.reason.HANDSHAKE_TIMEOUT | 204 |
# WiFi.monitor Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2017-12-20 | [Philip Gladstone](https://github.com/pjsg) | [Philip Gladstone](https://github.com/pjsg) | [wifi_monitor.c](../../../app/modules/wifi_monitor.c)|
This is an optional module that is only included if `LUA_USE_MODULES_WIFI_MONITOR` is defined in the `user_modules.h` file. This module
provides access to the monitor mode features of the ESP8266 chipset. In particular, it provides access to received WiFi management frames.
This module is not for casual use -- it requires an understanding of IEEE802.11 management protocols.
## wifi.monitor.start()
This registers a callback function to be called whenever a management frame is received. Note that this can be at quite a high rate, so some limited
filtering is provided before the callback is invoked. Only the first 110 bytes or so of the frame are returned -- this is an SDK restriction.
Any connected ap/station will be disconnected.
#### Syntax
`wifi.monitor.start([filter parameters,] mgmt_frame_callback)`
#### Parameters
- filter parameters. This is a byte offset (1 based) into the underlying data structure, a value to match against, and an optional mask to use for matching.
The data structure used for filtering is 12 bytes of [radio header](#the-radio-header), and then the actual frame. The first byte of the frame is therefore numbered 13. The filter
values of 13, 0x80 will just extract beacon frames.
- `mgmt_frame_callback` is a function which is invoked with a single argument which is a `wifi.packet` object which has many methods and attributes.
#### Returns
nothing.
#### Example
```
wifi.monitor.channel(6)
wifi.monitor.start(13, 0x80, function(pkt)
print ('Beacon: ' .. pkt.bssid_hex .. " '" .. pkt[0] .. "' ch " .. pkt[3]:byte(1))
end)
```
## wifi.monitor.stop()
This disables the monitor mode and returns to normal operation. There are no parameters and no return value.
#### Syntax
`wifi.monitor.stop()`
## wifi.monitor.channel()
This sets the channel number to monitor. Note that in many applications you will want to step through the channel numbers at regular intervals. Beacon
frames (in particular) are typically sent every 102 milliseconds, so a switch time of (say) 150 milliseconds seems to work well.
#### Syntax
`wifi.monitor.channel(channel)`
#### Parameters
- `channel` sets the channel number in the range 1 to 15.
#### Returns
nothing.
# wifi.packet object
This object provides access to the raw packet data and also many methods to extract data from the packet in a simple way.
## packet:radio_byte()
This is like the `string.byte` method, except that it gives access to the bytes of the [radio header](#the-radio-header).
#### Syntax
`packet:radio_byte(n)`
#### Parameters
- `n` the byte number (1 based) to get from the [radio header](#the-radio-header) portion of the packet
#### Returns
0-255 as the value of the byte
nothing if the offset is not within the [radio header](#the-radio-header).
## packet:frame_byte()
This is like the `string.byte` method, except that it gives access to the bytes of the received frame.
#### Syntax
`packet:frame_byte(n)`
#### Parameters
- `n` the byte number (1 based) to get from the received frame.
#### Returns
0-255 as the value of the byte
nothing if the offset is not within the received frame.
## packet:radio_sub()
This is like the `string.sub` method, except that it gives access to the bytes of the [radio header](#the-radio-header).
#### Syntax
`packet:radio_sub(start, end)`
#### Parameters
Same rules as for `string.sub` except that it operates on the [radio header](#the-radio-header).
#### Returns
A string according to the `string.sub` rules.
## packet:frame_sub()
This is like the `string.sub` method, except that it gives access to the bytes of the received frame.
#### Syntax
`packet:frame_sub(start, end)`
#### Parameters
Same rules as for `string.sub` except that it operates on the received frame.
#### Returns
A string according to the `string.sub` rules.
## packet:radio_subhex()
This is like the `string.sub` method, except that it gives access to the bytes of the [radio header](#the-radio-header). It also
converts them into hex efficiently.
#### Syntax
`packet:radio_subhex(start, end [, seperator])`
#### Parameters
Same rules as for `string.sub` except that it operates on the [radio header](#the-radio-header).
- `seperator` is an optional sting which is placed between the individual hex pairs returned.
#### Returns
A string according to the `string.sub` rules, converted into hex with possible inserted spacers.
## packet:frame_sub()
This is like the `string.sub` method, except that it gives access to the bytes of the received frame.
#### Syntax
`packet:frame_subhex(start, end [, seperator])`
#### Parameters
Same rules as for `string.sub` except that it operates on the received frame.
- `seperator` is an optional sting which is placed between the individual hex pairs returned.
#### Returns
A string according to the `string.sub` rules, converted into hex with possible inserted spacers.
## packet:ie_table()
This returns a table of the information elements from the management frame. The table keys values are the
information element numbers (0 - 255). Note that IE0 is the SSID. This method is mostly only useful if
you need to determine which information elements were in the management frame.
#### Syntax
`packet:ie_table()`
#### Parameters
None.
#### Returns
A table with all the information elements in it.
#### Example
```
print ("SSID", packet:ie_table()[0])
```
Note that this is possibly the worst way of getting the SSID.
#### Alternative
The `packet` object itself can be indexed to extract the information elements.
#### Example
```
print ("SSID", packet[0])
```
This is more efficient than the above approach, but requires you to remember that IE0 is the SSID.
## packet.<attribute>
The packet object has many attributes on it. These allow easy access to all the fields, though not an easy way to enumerate them. All integers are unsigned
except where noted. Information Elements are only returned if they are completely within the captured frame. This can mean that for some frames, some of the
information elements can be missing.
When a string is returned as the value of a field, it can (and often is) be a binary string with embedded nulls. All information elements are returned as strings
even if they are only one byte long and look like a number in the specification. This is purely to make the interface consistent. Note that even SSIDs can contain
embedded nulls.
| Attribute name | Type |
|:--------------------|:-------:|
| aggregation | Integer |
| ampdu_cnt | Integer |
| association_id | Integer |
| authentication_algorithm | Integer |
| authentication_transaction | Integer |
| beacon_interval | Integer |
| beacon_interval | Integer |
| bssid | String |
| bssid_hex | String |
| bssidmatch0 | Integer |
| bssidmatch1 | Integer |
| capability | Integer |
| channel | Integer |
| current_ap | String |
| cwb | Integer |
| dmatch0 | Integer |
| dmatch1 | Integer |
| dstmac | String |
| dstmac_hex | String |
| duration | Integer |
| fec_coding | Integer |
| frame | String (the entire received frame) |
| frame_hex | String |
| fromds | Integer |
| header | String (the fixed part of the management frame) |
| ht_length | Integer |
| ie_20_40_bss_coexistence | String |
| ie_20_40_bss_intolerant_channel_report | String |
| ie_advertisement_protocol | String |
| ie_aid | String |
| ie_antenna | String |
| ie_ap_channel_report | String |
| ie_authenticated_mesh_peering_exchange | String |
| ie_beacon_timing | String |
| ie_bss_ac_access_delay | String |
| ie_bss_available_admission_capacity | String |
| ie_bss_average_access_delay | String |
| ie_bss_load | String |
| ie_bss_max_idle_period | String |
| ie_cf_parameter_set | String |
| ie_challenge_text | String |
| ie_channel_switch_announcement | String |
| ie_channel_switch_timing | String |
| ie_channel_switch_wrapper | String |
| ie_channel_usage | String |
| ie_collocated_interference_report | String |
| ie_congestion_notification | String |
| ie_country | String |
| ie_destination_uri | String |
| ie_diagnostic_report | String |
| ie_diagnostic_request | String |
| ie_dms_request | String |
| ie_dms_response | String |
| ie_dse_registered_location | String |
| ie_dsss_parameter_set | String |
| ie_edca_parameter_set | String |
| ie_emergency_alart_identifier | String |
| ie_erp_information | String |
| ie_event_report | String |
| ie_event_request | String |
| ie_expedited_bandwidth_request | String |
| ie_extended_bss_load | String |
| ie_extended_capabilities | String |
| ie_extended_channel_switch_announcement | String |
| ie_extended_supported_rates | String |
| ie_fast_bss_transition | String |
| ie_fh_parameter_set | String |
| ie_fms_descriptor | String |
| ie_fms_request | String |
| ie_fms_response | String |
| ie_gann | String |
| ie_he_capabilities | String |
| ie_hopping_pattern_parameters | String |
| ie_hopping_pattern_table | String |
| ie_ht_capabilities | String |
| ie_ht_operation | String |
| ie_ibss_dfs | String |
| ie_ibss_parameter_set | String |
| ie_interworking | String |
| ie_link_identifier | String |
| ie_location_parameters | String |
| ie_management_mic | String |
| ie_mccaop | String |
| ie_mccaop_advertisement | String |
| ie_mccaop_advertisement_overview | String |
| ie_mccaop_setup_reply | String |
| ie_mccaop_setup_request | String |
| ie_measurement_pilot_transmission | String |
| ie_measurement_report | String |
| ie_measurement_request | String |
| ie_mesh_awake_window | String |
| ie_mesh_channel_switch_parameters | String |
| ie_mesh_configuration | String |
| ie_mesh_id | String |
| ie_mesh_link_metric_report | String |
| ie_mesh_peering_management | String |
| ie_mic | String |
| ie_mobility_domain | String |
| ie_multiple_bssid | String |
| ie_multiple_bssid_index | String |
| ie_neighbor_report | String |
| ie_nontransmitted_bssid_capability | String |
| ie_operating_mode_notification | String |
| ie_overlapping_bss_scan_parameters | String |
| ie_perr | String |
| ie_power_capability | String |
| ie_power_constraint | String |
| ie_prep | String |
| ie_preq | String |
| ie_proxy_update | String |
| ie_proxy_update_confirmation | String |
| ie_pti_control | String |
| ie_qos_capability | String |
| ie_qos_map_set | String |
| ie_qos_traffic_capability | String |
| ie_quiet | String |
| ie_quiet_channel | String |
| ie_rann | String |
| ie_rcpi | String |
| ie_request | String |
| ie_ric_data | String |
| ie_ric_descriptor | String |
| ie_rm_enabled_capacities | String |
| ie_roaming_consortium | String |
| ie_rsn | String |
| ie_rsni | String |
| ie_schedule | String |
| ie_secondary_channel_offset | String |
| ie_ssid | String |
| ie_ssid_list | String |
| ie_supported_channels | String |
| ie_supported_operating_classes | String |
| ie_supported_rates | String |
| ie_tclas | String |
| ie_tclas_processing | String |
| ie_tfs_request | String |
| ie_tfs_response | String |
| ie_tim | String |
| ie_tim_broadcast_request | String |
| ie_tim_broadcast_response | String |
| ie_time_advertisement | String |
| ie_time_zone | String |
| ie_timeout_interval | String |
| ie_tpc_report | String |
| ie_tpc_request | String |
| ie_tpu_buffer_status | String |
| ie_ts_delay | String |
| ie_tspec | String |
| ie_uapsd_coexistence | String |
| ie_vendor_specific | String |
| ie_vht_capabilities | String |
| ie_vht_operation | String |
| ie_vht_transmit_power_envelope | String |
| ie_wakeup_schedule | String |
| ie_wide_bandwidth_channel_switch | String |
| ie_wnm_sleep_mode | String |
| is_group | Integer |
| legacy_length | Integer |
| listen_interval | Integer |
| mcs | Integer |
| moredata | Integer |
| moreflag | Integer |
| not_counding | Integer |
| number | Integer |
| order | Integer |
| protectedframe | Integer |
| protocol | Integer |
| pwrmgmt | Integer |
| radio | String (the entire [radio header](#the-radio-header)) |
| rate | Integer |
| reason | Integer |
| retry | Integer |
| rssi | Signed Integer |
| rxend_state | Integer |
| sgi | Integer |
| sig_mode | Integer |
| smoothing | Integer |
| srcmac | String |
| srcmac_hex | String |
| status | Integer |
| stbc | Integer |
| subtype | Integer |
| timestamp | String |
| tods | Integer |
| type | Integer |
If you don't know what some of the attributes are, then you probably need to read the IEEE 802.11 specifications and other supporting material.
#### Example
```
print ("SSID", packet.ie_ssid)
```
## The Radio Header
The Radio Header has been mentioned above as a 12 byte structure. The layout is shown below. The only comments are in Chinese.
```
struct {
signed rssi:8;//表示该包的信号强度
unsigned rate:4;
unsigned is_group:1;
unsigned:1;
unsigned sig_mode:2;//表示该包是否是11n 的包,0 表示非11n,非0 表示11n
unsigned legacy_length:12;//如果不是11n 的包,它表示包的长度
unsigned damatch0:1;
unsigned damatch1:1;
unsigned bssidmatch0:1;
unsigned bssidmatch1:1;
unsigned MCS:7;//如果是11n 的包,它表示包的调制编码序列,有效值:0-76
unsigned CWB:1;//如果是11n 的包,它表示是否为HT40 的包
unsigned HT_length:16;//如果是11n 的包,它表示包的长度
unsigned Smoothing:1;
unsigned Not_Sounding:1;
unsigned:1;
unsigned Aggregation:1;
unsigned STBC:2;
unsigned FEC_CODING:1;//如果是11n 的包,它表示是否为LDPC 的包
unsigned SGI:1;
unsigned rxend_state:8;
unsigned ampdu_cnt:8;
unsigned channel:4;//表示该包所在的信道
unsigned:12;
}
```
# WS2812 effects Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2017-11-01 | [Konrad Huebner](https://github.com/skycoders) | [Konrad Huebner](https://github.com/skycoders) | [ws2812_effects.c](../../../app/modules/ws2812_effects.c)|
This module provides effects based on the [WS2812 library](ws2812.md). Some effects are inspired by / based on the [WS2812FX Library](https://github.com/kitesurfer1404/WS2812FX) but have been adopted to the specifics of the ws2812 library. The effects library works based on a buffer created through the ws2812 library and performs the operations on this buffer.
Note that dual mode is currently not supported for effects.
!!! caution
This module depends on the [color utils module](color-utils.md). Things **will** fail if that module is missing in the firmware!
#### Example usage
```lua
-- init the ws2812 module
ws2812.init(ws2812.MODE_SINGLE)
-- create a buffer, 60 LEDs with 3 color bytes
strip_buffer = ws2812.newBuffer(60, 3)
-- init the effects module, set color to red and start blinking
ws2812_effects.init(strip_buffer)
ws2812_effects.set_speed(100)
ws2812_effects.set_brightness(50)
ws2812_effects.set_color(0,255,0)
ws2812_effects.set_mode("blink")
ws2812_effects.start()
```
## ws2812_effects.init()
Initialize the effects library with the provided buffer for the connected LED strip.
#### Syntax
`ws2812_effects.init(buffer)`
#### Parameters
- `buffer` is a `ws2812.buffer` for the connected strip.
#### Returns
`nil`
## ws2812_effects.start()
Start the animation effect.
#### Syntax
`ws2812_effects.start()`
#### Parameters
`none`
#### Returns
`nil`
## ws2812_effects.stop()
Stop the animation effect.
#### Syntax
`ws2812_effects.stop()`
#### Parameters
`none`
#### Returns
`nil`
## ws2812_effects.set_brightness()
Set the brightness.
#### Syntax
`ws2812_effects.set_brightness(brightness)`
#### Parameters
- `brightness` brightness between 0 and 255
#### Returns
`nil`
## ws2812_effects.set_color()
Set the color.
#### Syntax
`ws2812_effects.set_color(g, r, b, [w])`
#### Parameters
- `g` is the green value between 0 and 255
- `r` is the red value between 0 and 255
- `b` is the blue value between 0 and 255
- `w` (optional) is the white value between 0 and 255
#### Returns
`nil`
## ws2812_effects.set_speed()
Set the speed.
#### Syntax
`ws2812_effects.set_speed(speed)`
#### Parameters
- `speed` speed between 0 and 255
#### Returns
`nil`
## ws2812_effects.get_speed()
Get current speed.
#### Syntax
`ws2812_effects.get_speed()`
#### Parameters
`none`
#### Returns
`speed` between 0 and 255
## ws2812_effects.set_delay()
Set the delay between two effect steps in milliseconds.
#### Syntax
`ws2812_effects.set_delay(delay)`
#### Parameters
- `delay` is the delay in milliseconds, minimum 10ms
#### Returns
`nil`
## ws2812_effects.get_delay()
Get current delay.
#### Syntax
`ws2812_effects.get_delay()`
#### Parameters
`none`
#### Returns
`delay` is the current effect delay in milliseconds
## ws2812_effects.set_mode()
Set the active effect mode.
#### Syntax
`ws2812_effects.set_mode(mode, [effect_param])`
#### Parameters
- `mode` is the effect mode as a string, can be one of
- `static` fills the buffer with the color set through `ws2812_effects.set_color()`
- `blink` fills the buffer with the color set through `ws2812_effects.set_color()` and starts blinking
- `gradient` fills the buffer with a gradient defined by the color values provided with the `effect_param`. This parameter must be a string containing the color values with same pixel size as the current buffer configuration. Minimum two colors must be provided. If more are provided, the strip is split in equal parts and the colors are used as intermediate colors. The gradient is calculated based on HSV color space, so no greyscale colors are supported as those cannot be converted to HSV.
- `gradient_rgb` similar to `gradient` but uses simple RGB value interpolation instead of conversions to the HSV color space.
- `random_color` fills the buffer completely with a random color and changes this color constantly
- `rainbow` animates through the full color spectrum, with the entire strip having the same color
- `rainbow_cycle` fills the buffer with a rainbow gradient. The optional second parameter states the number of repetitions (integer).
- `flicker` fills the buffer with the color set through `ws2812_effects.set_color()` and begins random flickering of pixels with a maximum flicker amount defined by the second parameter (integer, e.g. 50 to flicker with 50/255 of the color)
- `fire` is a fire flickering effect
- `fire_soft` is a soft fire flickering effect
- `fire_intense` is an intense fire flickering effect
- `halloween` fills the strip with purple and orange pixels and circles them
- `circus_combustus` fills the strip with red/white/black pixels and circles them
- `larson_scanner` is the K.I.T.T. scanner effect, based on the color set through `ws2812_effects.set_color()`
- `color_wipe` fills the strip pixel by pixel with the color set through `ws2812_effects.set_color()` and then starts turning pixels off again from beginning to end.
- `random_dot` sets random dots to the color set through `ws2812_effects.set_color()` and fades them out again
- `cycle` takes the buffer as-is and cycles it. With the second parameter it can be defined how many pixels the shift will be. Negative values shift to opposite direction.
- `effect_param` is an optional effect parameter. See the effect modes for further explanations. It can be an integer value or a string.
#### Returns
`nil`
#### Examples
Full initialization code for the strip, a buffer and the effect library can be found at top of this documentation. Only effect examples are shown here.
```lua
-- rainbow cycle with two repetitions
ws2812_effects.set_mode("rainbow_cycle", 2)
-- gradient from red to yellow to red
ws2812_effects.set_mode("gradient", string.char(0,200,0,200,200,0,0,200,0))
-- random dots with fading
ws2812_effects.set_mode("random_dot",3)
```
...@@ -63,7 +63,7 @@ file.close() ...@@ -63,7 +63,7 @@ file.close()
If the card doesn't work when calling `file.mount()` for the first time then re-try the command. It's possible that certain cards time out during the first initialization after power-up. If the card doesn't work when calling `file.mount()` for the first time then re-try the command. It's possible that certain cards time out during the first initialization after power-up.
The logical drives are mounted at the root of a unified directory tree where the mount points distinguish between internal flash (`/FLASH`) and the card's paritions (`/SD0` to `/SD3`). Files are accessed via either the absolute hierarchical path or relative to the current working directory. It defaults to `/FLASH` and can be changed with `file.chdir(path)`. The logical drives are mounted at the root of a unified directory tree where the mount points distinguish between internal flash (`/FLASH`) and the card's partitions (`/SD0` to `/SD3`). Files are accessed via either the absolute hierarchical path or relative to the current working directory. It defaults to `/FLASH` and can be changed with `file.chdir(path)`.
Subdirectories are supported on FAT volumes only. Subdirectories are supported on FAT volumes only.
......
...@@ -4,12 +4,12 @@ As with [flashing](flash.md) there are several ways to upload code from your com ...@@ -4,12 +4,12 @@ As with [flashing](flash.md) there are several ways to upload code from your com
The NodeMCU serial interface uses 115'200bps at boot time. To change the speed after booting, issue `uart.setup(0,9600,8,0,1,1)`. If the device panics and resets at any time, errors will be written to the serial interface at 115'200 bps. The NodeMCU serial interface uses 115'200bps at boot time. To change the speed after booting, issue `uart.setup(0,9600,8,0,1,1)`. If the device panics and resets at any time, errors will be written to the serial interface at 115'200 bps.
# Tools ## Tools
Transferring application code to ESP8266/8285 is an essential task, one that you'll perform quite frequently. Hence, it does make sense to try a few different uploading tools until you find one you feel comfortable with. [https://frightanic.com/iot/tools-ides-nodemcu/](https://frightanic.com/iot/tools-ides-nodemcu/) lists almost a dozen classical uploaders - in addition to IDEs or IDE-like applications which of course transfer code as well. Transferring application code to ESP8266/8285 is an essential task, one that you'll perform quite frequently. Hence, it does make sense to try a few different uploading tools until you find one you feel comfortable with. [https://frightanic.com/iot/tools-ides-nodemcu/](https://frightanic.com/iot/tools-ides-nodemcu/) lists almost a dozen classical uploaders - in addition to IDEs or IDE-like applications which of course transfer code as well.
The NodeMCU firmware team does not give any recommendations as for which uploader to use nor are there any "NodeMCU approved" tools. The below listed tools are just three, in no particular order, which seem popular and/or reasonably well maintained. The NodeMCU firmware team does not give any recommendations as for which uploader to use nor are there any "NodeMCU approved" tools. The below listed tools are just three, in no particular order, which seem popular and/or reasonably well maintained.
## ESPlorer ### ESPlorer
> The essential multiplatforms tools for any ESP8266 developer from luatool author’s, including Lua for NodeMCU and MicroPython. Also, all AT commands are supported. Requires Java (Standard Edition - SE ver 7 and above) installed. > The essential multiplatforms tools for any ESP8266 developer from luatool author’s, including Lua for NodeMCU and MicroPython. Also, all AT commands are supported. Requires Java (Standard Edition - SE ver 7 and above) installed.
...@@ -19,7 +19,7 @@ Source: [https://github.com/4refr0nt/ESPlorer](https://github.com/4refr0nt/ESPlo ...@@ -19,7 +19,7 @@ Source: [https://github.com/4refr0nt/ESPlorer](https://github.com/4refr0nt/ESPlo
Supported platforms: macOS, Linux, Windows, anything that runs Java Supported platforms: macOS, Linux, Windows, anything that runs Java
## nodemcu-uploader.py ### nodemcu-uploader.py
> A simple tool for uploading files to the filesystem of an ESP8266 running NodeMCU as well as some other useful commands. > A simple tool for uploading files to the filesystem of an ESP8266 running NodeMCU as well as some other useful commands.
...@@ -27,7 +27,7 @@ Source: [https://github.com/kmpm/nodemcu-uploader](https://github.com/kmpm/nodem ...@@ -27,7 +27,7 @@ Source: [https://github.com/kmpm/nodemcu-uploader](https://github.com/kmpm/nodem
Supported platforms: macOS, Linux, Windows, anything that runs Python Supported platforms: macOS, Linux, Windows, anything that runs Python
## NodeMCU-Tool ### NodeMCU-Tool
> Upload/Download Lua files to your ESP8266 module with NodeMCU firmware. > Upload/Download Lua files to your ESP8266 module with NodeMCU firmware.
> Simple. Command Line. Cross-Platform. File Management. NodeMCU. > Simple. Command Line. Cross-Platform. File Management. NodeMCU.
...@@ -36,7 +36,7 @@ Source: [https://github.com/andidittrich/NodeMCU-Tool](https://github.com/andidi ...@@ -36,7 +36,7 @@ Source: [https://github.com/andidittrich/NodeMCU-Tool](https://github.com/andidi
Supported platforms: macOS, Linux Windows, anything that runs Node.js Supported platforms: macOS, Linux Windows, anything that runs Node.js
# init.lua ## init.lua
You will see "lua: cannot open init.lua" printed to the serial console when the device boots after it's been freshly flashed. If NodeMCU finds a `init.lua` in the root of the file system it will execute it as part of the boot sequence (standard Lua feature). Hence, your application is initialized and triggered from `init.lua`. Usually you first set up the WiFi connection and only continue once that has been successful. You will see "lua: cannot open init.lua" printed to the serial console when the device boots after it's been freshly flashed. If NodeMCU finds a `init.lua` in the root of the file system it will execute it as part of the boot sequence (standard Lua feature). Hence, your application is initialized and triggered from `init.lua`. Usually you first set up the WiFi connection and only continue once that has been successful.
Be very careful not to lock yourself out! If there's a bug in your `init.lua` you may be stuck in an infinite reboot loop. It is, therefore, advisable to build a small delay into your startup sequence that would allow you to interrupt the sequence by e.g. deleting or renaming `init.lua` (see also [FAQ](lua-developer-faq.md#how-do-i-avoid-a-panic-loop-in-initlua)). Your `init.lua` is most likely going to be different than the one below but it's a good starting point for customizations: Be very careful not to lock yourself out! If there's a bug in your `init.lua` you may be stuck in an infinite reboot loop. It is, therefore, advisable to build a small delay into your startup sequence that would allow you to interrupt the sequence by e.g. deleting or renaming `init.lua` (see also [FAQ](lua-developer-faq.md#how-do-i-avoid-a-panic-loop-in-initlua)). Your `init.lua` is most likely going to be different than the one below but it's a good starting point for customizations:
...@@ -116,7 +116,7 @@ wifi.sta.config({ssid=SSID, pwd=PASSWORD}) ...@@ -116,7 +116,7 @@ wifi.sta.config({ssid=SSID, pwd=PASSWORD})
``` ```
# Compiling Lua on your PC for Uploading ## Compiling Lua on your PC for Uploading
If you install `lua` on your development PC or Laptop then you can use the standard Lua If you install `lua` on your development PC or Laptop then you can use the standard Lua
compiler to syntax check any Lua source before downloading it to the ESP8266 module. However, compiler to syntax check any Lua source before downloading it to the ESP8266 module. However,
......
favicon.ico was generated using https://realfavicongenerator.net. favicon_package_v0.16.zip in this folder contains icons and instructions for all sorts of browsers and platforms (incl. mobile variants). However, without modifying the MkDocs theme/template they’re of no use.
\ No newline at end of file
...@@ -8,7 +8,6 @@ var nodemcu = nodemcu || {}; ...@@ -8,7 +8,6 @@ var nodemcu = nodemcu || {};
$(document).ready(function () { $(document).ready(function () {
addToc(); addToc();
fixSearch();
hideNavigationForAllButSelectedLanguage(); hideNavigationForAllButSelectedLanguage();
addLanguageSelectorToRtdFlyOutMenu(); addLanguageSelectorToRtdFlyOutMenu();
replaceRelativeLinksWithStaticGitHubUrl(); replaceRelativeLinksWithStaticGitHubUrl();
...@@ -45,34 +44,6 @@ var nodemcu = nodemcu || {}; ...@@ -45,34 +44,6 @@ var nodemcu = nodemcu || {};
} }
} }
/*
* RTD messes up MkDocs' search feature by tinkering with the search box defined in the theme, see
* https://github.com/rtfd/readthedocs.org/issues/1088. This function sets up a DOM4 MutationObserver
* to react to changes to the search form (triggered by RTD on doc ready). It then reverts everything
* the RTD JS code modified.
*/
function fixSearch() {
var target = document.getElementById('rtd-search-form');
var config = {attributes: true, childList: true};
var observer = new MutationObserver(function(mutations) {
// if it isn't disconnected it'll loop infinitely because the observed element is modified
observer.disconnect();
var form = $('#rtd-search-form');
form.empty();
form.attr('action', 'https://' + window.location.hostname + '/en/' + determineSelectedBranch() + '/search.html');
$('<input>').attr({
type: "text",
name: "q",
placeholder: "Search docs"
}).appendTo(form);
});
if (window.location.origin.indexOf('readthedocs') > -1) {
observer.observe(target, config);
}
}
function hideNavigationForAllButSelectedLanguage() { function hideNavigationForAllButSelectedLanguage() {
var selectedLanguageCode = determineSelectedLanguageCode(); var selectedLanguageCode = determineSelectedLanguageCode();
var selectedLanguageName = languageCodeToNameMap[selectedLanguageCode]; var selectedLanguageName = languageCodeToNameMap[selectedLanguageCode];
......
...@@ -116,7 +116,6 @@ SECTIONS ...@@ -116,7 +116,6 @@ SECTIONS
/* *libcrypto.a:*(.literal .text) - tested that safe to keep in iROM */ /* *libcrypto.a:*(.literal .text) - tested that safe to keep in iROM */
/* *libdriver.a:*(.literal .text) - not used anywhere in NodeMCU */ /* *libdriver.a:*(.literal .text) - not used anywhere in NodeMCU */
/* *libespnow.a:*(.literal .text) - not used anywhere in NodeMCU */ /* *libespnow.a:*(.literal .text) - not used anywhere in NodeMCU */
/* *libmesh.a:*(.literal .text) - not used anywhere in NodeMCU */
/* *liblwip_536.a:*(.literal .text) - source-based library used instead */ /* *liblwip_536.a:*(.literal .text) - source-based library used instead */
/* *libpwm.a:*(.literal .text) - our own implementation used instead */ /* *libpwm.a:*(.literal .text) - our own implementation used instead */
/* *libwpa.a:*(.literal .text) - tested that safe to keep in iROM */ /* *libwpa.a:*(.literal .text) - tested that safe to keep in iROM */
...@@ -207,6 +206,7 @@ SECTIONS ...@@ -207,6 +206,7 @@ SECTIONS
*(COMMON) *(COMMON)
. = ALIGN (8); . = ALIGN (8);
_bss_end = ABSOLUTE(.); _bss_end = ABSOLUTE(.);
*(.noinit)
_heap_start = ABSOLUTE(.); _heap_start = ABSOLUTE(.);
/* _stack_sentry = ALIGN(0x8); */ /* _stack_sentry = ALIGN(0x8); */
} >dram0_0_seg :dram0_0_bss_phdr } >dram0_0_seg :dram0_0_bss_phdr
......
## ESP8266 Lua OTA
Espressif use an optional update approach for their firmware know as OTA (over the air).
This module offers an equivalent facility for Lua applications developers, and enables
module development and production updates by carrying out automatic synchronisation
with a named provisioning service at reboot.
### Overview
This `luaOTA` provisioning service uses a different approach to
[enduser setup](https://nodemcu.readthedocs.io/en/dev/en/modules/enduser-setup/).
The basic concept here is that the ESP modules are configured with a pre-imaged file
system that includes a number of files in the luaOTA namespace. (SPIFFS doesn't
implement a directory hierarchy as such, but instead simply treats the conventional
directory separator as a character in the filename. Nonetheless, the "luaOTA/"
prefix serves to separate the lc files in the luaOTA namespace.)
- `luaOTA/check.lc` This module should always be first executed at startup.
- `luaOTA/_init.lc`
- `luaOTA/_doTick.lc`
- `luaOTA/_provision.lc`
A fifth file `luaOTA/config.json` contains a JSON parameterised local configuration that
can be initially create by and subsequently updated by the provisioning process. Most
importantly this configuration contains the TCP address of the provisioning service, and
a shared secret that is used to sign any records exchanged between the ESP client and
the provisioning service.
Under this approach, `init.lua` is still required but it is reduced to a one-line lua
call which invokes the `luaOTA` module by a `require "luaOTA.check"` statement.
The `config.json` file which provides the minimum configuration parameters to connect to
the WiFi and provisioning server, however these can by overridden through the UART by
first doing a `tmr.stop(0)` and then a manual initialisation as described in the
[init.lua](#initlua) section below.
`luaOTA` configures the wifi and connects to the required sid in STA mode using the
local configuration. The ESP's IP address is allocated using DHCP unless the optional
three static IP parameters have been configured. It then attempts to establish a
connection to the named provisioning service. If this is absent, a timeout occurs or the
service returns a "no update" status, then module does a full clean up of all the
`luaOTA` resources (if the `leave` parameter is false, then the wifi stack is then also
shutdown.), and it then transfers control by a `node.task.post()` to the configured
application module and function.
If `luaOTA` does establish a connection to IP address:port of the provisioning service,
it then issues a "getupdate" request using its CPU ID and a configuration parameter
block as context. This update dialogue uses a simple JSON protocol(described below) that
enables the provision server either to respond with a "no update", or to start a
dialogue to reprovision the ESP8266's SPIFFS.
In the case of "no update", `luaOTA` is by design ephemeral, that is it shuts down the
net services and does a full resource clean up. Hence the presence of the provisioning
service is entirely optional and it doesn't needed to be online during normal operation,
as `luaOTA` will fall back to transferring control to the main Lua application.
In the case of an active update, **the ESP is restarted** so resource cleanup on
completion is not an issue. The provisioning dialogue is signed, so the host
provisioning service and the protocol are trusted, with the provisioning service driving
the process. This greatly simplifies the `luaOTA` client coding as this is a simple
responder, which actions simple commands such as:
- download a file,
- download and compile file,
- upload a file
- rename (or delete) a file
with the ESP being rebooted on completion of the updates to the SPIFFS. Hence in
practice the ESP boots into one one two modes:
- _normal execution_ or
- _OTA update_ followed by reboot and normal execution.
Note that even though NodeMCU follows the Lua convention of using the `lua` and `lc`
extensions respectively for source files that need to be compiled, and for pre-compiled
files, the Lua loader itself only uses the presence of a binary header to determine the
file mode. Hence if the `init.lua` file contains pre-compiled content, and similarly all
loaded modules use pre-compiled lc files, then the ESP can run in production mode
_without needing to invoke the compiler at all_.
The simplest strategy for the host provisioning service is to maintain a reference
source directory on the host (per ESP module). The Lua developer can maintain this under
**git** or equivalent and make any changes there, so that synchronisation of the ESP
will be done automatically on reboot.
### init.lua
This is typically includes a single line:
```Lua
require "LuaOTA.check"
```
however if the configuration is incomplete then this can be aborted as manual process
by entering the manual command through the UART
```Lua
tmr.stop(0); require "luaOTA.check":_init {ssid ="SOMESID" --[[etc. ]]}
```
where the parameters to the `_init` method are:
- `ssid` and `spwd`. The SSID of the Wifi service to connect to, together with its
password.
- `server` and `port`. The name or IP address and port of the provisioning server.
- `secret`. A site-specific secret shared with the provisioning server for MD5-based
signing of the protocol messages.
- `leave`. If true the STA service is left connected otherwise the wifi is shutdown
- `espip`,`gw`,`nm`,`ns`. These parameters are omitted if the ESP is using a DHCP
service for IP configuration, otherwise you need to specify the ESP's IP, gateway,
netmask and default nameserver.
If the global `DEBUG` is set, then LuaOTA will also dump out some diagnostic debug.
### luaOTA.check
This only has one public method: `_init` which can be called with the above parameters.
However the require wrapper in the check module also posts a call to `self:_init()` as a
new task. This new task function includes a guard to prevent a double call in the case
where the binding require includes an explicit call to `_init()`
Any provisioning changes results in a reboot, so the only normal "callback" is to invoke
the application entry method as defined in `config.json` using a `node.task.post()`
### luaOTAserver.lua
This is often tailored to specific project requirements, but a simple example of a
provisioning server is included which provides the corresponding server-side
functionality. This example is coded in Lua and can run on any development PC or server
that supports Lua 5.1 - 5.3 and the common modules `socket`, `lfs`, `md5` and `cjson`.
It can be easily be used as the basis of one for your specific project needs.
Note that even though this file is included in the `luaOTA` subdirectory within Lua
examples, this is designed to run on the host and should not be included in the
ESP SPIFFS.
## Implementation Notes
- The NodeMCu build must include the following modules: `wifi`, `net`, `file`, `tmr`,
`crypto` and`sjason`.
- This implementation follow ephemeral practices, that it is coded to ensure that all
resources used are collected by the Lua GC, and hence the available heap on
application start is the same as if luaOTA had not been called.
- Methods in the `check` file are static and inherit self as an upvalue.
- In order to run comfortably within ESP resources, luaOTA executes its main
functionality as a number of overlay methods. These are loaded dynamically (and largely
transparently) by an `__index` metamethod.
- Methods starting with a "_" are call-once and return the function reference
- All others are also entered in the self table so that successive calls will use
the preloaded function. The convention is that any dynamic function is called in object
form so they are loaded and executed with self as the first parameter, and hence are
called using the object form self:someFunc() to get the context as a parameter.
- Some common routines are also defined as closures within the dynamic methods
- This coding also makes a lot of use of tailcalls (See PiL 6.3) to keep the stack size
to a minimum.
- The update process uses a master timer in `tmr` slot 0. The index form is used here
in preference to the object form because of the reduced memory footprint. This also
allows the developer to abort the process early in the boot sequence by issuing a
`tmr.stop(0)` through UART0.
- The command protocol is unencrypted and uses JSON encoding, but all exchanges are
signed by a 6 char signature taken extracted from a MD5 based digest across the JSON
string. Any command which fails the signature causes the update to be aborted. Commands
are therefore regarded as trusted, and this simplifies the client module on the ESP.
- The process can support both source and compiled code provisioning, but the latter
is recommended as this permits a compile-free runtime for production use, and hence
minimises the memory use and fragmentation that occurs as a consequence of compilation.
- In earlier versions of the provisioning service example, I included `luaSrcDiet` but
this changes the line numbering which I found real pain for debugging, so I now just
include a simple filter to remove "--" comments and leading and trailing whitespace if
the source includes a `--SAFETRIM` flag. This typically reduced the size of lua files
transferred by ~30% and this also means that developers have no excuse for not properly
commenting their code!
- The chip ID is included in the configuration identification response to permit the
provisioning service to support different variants for different ESP8266 chips.
- The (optional update & reboot) operate model also has the side effect that the
`LuaOTA` client can reprovision itself.
- Though the simplest approach is always to do a `luaOTA.check` immediately on reboot,
there are other strategies that could be applied, for example to test a gpio pin or a
flag in RTC memory or even have the application call the require directly (assuming that
there's enough free RAM for it to run and this way avoid the connection delay to the
WiFi.
## Discussion on RAM usage
`luaOTA` also itself serves as a worked example of how to write ESP-friendly
applications.
- The functionality is divided into autoloaded processing chunks using a self
autoloader, so that `self:somefunction()` calls can load new code from flash in
a way that is simple and largely transparent to the application. The autoloader
preferentially loads the `lc` compiled code variant if available.
- The local environment is maintained in a self array, to keep scoping explicit. Note
that since loaded code cannot inherit upvalues, then `self` must be passed to the
function using an object constructor `self:self:somefunction()`, but where the function
can have a self argument then the alternative is to use an upvalue binding. See the
`tmr` alarm call at the end of `_init.lua` as an example:
```Lua
tmr.alarm(0, 500, tmr.ALARM_AUTO, self:_doTick())
```
- The `self:_doTick()` is evaluated before the alarm API call. This autoloads
`luaOTA/_doTick.lc` which stores `self` as a local and returns a function which takes
no arguments; it is this last returned function that is used as the timer callback,
and when this is called it can still access self as an upvalue.
- This code makes a lot of use of locals and upvalues as these are both fast and use
less memory footprint than globals or table entries.
- The lua GC will mark and sweep to reclaim any unreferenced resources: tables,
strings, functions, userdata. So if your code at the end of a processing phase leaves
no variables (directly or indirectly) in _G or the Lua registry, then all of the
resources that were loaded to carry out your application will be recovered by the GC.
In this case heap at the end of a "no provisioning" path is less than 1Kb smaller than
if luaOTA had not been called and this is an artifact of how the lua_registry system
adopts a lazy reuse of registry entries.
- If you find that an enumeration of `debug.getregistry()` includes function references
or tables other than ROMtables, then you have not been tidying up by doing the
appropriate closes or unregister calls. Any such stuck resources can result in a
stuck cascade due to upvalues being preserved in the function closure or entries in a
table.
tmr.stop(0)--SAFETRIM
-- function _doTick(self)
-- Upvals
local self = ...
local wifi,net = wifi,net
local sta = wifi.sta
local config,log,startApp = self.config,self.log,self.startApp
local tick_count = 0
local function socket_close(socket) --upval: self, startApp
if rawget(self,"socket") then
self.socket=nil -- remove circular reference in upval
pcall(socket.close,socket)
return startApp("Unexpected socket close")
end
end
local function receiveFirstRec(socket, rec) -- upval: self, crypto, startApp, tmr
local cmdlen = (rec:find('\n',1, true) or 0) - 1
local cmd,hash = rec:sub(1,cmdlen-6), rec:sub(cmdlen-5,cmdlen)
if cmd:find('"r":"OK!"',1,true) or cmdlen < 16 or
hash ~= crypto.toHex(crypto.hmac("MD5", cmd, self.secret):sub(-3)) then
print "No provisioning changes required"
self.socket = nil
self.post(function() --upval: socket
if socket then pcall(socket.close, socket) end
end)
return startApp("OK! No further updates needed")
end
-- Else a valid request has been received from the provision service free up
-- some resources that are no longer needed and set backstop timer for general
-- timeout. This also dereferences the previous doTick cb so it can now be GCed.
collectgarbage()
tmr.alarm(0, 30000, tmr.ALARM_SINGLE, self.startApp)
return self:_provision(socket,rec)
end
local function socket_connect(socket) --upval: self, socket_connect
print "Connected to provisioning service"
self.socket = socket
socket_connect = nil -- make this function available for GC
socket:on("receive", receiveFirstRec)
return self.socket_send(socket, self.config)
end
local conn
return function() -- the proper doTick() timer callback
tick_count = tick_count + 1
log("entering tick", tick_count, sta.getconfig(false), sta.getip())
if (tick_count < 20) then -- (wait up to 10 secs for Wifi connection)
local status, ip = sta.status(),{sta.getip()}
if (status == wifi.STA_GOTIP) then
log("Connected:", unpack(ip))
if (config.nsserver) then
net.dns.setdnsserver(config.nsserver, 0)
end
conn = net.createConnection(net.TCP, 0)
conn:on("connection", socket_connect)
conn:on("disconnection", socket_close)
conn:connect(config.port, config.server)
tick_count = 20
end
elseif (tick_count == 20) then -- assume timeout and exec app CB
return self.startApp("OK: Timeout on waiting for wifi station setup")
elseif (tick_count == 26) then -- wait up to 2.5 secs for TCP response
tmr.unregister(0)
pcall(conn.close, conn)
self.socket=nil
return startApp("OK: Timeout on waiting for provision service response")
end
end
-- end
--SAFETRIM
-- function _init(self, args)
local self, args = ...
-- The config is read from config.json but can be overridden by explicitly
-- setting the following args. Setting to "nil" deletes the config arg.
--
-- ssid, spwd Credentials for the WiFi
-- server, port, secret Provisioning server:port and signature secret
-- leave If true then the Wifi is left connected
-- espip, gw, nm, nsserver These need set if you are not using DHCP
local wifi, file, json, tmr = wifi, file, sjson, tmr
local log, sta, config = self.log, wifi.sta, nil
print ("\nStarting Provision Checks")
log("Starting Heap:", node.heap())
if file.open(self.prefix .. "config.json", "r") then
local s; s, config = pcall(json.decode, file.read())
if not s then print("Invalid configuration:", config) end
file.close()
end
if type(config) ~= "table" then config = {} end
for k,v in pairs(args or {}) do config[k] = (v ~= "nil" and v) end
config.id = node.chipid()
config.a = "HI"
self.config = config
self.secret = config.secret
config.secret = nil
log("Config is:",json.encode(self.config))
log("Mode is", wifi.setmode(wifi.STATION, false), config.ssid, config.spwd)
log("Config status is", sta.config(
{ ssid = config.ssid, pwd = config.spwd, auto = false, save = false } ))
if config.espip then
log( "Static IP setup:", sta.setip(
{ ip = config.espip, gateway = config.gw, netmask = config.nm }))
end
sta.connect(1)
package.loaded[self.modname] = nil
self.modname=nil
tmr.alarm(0, 500, tmr.ALARM_AUTO, self:_doTick())
-- end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment