element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Members
    Members
    • Benefits of Membership
    • Achievement Levels
    • Members Area
    • Personal Blogs
    • Feedback and Support
    • What's New on element14
  • Learn
    Learn
    • Learning Center
    • eBooks
    • STEM Academy
    • Webinars, Training and Events
    • More
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • More
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • More
  • Products
    Products
    • Arduino
    • Dev Tools
    • Manufacturers
    • Raspberry Pi
    • RoadTests & Reviews
    • Avnet Boards Community
    • More
  • Store
    Store
    • Visit Your Store
    • Choose Another Store
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
Think ON Design Challenge
  • Challenges & Projects
  • Design Challenges
  • Think ON Design Challenge
  • More
  • Cancel
Think ON Design Challenge
Blog ON Sweet Home - GUI is alive and the project is complete!
  • Blog
  • Forum
  • Documents
  • Events
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Blog Post Actions
  • Subscribe by email
  • More
  • Cancel
  • Share
  • Subscribe by email
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: luislabmo
  • Date Created: 20 Mar 2020 11:56 PM Date Created
  • Views 273 views
  • Likes 5 likes
  • Comments 6 comments
  • thinkon
  • rsl10-sense-db-gevk
  • bluetooth
  • beacon
  • rpi 4
  • on semiconductor ide
  • pla
  • rsl10-sense-gevk
  • think on design challenge
  • raspberry pi 4b
  • on semi
  • rsl10
  • think_on
  • btle
  • 3d pinting
  • luislab
  • on semiconductor
Related
Recommended

ON Sweet Home - GUI is alive and the project is complete!

luislabmo
luislabmo
20 Mar 2020

I'm really excited to show the completed project to my fellow readers. For the wrap-up post I made a GUI with Python and Tkinter to collect and display all the beacons data. In the video, two beacons are connected providing Temperature, Humidity and Battery Level feedback regularly.

 

You don't have permission to edit metadata of this video.
Edit media
x
Upload Preview

 

The Python program that reads all the sensor's data uses the functionality which I developed and explained in a previous blog post.

 

To capture the events in the video, I modified the RSL10 firmware (file CS_Platform_RSL10_HB.c) in order to turn ON the built-in RGB LED while the communication is in progress (Request and Response) which can be appreciated in few parts of the video.

static void CS_PlatformReadHandler(struct BLE_ICS_RxIndData *ind)
{
    cs_request = ind;
    LED_On(0); // Blue ON
    LED_On(1); // Green ON
    //LED_On(2); // Red ON


    CS_Loop(0);


    LED_Off(0); // Blue OFF
    LED_Off(1); // Green OFF
    //LED_Off(2); // Red OFF


    cs_request = NULL;
}

 

GUI Features

The GUI is designed to display the current date and time which are constantly being updated. It has a working power button which closes the application and room for 3 widgets (one beacon each) with some level of customization.

 

For each widget, the Beacon name or is displayed at the top, temperature, humidity and Battery Level are displayed and refreshed regularly. The Battery Level is displayed as a progress bar which changes color from green (good), yellow (medium) and red (low battery).

When tapping the Temperature Scale ºF or ºC it will automatically convert the temperature between Fahrenheit and Celsius (seen also in the video).

Source Code

The GUI can be launched remotely and will provide text feedback on the events that are happening in real time. To launch the GUI on the Pi's main screen, run the following before running the Python script remotely:

export DISPLAY=:0.0

 

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Luis Ortiz - luislab.com
# March 8, 2020


from bluepy import btle
import binascii
import subprocess
import re


import sys


from textcolor import textColor


import time
from datetime import datetime


import tkinter as tk
import tkinter.font


import random


class console_log:
  def __init__(self, debug = False, ansi = False):
    self.debug = debug
    self.ansi = ansi


  def Sys_Info(self, text:str):
    if self.debug:
      if not self.ansi:
        text = re.sub('(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]', '', text);
      print (text)


def elapsed_time(elapsed):
  s, ms = divmod(elapsed, 1000)
  m, s = divmod(s, 60)
  h, m = divmod(m, 60)


  time_str = ""
  if (h > 0):
    time_str = "%dh " % h
  if (m > 0):
    time_str += "%dm " % m
  if (s > 0):
    time_str += "%ds " % s
  if (ms > 0):
    time_str += "%dms " % ms


  return time_str.strip()


