Skip navigation
All Places > PI Developers Club > Blog > 2016 > December
2016

Introduction to Xamarin

 

Xamarin is a product that brings .NET/C# to both Android and iOS. Xamarin is pretty amazing in that it's fully .NET while being able to produce true Android and iOS apps at the same time, and apps that are compliant with the distribution requirements of both Google Play and the iOS App Store. On this blog post, we will develop a sample application developed using Xamarin Android that will retrieve the current value of sinusoid PI Point from a PI System on the cloud through PI Web API.

 

The sample application

 

In order to develop an Android application using Xamarin, you can use:

 

  • Xamarin Studio on a Mac
  • Visual Studio 2015 with Xamarin

 

Both options work great. In my case, I will be using Visual Studio.

 

If you prefer, the source code package is available on this GitHub repository.

 

Create a new project, select Visual C# --> Android --> Blank App.

 

 

Add a Portable Class Library for iOS, Android and Windows called PIWebAPIWrapper to your solution. This new project would be compatible with Android, iOS and Windows. Therefore, it is interesting to write all your business logic and models on this library specially if you plan to create an iOS version of your app. In this case, you will be able to integrate the Portable Class Library with your future iOS project.

 

 

Add the Microsoft.Net.Http NuGet package to your Portable Class library.

 

 

Repeat the same procedure for the Newtonsoft.Json NuGet package.

 

It is always a good practice to rebuild all your solution after adding packages to your Xamarin proejcts.

 

Let's start writing code to the Portable Class library first. Rename Class1 to PIWebApiService and add the following code to the file content:

 

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;


namespace PIWebAPIWrapper
{
    public class PIWebApiService
    {
        private string baseUrl = "https://webserver/piwebapi/";
        private string piDataArchiveName = "piservername";
        private string username = "username";
        private string password = "password";


        public async Task<string> DownloadWebData(string url)
        {
            var handler = new HttpClientHandler();
            handler.Credentials = new NetworkCredential(username, password);
            HttpClient httpClient = new HttpClient(handler);
            HttpResponseMessage httpMessage = await httpClient.GetAsync(url);
            if (httpMessage.IsSuccessStatusCode == true)
            {
                using (var stream = await httpMessage.Content.ReadAsStreamAsync())
                {
                    using (var streamReader = new StreamReader(stream))
                    {
                        return await streamReader.ReadToEndAsync();
                    }
                }
            }
            return string.Empty;
        }




        public async Task<double> GetSinusoidValue()
        {
            string url = string.Format(baseUrl + "points?path=\\\\{0}\\sinusoid", piDataArchiveName);
            string response = await DownloadWebData(url);
            JObject data = JsonConvert.DeserializeObject<dynamic>(response);
            string webId = data["WebId"].ToString();
            url = string.Format(baseUrl + "streams/{0}/value", webId);
            response = await DownloadWebData(url);
            JObject dataValue = JsonConvert.DeserializeObject<dynamic>(response);
            double value = Convert.ToDouble(dataValue["Value"].ToString());           
            return value;
        } 
    }
}

 

 

The PIWebApiService is responsable for getting the current value of the sinusoid PI Point by making two HTTP requests:

  1. Get the WebId of the sinusoid PI Point
  2. Get the current value of the sinusoid using the WebId returned on the previous step.

 

Let's move to the Android project. The main activity is the one that is going to appear once the application starts.

 

The layout of the MainActivity has mainly 3 objects:

 

  • TextView which will show the value of sinusoid
  • ProgressBar which will show a loading spinner while the app is making the HTTP requests
  • LinearLayout which defines the location of TextView and ProgressBar within the screen.

 

 

The code of the main.axml layout is below:

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:gravity="center"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/sinusoid_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

 

Finally, the code of the MainActivity is below:

 

using Android.App;
using Android.Widget;
using Android.OS;
using PIWebAPIWrapper;
using System.Threading.Tasks;


namespace XamarinPIWebAPIApp
{
    [Activity(Label = "PI Web API Sample Xamarin App", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        TextView sinusoidEditText = null;
        ProgressBar progressBar = null;
        Task t = null;


        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.Main);
            sinusoidEditText = FindViewById<TextView>(Resource.Id.sinusoid_text);
            progressBar = FindViewById<ProgressBar>(Resource.Id.progressBar);
            PIWebApiService service = new PIWebApiService();
            double value = -1;
            t  = Task.Run(async () =>
            {
                value = await service.GetSinusoidValue();
                this.RunOnUiThread(() =>
                {
                    progressBar.Visibility = Android.Views.ViewStates.Invisible;
                    sinusoidEditText.Text = string.Format("The current sinusoid value is {0}", value);
                });


            });
        }
    }
}

 

 

