WolkWriter
Published © GPL3+

Remote Control Light Switch Using IoT Technology

Control the state of a lamp remotely through a mobile app or web app by using Pycom's FiPy, MikroE's Relay click and WolkAbout IoT Platform.

IntermediateFull instructions provided1 hour71
Remote Control Light Switch Using IoT Technology

Things used in this project

Hardware components

FiPy
Pycom FiPy
The provided code should work for any Pycom board
×1
RELAY click
MikroElektronika RELAY click
×1
USB to TTL UART adapter
Used to power the board
×1
Female/Female Jumper Wires
Female/Female Jumper Wires
×1
Generic lamp
Any household lamp will do
×1

Software apps and online services

WolkAbout IoT Platform
WolkAbout IoT Platform

Story

Read more

Schematics

Schematic

Hackster uejyhe1i0b

Code

main.py

MicroPython
This script will connect to your preferred WiFI network and then connect to WolkAbout IoT Platform to send the current state of the Relay click that it has set to off state when booting up. It will then check for incoming actuations of the Relay click from WolkAbout IoT Platform every 20 milliseconds and change the state if a command has been received. Other than that, it will publish a ping message once every minute to keep the connection alive.
from crypto import getrandbits
from machine import unique_id, Pin, Timer
from network import WLAN
import pycom
from sys import print_exception
from time import sleep, sleep_ms
from ubinascii import hexlify

# External modules that need to be placed under /flash/lib
from mqtt import MQTTClient
import wolk

# WolkAbout
CLIENT_ID = hexlify(unique_id())
wolk.HOST = "api-demo.wolkabout.com"
wolk.PORT = 1883
wolk.DEVICE_KEY = "device_key"
wolk.DEVICE_PASSWORD = "some_password"
wolk.ACTUATOR_REFERENCES = ["SW"]

# WiFi
WIFI_SSID = "WIFI_SSID"
WIFI_AUTH = WLAN.WPA2  # WEP, WPA, WPA2, WPA2_ENT
WIFI_PASSWORD = "WIFI_PASSWORD"

# Relay Click connected on P3 (GPIO4)
LIGHT_SWITCH = Pin("P3", mode=Pin.OUT)
LIGHT_SWITCH.value(False)
# Set onboard LED to dim red
pycom.heartbeat(False)
pycom.rgbled(0x100000)


def get_actuator_status(reference):
    if reference == "SW":
        return wolk.ACTUATOR_STATE_READY, LIGHT_SWITCH.value()


def handle_actuation(reference, value):
    if reference == "SW":
        if value is True:
            LIGHT_SWITCH.value(True)
            pycom.rgbled(0x001000)
        else:
            LIGHT_SWITCH.value(False)
            pycom.rgbled(0x100000)


# WIFI setup
wlan = WLAN(mode=WLAN.STA)
wlan.connect(WIFI_SSID, auth=(WIFI_AUTH, WIFI_PASSWORD), timeout=5000)
sleep(2)  # wlan.isconnected can return a false positve so best to wait a bit
while not wlan.isconnected():
    machine.idle()
print("network configuration:", wlan.ifconfig())


# WolkAbout setup
MQTT_CLIENT = MQTTClient(
    CLIENT_ID, wolk.HOST, wolk.PORT, wolk.DEVICE_KEY, wolk.DEVICE_PASSWORD
)

WOLK_DEVICE = wolk.WolkConnect(MQTT_CLIENT, handle_actuation, get_actuator_status)


try:
    WOLK_DEVICE.connect()
    WOLK_DEVICE.publish_actuator_status("SW")
    LOOP_COUNTER = 0
    while True:
        LOOP_COUNTER += 1
        try:
            MQTT_CLIENT.check_msg()
        except OSError as os_e:
            # sometimes an empty socket read happens
            # and that needlessly kills the script
            pass
        if LOOP_COUNTER % 3000 == 0:  # every 60 seconds
            WOLK_DEVICE.send_ping()
            LOOP_COUNTER = 0
        sleep_ms(20)
