Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python3 support for scd30.py, wiring example, root comment for installing clock stretching and more Readme. #3

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,20 @@ Software to read out [Sensirion SCD30](https://www.sensirion.com/en/environmenta

This software is licenced under GPLv3 by [UnravelTEC OG](https://unraveltec.com) (https://unraveltec.com), 2018.

## Prerequsites
## Prerequsites

### Python
### Enable I2C interface on Raspberry Pi
`sudo raspi-config` navigate to `P5 I2C` and select `<Yes>`.

### Wiring SCD30 to Raspberry Pi 3 B
- SCD30: RX/SDA -> Pi: I2C1 SDA (GPIO2)
- SCD30: TX/SCL -> Pi: I2C1 SCL (GPIO3)
- SCD30: VIN -> Pi: 3.3V/5.5V (use one of PWR pinouts)
- SCD30: GND -> Pi: GND (use one of GND pinouts)

You might need to run the following commands as root e.g. by typing `sudo` before running a specific command.

### Python

Install the following python-libraries:

Expand Down Expand Up @@ -57,6 +68,8 @@ Remember: Maximum I2C speed for SCD30 is 100kHz.

# Run program

You might need to run the following as root e.g. by typing `sudo` before running the script.

```
python scd30.py
```
Expand Down
183 changes: 183 additions & 0 deletions py3scd30.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/usr/bin/env python
# coding=utf-8
#
# Copyright © 2018 UnravelTEC
# Michael Maier <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# If you want to relicense this code under another license, please contact [email protected].

# hints from https://www.raspberrypi.org/forums/viewtopic.php?p=600515#p600515

from __future__ import print_function

# This module uses the services of the C pigpio library. pigpio must be running on the Pi(s) whose GPIO are to be manipulated.
# cmd ref: http://abyz.me.uk/rpi/pigpio/python.html#i2c_write_byte_data
import pigpio # aptitude install python-pigpio
import time
import struct
import sys
import crcmod # aptitude install python-crcmod


def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)

PIGPIO_HOST = '::1'
PIGPIO_HOST = '127.0.0.1'

pi = pigpio.pi(PIGPIO_HOST)
if not pi.connected:
eprint("no connection to pigpio daemon at " + PIGPIO_HOST + ".")
exit(1)

I2C_SLAVE = 0x61
I2C_BUS = 1

try:
pi.i2c_close(0)
except:
if sys.exc_info()[1] and str(sys.exc_info()[1]) != "'unknown handle'":
eprint("Unknown error: ", sys.exc_type, ":", sys.exc_info()[1])

try:
h = pi.i2c_open(I2C_BUS, I2C_SLAVE)
except:
eprint("i2c open failed")
exit(1)

# read meas interval (not documented, but works)

def read_n_bytes(n):

try:
(count, data) = pi.i2c_read_device(h, n)
except:
eprint("error: i2c_read failed")
exit(1)

if count == n:
return data
else:
eprint("error: read measurement interval didnt return " + str(n) + "B")
return False

def i2cWrite(data):
try:
pi.i2c_write_device(h, data)
except:
eprint("error: i2c_write failed")
return -1
return True


def read_meas_interval():
ret = i2cWrite([0x46, 0x00])
if ret == -1:
return -1

try:
(count, data) = pi.i2c_read_device(h, 3)
except:
eprint("error: i2c_read failed")
exit(1)

if count == 3:
if len(data) == 3:
interval = int(data[0])*256 + int(data[1])
#print "measurement interval: " + str(interval) + "s, checksum " + str(data[2])
return interval
else:
eprint("error: no array len 3 returned, instead " + str(len(data)) + "type: " + str(type(data)))
else:
"error: read measurement interval didnt return 3B"

return -1

read_meas_result = read_meas_interval()
if read_meas_result == -1:
exit(1)

if read_meas_result != 2:
# if not every 2s, set it
eprint("setting interval to 2")
ret = i2cWrite([0x46, 0x00, 0x00, 0x02, 0xE3])
if ret == -1:
exit(1)
read_meas_interval()


#trigger cont meas
# TODO read out current pressure value
pressure_mbar = 972
LSB = 0xFF & pressure_mbar
MSB = 0xFF & (pressure_mbar >> 8)
#print ("MSB: " + hex(MSB) + " LSB: " + hex(LSB))
#pressure_re = LSB + (MSB * 256)
#print("press " + str(pressure_re))
pressure = [MSB, LSB]

pressure_array = ''.join(chr(x) for x in [pressure[0], pressure[1]])

#pressure_array = ''.join(chr(x) for x in [0xBE, 0xEF]) # use for testing crc, should be 0x92
#print(pressure_array)

f_crc8 = crcmod.mkCrcFun(0x131, 0xFF, False, 0x00)

#print("CRC: " + hex(f_crc8))
i2cWrite([0x00, 0x10, pressure[0], pressure[1], f_crc8(b'123456789')])

# read ready status
while True:
ret = i2cWrite([0x02, 0x02])
if ret == -1:
exit(1)

data = read_n_bytes(3)
if data == False:
time.sleep(0.1)
continue

if data[1] == 1:
#print "data ready"
break
else:
#eprint(".")
time.sleep(0.1)

#read measurement
i2cWrite([0x03, 0x00])
data = read_n_bytes(18)

#print "CO2: " + str(data[0]) +" "+ str(data[1]) +" "+ str(data[3]) +" "+ str(data[4])

struct_co2 = struct.pack('>BBBB', data[0], data[1], data[3], data[4])
float_co2 = struct.unpack('>f', struct_co2)[0]

struct_T = struct.pack('>BBBB', data[6], data[7], data[9], data[10])
float_T = struct.unpack('>f', struct_T)

struct_rH = struct.pack('>BBBB', data[12], data[13], data[15], data[16])
float_rH = struct.unpack('>f', struct_rH)[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you add [0] for float_co2 and float_rH, but not for float_T - is there any difference?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will test, but I think it was because the co2 and rh returned a list while the temperature returned a single value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm - the data format is exactly the same for all three... and struct.unpack('>f', ... ) should return a single float every time...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that makes sense, I will have time to test today and will put the output of the code here later.

Copy link
Author

@servetcoskun servetcoskun Mar 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should definitely be a [0] for temperature as well, you are right :)

struct_co2 = struct.pack('>BBBB', data[0], data[1], data[3], data[4])
float_co2 = struct.unpack('>f', struct_co2)[0]

struct_T = struct.pack('>BBBB', data[6], data[7], data[9], data[10])
float_T = struct.unpack('>f', struct_T)[0]

struct_rH = struct.pack('>BBBB', data[12], data[13], data[15], data[16])
float_rH = struct.unpack('>f', struct_rH)[0]

that is the correct way to do it.


if float_co2 > 0.0:
print("gas_ppm{sensor=\"SCD30\",gas=\"CO2\"} %f" % float_co2)

print("temperature_degC{sensor=\"SCD30\"} %f" % float_T)

if float_rH > 0.0:
print("humidity_rel_percent{sensor=\"SCD30\"} %f" % float_rH)

pi.i2c_close(h)