Turn your Raspberry Pi into a Personal Assistant using Cortana

Microsoft is constantly updating its latest version of Windows, version 10. For me, as a developer, it’s a wonderful operating system to program for. The UWP apps I build, run on both PC’s, laptops, Windows Surface Hub (up to 84 inches), The Xbox One and even on a Raspberry Pi. Yes, Windows 10 is running on a 35 dollar device.

But before you run to the store to replace your PC, I have to tell you it’s running the core of Windows 10, actually. There is no shell (no menu, no start bar etc.).

So this means you can run one visual (headed) UWP application and multiple background applications. And yes, you will love it!

This is a great interface for kiosk-like devices. And with the latest update (build 15063), it’s easy to add Cortana support.

Cortana is the speech service, available in Windows 10. If you know Siri or Alexa, then you know Cortana. Just ask her a question and she will try to answer it. The answer will be provided by speech or supported by browsers or other visual help.

Let’s take a look on how to enable Cortana on a Raspberry Pi.

Continue reading “Turn your Raspberry Pi into a Personal Assistant using Cortana”

Advertenties

Ping done easy (by yourself – coders only)

I just connected a headless Raspberry Pi to a wifi adapter but I did not know which IP Address was given. I need that address to make contact with PuTTY.

So I wanted to figure out the IP Address. As a programmer, what should I do? Surf the internet for some nifty tool? Of should I write some code? I have chosen the latter.

So now I ping each port with:

internal class Program
{
    private static void Main(string[] args)
    {
        for (int i = 90; i < 254; i++)
        {
            Console.Write("192.168.0." + i + ": ");
            Console.WriteLine(Ping("192.168.0." + i));
        }
    }
 
    public static bool Ping(string nameOrAddress)
    {
        Ping ping = new Ping();
        try
        {
            var response = ping.Send(nameOrAddress);
            var pinged = (response.Status == IPStatus.Success);
 
            return pinged;
        }
        catch (PingException)
        {
            return false;
        }
    }
}

So start it and take a cup of coffee…

And check the addresses:

ping01

This is simple to use, have fun with it!

 

Getting the token expiration date in Azure Mobile Services

Azure Mobile Services support storing the Oauth user credentials token in the PasswordVault of the OS a client app is running in. This is supported in Windows Universal apps, Xamarin, etc.

See here for the details regarding user credentials from the AMS documentation.

And this is a good thing, now the user does not have to supply the Oauth credentials every time the app is started. We can check if the vault contains the credentials we need and this saves a round trip to the server and an extra input screen. If it is not in the vault, just let the user login using Facebook, Google etc.  and store the token.

But wait: these stored credential tokens are not valid until the end of times. For example, Google credentials are valid for 30+ days. After that, the token stored in the vault is worthless.

Therefor, in the AMS example above a simple check is done by calling the AMS service with a trivial request:

...
try 
{ 
  // Try to return an item now to determine if the cached credential has expired. 
  await App.MobileService.GetTable<TodoItem>().Take(1).ToListAsync();
               // <<-- WHY??? 
} 
catch (MobileServiceInvalidOperationException ex) 
{ 
  if (ex.Response.StatusCode == System.Net.HttpStatusCode.Unauthorized) 
  { 
    // Remove the credential with the expired token. 
    vault.Remove(credential); credential = null; continue; 
   } 
}
...

Here a simple GET is done to check the validity of the token. If it is not valid, it is deleted and the user must login again.

But this is a web call I do not like.

This is done every time user logs in / starts the app, even in an offline scenario!

In an offline scenario I am not even interested in the expiration date…

Is there a better way?

Well, what we get from the Oauth providers, when the user logs in successfully, is a Json Web Token (JWT). And this token can be decoded following the JWT specifications.

This will result in something like:

public class Token
{
    [JsonProperty(PropertyName = "ver")]
    public string Ver { get; set; }
 
