Skip to content

Commit 7841f77

Browse files
feat: Telegram Support, High-Resolution Capture (#14)
* feat: better camera resolution * feat: new telegram method * refactor: remove unnecessary stuff * feat: send initial image * refactor: configure for telegram send * docs: update version * refactor: apply flake8 suggestions * fix: force reviewer to use toml file * fix: install flake8pyproject
1 parent c04be45 commit 7841f77

11 files changed

Lines changed: 121 additions & 42 deletions

File tree

.github/workflows/reviewer.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Install dependencies
1818
run: |
1919
python -m pip install --upgrade pip
20-
pip install flake8 pylint
20+
pip install flake8 pylint Flake8-pyproject
2121
- name: Checking the code using Flake8
2222
run: |
2323
flake8 $(git ls-files '*.py')

core/observers/observer/hss_observer.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from core.observers.subject.eye_subject import EyeSubject
77
from core.observers.subject.wifi_subject import WiFiSubject
88
from core.strategies.notifier.base_notifier_strategy import BaseNotifierStrategy
9+
from core.strategies.notifier.telegram_strategy import TelegramStrategy
10+
from core.strategies.notifier.whatsapp_strategy import WhatsappStrategy
911
from core.utils.datatypes import EyeStates, WiFiStates
1012
from core.utils.fileio_adaptor import read_latest_file, upload_to_fileio
1113
from core.utils.logger import get_logger
@@ -35,10 +37,20 @@ def update(self, subject: BaseSubject) -> None:
3537

3638
if self.wifi_state == WiFiStates.DISCONNECTED and self.eye_state == EyeStates.DETECTED:
3739
logger.info("There is an intruder!")
38-
fileio_link = upload_to_fileio(
39-
read_latest_file("~/.home-security-system/images")
40-
)
41-
self._notifier.notify_all(f"There is an intruder! Here is the image: {fileio_link}.")
40+
if isinstance(self._notifier, WhatsappStrategy):
41+
fileio_link = upload_to_fileio(
42+
read_latest_file("~/.home-security-system/images")
43+
)
44+
self._notifier.notify_all(
45+
f"There is an intruder! Here is the image: {fileio_link}."
46+
)
47+
elif isinstance(self._notifier, TelegramStrategy):
48+
latest_file = read_latest_file("~/.home-security-system/images")
49+
with open(latest_file, 'rb') as intruder_image:
50+
self._notifier.notify_all("There is an intruder! Here is the image:")
51+
self._notifier.send_image_all(intruder_image)
52+
else:
53+
logger.error("Notifier is not set!")
4254

4355
def set_notifier(self, notifier: BaseNotifierStrategy) -> None:
4456
"""This method is called when the observer is updated."""

core/observers/subject/eye_subject.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from concurrent.futures import Future, ThreadPoolExecutor
77
from datetime import datetime
88
from threading import Lock
9-
from time import sleep, time
9+
from time import sleep
1010
from typing import Optional
1111

1212
import cv2
@@ -80,6 +80,12 @@ def _run_in_loop(self,
8080
logger.debug("[EyeSubject] A lock instance is generated.")
8181
wifi_lock = Lock()
8282

83+
# Save an initial image.
84+
initial_frame = eye_strategy.get_frame()
85+
file_location = f"{self._image_path}/initial_frame.jpg"
86+
cv2.imwrite(file_location, initial_frame)
87+
logger.debug("[EyeSubject] Initial frame has been saved.")
88+
8389
while True:
8490
# If WiFi subject would give rights to use camera,
8591
# Check if any intruders detected.
@@ -119,10 +125,6 @@ def _save_image(self, result: EyeStrategyResult) -> None:
119125
def _cb_done(self, future) -> None:
120126
"""This method is called when the observer is updated."""
121127
logger.warning("[EyeSubject] The thread died.")
122-
# Create a txt file to indicate the thread died.
123-
file_location = "thread_die.txt"
124-
with open(file_location, "a", encoding="utf-8") as file:
125-
file.write(f"The EyeSubject thread died. Time: {time()}")
126128

127129
# Start the thread again.
128130
self.run(self._eye_strategy, self._wifi_lock)

core/observers/subject/wifi_subject.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"""
55
from concurrent.futures import Future, ThreadPoolExecutor
66
from threading import Lock
7-
from time import sleep, time
7+
from time import sleep
88
from typing import Optional
99

1010
from core.observers.subject.base_subject import BaseSubject
@@ -85,9 +85,6 @@ def _run_in_loop(self, wifi_strategy: BaseWiFiStrategy) -> None:
8585
def _cb_done(self, future) -> None:
8686
"""This method is called when the observer is updated."""
8787
logger.warning("[WiFiSubject] The thread died.")
88-
file_location = "thread_die.txt"
89-
with open(file_location, "a", encoding="utf-8") as file:
90-
file.write(f"The WiFiSubject thread died. Time: {time()}")
9188

9289
# Run the thread again.
9390
self.run(self._wifi_strategy)

core/strategies/detectors/efficientdet_strategy.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,16 @@ def detect_humans(cls, frame: numpy.ndarray) -> DetectorResult:
6161
continue
6262

6363
if (labels[int(pred_class)]) == 'person':
64-
min_y = round(box[0] * image_height)
65-
min_x = round(box[1] * image_width)
66-
max_y = round(box[2] * image_height)
67-
max_x = round(box[3] * image_width)
64+
frame_height, frame_width = frame.shape[:2]
65+
min_y = round(box[0] * frame_height)
66+
min_x = round(box[1] * frame_width)
67+
max_y = round(box[2] * frame_height)
68+
max_x = round(box[3] * frame_width)
6869
detection_regions.append((min_x, max_x, min_y, max_y))
69-
70-
cv2.rectangle(image, (min_x, min_y), (max_x, max_y), (0, 255, 0), 2)
70+
cv2.rectangle(frame, (min_x, min_y), (max_x, max_y), (0, 255, 0), 5)
7171

7272
result = DetectorResult(
73-
image=image,
73+
image=frame,
7474
human_found=len(detection_regions) > 0,
7575
regions=detection_regions,
7676
num_detections=len(detection_regions),

core/strategies/eye/picamera_strategy.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
The Camera strategy for eye strategies.
33
"""
4+
import cv2
45
import numpy
56
from picamera2 import Picamera2
67

@@ -32,11 +33,10 @@ def get_frame(self) -> numpy.ndarray:
3233
"""This method returns the frame from the camera."""
3334
# Internal attributes
3435
picam2 = Picamera2()
35-
picam2.configure(picam2.create_preview_configuration(
36-
main={"format": 'XRGB8888', "size": (640, 480)}
37-
))
36+
still_configuration = picam2.create_still_configuration(main={"format": 'XRGB8888'})
37+
picam2.configure(still_configuration)
3838
picam2.start()
3939
frame = picam2.capture_array()
4040
picam2.close()
4141
del picam2
42-
return frame
42+
return cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""
2+
This module contains the Telegram notifier strategy.
3+
"""
4+
import telebot
5+
6+
from core.strategies.notifier.base_notifier_strategy import BaseNotifierStrategy
7+
from core.utils.datatypes import TelegramReciever
8+
from core.utils.logger import get_logger
9+
10+
# Get the logger instance.
11+
logger = get_logger(__name__)
12+
13+
14+
class TelegramStrategy(BaseNotifierStrategy):
15+
"""
16+
The Telegram notifier strategy.
17+
"""
18+
19+
def __init__(self, api_key: str) -> 'TelegramStrategy':
20+
"""Initilizes the Telegram modifier strategy."""
21+
self._bot = telebot.TeleBot(token=api_key,
22+
threaded=True,
23+
num_threads=2)
24+
super().__init__()
25+
26+
def notify_all(self, message: str) -> None:
27+
"""This method is called when the notifier is updated."""
28+
for reciever in self._recievers:
29+
if isinstance(reciever, TelegramReciever):
30+
logger.debug("Sending Telegram message (%s) to %s", message, reciever.chat_id)
31+
self._bot.send_message(reciever.chat_id, message)
32+
33+
def send_image_all(self, image: bytes) -> None:
34+
"""This method is called when the notifier is updated."""
35+
for reciever in self._recievers:
36+
if isinstance(reciever, TelegramReciever):
37+
logger.debug("Sending image to %s", reciever.chat_id)
38+
self._bot.send_photo(reciever.chat_id, image)

core/utils/datatypes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,9 @@ class WhatsappReciever(NotifierReciever):
6464
"""This class represents WhatsApp reciever."""
6565
telephone_number: str
6666
api_key: str
67+
68+
69+
@dataclass
70+
class TelegramReciever(NotifierReciever):
71+
"""This class represents Telegram reciever."""
72+
chat_id: str

main.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44
import json
55
import sys
66
from concurrent.futures import wait
7+
from time import sleep
78
from typing import Any
89

910
from core.observers.observer.hss_observer import HomeSecuritySystemObserver
1011
from core.observers.subject.eye_subject import EyeSubject
1112
from core.observers.subject.wifi_subject import WiFiSubject
1213
from core.strategies.detectors.efficientdet_strategy import EfficientdetStrategy
1314
from core.strategies.eye.picamera_strategy import PiCameraStrategy
15+
from core.strategies.notifier.telegram_strategy import TelegramStrategy
1416
from core.strategies.notifier.whatsapp_strategy import WhatsappStrategy
1517
from core.strategies.wifi.admin_panel_strategy import AdminPanelStrategy
16-
from core.utils.datatypes import Protector, WhatsappReciever
18+
from core.utils.datatypes import Protector, TelegramReciever
19+
from core.utils.fileio_adaptor import upload_to_fileio
1720

1821

1922
def read_configurations() -> dict[str, Any]:
@@ -35,21 +38,20 @@ def main():
3538
# Read configurations.
3639
config, strategy_config = read_configurations()
3740
# Create a WhatsApp notifier.
38-
whatsapp_notifier = WhatsappStrategy()
41+
notifier = TelegramStrategy(strategy_config['telegram_strategy']['bot_key'])
3942
for reciever in config['recievers']:
40-
whatsapp_notifier.add_reciever(WhatsappReciever(reciever['name'],
41-
reciever['tel_no'],
42-
reciever['callmebot_key']))
43+
notifier.add_reciever(TelegramReciever(reciever['name'],
44+
reciever['chat_id']))
4345

4446
# Create a Protector within IpAddressStrategy.
45-
ip_address_strategy = AdminPanelStrategy(strategy_config)
47+
network_strategy = AdminPanelStrategy(strategy_config['admin_panel_strategy'])
4648
for protector in config['protectors']:
47-
ip_address_strategy.add_protector(Protector(protector['name'],
48-
protector['address']))
49+
network_strategy.add_protector(Protector(protector['name'],
50+
protector['address']))
4951

5052
# Create observer.
5153
hss_observer = HomeSecuritySystemObserver()
52-
hss_observer.set_notifier(whatsapp_notifier)
54+
hss_observer.set_notifier(notifier)
5355

5456
#  Create subjects to observe.
5557
wifi_subject = WiFiSubject()
@@ -58,23 +60,31 @@ def main():
5860
eye_subject.attach(hss_observer)
5961

6062
# Run subjects.
61-
wifi_subject.run(ip_address_strategy)
63+
wifi_subject.run(network_strategy)
6264

6365
# Set-up the camera to detect humans.
6466
camera = PiCameraStrategy()
6567
camera.set_detector(EfficientdetStrategy())
6668
eye_subject.run(camera, wifi_subject.get_protector_lock())
6769

6870
# Notify that the system is running.
69-
whatsapp_notifier.notify_all("Home Security System is started.")
71+
notifier.notify_all("Home Security System is started.")
72+
sleep(5)
73+
74+
if isinstance(notifier, TelegramStrategy):
75+
with open(f"{eye_subject._image_path}/initial_frame.jpg", "rb") as initial_frame:
76+
notifier.send_image_all(initial_frame)
77+
elif isinstance(notifier, WhatsappStrategy):
78+
fileio_link = upload_to_fileio(initial_frame)
79+
notifier.notify_all(f"Here is the initial frame: {fileio_link}.")
7080

7181
# Wait for the futures.
72-
_, failures = wait([wifi_subject.thread, eye_subject.thread])
82+
_, failures = wait([wifi_subject.thread, eye_subject.thread], return_when="FIRST_COMPLETED")
7383
for failure in failures:
74-
whatsapp_notifier.notify_all("Home Security System has failed to run. "
75-
"Please check the logs.")
76-
whatsapp_notifier.notify_all("Error: " + str(failure.exception()))
77-
whatsapp_notifier.notify_all("Result: " + str(failure.result()))
84+
notifier.notify_all("Home Security System has failed to run. Please check the logs.")
85+
notifier.notify_all("Failure: " + str(failure))
86+
notifier.notify_all("Error: " + str(failure.exception()))
87+
notifier.notify_all("Result: " + str(failure.result()))
7888
# Close the application to let systemd re-start it.
7989
sys.exit(1)
8090

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "home-security-system"
33
description = "Check human presence within a camera in your Raspberry Pi if your phone is not connected to the WiFi network."
44
keywords = ["security-system", "tinyml", "camera", "embedded-linux"]
5-
version = "2024.3.0"
5+
version = "2024.4.21"
66
readme = { file = "README.md", content-type = "text/markdown" }
77
license = { file = "LICENSE" }
88

0 commit comments

Comments
 (0)