This is my sixth blog post covering Windows 10 IoT Core.
Previous posts:
Part 1: https://blogs.perficient.com/microsoft/2016/01/windows-10-iot-editions-explained/
Part 2: https://blogs.perficient.com/microsoft/2016/01/iot-development-with-windows-10-and-raspberry-pi-what-you-need/
Part 3: https://blogs.perficient.com/microsoft/2016/01/iot-development-with-windows-10-and-raspberry-pi-setting-up/
Part 4: https://blogs.perficient.com/microsoft/2016/01/iot-development-with-windows-10-and-raspberry-pi-hello-world/
Part 5: https://blogs.perficient.com/microsoft/2016/01/iot-development-with-windows-10-and-raspberry-pi-public-display/
In previous blog post we development a Raspberry Pi 2 application which could be used to present a slideshow on a big public display (outdoor or indoor). That application was pulling images in real time from Flickr feed (to emulate a real image source). The problem with this approach is that application requires a constant internet connectivity, which may not be a case for a public display. The more realistic use case for such application is the following: it got connected to internet (or LAN) only for some period of time to upload images on devices. Then the device was disconnected and used offline for displaying images from local storage.
So, we need to implement the following:
– Check internet/network connectivity
– Synchronize local image storage with images on the network (i.e. load images from network to load storage)
– Display images from local storage
The most common way to storage anything on a low power device like Raspberry Pi is to use SQLite. SQLite a popular library (not database server) for mobile, low power devices which allows application to store date in structured relational storage and query that data with SQL. Fortunately, there is a port of this library to Windows Universal platform and hence to Raspberry Pi.
These are the steps to add SQLite support to your Raspberry Pi project:
- Install SQLite extension for Visual Studio 2015 for Universal App Platform (UAP) from here: http://sqlite.org/download.html. File name is sqlite-uap-3100100.vsix.
- Install SQLite.NET-PCL NuGet package (type Install-Package SQLite.Net-PCL in NuGet package manager console).
- Add reference to the extension downloaded to step 1 and Visual C++ 2015 Runtime for UAP (you’ll need to select Add References and then choose Universal Windows -> Extensions):
Now we all set up to use SQLite in our IoT application.
First, we need to define a schema for a table where we going to to be storing our images. Just like with Entity Framework Code First, we can do this by creating an entity model class (underlying table is going to be generated automatically):
public class StoredImage { /// /// Image ID /// [PrimaryKey, AutoIncrement] public int ImageID { get; set; } /// /// Image itself as BLOB /// public byte[] Image { get; set; } }
Note the [PrimaryKey] and [AutoIncrement] attributes above. They are telling SQLite framework that ImageID is going to be a primary key for StoredImage table.
Then, we need to modify our Flickr reader. In our previous example FlickrReader was preloading image names into local memory array and then was returning images one by one to caller. In this example we need to store images in local database, so it would make more sense to return all image references to caller at once:
public sealed class FlickrReader { private const string FeedBaseUrl = "https://api.flickr.com/services/feeds/photos_public.gne"; private readonly string _tags; public FlickrReader(string tags) { _tags = tags; } public async Task<ienumerable> GetImages() { // build request to flickr image feed var url = FeedBaseUrl + "?tags=" + _tags; // load image feed var feed = await XmlDocument.LoadFromUriAsync(new Uri(url)); // parse out images from ATOM 1.0 feed var images = feed .DocumentElement .ChildNodes .Where(n => n.NodeName == "entry") .SelectMany(n => n.ChildNodes) .Where(n => n.NodeName == "link") .Where(l => l.Attributes.Any(a => a.NodeName == "rel" && (string)a.NodeValue == "enclosure")) .SelectMany(e => e.Attributes.Where(a => a.NodeName == "href") .Select(a => (string)a.NodeValue)) .ToList(); return images; } }
We also need to know if we are connected to network, so we are able to load images into our local database:
public class InternetConnectivity { public static bool IsConnected() { ConnectionProfile connections = NetworkInformation.GetInternetConnectionProfile(); return connections != null && connections.GetNetworkConnectivityLevel() == NetworkConnectivityLevel.InternetAccess; } }
Now the new fun part: downloading images locally and storing them to the database:
public sealed class ImageLoader { private int? _currentImageIdx = null; private bool _isWritingImages = false; private SQLiteConnection GetConnection() { var dbPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "db.sqlite"); return new SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), dbPath); } /// /// Return next image from database /// /// public byte[] GetNextImage() { try { // get SQLite connection using (var conn = GetConnection()) { // get table reference var table = conn.Table(); // select image ids var ids = table.Select(i => i.ImageID).ToList(); int? imageID = null; if (!ids.Any()) { return null; } // get next image id if (!_currentImageIdx.HasValue || _currentImageIdx >= ids.Count) { imageID = ids.First(); _currentImageIdx = 0; } else { imageID = ids[_currentImageIdx.Value]; _currentImageIdx++; } // select image bytes from database return table .Where(i => i.ImageID == imageID) .Select(i => i.Image) .FirstOrDefault(); } } catch(Exception) { // we may be geting database deadlocks from time to time // because we reading and writing to the same table at the same time return null; } } /// /// Download and store images to local database /// /// /// public async Task StoreImages(IEnumerable images) { if(_isWritingImages) { return; } _isWritingImages = true; // get SQLite connection using (var conn = GetConnection()) { // ensure the table is created conn.CreateTable(); // get table reference var table = conn.Table(); // clear the table conn.DeleteAll(); foreach (string imageUrl in images) { using (var client = new HttpClient()) { // load image from Flickr into byte array var response = await client.GetAsync(new Uri(imageUrl)); var buffer = await response.Content.ReadAsBufferAsync(); byte[] rawBytes = new byte[buffer.Length]; using (var reader = DataReader.FromBuffer(buffer)) { reader.ReadBytes(rawBytes); } // create new database record var image = new StoredImage() { Image = rawBytes }; try { // try to insert it to database conn.Insert(image); } catch(Exception) { // we may be geting database deadlocks from time to time // because we reading and writing to the same table at the same time } } } } _isWritingImages = false; } }
Finally, we need to wire it all together:
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); var imageLoader = new ImageLoader(); // start image loading task, don't wait var task = ThreadPool.RunAsync(async (source) => { await LoadImages(imageLoader); }); // schedule image databaase refresh every 20 seconds TimeSpan imageLoadPeriod = TimeSpan.FromSeconds(20); ThreadPoolTimer imageLoadTimes = ThreadPoolTimer.CreatePeriodicTimer( async (source) => { await LoadImages(imageLoader); }, imageLoadPeriod); TimeSpan displayImagesPeriod = TimeSpan.FromSeconds(5); // display new images every five seconds ThreadPoolTimer imageDisplayTimer = ThreadPoolTimer.CreatePeriodicTimer( async (source) => { // get next image (byte aray) from database var imageBytes = imageLoader.GetNextImage(); if (imageBytes != null) { // we have to update UI in UI thread only await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => { // create bitmap from byte array BitmapImage bitmap = new BitmapImage(); MemoryStream ms = new MemoryStream(imageBytes); await bitmap.SetSourceAsync(ms.AsRandomAccessStream()); // display image splashImage.Source = bitmap; } ); } }, displayImagesPeriod); } private async Task LoadImages(ImageLoader imageLoader) { // only load images when we are connected if (InternetConnectivity.IsConnected()) { // create Flickr reader var reader = new FlickrReader("unicorn"); //load Flickr images var images = await reader.GetImages(); // store images to database await imageLoader.StoreImages(images); } } }
This is what we are doing in above code:
- When application starts, we check if device is connected to internet, and if it is then we getting Flickr image feed by tag, downloading images from it and storing them in local SQLite database.
- We are also scheduling the above process to run every 20 seconds, so if we didn’t have network connectivity originally, but got connected at some point of time, then we still can load images locally.
- In parallel, we starting process which is loading images from local database and displaying them on the screen.
- Having all of the above, application is able to handle both connected and disconnected states gracefully.
Full code for this blog post is available in my Github repository: https://github.com/starnovsky/IoTDisplayWithLocalStorage