Here are the steps of what happens on the MainActivity:

 

  1. It renders the Main.axml layout.
  2. It finds the TextView and the ProgressBar and store them on private variables.
  3. It creates a new Task which will call:
    1. Service.GetSinusoidValue() to get the current value of the sinusoid.
    2. Once the value is retrieved, the progressBar needs to disappear and the value should appear on the text of the sinusoidEditText UI object.
    3. These lines of code will manipulate the UI. This is why it needs to be executed inside an Action which would be an input of the RunOnUiThread.

 

Conclusions

 

This blog post is about developing Android application using the native Xamarin Android. I think this is a really interesting option for us since our community is very familiar with C# and Android is one of the most popular mobile platforms in the market.

 

Please don't hesitate to share your comments below!

Introduction

 

One of my most read blog post is about Using PI Web API on HTML with jQuery. Some time later due to the populatity of AngularJS, I have written another blog post about the same topic but using AngularJS instead of jQuery.

 

AngularJS has become one of the most popular JavaScript framework of the market. It is a toolset for building the framework most suited to your application development. It is extensible and works well with other libraries. Every feature can be modified or superseded to suit your unique development workflow and feature needs.

 

But web technology changes really fast. AngularJS was born in 2009 and after 6 years collecting feedback, Google has recently released Angular 2.

 

 

Why Angular 2?

 

There are a lot of articles that explains the benefits of choosing Angular 2 on your new web projects:

 

 

In a nutshell, the advantages of using Angular 2 are:

 

  • Better speed and performance
  • Easier to use when compared to AngularJS
  • Mobile driven
  • Cross platform, as it allows you to develop web apps, native mobile apps and desktop apps.

 

Using Typescript with Angular 2

 

You may be surprised with the fact that Angular 2 from Google was written using TypeScript from Microsoft.

 

TypeScript is a superset of JavaScript which primarily provides optional static typing, classes and interfaces. One of the big benefits is to enable IDEs to provide a richer environment for spotting common errors as you type the code.

To get an idea of what I mean, watch Microsoft's introductory video on the language.

For a large JavaScript project, adopting TypeScript might result in more robust software, while still being deployable where a regular JavaScript application would run.

Although you might use Angular 2 with JavaScript directly (ES5 or ES6), the recommended way to use Angular 2 is with TypeScript.

For more information, please refer to this article.

You can install Typescript using NodeJS/npm:

 

npm install -g typescript

 

 

Getting started with Visual Studio Code

 

After watching some video tutorials about using Angular 2, it seems pretty clear that the recommended IDE for developing a web application with Angular 2 is through Visual Studio Code, which is free, cross platform, fast and lightweight code editor developed by Microsoft for Windows, Linux and Mac. Please refer to this article for more information about the difference between both IDEs.

 

If you don't have Visual Studio Code, download it here.

 

Using Angular CLI

 

Together with Angular 2, the Angular2 CLI was also released to help you create an application that already works out of the box. You can install Angular CLI using npm through the Command Promt:

 

npm install -g angular-cli

 

Once it is installed use the command below to create our Angular 2 project:

 

ng new PI-Web-API-With-Angular2

 

It can take some minutes to install all packages for tooling via npm.

 

Once it has finished, run the web application using the following commands:

 

cd PI-Web-API-With-Angular2
ng serve

 

If you open the browser and navigate to http://localhost:4200, you will see your application showing "app works!".

 

The sample application

 

Let's start developing our sample application! If you want to download the source code package, just visit this GitHub repository.

 

Close the command promt, start Visual Studio code and open the project folder.

 

I will show you the files that need to be added or edited in order to make your app work fine. The purpose of this blog post is not to teach you about Angular 2 but to provide a sample application for you to get started using this technology with PI Web API. If you are interested on the topic, there are many videos, articles and books available to learn Angular 2.

 

Let's start editing the app.module.ts with the following content:

 

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';


import { AppComponent } from './app.component';
import { PIWebAPIService } from './piwebapi.service';




@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [PIWebAPIService],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

