Water Level Sensor

I’m looking to use this for my hydroponics system to monitor water level in a bucket. However, it doesn’t have any python code listed. Is there anyone who has started writing this and would like to share or point me in the right direction?

Hi @vondalej, which sensor you are using?

From the Grove - Water Level Sensor WiKi page it shows only Arduino library is completed. @Baozhu any updates on this.

Capture

Not yet, I’m sure we’ll have an update in the coming months.

2 Likes

That’s great :slightly_smiling_face::+1:

Any movement on getting this written??

Hi there,

I’m also interested in working with this sensor in Python. Anyone tried this already?

Actually, I just tried it. My code seems to work, but at some point it showed “Remote I/O error” (I’m not sure if I might have induced the error by accidentally touching the wires on the breadboard, though)

I had to compile the micropython library on my Pi: https://www.raspberrypi.org/forums/viewtopic.php?t=191744

Please see the code and the screenshots:

import time
import smbus
import numpy as np

NO_TOUCH = 0xFE
THRESHOLD = 100
ATTINY1_HIGH_ADDR = 0x78
ATTINY2_LOW_ADDR = 0x77

i2c_ch = 1
i2c_address1 = 0x77
i2c_address2 = 0x78

# Register addresses -- don't know what they are for
reg_temp = 0x00
reg_config = 0x01 # offset?

bus = smbus.SMBus(i2c_ch)

def getHigh12SectionValue():
    high_data = bus.read_i2c_block_data(ATTINY1_HIGH_ADDR, reg_config, 12)
    return high_data

def getLow8SectionValue():
    low_data = bus.read_i2c_block_data(ATTINY2_LOW_ADDR, reg_config, 8)
    return low_data
    
def check_water_level():
    sensorvalue_min = int(250)
    sensorvalue_max = int(255)
    low_count = int(0)
    high_count = int(0)
    
    touch_val = int(0)
    trig_section = int(0)
    low_count = 0
    high_count = 0
    
    low_data = getLow8SectionValue()
    high_data = getHigh12SectionValue()
    
    for i in range(8):
        if low_data[i] > THRESHOLD:
            touch_val += 1
       
    for i in range(12):
        if high_data[i] > THRESHOLD:
            touch_val += 1
    
    value = touch_val * 5
    print("water level = " + str(value) + "%")


while True:
    check_water_level()
    time.sleep(2)

Here’s a short video on how my code works:

Here’s the error message I got in the end, but now the code runs for a few minutes and nothing happens - so it probably was by accident:

I believe my code lacks some sensor diagnostics, e.g. if the bus actually could be read, or - I’m not using the NO_TOUCH variable, which probably determines some sensor parameters or readouts. I did not figure out how to use it from the Arduino code. If you would have any ideas on how to improve this, please share :slight_smile:

1 Like

Hi @mormegil6
WaW!Very good python code, can I update this code to our Wiki?
(I will sign your name for thank you.)
The OSError: [Errno 121] Remote I/O error offer occurred when the external hardware cannot be found, so you may not have the I2C permission, or the I2C address setting is incorrect, or the wiring is bad.

1 Like

Thanks @jiachenglu :slight_smile:

I am not sure if the code is that good - I think it could be smoother without the for loops, and use vector operations instead. My code might be polished a bit in this regard. Sure, you might use this code for the Wiki as it is right now, or you might wait for the polished version :wink:

Generally, some parts of code could be shorter, but I wanted my code to resemble the Arduino code as much as possible.
Furthermore, the part where I am declaring reg_temp and reg_config variables - I have found them in some other Python example regarding reading raw data from I2C bus (which was about the temperature sensor, hence reg_temp). I am also quite unsure about the meaning reg_config variable - from what I read in the documentation of the function it’s used, read_i2c_block_data(), the second value this function accepts is “offset”.
Additionally, I forgot to remove some unused variables and unused references, ie. i2c_address1 and i2c_address2, which are duplicates of ATTINY2_LOW_ADDR and ATTINY1_HIGH_ADDR. Also, I am not using the numpy library; I was considering using it but in its current shape it’s not needed.

Hi @mormegil6
Thanks for your python code, I followed the steps to build micropython and got the below output.
By the way, we are waiting for your polished version.
water

Hi @jiachenglu,
Sorry for the long time without a reply. I was extremely busy for the past 2 weeks, and I still am but not for long. I hope to get back to this code by the end of this week :slight_smile:

Hello @jiachenglu,
Sorry for the time it took. I reviewed my code, and I deleted unused variables etc. I did leave the for loops. I believe they can be done in a more fancy way ie. using vector logic, but I think they work quite straight-forward and they don’t need such fanciness.
Here, you can post this code on your Wiki page for Raspberry Pi / Python:

import time
import smbus
import numpy as np

NO_TOUCH = 0xFE # unused variable
THRESHOLD = 100
ATTINY1_HIGH_ADDR = 0x78
ATTINY2_LOW_ADDR = 0x77

i2c_ch = 1

reg_temp = 0x00 # register address -- temperature? / Unused variable
reg_config = 0x01 # register address -- offset?

bus = smbus.SMBus(i2c_ch)

def getHigh12SectionValue():
    high_data = bus.read_i2c_block_data(ATTINY1_HIGH_ADDR, reg_config, 12)
    return high_data
    
def getLow8SectionValue():
    low_data = bus.read_i2c_block_data(ATTINY2_LOW_ADDR, reg_config, 8)
    return low_data
    
def check_water_level():
    touch_val = int(0)
    
    low_data = getLow8SectionValue()
    high_data = getHigh12SectionValue()
    
    for i in range(8):
        if low_data[i] > THRESHOLD:
            touch_val += 1
        
    for i in range(12):
        if high_data[i] > THRESHOLD:
            touch_val += 1
        
    value = touch_val * 5
    print("water level = " + str(value) + "%")
	return value / 100

while True:
    sensorLevel = check_water_level()
    return sensorLevel
	time.sleep(2)

One more thing - I have made a little script that logs the sensor data. It was logging the water level with 5min time gap between readouts. I have noticed occasional spikes in the water level read. It should always show 0%, but sometimes it shows 5%, or even 60%:

Thanks for doing this! Had a few issues though. There’s a few indents when spaces were used for the rest. Also I’m having an issue with return sensorLevel is giving an error that it’s out of the function.

If I remove it and use print instead it starts functioning. The biggest issue is that it is always reading 100%. There were a few times where the first reading was 0 or 10% (which was right), but now i’ve got it out of the water and its 100%.

In looking further I’m seeing issues with 2 things. The threshold figure and the * 5.

If I make the threshold 101 and remove the * 5 i’m getting the 20% that it should be. I haven’t been able to test it as it’s jammed into my hydroponics system, but I’ll watch it.

@vondalej I don’t know why you needed to change the threshold value or remove the * 5 multiplier. I took it from the Arduino code example, and it worked just fine - as you can see on my YouTube video. I think @jiachenglu might comment more on that.

Regarding the other part, you’re right - the last return sensorLevel shouldn’t be there if you want to read the water level values in the command line terminal. I am using this as part of another program, therefore the return is needed - I’m logging the data to Google Spreadsheets. Please see the code corrected once again, which should run straigt out of “copy paste” :wink:

Also, I have found another issue with the sensor. When I start my code with the sensor submerged in the water container up to 100%, it shows 0% - as if it calibrated the initial state and detected it as 0. When I take my sensor out of water, dry it, then start it, and then submerge it, it shows the values correctly. I think it is quite an issue, that the sensor does not show correct values regardless of its initial state.
@vondalej maybe your issue also has something to do with sensor’s initial state?

The code:

import time
import smbus
import numpy as np

NO_TOUCH = 0xFE # unused variable
THRESHOLD = 100
ATTINY1_HIGH_ADDR = 0x78
ATTINY2_LOW_ADDR = 0x77

i2c_ch = 1

reg_temp = 0x00 # register address -- temperature? / Unused variable
reg_config = 0x01 # register address -- offset?

bus = smbus.SMBus(i2c_ch)

def getHigh12SectionValue():
    high_data = bus.read_i2c_block_data(ATTINY1_HIGH_ADDR, reg_config, 12)
    return high_data
    
def getLow8SectionValue():
    low_data = bus.read_i2c_block_data(ATTINY2_LOW_ADDR, reg_config, 8)
    return low_data
    
def check_water_level():
    touch_val = int(0)
    
    low_data = getLow8SectionValue()
    high_data = getHigh12SectionValue()
    
    for i in range(8):
        if low_data[i] > THRESHOLD:
            touch_val += 1
        
    for i in range(12):
        if high_data[i] > THRESHOLD:
            touch_val += 1
        
    value = (touch_val * 5) / 100
    return value

while True:
    sensorLevel = check_water_level()
    print("water level = " + str(sensorLevel * 100) + "%")
    time.sleep(2)

I am using the Grove Water Level Sensor 10CM SKU 101020635 on a Raspberry Pico RP2040.
The odd thing is, that it always start at 0%, if it is soaked in water.
To get a valid reading, I have to pull it out of the water, dry it, and put it back in.
Is there a way around this, or it it by design, that I need to pull it out first, and dry this sensor before running my code?

Result when soaked & code started:
bytearray(b’\x00\x00\x00\x00\x00\x00\x00\x00’) bytearray(b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00’)
water level = 0%

Result when dry & code started & then soaked:
bytearray(b’\xf9\xfa\xf8\xf9\xfa\xf9\xfa\xf9’)
bytearray(b’\xf8\xf8\xf9\xf8\xf9\xf8\xf8\xf8\xf8\xf8\xf8\xf9’)
water level = 100%

My code in MicroPython running in the RP2040:
import board
import busio
import time
#import smbus
from adafruit_bus_device.i2c_device import I2CDevice
#import numpy as np

NO_TOUCH = 0xFE
THRESHOLD = 100
grovewatersensorhigh = 0x78
grovewatersensorlow = 0x77
grovewatersensorhighsize = 12
grovewatersensorlowsize = 8

i2cwaterlevelsensorbus = busio.I2C(scl=board.GP3, sda=board.GP2)

#i2cwaterlevelsensorbus = busio.I2C(scl=board.GP3, sda=board.GP2)
waterlevelinterfacelow = I2CDevice(i2cwaterlevelsensorbus, grovewatersensorlow)
waterlevelinterfacehigh = I2CDevice(i2cwaterlevelsensorbus, grovewatersensorhigh)

def getHigh12SectionValue():
watersensorhighdata = bytearray(grovewatersensorhighsize)
with waterlevelinterfacehigh:
time.sleep(0.01)
waterlevelinterfacehigh.readinto(watersensorhighdata)
return watersensorhighdata

def getLow8SectionValue():
watersensorlowdata = bytearray(grovewatersensorlowsize)
with waterlevelinterfacelow:
time.sleep(0.01)
waterlevelinterfacelow.readinto(watersensorlowdata)
return watersensorlowdata

def check_water_level():
sensorvalue_min = int(250)
sensorvalue_max = int(255)
low_count = int(0)
high_count = int(0)

touch_val = int(0)
trig_section = int(0)
low_count = 0
high_count = 0

low_data = getLow8SectionValue()
high_data = getHigh12SectionValue()
print(low_data, high_data) 
for i in range(8):
    if low_data[i] > THRESHOLD:
        touch_val += 1
   
for i in range(12):
    if high_data[i] > THRESHOLD:
        touch_val += 1

value = touch_val * 5
print("water level = " + str(value) + "%")

while True:
check_water_level()
time.sleep(2)

1 Like

Guys, do you have any solutions to the above problem?