def __init__(self): self.motor_left = Motor(settings.PINS['motor']['left']) self.motor_right = Motor(settings.PINS['motor']['right']) self.encoder_left = Encoder.Worker(settings.PINS['encoder']['left']) self.encoder_right = Encoder.Worker(settings.PINS['encoder']['right']) self.mpu = MPU6050(0x6b) self.pid = PID() self.encoder_left.start() self.encoder_right.start()
def __init__(self, volume, playing, channel_a, channel_b, channel_sw): self.volume = volume self.playing = playing self.encoder = RotaryEncoder(channel_a=channel_a, channel_b=channel_b, channel_sw=channel_sw) self.encoder.add_step_callback(self.onStep) self.encoder.add_click_callback(self.onClick) self.encoder.run()
def __init__(self): self.motor_left = Motor(settings.PINS['motor']['left']) self.motor_right = Motor(settings.PINS['motor']['right']) self.encoder_left = Encoder.Worker(settings.PINS['encoder']['left']) self.encoder_right = Encoder.Worker(settings.PINS['encoder']['right']) self.mpu = MPU6050(0x68) self.remote = RemoteControlServer(settings.pid_params) self.pid = PID() self.encoder_left.start() self.encoder_right.start()
def __init__(self): self.motor_left = Motor(settings.PINS['motor']['left']) self.motor_right = Motor(settings.PINS['motor']['right']) self.encoder_left = Encoder.Worker(settings.PINS['encoder']['left']) self.encoder_right = Encoder.Worker(settings.PINS['encoder']['right']) self.pid = PID() self.mpu = AltIMU() self.mpu.enable() self.mpu.calibrateGyroAngles() self.mpu.initComplementaryFromAccel = True self.encoder_left.start() self.encoder_right.start() self.cstate = {} self.cstate['theta'] = 0 self.cstate['gamma'] = 0 self.cstate['phi'] = 0 self.cstate['wheelAngleR'] = 0 self.cstate['wheelAngleL'] = 0 self.setpoint = {} self.setpoint['phi'] = 0 self.setpoint['theta'] = 0 self.setpoint['phidot'] = settings.DRIVE_RATE_ADVANCED self.setpoint['gammadot'] = settings.TURN_RATE_ADVANCED self.setpoint['gamma'] = 0 self.D1 = Filter(DT, settings.edumip_params['D1']['num'], settings.edumip_params['D1']['den']) self.D1.gain = settings.edumip_params['D1']['gain'] self.D1.filter_enable_saturation(-1.0, 1.0) self.D1.filter_enable_soft_start(0.7) self.D1.name = 'D1' self.D2 = Filter(DT, settings.edumip_params['D2']['num'], settings.edumip_params['D2']['den']) self.D2.gain = settings.edumip_params['D2']['gain'] self.D2.filter_enable_saturation( -settings.edumip_params['D2']['theta_ref_max'], settings.edumip_params['D2']['theta_ref_max']) self.D2.filter_enable_soft_start(0.7) self.D2.name = 'D2' self.D3 = Filter() self.D3.filter_pid(settings.edumip_params['D3']['kp'], settings.edumip_params['D3']['ki'], settings.edumip_params['D3']['kd'], 4 * DT, DT) self.D3.filter_enable_saturation(-settings.STEERING_INPUT_MAX, settings.STEERING_INPUT_MAX) self.D3.name = 'D3'
def __init__(self, volume, playing, channel_a, channel_b, channel_sw): self.volume = volume self.playing = playing self.encoder = RotaryEncoder(channel_a = channel_a, channel_b = channel_b, channel_sw = channel_sw) self.encoder.add_step_callback(self.onStep) self.encoder.add_click_callback(self.onClick) self.encoder.run()
class VolumeIOManager(object): def __init__(self, volume, playing, channel_a, channel_b, channel_sw): self.volume = volume self.playing = playing self.encoder = RotaryEncoder(channel_a=channel_a, channel_b=channel_b, channel_sw=channel_sw) self.encoder.add_step_callback(self.onStep) self.encoder.add_click_callback(self.onClick) self.encoder.run() def onStep(self, delta): vol = self.volume.value vol = vol + delta if vol > 100: vol = 100 if vol < 0: vol = 0 self.volume.value = vol def onClick(self): if self.playing.value == 1: self.playing.value = 0 else: self.playing.value = 1
def add_rotary_encoder(self, name, callback, pinA, pinB, io_gnd=True): if pinA == pinB: raise ValueError("pinA and pinB can't be the same...") id = tuple(sorted((pinA, pinB))) for pair in self.rot_enc.keys(): if pair == id: self.rot_enc[pair].rot_enc.close() self.rot_enc.pop(pair) elif pinA in pair or pinB in pair: raise ValueError( "You can't share on io-pin between two rotary encoders") if name in [r.name for r in self.rot_enc.values()]: raise ValueError( "Specified name '{}' for rotary encoder is not unique..". format(name)) self.rot_enc[id] = self.Encoder( RotaryEncoder(self.gpio, callback, (pinA, pinB), io_gnd), name)
class VolumeIOManager(object): def __init__(self, volume, playing, channel_a, channel_b, channel_sw): self.volume = volume self.playing = playing self.encoder = RotaryEncoder(channel_a = channel_a, channel_b = channel_b, channel_sw = channel_sw) self.encoder.add_step_callback(self.onStep) self.encoder.add_click_callback(self.onClick) self.encoder.run() def onStep(self, delta): vol = self.volume.value vol = vol + delta if vol > 100: vol = 100 if vol < 0: vol = 0 self.volume.value = vol def onClick(self): if self.playing.value == 1: self.playing.value = 0 else: self.playing.value = 1
#!/usr/bin/env python """ encoder_test.py Rekha Seethamraju An example to demonstrate the use of the eQEP library for PyBBIO. This example program is in the public domain. """ from bbio import * from RotaryEncoder import RotaryEncoder encoder = RotaryEncoder(RotaryEncoder.EQEP2b) def setup(): encoder.setAbsolute() encoder.zero() def loop(): print "encoder position : " + encoder.getPosition() delay(1000) run(setup, loop)
# Create new multi turn pot handler mtpot = MultiTurnPot(1, 10) mtpot.registerCallback(MultiTurnPotHandler) def mtpotThread(): while True: mtpot.tick() sleep(.25) # Create rotary encoder handler rotenc = RotaryEncoder(16, 20, 12) rotenc.registerPressCallback(RotaryBtnHandler) rotenc.registerRotateCallback(RotaryRotateHandler) def rotencThread(): while True: rotenc.tick() sleep(.01) # Create volume knob handler volknob = VolumeKnob(0, VolumeOffHandler, True) def volknobThread():
from motor import Motor import settings import time from RotaryEncoder import RotaryEncoder as Encoder encoder_left = Encoder.Worker(settings.PINS['encoder']['left']) encoder_right = Encoder.Worker(settings.PINS['encoder']['right']) encoder_left.start() encoder_right.start() while True: print(encoder_left.speed, encoder_right.speed) time.sleep(1)
def __init__(self, epd): self.epd = epd self.menu_font = '/usr/share/fonts/truetype/freefont/FreeSans.ttf' # TODO: Move these out into a separate file self.possible_fonts = [{ 'filename': '/usr/share/fonts/truetype/fonts-georgewilliams/CaslonBold.ttf', 'title': 'Caslon Bold' }, { 'filename': '/usr/share/fonts/truetype/fonts-georgewilliams/Caslon-Black.ttf', 'title': 'Caslon Black' }, { 'filename': '/usr/share/fonts/truetype/fonts-georgewilliams/Caliban.ttf', 'title': 'Caliban' }, { 'filename': '/usr/share/fonts/truetype/fonts-georgewilliams/Cupola.ttf', 'title': 'Cupola' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSerif.ttf', 'title': 'Serif' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf', 'title': 'Serif Bold' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeMono.ttf', 'title': 'Monospace' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf', 'title': 'Monospace Bold' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeMonoOblique.ttf', 'title': 'Monospace Oblique' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeMonoBoldOblique.ttf', 'title': 'Monospace Bold Oblique' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSans.ttf', 'title': 'Sans-Serif' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSansBold.ttf', 'title': 'Sans-Serif Bold' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSansOblique.ttf', 'title': 'Sans-Serif Oblique' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSansBoldOblique.ttf', 'title': 'Sans-Serif Bold Oblique' }, { 'filename': '/usr/share/fonts/truetype/humor-sans/Humor-Sans.ttf', 'title': 'Humor' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf', 'title': 'DejaVu' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf', 'title': 'DejaVu Bold' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf', 'title': 'DejaVu Monospace' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf', 'title': 'DejaVu Monospace Bold' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 'title': 'DejaVu Sans' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 'title': 'DejaVu Sans Bold' }, { 'filename': '/usr/share/fonts/truetype/dustin/Dustismo.ttf', 'title': 'Dustismo' }, { 'filename': '/usr/share/fonts/truetype/dustin/dustismo_bold.ttf', 'title': 'Dustismo Bold' }, { 'filename': '/usr/share/fonts/truetype/dustin/dustismo_italic.ttf', 'title': 'Dustismo Italic' }, { 'filename': '/usr/share/fonts/truetype/dustin/Dustismo_Roman.ttf', 'title': 'Dustismo Roman' }, { 'filename': '/usr/share/fonts/truetype/dustin/Dustismo_Roman_Bold.ttf', 'title': 'Dustismo Roman Bold' }, { 'filename': '/usr/share/fonts/truetype/dustin/Dustismo_Roman_Italic.ttf', 'title': 'Dustismo Roman Italic' }, { 'filename': '/usr/share/fonts/truetype/dustin/Domestic_Manners.ttf', 'title': 'Domestic Manners' }, { 'filename': '/usr/share/fonts/truetype/dustin/Junkyard.ttf', 'title': 'Junkyard' }, { 'filename': '/usr/share/fonts/truetype/dustin/Wargames.ttf', 'title': 'Wargames' }, { 'filename': '/usr/share/fonts/truetype/dustin/PenguinAttack.ttf', 'title': 'Penguin Attack' }, { 'filename': '/usr/share/fonts/truetype/dustin/It_wasn_t_me.ttf', 'title': 'It wasn\'t me!' }] self.possible_tones = [ { 'filename': '/root/tones/Blues.mp3', 'title': 'Blues' }, { 'filename': '/root/tones/Piano Riff.mp3', 'title': 'Piano Riff' }, { 'filename': '/root/tones/Sci-Fi.mp3', 'title': 'Sci-Fi' }, { 'filename': '/root/tones/Pinball.mp3', 'title': 'Pinball' }, { 'filename': '/root/tones/Crickets.mp3', 'title': 'Crickets' }, { 'filename': '/root/tones/Motorcycle.mp3', 'title': 'Motorcycle' }, { 'filename': '/root/tones/Timba.mp3', 'title': 'Timba' }, { 'filename': '/root/tones/Bark.mp3', 'title': 'Bark' }, { 'filename': '/root/tones/Trill.mp3', 'title': 'Trill' }, { 'filename': '/root/tones/Robot.mp3', 'title': 'Robot' }, { 'filename': '/root/tones/Old Phone.mp3', 'title': 'Old Phone' }, { 'filename': '/root/tones/Marimba.mp3', 'title': 'Marimba' }, { 'filename': '/root/tones/Boing.mp3', 'title': 'Boing' }, { 'filename': '/root/tones/Strum.mp3', 'title': 'Strum' }, { 'filename': '/root/tones/Xylophone.mp3', 'title': 'Xylophone' }, { 'filename': '/root/tones/Digital.mp3', 'title': 'Digital' }, { 'filename': '/root/tones/Time Passing.mp3', 'title': 'Time Passing' }, { 'filename': '/root/tones/Harp.mp3', 'title': 'Harp' }, { 'filename': '/root/tones/Bell Tower.mp3', 'title': 'Bell Tower' }, { 'filename': '/root/tones/Alarm.mp3', 'title': 'Alarm' }, { 'filename': '/root/tones/Old Car Horn.mp3', 'title': 'Old Car Horn' }, { 'filename': '/root/tones/Doorbell.mp3', 'title': 'Doorbell' }, { 'filename': '/root/tones/Sonar.mp3', 'title': 'Sonar' }, { 'filename': '/root/tones/Ascending.mp3', 'title': 'Ascending' }, { 'filename': '/root/tones/Duck.mp3', 'title': 'Duck' }, ] try: with open("/sys/class/gpio/unexport", "w") as unexport: unexport.write("133\n") except IOError: pass GPIO.setup("CSID1", GPIO.OUT) GPIO.output("CSID1", GPIO.LOW) self.mode = "weather" self.timezones = country_timezones('US') self.timezone_index = 0 self.stop_alarming() atexit.register(self.stop_alarming) self.snooze = 0 self.lock = threading.Lock() # create lock for rotary switch self.pips = 0 self.presses = 0 self.re = RotaryEncoder([0x11], invert=False, factor=0.5) self.re.register_callbacks(turn=self.turn_cb, press=self.press_cb) try: ip = json.load(urlopen('http://jsonip.com'))['ip'] except Exception: try: ip = json.load( urlopen('https://api.ipify.org/?format=json'))['ip'] except Exception: raise g = GeoIP.open("/usr/share/GeoIP/GeoIPCity.dat", GeoIP.GEOIP_STANDARD) try: gr = g.record_by_addr(ip) print gr self.latitude = gr['latitude'] self.longitude = gr['longitude'] while self.timezones[self.timezone_index] != gr['time_zone']: self.timezone_index += 1 except Exception: raise try: with open('/root/alarmclock-settings.pickle', 'rb') as settings: self.settings = pickle.load(settings) except Exception as e: print "Failed to load settings; using defaults" print e self.settings = {} self.settings['twentyfour'] = False self.settings['alarm'] = False self.settings['font_index'] = 21 self.settings['tone_index'] = 0 self.settings['alarm_time'] = 360 # Minutes from midnight try: with open('/root/darksky.key', 'r') as f: self.darksky_key = f.readline().strip() self.weather = True except Exception as e: print "Couldn't get key from /root/darksky.key: " + str(e) self.weather = False if self.weather: self._update_weather()
import sys sys.path.append("../hardwareDevices") from RotaryEncoder import RotaryEncoder from time import sleep def PushNotifier(self): print("Button was pressed!") def RotateNotifier(num): if num > 1 or num < -1: print("Bogus number: " + str(num)) else: print("Encoder was rotated " + str(num) + " times") rotenc = RotaryEncoder(16, 20, 12) rotenc.registerPressCallback(PushNotifier) rotenc.registerRotateCallback(RotateNotifier) while True: rotenc.tick() sleep(.01)
import sys sys.path.append("../hardwareDevices") from RotaryEncoder import RotaryEncoder from time import sleep def PushNotifier(self): print ("Button was pressed!") def RotateNotifier(num): if num > 1 or num < -1: print ("Bogus number: " + str(num)) else: print ("Encoder was rotated " + str(num) + " times") rotenc = RotaryEncoder(16, 20, 12) rotenc.registerPressCallback(PushNotifier) rotenc.registerRotateCallback(RotateNotifier) while True: rotenc.tick() sleep(.01)
print("Volume is at 0!") # Create new multi turn pot handler mtpot = MultiTurnPot(1, 10) mtpot.registerCallback(MultiTurnPotHandler) def mtpotThread(): while True: mtpot.tick() sleep(.25) # Create rotary encoder handler rotenc = RotaryEncoder(ENCODER_PIN_A, ENCODER_PIN_B, ENCODER_GPIO_PRESS_BTN) rotenc.registerPressCallback(RotaryBtnHandler) rotenc.registerRotateCallback(RotaryRotateHandler) def rotencThread(): while True: rotenc.tick() sleep(.05) # Create volume knob handler volknob = VolumeKnob(0, VolumeOffHandler, True) def volknobThread():
import RPi.GPIO as GPIO import time from RotaryEncoder import RotaryEncoder #class RotaryEncoder: # # def __init__(self): # self._a = 4 # self._b = 3 # # def debounce(self): encoder = RotaryEncoder(4, 3, 2) encoder.setup() last_step = 0 while 1: rotary_step = encoder.get_steps() if last_step > rotary_step: print(">") last_step = rotary_step elif last_step < rotary_step: print("<") last_step = rotary_step # time.sleep(0.01)
class AlarmClock(): weather_icons = { 'clear-day': unichr(0xf00d), 'clear-night': unichr(0xf02e), 'rain': unichr(0xf019), 'snow': unichr(0xf076), 'sleet': unichr(0xf0b5), 'wind': unichr(0xf050), 'fog': unichr(0xf014), 'cloudy': unichr(0xf013), 'partly-cloudy-day': unichr(0xf002), 'partly-cloudy-night': unichr(0xf086), 'hail': unichr(0xf015), 'thunderstorm': unichr(0xf01d), 'tornado': unichr(0xf056) } def __init__(self, epd): self.epd = epd self.menu_font = '/usr/share/fonts/truetype/freefont/FreeSans.ttf' # TODO: Move these out into a separate file self.possible_fonts = [{ 'filename': '/usr/share/fonts/truetype/fonts-georgewilliams/CaslonBold.ttf', 'title': 'Caslon Bold' }, { 'filename': '/usr/share/fonts/truetype/fonts-georgewilliams/Caslon-Black.ttf', 'title': 'Caslon Black' }, { 'filename': '/usr/share/fonts/truetype/fonts-georgewilliams/Caliban.ttf', 'title': 'Caliban' }, { 'filename': '/usr/share/fonts/truetype/fonts-georgewilliams/Cupola.ttf', 'title': 'Cupola' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSerif.ttf', 'title': 'Serif' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf', 'title': 'Serif Bold' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeMono.ttf', 'title': 'Monospace' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf', 'title': 'Monospace Bold' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeMonoOblique.ttf', 'title': 'Monospace Oblique' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeMonoBoldOblique.ttf', 'title': 'Monospace Bold Oblique' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSans.ttf', 'title': 'Sans-Serif' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSansBold.ttf', 'title': 'Sans-Serif Bold' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSansOblique.ttf', 'title': 'Sans-Serif Oblique' }, { 'filename': '/usr/share/fonts/truetype/freefont/FreeSansBoldOblique.ttf', 'title': 'Sans-Serif Bold Oblique' }, { 'filename': '/usr/share/fonts/truetype/humor-sans/Humor-Sans.ttf', 'title': 'Humor' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf', 'title': 'DejaVu' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf', 'title': 'DejaVu Bold' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf', 'title': 'DejaVu Monospace' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf', 'title': 'DejaVu Monospace Bold' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 'title': 'DejaVu Sans' }, { 'filename': '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 'title': 'DejaVu Sans Bold' }, { 'filename': '/usr/share/fonts/truetype/dustin/Dustismo.ttf', 'title': 'Dustismo' }, { 'filename': '/usr/share/fonts/truetype/dustin/dustismo_bold.ttf', 'title': 'Dustismo Bold' }, { 'filename': '/usr/share/fonts/truetype/dustin/dustismo_italic.ttf', 'title': 'Dustismo Italic' }, { 'filename': '/usr/share/fonts/truetype/dustin/Dustismo_Roman.ttf', 'title': 'Dustismo Roman' }, { 'filename': '/usr/share/fonts/truetype/dustin/Dustismo_Roman_Bold.ttf', 'title': 'Dustismo Roman Bold' }, { 'filename': '/usr/share/fonts/truetype/dustin/Dustismo_Roman_Italic.ttf', 'title': 'Dustismo Roman Italic' }, { 'filename': '/usr/share/fonts/truetype/dustin/Domestic_Manners.ttf', 'title': 'Domestic Manners' }, { 'filename': '/usr/share/fonts/truetype/dustin/Junkyard.ttf', 'title': 'Junkyard' }, { 'filename': '/usr/share/fonts/truetype/dustin/Wargames.ttf', 'title': 'Wargames' }, { 'filename': '/usr/share/fonts/truetype/dustin/PenguinAttack.ttf', 'title': 'Penguin Attack' }, { 'filename': '/usr/share/fonts/truetype/dustin/It_wasn_t_me.ttf', 'title': 'It wasn\'t me!' }] self.possible_tones = [ { 'filename': '/root/tones/Blues.mp3', 'title': 'Blues' }, { 'filename': '/root/tones/Piano Riff.mp3', 'title': 'Piano Riff' }, { 'filename': '/root/tones/Sci-Fi.mp3', 'title': 'Sci-Fi' }, { 'filename': '/root/tones/Pinball.mp3', 'title': 'Pinball' }, { 'filename': '/root/tones/Crickets.mp3', 'title': 'Crickets' }, { 'filename': '/root/tones/Motorcycle.mp3', 'title': 'Motorcycle' }, { 'filename': '/root/tones/Timba.mp3', 'title': 'Timba' }, { 'filename': '/root/tones/Bark.mp3', 'title': 'Bark' }, { 'filename': '/root/tones/Trill.mp3', 'title': 'Trill' }, { 'filename': '/root/tones/Robot.mp3', 'title': 'Robot' }, { 'filename': '/root/tones/Old Phone.mp3', 'title': 'Old Phone' }, { 'filename': '/root/tones/Marimba.mp3', 'title': 'Marimba' }, { 'filename': '/root/tones/Boing.mp3', 'title': 'Boing' }, { 'filename': '/root/tones/Strum.mp3', 'title': 'Strum' }, { 'filename': '/root/tones/Xylophone.mp3', 'title': 'Xylophone' }, { 'filename': '/root/tones/Digital.mp3', 'title': 'Digital' }, { 'filename': '/root/tones/Time Passing.mp3', 'title': 'Time Passing' }, { 'filename': '/root/tones/Harp.mp3', 'title': 'Harp' }, { 'filename': '/root/tones/Bell Tower.mp3', 'title': 'Bell Tower' }, { 'filename': '/root/tones/Alarm.mp3', 'title': 'Alarm' }, { 'filename': '/root/tones/Old Car Horn.mp3', 'title': 'Old Car Horn' }, { 'filename': '/root/tones/Doorbell.mp3', 'title': 'Doorbell' }, { 'filename': '/root/tones/Sonar.mp3', 'title': 'Sonar' }, { 'filename': '/root/tones/Ascending.mp3', 'title': 'Ascending' }, { 'filename': '/root/tones/Duck.mp3', 'title': 'Duck' }, ] try: with open("/sys/class/gpio/unexport", "w") as unexport: unexport.write("133\n") except IOError: pass GPIO.setup("CSID1", GPIO.OUT) GPIO.output("CSID1", GPIO.LOW) self.mode = "weather" self.timezones = country_timezones('US') self.timezone_index = 0 self.stop_alarming() atexit.register(self.stop_alarming) self.snooze = 0 self.lock = threading.Lock() # create lock for rotary switch self.pips = 0 self.presses = 0 self.re = RotaryEncoder([0x11], invert=False, factor=0.5) self.re.register_callbacks(turn=self.turn_cb, press=self.press_cb) try: ip = json.load(urlopen('http://jsonip.com'))['ip'] except Exception: try: ip = json.load( urlopen('https://api.ipify.org/?format=json'))['ip'] except Exception: raise g = GeoIP.open("/usr/share/GeoIP/GeoIPCity.dat", GeoIP.GEOIP_STANDARD) try: gr = g.record_by_addr(ip) print gr self.latitude = gr['latitude'] self.longitude = gr['longitude'] while self.timezones[self.timezone_index] != gr['time_zone']: self.timezone_index += 1 except Exception: raise try: with open('/root/alarmclock-settings.pickle', 'rb') as settings: self.settings = pickle.load(settings) except Exception as e: print "Failed to load settings; using defaults" print e self.settings = {} self.settings['twentyfour'] = False self.settings['alarm'] = False self.settings['font_index'] = 21 self.settings['tone_index'] = 0 self.settings['alarm_time'] = 360 # Minutes from midnight try: with open('/root/darksky.key', 'r') as f: self.darksky_key = f.readline().strip() self.weather = True except Exception as e: print "Couldn't get key from /root/darksky.key: " + str(e) self.weather = False if self.weather: self._update_weather() def _set_setting(self, setting, value): self.settings[setting] = value with open('/root/alarmclock-settings.pickle', 'wb') as settings: pickle.dump(self.settings, settings) def _update_weather(self): try: with open('/root/weather-cache.pickle', 'rb') as wf: saved_weather = pickle.load(wf) self.forecast_time = saved_weather['then'] response = saved_weather['forecast'] self.forecast = Forecast(response.json(), response, response.headers) logging.debug("Loaded weather data from cache") except Exception as e: print "No cached weather data, or failed to load cache" print e self.forecast_time = datetime(1980, 1, 1) if self.weather: since = datetime.now() - self.forecast_time if since.total_seconds() >= (60 * 60): logging.debug( "Weather cache is %d seconds old; fetching new data" % since.total_seconds()) try: self.forecast = forecastio.load_forecast( self.darksky_key, self.latitude, self.longitude) except Exception as e: print "Failed to load forecast data" print e return self.forecast_time = datetime.now() with open('/root/weather-cache.pickle', 'wb') as wf: pickle.dump( { 'then': self.forecast_time, 'forecast': self.forecast.response }, wf) #else: # Weather is disabled def _calculate_fontsize(self, fontpath, str, size): image = Image.new('1', self.epd.size, WHITE) draw = ImageDraw.Draw(image) difference = size while True: font = ImageFont.truetype(fontpath, size) w, h = draw.textsize(str, font=font) difference = int(difference / 2) miss = self.epd.size[0] - w - 5 logging.debug("Width: %d, Miss: %d, Difference: %d" % (w, miss, difference)) if difference <= 0: if miss < 0: difference = 1 else: break if (miss > 0) and (miss < 10): break if miss < 10: size -= difference else: size += difference return font def _update_timezone(self): self.timezone = timezone(self.timezones[self.timezone_index]) def _update_fonts(self): self.settings['font_index'] = int(self.settings['font_index'] % len(self.possible_fonts)) font = self.possible_fonts[self.settings['font_index']]['filename'] print "Changing font to " + self.possible_fonts[ self.settings['font_index']]['title'] # if self.settings['twentyfour']: # string = "23:59" # else: string = "12:22 PM" self.clock_font = self._calculate_fontsize(font, string, 100) string = "Wednesday September 29th" self.date_font = self._calculate_fontsize(font, string, 42) self.icon_font = ImageFont.truetype( '/usr/share/fonts/truetype/WeatherIcons/weathericons-regular-webfont.ttf', 46) self.weather_font = ImageFont.truetype(font, 42) self.menu_font = ImageFont.truetype(font, 38) def _play_tone_once(self): try: self.mpg123.kill() except: pass finally: self.mpg123 = subprocess.Popen([ 'mpg123', '--quiet', '--mono', self.possible_tones[self.settings['tone_index']]['filename'] ], close_fds=True) def change_font(self, count): font_index = self.settings['font_index'] font_index += count font_index = font_index % len(self.possible_fonts) self._set_setting('font_index', font_index) self._update_fonts() return True def change_tone(self, count): self._set_setting('tone_index', self.settings['tone_index'] + count) self._play_tone_once() return True def snooze_button(self, button): self.stop_alarming() if self.snooze_value == 0: self.snooze = 0 self.mode = 'weather' print "Alarm stopped until tomorrow..." return True now = self.utc.localize(datetime.today()) now = now.astimezone(self.timezone) # Set snooze to snooze_value from now self.snooze = ( (now.hour * 60) + now.minute) - self.settings['alarm_time'] + self.snooze_value print 'Snoozing!' self.mode = 'weather' return True def start_alarming(self): self.alarming = True GPIO.output("CSID1", GPIO.HIGH) try: self.mpg123.kill() except: pass finally: self.mpg123 = subprocess.Popen([ 'mpg123', '--quiet', '--mono', '--loop', '-1', self.possible_tones[self.settings['tone_index']]['filename'] ], close_fds=True) def stop_alarming(self): self.alarming = False GPIO.output("CSID1", GPIO.LOW) try: self.mpg123.kill() except: pass def snooze_turn(self, count): self.snooze_value += count if self.snooze_value < 0: self.snooze_value = 0 return True def main_button(self, button): if self.alarming: now = self.utc.localize(datetime.today()) now = now.astimezone(self.timezone) # Set snooze to 5 minutes from now self.snooze = ( (now.hour * 60) + now.minute) - self.settings['alarm_time'] + 7 print 'Snoozing!' self.stop_alarming() else: self.mode = 'menu' self.menuItem = 'alarm' return False def main_knob(self, count): if self.alarming or self.snooze > 0: self.stop_alarming() self.mode = 'snooze' self.snooze_value = 5 return self.snooze_turn(count) else: self.mode = 'menu' self.menuItem = 'alarmOnly' return False menu = { # Main menu 'settings': { 'text': "Settings", 'subArrow': True, 'buttonAction': 'goto', 'buttonParam': 'setting-24h', 'next': 'alarm', 'prev': 'exit' }, 'alarm': { 'text': "Alarm", 'subArrow': False, 'buttonAction': 'toggle', 'buttonParam': 'alarm', 'next': 'set-alarm', 'prev': 'settings' }, 'set-alarm': { 'text': "Set Alarm", 'subArrow': True, 'buttonAction': 'mode', 'buttonParam': 'set', 'next': 'exit', 'prev': 'alarm' }, 'exit': { 'text': "Exit", 'subArrow': False, 'buttonAction': 'mode', 'buttonParam': 'weather', 'next': 'settings', 'prev': 'set-alarm' }, # Settings menu 'setting-24h': { 'text': "24 Hour", 'subArrow': False, 'buttonAction': 'toggle', 'buttonParam': 'twentyfour', 'next': 'setting-alarm-tone', 'prev': 'setting-exit' }, 'setting-alarm-tone': { 'text': "Ringtone", 'subArrow': True, 'buttonAction': 'mode', 'buttonParam': 'tone', 'next': 'setting-font', 'prev': 'setting-24h' }, 'setting-font': { 'text': "Font", 'subArrow': True, 'buttonAction': 'mode', 'buttonParam': 'font', 'next': 'setting-exit', 'prev': 'setting-alarm-tone' }, 'setting-exit': { 'text': "Exit", 'subArrow': False, 'buttonAction': 'mode', 'buttonParam': 'weather', 'next': 'setting-24h', 'prev': 'setting-font' }, # Alarm-only (Knob turned, not in menu) 'alarmOnly': { 'text': "Alarm", 'subArrow': False, 'buttonAction': 'toggle', 'buttonParam': 'alarm', 'next': 'alarmOnly-return', 'prev': 'alarmOnly-return' }, 'alarmOnly-return': { 'text': "Exit", 'subArrow': False, 'buttonAction': 'mode', 'buttonParam': 'weather', 'next': 'alarmOnly', 'prev': 'alarmOnly', }, } def draw_tone(self, draw, width, height, time_date_bottom): avail = height - time_date_bottom - 2 tonestr = "> " + self.possible_tones[ self.settings['tone_index']]['title'] w, h = draw.textsize(tonestr, font=self.date_font) vspace = (avail - h) / 2 y = time_date_bottom + vspace x = 8 draw.text((x, y), tonestr, fill=BLACK, font=self.date_font) x += w try: now = time.time() if now - self.lastAction > 15: self.mode = 'weather' except: self.lastAction = time.time() return False def draw_font(self, draw, width, height, time_date_bottom): avail = height - time_date_bottom - 2 fontstr = "> " + self.possible_fonts[ self.settings['font_index']]['title'] w, h = draw.textsize(fontstr, font=self.date_font) vspace = (avail - h) / 2 y = time_date_bottom + vspace x = 8 draw.text((x, y), fontstr, fill=BLACK, font=self.date_font) x += w try: now = time.time() if now - self.lastAction > 15: self.mode = 'weather' except: self.lastAction = time.time() return False def draw_menu(self, draw, width, height, time_date_bottom): avail = height - time_date_bottom - 2 menustr = "> " + AlarmClock.menu[self.menuItem]['text'] w, h = draw.textsize(menustr, font=self.menu_font) vspace = (avail - h) / 2 y = time_date_bottom + vspace x = 8 draw.text((x, y), menustr, fill=BLACK, font=self.menu_font) x += w if AlarmClock.menu[self.menuItem]['subArrow']: arrowstr = unichr(0xf04d) draw.text((x, y - 8), arrowstr, fill=BLACK, font=self.icon_font) elif AlarmClock.menu[self.menuItem]['buttonAction'] == 'toggle': param = AlarmClock.menu[self.menuItem]['buttonParam'] checktxt = " - " + ("ON" if self.settings[param] else "OFF") draw.text((x, y), checktxt, fill=BLACK, font=self.menu_font) try: now = time.time() if now - self.lastAction > 15: self.mode = 'weather' except: self.lastAction = time.time() return False def menu_turn(self, count): while (count > 0): self.menuItem = AlarmClock.menu[self.menuItem]['next'] count -= 1 while (count < 0): self.menuItem = AlarmClock.menu[self.menuItem]['prev'] count += 1 return False def menu_button(self, button): entry = AlarmClock.menu[self.menuItem] action = entry['buttonAction'] param = entry['buttonParam'] if action == 'toggle': self._set_setting(param, not self.settings[param]) elif action == 'mode': self.mode = param return True elif action == 'goto': self.menuItem = param return False def draw_snooze(self, draw, width, height, time_date_bottom): avail = height - time_date_bottom - 2 if self.snooze_value == 0: snooze_str = "Stop Alarm" else: snooze_str = "Snooze: " snooze_str += "%d Min" % self.snooze_value w, h = draw.textsize(snooze_str, font=self.menu_font) vspace = (avail - h) / 2 y = time_date_bottom + vspace x = 8 draw.text((x, y), snooze_str, fill=BLACK, font=self.menu_font) x += w try: now = time.time() if now - self.lastAction > 15: self.snooze_button(None) except: self.lastAction = time.time() return False def draw_set(self, draw, width, height, time_date_bottom): avail = height - time_date_bottom - 2 try: alarm_time = self.settings['alarm_time'] except: alarm_time = 360 alarm_hour = alarm_time / 60 alarm_minute = alarm_time % 60 alarmstr = "Alarm: " if self.settings['twentyfour']: alarmstr += "%d:%02d" % (alarm_hour, alarm_minute) else: alarmstr += "%d:%02d" % ( (lambda x: 12 if x == 0 else x)(alarm_hour % 12), alarm_minute) if alarm_hour >= 12: alarmstr += " PM" else: alarmstr += " AM" w, h = draw.textsize(alarmstr, font=self.menu_font) vspace = (avail - h) / 2 y = time_date_bottom + vspace x = 8 draw.text((x, y), alarmstr, fill=BLACK, font=self.menu_font) x += w try: now = time.time() if now - self.lastAction > 15: self.mode = 'weather' except: self.lastAction = time.time() return False def set_turn(self, count): if count > 10: count *= 10 elif count > 5: count *= 5 try: alarm_time = self.settings['alarm_time'] except: alarm_time = 360 self._set_setting('alarm_time', (alarm_time + count) % 1440) return False def set_button(self, button): self.mode = 'weather' logging.debug("Set alarm value to %d" % self.settings['alarm_time']) return True def draw_weather(self, draw, width, height, time_date_bottom): # The moon phase font represents the phase as a unicode glyph between f0d0 and f0eb # with the new moon at the end # # The DarkSky API represents the new moon as 0, the full moon as 0.5, and the rest # as a decimal fraction. # # To render the correct glpyh, first convert the fraction into an index between 0-28, # Subtract 1, and treat negative as a new moon... moonPhase = self.forecast.daily().data[0].moonPhase moonPhase *= 28.0 moonPhase = int(round(moonPhase)) - 1 if moonPhase < 0: moonPhase = 27 moonstr = unichr(moonPhase + 0xf0d0) # weather avail = height - 2 - time_date_bottom iconstr = AlarmClock.weather_icons[self.forecast.hourly().icon] iw, ih = draw.textsize(iconstr, font=self.icon_font) tempstr = u"%d\u00B0" % ( self.forecast.currently().apparentTemperature ) #, self.forecast.daily().data[0].apparentTemperatureMin, self.forecast.daily().data[0].apparentTemperatureMax) tw, th = draw.textsize(tempstr, font=self.weather_font) lowstr = u"Low:%d\u00B0" % self.forecast.daily( ).data[0].apparentTemperatureMin lw, lh = draw.textsize(lowstr, font=self.date_font) highstr = u"High:%d\u00B0" % self.forecast.daily( ).data[0].apparentTemperatureMax hw, hh = draw.textsize(highstr, font=self.date_font) mw, mh = draw.textsize(moonstr, font=self.icon_font) hlw = max(hw, lw) line_width = iw + tw + mw + max(hw, lw) horiz_space = (width - 4 - line_width) / 5 x = horiz_space + 2 y = time_date_bottom + ((avail - ih) / 2) draw.text((x, y), iconstr, fill=BLACK, font=self.icon_font) x += iw + horiz_space y = time_date_bottom + ((avail - th) / 2) draw.text((x, y), tempstr, fill=BLACK, font=self.weather_font) x += tw + horiz_space hlvspace = (avail - hh - lh) / 2 y = time_date_bottom + hlvspace draw.text((x, y), highstr, fill=BLACK, font=self.date_font) y += hh draw.text((x, y), lowstr, fill=BLACK, font=self.date_font) x += hlw + horiz_space y = time_date_bottom + ((avail - mh) / 2) draw.text((x, y), moonstr, fill=BLACK, font=self.icon_font) def turn_cb(self, address, pips): self.lock.acquire() self.pips += pips self.lock.release() def press_cb(self, address, presses): self.lock.acquire() self.presses += presses self.lock.release() def run(self): self.utc = timezone('UTC') blinked = False # initially set all white background image = Image.new('1', self.epd.size, WHITE) # prepare for drawing draw = ImageDraw.Draw(image) width, height = image.size # initial time self._update_timezone() now = self.utc.localize(datetime.today()) now = now.astimezone(self.timezone) full_update = True self._update_fonts() while True: prev = now start = time.time() # clear the display buffer draw.rectangle((0, 0, width, height), fill=WHITE, outline=WHITE) t_rect = time.time() # border draw.rectangle((1, 1, width - 1, height - 1), fill=WHITE, outline=BLACK) draw.rectangle((2, 2, width - 2, height - 2), fill=WHITE, outline=BLACK) t_border = time.time() if self.settings['twentyfour']: timefmt = "%-H:%M" else: timefmt = "%-I:%M %p" # Time & Date get 2/3rds of the display... daystr = '{dt:%A}, {dt:%B} {dt.day}'.format(dt=now) dw, dh = draw.textsize(daystr, font=self.date_font) timestr = now.strftime(timefmt) tw, th = draw.textsize(timestr, font=self.clock_font) avail = int((height - 4) * 0.60) space = (avail - dh - th) / 2 time_date_bottom = avail + 2 y = space + 2 draw.text(((width - tw) / 2, y), timestr, fill=BLACK, font=self.clock_font) y += th y += space draw.text(((width - dw) / 2, y), daystr, fill=BLACK, font=self.date_font) t_clock = time.time() try: logging.debug( "Calling " + str(AlarmClock.modes[self.mode]['render_bottom']) + " Mode: %s" % self.mode) AlarmClock.modes[self.mode]['render_bottom'](self, draw, width, height, time_date_bottom) except Exception as e: print e pass t_bottom = time.time() # display image on the panel self.epd.display(image) if not full_update: self.epd.partial_update() else: full_update = False self.epd.update() t_update = time.time() if (now.minute % 5) == 0: if blinked == False: blinked = True self.epd.blink() self._update_weather() else: blinked = False logging.debug("Clear: %s, Draw Border: %s, Draw Clock: %s, Draw Bottom: %s, Update: %s" % \ (str(t_rect - start), str(t_border - t_rect), str(t_clock - t_border), str(t_bottom - t_clock), str(t_update - t_bottom))) # wait for next minute i = 0 while True: if (i % 100) == 0: # Only call today() once every 100 loops now = self.utc.localize(datetime.today()) now = now.astimezone(self.timezone) if now.hour != prev.hour or now.minute != prev.minute: alarm_time = (self.settings['alarm_time'] + self.snooze) % 1440 if self.settings['alarm'] and ( (now.hour * 60) + now.minute) == alarm_time: self.start_alarming() print "Alarming!" elif self.settings['alarm']: value = ((now.hour * 60) + now.minute) logging.debug( "Waiting until %d to alarm (Currently: %d)" % (alarm_time, value)) break i += 1 self.lock.acquire() input = self.pips button = self.presses self.pips = 0 self.presses = 0 self.lock.release() if input or button: print(input, button) if input != 0: self.lastAction = time.time() logging.debug("Calling " + str(AlarmClock.modes[self.mode]['knob']) + " Mode: %s, Val: %d" % (self.mode, input)) full_update = AlarmClock.modes[self.mode]['knob'](self, input) break if button != 0: full_update = AlarmClock.modes[self.mode]['button'](self, button) break time.sleep(0.01) modes = { 'weather': { 'render_bottom': draw_weather, 'knob': main_knob, 'button': main_button }, 'menu': { 'render_bottom': draw_menu, 'knob': menu_turn, 'button': menu_button }, 'set': { 'render_bottom': draw_set, 'knob': set_turn, 'button': set_button }, 'font': { 'render_bottom': draw_font, 'knob': change_font, 'button': main_button }, 'tone': { 'render_bottom': draw_tone, 'knob': change_tone, 'button': main_button }, 'snooze': { 'render_bottom': draw_snooze, 'knob': snooze_turn, 'button': snooze_button }, }