DEV Community

loading...

Controlez vos lumières Triones Bluetooth Low Energy (BLE) avec votre Streamdeck (Partie 2)

Remy Jacquand
Prototype engineer, Maker
・6 min read

Avant d'aller plus loin, je tiens à apporter quelque précision sur ma démarche:

Ce post n'est pas à objectif pédagogique. Je ne suis pas un professionnel et je me suis adapté en fonction de mes connaissances, du matériel à ma disposition et du temps qui m'étais imparti.
Ce post, donc, est une explication de ma mise en place pour le sujet qui m'a été donné. Il sera plein de fautes, incomplet certainement voir même offrera des solutions plus compliqué que ce qu'il devrai être.

J'espère tout de même que ma démarche aidera d'autres personnes à imaginer de meilleur solution et dégrossira les difficultés qui peuvent être rencontré.

Merci à vous <3

Ps: C'est la 2e partie de mes recherhes. La partie 1 se trouve ici

Comment j'ai controlé les lumieres BLE Triones avec un Streamdeck (Partie 2)

Mise en place d'un Raspberry pi pour le controle Bluetooth des lumières

Après plusieurs essaie, j'ai décidé de me servir d'un systeme Linux pour communiqué avec les lumières à l'aide de la librairie intégrée Bluez. Cette librairie m'a permis en quelques lignes de commandes de découvrir mon réseau, me connecter et communiquer.

TODO : Détailler les opérations liés a l'installation, le scan, la connexion et les data

TODO : parler de la connection ssh + test coté windows avec streamdeck

Création du plugin Streamdeck en C

Préparation

Pour cette partie, j'ai simplement suivi le tuto de Claudio Bernasconi pour créer un plugin pour Streamdeck en C# avec Toolkit.

Pour ce faire :

  • Installez les pré-requis pour le "Streamdeck Toolkit", A savoir le Dotnet Core SDK et l'application "Streamdeck Software".
  • Installez également Visual studio 2019 Community edition pour gérer le projet et pouvoir le compiler.
  • Ouvre votre console préférée, rendez vous dans votre dossier de projet et tapez la ligne suivante:

    dotnet new -i StreamDeckPluginTemplate::0.5.2040

- Créez le dossier de votre projet. (pour moi ça sera BtTrionesBLELightStreamdeckPlugin)


mkdir BtTrionesBLELightStreamdeckPlugin

  • Entrez dedans

    cd BtTrionesBLELightStreamdeckPlugin

  • Saisissez la ligne suivante pour télécharger le template de création

    dotnet new streamdeck-plugin --plugin-name StreamdeckPluginTrionesBleLights --uuid com.khundar.streamdecktrionesblelights --skipRestore false

    . Attention à adapter le "plugin-name" et l'uuid.

    L'uuid est important pour que l'application streamdeck reconnaisse votre plugin.

  • Ouvrez le projet avec Visual Studio 2019 avec "Ouvrir un projet ou une solution" et sélectionnez le fichier BtTrionesBLELightStreamdeckPlugin.csproj

  • Vous voici dans le projet Template. Il "ne reste plus qu'à..."

Objectif 1: envoyer une commande

Mon but à ce moment-ci est d'effectuer la connexion ssh et lancer gatttool.
Pour exécuter une commande shell, j'ai trouvé cette façon de faire :

ProcessStartInfo processInfo;
Process process;

processInfo = new ProcessStartInfo("cmd.exe", "/c " + bashCmd);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;

process = Process.Start(processInfo);
process.WaitForExit();
process.Close();
Enter fullscreen mode Exit fullscreen mode

Ensuite, pour effectuer ma commande de manipulation de la lumière, je remplace la variable bashCmd


string lightMacAddr = "ff:ff:11:11:bb:9b"; //adresse de la lumiere
string switch = "cc2333"; //commande d'allumage
string color = "ff0000";
string sshconnect = "ssh pi@192.168.0.32"; //Connexion ssh
string stophci = "sudo hciconfig hci0 down"; //fermeture de la connexion bluetooth
string starthci = "sudo hciconfig hci0 up"; //fermeture de la connexion bluetooth
string gatttoolpwr = "sudo gatttool -b " + lightMacAddr + " --char-write-req --handle=0x0007 --value=" + switch;
string gatttoolcolor = "sudo gatttool -b " + lightMacAddr + " --char-write-req --handle=0x0007 --value=56" + color + "00f0aa";
string bashCmd = "\"" + stophci + " && " + starthci + " && " + gatttoolpwr + " && " + gatttoolcolor + " && " + stophci + "\"";
Enter fullscreen mode Exit fullscreen mode

