Commit c6979ec0 authored by Ralf's avatar Ralf
Browse files

port to I²C

parent ca45cafb
from libtuer import ThreadFunction, logger
import RPi.GPIO as GPIO
import time
class Actor:
......@@ -8,13 +7,16 @@ class Actor:
CMD_LOCK = 2
class CMD():
def __init__(self, name, pin, tid, todo, verbose = True):
def __init__(self, name, pin, tid, todo, i2c, invert, verbose = True):
self.name = name
self.pin = pin
self.tid = tid
self.todo = todo
self.i2c = i2c
self.translator = (lambda x:not x) if invert else bool
self.verbose = verbose
# don't do the GPIO setup here, the main init did not yet run
# prepare the pin
self.i2c.setPin(self.pin,self.translator(False))
def execute(self):
if self.verbose:
......@@ -24,9 +26,10 @@ class Actor:
for (value, delay) in self.todo:
if value is not None:
logger.debug("Actor: Setting pin %d to %d" % (self.pin, value))
GPIO.output(self.pin, value)
self.i2c.setPin(self.pin, self.translator(value))
if delay > 0:
time.sleep(delay)
''' todo list explanation
type:
......@@ -41,30 +44,29 @@ class Actor:
Worker threads do work one thing at a time from their input queue.
=> different commands with same tid will never be executed simultaneously
'''
CMDs = {
CMD_UNLOCK: CMD("unlock", pin=12, tid=0, todo=[(True, 0.3), (False, 0.1)]),
CMD_LOCK: CMD( "lock", pin=16, tid=0, todo=[(True, 0.3), (False, 0.1)]),
CMD_BUZZ: CMD( "buzz", pin=22, tid=1, todo=[(True, 2.5), (False, 0.1)]),
}
def __init__(self):
def __init__(self, i2c):
self._CMDs = {
CMD_UNLOCK: CMD("unlock", pin=(0x21,3), tid=0, todo=[(True, 0.3), (False, 0.1)], i2c=i2c, invert=True),
CMD_LOCK: CMD( "lock", pin=(0x21,2), tid=0, todo=[(True, 0.3), (False, 0.1)], i2c=i2c, invert=True),
CMD_BUZZ: CMD( "buzz", pin=(0x21,1), tid=1, todo=[(True, 2.5), (False, 0.1)], i2c=i2c, invert=False),
}
# launch threads, all running the "_execute" method
self.threads = {}
for cmd in Actor.CMDs.values():
GPIO.setup(cmd.pin, GPIO.OUT)
# initialize all output pins with False
GPIO.output(cmd.pin, False)
for cmd in self._CMDs.values():
# one thread for each tid
if not cmd.tid in self.threads:
self.threads[cmd.tid] = ThreadFunction(self._execute, "Actor TID %d" % cmd.tid)
def _execute(self, cmd):
Actor.CMDs[cmd].execute()
self._CMDs[cmd].execute()
def act(self, cmd):
# dispatch command to correct thread
self.threads[Actor.CMDs[cmd].tid](cmd)
self.threads[self._CMDs[cmd].tid](cmd)
def stop(self):
for thread in self.threads.values():
thread.stop()
# kate: space-indent off; tab-width 4; indent-width 4;
\ No newline at end of file
from smbus import SMBus
from threading import RLock
class I2C:
'''This class manages the I²C bus.
It is thread-safe.
'''
def __init__(self, bus):
self._bus = SMBus(bus)
self._bytes = {} # this maps bytes to the last written value
self._active = False # for now, we are in initialization mode
self._lock = RLock()
def activate(self):
'''This function activates the bus. Only now, stuff is sent to the HW.
Call this after all relevant pings have default values.'''
with self._lock:
self._active = True
for addr, byte in self._bytes.items():
self.setPins(addr, byte)
def setPins(self, addr, stateByte):
'''Set all pins of the given address'''
with self._lock:
print("Setting {0:x} to {1:08b}".format(addr, stateByte))
if self._active:
self._bus.write_byte(addr, stateByte)
self._bytes[addr] = stateByte
def setPin(self, pin, state):
'''Set the gien pin: An (address, bit) pair'''
with self._lock:
(addr, bit) = pin
assert 0 <= bit and bit < 8
byte = self._bytes.get(addr, 0x00)
# set the bit
if state:
byte |= (1 << bit)
else:
byte &= ~(1 << bit)
# now we know what to set
self.setPins(addr, byte)
def getPin(self, pin):
with self._lock:
if not self._active:
return None
# read the current value
(addr, bit) = pin
byte = self.bus.read_byte(addr)
# now we need to get the bit
mask = (1 << bit)
return bool(byte & mask)
# kate: space-indent off; tab-width 4; indent-width 4;
\ No newline at end of file
from libtuer import logger
import RPi.GPIO as GPIO
import time
import threading
import signal
class Blinker(threading.Thread):
def __init__(self, name, pin, pattern=[(False,60.0)]):
GPIO.setup(pin, GPIO.OUT)
def __init__(self, name, pin, i2c, pattern=[(False,60.0)]):
super().__init__()
self._name = name
self._pin = pin
self._pattern = pattern
self._i2c = i2c
# if set, pattern will be reloaded and iteration of the pattern will restart
self._notifyer = threading.Event()
# Do you want to terminate this thread? Because this is how you terminate this thread.
......@@ -24,12 +23,12 @@ class Blinker(threading.Thread):
self._notifyer.clear()
# list is copied to avoid concurrent modification exceptions
for (state, delay) in list(self._pattern):
GPIO.output(self._pin, state)
self._i2c.setPin(self._pin,not state)
self._notifyer.wait(delay)
if self._notifyer.is_set() or self._terminate:
break
# LEDs off in the end
GPIO.output(self._pin, False)
self._i2c.setPin(self._pin,True)
def _getTid(self):
return self._thread.ident
......@@ -47,16 +46,19 @@ class Blinker(threading.Thread):
class LedActor:
ledPins = {
"red" : 26,
"green" : 23,
"red" : (0x21,4),
"green" : (0x21,5),
}
def __init__(self):
def __init__(self,i2c):
# launch threads, all running the "_execute" method
self._threads = {}
for (name,pin) in self.ledPins.items():
logger.debug("Setting pin %d to output as \"%s\" LED, initial state: False" % (pin,name))
self._threads[name] = Blinker(name,pin)
# set LED off initially
i2c.setPin(pin,True)
# yes, True means off
self._threads[name] = Blinker(name,pin,i2c)
self._threads[name].start()
def setPattern(self, ledName, pattern):
......@@ -75,4 +77,4 @@ class LedActor:
def stop(self):
for blinker in self._threads.values():
blinker.stop()
\ No newline at end of file
blinker.stop()
import RPi.GPIO as GPIO
from collections import namedtuple
from libtuer import ThreadRepeater, logger
from statemachine import StateMachine
......@@ -7,27 +6,33 @@ class PinsState():
pass
class PinWatcher():
def __init__(self, pin, histlen):
GPIO.setup(pin, GPIO.IN)
def __init__(self, pin, histlen, i2c, invert):
assert histlen > 1 # otherwise our logic goes nuts...
self.pin = pin
self.i2c = i2c
self._histlen = histlen
self._translator = (lambda x:not x) if invert else bool
# state change detection
self.state = None
self._state = None
self._newstate = None # != None iff we are currently seeing a state change
self._newstatelen = 0 # only valid if newstate != None
# raise the pin so we can read it
i2c.setPin(pin, invert) # we need a 1 if the pin is inverted, a 0 otherwise
def read(self):
curstate = True if GPIO.input(self.pin) else False
curstate = self.i2c.getPin(self.pin)
if curstate is None:
# the i2c is not yet ready. Nothing to do.
return False
assert curstate in (True, False)
if curstate != self.state:
if curstate != self._state:
# the state is about to change
if curstate == self._newstate:
# we already saw this new state
self._newstatelen += 1
if self._newstatelen >= self._histlen:
# we saw it often enough to declare it the new state
self.state = curstate
self._state = curstate
self._newstate = None
return True
else:
......@@ -39,15 +44,19 @@ class PinWatcher():
self._newstate = None
return False
def state(self):
return self._translate(self._state)
class PinsWatcher():
def __init__(self, state_machine):
def __init__(self, state_machine, i2c):
self._pins = {
'bell_ringing': PinWatcher(18, 2),
'door_closed': PinWatcher(8, 4),
'door_locked': PinWatcher(10, 4),
'space_active': PinWatcher(24, 4),
'bell_ringing': PinWatcher((0x20,2), 2, i2c, invert=True),
'door_closed': PinWatcher((0x20,1), 4, i2c, invert=True),
'door_locked': PinWatcher((0x20,0), 4, i2c, invert=True),
'space_active': PinWatcher((0x20,3), 4, i2c, invert=True),
}
self._sm = state_machine
self._i2c = i2c
# start a thread doing the work
self._t = ThreadRepeater(self._read, 0.02, name="PinsWatcher")
......@@ -58,15 +67,17 @@ class PinsWatcher():
pin = self._pins[name]
if pin.read():
saw_change = True
logger.debug("Pin %s changed to %d" % (name, pin.state))
logger.debug("Pin %s changed to %d" % (name, pin.state()))
if not saw_change:
return None
# create return object
pinsState = PinsState()
for name in self._pins.keys():
setattr(pinsState, name, self._pins[name].state)
setattr(pinsState, name, self._pins[name].state())
# send it to state machine
self._sm.callback(StateMachine.CMD_PINS, pinsState)
def stop(self):
self._t.stop()
# kate: space-indent off; tab-width 4; indent-width 4;
\ No newline at end of file
Device 0x21 (Output)
Bit0 - NC
Bit1 - Relays (Tueroeffner) (immer auf 0, auf 1 zum summen)
Bit2 - T2 Tuer zu (immer auf 1, impuls auf 0 für signal)
Bit3 - T1 Tuer auf
Bit4 - LED0 (1 ist an)
Bit5 - LED1
Bit6 - LED2
Bit7 - LED3
Device 0x20 (Input)
Bit0 - Tuer Schloss
Bit1 - Tuer Rahmen
Bit2 - In Klingel
Bit3 - In Switch
Bit4 - NC
Bit5 - NC
Bit6 - T1 Feedback
Bit7 - T2 Feedback
from busfahrer import I2C
import time
b = I2C(1)
b.activate()
i = 0
while True:
b.setPin((0x21, 4), i % 2 == 0)
b.setPin((0x21, 5), (i//2) % 2 == 0)
b.setPin((0x21, 6), (i//4) % 2 == 0)
b.setPin((0x21, 7), (i//8) % 2 == 0)
time.sleep(0.2)
i += 1
#!/usr/bin/python3
import RPi.GPIO as GPIO
from busfahrer import I2C
import statemachine, actor, ledactor, pins, tysock, waker, spaceapi
from libtuer import logger
import argparse
......@@ -13,6 +13,7 @@ parser.add_argument("-f", "--fallback",
action="store_true", dest="fallback",
help="Fallback mode for unfunctional hardware: Depend on less sensor input")
args = parser.parse_args()
if args.debug:
import libtuer
libtuer.mailAddress = []
......@@ -25,17 +26,20 @@ else:
# Not let's go!
logger.info("Starting up...")
# initialize GPIO stuff
GPIO.setmode(GPIO.BOARD)
# initialize i2c stuff
i2c = I2C(1)
# bring 'em all up
the_actor = actor.Actor()
the_led_actor = ledactor.LedActor()
the_actor = actor.Actor(i2c)
the_led_actor = ledactor.LedActor(i2c)
the_waker = waker.Waker()
the_api = spaceapi.SpaceApi(the_waker)
the_machine = statemachine.StateMachine(the_actor, the_led_actor, the_waker, the_api, args.fallback)
the_socket = tysock.TySocket(the_machine)
the_pins = pins.PinsWatcher(the_machine)
the_pins = pins.PinsWatcher(the_machine,i2c)
# tell the i2c that all initial values are set
i2c.activate()
# we do the socket accept thing in the main thread
try:
......@@ -53,6 +57,3 @@ the_machine.stop()
the_api.stop()
the_actor.stop()
the_led_actor.stop()
# shutdown GPIO stuff
GPIO.cleanup()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment