It was InterSystems hackathon time and our team, consisting of Artem Viznyuk and me had Arduino board (one) and various parts of it (in overabundance). And so like that our course of action was set - like all other Arduino beginners, we decided to build a weather station. But with data persistent storage in Caché and visualization in DeepSee!

Work with devices


  InterSystems Caché can work directly with many types of physical and logical devices

As Arduino uses COM port for communication, we were all set.
Generally, work with devices could be divided into 5 steps:

  1. OPEN command to register device with current process and access it
  2. USE command to make it primary
  3. Do some actual work. READ to receive data from a device, and WRITE to send data
  4. USE again to switch primary device
  5. CLOSE command to free the device

So, that's theory, what's in practice?

Blink from Caché


Ferst, we built an Arduino device, which reads a number from COM port and powers the led for a specified number of milliseconds.

Circuit:
C code (for Arduino)
<span class="hljs-comment">/* Led.ino
 * Receive data on a COM port
 * Connect your led to ledPin
 */</span>

<span class="hljs-comment">// Pin, to connect your led</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> ledpin 8</span>

<span class="hljs-comment">// Received data buffer</span>
String inString = <span class="hljs-string">""</span>;

// Execute once at the beginning
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span> </span>{
	Serial.begin(<span class="hljs-number">9600</span>);
	pinMode(ledpin, OUTPUT);
	digitalWrite(ledpin, LOW);
}

// Execute indefinetly
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">loop</span><span class="hljs-params">()</span> </span>{
	<span class="hljs-comment">// Get data from com </span>
<span class="hljs-keyword"><code class="cpp hljs">	while</span> (Serial.available() > ) {
	int inChar = Serial.read();
	if (isDigit(inChar)) {
		// one character at a time
		// and append it to data buffer
		inString += (char)inChar;
	}

	// Encounter new line
	if (inChar == '\n') {
		// Power on the led
		digitalWrite(ledpin, HIGH);
		int time = inString.toInt();
		delay(time);
		digitalWrite(ledpin, LOW);
		// Flush the buffer
		inString = "";
	}
  }

} </code>


  And finally a Caché method, which sends 1000\n string to a com port:

<span class="hljs-comment">/// Send 1000\n to a com port</span>
<span class="hljs-keyword">ClassMethod</span> SendSerial()
{
	<span class="hljs-keyword">set</span> port = <span class="hljs-string">"COM1"</span>
	<span class="hljs-keyword">open</span> port:(:::<span class="hljs-string">" 0801n0"</span>:/BAUD=<span class="hljs-number">9600</span>)     <span class="hljs-comment">// Open device</span>
	<span class="hljs-keyword">set</span> old = <span class="hljs-built_in">$IO</span> <span class="hljs-comment">// Recorn current primary device</span>
	<span class="hljs-keyword">use</span> port  <span class="hljs-comment">// Switch to com port</span>
	<span class="hljs-keyword">write</span> <span class="hljs-built_in">$Char</span>(<span class="hljs-number">10</span>) <span class="hljs-comment">// Send some test data</span>
	<span class="hljs-keyword">hang</span> <span class="hljs-number">1</span>
	<span class="hljs-keyword">write</span> <span class="hljs-number">1000</span> _ <span class="hljs-built_in">$Char</span>(<span class="hljs-number">10</span>) <span class="hljs-comment">// Send 1000\n</span>
	<span class="hljs-keyword">use</span> old <span class="hljs-comment">// Back to old terminal</span>
	<span class="hljs-keyword">close</span> port <span class="hljs-comment">// Free the device</span>
}


«0801n0» is a string with parameters to access Com port, which is which is described in documentation. And /BAUD=9600 — is, of course, the connection speed.

If we execute  this method in a terminal:

<span class="hljs-selector-tag">do</span> #<span class="hljs-selector-id">#class</span>(<span class="hljs-selector-tag">Arduino</span><span class="hljs-selector-class">.Habr</span>)<span class="hljs-selector-class">.SendSerial</span>()


