A TileListView for Universal Windows

I just released a small library for arrange any visual item in a tiled-fashion, very similar to the famous “start screen” of Windows 8/10, but following a fixed-position configuration.

The source code on GitHub.

The problem with the ordinary tiled-layout views is about the possible re-arrangement of the tiles, thus an user might see a different result upon the actual viewport size. I needed something “fixed” instead. The user should define the layout by editing the grid via drag-and-drop, then that layout will be fixed for any screen. However, the editor should consider more than a single arrangement, whereas the feasible displays can’t fit the desired layout.

The demo leverages an useful MVVM pattern, and allows to define several kind of blocks: fixed, full-sizable, shrinkable, expandable, on just one or both the directions.

The library is working, but at the moment as a beta (several minor problems and refinements to work on). A reasonable prototype to put your hands on, though.

As described, the project is tailored for the Universal Windows Platform, so only Windows 10 (phones and IoT as well) is supported. You must use Visual Studio 2015 on Windows 10 for development also.

2016-03-05 (1)
Editing mode (drag-and-drop to add/move/remove tiles)
2016-03-05 (2)
Blocks size is modifiable at any time
2016-03-05 (3)
The view mode: as any normal hub page.

Have fun!

 

HD44780 LCD Module driver for Windows 10 IoT

This is my first post about Windows 10 IoT and small computers (embedded) after some experiences in the past with the .Net MicroFramework (Netduino, in essence).

I must say that this Windows 10 is finally a small masterpiece, or “everything you ever asked and none never responded you”. The programming way is easy but very flexible, although still many bricks are missing (in development).

The source code on GitHub

Here is a very simple experiment, a bit vintage, of driving a common alphanumeric LCD module (HD44780-based) with a Raspberry PI 2 and Windows 10 IoT.

WP_001158

The project isn’t anything new, but rather kinda “refresh” of another of mine where the Netduino board was used (see below). Someone of you may wonder “why” still using a so-old LCD module when several graphics are available on the market. Well, my first answer is: “why not?”. As said, this project hasn’t any specific purpose (although many of you may dig into their “miracles box” and pull out an “almost useless” LCD module). The aim is to test how hard is to drive something well known.

 

Some credits…

I can’t miss to mention Laurent Ellerbach, who gave me some inspiration (and motivation) on pursuing those hacking/funny activities.

 

The hardware.

All you need is very easy to find:

  • Raspberry PI2 (with Windows 10 IoT installed)
  • any suitable HD44780 LCD display module (mine is a 4×20)
  • 74HC595 shift-register
  • 220 Ohms resistor (only if you need the backlight)
  • 10k Ohms trimpot (22k or 47k are fine the same)

For sake of simplicity, I won’t detail how to set up the Raspberry, but there are many articles which describe very well that. I followed the Microsoft site and everything went fine, except for the suggested SD minimum size: I found that a 8GB won’t work. Simply consider a 16GB.

RPI2-HD44780_schem

RPI2-HD44780_bb

 

The software.

I wanted to publish the project keeping the sources simpler as possible. A similar application won’t have sense in a complex hardware (full-featured TFT displays and HDMI monitors perform way better than this module). The general guideline is: if you find convenient to connect a LCD module to a RPI, then make it working in minutes.

Since the LCD module’s capabilities are very limited, I embraced the idea to expose the APIs as it were a kind of “Console”. Just a “write” and something more, where a background task should manage the physical transfer by itself.

The project contains two different demo:

  1. a basic one, where some strings’ content is reflected on the display;
  2. a slightly more complex demo, which gets a bunch of RSS news from the BBC.uk channel, and rotates the titles on the screen.

 

Basic demo.

    class BasicDemo
    {

        public async Task RunAsync()
        {
            //write a static string
            DriverHD44780.Instance.DrawString(
                "This is a basic demo",
                new Point(0, 0)
                );

            int n = 0;
            while (true)
            {
                //display a simple counter
                DriverHD44780.Instance.DrawString(
                    $"Counting...{n}",
                    new Point(0, 1)
                    );

                //display current time and date
                var now = DateTime.Now;
                DriverHD44780.Instance.DrawString(
                    now.ToString("T") + "   ",
                    new Point(0, 2)
                    );

                DriverHD44780.Instance.DrawString(
                    now.ToString("M") + "   ",
                    new Point(0, 3)
                    );

                n++;
                await Task.Delay(1000);
            }
        }

    }

WP_001159

 

