Hello again and welcome back. This is part two in our four-part series
on firmware and embedded devices. Today, I will be discussing home
automation and the Internet of Things (IoT). More specifically, I'll
be talking about Blossom. Blossom is a cloud-based smart lawn
watering system that will 'automatically' water your lawn. Normally,
our goal is to break into the target device so I may inspect running
processes and resident binaries to ensure they are not designed to
work in ways that are counter to our interests. Today, I won't be
doing that. Instead, I am going to observe the functionality of the
device and how it interacts with the manufacturers cloud-based API.
Then, I'll force network traffic redirection from the device to a
server I control. Finally, I will recreate a bare minimum copy of the
manufacturer's API available internally so that the device will no
longer require internet access for a somewhat normal operation.
What does this mean? I am going to write an application to water my
lawn, when I want my lawn watered. Why? Because I like the
functionality of smart-enabled devices, but I do not like adding
network potential pivot points anywhere on my networks. My
hope is that this part in our series serves as a soft introduction
into the thought process I typically use when removing an unwanted
third-party from my networks or even attempting to attack the
underlying software of a target device.
So, how does the Blossom work? The first thing Blossom asks you to do
is move your wiring over to the new system, but I won't cover that.
Once you power it up, Blossom will start an Access Point that you can
connect to. The access point will be named as such: Blossom-XXXX where
'X' is a four digit number. Once you install the corresponding phone
app, it wants you to create an account and input some information
about the geographic location where the system resides. In retrospect,
since I don't care about managing my lawn settings while say ...
traveling or while being down the street at a friends, creating an
account and providing said information may be irrelevant. There is
also an HTTP portal and API which does not require any of that
information.
![]()
The wireless password for every Blossom unit is '12flowers'. Once
connected to the Blossom access point, you should visit the default
web portal (http://192.168.10.1). The latest Blossom firmware will
disable the access point after setup, but it will also disable the web
portal and API I use. My advice is to not update the firmware.
However, a bug in the older firmware exists where the access point
does not get torn down after setup. This was a concern to me as a
separate wireless adapter on the Blossom also exists, which is
connected to an internet routable network. A security issue in the
Blossom web application or WSGI API could result in another easy pivot
point. Fortunately, after emailing Blossom's support group they agreed
to provide me with a firmware build that would disable the access
point while retaining the web server and API. I wish other vendors
would be as responsive to my requests. Bravo Blossom!
![]()
Here you can configure a few things within the Blossom. The four main
sections within the UI are named: Provisioning, System Info, Advanced,
and Blossom. Provisioning allows you to connect Blossom to your
wireless access point. System Info displays basic network adapter and
operating system information. Advanced allows the wireless access
point settings to be changed, firmware to be upgraded, the device can
be rebooted, and reset to the factory issued state. The Blossom
section allows the user to change the 'Server Group' between Live,
Staging, and Test. I haven't tested to see if changing this value
alters system settings such as running services.
POST /sys/network HTTP/1.1
Host: [redacted]
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:42.0) Gecko/20100101 Firefox/42.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: http://[redacted]/
Content-Length: 175
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
{"ssid":"korelogicwashere","security":4,"key":"sillyfuntimes","ip":0,"ipaddr":"192.168.2.2","ipmask":"255.255.255.0","ipgw":"192.168.2.1","ipdns1":"0.0.0.0","ipdns2":"0.0.0.0"}
They've tried to hide some non-critical things, but it doesn't seem
like they put a lot of effort into it. For example, you can open and
close valves artibrarily and also change the LED colors! Their method
of hiding the functionality was to comment out the associated
javascript which displays that part of the UI. They didn't actually
remove the underlying functionality. So if you just view source:
![]()
Example requests for those looked like:
For the LED:
POST /bloom/led_custom HTTP/1.1
Host: [redacted]
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:42.0) Gecko/20100101 Firefox/42.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Content-Type: application/json; charset=utf-8
X-Requested-With: XMLHttpRequest
Referer: http://[redacted]/
Content-Length: 76
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
{"type":1,"r1":90,"g1":10,"b1":0,"r2":20,"g2":20,"b2":30,"t1":500,"t2":1000}
For valve control:
POST /bloom/valve HTTP/1.1
Host: [redacted]
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:42.0) Gecko/20100101 Firefox/42.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Content-Type: application/json; charset=utf-8
X-Requested-With: XMLHttpRequest
Referer: http://[redacted]/
Content-Length: 24
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
{"valve":1,"inverter":1}
At this point, my dest device is sitting on a rooted Linksys WRT54GL
wireless network. Inspecting the traffic is trivial from this position
within the network. Even more favorably, there is no encryption
between the device and the cloud-based API. As far as the type of
information within the network traffic, it's mostly non-sensitive
information pertaining to watering schedules, current weather
information, etc. However, it does also contain the approximate GPS
coordinates and physical address for the device. In my opinion, this
traffic probably should be encrypted. I used fake information but have
redacted the data shown here so as to not implicate unaffiliated third
parties. You know ...just in case.
HTTP/1.1 200 OK
Allow: GET, POST, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Date: Thu, 19 Nov 2015 22:58:38 GMT
Vary: Accept
transfer-encoding: chunked
Connection: keep-alive
{"latitude": [redacted], "longitude": [redacted], "zones": [{"id": 44318, "name": "Zone 1", "plant_type": 1,
"emitter_type": 5, "gets_rainfall": true, "user_watering_modulation": 1.0, "valve": 1, "active": true,
"illustration": null, "thumbnail_1": null, "thumbnail_2": null, "thumbnail_3": null, "thumbnail_4": null,
"schedule_manual_secs": 180, "auto_scheduling": true, "precipitation_rate": 16.51, "kc": 0.75},
{"id": 44319, "name": "Zone 2", "plant_type": 1, "emitter_type": 5, "gets_rainfall": true,
"user_watering_modulation": 1.0, "valve": 2, "active": true, "illustration": null, "thumbnail_1": null,
"thumbnail_2": null, "thumbnail_3": null, "thumbnail_4": null, "schedule_manual_secs": 180,
"auto_scheduling": true, "precipitation_rate": 16.51, "kc": 0.75}, {"id": 44320, "name": "Zone 3",
"plant_type": 1, "emitter_type": 5, "gets_rainfall": true, "user_watering_modulation": 1.0, "valve": 3,
"active": true, "illustration": null, "thumbnail_1": null, "thumbnail_2": null, "thumbnail_3": null,
"thumbnail_4": null, "schedule_manual_secs": 180, "auto_scheduling": true, "precipitation_rate": 16.51,
"kc": 0.75}, {"id": 44321, "name": "Zone 4", "plant_type": 1, "emitter_type": 5, "gets_rainfall": true,
"user_watering_modulation": 1.0, "valve": 4, "active": false, "illustration": null, "thumbnail_1": null,
"thumbnail_2": null, "thumbnail_3": null, "thumbnail_4": null, "schedule_manual_secs": 180,
"auto_scheduling": true, "precipitation_rate": 16.51, "kc": 0.75}, {"id": 44322, "name": "Zone 5",
"plant_type": 1, "emitter_type": 5, "gets_rainfall": true, "user_watering_modulation": 1.0, "valve": 5,
"active": false, "illustration": null, "thumbnail_1": null, "thumbnail_2": null, "thumbnail_3": null,
"thumbnail_4": null, "schedule_manual_secs": 180, "auto_scheduling": true, "precipitation_rate": 16.51,
"kc": 0.75}, {"id": 44323, "name": "Zone 6", "plant_type": 1, "emitter_type": 5, "gets_rainfall": true,
"user_watering_modulation": 1.0, "valve": 6, "active": false, "illustration": null, "thumbnail_1": null,
"thumbnail_2": null, "thumbnail_3": null, "thumbnail_4": null, "schedule_manual_secs": 180,
"auto_scheduling": true, "precipitation_rate": 16.51, "kc": 0.75}, {"id": 44324, "name": "Zone 7",
"plant_type": 1, "emitter_type": 5, "gets_rainfall": true, "user_watering_modulation": 1.0, "valve": 7,
"active": false, "illustration": null, "thumbnail_1": null, "thumbnail_2": null, "thumbnail_3": null,
"thumbnail_4": null, "schedule_manual_secs": 180, "auto_scheduling": true, "precipitation_rate": 16.51,
"kc": 0.75}, {"id": 44325, "name": "Zone 8", "plant_type": 1, "emitter_type": 5, "gets_rainfall": true,
"user_watering_modulation": 1.0, "valve": 8, "active": true, "illustration": null, "thumbnail_1": null,
"thumbnail_2": null, "thumbnail_3": null, "thumbnail_4": null, "schedule_manual_secs": 180,
"auto_scheduling": true, "precipitation_rate": 16.51, "kc": 0.75}, {"id": 44326, "name": "Zone 9",
"plant_type": 1, "emitter_type": 5, "gets_rainfall": true, "user_watering_modulation": 1.0, "valve": 9,
"active": true, "illustration": null, "thumbnail_1": null, "thumbnail_2": null, "thumbnail_3": null,
"thumbnail_4": null, "schedule_manual_secs": 180, "auto_scheduling": true, "precipitation_rate": 16.51,
"kc": 0.75}, {"id": 44327, "name": "Zone 10", "plant_type": 1, "emitter_type": 5, "gets_rainfall": true,
"user_watering_modulation": 1.0, "valve": 10, "active": true, "illustration": null, "thumbnail_1": null,
"thumbnail_2": null, "thumbnail_3": null, "thumbnail_4": null, "schedule_manual_secs": 180,
"auto_scheduling": true, "precipitation_rate": 16.51, "kc": 0.75}, {"id": 44328, "name": "Zone 11",
"plant_type": 1, "emitter_type": 5, "gets_rainfall": true, "user_watering_modulation": 1.0, "valve": 11,
"active": true, "illustration": null, "thumbnail_1": null, "thumbnail_2": null, "thumbnail_3": null,
"thumbnail_4": null, "schedule_manual_secs": 180, "auto_scheduling": true, "precipitation_rate": 16.51,
"kc": 0.75}, {"id": 44329, "name": "Zone 12", "plant_type": 1, "emitter_type": 5, "gets_rainfall": true,
"user_watering_modulation": 1.0, "valve": 12, "active": true, "illustration": null, "thumbnail_1": null,
"thumbnail_2": null, "thumbnail_3": null, "thumbnail_4": null, "schedule_manual_secs": 180,
"auto_scheduling": true, "precipitation_rate": 16.51, "kc": 0.75}], "channel": "channel_[redacted]",
"sch_start": "+60", "address": {"city": "[redacted]", "country": "[redacted]", "street2": null,
"zipcode": "[redacted]", "state": "[redacted]", "street": "[redacted]"}, "timezone": "[redacted]",
"current_time": "2015-11-19T14:58:37.925-08:00", "avg_eto": 1.74}
Anyhow, I am at a cross-road. There are two ways I can go about
accomplishing our goal. I got close to making both work, but in the
end and for the purpose of this blog, I will only discuss one. The
first way, and way I won't go into detail on how to recreate, has code
to almost completely rebuild their cloud-based API, including the
phone app. The phone app has a button that lets you run the sprinklers
arbitrarily. This is why it was my intial target for accomplishing
our goal. In the end, that route was taking much more effort than what
it would for the route I'll talk about later on. A bit of information
on how their cloud architecture works:
I thought it would work kind of like this:
[Phone App] -> [Cloud API] <-> [Blossom]
But really it works something like this:
[Phone App] -> [Blossom API] <-> [Blossom]
^
|
[PubNub API] <-----+
PubNub is a realtime messaging service and content delivery network.
The Blossom device makes thousands of HTTP calls per day, which can be
difficult to scale and can probably create debugging problems when
attempting to triage customer issues. PubNub likely helps with that
by integrating code into the device that allows a more granular way of
tracking device / user pairing and network state. PubNub returns a job
identifer along with an EPOCH time that I believe is correlated to the
job scheduled through the phone app.
There are big reasons why you want to remove third-parties from your
networks. One such example can be found in the Privacy Policy used by
the manufacturer of the Blossom. The manufacturer is owned by a
company called iConservo and the policy in full can be found at:
http://myblossom.com/legal/iconservo-privacy-statement/
What We Collect
[snip]
We also collect passive information such as your IP address on certain
IConservo Products, your ZIP code, as well as information about your
IConsevo Product such as MAC addresses, product model numbers,
software versions, chipset IDs, and region and language settings.
Passive information also includes information about the products you
request or purchase, the presence of other devices connected to your
local network, and the number of users and frequency of use of
IConservo Products and Services. We also collect passive information
regarding customer activities on our website. Some passive information
may be associated with personally identifying information. [snip]
Who We Share With
We work with third parties in connection with some aspects of the
ICONSERVO Products and Services, such as but not limited to processing
payments and providing marketing assistance.
[snip]
I'll just quote this again to make sure it's read: "the presence of
other devices connected to your local network".
I will let the readers individually infer what this legal text means
in relation to what the Blossom will collect about you and
devices on your network.
If you want to retain the cloud-based functionality, null routing
PubNub will not be an option as a HTTP response from PubNub is what
seems to actually start the watering cycle scheduled through the phone
app. There is another way to open and close valves manually through
the web portal as well.
Blossom communicates with two internet accessible API servers:
*.myblossom.com and *.pubnub.com
How do I take control of the network traffic? Just like a sandnet for
malware analysis. Set up DHCP and DNS. Configure DHCP to issue the
DNS server along with the lease. Next, configure DNS with records to
return internal IP addresses for the aforementioned API servers.
Blossom will honor this configuration.
The redacted content contains the local IP address for my Blossom
device on the testing network and a pairing code to associate the
device to my demo account. A sample of the traffic to *.myblossom.com
can be found below.
[redacted] - - [28/Nov/2015 08:21:38] "GET /api/device/v1/server/?q=0.8.1035&fs=0.0.9&c=[redacted]&n=p HTTP/1.1" 404 -
[redacted] - - [28/Nov/2015 08:21:53] "GET /api/device/v1/server/?q=0.8.1035&fs=0.0.9&c=[redacted]&n=p HTTP/1.1" 404 -
[redacted] - - [28/Nov/2015 08:22:08] "GET /api/device/v1/server/?q=0.8.1035&fs=0.0.9&c=[redacted]&n=p HTTP/1.1" 404 -
I noticed that if the device is unable to establish communication with
the API servers, it will turn the display LED red indicating
connectivity failure and eventually purple indicating the start of an
automated recovery process. If connectivity is not restored on the
subsequent boot, the recovery process will continue indefinitely.
On to the second option, which is more or less a derivative of the
first. I am going to trick the Blossom device into staying online.
Next, I'll track our watering schedule and issue commands to open and
close each valve I care about. In this way, I will remove the need for
both the internet-facing Blossom API and PubNub API while retaining
the ability to water my lawn when I want. Ok, here I go.....
Upon boot, the device performs a variety of requests to prepare for
operation.
![]()
Upon resolution of these names the Blossom will then make three HTTP
requests.
[redacted] - - [28/Nov/2015 09:12:46] "GET /firmware-check/0.json?q=0.8.1035&c=[redacted] HTTP/1.1" 404 -
[redacted] - - [28/Nov/2015 09:12:48] "GET /api/device/v1/server/?q=0.8.1035&fs=0.0.9&c=[redacted]&n=w HTTP/1.1" 200 -
[redacted] - - [28/Nov/2015 09:12:55] "GET /device/v1/server/ HTTP/1.1" 200 -
The first is a firmware check which seems to occur at every boot and
every 3600 seconds thereafter. The frequency of this check can be
changed using the ota_freq variable in the do_GET function of the
Handler class within the code for this blog post. If the device
receives a 404 HTTP responce code, it moves along. The second and
third requests are what keep the device online. These requests must
receive a properly constructed response. This is easy enough, just
follow their JSON format. The request for the firmware returns a 404
HTTP response code with no JSON structure, I'll skip that one.
GET /api/device/v1/server/?q=0.8.1035&fs=0.0.9&c=[redacted]&n=w HTTP/1.1
Host: home.myblossom.com
User-Agent: WMSDK
HTTP/1.1 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Date: Fri, 20 Nov 2015 15:32:32 GMT
Vary: Accept, Cookie
Content-Length: 88
Connection: keep-alive
{"ts": "2015-11-20T07:32:32.259-08:00", "pst_tz_offset": -8.0, "pst_tz_seconds": -28800}
GET /device/v1/server/ HTTP/1.1
Host: home.myblossom.com
User-Agent: WMSDK
HTTP/1.1 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Date: Fri, 20 Nov 2015 15:32:32 GMT
Vary: Accept, Cookie
Content-Length: 88
Connection: keep-alive
{"ts": "2015-11-20T07:32:41.212-08:00", "pst_tz_offset": -8.0, "pst_tz_seconds": -28800}
You'll know everything is going smoothly when the LED on the front
display remains a solid green throughout the duration of device
uptime.
How about making the Blossom water what I want and when I want it? I'm
glad you asked! A few notes first: If you want to make changes to your
watering cycle in the future, you'll have to stop the API and make the
those changes in code first. If you want to change the zones to be
watered in each cycle, you'll have to stop the API and make the
changes in code first. The same thought process can be used for zone
watering durations. That's okay though, it keeps the mind limber!
Lets get down to it.
The below code is annotated with ^""" blocks; the script is also
available without the extra annotations: blossom-py3.py
#!/usr/bin/env python3
"""
First, I need a bunch of imports:
"""
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.request import Request, urlopen
from time import strftime, sleep
from datetime import datetime
from threading import Thread
from json import dumps
from sys import exit
"""
I'll turn off debug, you can enable it later if you want an additional
logging statement.
"""
debug = 0
"""
I'll also create some code to log when valves are opened and closed
and recreate a python 2.7 feature that I needed when trying to quickly
port this over.
"""
def xrange(low,high):
''' Recreates the xrange I love from the 2.7 version of Python. '''
return iter(range(low,high))
def log_it(valve, state):
''' Creates a simple log entry for each operation. '''
fd = open('blossom.log', 'a+')
if (state == 1):
fd.write("{:s} -> Opened valve: {:s}\n".format(str(strftime("%Y-%m-%dT%H:%M:%S")), str(valve)))
else:
fd.write("{:s} -> Closed valve: {:s}\n".format(str(strftime("%Y-%m-%dT%H:%M:%S")), str(valve)))
fd.close()
return
"""
I'll need a class to tell Blossom to open and close our desired
valves:
"""
class Blossom:
''' Instructs the Blossom to perform a desired operation. '''"""
The IP address of the Blossom device should be put in the self.apiHost
variable.
"""
def __init__(self):
''' Defines a shared variable. '''
self.apiHost = "[redacted]" # IP Address of Blossom
return
"""
To open a valve, I simply send a POST request to the Blossom to
/bloom/valve with a JSON object containing two keys: valve and
inverter. The valve indicates which zone you want to run, and
inverter is a boolean (either 0 or 1) indicating what state the valve
should be in.
"""
def open_valve(self, valveNumber):
''' Instructs Blossom to open a valve. '''
request = Request("http://{:s}/bloom/valve".format(str(self.apiHost)), dumps({"valve":valveNumber,"inverter":1}).encode('ascii'))
fd = urlopen(request)
return fd.close()
"""
The same goes for closing a valve, only the inverter value has been
changed.
"""
def close_valve(self, valveNumber):
''' Instructs Blossom to close a valve. '''
request = Request("http://{:s}/bloom/valve".format(str(self.apiHost)), dumps({"valve":valveNumber,"inverter":0}).encode('ascii'))
fd = urlopen(request)
return fd.close()
"""
Now I need a class to handle HTTP requests, I'll call it Handler. I
need to define the UUID for our Blossom within this class as well.
"""
class Handler(BaseHTTPRequestHandler):
''' Decides how to handle each request. '''"""
A function called do_GET is used to handle each received GET request.
"""
def do_GET(self):
"""
I do some simple request inspection to decide how to respond. I'll
also set some headers for the response.
"""''' In the case of HTTP GET, the request type of checked and an appropriate response is returned. '''
uuid = "[redacted]" # Blossom UUID goes here
if ("firmware-check" not in self.path):
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Vary', 'Accept')
self.send_header('Connection', 'Close')
self.end_headers()
else:
self.send_response(404)
self.end_headers()
"""
This is where the API will decide how to respond based on the received
request. The self.path variable contains the URI as it was received
by the web server. I use this to determine how to build the proper
response. The self.wfile.write function calls are used to actually
build the response. The response gets delivered to the client upon
return.
The first URI is /device/v1/server/ which is more-or-less a server
ping. The response contains time information.
"""
if (self.path == '/device/v1/server/'):
self.wfile.write(str('{"ts": "{:s}", "pst_tz_offset": -8.0, "pst_tz_seconds": -28800}'.format(str(strftime("%Y-%m-%dT%H:%M:%S")))))
"""
The second is /api/device/v1/ and looks for an additional URI value
called parameter.
"""
elif ("api" in self.path and "device" in self.path and uuid in self.path):
"""
If the URI contains parameters,
"""
if ("api" in self.path and "parameters" in self.path):
self.wfile.write(str('{"stats_freq": 3600, "pn_keepalive": 1, "uap_debug": 1, "wave_boost": 1, "ota_freq": 3600, "current_time":"{:s}", "build": 1042, "opn_trip": 40}'.format(str(strftime("%Y-%m-%dT%H:%M:%S")))))
"""
Otherwise,
"""
else:
self.wfile.write(str('{"channel": "channel_{:s}", "current_time": "{:s}", "tz_offset": -8.0, "tz_seconds": -28800, "sch_load_time": 24900, "fetch_lead": 3600}'.format(str(uuid), str(strftime("%Y-%m-%dT%H:%M:%S")))))
"""
And finally, our return statement.
"""
return
"""
Now for some code to manage the watering schedule.
"""
class Monitor:
''' Monitors the current datetime and watering schedule. '''"""
I'll need to define a few variables for use later. The variables
*_scheduleDays indicate what days you want your lawn to be watered.
The variables *_scheduleTimes indicate what time of day you want your
lawn watered. The *_zones variable indicates which zones you want
watered and for how long in seconds you want each valve to be open.
"""
def __init__(self):
self.front_scheduleDays = ['Tue', 'Thu', 'Sat']
self.front_scheduleTimes = [400, 2330]
self.front_zones = {'zones': [1, 2, 3], 'water_times': [60, 60, 120]}
self.back_scheduleDays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
self.back_scheduleTimes = [530, 2355]
self.back_zones = {'zones': [8, 9, 10, 11, 12], 'water_times': [30, 30, 30, 30, 0]}
self.days = {'Mon': 0, 'Tue': 1, 'Wed': 2, 'Thu': 3, 'Fri': 4, 'Sat': 5, 'Sun': 6}
return
"""
Now I need to start a infinite loop to contintually check when to
water.
"""
def run(self):
''' Continually checks the current datetime and decides whether or not to water. '''
print(datetime.now(), " -> Starting")
while True:
print(datetime.now(), " -> Checking schedule")
"""
I start by getting the current day of the week and time.
"""
self.current_day = datetime.today().weekday()
self.current_time = int(datetime.now().strftime('%H%M'))
"""
Next, I decide whether or not self.current_day is a day of the week
that I want to water my lawn. This is for the front yard, I'll repeat
for the back yard as well.
"""
for schedule_day in self.front_scheduleDays:
if self.days[schedule_day] is self.current_day:
for schedule_time in self.front_scheduleTimes:
if debug == 1: print(datetime.now(), " -> [F] Checking if ", self.current_time, " is in ", xrange(schedule_time-1, schedule_time+1))
"""
Next, I decide whether or not self.current_time is within the time
window of when I want to water my lawn. If I use a sleep of thirty
seconds at the end of each loop iteration, assuming self.current_time
walls within the window alloted (+/- 1 minute) and you water two zones
at a minimum of thirty seconds each, there shouldn't be any issues.
This isn't the best way of doing this, but it works satisfactorily.
"""
if self.current_time in xrange(schedule_time-1, schedule_time+1):
"""
Finally, I can begin to iterate through our zones to be watered and
open the valves for each.
"""
for zone_info in zip(self.front_zones['zones'], self.front_zones['water_times']):
if (zone_info[1] > 0):
"""
Open the valve.
"""
print(datetime.now(), " -> [F] Opening valve: ", zone_info[0])
log_it(zone_info[0], 1)
Blossom().open_valve(zone_info[0])
"""
Make it rain!
"""
sleep(zone_info[1])
"""
Remember to close the valve. If you don't, you'll be watering until
you do.
"""
print(datetime.now(), " -> [F] Closing valve: ", zone_info[0])
log_it(zone_info[0], 0)
Blossom().close_valve(zone_info[0])
"""
Rinse and repeat with the back yard.
"""
for schedule_day in self.back_scheduleDays:
if self.days[schedule_day] is self.current_day:
for schedule_time in self.back_scheduleTimes:
if debug == 1: print(datetime.now(), " -> [B] Checking if ", self.current_time, " is in ", xrange(schedule_time-1, schedule_time+1))
if self.current_time in xrange(schedule_time-1, schedule_time+1):
for zone_info in zip(self.back_zones['zones'], self.back_zones['water_times']):
if (zone_info[1] > 0):
print(datetime.now(), " -> [B] Opening valve: ", zone_info[0])
log_it(zone_info[0], 1)
Blossom().open_valve(zone_info[0])
sleep(zone_info[1])
print(datetime.now(), " -> [B] Closing valve: ", zone_info[0])
log_it(zone_info[0], 0)
Blossom().close_valve(zone_info[1])
"""
Sleep at the end of each iteration.
"""
sleep(30)
"""
Finally, return to the caller.
"""
return
"""
I also need a main routine to get things going.
"""
def main():
''' Spawn a thread for datetime monitoring. Serve the API. '''"""
I'll use two threads. The parent will run the web server, and a child
thread will monitor the schedule and instruct Blossom on operations to
be performed.
"""
Thread(name='monitor-schedule', target=Monitor().run, args=()).start()
"""
Now I'll start the API.
"""
try:
server = HTTPServer(('', 81), Handler)
server.serve_forever()
except KeyboardInterrupt:
print("^C received, shutting down the web server")
server.socket.close()
return exit(1)
"""
Run main.
"""
if __name__ == "__main__":
main()
![]()
I hope you enjoyed reading this blog, I had fun writing it. I've had
this configuration deployed for a few weeks now, and so far, it's
working pretty well. Next week, I'll be talking about an undocumented
account in an IoT device.