class beacon:
  def __init__(self, macAddr: str):
    self.mac = macAddr
    self.minOrd = ord('0')
    self.maxOrd = ord('~')
    self.token_limit = (self.maxOrd - self.minOrd) + 1  # CS.c    if (request.token[0] < '0' || request.token[0] > '~')
    self.rToken = 0                                     # Request token
    self.color = textColor()
    self.cl = console_log(True, True)


    color = self.color
    cl    = self.cl


    cl.Sys_Info ("\nConnecting...")


    self.p = btle.Peripheral(macAddr, btle.ADDR_TYPE_PUBLIC)
    cl.Sys_Info ("\nConnected [" + color.green(self.p.addr) + "]")


    for gs in self.p.getServices():
      for gc in gs.getCharacteristics():
        if gc.supportsRead() and gc.uuid.getCommonName() == str(gc.uuid) and 'NOTIFY' in gc.propertiesToString():
          self.rxC = gc
          cl.Sys_Info('__init__ rxC %s' % self.rxC)
        elif gc.uuid.getCommonName() == str(gc.uuid) and 'WRITE' in gc.propertiesToString():
          self.txC = gc
          cl.Sys_Info('__init__ txC %s' % self.txC)
        elif gc.uuid == btle.AssignedNumbers.batteryLevel and 'NOTIFY' in gc.propertiesToString():
          self.battC = gc
          cl.Sys_Info('__init__ battC %s' % self.battC)
      
  def disconnect(self):
    color = self.color
    cl = self.cl
    self.p.disconnect()
    cl.Sys_Info ("\nBeacon [" + color.green(self.p.addr) + "] disconnected.")


  def getBatteryLevel(self):
    color = self.color
    cl = self.cl


    try:
      bValue = self.battC.read()
      sValue = binascii.b2a_hex(bValue).decode('utf-8')


      cl.Sys_Info(("Battery Level: " + color.cyan("0x%s") + \
                   " (" + color.yellow("%d%%") + ")") \
                  % (sValue, int(sValue, 16)))


      return sValue


    except btle.BTLEException as exc:
      cl.Sys_Info(exc)
      return None
    


  def readNode(self, node:str, prop:str):
    color = self.color
    cl = self.cl


    try:
      self.rToken = (self.rToken + 1) % self.token_limit
      cl.Sys_Info("\nrToken %d" % self.rToken)
      sReq = chr(self.rToken + self.minOrd) + '/' + node + '/' + prop
      bReq = sReq.encode('utf-8')
      cl.Sys_Info("Requested %s" % color.cyan('\'' + sReq + '\''))
      startTime = time.time()
      self.txC.write(bReq, withResponse = True)
      success = self.p.waitForNotifications(2)
      endTime = time.time()
      if not success:
        cl.Sys_Info("Request timeout.")
      else:
        cl.Sys_Info("Notification received in %.2f seconds"  % (endTime - startTime))


        bReq = self.rxC.read()
        sReq = bReq.decode('utf-8')


        cl.Sys_Info ("Read sReq %s" % color.pink(sReq))


        result = re.match('(.{1}\/)(.?)(\/)([0-9\.]+$)', sReq)
        if result:
          vType = result.group(2)
          value = result.group(4)


          return vType, value
        else:
          cl.Sys_Info ("No \"re\" match")
      return None, None


    except ValueError:
      return None, None
    except btle.BTLEException as exc:
      cl.Sys_Info (exc)
      return None, None


  def getNodeValue(self, node:str, prop:str=''):       # node (sensor), property
    color = self.color
    cl = self.cl


    if node == 'BL':    # Battery Level
      batteryLevel = int(self.getBatteryLevel(), 16)
      
      return batteryLevel
    elif node == 'EV':  # Environmental sensor (Temp, Humidity, Raw Pressure, Indoor Air Quality) 
      if prop in ('T', 'TF', 'H', 'PP', 'P', 'A'):      # Temp C
        valueType, nodeStrValue = self.readNode(node, prop)


        if nodeStrValue.replace('.', '1').isdigit():
          nodeValue = float(nodeStrValue)
          if prop in ('T'):
            tempC = nodeValue
            cl.Sys_Info (("valueType: %s, temp: " + color.yellow("%2.1f°C")) \
                         % (color.purple("\'" + valueType + "\'"), tempC))
            return tempC
          elif prop in ('TF'):
            tempF = nodeValue
            cl.Sys_Info (("valueType: %s, temp: " + color.yellow("%2.1f°F")) \
                         % (color.purple("\'" + valueType + "\'"), tempF))
            return tempF
          elif prop in ('H'):
            humidity = nodeValue
            cl.Sys_Info (("valueType: %s, humidity: " + color.yellow("%2.f%%")) \
                         % (color.purple("\'" + valueType + "\'"), humidity))
            return humidity
          else:
            cl.Sys_Info (("valueType: %s, value: " + color.yellow("%2.2f") + ", property: %s") \
                         % (color.purple("\'" + valueType + "\'"), nodeValue, color.cyan(prop)))
            return nodeValue
        else:
          cl.Sys_Info ("valueType: %s, nodeValue %s" \
                       % (color.purple("\'" + valueType + "\'"), color.purple("\'" + nodeStrValue + "\'")))
          return nodeStrValue
          
    elif node == 'AL':  # Light Sensor  
      if prop in ('L'):      # Lux
        valueType, nodeStrValue = self.readNode(node, prop)


        if nodeStrValue.replace('.', '1').isdigit():
          lux = float(nodeStrValue)
          cl.Sys_Info (("valueType: %s, illuminance: " + color.yellow("%2.2f lux")) % (color.purple("\'" + valueType + "\'"), lux))
          return lux
        else:
          cl.Sys_Info ("valueType: %s, nodeValue %s" % (color.purple("\'" + valueType + "\'"), color.purple("\'" + nodeStrValue + "\'")))
          return nodeStrValue


