From: Pat Thoyts Date: Mon, 18 Jan 2016 10:45:54 +0000 (+0000) Subject: Initial commit of oled_clock X-Git-Url: https://privyetmir.co.uk/gitweb.cgi?a=commitdiff_plain;h=5cfe76595850b9a1841bc0fdd73edd37675e0fe6;p=oled_clock Initial commit of oled_clock --- 5cfe76595850b9a1841bc0fdd73edd37675e0fe6 diff --git a/README b/README new file mode 100644 index 0000000..612d53a --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +Clock display for Raspberry Pi Zero. + +I Created a board to plug into the Raspberry Pi Zero that includes +a DS1307 real-time clock, a 0.96" OLED I2C display, a DS18B20 temperature +sensor, a couple of buttons, a TSOP IR sensor and a HC-SR04 ultrasound +distance sensor. + +This project provides a systemd service that displays the time, temperature +and distance on the display once the Pi has booted up. + diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..9cc386f --- /dev/null +++ b/install.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# +# Install the OLED Clock application and systemd service. + +install -o root -g staff -m 0775 oled_clock.py /usr/local/bin/oled_clock.py +install -o root -g root -m 0664 oled_clock.service /etc/systemd/system/oled_clock.service + +systemctl daemon-reload +systemctl start oled_clock.service +systemctl enable oled_clock.service + diff --git a/oled_clock.py b/oled_clock.py new file mode 100755 index 0000000..97a1697 --- /dev/null +++ b/oled_clock.py @@ -0,0 +1,211 @@ +#!/usr/bin/python3 + +from __future__ import print_function +from PIL import Image, ImageDraw, ImageFont +from Adafruit_SSD1306 import SSD1306_128_64 +from RPi import GPIO +from threading import Thread +from time import sleep, localtime, strftime, time +from statistics import median + +GPIO.setmode(GPIO.BCM) +GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_UP) +GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP) +button1,button2 = 0, 0 + +TRIG_PIN = 17 +ECHO_PIN = 27 + +class RangeSensor(): + def __init__(self): + self.cancelled = False + GPIO.setup(TRIG_PIN, GPIO.OUT) + GPIO.setup(ECHO_PIN, GPIO.IN) + GPIO.output(TRIG_PIN, False) # set low + + def cancel(self): + self.cancelled = true + + def measure(self): + GPIO.output(TRIG_PIN, False) # ensure a low period before the high pulse + sleep(0.000005) # 5us + GPIO.output(TRIG_PIN, True) + sleep(0.000010) # 10us + GPIO.output(TRIG_PIN, False) + init = start = stop = time() + valid = False + while GPIO.input(ECHO_PIN) == 0 and ((start - init) < 0.050) and not self.cancelled: + start = time() + while GPIO.input(ECHO_PIN) == 1 and ((start - init) < 0.050) and not self.cancelled: + valid = True + stop = time() + if self.cancelled or not valid: + return 0,0 + # max range is 4000mm so 24 ms echo time. Docs recomment 60ms between measurements + # to avoid problems with the previous pulse. + # note freq is 40kHz and cats can hear up to 60kHz. Short pulse though. + elapsed = stop - start + distance = elapsed * 340000 # speed of sound in mm/s + distance = distance / 2 # its an echo, so halve it. + return elapsed,int(distance + 0.5) + +class RangeReader(Thread): + """Read the distance sensor once every second""" + def __init__(self): + self.running = False + self.value = 0 + self.values = [] + self.sensor = RangeSensor() + #self.log = open('/var/tmp/sensor.log','a+') + Thread.__init__(self) + + @property + def distance(self): + """Return distance in mm""" + return self.value + + def stop(self): + self.running = False + self.sensor.cancel = True + + def run(self): + self.running = True + delay = 0 + while self.running: + if delay == 0: + self.update() + delay = 3 + else: + delay = delay - 1 + sleep(0.1) + + def update(self): + global button1 + if not button1: + elapsed,value = self.sensor.measure() + self.values.append((elapsed,value)) + if len(self.values) == 3: + self.value = median([x[1] for x in self.values]) + #self.log.write(repr(self.values)) + #self.log.write("\n") + self.values = [] + +def OnButtonPress(channel): + global button1, button2 + if channel == 22: + button1 = not button1 + else: + button2 = not button2 + +def add_button_event(channel): + result = False + try: + GPIO.add_event_detect(channel, GPIO.BOTH, + callback=OnButtonPress, bouncetime=300) + result = True + except RuntimeError as e: + pass + return result + +w1dev = r'28-0000063e0ff4' + +class W1Sensor(Thread): + """Reading the OneWire sensor is slow so use a worker thread + and post the results to the main application at 5 second intervals""" + + def __init__(self, id): + self.path = "/sys/bus/w1/devices/%s/w1_slave" % id + self.running = False + self.t = 0.0 + Thread.__init__(self) + + @property + def temperature(self): + return self.t + + def stop(self): + self.running = False + + def run(self): + self.running = True + delay = 0 + while self.running: + if delay == 0: + self.update() + delay = 50 + else: + delay = delay - 1 + sleep(0.1) + + def update(self): + """Get the temperature from the on-board OneWire sensor""" + try: + with open(self.path, 'rb') as f: + data = f.read().decode('utf-8') + t = data.split("\n")[1].split(" ")[9] + self.t = float(t[2:]) / 1000.0 + except Exception as e: + print(e) + raise + +def main(args = None): + for chan in [22,23]: + count = 0 + while count < 10: + count = count + 1 + if add_button_event(chan): + break + if count > 9: + print("failed to connect channel %d" % chan) + + d = SSD1306_128_64(i2c_bus=1,rst=0) + d.begin() + d.clear() + d.display() + + #font = ImageFont.load_default() + fontpath2 = '/usr/share/fonts/truetype/freefont/FreeMono.ttf' + fontpath = '/usr/share/fonts/truetype/roboto/Roboto-Bold.ttf' + #fontpath2= '/usr/share/fonts/truetype/roboto/Roboto-Regular.ttf' + + font = ImageFont.truetype(fontpath, 28) + font2 = ImageFont.truetype(fontpath, 15) + + buffer = Image.new('1', (d.width, d.height)) + draw = ImageDraw.Draw(buffer) + + rangeSensor = RangeReader() + rangeSensor.start() + sensor = W1Sensor(w1dev) + sensor.start() + + msg = last_msg = None + temp = last_temp = None + dist = last_dist = None + but = last_but = None + try: + while True: + now = localtime() + temp = "%.2f" % sensor.temperature + msg = strftime('%H:%M:%S', now) + dist = "%d mm" % rangeSensor.distance + but = "%d %d" % (button1, button2) + if not (temp == last_temp and msg == last_msg and but == last_but and dist == last_dist): + last_temp,last_msg,last_but,last_dist = temp,msg,but,dist + draw.rectangle((0,0,d.width,d.height), outline=0, fill=0) #erase + draw.text((0, 0), temp, font=font2, fill=255) + draw.text((90, 0), but, font=font2, fill=255) + draw.text((5, 18), msg, font=font, fill=255) + if not button1: + draw.text((0,48), dist, font=font2, fill=255) + d.image(buffer) + d.display() + sleep(0.1) + except KeyboardInterrupt as e: + sensor.stop() + rangeSensor.stop() + pass + +if __name__ == '__main__': + import sys + sys.exit(main()) diff --git a/oled_clock.service b/oled_clock.service new file mode 100644 index 0000000..e0edbf1 --- /dev/null +++ b/oled_clock.service @@ -0,0 +1,17 @@ +# systemd unit file for running the OLED clock +# +# This file needs to be installed in /etc/systemd/system/oled_clock.service +# then enable and start using 'systemctl start oled_clock.service' + +[Unit] +Description=OLED Clock +#Before=netct-auto@wlan0.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/oled_clock.py +SyslogIdentifier=oled_clock + +[Install] +WantedBy=multi-user.target +