kizniche / Mycodo

An environmental monitoring and regulation system
http://kylegabriel.com/projects/
GNU General Public License v3.0
2.99k stars 499 forks source link

[Suggestion] pH Atlas scientifique sensor #238

Closed leoneo closed 7 years ago

leoneo commented 7 years ago

I need to regulate the pH of medium with CO2. Can you please add the pH Atlas scientifique sensor with relay function, and calibration?

Thank you very much

kizniche commented 7 years ago

Do you have this sensor to perform testing with if I attempt to create a Mycodo sensor module?

leoneo commented 7 years ago

Yes I have all ready to test.

kizniche commented 7 years ago

Can you copy the output of their I2C script?

https://github.com/AtlasScientific/Raspberry-Pi-sample-code/blob/master/i2c.py

kizniche commented 7 years ago

I'm expecting the output to be something like "Command succeeded X.XX" but I need to verify exactly what it is to be able to build a sensor module.

leoneo commented 7 years ago

I have the V3.0 UART version https://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Biometric/pH_Stamp_3.0.pdf

I try this code :

#!/usr/bin/python

import serial
import sys
import time
import string 
from serial import SerialException

def read_line():
    """
    taken from the ftdi library and modified to 
    use the ezo line separator "\r"
    """
    lsl = len('\r')
    line_buffer = []
    while True:
        next_char = ser.read(1)
        if next_char == '':
            break
        line_buffer.append(next_char)
        if (len(line_buffer) >= lsl and
                line_buffer[-lsl:] == list('\r')):
            break
    return ''.join(line_buffer)

def read_lines():
    """
    also taken from ftdi lib to work with modified readline function
    """
    lines = []
    try:
        while True:
            line = read_line()
            if not line:
                break
                ser.flush_input()
            lines.append(line)
        return lines

    except SerialException as e:
        print "Error, ", e
        return None 

def send_cmd(cmd):
    """
    Send command to the Atlas Sensor.
    Before sending, add Carriage Return at the end of the command.
    :param cmd:
    :return:
    """
    buf = cmd + "\r"        # add carriage return
    try:
        ser.write(buf)
        return True
    except SerialException as e:
        print "Error, ", e
        return None

if __name__ == "__main__":

    print "\nWelcome to the Atlas Scientific Raspberry Pi UART example.\n"
    print("    Any commands entered are passed to the board via UART except:")
    print("    Poll,xx.x command continuously polls the board every xx.x seconds")
    print("    Pressing ctrl-c will stop the polling\n")
    print("    Press enter to receive all data in buffer (for continuous mode) \n")

    # to get a list of ports use the command: 
    # python -m serial.tools.list_ports
    # in the terminal
    usbport = '/dev/tty1' # change to match your pi's setup 

    print "Opening serial port now..."

    try:
        ser = serial.Serial(usbport, 38400, timeout=0)
    except serial.SerialException as e:
        print "Error, ", e
        sys.exit(0)

    while True:
        input_val = raw_input("Enter command: ")

        # continuous polling command automatically polls the board
        if input_val.upper().startswith("POLL"):
            delaytime = float(string.split(input_val, ',')[1])

            send_cmd("C,0") # turn off continuous mode
            #clear all previous data
            time.sleep(1)
            ser.flush()

            # get the information of the board you're polling
            print("Polling sensor every %0.2f seconds, press ctrl-c to stop polling" % delaytime)

            try:
                while True:
                    send_cmd("R")
                    lines = read_lines()
                    for i in range(len(lines)):
                        # print lines[i]
                        if lines[i][0] != '*':
                            print "Response: " , lines[i]
                    time.sleep(delaytime)

            except KeyboardInterrupt:       # catches the ctrl-c command, which breaks the loop above
                print("Continuous polling stopped")

        # if not a special keyword, pass commands straight to board
        else:
            if len(input_val) == 0:
                lines = read_lines()
                for i in range(len(lines)):
                    print lines[i]
            else:
                send_cmd(input_val)
                time.sleep(1.3)
                lines = read_lines()
                for i in range(len(lines)):
print lines[i]

connection work but no data

kizniche commented 7 years ago

Have you been able to get a reading from the sensor by any method?

leoneo commented 7 years ago

I am able to turn on/off the LED by l1/l0 commands but I have an error when aptent to read the pH by r command

pi@raspberrypi:~ $ sudo python /home/pi/Downloads/test3.py Welcome to the Atlas Scientific Raspberry Pi example. connected to: /dev/ttyAMA0 Enter your commands below. Insert "exit" to leave the application.

