Wolfie-Home / webserver2

IFTTT-like web server that connects IoT devices, written in Python Flask and ReactJS
6 stars 1 forks source link

Define Langauge For IFTTT #24

Open cjackie opened 7 years ago

cjackie commented 7 years ago

@kbumsik Not sure if this is the right way to solve this problem. I will give my thought.

We need to define a simple language that can be interpreted by the remote server. Something like:

Condition

a statement that can evaluated into True or False definition

Condition =>
     Operation Condition Condition |
     Operation Condition |
     DeviceValueName 'IS' DeviceValue

Operation => AND | OR | NOT

DeviceValueName => String
DeviceValue => String | Number

Action

a statement that can have effects on IoT devices

Action =>
    'SET' DeviceValueName DeviceValue

IFTTT Script

Script =>
  'IF' Condition 'THEN' Action

We have a user interface that will generate the script according to this definition, then we have a backend script engine that interpret and execute it.

For example, a script can be

IF AND relay IS 1 OR Led2 IS 0 Led1 IS 1 THEN SET Led3 0

And the backend script engine parses this, and execute it

kbumsik commented 7 years ago

Hmm I was just thinking to write structured JSON data to specify the operation. But this one sounds more interesting and make sense to me :) I think there should be a good reference designing this. Let me find one

kbumsik commented 7 years ago

I came up with a language design borrowing your idea. The format of below specification is based on EBNF.

Device = DeviceName'.'Property ;

Value = String | Number ;

Condition = 
    Device ('='|'>'|'<') Value ;

Action =
    'SET' Device '=' Value ;

IFTTTCommend =
    'IF' Condition {('OR'|'AND') Condition} 'THEN' {Action} 'END' ;

And the number of whitespace doesn't care. So we can write like the following as an example:

IF
    RPi_1.Button = "On"
    AND Gyro.x > 1.56
THEN
    SET RPi_1.Relay = 0
    SET RPi_2.Power = "Off"
END

What do you hink? BTW, lets start with a simpler way, supporting up to 2 conditions and only one action first.

cjackie commented 7 years ago

how do you parse and interpret it?

kbumsik commented 7 years ago

Parsing? No problem!

Catch'em all Regex!!

import re

# Want explanation? Use https://regex101.com/ . This site will explain MUCH better than me :)
ifttt_re = re.compile(r'IF\s+([\w\W]*)\s+THEN\s+([\w\W]*)\s+END')
condition_1st_re = re.compile(r'(\w+)\.(\w+)\s+(\=|\>|\<)\s+(\"\w*\"|[-+]?\d*\.\d+|[-+]?\d+)')
condition_re = re.compile(r'(AND|OR)\s+(\w+)\.(\w+)\s+(\=|\>|\<)\s+(\"\w*\"|[-+]?\d*\.\d+|[-+]?\d+)')
action_re = re.compile(r'SET\s+(\w+)\.(\w+)\s+(\=|\>|\<)\s+(\"\w*\"|[-+]?\d*\.\d+|[-+]?\d+)')

script = '''IF
    RPi_1.Button = "On"
    AND Gyro.x > 1.56
THEN
    SET RPi_1.Relay = 0
    SET RPi_2.Power = "Off"
END'''

ifttt = ifttt_re.match(script)
print(ifttt)
if ifttt:
    # Capture condition and action
    condition_group = ifttt.group(1)
    action_group = ifttt.group(2)
    print("==== Condition START ====")
    # Get first condition
    condition_match = condition_1st_re.match(condition_group)
    print("First condtition")
    print("\tDevice: ", condition_match.group(1))
    print("\tProperty: ", condition_match.group(2))
    print("\tComparator: ", condition_match.group(3))
    print("\tValue: ", condition_match.group(4))
    i = 1

    # Capture the rest of conditions
    for matched in condition_re.finditer(condition_group):
        i += 1
        print(i, "th action")
        print("\tOperator: ", matched.group(1))
        print("\tDevice: ", matched.group(2))
        print("\tProperty: ", matched.group(3))
        print("\tComparator: ", matched.group(4))
        print("\tValue: ", matched.group(5))
    print("==== Condition END ====")
    # Capture all actions
    print("==== Action START ====")
    i = 0
    for matched in action_re.finditer(action_group):
        i += 1
        print(i, "th action")
        print("\tDevice: ", matched.group(1))
        print("\tProperty: ", matched.group(2))
        print("\tComparator: ", matched.group(3))
        print("\tValue: ", matched.group(4))
    print("==== Action END ====")

Result

==== Condition START ====
First condtition
    Device:  RPi_1
    Property:  Button
    Comparator:  =
    Value:  "On"
2 th action
    Operator:  AND
    Device:  Gyro
    Property:  x
    Comparator:  >
    Value:  1.56
==== Condition END ====
==== Action START ====
1 th action
    Device:  RPi_1
    Property:  Relay
    Comparator:  =
    Value:  0
2 th action
    Device:  RPi_2
    Property:  Power
    Comparator:  =
    Value:  "Off"
==== Action END ====
kbumsik commented 7 years ago

For execution, I need to think the design yet. But it is almost there. :P