draptik

mostly tech stuff

Testing That Different Objects Have the Same Properties

Sometimes you want to ensure that 2 unrelated objects share a set of properties — without using an interface.

Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace Demo
{
    public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

First thought for C# developers: AutoMapper

Let’s do that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using AutoMapper;

namespace Demo
{
    public class MyMapping
    {
        public static IMapper Mapper;

        public static void Init()
        {
            var cfg = new MapperConfiguration(x =>
            {
                x.CreateMap<Customer, Person>();
            });
            Mapper = cfg.CreateMapper();
        }
    }
}

Now we can write a unit test to see if we can convert a Customer to a Person:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using Xunit;

namespace Demo
{
    public class SomeTests
    {
        [Fact]
        public void Given_Customer_Should_ConvertTo_Person()
        {
            // Arrange
            const string firstname = "foo";
            const string lastname = "bar";

            var customer = new Customer
            {
                FirstName = firstname,
                LastName = lastname
            };

            MyMapping.Init();

            // Act
            var person = MyMapping.Mapper.Map<Customer, Person>(customer);

            // Assert
            person.FirstName.Should().Be(firstname);
            person.LastName.Should().Be(lastname);
        }
  }
}  

This test passes.

But what happens when we want to ensure that a new Customer property (for example Email) is reflected in the Person object?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace Demo
{
    public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; } // <-- new property
    }

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

Our unit test still passes. ☹

Wouldn’t it be nice to have our unit test fail if the classes are not in sync?

Here is where FluentAssertions ShouldBeEquivalentTo comes in handy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using FluentAssertions;
using Xunit;

[Fact]
public void Given_Customer_Should_ConvertTo_Person_With_CurrentProperties()
{
    //Arrange
    const string firstname = "foo";
    const string lastname = "bar";

    var customer = new Customer
    {
        FirstName = firstname,
        LastName = lastname,
        Email = "foo@bar.com"
    };

    MyMapping.Init();

    // Act
    var person = MyMapping.Mapper.Map<Customer, Person>(customer);

    // Assert
    customer.ShouldBeEquivalentTo(person);
}

Subject has a member Email that the other object does not have.

Cool: This is the kind of message I want to have from a unit test!

ShouldBeEquivalentTo also takes an optional Lambda expression in case you need more fine grained control which properties are included in the comparison. Here is an example where we exlude the Email property on purpose:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using FluentAssertions;
using Xunit;

[Fact]
public void Given_Customer_Should_ConvertTo_Person_With_CurrentProperties_Excluding_Email()
{
    //Arrange
    const string firstname = "foo";
    const string lastname = "bar";

    var customer = new Customer
    {
        FirstName = firstname,
        LastName = lastname,
        Email = "foo@bar.com"
    };

    MyMapping.Init();

    // Act
    var person = MyMapping.Mapper.Map<Customer, Person>(customer);

    // Assert
    customer.ShouldBeEquivalentTo(person,
        options =>
            options.Excluding(x => x.Email));
}

This test passes.

The complete documentation for FluentAssertions’ ShouldBeEquivalentTo method can be found here.

Source code for this post

You can clone a copy of this project here: https://github.com/draptik/blog-demo-shouldbeequivalentto.

1
git clone https://github.com/draptik/blog-demo-shouldbeequivalentto.git

Remotly Measuring Temperatures With a Raspberry Pi Using Radio Frequency Modules From Ciseco (Part 3: UI)

Part 1 describes how to setup the hardware, part 2 describes how to to record/persist the sensor information.

In this post I’ll describe how to display the data.

TL;DR

Should be similar to http://rpi-temperatures-website-demo.firebaseapp.com/.

Choosing the right technology stack

This really depends on your individual needs. Here are some points to consider:

  • How many people will be accessing the site?
  • Do you have to access the site from outside of your LAN? Do you need a login mechanism?
  • Which technology stack are you comfortable with? Which technology stack is supported on the server?
  • Database interaction possible (this demo uses SQLite3)?

If you know that you’ll have many requests I would discourage you from using the Raspberry Pi (RPi) as a web server.

Otherwhise, the RPi is a good choice for a web server.

Some of the technology stacks available on the RPi are:

  • JVM: Java, Scala
  • .NET/Mono: C#, F#
  • Python
  • JS: Node.js

Since I only want to display data in my LAN I decided to use Javascript: Node.js in combination with the Express framework provides all possible features and is very lightweight.

No matter which stack you choose: Running the web site on the same RPi as the temperature recording from the previous posts saves you the hassle of installing software on a different machine. And it obviously saves energy, since the RPi is running 24/7 anyway recording temperature data.

User Interface

My primary goal was explorative data visualization. For this purpose I decided to show 2 plots:

  • an overview plot showing the past 14 days
  • and a detail plot, showing the selection of the overview plot

You can test the website with some sample data at http://rpi-temperatures-website-demo.firebaseapp.com/

Some of the UI features:

  • The detail plot can be dragged and the overview plot has a selection region which can be resized and dragged.
  • Changes to either plot are reflected in the other.
  • Mouse movement in the detail plot updates the legend.

All charting features are implemented using Flot.

Prerequisites: Node.js

Here is a very concise manual on how to install Node.js on the RPi (this gives you a more up to date version of Node.js than default Raspbian does): http://weworkweplay.com/play/raspberry-pi-nodejs/

Installation

All further instructions are expected to be executed on the RPi.

Download and unzip the source code from

https://github.com/draptik/rpi-temperature-website/archive/v1.0.zip

1
2
3
4
5
cd ~
mkdir website && cd website
wget https://github.com/draptik/rpi-temperature-website/archive/v1.0.zip
unzip *.zip
cd rpi*

Install backend packages (node packages are defined in packages.json):

1
npm install

Node.js packages are installed to folder node_modules.

  • the folder app_server contains the basic web site.
  • the folder app_api provides the REST backend.

Install frontend packages (bower packages are defined in bower.json):

1
bower install

Bower packages are installed to folder public/vendor/bower.

You should now be able to start the application (using the provided sample data in folder sample_data):

1
npm start

Configuration (development vs production)

The application uses a single switch between development mode and production mode:

NODE_ENV

This information is currently used in the following places in the application:

REST URL

Setting the URL for the REST service (in app.js):

1
var url = process.env.NODE_ENV === 'production' ? 'http://camel:3000' : 'http://localhost:3000';

