Skip to content

Commit 5bae8a0

Browse files
authored
Merge pull request #22 from jcostom/dev
Merge dev to main, release v3.0
2 parents 58d0b77 + 9cf23be commit 5bae8a0

File tree

7 files changed

+42
-41
lines changed

7 files changed

+42
-41
lines changed

.github/workflows/docker.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
uses: docker/build-push-action@v5
5151
with:
5252
context: .
53-
platforms: linux/arm/v7,linux/arm64
53+
platforms: linux/arm64
5454
push: ${{ github.event_name != 'pull_request' }}
5555
tags: ${{ steps.meta.outputs.tags }}
5656
labels: ${{ steps.meta.outputs.labels }}

.github/workflows/pr_docker_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ jobs:
2121
uses: docker/build-push-action@v5
2222
with:
2323
context: .
24-
platforms: linux/arm/v7,linux/arm64
24+
platforms: linux/arm64
2525
push: false

Dockerfile

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
1-
FROM python:3.12.3-slim-bookworm AS builder
1+
FROM ubuntu:noble-20240429
22

33
ARG TZ=America/New_York
4-
RUN apt update && DEBIAN_FRONTEND=noninteractive apt -yq install gcc make
5-
RUN pip install python-telegram-bot requests RPi.GPIO
6-
7-
FROM python:3.12.3-slim-bookworm
8-
9-
ARG TZ=America/New_York
10-
ARG PYVER=3.12
11-
12-
COPY --from=builder /usr/local/lib/python$PYVER/site-packages/ /usr/local/lib/python$PYVER/site-packages/
4+
RUN apt update && DEBIAN_FRONTEND=noninteractive apt -yq install python3-gpiozero python3-requests python3-python-telegram-bot
135

146
RUN mkdir /app
157

README.md

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,54 @@
11
# vibinator
22

