Initial commit of oled_clock
authorPat Thoyts <patthoyts@users.sourceforge.net>
Mon, 18 Jan 2016 10:45:54 +0000 (10:45 +0000)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Mon, 18 Jan 2016 10:45:54 +0000 (10:45 +0000)
README [new file with mode: 0644]
install.sh [new file with mode: 0755]
oled_clock.py [new file with mode: 0755]
oled_clock.service [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
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 (executable)
index 0000000..9cc386f
--- /dev/null
@@ -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 (executable)
index 0000000..97a1697
--- /dev/null
@@ -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 (file)
index 0000000..e0edbf1
--- /dev/null
@@ -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
+