Within my LAN the RPi is named camel

And in case you’re not familiar with the syntax

1
var result = someCondition ? 'string1' : 'string2';

It’s just a shorthand for

1
2
3
4
5
6
var result;
if (someCondition) {
  result = 'string1';
} else {
  result = 'string2';
}

Database location

Setting the database location (in app_api/models/db.js):

1
var dbLocation = process.env.NODE_ENV === 'production' ? '/var/www/templog.db' : 'sample_data/templog.db';

Usage

Once you’ve configured the LAN URL and the database location, you can set the environment variable NODE_ENV to production and start the application:

1
2
export NODE_ENV=production
npm start

Customizing

You will probably want to customize the UI, as my design skills are limited at best. ;–)

Here’s an overview of the project, so you know where to change things:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
├── app_api                     //  REST API
│   ├── controllers
│   │   └── temperatures.js
│   ├── models
│   │   ├── db.js               //  DATABASE
│   └── routes
│       └── index.js
├── app.js                      //  MAIN ENTRY POINT FOR THE APPLICATION
├── app_server                  //  web server (express.js)
│   ├── controllers
│   │   ├── main.js
│   │   └── temperatures.js
│   ├── models
│   ├── routes
│   │   ├── index.js
│   │   ├── temperatures.js
│   └── views
│       ├── error.jade
│       ├── index.jade
│       ├── layout.jade
│       └── temperatures-list.jade
├── bower.json                  //  Bower configuration (frontend)
├── node_modules                //  Location of node modules
├── nodemon.json                //  nodemon configuration
├── package.json                //  node configuration
├── public                      //  frontend stuff...
│   ├── images                  //  images
│   ├── scripts                 //  Javascript code
│   │   ├── chart.js            //  This file includes all charting code
│   │   ├── rest.js             //  wrapper code to access REST API
│   │   └── suncalc.js          //  calc sunrise/sunset on the fly
│   ├── stylesheets             //  ...
│   │   ├── app.css
│   │   ├── chart.css
│   └── vendor                  //  3rd party libraries
│       ├── bower               //  ...installed via bower
│       └── custom              //  ...other 3rd party libraries
├── sample_data                 //  sample data
│   └── templog.db              //  sqlite3 sample data set

That’s it. Have fun!

Remotly Measuring Temperatures With a Raspberry Pi Using Radio Frequency Modules From Ciseco (Part 2: Software)

In the previous post I described how to setup the hardware for measuring temperatures at home. This post will use a small python program to save the recorded temperatures to a database.

For simplicity’s sake we’ll be using SQLite3 as our database.

Aside from our (indoor) temperature sensors, we’ll also retrieve outdoor temperatures using the free service weather underground (registration required).

Note: In case your Raspberry Pi does not have access to the internet you can just exlude the weather underground parts in the code below.

All following code is intended to run on the Raspberry Pi receiving the data.

Create database (SQLite3)

The database schema is simple. We need one table to store the temperatures, and another to store sensor information.

Create a new file called templog.db:

1
$ touch templog.db

There are different ways to interact with SQLite3 (cli (=interactive), script, api).

The goal is to execute the following sql statements:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE sensors
(
    name TEXT NOT NULL,
    id TEXT NOT NULL,
    baudrate INTEGER,
    port TEXT NOT NULL,
    active INTEGER
);
CREATE TABLE temps
(
    timestamp TEXT,
    temp REAL,
    ID TEXT
);

Simplest solution is to open the newly created file templog.db with sqlite3 (cli/interactive) …

1
sqlite3 templog.db

…and past the previous code block. Should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ sqlite3 demo.db
SQLite version 3.8.10.2 2015-05-20 18:17:19
Enter ".help" for usage hints.
sqlite> CREATE TABLE sensors
   ...> (
   ...>     name TEXT NOT NULL,
   ...>     id TEXT NOT NULL,
   ...>     baudrate INTEGER,
   ...>     port TEXT NOT NULL,
   ...>     active INTEGER
   ...> );
sqlite> CREATE TABLE temps
   ...> (
   ...>     timestamp TEXT,
   ...>     temp REAL,
   ...>     ID TEXT
   ...> );

We’ve created a database.

The important table is temps: It will contain the measurements.

The other table (sensors) contains informations about the sensors, which are currently only needed for the weather underground ‘sensor’. And yes: the column names/types are not optimal.

Note: timestamp TEXT will bite us in the ass, but SQLite3 does NOT have any date type.

Monitor script

I found this nice script somewhere on Github. So Thank You kal001!

Here’s my gist link for the script below.

Place this script side-by-side to temps.log.

Save it as monitor.py.

You will probably want to modify the values for dbname, TIMEOUT, debug.txt, etc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env python

import sqlite3
import threading
from time import time, sleep, gmtime, strftime

import serial
import requests

# global variales

# sqlite database location
dbname = 'templog.db'

# serial device
DEVICE = '/dev/ttyAMA0'
BAUD = 9600

ser = serial.Serial(DEVICE, BAUD)

# timeout in seconds for waiting to read temperature from sensors
TIMEOUT = 30

# weather underground data
WUKEY = ''
STATION = ''
# time between weather underground samples in seconds
SAMPLE = 30 * 60


def log_temperature(temp):
    """
    Store the temperature in the database.
    """

    conn = sqlite3.connect(dbname)
    curs = conn.cursor()

    curs.execute("INSERT INTO temps values(datetime('now', 'localtime'), '{0}', '{1}' )".format(temp['temperature'], temp['id']))

    conn.commit()
    conn.close()


def get_temp():
    """
    Retrieves the temperature from the sensor.

    Returns -100 on error, or the temperature as a float.
    """

    global ser

    tempvalue = -100
    deviceid = '??'
    voltage = 0

    fim = time() + TIMEOUT

    while (time() < fim) and (tempvalue == -100):
        n = ser.inWaiting()
        if n != 0:
            data = ser.read(n)
            nb_msg = len(data) / 12
            for i in range(0, nb_msg):
                msg = data[i*12:(i+1)*12]
                deviceid = msg[1:3]

                if msg[3:7] == "TMPA":
                    tempvalue = msg[7:]

                if msg[3:7] == "BATT":
                    voltage = msg[7:11]
                    if voltage == "LOW":
                        voltage = 0
        else:
            sleep(5)

    return {'temperature':tempvalue, 'id':deviceid}


