Menu

TAMProxy Synced Sketches

Several teams are having problems with code like this:

class Test(Sketch):
    def setup(self):
        self.ultrashort = DigitalInput(self.tamp, us_pin)
        self.motor = Motor(self.tamp, dir_pin, pwm_pin)
        self.motor.write(1,0) 

    def loop(self):
        # If ultrashort is not triggered, drive forward, else drive reverse
        if self.ultrashort.val:
            self.motor.write(1, 100)
        else:
            self.motor.write(0,100)

if __name__ == "__main__":
    sketch = Test()
    sketch.run()

The problem

The problem, which you can observe by printing self.usread.val, is that the ultrashort sensor is unresponsive (self.ultrashort.val never changes). The reason is subtle, but important: There are two main loops that run on separate processes on your computer: The sketch loop in the code above, and the packet controller's loop that can send and receive a maximum of 1 packet per loop. Typically, because serial operations are slower, your sketch loop will run much more frequently than the packet controller's loop.

This is important because in this sketch and probably nearly everyone's, the sketch loop is queueing 1 packet request every loop iteration. Therefore, the packet controller will always be backed up with packet requests to write a certain pwm to the motor.

When the DigitalInput object is created for the ultra short sensor, it files a "continuous" request with the packet controller. The packet controller will only send this continuous request if it has no pending "explicit" requests (aka the motor write packets). This never happens because of the difference in loop frequencies, so the ultrashort packets never go through, and the value is never updated.

The solution

The solution, as I mentioned briefly in class, is to use a derived class from Sketch called Synced Sketch, which you can import like this: from tamproxy import SyncedSketch

What a synced sketch will do is maintain a basic feedback loop (only proportional) to regulate the ratio of packet loops to sketch loops to a certain value you give it. By doing this, you can slow down your sketch loop just enough to let the packet controller process enough packets. In this case, since we are always adding one packet to the queue per sketch loop, we need to give the packet controller at least two loops for every sketch loop to send that explicit packet and also the sensor's continuous request packet. Indeed, when the ratio is set to 2 or anything higher, the ultrashort sensor becomes fully responsive. Here is the new code:

class Test(SyncedSketch):
    def setup(self):
        self.ultrashort = DigitalInput(self.tamp, us_pin)
        self.motor = Motor(self.tamp, dir_pin, pwm_pin)
        self.motor.write(1,0) 

    def loop(self):
        # If ultrashort is not triggered, drive forward, else drive reverse
        if self.ultrashort.val:
            self.motor.write(1, 100)
        else:
            self.motor.write(0,100)

 if __name__ == "__main__":
     sketch = Test(2, -0.00001, 100)
     sketch.run()

The constructor arguments for SyncedSketch are as follows:

  1. The ratio of packet controller loops to sketch loops desired
  2. The proportional gain of the feedback loop (should be negative). You might need to adjust this, especially on the WinBook
  3. How often to run the feedback adjusting, in number of sketch loop iterations. A value of 100 means that the synced sketch will try to regulate itself every 100 sketch loop iterations.

Concluding notes

Remember that the ratio always needs to be calculated to an appropriate value depending on what you have going on in your sketch. If you have 2 continuous request devices and 2 write packets going out every sketch loop, then you need a minimum ratio of 2+2=4.

I will be working on some better tools to keep track of and benchmark your sketches to monitor these kinds of things. I also know that there is probably a better, more automatic solution to this problem, so I may work on that. Or your CS people can play with it and send me a pull request.