"""TCS3430 colour sensor: the whole API

Every method of TCS3430 once: the black and white calibration (taken first: without it the module's
warm LED makes a white sheet read yellow; the board stores it for the port, so later programs start
with it), X, Y, Z, both infrared channels, the chromaticity, whether a reading clipped, the colour it
sees and colours taught from its own readings, the gain, the integration time, the wait between
readings, the auto-zero and the Z window with its flag. The defaults (2.78 ms at 64x, no wait) suit a
target close in front, lit by the module's own LED.

Needs: an ams-OSRAM TCS3430 colour sensor on I2C port 16, a white sheet of paper
"""
import evn
from evn import StopWatch, TCS3430, wait
evn.set_stop_button(None)   # the program reads the user button (press_button): a press is an input, not the stop

PORT = 16


def press_button(what_to_do):
    """Print what to do, then wait until the user button is pressed and let go."""
    print()
    print(what_to_do)
    print("   ... then press the user button (to leave: Stop in the editor, or Ctrl-C).")
    while not evn.button.pressed():
        wait(10)
    while evn.button.pressed():
        wait(10)


xyz = TCS3430(PORT)                              # ID 0xDC: an APDS-9960 at the same address is refused
print(xyz, "gain %dx, integration %.2f ms" % (xyz.gain(), xyz.integration_time()))   # 64x, 2.78 ms

# calibrate first, at the distance the colours will be read (stored for the port: TCS3430(PORT) starts with it)
print("stored before:", xyz.stored_calibration())   # {'calibrated': ..., 'chip': 'TCS3430' or None, ...}
# the white first: at 64x a white sheet 1 cm away clips, so it is metered (shutter priority) and the gain it
# picks is stored with the white - every later TCS3430(PORT) starts at it; then the black at that setting
press_button("Hold a white sheet about 1 cm in front of the colour sensor.")
print("metered:", xyz.calibrate_white(auto="shutter"))   # the white now reads s 0, v 100
press_button("Nothing in front of the colour sensor (point it at open space).")
xyz.calibrate_black()                            # subtracted from every reading
print("black", xyz.black_reference(), "white", xyz.white_reference())   # (X, Y, Z) per 1x-gain cycle
print("stored:", xyz.stored_calibration())       # 'chip': 'TCS3430', the same black and white
print("white sheet:", xyz.color_match(), xyz.hsv())   # (Color.WHITE, ~1.0)

press_button("Hold the colour sensor about 1 cm over a coloured surface.")
clock = StopWatch()
while clock.time() < 5000:
    x, y, z = xyz.xyz()                          # raw counts
    print("X %d  Y %d  Z %d  IR %d  raw %s  xy %s  saturated %s  age %d ms"
          % (x, y, z, xyz.ir(), xyz.raw(), xyz.xy(), xyz.saturated(), xyz.age()))
    print("   colour %s  hsv %s" % (xyz.color(), xyz.hsv()))   # from the default detectable colours
    wait(250)

# teach the sensor your own colours from its own hsv() readings
print("default detectable colours:", xyz.detectable_colors())
press_button("Hold the colour sensor about 1 cm over the FIRST colour to teach it.")
first = xyz.hsv()
press_button("Now over the SECOND colour.")
second = xyz.hsv()
xyz.detectable_colors((first, second))
press_button("Move it between the two colours.")
clock.reset()
while clock.time() < 5000:
    colour, confidence = xyz.color_match()       # confidence 1.0 on a taught colour, 0.0 halfway
    print("%s  (confidence %.2f)" % ("first" if colour == first else "second", confidence))
    wait(250)

e = xyz.exposure("program", 70, 25)             # never waits: the setting, the levels (X, Y, Z in %) and the pick
print("gain %dx, %.2f ms, levels %s, auto would pick %s" % (e["gain"], e["integration_time"], e["levels"], e["auto"]))
press_button("Hold the white sheet about 1 cm in front again.")
print("auto_exposure:", xyz.auto_exposure("program", 70, 25))   # 25 readings a second at least; this program only
xyz.store_exposure()                             # the setting in force is the port's default now
press_button("Hold a black card about 1 cm in front, still.")
print("measured gain steps:", xyz.calibrate_gains())   # five, the first 1.0 (typical 1 : 4 : 16 : 66 : 137)
xyz.gain(16)                                     # less gain for a bright target close up
print("integration time set: %.2f ms" % xyz.integration_time(100))   # far-field / ambient light
print("16x, 100 ms: xyz %s, xy %s" % (xyz.xyz(), xyz.xy()))   # xy() is uncalibrated chromaticity
print("far infrared (IR2): %d" % xyz.ir2())      # measured now: about two cycles, X pauses meanwhile
print("wait set: %s ms" % xyz.wait_time(400))    # a reading every 100 + 400 ms now
print("auto-zero:", xyz.autozero())              # (127, 0): at the first cycle only
xyz.autozero(10)                                 # re-measure the dark offset every 10 cycles
# the Z window: Z outside 0..2 for 3 cycles in a row latches interrupt() (room light is above 2)
xyz.thresholds(0, 2, 3)
print("thresholds:", xyz.thresholds())           # (0, 2, 3)
wait(2000)
print("Z left the window:", xyz.interrupt())
xyz.clear_interrupt()
xyz.thresholds(0, 0, 0)                          # the start-up window: the flag set every cycle
xyz.wait_time(0)                                 # back to the start-up settings
xyz.autozero(127)
xyz.black_reference(None)                        # clear both references (and the stored record): raw readings again
xyz.white_reference(None)
print("references cleared:", xyz.black_reference(), xyz.white_reference())   # None None
print("stored record cleared:", xyz.clear_calibration())   # True: nothing of it left in flash
xyz.close()                                      # powers the chip down