def get_temp_wu():
    """
    Retrieves temperature(s) from weather underground (wu) and stores it to the database
    """

    try:
        conn = sqlite3.connect(dbname)
        curs = conn.cursor()
        query = "SELECT baudrate, port, id, active FROM sensors WHERE id like 'W_'"

        curs.execute(query)
        rows = curs.fetchall()

        #print(rows)

        conn.close()

        if rows != None:
            for row in rows[:]:
                WUKEY = row[1]
                STATION = row[0]

                if int(row[3]) > 0:
                    try:
                        url = "http://api.wunderground.com/api/{0}/conditions/q/{1}.json".format(WUKEY, STATION)
                        r = requests.get(url)
                        data = r.json()
                        log_temperature({'temperature': data['current_observation']['temp_c'], 'id': row[2]})
                    except Exception as e:
                        raise

    except Exception as e:
        text_file = open("debug.txt", "a+")
        text_file.write("{0} ERROR:\n{1}\n".format(strftime("%Y-%m-%d %H:%M:%S", gmtime()), str(e)))
        text_file.close()


def main():
    """
    Program starts here.
    """

    get_temp_wu()
    t = threading.Timer(SAMPLE, get_temp_wu)
    t.start()

    while True:
        temperature = get_temp()

        if temperature['temperature'] != -100:
            log_temperature(temperature)

        if t.is_alive() == False:
            t = threading.Timer(SAMPLE, get_temp_wu)
            t.start()

if __name__ == "__main__":
    main()

Run the script. Open another shell, take a look inside the database (new data arriving once an hour?).

Don’t forget to start the script after turning off the Raspberry Pi. Or include the script in your boot process (init, systemd).

Part 3 will provide a UI for the collected data.

Remotly Measuring Temperatures With a Raspberry Pi Using Radio Frequency Modules From Ciseco (Part 1: Hardware)

Since I’m a software developer, I’ve always been wanting to do some hardware stuff (including some soldering) with my Raspberry Pi. So, for starters, I picked something that was useful and only involved sensors (no actors – yet):

Measuring temperatures at home.

Neither rocket science nor cool IoT “coffe is ready when I get out of the shower in the morning”, I know.

The sensor(s) should send a signal once an hour, without cable, and run on battery.

As the whole IoT thing is still relatively new, there are no standards yet. I picked the product line from Ciseco (currently being rebranded to Wireless Things (www.wirelessthings.com)). Affordable and good documentation. And, more important: These people are passionate about their product!

Before we get started here is a quick preview of what we want to accomplish (for details about the UI see part3): http://rpi-temperatures-website-demo.firebaseapp.com/

So let’s get started:

  • 2 battery powered sensors transmitting temperature data once per hour via radio frequency.
  • Raspberry Pi is powered 24/7 and records signals from sensors.

Parts & Costs

Total: £56.54

If you only want a single sensor (1 Slice of Pi, 1 Sensor, 2 XRF modules): £36.16

Setup overview

  • On the left is the Raspberry Pi with an XRF module mounted to the Slice of Pi.
    • The Slice of Pi acts as a breakout board.
    • The XRF module will receive data.
    • The Raspberry Pi will be continously monitoring input and storing the information locally to an SQLite3 database. See part 2 for details.
  • On the right are two sensors with XRF modules.
    • These modules will send temperature data once per hour to the Raspberry Pi.
    • These modules run on battery power.
    • You can have as many of these sensors as you want.

Each XRF module requires a unique id (‘XRF ID’ in the image above).

Hardware: Fitting the pieces & soldering

The official documentation is pretty good. If you know what you’re doing.

In case you are not really sure what you are doing with the soldering iron: Keep calm. There is a lot of information on the internet. I found the following step-by-step instruction useful:

Raspberry Pi – Assemble your temperature THERMISTOR with an XRF transmitter probe

Here is what the assembled sensor looks like:

Here is a picture of the sensor in the box. You have to create the holes in the top of the box yourself. I just used the soldering iron to melt both holes, which is probably not considered best practice ;–)

And here is a picture of the Raspberry Pi with an XRF module mounted on the Slice of Pi:

Note: The sensor comes with a box. In case you want to place the sensor inside the box make sure not to solder the thermistor to close to the board. You will want to make a hole in the box and have the thermistor stick out. Otherwise the thermistor will be inside the closed box and measure the temperature inside the box (instead of outside the box). Here is a picture illustrating the issue:

OS

This is one of the reasons I picked Ciseco for my simple project: They provide a standard Linux distribution (Raspbian) including their drivers.

This means the “Slice of Pi” works out of the box.

And the rest of the system behaves like a normal Raspbian system. There is no vendor “lock-in”.

Ciseco’s patched version of Raspbian here. Currently this is http://openmicros.org/Download/2015-05-05-raspbain-wheezy-raswik-shrunk.img.zip. I used the slightly older version http://openmicros.org/Download/2014-12-24-wheezy-raspbian-ciseco-shrunk.img.zip.

Software (well, actually Firmware)

All parts are soldered and the RPi has the correct operating system.

Our next steps (from a bird’s eye perspective) are:

  • uploading the correct firmware onto the sensors’ XRF module
  • configuring the sensors’ XRF module

The following instructions are mostly taken from Sean Landsman’s excellent tutorial.

Upload firmware to sensor(s)‘ XRF module

Why do we have to do this?

The sensor board is generic and can be configured for working with different types of sensors. We’re using a thermistor, in case you forgot… The thing with the antenna is the XRF module. We have 3 XRF modules: 1 for receiving data, 2 for sending data. We’ll take care of the sending modules first.

The tool for uploading the appropriate firmware to XRF modules is called xrf_uploader.

Download the xrf_uploader source code from Ciseco’s Github page at https://github.com/CisecoPlc/XRF-Uploader to the Raspberry Pi.

Then compile the file xrf_uploader.cpp:

1
2
g++ xfr_uploader.cpp -o xrf_uploader
chmod +x xrf_uploader

Next, get a copy of the thermistor firmware from Ciseco’s Github page at https://github.com/CisecoPlc/XRF-Firmware-downloads/tree/master/XRFV2%20ARF%20SRF%20-%20LLAP. At the time of writing, the most current version of the termistor firmware was llapThermistor-V0.73-24MHz.bin.

  • Shutdown the Raspberry Pi (sudo init 0)
  • Connect the first XRF ‘sensor’ module with the Slice of Pi
  • Start the Raspberry Pi again