RSS demo.

    class RssDemo
    {

        public async Task RunAsync()
        {
            //write a static string
            DriverHD44780.Instance.DrawString(
                "Getting RSS...",
                new Point(0, 0)
                );

            //get the latest news using a normal HTTP GET request
            var http = new HttpClient();
            var endpoint = new Uri("http://feeds.bbci.co.uk/news/rss.xml");

            var srss = await http.GetStringAsync(endpoint);
            var xrss = XDocument.Parse(srss);

            //extract the news items, and sort them by date-time descending
            var xnews = xrss.Root
                .Element("channel")
                .Elements("item")
                .OrderByDescending(_ => (DateTime)_.Element("pubDate"))
                .ToList();

            int n = 0;
            while (true)
            {
                /**
                * Loop the news as one per page
                **/

                //the first row is for the publication date-time
                var dt = (DateTime)xnews[n].Element("pubDate");
                DriverHD44780.Instance.DrawString(
                    dt.ToString("g"),
                    new Point(0, 0)
                    );

                //the three other rows are for the title
                var title = (string)xnews[n].Element("title");
                title = title + new string(' ', 60);

                for (int row = 0; row < 3; row++)
                {
                    DriverHD44780.Instance.DrawString(
                        title.Substring(row * 20, 20),
                        new Point(0, row + 1)
                        );
                }

                //wait some seconds before flipping page
                n = (n + 1) % xnews.Count;
                await Task.Delay(3000);
            }
        }

    }

WP_001165

 

Performance.

You may wonder how well performs the driver. Well, there are two stages involved in the displaying process:

  1. the calls from the main application to the driver;
  2. the physical data transfer.

Any invokation by the main app involves always the cache: no matter how many calls are made, because everything is hosted in memory. For this reason, any manipulation is irrelevant in terms of performance impact. However, a fixed rate (typically 200ms) there’s a cache dump to the LCD screen, that is: the physical data transfer though the SPI.

How long takes the entire screen dump via SPI?

The circuit is very simple, thus there’s no way to take the transfer faster than the machine execution speed. Even adjusting the SPI clock rate, the resulting duration won’t change notably. Please, bear in mind that a too high SPI clock rate could face signal degradation due the wire length. I used a perfect 1 MHz value, and you can see from the below screenshot that the total transfer duration is less than 30ms.

UNIT0000

If you are interested in a faster way to dump the data via SPI, I suggest to read the following section which requires a decent knowledge about electronics.

 

The old “LCD Boost” library.

The original project was tailored for the .Net MicroFramework (Netduino) and many things were optimized for speed reasons. Moreover, the NetMF had some leaky problems mostly due to the super squeezed CLR, thus many solutions were solved as it were a low-level device.

Here are some links to the original articles of mine:

Very fast SPI-to-parallel interface for Netduino

LcdBoost library for Netduino

The GDI library for Netduino targets a LCD module.

A playable Invaders-like game with a Netduino Plus 2.

 

No, I don’t like JavaScript. Definitely.

Here is just another misleading faces of the JavaScript (or, better, EcmaScript).
Ten years ago or so, I probably had a better consideration of this language, but I really cannot avoid to compare it with C#. The strong yet clear rules of C# rarely lead to side-effects like this one.

Function: gimme your reference!

As in C#, a function can be referred using a delegate, and its “pointer” is uniquely identified in the program. The same thing is in C/C++: you can refer to a certain function by a simple pointer.
In JavaScript isn’t so simple, because the functions are instantiated on every scope creation. This leads to problems when you wish to subscribe and unsubscribe callbacks, events, and similar.
Honestly, I was unaware about this problem, and I bumped against it using the Socket.io library. Simply I wanted to unsubscribe the events (callbacks) from a socket, but the handler functions were nested inside another function. The result is plain simple: the unsubscription failed.

A neat example…

Consider this minimal pattern for subscribe/unsubscribe a single callback:

var E = (function () {
    var a = [];

    return {
        add: function (fn) {
            a.push(fn);
        },
        remove: function (fn) {
            for (var i = 0; i < a.length; i++) {
                if (a[i] === fn) {
                    a.splice(i, 1);
                    break;
                }
            }
        },
        len: function () {
            return a.length;
        }
    }
})();

Now let’s consider this trivial snippet where we’ll implement a basic test for a callback:

function xyz() {

    return {
        add: function () {
            E.add(f);
        },
        remove: function () {
            E.remove(f);
        }
    }
}

var b = new xyz();
b.add();
b.remove();
alert(E.len());     //yields 0...Correct!

The result is zero, as expected.

In order to understand where the problem is, just add a nested function which will leverage the same un/sub pattern:

