Tuesday, July 15, 2014

C# (Mono) Code to Read an TMP102 I2C Temperature Sensor on a Raspberry Pi

Mono is an open source implementation of the Microsoft .NET framework that allows you to compile and run .NET programs on non-Windows platforms.  That includes the Raspberry Pi.  It actually proved to be quite easy to install Mono (v. 3.2.8) on the RPi and get some C# code running.  This example shows how to call the i2cget utility from a C# program to read the temperature from a TMP102 and print the results in degrees Celsius and Fahrenheit in the terminal window.

To install Mono, run the following apt-get command as root:

apt-get install mono-complete

The will get you the complete Mono package.

For this example, I am using the TMP102 on a breakout board from Sparkfun.

Connect the SDA and SCL pins between the TMP102 and the RPi.  Connect VCC to 3.3V and connect the ground.  With the solder jumper on the TMP102 in place (by default) the I2C device address is 0x48.

The i2cget utility is included with the i2c-tools package. If you don't already have it installed, you can run the following command as root to install it -

apt-get install i2c-tools 

C# Code


Use your favorite text editor to save this code to a file called Tmp102.cs.

using System;
using System.Diagnostics;
using System.Threading;

public class Tmp102
{
    private string i2cgetExe = "/usr/sbin/i2cget";
    private string i2cgetCmdArgs = "-y 1 0x48 0 w";
    private string hexString = "";
    private Process p;

    public Tmp102()
    {
        p = new Process();
    }

    public double tempC
    {
        get { return readRawTempData() * 0.0625; }
    }
    public double tempF
    {
        get { return this.tempC * 1.8 + 32; }
    }

    private int readRawTempData()
    {
        // Don't raise event when process exits
        p.EnableRaisingEvents = false;
        // We're using an executable not document, so UseShellExecute false
        p.StartInfo.UseShellExecute = false;
        // Redirect StandardError
        p.StartInfo.RedirectStandardError = true;
        // Redirect StandardOutput so we can capture it
        p.StartInfo.RedirectStandardOutput = true;
        // i2cgetExe has full path to executable
        // Need full path because UseShellExecute is false

        p.StartInfo.FileName = i2cgetExe;
        // Pass arguments as a single string
        p.StartInfo.Arguments = i2cgetCmdArgs;
        // Now run i2cget & wait for it to finish
        p.Start();
        p.WaitForExit();
        // Data returned in format 0xa017
        // Last 2 digits are actually most significant byte (MSB)
        // 2 digits right after 0x are really least significant byte (LSB)
        string data = p.StandardOutput.ReadToEnd();
        // Get LSB & parse as integer
        hexString = data.Substring(2, 2);
        int lsb = Int32.Parse(hexString,
            System.Globalization.NumberStyles.AllowHexSpecifier);
        // Get MSB & parse as integer
        hexString = data.Substring(4, 2);
        int msb = Int32.Parse(hexString,
            System.Globalization.NumberStyles.AllowHexSpecifier);
        // Shift bits as indicated in TMP102 docs & return
        return (((msb << 8) | lsb) >> 4);
    }

    public static void Main()
    {
        Tmp102 t = new Tmp102();
        while(true)
        {
            // Print temp in degrees C and F to console
            Console.WriteLine("{0}°C  {1}°F", t.tempC , t.tempF);
            Thread.Sleep(1000);
        }
    }
}

Compiling & Running the Code


To compile a Mono executable, run the following command:

gmcs Tmp102.cs

Then execute the program by running 

mono Tmp102.exe

Press Ctrl-C to terminate the program.

Saturday, July 12, 2014

Displaying Current Temperature on a Web Page Using a TMP102 & PHP on a Raspberry Pi

Here is an example of using PHP to read the current temperature from a TMP102 I2C temperature sensor and display it (in degrees Fahrenheit) in a Web page.

Prerequisites


Make sure that you have installed the i2c-tools package because this package provides i2cget to read the registers on the TMP102.  The following apt-get command will install it for you:


sudo apt-get install i2c-tools

Install the Apache2 Web server:

apt-get install apache2

Then install PHP5:

apt-get install php5

Granting Access to I2C System Device


By default, and for the sake of security, the account used to run the Apache2 Web server doesn't have permission to access the /dev/i2c-1 device.  To fix this, edit the udev configuration file that controls the file permissions for /dev/i2c-1.

Use nano or your favorite text editor to edit /lib/udev/rules.d/60-i2c-tools.rule. Replace the contents of the file with these two lines.  

KERNEL=="i2c-0", GROUP="i2c", MODE="0660"
KERNEL=="i2c-1", GROUP="i2c", MODE="0666"

This leaves the i2c-0 device with the default permissions and changes the access to i2c-1 to allow a non-privileged user to read the device.

PHP Code


Here is a simple PHP page that reads the temperature data using i2cget, converts it to degrees Fahrenheit and displays it.  Put this code in /var/www/temp.php -

<?php
    $output = `/usr/sbin/i2cget -y 1 0x48 0 w`;
    $msb = hexdec(substr($output, 4, 2));
    $lsb = hexdec(substr($output, 2, 2));
    $tempF = ((($msb << 8) | $lsb) >> 4) * 0.0625 * 1.8 + 32;
    echo "Temp: $tempF&deg;F";
?>

The backticks (` `) tell PHP to run the enclosed command in a (Linux) command shell.  The backtick is on the key to the left of the 1 key near the top of the keyboard (on the same key as the ~).

In the command i2cget -y 1 0x48 0 w

  • -y cancels the user prompt that asks if you really want to read from the device
  • 1 actually refers to the 2nd I2C bus, /dev/i2c-1
  • 0x48 is the default I2C device address for the TMP102
  • 0 is the register with the temperature data
  • w means we want to read a word (rather than just a byte).

Note that when i2cget reads the data, the byte order is reversed.  The first byte is actually the least significant byte (LSB) and the second byte is the most significant byte (MSB).  The string returned by i2cget is something like 0xb01d.  The code uses substr() to pull out the two digits for each byte, and then hexdec() to convert a string representing a hexadecimal value to a decimal numeric value.

From a computer on the same network, access the URL for the page (where the IP address will vary):

http://192.168.0.108/temp.php



Sample Java Code to Read Temperature from an TMP102 via I2C & Save Data to a MySQL Database Table

The Java code below reads current temperature data from the TMP102 temperature sensor using the i2cget command line utility to read the TMP102's registers.  The reading is taken every second and the result is saved to a MySQL database table.  The table contains a column for the date and time and a column for the temperature in degrees Fahrenheit.  The code below should work with Java 6, 7, or 8.  You will need to have the complete JDK installed to compile the code.

Prerequisites


The i2cget command line utility is part of the I2C Tools package.  You can install this package on the Raspberry Pi using the following apt-get command:

apt-get install i2c-tools

To install MySQL, use the following command and follow the prompts:

apt-get install mysql-server

The following command installs the Java database (JDBC) driver that allows your code to access the database:

apt-get install java-mysqldb


Database Structure


CREATE TABLE tmp102 (
  date_time        datetime NOT NULL,
  temp_f            double,
  PRIMARY KEY (date_time)
);

Java Code

import java.io.*;
import java.util.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.PreparedStatement;

public class Tmp102 {
    // i2cget command to read raw data as a word (16-bit value)
// from TMP102 sensor.  -y tells command to run 
    // without confirmation from user.
    // I2C address for TMP102 by default is 0x48.
    // Command is passed a separate String, unlike loadPinMapCmd
    // that has to be passed as an array of Strings.
    String runI2CGetCmd = "i2cget -y 1 0x48 0 w";

    Connection conn = null;
    PreparedStatement stmt = null;
    // Change gpsdb to name of your DB. Also change
    // DB user name & password
    String url = "jdbc:mysql://localhost/temperature?" +
        "user=YourDBUser&password=YourDBPassword";

    String insertSQL = "INSERT INTO tmp102(date_time, temp_f) " +
        "VALUES (now(), ?)";

    public Tmp102() {
        // Constructor sets up DB connection
        try {
            Class.forName("com.mysql.jdbc.Driver").newInstance();
            conn = DriverManager.getConnection(url);
        }
        catch(Exception ex) { ex.printStackTrace(); }
    }

    // Read temperatures from both sensors & print it out to terminal
    public void readTemperature() {
        short tempData = 0;
        double temp = 0.00;
        tempData = runI2CGet(runI2CGetCmd);
        tempData = reverseBytes(tempData);
        temp = convertToDegreesF(tempData);
        try {
            stmt = conn.prepareStatement(insertSQL);
            stmt.setDouble(1, temp);
            stmt.execute();
            System.out.printf("%.3f\n", temp);
        }
        catch(Exception ex) { ex.printStackTrace(); }
    }

    // Need to reverse bytes in value returned by i2cget command
    private short reverseBytes(short tempData) {
        int b1 = (byte)  tempData & 0xFF;
        int b2 = (byte) (tempData >> 8) & 0xFF;
        return (short) (b1 << 8 | b2);
    }

    // Convert raw temperature data to degrees Fahrenheit
    private double convertToDegreesF(short tempData) {
        return (tempData >> 4) * 0.0625 * 1.8 + 32;
    }

    private short runI2CGet(String cmd) {
        String line = "";
        short data = 0;
        try {
            Process p = Runtime.getRuntime().exec(cmd);
            p.waitFor();
            // Use Scanner to read results from i2cget command
            Scanner scan = new Scanner(
                new InputStreamReader(p.getInputStream()));
            // Shell command returns hex number as string like 0x6017.
            // Need to use substring to skip over 0x for parsing.
            // Using Short.parseShort() sometimes throws NumberFormatExceptions.
            if(scan.hasNext()) {
                data = (short) Integer.parseInt((scan.nextLine())
                .substring(2), 16);
            }
        }
        catch(Exception ex) { ex.printStackTrace(); }
        return data;
    }

    private void closeDBConn() {
        try {
            stmt.close();
            conn.close();
        } catch(Exception exc){ exc.printStackTrace(); }
    }

    public static void main(String[] args) {
        Tmp102 temp = new Tmp102();
        try {
            while(true) {
                temp.readTemperature();
                Thread.sleep(1000);
            }
        }
        catch(Exception ex) { ex.printStackTrace(); }
        finally { temp.closeDBConn(); }
    }
}

Compiling & Running the Program

javac -cp /opt/mysql-connector-java-5.1.31/mysql-connector-java-5.1.31-bin.jar Tmp102.java

Adjust the -cp path for the MySQL-Connector .jar file to match your installation, if needed.

The following command runs the program.  The temperature readings are printed to the terminal, while the current timestamp and temperature are saved in the database. 

java -cp .:/opt/mysql-connector-java-5.1.31/mysql-connector-java-5.1.31-bin.jar Tmp102

Again, adjust the -cp path to match the location of your MySQL-Connector .jar file.

Stop the program by pressing Ctrl-C.


Saturday, July 5, 2014

Note about External Connections to a MySQL Instance Running on the Raspberry Pi

If you want to connect to a MySQL DB server instance running on a Raspberry Pi, a couple bits of additional configuration are required.  

On the Raspberry Pi, use nano or another text editor to edit the /etc/mysql/my.conf file.  Under the [mysqld] section, edit the bind-address to match the RPi's IP address, if you wish to restrict access to one IP interface's address or set it to 0.0.0.0 to all access using the IP address for any available network interface.

To allow remote access via the MySQL root login, (re)run the mysql_secure_installation script and answer the relevant question about remote access for root appropriately.  The following MySQL command (issued at the MySQL command line) has the same effect.  Change the password as appropriate.

 grant all privileges on *.* to 'root'@'%' identified by 'MyRootPassword' with grant option;