Copy the thermistor firmware into the same folder as the xrf_uploader and upload the firmware to the newly connected XRF module:

1
./xrf_uploader -d /dev/ttyAMA0 -f llapThermistor-V0.73-24MHz.bin

Note: /dev/ttyAMA0 is the Raspberry Pi’s location of the UART.

The upload should look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pi@raspberrypi ~/xrf_loader $ ./xrf_uploader -d /dev/ttyAMA0 -f llapThermistor-V0.73-24MHz.bin
Writing new firmware file llapThermistor-V0.50-24MHz.bin to device /dev/ttyAMA0 with baud rate 9600...
Reading firmware file...
Read 1300 lines from firmware file
Opening device...
Setting serial parameters...
Waiting for device to settle...

<> Entering command modus
<- OK
-> ATVR
<- 0.63B XRF
<- OK
-> ATPG
<- OK
<> Sent 1300 of 1300 lines...

All OK, XRF successfully reprogrammed!

Waiting for device to settle...

<> Entering command modus
<- OK
-> ATVR
<- 0.50B APTHERM
<- OK
  • Shutdown the Raspberry Pi
  • Detach the freshly configured XRF ‘sensor’ module and replace it with the XRF module which will be receiving temperature information (this XRF module is called the ‘pass-through’).
  • Connect the the ‘sensor’ XRF module used for temperature measurement with thermistor board.
  • Do not insert the battery yet!
  • Start the Raspberry Pi again.

Configure sensors (XRF modules)

We now have

  • fully equipped sensor(s) without battery power
  • Raspberry Pi with receiving sensor

Again: Do not insert batteries into the sensors yet.

We first have to install a protocol to communicate between the ‘sensor’ and the ‘pass-through’ (aka ‘receiving’) XRF device.

Download pySerial to the Raspberry Pi.

Unpack and install pySerial:

1
2
3
$ tar xvzf pyserial-2.5.tar.gz
$ cd pyserial-2.5
$ sudo python setup.py install

Using pySerial/miniterm

pySerial comes with miniterm.py, a small serial terminal. Attach to the terminal:

1
$ python ~/pyserial-2.5/examples/miniterm.py /dev/ttyAMA0

Press Ctrl+T, followed by Ctrl+E to enable the echo area. This helps during debugging.

Note: The terminal is not intended for typing commands: Always paste the commands from somewhere else.

Note: Once the battery is inserted it will drain very quickly during debugging. Ensure to unplug the battery if it’s not needed.

miniterm: First contact

While miniterm is running, insert the battery. The output should look something like this:

1
a--STARTED--a--STARTED--a--STARTED--a--STARTED--a--STARTED--a--STARTED--

What we are seeing is an example of LLAP (Ciseco’s lightweight local automation protocol). A complete documentation of the protocol can be found here and here.

From Ciseco’s documentation:

1
2
3
4
5
6
7
8
9
10
11
12
The message structure

[...] the message is 12 characters long and in 3 distinct sections. To illustrate the 3 separate parts to the message, see this example:

aXXDDDDDDDD
1.     "a" is lower case and shows the start of the message

2.     XX is the device identifier (address A-Z & A-Z)

3.      DDDDDDDDDD is the data being exchanged.

Note: Only the first "a" character is lowercase, the remaining message always uses uppercase.

Paste a--HELLO---- into the miniterm. In case your wondering: The default identifier is --. We’ll change that later.

1
2
3
4
5
$ python ~/pyserial-2.5/examples/miniterm.py /dev/ttyAMA0
--- Miniterm on /dev/ttyAMA0: 9600,8,N,1 ---
--- Quit: Ctrl+]  |  Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
--- local echo active ---
a--HELLO----

If the remote device is running and configured correctly the output should immediatly change to:

1
2
3
4
5
$ python ~/pyserial-2.5/examples/miniterm.py /dev/ttyAMA0
--- Miniterm on /dev/ttyAMA0: 9600,8,N,1 ---
--- Quit: Ctrl+]  |  Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
--- local echo active ---
a--HELLO----a--HELLO----

Note the duplicate a--HELLO---- in the last line. The second a--HELLO---- is the answer from the remote device.

miniterm: Change device ID

Since all devices have the same initial ID, it is a good idea to change the device ID in case you intend to use more than one remote device.

The following code changes the device ID to ZZ:

1
2
a--CHDEVIDZZ
a--REBOOT---

The terminal output should look like this:

1
2
3
4
5
6
$ python ~/pyserial-2.5/examples/miniterm.py /dev/ttyAMA0
--- Miniterm on /dev/ttyAMA0: 9600,8,N,1 ---
--- Quit: Ctrl+]  |  Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
--- local echo active ---
a--CHDEVIDZZ
a--REBOOT---aZZSTARTED--aZZSTARTED--aZZSTARTED--aZZSTARTED--aZZSTARTED--aZZSTARTED--
miniterm: Read temperature

Assuming the device ID is ZZ reading the temperature is accomplished by aZZTEMP-----:

1
2
3
4
5
$ python ~/pyserial-2.5/examples/miniterm.py /dev/ttyAMA0
--- Miniterm on /dev/ttyAMA0: 9600,8,N,1 ---
--- Quit: Ctrl+]  |  Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
--- local echo active ---
aZZTEMP-----aZZTMPA24.21

In the above answer from the remote device the temperature is 24.21 degrees Celsius.

miniterm: Configure measurement interval

Currently the remote device is contiously sending information. And draining the battery. To preserve battery power the interval can be configured to send information periodically using the command a--INTVL. The interval is defined with a 3 digit number followed by the time period: S(seconds), M(minutes), H(hours), D(days). For example the command aZZINTVL005S would set the interval of the remote device to 5 seconds.

Additionally the device should be sent to sleep in between cylces by issuing the command aZZCYCLE----:

1
2
3
4
5
6
$ python ~/pyserial-2.5/examples/miniterm.py /dev/ttyAMA0
--- Miniterm on /dev/ttyAMA0: 9600,8,N,1 ---
--- Quit: Ctrl+]  |  Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
--- local echo active ---
aZZINTVL001H
aZZCYCLE----

The above example sets the interval to one hour (001H).

