Note: staff-provided content does not represent an official statement from FARGOS Development, LLC. The policy on staff-authored content can be found here.


Go back to Geoff's home page.

Interfacing Blossom Sprinkler Controllers to Open Sprinkler

Blossom Sprinkler System Controllers were decent cloud-dependent devices that also supported integration with Amazon's Alexa. The original company was acquired by Scotts and the Blossom product line was sunset. At the end of 2021, Scotts turned off the cloud servers that enabled the use of the Blossom controllers, converting them to plastic bricks for most users.

Fortunately, there is an HTTP server built into the Blossom controller that continues to operate despite the inability of the controller to contact the discontinued cloud service. Although their existence was hidden, there are HTTP commands available to manually turn on a watering zone or turn off all watering zones.

A package of scripts and systemd service unit definitons can be downloaded that restores operation of Blossom controllers with the open source Open Sprinkler offering. The respective files are discussed below.

Opening a valve using the Blossom Controller

The turnSprinklerValve script below can open valves for a period of time and then close them. It uses wget to issue the relevant requests via HTTP to the Blossom controller.

One complication of the script is that it needs to periodically reissue commands to the Blossom controller because the controller itself will keep attempting to contact the cloud servers that originally supported the product. When these connection attempts fail, the Blossom device halts any ongoing sprinkler activity by closing all valves and starts blinking red LEDs on the device's front cover. A maximum of 5 minutes of sprinkling could be achieved if the open valve directives were not reissued. The refresh interval can be reduced if needed to increase the accuracy of computed total run time.

#!/bin/bash
# Open Blossom valve for a period of time
# Author:  Geoff Carpenter gcc@fargos.net http://www.fargos.net/gcc.html

blossomHost="172.28.44.5" # change to local default
ledColor=12
valveNum=0 # close
inverterFlag=1
duration=0
refreshInterval=10
tmpFile="/dev/null"
logFile="/dev/stdout"
forceClose=0
busyFile="/tmp/tmp.valveOpen.flag"

while test $# -gt 0

do
        case "${1}" in
        -h | -help | --help)
                printf "%s: --host addr --valve id [--inverter mode] [--color code] [--duration seconds] [--refresh interval] [--close] [--output dataFile] [--log fileName]\n" "${0}" >&2
                exit 1
                ;;
        --host)
                blossomHost="${2}"
                shift
                ;;
        --valve)
                valveNum="${2}"
                shift
                ;;
        --inverter)
                inverterFlag="${2}"
                shift
                ;;
        --color)
                ledColor="${2}"
                shift
                ;;
        --duration)
                duration="${2}"
                shift
                ;;
        --refresh)
                refreshInterval="${2}"
                shift
                ;;
        --output)
                tmpFile="${2}"
                shift
                ;;
        --log)
                logFile="${2}"
                shift
                ;;
        --busy)
                busyFile="${2}"
                shift
                ;;
        --close)
                forceClose=1
                ;;
        -*)
                printf "%s: unrecognized option: \"%s\"\n" "${0}" "${1}" >&2
                exit 1
                ;;
        *)
                printf "%s: unrecognized argument: \"%s\"\n" "${0}" "${1}" >&2
                exit 1
                ;;
        esac
        shift
done

# Issue open/close valve request to the Blossom sprinkler controller 
# Note that close requests are done using valve identifier 0,
# so closing a valve closes all valves.
function turnValve ()
{
        valveId=${1:-0}
        inverterMode=${2:-1}
        wget --output-document="${tmpFile}" --header="Content-type: application/json" --post-data="{\"valve\":${valveId},\"inverter\":${inverterMode}}"  http://${blossomHost}/bloom/valve
}

function set_LED_sequence ()
{
        sequenceCode="${1:-12}"
        wget --output-document="${tmpFile}" --header="Content-type: application/json" --post-data="{\"red\":0, \"green\":0, \"blue\":0, \"sequence\":${sequenceCode}}"  http://${blossomHost}/bloom/led
}

