Install Libraries

Make sure you are running the latest version of Adafruit CircuitPython for your board. This project requires at least CircuitPython version 6.1.0. You will need to install the appropriate libraries to use the hardware--carefully follow the steps to find and install these libraries from Adafruit's CircuitPython library bundle matching your version of CircuitPython. 

  • adafruit_bitmap_font/
  • adafruit_bus_device/
  • adafruit_button.mpy
  • adafruit_display_shapes/
  • adafruit_display_text/
  • adafruit_esp32spi/
  • adafruit_io/
  • adafruit_mcp9600.mpy
  • adafruit_portalbase/
  • adafruit_pyportal/
  • adafruit_register/
  • adafruit_requests.mpy
  • adafruit_touchscreen.mpy
  • neopixel.mpy

Install Code

Download the project files from the GitHub repo linked to the green button below.

In any of the code boxes below, you can get all the files in one step by clicking "Download: Zip".

In addition to the code.py file, this project also requires several supporting folders and files. These folders are required for this project:

  • fonts - Contains fonts used for displaying text on the PyPortal
  • profiles - Contains profiles of solder pastes

These additional files are also needed:

  • codecalibrate.py - A program used to calibrate the toaster oven with the EZ Make Oven code. Run this when installing new versions or replacing the toaster oven.
  • config.json - Contains configuration settings, including the values created from the codecalibrate.py program.

Calibrating the Oven

Not all toaster ovens are created equally. The EZ Make Oven takes into account various factors in a toaster oven, including size, wattage, and rate of temperature rise. This is done using the calibration sketch, which is used gather information about how the oven reacts to temperature changes.

You will need to temporarily rename the codecalibrate.py program to code.py (and rename code.py to another name temporarily) in order to run the calibration program.

This program will turn on the oven, let the oven temperature ramp up to 100 degrees C, turn it off, and measure the time and temperature difference when the temperature levels off. These values will be printed on the display, which will need to be written down and then entered in the config.json file.

This calibration program should be run when installing new versions of the EZ Make Oven or when replacing the toaster oven for another toaster oven.

temperature___humidity_calibration.jpg
Copy the values calculated from the calibration program to your config.json file
import time
import board
import busio
import digitalio
import sys
from adafruit_mcp9600 import MCP9600

SENSOR_ADDR = 0X67

i2c = busio.I2C(board.SCL, board.SDA,frequency=200000)
try:
    sensor = MCP9600(i2c,SENSOR_ADDR,"K")
except ValueError as e:
    print(e)
    print("Unable to connect to the thermocouple sensor.")
    sys.exit(1)

oven = digitalio.DigitalInOut(board.D4)
oven.direction = digitalio.Direction.OUTPUT

def oven_control(enable=False):
    #board.D4
    oven.value = enable

check_temp = 100
print("This program will determine calibration settings ")
print("for your oven to use with the EZ Make Oven.\n\n")
for i in range(10):
    print("Calibration will start in %d seconds..." % (10-i))
    time.sleep(1)
print("Starting...")
print("Calibrating oven temperature to %d C" % check_temp)
finish = False
oven_control(True)
maxloop=300
counter = 0
while not finish:
    time.sleep(1)
    counter += 1
    current_temp = sensor.temperature
    print("%.02f C" % current_temp)
    if current_temp >= check_temp:
        finish = True
        oven_control(False)
    if counter >= maxloop:
        raise Exception("Oven not working or bad sensor")

print("checking oven lag time and temperature")
finish = False
start_time = time.monotonic()
start_temp = sensor.temperature
last_temp = start_temp

while not finish:
    time.sleep(1)
    current_temp = sensor.temperature
    print(current_temp)
    if current_temp <= last_temp:
        finish = True
    last_temp = current_temp

lag_temp = last_temp - check_temp
lag_time = int(time.monotonic() - start_time)

print("** Calibration Results **")
print("Modify config.json with these values for your oven:")
print("calibrate_temp:", lag_temp)
print("calibrate_seconds:",lag_time)

Solder Paste Profiles

The EZ Make Oven contains a few solder paste profiles in the profiles folder. You can view these profiles with a text editor to see if one of them closely matches the solder paste you are using.