class beaconCSV:
  def __init__(self, fileName):
    self.fileName = fileName


    try:
      with open(self.fileName, mode='xt', encoding='utf-8') as f:
        f.write("\"Date\",\"TempC\",\"TempF\",\"Humidity\"\n")
    except FileExistsError:
      pass


  def write(self, tempC, tempF, humidity):
    dt_string = datetime.now().strftime('%Y%m%d %H:%M:%S')
    fileStr = "\"" + dt_string + "\",%2.1f" % tempC + ",%2.1f" % tempF + ",%2.f" % humidity
    cl.Sys_Info(fileStr)


    with open(self.fileName, mode='at', encoding='utf-8') as f:
      f.write(fileStr + "\n")


class Application(tk.Tk):
  def refreshDateTime(self):
    self.date_str.set(datetime.now().strftime('%a, %b %-d'))
    self.time_str.set(datetime.now().strftime('%-I:%M %p'))


  def closeApp(self):
    self.destroy()
    for b in self.beacons:
      if b[0]:
        b[0].disconnect()


  def __init__(self, width=800, height=480):
    tk.Tk.__init__(self)
    self.title('On Sweet Home')
    self.gui_fg='white'   # Foreground color
    self.gui_bg='black'   # Background color
    self.gui_width=width
    self.gui_height=height


    self.geometry(str(self.gui_width) + "x" + str(self.gui_height))
    self.resizable(0, 0)
    self.configure(background=self.gui_bg, cursor="none")
    self.title("On Sweet Home")
    self.overrideredirect(True)


    self.backgroundImage = tk.PhotoImage(file = './images/hub.png')
    self.backgroundLabel = tk.Label(self, image=self.backgroundImage).place(x=0, y=0, relwidth=1, relheight=1)


    self.beacons = []
    self.beaconsLen = 0


    self.refreshLoop = 0
    self.createWidgets()


  def createWidgets(self):
    self.widg_bg='#333333' # gray20


    self.iconExit = tk.PhotoImage(file = './icons/power.png')
    self.buttonExit = tk.Button(self, image=self.iconExit, fg=self.gui_bg, bg=self.gui_bg, highlightthickness=0, bd=0, \
                                activebackground=self.gui_bg, command=self.closeApp)
    self.buttonExit.place(x=(self.gui_width - 40), y=8)


    self.date_str = tk.StringVar()
    self.time_str = tk.StringVar()
    self.refreshDateTime()


    self.dateLabel = tk.Label(self, textvariable=self.date_str, font=("Quicksand", 22), fg="gray60", bg=self.gui_bg).place(x=600, y=95, anchor="n")
    self.timeLabel = tk.Label(self, textvariable=self.time_str, font="Quicksand 52 bold", fg=self.gui_fg, bg=self.gui_bg).place(x=600, y=140, anchor="n")
    self.acTemp = tk.Label(self, text="72", font="Quicksand 90 normal", padx=0, pady=0, fg=self.gui_fg, bg=self.widg_bg, bd=0).place(x=275, y=140, anchor="ne")
    self.acTempUn = tk.Label(self, text="°F", font="Quicksand 30 normal", fg=self.gui_fg, bg=self.widg_bg, borderwidth=0).place(x=275, y=165, anchor="nw")


    self.acMode1 = tk.Label(self, text="COOL", font="Arial 10 normal", fg="gray10", bg=self.widg_bg, borderwidth=0).place(x=85, y=205, anchor="w")
    self.acMode2 = tk.Label(self, text="HEAT", font="Arial 10 normal", fg=self.gui_fg, bg=self.widg_bg, borderwidth=0).place(x=85, y=225, anchor="w")
    self.acMode3 = tk.Label(self, text="OFF", font="Arial 10 normal", fg="gray10", bg=self.widg_bg, borderwidth=0).place(x=85, y=245, anchor="w")


    self.iconBattery = tk.PhotoImage(file = './icons/battery.png')


  def getBeaconData(self, i):
    if self.beacons[i][0]:                                               # If there is a Beacon available, read the sensor's data
      print (("\nRequesting Beacon_%d data...") % (i))
      self.beacons[i][1] = int(self.beacons[i][0].getNodeValue('EV', 'TF'))
      self.beacons[i][2] = 'F'
      self.beacons[i][5] = int(self.beacons[i][0].getNodeValue('EV', 'H'))
      self.beacons[i][7] = self.beacons[i][0].getNodeValue('BL') / 100
    self.refreshWidget(i)
      
  def refreshWidget(self, i):
    temp      = self.beacons[i][1]    # Temp is always stored in F
    tempScale = self.beacons[i][2]
    humidity  = self.beacons[i][5]
    battLevel =  self.beacons[i][7] 
    posX      = self.beacons[i][9][0]
    posY      = self.beacons[i][9][1]


    canvW = battLevel * 20     # Canvas width represents Battery Level


    if battLevel <= 0.3:       # Canvas color according to the battery level
      canvBG="#cc0000"
    elif battLevel <= 0.65: 
      canvBG="#aaaa00"
    else:
      canvBG="#009900"
    
    if tempScale == 'C':
      tempC = int((self.beacons[i][1] - 32) / 1.8)
      self.beacons[i][3].set(str(tempC))                 # Temperature C
    else:
      self.beacons[i][3].set(str(self.beacons[i][1]))                 # Temperature F


    self.beacons[i][4].set("°" + tempScale)                # Temperature Scale (C, F)
    self.beacons[i][6].set(str(humidity) + "%")           # Humidity


    self.beacons[i][8].place(x=posX+240-35+(20-canvW), y=posY+13)   # Battery level bar
    self.beacons[i][8].config(bg=canvBG, width=canvW)
    
  def changeTempScale(self, i):
    if self.beacons[i][2] == 'F':
      self.beacons[i][2] = 'C'
    else:
      self.beacons[i][2] = 'F'
    print("Changed temperature scale to °%s, Widget[%d]" % (self.beacons[i][2], i))
    self.refreshWidget(i)
    
  def addWidget(self, mac, name, color, posX, posY):
    canvW=20


    widgName = tk.Label(self, text=name, font=("Arial 12"), fg="gray60", bg=self.widg_bg)
    widgName.place(x=posX+105, y=posY+15, anchor="center")


    batteryIcon = tk.Label(self, image=self.iconBattery, fg=self.widg_bg, bg=self.widg_bg)
    batteryIcon.place(x=posX+240-40, y=posY+10)


    canvBatt = tk.Canvas(self, width=canvW, height=10, bg=self.gui_bg, highlightthickness=0)


    tempStr = tk.StringVar()  # Temperature
    tempScl = tk.StringVar()  # Temperature Scale
    humdStr = tk.StringVar()  # Humidity


    # Initialization Values
    self.beacons.append([None, \
                         0, \
                         "F", \
                         tempStr, \
                         tempScl, \
                         35, \
                         humdStr, \
                         0, \
                         canvBatt, \
                         [posX, posY]])


    self.beaconsLen += 1
    i = self.beaconsLen - 1


    if mac:                             # Add BTLE beacon (RSL10-SENSE-GEVK)
      self.beacons[i][0] = beacon(mac)
      self.getBeaconData(i)
    else:                               # If no beacon available, make random data available
      self.beacons[i][1] = random.randrange(68, 74)
      self.beacons[i][5] = random.randrange(10, 25)
      self.beacons[i][7] = random.random()
    widgHumidity = tk.Button(self, textvariable=self.beacons[i][6], font=("Quicksand", 24), fg=color, bg=self.widg_bg, \
                             activebackground=self.widg_bg, highlightthickness=0, bd=0, command = None)
    widgHumidity.place(x=posX+150, y=posY+130, anchor="sw")
    widgTemp = tk.Button(self, textvariable=self.beacons[i][3], font=("Quicksand", 62), fg=color, bg=self.widg_bg, \
                         activebackground=self.widg_bg, highlightthickness=0, bd=0, command = None)
    widgTemp.place(x=posX+123, y=posY+30, anchor="ne")
    widgTempScale = tk.Button(self, textvariable=self.beacons[i][4], font=("Quicksand", 21), fg=color, bg=self.widg_bg, \
                              activeforeground=color, activebackground=self.widg_bg, highlightthickness=0, bd=0, height=1, width=1, command=lambda: self.changeTempScale(i))
    widgTempScale.place(x=posX+110, y=posY+47, anchor="nw")
  def refresh(self):
    self.refreshDateTime()
    if self.refreshLoop >= 2:
      self.getBeaconData(0)
      self.getBeaconData(1)
      self.getBeaconData(2)
      self.refreshLoop = 0
    else:
      self.refreshLoop += 1
    self.refreshWidget(2)
    self.after(5000, self.refresh)