    [JsonProperty(PropertyName = "uid")]
    public string Uid { get; set; }
 
    [JsonProperty(PropertyName = "exp")]
    public string Exp { get; set; }
 
    [JsonProperty(PropertyName = "nbf")]
    public string Nbf { get; set; }
 
    [JsonProperty(PropertyName = "aud")]
    public string Aud { get; set; }
}

This token is available on the server as:

App.MobileService.CurrentUser.MobileServiceAuthenticationToken

And the token can be decoded using a Nuget package. The only drawback is that this package is not available on the Universal apps client, only on the Azure Mobile Services server, the backend.

So I wrote this extra API controller:

public class DecodeExpirationController : ApiController
{
  public ApiServices Services { get; set; }
 
  // GET api/DecodeExpiration
  [AuthorizeLevel(AuthorizationLevel.User)]
  public DateTime? Get(string token)
  {
    string adminKey;
 
    if (!Services.Settings.TryGetValue("ADMIN_KEY", out adminKey))
    {
      return null;
    }
 
    try
    {
      var jsonPayload = JWT.JsonWebToken.Decode(token, adminKey, false);
 
      var result = JsonConvert.DeserializeObject<Token>(jsonPayload);
 
      var startOfUtcCount = 
        new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
 
      var expirationDate = startOfUtcCount.
                             AddSeconds(Convert.ToInt32(result.Exp));
 
      return expirationDate;
    }
    catch (JWT.SignatureVerificationException)
    {
      return null;
    }
    catch (Exception ex)
    {
      return null;
    }
  }
}

Note the level of authorization, the user should be logged in by now…

And we can call this WebApi GET on the client:

try
{
  var parameters = new Dictionary<string, string>();
  parameters.Add("token",
    App.MobileService.CurrentUser.MobileServiceAuthenticationToken);
  var result = await App.MobileService.
        InvokeApiAsync<DateTime?>("DecodeExpiration", HttpMethod.Get, parameters);
 
  message = result.HasValue ? result.Value.ToString() : "Nothing returned.";
}
catch (Exception ex)
{
  message = "Exception: " + ex.Message;
}

This should work using with every provider.

The Admin Key is coming from the portal. I have added this appsetting to the configuration of the AMS.

Using this call we get the UTC time of the token expiration date. So store this on the client and check this value every time (give or take a few hours due to correct daylight savings when applicable).

“Don’t shoot me, I’m just the In Process messenger”

In need of an English translation? Please drop me a note.

Voor ons huidige project zochten we naar een goed ontkoppeling tussen de user interface en de communicatie met hardware. Omdat het een WPF applicatie is en we gebruik maken van MVVM draait alles in hetzelfde applicatie.  De hardware willen we real time uitlezen en reageren op het gedrag.

Er werd dus nagedacht over het gebruik van Events. Dat is een prima oplossing voor “Loose coupling”. Maar hierbij is de ontkoppeling maar één kant op. De ‘luisteraar’ kent de verstrekker van de gebeurtenissen want hij moet zich daarop abonneren.

Omdat we MVVMLight gebruiken kregen we de mogelijkheid om nog verder te gaan met ontkoppeling. MVVMLight bezit namelijk ook een Messagebus.

De messagebus kennen we vooral vanuit SOA en Enterprise architectuur:

Maar hier hebben we te maken met messsages die In Process verstuurd worden. Hierbij zijn de volgende klassen te herkennen:

  1. De boodschap, de Message
  2. Diegene die ‘m verstuurt, de Sender
  3. Diegene die zich abonneert op dit type bericht, de Target of algemener, de Receiver
  4. De Messenger

Bij MVVMLight is dit opgelost met een aantal objecten die globaal beschikbaar zijn en via locking threadsafe zijn gemaakt. Hierdoor wordt het mogelijk om overal in de applicatie boodschappen te verzenden en eventueel te ontvangen.

