Add mcp23017 Lua module (#3197)
This commit is contained in:
parent
29e5108876
commit
7f30381301
|
@ -0,0 +1,230 @@
|
||||||
|
# Lua MCP23017 Module for NodeMCU / ESP8266
|
||||||
|
|
||||||
|
| Since | Origin / Contributor | Maintainer | Source |
|
||||||
|
| :----- | :-------------------- | :---------- | :------ |
|
||||||
|
| 2020-04-10 | [Marcel P.](https://github.com/plomi-net) | [Marcel P.](https://github.com/plomi-net) | [mcp23017.lua](../../lua_modules/mcp23017/mcp23017.lua) |
|
||||||
|
|
||||||
|
|
||||||
|
This Lua module provides access to the MCP23017 I²C I/O Expander.
|
||||||
|
|
||||||
|
The [MCP23017](http://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf) is a port expander and provides 16 channels for inputs and outputs.
|
||||||
|
Up to 8 devices (128 channels) are possible by the configurable address (A0 - A2).
|
||||||
|
|
||||||
|
Due to the 16 channels, 2 bytes are required for switching outputs or reading input signals. These are A and B.
|
||||||
|
A single pin can be set or a whole byte.
|
||||||
|
|
||||||
|
The numbering of the individual pins starts at 0 and ends with 7.
|
||||||
|
The numbers are for each register GPIO A and GPIO B.
|
||||||
|
|
||||||
|
|
||||||
|
!!! important
|
||||||
|
The module requires `i2c` and `bit` C module built into firmware.
|
||||||
|
|
||||||
|
|
||||||
|
### Require
|
||||||
|
```lua
|
||||||
|
mcp = require "mcp23017"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Script
|
||||||
|
The example script can be found [here](../../lua_examples/mcp23017/mcp23017_example.lua)
|
||||||
|
|
||||||
|
## setup()
|
||||||
|
Configures the address of the module and tests the connection to the i2c bus.
|
||||||
|
The i2c id is required for an existing i2c interface, alternatively the sda and scl pins can be specified.
|
||||||
|
Then this function will establish the connection.
|
||||||
|
Automatically resets the device state (see `mcp23017:reset()`)
|
||||||
|
|
||||||
|
#### Syntax
|
||||||
|
`mcp23017:setup(address, i2c_id)`
|
||||||
|
|
||||||
|
#### Parameter
|
||||||
|
- `address` address for MCP23017, default: 0x20 (should be between 0x20 and 0x27)
|
||||||
|
- `i2c_id` id for the i2c bus connection (remember to call i2c.setup before)
|
||||||
|
|
||||||
|
#### Return
|
||||||
|
`true` if device found, otherwise `false`.
|
||||||
|
|
||||||
|
#### possible Errors
|
||||||
|
- `MCP23017 device on address not found`
|
||||||
|
- `MCP23017 address is out of range`
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
local mcp23017 = require "mcp23017"
|
||||||
|
|
||||||
|
local address = 0x20
|
||||||
|
local cSCL = 1
|
||||||
|
local cSDA = 2
|
||||||
|
local i2c_instance = 0
|
||||||
|
|
||||||
|
-- setup i2c bus and create instance for mcp23017 (assigned to mcp)
|
||||||
|
i2c.setup(i2c_instance, cSDA, cSCL, i2c.SLOW)
|
||||||
|
local mcp = mcp23017(address, i2c_instance)
|
||||||
|
```
|
||||||
|
|
||||||
|
## setMode()
|
||||||
|
Set the mode of a single channel. This can be OUTPUT or INPUT.
|
||||||
|
|
||||||
|
#### Syntax
|
||||||
|
`mcp23017:setMode(register, pin, mode)`
|
||||||
|
|
||||||
|
#### Parameter
|
||||||
|
- `register` the side of channels (GPA or GPB)
|
||||||
|
- `pin` the number to be set for the channel (0-15)
|
||||||
|
- `mode` the mode for the channel. This can be `mcp23017.INPUT` or `mcp23017.OUTPUT`
|
||||||
|
|
||||||
|
#### Return
|
||||||
|
`true`, in case of error `nil`.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
-- set pin 7 and 8 to output (GPA7 and GPB0) and GPB1 to input
|
||||||
|
mcp:setMode(mcp.GPA, 7, mcp.OUTPUT)
|
||||||
|
mcp:setMode(mcp.GPB, 0, mcp.OUTPUT)
|
||||||
|
mcp:setMode(mcp.GPB, 1, mcp.INPUT)
|
||||||
|
```
|
||||||
|
|
||||||
|
## setPin()
|
||||||
|
Set the state of a single channel. This can be HIGH or LOW.
|
||||||
|
|
||||||
|
#### Syntax
|
||||||
|
`mcp23017:setMode(register, pin, state)`
|
||||||
|
|
||||||
|
#### Parameter
|
||||||
|
- `register` the side of channels (GPA or GPB)
|
||||||
|
- `pin` the number to be set for the channel (0-15)
|
||||||
|
- `state` the state for the channel. This can be `mcp23017.HIGH` or `mcp23017.LOW`
|
||||||
|
|
||||||
|
#### Return
|
||||||
|
`true`, in case of error `nil`.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
-- set pin 7 to high (GPA7)
|
||||||
|
mcp:setPin(mcp.GPA, 7, mcp.HIGH)
|
||||||
|
-- set pin 8 to low (GPB0)
|
||||||
|
mcp:setPin(mcp.GPB, 0, mcp.LOW)
|
||||||
|
```
|
||||||
|
|
||||||
|
## getPinState()
|
||||||
|
get the state for a single channel. This can be HIGH or LOW.
|
||||||
|
|
||||||
|
#### Syntax
|
||||||
|
`mcp23017:getPinState(register, pin)`
|
||||||
|
|
||||||
|
#### Parameter
|
||||||
|
- `register` the side of channels (GPA or GPB)
|
||||||
|
- `pin` the number for which a state is to be queried (0-15)
|
||||||
|
|
||||||
|
#### Return
|
||||||
|
`true` for HIGH, `false` for LOW, in case of error `nil`.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
-- get the state for pin 9 (GPB1)
|
||||||
|
print(mcp:getPinState(mcp.GPB, 1))
|
||||||
|
```
|
||||||
|
|
||||||
|
## reset()
|
||||||
|
By calling this function, a safe state is established.
|
||||||
|
All channels are set to input.
|
||||||
|
This function can be used for a panic program.
|
||||||
|
|
||||||
|
#### Syntax
|
||||||
|
`mcp23017:reset()`
|
||||||
|
|
||||||
|
#### Parameter
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Return
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
-- reset the mcp23017 to startup defaults
|
||||||
|
mcp:reset()
|
||||||
|
```
|
||||||
|
|
||||||
|
## setInternalPullUp()
|
||||||
|
Enable or disable the internal pullup resistors.
|
||||||
|
|
||||||
|
#### Syntax
|
||||||
|
`mcp23017:setInternalPullUp(register, byte)`
|
||||||
|
|
||||||
|
#### Parameter
|
||||||
|
- `register` the side of channels (GPA or GPB)
|
||||||
|
- `byte` byte to set the pullup resistors
|
||||||
|
|
||||||
|
#### Return
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
-- enable all pullup resistors for GPA
|
||||||
|
print(mcp:setInternalPullUp(mcp.GPA, 0xFF))
|
||||||
|
-- disable all pullup resistors for GPA
|
||||||
|
print(mcp:setInternalPullUp(mcp.GPA, 0x00))
|
||||||
|
```
|
||||||
|
|
||||||
|
## writeIODIR()
|
||||||
|
Setup the mode of the channels with a whole byte.
|
||||||
|
|
||||||
|
|
||||||
|
#### Syntax
|
||||||
|
`mcp23017:writeIODIR(register, byte)`
|
||||||
|
|
||||||
|
#### Parameter
|
||||||
|
- `register` the side of channels (GPA or GPB)
|
||||||
|
- `byte` byte to set the mode for all channels for this register
|
||||||
|
|
||||||
|
#### Return
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
-- set all GPA to input
|
||||||
|
print(mcp:writeIODIR(mcp.GPA, 0xFF))
|
||||||
|
-- set all GPA to output
|
||||||
|
print(mcp:writeIODIR(mcp.GPA, 0x00))
|
||||||
|
```
|
||||||
|
|
||||||
|
## writeGPIO()
|
||||||
|
Setup the output state of the channels with a whole byte.
|
||||||
|
|
||||||
|
#### Syntax
|
||||||
|
`mcp23017:writeGPIO(register, byte)`
|
||||||
|
|
||||||
|
#### Parameter
|
||||||
|
- `register` the side of channels (GPA or GPB)
|
||||||
|
- `byte` byte to set the state for all channels for this register
|
||||||
|
|
||||||
|
#### Return
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
-- set all GPA to HIGH
|
||||||
|
print(mcp:writeGPIO(mcp.GPA, 0xFF))
|
||||||
|
-- set all GPA to LOW
|
||||||
|
print(mcp:writeGPIO(mcp.GPA, 0x00))
|
||||||
|
```
|
||||||
|
|
||||||
|
## readGPIO()
|
||||||
|
Read the input states of the channels with a whole byte.
|
||||||
|
|
||||||
|
#### Syntax
|
||||||
|
`mcp23017:readGPIO(register)`
|
||||||
|
|
||||||
|
#### Parameter
|
||||||
|
- `register` the side of channels (GPA or GPB)
|
||||||
|
|
||||||
|
#### Return
|
||||||
|
byte with states
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
-- get states for GPA
|
||||||
|
print(mcp:readGPIO(mcp.GPA))
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
--[[
|
||||||
|
This example demonstrates how to use the different functions of the mcp23017 lua module.
|
||||||
|
|
||||||
|
@author Marcel P. | Plomi.net
|
||||||
|
@github https://github.com/plomi-net
|
||||||
|
@version 1.0.0
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
|
initialize and setup
|
||||||
|
]]
|
||||||
|
-- initialize module
|
||||||
|
local mcp23017 = require "mcp23017"
|
||||||
|
local address = 0x20 -- the address for MCP23017 (between 0x20 and 0x27)
|
||||||
|
local cSCL = 1 -- SCL pin = 1 = D1 / GPIO 5 (ESP8266)
|
||||||
|
local cSDA = 2 -- SDA pin = 2 = D2 / GPIO4 (ESP8266)
|
||||||
|
local i2cId = 0 -- i2c id
|
||||||
|
-- setup i2c bus and create instance for mcp23017 (assigned to mcp)
|
||||||
|
i2c.setup(i2cId, cSDA, cSCL, i2c.SLOW)
|
||||||
|
local mcp = mcp23017(address, i2cId)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
set output and input channels
|
||||||
|
]]
|
||||||
|
-- set pin 7 and 8 to output (GPA7 and GPB0) and GPB1 to input
|
||||||
|
mcp:setMode(mcp.GPA, 7, mcp.OUTPUT)
|
||||||
|
mcp:setMode(mcp.GPB, 0, mcp.OUTPUT)
|
||||||
|
mcp:setMode(mcp.GPB, 1, mcp.INPUT)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
set output channels to high and low
|
||||||
|
]]
|
||||||
|
-- set pin 7 to high (GPA7)
|
||||||
|
mcp:setPin(mcp.GPA, 7, mcp.HIGH)
|
||||||
|
-- set pin 8 to low (GPB0)
|
||||||
|
mcp:setPin(mcp.GPB, 0, mcp.LOW)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
toggle pin 6 channel state every second (blinking)
|
||||||
|
]]
|
||||||
|
local currentPin = 6
|
||||||
|
local currentReg = mcp.GPA
|
||||||
|
local currentState = false
|
||||||
|
|
||||||
|
mcp:setMode(currentReg, currentPin, mcp.OUTPUT)
|
||||||
|
tmr.create():alarm(1000, tmr.ALARM_AUTO, function()
|
||||||
|
if currentState == true then
|
||||||
|
-- print("set to low")
|
||||||
|
mcp:setPin(currentReg, currentPin, mcp.LOW)
|
||||||
|
currentState = false
|
||||||
|
else
|
||||||
|
-- print("set to high")
|
||||||
|
mcp:setPin(currentReg, currentPin, mcp.HIGH)
|
||||||
|
currentState = true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
read input channels and display every 7 seconds
|
||||||
|
]]
|
||||||
|
-- read input register
|
||||||
|
tmr.create():alarm(7000, tmr.ALARM_AUTO, function()
|
||||||
|
local a = mcp:readGPIO(mcp.GPA)
|
||||||
|
if a ~= nil then
|
||||||
|
print("GPIO A input states: " .. a)
|
||||||
|
else
|
||||||
|
print("GPIO A unreadable, check device")
|
||||||
|
end
|
||||||
|
|
||||||
|
local b = mcp:readGPIO(mcp.GPB)
|
||||||
|
if b ~= nil then
|
||||||
|
print("GPIO B input states: " .. b)
|
||||||
|
else
|
||||||
|
print("GPIO B unreadable, check device")
|
||||||
|
end
|
||||||
|
print(" ")
|
||||||
|
end)
|
|
@ -0,0 +1,3 @@
|
||||||
|
# MCP23017 Module
|
||||||
|
|
||||||
|
Documentation for this Lua module is available in the [mcp23017.md](../../docs/lua-modules/mcp23017.md) file and in the [Official NodeMCU Documentation](https://nodemcu.readthedocs.io/) in `Lua Modules` section.
|
|
@ -0,0 +1,185 @@
|
||||||
|
--[[
|
||||||
|
This Lua module provides access to the MCP23017 module.
|
||||||
|
|
||||||
|
The MCP23017 is a port expander and provides 16 channels for inputs and outputs.
|
||||||
|
Up to 8 devices (128 channels) are possible by the configurable address (A0 - A2 Pins).
|
||||||
|
|
||||||
|
Due to the 16 channels, 2 bytes are required for switching outputs or reading input signals. These are A and B.
|
||||||
|
A single pin can be set or a whole byte.
|
||||||
|
|
||||||
|
The numbering of the individual pins starts at 0 and ends with 7.
|
||||||
|
The numbers are for each register GPIO A and GPIO B.
|
||||||
|
|
||||||
|
The module requires `i2c` and `bit` C module built into firmware.
|
||||||
|
|
||||||
|
@author Marcel P. | Plomi.net
|
||||||
|
@github https://github.com/plomi-net
|
||||||
|
@version 1.0.0
|
||||||
|
]]
|
||||||
|
|
||||||
|
local i2c, string, issetBit, setBit, clearBit, error =
|
||||||
|
i2c, string, bit.isset, bit.set, bit.clear, error
|
||||||
|
local isINPUT, isGPB, isHIGH = true, true, true
|
||||||
|
|
||||||
|
-- registers (not used registers are commented out)
|
||||||
|
local MCP23017_IODIRA = 0x00
|
||||||
|
local MCP23017_IODIRB = 0x01
|
||||||
|
local MCP23017_DEFVALA = 0x06
|
||||||
|
local MCP23017_DEFVALB = 0x07
|
||||||
|
local MCP23017_GPIOA = 0x12
|
||||||
|
local MCP23017_GPIOB = 0x13
|
||||||
|
--[[
|
||||||
|
local MCP23017_IPOLA = 0x02
|
||||||
|
local MCP23017_IPOLB = 0x03
|
||||||
|
local MCP23017_GPINTENA = 0x04
|
||||||
|
local MCP23017_GPINTENB = 0x05
|
||||||
|
local MCP23017_DEFVALA = 0x06
|
||||||
|
local MCP23017_DEFVALB = 0x07
|
||||||
|
local MCP23017_INTCONA = 0x08
|
||||||
|
local MCP23017_INTCONB = 0x09
|
||||||
|
local MCP23017_IOCON = 0x0A
|
||||||
|
local MCP23017_IOCON2 = 0x0B
|
||||||
|
local MCP23017_GPPUA = 0x0C
|
||||||
|
local MCP23017_GPPUB = 0x0D
|
||||||
|
local MCP23017_INTFA = 0x0E
|
||||||
|
local MCP23017_INTFB = 0x0F
|
||||||
|
local MCP23017_INTCAPA = 0x10
|
||||||
|
local MCP23017_INTCAPB = 0x11
|
||||||
|
local MCP23017_OLATA = 0x14
|
||||||
|
local MCP23017_OLATB = 0x15
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- metatable
|
||||||
|
local mcp23017 = {
|
||||||
|
INPUT = isINPUT,
|
||||||
|
OUTPUT = not isINPUT,
|
||||||
|
GPA = not isGPB,
|
||||||
|
GPB = isGPB,
|
||||||
|
HIGH = isHIGH,
|
||||||
|
LOW = not isHIGH
|
||||||
|
}
|
||||||
|
mcp23017.__index = mcp23017
|
||||||
|
|
||||||
|
-- check device is available on address
|
||||||
|
local function checkDevice(address, i2cId)
|
||||||
|
i2c.start(i2cId)
|
||||||
|
local response = i2c.address(i2cId, address, i2c.TRANSMITTER)
|
||||||
|
i2c.stop(i2cId)
|
||||||
|
return response
|
||||||
|
end
|
||||||
|
|
||||||
|
-- write byte
|
||||||
|
local function writeByte(address, i2cId, registerAddr, val)
|
||||||
|
i2c.start(i2cId)
|
||||||
|
i2c.address(i2cId, address, i2c.TRANSMITTER)
|
||||||
|
i2c.write(i2cId, registerAddr)
|
||||||
|
i2c.write(i2cId, val)
|
||||||
|
i2c.stop(i2cId)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read byte
|
||||||
|
local function readByte(address, i2cId, registerAddr)
|
||||||
|
i2c.start(i2cId)
|
||||||
|
i2c.address(i2cId, address, i2c.TRANSMITTER)
|
||||||
|
i2c.write(i2cId, registerAddr)
|
||||||
|
i2c.stop(i2cId)
|
||||||
|
i2c.start(i2cId)
|
||||||
|
i2c.address(i2cId, address, i2c.RECEIVER)
|
||||||
|
local data = i2c.read(i2cId, 1)
|
||||||
|
i2c.stop(i2cId)
|
||||||
|
return string.byte(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check pin is in range
|
||||||
|
local function checkPinIsInRange(pin)
|
||||||
|
if pin > 7 or pin < 0 then
|
||||||
|
error("MCP23017 the pin must be between 0 and 7")
|
||||||
|
end
|
||||||
|
return pin
|
||||||
|
end
|
||||||
|
|
||||||
|
local function reset(address, i2cId)
|
||||||
|
writeByte(address, i2cId, MCP23017_IODIRA, 0xFF)
|
||||||
|
writeByte(address, i2cId, MCP23017_IODIRB, 0xFF)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- setup device
|
||||||
|
local function setup(address, i2cId)
|
||||||
|
|
||||||
|
-- check device address (0x20 to 0x27)
|
||||||
|
if (address < 32 or address > 39) then
|
||||||
|
error("MCP23017 address is out of range")
|
||||||
|
end
|
||||||
|
|
||||||
|
if (checkDevice(address, i2cId) ~= true) then
|
||||||
|
error("MCP23017 device on " .. string.format('0x%02X', address) .. " not found")
|
||||||
|
else
|
||||||
|
reset(address, i2cId)
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(address, i2cId)
|
||||||
|
local self = setmetatable({}, mcp23017)
|
||||||
|
|
||||||
|
if setup(address, i2cId) then
|
||||||
|
self.writeIODIR = function(sf, bReg, newByte) -- luacheck: no unused
|
||||||
|
writeByte(address, i2cId,
|
||||||
|
bReg == isGPB and MCP23017_IODIRB or MCP23017_IODIRA,
|
||||||
|
newByte)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.writeGPIO = function(sf, bReg, newByte) -- luacheck: no unused
|
||||||
|
writeByte(address, i2cId,
|
||||||
|
bReg == isGPB and MCP23017_GPIOB or MCP23017_GPIOA, newByte)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.readGPIO = function(sf, bReg) -- luacheck: no unused
|
||||||
|
return readByte(address, i2cId, -- upvals
|
||||||
|
bReg == isGPB and MCP23017_GPIOB or MCP23017_GPIOA)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read pin input
|
||||||
|
self.getPinState = function(sf, bReg, pin) -- luacheck: no unused
|
||||||
|
return issetBit(readByte(address, i2cId,
|
||||||
|
bReg == isGPB and MCP23017_GPIOB or MCP23017_GPIOA),
|
||||||
|
checkPinIsInRange(pin))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set pin to low or high
|
||||||
|
self.setPin = function(sf, bReg, pin, state) -- luacheck: no unused
|
||||||
|
local inReq = bReg == isGPB and MCP23017_GPIOB or MCP23017_GPIOA
|
||||||
|
local inPin = checkPinIsInRange(pin)
|
||||||
|
local response = readByte(address, i2cId, inReq)
|
||||||
|
writeByte(address, i2cId, inReq,
|
||||||
|
state == isHIGH and setBit(response, inPin) or clearBit(response, inPin))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set mode for a pin
|
||||||
|
self.setMode = function(sf, bReg, pin, mode) -- luacheck: no unused
|
||||||
|
local inReq = bReg == isGPB and MCP23017_IODIRB or MCP23017_IODIRA
|
||||||
|
local inPin = checkPinIsInRange(pin)
|
||||||
|
local response = readByte(address, i2cId, inReq)
|
||||||
|
writeByte(address, i2cId, inReq,
|
||||||
|
mode == isINPUT and setBit(response, inPin) or clearBit(response, inPin))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- reset gpio mode
|
||||||
|
self.reset = function(sf) -- luacheck: no unused
|
||||||
|
reset(address, i2cId)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- setup internal pullup
|
||||||
|
self.setInternalPullUp = function(sf, bReg, iByte) -- luacheck: no unused
|
||||||
|
writeByte(address, i2cId,
|
||||||
|
bReg == isGPB and MCP23017_DEFVALB or MCP23017_DEFVALA, iByte)
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ pages:
|
||||||
- 'liquidcrystal': 'lua-modules/liquidcrystal.md'
|
- 'liquidcrystal': 'lua-modules/liquidcrystal.md'
|
||||||
- 'lm92': 'lua-modules/lm92.md'
|
- 'lm92': 'lua-modules/lm92.md'
|
||||||
- 'mcp23008': 'lua-modules/mcp23008.md'
|
- 'mcp23008': 'lua-modules/mcp23008.md'
|
||||||
|
- 'mcp23017': 'lua-modules/mcp23017.md'
|
||||||
- 'redis': 'lua-modules/redis.md'
|
- 'redis': 'lua-modules/redis.md'
|
||||||
- 'telnet': 'lua-modules/telnet.md'
|
- 'telnet': 'lua-modules/telnet.md'
|
||||||
- 'yeelink': 'lua-modules/yeelink.md'
|
- 'yeelink': 'lua-modules/yeelink.md'
|
||||||
|
|
Loading…
Reference in New Issue