Wearable Solution

QuecPython has launched a GUI solution for the wearable industry, including clock display, call making/answering, heart rate/temperature/blood oxygen measurement, step count display, system settings and other optional functions.

image-20231124092228717

The wearable industry solution uses LVGL to draw graphical interfaces. LVGL is a lightweight, open source embedded graphics library. QuecPython has integrated LVGL and uses NXP's GUI Guider as a graphical interface design tool to automatically generate QuecPython code, greatly improving the efficiency of embedded platform graphical interface design.

Interface Introduction

The wearable solution provides home page, dial page, application page, standby page, dial page, heart rate/blood oxygen/temperature detection page, settings page and other pages.

Dial Page

Show Image

Components:

  1. Common Top Bar:
    • Signal display
    • Operator display
    • Battery level display
    • Navigation icon display
  2. Dial Content:
    • Time display: The time is refreshed in real time
    • Hour/Minute/Second hand: Updated in real time with the current time
    • Background dial showing current time
    • Date display: The date is refreshed in real time
  3. Interactions:
    • Swipe left on the digital dial to enter the lock screen, swipe right to enter the analog dial
    • Swipe left on the analog dial to enter the digital dial, swipe right to enter the app list 1
    • After 30s of inactivity, the device will enter lock screen. Swipe right to unlock and enter dial

Application List Page

Show Image

  1. Heart Rate Measurement:
    • Tap to enter heart rate measurement page
  2. Phone:
    • Tap to enter phone dial page
  3. Chat:
    • Tap to enter chat page to send/receive voice
  4. Timer:
    • Tap to enter timer page
  5. Interactions:
    • Swipe right on app list page to enter app list page 2
    • Swipe left on app list page 2 to return to app list page 1, swipe right to enter detection pages

Detection Page

Show Image

The detection page includes body temperature/blood oxygen/heart rate measurement, measurement status and historical measurement display.

Phone Page

Show Image

Provides phone dialing, incoming/outgoing call modes and displays.

Step Count Page

Show Image

Provides daily step count, historical step count display, and step goal setting display.

Timer Page

Show Image

The timer page provides time setting, countdown timer, and alert when timer ends.

Alarm Page

Show Image

Provides alarm setting and deletion, alarm alerts and displays.

Settings Page

Show Image

  1. About Device
    • Provides basic system information like manufacturer, model, OS version
  2. System Upgrade
    • Used to check for system software updates to ensure all features are up-to-date
    • May include auto-update options to automatically download and install updates when available
  3. Set Watch Face
    • Allows switching and displaying different watch faces
  4. Device Ringtone
    • Set incoming call ringtone
    • Set alarm ringtone
  5. Vibration Mode
    • If device has a vibration motor, vibration mode can be set
  6. Factory Reset
    • Reset device to factory default settings
  7. Power Off
    • Send power off command to device

Code Introduction

Code Github Repo: https://github.com/QuecPython/solution-wearable

Code Directory Structure

Copy code.
|-- EventMesh.py
|-- common.py
|-- constant.py
|-- lcd.py
|-- css.py
|-- ui.py
|-- mgr.py 
|-- main_t.py
|-- 16px.bin
|-- 28px.bin
|-- 56px.bin
|-- img
    |-- point.png
    |-- gps.png
    |-- bat4.png
    |-- ...png
  • EventMesh.py is an event management module. When events are triggered on screen controls, users only need to call related interfaces to send events and EventMesh will automatically switch screen contents.
  • common.py mainly provides common abstract base classes and common dependent classes. For example, class Abstract provides lifecycle abstractions.
  • constant.py mainly provides constant configurations.
  • lcd.py mainly provides LCD driver initialization, TP driver initialization, and LVGL initialization. The screen will only display after successful initialization of this file.
  • css.py mainly provides css functionalities, static styles, font styles etc for screen objects in ui.py to use.
  • ui.py
    • Provides base class class Screen() for screen objects, with public methods needed by all screens, like screen creation, status bar info display (operator name, signal strength, system time, battery level etc).
    • class **Screen implements a class that inherits from class Screen(), representing code implementation for each screen's display content. The interface code is mainly automatically generated by the GUI Guider tool, with minor modifications done afterwards, primarily in the object initialization method __init__() and control event handler callbacks __xxx_event_cb(). Modifications can also be done at various lifecycle stages.
  • mgr.py mainly provides background functionalities, allowing interfaces to interact with various managers at different lifecycle stages or upon event triggers.
  • main_t.py is the application entry script file.
    • Calls lcd.py to initialize screen drivers and display.
    • Initializes APP, the wearable app.
    • Calls class UI() in ui.py to create the GUI object for the wearable solution, and adds required screen objects to this GUI object's screen object adder, which would execute the page's initialization method during addition.
    • Adds managers to APP and executes managers' initialization methods.
    • Starts APP.
    • Jumps to main interface.