Een boodschap is in het algemeen een overerving  van MessageBase, een standaard klasse beschikbaar in MVVMLight:

public class DeviceMessage : MessageBase
{
  public DeviceMessage(string message)
  {
    Message = message;
  }

  public string Message { get; private set; }

  public override string ToString()
  {
    return "Device message: " + Message;
  }
}

Hier hebben we een DeviceMessage die één property genaamd Message bezit.
Een message wordt verstuurd via de Send methode op de static Messenger:

Messenger.Default.Send<DeviceMessage>
    (new DeviceMessage("Time on device: " + DateTime.Now.ToString()));

Meer is er niet nodig om een boodschap op de Messagebus te zetten. In dit voorbeeld kan ik berichten met een datum/tijd verwachten. Zo kan ik ook vanuit een ViewModel juist een start- of stopcommando doorgeven aan een bepaald apparaat (of beter: een instanciering van iets wat de interface van dat apparaat implementeert). Ik heb geen kennis van de luisteraar, ik weet niet eens of die wel bestaat.

Hoe wordt het bericht ontvangen? Via een constructor of initialisatie heeft een andere partij zich geabonneerd op deze berichten:

Messenger.Default.Register<DeviceMessage>
  (this, true, m => {   Message = m.Message; });

In dit geval wordt de boodschap in op een ViewModel de m.Message uitgelezen en op het scherm getoond (via een bindable property).

Maar er is ook een bonus. De MessageBus is globaal beschikbaar en iedereen kan zich abonneren op berichten. MVVMLight biedt de mogelijkheid om met een Message ook een filtertoken op te nemen.

Messenger.Default.Send<SwitchMessage>
  (new SwitchMessage(this, "AnotherDevice"), "DeviceSwitchToken");

In dit geval kunnen meerdere partijen zich abonneren op een SwitchMessage. Maar door de meegestuurde “DeviceSwitchToken” zal wellicht niet iedereen de SwitchMessage verwerken, ze wachten op zo’n bericht vergezeld met een ander token.

En het is ook mogelijk om zich juist op een scala van berichten te abonneren. Het is mogelijk om alle overervingen van een baseclass te ontvangen:

public class LogProvider : ILogProvider
{
  public void Initialize()
  {
    // log all messages
    Messenger.Default.Register<MessageBase>(this, true, m =>
    {
      Debug.WriteLine(
        String.Format(
           "At {0} we received: {1}", DateTime.Now.ToShortTimeString(), m));
    });
  }
}

Hier heb ik een logger geabonneerd op alle overervingen van MessageBase.

Door de totale ontkoppeling van de zenders en ontvangers kan je het overzicht verliezen. Maar juist door deze logger heb ik een mooie tijdslijn van wat er onderling verstuurd is en kan ik analyseren wat er mis is gegaan. Daarom implementeer ik ook de ToString() zodat ik de messages hun eigen te loggen boodschap kan laten beschrijven.

Ik kan ook als een soort van watchdog een pulse afgeven. Als ik niet de gewenste antwoorden ontvang kan ik ‘alarm slaan’. En dit kan ik doen zonder dat ik de daadwerkelijke implementatie van de te controleren instanties niet ken.

Er zijn in MVVMLight nog een aantal andere boodschappen mogelijk. Ik wil er nog één aanstippen.

MVVMLight kent ook een generic message. Deze bevat standaard een Content property waarin een andere klasse gestopt kan worden. In bovenstaande voorbeelden is steeds gebruik gemaakt van een MessageBase overerving met extra properties. Met de generic oplossing kunnen die properties in de Content geplaatst worden. De Content zier er dan zo uit:

public class SwitchContent
{
  public SwitchContent(int id, string message)
  {
    ID = id;
    Message = message;
  }

  public int ID { get; private set; }

  public string Message { get; private set; }
}

De message wordt een stuk eenvoudiger:

public class GenericSwitchMessage : GenericMessage<SwitchContent>
{
  public GenericSwitchMessage(SwitchContent switchParameter)
        : base(switchParameter)
  {
  }

  public override string ToString()
  {
    return String.Format(
      "Switch message: sender{0}; target={1}", Sender, Target);
  }
}

Het te verzenden bericht wordt nu gecombineerd met de context:

Messenger.Default.Send<GenericSwitchMessage>
  (new GenericSwitchMessage(new SwitchContent (1, "Hallo")));

En het uitluisten ziet er ook wat rustiger uit:

public class LogProvider : ILogProvider
{
  public void Initialize()
  {
    // log all messages
    Messenger.Default.Register<GenericSwitchMessage>(this, true, m =>
    {
      // m.Content.ID
      // m.Content.Message
    });
  }
}

Conclusie:

De MVVMLight Messenger biedt een fraaie oplossing voor de totale ontkoppeling. Er zijn ook andere implementaties beschikbaar (iedere MVVM implementatie lijkt er wel één te bieden) dus het is eenvoudig om er aan te komen en om het eens uit te proberen.

Een WPF StoryBoard starten vanuit het MVVM Viewmodel

 

In need of an English translation? Please drop me a note.

Sinds enige tijd werk ik aan een project waarbij de UI volledige in WPF4 is geschreven. Hierbij heb ik mezelf het doel gesteld om de architectuur volledig op MVVM te baseren.

Dit betekent concreet dat de schermen (de Views) geen code-behind hebben maar via een ViewModel interactie hebben met het Model. Alle schermwijzigingen worden met bindings en commands afgehandeld.

Zelf MVVM implementeren kan vast wel maar er zijn ook veel frameworks: grote, kleine, goede en foute… Uiteindelijk ben ik bij MVVMLight uitgekomen.

Ontwikkelen in WPF blijft door het gebruik van MVVM een feest, ondanks de steile leercurve maar dankzij de strikte scheiding tussen de te tonen data (ViewModel) en de representatie (XAML).

En ik ben ook begonnen met het gebruik van StoryBoards. Dit zijn notaties om de vorm verschijning en plaats van Xaml objecten aan te passen op een tijdslijn. Zo is het mogelijk om animaties in de UI te verwerken die wel degelijk van functioneel nut kunnen zijn.

StoryBoards worden bij voorkeur aan gebeurtenissen gehangen. Als voorbeeld toon ik hier hoe een plaatje langzaam zichtbaar wordt bij het starten van de applicatie door de Opacity in vijf seconden van 0 naar 1 te laten lopen.

Hieronder is de XAML beschreven en de code in het viewmodel. Dit zijn Snippets die zo te combineren zijn met een MvvmLight WPF4 template project.

<Grid x:Name="LayoutRoot"
      Width="284">
  <TextBox FontSize="36"
           FontWeight="Bold"
           Foreground="Purple"
           Text="{Binding WelcomeTitle}"
           VerticalAlignment="Center"
           HorizontalAlignment="Center"
           TextWrapping="Wrap"
           Margin="12,12,42,100" />
  <Image Height="51"
         Name="image"
         Source="Assets\troll-face.png"
         Opacity="0.5"
         Stretch="Uniform"
         HorizontalAlignment="Left"
         Margin="178,182,0,0"
         VerticalAlignment="Top"
         Width="61">
    <Image.Triggers>
      <EventTrigger RoutedEvent="Image.Loaded">
        <BeginStoryboard Name="MyBeginStoryboard">
          <Storyboard>
            <DoubleAnimation Storyboard.TargetProperty="Opacity"
                             From="0"
                             To="1"
                             Duration="0:0:5" />
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger>
    </Image.Triggers>
  </Image>
</Grid>

Er staat een Textbox op de pagina voor de invoer van een string en een plaatje. Dit plaatje bevat een StoryBoard dit wordt afgevuurd als de pagina wordt geladen voor vertoning.

De Textbox is gebonden aan de WelcomeTitle op het ViewModel.