Create a new file called piwebapi.service.ts under the /src/app folder, which is a TypeScript file that will create the service for making HTTP request against PI Web API. The content of this file is below:

 

import { Injectable } from '@angular/core';
import { Headers, Http, Response } from '@angular/http';
import { Observable } from "rxjs/Observable";






@Injectable()
export class PIWebAPIService {
    private piWebApiBaseUrl = 'https://marc-web-sql.marc.net/piwebapi';  // URL to web api
    constructor(private http: Http)
     { 


     }


     getHeader() : any {
         var authHeader = new Headers();
         authHeader.append('Authorization', 'Basic ' + btoa('username:password')); 
         return authHeader;
     }


     validPIServerName(piServerName : string) : any { 
         return this.http.get(this.piWebApiBaseUrl + "dataservers?name=" + piServerName,  {headers: this.getHeader() });
    };




     validPIPointName(piServerName : string, piPointName : string) : any {
         return this.http.get(this.piWebApiBaseUrl + "points?path=\\\\" + piServerName + "\\" + piPointName,  {headers: this.getHeader() });
    };




    getSnapshotValue(webId:string) : any {
         return this.http.get(this.piWebApiBaseUrl + 'streams/' + webId + '/value',  {headers: this.getHeader() });
    };


    getRecordedValues(webId : string, startTime : string, endTime : string) {
        return this.http.get(this.piWebApiBaseUrl + 'streams/' + webId + '/recorded?starttime=' + startTime + '&endtime=' + endTime,  {headers: this.getHeader() });
    };


   getInterpolatedValues(webId : string, startTime  :string, endTime : string, interval : string) {
        return this.http.get(this.piWebApiBaseUrl  + 'streams/' + webId + '/interpolated?starttime=' + startTime + '&endtime=' + endTime + "&interval=" + interval,  {headers: this.getHeader() });
    };
}

 

Edit the app.component.ts that has all the JavaScript logic of your app, which will call the PI Web API service in order to make the HTTP calls. It has the following content:

 

import { Component } from '@angular/core';
import { PIWebAPIService } from './piwebapi.service';




export class ComboboxOption {
  value: boolean;
  name: string;
}


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  requestMode = true;
  piServerName: string;
  piPointName: string;
  startTime: string;
  endTime: "*";
  interval: "1h";
  yesOrNoOptions: ComboboxOption[] = [{ "value": true, "name": "Yes" }, { "value": false, "name": "No" }];
  getSnap: ComboboxOption = this.yesOrNoOptions[0];
  getRec: ComboboxOption = this.yesOrNoOptions[0];
  getInt: ComboboxOption = this.yesOrNoOptions[0];
  piServerData: any;
  piServerExistsValue: boolean;
  piServerError: any;
  piPointData: any;
  piPointExistsValue: boolean;
  webId: string;
  snapshotData: any;
  snapshotError: any;
  recordedData: any;
  interpolatedData: any;
  recordedError: any;
  interpolatedError: any;
  piPointError: any;


  constructor(private piWebApiHttpService: PIWebAPIService) { }


  defaultValues(): void {
    this.piServerName = "PISERVERNAME";
    this.piPointName = "SINUSOID";
    this.startTime = "*-1d";
    this.endTime = "*";
    this.interval = "1h";
    this.getSnap = this.yesOrNoOptions[0];
    this.getRec = this.yesOrNoOptions[0];
    this.getInt = this.yesOrNoOptions[0];
  }


  //get data by making http calls
  getData(): void {
    //switch div to display the results
    this.requestMode = false;
    //all HTTP requests are done through the  piWebApiHttpService factory object
    this.piWebApiHttpService.validPIServerName(this.piServerName)
      .subscribe(res => {
        this.piServerData = res.json();
        this.piServerExistsValue = true;
      },
      error => {
        this.piServerError = error.json();
        this.piServerExistsValue = false;
      });




    this.piWebApiHttpService.validPIPointName(this.piServerName, this.piPointName).subscribe(res => {
      this.piPointData = res.json();
      this.piPointExistsValue = true;
      //in case of success, we will get the webId of the PI Point which will be used by other requests
      this.webId = res.json().WebId;
      this.piWebApiHttpService.getSnapshotValue(this.webId).subscribe(res => {
        this.snapshotData = res.json();
      }, error => {
        this.snapshotError = error.json();
      });
      //The following requests use the webId already stored
      this.piWebApiHttpService.getRecordedValues(this.webId, this.startTime, this.endTime).subscribe(res => {
        this.recordedData = res.json();
      }, error => {
        this.recordedError = error.json();
      });


      this.piWebApiHttpService.getInterpolatedValues(this.webId, this.startTime, this.endTime, this.interval).subscribe(res => {
        this.interpolatedData = res.json();
      }, error => {
        this.interpolatedError = error.json();
      });
    }, error => {
      this.piPointError = error.data;
      this.piPointExistsValue = false;
    });
  }
}

 