function xyz() {

    function f(enable) {
        function handler() {
            //
        }

        if (enable) {
            E.add(handler);
        } else {
            E.remove(handler);
        }
    }

    return {
        f: f,
        add: function () {
            E.add(f);
        },
        remove: function () {
            E.remove(f);
        }
    }
}

Once run the proper test, the result is NOT the expected one:

var b = new xyz();
b.f(true);
b.f(false);
alert(E.len());    //yields 1...WRONG!

As said, the reason is that the “handler” function is instantiated (as a new “object”) every time the containing function “f” is called. Thus, the first “handler” reference won’t be the same as the second call.

Conclusions.

You will say: “that’s fine: it’s all about the language!”. Then I’ll answer: “Okay: may I say that I don’t like that?”.

AngularJS experimental page routing/templating

This is my very first post about “pure-web” tech, and it’s also very short. I began to deal with those things some months ago, but I feel there’s a long road to walk.
Here is an attempt to re-think the Single-Page (web) Application (a.k.a. SPA) using Angular-JS, toward a more abstracted templating way. The reasons behind a similar solution are pretty hard to understand only reading this post, but shortly I’ll post a much larger yet concrete framework for telemetry applications.
As a hint, think the ability to compose a page from a series of components, and store/retrieve the layout on any persistent medium (e.g. file, database, etc)

From what I meant, AngularJS is among the closest web-frameworks to the desktop’s WPF, which is (at least in my mind) the best framework for LOB apps.
However, I noticed that the ability to reuse components, abstract views and so away, is still somewhat not yet standardized, nor used. That’s because I thrown myself in this challenge, and the result isn’t bad as expected (for a web-dev noob like me).
A short video should explain way better than thousand words how the result is:

Follow the project on the Github repository:
https://github.com/highfield/ng-route1
Stay tuned for hotter articles in the near future!

Azure Veneziano – LinkIt ONE

This post follows my first one on Internet-of-Things telemetry project based on Azure.
At that time I used a Netduino Plus 2 as remote sensor device: this time I use the awesome Seeedstudio LinkIt ONE board.
I strongly suggest to read the old one first, because most of the below content relies on concepts already discussed in the past.

Here are the previous parts:

The source of the project is hosted in the azure-veneziano GitHub repository.

WP_000868

The LinkIt ONE board impressions.

My viewpoint is the one from who does NOT like C/C++, does NOT have (decent) experience in such a languages, rather loves RAD environment. Even Netduino, which is based on the .Net Micro Framework, relies on a enjoyable IDE (Visual Studio). The .Net libraries together with the awesome debugger really makes the Netduino board the best ever.

Every time I must face a C/C++ program, I feel frustrated.
For this project, I had to “translate” the original Netduino C# sources to a Arduino/C/C++ “working” sketch. I say “working”, because my goal was just make the board working, not writing a decent program.
Why “frustrated”? Because I think it’s a nonsense wasting time against a hyper-verbose C/C++ project, than simply writing high-level languages like C# or Java. It’s a super-board, even faster and larger in terms of resources than a Netduino Plus 2. It’s not just a 8-bit AVR.

So, why the LinkIt ONE board should be “awesome”?
First of all, because the hardware. The board is a little masterpiece for hobbists, because is really full-featured, but also comes with a very reasonable price.
Secondly, I’d say the Arduino APIs approach, which simplifies the things for dumb+lazy programmers like me.
Honestly speaking, the APIs cover many of the board features, but I’d expect a better coverage. My impression is rather a thin layer which is wrapping the real OS running into the board. That’s pity, because the users may feel some limitation very early.

An example is the HTTP support, which is nonexistent.
A clearly claimed board “for prototyping Wearables and IoT devices” should expose at least the most common services, that a typical IoT project use.
I personally found annoying digging in the Internet for open source projects about very basic services like JSON manipulation, HTTP client and so other. Thankfully the Arduino-world is still huge, and there are plenty of users contributing with their own works.

To close this argument, most of the time I’ve spent on the porting was…about ME, and my inexperience on writing C/C++, searching for libraries on the Internet, and make them working in a decent way.
A normal C/C++ developer shouldn’t have any problem on writing apps for this board.

The software.

As said, except for the JSON and the HTTP libraries, almost everything was “cloned” from the original C# sources. That’s because many of you, maybe C/C++ skilled, will smile (laugh) by seeing how I’ve written the ported source.