cl = console_log(True, True)


app = Application(800, 480)
app.addWidget(mac='60:C0:BF:29:EF:50', name='Living room', color="#66ccff", posX=20, posY=310)
app.addWidget(mac=None, name='Bedroom', color="#ff9900", posX=280, posY=310)
app.addWidget(mac=None, name='Bedroom', color="#cc66ff", posX=540, posY=310)
app.after(0, app.refresh)
app.mainloop()
print("Bye.")

 

{gallery:width=648,height=432,autoplay=false} ON Sweet Home

Enclosure: Enclosure with wall mount for the RSL10-Sense-Gevk

Enclosure: Enclosure Top Cap (RSL10-Sense-Gevk)

Enclosure: Enclosure Top Cap - Rear (RSL10-Sense-Gevk)

 

If you made it this far, thanks for reading. I really hope all the resources of this project are useful in any way to you. A big thanks to Element14 for sponsoring my project and all the community members who showed up in any way.

 

Luis

 

Blogs in this series

  1. ON Sweet Home - Introduction
  2. ON Sweet Home - Getting to know your RSL10
  3. ON Sweet Home - Collecting the Beacon data
  4. ON Sweet Home - 3D printed parts
  5. ON Sweet Home - GUI is alive and the project is complete!

 

  • My Think ON Design Challenge entries
Anonymous

Top Comments

  • three-phase
    three-phase over 2 years ago +3

    Your project has turned out really well bringing all the elements together. I really like the display, adds a real professional look along with the neat 3D printed cases with the different options.

     

    Kind…

  • stevesmythe
    stevesmythe over 2 years ago +2

    Nice project!

  • genebren
    genebren over 2 years ago +2

    Great project.  I think that your 3D printed parts turned out really nice.

  • luislabmo
    luislabmo over 2 years ago in reply to three-phase

    Thank you three-phase

    • Cancel
    • Up +2 Down
    • Reply
    • More
    • Cancel
  • three-phase
    three-phase over 2 years ago

    Your project has turned out really well bringing all the elements together. I really like the display, adds a real professional look along with the neat 3D printed cases with the different options.

     

    Kind regards

    • Cancel
    • Up +3 Down
    • Reply
    • More
    • Cancel
  • luislabmo
    luislabmo over 2 years ago in reply to genebren

    Thank you Gene!

    • Cancel
    • Up +1 Down
    • Reply
    • More
    • Cancel
  • genebren
    genebren over 2 years ago

    Great project.  I think that your 3D printed parts turned out really nice.

    • Cancel
    • Up +2 Down
    • Reply
    • More
    • Cancel
  • luislabmo
    luislabmo over 2 years ago in reply to stevesmythe

    Thank you stevesmythe

    • Cancel
    • Up +1 Down
    • Reply
    • More
    • Cancel
>
Element14

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2022 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • Facebook
  • Twitter
  • linkedin
  • YouTube