Lights On Granger Lane logo
  • Home 
  • Rules for Viewing 
  • About the Show 
  1.   Posts
  1. Home
  2. Posts
  3. Light Show On-Demand via SMS/Text

Light Show On-Demand via SMS/Text

Posted on November 23, 2025 • 9 min read • 1,904 words
Technical  
Technical  
Share via
Lights On Granger Lane
Link copied to clipboard

On this page
Background   The Goal   Prerequisites   The Workflow   Implementation   The Gateway: VoIP.ms Configuration   The Command Broker: Receiving and Routing   Validating the Request: The ‘Checks’   Action and Integration   Preparing and Sending the Response   Conclusion  
Light Show On-Demand via SMS/Text

Background  

This solution pre-dates the web based solution by a few years. I wanted a quick and easy way for my show visitors to trigger my light show so that it only ran the flashy lights when someone was actually there to watch, and reduce disturbing my neighbors.

At the time, there were a few FPP plugins available that might have done the job, but since I was already using home automation tools to do more than just control my light show, and I wanted more control to do some things I’ll describe below, I did it myself.

This guide walks through the features, key concepts and some detailed setup steps.

The Goal  

The objective is allow visitors to interact with the light show via SMS/text message. We want to publish a phone number to which they can text any number of commands to trigger various light show functions, including playing the entire show, playing a single sequence, retrieving light show information, donation information, or anything else you can think of. The feature should also respond to visitors by text to acknowledge receipt, provide status or other requested information, or let them know if there was an error.

Prerequisites  

  • FPP: Running your holiday sequences.
  • SMS/Text Provider: I recommend VoIP.ms  , and that’s what I will be referencing in this guide. You need a service that takes an incoming text message and relays that to a callback URL/API endpoint/webhook.
  • Command Broker: I recommend something like Node-RED  , but if you already use HomeAssistant  , much of this can be done with HomeAssistant Automations too. If you don’t want to use a command broker and instead have FPP do all the work, you’re probably better off using one of the existing plug-ins.

The Workflow  

  • The Text Message: Light show visitor sends a text message to the published phone number (hosted at VoIP.ms) with a command.
  • Forward the Payload: VoIP.ms forwards the originating phone number and command text to a callback URL (a HomeAssistant webhook, a Node-RED HTTP IN node, a Cloudflare Worker acting as a secure proxy, etc.)
  • Process the Payload: The Command Processor evaluates the message to extract the command and routes to the appropriate flow to control the show.
  • Respond to Message: Send a message back to the show visitor with status or information requested, based on the command.

Implementation  

The Gateway: VoIP.ms Configuration  

I won’t get into all the details here. But once you sign up and have your DID number, assuming you don’t want to receive phone calls here, is to navigate to DID Numbers | Manage DIDs. Below, I’ve detailed the settings you should choose, and skipped those I think are irrelevant.

  • Routing Settings: Check System and select Hangup.
  • DID Point of Presence: If not automatically selected, choose one closest to you.
  • Message Service (SMS/MMS):
    • Message Service (SMS/MMS): Enabled
    • SMS/MMS Forward to an Email Address: (Optional) Enter your email address if you want incoming text messages emailed to you.
    • SMS/MMS Forward to a Phone Number: (Optional) Enter a number here if you want incoming messages forwarded to another number.
    • SMS/MMS URL Callback: (Important) https://[YOUR_IP_ADDRESS_OR_DOMAIN]/[YOUR_WEBHOOK_OR_ENDPOINT]?from={FROM}&message={MESSAGE}

The Command Broker: Receiving and Routing  

Node-RED handles parsing the inbound text message payload and figuring out what to do with it. As a first step, you just need to get that message data into Node-RED.

  • HTTP IN (node): This node receives the inbound payload from VoIP.ms, stored in msg.payload.
    • Method: GET
    • URL: Set this to the same SMS/MMS URL Callback URL from your VoIP.ms configuration above.
    • Output: Connects to the HTTP Response and Switch nodes.
  • HTTP Response (node): Be nice and send a response back to VoIP.ms.
    • Input: Connects from the HTTP In node.
    • Status Code: 200
  • Switch (node): Handles the initial routing based on the received command, in msg.payload.message.
    • Input: Connects from the HTTP In node.
    • Property: $lowercase(payload.message) Using lowercase() for comparison.
    • Output (rules): This will vary based on what commands you want to receive and act on. Here are some examples:
Output Rule Type Command Match Destination
Output 1 Equals info Respond with instructions & link to website.
Output 2 Equals donate Respond with info & link to my favorite charity.
Output 3 RegEx ^go(\d+)?$ The main play/error path. Matches anything starting with go.
Output 4 Equals bday Plays a special birthday sequence for a friend & overrides ‘checks’.
Output 5 Otherwise (Default) Respond with, “I didn’t understand, reply with info for help.”

Validating the Request: The ‘Checks’  

If the incoming command matches the “go” regex (ie, is go alone or followed by a number) and is routed out the main play/error path, the system performs a series of prerequisite checks before fulfilling the request. These will vary based on what is important to you. Here are some samplee cases to test for:

  • Is it ‘Show Season’?: A Switch node confirms a ‘holiday’ variable is set. If it fails, the system replies with a message like, “We’re done for the season. Check back in September.” or “We’re changing over to Christmas. Check the web for start dates.” I change this often.
  • Daylight: A within-time node checks to ensure it’s dark outside. If the message is received between sunrise and sunset, the system replies with a message asking the visitor to come back after dark.
  • Is On-Demand On?: My lights are on from sunset until midnight, but I don’t want sequences triggered late at night (eg, after 9:30pm on school nights). I also only want to display my phone number when the service is available. I accomplish this by using a toggle switch in HomeAssistant that I can automatically or manually control. This check confirms that the on-demand service is currently enabled. If it fails, the system replies with a messaging like, ‘On-Demand is off for maintenance or outside show hours. Visit again soon.’
  • Is FPP Playing?: I don’t want a new request to interrupt if another sequence is already playing. This check confirms the status before moving on. It checks the MQTT topic fpp01/falcon/player/FPP/status to confirm the player is idle. If it fails, the system replies saying, “Sorry. Something else is playing now. Try again when it finishes.”
  • Presence Detection (testing): Sometimes the show is triggered when no one is watching. I suspect it’s just neighborhood kids playing around. I’ve started testing object detection from my security cameras to confirm there is a car parked or a person standing in front of the house before proceeding to play.

Action and Integration  

If the received command passes all the checks, it’s time to play something. My show plays in three modes:

  • Randomized Playlist: When my show ‘starts up’ at sunset every night, it checks the date and then populates a HomeAssistant input_select.fpp_sequences entity from the holiday-specific playlists set up in FPP. If the command is go (or go0, but no one does this), it checks an input_select.song_playlist entity for length (this will make sense in a minute).

    If the length is <= 1, Node-RED randomizes the items from input_select.fpp_sequences, populates the input_select.song_playlist entity with the full randomized list, then plays from that list. As my Halloween and Christmas shows have 30+ sequences each and few people will sit for hours to watch all of them in one session, I play a set of 4-6 sequences at a time. When it’s done, if they want more, the visitor can text go again to play the next set, picking up where they left off. Since the playlist has more than one entry, it will not re-randomize on subsequent triggers.

  • Specific, Selected Sequence: If the command is goN (where N is a non-zero number), Node-RED performs a lookup against input_select.fpp_sequences, gets the title of the Nth sequence, puts it in input_select.song_playlist and plays it.

  • Single, Randomized Sequence (on a timer): Not directly related to the on-demand feature, I also have a timer running to play a random sequence every 15 minutes. When this executes, it runs the same randomization flow as the Randomized Playlist above, but populates input_select.song_playlist, with only the first song. The idea here is that if a visitor triggers 1 or 2 sets then leaves, this timed event will fire about 15 minutes after the last sequence was played.