public class MainViewModel : ViewModelBase
{
  public MainViewModel()
  {
    WelcomeTitle = "Hello";
  }

  public const string WelcomeTitlePropertyName = "WelcomeTitle";
  private string _welcomeTitle = string.Empty;
  public string WelcomeTitle
  {
    get
    {
      return _welcomeTitle;
    }
    set
    {
      if (_welcomeTitle == value)
      {
        return;
      }

      _welcomeTitle = value;

      RaisePropertyChanged(WelcomeTitlePropertyName);
    }
  }
}

De uiteindelijke functionaliteit is eenvoudig. Indien de Textbox wordt verlaten zal de Getter van WelcomeTitle uitgevoerd worden.

Het scherm zier er zo uit:

Mijn wens is dat het Troll-plaatje pas getoond wordt als de textbox wordt verlaten en de WelcomeText korter dan drie karakters is.

Maar door de MVVM ‘belemmering’ (ik kan geen (visuele) XAML onderdelen direct vanuit code aanraken en aanspreken) wordt het lastig om StoryBoards aan te spreken. Een Command werkt ook niet want die vuurt juist in de richting van het viewmodel af (bv. indien de gebruiker een knop indruk of via de MVVMLight EventToCommand).

Ik moet iets anders gebruiken en het antwoord is: DataTriggers.

Het is mogelijk om de XAML naar wijzigingen op het ViewModel te laten luisteren en hierop te laten reageren.

Wat heb ik nodig?

  1. Een bindable (boolean) property op het ViewModel. Hiervoor voeg ik de “ShowTroll” property toe aan het ViewModel.
  2. Op het Viewmodel moet de nieuwe property van waarde kunnen veranderen. Dit gebeurt in de Setter van de WelcomeTitle. Als een waarde met een lengte kleiner dan drie karakters voorbij komt, dan wordt de ShowTroll op True gezet.
  3. Een Button in de Xaml. Deze doet op zich niets maar kan wel de Focus krijgen. Als deze aangeraakt wordt, zal de TextBox de focus verliezen en wordt de Setter van WelcomeTitle uitgevoerd. Een dummy dus.
  4. Een DataTrigger op de Image, gekoppeld aan de ShowTroll boolean
  5. Twee StoryBoards. Als de ShowTroll op true wordt gezet, zal de ene afgespeeld worden en anders de andere.

Dit betekent dus dat het viewmodel met een ShowTroll property wordt  uitgebreid en dat deze verandert in de Setter van de WelcomeTitle property:

public class MainViewModel : ViewModelBase
{
  public MainViewModel()
  {
    WelcomeTitle = "Hello";
  }

  public const string WelcomeTitlePropertyName = "WelcomeTitle";
  private string _welcomeTitle = string.Empty;
  public string WelcomeTitle
  {
    get
    {
      return _welcomeTitle;
    }
    set
    {
      if (_welcomeTitle == value)
      {
        return;
      }

      _welcomeTitle = value;
      ShowTroll = (_welcomeTitle.Length < 3); // Force data change
      RaisePropertyChanged(WelcomeTitlePropertyName);
    }
  }

  public const string ShowTrollPropertyName = "ShowTroll";
  private bool _ShowTroll = false;
  public bool ShowTroll
  {
    get
    {
      return _ShowTroll;
    }
    set
    {
      if (_ShowTroll == value)
      {
        return;
      }

      _ShowTroll = value;
      RaisePropertyChanged(ShowTrollPropertyName);
    }
  }
}

En er wordt dus in de Xaml een Button toegevoegd en de Image wordt iets aangepast. Zie dat de Opacity standaard nul (volledige doorzichtig) is. En er zijn twee StoryBoards om de Opacity binnen twee seconden op één of op nul te brengen.

De DataTrigger is aan de ShowTroll property gekoppeld en reageert zowel op True als op False.