Repeat the sensor setup and configuration for each sensor which needs to send data. The ‘pass-through’ (aka receiving) sensor does not have to be configured.

Part 2 describes a simple program to monitor the data being produced by this setup.

.NET Backend Providing REST

TL;DR

My AngularJS demo app has a new backend implementation using .NET Web API.

Recap

Our goals:

  • server side: minimal working REST API providing
    • GET dummy
    • CRUD users
  • client side (angular): communicate with server side

Setup

Creating a Web API project is straightforward: Just follow the instructions at

The final project structure will look like this:

Adding Models

Create two new POCOs for User and Dummy:

Models/Dummy.cs
1
2
3
4
5
6
7
8
9
namespace WebService.Models
{
    public class Dummy
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}
Models/User.cs
1
2
3
4
5
6
7
8
9
namespace WebService.Models
{
    public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

Adding Service Layer

Create a new folder Service and add a UserService with corresponding interface IUserService.

Service/IUserService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System.Collections.Generic;
using WebService.Models;

namespace WebService.Service
{
    public interface IUserService
    {
        ICollection<User> GetAllUsers();
        User GetById(int userId);
        User UpdateUser(User user);
        User CreateNewUser(User user);
        void RemoveUserById(int userId);
    }
}
Service/UserService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using WebService.Models;

namespace WebService.Service
{
    public class UserService : IUserService
    {
        public UserService()
        {
            this.Users = new Collection<User>();
            this.CreateUsers();
        }

        private ICollection<User> Users { get; set; }

        public ICollection<User> GetAllUsers()
        {
            return this.Users;
        }

        public User GetById(int userId)
        {
            return this.Users.SingleOrDefault(x => x.Id.Equals(userId));
        }

        public User UpdateUser(User user)
        {
            var u = this.Users.SingleOrDefault(x => x.Id.Equals(user.Id));
            if (u != null) {
                u.FirstName = user.FirstName;
                u.LastName = user.LastName;
            }
            return u;
        }

        public User CreateNewUser(User user)
        {
            var newUser = new User
            {
                Id = this.Users.Max(x => x.Id) + 1,
                FirstName = user.FirstName,
                LastName = user.LastName
            };

            this.Users.Add(newUser);

            return newUser;
        }

        public void RemoveUserById(int userId)
        {
            this.Users.Remove(this.Users.SingleOrDefault(x => x.Id.Equals(userId)));
        }

        private void CreateUsers()
        {
            const int numberOfUsers = 10;
            for (int id = 1; id <= numberOfUsers; id++) {
                this.Users.Add(new User {Id = id, FirstName = "Foo" + id, LastName = "Bar" + id});
            }
        }
    }
}

Note: This is just a quick and dirty setup to get a working REST API without much overhead. In a real application the service will probably be a bit more fine grained. For example: In this demo app the users are simply stored in memory and not persisted to a database.

Adding IoC for Web API

Add inversion of control (IoC) to Web API:

IoC/UnityResolver.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;
using Microsoft.Practices.Unity;

namespace WebService.IoC
{
    /// <summary>
    /// http://www.asp.net/web-api/overview/extensibility/using-the-web-api-dependency-resolver
    /// </summary>
    public class UnityResolver : IDependencyResolver
    {
        private readonly IUnityContainer container;

        public UnityResolver(IUnityContainer container)
        {
            if (container == null) {
                throw new ArgumentNullException("container");
            }
            this.container = container;
        }

        public void Dispose()
        {
            this.container.Dispose();
        }

        public object GetService(Type serviceType)
        {
            try {
                return this.container.Resolve(serviceType);
            }
            catch (ResolutionFailedException) {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try {
                return this.container.ResolveAll(serviceType);
            }
            catch (ResolutionFailedException) {
                return new List<object>();
            }
        }

        public IDependencyScope BeginScope()
        {
            var child = this.container.CreateChildContainer();
            return new UnityResolver(child);
        }
    }
}

Adding Controllers

Create controllers UsersController and DummyController:

Controllers/Dummy.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Web.Http;
using System.Web.Http.Cors;
using WebService.Models;

namespace WebService.Controllers
{
    [EnableCors(origins: "http://localhost:9000", headers: "*", methods: "*")]
    public class DummyController : ApiController
    {
        public Dummy Get()
        {
            return new Dummy
            {
                Id = 0,
                FirstName = "JonFromREST",
                LastName = "Doe"
            };
        }
    }
}
Controllers/UsersController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System.Collections.Generic;
using System.Web.Http;
using System.Web.Http.Cors;
using WebService.Models;
using WebService.Service;

namespace WebService.Controllers
{
    [EnableCors(origins: "http://localhost:9000", headers: "*", methods: "*")]
    public class UsersController : ApiController
    {
        private readonly IUserService userService;

        public UsersController(IUserService userService)
        {
            this.userService = userService;
        }

        public ICollection<User> Get()
        {
            return this.userService.GetAllUsers();
        }

        public User Get(int id)
        {
            return this.userService.GetById(id);
        }

        public User Put(User user)
        {
            return this.userService.UpdateUser(user);
        }

        public User Post(User user)
        {
            return this.userService.CreateNewUser(user);
        }

        public void Delete(int id)
        {
            this.userService.RemoveUserById(id);
        }
    }
}

Putting the pieces together

Within WebApiConfig.cs:

  • configure the IoC container
  • activate CORS
  • return JSON
  • configure routes
App_Start/WebApiConfig.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System.Net.Http.Headers;
using System.Web.Http;
using Microsoft.Practices.Unity;
using WebService.IoC;
using WebService.Service;

namespace WebService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // IoC container
            //
            // http://www.asp.net/web-api/overview/extensibility/using-the-web-api-dependency-resolver
            var container = new UnityContainer();
            // Note: for this demo we want the user service to be a singleton ('ContainerControlledLifetimeManager' in Unity syntax)
            container.RegisterType<IUserService, UserService>(new ContainerControlledLifetimeManager());
            config.DependencyResolver = new UnityResolver(container);

            // Web API configuration and services

            config.EnableCors();

            // Return JSON instead of XML http://stackoverflow.com/a/13277616/1062607
            config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));

            // Web API routes
            config.MapHttpAttributeRoutes();

            const string baseUrl = "ngdemo/web";

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: baseUrl + "/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

Returning Lower Case JSON from .NET Web API

C# uses upper case property names by default. JavaScript uses lower case property names by default.