The JSON library comes from Benoît Blanchon, who made a very cool job.
The library is well written, in my opinion, but you should bear in mind that the Benoît target was the “real” Arduino, as very poor on resources (and language features).
That is, the LinkIt board is a 32-bit ARM core, thus the memory usage is way higher than what described in the library Wiki.
Moreover, the Arduino default platform comes without “new” and “delete”, and the JSON library adds its own “new” operator overloading. However, that will fight with the LinkIt SDK, which is a C/C++ featured platform, and such operators are fully supported.
By the way, this board comes with 4MB of RAM. Far from being a problem allocating some KB of cache!

For the HTTP client, I chose this library.
It’s a minimalistic HTTP composer and parser, but way enough for what any hobby project requires. It comes also with the BASIC authentication.

There are some other difference from the original Netduino project.
First off, the TCP connection is made via Wi-Fi, because the LinkIt ONE does not comes with any Ethernet plug. However, this is a very straightful task, because the APIs/samples are very clear on how to set-up a wireless connection.
Another difference is on the GPS sensor monitoring, which is absent in the Netduino, but included in the standard LinkIt ONE package. Despite I used it mostly for fun, I believe it would be useful whereas you need to track the device position if it’s moveable.
Again, very easy to set-up.

The sketch initialization isn’t much different than the old Netduino’s one:

/**
* Hardware input ports definition
**/

InputPortWrapper* _switch0;
InputPortWrapper* _switch1;

AnalogInputWrapper* _analog0;
AnalogInputWrapper* _analog1;

IInput* _inputPorts[16];
int _portCount;

#define LED 13

#define WIFINAME &quot;(your wi-fi name)&quot;
#define WIFIPWD &quot;(your wi-fi password)&quot;

int _wifiStatus;
MobileServiceClient* _ms;

gpsSentenceInfoStruct _gpsinfo;


void setup()
{
	/**
	* Hardware input ports definition
	**/
	_switch0 = new InputPortWrapper(
		&quot;Switch0&quot;,
		0
		);

	_switch1 = new InputPortWrapper(
		&quot;Switch1&quot;,
		1
		);


	_analog0 = new AnalogInputWrapper(
		&quot;Analog0&quot;,
		0,
		100.0,
		0.0
		);

	_analog1 = new AnalogInputWrapper(
		&quot;Analog1&quot;,
		1,
		100.0,
		0.0
		);

	//collect all the input ports as an array
	_inputPorts[0] = _switch0;
	_inputPorts[1] = _switch1;
	_inputPorts[2] = new RampGenerator(&quot;Ramp20min&quot;, 1200, 100, 0);
	_inputPorts[3] = new RampGenerator(&quot;Ramp30min&quot;, 1800, 150, 50);
	_inputPorts[4] = _analog0;
	_inputPorts[5] = _analog1;

	_portCount = 6;

	//just the led port used as a visual heartbeat
	pinMode(LED, OUTPUT);

	//turn-on the GPS sensor
	LGPS.powerOn();

	//setup the WI-FI connection
	LWiFi.begin();

	_wifiStatus = LWiFi.connectWPA(WIFINAME, WIFIPWD);

	//istantiate a new Azure-mobile service client
	_ms = new MobileServiceClient(
		&quot;(your service name).azure-mobile.net&quot;,
		&quot;(your application-id)&quot;,
		&quot;(your master key)&quot;
		);
}

Then there’s the “loop” section, which is also pretty easy:

void loop()
{
	if (_wifiStatus &gt;= 0)
	{
		bool hasChanged = false;

		//perform the logic sampling for every port of the array
		for (int i = 0; i &lt; _portCount; i++)
		{
			if (_inputPorts[i]-&gt;sample())
			{
				hasChanged = true;
			}
		}

		if (hasChanged)
		{
			//something has changed, so wrap up the data transaction
			StaticJsonBuffer&lt;4096&gt; jsonBuffer;

			//read the GPS info
			LGPS.getData(&amp;_gpsinfo);

			JsonObject&amp; jobj = jsonBuffer.createObject();
			jobj[&quot;devId&quot;] = &quot;01234567&quot;;
			jobj[&quot;ver&quot;] = 987654321;
			jobj[&quot;pos&quot;] = (char*)_gpsinfo.GPGGA;

			JsonArray&amp; jdata = jobj.createNestedArray(&quot;data&quot;);

			//append only the port data which have been changed
			for (int i = 0; i &lt; _portCount; i++)
			{
				IInput* port;
				if ((port = _inputPorts[i])-&gt;getHasChanged())
				{
					port-&gt;serialize(&amp;jdata);
				}
			}

			//execute the query against the server
			_ms-&gt;apiOperation(
				&quot;myapi&quot;,
				REST_Create,
				&amp;jobj
				);
		}

		//invert the led status
		digitalWrite(
			LED,
			digitalRead(LED) == 0
			);

		//take a rest...
		delay(1000);
	}
}

