Beacon_Receiver.py

Base layer of receiving and decoding beacons (included by the previous two files). Run this if you just want to receive, and not start the web app. It listens for IP broadcasts on the subnet.

import sys
import socket
import struct
import datetime
import os
from threading import Thread
import binascii
import sqlite3
import traceback
import requests
import json
import time

# ---------- Configurable Options ------------

beacon_listening_port = 4444
# prox1_ip = '127.0.0.1'
prox1_ip = '44.36.1.193'
prox1_beacon_port = 2222
files_dir = 'Beacon_Files/'
files_dir2 = 'Individual_Beacons/'

# ----------- Global Constants ------------
BEACON_LENGTH = 472
ENCRYPTED_BEACON_LENGTH = 480

BYTE_FLAG_TYPE = "byte_flag"
CHAR_TYPE = "char"
FLOAT_TYPE = "float"
INT_TYPE = "int"
LONG_TYPE = "long"
SHORT_TYPE = "short"
TRUNCATED_FLOAT_TYPE = "truncated_float"

SQLITE_TYPES = {BYTE_FLAG_TYPE : "TEXT",
				CHAR_TYPE : "INT",
				FLOAT_TYPE : "REAL",
				INT_TYPE : "INT",
				LONG_TYPE : "INT",
				SHORT_TYPE : "INT",
				TRUNCATED_FLOAT_TYPE : "REAL"}

