* Python * Controls ECHONET Lite compatible home appliances (emulators)

Introduction

This article can be understood by anyone who has used Flask and has written a socket communication program. Let's try controlling home appliances using Flask of Python. Node.js is often used to control home appliances, but since the number of people using Python is increasing, I will use Python to control home appliances. First, as the communication protocol, ECHONET Lite, which is used as standard when controlling home appliances, is used. Not many home appliances are compatible with ECHONET Lite. However, even if it cannot be controlled by ECHONET Lite, it may be possible to control it from there if WebAPI etc. is provided. When finding a home appliance to control, try searching for WebAPI as well as ECHONET Lite. Next, regarding the home appliances to be controlled this time, it is difficult to prepare ECHONET LITE compatible home appliances, so we will use an emulator instead.

Premise

The development environment is as follows. This time we will use two PCs. --PC for running the emulator - Windows 10 (64bit) --Java installed

--PC for executing home appliance control program - Python 3.8.2 - Ubuntu 18.04.5 LTS

Run emulator

The emulator uses MoekadenRoom, so go to GitHub page and go to README.md Download the compressed file from Download executables of. Extract the compressed file and execute MoekadenRoom.exe. To execute it, Java must be installed. If Java is not installed, please install it from here. When you execute MoekadenRoom.exe, the emulator will be displayed as shown below. You can operate home appliances with GUI, so please try it.

Control of home appliances (emulator) by Python (no control command / response)

Send control commands to home appliances with Python. Since it is almost the same as a general socket communication client program, the sample code is shown first. If you execute the sample code while MoekadenRoom is running, the emulator lights will turn on.

Sample code

echonet_lite.py


import socket
import binascii


def send_command():
    #IP address of the PC running Moekaden Room (change each one)
    ip = '192.168.0.10'
    #ECHONET Lite uses UDP port 3610
    ECHONETport = 3610
    format_echonet_lite = ['EHD', 'TID', 'SEOJ', 'DEOJ', 'ESV', 'OPC', 'EPC', 'PDC', 'EDT']
    data_format = {
        format_echonet_lite[0]: '1081',
        format_echonet_lite[1]: '0000',
        format_echonet_lite[2]: '05FF01',
        format_echonet_lite[3]: '029001',
        format_echonet_lite[4]: '60',
        format_echonet_lite[5]: '01',
        format_echonet_lite[6]: '80',
        format_echonet_lite[7]: '01',
        format_echonet_lite[8]: '30'  #You can turn off the lights by changing to 31
    }

    frame = ''
    for key in format_echonet_lite:
        frame += data_format[key]

    #Convert a hexadecimal string to a byte string
    msg = binascii.unhexlify(frame)

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.sendto(msg, (ip, ECHONETport))
    
    
if __name__ == '__main__':
    send_command();

Frame to transfer

This is an explanation of the sample code. Creates a message to be forwarded based on the format of the ECHONET Lite frame. Please refer to the following page for the explanation about the frame. -How to create a telegram for ECHONET Lite -ECHONET Lite Standard Chapter 3 Telegram Structure

Refer to the above page and enter values in ʻEHD, TID, SEOJ, DEOJ, ESV, OPC, EPC, PDC, EDT`. Please check the above page for details. The value is a hexadecimal number.

Telegram component Value (description)
ETD 1081 (ECHONET protocol type, 1081 is ECHONET Lite)
TID 0000 (An ID for associating a request and a response, which can be any numerical value.)
SEOJ 05FF01 (Indicates the source device. The value of the controller is entered.
reference:Detailed regulations for device objects)
DEOJ 029001 (The device (lighting) of the destination is shown. I put the value of lighting)
ESV 60 (60 is a control command with no response from the destination)
OPC 01 (Shows the number of properties to process in one telegram)
EPC 80 (Specify the property.MoekadenRoomReadme.SearchforLightningEPCfrommd)
PDC 01 (Indicates the number of bytes in EDT. 01 because there is only one control content)
EDT 30 (Enter the value according to the proper name specified by EPC.MoekadenRoomReadme.SearchforEDTtoturnonthelightfrommd)

Control of home appliances (emulator) by Python (with control commands and responses)

The previous control command was unresponsive. Here, we will send a control command and receive a response from the home appliance. As before, turn on the emulator lamp. The difference is that you receive a response. In other words, we created only the socket communication client program earlier, but here we also need to create the socket communication server program in order to receive the response. We will explain two methods, one is to separate the files for the client program and the server program and execute them separately, and the other is to combine them into one file. First, I will explain from the simple former.

How to separate the client and server into separate files

The client program is almost the same as before. It is the ECHONET Lite frame value that is changed. By setting the value of ʻESV to 61`, you can send a control command with a response.

