Introduction: Enable Shelly Lights to Follow Music :D

This guide shows how to develop your own application using Shelly 1 and the REST interfaces.

In this case, we will use the C# programming language and write an application that allows us to map the sounds from MIDI files to the different Shelly 1s that control the individual room lights in our home.

In this way it is possible that the lighting of Beethoven's For Elise follows what you see on the video.

Supplies

You need at least:

  • 16 Shelly's - Or Lights you can control with a shelly the shelly REST API.
  • Microsoft Visual Studio - To create the Application reading the music and control the shelly's

Step 1: Get the IP's of Your Shelly Devices

To control the lights with your own application via the REST interface you need to know the IP's of the corresponding Shelly products. The IP's can be found in the Shelly App by clicking on the respective device and displaying the device information in the settings.

First of all you need to write down the IP addresses of the 16 Shelly products for the lighting control.

Step 2: Create Your Application

Create a new console application in Visual Studio and use the following code. Use the following code and install the Package "Managed-Midi" from NuGet.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Commons.Music.Midi;

namespace ShellyMIDIToLight
{
    class Program
    {
        static void Main(string[] args)
        {
            var midiSourcePaths = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            var files = Directory.GetFiles(midiSourcePaths, "*.mid");
            Console.WriteLine("Select a file:");
            for (int n = 0; n < files.Length; n++)
            {
                
            var midiSourcePaths = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            var files = Directory.GetFiles(midiSourcePaths, "*.mid");
            Console.WriteLine("Select a file:");
            for (int n = 0; n < files.Length; n++)
            {
                Console.WriteLine($"{n + 1} - {Path.GetFileName(files[n])}");
            }
            Console.Write("Eingabe: ");
            var selected = Int32.Parse(Console.ReadLine()) - 1;

            var midiFile = files[selected];

            if (!File.Exists(midiFile))
                return;
            
            var access = MidiAccessManager.Default;
            var output = access.OpenOutputAsync(access.Outputs.Last().Id).Result;
            var music = MidiMusic.Read(System.IO.File.OpenRead(midiFile));
            var player = new MidiPlayer(music, output);
            
            player.EventReceived += PlayerOnEventReceived;
                
            player.PlayAsync();
            Console.WriteLine("Type [CR] to stop.");
            Console.ReadLine();
            player.Dispose();
        }

        private static async void PlayerOnEventReceived(MidiEvent e)
        {
            try
            {
                if (e.EventType == MidiEvent.Program)
                    Console.WriteLine ($"Program changed: Channel:{e.Channel} Instrument:{e.Msb}");
                else if (e.EventType == MidiEvent.NoteOn)
                {
                    var note = (e.Msb % 16) + 60;
                    var ri = GetRoomInfo(note);
                    ri?.TurnOn();
                    WriteRoomInfoToConsole(ri, note, "on", ConsoleColor.Green);
                }
                else if (e.EventType == MidiEvent.NoteOff)
                {
                    var note = (e.Msb % 16) + 60;
                    var ri = GetRoomInfo(note);
                    ri?.TurnOff();
                    WriteRoomInfoToConsole(ri, note, "off", ConsoleColor.Red);
                }
            }
            catch
            {
            }
        }

        private static void WriteRoomInfoToConsole(RoomInfo ri, int msb, string state, ConsoleColor cc)
        {
            var oldCc = Console.ForegroundColor;
            Console.ForegroundColor = ri == null ? ConsoleColor.Yellow : cc;
            Console.WriteLine($"Setting room {ri?.Name} to state: {state} - {msb}");
            Console.ForegroundColor = oldCc;
        }

        private static RoomInfo GetRoomInfo(int note)
        {
            if (_noteRoomInfos.ContainsKey(note))
                return _noteRoomInfos[note];

            return null;
        }
        
        private static Dictionary _noteRoomInfos = new Dictionary()
        {
            { 60, new RoomInfo("Technik", "192.168.1.171") },
            { 61, new RoomInfo("Kinderzimmer", "192.168.1.222") },
            { 62, new RoomInfo("Lager", "192.168.1.235") },
            { 63, new RoomInfo("Diele", "192.168.1.162") },
            { 64, new RoomInfo("Arbeit", "192.168.1.42") },
            { 65, new RoomInfo("Spielzimmer", "192.168.1.99") },
            { 66, new RoomInfo("Lager", "192.168.1.235") },
            { 67, new RoomInfo("Kinderzimmer", "192.168.1.222") },
            { 68, new RoomInfo("Küche", "192.168.1.14")},
            { 69, new RoomInfo("Technik", "192.168.1.171") },
            { 70, new RoomInfo("Kinderzimmer", "192.168.1.222") },
            { 71, new RoomInfo("Lager", "192.168.1.235") },
            { 72, new RoomInfo("Diele", "192.168.1.162")},
            { 73, new RoomInfo("Arbeit", "192.168.1.42") },
            { 74, new RoomInfo("Spielzimmer", "192.168.1.99") },
            { 75, new RoomInfo("Lager", "192.168.1.235") }
            
        };

        public class RoomInfo
        {
            public string Ip { get; }

            public string Name { get; }


            public RoomInfo(string name, string ip)
            {
                Ip = ip;
                Name = name;
            }

            public async void TurnOn()
            {
                HttpClient c = new HttpClient();
                await c.GetAsync($"http://{Ip}/relay/0?turn=on");
            }

            public async void TurnOff()
            {
                HttpClient c = new HttpClient();
                await c.GetAsync($"http://{Ip}/relay/0?turn=off");
            }
        }
    }
}

The code first reads all MIDI files in the application directory and makes them available for selection. After selection it reacts to the start and end of tone events within the corresponding MIDI files to switch the Shelly based lights on or off.

To make this work in your smart home environment, adjust the following location within the program.

        private static Dictionary<int, RoomInfo> _noteRoomInfos = new Dictionary<int, RoomInfo>()
        {
            { 60, new RoomInfo("Technik", "192.168.1.171") },
            { 61, new RoomInfo("Kinderzimmer", "192.168.1.222") },
            { 62, new RoomInfo("Lager", "192.168.1.235") },
            { 63, new RoomInfo("Diele", "192.168.1.162") },
            { 64, new RoomInfo("Arbeit", "192.168.1.42") },
            { 65, new RoomInfo("Spielzimmer", "192.168.1.99") },
            { 66, new RoomInfo("Lager", "192.168.1.235") },
            { 67, new RoomInfo("Kinderzimmer", "192.168.1.222") },
            { 68, new RoomInfo("Küche", "192.168.1.14")},
            { 69, new RoomInfo("Technik", "192.168.1.171") },
            { 70, new RoomInfo("Kinderzimmer", "192.168.1.222") },
            { 71, new RoomInfo("Lager", "192.168.1.235") },
            { 72, new RoomInfo("Diele", "192.168.1.162")},
            { 73, new RoomInfo("Arbeit", "192.168.1.42") },
            { 74, new RoomInfo("Spielzimmer", "192.168.1.99") },
            { 75, new RoomInfo("Lager", "192.168.1.235") }
            
        };

Replace the IP addresses with the IP addresses you determined in step 1.

Step 3: Run the Application

Insert a MIDI file into the output directory and start the application. After that you will get a list of the files and you can select them.

If you have configured all lights correctly and the REST interfaces are accessible, you can now see how your lights follow the sound.