except Exception as e:
    WOLK_DEVICE.disconnect()
    print_exception(e)

boot.py

MicroPython
Sets up script output to UART and runs the main.py script
from machine import UART
import machine
import os

uart = UART(0, baudrate=115200)
os.dupterm(uart)

machine.main('main.py')

mqtt.py

MicroPython
Pycom's implementation of an MQTT client that is a dependency of WolkConnect-MicroPython, and that is hosted on GitHub: https://github.com/pycom/pycom-libraries/blob/master/lib/mqtt/mqtt.py
import usocket as socket
import ustruct as struct
from ubinascii import hexlify

class MQTTException(Exception):
    pass

class MQTTClient:

    def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0,
                 ssl=False, ssl_params={}):
        if port == 0:
            port = 8883 if ssl else 1883
        self.client_id = client_id
        self.sock = None
        self.addr = socket.getaddrinfo(server, port)[0][-1]
        self.ssl = ssl
        self.ssl_params = ssl_params
        self.pid = 0
        self.cb = None
        self.user = user
        self.pswd = password
        self.keepalive = keepalive
        self.lw_topic = None
        self.lw_msg = None
        self.lw_qos = 0
        self.lw_retain = False

    def _send_str(self, s):
        self.sock.write(struct.pack("!H", len(s)))
        self.sock.write(s)

    def _recv_len(self):
        n = 0
        sh = 0
        while 1:
            b = self.sock.read(1)[0]
            n |= (b & 0x7f) << sh
            if not b & 0x80:
                return n
            sh += 7

    def set_callback(self, f):
        self.cb = f

    def set_last_will(self, topic, msg, retain=False, qos=0):
        assert 0 <= qos <= 2
        assert topic
        self.lw_topic = topic
        self.lw_msg = msg
        self.lw_qos = qos
        self.lw_retain = retain

    def connect(self, clean_session=True):
        self.sock = socket.socket()
        self.sock.connect(self.addr)
        if self.ssl:
            import ussl
            self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
        msg = bytearray(b"\x10\0\0\x04MQTT\x04\x02\0\0")
        msg[1] = 10 + 2 + len(self.client_id)
        msg[9] = clean_session << 1
        if self.user is not None:
            msg[1] += 2 + len(self.user) + 2 + len(self.pswd)
            msg[9] |= 0xC0
        if self.keepalive:
            assert self.keepalive < 65536
            msg[10] |= self.keepalive >> 8
            msg[11] |= self.keepalive & 0x00FF
        if self.lw_topic:
            msg[1] += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
            msg[9] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
            msg[9] |= self.lw_retain << 5
        self.sock.write(msg)
        #print(hex(len(msg)), hexlify(msg, ":"))
        self._send_str(self.client_id)
        if self.lw_topic:
            self._send_str(self.lw_topic)
            self._send_str(self.lw_msg)
        if self.user is not None:
            self._send_str(self.user)
            self._send_str(self.pswd)
        resp = self.sock.read(4)
        assert resp[0] == 0x20 and resp[1] == 0x02
        if resp[3] != 0:
            raise MQTTException(resp[3])
        return resp[2] & 1

    def disconnect(self):
        self.sock.write(b"\xe0\0")
        self.sock.close()

    def ping(self):
        self.sock.write(b"\xc0\0")

    def publish(self, topic, msg, retain=False, qos=0):
        pkt = bytearray(b"\x30\0\0\0")
        pkt[0] |= qos << 1 | retain
        sz = 2 + len(topic) + len(msg)
        if qos > 0:
            sz += 2
        assert sz < 2097152
        i = 1
        while sz > 0x7f:
            pkt[i] = (sz & 0x7f) | 0x80
            sz >>= 7
            i += 1
        pkt[i] = sz
        #print(hex(len(pkt)), hexlify(pkt, ":"))
        self.sock.write(pkt, i + 1)
        self._send_str(topic)
        if qos > 0:
            self.pid += 1
            pid = self.pid
            struct.pack_into("!H", pkt, 0, pid)
            self.sock.write(pkt, 2)
        self.sock.write(msg)
        if qos == 1:
            while 1:
                op = self.wait_msg()
                if op == 0x40:
                    sz = self.sock.read(1)
                    assert sz == b"\x02"
                    rcv_pid = self.sock.read(2)
                    rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
                    if pid == rcv_pid:
                        return
        elif qos == 2:
            assert 0

    def subscribe(self, topic, qos=0):
        assert self.cb is not None, "Subscribe callback is not set"
        pkt = bytearray(b"\x82\0\0\0")
        self.pid += 1
        struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
        #print(hex(len(pkt)), hexlify(pkt, ":"))
        self.sock.write(pkt)
        self._send_str(topic)
        self.sock.write(qos.to_bytes(1, 'little'))
        while 1:
            op = self.wait_msg()
            if op == 0x90:
                resp = self.sock.read(4)
                #print(resp)
                assert resp[1] == pkt[2] and resp[2] == pkt[3]
                if resp[3] == 0x80:
                    raise MQTTException(resp[3])
                return

    # Wait for a single incoming MQTT message and process it.
    # Subscribed messages are delivered to a callback previously
    # set by .set_callback() method. Other (internal) MQTT
    # messages processed internally.
    def wait_msg(self):
        res = self.sock.read(1)
        self.sock.setblocking(True)
        if res is None:
            return None
        if res == b"":
            raise OSError(-1)
        if res == b"\xd0":  # PINGRESP
            sz = self.sock.read(1)[0]
            assert sz == 0
            return None
        op = res[0]
        if op & 0xf0 != 0x30:
            return op
        sz = self._recv_len()
        topic_len = self.sock.read(2)
        topic_len = (topic_len[0] << 8) | topic_len[1]
        topic = self.sock.read(topic_len)
        sz -= topic_len + 2
        if op & 6:
            pid = self.sock.read(2)
            pid = pid[0] << 8 | pid[1]
            sz -= 2
        msg = self.sock.read(sz)
        self.cb(topic, msg)
        if op & 6 == 2:
            pkt = bytearray(b"\x40\x02\0\0")
            struct.pack_into("!H", pkt, 2, pid)
            self.sock.write(pkt)
        elif op & 6 == 4:
            assert 0

    # Checks whether a pending message from server is available.
    # If not, returns immediately with None. Otherwise, does
    # the same processing as wait_msg.
    def check_msg(self):
        self.sock.setblocking(False)
        return self.wait_msg()