To automatically convert between both worlds you can add a ContractResolver to your Global.asax.cs:

Global.asax.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.Web;
using System.Web.Http;
using Newtonsoft.Json.Serialization;

namespace WebService
{
    public class WebApiApplication : HttpApplication
    {
        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);

            // lower case property names in serialized JSON: http://stackoverflow.com/a/22130487/1062607
            GlobalConfiguration.Configuration
                .Formatters
                .JsonFormatter
                .SerializerSettings
                .ContractResolver = new CamelCasePropertyNamesContractResolver();
        }
    }
}

Done?

Almost: For the ASP.NET backend to be reachable by the same URL as the other backends (NodeJs backend and Java backend) we have to change the default port of the application to 8080:

Now we can start the Web API backend from Visual Studio (F5).

In the newly openend browser, check the URL http:localhost:8080/nodedemo/web/dummy/. The dummy JSON object should be visible:

Check the API

Start the backend

Start the Web API backend from Visual Studio.

Start the frontend

Note: For setting up the frontend, you will need to install NodeJS and Grunt. Please have a look at the README.md file in the frontend folder for further details.

Open a command prompt and navigate to the frontend folder.

Run grunt server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
>grunt server
Running "server" task

Running "clean:server" (clean) task
Cleaning .tmp...OK

Running "concurrent:server" (concurrent) task

Running "coffee:dist" (coffee) task

Done, without errors.

Running "copy:styles" (copy) task


Done, without errors.

Running "compass:server" (compass) task
directory .tmp/styles/
       create .tmp/styles/main.css (1.718s)
    Compilation took 1.802s

Done, without errors.

Running "autoprefixer:dist" (autoprefixer) task
File ".tmp/styles/main.css" created.

Running "connect:livereload" (connect) task
Started connect web server on localhost:9000.

Running "open:server" (open) task

Running "watch" task
Waiting...

Visit URL http://localhost:9000/#/dummy:

That’s it.

Source code for this post

You can clone a copy of this project here: https://github.com/draptik/angulardemorestful.

To checkout the correct version for this demo, use the following code:

1
2
3
git clone git@github.com:draptik/angulardemorestful.git
cd angulardemorestful
git checkout -f step7-aspnet-webapi-backend

In case you are not using git you can also download the project as ZIP or tar.gz file here: https://github.com/draptik/angulardemorestful/releases/tag/step7-aspnet-webapi-backend

Link Collection #4

I’m a bit behind on this section, I know. Here’s the first batch:

currently reading:

c# stuff:

sql stuff:

meta:

‘Important’ images/quotes:

other fun stuff:

not so fun:

And the best tv show I’ve seen in ages is called True Detective.

Installing Seafile on Raspberry Pi

With all the security issues in the past relating to privacy I’ve been wanting to install a private cloud service similar to Dropbox for some time now. So, here’s a post on how to install Seafile on the Raspberry Pi.

I choose Seafile over Owncloud because I have read multiple posts that (1) Owncloud is not very responsive an a Raspberry Pi and (2) Seafile has a better security model (see 1, 2, 3).

Using a Raspberry Pi for the server seems like a good choice, because it has very low power consumption, so you can have it running 24/7. Furthermore, the Rasperry Pi can be setup with Debian GNU/Linux (f. ex. Raspbian) running in server mode. As Debian is a widely used Linux distribution, most problems can be easily solved by searching the web.

Installing Seafile should be straightforward from following the instructions at the official Seafile Wiki. If you can read German, you can also follow the excellent instructions on Jan Karres’s blog Raspberry Pi: Owncloud-Alternative Seafile Server installieren.

As there are some pitfalls along the way, I’ll describe how I installed Seafile with SSL support.

Installation

Note: This is mainly a merge of Jan Karres’s blog post (in German) and the official wiki documentation from Seafile.

Demo values

1
2
3
4
5
domain: no-ip.org
sub-domain: mycloud
DDNS-domain: mycloud.no-ip.org
internal server name: mycloud
internal IP address of Raspberry Pi: 192.168.1.42

Prerequisites

I’ll assume you have a working Raspian installation on a Raspberry Pi.

If you want to reach your Seafile server from the internet and your ISP only provides you with a dynamic IP you will have to register with a DDNS provider such as Dyn or no-ip.com.

Step 0 Update

Update Raspbian:

$ sudo aptitude update

Step 1 Install dependencies

Install dependencies required by Seafile:

$ sudo aptitude -y install python2.7 python-setuptools python-simplejson python-imaging sqlite3

Step 2 Create seafile user

For security reasons we’ll create a separate user for running Seafile. The user will be called seafile and will not require a password, since we will never be accessing this user directly through SSH.

$ sudo adduser seafile --disabled-password

Switch to being seafile user:

$ sudo su seafile

Change to seafile’s home directory:

$ cd

Step 3 Download and unpack

First we’ll create a new folder mycloud in the seafile user’s home directory:

$ mkdir mycloud && cd mycloud

Download and unpack Seafile Server for Raspberry Pi from http://www.seafile.com/en/download/.

1
2
3
4
wget https://bitbucket.org/haiwen/seafile/downloads/seafile-server_2.1.5_pi.tar.gz
tar -xvzf seafile-server_*
mkdir installed
mv seafile-server_* installed

You should have the following directory structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
tree /home/seafile/mycloud/ -L 2
/home/seafile/mycloud/
├── installed
│   └── seafile-server_2.1.5_pi.tar.gz
└── seafile-server-2.1.5
    ├── reset-admin.sh
    ├── runtime
    ├── seaf-fuse.sh
    ├── seafile
    ├── seafile.sh
    ├── seahub
    ├── seahub.sh
    ├── setup-seafile-mysql.py
    ├── setup-seafile-mysql.sh
    ├── setup-seafile.sh
    └── upgrade

All config files are in the folder mycloud (currently there are no config files present yet). New versions can be installed side by side without having to change the config files. The install process will create a soft link seafile-server-latest pointing the the most current installation.

Step 4 Installation

$ cd seafile-server-2.1.5

Before you start the install process you can have a look at the options being configured during installation here.

For this example we’ll assume your DDNS domain is mycloud.no-ip.org and that we’ll use the default location for storing our data. Furthermore, we’ll use mycloud as our server name.

During the installation of Seahub (the web frontend for the Seafile server) you must enter an admin email address and provide a password (this password is your Seafile admin password and should not be the same as your email account password).