Pour finir, je souhaite effectuer la commande sur un "keyup". Celle déjà en place suffira:


// ./StreamdeckPluginTrionesBleLightsAction.cs

public override async Task OnKeyUp(StreamDeckEventPayload args)
{

    string lightMacAddr = "ff:ff:11:11:bb:9b"; //adresse de la lumiere
    string switch = "cc2333"; //commande d'allumage
    string color = "ff0000";
    string sshconnect = "ssh pi@192.168.0.32"; //Connexion ssh
    string stophci = "sudo hciconfig hci0 down"; //fermeture de la connexion bluetooth
    string starthci = "sudo hciconfig hci0 up"; //fermeture de la connexion bluetooth
    string gatttoolpwr = "sudo gatttool -b " + lightMacAddr + " --char-write-req --handle=0x0007 --value=" + switch;
    string gatttoolcolor = "sudo gatttool -b " + lightMacAddr + " --char-write-req --handle=0x0007 --value=56" + color + "00f0aa";
    string bashCmd = "\"" + stophci + " && " + starthci + " && " + gatttoolpwr + " && " + gatttoolcolor + " && " + stophci + "\"";
    processInfo = new ProcessStartInfo("cmd.exe", "/c " + sshconnect + " " + bashCmd);
    processInfo.CreateNoWindow = true;
    processInfo.UseShellExecute = false;
    processInfo.RedirectStandardError = true;
    processInfo.RedirectStandardOutput = true;

    process = Process.Start(processInfo);
    process.WaitForExit();
    process.Close();


    //update settings
    await Manager.SetSettingsAsync(args.context, SettingsModel);
}
Enter fullscreen mode Exit fullscreen mode

Très bien, mon action est prête.

Objectif 2: Dynamiser cette commande

À présent, isolons les variables nécessaires à la configuration d'une lumière :

  • l'adresse MAC de la lumière => lightMacAddr
  • l'état de la lumière => switch
  • la couleur => color

Commençons par modifier le model


// ./models/TrionesBLESettingsModel.cs

namespace StreamdeckPluginTrionesBleLights.Models
{
    public class TrionesBLESettingsModel
    {
        public string Switch { get; set; } = "000000";
        public string Color { get; set; } = "000000";
        public string LightMacAddr { get; set; } = "xx:xx:xx:xx:xx:xx";

    }
}

Enter fullscreen mode Exit fullscreen mode

Ensuite, on ajoute les attributs du model dans le controleur.


// ./StreamdeckPluginTrionesBleLightsAction.cs

public override async Task OnKeyUp(StreamDeckEventPayload args)
{

    string lightMacAddr = SettingsModel.LightMacAddr; //adresse de la lumiere
    string switch = SettingsModel.LightMacAddr; //commande d'allumage
    string color = SettingsModel.Color;
    string sshconnect = "ssh pi@192.168.0.32"; //Connexion ssh
    string stophci = "sudo hciconfig hci0 down"; //fermeture de la connexion bluetooth
    string starthci = "sudo hciconfig hci0 up"; //fermeture de la connexion bluetooth
    string gatttoolpwr = "sudo gatttool -b " + lightMacAddr + " --char-write-req --handle=0x0007 --value=" + switch;
    string gatttoolcolor = "sudo gatttool -b " + lightMacAddr + " --char-write-req --handle=0x0007 --value=56" + color + "00f0aa";
    string bashCmd = "\"" + stophci + " && " + starthci + " && " + gatttoolpwr + " && " + gatttoolcolor + " && " + stophci + "\"";
    processInfo = new ProcessStartInfo("cmd.exe", "/c " + sshconnect + " " + bashCmd);
    processInfo.CreateNoWindow = true;
    processInfo.UseShellExecute = false;
    processInfo.RedirectStandardError = true;
    processInfo.RedirectStandardOutput = true;

    process = Process.Start(processInfo);
    process.WaitForExit();
    process.Close();


    //update settings
    await Manager.SetSettingsAsync(args.context, SettingsModel);
}

Enter fullscreen mode Exit fullscreen mode

Et on fini avec la vue.


<!-- ./property_inspector/property_inspector.html -->