Application Startup Process

The application startup process is as follows:

Show Image

Software Design

Software Framework Design

Application Design

The software design framework diagram for the wearable solution is as follows:

Show Image

Interface Switching Mechanism

EventMesh Event Management Framework

As mentioned above, interface switching is driven by the EventMesh event management framework.

  • EventMesh is an application framework based on the publish-subscribe mechanism. Users can subscribe to any topics they are interested in, representing specific events.
  • When subscribing to a topic, a callback function needs to be registered to handle the generated event.
  • Users can publish the event from anywhere events are triggered, carrying message content. The registered callback functions of subscribers will then be invoked to process the event.

The EventMesh design framework is shown below:

Show Image

Interface Switching Principles

Show Image

Page Template and Lifecycle Example

# ui.py

# Any new class **Screen must inherit from Screen. The lv_obj must be predefined.  

class DemoScreen(Screen):
    # NAME is required, it is used to find the screen when switching 
    # For example we use EventMesh.publish("load_screen", {"screen": "demo_screen"}) to find this screen
    NAME = "demo_screen"  
    def __init__(self):
        super().__init__()
    	# meta is the core switching object 
        self.meta = app_list_screen        
    	# prop contains info passed during the jump 
    	# if EventMesh.publish("load_screen", {"screen": "demo_screen", “test”:1}) is called
    	# then prop = {"screen": "demo_screen", “test”:1}
        self.prop = None	

    # Called only once when ui.add_screen(DemoScreen())
    # Users can implement initialization here 
    def post_processor_after_instantiation(self, *args, **kwargs): 
        """Called after instantiation"""
        pass
    
    # These methods will be called in sequence whenever 
    # EventMesh.publish("load_screen", {"screen": "demo_screen"}) switches to this screen
        # post_processor_before_initialization
    	# initialization
    	# post_processor_after_initialization
    # Users can implement functionality here, called every time when switching to this screen
    def post_processor_before_initialization(self, *args, **kwargs):
        """Called before initialization""" 
        pass

    def initialization(self, *args, **kwargs):
        """initialization"""
        pass


    def post_processor_after_initialization(self, *args, **kwargs):
        """Called after initialization"""
        pass
    	
    # Called when leaving this screen, for example below will 
    # first call deactivate() before calling main screen init()
    def deactivate(self, *args, **kwargs):
        """initialize load"""
        pass


# Note: If the screen is in a tileview, the mapping must be added for switching with  
# EventMesh.publish("load_screen", {"screen": "main_screen"}) to work
# See example mapping below in UI class 

class UI(Abstract):
    def __init__(self):
        self.screens = []
        self.current = None
        # display_screen maps to tile at column 0 row 0
        # main_screen maps to tile at column 0 row 1, etc
        self.tileview_map = {  
            "display_screen": [0, 0, lv.ANIM.OFF],
    		"main_screen": [1, 0, lv.ANIM.OFF],
            "watch_face_screen": [2, 0, lv.ANIM.OFF],
            "app_list_1_screen": [3, 0, lv.ANIM.OFF],
    		#...
        }
    	# Reverse mapping of above
        self.tileview_position_map = {
            (0, 0): "display_screen",
            (1, 0): "main_screen",
            (2, 0): "watch_face_screen", 
    		#...
        }
    	