All other question can be answered by using the default values (press ENTER).

./setup-seafile.sh

Your directory tree should now look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
tree . -L 2
.
├── ccnet # <--------------------------- configuration files
│   ├── ccnet.conf
│   ├── ccnet.conf.lan
│   ├── ccnet.conf.wan
│   ├── ccnet.db
│   ├── GroupMgr
│   ├── misc
│   ├── mykey.peer
│   ├── OrgMgr
│   ├── PeerMgr
│   └── seafile.ini
├── conf
│   └── seafdav.conf
├── installed
│   └── seafile-server_2.1.5_pi.tar.gz
├── logs
│   ├── ccnet.log
│   ├── controller.log
│   ├── http.log
│   ├── seafile.log
│   ├── seahub_django_request.log
│   └── seahub.log
├── seafile-server-2.1.5
│   ├── reset-admin.sh
│   ├── runtime
│   ├── seaf-fuse.sh
│   ├── seafile
│   ├── seafile.sh
│   ├── seahub
│   ├── seahub.sh
│   ├── setup-seafile-mysql.py
│   ├── setup-seafile-mysql.sh
│   ├── setup-seafile.sh
│   └── upgrade
├── seafile-server-latest -> seafile-server-2.1.5
├── seahub-data
│   └── avatars
├── seahub.db
├── seahub_settings.py
└── seahub_settings.pyc

Step 5 Update URLs for Seahub

nano /home/seafile/mycloud/ccnet/ccnet.conf

/home/seafile/mycloud/ccnet/ccnet.conf
1
SERVICE_URL = https://mycloud.no-ip.org:8001

Don’t forget replacing http with https

Also add a line to seahub_settings.py.

nano /home/seafile/mycloud/seahub_settings.py

/home/seafile/mycloud/seahub_settings.py
1
2
SECRET_KEY ...
HTTP_SERVER_ROOT = 'https://mycloud.no-ip.org:8001/seafhttp'

Step 6 (Re)start Seahub in FastCGI mode

/home/seafile/mycloud/seafile-server-latest/seahub.sh stop

/home/seafile/mycloud/seafile-server-latest/seahub.sh start-fastcgi

Step 7 Install nginx (as admin)

IMPORTANT: Do not run steps 7 to 11 as user seafile. Use the default pi user for admin stuff.

sudo aptitude install nginx

Patching nginx for Raspberry Pi:

1
2
3
sudo sed -i "s/worker_processes 4;/worker_processes 1;/g" /etc/nginx/nginx.conf
sudo sed -i "s/worker_connections 768;/worker_connections 128;/g" /etc/nginx/nginx.conf
sudo /etc/init.d/nginx start

Step 8 Create a self certified SSL certificate (as admin)

The following commands create a self certified SSL certificate. The second to last command is interactive and will ask a few questions. Provide Country Name (enter your two letter country code, i.e. DE for Germany, UK for United Kingdom) and Common Name. The later should be your DDNS name (mycloud.no-ip.org in this example).

1
2
3
4
5
sudo mkdir /etc/nginx/ssl
cd /etc/nginx/ssl
sudo openssl genrsa -out seahub.key 2048
sudo openssl req -new -key seahub.key -out seahub.csr
sudo openssl x509 -req -days 3650 -in seahub.csr -signkey seahub.key -out seahub.crt

Step 9 Create nginx Seahub site (as admin)

sudo nano /etc/nginx/sites-available/seahub

/etc/nginx/sites-available/seahub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
server {
    listen 8001; # <--------------------------------------- NGINX PORT
    ssl on; # <-------------------------------------------- SSL
    ssl_certificate /etc/nginx/ssl/seahub.crt; # <--------- SSL
    ssl_certificate_key /etc/nginx/ssl/seahub.key; # <----- SSL
    server_name mycloud.no-ip.org.tld; # <----------------- CHANGE THIS
    error_page 497  https://$host:$server_port$request_uri;

    location / {
        fastcgi_pass    127.0.0.1:8000;
        fastcgi_param   SCRIPT_FILENAME     $document_root$fastcgi_script_name;
        fastcgi_param   PATH_INFO           $fastcgi_script_name;

        fastcgi_param   SERVER_PROTOCOL $server_protocol;
        fastcgi_param   QUERY_STRING        $query_string;
        fastcgi_param   REQUEST_METHOD      $request_method;
        fastcgi_param   CONTENT_TYPE        $content_type;
        fastcgi_param   CONTENT_LENGTH      $content_length;
        fastcgi_param   SERVER_ADDR         $server_addr;
        fastcgi_param   SERVER_PORT         $server_port;
        fastcgi_param   SERVER_NAME         $server_name;
        fastcgi_param   HTTPS   on;
        fastcgi_param HTTP_SCHEME https;

        access_log      /var/log/nginx/seahub.access.log;
        error_log       /var/log/nginx/seahub.error.log;
    }
    location /seafhttp {
        rewrite ^/seafhttp(.*)$ $1 break;
        proxy_pass http://127.0.0.1:8082;
        client_max_body_size 0;
    }

    location /media {
        root /home/seafile/mycloud/seafile-server-latest/seahub; # <-- change: 2014-07-11
        # include /etc/nginx/mime.types; # <--- UNCOMMENT THIS IF CSS FILES AREN'T LOADED
    }
}

Step 10 Activate nginx Seahub site (as admin)

sudo ln -s /etc/nginx/sites-available/seahub /etc/nginx/sites-enabled/seahub

Step 11 Restart nginx (as admin)

sudo /etc/init.d/nginx restart

Step 12 Network: Setup port forwarding

Depending on your router, the naming might differ. Some routers call it “port mapping”, some call it “port forwarding”. For what we are doing, it’s all the same.

My ISP provided me with a combined dsl-modem/router called “Easybox 904 xDSL”, so YMMV:

Step 13 Test if everything works

LAN test using IP address

Test if Seafile/Seahub is reachable from within your LAN using the IP address of your Raspberry Pi. Assuming the Raspberry Pi has the internal IP address 192.168.1.42, entering https://192.168.1.42:8001 in your browser should render the Seahub page.

If this fails, go back and confirm that you don’t have any typos in your config files.

Internet test