echonet_lite.py


import socket
import binascii


def send_command():
    #IP address of the PC running Moekaden Room (change each one)
    ip = '192.168.0.10'
    #ECHONET Lite uses UDP port 3610
    ECHONETport = 3610
    format_echonet_lite = ['EHD', 'TID', 'SEOJ', 'DEOJ', 'ESV', 'OPC', 'EPC', 'PDC', 'EDT']
    data_format = {
        format_echonet_lite[0]: '1081',
        format_echonet_lite[1]: '0000',
        format_echonet_lite[2]: '05FF01',
        format_echonet_lite[3]: '029001',
        format_echonet_lite[4]: '61', #Change the previous program (60 → 61)
        format_echonet_lite[5]: '01',
        format_echonet_lite[6]: '80',
        format_echonet_lite[7]: '01',
        format_echonet_lite[8]: '30' 
    }

    frame = ''
    for key in format_echonet_lite:
        frame += data_format[key]

    #Convert a hexadecimal string to a byte string
    msg = binascii.unhexlify(frame)

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.sendto(msg, (ip, ECHONETport))
    
    
if __name__ == '__main__':
    send_command();

The program that receives the response is no different from a general socket communication server program. However, since the response is received as a byte string, it must be converted to a hexadecimal string. Since the data received at the time of response is in the same format as the frame sent earlier, the contents can be read by converting it to a hexadecimal character string.

socket_server.py


import socket
import binascii

def receive_state():
    ECHONETport = 3610

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('', ECHONETport))
    data, addr = sock.recvfrom(4096)
    
    #Convert a byte string to a hexadecimal string
    data = str(binascii.hexlify(data), 'utf-8')
    print(data)


if __name__ == '__main__':
    receive_state()

First, start MoekadenRoom and execute the server program and then the client program in that order. As a result of executing the server program, the following output will be obtained.

$ python3 socket_server.py
#Output result
1081000002900105ff0171018000