# What is tileview? For example, tile1 is at position column 1 row 0, supports left/right slide  
tile1 = tileview.add_tile(1, 0, lv.DIR.RIGHT | lv.DIR.LEFT) 
# Put main_screen on tile1, swiping left enters tile at (0,0), swiping right enters tile at (2, 0)
main_screen = lv_obj(  
    parent=tile1, 
    #...
)

As can be seen from the above code, the callback implementation for each touch event is similar.

Taking the application list control touch as an example, the code is:

# Event handling when app icon clicked
def btn_click(self, event, i):  
    screen_info = self.profile[i][1]
    if screen_info:
    	# Found click event, switch page  
        EventMesh.publish("load_screen", screen_info)

# Render app icons     
def post_processor_after_instantiation(self):
    for i, btn_profile in enumerate(self.profile): 
    	btn = lv_img(  
    		parent=self.container,
    		#...
            )
    	# Add click event to each button
    	btn.add_event_cb(lambda event, cur=i: self.btn_click(event, cur), lv.EVENT.CLICKED, None)  
    	self.btn_list.append(btn)

The core statement is EventMesh.publish("load_screen", screen_info), which sends a "load_screen" event indicating loading a new screen, carrying the message screen_info which contains the name of the screen to switch to, like "blood_screen".

UI Startup

The "load_screen" event is subscribed in the start() method of class AgriUi(object) in ui.py:

class UI(Abstract):

    def lv_load(self, event, msg):
    	# Find screen based on name
        for screen in self.screens:
            if screen.NAME == msg["screen"]:
    			# Switch logic
    			pass
    			
    def start(self):
    	# Subscribe event
        EventMesh.subscribe("load_screen", self.lv_load)  
    	# Load first screen
        EventMesh.publish("load_screen", {"screen": "main_screen"})

The above code starts up the wearable GUI:

  • add_screen creates metadata for each screen

  • EventMesh.subscribe() subscribes to "load_screen", invoking lv_load() when event occurs

    lv_load() finds the target screen based on name, loads it with lv.scr_load()
    
  • First screen loaded with EventMesh.publish("load_screen", {"screen": "main_screen"})

This completes wearable GUI startup, subsequent screen switching is driven by user touch.

Graphical Interface Design

As mentioned before, QuecPython uses NXP's GUI Guider tool for graphical interface design. This tool allows not only interface layout design, but also automatic QuecPython code generation. Click here for a GUI Guider tutorial.

The following sections demonstrate the graphical interface design process using the wearable solution app as an example.

Layout and Background Setup

Start by selecting a suitable template and background design for a watch face app. This example starts from a blank template, setting the resolution to 240*280px.

Show Image

Interface Drawing

This section focuses on drawing the main dial page to demonstrate layout and quick code implementation.

  1. Top Bar

Show Image

  • Components: obj, img, label

  • Operator and Signal (Box 1):

    An obj containing an img for signal and label for operator name, left aligned in layout, 4px gap between img and label

    • obj size is specific pixel width * 20px
    • img size is 20px * 20px
  • Navigation and Battery (Box 2):

    An obj containing a navigation img and battery level img, 8px gap between them, sizes:

    • obj size is 48px * 20px
    • img size is 20px * 20px
  • Overall Layout:

    • Flex layout, default horizontal axis
    • Size 240px * 34px
    • Main axis left-right aligned using space-between
    • Children aligned flex-end on cross axis, i.e. bottom
  • Layout padding: top 0px, right 12px, bottom 4px, left 12px

Implementation:

# Styles from css.py  

# Main page
main_screen = lv_obj(  
    size=(240, 280),
    style=[...]
)

# Top bar  
main_top = lv_obj(
    parent=main_screen,
    size=(240, 34),
    flex_flow=lv.FLEX_FLOW.ROW,
    flex_align=(lv.FLEX_ALIGN.SPACE_BETWEEN, lv.FLEX_ALIGN.END, lv.FLEX_ALIGN.END),   
    style=[...]  
)

# Box 1
main_top_cont_1 = lv_obj(
    parent=main_top,
    size=(88, 20),
    flex_flow=lv.FLEX_FLOW.ROW,
    flex_align=(...),
    style=[
    	# gap set to 4px in style  
    	(style_main_top_cont_1, lv.PART.MAIN | lv.STATE.DEFAULT),
    	...]
)

# Signal img parent is Box 1  
main_top_cont_1_img_signal = lv_img(  
    parent=main_top_cont_1,
    size=(20, 20),
    src="U:/media/s4.png", 
    style[...]
)

# Operator label parent is Box 1
main_top_cont_1_label_operator = lv_label(
    parent=main_top_cont_1,
    size=(64, 19), 
    text="Operator",
    style[...]  
)

# Box 2
main_top_cont_2 = lv_obj(  
    parent=main_top,
    size=(48, 20),
    flex_flow=lv.FLEX_FLOW.ROW,
    flex_align=(...), 
    style[...]
)

# Navigation img parent is Box 2  
main_top_cont_2_img_gps = lv_img(
    parent=main_top_cont_2,
    ...)
    
# Battery img parent is Box 2
main_top_cont_2_img_bat = lv_img(
    parent=main_top_cont_2, 
    ...)
  1. Time Section

Show Image

  • Time Section
    • Size 116px * 200px
    • Position:
      • top: 64px
      • left: 16px
  • Hour Hand Section (Box 1)
    • Size 118px * 200px
    • 2 images
      • Each image size 58 * 100px
    • Flex layout, horizontal axis
  • Minute Hand Section (Box 2)
    • 2 images
    • Image size 58 * 100px each
    • Flex layout, horizontal axis
  • Layout:
    • Flex layout, vertical axis
# ... main_screen 

# Create time section  
main_content_cont_1 = lv_obj(
    parent=main_screen,
    size=(116, 200),
    pos=(19, 64),
    flex_flow=lv.FLEX_FLOW.COLUMN,
    flex_align=(...),
    style[...]  
)

# Create Box 1  
main_content_cont_1_hour = lv_obj(  
    parent=main_content_cont_1,
    size=(116, 100), 
    flex_flow=lv.FLEX_FLOW.ROW,
    flex_align=(...),
    style[...]
)

# Hour hand img 1 in Box 1
main_content_cont_1_hour_0 = lv_img(  
    parent=main_content_cont_1_hour, 
    size=(58, 100),
    src="U:/media/h0.png",
    style[...]
)  

# Hour hand img 2 in Box 1 
main_content_cont_1_hour_1 = lv_img(
    ...)
    
# Create Box 2  
main_content_cont_1_m = lv_obj(
    parent=main_content_cont_1,
    # Inherits vertical flex layout from parent
    flex_flow=lv.FLEX_FLOW.ROW, 
    ...)
    
# Minute hand img 0 in Box 2
main_content_cont_1_m_0 = lv_img(  
    ...)
    
# Minute hand img 1 in Box 2
main_content_cont_1_m_1 = lv_img(
    ...)

When placing elements in GUI Guider, ensure components align to grid and layout appropriately for different screen sizes and resolutions. The design should be clean and minimalist, use appropriate color schemes to make key info prominent yet non-intrusive, interactive elements should provide instant feedback e.g. hover/click change appearance. Test on different devices to ensure usability and performance.

If the system will be used under various lighting conditions like direct sunlight, adjust contrast and brightness to enhance visibility. Finally, the design should be intuitive for new users to navigate without extensive training, reflecting good user experience (UX) design principles.

The code generated by GUI tools lacks flexibility and maintainability. It's recommended to refactor as shown above for better encapsulation, simplicity and maintainability.