Commit 136e0973 authored by Nathaniel Wesley Filardo's avatar Nathaniel Wesley Filardo
Browse files

Merge dev into release

While we intend our release strategy to be that we just fast-forward our
`release` branch to `dev`, things have come a little off the wheels.
This is a "git merge -s recursive -X theirs" of `dev` into `release`
instead.
parents 4f679277 c212b30a
# Pixel Buffer (pixbuf) Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2020-?? | [nwf](https://github.com/nwf) | nwf | [pixbuf.c](../../app/modules/pixbuf.c) |
The pixbuf library offers C-array byte objects and convenient utility functions
for maintaining small frame buffers, usually for use with LED arrays, as
supported by, e.g., ws2812.
## pixbuf.newBuffer()
Allocate a new memory buffer to store LED values.
#### Syntax
`pixbuf.newBuffer(numberOfLeds, numberOfChannels)`
#### Parameters
- `numberOfLeds` length of the LED strip (in pixels)
- `numberOfChannels` the channel count (bytes per pixel)
#### Returns
`pixbuf.buffer` object
## pixbuf.buffer:get()
Return the value at the given position, in native strip color order
#### Syntax
`buffer:get(index)`
#### Parameters
- `index` position in the buffer (1 for first LED)
#### Returns
`(color)`
#### Example
```lua
buffer = pixbuf.newBuffer(32, 4)
print(buffer:get(1))
0 0 0 0
```
## pixbuf.buffer:set()
Set the value at the given position, in native strip color order
#### Syntax
`buffer:set(index, color)`
#### Parameters
- `index` position in the buffer (1 for the first LED)
- `color` payload of the color
Payload could be:
- `number, number, ...`, passing as many colors as required by the array type
- `table` should contain one value per color required by the array type
- `string` with a natural multiple of the colors required by the array type
`string` inputs may be used to set multiple consecutive pixels!
#### Returns
The buffer
#### Example
```lua
buffer = pixbuf.newBuffer(32, 3)
buffer:set(1, 255, 0, 0) -- set the first LED green for a GRB strip
```
```lua
buffer = pixbuf.newBuffer(32, 4)
buffer:set(1, {255, 0, 0, 255}) -- set the first LED white and red for a RGBW strip
```
```lua
-- set the first LED green for a RGB strip and exploit the return value
buffer = pixbuf.newBuffer(32, 3):set(1, string.char(0, 255, 0))
```
## pixbuf.buffer:size()
Return the size of the buffer in number of LEDs
#### Syntax
`buffer:size()`
#### Parameters
none
#### Returns
`int`
## pixbuf.buffer:channels()
Return the buffer's channel count
#### Syntax
`buffer:channels()`
#### Parameters
none
#### Returns
`int`
## pixbuf.buffer:fill()
Fill the buffer with the given color.
The number of given bytes must match the channel count of the buffer.
#### Syntax
`buffer:fill(color)`
#### Parameters
- `color` bytes for each channel
#### Returns
The buffer
#### Example
```lua
buffer:fill(0, 0, 0) -- fill the buffer with black for a RGB strip
```
## pixbuf.buffer:dump()
Returns the contents of the buffer (the pixel values) as a string. This can then be saved to a file or sent over a network and may be fed back to [`pixbuf.buffer:set()`](#pixbufbufferset).
#### Syntax
`buffer:dump()`
#### Returns
A string containing the pixel values.
#### Example
```lua
local s = buffer:dump()
```
## pixbuf.buffer:replace()
Inserts a string (or a pixbuf) into another buffer with an offset.
The buffer must be of the same type or an error will be thrown.
#### Syntax
`buffer:replace(source[, offset])`
#### Parameters
- `source` the pixel values to be set into the buffer. This is either a string or a pixbuf.
- `offset` the offset where the source is to be placed in the buffer. Default is 1. Negative values can be used.
#### Returns
`nil`
#### Example
```lua
buffer:replace(anotherbuffer:dump()) -- copy one buffer into another via a string
buffer:replace(anotherbuffer) -- copy one buffer into another
newbuffer = buffer.sub(1) -- make a copy of a buffer into a new buffer
```
## pixbuf.buffer:mix()
This is a general method that loads data into a buffer that is a linear combination of data from other buffers. It can be used to copy a buffer or,
more usefully, do a cross fade. The pixel values are computed as integers and then range limited to [0, 255]. This means that negative
factors work as expected, and that the order of combining buffers does not matter.
#### Syntax
`buffer:mix(factor1, buffer1, ...)`
#### Parameters
- `factor1` This is the factor that the contents of `buffer1` are multiplied by. This factor is scaled by a factor of 256. Thus `factor1` value of 256 is a factor of 1.0.
- `buffer1` This is the source buffer. It must be of the same shape as the destination buffer.
There can be any number of factor/buffer pairs.
#### Returns
The output buffer.
#### Example
```lua
-- loads buffer with a crossfade between buffer1 and buffer2
buffer:mix(256 - crossmix, buffer1, crossmix, buffer2)
-- multiplies all values in buffer by 0.75
-- This can be used in place of buffer:fade
buffer:mix(192, buffer)
```
## pixbuf.buffer:mix4I5()
Like [`pixbuf.buffer:mix()`](#pixbufbuffermix) but treats the first channel as
a scaling, 5-bit intensity value. The buffers must all have four channels.
This is mostly useful for APA102 LEDs.
## pixbuf.buffer:power()
Computes the total energy requirement for the buffer. This is merely the total sum of all the pixel values (which assumes that each color in each
pixel consumes the same amount of power). A real WS2812 (or WS2811) has three constant current drivers of 20mA -- one for each of R, G and B. The
pulse width modulation will cause the *average* current to scale linearly with pixel value.
#### Syntax
`buffer:power()`
#### Returns
An integer which is the sum of all the pixel values.
#### Example
```lua
-- Dim the buffer to no more than the PSU can provide
local psu_current_ma = 1000
local led_current_ma = 20
local led_sum = psu_current_ma * 255 / led_current_ma
local p = buffer:power()
if p > led_sum then
buffer:mix(256 * led_sum / p, buffer) -- power is now limited
end
```
## pixbuf.buffer:powerI()
Like [`pixbuf.buffer:power()`](#pixbufbufferpower) but treats the first channel as
a scaling intensity value.
## pixbuf.buffer:fade()
Fade in or out. Defaults to out. Multiply or divide each byte of each led with/by the given value. Useful for a fading effect.
#### Syntax
`buffer:fade(value [, direction])`
#### Parameters
- `value` value by which to divide or multiply each byte
- `direction` pixbuf.FADE\_IN or pixbuf.FADE\_OUT. Defaults to pixbuf.FADE\_OUT
#### Returns
`nil`
#### Example
```lua
buffer:fade(2)
buffer:fade(2, pixbuf.FADE_IN)
```
## pixbuf.buffer:fadeI()
Like [`pixbuf.buffer:fade()`](#pixbufbufferfade) but treats the first channel as
a scaling intensity value. This is mostly useful for APA102 LEDs.
## pixbuf.buffer:shift()
Shift the content of (a piece of) the buffer in positive or negative direction. This allows simple animation effects. A slice of the buffer can be specified by using the
standard start and end offset Lua notation. Negative values count backwards from the end of the buffer.
#### Syntax
`buffer:shift(value [, mode[, i[, j]]])`
#### Parameters
- `value` number of pixels by which to rotate the buffer. Positive values rotate forwards, negative values backwards.
- `mode` is the shift mode to use. Can be one of `pixbuf.SHIFT_LOGICAL` or `pixbuf.SHIFT_CIRCULAR`. In case of SHIFT\_LOGICAL, the freed pixels are set to 0 (off). In case of SHIFT\_CIRCULAR, the buffer is treated like a ring buffer, inserting the pixels falling out on one end again on the other end. Defaults to SHIFT\_LOGICAL.
- `i` is the first offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is 1.
- `j` is the last offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is -1.
#### Returns
`nil`
#### Example
```lua
buffer:shift(3)
```
## pixbuf.buffer:sub()
This implements the extraction function like `string.sub`. The indexes are in leds and all the same rules apply.
#### Syntax
`buffer1:sub(i[, j])`
#### Parameters
- `i` This is the start of the extracted data. Negative values can be used.
- `j` this is the end of the extracted data. Negative values can be used. The default is -1.
#### Returns
A buffer containing the extracted piece.
#### Example
```
b = buffer:sub(1,10)
```
## pixbuf.buffer:__concat()
This implements the `..` operator to concatenate two buffers. They must have the same number of colors per led.
#### Syntax
`buffer1 .. buffer2`
#### Parameters
- `buffer1` this is the start of the resulting buffer
- `buffer2` this is the end of the resulting buffer
#### Returns
The concatenated buffer.
#### Example
```
ws2812.write(buffer1 .. buffer2)
```
## pixbuf.buffer:map()
Map a function across each pixel of one, or zip a function along two,
pixbuf(s), storing into the buffer on which it is called.
#### Syntax
`buffer0:map(f, [buffer1], [start1], [end1], [buffer2, [start2]])`
#### Parameters
- `f` This is the mapping function; it is applied for each pixel to all channels of `buffer1` and
all channels of `buffer2`, if given. It must return a value for each channel of the output
buffer, `buffer0`.
- `buffer1` The first source buffer. Defaults to `buffer0`.
- `start1` This is the start of the mapped range of `buffer1`. Negative values can be used and will be interpreted as before the end of `buffer1`. The default is 1.
- `end1` this is the end of the mapped range. Negative values can be used. The default is -1 (i.e., the end of `buffer1`).
- `buffer2` is a second buffer, for zip operations
- `start2` This is the start of the mapped range within `buffer2`. Negative values can be used and will be interpreted as before the end of `buffer2`. The default is 1.
`buffer0` must have sufficient room to recieve all pixels from `start1` to
`end1` (which is true of the defaults, when `buffer1` is `buffer0` and `start1`
is 1 and `end1` is -1). `buffer2`, if given, must have sufficient pixels after
`start2`.
#### Returns
`buffer0`
#### Examples
Change channel order within a single buffer:
```Lua
buffer:map(function(r,g,b) return g,r,b end)
```
Change channel order for a subset of pixels:
```Lua
buffer:map(function(r,g,b) return g,r,b end, nil, 2, 5)
```
Extract one channel for a subset of pixels:
```Lua
outbuf = pixbuf.create(11, 1)
outbuf:map(function(r,g,b) return b end, inbuf, 10, 20)
```
Concatenate channels per pixel, possibly with different offsets in buffers:
```Lua
outbuf:map(function(...) return ... end, inbuf1, inbuf2)
outbuf:map(function(...) return ... end, inbuf1, 5, 10, inbuf2, 3)
```
......@@ -3,9 +3,9 @@
| :----- | :-------------------- | :---------- | :------ |
| 2016-09-27 | [vsky279](https://github.com/vsky279) | [vsky279](https://github.com/vsky279) | [somfy.c](../../app/modules/somfy.c)|
This module provides a simple interface to control Somfy blinds via an RF transmitter (433.42 MHz). It is based on [Nickduino Somfy Remote Arduino skecth](https://github.com/Nickduino/Somfy_Remote).
This module provides a simple interface to control Somfy blinds via an RF transmitter (433.42 MHz). It is based on [Nickduino Somfy Remote Arduino skecth](https://github.com/Nickduino/Somfy_Remote). It also allows listening to commands transmitted by the Somfy remote control.
The hardware used is the standard 433 MHz RF transmitter. Unfortunately these chips are usually transmitting at he frequency of 433.92MHz so the crystal resonator should be replaced with the 433.42 MHz resonator though some reporting that it is working even with the original crystal.
The hardware used is the standard 433 MHz RF transmitter. Unfortunately these chips are usually transmitting at he frequency of 433.92MHz so the crystal resonator should be replaced with the resonator on this frequency though some reporting that it is working even with the original crystal.
To understand details of the Somfy protocol please refer to [Somfy RTS protocol](https://pushstack.wordpress.com/somfy-rts-protocol/) and also discussion [here](https://forum.arduino.cc/index.php?topic=208346.0).
......@@ -16,7 +16,7 @@ The module is using hardware timer so it cannot be used at the same time with ot
Builds an frame defined by Somfy protocol and sends it to the RF transmitter.
#### Syntax
`somfy.sendcommand(pin, remote_address, command, rolling_code, repeat_count, call_back)`
`somfy.sendcommand(pin, remote_address, command, rolling_code, repeat_count, callback)`
#### Parameters
- `pin` GPIO pin the RF transmitter is connected to.
......@@ -24,9 +24,9 @@ Builds an frame defined by Somfy protocol and sends it to the RF transmitter.
- `command` command to be transmitted. Can be one of `somfy.SOMFY_UP`, `somfy.SOMFY_DOWN`, `somfy.SOMFY_PROG`, `somfy.SOMFY_STOP`
- `rolling_code` The rolling code is increased every time a button is pressed. The receiver only accepts command if the rolling code is above the last received code and is not to far ahead of the last received code. This window is in the order of a 100 big. The rolling code needs to be stored in the EEPROM (i.e. filesystem) to survive the ESP8266 reset.
- `repeat_count` how many times the command is repeated
- `call_back` a function to be called after the command is transmitted. Allows chaining commands to set the blinds to a defined position.
- `callback` a function to be called after the command is transmitted. Allows chaining commands to set the blinds to a defined position.
My original remote is [TELIS 4 MODULIS RTS](https://www.somfy.co.uk/products/1810765/telis-4-modulis-rts). This remote is working with the additional info - additional 56 bits that follow data (shortening the Inter-frame gap). It seems that the scrambling algorithm has not been revealed yet.
My original remote is [TELIS 4 MODULIS RTS](https://www.somfy.co.uk/products/blinds-and-curtains/buy-products/controls). This remote is working with the additional info - additional 56 bits that follow data (shortening the Inter-frame gap). It seems that the scrambling algorithm has not been revealed yet.
When I send the `somfy.DOWN` command, repeating the frame twice (which seems to be the standard for a short button press), i.e. `repeat_count` equal to 2, the blinds go only 1 step down. This corresponds to the movement of the wheel on the original remote. The down button on the original remote sends also `somfy.DOWN` command but the additional info is different and this makes the blinds go full down. Fortunately it seems that repeating the frame 16 times makes the blinds go fully down.
......@@ -43,3 +43,32 @@ To start with controlling your Somfy blinds you need to:
- running `somfy.sendcommand(4, 123, somfy.DOWN, 2, 16)` - fully closes the blinds
For more elaborated example please refer to [`somfy.lua`](../../lua_examples/somfy.lua).
## somfy.listen()
Using RF receiver listens to Somfy commands and triggers a callback when command is identified.
#### Syntax
`somfy.listen(pin, callback)`
#### Parameters
- `pin` GPIO pin the RF receiver is connected to.
- `callback(address, command, rc, frame)` a function called when a Somfy command is identified. Use `nil` to stop listening.
- `address` of the remote controller sending the command
- `command` sent by the remote controller. A number between 0 and 0xf. Can be `somfy.SOMFY_UP`, `somfy.SOMFY_DOWN`, `somfy.SOMFY_PROG`, `somfy.SOMFY_STOP`.
- `rc` rolling code
- `frame` String of 10 characters with the full captured data frame.
#### Returns
nil
#### Example
```Lua
somfy.listen(4, function(address, command, rc, frame)
print(("Address:\t0x%x\nCommand:\t0x%x\nRolling code:\t%d"):format(address, command, rc))
print(("Frame:\t"..("%02x "):rep(#frame)):format(frame:byte(1, -1)))
end)
```
Use `somfy.listen()` or `somfy.listen(4, nil)` to unhook the GPIO and free the callback.
......@@ -15,7 +15,9 @@ Send data to a led strip using native chip format.
`tm1829.write(string)`
#### Parameters
- `string` payload to be sent to one or more TM1829 leds.
- `string` payload to be sent to one or more TM1829 leds. It is either
a 3-channel [pixbuf](pixbuf) (e.g., `pixbuf.TYPE_RGB`) or a string of
raw byte values to be sent.
#### Returns
`nil`
......
......@@ -66,7 +66,7 @@ All other pins can be assigned to any available GPIO:
* D/C
* RES (optional for some displays)
Also refer to the initialization sequence eg in [GraphicsTest.lua](https://github.com/nodemcu/nodemcu-firmware/blob/master/lua_examples/ucglib/GraphicsTest.lua):
Also refer to the initialization sequence eg in [GraphicsTest.lua](https://github.com/nodemcu/nodemcu-firmware/blob/release/lua_examples/ucglib/GraphicsTest.lua):
```lua
spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 8)
```
......@@ -83,7 +83,7 @@ disp = ucg.ili9341_18x240x320_hw_spi(cs, dc, res)
```
This object provides all of ucglib's methods to control the display.
Again, refer to [GraphicsTest.lua](https://github.com/nodemcu/nodemcu-firmware/blob/master/lua_examples/ucglib/GraphicsTest.lua) to get an impression how this is achieved with Lua code. Visit the [ucglib homepage](https://github.com/olikraus/ucglib) for technical details.
Again, refer to [GraphicsTest.lua](https://github.com/nodemcu/nodemcu-firmware/blob/release/lua_examples/ucglib/GraphicsTest.lua) to get an impression how this is achieved with Lua code. Visit the [ucglib homepage](https://github.com/olikraus/ucglib) for technical details.
### Display selection
HW SPI based displays with support in u8g2 can be enabled.
......
......@@ -1603,46 +1603,46 @@ T: Table returned by event.
#### Example
```lua
wifi.eventmon.register(wifi.eventmon.STA_CONNECTED, function(T)
print("\n\tSTA - CONNECTED".."\n\tSSID: "..T.SSID.."\n\tBSSID: "..
T.BSSID.."\n\tChannel: "..T.channel)
end)
wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, function(T)
print("\n\tSTA - DISCONNECTED".."\n\tSSID: "..T.SSID.."\n\tBSSID: "..
T.BSSID.."\n\treason: "..T.reason)
end)
wifi.eventmon.register(wifi.eventmon.STA_AUTHMODE_CHANGE, function(T)
print("\n\tSTA - AUTHMODE CHANGE".."\n\told_auth_mode: "..
T.old_auth_mode.."\n\tnew_auth_mode: "..T.new_auth_mode)
end)
wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function(T)
print("\n\tSTA - GOT IP".."\n\tStation IP: "..T.IP.."\n\tSubnet mask: "..
T.netmask.."\n\tGateway IP: "..T.gateway)
end)
wifi.eventmon.register(wifi.eventmon.STA_DHCP_TIMEOUT, function()
print("\n\tSTA - DHCP TIMEOUT")
end)
wifi.eventmon.register(wifi.eventmon.AP_STACONNECTED, function(T)
print("\n\tAP - STATION CONNECTED".."\n\tMAC: "..T.MAC.."\n\tAID: "..T.AID)
end)
wifi.eventmon.register(wifi.eventmon.AP_STADISCONNECTED, function(T)
print("\n\tAP - STATION DISCONNECTED".."\n\tMAC: "..T.MAC.."\n\tAID: "..T.AID)
end)
wifi.eventmon.register(wifi.eventmon.AP_PROBEREQRECVED, function(T)
print("\n\tAP - PROBE REQUEST RECEIVED".."\n\tMAC: ".. T.MAC.."\n\tRSSI: "..T.RSSI)
end)
wifi.eventmon.register(wifi.eventmon.WIFI_MODE_CHANGED, function(T)
print("\n\tSTA - WIFI MODE CHANGED".."\n\told_mode: "..
T.old_mode.."\n\tnew_mode: "..T.new_mode)
end)
wifi.eventmon.register(wifi.eventmon.STA_CONNECTED, function(T)
print("\n\tSTA - CONNECTED".."\n\tSSID: "..T.SSID.."\n\tBSSID: "..
T.BSSID.."\n\tChannel: "..T.channel)
end)
wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, function(T)
print("\n\tSTA - DISCONNECTED".."\n\tSSID: "..T.SSID.."\n\tBSSID: "..
T.BSSID.."\n\treason: "..T.reason)
end)
wifi.eventmon.register(wifi.eventmon.STA_AUTHMODE_CHANGE, function(T)
print("\n\tSTA - AUTHMODE CHANGE".."\n\told_auth_mode: "..
T.old_auth_mode.."\n\tnew_auth_mode: "..T.new_auth_mode)
end)
wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function(T)
print("\n\tSTA - GOT IP".."\n\tStation IP: "..T.IP.."\n\tSubnet mask: "..
T.netmask.."\n\tGateway IP: "..T.gateway)
end)
wifi.eventmon.register(wifi.eventmon.STA_DHCP_TIMEOUT, function()
print("\n\tSTA - DHCP TIMEOUT")
end)
wifi.eventmon.register(wifi.eventmon.AP_STACONNECTED, function(T)
print("\n\tAP - STATION CONNECTED".."\n\tMAC: "..T.MAC.."\n\tAID: "..T.AID)
end)
wifi.eventmon.register(wifi.eventmon.AP_STADISCONNECTED, function(T)
print("\n\tAP - STATION DISCONNECTED".."\n\tMAC: "..T.MAC.."\n\tAID: "..T.AID)
end)
wifi.eventmon.register(wifi.eventmon.AP_PROBEREQRECVED, function(T)
print("\n\tAP - PROBE REQUEST RECEIVED".."\n\tMAC: ".. T.MAC.."\n\tRSSI: "..T.RSSI)
end)
wifi.eventmon.register(wifi.eventmon.WIFI_MODE_CHANGED, function(T)
print("\n\tSTA - WIFI MODE CHANGED".."\n\told_mode: "..
T.old_mode.."\n\tnew_mode: "..T.new_mode)
end)
```
#### See also
- [`wifi.eventmon.unregister()`](#wifieventmonunregister)
......@@ -1655,7 +1655,7 @@ Unregister callbacks for WiFi event monitor.
wifi.eventmon.unregister(Event)
#### Parameters
Event: WiFi event you would like to set a callback for.
Event: WiFi event you would like to remove the callback for.
- Valid WiFi events:
- wifi.eventmon.STA_CONNECTED
......
......@@ -13,7 +13,7 @@ handle two led strips at the same time.
**WARNING**: In dual mode, you will loose access to the Lua's console
through the serial port (it will be reconfigured to support WS2812-like
protocol). If you want to keep access to Lua's console, you will have to
use an other input channel like a TCP server (see [example](https://github.com/nodemcu/nodemcu-firmware/blob/master/lua_examples/telnet/telnet.lua))
use an other input channel like a TCP server (see [example](https://github.com/nodemcu/nodemcu-firmware/blob/release/lua_modules/telnet/telnet.lua))
## ws2812.init()
Initialize UART1 and GPIO2, should be called once and before write().
......@@ -31,8 +31,20 @@ In `ws2812.MODE_DUAL` mode you will be able to handle two strips in parallel but
`nil`
## ws2812.write()
Send data to one or two led strip using its native format which is generally Green,Red,Blue for RGB strips
and Green,Red,Blue,White for RGBW strips.
Send data to one or two led strip using its native format, which is generally
Green, Red, Blue for RGB strips and Green, Red, Blue, White for RGBW strips.
(However, ws2812 drivers have been observed wired up in other orders.)
Because this function uses the hardware UART(s), it is able to return and allow
Lua to resume execution up to 300 microseconds before the data has finished
being sent. If you wish to perform actions synchronous with the end of the
data transmission, [`tmr.delay()`](../tmr#tmr.delay()) for 300 microseconds.
Separately, because this function returns early, back-to-back invocations may
not leave enough time for the strip to latch, and so may appear to the ws2812
drivers to be simply writes to a longer LED strip. Please ensure that you have
more than 350 microseconds between the return of `ws2812.write()` to your Lua
and the next invocation thereof.
#### Syntax
`ws2812.write(data1, [data2])`
......@@ -44,7 +56,7 @@ and Green,Red,Blue,White for RGBW strips.
Payload type could be:
- `nil` nothing is done
- `string` representing bytes to send
- `ws2812.buffer` see [Buffer module](#buffer-module)
- a [pixbuf](pixbuf) object containing the bytes to send. The pixbuf's type is not checked!
#### Returns
`nil`
......@@ -70,280 +82,31 @@ ws2812.init(ws2812.MODE_DUAL)
ws2812.write(nil, string.char(0, 255, 0, 0, 255, 0)) -- turn the two first RGB leds to red on the second strip, do nothing on the first
```
# Buffer module
# Pixbuf support
For more advanced animations, it is useful to keep a "framebuffer" of the strip,
interact with it and flush it to the strip.
For this purpose, the ws2812 library offers a read/write buffer. This buffer has a `__tostring` method so that it can be printed. This is useful for debugging.
For this purpose, the [pixbuf](pixbuf) library offers a read/write buffer and
convenient functions for pixel value manipulation.
For backwards-compatibility, `pixbuf.newBuffer()` is aliased as
`ws2812.newBuffer`, but this will be removed in the next nodemcu-firmware
release.
#### Example
Led chaser with a RGBW strip
```lua
ws2812.init()
local i, buffer = 0, ws2812.newBuffer(300, 4); buffer:fill(0, 0, 0, 0); tmr.create():alarm(50, 1, function()
i, buffer = 0, pixbuf.newBuffer(300, 4)
buffer:fill(0, 0, 0, 0)
tmr.create():alarm(50, 1, function()
i = i + 1
buffer:fade(2)
buffer:set(i % buffer:size() + 1, 0, 0, 0, 255)
ws2812.write(buffer)
end)
```
## ws2812.newBuffer()
Allocate a new memory buffer to store led values.
#### Syntax
`ws2812.newBuffer(numberOfLeds, bytesPerLed)`
#### Parameters
- `numberOfLeds` length of the led strip
- `bytesPerLed` 3 for RGB strips and 4 for RGBW strips
#### Returns
`ws2812.buffer`
## ws2812.buffer:get()
Return the value at the given position
#### Syntax
`buffer:get(index)`
#### Parameters
- `index` position in the buffer (1 for first led)
#### Returns
`(color)`
#### Example
```lua
buffer = ws2812.newBuffer(32, 4)
print(buffer:get(1))
0 0 0 0
```
## ws2812.buffer:set()
Set the value at the given position
#### Syntax
`buffer:set(index, color)`
#### Parameters
- `index` position in the buffer (1 for the first led)
- `color` payload of the color
Payload could be:
- `number, number, ...` you should pass as many arguments as `bytesPerLed`
- `table` should contains `bytesPerLed` numbers
- `string` should contains `bytesPerLed` bytes
#### Returns
`nil`
#### Example
```lua
buffer = ws2812.newBuffer(32, 3)
buffer:set(1, 255, 0, 0) -- set the first led green for a RGB strip
```
```lua
buffer = ws2812.newBuffer(32, 4)
buffer:set(1, {0, 0, 0, 255}) -- set the first led white for a RGBW strip
```
```lua
buffer = ws2812.newBuffer(32, 3)
buffer:set(1, string.char(255, 0, 0)) -- set the first led green for a RGB strip
```
## ws2812.buffer:size()
Return the size of the buffer in number of leds
#### Syntax
`buffer:size()`
#### Parameters
none
#### Returns
`int`
## ws2812.buffer:fill()
Fill the buffer with the given color.
The number of given bytes must match the number of bytesPerLed of the buffer
#### Syntax
`buffer:fill(color)`
#### Parameters
- `color` bytes of the color, you should pass as many arguments as `bytesPerLed`
#### Returns
`nil`
#### Example
```lua
buffer:fill(0, 0, 0) -- fill the buffer with black for a RGB strip
```
## ws2812.buffer:dump()
Returns the contents of the buffer (the pixel values) as a string. This can then be saved to a file or sent over a network.
#### Syntax
`buffer:dump()`
#### Returns
A string containing the pixel values.
#### Example
```lua
local s = buffer:dump()
```
## ws2812.buffer:replace()
Inserts a string (or a buffer) into another buffer with an offset.
The buffer must have the same number of colors per led or an error will be thrown.
#### Syntax
`buffer:replace(source[, offset])`
#### Parameters
- `source` the pixel values to be set into the buffer. This is either a string or a buffer.
- `offset` the offset where the source is to be placed in the buffer. Default is 1. Negative values can be used.
#### Returns
`nil`
#### Example
```lua
buffer:replace(anotherbuffer:dump()) -- copy one buffer into another via a string
buffer:replace(anotherbuffer) -- copy one buffer into another
newbuffer = buffer.sub(1) -- make a copy of a buffer into a new buffer
```
## ws2812.buffer:mix()
This is a general method that loads data into a buffer that is a linear combination of data from other buffers. It can be used to copy a buffer or,
more usefully, do a cross fade. The pixel values are computed as integers and then range limited to [0, 255]. This means that negative
factors work as expected, and that the order of combining buffers does not matter.
#### Syntax
`buffer:mix(factor1, buffer1, ...)`
#### Parameters
- `factor1` This is the factor that the contents of `buffer1` are multiplied by. This factor is scaled by a factor of 256. Thus `factor1` value of 256 is a factor of 1.0.
- `buffer1` This is the source buffer. It must be of the same shape as the destination buffer.
There can be any number of factor/buffer pairs.
#### Returns
`nil`
#### Example
```lua
-- loads buffer with a crossfade between buffer1 and buffer2
buffer:mix(256 - crossmix, buffer1, crossmix, buffer2)
-- multiplies all values in buffer by 0.75
-- This can be used in place of buffer:fade
buffer:mix(192, buffer)
```
## ws2812.buffer:power()
Computes the total energy requirement for the buffer. This is merely the total sum of all the pixel values (which assumes that each color in each
pixel consumes the same amount of power). A real WS2812 (or WS2811) has three constant current drivers of 20mA -- one for each of R, G and B. The
pulse width modulation will cause the *average* current to scale linearly with pixel value.
#### Syntax
`buffer:power()`
#### Returns
An integer which is the sum of all the pixel values.
#### Example
```lua
-- Dim the buffer to no more than the PSU can provide
local psu_current_ma = 1000
local led_current_ma = 20
local led_sum = psu_current_ma * 255 / led_current_ma
local p = buffer:power()
if p > led_sum then
buffer:mix(256 * led_sum / p, buffer) -- power is now limited
end
```
## ws2812.buffer:fade()
Fade in or out. Defaults to out. Multiply or divide each byte of each led with/by the given value. Useful for a fading effect.
#### Syntax
`buffer:fade(value [, direction])`
#### Parameters
- `value` value by which to divide or multiply each byte
- `direction` ws2812.FADE\_IN or ws2812.FADE\_OUT. Defaults to ws2812.FADE\_OUT
#### Returns
`nil`
#### Example
```lua
buffer:fade(2)
buffer:fade(2, ws2812.FADE_IN)
```
## ws2812.buffer:shift()
Shift the content of (a piece of) the buffer in positive or negative direction. This allows simple animation effects. A slice of the buffer can be specified by using the
standard start and end offset Lua notation. Negative values count backwards from the end of the buffer.
#### Syntax
`buffer:shift(value [, mode[, i[, j]]])`
#### Parameters
- `value` number of pixels by which to rotate the buffer. Positive values rotate forwards, negative values backwards.
- `mode` is the shift mode to use. Can be one of `ws2812.SHIFT_LOGICAL` or `ws2812.SHIFT_CIRCULAR`. In case of SHIFT\_LOGICAL, the freed pixels are set to 0 (off). In case of SHIFT\_CIRCULAR, the buffer is treated like a ring buffer, inserting the pixels falling out on one end again on the other end. Defaults to SHIFT\_LOGICAL.
- `i` is the first offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is 1.
- `j` is the last offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is -1.
#### Returns
`nil`
#### Example
```lua
buffer:shift(3)
```
## ws2812.buffer:sub()
This implements the extraction function like `string.sub`. The indexes are in leds and all the same rules apply.
#### Syntax
`buffer1:sub(i[, j])`
#### Parameters
- `i` This is the start of the extracted data. Negative values can be used.
- `j` this is the end of the extracted data. Negative values can be used. The default is -1.
#### Returns
A buffer containing the extracted piece.
#### Example
```
b = buffer:sub(1,10)
```
## ws2812.buffer:__concat()
This implements the `..` operator to concatenate two buffers. They must have the same number of colors per led.
#### Syntax
`buffer1 .. buffer2`
#### Parameters
- `buffer1` this is the start of the resulting buffer
- `buffer2` this is the end of the resulting buffer
#### Returns
The concatenated buffer.
#### Example
```
ws2812.write(buffer1 .. buffer2)
```
-- A collection of pipe-based utility functions
-- A convenience wrapper for chunking data arriving in bursts into more sizable
-- blocks; `o` will be called once per chunk. The `flush` method can be used to
-- drain the internal buffer. `flush` MUST be called at the end of the stream,
-- **even if the stream is a multiple of the chunk size** due to internal
-- buffering. Flushing results in smaller chunk(s) being output, of course.
local function chunker(o, csize, prio)
assert (type(o) == "function" and type(csize) == "number" and 1 <= csize)
local p = pipe.create(function(p)
-- wait until it looks very likely that read is going to succeed
-- and we won't have to unread. This may hold slightly more than
-- a chunk in the underlying pipe object.
if 256 * (p:nrec() - 1) <= csize then return nil end
local d = p:read(csize)
if #d < csize
then p:unread(d) return false
else o(d) return true
end
end, prio or node.task.LOW_PRIORITY)
return {
flush = function() for d in p:reader(csize) do o(d) end end,
write = function(d) p:write(d) end
}
end
-- Stream and decode lines of complete base64 blocks, calling `o(data)` with
-- decoded chunks or calling `e(badinput, errorstr)` on error; the error
-- callback must ensure that this conduit is never written to again.
local function debase64(o, e, prio)
assert (type(o) == "function" and type(e) == "function")
local p = pipe.create(function(p)
local s = p:read("\n+")
if s:sub(-1) == "\n" then -- guard against incomplete line
s = s:match("^%s*(%S*)%s*$")
if #s ~= 0 then -- guard against empty line
local ok, d = pcall(encoder.fromBase64, s)
if ok then o(d) else e(s, d); return false end
end
return true
else
p:unread(s)
return false
end
end, prio or node.task.LOW_PRIORITY)
return { write = function(d) p:write(d) end }
end
return {
chunker = chunker,
debase64 = debase64,
}
......@@ -130,10 +130,10 @@ function bme280_startreadout(self, callback, delay, alt)
delay = delay or BME280_SAMPLING_DELAY
if self._isbme then write_reg(self.id, self.addr, BME280_REGISTER_CONTROL_HUM, self._config[2]) end
write_reg(self.id, self.addr, BME280_REGISTER_CONTROL, math_floor(self._config[3]:byte(1)/4)+ 1)
-- math_floor(self._config[3]:byte(1)/4)+ 1
write_reg(self.id, self.addr, BME280_REGISTER_CONTROL, 4*math_floor(self._config[3]:byte(1)/4)+ 1) -- LUA51
-- 4*math_floor(self._config[3]:byte(1)/4)+ 1
-- an awful way to avoid bit operations but calculate (config[3] & 0xFC) | BME280_FORCED_MODE
-- Lua 5.3 integer division // would be more suitable
-- Lua 5.3: write_reg(self.id, self.addr, BME280_REGISTER_CONTROL, (self._config[3]:byte(1) & 0xFC) | 1)
tmr_create():alarm(delay, tmr_ALARM_SINGLE,
function()
......
--------------------------------------------------------------------------------
-- DS18B20 one wire module for NODEMCU
-- NODEMCU TEAM
-- LICENCE: http://opensource.org/licenses/MIT
-- @voborsky, @devsaurus, TerryE 26 Mar 2017
--------------------------------------------------------------------------------
local modname = ...
-- Used modules and functions
local type, tostring, pcall, ipairs =
type, tostring, pcall, ipairs
-- Local functions
local ow_setup, ow_search, ow_select, ow_read, ow_read_bytes, ow_write, ow_crc8,
ow_reset, ow_reset_search, ow_skip, ow_depower =
ow.setup, ow.search, ow.select, ow.read, ow.read_bytes, ow.write, ow.crc8,
ow.reset, ow.reset_search, ow.skip, ow.depower
local node_task_post, node_task_LOW_PRIORITY = node.task.post, node.task.LOW_PRIORITY
local string_char, string_dump = string.char, string.dump
local now, tmr_create, tmr_ALARM_SINGLE = tmr.now, tmr.create, tmr.ALARM_SINGLE
local table_sort, table_concat = table.sort, table.concat
local file_open = file.open
local conversion
local DS18B20FAMILY = 0x28
local DS1920FAMILY = 0x10 -- and DS18S20 series
local CONVERT_T = 0x44
local READ_SCRATCHPAD = 0xBE
local READ_POWERSUPPLY= 0xB4
local MODE = 1
local pin, cb, unit = 3
local status = {}
local debugPrint = function() return end
--------------------------------------------------------------------------------
-- Implementation
--------------------------------------------------------------------------------
local function enable_debug()
debugPrint = function (...) print(now(),' ', ...) end
end
local function to_string(addr, esc)
if type(addr) == 'string' and #addr == 8 then
return ( esc == true and
'"\\%u\\%u\\%u\\%u\\%u\\%u\\%u\\%u"' or
'%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X '):format(addr:byte(1,8))
else
return tostring(addr)
end
end
local function readout(self)
local next = false
local sens = self.sens
local temp = self.temp
for i, s in ipairs(sens) do
if status[i] == 1 then
ow_reset(pin)
local addr = s:sub(1,8)
ow_select(pin, addr) -- select the sensor
ow_write(pin, READ_SCRATCHPAD, MODE)
local data = ow_read_bytes(pin, 9)
local t=(data:byte(1)+data:byte(2)*256)
-- t is actually signed so process the sign bit and adjust for fractional bits
-- the DS18B20 family has 4 fractional bits and the DS18S20s, 1 fractional bit
t = ((t <= 32767) and t or t - 65536) *
((addr:byte(1) == DS18B20FAMILY) and 625 or 5000)
local crc, b9 = ow_crc8(string.sub(data,1,8)), data:byte(9)
if unit == 'F' then
t = (t * 18)/10 + 320000
elseif unit == 'K' then
t = t + 2731500
end
local sgn = t<0 and -1 or 1
local tA = sgn*t
local tH=tA/10000
local tL=(tA%10000)/1000 + ((tA%1000)/100 >= 5 and 1 or 0)
if tH and (t~=850000) then
debugPrint(to_string(addr),(sgn<0 and "-" or "")..tH.."."..tL, crc, b9)
if crc==b9 then temp[addr]=t end
status[i] = 2
end
end
next = next or status[i] == 0
end
if next then
node_task_post(node_task_LOW_PRIORITY, function() return conversion(self) end)
else
--sens = {}
if cb then
node_task_post(node_task_LOW_PRIORITY, function() return cb(temp) end)
end
end
end
conversion = (function (self)
local sens = self.sens
local powered_only = true
for _, s in ipairs(sens) do powered_only = powered_only and s:byte(9) ~= 1 end
if powered_only then
debugPrint("starting conversion: all sensors")
ow_reset(pin)
ow_skip(pin) -- skip ROM selection, talk to all sensors
ow_write(pin, CONVERT_T, MODE) -- and start conversion
for i, _ in ipairs(sens) do status[i] = 1 end
else
local started = false
for i, s in ipairs(sens) do
if status[i] == 0 then
local addr, parasite = s:sub(1,8), s:byte(9) == 1
if parasite and started then break end -- do not start concurrent conversion of powered and parasite
debugPrint("starting conversion:", to_string(addr), parasite and "parasite" or "")
ow_reset(pin)
ow_select(pin, addr) -- select the sensor
ow_write(pin, CONVERT_T, MODE) -- and start conversion
status[i] = 1
if parasite then break end -- parasite sensor blocks bus during conversion
started = true
end
end
end
tmr_create():alarm(750, tmr_ALARM_SINGLE, function() return readout(self) end)
end)
local function _search(self, lcb, lpin, search, save)
self.temp = {}
if search then self.sens = {}; status = {} end
local sens = self.sens
pin = lpin or pin
local addr
if not search and #sens == 0 then
-- load addreses if available
debugPrint ("geting addreses from flash")
local s,check,a = pcall(dofile, "ds18b20_save.lc")
if s and check == "ds18b20" then
for i = 1, #a do sens[i] = a[i] end
end
debugPrint (#sens, "addreses found")
end
ow_setup(pin)
if search or #sens == 0 then
ow_reset_search(pin)
-- ow_target_search(pin,0x28)
-- search the first device
addr = ow_search(pin)
else
for i, _ in ipairs(sens) do status[i] = 0 end
end
local function cycle()
if addr then
local crc=ow_crc8(addr:sub(1,7))
if (crc==addr:byte(8)) and ((addr:byte(1)==DS1920FAMILY) or (addr:byte(1)==DS18B20FAMILY)) then
ow_reset(pin)
ow_select(pin, addr)
ow_write(pin, READ_POWERSUPPLY, MODE)
local parasite = (ow_read(pin)==0 and 1 or 0)
sens[#sens+1]= addr..string_char(parasite)
status[#sens] = 0
debugPrint("contact: ", to_string(addr), parasite == 1 and "parasite" or "")
end
addr = ow_search(pin)
node_task_post(node_task_LOW_PRIORITY, cycle)
else
ow_depower(pin)
-- place powered sensors first
table_sort(sens, function(a, b) return a:byte(9)<b:byte(9) end) -- parasite
-- save sensor addreses
if save then
debugPrint ("saving addreses to flash")
local addr_list = {}
for i =1, #sens do
local s = sens[i]
addr_list[i] = to_string(s:sub(1,8), true)..('.."\\%u"'):format(s:byte(9))
end
local save_statement = 'return "ds18b20", {' .. table_concat(addr_list, ',') .. '}'
debugPrint (save_statement)
local save_file = file_open("ds18b20_save.lc","w")
save_file:write(string_dump(loadstring(save_statement)))
save_file:close()
end
-- end save sensor addreses
if lcb then node_task_post(node_task_LOW_PRIORITY, lcb) end
end
end
cycle()
end
local function read_temp(self, lcb, lpin, lunit, force_search, save_search)
cb, unit = lcb, lunit or unit
_search(self, function() return conversion(self) end, lpin, force_search, save_search)
end
-- Set module name as parameter of require and return module table
local M = {
sens = {},
temp = {},
C = 'C', F = 'F', K = 'K',
read_temp = read_temp, enable_debug = enable_debug
}
_G[modname or 'ds18b20'] = M
return M
......@@ -71,38 +71,16 @@ local function readout(self)
((addr:byte(1) == DS18B20FAMILY) and 625 or 5000)
local crc, b9 = ow_crc8(string.sub(data,1,8)), data:byte(9)
if 1/2 == 0 then
-- integer version
t = t / 10000
if math_floor(t)~=85 then
if unit == 'F' then
t = (t * 18)/10 + 320000
t = t * 18/10 + 32
elseif unit == 'K' then
t = t + 2731500
t = t + 27315/100
end
local sgn = t<0 and -1 or 1
local tA = sgn*t
local tH=tA/10000
local tL=(tA%10000)/1000 + ((tA%1000)/100 >= 5 and 1 or 0)
if tH and (t~=850000) then
debugPrint(to_string(addr),(sgn<0 and "-" or "")..tH.."."..tL, crc, b9)
if crc==b9 then temp[addr]=(sgn<0 and "-" or "")..tH.."."..tL end
status[i] = 2
end
-- end integer version
else
-- float version
t = t / 10000
if math_floor(t)~=85 then
if unit == 'F' then
t = t * 18/10 + 32
elseif unit == 'K' then
t = t + 27315/100
end
debugPrint(to_string(addr), t, crc, b9)
if crc==b9 then temp[addr]=t end
status[i] = 2
end
-- end float version
debugPrint(to_string(addr), t, crc, b9)
if crc==b9 then temp[addr]=t end
status[i] = 2
end
end
next = next or status[i] == 0
......
......@@ -86,19 +86,22 @@ do
local buf = ""
local method, url
local ondisconnect = function(connection)
connection:on("receive", nil)
connection:on("disconnection", nil)
connection:on("sent", nil)
collectgarbage("collect")
end
local cfini = function()
conn:on("receive", nil)
conn:on("disconnection", nil)
csend(function()
conn:on("sent", nil)
conn:close()
ondisconnect(conn)
end)
end
local ondisconnect = function(connection)
connection:on("sent", nil)
collectgarbage("collect")
end
-- header parser
local cnt_len = 0
......
......@@ -22,27 +22,27 @@ return function(bus_args)
-- The onus is on us to maintain the backlight state
local backlight = true
local function send4bitI2C(value, rs_en, rw_en, read)
local function exchange(data, unset_read)
local rv = data
local function exchange(data, read)
local rv = data
i2c.start(busid)
i2c.address(busid, busad, i2c.TRANSMITTER)
i2c.write(busid, bit.set(data, en)) -- set data with en
if read then
i2c.start(busid) -- read 1 byte and go back to tx mode
i2c.address(busid, busad, i2c.RECEIVER)
rv = i2c.read(busid, 1):byte(1)
i2c.start(busid)
i2c.address(busid, busad, i2c.TRANSMITTER)
i2c.write(busid, bit.set(data, en))
if read then
i2c.start(busid)
i2c.address(busid, busad, i2c.RECEIVER)
rv = i2c.read(busid, 1):byte(1)
i2c.start(busid)
i2c.address(busid, busad, i2c.TRANSMITTER)
if unset_read then data = bit.bor(bit.bit(rs),
bit.bit(rw),
backlight and bit.bit(bl) or 0) end
i2c.write(busid, bit.set(data, en))
end
i2c.write(busid, bit.clear(data, en))
i2c.stop(busid)
return rv
end
i2c.write(busid, data) -- lower en
i2c.stop(busid)
return rv
end
local function send4bitI2C(value, rs_en, rw_en)
local meta = bit.bor(rs_en and bit.bit(rs) or 0,
rw_en and bit.bit(rw) or 0,
backlight and bit.bit(bl) or 0)
local lo = bit.bor(bit.isset(value, 0) and bit.bit(d4) or 0,
bit.isset(value, 1) and bit.bit(d5) or 0,
bit.isset(value, 2) and bit.bit(d6) or 0,
......@@ -51,11 +51,8 @@ return function(bus_args)
bit.isset(value, 5) and bit.bit(d5) or 0,
bit.isset(value, 6) and bit.bit(d6) or 0,
bit.isset(value, 7) and bit.bit(d7) or 0)
local cmd = bit.bor(rs_en and bit.bit(rs) or 0,
rw_en and bit.bit(rw) or 0,
backlight and bit.bit(bl) or 0)
hi = exchange(bit.bor(cmd, hi), false)
lo = exchange(bit.bor(cmd, lo), true)
hi = exchange(bit.bor(meta, hi), rw_en)
lo = exchange(bit.bor(meta, lo), rw_en)
return bit.bor(bit.lshift(bit.isset(lo, d4) and 1 or 0, 0),
bit.lshift(bit.isset(lo, d5) and 1 or 0, 1),
bit.lshift(bit.isset(lo, d6) and 1 or 0, 2),
......@@ -66,36 +63,45 @@ return function(bus_args)
bit.lshift(bit.isset(hi, d7) and 1 or 0, 7))
end
-- init sequence from datasheet
send4bitI2C(0x33, false, false, false)
send4bitI2C(0x32, false, false, false)
-- init sequence from datasheet (Figure 24)
local function justsend(what)
i2c.start(busid)
i2c.address(busid, busad, i2c.TRANSMITTER)
i2c.write(busid, bit.set(what, en))
i2c.write(busid, what)
i2c.stop(busid)
end
local three = bit.bor(bit.bit(d4), bit.bit(d5))
justsend(three)
tmr.delay(5)
justsend(three)
tmr.delay(1)
justsend(three)
tmr.delay(1)
justsend(bit.bit(d5))
-- we are now primed for the FUNCTIONSET command from the liquidcrystal ctor
-- Return backend object
return {
fourbits = true,
command = function (_, cmd)
return send4bitI2C(cmd, false, false, false)
return send4bitI2C(cmd, false, false)
end,
busy = function(_)
local rv = send4bitI2C(0xff, false, true, true)
send4bitI2C(bit.bor(0x80, bit.clear(rv, 7)), false, false, false)
return bit.isset(rv, 7)
return bit.isset(send4bitI2C(0xff, false, true), 7)
end,
position = function(_)
local rv = bit.clear(send4bitI2C(0xff, false, true, true), 7)
send4bitI2C(bit.bor(0x80, rv), false, false, false)
return rv
return bit.clear(send4bitI2C(0xff, false, true), 7)
end,
write = function(_, value)
return send4bitI2C(value, true, false, false)
return send4bitI2C(value, true, false)
end,
read = function(_)
return send4bitI2C(0xff, true, true, true)
return send4bitI2C(0xff, true, true)
end,
backlight = function(_, on)
backlight = on
local rv = bit.clear(send4bitI2C(0xff, false, true, true), 7)
send4bitI2C(bit.bor(0x80, rv), false, false, false)
send4bitI2C(0, false, false) -- No-op
return on
end,
}
......
local moduleName = ... or 'mispec'
local M = {}
_G[moduleName] = M
-- Helpers:
function ok(expression, desc)
if expression == nil then expression = false end
desc = desc or 'expression is not ok'
if not expression then
error(desc .. '\n' .. debug.traceback())
end
end
function ko(expression, desc)
if expression == nil then expression = false end
desc = desc or 'expression is not ko'
if expression then
error(desc .. '\n' .. debug.traceback())
end
end
function eq(a, b)
if type(a) ~= type(b) then
error('type ' .. type(a) .. ' is not equal to ' .. type(b) .. '\n' .. debug.traceback())
end
if type(a) == 'function' then
return string.dump(a) == string.dump(b)
end
if a == b then return true end
if type(a) ~= 'table' then
error(string.format("%q",tostring(a)) .. ' is not equal to ' .. string.format("%q",tostring(b)) .. '\n' .. debug.traceback())
end
for k,v in pairs(a) do
if b[k] == nil or not eq(v, b[k]) then return false end
end
for k,v in pairs(b) do
if a[k] == nil or not eq(v, a[k]) then return false end
end
return true
end
function failwith(message, func, ...)
local status, err = pcall(func, ...)
if status then
local messagePart = ""
if message then
messagePart = " containing \"" .. message .. "\""
end
error("Error expected" .. messagePart .. '\n' .. debug.traceback())
end
if (message and not string.find(err, message)) then
error("expected errormessage \"" .. err .. "\" to contain \"" .. message .. "\"" .. '\n' .. debug.traceback() )
end
return true
end
function fail(func, ...)
return failwith(nil, func, ...)
end
local function eventuallyImpl(func, retries, delayMs)
local prevEventually = _G.eventually
_G.eventually = function() error("Cannot nest eventually/andThen.") end
local status, err = pcall(func)
_G.eventually = prevEventually
if status then
M.queuedEventuallyCount = M.queuedEventuallyCount - 1
M.runNextPending()
else
if retries > 0 then
local t = tmr.create()
t:register(delayMs, tmr.ALARM_SINGLE, M.runNextPending)
t:start()
table.insert(M.pending, 1, function() eventuallyImpl(func, retries - 1, delayMs) end)
else
M.failed = M.failed + 1
print("\n ! it failed:", err)
-- remove all pending eventuallies as spec has failed at this point
for i = 1, M.queuedEventuallyCount - 1 do
table.remove(M.pending, 1)
end
M.queuedEventuallyCount = 0
M.runNextPending()
end
end
end
function eventually(func, retries, delayMs)
retries = retries or 10
delayMs = delayMs or 300
M.queuedEventuallyCount = M.queuedEventuallyCount + 1
table.insert(M.pending, M.queuedEventuallyCount, function()
eventuallyImpl(func, retries, delayMs)
end)
end
function andThen(func)
eventually(func, 0, 0)
end
function describe(name, itshoulds)
M.name = name
M.itshoulds = itshoulds
end
-- Module:
M.runNextPending = function()
local next = table.remove(M.pending, 1)
if next then
node.task.post(next)
next = nil
else
M.succeeded = M.total - M.failed
local elapsedSeconds = (tmr.now() - M.startTime) / 1000 / 1000
print(string.format(
'\n\nCompleted in %d seconds; %d failed out of %d.',
elapsedSeconds, M.failed, M.total))
M.pending = nil
M.queuedEventuallyCount = nil
end
end
M.run = function()
M.pending = {}
M.queuedEventuallyCount = 0
M.startTime = tmr.now()
M.total = 0
M.failed = 0
local it = {}
it.should = function(_, desc, func)
table.insert(M.pending, function()
print('\n * ' .. desc)
M.total = M.total + 1
if M.pre then M.pre() end
local status, err = pcall(func)
if not status then
print("\n ! it failed:", err)
M.failed = M.failed + 1
end
if M.post then M.post() end
M.runNextPending()
end)
end
it.initialize = function(_, pre) M.pre = pre end;
it.cleanup = function(_, post) M.post = post end;
M.itshoulds(it)
print('' .. M.name .. ', it should:')
M.runNextPending()
M.itshoulds = nil
M.name = nil
end
print ("loaded mispec")
require 'mispec'
local buffer, buffer1, buffer2
local function initBuffer(buffer, ...)
local i,v
for i,v in ipairs({...}) do
buffer:set(i, v, v*2, v*3, v*4)
end
return buffer
end
local function equalsBuffer(buffer1, buffer2)
return eq(buffer1:dump(), buffer2:dump())
end
describe('WS2812 buffers', function(it)
it:should('initialize a buffer', function()
buffer = ws2812.newBuffer(9, 3)
ko(buffer == nil)
ok(eq(buffer:size(), 9), "check size")
ok(eq(buffer:dump(), string.char(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)), "initialize with 0")
failwith("should be a positive integer", ws2812.newBuffer, 9, 0)
failwith("should be a positive integer", ws2812.newBuffer, 9, -1)
failwith("should be a positive integer", ws2812.newBuffer, 0, 3)
failwith("should be a positive integer", ws2812.newBuffer, -1, 3)
end)
it:should('have correct size', function()
buffer = ws2812.newBuffer(9, 3)
ok(eq(buffer:size(), 9), "check size")
buffer = ws2812.newBuffer(9, 22)
ok(eq(buffer:size(), 9), "check size")
buffer = ws2812.newBuffer(13, 1)
ok(eq(buffer:size(), 13), "check size")
end)
it:should('fill a buffer with one color', function()
buffer = ws2812.newBuffer(3, 3)
buffer:fill(1,222,55)
ok(eq(buffer:dump(), string.char(1,222,55,1,222,55,1,222,55)), "RGB")
buffer = ws2812.newBuffer(3, 4)
buffer:fill(1,222,55, 77)
ok(eq(buffer:dump(), string.char(1,222,55,77,1,222,55,77,1,222,55,77)), "RGBW")
end)
it:should('replace correctly', function()
buffer = ws2812.newBuffer(5, 3)
buffer:replace(string.char(3,255,165,33,0,244,12,87,255))
ok(eq(buffer:dump(), string.char(3,255,165,33,0,244,12,87,255,0,0,0,0,0,0)), "RGBW")
buffer = ws2812.newBuffer(5, 3)
buffer:replace(string.char(3,255,165,33,0,244,12,87,255), 2)
ok(eq(buffer:dump(), string.char(0,0,0,3,255,165,33,0,244,12,87,255,0,0,0)), "RGBW")
buffer = ws2812.newBuffer(5, 3)
buffer:replace(string.char(3,255,165,33,0,244,12,87,255), -5)
ok(eq(buffer:dump(), string.char(3,255,165,33,0,244,12,87,255,0,0,0,0,0,0)), "RGBW")
failwith("Does not fit into destination", function() buffer:replace(string.char(3,255,165,33,0,244,12,87,255), 4) end)
end)
it:should('replace correctly issue #2921', function()
local buffer = ws2812.newBuffer(5, 3)
buffer:replace(string.char(3,255,165,33,0,244,12,87,255), -7)
ok(eq(buffer:dump(), string.char(3,255,165,33,0,244,12,87,255,0,0,0,0,0,0)), "RGBW")
end)
it:should('get/set correctly', function()
buffer = ws2812.newBuffer(3, 4)
buffer:fill(1,222,55,13)
ok(eq({buffer:get(2)},{1,222,55,13}))
buffer:set(2, 4,53,99,0)
ok(eq({buffer:get(1)},{1,222,55,13}))
ok(eq({buffer:get(2)},{4,53,99,0}))
ok(eq(buffer:dump(), string.char(1,222,55,13,4,53,99,0,1,222,55,13)), "RGBW")
failwith("index out of range", function() buffer:get(0) end)
failwith("index out of range", function() buffer:get(4) end)
failwith("index out of range", function() buffer:set(0,1,2,3,4) end)
failwith("index out of range", function() buffer:set(4,1,2,3,4) end)
failwith("number expected, got no value", function() buffer:set(2,1,2,3) end)
-- failwith("extra values given", function() buffer:set(2,1,2,3,4,5) end)
end)
it:should('fade correctly', function()
buffer = ws2812.newBuffer(1, 3)
buffer:fill(1,222,55)
buffer:fade(2)
ok(buffer:dump() == string.char(0,111,27), "RGB")
buffer:fill(1,222,55)
buffer:fade(3, ws2812.FADE_OUT)
ok(buffer:dump() == string.char(0,222/3,55/3), "RGB")
buffer:fill(1,222,55)
buffer:fade(3, ws2812.FADE_IN)
ok(buffer:dump() == string.char(3,255,165), "RGB")
buffer = ws2812.newBuffer(1, 4)
buffer:fill(1,222,55, 77)
buffer:fade(2, ws2812.FADE_OUT)
ok(eq(buffer:dump(), string.char(0,111,27,38)), "RGBW")
end)
it:should('mix correctly issue #1736', function()
buffer1 = ws2812.newBuffer(1, 3)
buffer2 = ws2812.newBuffer(1, 3)
buffer1:fill(10,22,54)
buffer2:fill(10,27,55)
buffer1:mix(256/8*7,buffer1,256/8,buffer2)
ok(eq({buffer1:get(1)}, {10,23,54}))
end)
it:should('mix saturation correctly ', function()
buffer1 = ws2812.newBuffer(1, 3)
buffer2 = ws2812.newBuffer(1, 3)
buffer1:fill(10,22,54)
buffer2:fill(10,27,55)
buffer1:mix(256/2,buffer1,-256,buffer2)
ok(eq({buffer1:get(1)}, {0,0,0}))
buffer1:fill(10,22,54)
buffer2:fill(10,27,55)
buffer1:mix(25600,buffer1,256/8,buffer2)
ok(eq({buffer1:get(1)}, {255,255,255}))
buffer1:fill(10,22,54)
buffer2:fill(10,27,55)
buffer1:mix(-257,buffer1,255,buffer2)
ok(eq({buffer1:get(1)}, {0,5,1}))
end)
it:should('mix with strings correctly ', function()
buffer1 = ws2812.newBuffer(1, 3)
buffer2 = ws2812.newBuffer(1, 3)
buffer1:fill(10,22,54)
buffer2:fill(10,27,55)
buffer1:mix(-257,buffer1:dump(),255,buffer2:dump())
ok(eq({buffer1:get(1)}, {0,5,1}))
end)
it:should('power', function()
buffer = ws2812.newBuffer(2, 4)
buffer:fill(10,22,54,234)
ok(eq(buffer:power(), 2*(10+22+54+234)))
end)
end)
mispec.run()
require 'mispec'
local buffer, buffer1, buffer2
local function initBuffer(buffer, ...)
local i,v
for i,v in ipairs({...}) do
buffer:set(i, v, v*2, v*3, v*4)
end
return buffer
end
local function equalsBuffer(buffer1, buffer2)
return eq(buffer1:dump(), buffer2:dump())
end
describe('WS2812 buffers', function(it)
it:should('shift LOGICAL', function()
buffer1 = ws2812.newBuffer(4, 4)
buffer2 = ws2812.newBuffer(4, 4)
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,0,0,7,8)
buffer1:shift(2)
ok(equalsBuffer(buffer1, buffer2), "shift right")
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,9,12,0,0)
buffer1:shift(-2)
ok(equalsBuffer(buffer1, buffer2), "shift left")
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,7,0,8,12)
buffer1:shift(1, nil, 2,3)
ok(equalsBuffer(buffer1, buffer2), "shift middle right")
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,7,9,0,12)
buffer1:shift(-1, nil, 2,3)
ok(equalsBuffer(buffer1, buffer2), "shift middle left")
-- bounds checks, handle gracefully as string:sub does
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,8,9,12,0)
buffer1:shift(-1, ws2812.SHIFT_LOGICAL, 0,5)
ok(equalsBuffer(buffer1, buffer2), "shift left out of bound")
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,0,7,8,9)
buffer1:shift(1, ws2812.SHIFT_LOGICAL, 0,5)
ok(equalsBuffer(buffer1, buffer2), "shift right out of bound")
end)
it:should('shift LOGICAL issue #2946', function()
buffer1 = ws2812.newBuffer(4, 4)
buffer2 = ws2812.newBuffer(4, 4)
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,0,0,0,0)
buffer1:shift(4)
ok(equalsBuffer(buffer1, buffer2), "shift all right")
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,0,0,0,0)
buffer1:shift(-4)
ok(equalsBuffer(buffer1, buffer2), "shift all left")
failwith("shifting more elements than buffer size", function() buffer1:shift(10) end)
failwith("shifting more elements than buffer size", function() buffer1:shift(-6) end)
end)
it:should('shift CIRCULAR', function()
buffer1 = ws2812.newBuffer(4, 4)
buffer2 = ws2812.newBuffer(4, 4)
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,9,12,7,8)
buffer1:shift(2, ws2812.SHIFT_CIRCULAR)
ok(equalsBuffer(buffer1, buffer2), "shift right")
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,9,12,7,8)
buffer1:shift(-2, ws2812.SHIFT_CIRCULAR)
ok(equalsBuffer(buffer1, buffer2), "shift left")
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,7,9,8,12)
buffer1:shift(1, ws2812.SHIFT_CIRCULAR, 2,3)
ok(equalsBuffer(buffer1, buffer2), "shift middle right")
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,7,9,8,12)
buffer1:shift(-1, ws2812.SHIFT_CIRCULAR, 2,3)
ok(equalsBuffer(buffer1, buffer2), "shift middle left")
-- bounds checks, handle gracefully as string:sub does
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,8,9,12,7)
buffer1:shift(-1, ws2812.SHIFT_CIRCULAR, 0,5)
ok(equalsBuffer(buffer1, buffer2), "shift left out of bound")
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,12,7,8,9)
buffer1:shift(1, ws2812.SHIFT_CIRCULAR, 0,5)
ok(equalsBuffer(buffer1, buffer2), "shift right out of bound")
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,12,7,8,9)
buffer1:shift(1, ws2812.SHIFT_CIRCULAR, -12,12)
ok(equalsBuffer(buffer1, buffer2), "shift right way out of bound")
end)
it:should('sub', function()
buffer1 = ws2812.newBuffer(4, 4)
buffer2 = ws2812.newBuffer(4, 4)
initBuffer(buffer1,7,8,9,12)
buffer1 = buffer1:sub(4,3)
ok(eq(buffer1:size(), 0), "sub empty")
buffer1 = ws2812.newBuffer(4, 4)
buffer2 = ws2812.newBuffer(2, 4)
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,9,12)
buffer1 = buffer1:sub(3,4)
ok(equalsBuffer(buffer1, buffer2), "sub")
buffer1 = ws2812.newBuffer(4, 4)
buffer2 = ws2812.newBuffer(4, 4)
initBuffer(buffer1,7,8,9,12)
initBuffer(buffer2,7,8,9,12)
buffer1 = buffer1:sub(-12,33)
ok(equalsBuffer(buffer1, buffer2), "out of bounds")
end)
--[[
ws2812.buffer:__concat()
--]]
end)
mispec.run()
require 'mispec'
local buffer, buffer1, buffer2
describe('WS2812_effects', function(it)
it:should('set_speed', function()
buffer = ws2812.newBuffer(9, 3)
ws2812_effects.init(buffer)
ws2812_effects.set_speed(0)
ws2812_effects.set_speed(255)
failwith("should be", ws2812_effects.set_speed, -1)
failwith("should be", ws2812_effects.set_speed, 256)
end)
it:should('set_brightness', function()
buffer = ws2812.newBuffer(9, 3)
ws2812_effects.init(buffer)
ws2812_effects.set_brightness(0)
ws2812_effects.set_brightness(255)
failwith("should be", ws2812_effects.set_brightness, -1)
failwith("should be", ws2812_effects.set_brightness, 256)
end)
end)
mispec.run()
......@@ -105,6 +105,7 @@ pages:
- 'pcm' : 'modules/pcm.md'
- 'perf': 'modules/perf.md'
- 'pipe': 'modules/pipe.md'
- 'pixbuf': 'modules/pixbuf.md'
- 'pwm' : 'modules/pwm.md'
- 'pwm2' : 'modules/pwm2.md'
- 'rfswitch' : 'modules/rfswitch.md'
......
# Enforce Unix newlines
*.css text eol=lf
*.html text eol=lf
*.js text eol=lf
*.json text eol=lf
*.less text eol=lf
*.md text eol=lf
*.svg text eol=lf
*.yml text eol=lf
*.py text eol=lf
*.sh text eol=lf
*.tcl text eol=lf
*.expect text eol=lf
Hardware Test Harness
=====================
There is an implementation of the hardware testing design which is a small 4in x 4in board with positions for
two Wemos D! Mini ESP8266 boards, a breadboard area and a number of positions for peripherals. The schematics
are in [schematic](Test-harness-schematic-v1.pdf) and a rendering of the board is ![Board Render](Test-Harness-Render-V1.png).
The test harness runs from a dedicated host computer, which is expected
to have reset- and programming-capable UART links to both ESP8266
devices, as found on almost all ESP8266 boards with USB to UART
adapters, but the host does not necessarily need to use USB to connect,
so long as TXD, RXD, DTR, and RTS are wired across.
The alternate pins on the primary D1 Mini (DUT0) are cross wired to the
RX and TX pins on the secondary D1 Mini (DUT1) and these are enabled by
a pin on the MCP23017.
Build Notes
-----------
The only thing that needs to be done is to solder on 0.1" headers at the required
positions. Typically D1 Minis come with 2 sets of 8 pin headers, both male and female.
I solder the female headers to the board, and the maie headers to the D1 minis. Other,
mostly 4 pin, headers can be soldered at the other positions. The 7 pin header for
the color sensor (TCS34725) requires some care as the board needs to be mounted
upside down so that the sensor is directly above the WS2812.
The screw holes at the corners are for M3 screws. A standard adhesive rubber foot can
also be used. There are no components on the underside of the test board, so not much clearance
is required (only the length of the various headers soldered on the board).
Power
-----
The board is powered by either (or both) D1 Mini USB connection. Given the cross connects
between the two D1 Minis, I think that all the tests can be conducted from DUT0, but
it is probably easier to connected both of the D1 Minis via USB to the test runner.
There is a small resistor between the two 5 volt rails to prevent large currents
if the two USB feeds are at slughtly different voltages. The 3.3 volt rails are
directly connected together. If the regulators produce slightly different voltages,
then the one producing the higher voltage will end up providing all the power for the
3.3 volt devices.
Peripherals
-----------
### I2C Bus
There is an I2C bus hanging off DUT 0. Attached hardware is used both as
tests of modules directly and also to facilitate testing other modules
(e.g., gpio).
Most of the positions on the board are connected to the DUT1 I2C bus.
#### MCP23017: I/O Expander
At address 0x20. An 16-bit tristate GPIO expander, this chip is used to
test I2C, GPIO, and ADC functionality. This chip's interconnections are
as follows:
MPC23017 | Purpose
---------|--------------------------------------------------------------
/RESET |DUT0 reset. This resets the chip whenever the host computer resets DUT 0 over its serial link (using DTR/RTS).
B 0 |4K7 resistor to DUT 0 ADC.
B 1 |2K2 resistor to DUT 0 ADC.
B 2 |Direct to DUT1 RST
B 3 |Direct to DUT1 D3
B 4 |When low, connects the alternate UART pins on DUT0 to RX,TX on DUT1
B 5 |DUT1 GPIO16/WAKE via 4K7 resitor
B 6 |DUT0 GPIO13 via 4K4 resistor and DUT1 GPIO15 via 4K7 resistor (also feeds in the primary TX from DUT1 when enabled by B4)
B 7 |DUT0 GPIO15 via 4K7 resistor and DUT1 GPIO13 via 4K7 resistor (also feeds the primary RX on DUT1 when enabled by B4)
Notes:
- DUT 0's ADC pin is connected via a 2K2 reistor to this chip's port
B, pin 1 and via a 4K7 resistor to port B, pin 0. This gives us the
ability to produce approximately 0 (both pins low), 1.1 (pin 0 high,
pin 1 low), 2.2 (pin 1 high, pin 0 low), and 3.3V (both pins high)
on the ADC pin.
- Port B pins 6 and 7 sit on the UART cross-wiring between DUT 0 and
DUT 1. The 23017 will be tristated for inter-DUT UART tests, but
these
- All of port A, remain available for expansion and are routed to the breadboard area.
#### WS2812s
There are three WS2812s connected on DUT1/D4. The last Ws2812 is positioned so that a TCS34725 module
can be mounted upside down over it to read out the color of the WS2812. That device is connected to
the I2C port on DUT0. A suitable board is [CJMCU-34725 TCS34725 Color Sensor RGB color sensor development board module](https://www.aliexpress.com/item/32412698433.html). The illuminating
LED is connected to the INT pin and so you can disable the LED under software control.
#### Oled Displays
Each of the D1 Minis is connected to a position for a 128x64 OLED display, again on the primary I2C bus.
#### Servo
On DUT1 pin D4/GPIO 2 there is a connection to a position for a small servo. The servo is powered by the
5V voltage rail.
#### DHTxx
On DUT1 pin D6/GPIO 12 there is a connection to a position for a DHTxx device. The silk screen indicates the
orientation of the device.
#### DS18B20
There are two positions for DS18B20s -- one with the VCC pin connected and one without. The data pin is
connected to DUT1 pin D5/GPIO 14.
#### I2C devices with VCC/GND/SCL/SDA pin order
There are three positions for I2C devices which have the pins in the VCC/GND/SCL/SDA order. These
are on the DUT1 I2 bus.
#### I2C devices with other pin orders
There are three positions for I2C devices with other pin orders. Each of these positions is next
to a crossbar switch and so four blobs of solder can configure each of these headers into any
desired pin order. As far as I can tell, most of the cheap modules use the VCC/GND/SCL/SDA order.
Breadboard Area
===============
All the pins on each D1 Mini and the A port of the MCP23017 are brought out to a breadboard
area. This can be used to solder components and/or wires, or even a header could be soldered
on to transfer all the signals to a conventional breadboard.
ESP8266 Device 0 Connections
----------------------------
ESP | Usage
----------|----------------------------------------------------------
D3/GPIO 0 |Used to enter programming mode; otherwise unused in test environment.
TX/GPIO 1 |Primary UART transmit; reserved for host communication
D4/GPIO 2 |[reserved for 1-Wire] [+ reserved for 23017 INT[AB] connections]
RX/GPIO 3 |Primary UART recieve; reserved for host communication
D2/GPIO 4 |I2C SDA. Connected to MCP23017, Oled display and the TCS34725 if present.
D1/GPIO 5 |I2C SCL
GPIO 6 |[Reserved for on-chip flash]
GPIO 7 |[Reserved for on-chip flash]
GPIO 8 |[Reserved for on-chip flash]
GPIO 9 |[Reserved for on-chip flash]
GPIO 10 |[Reserved for on-chip flash]
GPIO 11 |[Reserved for on-chip flash]
D6/GPIO 12 |
D7/GPIO 13 |Secondary UART RX; DUT 1 GPIO 15, I/O expander B 6
D5/GPIO 14 |
D8/GPIO 15 |Secondary UART TX; DUT 1 GPIO 13, I/O expander B 7
D0/GPIO 16 |
A0/ADC 0 |Resistor divider with I/O expander
ESP8266 Device 1 Connections
----------------------------
ESP | Usage
----------|----------------------------------------------------------
D3/GPIO 0 |Used to enter programming mode; otherwise unused in test environment.
TX/GPIO 1 |Primary UART transmit; reserved for host communication
D4/GPIO 2 |Connected to chain of 3 WS2812s. Also connected to the servo position.
RX/GPIO 3 |Primary UART recieve; reserved for host communication
D2/GPIO 4 |I2C SDA. Connected to all the other I2C positions on the board
D1/GPIO 5 |I2C SCL
GPIO 6 |[Reserved for on-chip flash]
GPIO 7 |[Reserved for on-chip flash]
GPIO 8 |[Reserved for on-chip flash]
GPIO 9 |[Reserved for on-chip flash]
GPIO 10 |[Reserved for on-chip flash]
GPIO 11 |[Reserved for on-chip flash]
D6/GPIO 12 |Connected to data pin for DHTxx
D7/GPIO 13 |Secondary UART RX; DUT 0 GPIO 15, I/O exp B 7 via 4K7 Also used as HSPI MOSI for SPI tests
D5/GPIO 14 |Connected to data pin for DS18B20s.
D8/GPIO 15 |Secondary UART TX; DUT 0 GPIO 13, I/O exp B 6 via 4K7 Also used as HSPI /CS for SPI tests
D0/GPIO 16 |I/O expander B 5 via 4K7 resistor, for deep-sleep tests
A0/ADC 0 |
local function TERMINAL_HANDLER(e, test, msg, errormsg)
if errormsg then
errormsg = ": "..errormsg
else
errormsg = ""
end
if e == 'start' then
print("######## "..e.."ed "..test.." tests")
elseif e == 'pass' then
print(" "..e.." "..test..': '..msg)
elseif e == 'fail' then
print(" ==> "..e.." "..test..': '..msg..errormsg)
elseif e == 'except' then
print(" ==> "..e.." "..test..': '..msg..errormsg)
elseif e == 'finish' then
print("######## "..e.."ed "..test.." tests")
else
print(e.." "..test)
end
end
-- implement pseudo task handling for on host testing
local drain_post_queue = function() end
if not node then -- assume we run on host, not on MCU
local post_queue = {{},{},{}}
drain_post_queue = function()
while #post_queue[1] + #post_queue[2] + #post_queue[3] > 0 do
for i = 3, 1, -1 do
if #post_queue[i] > 0 then
local f = table.remove(post_queue[i], 1)
if f then
f()
end
break
end
end
end
end
-- luacheck: push ignore 121 122 (setting read-only global variable)
node = {}
node.task = {LOW_PRIORITY = 1, MEDIUM_PRIORITY = 2, HIGH_PRIORITY = 3}
node.task.post = function (p, f)
table.insert(post_queue[p], f)
end
node.setonerror = function(fn) node.Host_Error_Func = fn end -- luacheck: ignore 142
-- luacheck: pop
end
--[[
if equal returns true
if different returns {msg = "<reason>"}
this will be handled spechially by ok and nok
--]]
local function deepeq(a, b)
local function notEqual(m)
return { msg=m }
end
-- Different types: false
if type(a) ~= type(b) then return notEqual("type 1 is "..type(a)..", type 2 is "..type(b)) end
-- Functions
if type(a) == 'function' then
if string.dump(a) == string.dump(b) then
return true
else
return notEqual("functions differ")
end
end
-- Primitives and equal pointers
if a == b then return true end
-- Only equal tables could have passed previous tests
if type(a) ~= 'table' then return notEqual("different "..type(a).."s expected "..a.." vs. "..b) end
-- Compare tables field by field
for k,v in pairs(a) do
if b[k] == nil then return notEqual("key "..k.."only contained in left part") end
local result = deepeq(v, b[k])
if type(result) == 'table' then return result end
end
for k,v in pairs(b) do
if a[k] == nil then return notEqual("key "..k.."only contained in right part") end
local result = deepeq(a[k], v)
if type(result) == 'table' then return result end
end
return true
end
-- Compatibility for Lua 5.1 and Lua 5.2
local function args(...)
return {n=select('#', ...), ...}
end
local function spy(f)
local mt = {}
setmetatable(mt, {__call = function(s, ...)
s.called = s.called or {}
local a = args(...)
table.insert(s.called, {...})
if f then
local r
r = args(pcall(f, unpack(a, 1, a.n)))
if not r[1] then
s.errors = s.errors or {}
s.errors[#s.called] = r[2]
else
return unpack(r, 2, r.n)
end
end
end})
return mt
end
local function getstackframe()
-- debug.getinfo() does not exist in NodeMCU Lua 5.1
if debug.getinfo then
return debug.getinfo(5, 'S').short_src:match("([^\\/]*)$")..":"..debug.getinfo(5, 'l').currentline
end
local msg
msg = debug.traceback()
msg = msg:match("\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t([^\t]*): in") -- Get 5th stack frame
msg = msg:match(".-([^\\/]*)$") -- cut off path of filename
return msg
end
local function assertok(handler, name, invert, cond, msg)
local errormsg
-- check if cond is return object of 'eq' call
if type(cond) == 'table' and cond.msg then
errormsg = cond.msg
cond = false
end
if not msg then
msg = getstackframe()
end
if invert then
cond = not cond
end
if cond then
handler('pass', name, msg)
else
handler('fail', name, msg, errormsg)
error('_*_TestAbort_*_')
end
end
local function fail(handler, name, func, expected, msg)
local status, err = pcall(func)
if not msg then
msg = getstackframe()
end
if status then
local messageParts = {"Expected to fail with Error"}
if expected then
messageParts[2] = " containing \"" .. expected .. "\""
end
handler('fail', name, msg, table.concat(messageParts, ""))
error('_*_TestAbort_*_')
end
if (expected and not string.find(err, expected)) then
err = err:match(".-([^\\/]*)$") -- cut off path of filename
handler('fail', name, msg, "expected errormessage \"" .. err .. "\" to contain \"" .. expected .. "\"")
error('_*_TestAbort_*_')
end
handler('pass', name, msg)
end
local nmt = {
env = _G,
outputhandler = TERMINAL_HANDLER
}
nmt.__index = nmt
return function(testrunname)
local pendingtests = {}
local started
local N = setmetatable({}, nmt)
local function runpending()
if pendingtests[1] ~= nil then
node.task.post(node.task.LOW_PRIORITY, function()
pendingtests[1](runpending)
end)
else
N.outputhandler('finish', testrunname)
end
end
local function copyenv(dest, src)
dest.eq = src.eq
dest.spy = src.spy
dest.ok = src.ok
dest.nok = src.nok
dest.fail = src.fail
end
local function testimpl(name, f, async)
local testfn = function(next)
local prev = {}
copyenv(prev, N.env)
local handler = N.outputhandler
local restore = function(err)
if err then
err = err:match(".-([^\\/]*)$") -- cut off path of filename
if not err:match('_*_TestAbort_*_') then
handler('except', name, err)
end
end
if node then node.setonerror() end
copyenv(N.env, prev)
handler('end', name)
table.remove(pendingtests, 1)
collectgarbage()
if next then next() end
end
local function wrap(method, ...)
method(handler, name, ...)
end
local function cbError(err)
err = err:match(".-([^\\/]*)$") -- cut off path of filename
if not err:match('_*_TestAbort_*_') then
handler('except', name, err)
end
restore()
end
local env = N.env
env.eq = deepeq
env.spy = spy
env.ok = function (cond, msg) wrap(assertok, false, cond, msg) end
env.nok = function(cond, msg) wrap(assertok, true, cond, msg) end
env.fail = function (func, expected, msg) wrap(fail, func, expected, msg) end
handler('begin', name)
node.setonerror(cbError)
local ok, err = pcall(f, async and restore)
if not ok then
err = err:match(".-([^\\/]*)$") -- cut off path of filename
if not err:match('_*_TestAbort_*_') then
handler('except', name, err)
end
if async then
restore()
end
end
if not async then
restore()
end
end
if not started then
N.outputhandler('start', testrunname)
started = true
end
table.insert(pendingtests, testfn)
if #pendingtests == 1 then
runpending()
drain_post_queue()
end
end
function N.test(name, f)
testimpl(name, f)
end
function N.testasync(name, f)
testimpl(name, f, true)
end
local currentCoName
function N.testco(name, func)
-- local t = tmr.create();
local co
N.testasync(name, function(Next)
currentCoName = name
local function getCB(cbName)
return function(...) -- upval: co, cbName
local result, err = coroutine.resume(co, cbName, ...)
if (not result) then
if (name == currentCoName) then
currentCoName = nil
Next(err)
else
N.outputhandler('fail', name, "Found stray Callback '"..cbName.."' from test '"..name.."'")
end
elseif coroutine.status(co) == "dead" then
currentCoName = nil
Next()
end
end
end
local function waitCb()
return coroutine.yield()
end
co = coroutine.create(function(wr, wa)
func(wr, wa)
end)
local result, err = coroutine.resume(co, getCB, waitCb)
if (not result) then
currentCoName = nil
Next(err)
elseif coroutine.status(co) == "dead" then
currentCoName = nil
Next()
end
end)
end
return N
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