If it does, great! Update the config.json file with the appropriate solder paste file name (without the ".json" extension).

If not, you can easily create a new profile in the profiles folder. Use one of the existing profiles as a guide. It would not hurt to add a few additional data points to smooth out the curve, as the EZ Make Oven uses straight lines to connect the profile points.

Here are some of the items in the profile file explained:

  • temp_range: temperature range of the profile, used to define the minimum and maximum y axis of the graph
  • time_range: time range of the profile, used to define the minimum and maximum x axis of the graph
  • stages: start points for the stages preheat, soak, reflow and cool. The format of the point is [x, y], where x is the time value and y is the temperature value of the starting point of the stage
  • profile: data points that make up the solder profile. The format of each point is [x, y], where x is the time value and y is the temperature value value of a point on the solder profile graph

The Code for the EZ Make Oven

The code is available from GitHub. You will need to copy and modify the config.json file, which contains the solder profile, the MCP9600 I2C address of the sensor board and the calibration settings for the oven.

{
    "sensor_address": 103,
    "profile": "sn63pb37",
    "calibrate_temp": 29.5625,
    "calibrate_seconds": 37
}

The EZ Make Oven code can be downloaded from the link below or copied and paste to the file code.py on the PyPortal CIRCUITPY drive. 

import time
import json
import array
import math
import gc
import board
import busio
import audioio
import audiocore
import displayio
import digitalio
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import bitmap_label as label
from adafruit_display_shapes.circle import Circle
from adafruit_button import Button
import adafruit_touchscreen
from adafruit_mcp9600 import MCP9600

TITLE = "EZ Make Oven Controller"
VERSION = "1.3.1"

print(TITLE, "version ", VERSION)
time.sleep(2)

display_group = displayio.Group(max_size=20)
board.DISPLAY.show(display_group)

PROFILE_SIZE = 2  # plot thickness
GRID_SIZE = 2
GRID_STYLE = 3
TEMP_SIZE = 2
AXIS_SIZE = 2

BLACK = 0x0
BLUE = 0x2020FF
GREEN = 0x00FF55
RED = 0xFF0000
YELLOW = 0xFFFF00

WIDTH = board.DISPLAY.width
HEIGHT = board.DISPLAY.height

palette = displayio.Palette(5)
palette[0] = BLACK
palette[1] = GREEN
palette[2] = BLUE
palette[3] = RED
palette[4] = YELLOW

palette.make_transparent(0)

BACKGROUND_COLOR = 0
PROFILE_COLOR = 1
GRID_COLOR = 2
TEMP_COLOR = 3
AXIS_COLOR = 2

GXSTART = 100
GYSTART = 80
GWIDTH = WIDTH - GXSTART
GHEIGHT = HEIGHT - GYSTART
plot = displayio.Bitmap(GWIDTH, GHEIGHT, 4)

display_group.append(
    displayio.TileGrid(plot, pixel_shader=palette, x=GXSTART, y=GYSTART)
)

ts = adafruit_touchscreen.Touchscreen(
    board.TOUCH_XL,
    board.TOUCH_XR,
    board.TOUCH_YD,
    board.TOUCH_YU,
    calibration=((5200, 59000), (5800, 57000)),
    size=(WIDTH, HEIGHT),
)


class Beep(object):
    def __init__(self):
        self.duration = 0
        self.start = 0
        tone_volume = 1  # volume is from 0.0 to 1.0
        frequency = 440  # Set this to the Hz of the tone you want to generate.
        length = 4000 // frequency
        sine_wave = array.array("H", [0] * length)
        for i in range(length):
            sine_wave[i] = int(
                (1 + math.sin(math.pi * 2 * i / length)) * tone_volume * (2 ** 15 - 1)
            )
        self.sine_wave_sample = audiocore.RawSample(sine_wave)

        self._speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
        self._speaker_enable.switch_to_output(False)

        if hasattr(board, "AUDIO_OUT"):
            self.audio = audioio.AudioOut(board.AUDIO_OUT)
        elif hasattr(board, "SPEAKER"):
            self.audio = audioio.AudioOut(board.SPEAKER)
        else:
            raise AttributeError("Board does not have a builtin speaker!")

    # pylint: disable=protected-access
    def play(self, duration=0.1):
        if not self._speaker_enable.value:
            self._speaker_enable.value = True
            self.audio.play(self.sine_wave_sample, loop=True)
            self.start = time.monotonic()
            self.duration = duration
            if duration <= 0.5:
                # for beeps less than .5 sec, sleep here,
                # otherwise, use refresh() in loop to turn off long beep
                time.sleep(duration)
                self.stop()

    def stop(self):
        if self._speaker_enable.value:
            self.duration = 0
            self.audio.stop()
            self._speaker_enable.value = False

    def refresh(self):
        if time.monotonic() - self.start >= self.duration:
            self.stop()