<Grid x:Name="LayoutRoot"
      Width="284">
  <TextBox FontSize="36"
           FontWeight="Bold"
           Foreground="Purple"
           Text="{Binding WelcomeTitle}"
           VerticalAlignment="Center"
           HorizontalAlignment="Center"
           TextWrapping="Wrap"
           Margin="12,12,42,100" />
  <Button Content="Button"
          Height="23"
          HorizontalAlignment="Left"
          Margin="31,226,0,0"
          Name="button1"
          VerticalAlignment="Top"
          Width="75" />
  <Image Height="51"
         Name="image"
         Source="Assets\troll-face.png"
         Opacity="0"
         Stretch="Uniform"
         HorizontalAlignment="Left"
         Margin="178,182,0,0"
         VerticalAlignment="Top"
         Width="61">
    <Image.Resources>
      <Storyboard x:Key="TrollBeginStoryboard">
        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                         From="0"
                         To="1"
                         Duration="0:0:2" />
      </Storyboard>
      <Storyboard x:Key="TrollEndStoryboard">
        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                         From="1"
                         To="0"
                         Duration="0:0:2" />
      </Storyboard>
    </Image.Resources>
    <Image.Style>
      <Style>
        <Style.Triggers>
          <DataTrigger Binding="{Binding ShowTroll}"
                       Value="true">
            <DataTrigger.EnterActions>
              <BeginStoryboard>
                <Storyboard>
                  <StaticResource ResourceKey="TrollBeginStoryboard" />
                </Storyboard>
              </BeginStoryboard>
            </DataTrigger.EnterActions>
            <DataTrigger.ExitActions>
              <BeginStoryboard>
                <Storyboard>
                  <StaticResource ResourceKey="TrollEndStoryboard" />
                </Storyboard>
              </BeginStoryboard>
            </DataTrigger.ExitActions>
          </DataTrigger>
        </Style.Triggers>
      </Style>
    </Image.Style>
  </Image>
</Grid>

Hiervoor gebruiken we zowel de EnterActions als de ExitActions.

Dit geeft dus als effect dat als er een langere tekst is ingevoerd, de ‘troll’ niet zichtbaar is.

Maar als de gebruiker een tekst korter dan drie karakters invult, dan wordt binnen twee seconden de Troll zichtbaar.

En indien de tekst weer langer wordt gemaakt, dan is de Troll weer verstopt.

Ik wil in dit voorbeeld aangeven dat het relatief eenvoudig is om complexe UI veranderingen door te voeren door slecht een property op een ViewModel te manipuleren. Hiermee wordt het opeens mogelijk om binnen StoryBoards meerdere objecten te wijzigen. Dit is oogsnoep voor de gebruiker en zo wordt het een visueel feestje voor zowel de ontwikkelaar (of moet ik zeggen: Interactive Designer) en de gebruiker.

Eenvoudig PDF printen in C#

In need of an English translation? Please drop me a note!

Printen in het .Net framework is op zich niet zo lastig. En met de komst van WPF en de bijbehorende XpsDocumentWriter kan bv. handig gebruik gemaakt worden van de FlowDocument klasse en de PrintQueue klasse met bijbehorende CreateXpsDocumentWriter methode. Hiermee kun je zelf je templates opmaken, mergen en printen.

Maar specifiek een aangeboden PDF printen is een stuk lastiger. Met de hand lijkt dit eenvoudig, je start gewoon Adobe Reader of iets dergelijks.

Maar een PDF vanuit code uitprinten is niet eenvoudig. De opmaak binnen PDF’s wordt niet direct door de meeste printers herkend. Op het internet zwerven veel oplossingen rond allemaal met hun eigen voor- en nadelen. Adobe Reader is bv. gratis maar geeft een ‘nag’ screen en er zijn commerciële oplossingen maar die zijn niet bepaald goedkoop.

