google / python-laurel

Python control of C by GE Bluetooth lightbulbs
Apache License 2.0
60 stars 18 forks source link

No Error Control if there are defunct devices #7

Open williamkennyAK opened 4 years ago

williamkennyAK commented 4 years ago

Got it working!!! Submitted changes...

The biggest change was error handling for properties as I was getting this error:

Failed to connect to F4:BC:DA:3F:E4:39 Failed to connect to peripheral F4:BC:DA:3F:E4:39, addr type: public
Traceback (most recent call last):
File "ge_plug.py", line 7, in
devices.devices[0].network.connect()
File "/home/wkenny/.local/lib/python3.8/site-packages/laurel/init.py", line 140, in connect
raise Exception("Unable to connect to mesh %s" % self.address)
Exception: Unable to connect to mesh 90E6BE00E748
if 'error' in properties:
                continue
            else:
                for bulb in properties['bulbsArray']:
                    id = int(bulb['deviceID'][-3:])
                    mac = [bulb['mac'][i:i+2] for i in range(0, 12, 2)]
                    mac = "%s:%s:%s:%s:%s:%s" % (mac[5], mac[4], mac[3], mac[2], mac[1], mac[0])
                    if network is None:
                        network = laurel_mesh(mesh['mac'], mesh['access_key'], self.mesh)
                    device = laurel_device(network, {'name': bulb['displayName'], 'mac': mac, 'id': id, 'type': bulb['deviceType']})
                    network.devices.append(device)
                    self.devices.append(device)

Here's what I'm doing with it...

Using the C by GE Smart Plug to flash Halloween lights on and off to "look like lightning" on my front porch. It should work with the bulbs too, but I don't have any to test it on.

This is my absolute first time writing anything in Python. I think I'm hooked.

#!/usr/bin/env python3
# flashy_lights.py
# William Kenny
# william.j.kenny.ak@gmail.com
# 
# Utility to flash "holiday" lights using C by GE Smart Plugs
# Load up required modules first...
import laurel #module to communicate with C by GE products
import time
import random
import sys
import shelve
import argparse

# Let's deal with command line variables...
parser = argparse.ArgumentParser()
parser.add_argument("-tm","--timing_min", type=float, default=0.1, help='time lights will be flashed on for (0.1)')
parser.add_argument("-tmm","--timing_max", type=float, default=0.6, help='time lights will be flashed off for (0.6)')
parser.add_argument("-lm","--loop_min", type=int, default=3, help='min number of times lights will be flashed (3)')
parser.add_argument("-lmm","--loop_max", type=int, default=7, help='max number of times lights will be flashed (7)')
parser.add_argument("-r","--resting_state",action='store_true',default=False, help='state lights will be in when not flashing -- True is on, False is off. (False)')
parser.add_argument("-rt","--run_time", type=int, default=5, help='time in minutes script will run for in minutes (5)')
parser.add_argument("-p","--print_args",action='store_true', help='print arguments (False)')
parser.add_argument("-t", "--test_timing",action='store_true', help='test timing functions only (False)')
parser.add_argument("-f", "--flash", action='store_true', help='flash the lights once for testing (False)')
parser.add_argument("-v", "--verbose", action='store_true', help='sets verbose mode (False)')
parser.add_argument("-l", "--loop", action='store_true', help='do one loop and exit (False)')
parser.add_argument("-blm", "--between_loops_min", type=int, default=5,help='minimum time between loops in seconds (5)')
parser.add_argument("-blmm", "--between_loops_max", type=int, default=15,help='maximum time between loops in seconds (25)')
args = parser.parse_args()

#List out the arguments if we tell it to...
if args.print_args == True or args.verbose == True:
    print(f'timing_min      = {args.timing_min}')
    print(f'timing_max      = {args.timing_max}')
    print(f'loop_min        = {args.loop_min}')
    print(f'loop_max        = {args.loop_max}')
    print(f'resting_state   = {args.resting_state}')
    print(f'run_time        = {args.run_time}')
    print(f'print_args      = {args.print_args}')
    print(f'test_timing     = {args.test_timing}')
    print(f'flash           = {args.flash}')
    print(f'verbose         = {args.verbose}')

