Relay and ESP8266 – part 1

Live forced me to create a light switch controlled via phone. My mom needs one. It is also a good opportunity to play little more with ESP.

We will work on circuit and software. Last time we use ESP as a client broadcaster (to control Piader), this time we will use it as a server.
How to power this ? This time it is not a prototype and it should quickly be installed. We will take power for chipset and relay from 5V charger. This time I’m using NodeMCU v3. Why v3? Because it has one important difference comparing to v2. One pinout is 5V from USB. This is what we need to power coil. To power optotriac we will use GPIO.

As software goes we will start from boilerplate and add UDP server. This one will listen to broadcast packets and respond to defined events.

Get source code from GitHub

Wiring

We have an NodeMCU v3 board, 2 relay module and few wires. First lets see connections:

NodeMCU v3            2 Relay Module

D2 *-------------------* IN1
D3 *-------------------* IN2
3V *-------------------* VCC

G  *-------------------* GND
VU *-------------------* JD-VSS

Power to coil and optotriac is separated. Coil taks 5V while control signals are 3.3V.

To achieve this we disconnect VCC and JD-VCC pins.Β  And set power as required.
img_20161204_130222

As I mention NodeMCU v3 has a nice feature, it gives USB voltage on VU pin. We don’t need to add anything. In case of V2 we would have to plug into micro usb and take 5V from there.

For control signals I choose GPIO’s D2 and D3. Why ? Because D1 is used as emergency break during boot.

Connection 230V to relay is simple, get one cable, cut it and plug into relay πŸ™‚ But be careful! It is 230V and it may hurt you !

Hardware done time to write something .. or not yet.

Protocol and packets

Before we write code we need to think a little about communication. We will use JSON messages but what we will send in them ?

We need to look from server perspective, it receive packet and must detect if this packet is for it. And next it needs to know what to do with it.
We will use protocol field to specify protocol name for our messages. Not all broadcasted packet are ours. This field makes a fist selection.

Actions are simple because we already have an event field. But target – we do not have such field. I think it must be an array because packet may be sent to few targets. Like switch off all lights or switch on light in room A.

Summarizing our message looks like this:

{
    protocol: "iot:1",
    event: "channel1.on",
    node: "computer",
    targets: [
       "big-room-support-light"
    ]
}

Software – simulate client

Lets start from writing a client in python that will send packet with JSON message after each 3 seconds. This will allows us to focus only on LUA server.


import socket
import random
import json
import time

address = ('<broadcast>', 5053)

events = [
    "channel1.on",
    "channel1.off",
    "channel2.on",
    "channel2.off",
]

packet = {
    "protocol": "iot:1",
    "node": "computer",
    "targets": [
        "big-room-support-light",
        "big-room-main-light"
    ]
}

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

while True:
    packet["event"] = random.choice(events)
    print("send", packet)
    s.sendto(json.dumps(packet).encode(), address)
    time.sleep(3)

I do not think this code require any explanation.

Software – server

Get the boilerplate. This takes care of network and other stuff. We just need to set some variables. Open parameter.lua and add

PROTOCOL = "iot:1"
CHANNEL_0 = 2
CHANNEL_1 = 3

Next add to parameters-device.lua:

NODE_ID = "big-room-support-light"

Now open main.lua. Fist we will initialize GPIOs:

gpio.mode(CHANNEL_0, gpio.OUTPUT, gpio.PULLUP)
gpio.mode(CHANNEL_1, gpio.OUTPUT, gpio.PULLUP)

What next? We have few steps to do:
– create a server to receive broadcast packets
– decode message
— check if correct JSON
— check field protocol
— check if target is this node
– execute action

Server

We are going to listen for UDP broadcast packages. After receiving something we will send content to function that will decode it. But for now we will just print it. From what I read LUA listen for broadcast as default. We don’t need to set any additional options. Lets check it:

svr = net.createServer(net.UDP)
svr:on("receive", function(socket, message)
    print(message)
end)

svr:listen(5053)    

And it was not receiving anything! But it should. To do an additional check I ran WiFi keyboard we used some time ago. And it was working, server received packets.
After some checks I discovered that my computer didn’t attach python script to proper network. I have few virtual switches (from Docker, Xamarin) and that was the problem. Fix was to change const <broadcast> to IP.

Decode and validate the message

Before we write decoding lets change a little our python client. Replace while loop with:

while True:
    packet["event"] = random.choice(events)
    msg = json.dumps(packet)
    if random.randint(1,100) > 50:
        msg += "fault"

    print("send", msg)
    s.sendto(msg.encode(), address)
    time.sleep(3)

This will simulate sending non JSON messages. Our decoder should handle this.

Time for decode function, input is string and output is a table or nil. Received string may be incorrect JSON – that is first error, or it may not contain proper field protocol – second quick error or it may be not for this node – third error.

svr:on("receive", function(socket, message)

    function validateMessage(json)        
        if json == nil then
            return false
         end
    
        return true
    end
    
    function decodeMessage(message)        
        ok, json = pcall(cjson.decode, message)
        if not ok or not validateMessage(json) then
            json = nil
        end
        
        return json
    end
    
    message = decodeMessage(message)
    print(message)
end)

We call decodeMessage which decode message to JSON and next call function validateMessage were we gonna put all additional checks.
Lets tweak validateMessage:

function validateMessage(json)                
        if json == nil or json['protocol'] ~= PROTOCOL or type(json['targets']) ~= 'table' then
            return false
         end    

         isTarget = false
         for k,v in pairs(json['targets']) do
            if v == NODE_ID then isTarget = true end
         end
    
        return isTarget
    end

What is going in there? We check if json variable is a table and its version is equal to our. We also check if our node is in targets table.

After all this we have a message and can parse an event.

Execute action

This require some ifs:

 message = decodeMessage(message)

 if message['event'] ~= nil then
        print(message['event'])
        if message['event'] == "channel1.on" then
            gpio.write(CHANNEL_0, gpio.LOW)
        elseif message['event'] == "channel2.on" then
            gpio.write(CHANNEL_1, gpio.LOW)
        elseif message['event'] == "channel1.off" then
            gpio.write(CHANNEL_0, gpio.HIGH)
        elseif message['event'] == "channel2.off" then
            gpio.write(CHANNEL_1, gpio.HIGH)
        end
    end

This is the simplest part of our sever πŸ™‚

Summary

And that’s it for now. We have a node that can trigger light – that’s good, but we don’t have an easy way to use it – and that’s bad πŸ™‚
In next part we will slight change code, we need to extract decodeMessage to boilerplate and add events to read current state. After that we will create app for mobile phone.

Advertisements

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