Newer
Older
devtimer / devtimer.py
#!/usr/bin/env python3

# devtimer.py - Temperature Controlled Photographic Darkroom Timer
# Targeted for RaspberryPi
# Copyright (c) 2018 TundraWare Inc.
# Permission Hereby Granted For Unrestricted Personal Or Commercial Use


from threading import Thread
from time import time, sleep
from tm1637 import *
from wiringpi import wiringPiSetupGpio

#####
# Constants
#####

# Display

BRIGHTNESS0 = 0x09
CLK0 = 21
DIO0 = 20

# General Constants

DEBUG = True                  # Debugging switch

BEEP = 15                     # Beep interval
CALIBRATION_OFFSET = 0.003    # Compensate for program overhead in master loop

# Profile Constants

REALTIME = 0
PAPER = 1
FILM = 2


# Globals

# These get updated by the threads that read the switches and
# thermocouple.  On a slow machine like the Pi Zero, we want to avoid
# unnecessary function calls, so we make these globally RW.
# So, shoot me ...

CURRENT_PROFILE = FILM
CURRENT_TEMP = 20        # Stored as index relative to 60F

#####
# Lookup Table For Compensating Factors
#####

'''
  There are 3 tables in the list below.  In order:

      Realtime
      Paper
      Film

  Each contains entires for multiplicative corrections from 60F to 80F.

  The profile global above selects which of these tuples to index into
  - using the normalized temp global above as the index.  We don't
  want to use a dictionary here (with profile as the key) because of
  the overhead that incurs.  Straight tuple indexing should be much
  quicker.

  WARNING: It takes about 250ms to update the display on a Pi Zero.
           So, if the "virtual second" falls at or below this, the
           code will be attempting to do updates faster than the
           display can handle. So ... the total compensation cannot
           reduce the virtual second to less than about 0.300 to be on
           the safe side.
'''

compensate = (
              (1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000),
              (1.724, 1.611, 1.505, 1.406, 1.313, 1.227, 1.146, 1.070, 1.000, 0.934, 0.873, 0.815, 0.762, 0.711, 0.665, 0.621, 0.580, 0.542, 0.506, 0.473, 0.442),
              (1.445, 1.380, 1.318, 1.259, 1.202, 1.148, 1.096, 1.047, 1.000, 0.955, 0.912, 0.871, 0.832, 0.795, 0.759, 0.725, 0.692, 0.661, 0.631, 0.603, 0.576)
             )


# Beep at the user at fixed intervals

def beep():
    print("Beep!")


# Update the display with elapsed time

def show_elapsed(display0, elapsed):

    min = elapsed // 60
    sec = elapsed % 60
    d0 = display0.digit_to_segment[min // 10]
    d1 = display0.digit_to_segment[min % 10]
    d2 = display0.digit_to_segment[sec // 10]
    d3 = display0.digit_to_segment[sec % 10]
    display0.set_segments([d0, 0x80 + d1, d2, d3])

#####
# Program entry point
#####

'''
  Notice that the actual updating of the display gets run on its own
  thread.  That's because - on a Pi Zero, at least - it takes over
  250ms to do this.  We don't want that time added to our timing loop,
  so we send it off on a parallel thread, and initiate timing for
  the next round in this thread.
'''

if __name__ == "__main__":

    # Setup the hardware

    wiringPiSetupGpio()
    display0 = TM1637(CLK0, DIO0, BRIGHTNESS0)

    # Start timing, using the selected profile and measured temperature

    elapsed_time = 0
    while True:

        # Beep periodically

        if not elapsed_time % BEEP:
            beep()

        if DEBUG:
            last = time()

        update_thread = Thread(None, show_elapsed, None, (display0, elapsed_time))
        sleep(compensate[CURRENT_PROFILE][CURRENT_TEMP] - CALIBRATION_OFFSET)
        elapsed_time += 1
        elapsed_time %= 6000
        update_thread = Thread(None, show_elapsed, None, (display0, elapsed_time))

        if DEBUG:
            print("Inter-update Time: %s" % str(time()-last))

        update_thread.start()