function forceValveClose ()
{
        now=`date '+%Y/%m/%d %H:%M:%S'`
        echo ${now} $0 force close of valves >> ${logFile}
        turnValve 0 ${inverterFlag}
        set_LED_sequence 8
}

if test "${forceClose}" != 0
then
        forceCmd="; forceValveClose"

fi
trap "rm -f ${busyFile} ${forceCmd}" EXIT

now=`date '+%Y/%m/%d %H:%M:%S'`
echo ${now} $0 turn valve ${valveNum} >> ${logFile}
echo ${now} $0 turn valve ${valveNum} >> ${busyFile}
set_LED_sequence ${ledColor}
turnValve ${valveNum} ${inverterFlag}

startTime=`date +%s`
desiredEndTime=`expr ${startTime} + ${duration}`
curTime="${startTime}"
while test ${curTime} -lt ${desiredEndTime}
do
        sleep ${refreshInterval}
        now=`date '+%Y/%m/%d %H:%M:%S'`
        echo ${now} $0 refresh valve ${valveNum} >> ${logFile}
        set_LED_sequence ${ledColor}
        turnValve ${valveNum} ${inverterFlag}
        curTime=`date +%s`
done

exit 0

Blossom Interface For Open Sprinkler

While the above script can be used to manually turn on a watering zone for a period of time, it lacks all of the expected automation. The open source Open Sprinkler project provides a very flexible foundation that replaces the no-longer-supported Blossom app. The Open Sprinkler server (aka, "Unified Firmware") supports a variety of interfaces to controllers and valves, one of which is remote control via HTTP requests. The doSprinklerRequests script below acts as a long-running daemon that accepts requests from Open Sprinkler and issues appropriate requests to the Blossom controller.

#!/bin/bash
# USAGE: doSprinklerRequests [blossomAddress [listenPort]]
# Interface to enable access by Open Sprinkler to no-longer-supported
# (as of 2022) Blossom sprinkler controller hardware.
# Author:  Geoff Carpenter gcc@fargos.net http://www.fargos.net/gcc.html
#
# Accepts from Open Sprinkler HTTP requests of the form:
#    GET /valve/${valveNum}/open and
#    GET /valve/${valveNum}/close
#
# LOCAL CONFIGURATION/CUSTOMIZATION
# NOTE: blossomHost must be set correctly to address of the Blossom
# controller.  This can be the name of the controller if your local
# DHCP server knows it; otherwise (and most reliably), it should be
# the statically assigned IP address.
blossomHost="${1:-172.28.44.5}" # MUST be set to address of Blossom controller
listenPort="${2:-8081}"
debugLevel=0

waitingColor=4 # choose from set_LED_sequence options below
wateringColor=12 # choose from set_LED_sequence options below
disabledColor=8 # choose from set_LED_sequence options below

maxDuration=1800 # maximum possible continuous valve open time

# Stations need to be configured in Open Sprinkler as HTTP remote stations.
# To do this from the Open Sprinkler GUI, go to the Advanced tab of the
# station settings:
# Station Type:  HTTP
# Server Name:  127.0.0.1
# Server Port:  8081 (or what listenPort is set to below)
# On Command:   valve/1/on
# Off Command:  valve/1/off
#
# NOTE:  commands do not begin with a "/" as the Open Sprinkler firmware
# always prepends a "/" to the GET request.

originDir=`dirname "${0}"`
logFile=/tmp/tmp.`basename $0`.$$ # set to /dev/null to ignore output

latestLog=/tmp/sprinklerRequests.log
tmpFile=/tmp/tmp.valveRequest.$$
busyFile=/tmp/tmp.busy.$$


# Issue open/close valve request to the Blossom sprinkler controller 
# Note that close requests are done using valve identifier 0,
# so closing a valve closes all valves.
function turnValve ()
{
        valveId=${1:-0}
        inverterMode=${2:-1}
        wget --output-document="${tmpFile}" --header="Content-type: application/json" --post-data="{\"valve\":${valveId},\"inverter\":${inverterMode}}"  http://${blossomHost}/bloom/valve
}

# 1=Pulsing Blue/Green
# 2=Blinking Red
# 3=Fixed Blue
# 4=Slowly Pulsing Blue
# 5=Fixed Blue (Watering 1)
# 6=Fixed Blue/Green (Watering 2)
# 7=Pulsing Pink
# 8=Fast Blinking Pink
# 9=Fixed orange
# 10=Pulsing Red
# 11=Fast Blinking Blue/Green
# 12=Fast Blinking purple
# 13=Fixed Purple

function set_LED_sequence ()
{
        sequenceCode="${1:-${waitingColor}}"
        wget --output-document="${tmpFile}" --header="Content-type: application/json" --post-data="{\"red\":0, \"green\":0, \"blue\":0, \"sequence\":${sequenceCode}}"  http://${blossomHost}/bloom/led
}

# Set custom LED color on Blossom box as RGB value
function set_LED_RGB ()
{
        red="${1:-0}"
        green="${2:-0}"
        blue="${3:-0}"
        wget --output-document="${tmpFile}" --header="Content-type: application/json" --post-data="{\"red\":${red}, \"green\":${green}, \"blue\":${blue}, \"sequence\":0}"  http://${blossomHost}/bloom/led
}

now=`date '+%Y/%m/%d %H:%M:%S'`
echo ${now} $0 begins operation >> ${logFile}

# if output is to regular file, make link to common persistent name
if test -f "${logFile}"
then # Use hard link for common log file name
        rm -f "${latestLog}"
        ln "${logFile}" "${latestLog}"
        logFileToRemove="${logFile}"
fi

nice ${originDir}/refreshBlossomColor ${blossomHost} $$ ${busyFile} ${waitingColor} ${disabledColor} >& /dev/null &
refreshPID=$!

# Force valve close on exit, indicate not operating
trap "turnValve 0 1; set_LED_sequence ${disabledColor}; rm -f ${tmpFile} ${logFileToRemove} ${busyFile}" EXIT


openTime=0
lastOpenPID=""
while true
do
   now=`date '+%Y/%m/%d %H:%M:%S'`
   echo ${now} listen on ${listenPort} >> ${logFile}
   set_LED_sequence ${waitingColor} # waiting
   nc -l -k -p ${listenPort} | while read cmd arg proto everything
do
        now=`date '+%Y/%m/%d %H:%M:%S'`
        if test ${debugLevel} -gt 0
        then
                echo ${now} cmd ${cmd} arg ${arg} >> ${logFile}
                if test ${debugLevel} -gt 1
                then
                        echo ${now} proto ${proto} remainder ${everything} >> ${logFile}
                fi
        fi
        case X"${cmd}" in
        XGET)
                valveNum=`echo ${arg} | cut -d / -f 3`
                openOrClose=`echo ${arg} | cut -d / -f 4`
                optionalInverterMode=`echo ${arg} | cut -d / -f 5`
                mode=${optionalInverterMode:-1}
                case X"${openOrClose}" in
                Xon)
                        openTime=`date '+%s'`
                        echo ${now} ${openTime} Open valve ${valveNum} >> ${logFile}
                        echo ${now} ${openTime} Open valve ${valveNum} >> ${busyFile}
                        ${originDir}/turnSprinklerValve --host ${blossomHost} --valve ${valveNum} --inverter ${mode} --color ${wateringColor} --duration ${maxDuration} --output ${tmpFile} --log ${logFile} --busy ${busyFile} --close &
                        lastOpenPID=$!
                        ;;
                Xoff | 'X*') # Blossom uses valve 0 to close
                        closeTime=`date +%s`
                        duration=`expr ${closeTime} - ${openTime}`
                        echo ${now} ${closeTime} Close valve ${valveNum} after ${duration} seconds >> ${logFile}
                        if test -n "${lastOpenPID}"
                        then
                                kill ${lastOpenPID}
                                wait
                                lastOpenPID=""
                        fi
                        turnValve 0 ${mode}
                        set_LED_sequence ${waitingColor}
                        rm -f ${busyFile}
                        ;;
                esac
                ;;
        XHOST)
                ;;
        X)
                ;;
        *)
                ;;
        esac