The file app.component.html has the HTML markup of your app. The content of this file should be modified to:

 

<div [hidden]="requestMode==false">
  <h1>Request PI Data</h1>
  <p>
  Please fill in your details below and click  on &quot;Get PI Data&quot;. Fields marked with an asterisk (*) are required.
  </p>
  <div>
  <div style="width: 30em;">
  <label for="piServerName">PI Server Name *</label>
  <!--The value of the input below is bound to the value of the $scope.piServerName.-->
  <input type="text" name="piServerName" id="piServerName" [(ngModel)]="piServerName" />
  <label for="piPointName">PI Point name *</label>
  <input type="text" name="piPointName" id="piPointName" [(ngModel)]="piPointName" />
  <label for="startTime">Start time *</label>
  <input type="text" name="startTime" id="startTime" [(ngModel)]="startTime" />
  <label for="endTime">End time *</label>
  <input type="text" name="endTime" id="endTime" [(ngModel)]="endTime" />
  <label for="interval">Interval *</label>
  <input type="text" name="interval" id="interval" [(ngModel)]="interval" />


  <label for="getsnap">Get Snapshot?</label>
  <select name="getsnap" id="getsnap" size="1" [(ngModel)]="getSnap">
        <option *ngFor="let option of yesOrNoOptions" [value]="option">{{option.name}}</option>
      </select>
  <label for="getrec">Get Recorded Data?</label>
  <select name="getrec" id="getrec" size="1" [(ngModel)]="getRec">
            <option *ngFor="let option of yesOrNoOptions" [value]="option">{{option.name}}</option>
      </select>


  <label for="getint">Get Interpolated Data?</label>
  <select name="getint" id="getint" size="1" [(ngModel)]="getInt">
            <option *ngFor="let option of yesOrNoOptions" [value]="option">{{option.name}}</option>
      </select>


  <div style="clear: both;">
  <!--This function will make Http request and store the results-->
  <input type="submit" id="submitButton" value="Get PI Data!" (click)="getData()" />
  <!--This function will change the values declared on $scope which are bound to some DOM elements-->
  <input type="button" id="DefaultButton" (click)="defaultValues()" value="Default Values" style="margin-right: 20px;" />
  </div>
  </div>
  </div>