When the output result is read, it will be as follows. From the value of ʻEPC`, we can see that it is a response to the control command.

Telegram component Value (description)
ETD 1081 (ECHONET protocol type, 1081 is ECHONET Lite)
TID 0000 (An ID for associating a request and a response, which can be any numerical value.)
SEOJ 029001 (Source device: Lighting)
DEOJ 05FF01 (Destination device: Controller)
ESV 71 (The response to 61 is returned with 71)
OPC 01 (Number of properties to process in one telegram)
EPC 80 (Property specification. Lighting EPC)
PDC 00 (Number of bytes in EDT. 00 because it is not control)
EDT Sky(Do not specify EDT because it is not a control)

How to combine client and server into one file

In the server program, it waits until it receives the data (blocking), so the processing stops at that part. To process the client program at the same time, it is better to use thread. The server program is processed in a separate thread.

To pass the process to another thread, create an instance of the Thread class and specify the function you want to process in the other thread in the argument target. Start thread with the start method.

th = Thread(target=function)
th.start()

Now, here is the sample code. It is a program that sends a control command to the lighting and returns a response 3 seconds after the program is executed.

echonet_lite.py


import socket
import binascii
from threading import Thread
import time

def send_command():
    #IP address of the PC running Moekaden Room (change each one)
    ip = '192.168.0.10'
    ECHONETport = 3610
    format_echonet_lite = ['EHD', 'TID', 'SEOJ', 'DEOJ', 'ESV', 'OPC', 'EPC', 'PDC', 'EDT']
    data_format = {
        format_echonet_lite[0]: '1081',
        format_echonet_lite[1]: '0000',
        format_echonet_lite[2]: '05FF01',
        format_echonet_lite[3]: '029001',
        format_echonet_lite[4]: '61',
        format_echonet_lite[5]: '01',
        format_echonet_lite[6]: '80',
        format_echonet_lite[7]: '01',
        format_echonet_lite[8]: '30',   #You can turn off the lights by changing to 31
    }

    frame = ''
    for key in format_echonet_lite:
        frame += data_format[key]
    msg = binascii.unhexlify(frame)

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.sendto(msg, (ip, ECHONETport))
    print('send')


def receive_state():
    ECHONETport = 3610

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('', ECHONETport))
    data, addr = sock.recvfrom(4096)
    data = str(binascii.hexlify(data), 'utf-8')
    print('receive')
    print(data)


if __name__ == '__main__':
    th = Thread(target=receive_state)
    th.start()
    time.sleep(3)
    send_command()

Control of home appliances (emulator) by Python (state request)

As a control of home appliances, I will try to acquire the current state of home appliances. In addition to the control commands (with or without response) explained above, the status requests explained here are the main types of home appliance control. Since there is also a response to the status request, it is necessary to create both a client program and a server program for socket communication. In the status request, the value of ʻESVat the time of transmission is62, and the value of ʻESV at the time of response is 72. Also, since it is not a control command, the value of PDC is 00 and the value of ʻEDT is empty`. Based on these, the sample code is shown below. In the sample code, the response is output every 5 seconds. While executing the sample code, click the lighting button of Moekaden Room to switch it ON / OFF. The output EDT value should change.

echonet_lite.py


import socket
import binascii
from threading import Thread
import time

def receive_state():
    ECHONETport = 3610
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('', ECHONETport))
    while True:
        data, addr = sock.recvfrom(4096)
        data = str(binascii.hexlify(data), 'utf-8')
        print(data)

def confirm_state():
    ip = '192.168.0.10'
    ECHONETport = 3610
    format_echonet_lite = ['EHD', 'TID', 'SEOJ', 'DEOJ', 'ESV', 'OPC', 'EPC', 'PDC', 'EDT']
    data_format = {
        format_echonet_lite[0]: '1081',
        format_echonet_lite[1]: '0000',
        format_echonet_lite[2]: '05FF01',
        format_echonet_lite[3]: '029001',
        format_echonet_lite[4]: '62',
        format_echonet_lite[5]: '01',
        format_echonet_lite[6]: '80',
        format_echonet_lite[7]: '00',
        format_echonet_lite[8]: ''
    }

    frame = ''
    for key in format_echonet_lite:
        frame += data_format[key]
    msg = binascii.unhexlify(frame)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    while True:
        sock.sendto(msg, (ip, ECHONETport))
        time.sleep(5)
        
if __name__ == '__main__':
    th1 = Thread(target=receive_state)
    th1.start()
    confirm_state()

Cooperation with Flask

Finally, use Flask to set up an HTTP server to control home appliances. Managing the status of home appliances with an HTTP server is a method often used when controlling multiple home appliances. By applying this, it can be used to control home appliances from Android and also to collect IoT data. First, let's take a look at a simple program that controls Flask and home appliances as a step toward that end. Here, queue is used because multiple threads are used. Queue is a structure that can retrieve data in the order in which it was entered. The basic usage of queue is as follows.

#Creating a queue
q = Queue();
#Insert data into the queue with "put" and retrieve it with "get"
q_msg = {"name": "foge"}
q.put(q_msg)
q_result = q.get()
print(q_result["name"])

Now, it's a little complicated, but here's a sample code. To execute the program, first start MoekadenRoom, then execute the program, and finally access http://192.168.0.10:5000. It is a program that acquires the status of home appliances every 5 seconds and outputs them, and gives ON / OFF commands to home appliances every 10 seconds.