3-
This is a complementary app to my [plugmon app](https://github.com/jcostom/plugmon). I'm using plugmon to monitor the Etekcity smart plug that our washer is plugged into. This enables me to tell when the washer is done.
3+
This is a complementary app to my [washerbot app](https://github.com/jcostom/washerbot). I'm using washerbot to monitor the Kasa smart plug that our washer is plugged into. By monitoring power use through the plug's API, I can tell when the washer finishes a load and then kick out a notification to the family.
44

5-
Ordinarily, I'd just recycle the code and use the same to keep an eye on the dryer, but there's a problem. You see, we've got an electric dryer, and nobody makes a smart plug for a 240V 30A appliance like a dryer. If we got a gas dryer, this would be easy, but I'm not dropping that kind of cash just to get this done.
5+
Ordinarily, I'd just recycle that code and just spin another instance of the same container to monitor the dryer, but there's a hitch. We've got an electric dryer, and nobody makes a 240V 30A smart plug. If we had a gas dryer I could do it, but I'm not about to buy a new dryer just to support notifications.
66

7-
So, instead of monitoring voltage, we'll look at vibration. When the dryer is running, it's vibrating.
7+
So, instead of monitoring voltage, we'll look at vibration. When the dryer is running, it's vibrating. I'm using an 801s vibration sensor, wiring up +5V DC, Ground, and Digital Output from the sensor to the Pi.
88

9-
Update - March 2022 - I'm in the process of refactoring this code to run under Docker. It's only ever going to be built as armv7 and arm64 images, as it just doesn't make sense to build as amd64 images ever.
9+
**Update**: As of v3.0, I'm rewriting some of the code here to migrate to the gpiozero Python module. Why do this? The Raspberry Pi 5 completely changed how GPIO works. Fortunately, the gpiozero module supports both old-style GPIO as well as the Pi 5! Also as of v3.0, I'm discontinuing support for non-64-bit ARM platforms.
1010

1111
Check out the example docker-compose file for how you should be launching this thing. Environment variables, with their default values follow:
1212

1313
* TZ: Your Time Zone, default is America/New_York*
1414
* INTERVAL: your polling interval, default is 120s (internally, this is carved into 4 slices)
1515
* SENSOR_PIN: which GPIO pin you're using for the sensor, default is pin 14
16-
* AVG_THRESHOLD: above this value, you declare the dryer as being "on", used to prevent false positives if you're in a "noisy" environment. Default is 0.2
17-
* LOGALL: logs more data during monitoring - useful for debugging monitor intervals and threshold levels, default is False. Set to True if you want more logs. Don't leave this on forever if you use a Pi with a flash card, as flash cards have a finite number of write ops.
16+
* AVG_THRESHOLD: above this value, you declare the dryer as being "on", used to prevent false positives if you're in a "noisy" environment. Default is 0.4
17+
* DEBUG: logs more data during monitoring - useful for debugging monitor intervals and threshold levels, default is False. Set to True if you want more logs. Don't leave this on forever if you use a Pi with a flash card, as flash cards have a finite number of write ops.
1818

19-
You should map the /dev/gpiomem device into the container as well. I believe you can also do a volume mount of /sys:/sys, but I wouldn't advise that for security reasons. Similarly, you could invoke the container as priviliged, but again, I wouldn't do that for security reasons.
19+
You should run this container in privileged mode.
20+
21+
## Wiring and tuning your sensor
22+
23+
I'm using an 801s sensor, which definitely needed some tuning. Typically there's a little screw on the sensor, and you turn it with a screwdriver to tune. They can sometimes be fiddly. If you're tuning, consider dialing down the INTERVAL, and turn on DEBUG while you're tuning. Here's what mine looks like. You don't have to do the same, but I can at least say it works.
24+
25+
![wiring diagram](pi5-with-sensor.png)
26+
27+
## Notifications
2028

2129
As of v2.5 of the container, multiple notification types are supported. Yes, you can do multiple notification types simultaneously too!
2230

23-
## Setting up Telegram
31+
### Setting up Telegram
2432

2533
There are a ton of tutorials out there to teach you how to create a Telegram Bot. Follow one and come back with your Chat ID and Token values. Set the USE_TELEGRAM variable to 1, and set the TELEGRAM_CHATID and TELEGRAM_TOKEN variables and you're set. The old variables of CHATID and MYTOKEN still work as well, but be a good citizen and update to the new variable names please.
2634

27-
## Setting up Pushover
35+
### Setting up Pushover
2836

2937
1. Sign up for an account at the [Pushover](https://pushover.net/) website and install the app on your device(s). Make note of your User Key in the app. It's easy to find it in the settings.
3038

3139
2. Follow their [API Docs](https://pushover.net/api) to create yourself an app you intend to use.
3240

3341
3. Pass the variables USE_PUSHOVER (set this to 1!), PUSHOVER_APP_TOKEN, and PUSHOVER_USER_KEY into the container and magic will happen.
3442

35-
## Setting up Pushbullet
43+
### Setting up Pushbullet
3644

3745
1. Sign up for an account at the Pushbullet website.
3846

3947
2. In the Settings > Account page, setup an API key.
4048

4149
3. Pass the variables USE_PUSHBULLET and PUSHBULLET_APIKEY to the container and wait for magic.
4250

43-
## Setting up Alexa Notifications
51+
### Setting up Alexa Notifications
4452

4553
1. Add the "Notify Me" skill to your Alexa account
4654

example-docker-compose.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ services:
55
vibinator:
66
image: jcostom/vibinator:latest
77
container_name: vibinator
8-
devices:
9-
- /dev/gpiomem:/dev/gpiomem
108
environment:
119
- USE_TELEGRAM=1
1210
- TELEGRAM_CHATID=your-chatid-value
1311
- TELEGRAM_TOKEN=your-token-name
1412
- TZ=America/New_York
13+
- DEBUG=0
14+
- INTERVAL=60
15+
- AVG_THRESHOLD=0.95
1516
restart: unless-stopped
17+
privileged: true
1618
networks:
1719
- containers
1820

pi5-with-sensor.png

166 KB
Loading

vibinator.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import json
77
import requests
88
import telegram
9-
import RPi.GPIO
9+
from gpiozero import LineSensor
1010
from time import sleep, strftime
1111

1212
# --- To be passed in to container ---
@@ -15,10 +15,10 @@
1515
SENSOR_PIN = int(os.getenv('SENSOR_PIN', 14))
1616
INTERVAL = int(os.getenv('INTERVAL', 120))
1717
READINGS = int(os.getenv('READINGS', 1000000))
18-
AVG_THRESHOLD = float(os.getenv('AVG_THRESHOLD', 0.2))
18+
AVG_THRESHOLD = float(os.getenv('AVG_THRESHOLD', 0.8))
1919
SLICES = int(os.getenv('SLICES', 4))
20-
RAMP_UP_READINGS = int(os.getenv('RAMP_UP_READINGS', 4))
21-
RAMP_DOWN_READINGS = int(os.getenv('RAMP_DOWN_READINGS', 4))
20+
RAMP_UP_READINGS = int(os.getenv('RAMP_UP_READINGS', 3))
21+
RAMP_DOWN_READINGS = int(os.getenv('RAMP_DOWN_READINGS', 3))
2222

2323
# Optional
2424
DEBUG = int(os.getenv('DEBUG', 0))
@@ -43,7 +43,7 @@
4343
ALEXA_ACCESSCODE = os.getenv('ALEXA_ACCESSCODE')
4444

4545
# Other Globals
46-
VER = '2.5.4'
46+
VER = '3.0'
4747
USER_AGENT = f"vibinator.py/{VER}"
4848

4949
# Setup logger
@@ -96,36 +96,35 @@ def send_notifications(msg: str) -> None:
9696
send_alexa(msg, ALEXA_ACCESSCODE)
9797

9898

99-
def sensor_init(pin: int) -> None:
100-
RPi.GPIO.setwarnings(False)
101-
RPi.GPIO.setmode(RPi.GPIO.BCM)
102-
RPi.GPIO.setup(pin, RPi.GPIO.IN, pull_up_down=RPi.GPIO.PUD_DOWN)
99+
def sensor_init(pin: int) -> LineSensor:
100+
s = LineSensor(pin=pin, pull_up=True)
101+
return s
103102

104103

105-
def take_reading(num_readings: int, pin: int) -> float:
104+
def take_reading(num_readings: int, s: LineSensor) -> float:
106105
total_readings = 0
107106
for _ in range(num_readings):
108-
total_readings += RPi.GPIO.input(pin)
107+
total_readings += s.value
109108
return (total_readings / num_readings)
110109

111110

112111
def main() -> None:
113-
sensor_init(SENSOR_PIN)
114112
logger.info(f"Startup: {USER_AGENT}")
113+
sensor = sensor_init(SENSOR_PIN)
115114
is_running = 0
116115
ramp_up = 0
117116
ramp_down = 0
118117
while True:
119118
slice_sum = 0
120119
for i in range(SLICES):
121-
result = take_reading(READINGS, SENSOR_PIN)
120+
result = take_reading(READINGS, sensor)
122121
DEBUG and logger.debug(f"Slice result was: {result}")
123122
slice_sum += result
124-
sleep(INTERVAL/SLICES)
123+
sleep(INTERVAL / SLICES)
125124
slice_avg = slice_sum / SLICES
126125
DEBUG and logger.debug(f"slice_avg was: {slice_avg}")
127126
if is_running == 0:
128-
if slice_avg >= AVG_THRESHOLD:
127+
if slice_avg <= AVG_THRESHOLD:
129128
ramp_up += 1
130129
if ramp_up > RAMP_UP_READINGS:
131130
is_running = 1
@@ -136,7 +135,7 @@ def main() -> None:
136135
ramp_up = 0
137136
DEBUG and logger.debug(f"Remains stopped: {slice_avg}")
138137
else:
139-
if slice_avg < AVG_THRESHOLD:
138+
if slice_avg > AVG_THRESHOLD:
140139
ramp_down += 1
141140
if ramp_down > RAMP_DOWN_READINGS:
142141
is_running = 0

0 commit comments

Comments
 (0)