There are an increasing number of WiFi enabled lights. Some are purely WiFi, others use some protocol such as Zigbee to talk to a hub which in turn has a WiFi interface. Included in the second group are the Phillips Hue, the Cree lights, the GE lights, while in the first group are LIFX. IKEA lights and Yeelight. This chapter looks at the Yeelight bulbs, as both typical examples and with its own unique features.
Yeelight make a number of lights, including a ceiling light, desk light, and screw-in GU10 white and colour bulbs. In Australia these run on 240 volts.
Yeelight have apps for IoS and Android. They also claim
to integrate with IFTT, Amazon Alexa and Google Assistant.
Linking a Yeelight to Google Assistant is described
at
Yeelight work with Google Home
.
It took me a while to get mine working, as I somehow managed to
create two accounts on xiaomi.com
, one for my
email and a separate one for my phone. Only one can be linked
to the Yeelight and I kept trying the other, until I discovered
my error (thanks to
yusure
of the Yeelight staff - very patient and
helpful!)
The Google app works fine, and presumably the Amazon app works fine too.
The voice commands supported by Google Home are
The following commands are supported: Turn on/off: 1. Turn on/off [target_device] Dim: 1. Dim [target_device] 2. Dim [target_device] to xx% 3. Set brightness of [target_device] to xx% 4. Turn brightness of [target_device] to maximum/minimum Color: 1. Set [target_device] to [color] 2. Change [target_device] to [color] Scene: 1. Activate [target_scene] Notice: 1. target_device could be described as “device name”, “all lights”, “all lights in some room” 2. target_scene is the scene you setup in Yeelight app
Most of the home IoT devices require internet access to a cloud server, so that whatever you do with the device is potentially recorded at a third party site. This method of access is probably required if you need to control the device from a remote location, but if you are on the same network, there shouldn't be any need for this.
Yeelight recognise this and offer a mechanism whereby you can control a Yeelight directly on your LAN. This is described in the Yeelight WiFi Light Inter-Operation Specification .
What that document doesn't describe is how to set up your
Yeelight to work on the LAN. You have to enable
the "Control LAN" mode, which is found by selecting a
yeelight device, scrolling down to the bottom of the screen
and choosing the rightmost icon and then the Control LAN
icon. The following images from Yeelight staff show this:
Note taht if you do a hard reset on your bulb, you will need to additionally reset Lan Control.
The Yeelight when enabled for LAN mode broadcasts about every 3 minutes a multicast packet to multicast group 239.255.255.250 on port 1982. This looks like
NOTIFY * HTTP/1.1
Host: 239.255.255.250:1982
Cache-Control: max-age=3584
Location: yeelight://192.168.1.25:55443
NTS: ssdp:alive
Server: POSIX, UPnP/1.0 YGLC/1
id: 0x00000000035ed2f1
model: color
fw_ver: 57
support: get_prop set_default set_power toggle set_bright start_cf stop_cf set_scene cron_add cron_get cron_del set_ct_abx set_rgb set_hsv set_adjust adjust_bright adjust_ct adjust_color set_music set
power: on
bright: 50
color_mode: 1
ct: 4000
rgb: 16261119
hue: 359
sat: 100
The fourth line is the important one for control and information.
It says that the Yeelight will be running a TCP server
at address 192.168.1.25 on port 55443.
A python program to listen and print this information is listen.py:
import socket
import struct
import datetime
MCAST_GRP = '239.255.255.250'
MCAST_PORT = 1982
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', MCAST_PORT)) # use MCAST_GRP instead of '' to listen only
# to MCAST_GRP, not all groups on MCAST_PORT
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
print ('Listening')
while True:
print sock.recv(10240)
print datetime.datetime.now()
In addition, a muticast query can be made of all Yeelight bulbs on the network, using the same multicast address and port. A bulb will reply on the same port as the request came from. This is typically a random port, so it is easiest to fix the port. A Python program to do this is send.py:
import socket
import struct
MCAST_GRP = '239.255.255.250'
MCAST_PORT = 1982
SRC_PORT = 5159 # my random port
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.bind(('', SRC_PORT))
sock.sendto("M-SEARCH * HTTP/1.1\r\n\
HOST: 239.255.255.250:1982\r\n\
MAN: \"ssdp:discover\"\r\n\
ST: wifi_bulb\r\n", (MCAST_GRP, MCAST_PORT))
# close this multicast socket
sock.close()
# and open a new receive socket on the same port
sock_recv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock_recv.bind(('', SRC_PORT))
while True:
print sock_recv.recv(10240)
Commands are sent on the TCP port as a JSON string. The set of commands are given in the Yeelight WiFi Light Inter-Operation Specification . Results are returned as a JSON string, which can be parsed in Python using the json package.
The following program uses multicast to get the TCP address
and port and then executes a series of commands on the first
light that answers.
It defines procedures to get_prop
, set_power
and set_bright
. Each command opens a new TCP socket,
sends the command, parses the response, closes the socket
and returns the result.
The program is command.py:
import socket
import struct
import re
import json
import time
MCAST_GRP = '239.255.255.250'
MCAST_PORT = 1982
SRC_PORT = 5159 # my random port
CR_LF = "\r\n"
def get_ip_port():
return ('192.168.1.25', 55443)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.bind(('', SRC_PORT))
# need to ensure byte format for Python3
command = b"M-SEARCH * HTTP/1.1\r\n\
HOST: 239.255.255.250:1982\r\n\
MAN: \"ssdp:discover\"\r\n\
ST: wifi_bulb\r\n"
#sock.sendto(bytes(command, 'utf-8'), (MCAST_GRP, MCAST_PORT))
sock.sendto(command, (MCAST_GRP, MCAST_PORT))
sock.close()
sock_recv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
socket.IPPROTO_UDP)
# ensure this socket is listening on the same
# port as the multicast went on
sock_recv.bind(('', SRC_PORT))
response = sock_recv.recv(10240)
response = response.decode('utf-8')
sock_recv.close()
# match on a line like "Location: yeelight://192.168.1.25:55443"
# to pull ip out of group(1), port out of group(2)
prog = re.compile("Location: yeelight://(\d*\.\d*\.\d*\.\d*):(\d*).*")
for line in response.splitlines():
result = prog.match(line)
if result != None:
ip = result.group(1)
port = result.group(2)
return (ip, int(port))
return (None, None)
def sendto(ip, port, command):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
sock.connect((ip, port))
sock.send(bytes(command + CR_LF, 'utf-8'))
response = sock.recv(10240)
#print(response)
# the response is a JSON string, parse it and return
# the "result" field
dict = json.loads(response)
sock.close()
#print "Response was ", response
return(dict["result"])
def get_prop(prop, ip, port):
# hard code the JSON string
command = '{"id":1,"method":"get_prop","params":["' + prop + '"]}'
response = sendto(ip, port, command)
return response
def set_prop(prop, params, ip, port):
# hard code the JSON string
command = '{"id":1,"method":"set_' + prop +\
'", "params":' + params +\
'}'
print(command)
response = sendto(ip, port, command)
return response
def set_name(name, ip, port):
params = '["' + name + '"]'
response = set_prop('name', params, ip, port)
return response
def set_power(state, ip, port):
params = '["' + state + '", "smooth", 500]'
response = set_prop('power', params, ip, port)
return response
def set_bright(state, ip, port):
params = '[' + str(state) + ', "smooth", 500]'
response = set_prop('bright', params, ip, port)
return response
if __name__ == "__main__":
print('Starting')
(ip, port) = get_ip_port()
if (ip, port) == (None, None):
print("Can't get address of light")
exit(1)
print('IP is ', ip, ' port is ', port)
time.sleep(5)
# sample set commands:
success = set_power("on", ip, port)
print('Power set is', success[0])
time.sleep(5)
# sample set commands:
success = set_power("off", ip, port)
print('Power set is', success[0])
#exit()
time.sleep(5)
# sample set commands:
success = set_power("on", ip, port)
print('Power set is', success[0])
success = set_bright(90, ip, port)
print('Brightness set is', success[0])
name = set_name('bedroom', ip, port)
print('Name is ', name[0])
# sample get commands:
power = get_prop("power", ip, port)
print('Power is', power[0])
bright = get_prop("bright", ip, port)
print("Bright is ", bright[0])
name = get_prop('name', ip, port)
print("name now is", name[0])
# getting multiple properties at once.
# Be careful with the quotes, words need to be separated by "," !
prop_list = get_prop('name", "power", "bright', ip, port)
print("Property list is", prop_list)
Copyright © Jan Newmarch, jan@newmarch.name
"The Internet of Things - a techie's viewpoint" by Jan Newmarch is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Based on a work at https://jan.newmarch.name/IoT/.
If you like this book, please donate using PayPal