Once the program is deployed, the data incoming the Azure APIs show something like the following:

log-entry

Conclusions.

Although I don’t like the rough C/C++ programming style, the LinkIt ONE board is really awesome. I’d only encourage the Seeedstudio team to:

  • Cover the rest of the board hardware with more APIs (e.g. sound, phone calls managements, etc)
  • Offer an higher-level language choice, such as Java/C#/JavaScript: Node.js running in the LinkIt would be the top!
  • Embed the most used (basic) services for IoT, such HTTP client/server, JSON, etc.

Have fun!

Azure Veneziano – Part 3

This is the third part of my Internet-of-Things telemetry project based on Azure.

The source of the project is hosted in the azure-veneziano GitHub repository.

The sample report as shown in this article can be downloaded here.

Here are the other parts:

In this article I’ll show you how to refine the notification of an event by creating a detailed data report.
This post closes the basic part of the project. There will be other articles related to the Azure Veneziano project, but they are mostly additional components and enrichment to the base system.

The problem.

When the system alerts you about, for instance, the outside temperature which is getting higher the more is sunny, I believe there’s no need of any detail on.
However, the things could turn dramatically different if you receive a notification such as “your aquarium temperature is greater than 35°C”. Unless you have very special fishes, there are just two possibilities:

  1. the fishes are in a serious danger, or
  2. something is broken (e.g. the probe, the wiring, etc).

Fish_tank_(2)In the first case, there’s no other way than making some immediate action before the fishes die. In the second case, you could even tolerate the failure knowing that the system is unable to work properly, until it will be fixed.
However, if you’re at the mall, for instance, and you receive such a mail: what would you do? Better question: how to know what kind of problem is? Also, how the system evolved before facing the issue?
Of course you can add “redundancy” to the telemetry system, so that you’ll have more info (and that’s always a good thing). For instance, you could use two probes instead of just one. Since the fishes life is in danger, a probe more is actually a natural choice.
Anyway, if you receive a simple message like “the water temperature is 55°C”, you can’t understand where the problem is. A bit different if the message shows you the “evolution” of that temperature. If the evolution acts like a “step”, where the temperature rises to a prohibitive value in a few time, then it’s probably something broken in the hardware. Reasonably, the aquarium tank can’t get hotter in minutes or less.
All that depicts a scenery where a collection of values over time is an useful “attachment” to the alerting message. Here, the target is representing the data collected as both chart and table fashion.

Looking for the right library.

As for report I mean a simple document, which contains details on what happened. For this project, we’ll create a three-pages report with a couple of charts, and also a brief tabular history of the collected data.
Once again, I’d like to remember that this project is a kind of “sandbox” for something professional. Thus, I prefer to try “a bit of everything” in order to take practice with the environment: Azure at first, then several accessories. For this reason, I wanted the ability to create the report document in both Word- and PDF-format.
Around the Internet there is plenty of creation and conversion tools, but most of them are very expensive. In a professional context that would be feasible, but of course isn’t acceptable for any home/hobby target.

Finally, I bumped against the Spire.Doc Free-edition by e-iceblue.
They offer a complete suite of tools for many standard formats. Despite their regular price is off the hobbyist-pocket, they also offer the Free-Edition option. I tested only the Spire.Doc component (tailored for the Word documents), and the limitations are pretty acceptable. At first glance some limitation looks like a wall, but it’s easy to play around the APIs and to find the right trick!
Moreover, when I had some trouble with the library, I asked them an help by the forum, and the answer came very quickly.

How to create your own report.

The usage of the Spire.Doc library is very simple, however I created a series of support functions in order to specialize the code for the report creation.
It’s worthwhile to say that the generated report is meant as “attachment” for the notification mail, so the below code is called automatically when the logic sends a mail.
The only thing the logic should specify is the list of useful variables to detail in the report. That’s an obvious requirement, especially when you deal with many variables.

        public void Run()
        {
            LogicVar analog0 = MachineStatus.Instance.Variables["Analog0"];
            LogicVar analog1 = MachineStatus.Instance.Variables["Analog1"];

            if ((analog0.IsChanged || analog1.IsChanged) &&
                (double)analog0.Value > (double)analog1.Value
                )
            {
                var message = "The value of Analog0 is greater than Analog1.";

                var mail = new MailMessage();
                mail.To.Add("vernarim@outlook.com");
                mail.Body = message;

                var rdp = new ReportDataParameters();
                rdp.OverviewText.Add(message);

                rdp.PlotIds.Add("Analog0");
                rdp.PlotIds.Add("Analog1");
                rdp.PlotIds.Add("Switch0");
                rdp.PlotIds.Add("Switch1");
                rdp.PlotIds.Add("Ramp20min");
                rdp.PlotIds.Add("Ramp30min");

                MachineStatus.Instance.SendMail(
                    mail,
                    rdp
                    );
            }
        }