r Traceback (most recent call last): File "/home/pi/Downloads/test3.py", line 38, in data = ser.read() File "/usr/lib/python2.7/dist-packages/serial/serialposix.py", line 460, in read raise SerialException('device reports readiness to read but returned no data (device disconnected?)') serial.serialutil.SerialException: device reports readiness to read but returned no data (device disconnected?)

The code :

import serial
import time

print "Welcome to the Atlas Scientific Raspberry Pi example."

ser = serial.Serial(
        port='/dev/ttyAMA0',
        baudrate=38400,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        bytesize=serial.EIGHTBITS
)

#ser.open()
ser.close()
ser.open()
ser.isOpen()

print("connected to: " + ser.portstr)

print 'Enter your commands below.\r\nInsert "exit" to leave the application.'

input=1

while True:
                input = raw_input(">> ")
        # Python 3 users
        # input = input(">> ")
                if input == 'exit':
                        ser.close()
                        exit()
                else:
                        ser.write(input + '\r')
                        line = ""
                        data = ""
                        time.sleep(1.3)
                        data = ser.read()
                       # if(data == "\r"):
                        #        print ("Received from sensor:" + line)
                        #       line = ""
                        #else:
                         #      line = line + data
                        #       print (line)
                        time.sleep(1.3)
                        print ("> >" + str(data))

I am noobs in python

kizniche commented 7 years ago

I believe the command is "R", not "r"

leoneo commented 7 years ago

Have take K30.py file and modified for pH, maybe it works

# coding=utf-8

from lockfile import LockFile
import logging
import serial
import time
import RPi.GPIO as GPIO
from .base_sensor import AbstractSensor

logger = logging.getLogger("mycodo.sensors.pHAtlas")
pHAtlas_LOCK_FILE = "/var/lock/sensor-pHAtlas"

class pHAtlasSensor(AbstractSensor):
    """ A sensor support class that monitors the Atlas Scientifique's pH sensor """

    def __init__(self):
        super(pHAtlasSensor, self).__init__()
        self._pH = 0
        if GPIO.RPI_INFO['P1_REVISION'] == 3:
            self.serial_device = "/dev/ttyS0"
        else:
            self.serial_device = "/dev/ttyAMA0"

    def __repr__(self):
        """  Representation of object """
        return "<{cls}(pH={pH})>".format(
            cls=type(self).__name__,
            pH="{0:.2f}".format(self._pH))

    def __str__(self):
        """ Return pH information """
        return "pH: {pH}".format(pH="{0:.2f}".format(self._pH))

    def __iter__(self):  # must return an iterator
        """ pHAtlas iterates through live pH readings """
        return self

    def next(self):
        """ Get next pH reading """
        if self.read():  # raised an error
            raise StopIteration  # required
        return dict(pH=float('{0:.2f}'.format(self._pH)))

    def info(self):
        conditions_measured = [
            ("pH", "pH", "float", "0.00", self._pH, self.pH)
        ]
        return conditions_measured

    @property
    def pH(self):
        """ pH measurement """
        if not self._pH:  # update if needed
            self.read()
        return self._pH

    def get_measurement(self):
        """ Gets the Atlas's pH measurement via UART"""
        ser = serial.Serial(self.serial_device, 
        baudrate=38400,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        bytesize=serial.EIGHTBITS,
    timeout=1)  # Wait 1 second for reply
        ser.flushInput()
    ser.flushOutput()
        time.sleep(1)
        ser.write("R\r")
        time.sleep(.264)
        resp = ser.read(8)
        if len(resp) == 0:
            pH = None
        else:
            pH = resp
        return pH

    def read(self):
        """
        Takes a reading from the pHAtlas and updates the self._pH value
        :returns: None on success or 1 on error
        """
        lock = LockFile(pHAtlas_LOCK_FILE)
        try:
            # Acquire lock on pHAtlas to ensure more than one read isn't
            # being attempted at once.

            while not lock.i_am_locking():
                try:
                    lock.acquire(timeout=60)  # wait up to 60 seconds before breaking lock
                except Exception as e:
                    logger.error("{cls} 60 second timeout, {lock} lock broken: "
                                 "{err}".format(cls=type(self).__name__,
                                                lock=pHAtlas_LOCK_FILE,
                                                err=e))
                    lock.break_lock()
                    lock.acquire()
            self._pH = self.get_measurement()
            lock.release()
            if self._pH is None:
                return 1
            return  # success - no errors
        except Exception as e:
            logger.error("{cls} raised an exception when taking a reading: "
                         "{err}".format(cls=type(self).__name__, err=e))
            lock.release()
return 1
leoneo commented 7 years ago

Hello, My code works

import time
from datetime import datetime
import serial

