Security Cameras

From i3Detroit
Jump to: navigation, search
Current camera locations

Rules For Use

Security cameras are governed by i3Detroit's Security Camera policy and exist for the safety and prosperity of the space. Use of security cameras was approved originally in 2012. The policy was updated in 2015 to be included in the standing rules. The discussion of the 2015 proposal happened largely in Slack, in the archived channel #camera-policy. The standing rules section was modified in early 2016 to replace the 4-person viewing team with the Board of Directors. It was again modified in early 2017 to expand the viewing team to consist of at least one director and either one other director, officer, or zone coordinator. The full rules:

  1. The Security Camera Policy is the business of the General Membership and as such will be made and amended by a majority vote of the General Membership.
  2. The Technical Point of Contact for the security camera system will be the Infrastructure Zone Coordinator or their designee, who will handle
    • Requests to view real-time camera streaming
    • Complaints/suggestions about the security camera system at i3Detroit
    • Requests to reposition cameras
    • Other issues of technical implementation
  3. Storage of Footage
    • The past 60 days of footage shall be stored.
    • Specific footage may be saved for an indefinite period of time as needed for investigation purposes.
  4. Camera Location and Positioning
    • The current approved list of camera locations is available on the wiki.
    • A camera location can be added or removed from the current list by a majority vote of the General Membership.
    • Zone Coordinators may make minor adjustments of the viewing angle of cameras in their zone.
  5. Real-Time Camera Access
    • Any member may, under the supervision of a board member/officer/zone coordinator, view real-time security camera footage when at i3 Detroit for the purpose of confirming camera views.
    • The infrastructure Zone Coordinator or delegate may view real-time security camera footage for maintenance purposes when at i3Detroit.
    • No other active monitoring of the camera system is permitted.
  6. Request to View Historical Footage
    • A member or members may request to view historical video by contacting the Board.
    • Footage may be viewed when approved by and accompanied by one member of the board of directors and one member of either the Board of Directors, Corporate Officers or Zone Coordinators.
    • Information about an approved request will be posted to the mailing list by the Board, said information to include the requesting member(s) and the specifics of which footage was requested.
    • Non-video or image information from the viewing will be distributed to appropriate members as deemed necessary by the requesting member(s) only.
  7. Other Rules:
    • Off site viewing of the footage of the camera system is not permitted.
    • No other unattended cameras may be installed or operated.
  8. There is no special physical security measures of the security camera system required by this policy.

Camera Locations

List of Current Cameras

