Piechart and Meters Reading using Wio Terminal and Ardupy

It has been 3 months since we released Wio Terminal. We have got a lot of positive feedbacks and awesome projects from the community. Please refer to our continuously updating blog Resource Roundup for Wio Terminal: Tutorials, Reviews, and Projects from Community to get more details for Wio Terminal. Thank you all for liking this cute IoT device!

In this blog, I will take you to look into how to simply use Ardupy to show piechart and meters reading on Wio Terminal. Firstly, let’s figure out what is Ardupy in case you haven’t heard about this extremely easy IDE just released by the Seeed team.

Brief introduction of ArduPy

ArduPy is a combination of Arduino and MicroPython. MicroPython uses the universal Arduino API to control the hardware so that it can be quickly compatible with new platforms. At the same time, we provide aip package manager. You can transform your favourite Arduino library into a MicroPython library. Through ArduPy, you can use Python to verify ideas quickly, learn python programming, and learn Open-Source hardware.

Getting Started

To get started with ArduPy, first need to install aip – ArduPy Integrated Platform is a utility to develop ArduPy and interact witch ArduPy board. It enables users to quickly get started with Ardupy. aip is meant to be a simple command-line tool. You can customize your own Ardupy firmware through it, without needing to know more details about ArduPy.

Install with macOS

For macOS user, you can simply run the following in Terminal to install ArduPy-aip:

pip3 install ardupy-aip 

Note: please make sure that you have installed Python 3 on macOS. To install Python 3, you can use Homebrew which is a software package manager for macOS. Once installed Homebrew, run the following to install python 3: brew install python3. You can also download and install Python 3 from the official Python website.

Install with Windows

You can download the latest versions of ArduPy for windows in the below session. Once downloaded, extract the binary aip into a directory that is in your PATH.

OR if you have Python 3 environment, you can also install using pip3:

pip3 install ardupy-aip 

Install with Linux

For Linux user, you can simply run the following in Terminal to install ArduPy-aip:

pip3 install ardupy-aip 

Note: please make sure you have Python 3 installed and pip3 is up to date.

ArduPy-aip CLI Getting Started

aip is a Command-Line Interface (CLI) for ArduPy, you can use aip to install ArduPy libraries, build and flash ArduPy firmware to hardware with ease. Simply use help to get more information:

aip help
  • To get board information:
aip board 
  • To install Arduino libraries binding with ArduPy:
aip install <ArduPy LibraryPath> 
# Example Usage: 
# aip install Seeed-Studio/seeed-ardupy-ultrasonic-sensor
  • To uninstall ArduPy libraries:
aip uninstall <ArduPy LibraryPath> 
# Example Usage: 
# aip uninstall Seeed-Studio/seeed-ardupy-ultrasonic-sensor
  • To list installed ArduPy Libraries :
aip list
  • To build ArduPy Firmware contains the libraries you installed and the basic ArduPy features:
aip build 
  • To flash ArduPy Firmware into hardware:
aip flash 
  • To interact with the board (shell-based file explorer):
aip shell

Note: Once entered the ardupy-mpfshell, you can use help for more information and commands.

  • To run Python file:
aip shell -n -c "runfile <YourPythonFilePath> [Path]"
# Example Usage:
# aip shell -n -c "runfile /Users/ansonhe/Desktop/ur.py"
  • To load files into the board using the shell:
aip shell -n -c "put <YourPythonFilePath> [Path]"
# Example Usage:
# aip shell -n -c "put /Users/ansonhe/Desktop/ur.py"
 
  • Entering REPL mode:
aip shell -c "repl" 

For further detailed instructions about ArduPy, please visit ArduPy Getting Started to explore.

Immediate usages

Here are two samples illustrating the outcomes of using Wio Terminal with ArduPy:

Usage 1 (PieChart):

aip shell -n -c "runfile <YourPythonFilePath> [Path]"
# Example Usage:
# aip shell -n -c "runfile F:\Ardupy-wio_terminal\PieChart.py

And the outcome will display on the screen of Wio Terminal as below if all settings are installed properly

Usage Code:

from machine import LCD, Sprite
import time, math

DEG2RAD = 0.0174532925
lcd = LCD()
lcd.fillScreen(lcd.color.BLACK)

# DRAW CIRCLE SEGMENTS
# x,y == coords of centre of circle
# start_angle = 0 - 359
# sub_angle   = 0 - 360 = subtended angle
# r = radius
# colour = 16 bit colour value

def fillSegment(x, y, startAngle, subAngle, r, color):
    # Calculate first pair of coordinates for segment start
    sx = math.cos((startAngle - 90) * DEG2RAD)
    sy = math.sin((startAngle - 90) * DEG2RAD)
    x1 = sx * r + x
    y1 = sy * r + y

    # Draw colour blocks every inc degrees
    for i in range(startAngle, startAngle+subAngle, 1):
        # Calculate pair of coordinates for segment end
        x2 = math.cos((i + 1 - 90) * DEG2RAD) * r + x
        y2 = math.sin((i + 1 - 90) * DEG2RAD) * r + y

        lcd.fillTriangle(int(x1), int(y1), int(x2), int(y2), x, y, color)

        # Copy segment end to segment start for next segment
        x1 = x2
        y1 = y2
    
def main():
    # Draw 4 pie chart segments
    fillSegment(160, 120, 0, 60, 100, lcd.color.RED)
    fillSegment(160, 120, 60, 30, 100, lcd.color.GREEN)
    fillSegment(160, 120, 60 + 30, 120, 100, lcd.color.BLUE)
    fillSegment(160, 120, 60 + 30 + 120, 150, 100, lcd.color.YELLOW)
    time.sleep(1)
    fillSegment(160, 120, 0, 360, 100, lcd.color.BLACK)

if __name__ == "__main__":
    while True:
        main()

Usage 2 (Meter):

aip shell -n -c "runfile  [Path]"
# Example Usage:
# aip shell -n -c "runfile F:\Ardupy-wio_terminal\Meter.py"

Usage code:

from machine import LCD
import time, math

M_SIZE = 1.3333
LOOP_PERIOD = 35

ltx = 0
osx = M_SIZE * 120
osy = M_SIZE * 120
updateTime = 0
old_analog = -999
d = 0

tft = LCD()
tft.fillScreen(tft.color.BLACK)

def valmap(value, istart, istop, ostart, ostop):
  return ostart + (ostop - ostart) * ((value - istart) / (istop - istart))

def plotNeedle(value, ms_delay):
    global old_analog
    global osx, osy, ltx
    tft.setTextColor(tft.color.BLACK, tft.color.WHITE)
    if (value < -10):
        value = -10   # Limit value to emulate needle end stops

    if (value > 110):
        value = 110
    
    while(value != old_analog):
        if (old_analog < value):
            old_analog+=1
        else:
            old_analog-=1
    
        if (ms_delay == 0):
            old_analog = value
        
        sdeg = valmap(old_analog, -10, 110, -150, -30) # Map value to angle
        # Calculate tip of needle coords
        sx = math.cos(sdeg * 0.0174532925)
        sy = math.sin(sdeg * 0.0174532925)

        # Calculate x delta of needle start (does not start at pivot point)
        tx = math.tan((sdeg + 90) * 0.0174532925)

        # Erase old needle image
        tft.drawLine(int(M_SIZE * (120 + 20 * ltx - 1)), int(M_SIZE * (140 - 20)), int(osx - 1), int(osy), tft.color.WHITE)
        tft.drawLine(int(M_SIZE * (120 + 20 * ltx)), int(M_SIZE * (140 - 20)), int(osx), int(osy), tft.color.WHITE)
        tft.drawLine(int(M_SIZE * (120 + 20 * ltx + 1)), int(M_SIZE * (140 - 20)), int(osx + 1), int(osy), tft.color.WHITE)

        # Re-plot text under needle
        tft.setTextColor(tft.color.BLACK);
        tft.drawCentreString("%RH", int(M_SIZE * 120), int(M_SIZE * 70), 4); # Comment out to avoid font 4

        # Store new needle end coords for next erase
        ltx = tx
        osx = M_SIZE * (sx * 98 + 120)
        osy = M_SIZE * (sy * 98 + 140)

        # Draw the needle in the new postion, magenta makes needle a bit bolder
        # draws 3 lines to thicken needle
        tft.drawLine(int(M_SIZE * (120 + 20 * ltx - 1)),int( M_SIZE * (140 - 20)), int(osx - 1), int(osy), tft.color.RED)
        tft.drawLine(int(M_SIZE * (120 + 20 * ltx)), int(M_SIZE * (140 - 20)), int(osx), int(osy), tft.color.MAGENTA)
        tft.drawLine(int(M_SIZE * (120 + 20 * ltx + 1)), int(M_SIZE * (140 - 20)), int(osx + 1), int(osy), tft.color.RED)

        if(math.fabs(old_analog - value) < 10):
            ms_delay += ms_delay / 5
        
        time.sleep(ms_delay)

def analogMeter():
    tft.fillRect(0, 0, int(M_SIZE * 239), int(M_SIZE * 126), tft.color.LIGHTGREY)
    tft.fillRect(5, 3, int(M_SIZE * 230), int(M_SIZE * 119), tft.color.WHITE)

    tft.setTextColor(tft.color.BLACK)

    # Draw ticks every 5 degrees from -50 to +50 degrees (100 deg. FSD swing)
    for i in range(-50, 51, 5):
        # Long scale tick length
        tl = 15

        # Coordinates of tick to draw
        sx = math.cos((i - 90) * 0.0174532925)
        sy = math.sin((i - 90) * 0.0174532925)
        x0 = sx * (M_SIZE * 100 + tl) + M_SIZE * 120
        y0 = sy * (M_SIZE * 100 + tl) + M_SIZE * 140
        x1 = sx * M_SIZE * 100 + M_SIZE * 120
        y1 = sy * M_SIZE * 100 + M_SIZE * 140

        # Coordinates of next tick for zone fill
        sx2 = math.cos((i + 5 - 90) * 0.0174532925)
        sy2 = math.sin((i + 5 - 90) * 0.0174532925)
        x2 = sx2 * (M_SIZE * 100 + tl) + M_SIZE * 120
        y2 = sy2 * (M_SIZE * 100 + tl) + M_SIZE * 140
        x3 = sx2 * M_SIZE * 100 + M_SIZE * 120
        y3 = sy2 * M_SIZE * 100 + M_SIZE * 140

        # Yellow zone limits
        if (i >= -50 and i < 0):
          tft.fillTriangle(int(x0), int(y0), int(x1), int(y1), int(x2), int(y2), tft.color.GREEN)
          tft.fillTriangle(int(x1), int(y1), int(x2), int(y2), int(x3), int(y3),tft.color.GREEN)
        
        # Green Zone limits
        if (i >= 0 and i < 25):
            tft.fillTriangle(int(x0), int(y0), int(x1), int(y1), int(x2), int(y2), tft.color.YELLOW)
            tft.fillTriangle(int(x1), int(y1), int(x2), int(y2), int(x3), int(y3), tft.color.YELLOW)
        
        # Orange zone limits
        if (i >= 25 and i < 50):
            tft.fillTriangle(int(x0), int(y0), int(x1), int(y1), int(x2), int(y2), tft.color.ORANGE)
            tft.fillTriangle(int(x1), int(y1), int(x2), int(y2), int(x3), int(y3), tft.color.ORANGE)
        
        # Short scale tick length
        if (i % 25 != 0):
            tl = 8
        # Recalculate coords incase tick length changed
        x0 = sx * (M_SIZE * 100 + tl) + M_SIZE * 120
        y0 = sy * (M_SIZE * 100 + tl) + M_SIZE * 140
        x1 = sx * M_SIZE * 100 + M_SIZE * 120
        y1 = sy * M_SIZE * 100 + M_SIZE * 140

        # Draw tick
        tft.drawLine(int(x0), int(y0), int(x1), int(y1), tft.color.BLACK)

        # Check if labels should be drawn, with position tweaks
        if (i % 25 == 0):
            x0 = sx * (M_SIZE * 100 + tl + 10) + M_SIZE * 120
            y0 = sy * (M_SIZE * 100 + tl + 10) + M_SIZE * 140

            if(i/25 == -2 ):
                tft.drawCentreString("0", int(x0), int(y0) - 12, 2)
            elif (i/25 == -1 ):
                tft.drawCentreString("25", int(x0), int(y0) - 9, 2)
            elif (i/25 == -0 ):
                tft.drawCentreString("50", int(x0), int(y0) - 7, 2)
            elif (i/25 == 1 ):
                tft.drawCentreString("75", int(x0), int(y0) - 9, 2)
            elif (i/25 == 2 ): 
                tft.drawCentreString("100", int(x0), int(y0) - 12, 2)
        
            # Now draw the arc of the scale
            # sx = math.cos((i + 5 - 90) * 0.0174532925)
            # sy = math.sin((i + 5 - 90) * 0.0174532925)
            # x0 = sx * M_SIZE * 100 + M_SIZE * 120
            # y0 = sy * M_SIZE * 100 + M_SIZE * 140
            # # Draw scale arc, don't draw the last part
            # if (i < 50):
            #     tft.drawLine(int(x0), int(y0), int(x1), int(y1), tft.color.BLACK)

        tft.drawString("%RH", int(M_SIZE * (5 + 230 - 40)), int(M_SIZE * (119 - 20)), 2); # Units at bottom right
        tft.drawCentreString("%RH", int(M_SIZE * 120), int(M_SIZE * 70), 4); # Comment out to avoid font 4
        tft.drawRect(5, 3, int(M_SIZE * 230), int(M_SIZE * 119), tft.color.BLACK); # Draw bezel line

        plotNeedle(0, 0)


def initial():
    analogMeter()
    updateTime = time.ticks_ms()
        
def main():
    global updateTime, d
    if (updateTime <= time.ticks_ms()):
        updateTime = time.ticks_ms() + 100
        d += 4
        if (d >= 360):
            d = 0
        
        value = 50 + 50 * math.sin((d+0)*0.0174532925)
        print(value)
        plotNeedle(value, 0)


if __name__ == "__main__":
    initial()
    while True:
        main()

Don’t hesitate to visit our Wiki for further interesting usages and also don’t forget to submit any technical issue into our Forum.

About Author

Calendar

June 2020
M T W T F S S
1234567
891011121314
15161718192021
22232425262728
2930