More Adabox016 fun

Well, COVID19 is taking a hit on things at SUNY Brockport. The administration told students:

  • Classes are in session next week
  • If you get a negative COVID test, go home early and the faculty will deal with it

I’m dealing with it by taking some personal time and playing with blinky lights.

IoT radar system

One of my long-term goals is to develop Internet-of-Things (IoT) based sensors that can be used for remote chemical education. Furthermore, I want my research students to be able to do this. Few of our chemistry majors have programming experience, so it is hard to decide whether to introduce them to microcontroller programming via C++ (aka standard Arduino programming) or Python via CircuitPython. After spending some time with the toys in Adabox016, I’m thinking Python might be a good approach.

So here’s the latest. The above may just look like a simple radar screen with a red ping where your enemy is, but the question is: where does the enemy’s location come from? In this example, I have a Raspberry Pi running a python script that randomly walks the enemy around a grid the same size as the RGB matrix. It then sends that positioning information to Adafruit.IO, which is polled by the RGB Matrix. The pause in image above is real; it’s the RGB matrix asking Adafruit.IO what the current location of the enemy is.

What’s this have to do with chemical education and remote sensors? Everything. The method I’m using to allow the Raspberry Pi to communicate with the RGB matrix is the same method that can be used for sensors. The point of using these two devices is to demonstrate that the method is applicable to a wide array of tools (Linux computers, microcontrollers, etc).

The code that I’m using is below, in case you are interested. More likely, it’s here for when one of my systems crashes and I need to remember what I wrote.

The Matrix Portal code

import board # Required
import time
import displayio
import random
# import terminalio
# from adafruit_bitmap_font import bitmap_font
# from adafruit_display_text import label
# from adafruit_display_shapes.rect import Rect
from adafruit_display_shapes.line import Line
from adafruit_display_shapes.circle import Circle
from math import *
from adafruit_matrixportal.matrixportal import MatrixPortal
import adafruit_requests as requests
from secrets import *

matrixportal = MatrixPortal(status_neopixel=board.NEOPIXEL, bit_depth=6, debug=True)
display = matrixportal.display

# Create some constants
CX = floor(display.width / 2)
CY = floor(display.height / 2)
R = ceil(sqrt(CX*CX + CY*CY))

def radar_line(theta = 0, color = 0x00FF00):
    """
    Creates a line starting from the center of the matrix and directed
    outwards at the desired angle.
    """
    return Line(CX, CY, CX + floor(R*cos(radians(theta))),
        CY + floor(R*sin(radians(theta))),color)

def light_color(index = 31, rgb = 0):
    """
    Create one of the primary colors with varying intensity.
    """
    colors =  (8, 2048, 524288)
    return colors[(rgb % 3)]*(index % 32)

def trail(th):
    """
    Creates multiple radar lines with decreasing intensity to give a
    ghosting effect to the radar line.  Be sure to account for these lines in
    the total number of items in the displayio group
    """
    delta = 2
    lines = []
    intensity = (31,8,4,1)
    for i in range(0,4):
        lines.append(radar_line(theta=th-delta*i,color=light_color(intensity[i],1)))
    return [l for l in reversed(lines) ]

def point(x, y, color):
    """
    Simple routine to create a point
    """
    #return Line(x,y,x,y,color)
    return Circle(x,y,1,fill=color)

def find_angle(location):
    """
    Clunky method to get the angle of vector from Matrix location {CX, CY} to
    <location>.  Have to check for points on the axes and play some tricks
    with signs of the angles
    """
    loc_x = location[0]-32
    loc_y = 16 - location[1]
    if (loc_x==0):
        theta = 90
        if (loc_y < 0):
            theta *= -1
    else:
        theta = degrees(atan(loc_y/loc_x))

    # Fix points on the horizontal axis
    if (loc_y == 0):
        if (loc_x > 0):
            return 0
        else:
            return 180

    # Transform coordinate system
    if (theta < 0):
        if (loc_y > 0):
            theta = 180 - theta
        else:
            theta = -theta
    else:
        if (loc_y > 0):
            theta = 360 - theta
        else:
            theta = 180 - theta

    return theta

def close_to(val, ref, tol):
    """
    Alternative to equal, returns True if value is close enough (within <tol>)
    """
    return (ref - tol < val < ref + tol)

def constrain(val, min_val, max_val):

    if val < min_val: return min_val
    if val > max_val: return max_val
    return val

def update_location():
    """
    Grabs location of walker from the position feed at Adafruit.io
    """

    headers = {
        "X-AIO-Key": secrets["aio_key"]
    }
    url = "https://io.adafruit.com/api/v2/bobthechemist/feeds/position/data/retain"
    t1 = time.monotonic_ns()/1000000
    response = requests.get(url)
    t2 = time.monotonic_ns()/1000000
    loc_string = response.text.split(",")[0]
    loc_string = loc_string.split("#")

    print(t2-t1)
    return (int(loc_string[0]), int(loc_string[1]))

# Set up networking by checking the time
matrixportal.get_local_time()

th = 0 # Set the initial angle
blip = False # Disable the blip until overlap with the radar

location = update_location() # location of blip


tolerance = 3 # change in theta per cycle
print(location)

while True:
    g = displayio.Group(max_size=6)
    # Create the radar trail
    for l in trail(th):
         g.append(l)
    # Check if blip falls on line of radar trail
    if (close_to(find_angle(location),th,tolerance)) and (blip == False):
        blip = True
        counter = 8
    # if blip activated, show the blip and get it to fade
    if (blip):
        g.append(point(location[0],location[1],light_color(counter,2)))
        counter -= 1
        if (counter == 0):
            blip = False
            location = update_location()
    display.show(g)
    # Move radar to new location
    th  = (th + tolerance) % 360
    time.sleep(.05)

The Raspberry Pi code

import time
import requests
from secrets import *
import random

url = "https://io.adafruit.com/api/v2/" + secrets['aio_username'] + "/feeds"

headers = {'X-AIO-Key': secrets['aio_key']}

#r = requests.get(url, headers=headers)

# Initialize location
location = [random.randint(0,63), random.randint(0,31)]

def constrain(value, min_val, max_val):
  if value < min_val: return min_val
  if value > max_val: return max_val
  return value

def walk(location):
  walk_probability = 1 # Right now, always walk
  temp_location = location
  if (random.random() < walk_probability):
    # axis? 0 for x, 1 for y
    axis  = random.randint(0,1)
    # direction? 0 for negative and 1 for positive
    direction = random.randint(0,1)
    if (direction == 0):
      temp_location[axis] -= 1
    else:
      temp_location[axis] += 1
    # make sure we don't walk off the map
    return [constrain(temp_location[0],0,63), constrain(temp_location[1],0,31)]


while True:
  location = walk(location) 
  print(location)
  # Send location to the cloud
  headers = {
    "Content-Type": "application/json",
    "X-AIO-Key": secrets["aio_key"]
    }
  data = '{"value":"' + "#".join([str(p) for p in location]) + '"}'
  t1 = time.clock_gettime_ns(0)
  response = requests.post(url + "/position/data",headers=headers, data=data)
  t2 = time.clock_gettime_ns(0)
  print(response.text)
  print(t2 - t1)
  time.sleep(10)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.