It would output nothing, but a led would flash for a second.

Receive data

Now let's attach keypad to Cache and receive entered data. This could be used as a custom user authentication with authentication delegation and ZAUTHENTICATE.mac routine.

Circuit:
C code
<span class="hljs-comment">/* Keypadtest.ino * 
 * Uses Keypad library,
 * Connect Keypad to Arduino pins 
 * as specified in rowPins[] and colPins[]. 
 * 
 */</span>

<span class="hljs-comment">// Repository:</span>
<span class="hljs-comment">// https://github.com/Chris--A/Keypad</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><Keypad.h></span></span>

<span class="hljs-keyword">const</span> byte ROWS = <span class="hljs-number">4</span>; <span class="hljs-comment">// Four rows</span>
<span class="hljs-keyword">const</span> byte COLS = <span class="hljs-number">4</span>; <span class="hljs-comment">// Three columns</span>
<span class="hljs-comment">// Map symbols to keys</span>
<span class="hljs-keyword">char</span> keys[ROWS][COLS] = {
	{<span class="hljs-string">'1'</span>,<span class="hljs-string">'2'</span>,<span class="hljs-string">'3'</span>,<span class="hljs-string">'A'</span>},
	{<span class="hljs-string">'4'</span>,<span class="hljs-string">'5'</span>,<span class="hljs-string">'6'</span>,<span class="hljs-string">'B'</span>},
	{<span class="hljs-string">'7'</span>,<span class="hljs-string">'8'</span>,<span class="hljs-string">'9'</span>,<span class="hljs-string">'C'</span>},
	{<span class="hljs-string">'*'</span>,<span class="hljs-string">'0'</span>,<span class="hljs-string">'#'</span>,<span class="hljs-string">'D'</span>}
};
<span class="hljs-comment">// Connect keypad pins 1-8 (up-down) to Arduino pins 11-4: 1->11, 2->10, ... , 8->4 </span>
<span class="hljs-comment">// Connect keypad ROW0, ROW1, ROW2 и ROW3 to this Arduino pins</span>
byte rowPins[ROWS] = { <span class="hljs-number">7</span>, <span class="hljs-number">6</span>, <span class="hljs-number">5</span>, <span class="hljs-number">4</span> };

<span class="hljs-comment">// Connect keypad COL0, COL1 and COL2 <code class="cpp hljs"><span class="hljs-comment">to this Arduino pins</span></span>
byte colPins[COLS] = { 8, 9, 10, 11 }; 

// Keypad initialization
Keypad kpd = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

void setup() {
	Serial.begin(9600); 
}

void loop() {
	char key = kpd.getKey(); // Receive key pressed
	if(key)
	{
		switch (key)
	{
		case '#':
			Serial.println();
		default:
			Serial.print(key);
	}
	}
}

</code>


And here is a Caché method used to get data from a com port, one line at a time:

<span class="hljs-comment">/// Receive one line till we encounter line terminator from COM1</span>
<span class="hljs-keyword">ClassMethod</span> ReceiveOneLine() <span class="hljs-keyword">As</span> <span class="hljs-built_in">%String</span>
{
	port = <span class="hljs-string">"COM1"</span>
	<span class="hljs-keyword">set</span> str=<span class="hljs-string">""</span>
	<span class="hljs-keyword">try</span> {
		<span class="hljs-keyword">open</span> port:(:::<span class="hljs-string">" 0801n0"</span>:/BAUD=<span class="hljs-number">9600</span>)
		<span class="hljs-keyword">set</span> old = <span class="hljs-built_in">$io</span>
		<span class="hljs-keyword">use</span> port
		<span class="hljs-keyword">read</span> str <span class="hljs-comment">// Read <code class="hljs cos"><span class="hljs-comment">till we encounter line terminator</span></span>
		use old
		close port
	} catch ex {
		close port
	}
	return str
}</code>


