We will show a possibility to add data to an NMEA stream.
We will use the NMEA Console project as a starting point.
ocss.nmea.parser.StringGenerator.java
, along with their reciprocal functions in ocss.nmea.parser.StringParsers.java
.
NMEAListener
and NMEAReaderListener
, one is used to parse the incoming data and put them in the cache, the other to trigger the possible re-broadcasting (which we are specially interested in).
olivsoftdesktopuserexits.Sensor2NMEA
, that implements the DesktopUserExitInterface
interface.
olivsoftdesktopuserexits.rpisensor.AdafruitBMP180Reader
, that reads the BMP180 sensor.Sensor2NMEA.java
1 package olivsoftdesktopuserexits; 2 3 import olivsoftdesktop.DesktopUserExitInterface; 4 5 import olivsoftdesktopuserexits.rpisensor.AdafruitBMP180Reader; 6 7 /** 8 * Reads a sensor (BMP180) connected to the Raspberry PI 9 * and turn the data into NMEA string to broadcast them. 10 * 11 * to be used with -ue:olivsoftdesktopuserexits.Sensor2NMEA 12 */ 13 public class Sensor2NMEA 14 implements DesktopUserExitInterface 15 { 16 private AdafruitBMP180Reader sensorReader = null; 17 18 public Sensor2NMEA() 19 { 20 super(); 21 sensorReader = new AdafruitBMP180Reader(); 22 } 23 24 @Override 25 public void start() 26 { 27 System.out.println("Method 'start':" + this.getClass().getName() + " User exit is starting..."); 28 // Start reading the sensor and broadcasting NMEA data 29 Thread ue = new Thread() 30 { 31 public void run() 32 { 33 sensorReader.startReading(); 34 } 35 }; 36 ue.start(); 37 } 38 39 @Override 40 public void stop() 41 { 42 System.out.println(this.getClass().getName() + " is terminating"); 43 sensorReader.stopReading(); 44 } 45 46 @Override 47 public void describe() 48 { 49 System.out.println("Designed to run on the Raspberry PI."); 50 System.out.println("Reads the BMP180 sensor, and turns it Air Temperature and Barometric Pressure data into MTA & MMB NMEA Strings."); 51 System.out.println("Those data will be treated as if they were coming from the NMEA Station."); 52 } 53 }
AdafruitBMP180Reader
1 package olivsoftdesktopuserexits.rpisensor; 2 3 4 import com.pi4j.io.i2c.I2CBus; 5 import com.pi4j.io.i2c.I2CDevice; 6 import com.pi4j.io.i2c.I2CFactory; 7 8 import java.io.IOException; 9 10 import nmea.event.NMEAReaderListener; 11 12 import nmea.server.ctx.NMEAContext; 13 14 import ocss.nmea.api.NMEAEvent; 15 import ocss.nmea.api.NMEAListener; 16 import ocss.nmea.parser.StringGenerator; 17 18 /* 19 * Altitude, Pressure, Temperature 20 */ 21 public class AdafruitBMP180Reader 22 { 23 // Minimal constants carried over from Arduino library 24 /* 25 Prompt> sudo i2cdetect -y 1 26 0 1 2 3 4 5 6 7 8 9 a b c d e f 27 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 28 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 29 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 31 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 32 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 33 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 34 70: -- -- -- -- -- -- -- 77 35 */ 36 // Those 2 next addresses are returned by "sudo i2cdetect -y 1", see above. 37 public final static int BMP180_ADDRESS = 0x77; 38 // Operating Modes 39 public final static int BMP180_ULTRALOWPOWER = 0; 40 public final static int BMP180_STANDARD = 1; 41 public final static int BMP180_HIGHRES = 2; 42 public final static int BMP180_ULTRAHIGHRES = 3; 43 44 // BMP085 Registers 45 public final static int BMP180_CAL_AC1 = 0xAA; // R Calibration data (16 bits) 46 public final static int BMP180_CAL_AC2 = 0xAC; // R Calibration data (16 bits) 47 public final static int BMP180_CAL_AC3 = 0xAE; // R Calibration data (16 bits) 48 public final static int BMP180_CAL_AC4 = 0xB0; // R Calibration data (16 bits) 49 public final static int BMP180_CAL_AC5 = 0xB2; // R Calibration data (16 bits) 50 public final static int BMP180_CAL_AC6 = 0xB4; // R Calibration data (16 bits) 51 public final static int BMP180_CAL_B1 = 0xB6; // R Calibration data (16 bits) 52 public final static int BMP180_CAL_B2 = 0xB8; // R Calibration data (16 bits) 53 public final static int BMP180_CAL_MB = 0xBA; // R Calibration data (16 bits) 54 public final static int BMP180_CAL_MC = 0xBC; // R Calibration data (16 bits) 55 public final static int BMP180_CAL_MD = 0xBE; // R Calibration data (16 bits) 56 public final static int BMP180_CONTROL = 0xF4; 57 public final static int BMP180_TEMPDATA = 0xF6; 58 public final static int BMP180_PRESSUREDATA = 0xF6; 59 public final static int BMP180_READTEMPCMD = 0x2E; 60 public final static int BMP180_READPRESSURECMD = 0x34; 61 62 private int cal_AC1 = 0; 63 private int cal_AC2 = 0; 64 private int cal_AC3 = 0; 65 private int cal_AC4 = 0; 66 private int cal_AC5 = 0; 67 private int cal_AC6 = 0; 68 private int cal_B1 = 0; 69 private int cal_B2 = 0; 70 private int cal_MB = 0; 71 private int cal_MC = 0; 72 private int cal_MD = 0; 73 74 private static boolean verbose = false; 75 76 private I2CBus bus; 77 private I2CDevice bmp180; 78 private int mode = BMP180_STANDARD; 79 80 public AdafruitBMP180Reader() 81 { 82 this(BMP180_ADDRESS); 83 } 84 85 public AdafruitBMP180Reader(int address) 86 { 87 try 88 { 89 // Get i2c bus 90 bus = I2CFactory.getInstance(I2CBus.BUS_1); // Depends onthe RasPI version 91 if (verbose) 92 System.out.println("Connected to bus. OK."); 93 94 // Get device itself 95 bmp180 = bus.getDevice(address); 96 if (verbose) 97 System.out.println("Connected to device. OK."); 98 99 try { this.readCalibrationData(); } 100 catch (Exception ex) 101 { ex.printStackTrace(); } 102 } 103 catch (IOException e) 104 { 105 System.err.println(e.getMessage()); 106 } 107 } 108 109 private int readU8(int reg) throws Exception 110 { 111 // "Read an unsigned byte from the I2C device" 112 int result = 0; 113 try 114 { 115 result = this.bmp180.read(reg); 116 if (verbose) 117 System.out.println("I2C: Device " + BMP180_ADDRESS + " returned " + result + " from reg " + reg); 118 } 119 catch (Exception ex) 120 { ex.printStackTrace(); } 121 return result; 122 } 123 124 private int readS8(int reg) throws Exception 125 { 126 // "Reads a signed byte from the I2C device" 127 int result = 0; 128 try 129 { 130 result = this.bmp180.read(reg); 131 if (result > 127) 132 result -= 256; 133 if (verbose) 134 System.out.println("I2C: Device " + BMP180_ADDRESS + " returned " + result + " from reg " + reg); 135 } 136 catch (Exception ex) 137 { ex.printStackTrace(); } 138 return result; 139 } 140 141 private int readU16(int register) throws Exception 142 { 143 int hi = this.readU8(register); 144 int lo = this.readU8(register + 1); 145 return (hi << 8) + lo; 146 } 147 148 private int readS16(int register) throws Exception 149 { 150 int hi = this.readS8(register); 151 int lo = this.readU8(register + 1); 152 return (hi << 8) + lo; 153 } 154 155 public void readCalibrationData() throws Exception 156 { 157 // "Reads the calibration data from the IC" 158 cal_AC1 = readS16(BMP180_CAL_AC1); // INT16 159 cal_AC2 = readS16(BMP180_CAL_AC2); // INT16 160 cal_AC3 = readS16(BMP180_CAL_AC3); // INT16 161 cal_AC4 = readU16(BMP180_CAL_AC4); // UINT16 162 cal_AC5 = readU16(BMP180_CAL_AC5); // UINT16 163 cal_AC6 = readU16(BMP180_CAL_AC6); // UINT16 164 cal_B1 = readS16(BMP180_CAL_B1); // INT16 165 cal_B2 = readS16(BMP180_CAL_B2); // INT16 166 cal_MB = readS16(BMP180_CAL_MB); // INT16 167 cal_MC = readS16(BMP180_CAL_MC); // INT16 168 cal_MD = readS16(BMP180_CAL_MD); // INT16 169 if (verbose) 170 showCalibrationData(); 171 } 172 173 private void showCalibrationData() 174 { 175 // "Displays the calibration values for debugging purposes" 176 System.out.println("DBG: AC1 = " + cal_AC1); 177 System.out.println("DBG: AC2 = " + cal_AC2); 178 System.out.println("DBG: AC3 = " + cal_AC3); 179 System.out.println("DBG: AC4 = " + cal_AC4); 180 System.out.println("DBG: AC5 = " + cal_AC5); 181 System.out.println("DBG: AC6 = " + cal_AC6); 182 System.out.println("DBG: B1 = " + cal_B1); 183 System.out.println("DBG: B2 = " + cal_B2); 184 System.out.println("DBG: MB = " + cal_MB); 185 System.out.println("DBG: MC = " + cal_MC); 186 System.out.println("DBG: MD = " + cal_MD); 187 } 188 189 public int readRawTemp() throws Exception 190 { 191 // "Reads the raw (uncompensated) temperature from the sensor" 192 bmp180.write(BMP180_CONTROL, (byte)BMP180_READTEMPCMD); 193 waitfor(5); // Wait 5ms 194 int raw = readU16(BMP180_TEMPDATA); 195 if (verbose) 196 System.out.println("DBG: Raw Temp: " + (raw & 0xFFFF) + ", " + raw); 197 return raw; 198 } 199 200 public int readRawPressure() throws Exception 201 { 202 // "Reads the raw (uncompensated) pressure level from the sensor" 203 bmp180.write(BMP180_CONTROL, (byte)(BMP180_READPRESSURECMD + (this.mode << 6))); 204 if (this.mode == BMP180_ULTRALOWPOWER) 205 waitfor(5); 206 else if (this.mode == BMP180_HIGHRES) 207 waitfor(14); 208 else if (this.mode == BMP180_ULTRAHIGHRES) 209 waitfor(26); 210 else 211 waitfor(8); 212 int msb = bmp180.read(BMP180_PRESSUREDATA); 213 int lsb = bmp180.read(BMP180_PRESSUREDATA + 1); 214 int xlsb = bmp180.read(BMP180_PRESSUREDATA + 2); 215 int raw = ((msb << 16) + (lsb << 8) + xlsb) >> (8 - this.mode); 216 if (verbose) 217 System.out.println("DBG: Raw Pressure: " + (raw & 0xFFFF) + ", " + raw); 218 return raw; 219 } 220 221 public float readTemperature() throws Exception 222 { 223 // "Gets the compensated temperature in degrees celcius" 224 int UT = 0; 225 int X1 = 0; 226 int X2 = 0; 227 int B5 = 0; 228 float temp = 0.0f; 229 230 // Read raw temp before aligning it with the calibration values 231 UT = this.readRawTemp(); 232 X1 = ((UT - this.cal_AC6) * this.cal_AC5) >> 15; 233 X2 = (this.cal_MC << 11) / (X1 + this.cal_MD); 234 B5 = X1 + X2; 235 temp = ((B5 + 8) >> 4) / 10.0f; 236 if (verbose) 237 System.out.println("DBG: Calibrated temperature = " + temp + " C"); 238 return temp; 239 } 240 241 public float readPressure() throws Exception 242 { 243 // "Gets the compensated pressure in pascal" 244 int UT = 0; 245 int UP = 0; 246 int B3 = 0; 247 int B5 = 0; 248 int B6 = 0; 249 int X1 = 0; 250 int X2 = 0; 251 int X3 = 0; 252 int p = 0; 253 int B4 = 0; 254 int B7 = 0; 255 256 UT = this.readRawTemp(); 257 UP = this.readRawPressure(); 258 259 // You can use the datasheet values to test the conversion results 260 // boolean dsValues = true; 261 boolean dsValues = false; 262 263 if (dsValues) 264 { 265 UT = 27898; 266 UP = 23843; 267 this.cal_AC6 = 23153; 268 this.cal_AC5 = 32757; 269 this.cal_MB = -32768; 270 this.cal_MC = -8711; 271 this.cal_MD = 2868; 272 this.cal_B1 = 6190; 273 this.cal_B2 = 4; 274 this.cal_AC3 = -14383; 275 this.cal_AC2 = -72; 276 this.cal_AC1 = 408; 277 this.cal_AC4 = 32741; 278 this.mode = BMP180_ULTRALOWPOWER; 279 if (verbose) 280 this.showCalibrationData(); 281 } 282 // True Temperature Calculations 283 X1 = (int)((UT - this.cal_AC6) * this.cal_AC5) >> 15; 284 X2 = (this.cal_MC << 11) / (X1 + this.cal_MD); 285 B5 = X1 + X2; 286 if (verbose) 287 { 288 System.out.println("DBG: X1 = " + X1); 289 System.out.println("DBG: X2 = " + X2); 290 System.out.println("DBG: B5 = " + B5); 291 System.out.println("DBG: True Temperature = " + (((B5 + 8) >> 4) / 10.0) + " C"); 292 } 293 // Pressure Calculations 294 B6 = B5 - 4000; 295 X1 = (this.cal_B2 * (B6 * B6) >> 12) >> 11; 296 X2 = (this.cal_AC2 * B6) >> 11; 297 X3 = X1 + X2; 298 B3 = (((this.cal_AC1 * 4 + X3) << this.mode) + 2) / 4; 299 if (verbose) 300 { 301 System.out.println("DBG: B6 = " + B6); 302 System.out.println("DBG: X1 = " + X1); 303 System.out.println("DBG: X2 = " + X2); 304 System.out.println("DBG: X3 = " + X3); 305 System.out.println("DBG: B3 = " + B3); 306 } 307 X1 = (this.cal_AC3 * B6) >> 13; 308 X2 = (this.cal_B1 * ((B6 * B6) >> 12)) >> 16; 309 X3 = ((X1 + X2) + 2) >> 2; 310 B4 = (this.cal_AC4 * (X3 + 32768)) >> 15; 311 B7 = (UP - B3) * (50000 >> this.mode); 312 if (verbose) 313 { 314 System.out.println("DBG: X1 = " + X1); 315 System.out.println("DBG: X2 = " + X2); 316 System.out.println("DBG: X3 = " + X3); 317 System.out.println("DBG: B4 = " + B4); 318 System.out.println("DBG: B7 = " + B7); 319 } 320 if (B7 < 0x80000000) 321 p = (B7 * 2) / B4; 322 else 323 p = (B7 / B4) * 2; 324 325 if (verbose) 326 System.out.println("DBG: X1 = " + X1); 327 328 X1 = (p >> 8) * (p >> 8); 329 X1 = (X1 * 3038) >> 16; 330 X2 = (-7357 * p) >> 16; 331 if (verbose) 332 { 333 System.out.println("DBG: p = " + p); 334 System.out.println("DBG: X1 = " + X1); 335 System.out.println("DBG: X2 = " + X2); 336 } 337 p = p + ((X1 + X2 + 3791) >> 4); 338 if (verbose) 339 System.out.println("DBG: Pressure = " + p + " Pa"); 340 341 return p; 342 } 343 344 private int standardSeaLevelPressure = 101325; 345 346 public void setStandardSeaLevelPressure(int standardSeaLevelPressure) 347 { 348 this.standardSeaLevelPressure = standardSeaLevelPressure; 349 } 350 351 public double readAltitude() throws Exception 352 { 353 // "Calculates the altitude in meters" 354 double altitude = 0.0; 355 float pressure = readPressure(); 356 altitude = 44330.0 * (1.0 - Math.pow(pressure / standardSeaLevelPressure, 0.1903)); 357 if (verbose) 358 System.out.println("DBG: Altitude = " + altitude); 359 return altitude; 360 } 361 362 private static void waitfor(long howMuch) 363 { 364 try 365 { 366 synchronized (Thread.currentThread()) 367 { 368 Thread.currentThread().wait(howMuch); 369 } 370 } catch (InterruptedException ie) { ie.printStackTrace(); } 371 } 372 373 374 private boolean go = true; 375 376 public void stopReading() 377 { 378 go = false; 379 synchronized (Thread.currentThread()) 380 { 381 System.out.println("Stopping the reader"); 382 Thread.currentThread().notify(); 383 } 384 } 385 386 public void startReading() 387 { 388 System.out.println("Starting " + this.getClass().getName() + "..."); 389 go = true; 390 while (go) 391 { 392 float press = 0; 393 float temp = 0; 394 double alt = 0; 395 396 try { press = this.readPressure(); } 397 catch (Exception ex) 398 { 399 System.err.println(ex.getMessage()); 400 ex.printStackTrace(); 401 } 402 this.setStandardSeaLevelPressure((int)press); // As we ARE at the sea level (in San Francisco). 403 try { alt = this.readAltitude(); } 404 catch (Exception ex) 405 { 406 System.err.println(ex.getMessage()); 407 ex.printStackTrace(); 408 } 409 try { temp = this.readTemperature(); } 410 catch (Exception ex) 411 { 412 System.err.println(ex.getMessage()); 413 ex.printStackTrace(); 414 } 415 416 String nmeaMMB = StringGenerator.generateMMB("II", (press / 100)); // Bars 417 String nmeaMTA = StringGenerator.generateMTA("II", temp); // In Celcius 418 // System.out.println("... " + nmeaMMB + ", " + nmeaMTA + ", " + NMEAContext.getInstance().getNMEAListeners().size() + ", " + NMEAContext.getInstance().getReaderListeners().size() + " listener(s)"); 419 broadcastNMEASentence(nmeaMMB); 420 broadcastNMEASentence(nmeaMTA); 421 422 waitfor(1000L); // One sec. 423 } 424 System.out.println("Reader stopped."); 425 } 426 427 private void broadcastNMEASentence(String nmea) 428 { 429 for (NMEAListener l : NMEAContext.getInstance().getNMEAListeners()) 430 l.dataDetected(new NMEAEvent(this, nmea)); 431 for (NMEAReaderListener l : NMEAContext.getInstance().getReaderListeners()) 432 l.manageNMEAString(nmea); 433 } 434 }
broadcastNMEASentence
function, defined line 427.
To know how to compile, archive, and use the code as a user-exit in the NMEA Console, take a look at this document.
The Raspberry PI, with its BPM180 sensor, on a Slice of PI.
Note: After doing some tests, this is not the best location for the BMP180, it is too close to the CPU of the Raspberry PI, that generates heat. The temperature readings are impacted by the CPU temperature...
From another machine, connect on the RasPI using - for example - PuTTY, and start the vncserver.
VNC is now started, we can connect.
Start the "olivsoft" console, and choose HC, for Headless Console.
Option for the Headless Console...
That's it! From another machine, access the RasPI (TCP:7001 in this case), and check out the Data Viewer.
The Air Temperature and Atmospheric Pressure - coming from the BMP180 connected on the RasPI - are available in the list, good for display, for logging, rendering..., whatever can be done with all the other data.
Whatever understands NMEA cannot tell the difference. Here is OpenCPN.
Here is a sample of the logging (regular logging) that can be done with the sensors injecting data in the NMEA stream:
$IIHDG,003,,,15,E*15 $IIMTW,+15.0,C*3C $IIMWV,155,R,06.2,N,A*16 $IIMWV,155,T,06.6,N,A*14 $IIMMB,29.9870,I,1.0154,B*75 $IIMTA,25.6,C*04 $IIRMC,214009,A,3730.080,N,12228.857,W,00.0,080,090714,15,E,A*19 $IIXDR,P,1.0154,B,0*73 $IIVHW,,,003,M,00.0,N,,*67 $WIMDA,29.984,I,1.015,B,25.6,C,15.0,C,,,,,172.0,T,173.0,M,6.2,N,3.2,M*7D $IIVLW,08195,N,000.0,N*56 $IIVWR,153,R,06.1,N,,,,*61 $IIGLL,3730.081,N,12228.856,W,214011,A,A*4B $IIHDG,003,,,15,E*15 $IIMTW,+15.0,C*3C $IIMWV,153,R,06.1,N,A*13 $IIMWV,155,T,06.2,N,A*10 $IIRMB,A,0.00,L,,HMB-3 ,,,,,001.20,184,,V,A*00 $IIMMB,29.9900,I,1.0155,B*72 $IIMTA,25.6,C*04 $IIXDR,P,1.0155,B,0*72 $WIMDA,29.987,I,1.015,B,25.6,C,15.0,C,,,,,170.0,T,171.0,M,6.1,N,3.1,M*7E $IIRMC,214011,A,3730.081,N,12228.856,W,00.0,080,090714,15,E,A*10 $IIVHW,,,003,M,00.0,N,,*67 $IIVLW,08195,N,000.0,N*56 $IIVWR,150,R,05.9,N,,,,*69 $IIGLL,3730.081,N,12228.856,W,214011,A,A*4B $IIHDG,003,,,15,E*15 $IIMTW,+15.0,C*3C $IIMWV,150,R,05.9,N,A*1B $IIMWV,153,T,06.1,N,A*15 $XXBAT,13.34,V,910,88*12 $IIMMB,29.9882,I,1.0154,B*78 $IIMTA,25.6,C*04 $IIRMB,A,0.00,L,,HMB-3 ,,,,,001.20,184,,V,A*00 $IIXDR,P,1.0154,B,0*73 $IIRMC,214013,A,3730.080,N,12228.856,W,00.0,080,090714,15,E,A*13
A small detail: the BMP180 is only $9.95...