</div>
<div *ngIf="requestMode==false">
  <h1>Displaying SINUSOID data</h1>


  <h2>Connection information</h2>
  <br />
  <table border="0" style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Property</th>
  <th>Value</th>
  </tr>


  <tr>
  <td>PI Server</td>
  <!--Using { {} } also binds the DOM with variables declared under $scope-->
  <td id="PIServerNameValue">{{piServerName}}</td>
  </tr>
  <tr>
  <td>PI Point</td>
  <td id="PIPointNameValue">{{piPointName}}</td>
  </tr>
  <tr>
  <td>Start time</td>
  <td id="StartTimeValue">{{startTime}}</td>
  </tr>
  <tr>
  <td>End time</td>
  <td id="EndTimeValue">{{endTime}}</td>
  </tr>
  <tr>
  <td>Interval</td>
  <td id="IntervalValue">{{interval}}</td>
  </tr>


  <tr>
  <td>Does the PI Server exist?</td>
  <td id="PIServerExistValue">{{piServerExistsValue}}</td>
  </tr>
  <tr>
  <td>Does the PI Point exist?</td>
  <td id="PIPointExistValue">{{piPointExistsValue}}</td>
  </tr>
  </table>


  <div id="errors">
  <div *ngIf="snapshotError && snapshotError.Errors">
  <p *ngFor="let error of snapshotError.Errors">{{error}}</p>
  </div>
  <div *ngIf="recordedError && recordedError.Errors">
  <p *ngFor="let error of recordedError.Errors">{{error}}</p>
  </div>
  <div *ngIf="interpolatedError && interpolatedError.Errors">
  <p *ngFor="let error of interpolatedError.Errors">{{error}}</p>
  </div>
  <div *ngIf="piServerError && piServerError.Errors">
  <p *ngFor="let error of piServerError.Errors">{{error}}</p>
  </div>
  <div *ngIf="piPointError && piPointError.Errors">
  <p *ngFor="let error of piPointError.Errors">{{error}}</p>
  </div>
  </div>
  <div *ngIf="getSnap.value == true &&webId != null">
  <h2 class="snapshot">Snapshot Value of Sinusoid</h2>
  <br />
  <table class="snapshot" border="0" style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Value</th>
  <th>Timestamp</th>
  </tr>
  <tr *ngIf="snapshotData">
  <td>{{snapshotData.Value}}</td>
  <td>{{snapshotData.Timestamp}}</td>
  </tr>
  </table>
  </div>
  <br />
  <br />


  <div *ngIf="getRec.value == true && recordedData && recordedData.Items">
  <h2 class="recorded">Recorded Values of Sinusoid</h2>
  <br />
  <table class="recorded" border="0" style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Value</th>
  <th>Timestamp</th>
  </tr>




  <tr *ngFor="let item of recordedData.Items">
  <td>{{item.Value}}</td>
  <td>{{item.Timestamp}}</td>
  </tr>
  </table>
  </div>
  <br />
  <br />
  <div *ngIf="getInt.value == true && interpolatedData && interpolatedData.Items">
  <h2 class="interpolated">Interpolated Values of Sinusoid</h2>
  <br />
  <table class="interpolated" border="0" style="width: 20em; border: 1px solid #666;">
  <tr>
  <th>Value</th>
  <th>Timestamp</th>
  </tr>
  <tr *ngFor="let item of interpolatedData.Items">
  <td>{{item.Value}}</td>
  <td>{{item.Timestamp}}</td>
  </tr>
  </table>
  <br /><br />
  </div>
</div>

 

Finally, we need to add the styles by editing the app.component.css file:

 

/* Page body */
body {
    font-family: Arial, Helvetica, sans-serif;
}
/* Definition lists */
dl {
    width: 100%;
    margin: 2em 0;
    padding: 0;
    clear: both;
    overflow: auto;
}


dt {
    width: 30%;
    float: left;
    margin: 0;
    padding: 5px 9.9% 5px 0;
    border-top: 1px solid #DDDDB7;
    font-weight: bold;
    overflow: auto;
    clear: left;
}


dd {
    width: 60%;
    float: left;
    margin: 0;
    padding: 6px 0 5px 0;
    border-top: 1px solid #DDDDB7;
    overflow: auto;
}
/* Headings */
h1 {
    font-weight: bold;
    margin: 35px 0 14px;
    color: #666;
    font-size: 1.5em;
}


h2 {
    font-weight: bold;
    margin: 30px 0 12px;
    color: #666;
    font-size: 1.3em;
}


h3 {
    font-weight: normal;
    margin: 30px 0 12px;
    color: #666;
    font-size: 1.2em;
}


h4 {
    font-weight: bold;
    margin: 25px 0 12px;
    color: #666;
    font-size: 1.0em;
}


h5 {
    font-weight: bold;
    margin: 25px 0 12px;
    color: #666;
    font-size: 0.9em;
}
/* Forms */
label {
    display: block;
    float: left;
    clear: both;
    text-align: right;
    margin: 0.6em 5px 0 0;
    width: 40%;
}


input, select, textarea {
    float: right;
    margin: 1em 0 0 0;
    width: 57%;
}


input {
    border: 1px solid #666;
}


    input[type=radio], input[type=checkbox], input[type=submit],
    input[type=reset], input[type=button], input[type=image] {
        width: auto;
    }

 

 

 

    Figure 1 - Sample application using Angular 2

 

Conclusions

 

Choosing the technology for your application is a really important step. You need to consider many factors including which technology your developer team is experienced and for how many years do you expect your app will become deprecated. Angular 2 is becoming really popular and it was developed to replace AngularJS. You can refer to this blog post in order to do the migration. If you are starting a new project, you are probably going to find a good reason to use Angular 2.

 

Please do not hesitate to share your comments below!

 

Finally, I would like to thank you for reading it!

Filter Blog

By date: By tag: