diff --git a/client/build/index.html b/client/build/index.html
new file mode 100644
index 0000000..3b18e51
--- /dev/null
+++ b/client/build/index.html
@@ -0,0 +1 @@
+hello world
diff --git a/server/.gitignore b/server/.gitignore
new file mode 100644
index 0000000..947f30e
--- /dev/null
+++ b/server/.gitignore
@@ -0,0 +1,105 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+
+# Editor
+*.swp
+*.swo
+
+paramiko.log
diff --git a/server/capture.py b/server/capture.py
new file mode 100644
index 0000000..262e14e
--- /dev/null
+++ b/server/capture.py
@@ -0,0 +1,30 @@
+import socket
+import sys
+import time
+
+# make sure multicast is being routed to the right interface ie.
+# sudo route add -net 224.0.0.0 netmask 240.0.0.0 dev enx00249b649e67
+
+def trigger_capture():
+ charid = 1
+ unitid = 1
+ groupid = 1
+
+ gtdate = time.gmtime()
+ now = str(gtdate.tm_year) + str(gtdate.tm_mon) + str(gtdate.tm_mday) + str(gtdate.tm_hour) + str(gtdate.tm_min) + str(gtdate.tm_sec)
+
+ SDATA = str(now)
+
+ print('Sending: ' + SDATA)
+ MCAST_GRP = '224.1.1.1'
+ MCAST_PORT = 5007
+ SCMD = chr(charid)
+ SUNIT = chr(unitid)
+ SGROUP = chr(groupid)
+ SEND = SCMD+SUNIT+SGROUP+SDATA
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+ dev = 'eth0' + '\0'
+ sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
+ sock.sendto(SEND.encode('utf-8'), (MCAST_GRP, MCAST_PORT))
+ sock.close()
+ print('Sent.')
diff --git a/server/download.py b/server/download.py
new file mode 100644
index 0000000..5ad784b
--- /dev/null
+++ b/server/download.py
@@ -0,0 +1,42 @@
+import os
+import settings
+import threading
+import paramiko
+import time
+paramiko.util.log_to_file('paramiko.log')
+
+def download(ip, dest):
+ print('Downloading from', ip)
+
+ port = 22
+ transport = paramiko.Transport((ip, port))
+ transport.connect(None, settings.RASPBERRY_USER, settings.RASPBERRY_PASS)
+
+ sftp = paramiko.SFTPClient.from_transport(transport)
+
+ files = sftp.listdir('/3dscan/')
+
+ for f in files:
+ source_file = '/3dscan/' + f
+ dest_file = dest + f
+ print('Grabbing file', source_file)
+ sftp.get(source_file, dest_file)
+ sftp.remove(source_file)
+
+ if sftp: sftp.close()
+ if transport: transport.close()
+
+ print('Finished downloading from', ip)
+
+def download_all_photos(dest):
+ if not dest.endswith('/'):
+ raise Exception('Destination must end with /')
+
+ if not os.path.exists(dest):
+ raise Exception('Destination does not exist')
+
+ print('Downloading all photos to', dest)
+
+ for ip in settings.RASPBERRY_IPS:
+ t = threading.Thread(target=download, args=(ip, dest))
+ t.start()
diff --git a/server/main.py b/server/main.py
new file mode 100644
index 0000000..aabbdd4
--- /dev/null
+++ b/server/main.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask_cors import CORS
+
+build_folder = '../client/build'
+app = Flask(__name__, static_folder=build_folder, static_url_path='')
+CORS(app)
+
+@app.route('/')
+def index():
+ return app.send_static_file('index.html')
+
+app.run()
diff --git a/server/power.py b/server/power.py
new file mode 100644
index 0000000..5710e23
--- /dev/null
+++ b/server/power.py
@@ -0,0 +1,51 @@
+import requests
+import settings
+import time
+
+def np_02B_api(ip, username, password, is_on):
+ if is_on:
+ endpoint = '/cmd.cgi?grp=0'
+ else:
+ endpoint = '/cmd.cgi?grp=30'
+
+ url = 'http://' + ip + endpoint
+ r = requests.get(url, auth=(username, password), timeout=4)
+ r.raise_for_status()
+
+def lights_on():
+ np_02B_api(settings.LIGHT_IP, settings.LIGHT_USER, settings.LIGHT_PASS, True)
+
+def lights_off():
+ np_02B_api(settings.LIGHT_IP, settings.LIGHT_USER, settings.LIGHT_PASS, False)
+
+def grid_on():
+ np_02B_api(settings.GRID_IP, settings.GRID_USER, settings.GRID_PASS, True)
+
+def grid_off():
+ np_02B_api(settings.GRID_IP, settings.GRID_USER, settings.GRID_PASS, False)
+
+
+if __name__ == '__main__':
+ try:
+ print('Turning lights on...')
+ lights_on()
+
+ print('Waiting three seconds...')
+ time.sleep(3)
+
+ print('Turning lights off...')
+ lights_off()
+ except BaseException as e:
+ print('Problem with lights: {} - {}'.format(e.__class__.__name__, str(e)))
+
+ try:
+ print('Turning grid on...')
+ grid_on()
+
+ print('Waiting three seconds...')
+ time.sleep(3)
+
+ print('Turning grid off...')
+ grid_off()
+ except BaseException as e:
+ print('Problem with grid: {} - {}'.format(e.__class__.__name__, str(e)))
diff --git a/server/requirements.txt b/server/requirements.txt
new file mode 100644
index 0000000..167fbfb
--- /dev/null
+++ b/server/requirements.txt
@@ -0,0 +1,5 @@
+certifi==2020.12.5
+chardet==4.0.0
+idna==2.10
+requests==2.25.1
+urllib3==1.26.3
diff --git a/server/settings.py b/server/settings.py
new file mode 100644
index 0000000..648a02c
--- /dev/null
+++ b/server/settings.py
@@ -0,0 +1,101 @@
+LIGHT_IP = '192.168.99.25'
+LIGHT_USER = 'admin'
+LIGHT_PASS = 'admin'
+
+GRID_IP = '192.168.99.20'
+GRID_USER = 'admin'
+GRID_PASS = 'admin'
+
+RASPBERRY_USER = 'root'
+RASPBERRY_PASS = '3dscan'
+RASPBERRY_IPS = [
+ '192.168.99.101',
+ '192.168.99.103',
+ '192.168.99.104',
+ '192.168.99.105',
+ '192.168.99.106',
+ '192.168.99.107',
+ '192.168.99.108',
+ '192.168.99.109',
+ '192.168.99.110',
+ '192.168.99.111',
+ '192.168.99.112',
+ '192.168.99.113',
+ '192.168.99.114',
+ '192.168.99.115',
+ '192.168.99.116',
+ '192.168.99.117',
+ '192.168.99.118',
+ '192.168.99.119',
+ '192.168.99.120',
+ '192.168.99.121',
+ '192.168.99.122',
+ '192.168.99.123',
+ '192.168.99.124',
+ '192.168.99.125',
+ '192.168.99.126',
+ '192.168.99.127',
+ '192.168.99.128',
+ '192.168.99.129',
+ '192.168.99.130',
+ '192.168.99.131',
+ '192.168.99.132',
+ '192.168.99.133',
+ '192.168.99.134',
+ '192.168.99.135',
+ '192.168.99.136',
+ '192.168.99.137',
+ '192.168.99.138',
+ '192.168.99.139',
+ '192.168.99.140',
+ '192.168.99.141',
+ '192.168.99.142',
+ '192.168.99.143',
+ '192.168.99.144',
+ '192.168.99.145',
+ '192.168.99.146',
+ '192.168.99.147',
+ '192.168.99.148',
+ '192.168.99.149',
+ '192.168.99.150',
+ '192.168.99.151',
+ '192.168.99.152',
+ '192.168.99.153',
+ '192.168.99.154',
+ '192.168.99.155',
+ '192.168.99.156',
+ '192.168.99.157',
+ '192.168.99.158',
+ '192.168.99.159',
+ '192.168.99.160',
+ '192.168.99.161',
+ '192.168.99.162',
+ '192.168.99.163',
+ '192.168.99.164',
+ '192.168.99.165',
+ '192.168.99.166',
+ '192.168.99.167',
+ '192.168.99.168',
+ '192.168.99.169',
+ '192.168.99.170',
+ '192.168.99.171',
+ '192.168.99.172',
+ '192.168.99.173',
+ '192.168.99.174',
+ '192.168.99.175',
+ '192.168.99.176',
+ '192.168.99.177',
+ '192.168.99.178',
+ '192.168.99.179',
+ '192.168.99.180',
+ '192.168.99.181',
+ '192.168.99.182',
+ '192.168.99.183',
+ '192.168.99.184',
+ '192.168.99.185',
+ '192.168.99.191',
+ '192.168.99.192',
+ '192.168.99.193',
+ '192.168.99.194',
+ '192.168.99.195',
+]
diff --git a/server/src/main.py b/server/src/main.py
deleted file mode 100644
index 3baf8a9..0000000
--- a/server/src/main.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import socket
-import sys
-import time