Here is the mail incoming in my mailbox…

mailbox

…and here once I open the message:

mail

Let’s walk the report generation code step-by-step…

The very first thing is to specify some personalization data, such as the title, some pictures, and even the page size (default is for European A4-sheet).

        private void CreateReport(
            MailMessage mail,
            ReportDataParameters rdp
            )
        {
            //set the basic info for the report generation
            var info = new ReportGeneratorInfo();
            info.ProjectTitle = "Azure Veneziano Project";
            info.ProjectUri = "https://highfieldtales.wordpress.com/";
            info.ProjectVersion = "2014";

            info.ReportTitle = "Alert Data Report";

            //cover logo image
            var stream = this.GetType()
                .Assembly
                .GetManifestResourceStream("AzureVeneziano.WebJob.Images.WP_000687_320x240.jpg");

            info.CoverLogoImage = new System.Drawing.Bitmap(stream);

Then, since the cover is made up from a well-defined template, its creation is straightful immediately after the document model. I also used an extension-method pattern so that the various function invocation will shape as fluent-fashion.

            /**
             * Cover
             **/
            var document = ReportGeneratorHelpers.CreateDocument(info)
                .AddStandardCover(info);

What’s behind?
There’s nothing secret. Those functions are only a shortcut for easy manipulating a report, but anyone could create his/her own functions.
The document generation creates a Document instance, defines its properties as well as the available styles. Please, note that the library comes with several pre-defined styles, but I wanted to create my own:

        /// <summary>
        /// Create a new Word document based on the given parameters
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        public static Spire.Doc.Document CreateDocument(
            ReportGeneratorInfo info
            )
        {
            //create new document
            var document = new Spire.Doc.Document();

            /**
             * Define styles
             **/
            {
                var style = new Spire.Doc.Documents.ParagraphStyle(document);
                style.Name = "Title";
                style.CharacterFormat.FontName = "Calibri Light";
                style.CharacterFormat.FontSize = 28;
                style.CharacterFormat.TextColor = System.Drawing.Color.FromArgb(91, 155, 213);
                style.ParagraphFormat.BeforeSpacing = InchesToDots(0.2);
                style.ParagraphFormat.AfterSpacing = InchesToDots(0.2);
                document.Styles.Add(style);
            }

            // ...

            //create section...
            Spire.Doc.Section section = document.AddSection();
            section.PageSetup.DifferentFirstPageHeaderFooter = true;
            section.PageSetup.DifferentOddAndEvenPagesHeaderFooter = true;

            //...define page size and orientation
            section.PageSetup.PageSize = info.PageSize;
            section.PageSetup.Orientation = info.PageOrientation;

            //...then margins...
            section.PageSetup.Margins.Top = InchesToDots(info.PageMargin.Top);
            section.PageSetup.Margins.Bottom = InchesToDots(info.PageMargin.Bottom);
            section.PageSetup.Margins.Left = InchesToDots(info.PageMargin.Left);
            section.PageSetup.Margins.Right = InchesToDots(info.PageMargin.Right);

            /**
             * Page header
             **/

            // ...

            /**
             * Page footer
             **/

            // ...

            return document;
        }

So far, so well.
If you wonder what’s the result at this point, here is a snapshot:

report-1

Please, since I was running out of logo pictures of my “Home Company”, I turned for a picture of my boss, far serious than many CEOs all around the world.
Hope you love her!

Let’s turn page: here the work begins to get harder.
The second page should give a brief overview of what happened at the very beginning. At first glance, the reader should mean WHY the mail has been sent. That’s still pretty easy to do, because it’s just a bunch of “Paragraph” to insert into the current page.

            /**
             * Page 2
             **/
            
            //overview (brief description)
            document.AddHeading1("Overview")
                .AddNormal(rdp.OverviewText)
                .AddBreak(Spire.Doc.Documents.BreakType.LineBreak)
                .AddBreak(Spire.Doc.Documents.BreakType.LineBreak);

Since some lines of text shouldn’t steal much space on the page, I want to place a couple of charts about the most recent evolution of the selected variables.
Later we’ll cover how the chart generation works.

            //charts
            var chart1 = this.CreateChart(
                rdp, 
                rdp.DateTimeBegin, 
                rdp.DateTimeEnd
                );

            var chart2 = this.CreateChart(
                rdp,
                rdp.DateTimeEnd - TimeSpan.FromMinutes(15),
                rdp.DateTimeEnd
                );

            document.AddHeading1("Charts")
                .AddFrameworkElement(chart1)
                .AddBreak(Spire.Doc.Documents.BreakType.LineBreak)
                .AddFrameworkElement(chart2)
                .AddBreak(Spire.Doc.Documents.BreakType.PageBreak);

Here is how the second page looks:

report-2

The third (and likely last) page is for the tabular view of the most recent data.
Why showing the same data twice?
Because charts and tables aren’t the same thing: each one has its own pros and cons.

            /**
             * Page 3
             **/

            //data table
            var table = new ReportDataTable();

            {
                //add the date/time column
                var column = new ReportDataTableColumn(
                    "Timestamp",
                    "Date/time",
                    new GridLength(1, GridUnitType.Star)
                    );

                table.Columns.Add(column);
            }

            //add the remaining columns
            foreach(string id in rdp.PlotIds)
            {
                var plot = MyPlotResources.Instance.GetPlot(id);
                var column = new ReportDataTableColumn(
                    plot.InstanceId,
                    plot.Description,
                    new GridLength(0.75, GridUnitType.Pixel)    //inches
                    );

                table.Columns.Add(column);

                if (table.Columns.Count >= 7)
                    break;
            }

            //query the DB for the most recent data collected
            using (var sqlConnection1 = new SqlConnection(SQLConnectionString))
            {
                sqlConnection1.Open();

                var sqlText =
                    "SELECT * FROM highfieldtales.thistory " +
                    "WHERE __createdAt >= @begin AND __createdAt <= @end " +
                    "ORDER BY __createdAt DESC";

                var cmd = new SqlCommand(
                    sqlText,
                    sqlConnection1
                    );

                cmd.Parameters.Add(
                    new SqlParameter("@begin", rdp.DateTimeBegin)
                    );

                cmd.Parameters.Add(
                    new SqlParameter("@end", rdp.DateTimeEnd)
                    );

                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var row = new ReportDataTableRow();
                        row["Timestamp"] = reader["__createdAt"].ToString();

                        var colName = (string)reader["name"];
                        row[colName] = string.Format(
                            "{0:F1}",
                            (double)reader["value"]
                            );

                        table.Rows.Add(row);

                        if (table.Rows.Count >= 50)
                            break;
                    }
                }
            }

            document.AddHeading1("Table")
                .AddTable(table);