Voor mijn huidige project heeft het team gekeken naar de mogelijkheid om GhostScript te gebruiken. Dit is een bibliotheek om PDF naar ‘niet-postscript’ om te zetten. We zijn met name geïnteresseerd in PCL, dit kunnen de meeste (HP) laserprinters wel aan. Op GhostScript zit een GNU licentie met beperkt commercieel gebruik.

En wat blijkt, in slechts enkele stappen komt het printen van PDF ook voor .Net ontwikkelaars binnen bereik!

Stap 1: Download GhostScript en installeer deze lokaal. De 32 bits versie is een prettige keuze, vooral in combinatie met de andere benodigdheden. Deze werkt ook met een 64 bits Windows7. Refereer naar de gsdll32.dll in je C# project.

We zijn dus geïnteresseerd in die gsdll32.dll en deze is geschreven in C . Gelukkig heeft Matthew Ephraim al de moeite genomen om deze in C# te verpakken. Deze bibliotheek heet heel toepasselijk GhostScriptSharp. De broncode kan je ophalen bv. op GitHub.

Deze wrapper rond de 32 bits GhostScript dll is heel aardig. Er is via de methode GenerateOutput() de mogelijkheid beschikbaar om een PDF om te zetten naar PCL. Als onderdeel van de instellingen moet wel een ‘Device’ meegegeven worden. Hier heb ik een simpele aanpassing moeten maken op GhostScriptSharp. De GhostscriptDevices enum krijgt nu de extra ljet4 voor PCL support want die ontbrak. Waarom deze in de huidige versie niet aanwezig is, is mij onbekend.

Stap 2: download nu de GhostScriptsharp code, integreer deze in je eigen code en breid de enum even uit met de keuze ‘ljet4’. Zie dat die broncode standaard naar de 32 bits versie van GhostScript refereert.

Als laatste moet nu de, door GhostScript uit de PDF, gegenereerde PCL naar de printer gestuurd worden. Jouw printer dient dus wel PCL te ondersteunen :-).

Hiervoor gebruiken we een voorbeeld op de MSDN van Microsoft zelf. Dit voorbeeld gebruikt de Win32 Spooler Application Programmer Interfaces (APIs) en dit werkt ook prima samen met Windows7. De in het voorbeeld genoemde RawPrinterHelper klasse is prima. Er zitten wel enkele geheugenlekjes in (geen usings op de FileStream en de BinaryReader) dus een kleine aanpassing is gewenst.

Stap 3: Integreer de voorbeeldcode van Microsoft in je project en repareer even de RawPrinterHelper.SendFileToPrinter() methode.

Nu wordt het mogelijk om alles bij elkaar te brengen in één aanroep:

string pdfFileName = "test.pdf";
string tempFileName =
    Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
GhostscriptSettings gss = new GhostscriptSettings
{
  Size = new GhostscriptPageSize() { Native = GhostscriptPageSizes.a4 },
  Device = GhostscriptDevices.ljet4,
  Resolution = new Size(600, 600)
};

GhostscriptSharp.GhostscriptWrapper.GenerateOutput
    (pdfFileName, tempFileName, gss);
RawPrinterHelper.SendFileToPrinter(printerName, tempFileName);
File.Delete(tempFileName);

Eerst stellen we een naam samen voor een tijdelijk bestand. Vervolgens laten we via GhostScript vanuit een aangeboden PDF dit tijdelijk bestand vullen met PCL code (op A4 formaat met een resolutie van 600 dpi). En vervolgens laten we dit PCL bestand ruw naar de printer sturen. En achteraf verwijderen we natuurlijk nog even het tijdelijk bestand.

Dit moet voldoende zijn. Natuurlijk moet deze oplossing uitvoerig getest worden doordat het een heleboel zaken integreert… Maar in onze testopstelling functioneert het heel aardig. Persoonlijk had ik liever niet met een tijdelijk bestand op schijf gewerkt, maar met bijvoorbeeld een memorystream.

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.