New gauge UI that I am working on

LVGL has a lot of power built into it.

3 Likes

Wow, it’s truely amazing!

This is very cool!
Mind sharing how you did it?

I used a static image for the things that do not change and for the needle I used elliptical math across a couple of ellipses. I wanted to use lv_canvas do render to so the needle would move with the background if the screen was scrolled. lv_canvas doesn’t support alpha so that was a bust. I ended up using the LV_DRAW_PART_END event and lv_draw_ctx to render the needle instead as this supports alpha channel. Issue with doing that is the needle doesn’t move with the background if scrolled, it stays locked in place on the display. I could correct the issue using one of the SCROLL events and moving the needle when scrolling is taking place. I just haven’t got around to messing with it yet.

Unfortunately the code is too large for the online simulator to be able to save it. So I am not able to share a link to it so you can see how I went about it.

performance isn’t all that bad either. Considering it is written in Python and also the simulator not having the native or viper code emitters available. That last thing is a HUGE performance hit.

Hi @kdschlosser

cool widget! What about to accelerate this job using GPU like NXP’s VGLite?

Mike

anything that LVGL has built in internally to use 2d accelerators I am sure would help with the rendering part. There is nothing that is built into LVGL to handle an ellipsis. There is code for an arc/circle. A circle is not an ellipsis but an ellipsis is a circle. kind of like the square and the rectangle. All squares are rectangles but not every rectangle is a square. The code to calculate angles given a point, or to calculate a point given an angle in an ellipsis also works on a circle but the same does not apply in reverse.

I was actually kind of surprised to see circle math and not elliptical math built into LVGL

Here is the code that does the heavy lifting. There is a lot of floating point math involved and lv_point_t does not hold floats, only integers, so I made a class that holds the float values and can be easily turned into an lv_point_t instance for use when calling an lvgl function.

pi2 = 2.0 * math.pi

class Point(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y

    @property
    def lv_point(self):
        return lv.point_t({'x': int(self.x), 'y': int(self.y)})

    def __str__(self):
        return 'Point(x={}, y={})'.format(int(self.x), int(self.y))


class Ellipse(object):

    def __init__(
        self,
        x,
        y,
        radius_horizontal,
        radius_vertical
    ):
        self.x = x
        self.y = y
        self.radius_horizontal = radius_horizontal
        self.radius_vertical = radius_vertical

    def __str__(self):
        return 'Ellipse(x={}, y={}, radius_horizontal={}, radius_vertical={})'.format(self.x, self.y, self.radius_horizontal, self.radius_vertical)

    @property
    def center(self):
        return Point(self.x, self.y)

    def get_angle(self, point):
        return math.degrees(math.atan2(self.y - point.y, self.x - point.x)) + 180

    def get_point(self, a):
        x = self.x
        y = self.y
        rh = self.radius_horizontal
        rv = self.radius_vertical

        ang = math.radians(a)
        cos = math.cos(ang)  # axis direction at angle ang
        sin = math.sin(ang)
        aa = x * x
        bb = y * y  # intersection between ellipse and axis
        t = aa * bb / ((cos * cos * bb) + (sin * sin * aa))
        cos *= t
        sin *= t
        sin *= x / y  # convert to circle
        ea = math.atan2(sin, cos)  # compute elliptic angle

        if ea < 0.0:
            ea += pi2  # normalize to <0,pi2>
        
        cos = math.cos(ea)
        sin = math.sin(ea)
        ta = sin / cos  # tan
        tt = ta * rh / rv  # tan
        d = 1.0 / math.sqrt(1.0 + tt * tt)
        x += math.copysign(rh * d, cos)
        y += math.copysign(rv * tt * d, sin)
        return Point(x, y)

    def __contains__(self, point):
        # checking the equation of
        # ellipse with the given point
        p = (
            (((point.x - self.x) ** 2) / (self.radius_horizontal ** 2)) +
            (((point.y - self.y) ** 2) / (self.radius_vertical ** 2))
        )

        return p > 1


def get_distance(point1, point2):
    return math.sqrt((point2.x - point1.x) ** 2 + (point2.y - point1.y) ** 2)

I am no math wiz and I am sure there is a better way to calculate the angles properly. If someone knows how to do this and is willing to share I am all ears. In order to use the viper code emitter in MicroPython and gain the most it has to offer I would need to convert all of the above code into integer math instead of floating point. Not too hard to do it just makes for a lot more code. I know the math functions in Micropython just make calls to the functions located in the std lib for C. so nothing to gain performance wise by replicating the code. Only going to save the time it takes to make the function calls which doesn’t amount to a whole lot.

got the performance issue sorted out.

2 Likes

Really nice!