Smart light-manifest.json

JSON
This is a device manifest that should be imported on WolkAbout IoT Platform in order to be able to create a device.
{
  "id": 873,
  "name": "Smart light",
  "protocol": "JsonSingleReferenceProtocol",
  "description": "",
  "deviceType": "STANDARD",
  "connectivityType": "MQTT_BROKER",
  "published": false,
  "feeds": [],
  "actuators": [
    {
      "id": 793,
      "name": "Switch",
      "reference": "SW",
      "description": "",
      "unit": {
        "id": 145,
        "name": "BOOLEAN(ACTUATOR)",
        "symbol": null,
        "readingTypeId": 31,
        "system": "CUSTOM",
        "precision": 1,
        "context": null,
        "inUse": true,
        "readingTypeName": "SWITCH(ACTUATOR)"
      },
      "minimum": 0,
      "maximum": 1,
      "readingType": {
        "id": 31,
        "name": "SWITCH(ACTUATOR)",
        "dataType": "BOOLEAN",
        "size": 1,
        "labels": null,
        "iconName": "ico_switcher"
      }
    }
  ],
  "alarms": [],
  "configs": [],
  "generallyAvailable": false
}

WolkConnect-MicroPython

Library for connecting MicroPython enabled devices to WolkAbout IoT Platform

Credits

WolkWriter

WolkWriter

6 projects • 14 followers
With WolkAbout IoT Platform, we give you proven technology to develop powerful IoT applications and control your business ecosystem.

Comments