ser = serial.Serial(port='/dev/ttyAMA0',
baudrate=38400,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS)
ser.close()
ser.open()
ser.isOpen()
ser.flushInput()
time.sleep(1)

while True:
    input_val = raw_input("exit for quit Enter command: ")
    if input_val == 'exit':
        ser.close()
        exit()
    else:
        ser.flushOutput
        ser.write(input_val+'\r')
        time.sleep(0.3)
        bytesToRead = ser.inWaiting()
        if bytesToRead != 0:
            datapH = ser.read(bytesToRead)
            print ">>" + datapH
            ser.flushInput()
time.sleep(1)

I have follow the UART mode RPI config at https://github.com/AtlasScientific/Raspberry-Pi-sample-code

leoneo commented 7 years ago

to read pH > input_val = r or R to correct pH in function of Temperatur > input_val = TT or TT.TT (ex = 25.00) it is all we need

kizniche commented 7 years ago

Can you provide a little more information:

  1. source of code
  2. calibration commands
  3. exact output from pH query
  4. explanation of of what "to correct pH in function of Temperatur" means

Thanks

leoneo commented 7 years ago

Yes

1 it is my code 2 at page 10 : https://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Biometric/pH_Stamp_3.0.pdf

  1. output is something like XX.XX (ex : 6.88)
  2. The pH value is correlated with the temperature of the medium so we need to send the temperature at the sensor for auto-correction of the pH value.

Thanks P.S: sorry for my english

etiology commented 7 years ago

Just a heads up. I noticed that your code is mixing upper and lower case letters in your names. You may want to fix these in your variables, methods, and functions. The code looks like a JavaScript style and not python. Python uses a different style guide.

Take a look at python's style guide called PEP8.

etiology commented 7 years ago

I guess most of that is from the serial package so nvm

kizniche commented 7 years ago

It does look a bit odd. That was one of the things I most recently started becoming conscious of. Now, whenever I see it in code I previously write, I change it.

kizniche commented 7 years ago

Doesn't proper calibration require a 4 pH medium and and 10 pH medium?

leoneo commented 7 years ago

yes you need to put the sensor on the 4, 7 and 10 pH buffer solutions to make the calibration. So we need a text box with "Please put the sensor on the 4 pH buffer solution" sleep until user valid, read the value, ask to put sensor on 10 pH buffer, sleep... , read, ask to put on 7 pH buffer, sleep, read the last value and it is good

kizniche commented 7 years ago

Yes, but it's still not clear to me how calibration is conducted, code-wise.

kizniche commented 7 years ago

Okay, nevermind. I see the proper procedure.

leoneo commented 7 years ago

simply add 3 click box on the pH sensor page calibration 4 pH buffer ->in the code -> input_val = f and make after an input_val = e calibration 7 pH buffer ->in the code -> input_val = s and make after an input_val = e calibration 10 pH buffer ->in the code -> input_val = t and make after an input_val = e

leoneo commented 7 years ago

When the user is ready (he have put the sensor in the good buffer), he click on the corresponding buffer to make the calibration

kizniche commented 7 years ago

Calibration must be performed in the order: pH 7, pH 4, pH 10, therefore a step-wise calibration page sequence will need to be created, with the first page instructing the user to place the probe in a pH 7 solution and clicking the submit button. A 2 minute countdown timer will present, preventing the user from continuing until the required period has elapsed, at which time the page will refresh (and calibration command sent), and the user will be further instructed to rinse and place the probe in the next solution, etc.

leoneo commented 7 years ago

yes this will be very great !

kizniche commented 7 years ago

I got the UI calibration pages working yesterday. I'll work on the sensor module tonight if I have time, and give you a way to test it before it goes live in the next release.

kizniche commented 7 years ago

Do you have the calibration solutions with different pHs to test?

leoneo commented 7 years ago

Thank you very much, It's been a long time since I wanted to ask you to add this sensor.

Yes I have the solutions

kizniche commented 7 years ago

I just committed some code that should get the sensor working in the new_sensor_atlas_ph branch. Currently this should only be used to test if measurements can be read from the sensor. There is the calibration page under [More] -> [Calibrate pH Sensor] but this currently does not perform the calibration, it merely is there to demonstrate an experimental method to conduct the calibration. Once you can verify measurements can be read from the sensor, I'll go ahead and work on the calibration portion and we can continue testing from there. Here are the steps you can take to get this branch up and running in order to test adding the new sensor:

Stop the currently-running daemon and web server

sudo service mycodo stop sudo /etc/init.d/apache2 stop

Move the current Mycodo directory

mv ~/Mycodo ~/Mycodo-old

