NodeMCU and relays – upgrade

A long time ago we created a NodeMCU Node with two channel relay. It was working but it’s code bases on old boilerplate. I recently bought an SSR (solid state relay) so let’s take this opportunity and upgrade our node to the newest boilerplate and write a handler for relays.

NodeMCU boilerplate – GitHub

Planning

The old code was fixed for only two channels. Big no this time, it needs to be flexible. The number of channels and theirs GPIOs will be defined in parameters.lua.
We do not need a class to control relay just a handler is enough. It is a simple on/off case. No calculations, no protocols just good old GPIOs 😛
If we only have a handler, it must take care of pins initialization.
Another matter is an event. An old version has hard coded channels. We need to make it flexible. But how?
Maybe we will have events: channel.on and channel.off, and channel number goes to a parameters field? It should work. So our message will look like this:

{
    'protocol': 'iot:1',
    'node': 'computer',
    'chip_id': 'd45656b45afb58b1f0a46',
    'event': 'channel.on',
    'parameters': {
        'channel': 1
    },
    'targets': [
        'ALL'
    ]
}

Wiring

I’m using two relay modules. First is 2 channel relay module and a second is 2 channel SSR. Electromagnetic one requires 5V to power up and 5V or 3V to control it. I’m using 3V so I had to use more wires and open jumper on voltage section.
SSR can work with 3V so it is easier to connect.

NodeMCUv3            2 Relay Module
D1  ------------------ IN1
D2  ------------------ IN2
3V  ------------------ VCC (next to IN2 pin)
GND ------------------ GND (power section)
VU  ------------------ JD-VCC

                     2 solid state relay
D3  ------------------ CH1
D4  ------------------ CH2
G   ------------------ DC-
3V  ------------------ DC+

Preparing

First, let’s configure GPIOs and channels. In parameters.lua we need to add:

CHANNELS = {2, 3, 4, 1}

This tells us wich pins is for a channel (do not forget that LUA index arrays from 1, so our channel no starts from 1).
The template for a handler:

local relay_handler = {}
relay_handler.__index = relay_handler

setmetatable(relay_handler, {
    __call = function(cls, ...)
        return cls.new(...)
    end,
})

function relay_handler.new(channels)
    local self = setmetatable({}, relay_handler)
    self.channels = channels
    return self
end    

return relay_handler

It is LUA way of saying: classes are simple 😀

Let’s prepare main.lua:

print ("core ready")

network_message = require "network_message"
relay_handler = require "relay_handler"
server_listener = require "server_listener"

handler = relay_handler(CHANNELS)

Handler

First, the handler needs to initialize pins. It should do it instantly after being instanced.

function relay_handler.new(channels)
    local self = setmetatable({}, relay_handler)
    self.channels = channels
    relay_handler.init(self)
    return self
end    

function relay_handler:init()
    for k, v in pairs(self.channels) do
        gpio.mode(v, gpio.OUTPUT, gpio.PULLUP)
    end
end

Pins are ready. It is time to handle an event. As we planned, event tells if set a channel on or off and the channel number is in parameters.
LUA index table from 1 so we need to increase channel no received in parameters (we will count it from 0 and we need adapt to LUA)

function relay_handler:handle(socket, message)   
    response = false
    if message ~= nil and message.event ~= nil and type(message['parameters']) == 'table' and message.parameters.channel ~= nil then     
        if message.event == 'channel.on' then
            gpio.write(self.channels[message.parameters.channel + 1], gpio.LOW)
        end
        if message.event == 'channel.off' then
            gpio.write(self.channels[message.parameters.channel + 1], gpio.HIGH)
        end
    end           
end

What is left? Add those lines to main.lua:

-- add handlers to listener
server_listener.add("relay", handler)

-- run server
server_listener.start(PORT)

Testing

Like a few times earlier I’m using this Python script to test Node:

import socket
import json

address = ('192.168.1.255', 5053)
packet = {
    'protocol': 'iot:1',
    'node': 'computer',
    'chip_id': 'd45656b45afb58b1f0a46',
    'event': 'channel.off',
    'parameters': {
        'channel': 0
    },
    'targets': [
        'ALL'
    ]
}

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
msg = json.dumps(packet)
print(msg)
s.sendto(msg.encode(), address)

And it is working 🙂

Upgrades

It is nice that we can toggle power channels but what if we want to know current state? Hmm… we may try an guess it 😀 or me may add another event and response for it. So it is an upgrade to the upgrade 🙂
A message asking for a current state should pass channel.states in event field. In a response, an array is returned.

Now send a message to turn on a channel that doesn’t exist… crash. We have to add another if.
Function handle after all changes looks like this:


function relay_handler:handle(socket, message)   
    response = false
    if message ~= nil and message.event ~= nil and type(message['parameters']) == 'table' and message.parameters.channel ~= nil then
        channel = self.channels[message.parameters.channel + 1]
        if message.event == 'channel.on' and channel ~= nil then
            gpio.write(channel, gpio.LOW)
            response = true
        end
        if message.event == 'channel.off' and channel ~= nil then
            gpio.write(channel, gpio.HIGH)
            response = true
        end
        if message.event == 'channel.states' then
            message = network_message.prepareMessage()
            states = {}
            for k,v in pairs(self.channels) do
                states[k] = gpio.read(v) == 0 and 1 or 0
            end    
            message.response = states
            network_message.sendMessage(socket, message)
            response = true
        end
    end           
end

Why we are reversing the gpio reading?

 states[k] = gpio.read(v) == 0 and 1 or 0

It is because 0 is enabled and 1 is disabled but it is not normal 🙂

Summary

Our Node does not care about relay type or it’s numbers of channels. What it needs to know is a table with pins to switch a control circuit.
With the handler, we may start the node with 6 lines of code and add it to our network.
Code is added to NodeMCU boilerplate and you can find it at GitHub

Advertisements

2 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s