diff --git a/RTC-Hat-Examples/README.md b/RTC-Hat-Examples/README.md new file mode 100644 index 0000000..25caf97 --- /dev/null +++ b/RTC-Hat-Examples/README.md @@ -0,0 +1,151 @@ +# Using the MCP7940N Hardware Clock on the Papirus HAT + +During the Papirus Kickstarter campaign a lot of time has been spent on finding a hardware clock +with a proper alarm function and the right pogo pin to connect the alarm output to the Pi reset header. +This to make a wake-on-alarm function possible. +However a wake-on-alarm software example was not provided. The two examples in [RTCreboot](RTCreboot) and [RTCgpio](RTCgpio) +should give you sufficient information to use the hardware clock. + +The Papirus HAT (not the Papirus Zero) has a MCP7940N battery backed-up hardware clock. +The MCP7940N is controlled via the i2c bus. Its i2c address is 0x6f. +For all the details on the MCP7940N see the [datasheet](mcp7940n.pdf). + +The MCP7940N alarm output (MFP pin) on the Papirus hat is connected to a pulse shaping circuit. The output of this +circuit (signal RTC_OUT on the Papirus hat schematic) is connected to both the pogo pin and to RPi's GPIO 27 +(pin 13 on the GPIO connector). The normal state of RTC_OUT is high. When the MFP output goes from high to low +a low pulse of about 12 msec is output on RTC_OUT. A low to high transition of the MFP output causes no change of RTC_OUT. + +When the pogo pin is connected to the Raspberry Pi reset header the RTC_OUT pulse will cause +a (re)boot of the Pi even after it is shut down (but with power still connected). +When the pogo pin is not connected to the Pi reset header the RTC_OUT pulse can still be detected with GPIO 27. + +The example in the [RTCreboot](RTCreboot) directory shows how to use the wake-on-alarm function. +When the pogo pin is not connected to the Raspberry Pi reset header you can stil detect the RTC_OUT pulses +using GPIO 27. See [RTCgpio](RTCgpio). + +But first how to use the clock function to keep the time on the Pi between boots. + +# Using the Hardware Clock + +Raspbian Jessie has built-in support for the MCP7940N provided by the rtc_ds1307 module. +This module supports various i2c based real time clocks including the MCP7940N. +Add the following line to `/boot/config.txt`: +``` +dtoverlay=i2c-rtc,mcp7940x=1 +``` +In order to set the system time from the hardware clock at boot you need to modify `/lib/udev/hwclock-set` by +commenting out the check for systemd: +``` +#!/bin/sh +# Reset the System Clock to UTC if the hardware clock from which it +# was copied by the kernel was in localtime. + +dev=$1 + +#if [ -e /run/systemd/system ] ; then +# exit 0 +#fi + +if [ -e /run/udev/hwclock-set ]; then + exit 0 +fi +``` +The reason for this is that standard Debian assumes the system time is already set from the hardware clock by systemd. +This requires a built-in hardware clock driver in the kernel since systemd does this before any modules are loaded. +Since the Pi can only use external hardware clocks, Raspbian only supports the hardware clock driver as a module. +Therefore we have to set the system clock from the hardware clock when udev detects the hardware clock. +Note that the system clock (and hence the hardware clock) on a Linux system is usually kept in UTC. + +After a reboot you can check if the MCP7940N has been recognized. +In the dmesg output you should find something like: +``` +[ 5.735640] rtc-ds1307 1-006f: rtc core: registered mcp7940x as rtc0 +[ 5.735692] rtc-ds1307 1-006f: 64 bytes nvram +``` +If you have i2c-tools installed you can look at the i2cdetect output: +``` +pi@papirus-hat:~ $ i2cdetect -y 1 + 0 1 2 3 4 5 6 7 8 9 a b c d e f +00: -- -- -- -- -- -- -- -- -- -- -- -- -- +10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- -- +50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- UU +70: -- -- -- -- -- -- -- -- +``` +The `UU` for MCP7940N address 0x6f means this address is used by a kernel driver (rtc_ds1307). +Address 0x48 is the temperature sensor on the Papirus hat. + +You can check the current hardware clock time with the command `sudo hwclock -r`. +For more info try `sudo hwclock -r --debug`. Here is a sample output: +``` +pi@papirus-hat:~ $ sudo hwclock -r --debug +hwclock from util-linux 2.25.2 +Using the /dev interface to the clock. +Last drift adjustment done at 1470071156 seconds after 1969 +Last calibration done at 1470071156 seconds after 1969 +Hardware clock is on UTC time +Assuming hardware clock is kept in UTC time. +Waiting for clock tick... +/dev/rtc does not have interrupt functions. Waiting in loop for time from /dev/rtc to change +...got clock tick +Time read from Hardware Clock: 2016/08/05 19:33:53 +Hw clock time : 2016/08/05 19:33:53 = 1470425633 seconds since 1969 +Fri 05 Aug 2016 21:33:53 CEST -0.133460 seconds +``` +Note the hardware clock is in UTC, but the time is presented in CEST (Central European Summer Time). + +You can set the hardware clock to the system clock with `sudo hwclock -w` +When you are connected to the internet you can just wait about 15 minutes. +The system (provided the ntp daemon is enabled, which it normally is in Raspbian) will copy the system +time to the hardware clock approximately once every 15 minutes. + +Systemd provides a hwclock-save service which is run at shutdown. However when ntp is installed (/usr/sbin/ntpd +is an executable file) the system time is *not* copied to the hardware clock. Therefore I recommend to +comment out the ntpd check in `/lib/systemd/system/hwclock-save.service`: +``` +[Unit] +Description=Synchronise Hardware Clock to System Clock +DefaultDependencies=no +Before=shutdown.target +#ConditionFileIsExecutable=!/usr/sbin/ntpd +ConditionFileIsExecutable=!/usr/sbin/openntpd +ConditionFileIsExecutable=!/usr/sbin/chrony +ConditionVirtualization=!container + +[Service] +Type=oneshot +ExecStart=/sbin/hwclock -D --systohc + +[Install] +WantedBy=reboot.target halt.target poweroff.target +``` +This ensures the hardware clock is always set from the system clock at shutdown independent from the network and ntp. + +# Accessing the MCP7940N from Python with the kernel driver (rtc_ds1307) loaded + +If you try to access the MCP7940N registers with i2cget or i2cset (from i2c-tools) or from Python with the smbus +module you will get errors since the kernel has already claimed i2c address 0x6f. +In order to be able to set the MCP7940N alarm registers from Python we need to be able to access i2c addres 0x6f. + +I have modified the smbus module (renamed to smbusf) so it allows access to an i2c device which the kernel has +already claimed. Smbusf uses the same mechanism as the '-f' option of i2cget/i2cset. +See the [py-smbusf](py-smbusf) directory for details how to build and install this module. +Both the MCP7940N Python examples use the smbusf module. The smbusf calling interface is identical to the standard smbus +module. + +# The Sample Programs + +## Pogo pin connected to the Raspberry Pi reset header + +See the [RTCreboot](RTCreboot) directory for details. + +## Detecting the alarm output on GPIO 27 when the pogo pin is not connected + +See the [RTCgpio](RTCgpio) directory for details. + +## Video + +See both in action on [YouTube](https://youtu.be/H8aviSTrx4Q). diff --git a/RTC-Hat-Examples/RTCgpio/README.md b/RTC-Hat-Examples/RTCgpio/README.md new file mode 100644 index 0000000..11fc6f3 --- /dev/null +++ b/RTC-Hat-Examples/RTCgpio/README.md @@ -0,0 +1,27 @@ +# RTC GPIO + +This demo shows the 3 different output modes for the MFP (Multi Function Pin) of the MCP7940N hardware clock: +- Alarm output from the two independent alarm clocks. +- Square wave output of various frequencies (the program only uses the lowest one: 1 Hz). +- Direct programming of the MFP output. + +Remember that the circuit on the PaPiRus only generates a pulse on GPIO 27 on a high to low transition +of the MFP output. + +Also this program relies on the smbusf module (See [py_smbusf](../py-smbusf) for build and installation instructions). + +## Alarm output + +We set the two alarms 10 seconds apart and wait for them to trigger. +Only one alarm can be enabled at the same time. The first alarm is enabled. +When the first alarm triggers, we disable it and enable the second alarm. +See the datasheet, section 5.5 for details. + +## Square wave output + +A square wave can be output of 1 Hz, 4096 kHz, 8192 kHz and 32768 kHz. +The demo uses the 1 Hz output becuase we want to show the individual interrupts on the Papirus display. + +## Direct programming + +The demo generates 5 interrupts 1 second apart followed by 5 interrupts 2 seconds apart. diff --git a/RTC-Hat-Examples/RTCgpio/prtc.py b/RTC-Hat-Examples/RTCgpio/prtc.py new file mode 120000 index 0000000..63786e4 --- /dev/null +++ b/RTC-Hat-Examples/RTCgpio/prtc.py @@ -0,0 +1 @@ +../RTCreboot/prtc.py \ No newline at end of file diff --git a/RTC-Hat-Examples/RTCgpio/pwrite_text.py b/RTC-Hat-Examples/RTCgpio/pwrite_text.py new file mode 120000 index 0000000..07e0a26 --- /dev/null +++ b/RTC-Hat-Examples/RTCgpio/pwrite_text.py @@ -0,0 +1 @@ +../RTCreboot/pwrite_text.py \ No newline at end of file diff --git a/RTC-Hat-Examples/RTCgpio/rtcgpio b/RTC-Hat-Examples/RTCgpio/rtcgpio new file mode 100755 index 0000000..6c5e962 --- /dev/null +++ b/RTC-Hat-Examples/RTCgpio/rtcgpio @@ -0,0 +1,159 @@ +#!/usr/bin/env python + +from smbusf import SMBus +from datetime import datetime, timedelta +from prtc import readrtc, writealm, readalm +from prtc import enablealm0, enablealm1, disablealm0, disablealm1 +from prtc import enablesqw, disablesqw, mfpoutput +from papirus import Papirus +from pwrite_text import write_text, replace_line, update_lines +import os +import time +import RPi.GPIO as GPIO +import threading + +def alm_callback(channel): + global nrint, alm0time, alm1time + if nrint == 0: + # If you want to use both alarms to trigger the interrupt, then only one alarm can be enabled. + # See the datasheet section 5.5 for details. + # On the first interrupt disable alarm 0 en enable alarm 1 + disablealm0(i2cbus) + enablealm1(i2cbus) + alm0time = readrtc(i2cbus).strftime('%H:%M:%S') + ' UTC' + elif nrint == 1: + alm1time = readrtc(i2cbus).strftime('%H:%M:%S') + ' UTC' + nrint = nrint + 1 + +def sqw_callback(channel): + global nrint + nrint = nrint + 1 + +def mfpoutputtask(): + for i in range(4): + mfpoutput(i2cbus, 0) + time.sleep(0.5) + mfpoutput(i2cbus, 1) + time.sleep(0.5) + for i in range(6): + mfpoutput(i2cbus, 0) + time.sleep(1) + mfpoutput(i2cbus, 1) + time.sleep(1) + mfpoutput(i2cbus, 0) + mfpoutput(i2cbus, 1) + +i2cbus = SMBus(1) +papirus = Papirus() +papirus.clear() +GPIO.setmode(GPIO.BCM) +GPIO.setup(27, GPIO.IN) + +# --------- 1. Use of the two alarms --------- + +# Set up the GPIO 27 interrupt and disable both alarms on the MCP7940N + +nrint = 0 +GPIO.add_event_detect(27, GPIO.FALLING, callback = alm_callback) +disablealm0(i2cbus) +disablealm1(i2cbus) + +# Set the two alarm times + +dtrtc = readrtc(i2cbus) +alarm0 = dtrtc + timedelta(seconds = 15) +alarm1 = dtrtc + timedelta(seconds = 25) +writealm(i2cbus, 0, alarm0) +writealm(i2cbus, 1, alarm1) + +# Display the two alarm times on the Papirus diplay + +alm0time = readalm(i2cbus, 0).strftime('%H:%M:%S') +alm1time = readalm(i2cbus, 1).strftime('%H:%M:%S') +write_text(papirus, 'Alarm Times Set:', x=20) +replace_line(papirus, 15, 20, 'Alm0: ' + alm0time + ' UTC') +replace_line(papirus, 15, 40, 'Alm1: ' + alm1time + ' UTC') +update_lines(papirus) + +# Enable alarm 0 +enablealm0(i2cbus) + +# Display the hardware clock time and wait for the 2 alarms +prevsec = -1 +alm0time = alm1time = '' +while nrint <= 2: + dtrtc = readrtc(i2cbus) + if dtrtc.second != prevsec: + replace_line(papirus, 60, 150, dtrtc.strftime('%H:%M:%S') + ' UTC') + prevsec = dtrtc.second + if alm0time != '': + replace_line(papirus, 15, 80, 'Alm0 at ' + alm0time) + alm0time = '' + if alm1time != '': + replace_line(papirus, 15, 100, 'Alm1 at ' + alm1time) + alm1time = '' + update_lines(papirus) + if nrint == 2: + break + time.sleep(0.1) + +GPIO.remove_event_detect(27) +time.sleep(0.5) +replace_line(papirus, 60, 150, '') +update_lines(papirus) +time.sleep(2) + +# --------- 2. 1 Hz swuare wave --------- + +write_text(papirus, '1 Hz square wave', x=15) + +GPIO.add_event_detect(27, GPIO.FALLING, callback = sqw_callback) +enablesqw(i2cbus) + +nrint = prevnrint = 0 +prevsec = -1 +while nrint < 21: + dtrtc = readrtc(i2cbus) + if dtrtc.second != prevsec: + replace_line(papirus, 60, 150, dtrtc.strftime('%H:%M:%S') + ' UTC') + prevsec = dtrtc.second + if nrint != prevnrint: + replace_line(papirus, 60, 65, 'Interrupt ' + format(nrint)) + prevnrint = nrint + update_lines(papirus) + time.sleep(0.1) + +GPIO.remove_event_detect(27) +disablesqw(i2cbus) +time.sleep(0.5) +replace_line(papirus, 60, 150, '') +update_lines(papirus) +time.sleep(2) + +# --------- 3. Set mfp output directly --------- + +write_text(papirus, 'Programmed output', x=20) + +GPIO.add_event_detect(27, GPIO.FALLING, callback = sqw_callback) + +t = threading.Thread(target=mfpoutputtask) +t.start() + +nrint = prevnrint = 0 +prevsec = -1 +while t.isAlive(): + dtrtc = readrtc(i2cbus) + if dtrtc.second != prevsec: + replace_line(papirus, 60, 150, dtrtc.strftime('%H:%M:%S') + ' UTC') + prevsec = dtrtc.second + if nrint != prevnrint: + replace_line(papirus, 60, 65, 'Interrupt ' + format(nrint)) + prevnrint = nrint + update_lines(papirus) + time.sleep(0.1) + +replace_line(papirus, 60, 150, '') +update_lines(papirus) +time.sleep(2) + +write_text(papirus, 'End of Demo', x=50, y=papirus.height/2 - 10) diff --git a/RTC-Hat-Examples/RTCreboot/README.md b/RTC-Hat-Examples/RTCreboot/README.md new file mode 100644 index 0000000..a131981 --- /dev/null +++ b/RTC-Hat-Examples/RTCreboot/README.md @@ -0,0 +1,27 @@ +# Rebooting the PI using the wake-on-alarm function of the MCP7940N + +This example uses two programs: + +* `rtcreboot` which sets the alarm a number of seconds in the future and then shuts down the RPi. + The default is 120 seconds. You can specify a different delay (in seconds) as an argument to rtcreboot. + `rtcreboot` writes the expected reboot time on the Papirus display. + +* the second program is `bootinfo` which should be run at system boot. This writes the start time (the output + from `uptime -s`, the current system time and the hardware clock time (normally in UTC) to the Papirus display. + +Both programs rely on the smbusf module (See [py_smbusf](../py-smbusf) for build and installation instructions). +In order to run `bootinfo` at system boot add the following line to `/etc/rc.local`: +``` +sudo -u pi /home/pi/PaPiRus/RTC-Hat-Examples/RTCreboot/bootinfo +``` +Modify the path as required for your installation. +`bootinfo` has to be run as user pi, since the smbusf module is only installed for user pi when you follow the +instructions in [py_smbusf](../py-smbusf). + +Both programs use support functions in `prtc.py` (hardware clock access functions) and `pwrite_text.py` (custom +version to write text on the Papirus display) + +The programs text output is layed out for the large Papirus display (2.7 inch 264 x 176). + +For the programming details of the MCP7940N the [datasheet](../mcp7940n.pdf) is the best reference. +See section 5 and especially section 5.4 about alarms. diff --git a/RTC-Hat-Examples/RTCreboot/bootinfo b/RTC-Hat-Examples/RTCreboot/bootinfo new file mode 100755 index 0000000..b409534 --- /dev/null +++ b/RTC-Hat-Examples/RTCreboot/bootinfo @@ -0,0 +1,21 @@ +#! /usr/bin/env python + +from papirus import Papirus +from prtc import readrtc, disablealm0 +from smbusf import SMBus +from pwrite_text import write_text +from subprocess import check_output +from datetime import datetime +import os + +papirus = Papirus() +i2cbus = SMBus(1) + +disablealm0(i2cbus) + +upsince = check_output(["uptime", "-s"]) +now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') +rtc = readrtc(i2cbus).strftime('%Y-%m-%d %H:%M:%S') + +write_text(papirus, 'System up since ' + upsince + ' Current time ' + now + ' Real Time Clock time ' + rtc, y = 50, load = True, ldfile = os.environ['HOME'] + '/rtcreboot.bmp') + diff --git a/RTC-Hat-Examples/RTCreboot/prtc.py b/RTC-Hat-Examples/RTCreboot/prtc.py new file mode 100644 index 0000000..7befaa6 --- /dev/null +++ b/RTC-Hat-Examples/RTCreboot/prtc.py @@ -0,0 +1,131 @@ +# utility functions for Papirus Hat hardware clock (MCP7940N) + +from datetime import datetime +from calendar import isleap +from smbusf import SMBus + +RTCADR = 0x6f +STBIT = 0x80 +LPYR = 0x20 +almbase = [0xa, 0x11] + +def tobcd(val): + return (val % 10) | (val / 10) << 4 + +def writertc(i2cbus, dt): + sec = dt.second + min = dt.minute + hour = dt.hour + # rtc-ds1307 uses weekday convention Sun = 1, Sat = 7 + wkday = (dt.weekday() + 1)%7 + 1 + day = dt.day + month = dt.month + year = dt.year + leap = isleap(year) + + data = [0,0,0,0,0,0,0] + data[0] = tobcd(sec) | STBIT + data[1] = tobcd(min) + data[2] = tobcd(hour) + bits345 = i2cbus.read_byte_data(RTCADR, 3) & 0x38 + data[3] = bits345 | wkday + data[4] = tobcd(day) + data[5] = tobcd(month) + if leap: + data[5] |= LPYR + data[6] = tobcd(year % 100) + + i2cbus.write_i2c_block_data(RTCADR, 0, data) + +def writealm(i2cbus, alm, dt): + if alm > 0: + alm = 1 + else: + alm = 0 + sec = dt.second + min = dt.minute + hour = dt.hour + # rtc-ds1307 uses weekday convention Sun = 1, Sat = 7 + # wkday in alarm has to match the wkday of rtc time for the alarm to trigger + wkday = (dt.weekday() + 1)%7 + 1 + day = dt.day + month = dt.month + year = dt.year + + data = [0,0,0,0,0,0] + data[0] = tobcd(sec) + data[1] = tobcd(min) + data[2] = tobcd(hour) + data[3] = 0x70 | wkday # ALM0MSK = 111 + data[4] = tobcd(day) + data[5] = tobcd(month) + + i2cbus.write_i2c_block_data(RTCADR, almbase[alm], data) + +def readrtc(i2cbus): + data=i2cbus.read_i2c_block_data(RTCADR, 0, 7) + + sec = (data[0] & 0x7f) / 16 * 10 + (data[0] & 0x0f) + min = data[1] / 16 * 10 + (data[1] & 0x0f) + hour = data[2] / 16 * 10 + (data[2] & 0x0f) + day = data[4] / 16 * 10 + (data[4] & 0x0f) + month = (data[5] & 0x10) / 16 * 10 + (data[5] & 0x0f) + year = data[6] / 16 * 10 + (data[6] & 0x0f) + dt = datetime(2000+year, month, day, hour, min, sec) + return dt + +def readalm(i2cbus, alm): + if alm > 0: + alm = 1 + else: + alm = 0 + data = i2cbus.read_i2c_block_data(RTCADR, almbase[alm], 6) + + sec = data[0] / 16 * 10 + (data[0] & 0x0f) + min = data[1] / 16 * 10 + (data[1] & 0x0f) + hour = data[2] / 16 * 10 + (data[2] & 0x0f) + day = data[4] / 16 * 10 + (data[4] & 0x0f) + month = data[5] / 16 * 10 + (data[5] & 0x0f) + # year not used in alarm time + dt = datetime(2000, month, day, hour, min, sec) + return dt + +def enablealm0(i2cbus): + data = i2cbus.read_byte_data(RTCADR, 7) + data |= 0x10 + i2cbus.write_byte_data(RTCADR, 7, data) + +def enablealm1(i2cbus): + data = i2cbus.read_byte_data(RTCADR, 7) + data |= 0x20 + i2cbus.write_byte_data(RTCADR, 7, data) + +def disablealm0(i2cbus): + # When disabling the alarm, keep the mfp output high (otherwise we'll get an immediate reboot) + data = i2cbus.read_byte_data(RTCADR, 7) + data &= 0xef + data |= 0x80 + i2cbus.write_byte_data(RTCADR, 7, data) + +def disablealm1(i2cbus): + # When disabling the alarm, keep the mfp output high (otherwise we'll get an immediate reboot) + data = i2cbus.read_byte_data(RTCADR, 7) + data &= 0xdf + data |= 0x80 + i2cbus.write_byte_data(RTCADR, 7, data) + +def enablesqw(i2cbus): + # Set 1 Hz square wave output + i2cbus.write_byte_data(RTCADR, 7, 0x40) + +def disablesqw(i2cbus): + # Disable square wave and set ouput high + i2cbus.write_byte_data(RTCADR, 7, 0x80) + +def mfpoutput(i2cbus, val): + # set MFP output directly + if val == 0: + data = 0x00 + else: + data = 0x80 + i2cbus.write_byte_data(RTCADR, 7, data) diff --git a/RTC-Hat-Examples/RTCreboot/pwrite_text.py b/RTC-Hat-Examples/RTCreboot/pwrite_text.py new file mode 100644 index 0000000..58d233b --- /dev/null +++ b/RTC-Hat-Examples/RTCreboot/pwrite_text.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +from papirus import Papirus +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +from smbusf import SMBus +import os + +WHITE = 1 +BLACK = 0 +RTCADR = 0x6f + +lock = False + +def main(): + papirus = Papirus() + i2cbus=SMBus(1) + + write_text(papirus, 'Line 1', save = True) + write_text(papirus, 'Line 2', y = 20, load = True, ldfile = 'save.bmp') + +def write_text(papirus, text, x=0, y=0, size=20, load = False, ldfile = ' ', save = False, file = 'save.bmp'): + global image, draw, font + + if os.path.isfile(ldfile): + image = Image.open(ldfile) + image.load() + os.remove(ldfile) + else: + # set all white background + image = Image.new('1', papirus.size, WHITE) + + # prepare for drawing + draw = ImageDraw.Draw(image) + + font = ImageFont.truetype('/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf', size) + + # Calculate the max number of char to fit on line + line_size = ((papirus.width - x) / (size*0.65)) + + current_line = 0 + text_lines = [""] + + # Compute each line + for word in text.split(): + # If there is space on line add the word to it + if (len(text_lines[current_line]) + len(word)) < line_size: + text_lines[current_line] += " " + word + else: + # No space left on line so move to next one + text_lines.append("") + current_line += 1 + text_lines[current_line] += " " + word + + current_line = 0 + for l in text_lines: + draw.text( (x, (size*current_line + y)), l, font=font, fill=BLACK) + current_line += 1 + + papirus.display(image) + papirus.partial_update() + if (save): + image.save(file) + +def replace_line(papirus, x, y, text, size=20): + global image, draw, font, lock + + while lock == True: + pass + lock = True + draw.rectangle((x, y, papirus.width, y + size), fill=WHITE, outline=WHITE) + draw.text((x, y), text, font=font, fill=BLACK) + #papirus.display(image) + #papirus.partial_update() + lock = False + +def update_lines(papirus): + global image + papirus.display(image) + papirus.partial_update() + +if __name__ == '__main__': + main() diff --git a/RTC-Hat-Examples/RTCreboot/rtcreboot b/RTC-Hat-Examples/RTCreboot/rtcreboot new file mode 100755 index 0000000..49463b3 --- /dev/null +++ b/RTC-Hat-Examples/RTCreboot/rtcreboot @@ -0,0 +1,39 @@ +#! /usr/bin/env python + +import argparse +from smbusf import SMBus +from datetime import datetime, timedelta +from dateutil import tz +from prtc import readrtc, writealm, enablealm0 +from papirus import Papirus +from pwrite_text import write_text +import os + +parser = argparse.ArgumentParser() +parser.add_argument('delay', nargs='?', default=120, type=int, help='delay in seconds till reboot, default 120') +args=parser.parse_args() + +i2cbus = SMBus(1) +papirus = Papirus() +papirus.clear() + +# time to wait before booting +delta=timedelta(seconds = args.delay) + +dtrtc = readrtc(i2cbus) +reboot = dtrtc + delta +writealm(i2cbus, 0, reboot) + +# RTC is in UTC, convert to local time +HERE = tz.tzlocal() +UTC = tz.gettz('UTC') +reboot = reboot.replace(tzinfo=UTC) +reboot = reboot.astimezone(HERE) +reboottime = reboot.strftime('%H:%M:%S') + +shutdowntext = "Shutting down. Reboot at " + reboottime +write_text(papirus, shutdowntext, save = True, file = os.environ['HOME'] + '/rtcreboot.bmp') + +# Enable alrm 0 and shut down the RPi +enablealm0(i2cbus) +os.system("sudo poweroff") diff --git a/RTC-Hat-Examples/mcp7940n.pdf b/RTC-Hat-Examples/mcp7940n.pdf new file mode 100644 index 0000000..1c7b19f Binary files /dev/null and b/RTC-Hat-Examples/mcp7940n.pdf differ diff --git a/RTC-Hat-Examples/py-smbusf/Makefile b/RTC-Hat-Examples/py-smbusf/Makefile new file mode 100644 index 0000000..7662669 --- /dev/null +++ b/RTC-Hat-Examples/py-smbusf/Makefile @@ -0,0 +1,29 @@ +# I2C tools for Linux +# +# Copyright (C) 2007-2012 Jean Delvare +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +CC ?= gcc + +CFLAGS ?= -O2 +# When debugging, use the following instead +#CFLAGS := -O -g +CFLAGS += -Wall + +PYTHON ?= python +DISTUTILS := \ + CPPFLAGS="$(CPPFLAGS) -Iinclude" $(PYTHON) setup.py + +all: include/linux/i2c-dev.h + $(DISTUTILS) build + +clean: + $(DISTUTILS) clean + rm -rf build + +install: + $(DISTUTILS) install --user diff --git a/RTC-Hat-Examples/py-smbusf/README.md b/RTC-Hat-Examples/py-smbusf/README.md new file mode 100644 index 0000000..d3802ca --- /dev/null +++ b/RTC-Hat-Examples/py-smbusf/README.md @@ -0,0 +1,17 @@ +# py-smbusf + +This module is identical to the normal smbus module, but it allows to access +i2c devices already claimed by a kernel driveri (e.g. the hardware clock in our case). +This is the same as using the '-f' option on the i2c-get and i2c-set programs from i2c-tools. + +You need the python-devel package for building this module: +``` + sudo apt-get install python-dev +``` + +To build and install: +``` + $ make install +``` +This will install the module in the directory: `/home/pi/.local/lib/python2.7/site-packages`. +Programs using this module therefore need to run as user pi. diff --git a/RTC-Hat-Examples/py-smbusf/include/linux/i2c-dev.h b/RTC-Hat-Examples/py-smbusf/include/linux/i2c-dev.h new file mode 100644 index 0000000..23f7c2c --- /dev/null +++ b/RTC-Hat-Examples/py-smbusf/include/linux/i2c-dev.h @@ -0,0 +1,330 @@ +/* + i2c-dev.h - i2c-bus driver, char device interface + + Copyright (C) 1995-97 Simon G. Vogl + Copyright (C) 1998-99 Frodo Looijaard + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA. +*/ + +#ifndef _LINUX_I2C_DEV_H +#define _LINUX_I2C_DEV_H + +#include +#include +#include + + +/* -- i2c.h -- */ + + +/* + * I2C Message - used for pure i2c transaction, also from /dev interface + */ +struct i2c_msg { + __u16 addr; /* slave address */ + unsigned short flags; +#define I2C_M_TEN 0x10 /* we have a ten bit chip address */ +#define I2C_M_RD 0x01 +#define I2C_M_NOSTART 0x4000 +#define I2C_M_REV_DIR_ADDR 0x2000 +#define I2C_M_IGNORE_NAK 0x1000 +#define I2C_M_NO_RD_ACK 0x0800 + short len; /* msg length */ + char *buf; /* pointer to msg data */ +}; + +/* To determine what functionality is present */ + +#define I2C_FUNC_I2C 0x00000001 +#define I2C_FUNC_10BIT_ADDR 0x00000002 +#define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* I2C_M_{REV_DIR_ADDR,NOSTART,..} */ +#define I2C_FUNC_SMBUS_PEC 0x00000008 +#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 */ +#define I2C_FUNC_SMBUS_QUICK 0x00010000 +#define I2C_FUNC_SMBUS_READ_BYTE 0x00020000 +#define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000 +#define I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000 +#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000 +#define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000 +#define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000 +#define I2C_FUNC_SMBUS_PROC_CALL 0x00800000 +#define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000 +#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000 +#define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */ +#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */ + +#define I2C_FUNC_SMBUS_BYTE (I2C_FUNC_SMBUS_READ_BYTE | \ + I2C_FUNC_SMBUS_WRITE_BYTE) +#define I2C_FUNC_SMBUS_BYTE_DATA (I2C_FUNC_SMBUS_READ_BYTE_DATA | \ + I2C_FUNC_SMBUS_WRITE_BYTE_DATA) +#define I2C_FUNC_SMBUS_WORD_DATA (I2C_FUNC_SMBUS_READ_WORD_DATA | \ + I2C_FUNC_SMBUS_WRITE_WORD_DATA) +#define I2C_FUNC_SMBUS_BLOCK_DATA (I2C_FUNC_SMBUS_READ_BLOCK_DATA | \ + I2C_FUNC_SMBUS_WRITE_BLOCK_DATA) +#define I2C_FUNC_SMBUS_I2C_BLOCK (I2C_FUNC_SMBUS_READ_I2C_BLOCK | \ + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK) + +/* Old name, for compatibility */ +#define I2C_FUNC_SMBUS_HWPEC_CALC I2C_FUNC_SMBUS_PEC + +/* + * Data for SMBus Messages + */ +#define I2C_SMBUS_BLOCK_MAX 32 /* As specified in SMBus standard */ +#define I2C_SMBUS_I2C_BLOCK_MAX 32 /* Not specified but we use same structure */ +union i2c_smbus_data { + __u8 byte; + __u16 word; + __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */ + /* and one more for PEC */ +}; + +/* smbus_access read or write markers */ +#define I2C_SMBUS_READ 1 +#define I2C_SMBUS_WRITE 0 + +/* SMBus transaction types (size parameter in the above functions) + Note: these no longer correspond to the (arbitrary) PIIX4 internal codes! */ +#define I2C_SMBUS_QUICK 0 +#define I2C_SMBUS_BYTE 1 +#define I2C_SMBUS_BYTE_DATA 2 +#define I2C_SMBUS_WORD_DATA 3 +#define I2C_SMBUS_PROC_CALL 4 +#define I2C_SMBUS_BLOCK_DATA 5 +#define I2C_SMBUS_I2C_BLOCK_BROKEN 6 +#define I2C_SMBUS_BLOCK_PROC_CALL 7 /* SMBus 2.0 */ +#define I2C_SMBUS_I2C_BLOCK_DATA 8 + + +/* /dev/i2c-X ioctl commands. The ioctl's parameter is always an + * unsigned long, except for: + * - I2C_FUNCS, takes pointer to an unsigned long + * - I2C_RDWR, takes pointer to struct i2c_rdwr_ioctl_data + * - I2C_SMBUS, takes pointer to struct i2c_smbus_ioctl_data + */ +#define I2C_RETRIES 0x0701 /* number of times a device address should + be polled when not acknowledging */ +#define I2C_TIMEOUT 0x0702 /* set timeout in units of 10 ms */ + +/* NOTE: Slave address is 7 or 10 bits, but 10-bit addresses + * are NOT supported! (due to code brokenness) + */ +#define I2C_SLAVE 0x0703 /* Use this slave address */ +#define I2C_SLAVE_FORCE 0x0706 /* Use this slave address, even if it + is already in use by a driver! */ +#define I2C_TENBIT 0x0704 /* 0 for 7 bit addrs, != 0 for 10 bit */ + +#define I2C_FUNCS 0x0705 /* Get the adapter functionality mask */ + +#define I2C_RDWR 0x0707 /* Combined R/W transfer (one STOP only) */ + +#define I2C_PEC 0x0708 /* != 0 to use PEC with SMBus */ +#define I2C_SMBUS 0x0720 /* SMBus transfer */ + + +/* This is the structure as used in the I2C_SMBUS ioctl call */ +struct i2c_smbus_ioctl_data { + __u8 read_write; + __u8 command; + __u32 size; + union i2c_smbus_data *data; +}; + +/* This is the structure as used in the I2C_RDWR ioctl call */ +struct i2c_rdwr_ioctl_data { + struct i2c_msg *msgs; /* pointers to i2c_msgs */ + __u32 nmsgs; /* number of i2c_msgs */ +}; + +#define I2C_RDRW_IOCTL_MAX_MSGS 42 + + +static inline __s32 i2c_smbus_access(int file, char read_write, __u8 command, + int size, union i2c_smbus_data *data) +{ + struct i2c_smbus_ioctl_data args; + + args.read_write = read_write; + args.command = command; + args.size = size; + args.data = data; + return ioctl(file,I2C_SMBUS,&args); +} + + +static inline __s32 i2c_smbus_write_quick(int file, __u8 value) +{ + return i2c_smbus_access(file,value,0,I2C_SMBUS_QUICK,NULL); +} + +static inline __s32 i2c_smbus_read_byte(int file) +{ + union i2c_smbus_data data; + if (i2c_smbus_access(file,I2C_SMBUS_READ,0,I2C_SMBUS_BYTE,&data)) + return -1; + else + return 0x0FF & data.byte; +} + +static inline __s32 i2c_smbus_write_byte(int file, __u8 value) +{ + return i2c_smbus_access(file,I2C_SMBUS_WRITE,value, + I2C_SMBUS_BYTE,NULL); +} + +static inline __s32 i2c_smbus_read_byte_data(int file, __u8 command) +{ + union i2c_smbus_data data; + if (i2c_smbus_access(file,I2C_SMBUS_READ,command, + I2C_SMBUS_BYTE_DATA,&data)) + return -1; + else + return 0x0FF & data.byte; +} + +static inline __s32 i2c_smbus_write_byte_data(int file, __u8 command, + __u8 value) +{ + union i2c_smbus_data data; + data.byte = value; + return i2c_smbus_access(file,I2C_SMBUS_WRITE,command, + I2C_SMBUS_BYTE_DATA, &data); +} + +static inline __s32 i2c_smbus_read_word_data(int file, __u8 command) +{ + union i2c_smbus_data data; + if (i2c_smbus_access(file,I2C_SMBUS_READ,command, + I2C_SMBUS_WORD_DATA,&data)) + return -1; + else + return 0x0FFFF & data.word; +} + +static inline __s32 i2c_smbus_write_word_data(int file, __u8 command, + __u16 value) +{ + union i2c_smbus_data data; + data.word = value; + return i2c_smbus_access(file,I2C_SMBUS_WRITE,command, + I2C_SMBUS_WORD_DATA, &data); +} + +static inline __s32 i2c_smbus_process_call(int file, __u8 command, __u16 value) +{ + union i2c_smbus_data data; + data.word = value; + if (i2c_smbus_access(file,I2C_SMBUS_WRITE,command, + I2C_SMBUS_PROC_CALL,&data)) + return -1; + else + return 0x0FFFF & data.word; +} + + +/* Returns the number of read bytes */ +static inline __s32 i2c_smbus_read_block_data(int file, __u8 command, + __u8 *values) +{ + union i2c_smbus_data data; + int i; + if (i2c_smbus_access(file,I2C_SMBUS_READ,command, + I2C_SMBUS_BLOCK_DATA,&data)) + return -1; + else { + for (i = 1; i <= data.block[0]; i++) + values[i-1] = data.block[i]; + return data.block[0]; + } +} + +static inline __s32 i2c_smbus_write_block_data(int file, __u8 command, + __u8 length, const __u8 *values) +{ + union i2c_smbus_data data; + int i; + if (length > 32) + length = 32; + for (i = 1; i <= length; i++) + data.block[i] = values[i-1]; + data.block[0] = length; + return i2c_smbus_access(file,I2C_SMBUS_WRITE,command, + I2C_SMBUS_BLOCK_DATA, &data); +} + +/* Returns the number of read bytes */ +/* Until kernel 2.6.22, the length is hardcoded to 32 bytes. If you + ask for less than 32 bytes, your code will only work with kernels + 2.6.23 and later. */ +static inline __s32 i2c_smbus_read_i2c_block_data(int file, __u8 command, + __u8 length, __u8 *values) +{ + union i2c_smbus_data data; + int i; + + if (length > 32) + length = 32; + data.block[0] = length; + if (i2c_smbus_access(file,I2C_SMBUS_READ,command, + length == 32 ? I2C_SMBUS_I2C_BLOCK_BROKEN : + I2C_SMBUS_I2C_BLOCK_DATA,&data)) + return -1; + else { + for (i = 1; i <= data.block[0]; i++) + values[i-1] = data.block[i]; + return data.block[0]; + } +} + +static inline __s32 i2c_smbus_write_i2c_block_data(int file, __u8 command, + __u8 length, + const __u8 *values) +{ + union i2c_smbus_data data; + int i; + if (length > 32) + length = 32; + for (i = 1; i <= length; i++) + data.block[i] = values[i-1]; + data.block[0] = length; + return i2c_smbus_access(file,I2C_SMBUS_WRITE,command, + I2C_SMBUS_I2C_BLOCK_BROKEN, &data); +} + +/* Returns the number of read bytes */ +static inline __s32 i2c_smbus_block_process_call(int file, __u8 command, + __u8 length, __u8 *values) +{ + union i2c_smbus_data data; + int i; + if (length > 32) + length = 32; + for (i = 1; i <= length; i++) + data.block[i] = values[i-1]; + data.block[0] = length; + if (i2c_smbus_access(file,I2C_SMBUS_WRITE,command, + I2C_SMBUS_BLOCK_PROC_CALL,&data)) + return -1; + else { + for (i = 1; i <= data.block[0]; i++) + values[i-1] = data.block[i]; + return data.block[0]; + } +} + + +#endif /* _LINUX_I2C_DEV_H */ diff --git a/RTC-Hat-Examples/py-smbusf/setup.py b/RTC-Hat-Examples/py-smbusf/setup.py new file mode 100644 index 0000000..f26deeb --- /dev/null +++ b/RTC-Hat-Examples/py-smbusf/setup.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +from distutils.core import setup, Extension + +setup( name="smbusf", + version="1.1", + description="Python bindings for Linux SMBus access through i2c-dev, forced access version", + author="Mark M. Hoffman", + author_email="mhoffman@lightlink.com", + maintainer="Mark M. Hoffman", + maintainer_email="linux-i2c@vger.kernel.org", + license="GPLv2", + url="http://lm-sensors.org/", + ext_modules=[Extension("smbusf", ["smbusmodule.c"])]) diff --git a/RTC-Hat-Examples/py-smbusf/smbusmodule.c b/RTC-Hat-Examples/py-smbusf/smbusmodule.c new file mode 100644 index 0000000..039eb6d --- /dev/null +++ b/RTC-Hat-Examples/py-smbusf/smbusmodule.c @@ -0,0 +1,699 @@ +/* + * smbusmodule.c - Python bindings for Linux SMBus access through i2c-dev + * Copyright (C) 2005-2007 Mark M. Hoffman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include "structmember.h" +#include +#include +#include +#include +#include + +/* +** These are required to build this module against Linux older than 2.6.23. +*/ +#ifndef I2C_SMBUS_I2C_BLOCK_BROKEN +#undef I2C_SMBUS_I2C_BLOCK_DATA +#define I2C_SMBUS_I2C_BLOCK_BROKEN 6 +#define I2C_SMBUS_I2C_BLOCK_DATA 8 +#endif + +PyDoc_STRVAR(SMBus_module_doc, + "This module defines an object type that allows SMBus transactions\n" + "on hosts running the Linux kernel. The host kernel must have I2C\n" + "support, I2C device interface support, and a bus adapter driver.\n" + "All of these can be either built-in to the kernel, or loaded from\n" + "modules.\n" + "\n" + "Because the I2C device interface is opened R/W, users of this\n" + "module usually must have root permissions.\n"); + +typedef struct { + PyObject_HEAD + + int fd; /* open file descriptor: /dev/i2c-?, or -1 */ + int addr; /* current client SMBus address */ + int pec; /* !0 => Packet Error Codes enabled */ +} SMBus; + +static PyObject * +SMBus_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + SMBus *self; + + if ((self = (SMBus *)type->tp_alloc(type, 0)) == NULL) + return NULL; + + self->fd = -1; + self->addr = -1; + self->pec = 0; + + return (PyObject *)self; +} + +PyDoc_STRVAR(SMBus_close_doc, + "close()\n\n" + "Disconnects the object from the bus.\n"); + +static PyObject * +SMBus_close(SMBus *self) +{ + if ((self->fd != -1) && (close(self->fd) == -1)) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + self->fd = -1; + self->addr = -1; + self->pec = 0; + + Py_INCREF(Py_None); + return Py_None; +} + +static void +SMBus_dealloc(SMBus *self) +{ + PyObject *ref = SMBus_close(self); + Py_XDECREF(ref); + + self->ob_type->tp_free((PyObject *)self); +} + +#define MAXPATH 16 + +PyDoc_STRVAR(SMBus_open_doc, + "open(bus)\n\n" + "Connects the object to the specified SMBus.\n"); + +static PyObject * +SMBus_open(SMBus *self, PyObject *args, PyObject *kwds) +{ + int bus; + char path[MAXPATH]; + + static char *kwlist[] = {"bus", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i:open", kwlist, &bus)) + return NULL; + + if (snprintf(path, MAXPATH, "/dev/i2c-%d", bus) >= MAXPATH) { + PyErr_SetString(PyExc_OverflowError, + "Bus number is invalid."); + return NULL; + } + + if ((self->fd = open(path, O_RDWR, 0)) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static int +SMBus_init(SMBus *self, PyObject *args, PyObject *kwds) +{ + int bus = -1; + + static char *kwlist[] = {"bus", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i:__init__", + kwlist, &bus)) + return -1; + + if (bus >= 0) { + SMBus_open(self, args, kwds); + if (PyErr_Occurred()) + return -1; + } + + return 0; +} + +/* + * private helper function, 0 => success, !0 => error + */ +static int +SMBus_set_addr(SMBus *self, int addr) +{ + int ret = 0; + + if (self->addr != addr) { + ret = ioctl(self->fd, I2C_SLAVE_FORCE, addr); + self->addr = addr; + } + + return ret; +} + +#define SMBus_SET_ADDR(self, addr) do { \ + if (SMBus_set_addr(self, addr)) { \ + PyErr_SetFromErrno(PyExc_IOError); \ + return NULL; \ + } \ +} while(0) + +PyDoc_STRVAR(SMBus_write_quick_doc, + "write_quick(addr)\n\n" + "Perform SMBus Quick transaction.\n"); + +static PyObject * +SMBus_write_quick(SMBus *self, PyObject *args) +{ + int addr; + __s32 result; + + if (!PyArg_ParseTuple(args, "i:write_quick", &addr)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + if ((result = i2c_smbus_write_quick(self->fd, I2C_SMBUS_WRITE))) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(SMBus_read_byte_doc, + "read_byte(addr) -> result\n\n" + "Perform SMBus Read Byte transaction.\n"); + +static PyObject * +SMBus_read_byte(SMBus *self, PyObject *args) +{ + int addr; + __s32 result; + + if (!PyArg_ParseTuple(args, "i:read_byte", &addr)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + if ((result = i2c_smbus_read_byte(self->fd)) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + return Py_BuildValue("l", (long)result); +} + +PyDoc_STRVAR(SMBus_write_byte_doc, + "write_byte(addr, val)\n\n" + "Perform SMBus Write Byte transaction.\n"); + +static PyObject * +SMBus_write_byte(SMBus *self, PyObject *args) +{ + int addr, val; + __s32 result; + + if (!PyArg_ParseTuple(args, "ii:write_byte", &addr, &val)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + if ((result = i2c_smbus_write_byte(self->fd, (__u8)val)) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(SMBus_read_byte_data_doc, + "read_byte_data(addr, cmd) -> result\n\n" + "Perform SMBus Read Byte Data transaction.\n"); + +static PyObject * +SMBus_read_byte_data(SMBus *self, PyObject *args) +{ + int addr, cmd; + __s32 result; + + if (!PyArg_ParseTuple(args, "ii:read_byte_data", &addr, &cmd)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + if ((result = i2c_smbus_read_byte_data(self->fd, (__u8)cmd)) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + return Py_BuildValue("l", (long)result); +} + +PyDoc_STRVAR(SMBus_write_byte_data_doc, + "write_byte_data(addr, cmd, val)\n\n" + "Perform SMBus Write Byte Data transaction.\n"); + +static PyObject * +SMBus_write_byte_data(SMBus *self, PyObject *args) +{ + int addr, cmd, val; + __s32 result; + + if (!PyArg_ParseTuple(args, "iii:write_byte_data", &addr, &cmd, &val)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + if ((result = i2c_smbus_write_byte_data(self->fd, + (__u8)cmd, (__u8)val)) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(SMBus_read_word_data_doc, + "read_word_data(addr, cmd) -> result\n\n" + "Perform SMBus Read Word Data transaction.\n"); + +static PyObject * +SMBus_read_word_data(SMBus *self, PyObject *args) +{ + int addr, cmd; + __s32 result; + + if (!PyArg_ParseTuple(args, "ii:read_word_data", &addr, &cmd)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + if ((result = i2c_smbus_read_word_data(self->fd, (__u8)cmd)) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + return Py_BuildValue("l", (long)result); +} + +PyDoc_STRVAR(SMBus_write_word_data_doc, + "write_word_data(addr, cmd, val)\n\n" + "Perform SMBus Write Word Data transaction.\n"); + +static PyObject * +SMBus_write_word_data(SMBus *self, PyObject *args) +{ + int addr, cmd, val; + __s32 result; + + if (!PyArg_ParseTuple(args, "iii:write_word_data", &addr, &cmd, &val)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + if ((result = i2c_smbus_write_word_data(self->fd, + (__u8)cmd, (__u16)val)) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(SMBus_process_call_doc, + "process_call(addr, cmd, val)\n\n" + "Perform SMBus Process Call transaction.\n"); + +static PyObject * +SMBus_process_call(SMBus *self, PyObject *args) +{ + int addr, cmd, val; + __s32 result; + + if (!PyArg_ParseTuple(args, "iii:process_call", &addr, &cmd, &val)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + if ((result = i2c_smbus_process_call(self->fd, + (__u8)cmd, (__u16)val)) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +/* + * private helper function; returns a new list of integers + */ +static PyObject * +SMBus_buf_to_list(__u8 const *buf, int len) +{ + PyObject *list = PyList_New(len); + int ii; + + if (list == NULL) + return NULL; + + for (ii = 0; ii < len; ii++) { + PyObject *val = Py_BuildValue("l", (long)buf[ii]); + PyList_SET_ITEM(list, ii, val); + } + return list; +} + +PyDoc_STRVAR(SMBus_read_block_data_doc, + "read_block_data(addr, cmd) -> results\n\n" + "Perform SMBus Read Block Data transaction.\n"); + +static PyObject * +SMBus_read_block_data(SMBus *self, PyObject *args) +{ + int addr, cmd; + union i2c_smbus_data data; + + if (!PyArg_ParseTuple(args, "ii:read_block_data", &addr, &cmd)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + /* save a bit of code by calling the access function directly */ + if (i2c_smbus_access(self->fd, I2C_SMBUS_READ, (__u8)cmd, + I2C_SMBUS_BLOCK_DATA, &data)) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + /* first byte of the block contains (remaining) data length */ + return SMBus_buf_to_list(&data.block[1], data.block[0]); +} + +/* + * private helper function: convert an integer list to union i2c_smbus_data + */ +static int +SMBus_list_to_data(PyObject *list, union i2c_smbus_data *data) +{ + static char *msg = "Third argument must be a list of at least one, " + "but not more than 32 integers"; + int ii, len; + + if (!PyList_Check(list)) { + PyErr_SetString(PyExc_TypeError, msg); + return 0; /* fail */ + } + + if ((len = PyList_GET_SIZE(list)) > 32) { + PyErr_SetString(PyExc_OverflowError, msg); + return 0; /* fail */ + } + + /* first byte is the length */ + data->block[0] = (__u8)len; + + for (ii = 0; ii < len; ii++) { + PyObject *val = PyList_GET_ITEM(list, ii); + if (!PyInt_Check(val)) { + PyErr_SetString(PyExc_TypeError, msg); + return 0; /* fail */ + } + data->block[ii+1] = (__u8)PyInt_AS_LONG(val); + } + + return 1; /* success */ +} + +PyDoc_STRVAR(SMBus_write_block_data_doc, + "write_block_data(addr, cmd, [vals])\n\n" + "Perform SMBus Write Block Data transaction.\n"); + +static PyObject * +SMBus_write_block_data(SMBus *self, PyObject *args) +{ + int addr, cmd; + union i2c_smbus_data data; + + if (!PyArg_ParseTuple(args, "iiO&:write_block_data", &addr, &cmd, + SMBus_list_to_data, &data)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + /* save a bit of code by calling the access function directly */ + if (i2c_smbus_access(self->fd, I2C_SMBUS_WRITE, (__u8)cmd, + I2C_SMBUS_BLOCK_DATA, &data)) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(SMBus_block_process_call_doc, + "block_process_call(addr, cmd, [vals]) -> results\n\n" + "Perform SMBus Block Process Call transaction.\n"); + +static PyObject * +SMBus_block_process_call(SMBus *self, PyObject *args) +{ + int addr, cmd; + union i2c_smbus_data data; + + if (!PyArg_ParseTuple(args, "iiO&:block_process_call", &addr, &cmd, + SMBus_list_to_data, &data)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + /* save a bit of code by calling the access function directly */ + if (i2c_smbus_access(self->fd, I2C_SMBUS_WRITE, (__u8)cmd, + I2C_SMBUS_BLOCK_PROC_CALL, &data)) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + /* first byte of the block contains (remaining) data length */ + return SMBus_buf_to_list(&data.block[1], data.block[0]); +} + +PyDoc_STRVAR(SMBus_read_i2c_block_data_doc, + "read_i2c_block_data(addr, cmd, len=32) -> results\n\n" + "Perform I2C Block Read transaction.\n"); + +static PyObject * +SMBus_read_i2c_block_data(SMBus *self, PyObject *args) +{ + int addr, cmd, len=32; + union i2c_smbus_data data; + + if (!PyArg_ParseTuple(args, "ii|i:read_i2c_block_data", &addr, &cmd, + &len)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + data.block[0] = len; + /* save a bit of code by calling the access function directly */ + if (i2c_smbus_access(self->fd, I2C_SMBUS_READ, (__u8)cmd, + len == 32 ? I2C_SMBUS_I2C_BLOCK_BROKEN: + I2C_SMBUS_I2C_BLOCK_DATA, &data)) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + /* first byte of the block contains (remaining) data length */ + return SMBus_buf_to_list(&data.block[1], data.block[0]); +} + +PyDoc_STRVAR(SMBus_write_i2c_block_data_doc, + "write_i2c_block_data(addr, cmd, [vals])\n\n" + "Perform I2C Block Write transaction.\n"); + +static PyObject * +SMBus_write_i2c_block_data(SMBus *self, PyObject *args) +{ + int addr, cmd; + union i2c_smbus_data data; + + if (!PyArg_ParseTuple(args, "iiO&:write_i2c_block_data", &addr, &cmd, + SMBus_list_to_data, &data)) + return NULL; + + SMBus_SET_ADDR(self, addr); + + /* save a bit of code by calling the access function directly */ + if (i2c_smbus_access(self->fd, I2C_SMBUS_WRITE, (__u8)cmd, + I2C_SMBUS_I2C_BLOCK_BROKEN, &data)) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(SMBus_type_doc, + "SMBus([bus]) -> SMBus\n\n" + "Return a new SMBus object that is (optionally) connected to the\n" + "specified I2C device interface.\n"); + +static PyMethodDef SMBus_methods[] = { + {"open", (PyCFunction)SMBus_open, METH_VARARGS | METH_KEYWORDS, + SMBus_open_doc}, + {"close", (PyCFunction)SMBus_close, METH_NOARGS, + SMBus_close_doc}, + {"write_quick", (PyCFunction)SMBus_write_quick, METH_VARARGS, + SMBus_write_quick_doc}, + {"read_byte", (PyCFunction)SMBus_read_byte, METH_VARARGS, + SMBus_read_byte_doc}, + {"write_byte", (PyCFunction)SMBus_write_byte, METH_VARARGS, + SMBus_write_byte_doc}, + {"read_byte_data", (PyCFunction)SMBus_read_byte_data, METH_VARARGS, + SMBus_read_byte_data_doc}, + {"write_byte_data", (PyCFunction)SMBus_write_byte_data, METH_VARARGS, + SMBus_write_byte_data_doc}, + {"read_word_data", (PyCFunction)SMBus_read_word_data, METH_VARARGS, + SMBus_read_word_data_doc}, + {"write_word_data", (PyCFunction)SMBus_write_word_data, METH_VARARGS, + SMBus_write_word_data_doc}, + {"process_call", (PyCFunction)SMBus_process_call, METH_VARARGS, + SMBus_process_call_doc}, + {"read_block_data", (PyCFunction)SMBus_read_block_data, METH_VARARGS, + SMBus_read_block_data_doc}, + {"write_block_data", (PyCFunction)SMBus_write_block_data, METH_VARARGS, + SMBus_write_block_data_doc}, + {"block_process_call", (PyCFunction)SMBus_block_process_call, + METH_VARARGS, SMBus_block_process_call_doc}, + {"read_i2c_block_data", (PyCFunction)SMBus_read_i2c_block_data, + METH_VARARGS, SMBus_read_i2c_block_data_doc}, + {"write_i2c_block_data", (PyCFunction)SMBus_write_i2c_block_data, + METH_VARARGS, SMBus_write_i2c_block_data_doc}, + {NULL}, +}; + +static PyObject * +SMBus_get_pec(SMBus *self, void *closure) +{ + PyObject *result = self->pec ? Py_True : Py_False; + Py_INCREF(result); + return result; +} + +static int +SMBus_set_pec(SMBus *self, PyObject *val, void *closure) +{ + int pec; + + pec = PyObject_IsTrue(val); + + if (val == NULL) { + PyErr_SetString(PyExc_TypeError, + "Cannot delete attribute"); + return -1; + } + else if (pec == -1) { + PyErr_SetString(PyExc_TypeError, + "The pec attribute must be a boolean."); + return -1; + } + + if (self->pec != pec) { + if (ioctl(self->fd, I2C_PEC, pec)) { + PyErr_SetFromErrno(PyExc_IOError); + return -1; + } + self->pec = pec; + } + + return 0; +} + +static PyGetSetDef SMBus_getset[] = { + {"pec", (getter)SMBus_get_pec, (setter)SMBus_set_pec, + "True if Packet Error Codes (PEC) are enabled"}, + {NULL}, +}; + +static PyTypeObject SMBus_type = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "smbus.SMBus", /* tp_name */ + sizeof(SMBus), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)SMBus_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + SMBus_type_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + SMBus_methods, /* tp_methods */ + 0, /* tp_members */ + SMBus_getset, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)SMBus_init, /* tp_init */ + 0, /* tp_alloc */ + SMBus_new, /* tp_new */ +}; + +static PyMethodDef SMBus_module_methods[] = { + {NULL} +}; + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif +PyMODINIT_FUNC +initsmbusf(void) +{ + PyObject* m; + + if (PyType_Ready(&SMBus_type) < 0) + return; + + m = Py_InitModule3("smbusf", SMBus_module_methods, SMBus_module_doc); + + Py_INCREF(&SMBus_type); + PyModule_AddObject(m, "SMBus", (PyObject *)&SMBus_type); +} +