Execute in a terminal:

<span class="hljs-selector-tag">write</span> #<span class="hljs-selector-id">#class</span>(<span class="hljs-selector-tag">Arduino</span><span class="hljs-selector-class">.Habr</span>)<span class="hljs-selector-class">.ReceiveOneLine</span>()

And it would start waiting for input till # is pressed (which would be sent as a line terminator), after which entered data would be displayed in a terminal.

That was Arduino-Caché I/O basics, and now we're ready to build our own weather station.

Weather station

We're finally getting to the weather station! We used a photoresistor and a DHT11 Humidity &Temperature Sensor to gather data.

Circuit:

C code
<span class="hljs-comment">/* Meteo.ino * 
 * Register <code class="cpp hljs"><span class="hljs-comment">humidity</span>, temperature and light level 
 * And send them to a COM port 
 * Output sample: H=1.0;T=1.0;LL=1;
 */</span>

// Photoresistor pin (analog)
int lightPin = ;

// DHT-11 pin (digital)
int DHpin = 8; 

// Array to store DHT-11 temporary data
byte dat[5]; 

void setup() {
	Serial.begin(9600); 
	pinMode(DHpin,OUTPUT); 
}
 
void loop() {
	delay(1000); // measure everything once per second
	int lightLevel = analogRead(lightPin); //Get brightness level 

	temp_hum(); // Get temperature and humidity into dat variable
	// And output the result
	Serial.print("H="); 
	Serial.print(dat[], DEC);   
	Serial.print('.'); 
	Serial.print(dat[1],DEC);	
	Serial.print(";T="); 
	Serial.print(dat[2], DEC);	
	Serial.print('.'); 
	Serial.print(dat[3],DEC);	 
	Serial.print(";LL="); 
	Serial.print(lightLevel);
	Serial.println(";");
}

// Get DHT-11 data into dat
void temp_hum() { 
	digitalWrite(DHpin,LOW);
	delay(30);  
	digitalWrite(DHpin,HIGH); 
	delayMicroseconds(40);
	pinMode(DHpin,INPUT);
	while(digitalRead(DHpin) == HIGH);
	delayMicroseconds(80);
	if(digitalRead(DHpin) == LOW); 
	delayMicroseconds(80);
	for(int i=;i<4;i++)
	{
	  dat[i] = read_data();
	}
	pinMode(DHpin,OUTPUT);
	digitalWrite(DHpin,HIGH);
} 

// Get a chunk of data from DHT-11
byte read_data() {
	byte data; 
	for(int i=; i<8; i++) 
	{ 
		if(digitalRead(DHpin) == LOW) 
		{ 
			while(digitalRead(DHpin) == LOW); 
			delayMicroseconds(30);
			if(digitalRead(DHpin) == HIGH) 
			{
				data |= (1<<(7-i));
			}
			while(digitalRead(DHpin) == HIGH); 
		}
	} 
	return data; 
} </code>


After we had loaded this code into Arduino, it started sending data from COM port in the following format:

H=34.0;T=24.0;LL=605;


Where:


Let's store this data in Caché, for that we wrote a new Arduino.Info class:

 
<span class="hljs-keyword">Class</span> Arduino.Info <span class="hljs-keyword">Extends</span> <span class="hljs-built_in">%Persistent</span>
{

<span class="hljs-keyword">Parameter</span> SerialPort <span class="hljs-keyword">As</span> <span class="hljs-built_in">%String</span> = <span class="hljs-string">"com1"</span><span class="hljs-comment">;</span>

<span class="hljs-keyword">Property</span> DateTime <span class="hljs-keyword">As</span> <span class="hljs-built_in">%DateTime</span><span class="hljs-comment">;</span>

<span class="hljs-keyword">Property</span> Temperature <span class="hljs-keyword">As</span> <span class="hljs-built_in">%Double</span><span class="hljs-comment">;</span>

<span class="hljs-keyword">Property</span> Humidity <span class="hljs-keyword">As</span> <span class="hljs-built_in">%Double</span>(MAXVAL = <span class="hljs-number">100</span>, MINVAL = <span class="hljs-number">0</span>)<span class="hljs-comment">;</span>

<span class="hljs-keyword">Property</span> Brightness <span class="hljs-keyword">As</span> <span class="hljs-built_in">%Double</span>(MAXVAL = <span class="hljs-number">100</span>, MINVAL = <span class="hljs-number">0</span>)<span class="hljs-comment">;</span>

<span class="hljs-keyword">Property</span> Volume <span class="hljs-keyword">As</span> <span class="hljs-built_in">%Double</span>(MAXVAL = <span class="hljs-number">100</span>, MINVAL = <span class="hljs-number">0</span>)<span class="hljs-comment">;</span>

<span class="hljs-keyword">ClassMethod</span> AddNew(Temperature = <span class="hljs-number">0</span>, Humidity = <span class="hljs-number">0</span>, Brightness = <span class="hljs-number">0</span>, Volume = <span class="hljs-number">0</span>)
{
	<span class="hljs-keyword">set</span> obj = ..<span class="hljs-built_in">%New</span>()
	<span class="hljs-keyword">set</span> obj.DateTime=<span class="hljs-built_in">$ZDT</span>(<span class="hljs-built_in">$H</span>,<span class="hljs-number">3</span>,<span class="hljs-number">1</span>)
	<span class="hljs-keyword">set</span> obj.Temperature=Temperature
	<span class="hljs-keyword">set</span> obj.Humidity=Humidity
	<span class="hljs-keyword">set</span> obj.Brightness=Brightness/<span class="hljs-number">1023</span>*<span class="hljs-number">100</span>
	<span class="hljs-keyword">set</span> obj.Volume=Volume
	<span class="hljs-keyword">write</span> <span class="hljs-built_in">$SYSTEM</span>.Status.DisplayError(obj.<span class="hljs-built_in">%Save</span>())
}

After that then we wrote a method which would receive data from Arduino and transform them into Arduino.Info class objects

/// Receive a RAW data in this format: H=34.0;T=24.0;LL=605;\n
/// Convert into Arduino.Info objects
ClassMethod ReceiveSerial(port = {..#SerialPort})
{
    try {
        open port:(:::" 0801n0":/BAUD=9600)
        set old = $IO
        use port
        for {
            read x //read one line
            if (x '= "") {
                   set Humidity = $Piece($Piece(x,";",1),"=",2)
                set Temperature =  $Piece($Piece(x,";",2),"=",2)
                set Brightness =  $Piece($Piece(x,";",3),"=",2)
                do ..AddNew(Temperature,Humidity,Brightness) // Add data
            }
        }
    } catch anyError {
        close port
    }
}


And finally we connected Arduino  and executed ReceiveSerial method:

<span class="hljs-selector-tag">write</span> #<span class="hljs-selector-id">#class</span>(<span class="hljs-selector-tag">Arduino</span><span class="hljs-selector-class">.Info</span>)<span class="hljs-selector-class">.ReceiveSerial</span>()

This method would receive and store data from Arduino indefinitely

Data visualisation


After we built our device we set it outside to collect data for a night:

Come morning we got some 36000+ records and we decided to visualize them in DeepSee with MDX2JSON server-side REST API and DeepSeeWeb dashboard renderer, here are the results:


Brightness levels. Sunrise is clearly visible around 5:50:


Temperature and humidity graphs:



Negative correlation between humidity and temperature is clearly visible.

Demo

Available here.

Conclusion


With InterSystems Caché you can communicate with a large number of different devices directly. You can develop your solutions for data processing and visualization rapidly - it took our team around 4 hours to build our own weather station connect it to Caché and visualize the results and we mainly spend them designing the circuit and writing C code.


» Documentation
» GitHub repository