Clone the Mycodo repo and checkout the branch

cd ~ && git clone https://github.com/kizniche/Mycodo && cd Mycodo && git checkout new_sensor_atlas_ph && sudo /bin/bash ~/Mycodo/install/setup.sh

This will install and start Mycodo that is updated with the new sensor code. You will be able to update from this version of Mycodo if I commit new code to this branch by doing the following:

sudo /etc/init.d/apache2 stop && sudo service mycodo stop && cd ~/Mycodo && sudo git pull && sudo /bin/bash ~/Mycodo/mycodo/scripts/upgrade_commands.sh initialize && sudo /etc/init.d/apache2 start && sudo service mycodo start

When I finally release the next version of Mycodo, you can use the upgrade menu of the Mycodo web UI to upgrade as you would if you were on a normal release, without having to reinstall.

Let me know if you have any issues with the commands above, or while testing the new sensor code.

leoneo commented 7 years ago

I have this error when I click on Activate : Could not activate Sensor controller with ID 4: [Errno 2] could not open port /dev/ttyS0: [Errno 2] No such file or directory: '/dev/ttyS0'

Daemon log :

  ready, cont_id)
  File "/var/www/mycodo/mycodo/controller_sensor.py", line 260, in __init__
    self.measure_sensor = AtlaspHUARTSensor()
  File "/var/www/mycodo/mycodo/sensors/atlas_ph_uart.py", line 27, in __init__
    bytesize=serial.EIGHTBITS)
  File "/var/www/mycodo/env/local/lib/python2.7/site-packages/serial/serialutil.py", line 182, in __init__
    self.open()
  File "/var/www/mycodo/env/local/lib/python2.7/site-packages/serial/serialposix.py", line 245, in open
    raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
SerialException: [Errno 2] could not open port /dev/ttyS0: [Errno 2] No such file or directory: '/dev/ttyS0'
2017-04-17 09:55:25,731 - mycodo.sensor_1 - ERROR - StopIteration raised. Possibly could not read sensor. Ensure it's connected properly and detected.
2017-04-17 09:56:10,908 - mycodo.sensor_1 - ERROR - StopIteration raised. Possibly could not read sensor. Ensure it's connected properly and detected.
2017-04-17 09:56:56,113 - mycodo.sensor_1 - ERROR - StopIteration raised. Possibly could not read sensor. Ensure it's connected properly and detected.
2017-04-17 09:57:29,141 - mycodo - WARNING - Sensor controller with ID 4 not found
2017-04-17 09:57:41,331 - mycodo.sensor_1 - ERROR - StopIteration raised. Possibly could not read sensor. Ensure it's connected properly and detected.
2017-04-17 09:57:43,241 - mycodo - ERROR - Could not activate Sensor controller with ID 4: [Errno 2] could not open port /dev/ttyS0: [Errno 2] No such file or directory: '/dev/ttyS0'
Traceback (most recent call last):
  File "/var/www/mycodo/mycodo/mycodo_daemon.py", line 415, in controller_activate
    ready, cont_id)
  File "/var/www/mycodo/mycodo/controller_sensor.py", line 260, in __init__
    self.measure_sensor = AtlaspHUARTSensor()
  File "/var/www/mycodo/mycodo/sensors/atlas_ph_uart.py", line 27, in __init__
    bytesize=serial.EIGHTBITS)
  File "/var/www/mycodo/env/local/lib/python2.7/site-packages/serial/serialutil.py", line 182, in __init__
    self.open()
  File "/var/www/mycodo/env/local/lib/python2.7/site-packages/serial/serialposix.py", line 245, in open
    raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
SerialException: [Errno 2] could not open port /dev/ttyS0: [Errno 2] No such file or directory: '/dev/ttyS0'
2017-04-17 09:58:26,558 - mycodo.sensor_1 - ERROR - StopIteration raised. Possibly could not read sensor. Ensure it's connected properly and detected.
2017-04-17 09:59:11,774 - mycodo.sensor_1 - ERROR - StopIteration raised. Possibly could not read sensor. Ensure it's connected properly and detected.

I have the raspberry Pi 2 normally the open port is ttyAMA0 and not ttyS0 ?

kizniche commented 7 years ago

Are you sure you don't have a Pi 3? The code chat determines the device indicates you do:

        if GPIO.RPI_INFO['P1_REVISION'] == 3:
            self.serial_device = "/dev/ttyS0"
        else:
            self.serial_device = "/dev/ttyAMA0"
        self.ser = serial.Serial(port=self.serial_device,
                                 baudrate=38400,
                                 parity=serial.PARITY_NONE,
                                 stopbits=serial.STOPBITS_ONE,
                                 bytesize=serial.EIGHTBITS)

Can you go to the [Gear Icon] -> System Information page and copy the first output line from the "gpio readall" command?

kizniche commented 7 years ago

Nevermind, my mistake, 3 would indicate a Pi2.

leoneo commented 7 years ago

+-----+-----+---------+------+---+---Pi 2---+---+------+---------+-----+-----

Yes Pi 2

kizniche commented 7 years ago

What was the procedure you used to set up the UART?

kizniche commented 7 years ago

Did you follow the instructions provided by Mycodo?

kizniche commented 7 years ago

Found in the manual UART section.

leoneo commented 7 years ago

the UART procedure : https://github.com/AtlasScientific/Raspberry-Pi-sample-code

kizniche commented 7 years ago

Okay, let me review that

leoneo commented 7 years ago

I have not use the instruction UART Mycodo

kizniche commented 7 years ago

In the documentation you provided, the steps outline the same steps I linked to, which opens /dev/ttyS0 as your UART device.

leoneo commented 7 years ago

maybe linux permission to access ttyAMA0 is not good

I need to use sudo command in Terminal to acces and read pH with my python code.

If I run my code directly with Python 2.7 I have error permission

kizniche commented 7 years ago

Mycodo has root privileges to access devices, but what I'm wondering is how you got your UART device to be /dev/ttyAMA0 when the guide you linked to sets UART up on /dev/ttyS0

leoneo commented 7 years ago

I have change atlas_ph_uart.py line by

    if GPIO.RPI_INFO['P1_REVISION'] == 3:
        self.serial_device = "/dev/ttyAMA0"
    else:

and reboot

There are now no error and the light sensor indicate reading value every 15s

but in live page pH value still 0 pH no data last 30min

kizniche commented 7 years ago

I have change atlas_ph_uart.py line by

Just be aware I don't intend to change this part of the code that selects the proper UART device, as it appears you deviated from the setup instructions at some point. This needs to remain standardized, and I'm going to keep it the same instructions for setting up UART for the K30 CO2 sensor.

With that being said, I'm going to update the code to put some logging lines in to see what's going on in the sensor module itself. Once I push the commit (watch for its mention in this thread), you can use the update instructions, above, to get the new code. Then you can activate the sensor and view the daemon log to see the new debug logging line that occurs at every sensor measurement.

kizniche commented 7 years ago

I found an error in the module. The next commit (in a few minutes) should get the measurements turning up in Mycodo.

kizniche commented 7 years ago

Can you try to get this code working again: https://github.com/AtlasScientific/Raspberry-Pi-sample-code/blob/master/uart.py

The working code you provided appears to have extraneous commands and I would like to clean it all up by using their version of the code.

kizniche commented 7 years ago

Can you provide a photo of your pH circuit board?

leoneo commented 7 years ago

Hi Kyle,

the code https://github.com/AtlasScientific/Raspberry-Pi-sample-code/blob/master/uart.py works well with changing bauderate 9600 by 38400.

In mycodo i still no receive pH data.

How can I put the photos here ?

kizniche commented 7 years ago

There is now a debug line that should be printing in the daemon log upon every sensor read. Please paste the output if it's there.

Images can be dragged into the text input box to attach.

leoneo commented 7 years ago
2017-04-19 19:01:45,954 - mycodo.sensors.atlas_ph - INFO - RETURNED VALUE: 6.77
6.77
2017-04-19 19:01:45,955 - mycodo.sensor_4 - ERROR - Error while attempting to read sensor: Unknown format code 'f' for object of type 'str'
Traceback (most recent call last):
  File "/var/www/mycodo/mycodo/controller_sensor.py", line 709, in update_measure
    measurements = self.measure_sensor.next()
  File "/var/www/mycodo/mycodo/sensors/atlas_ph_uart.py", line 52, in next
    return dict(ph=float('{0:.2f}'.format(self._ph)))
ValueError: Unknown format code 'f' for object of type 'str'
2017-04-19 19:02:00,993 - mycodo.sensors.atlas_ph - ERROR - AtlaspHUARTSensor raised an exception when taking a reading: argument of type 'NoneType' is not iterable
2017-04-19 19:02:00,994 - mycodo.sensor_4 - ERROR - StopIteration raised. Possibly could not read sensor. Ensure it's connected properly and detected.
kizniche commented 7 years ago

Okay, that error can be fixed by enclosing self._ph on line 52 of ~/Mycodo/mycodo/sensors/atlas_ph_uart.py with float(). You can modify it to look like return dict(ph=float('{0:.2f}'.format(float(self._ph)))) to test if it fixes that issue.