# tuple format: (field_name, num_bytes, data_type)
BEACON_FIELDS = [
	("beacon_sequence", 2, SHORT_TYPE),
	("GPStime", 4, INT_TYPE),
	("r_P1_0", 4, FLOAT_TYPE),
	("r_P1_1", 4, FLOAT_TYPE),
	("r_P1_2", 4, FLOAT_TYPE),
	("v_P1_0", 4, FLOAT_TYPE),
	("v_P1_1", 4, FLOAT_TYPE),
	("v_P1_2", 4, FLOAT_TYPE),
	("w_P1_0", 4, FLOAT_TYPE),
	("w_P1_1", 4, FLOAT_TYPE),
	("w_P1_2", 4, FLOAT_TYPE),
	("q_P1_0", 4, FLOAT_TYPE),
	("q_P1_1", 4, FLOAT_TYPE),
	("q_P1_2", 4, FLOAT_TYPE),
	("q_P1_3", 4, FLOAT_TYPE),
	("r_rel_0", 4, FLOAT_TYPE),
	("r_rel_1", 4, FLOAT_TYPE),
	("r_rel_2", 4, FLOAT_TYPE),
	("v_rel_0", 4, FLOAT_TYPE),
	("v_rel_1", 4, FLOAT_TYPE),
	("v_rel_2", 4, FLOAT_TYPE),
	("TargetLastSeen", 4, INT_TYPE),
	("gnc_flags", 1, BYTE_FLAG_TYPE),
	("rx_counter", 1, CHAR_TYPE),
	("mission_timer", 4, INT_TYPE),
	("mission_flags", 1, BYTE_FLAG_TYPE),
	("ppod_status", 1, BYTE_FLAG_TYPE),
	("startup_status", 1, BYTE_FLAG_TYPE),
	("restart_count", 2, SHORT_TYPE),
	("mem_usage", 4, FLOAT_TYPE),
	("load_avg", 4, FLOAT_TYPE),
	("v_5", 4, FLOAT_TYPE),
	("c_5", 4, FLOAT_TYPE),
	("v_28", 4, FLOAT_TYPE),
	("c_28", 4, FLOAT_TYPE),
	("v_BAT", 4, FLOAT_TYPE),
	("c_BAT", 4, FLOAT_TYPE),
	("v_BAT_1", 4, FLOAT_TYPE),
	("c_BAT_1", 4, FLOAT_TYPE),
	("v_BAT_2", 4, FLOAT_TYPE),
	("c_BAT_2", 4, FLOAT_TYPE),
	("v_BAT_3", 4, FLOAT_TYPE),
	("c_BAT_3", 4, FLOAT_TYPE),
	("v_BCR1", 4, FLOAT_TYPE),
	("c_BCR1A", 4, FLOAT_TYPE),
	("c_BCR1B", 4, FLOAT_TYPE),
	("v_BCR2", 4, FLOAT_TYPE),
	("c_BCR2A", 4, FLOAT_TYPE),
	("c_BCR2B", 4, FLOAT_TYPE),
	("v_BCR4", 4, FLOAT_TYPE),
	("c_BCR4A", 4, FLOAT_TYPE),
	("c_BCR4B", 4, FLOAT_TYPE),
	("v_BCR5", 4, FLOAT_TYPE),
	("c_BCR5A", 4, FLOAT_TYPE),
	("c_BCR5B", 4, FLOAT_TYPE),
	("v_BCR6", 4, FLOAT_TYPE),
	("c_BCR6A", 4, FLOAT_TYPE),
	("c_BCR6B", 4, FLOAT_TYPE),
	("v_BCR7", 4, FLOAT_TYPE),
	("c_BCR7A", 4, FLOAT_TYPE),
	("c_BCR7B", 4, FLOAT_TYPE),
	("v_BCR8", 4, FLOAT_TYPE),
	("c_BCR8A", 4, FLOAT_TYPE),
	("c_BCR8B", 4, FLOAT_TYPE),
	("v_BCR9", 4, FLOAT_TYPE),
	("c_BCR9A", 4, FLOAT_TYPE),
	("c_BCR9B", 4, FLOAT_TYPE),
	("c_BAT_DIRECTIONS", 1, BYTE_FLAG_TYPE),
	("switch_status", 4, BYTE_FLAG_TYPE),
	("Prop_Ping", 1, BYTE_FLAG_TYPE),
	("AllSensor_Data", 4, INT_TYPE),
	("MainTank_Temp", 4, FLOAT_TYPE),
	("MainTank_Press", 4, FLOAT_TYPE),
	("Plenum_Temp", 4, FLOAT_TYPE),
	("Plenum_Press", 4, FLOAT_TYPE),
	("Last_Fire", 4, INT_TYPE),
	("TR_Status", 1, BYTE_FLAG_TYPE),
	("ACCEL_X", 4, FLOAT_TYPE),
	("ACCEL_Y", 4, FLOAT_TYPE),
	("ACCEL_Z", 4, FLOAT_TYPE),
	("GYRO_X", 4, FLOAT_TYPE),
	("GYRO_Y", 4, FLOAT_TYPE),
	("GYRO_Z", 4, FLOAT_TYPE),
	("MAGNETOMETER_X_GET", 4, FLOAT_TYPE),
	("MAGNETOMETER_Y_GET", 4, FLOAT_TYPE),
	("MAGNETOMETER_Z_GET", 4, FLOAT_TYPE),
	("ESS1_XYANGLE_GET_0", 4, FLOAT_TYPE),
	("ESS1_XYANGLE_GET_1", 4, FLOAT_TYPE),
	("ESS2_XYANGLE_GET_0", 4, FLOAT_TYPE),
	("ESS2_XYANGLE_GET_1", 4, FLOAT_TYPE),
	("ESS3_XYANGLE_GET_0", 4, FLOAT_TYPE),
	("ESS3_XYANGLE_GET_1", 4, FLOAT_TYPE),
	("ESS4_XYANGLE_GET_0", 4, FLOAT_TYPE),
	("ESS4_XYANGLE_GET_1", 4, FLOAT_TYPE),
	("IMU_SUPPLYVOLTAGE_GET", 4, FLOAT_TYPE),
	("IMU_SYSTEMSTATUS_GET", 2, BYTE_FLAG_TYPE),
	("CMG_Status_0", 4, LONG_TYPE),
	("CMG_Status_1", 4, LONG_TYPE),
	("CMG_Status_2", 4, LONG_TYPE),
	("CMG_Status_3", 4, LONG_TYPE),
	("CMG_Gimbal_position_0", 4, FLOAT_TYPE),
	("CMG_Gimbal_position_1", 4, FLOAT_TYPE),
	("CMG_Gimbal_position_2", 4, FLOAT_TYPE),
	("CMG_Gimbal_position_3", 4, FLOAT_TYPE),
	("CMG_Gimbal_rate_0", 4, FLOAT_TYPE),
	("CMG_Gimbal_rate_1", 4, FLOAT_TYPE),
	("CMG_Gimbal_rate_2", 4, FLOAT_TYPE),
	("CMG_Gimbal_rate_3", 4, FLOAT_TYPE),
	("CMG_Flywheel_rate_0", 4, FLOAT_TYPE),
	("CMG_Flywheel_rate_1", 4, FLOAT_TYPE),
	("CMG_Flywheel_rate_2", 4, FLOAT_TYPE),
	("CMG_Flywheel_rate_3", 4, FLOAT_TYPE),
	("CMG_Checkout_status", 1, BYTE_FLAG_TYPE),
	("TC_Temp_1", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_2", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_3", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_4", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_5", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_6", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_7", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_8", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_9", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_10", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_11", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_12", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_13", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_14", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_15", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_16", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_17", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_18", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_19", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_20", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_21", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_22", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_23", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Temp_24", 2, TRUNCATED_FLOAT_TYPE),
	("TC_Heater_Status", 1, BYTE_FLAG_TYPE),
	("Inst_mem_usage", 2, TRUNCATED_FLOAT_TYPE),
	("Inst_checkout_status", 1, BYTE_FLAG_TYPE),
	("Prop_checkout_status", 1, BYTE_FLAG_TYPE)
	]

#------------ Delegates -----------

def received_beacon(d): return

# ---------- Setup File Directory ----------------

if not os.path.exists(files_dir):
	os.makedirs(files_dir)
	
if not os.path.exists(files_dir+files_dir2):
	os.makedirs(files_dir+files_dir2)
	
# ---------- Setup Database --------------------

def create_gb():
	conn = sqlite3.connect(files_dir+'Beacons.db')
	cursor = conn.cursor()
	try:
		sqlite_fields = ["{} {}".format(field[0], SQLITE_TYPES[field[2]]) for field in BEACON_FIELDS]
		create_query = "CREATE TABLE Beacons (time_received TEXT, {})".format(", ".join(sqlite_fields))
		cursor.execute(create_query)
	except:
		pass
	return (conn,cursor)

# ---------- Setup Log File & Managment ----------------

def log(item):
	logfile = open(files_dir+'WarningLogFile.txt','a')
	logfile.write(str(datetime.datetime.now()) + ': ' + item+'\n')
	logfile.close()
	
slack_counter = 0	
def log_beacon(beacon,conn,cursor):
	filename = beacon['time_received'].replace(' ','_').replace('/','_').replace(':','_')+'_beacon_'+str(beacon['beacon_sequence'])+'.txt'
	logfile = open(files_dir+files_dir2+filename,'w+')
	beacon_fields = ['time_received'] + [field[0] for field in BEACON_FIELDS]
	beacon_str = ' | '.join(["{} : {}".format(field, beacon[field]) for field in beacon_fields])
	logfile.write(beacon_str)
	logfile.close()	
	insert_query = "INSERT INTO Beacons VALUES ({})".format(",".join(["?"]*(len(BEACON_FIELDS) + 1)))
	cursor.execute(insert_query, [beacon['time_received']] + [beacon[field[0]] for field in BEACON_FIELDS])
	conn.commit()
	global slack_counter
	if slack_counter == 4: # every minute
		slack_counter = 0
		payload = {'text':beacon_str}
		data = json.dumps(payload)
		requests.post('https://hooks.slack.com/services/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', data=data)
	slack_counter += 1

# ---------- Receive and Parse Beacons -----------
	
def receive_message(conn, cursor, data):
	index = 0
	beacon_data = {}
	try:
		for (field_name, length, data_type) in BEACON_FIELDS:
			field_data = data[index:index+length]

			if data_type == BYTE_FLAG_TYPE:
				beacon_data[field_name] = str(binascii.hexlify(field_data))
			elif data_type == CHAR_TYPE:
				beacon_data[field_name] = struct.unpack_from('!B', field_data, 0)[0]
			elif data_type == FLOAT_TYPE:
				beacon_data[field_name] = struct.unpack_from('!f', field_data, 0)[0]
			elif data_type == INT_TYPE:
				beacon_data[field_name] = struct.unpack_from('!i', field_data, 0)[0]
			elif data_type == LONG_TYPE:
				beacon_data[field_name] = struct.unpack_from('!l', field_data, 0)[0]
			elif data_type == SHORT_TYPE:
				beacon_data[field_name] = struct.unpack_from('!h', field_data, 0)[0]
			elif data_type == TRUNCATED_FLOAT_TYPE:
				beacon_data[field_name] = (struct.unpack_from('!h', field_data, 0)[0])/100.0

			index = index + length
		beacon_data["time_received"] = datetime.datetime.now().strftime("%m/%d/%y %H:%M:%S.%f")[:-3]
		log_beacon(beacon_data,conn,cursor)
		received_beacon(beacon_data)
		print 'Received Beacon #' + str(beacon_data['beacon_sequence'])
	except Exception as e:
		traceback.print_exc()
		log('Received Malformed Beacon: ' + str(e))
		print 'Received Malformed Beacon'

def receive_messages(sock):
	(conn, cursor) = create_gb()
	while True:
		(data, from_addr) = sock.recvfrom(472)
		if from_addr != (prox1_ip,prox1_beacon_port): 
			print ('Received Packet from Unknown IP Address: ' + str(from_addr) + '.')
			log('Received Packet from Unknown IP Address: ' + str(from_addr) + '.')
			return
		receive_message(conn, cursor, data)


def listen():
	# ---------------------- Setup UDP Socket ----------------
	try:
		sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #second arg = UDP
		sock.bind(('',beacon_listening_port))
		print 'Beacon Receiving Ground Program Running on ' + str(socket.gethostbyname(socket.gethostname())) + ':' + str(beacon_listening_port)
		print 'Receiving Beacons from Prox-1 at ' + prox1_ip + ':' + str(prox1_beacon_port) + '\n\n'
	except Exception as e:
		sys.exit('Socket Error: ' + str(e))
		
	receiving_thread = Thread(target=receive_messages, args=(sock,))
	receiving_thread.setDaemon(True)
	receiving_thread.start()
	return sock


if __name__ == "__main__":
	listen()
	while True:
		time.sleep(1)