#Let's flash the lights
# function flash_it
#    turns light on, then off using predetermined lengths of on and off times
#    timing_on  - float - time lights will be on (resolution to 2 decimal points, no less than .10
#    timing_off - float - time lights will be off (resolution to 2 decimal points, no less than .10
def flash_it(timing_on,timing_off,v):
    #turn lights on
    if v == True:
        print(f' ON for {timing_on} seconds.')
    if args.test_timing == False:
        devices.devices[0].set_power(True)
    #wait before we turn it off for <timeon> seconds
    time.sleep(timing_on)
    #turn lights off
    if v == True:
        print(f' OFF for {timing_off} seconds.')
        print()
    if args.test_timing == False:
        devices.devices[0].set_power(False)
    #wait before we move on for <timeoff> seconds
    time.sleep(timing_off)

# function get_flash_it_timing
#    randomly generates timing for "flash_it" using 2 floating point numbers
#    tm - float - minimum time lights will be on/off
#    tmm - float - maximum time lights will be on/off
def get_flash_it_timing(tm,tmm):
    return round(random.uniform(tm,tmm),2)

# function get_loop_amount
#    randomly sets number of times "flash_it" will loop
#    loop_min - int - minimum times "flash_it" will loop
#    loop_max - int - maximum times "flash_it" will loop
def get_loop_amount(lm,lmm):
    return random.randint(lm,lmm)

# function get_loop_timing
#    randomly sets time between loops
#    between_loops_min - int - minimum time between loops
#    between_loops_max - int - maximum time between loops
def get_loop_timing(blm,blmm):
    return random.randint(blm,blmm)

# function cache_it
#    caches variables to file so we can save some time
#    file_name - path to file where data will be cached
param = ""
def cache_it(file_name):
    d = shelve.open(file_name)
    def decorator(func):
        def new_func(param):
            if param not in d:
                d[param] = func(param)
            return d[param]
        return new_func
    return decorator

# function get_devices(param) -- this is cached by using @chache_it(file_name)
#    This function will usually take 5-20 seconds depending on internet latency and server response times.
#    param - cached data...
#    Let's set our filename to use for caching our data...
file_name = "flashy_lights_devices.cache"
@cache_it(file_name)
def get_devices(param):
    #I want to find a way to hash the password...  More to come...
    return(laurel.laurel("MyEmail","MyPassword",True))

# Let's get to it already!!!

#First, we need to connect...
if args.test_timing == False:
    devices = get_devices(param)
    devices.devices[0].network.connect()
#Should we do a test flash?
if args.flash == True:
    timing_on = get_flash_it_timing(args.timing_min,args.timing_max)
    timing_off = get_flash_it_timing(args.timing_min,args.timing_max)
    flash_it(timing_on,timing_off,args.verbose)

if args.loop == True:
    num_loops = get_loop_amount(args.loop_min,args.loop_max)
    if args.verbose == True:
        print(f'looping {num_loops} times')
    i = 0
    while i < num_loops:
        timing_on = get_flash_it_timing(args.timing_min,args.timing_max)
        timing_off = get_flash_it_timing(args.timing_min,args.timing_max)
        flash_it(timing_on,timing_off,args.verbose)
        i+=1

if args.loop == True or args.flash == True:
    args.run_time = 0

if args.run_time > 0:
    if args.verbose == True:
        if args.resting_state == False:
            state = "off"
            if args.test_timing == False:
                devices.devices[0].set_power(False)
        else:
            state = "on"
            if args.test_timing == False:
                devices.devices[0].set_power(False)
        print(f'Lights will be {state} between flash_it loops.')
    t_end = time.time() + 60*args.run_time
    while time.time() < t_end:
        num_loops = get_loop_amount(args.loop_min,args.loop_max)
        if args.verbose == True:
            print(f'looping {num_loops} times')
        i = 0
        while i < num_loops:
            timing_on = get_flash_it_timing(args.timing_min,args.timing_max)
            timing_off = get_flash_it_timing(args.timing_min,args.timing_max)
            flash_it(timing_on,timing_off,args.verbose)
            i+=1
        pause_time = get_loop_timing(args.between_loops_min,args.between_loops_max)
        if args.verbose == True:
            print(f'Waiting for {pause_time} seconds.')
        time.sleep(pause_time)

If anyone has any suggestions to improve this, please let me know...

mjg59 commented 3 years ago

Thanks, I hit this independently and have a fix in https://github.com/mjg59/python-laurel