app.py


import socket
import binascii
from flask import Flask
from queue import Queue
from threading import Thread
import time

app = Flask(__name__)

q = Queue()
state_q = Queue()

@app.route('/', methods=['GET'])
def home():
    th1 = Thread(target=receive_state)
    th1.start()
    th2 = Thread(target=confirm_state)
    th2.start()
    th3 = Thread(target=send_command)
    th3.start()
    return 'This is a Sample Webpage'
    
def send_command():
    while True:
        time.sleep(10)
        if state_q.empty():
            state = '30'
        else:
            q_state_msg = state_q.get()
            q_state = q_state_msg['state']
            if q_state == '30':
                state = '31'
            else:
                state = '30'
        #IP address of the PC running Moekaden Room (change each one)
        ip = '192.168.0.10'
        ECHONETport = 3610
        format_echonet_lite = ['EHD', 'TID', 'SEOJ', 'DEOJ', 'ESV', 'OPC', 'EPC', 'PDC', 'EDT']
        data_format = {
            format_echonet_lite[0]: '1081',
            format_echonet_lite[1]: '0000',
            format_echonet_lite[2]: '05FF01',
            format_echonet_lite[3]: '029001',
            format_echonet_lite[4]: '60',
            format_echonet_lite[5]: '01',
            format_echonet_lite[6]: '80',
            format_echonet_lite[7]: '01',
            format_echonet_lite[8]: state  # state: 30 or 31 (ON or OFF)
        }
        frame = ''
        for key in format_echonet_lite:
            frame += data_format[key]
        msg = binascii.unhexlify(frame)
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.sendto(msg, (ip, ECHONETport))
        print('send_command')

def receive_state():
    ECHONETport = 3610
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('', ECHONETport))
    while True:
        data, addr = sock.recvfrom(4096)
        data = str(binascii.hexlify(data), 'utf-8')
        queue_msg = q.get()
        if state_q.empty():
            data_format = queue_msg['format']
            state = get_state(data, data_format)
            q_state_msg = {
                'state': state
            }
            state_q.put(q_state_msg)
        print(data)

def confirm_state():
    ip = '192.168.0.10'
    ECHONETport = 3610
    format_echonet_lite = ['EHD', 'TID', 'SEOJ', 'DEOJ', 'ESV', 'OPC', 'EPC', 'PDC', 'EDT']
    data_format = {
        format_echonet_lite[0]: '1081',
        format_echonet_lite[1]: '0000',
        format_echonet_lite[2]: '05FF01',
        format_echonet_lite[3]: '029001',
        format_echonet_lite[4]: '62',
        format_echonet_lite[5]: '01',
        format_echonet_lite[6]: '80',
        format_echonet_lite[7]: '00',
        format_echonet_lite[8]: ''
    }
    frame = ''
    for key in format_echonet_lite:
        frame += data_format[key]
    msg = binascii.unhexlify(frame)

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    while True:
        sock.sendto(msg, (ip, ECHONETport))
        print('send_confirm_state')
        queue_msg = {
            'format': data_format
        }
        q.put(queue_msg)
        time.sleep(5)

#A function to get the EDT value from a frame of a hexadecimal string
def get_state(data, data_format):
    data_list = []
    data_pos = 0
    for value in data_format.values():
        element_size = len(value)
        if element_size == 0:
            element_size = 2
        data_list.append(data[data_pos: data_pos + element_size])
        data_pos += element_size
    state = data_list[8]
    return state

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

At the end

I used ECHONET Lite in Python to control home appliances (emulators). This time, we focused on the simplest controllable lighting. Based on this sample code, try to realize various controls for home appliances. Since there is little information on controlling home appliances using Python, let's gradually increase the articles on Qiita. (Until you find the most suitable interface to control your home appliances ...)

Recommended Posts

* Python * Controls ECHONET Lite compatible home appliances (emulators)
Operate ECHONET Lite appliances with Python
Operate home appliances with Python and IRKit