├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── howmanypeoplearearound ├── __init__.py ├── __main__.py ├── analysis.py ├── colors.py ├── oui.py └── plotlyjs.py └── setup.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: schollz 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | index.html 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | .vscode 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | 93 | # Pycharm 94 | .idea 95 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | language: python 4 | cache: pip 5 | python: 6 | - 2.7.13 7 | - 3.6 8 | install: 9 | - pip install --upgrade pip setuptools 10 | - pip install flake8 11 | before_script: 12 | flake8 . --count --exclude=./howmanypeoplearearound/plotlyjs.py --exit-zero --max-line-length=127 --statistics # 127 == GitHub editor width 13 | script: 14 | flake8 . --exclude=./howmanypeoplearearound/plotlyjs.py --select=E999,F821,F822,F823 --statistics # Stop build on syntax errors 15 | notifications: 16 | on_success: change 17 | on_failure: always 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for howmanypeoplearearound 2 | # Usage: docker build -t howmanypeoplearearound . 3 | 4 | FROM python:3 5 | 6 | LABEL "repo"="https://github.com/schollz/howmanypeoplearearound" 7 | 8 | RUN apt-get update \ 9 | && apt-get upgrade --yes \ 10 | && DEBIAN_FRONTEND=noninteractive apt-get install -y tshark \ 11 | && yes | dpkg-reconfigure -f noninteractive wireshark-common \ 12 | && addgroup wireshark \ 13 | && usermod -a -G wireshark ${USER:-root} \ 14 | && newgrp wireshark \ 15 | && pip install howmanypeoplearearound \ 16 | && echo "===========================================================================================" \ 17 | && echo "Please type: docker run -it --net=host --name howmanypeoplearearound howmanypeoplearearound" 18 | 19 | CMD [ "howmanypeoplearearound" ] 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Zack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # howmanypeoplearearound 3 | 4 | Count the number of people around you :family_man_man_boy: by monitoring wifi signals :satellite:. 5 | 6 | *howmanypeoplearearound* calculates the number of people in the vicinity 7 | using the approximate number of smartphones as a proxy (since [~70% of people have smartphones nowadays](https://twitter.com/conradhackett/status/701798230619590656)). 8 | A cellphone is determined to be in proximity to the computer based on sniffing WiFi probe 9 | requests. Possible uses of *howmanypeoplearearound* include: monitoring foot traffic in your house 10 | with Raspberry Pis, seeing if your roommates are home, etc. 11 | 12 | Tested on Linux (Raspbian and Ubuntu) and Mac OS X. 13 | 14 | ### **It may be illegal** to monitor networks for MAC addresses, especially on networks that *you do not own*. Please check your country's laws (for US [Section 18 U.S. Code § 2511](https://www.law.cornell.edu/uscode/text/18/2511)) - [discussion](https://github.com/schollz/howmanypeoplearearound/issues/4). 15 | 16 | Getting started 17 | =============== 18 | 19 | For a video walkthrough on how to install, checkout [PNPtutorials](https://youtu.be/dLzouUfJyMM?t=3m2s). 20 | 21 | ## Dependencies 22 | 23 | Python 2.7 or preferably Python 3 must be installed on your machine with the `pip` command also available. 24 | ``` 25 | python -V 26 | pip -V 27 | ``` 28 | 29 | ### WiFi adapter that supports monitor mode 30 | 31 | There are a number of possible USB WiFi adapters that support monitor mode. Here's a list that are popular: 32 | 33 | - [USB Rt3070 $14](https://www.amazon.com/gp/product/B00NAXX40C/ref=as_li_tl?ie=UTF8&tag=scholl-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B00NAXX40C&linkId=b72d3a481799c15e483ea93c551742f4) 34 | - [Panda PAU5 $14](https://www.amazon.com/gp/product/B00EQT0YK2/ref=as_li_tl?ie=UTF8&tag=scholl-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B00EQT0YK2&linkId=e5b954672d93f1e9ce9c9981331515c4) 35 | - [Panda PAU6 $15](https://www.amazon.com/gp/product/B00JDVRCI0/ref=as_li_tl?ie=UTF8&tag=scholl-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B00JDVRCI0&linkId=e73e93e020941cada0e64b92186a2546) 36 | - [Panda PAU9 $36](https://www.amazon.com/gp/product/B01LY35HGO/ref=as_li_tl?ie=UTF8&tag=scholl-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B01LY35HGO&linkId=e63f3beda9855abd59009d6173234918) 37 | - [Alfa AWUSO36NH $33](https://www.amazon.com/gp/product/B0035APGP6/ref=as_li_tl?ie=UTF8&tag=scholl-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B0035APGP6&linkId=b4e25ba82357ca6f1a33cb23941befb3) 38 | - [Alfa AWUS036NHA $40](https://www.amazon.com/gp/product/B004Y6MIXS/ref=as_li_tl?ie=UTF8&tag=scholl-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B004Y6MIXS&linkId=0277ca161967134a7f75dd7b3443bded) 39 | - [Alfa AWUS036NEH $40](https://www.amazon.com/gp/product/B0035OCVO6/ref=as_li_tl?ie=UTF8&tag=scholl-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B0035OCVO6&linkId=bd45697540120291a2f6e169dcf81b96) 40 | - [Sabrent NT-WGHU $15 (b/g) only](https://www.amazon.com/gp/product/B003EVO9U4/ref=as_li_tl?ie=UTF8&tag=scholl-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B003EVO9U4&linkId=06d4784d38b6bcef5957f3f6e74af8c8) 41 | 42 | Namely you want to find a USB adapter with one of the following chipsets: Atheros AR9271, Ralink RT3070, Ralink RT3572, or Ralink RT5572. 43 | 44 | ### Mac OS X 45 | ``` 46 | brew install wireshark 47 | brew cask install wireshark-chmodbpf 48 | ``` 49 | 50 | You need to dissociate from any AP before initiating the scanning: 51 | ``` 52 | sudo /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -z 53 | ``` 54 | 55 | ### Linux [tshark](https://www.wireshark.org/docs/man-pages/tshark.html) 56 | ``` 57 | sudo apt-get install tshark 58 | ``` 59 | 60 | Then update it so it can be run as non-root: 61 | ``` 62 | sudo dpkg-reconfigure wireshark-common (select YES) 63 | sudo usermod -a -G wireshark ${USER:-root} 64 | newgrp wireshark 65 | ``` 66 | 67 | ## Install 68 | ``` 69 | pip install howmanypeoplearearound 70 | ``` 71 | 72 | 73 | ## Run 74 | 75 | ### Quickstart 76 | 77 | To run, simply type in 78 | ```bash 79 | $ howmanypeoplearearound 80 | Using wlan1 adapter and scanning for 60 seconds... 81 | [==================================================] 100% 0s left 82 | There are about 3 people around. 83 | ``` 84 | 85 | You will be prompted for the WiFi adapter to use for scanning. Make sure to use 86 | an adapter that supports "monitor" mode. 87 | 88 | ### Docker alternative 89 | 90 | If Docker is installed locally and you want to take *howmanypeoplearearound* out for a quick spin, you can try the following: 91 | 1. Copy Dockerfile from this repo in your current working directory 92 | 2. `docker build -t howmanypeoplearearound .` # that . at the end is important 93 | 3. `docker run -it --net=host --name howmanypeoplearearound howmanypeoplearearound` 94 | 95 | NOTE: This Docker alternative is known to work on Ubuntu but *not* on Mac OS X. Feedback on other platforms would be appreciated. 96 | 97 | 98 | ### Options 99 | 100 | You can modify the scan time, designate the adapter, or modify the output using some command-line options. 101 | ```bash 102 | $ howmanypeoplearearound --help 103 | 104 | Options: 105 | -a, --adapter TEXT adapter to use 106 | -z, --analyze TEXT analyze file 107 | -s, --scantime TEXT time in seconds to scan 108 | -o, --out TEXT output cellphone data to file 109 | -v, --verbose verbose mode 110 | --number just print the number 111 | -j, --jsonprint print JSON of cellphone data 112 | -n, --nearby only quantify signals that are nearby (rssi > -70) 113 | --nocorrection do not apply correction 114 | --loop loop forever 115 | --sort sort cellphone data by distance (rssi) 116 | ``` 117 | 118 | ### Print JSON 119 | 120 | You can generate an JSON-formatted output to see what kind of phones are around: 121 | ```bash 122 | $ howmanypeoplearearound -o test.json -a wlan1 123 | [==================================================] 100% 0s left 124 | There are about 4 people around. 125 | $ cat test.json | python3 -m json.tool 126 | [ 127 | { 128 | "rssi": -86.0, 129 | "mac": "90:e7:c4:xx:xx:xx", 130 | "company": "HTC Corporation" 131 | }, 132 | { 133 | "rssi": -84.0, 134 | "mac": "80:e6:50:xx:xx:xx", 135 | "company": "Apple, Inc." 136 | }, 137 | { 138 | "rssi": -49.0, 139 | "mac": "ac:37:43:xx:xx:xx", 140 | "company": "HTC Corporation" 141 | } 142 | ] 143 | ``` 144 | 145 | A higher rssi means closer (one of these phones is mine, and the other two are my roommates' who were upstairs). 146 | 147 | ### Run forever 148 | 149 | You can add `--loop` to make this run forever and append new lines an output file, `test.json`: 150 | ```bash 151 | $ howmanypeoplearearound -o test.json -a wlan1 --loop 152 | ``` 153 | 154 | ### Visualize 155 | 156 | You can visualize the output from a looped command via a browser using: 157 | ```bash 158 | $ howmanypeoplearearound --analyze test.json 159 | Wrote index.html 160 | Open browser to http://localhost:8001 161 | Type Ctl+C to exit 162 | ``` 163 | 164 | Then just open up `index.html` in a browser and you should see plots. The first plot shows the number of people over time. Here you can see that people start arriving at work place around 8-9am (when work starts!). 165 | 166 | ![newplot](https://cloud.githubusercontent.com/assets/6550035/26174159/b478b764-3b0b-11e7-9600-2aa215b789d0.png) 167 | 168 | The second plot shows the RSSI values for the mac addresses seen. You can double-click on one of them in particular to highlight that trajectory, as I have done here for my phone (you can see when I leave from and when I arrive to work!): 169 | 170 | ![newplot 1](https://cloud.githubusercontent.com/assets/6550035/26174160/b4911ae8-3b0b-11e7-93b2-92c3efaa01aa.png) 171 | 172 | 173 | How does it work? 174 | ================== 175 | 176 | *howmanypeoplearearound* counts up the number of probe requests coming from cellphones in a given amount of time. 177 | The probe requests can be "sniffed" from a monitor-mode enabled WiFi adapter using `tshark`. An accurate count does 178 | depend on everyone having cellphone and also scanning long enough (1 - 10 minutes) to capture the packet when 179 | a phone pings the WiFi network (which happens every 1 to 10 minutes unless the phone is off or WiFi is disabled). 180 | 181 | This is a simplification of another program I wrote, [find-lf](https://github.com/schollz/find-lf) which uses a similar idea with a cluster of Raspberry Pis to geolocate positions of cellphones within the vicinity. 182 | 183 | License 184 | ======= 185 | 186 | MIT 187 | -------------------------------------------------------------------------------- /howmanypeoplearearound/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schollz/howmanypeoplearearound/b05e06a78c8182d2a92a5a817ba5ad3cb373ee10/howmanypeoplearearound/__init__.py -------------------------------------------------------------------------------- /howmanypeoplearearound/__main__.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import sys 3 | import os 4 | import os.path 5 | import platform 6 | import subprocess 7 | import json 8 | import time 9 | 10 | import netifaces 11 | import click 12 | 13 | from howmanypeoplearearound.oui import load_dictionary, download_oui 14 | from howmanypeoplearearound.analysis import analyze_file 15 | from howmanypeoplearearound.colors import * 16 | 17 | if os.name != 'nt': 18 | from pick import pick 19 | import curses 20 | 21 | def which(program): 22 | """Determines whether program exists 23 | """ 24 | def is_exe(fpath): 25 | return os.path.isfile(fpath) and os.access(fpath, os.X_OK) 26 | 27 | fpath, fname = os.path.split(program) 28 | if fpath: 29 | if is_exe(program): 30 | return program 31 | else: 32 | for path in os.environ["PATH"].split(os.pathsep): 33 | path = path.strip('"') 34 | exe_file = os.path.join(path, program) 35 | if is_exe(exe_file): 36 | return exe_file 37 | raise 38 | 39 | 40 | def showTimer(timeleft): 41 | """Shows a countdown timer""" 42 | total = int(timeleft) * 10 43 | for i in range(total): 44 | sys.stdout.write('\r') 45 | # the exact output you're looking for: 46 | timeleft_string = '%ds left' % int((total - i + 1) / 10) 47 | if (total - i + 1) > 600: 48 | timeleft_string = '%dmin %ds left' % ( 49 | int((total - i + 1) / 600), int((total - i + 1) / 10 % 60)) 50 | sys.stdout.write("[%-50s] %d%% %15s" % 51 | ('=' * int(50.5 * i / total), 101 * i / total, timeleft_string)) 52 | sys.stdout.flush() 53 | time.sleep(0.1) 54 | print("") 55 | 56 | def fileToMacSet(path): 57 | with open(path, 'r') as f: 58 | maclist = f.readlines() 59 | return set([x.strip() for x in maclist]) 60 | 61 | @click.command() 62 | @click.option('-a', '--adapter', default='', help='adapter to use') 63 | @click.option('-z', '--analyze', default='', help='analyze file') 64 | @click.option('-s', '--scantime', default='60', help='time in seconds to scan') 65 | @click.option('-o', '--out', default='', help='output cellphone data to file') 66 | @click.option('-d', '--dictionary', default='oui.txt', help='OUI dictionary') 67 | @click.option('-v', '--verbose', help='verbose mode', is_flag=True) 68 | @click.option('--number', help='just print the number', is_flag=True) 69 | @click.option('-j', '--jsonprint', help='print JSON of cellphone data', is_flag=True) 70 | @click.option('-n', '--nearby', help='only quantify signals that are nearby (rssi > -70)', is_flag=True) 71 | @click.option('--allmacaddresses', help='do not check MAC addresses against the OUI database to only recognize known cellphone manufacturers', is_flag=True) # noqa 72 | @click.option('-m', '--manufacturers', default='', help='read list of known manufacturers from file') 73 | @click.option('--nocorrection', help='do not apply correction', is_flag=True) 74 | @click.option('--loop', help='loop forever', is_flag=True) 75 | @click.option('--port', default=8001, help='port to use when serving analysis') 76 | @click.option('--sort', help='sort cellphone data by distance (rssi)', is_flag=True) 77 | @click.option('--targetmacs', help='read a file that contains target MAC addresses', default='') 78 | @click.option('-f', '--pcap', help='read a pcap file instead of capturing') 79 | def main(adapter, scantime, verbose, dictionary, number, nearby, jsonprint, out, allmacaddresses, manufacturers, nocorrection, loop, analyze, port, sort, targetmacs, pcap): 80 | if analyze != '': 81 | analyze_file(analyze, port) 82 | return 83 | if loop: 84 | while True: 85 | adapter = scan(adapter, scantime, verbose, dictionary, number, 86 | nearby, jsonprint, out, allmacaddresses, manufacturers, nocorrection, loop, sort, targetmacs, pcap) 87 | else: 88 | scan(adapter, scantime, verbose, dictionary, number, 89 | nearby, jsonprint, out, allmacaddresses, manufacturers, nocorrection, loop, sort, targetmacs, pcap) 90 | 91 | 92 | def scan(adapter, scantime, verbose, dictionary, number, nearby, jsonprint, out, allmacaddresses, manufacturers, nocorrection, loop, sort, targetmacs, pcap): 93 | """Monitor wifi signals to count the number of people around you""" 94 | 95 | # print("OS: " + os.name) 96 | # print("Platform: " + platform.system()) 97 | 98 | if (not os.path.isfile(dictionary)) or (not os.access(dictionary, os.R_OK)): 99 | download_oui(dictionary) 100 | 101 | oui = load_dictionary(dictionary) 102 | 103 | if not oui: 104 | print('couldn\'t load [%s]' % dictionary) 105 | sys.exit(1) 106 | 107 | try: 108 | tshark = which("tshark") 109 | except: 110 | if platform.system() != 'Darwin': 111 | print('tshark not found, install using\n\napt-get install tshark\n') 112 | else: 113 | print('wireshark not found, install using: \n\tbrew install wireshark') 114 | print( 115 | 'you may also need to execute: \n\tbrew cask install wireshark-chmodbpf') 116 | sys.exit(1) 117 | 118 | if jsonprint: 119 | number = True 120 | if number: 121 | verbose = False 122 | 123 | if not pcap: 124 | if len(adapter) == 0: 125 | if os.name == 'nt': 126 | print('You must specify the adapter with -a ADAPTER') 127 | print('Choose from the following: ' + 128 | ', '.join(netifaces.interfaces())) 129 | sys.exit(1) 130 | title = 'Please choose the adapter you want to use: ' 131 | try: 132 | adapter, index = pick(netifaces.interfaces(), title) 133 | except curses.error as e: 134 | print('Please check your $TERM settings: %s' % (e)) 135 | sys.exit(1) 136 | 137 | print("Using %s adapter and scanning for %s seconds..." % 138 | (adapter, scantime)) 139 | 140 | if not number: 141 | # Start timer 142 | t1 = threading.Thread(target=showTimer, args=(scantime,)) 143 | t1.daemon = True 144 | t1.start() 145 | 146 | dump_file = '/tmp/tshark-temp' 147 | # Scan with tshark 148 | command = [tshark, '-I', '-i', adapter, '-a', 149 | 'duration:' + scantime, '-w', dump_file] 150 | if verbose: 151 | print(' '.join(command)) 152 | run_tshark = subprocess.Popen( 153 | command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 154 | stdout, nothing = run_tshark.communicate() 155 | 156 | 157 | if not number: 158 | t1.join() 159 | else: 160 | dump_file = pcap 161 | 162 | # Read tshark output 163 | command = [ 164 | tshark, '-r', 165 | dump_file, '-T', 166 | 'fields', '-e', 167 | 'wlan.sa', '-e', 168 | 'wlan.bssid', '-e', 169 | 'radiotap.dbm_antsignal' 170 | ] 171 | if verbose: 172 | print(' '.join(command)) 173 | run_tshark = subprocess.Popen( 174 | command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 175 | output, nothing = run_tshark.communicate() 176 | 177 | # read target MAC address 178 | targetmacset = set() 179 | if targetmacs != '': 180 | targetmacset = fileToMacSet(targetmacs) 181 | 182 | foundMacs = {} 183 | for line in output.decode('utf-8').split('\n'): 184 | if verbose: 185 | print(line) 186 | if line.strip() == '': 187 | continue 188 | mac = line.split()[0].strip().split(',')[0] 189 | dats = line.split() 190 | if len(dats) == 3: 191 | if ':' not in dats[0] or len(dats) != 3: 192 | continue 193 | if mac not in foundMacs: 194 | foundMacs[mac] = [] 195 | dats_2_split = dats[2].split(',') 196 | if len(dats_2_split) > 1: 197 | rssi = float(dats_2_split[0]) / 2 + float(dats_2_split[1]) / 2 198 | else: 199 | rssi = float(dats_2_split[0]) 200 | foundMacs[mac].append(rssi) 201 | 202 | if not foundMacs: 203 | print("Found no signals, are you sure %s supports monitor mode?" % adapter) 204 | sys.exit(1) 205 | 206 | for key, value in foundMacs.items(): 207 | foundMacs[key] = float(sum(value)) / float(len(value)) 208 | 209 | # Find target MAC address in foundMacs 210 | if targetmacset: 211 | sys.stdout.write(RED) 212 | for mac in foundMacs: 213 | if mac in targetmacset: 214 | print("Found MAC address: %s" % mac) 215 | print("rssi: %s" % str(foundMacs[mac])) 216 | sys.stdout.write(RESET) 217 | 218 | if manufacturers: 219 | f = open(manufacturers,'r') 220 | cellphone = [line.rstrip('\n') for line in f.readlines()] 221 | f.close() 222 | else: 223 | cellphone = [ 224 | 'Motorola Mobility LLC, a Lenovo Company', 225 | 'GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD', 226 | 'Huawei Symantec Technologies Co.,Ltd.', 227 | 'Microsoft', 228 | 'HTC Corporation', 229 | 'Samsung Electronics Co.,Ltd', 230 | 'SAMSUNG ELECTRO-MECHANICS(THAILAND)', 231 | 'BlackBerry RTS', 232 | 'LG ELECTRONICS INC', 233 | 'Apple, Inc.', 234 | 'LG Electronics', 235 | 'OnePlus Tech (Shenzhen) Ltd', 236 | 'Xiaomi Communications Co Ltd', 237 | 'LG Electronics (Mobile Communications)'] 238 | 239 | cellphone_people = [] 240 | for mac in foundMacs: 241 | oui_id = 'Not in OUI' 242 | if mac[:8] in oui: 243 | oui_id = oui[mac[:8]] 244 | if verbose: 245 | print(mac, oui_id, oui_id in cellphone) 246 | if allmacaddresses or oui_id in cellphone: 247 | if not nearby or (nearby and foundMacs[mac] > -70): 248 | cellphone_people.append( 249 | {'company': oui_id, 'rssi': foundMacs[mac], 'mac': mac}) 250 | if sort: 251 | cellphone_people.sort(key=lambda x: x['rssi'], reverse=True) 252 | if verbose: 253 | print(json.dumps(cellphone_people, indent=2)) 254 | 255 | # US / Canada: https://twitter.com/conradhackett/status/701798230619590656 256 | percentage_of_people_with_phones = 0.7 257 | if nocorrection: 258 | percentage_of_people_with_phones = 1 259 | num_people = int(round(len(cellphone_people) / 260 | percentage_of_people_with_phones)) 261 | 262 | if number and not jsonprint: 263 | print(num_people) 264 | elif jsonprint: 265 | print(json.dumps(cellphone_people, indent=2)) 266 | else: 267 | if num_people == 0: 268 | print("No one around (not even you!).") 269 | elif num_people == 1: 270 | print("No one around, but you.") 271 | else: 272 | print("There are about %d people around." % num_people) 273 | 274 | if out: 275 | with open(out, 'a') as f: 276 | data_dump = {'cellphones': cellphone_people, 'time': time.time()} 277 | f.write(json.dumps(data_dump) + "\n") 278 | if verbose: 279 | print("Wrote %d records to %s" % (len(cellphone_people), out)) 280 | if not pcap: 281 | os.remove(dump_file) 282 | return adapter 283 | 284 | 285 | if __name__ == '__main__': 286 | main() 287 | -------------------------------------------------------------------------------- /howmanypeoplearearound/analysis.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import datetime 3 | import json 4 | import sys 5 | 6 | from howmanypeoplearearound.plotlyjs import * 7 | 8 | 9 | def analyze_file(fname, port): 10 | lines = [] 11 | with open(fname, 'r') as f: 12 | for line in f: 13 | try: 14 | lines.append(json.loads(line)) 15 | except: 16 | pass 17 | lines = sorted(lines, key=lambda k: k['time']) 18 | macs_to_add = [] 19 | for data in lines: 20 | for c in data['cellphones']: 21 | if c['rssi'] > -80 and c['mac'] not in macs_to_add: 22 | macs_to_add.append(c['mac']) 23 | mac_data = {mac: {'y': []} for mac in macs_to_add} 24 | num = {'x': [], 'y': []} 25 | for data in lines: 26 | rssi = {} 27 | for mac in macs_to_add: 28 | rssi[mac] = -100 29 | for c in data['cellphones']: 30 | if c['mac'] in rssi: 31 | rssi[c['mac']] = c['rssi'] 32 | for mac in mac_data: 33 | mac_data[mac]['y'].append(str(rssi[mac] + 100)) 34 | num['x'].append("'" + datetime.datetime.fromtimestamp( 35 | data['time']).isoformat().split('.')[0].replace('T', ' ') + "'") 36 | num['y'].append(str(len(data['cellphones']))) 37 | 38 | mac_names = copy.deepcopy(macs_to_add) 39 | for i, mac in enumerate(mac_names): 40 | mac_names[i] = 'mac' + mac.replace(':', '') 41 | 42 | # remove pings 43 | for mac in mac_data: 44 | for i, y in enumerate(mac_data[mac]['y']): 45 | if y == "0" and i > 2: 46 | if mac_data[mac]['y'][i - 3] == "0" and (mac_data[mac]['y'][i - 1] != "0" or mac_data[mac]['y'][i - 2] != "0"): 47 | mac_data[mac]['y'][i - 1] = "0" 48 | mac_data[mac]['y'][i - 2] = "0" 49 | 50 | js = "" 51 | js += ('timex = [%s]' % ', '.join(num['x'])) 52 | for i, mac in enumerate(macs_to_add): 53 | js += ('\nvar %s = {' % mac_names[i]) 54 | js += ('\n x: timex,') 55 | js += ('\n y: [%s],' % ', '.join(mac_data[mac]['y'])) 56 | js += ("\n name: '%s', mode: 'lines', type:'scatter' };\n\n" % mac) 57 | js += ('\n\nvar data = [%s];' % ', '.join(mac_names)) 58 | js += ("\n\nPlotly.newPlot('myDiv',data,layout2);") 59 | js += ('\nvar num_cellphones = {') 60 | js += ('\n x: timex,') 61 | js += ('\n y: [%s],' % ', '.join(num['y'])) 62 | js += ("\n name: 'N', mode: 'lines', type:'scatter' };\n\n") 63 | js += ("\n\nPlotly.newPlot('myDiv2',[num_cellphones],layout1);") 64 | 65 | with open('index.html', 'w') as f: 66 | f.write(""" 67 | 68 | 69 | 70 | 71 | 72 |
73 | 74 |
75 | 76 |
77 | 78 |
79 | 120 | """ % (js)) 121 | print("Wrote index.html") 122 | print("Open browser to http://localhost:" + str(port)) 123 | print("Type Ctl+C to exit") 124 | if sys.version_info >= (3, 0): 125 | # Python 3 code in this block 126 | from http.server import HTTPServer, SimpleHTTPRequestHandler 127 | httpd = HTTPServer(('localhost', port), SimpleHTTPRequestHandler) 128 | httpd.serve_forever() 129 | else: 130 | # Python 2 code in this block 131 | import SimpleHTTPServer 132 | import SocketServer 133 | httpd = SocketServer.TCPServer(("", port), SimpleHTTPServer.SimpleHTTPRequestHandler) 134 | httpd.serve_forever() 135 | -------------------------------------------------------------------------------- /howmanypeoplearearound/colors.py: -------------------------------------------------------------------------------- 1 | # Output colored text in terminal 2 | # Define colors 3 | RED = "\033[1;31m" 4 | BLUE = "\033[1;34m" 5 | CYAN = "\033[1;36m" 6 | GREEN = "\033[0;32m" 7 | RESET = "\033[0;0m" 8 | BOLD = "\033[;1m" 9 | REVERSE = "\033[;7m" -------------------------------------------------------------------------------- /howmanypeoplearearound/oui.py: -------------------------------------------------------------------------------- 1 | try: #python3 2 | from urllib.request import urlopen 3 | except: #python2 4 | from urllib2 import urlopen 5 | 6 | 7 | def load_dictionary(file): 8 | oui = {} 9 | with open(file, 'r') as f: 10 | for line in f: 11 | if '(hex)' in line: 12 | data = line.split('(hex)') 13 | key = data[0].replace('-', ':').lower().strip() 14 | company = data[1].strip() 15 | oui[key] = company 16 | return oui 17 | 18 | 19 | def download_oui(to_file): 20 | uri = 'http://standards-oui.ieee.org/oui/oui.txt' 21 | print("Trying to download current version of oui.txt from [%s] to file [%s]" % (uri, to_file)) 22 | oui_data = urlopen(uri, timeout=10).read() 23 | with open(to_file, 'wb') as oui_file: 24 | oui_file.write(oui_data) 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='howmanypeoplearearound', 5 | packages=['howmanypeoplearearound'], 6 | version='0.5.0', 7 | description='A tshark wrapper to count the number of cellphones in the vicinity', 8 | author='schollz', 9 | url='https://github.com/schollz/howmanypeoplearearound', 10 | author_email='hypercube.platforms@gmail.com', 11 | download_url='https://github.com/schollz/howmanypeoplearearound/archive/v0.5.0.tar.gz', 12 | keywords=['tshark', 'wifi', 'location'], 13 | classifiers=[], 14 | install_requires=[ 15 | "click", 16 | "netifaces", 17 | "pick", 18 | ], 19 | setup_requires=[], 20 | tests_require=[], 21 | entry_points={'console_scripts': [ 22 | 'howmanypeoplearearound = howmanypeoplearearound.__main__:main', 23 | ], }, 24 | ) 25 | --------------------------------------------------------------------------------