It’s worthwhile noting that I didn’t use the Table APIs as the library exposes. Again, I wanted to play around the library to face its flexibility.
At the end, I used the “tabulation-technique” and I must admit that the Spire.Doc library is very easy yet very powerful to use.

report-3

The very last thing to do is obviously to save the composed document. As described earlier, there’s no a permanent place where the document is stored, rather it is streamed directly as attachment to the mail.
Here below there is the snippet for both the Word- and the PDF-formats, so that the mail will carry two identical reports, then the user can open with the favorite reader.

            {
                //save the document as Word
                var ms = new MemoryStream();
                document.SaveToStream(
                    ms,
                    Spire.Doc.FileFormat.Docx
                    );

                ms.Position = 0;
                var attachment = new Attachment(
                    ms, 
                    "Report.docx", 
                    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
                    );

                mail.Attachments.Add(attachment);
            }
            {
                //save the document as PDF
                var ms = new MemoryStream();
                document.SaveToStream(
                    ms,
                    Spire.Doc.FileFormat.PDF
                    );

                ms.Position = 0;
                var attachment = new Attachment(
                    ms, 
                    "Report.pdf", 
                    "application/pdf"
                    );

                mail.Attachments.Add(attachment);
            }

That’s it!

How does it appear on my phone?

I believe it’s funny trying to read a data report on a phone. I mean that even on a relatively small screen you can read the same things as you were on a PC. Well, it’s not as easy as it looks, but I really love it as a start point!

Nice enough!…

Some words about the PDF document creation.

As you know, the Azure Veneziano project has been built against a totally free environment. By the way, the free websites that Azure offers where this WebJob runs, come with some limitation. In this case, the Spire.Doc library require GDI/GDI+ for the PDF generation, and that’s unavailable/unsupported by the Azure free context.
I fully tested the PDF generation in a desktop application, and the result was always perfect. However, if you need the Azure-side PDF generation, I believe there are at least two choices:

  • move the WebJob to a Cloud Service/VM (or any paid context) as suggested here;
  • leverage some online services such as this one, which offers up to 500 documents per months fro free.

