The art of Windows Phone

Menu

Netduino Temperature Logger with Universal Windows app

In this previous post I described how to discover Netduinos on your network without knowing their IP’s. Building on that, this post describes how to make a Netduino log temperature data, and then how to build a Universal Windows 10 app that displays that data.

Why

I’ve always been pretty fascinated by the Mpemba Effect, which states that hot water can freeze faster than cold water. At first glance you may think that means that hot water just decreases in temperature at a faster rate than cold water. However, as odd as it sounds, it really means that if you make ice cubes from hot and cold water, you’ll be sipping whiskey off of the hot-water ones first.

I wanted to test this myself, so I build this little Netduino temperature logger, hooked it up to my Xiaomi 10000mAh powerbank, and shoved it all in the deep-freeze. I was going to test by freezing 4 different jugs of water (each 800ml): boiling water, hot tap water, cold tap water, cold fridge water.

Long-story-short, it turns out that I suck at sciencing. I had assumed that I could just record the time it took each to get to zero degrees (real degrees, not your silly Fahrenheits). Turns out that when you put water in the freezer it will drop to ~0 degrees, and then sit there for a fair amount of time until it really freezes. That means that in pure graph-form it is impossible to tell when the water actually freezes. I did try stabbing the jug periodically with a knife to see if there was liquid water under the frozen surface – but decided that that would likely skew the results so much it wasn’t worth it.

Anyway, I only ended up doing boiling water and cold fridge water before giving up. Here is a graph comparing the boiling water and cold fridge water down to zero.The boiling water takes 195 minutes to get from 93° to 0°. That is 0.48° per minute.
The cold water takes 66 minutes to get from 9° to 0°. That is 0.14° per minute.

Netduino Hardware

The hardware setup is pretty minimal.

4.7k resistor

Netduino with network (I used a Netduino 3 WiFi)

DS18B20 waterproof temperature sensor

The DS18B20 waterproof temperature sensor is a really cheap, really useful piece of kit to have. You can get it for $10 from SparkFun, $3 from BangGood, or as low as $1 from AliExpress.(the latter two are if you’re willing to wait a couple weeks for shipping from China).

The temperature sensor is powered through GND and 5V direct from the Netduino, and the signal wire (white or yellow usually) goes to Digital pin 0. Finally there is a 4.7k resistor between 5V and signal as a pull-up.

And here is a photo of the real setup.

Netduino Code

In this example, you’ll notice that the sensor data is never persisted to an SD card (or other storage) – that is for simplicity, plus isn’t needed for now. We keep all the values in memory, and return those to whatever client app requests them. This does mean that if you lose power, you’ll lose the current values too. But for short tests like this it is fine. In terms of running out of memory – you should be good for a week or so of data, which is way more than we need here.

The temperature sensor is a OneWire sensor, so we need a couple helper classes to read from it. Create a folder called Sensor, and add:

DS18B20.cs

C#

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

usingSystem;

usingMicrosoft.SPOT.Hardware;

namespaceTemperatureLogger.Netduino.Sensor

{

publicclassDS18B20

{

staticOneWire _wire;

publicDS18B20(Cpu.Pin sensorPin)

{

_wire=newOneWire(newOutputPort(sensorPin,false));

}

publicdoubleGetTemperature()

{

if(_wire.TouchReset()<=0)thrownewException("No device");

_wire.WriteByte(0xCC);// Skip ROM, we only have one device

_wire.WriteByte(0x44);// Start temperature conversion

while(_wire.ReadByte()==0){}// wait while busy

_wire.TouchReset();

_wire.WriteByte(0xCC);// skip ROM

_wire.WriteByte(0xBE);// Read Scratchpad

varlow=(byte)_wire.ReadByte();

varhigh=(byte)_wire.ReadByte();

return((short)((high<<8)|low))/16F;

}

}

}

And OneWireBus.cs

C#

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

// From https://www.ghielectronics.com/community/codeshare/entry/438

usingSystem.Collections;

usingMicrosoft.SPOT.Hardware;

namespaceThreelnDotOrg.NETMF.Hardware