Location View (not live)
Classroom Classroomcam.jpg
Craft Room Craftroomcam.jpg
CNC Shop CNCcam.jpg
Commons Area Commonscam.jpg
Commons Area & Snack Zone CommonsNE.jpg
Electronics Lab Elabcam.png
Fab Lab FabLabCam.jpg
Front & Garage Doors Entrancecam.PNG
Front Door Exterior Exteriorcam.jpg
Laser Zone Lasercam.jpg
Machine Shop 1 MachineShopCam1.JPG
Machine Shop 2 Machineshopcam2.jpg
Media Lab Medialabcam.jpg
Suite B Rear Doors Westsideoffice.jpg
Suite B Rear Doors Westsiderear.jpg
Suite B Shop Doors Westsideshopdoors.jpg
Tool Crib ToolCribCam.jpg
Welding Zone Weldingcam.PNG
West Shop (Bike, Vinyl, & Jewelry zones Westshopcam.jpg
Wood Shop North WoodShopNorthCam.jpg
Wood Shop South & Tool Crib benches Woodshopsouthcam.jpg

History of Camera Location Approval

Status Report from Project Lead


  • 18 of 18 approved planned cameras have been installed. See below for technical details.
  • (10/19/17) System is in working order and has been for several months now.
  • (6/9/2018) System still working well, no major issues. 15/18 cameras have 240+ days continuous uptime.

To Do

Task Name Description Priority Champion
Reconfigure Network for camera VLAN Move all the cameras over to their own VLAN separate from the main public network. 3
Install GPU in VMServer Install an Nvidia 6xx or newer GPU to enable GPU accelerated h.264 encoding for archived video. 5

Technical Details of Zoneminder & IP Camera System

  • Zoneminder v1.32.1 running on VMServer
  • 1x 4TB Western Digital Purple Surveilance, 1x 4TB Western Digital Red hard disks installed in the VMServer
  • 18 wired PoE IP cameras
    • 13xHikvision 4.1MP DS-2CD2042WD-I 4mm IP Camera
    • 5xHikvision DS-2CD2432F-IW 3MP Indoor 2.8mm IP Camera

Maintenance Scripts


Zoneminder currently stores all data as jpeg frames. This is nice and simple, but uses 10 times as much disk space as the data does once encoded as h264 video. This script is set to run daily to archive the previous day as h264 video, one file per 1-hour period per camera. It also makes sure back-ups were done on previous days.


# get a list of active monitor numbers from zoneminder
# I have no idea how this works but it does
camrange=$(zmu -U USERNAME -P PASSWORD -l | awk '{ print $1}' | sed 's/Id//g' | sed ':a;N;$!ba;s/\n/ /g')

# associate plaintext names with monitor numbers for humans
for i in {0..25}; do cameralist[$i]="undefined"; done

# get an offset date value - offset must be >= 1 unless libtimetravel is installed
year=$(date +"%Y" -d "-${offset}days")
month=$(date +"%m" -d "-${offset}days")
day=$(date +"%d" -d "-${offset}days")
shortyear=$(date +"%y" -d "-${offset}days")
nextday=$(date +"%d" -d "-${nextdayoffset}days")
nextdaymonth=$(date +"%m" -d "-${nextdayoffset}days")
nextdayyear=$(date +"%Y" -d "-${nextdayoffset}days")


# directories to put stuff
mkdir $ArchiveDir/$year
mkdir $ArchiveDir/$year/$month
mkdir $ArchiveDir/$year/$month/$day

#backup the SQL database info for the day's events
mysql --user=redacted --password=zmpass --database=zm -e "select * from Events where StartTime >= '$year-$month-$day 00:00:00' AND StartTime < '$nextdayyear-$nextdaymonth-$nextday 00:00:00';" > /mnt/cameraArchive/metadata/$year-$month-$day-metadata.txt

# Doing the backups
for cam in $camrange
	mkdir $ArchiveDir/$year/$month/$day/$cam-${cameralist[$cam]}
	mkdir $ArchiveDir/$year/$month/$day/$cam-${cameralist[$cam]}/logs
	for hour in {00..23}
		# Write a list of frames to a file, in a format to pass to ffmpeg
		for f in /var/cache/zoneminder/events/$cam/$shortyear/$month/$day/$hour/*/*/*.jpg
			echo "file '$f'" >> $ArchiveDir/$year/$month/$day/$cam-${cameralist[$cam]}/logs/${hour}00.log
		# Do the actual conversion - throw every jpeg in the whole hour (in the range X0000-X9999) into one video file
		# Currently have no fancy output parameters. -r 25 sets 25fps, which is totally arbitrary and doesn't really matter.
		# Most monitors are recording at 4 or 6 fps, so this just speeds up playback/review.
		# ffmpeg was self-compiled due to Debian's ffmpeg/libav debacle
		echo "Processing $year $month $day - Hour $hour - $cam ${cameralist[$cam]}"
		/home/mkfink/bin/ffmpeg -f concat -safe 0 -i $ArchiveDir/$year/$month/$day/$cam-${cameralist[$cam]}/logs/${hour}00.log -vf scale=iw*.75:ih*.75 -pix_fmt yuv420p -map 0 $ArchiveDir/$year/$month/$day/${cam}-${cameralist[$cam]}/${cam}-${cameralist[$cam]}-$year$month${day}_${hour}00.mkv
chown -R www-data:www-data $ArchiveDir/$year/$month/$day

# Create a log file indicating the backup has finished
# and throw a note into slack that the job finished
pretext="Archiving complete for "
posttext=". Size of day encoded is "
daysize=$(du -hs $ArchiveDir/$year/$month/$day | awk '{print $1}')
zmsize=$(du -hsc /var/cache/zoneminder/events/*/$shortyear/$month/$day/ | tail -n1)
postposttext=" from ZM data size "
# make the log file
echo $text > $ArchiveDir/$year/$month/$day/backupcomplete.log
# send to slack
escapedText=$(echo $text | sed 's/"/\"/g' | sed "s/'/\'/g" )
json="{\"channel\": \"$channel\", \"text\": \"$escapedText\"}"
curl -s -d "payload=$json" "$webhook_url"

# check for missing backups
while [ $dayexists == 0 ]
	testyear=$(date +"%Y" -d "-${testoffset}days")
	testmonth=$(date +"%m" -d "-${testoffset}days")
	testday=$(date +"%d" -d "-${testoffset}days")
	if [ -f "$ArchiveDir/$testyear/$testmonth/$testday/backupcomplete.log" ]
		echo "true"
		echo $dayexists$testyear$testmonth$testday

# complain about missing backups
if [ $missingdays -gt 0 ]
	pretext2="Alert! Backups are incomplete or missing for "
	posttext2=" days. Please manually start backups."
	escapedText=$(echo $text2 | sed 's/"/\"/g' | sed "s/'/\'/g" )
	json="{\"channel\": \"$channel\", \"text\": \"$escapedText\"}"
	curl -s -d "payload=$json" "$webhook_url"


Sometimes cameras get disconnected and zoneminder shows a blank blue 'no signal' image. This runs a few times a day, checking to see if any cameras are doing this.


# get a list of active monitor numbers from zoneminder
range=$(zmu -U USERNAME -P PASSWORD -l | awk '{ print $1}' | sed 's/Id//g' | sed ':a;N;$!ba;s/\n/ /g')

for x in $range
	#echo $x
	zmu -U USERNAME -P PASSWORD -i -m $x
	redval=$(convert Monitor$x.jpg -scale 1x1\! -format '%[fx:int(255*r+.5)]' info:-)
	greenval=$(convert Monitor$x.jpg -scale 1x1\! -format '%[fx:int(255*g+.5)]' info:-)
	blueval=$(convert Monitor$x.jpg -scale 1x1\! -format '%[fx:int(255*b+.5)]' info:-)
	if [ $blueval -gt 190 ] && [ $redval -lt 4 ] && [ $greenval -lt 4 ]
		#Run a script to complain about this in Slack
		/home/mkfink/camscripts/ $x
	rm Monitor$x.jpg

Zoneminder does a good job policing its own disk use (as long as it's not generating data faster than it deletes it, which happened once. Oops.) but just to make sure, and to keep track of the h264 archiviving, this reports disk use to slack every day.

#get current disk usage percent as a number

CURRENT=$(df /mnt/cameraCurrent | grep / | awk '{ print $5}' | sed 's/%//g')
ARCHIVE=$(df /mnt/cameraArchive | grep / | awk '{ print $5}' | sed 's/%//g')



pretext="Active camera disk usage is "
posttext="%. "
pretext2="Archive disk usage is "

escapedText=$(echo $text | sed 's/"/\"/g' | sed "s/'/\'/g" )
json="{\"channel\": \"$channel\", \"text\": \"$escapedText\"}"

curl -s -d "payload=$json" "$webhook_url"