<body>
    <div class="sdpi-wrapper">
        <!-- select light -->
        <div class="sdpi-item" id="select_light">
            <div class="sdpi-item-label">Lights</div>
            <select class="sdpi-item-value select" id="light" onchange="setSettings(event.target.value, 'LightMacAddr');">
                <option value="xx:xx:xx:xx:xx:xx">--Select a light--</option>
                <option value="ff:ff:11:11:bb:9b" data-name="Light 1">Light 1</option>
                <option value="ff:ff:50:03:10:3c" data-name="Light 2">Light 2</option>
                <option value="ff:ff:66:60:cf:58" data-name="Light 3">Light 3</option>
                <option value="ff:ff:77:70:b1:a4" data-name="Light 4">Light 4</option>
                <option value="ff:ff:33:30:fe:ee" data-name="Light 5">Light 5</option>
                <option value="ff:ff:44:42:77:82" data-name="Light 6">Light 6</option>

            </select>
        </div>
        <!-- switch on/off -->
        <div class="sdpi-item" id="select_switch"> 
            <div class="sdpi-item-label">Switch</div>
            <select class="sdpi-item-value select" id="switch" onchange="setSettings(event.target.value, 'Switch')">
                <option value="cc2333">ON</option>
                <option value="cc2433">OFF</option>
                <option value="000000">--Select a Power--</option>
            </select>
        </div>
        <!-- color -->
        <div type="range" class="sdpi-item" id="colorslider" onchange="setSettings(event.target.value, 'Color')">
            <div class="sdpi-item-label">Color</div>
            <div class="sdpi-item-value">
                <input type="color" id="color" value="#000000">
            </div>
        </div>
    </div>
    <script src="js/property-inspector.js"></script>
</body>
Enter fullscreen mode Exit fullscreen mode

et le js

// global websocket, used to communicate from/to Stream Deck software
// as well as some info about our plugin, as sent by Stream Deck software 
var websocket = null,
    uuid = null,
    inInfo = null,
    actionInfo = {},
    settingsModel = {
        LightMacAddr: 'xx:xx:xx:xx:xx:xx',
        Switch: '000000',
        Color: '000000'
    };

function connectElgatoStreamDeckSocket(inPort, inUUID, inRegisterEvent, inInfo, inActionInfo) {
    uuid = inUUID;
    actionInfo = JSON.parse(inActionInfo);
    inInfo = JSON.parse(inInfo);
    websocket = new WebSocket('ws://localhost:' + inPort);

    //initialize values
    if (actionInfo.payload.settings.settingsModel) {
        settingsModel.LightMacAddr = actionInfo.payload.settings.settingsModel.LightMacAddr;
        settingsModel.Switch = actionInfo.payload.settings.settingsModel.Switch;
        settingsModel.Color = actionInfo.payload.settings.settingsModel.Color;
        settingsModel.Name = actionInfo.payload.settings.settingsModel.Name;

    }
    websocket.onopen = function () {
        console.log(settingsModel);
        setSelectField('light', settingsModel.LightMacAddr);
        setSelectField('switch', settingsModel.Switch);
        document.getElementById("color").value = '#' + settingsModel.Color;


        var json = { event: inRegisterEvent, uuid: inUUID };
        // register property inspector to Stream Deck
        websocket.send(JSON.stringify(json));

    };

    websocket.onmessage = function (evt) {
        // Received message from Stream Deck
        var jsonObj = JSON.parse(evt.data);
        var sdEvent = jsonObj['event'];
        switch (sdEvent) {
            case "didReceiveSettings":

                break;
            default:
                break;
        }
    };
    function setSelectField(id, value) {
        var allInputs = document.getElementById(id);
        for (var x = 0; x < allInputs.length; x++)
            if (allInputs[x].value == value) {
                allInputs[x].setAttribute('selected', 'selected');
                console.log(allInputs[x]);
            }

    }
}

const setSettings = (value, param) => {
    console.log(param + "=" + value);
    if (websocket) {

        if (param == 'Color')
            value = value.substring(1);

        settingsModel[param] = value;
        var json = {
            "event": "setSettings",
            "context": uuid,
            "payload": {
                "settingsModel": settingsModel
            }
        };

        websocket.send(JSON.stringify(json));
    }

};
Enter fullscreen mode Exit fullscreen mode

A présent, mon plugin est prêt à être utiliser

Vue du plugin

Discussion (0)