amBX lights met TFS 2010, een Deja Vu

Eind 2009 (het was de nacht voor kerstmis, je ziet de kerstboom op de achtergrond) heb ik een artikeltje geplaatst over het aansturen van ledverlichting met Team Foundation Service 2008. Het was een ludieke actie rond eenzelfde oplossing van een collega (gebaseerd op een dure electronica) maar ik gebruikte gewoon een lampensetje van 25 euro gekocht bij een computerzaak tesamen met een gratis DLL. (https://sandervandevelde.wordpress.com/tag/ambx-lights/ )

Deze oplossing was gebaseerd op het aanpassen van het buildscript en wat handig omgaan met WCF. Ik heb destijds nog gekeken of ik het ook aan de praat kreeg met TFS 2010 maar de buildscripts waren inmiddels door Workflow Foundation (WF4) vervangen en het paste dus niet meer op mijn laptopschermpje.

Vandaag heb ik mijn amBX setje uit het vet gehaald voor een ander klusje waarbij de lampen ook hun opwachting mogen maken. Ik kreeg mijn setje zelfs aan de praat op Windows 7 64 bits. Het kan nog steeds in combinatie met de oude 1.10 drivers! En dus dacht ik, er staat nog een klusje open…

Maar nu hebben we de Team Foundation Service API. Haal het even op… Na installatie staan er een hoop assemblies in C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0

Onderstaande oplossing werkt prima. Ik start de app even op en zie de status in de lampen terug.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using amBXLibrary;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Common;

namespace TfsBuildNotify
{
  internal class Program
  {
    private static int Main(string[] args)
    {
      try
      {
        string uri = "http:// s erver. c om:8080/tfs/prd";
        string teamProjectName = "teamprojectname";
        string buildDefinitionName = "builddefinitionname";
        string userName = "username";
        string password = "password";
        string domain = "domainname";
        bool readKey = true;
        using (var TeamProjectCollection =
          new TfsTeamProjectCollection(new Uri(uri),
          new NetworkCredential(userName, password, domain)))
        {
        TeamProjectCollection.Connect(ConnectOptions.IncludeServices);
        TeamProjectCollection.EnsureAuthenticated();
        IBuildServer buildServer =
          (IBuildServer)TeamProjectCollection.GetService(typeof(IBuildServer));
        var buildDefinitions = buildServer.QueryBuildDefinitions(teamProjectName);
        IBuildDefinition myBuildDefinition = null;
        foreach (var buildDefinition in buildDefinitions)
        {
          if (buildDefinition.Name == buildDefinitionName)
          {
            Console.WriteLine(buildDefinition.Name);
            myBuildDefinition = buildDefinition;
            break;
          }
        }
        if (myBuildDefinition != null)
        {
          var builds = buildServer.QueryBuilds(myBuildDefinition);
          // find current build in progress
          IBuildDetail myBuild = (from build in builds
            where build.Status == BuildStatus.InProgress
            select build).FirstOrDefault();
          if (myBuild == null)
          {
            // find last build started
            myBuild = (from build in builds
              orderby build.StartTime descending
              select build).FirstOrDefault();
          }
          if (myBuild != null)
          {
            // Check state of found build
            try
            {
              switch (myBuild.Status)
              {
                case BuildStatus.Failed:
                  amBXWrapper.PlayRed(200, 10);
                  break;
                case BuildStatus.InProgress:
                  amBXWrapper.PlayWhite(200, 10);
                  break;
                case BuildStatus.PartiallySucceeded:
                  amBXWrapper.PlayRed(1000, 5);
                  break;
                case BuildStatus.Stopped:
                  amBXWrapper.PlayRed(1800, 5);
                  break;
                case BuildStatus.Succeeded:
                  amBXWrapper.PlayGreen(1800, 5);
                  break;
                }

                Console.WriteLine("State: " + myBuild.Status.ToString());
              }
              catch (Exception ex)
              {
                throw new ArgumentException("amBX error: " + ex.Message);
              }
            }
            else
            {
              // no build found
              amBXWrapper.PlayWhite(1800, 5);
              Console.WriteLine("Found: None");
            }
          }
          if (readKey)
          {
            Console.ReadKey();
          }
        }
        return 0;
      }
      catch (Exception ex)
      {
        Console.WriteLine("Error: " + ex.Message);
        Console.ReadKey();
        return 1;
      }
    }
  }
}

Eerst haal ik de TeamProjectCollection van de server via mijn credentials. Mocht je tegen de beveiliging van TFS aanlopen, kijk dan eens naar deze blog . Daar staat prima uitgelegd hoe je met de credentials moet omgaan.

Vervolgens haal ik via de buildServer de BuildDefinitions voor ons teamproject op. Hierbij wil ik er speciaal eentje uitpikken…

En van die definitie probeer ik de meest recente build te achterhalen. Eerst kijk ik of er een build “in progress” is. Indien dit niet het geval is, probeer ik de laatst gestarte build te achterhalen.

Achteraf stuur ik de lampen aan nav. de status van de gevonden build. Het is dus een simpele oplossing voor een simpel probleem. De Build class ondersteunt ook events maar daar ben ik niet verder op in gegaan. Ook kan, indien je voldoende rechten hebt, een build beïnvloed worden (start/stop) of zelfs volledig met code aangemaakt worden.

Succes met je eigen builds.

Team Foundation Server controlled …ehhm… amBX lights?

Enige tijd geleden heeft mijn collega Stephan Smetsers een fraai artikel gepubliceerd om gloeilampen als stoplichten te schakelen. Maar gloeilampen zijn zo 2008, in 2010 willen we LED verlichting!

Natuurlijk kunnen we in de kerst-uitverkoopjes van een online gadget winkel enkele USB kerstboompjes uitzoeken welke met een programmaatje op het
scherm geschakeld worden, maar we willen de lat hoger leggen. Tegenwoordig heb je fraaie Ambilight flatscreens, dus waarom zouden we minder willen?

ambx lights

Toevallig heeft een Philips spin-off Ambilight naar de PC gebracht  http://www.ambx.com/ . Hierdoor is het mogelijk om het spelen van een PC spel te vervolmaken met geluid, licht, wind en trillingen. Leuk voor de kerstdagen maar als developer denken we verder!

Want toevallig is er ook een SDK beschikbaar http://developer.ambx.com/ zodat we toch niet tijdens de feestdagen uit het raam hoeven te staren. Dus wat moeten we nog snel inkopen en/of downloaden voordat de winkels dicht zijn:

Philips amBX starter kitofPhilips amBX 2.1 premium kit 25 euro bij Computerland http://www.computerland.nl/66 euro bij Paradigit http://www.paradigit.nl/
Visual Studio 2008 Express Gratis te downloaden op http://www.microsoft.com/exPress/
Een amBX Developer account Aan te maken op http://developer.ambx.com/
De amBX SDK 1.1.0 Latest release – 26th February 2009 https://developer.ambx.com/component/attachments/download/6
Een C# wrapper rond de ambxrt.dll http://grazz.com/projects/ambxlib/(of https://developer.ambx.com/forum/viewtopic.php?f=5&t=80 )

Vervolgens bouwen we drie hoofdcomponenten:

TFS Custom MsBuild Task Deze task roept de webservice methodes aan om de status van een build aan te geven. Via parameters is door te geven of een build start of stopt. Tevens is de status door te geven (niet of wel gebuild)
Webservice De webservice krijgt commando’s om de status van de build bij te
houden
WindowsService De windows service controleert regelmatig de status op de webservice en stuurt echt de lampen aan via een proxy klasse.

De webservice moet dus verschillende methodes ondersteunen:

Build X is gestart Flikkerend ambi light effecten
Build X is geslaagd Groene glow, langzaam aan en uit
Build X is niet geslaagd:  build faalde Rode glow, snel aan en uit
Wat is de status vande huidige build? Gelezen door windows service

De opstelling wordt dan als volgt:

ambx architectuur

  • De TFS server heeft de mogelijkheid om de amBX server aan te roepen via de custom MSBuild tasks.
  • De webservice op de amBX Server ontvangt de opdrachten en houdt de status (semafoor) vast.
  • Een Windows Service controleert regematig (met een timer) de build status en stuurt de lampen aan.
  • Een ontwikkelaar is verbaasd omdat zijn briljante code wel heel veel
    briljante kleuren oplevert. Zijn baas maakt een aantekening…

Maak dus eerst een build aan op de TFS Server:

TFS Build

Op de TFS kan dan de bijbehorende TFSBuild.proj aangepast worden:

<?xml version="1.0" encoding="utf-8"?>
<Project>
  <UsingTask TaskName="AmbxLighterTask.AmbxTask"
             AssemblyFile="C:\BuildOutput\AmbxLighterTaskLibrary.dll"/>
  <Target Name="BeforeEndToEndIteration">
    <AmbxLighterTask.AmbxTask
      Status="1"
      TfsAmbxWebService="htt p://AmbxServer/TfsAmbxLighter/Service.svc"
      ContinueOnError="true"/>
  </Target>
  <Target Name="AfterEndToEndIteration">
    <AmbxLighterTask.AmbxTask
      Status="2"
      TfsAmbxWebService="htt p://AmbxServer/TfsAmbxLighter/Service.svc"
      ContinueOnError="true"/>
  </Target>
  <Target Name="BeforeOnBuildBreak">
    <AmbxLighterTask.AmbxTask
      Status="3"
      TfsAmbxWebService="htt p://AmbxServer/TfsAmbxLighter/Service.svc"
      ContinueOnError="true"/>
  </Target>
</Project>

Check deze weer in als deze aangepast is.

Plaat vervolgende op de opgegeven lokatie op de TFS server de dll met
de custom MSBuild task (AmbxLighterTaskLibrary.dll)

public class AmbxTask : Task
{
  [Required]
  public string Status { get; set; }
  [Required]
  public string TfsAmbxWebService { get; set; }
  public override bool Execute()
  {
    try
    {
      SemaphoreType semaphore =
        (SemaphoreType)Convert.ToInt32(Status);
      BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
      EndpointAddress endpointAddress = new
        EndpointAddress(TfsAmbxWebService);
      ChannelFactory<IService> channelFactory =
        new ChannelFactory<IService>(basicHttpBinding,
          endpointAddress);
      IService service = channelFactory.CreateChannel();
      switch (semaphore)
      {
        case SemaphoreType.BuildIsStarted:
          service.BuildHasStarted();
        break;

        case SemaphoreType.BuildIsSucceeded:
          service.BuildHasSucceeded();
        break;

        case SemaphoreType.BuildIsFailed:
          service.BuildHasFailed();
        break;
      }

      channelFactory.Close();
      return true;
    }
    catch (Exception ex)
    {
      Log.LogMessage("*** AmbxTask " + Status
          + " exception: " + ex.Message);
      return false;
    }
  }
}

In de task word een aanroep naar de AmbxServer uitgevoerd (geen extra config
nodig met WCF settings voor de connectie in dit geval).

De WCF service heeft het volgende contract:

[ServiceContract]
public interface IService
{
  [OperationContract]
  void BuildHasStarted();
  [OperationContract]
  void BuildHasSucceeded();
  [OperationContract]
  void BuildHasFailed();
  [OperationContract]
  int ShowStatus();
}

De service draait dus op de Ambx server en is stateless met uitzondering van
één STATUS waarde. Deze wordt met een WindowsService timer uitgelezen:

void _Timer_Elapsed(object sender, ElapsedEventArgs e)
{
  TfsAmbxLighterServiceReference.ServiceClient
    client =
      new TfsAmbxLighterServiceReference.ServiceClient();
  SemaphoreType semaphore = (SemaphoreType) client.ShowStatus();
  switch (semaphore)
  {
    case SemaphoreType.BuildIsStarted:
      ResetTeller();
      AmbxProxy.ShowBuilding();
    break;

    case SemaphoreType.BuildIsSucceeded:
      _Teller++;
      if (_Teller >= _IdleInterval)
      {
        if (_Teller % _IdleInterval == 0)
        {
          AmbxProxy.ShowBuildSucceed();
          break;
        }
        break;
      }
      AmbxProxy.ShowBuildSucceed();
    break;

    case SemaphoreType.BuildIsFailed:
      _Teller++;
      if (_Teller >= _IdleInterval)
      {
        if (_Teller % _IdleInterval == 0)
        {
          AmbxProxy.ShowBuildFailed();
          break;
        }
        break;
      }
      AmbxProxy.ShowBuildFailed();
    break;
  }
  client.Close();
}

En de windowsService roept du s een Proxy klasse rond de amBX api aan (met
gebruik van amBXLib van http://grazz.com/projects/ambxlib/

public class AmbxProxy
{
  public static void ShowBuildSucceed()
  {
    using (amBX engine = new amBX(1, 0, "AmbxProxy", "v1.0"))
    {
      amBXLight everyLight =
        engine.CreateLight(
          CompassDirection.Everywhere,
          RelativeHeight.AnyHeight);
      everyLight.FadeTime = 1800;
      everyLight.Color = new amBXColor(){Red=0f,Green=1f,Blue=0f};
      Thread.Sleep(1800);
      everyLight.Color = new amBXColor(){Red=1f,Green=1f,Blue=1f};
      Thread.Sleep(1800);
      everyLight.Color = new amBXColor(){Red=0f,Green=0f,Blue=0f};
      Thread.Sleep(1800);
    }
  }
}

De amBXLight instantie wordt dus steeds van een andere kleur voorzien
en de opgegeven fade krijgt de kans door de blocked call (de sleep implementatie). Het is prettig voor de gebruiker als je weer terug fade naar zwart.

Het resultaat is hier te bekijken op YouTube waarbij ik de  werking demonstreer met een laptop en een  server.

Conclusie

Voor een paar centen  heb je een hoop lol en leer je terloops ook nog iets over TFS uitrollen en  inrichten. Ook WCF Webservice, Windows Services en MSBuild komt langs. Dus je hoeft je niet te vervelen. Mocht het toch niks worden met TFS dan kun je altijd nog de kerstperiode vullen met het bijgeleverde racespel.

Prettige kerstdagen!

Update: Met TFS 2010 stapt MS over op Workflow Foundation (WF4) als build script. Hoewel bestaande MSBuild oplossingen ook nog ondersteund worden zou bij WF4 gekozen kunnen worden voor het simpel toevoegen van een SendActivity. Deze al bestaande Activity kan WCF services aanroepen. De AmbxTask verandert in een geconfigureerde activity. Dus kan gewoon de juiste methode op de http://AmbxServer/TfsAmbxLighter/Service.svc aangeroepen
worden.