class ReflowOvenControl(object):
    states = ("wait", "ready", "start", "preheat", "soak", "reflow", "cool")

    def __init__(self, pin):
        self.oven = digitalio.DigitalInOut(pin)
        self.oven.direction = digitalio.Direction.OUTPUT
        with open("/config.json", mode="r") as fpr:
            self.config = json.load(fpr)
            fpr.close()
        self.sensor_status = False
        with open("/profiles/" + self.config["profile"] + ".json", mode="r") as fpr:
            self.sprofile = json.load(fpr)
            fpr.close()
        i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
        try:
            self.sensor = MCP9600(i2c, self.config["sensor_address"], "K")
            self.ontemp = self.sensor.temperature
            self.offtemp = self.ontemp
            self.sensor_status = True
        except ValueError:
            print("temperature sensor not available")
        self.control = False
        self.reset()
        self.beep = Beep()
        self.set_state("ready")
        if self.sensor_status:
            if self.sensor.temperature >= 50:
                self.last_state = "wait"
                self.set_state("wait")

    def reset(self):
        self.ontime = 0
        self.offtime = 0
        self.enable(False)
        self.reflow_start = 0

    def get_profile_temp(self, seconds):
        x1 = self.sprofile["profile"][0][0]
        y1 = self.sprofile["profile"][0][1]
        for point in self.sprofile["profile"]:
            x2 = point[0]
            y2 = point[1]
            if x1 <= seconds < x2:
                temp = y1 + (y2 - y1) * (seconds - x1) // (x2 - x1)
                return temp
            x1 = x2
            y1 = y2
        return 0

    def set_state(self, state):
        self.state = state
        self.check_state()
        self.last_state = state

    # pylint: disable=too-many-branches, too-many-statements
    def check_state(self):
        try:
            temp = self.sensor.temperature
        except AttributeError:
            temp = 32  # sensor not available, use 32 for testing
            self.sensor_status = False
            # message.text = "Temperature sensor missing"
        self.beep.refresh()
        if self.state == "wait":
            self.enable(False)
            if self.state != self.last_state:
                # change in status, time for a beep!
                self.beep.play(0.1)
            if temp < 35:
                self.set_state("ready")
                oven.reset()
                draw_profile(sgraph, oven.sprofile)
                timer_data.text = format_time(0)

        if self.state == "ready":
            self.enable(False)
        if self.state == "start" and temp >= 50:
            self.set_state("preheat")
        if self.state == "start":
            message.text = "Starting"
            self.enable(True)
        if self.state == "preheat" and temp >= self.sprofile["stages"]["soak"][1]:
            self.set_state("soak")
        if self.state == "preheat":
            message.text = "Preheat"
        if self.state == "soak" and temp >= self.sprofile["stages"]["reflow"][1]:
            self.set_state("reflow")
        if self.state == "soak":
            message.text = "Soak"
        if (
            self.state == "reflow"
            and temp >= self.sprofile["stages"]["cool"][1]
            and self.reflow_start > 0
            and (
                time.monotonic() - self.reflow_start
                >= self.sprofile["stages"]["cool"][0]
                - self.sprofile["stages"]["reflow"][0]
            )
        ):
            self.set_state("cool")
            self.beep.play(5)
        if self.state == "reflow":
            message.text = "Reflow"
            if self.last_state != "reflow":
                self.reflow_start = time.monotonic()
        if self.state == "cool":
            self.enable(False)
            message.text = "Cool Down, Open Door"

        if self.state in ("start", "preheat", "soak", "reflow"):
            if self.state != self.last_state:
                # change in status, time for a beep!
                self.beep.play(0.1)
            # oven temp control here
            # check range of calibration to catch any humps in the graph
            checktime = 0
            checktimemax = self.config["calibrate_seconds"]
            checkoven = False
            if not self.control:
                checktimemax = max(
                    0,
                    self.config["calibrate_seconds"]
                    - (time.monotonic() - self.offtime),
                )
            while checktime <= checktimemax:
                check_temp = self.get_profile_temp(int(timediff + checktime))
                if (
                    temp + self.config["calibrate_temp"] * checktime / checktimemax
                    < check_temp
                ):
                    checkoven = True
                    break
                checktime += 5
            if not checkoven:
                # hold oven temperature
                if (
                    self.state in ("start", "preheat", "soak")
                    and self.offtemp > self.sensor.temperature
                ):
                    checkoven = True
            self.enable(checkoven)

    # turn oven on or off
    def enable(self, enable):
        try:
            self.oven.value = enable
            self.control = enable
            if enable:
                self.offtime = 0
                self.ontime = time.monotonic()
                self.ontemp = self.sensor.temperature
                print("oven on")
            else:
                self.offtime = time.monotonic()
                self.ontime = 0
                self.offtemp = self.sensor.temperature
                print("oven off")
        except AttributeError:
            # bad sensor
            pass


