Distance Sensor readings integration to YetiWeb.py

Hello,

---project little rover running Zeroborg with added distance sensor----

I would like to add an ultra-sonic sensor for simple continuous readings of distance to obstacle.
The idea is to show the sensor readings from the sensor using the web server interface YetiWeb.py

I need guidance to implement this idea. What would be the most pythonic/effective way to go about it?

So far, I have tried to merge both stand alone python scripts in attachment unsuccessfully.
My attempt of creating a separate thread in YetiWeb.py to do the job fell flat as I cannot combine both my Threading script with Distance script into one working script.

Maybe using an already existing thread in YetiWeb.py is the solution... or not using any threads at all could do as well.

Really not sure what type of object I should use to be honest.

Would appreciate if I could be pointed into the right direction.

Thanks -Marc

Images: 
piborg's picture

I have had a look at your code and tried to combine the two into a single threaded script.
At the bottom it has a little loop which gets the reading and prints it out.

If that works fine it should be something that you can merge into the YetiWeb.py script :)

Attached as a file below.

import threading
import RPi.GPIO as GPIO
import time

# The value used to hold the distance
global distance
distance = 0

#GPIO Mode (BOARD / BCM)
GPIO.setmode(GPIO.BCM)
 
#set GPIO Pins
GPIO_TRIG = 23
GPIO_ECHO = 24
 
#set GPIO direction (IN / OUT)
GPIO.setup(GPIO_TRIG, GPIO.OUT)
GPIO.setup(GPIO_ECHO, GPIO.IN)

class Sensor(threading.Thread):
 
    def __init__(self,interval):    
        self.inter = interval
        self.terminated = False
        self.start()

    def run(self):
        global distance
        while not self.terminated:
            # set Trigger to HIGH
            GPIO.output(GPIO_TRIG, True)
         
            # set Trigger to LOW after 0.01ms 
            time.sleep(0.00001)
            GPIO.output(GPIO_TRIG, False)
         
            StartTime = time.time()
            StopTime = time.time()
         
            # save StartTime
            while GPIO.input(GPIO_ECHO) == 0:
                StartTime = time.time()
         
            # save time of arrival
            while GPIO.input(GPIO_ECHO) == 1:
                StopTime = time.time()
         
            # time difference between start and arrival
            TimeElapsed = StopTime - StartTime
            # multiply by sonic speed (34300 cm/s)
            # and divide by 2, because there and back
            distance = (TimeElapsed * 34300) / 2
         
            time.sleep(self.inter)

SensorA = Sensor(1)

# Example bit of code to read the value from the thread
global distance
try:
    while True:
        print("Measured Distance = %.1f cm" % distance)
        time.sleep(1)
except KeyboardInterrupt:
    GPIO.cleanup()
    SensorA.terminated = True
Attachments: 

Thanks for having a go at it. Really appreciate since I am so unfamiliar with threading objects.
Good effort and that is much better than what I came up with scope-wise!

Unfortunately, there is still something wrong with, I believe, the scoping of variable distance.
I spent a little while tweaking the script but cannot make it run. I will have to read the documentation for further understanding of the Thread object.

The way I would approach it: variable distance should be an attribute of the Sensor object. Distance can be read be invoking a method to get() the changing distance of the sensor object. I would get rid of global distance and try to fully encapsulate distance within sensor/thread object.

what do you think?

piborg's picture

That would be a cleaner way of doing things, particularly if you are planning to add more sensors later :)

There you go! I went for a thread base class, which is probably not the most robust/pythonesc implementation of all but it works (accessing an object's attribute directly is not recommended)

Note the GPIOZero library already provides supports for the ultrasonic sensor

Hello - I am yet to integrate the continuous reading value of the sensor to the web page frame shape. I removed the motors percentage reading and would like to display a continuous distance-to-obstacle info instead.

From my understanding, some syntax along the lines of....

httpText += '<center>"Obstacle in " %f "cm"</center>\n' % (SensorA.dist)

....should be added to the following part of yetiWeb.py:

elif getPath == '/stream':
            # Streaming frame, set a delayed refresh
           # httpText += '<body onLoad="setTimeout(\'refreshImage()\', %d)">\n' % (displayDelay)
            httpText += '<center><img src="/cam.jpg" style="width:640;height:480;" name="rpicam"/</center>\n'
            httpText += '<center>"Obstacle in " %f "cm"</center>\n' % (SensorA.dist)
            httpText += '</body>\n'
            httpText += '</html>\n'

Any help on getting the syntax corrected would be very much appreciated
Thanks,

Marc

piborg's picture

You are basically correct with the syntax for the line, but the " symbols are not really needed:

httpText += '<center>Obstacle in %f cm</center>\n' % (SensorA.dist)

Getting the reading to keep refreshing is a little more difficult. First I would add a new section which returns the values only:

elif getPath.startswith('/distance'):
    # Get the latest distance readings
    httpText = '<html><body>'
    httpText += '<center>Obstacle in %f cm</center>\n' % (SensorA.dist)
    httpText += '</body></html>'
    self.send(httpText)

You can check this works by changing the page URL to end with /distance and check you get a single reading.

The stream section does not need updating but the / section does. What we need to do is:

  1. Add a function to update the distance (refreshDistance)
  2. Add some code to call this function at an interval
  3. Add an iframe to display the result

This gives us something like:

elif getPath == '/':
    # Main page, click buttons to move and to stop
    httpText = '<html>\n'
    httpText += '<head>\n'
    httpText += '<script language="JavaScript"><!--\n'
    httpText += 'function Drive(left, right) {\n'
    httpText += ' var iframe = document.getElementById("setDrive");\n'
    httpText += ' var slider = document.getElementById("speed");\n'
    httpText += ' left *= speed.value / 100.0;'
    httpText += ' right *= speed.value / 100.0;'
    httpText += ' iframe.src = "/set/" + left + "/" + right;\n'
    httpText += '}\n'
    httpText += 'function Off() {\n'
    httpText += ' var iframe = document.getElementById("setDrive");\n'
    httpText += ' iframe.src = "/off";\n'
    httpText += '}\n'
    httpText += 'function Photo() {\n'
    httpText += ' var iframe = document.getElementById("setDrive");\n'
    httpText += ' iframe.src = "/photo";\n'
    httpText += '}\n'
    httpText += 'function refreshDistance() {\n'
    httpText += ' document.getElementById("distance").src = "/distance?" + Math.random();\n';
    httpText += '}\n'           
    httpTest += 'window.setInterval("refreshDistance();", 200);'
    httpText += '//--></script>\n'
    httpText += '</head>\n'
    httpText += '<body>\n'
    httpText += '<iframe src="/stream" width="100%" height="500" frameborder="0"></iframe>\n'
    httpText += '<iframe id="distance" src="/distance" width="100%" height="50" frameborder="0"></iframe>\n'
    httpText += '<iframe id="setDrive" src="/off" width="100%" height="50" frameborder="0"></iframe>\n'
    httpText += '<center>\n'
    httpText += '<button onclick="Drive(-1,1)" style="width:200px;height:100px;"><b>Spin Left</b></button>\n'
    httpText += '<button onclick="Drive(1,1)" style="width:200px;height:100px;"><b>Forward</b></button>\n'
    httpText += '<button onclick="Drive(1,-1)" style="width:200px;height:100px;"><b>Spin Right</b></button>\n'
    httpText += '<br /><br />\n'
    httpText += '<button onclick="Drive(0,1)" style="width:200px;height:100px;"><b>Turn Left</b></button>\n'
    httpText += '<button onclick="Drive(-1,-1)" style="width:200px;height:100px;"><b>Reverse</b></button>\n'
    httpText += '<button onclick="Drive(1,0)" style="width:200px;height:100px;"><b>Turn Right</b></button>\n'
    httpText += '<br /><br />\n'
    httpText += '<button onclick="Off()" style="width:200px;height:100px;"><b>Stop</b></button>\n'
    httpText += '<br /><br />\n'
    httpText += '<button onclick="Photo()" style="width:200px;height:100px;"><b>Save Photo</b></button>\n'
    httpText += '<br /><br />\n'
    httpText += '<input id="speed" type="range" min="0" max="100" value="100" style="width:600px" />\n'
    httpText += '</center>\n'
    httpText += '</body>\n'
    httpText += '</html>\n'
    self.send(httpText)

Lines 21 to 23 are the new function, line 24 calls this function every 200 ms, and line 29 is the new display. Everything else is as the standard example.

Thanks a lot for helping with the HTML. It is the very last hurdle in my project and I think all is now provided and explained to complete the distance reading integration to the frame.

I have had a go at it but couldn't avoid some issues on the main "/" address. The "/distance" directory worked fine and showed a single reading as expected.

This deserve more time to check and test. I need to put everything together again from a fresh yetiWeb.py file and test again to understand whether there is something missing or not.

Bear with me until I thoroughly take it from scratch integrating the sensor object + new frame refresh.

I will be back in touch next week.
Marc

Hello there

The integration of the sensor reading thread works only for a very short while before stopping. I believe a watchdog to reset the thread whenever it times out is necessary to achieve continuous reading when integrated to yetiWeb.py

Could the already existing Watchdog class be used to take care of my sensor thread timing out?

Any other implementation suggestion is welcome.

Marc

piborg's picture

It is possible that a copy of the current watchdog thread which does something else on a shorter time out might work.

The question is what is happening to cause the sensor thread to time out in the first place? If you can fix that then a watchdog is probably not needed.

Does the camera image also seem to stop, or is it just the readings?

The camera keeps running. Only the distance reading is stopping. Agreed, I need to further test to understand the root cause. I shall report back once I made progress with debugging.

ps: I tried the GPIO Zero library sensor class. From what I could see, it is not working well at all for now.

piborg's picture

That is interesting, it suggests the problem is more likely to be around the thread, rather than the HTML code. Let us know what you find out, more heads are better when it come to debugging these type of problems :)

I am a little surprised about the gpiozero DistanceSensor class as it looks like it should be written with at this type of use in mind from a quick glance.

Subscribe to Comments for &quot;Distance Sensor readings integration to YetiWeb.py&quot;