Git initial commit

This commit is contained in:
Lars Hahn 2023-08-19 23:38:29 +02:00
commit ca3998a2d7
6 changed files with 577 additions and 0 deletions

9
LICENSE Executable file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

3
README.md Executable file
View File

@ -0,0 +1,3 @@
# smart-sensor
Repository to setup a library for sensors; can be used e.g. for home automatisation, environmental sensors for irrigation or else.

View File

@ -0,0 +1,2 @@
from led import LED
from switch import Switch

155
lib/python3/odcsensor/led.py Executable file
View File

@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""
This module provides a class to easy setup an LED instance for GPIO that
is capable to handle a real LED like switching on and off or to dim the light.
Each instance holds one pin and controls this pin.
The developer/user has to make sure, that there are no overlapping instances used...
In addition some basic functionality tests are provide as stand-alone script.
Classes:
LED
Functions:
main
class_test
"""
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
class LED:
"""
Class to controll an LED via a GPIO PIN in GPIO.BOARD configuration.
Each class instance controls exactly one pin.
Make sure they are not overlapping!
Methods:
__init__(pin, freq, is_inverse)
__del__()
freq()
set_freq(freq)
duty_cycle()
set_duty_cycle(duty_cycle)
set_on()
set_off()
"""
def __init__(self, pin, freq=2000, is_inverse=False):
"""
Constructor to create a single LED instance with one pin asociated.
The frequency can be set and also an inverse_state.
Keyword Arguments:
pin -- the GPIO.BOARD pin
freq -- the frequency for the LED (default: 2000)
is_inverse -- boolean if the LED is inverted (connected to 3.3V instead of GND) (default: False)
"""
self._pin = pin
self._is_inverse = is_inverse
GPIO.setup(self._pin, GPIO.OUT)
GPIO.output(self._pin, GPIO.LOW)
self._freq = freq
self._duty_cycle = 0 if not self._is_inverse else 100
self._pwm = GPIO.PWM(self._pin, self._freq)
self._pwm.start(self._duty_cycle)
def __del__(self):
"""
Destructor to stop PWM activated on a pin and setup the output low.
"""
self._pwm.stop()
GPIO.output(self._pin, GPIO.LOW)
def freq(self):
"""
Function to get the current used frequency.
Returns: freq
"""
return self._freq
def set_freq(self, freq):
"""
Function to set the frequency for the LED.
Keyword Arguments:
freq -- the frequency to be set
"""
self._freq = freq
self._pwm.ChangeFrequency(freq)
def duty_cycle(self):
"""
Function to get the current used duty cycle (PWM; dimming).
Is an integer 0 <= duty_cycle <= 100.
Returns: duty_cycle
"""
return self._duty_cycle
def set_duty_cycle(self, duty_cycle):
"""
Function to set the duty cycle (PWM; dimming) for the LED.
Has to be an integer 0 <= duty_cycle <= 100.
Keyword Arguments:
duty_cycle -- the frequency to be set
"""
dc = min(100,max(duty_cycle,0))
self._duty_cycle = dc if not self._is_inverse else 100 - dc
self._pwm.ChangeDutyCycle(self._duty_cycle)
def set_on(self):
"""
Function to switch an LED on and set the duty cycle to max.
"""
self.set_duty_cycle(100)
GPIO.output(self._pin, GPIO.HIGH)
def set_off(self):
"""
Function to switch an LED off and set the duty cycle to min.
"""
self.set_duty_cycle(0)
GPIO.output(self._pin, GPIO.LOW)
def class_test():
"""
Class to provide basic functionality testing.
Connect LED to pin 11,12 and GND.
Run led.py locally. The LED should turn on, switch
a bit and dim it self.
For each LED on pin 11 and 12 individually
"""
#Basic Testing
pins = (11,12)
for pin in pins:
LED1 = LED(pin)
#Basic Turn on and off
LED1.set_on()
time.sleep(1)
LED1.set_off()
time.sleep(1)
# Use simple PWM and off
LED1.set_duty_cycle(50)
time.sleep(1)
LED1.set_off()
time.sleep(2)
# Turn slowly down
for dc in range(100,-1,-1):
LED1.set_duty_cycle(dc)
time.sleep(0.1)
del LED1
GPIO.cleanup()
if __name__ == "__main__":
"""
A main function that is used, when this module is used as a stand-alone script.
Local imports in order not to disturb library import functionality (keeping it clean!)
"""
import time
class_test()

164
lib/python3/odcsensor/movement.py Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""
"""
from math import log2, fabs
from time import sleep
import pigpio
class Adxl345():
ADDR_DEV_ID = 0x00
ADDR_RATE_BW = 0x2C
ADDR_DATA_FORMAT = 0x31
ADDR_DATA_X_0 = 0x32 # from here on 6 bytes (2 per coordinate) for X,Y,Z
ADDR_OFFSET_X = 0x1E
ADDR_OFFSET_Y = 0x1F
ADDR_OFFSET_Z = 0x20
ADDR_POWER_CTL = 0x2D
def __init__(
self, sensitivity_range=16, data_rate_level=0):
# high range values means lower resolution for small movements!
# low range values means higher resolution for small movements!
self.set_sensitivity_range(sensitivity_range)
self.set_data_rate_level(data_rate_level)
def decode(self, lsb, msb):
accl = (msb << 8) | lsb
adjust = 2 * (2**(self.sensitivity_range + 1)) / (2**13) # given g range with 13 bit precision
correct_accl = (accl - (1 << 16)) * adjust if accl & (1 << 15) else accl * adjust
return correct_accl
def set_sensitivity_range(self, sensitivity_range):
errmsg = f"Sensitivity Range '{sensitivity_range}' needs to integer of 2,4,8,16"
if not isinstance(sensitivity_range, int):
raise TypeError(errmsg)
if sensitivity_range not in (2,4,8,16):
raise ValueError(errmsg)
self.sensitivity_range = int(log2(sensitivity_range))-1
data = self.from_address(Adxl345.ADDR_DATA_FORMAT, 1)[0] & ~0x0F | self.sensitivity_range | 0x08
self.to_address(Adxl345.ADDR_DATA_FORMAT, data)
def set_data_rate_level(self, data_rate_level):
errmsg = f"DataRateLevel '{data_rate_level}' needs to be integer within 0 < DRL <= 16!"
if not isinstance(data_rate_level, int):
raise TypeError(errmsg)
if data_rate_level < 0 or data_rate_level > 16:
raise ValueError(errmsg)
self.data_rate_code = 0b1111-(data_rate_level)
self.data_rate = int(3200/(2**(data_rate_level)))
self.to_address(Adxl345.ADDR_RATE_BW, self.data_rate_code)
def set_offset(self, x, y, z):
offset = {
Adxl345.ADDR_OFFSET_X: x,
Adxl345.ADDR_OFFSET_Y: y,
Adxl345.ADDR_OFFSET_Z: z,
}
for addr in offset.keys():
self.to_address(
addr,
int(offset[addr] / Adxl345.FACTOR_HIGH_RES / 4 ) & 0xFF
)
def set_on(self):
self.to_address(Adxl345.ADDR_POWER_CTL, 0x08)
def set_off(self):
self.to_address(Adxl345.ADDR_POWER_CTL, 0x00)
def calibrate(self, margin=0.1):
self.set_offset(0,0,0)
accel = self.get_acceleration()
x,y,z = accel[0], accel[1], accel[2]
if not all(
(0-margin < fabs(v) < 0+margin) or (1-margin < fabs(v) < 1+margin)
for v in (x, y, z)
):
raise ValueError(
f"WARNING! Please place sensor on appropriate surface; "
f"values should be around 0 or 1 and not {x,y,z}")
if not round(x) ^ round(y) ^ round(z):
raise ValueError(
"WARNING! Please place sensor on appropriate surface; "
"values should be around 0 or 1, with only one value 1 "
f"and not {x,y,z}"
)
calibration = [
round(v) - v
for v in (x,y,z)
]
self.set_offset(*calibration)
def get_acceleration(self,axis=0b111):
byte_ctr = 6 #2 bytes each for x,y,z values
data = self.from_address(Adxl345.ADDR_DATA_X_0, byte_ctr)
return [
self.decode(data[idx], data[idx+1])
for idx in range(0,byte_ctr, 2)
if axis & (1 << (idx//2))
]
class Adxl345Spi(Adxl345):
BITMASK_READ = 0x80
BITMASK_MULTI = 0x40
ADDR_SELECT_MASK = 0x3f
def __init__(self, channel=0, mode=0b11, baudrate=2e6):
self.channel = int(channel)
self.mode = int(mode)
self.baudrate = int(baudrate)
self.pi = pigpio.pi()
self.spi = self.pi.spi_open(self.channel, self.baudrate, self.mode)
super().__init__()
def from_address(self, addr, byte_count):
bit_msg = [
addr | Adxl345Spi.BITMASK_READ | (Adxl345Spi.BITMASK_MULTI * (byte_count > 1))
]
# add some random bytes, read bitmask is set -> no writing!
bit_msg.extend([
0xFF
for _ in range(byte_count)
])
count, data = self.pi.spi_xfer(self.spi, bit_msg)
if count != (byte_count+1) or len(data) != count:
raise ValueError(
f"Returned SPI bytes from {addr} seems not to be correct!\n"
f"Found {[x for x in data]}"
)
return data[1:]
def to_address(self, addr, values):
data_values = values if isinstance(values, list) else [values]
bit_msg = [
addr | (Adxl345Spi.BITMASK_MULTI * (len(data_values) > 1))
]
bit_msg.extend(data_values)
self.pi.spi_xfer(self.spi, bit_msg)
def stop(self):
self.pi.spi_close(self.spi)
self.pi.stop()
class Adxl345I2C(Adxl345):
pass
def main():
test = Adxl345Spi()
test.set_on()
for _ in range(10):
print(", ".join(str(val) for val in test.get_acceleration()))
sleep(1)
test.stop()
if __name__ == "__main__":
# execute only if run as a script
main()

244
lib/python3/odcsensor/switch.py Executable file
View File

@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""
This module provides classes that relate/beling to the physical class of switches.
It can be used to easily read state from switches and transform physical states
into computer and human readable values.
Each switch instance will represent exactly one physical switch, so that
a certain switch pin related to exact one Switch.
The developer/user has to make sure, that there are no overlapping instances used...
In addition some basic functionality tests are provide as stand-alone script.
Classes:
Switch
Functions:
main
class_Switch_test
class_Switch_functor
"""
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
class Switch:
"""
Class to handle input/states of physical switches in GPIO.BOARD configuration.
Each class instance controls exactly one switch.
Can be used for simple buttons aswell as binary tilt-switches or binary
vibrations switches.
Make sure pins are mutual exclusiv!
Methods:
__init__(pin, freq, is_inverse)
bouncetime()
set_bouncetime(bouncetime)
functor()
set_functor(functor)
edge()
set_edge(edge)
_update_callback()
_press_btn(*args)
default_functor()
"""
def map_edge(edge):
"""
Translate users integer input to return corresponding
GPIO edge detection states (GPIO.RISING, GPIO.FALLING, GPIO.BOTH).
GPIO.RISING means callback connected to an voltage increase
(e.g. pull-down-resistor with button pressed)
Keyword Arguments:
edge -- Integer to set/update the wished edge detection mode.
>0: RISING, ==0: BOTH, <0: FALLING
"""
if edge > 0:
return GPIO.RISING
if edge < 0:
return GPIO.FALLING
return GPIO.BOTH
def __init__(self, pin, functor=None, bouncetime=10, edge_detector=0, pud=1):
"""
Constructor to create connect to a single switch; bouncetime, edge detection,
PUD and a functor (what should be called on switch interaction) can be set.
Mandatory to set is the pin to read the signal/state from a button.
Keyword Arguments:
pin -- the GPIO.BOARD pin
functor -- function pointer, what should be called on switch action; (default: None -> calls default functor)
bouncetime -- switch sleepyness, in which periods signals are ignored (do not react on button flickering) (default: 10)
edge_detector -- integer to indicate on which kind of signal change (edge) to be listened to (default: 0 -> GPIO.BOTH)
pud -- Integer to indicate if it is pull-up or pull-down resistor (default: 1 -> GPIO.PUD_UP)
"""
self._pin = pin
self._functor = functor if functor is not None else Switch.default_functor
self._bouncetime = bouncetime
self._edge = Switch.map_edge(edge_detector)
self._pud = GPIO.PUD_UP if pud >= 0 else GPIO.PUD_DOWN
GPIO.setup(self._pin, GPIO.IN, pull_up_down=self._pud)
self._update_callback()
def pud(self):
"""
Return the PUD (pull up or down) resistor state of switch.
Cannot be set, only during creation of switch instance.
Returns: GPIO.PUD_UP or GPIO.PUD_DOWN
"""
return self._pud
def bouncetime(self):
"""
Return the currently used bouncetime; bouncetime indicates how fast a signal change (edge)
should/can trigger the callback function.
Returns: bouncetime -- integer
"""
return self._bouncetime
def set_bouncetime(self, bouncetime):
"""
Override the currently used bouncetime; will trigger an update of the callback function
by removing and adding (with new values) an event_detection to button signal pin.
Bouncetime indicates how fast a signal change (edge) should/can trigger the callback function.
Keyword Arguments:
bouncetime -- switch sleepyness, after which time periods signals are recognised.
"""
self._bouncetime = bouncetime
self._update_callback()
def functor(self):
"""
Returns the currently used functor that is used within callback triggering of the switch.
No real usage except comporing somewhere maybe ...
Returns: functor -- function pointer
"""
return self._functor
def set_functor(self, functor):
"""
Set the functor, function pointer that is used during the callback triggering; the input
for the functor is the current state of the button when pressed/released (typical: 0,1).
Allows the overriding for general purpose usage.
Keyword Arguments:
functor -- function pointer
"""
self._functor = functor
def edge(self):
"""
Returns the currently used edge detection for the switch.
Indicates if it is configured to listen on GPIO.RISING, -.FALLING or -.BOTH.
Returns: GPIO.BOTH, GPIO.FALLING or GPIO.RISING
"""
return self._edge
def set_edge(self, edge_detector):
"""
Allows overwriting of the edge detectiong during the callback triggering.
Based on the PUD resitor type, we can listen to a falling, rising or both
voltage changes.
Input is an integer, that is mapped accordingly to the three states
RISING, BOTH, FALLING (>0, ==0 , <0).
Keyword Arguments:
edge_detector -- integer, where the sign indicates for the edge_detection
(RISING, BOTH, FALLING)
"""
self._edge = Switch.map_edge(edge_detector)
self._update_callback()
def _update_callback(self):
"""
Internal function that is used to update the callback by removing and adding
the event_detection with adjusted values.
Is used in order to included changes on edge or bouncetime.
"""
GPIO.remove_event_detect(self._pin)
GPIO.add_event_detect(self._pin, self._edge, callback=self._press_btn, bouncetime=self._bouncetime)
def _press_btn(self, *args):
"""
Internal callback function that is used when a switch is triggered;
generically makes use of the given functor (default or adjusted by needs);
the current button/pin state is provided to the functor.
Keyword Arguments:
args -- generic arguments from the callback, currently not used.
"""
self._functor(GPIO.input(self._pin))
def default_functor(input):
"""
Static method, default functor that is used if not specified otherwise in
constructor.
Gets an input (button state) and prints based on the current state some
information (button pressed or not).
Keyword Arguments:
input -- integer, 0,1 and the current button state if pressed/released
"""
if input == 0:
print("Switch pressed")
elif input == 1:
print("Switch released")
else:
print("WARNING! UNKNOWN Switch STATE")
def class_switch_functor(input):
"""
Example switch functor that mutual exclusively turns
two LEDs on and off according to button pressing.
LED1+2 are set globally in main function in order to not
disturb the class functionality.
Keyword Arguments:
input -- integer, 0,1 and the current button state if pressed/released
"""
if input == 0:
LED1.set_on()
LED2.set_off()
elif input == 1:
LED1.set_off()
LED2.set_on()
else:
LED1.set_off()
LED2.set_off()
def class_switch_test():
"""
Example function to test the basic functionality of the switch class.
Makes use of in main globally setup LED1+2 instances. Is only called whe
library is called as it's own script (not imported).
Will call the default switch functor and a functor turning off and on
LEDs in pin 12 and 13.
"""
BTN1 = Switch(11)
print("Please press Switch (default test) ... 10s")
time.sleep(10)
print("Done!")
print("Now again please press Switch (functor test) ... 10s")
BTN1.set_functor(class_switch_functor)
time.sleep(10)
print("Done!")
GPIO.cleanup()
if __name__ == "__main__":
"""
A main function that is used, when this module is used as a stand-alone script.
Local imports in order not to disturb library import functionality (keeping it clean!)
"""
import time
from led import LED
LED1 = LED(12)
LED2 = LED(13)
class_switch_test()