class Graph(object):
    def __init__(self):
        self.xmin = 0
        self.xmax = 720  # graph up to 12 minutes
        self.ymin = 0
        self.ymax = 240
        self.xstart = 0
        self.ystart = 0
        self.width = GWIDTH
        self.height = GHEIGHT

    # pylint: disable=too-many-branches
    def draw_line(self, x1, y1, x2, y2, size=PROFILE_SIZE, color=1, style=1):
        # print("draw_line:", x1, y1, x2, y2)
        # convert graph coords to screen coords
        x1p = self.xstart + self.width * (x1 - self.xmin) // (self.xmax - self.xmin)
        y1p = self.ystart + int(
            self.height * (y1 - self.ymin) / (self.ymax - self.ymin)
        )
        x2p = self.xstart + self.width * (x2 - self.xmin) // (self.xmax - self.xmin)
        y2p = self.ystart + int(
            self.height * (y2 - self.ymin) / (self.ymax - self.ymin)
        )
        # print("screen coords:", x1p, y1p, x2p, y2p)

        if (max(x1p, x2p) - min(x1p, x2p)) > (max(y1p, y2p) - min(y1p, y2p)):
            for xx in range(min(x1p, x2p), max(x1p, x2p)):
                if x2p != x1p:
                    yy = y1p + (y2p - y1p) * (xx - x1p) // (x2p - x1p)
                    if style == 2:
                        if xx % 2 == 0:
                            self.draw_point(xx, yy, size, color)
                    elif style == 3:
                        if xx % 8 == 0:
                            self.draw_point(xx, yy, size, color)
                    elif style == 4:
                        if xx % 12 == 0:
                            self.draw_point(xx, yy, size, color)
                    else:
                        self.draw_point(xx, yy, size, color)
        else:
            for yy in range(min(y1p, y2p), max(y1p, y2p)):
                if y2p != y1p:
                    xx = x1p + (x2p - x1p) * (yy - y1p) // (y2p - y1p)
                    if style == 2:
                        if yy % 2 == 0:
                            self.draw_point(xx, yy, size, color)
                    elif style == 3:
                        if yy % 8 == 0:
                            self.draw_point(xx, yy, size, color)
                    elif style == 4:
                        if yy % 12 == 0:
                            self.draw_point(xx, yy, size, color)
                    else:
                        self.draw_point(xx, yy, size, color)

    def draw_graph_point(self, x, y, size=PROFILE_SIZE, color=1):
        """ draw point using graph coordinates """

        # wrap around graph point when x goes out of bounds
        x = (x - self.xmin) % (self.xmax - self.xmin) + self.xmin
        xx = self.xstart + self.width * (x - self.xmin) // (self.xmax - self.xmin)
        yy = self.ystart + int(self.height * (y - self.ymin) / (self.ymax - self.ymin))
        print("graph point:", x, y, xx, yy)
        self.draw_point(xx, max(0 + size, yy), size, color)

    def draw_point(self, x, y, size=PROFILE_SIZE, color=1):
        """Draw data point on to the plot bitmap at (x,y)."""
        if y is None:
            return
        offset = size // 2
        for xx in range(x - offset, x + offset + 1):
            if xx in range(self.xstart, self.xstart + self.width):
                for yy in range(y - offset, y + offset + 1):
                    if yy in range(self.ystart, self.ystart + self.height):
                        try:
                            yy = GHEIGHT - yy
                            plot[xx, yy] = color
                        except IndexError:
                            pass


def draw_profile(graph, profile):
    """Update the display with current info."""
    for i in range(GWIDTH * GHEIGHT):
        plot[i] = 0

    # draw stage lines
    # preheat
    graph.draw_line(
        profile["stages"]["preheat"][0],
        profile["temp_range"][0],
        profile["stages"]["preheat"][0],
        profile["temp_range"][1] * 1.1,
        GRID_SIZE,
        GRID_COLOR,
        GRID_STYLE,
    )
    graph.draw_line(
        profile["time_range"][0],
        profile["stages"]["preheat"][1],
        profile["time_range"][1],
        profile["stages"]["preheat"][1],
        GRID_SIZE,
        GRID_COLOR,
        GRID_STYLE,
    )
    # soak
    graph.draw_line(
        profile["stages"]["soak"][0],
        profile["temp_range"][0],
        profile["stages"]["soak"][0],
        profile["temp_range"][1] * 1.1,
        GRID_SIZE,
        GRID_COLOR,
        GRID_STYLE,
    )
    graph.draw_line(
        profile["time_range"][0],
        profile["stages"]["soak"][1],
        profile["time_range"][1],
        profile["stages"]["soak"][1],
        GRID_SIZE,
        GRID_COLOR,
        GRID_STYLE,
    )
    # reflow
    graph.draw_line(
        profile["stages"]["reflow"][0],
        profile["temp_range"][0],
        profile["stages"]["reflow"][0],
        profile["temp_range"][1] * 1.1,
        GRID_SIZE,
        GRID_COLOR,
        GRID_STYLE,
    )
    graph.draw_line(
        profile["time_range"][0],
        profile["stages"]["reflow"][1],
        profile["time_range"][1],
        profile["stages"]["reflow"][1],
        GRID_SIZE,
        GRID_COLOR,
        GRID_STYLE,
    )
    # cool
    graph.draw_line(
        profile["stages"]["cool"][0],
        profile["temp_range"][0],
        profile["stages"]["cool"][0],
        profile["temp_range"][1] * 1.1,
        GRID_SIZE,
        GRID_COLOR,
        GRID_STYLE,
    )
    graph.draw_line(
        profile["time_range"][0],
        profile["stages"]["cool"][1],
        profile["time_range"][1],
        profile["stages"]["cool"][1],
        GRID_SIZE,
        GRID_COLOR,
        GRID_STYLE,
    )

    # draw labels
    x = profile["time_range"][0]
    y = profile["stages"]["reflow"][1]
    xp = int(GXSTART + graph.width * (x - graph.xmin) // (graph.xmax - graph.xmin))
    yp = int(GHEIGHT * (y - graph.ymin) // (graph.ymax - graph.ymin))

    label_reflow.x = xp + 10
    label_reflow.y = HEIGHT - yp
    label_reflow.text = str(profile["stages"]["reflow"][1])
    print("reflow temp:", str(profile["stages"]["reflow"][1]))
    print("graph point: ", x, y, "->", xp, yp)

    x = profile["stages"]["reflow"][0]
    y = profile["stages"]["reflow"][1]

    # draw time line (horizontal)
    graph.draw_line(
        graph.xmin, graph.ymin + 1, graph.xmax, graph.ymin + 1, AXIS_SIZE, AXIS_COLOR, 1
    )
    graph.draw_line(
        graph.xmin, graph.ymax, graph.xmax, graph.ymax, AXIS_SIZE, AXIS_COLOR, 1
    )
    # draw time ticks
    tick = graph.xmin
    while tick < (graph.xmax - graph.xmin):
        graph.draw_line(
            tick, graph.ymin, tick, graph.ymin + 10, AXIS_SIZE, AXIS_COLOR, 1
        )
        graph.draw_line(
            tick,
            graph.ymax,
            tick,
            graph.ymax - 10 - AXIS_SIZE,
            AXIS_SIZE,
            AXIS_COLOR,
            1,
        )
        tick += 60

    # draw temperature line (vertical)
    graph.draw_line(
        graph.xmin, graph.ymin, graph.xmin, graph.ymax, AXIS_SIZE, AXIS_COLOR, 1
    )
    graph.draw_line(
        graph.xmax - AXIS_SIZE + 1,
        graph.ymin,
        graph.xmax - AXIS_SIZE + 1,
        graph.ymax,
        AXIS_SIZE,
        AXIS_COLOR,
        1,
    )
    # draw temperature ticks
    tick = graph.ymin
    while tick < (graph.ymax - graph.ymin) * 1.1:
        graph.draw_line(
            graph.xmin, tick, graph.xmin + 10, tick, AXIS_SIZE, AXIS_COLOR, 1
        )
        graph.draw_line(
            graph.xmax,
            tick,
            graph.xmax - 10 - AXIS_SIZE,
            tick,
            AXIS_SIZE,
            AXIS_COLOR,
            1,
        )
        tick += 50

    # draw profile
    x1 = profile["profile"][0][0]
    y1 = profile["profile"][0][1]
    for point in profile["profile"]:
        x2 = point[0]
        y2 = point[1]
        graph.draw_line(x1, y1, x2, y2, PROFILE_SIZE, PROFILE_COLOR, 1)
        # print(point)
        x1 = x2
        y1 = y2


def format_time(seconds):
    minutes = seconds // 60
    seconds = int(seconds) % 60
    return "{:02d}:{:02d}".format(minutes, seconds, width=2)


timediff = 0
oven = ReflowOvenControl(board.D4)
print("melting point: ", oven.sprofile["melting_point"])
font1 = bitmap_font.load_font("/fonts/OpenSans-9.bdf")

font2 = bitmap_font.load_font("/fonts/OpenSans-12.bdf")

font3 = bitmap_font.load_font("/fonts/OpenSans-16.bdf")

label_reflow = label.Label(
    font1, text="", max_glyphs=10, color=0xFFFFFF, line_spacing=0
)
label_reflow.x = 0
label_reflow.y = -20
display_group.append(label_reflow)
title_label = label.Label(font3, text=TITLE)
title_label.x = 5
title_label.y = 14
display_group.append(title_label)
# version_label = label.Label(font1, text=VERSION, color=0xAAAAAA)
# version_label.x = 300
# version_label.y = 40
# display_group.append(version_label)
message = label.Label(font2, text="Wait", max_glyphs=30)
message.x = 100
message.y = 40
display_group.append(message)
alloy_label = label.Label(font1, text="Alloy:", color=0xAAAAAA)
alloy_label.x = 5
alloy_label.y = 40
display_group.append(alloy_label)
alloy_data = label.Label(font1, text=str(oven.sprofile["alloy"]))
alloy_data.x = 10
alloy_data.y = 60
display_group.append(alloy_data)
profile_label = label.Label(font1, text="Profile:", color=0xAAAAAA)
profile_label.x = 5
profile_label.y = 80
display_group.append(profile_label)
profile_data = label.Label(font1, text=oven.sprofile["title"])
profile_data.x = 10
profile_data.y = 100
display_group.append(profile_data)
timer_label = label.Label(font1, text="Time:", color=0xAAAAAA)
timer_label.x = 5
timer_label.y = 120
display_group.append(timer_label)
timer_data = label.Label(font3, text=format_time(timediff), max_glyphs=10)
timer_data.x = 10
timer_data.y = 140
display_group.append(timer_data)
temp_label = label.Label(font1, text="Temp(C):", color=0xAAAAAA)
temp_label.x = 5
temp_label.y = 160
display_group.append(temp_label)
temp_data = label.Label(font3, text="--", max_glyphs=10)
temp_data.x = 10
temp_data.y = 180
display_group.append(temp_data)
circle = Circle(308, 12, 8, fill=0)
display_group.append(circle)

sgraph = Graph()

# sgraph.xstart = 100
# sgraph.ystart = 4
sgraph.xstart = 0
sgraph.ystart = 0
# sgraph.width = WIDTH - sgraph.xstart - 4  # 216 for standard PyPortal
# sgraph.height = HEIGHT - 80  # 160 for standard PyPortal
sgraph.width = GWIDTH  # 216 for standard PyPortal
sgraph.height = GHEIGHT  # 160 for standard PyPortal
sgraph.xmin = oven.sprofile["time_range"][0]
sgraph.xmax = oven.sprofile["time_range"][1]
sgraph.ymin = oven.sprofile["temp_range"][0]
sgraph.ymax = oven.sprofile["temp_range"][1] * 1.1
print("x range:", sgraph.xmin, sgraph.xmax)
print("y range:", sgraph.ymin, sgraph.ymax)
draw_profile(sgraph, oven.sprofile)
buttons = []
if oven.sensor_status:
    button = Button(
        x=0, y=HEIGHT - 40, width=80, height=40, label="Start", label_font=font2
    )
    buttons.append(button)

for b in buttons:
    display_group.append(b.group)

try:
    board.DISPLAY.refresh(target_frames_per_second=60)
except AttributeError:
    board.DISPLAY.refresh_soon()
print("display complete")
last_temp = 0
last_state = "ready"
last_control = False
second_timer = time.monotonic()
timer = time.monotonic()
while True:
    gc.collect()
    try:
        board.DISPLAY.refresh(target_frames_per_second=60)
    except AttributeError:
        board.DISPLAY.refresh_soon()
    oven.beep.refresh()  # this allows beeps less than one second in length
    try:
        oven_temp = int(oven.sensor.temperature)
    except AttributeError:
        oven_temp = 32  # testing
        oven.sensor_status = False
        message.text = "Bad/missing temp sensor"
    if oven.control != last_control:
        last_control = oven.control
        if oven.control:
            circle.fill = 0xFF0000
        else:
            circle.fill = 0x0
    p = ts.touch_point
    status = ""
    last_status = ""

    if p:
        if p[0] >= 0 and p[0] <= 80 and p[1] >= HEIGHT - 40 and p[1] <= HEIGHT:
            print("touch!")
            if oven.state == "ready":
                button.label = "Stop"
                oven.set_state("start")

            else:
                # cancel operation
                message.text = "Wait"
                button.label = "Wait"
                oven.set_state("wait")
            time.sleep(1)  # for debounce
    if oven.sensor_status:
        if oven.state == "ready":
            status = "Ready"
            if last_state != "ready":
                oven.beep.refresh()
                oven.reset()
                draw_profile(sgraph, oven.sprofile)
                timer_data.text = format_time(0)
            if button.label != "Start":
                button.label = "Start"
        if oven.state == "start":
            status = "Starting"
            if last_state != "start":
                timer = time.monotonic()
        if oven.state == "preheat":
            if last_state != "preheat":
                timer = time.monotonic()  # reset timer when preheat starts
            status = "Preheat"
        if oven.state == "soak":
            status = "Soak"
        if oven.state == "reflow":
            status = "Reflow"
        if oven.state == "cool" or oven.state == "wait":
            status = "Cool Down, Open Door"
        if last_status != status:
            message.text = status
            last_status = status

        if oven_temp != last_temp and oven.sensor_status:
            last_temp = oven_temp
            temp_data.text = str(oven_temp)
        # update once per second when oven is active
        if oven.state != "ready" and time.monotonic() - second_timer >= 1.0:
            second_timer = time.monotonic()
            oven.check_state()
            if oven.state == "preheat" and last_state != "preheat":
                timer = time.monotonic()  # reset timer at start of preheat
            timediff = int(time.monotonic() - timer)
            timer_data.text = format_time(timediff)
            print(oven.state)
            if oven_temp >= 50:
                sgraph.draw_graph_point(
                    int(timediff), oven_temp, size=TEMP_SIZE, color=TEMP_COLOR
                )

        last_state = oven.state

This guide was first published on Oct 23, 2019. It was last updated on Oct 23, 2019.

This page (Installing the Code) was last updated on Apr 10, 2021.

Text editor powered by tinymce.