Test if Seafile/Seahub is reachable from the internet. You must use a device which is not connected to your LAN. If you have a smartphone, you can deactivate your WiFi, and try connecting to your newly setup server at https://mycloud.no-ip.org:8001. The Seahub page should be rendered correctly in your ‘internet’ browser.

If this fails, go back and confirm that you don’t have any typos in your config files.

LAN test using DDNS

Try connecting to Seafile/Seahub from within the LAN using the DDNS name: https://mycloud.no-ip.org:8001.

If this works out of the box, you can skip the rest of this article.

Step 14

I should probably mention that I am not a trained network admin. So everything below falls under the category “works for my setup, sorry I can’t provide support”.

Here’s an image of what has to be accomplished:

Step 14a NAT loopback

If trying to reach the IP of your DDNS from within your LAN fails, you should try to setup your NAT loopback (see your router documentation for details).

Step 14b Workaround, in case NAT loopback doesn’t work

This is the tricky part. (Marko, thanks!)

BEWARE: This works with my setup, but it might ruin your setup.

  1. Rename internal LAN DNS zone to no-ip.org (replace with your domain)

  2. Rename internal LAN name (static DHCP lease) to mycloud (replace with your sub domain)

Link Collection #3

JavaScript stuff:

c# stuff:

  • This video by Roy Osherove shows how to introduce seams into brownfield projects (start at approx. 45min into the video if you want to skip the book recommendations and the intro to the SOLID principle). I learned how to introduce seams into static c# classes and methods using virtual…
  • C# psychology by Eric Lippert…
  • NHibernate turbo? (untested, but from the man himself: pimping NHibernate by Ayende)

Node.js Backend Providing REST

TL;DR

My AngularJS demo app has a new backend implementation using node.js.

After some reading I decided I’ll stick with node’s express module.

OK. Here is a ‘minimal’ setup for a node.js server:

app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
var express = require('express');
var app = express();

//CORS middleware
var allowCrossDomain = function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, X-XSRF-TOKEN');
  next();
};

app.use(allowCrossDomain);

/* we'll use the same port as tomcat... */
var MY_PORT = 8080; // default: 4730


/* REST API =========================================== */
var baseUrl = '/ngdemo/web';

/* GET ALL -------------------------------------------- */
app.get(baseUrl + '/users', function(req, res) {
  res.json(userRepository.getAll());
});

/* GET Dummy ------------------------------------------ */
app.get(baseUrl + '/dummy', function(req, res) {
  res.json({id: 0, firstName: 'JonFromREST', lastName: 'DoeFromREST'});
});

/* GET By Id ------------------------------------------ */
app.get(baseUrl + '/users/:id', function(req, res) {
  console.log('trying to retrieve user with id: ' + req.params.id);
  var user = userRepository.getById(req.params.id);
  res.json(user);
});


/* POST Create ---------------------------------------- */
app.post(baseUrl + '/users', function(req, res) {
  if(!req.body.hasOwnProperty('firstName') || !req.body.hasOwnProperty('lastName')) {
    res.statusCode = 400;
    return res.send('Error 400: POST syntax incorrect.');
  }

  var newUser = userRepository.addNewUser(req.body.firstName, req.body.lastName);
  res.json(newUser);
});

/* PUT (Update) --------------------------------------- */
app.put(baseUrl + '/users/:id', function (req, res) {
  if(!req.body.hasOwnProperty('id') || !req.body.hasOwnProperty('firstName') || !req.body.hasOwnProperty('lastName')) {
    res.statusCode = 400;
    return res.send('Error 400: PUT syntax incorrect.');
  }
  var changedUser = userRepository.changeUser(req.params.id, req.body.firstName, req.body.lastName);
  res.json(changedUser);
});

/* DELETE --------------------------------------------- */
app.delete(baseUrl + '/users/:id', function(req, res) {
  console.log('trying to delete user with id: ' + req.params.id);
  userRepository.deleteUser(req.params.id);
  res.json(true);
});

/* ==================================================== */

app.listen(process.env.PORT || MY_PORT);

/* Mmmhh... how can I place the code below into a seperate file and load it here? */

function User(id, firstName, lastName) {
  this.id = id;
  this.firstName = firstName;
  this.lastName = lastName;
};


function UserRepository() {

  this.users = [];

  this.createUsers = function() {
    var numberOfUsers = 10;
    for (var i = 0; i < numberOfUsers; i++) {
      var id = i + 1;
      this.users.push(new User(id, 'Foo' + id, 'Bar' + id));
    };
    return this.users;
  };

  this.getMaxUserId = function() {
    return Math.max.apply(Math, this.users.map(function(user) {
      return user.id;
    }));
  };

  this.getNumberOfUsers = function() {
    return this.users.length;
  };

  this.getAll = function() {
    return this.users;
  };

  this.getById = function(id) {
    var foundUser = false;
    for (var i = 0; i < this.users.length; i++) {
      var user = this.users[i];
      console.log('...checking user.id ' + user.id);
      if (user.id == id) {
        foundUser = true;
        return user;
      };
    };
    if (!foundUser) {
      console.log('Could not find user with id: ' + id);
      return 'user with id ' + id + ' not found.';
    };
  };

  this.addNewUser = function(firstName, lastName) {
    var newUser = new User(this.getMaxUserId() + 1, firstName, lastName);
    this.users.push(newUser);
    return this.getById(newUser.id);
  };

  this.changeUser = function(id, firstName, lastName) {
    var user = this.getById(id);
    user.firstName = firstName;
    user.lastName = lastName;
    return user;
  };

  this.deleteUser = function(id) {
    // sorry, i'm tired and don't know javascript that well...
    var indexToDelete = -1;
    for (var i = 0; i < this.users.length; i++) {
      var user = this.users[i];
      if (user.id == id) {
        indexToDelete = i;
        break;
      };
    };

    if (indexToDelete >= 0) {
      this.users.splice(indexToDelete, 1);
    };
  };
};

As you can see I am just dabbling with JS…

But hey: It works! ;–)

Source code for this post

You can clone a copy of this project here: https://github.com/draptik/angulardemorestful.

To checkout the correct version for this demo, use the following code:

1
2
3
git clone git@github.com:draptik/angulardemorestful.git
cd angulardemorestful
git checkout -f step6-nodejs-backend

In case you are not using git you can also download the project as ZIP or tar.gz file here: https://github.com/draptik/angulardemorestful/releases/tag/step6-nodejs-backend