The chart generation.

There are plenty of chart libraries on the web, both free and commercial. However, I created my own charting library because I needed several features that they’re hard to find all around. More specifically, my charting library is tailored for our industrial supervisory control systems, where the requirements are very different from, for instance, financial applications.
For the Azure Veneziano project, I took a small fraction of this library, but far enough to render multi-plots, multi-axes, WPF charts.

The usage is pretty simple, although an user may find uselessly verbose the code. Again, that’s because the original library is full-featured, and many functions don’t come easy without a certain dose of source code.

        private FrameworkElement CreateChart(
            ReportDataParameters rdp,
            DateTime dtBeginView,
            DateTime dtEndView
            )
        {
            var uc = new MyChartControl();

            /**
             * CHART MODEL
             **/
            var cm = new ChartModelXY();
            uc.DataContext = cm;

            foreach (string id in rdp.PlotIds)
            {
                cm.Plots.Add(
                    MyPlotResources.Instance.GetPlot(id)
                    );
            }

            //adds the required axes
            foreach (var plot in cm.Plots)
            {
                var cartesian = plot as ChartPlotCartesianBase;
                if (cartesian != null)
                {
                    ChartAxisBase axis;
                    axis = MyAxisResources.Instance.AddWhenRequired(cm, cartesian.HorizontalAxisId);
                    axis = MyAxisResources.Instance.AddWhenRequired(cm, cartesian.VerticalAxisId);
                }
            }

            //update data from source
            using (var sqlConnection1 = new SqlConnection(SQLConnectionString))
            {
                sqlConnection1.Open();

                var sqlText =
                    "SELECT * FROM highfieldtales.thistory " +
                    "WHERE __createdAt >= @begin AND __createdAt <= @end " +
                    "ORDER BY __createdAt ASC";

                var cmd = new SqlCommand(
                    sqlText,
                    sqlConnection1
                    );

                cmd.Parameters.Add(
                    new SqlParameter("@begin", dtBeginView)
                    );

                cmd.Parameters.Add(
                    new SqlParameter("@end", dtEndView)
                    );

                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var timestamp = (DateTimeOffset)reader["__createdAt"];

                        var name = (string)reader["name"];
                        var plot = cm.Plots
                            .FirstOrDefault(_ => _.InstanceId == name);

                        var dbl = plot as ChartPlotCartesianLinear;
                        if (dbl != null)
                        {
                            dbl.Points = dbl.Points ?? new List<Point>();

                            var pt = new Point(
                                timestamp.Ticks,
                                (double)reader["value"]
                                );

                            dbl.Points.Add(pt);
                        }
                    }
                }
            }

            var timeline = cm.Axes
                .OfType<ChartAxisTimeline>()
                .FirstOrDefault();

            if (timeline != null)
            {
                timeline.LowestBound = rdp.DateTimeBegin.Ticks;
                timeline.HightestBound = rdp.DateTimeEnd.Ticks;

                timeline.LowerBound = dtBeginView.Ticks;
                timeline.UpperBound = dtEndView.Ticks;
            }

            uc.Measure(new Size(uc.Width, uc.Height));
            uc.Arrange(new Rect(0, 0, uc.Width, uc.Height));

            cm.InvalidateRender();

            return uc;
        }

The only “strange” thing is that we actually DO NOT HAVE a WPF application, but something like a Console application, “hidden” somewhere in the Azure cloud.
However, this isn’t surprising at all, because the above code instantiate an UserControl as container for the chart. Once the chart model setup is done, there’s a “fake” measuring+arranging pass, followed by a final rendering against the control’s face.
The very final step is to capture the visual of this usercontrol and save as a bitmap image (PNG format). This image is inserted in the document as usual

        /// <summary>
        /// Render a visual to a bitmap image
        /// </summary>
        /// <param name="visual"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <returns></returns>
        private static System.Drawing.Image SaveImage(
            Visual visual,
            double width,
            double height
            )
        {
            var bitmap = new RenderTargetBitmap(
                (int)width,
                (int)height,
                96,
                96,
                PixelFormats.Pbgra32
                );

            bitmap.Render(visual);

            var image = new PngBitmapEncoder();
            image.Frames.Add(BitmapFrame.Create(bitmap));
            var ms = new System.IO.MemoryStream();
            image.Save(ms);

            return System.Drawing.Image.FromStream(ms);
        }

Thankfully, the free context of Azure supports without any pain the WPF framework.

Conclusions.

As said many times, this article closes the project as essential.
From this time on, there will be some other projects which could extend yet improve the base.
Keep in touch!