-
Notifications
You must be signed in to change notification settings - Fork 0
Logging environmental data using a I2C sensor on a SBC
The end goal is to have the data logged on a database running on the Nano Pi and showing it all with Grafana.
NOTE: This is not compatible with the current version (0.11.0)
- The BME280 measures barometric pressure, humidity and ambiant temperature.
- This is connected to a Nano Pi Air running Armbian Buster but any other SBC will do.
The pinout of the Nano pi looks like this:
So the pins used are:
- Pin 1: 3V3
- Pin 3: I2C0_SDA
- Pin 5: I2C0_SCL
- Pin 6: GND
- InfluxDB (I followed this guide but pick bmedata as database name)
- Grafana (I followed this guide)
- Putty (or ssh/telnet command in linux)
- Winscp to copy from windows to SBC
- AdoptOpenJDK11 and select java11 and appropriate arch, you must use JDK for some reason JRE doesn't work
- dcafs 0.8.3 or newer
Note: On a 500MB ram sbc, InfluxDB will use about 21% ram while MariaDB and dcafs will each use 12% and grafana around 9%. So i memory is tight Mariadb might be a better option. Grafana doesn't support SQLiteDB so that's not an option.
From this point onwards i assume InfluxDB, Grafana, JDK11 are installed on the SBC and optionally putty and Winscp on a windows host machine.
- Start an SSH session to the SBC or connect to it via Winscp (in windows)
- In the home folder create a folder names 'scripts' and another on 'java'
- scripts: hold the bash script that starts dcafs on boot and will be executed by crontab
- java: contains the (various) project(s) using dcafs and sometimes the JDK/jre
- Copy the extracted dcafs release to the java folder and move into it on the shell
- Run the jar using 'sudo java -jar dcafs-0.*' a lot of text should start appearing (sudo is needed to host the telnet server) and finally DAS.main Barebone boot finished!
- Log into the telnet session using putty or telnet command (default port).
-
You can get an overview of all the i2c related commands with
i2c:?
-
First we need to be sure that the device is detected on the controller, in my case this is 0.
i2c:detect,0
0x76 -
Given that we know nothing else is connected to this bus and the address matches the datasheets options, next up is registering it. The command for that is i2c:adddevice,id,bus,address,script in our case:
- id = bme -> this will be used later to refer to it
- bus = 0 -> because that's the bus it's on
- address = 0x76 -> this is the number found with the previous command
- script = bme280 -> this is the name for the script that will hold all the communication with the chip (and will be created in the scripts subfolder)
i2c:adddevice,bme,0,0x76,bme280
Device added, created blank script at devices/bme280.xml
If there would be multiple BME280's connected, they all share the same script. To check if this workedi2c:list
bme -> @0:0x76 using script bme280 with label bme280
-Stored Scripts-
bme280
cmdname -> what this does (8bits)
After this, your settings.xml should look like this:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<das>
<settings>
<mode>normal</mode>
<!-- Settings related to the telnet server -->
<telnet port="23" title="DAS">
<ignore/>
</telnet>
<i2c>
<bus controller="0">
<device address="0x76" id="bme" script="bme280"/>
</bus>
</i2c>
</settings>
<streams>
<!-- Defining the various streams that need to be read -->
</streams>
</das>
A file called bme280.xml will be generated in a devices subfolder with the following content:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<commandset script="bme280">
<!-- An empty command to start with -->
<command id="cmdname" info="what this does"/>
</commandset>
- Normally this step involves reading the datasheet and figuring the communication out, but we'll skip it. The full bme280 script will be given at the end, but let's limit to temperature for now.
Alter the bme280.xml to look like this
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<commandset script="bme280">
<!-- Read the temperature data -->
<command id="calc_temp" info="Read the temperature registers." bits="16">
<read reg="0xFA" bits="20" return="3"/>
<read reg="0x88" msbfirst="false" return="2"/> <!-- T1: unsigned 16bit -->
<read reg="0x8A" msbfirst="false" signed="true" return="4"/> <!-- T2 to T3: signed 16bit -->
</command>
</commandset>
The above adds the following info.
- command attribute bits="16" -> All read operations in this command will return 16bits numbers by default
- read node -> execute a read operation on the device
- reg="0xFA" -> the register to read is 0xFA
- msbfirst="false" -> the first byte read is the lsb
- bits="20" -> the amount of bits expected in the answer because it doesn't match the default
- signed="true" -> whether or not the received data is signed
- return "3" -> the amount of bytes that ought to be received
Note: Any attribute in the command node counts for all read nodes, unless overriden by a read node, so you could add signed="true" to the command node and remove all occurences of this in the read nodes. But this depends on personal preference.
-
Reload the script to apply it
i2c:reload
All files (1) read ok. -
Before we can see the result of the command, we must request the data from the device to be shown in telnet
i2c:forward,bme
(-> Forward the data received from the i2c device with the id bme to whoever issued this command) Added forward -
Run the command
i2c:bme,calc_temp
(-> Check the script associated with the device with id bme for a command with id calc_temp and run it) Command added to the queue.
And some time later...
bme;calc_temp;518800;28350;26864;50 (-> default output of a read is deviceid;commandid;values read according to set bits/signed/return)
So now dcafs can interact with the sensor, but we'd want a continuous stream of data...
-
For this we'll create a blank taskmanager using
tm:addblank,scheduler
(-> Create a taskmanager with the id scheduler and a default xml script in scripts subfolder called scheduler.xml) Tasks script created, usetm:reload,scheduler
to run it. -
This part doesn't have a telnet interface yet, so we'll have to edit the file directly. Look in the scripts subfolder for scheduler.xml and open it.
-
You'll see that it contains a lot of info for a first time use and some examples, either read it or just delete all comments and the taskset node.
-
Alter the task
<task output="system" trigger="delay:1s">taskset:example</task>
<!-- output="system" means the value is a command -->
<!-- trigger="delay:1s" means one sec after this task is called (which is during startup of the taskmanager -->
<!-- taskset:example means it will run the taskset with the id example -->
# to
<!-- output stays the same because it should run a command -->
<!-- trigger should be an interval, normally this would be 5min of more, but for the guide we'll take 5s -->
<!-- i2c:bme,calc_temp is the same command used earlier to read the temp registers -->
<task output="system" trigger="interval:5s">i2c:bme,calc_temp</task>
<!-- Note that interval can take two periods, an initial delay and an interval by default the initial delay is the interval is not specified -->
- Back in telnet, use the earlier suggested command
tm:reload,scheduler
Tasks:reloaded - If nothing else happens, then the forward isn't active anymore. To reactive
i2c:forward,bme
That's it, now every five seconds the chip is interrogated for the temperature info. Next step is converting this to actual temperature.
- This is done using the MathForward feature
- In telnet, to create a blank math
mf:addblank,id,source
so...
mf:addblank,bmet,i2c:forward,bme
(-> Creates a mathforward with id bmet and i2c:forward,bme as source)
Blank math with id bmet created. - Some settings can be altered through commands:
mf:bmet,alter,delim:;
(-> Change the delimiter to ; from the default ,)
mf:bmet,alter,label:generic:bmet
(-> Set the label to use a generic with the id bmet, the label determines what is done with the data afterwards) - You could add the operations with a command, but it's easier to just edit the settings.xml
<maths>
<!-- the delimiter is used to convert the dataline into an array, so the first element is at index0 and is referred to with i0, then i1 and so on -->
<math id="bmet" delimiter=";" label="generic:bmet" src="i2c:forward,bme">
<operations> <!-- add this operations block -->
<op cmd="maths:scratchpad,*,$" index="2">( (i2/16384-i3/1024)*i4 + ((i2/131072-i3/8192)*(i2/131072-i3/8192))*i5)</op>
<!-- The cmd attribute allows a command to be run with the $ replaced with the result of the operation, in this case
It passes the semi raw temperature to the other math instances (*=all other), this is available as o0 in those math -->
<op index="2">i2/5120</op>
<op index="2" type="scale">2</op> <!-- scale to only have two digits -->
</operations>
</math>
</maths>
-
Reload the math
mf:reload,bmet
Math reloaded -
Now to see the result in telnet
math:bmet
Request for math:bmet ok. -
Now every 5 seconds the following will be shown, the first line is the raw and the second one has the result of the math bme;calc_temp;518416;28350;26864;50 bme;calc_temp;20.76;28350;26864;50
- Create a connection to the earlier set up influx db
dbm:addinflux,influx,bmedata,localhost,user:pass
- Earlier we mentioned the use of a generic, now it will be created. Given that the format of data is temperature at index 2.
gens:addblank,bmet,ssr
(-> Add a blank generic with the id bmet and we dont care about the first two array elements (s=skip) but third one is a real)
Look in the settings.xml for the generics node and alter accordingly
<generics>
<generic delimiter=";" id="bmet" influx="influx"> <!-- add influx="influx" attribute to link it to the database -->
<real index="2">temperature</real> <!-- replace the . here with temperature -->
</generic>
</generics>
- Reload the generic with
gens:reload
That's it. Now the database should slowly start receiving data.
This can be checked with st
because this requests the current status of dcafs.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<das>
<!-- In here telnet, transserver, databases etc are defined -->
<settings>
<!-- Mode dcafsis running in, options are: normal,debug and log. -->
<mode>normal</mode>
<databases>
<server id="flux" type="influxdb">
<db pass="pass" user="user">bmedata</db>
<address>localhost</address>
</server>
</databases>
<!-- Databases can be defined here -->
<!-- Settings related to the telnet server -->
<telnet port="23" title="BMEreader">
<ignore/>
</telnet>
<i2c>
<bus controller="0">
<device address="0x76" id="bme" label="void" script="bme280"/>
</bus>
</i2c>
<taskmanager id="scheduler">scripts/scheduler.xml</taskmanager>
</settings>
<filters>
<filter id="bmet" src="i2c:forward,bme" type="contains">calc_temp</filter>
<!-- get the data regarding temperature -->
<filter id="bmeh" src="i2c:forward,bme" type="contains">calc_hum</filter>
<!-- get the data regarding pressure -->
<filter id="bmep" src="i2c:forward,bme" type="contains">calc_pressure</filter>
<!-- get the data regarding humidity -->
</filters>
<maths>
<!-- Some info on what the test math does -->
<math id="bmet" label="generic:bmet" src="filter:bmet">
<!-- works -->
<operations>
<op giveto="maths:scratchpad,*,$" index="2">( (i2/16384-i3/1024)*i4 + ((i2/131072-i3/8192)*(i2/131072-i3/8192))*i5)</op>
<!-- i5 = max -> 6 -->
<op index="2">i2/5120</op>
<!-- i2 = max -> 3 -->
<op index="2" type="scale">2</op>
</operations>
</math>
<math id="bmeh" label="generic:bmeh" src="filter:bmeh">
<operations>
<op index="2">((i2 - (i6*64 + i7/16384*(o0 - 76800)))*(i4/65536*(1+i8/67108864*(o0 - 76800)* (1+i5/67108864*(o0 - 76800)))))</op>
<op index="2">i2*(1-i3*i2/524288)</op>
<op index="2" type="scale">2</op>
</operations>
</math>
<math id="bmep" label="generic:bmep" src="filter:bmep">
<!-- works -->
<operations>
<op index="6">((o0/2 -64000)^2*i8/32768 + (o0/2 -64000)*i7*2)/4 + (i6*65536.0)</op>
<op index="3">(1 + ((i5*(o0/2 -64000)^2/524288 + i4*(o0/2 -64000))/524288)/32768.0)*i3</op>
<op index="2">1048576 - i2</op>
<op index="2">(i2-(i6/4096)) * 6250.0/i3</op>
<op index="2">i2 + ( i11*i2*i2/2147483648.0 + i2*i10/32768 + i9)/16</op>
<op index="2">i2/100</op>
<!-- convert to hPa -->
<op index="2" type="scale">2</op>
</operations>
</math>
</maths>
<generics>
<!-- Where the generics are defined -->
<generic delimiter=";" id="bmet" influx="flux:temps">
<real index="2">temperature</real>
</generic>
<generic delimiter=";" id="bmeh" influx="flux:hums">
<real index="2">humidity</real>
</generic>
<generic delimiter=";" id="bmep" influx="flux:press">
<real index="2">pressure</real>
</generic>
</generics>
</das>
<commandset script="bme280">
<!-- Read all the data/cal registers from the device -->
<command id="read_hum" info="Read the humidity registers." bits="16">
<read reg="0xFD" return="2"/>
</command>
<command id="read_temp" info="Read the temperature registers." bits="20">
<read reg="0xFA" return="3"/>
<!-- 0x7F 0xF5 0x00 so 0x00F57F -->
</command>
<command id="calc_temp" info="Read the temperature registers." bits="16">
<read reg="0xFA" bits="20" return="3"/>
<read reg="0x88" msbfirst="false" return="2"/> <!-- T1: unsigned 16bit -->
<read reg="0x8A" msbfirst="false" signed="true" return="4"/> <!-- T2 to T3: signed 16bit -->
</command>
<command id="calc_hum" info="Read the humidity registers." msbfirst="false">
<read reg="0xFD" msbfirst="true" bits="16" return="2"/>
<read reg="0xA1" bits="8" signed="false" return="1"/> <!-- H1: unsigned 8bit -->
<read reg="0xE1" bits="16" signed="true" return="2"/> <!-- H2: signed 16bit -->
<read reg="0xE3" bits="8" signed="false" return="1"/> <!-- H3: unsigned 8bit -->
<read reg="0xE4" msbfirst="true" bits="12" signed="true" return="2"/> <!-- H4: signed 12bit -->
<read reg="0xE5" bits="12" signed="true" return="2"/> <!-- H5: humidity signed short, WRONG SHIFT-->
<read reg="0xE5" bits="8" signed="true" return="1"/> <!-- H6: humidity signed char -->
</command>
<command id="calc_pressure" info="Read the pressure registers." bits="16" msbfirst="false">
<read reg="0xF7" msbfirst="true" bits="20" return="3"/>
<read reg="0x8E" signed="false" return="2"/> <!-- P1: unsigned 16bit-->
<read reg="0x90" signed="true" return="16"/> <!-- P2 to P9: signed 16bit -->
</command>
<!--
double var1 = (adc/16384 - comp[0]/1024) * comp[1];
double var2 = (adc/131072 - (comp[0]/8192)) * (adc/131072.0 - comp[0]/8192) * comp[2];
t_fine = var1 + var2;
double T = Tools.roundDouble((t_fine)/5120,2);
x0 = i0/16384
x1 = i1/1024
x2 = x0 - x1
x3 = x2 * i2
x4 = i0/131072 - i1/8192
x4 *= x4
x5 = x4 * i3
x6 = x3 + x5
x7 = x6/5120
-->
<command id="read_press" info="Read the pressure registers." bits="24">
<read reg="0xF7" return="3"/>
</command>
<command id="read_status" info="Read the status registers." bits="8">
<read reg="0xF3" return="1"/>
</command>
<command id="read_ctrl" info="Read the ctrl registers." bits="8">
<read reg="0xF2" return="1"/>
<read reg="0xF4" return="1"/>
</command>
<command id="read_coeff" info="Read the calibration values from the ic" msbfirst="false" bits="16">
<!-- Temperature -->
<!-- 48750; -3992 ; 12800 -->
<read reg="0x88" return="2"/> <!-- T1: unsigned 16bit -->
<read reg="0x8A" signed="true" return="4"/> <!-- T2 to T3: signed 16bit -->
<!-- Pressure -->
<!-- 16785 ; 32982 ; 53259 ; 56609 ; 51455 ; 63999 ; 44070 ; 2776 ; 48400 -->
<read reg="0x8E" signed="true" return="2"/> <!-- P1: signed 16bit-->
<read reg="0x90" signed="false" return="16"/> <!-- P2 to P9: unsigned 16bit -->
<!-- Humidity -->
<!-- 75 ; -30463 ; 0 ; 242 ; 688 -->
<read reg="0xA1" bits="8" signed="false" return="1"/> <!-- H1: unsigned 8bit -->
<read reg="0xE1" bits="16" signed="true" return="2"/> <!-- H2: signed 16bit -->
<read reg="0xE3" bits="8" signed="false" return="1"/> <!-- H3: unsigned 8bit -->
<read reg="0xE4" bits="12" signed="true" return="2"/> <!-- H4: signed 12bit -->
<read reg="0xE5" bits="12" signed="true" return="2"/> <!-- H5: humidity signed short, WRONG SHIFT-->
</command>
<!-- Mode changes -->
<!-- Recommended settings for specific situations -->
<command id="weather" info="Set weather recommends (1 sample/min, no filter, no oversampling)">
<write reg="0xF5" >0x00</write> <!-- standby dont care (forced mode), filter off, no spi 3w -->
<write reg="0xF2" >0x01</write> <!-- Humidity oversampling -->
<write reg="0xF4" >0x26</write> <!-- 001 001 10 -->
</command>
<command id="humidity" info="Set humidity recommends (1 sample/sec, no filter, no oversampling, no pressure)">
<write reg="0xF5" >0x00</write> <!-- standby dont care (forced mode), filter off, no spi 3w -->
<write reg="0xF2" >0x01</write> <!-- Humidity oversampling x1 -->
<write reg="0xF4" >0x22</write> <!-- temp x1, press x1, forced -->
</command>
<command id="indoornav" info="Set indoor nav recommends (normal mode, tstandby=0.5ms,pressure x16,temp x2, hum x1, filter 16)">
<write reg="0xF5" >0x10</write> <!-- standby 0.5ms, filter 16, no spi 3w -->
<write reg="0xF2" >0x01</write> <!-- Humidity oversampling x1 -->
<write reg="0xF4" >0x57</write> <!-- temp x2=010, press x16=101, normal = 11 -->
</command>
<!-- General mode setting -->
<command id="readmode" info="Read mode register">
<read reg="0xF4" return="1"/>
</command>
<command id="start" info="Start the sensors acc. at x1 oversampling" >
<write reg="0xF2" >0x01</write> <!-- -->
<write reg="0xF4" >0x27</write> <!-- oversampling x1 and active -> 0010 0111 -> 0x27 -->
</command>
<command id="sleepmode" info="Go to sleep mode">
<alter reg="0xF4" operand="and">0xFC</alter> <!-- Default:0x00 xxxx xxxx AND 0xFD = xxxx xx 00 -->
</command>
<command id="reset" info="Reset the ic" >
<write reg="0xE0" >0xB6</write> <!-- -->
</command>
</commandset>
<!--
0xF2 - ctrl hum
humidity oversampling
reserved[4:0] osr_h[2:0]
0000 0 000 = skipped
001 = oversampling x1
010 = oversampling x2
011 = oversampling x4
100 = oversampling x8
101 = oversampling x16
0xF4 - ctrl meas
osrs_t[2:0] osrs_p[2:0] mode[1:0]
temperaure oversampling pressure oversampling 00 = sleep mode
000 = skipped 000 = skipped 01 = forced mode
001 = oversampling x1 001 = oversampling x1 10 = forced mode
010 = oversampling x2 010 = oversampling x2 11 = normal mode
011 = oversampling x4 011 = oversampling x4
100 = oversampling x8 100 = oversampling x8
101 = oversampling x16 101 = oversampling x16
0xF5 - config
t_sb[2:0] filter[2:0] spi3w_en[0]
-> 000 = 0,5ms 000 = filter off 0 = spi 3wire disabled
-> 001 = 62.5ms 001 = 2 1 = spi 3wire enabled
-> 010 = 125ms 010 = 4
-> 011 = 250ms 011 = 8
-> 100 = 500ms 1xx = 16
-> 101 = 1000ms
-> 110 = 10ms
-> 111 = 20ms
Temperatue ADC data
0xFA = MSB
0xFB = LSB
0xFC = xLSB
-->
- Getting to know dcafs
- Filterforward
- MathForward
- EditForward (todo)
- Combining
- TaskManager
- LabelWorker
- CommandPool
- DebugWorker
- Realtimevalues
- IssuePool