{

publicstaticclassOneWireBus

{

publicclassDevice

{

publicbyte[]Address{get;privateset;}

publicFamilyFamily{get;privateset;}

internalDevice(byte[]addr)

{

Address=addr;

switch(addr[0])

{

case0x10:

Family=OneWireBus.Family.DS18S20;

break;

case0x28:

Family=OneWireBus.Family.DS18B20;

break;

default:

Family=OneWireBus.Family.Unknown;

break;

}

}

}

publicenumFamily:byte

{

Unknown=0x00,

DS18S20=0x10,

DS18B20=0x28,

}

publicstaticDevice[]Scan(OneWire ow,paramsFamily[]includeFamilies)

{

varlist=newArrayList();

varall=false;

vardevs=ow.FindAllDevices();

if(includeFamilies!=null)

{

foreach(varfinincludeFamilies)

{

if(f==Family.Unknown)

all=true;

}

}

foreach(byte[]da indevs)

{

if(includeFamilies==null||includeFamilies.Length==0||all)

{

list.Add(newDevice(da));

}

else

{

foreach(varfinincludeFamilies)

{

if(da[0]==(byte)f)

list.Add(newDevice(da));

}

}

}

return(Device[])list.ToArray(typeof(Device));

}

publicstaticDevice[]Scan(Cpu.Pin pin,paramsFamily[]includeFamilies)

{

using(varop=newOutputPort(pin,false))

returnScan(newOneWire(op),includeFamilies);

}

}

}

Next we need a small class to store our sensor readings. Create SensorReading.cs.

And then the rest of the code is setting up a TCP server on port 80 to listen for requests from the app. When a request comes in it loops over the data and returns it.

Notice that we’re able to have the UDP network discovery server plus the TCP server running at the same time, both on port 80. You can also change the port to something else if you’d prefer.

Universal Windows 10 App

To keep the Windows app nice and clean, we’ll use the MVVM pattern – which you should be doing in pretty much every app you make. Specifically, I like using MVVMLight.
For the graph, we’ll use the one in the WinRT XAML Toolkit.

Before we add code to download the data from the device, we need a class to store it in. Create SensorReading.cs and add the following code (in my case I put it in a folder called Model).

C#

1

2

3

4

5

6

7

8

namespaceTemperatureLogger.Windows.Model

{

publicclassSensorReading

{

publicintMinutesSinceStart{get;set;}

publicdoubleTemperature{get;set;}

}

}

The MainViewModel.cs is more complicated, but I’ve put lots of comments to explain what is going on. Essentially, the user hits the Refresh button in the UI, which then goes and discovers devices and picks the first one, then it downloads the data from the first device and converts it from text into proper types, then sets that into an ObservableCollection which the XAML chart is bound to.

The exporting code just takes the data and converts it to a comma separated value string and saves to a file that the user selects.

C#

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

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

usingSystem;

usingSystem.Collections.Generic;

usingSystem.Collections.ObjectModel;

usingSystem.Diagnostics;

usingSystem.IO;

usingSystem.Linq;

usingSystem.Net.Http;

usingSystem.Text;

usingSystem.Threading.Tasks;

usingWindows.Networking;

usingWindows.Networking.Sockets;

usingWindows.Storage.Pickers;

usingWindows.Storage.Streams;

usingWindows.UI.Popups;

usingGalaSoft.MvvmLight;

usingGalaSoft.MvvmLight.Command;

usingTemperatureLogger.Windows.Model;

namespaceTemperatureLogger.Windows.ViewModel

{

publicclassMainViewModel:ViewModelBase

{

bool_isLoading;

/// <summary>

/// Used to keep track of whether we're loading or not, and change the UI appropriately.

/// </summary>

publicboolIsLoading

{

get{return_isLoading;}

set{Set(ref_isLoading,value);}

}

RelayCommand _refreshDevicesCommand;

/// <summary>

/// Command to discover devices, then load sensor data from the first one.

// Pop up a file picker so the user can select where they'd like to save the file.

varfileSavePicker=newFileSavePicker{SuggestedFileName=filename};

fileSavePicker.FileTypeChoices.Add("CSV",new[]{".csv"});

varfile=awaitfileSavePicker.PickSaveFileAsync();

if(file!=null)

{

using(varstream=awaitfile.OpenStreamForWriteAsync())

{

// Write the text to file.

varbytes=Encoding.UTF8.GetBytes(csv.ToCharArray());

stream.Write(bytes,0,bytes.Length);

}

await(newMessageDialog($"Exported to {file.DisplayName} in Documents")).ShowAsync();

}

SetIsLoading(false);

}

/// <summary>

/// Helper method to set the loading state.

/// </summary>

voidSetIsLoading(boolisLoading)

{

IsLoading=isLoading;

// We do this to enable and disable the UI buttons appropriately.

RefreshDevicesCommand.RaiseCanExecuteChanged();

ExportReadingsCommand.RaiseCanExecuteChanged();

}

}

}

Battery Life

I was pleasantly surprised at the battery usage of this. Without doing anything special to save battery, it lasted about 3 days before the 10,000 mAh battery got down to 50% (I didn’t test longer than that).

Freezing Stuff / Disclaimer

When I was doing my tests I put the Netduino and powerbank together in a zip-lock bag with a small gap for the temperature sensor cable to come out. This probably isn’t a good idea. Although technically most of these components are able to handle below zero temperatures, they’ll get tons of moisture on them when they start heating up and condensation forms. I also think it is probably pretty bad for the batteries.