done
now=`date +'%Y/%m/%d %H:%M:%S'`
echo ${now} connection from Open Sprinkler closed  >> ${logFile}

done

Blossom LED Status Refresh

As noted previously, the Blossom controller periodically attempts to connect to the defunct cloud services that once supported its operation. The inevitable failure to success leads to flashing red LEDS being presented on the Blossom case and closing of any open valves. The refreshBlossomColor script periodically resets the LED status to indicate that the Blossom device is being controlled by doSprinklerRequets.

#!/bin/bash
# Periodicallly refresh color of status LEDs on Blossom controller
# Blossom firmware will periodically reattempt connection to the
# defunct cloud services, fail, and then turn the LEDs red.
# refreshBlossomColor reinjects the desired color mode while its
# parent doSprinklerRequests script waits for requests.
# Author:  Geoff Carpenter gcc@fargos.net http://www.fargos.net/gcc.html


refreshInterval=10
myPID=$$

blossomHost="${1}"
parentPID="${2}"
monitorFile="${3}"
waitingColor=${4:-4} # choose from set_LED_sequence options 
disabledColor=${5:-8} # choose from set_LED_sequence options

tmpFile="/tmp/tmp.blossomRefresh.$$"

trap "rm -f ${tmpFile}" EXIT

function set_LED_sequence ()
{
        sequenceCode="${1:-${waitingColor}}"
        wget --output-document="${tmpFile}" --header="Content-type: application/json" --post-data="{\"red\":0, \"green\":0, \"blue\":0, \"sequence\":${sequenceCode}}"  http://${blossomHost}/bloom/led
}

kill -0 ${parentPID}
parentGone=$?
while test ${parentGone} -eq 0
do
        if test ! -f "${monitorFile}"
        then
                set_LED_sequence ${waitingColor}
        fi
        sleep ${refreshInterval}
        kill -0 ${parentPID}
        parentGone=$?
done
set_LED_sequence ${disabledColor}

Blossom Service Definition

The following blossom.service prototype service unit definition will work with systemd to launch the Blossom gateway. Substitute "{sprinkler}" with the user name used to build the software. Substitute "blossomAddress" with the IP address of the Blossom controller.

[Unit]
Description=Blossom gateway for Open Sprinkler 

[Service]
User={sprinkler}
ExecStart=/home/{sprinkler}/doSprinklerRequests blossomAddress
WorkingDirectory=/home/{sprinkler}

Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Open Sprinkler Weather Service

It is not strictly necessary to use a local weather service daemon, but doing so insulates one from depending on the Open Sprinkler team continuing to provide such services on behalf of the public. Note that Open Sprinkler defaulted to using Dark Sky as a source for weather data, but Apple's acquisition of the service has resulted in support for the API being turned off in March of 2023. The Open Sprinkler weather service can be configured to use Open Weather as an alternative and this was recommended practice as of 2022.

The following weather.service provides a service unit definition for the Open Sprinkler weather daemon. Substitute "{sprinkler}" with the user name used to build the software.

[Unit]
Description=OpenSprinkler Weather Server

[Service]
User={sprinkler}
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/{sprinkler}/OpenSprinkler-Weather

Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Open Sprinkler Daemon Service Definition

The following sprinkler.service provides a service unit definition for the Open Sprinkler daemon, referred to as the "unified firmware". Substitute "{sprinkler}" with the user name used to build the software.

[Unit]
Description=Open Sprinkler Server
Wants=weather.service
Requires=blossom.service

[Service]
User={sprinkler}
ExecStart=/home/{sprinkler}/OpenSprinkler-Firmware/OpenSprinkler
WorkingDirectory=/home/{sprinkler}/OpenSprinkler-Firmware

Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target