In the two latter scenarios, since input_select.song_playlist has only one entry, the next visitor to trigger the Randomized Playlist flow with go will get a new, randomized playlist.

Playing a sequence from Node-RED is pretty straightfoward.

  • Use a HomeAssistant Current State node to get the selected sequence title from the target input_select entity into msg.payload.
  • Then, use a Template node to build the FPP command to be published to MQTT (eg, {"command":"Start Playlist","args":["{{payload}}.fseq",false,false]}).
  • Finally, use an MQTT Out node to push that to the fpp01/falcon/player/FPP/set/command topic.

Preparing and Sending the Response  

I have about 10 total possible SMS replies to all of the above various situations. There are numerous ways one could go about managing them, including hard-coding into Change nodes or consolidating into a single Function node (which I’m considering). For now, though, I use a HomeAssistant input_text helper entity for each response message that I call with a HomeAssistant Current State node.

Whether it’s a reply in response to an information request, a failure on one of the prerequisite checks, or letting the visitor know “The show is starting”, I pull in the appropriate message and save it to msg.data. I use a HomeAssistant Action node to call rest_command.sms_reply service because it’s already set up for other uses. But as you will see, you could simply send the message with an HTTP Request node.

Node-RED Flow  

  • Current State (node): Gets the message text from the desired input_text entity.
    • Input: Connects from virtually any node near the end of the flow that contains the original msg.payload.
    • Entity ID: The entity_id of the input_text entity (eg, input_text.off_season).
    • Output Properties: msg.data = entity state.
    • Output: Connects to the Change node.
  • Change (node): This is optional, but saves time. I append a link to my website at the end of every response. _ Input: Connects from the Current State node.
    • Rules:
      • Set | msg.data
      • to the value data & " Visit https://lightsongranger.com for more info." (JSONata)
    • Output: Connects to the Action node.
  • Action (node): - This calls the rest_command service to send the message via VoIP.ms.
    • Input: Connects from the Change node.
    • Server: Assuming, by now, you have this set up already, select your HomeAssistant instance.
    • Action: rest_command.sms_reply (or whatever you’ve named your service, see below).
    • Data: Select JSONata Expression and enter {"dst":"{{payload.from}}","message":"{{{data}}}"}

HomeAssistant Rest Command Entity  

To send the SMS message, use the VoIP.ms API to send a GET request like this:

https://voip.ms/api/v1/rest.php?api_username=[YOUR_VOIPMS_USERNAME]&api_password=[YOUR_VOIPMS_PASSWORD]&method=sendSMS&did=[YOUR_VOIPMS_PHONE_NUMBER]&dst={{ dst }}&message={{ message }}"

If using HomeAssistant, add this to your configuration.yaml under your rest_command section:

sms_reply:
  url: "https://voip.ms/api/v1/rest.php?api_username=[YOUR_VOIPMS_USERNAME]&api_password=[YOUR_VOIPMS_PASSWORD]&method=sendSMS&did=[YOUR_VOIPMS_PHONE_NUMBER]&dst={{ dst }}&message={{ message }}"
  method: get

Conclusion  

Even though I recently added the ability to trigger the show from a web page, thus far I still have many more visitors using this SMS/text method. Few seem that interested in selecting specific songs and instead prefer triggering a set from the playlist, then sitting back to watch whatever plays.

But whichever method, I do think having an on-demand option to limit the playing of flashy sequences when no one is watching goes a long way to appease any neighbors who might be slightly (or more) annoyed with the show and traffic.

I hope this has been helpful and inspires you to make your own.

Light Show On-Demand via the Web 
On this page:
Background   The Goal   Prerequisites   The Workflow   Implementation   The Gateway: VoIP.ms Configuration   The Command Broker: Receiving and Routing   Validating the Request: The ‘Checks’   Action and Integration   Preparing and Sending the Response   Conclusion  

Connect with me on Bluesky or subcribe on Youtube...

   
Copyright © 2025 Lights on Granger. All rights reserved. |
Lights On Granger Lane
Code copied to clipboard