Atlas PH sensor on rasberry pi and using I2C

So I would like to add a PH sensor (atlas scientific) over the I2C bus on a Raspberry PI for a hydroponics project. So I pretty much totally newbie here with Cayenne and wondering how I should start trying to write a custom MQTT? driver for it. Hopefully I could make up a python library but not sure how to even just start with it. I seen a few things that might be close to what I want but a lot of stuff here is for Ardinio and not Raspberry Pi applications.

Try to look at this project, working with Raspberry Pi and MQTT.

1 Like

well after figuring out if I put my code in the example directory of the API library files I was able to play with it…

Here is what I mashed together from the atlas example and the cayenne python MQTT example. Its just a start but at least it seems to be working so far…

#!/usr/bin/env python

import cayenne.client

import io # used to create file streams
import fcntl # used to access I2C parameters like addresses
import time
import string # helps parse strings

class atlas_i2c:
    long_timeout = 3 # the timeout needed to query readings and calibrations
    short_timeout = .5 # timeout for regular commands
    default_bus = 1 # the default bus for I2C on the newer Raspberry Pis, certain older boards use bus 0
    default_address = 99 # the default address for the pH sensor

    def __init__(self, address = default_address, bus = default_bus):
        # open two file streams, one for reading and one for writing
        # the specific I2C channel is selected with bus
        # it is usually 1, except for older revisions where its 0
        # wb and rb indicate binary read and write
        self.file_read ="/dev/i2c-"+str(bus), "rb", buffering = 0)
        self.file_write ="/dev/i2c-"+str(bus), "wb", buffering = 0)

        # initializes I2C to either a user specified or default address

    def set_i2c_address(self, addr):
        # set the I2C communications to the slave specified by the address
        # The commands for I2C dev using the ioctl functions are specified in
        # the i2c-dev.h file from i2c-tools
        I2C_SLAVE = 0x703
        fcntl.ioctl(self.file_read, I2C_SLAVE, addr)
        fcntl.ioctl(self.file_write, I2C_SLAVE, addr)

    def write(self, string):
        # appends the null character and sends the string over I2C
        string += "\00"

    def read(self, num_of_bytes = 31):
        # reads a specified number of bytes from I2C, then parses and displays the result
        res = # read from the board
        response = filter(lambda x: x != '\x00', res) # remove the null characters to get the response
        if(ord(response[0]) == 1): # if the response isnt an error
            char_list = map(lambda x: chr(ord(x) & ~0x80), list(response[1:])) # change MSB to 0 for all received characters except the first and get a list of characters
            # NOTE: having to change the MSB to 0 is a glitch in the raspberry pi, and you shouldn't have to do this!
            return "Command succeeded " + ''.join(char_list) # convert the char list to a string and returns it
            return "Error " + str(ord(response[0]))

    def query(self, string):
        # write a command to the board, wait the correct timeout, and read the response

        # the read and calibration commands require a longer timeout
        if((string.upper().startswith("R")) or
            return "sleep mode"


    def close(self):

def main():
    device = atlas_i2c() # creates the I2C port object, specify the address or bus if necessary

    # Cayenne authentication info. This should be obtained from the Cayenne Dashboard.

    # The callback for when a message is received from Cayenne.
    def on_message(message):
        print("message received: " + str(message))
        # If there is an error processing the message return an error string, otherwise return nothing.

    client = cayenne.client.CayenneMQTTClient()
    client.on_message = on_message

    timestamp = 0
    output = "0.00"
    phvalue = 7

    while True:

        if (time.time() > timestamp + 10):

               output = device.query("R")
            except IOError:
               print("Query failed")

            phvalue = float(string.split(output, ' ')[2])

            client.virtualWrite(1, phvalue, dataType='PH', dataUnit='PH')
            timestamp = time.time()
            i = i+1

if __name__ == '__main__':


When you share the code, remember to delete your MQTT username, password and clientID. :grinning:

MQTT_USERNAME  = "username"
MQTT_PASSWORD  = "password"

doh… thanks…

1 Like

I already have aph probe would something like this work?

yes, connect the pH sensor to the pi and get reading from it by following this and then use cayenne MQTT python library to send data to cayenne GitHub - myDevicesIoT/Cayenne-MQTT-Python: Python Library for Cayenne MQTT API

thank you, the code worked perfect. I had been fighting with trying to get/make code to use the ADS1115 which I am still working on for some other analog sensors but this worked perfect to at least get me the ph sensor working fine.

I’m hoping that maybe this winter I get back into working on this project. Glad you figured out what it was you were trying to get work.

yea it just sucks that the pia2d and pwm do not work anymore I want to add some pumps with a pwm module to add ph+ and -.

@pihydro.0001 the only option you have currently is to write your own code for the ph and pwm and use cayenne MQTT python library to send/recieve data from cayenne.

Hi Thanks for your help. I keep Cayenne in a box but I 've decided to write my own HQ! :smiley:

Hi, thank you for your work.
It is really very helpfull for me.
I have one question and I appreciate if you could teach me how to solve my problem.

I measure PH and EC with atlas-scientific probes and Rpi3.
I would like to check water quality all day and make my project non-stopping.
But EC sensor stops very often.
From the below error message, do you know how to deal with the message?

Traceback (most recent call last):
File “”, line 112, in
File “”, line 100, in main
output = device.query(“R”)
File “”, line 65, in query
File “”, line 45, in read
if(ord(response[0]) == 1): # if the response isnt an error
IndexError: string index out of range

thats means the string index you are looking is not there. you might not get any response from the sensor and gave a null result. so try this guide and see if you get the error again

Thank you very much, May I ask one more question?
I would like to receive alert mail, but it seems not to be sent so far.
I use Atlas pH sensor and I hope to be notified at the value higher than 8.
I selected the device(pH) and input min(0) step(0.1) value(8) max(14), checked “Nothing selected” and “Sensor above”.
then notfiy
Add custom recipient
imput “my email address”.

I can receive “online/offline” notification, but can’t receive pH alert notification.
What is wrong?
I appreciate if you tell me how to set up.
Thank you in advance.

Is the counter for the trigger increasing?

Also check if you are sending the correct data type.

Ran 0 times.

I use the code published above dated 3 Feb '18 by “tpangburn”.

phvalue = float(string.split(output, ’ ')[2])
client.virtualWrite(1, phvalue, dataType=‘PH’, dataUnit=‘PH’)

Could you please teach me how to check the data type?
Thank you in advance.

At present, phvalue is higher than 8.
But no mail and the counter remains zero 0 times.