├── .gitignore
├── .startscript.sh
├── License.md
├── Motion Detector Camera with ePIR (discontinued part).fzz
├── Motion Detector Camera.fzz
├── README.md
├── Raspberry Pi Costing.xls
├── __make_executable
├── _showproc
├── doorbell2.ogg
├── install_python_linphone.sh
├── jampi.png
├── jampi.svg
├── security_camera.py
└── security_off
/.gitignore:
--------------------------------------------------------------------------------
1 | *.bak
2 | .~lock.Raspberry Pi Costing.xls#
3 | *.xls#
4 |
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | env/
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *,cover
50 | .hypothesis/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 |
59 | # Sphinx documentation
60 | docs/_build/
61 |
62 | # PyBuilder
63 | target/
64 |
65 | #Ipython Notebook
66 | .ipynb_checkpoints
67 |
--------------------------------------------------------------------------------
/.startscript.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #curl wttr.in/SGF
3 | #sleep 5
4 | # You probably want to remove the line with 'sudo rm...', unless you really do
5 | # want to delete the log file every time.
6 | # The double 'clear' commands are due to a quirk in Putty--it always keeps
7 | # one screenfull in the scrollback buffer unless you do this.
8 | echo 'Stopping any currently running security_camera.py Python script...'
9 | ./security_off
10 | sleep 3
11 | # Comment-out the next two lines if you always want to append to the log.
12 | # You should monitor the disk space and move the log file across the network/to a USB.
13 | echo 'Cleaning log...'
14 | sudo rm /var/log/security_camera.log; clear; clear
15 | echo 'Starting security_camera.py, tailing the log.'
16 | sudo python security_camera.py >>security_camera_log 2>&1 &
17 | sleep 3
18 | tail -f /var/log/security_camera.log
19 | cp /var/log/security_camera.log ./
20 | #~/ngrok tcp 22
21 |
22 |
--------------------------------------------------------------------------------
/License.md:
--------------------------------------------------------------------------------
1 | **The MIT License (MIT)**
2 |
3 | Copyright (c) 2016 Leland Green
4 |
5 | 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:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | 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.
10 |
--------------------------------------------------------------------------------
/Motion Detector Camera with ePIR (discontinued part).fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lelandg/Raspberry-Pi-Security-Camera/978899c9d111c4d53e6fdb41efc6bca119398d80/Motion Detector Camera with ePIR (discontinued part).fzz
--------------------------------------------------------------------------------
/Motion Detector Camera.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lelandg/Raspberry-Pi-Security-Camera/978899c9d111c4d53e6fdb41efc6bca119398d80/Motion Detector Camera.fzz
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Raspberry-Pi-Security-Camera
2 | 
3 |
4 | Copyright (C) 2016 Leland Green... All rights reserved.
5 | Released under MIT license so you can use for any purpose.
6 | See License.md for the licensing text.
7 |
8 | **Definitions**
9 |
10 | * **Significant Changes** : Anything that makes the project commercial; non--open-source; or changes the core functionality of the project (i.e., it is no longer a security camera or video doorbell
11 | * **Commercial Software** : If you are *now making money* from the project or there is *any chance* that you will *ever make money* from the project, it is considered commercial software.
12 | * **Educational Setting** : Within the context of teaching. Not necessarily teaching about the Raspberry Pi, but teaching about any subject where this project will be useful. While every institution of formal education would certainly qualify, such an instution is *not* required. E.g., a tutor could certainly claim that they were also using within an *educational setting*.
13 |
14 | If you make **significant changes** I require only two things: **the graphic (JamPi Logo)** and **JamPi name** must be removed. You are free to use those (name and graphic) in a **non-commercial** , **or educational** setting, *only*. For a project with significant changes, you must (1) remove them from any place they are shown to the end-user and (2) state that your project was derived from the **JamPi** project in the documentation and/or the comments of the source code. In that case you must also include a link to the original project page. (This will suffice: https://github.com/lelandg/Raspberry-Pi-Security-Camera)
15 |
16 | Since the graphic is currently not used, you can just delete it, and then do not show the name **JamPi** to the *end-user* and we will be OK with each other. :-) **Simply search/replace in the file security_camera.py for "JamPi" and replace it with your product name** ; simple... especially since that's only used in the initialization log message. You are allowed to leave that in, until you make **significant changes** , when you must rename it. Fair? Thank you for your cooperation.)
17 |
18 | See *Requirements* and then *Installation* below if you want to jump right in!
19 |
20 | **Features**
21 |
22 | * This is an active project! New features are planned (no matter *when* you read this! :D ). So I hope you'll check back once-in-a-while, or follow me on Google+ or Facebook. (Everything I do is 100% visible to the public.)
23 | * Works with a Raspberry Pi camera module (AKA "RaspiCam") *OR* a USB webcam! Preferably one that has hardware H2.64 compression.
24 | * Motion detector (PIR/ePIR sensor) triggered events. When motion is detected:
25 | * Send a chat message to a list of SIP addresses. This is only done every X seconds. (Configurable; Default=1 minute)
26 | * Email and image of the intruder to a single person or to several people. Also done only ever X2 seconds. (Separate configuration for time. Default=1 minute.)
27 | * Start recording video to the SD card (default is for 15 seconds).
28 | * Flash an LED (optional)
29 | * Log on to the camera, using any Linphone client for live, streaming video from the camera.
30 | * No limit to connection time!
31 | * While connected to the live camera, the notification features are turned off. (This is to save on resources more than a lack of the Raspberry Pi, which is fully capable of doing everything simultaneously.
32 | * Only people configured (in the Python script) are allowed to connect
33 | * Optionally use as a "video doorbell". Put the button and camera (and optional motion sensor) outside the door and record a picture of everyone who rings your doorbell, plus, it rings through to your SIP videophone (Linphone is a good one), so you can see video of the person! Right now you can talk to them, but they can't talk to you. (Sound is only working one way... maybe just my system?) This is true of both the doorbell and when you make a call to this device.
34 |
35 | **Requirements**
36 |
37 | * This script requires custom hardware, which is included as a Fritzing/PNG files. Uses a PIR/ePIR sensor to detect motion.
38 | * It is recommended that you have two SIP accounts, one for the security camera device and one for you. This greatly simplifies connecting to the camera via Linphone. Simply use the "address" that is the SIP account running on the device.
39 |
40 | Sending video is resource-intensive so you should boot to Console mode (probably with auto-login if this is to be a remote deployment). To be clear, I could never get video to work when I booted my Pi to X desktop. So that is unsupported at this time. (This may be a misunderstanding of something on my part so please speak-up if you have info that may help me.)
41 |
42 | First, change to the directory with:
43 | `cd Raspberry-Pi-Security-Camera`
44 |
45 | Because it uses an I/O device you need elevated permissions to run it. So you will need to run the script with:
46 | `.startscript.sh`
47 | *or*
48 | `sudo python security_camera.py`.
49 | However, you should *always* use `.startscript.sh`. If you want something different, edit the script once and it will be changed from then on! :)
50 |
51 | **TIP**
52 | If you want to find programs in your home directory without typing the preceding `./`, whether or not you're running as sudo, you can add to your `.bashrc` with:
53 | * `nano ~/.bashrc`
54 | * Scroll to the end of the file and add the line:
55 | `export PATH=$PATH:/home/pi`
56 | * After this change you can simply type `.startscript.sh`. (And *actually* just type ".sta" and press the [tab]. It will probably type the rest of the command for you. Same thing with the other shell scripts! Save your typing for Github! :)
57 |
58 | This project also represents my first (serious) attempt at using GitHub as it was intended. You may see me open issues and then close them after a commit/merge. I hope that these comments prove useful to you.
59 |
60 | **Installation**
61 | * If you do not have git installed, run:
62 | ```
63 | sudo apt-get install git
64 | ```
65 | Then everyone should run:
66 | ```
67 | git clone https://github.com/lelandg/Raspberry-Pi-Security-Camera
68 | cd Raspberry-Pi-Security-Camera
69 | ```
70 | * After that, when updating, always run that same command to start with (or else download & unzip all files to your home directory, or whichever one you want to run from). Only the program files will be installed to this directory. In keeping with Linux standards the log will alwas be /var/log/security_camera.log
71 | * This file is owned by root so you may need to chown on it if you can't access it. I just copy to Windows and use my favorite GUI editor.
72 | * run:
73 | ```
74 | sudo chmod +x __make_executable
75 | __make_executable
76 | ```
77 | * This last command simply runs chmod on the three utility scripts for you. :) It is technically not required--you can run with only "sudo python security_camera.py" if you want to! But that's a mouthful, so I've made it simpler by including three scripts. (See next section.)
78 | * Install Python Linphone
79 | * Please see https://wiki.linphone.org/wiki/index.php/Raspberrypi:start for complete instructions.
80 |
81 | **Optionally**
82 |
83 | * Either install python-espeak or set "talkToEm = False" in the script. (Change the existing line, don't add one!)
84 | ```
85 | sudo apt-get install python-espeak
86 | ```
87 |
88 | * If you hit errors, on a Raspberry Pi, especially, please open an issue under this project. I appreciate all reports, even if it's something you do not understand! Please help me to understand what you do not understand, if at all possible. If not, just send me the log file, preferably via your DropBox (or similar service). If you need an invitation to drop box, please let me know and I will send you one.
89 |
90 | **Operation**
91 |
92 | Three shell scripts are included to make operation of the Python script a snap. They have very different names to aid you in starting, stopping and showing the process (if any) for the currently running script.
93 |
94 | You do *not* need to prefix these with "sudo". The scripts do that for you! :) These scripts are:
95 | * `.startscript.sh` -- Starts the security_camera.py after some basic "housekeeping".
96 | *This is the recommended way to run your `security_camera.py` script!* This script will:
97 | * Stop one previosly running script. (But only one!)
98 | * Remove the log file. So if you have errors you want to keep, rename `/var/log/security_camera.log` to something unique *before* you restart the script, or it will be overwritten. Or better yet, rename them into your DropBox folder and email me a link! :)
99 | * Start `security_camera.py` running in the background.
100 | * `tail -f /var/log/security_camera.log`, so you see all messages on the console. (You could disable the `tail` command if speed is an issue.) Yet they are still saved in the log file, until you restart the script.
101 | * `security_off` -- Kills the currently running script. You can run this multiple times, it won't kill anything else. (Unless the python script has exactly the same name, but then it would be the same script. Ha!)
102 | * `_showproc` -- Shows any currently running security camera Python scripts. Note that if you see only one line, you should also see a "grep" in the command portion of the output, which means that is the _showproc script, itself, *not* the Python script. So if you only see one line in the output, that means you have all instances of the security camera script stopped.
103 |
104 | **NOTES**
105 |
106 | If you see an error like this: `WARNING: ./share/sounds/linphone/rings/oldphone.wav does not exist`, you can run:
107 | ```
108 | sudo find / -iname '*.wav' > /home/pi/soundfiles.txt && less ~/soundfiles.txt
109 | ```
110 | to find all sound files on your machine. Once the list is displayed (and stored in `~/soundfiles.txt`, if you see a line with `/usr/local/lib/python2.7/dist-packages/linphone/share/sounds/linphone/ringback.wav`, use the parent directory as input to this command `sudo ln -s /usr/local/lib/python2.7/dist-packages/linphone/share/sounds/linphone `; such that, for this example:
111 | ```
112 | sudo ln -s /usr/local/lib/python2.7/dist-packages/linphone/share/sounds/linphone /usr/share/sounds/linphone
113 | ```
114 |
115 | **IMPORTANT NOTE** In your home directory you will find the file `.linphonerc` ( `~/.linphonerc` should always access it). This file has a line for `contact=`. I recommend opening this file and changing it to contain your current SIP account so that it does not conflict with that given in the script. Depending on how you've used Linphone on RPi, you may have other options that conflict! I cannot hope to document all of those, so you need to look up the documentation for these.
116 |
117 | **WARNING** Do *not* run more than one instance of the script! The shell scripts *attempt* to prevent you from doing this by calling `security_off` before it actually "turns it back on".
118 |
119 | **WARNING** Running multiple instances of the script is certainly unsupported and behavior is undefined, at best!
120 |
--------------------------------------------------------------------------------
/Raspberry Pi Costing.xls:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lelandg/Raspberry-Pi-Security-Camera/978899c9d111c4d53e6fdb41efc6bca119398d80/Raspberry Pi Costing.xls
--------------------------------------------------------------------------------
/__make_executable:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | sudo chmod +x ./install_python_linphone.sh
3 | sudo chmod +x ./.startscript.sh
4 | sudo chmod +x ./security_off
5 | sudo chmod +x ./_showproc
6 |
--------------------------------------------------------------------------------
/_showproc:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | sudo ps aux | grep 'python security_camera.py'
3 |
--------------------------------------------------------------------------------
/doorbell2.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lelandg/Raspberry-Pi-Security-Camera/978899c9d111c4d53e6fdb41efc6bca119398d80/doorbell2.ogg
--------------------------------------------------------------------------------
/install_python_linphone.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | wget https://linphone.org/releases/linphone-python-raspberry/linphone4raspberry-3.9.0-cp27-none-any.whl
3 | sudo apt-get install python-setuptools
4 | sudo easy_install pip
5 | sudo pip install wheel
6 | sudo pip install --upgrade pip
7 | sudo pip install linphone4raspberry-3.9.0-cp27-none-any.whl
8 |
9 |
--------------------------------------------------------------------------------
/jampi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lelandg/Raspberry-Pi-Security-Camera/978899c9d111c4d53e6fdb41efc6bca119398d80/jampi.png
--------------------------------------------------------------------------------
/jampi.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
41 |
--------------------------------------------------------------------------------
/security_camera.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # Based on the "security camera" example Python script found on Linphone.org here:
3 | # https://wiki.linphone.org/wiki/index.php/Raspberrypi:start
4 | # Heavily modified to add motion detector features for Zilog ePIR SBC with ZDots. This is an advanced
5 | # version and I cannot find this exact model, so I think it's discontinued.
6 | #
7 | # Now supports PIR sensor (newer style, lower power) by simply setting PIRPIN > 0.
8 | # The default is to use pin 4 (in BCM mode = GPIO4 = physical pin = 7 = BOARD mode pin 7).
9 | #
10 | # On the off-chance that you DO have an ePIR, simply set PIRPIN = 0 and MDPIN = pin 5 on the ePIR. You'll
11 | # actually want to refer to the Fritzing/PNG file for the schematic in that case, too. In order to support
12 | # all features (at a future date), I wired up all of the ePIR pins. So it's GPIO-hungry. But I can add things
13 | # like ambient light level (which I plan to do, BTW--even if I AM the only one with the module! :) ).
14 | #
15 | # Tip: If you want to disable the LED on the camera completely, add "disable_camera_led=1" (with no quotes) to
16 | # the end of the line in /boot/cmdline.txt. *However* this is not working with Raspbian Jessie.
17 | #
18 | # Please let me know if you have questions. I will do my best to help you. (Please be patient. I am on disability.)
19 | # All modifications written by: Leland Green - aboogieman - a t - gmail.com
20 | __version__ = "0.2.1"
21 |
22 | import os
23 | import traceback
24 | import datetime
25 | import picamera
26 | import linphone # From python
27 | import pygame # pygame is only used to play sound files (in OGG format). You could easily remove all of this if needed.
28 | from email.MIMEMultipart import MIMEMultipart
29 | from email.MIMEText import MIMEText
30 | from email.MIMEImage import MIMEImage
31 | import RPi.GPIO as GPIO
32 | import io
33 | import logging
34 | # import stun
35 | import serial
36 | import signal
37 | import time
38 | import smtplib
39 | import sys
40 |
41 | # Global variables:
42 |
43 | # This is only for additional trace messages, logging some info for the outgoing call, attempting external IP, etc.
44 | DEBUG = False
45 |
46 | # Constants
47 | thisDeviceName = "rpi01" # This is used for the email subject line. This should be unique per-device!
48 | # While this is *not* required, it will let you know at a glance which camera triggered
49 | # the email! :)
50 |
51 | PRODUCTNAME = 'JamPi' # You *must* change this in any commercial setting.
52 |
53 | # WAITSECONDS also controls the shortest amount of time between printing "Motion detected".
54 | WAITSECONDS = 60 # Set to zero to send a chat message every time motion is detected. (Not advised!)
55 |
56 | # How long to wait between sending emails. Independent of WAITSECONDS. I recommend 60 or more, but use what you want.
57 | WAITEMAILSECONDS = 60
58 |
59 | RECORDVIDEO = True # If you think you want a time lapse, please try a video first. Video is *less resource intensive*!
60 | # If True, a video of length (WAITEMAILSECONDS / 2) seconds will be recorded.
61 |
62 | # Set 'disableCameraLED = True' to turn off camera LED while recording
63 | disableCameraLED = not DEBUG # This leaves the camera LED *on* when DEBUG == True. Check it out! :-)
64 | # disableCameraLED = True
65 |
66 | # Video length: default = record for 3/4 the time up until next email-event.
67 | VIDEOLENTGH = ((WAITEMAILSECONDS / 4) * 3)
68 |
69 | RECORDTIMELAPSE = False
70 |
71 | camera = 'V4L2: /dev/video0' # This *should* pick up either the RaspiCam *or* a USB web cam. :)
72 | RESOLUTION = (1296, 972) # For high-latency networks, you probably want more like this. Max is (2592, 1944)
73 | VIDEORESOLUTION = (1920, 1080) # This is not transmitted, only stored on SD card/disk.
74 |
75 | # Possible values are:
76 | # 1080p=1920x1080, uxga=1600x1200, sxga=1280x960, 720p=1280x720, xga=1024x768,
77 | # svga=800x600, 4cif=704x576, vga=640x480, cif=352x288, qvga=320x240, qcif=176x144
78 |
79 | resolutions = \
80 | {
81 | '1080p': (1920, 1080),
82 | 'uxga': (1600, 1200),
83 | 'sxga': (1280, 960),
84 | '720p': (1280, 720),
85 | 'xga': (1024, 768),
86 | 'svga': (800, 600),
87 | '4cif': (704, 576),
88 | 'vga': (640, 480),
89 | 'cif': (352, 288),
90 | 'qvga': (320, 240),
91 | 'qcif': (176, 144)
92 | }
93 | # TODO: Implement resolution by VIDEOPHONE_RESOLUTION_BY_NAME
94 | VIDEOPHONE_RESOLUTION_BY_NAME = 'cif' # 'cif' is the default resolution. See above fore other possible choices.
95 |
96 | # Note that if you are NOT running as root, you should change this to '~/security_camera.log', or similar.
97 | LOGFILENAME = '/var/log/security_camera.log'
98 | SIPSERVER = 'sip:sip.linphone.org;transport=tls'
99 | AUTHORIZATIONDOMAN = 'sip.linphone.org'
100 |
101 | # Whether or not to save all images that are emailed to disk. (Or SD card, depending on the path you give.)
102 | SAVEEMAILEDIMAGES = True
103 | # Specify a unique mount-point here to save images to external/USB/network, etc.
104 | SAVEIMAGEDIR = '/home/pi/' # Leave this blank for current directory, else end with '/' char.
105 |
106 | # sipUserName = 'yourSipUsername'
107 | # sipPassword = 'yourSipPassword'
108 | sipUserName = 'yourEmailAddressFrom(UserNameOnly)'
109 | sipPassword = 'yoursippassword'
110 | # doorbellToAddress is the SIP (or URL) address that will be called when the "doorbell" is pressed.
111 | doorbellToAddress = 'sip:yourSipUsername@sip.linphone.org' # Who to "ring". SIP address format
112 | doorBellSoundWav = 'doorbell2.wav' # Sound for local "doorbell ring". Person pushing button hears this.
113 | doorBellSoundOgg = 'doorbell2.ogg' # Sound for local "doorbell ring". Person pushing button hears this.
114 |
115 | # sndCapture = 'ALSA: C-Media USB Headphone Set'
116 | sndCapture = 'ALSA: default device'
117 |
118 | # sndPlayback = 'snd_rpi_hifiberry_dac'
119 | sndPlayback = 'ALSA: default device'
120 | # Stuff that did NOT work for me:
121 | # 'sndrpihifiberry' # 'snd_rpi_hifiber' # 'snd_soc_hifiberry_dac' # alsa.driver_name from 'pacmd'
122 | # 'snd_rpi_hifiberry_dac' # = 'HifiBerry DAC HiFi pcm5102a-hifi-0' = 'snd_rpi_hifiberry_dac'
123 |
124 | # Now a set, not a list, which is mutable.
125 | whiteList = ('sip:yourSipUsername@sip.linphone.org',
126 | 'sip:aSecondSIPAccount@sip.linphone.org')
127 |
128 | emailFromAddress = 'yourEmailAddressFrom(UserNameOnly)'
129 | emailServer = 'smtp.gmail.com'
130 | emailPort = 587
131 |
132 | # Specify multiple destination email addresses by using this format:
133 | # emailAddressTo = ['youremail@example.com', 'anotheremail@example.com'] # Do it like this for several,
134 |
135 | # Specify a single destination email address like this:
136 | emailAddressTo = 'youremail@example.com' # or like this for just one recipient.
137 |
138 | emailAccount = 'emailaddressfrom@example.com' # Account to use for emailing
139 | emailPassword = 'youremailpassword' # Password for that account
140 | emailSubject = 'Motion detected' # Whatever you'd like in the subject line
141 | # Alternate text (which *you* may not see, but others will!):
142 | emailTextAlternate = 'Motion was detected. An image is included in the alternate MIME of this email.'
143 |
144 | # Note: This can be overridden! See a few lines down.
145 | detectMotion = True # Whether or not we have the motion detector SBC connected. True = connected.
146 | FLIPVERTICAL = False
147 | ACK = 6 # "Acknowledge" character
148 | NACK = 21 # "Non-Acknowledge" character
149 |
150 | # *** WARNING *** Do not change these unless you are SURE what you are doing! ***
151 | # *** Specifying an incorrect value for LEDPIN can *DAMAGE YOUR RASPBERRY PI*! ***
152 | # These pin numbers refer to the GPIO.BCM numbers.
153 | # The first two (LEDPIN and BUTTONPIN) can be set to zero to disable.
154 | LEDPIN = 17 # Motion-detected status - Blinking = motion detected. 0 = disable.
155 | BUTTONPIN = 18 # Button to trigger start outgoing call. 0 = disable. (E.g., for use as a "video doorbell". Thanks Toshi!)
156 |
157 | # You can--and it is recommended that YOU DO--add an additional LED by simply changing this:
158 | LEDPINDOORBELL = LEDPIN # If <> 0, this will stay on at all times, except blink when doorbell is rang.
159 | # If you use two LED's, then LEDPIN will be exclusively for motion-detection status.
160 | LEDDOORBELLBLINK = 2 # Time in seconds to blink. Blink will be ~4Hz, so at least 2 seconds is recommended.
161 |
162 | # Setting the next two to 0 (or False) will disable the motion sensor. *THIS OVERRIDES ABOVE SETTING*
163 | # MDPIN = 22 # Motion Detected pin (on ePIR). This can be any valid GPIO pin value, in BCM numbering scheme.
164 | MDPIN = 0 # Set this *AND* PIRPIN = 0 to use as a "video doorbell", only. You can do both, too, if you'd like.
165 | PIRPIN = 4 # Set this to zero to always use the MDPIN. That's for legacy ePIR devices, which you probably don't need.
166 |
167 | # Changes expected below this line. Careful changes. :)
168 | # Set GPIO for camera LED. Use 5 for Model A/B and 32 for Model B+.
169 | CAMLEDPIN = 32 # This is sporadic across models so I discourage using it.
170 |
171 | blinkCamLed = False
172 |
173 | talkToEm = True
174 | if talkToEm:
175 | from espeak import espeak
176 |
177 |
178 | # These can be changed, but beware of setting them too low because camera IO takes place during both
179 | # motion detection and sending email phases:
180 | # WARNING: THIS FUNCTION IS DEPRECATED
181 | # noinspection PyPep8Naming
182 | def readLineCR(port):
183 | s = ''
184 | while True:
185 | try:
186 | ch = port.read(1)
187 | s += ch
188 | if ch == '\r' or ch == '':
189 | break
190 | except: # TODO: Add specific exceptions here.
191 | # if traceback.print_exc() # Will be a "ready to read, but no data", in my experience.
192 | pass
193 | return s
194 |
195 |
196 | def setup_log_colors():
197 | logging.addLevelName(logging.DEBUG, '\033[1;37m%s\033[1;0m' % logging.getLevelName(logging.DEBUG))
198 | logging.addLevelName(logging.INFO, '\033[1;36m%s\033[1;0m' % logging.getLevelName(logging.INFO))
199 | logging.addLevelName(logging.WARNING, '\033[1;31m%s\033[1;0m' % logging.getLevelName(logging.WARNING))
200 | logging.addLevelName(logging.ERROR, '\033[1;41m%s\033[1;0m' % logging.getLevelName(logging.ERROR))
201 |
202 |
203 | def setup_log(log, trace):
204 | # if log is None:
205 | setup_log_colors()
206 | fmt = "%(asctime)s.%(msecs)03d %(levelname)s: %(message)s"
207 | datefmt = "%H:%M:%S"
208 | if trace:
209 | level = logging.DEBUG
210 | else:
211 | level = logging.INFO
212 | logging.basicConfig(filename=log, level=level, format=fmt, datefmt=datefmt)
213 |
214 |
215 | def log_handler(level, msg):
216 | method = getattr(logging, level)
217 | method(msg)
218 |
219 |
220 | class SecurityCamera:
221 | def __init__(self, username='', password='', whitelist=(), camera='', snd_capture='', snd_playback=''):
222 | # type: (string, string, list, string, string, string) -> object
223 | """
224 | Main class, params are what you'd expect based on the names.
225 | :param username:
226 | :param password:
227 | :param whitelist:
228 | :param camera:
229 | :param snd_capture:
230 | :param snd_playback:
231 | """
232 | logging.debug("__init__")
233 | logging.info(
234 | "Initializaing {product} System version {version}...".format(product=PRODUCTNAME, version=__version__))
235 | # logging.debug("setting audio_dscp")
236 | # Pulling my values from "Commonly used DSCP Values" table in this article:
237 | # https://en.wikipedia.org/wiki/Differentiated_services
238 | # self.core.audio_dscp = 26
239 | # self.core.video_dscp = 46 # 46 = High Priority Expedited Forwarding (EF) - TODO: Can this be lowered???
240 |
241 | self.lastMessageTicks = time.time() # Wait one "cycle" so everything gets initialized
242 | self.lastEmailTicks = time.time() # via the TCP/IP (UDP/TLS/DTLS).
243 |
244 | self.dirname = '/var/log/jampi'
245 | if not os.path.exists(self.dirname):
246 | os.makedirs(self.dirname)
247 |
248 | # Initialize email
249 | self.smtp = smtplib.SMTP()
250 |
251 | # Initialize the motion detector. This is for the Zilog ePIR ZDot SBC. It has more features via serial mode,
252 | # so that's what we'll use here.
253 | GPIO.setwarnings(False) # Disable "this channel already in use", etc.
254 | GPIO.setmode(GPIO.BCM)
255 | # GPIO.setup(CAMLEDPIN, GPIO.OUT, initial=False)
256 |
257 | self.port = serial.Serial("/dev/ttyAMA0", baudrate=9600, timeout=2)
258 |
259 | s = "Waiting for motion sensor to come online...."
260 | print s
261 | logging.debug(s)
262 | # time.sleep(10) # Arduino example says need delays between commands for proper operation. (I suspect for 9600 bps it needs time.)
263 | time.sleep(5)
264 |
265 | self.imageDir = os.getcwd()
266 | # self.imageDir = os.path.join(os.getcwd(), 'security-images')
267 | if not os.path.exists(self.imageDir):
268 | os.makedirs(self.imageDir)
269 |
270 | self.videoDir = os.getcwd()
271 | # self.videoDir = os.path.join(os.getcwd(), 'security-videos')
272 | if not os.path.exists(self.videoDir):
273 | os.makedirs(self.videoDir)
274 |
275 | if 0 != PIRPIN:
276 | # Assume newer PIR device, signal hooked to PIRPIN
277 | logging.info("Sensor online... Turning on motion sensor...")
278 | logging.debug('calling GPIO.setup(PIRPIN, ...)')
279 | GPIO.setup(PIRPIN, GPIO.IN, GPIO.PUD_DOWN)
280 | # GPIO.add_event_detect(PIRPIN, GPIO.RISING, self.motion_detected, bouncetime=2000) # add rising edge detection on a channel
281 | print "Waiting for it to stabilize..."
282 | time.sleep(5)
283 | # while GPIO.input(PIRPIN)==1:
284 | # Current_State = 0
285 | logging.info("PIR sensor is ready.")
286 | elif 0 != MDPIN:
287 | # let the ePIR sensor wake up.
288 | # time.sleep(10) # Arduino example says need delays between commands for proper operation. (I suspect for 9600 bps it needs time.)
289 | ch = 'U'
290 | while ch == 'U': # Repeat loop if not stablized. (ePIR replies with the character 'U' until the device becomes stable)
291 | # time.sleep(1)
292 | ch = self.port.read(
293 | 1) # Sends status command to ePIR and assigns reply from ePIR to variable ch. (READ ONLY function)
294 | logging.debug('ch = %s' % (ch,))
295 |
296 | ch = readLineCR(self.port)
297 | s = "ePIR"
298 | if PIRPIN:
299 | s = "PIR"
300 | time.sleep(1)
301 | # print "%s sensor device online..." % (s, )
302 |
303 | self.port.write('CM')
304 |
305 | # If we don't do this, the next readLineCR() will get garbage and will take an undetermined amount of time!
306 | time.sleep(1)
307 | result = readLineCR(self.port)
308 |
309 | if len(result) > 1: result = result[-1]
310 |
311 | if result == 'R':
312 | print 'ePIR reset!'
313 | elif result == 'M' or result == 'N':
314 | print 'Motion detection mode confirmed.'
315 | else:
316 | logging.debug('Result = "%s"' % (result,))
317 |
318 | logging.debug("ch = '%s'\r\nDevice Ready" % (ch,))
319 | logging.info("\nePIR sensor ready.")
320 |
321 | if 0 != LEDPIN:
322 | GPIO.setup(LEDPIN, GPIO.OUT) # This light blinks when motion detected. Connect with long wires to monitor!
323 | # If the video and chat are not enough notifications. :)
324 | if 0 != LEDPINDOORBELL:
325 | GPIO.setup(LEDPINDOORBELL, GPIO.OUT)
326 | GPIO.output(LEDPINDOORBELL, 1) # Keep this LED ON.
327 |
328 | if 0 != MDPIN:
329 | GPIO.setup(MDPIN, GPIO.IN)
330 |
331 | self.doorbell_sound = None
332 | if 0 != BUTTONPIN:
333 | GPIO.setup(BUTTONPIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
334 |
335 | # try: # Background music is loaded here.
336 | # #pygame.mixer.pre_init(48000, -16, 2, 4096)
337 | # pygame.init()
338 | # pygame.mixer.init(44100, -16, 2, 2048)
339 | # self.doorbell_sound = pygame.mixer.Sound(doorBellSoundOgg)
340 | # except pygame.error, message:
341 | # logging.error("Cannot load doorbell sound: {doorbellsound}\r\n{exception}".format(
342 | # doorbellsound=doorBellSoundOgg, exception=traceback.format_exc()))
343 |
344 | global blinkCamLed
345 | val1 = blinkCamLed # Only time we force a blinking LED is during initialization, so you know it's ready.
346 | blinkCamLed = True
347 | if LEDPINDOORBELL:
348 | self.flash_led(ledpin=LEDPINDOORBELL)
349 | else:
350 | self.flash_led()
351 | blinkCamLed = val1
352 |
353 | # Other member variables:
354 | self.imgStream = io.BytesIO()
355 | # time.sleep(2) #Allow time for ePIR warming-up
356 |
357 | self.quit = False
358 | self.username = ""
359 | self.current_call = None
360 | self.whitelist = whitelist
361 | callbacks = {
362 | 'call_state_changed': self.call_state_changed,
363 | }
364 |
365 | # Initialize & Configure the linphone core
366 | logging.basicConfig(level=logging.INFO)
367 | signal.signal(signal.SIGINT, self.signal_handler)
368 | linphone.set_log_handler(log_handler)
369 | self.core = linphone.Core.new(callbacks, None, None)
370 | self.core.max_calls = 3
371 | self.core.video_adaptive_jittcomp_enabled = False
372 | self.core.adaptive_rate_control_enabled = False
373 | # self.core.quality_reporting_enabled = False # This fails straight away.
374 | self.core.echo_cancellation_enabled = False
375 | self.core.video_capture_enabled = True
376 | self.core.video_display_enabled = False # This will only show up on a composite/HDMI monitor.
377 | self.core.keep_alive_enabled = True # This is the default at time of writing.
378 |
379 | self.core.mic_enabled = True
380 | self.core.ringback = doorBellSoundWav # This causes terrible distortion on my system
381 | # self.core.ring = doorBellSoundWav # TODO: Use a separate sound for this.
382 |
383 | tr = self.core.sip_transports
384 | # assert_equals(tr.udp_port, 5060) # default config
385 | # assert_equals(tr.tcp_port, 5060) # default config
386 | tr.udp_port = 5063
387 | tr.tcp_port = 5067
388 | tr.tls_port = 32737
389 | tr.dtls_port = 32738
390 | self.core.sip_transports = tr
391 | tr = self.core.sip_transports
392 | logging.debug('Transports = UDP: %s, TCP %s, TLS %s, DTLS %s' % \
393 | (tr.udp_port, tr.tcp_port, tr.tls_port, tr.dtls_port))
394 |
395 | # tr = self.core.sip_transports
396 | # tr.dtls_port = 5060
397 | # tr.tcp_port = 5061
398 | # tr.udp_port = 5062
399 | # tr.tls_port = 5063
400 | # self.core.sip_transports = tr
401 | self.core.stun_server = 'stun.linphone.org'
402 | self.core.firewall_policy = linphone.FirewallPolicy.PolicyUseIce
403 | if len(camera):
404 | self.core.video_device = camera
405 | if len(snd_capture):
406 | self.core.capture_device = snd_capture
407 | if len(snd_playback):
408 | self.core.playback_device = snd_playback
409 |
410 | # Only enable PCMU, PCMA and speex audio codecs
411 | for codec in self.core.audio_codecs:
412 | if codec.mime_type in ["PCMA",
413 | "PCMU"]: # [, "speex", "opus", "VP8", "H264", "opus", "VP8", "H264"]: # Overkill! , "SILK"
414 | self.core.enable_payload_type(codec, True)
415 | logging.info("Adding codec %s..." % (codec.mime_type,))
416 | else:
417 | self.core.enable_payload_type(codec, False)
418 |
419 | # Only enable VP8 video codecs
420 | for codec in self.core.video_codecs:
421 | if codec.mime_type in ["VP8"]:
422 | logging.info("Adding codec %s..." % (codec.mime_type,))
423 | self.core.enable_payload_type(codec, True)
424 | else:
425 | self.core.enable_payload_type(codec, False)
426 |
427 | logging.debug("Configuring SIP account...")
428 | self.configure_sip_account(username, password)
429 |
430 | if talkToEm:
431 | espeak.synth('Security system is now on line.')
432 | time.sleep(3)
433 | self.configured = False
434 |
435 | def captureImage(self):
436 | # Create an in-memory stream
437 | with picamera.PiCamera(sensor_mode=4) as camera: # A hack! Pin 0 causes it to not light at all.
438 | camera.led = False
439 | if FLIPVERTICAL:
440 | camera.vflip = True
441 | camera.hflip = True # No separate option, just flip both directions.
442 | # Native mode: 2592 x 1944
443 | camera.resolution = RESOLUTION
444 | camera.start_preview()
445 | # camera.framerate = 30
446 | # camera.annotate_text = "You are SO BUSTED! This image has ALREADY been emailed to security!" # Fun! :-)
447 | time.sleep(2) # Wait for the automatic gain control to settle
448 | # Now fix the values
449 | camera.shutter_speed = camera.exposure_speed
450 | camera.exposure_mode = 'off'
451 | g = camera.awb_gains
452 | camera.awb_mode = 'off'
453 | camera.awb_gains = g
454 | # Finally, take several photos with the fixed settings
455 |
456 | # Camera warm-up time
457 | # fname = '%s' + str(datetime.datetime.now())
458 | self.imgStream.seek(0)
459 | camera.capture(self.imgStream, 'jpeg', use_video_port=True)
460 | try:
461 | outname = 'security_camera_image-{timestamp}.jpg'.format(timestamp=datetime.datetime.now().isoformat())
462 | outf = file(outname, 'wb')
463 | self.imgStream.seek(0)
464 | outf.write(self.imgStream.read())
465 | except:
466 | logging.error(traceback.format_exc())
467 | camera.stop_preview()
468 | return self.imgStream
469 |
470 | def emailImage(self):
471 | logging.debug("emailImage() called")
472 | self.captureImage()
473 |
474 | # if RECORDVIDEO or RECORDTIMELAPSE:
475 | # return
476 |
477 | # Create the root message and fill in the from, to, and subject headers
478 | msgRoot = MIMEMultipart('related')
479 | msgRoot['Subject'] = 'Motion detected'
480 | msgRoot['From'] = emailFromAddress
481 | msgRoot['To'] = 'Security Team for camera ' + thisDeviceName # emailAddressTo
482 | msgRoot.preamble = 'This is a multi-part message in MIME format.'
483 |
484 | # Encapsulate the plain and HTML versions of the message body in an
485 | # 'alternative' part, so message agents can decide which they want to display.
486 | msgAlternative = MIMEMultipart('alternative')
487 | msgRoot.attach(msgAlternative)
488 |
489 | msgText = MIMEText(emailTextAlternate)
490 | msgAlternative.attach(msgText)
491 |
492 | # We reference the image in the IMG SRC attribute by the ID we give it below
493 | msgText = MIMEText(
494 | 'Alert motion detected on camera %s and here is a picture of what/whomever triggered this alert! ^^^ The Culprit ^^^' % (
495 | thisDeviceName), 'html')
496 | msgAlternative.attach(msgText)
497 |
498 | self.imgStream.seek(0L)
499 | msgImage = MIMEImage(self.imgStream.read())
500 |
501 | # Define the image's ID as referenced above
502 | msgImage.add_header('Content-ID', '')
503 | msgRoot.attach(msgImage)
504 |
505 | # Send the email (this example assumes SMTP authentication is required)
506 | self.smtp.connect(emailServer, emailPort)
507 | self.smtp.starttls() # TODO: Can this be moved out of the iteration? Is it expensive?
508 | self.smtp.login(emailAccount, emailPassword)
509 | self.smtp.sendmail(emailFromAddress, emailAddressTo, msgRoot.as_string())
510 | self.smtp.quit() # TODO: Determine if this can be elsewhere, or if we can leave this open.
511 | self.lastEmailTicks = time.time()
512 | if SAVEEMAILEDIMAGES:
513 | fname = '{saveimagedir}emailedSecurityCameraImage-{date}.jpg'.format(
514 | saveimagedir=SAVEIMAGEDIR,
515 | date=datetime.datetime.now().isoformat()
516 | )
517 | outf = file(fname, 'wb')
518 | self.imgStream.seek(0L)
519 | outf.write(self.imgStream.read())
520 | outf.close()
521 | logging.info('Emailed and saved image to "{fname}"'.format(fname=fname))
522 | else:
523 | logging.info('Emailed image at {currenttime}'.format(currenttime=datetime.datetime.now().isoformat()))
524 |
525 | def signal_handler(self, signal, frame):
526 | self.core.terminate_all_calls()
527 | self.quit = True
528 |
529 | def call_state_changed(self, core, call, state, message):
530 | body = "State: {state}".format(state=linphone.CallState.string(state))
531 | if state == linphone.CallState.IncomingReceived:
532 | if call.remote_address.as_string_uri_only() in self.whitelist:
533 | params = core.create_call_params(call)
534 | params.audio_enabled = True
535 | params.audio_multicast_enabled = False
536 | params.video_multicast_enabled = False
537 | logging.debug("Call params:\r\n" "%s" % (str(params),))
538 | core.accept_call_with_params(call, params)
539 | # call.microphone_volume_gain = 0.98 # Maximum value, I believe....
540 | self.current_call = call
541 | logging.debug('sip_transports: dtls = %d, tcp = %d, udp = %d, tls = %d' % \
542 | (self.core.sip_transports.dtls_port, self.core.sip_transports.tcp_port,
543 | self.core.sip_transports.udp_port, self.core.sip_transports.tls_port))
544 | logging.debug('sip_transports_used: dtls = %d, tcp = %d, udp = %d, tls = %d' % \
545 | (self.core.sip_transports_used.dtls_port, self.core.sip_transports_used.tcp_port,
546 | self.core.sip_transports_used.udp_port, self.core.sip_transports_used.tls_port))
547 | if talkToEm:
548 | espeak.synth('Incoming call answered. You are being watched!')
549 | time.sleep(2)
550 | else:
551 | core.decline_call(call, linphone.Reason.Declined)
552 | for contact in self.whitelist:
553 | chat_room = core.get_chat_room_from_uri(contact)
554 | if not chat_room:
555 | continue
556 | msg = chat_room.create_message(call.remote_address_as_string + ' tried to call')
557 | chat_room.send_chat_message(msg)
558 | elif state == linphone.CallState.CallOutgoingInit \
559 | or state == linphone.CallState.CallOutgoingProgress \
560 | or state == linphone.CallState.CallOutgoingRinging:
561 | logging.info("\nCall remote: {address}".format(address=call.remote_address.as_string()))
562 | body += "\nFrom: {address}".format(address=call.remote_address.as_string())
563 |
564 | elif state == linphone.CallState.End:
565 | logging.info("Call ended normally.")
566 | self.current_call = None
567 | elif state == linphone.CallState.Error:
568 | logging.error("Error ... ending call!")
569 | # core.end_call(call)
570 | self.current_call = None
571 | else:
572 | logging.debug("call_state_changed() state = %s" % (linphone.CallState.string(state),))
573 | logging.info(body)
574 |
575 | def configure_sip_account(self, username, password):
576 | # Configure the SIP account
577 | proxy_cfg = self.core.create_proxy_config()
578 | proxy_cfg.identity_address = self.core.create_address(
579 | # 'sip:{username}@{authdomain}:5061'.format(username=username, authdomain=AUTHORIZATIONDOMAN))
580 | 'sip:{username}@{authdomain}:5061'.format(username=username, authdomain=AUTHORIZATIONDOMAN))
581 | # proxy_cfg.server_addr = SIPSERVER
582 | proxy_cfg.server_addr = 'sip:' + AUTHORIZATIONDOMAN
583 | proxy_cfg.register_enabled = True
584 | self.core.add_proxy_config(proxy_cfg)
585 | self.core.default_proxy_config = proxy_cfg
586 | # auth_info = self.core.create_auth_info(username, None, password, None, None, 'sip.linphone.org')
587 | auth_info = self.core.create_auth_info(username, None, password, None, None, None)
588 | self.core.add_auth_info(auth_info)
589 | logging.info('auth_info.username = {username}, '.format(username=username, authdomain=AUTHORIZATIONDOMAN))
590 | self.username = username
591 |
592 | def run(self):
593 | while not self.quit:
594 | button_pressed = False
595 | if 0 != BUTTONPIN:
596 | button_pressed = not GPIO.input(BUTTONPIN)
597 | if button_pressed and self.core.current_call is None:
598 | # We do not check the time here. They can keep "ringing" the doorbell if they want
599 | # but it won't matter once a call is initiated.
600 | if self.doorbell_sound:
601 | self.doorbell_sound.play()
602 | try:
603 | if time.time() - self.lastMessageTicks > WAITSECONDS:
604 | self.notify_chat_contacts(message_template="Doorbell ring on %s at %s")
605 | params = self.core.create_call_params(None)
606 | params.audio_enabled = True
607 | params.video_enabled = True
608 | params.audio_multicast_enabled = False # Set these = True if you want multiple
609 | params.video_multicast_enabled = False # people to connect at once.
610 | address = linphone.Address.new(doorbellToAddress)
611 | logging.info('address = {address}, used_video_codec = {codec}'.format(
612 | address=address,
613 | codec=params.used_video_codec))
614 | self.current_call = self.core.invite_address_with_params(address, params)
615 | if None is self.current_call:
616 | logging.error("Error creating call and inviting with params... outgoing call aborted.")
617 | if time.time() - self.lastEmailTicks >= WAITEMAILSECONDS:
618 | if LEDPINDOORBELL:
619 | self.flash_led(ledpin=LEDPINDOORBELL, stay_on=True,
620 | blink_cam_led=False, delay=0.25, blink_count=8)
621 | else:
622 | self.flash_led()
623 | self.notify_email_contacts()
624 | except KeyboardInterrupt:
625 | self.quit = True
626 | break
627 | elif detectMotion and self.core.current_call is None:
628 | motion_detected = False
629 | # Incoming calls have been handled, so check the motion detector:
630 | if 0 != PIRPIN:
631 | # motion_detected = GPIO.wait_for_edge(PIRPIN,GPIO.RISING)
632 | # motion_detected = GPIO.event_detected(PIRPIN)
633 | motion_detected = GPIO.input(PIRPIN)
634 | logging.debug("\rmotion_detected = %s, GPIO.input(PIRPIN) = %s" % (
635 | str(motion_detected), str(GPIO.input(PIRPIN))))
636 | elif 0 != MDPIN:
637 | motion_detected = GPIO.input(MDPIN) == 0
638 |
639 | if motion_detected:
640 | self.motion_detected()
641 | # else:
642 | # time.sleep(0.01) #
643 | self.core.iterate()
644 |
645 | def motion_detected(self):
646 | t = time.time()
647 | # logging.info('*Motion detected!*') # This may be useful when adjusting potentiometers on PIR sensor.
648 | if t - self.lastMessageTicks < WAITSECONDS and t - self.lastEmailTicks < WAITEMAILSECONDS:
649 | return
650 | logging.info('*Motion detected!*')
651 | if DEBUG:
652 | logging.debug('sip_transports: dtls = %d, tcp = %d, udp = %d, tls = %d' % \
653 | (self.core.sip_transports.dtls_port, self.core.sip_transports.tcp_port, \
654 | self.core.sip_transports.udp_port, self.core.sip_transports.tls_port))
655 | logging.debug('sip_transports_used: dtls = %d, tcp = %d, udp = %d, tls = %d' % \
656 | (self.core.sip_transports_used.dtls_port, self.core.sip_transports_used.tcp_port, \
657 | self.core.sip_transports_used.udp_port, self.core.sip_transports_used.tls_port))
658 | logging.debug('self.core.upnp_external_ipaddress = %s' % (self.core.upnp_external_ipaddress,))
659 | logging.debug('self.core.nat_address = %s' % (self.core.nat_address,))
660 | tr = self.core.sip_transports_used
661 | logging.debug(
662 | 'self.core.linphone_core_get_sip_transports_used = dtls = %d, tcp = %d, udp = %d, tls = %d' % \
663 | (tr.dtls_port, tr.tcp_port, tr.udp_port, tr.tls_port))
664 |
665 | if time.time() - self.lastMessageTicks > WAITSECONDS:
666 | self.notify_chat_contacts() # Note that times for message and email are independent.
667 | if time.time() - self.lastEmailTicks >= WAITEMAILSECONDS:
668 | try:
669 | self.notify_email_contacts()
670 | self.record_video_event()
671 | except KeyboardInterrupt:
672 | self.quit = True
673 | self.flash_led()
674 |
675 | def record_video_event(self):
676 | if RECORDVIDEO:
677 | with picamera.PiCamera(sensor_mode=1) as camera:
678 | if disableCameraLED:
679 | camera.led = False # The LED will still blink
680 | # What resolution for video is best??? This is full 1080p
681 | # On the Pi, the full resolution of the sensor is (2592 x 1944), and there's a 1080p video mode (1920 x 1080).
682 | # (1296, 972) # Had this
683 | camera.resolution = VIDEORESOLUTION
684 | camera.start_recording(os.path.join(self.videoDir, 'security_camera-%s.h264' % (
685 | datetime.datetime.now().isoformat(' ')), ))
686 | camera.wait_recording(VIDEOLENTGH)
687 | camera.stop_recording()
688 | elif RECORDTIMELAPSE:
689 | with picamera.PiCamera(sensor_mode=4) as camera: # 1296 x 972, 4:3
690 | if disableCameraLED:
691 | camera.led = False
692 | camera.resolution = (1296, 972)
693 | logging.debug(('\rtime.time() - self.lastEmailTicks = %s, WAITEMAILSECONDS/2 = %d' % \
694 | (time.time() - self.lastEmailTicks, WAITEMAILSECONDS / 2)))
695 | while time.time() - self.lastEmailTicks <= WAITEMAILSECONDS / 2:
696 | for filename in camera.capture_continuous(os.path.join(self.imageDir,
697 | 'img-%s{counter:03d}.jpg' % (
698 | datetime.datetime.now().isoformat(
699 | ' '),))):
700 | self.core.iterate()
701 | logging.info('Captured %s' % filename)
702 | time.sleep(0.2) # wait 5 minutes
703 |
704 | def notify_email_contacts(self):
705 | self.lastEmailTicks = time.time()
706 | self.emailImage()
707 | if talkToEm:
708 | # Note we *tell them* we're going to record a video, even if we're not. Sneaky! :)
709 | espeak.synth('An image has just been emailed to security.')
710 | time.sleep(2)
711 | espeak.synth('A video is being recorded of you, even as we speak.')
712 | time.sleep(2)
713 |
714 | def notify_chat_contacts(self, message_template="'Motion detected on %s at %s'"):
715 | logging.info("Notifying contacts...")
716 | for contact in self.whitelist:
717 | logging.info("Notifying %s" % (contact,))
718 | chat_room = self.core.get_chat_room_from_uri(contact)
719 | if not chat_room:
720 | continue
721 | dt = datetime.datetime.now()
722 | c = self.core.primary_contact_parsed
723 | ip = self.core.upnp_external_ipaddress
724 | ea = c.as_string()
725 | un = c.username
726 | cl = c.clean()
727 | # po = c.port
728 | logging.debug("c.port = {p}".format(p=c.port))
729 | po = 5061
730 | logging.debug("dir(c) = {d}".format(d=dir(c)))
731 | logging.info("c.display_name = {cdn}, c.username = {cun}, c.port = {cp}".format(
732 | cdn=c.display_name, cun=c.username, cp=c.port))
733 | logging.info("po = {po}, cl = {cl}, c = {c}, ip = {ip}, ea = {ea}, un = {un}".format(
734 | po=po, cl=cl, c=c, ip=ip, ea=ea, un=un))
735 | # nattype, external_ip, external_port = stun.get_ip_info('0.0.0.0', 54320, self.core.stun_server, 3478)
736 | # logging.debug('nattype = %s, external_ip = %s, external_port = %s' % (nattype, external_ip, external_port))
737 | # sipaddress = 'sip:yourSipUsername@%s:%s' %(external_ip, external_port)
738 | # logging.debug('sipaddress = %s' % (sipaddress, ))
739 | msg = chat_room.create_message(message_template % (ea, dt.isoformat(' ')))
740 | chat_room.send_chat_message(msg)
741 | self.lastMessageTicks = time.time()
742 | self.core.iterate()
743 |
744 | def flash_led(self, ledpin=LEDPIN, stay_on=False, blink_cam_led=blinkCamLed, delay=0.05, blink_count=10):
745 | if ledpin == LEDPINDOORBELL and not stay_on: # Goof-proof? :)
746 | logging.debug('Recursion for flash_led()...')
747 | return self.flash_led(ledpin=LEDPINDOORBELL, stay_on=True, blink_cam_led=False, delay=0.125,
748 | blink_count=int(1 / delay) * LEDDOORBELLBLINK)
749 | for j in range(0, blink_count):
750 | if ledpin:
751 | GPIO.output(ledpin, True)
752 | if blink_cam_led:
753 | GPIO.output(CAMLEDPIN, True)
754 | time.sleep(delay)
755 | if ledpin:
756 | GPIO.output(ledpin, False)
757 | if blink_cam_led:
758 | GPIO.output(CAMLEDPIN, False)
759 | time.sleep(delay)
760 | if stay_on and ledpin:
761 | GPIO.output(ledpin, True)
762 |
763 |
764 | def main(argc, argv):
765 | error = None
766 | try:
767 | setup_log(LOGFILENAME, trace=DEBUG)
768 | # These are your SIP username and password and system devices
769 | cam = SecurityCamera(username=sipUserName, password=sipPassword, whitelist=whiteList, camera=camera,
770 | snd_capture=sndCapture, snd_playback=sndPlayback)
771 | cam.run()
772 | except:
773 | traceback.print_exc() # Print the error
774 | error = traceback.format_exc() # and format to log it (if possible)
775 | finally:
776 | GPIO.cleanup()
777 | if error:
778 | logging.error(error)
779 |
780 |
781 | if __name__ == '__main__':
782 | main(len(sys.argv), sys.argv)
783 |
--------------------------------------------------------------------------------
/security_off:
--------------------------------------------------------------------------------
1 | sudo kill -s KILL $(ps aux | grep 'sudo python security_camera.py' | awk -v i=1 -v j=2 'FNR == i {print $j}')
2 | sudo kill -s KILL $(ps aux | grep 'python security_camera.py' | awk -v i=1 -v j=2 'FNR == i {print $j}')
3 | cp /var/log/security_camera.log ./
4 |
5 |
--------------------------------------------------------------------------------